Linux系统编程之信号

本篇博客所回顾的知识(学习目标):

一、信号介绍

 

 

 从图中可以看出,信号的优先级是高于普通操作的!出现信号就一定要先执行完才能继续做之前的事情!

 

注意①:如何查看信号都有哪些呢?

答:用命令: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:

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fanfan21ya

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值