Linux 进程信号进阶:信号集操作、捕捉机制与核心概念全解析—-《Hello Linux!》(16)
文章目录
- 前言
- 信号集操作函数
- 信号的捕捉
-
- sigaction
- 可重入函数
- volatile
- SIGCHLD信号(了解即可)
- 作业部分
前言
在上一篇文章中,我们已经掌握了 Linux 进程信号的基础认知 —— 包括信号的本质、三种处理方式、五大产生途径,以及内核中block(阻塞)、pending(未决)、handler(处理函数)的核心保存机制。而当我们进入实际开发场景,仅了解基础原理远远不够:如何批量管理多个信号?如何可靠地捕捉信号并自定义处理逻辑?信号处理时用户态与内核态如何切换?哪些函数在并发场景下会引发问题?volatile关键字为何能避免编译器优化带来的坑?
本文将聚焦进程信号的 “进阶实操与底层细节”,带你从 “知其然” 走向 “知其所以然”。首先详解sigemptyset、sigprocmask、sigpending等信号集操作函数,掌握信号阻塞与未决状态的手动管理技巧;接着深入信号捕捉的底层流程 —— 剖析用户态与内核态的切换逻辑,对比signal与sigaction的使用场景,解锁信号处理函数的灵活配置;随后拆解可重入函数的判定标准、volatile关键字的内存可见性原理,以及SIGCHLD信号的特殊处理规则;最后通过经典面试 / 笔试真题巩固知识点,帮你避开实际开发中的高频踩坑点。
无论你是需要实现复杂信号管理的开发者,还是备战技术面试的求职者,本文都能为你搭建 “实操函数 – 底层机制 – 核心概念 – 真题巩固” 的完整知识体系,让你彻底吃透进程信号的进阶核心,从容应对信号处理相关的开发需求与面试挑战。
信号集操作函数
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
信号集:是一种用于管理多个信号的集合型数据结构,是用位图实现的
sigemptyset:初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
sigfillset:初始化set所指向的信号集,使其中所有信号的对应bit置1,表示 该信号集的有效信号包括系统支持的所有信号。
sigaddset:让set所指向的信号集里面的signo对应的信号置为1
sigdelset:让set所指向的信号集里面的signo对应的信号置为0上面这四种函数的返回值:成功返回
0,出错返回-1
sigismember:用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1使用eg: sigset_t bset; sigemptyset(&bset);//这才初始化好了一个信号集
sigprocmask:作用:用来改当前进程的
block表
参数:
how:只有三种填法:(这里面三选一哈)
SIG_BLOCK:将set中的信号添加到当前屏蔽字(即屏蔽这些信号)
SIG_UNBLOCK:将set中的信号从当前屏蔽字中移除(即不再屏蔽这些信号)
SIG_SETMASK:直接将进程的信号屏蔽字设置为set所指向的信号集合。
oldset:是输出型参数,存的是修改前的信号屏蔽字(不需要的话就搞成NULL)返回值:若成功则为0,若出错则为-1
注意:9号和19号信号是被屏蔽不了的
sigpending:作用:获取当前进程的未决信号集
set:输出型参数,把当前进程对的未决信号集存在set里面返回值:成功返回
0,失败返回-1
信号的捕捉
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号
先引入信号处理:
当进程从内核态返回到用户态时,会进行信号的检测和处理
内核态:此时可以访问操作系统的代码和数据(访问用户的代码和数据的话会受控)
用户态:此时只能访问用户自己的代码和数据
这个的原因要研究进程地址空间了,进程地址空间一共4G,下面低地址的那3G是用户空间的,上面那1G是内核空间的–内核空间也有内核级的页表使用,然后指向比如:操作系统的代码和数据
引申:内核级页表只有一份 但是有几个进程就有几个用户级页表
每一个进程看到的内核空间的东西是一样的操作系统是会自动进行身份切换的,比如调用系统调用的时候–用户身份会从内核态变成用户态或者从用户态变成内核态
比如:用户态变成内核态:发起系统调用时,会通过
int 80指令让CPU的cu寄存器从低特权级指向高特权级,然后就变成内核态了使用系统调用时,就是正文代码跳转到内核空间里面去进行操作了
如果把信号的处理搞成了自定义动作,这个信号的处理流程:
可以简记为下面这个图:
引申:不止系统调用才能把用户态切换成内核态–比如:中断也是可以的
引申:操作系统的本质:基于时钟中断的死循环
理解:在计算机硬件中,有一个时钟芯片,每隔极短的时间,就会向CPU发送时钟中断,CPU就会进入内核态处理时钟中断,然后去进程队列里面重新调度进程
时间片的实现也是依赖于时间中断
sigaction
作用:可以读取和修改与指定信号相关联的处理动作

这里面的
struct sigaction是:
void (*sa_handler)(int):这个就是自定义处理信息动作 –跟signal那里的handler的定义方法差不多
sa_mask是屏蔽信号集参数:
signum是信号是几号
act里面存的是新的信号处理配置
oact是输出型参数,里面存的是旧的结构体(不需要的话就填NULL)返回值:成功返回
0,失败返回-1
使用展示:
void handler(int signo)
{ ...... }
struct sigaction act, oact;
memset(&act, 0, sizeof(act));
memset(&oact, 0, sizeof(oact));//把结构体所有字节置为0
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, 1);//这样的话,当handler在处理时,会把1号信号也屏蔽掉
act.sa_handler = handler;
sigaction(2, &act, &oact);
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字 –这样可以防止信号被嵌套调用
如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字
可重入函数
如果一个函数被重复进入(多个执行流执行)的情况下,会出错或者可能会出错 这叫做不可重入函数
不然就叫做可重入函数
目前接触到的大多数函数都是不可重入函数
比如:链表的头插
volatile
这是个关键字
作用:防止编译器过度优化,保持内存的可见性
比如:编译器优化级别被开启或者变量没在外部函数修改时,就会触发寄存器缓存优化
这个优化会把变量的值存储在寄存器里面,下次用到这个值的时候就不去内存中读了
加上
volatile的话,每次需要这个变量,都会去内存里面读取用法: eg: volatile int flag = 0;
SIGCHLD信号(了解即可)
子进程在退出时,会给父进程传递一个信号
SIGCHLD
SIGCHLD信号是17号信号,它的默认处理动作是忽略的这个忽略跟
SIG_IGN不一样哈这个忽略是
SIG_DFL,然后它里面的默认处理动作是忽略如果是
SIG_IGN的话,是信号到达跟不到达一个样–改成这个的话,子进程就不会有僵尸进程了
SIG_DFL的话是需要进程接收,接收后才处理的
作业部分
以下描述正确的有:[多选](BD)
A.若信号被阻塞,则信号将无法添加到未决信号集合中
B.若信号被阻塞,则信号依然可以添加到未决信号集合中
C.若信号被忽略,则信号将无法添加到未决信号集合中
D.若信号被忽略,则信号依然可以添加到未决信号集合中
//信号忽略,只是说信号的处理方式就是忽略处理,并不表示不再接收指定信号
一个进程无法被kill杀死的可能有哪些?[多选](ABCD)
A.这个进程阻塞了信号
B.用户有可能自定义了信号的处理方式
C.这个进程有可能是僵尸进程
D.这个进程当前状态是停止状态//就不再处理信号了,但会加入pending
以下描述正确的有:(B)
A.未决信号指的是已经被处理的信号
B.未决信号指的是还未被处理的信号
C.同一个信号可以在未决信号集合中添加多次
//可靠信号可以,非可靠不行;可靠信号的pending存储方式跟非可靠不一样
D.每一个信号处理完毕后都会从pending集合中移除//可靠信号是先检查还有没有这个信号,再移除
引申:非可靠信号:1-31号 可靠信号:34-64号
下列选项中,会导致用户进程从用户态切换到内核态的操作是(B)
I. 整数除以零
II. sin()函数调用 //库函数不会导致运行态的切换
III. read 系统调用
A.仅 I、 II
B.仅 I、 III
C.仅 II、 III
D.I、 II 和 III
以下描述正确的有:(C)//不会被打断,就是可重入的
A.在一个函数中若对局部变量进行了操作,则这个函数一定是不可重入的
B.在一个函数中若对全局变量进行了操作,则这个函数一定是可重入的
C.在一个函数中若对全局变量进行了原子操作,则这个函数一定是可重入的
D.在一个函数中若对局部变量进行了原子操作,则这个函数一定是不可重入的
引申:原子操作指的是一次完成,中间不会被打断的操作
以下描述正确的有:(D)
A.只能使用signal函数自定义信号捕捉函数//sigaction也行
B.若当前进程处于阻塞状态,则此时到来的信号暂时无法处理//信号会打断进程当前的阻塞状态去处理信号
C.使用signal函数进行信号捕捉函数修改时,可以指定SIG_DFL设置为忽略处理//SIG_DFL表示默认处理方式
D.使用signal函数进行信号捕捉函数修改时,可以指定SIG_IGN设置为忽略处理
//SIG_IGN,表示将该信号设置为 忽略处理




