深入理解sigaction函数:Linux信号处理机制与使用指南
目录
一、函数原型
二、功能概述
三、参数详解
1、signo
2、act
1. sa_handler
2. sa_mask
3. sa_flags
4. sa_sigaction
3、oact
四、使用示例
1、初始化阶段
2、注册信号处理
3、等待信号
4、信号递达过程(当按下 Ctrl+C)
5、三个表的状态变化
6、程序行为
7、关键特点
五、信号处理流程
六、注意事项
1、信号处理函数的简洁性
2、信号屏蔽字的设置
3、错误处理
一、函数原型

#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结构体的指针,用于指定新的信号处理动作。 -
如果
act为NULL,则表示不修改当前信号的处理动作,仅查询原有动作。
struct sigaction 结构体的详细定义如下:
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参数传出。这样,调用者就可以获取到进程当前的指定信号原来的处理动作。(输出型参数) -
如果
oact为NULL,则表示不关心原有处理动作,仅设置新的处理动作。
四、使用示例
下面是一个使用 sigaction 函数设置自定义信号处理函数的示例代码:
#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、初始化阶段
struct sigaction act, oact;
act.sa_handler = my_handler; // 设置处理函数
sigemptyset(&act.sa_mask); // 不额外屏蔽信号
act.sa_flags = 0; // 默认标志
-
创建新的信号动作结构体
act -
指定信号处理函数为
my_handler -
设置空的信号屏蔽字(执行handler时不阻塞其他信号)
2、注册信号处理
sigaction(SIGINT, &act, &oact);
内核表变化:
-
handler表:SIGINT 的处理函数改为my_handler -
保存旧的处理方式到
oact(可用于恢复)
3、等待信号
while (1) {pause(); // 暂停进程,等待信号
}
-
pause()使进程挂起,直到收到信号 -
此时进程处于可中断的睡眠状态
4、信号递达过程(当按下 Ctrl+C)
-
信号产生:内核检测到 Ctrl+C,生成 SIGINT(2) 信号
-
检查阻塞:检查进程的 block 表,SIGINT 未被阻塞
-
执行处理:
-
内核临时将 SIGINT 加入进程的 block 表(防止重入,也就是防止再次执行对应信号的处理函数)
-
调用
my_handler(2)执行自定义处理 -
恢复原来的 block 表
-
-
继续运行:处理完成后,进程从
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 注册了自定义信号处理函数后,信号的处理流程如下:
-
当信号递达至进程时,内核会检查该信号是否被忽略或设置了默认处理动作。如果设置了自定义处理函数,内核会保存当前进程的上下文(包括寄存器状态、堆栈指针等),然后切换到信号处理函数的堆栈空间。
-
调用自定义信号处理函数,并将信号编号作为参数传递给该函数。
-
信号处理函数执行完毕后,内核会自动恢复之前保存的进程上下文,使进程从被中断的位置继续执行。
-
如果在信号处理函数执行期间,有其他信号递达且该信号被包含在
sa_mask中,这些信号会被阻塞,直到信号处理函数返回后才被处理。
六、注意事项
1、信号处理函数的简洁性
- 由于信号处理函数是在一个相对独立的环境中执行的,且可能在任何时候被调用,因此应尽量保持信号处理函数的简洁,避免执行复杂的操作,如调用不可重入函数(如
printf、malloc等),以免引发竞态条件或其他问题。
2、信号屏蔽字的设置
-
合理设置
sa_mask可以避免信号处理过程中的竞态条件。 -
例如,在处理某个信号时,如果该信号的处理函数会修改某些共享数据,可以屏蔽其他可能访问这些数据的信号,确保数据的一致性。
3、错误处理
-
在使用
sigaction函数时,应始终检查其返回值,以确保操作成功。 -
如果调用失败,可以通过
perror等函数输出错误信息,便于调试。
通过合理使用 sigaction 函数,程序可以实现对信号的灵活控制,提高程序的健壮性和可靠性。
