900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > 子进程信号继承;kill+raise+alarm+pause+信号发生接收和处理+信号屏蔽

子进程信号继承;kill+raise+alarm+pause+信号发生接收和处理+信号屏蔽

时间:2022-05-15 13:52:08

相关推荐

子进程信号继承;kill+raise+alarm+pause+信号发生接收和处理+信号屏蔽

子进程对父进程信号继承情况

fork创建子进程,但子进程没有exec

在fork子进程之前:

如果父进程调用signal设置了某个信号的处理方式的话,那么fork出的子进程会继承父进程对该信号设置的处理

强调:只有在fork之前,父进程所设置的信号处理方式,才会被子进程继承

#include<stdio.h>#include<stdlib.h>#include <sys/types.h>#include <unistd.h>#include<signal.h>//pid_t fork(void);int main(void){pid_t ret=0;/*在创建子进程前,设置信号忽略*/signal(SIGINT,SIG_IGN);ret=fork();if(ret>0){//父进程}else if(ret==0){//子进程}while(1);//防止进程太早结束return 0;}

该信号被忽略后,父子进程都忽略

#include<stdio.h>#include<stdlib.h>#include <sys/types.h>#include <unistd.h>#include<signal.h>void signal_fun(int sig){printf("pid=%d\t,singo=%d\n",getpid(),sig);}int main(void){pid_t ret=0;/*在创建子进程前,设置信号忽略*/signal(SIGINT,signal_fun);ret=fork();if(ret>0){//父进程}else if(ret==0){//子进程}while(1);//防止进程太早结束return 0;}

^Cpid=5992,singo=2pid=5993 ,singo=2^Cpid=5992,singo=2pid=5993 ,singo=2//父子进程都会设置该信号

为什么捕获函数在子进程里面依然有效

因为子进程复制了父进程的代码和数据,子进程自然也会包含信号处理函数的代码,所在子进程中依然有效。

父子进程各自设置信号

#include<stdio.h>#include<stdlib.h>#include <sys/types.h>#include <unistd.h>#include<signal.h>void singnal_fun1(int sig){printf("1---Pid=%d\n",getpid());}void singnal_fun2(int sig){printf("2---Pid=%d\n",getpid());}int main(void){pid_t ret=0;/*在创建子进程前,设置信号忽略*/signal(SIGINT,SIG_DFL);ret=fork();if(ret>0){//父进程signal(SIGINT,singnal_fun1);}else if(ret==0){//子进程signal(SIGINT,singnal_fun2);//这里的只设置子进程}while(1);//防止进程太早结束return 0;}

guojiawei@ubantu-gjw:~/Desktop/signal$ gcc fork_signal.c -o exeguojiawei@ubantu-gjw:~/Desktop/signal$ ./exe^C1---Pid=67662---Pid=6767

有调用exec加载新程序

fork之前,父进程设置的处理方式是忽略或默认

exec加载新程序后,忽略和默认设置依然有效

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>int main(int argc,char** argv,char** environ){pid_t ret=0;signal(SIGINT,SIG_IGN);ret=fork();if(ret>0){//父进程}else if(ret==0){//子进程execve("./child",argv,environ);}while(1);//防止进程太早结束return 0;}

ctrl+c对父子进程均不起作用

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>int main(int argc,char** argv,char** environ){pid_t ret=0;signal(SIGINT,SIG_DFL);ret=fork();if(ret>0){//父进程}else if(ret==0){//子进程execve("./child",argv,environ);}while(1);//防止进程太早结束return 0;}

默认:父子进程均有效

fork之前,父进程设置处理方式是捕获时

新程序的代码会覆盖子进程中原有的父进程的代码,信号捕获函数的代码也会被覆盖

既然捕获函数已经不存在了,捕获处理方式自然也就没有意义了,所以信号的处理方式会被还原为默认处理方式。

如果子进程所继承的信号处理方式是捕获的话,exec加载新程序后,捕获处理方式会被还原为默认处理方式。

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>void signal_fun(int sigo){printf("hello world!\n");}int main(int argc,char** argv,char** environ){pid_t ret=0;signal(SIGINT,signal_fun);ret=fork();if(ret>0){//父进程}else if(ret==0){//子进程execve("./child",argv,environ);}while(1);//防止进程太早结束return 0;}

guojiawei@ubantu-gjw:~/Desktop/signal$ gcc fork.c -o mainguojiawei@ubantu-gjw:~/Desktop/signal$ ./main^Chello world!

父进程捕获,但是子进程被杀死了

子程序只能在自己的程序定义捕获

#include<stdio.h>#include<stdlib.h>#include<signal.h>void signal_fun(int sigo){printf("child\n");}int main(){signal(SIGINT,signal_fun);while(1);return 0;}//编译成./child

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>void signal_fun(int sigo){printf("\nfather\n");}int main(int argc,char** argv,char** environ){pid_t ret=0;signal(SIGINT,signal_fun);ret=fork();if(ret>0){//父进程}else if(ret==0){//子进程execve("./child",argv,environ);}while(1);//防止进程太早结束return 0;}

总结

1.仅fork时子进程会继承父进程fork之前所设置的信号处理方式。2.当有exec加载新程序时(1)子进程继承的处理方式是忽略或默认处理方式时,exec新程序后设置依然有(2)如果子进程继承是捕获处理方式时,exec新程序后将被还原为默认处理方式

kill(),raise()

#include <sys/types.h>#include <signal.h>//kill:向PID所指向的进程发送指定的信号int kill(pid_t pid, int sig);//成功返回0,失败返回-1,errno被设置

#include <signal.h>//raise:向当前进程发送指定信号int raise(int sig);//成功返回0,失败返回非0

kill(getpid(),SIGUSR1);raise(SIGKILL);

当多个进程协同工作时,kill函数有时还是会用到的

比如向其它进程发送某信号,通知其某件事情发生了,其它进程收到这个信号后,就会调用信号处理函数进行相应的处理,以实现协同工作

alarm(),pause()

#include <unistd.h>//设置一个定时时间,当所设置的时间到后,内核会向调用alarm的进程发送SIGALRM信号//SIGALRM的默认处理方式是终止unsigned int alarm(unsigned int seconds);/*返回值:返回上一次调用alarm时所设置时间的剩余值*/

#include <unistd.h>//调用该函数的进程会永久挂起(阻塞或者休眠),直至被信号(任意一个信号)唤醒为止int pause(void);/*返回值:当被信号唤醒后会返回-1,表示失败了,errno的错误号被设置EINTR(表示函数被信号中断)*/

void signal_fun(int sigo){printf("time up!\n");}int main(){signal(SIGALRM,signal_fun);alarm(5);//5秒while(1);return 0;}

返回值

int main(){unsigned int res=0;alarm(4);//5秒sleep(2);res=alarm(3);//上次剩余时间printf("res=%d\n",res);while(1);return 0;}

guojiawei@ubantu-gjw:~/Desktop/signal$ ./mainres=2闹钟//因为4秒睡眠2秒,还剩3秒结束

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>void signal_fun(int signo){}int main(){unsigned int res=0;signal(SIGINT,signal_fun);pause();printf("\nhello\n");while(1);return 0;}/*guojiawei@ubantu-gjw:~/Desktop/signal$ gcc fork.c -o mainguojiawei@ubantu-gjw:~/Desktop/signal$ ./main^Chello^\退出 (核心已转储)*/

在开发中往往会使用pause()函数来帮助调试不想继续休眠时使用信号唤醒即可

abort()~raise(SIGABRT)

使用信号唤醒休眠函数

调用sleep、pause等函数时,这些函数会使进程进入休眠状态

如果不想继续休眠时,可以使用信号将其唤醒

唤醒的方法-----给信号登记一个空捕获函数即可

唤醒的过程

当信号发送给进程后,会中断当前休眠的函数,然后去执行捕获函数,捕获函数执行完毕返回后,不再调用休眠函数,而是执行休眠函数之后的代码,这样函数就被唤醒了

#include <signal.h>void signal_fun(int signo){printf("execute signal_fun\n");}int main(){signal(SIGINT,signal_fun);pause();//sleep(10);printf("pause code!\n");while(1);return 0;}

既要捕获信号,又想继续执行pause或sleep

手动重启

pause()返回值

​ pause() returns only when a signal was caught and the

​ signal-catching function returned.

In this case, pause()returns -1, and errno is set to EINTR.

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>#include <errno.h>void signal_fun(int signo){printf("execute signal_fun\n");}int main(){int res=0;signal(SIGINT,signal_fun);lable: res=pause();/*手动重启*/if(res==-1&&errno==EINTR){goto lable;}printf("pause code!\n");//这句话总是执行不出while(1);return 0;}/*guojiawei@ubantu-gjw:~/Desktop/signal$ ./main^Cexecute signal_fun^Cexecute signal_fun^Cexecute signal_fun^Cexecute signal_fun^\退出 (核心已转储)*/

sleep()返回值

1. Zero if the requested time has elapsed

the number of seconds left to sleep, if the call was interrupted by a signal handler.

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>#include <errno.h>void signal_fun(int signo){printf("execute signal_fun\n");}int main(){int res=0;signal(SIGINT,signal_fun);/*手动重启*/res=10;lable_sleep: res=sleep(res);if(res!=0){printf("res=%d\n",res);goto lable_sleep;}printf("pause code!\n");//这句话总是执行不出while(1);return 0;}

guojiawei@ubantu-gjw:~/Desktop/signal$ gcc fork.c -o mmmguojiawei@ubantu-gjw:~/Desktop/signal$ ./mmm^Cexecute signal_funres=8^Cexecute signal_funres=6^Cexecute signal_funres=5^Cexecute signal_funres=4^Cexecute signal_funres=3^Cexecute signal_funres=1pause code!

休眠函数自启动

使用read从键盘读取数据,当键盘没有输入任何数据时,read会休眠,不过函数被信号唤醒后,会自动重启read的调用

​ read函数读数据时,并不一定会休眠

读硬盘上的普通文件时,不管文件有没有数据,read都不会休眠,而是会返回继续向下运行如果read读的是键盘的话,如果键盘没有数据时read就会休眠。

void signal_fun(int signo){printf("execute signal\n");}int main(){int res=0;signal(SIGINT,signal_fun);char buf[100]={0};read(0,buf,sizeof(buf));printf("have read!\n");while(1);return 0;}

guojiawei@ubantu-gjw:~/Desktop/signal$ ./mmm^Cexecute signal^Cexecute signalmmmmhave read!^\退出 (核心已转储)

也就是有些休眠函数自动启动,有些休眠函数手动启动

信号发生,接收和处理过程

信号屏蔽字

作用

用来屏蔽信号的

每个进程能够接收的信号有62种,信号屏蔽字的每一位记录了每个信号是被屏蔽的还是被打开的

如果是打开的就立即处理如果是屏蔽的就暂不处理

屏蔽字放在了哪里

每一个进程都有一个信号屏蔽字,它被放在了进程表(task_struct结构体变量)中

什么是屏蔽字
简单地认为屏蔽字就是一个64位的unsigned int数,每一位对应着一个信号 如果这一位为0,表示信号可以被立即处理如果为1表示该信号被屏蔽了,暂不处理

1 2 3 4 561 62 63 64 * * * * * ...... * * * * ------------------------------------------<EX>第1位:对应编号为1(SIGHUP)的信号,该位为1)0:表示1(SIGHUP)这个信号是打开的,可以被立即处理2)1:表示信号被屏蔽了,暂时不能处理

有对应的API用于修改信号屏蔽字的-----实现某个信号的打开和屏蔽

在默认情况下,信号屏蔽字中所有的位都为0

未处理信号集

作用:

跟屏蔽字一样,也一个64位的无符号整形数,专门用于记录未处理的信号

“未处理信号集”同样也是被放在了进程的进程表中(task_struct)

什么时候会记录

​ 信号来了,当进程的信号处理机制,检查该信号在屏蔽字中的对应位时发现是1,表示该信号被屏蔽了,暂时不能被处理,此时就会将“未处理信号集”中该信号编号所对应的位设置为1,这个记录就表示,有一个信号未被处理。

​ 如果该信号发送了多次,但是每一次都因为被屏蔽了而无法处理的话,在“未处理信号集”中只记录一次

什么时候处理未处理信号

当屏蔽字中该信号的位变成0时(被打开了),此时就回去检查“未处理信号”,处理表中的信号

信号处理的完整过程

屏蔽字+未处理信号集

内核接收信号,首先去查看信号处理方式这张表,看是捕获默认还是终止 如果是忽视,那么直接丢弃信号 如果是捕获或默认或终止,会查看信号屏蔽字这张表检查相应的信号屏蔽字是0还是1 如果是0,然后把0设置成1,就执行相应的捕获默认终止的操作如果是1,表名正在执行信号,就记录到未处理信号集

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>#include <errno.h>void signal_fun(int signo){printf("hello\n");sleep(3);printf("world\n");}int main(){pid_t ret=0;signal(SIGINT,signal_fun);while(1);return 0;}

guojiawei@ubantu-gjw:~/Desktop/signal$ ./mmm^Chello^C^C^C^C^Cworldhelloworld^Chello^C^C^C^C^C^C^C^C^C^C^Cworldhelloworld//按下很多次,但只记录了一次

修改信号屏蔽字

修改的原理

定义一个64位的与屏蔽字类似的变量

将该变量设置为想要的值

将某信号对应的位设置为0或者为1

使用这个变量中的值来修改屏蔽字

完全的替换

使用变量的值去完全替换掉屏蔽字

​ 比如:屏蔽字 = 变量(1111111…11111)

使用|操作,将对应的位设置为1,只屏蔽某个或者某两个信号

​ 比如:屏蔽字 = 屏蔽字 | 0000…10

第三种:使用位&操作,将对应的位清0,打开信号

​ 比如:屏蔽字 = 屏蔽字 & (~0000…10)

​ 屏蔽字 = 屏蔽字 & 1111…01

SIGKILL和SIGSTOP信号是不能被屏蔽,就算在屏蔽字中它们对应的位设置为了1,也不会起到屏蔽的作用

设置变量API

#include <signal.h>//将变量set的64位全部设置为0int sigemptyset(sigset_t *set);//将变量set的64位全部设置为1int sigfillset(sigset_t *set);//将变量set中,signum(信号编号)对应的那一位设置为1,其它为不变int sigaddset(sigset_t *set, int signum);//将变量set的signum(信号编号)对应的那一位设置为0,其它位不变int sigdelset(sigset_t *set, int signum);/*调用成功返回0,失败返回-1,并且errno被设置*/

设置变量修改屏蔽字API

#include <signal.h>/* Prototype for the glibc wrapper function *///使用设置好的变量set去修改信号屏蔽字int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

1)how:修改方式,前面说过有三种修改方式(a)SIG_BLOCK:屏蔽某个信号----屏蔽字=屏蔽字 | set(b)SIG_UNBLOCK:对屏蔽字的某位进行清0操作-----屏蔽字=屏蔽字&(~set)(c)SIG_SETMASK:直接使用set的值替换掉屏蔽字2)set:set的地址3)oldset:保存修改之前屏蔽字的值,如果写为NULL的话,就表示不保存

代码:

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>#include <errno.h>void signal_fun(int signo){sigset_t set;printf("hello\n");/*得到:010000*/sigemptyset(&set);//取地址sigaddset(&set,SIGINT);/*修改屏蔽字*///SIG_UNBLOCK:取反相与sigprocmask(SIG_UNBLOCK,&set,NULL);//不保存旧值sleep(3);printf("world\n");}int main(){pid_t ret=0;signal(SIGINT,signal_fun);while(1);return 0;}

guojiawei@ubantu-gjw:~/Desktop/signal$ ./mm^Chello^Chello^Chello^Chello^Chello^Chelloworldworldworldworldworldworld

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。