Fundamentos UNIX
1. Criação de Processos
1.1 Identificadores
- pid (process id): identificador único de um processo.
- ppid (parent process id): identificador do processo pai.
Exemplo:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t meupid = getpid();
printf("Pid atual do processo: %d\n", meupid);
pid_t pidpai = getppid();
printf("Pid do pai do processo: %d\n", pidpai);
return 0;
}
1.2 fork()
- Cria uma cópia do processo atual (pai → filho).
- Pai e filho continuam a execução do mesmo ponto.
-
O filho herda quase toda a tabela de processos do pai.
-
Retorno:
- 0 → processo filho.
- pid do filho → processo pai.
Exemplo:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t id_filho;
printf("Processo inicial possui pid: %d\n", getpid());
id_filho = fork();
if (id_filho == 0)
printf("Olá, sou o processo filho de pid: %d!\n", getpid());
else
printf("Olá, sou o processo pai (%d) e criei o filho: %d!\n", getpid(), id_filho);
return 0;
}
// Saída esperada:
// Processo inicial possui pid: 16370
// Olá, sou o processo pai (16370) e criei o filho: 16371!
// Olá, sou o processo filho de pid: 16371!
2. Carga de Arquivo Executável
- fork(): cria processos por clonagem (mesmo programa).
-
exec(): substitui o processo atual por um novo programa.
-
O pid continua o mesmo, mas código e dados são substituídos.
- Retorna apenas em caso de falha.
-
Em caso de sucesso:
- Código do programa atual é descartado.
- O processo carrega o novo programa na memória.
- A execução começa do início (
main()
).
2.1 Famílias de funções exec
Formas de passar argumentos:
v
: argumentos em vetor →execv("/bin/ls", argv)
.l
: argumentos em lista variada →execl("/bin/ls", "ls", "-l", NULL)
.
Onde procurar o programa:
p
: procura no PATH →execlp("ls", "ls", "-l", NULL)
.- sem
p
: exige caminho completo →execl("/bin/ls", "ls", "-l", NULL)
.
Variáveis de ambiente:
- Com
e
: permite passar variáveis de ambiente. - Sem
e
: herda o ambiente do processo atual.
3. Sinais
3.1 O que são
- Mecanismo do UNIX para comunicação entre processos.
- Alteram o comportamento do processo alvo.
-
Ao receber um sinal, o processo pode:
-
Ser interrompido e executar um handler (função tratadora).
- Ignorar o sinal.
3.2 Exemplos de sinais
SIGTERM
: Terminar (padrão).SIGKILL
: Finalização forçada (não pode ser ignorado).SIGUSR1
,SIGUSR2
: Uso definido pelo usuário.SIGSEGV
: Acesso inválido à memória (segfault).SIGFPE
: Erro de ponto flutuante.
3.3 Envio de sinais
Pelo terminal:
- Executar o programa.
- Encontrar o pid com
ps aux | grep nome_do_programa
. - Enviar sinal:
kill -SIGUSR1 2240
.
No código em C:
```c
kill(pid_filho, SIGUSR1);
```
3.4 Exemplo completo
Exemplo:
#include <signal.h>
#include <stdio.h>
int contador = 0;
void handler(int signal_number) {
contador++;
}
int main() {
struct sigaction sa = {};
sa.sa_handler = &handler; // define o tratador
sigaction(SIGUSR1, &sa, NULL);
// Quando esse processo receber SIGUSR1, executa handler
printf("Pressione E para terminar a execução:\n");
while (getchar() != 'E') ;
printf("SIGUSR1 recebido %d vezes\n", contador);
return 0;
}
4. Término de Processos
4.1 Formas de término
Por sinais: SIGTERM
, SIGKILL
.
Voluntariamente:
- Retornando da função
main
. - Chamando
exit()
:exit(0)
: sucesso.exit(1)
: erro.
4.2 Comunicação com processo pai
- wait(): processo pai bloqueia até o filho terminar.
- pause(): processo atual pausa até receber sinal.
-
Macros úteis:
WIFEXITED(status)
: verifica se o filho terminou normalmente.WEXITSTATUS(status)
: obtém valor de retorno do filho.
4.3 Processos Zumbis
Normal: pai cria filho, depois chama wait()
para receber informações de término.
Zumbi: filho termina, mas o pai não chama wait()
. O processo permanece na tabela como "zumbi" até o pai coletar seu status.
Exemplo:
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
pid_t filho;
filho = fork();
if (filho > 0)
sleep(6);
else
exit(0);
wait(NULL); // elimina o zumbi
return 0;
}
5. Considerações Finais
- fork + exec são a base para criação e substituição de processos no UNIX.
- Sinais permitem comunicação e controle.
- O gerenciamento de término é essencial para evitar processos zumbis.