Por que fork () foi projetado para retornar um descritor de arquivo?

16

Em sua página da web sobre o truque do autoduto , Dan Bernstein explica uma condição de corrida com select()e sinaliza, oferece uma solução alternativa e conclui que

Obviamente, a coisa certa seria fork()retornar um descritor de arquivo, não um ID do processo.

O que ele quer dizer com isso - é algo sobre ser capaz de, select()nos processos filhos, manipular suas alterações de estado, em vez de precisar usar um manipulador de sinal para ser notificado dessas alterações de estado?

Lassi
fonte
Esse artigo confunde entrada e saída ou não estou lendo o texto corretamente?
Ctrl-alt-delor 20/07/19
Você pode solicitar que os sinais sejam entregues através de tubos. É isso que eu faço.
Ctrl-alt-delor 20/07/19
@ ctrl-alt-delor, sim, ele parece usar "entrada / saída de canal" um pouco estranhamente, mas acho que está claro onde ele está escrevendo e onde está lendo um canal. Esse texto é de 2003, e não tenho certeza se signalfdisso era algo naquela época?
21919 ilkkachu
5
Dan sabe do que está falando, embora possa ser um pouco provocador. Se eu estivesse deliberadamente provocando, opino que, é claro, a coisa certa seria se livrar do SIGCHLD.
Steve Summit
1
@mosvy Estou exagerando um pouco, mas todos os programas e programadores que já vi que tentaram usar o SIGCHLD tiveram problemas com isso. É uma condição de corrida esperando para acontecer. Quando tudo o que tínhamos era o bloqueio wait(), havia coisas que você não podia fazer, então alguém inventou o SIGCHLD, mas foi um trabalho ruim. Na minha experiência, e agora que eles existem, polvilhando bom, não bloqueante wait3(), wait4(), e / ou waitpid()chamadas em lugares-chave (talvez o seu principal ciclo de eventos) é uma alternativa muito superior.
Steve Summit

Respostas:

14

O problema é descrito lá na sua fonte, select()deve ser interrompido por sinais como SIGCHLD, mas em alguns casos, não funciona tão bem. Portanto, a solução alternativa é ter gravação de sinal em um pipe, que é então observado por select(). Observar os descritores de arquivo é o que select()serve, para que ele resolva o problema.

A solução alternativa transforma essencialmente o evento do sinal em um evento do descritor de arquivo. Se fork()apenas retornasse um fd em primeiro lugar, a solução alternativa não seria necessária, pois esse fd poderia presumivelmente ser usado diretamente com select().

Então, sim, sua descrição no último parágrafo parece correta para mim.


Outro motivo pelo qual um fd (ou algum outro tipo de identificador de kernel) seria melhor que um número de identificação de processo simples é que os PIDs podem ser reutilizados depois que o processo morre. Isso pode ser um problema em alguns casos, ao enviar sinais para processos, talvez não seja possível saber com certeza que o processo é o que você pensa que é e não outro reutilizando o mesmo PID. (Embora eu ache que isso não deve ser um problema ao enviar sinais para um processo filho, pois o pai precisa executar wait()o filho para que seu PID seja liberado.)

ilkkachu
fonte
Dito isso, não me lembro exatamente dos casos que li sobre a reutilização de PID ser um problema; portanto, se alguém quiser elaborar ou esclarecer isso, ou mesmo editar o acima, sinta-se à vontade para fazê-lo.
21919 ilkkachu
2
Como você já afirmou, não há desculpa para um pai descobrir que seu próprio filho foi reutilizado. Está no controle total dessa situação, porque é quem chama wait().
21719 Joshua
Estes são chamados de processos zumbis : "um processo que concluiu a execução, mas ainda possui uma entrada na tabela de processos: é um processo no" estado Terminado ". Isso ocorre nos processos filhos, nos quais a entrada ainda é necessária para permitir que o pai processo para ler o status de saída de seu filho "
Lassi
6
Vale ressaltar que agora o Linux pode retornar um descritor de arquivo (pidfd) clone, que é a chamada de sistema real que o fork chama no LInux. O sinalizador para ativar isso é chamado CLONE_PIDFD- Veja, por exemplo, lwn.net/Articles/784831 .
KJ Tsanaktsidis 21/07/19
1
@ Ryan Ryan, Re " Ter fork () retorna um identificador global em vez de um identificador local é conceitualmente mais correto porque ", o Windows usa identificadores de processo. O código pid e exit permanece até que todas as alças do processo sejam fechadas (em vez de esperar a colheita dos pais), evitando condições de corrida comuns em sistemas unix. Quando as alças mantêm o processo ativo, faz muito mais sentido que sejam alças locais e não globais.
Ikegami
9

É apenas uma reflexão sobre as linhas de "seria ótimo se o Unix tivesse um design diferente do que é".

O problema com os PIDs é que eles vivem em um espaço para nome global onde podem ser reutilizados para outro processo, e seria bom se fork()retornasse ao pai algum tipo de identificador que seria garantido que sempre se referisse ao processo filho e que poderia passar para outros processos via herança ou soquetes unix / SCM_RIGHTS[1].

Veja também a discussão aqui para um esforço recente para "consertar" isso no Linux, incluindo a adição de um sinalizador ao clone()qual fará com que ele retorne um pid-fd em vez de um PID.

Mas mesmo assim, isso não eliminaria a necessidade desse hack de auto-pipe [2] ou de interfaces melhores, pois os sinais que notificam um processo pai sobre o estado de um filho não são os únicos que você gostaria de tratar no loop principal do programa. Infelizmente, coisas como epoll(7) + signalfd(2)no Linux ou kqueue(2)no BSD não são padrão - a única interface padrão (mas não suportada em sistemas mais antigos) é muito inferior pselect(2).

[1] Evitar que o PID seja reciclado novamente no momento em que o waitpid()syscall retornou e seu valor de retorno foi usado provavelmente poderia ser alcançado em sistemas mais recentes, usando-o waitid(.., WNOWAIT).

[2] Eu não comentaria sobre o DJ Bernstein alegando que ele o inventou (desculpe a apofasia ;-)).

mosvy
fonte
8

Bernstein não fornece muito contexto para esta observação "Right Thing", mas vou arriscar um palpite: ter fork (2) retornando um PID é inconsistente com open (2), creat (2) etc, retornando descritores de arquivo. O restante do sistema Unix poderia ter manipulado o processo com um descritor de arquivo representando um processo, em vez de um PID. Existe uma chamada de sistema signalfd (2) , que permite uma interação um pouco melhor entre sinais e descritores de arquivo, e mostra que um descritor de arquivo representando um processo pode funcionar.

Bruce Ediger
fonte
signalfd (2) parece incrível, obrigado por mencionar! Pena que é apenas Linux.
Lassi
1
Houve discussões pidfd_opentambém no Linux, veja, por exemplo, lwn.net/Articles/789023
dhag