Всё, что ниже, актуально на 2026 год: современный Linux с systemd, ядро 6.x, cgroup v2 по умолчанию и зрелый eBPF в арсенале.
Что такое сигнал и почему kill это про сигналы
Сигнал - это короткое асинхронное уведомление, которое ядро доставляет процессу. По сути это "стук в дверь": эй, тебе тут сообщение. Процесс может на этот стук отреагировать своим обработчиком (закрыть файлы, дописать лог, попрощаться), может проигнорировать, а может ничего не делать - и тогда сработает действие по умолчанию (чаще всего завершение). Всё зависит от того, какой сигнал и что для него настроено.
Команда kill не "убивает" сама по себе. Она просто отправляет сигнал процессу по его PID. Название историческое и сбивает с толку новичков - на самом деле это "послать сигнал". Полный список сигналов смотрим так:
Код: Выделить всё
kill -l- TERM (15) - "пожалуйста, завершись". Сигнал по умолчанию, если номер не указан. Процесс получает шанс прибраться: сбросить буферы на диск, закрыть соединения, удалить временные файлы. Так и надо просить в первую очередь.
- KILL (9) - "умри немедленно". Этот сигнал процесс НЕ может перехватить, заблокировать или проигнорировать - его обрабатывает само ядро, минуя код процесса. Но и тут есть подвох, о нём дальше.
- HUP (1) - "перечитай конфиг". Исторически "hang up" (повесили трубку модема), сейчас демоны вроде nginx, rsyslog, haproxy по нему перечитывают конфигурацию без рестарта. Это не убийство, это "обновись". Будь осторожен: у некоторых программ HUP по умолчанию означает именно завершение, так что сверяйся с докой конкретного демона.
- INT (2) - то, что шлёт Ctrl+C в терминале. Вежливое прерывание, его можно перехватить.
- QUIT (3) - Ctrl+\ в терминале. Как INT, но действие по умолчанию - завершение С дампом памяти (core dump). Удобно, когда надо снять состояние зависшего процесса для разбора.
- STOP / CONT - заморозить процесс и разморозить. STOP, как и KILL, нельзя перехватить или проигнорировать. Парный ему TSTP (Ctrl+Z) - перехватываемый вариант заморозки.
- USR1 / USR2 - "пользовательские" сигналы без предопределённого смысла. Программы вешают на них что угодно: nginx по USR1 переоткрывает лог-файлы (нужно после ротации логов), по USR2 запускает обновлённый бинарник на лету.
Код: Выделить всё
kill -TERM 4821
kill -15 4821
kill -9 4821
kill -HUP $(pidof nginx)
kill -s SIGUSR1 $(pidof nginx)
kill, killall и pkill: как убить зависший процесс по имени
Гоняться за PID руками неудобно. Есть инструменты, которые бьют по имени или по пользователю.
killall - шлёт сигнал всем процессам с точным совпадением имени:
Код: Выделить всё
killall firefox
sudo killall -9 nginxpkill - то же, но матчит по части имени (regex по умолчанию) и умеет фильтровать по куче критериев:
Код: Выделить всё
pkill -f "python.*worker"
pkill -u www-data
pkill -HUP nginxКод: Выделить всё
pgrep -af "python.*worker"Отдельно про systemd (актуально на 2026, cgroup v2 по умолчанию). Если процесс - часть сервиса, правильнее работать не с PID, а с юнитом. Тогда ядро гарантированно снимет ВСЁ дерево процессов сервиса через cgroup, и ни один форк-потомок не сбежит:
Код: Выделить всё
systemctl kill mysql.service
systemctl kill -s SIGKILL mysql.service
systemd-cgls # дерево процессов по cgroup
systemctl status mysql # видно главный PID и потомковПочему kill -9 не помогает: зависший процесс в состоянии D
А теперь главное, ради чего урок. Ты шлёшь kill -9, а процесс жив. Смотришь в ps, а у него в колонке состояния стоит буква D. Это не баг и не зомби - это uninterruptible sleep, непрерываемый сон.
Сначала научимся читать состояния. Запусти:
Код: Выделить всё
ps -eo pid,ppid,user,stat,wchan:24,comm- R - бежит на CPU или готов бежать (стоит в очереди runqueue). Норма. Если таких много и постоянно - смотри на загрузку CPU.
- S - спит, ждёт события (сети, ввода, таймера), прерываемый сон. Абсолютное большинство процессов в системе именно такие, это норма. Такой сон сигналом будится мгновенно.
- D - непрерываемый сон. Процесс застрял внутри ядра, ждёт завершения операции (почти всегда дискового или сетевого ввода-вывода). Вот он сигналы НЕ принимает прямо сейчас.
- Z - зомби (defunct). Процесс уже мёртв, но родитель ещё не забрал его код возврата.
- T - остановлен (получил STOP/TSTP). Заглавная T - стоп; строчная t означает остановку трассировщиком (под gdb/strace).
- I - idle, простаивающий поток ядра (kernel thread). Современные ядра выделяют такие в отдельное состояние, чтобы они не пугали в выводе как D. Это норма.
Почему D не убивается? Когда процесс просит ядро прочитать блок с диска или с NFS, он засыпает ВНУТРИ ядра и помечается как непрерываемый (TASK_UNINTERRUPTIBLE). В этом состоянии он физически не проверяет очередь сигналов - ему сейчас не до них, он ждёт железо, и прерывать его в этот момент небезопасно (можно поймать порчу данных или утечку ресурсов ядра при DMA). SIGKILL встанет в очередь, но доставится только когда процесс вернётся из ядра, то есть когда ввод-вывод завершится или отвалится по таймауту. Пока диск тупит или сетевая ФС не отвечает - процесс будет в D, и kill 9 linux его не возьмёт. Ядро не игнорирует твой сигнал, оно его откладывает.
Это означает важную вещь: массовый D-state - это не проблема процессов, это симптом проблемы с диском или сетью. Смотреть надо не в kill, а в железо. Куда копать:
Код: Выделить всё
# кто именно висит в D
ps -eo pid,stat,wchan:32,comm | awk '$2 ~ /^D/'
# на чём конкретно застрял процесс (имя функции ядра)
cat /proc/4821/wchan; echo
# полный стек в ядре - самое полезное
sudo cat /proc/4821/stackКод: Выделить всё
iostat -x 1
# %util близко к 100 - устройство загружено под завязку
# await и r_await/w_await растут (десятки-сотни мс) - запросы ждут
# aqu-sz большой - длинная очередь к дискуИ обязательно загляни в dmesg - там ядро прямым текстом расскажет про проблемы:
Код: Выделить всё
sudo dmesg -T | tail -40Код: Выделить всё
echo w | sudo tee /proc/sysrq-trigger
sudo dmesg -T | tail -60eBPF: современный способ поймать причину (актуально на 2026)
В 2026 году для разбора "почему процесс не на CPU" уже не нужно гадать по wchan вручную - есть зрелый eBPF (нужно ядро 4.9+, реально это любой современный дистрибутив). Инструменты идут в пакетах bpfcc-tools (bcc) и bpftrace.
Код: Выделить всё
# сколько времени и на каких стеках процессы спят вне CPU
sudo offcputime-bpfcc -p $(pidof myapp) 10
# гистограмма латентности блочного I/O (диск)
sudo biolatency-bpfcc 5 1
# латентность I/O с привязкой к стеку инициации запроса
sudo biostacks.btИтог по D: убить процесс в D нельзя в принципе. Варианты ровно три - дождаться, пока ввод-вывод завершится и процесс сам умрёт от уже поставленного в очередь сигнала; починить источник (поднять NFS-сервер, перемонтировать зависшую ФС, заменить умирающий диск); в безвыходной ситуации - перезагрузка узла. Никакого "форсированного kill для D" в Linux нет, и это сознательное решение разработчиков ядра.
Зомби и типичные грабли
Второй процесс, который "не убивается" - зомби (STAT = Z, в выводе ps помечен как <defunct>). Тут засада в логике: зомби уже мёртв. Его код отработал, память освобождена, осталась одна строчка в таблице процессов - чтобы родитель успел прочитать код возврата вызовом wait(). Слать kill в зомби бессмысленно, убивать там нечего, он не потребляет ни CPU, ни память.
Зомби исчезает, когда родитель его "пожинает" (reap). Если родитель забыл это сделать (баг в программе, не вызывает wait) - зомби копится. Лечится так: найти родителя и разобраться с ним.
Код: Выделить всё
ps -eo pid,ppid,stat,comm | awk '$3 ~ /Z/'
# взять PPID зомби и посмотреть, что за родитель
ps -p <PPID> -o pid,comm
# мягко пнуть родителя - часто он дочищает детей
kill -HUP <PPID>Грабли, на которые наступают почти все:
- Сразу kill -9 вместо TERM. Отучайся. Девятка не даёт прибраться - привет битым данным и осиротевшим lock-файлам.
- Думать, что -9 убьёт что угодно. Нет: D-state и зомби ему не по зубам, и это правильное поведение ядра, а не глюк.
- Бить по PID, который уже переиспользован. Пока ты копировал старый PID, процесс умер, а номер занял другой. Поэтому pkill/killall по имени или systemctl kill по юниту надёжнее для короткоживущих.
- pkill без pgrep. Шаблон шире, чем кажется (это regex) - проверяй охват заранее, особенно с -f.
- Грузить систему попытками убить D-процесс. Бесполезно. Иди диагностировать ввод-вывод: stack, iostat, dmesg, offcputime.
- Слать KILL сервису через PID. Потомки могут сбежать из-под наблюдения. Через systemctl kill уйдёт всё дерево по cgroup.
Мини-лаба: повтори руками прямо сейчас
- Запусти sleep 600 & в фоне, найди его через pgrep -a sleep, отправь kill -STOP <PID>, посмотри ps - состояние станет T. Разбуди через kill -CONT, потом сними kill -TERM. Прочувствуй разницу сигналов на живом примере.
- Открой top или htop, найди процессы в состоянии S - убедись, что таких большинство и это норма.
- Запусти ps -eo pid,stat,wchan:32,comm и посмотри, кто в каком сне и на какой функции ядра ждёт.
- Сделай sudo dmesg -T | tail -40 и просто почитай, о чём ядро говорит прямо сейчас. Привыкай туда заглядывать первым делом.
- Если есть права - запусти sudo offcputime-bpfcc 5 и посмотри, на каких стеках процессы реально спят вне CPU. Это твой будущий главный инструмент.
- Чем TERM отличается от KILL и почему по умолчанию правильно слать именно TERM?
- Процесс в состоянии D не реагирует на kill -9. Почему так происходит и куда смотреть, чтобы найти причину?
- Можно ли убить зомби сигналом KILL? Если нет - что с ним вообще делать?
- В чём разница между killall и pkill, зачем перед pkill запускать pgrep, и почему для сервиса лучше systemctl kill?
kill не убивает, а шлёт сигнал. Проси по-хорошему через TERM, девятку держи как аварийный молоток. kill 9 linux всесилен только над живыми процессами в R и S - но бессилен над D (непрерываемый ввод-вывод) и над зомби (там убивать уже нечего). Если зависший процесс linux стоит в D - это симптом проблемы с диском или сетью: читай /proc/PID/stack, iostat и dmesg, в 2026 добавь offcputime/biolatency, а не долби сигналами. Зомби лечатся через родителя. Сервисы прибивай через systemctl kill, чтобы не упустить потомков. И всегда pgrep перед pkill, чтобы не снести лишнего.