Qual é o motivo para executar uma bifurcação dupla ao criar um daemon?


165

Estou tentando criar um daemon em python. Encontrei a seguinte pergunta , que possui alguns bons recursos que estou seguindo atualmente, mas estou curioso para saber por que um garfo duplo é necessário. Eu procurei no google e encontrei muitos recursos declarando que um é necessário, mas não o porquê.

Alguns mencionam que é para impedir que o daemon adquira um terminal de controle. Como isso seria feito sem o segundo garfo? Quais são as repercussões?



2
Uma dificuldade em fazer uma bifurcação dupla é que o pai não pode obter facilmente o PID do processo neto (a fork()chamada retorna o PID da criança ao pai, por isso é fácil obter o PID do processo filho, mas não é tão fácil obter o PID do processo do neto ).
Craig McQueen

Respostas:


105

Observando o código referenciado na pergunta, a justificativa é:

Garfo um segundo filho e saia imediatamente para evitar zumbis. Isso faz com que o segundo processo filho fique órfão, tornando o processo init responsável por sua limpeza. E, como o primeiro filho é um líder de sessão sem um terminal de controle, é possível adquirir um abrindo um terminal no futuro (sistemas baseados no System V). Essa segunda bifurcação garante que a criança não seja mais uma líder de sessão, impedindo que o daemon adquira um terminal de controle.

Portanto, é para garantir que o daemon seja reinicializado no init (apenas no caso de o processo iniciar o daemon durar muito) e remove qualquer chance de o daemon recuperar um tty de controle. Portanto, se nenhum desses casos se aplicar, um garfo deverá ser suficiente. " Unix Network Programming - Stevens " tem uma boa seção sobre isso.


28
Isso não é inteiramente correto. A maneira padrão de criar um daemon é simplesmente fazer p=fork(); if(p) exit(); setsid(). Nesse caso, o pai também sai e o processo do primeiro filho é reparado. A mágica de garfo duplo é necessária apenas para impedir que o daemon adquira um tty.
Parietietje

1
Então, pelo que entendi, se meu programa iniciar e forksum childprocesso, esse primeiro processo filho será um session leadere poderá abrir um terminal TTY. Mas se eu retirar novamente esse filho e terminar este primeiro filho, o segundo filho bifurcado não será um session leadere não poderá abrir um terminal TTY. Esta afirmação está correta?
tonix

2
@tonix: simplesmente bifurcar não cria um líder de sessão. Isso é feito por setsid(). Portanto, o primeiro processo bifurcado se torna um líder de sessão após a chamada setsid()e depois bifurcamos novamente para que o processo final de bifurcação não seja mais um líder de sessão. Além do requisito de setsid()ser um líder de sessão, você está no local.
Dbmikus 23/05

169

Eu estava tentando entender o garfo duplo e me deparei com essa pergunta aqui. Depois de muita pesquisa, foi isso que eu descobri. Espero que ajude a esclarecer melhor as coisas para quem tem a mesma pergunta.

No Unix, todo processo pertence a um grupo que, por sua vez, pertence a uma sessão. Aqui está a hierarquia…

Sessão (SID) → Grupo de processos (PGID) → Processo (PID)

O primeiro processo no grupo de processos se torna o líder do grupo de processos e o primeiro processo na sessão se torna o líder da sessão. Cada sessão pode ter um TTY associado a ela. Somente um líder de sessão pode assumir o controle de um TTY. Para que um processo seja verdadeiramente daemonizado (executado em segundo plano), devemos garantir que o líder da sessão seja morto para que não haja possibilidade da sessão assumir o controle do TTY.

Executei o programa de daemon de exemplo python de Sander Marechal neste site no meu Ubuntu. Aqui estão os resultados com meus comentários.

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

Observe que o processo é o líder da sessão depois Decouple#1, porque éPID = SID . Ainda poderia assumir o controle de um TTY.

Observe que Fork#2não é mais o líder da sessão PID != SID. Esse processo nunca pode assumir o controle de um TTY.Verdadeiramente daemonized.

Pessoalmente, acho que a terminologia é duas vezes confusa. Um idioma melhor pode ser garfo-desacoplamento-garfo.

Links adicionais de interesse:


A bifurcação duas vezes também impede a criação de zumbis quando o processo pai é executado por mais tempo e, por algum motivo, a remoção do manipulador padrão do sinal que informa que o processo morreu.
Trismegistos 28/03

Mas o segundo também pode chamar desacoplamento e tornar-se o líder da sessão e depois adquirir o terminal.
Trismegistos

2
Isso não é verdade. O primeiro fork()já impede a criação de zumbis, desde que você feche o pai.
Parietietje

1
Um exemplo mínimo para produzir os resultados citados acima: gist.github.com/cannium/7aa58f13c834920bb32c
can.

1
Seria bom ligar setsid() antes de um single fork()? Na verdade, acho que as respostas dessa pergunta respondem a isso.
Craig McQueen

118

Estritamente falando, a bifurcação dupla não tem nada a ver com re-criação do daemon como filho de init. Tudo o que é necessário para re-pai do filho é que ele deve sair. Isso pode ser feito com apenas um único garfo. Além disso, fazer uma bifurcação por si só não reinicia o processo daemon init; o pai do daemon deve sair. Em outras palavras, o pai sempre sai quando bifurca um daemon adequado, para que o processo do daemon seja re-pai init.

Então, por que o garfo duplo? A Seção 11.1.3 do POSIX.1-2008 , " O terminal de controle ", tem a resposta (ênfase adicionada):

O terminal de controle de uma sessão é alocado pelo líder da sessão de uma maneira definida pela implementação. Se um líder de sessão não tiver um terminal de controle e abrir um arquivo de dispositivo de terminal que ainda não esteja associado a uma sessão sem usar a O_NOCTTYopção (consulte open()), será definido como implementação se o terminal se tornará o terminal de controle do líder de sessão. Se um processo que não seja um líder de sessão abrir um arquivo de terminal ou a O_NOCTTYopção for usada open(), esse terminal não se tornará o terminal de controle do processo de chamada .

Isso nos diz que, se um processo daemon fizer algo assim ...

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

... o processo daemon pode adquirir /dev/consolecomo seu terminal de controle, dependendo se o processo daemon é um líder de sessão e dependendo da implementação do sistema. O programa pode garantir que a chamada acima não adquira um terminal de controle se o programa primeiro garantir que não é um líder de sessão.

Normalmente, ao iniciar um daemon, setsidé chamado (do processo filho após a chamada fork) para dissociar o daemon do seu terminal de controle. No entanto, chamar setsidtambém significa que o processo de chamada será o líder da nova sessão, o que deixa em aberto a possibilidade de o daemon recuperar novamente um terminal de controle. A técnica de bifurcação dupla garante que o processo daemon não seja o líder da sessão, o que garante que uma chamada para open, como no exemplo acima, não resultará no processo daemon recuperando um terminal de controle.

A técnica de garfo duplo é um pouco paranóica. Pode não ser necessário se você souber que o daemon nunca abrirá um arquivo de dispositivo do terminal. Além disso, em alguns sistemas, pode não ser necessário, mesmo que o daemon abra um arquivo de dispositivo do terminal, pois esse comportamento é definido pela implementação. No entanto, uma coisa que não é definida pela implementação é que apenas um líder de sessão pode alocar o terminal de controle. Se um processo não for um líder de sessão, ele não poderá alocar um terminal de controle. Portanto, se você deseja ser paranóico e ter certeza de que o processo daemon não pode adquirir inadvertidamente um terminal de controle, independentemente de quaisquer especificações definidas pela implementação, a técnica de bifurcação dupla é essencial.


3
+1 Pena que esta resposta veio ~ quatro anos depois que a pergunta foi feita.
Tim Seguine 27/09/13

12
Mas isso ainda não explica por que é tão terrivelmente importante que um daemon não pode readquirir a controlar o terminal
UloPe

7
A palavra-chave é "inadvertidamente" adquirir um terminal de controle. Se o processo abrir um terminal e ele se tornar o terminal de controle de processos, se alguém emitir um ^ C desse terminal, ele poderá encerrar o processo. Portanto, pode ser bom proteger um processo de que isso aconteça inadvertidamente. Pessoalmente, vou me ater a um único garfo e setsid () para o código que escrevo que sei que não abrirá terminais.
precisa saber é o seguinte

1
@BobDoolittle, como isso aconteceu "inadvertidamente"? Um processo não acabará abrindo terminais se não estiver escrito para fazê-lo. Talvez a bifurcação dupla seja útil se o programador não souber o código e não souber se ele pode abrir um tty.
Marius

10
@Marius Imagine o que poderia acontecer se você adicionar uma linha como essa ao arquivo de configuração do daemon: LogFile=/dev/console. Programas nem sempre têm o controle em tempo de compilação sobre quais arquivos eles podem abrir;)
Dan Moulding

11

Retirado de Bad CTK :

"Em alguns tipos de Unix, você é forçado a fazer um garfo duplo na inicialização, para entrar no modo daemon. Isso ocorre porque não é garantido que o garfo único se solte do terminal de controle."


3
Como o garfo único não pode se desconectar do terminal de controle, mas o garfo duplo o faz? Em quais unixes isso ocorre?
bdonlan

12
Um daemon deve fechar seus descritores de arquivo de entrada e saída (fds), caso contrário, ele ainda estará anexado ao terminal em que foi iniciado. Um processo bifurcado herda os do pai. Aparentemente, o primeiro filho fecha o fds, mas isso não limpa tudo. No segundo fork, o fds não existe, então o segundo filho não pode mais ser conectado a nada.
21414 Aaron Digulla

4
@ Aaron: Não, um daemon se "desconecta" adequadamente de seu terminal de controle chamando setsidapós um fork inicial. Em seguida, garante que ele permaneça desconectado de um terminal de controle, bifurcando-o novamente e fazendo com que o líder da sessão (o processo que chamou setsid) saia.
Dan Molding

2
@dondonlan: Não é isso forkque se desconecta do terminal de controle. É setsidisso que faz. Mas setsidfalhará se for chamado de um líder de grupo de processos. Portanto, uma inicial forkdeve ser feita antes setsidpara garantir que setsidseja chamada de um processo que não seja um líder de grupo de processos. O segundo forkgarante que o processo final (aquele que será o daemon) não seja um líder de sessão. Somente os líderes da sessão podem adquirir um terminal de controle, portanto, este segundo garfo garante que o daemon não recupere inadvertidamente um terminal de controle. Isso vale para qualquer sistema operacional POSIX.
Dan Molding

@DanMoulding Isso não garante que o segundo filho não adquira o terminal de controle, pois ele pode chamar setsid e se tornar líder da sessão e, em seguida, adquirir o terminal de controle.
Trismegistos

7

De acordo com a "Programação Avançada no Ambiente Unix", de Stephens e Rago, a segunda bifurcação é mais uma recomendação e é feita para garantir que o daemon não adquira um terminal de controle em sistemas baseados no System V.


3

Uma razão é que o processo pai pode esperar wait_pid () imediatamente pelo filho e esquecê-lo. Quando o neto morre, seu pai é init, e espera () por isso - e tira-o do estado de zumbi.

O resultado é que o processo pai não precisa estar ciente dos filhos bifurcados e também possibilita a bifurcação de processos de execução longa de bibliotecas etc.


2

A chamada daemon () possui a chamada pai _exit () se for bem-sucedida. A motivação original pode ter sido permitir que os pais realizassem algum trabalho extra enquanto a criança daemonizava.

Também pode ser baseado em uma crença equivocada de que é necessário para garantir que o daemon não tenha processo pai e seja reparado para iniciar - mas isso acontecerá assim que o pai morrer no caso de garfo único.

Portanto, suponho que tudo se resume à tradição no final - um único garfo é suficiente desde que o pai morra em pouco tempo de qualquer maneira.



-1

Pode ser mais fácil entender dessa maneira:

  • O primeiro fork e o setsid criarão uma nova sessão (mas o ID do processo == ID da sessão).
  • O segundo fork garante que o ID do processo! = ID da sessão.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.