Quando a extremidade de leitura do pipe estiver fechada, a tentativa de gravação é um erro. Isso matará o processo com o SIGPIPE. Ou se esse sinal for bloqueado, a gravação retornará imediatamente com errno == EPIPE
. Isso deve explicar seu comportamento. É uma das características originais dos canais do UNIX.
Acontece quando a última referência restante para o final de leitura do pipe é fechada. Pode haver outras referências, p. de dup()
.
No seu caso, você tem fork()
ed um novo processo, então o processo filho começa com todos os mesmos descritores de arquivo. Para o pipe ser fechado, os descritores de arquivo do pai e da criança devem ser fechados. Observe que close()
no pai não afeta os descritores de arquivos da criança (ou vice-versa).
Este é um exemplo do conceito geral de contagem de referência. O kernel mantém a contagem de quantos descritores de arquivos se referem à extremidade de leitura do pipe. Reduz a contagem em um para cada chamada close()
. Se a contagem cair para zero, o kernel executa a função de limpeza apropriada. No kernel do Linux, esse é um ponteiro de função chamado .release
, pois libera todos os recursos associados.
O sistema de contagem de referência é essencial para os descritores de arquivos do UNIX. Por exemplo, eu posso encontrar dup () e fork ( ) usado na pesquisa UNIX V5.
Se você quiser saber por que o SIGPIPE está bloqueado em subprocessos iniciados no python2.6, consulte o link .
Se você ficou surpreso com o fato de os FDs do canal vazarem para P2 e P3, consulte o link . Ou seja para obter um comportamento mais sensato de Popen()
, você pode passar close_fds=True
.
Caso contrário, se você quiser passar FDs extras específicos para P2 e P3, eu realmente quero explicitar isso usando o parâmetro pass_fds
.
Eu vou assumir que você quer, caso contrário eu realmente não vejo o que este programa de exemplo deveria estar fazendo. Você está descartando os objetos de subprocesso e, em seguida, saindo. Portanto, o processo pai está fechando seus FDs de pipe, pelo menos quando sai.
Podemos reproduzir isso no shell, sem depender de detalhes que pareçam ser dependentes da versão específica do python.
$ strace -f sh -c 'cat </dev/zero | { sleep 1& sleep 2& }'
...
[pid 26477] read(0, "$ strace -f sh -c 'cat </dev/zero | { sleep 1& sleep 2& }'
...
[pid 26477] read(0, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 131072) = 131072
[pid 26477] write(1, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 131072 <unfinished ...>
...
[pid 26480] nanosleep({tv_sec=2, tv_nsec=0}, <unfinished ...>
...
[pid 26479] nanosleep({tv_sec=1, tv_nsec=0}, NULL) = 0
...
[pid 26479] +++ exited with 0 +++
[pid 26480] <... nanosleep resumed> NULL) = 0
...
[pid 26480] +++ exited with 0 +++
[pid 26477] <... write resumed> ) = 65536
[pid 26477] --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=26477, si_uid=1001} ---
[pid 26477] +++ killed by SIGPIPE +++
%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 131072) = 131072
[pid 26477] write(1, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 131072 <unfinished ...>
...
[pid 26480] nanosleep({tv_sec=2, tv_nsec=0}, <unfinished ...>
...
[pid 26479] nanosleep({tv_sec=1, tv_nsec=0}, NULL) = 0
...
[pid 26479] +++ exited with 0 +++
[pid 26480] <... nanosleep resumed> NULL) = 0
...
[pid 26480] +++ exited with 0 +++
[pid 26477] <... write resumed> ) = 65536
[pid 26477] --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=26477, si_uid=1001} ---
[pid 26477] +++ killed by SIGPIPE +++
Eu noto um detalhe aqui que eu não tinha pensado antes. write()
está retornando que ele gravou com sucesso apenas 64K no buffer do pipe. O que aconteceria se o chamador desativasse a ação de finalização padrão do SIGPIPE? "gravações curtas" são algo que você geralmente tem que tolerar em pipes ou soquetes, tentando novamente. Por exemplo. isso pode acontecer se o processo receber um sinal não relacionado e houver uma função de manipulador configurada para esse sinal. Assim, o chamador deve continuar tentando novamente write()
com os dados restantes, e essa write()
chamada retornará imediatamente com errno == EPIPE
.