Linux sigaction函数详解:信号处理的强大工具

Linux sigaction函数详解:信号处理的强大工具
引言
sigaction函数概述
struct sigaction结构体
关键成员解析
sigaction的核心优势
使用示例
基本用法
高级用法(使用SA_SIGINFO)
信号处理的最佳实践
常见问题与解决方案
1. 系统调用被信号中断
2. 信号处理函数中调用不可重入函数
3. 信号丢失
与signal()函数的比较
结论
引言
在Linux系统编程中,信号处理是一个非常重要的主题。信号是进程间通信的一种基本方式,用于通知进程发生了某种事件。传统的signal()函数虽然简单易用,但在功能性和可移植性方面存在诸多限制。而sigaction()函数提供了更强大、更灵活的信号处理机制,是现代Linux程序设计中处理信号的首选方式。

sigaction函数概述
sigaction()函数允许我们检查或指定与特定信号相关联的处理动作,其函数原型如下:

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
AI写代码
c
运行
1
2
3
参数说明:

signum:要操作的信号编号(如SIGINT、SIGTERM等)
act:指向新的信号处理动作的指针
oldact:用于保存之前的信号处理动作(可为NULL)
返回值:

成功时返回0,失败时返回-1并设置errno
struct sigaction结构体
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); // 已废弃
};
AI写代码
c
运行
1
2
3
4
5
6
7
关键成员解析
sa_handler:传统的信号处理函数指针,与signal()函数使用的处理函数类型相同。

sa_sigaction:更高级的信号处理函数指针,可以接收额外的信号信息。当sa_flags包含SA_SIGINFO标志时使用此处理函数。

sa_mask:在执行信号处理函数期间,需要阻塞的信号集。这可以防止信号处理函数被其他信号中断。

sa_flags:控制信号行为的各种标志,常见的有:

SA_RESTART:被信号中断的系统调用自动重启
SA_SIGINFO:使用sa_sigaction而非sa_handler作为处理函数
SA_NOCLDSTOP:对于SIGCHLD信号,不接收子进程停止的通知
SA_NODEFER:不阻塞当前信号(不自动将当前信号加入信号掩码)
sigaction的核心优势
相比传统的signal()函数,sigaction()具有以下显著优势:

更可靠的信号处理:提供了对信号处理过程的更精细控制
信号屏蔽:可以指定在处理当前信号时阻塞哪些其他信号
附加信息:通过SA_SIGINFO标志可以获取关于信号的更多上下文信息
可移植性:在不同UNIX系统上行为一致
系统调用重启:通过SA_RESTART标志可以自动重启被中断的系统调用
使用示例
基本用法
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void handler(int sig) {
printf("Received signal %d\n", sig);
}

int main() {
struct sigaction sa;

sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;

if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}

while (1) {
printf("Waiting for signal...\n");
sleep(1);
}

return 0;
}
AI写代码
c
运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
高级用法(使用SA_SIGINFO)
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

void handler(int sig, siginfo_t *info, void *ucontext) {
printf("Received signal %d from process %d\n", sig, info->si_pid);
printf("Additional data: %d\n", info->si_value.sival_int);
}

int main() {
struct sigaction sa;

memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;

if (sigaction(SIGUSR1, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}

printf("My PID: %d\n", getpid());
printf("Waiting for signal...\n");

while (1) {
pause();
}

return 0;
}
AI写代码
c
运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
信号处理的最佳实践
保持信号处理函数简单:信号处理函数应尽可能简单,避免调用非异步信号安全的函数。

正确处理可重入性:确保在信号处理函数中只使用异步信号安全的函数。

使用volatile sig_atomic_t:对于需要在信号处理函数和主程序之间共享的变量,应使用volatile sig_atomic_t类型。

考虑信号排队:注意大多数信号不会排队,连续发送的相同信号可能会被合并。

正确处理EINTR:即使使用SA_RESTART,某些系统调用仍可能被中断,需要适当处理。

常见问题与解决方案
1. 系统调用被信号中断
解决方案:设置SA_RESTART标志或手动重启系统调用。

sa.sa_flags = SA_RESTART; // 自动重启被中断的系统调用
AI写代码
c
运行
1
2. 信号处理函数中调用不可重入函数
解决方案:仅使用异步信号安全的函数,或通过设置标志在主程序中执行复杂操作。

3. 信号丢失
解决方案:使用实时信号(SIGRTMIN到SIGRTMAX)并设置SA_SIGINFO标志,这些信号支持排队。

与signal()函数的比较
特性 signal() sigaction()
可移植性 差 好
信号屏蔽控制 无 有
附加信号信息 无 有
系统调用重启控制 无 有
处理函数重置行为 不一致 可控
结论
sigaction()函数是Linux系统编程中处理信号的强大工具,它提供了比传统signal()函数更丰富、更可靠的功能特性。通过合理使用sigaction(),开发者可以构建更健壮、更可靠的应用程序,有效处理各种异步事件。掌握sigaction()的使用是成为熟练Linux系统程序员的重要一步。

在实际开发中,建议始终使用sigaction()而非signal(),除非有特别简单的需求且可移植性不是考虑因素。通过sigaction()提供的丰富选项,可以应对各种复杂的信号处理场景。
————————————————
版权声明:本文为CSDN博主「郝学胜-神的一滴」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2503_92624912/article/details/155679214

阅读剩余
THE END