Linux进程信号
信号的产生1.通过终端按键产生信号如下代码:1 #include <stdio.h>2 #include <unistd.h>3 int main(){4while(1){5printf("i amtest process\n");6sleep(1);7
目录
信号的概念
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 }
生成可执行程序并运行:
以上就是使用自定义信号处理解决进程等待时的问题。
更多推荐
所有评论(0)