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

Por: Roland Teodorowitsch em 29/03/2009


A resposta



No caminho até a minha casa, pensando na influência da chamada fflush(stdout) e na definição da chamada de sistema fork() - cria uma cópia idêntica do processo... -, chequei a conclusão de que o que estava acontecendo era que o fork() duplicava também os buffers do processo, entre eles, é claro, o buffer de saída (stdout). A questão era que alguns processos-filho estavam recebendo caracteres "x" nos buffers que herdavam de seus pais.

Encontrada uma resposta lógica, o problema passou a ser: como provar esta teoria através de um exemplo prático e real.

A prova

Para facilitar o entendimento do que estava ocorrendo com o programa do laço de chamadas fork() (Figura 3), em vez de imprimir caracteres "x", fica mais interessante imprimir um número correspondente à iteração do laço. Esta modificação gera o programa da Figura 5.

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

int main()
{
  int i;

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

Figura 5 - Exemplo de código-fonte do laço de chamadas fork() imprimindo o número da iteração

No caso da Figura 5, o resultado esperado deveria ser "010111", mas obtém-se "01010101" devido a duplicação dos buffers de saída. A Figura 6 apresenta um fluxograma do que ocorre ao longo da execução do código da Figura 5. Ao final da execução, ter-se-á 4 processos executando. Como mostra a figura, após cada fork(), o conteúdo do buffer de saída stdout é duplicado no processo filho, o que significa que o processo filho recebe neste buffer o que o seu processo-pai havia mandado imprimir via printf().
Linux: A duplicação do buffer de saída na chamada de sistema fork() do Linux
Figura 6 - Fluxograma da execução do programa da Figura 5
Há, no entanto, uma forma mais simples de comprovar o comportamento da chamada fork() em relação a situação descrita neste artigo. Este é o caso do programa da Figura 7. Neste código, depois de imprimir um caractere "x", executa-se uma chamada fork() e em seguida imprime-se um caractere "y". O resultado esperado deveria ser "xyy". No entanto, em função da duplicação do buffer de saída na chamada fork(), o resultado será "xyxy".

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

int main()
{
  printf("x");
  fork();
  printf("y");
  return(0);
}

Figura 7 - Teste final

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

SDL e C - Uma dupla sensacional

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

Programação com números inteiros gigantes

Alocação dinâmica de memória em C

Programação de Jogos com SDL

  
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