Linux系统编程之信号中断处理(下)

246 阅读8分钟

  信号集与信号阻塞集

  信号集

  为了方便对多个信号进行处理,一个用户进程常常需要对多个信号做出处理,在Linux系统中引入了信号集(信号的集合)。这个信号集有点类似于我们的QQ群,一个个的信号相当于QQ群里的一个个好友。

  信号集是用来表示多个信号的数据类型(sigset_t),其定义路径为:/usr/include/i386-linux-gnu/bits/sigset.h。

  信号集相关的操作主要有如下几个函数:

  #include<signal.h>

  int sigemptyset(sigset_t*set);

  int sigfillset(sigset_t*set);

  int sigismember(const sigset_t*set,int signum);

  int sigaddset(sigset_t*set,int signum);

  int sigdelset(sigset_t*set,int signum);

  通过例子来查看他的使用方法:

  #include<signal.h>

  #include<stdio.h>

  int main(int argc,char*argv[])

  {

  sigset_t set;//定义一个信号集变量

  int ret=0;

  sigemptyset(&set);//清空信号集的内容

  //判断SIGINT是否在信号集set里

  //在返回1,不在返回0

  ret=sigismember(&set,SIGINT);

  if(ret==0){

  printf("SIGINT is not a member of set\nret=%d\n",ret);

  }

  sigaddset(&set,SIGINT);//把SIGINT添加到信号集set

  sigaddset(&set,SIGQUIT);//把SIGQUIT添加到信号集set

  //判断SIGINT是否在信号集set里

  //在返回1,不在返回0

  ret=sigismember(&set,SIGINT);

  if(ret==1){

  printf("SIGINT is a member of set\nret=%d\n",ret);

  }

  sigdelset(&set,SIGQUIT);//把SIGQUIT从信号集set移除

  //判断SIGQUIT是否在信号集set里

  //在返回1,不在返回0

  ret=sigismember(&set,SIGQUIT);

  if(ret==0){

  printf("SIGQUIT is not a member of set\nret=%d\n",ret);

  }

  return 0;

  }

  运行结果:

  信号阻塞集(屏蔽集、掩码)

  信号阻塞集也称信号屏蔽集、信号掩码。每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。

  所谓阻塞并不是禁止传送信号,而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。

  我们可以通过sigprocmask()修改当前的信号掩码来改变信号的阻塞情况。

  所需头文件:

  #include<signal.h>

  int sigprocmask(int how,const sigset_tset,sigset_toldset);

  功能:

  检查或修改信号阻塞集,根据how指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由set指定,而原先的信号阻塞集合由oldset保存。

  参数:

  how:信号阻塞集合的修改方法,有3种情况:

  SIG_BLOCK:向信号阻塞集合中添加set信号集,新的信号掩码是set和旧信号掩码的并集。

  SIG_UNBLOCK:从信号阻塞集合中删除set信号集,从当前信号掩码中去除set中的信号。

  SIG_SETMASK:将信号阻塞集合设为set信号集,相当于原来信号阻塞集的内容清空,然后按照set中的信号重新设置信号阻塞集。

  set:要操作的信号集地址

  若set为NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到oldset中。

  oldset:保存原先信号阻塞集地址

  返回值:

  成功:0,

  失败:-1,失败时错误代码只可能是EINVAL,表示参数how不合法。

  注意:不能阻塞SIGKILL和SIGSTOP等信号,但是当set参数包含这些信号时sigprocmask()不返回错误,只是忽略它们。另外,阻塞SIGFPE这样的信号可能导致不可挽回的结果,因为这些信号是由程序错误产生的,忽略它们只能导致程序无法执行而被终止。

  示例代码如下:

  #include<stdio.h>

  #include<stdlib.h>

  #include<unistd.h>

  #include<signal.h>

  int main(int argc,char*argv[])

  {

  sigset_t set;//信号集合

  int i=0;

  sigemptyset(&set);//清空信号集合

  sigaddset(&set,SIGINT);//SIGINT加入set集合

  while(1)

  {

  //set集合加入阻塞集,在没有移除前,SIGINT会被阻塞

  sigprocmask(SIG_BLOCK,&set,NULL);

  for(i=0;i<5;i++)

  {

  printf("SIGINT signal is blocked\n");

  sleep(1);

  }

  //set集合从阻塞集中移除

  //假如SIGINT信号在被阻塞时发生了

  //此刻,SIGINT信号立马生效,中断当前进程

  sigprocmask(SIG_UNBLOCK,&set,NULL);

  for(i=0;i<5;i++)

  {

  printf("SIGINT signal unblocked\n");

  sleep(1);

  }

  }

  return 0;

  }

  运行结果:

  

  可靠信号的操作

  从UNIX系统继承过来的信号(SIGHUP~SIGSYS,前32个)都是不可靠信号,不支持排队(多次发送相同的信号,进程可能只能收到一次,可能会丢失)。

  SIGRTMIN至SIGRTMAX的信号支持排队(发多少次,就可以收到多少次,不会丢失),故称为可靠信号。

  可靠信号就是实时信号,非可靠信号就是非实时信号。

  signal()函数只能提供简单的信号安装操作,使用signal()函数处理信号比较简单,只要把要处理的信号和处理函数列出即可。

  signal()函数主要用于前面32种不可靠、非实时信号的处理,并且不支持信号传递信息。

  Linux提供了功能更强大的sigaction()函数,此函数可以用来检查和更改信号处理操作,可以支持可靠、实时信号的处理,并且支持信号传递信息。

  下面我们一起学习其相关函数的使用。

  所需头文件:

  #include<signal.h>

  int sigqueue(pid_t pid,int sig,const union sigval value);

  功能:给指定进程发送信号。

  参数:

  pid:进程号。

  sig:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令kill-l("l"为字母)进行相应查看。

  value:通过信号传递的参数。

  union sigval类型如下:

  union sigval

  {

  int sival_int;

  void*sival_ptr;

  };

  返回值:

  成功:0

  失败:-1

  int sigaction(int signum,const struct sigactionact,struct sigactionoldact);

  功能:

  检查或修改指定信号的设置(或同时执行这两种操作)。

  参数:

  signum:要操作的信号。

  act:要设置的对信号的新处理方式(设置)。

  oldact:原来对信号的处理方式(设置)。

  如果act指针非空,则要改变指定信号的处理方式(设置),如果oldact指针非空,则系统将此前指定信号的处理方式(设置)存入oldact。

  返回值:

  成功:0

  失败:-1

  信号设置结构体:

  struct sigaction

  {

  /旧的信号处理函数指针/

  void(*sa_handler)(int signum);

  /新的信号处理函数指针/

  void(sa_sigaction)(int signum,siginfo_tinfo,void*context);

  sigset_t sa_mask;/信号阻塞集/

  int sa_flags;/信号处理的方式/

  };

  sa_handler、sa_sigaction:信号处理函数指针,和signal()里的函数指针用法一样,应根据情况给sa_sigaction、sa_handler两者之一赋值,其取值如下:

  SIG_IGN:忽略该信号

  SIG_DFL:执行系统默认动作

  处理函数名:自定义信号处理函数

  sa_mask:信号阻塞集

  sa_flags:用于指定信号处理的行为,它可以是一下值的“按位或”组合:

  SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃)

  SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到SIGCHLD信号。

  SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到SIGCHLD信号,这时子进程如果退出也不会成为僵尸进程。

  SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。

  SA_RESETHAND:信号处理之后重新设置为默认的处理方式。

  SA_SIGINFO:使用sa_sigaction成员而不是sa_handler作为信号处理函数。

  信号处理函数:

  void(sa_sigaction)(int signum,siginfo_tinfo,void*context);

  参数说明:

  signum:信号的编号。

  info:记录信号发送进程信息的结构体,进程信息结构体路径:/usr/include/i386-linux-gnu/bits/siginfo.h,其结构体详情请点此链接。

  context:可以赋给指向ucontext_t类型的一个对象的指针,以引用在传递信号时被中断的接收进程或线程的上下文

  下面我们做这么一个例子,一个进程在发送信号,一个进程在接收信号的发送。

  发送信号示例代码:

  #include<stdio.h>

  #include<signal.h>

  #include<sys/types.h>

  #include<unistd.h>

  /*******************************************************

  *功能:发SIGINT信号及信号携带的值给指定的进程

  *参数:argv[1]:进程号

  argv[2]:待发送的值(默认为100)

  *返回值:0

  ********************************************************/

  int main(int argc,char*argv[])

  {

  if(argc>=2)

  {

  pid_t pid,pid_self;

  union sigval tmp;

  pid=atoi(argv[1]);//进程号

  if(argc>=3)

  {

  tmp.sival_int=atoi(argv[2]);

  }

  else

  {

  tmp.sival_int=100;

  }

  //给进程pid,发送SIGINT信号,并把tmp传递过去

  sigqueue(pid,SIGINT,tmp);

  pid_self=getpid();//进程号

  printf("pid=%d,pid_self=%d\n",pid,pid_self);

  }

  return 0;

  }

  接收信号示例代码如下:

  [cpp]view plaincopy

  #include<signal.h>

  #include<stdio.h>

  //信号处理回电函数

  void signal_handler(int signum,siginfo_tinfo,voidptr)

  {

  printf("signum=%d\n",signum);//信号编号

  printf("info->si_pid=%d\n",info->si_pid);//对方的进程号

  printf("info->si_sigval=%d\n",info->si_value.sival_int);//对方传递过来的信息

  }

  int main(int argc,char*argv[])

  {

  struct sigaction act,oact;

  act.sa_sigaction=signal_handler;//指定信号处理回调函数

  sigemptyset(&act.sa_mask);//阻塞集为空

  act.sa_flags=SA_SIGINFO;//指定调用signal_handler

  //注册信号SIGINT

  sigaction(SIGINT,&act,&oact);

  while(1)

  {

  printf("pid is%d\n",getpid());//进程号

  pause();//捕获信号,此函数会阻塞

  }

  return 0;

  }

  两个终端分别编译代码,一个进程接收,一个进程发送,运行结果如下:

  

  最后:

  关注回复“物联网”即可获取物联网全套视频教程