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: 29.539 ]

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

Bug afeta todas as distros

Programação com números inteiros gigantes

Como funcionam os alocadores de memória do STD C?

Instalando Facebook Folly através do Conan

  
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




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts