O que faz um fork?

Um "fork" é uma característica dos sistemas Unix ou Linux utilizada para a criação de processos. Veja, a seguir, como ele funciona.

Funcionamento

Para explicar como funciona um fork, partiremos 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

A fim de identificar claramente as questões do fork (garfo), observe 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 uma pequena variável global que será usada posteriormente para indicar o estado do processo (pai ou filho).

Veja como ficará o processo inicial:

<bold>O PAPAI</bold>      

<bold>Descritores de arquivos</bold>      
1: (Stdout) Posição = 0      

<bold>Variáveis globais</bold>      
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 foi executado nada, o usuário está no começo do main. Então, execute as duas primeiras instruções:

<bold>O PAPAI</bold>     

<bold>Descritores de arquivos</bold>     
1: (Stdout) Posição = 0     

<bold>Variáveis globais</bold>     
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;     
}     

Pode-se ver, em vermelho, as instruções que foram executadas, assim como os dados que foram modificados pela última declaração.

O fork

A próxima instrução é a mais difícil de entender, então precisará ser executada.

<bold>O PAPAI</bold>    

<bold>Descritores de arquivos</bold>    
1: (Stdout) Posição = 0    

<bold>Variáveis globais</bold>    
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;    
}    

<bold>O FILHO</bold>    

<bold>Descritores de arquivos</bold>    
1: (Stdout) Posição = 0    

<bold>Variáveis globais</bold>    
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 significa 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.

Controlar a execução do pai e do filho

<bold>O PAPAI</bold>   

<bold>Descritores de arquivos</bold>   
1: (Stdout) Posição = 0   

<bold>Variáveis globais</bold>   
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;   
}   


<bold>O FILHO</bold>   

<bold>Descritores de arquivos</bold>   
1: (Stdout) Posição = 0   

<bold>Variáveis globais</bold>   
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 se controla a execução do pai e do filho: verificando o valor da variável pid, que é diferente para ambos.

As variáveis e os descritores de arquivos

<bold>O PAPAI</bold>  

<bold>Descritores de arquivos</bold>  
1: (Stdout) Posição = 15  

<bold>Variáveis globais</bold>  
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;  
}  


<bold>O FILHO</bold>  

<bold>Descritores de arquivos</bold>  
1: (Stdout) Posição = 15  

<bold>Variáveis globais</bold>  
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, os seguintes pontos não podem ser negligenciados!

  • 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. Sendo assim, 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). Então, 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

<bold>O PAPAI</bold> 

<bold>Descritores de arquivos</bold> 
1: (Stdout) Posição = 30 

<bold>Variáveis globais</bold> 
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; 
} 


<bold>O FILHO</bold> 

<bold>Descritores de arquivos</bold> 
1: (Stdout) Posição = 30 

<bold>Variáveis globais</bold> 
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 para 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.

<bold>O PAPAI</bold>

<bold>Descritores de arquivos</bold>
1: (Stdout) Posição = 30

<bold>Variáveis globais</bold>
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;
}


<bold>O FILHO</bold>

<bold>Descritores de arquivos</bold>
1: (Stdout) Posição = 30

<bold>Variáveis globais</bold>
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, entã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.

Foto: ©Sai Kiran Anagani - Unsplash

Nosso conteúdo é produzido em colaboração com especialistas em tecnologia da informação sob o comando de Jean-François Pillou, fundador do CCM.net. CCM é um site sobre tecnologia líder em nível internacional e está disponível em 11 idiomas.
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.

Assine nossa newsletter!

Assine nossa newsletter!
Junte-se à comunidade