信号初识
什么是信号
信号用来给某个进程直接传达的命令,因此信号的发出对象为操作系统。
按照理论,被接收信号的进程可以选择:
- 默认执行:直接按照信号的实现方法默认执行
- 不执行:选择无视信号
- 自定义执行:不顾具体的信号实行方法,自己按自己的方法实行
查看信号
可以执行下面的命令查看所有的信号:可明显发现数字序号和对应的大写字符串其实是宏实现的。
kill -l
其中:131 号信号为普通信号,3464 号信号为实时信号(暂时不了解)。
('Ctrl+C'在我们进程死循环时可以强制退出,它对应的实现是 2 号信号)
在 Linux 中,会存在前台进程和后台进程:
- 前台进程:直接展现给用户的(默认打开的是 bash 进程),每次登录只能允许一个前台进程存在
- 后台进程:隐藏起来执行的进程
补充:数据输入输出流程
我们知道操作系统和硬件之间夹着驱动程序,键盘输入时对应的驱动程序会发生中断请求给 CPU,CPU 接到请求知道了有数据需要从键盘读取,它会让操作系统拿数据到对应缓冲区,待操作系统有时间处理(感觉就是进入运行队列!)就会刷新到显示器文件,显示器拿到了数据。
发送信号
在学习发生信号的方式之前,我们需要先看一下另一个接口:signal()
(注意:**9 号信号'强制终止'和19 号信号'强制暂停'**是无法被替换执行方法的)
#include <signal.h> // 信号处理函数类型定义(参数为信号编号) typedef void (*sighandler_t)(int); // 注册信号处理函数 sighandler_t signal(int signum, sighandler_t handler);
参数:
- 第一个参数:信号
- 第二个参数:函数指针(用来自定义一个函数作为参数)
作用:执行该接口后,该信号默认执行方法失效,捕捉该信号执行自定义执行方法(即该函数)。当捕捉到 2 号信号,也就意味着原来 2 号信号的执行方法将不再生效!
指令发送
指令发送即执行:指令发送我们就不多谈了,实行 kill 指令与选项即可。
kill -序号 进程 ID
键盘触发
比如:Ctrl+C(对应 2 号信号)、Ctrl+\(对应 3 号信号)
系统调用
下面介绍三个可以发送信号的系统调用接口:你会发现最后两个接口都可以通过 kill() 实现!
kill()
#include <signal.h>
#include <sys/types.h>
int kill(pid_t pid, int sig);
参数:
- 第一个参数:目标进程
- 第二个参数:要发送的信号编号
返回值:
- 成功:返回
0; - 失败:返回
-1,并设置errno
作用:给指定进程发送对应的信号
raise()
#include <signal.h>
int raise(int sig);
参数:信号编号
返回值:
- 成功:返回
0; - 失败:返回非
0(设置 errno)
作用:向正在运行的(即调用该函数的进程)进程发送信号
abort()
#include <stdlib.h>
void abort(void);
参数:无
返回值:无
作用:触发程序的异常退出(发送 6 号 SIGABRT 信号)
异常(硬件发送)
比如代码中出现除 0 错误,对空指针解引用....下面我们来梳理该类错误的产生过程:
CPU 中会存在程序计数器(比如 PC 指针)用来找到下一行代码(不会干扰其它进程),当 CPU 知道下一句代码指令是违规的(可能是语法规定有被解析到),就会暂停执行代码,告诉操作系统错误情况(比如错误位置、原因等等),操作系统拿到 CPU 的反馈错误后再通过信号向该进程发送,用户再通过提示知道错误信息,一般出现信号错误都是很严重的错误,进程会终止,但是在大型的服务器上,会循序重新执行程序,只给你报错误信息!
了解即可:错误信息其实是被保存在 core dump 文件(这个是需要手动开启)
难道异常都是由硬件发出吗?当然不是软件也可以,请继续向下看:
信号(软件发送)
以 alarm() 函数为例:给当前程序定一个闹钟(到时发送 SIGALRM-14 号信号)
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
参数:设置的秒数(比如 5 秒,seconds=5)
返回值:上一次设置的定时器剩余的秒数(混淆点)比如:
第一个 alarm() 函数的返回值应该是 0,因为它之前没有闹钟
如果有多个 alarm():前一次调用 alarm 设置的定时器'还没到期'(还没触发 SIGALRM 信号)时,再次调用 alarm。就会覆盖前面的秒数直接执行当前 alarm()。
例如:我先设置一个闹钟,alarm(20),然后观察返回值以及验证是否发送的是 14 号信号:
结果:我发送了两次 14 号信号,被正常捕捉没有问题。但是 20 秒后闹钟响了又被捕捉到了
下面验证如果存在多个 alarm(),是否会覆盖旧的闹钟秒数:
操作系统内部处理
操作系统给这个进程发信号,实质是修改 task_struct 中的 int signal 参数:

