Forking de processos
Vamos começar com um exemplo simples que imprime "Hello" na saída padrão:
#include <stdio.h>
int main() {
printf("Hello\n");
return 0;
}
Primeiro passo é compilar o arquivo para um executável do SO:
$ gcc forking.c -o forking
Após isso, rodamos o executável e:
$ ./forking
Hello
Dica: se quiser executar tudo em uma linha apenas, pode-se utilizar "gcc forking.c -o forking && ./forking"
So far, so good.
O primeiro fork
Para fazer forking de processos em C, precisamos usar a syscall fork através do cabeçalho unistd.h
(UNIX Standard), que fornece acesso a syscalls do Kernel, permitindo interagir diretamente com o sistema operacional:
#include <stdio.h>
#include <unistd.h>
int main() {
fork();
printf("Hello\n");
return 0;
}
Hello
Hello
E por quê vemos agora a mensagem sendo impressa duas vezes? O quê acontece quando chamamos fork? Vamos detalhar o fluxo do programa:
o processo pai é iniciado no começo do programa; apenas o pai está em execução
quando o
fork
é chamado, o sistema cria um novo processo filho, que é uma cópia exata do processo pai, incluindo o estado do programa naquele exato momentoqualquer código depois do fork é executado tanto no processo pai quanto no processo filho; como ambos chamam a função "printf" com "Hello", então a mensagem é impressa 2 vezes
Um ponto a destacar aqui é que, no momento do fork, todos os file descriptors do pai (incluindo o STDOUT) foram herdados pelo filho, e é por isso que vemos a mensagem na mesma saída padrão (tela do terminal) 2 vezes.
É possível fazer com que o filho tenha outra saída padrão diferente do pai, mas não vamos entrar nestes detalhes aqui, não importam muito para o assunto principal que é concorrência
Se quisermos que cada um imprima uma mensagem diferente, teríamos que saber , de alguma forma após a chamada do fork, se estamos dentro do processo pai ou do filho.
Manual, o nosso melhor amigo
De acordo com o manual, a syscall fork tem o seguinte retorno:
On success, the PID of the child process is returned in the parent, and 0 is returned in the child. On failure, -1 is returned in the parent, no child process is created, and is set to indicate the error.
Ou seja, dentro do filho, o retorno da função é 0
. E dentro do pai, o retorno é o PID do filho. Com isto, podemos ter o seguinte código:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("Hello from child\n");
} else {
printf("Hello from parent\n");
}
return 0;
}
Que se executado, tem a seguinte saída:
Hello from parent
Hello from child
Yay!
Mas repare que, devido à natureza preemptiva do escalonador, não temos controle sobre qual será executado primeiro. Poderia ser o pai, ou mesmo o filho.
O que temos que entender aqui é que, no momento do fork, é criado um novo processo no sistema operacional, que irá competir por recursos junto a outros processos, ou seja, todo mundo junto no mesmo balaio de concorrência.
Um exemplo mais robusto
Agora, vamos a um exemplo um pouco mais robusto com o uso de fork de processos, onde um processo pai dispara 3 processos filhos que irão executar uma tarefa que demora 2 segundos cada:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
void perform() {
printf("Processo filho (PID: %d) executando tarefa...\n", getpid());
sleep(2); // Simula uma tarefa que leva 2 segundos
printf("Processo filho (PID: %d) completou a tarefa!\n", getpid());
}
int main() {
int num_children = 3;
pid_t pid;
for (int i = 0; i < num_children; i++) {
pid = fork(); // Cria um novo processo
if (pid == 0) {
// Processo filho
perform();
return 0;
}
}
for (int i = 0; i < num_children; i++) {
pid_t child_pid = wait(NULL); // Aguarda qualquer filho terminar
printf("Pai: Processo filho com PID %d terminou.\n", child_pid);
}
printf("Pai: Todos os filhos terminaram. Finalizando.\n");
return 0;
}
Processo filho (PID: 271052) executando tarefa...
Processo filho (PID: 271053) executando tarefa...
Processo filho (PID: 271054) executando tarefa...
Processo filho (PID: 271052) completou a tarefa!
Processo filho (PID: 271053) completou a tarefa!
Pai: Processo filho com PID 271052 terminou.
Pai: Processo filho com PID 271053 terminou.
Processo filho (PID: 271054) completou a tarefa!
Pai: Processo filho com PID 271054 terminou.
Pai: Todos os filhos terminaram. Finalizando.
Interessante notar aqui:
os 3 processos filhos foram criados e iniciaram sua execução (271052, 271053, 271054)
2 processos terminaram um pouco antes, sendo que o processo pai notificou que ambos tinham concluído execução
um último processo (271054) terminou depois que o pai tinha notificado sobre os outros dois que concluíram primeiro
o pai informa que todos os filhos foram concluídos e o programa finaliza
Isto, senhoras e senhores, é a maravilha da concorrência. Não temos controle algum sobre a ordem e execução das tarefas!
Sim, vou repetir isso inúmeras vezes neste guia kk
Comunicação entre processos (IPC)
Como processos não compartilham memória (a priori), precisamos de um mecanismo de comunicação entre processos, também chamado de IPC (Inter-process communication), pois um processo sem comunicação é bastante inútil.
Uma das formas de IPC é através de pipes, que são uma comunicação uni-direcional entre processos.
Se você quer entender mais sobre IPC e UNIX pipes, escrevi um artigo sobre o tema, onde é possível aprofundar nos conceitos de forma prática, e você só precisa de um terminal com shell/bash ou outra shell de preferência
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main() {
int pipe_fd[2];
pid_t pid;
pid = fork();
if (pid == 0) {
// Processo filho
close(pipe_fd[0]); // Fecha a extremidade de leitura
char mensagem[] = "Message from child!";
// Escreve no pipe
write(pipe_fd[1], mensagem, strlen(mensagem) + 1);
close(pipe_fd[1]); // Fecha a extremidade de escrita
} else {
// Processo pai
close(pipe_fd[1]); // Fecha a extremidade de escrita
char buffer[100];
// Lê a partir do pipe
read(pipe_fd[0], buffer, sizeof(buffer));
printf("Parent received message: %s\n", buffer);
close(pipe_fd[0]); // Fecha a extremidade de leitura
}
return 0;
}
Este é um exemplo bastante simples de como 2 processos distintos podem conversar entre si, através da utilização de UNIX pipes.
Tá gostando do guia?
Se está gostando deste trabalho e considera fortalecer, o QRCode de PIX abaixo tá no jeito hein?

Ou copia a cola:
00020126850014BR.GOV.BCB.PIX013638ee4bde-574b-4197-b10f-68742087b00b0223Gratidão pelo cafezinho5204000053039865802BR5925Leandro Freitas Maringolo6009SAO PAULO62140510qrN6Ov1wRl63041A3C
Não vamos entrar em mais detalhes sobre uso de forking em C, isto já é o suficiente para o objetivo deste guia. No próximo tópico, iremos abordar o uso de threads em C.
Last updated