В чем причина создания двойного форка при создании демона?

Я пытаюсь создать демон в Python. Я нашел следующий вопрос, в котором есть несколько полезных ресурсов, за которым я сейчас следую, но мне любопытно, зачем нужна двойная вилка. Я покопался в гугле и нашел множество ресурсов, объявляющих, что это необходимо, но не зачем.

Некоторые упоминают, что это предотвращает приобретение демоном управляющего терминала. Как бы это сделать без второй развилки? Каковы последствия?

9 ответов

Решение

Глядя на код, указанный в вопросе, обоснование:

# Fork a second child and exit immediately to prevent zombies.  This
# causes the second child process to be orphaned, making the init
# process responsible for its cleanup.  And, since the first child is
# a session leader without a controlling terminal, it's possible for
# it to acquire one by opening a terminal in the future (System V-
# based systems).  This second fork guarantees that the child is no
# longer a session leader, preventing the daemon from ever acquiring
# a controlling terminal.

Таким образом, он должен гарантировать, что демон повторно связан с init (на случай, если процесс, запускающий демон, является долгоживущим), и исключает любые шансы демона на повторное получение управляющего tty. Так что, если ни один из этих случаев не применим, то одной вилки должно быть достаточно. " Сетевое программирование Unix - Стивенс" имеет хороший раздел на эту тему.

Я пытался понять двойную вилку и наткнулся на этот вопрос здесь. После долгих исследований это то, что я понял. Надеюсь, это поможет прояснить ситуацию лучше для тех, у кого такой же вопрос.

В Unix каждый процесс принадлежит группе, которая, в свою очередь, принадлежит сеансу. Вот иерархия...

Сеанс (SID) → Группа процессов (PGID) → Процесс (PID)

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

Я запустил программу-демон Сандера Марешала на Python с этого сайта на моем Ubuntu. Вот результаты с моими комментариями.

1. `Parent`    = PID: 28084, PGID: 28084, SID: 28046
2. `Fork#1`    = PID: 28085, PGID: 28084, SID: 28046
3. `Decouple#1`= PID: 28085, PGID: 28085, SID: 28085
4. `Fork#2`    = PID: 28086, PGID: 28085, SID: 28085

Обратите внимание, что процесс является лидером сеанса после Decouple#1, потому что это PID = SID, Это все еще может взять под контроль TTY.

Обратите внимание, что Fork#2 больше не лидер сессии PID != SID, Этот процесс никогда не может взять под контроль TTY. Истинно демонизирован.

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

Дополнительные интересные ссылки:

Строго говоря, двойная вилка не имеет ничего общего с повторным воспитанием демона как потомка init, Все, что необходимо для повторного воспитания ребенка, это то, что родитель должен выйти. Это может быть сделано только с одной вилкой. Кроме того, двойное разветвление само по себе не переопределяет процесс-демон init; родитель демона должен выйти. Другими словами, родитель всегда выходит, когда разветвляет правильного демона, так что процесс демона переименовывается в init,

Так почему двойная вилка? POSIX.1-2008 Раздел 11.1.3, " Терминал управления", содержит ответ (выделение добавлено):

Управляющий терминал для сеанса назначается руководителем сеанса в соответствии с реализацией. Если руководитель сеанса не имеет управляющего терминала и открывает файл терминального устройства, который еще не связан с сеансом, без использования опции O_NOCTTY (см. Open()), то определяется, будет ли реализация управлять терминалом сеанса. лидер. Если процесс, который не является лидером сеанса, открывает файл терминала или в open () используется опция O_NOCTTY, то этот терминал не должен становиться управляющим терминалом вызывающего процесса.

Это говорит нам о том, что если процесс-демон делает что-то вроде этого...

int fd = open("/dev/console", O_RDWR);

... тогда процесс демона может получить /dev/console в качестве управляющего терминала, в зависимости от того, является ли процесс-демон лидером сеанса, и в зависимости от реализации системы. Программа может гарантировать, что вышеуказанный вызов не получит управляющий терминал, если программа сначала гарантирует, что он не является лидером сеанса.

Обычно при запуске демона, setsid вызывается (из дочернего процесса после вызова forkотсоединить демона от управляющего терминала. Тем не менее, призывая setsid также означает, что вызывающий процесс будет лидером сеанса нового сеанса, что оставляет открытой возможность того, что демон может повторно получить управляющий терминал. Метод двойного разветвления гарантирует, что процесс-демон не является лидером сеанса, что затем гарантирует, что вызов openкак в примере выше, не приведет к тому, что процесс-демон снова запросит управляющий терминал.

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

Взято из Bad CTK:

"В некоторых разновидностях Unix вы вынуждены делать двойную развилку при запуске, чтобы перейти в режим демона. Это потому, что одиночное разветвление не гарантирует отсоединение от управляющего терминала".

Согласно "Расширенному программированию в среде Unix" Стивенса и Раго, второй ответвление является скорее рекомендацией, и оно сделано для того, чтобы гарантировать, что демон не получит управляющий терминал в системах на основе System V.

Одна из причин заключается в том, что родительский процесс может немедленно поднять wait_pid() для потомка, а затем забыть об этом. Когда потом умирает внучка, его родителем является init, и он будет ждать () этого - и выводить его из состояния зомби.

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

Это может быть легче понять следующим образом:

  • Первый форк и setsid создадут новый сеанс (но идентификатор процесса == идентификатор сеанса).
  • Вторая ветвь удостоверяется, что идентификатор процесса!= Идентификатор сессии.

Достойное обсуждение этого, кажется, находится на http://www.developerweb.net/forum/showthread.php?t=3025

Цитирую Млампкина оттуда:

... думать о вызове setsid() как о "новом" способе выполнения действий (отсоединении от терминала) и о вызове [second] fork() после него как о избыточности для работы с SVr4...

Вызов daemon() имеет родительский вызов _exit() в случае успеха. Первоначальная мотивация, возможно, заключалась в том, чтобы позволить родителю выполнять дополнительную работу, пока ребенок демонизирует.

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

Итак, я полагаю, что в конце концов все сводится к традиции - достаточно одной развилки, если родитель все равно умрет в коротком порядке.

Другие вопросы по тегам