Um "fork" é uma característica dos sistemas Unix ou Linux utilizada para a criação de processos. Veja, a seguir, como ele funciona.
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).
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.
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.
<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.
<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).
<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