本篇博客所回顾的知识(学习目标):
一、信号介绍
从图中可以看出,信号的优先级是高于普通操作的!出现信号就一定要先执行完才能继续做之前的事情!
注意①:如何查看信号都有哪些呢?
答:用命令:kill -l 来do!
注意②:每一个进程都有一个唯一的定时器alarm!
用man 7 signal可以把信号的相关信息全都显示在终端上!
无需死记硬背,见到这些关键字时有个大概印象,然后查man 7 signal看回来会用,知道是啥意思就行!
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
这里,首先类型定义了一个名为 sighandler_t 的函数指针,执行一个返回值类型为void,参数列表只有一个int型参数的函数。当然,这个参数其实就是signum信号的编号(我们直接写信号对应名称即可)
当 signum 对应的宏(信号)产生后,内核就会自动去处理(调用)名为sighandler的函数!当执行完sighandler函数的操作后,会再继续你当前进程中调用sighandler函数之前还没执行完的代码!!!并不是说就直接把你当前的进程终结了,而是停在这儿,去干点别的置顶信号的信号处理函数的事情,然后再回来执行当前进程剩余未执行的代码!
test codes:
//signal函数测试:测试给没有读端的pipe管道写数据,会产生SIGPIPE信号
#include<stdio.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<sys/types.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
//信号处理函数
void sighandler(int signum){
printf("signum==[%d]\n",signum);
}
int main(){
//创建管道
int fd[2];
int ret = pipe(fd);
if(ret < 0){
perror("pipe error!\n");
return -1;
}
//注册SIGPIPE信号处理函数
signal(SIGPIPE,sighandler);
//关闭读端
close(fd[0]);
write(fd[1],"hello world!",strlen("hello world!"));
return 0;
}
result:
13号喜好就是SIGPIPE!(可用kill -l命令查询验证)
int kill(pid_t pid,int sig);
其中,pid就是进程的id,sig就是signum,即信号宏名!
%99的情况下,我们都只用 pid > 0 这一种case!
小总结:
signal函数:让 内核 注册 信号 处理函数
kill函数:发送 指定信号 给 指定进程
(按照指定信号的功能来do事情!可用man 2 signal来查!指定信号宏的功能)
下面介绍的,abort和raise,这两个函数在实际开发中用的不多!
test codes1:
//raise函数测试代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
int main(){
//给当前进程发送信号(自己)
raise(SIGKILL);
printf("hello world!\n");
return 0;
}
result1:
test codes2:
//abort函数测试代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
int main(){
//给当前进程发送异常终止信号6号signal信号SIGABRT(自己)
abort();
printf("hello world!\n");
return 0;
}
result2:
Term == Terminated 的意思!即进程接受到内核发送的SIGALRM信号后,那么该进程默认的动作将会被终止掉!
alarm(seconds)就表示seconds秒钟之后内核会产生一个SIGALRM信号给到当前进程。让其动作终止!
这时候,若有多个闹钟,则 后面的闹钟alarm 会覆盖 前面的alarm闹钟!
alarm(0)可以取消所有时钟操作!!!即后序不论执行多少秒钟之后,都不会产生SIGALRM信号给到当前的进程!
test codes1:
//alarm函数测试代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
void sighandler(int signo){
printf("signum==[%d]",signo);
}
int main(){
//给内核 注册 一个 信号捕捉函数signal
signal(SIGALRM,sighandler);
//设置时钟
int n = alarm(5);
//alarm(5) <==> 当5s走完后,会产生SIGALRM信号,此时内核捕捉到后就马上执行sighandler函数!
printf("n==[%d]\n",n);
sleep(2);
n = alarm(5);
printf("n==[%d]\n",n);
sleep(5);//等待alarm结束,产生SIGALRM信号让内核捕捉到然后执行sighandler函数!
return 0;
}
result1:
test codes2:
//alarm函数测试代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
void sighandler(int signo){
printf("signum==[%d]\n",signo);
}
int main(){
//给内核 注册 一个 信号捕捉函数signal
signal(SIGALRM,sighandler);
//设置时钟
int n = alarm(5);
//alarm(5) <==> 当5s走完后,会产生SIGALRM信号,此时内核捕捉到后就马上执行sighandler函数!
printf("n==[%d]\n",n);
sleep(2);
n = alarm(2);//5s后 产生SIGALRM信号 让当前进程终止
printf("n==[%d]\n",n);
n = alarm(0);//alarm(0) 取消all的时钟(定时器)
printf("after alarm(0)\nn==[%d]\n",n);
sleep(8);//等待alarm结束,产生SIGALRM信号让内核捕捉到然后执行sighandler函数!
return 0;
}
result2:
小总结:
alarm:
1 每个进程都只有一个时钟(若后续重复出现多个,则最后一个出现的alarm将会覆盖之前all的!)。
2 alarm函数的返回值是:0或者上一个alarm函数剩余的秒数。
3 alarm(0)是取消定时器的意思。(让内核无法再捕捉这个信号,从而无法终止当前进程了)
4 alarm函数发送的是SIGALRM信号!
test codes3:(测试一秒钟 你的电脑能 数多少个数字)
//alarm函数的代码
//:测试一秒钟 你的电脑能 数多少个数字
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
int main(){
//设置时钟
alarm(1);//测试1s内 数了多少个数字!1s之后,产生的SIGALRM信号就会终止掉当前的进程!
int i = 0;
while(1){
printf("[%d]\n",i++);
}
return 0;
}
result3:
执行命令 time ./alarm2:
可以看出来, 我的linux服务器1s可以数78954个数字!!!
文件重定向后:
对该问题的总结:
实际time == 损耗time + 系统time + 用户time
每一个数字都直接打印:(printf("[%d]\n",i++);)
./alarm2:
real 0m1.001s
user 0m0.066s
sys 0m0.125s
1s内数了 78954 个数字。
损耗time == 实际time - (系统time+用户time) == 1.001 -(0.066+0.125) == 0.81
文件重定向之后:time ./alarm2 > test.log:
real 0m1.106s
user 0m0.574s
sys 0m0.197s
1s内数了 3786042 个数字。
损耗time == 实际time - (系统time+用户time) == 1.106 -(0.574+0.197) == 0.335
可以看出来,重定向之后的数 数字高效多了!!!
reason:调用printf函数打印数字遇到\n才会打印,打印过程中涉及到
从用户区到内核区的切换,切换次数越多消耗的时间就越长,效率就越低;
而使用文件重定向操作后,由于文件操作是带缓冲的,所以涉及到用户区到内核区的
切换次数会大大减少,从而使得损耗降低。
自然定时:到时后,产生SIGALRM信号,终止当前的进程!
man 2 setitimer:
test codes:
//setitimer函数测试代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/time.h>
//SIGALRM信号 的 信号 处理函数
void sighandler(int signum){
printf("signum==[%d]\n",signum);
}
int main(){
//注册 SIGALRM信号 的 信号 处理函数
signal(SIGALRM,sighandler);//注意:这个函数并不是调用完成后马上就取执行sighandler函数
//而是等待setitimer执行产生SIGALRM信号才会
// int setitimer(int which, const struct itimerval *new_value,
// struct itimerval *old_value);
struct itimerval tm;
//设置闹钟触发的 周期:每隔1s钟!do一次生成SIGALRM信号的工作!
tm.it_interval.tv_sec = 1;
tm.it_interval.tv_usec = 0;
//设置闹钟第一次触发的 时间:隔3s钟 生成SIGALRM信号!
tm.it_value.tv_sec = 3;
tm.it_value.tv_usec = 0;
//周期性设置闹钟!do 终止当前进程的工作!
setitimer(ITIMER_REAL,&tm,NULL);
while(1){//while循环的意思是:循环打印出hello world!
//当1s钟的周期时间还没来到时,你当前进程就执行完毕了,那就莫得打印了!
printf("hello,world!\n");
sleep(1);
}
//这个程序代码段意思是:一开始等3s,然后边等边打印hello world!
//接着,每隔1s钟就打印1句hello world!+上1局signum==[14]
return 0;
}
result:
建议:把这个内核中的信号集的关系,用自己的语言复述一遍!!!这样才能有一个较为深刻的理解!
所谓的屏蔽信号,就是阻塞信号的意思!so解除屏蔽就是解除阻塞的意思了!
一般,sigprocmask函数的how参数,只使用前面2个就够了!
oldset参数一般我们都设置为NULL,因为我们并不关心旧的信号屏蔽字!
test codes:
//信号集相关函数的测试
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
#include <sys/time.h>
//信号处理函数
void sighandler(int signo){
printf("signo==[%d]\n",signo);
}
int main(){
//注冊( 信号 处理函数 让内核捕获到该信号并执行用户自定义的函数!
signal(SIGINT,sighandler);
signal(SIGQUIT,sighandler);
//定义信号集变量
sigset_t set;
sigset_t old_set;
//初始化信号集
//<==> 信号集上所有的位都置为0了!
sigemptyset(&set);
sigemptyset(&old_set);
//将SIGINT SIGQUIT信号加入到信号集合set中
//这两个信号都是常见的信号
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
//将set集合中的SIGINT SIGQUIT信号加入到阻塞信号集中
//int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
//此时的how参数应该用宏:SIG_BLOCK
// sigprocmask(SIG_BLOCK,&set,NULL);
sigprocmask(SIG_BLOCK,&set,&old_set);
int i = 0;
int j = 1;
sigset_t pend;
while(1){
//先初始化 一个 信号集变量
sigemptyset(&pend);
//获取未决信号集(中的信号)
sigpending(&pend);
for(i=1;i<32;++i){
//判断某个信号是否 在 信号集 中!
//又因为信号既可以用宏来标识,也可以用数字来标识
//因此,我们就可以用i来遍历1~31号常规的信号
//不信的话,大可用命令kill -l来查看是否能用数字来标识信号!
//以判断该信号是否在未决信号集中!
int ret = sigismember(&pend,i);
if(ret == 1){
// printf("signal [%d]th is a member of 未决信号 set!\n",i);
printf("1");
}else{
// printf("signal [%d]th is not a member of 未决信号 set!\n",i);
printf("0");
}
}
//循环10的倍数次之后,解除对SIGINT SIGQUIT信号的阻塞
if(j++ % 10 == 0){
// sigprocmask(SIG_UNBLOCK,&set,NULL);//if循环了10的倍数次,就解除阻塞!
sigprocmask(SIG_SETMASK,&old_set,NULL);
}else{
//没到10次,就继续保持阻塞!
sigprocmask(SIG_BLOCK,&set,NULL);//if没有循环10的倍数次,就继续保持阻塞!
}
printf("\n");
sleep(2);//避免循环太快了!无法准确观察结果!
}
return 0;
}
man signal:
由于不同的UNIX/Linux版本,signal函数的表现会不一样,因此建议要避免使用signal函数来do注册信号处理函数这件事情!转而是去使用sigaction来do这件事!
若你不关心之前旧的处理方式,oldact这个参数这里可以直接设置为NULL!
struct sigaction{
void (*sa_handler)(int);//信号处理函数
void (*sa_sigaction)(int,siginfo_t *,void*);//信号处理函数,但基本不用 !
sigset_t sa_mask;//信号处理函数执行期间 需要阻塞 的信号
int sa_flags;//通常为0,表示使用默认的标识
void (*sa_restorer)(void);//该标记已经不再使用,废弃了!
};
test codes:
//sigaction函数测试代码:
//完成信号的注册
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
//信号处理函数
void sighandler(int signo){
printf("signum==[%d]\n",signo);
sleep(3);
}
int main(){
//注册SIGINT信号处理函数
struct sigaction act;
act.sa_handler = sighandler;//信号处理函数
sigemptyset(&act.sa_mask);//将要阻塞的信号集初始化(全置为空)
//if要阻塞哪个信号,就将哪个信号加入到信号集合中
sigaddset(&act.sa_mask,SIGQUIT);
//在信号处理函数执行期间,阻塞SIGQUIT信号!
act.sa_flags = 0;
sigaction(SIGINT,&act,NULL);
sigaction(SIGQUIT,&act,NULL);
//while循环,不断生成信号
while(1){
sleep(1);
}
return 0;
}
SIGCHLD信号,是信号这一章知识点中的重点知识!!!
使用SIGCHLD信号,对子进程进行资源回收掉方法,才是工作中,真正用得到,用得上的方法!!!
一问:那么,父进程收到内核发给其的SIGCHLD信号之后如何完成对其子进程资源的回收呢?
一答:用waitpid/wait函数来进行回收!(当然,通常都是前者,因为wait函数只能以堵塞的方式回收!这样就不太好)
注意:并不是说收你父进程一收到SIGCHLD信号就将其子进程回收,而是一旦其子进程退出之后,而父进程又收到了SIGCHLD信号的话,那此时肯定是需要该父进程调用waitpid函数去回收掉子进程的资源的!
test codes:(测试SIGCHLD信号产生的条件的代码)
//验证SIGCHLD信号产生的条件
/*
case 1-子进程退出
case 2-子进程收到SIGSTOP信号
case 3-当子进程暂停时,收到SIGCONT信号
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
#include<errno.h>
void sighandler(int signo){
printf("signum==[%d],此时,内核给其父进程发去一个SIGCHLD信号让其回收子进程的资源!\n",signo);
}
int main(){
pid_t pid;
//注册信号SIGCHLD的处理函数,等产生一个SIGSTOP信号后,内核捕获到就马上暂停该进程去执行用户自定义的函数
signal(SIGCHLD,sighandler);
printf("子进程产生SIGCHLD信号的三种cases(reasons):\n");
pid = fork();
if(pid < 0){
perror("fork error!\n");
return -1;
}
else if(pid > 0){
printf("father process,it's pid==[%d],child pid==[%d]\n",getpid(),pid);
while (1)//让父进程都不死掉(不结束)方便我们观察 子进程会如何产生SIGCHLD信号!
{
sleep(1);
}
}
else if(pid == 0){
printf("child process,fpid==[%d],child pid==[%d]\n",getppid(),getpid());
//break;//防止产生孙子进程or曾孙子进程!
while (1)//让子进程都不死掉(不结束)
{
sleep(1);
}
}
return 0;
}
result:
案例:父进程用SIGCHLD信号对子进程进行资源回收工作
test codes:
/**************************************************************
测试代码:
父进程用SIGCHLD信号完成对各个子进程资源的回收工作!
这里我们以循环创建子进程的代码来test!
(父进程循环创建3个子进程)
**************************************************************/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<sys/wait.h>
//SIGCHLD信号处理函数:
void waitchild_sighandler(int signo){
// print("signum==[%d]\n",signo);
//在这里信号处理函数中,需要让父进程 回收 其子进程的资源!(用waitpid函数)
while(1){
//pid_t waitpid(pid_t pid,int* wstatus,int options);
pid_t inputPid = -1;//回收all子进程
int* wstatus = NULL;//不关心子进程的退出 状态
int options = WNOHANG;//非阻塞的
pid_t wait_pid = waitpid(inputPid,wstatus,options);
if(wait_pid > 0){//表示成功回收掉子进程了!
printf("成功回收掉子进程了,此时所回收掉的子进程id==[%d]\n",wait_pid);
continue;
}
else if(wait_pid == -1){
printf("当前没有需要回收掉的子进程了!\n");
break;//此时即可 结束掉 回收子进程的while循环
}
else if(wait_pid == 0 && options == WNOHANG){
printf("当前需要回收的子进程还在运行中,尚未退出!\n");
continue;
}
}
printf("\n");//回车
}
int main(int argc,char* argv[])
{
int i = 0;
for(i=0;i<3;++i)
{
//创建子进程函数原型:
//pid_t fork(void);
pid_t pid = fork();
if(pid < 0)//fork失败的case
{
perror("fork error!");
return -1;
}
else if(pid == 0)//pid == 0 时,则当前进程为子进程
{
printf("child process: pid==[%d],fpid==[%d]\n",getpid(),getppid());
break;
}
else//pid > 0 时,则当前进程为父进程
{
//pid_t getpid(void);
//这个函数会返回调用该函数的进程的ID
//(哪个进程调用它,它就返回谁的PID)
printf("father process: pid==[%d],fpid==[%d]\n",getpid(),getppid());
sleep(2);//此時休眠2s钟
}
}
// 这里sleep(1) sleep(2) sleep(3)
// 是为了让子进程有 先后顺序 而退出!
//这只是为了便于观察而已!你当然可以不设置这个!
//第1个子进程
if(i==0){
printf("this is [%d]th child,PID==[%d]\n",i+1,getpid());
sleep(1);
}
//第2个子进程
if(i==1){
printf("this is [%d]th child,PID==[%d]\n",i+1,getpid());
sleep(2);
}
//第3个子进程
if(i==2){
printf("this is [%d]th child,PID==[%d]\n",i+1,getpid());
sleep(3);
}
//父进程
if(i==3){
printf("this is father,PID==[%d]\n",getpid());
//因为,这是在父进程中,完成对其子进程的回收,因此要在父进程中注册 信号处理函数
//注册 信号处理函数
struct sigaction act;
act.sa_handler = waitchild_sighandler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD,&act,NULL);
while(1){
sleep(1);//让父进程先别退出(执行别的事情)
//执行别的事情...(当然,这里为了测试,就弄了个 死循环!)
}
}
return 0;
}
result:
test codes2:
/**************************************************************
测试代码:
父进程用SIGCHLD信号完成对各个子进程资源的回收工作!
这里我们以循环创建子进程的代码来test!
(父进程循环创建3个子进程)
**************************************************************/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<sys/wait.h>
//SIGCHLD信号处理函数:
void waitchild_sighandler(int signo){
// print("signum==[%d]\n",signo);
//在这里信号处理函数中,需要让父进程 回收 其子进程的资源!(用waitpid函数)
printf("\n此时SIGCHLD信号处理函数开始被内核调用!\n");
while(1){
//收到一个SIGCHLD信号,通过while循环就可以回收多个死掉的子进程
//或者说是有多少个退出了的子进程就回收多少个!防止回收遗漏使得某个子进程变成僵尸进程
//进而浪费系统资源!
//pid_t waitpid(pid_t pid,int* wstatus,int options);
pid_t inputPid = -1;//回收all子进程
int* wstatus = NULL;//不关心子进程的退出 状态
int options = WNOHANG;//非阻塞的
pid_t wait_pid = waitpid(inputPid,wstatus,options);
if(wait_pid > 0){//表示成功回收掉子进程了!
printf("成功回收掉子进程了,此时所回收掉的子进程id==[%d]\n",wait_pid);
continue;
}
else if(wait_pid == -1){
printf("当前没有需要回收掉的子进程了!\n");
break;//退出while循环
}
else if(wait_pid == 0 && options == WNOHANG){
printf("当前需要回收的子进程还在运行中,尚未退出!\n");
continue;
}
}
printf("\n");//回车
}
int main(int argc,char* argv[])
{
//先将SIGCHLD信号 阻塞
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask,SIGCHLD);
sigprocmask(SIG_BLOCK,&mask,NULL);
int i = 0;
for(i=0;i<3;++i)
{
//创建子进程函数原型:
//pid_t fork(void);
pid_t pid = fork();
if(pid < 0)//fork失败的case
{
perror("fork error!");
return -1;
}
else if(pid == 0)//pid == 0 时,则当前进程为子进程
{
printf("child process: pid==[%d],fpid==[%d]\n",getpid(),getppid());
break;
}
else//pid > 0 时,则当前进程为父进程
{
//pid_t getpid(void);
//这个函数会返回调用该函数的进程的ID
//(哪个进程调用它,它就返回谁的PID)
printf("father process: pid==[%d],fpid==[%d]\n",getpid(),getppid());
sleep(2);//此時休眠2s钟
}
}
// 这里sleep(1) sleep(2) sleep(3)
// 是为了让子进程有 先后顺序 而退出!
//这只是为了便于观察而已!你当然可以不设置这个!
//第1个子进程
if(i==0){
printf("this is [%d]th child,PID==[%d]\n",i+1,getpid());
sleep(1);
}
//第2个子进程
if(i==1){
printf("this is [%d]th child,PID==[%d]\n",i+1,getpid());
sleep(2);
}
//第3个子进程
if(i==2){
printf("this is [%d]th child,PID==[%d]\n",i+1,getpid());
sleep(3);
}
//父进程
if(i==3){
printf("this is father,PID==[%d]\n",getpid());
//因为,这是在父进程中,完成对其子进程的回收,因此要在父进程中注册 信号处理函数
//注册 信号处理函数
struct sigaction act;
act.sa_handler = waitchild_sighandler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sleep(5);//这里的sleep 5s 钟达到的效果是:让子进程都先退出
printf("\n此时all的子进程都先退出了!\n");
sigaction(SIGCHLD,&act,NULL);
printf("\n此时SIGCHLD信号处理函数注册成功!\n");
//等完成SIGCHLD信号的注册之后,解除SIGCHLD信号 的阻塞
//此时内核就会去调用SIGCHLD的信号处理函数
//去让父进程回收子进程资源了!
// sigprocmask(SIG_UNBLOCK,&mask,NULL);
while(1){
sleep(1);//让父进程先别退出(执行别的事情)
//执行别的事情...(当然,这里为了测试,就弄了个 死循环!)
}
}
return 0;
}
此时就算你的SIGCHLD函数注册成功后,也没有SIGCHLD信号了,也就无法让内核调用SIGCHLD对应的信号处理函数来让父进程完成对子进程资源的回收了!
(有可能,你在注册信号处理函数之前,你的所有子进程就全都退出掉了,导致你的子进程都变成僵尸进程了!为deal这个问题,我们在注册SIGCHLD信号前,先将SIGCHLD信号进行阻塞,此时SIGCHLD信号处于未决信号集中,当完成SIGCHLD信号的注册之后,再解除SIGCHLD信号 的阻塞。此时SIGCHLD信号就要被处理了!也即此时信号处理函数需要被调用来回收子进程资源了!)
deal codes:
/**************************************************************
测试代码:
父进程用SIGCHLD信号完成对各个子进程资源的回收工作!
这里我们以循环创建子进程的代码来test!
(父进程循环创建3个子进程)
**************************************************************/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<sys/wait.h>
//SIGCHLD信号处理函数:
void waitchild_sighandler(int signo){
// print("signum==[%d]\n",signo);
//在这里信号处理函数中,需要让父进程 回收 其子进程的资源!(用waitpid函数)
printf("\n此时SIGCHLD信号处理函数开始被内核调用!\n");
while(1){
//收到一个SIGCHLD信号,通过while循环就可以回收多个死掉的子进程
//或者说是有多少个退出了的子进程就回收多少个!防止回收遗漏使得某个子进程变成僵尸进程
//进而浪费系统资源!
//pid_t waitpid(pid_t pid,int* wstatus,int options);
pid_t inputPid = -1;//回收all子进程
int* wstatus = NULL;//不关心子进程的退出 状态
int options = WNOHANG;//非阻塞的
pid_t wait_pid = waitpid(inputPid,wstatus,options);
if(wait_pid > 0){//表示成功回收掉子进程了!
printf("成功回收掉子进程了,此时所回收掉的子进程id==[%d]\n",wait_pid);
continue;
}
else if(wait_pid == -1){
printf("当前没有需要回收掉的子进程了!\n");
break;//退出while循环
}
else if(wait_pid == 0 && options == WNOHANG){
printf("当前需要回收的子进程还在运行中,尚未退出!\n");
continue;
}
}
printf("\n");//回车
}
int main(int argc,char* argv[])
{
//先将SIGCHLD信号 阻塞
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask,SIGCHLD);
sigprocmask(SIG_BLOCK,&mask,NULL);
int i = 0;
for(i=0;i<3;++i)
{
//创建子进程函数原型:
//pid_t fork(void);
pid_t pid = fork();
if(pid < 0)//fork失败的case
{
perror("fork error!");
return -1;
}
else if(pid == 0)//pid == 0 时,则当前进程为子进程
{
printf("child process: pid==[%d],fpid==[%d]\n",getpid(),getppid());
break;
}
else//pid > 0 时,则当前进程为父进程
{
//pid_t getpid(void);
//这个函数会返回调用该函数的进程的ID
//(哪个进程调用它,它就返回谁的PID)
printf("father process: pid==[%d],fpid==[%d]\n",getpid(),getppid());
sleep(2);//此時休眠2s钟
}
}
// 这里sleep(1) sleep(2) sleep(3)
// 是为了让子进程有 先后顺序 而退出!
//这只是为了便于观察而已!你当然可以不设置这个!
//第1个子进程
if(i==0){
printf("this is [%d]th child,PID==[%d]\n",i+1,getpid());
sleep(1);
}
//第2个子进程
if(i==1){
printf("this is [%d]th child,PID==[%d]\n",i+1,getpid());
sleep(2);
}
//第3个子进程
if(i==2){
printf("this is [%d]th child,PID==[%d]\n",i+1,getpid());
sleep(3);
}
//父进程
if(i==3){
printf("this is father,PID==[%d]\n",getpid());
//因为,这是在父进程中,完成对其子进程的回收,因此要在父进程中注册 信号处理函数
//注册 信号处理函数
struct sigaction act;
act.sa_handler = waitchild_sighandler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sleep(5);//这里的sleep 5s 钟达到的效果是:让子进程都先退出
printf("\n此时all的子进程都先退出了!\n");
sigaction(SIGCHLD,&act,NULL);
printf("\n此时SIGCHLD信号处理函数注册成功!\n");
//等完成SIGCHLD信号的注册之后,解除SIGCHLD信号 的阻塞
//此时内核就会去调用SIGCHLD的信号处理函数
//去让父进程回收子进程资源了!
sigprocmask(SIG_UNBLOCK,&mask,NULL);
while(1){
sleep(1);//让父进程先别退出(执行别的事情)
//执行别的事情...(当然,这里为了测试,就弄了个 死循环!)
}
}
return 0;
}
result2: