Linux下信号--阻塞信号

这篇博客http://blog.csdn.net/l_xrui/article/details/72885978讲了信号的基本概念与产生方式。

了解以下三种概念:

信号递达(Delivery):实际执行信号的处理动作(三种);

信号未决(Pending):信号从产生到递达之间的状态;

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

注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。


在每个进程的task_struct中,操作系统为每个进程提供一套信号机制:


以上可知分别有三个表:阻塞(block)表,未决(pending)表,递达表

block表与pending表在task_struct中其是以相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型变量对应每一bit位可以表示对应每个信号的“有效(1)”或“无效(0)”状态,在阻塞信号集(block)中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信是否处于未决状态;

递达表则是以数组来表示,数组下标表示哪一个信号,数组里内容为函数指针,指向对应信号的递达动作。

每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志,若该信号被阻塞,信号产生时,一直处在未决状态,直至信号被取消阻塞。

如果在进程解除对某信号的阻塞之前这种信号产生过多次,Linux处理机制是:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里记录。

由此也可描述上图:

(1).  1号信号未被阻塞也未产生,它的递达动作为忽略;

(2).  2号信号被阻塞并已产生,所以一直在未决状态,它的递达动作为自定义处理动作,当其被取消阻塞,则会在适当时间执行其递达动作;

(3).  3号信号被阻塞但未产生,其递达动作为默认,但当它产生因被阻塞也会一直在阻塞状态。


由上一篇博客可知指针非法操作进程异常退出可这样描述:

在进程运行中,非法指针地址通过页表映射时不可映射访问被MMU发现,此时操作系统发现MMU出错,则查找出错误原因发送正确信号(SIGSEGV)11号信号给进程,即改变进程pending表的11位改为有效状态(1),然后进程发现自己pending表发生改变,接收到信号,其再去查询block表11号信号是否被阻塞,没有被阻塞,则信号递达,信号pending表恢复无效(0),进程就去找到hander表保存的对应的函数地址,去执行了默认动作,最后退出。


信号集操作函数:

使用者只能调用以下函数来操作sigset_t变量:

#include <signal.h>
int sigemptyset(sigset_t *set);   //初始化set所指向的信号集,使其中所有信号的对应bit清零
int sigfillset(sigset_t *set);  //初始化set所指向的信号集,使其中所有信号的对应bit置有效1位
int sigaddset(sigset_t *set, int signo); //向信号集set中添加使signo位信号有效
int sigdelset(sigset_t *set, int signo);  //向信号集set中删除使signo位信号无效
int sigismember(const sigset_t *set, int signo);  //判断信号集set的有效信号中是否包含signo信号,若包含则返回1,不包含则返回0,出错返回-1

调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集):

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
参数:

oset:若set是空指针,oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出;

         若oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字;

set:若set是非空指针,则更改进程的信号屏蔽字;

how:如何修改,可选值如下:

1. SIG_BLOCK  //将set信号集中的有效信号添加到当前阻塞表中阻塞它们
2. SIG_UNBLOCK  //将set信号集中的有效信号从当前阻塞表中删除对它们取消阻塞
3.SIG_SETMASK  //将当前阻塞表清空只将set信号集中的有效信号添加进去阻塞它们
返回值:若成功则为0,若出错则为-1

注意:如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。


函数:

int sigpending(sigset_t *set);  //sigpending读取当前进程的未决信号集,通过set参数传出
返回值:调用成功则返回0,出错则返回-1


以下用代码验证三张表以上关系:

sigblock.c:此处函数实现功能以2号信号(Ctrl-C)举例:

(1)设置信号集将2号信号加入信号集中,并调用函数sigprocmask()将其阻塞,并为2号信号设自定义信号捕捉函数(打印当前进程pid与接收到的信号sig);

(2)设一个计数器count,进入无限循环,每隔一秒打印一次block表与pending表,并使count++;

(3)在无限循环中,当count==10时,再调用函数sigprocmask()对2号信号取消阻塞。

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

//信号递达动作 
void myhander(int sig)
{
	printf("pid## %d receiving sig## %d\n",getpid(),sig);
}
// 打印pending表(信号未决) 
void PrintPending(sigset_t set)
{
	int i=1;
	for(;i<32;++i)
	{
		if(sigismember(&set,i))
		{
			printf("1 ");
		}
		else
		{
			printf("0 ");
		}
	}
	printf("\n");
}
int main()
{
	//定义信号集 
	sigset_t s;
	//初始化信号集为0 
	sigemptyset(&s);
	//将2号信号加入信号集中:ctrl c==2
	sigaddset(&s,2);

	sigset_t oldset;    //以保存block表中旧的信号屏蔽字 
	//sigprocmask(SIG_BLOCK,&s,&oldset);     //将S信号集中信号加入block(阻塞表)中 :此处阻塞了2号信号 
	sigprocmask(SIG_SETMASK,&s,&oldset);     //将block表中阻塞信号清空只将S信号集中的信号屏蔽(设为S的值) 
	
	signal(2,myhander);   //给2号信号设捕捉函数 
	int count=0;
	while(1)
	{
		//获取当前阻塞(block)表,并打印 
		sigset_t s1;
		sigprocmask(0,NULL,&s1);
		printf("block list:");
		PrintPending(s1);
		
		//定义 pending表 
		sigset_t p;
		sigpending(&p);   //获取当前进程的pending表(即进程收到哪些信号并处在未决状态) 
		printf("pend  list:");
		PrintPending(p);   //打印查看pending表 

		if(count==10)
		{
			//sigprocmask(SIG_UNBLOCK,&s,&oldset);   //从block表中删除信号集S中的信号,即取消它们的阻塞 
			sigprocmask(SIG_SETMASK,&oldset,&s);    //此代码中以此可实现同样效果 :以上oldset保存的为全0 
		}
		sleep(1);

		++count;
	}

	return 0;
}
效果如下:

(1)在前10秒count<=10时,2号信号被阻塞,此时block表与pending表依次为:

0 1 0 0 0 0 0 0 0 0.........

0 0 0 0 0 0 0 0 0 0.........

在前10秒因为2号信号被阻塞,若这时键盘发送Ctrl-C,2号信号不会递达(不执行自定义捕捉函数),其会先保持在未决状态,此时block表与pending表依次为:

0 1 0 0 0 0 0 0 0 0.........

0 1 0 0 0 0 0 0 0 0.........

(2)当刚过10秒,count==10,2号信号被取消阻塞,这时(1)刚才发送的2号信号递达,则执行自定义捕捉函数,并且block表与pending表(2号信号不在未决状态恢复为0)依次变为:

0 0 0 0 0 0 0 0 0 0.........

0 0 0 0 0 0 0 0 0 0.........

(3)在以后,2号信号再没被阻塞,此时在发送则执行自定义捕捉函数,并且block表与pending表保持为:

0 0 0 0 0 0 0 0 0 0.........

0 0 0 0 0 0 0 0 0 0.........











你可能感兴趣的:(Linux下信号--阻塞信号)