Em sua página da web sobre o truque do tubo automático , Dan Bernstein explica uma condição de corrida select()
e sinaliza, oferece uma solução alternativa e conclui que
Claro, a Coisa Certa seria ter
fork()
retornado um descritor de arquivo, não um ID de processo.
O que ele quer dizer com isso - é algo sobre poder select()
nos processos filho manipular suas mudanças de estado em vez de ter que usar um manipulador de sinal para ser notificado dessas mudanças de estado?
O problema está descrito lá na sua fonte,
select()
deve ser interrompido por sinais comoSIGCHLD
, mas em alguns casos não funciona tão bem. Portanto, a solução alternativa é fazer com que o sinal seja gravado em um pipe, que é então observado porselect()
. Observar os descritores de arquivosselect()
é para isso, então isso resolve o problema.A solução alternativa basicamente transforma o evento de sinal em um evento de 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 comselect()
.Então, sim, sua descrição no último parágrafo parece certa para mim.
Outra razão pela qual um fd (ou algum outro tipo de identificador de kernel) seria melhor do que um número de identificação de processo simples, é que os PIDs podem ser reutilizados após a morte do processo. Isso pode ser um problema em alguns casos ao enviar sinais para processos, pode não ser possível saber com certeza se o processo é aquele 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, já que o pai precisa executar
wait()
no filho para que seu PID seja liberado.)Bernstein não dá muito contexto para esta observação "Coisa certa", mas vou arriscar um palpite: ter fork(2) retornar um PID é inconsistente com open(2), creat(2) etc retornando descritores de arquivo. O resto do sistema Unix poderia ter feito a manipulação do 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 poderia funcionar.
É apenas uma reflexão sobre as linhas de "seria ótimo se o Unix fosse projetado de forma diferente do que é".
O problema com os PIDs é que eles vivem em um namespace global onde podem ser reutilizados para outro processo, e seria bom se
fork()
retornasse no pai algum tipo de handle que garantisse sempre se referir 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 daquele hack de auto-pipe [2] ou interfaces melhores, já que os sinais que notificam um processo pai sobre o estado de um filho não são os únicos que você gostaria de manipular no loop principal do programa. Infelizmente, coisas como
epoll(7) + signalfd(2)
no Linux oukqueue(2)
no BSD não são padrão - a única interface padrão (mas não suportada em sistemas mais antigos) é muito inferiorpselect(2)
.[1] Impedir que o PID seja reciclado no momento em que o
waitpid()
syscall retornar e seu valor de retorno for usado provavelmente poderia ser alcançado em sistemas mais novos usandowaitid(.., WNOWAIT)
em vez disso.[2] Eu não comentaria sobre a afirmação de DJ Bernstein que ele inventou (desculpe a apófase ;-)).
O ponto é que existem muitos programas que operam um modelo de estilo de loop de eventos baseado em descritores de arquivos de monitoramento com select(2) / poll(2) / epoll(7) . Mas havia vários eventos que historicamente não notificavam usando descritores de arquivo - entre eles a transição de estado do processo (por exemplo, encerramento) de um processo filho. A necessidade de lidar separadamente com esses outros eventos (que incluem expirações de temporizadores, sinais e eventos de sincronização, como alterações de semáforo) complicou a programação do modelo de loop de eventos.
Nos últimos anos, o desenvolvimento do Linux vem resolvendo esse problema, de modo que hoje em dia temos signalfd(2) (torna os sinais legíveis a partir de um descritor de arquivo), eventfd(2) (uma primitiva de sincronização cujo identificador é um descritor de arquivo) , e timerfd_create(2) (criam temporizadores que notificam por meio de um descritor de arquivo), todos os quais produzem descritores de arquivo que podem ser alimentados para select(2) / poll(2) / epoll(7) .
E, finalmente, versões recentes do Linux adicionaram o conceito de um identificador para um processo como descritor de arquivo. O
CLONE_PIDFD
sinalizador de clone3() pode ser usado para criar um processo filho para o qual um descritor de arquivo como identificador é retornado. O descritor de arquivo também pode ser alimentado em select(2) / poll(2) / epoll(7) e indica como legível se o processo filho termina.