Estados de Processos no Linux: Um Guia Prático

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 com SIGCONT.
  • 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.

Tags: Linux task_struct escalonamento de processos estados de processo fila de espera

Publicado em 7-13 23:26