结构体 sigaction 详解

615 阅读4分钟

结构体 sigaction 详解

在 Unix 和类 Unix 系统(如 Linux)中,信号(signal)是操作系统与进程之间的一种异步通信机制。信号可以用于通知进程某些异步事件的发生,例如按下 Ctrl+C 触发的 SIGINT 信号,或者一个子进程结束时触发的 SIGCHLD 信号。为了处理这些信号,程序需要使用适当的信号处理机制。struct sigaction 是一种现代且灵活的方式来处理信号,与传统的 signal 函数相比,它提供了更高的控制能力。

什么是 struct sigaction

struct sigaction 是用来设置自定义的信号处理函数的结构体,它包含了信号处理函数以及一些控制信号处理行为的选项。它通常与 sigaction() 系统调用一起使用,用于捕捉和处理信号。

struct sigaction 的定义

在头文件 <signal.h> 中,struct sigaction 通常定义如下:

struct sigaction {
    void (*sa_handler)(int);         // 信号处理函数
    void (*sa_sigaction)(int, siginfo_t *, void *);  // 扩展的信号处理函数
    sigset_t sa_mask;                // 在处理该信号时要阻塞的其他信号
    int sa_flags;                    // 控制信号处理行为的标志
    void (*sa_restorer)(void);       // 废弃字段(通常不使用)
};

struct sigaction 的成员解释

  1. sa_handler

    • 这是指向信号处理函数的指针。当某个信号发生时,操作系统会调用这个函数。
    • 该函数接受一个 int 类型的参数,表示信号编号(如 SIGINT, SIGTERM 等)。
    • 可以将 sa_handler 设置为以下几种值:
      • 自定义信号处理函数:程序自定义的函数,用于处理信号。
      • SIG_DFL:执行该信号的默认处理动作。
      • SIG_IGN:忽略该信号。

    示例:

    void signal_handler(int signum) {
        printf("Received signal %d\n", signum);
    }
    
  2. sa_sigaction

    • 这是 sa_handler 的一个增强版本,适用于需要获取更详细信号信息的情况。
    • sa_flags 中设置了 SA_SIGINFO 标志时,sa_sigaction 会被调用,而不是 sa_handler
    • sa_sigaction 提供了三个参数:
      • int:表示信号编号。
      • siginfo_t *:提供关于信号的更多详细信息,如信号来源、进程ID等。
      • void *:提供与信号相关的上下文信息(如 CPU 寄存器的状态)。

    示例:

    void extended_signal_handler(int signum, siginfo_t *info, void *context) {
        printf("Received signal %d from process %d\n", signum, info->si_pid);
    }
    
  3. sa_mask

    • sa_mask 是一个 sigset_t 类型的信号集,用于指定在处理当前信号时,应该被阻塞的其他信号。
    • 在信号处理程序运行时,sa_mask 中的信号会被暂时阻塞,以防止它们中断当前的信号处理。
    • 可以通过 sigemptyset() 清空信号集,或通过 sigaddset() 添加需要阻塞的信号。

    示例:

    sigemptyset(&act.sa_mask);   // 清空信号掩码
    sigaddset(&act.sa_mask, SIGTERM);   // 阻塞 SIGTERM 信号
    
  4. sa_flags

    • sa_flags 用于设置信号处理行为的标志位,可以取多个值:
      • SA_RESTART:让被信号中断的系统调用自动重启。
      • SA_SIGINFO:启用 sa_sigaction 处理信号,而非 sa_handler
      • SA_NOCLDSTOP:如果信号为 SIGCHLD,当子进程暂停时,不发送此信号。

    示例:

    act.sa_flags = SA_RESTART;  // 启用系统调用自动重启
    
  5. sa_restorer

    • 这是一个过时的字段,通常不需要设置和使用。它曾经用于指定信号处理函数返回时的清理函数,但现在已经被废弃。

使用 struct sigaction 设置信号处理程序

要使用 sigaction 处理信号,首先需要定义一个 struct sigaction 结构体,然后使用 sigaction() 系统调用来注册信号处理程序。

示例代码

以下是一个完整的使用 sigaction 捕捉 SIGINT 信号(通常是按下 Ctrl+C 时触发的信号)的示例:

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

void signal_handler(int signum) {
    printf("Caught signal %d\n", signum);
}

int main() {
    struct sigaction act;
    
    // 指定处理函数
    act.sa_handler = signal_handler;
    
    // 清空信号掩码,表示不阻塞任何信号
    sigemptyset(&act.sa_mask);
    
    // 使用默认标志
    act.sa_flags = 0;
    
    // 注册 SIGINT 信号的处理程序
    sigaction(SIGINT, &act, NULL);
    
    // 无限循环,等待信号
    while (1) {
        printf("Waiting for signal...\n");
        sleep(1);
    }

    return 0;
}

输出:

当按下 Ctrl+C 时,程序将输出:

Waiting for signal...
Caught signal 2

其中,2SIGINT 信号的编号。

总结

struct sigaction 提供了灵活且强大的信号处理机制,使程序可以在接收到信号时执行自定义的处理函数。与传统的 signal() 函数相比,它提供了更多的选项,如信号掩码、扩展的 sa_sigaction 和更多的控制标志。通过 sigaction,程序可以在异步事件发生时做出更精细和可靠的处理,广泛应用于系统级编程、网络服务等需要信号管理的领域。