一.信号
信号是一种用户,OS,其他进程,向目标进程发送异步事件的一种方式。
在详细的学习信号时我们先提出几个问题,带着问题去学习:
1.你怎么能识别信号呢?识别信号,是内置的。进程认识信号是程序员内置的特性。
2.信号产生之后,怎么处理的?如果没有产生,信号怎么处理?信号的处理方法,在信号产生之前就已经准备好了。
3.处理信号是立即处理吗?如果我在做优先级很高的事情可能并不是优先处理的。
4.怎么处理信号a.默认行为b.忽略信号c.自定义
-
实际执行信号的处理动作称为信号递达(Delivery)
-
信号从产生到递达之间的状态,称为信号未决(Pending)
-
进程可以选择阻塞(Block)某个信号(阻塞特定信号,信号产生了,一定把信号要进行pending,永远不递达,除非我们解除阻塞)
-
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作
-
注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作
二.所需要学习的函数
1.sgnal
signal 是一个用于信号处理 的库函数,主要作用是为特定的信号 (如中断、终止、非法操作等)设置一个自定义的处理函数,从而改变程序收到该信号时的默认行为。
-
捕获信号:让程序在收到某个信号时,执行你写的代码(例如清理资源、记录日志、忽略信号)。
-
恢复默认:把某个信号的处理方式恢复为系统默认动作。
-
忽略信号 :让程序忽略某些信号(例如
SIGINT按 Ctrl+C 时不中断)。
#include <signal.h>
void (*signal(int sig, void (*handler)(int)))(int);
// 更易理解的版本:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
-
sig:要处理的信号编号,例如:-
SIGINT(Ctrl+C 中断信号) -
SIGTERM(终止信号) -
SIGSEGV(段错误,非法内存访问)
-
-
handler:三种取值之一:-
SIG_IGN→ 忽略该信号 -
SIG_DFL→ 恢复默认处理(通常是终止程序) -
自定义函数指针 → 执行你写的处理逻辑
-
2.kill
kill的作用是:向一个进程发送信号。它不一定是"杀死"进程,而是告诉进程"你该做什么"。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数详解
第一个参数:pid_t pid(目标进程)
| pid 值 | 含义 |
|---|---|
| > 0 | 发送给指定 PID 的单个进程 |
| == 0 | 发送给当前进程所在进程组的所有进程 |
| == -1 | 发送给调用进程有权发送信号的所有进程(不包括 init/systemd 进程) |
| < -1 | 发送给进程组 ID = |pid| 的所有进程(如 pid=-1234,发给进程组1234) |
第二个参数:int sig(信号编号)
| 值 | 含义 |
|---|---|
| 0 | 空信号,不真正发送,仅用于检查进程是否存在 |
| 1~31 | 标准 Unix 信号(如 9=SIGKILL, 15=SIGTERM) |
| 34~64 | 实时信号(Linux 扩展) |
| 返回值 | 含义 |
|---|---|
| 0 | 成功。至少有一个信号被成功发送 |
| -1 | 失败。此时需要查看 errno 获取具体错误原因 |
3.raise
raise() 是一个 库函数 ,用于进程向自己发送信号。
与 kill() 的区别
| 函数 | 发送方向 |
|---|---|
kill() |
向其他进程 或自己发送信号 |
raise() |
只能向自己发送信号 |
#include <signal.h>
int raise(int sig);
| 参数 | 含义 |
|---|---|
sig |
要发送的信号编号(如 SIGTERM、SIGKILL、SIGINT) |
| 返回值 | 含义 |
|---|---|
| 0 | 成功 |
| 非0 | 失败 |
4.abord
abort() 是一个库函数 ,用于异常终止当前进程,并生成 core dump(核心转储文件)用于调试。
abort() 让程序非正常崩溃,而不是正常退出。
#include <stdlib.h>
void abort(void);
注意:
abort()没有返回值,因为它永远不会正常返回。
他对应的是6号。
5.alarm
alarm() 是一个系统调用 ,用于设置一个定时器 ,在指定秒数后向自己发送 SIGALRM 信号。
设置一个闹钟,N 秒后给自己发一个 SIGALRM 信号。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
| 参数 | 含义 |
|---|---|
seconds |
定时秒数,0 表示取消之前的闹钟 |
| 返回值 | 含义 |
|---|---|
| 0 | 之前没有设置过闹钟 |
| >0 | 之前闹钟剩余的秒数(如果之前有未触发的闹钟) |
-
每个进程只有一个闹钟 :再次调用
alarm()会重置之前的闹钟 -
到达指定时间后,进程收到
SIGALRM信号(默认行为是终止进程) -
如果
seconds = 0:取消当前闹钟,不设置新闹钟
6.pause
pause() 是一个系统调用 ,用于让进程挂起(睡眠),直到收到一个信号。
让进程停下来睡觉,直到被信号唤醒。
#include <unistd.h>
int pause(void);
| 返回值 | 含义 |
|---|---|
| -1 | 总是返回 -1(因为被信号中断) |
注意:
pause()永远不返回 0 ,它只会在收到信号后返回 -1,并设置errno为EINTR。
| errno | 含义 |
|---|---|
EINTR |
被信号中断(正常情况) |
-
调用
pause()的进程会一直挂起(进入睡眠状态) -
进程不占用 CPU 时间
-
直到收到一个信号 ,并且该信号的处理函数执行完毕 后,
pause()才返回 -
如果信号默认行为是终止进程,则
pause()永远不会返回
7.sigaction
sigaction 是一个功能强大且灵活的 POSIX 系统调用,用于检查和更改进程接收到特定信号时的行为。与 signal 函数相比,它提供了更精细的控制、更好的跨平台移植性,是编写健壮信号处理程序的推荐方法
#include \<signal.h\>
int sigaction(int signum, const struct sigaction \*act, struct sigaction \*oldact);
-
signum:指定要操作的信号,可以是除SIGKILL和SIGSTOP之外的任何信号。 -
act:如果非空,则指向一个struct sigaction结构体,用于设置新的信号处理行为。 -
oldact:如果非空,则用于保存之前的信号处理行为,方便之后恢复。
返回值 :成功时返回 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); // 已废弃,仅供内部使用,无需关注
};
**void (*sa_handler)(int);**这个就是我们在signal中的hander函数。
三.sigset_t
sigset_t 是一个信号集类型,用于表示多个信号的集合。
头文件:
#include <signal.h>
五个核心函数
| 函数 | 功能 | 相当于位操作 |
|---|---|---|
sigemptyset() |
清空信号集(全部置0) | set = 0 |
sigfillset() |
填满信号集(全部置1) | set = ~0 |
sigaddset() |
添加一个信号(置1) | `set |
sigdelset() |
删除一个信号(置0) | set &= ~(1 << sig) |
sigismember() |
测试信号是否存在(读位) | (set >> sig) & 1 |
1.sigprocmask():修改block表
sigprocmask() 是一个系统调用 ,用于读取或修改进程的信号屏蔽字 ,决定哪些信号被阻塞(暂时不递送)。设置或获取进程的"信号黑名单",被屏蔽的信号暂时不会被进程处理。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
第一个参数:how(操作方式)
| how 值 | 含义 | 公式 |
|---|---|---|
SIG_BLOCK |
将 set 中的信号添加到当前屏蔽字 |
`mask = mask |
SIG_UNBLOCK |
将 set 中的信号从当前屏蔽字移除 |
mask = mask & ~set |
SIG_SETMASK |
将当前屏蔽字设置 为 set |
mask = set |
第二个参数:set(新屏蔽集)
-
指向要设置的信号集
-
可以为
NULL(此时how被忽略,用于仅查询)
第三个参数:oldset(旧屏蔽集)
-
用于保存修改前的信号屏蔽字
-
可以为
NULL(不保存旧值)
返回值
| 返回值 | 含义 |
|---|---|
| 0 | 成功 |
| -1 | 失败,并设置 errno |
错误码
| errno | 含义 |
|---|---|
EINVAL |
how 参数无效 |
EFAULT |
set 或 oldset 指针无效 |
每个进程都有一个信号屏蔽字 ,是一个信号集,表示当前被阻塞的信号:
sigset_t pending; // 被阻塞的信号会进入"待处理"状态
重要规则:
-
被屏蔽的信号不会丢失 ,只是暂时不递送
-
当解除屏蔽后,信号会被递送
-
SIGKILL和SIGSTOP不能被屏蔽
2.sigpending():pending表
sigpending() 是一个系统调用 ,用于获取进程当前被阻塞(挂起)的待处理信号。
查看哪些信号被阻塞了但还没有被处理(即已经到达但因为被屏蔽而排队等待的信号)。
#include \<signal.h\>
int sigpending(sigset_t \*set);
| 参数 | 含义 |
|---|---|
set |
指向 sigset_t 的指针,用于返回当前待处理的信号集 |
| 返回值 | 含义 |
|---|---|
| 0 | 成功 |
| -1 | 失败,并设置 errno |
注意:在 Linux 中,
sigpending()总是成功(返回 0),但为了可移植性,仍应检查返回值。
信号生命周期:
发送信号 → 是否被屏蔽?
↓ ↓
否 是
↓ ↓
立即递送 进入"待处理"队列
给进程处理 (pending状态)
↓
解除屏蔽后
↓
立即递送
关键点:
-
待处理信号:已经发送给进程,但因为被屏蔽,暂时无法递送
-
每个信号在待处理队列中只有一个(重复发送不会排队多次)
-
解除屏蔽后,待处理信号会立即被递送
