【从浅学到熟知Linux】信号机制下篇=>信号集操作函数,信号捕捉,可重入函数、volatile关键字(含sigprocmask/sigpending、sigaction等详谈)

本文围绕Linux信号机制展开,介绍了信号的内核表示与阻塞,包括信号相关概念、内核表示及信号集操作函数。还详细阐述了信号捕捉、可重入函数、volatile关键字以及SIGCHLD信号处理等内容,帮助读者深入理解Linux系统编程中的信号机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

🏠关于专栏:Linux的浅学到熟知专栏用于记录Linux系统编程、网络编程等内容。
🎯每天努力一点点,技术变化看得见


信号的内核表示与信号阻塞

信号的相关概念

●信号递达:实际执行信号的处理动作。
●信号未决:信号从产生到递达之间的状态。

★ps:进程可以选择阻塞某个信号,被阻塞的信号产生时将保持在未决状态,直到进行解除对信号的阻塞,才会执行递达的动作。

★ps:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达时可选的一种处理动作。

信号在内核中的表示

系统中的信号是发送给进程的,对于进程而言,信号是给进程的PCB发的,PCB使用位图结构记录收到的信号,这样进程执行时就知道自己收到什么信号了。

信号为什么要保存呢?进程收到信号之后,可能不会立即处理。这个信号从产生到递达之间的时间窗口内,进程需要记录该信号已经产生了,等到处理的时候才能知道哪些信号已经发生了。

  1. 比特位的内容为1或0,表示是否收到某个信号
  2. 比特位的位置(第几个),表示信号的编号c
  3. 所谓的“发信号”,本质就是操作系统去修改task_struct的信号位图的对应比特位,发送信号其实就是写信号。

操作系统是进程的管理者,只有它才有资格去修改task_struct内部的属性,即操作系统需要提供相应的系统调用以实现信号的发送、阻塞等功能。

task_struct中针对信号,包含了2张位图和1张函数指针表,分别是block位图、pending位图、handler处理函数指针表↓↓↓

block位图记录某个信号是否被阻塞,如果某个信号对应的比特为1表示该信号被阻塞,为0表示没有阻塞;对于被阻塞的信号,即使该信号产生了,进程也不会对该信号做任何处理。

pending位图记录是否收到某个信号,如果收到某个信号,则会将对应的比特位置1;处理完某个信号后,会将对应的比特位置0。由于每个比特位只能表示信号的有无,若在信号产生到信号递达的时间窗口内,重复收到多个同样的信号,最终也只会递达一次。

handler函数指针数组用于记录对各个信号的处理方式。如果设置为SIG_DFL表示执行系统默认处理函数,设置为SIG_IGN表示忽略该信号;设置为用户空间的某个函数时,待信号递达时,则会从内核态切换回用户态以执行该部分代码。

在这里插入图片描述
对于每个信号都有两种标志位分别表示是否阻塞和是否已经处理,还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的pending表对应比特位为未决状态,即比特位置1,直到信号递达才清除该标志,即将对应比特位置0。

对于上图,penging表记录了当前进程收到了2号到6号信号,而1号信号并没有收到。block表记录了,当前进程对3号和5号信号进行了阻塞。

对于2号信号,penging表记录了该信号已经产生,block表记录了该信号没有被阻塞,故进程会执行该信号对应的handler函数指针数组中的操作,即系统的默认操作。

对于4号信号,pending表记录了该信号已经发送,block表记录了该信号没有被阻塞,故该进程会执行对应的操作,即对该信号进行忽略。

对于6号信号,pending表记录了该信号已经发生,block表记录了该信号没有被阻塞,故该进程会执行对应的操作,即指定用户指定的SigHandler函数,此时会发生内核态到用户态的转换。

5号SIGSTAP信号产生过,但正在被阻塞,暂时不能递达。虽然它的默认处理动作是忽略,但是没有解除阻塞之前,不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

3号SIGQUIT信号产生过,一旦解除对该信号的屏蔽,则它就会被递达,因为它的pending表对应比特位尚未被置0。一旦解除对该信号的屏蔽,则会执行SIG_DFL默认动作。

★ps:如果在进程解除对某信号的阻塞之前,这个信号产生过多次,将如何处理呢?POSIX.1允许系统递送该信号一次或多次,Linux是这样实现的:常规信号在递达之前产生多次只记录一次,而实时信号在递达之前产生多次可以依次放在队列里,这里对实时信号不做讨论。

信号集操作函数

从上面的讨论可知,对于阻塞信号集和pending信号集来说,都可以使用同一种类型来实现,只要能够标识某个信号是否被阻塞(或者是否已经发生)即可。故系统提供了一种统一的类型sigset_t来处理这两个位图结构。但为了保证该类型的易用性、可移植性等问题,故用户不能直接操作,而需要使用系统提供的接口。且若是sigset_t指针,指向PCB结构的sigset_t字段时,则不能直接对该结构进行位操作,因为只有操作系统有权限修改PCB的属性信息。↓↓↓
在这里插入图片描述

★ps:阻塞信号集(block位图)也就做当前进程的信号屏蔽字,这里的“屏蔽”应该理解为阻塞,而不是忽略。

上图各个系统调用接口的用法如下表所示↓↓↓

系统调用接口 用途
sigemptyset 将传入的set位图的各个位清零
sigfillset 将传入的set位图的各个位置1
sigaddset 将signum信号在set中对应的比特位置1
sigdelset 将signum信号在set中对应的比特位置0
sigismember 查看signum对应的比特位在set中是否为1,为1则返回1,否则返回0

除了sigismember外,其他的调用接口成功返回0,失败返回-1。

注意,在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后,可以调用sigaddset和sigdelset在该信号集中添加或删除某种信号。

★ps:使用时不应该对sigset_t的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的。

上述接口只是对sigset_t这个位图结构做操作,并未对block和pending位图直接做操作,下面介绍关于这两个位图的系统调用函数↓↓↓

sigprocmask

在这里插入图片描述
调用函数sigprocmask可以读取或修改进程的信号屏蔽字(阻塞信号集)。关于上述各个参数的设置,即how的可选数值如下标所示↓↓↓

how取值 用法及含义
SIG_BLOCK set包含我们希望添加到当前信号屏蔽字中的信号,相当于mask=mask|set
SIG_UNBLOCK set包含我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
SIG_SETMASK 设置当前信号屏蔽字为set所指向的值,相当于mask=set

第三个参数,在用户传入非空的sigset_t类型的变量时,则会返回执行当前sigprocmask之前的进程信号屏蔽字。

★ps:如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回之前,至少将其中一个信号递达(如果收到解除屏蔽的信号的前提下)。

下面代码演示了如何获取当前进程的信号屏蔽字↓↓↓

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

int main()
{
   
	sigset_t set;
	sigprocmask(SIG_BLOCK, NULL, &set);
	int i = 1;
	for(; i < NSIG; i++)
	{
   
		if(sigismember(&set, i))
		{
   
			printf("1");
		}
		else
		{
   
			printf("0");
		}
	}
	printf("\n");
	return 0;
}

在这里插入图片描述
★ps:NSIG的取值是最大的信号数+1。

下面代码演示使用sigprocmask设置1号到8号信号的信号屏蔽,后解除1到5号信号的信号屏蔽,最终将信号屏蔽字改为仅屏蔽11到15号信号↓↓↓

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

void PrintSig(sigset_t set)
{
   
	int i = 1;
	for(;i < NSIG; i++)
	{
   
		if(sigismember(&set, i)) printf("1");
		else printf("0");
	}
	printf("\n");
}

int main()
{
   
	sigset_t set;
	sigemptyset(&set);
	int i = 1;
	for(; i <= 8; i++)
	{
   
		sigaddset(&set, i);
	}
	sigset_t oldset;
	sigprocmask(SIG_BLOCK, &set, &oldset);
	printf("初始的信号屏蔽字:");
	PrintSig(oldset);

	sigemptyset(&set);
	i = 1;
	for(; i <= 5; i++)
	{
   
		sigaddset(&set, i);
	}
	sigprocmask(SIG_UNBLOCK, &set, &oldset);
	printf("屏蔽1到8号信号屏蔽字:")
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值