Почему процесс-зомби вообще существует?

mr. Cooper 1 неделю назад Технологии
Почему процесс-зомби вообще существует?

Если вы когда-нибудь смотрели на список процессов в Linux и замечали состояние Z, возникает вполне закономерный вопрос: почему процесс-зомби вообще существует, если он уже завершился? Он больше не выполняет код, не использует процессор и фактически перестал быть программой - но операционная система всё ещё показывает его среди процессов.

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

И вот здесь начинается самое интересное. Чтобы понять, зачем Linux оставляет после умершего процесса такую «тень», нужно разобраться, что происходит между exit() дочернего процесса и вызовом родителем wait() или waitpid().

Процесс закончил работу. А родитель ещё не получил ответ

Допустим, одна программа запускает другую.

Родительский процесс создаёт дочерний. Тот выполняет свою работу и завершает выполнение:

//c

return 42;

Для дочернего процесса история закончилась.

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

Для этого существуют wait() и waitpid().

Получается довольно необычная последовательность: процесс уже завершился, но его родитель ещё не получил результаты этого завершения.

Вот это состояние и называется zombie.

Причём от самого процесса к этому моменту остаётся совсем немного. Это уже не работающая программа и не кусок кода, который где-то завис. В ядре просто сохраняется необходимая информация о завершившемся процессе.

Почему нельзя сразу всё стереть

Можно представить более простой вариант: процесс завершился - ядро тут же удалило запись о нём.

Тогда возникает другая проблема.

Родитель мог ещё не узнать, что произошло.

Например, ему нужно различать:

//text

0  - программа завершилась успешно
1  - произошла ошибка
42 - вернула специальный результат

Код завершения - это часть коммуникации между дочерним и родительским процессами.

Поэтому ядру приходится немного подождать.

И здесь слово «подождать» важно не перепутать с ожиданием самого процесса. Zombie ничего не ждёт. Он уже закончил выполнение.

Ждёт фактически родитель - вернее, система ждёт момента, когда родитель заберёт статус.

После wait() запись больше не нужна и может быть удалена.

Поэтому zombie - это не зависший процесс

Это, пожалуй, самая частая ошибка в понимании этого состояния.

Если программа зависла, она всё ещё существует и может занимать CPU, ждать диск, сеть, блокировку или какой-нибудь другой ресурс.

Zombie находится в совершенно другой ситуации.

Он уже не выполняется.

Если посмотреть на процессы:

//text

PID    STAT    COMMAND
4210   Z       my_program

Z означает именно zombie.

У него нет смысла отнимать процессорное время. Нечему его выполнять.

И поэтому команда вроде kill 4210 не решает проблему так, как многие ожидают. Процесс уже завершён. Убивать здесь фактически нечего.

Откуда тогда берётся проблема

Сам по себе один zombie - не катастрофа.

Проблема появляется, когда родитель создаёт дочерние процессы, они заканчивают работу, а родитель не вызывает wait().

Например:

//text

родитель
 ├── ребёнок → завершился
 ├── ребёнок → завершился
 ├── ребёнок → завершился
 ├── ребёнок → завершился
 └── ребёнок → завершился

Если статусы этих процессов никто не забирает, записи начинают накапливаться.

Это уже неприятно не потому, что каждый zombie внезапно начинает потреблять процессор или гигабайты памяти. Он как раз почти ничего не делает.

Проблема в количестве.

У системы конечные ресурсы для учёта процессов и PID. Если программа способна оставить после себя тысячи таких записей, рано или поздно это начинает мешать созданию новых процессов.

И тогда zombie превращается из любопытной особенности Unix в вполне настоящий баг родительской программы.

А если родитель умер первым?

Тут возникает ещё один естественный вопрос.

Что делать с zombie, если программа, которая должна была вызвать wait(), сама завершилась?

Для этого в Unix-подобных системах предусмотрен механизм переподчинения осиротевших процессов. Их может принять системный процесс или другой процесс, назначенный субреапером.

После этого новый родитель может забрать статус завершившегося процесса.

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

Почему вообще используется такое решение

На первый взгляд всё это кажется довольно странным.

Процесс завершился - почему бы просто не считать его исчезнувшим?

Потому что процесс - это не только выполняющийся код.

Есть ещё его отношения с родителем.

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

Если представить процесс как человека, который выполняет работу, то zombie - это не человек, который продолжает ходить после смерти.

Скорее, это карточка с результатом работы, которую ещё не забрали из системы.

Именно поэтому аналогия с «ожившим процессом» немного обманчива. В zombie нет никакой жизни.

Есть только оставшийся статус.

Самое важное здесь - разделить две вещи

Когда говорят «процесс завершился», обычно представляют одно событие:

//text

работал → умер → исчез

В Unix это чуть сложнее:

//text

работал
   ↓
завершился
   ↓
ядро сохранило статус
   ↓
родитель вызвал wait()
   ↓
запись удалена

И вот этот небольшой промежуток между двумя последними событиями и породил само понятие zombie.

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

Так что zombie - это не странная ошибка Linux и не процесс, который каким-то образом отказался умирать.

Это скорее след от уже завершившейся работы.

Пока родитель его не забрал - след остаётся.

Комментарии

Пока нет комментариев. Будьте первым, кто напишет.

Чтобы оставить комментарий, войдите в аккаунт.

Похожие статьи