Linux信号集操作函数详解
- [1. 信号集概述](#1. 信号集概述)
- [2. 信号集的基本操作函数](#2. 信号集的基本操作函数)
-
- [2.1 初始化和清空信号集](#2.1 初始化和清空信号集)
- [2.2 进程信号掩码操作](#2.2 进程信号掩码操作)
- [2.3 检查挂起的信号](#2.3 检查挂起的信号)
- [2.4 等待信号](#2.4 等待信号)
- [3. 高级信号集操作](#3. 高级信号集操作)
-
- [3.1 信号集与字符串转换](#3.1 信号集与字符串转换)
- [3.2 多线程环境中的信号处理](#3.2 多线程环境中的信号处理)
- [4. 实际应用案例](#4. 实际应用案例)
-
- [4.1 安全地处理临界区](#4.1 安全地处理临界区)
- [4.2 等待特定信号](#4.2 等待特定信号)
- [5. 注意事项](#5. 注意事项)
- [6. 总结](#6. 总结)
1. 信号集概述
在Linux系统中,信号集(sigset_t)是用来表示一组信号的数据结构。它本质上是一个位掩码,每一位对应一个特定的信号。信号集操作函数允许我们方便地管理多个信号,常用于信号屏蔽、信号等待等场景。
信号集的主要用途包括:
- 设置进程的信号掩码(阻塞/非阻塞特定信号)
- 检查挂起的信号
- 等待特定信号的发生
2. 信号集的基本操作函数
2.1 初始化和清空信号集
c
#include <signal.h>
int sigemptyset(sigset_t *set); // 清空信号集,所有信号位设为0
int sigfillset(sigset_t *set); // 填充信号集,所有信号位设为1
int sigaddset(sigset_t *set, int signo); // 向信号集中添加信号
int sigdelset(sigset_t *set, int signo); // 从信号集中删除信号
int sigismember(const sigset_t *set, int signo); // 检查信号是否在信号集中
示例代码:
c
sigset_t mask;
sigemptyset(&mask); // 初始化空信号集
sigaddset(&mask, SIGINT); // 添加SIGINT信号
sigaddset(&mask, SIGQUIT); // 添加SIGQUIT信号
if (sigismember(&mask, SIGINT)) {
printf("SIGINT is in the signal set\n");
}
2.2 进程信号掩码操作
c
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how参数指定操作方式:
SIG_BLOCK:将set中的信号添加到当前阻塞信号集中SIG_UNBLOCK:从当前阻塞信号集中移除set中的信号SIG_SETMASK:将当前阻塞信号集设置为set
使用示例:
c
sigset_t new_mask, old_mask;
sigemptyset(&new_mask);
sigaddset(&new_mask, SIGINT);
// 阻塞SIGINT信号,并保存旧的信号掩码
if (sigprocmask(SIG_BLOCK, &new_mask, &old_mask) == -1) {
perror("sigprocmask");
exit(EXIT_FAILURE);
}
// 临界区代码,不会被SIGINT中断
// 恢复旧的信号掩码
if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) {
perror("sigprocmask");
exit(EXIT_FAILURE);
}
2.3 检查挂起的信号
c
int sigpending(sigset_t *set);
该函数获取当前被阻塞且处于挂起状态的信号集。
示例:
c
sigset_t pending;
if (sigpending(&pending) == -1) {
perror("sigpending");
exit(EXIT_FAILURE);
}
if (sigismember(&pending, SIGINT)) {
printf("SIGINT is pending\n");
}
2.4 等待信号
c
int sigsuspend(const sigset_t *mask);
该函数临时将进程的信号掩码设置为mask,然后挂起进程直到收到一个信号。信号处理函数执行完毕后,sigsuspend返回,并恢复原来的信号掩码。
典型用法:
c
sigset_t empty_mask;
sigemptyset(&empty_mask);
// 原子操作:解除信号阻塞并等待
sigsuspend(&empty_mask);
3. 高级信号集操作
3.1 信号集与字符串转换
c
#include <string.h>
int sigsetfromstr(sigset_t *set, const char *str);
int sigsettostr(const sigset_t *set, char *str, size_t maxlen);
这些函数(非标准)可以方便地在信号集和字符串表示之间转换。
3.2 多线程环境中的信号处理
在多线程程序中,应使用pthread_sigmask而不是sigprocmask:
c
#include <pthread.h>
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
用法与sigprocmask类似,但作用范围是调用线程而非整个进程。
4. 实际应用案例
4.1 安全地处理临界区
c
void critical_section(void)
{
sigset_t new_mask, old_mask;
// 阻塞SIGINT和SIGTERM
sigemptyset(&new_mask);
sigaddset(&new_mask, SIGINT);
sigaddset(&new_mask, SIGTERM);
if (pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask) != 0) {
perror("pthread_sigmask");
return;
}
// 执行临界区代码
// ...
// 恢复信号掩码
if (pthread_sigmask(SIG_SETMASK, &old_mask, NULL) != 0) {
perror("pthread_sigmask");
}
}
4.2 等待特定信号
c
void wait_for_signal(void)
{
sigset_t wait_mask;
int sig;
sigemptyset(&wait_mask);
sigaddset(&wait_mask, SIGUSR1);
sigaddset(&wait_mask, SIGUSR2);
printf("Waiting for SIGUSR1 or SIGUSR2...\n");
// 阻塞所有信号除了wait_mask中的信号
sigprocmask(SIG_BLOCK, &wait_mask, NULL);
// 等待信号
sigwait(&wait_mask, &sig);
printf("Received signal %d\n", sig);
}
5. 注意事项
-
信号编号范围 :不同系统可能有不同的信号编号范围,使用前应检查
<signal.h>中的定义。 -
实时信号:对于实时信号(SIGRTMIN到SIGRTMAX),信号集操作同样适用。
-
可重入性:信号处理函数中应避免使用非可重入函数,如printf、malloc等。
-
原子性 :
sigsuspend提供了原子性的"解除阻塞+等待"操作,比单独调用sigprocmask和pause更安全。 -
多线程:在多线程程序中,信号处理是进程范围的,但信号掩码是线程范围的。
6. 总结
Linux信号集操作函数为信号处理提供了灵活而强大的工具集。通过合理使用这些函数,可以实现:
- 精确控制哪些信号会被阻塞
- 安全地处理临界区代码
- 高效地等待特定信号
- 在多线程环境中正确管理信号
掌握这些函数对于编写健壮的、可响应信号的Linux应用程序至关重要。在实际开发中,应根据具体需求选择合适的函数组合,并注意信号处理的原子性和线程安全性问题。