子进程对父进程信号继承情况
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=2
pid=5993 ,singo=2
^Cpid=5992 ,singo=2
pid=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 exe
guojiawei@ubantu-gjw:~/Desktop/signal$ ./exe
^C1---Pid=6766
2---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 main
guojiawei@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$ ./main
res=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 main
guojiawei@ubantu-gjw:~/Desktop/signal$ ./main
^C
hello
^\退出 (核心已转储)
*/
- 在开发中往往会使用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 mmm
guojiawei@ubantu-gjw:~/Desktop/signal$ ./mmm
^Cexecute signal_fun
res=8
^Cexecute signal_fun
res=6
^Cexecute signal_fun
res=5
^Cexecute signal_fun
res=4
^Cexecute signal_fun
res=3
^Cexecute signal_fun
res=1
pause 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 signal
mmmm
have read!
^\退出 (核心已转储)
也就是有些休眠函数自动启动,有些休眠函数手动启动
信号发生,接收和处理过程
信号屏蔽字
作用
用来屏蔽信号的
每个进程能够接收的信号有62种,信号屏蔽字的每一位记录了每个信号是被屏蔽的还是被打开的
- 如果是打开的就立即处理
- 如果是屏蔽的就暂不处理
屏蔽字放在了哪里
每一个进程都有一个信号屏蔽字,它被放在了进程表
(task_struct结构体变量)中
什么是屏蔽字
- 简单地认为屏蔽字就是一个64位的unsigned int数,每一位对应着一个信号
-
- 如果这一位为0,表示信号可以被立即处理
- 如果为1表示该信号被屏蔽了,暂不处理
1 2 3 4 5 61 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^Cworld
hello
world
^Chello
^C^C^C^C^C^C^C^C^C^C^Cworld
hello
world
//按下很多次,但只记录了一次
修改信号屏蔽字
修改的原理
定义一个64位的与屏蔽字类似的变量
将该变量设置为想要的值
将某信号对应的位设置为0或者为1
使用这个变量中的值来修改屏蔽字
完全的替换
使用变量的值去完全替换掉屏蔽字 比如:屏蔽字 = 变量(1111111…11111)
使用|操作,将对应的位设置为1,只屏蔽某个或者某两个信号
比如:屏蔽字 = 屏蔽字 | 0000…10
第三种:使用位&操作,将对应的位清0,打开信号
比如:屏蔽字 = 屏蔽字 & (~0000…10)
屏蔽字 = 屏蔽字 & 1111…01
SIGKILL和SIGSTOP信号是不能被屏蔽,就算在屏蔽字中它们对应的位设置为了1,也不会起到屏蔽的作用
设置变量API
#include <signal.h>
//将变量set的64位全部设置为0
int sigemptyset(sigset_t *set);
//将变量set的64位全部设置为1
int 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
^Chello
world
world
world
world
world
world