☰
Linux信号(一)
2026/9/28 4:50:24 网站建设 项目流程

一.何谓信号?

信号的到来会让进程中断正在做的事情,告诉进程该做另一件事了;这是一种给进程发送的,用来进行事件的异步通知机制!

那什么又是异步通知呢?

异步:两件事情在同时发生,互不干扰。所以信号的产生相对于进程的运行,是异步的。产生信号和进程运行,是各干各的事情,互不干扰。就像蒋校长在水饺是一件事,同时闹钟响了,这个信号产生又是一件事,这俩事在闹钟响的一刹那,是异步的。

小结论:

1.信号处理:在红灯没有亮时,我就知道红灯亮,该停下。同样,对于进程来说,在信号没有产生的时候,它就知道信号该怎么处理了。

2. 信号的处理,不是立刻就处理,可以等一会再处理,在合适的时候处理就行了。就像闹钟响了,我还是很困,那可不可以眯会再处理闹钟呢?那当然可以。

3.人能知道信号出现以后,怎么处理(识别信号),是提前被人或事“教育”(拷打)过的;进程亦是如此,教进程识别信号的,是设计进程的OS程序员,进程早就被程序员内置了对于信号的识别与处理方式!

4.给进程产生信号的信号源,非常多!

二.信号的产生

1.键盘产生信号

就比如一直在用的,键盘上输入ctrl+c就可以结束掉一个进程,ctrl+c就是给目标进程发送信号。相当一部分进程,对信号的处理动作,就是让自己终止。

①.键盘产生的信号都有哪些?——ctrl+c给进程发的是哪一个信号呢?

用kill -l指令,就可以看到所有的信号:

这些信号都是一个个整型数字,为了可读性更好,会把这些数字定义成宏,这些宏就展示了对应信号的处理动作。

34-64是实时信号,我们不考虑它们。我们着重学习的是1-31号的普通信号,这些信号就像上面说的,可以等一会再处理,不需要马上处理。

那么回到标题的问题,ctrl+c发送的是哪一个信号呢?答案是2号信号。

②.如何证明发的是2号信号呢?

进程在收到信号后都会有三种处理动作:

1.默认处理动作,就像遇到红灯亮时,好市民会有素质的停下等红灯。

2.自定义信号处理动作(自定义捕捉),就像从疯人院里逃出来的人,遇到红灯亮时,会跳舞。

3.忽略处理。就像低素质人群遇到红灯亮时,赌自己不会撞大运,一脚油门冲过去。

而相当有部分的进程,在收到信号以后,默认的处理动作,就是让自己终止。

注意:为了防止所有信号因恶意的自定义捕捉,导致进程再也结束不了,9号信号永远无法被自定义捕捉!

要证明发的是2号信号,就要看到信号的处理过程,想要看到信号的处理过程,可以更改进程的默认信号处理动作,可以调用一个系统调用函数:

返回值是个typedef的函数指针。第一个形参signum是上面的信号(数字或者宏),第二个形参也是个函数指针,当使用该函数后,某个信号发给当前进程时,当前进程不再执行对应信号的默认动作,反而执行函数指针指向的方法(函数)。也就是说这个函数可以玩信号处理动作的自定义。

我们来使用一下这个函数,对2号信号的处理动作做修改:

这时ctrl+c一下,发出信号以后,进程做出的处理动作,就变成打印上面那句话了:

既然我改了2号信号的处理动作,然后ctrl + c信号发给进程后,进程做的处理动作是我更改过的2号信号的处理动作,这就必然能说明ctrl + c发给进程的是2号信号。

③.目标进程是??前台or后台进程

Ⅰ.概念理解——前台与后台进程

在命令行直接运行某进程:#./xxxx——该进程被叫做前台进程。键盘产生的信号,只能发给前台进程。

如果是:#./xxxx&——这里产生的,就是后台进程。后台进程对通过键盘的ctrl+c发出的信号,不做处理。而且ctrl+c是输入给前台bash进程的,bash进程本身是不会被ctrl+c结束掉的,它在用户退出登录时,才会终止。

那么如何终止后台进程呢?

用指令给目标进程发信号,直接输入kill -9 + 目标进程pid指令。9号信号很强势,可以让后台进程结束。

Ⅱ.前台进程vs后台进程

只有前台进程能从键盘获取标准输入,原因在于键盘只有一个,输入数据一定是给一个确定的进程。而前台进程有且只有一个,符合确定这一条件。

在没有启动任何程序时,我们系统里一直有个进程为我们提供服务,该进程就是命令行shell进程!该进程开始时是个前台进程。所以我们能够在命令行用键盘输入指令。

当然,前后台进程不是一成不变的,当./xxx启动程序,产生进程时,目标进程会成为前台进程,而bash进程转而成为后台进程,虽然目标进程是前台,但如果进程内没有调用scanf等,从键盘获取数据的接口,那么前台目标进程依旧对键盘发送的信号无感。

当./xxx &启动程序,产生进程时,进程是后台进程,bash进程还是前台进程。

Ⅲ.前后台移动的指令

ctrl+z:将进程从前台提到后台,它本质是将进程暂停,前台进程是不能被暂停的,它要一直等待用户输入,一旦被暂停了,就会卡死,键盘输啥都没用了,所以某个前台进程一旦被ctrl + z了,就会被提到后台,然后暂停。

bg + 任务号(jobs有显示):让暂停的后台进程重新运行起来。bg就是back ground的意思。

回到疑问,ctrl + c是给目标进程发信号的,既然支持从键盘获取标准输入,那么目标进程是一个前台进程。

④.什么叫给进程发信号?

信号产生以后,并不是立马处理,所以要求进程把信号记录下来,到合适的时候处理。那么就会引出两个问题:记录在哪里和如何记录。

记录在目标进程的PCB——task_struct里,在task_struct里有个unsigned int的位图——sigs,它拥有32位,比特位的位置,代表信号编号;比特位的内容,代表是否收到,1就是收到。

所以我们回到问题,给进程发信号的本质其实是向进程写信号,本质的本质,是修改位图!从上面两个问题的答案,我们可以知晓,想要修改位图,需要准确定位对应的信号,就需要进程的pid和信号编号。修改位图,只需要根据这两个数据,找到对应信号,把0置为1就行了。

但是task_struct属于OS内部的数据结构对象,是内核级的,修改位图,就是修改内核数据,这事只能让OS来做!所以无论以什么方法产生,发送信号,在底层必须让给OS来发送!因而,用户想要让OS发信号,就必须提供相应的系统调用。

2.系统调用产生信号

①.kill

#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);

作用:向进程/进程组发送信号

注意:名字叫kill,但不一定是杀死进程,只是发送信号。信号是什么,目标进程就做对应的动作。

第一个参数 pid(高频考点,必背)

1. pid > 0 :信号发给 PID等于pid的这一个进程

2. pid = 0 :信号发给当前进程所在进程组的所有进程

3. pid < -1 :信号发给进程组ID = -pid 的整个进程组

4. pid = -1 :发给当前进程有权限发送信号的几乎所有进程(除init进程)

特殊: sig = 0 ,不会发送任何信号,只做权限和PID是否存在的检测,用来判断进程是否存活。

第二个参数 sig

要发送的信号编号/宏:

- SIGINT 2号信号(Ctrl+C)

- SIGKILL 9号,强制杀死,不能被捕获、不能忽略

- SIGTERM 15号,默认kill命令发的信号,可以被捕获处理

②.raise

自己给自己发信号,形参是信号编号(宏)。

③.abort

给当前进程(自己给自己)固定发6号信号,引起进程的终止。哪怕6号信号做过信号捕捉,在abort这儿发出去的,也是原版退出进程的6号信号。

3.使用指令kill来发送信号

没什么说的:

kill + 信号编号 + PID

4.通过硬件异常产生信号

进程除0的错误操作以后,为什么进程会崩溃呢?答案是进程收到了8号信号,结束了!

8号信号显示的是浮点数异常:

进程进行野指针的操作后,收到11号信号,也结束了。

11号信号显示的是段错误:

野指针的虚拟地址为0(nullptr),0号地址在页表中并不存在映射关系,因此没法映射到物理内存中。而比对页表做映射的是cpu里的硬件单元——MMU。野指针(0号地址)想要做映射转化物理地址时,CPU内CR3寄存器会把其内部页表的起始地址交给MMU,然后把0号地址这个虚拟地址也同时交给MMU,由于不存在映射关系,MMU转化失败,然后硬件报错;作为软硬件的管理者,OS在收到报错以后,就会向进程发送11号信号,进程随即结束。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询