A duplicação do buffer de saída na chamada de sistema fork() do Linux

Este artigo relata uma situação inusitada que ocorreu durante a execução de um programa de teste da chamada de sistema fork() do Unix e do Linux, esclarecendo detalhes sobre o funcionamento desta chamada de sistema no que diz respeito a buffers.

[ Hits: 28.623 ]

Por: Roland Teodorowitsch em 29/03/2009


Introdução



Durante a aula de projeto de sistemas operacionais do curso de Ciência da Computação da Universidade Luterana do Brasil, campus Gravataí, em 12 de março de 2009, eu estava explicando aos alunos o funcionamento da chamada de sistema fork() do Unix, quando ocorreu um fato inusitado. Esta chamada cria uma cópia do processo atual, duplicando código e valores de variáveis. Trata-se da chamada básica do Unix para criar um novo processo. Como código e variáveis são duplicados, as diferenças entre o processo criador (também chamado de pai) e o processo criado (chamado de filho) são:
  • o novo processo terá um PID (Process IDentification) diferente do processo criador;
  • e a chamada fork() retornará o PID do processo-filho para o processo-pai e 0 para o processo-filho.

E, para ambos os processos, a execução continuará na instrução seguinte à chamada fork().

No restante deste artigo são apresentados alguns exemplos de uso da chamada fork() para testar o seu funcionamento, o "desafio" que gerou a "situação inusitada", a explicação para a origem da situação inusitada e a prova da explicação. Por fim, são apresentadas algumas conclusões.

Desafios iniciais

Depois de descrever o funcionamento da chamada fork(), sempre proponho alguns desafios aos alunos para ver se eles entenderam a explicação. O primeiro desafio gosto de apresentar lembrando aos alunos que nas disciplinas de linguagens de programação eles sempre aprendem que, no caso de comandos como if e else, se a condição do if for verdadeira, o comando ou bloco executado será o do if. Caso a condição seja falsa, o comando ou bloco executado será o do else. Pergunto-lhes então o que seria impresso depois do trecho de código-fonte mostrado na Figura 1.

p = fork();
if (p == 0)
   printf("FILHO\n");
else
   printf("PAI\n");

Figura 1 - Exemplo de código-fonte com if

A resposta, por mais estranha que seja, não pode ser outra que não: as duas cadeias de caracteres são impressas. A execução do fork() duplica o processo, de forma que, apesar de termos um único código-fonte, teremos 2 processos executando o if. Para um dos processos a condição do if será falsa, para o outro, não.

Outro desafio interessante é questionar quanto ao número de caracteres 'x' impresso após a execução do trecho de código-fonte da Figura 2.

fork();
fork();
fork();
printf("x");

Figura 2 - Exemplo de código-fonte com vários fork() em sequência

Às vezes é possível ouvir alguns números improváveis antes da resposta correta: oito. Após o primeiro fork(), teremos 2 processos. Cada um deles executa um fork(), resultando em 4 processos. E, por fim, cada um destes 4 processos executa um último fork(), resultando em 8 processos. E cada um destes 8 processos imprime um caractere 'x'.

    Próxima página

Páginas do artigo
   1. Introdução
   2. Um novo desafio
   3. A resposta
   4. Conclusão
Outros artigos deste autor
Nenhum artigo encontrado.
Leitura recomendada

Compilando o Mono 2.2 no Ubuntu 8.10

Algum humor e C++ Design Patterns (parte 2)

Bug afeta todas as distros

Cuidado com números em Ponto Flutuante

Desenvolvendo para microcontroladores em GNU/Linux

  
Comentários
[1] Comentário enviado por f_Candido em 29/03/2009 - 09:19h

Muito Legal. Uma pequena alteração... Faz toda a diferença.

Abraços

[2] Comentário enviado por pedroarthur.jedi em 30/03/2009 - 09:28h

Ficou muito bom o post!
E a didática do "vamos fazer pra entender" se mostrou bastante eficiente!

Parabéns!

[3] Comentário enviado por elgio em 30/03/2009 - 11:50h

Muito bom, muito bom mesmo.

ao analisar o código sem executar:

for (i=0; i<2; ++i) {
. . . fork();
. . . printf("%d",i);
}

Esperaria que ele imprimisse 001111

A saber:

primeiro 0: pai imprime 0
segundo 0: primeiro filho imprime 0
primeiro 1: pai imprime 1 (e termina)
segundo 1: primeiro filho imprime 1 (e termina)
demais 1's: terceiro e quarto filhos, que são criados com i=1; imprimem 1.

Imprimir em outra ordem já deve ser efeito do buffer e/ou do escalonador do Linux, certo?

Entendo que colocando fflush para forçar a saída do buffer:

for (i=0; i<2; ++i) {
. . . fork();
. . . printf("%d",i);
. . . fflush(stdout);
}

pode-se ter sequências DIFERENTES de impressão. Exemplo: com um core, é provável que o pai termine toda a sua execução antes de passar a CPU para o filho. Já em um CORE 2, pode pai e filho imprimirem "ao mesmo tempo" (disputando a exclusividade da tela?) e qualquer combinação pode ser apresentada. Correto?

[4] Comentário enviado por pedroarthur.jedi em 30/03/2009 - 11:59h

Pela minha experiência com programação concorrente, diria que não há saída preditível. Tudo vai depender do escalonador, da carga do sistema, da quantidade de (núcleos|processadores) e outros fatores. Claro que haverá saídas mais prováveis que outras. Mas como dizem os grandes mestres, (Tanenbaum, Silberchartz) temos que estar preparado para o pior...

[5] Comentário enviado por Douglas_Martins em 31/03/2009 - 10:32h

Eu estava nessa aula e ficamos mesmo intrigados com o que tinha ocorrido.
Valeu pela explicação Roland...


Contribuir com comentário