目录
一.信号的概念
1.生活中的信号
红绿灯就是一种信号,因为我们
- 知道它的特征,能识别
- 知道对应的灯亮,意味着什么,要做什么
- 我为什么认识这个信号?因为有人提前告诉过我,所以信号没有产生的时候,我们已经能知道怎么处理这个信号,信号到来,我也能立即识别出来
- 信号的到来,我们并不清楚具体什么时候,即信号到来相对于我正在做的工作,是异步产生的
- 信号产生了,我们不一定要立即处理它,而是在合适的时候处理,所以我要有一种能力将已经到来的信号暂时保存
2.什么是信号
信号是向目标进程发送通知消息的一种机制。
目标进程能识别信号,并且知道怎么处理
二.信号的产生
1.前台进程与后台进程
进程分为前台和后台(./xxx &),前台进程在命令行操作时只能有1个,后台进程可以有多个
前台和后台的本质区别是前台进程能够接受用户输入,后台进程不能接受用户输入,所以前台进程只能有1个。键盘输入ctrl + c,前台进程接收(通过接收信号的方式来间接接收)后终止。
当我们启动一个前台进程,shell无法接收指令,因为它被操作系统提到后台。终止前台进程后,操作系统将shell提到前台,可以接收指令。shell也是一个进程,但是不能被ctrl+c终止。
- ctrl + c :终止前台进程
- jobs :查看后台进程
- fg [number] :将后台进程提到前台
- ctrl + z :将前台进程暂停,前台进程如果被暂停,会立即被操作系统提到后台,shell提到前台,否则键盘会失效
- bg [number] :将后台暂停的进程在后台启动(一定是后台暂停的进程,因为只有shell在前台运行才能接收你的指令)
- 终止一个后台进程:(1)fg [number] , ctrl+c (2)kill -9 pid
2. 中断
操作系统怎么知道键盘有输入?
CPU和外设有针脚相连(间接相连),不同外设可以向特定针脚发送电信号。给每个针脚一个编号,叫做中断号。当某个外设数据就绪时时,向针脚发送高电平,CPU识别到某个针脚的高电平,就将中断号写到寄存器,操作系统就可以读取到这个编号。
计算机启动时,操作系统会将一个函数指针数组加载到内存,这个数组叫做中断向量表,内容是各种外设的读取方法,数组下标就是对应外设的中断号。外设向CPU发送中断号后,操作系统会立即停止手头的工作,从寄存器读取中断号,到中断向量表寻找读取方法,将数据从外设读取到内存。
操作系统怎么知道键盘有输入?
当用户按下键盘,键盘向CPU发送电信号,CPU写入中断号,通知操作系统,操作系统提取中断号,执行中断向量表中的键盘驱动读取方法,将数据从外设拷贝到指定的缓冲区中。
信号就是用软件来模拟中断的行为,中断是外设和操作系统之间的信息通知,信号是进程和进程之间的信息通知。
3.操作系统中的信号
man 7 signal :查询信号
我们既可以使用信号编号,也可以使用信号名称,实际上这些名称就是一个个宏。
细节:
- 没有0号信号。因为进程的退出信息有退出信号和退出码,如果退出信号为0,表示进程不是因为信号而退出,是正常终止的,所以没有0号信号是为了标识进程未收到信号正常退出。
- 1~31号信号是普通信号,34~64是实时信号,我们只谈普通信号
- 每个进程都有函数指针数组,信号编号和数组下标强相关(信号从1开始,下标从0开始)
4.产生信号的四种方式
(1)键盘输入产生信号
例如键盘输入ctrl+c,键盘向CPU触发中断,CPU通知操作系统,操作系统读取中断号,从中断向量表中调用键盘的驱动读取方法。键盘的数据分为普通数据和控制数据,操作系统解析”ctrl+c“为控制数据,所以不会把数据写到缓冲区,而是转化为向前台进程发送2号信号。
类似地,键盘输入ctrl+z 转化为向前台进程发送19号信号,该信号的默认处理方法是暂停进程。
键盘输入ctrl+\ 转化为向前台进程发送3号信号,该信号的默认处理方法是终止进程。
(2)系统调用产生信号
功能:向指定的进程发送指定的信号
功能:向本进程发送指定的信号
//代码实例:实现kill指令对应的可执行程序
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string>
using namespace std;
void Usage(const string& proc)
{
cout << "\nUsage: " << proc << "signo processid" << endl;
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 0;
}
int signo = stoi(argv[1] + 1);
int processid = stoi(argv[2]);
kill(processid, signo);
}
(3)硬件异常产生信号
除0错误
CPU执行进程的代码,例如10/0,状态寄存器中的溢出标志位被置1,CPU通知操作系统自己出现异常,操作系统将硬件异常,转换为向导致该异常的进程发送8号信号SIGFPE(float point exception),该信号的默认处理方法是终止进程。
void handler(int signo)
{
cout << "signo: " << signo << endl;
sleep(1);
}
int main()
{
signal(8, handler);
int a = 5 / 0;
return 0;
}
以上代码会陷入死循环:CPU执行进程代码引发异常,不再向后执行,通知操作系统,操作系统让CPU调度其它进程,同时向目标进程发送8号信号,但由于用户对8号信号自定义捕捉,进程没有退出,当它重新被CPU调度时,硬件上下文加载到CPU中,再次引发异常。
解引用空指针
空指针就是进程地址空间中的0号地址,是一个无效地址,页表中没有映射关系。当CPU访问0号地址,通过MMU(内存管理单元,一种硬件,集成在CPU中),查询页表从虚