欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux中关于信号的一些知识

程序员文章站 2022-07-12 10:30:09
...

 一.什仫是信号?

  信号其实是一种软件中断,它为程序提供了一种处理异步时间的方法,而所谓的异步时间就是时间可能会在任何时间内发生,很多重要的程序都需要对信号进行处理。可以使用kill -l查看系统中所有的信号列表以及他们的信号编号。

  Linux中关于信号的一些知识

     我们把编号为1~31的信号叫做普通信号,把34~64的信号称作实时信号。所有的信号都包含在头文件signal.h中,且都被定义为正整数常量,也就是他们的信号编号。

二.信号的产生方式?

    在Linux下信号一般通过以下几种方式产生:

    1).信号来自键盘。用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,但是该信号只能使前台进程终止。比如ctrl+c发送SIGINT信号,ctrl+\发送SIGQUIT信号,ctrl+z发送SIGTSTP信号。

2).软硬件异常。例如除数为0、无效的内存引用对应的SIGSEGV信号,硬件异常产生信号不同,一旦硬件异常产生 ,那么他会一直存在,直到程序被终止位置,所以处理硬件异常信号一般都采用终止程序的方法。

3).经过系统调用向进程发送信号,进程可以通过在shell下运行kill指令来对某个进程发送信号,kill指令是kill系统调用的一个接口。

4).由软件条件产生异常.

 三.信号的处理方式?

1).忽略此信号。大多数信号都可以采用这种方式进行处理,除了9号信号SIGKILL 和19号信号SIGSTOP 。因为这两种信号都直接向内核提供了进程终止和停止的可靠办法。(SIGKILL),还有硬件异常信号我们最好不要忽略,因为硬件异常一旦产生如果不进行处理就会一直存在。

2).执行默认动作,一般是终止该进程。

3).提供一个信号处理函数,捕捉该信号,属于用户自定义动作。一般使用的是signal函数。

四.阻塞信号?

1.在这里就要提到几个概念:

1).信号递达:实际执行信号的处理动作 。

2).信号未决:pending,表示信号从产生到信号递达的状态。 

3).信号阻塞:block,表示信号不能被递达,如果一个信号被阻塞,那么这个信号将会一直处于未决状态,直到解除阻塞为止。 

在Linux系统中,信号未决和信号阻塞的状态都被组织在两张位图中的,即位置表示信号编号,如果在pending表中该位置为1则表示被pending为0表示尚未被pending,如果在block表中则表示是否被block。
2.信号在内核中的表示示意图?
每个进程中操作系统都会为其分配一整套的block表,pending表以及handler表。
Linux中关于信号的一些知识

每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上述例子中可以发现:

1). SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。

2). SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
3). SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。

3.内核是如何处理信号的?
Linux中关于信号的一些知识
从图中我们能发现,当一个正在运行的进程收到了中断或者是调用了系统调用,则该进程会从用户态进入到内核态,当进程准备从内核态返回到用户态时,内核会检查要返回的进程的pcb中的signal位图信息,如果当前的pengding表中有标志1,那么内核会把pengding链表中悬挂的信号拿出来进程处理,处理的过程如下:如果handler指向了用户自定义的处理函数,那么会先从内核态返回到用户态执行完处理函数后再返回到内核态,最后再从内核态返回到用户态中因为中断或者系统调用进入内核态的代码从而继续执行。
4.信号集操作函数
       #include <signal.h>
       int sigemptyset(sigset_t *set);   //初始化set所指向的信号集,使set包含所有信号
       int sigfillset(sigset_t *set);        //初始化set所指向的信号集,使其中所有信号的对
应bit置位,表示该信号集的有效信号包括系统支持的所有信号
       int sigaddset(sigset_t *set, int signum);   //将一个信号添加到已经存在的信号集中
       int sigdelset(sigset_t *set, int signum);   //从已有信号集中删除一个信号
       int sigismember(const sigset_t *set, int signum);  //测试信号集是否包含signum信号


我们常常通过信号集处理函数来对信号集中的pending表,block位图还有hanlder表进行操作。
5.sigprocmask
该函数用来规定当前阻塞而不能递达给进程的信号集。调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。
       #include <signal.h>
       int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

oldset:进程的当前信号屏蔽字通过oldset返回。 
sigprocmask:仅为单线程进程进行定义的,在处理多线程时应该使用sigaction函数。 
how:指示如何修改当前的信号屏蔽字,可以选择下面三个参数:
 Linux中关于信号的一些知识

五.一个简单的例子信号的发送细节
刚开始时1~31个信号全为0,当发送ctrl+c的时候2号信号的位置置为1,当经过一段时间之后又更新为0,可以再次发送ctrl+c验证。
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void showpending(sigset_t *pending)
{
	int i=0;
	for(i=1;i<=31;i++)
	{
		if(sigismember(pending,i))
			printf("1 ");
		else
			printf("0 ");
	}
	printf("\n");
}

void handler(int sign)
{
	printf("pid is %d,sign is %d\n",getpid(),sign);
	return;
}

int main()
{
	sigset_t sigset,osigset;  //定义信号集对象
	sigemptyset(&sigset);     //清空初始化
	sigemptyset(&osigset);    
	sigaddset(&sigset,SIGINT);    //添加2号信号到已经存在的信号集中
	sigprocmask(SIG_SETMASK,&sigset,&osigset); //读取或更改信号的信号屏蔽字

	signal(2,handler);  //对2号信号进行自定义的信号处理操作

	int count=0;
	sigset_t pending;
	while(1)
	{
		sigpending(&pending);  //获取未决信号
		showpending(&pending); //打印pending表
		sleep(1);
		if(count++ > 3)
		{
			sigprocmask(SIG_SETMASK,&osigset,&sigset);  //count秒之后清空pending表
			count=0;
		}
	}
	return 0;
}

Linux中关于信号的一些知识