В чем причина выполнения двойного форка при создании демона? - PullRequest
150 голосов
/ 19 мая 2009

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

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

Ответы [ 9 ]

156 голосов
/ 22 марта 2011

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

В 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. Истинно демонизирован.

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

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

102 голосов
/ 19 мая 2009

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

Разветвите второго ребенка и немедленно выйдите, чтобы предотвратить появление зомби. это заставляет второй дочерний процесс быть осиротевшим, делая иници процесс отвечает за его очистку. И, так как первый ребенок лидер сеанса без управляющего терминала, это возможно для приобрести его, открыв в будущем терминал (Система V- основанные системы). Эта вторая вилка гарантирует, что ребенка нет более длинный лидер сеанса, предотвращающий когда-либо демона управляющий терминал.

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

100 голосов
/ 01 мая 2013

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

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

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

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

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

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

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

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

11 голосов
/ 19 мая 2009

Взято из Плохой CTK :

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

7 голосов
/ 19 мая 2009

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

3 голосов
/ 19 мая 2009

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

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

2 голосов
/ 19 мая 2009

Приличное обсуждение этого, кажется, в http://www.developerweb.net/forum/showthread.php?t=3025

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

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

2 голосов
/ 19 мая 2009

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

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

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

0 голосов
/ 28 августа 2018

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

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