【Linux】信号的基本概念、信号的产生、记录与处理
一、信号的基本概念
在我们的日常生活中无处不 encountered 有 signal 的存在。这些 signal 包括课间铃声、广播、红绿灯装置以及警报器等设备发出的声音等众多形式。即使 signal 并未发出 但我们也掌握着应对它的技巧例如 遇到红灯应当立即停车 而当手机提示音响起应当及时接听此类情况。这些知识之所以能够被我们所掌握 是因为我们最初接触 signal 时就对其特征以及相应的应对措施进行了深刻的记忆 因此我们便能够迅速识别出当 signal 出现时应该如何处理它们。基于以上分析 我们可以得出以下结论:
在生活场景中观察到信号的发生具有一定的随机性;即使未检测到信号的存在,在特定情况下也能发挥相应的作用;我们对信号的特性及其处理策略进行了详细记录。
在计算机系统中,信号扮演着重要角色。在进程之间通过异步事件进行非同步通知的方式被称为信号。这种机制被归类为软中断行为。
在计算机中 :**①相对于进程来说信号的产生是异步的过程;②即使尚未产生相应的信号也会知晓相关的处理方式;③因为已预先记录相关信息所以进程能够了解并执行相应的操作。
那么我们需要知道进程是怎么记录信号、在哪里记录信号呢?
首先来看一下这个 Linux 系统中的命令:通过使用 kill -l命令可以查看进程所处的状态及其相关联的资源情况。在进程状态这一栏中可以看到一个状态字段(通常用数字表示),该字段的具体含义可以通过执行 man kill 命令来获取详细的描述信息。

每一个信号都具有唯一的一个标识符和一个宏名,这些宏名可以在包含signal.h的头文件中可以看到,其中包含了一个示例:#define SIGINT 2
二、信号的产生
信号的产生必须直接或者间接通过操作系统
其实 准确的说不是发信号,而是操作系统向进程的PCB写信号
信号的产生一共有四种途径:
1.通过终端按键产生信号
在不小心编写了一个死循环时(如果)按下键盘组合Ctrl+C(键盘产生了信号),实际上操作系统会将此信号记录在进程的PCB上(通过发送一个2号SIGINT信号至目标进程或前窗进程中)。操作系统的处理器会解析这个特定类型的中断并触发相应的处理程序(导致),最终会使得当前正在执行循环的那个进程立即终止(因此你就可以优雅地结束掉这个死循环)。
Linux
Linux
2.系统调用(命令或者函数)
①命令kill
我们在后台启动了一个死循环进程 a.out ,随后在打开终端并使用命令进行监控时发现其状态为R状态。接着我们使用 kill+进程ID 命令成功将其终止,并最终完成了这个死循环的处理。

事实上我们也可以注意到,在kill命令执行时它也触发了一个SIGSEGV信号码从而导致其终止。

17261是test进程的ID。这是因为再次按回车才能显示 Segmentation fault ,是在4568进程终止之前已经返回到Shell提示符等待用户输入下一条命令。Shell不希望Segmentation fault信息与用户的输入混杂在一起,因此只有在用户输入命令后才会显示。
为了传递特定类型的信号以终止进程并引发相关操作系统的响应, 系统允许用户通过多种方式执行 kill 命令. 具体来说, 这些命令分别对应于不同的信号编号: 比如说, kill -SIGSEGV 17261 或者 kill -11 17261, 其中数字表示对应的信号编码. 需要注意的是, 虽然通常情况下程序出现段错误是由非法内存访问引起的, 然而即便向该进程发送 SIGSEGV 信号也会导致同样的问题发生.
②系统调用函数
如果不打算使用命令终止死循环,则可以通过系统调用函数来实现这一目标.kick命令是通过调用kick函数来实现这一机制.该功能能够向指定的一个进程发送特定类型的信号.
> 1. #include <signal.h>
>
> 2. #include <sys/types.h>
>
> 3.
>
> 4. int kill(pid_t pid,int sig);
>
>
>
>
> ```
>
>

函数写好后,我们来看结果:
再打开一个终端,查看死循环进程的进程ID,运行kill程序,将该进程的进程ID以及要发送的信号以命令行参数进行传入kill系统调用函数,最后使用kill系统调用函数将该死循环进程终止。

#### 3.由软件条件产生信号
**(1)** 比如在某个进程里创建了一个管道,操作系统识别到这个事件,然后读端被关闭了,这叫做软件条件,操作系统检测到之后认为没有必要在进行写了,所以操作系统就会自发的向该进程发送13号信号:SIGPIPE 来关闭写端,从而终止该进程。
也就是说:操作系统识别了一个事件,虽然没有发生错误,但是发现这个事件不具备某些条件了,所以操作系统就会自发的向进程发送信号。大部分信号都会导致进程退出。
**(2)** 来看一下14号信号:SIGALRM,这个信号叫做超时信号或者闹钟信号。如果我们在某个进程中用alarm函数设定了一个闹钟,那么闹钟一旦超时之后,操作系统就会给该进程发送SIGALRM。
>
1. #include <unistd.h>
2. unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
为了验证系统响应速度,请注意以下操作:在执行完毕后的一秒内,在操作完成后的一秒内,在操作完成后的一秒内 SIGALARM 信号被发送到相关进程,并导致其停止。

4.异常(硬件、软件引发的)
进程出现异常后会触发软硬件的异常机制,并由操作系统主动向该进程发送相应的异常信号
例如,在我们的代码中引入了一个野指针时,默认情况下该指针可能会指向未预期的位置。这违反了内存保护规定,在执行虚拟地址空间映射至物理内存的过程中(即当虚拟地址空间需要映射至物理内存时),MMU系统会审查页表信息。MMU发现存在这种问题后会立即触发异常情况,并向该进程发送第11号SIGSEGV(segmentation fault:段错误)信号。该进程收到此信号后会在适当的时间内进行处理。
再举个栗子:我们此时编写了一个除法运算的程序,在该程序的代码中存在无法执行的除以零操作。当运行时,在编译阶段CPU会检测到这个非法操作并报告给操作系统。随后的操作系统会将此中断触发为第8个SIGFPE中断(floating point exception),该进程需及时处理这个信号。
注意:
- 唯一能够接收Ctrl-C触发的信号的是前置程序。通过在指令末尾添加&符号即可将该指令置于后台运行的状态下,从而使得Shell无需等待子过程结束即可接收新指令。
- 在同一个时间窗口内最多可启动一个前置程序及多个后置程序;只有前置程序才能够接收由Ctrl-C等控制键引发的事件。
- 在前置程序的操作过程中,用户随时有可能按下Ctrl-C并引发相关事件;这意味着在其代码执行到任何位置时都会受到SIGINT信令的影响而终止;因此从流程层面来看这些信令是异步处理的。
三、信号的记录
系统向进程发出信号时会进行相应的响应,并且在接收每个信号之前必须进行追踪。为了确保系统的稳定性与可靠性,在接收每个信号之前必须进行追踪,并且确定系统是如何检测到这些信号的。
- 该实际执行动作即为 signal 的 delivery(即为 signal 达成传递这一过程)
- 在 signal 的产生与到达之间停留的状态则被称为 pending(也即 signal 处于待处理状态)
- Process 可选择对某 signal 实施 block(亦可表述为 process 可选择对该 signal 发生 block)
- 当被 block 的 signal 生成时则会停留在 pending 状态直至 process 解除对该 signal 的 block 才能完成 delivery
- 注意区分的是,在被 block 的情况下不会导致 delivery 发生……
- 如果 signal 被 blocked 则永远无法到达 deliver 状态
- 而 ignore 则是在 deliver 完成后仍可选择采取的一种处理方式
描述进程所使用的设备是程序可中断中断机制(PCI)。
在PCB设计中使用位图分别描述阻塞和未处理的信号。这些位图分别称为block表和pending表,并且每个对应着31个函数。因此,在PCB设计中还设置了函数指针数组用于存储这些处理动作的地址。我们称这个数组为handler表。

- 每个信号均包含两个字段分别标识阻塞状态(block)与待办状态(pending),并附有一个函数指针用于指定处理动作。当信号产生时,在内核的进程控制块中将该信号的待办标记进行设置(直至该信号到达触达点时才会清除其待办标记)。
- SIGINT类信号虽已被阻塞而尚未到达触达点(无法到达),因此仍需等待其自然释放在某个时刻。
- SIGQUIT类信号未曾出现过(若出现过,则会被立即阻塞),一旦出现此类信号将被立即阻塞(其处理动作是由用户自定义的sighandler函数完成)。需要注意的是,在进程解除对某类特定信令的阻塞之前若此类信令曾出现过多次,则如何进行处理?在Linux系统中这一行为是这样实现的:常规信令在到达触达点之前最多只记录一次事件(实时信令则可依次将多个事件放置于一个队列之中)。
四、信号的处理
我们已掌握信号产生机制共有四种类型,并清楚地记录在位图中。接下来应当着手处理相关事务。由于信号产生并非立即生效,在适当时机介入则由操作系统的调控机制决定。具体何时介入则由操作系统的调控机制决定。让我们深入探讨信号的各种处理方式,并可归纳为三类动作。
信号处理的三种动作:
- ①预设行为: 执行该信号的预设行为。
- ②过滤操作: 此信号将被过滤掉。
- ③自定义处理流程: 提供一个专门的处理函数,在内核切换至用户态后执行该功能。这种方法通常被称为'信号捕捉'。
这里的前两项操作较为直观,在分析中通过位图即可掌握其处理流程。然而自定义功能往往涉及较多复杂的设置。接下来将分别从技术实现和性能优化两个维度展开说明。
Linux
Linux
Linux环境下
Linux
Linux
