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

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

Programação com números inteiros gigantes

Dynamic libraries com libtool

Sinais em Linux

Cuidado com números em Ponto Flutuante

Desenvolvendo aplicativo para autenticação biométrica utilizando a Libfprint

  
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