¿Cuál es la razón para realizar una doble bifurcación al crear un demonio?

165

Estoy tratando de crear un demonio en python. He encontrado la siguiente pregunta , que tiene algunos buenos recursos que estoy siguiendo actualmente, pero tengo curiosidad por saber por qué es necesaria una doble bifurcación. Busqué en Google y encontré muchos recursos declarando que uno es necesario, pero no por qué.

Algunos mencionan que es para evitar que el demonio adquiera un terminal de control. ¿Cómo haría esto sin el segundo tenedor? ¿Cuáles son las repercusiones?

Shabbyrobe
fuente
2
Una dificultad para hacer una bifurcación doble es que el padre no puede obtener fácilmente el PID del proceso del nieto (la fork()llamada devuelve el PID del niño al padre, por lo que es fácil obtener el PID del proceso hijo, pero no es tan fácil obtener el PID del proceso de nieto ).
Craig McQueen el

Respuestas:

105

Mirando el código al que se hace referencia en la pregunta, la justificación es:

Bifurca a un segundo niño y sal de inmediato para evitar zombis. Esto hace que el segundo proceso secundario quede huérfano, haciendo que el proceso init sea responsable de su limpieza. Y, dado que el primer hijo es un líder de sesión sin un terminal de control, es posible que adquiera uno abriendo un terminal en el futuro (sistemas basados ​​en el Sistema V). Este segundo tenedor garantiza que el niño ya no sea un líder de sesión, evitando que el demonio adquiera una terminal de control.

Por lo tanto, es para asegurarse de que el demonio se vuelva a criar en init (solo en caso de que el proceso de inicio del demonio sea de larga duración) y elimine cualquier posibilidad de que el demonio recupere un tty de control. Entonces, si ninguno de estos casos se aplica, entonces una bifurcación debería ser suficiente. " Programación de red Unix - Stevens " tiene una buena sección sobre esto.

Comilona
fuente
28
Esto no es del todo correcto. La forma estándar de crear un demonio es simplemente hacerlo p=fork(); if(p) exit(); setsid(). En este caso, el padre también sale y el proceso del primer hijo es reparentado. La magia de doble tenedor solo es necesaria para evitar que el demonio adquiera un tty.
parasietje
1
Entonces, según tengo entendido, si mi programa se inicia y es forksun childproceso, este primer proceso secundario será session leadery podrá abrir un terminal TTY. Pero si vuelvo a bifurcar de este niño y termino este primer niño, el segundo niño bifurcado no será session leadery no podrá abrir una terminal TTY. ¿Es correcta esta afirmación?
tonix
2
@tonix: simplemente bifurcar no crea un líder de sesión. Eso es hecho por setsid(). Entonces, el primer proceso bifurcado se convierte en un líder de sesión después de llamar setsid()y luego se bifurca nuevamente para que el proceso final de doble bifurcación ya no sea un líder de sesión. Aparte del requisito de setsid()ser un líder de sesión, eres perfecto.
dbmikus
169

Estaba tratando de entender el doble tenedor y me topé con esta pregunta aquí. Después de mucha investigación, esto es lo que descubrí. Con suerte, ayudará a aclarar mejor las cosas para cualquiera que tenga la misma pregunta.

En Unix cada proceso pertenece a un grupo que a su vez pertenece a una sesión. Aquí está la jerarquía ...

Sesión (SID) → Grupo de proceso (PGID) → Proceso (PID)

El primer proceso en el grupo de proceso se convierte en el líder del grupo de proceso y el primer proceso en la sesión se convierte en el líder de la sesión. Cada sesión puede tener un TTY asociado. Solo un líder de sesión puede tomar el control de un TTY. Para que un proceso sea realmente demonizado (ejecutado en segundo plano) debemos asegurarnos de que el líder de la sesión sea asesinado para que no haya posibilidad de que la sesión tome el control del TTY.

Ejecuté el programa de demonio de ejemplo Python de Sander Marechal desde este sitio en mi Ubuntu. Aquí están los resultados con mis comentarios.

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

Tenga en cuenta que el proceso es el líder de la sesión después Decouple#1, porque lo es PID = SID. Todavía podría tomar el control de un TTY.

Tenga en cuenta que Fork#2ya no es el líder de la sesión PID != SID. Este proceso nunca puede tomar el control de un TTY. Realmente demonizado.

Personalmente, encuentro que la terminología fork-two es confusa. Un idioma mejor podría ser tenedor-desacoplador-tenedor.

Enlaces de interés adicionales:

Praveen Gollakota
fuente
La bifurcación dos veces también evita la creación de zombis cuando el proceso principal se ejecuta durante más tiempo y, por alguna razón, elimina el controlador predeterminado para la señal que informa que ese proceso murió.
Trismegistos
Pero en segundo lugar también puede llamar a desacoplarse y convertirse en líder de sesión y luego adquirir terminal.
Trismegistos
2
Esto no es verdad. El primero fork()ya evita la creación de zombies, siempre que cierre al padre.
parasietje
1
Un ejemplo mínimo para producir los resultados citados anteriormente: gist.github.com/cannium/7aa58f13c834920bb32c
can.
1
¿Sería bueno llamar setsid() antes de un sencillo fork()? En realidad, supongo que las respuestas de esta pregunta responden eso.
Craig McQueen el
118

Hablando estrictamente, el doble tenedor no tiene nada que ver con volver a criar al demonio como hijo de init. Todo lo que se necesita para volver a criar al hijo es que el padre debe salir. Esto se puede hacer con un solo tenedor. Además, hacer una doble bifurcación por sí solo no vuelve a crear el proceso del demonio init; los padres del demonio deben salir. En otras palabras, el padre siempre sale cuando se bifurca un demonio apropiado para que el proceso del demonio se vuelva a parear init.

Entonces, ¿por qué el doble tenedor? POSIX.1-2008 La sección 11.1.3, " El terminal de control ", tiene la respuesta (énfasis agregado):

El líder de sesión asigna el terminal de control para una sesión de una manera definida por la implementación. Si un líder de sesión no tiene terminal de control y abre un archivo de dispositivo de terminal que no está asociado con una sesión sin usar la O_NOCTTYopción (ver open()), se define la implementación si el terminal se convierte en el terminal de control del líder de sesión. Si un proceso que no es un líder de sesión abre un archivo de terminal, o O_NOCTTYse usa la opción open(), entonces ese terminal no se convertirá en el terminal de control del proceso de llamada .

Esto nos dice que si un proceso de demonio hace algo como esto ...

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

... entonces el proceso del demonio podría adquirir /dev/consolecomo su terminal de control, dependiendo de si el proceso del demonio es un líder de sesión, y dependiendo de la implementación del sistema. El programa puede garantizar que la llamada anterior no adquirirá un terminal de control si el programa primero asegura que no es un líder de sesión.

Normalmente, cuando se inicia un demonio, setsidse llama (desde el proceso secundario después de llamar fork) para disociar el demonio de su terminal de control. Sin embargo, llamar setsidtambién significa que el proceso de llamada será el líder de la sesión de la nueva sesión, lo que deja abierta la posibilidad de que el demonio pueda volver a adquirir un terminal de control. La técnica de doble tenedor garantiza que el proceso del demonio no sea el líder de la sesión, lo que garantiza que una llamada a open, como en el ejemplo anterior, no dará como resultado que el proceso del demonio vuelva a adquirir un terminal de control.

La técnica del doble tenedor es un poco paranoica. Es posible que no sea necesario si sabe que el demonio nunca abrirá un archivo de dispositivo terminal. Además, en algunos sistemas puede no ser necesario incluso si el demonio abre un archivo de dispositivo terminal, ya que ese comportamiento está definido por la implementación. Sin embargo, una cosa que no está definida por la implementación es que solo un líder de sesión puede asignar el terminal de control. Si un proceso no es un líder de sesión, no puede asignar un terminal de control. Por lo tanto, si desea ser paranoico y estar seguro de que el proceso del demonio no puede adquirir accidentalmente un terminal de control, independientemente de los detalles específicos de la implementación, entonces la técnica de doble tenedor es esencial.

Dan Molding
fuente
3
+1 Lástima que esta respuesta llegó ~ cuatro años después de la pregunta.
Tim Seguine
12
Pero eso todavía no explica por qué es tan terriblemente importante que un demonio no puede volver a adquirir un terminal de control
UloPe
77
La palabra clave es "inadvertidamente" adquirir un terminal de control. Si el proceso abre un terminal y se convierte en el terminal de control de procesos, entonces si alguien emite un ^ C desde ese terminal, podría terminar el proceso. Por lo tanto, podría ser bueno proteger un proceso de que eso le suceda sin darse cuenta. Personalmente, me adheriré a una sola bifurcación y setsid () para el código que escribo que sé que no abrirá terminales.
BobDoolittle
1
@BobDoolittle, ¿cómo podría suceder esto "sin darse cuenta"? Un proceso no solo terminará abriendo terminales si no está escrito para hacerlo. Tal vez la doble bifurcación es útil si el programador no conoce el código y no sabe si podría abrir un tty.
Marius
10
@Marius Imagínese lo que podría ocurrir si se agrega una línea como esta en el archivo de configuración del daemon: LogFile=/dev/console. Los programas no siempre tienen control en tiempo de compilación sobre qué archivos pueden abrir;)
Dan Molding
11

Tomado de Bad CTK :

"En algunos sabores de Unix, se te obliga a hacer una doble bifurcación en el inicio, para entrar en modo demonio. Esto se debe a que no se garantiza que la bifurcación individual se separe del terminal de control".

Stefan Thyberg
fuente
3
¿Cómo puede una horquilla simple no separarse de la terminal de control pero la horquilla doble puede hacerlo? ¿En qué unixes ocurre esto?
bdonlan
12
Un demonio debe cerrar sus descriptores de archivo de entrada y salida (fds), de lo contrario, todavía estaría conectado al terminal en el que se inició. Un proceso bifurcado hereda los del padre. Aparentemente, el primer niño cierra los fds pero eso no limpia todo. En la segunda bifurcación, los fds no existen, por lo que el segundo hijo ya no puede conectarse a nada.
Aaron Digulla
44
@ Aaron: No, un demonio se "separa" correctamente de su terminal de control llamando setsiddespués de una bifurcación inicial. Luego se asegura de que se mantenga separado de un terminal de control bifurcando nuevamente y haciendo que el líder de la sesión (el proceso que llamó setsid) salga.
Dan Molding
2
@bdonlan: No es forkque se separe de la terminal de control. Es setsideso lo que hace. Pero setsidfallará si se llama desde un líder de grupo de proceso. Por lo tanto, se forkdebe hacer una inicial antes setsidpara garantizar que setsidse llame desde un proceso que no sea un líder de grupo de procesos. El segundo forkasegura que el proceso final (el que será el demonio) no sea un líder de sesión. Solo los líderes de sesión pueden adquirir un terminal de control, por lo que esta segunda bifurcación garantiza que el demonio no volverá a adquirir un terminal de control sin darse cuenta. Esto es cierto para cualquier sistema operativo POSIX.
Dan Molding
@DanMoulding Esto no garantiza que el segundo hijo no adquiera el terminal de control porque puede llamar a setsid y convertirse en líder de la sesión y luego adquirir el terminal de control.
Trismegistos
7

Según "Programación avanzada en el entorno Unix", de Stephens y Rago, la segunda bifurcación es más una recomendación, y se hace para garantizar que el demonio no adquiera un terminal de control en los sistemas basados ​​en el sistema V.

Paolo Tedesco
fuente
3

Una de las razones es que el proceso padre puede esperar inmediatamente_pid () para el hijo y luego olvidarse de él. Cuando el nieto muere, su padre es init, y lo esperará () y lo sacará del estado zombie.

El resultado es que el proceso padre no necesita estar al tanto de los niños bifurcados, y también hace posible bifurcar procesos de ejecución prolongada desde libs, etc.

KarlP
fuente
2

La llamada daemon () tiene la llamada padre _exit () si tiene éxito. La motivación original puede haber sido permitir que el padre haga un trabajo extra mientras el niño está demonizando.

También puede basarse en una creencia errónea de que es necesario para garantizar que el demonio no tenga un proceso principal y esté reparentado para iniciar, pero esto sucederá de todos modos una vez que el padre muera en el caso de la bifurcación individual.

Así que supongo que todo se reduce a la tradición al final: un solo tenedor es suficiente siempre que el padre muera en poco tiempo de todos modos.

bdonlan
fuente
2

Parece que hay una discusión decente en http://www.developerweb.net/forum/showthread.php?t=3025

Citando a mlampkin desde allí:

... piense en la llamada setsid () como la "nueva" forma de hacer las cosas (disociarse del terminal) y la llamada [segunda] fork () después como una redundancia para tratar con el SVr4 ...

Stobor
fuente
-1

Puede ser más fácil de entender de esta manera:

  • La primera bifurcación y setsid crearán una nueva sesión (pero el ID del proceso == ID de la sesión).
  • La segunda bifurcación asegura que la ID del proceso! = ID de sesión.
pandy.song
fuente