Linux信号处理浅析

一、信号从发送到被处理经历的过程

1、常见概念

(1) 信号阻塞

阻塞,即被进程拉黑,信号被发送后,分为两种情况,一种是被阻塞了(被拉黑了),一种是没有被阻塞。

(2) 信号未决

在信号被进程处理之前的过程,都可以称为信号未决

(3) 信号递达

信号被进程处理,我们称为信号递达

(4) 信号忽略

进程收到信号后直接忽略,不进行处理,是否还有默认处理呢?(忽略中断信号,验证是否会中断)

(5) 阻塞信号集

指一个进程中当前阻塞而不能够递送给该进程的信号集

(6) 未决信号集

指当前进程未处理的信号集,收到阻塞的信号,未决信号集该信号位会置1,解除阻塞后会置0。

2、信号保存的方式(内核级)

block表:对应的信号是否被阻塞,0表示不阻塞,1表示阻塞

pending表**:**信号是否被收到,0表示未收到该信号,1表示收到该信号

handler表:表示对应信号的处理方式(函数),存放的是函数的地址(函数指针)

3、信号从发送到被处理

第一列表示第 N 号信号,以第二行为例,阻塞信号为 1 ,说明该信号被阻塞了(相当于在进程的黑名单里),后面的未决信号是0还是1都无所谓了

以第四行为例,阻塞信号为0,该信号没有被阻塞,未决信号为1,说明收到这个信号了,对应的处理方式是 sighandler函数,那就会执行该函数

注意:

SIG_DFL(signal default):信号默认处理方式

SIG_IGN(signal ignore):忽略信号

总结

先看block,如果block为1,即信号被拉黑,是否收到信号都不重要了

如果block不为1,那就再看pending是否为1,即是否收到信号

二、信号集操作(信号保存)

阻塞信号其实就是要修改block表,但是要怎么修改呢?如果只是传递单个信号,或许可以直接以某种方式告诉OS我希望阻塞哪个信号,但是,如果要修改多个信号呢??OS给出了一种方案来解决传递单个或者多个信号,那就是直接传递位图!!

收到一个信号之后执行信号处理函数,在执行信号处理函数的过程中如果又来了一个相同的信号,那么这个信号将会被阻塞,直到信号处理函数执行完之后,再响应被阻塞的信号,注意如果信号被阻塞期间又收到了该信号,那么多个信号的处理会被合并为1次。

信号集就是用来记录当前收到了哪个信号,会把当前信号的标志位置成"正在处理",如果此时再收到该信号,那么信号就阻塞等待,使用数据类型sigset_t表示信号集,在Linux中该类型是一个32位无符号整数,这是因为在Linux中定义了32种信号,每一个信号用32位无符号整型变量中的一位来标志,如果该位置为1,那么表示正在处理该信号,如果置为0表示可以处理该信号。

未决信号集保存当前为处理的信号集。

信号屏蔽字又叫阻塞信号集,是指一个进程中当前阻塞而不能够递送给该进程的信号集。每个进程都有一个信号屏蔽字,它规定了当前要阻塞递达到该进程的信号集

1、特定的数据类型sigset_t

既然要传递位图,要用什么来表示位图呢?第一想法是int/uint32_t,不同的操作系统,实现位图的方式可能有所不同,Linux操作系统不光给我们提供了 保存位图的数据类型 sigset_t,还有对应的函数来操作位图(下面说)

------》sigset_t 可以看作是一个信号集,保存着1~31号信号的状态,0表示没收到,1表示收到了

2、操作信号集的函数

(1) sigemptyset函数,清空信号集,全部位置置0。GUN C库中的非标准函数

int sigemptyset ( sigset_t *set);

成功返回0,失败返回-1

(2) sigfillset函数:初始化信号集,全部置1。GUN C库中的非标准函数

int sigfillset( sigset_t *set);

成功返回0,失败返回-1

(3) sigaddset函数:向信号集中添加一个信号,将对应信号置1。标准函数

假设一开始所有的比特位都是 0(0000 0000 ...),如果我们希望把第二个信号阻塞,那么就需要先把信号集的第二个比特位设置为1(0100 0000....),然后再传给信号阻塞函数

int sigaddset( sigset_t *set, int signum);

第一个参数是传入信号集的地址,第二个参数是要把哪个位置(信号)设置成1,成功返回0,失败返回-1

(4) sigdelset函数:从信号集中删除一个信号,将对应信号置0。标准函数

(假设前面已经阻塞了2号信号)这个时候的比特位显示为0100 0000....,现在我们不希望阻塞2号信号,那么需要把第二个位置(信号)设置为 0 (0000 0000....)

int sigdelset( sigset_t *set, int signum);

第一个参数是传入信号集的地址,第二个参数是要把哪个位置(信号)设置成1,成功返回0,失败返回-1

(5) sigismember函数:判断某个信号是否为信号集的成员(判断是否在信号集中)。标准函数

如果对应位置(信号)的比特位为1,说明在信号集中,如果为0,说明不在信号集中

int sigismember( sigset_t *set, int signum);

成功返回1,失败返回0

如果使用sigemptyset()初始化信号集(信号集的所有位置0),此时可以接收到所有的信号

如果使用sigfillset()初始化信号集(信号集的所有位置1),此时屏蔽所有信号,当然SIGKILL和SIGQUIT信号是不能屏蔽的

sigaddset()和sigdelset()的意义在于指定屏蔽某信号或者接收某信号

int sigprocmask (int how, const sigset_t *restrict set, sigset_t *restrict oset);

how取值有下列三个:

SIG_BLOCK :set中包含的是希望阻塞的附加信号,合并set中的信号集

SIG_UNBLOCK:set中包含的是希望解除阻塞的信号,删除set中的信号集

SIG_SETMASK:set中包含的是现有屏蔽字的代替值,替换set中的信号集

若oset是非空指针,则返回进程的当前信号屏蔽字。

若set为空,则进程信号屏蔽字不变,how值无意义。

另外,注意一点,不可以阻塞SIGKILL和SIGSTOP信号。

注意:调用该接口将信号设置为非阻塞时,若该信号已经是阻塞状态,则在该函数返回前马上会发给该线程

int sigpending ( sigset_t *set );

此函数通过set返回当前检查未决的信号集。其中的各个信号对于调用进程是阻塞的而不能递送 ,因而也一定是当前未决的。

signal 函数的使用方法简单,但并不属于 POSIX 标准,在各类 UNIX 平台上的实现不尽相同,因此其用途受到了一定的限制。而 POSIX 标准定义的信号处理接口是 sigaction 函数。

int sigaction ( int signum, const struct sigaction *act, struct sigaction *oldact ) ;

signum:要操作的信号。

act:要设置的对信号的新处理方式。

oldact:原来对信号的处理方式。

返回值:0 表示成功,-1 表示有错误发生。

struct sigaction

{

void (*sa_handler)(int);

void (*sa_sigaction)(int, siginfo_t *, void*);

sigset_t sa_mask;

int sa_flags;

void (*sa_restorer)(void);

};

这个结构体中,成员 sa_handler 是一个函数指针,其含义与 signal 函数中的信号处理函数类似。成员sa_sigaction 则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。

sa_mask 用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。

sa_flags 用于指定信号处理的行为,它可以是一下值的"按位或"组合。

◆ SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用

◆ SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。

◆ SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。

◆ SA_NODEFER:一般情况下,当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记,那么在该信号处理函数运行时,内核将不会阻塞该信号。

◆ SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL。

◆ SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

re_restorer 是一个已经废弃的数据域,不要使用。

可重入函数

一个函数被重入,表示这个函数没有执行完成,由于外部因素或内部调用,又一次进入该函数执行。一个函数要被重入,只有两种情况:

1、多线程同时执行这个函数

2、函数自身(可能经过多层调用之后)调用自身

一个函数可重入,表示这个函数被重入后不会产生任何不良后果。要成人可重入函数必须有以下特定:

1、不使用任何(局部)静态或全局的非const变量

2、不返回任何(局部)静态或全局的非const变量的指针

3、依赖调用方提供参数

4、不依赖任何单个资源的锁

5、不调用任何不可重入的函数

相关推荐
鲁正杰33 分钟前
在一个服务器上抓取 Docker 镜像并在另一个服务器上运行
运维·服务器·docker
F-2H1 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
aherhuo1 小时前
基于openEuler22.09部署OpenStack Yoga云平台(一)
linux·运维·服务器·openstack
WebDeveloper20011 小时前
如何使用美国域名中心US Domain Center和WordPress创建商业网站
运维·服务器·css·网络·html
檀越剑指大厂1 小时前
【Linux系列】Shell 脚本中的条件判断:`[ ]`与`[[ ]]`的比较
linux·运维·服务器
2301_819287124 小时前
ce第六次作业
linux·运维·服务器·网络
武汉联从信息4 小时前
如何使用linux日志管理工具来管理oracle osb服务器日志文件?
linux·运维·服务器
天天进步20154 小时前
STUN服务器实现NAT穿透
运维·服务器
月如琉璃4 小时前
1.gitlab 服务器搭建流程
服务器·gitlab
Kika写代码4 小时前
【微信小程序】页面跳转基础 | 我的咖啡店-综合实训
服务器·微信小程序·小程序