目录

信号的概念

信号的产生

通过终端按键产生信号

通过命令或函数产生

 信号的处理方式

信号的注册

注册过程

非实时信号与实时信号注册时的区别

信号的注销

非可靠信号

可靠信号

信号的自定义处理方式

signal

sigaction

原理

 信号的阻塞

阻塞接口

 原理

代码验证

信号的捕捉流程

父子进程+进程等待+自定义信号处理


信号的概念

信号是进程之间事件异步通知的一种方式,属于软中断。
使用kill -l命令可以查看Linux下信号的种类:

Linux下有62种信号,每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到。其中1-31号信号是非可靠(实时)信号,34-64信号为可靠(实时)信号。

信号的产生

通过终端按键产生信号

如下代码:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 int main(){
  4     while(1){
  5         printf("i am  test process\n");                                                                
  6         sleep(1);
  7     }
  8     return 0;
  9 }

代码运行后,通过键盘输入,向进程发送不同的信号:

向进程发送2号信号:

 向进程发送20号信号:

 向进程发送3号信号:

通过命令或函数产生

命令"kill -[信号值] [pid]" 可以向进程发送信号,如上代码运行后,使用kill向其发送9号信号:

 kill函数也可以向进程发送信号:

 如下代码中,调用kill函数向当前进程发送2号信号,此时进程直接退出。

 信号的处理方式

1.默认处理方式:SIG_DFL,操作系统中已经定义了信号的处理方式了,例如:2号信号是终止一个进程。

2.忽略处理:SIG_IGN,进程收到忽略处理的信号后是不进行处理的。例如:子进程先于父进程退出,子进程退出的时候会给父进程发送SIGCHLD信号,而父进程接收到这个信号后,是忽略处理的,导致了父进程没有回收子进程的退出状态信息,从而子进程变成了僵尸进程。

3.自定义处理方式:程序员可以自定义信号的处理方式,定义一个函数,当进程收到该信号时,调用自己写的函数。

信号的注册

一个进程收到一个信号,这个过程称之为注册。

注册过程

1.信号注册的时候,将信号对应的比特位从0修改为1,表示当前进程收到了该信号,对应的比特位在task_struct结构体中,如下图:

2.在sigqueue队列中添加一个sigqueue节点,此队列在操作系统中本质上是一个双向链表。

非实时信号与实时信号注册时的区别

非实时信号:第一次注册时修改sig位图(0-1),添加sigqueue节点。第二次注册相同信号时,修改sig位图(1-1),并不会添加sigqueue节点。

实时信号:第一次注册时修改sig位图(0-1),添加sigqueue节点。第二次注册相同信号时,修改sig位图(1-1),继续添加sigqueue节点到队列中。

信号的注销

非可靠信号

1.将信号对应的sig位图修改(1-0)

2.将对应的sigqueue节点进行出队操作

可靠信号

1.将对应的sigqueue节点进行出队操作

2.判断sigqueue队列中是否还有对应信号的sigqueue节点。如果有,则不修改对应位图。如果没有,则修改对应位图(1-0)

信号的自定义处理方式

自定义处理方式,就是让程序员自己定义某一个信号的处理方式,函数接口 如下:

signal

注意:注册自定义函数的时候并没有调用,只有当收到某个信号后,才会回调这个函数。

下面代码中将2号信号和3号信号的处理方式自定义:

 观察代码执行后的结果,自定义函数的参数就是信号值。

注意9号信号不可以被自定义处理。

sigaction

其中结构体指针指向的结构体内容如下: 

 用一段代码验证以下这个函数的功能:

观察函数运行后的现象,成功将2号信号自定义处理了。 

原理

通过下图来理解

 信号的阻塞

在task_struct结构体中还有一个关于信号阻塞的位图,直接以"sigset_t blocked,real blocked"的形式出现在结构体中,它的用法与信号注册位图相类似。两者互不干扰,只是说当收到一个信号后,这个信号是阻塞的,那么暂时不处理这个信号。

阻塞接口

 原理

1.当how为SIG_BLOCK时,计算新的位图方式为:block(new)=block(old)|set

2.当how为SIG_UNBLOCK时,计算新的位图方式为:block(new)=block(old)&(~set)

3.当how为SIG_SETMASK时,计算新的位图方式为:block(new)=set

代码验证

下面代码中,将所有信号都阻塞掉,运行程序:

 此时,向进程发送的 2 3 20号信号都被阻塞了,无法执行相应操作。但是9号信号和19号信号时不能被阻塞的,只要向进程发送9号信号,进程就可以被杀死。

信号的捕捉流程

信号的捕捉流程如下图所示:

 总结一句话:当进程从想从内核态切换回用户态的时候,会调用do_signal()函数判断是否有信号需要处理。

父子进程+进程等待+自定义信号处理

在进程控制章节讲到,为了防止子进程变成僵尸进程,此时父进程需要调用wait或者waitpid函数进行进程等待,虽然这样可以防止子进程变成僵尸进程,但是此时有一个很大的缺点。如果调用的是wait函数,那么父进程此时就阻塞了,父进程不能运行自己的代码。如果调用waitpid函数并且设置了非阻塞,那么此时需要循环调用,父进程就只能一直运行循环内的代码,循环外的代码不能运行。那么怎样才能让父进程既能运行自己代码,又能在子进程退出时回收子进程的退出状态信息呢?

子进程退出时会给父进程发送SIGCHLD信号,而父进程是忽略处理的。那么我们可以自定义SIGCHLD信号的处理方式,让进程在处理信号时将子进程的退出状态信息回收了,代码如下:

  1 #include <stdio.h>                                                                                    
  2 #include <unistd.h>
  3 #include <signal.h>
  4 #include <sys/wait.h>
  5 void sigcallback(int sig){//回调
  6     printf("sig num is:%d\n",sig);
  7     int status;
  8     wait(&status);
  9     printf("i wait until the child process\n");
 10 }
 11 int main(){
 12     pid_t pid=fork();
 13     if(pid<0){
 14         perror("fork");
 15         return 0;
 16     }else if(pid==0){
 17         printf("i am child process\n");
 18         sleep(10);
 19     }else{
 20         signal(SIGCHLD,sigcallback);//自定义信号处理方式
 21         while(1){//父进程执行自己的代码
 22             sleep(1);
 23         }
 24     }                                                                                                 
 25     return 0;
 26 }

生成可执行程序并运行:

 以上就是使用自定义信号处理解决进程等待时的问题。

Logo

更多推荐