Linux进程信号
- 1.认识信号
- 1.1 信号的概念
- 2. 信号的产生
- 2.1 通过键盘进行信号产生
- 2.2 通过系统调用,指令来发送信号
- 2.3 进程异常
- 2.4 软件条件
- 3.信号的保存
- 4.信号的捕捉和处理
- 4.1信号集操作函数
- 4.2 volatile去优化
1.认识信号
1.1 信号的概念
下面以生活中红绿灯来认识信号
我们为什么会认识红绿灯,这是因为从小有人告诉你,你才能识别它,也知道对应的灯亮了,要做成什么样的动作。
信号没有产生的时候,其实我们已经知道怎么去处理这个信号
当信号到来,我们并不清楚具体什么时候,信号相对于我正在做的工作是异步产生的
信号产生了,我们不一定要立即处理它,而是我们在合适的时候处理,也就是说我要有一种能力将已经到来的信号,进行暂时保存
什么叫做信号:信号是一种向目标进程发送通知消息的一种机制。
2. 信号的产生
进程在运行的时候,前台(命令行操作的时候,只能有一个),后台(./XXX &)(可以有多个)
而检测是否是前台进程的标准:是看看你有没有能力接受用户输入(就是在运行程序时在shell上输入指令)
ctrl+c:终止前台进程
前台进程不能被暂停(ctrl+z),如果被暂停,该前台进程必须立即被放到后台
有关前台和后台进程切换的指令:
./XXXX & #这个是将程序放到后台进行运行
jobs #用于显示当前用户的所有作业(jobs)状态
fg number #用于将后台作业带回到前台
ctrl+z #前台进程不能被暂停(ctrl+z),如果被暂停,该前台进程必须立即被放到后台
bg number #用于将一个被暂停的作业放到后台继续运行
信号在合适的时候处理:
1.默认行为
2.忽略
3.自定义—信号的捕捉
拓展:OS怎么知道键盘有数据输入了?中断
硬件:键盘
键盘硬件:键盘是输入设备,当你按下一个键时,内部电路会产生电信号。
中断请求
中断请求(IRQ):当你按下键盘的某个键时,键盘生成一个中断请求信号(IRQ)并发送给CPU,通知它有输入数据需要处理。这个信号会打断CPU当前正在执行的任务。
CPU的作用
中断处理:当CPU接收到中断请求后,它会完成当前的指令执行,然后保存当前任务的状态(如寄存器的内容、程序计数器等),以便在处理完中断后可以恢复继续执行。
查找中断向量:CPU使用中断向量表来确定处理该中断的具体中断处理程序的地址。中断向量表是一个在内存中存储的表格,它为每种可能的中断提供了一个处理程序的入口点。
中断向量表
中断向量表:内有函数指针数组,而函数指针数组中有特定硬件的读取方法(键盘的读取方法等)
信号产生的四种方式:
2.1 通过键盘进行信号产生
普通信号是1 ~ 31号信号,实时信号34 ~ 64号信号
每个进程对于信号会有函数指针数组和信号位图,函数指针数组的数组下标与信号编号强相关,信号位图就可以存储信号的相关信息,位图中比特位的位置,决定信号编号,比特位的内容决定是否收到信号,而发送信号的本质其实是OS向目标进程写信号,也就是说修改位图中对应信号的比特位。
Core Dump
一个进程要异常终止时,可以选择把进程的用户空间内存数据全部 保存到磁盘上,文件名通常是core,这叫做Core Dump
2.2 通过系统调用,指令来发送信号
kill(processpid,signumber);
int raise(int sig);//raise函数用于向当前进程发送信号,通常用于在程序中主动生成信号,以响应特定的条件或事件
abort()//生成SIGABRT信号并终止进程
2.3 进程异常
int a=10;
a/=0;
硬件MMU的作用:将虚拟地址转换为物理地址
2.4 软件条件
闹钟是一种软件条件
void handler(int signo)
{alarm(2);// n=alarm(0);cout << "result: " << n << endl;// std::cout<<"get a signo: "<<signo<<"alarm : "<<cnt<<std::endl;// exit(0);
}int main()
{signal(14, handler);cout << "pid: " << getpid() << endl;alarm(2);while (true){// cnt++;sleep(1);cout << "running ..." << endl;}
}
操作系统中的时间:
1.所有用户的行为,都是以进程的形式在OS中表现的。
2.操作系统只要把进程调度好,就能完成所有的用户任务。
3.CMOS,周期性,高频率的,向CPU发送时钟中断。
操作系统本质上就是一个死循环,硬件CMOS刺激CPU执行中断向量表中的调度方法,所以操作系统的执行其实是基于硬件中断的
3.信号的保存
实际执行信号的处理动作称作信号递达(Delivery):
信号的忽略;信号的默认;信号的自定义捕捉
信号从产生到递达之间的状态称作信号未决(Pending):
在位图中就叫做信号未决
进程可以选择阻塞(Block)某个信号:
未决之后,暂时不递达,直到解除对信号的阻塞
4.信号的捕捉和处理
4.1信号集操作函数
#include <signal.h>
//函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
int sigemptyset(sigset_t *set);
//函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置1,表示 该信号集的有效信号包括系统支持的所有信号。
int sigfillset(sigset_t *set);
//sigaddset和sigdelset在该信号集中添加或删除某种有效信号
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
//在该信号集中判断是否有有效信号
int sigismember(const sigset_t *set, int signo);
这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包某种 信号,若包含则返回1,不包含则返回0,出错返回-1。
sigprocmask
调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
SIG_BLOCK | set包含了我们希望添加到当前信号屏蔽字的信号 |
SIG_UNBLOCK | set包含了我们希望当前信号屏蔽字中解除阻塞的信号 |
SIG_SETMASK | 设置当前信号屏蔽字为set所指向的值 |
sigpending
#include <signal.h>
sigpending
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。
sigaction
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
//试例代码
struct sigaction act, oact;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, 3);
sigaction(2, &act, &oact);
信号在合适的时候被处理,合适的时候其实是进程从内核态返回用户态的时候,进行信号的检测和信号的处理。
用户态是一种受控的状态,能够访问的资源是有限的。
内核态是一种操作系统的工作状态,能够访问大部分系统资源
信号捕捉中,一共会涉及四次状态切换
4.2 volatile去优化
首先flag定义在内存中值为0,因为while的逻辑判断,加载到cpu中进行判断,非0为真会一直循环,当发送2号信号时,在内存中flag0变成了1,然而在main函数中flag只进行了逻辑判断,编译器会进行优化,导致再次进行逻辑判断时,不再从cpu中加载flag到逻辑计算单元,而是直接拿历史cpu中的flag用于逻辑判断,也就导致了while循环一直循环,而在flag定义的时候加上volatile就告诉编译器,该关键字修饰的变量不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作。