Linux信号捕捉的特性详解
- 信号的基本概念
- 信号捕捉的基本机制
-
- [1. signal()函数](#1. signal()函数)
- [2. sigaction()函数](#2. sigaction()函数)
- 信号捕捉的关键特性
-
- [1. 信号处理函数的可重入性](#1. 信号处理函数的可重入性)
- [2. 信号屏蔽与阻塞](#2. 信号屏蔽与阻塞)
- [3. 信号的可靠性与不可靠信号](#3. 信号的可靠性与不可靠信号)
- [4. 信号处理期间的信号屏蔽](#4. 信号处理期间的信号屏蔽)
- 高级信号处理技术
-
- [1. 使用sigwait同步处理信号](#1. 使用sigwait同步处理信号)
- [2. 信号栈](#2. 信号栈)
- [3. 实时信号](#3. 实时信号)
- 信号捕捉的最佳实践
- 示例:完整的信号处理程序
- 总结
信号的基本概念
在Linux系统中,信号是进程间通信的一种基本方式,用于通知进程发生了某种事件。信号可以由内核、其他进程或进程自身发送。Linux系统支持多种信号,每种信号都有一个唯一的编号和名称(如SIGINT、SIGTERM等)。
信号的主要特点包括:
- 异步性:信号可以在任何时候发送给进程
- 优先级:某些信号比其他信号有更高的优先级
- 处理方式:进程可以自定义信号处理函数或使用默认行为
信号捕捉的基本机制
Linux提供了多种方式来捕捉和处理信号:
1. signal()函数
c
#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);
signal()是最基础的信号处理函数,它允许进程为特定信号注册一个处理函数。当信号发生时,内核会中断进程的正常执行流程,转而执行注册的处理函数。
示例:
c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int signo) {
if (signo == SIGINT)
printf("Received SIGINT\n");
}
int main(void) {
if (signal(SIGINT, sig_handler) == SIG_ERR)
printf("\ncan't catch SIGINT\n");
while(1)
sleep(1);
return 0;
}
2. sigaction()函数
c
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
sigaction()是更强大和灵活的信号处理接口,它提供了比signal()更多的控制选项:
c
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
主要特性包括:
- 可以指定信号处理期间要阻塞的其他信号
- 可以获取关于信号的更多信息(通过
siginfo_t) - 更可靠的行为,特别是在信号处理函数执行期间
信号捕捉的关键特性
1. 信号处理函数的可重入性
信号处理函数必须是可重入的(reentrant),因为信号可能在进程执行的任何时刻到达。这意味着:
- 不能使用非可重入函数(如
malloc,printf等) - 需要小心处理全局变量和静态变量
- 最好只设置标志变量,在主循环中检查并处理
2. 信号屏蔽与阻塞
进程可以暂时阻塞某些信号的传递:
c
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(SIG_BLOCK, &set, NULL); // 阻塞SIGINT
sigprocmask(SIG_UNBLOCK, &set, NULL); // 解除阻塞
3. 信号的可靠性与不可靠信号
Linux将信号分为两类:
- 不可靠信号(1-31):传统的Unix信号,可能会丢失
- 可靠信号(34-64):Linux扩展的实时信号,不会丢失
4. 信号处理期间的信号屏蔽
当一个信号处理函数正在执行时,默认情况下,相同的信号会被自动阻塞,直到处理函数返回。这可以通过sa_mask和sa_flags进行调整。
高级信号处理技术
1. 使用sigwait同步处理信号
c
#include <signal.h>
int sigwait(const sigset_t *set, int *sig);
sigwait允许线程同步地等待信号,而不是异步地处理它们。这在多线程程序中特别有用。
2. 信号栈
对于需要大量栈空间的信号处理函数,可以使用独立的信号栈:
c
#include <signal.h>
int sigaltstack(const stack_t *ss, stack_t *oss);
3. 实时信号
Linux支持实时信号(SIGRTMIN到SIGRTMAX),它们具有以下特性:
- 可以排队,不会丢失
- 带有附加信息
- 有优先级顺序
信号捕捉的最佳实践
- 保持处理函数简单:最好只设置标志变量,在主循环中处理实际逻辑
- 使用volatile变量:用于信号处理函数和主程序之间共享的标志
- 避免使用不可重入函数 :如
printf,malloc等 - 考虑多线程环境:在多线程程序中,信号是发送给整个进程的,但可以由特定线程处理
- 正确处理信号中断的系统调用 :使用
EINTR错误码检查
示例:完整的信号处理程序
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
volatile sig_atomic_t flag = 0;
void handler(int sig) {
flag = 1;
}
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) {
if (flag) {
printf("Signal received!\n");
flag = 0;
}
// 模拟可能被中断的系统调用
int ret = sleep(10);
if (ret != 0 && errno == EINTR) {
printf("sleep interrupted by signal\n");
}
}
return 0;
}
总结
Linux的信号捕捉机制提供了强大的进程间通信和异常处理能力,但也带来了复杂性。理解信号的特性和正确处理信号是编写健壮Linux应用程序的关键。通过合理使用signal()、sigaction()等API,并遵循信号处理的最佳实践,可以构建出既响应迅速又稳定可靠的系统。