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.427 ]

Por: Roland Teodorowitsch em 29/03/2009


Um novo desafio



Talvez estimulado pelo fado da resposta correta ao desafio da Figura 2 não ter demorado a aparecer, pensei então em outro desafio. E se o fork() for colocado em um laço? Antes de lançar o desafio para a turma, ainda considerei se a explicação não poderia se tornar mais complicada do que eu gostaria. Mas quem trabalha com Sistemas Operacionais não pode ter medo de códigos ou explicações complicadas. E a situação poderia ilustrar perfeitamente as consequências de se colocar um fork() dentro de um laço sem maiores cuidados.

Lancei então o desafio para a turma e não demorou muito para que o fato inusitado, ao qual me referi no início do artigo, surgisse. O desafio consistia em determinar quantos caracteres 'x' apareceriam na tela depois da execução do código-fonte da Figura 3.

#include <stdio.h>
#include <unistd.h>

int main()
{
  int i;

  for (i=0; i<2; ++i)  {
      fork();
      printf("x");
  }
  return(0);
}

Figura 3 - Exemplo de código-fonte para o novo desafio

Resultados diferentes

Recomendei que a turma digitasse e testasse o código da Figura 3 e em seguida discutiríamos os resultados. Para minha surpresa a maioria da turma obteve 8 como resposta e dois alunos obtiveram 6...

Analisamos então o código-fonte e chegamos à seguinte conclusão: na primeira iteração do laço, um fork() seria executado e dois processos imprimiriam o caractere 'x', indo em seguida para a segunda iteração. Nesta segunda iteração (ainda bem que não usei 10 como limite para o laço...), os dois processos executariam uma chamada fork(), tendo-se 4 processos, cada um imprimindo mais um caractere 'x'. Total de caracteres 'x' impressos: 6 (seis).

Verificamos então as diferenças entre os códigos que resultaram no valor esperado (seis) e no valor que a maioria obteve (oito). A maioria digitou o código exatamente da forma apresentada na Figura 3. Os dois alunos que obtiveram o valor esperado, digitaram o código "à sua maneira", fazendo pequenas adaptações. Considerando as poucas variações que um código tão pequeno pode apresentar, chega a ser difícil compreender a origem da diferença de resultado.

Isolando e eliminando as diferenças insignificantes, ficou claro que o que estava ocasionando a diferença era um '\n' (caractere de nova linha) colocado na cadeia de caracteres da chamada printf(). Uma chamada fflush(stdout) colocada logo após o printf() tinha o mesmo efeito do uso de printf() com '\n'. A chamada fflush(stdout) esvazia o buffer de saída do processo (que por padrão é o terminal de vídeo), garantindo que tudo o que foi mandado imprimir seja visualizado imediatamente. O código com esta chamada, que gera o resultado esperado (seis), pode ser visto na Figura 4.

#include <stdio.h>
#include <unistd.h>

int main()
{
  int i;

  for (i=0; i<2; ++i)  {
      fork();
      printf("x");
      fflush(stdout);
  }
  return(0);
}

Figura 4 - Exemplo de código-fonte para o novo desafio com fflush(stdout)

A questão principal era então: por que o primeiro código apresentou dois caracteres "x" a mais além do esperado? De onde saíram estes dois caracteres "x"?

Como o horário da aula estava se encerrando, não foi possível encontrar uma resposta para a questão na hora.

Página anterior     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

Mapear objetos em C

Tratamento de exceções na linguagem C

Programação com números inteiros gigantes

Dynamic libraries com libtool

lib cURL - Trabalhe com URLs em C

  
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