【Linux】Linux 进程信号核心拆解:pending/block/handler 三张表 + signal/alarm 实战
本文详细介绍了Linux进程信号的产生与处理机制。信号是操作系统向进程发送的异步事件通知,共有1-31号普通信号和34-64号实时信号两种类型。信号的产生方式包括键盘输入(如ctrl+c)、系统调用(kill/raise/abort)、程序异常(除0/野指针)和软件条件(alarm等)。进程可通过signal函数自定义信号处理方式,或选择默认/忽略动作。重点讲解了前台/后台进程的信号处理差异,以及
前言:欢迎各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》
Linux进程信号详解
信号与信号量的区别
信号和信号量,就像“老婆”和“老婆饼”——没有任何关系。
信号的概念
什么是信号?
信号是一种给进程发送的异步事件通知机制。简单说,就是能“打断”进程正在做的事,让进程去处理这个通知。
比如生活中,你正在上课(进程运行),快递员敲门(信号产生),你需要暂停上课去处理快递(进程处理信号)。这里的关键是:快递员啥时候来不确定(信号异步产生),但你知道该怎么接快递(进程提前知道信号处理方式)。
基本结论
- 进程在信号产生前,就已经知道该怎么处理(提前“学会”了)。
- 信号不是立即处理,而是等进程到“合适的时机”再处理。
- 进程对信号的识别和处理方式,是操作系统设计时就确定好的。
- 信号的来源很多(键盘、系统调用、异常等)。
信号的产生方式
1. 键盘产生(仅前台进程)
键盘操作能给前台进程发信号,比如ctrl+c
。
- 用
kill -l
可查看所有信号,1-31是普通信号(进程择时处理),34-64是实时信号(立即处理)。 ctrl+c
实际发送的是2号信号(SIGINT),默认处理动作是终止进程。
信号的处理方式
进程处理信号有3种方式:
- 默认动作(比如大部分信号默认终止进程);
- 自定义动作(用代码修改处理逻辑);
- 忽略(收到信号不做任何处理)。
用signal函数自定义信号处理
signal
函数可以修改信号的默认处理动作,格式如下:
示例代码:
运行结果:ctrl+c
不再终止进程,而是打印提示,因为我们把2号信号的处理动作改成了自定义函数。
前台与后台进程
- 前台进程:直接运行(
./xxx
),能接收键盘信号(如ctrl+c
)。 - 后台进程:加
&
运行(./xxx &
),不接收键盘信号,需用kill
命令终止。 - 用
jobs
查看后台任务,fg 1
将后台任务1转为前台,ctrl+z
将前台进程转后台,bg 1
让后台进程继续运行。
2. 系统调用产生信号
发送信号的本质是修改进程内核数据结构(task_struct)中的信号位图(记录收到的信号)。操作系统提供了多个系统调用用于发送信号。
kill函数
给指定进程发信号,格式:
示例:给PID为1234的进程发9号信号(强制终止):kill(1234, 9)
。
raise函数
进程给自己发信号,示例代码:
运行结果:进程给自己发9号信号后终止(9号信号无法被自定义处理)。
abort函数
让进程自己终止,固定发送6号信号(SIGABORT),且会忽略自定义处理(强制用默认动作)。
运行结果:进程收到6号信号后终止。
3. 异常产生信号
程序运行出错时,操作系统会给进程发信号,导致进程崩溃。
除0错误
代码:
结果:触发8号信号(SIGFPE,浮点数错误),进程终止。
野指针错误
代码:
结果:触发11号信号(SIGSEGV,段错误),进程终止。
为什么操作系统会发信号?
操作系统是软硬件管理者,能检测CPU寄存器状态(如除0时的溢出标志)、内存访问错误(如野指针访问无效地址),一旦检测到,就给对应进程发信号。
4. 软件条件产生信号
某些软件场景会触发信号,比如管道读写错误、闹钟超时等。
alarm函数(闹钟)
给进程设置闹钟,时间到后操作系统发送14号信号(SIGALRM)。
- 格式:
unsigned int alarm(unsigned int seconds);
- 功能:
alarm(5)
表示5秒后发SIGALRM;alarm(0)
取消闹钟。 - 返回值:上一个闹钟剩余的秒数(无则返回0)。
示例1:自定义处理SIGALRM
代码:
结果:5秒后收到14号信号,执行自定义打印。
示例2:重复闹钟
代码:在信号处理函数中重新设置闹钟,实现每秒触发一次。
结果:每秒打印一次提示。
pause函数
让进程暂停,直到收到一个信号才继续运行。
代码:结合alarm和pause,实现定时任务。
结果:每1秒执行一次任务。
操作系统如何管理闹钟?
操作系统用“先描述再组织”的方式管理多个闹钟:
- 每个闹钟用结构体描述(包含目标进程、超时时间等)。
- 用类似“最小堆”的结构组织,堆顶是最早超时的闹钟,定期检查,超时则给进程发SIGALRM。
信号的保存
进程收到信号后不会立即处理,而是先保存起来。保存信号依赖三张“表”:pending(未决信号集)、block(阻塞信号集)、handler(处理函数集)。
三张表的作用
- pending(未决信号集):位图,记录进程收到了哪些信号(比特位1表示收到,0表示未收到)。
- block(阻塞信号集):位图,记录哪些信号被“屏蔽”(比特位1表示阻塞,此时即使pending有该信号,也不会处理)。
- handler(处理函数集):函数数组,记录每个信号的处理方式(默认、忽略、自定义函数)。
信号集操作函数
Linux提供函数操作这三张表,核心是sigset_t
(信号集类型,类似位图)。
常用函数
sigemptyset
:初始化信号集为空(所有比特位0)。sigfillset
:初始化信号集为满(所有比特位1)。sigaddset
:向信号集添加某个信号(置位)。sigdelset
:从信号集删除某个信号(清位)。sigismember
:判断信号是否在信号集中。
sigprocmask:修改block表
用于设置进程的阻塞信号集(block表)。
sigpending:获取pending表
获取当前未决的信号集(pending表)。
整合示例
代码:阻塞2号信号,发送信号后查看pending表,再解除阻塞观察处理结果。
结果:
- 阻塞期间,
ctrl+c
(2号信号)被记录在pending表(显示有未决信号)。 - 解除阻塞后,信号被处理(执行自定义函数),pending表中对应位清0。
信号的处理
信号处理的时机:进程从内核态返回用户态时,会检查pending表中未被阻塞的信号,按handler表的方式处理。
处理流程:
- 检查pending表,找到未被block的信号。
- 清空该信号在pending表中的位(1→0)。
- 执行handler表中对应的处理动作(默认、忽略、自定义函数)。
补充知识
信号的重复产生
普通信号(1-31)多次产生时,pending表中只会记录一次(位图位只能是1);实时信号(34-64)会按顺序处理多次。
Core与Term的区别
进程终止的两种默认动作:
- Term:直接终止进程,不保留现场。
- Core:终止进程并生成core文件(核心转储),保存进程崩溃时的内存状态,用于调试。
开启Core dump
默认情况下,很多系统关闭了Core dump(避免占用空间),可通过ulimit
临时开启:
ulimit -a
:查看当前限制(core file size为0表示关闭)。ulimit -c unlimited
:开启Core dump(不限制大小)。
使用core文件调试
进程崩溃生成core文件后,可用gdb
调试:gdb ./程序名 core
,直接定位到出错行。
进程终止总结
更多推荐
所有评论(0)