当前位置:网站首页>信号的处理与捕捉

信号的处理与捕捉

2022-06-11 18:14:00 HHYX.

信号处理

信号的产生

一般而言,Linux系统下信号产生有如下四种方式:

  1. 键盘输入产生
  2. 进程异常产生信号
  3. 系统调用接口,如(kill,raise等)
  4. 软件条件(管道,alarm等)

信号的存放

由于信号产生是异步的,信号可能随时会产生,但是操作系统并不能及时的去处理,这就需要要求进程有保存信号的能力。上一篇信号博客中讲解了进程存放信号的pending位图结构,阻塞状态位图以及handler函数指针数组的相关内容,这里就不多赘述了。信号的处理正是对这些内容进行操作。

信号集

sigset_t类型是一种数据类型,他也被叫做信号集,用来表示每个信号有效或者无效的状态。从pending和block表的位图结构来看,每个信号只有一个bit位的标志,非0即1.因此使用这个信号集类型在block表中可以表示该信号是否被阻塞,而在pending表中则表示该信号是否处于未决状态。

信号集操作函数

信号集并不是一个整型或者其他常见类型的数据类型,他对于每种信号用一个bit位表示有效或者无效状态,但是这个类型内部具体如何实现的则依赖于操作系统。因此使用者不能直接对信号集进行操作,需要用到以下的接口函数。

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo); 

这里可以看到这些信号集操作函数都包含在signal.h的头文件中。其中依次来介绍这些函数的功能。
sigemptyset这个函数是将信号集进行初始化,使其内部所有bit位清零,不包含任何有效信号。
sigfillset这个函数也是对信号集进行初始化,但是与上面那个相反,它是将所有的bit置为有效。该信号集的有效信号包括系统支持的所有信号。
需要注意的是,在使用sigset_ t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
sigaddset这个函数正是在信号集初始化后将某个特定的信号位置为有效,只要传入对应的信号编号即可
sigdelset这个函数是在信号集初始化后将某个特定的信号位置置为无效,只要传入对应的信号编号即可。
sigismember这个函数是一个bool类型的函数,用于判断一个信号集的有效信号中是否包含指定的信号,如果包含则返回1,不包含则返回0,出错返回-1。

sigprocmask

在这里插入图片描述
这个函数是用来读取或者更改进程的信号屏蔽字也就是block表中的内容的。成功则返回0失败则返回-1.
这个函数中有3个参数,下面依次介绍一下这三个参数的作用:
先介绍后面两个参数在介绍第一个。
set参数是一个输入型参数,他是一个sigset_t类型的参数,也就是一个信号集类型,他与第一个参数配合来设置具体的信号屏蔽字段。
oldset参数是一个输出型参数,用来返回老的信号屏蔽字block内容,如果不关心可以设置为空。
其中第一个参数是需要传入修改block表的方法,一般常用的有以下3种:
SIG_BLOCK:使用这个选项后set需要包含希望添加到当前信号屏蔽字的信号,相当于mask = mask | set
SIG_UNBLOCK:使用这个选项后set需要包含需要从当前信号屏蔽字中解除阻塞的信号,相当于 mask = mask &~set。
SIG_SETMASK:设置当前信号屏蔽字为set所指向的值,相当于mask = set。
测试代码如下:

int main()
{
    
  sigset_t iset;
  sigset_t oset;
  sigemptyset(&iset);
  sigemptyset(&oset);
  sigaddset(&iset,2);
  sigprocmask(SIG_SETMASK,&iset,&oset);//阻塞二号信号
  while(1)
  {
    
    printf("hello world\n");
    sleep(1);
  }
  return 0;
}

在这里插入图片描述
这里对2号信号进行了阻塞设置,使得2号没法无法被递达。

sigpending

在这里插入图片描述
该函数的作用是读取当前进程的pending信号集,通过set参数返回,这里的set也是一个输出型参数,调用成功则返回0,否则返回-1。对于pending表,不需要对他进行修改,只需要修改block表即可控制信号的递达,pending仅仅代表是否收到该信号,只需要进行查看即可。测试代码如下:

int main()
{
    
  sigset_t iset;
  sigset_t oset;
  sigemptyset(&iset);
  sigemptyset(&oset);
  sigaddset(&iset,2);
  sigprocmask(SIG_SETMASK,&iset,&oset);//阻塞二号信号
  sigset_t pending;
  sigemptyset(&pending);
  while(1)
  {
    
    printf("hello world\n");
    sigpending(&pending);
    if(sigismember(&pending,2))
    {
    
      printf("收到2号信号\n");
    }
    else 
    {
    
      printf("未接受2号信号\n");
    }
    sleep(1);
  }
  return 0;
}

在这里插入图片描述
这里可以看到是否接收到了2号信号,但是由于2号信号处于信号屏蔽字中,因此不会被递达。

信号的捕捉

信号处理方案一般有如下3种:默认、忽略以及自定义

signal

在这里插入图片描述
这里信号捕捉的方式上一篇博客已经介绍过了,这里也就不多介绍了,主要介绍一下另外一种新的信号捕捉方式

sigaction

在这里插入图片描述
这个函数和signal功能类似都是实现对信号的捕捉,修改的是handler函数指针数组。
其中第一个参数是输入需要捕捉的信号的编号
第二个参数是一个结构体,是一个输入型参数,这里结构体名称和函数名相同,将结构体中的处理方法改为自定义的就可以实现自定义信号处理方案。
第三个参数也是这个结构体,但是他是一个输出型参数,返回之前进程的结构体,这一点和上面的sigprocmask类似。

在这里插入图片描述
测试代码如下:

void handler(int signo)
{
    
  printf("get a signal :%d\n",signo);
}


int main()
{
    
  struct sigaction act;
  memset(&act,0,sizeof(act));
  act.sa_handler =handler;
  sigaction(2,&act,NULL);
  while(1)
  {
    
    printf("hello world\n");
    sleep(1);
  }
  return 0;
}

在这里插入图片描述

这里为止几乎和signal函数没什么区别,这里的handler可以直接使用宏。

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

void handler(int signo)
{
    
	while(1)
	{
    
	  printf("get a signal :%d\n",signo);
	  sleep(1);
	}

}


int main()
{
    
  struct sigaction act;
  memset(&act,0,sizeof(act));
  act.sa_handler =handler;
  sigemptyset(&act.sa_mask);
  sigaddset(&act.sa_mask,3);
  sigaction(2,&act,NULL);
  while(1)
  {
    
    printf("hello world\n");
    sleep(1);
  }
  return 0;
}

在这里插入图片描述
sa_mask作用于信号函数处理过程中进行信号屏蔽。

原网站

版权声明
本文为[HHYX.]所创,转载请带上原文链接,感谢
https://blog.csdn.net/h1091068389/article/details/125014699