文章目录
- [一、人眼中的信号 VS 进程眼中的信号](#一、人眼中的信号 VS 进程眼中的信号)
- [二、ctrl+c 终止一个前台进程](#二、ctrl+c 终止一个前台进程)
- 三、查看信号信息
-
- [3.1 Core dump------核心转储功能验证](#3.1 Core dump——核心转储功能验证)
- 四、信号的处理方式
- [五、ctrl+c 被解释成2号信号验证](#五、ctrl+c 被解释成2号信号验证)
-
- [5.1 signal------设置自定义捕捉方法](#5.1 signal——设置自定义捕捉方法)
- [六、ctrl+c 是如何变成信号的?](#六、ctrl+c 是如何变成信号的?)
- 七、异步、软中断
- 八、结语
一、人眼中的信号 VS 进程眼中的信号
人眼中的信号:
- 以人为例,我们认识信号,一定是因为有人教我们,我们记住了。**认识信号包括:识别信号和知道信号的处理方法。**例如,我们可以识别红灯这个信号,并且知道红灯亮了就不能再过马路了。
- 即便是现在没有信号产生,我们也知道产生信号后,我们应该做什么。
- 信号产生了,我们可能并不立即处理这个信号,而是在合适的时机去处理,因为我们当前可能正在做更重要的事情,所以在信号产生后一直到信号处理前,有一个时间窗口,在这个时间窗口内,我们必须记住信号的到来。
进程眼中的信号:
- 进程必须能够识别、处理信号,即使信号没有产生,也要具备处理信号的能力。信号的处理能力,属于进程内置功能的一部分。
- 进程即便是没有收到信号,也能知道哪些信号该怎么处理。
- 当进程真的收到了一个具体的信号的时候,进程可能并不会立即处理这个信号,在合适的时候处理。
- 一个进程必须当信号产生了,到信号开始被处理,就一定会有时间窗口,进程具有临时保存哪些信号已经发生了的能力。
二、ctrl+c 终止一个前台进程
现象 :ctrl+c
可以杀掉一个前台 进程。Linux 中,一次登陆中,一个终端,一般会配上一个 bash
,每一个登陆,只允许,值允许一个进程是前台进程,可以允许多个进程是后台进程。bash
也是进程,在向 bash
输入指令之前,bash
就是前台进程,此时如果在 baash
中输入一个指令 ./myprocess
,以我们自己写的可执行程序为例,该程序中有一个死循环,一直向显示器打印,那么该程就会变成前台进程,bash
就会变成后台进程,此时再向 bash
中输入指令是没有任何反应的。前台进程和后台进程的一个本质区别是,谁来获取键盘输入,只有前台进程才能拿到键盘输入。 ./myprocess &
此时 myprocess 是一个后台进程。ctrl+c
**本质是被进程解释为收到了 2 号信号。**进程收到2号信号的默认动作就是终止自己。
三、查看信号信息
pidof myprocess
:显示操作系统中所有 myprocess 进程的 pid。
pidof myprocess | xargs kill -9
:杀掉当前操作系统启动的所有 myprocess 进程。
kill -l
:查看系统的信号列表。
没有0、32、33号信号,一共有62个信号。1-31号称为普通信号;34-64号称为实时信号,进程一旦收到实时信号,必须立即处理。信号本质上就是数字,右边的字符是信号的名称,在操作系统的内核中,信号一定是以宏定义的方式提供的,这些宏定义在 signal.h
中可以找到。
man 7 signal
:查看信号的详细说明。
其中 Term
、Core
表示终止;Ign
标记忽略;Cont
表示继续;Stop
表示暂停。
其中 core dump
标志就是用来区分 Term
和 Core
的。云服务器的 Core dump
功能默认是关闭的,可以通过 ulimit -a
指令来查看当前系统的所有资源限制。
可以通过 ulimit -c size
、 去设置它的大小为 size,如果 size > 0
就表示开启 Core dump
功能。
3.1 Core dump------核心转储功能验证
cpp
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
pid_t id = fork();
if(id == 0)
{
// child
int cnt = 500;
while(cnt--)
{
cout << "I am child, my pid is: " << getpid() << endl;
sleep(1);
}
}
else if(id > 0)
{
// father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret == id)
{
cout << "wait " << id << "success, " << "exit code: " << WEXITSTATUS(status) << ", exit signalnum: " << WTERMSIG(status) << ", core dump: " << ((status >> 7)&1) << endl;
}
}
return 0;
}
可以看到,Term
对应的 core dump
标志位是 0;Core
对应的 core dump
标志位是 1。前者表示正常终止,后者表示异常终止。打开系统的 core dump
功能,一旦进程出现异常,操作系统会将进程在内存中的运行信息,给 dump
(转储)到进程的当前目录中,形成 core.pid
文件,core dump
被称作核心转储 。core.pid
文件中详细记录了程序的异常原因,可以直接帮我们定位到出错行。
cpp
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
int a = 10;
int b = 0;
a /= b;
return 0;
}
四、信号的处理方式
收到信号有以下三种处理方式:
- 默认动作:人看到红灯,默认动作就是站在原地,等待红灯结束。
- 忽略:人看到红灯之后,不管红灯,继续往前走。
- 自定义动作(信号的捕捉):人看到红灯后,开始唱歌。
进程收到一个信号后,它的处理动作只能在上面这三个动作中进行三选一。
五、ctrl+c 被解释成2号信号验证
5.1 signal------设置自定义捕捉方法
signal------设置进程对某个信号的自定义捕捉方法 :当进程收到 signum
信号的时候,去执行 handler
方法。
signum
:信号的编号。handler
:一个函数指针,指向信号的自定义捕捉动作。sighandler_t
:一个函数指针类型,指向一个返回值为void
参数为int
的函数,这个参数值就是收到的信号。为什么要有这个参数呢?因为同一个自定捕捉方法,可以捕捉多个不同的信号,所以在该方法中需要知道是收到哪个信号才来执行的该方法。
signal
函数只需要设置一次,往后都有效。sighandler_t
函数只会在收到对应的信号后才会执行,假如程序一直没有收到对应的信号,那么该函数就一直不会被调用。
cpp
// mysignal
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void myhandler(int signo)
{
cout << "process get a signal: " << signo << endl;
}
int main()
{
signal(2, myhandler);
while(true)
{
cout << "Hello Linux" << endl;
sleep(1);
}
return 0;
}
进程收到信号后的默认动作和自定义捕捉动作只能执行一个,设置了自定义捕捉动作,默认动作就不再执行了。
六、ctrl+c 是如何变成信号的?
由于进程无法直接访问硬件资源,所以键盘被按下,一定是操作系统先知道的。
OS 怎么知道键盘上有数据了?
最简单的方法就是,操作系统定期的去检查键盘文件。把键盘数据读到操作系统当中,本质上是将键盘外设(文件)中的数据,拷贝到内核中的文件页缓冲区中。因为操作系统中有大量的外设,如果都采用定期检查的方法,去判断外设中是否有数据是非常浪费时间的,因此这种方法不可取。在数据层面,CPU 是不会直接和外设打交道的,但是在控制层面,当外设有数据了,外设可以给 CPU 发送硬件中断 。 中断有自己的中断号,CPU 通过中断号来判断该中断是哪个外设发送的。之后 CPU 会以这个中断号为下标,去操作系统的中断向量表中执行对应的方法。 操作系统在将键盘文件中的数据拷贝到操作系统之前,会先判断输入的是数据还是控制,如果是控制,ctrl+c
就是控制,会被操作系统转换成2号信号发送给进程。
键盘和显示器有各自独立的文件页缓冲区:
七、异步、软中断
信号的产生和代码的执行是异步的,所谓异步就是在我们代码执行的任何时候都有可能收到信号。信号是进程之间事件异步通知的一种方式,属于软中断。
八、结语
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!