Подсистема ввода-вывода: путь запроса от приложения до диска

Рейтинг: 67.6% · 8 голосов
Подробный курс по диагностике и производительности Linux для админов и DevOps: с чего начать, методология USE, логи (journalctl, dmesg), процессы, CPU, память и OOM, диск и IO (iostat), сеть (ss, tcpdump), системные вызовы и strace, ltrace, perf, флеймграфы, ftrace, eBPF и bpftrace, lsof, мониторинг. Разбор каждого инструмента с чтением вывода.
Ответить
Аватара пользователя
Dmitry_SRE
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Подсистема ввода-вывода: путь запроса от приложения до диска

Сообщение Dmitry_SRE »

Оглавление курса (47)
  1. С чего начать диагностику Linux: методология вместо паники
  2. Первые 60 секунд: экспресс-диагностика нагруженного сервера
  3. Методологии диагностики: USE, RED и здравый смысл
  4. Логи systemd через journalctl: где искать причину
  5. Где лежат логи Linux: /var/log, dmesg и rsyslog
  6. Источники правды: load average, /proc и /sys
  7. Процессы Linux: ps, pstree и состояния процессов
  8. Интерактивный мониторинг: top и htop
  9. Метрики процессов во времени: pidstat
  10. Приоритеты и ограничения: nice, ionice, cgroups
  11. Сигналы и зависшие процессы: kill, и что делать с D-state
  12. Загрузка CPU: user, system, iowait, steal и контекст-свитчи
  13. Диагностика CPU по ядрам: vmstat и mpstat
  14. Профилирование CPU: perf top и поиск пожирателя
  15. Частоты, троттлинг и NUMA: turbostat и numastat
  16. Память Linux: RSS, VSZ, page cache и миф о нехватке памяти
  17. Сколько памяти занято: free, /proc/meminfo и vmstat
  18. Поиск утечек и пожирателей памяти: smem, pmap, smaps
  19. Swap и подкачка: swapon, swappiness, когда своп - это боль
  20. OOM killer: кто и за что убил процесс
  21. Память ядра и slab: slabtop и куда уходит RAM
  22. Подсистема ввода-вывода: путь запроса от приложения до диска (вы здесь)
  23. Диагностика диска: iostat и чтение await, %util
  24. Кто грузит диск: iotop и атрибуция io процессам
  25. Файловые системы: df, du, иноды и куда делось место
  26. Глубокая диагностика блочного слоя: blktrace и biolatency
  27. Кеш страниц и грязные данные: dirty pages, fsync, drop_caches
  28. Сетевой стек Linux: путь пакета и где возникают задержки
  29. Сокеты и соединения: ss и netstat
  30. Захват трафика: tcpdump для диагностики сети
  31. Задержки и потери в сети: ping, mtr, диагностика латентности
  32. Сеть как источник проблем: nftables, conntrack, дропы
  33. Что такое системный вызов и зачем его трассировать
  34. strace: трассировка системных вызовов на практике
  35. strace в бою: почему программа висит, падает или тормозит
  36. ltrace: трассировка вызовов библиотек
  37. Накладные расходы трассировки и безопасные альтернативы
  38. perf: универсальный профайлер Linux
  39. Флеймграфы: визуализация профиля производительности
  40. Трассировка ядра: ftrace и trace-cmd
  41. eBPF: революция в наблюдаемости Linux
  42. Инструменты eBPF на практике: BCC и bpftrace
  43. lsof: открытые файлы, дескрипторы, кто держит файл и порт
  44. Разбор кейса: приложение тормозит - пошаговая диагностика
  45. Непрерывный мониторинг: sar, node_exporter, Grafana, Zabbix
  46. Диагностика в контейнерах: cgroups, лимиты, Docker и Kubernetes
  47. Карта инструментов и сквозной разбор инцидента производительности
Знакомая боль: база данных тормозит, сайт еле ворочается, а top показывает, что процессор почти простаивает. Зато в строке load average цифры неприлично большие, а в выводе видна загадочная колонка iowait, которая растет на глазах. Куда копать? В приложение? В файловую систему? В сам диск? Если не понимать, как устроен дисковый io в Linux, ты будешь тыкаться вслепую и менять SSD там, где виноват один кривой fsync в коде.

Этот урок - карта. Не команда-волшебная-палочка, а именно карта местности: по каким слоям проходит запрос на чтение или запись, прежде чем превратиться в реальное движение данных, и где на этом пути возникают задержки. Когда карта в голове есть, выбор инструмента становится очевидным. Болит на уровне приложения - берешь strace или eBPF. Болит на уровне устройства - берешь iostat. Без карты любой инструмент кажется одинаково непонятным набором цифр.

Как устроена io подсистема linux: слои сверху вниз

Представь себе многоэтажку, где запрос на чтение байтов спускается с верхнего этажа в подвал, к железу. Вот эти этажи по порядку.
  • Приложение. Твой код вызывает read(), write(), pread(), pwrite() или open(). Для программы это просто функция, но под капотом это вход в ядро.
  • Системный вызов (syscall). Граница между пользовательским кодом и ядром. Именно здесь strace перехватывает и показывает, что именно просит приложение.
  • VFS (Virtual File System). Слой-переводчик. Он прячет различия между файловыми системами: для VFS и ext4, и xfs, и сетевая NFS выглядят одинаково. Благодаря ему read() работает с любой ФС без переписывания кода.
  • Файловая система - ext4, xfs, btrfs. Знает, в каких блоках на диске лежит твой файл, ведет журнал, отвечает за целостность.
  • Page cache - кэш страниц в оперативной памяти. Критически важный слой. Большая часть чтений сюда и попадает, не доходя до диска вообще.
  • Block layer (блочный слой) - сердце дискового стека. Собирает запросы в очередь, объединяет соседние (merge), упорядочивает их планировщиком io. Здесь работает blk-mq - многоочередная архитектура для современных дисков.
  • Драйвер устройства. Превращает абстрактный block-запрос в команды конкретного контроллера (SATA, SAS, NVMe).
  • Устройство - физический накопитель HDD, SSD или NVMe.
Это и есть концептуальная карта io-стека Linux, тот самый разрез по слоям из карт Брендана Грегга. Запомни направление: чем ниже слой, тем дороже туда добраться, и тем больше там потенциальная задержка. Чтение из page cache - наносекунды-микросекунды. Поход на NVMe - десятки микросекунд. Поход на HDD со случайным доступом - миллисекунды, то есть в тысячи раз дороже.

Изображение

Page cache, синхронный и буферизованный io: где прячется задержка

Главная развилка, без понимания которой дисковый io в Linux выглядит магией - это page cache.

Когда ты пишешь обычным write(), данные по умолчанию НЕ летят сразу на диск. Они оседают в page cache (это так называемые "грязные", dirty, страницы), а ядро сбрасывает их на устройство потом, фоновым потоком writeback. Такая запись называется буферизованной (buffered). Она быстрая для приложения, потому что фактическая работа диска отложена. Минус: если выдернуть питание до сброса, эти данные потеряются.

Сколько "грязи" ядро готово копить, регулируют параметры vm.dirty_ratio и vm.dirty_background_ratio (в /proc/sys/vm/). Когда грязных страниц набирается до фонового порога, просыпается writeback и начинает сброс. Когда грязи становится слишком много (верхний порог), ядро притормаживает само приложение, заставляя его писать синхронно - это явление называют writeback throttling, и со стороны оно выглядит как внезапные "залипания" записи. Полезно знать, если видишь рваный профиль записи в iostat.

Чтение тоже идет через кэш. Первый раз файл читается с диска и кладется в page cache. Второй раз он прилетает уже из оперативки - в сотни раз быстрее. Поэтому повторный запуск той же команды часто "ускоряется" - это не магия, это кэш. Объем кэша видно в free -h (колонка buff/cache) и в /proc/meminfo (поля Cached и Dirty).

А вот когда программе нужна гарантия, что данные на диске, она зовет fsync() (целиком файл) или fdatasync() (только данные, без лишних метаданных - чуть дешевле), либо открывает файл с флагом O_SYNC или O_DIRECT. fsync блокирует приложение до тех пор, пока устройство не подтвердит запись. Базы данных делают это на каждый коммит. Вот почему медленный fsync на дешевом SSD без конденсатора (power loss protection) убивает производительность Postgres, хотя "диск вроде не загружен". Синхронный io - это плата за надежность, и часто именно он, а не пропускная способность, оказывается узким местом.

io-bound - это про задачу, которая упирается в скорость диска, а не процессора. Если процесс почти все время ждет данные с накопителя, он io-bound. Противоположность - cpu-bound, где узкое место в вычислениях. iowait в выводе top как раз показывает долю времени, когда CPU простаивал в ожидании io. Высокий iowait - сигнал "ищи проблему в дисковом стеке", но сам по себе он не диагноз: иногда iowait высокий просто потому, что нагрузки на CPU нет, и ему нечем заняться, кроме ожидания одного-единственного потока.

Где мерить блочный слой и устройство: практика и чтение вывода

Карта нужна, чтобы выбрать точку измерения. Начнем снизу - с устройства. Главный инструмент тут iostat из пакета sysstat.

Код: Выделить всё

sudo iostat -xz 1 3
Флаг -x - расширенная статистика, -z прячет простаивающие устройства (удобно, чтоб не листать пустые строки), 1 - интервал в секундах, 3 - число снимков. Первый снимок усреднен с момента загрузки, смотри со ВТОРОГО. Вывод по устройству (упрощенно, актуальный набор колонок sysstat 12.x):

Код: Выделить всё

Device   r/s    w/s   rkB/s   wkB/s  rareq-sz wareq-sz r_await w_await aqu-sz  %util
nvme0n1  12.0  340.0   480.0  21760.0   40.0     64.0     0.18    8.40    2.95   62.30
Разбираем по колонкам, это ключевое:
  • r/s, w/s - операций чтения и записи в секунду (это и есть IOPS). Здесь явный перекос в запись.
  • rkB/s, wkB/s - килобайты в секунду, то есть пропускная способность (throughput).
  • rareq-sz, wareq-sz - средний размер одного запроса в килобайтах. Маленький размер при большом числе операций - это random io (много мелких разбросанных запросов), большой размер - последовательный (sequential). Эта пара колонок мгновенно отвечает на вопрос "это случайная нагрузка или потоковая", что критично для HDD.
  • r_await, w_await - среднее время обслуживания запроса в миллисекундах, включая ожидание в очереди планировщика плюс время самого устройства. Это твой главный индикатор задержки (latency). Для NVMe норма - доли миллисекунды, для SSD - единицы мс, для HDD - десятки мс. Здесь w_await=8.4 мс для NVMe - это уже подозрительно много, диск захлебывается записью или ждет flush.
  • aqu-sz - средняя длина очереди запросов (раньше колонка называлась avgqu-sz). Это суммарно очередь планировщика плюс запросы, уже отданные в устройство. Близко к нулю - диск скучает. Стабильно больше единицы - запросы выстраиваются, устройство не успевает.
  • %util - процент времени, когда устройство было занято хоть одним запросом. У старых HDD близость к 100% означала насыщение. ВНИМАНИЕ, типичная ловушка 2026 года: для NVMe и SSD %util врет. Эти диски обрабатывают много запросов параллельно (несколько аппаратных очередей), поэтому %util считает занятость, как будто очередь одна, и быстро упирается в 100%, хотя диск выжат на 20 процентов. Смотри на await, aqu-sz и реальные IOPS, а не на util.
Логика анализа простая. await высокий И aqu-sz большой - запросы реально стоят в очереди, узкое место в устройстве или его насыщении. await высокий, а aqu-sz маленький (около 1) - очереди нет, но каждый отдельный запрос медленный: похоже на синхронную запись с fsync или дешевый диск. Низкий await при любом util - с диском все хорошо, ищи проблему выше.

Теперь посмотрим на блочный слой - какой планировщик io работает:

Код: Выделить всё

cat /sys/block/nvme0n1/queue/scheduler
В квадратных скобках - активный. Вывод вроде

Код: Выделить всё

[none] mq-deadline kyber bfq
значит, что выбран none. На современном ядре (6.x, актуально на 2026) дефолты ставит сам блочный слой: для NVMe и многоочередных устройств - none (быстрому параллельному диску переупорядочивать нечего, лишняя работа только мешает), для обычных дисков и SSD - чаще mq-deadline. Доступные планировщики:
  • none - без переупорядочивания, запросы идут в порядке поступления. Дефолт и правильный выбор для NVMe.
  • mq-deadline - следит за дедлайнами запросов, не дает чтению голодать из-за записи. Хорош для HDD, SATA SSD и виртуалок.
  • kyber - легкий латентностный планировщик для быстрых дисков, держит целевые задержки чтения/записи.
  • bfq - честное деление полосы между процессами, дает отзывчивость на интерактивном десктопе, но имеет накладные расходы и не любит очень высокий IOPS.
Поменять можно на лету (не переживет перезагрузку - для постоянства правят udev-правило):

Код: Выделить всё

echo mq-deadline | sudo tee /sys/block/sda/queue/scheduler
Поднимаемся к приложению: strace и зрелый eBPF

iostat показывает, что диск занят, но не объясняет, КТО и ЗАЧЕМ его грузит. Чтобы понять, что делает само приложение на уровне syscall, поднимаемся на верх стека.

Классика - strace:

Код: Выделить всё

sudo strace -T -e trace=read,write,fsync,fdatasync -p 1234
Флаг -T дописывает в конце каждой строки время выполнения вызова в секундах. Видишь строку вида

Код: Выделить всё

fsync(7) = 0 <0.412019>
- это значит, что один fsync занял 412 миллисекунд. Вот он, виновник: приложение само сидит в синхронной записи. Минус strace - он тормозит цель, потому что на каждый syscall дергает ptrace и останавливает процесс. На горячей нагрузке это заметно искажает картину, поэтому на боевых базах strace включают точечно и ненадолго.

В 2026 году правильный инструмент для верха стека под нагрузкой - это eBPF. Он давно зрелый: на ядрах 5.x и 6.x пакеты bcc и bpftrace есть в репозиториях всех серьезных дистрибутивов, и для многих задач они уже вытеснили strace и старый blktrace, потому что почти не дают накладных расходов и видят то, что снаружи невидимо. Вот рабочий минимум, который стоит знать.

Код: Выделить всё

sudo biolatency.bt
biolatency (есть и bcc-версия biolatency, и bpftrace-версия biolatency.bt) показывает распределение задержек блочного io гистограммой со степенями двойки. Это не среднее, как await, а именно распределение - и оно ловит "длинный хвост". Средняя задержка может быть 0.2 мс, а гистограмма покажет, что раз в секунду прилетают запросы по 50 мс, и именно они портят отзывчивость. Среднее это прячет, гистограмма вскрывает.

Код: Выделить всё

sudo biosnoop.bt
biosnoop трассирует КАЖДУЮ блочную операцию: время, имя процесса, PID, диск, тип (R/W), сектор, размер и latency каждого io. Это прямой ответ на вопрос "какой именно процесс прямо сейчас грузит диск и с какими задержками". Связка получается железная: biolatency или iostat снизу говорит "диск страдает", biosnoop посередине показывает виновный процесс, а strace или bpftrace по syscall сверху объясняет, зачем он это делает. И отдельно полезен tool cachestat - он показывает эффективность page cache (hit/miss), то есть напрямую отвечает, доходят ли чтения до диска вообще.

Типы накопителей и их профили задержек держи в голове как реперные точки. HDD: механика, головка физически едет к дорожке, случайное чтение 5-15 мс, ненавидит random io, любит sequential. SATA SSD: нет механики, около 0.1-0.5 мс, держит десятки тысяч IOPS. NVMe: подключен прямо в PCI Express, сотни тысяч IOPS, задержки в десятки микросекунд. Если на NVMe ты видишь await в миллисекундах - что-то не так: либо диск дешевый без аппаратной защиты кэша, либо перегружен, либо тормозит fsync/flush.

cgroup v2 и io: кто на самом деле делит диск

Еще один пласт, актуальный на 2026: на современных системах с systemd по умолчанию работает cgroup v2 (единая иерархия). Через нее ядро умеет ограничивать и взвешивать дисковый io между сервисами контроллером io. Это значит, что "тормоза диска" у конкретного сервиса могут быть не свойством железа, а заданным лимитом. Посмотреть статистику io по cgroup:

Код: Выделить всё

cat /sys/fs/cgroup/system.slice/<имя>.service/io.stat
Лимиты задают через io.max (жесткий потолок по IOPS/байтам на устройство) и io.weight (относительная доля при конкуренции), в том числе из unit-файла systemd директивами IOReadBandwidthMax, IOWeight и подобными. Если сервис уперся в io.max, iostat покажет, что устройство свободно, а сервис все равно ждет - и без знания про cgroup v2 это выглядит мистикой. Поэтому при разборе "почему именно этот контейнер тормозит" сначала проверь io.max его cgroup.

Типичные грабли и заблуждения
  • "Высокий iowait = виноват диск". Не всегда. iowait - это просто простой CPU в ожидании io. На почти простаивающей машине он может быть высоким без всякой проблемы. Смотри в комплексе: await в iostat плюс iowait плюс гистограмма biolatency.
  • "%util 100% = диск на пределе". Для HDD близко к правде, для SSD/NVMe - нет. Эти устройства параллельны, util их недооценивает и упирается в 100% раньше времени. Верь await, aqu-sz и абсолютным IOPS.
  • "Записал в файл - значит, на диске". Нет. Без fsync данные в page cache и при сбое питания теряются. И наоборот: кажущиеся "тормоза диска" - это часто честный fsync, а не медленное железо.
  • "Повторный тест быстрее, диск разогрелся". Нет, это page cache отдает данные из памяти. Для честного бенчмарка чтения кэш сбрасывают:

    Код: Выделить всё

    sync; echo 3 | sudo tee /proc/sys/vm/drop_caches
  • "Сменю планировщик на bfq и станет быстрее". На NVMe чаще наоборот: none быстрее, а bfq добавляет накладные расходы. Планировщик меняют под конкретный профиль нагрузки и проверяют бенчмарком, а не "на всякий случай".
  • "strace покажет проблему на проде". Покажет, но затормозит горячий процесс. Под нагрузкой используй eBPF (biosnoop, biolatency), а strace - точечно.
Мини-лаба: пройди стек руками прямо сейчас
  • Запусти

    Код: Выделить всё

    iostat -xz 1
    в одном терминале и оставь работать.
  • Во втором создай нагрузку на запись:

    Код: Выделить всё

    dd if=/dev/zero of=/tmp/testfile bs=1M count=2000 oflag=direct
    (флаг oflag=direct - это O_DIRECT, обход page cache, чтобы увидеть реальный диск). Смотри, как растут w/s, wkB/s и w_await.
  • Повтори dd БЕЗ oflag=direct и сравни: с буферизацией приложение "отстреляется" мгновенно, а реальная запись растянется во времени - это и есть работа page cache и writeback.
  • Если доступен bpftrace: в третьем терминале запусти

    Код: Выделить всё

    sudo biolatency.bt
    , повтори dd и прерви биолатенси по Ctrl-C - получишь гистограмму задержек блочного io.
  • Посмотри планировщик:

    Код: Выделить всё

    cat /sys/block/*/queue/scheduler
  • Сбрось кэш и убедись, что повторное чтение снова идет с диска:

    Код: Выделить всё

    sync; echo 3 | sudo tee /proc/sys/vm/drop_caches
  • Удали тестовый файл:

    Код: Выделить всё

    rm /tmp/testfile
Заметка для отечественных дистрибутивов: на Astra Linux и RED OS все то же самое - те же iostat, strace, bpftrace, тот же blk-mq и пути в /sys/block, тот же cgroup v2. Ядро современное, systemd на месте, особых отличий в дисковом стеке нет. Если bpftrace не стоит, его ставят из репозитория пакетом bpftrace (и bcc-tools при необходимости).

Контрольные вопросы
  • По каким слоям проходит запрос write() от приложения до пластины диска? Назови их по порядку.
  • Чем буферизованная запись отличается от вызова fsync() и почему fsync может стать узким местом даже на быстром NVMe?
  • Почему на NVMe нельзя верить колонке %util, и на какие колонки iostat смотреть вместо нее?
  • Чем biolatency полезнее средней колонки await, и когда вместо strace на проде стоит брать biosnoop?
Что запомнить. Дисковый io в Linux - это многослойный стек: приложение, syscall, VFS, ФС, page cache, блочный слой (blk-mq), драйвер, устройство. Page cache прячет реальные обращения к диску, fsync их вскрывает. iowait, await и %util - сигналы, но не диагноз; на NVMe верь await и aqu-sz, а не util. Карта слоев нужна, чтобы выбрать инструмент: strace и bpftrace для верха (что и зачем просит приложение), biosnoop/biolatency посередине (кто грузит и с каким хвостом задержек), iostat для низа (как отвечает устройство), io.stat в cgroup v2 (не уперлись ли мы в лимит). Понял, где мерить, - наполовину решил проблему.
👍3 ❤️3 🔥3 😄 🤔
Аватара пользователя
marcorighini
Сообщения: 1
Зарегистрирован: 18 май 2026, 19:59

Re: Подсистема ввода-вывода: путь запроса от приложения до диска

Сообщение marcorighini »

Спасибо, наконец до меня дошло, почему повторный grep по большому логу летает - это page cache, а не магия. И теперь понятно, зачем drop_caches перед бенчем.
👍2 ❤️2 🔥 😄 🤔
Аватара пользователя
reactsmith
Сообщения: 1
Зарегистрирован: 28 май 2026, 16:42

Re: Подсистема ввода-вывода: путь запроса от приложения до диска

Сообщение reactsmith »

Попробовал biolatency на проде - и реально, средний await был 0.3 мс, а в гистограмме вылез хвост по 40 мс раз в пару секунд. Никогда бы по iostat это не поймал, спасибо за наводку на eBPF.
👍 ❤️1 🔥1 😄 🤔
Ответить
← Предыдущая глава
Память ядра и slab: slabtop и куда уходит RAM
Следующая глава →
Диагностика диска: iostat и чтение await, %util

Все главы курса «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Поделиться темой: ✈ Telegram VK
Похожие запросы: iostat как читать await и util диска linuxФайловая система APFSУправление дисками на MacРезервное копирование Mac (Time Machine)Установка приложений на Mac

Вернуться в «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость