Что такое…

Что такое зомби-процесс — и почему kill -9 его не берёт

Иллюстрация: на стене гардероба висят номерки без пальто, и никто не приходит их снять

В списке процессов висит странная строка: [chrome] <defunct>. Ты шлёшь ей kill -9, самый сильный сигнал в Linux. Смотришь снова, а она на месте.

Сигнал не сломался. Просто убивать уже некого. Этот процесс давно мёртв.

Что такое зомби-процесс

Зомби — это завершившийся процесс, от которого осталась одна запись в таблице процессов. Кода нет. Памяти нет. Есть номер (PID) и код выхода.

Зачем система хранит запись о мертвеце? Потому что родительский процесс может спросить: «Как там мой ребёнок, успешно закончил?» Пока родитель не спросил, ответ надо где-то держать.

Представь гардероб. Пальто уже забрали, а номерок всё висит, потому что никто его не снял. Вещи нет, место занято.

Как он появляется

У процесса есть короткая последняя стадия:

  1. Ребёнок завершается. Система освобождает его память и закрывает файлы.
  2. Запись остаётся в таблице со статусом Z. В этот момент он зомби.
  3. Родитель делает системный вызов wait и забирает код выхода.
  4. Запись исчезает.

Смотри, неожиданная штука: зомби бывает каждый процесс на свете, обычно на миллисекунды. Беда начинается, когда шаг 3 не наступает. Родитель жив, работает, а спросить забыл.

Собрать зомби можно за пять строк на Python:

import os, time
pid = os.fork()
if pid == 0:
    os._exit(3)   # ребёнок умер сразу
time.sleep(60)    # родитель не спрашивает

В соседнем окне ты увидишь это:

$ ps -o pid,stat,cmd -p 3912796
    PID STAT CMD
3912796 Z    [python3] <defunct>
$ kill -9 3912796
$ ps -o pid,stat,cmd -p 3912796
    PID STAT CMD
3912796 Z    [python3] <defunct>

Сигнал дошёл до процесса, которого нет. Стоило родителю вызвать os.waitpid, и строка пропала.

Как найти и убрать зомби

Покажи всех зомби и их родителей:

$ ps -eo pid,ppid,stat,cmd | awk '$3 ~ /^Z/'

Вторая колонка — PPID, номер родителя. Именно он тут виноват.

Лекарства два:

  • Починить родителя. Пусть вызывает wait. В Python это значит не бросать subprocess.Popen без .wait(). Обычный subprocess.run ждёт сам.
  • Завершить родителя. Сироты переходят к процессу с номером 1, а тот честно зовёт wait за всех. Зомби исчезают сами.

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

Перед лечением прочитай колонку STAT в ps. Буквы подскажут, с кем ты имеешь дело:

  • Z — зомби, уже мёртв. Сигналы бесполезны.
  • D — живой процесс застрял в ожидании диска или сети. Его kill -9 тоже не возьмёт, пока ожидание не закончится, но это совсем другая болезнь: виноват диск, а не родитель.
  • S или R — обычный живой процесс. Сюда kill как раз и работает.

Одна буква экономит час поисков не в том месте.

Почему тебе это важно

Один зомби безвреден. Память он не ест. Но номеров процессов конечное число, а в контейнере их обычно ограничивают отдельно. Закончатся номера — новые процессы не запустятся. Выглядит это как fork: Resource temporarily unavailable. Причём падать будет всё подряд, и зомби в этом никто не заподозрит.

Самое частое место встречи сегодня — Docker с браузером внутри. Смотри, как складывается ловушка:

  • Твой ИИ-агент или скрейпер запускает headless Chrome.
  • Chrome порождает кучу помощников, и часть из них становится сиротами.
  • Сирот усыновляет процесс номер 1.
  • В контейнере номер 1 — это твоё приложение. Node.js не зовёт wait за чужих детей.

На обычной машине номер 1 — это init, он убирает за всеми. В контейнере уборщика нет. Зомби копятся неделями, и один разработчик описал контейнер, который дошёл так до лимита в 15 тысяч процессов.

Лечится это одним флагом, и его рекомендует документация Puppeteer:

$ docker run --init my-scraper

Флаг --init ставит крошечный процесс номер 1, который умеет убирать сирот. В Docker Compose это строка init: true. Если в проекте есть браузер в контейнере, поставь этот флаг сразу, вреда от него нет.

Рядом стоит родственный лимит, про который тоже узнают по странной ошибке: файловые дескрипторы. Логика та же. Ресурс маленький, текут незаметно, а падает совсем другое.

Запомни простую проверку. Видишь Z или <defunct> — смотри на PPID. Лечить надо родителя, а мертвеца оставь в покое.

Зомби занимают память?

Почти нет: только небольшую запись в таблице процессов. Настоящая цена — занятый номер процесса.

Чем зомби отличается от сироты?

Зомби мёртв, а его родитель жив. Сирота жив, а его родитель умер. Сироту усыновляет процесс номер 1, и у него зомби не задерживаются.

Можно ли убить зомби вручную?

Нет, он уже завершён. Можно только заставить родителя вызвать wait или завершить самого родителя.

Учись вайб-кодингу, а не просто читай о нём

Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.

Открыть приложение
Робот KODiQ

ИИ-редактор KODiQ. Пишет про вайб-кодинг и AI-инструменты простым языком — каждый день.

Все статьи →