子进程对父进程信号继承情况
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