Uso da função sigaction e do sinal SIGCHLD em Linux

A função sigaction é utilizada para registrar uma rotina de tratamento de sinais no sistema Linux.

Assinatura da função:

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

Parâmetros:

  • signum: o sinal que será capturado
  • act: parâmetro de entrada, define o novo comportamento para o sinal
  • oldact: parâmetro de saída, armazena o comportamento anterior do sinal

A estrutura sigaction é definida da seguinte forma:

struct sigaction {
    void (*sa_handler)(int);      // Função de tratamento do sinal
    void (*sa_sigaction)(int, siginfo_t *, void *); // Função alternativa de tratamento
    sigset_t sa_mask;             // Sinais a serem bloqueados durante o tratamento
    int sa_flags;                 // Sinalizadores de comportamento
    void (*sa_restorer)(void);    // Função de restauração (obsoleta)
};

Exemplo de implementação

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>

void manipulador_sinal(int sinal) {
    printf("Sinal recebido: [%d]\n", sinal);
    sleep(3);
}

int principal() {
    struct sigacao acao;
    
    // Define a função de tratamento
    acao.sa_handler = manipulador_sinal;
    
    // Inicializa o conjunto de sinais bloqueados (nenhum bloqueado)
    sigemptyset(&acao.sa_mask);
    
    // Define flags padrão
    acao.sa_flags = 0;
    
    // Registra o tratamento para SIGINT (Ctrl+C)
    if (sigaction(SIGINT, &acao, NULL) == -1) {
        perror("Falha ao registrar sigaction");
        return 1;
    }
    
    // Loop infinito para manter o programa executando
    while(1) {
        sleep(1);
    }
    
    return 0;
}

Comportamento observado: Durante a execução da função de tratamento de um sinal, se o mesmo sinal for gerado múltiplas vezes, eles entram no conjunto de sinais pendentes (bloqueados). Após a conclusão do tratamento, apenas uma instância do sinal será processada, pois os sinais não são enfileirados.

Bloqueio de sinais: Se outros sinais forem especificados em sa_mask, eles também serão bloqueados durante a execução da função de tratamento. Esses sinais pendentes serão processados apenas após a conclusão do tratamento atual.

Sinal SIGCHLD

Condições que geram o sinal SIGCHLD:

  • Quando um processo filho termina
  • Quando um processo filho recebe o sinal SIGSTOP
  • Quando um processo filho parado recebe o sinal SIGCONT

Função do sinal SIGCHLD:

O kernel envia o sinal SIGCHLD ao processo pai quando um processo filho termina. Ao receber este sinal, o processo pai pode utilizar as funções wait() ou waitpid() para recuperar os recursos do processo filho.

Exemplo: Processo pai gerenciando múltiplos processos filhos

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<signal.h>

void manipulador_sigchld(int sinal) {
    pid_t pid_filho;
    
    // Loop contínuo para evitar僵尸进程 (processos zumbi)
    while(1) {
        // Espera por qualquer processo filho com WNOHANG (não bloqueante)
        pid_filho = waitpid(-1, NULL, WNOHANG);
        
        if (pid_filho > 0) {
            printf("Processo filho [%d] encerrado\n", pid_filho);
        } 
        else if (pid_filho == 0) {
            // Ainda há processos filhos em execução
            break;
        } 
        else {
            // Não há mais processos filhos
            printf("Todos os processos filhos foram encerrados\n");
            break;
        }
    }
}

int principal() {
    sigset_t conjunto_sinais;
    
    // Bloqueia SIGCHLD temporariamente
    sigemptyset(&conjunto_sinais);
    sigaddset(&conjunto_sinais, SIGCHLD);
    sigprocmask(SIG_BLOCK, &conjunto_sinais, NULL);
    
    // Cria dois processos filhos
    for (int i = 0; i < 2; i++) {
        pid_t pid = fork();
        
        if (pid < 0) {
            perror("Falha ao criar processo filho");
            return 1;
        } 
        else if (pid == 0) {
            // Processo filho
            printf("Processo filho [%d] criado (PID: %d, PID pai: %d)\n", 
                   i, getpid(), getppid());
            exit(0);
        }
    }
    
    // Configura o manipulador de SIGCHLD
    struct sigacao acao;
    acao.sa_handler = manipulador_sigchld;
    sigemptyset(&acao.sa_mask);
    acao.sa_flags = 0;
    sigaction(SIGCHLD, &acao, NULL);
    
    // Desbloqueia SIGCHLD
    sigprocmask(SIG_UNBLOCK, &conjunto_sinais, NULL);
    
    // Mantém o processo pai executando
    while(1) {
        sleep(1);
    }
    
    return 0;
}

Considerações importantes:

  • É possível que os processos filhos terminem antes que o manipulador de sinais seja registrado. A solução é bloquear o sinal SIGCHLD antes de criar os processos filhos e desbloqueá-lo após registrar o manipulador.
  • Se um novo sinal SIGCHLD for gerado durante o tratamento do sinal atual, ele será bloqueado. Como os sinais não são enfileiraods, múltiplas omissões podem resultar em僵尸进程. A solução é usar um loop contínuo na função de tratamento para recuperar todos os processos filhos que podem ter terminado.

Comunicação entre processos pai e filho via sinais

Exemplo de implementação de uma contagem alternada entre processos pai e filho usando sinais:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>

int contador = 0;  // Variável global para contagem
int controle = 0;  // Variável de controle

void manipulador_pai(int sinal) {
    printf("Pai: [%d]\n", contador);
    contador += 2;
    controle = 0;
    sleep(1);
}

void manipulador_filho(int sinal) {
    printf("Filho: [%d]\n", contador);
    contador += 2;
    controle = 0;
    sleep(1);
}

int principal() {
    pid_t pid = fork();
    
    if (pid < 0) {
        perror("Falha ao criar processo filho");
        return 1;
    } 
    else if (pid > 0) {
        // Processo pai
        signal(SIGUSR1, manipulador_pai);
        controle = 1;  // Inicia com controle para o filho enviar primeiro
        
        while(1) {
            if (controle == 0) {
                kill(pid, SIGUSR2);
                controle = 1;
            }
        }
    } 
    else {
        // Processo filho
        contador = 1;  // Filho começa com 1
        signal(SIGUSR2, manipulador_filho);
        
        while(1) {
            if (controle == 0) {
                kill(getppid(), SIGUSR1);
                controle = 1;
            }
        }
    }
}

Neste exemplo, os processos pai e filho alternam a execução enviando sinais um ao outro, criando um efeito de contagem alternada entre os dois processos.

Tags: Linux sinais processos sigaction SIGCHLD

Publicado em 6-8 20:49 por Thomas