记录linux系统中信号的相关概念和用法
一、信号的基本概念
信号(Signal)是Linux系统中进程间异步通信的核心机制,用于通知进程发生特定事件(如用户输入、错误、进程状态变化等)。进程收到信号后会中断当前执行流程,转而去执行预设的处理逻辑。
1. 信号本质与特性
-
异步性:信号可在任意时刻触发,不受进程执行流程控制。
-
不可靠性:标准信号(1-31)不支持排队,多次发送同一信号可能被合并;实时信号(34-64,
SIGRTMIN~SIGRTMAX)支持排队和携带数据,更可靠。 -
不可屏蔽性:
SIGKILL(9)和SIGSTOP(19)无法被捕获、忽略或修改处理方式,用于强制终止/暂停进程。
2. 信号来源
-
硬件触发:如键盘输入(
Ctrl+C产生SIGINT)、段错误(SIGSEGV)、算术错误(SIGFPE)。 -
软件触发:如
kill命令、kill()函数发送信号,子进程终止(SIGCHLD),定时器到期(SIGALRM),向关闭的套接字写数据(SIGPIPE)等。
二、信号处理核心函数与结构体
Linux提供了一系列信号处理函数,其中sigaction是POSIX标准推荐的接口(功能最全面),配合其他函数可实现灵活的信号控制。
1. sigaction函数(推荐使用)
功能:设置或获取信号的处理方式(替代旧的signal()函数,支持更多控制选项)。
原型:
1 |
|
参数:
-
signum:目标信号编号(如SIGINT、SIGPIPE)。 -
act:非空时,指定新的信号处理配置(struct sigaction类型)。 -
oldact:非空时,保存之前的处理配置(用于恢复)。
返回值:成功返回0,失败返回-1(设置errno)。
2. struct sigaction结构体
用于描述信号的处理逻辑,定义如下:
1 | struct sigaction { |
成员详解:
-
sa_handler:简化处理函数,参数为信号编号。可设为SIG_IGN(忽略信号)、SIG_DFL(默认行为)或自定义函数。 -
sa_sigaction:扩展处理函数(需sa_flags设置SA_SIGINFO),可获取信号详细信息(siginfo_t)。 -
sa_mask:信号集(sigset_t),表示处理当前信号时需阻塞的其他信号(避免嵌套处理)。 -
sa_flags:标志位(可组合):SA_RESTART:被信号中断的系统调用(如read、accept)自动重启(避免EINTR错误)。SA_SIGINFO:使用sa_sigaction作为处理函数。SA_NOCLDSTOP:SIGCHLD仅在子进程终止时通知(忽略暂停事件)。
3. siginfo_t结构体(扩展信息载体)
当使用sa_sigaction时,用于传递信号详细信息:
1 | typedef struct { |
4. 信号集操作函数(sigset_t)
sigset_t是表示“信号集”的位掩码类型,需通过以下函数操作:
-
sigemptyset(sigset_t *set):初始化信号集为空。 -
sigfillset(sigset_t *set):初始化信号集包含所有信号。 -
sigaddset(sigset_t *set, int signum):添加信号到集合。 -
sigdelset(sigset_t *set, int signum):从集合移除信号。 -
sigismember(const sigset_t *set, int signum):判断信号是否在集合中(返回1/0/-1)。
5. 其他核心函数
| 函数原型 | 功能 | 关键参数 |
|---|---|---|
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset) |
修改进程的信号掩码(全局阻塞信号) | how:SIG_BLOCK(添加阻塞)、SIG_UNBLOCK(解除阻塞)、SIG_SETMASK(替换掩码) |
int sigpending(sigset_t *set) |
获取当前进程中“已产生但未处理”的信号(pending集) | - |
int kill(pid_t pid, int sig) |
向指定进程发送信号 | pid:目标进程ID(pid>0指定进程,pid=0同组进程) |
int raise(int sig) |
向当前进程发送信号(等价于kill(getpid(), sig)) |
- |
int sigwait(const sigset_t *set, int *sig) |
同步等待信号(将异步信号转为同步处理) | 需先通过sigprocmask阻塞目标信号 |
int sigqueue(pid_t pid, int sig, const union sigval value) |
向进程发送带数据的实时信号 | value:附加数据(sival_int或sival_ptr) |
6. 兼容性函数signal()
早期接口,功能简化但跨系统行为可能不一致:
1 | void (*signal(int signum, void (*handler)(int)))(int); |
局限性:不支持sa_mask和sa_flags,信号处理期间仅阻塞当前信号,部分系统会自动重置为默认行为。仅推荐用于简单演示,实际开发优先用sigaction。
三、常用信号详解(网络编程高频场景)
系统定义了64种信号,以下为网络编程中需重点关注的信号:
| 编号 | 信号名 | 触发场景 | 默认行为 | 网络编程中的意义 |
|---|---|---|---|---|
| 2 | SIGINT |
Ctrl+C终端中断 |
终止进程 | 需捕获以优雅关闭连接(如释放socket) |
| 3 | SIGQUIT |
Ctrl+\终端退出 |
终止+核心转储 | 同上,可用于强制退出并生成调试信息 |
| 6 | SIGABRT |
调用abort() |
终止+核心转储 | 检测到严重错误(如内存破坏)时触发 |
| 8 | SIGFPE |
算术错误(除零、溢出) | 终止+核心转储 | 协议解析中的数值计算错误 |
| 10 | SIGUSR1 |
用户自定义 | 终止 | 自定义事件(如重新加载配置) |
| 12 | SIGUSR2 |
用户自定义 | 终止 | 与SIGUSR1配合区分不同事件 |
| 13 | SIGPIPE |
向已关闭的套接字写数据 | 终止 | 必须处理(忽略或捕获),避免程序意外退出 |
| 14 | SIGALRM |
定时器到期 | 终止 | 实现网络超时(如connect超时控制) |
| 17 | SIGCHLD |
子进程终止/暂停 | 忽略 | 需捕获以回收子进程(避免僵尸进程) |
| 16 | SIGURG |
套接字收到紧急数据(OOB) | 忽略 | 处理TCP带外数据(如紧急指针) |
| 20 | SIGTSTP |
Ctrl+Z终端暂停 |
暂停进程 | 后台服务需忽略(避免意外暂停) |
关键信号深度解析:
-
SIGPIPE(13)
当TCP连接被对方关闭后,本地仍调用write()/send()时触发。默认终止进程,网络编程中必须处理:- 忽略信号:
signal(SIGPIPE, SIG_IGN);,此时写操作返回-1并设置errno=EPIPE,便于程序捕获错误。 - 捕获信号:在处理函数中记录连接异常日志。
- 忽略信号:
-
SIGCHLD(17)
子进程终止时,父进程需回收资源(否则子进程变为僵尸进程)。处理方式:1
2
3
4
5
6
7void handle_sigchld(int sig) {
pid_t pid;
// 非阻塞回收所有终止的子进程
while ((pid = waitpid(-1, nullptr, WNOHANG)) > 0) {
// 处理子进程回收逻辑
}
} -
SIGURG(16)
套接字收到带外数据时触发,需结合socket选项SO_OOBINLINE使用,在处理函数中调用recv()读取紧急数据。
四、实践注意事项
-
信号处理函数的可重入性
信号处理函数可能中断任意代码,需使用可重入函数(如write、_exit、sigprocmask),避免调用不可重入函数(printf、malloc、fopen等,可能导致资源竞争)。若需输出,可用write(STDOUT_FILENO, ...)替代printf。 -
多线程信号处理
信号默认随机投递到任意未阻塞该信号的线程,推荐做法:- 主线程通过
pthread_sigmask阻塞所有信号。 - 创建专门的信号处理线程,用
sigwait()同步处理信号(避免中断业务逻辑):1
2
3
4
5
6
7
8
9
10
11
12
13sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
pthread_sigmask(SIG_BLOCK, &set, nullptr); // 阻塞SIGINT
pthread_t tid;
pthread_create(&tid, nullptr, [](void* arg) {
int sig;
while (sigwait((sigset_t*)arg, &sig) == 0) {
// 处理信号(如SIGINT)
}
return nullptr;
}, &set);
- 主线程通过
-
系统调用中断与重启
网络编程中的accept、recv、select等系统调用可能被信号中断,返回-1并设置errno=EINTR。解决方式:- 设置
sa_flags = SA_RESTART,使系统调用自动重启(部分调用如select不支持,需手动重试)。 - 手动判断并重试:
1
2ssize_t n;
while ((n = recv(fd, buf, sizeof(buf), 0)) == -1 && errno == EINTR);
- 设置
-
实时信号vs标准信号
- 标准信号(1~31):适合处理无需排队的事件(如中断、终止),多次发送可能合并。
- 实时信号(34~64):支持排队和带数据,适合需要可靠传递的场景(如多任务状态通知)。