Linux系统编程44 信号 - 信号的响应过程分析!!!

信号的不可靠
是指信号的行为不可靠,因为执行现场不是我们布置的,而是由内核布置的,所以有可能第一次调用还没有结束的时候,就发生了第二次调用。

可重入函数:就是为了解决信号的不可靠
第一次调用还没有结束的时候,就发生了第二次调用,但是不会出错,这样的函数叫做 可重入函数。所有的系统调用都是可重入的,一部分库函数也是可重入的,比如:memcpy()。

内核为每个进程都维护了两个位图:
信号屏蔽字 mask :用来表示当前信号的状态,mask初始值一般全部都是1
pending位图: 用来记录当前进程收到哪些信号,一般初始值全部都是0

思考:
1 信号从收到 到 响应 有一个不可避免的延迟
2 如何忽略掉一个信号
3 标准信号为什么要丢失
4 标准信号的响应没有严格的顺序

5 不能从信号处理函数中随意的往外跳(setjmp.longjmp)

以上节实验为例 分析 信号的响应过程:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void sig_handler(int s)
{
	write(1,"!",1);
}

int main()
{
	int i;

	signal(SIGINT,sig_handler);

	for(i=0 ; i<12 ; i++)
	{
		write(1,"*",1);
		sleep(1);
	}

	exit(0);
}

在这里插入图片描述

第一步:
程序运行, 当程序的时间片耗尽(也可以理解为 某种形式发给自己的中断信号),程序保存当前进程状态(包括程序执行位置,用于返回之前的状态)转为内核态,挂载到内核中 等待就绪的 等待队列,等待获取时间片,即等待调度到自己,调度到自己之后,携带保存的进程状态 返回 用户态,此时从内核态 切换到 用户态 的时间点很重要,这个时刻 会做一个工作 :mask位图 按位与 pending位图 ,来判断是否接收到信号,如按照两个位图初始状态计算,则按位与之后 为0,表示没有任何信号。之后才会回到之前程序执行的地址继续执行之前的程序。

第二步:
从终端 执行 CTRL+C 向程序 发送 SIGINT 终止信号,这时 pending位图 SIGINT信号对应的为置1,表示当前程序收到了 SIGINT信号。但是 此时程序 并不能响应 SIGINT 信号,为什么呢? 因为程序从接收到信号 到 响应信号会有一个不可避免的延迟,从第一步得知,只有程序从内核态 切换到 用户态的时候,才会比较 mask位图 和 pending位图,只有这个时候才能知道 是否收到了信号,收到了哪个信号,以及才会响应信号。

第三步:
程序时间片再次被耗尽(也可以理解为 某种形式发给自己的中断信号),程序再次保存当前程序状态 进入内核态,挂载到调度队列等待被调度。程序再次被调度,获取时间片,从内核态 切换回到用户态,此时,此时,此时,程序会 比较 mask位图 和 pending位图,做按位与运算,判断自己有没有收到信号,发现 pending位图的 SIGINT位为 1,则表明 收到了 SIGINT信号,于是开始准备响应 SIGINT 信号,即执行信号响应程序。此时 程序不会回到之前执行到的地址,而是会到 之前注册的信号响应函数的地址去执行信号响应,即 将保存的程序信息中的地址信息改成 信号响应函数的地址,此时 mask位图 和 pending位图的 SIGINT信号位都会被置0。等执行完响应程序,再次回到内核态,并且将 mask位图的 SIGINT为置1。并且将保存的地址信息改回到之前的执行地址,并再次从内核态 切换到 用户态,并再次 比较 mask位图与pengding位图。如果在这期间 没有再次重复收到 SIGINT信号,则此时比较结果是0,即发现没有收到新的 SIGINT信号,其他位的信号也一样,而后会继续执行之前的程序,即打印*

所以思考的问题:

1 信号从收到 到 响应 有一个不可避免的延迟
可以理解为,信号是 程序从 内核态 切换会 用户态的 路上响应的,并且只能在这个时间点响应。所以这个延迟就是 程序 必须重新要从内核态切换到用户态,即必须要有一个新的中断或者时间片耗尽,程序进入内核态 并且 等待调度后 切回 用户态 所需要的时间,所以如果程序收到信号后,一直没有中断打断他,或者时间片一直没有耗尽,即一直没有进入内核态,也就不能从 内核态 切换为 用户态,也就不能比较 两个位图,也就是不能发现信号。

一句话,信号 是程序 从内核态 回到 用户态的路上响应的。

2 如何忽略掉一个信号
从上面的信号响应过程可以知道,只要将 mask位图中的 对应信号位 永远置0 即可。这样 就算接收到 对应信号,两个位图比较后,结果也是0,即无信号。

3 标准信号为什么要丢失
程序在响应信号的时候,mask位图,和pending位图的对应信号位都会被置0,而此时如果再发来 一万次 SIGINT信号,那么结果也只是 pending位图的 SIGINT信号为 被反复置1 一万次,结果还是1。即 等程序执行完 信号响应 并重新切换回用户态,mask位图 SIGINT位被重新置1,并再次比较两个位图的时候,尽管接受了一万次SIGINT信号,但是也只知道 最后一次收到的信号。

4 标准信号的响应没有严格的顺序

5 不能从信号处理函数中随意的往外跳
可知,程序从内核态切换到用户态,发现 mask位图 和 pending位图的变化,于是扎内核 替换执行程序地址,去执行信号响应程序,并且将 mask信号屏蔽位置0,等执行完响应程序后 再次重新扎内核,重新将mask信号屏蔽位置1,即将信号屏蔽字 解除阻塞,这样以后才能正常接收信号,最后再回到用户态。如果 在信号响应程序中跳转态其他位置,那么会错过 将信号屏蔽字 解除阻塞的操作,那么以后就无法响应对应的信号了。
所以 setjmp() 和 longjmp() 在信号处理函数中要慎用。

这个过程中 程序响应信号时 将mask位图对应为置0 是为了防止重入现象,后面详细补充。

猜你喜欢

转载自blog.csdn.net/LinuxArmbiggod/article/details/114004435