深入理解sigaction函数:Linux信号处理机制与使用指南

目录

一、函数原型

二、功能概述

三、参数详解

1、signo

2、act

[1. sa_handler](#1. sa_handler)

[2. sa_mask](#2. sa_mask)

[3. sa_flags](#3. sa_flags)

[4. sa_sigaction](#4. sa_sigaction)

3、oact

四、使用示例

1、初始化阶段

2、注册信号处理

3、等待信号

[4、信号递达过程(当按下 Ctrl+C)](#4、信号递达过程(当按下 Ctrl+C))

5、三个表的状态变化

6、程序行为

7、关键特点

五、信号处理流程

六、注意事项

1、信号处理函数的简洁性

2、信号屏蔽字的设置

3、错误处理


一、函数原型

cpp 复制代码
#include <signal.h> 
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

二、功能概述

  • sigaction 函数是 Unix/Linux 系统中用于读取和修改与指定信号相关联的处理动作的核心函数。

  • 通过调用该函数,程序可以灵活地控制信号的处理方式,包括忽略信号、执行系统默认动作或自定义信号处理函数。调用成功时返回 0,失败则返回 -1 并设置相应的错误码。

  • 这个函数同之前学过的sigprocmask 函数有异曲同工之妙!!!可以类比来学习!!!


三、参数详解

1、signo

  • 指定要操作或查询的信号编号,例如 SIGINT(中断信号,通常由 Ctrl+C 触发)、SIGQUIT(退出信号,通常由 Ctrl+\ 触发)等。

  • 信号编号是系统预定义的常量,不同系统可能支持的信号种类和编号略有差异,但常见的信号在大多数系统中都是一致的。我们可以使用kill -l命令查看我们目前系统中的信号:

2、act

  • 指向 struct sigaction 结构体的指针,用于指定新的信号处理动作。

  • 如果 actNULL,则表示不修改当前信号的处理动作,仅查询原有动作。

struct sigaction 结构体的详细定义如下:

cpp 复制代码
struct sigaction {
    void (*sa_handler)(int);      // 信号处理函数指针或特殊常量
    sigset_t sa_mask;             // 信号屏蔽字
    int sa_flags;                 // 标志位,控制信号处理行为
    void (*sa_sigaction)(int, siginfo_t *, void *); // 实时信号处理函数(本章不详细讨论)
};

1. sa_handler

可以赋值为以下三种值之一:

  • SIG_IGN:表示忽略该信号。当信号递达时,系统将直接丢弃该信号,不会执行任何处理动作。

  • SIG_DFL :表示执行系统默认动作。不同信号的默认动作可能不同,例如 SIGINT 的默认动作是终止进程,SIGCHLD 的默认动作是忽略该信号。

  • 函数指针 :指向一个用户自定义的信号处理函数。该函数返回类型为 void,可以带一个 int 参数,通过该参数可以得知当前信号的编号,从而可以用同一个函数处理多种信号。需要注意的是,信号处理函数是一个回调函数,它不是由 main 函数直接调用,而是由系统在信号递达时自动调用。

2. sa_mask

  • 用于指定在调用信号处理函数期间需要额外屏蔽的信号集合。

  • 当某个信号的处理函数被调用时,内核会自动将当前信号加入进程的信号屏蔽字,即阻塞当前信号,防止在信号处理函数执行期间再次递达该信号,从而导致递归调用。(这个是它的作用和使用时机,很重要!!!)

  • 如果希望在调用信号处理函数时自动屏蔽其他一些信号,可以在 sa_mask 中设置这些信号的掩码。当信号处理函数返回时,内核会自动恢复原来的信号屏蔽字。(还有恢复处理执行动作!!!同样重要!!!)

3. sa_flags

包含一些标志位,用于控制信号处理的行为。在本章的代码示例中,通常将 sa_flags 设为 0,表示采用默认行为。常见的标志位包括:

  • SA_RESTART:如果信号中断了某个系统调用,设置该标志位后,系统调用会自动重新启动,而不是返回错误。

  • SA_NOCLDSTOP:对于 SIGCHLD 信号,设置该标志位后,当子进程停止或继续执行时,不会产生 SIGCHLD 信号。

4. sa_sigaction

是用于实时信号的处理函数,与 sa_handler 类似,但提供了更多的信息。在本章中不进行详细讨论,有兴趣的读者可以进一步了解实时信号处理机制。

3、oact

  • 如果 oact 是非空指针,则函数会将进程当前的act,也就是 struct sigaction 类型的结构体内容通过 oact 参数传出。这样,调用者就可以获取到进程当前的指定信号原来的处理动作。(输出型参数)

  • 如果 oactNULL,则表示不关心原有处理动作,仅设置新的处理动作。


四、使用示例

下面是一个使用 sigaction 函数设置自定义信号处理函数的示例代码:

cpp 复制代码
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

// 自定义信号处理函数
void my_handler(int signo) {
    printf("Received signal: %d\n", signo);
}

int main() {
    struct sigaction act, oact;

    // 设置新的信号处理动作
    act.sa_handler = my_handler; // 指定自定义处理函数
    sigemptyset(&act.sa_mask);  // 清空信号屏蔽字,不额外屏蔽任何信号
    act.sa_flags = 0;           // 采用默认标志位

    // 注册 SIGINT 信号的处理函数
    if (sigaction(SIGINT, &act, &oact) == -1) {
        perror("sigaction error");
        return 1;
    }

    printf("Press Ctrl+C to trigger SIGINT signal...\n");

    // 无限循环,等待信号触发
    while (1) {
        pause(); // 暂停进程,等待信号递达
    }

    return 0;
}

1、初始化阶段

cpp 复制代码
struct sigaction act, oact;
act.sa_handler = my_handler;  // 设置处理函数
sigemptyset(&act.sa_mask);    // 不额外屏蔽信号  
act.sa_flags = 0;             // 默认标志
  • 创建新的信号动作结构体 act

  • 指定信号处理函数为 my_handler

  • 设置空的信号屏蔽字(执行handler时不阻塞其他信号)

2、注册信号处理

cpp 复制代码
sigaction(SIGINT, &act, &oact);

内核表变化

  • handler表:SIGINT 的处理函数改为 my_handler

  • 保存旧的处理方式到 oact(可用于恢复)

3、等待信号

cpp 复制代码
while (1) {
    pause(); // 暂停进程,等待信号
}
  • pause() 使进程挂起,直到收到信号

  • 此时进程处于可中断的睡眠状态

4、信号递达过程(当按下 Ctrl+C)

  1. 信号产生:内核检测到 Ctrl+C,生成 SIGINT(2) 信号

  2. 检查阻塞:检查进程的 block 表,SIGINT 未被阻塞

  3. 执行处理

    • 内核临时 将 SIGINT 加入进程的 block 表**(防止重入,也就是防止再次执行对应信号的处理函数)**

    • 调用 my_handler(2) 执行自定义处理

    • 恢复原来的 block 表

  4. 继续运行 :处理完成后,进程从 pause() 返回,继续循环

5、三个表的状态变化

表类型 初始状态 信号产生时 处理过程中
handler表 SIGINT → my_handler 不变 不变
block表 SIGINT未阻塞 不变 临时阻塞SIGINT
pending表 全0 SIGINT位=1 清0再处理

6、程序行为

  • 运行:程序启动后显示提示信息,然后挂起

  • 触发 :按下 Ctrl+C 时执行 my_handler 打印消息

  • 结果:每次 Ctrl+C 都会打印 "Received signal: 2",程序继续运行

7、关键特点

  • 使用 sigaction :比 signal 更可靠,可移植性更好

  • 自动防重入:执行handler时自动阻塞同种信号,直到执行完当前的信号处理函数后再清除当前的阻塞

  • 不会终止:自定义handler改变了SIGINT的默认终止行为

这是一个典型的信号捕获示例,展示了如何改变信号的默认行为!


五、信号处理流程

当使用 sigaction 注册了自定义信号处理函数后,信号的处理流程如下:

  1. 当信号递达至进程时,内核会检查该信号是否被忽略或设置了默认处理动作。如果设置了自定义处理函数,内核会保存当前进程的上下文(包括寄存器状态、堆栈指针等),然后切换到信号处理函数的堆栈空间。

  2. 调用自定义信号处理函数,并将信号编号作为参数传递给该函数。

  3. 信号处理函数执行完毕后,内核会自动恢复之前保存的进程上下文,使进程从被中断的位置继续执行。

  4. 如果在信号处理函数执行期间,有其他信号递达且该信号被包含在 sa_mask 中,这些信号会被阻塞,直到信号处理函数返回后才被处理。


六、注意事项

1、信号处理函数的简洁性

  • 由于信号处理函数是在一个相对独立的环境中执行的,且可能在任何时候被调用,因此应尽量保持信号处理函数的简洁,避免执行复杂的操作,如调用不可重入函数(如 printfmalloc 等),以免引发竞态条件或其他问题。

2、信号屏蔽字的设置

  • 合理设置 sa_mask 可以避免信号处理过程中的竞态条件。

  • 例如,在处理某个信号时,如果该信号的处理函数会修改某些共享数据,可以屏蔽其他可能访问这些数据的信号,确保数据的一致性。

3、错误处理

  • 在使用 sigaction 函数时,应始终检查其返回值,以确保操作成功。

  • 如果调用失败,可以通过 perror 等函数输出错误信息,便于调试。

通过合理使用 sigaction 函数,程序可以实现对信号的灵活控制,提高程序的健壮性和可靠性。

相关推荐
_dindong3 小时前
Linux网络编程:进程间关系和守护进程
linux·运维·服务器·网络·c++·学习
zhilin_tang3 小时前
如何写一个WebRTC ACE音频应用处理模块
linux·c语言·c++
Jtti4 小时前
如何通过检查MySQL与系统日志以找出服务器CPU占用源
服务器·mysql·adb
zt1985q4 小时前
本地部署消息中间件 RabbitMQ 并实现外网访问 (Linux 版本)
linux·运维·服务器·windows·分布式·rabbitmq
tb_first4 小时前
Linux入门2(1/3)
linux·运维·服务器
鼓掌MVP4 小时前
通过MCP协议结合腾讯云Lighthouse
大数据·运维
久曲健的测试窝4 小时前
Jenkins Share Library教程 —— 企业级 Jenkins Shared Library 实战示例
运维·jenkins
Wang's Blog4 小时前
Linux小课堂: Tomcat容器中部署Jenkins的完整流程与关键技术要点
linux·tomcat·jenkins
要站在顶端5 小时前
Jenkins Pipeline 多job依赖、触发多Job、并行执行及制品下载
运维·servlet·jenkins