Диагностика проблем с 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-скрипт, который автоматизирует сбор информации о зависших процессах:
- Находит все процессы в D-state
- Сохраняет их стеки вызовов
- Включает ftrace для этих процессов на 10 секунд
- Собирает результаты в архив
Важные моменты при работе с ftrace: всегда отключайте трассировку после диагностики. Она потребляет ресурсы процессора и может влиять на производительность. Очищайте буфер трассировки перед новым запуском:
text
echo > /sys/kernel/debug/tracing/trace
И помните про ограничение размера буфера. По умолчанию он довольно маленький. Для длительной трассировки увеличьте его:
text
echo 40960 > /sys/kernel/debug/tracing/buffer_size_kb
Заключение: инструменты, которые всегда под рукой
Диагностика D-state процессов перестаёт быть чёрной магией, когда знаешь правильные инструменты. Файл /proc/*/stack даёт моментальный снимок проблемы, а ftrace позволяет увидеть полную картину в динамике.
Эти инструменты встроены в ядро Linux и доступны на любой системе. Не нужно устанавливать дополнительные пакеты или перезагружать сервер. Достаточно знать, где искать и как интерпретировать результаты.
Конечно, разбор стеков ядра требует определённого опыта. Но даже базовое понимание позволяет быстро определить направление поиска: проблема в файловой системе, сети или драйвере устройства. А дальше можно углубляться в детали или привлекать профильных специалистов.
В моей практике эти инструменты не раз помогали найти корень проблемы там, где другие методы были бессильны. Зависший процесс больше не повод для паники, а интересная задача с конкретными шагами решения.