Linux 信号

目录

一.前置知识

1.前台进程和后台进程

a.概念理解

b.相关指令

2.信号的前置知识

[a.Linux 系统下信号的概念](#a.Linux 系统下信号的概念)

b.进程对信号的处理方式

c.信号捕捉

3.信号的底层机制

二.详解信号

1.信号的产生

a.键盘组合键

[b.kill 指令和系统调用接口](#b.kill 指令和系统调用接口)

[① kill 指令](#① kill 指令)

[② kill() 系统调用接口](#② kill() 系统调用接口)

[③ raise() 系统调用接口](#③ raise() 系统调用接口)

c.硬件级异常问题产生信号

①除零报错

②野指针报错

d.由软件条件产生信号

①匿名管道

②闹钟

2.信号的发送

a.OS给进程发信号的实质

[① Pending表(未决信号集)](#① Pending表(未决信号集))

[② Block表(信号屏蔽集)](#② Block表(信号屏蔽集))

[③ handler表(信号处理函数表)](#③ handler表(信号处理函数表))

④小结

b.信号集操作函数

①有关sigset_t类型位图操作的函数接口

②sigprocmask

③sigpending

c.屏蔽进程的指定信号(代码实战)

3.信号的处理

a.用户态和内核态

b.用户态和内核态间的切换方式

①系统调用

②抛异常

③中断

c.重谈地址空间


一.前置知识

1.前台进程和后台进程

a.概念理解

前台进程: Linux系统中,前台只能运行一个进程 ,并且在进程运行期间,命令行失效,此时,如果我们想控制该进程,就只能通过信号!!

后台进程: 可以同时运行多个进程,进程在运行期间,命令行仍起作用,但我们无法以键盘组合键的方式向后台进程发送控制指令。

由于后台进程可能同时运行多个,所以,每个后台运行的进程都会有自己的编号 [x]

b.相关指令

当我们向运行某个可执行程序时,如:./test 即让 test 可执行程序在前台运行 。如果我们在其后加上 & 如: ./test & 则表示使 test 可执行程序在后台运行。

jobs ------ 查看系统后台进程情况

fg + [x]------ 可以将编号为x的进程切换到前台

Ctrl + z ------ 暂停前台进程,同时将该进程转到后台去

bg+ [x]------ 将后台被暂停的编号为x的进程启动

2.信号的前置知识

a.Linux 系统下信号的概念

什么是信号?简单来说,操作系统向目标进程发送的控制指令就是信号。

进程和信号的关系

①进程必须要有能力**"识别"并"处理"**信号,即使进程还未收到这一信号。

②进程收到某一信号时,可能不会立即对这一信号做出处理动作,也就是说,进程在收到信号和处理信号间有一个"空窗期",这就要求进程具备对信号的临时存储能力。

kill -l 查看Linux系统中的各个信号,如下图:

**man 7 signal ------**用于查看信号相关信息的命令。该命令会展示关于信号机制的详细解释,包括信号的含义、产生原因、默认行为、处理方式。

b.进程对信号的处理方式

进程对信号的处理方式一共有三种:①默认动作; ②忽略; ③*自定义动作(信号捕捉)。

什么是默认处理动作?--- 就是进程处理某一信号时, 该信号的功能是系统默认赋予的。例如:9号信号的功能是终止某一进程,当进程收到9号信号时,执行的功能就是将自己终结。

忽略行为该如何理解?--- 进程收到信号,但却不执行信号的功能,就是忽略。

*自定义动作指的是什么?--- 用户捕捉某个或某些信号,并对它们的功能进行修改,当进程收到这些信号时,会执行用户重新为信号赋予的功能,这就是自定义动作。

c.信号捕捉

信号捕捉的相关系统调用接口如下:

sighandler_t signal ( int signum , sighandler_t handler );

信号的忽略 ------ signal( signum , SIG_IGN );

信号的默认处理 ------ signal( signum, SIG_DFL);

信号的自定义捕捉 ------ signal( signum, handler);

signum 是我们要自定义的信号**,handler** 是 void ( * )( int ) 类型的回调函数,该回调函数的功能需要我们自主实现。当进程的代码执行到该系统调用接口后,此时,如果我们在向该进程发送 signum 信号的话,进程执行的就是 handler 函数中的代码~~

示例:

3.信号的底层机制

情景:当前台进程正在运行时,我们输入的 Ctrl + C 是如何终止进程的?或者说,我们输入 Ctrl + C后,OS底层都做了哪些事?

首先,OS如何知道外设的数据输入??------ 中断技术

当硬件设备有数据输入时,产生的光电信号经过8259表的特殊处理,会激活CPU上与该外设相连接的针脚 ,在CPU寄存器 上生成中断号 ,随后,OS通过该中断号遍历中断向量表 ,找到表上记录的相应硬件的处理方法,即OS会从键盘缓冲区读 取数据,并对读取的数据类型做判断,若其为组合的控制键 (如:Ctrl + c、Ctrl + v),则OS向前台进程发送对应的信号 ,若为普通的输入数据,则将其拷贝到指定的内存缓冲区上。

至于,当进程收到信号后,对信号的识别、存储和执行,咱们放在后文中细讲~~

但有一点需要先提一下:信号的本质就是用软件来模拟中断的行为!!

二.详解信号

1.信号的产生

a.键盘组合键

如:

Ctrl + c 向前台进程发送2号信号 ------ 其功能默认是终止进程

Ctrl + \ 向前台进程发送3号信号 ------ 其功能默认是终止进程

注意:9号(暂停进程)和19号(杀掉进程)不能被signal自定义捕捉!!

b.kill 指令和系统调用接口

① kill 指令

命令行上,我们可以使用 kill -x + pid,通过目标进程的pid,向目标进程发送 x 号信号。

② kill() 系统调用接口

代码中 ,我们可以使用 **kill(pid_t pid , int signal)**函数,通过进程的 pid,向目标进程发送 signal 号信号。若发送成功,返回0;发送失败,则返回-1.

为了能够灵活的指定目标进程的控制信号,我们可以通过用命令行参数的方式拿到目标进程的pid和控制信号,如:

运行效果:

③ raise() 系统调用接口

当代码执行到 raise(int signal)函数时,OS会向自身发送 signal 号信号。

c.硬件级异常问题产生信号

①除零报错

当源文件的代码有类似除零这样的运算错误时,可执行程序运行时报错的底层原理是什么??

当可执行程序被加载到内存后,OS先将进程的 task_struct 放到运行队列 ,随后CPU执行程序代码,当CPU状态寄存器 发现有除零操作时,其溢出标记位 直接被置为1,从而形成在硬件层面上的报错,作为硬件的管理者,OS会从CPU那里拿到错误信息,又作为进程的管理者,OS把CPU溢出标记位信息翻译成终止信号,最后向目标进程发信号将其终止掉。

②野指针报错

当程序中出现这样的代码时:int * p=nullptr; *p=1; 典型的野指针问题,程序运行出错(段错误),那么其底层的原理是什么?

nullptr进程地址空间 上的位置是0号地址*p=1 是通过页表将1 写到**"与0号地址空间构成映射关系"** 的物理内存 上,但是,由于页表并未记录物理内存与地址空间上0号地址的映射关系 ,并且0号地址非法 ,所以当CPU通过MMU(内存管理单元,可以将虚拟地址翻译成物理地址)将值写入内存时,MMU报错,即硬件层面上的出错!!报错信息会被OS捕捉到,然后OS会向该进程发送终止信号,把该进程杀掉。

d.由软件条件产生信号

①匿名管道

进程间通信机制中,有个东西叫做匿名管道 (博主以前文章中有过详细讲解),当匿名管道的读端关闭,写端一直向管道内写入数据 的话,OS就会向该进程发送**13号信号(SIGPIPE)**干掉进程!

②闹钟

alarm() 函数是一个用于设置定时器的系统调用函数,它的主要功能是设置一个定时器(闹钟),当定时器时间到达时,内核会向当前进程发送14号信号(SIGALRM)信号。

总结:产生信号的方式可能有很多,但向进程发送信号的只能是OS!!

2.信号的发送

a.OS给进程发信号的实质

OS通过进程pid找到进程的PCB,然后将发送的信号写到进程PCB内的信号位图上,这就是OS给进程发送信号的本质。

那么问题来了,信号位图是什么?

我们知道,进程PCB在创建时,就已经内置了对信号的处理方法,即PCB需要具有识别、存储并执行每一种信号的能力,那么,这个能力是什么??--- 进程PCB内有关信号的三张表

进程PCB内有关信号的三张表指的是什么?

① Pending表(未决信号集)

它本身是一个位图 ,其中,比特位的大小表示信号值 (如:位图的第8个比特位表示8号信号),比特位的内容表示进程是否收到对应信号 (如:位图的第8个比特位值为0,表示进程未收到8号信号;若值为1,则表示进程收到了8号信号),Pending 表的功能是用来记录当前进程收到了哪些信号。

Block表(信号屏蔽集)

它也是一个位图,其中,比特位的大小表示信号值 ,比特位的内容表示对应信号是否被进程屏蔽。

注意信号屏蔽和信号忽略的区别

信号屏蔽 :信号屏蔽是指进程对特定信号进行屏蔽,使得这些信号在发生时不会被立即处理 ,而是处于未决状态 。只有当进程的信号屏蔽集 发生改变,不再屏蔽这些信号时,这些信号才会被捕获并处理 。信号屏蔽是一种将信号处理进行延后的机制。

信号忽略:信号忽略则是指进程对特定信号进行忽略处理,即当这些信号发生时,进程会接收到信号,但不会对信号进行任何处理。需要注意的是,并非所有信号都可以被忽略,如SIGKILL和SIGSTOP等信号就不能被忽略。

③ handler表(信号处理函数表)

定义 :handler表是一个映射表,它将信号的编号 (如SIGINT、SIGTERM等)与相应的处理函数 (这些处理函数可以是用户自定义的,也可以是系统默认的)建立映射关系

作用 :当进程接收到一个信号时,内核会暂停当前进程的执行,并根据信号的编号 在handler表中查找对应的处理函数。如果找到了处理函数,则执行该函数;如果没有找到(即该信号被忽略或未设置处理函数),则根据信号的默认行为进行处理。

④小结

实际执行信号的处理动作称为信号递达(handler).

信号从产生到递达之间的状态称为信号未决(pending).

进程可以选择阻塞某个信号(block).

被阻塞的信号产生时将保持在未决状态,暂时不递达(不处理),直到进程解除对此信号的阻塞,才执行递达的动作。

b.信号集操作函数

①有关sigset_t类型位图操作的函数接口

int sigemptyset( sigset_t* set); 功能:将set中所有比特位的值置为0

int sigfillset( sigset_t* set); 功能:将set中所有比特位的值置为1

int sigaddset( sigset_t* set , int signo); 功能:在set中添加信号signo

int sigdelset( sigset_t* set , int signo); 功能:在set中将信号signo删除

int sigismember( sigset_t* set , int signo); 功能:判断set中信号signo是否存在

②sigprocmask
③sigpending

int sigpending(sigset_t *set); 函数能够查询当前进程或线程的未决信号集(也就是Pending表的内容),并将该集合复制到set参数指向的信号集中。

sigpending() 函数通常与sigprocmask() 函数一起使用,以管理进程的信号屏蔽字和未决信号集合。例如,在更改进程的信号屏蔽字之前,可以使用sigpending() 函数查询当前的未决信号集合,以便在之后恢复信号屏蔽字时能够正确处理这些未决信号。

c.屏蔽进程的指定信号(代码实战)

3.信号的处理

我们已经知道:OS向进程发送信号的实质是将信号写入进程PCB中的Pending表。那么,进程是在什么时候执行信号的具体功能的呢?--- 进程从内核态返回到用户态的时候,进行信号的检测和信号的处理。

那么,什么是内核态?什么又是用户态?进程又是如何从内核态返回到用户态的呢?

a.用户态和内核态

用户态和内核态是操作系统中的两种重要状态,它们分别代表了不同的运行级别和权限范围。

用户态: 用户态是用户程序运行时的状态,在这种状态下,CPU只能执行非特权指令不能直接访问内存硬件设备 ,也不能执行特权操作 ,如修改系统配置、访问其他进程的内存 等。用户态下的程序运行在用户空间,其资源访问权限受到严格限制,**只能访问自己的[0 , 3GB]空间,**以确保系统的安全性和稳定性。

内核态 :内核态是操作系统内核运行时的状态,在这种状态下,CPU可以执行所有的指令,包括特权指令,可以访问所有的内存地址和硬件设备 ,拥有最高的权限。内核态下的程序运行在内核空间,负责管理系统资源、处理硬件事件、提供系统服务等。

b.用户态和内核态间的切换方式

①系统调用

用户态进程通过系统调用请求操作系统提供服务时,会触发从用户态到内核态的切换,系统调用是用户态进程主动要求切换到内核态的一种方式。

②抛异常

当CPU在执行用户态下的程序时,如果发生某些事先不可知的异常(如缺页异常),会触发由当前运行进程切换到处理此异常的内核相关程序中,从而转到内核态。

③中断

当外围设备完成用户请求的操作后,会向CPU发出中断信号。CPU在接收到中断信号后,会暂停执行当前的用户态程序,转而执行与中断信号对应的内核中断处理程序,从而完成从用户态到内核态的切换。

c.重谈地址空间

就如曾经的库函数调用一样,调用系统调用接口,也是在进程的地址空间上进行的!!

操作系统的朴素理解:就是基于一个时钟中断的死循环!!

相关推荐
pk_xz1234562 小时前
Shell 脚本中变量和字符串的入门介绍
linux·运维·服务器
小珑也要变强2 小时前
Linux之sed命令详解
linux·运维·服务器
Lary_Rock4 小时前
RK3576 LINUX RKNN SDK 测试
linux·运维·服务器
云飞云共享云桌面6 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
Peter_chq6 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
一坨阿亮7 小时前
Linux 使用中的问题
linux·运维
dsywws8 小时前
Linux学习笔记之vim入门
linux·笔记·学习
幺零九零零9 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
小林熬夜学编程10 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
程思扬11 小时前
为什么Uptime+Kuma本地部署与远程使用是网站监控新选择?
linux·服务器·网络·经验分享·后端·网络协议·1024程序员节