MAATRIX / Блог / Состояние D: почему бывает процесс, который не убивает даже kill -9

Состояние D: почему бывает процесс, который не убивает даже kill -9

MAATRIX

Рано или поздно каждый, кто администрирует Linux-сервер, натыкается на процесс, который не убивается ничем. kill -9, kill -SIGKILL, даже перезапуск демона через systemd — процесс просто продолжает висеть в выводе ps, а top показывает у него состояние D. Это не глюк и не редкость: так себя ведёт ядро специально, и понимание того, почему оно так делает, экономит часы паники в 3 часа ночи.

Что означает буква D в выводе ps

Когда вы запускаете ps aux или top, в колонке STAT у каждого процесса стоит одна из букв, обозначающих его состояние в планировщике ядра:

ps aux | awk '{print $8, $2, $11}' | sort | uniq -c | sort -rn | head

Основные состояния:

БукваНазваниеЧто значит
RRunningпроцесс выполняется или готов к выполнению на CPU
SInterruptible sleepпроцесс спит и может быть разбужен сигналом
DUninterruptible sleepпроцесс спит и НЕ реагирует на сигналы
ZZombieпроцесс завершился, но родитель не забрал код возврата
TStoppedпроцесс приостановлен (например, SIGSTOP)

Обычный сон (S) — это, например, процесс, который ждёт данных из сети через read() на сокете, или просто спит по таймеру. Такой процесс в любой момент можно разбудить сигналом: он либо обработает его сам, либо ядро прервёт системный вызов с ошибкой EINTR, и процесс вернётся в пользовательское пространство раньше, чем закончил ждать.

Uninterruptible sleep — принципиально другое состояние. Процесс находится не просто «в ожидании», а внутри кода ядра, который выполняет операцию ввода-вывода на низком уровне: отправил запрос драйверу устройства и ждёт ответа от него, а не от планировщика. Пока ответ не пришёл, ядро физически не может безопасно прервать эту операцию.

Почему сигналы, включая SIGKILL, тут бессильны

Здесь кроется главное заблуждение: многие считают, что SIGKILL — это какая-то «супер-команда», которую ничто не может проигнорировать. На уровне пользовательского процесса это почти правда: SIGKILL нельзя перехватить обработчиком, нельзя заблокировать через sigprocmask. Но есть нюанс — сигнал в принципе доставляется процессу только тогда, когда тот находится в контексте, где ядро способно его обработать: либо в пользовательском режиме, либо на границе системного вызова, откуда можно безопасно выйти.

Пока процесс сидит внутри кода ядра в состоянии TASK_UNINTERRUPTIBLE, он ждёт ответа не от планировщика процессов, а от конкретного аппаратного или сетевого ресурса — обычно диска или сетевой файловой системы. Ядро в этот момент не проверяет очередь сигналов у процесса, потому что находится в критической секции: операция уже отправлена контроллеру диска или удалённому NFS-серверу, и откатить её на середине не всегда возможно и не всегда безопасно.

SIGKILL в такой ситуации не «отменяется» — он честно ставится в очередь ожидающих сигналов процесса (это видно в /proc/PID/status в строке SigPnd), но обрабатывается только при следующем выходе процесса из режима ядра в пользовательский режим. Если этот выход никогда не наступает — потому что диск не отвечает или сетевая ФС недоступна — процесс так и остаётся «зомби наоборот»: живой, потребляющий PID, но недоступный ни для чего, кроме ожидания.

Проверить, чего именно ждёт процесс, можно через wchan — имя функции ядра, в которой он завис:

cat /proc/<PID>/wchan; echo
cat /proc/<PID>/stack   # нужен root и включённый CONFIG_STACKTRACE

Типичные значения wchan для зависших в D процессов — что-то вроде io_schedule, wait_on_page_bit, nfs_wait_bit_killable, xfs_ail_push_all — уже по одному этому имени понятно, что процесс уперся в дисковую или сетевую подсистему, а не завис в собственном коде.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Почему это защита, а не баг ядра

Идея «нельзя прерывать операцию ввода-вывода на середине» — не прихоть разработчиков ядра, а осознанное решение в пользу целостности данных. Представьте, что происходит на уровне блочного устройства: процесс инициировал запись страницы в файл, ядро сформировало запрос к диску, диск подтвердил приём команды. Если в этот момент разрешить произвольно оборвать ожидание и вернуть процессу управление — что делать со сформированным, но не завершённым запросом? Диск может дописать данные позже без ведома процесса, может начать писать по смещению, которое уже считается свободным, потому что процесс «как бы вышел из write()».

Ещё нагляднее ситуация со страничным кэшем и mmap: если процесс обращается к странице памяти, замапленной из файла, а эта страница ещё не считана с диска, ядро обязано либо дождаться чтения, либо чем-то заполнить память — второго варианта без потери целостности не существует. Прерывание такого ожидания оставило бы процесс с мусором в адресном пространстве вместо реальных данных.

Похожая логика касается NFS в режиме hard mount (поведение по умолчанию, отличается от soft): смысл жёсткого монтирования именно в том, чтобы операция ввода-вывода либо гарантированно завершилась, либо процесс ждал сколько угодно — но не получил тихую потерю данных из-за временной недоступности сервера. Разработчики NFS в ядре сознательно выбрали «пусть лучше зависнет, чем молча повредит файл».

Отдельно стоит сказать про hung_task_timeout_secs — ядро само следит за процессами, застрявшими в D дольше заданного порога (по умолчанию 120 секунд), и пишет в dmesg предупреждение:

dmesg | grep -i "blocked for more than"
sysctl kernel.hung_task_timeout_secs
sysctl kernel.hung_task_panic   # 0 или 1 — паниковать ли ядру при обнаружении

Это не механизм автоматического исправления — ядро не может «решить» проблему за вас, потому что не знает, что на другом конце запроса: временный сбой, который вот-вот пройдёт, или мёртвый диск. Оно лишь честно сигнализирует администратору, что где-то в системе есть операция ввода-вывода, которая не завершается ожидаемо долго.

Как найти процессы в D и понять масштаб проблемы

Прежде чем пытаться что-то чинить, полезно понять, сколько процессов застряло и насколько давно. Быстрая проверка:

ps -eo pid,ppid,state,etime,cmd | awk '$3=="D"'

Если результат пуст — сервер здоров в этом смысле. Если строк много и время (etime) растёт от проверки к проверке — перед вами не кратковременный всплеск нагрузки, а зависшая операция.

Важный побочный эффект: процессы в состоянии D учитываются в load average наравне с процессами, реально потребляющими CPU. Это классическая причина, когда uptime показывает пугающе высокий load average на сервере, где top при этом не видит загрузки процессора — почти вся «нагрузка» на самом деле состоит из процессов, ожидающих диск, а не считающих что-либо.

Полезная связка для диагностики — сопоставить iostat и список D-процессов по времени:

iostat -x 1 5
vmstat 1 5

Если в iostat колонка %util близка к 100%, а await (среднее время ожидания запроса) аномально велико по сравнению с обычным для этого диска значением — весьма вероятно, что именно дисковая подсистема и есть источник зависших процессов. Если %util низкий, а зависание всё равно есть — стоит проверять не диск, а сетевую файловую систему или контроллер устройства.

Типичные первопричины на практике

D-состояние — это симптом, а не диагноз. За ним почти всегда стоит одна из нескольких групп причин.

Проблемное или умирающее блочное устройство. Диск начинает отвечать на команды с большой задержкой или не отвечает вовсе, но ещё не признан ядром окончательно нерабочим. Показательный случай — когда SMART-статус диска формально в порядке, а реальная причина зависаний в кабеле или разъёме: подробный разбор такой ситуации есть в статье диск был здоров по SMART, а данные бились — виноват кабель. SMART не видит проблем физического соединения, поэтому полагаться только на него нельзя.

Недоступная сетевая файловая система. NFS, реже — SMB или другие сетевые ФС, смонтированные в режиме hard, при потере связи с сервером или его перезагрузке заставляют все процессы, работающие с этими файлами, уйти в D до восстановления связи. Пример реальной проблемы с параллельной записью в NFS с двух серверов и повреждением файлов разобран в статье два сервера писали в один NFS и получили битые файлы — там же видно, как поведение ядра при недоступности NFS-сервера превращается в зависшие процессы на клиентах.

Ребилд или деградация RAID-массива. Во время пересборки массива после замены диска или при деградации из-за сбойного диска запросы к массиву могут выполняться на порядок медленнее обычного, и процессы, ожидающие чтения или записи, массово переходят в D.

Проблемы на уровне контроллера или драйвера. Реже, но встречается: сбой в прошивке контроллера, зависший драйвер USB-накопителя, неполадка виртуального диска у гипервизора (актуально для VPS — если хост-система перегружена по дисковой подсистеме, гостевые ВМ могут массово получать зависшие D-процессы без каких-либо локальных признаков поломки).

Медленный, но формально живой диск. Отдельная категория — не сломанный, а просто перегруженный или технически слабый диск, который физически не успевает обрабатывать поток запросов. Как отличить медленный диск от зависшего, подробно разобрано в статье медленный диск на VPS — как проверить.

Что реально можно сделать

Первое и самое важное: искать первопричину, а не пытаться убить процесс. Попытки kill -9 в цикле, перезапуск сервиса через systemd с TimeoutStopSec, даже SIGKILL от oom-killer при нехватке памяти не сработают, пока процесс физически ждёт ответа от устройства — oom-killer умеет послать сигнал, но не умеет заставить процесс его обработать; подробнее о логике выбора жертвы — в статье как ядро выбирает жертву OOM killer.

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

  1. Определите wchan зависших процессов — это сразу сузит круг подозреваемых (диск против сети против конкретной ФС).
  2. Проверьте состояние диска физически: smartctl -a /dev/sdX, но помните, что SMART не покрывает всё — проверьте также dmesg на ошибки контроллера и кабеля (ata_id, SATA link down, I/O error и подобные записи).
  3. Проверьте доступность сетевой ФС, если она используется: showmount -e <nfs-server>, ping, проверка портов NFS/RPC. Если сервер недоступен и это временная сетевая проблема — восстановление связи разбудит зависшие процессы автоматически, без перезагрузки.
  4. Если это RAID — проверьте статус пересборки (cat /proc/mdstat для mdadm, соответствующие утилиты для аппаратного контроллера) и оцените, сколько ещё продлится ребилд.
  5. Для NFS-монтирований рассмотрите профилактику на будущее: опции soft,intr,timeo=... в /etc/fstab дают процессу шанс прерваться по таймауту вместо бесконечного D — ценой риска частичной записи при реальном сбое. Это осознанный компромисс, а не универсально «правильная» настройка: для критичных данных hard зачастую предпочтительнее, несмотря на риск зависания.
  6. Если первопричина устранена, а процесс всё ещё в D — обычно достаточно подождать: как только устройство или сеть отвечают, ядро завершает системный вызов и процесс либо возвращается к работе, либо получает поставленный в очередь SIGKILL и корректно завершается.
  7. Если первопричина неустранима на живой системе (диск физически умер, контроллер завис на уровне прошивки) — единственный реалистичный выход это перезагрузка сервера. Но и тут есть нюанс: обычная команда reboot тоже может зависнуть, потому что процесс завершения работы пытается синхронизировать файловые системы с тем же неотвечающим устройством. В этом случае используется механизм SysRq для принудительной перезагрузки в обход штатной процедуры завершения:
echo 1 > /proc/sys/kernel/sysrq
echo b > /proc/sysrq-trigger

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

Отдельно — про профилактику на уровне инфраструктуры. Массовые зависания в D на VPS часто указывают не на локальную проблему гостевой системы, а на перегруженную дисковую подсистему хоста. Если эпизоды повторяются без явной локальной причины, стоит смотреть в сторону сервера с более предсказуемой дисковой производительностью — NVMe вместо перегруженного сетевого хранилища, выделенный сервер вместо переподписанного VPS — а не бесконечно охотиться за симптомом на своей виртуалке.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли вообще как-то прервать процесс в состоянии D программно?

Штатно — нет, если он действительно ждёт ответа от устройства внутри некэшируемого системного вызова. Начиная с определённых версий ядра часть таких ожиданий переведена в состояние TASK_KILLABLE (это отображается в ps как D с дополнительным флагом или, в некоторых версиях ps, отдельным обозначением), которое всё же реагирует на SIGKILL, но не на обычные сигналы. Для классических блокирующих операций с диском такого послабления обычно нет.

Процесс висит в D уже несколько часов — это точно значит, что диск сломан?

Не обязательно точно, но с высокой вероятностью что-то не в порядке — либо с диском, либо с сетевой ФС, либо это очень долгая легитимная операция (например, гигантский fsck или ребилд массива на большом объёме). Первый шаг — посмотреть wchan и dmesg, а не делать выводы по одной длительности.

Влияет ли количество процессов в D на производительность остального сервера?

Косвенно да. Сами по себе они не потребляют CPU, но раздувают load average, что может ввести в заблуждение при мониторинге, а если они держат блокировки на файлах или таблицах (например, в базе данных) — способны застопорить и другие, формально не зависшие процессы, которые ждут этих блокировок.

Стоит ли настраивать hung_task_panic=1, чтобы сервер сам перезагружался при обнаружении зависших задач?

Это спорная настройка для продакшена: она превращает «диагностируемую» проблему в внезапную перезагрузку, что может быть хуже временного зависания части процессов. Обычно разумнее держать мониторинг dmesg на такие сообщения и разбираться руками, оставляя автоматическую панику для тестовых стендов или систем, где зависание хуже перезагрузки в принципе.

Чем состояние D отличается от зомби-процесса?

Это разные вещи на разных уровнях. Зомби (Z) — процесс, который уже завершился и освободил ресурсы, но чей код возврата не забран родителем; он не потребляет ничего, кроме записи в таблице процессов. D — живой, работающий процесс, застрявший внутри операции ввода-вывода. Зомби безвреден почти всегда, D-процессы в массе — явный признак проблемы с железом или сетью.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →