跳到主要内容 Linux 进程信号详解:产生方式与闹钟机制 | 极客日志
C
Linux 进程信号详解:产生方式与闹钟机制 Linux 进程信号是操作系统向进程发送的异步通知机制。文章介绍了信号的分类,包括普通信号和实时信号,以及信号的本质是事件通知。详细阐述了信号的产生方式,如使用 kill 命令、键盘输入、系统调用(kill、raise、abort)及硬件异常。讲解了信号的处理机制,包括默认行为、忽略和自定义捕捉,并说明了 PCB 中保存信号位图的概念。此外,还涉及了作业控制,区分前台与后台进程对信号接收的影响,最后深入分析了 alarm 函数的用法、返回值特性及其在实现看门狗等场景中的应用。
MqEngine 发布于 2026/3/30 更新于 2026/4/23 0 浏览
1 ~> 理解信号是什么,为什么要有?生活中的信号
1.1 信号是什么?
1.1.1 普通信号和实时信号 常用的只有 1~31,34~64,没有 0 号——一共 62 个,不是 64 个哦!
前 31 个是普通信号,后面带 RT(real-time)实时信号。我们只考虑 1-31 就好了。
1.1.2 信号的本质
通知:这个通知需要我们理解一下——事件通知。
异步:通知的到来,跟我不同步。
举个例子,我点了个外卖,然后就打游戏,外卖小哥送货上门,敲门是一个通知,说明外卖到了,我和外卖小哥是不同步的。
异步关系就是没关系(你做你的,我做我的);同步关系就是有关系(我得等你做完再做)。
再比如,比如我是一个老师,我讲课,突然快递电话打过来了,我叫张三去帮我取快递,但是我等张三找完快递回来再继续讲,这就是个同步的关系,张三不回来,我就不往下讲。
如果我继续讲课,张三也在找快递,我讲课、他帮我办事,这个就是个异步的关系——我讲我的课,张三找他要帮我找的快递。
1.2 生活中有哪些信号?以及一些结论总结 人是经过教育的,早就知道信号对应处理动作的对应关系。
操作系统给目标进程发送信号,目标进程能不能识别呢?
答案是:进程天然能够识别——进程和信号都是程序员写的代码 (进程相当于我们人,已经被程序员教育过了,程序员设计好了)
进程能够识别信号,并且已经知道怎么处理信号了。
1.2.1 man 7 signal:查看信号部分的内容 man 7本身不是查看信号的指令,而是指定要查看的手册章节为第 7 章。在 Linux 系统中,信号相关的概述通常位于第 7 章,因此要查看信号的详细信息,可以使用命令:
会显示信号的概念、列表、默认行为等全局说明。此外,信号相关的系统调用(如 signal()、sigaction())一般在第 2 章,库函数(如 sigsetops())可能在第 3 章,而第 7 章主要提供更上层的概述和标准约定。
1.2.2 信号没有产生也知道怎么处理
1.2.3 收到一个信号会立即处理它吗? 当我们收到一个信号,准备处理这个信号,这里的我们即进程,会立即处理这个信号吗?有时候中断不了呢!处理不了信号。
对于信号的处理——不会立即处理——信号可能会立即处理,只有合适的时候会处理。
既然有一定概率不会立即处理,那么得要有把信号临时保存起来的能力——不会立即处理,就得有临时保存 的能力。
比如外卖小哥打电话,答应去取外卖之后如果没有保存信号,是不是就打游戏打着打着就忘了取外卖了。
1.2.4 信号怎么处理?
默认 :我后面打完游戏去拿外卖回来吃
忽略 :我直接就不想拿了 (但是并不是忘了,只是不想)
自定义 :比如我拿完外卖直接丢了或者干别的事,自定义行为
红灯——自动停了——默认信号。
收到信号,也处理了,但是处理的方式是忽略——忽略信号。
红灯亮了,别人要么忽略要么停下,而你在跳舞——自定义信号。
1.2.5 总结一些这些小结论
设别信号是内置的,进程能自己识别信号,是内核程序员写的内置特性。
信号的处理方法,在信号产生之前就已经准备好了。
处理信号不是立即处理的,因为我可能正在做优先级更高的事情,会选择合适的时候进行处理。比如我在打游戏,外卖员给我打电话叫拿外卖,那我肯定是先忙完手头的事再去拿。
信号不会被立即处理,所以就注定了进程要有临时保存信号的能力!
信号处理的动作有三种:默认、忽略、自定义 ,后续都叫信号捕捉 。
1.3 信号具体化
1.3.1 为什么是大写?这些大写的名字是什么?
1.3.2 头文件(含内核查看)
1~31是普通信号 ,我们现在是分时操作系统;34~64是实时信号 (有 RT,就是 real-time)。
1.3.3 保存信号
1.3.3.1 进程一定要有临时保存信号的能力 进程一定要有临时保存信号的能力 ——原因我们前面已经分析了。
信号就是个数字吗?还不够具体。
进程需要使用特定的数据类型来保存对应的信号!
1、我们要保存什么信号?哪一个信号?
2、我们要保存信号的侧重点?哪一个信号是否产生了?
1.3.3.2 我们可以用什么样的特定的数据类型来保存信号?位图
1、比特位的位置表示信号编号
2、比特位的内容要么是 1 要么是 0,1 或者 0——是否收到对应的信号!
这个位图应该在哪里?设计在 进程的 PCB 里面。
1.3.4 信号的位图在进程 PCB 里面,谁有资格修改内核数据结构中的字段?
信号的位图在进程 PCB 里面,谁有资格修改内核数据结构中的字段?
谁有资格——这个世界上,能够给进程写入信号的家伙只有一个——就是操作系统 OS。
操作系统不能决定全部的往进程写入信号(有能力,但是也只是一个办事的)——用户让你办的!
1.4 为什么要有信号?
我们一看到问为什么,一定要想到没有这个东西会出现怎么样的问题。
Linux 之所以需要信号,是为了提供一种 异步且轻量级的进程间通信与事件通知机制 ,使操作系统能够立即打断进程的正常执行流,以通知其发生了诸如用户中断(Ctrl+C)、硬件异常(段错误)、定时器到期或子进程退出等突发状况。信号机制弥补了管道或共享内存等同步通信方式的不足,让进程能够以一种统一、即时的方式响应外部与内部的高优先级事件,是实现进程控制、异常处理和系统紧急交互的核心基础。
1.5 信号是什么的思维导图
1.6 如何自定义捕捉信号? signal 函数其实是对指定信号未来进行自定义处理的一种延时设定。
我们挑一个信号——这里挑选 2 号信号:SIGINT
我们在手册(man 7 signal)里面查看 2 号信号——
要让 2 号信号不要终止进程,调用我自定义的方法——
Ctrl C(就是给前台发送 2 号信号)也终止不了了
信号最终是由进程自己去处理的,通过上面的代码可以验证——两个 pid 一样
本来就是设定了是 2 号信号,为什么还要把 2 号信号传进来呢?
Linux 内部,不同的的信号捕捉的处理方法可以是同一个!
所以我们得知道是哪一个信号——我们多个信号设置处理方法,可以是同一个处理方法。
可以通过传入的参数来区分是哪个信号 触发了自定义捕捉。
所有的 2 号信号都被忽略了!
还有一种是恢复默认:SIG_DFL
虽然 1~31 都能够设定自定义捕捉,但是有两个信号是不能够设定的:9 号信号和 19 / 20 号新号(看系统,具体是哪个)
9 号信号很重要:9 号信号不能被自定义捕捉 、不能被忽略 。
2 ~> 信号的产生 操作系统给目标进程写信号——不管怎么样,必须经过操作系统——因为只有操作系统能够有资格修改内核数据结构。
产生信号这个话题内容有点多。信号的产生是异步产生的,所以不会立即处理。
2.1 信号产生的方式
2.1.1 使用系统命令产生,kill 命令
2.1.2 通过键盘产生信号 我们查看手册,来一一对应一下,看看它对于当前进程的处理动作:
Ctrl C证明我们可以通过键盘产生信号(组合键的方式)。
为什么你如果启动一个进程之后,Ctrl C可以终止这个进程!
Ctrl C:发送了 2 号信号,2 号信号的处理动作是终止!
我们保留一个问题:键盘怎么能够向目标进程发送信号呢?
键盘是个硬件啊,进程是个软件啊,怎么发送信号?
2.1.3 产生信号的还有一种方式:系统调用 第一个参数:发送给哪个进程;第二个参数:发送哪个信号。
管道那里有一个对应的命令:mkfifo
Stat:获取函数的属性
printf:底层也是调用了同名系统调用
输出参数不匹配,就给你打印一个使用手册,类似于平常编译时候的报错
类型要转成整数
然后就可以使用 kill 系统调用,向目标进程写入信号——
我们再运行,还是前面的那种运行的图,这次我们看本次运行的结果:
kill 命令底层调用了 kill 系统调用——让操作系统向目标进程发送指定信号。
如下图,验证了9 号信号不能被捕捉
raise:自己给自己可以发任何信号
修改代码,让进程自己给自己发送信号
自己给自己发,1 秒打一次——
可以设置捕捉动作,但是最后还是会终止进程!——异常。
2.1.4 第四种产生信号的方式:异常 信号捕捉函数一直被触发(不是因为 while 循环)——死循环。
尽管名字包含'浮点',但它实际上涵盖了更广泛的算术错误,包括整数运算中的错误。
为什么会把进程异常当成信号?进程异常是如何被 OS 识别、并且解释成为信号的?!
2.1.5 第五种信号产生的方式:由软件条件产生信号
管道:R 端关闭,W 端打开,W 端一直写 --> OS 就会把写进程直接就杀掉了
操作系统认为管道不具备写条件——OS 就把写进程杀掉了。
关于闹钟(alarm)的相关话题我们在 2.4 细说。
2.2 三个系统调用层层递进
2.2.1 三个系统调用层层递进 前面的 kill、raise、abort 三个系统调用我们发现是层层递进的!
2.2.2 OS 先收到键盘输入,再由操作系统转换成信号发送给目标进程 只有操作系统可以在内核操作系统里面对 PCB 内部的位图进行修改。
换句话说,通过键盘产生信号也并不是键盘直接产生的信号,必然是 OS 先收到键盘的输入的,再由操作系统转换成信号发送给目标进程!
2.3 键盘怎么能够目标进程发送信号呢?
1、如何理解键盘输入?键盘是基于硬件中断来进行工作的!
2、OS 如何解释快捷键?(组合键)ctrl c、ctrl \,……可能会直接解释成为信号!
3、OS 怎么知道信号应该发送给哪一个进程呢?操作系统怎么知道?要补充一点网络时要用的知识!
2.3.1 如何理解键盘输入? 我的操作系统怎么知道用户把键盘按下了、按下了哪个(键)字符?——驱动去处理的。
计算机的外设很多,操作系统要做各种工作,你能够轮询吗?这种做法在技术上可以实现,但是这会让操作系统变得很慢!
中断:是计算机组成原理的最重要的重点,没有之一!没有学懂中断,就是没有学懂操作系统。
操作系统有些东西没有学懂,问题可能不在操作系统上面,可能是在理解计算机组成原理上。
操作系统并不知道键盘上有数据了,而是中断告诉 CPU 了。
2.3.2 OS 如何解释快捷键? 键盘的字符既有'ABCD……'这样的正常的字符,也有'Ctrl C''Ctrl V'……这样的组合键,操作系统会把组合键解释成信号(会判断,ctrl c 是特殊字符,在 ASCII 码表里存在)——操作系统会有系统调用:给目标进程发送信号 ——kill(pid,signum) 。
2.3.3 OS 怎么知道信号应该发送给哪一个进程呢? 三个进程的父进程都是同一个:bash;即这三个进程是兄弟进程。
2.3.3.1 进程组
2.3.3.2 会话 ID:SID 这三个兄弟进程的 SID 一样,属于同一个会话,以 bash 进程命名:
一般情况下,同一个组里面大家会使用同一个终端文件(因为我们是远程登录的 Xshell,这里的终端文件是虚拟网络文件)。
我们发现这个进程和那三个进程不在一个进程组里面,但是在一个会话组里。
本质是一个会话里面创建一个新的进程组(哪怕进程组只有我一个进程)。
我们把 for 循环也注释掉,方便 ctrl c 等操作
2.3.3.3 前台进程和后台进程 + 前后台进程切换
2.3.3.3.1 前台进程 定义:占用当前终端,必须等该进程执行完毕才能输入其他命令。
示例:直接执行 sleep 100,终端会被占用 100 秒,无法输入新命令。
2.3.3.3.2 后台进程 定义:在后台运行,不占用终端,用户可继续执行其他命令。
2.3.3.3.3 作业控制命令
1 jobs
2 fg(foreground)
3 bg(background)
先用 Ctrl+Z 暂停前台进程,此时作业变为'已停止'状态。
执行 bg %1 让它在后台继续运行 。
4 Ctrl+Z
5 Ctrl+C 进程可以在前台或后台运行,并通过 fg 和 bg 命令进行切换
2.3.3.4 一些结论
结论 1:Ctrl C 只能用于终止前台进程!
作业 / 独立的进程组有什么关系? 在命令行./进程(启动一个进程)属于上面所说的特殊情况。
task(作业 / 任务)——进程组和作业就像是硬币,一体两面。
会话与进程组(作业)的作业之间的关系? 会话: 是由一个或者多个进程组构成的 进程组的集合 ,通常会有属于自己的终端文件!
通过 \\ fg [任务号]\*\*把后台任务变成前台之后,就可以 Ctrl C 删掉了。
在一个会话中,任何时刻,只允许一个进程(组)在前台! 只要一个进程在前台,我的命令就没法被其它进程执行了。
因为在一个会话中,任何时刻,只允许一个进程(组)在前台,能够接受、执行命令的 bash 在哪里?自动切换成为后台进程组了(为什么 bash 变成后台了,命令就无法进行了)!获取不到命令,就无法执行命令了!
键盘只有一个,在会话里有这么多进程,谁拥有键盘,谁就是前台进程。
2、什么叫做前后台?
能够直接获取用户输入的基础,叫做前台进程;
否则叫做后台进程
因为键盘只有一个,任何时刻只允许一个人输入(只有一个人,一个输入),谁拥有终端文件(主要是键盘),谁就是前台!获取不到键盘,所以就无法输入了。
后台进程可以写数据(向显示器打印),但是后台进程无法从键盘读取数据(读取的时候会被系统直接暂停掉)。
这就叫做 【会话管理】 !
前台进程只有一个,所以操作系统知道未来要把信号发给哪个进程!
Ctrl C 严格意义上不是杀掉一个进程,而是杀掉一个进程组。
登录两次、三次、……系统是不是每次都要创建这些东西?
剩下的,在网络讲【守护进程】的时候再介绍。
后台不允许获取 把前台进程被暂停了,前台进程就会自动切换到后台进程。
2.4 alarm:设置一个若干秒之后的闹钟
2.4.1 闹钟的返回值问题 我想看到的现象是收到闹钟前,进程会疯狂打印 cnt 计数器:
为了对比为什么 CPU 计算速度这么快还是只打印 1 万多,证明是收到了闹钟信号:
一万多次肯定对于当前的 CPU 还是太少了,我们纯在内存当中做++:
输出的本质是 IO,IO 比较慢,这个 IO 是要经过网络的,要访问外设。
4 亿多次是因为没有访问外设,直接访问了内存,内存级别的操作。
这样我们对 CPU 访问外设速度非常慢有了一个量化的认识。
如果我们设置一个 20 秒的闹钟,在第五秒的时候就把闹钟提前唤醒了,这种闹钟给一个进程只能设置一个,已经设置了,过了一段时间后悔了,要重设闹钟,下一次返回值就是返回上一个闹钟的剩余时间。
如下图,我们先设置 alarm(10);,再设置成 alarm(0);之后,因为第一个闹钟是 10 秒,使用返回了 10 - 0 = 10 秒。
这就说明,alarm 是一个一次性闹钟,而且这个闹钟只对这个进程只会有同一个闹钟(从头到尾调用 alarm 就一个闹钟)。
当我调用下一个的时候闹钟就收不到闹钟了——上一个闹钟根本没有响!
2.4.2 闹钟的应用场景 其实严格意义上这个工作不是闹钟完成的,而是定时器完成的。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#define MAX_COUNT 100
#define SHM_KEY 0x1234
int *counter;
void watchdog_handler (int signum) {
(*counter)--;
printf ("watchdog: counter = %d\n" , *counter);
if (*counter <= 0 ){
printf ("watchdog: no feed, shutting down...\n" );
exit (0 );
}
alarm(1 );
}
int main () {
int shmid = shmget(SHM_KEY, sizeof (int ), IPC_CREAT | 0666 );
if (shmid < 0 ){
perror("shmget" );
exit (1 );
}
counter = (int *)shmat(shmid, NULL , 0 );
*counter = MAX_COUNT;
signal(SIGALRM, watchdog_handler);
alarm(1 );
while (1 ){
pause();
}
return 0 ;
}
A 进程只需要通过共享内存访问计数器,并定期重置:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/shm.h>
#define MAX_COUNT 100
#define SHM_KEY 0x1234
int *counter;
int main () {
int shmid = shmget(SHM_KEY, sizeof (int ), 0666 );
if (shmid < 0 ){
perror("shmget" );
exit (1 );
}
counter = (int *)shmat(shmid, NULL , 0 );
while (1 ){
printf ("working...\n" );
sleep(80 );
*counter = MAX_COUNT;
printf ("feed watchdog, counter reset to %d\n" , *counter);
}
return 0 ;
}
2.4.3 理解闹钟 理解闹钟,看看操作系统内同时存在多种闹钟(定时器)?操作系统要不要管理?怎么管理?先描述,再组织。
如果未来 5 秒的闹钟没有响,那么后面的 15 秒的 50 秒的、150 秒的一定没响。
未来自己使用定时器应该会使用堆结构,这就是为了方便理解。
闹钟,是单独硬件来计时吗?如果是 CPU 来计时的话,它不得很忙,一边处理进程,还一边计时。
问题 2:闹钟计时取决于操作系统是如何运行的,操作系统也是个软件,谁运行操作系统?原因是,键盘可以产生中断,操作系统内部也存在一种硬件单元,晶振,也会触发中断,中断向量表,操作系统是基于中断的一种集合。
一般触发时钟中断的硬件周期是固定的,比如每隔一纳秒,因此在 OS 内部就可以定义一个全局变量记录中断次数,如果是 100,等于说从开机到现在已经过了 100 纳秒了,次数 = 时间。时钟中断,token 中断,操作系统能够算出来从开机到现在过了多少秒,5 秒会变成 5^9 次!如果次数超过了就是超时。计算机里把时间转化成次数,计时不是由 CPU 计时的,由外部晶振的振动周期决定的。中断越强,CPU 响应能力越强。
除 0 死循环,标志位没清,再次上下文时,要先检查 core dumped 标志位,才运行吗
CPU 已经识别出来了,也会走中断,走中断也会走中断向量表,也属于异常。
自定义捕捉了就没有 core dump 了,没有走自定义捕捉就没有走系统那一套,就没有 core dump 了。
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online