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

824 阅读7分钟

  什么是信号?

  信号是Linux进程间通信的最古老的方式。信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

  “中断”在我们生活中经常遇到,譬如,我正在房间里打游戏,突然送快递的来了,把正在玩游戏的我给“中断”了,我去签收快递(处理中断),处理完成后,再继续玩我的游戏。这里我们学习的“信号”就是属于这么一种“中断”。我们在终端上敲“Ctrl+c”,就产生一个“中断”,相当于产生一个信号,接着就会处理这么一个“中断任务”(默认的处理方式为中断当前进程)。

  信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知用户空间进程发生了哪些系统事件。

  一个完整的信号周期包括三个部分:信号的产生,信号在进程中的注册,信号在进程中的注销,执行信号处理函数。如下图所示:   

  注意:这里信号的产生,注册,注销时信号的内部机制,而不是信号的函数实现。

  Linux可使用命令:kill-l("l"为字母),查看相应的信号。   

  列表中,编号为1~31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32~63的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。非可靠信号一般都有确定的用途及含义,可靠信号则可以让用户自定义使用。更多详情,请看《Linux信号列表》。

  信号的产生方式

  1)当用户按某些终端键时,将产生信号。

  终端上按“Ctrl+c”组合键通常产生中断信号SIGINT,终端上按“Ctrl+\”键通常产生中断信号SIGQUIT,终端上按“Ctrl+z”键通常产生中断信号SIGSTOP等。

  2)硬件异常将产生信号。

  除数为0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。

  3)软件异常将产生信号。

  当检测到某种软件条件已发生,并将其通知有关进程时,产生信号。

  4)调用kill()函数将发送信号。

  注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。

  5)运行kill命令将发送信号。

  此程序实际上是使用kill函数来发送信号。也常用此命令终止一个失控的后台进程。

  信号的常用操作:

  发送信号

  所需头文件:

  #include<sys/types.h>

  #include<signal.h>

  int kill(pid_t pid,intsignum);

  功能:

  给指定进程发送信号。

  注意:使用kill()函数发送信号,接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者是超级用户。

  参数:

  pid:取值有4种情况:

  pid>0:将信号传送给进程ID为pid的进程。

  pid=0:将信号传送给当前进程所在进程组中的所有进程。

  pid=-1:将信号传送给系统内所有的进程。

  pid<-1:将信号传给指定进程组的所有进程。这个进程组号等于pid的绝对值。

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

  返回值:

  成功:0

  失败:-1

  下面为测试代码,本来父子进程各自每隔一秒打印一句话,3秒后,父进程通过kill()函数给子进程发送一个中断信号SIGINT(2号信号),最终,子进程结束,剩下父进程在打印信息:

  #include<stdio.h>

  #include<stdlib.h>

  #include<unistd.h>

  #include<sys/types.h>

  #include<signal.h>

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

  {

  pid_t pid;

  int i=0;

  pid=fork();//创建进程

  if(pid<0){//出错

  perror("fork");

  }

  if(pid==0){//子进程

  while(1){

  printf("I am son\n");

  sleep(1);

  }

  }else if(pid>0){//父进程

  while(1){

  printf("I am father\n");

  sleep(1);

  i++;

  if(3==i){//3秒后

  kill(pid,SIGINT);//给子进程pid,发送中断信号SIGINT

  //kill(pid,2);//等级于kill(pid,SIGINT);

  }

  }

  }

  return 0;

  }

  运行结果:   

  等待信号

  所需头文件:

  #include<unistd.h>

  int pause(void);

  功能:

  等待信号的到来(此函数会阻塞)。将调用进程挂起直至捕捉到信号为止,此函数通常用于判断信号是否已到。

  参数:无。

  返回值:

  直到捕获到信号才返回-1,且errno被设置成EINTR。

  测试代码如下:

  #include<unistd.h>

  #include<stdio.h>

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

  {

  printf("in pause function\n");

  pause();

  return 0;

  }

  没有产生信号前,进程一直阻塞在pause()不会往下执行,假如,我们按“Ctrl+c”,pause()会捕获到此信号,中断当前进程

  处理信号

  一个进程收到一个信号的时候,可以用如下方法进行处理:

  1)执行系统默认动作

  对大多数信号来说,系统默认动作是用来终止该进程。

  2)忽略此信号

  接收到此信号后没有任何动作。

  3)执行自定义信号处理函数

  用用户定义的信号处理函数处理该信号。

  注意:SIGKILL和SIGSTOP不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。

  产生一个信号,我们可以让其执行自定义信号处理函数。假如有函数A,B,C,我们如何确定信号产生后只调用函数A,而不是函数B或C。这时候,我们需要一种规则规定,信号产生后就调用函数A,就像交通规则一样,红灯走绿灯行,信号注册函数signal()就是做这样的事情。

  所需头文件:

  #include<signal.h>

  typedef void(*sighandler_t)(int);//回调函数的声明

  sighandler_t signal(int signum,sighandler_t handler);

  功能:

  注册信号处理函数(不可用于SIGKILL、SIGSTOP信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞。

  参数:

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

  handler:取值有3种情况:

  SIG_IGN:忽略该信号

  SIG_DFL:执行系统默认动作

  信号处理函数名:自定义信号处理函数,如:fun

  回调函数的定义如下:

  void fun(int signo)

  {

  //signo为触发的信号,为signal()第一个参数的值

  }

  注意:信号处理函数应该为可重入函数,关于可重入函数的更多详情,请《浅谈可重入函数与不可重入函数》。

  返回值:

  成功:第一次返回NULL,下一次返回此信号上一次注册的信号处理函数的地址。如果需要使用此返回值,必须在前面先声明此函数指针的类型。

  失败:返回SIG_ERR

  实例1:

  #include<stdio.h>

  #include<signal.h>

  #include<unistd.h>

  //信号处理函数

  void signal_handler(int signo)

  {

  if(signo==SIGINT){

  printf("recv SIGINT\n");

  }else if(signo==SIGQUIT){

  printf("recv SIGQUIT\n");

  }

  }

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

  {

  printf("wait for SIGINT OR SIGQUIT\n");

  /*SIGINT:Ctrl+c;SIGQUIT:Ctrl+*/

  //信号注册函数

  signal(SIGINT,signal_handler);

  signal(SIGQUIT,signal_handler);

  //等待信号

  pause();

  pause();

  return 0;

  }

  运行结果   

  实例2:

  #include<stdio.h>

  #include<signal.h>

  #include<unistd.h>

  //回调函数的声明

  typedef void(*sighandler_t)(int);

  void fun1(int signo)

  {

  printf("in fun1\n");

  }

  void fun2(int signo)

  {

  printf("in fun2\n");

  }

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

  {

  sighandler_t previous=NULL;

  //第一次返回NULL

  previous=signal(SIGINT,fun1);

  if(previous==NULL)

  {

  printf("return fun addr is NULL\n");

  }

  //下一次返回此信号上一次注册的信号处理函数的地址。

  previous=signal(SIGINT,fun2);

  if(previous==fun1)

  {

  printf("return fun addr is fun1\n");

  }

  //还是返回NULL,因为处理的信号变了

  previous=signal(SIGQUIT,fun1);

  if(previous==NULL)

  {

  printf("return fun addr is NULL\n");

  }

  return 0;

  }

  运行结果:   

  最后:

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