信号的发送、接收和处理的详细过程,信号屏蔽字,修改信号屏蔽字的API(信号)【linux】(zu)

信号的发送、接收和处理的详细过程

信号屏蔽字

信号屏蔽字的作用,以及它被放在了哪里

作用

屏蔽子的作用就是用来屏蔽信号的,有点像公司前台,信号来了先问前台(屏蔽字),我能被立即处理不,能就立即处理,不能就暂不处理。

每个进程能够接收的信号有62种,信号屏蔽字的每一位记录了每个信号是被屏蔽的还是被打开的。如果是打开的就立即处理,如果是屏蔽的就暂不处理。

屏蔽字放在了哪里

每一个进程都有一个信号屏蔽字,它被放在了进程表(task_struct结构体变量)中。

屏蔽字长什么样子?

为了方便理解,我们简单地认为屏蔽字就是一个64位的unsigned int数,每一位对应着一个信号,如果这一位为0,表示信号可以被立即处理,如果为1表示该信号被屏蔽了,暂不处理。实际是62位,这里64位只是为了便于理解。
在这里插入图片描述

第1位:对应编号为1(SIGHUP)的信号,该位为
1)0:表示1(SIGHUP)这个信号是打开的,可以被立即处理
2)1:表示信号被屏蔽了,暂时不能处理

第2位:对应编号为2(SIGINT)的信号
1)0:表示2这个信号可以被立即处理
2)1:表示信号被屏蔽了,暂时不能处理

以此类推。

我们可以自己修改信号屏蔽字,实现某个信号的打开和屏蔽,本篇博客后面不会就会说说明到屏蔽字的修改。

只不过在默认情况下,信号屏蔽字中所有的位都为0,也就说默认将所有的信号都打开了。

未处理信号集

作用

跟屏蔽字一样,也一个64位的无符号整形数,专门用于记录未处理的信号。
“未处理信号集”同样也是被放在了进程的进程表中(task_struct)。

什么时候会记录

信号来了,当进程的信号处理机制,检查该信号在屏蔽字中的对应位时发现是1,表示该信号被屏蔽了,暂时不能被处理,此时就会将 “未处理信号集”中该信号编号所对应的位设置为1,这个记录就表示,有一个信号未被处理。

这就有点像你去访问领导,前台(屏蔽字)跟你说领导正忙,请你到休息室(未处理信号集)休息。
如果该信号发送了多次,但是每一次都因为被屏蔽了而无法处理的话,在“未处理信号集”中只记录一次。

这就有点像别人欠你钱,你去催债,别人还的的慢了,所以你催了好多回,但是不管催多少回,人家只还你一次。

什么时候处理记录的“未处理信号”

当屏蔽字中该信号的位变成0时(被打开了),此时就回去检查“未处理信号”,看该信号有没有未决的情况,有的话就处理它。

信号处理的完整过程

有了屏蔽字和未处理信号集的铺垫,现在就可以来看一看进程处理信号的完整过程了。

图解:

在这里插入图片描述

文字描述

信号处理方式,信号屏蔽字,未处理信号集都放在该进程的task_struct结构体变量当中。
信号处理方式,信号屏蔽字,未处理信号集里面的对应位都可以被修改。
详细过程描述:
信号发生源:内核或者其他进程因为某个事件发送信号给当前进程,当前进程收到信号之后就开始处理信号,首先检查信号处理方式,对照信号处理方式表,如果是忽略就不对信号做任何处理。如果是捕获,默认或者终止就向信号屏蔽字递送信号,然后检查信号屏蔽字对应位是0还是1.如果是0就把这一位改为1,然后去处理信号,处理的时候如果是终止的话,那么就默认把进程终止,如果是捕获则调用捕获函数,如果捕获函数里面有exit则直接结束进程。如果如果没有exit捕获函数就会返回,返回之后把信号屏蔽字中的1设置为0。在信号处理的这个过程当中,除非认为修改屏蔽字位,否则屏蔽字位一直为1。前面说,如果信号正在处理,又发送了一个同样的信号,那么当信号到达屏蔽字的时候发现屏蔽字位为1,就表示这个信号暂时不能被处理,这个时候就回去检查未处理信号集,如果未处理信号集为0就设置为1,记录信号没有被处理,当前面一个信号处理完成之后信号屏蔽字位设置为0之后就会去检查未处理信号集,那么未处理信号集对应位为1就表示有一个信号未被处理,就会信号屏蔽字为重新设置为1然后去处理信号。
如果信号屏蔽字对应位为1,未处理信号集对应位也为1,再来了一个同样的信号,那么当信号发送到未处理信号集的时候发现对应位为1,就不再进行设置。就是说如果未处理信号集该位已经是1的时候,当有多次没有被处理的信号发送到未处理信号集的时候,只记录一次。没有排队机制。

代码演示

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>


void signal_fun(int signo)
{	
	printf("hello\n");
	sleep(3);
	printf("world\n");	
}

int main(int argc, char **argv, char **environ)
{
	pid_t ret = 0;
	
	signal(SIGINT, signal_fun);

	
	while(1);
	
	return 0;
}

我们在捕获处理函数调用sleep休眠3秒钟。
运行结果为:

在这里插入图片描述

我们可以看到按下多次ctrl+c但是只打印了一次world,第一次信号在处理的过程中,信号屏蔽字对应位为1,在休眠3秒的过程中,信号处理函数一直处于调用状态,所以再次按下ctrl+c,又有SIGINT信号到达信号屏蔽字,可是信号屏蔽字为1,就会设置到未处理信号集对应位为1,但是只能记录一次未处理信号集的对应位,所以多次按下ctrl+c之后,只有当三秒休眠完之后,把信号屏蔽字对应位设置为0,然后检查未处理信号集对应位为1,就把未处理信号集对应位设置为0,然后重新把信号屏蔽字对应位设置为1,调用信号捕获函数,只会处理一次,所以多次按下ctrl+c只会打印出来一个world。

那么如果我们要快速响应的话,就需要把信号屏蔽字正在处理信号过程中的1改为0,那么下一次信号在到达之后发现信号屏蔽字对应位为0就会去立刻响应。

修改信号屏蔽字的API

修改的原理

① 定义一个64位的与屏蔽字类似的变量
②将该变量设置为要的值将某信号对应的位设置为0或者为1。
③使用这个变量中的值来修改屏蔽字

修改方法有三种,当然以下这三种修改方法,我们并不需要自己亲自操作,只需要调用相应的API,API就会自动的实现。

完全的替换

使用变量的值去完全替换掉屏蔽字

比如:

屏蔽字 = 变量(1111111…11111)

屏蔽所有信号,当然里面的SIGKILL和SIGSTOP信号是不能被屏蔽,就算在屏蔽字中它们对应的位设置为了1,也不会起到屏蔽的作用。SIGKILL和SIGSTOP信号不能被忽略,不能被屏蔽,不能被捕获。

使用|操作,将对应的位设置为1,只屏蔽某个或者某两个信号

屏蔽字 = 屏蔽字 | 变量

比如:
屏蔽字 = 屏蔽字 | 0000…10

将编号为2(SIGINT)的信号,在屏蔽字中对应的位设置为1,屏蔽字中其它的位不变。

使用位&操作,将对应的位清0,打开信号

屏蔽字 = 屏蔽字 & (~变量)

比如:屏蔽字 = 屏蔽字 & (~0000…10)
屏蔽字 = 屏蔽字 & 1111…01,

将编号为2(SIGINT)的信号,在屏蔽字中对应的位清0,其它位不变。

设置变量的API

函数原型

#include <signal.h>
			
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);

set就是我们前面说的变量,至于变量名也可以定义为其它的名字,不一定非要叫set。

功能:设置变量的值
1)sigemptyset:将变量set的64位全部设置为0。
2)sigfillset:将变量set的64位全部设置为1。
3)sigaddset:将变量set中,signum(信号编号)对应的那一位设置为1,其它为不变。
4)sigdelset:将变量set的signum(信号编号)对应的那一位设置为0,其它位不变。

返回值
调用成功返回0,失败返回-1,并且errno被设置。

使用变量修改屏蔽字的API

函数原型

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

功能
使用设置好的变量set去修改信号屏蔽字。

参数
1、how:修改方式,前面说过有三种修改方式。
(a)SIG_BLOCK:屏蔽某个信号
屏蔽字=屏蔽字 | set

(b)SIG_UNBLOCK:打开某个信号(不要屏蔽),实际就是对屏蔽字的某位进行清0操作。
屏蔽字=屏蔽字&(~set)

(c)SIG_SETMASK:直接使用set的值替换掉屏蔽字

2、set:set的地址

3、oldset:保存修改之前屏蔽字的值
如果写为NULL的话,就表示不保存。

返回值
函数调用成功返回0,失败返回-1。

代码演示

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>


void signal_fun(int signo)
{
        sigset_t set, oldset;

        printf("hello\n");

        sigemptyset(&set);
        sigaddset(&set, SIGINT);
        sigprocmask(SIG_UNBLOCK, &set, NULL);

        sleep(3);

        printf("world\n");
}

int main(int argc, char **argv, char **environ)
{
        pid_t ret = 0;

        signal(SIGINT, signal_fun);


        while(1);

        return 0;
}

运行结果为:

在这里插入图片描述

不管按ctrl+c多次hello都会响应world。上面执行的过程类似于栈结构的调用过程。

发布了163 篇原创文章 · 获赞 94 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43648751/article/details/104675751