Um processo no Linux, representado pela estrutura task_struct, possui um estado interno indicado por um valor inteiro. O kernel define um conjunto de estados possíveis, descritos no array task_state_array:
static const char *const task_state_array[] = {
"R (running)",
"S (sleeping)",
"D (disk sleep)",
"T (stopped)",
"t (tracing stop)",
"X (dead)",
"Z (zombie)",
};
- R (Running): O processo está em execução ou na fila de prontos para executar.
- S (Sleeping): O processo aguarda a conclusão de um evento (sono interrompível).
- D (Disk Sleep): Sono ininterruptível, geralmente esperando operações de E/S.
- T (Stopped): O processo foi pausado (ex: sinal
SIGSTOP) e pode ser retomado comSIGCONT. - X (Dead): Estado de retorno, não visível em listas de processos.
Para verificar os estados, utilize comandos como ps aux ou ps axj:
-a: Processos do terminal atual, incluindo de outros usuários.-x: Processos sem terminal de controle (ex: daemons).-j: Exibe informações de grupo, sessão e processo pai.-u: Formato centrado no usuário com detalhes de CPU/memória.
Escalonamento de Processos
O escalonador do kernel distribui o tempo de CPU entre múltiplos processos para garantir:
- Justiça: Cada processo recebe uma fração razoável da CPU.
- Eficiência: Baixo custo de troca de contexto.
- Tempo real: Resposta rápida para processos críticos.
- Balanceamento de carga: Distribuição equilibrada em sistemas multicore.
Cada task_struct contém ponteiros next e prev, formando uma lista ligada que constitui a fila de escalonamento (runqueue). A fila opera em FIFO: o processo na frente é o próximo a executar. Para evitar que um processo monopoilze a CPU, o kernel usa fatias de tempo (time slices). Quando a fatia expira, o processo é suspenso e o próximo da fila é executado.
Execução, Bloqueio e Suspensão
Execução
Processos em execução (estado R) estão ativos na CPU ou aguardando na fila de prontos.
Bloqueio
O bloqueio ocorre quando um processo aguarda um evento (ex: conclusão de E/S, aquisição de um lock, sinal). O task_struct é removido da CPU e inserido na fila de espera do dispositivo correspondente. Quando o evento ocorre, o kernel acorda o processo, copiando dados do buffer do kernel para o espaço do usuário.
O estado S (sono interrompível) permite que o processo seja morto (ex: Ctrl+C). Já o estado D (sono ininterruptível) protege operações críticas (ex: transações bancárias) contra interrupções.
Suspensão
A suspensão (estado T) é acionada externamente por sinais como SIGSTOP ou pelo depurador (ptrace). O processo permancee na memória, mas não é escalonado até receber SIGCONT. Diferente do bloqueio, a suspensão é uma intervenção externa, não uma espera interna.
Comparação: Bloqueio vs. Suspensão
| Dimensão | Bloqueio | Suspensão |
|---|---|---|
| Gatilho | Chamada de sistema interna (ex: read, lock) | Sinal externo (ex: SIGSTOP, depuradro) |
| Estado Linux | S ou D | T |
| Recuperação | Automática (kernel acorda o processo) | Manual (necessita SIGCONT) |
| Sinais | S: responde; D: não responde | Responde, mas alguns são armazenados |
| Memória | Normalmente residente | Residente (pode ser swapada) |
Listas Ligadas no Kernel
O kernel utiliza a estrutura list_head para conectar objetos de dados, não ponteiros diretos. list_head contém ponteiros next e prev. Como a lista não sabe o tipo do objeto que a contém, usa-se a macro container_of para obter o ponteiro do objeto a partir do membro da lista:
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
struct my_data {
int valor;
struct list_head lista;
};
struct my_data *entry = container_of(list_node, struct my_data, lista);
Múltiplos campos list_head em uma task_struct permitem que a mesma estrutura participe de várias filas simultaneamente (ex: fila de escalonamento e fila de espera).
Processos Zumbi
Um processo zumbi (estado Z) ocorre quando o processo filho encerra, mas o pai não lê seu código de retorno (via wait() ou similar). O task_struct do filho permanece na tabela de processos até que o pai colete o status, consumindo memória.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return 1;
}
if (pid > 0) { // pai
printf("Pai[%d] dormindo...\n", getpid());
sleep(30);
} else { // filho
printf("Filho[%d] entrou em estado Z...\n", getpid());
sleep(5);
exit(0);
}
return 0;
}
Consequências: Processos zumbi acumulados consomem entradas na tabela de processos e memória do kernel, caracterizando um vazamento de recursos.
Processos Órfãos
Quando o pai termina antes do filho, o processo filho torna-se órfão. O kernel reassigna o filho ao processo init (PID 1), que coletará seu status de saída.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return 1;
}
if (pid == 0) { // filho
printf("Filho, pid: %d\n", getpid());
sleep(30);
} else { // pai
printf("Pai, pid: %d\n", getpid());
exit(0);
}
return 0;
}
Após o pai sair, o filho será adotado pelo init, que aguardará sua finalização e liberará seus recursos.