深入解析Linux信号处理机制

一.信号

信号是一种用户,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 要发送的信号编号(如 SIGTERMSIGKILLSIGINT
返回值 含义
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 之前闹钟剩余的秒数(如果之前有未触发的闹钟)
  1. 每个进程只有一个闹钟 :再次调用 alarm()重置之前的闹钟

  2. 到达指定时间后,进程收到 SIGALRM 信号(默认行为是终止进程)

  3. 如果 seconds = 0:取消当前闹钟,不设置新闹钟

6.pause

pause() 是一个系统调用 ,用于让进程挂起(睡眠),直到收到一个信号。

让进程停下来睡觉,直到被信号唤醒。

复制代码
#include <unistd.h>
复制代码
int pause(void);

返回值 含义
-1 总是返回 -1(因为被信号中断)

注意:pause() 永远不返回 0 ,它只会在收到信号后返回 -1,并设置 errnoEINTR

errno 含义
EINTR 被信号中断(正常情况)
  1. 调用 pause() 的进程会一直挂起(进入睡眠状态)

  2. 进程不占用 CPU 时间

  3. 直到收到一个信号 ,并且该信号的处理函数执行完毕 后,pause() 才返回

  4. 如果信号默认行为是终止进程,则 pause() 永远不会返回

7.sigaction

sigaction 是一个功能强大且灵活的 POSIX 系统调用,用于检查和更改进程接收到特定信号时的行为。与 signal 函数相比,它提供了更精细的控制、更好的跨平台移植性,是编写健壮信号处理程序的推荐方法

复制代码
#include \<signal.h\>
int sigaction(int signum, const struct sigaction \*act, struct sigaction \*oldact);
  • signum :指定要操作的信号,可以是除 SIGKILLSIGSTOP 之外的任何信号。

  • 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 setoldset 指针无效

每个进程都有一个信号屏蔽字 ,是一个信号集,表示当前被阻塞的信号

复制代码
sigset_t pending;  // 被阻塞的信号会进入"待处理"状态

重要规则

  • 被屏蔽的信号不会丢失 ,只是暂时不递送

  • 当解除屏蔽后,信号会被递送

  • SIGKILLSIGSTOP 不能被屏蔽

2.sigpending():pending表

sigpending() 是一个系统调用 ,用于获取进程当前被阻塞(挂起)的待处理信号

查看哪些信号被阻塞了但还没有被处理(即已经到达但因为被屏蔽而排队等待的信号)。

复制代码
#include \<signal.h\>
复制代码
int sigpending(sigset_t \*set);
参数 含义
set 指向 sigset_t 的指针,用于返回当前待处理的信号集
返回值 含义
0 成功
-1 失败,并设置 errno

注意:在 Linux 中,sigpending() 总是成功(返回 0),但为了可移植性,仍应检查返回值。

复制代码
信号生命周期:
           
发送信号 → 是否被屏蔽?
              ↓           ↓
             否          是
              ↓           ↓
          立即递送   进入"待处理"队列
          给进程处理   (pending状态)
                        ↓
                   解除屏蔽后
                        ↓
                     立即递送

关键点

  • 待处理信号:已经发送给进程,但因为被屏蔽,暂时无法递送

  • 每个信号在待处理队列中只有一个(重复发送不会排队多次)

  • 解除屏蔽后,待处理信号会立即被递送

相关推荐
阿Y加油吧1 小时前
二刷 LeetCode:动态规划经典双题复盘
算法·leetcode·动态规划
上弦月-编程2 小时前
C语言指针超详细教程——从入门到精通(面向初学者)
java·数据结构·算法
莫等闲-2 小时前
代码随想录一刷记录Day44——leetcode1143.最长公共子序列 53. 最大子序和
数据结构·c++·算法·leetcode·动态规划
生成论实验室2 小时前
《事件关系阴阳博弈动力学:识势应势之道》第七篇:社会与情感关系——连接、表达与共鸣
人工智能·算法·架构·交互·创业创新
承渊政道2 小时前
【动态规划算法】(背包问题经典模型与解题套路)
数据结构·c++·学习·算法·leetcode·动态规划·哈希算法
yyy(十一月限定版)2 小时前
数电1对应latex代码
算法
jieyucx2 小时前
Go语言切片:动态灵活的数据序列
算法·golang·指针·顺序表·数组·结构体·切片
我头发多我先学2 小时前
C++ 红黑树:从规则到实现,手把手带你写一棵红黑树
数据结构·c++·算法
nlpming3 小时前
opencode SQLite 数据库结构与查询手册
算法