信号
信号的特点:简单、不能带大量信息,满足特定条件发生。
信号也叫软件产生的中断,有可能会有延迟。(比如运行大量的进程,CPU资源被占据,导致信号延迟。)
每个进程收到的所有信号,都是由内核负责发送的,内核处理。
信号的产生方式:
1、按键产生:ctrl + c 、ctrl + z 、ctrl +
2、调用函数:kill、raise、abort
3、定时器:alarm、setitimer
4、命令产生kill
5、硬件异常、段错误、浮点型错误、总线错误、SIGPIPE
信号的状态:
1、产生
2、递达:递送并且到达进程。
3、未决:产生和递达之间的状态,主要由于信号被阻塞(屏蔽)所导致。
信号的默认处理方式:
1、忽略
2、执行默认动作
3、捕获
其中,9、19号信号不能捕获,不能忽略,甚至不能阻塞。
信号的四要素:
1、编号
2、事件
3、名称
4、默认的处理动作
默认的处理动作可以通过命令 man 7 signal来查看,有以下5个。
Term:终止
Ign:忽略
Core:终止+Core
Stop:暂停
Cont:继续
信号的编号
可以使用kill –l命令查看当前系统可使用的信号有哪些。
不存在编号为0的信号。其中1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64称之为实时信号,驱动编程与硬件相关。名字上区别不大。而前32个名字各不相同。
阻塞信号集、未决信号集、信号产生
Linux内核的进程控制块PCB是一个结构体,task_struct,除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。
阻塞信号集(信号屏蔽字): 将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将被推后(解除屏蔽后)。
未决信号集:
1、信号产生,未决信号集中描述该信号的位立刻翻转为1,表示信号处于未决状态。当信号被处理,对应位翻转回为0。这一时刻往往非常短暂(内核产生内核处理)。
2、信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。
阻塞信号集的作用实际上就是影响未决信号集的。阻塞信号集相当于未决信号集的前面设置了一堵墙。
“集” 理解为位图。如下图。
信号产生
练习:弑父
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc,char **argv)
{
int i;
for(i=0;i<5;i++)
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork err");
exit(1);
}
else if(pid==0)
{
break;
}
}
if(i==2)
{
printf("I will kill father after 5s.\n");
sleep(5);
kill(getppid(),SIGKILL);
while(1)
{
sleep(1);
}
}
else if(i==5)
{
//parent
while(1)
{
printf("I am father.\n ");
sleep(1);
}
}
return 0;
}
练习:父杀子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc,char **argv)
{
int i;
pid_t pid3;
for(i=0;i<5;i++)
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork err");
exit(1);
}
else if(pid==0)
{
break;
}
if(i==2)
{
pid3 = pid;
}
}
if(i<5)
{
while(1)
{
printf("I am chile_%d,pid=%d,ppid=%d.\n",i,getpid(),getppid());
sleep(2);
}
}
else if(i==5)
{
printf("I am father.I will kill pid3 after 5s.\n");
sleep(5);
kill(pid3,SIGKILL);
while(1)
{
printf("I am father.\n ");
sleep(1);
}
}
return 0;
}
raise:自己给自己发信号。
练习:自杀
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc,char **argv)
{
printf("I will die!.\n");
sleep(2);
raise(SIGKILL);//相当于执行kill(getpid(),sig);
return 0;
}
abort:直接自己给自己发送一个异常信号SIGABRT,然后终止程序运行。
练习
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc,char **argv)
{
printf("I will die!.\n");
sleep(2);
abort();
return 0;
}
运行结果:
time函数
time函数获得日历时间。日历时间,是用“从一个标准时间点到此时的时间经过的秒数”来表示的时间。这个标准时间点对不同的编译器来说会有所不同,但对一个编译系统来说,这个标准时间点是不变的,该编译系统中的时间对应的日历时间都通过该标准时间点来衡量,所以可以说日历时间是“相对时间”,但是无论你在哪一个时区,在同一时刻对同一个标准时间点来说,日历时间都是一样的。
#include <time.h>
time_t time(time_t *tloc);
是当前相对与过去的某个时间点所走过的时间。
时钟信号产生:alarm
设定几秒后发送信号SIGALRM。有点像定闹钟。
函数返回值是上次闹钟剩余的秒数。
特别的,如果传入参数为0,则代表取消闹钟。
#include <stdio.h>
#include <unistd.h>
int main()
{
alarm(6);
while(1)
{
printf("Hello.\n");
sleep(1);
}
}
6秒后,该进程给自己发送一个SIGALRM信号,然后终止正在运行的进程。
看看返回值。
#include <stdio.h>
#include <unistd.h>
int main()
{
int ret = alarm(6);//定时六秒,ret = 0.
printf("ret = %d.\n",ret);
sleep(3);
ret = alarm(2);//经过三秒,还剩下3秒,ret = 3,又重新定时2秒.
printf("ret = %d.\n",ret);
sleep(1);
ret = alarm(0);//又经过1秒,还剩下1秒,取消定时.
printf("ret = %d.\n",ret);
while(1)
{
printf("Hello.\n");
sleep(1);
}
}
时钟信号产生:setitimer
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
//new_value是这一次的时间设置
//old_value是距离上一次的闹钟的定时时间到,还剩多长时间
1、使用setitimer函数来实现alarm的定时功能
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
//int getitimer(int which, struct itimerval *curr_value);
//int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
//new_value是这一次的时间设置
//old_value是距离上一次的闹钟的定时时间到,还剩多长时间
//使用setitimer函数来实现alarm的定时功能
#if 0
Timer values are defined by the following structures:
struct itimerval { /* 周期性的时间设置 */
struct timeval it_interval; /* Interval for periodic timer */
struct timeval it_value; /* Time until next expiration */
}; /* 下次闹钟的定时时间 */
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
#endif
int main()
{
struct itimerval mytimer={{0,0},{3,0}};
setitimer(ITIMER_REAL,&mytimer,NULL);
while(1)
{
printf("Hello.\n");
sleep(1);
}
return 0;
}
注意使用该函数的写法。
2、使用setitimer函数来实现周期性的时间设置
要想实现周期性的时间设置,需要先了解下信号捕获函数。
注意函数的参数。
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
//int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
//使用setitimer函数来实现周期性时间设置的功能
#if 0
Timer values are defined by the following structures:
struct itimerval { /* 周期性的时间设置 */
struct timeval it_interval; /* Interval for periodic timer */
struct timeval it_value; /* Time until next expiration */
}; /* 下次闹钟的定时时间 */
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
#endif
//根据函数指针来定义
void catch_sig(int num)
{
printf("cat %d sig.\n",num);
}
int main()
{
//通过调用signal这个函数,表示告诉系统当signum这个信号产生的时候,调用指针函数指向的那个函数。
//在这个函数中我们自定义我们想要做的事情。
//在这里我们抓SIGALRM这个信号。
signal(SIGALRM,catch_sig);
struct itimerval mytimer={{3,0},{5,0}};//第一次定时5s,5s之后,发送一次SIGALRM信号.之后,每隔3s发一次SIGALRM信号。
setitimer(ITIMER_REAL,&mytimer,NULL);
while(1)
{
printf("Hello.\n");
sleep(1);
}
return 0;
}
运行结果如下:
3、使用setitimer函数来计算上一次定时还剩多少时间
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
//int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
unsigned int myalarm(unsigned int seconds)
{
struct itimerval oldtimer,newtimer={{0,0},{0,0}};
newtimer.it_value.tv_sec = seconds;
setitimer(ITIMER_REAL,&newtimer,&oldtimer);//seconds秒后发送SIGALRM信号
printf("tv_sec=%ld,tv_mirsec=%ld.\n",oldtimer.it_value.tv_sec,oldtimer.it_value.tv_usec);
return oldtimer.it_value.tv_sec*1000000+oldtimer.it_value.tv_usec;
}
int main()
{
int i = 0;
unsigned int ret = 0;
ret = myalarm(5);
printf("ret= %d us\n",ret);
sleep(3);
ret = myalarm(3);
printf("ret= %d us\n",ret);
while(1)
{
printf("Hello %3d.\n",++i);
sleep(1);
}
return 0;
}
输出结果如下:
为什么不是整整的2秒?原因是:指令的执行时间也有一定的消耗。
信号集处理函数
#include <signal.h>
//清空信号集,信号集(位图)清空变成0
int sigemptyset(sigset_t *set);
//填充信号集,信号集(位图)填充变成1
int sigfillset(sigset_t *set);
//添加某个信号到信号集
int sigaddset(sigset_t *set, int signum);
//从信号集中删除某个信号
int sigdelset(sigset_t *set, int signum);
//是否为集合中的成员
int sigismember(const sigset_t *set, int signum);
//返回值判断
sigemptyset(), sigfillset(), sigaddset(), and sigdelset() return 0 on success and -1 on error.
sigismember() returns 1 if signum is a member of set, 0 if signum is not a member, and -1 on error.
sigprocmask函数
功能:用来设置set为当前进程的阻塞信号集。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数:
int how:
SIG_BLOCK: 设置阻塞
SIG_UNBLOCK: 解除阻塞
SIG_SETMASK: 设置set为新的阻塞信号集
set: 传入参数,传入的信号集
oldset: 传出参数,旧的信号集
设置旧信号集有利于恢复之前的状态。
返回值:
sigprocmask() returns 0 on success and -1 on error.
sigpending函数
功能:获取当前进程的未决信号集。
#include <signal.h>
int sigpending(sigset_t *set);
参数:
set:传出参数,当前的未决信号集。
返回值:
sigpending() returns 0 on success and -1 on error.
1、使用sigpending来获取当前进程的未决信号集
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main()
{
int i;
sigset_t weijue_sigset;
sigpending(&weijue_sigset);
for(i=1;i<32;i++)//0-31为标准(普通)信号
{
//是否为信号集中的成员
//不是返回0,是返回1.
if(sigismember(&weijue_sigset,i) == 1)
printf("1");
else
printf("0");
}
printf("\n");
return 0;
}
输出结果如下:
我们当前进程中没有未决信号。
2、设置阻塞信号,然后再打印未决信号集。
这里使用终端按键产生信号,然后设置阻塞不同按键产生的信号。
程序思路:
1】设置阻塞信号集。
方式:使用sigprocmask函数
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
2】添加信号到阻塞信号集。
方式:使用sigaddset函数添加某个信号到信号集。
int sigaddset(sigset_t *set, int signum);
3】获取当前进程的未决信号集。
方式:使用sigpending函数获取
4】打印未决信号集。
方式:通过sigismember判断是否为当前未决信号集合中的成员
int sigismember(const sigset_t *set, int signum);
注】对信号集操作之前先使用sigemptyset函数清空信号集。
方式:int sigemptyset(sigset_t *set);
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main()
{
//定义阻塞信号集.
sigset_t BlockSigSet;
//对信号集操作之前先使用sigemptyset函数清空信号集。
sigemptyset(&BlockSigSet);
//添加阻塞信号到阻塞信号集.
sigaddset(&BlockSigSet,SIGINT);//这里阻塞SIGINT信号,SIGINT信号的值为2
//设置信号集为当前进程的阻塞信号集.
sigprocmask(SIG_BLOCK, &BlockSigSet, NULL);
int i;
sigset_t weijue_SigSet;
while(1)
{
sigpending(&weijue_SigSet);
for(i=1;i<32;i++)
{
//是否为信号集中的成员
//不是返回0,是返回1.
if(sigismember(&weijue_SigSet,i) == 1)
printf("1");
else
printf("0");
}
printf("\n");
sleep(1);
}
return 0;
}
运行结果如下:
前面说9号信号SIGKILL不能被阻塞,我们在程序中验证下。
sigaddset(&BlockSigSet,SIGKILL);//这里阻塞SIGKILL信号,SIGKILL信号的值为9
运行下程序:
然后,使用9号信号。
可以看出还是被杀死了,说明9号信号是不能被阻塞的。
信号捕捉函数
作用:可以防止进程意外死亡。
假设在程序执行过程中,突然收到一个异常信号,该信号可能会导致我们的正在运行的程序死亡,这时,如果我们在程序中设置了捕获函数,那么就对该异常信号进行捕获,最后根据捕获的异常信号,执行对应操作,从而确保我们的程序能够正常执行下去。
在上面,我们已经提到了信号捕获函数signal,我们用该函数实现了周期性的时间设置。
我们再来看看这个函数。
sighandler_t这个数据类型并不是标准的类型。而是自定义的函数指针类型,在程序中如果使用到这个数据类型,就要加上下面这句,
typedef void ( * sighandler_t)(int);
这个函数的函数名为signal,signal可能在不同平台上的用处不一样,可能会造成冲突,所以我们一般使用另外一个函数sigaction,这个函数的作用也是捕获信号。
sigaction函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
看下struct sigaction这个结构体。
返回值:
sigaction() returns 0 on success; on error, -1 is returned, and errno is set to indicate the error.
练习:使用sigaction函数来实现周期性的时间设置—与上面使用signal函数实现一样的功能。
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
//int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
//使用setitimer函数来实现周期性时间设置的功能
#if 0
Timer values are defined by the following structures:
struct itimerval { /* 周期性的时间设置 */
struct timeval it_interval; /* Interval for periodic timer */
struct timeval it_value; /* Time until next expiration */
}; /* 下次闹钟的定时时间 */
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
#endif
//根据函数指针来定义
void catch_sig(int num)
{
printf("cat %d sig.\n",num);
}
int main()
{
//通过调用sigaction这个函数,表示告诉系统当signum这个信号产生的时候,调用指针函数指向的那个函数。
//在这个函数中我们自定义我们想要做的事情。
//在这里我们抓SIGALRM这个值为14的信号。
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = catch_sig;
//我们捕捉这个函数的期间,没有要屏蔽的信号。
//int sigemptyset(sigset_t *set);
sigemptyset(&act.sa_mask);
//int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
sigaction(SIGALRM, &act,NULL);
struct itimerval mytimer={{3,0},{5,0}};//第一次定时5s,5s之后,发送一次SIGALRM信号.之后,每隔3s发一次SIGALRM信号。
setitimer(ITIMER_REAL,&mytimer,NULL);
while(1)
{
printf("Hello.\n");
sleep(1);
}
return 0;
}
程序运行结果如下:
我们试着使用kill命令来给这个进程发14号信号,看看能否被捕获。
可以看出14号信号被捕获了。
信号的捕捉特性
1、进程正常运行时,默认pcb中有一个信号屏蔽字,假定为☆,它决定了进程自动屏蔽哪些信号,当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数,而该函数有可能执行很长时间,在这期间所屏蔽的信号不由☆来指定,而是用sa_mask来指定,执行完信号处理函数,再次恢复为☆。
2、xxx信号捕捉函数执行期间(可能执行时间比较长),xxx信号会自动被屏蔽。(再产生该信号时,该信号会被阻塞,但该阻塞的常规信号不支持排队,虽然产生多次但只记录一次。(后32个实时信号支持排队))
验证第2点特性:
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
//根据函数指针来定义
void catch_sig(int num)
{
printf("catch %d sig begin.\n",num);
sleep(3);//模拟该函数执行时间很长
printf("catch %d sig end.\n",num);
}
int main()
{
//通过调用sigaction这个函数,表示告诉系统当signum这个信号产生的时候,调用指针函数指向的那个函数。
//在这个函数中我们自定义我们想要做的事情。
//在这里我们抓SIGALRM这个值为14的信号。
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = catch_sig;
//我们捕捉这个函数的期间,没有要屏蔽的信号。
//int sigemptyset(sigset_t *set);
sigemptyset(&act.sa_mask);
//int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
sigaction(SIGINT, &act,NULL);
while(1)
{
printf("Hello.\n");
sleep(1);
}
return 0;
}
测试输出结果如下图所示。
验证第1点特性:
在捕获函数的动作执行期间,临时屏蔽 ctrl+\ 信号。但等该当作执行完后,由于屏蔽信号集中没有屏蔽该信号,所以在动作执行结束之后会继续响应该信号。
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
//根据函数指针来定义
void catch_sig(int num)
{
printf("catch %d sig begin.\n",num);
sleep(3);//模拟该函数执行时间很长
printf("catch %d sig end.\n",num);
}
int main()
{
//通过调用sigaction这个函数,表示告诉系统当signum这个信号产生的时候,调用指针函数指向的那个函数。
//在这个函数中我们自定义我们想要做的事情。
//在这里我们抓SIGALRM这个值为14的信号。
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = catch_sig;
//我们捕捉这个函数的期间,没有要屏蔽的信号。
//int sigemptyset(sigset_t *set);
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGQUIT);//临时屏蔽 ctrl+\ 信号
//int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
sigaction(SIGINT, &act,NULL);
while(1)
{
printf("Hello.\n");
sleep(1);
}
return 0;
}
内核实现信号捕捉过程:
SIGCHLD信号
SIGCHLD信号的产生条件
1、子进程终止时。
2、子进程接收到SIGSTOP信号停止时。
3、子进程处于停止态,接收到SIGCONT信号后被唤醒时。
我们在man 7 signal中可以看下。
当子进程暂停或者终止时,会发出SIGCHLD这个信号,但是内核对这个信号的默认处理动作是忽略。
我们学过了信号捕获函数,可以和这个信号结合起来,来实现对子进程资源的回收,从而可以避免父进程一直使用wait函数等待,不停的去判断。
下面按照这个思路来实现一下。
注意:这里让每个子进程睡上相应的i秒之后,才让他死去。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void huishou_pid(int num)
{
pid_t wpid = waitpid(-1,NULL,WNOHANG);
if(wpid >0)
{
printf("wait child %d ok.\n",wpid);
}
}
int main()
{
int i = 0;
pid_t pid;
for(i=0;i<10;i++)
{
pid = fork();
if(pid == 0)
{
//fork son ok.
break;
}
}
if(i==10)
{
//parent
struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = huishou_pid;
sigaction(SIGCHLD,&act,NULL);
while(1)
{
sleep(1);
}
}
else if(i < 10)
{
printf("I am %d child,pid = %d.\n",i,getpid());
sleep(i);
}
return 0;
}
程序运行一下:
都给回收了。
再通过命令ps aux来看下。
子进程都被回收了,无僵尸进程产生。
如果让每个子进程都只是打印一句话,就死去。也就是将sleep(i)这句代码给屏蔽了。
再运行一下:
可以看出,10个进程只回收了7个。
再通过命令ps aux来看下。
果然,产生了三个僵尸进程。
那是什么原因造成的呢?
分析下,在前面,我们提到了
捕获信号的特性2
“xxx信号捕捉函数执行期间(可能执行时间比较长),再产生该信号时,该信号会被阻塞,但该阻塞的常规信号不支持排队,虽然产生多次但只记录一次。”
也就是说当多个子进程在几乎同一时间死去时,我们的回收函数来不及反应,最终只回收一个。从而造成僵尸进程的出现。
那怎么解决这个问题呢?
我们需要对我们的捕获函数进行修改,不让他回收一个就撤,而是使用while来一次性能回收多个。
void huishou_pid(int num)
{
pid_t wpid;
while((wpid = waitpid(-1,NULL,WNOHANG))>0)
{
printf("wait child %d ok.\n",wpid);
}
}
程序运行结果如下:
再看下是否还有僵尸进程吧。
果然,都回收了。
特别的,需要注意下面这种有可能出现的极端情况,要避免。
当捕获函数运行之前,子程序就全部死光了呢?
比如,在父进程中注册捕获函数之前,先让它睡几秒。
if(i==10)
{
//parent
sleep(2);
struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = huishou_pid;
sigaction(SIGCHLD,&act,NULL);
while(1)
{
sleep(1);
}
}
再运行下程序。
一个都没回收。都变成了僵尸进程。
那应该怎么避免这种情况出现呢?
在子进程被fork之前,将SIGCHLD加入阻塞信号集,在捕获函数注册完成后,将SIGCHLD从阻塞信号集中解除。
这可能需要回顾对阻塞信号集概念的理解。
阻塞信号集(信号屏蔽字): 将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将被推后(解除屏蔽后)。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void huishou_pid(int num)
{
pid_t wpid;
while((wpid = waitpid(-1,NULL,WNOHANG))>0)
{
printf("wait child %d ok.\n",wpid);
}
}
int main()
{
int i = 0;
pid_t pid;
//在子进程被fork之前,将SIGCHILD加入阻塞信号集
sigset_t myBlockSet;
sigset_t oldset;
sigemptyset(&myBlockSet);
sigaddset(&myBlockSet,SIGCHLD);
//sigprocmask用来设置set为当前的阻塞信号集
//int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
sigprocmask(SIG_BLOCK,&myBlockSet,&oldset);
for(i=0;i<10;i++)
{
pid = fork();
if(pid == 0)
{
//fork son ok.
break;
}
}
if(i==10)
{
//parent
sleep(2);
struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = huishou_pid;
sigaction(SIGCHLD,&act,NULL);
//使用SIG_SETMASK参数恢复阻塞信号集
sigprocmask(SIG_SETMASK,&oldset,NULL);
while(1)
{
sleep(1);
}
}
else if(i < 10)
{
printf("I am %d child,pid = %d.\n",i,getpid());
// sleep(i);
}
return 0;
}
运行一下程序:
再使用ps aux命令来看下。
测试成功。
作业
利用SIGUSR1和SIGUSR2在父子进程之间进行消息传递,实现父子进程交替报数(间隔1s)。
提示:
1、使用kill(pid,sig)发送信号。
2、父子进程,捕获信号。
pid > 0那一方即父进程的逻辑总是先执行的。
但当父进程执行后,把SIGUSR2发出后,子进程捕获不到该信号。
解决方法:
1、加上延时函数usleep
2、将SIGUSR2加入阻塞信号集。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
pid_t pid;
int count = 0;
int flag = 0;
void son_catch_signal(int num)
{
sleep(1);
flag=1;
printf("%d\n",count);
count += 2;
}
void parent_catch_signal(int num)
{
sleep(1);
flag=1;
printf("%d\n",count);
count += 2;
}
int main()
{
//在子进程被fork之前,将SIGUSR2加入阻塞信号集
sigset_t myBlockSet;
sigset_t oldset;
sigemptyset(&myBlockSet);
sigaddset(&myBlockSet,SIGUSR2);
//sigprocmask用来设置set为当前的阻塞信号集
//int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
sigprocmask(SIG_BLOCK,&myBlockSet,&oldset);
pid = fork();
if(pid == 0)
{
//son
count = 1;
signal(SIGUSR2,son_catch_signal);
pid_t ppid = getppid();
//使用SIG_SETMASK参数恢复阻塞信号集
sigprocmask(SIG_SETMASK,&oldset,NULL);
while(1)
{
if(flag==1)
{
kill(ppid,SIGUSR1);
flag = 0;
}
}
}
else if(pid > 0)
{
//parent
// usleep(10);//保证子进程先运行(当取消注释时,可不将SIGUSR2加入阻塞信号集)
count = 2;
signal(SIGUSR1,parent_catch_signal);
kill(pid,SIGUSR2);//必须先给子进程一个信号才能开始
while(1)
{
if(flag==1)
{
kill(pid,SIGUSR2);
flag = 0;
}
}
}
return 0;
}