Dado o programa fork.c a seguir:
fork.c
/*
Criação de processos em UNIX.
Compilar com gcc -Wall fork.c -o fork
Carlos Maziero, DINF/UFPR 2020
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main ()
{
int retval ;
printf (Ola, sou o processo %5d\n, getpid()) ;
retval = fork () ;
printf ([retval: %5d] sou %5d, filho de %5d\n, retval, getpid(), getppid()) ;
if ( retval < 0 ) // erro no fork()
{
perror (Erro) ;
exit (1) ;
}
else
if ( retval > 0 ) // sou o processo pai
wait (0) ;
else // sou o processo filho
sleep (5) ;
printf (Tchau de %5d!\n, getpid()) ;
exit (0) ;
}
  1. Analise seu código e o comente detalhadamente.

Passo 1

Então, a gente tem aqui um exercício que pede para analisar e comentar detalhadamente um código em C chamado fork.c. O foco principal desse código é a criação de processos no sistema operacional UNIX usando a função fork(). Para entender e resolver esse exercício, a gente vai precisar conhecer alguns conceitos básicos de sistemas operacionais, como processos, e entender como a função fork() funciona para criar processos filhos a partir de um processo pai. Também vamos precisar entender algumas funções básicas de C, como printf(), getpid(), getppid(), perror(), exit(), wait() e sleep().

Passo 2

Vamos começar analisando o início do código:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
Aqui, o programa inclui várias bibliotecas padrão em C:
  • <unistd.h>: contém as declarações para as funções POSIX, como fork(), getpid(), etc.
  • <stdio.h>: usada para funções de entrada e saída, como printf().
  • <stdlib.h>: inclui funções de utilidade geral, como exit().
  • <sys/types.h> e <sys/wait.h>: usadas para manipular tipos de dados e funções relacionadas à espera de processos, como wait().

Passo 3

Agora, vamos para a função main():
int main ()
{
    int retval ;
    printf (Ola, sou o processo %5d\n, getpid()) ;
    retval = fork () ;
    printf ([retval: %5d] sou %5d, filho de %5d\n, retval, getpid(), getppid()) ;
    ...
}
Primeiro, é declarado um inteiro retval que vai armazenar o valor de retorno da função fork().
Em seguida, temos uma chamada para printf():
printf (Ola, sou o processo %5d\n, getpid()) ;
Parece que há um pequeno erro na sintaxe do printf(). O correto seria:
printf("Ola, sou o processo %5d\n", getpid());
Aqui, printf() está imprimindo uma mensagem que inclui o ID do processo atual, obtido pela função getpid().

Passo 4

Agora, a linha importante:
retval = fork () ;
A função fork() é usada para criar um novo processo. Quando fork() é chamado, ele cria um processo filho que é uma cópia quase exata do processo pai. O valor de retorno de fork() é:
  • Negativo: se a criação do processo filho falhar.
  • Zero: para o processo filho recém-criado.
  • Positivo: o ID do processo filho para o processo pai.
Esse valor de retorno é armazenado na variável retval.

Passo 5

Após o fork(), temos outro printf():
printf ([retval: %5d] sou %5d, filho de %5d\n, retval, getpid(), getppid()) ;
Novamente, há um pequeno erro de sintaxe. O correto seria:
printf("[retval: %5d] sou %5d, filho de %5d\n", retval, getpid(), getppid());
Aqui, o programa está imprimindo três informações:
  1. retval: o valor retornado por fork().
  2. getpid(): o ID do processo atual.
  3. getppid(): o ID do processo pai.
Isso ajuda a identificar se estamos no processo pai ou no filho e qual é a relação entre eles.

Passo 6

Em seguida, temos uma estrutura condicional para tratar diferentes casos com base no valor de retval:
if ( retval < 0 ) // erro no fork()
{
    perror (Erro) ;
    exit (1) ;
}
else
if ( retval > 0 ) // sou o processo pai
    wait (0) ;
else // sou o processo filho
    sleep (5) ;
Vamos detalhar isso:
  • retval < 0: Significa que houve um erro ao tentar criar o processo filho.
    • perror("Erro");: A função perror() imprime uma mensagem de erro baseada no erro que ocorreu.
    • exit(1);: Encerra o programa com o código de saída 1, indicando que houve um erro.
  • retval > 0: Isso ocorre no processo pai, pois retval recebe o ID do processo filho.
    • wait(0);: A função wait() faz com que o processo pai espere até que o processo filho termine sua execução.
  • else: Esse é o caso em que retval == 0, ou seja, estamos no processo filho.
    • sleep(5);: Faz com que o processo filho “durma” por 5 segundos antes de continuar.

Passo 7

Depois da estrutura condicional, temos mais um printf() e a finalização do programa:
printf (Tchau de %5d!\n, getpid()) ;
exit (0) ;
Novamente, corrigindo a sintaxe:
printf("Tchau de %5d!\n", getpid());
exit(0);
Aqui, o programa está imprimindo uma mensagem de despedida que inclui o ID do processo atual e, em seguida, encerra o processo com exit(0);, indicando uma finalização bem-sucedida.

Passo 8

Resumindo, o que esse programa faz é:
  1. Imprime o ID do processo inicial.
  2. Tenta criar um processo filho usando fork().
  3. Imprime o valor retornado por fork(), o ID do processo atual e o ID do processo pai.
  4. Se fork() falhar, imprime uma mensagem de erro e encerra.
  5. Se for o processo pai, espera que o processo filho termine.
  6. Se for o processo filho, “dorme” por 5 segundos.
  7. Ambos os processos (pai e filho) imprimem uma mensagem de despedida e encerram.

Resposta

O código fork.c demonstra como criar um processo filho a partir de um processo pai usando a função fork() em sistemas UNIX. Ele imprime informações sobre os processos, trata possíveis erros na criação do processo filho e coordena a execução entre pai e filho usando funções como wait() e sleep(). Essa análise detalhada ajuda a entender como os processos se relacionam e como o gerenciamento de processos funciona em ambiente UNIX.

Exercícios relacionados