信号(三)——阻塞(信号屏蔽)与未决、sigset_t信号集、sigprocmask 和 sigpending 函数——linux系统编程

信号在内核中的表示:递达(Delivery)、未决(Pending)、阻塞(Block)

  • 递达(Delivery):执行信号的动作
  • 未决(Pending):被阻塞的信号处在的状态,信号从产生到递达之间的状态
  • 阻塞(Block):可以理解为屏蔽信号,一个信号可以若被阻塞,它将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。

注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:
在这里插入图片描述
图中:

  • block集(阻塞集、屏蔽集):一个进程所要屏蔽的信号,在对应要屏蔽的信号位置1
  • pending集(未决信号集):如果某个信号在进程的阻塞集之中,则也在未决集中对应位置1,表示该信号不能被递达,不会被处理
  • handler(信号处理函数集):表示每个信号所对应的信号处理函数,当信号不在未决集中时,将被调用
    每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,
  1. SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。

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

  3. SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。

sigset_t信号集、信号集操作函数

未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

阻塞信号集:人为赋值,要屏蔽的信号
未决信号集:没有处理的信号的集合
阻塞信号集和未决信号集在类型上其实是一个东西,不过一个是输入(阻塞)一个是取出(未决)故分成两个名字

信号集操作函数

#include <signal.h> 

 int sigemptyset(sigset_t *set);/* Clear all signals from SET.  清空信号集*/
  int sigfillset(sigset_t *set);// 把所有信号的对应bit置位=把64种信号都放入信号集(没有32 33)   /* Set all signals in SET. */ 
  int sigaddset(sigset_t *set, int signo);// 增加某个指定信号 
  int sigdelset(sigset_t *set, int signo);//删除某个指定信号 
  int sigismember(const sigset_t *set, int signo);// 查看信号是否在集合里面 /* Return 1 if SIGNO is in SET, 0 if not. */

注意,在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。上面四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。

sigprocmask函数

man:

NAME
       sigprocmask, rt_sigprocmask - examine and change blocked signals
SYNOPSIS
       #include <signal.h>
       int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       sigprocmask(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

DESCRIPTION
       sigprocmask()  is  used to fetch and/or change the signal mask of the calling thread.  The signal mask is the set of signals whose delivery is currently blocked for
       the caller (see also signal(7) for more details).

       The behavior of the call is dependent on the value of how, as follows.

       SIG_BLOCK
              The set of blocked signals is the union of the current set and the set argument.

       SIG_UNBLOCK
              The signals in set are removed from the current set of blocked signals.  It is permissible to attempt to unblock a signal which is not blocked.

       SIG_SETMASK
              The set of blocked signals is set to the argument set.

       If oldset is non-NULL, the previous value of the signal mask is stored in oldset.

       If set is NULL, then the signal mask is unchanged (i.e., how is ignored), but the current value of the signal mask is nevertheless returned in oldset (if it is  not
       NULL).

       The use of sigprocmask() is unspecified in a multithreaded process; see pthread_sigmask(3).

RETURN VALUE
       sigprocmask() returns 0 on success and -1 on error.  In the event of an error, errno is set to indicate the cause.

  • #include <signal.h>
  • int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • 功能:读取或更改进程的信号屏蔽字。 (sigset_t)
  • 返回值:若成功则为0,若出错则为-1
  • 如果oldset是非空指针,则读取进程的当前信号屏蔽字通过oldset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oldset和set都是非空指针,则先将原来的信号屏蔽字备份到oldset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

在这里插入图片描述

sigpending函数

man:

NAME
       sigpending, rt_sigpending - examine pending signals

SYNOPSIS
       #include <signal.h>

       int sigpending(sigset_t *set);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       sigpending(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

DESCRIPTION
       sigpending()  returns  the set of signals that are pending for delivery to the calling thread (i.e., the signals which have been raised while blocked).  The mask of
       pending signals is returned in set.

RETURN VALUE
       sigpending() returns 0 on success and -1 on error.  In the event of an error, errno is set to indicate the cause.

ERRORS
       EFAULT set points to memory which is not a valid part of the process address space.

  • #include <signal.h>
  • int sigpending(sigset_t *set);
  • 功能: sigpending读取当前进程的未决信号集,也就是获得当前已递送到进程,却被阻塞的所有信号,通过set参数传出。
  • 返回值:若成功则为0,若出错则为-1

代码实例:

sigprocmask

代码功能:安装一个信号SIGUSR1与信号处理函数signal_handle_new关联,signal_handle_new中执行打印"signal_handle_new is running"。用sigprocmask函数,将sigprocmask当前所在进程的mask中加入要阻塞(屏蔽)的信号SIGUSR1,用一个死循环使当前进程不断运行不结束。由SSH终端手动发送信号kill -10 进程号,观察是否会进入信号处理函数打印"signal_handle_new is running" ,若没有打印,说明SIGUSR1信号在当前进程中确实被屏蔽了

#include <iostream>
#include<unistd.h>//unix stand lib
#include<sys/types.h>
#include<sys/fcntl.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<dirent.h>//file dir
#include <sys/wait.h>//wait func
#include <stdlib.h>//ststem
#include <signal.h>

using namespace std;


//新的信号处理函数多了两个参数,其中signfo_t里面携带了我们想要的数据
void signal_handle_new(int num, siginfo_t *info, void *d)
{
	cout << "signal_handle_new is running" << endl;
}

int main(int argc, char *argv[])
{
	//安装信号
	struct sigaction sig_act;//新建信号安装结构体
	sig_act.sa_sigaction = signal_handle_new;//指定信号关联函数
	sig_act.sa_flags = SA_SIGINFO;//声明信号是携带数据的
	//将信号和安装信号结构体关联
	sigaction(SIGUSR1, &sig_act,NULL);//信号num,新信号结构体指针,旧信号结构体指针

	
	//现在我们想屏蔽SIGUSR1信号,程序运行起来后,我们可以通过kill -10 pid 命令,手动给它发信号
	sigset_t sig_array;
	sigemptyset(&sig_array);//初始化,清空信号集
	sigaddset(&sig_array, SIGUSR1);//阻塞信号集中加入要屏蔽(阻塞)的信号SIGUSR1
	
	//把SIGUSR1的信号屏蔽,SIG_BLOCK是向mask中添加屏蔽的信号集
	if (sigprocmask(SIG_BLOCK, &sig_array, NULL) < 0)
	{
		perror("sigpromask error:");
		return -1;
	}

	while (1)
	{
		cout << "process run..." <<getpid()<< endl;
		sleep(1);
	}
	
	return 0;
}

在这里插入图片描述
我们在SSH发送信号,没有得到打印,说明确实SIGUSR1信号被屏蔽了

sigprocmask+sigpending+sigismember

接下来我们试图用sigpending获取被屏蔽的信号是什么,并并用sigismember判断被屏蔽的信号是不是SIGUSR1,我们在sigpromask之后加入一些代码,为了在sigpending获取之前给我们发送信号留够时间,我们sleep(20)
在20s内手动kill发送信号,之后若打印了SIGUSR1 is pending…说明确实获取到了未决信号集,并判断出了未决信号就是SIGUSR1

#include <iostream>
#include<unistd.h>//unix stand lib
#include<sys/types.h>
#include<sys/fcntl.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<dirent.h>//file dir
#include <sys/wait.h>//wait func
#include <stdlib.h>//ststem
#include <signal.h>

using namespace std;


//新的信号处理函数多了两个参数,其中signfo_t里面携带了我们想要的数据
void signal_handle_new(int num, siginfo_t *info, void *d)
{
	cout << "signal_handle_new is running" << endl;
}

int main(int argc, char *argv[])
{
	//安装信号
	struct sigaction sig_act;//新建信号安装结构体
	sig_act.sa_sigaction = signal_handle_new;//指定信号关联函数
	//将信号和安装信号结构体关联
	sigaction(SIGUSR1, &sig_act,NULL);//信号num,新信号结构体指针,旧信号结构体指针

	
	//现在我们想屏蔽SIGUSR1信号,程序运行起来后,我们可以通过kill -10 pid 命令,手动给它发信号
	sigset_t sig_array;
	sigemptyset(&sig_array);//初始化,清空信号集
	sigaddset(&sig_array, SIGUSR1);//阻塞信号集中加入要屏蔽(阻塞)的信号SIGUSR1
	
	//把SIGUSR1的信号屏蔽,SIG_BLOCK是向mask中添加屏蔽的信号集
	if (sigprocmask(SIG_BLOCK, &sig_array, NULL) < 0)
	{
		perror("sigpromask error:");
		return -1;
	}
	
	cout << "process run..." << getpid() << endl;//获取要kill的进程号
	cout << "sleep 20" << endl;
	sleep(20);//在20s内手动kill发送信号SIGUSR1
	//sigpending来检测有没有发送但被屏蔽(阻塞)的信号
	if (sigpending(&sig_array) == 0)//读取当前的未决信号集,保存到sig_array中
	{
		if (sigismember(&sig_array, SIGUSR1))//判断未决信号集中是否有SIGUSR1
		{
			cout << "SIGUSR1 is pending...\n" << endl;
		}
		
	}
	while (1)
	{
		cout << "process run..." <<getpid()<< endl;
		sleep(1);
	}
	return 0;
}

在这里插入图片描述
结果可见,未决信号-10被识别出来了,判断成功

设置信号处理函数不被指定(不同种)信号干扰(打断)

上一篇文章中我们讲到

  • 如果linux执行一个信号处理函数的时候如果又收到一个不同种信号,会去执行新的信号处理函数,执行完之后再回来执行。
    如果我们需要保证一个信号处理函数在执行过程中,不被指定的(不同种)信号干扰(打断)而去执行其他信号处理函数,应该怎么办呢?
    记得我们在上一篇中提到一个信号处理函数的sigaction 结构体中,sa_mask用于存放在当前信号处理函数执行过程中,需要手动屏蔽的信号,这里我们应该把它用起来
    我们假设希望在signal_handle_new函数执行的过程中,不被SIGUSR2信号干扰,于是如下编码:
#include <iostream>
#include<unistd.h>//unix stand lib
#include<sys/types.h>
#include<sys/fcntl.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<dirent.h>//file dir
#include <sys/wait.h>//wait func
#include <stdlib.h>//ststem
#include <signal.h>

using namespace std;


//新的信号处理函数多了两个参数,其中signfo_t里面携带了我们想要的数据
void signal_handle_new(int num, siginfo_t *info, void *d)
{
	for (int i = 0; i < 10; i++)
	{
		cout << "signal_handle_new is running" <<i<< endl;
		sleep(1);
	}
}

void signal_handle_new2(int num, siginfo_t *info, void *d)
{
	cout << "signal_handle_new2 is running" << endl;
}

int main(int argc, char *argv[])
{
	//安装信号
	struct sigaction sig_act, sig_act2;//新建信号安装结构体
	sig_act.sa_sigaction = signal_handle_new;//指定信号关联函数
	sig_act2.sa_sigaction = signal_handle_new2;//指定信号关联函数
	
	//希望在signal_handle_new函数执行的过程中,不被SIGUSR2信号干扰
	sigset_t sig_array;
	sigemptyset(&sig_array);//初始化,清空信号集
	sigaddset(&sig_array, SIGUSR2);//阻塞信号集中加入要屏蔽(阻塞)的信号SIGUSR2
	
	//=======================
	//把signal_handle_new函数的sigaction结构体的mask复制为指定sig_array,使得signal_handle_new执行的过程中,不被SIGUSR2信号干扰
	sig_act.sa_mask = sig_array;//sa_mask用于存放在当前信号处理函数执行过程中,需要手动屏蔽的信号
	//=======================
	
	//将信号和安装信号结构体关联
	sigaction(SIGUSR1, &sig_act, NULL);//信号num,新信号结构体指针,旧信号结构体指针
	sigaction(SIGUSR2, &sig_act2, NULL);//信号num,新信号结构体指针,旧信号结构体指针
	
	while (1)
	{
		cout << "process run..." <<getpid()<< endl;
		sleep(1);
	}
	return 0;
}

在这里插入图片描述
我们在signal_handle_new执行的10s内发送12信号,但没有得到打印,直到signal_handle_new执行完之后才排队得到执行,对比前一篇文章的结果,可见确实实现了在信号处理函数执行过程中对指定信号的屏蔽。

本文参考:
https://www.cnblogs.com/mickole/p/3191281.html
https://blog.csdn.net/jnu_simba/article/details/8944982

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值