Qual è il motivo per eseguire un doppio fork durante la creazione di un demone?


165

Sto cercando di creare un demone in Python. Ho trovato la seguente domanda , che contiene alcune buone risorse che sto seguendo attualmente, ma sono curioso di sapere perché è necessario un doppio fork. Ho grattato su google e ho trovato molte risorse dichiarando che è necessario, ma non perché.

Alcuni affermano che è per impedire al demone di acquisire un terminale di controllo. Come lo farebbe senza il secondo fork? Quali sono le ripercussioni?



2
Una difficoltà nel fare un doppio fork è che il genitore non può facilmente ottenere il PID del processo nipote (la fork()chiamata restituisce il PID del bambino al genitore, quindi è facile ottenere il PID del processo figlio, ma non è così facile ottenere il PID del processo nipote ).
Craig McQueen,

Risposte:


105

Guardando il codice a cui si fa riferimento nella domanda, la giustificazione è:

Biforca un secondo figlio ed esci immediatamente per prevenire gli zombi. Questo fa sì che il secondo processo figlio rimanga orfano, rendendo il processo init responsabile della sua pulizia. Inoltre, poiché il primo figlio è un leader di sessione senza un terminale di controllo, è possibile acquisirne uno aprendo un terminale in futuro (sistemi basati su System V). Questo secondo fork garantisce che il bambino non è più un leader della sessione, impedendo al demone di acquisire un terminale di controllo.

Quindi è per garantire che il demone sia riprogrammato su init (nel caso in cui il processo di avvio del demone sia di lunga durata) e rimuova ogni possibilità che il demone riacquisisca un tty di controllo. Quindi, se nessuno di questi casi si applica, una forcella dovrebbe essere sufficiente. " Unix Network Programming - Stevens " ha una buona sezione su questo.


28
Questo non è del tutto corretto. Il modo standard per creare un demone è semplicemente farlo p=fork(); if(p) exit(); setsid(). In questo caso, anche il genitore esce e il processo del primo figlio viene riparato. La magia a doppia forcella è necessaria solo per impedire al demone di acquisire un tty.
parasietje,

1
Quindi, a quanto ho capito, se il mio programma inizia e forksun childprocesso, questo primissimo processo figlio sarà un session leadere sarà in grado di aprire un terminale TTY. Ma se forzo di nuovo da questo bambino e termino questo primo figlio, il secondo figlio biforcuto non sarà un session leadere non sarà in grado di aprire un terminale TTY. Questa affermazione è corretta?
tonix,

2
@tonix: semplicemente il fork non crea un leader di sessione. Questo è fatto da setsid(). Quindi, il primo processo biforcato diventa un leader di sessione dopo aver chiamato setsid()e quindi eseguiamo nuovamente il fork in modo che il processo finale a doppio fork non sia più un leader di sessione. Oltre al requisito di setsid()essere un leader della sessione, sei perfetto.
dbmikus,

169

Stavo cercando di capire la doppia forcella e mi sono imbattuto in questa domanda qui. Dopo molte ricerche questo è quello che ho capito. Speriamo che aiuti a chiarire meglio le cose per chiunque abbia la stessa domanda.

In Unix ogni processo appartiene a un gruppo che a sua volta appartiene a una sessione. Ecco la gerarchia ...

Sessione (SID) → Gruppo di processi (PGID) → Processo (PID)

Il primo processo nel gruppo di processi diventa il leader del gruppo di processi e il primo processo nella sessione diventa il leader della sessione. Ad ogni sessione può essere associato un TTY. Solo un leader di sessione può assumere il controllo di un TTY. Affinché un processo sia veramente demonizzato (eseguito in background) dovremmo assicurarci che il leader della sessione venga ucciso in modo che non ci sia alcuna possibilità che la sessione prenda il controllo del TTY.

Ho eseguito il programma demone di esempio python di Sander Marechal da questo sito sul mio Ubuntu. Ecco i risultati con i miei commenti.

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

Nota che il processo è il leader della sessione dopo Decouple#1, perché lo è PID = SID. Potrebbe ancora prendere il controllo di un TTY.

Nota che Fork#2non è più il leader della sessione PID != SID. Questo processo non può mai assumere il controllo di un TTY. Davvero demone.

Personalmente trovo che la terminologia fork-due volte sia fonte di confusione. Un linguaggio migliore potrebbe essere fork-decouple-fork.

Ulteriori collegamenti di interesse:


Il fork di due volte impedisce anche la creazione di zombi quando il processo padre viene eseguito per un tempo più lungo e per qualche motivo la rimozione del gestore predefinito per il segnale che informa che il processo è morto.
Trismegistos,

Ma secondo per può anche chiamare il disaccoppiamento e diventare leader di sessione e quindi acquisire terminale.
Trismegistos,

2
Questo non è vero. Il primo fork()impedisce già la creazione di zombi, purché tu chiuda il genitore.
parasietje,

1
Un esempio minimo per produrre i risultati sopra citati: gist.github.com/cannium/7aa58f13c834920bb32c
can.

1
Sarebbe utile chiamare setsid() prima di un singolo fork()? In realtà immagino che le risposte a questa domanda rispondano a questo.
Craig McQueen,

118

A rigor di termini, il doppio fork non ha nulla a che fare con il ri-genitorialità del demone come figlio di init. Tutto ciò che è necessario per ricoprire il genitore è che il genitore debba uscire. Questo può essere fatto con una sola forcella. Inoltre, fare un doppio fork da solo non riconduce il processo daemon init; il genitore del demone deve uscire. In altre parole, il genitore esce sempre quando si biforca un demone appropriato in modo che il processo del demone venga riprogrammato init.

Allora perché la doppia forcella? POSIX.1-2008 Sezione 11.1.3, " Il terminale di controllo ", ha la risposta (enfasi aggiunta):

Il terminale di controllo per una sessione è assegnato dal leader della sessione in un modo definito dall'implementazione. Se un leader di sessione non ha un terminale di controllo e apre un file di dispositivo terminale che non è già associato a una sessione senza utilizzare l' O_NOCTTYopzione (vedere open()), viene definito dall'implementazione se il terminale diventa il terminale di controllo del leader di sessione. Se un processo che non è un leader della sessione apre un file di terminale, o l' O_NOCTTYopzione è usata su open(), quel terminale non diventerà il terminale di controllo del processo di chiamata .

Questo ci dice che se un processo daemon fa qualcosa del genere ...

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

... quindi il processo daemon potrebbe essere acquisito /dev/consolecome terminale di controllo, a seconda che il processo daemon sia un leader della sessione e in base all'implementazione del sistema. Il programma può garantire che la chiamata di cui sopra non acquisirà un terminale di controllo se il programma garantisce innanzitutto che non è un leader della sessione.

Normalmente, quando si avvia un demone, setsidviene chiamato (dal processo figlio dopo aver chiamato fork) per dissociare il demone dal suo terminale di controllo. Tuttavia, chiamare setsidsignifica anche che il processo di chiamata sarà il leader della sessione della nuova sessione, il che lascia aperta la possibilità che il demone possa riacquistare un terminale di controllo. La tecnica a doppio fork assicura che il processo daemon non sia il leader della sessione, il che garantisce che una chiamata a open, come nell'esempio sopra, non comporterà il processo daemon che riacquista un terminale di controllo.

La tecnica della doppia forcella è un po 'paranoica. Potrebbe non essere necessario se sai che il demone non aprirà mai un file del dispositivo terminale. Inoltre, su alcuni sistemi potrebbe non essere necessario anche se il demone apre un file del dispositivo terminale, poiché tale comportamento è definito dall'implementazione. Tuttavia, una cosa che non è definita dall'implementazione è che solo un leader di sessione può allocare il terminale di controllo. Se un processo non è un leader di sessione, non può allocare un terminale di controllo. Pertanto, se si desidera essere paranoici ed essere certi che il processo daemon non possa inavvertitamente acquisire un terminale di controllo, indipendentemente da eventuali specifiche definite dall'implementazione, la tecnica del doppio fork è essenziale.


3
+1 Peccato che questa risposta sia arrivata ~ quattro anni dopo la domanda.
Tim Seguine,

12
Ma ciò non spiega ancora perché sia ​​così terribilmente importante che un demone non possa riacquistare un terminale di controllo
UloPe,

7
La parola chiave è "inavvertitamente" acquisire un terminale di controllo. Se il processo apre un terminale e diventa il terminale di controllo dei processi, allora se qualcuno emette un ^ C da quel terminale potrebbe terminare il processo. Quindi potrebbe essere bello proteggere un processo dall'avvenire inavvertitamente. Personalmente mi atterrò a un singolo fork e setsid () per il codice che scrivo che so che non apriranno i terminali.
BobDoolittle

1
@BobDoolittle come è potuto accadere "inavvertitamente"? Un processo non finirà necessariamente per aprire terminali se non è scritto per farlo. Forse il doppio fork è utile se il programmatore non conosce il codice e non sa se potrebbe aprire un tty.
Marius,

10
@Marius Immaginate cosa potrebbe accadere se si aggiunge una linea come questa al file di configurazione del demone: LogFile=/dev/console. I programmi non hanno sempre il controllo in fase di compilazione su quali file potrebbero aprire;)
Dan Molding

11

Tratto da Bad CTK :

"Su alcune versioni di Unix, sei costretto a fare un doppio fork all'avvio, per passare alla modalità demone. Questo perché il singolo fork non è garantito per staccarsi dal terminale di controllo."


3
Come può una singola forcella non staccarsi dal terminale di controllo ma una doppia forcella può farlo? Su quali unix si verifica?
bdonlan,

12
Un demone deve chiudere i descrittori dei file di input e output (fds), altrimenti sarebbe comunque collegato al terminale in cui è stato avviato. Un processo a fork eredita quelli dal genitore. Apparentemente, il primo figlio chiude il disco ma non pulisce tutto. Sul secondo fork, i fds non esistono, quindi il secondo figlio non può più essere collegato a nulla.
Aaron Digulla,

4
@Aaron: No, un demone si "stacca" correttamente dal suo terminale di controllo chiamando setsiddopo un fork iniziale. Quindi assicura che rimanga distaccato da un terminale di controllo facendo nuovamente fork e facendo setsiduscire il leader della sessione (il processo che ha chiamato ).
Dan Molding

2
@bdonlan: non è forkche si stacchi dal terminale di controllo. È setsidquello che fa. Ma setsidfallirà se viene chiamato da un leader del gruppo di processo. Quindi è forknecessario eseguire un'iniziale prima setsiddi assicurarsi che setsidvenga chiamato da un processo che non è un leader del gruppo di processi. Il secondo forkassicura che il processo finale (quello che sarà il demone) non sarà un leader della sessione. Solo i leader della sessione possono acquisire un terminale di controllo, quindi questo secondo fork garantisce che il demone non riacquista inavvertitamente un terminale di controllo. Questo vale per qualsiasi sistema operativo POSIX.
Dan Molding

@DanMoulding Questo non garantisce che il secondo figlio non acquisirà il terminale di controllo perché può chiamare setsid e diventare leader della sessione e quindi acquisire il terminale di controllo.
Trismegistos,

7

Secondo "Advanced Programming in the Unix Environment", di Stephens e Rago, il secondo fork è più una raccomandazione e viene fatto per garantire che il demone non acquisisca un terminale di controllo su sistemi basati su System V.


3

Uno dei motivi è che il processo padre può immediatamente wait_pid () per il figlio e poi dimenticarsene. Quando poi il nipote muore, il suo genitore è init e aspetterà () per farlo - e portandolo fuori dallo stato zombi.

Il risultato è che il processo genitore non ha bisogno di essere a conoscenza dei figli biforcati, e rende anche possibile biforcare processi di lunga durata da librerie ecc.


2

La chiamata daemon () ha la chiamata principale _exit () se ha esito positivo. La motivazione originale potrebbe essere stata quella di consentire al genitore di fare qualche lavoro extra mentre il bambino sta demonizzando.

Potrebbe anche essere basato su una convinzione errata che sia necessario per garantire che il demone non abbia alcun processo genitore e che sia reintegrato in init, ma ciò accadrà comunque una volta che il genitore muore nel caso single fork.

Quindi suppongo che alla fine tutto si riduca alla tradizione: una sola forchetta è sufficiente finché il genitore muore comunque in breve tempo.



-1

Potrebbe essere più facile da capire in questo modo:

  • Il primo fork e setsid creeranno una nuova sessione (ma l'ID processo == ID sessione).
  • Il secondo fork si assicura che l'ID processo! = ID sessione.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.