O que faz um fork ()?

Março 2017




Introdução


Um "fork" é uma característica dos sistemas Unix ou Linux para duplicar um processo.

Para explicar como funciona, vamos partir de um processo que chamaremos, carinhosamente, de "papai". Este processo vai simplesmente se duplicar e os dois processos (pai e filho) mostrarão a cada um os seu status (pai ou filho).

Execução do papai


Vamos partir de um processo pai. , A fim de identificar claramente as questões do fork (garfo), também vamos observar seus descritores de arquivos, ou melhor, um dos mais importantes: o do fluxo de saída (stdout), ou seja, a tela. Vamos também colocar-lhe uma pequena variável global que será usado posteriormente para indicar o estado do processo (pai ou filho).

Veja como ficará o nosso processo inicial:

O PAPAI      

Descritores de arquivos      
1: (Stdout) Posição = 0      

Variáveis globais      
const char* quemsoueu = NULL;      

int main()       
{      
   pid_t pid;      
   quemsoueu = "O papai";      
    pid = fork();      
    if(pid = = 0){      
         quemsoueu = "O filho";      
          printf("Eu sou %s, quemsoueu);      
 }      
else{      
 printf("Eu sou %s, quemsoueu);      
 wait(NULL);      
  }      
         return 0;      
}



A seta vermelha aponta para a próxima instrução a ser executada. Como ainda não executamos nada, estamos no começo do main. Então, vamos executar as duas primeiras instruções:

O PAPAI     

Descritores de arquivos     
1: (Stdout) Posição = 0     

Variáveis globais     
const char* quemsoueu = "O PAPAI";     

int main()      
{     
     pid_t pid;     
     quemsoueu = "O papai";     
     pid = fork();     
     if(pid = = 0){     
         quemsoueu = "O filho";     
          printf("Eu sou %s, quemsoueu);     
      }     
      else{     
          printf("Eu sou %s, quemsoueu);     
          wait(NULL);     
      }     
      return 0;     
}     


Podemos ver em vermelho as instruções que foram executadas, assim como os dados que foram modificados pela última declaração. Até aqui tudo bem, vamos mudar o valor do quemsoueu para atribuir-lhe a identidade do pai. Passemos para a próxima instrução.

O fork


A próxima instrução é a mais difícil de entender, vamos executá-la e ver o que acontece.

O PAPAI    

Descritores de arquivos    
1: (Stdout) Posição = 0    

Variáveis globais    
const char* quemsoueu = "O PAPAI";    

int main()     
{    
     pid_t pid;    
     quemsoueu = "O papai";    
     pid = fork(); //pid = 1000    
     if(pid = = 0){    
         quemsoueu = "O filho";    
          printf("Eu sou %s, quemsoueu);    
      }    
      else{    
          printf("Eu sou %s, quemsoueu);    
          wait(NULL);    
      }    
      return 0;    
}    


O FILHO    

Descritores de arquivos    
1: (Stdout) Posição = 0    

Variáveis globais    
const char* quemsoueu = "O PAPAI";    

int main()     
{    
     pid_t pid;    
     quemsoueu = "O papai";    
     pid = fork(); //pid = 0    
     if(pid = = 0){    
         quemsoueu = "O filho";    
          printf("Eu sou %s, quemsoueu);    
      }    
      else{    
          printf("Eu sou %s, quemsoueu);    
          wait(NULL);    
      }    
      return 0;    
}    



O pai chamou o garfo e se duplicou. Isto implica várias coisas:
  • Um novo processo foi criado, ele é considerado como o filho do processo que chamou o fork()
  • Este processo é uma cópia fiel do seu pai. Aliás, a próxima instrução a ser executada será a mesma para ambos: a condição "if".
  • A função fork() não retorna a mesma coisa para ambos os processos. Para o filho, ele retornará o 0. Para o pai, ele retornará o pid do filho (seu número de processo).
  • Esta duplicação envolve algumas coisas sobre as variáveis e os descritores de arquivos. Vamos chegar lá.


Passemos à instrução seguinte para os dois.

Controlar a execução do pai e do filho


O PAPAI   

Descritores de arquivos   
1: (Stdout) Posição = 0   

Variáveis globais   
const char* quemsoueu = "O PAPAI";   

int main()    
{   
     pid_t pid;   
     quemsoueu = "O papai";   
     pid = fork(); //pid = 1000   
     if(pid = = 0){   
         quemsoueu = "O filho";   
          printf("Eu sou %s, quemsoueu);   
      }   
      else{   
          printf("Eu sou %s, quemsoueu);   
          wait(NULL);   
      }   
      return 0;   
}   


O FILHO   

Descritores de arquivos   
1: (Stdout) Posição = 0   

Variáveis globais   
const char* quemsoueu = "O PAPAI";   

int main()    
{   
     pid_t pid;   
     quemsoueu = "O papai";   
     pid = fork(); //pid = 0   
     if(pid = = 0){   
         quemsoueu = "O filho";   
          printf("Eu sou %s, quemsoueu);   
      }   
      else{   
          printf("Eu sou %s, quemsoueu);   
          wait(NULL);   
      }   
      return 0;   
}   


Ambos os processos acabaram de verificar a condição if. Já que no pai, a variável pid será diferente de 0, ele continuará no else. Em compensação, o filho entrará no bloco do "if" pois, para ele, o pid é igual a 0.
Importante: É assim então, que controlaremos a execução do pai e do filho: verificando o valor da variável pid, que é diferente para ambos.
Vamos continuar.

As variáveis e os descritores de arquivos


O PAPAI  

Descritores de arquivos  
1: (Stdout) Posição = 15  

Variáveis globais  
const char* quemsoueu = "O PAPAI";  

int main()   
{  
     pid_t pid;  
     quemsoueu = "O papai";  
     pid = fork(); //pid = 1000  
     if(pid = = 0){  
         quemsoueu = "O filho";  
          printf("Eu sou %s, quemsoueu);  
      }  
      else{  
          printf("Eu sou %s, quemsoueu);  
          wait(NULL);  
      }  
      return 0;  
}  


O FILHO  

Descritores de arquivos  
1: (Stdout) Posição = 15  

Variáveis globais  
const char* quemsoueu = "O filho";  

int main()   
{  
     pid_t pid;  
     quemsoueu = "O papai";  
     pid = fork(); //pid = 0  
     if(pid = = 0){  
         quemsoueu = "O filho";  
          printf("Eu sou %s, quemsoueu);  
      }  
      else{  
          printf("Eu sou %s, quemsoueu);  
          wait(NULL);  
      }  
      return 0;  
}  


Atenção, este ponto não pode ser negligenciado!
  • O filho mudou o valor da sua variável quemsoueu. Isso mudou o valor de sua própria variável quemsoueu, mas não a do pai. Veja então a nossa primeira conclusão: as variáveis do pai e do filho são inteiramente distintas; mesmo que tenham o mesmo nome, não são as mesmas variáveis. Por outro lado, você notará que no momento do "fork", o filho tinha herdado os valores de todas as variáveis do seu pai.
  • O pai acabou de fazer um printf e, consequentemente, escreveu: "Eu sou o pai" no fluxo de saída padrão (stdout). Assim, após este registro, o ponteiro do arquivo stdout aumentou para 15 caracteres (o comprimento da frase exibida). Você já reparou que aconteceu o mesmo com o filho? Na verdade, se, por uma lado, o pai e o filho têm variáveis distintas, por outro, os seus descritores de arquivos são os mesmos . Então, se um dos dois processos muda o seu ponteiro de posição em um arquivo, isso também será refletido no outro. Atenção, isso só é válido para os descritores de arquivos herdados durante o fork. Se o pai ou o filho abrirem outros arquivos após o fork, esses descritores não serão compartilhados entre eles. Da mesma forma, se o filho fecha um descritor de arquivo herdado do pai, o descritor de arquivo do pai não será fechado (idem no sentido contrário).

Sincronização


O PAPAI 

Descritores de arquivos 
1: (Stdout) Posição = 30 

Variáveis globais 
const char* quemsoueu = "O PAPAI"; 

int main()  
{ 
     pid_t pid; 
     quemsoueu = "O papai"; 
     pid = fork(); //pid = 1000 
     if(pid = = 0){ 
         quemsoueu = "O filho"; 
          printf("Eu sou %s, quemsoueu); 
      } 
      else{ 
          printf("Eu sou %s, quemsoueu); 
          wait(NULL); 
      } 
      return 0; 
} 


O FILHO 

Descritores de arquivos 
1: (Stdout) Posição = 30 

Variáveis globais 
const char* quemsoueu = "O filho"; 

int main()  
{ 
     pid_t pid; 
     quemsoueu = "O papai"; 
     pid = fork(); //pid = 0 
     if(pid = = 0){ 
         quemsoueu = "O filho"; 
          printf("Eu sou %s, quemsoueu); 
      } 
      else{ 
          printf("Eu sou %s, quemsoueu); 
          wait(NULL); 
      } 
      return 0; 
} 
  • Quanto ao Filho: um printf foi feito, desta vez para exibir "Eu sou o filho." O ponteiro do arquivo aumentou ara 15 no filho, o que se reflete no pai.
  • Quanto ao Pai: o pai executou a função wait(). Esta função permite a sincronização entre o pai e todos os seus filhos. Isto significa que o pai vai parar de se executar (neste caso, diz-se que ele está dormindo) até que seu filho termine completamente.

Fim


O PAPAI

Descritores de arquivos
1: (Stdout) Posição = 30

Variáveis globais
const char* quemsoueu = "O PAPAI";

int main()	
{
     pid_t pid;
     quemsoueu = "O papai";
     pid = fork(); //pid = 1000
     if(pid = = 0){
         quemsoueu = "O filho";
          printf("Eu sou %s, quemsoueu);
      }
      else{
          printf("Eu sou %s, quemsoueu);
          wait(NULL);
      }
      return 0;
}


O FILHO

Descritores de arquivos
1: (Stdout) Posição = 30

Variáveis globais
const char* quemsoueu = "O filho";

int main()	
{
     pid_t pid;
     quemsoueu = "O papai";
     pid = fork(); //pid = 0
     if(pid = = 0){
         quemsoueu = "O filho";
          printf("Eu sou %s, quemsoueu);
      }
      else{
          printf("Eu sou %s, quemsoueu);
          wait(NULL);
      }
      return 0;
}



O filho acabou de executar a sua última instrução, agora ele não existe mais. Durante esse tempo, o pai ainda estava esperando, mas logo ele irá acordar, já que o filho terminou. Finalmente, o pai também vai acabar.

Notas e agradecimentos


A imagem que precede o sumário é a alteração de uma fotografia original produzida pela tanakawho.


Tradução feita por Lucia Maurity y Nouira

Veja também

Artigo original publicado por . Tradução feita por pintuda.
Este documento, intitulado 'O que faz um fork ()?', está disponível sob a licença Creative Commons. Você pode copiar e/ou modificar o conteúdo desta página com base nas condições estipuladas pela licença. Não se esqueça de creditar o CCM (br.ccm.net) ao utilizar este artigo.