Linux信号入门
目录
一、生活中的信号:你的快递到了!
二、技术视角:Ctrl-C背后的魔法
三、深入信号世界
1. 信号身份证
2. 信号记录原理
3. 信号三处理法
一、生活中的信号:你的快递到了!
想象你在网上疯狂购物后,等待不同快递送货的场景。这个过程中蕴含着与计算机信号处理惊人相似的逻辑:
-
识别快递:尽管快递尚未抵达,但你清楚地知道,当快递到达时,你需要采取相应的行动,这便是你能够“识别快递”。(就像进程知道如何处理信号)
-
延迟处理:当快递员将快递送到楼下并通知你时,你正在忙于游戏,需要5分钟后才能去取快递。在这5分钟内,你虽未立即取快递,但已知晓有快递到来,这就是“延迟处理”。(信号可被暂存不立即处理)
-
记忆机制:从收到通知到拿到快递这段时间,你记住了有快递要取,这便是信号的“记忆机制”。(信号被操作系统记录)
-
处理方式:当你时间合适,顺利拿到快递后,便要开始处理快递,处理方式通常有三种:
-
✅ 默认操作:开心地打开快递使用商品(默认处理)
-
🎁 自定义操作:将零食快递送给女朋友(自定义处理)
-
🗑️ 忽略操作:将快递扔在床头继续游戏(忽略信号)
-
整个快递到来的过程对你而言是异步的,你无法准确预知快递员何时会打电话。
二、技术视角:Ctrl-C背后的魔法
在技术领域,信号是进程间事件异步通知的一种方式,属于软中断。观察下面这个典型的无限循环程序:
#include <stdio.h>
#include <unistd.h>
int main()
{
while(1){
printf("hello signal!\n");
sleep(1);
}
return 0;
}
当程序运行时,该程序会陷入死循环,不断打印"hello signal!"。要终止这种死循环,通常使用Ctrl+C。当用户按下Ctrl+C时,键盘输入会产生一个硬中断,操作系统获取并将其解释为信号(Ctrl+C对应2号信号),随后将2号信号发送给目标前台进程,前台进程收到2号信号后便会退出。
为了证明进程收到Ctrl+C时确实收到了2号信号,可以使用signal()函数进行信号捕捉实验。这个系统调用的原型为:
void (*signal(int sig, void (*handler)(int)))(int);
参数1:指定要捕捉的信号编号(如2号信号)
参数2:定义处理函数(参数为信号值,无返回值)
返回值:原信号处理函数指针
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void handler(int sig)
{
// 注意:此处仅演示信号捕捉,实际应避免在信号处理函数中使用printf等非异步安全函数
printf("捕获到%d号信号!\n", sig);
}
int main()
{
signal(SIGINT, handler);//注册2号信号处理
while(1)
{
printf("hello signal!\n");
sleep(1);
}
return 0;
}
运行改造后的程序时,每次按下Ctrl+C,不会再直接终止进程,而会立即执行handler函数,输出"捕获到2号信号!",然后程序继续执行循环。
🍇下面我们将生活例子和 Ctrl-C 信号处理过程相结合,来解释一下信号处理过程:
生活例子引入:
假设你是一个忙碌的进程,正在执行一项任务,比如写作业。操作系统是快递员,负责给你传递各种消息。信号就像是快递员送来的不同类型的快递。当快递员(操作系统)给你送来一个快递(信号)时,你需要决定如何处理它:是立即停下手中的活去处理快递,还是稍后再处理,或者直接拒收。
信号处理过程:
-
信号产生:当用户按下 Ctrl-C 键时,终端会生成一个中断信号(SIGINT),这个信号会被发送给当前正在运行的进程。
-
信号传递:操作系统(内核)捕捉到这个信号后,会暂停进程的当前执行,并将信号传递给该进程。
-
信号处理:
-
默认处理:如果进程没有对 SIGINT 信号进行特殊处理,操作系统会执行默认动作,通常是终止进程。
-
自定义处理:如果进程安装了信号处理函数(比如通过 signal 或 sigaction 函数),操作系统会将控制权转交给该处理函数,执行用户自定义的代码来处理信号。
-
-
恢复执行:信号处理完成后,操作系统会恢复进程的执行,继续执行被中断的代码。
与生活例子的结合:
-
快递(信号)的产生:就像有人给你下单寄了一个快递,快递员(操作系统)会收到这个任务。
-
快递(信号)的传递:快递员会把快递送到你的手中,这相当于操作系统将信号传递给进程。
-
处理快递(信号):
-
如果你没有特别说明如何处理快递(没有安装信号处理函数),快递员可能会直接把快递放在门口(默认处理),比如当你收到一个“停止”信号时,你停止当前活动。
-
如果你提前告诉快递员如何处理快递(安装了信号处理函数),比如“请把快递放在桌子上”,快递员会按照你的要求去做,这相当于进程执行自定义的信号处理函数。
-
-
恢复活动(恢复执行):处理完快递后,你可以继续做之前的事情,就像进程在处理完信号后继续执行后续的代码。
🌴注意事项:
-
信号发送对象 :按下 Ctrl-C 产生的信号仅会发送给前台进程。若在命令后添加 “&” 符号,可将命令对应的进程置于后台运行,此时 Shell 无需等待该后台进程完成,便可继续接收新的命令并启动其他进程。
-
Shell 进程管理 :Shell 能够同时管理一个前台进程以及数量不限的后台进程,但唯有前台进程能够接收像 Ctrl-C 这类由控制键产生的信号。
-
信号接收特性 :前台进程在执行期间,用户可在任意时刻按下 Ctrl-C 产生信号,这意味着该进程的用户空间代码在执行的任何位置都有可能收到 SIGINT 信号进而被终止,所以信号对于进程的控制流程而言是异步的。
信号概念:信号是进程之间事件异步通知的一种方式,属于软中断。
三、深入信号世界
1. 信号身份证
kill -l //查看所有信号
每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,其中1~31号信号是普通信号,34~64号信号是实时信号。
2. 信号记录原理
当一个进程接收到某种信号后,该信号会被记录在该进程的进程控制块中。进程控制块本质上是一个结构体变量,对于信号而言,主要记录某种信号是否产生,可以用一个32位的位图来记录信号是否产生,比特位的位置代表信号编号,内容代表是否收到对应信号。
// 简化的PCB结构
struct task_struct {
// ...
unsigned long signal; // 32位位图
// ...
};
示例位图:
3. 信号三处理法
信号处理有三种可选动作:忽略此信号;执行信号的默认处理动作;提供一个信号处理函数,要求内核在处理信号时切换到用户态执行这个处理函数,即捕捉信号。
在Linux中,可通过man手册查看各个信号默认的处理动作,命令为 man 7 signal
。
处理方式 | 生活比喻 | 代码示例 |
---|---|---|
默认 | 按惯例拆快递 | signal(SIGINT, SIG_DFL) |
忽略 | 假装没听到敲门 | signal(SIGINT, SIG_IGN) |
自定义 | 定制特殊收件流程 | signal(SIGINT, handler) |