November 10, 2025

Диагностика проблем с D-state процессами через /proc/*/stack и ftrace

Когда система молчит: первое знакомство с D-state

Работая с Linux-системами, я часто сталкиваюсь с ситуациями, когда процесс просто застывает. Не реагирует на сигналы, игнорирует kill -9, словно попал в параллельное измерение. Такое состояние называется uninterruptible sleep или D-state, и оно может превратить обычный рабочий день в настоящий квест по поиску причин зависания.

Процесс в D-state ожидает завершения операции ввода-вывода на уровне ядра. Это нормальное явление, если длится доли секунды. Но когда счёт идёт на минуты или часы, начинаешь понимать: что-то пошло не так. Может быть, проблема с диском, сетевой файловой системой или драйвером устройства. А может, процесс попал в deadlock внутри ядра.

Первым делом я обычно проверяю вывод команды ps aux. Процессы в D-state помечены буквой D в колонке STAT. Увидел такой процесс? Значит, пора копать глубже. И тут на помощь приходят мощные инструменты диагностики, встроенные прямо в ядро Linux.

Анатомия стека вызовов: изучаем /proc/*/stack

Файловая система /proc хранит невероятное количество информации о работающих процессах. Среди всего этого богатства особое место занимает файл /proc/[pid]/stack. Он показывает текущий стек вызовов ядра для конкретного процесса. По сути, это снимок того, чем именно занято ядро в данный момент для этого процесса.

Допустим, я нашёл зависший процесс с PID 12345. Смотрю его стек:

text
cat /proc/12345/stack

И вижу примерно такую картину:

text
[<0>] io_schedule+0x16/0x40
[<0>] wait_on_page_bit_common+0x11e/0x3c0
[<0>] filemap_fault+0x6df/0xa20
[<0>] ext4_filemap_fault+0x2c/0x40

Каждая строка показывает функцию ядра и смещение внутри неё. Читается снизу вверх: ext4_filemap_fault вызвала filemap_fault, та вызвала wait_on_page_bit_common, и так далее. Видно, что процесс ждёт завершения операции с памятью, связанной с файловой системой ext4.

Честно говоря, первый раз разбираясь с такими стеками, я чувствовал себя археологом, расшифровывающим древние письмена. Но со временем начинаешь видеть паттерны. Например, если в стеке есть nfs_* функции, проблема скорее всего связана с сетевой файловой системой. Функции с префиксом usb_* указывают на проблемы с USB-устройствами.

Динамическая трассировка: включаем ftrace

Статический снимок стека полезен, но иногда нужно увидеть процесс в динамике. Как именно система пришла к текущему состоянию? Какие функции вызывались перед зависанием? Для таких задач я использую ftrace, встроенный в ядро трассировщик.

Ftrace живёт в директории /sys/kernel/debug/tracing. Перед использованием нужно убедиться, что debugfs примонтирована:

text
mount -t debugfs none /sys/kernel/debug

Начинаю с простого: включаю трассировку функций для конкретного процесса. Записываю его PID в специальный файл:

text
echo 12345 > /sys/kernel/debug/tracing/set_ftrace_pid

Выбираю трассировщик функций:

text
echo function > /sys/kernel/debug/tracing/current_tracer

И включаю трассировку:

text
echo 1 > /sys/kernel/debug/tracing/tracing_on

Теперь в файле /sys/kernel/debug/tracing/trace накапливается информация о всех вызовах функций ядра для этого процесса. Это похоже на запись чёрного ящика: видно всё, что происходило до момента проблемы.

Фильтрация и анализ: находим иголку в стоге сена

Полная трассировка генерирует огромный объём данных. Тысячи строк в секунду. Как найти именно то, что нужно? Использую фильтры ftrace.

Можно ограничить трассировку конкретными функциями. Например, меня интересуют только операции блочного ввода-вывода:

text
echo 'blk_*' > /sys/kernel/debug/tracing/set_ftrace_filter

Или наоборот, исключить часто вызываемые, но неинтересные функции:

text
echo '!sched_*' >> /sys/kernel/debug/tracing/set_ftrace_notrace

Особенно полезен трассировщик function_graph. Он показывает не просто список вызовов, а полное дерево с временем выполнения каждой функции:

text
echo function_graph > /sys/kernel/debug/tracing/current_tracer

В выводе видно, какие функции занимают больше всего времени. Если какая-то функция выполняется секундами вместо микросекунд, это явный признак проблемы.

Практические кейсы: от теории к реальным проблемам

Недавно столкнулся с интересным случаем. На сервере процесс rsync завис в D-state при копировании данных с NFS-шара. Классическая ситуация, думаете вы? Не совсем.

Смотрю стек процесса, вижу:

text
[<0>] rpc_wait_bit_killable+0x1e/0xa0
[<0>] nfs4_do_close+0x21b/0x2c0
[<0>] nfs4_close_sync+0x12/0x20

Процесс пытается закрыть файл на NFS, но операция не завершается. Включаю ftrace с фильтром на nfs* и rpc* функции. Через несколько секунд вижу картину: RPC-вызовы к NFS-серверу отправляются, но ответы не приходят. При этом сеть работает, ping до сервера проходит.

Оказалось, на NFS-сервере был исчерпан лимит на количество одновременных NFS-операций. Клиент честно ждал своей очереди, но из-за особенностей конфигурации очередь не двигалась. Увеличение параметра nfsd threads на сервере решило проблему.

Другой случай связан с USB-диском. Процесс dd завис при записи образа. Стек показывал:

text
[<0>] usb_start_wait_urb+0x60/0x160
[<0>] usb_bulk_msg+0xb5/0x160
[<0>] usb_stor_bulk_transfer_buf+0x56/0xb0

Ftrace выявил, что USB-контроллер периодически сбрасывается из-за ошибок. Проблема оказалась в некачественном USB-кабеле. Замена кабеля мгновенно устранила зависания.

Автоматизация диагностики: скрипты и best practices

Каждый раз вручную настраивать ftrace утомительно. Я написал простой bash-скрипт, который автоматизирует сбор информации о зависших процессах:

  1. Находит все процессы в D-state
  2. Сохраняет их стеки вызовов
  3. Включает ftrace для этих процессов на 10 секунд
  4. Собирает результаты в архив

Важные моменты при работе с ftrace: всегда отключайте трассировку после диагностики. Она потребляет ресурсы процессора и может влиять на производительность. Очищайте буфер трассировки перед новым запуском:

text
echo > /sys/kernel/debug/tracing/trace

И помните про ограничение размера буфера. По умолчанию он довольно маленький. Для длительной трассировки увеличьте его:

text
echo 40960 > /sys/kernel/debug/tracing/buffer_size_kb

Заключение: инструменты, которые всегда под рукой

Диагностика D-state процессов перестаёт быть чёрной магией, когда знаешь правильные инструменты. Файл /proc/*/stack даёт моментальный снимок проблемы, а ftrace позволяет увидеть полную картину в динамике.

Эти инструменты встроены в ядро Linux и доступны на любой системе. Не нужно устанавливать дополнительные пакеты или перезагружать сервер. Достаточно знать, где искать и как интерпретировать результаты.

Конечно, разбор стеков ядра требует определённого опыта. Но даже базовое понимание позволяет быстро определить направление поиска: проблема в файловой системе, сети или драйвере устройства. А дальше можно углубляться в детали или привлекать профильных специалистов.

В моей практике эти инструменты не раз помогали найти корень проблемы там, где другие методы были бессильны. Зависший процесс больше не повод для паники, а интересная задача с конкретными шагами решения.