Linux系统编程——信号

信号的概念与机制

信号的概念

 信号再我们的生活中随处可见,如:古代的摔杯为号,鸣金收兵,以及如今的信号弹。它们都有共性:
1.简单、2. 不能携带大量信息、3.满足某个特设的条件才能发送

 信号是信息的载体,在 Unix/Linux 环境下,古老、经典的通信方式,目前依然是主要的通信手段。

信号的机制

  A 给 B 发送信号, B 收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕后再继续执行。与硬件中断类似--异步模式。但是信号实在软件层面上实现的中断,早期被称为 软中断

信号的特质

 由于信号通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用于来说,这个延迟时间非常短,不易察觉。
每个进程收到的所有信号都是由内核负责发送的,内核处理

与信号相关的事件和状态

产生信号:

  1. 按键产生,如:Ctrl + cCtrl + zCtrl + \
  2. 系统调用产生,如:killraiseabort
  3. 软件条件产生,如:定时器 alarm
  4. 硬件异常产生,如:非法访问内存(段错误),除0(浮点数例外)、内存对齐出错(总线错误)
  5. 命令产生,如:kill命令

递达:产生并且送达到进程,直接被内核处理掉。

未决:产生与递达之间状态,主要由于阻塞(屏蔽)导致该状态。

信号的处理方式

  1. 执行默认操作
  2. 忽略(丢弃)
  3. 捕捉(自定义)

 Linux 内核的 PCB 进程控制块是一个结构体,task struct 除了包含了进程 id, 状态,工作目录,用户 id,组 id,文件描述符表,还包含了信号的相关信息,主要指阻塞信号集未决信号集

阻塞信号集(信号屏蔽字):本质:位图。将某些信号加入该集合,对它们设置屏蔽,当 X 信号被屏蔽后,再收到该信号,该信号将再解除屏蔽后执行

未决信号集

  1. 本质:位图

  2. 信号产生,未决信号集中描述该信号的位立刻翻转为 1, 表示信号处于未决状态,当信号被处理后对应位翻转为 0,这一时刻往往非常短暂

  3. 信号产生后由于某些原因(主要是阻塞)不能抵达,这类信号的集合称之为未决信号集,在屏蔽解除前,信号一直处于未决状态。
    阻塞信号集和未决信号集.png

信号编号

通过 kill -l 可以查看全部的信号编号
image.png

 在 Linux 下,每个信号的名字都以字符 SIG 开头,每个信号和一个数字编码相对应,在头文件 signum.h 中,这些信号都被定义为正整数。信号名定义路径:/usr/include/i386-linux-gnu/bits/signum.h

列表中,编号为 1 ~ 31 的信号为传统 UNIX 支持的信号,是不可靠信号(非实时的),编号为 32 ~ 63 的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。前者有默认得处理动作与事件,而后者没有。

非可靠信号一般都有确定的用途及含义, 可靠信号则可以让用户自定义使用。

信号四要素

1. 编号 2. 名称 3. 事件 4. 默认处理动作

可通过 man 7 signal查看帮助文档获取,也可以vim /usr/src/linux-headers-4.15.0-101/arch/s390/include/uapi/asm/signal.h

Signal     Value     Action   Comment
──────────────────────────────────────────────────────────────────────
SIGHUP        1       Term    Hangup detected on controlling terminal
                                     or death of controlling process
SIGINT        2       Term    Interrupt from keyboard
SIGQUIT       3       Core    Quit from keyboard
SIGILL        4       Core    Illegal Instruction
SIGABRT       6       Core    Abort signal from abort(3)
SIGFPE        8       Core    Floating point exception
SIGKILL       9       Term    Kill signal
SIGSEGV      11       Core    Invalid memory reference
SIGPIPE      13       Term    Broken pipe: write to pipe with no
                                     readers
SIGALRM      14       Term    Timer signal from alarm(2)
SIGTERM      15       Term    Termination signal
SIGUSR1   30,10,16    Term    User-defined signal 1
SIGUSR2   31,12,17    Term    User-defined signal 2
SIGCHLD   20,17,18    Ign     Child stopped or terminated
SIGCONT   19,18,25    Cont    Continue if stopped
SIGSTOP   17,19,23    Stop    Stop process
SIGTSTP   18,20,24    Stop    Stop typed at terminal
SIGTTIN   21,21,26    Stop    Terminal input for background process
SIGTTOU   22,22,27    Stop    Terminal output for background process

 在标准信号中,有一些信号是有三个 Value第一个值通常对 alphasparc架构,中间的值通常用于x86arm 和其他架构,而最右侧的值则用于 mips架构。一个 -表示在对应架构上尚未定义该信号。所以通常建议使用信号的名称而非信号的编号

 不同的操作系统定义了不同的编号,因此有些信号出现在 Unix 系统内,也出现在 Linux 中,而有的信号出现在 FreeBSDMac os 中却没有出现在 Linux 下。这里我们指研究 Linux 系统中的信号。

Linux常规信号一览表

  1. SIGHUP:当用户退出 shell 时,由该 shell 启动的所有进程都将收到这个信号。 默认动作为终止进程

  2. SIGINT:当用户按下 Ctrl + c 组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。

  3. SIGQUIT:当用户按下 Ctrl + \ 组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。

  4. SIGILL:CPU检测到进程执行了非法指令。默认动作为终止进程,并产生 core 文件。

  5. SIGTRAP:该信号由断点指令或其他 trap 指令产生。默认动作为终止进程,并产生 core 文件。

  6. SIGABRT:调用 abort 函数时产生的信号。默认动作为终止进程,并产生 core 文件。

  7. SIGBUS:非法访问内存地址,包括内存对齐出错。默认动作为终止进程,并产生 core 文件。

  8. SIGFPE:在发生致命的运算错误时发出,不仅包括浮点运算错误,还包括溢出及除数为 0 等所有的算法错误。默认动作为终止进程,并产生 core 文件。

  9. SIGKILL无条件终止进程,本信号不能被忽略,处理和阻塞。默认动作为终止进程,它向系统管理员提供了可以杀死任何进程的方法。

  10. SIGUSE1:用户自定义的信号,即程序员可以在程序中定义并使用该信号。默认动作为终止进程。

  11. SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程,并产生 core 文件。

  12. SIGUSER2:另一个用户自定义的信号,即程序员可以在程序中定义并使用该信号。默认动作为终止进程。

  13. SIGPIPEBroken pipe 向一个没有读端的管道写数据。默认动作为终止进程。

  14. SIGALRM:定时器超时,超时的事件由系统调用 alarm 设置。默认动作为终止进程。

  15. SIGTERM:程序结束信号,与 SIGKILL 不同的是,该信号可以被阻塞和终止。通常用来使程序正常退
    出,执行 shell 命令 kill时,缺省产生这个信号。默认动作为终止进程。

  16. SIGSTKFLT:Liunx 早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。

  17. SIGCHLD:子进程状态发生变化时,父进程会收到这个信号。默认动作忽略这个信号。

  18. SIGCONT:如果进程已停止,则继续使其运行。默认动作为继续/忽略

  19. SIGSTOP停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。

  20. SIGTSTP:停止终端交互进程的运行。按下 Ctrl + z组合键时发出这个信号。默认动作为暂停进程。

  21. SIGTTIN:后台进程读终端控制台。默认动作为暂停进程

  22. SIGTTOUT:该信号类似于 SIGTTIN ,在后台进程向终端输出数据时发送。默认动作为暂停进程。

  23. SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达,如网络带外数据到达。默认动作为忽略该信号。

  24. SIGXCPU:进程执行时间超过了分配给该进程的 CPU 时间,系统产生该信号并发送给该进程。默认动作为终止进程。

  25. SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程

  26. SIGVTALRM:虚拟时钟超时时产生该信号,类似于 SIGALRM,但是该信号只计算该进程占用 CPU 的使用时间。默认动作为终止进程。

  27. SIGPROF:类似于 SIGVTALRM ,它不仅包括该进程占用 CPU 时间还包括执行系统调用的时间。默认动作为终止进程。

  28. SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号。

  29. SIGIO:此信号向进程发出了一个异步 IO 事件。默认动作为忽略。

  30. SIGPWR:关机。默认动作为终止进程。

  31. SIGSYS:无效的系统调用。默认动作为终止进程,并产生 core 文件。

  32. SIGRTMIN ~ 64. SIGRTMAX:Linux 的实时信号,他们没有固定的含义(可以由用户组定义)。所有的实时信号默认动作都是终止进程。

在以上列出的信号中,比较特殊的有:

  • 程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP
  • 不能恢复至默认动作的信号有:SIGILL,SIGTRAP
  • 默认会导致进程流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
  • 默认会导致进程退出的信号有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
  • 默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
  • 默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH
  • 此外,SIGIOSVR4 是退出,在 4.3BSD 中是忽略;SIGCONT 在进程挂起时是继续,否则是忽略,不能被阻塞。 

上述的信号中标注橙色的信号为常用信号,需要记住信号的名称,发生的事件以及默认的处理动作。

信号的产生

按键产生

  • Ctrl + c --> 2.SIGINT(终止/中断)
  • Ctrl + z --> 20.SIGTSTP(暂停/停止)
  • Ctrl + \ --> 3.SIGQUIT(退出)

硬件异常产生信号

  • 除 0 操作 --> 8. SIGFPE(浮点数例外)
  • 非法访问内存 --> 11. SIGSEGC(段错误)
  • 总线错误 --> 7. SIGBUS

kill函数/命令产生信号

kill 命令产生信号 kill-SIGKILL pid
kill 函数

作用: 给指定进程发送指定信号(不一定杀死)

函数原型: int kill(pid_t pid, int sig);

头文件:

  • #include <sys/types.h>
  • #include <signal.h>

参数:

  • pid:进程号
    • pid > 0:发送信号给指定进程
    • pid = 0:发送信号给于调用 kill 函数进程属于同一进程组的所有进程
    • pid = -1:发送信号给所有该进程有权限发送的系统中的所有进程
    • pid < -1:取 |pid|然后发送信号给对应的进程组
  • sig:要发送的信号,不推荐直接使用数字,应使用宏名

返回值:

  • 成功:返回 0
  • 失败:-1,设置 errno(ID非法,信号非法,普通用户杀 init 进程等权级问题)

进程组: 没个进程都属于一个进程组,进程组是一个或多个进程集合,他们相互关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组 ID 与进程组长 ID 相同。

demo

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <stdlib.h>
#include <signal.h>

void sys_error(char *str){
    perror(str);
    exit(1);
}

int main(int argc, char *argv[]){
    int i = 0;

    while(1){ 
        i++;
        if(i == 5) //死循环,如果循环了5次,发送 SIGKILL 信号杀死该进程
            kill(getpid(), SIGKILL);
        else
            printf("%d\n",i);
    }   

    return 0;
}

软件条件产生信号

alarm函数

函数原型: unsigned int alarm(unsigned int seconds);
作用:设置定时器(闹钟),使用自然计时法,在指定seconds后,内核会给当前进程发送 14.SIGALRM 信号,进程收到该信号,默认终止进程。

每个进程都有且只有一个定时器

返回值:

  • 成功:返回上次定时剩余时间
  • 没有失败现象

alarm(0);取消闹钟。

定时与进程状态无关!就绪、运行、挂起(阻塞、暂停)、终止、僵尸...无论进程处于何种状态 alarm 都计时。

demo

测试计算机 1 秒钟能数多少个数

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>

void sys_error(char *str){
    perror(str);
    exit(1);
}

int main(int argc, char *argv[]){
    int i = 0;
    alarm(1);
    while(1){
        i++;
        printf("%d\n",i);
    }   
    return 0;
}

通过 time 命令查看程序执行时间,分别直接如下两条指令

  • time ./程序名
    image.png

  • time ./程序名 > out.txt
    image.png

    • real:程序实际执行所用的事件
    • user:在用户空间的用时
    • sys:在内核空间的用时

上述执行结果表明了两个问题:

  1. 实际执行时间 = 系统时间 + 用户时间 + 等待时间
  2. 通过上述的结果发现,在使用输出重定向后,减少了等待时间(向设备打印),程序执行的效率显著提高,所以程序运行的瓶颈在于 I/O,优化程序首选优化 I/O

setitimer

作用: 设置定时器(闹钟),可替代 alarm 函数,精度为微秒(us) ,可以实现周期定时。

函数原型:
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);

头文件:
#include <sys/time.h>

参数:

  • which
    • ITMER_REAL --> 14.SIGALRM 自然定时,计算自然时间
    • ITMER_VIRTUAL --> 26.SIGVTALRM 虚拟空间(用户空间)计时,只计算进程占用 CPU 时间
    • ITMER_PROF --> 27.SIGPROF 运行时时计时(用户+内核),计算占用 CPU 及执行系统调用的时间
  • new_value:定时秒数
//new_value是一个结构体,里面内容如下
struct itimerval {

    struct timeval {
        time_t      tv_sec;         /* seconds */
        suseconds_t tv_usec;        /* microseconds */
    }it_interval;---> 周期定时秒数

    struct timeval {
        time_t      tv_sec; //秒
        suseconds_t tv_usec;    //微秒    
    }it_value;  ---> 第一次定时秒数  

};
  • old_value:传出参数,上次定时剩余时间。

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno
demo

使用setitimer函数实现alarm函数,计算计算机 1 秒能数多少个数

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/time.h>

void sys_error(char *str){
    perror(str);
    exit(1);
}

int main(int argc, char *argv[]){
    int i = 0;
    struct itimerval newtime;
    newtime.it_interval.tv_sec = 0; //设置周期定时,两次定时之间的间隔时常
    newtime.it_interval.tv_usec = 0;
    
    newtime.it_value.tv_sec = 1; //设置定时市场
    newtime.it_value.tv_usec = 0;

    setitimer(ITIMER_REAL, &newtime, NULL); //三种计算时常的方式
    //setitimer(ITIMER_VIRTUAL, &new, NULL);
    //setitimer(ITIMER_PROF, &new, NULL);

    while(1){
        i++;
        printf("%d\n",i);
    }   
    return 0;
}

信号集操作函数

先来回顾一下未决信号集是怎么回事。

 信号从产生到抵达目的地,叫作信号递达。而信号从产生到递达的中间状态,叫作信号的未决状态。产生未决状态的原因有可能是信号受到阻塞了,也就是信号屏蔽字(或称阻塞信号集,mask)对应位被置1。阻塞信号集和未决信号集均是由内核维护的,整个过程如下图示:

内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字mask可以影响未决信号集。而我们可以在应用程序中自定义 set 来改变 mask 。已达到屏蔽指定信号的目的。

sigset_t set;    //typedef unsigner long sigset_t

int sigemptyset(sigset_t *set)    //将某个信号集清 0   成功返回:0, 失败返回:-1

int sigfillset(sigset_t *set)    //将某个信号集置  1   成功返回:0, 失败返回:-1

int sigaddset(sigset_t *set, int signum);    //将某个信号加入信号集   成功返回:0, 失败返回:-1

int sigdelset(sigset_t *set, int signum);/将某个信号清楚出信号集   成功返回:0, 失败返回:-1

int sigismember(const sigset_t *set, int signum);/判断某个信号是否在信号集中   存在返回:1, 不在返回:-1

sigset_t 类型的本质是位图,但不应该直接使用位操作,而因该使用上述函数,保证跨系统操作有效

sigprocmask函数

通过上述函数创建完信号集后,通过系统提供的 sigprocmask 函数去改变信号屏蔽集。

作用:用来 屏蔽/解除屏蔽信号,本质是读取或修改进程的信号屏蔽集(PCB中)
严格注意,屏蔽信号:只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢处理。

函数原型
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

头文件:
#include <signal.h>

参数:

  • how:取值如下(假设当前的信号屏蔽字为 mask)
    • SIG_BLOCK:当 how 设置为此值时,set表示需要屏蔽的信号,相当于 mask = mask|set
    • SIG_UNBLOCK:当 how 设置为此值时,set表示需要屏蔽的信号,相当于 mask = mask&~set
    • SIG_SETMSK:当 how 设置为此值时,set表示用于代替原始屏蔽的新屏蔽集,相当于 mask = set,若调用 sigprocmask 解除了对当前若干个信号的阻塞,则在 sigprocmask 返回前,至少将其中一个信号递达
  • set:传入参数,本质是一个位图,set 中哪个位置是 1,就代表屏蔽哪个信号
  • oldset:传出参数,保存旧的信号屏蔽集

返回值:

  • 成功:返回 0
  • 失败:返回 -1 设置 errno

sigpending函数

作用:读取当前进程的未决信号集

函数原型:
int sigpending(sigset_t *set);

参数:

  • set:传出参数,传出的未决信号集。

demo

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>

void sys_error(char *str){
    perror(str);
    exit(1);
}

void print(sigset_t *set){
    int i = 1;
    for(i = 1;i < 32;i++){
        if(sigismember(set, i))  //判断信号是否未决
            printf("1");
        else
            printf("0");
    }   
    printf("\n");
}

int main(int argc, char *argv[]){
    sigset_t set, sys_set;
    
    sigemptyset(&set); 
    sigaddset(&set, SIGINT); //将 SIGINT 信号屏蔽
    sigprocmask(SIG_BLOCK, &set, NULL); //设置 mask

    while(1){
        sigpending(&sys_set); //获取当前未决信号集
        print(&sys_set);
        sleep(1);
    }   
    return 0;
}

信号捕捉

signal函数

作用注册 一个信号捕捉函数

函数原型:

  • typedef void (*sighandler_t)(int);
  • sighandler_t signal(int signum, sighandler_t handler);
demo
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <signal.h>

void sys_error(char *str){
    perror(str);
    exit(1);
}

void catch_sig(int signo){
    printf("catch signo is %d\n", signo);
}

int main(int argc, char *argv[]){
    signal(SIGINT, catch_sig);
    while(1);

    return 0;
}

该函数由 ANSI 定义,由于历史原因在不同版本的 Unix 和不同版本的 Linux 中可能有不同的行为。因此应该尽量避免使用它,取而代之使用 sigaction 函数。

sigaction函数

作用:修改一个信号的处理动作(在 Linux 中通常用于注册 一个信号捕捉函数)

函数原型:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数:

  • signum:要改变的信号
  • act:传入参数,新的处理方式
  • oldact:传出参数,旧的处理方式
​struct sigaction {
​     
    void (*sa_handler)(int);
​    void (*sa_sigaction)(int, siginfo_t *, void *);//当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。(很少使用)
​    sigset_t sa_mask;
​    int sa_flags;
​    void (*sa_restorer)(void);//该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。(弃用)

​};

重点掌握:

  • sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为 SIG_IGN 表忽略 或 SIG_DFL 表执行默认动作

  • sa_mask: 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。

  • sa_flags:通常设置为0,表使用默认属性(捕捉函数执行期间,本信号自动被屏蔽)。

demo
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>

void sys_error(char *str){
    perror(str);
    exit(1);
}

void catch_sig(int signo){
    printf("catch signo is %d\n", signo);
}

int main(int argc, char *argv[]){

    struct sigaction act;
    
    act.sa_handler = catch_sig;
    sigemptyset(&act.sa_mask);
        
    int ret = sigaction(SIGINT, &act, NULL);
    
    if(ret == -1) 
        sys_error("sigaction error");

    while(1);

    return 0;
}

信号捕捉的特性

  • 进程正常运行时,默认 PCB 中有一个信号屏蔽字,假定为 ☆ ,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由☆来指定。而是用 sa_mask 来指定。调用完信号处理函数,再恢复为 ☆。

  • XXX信号捕捉函数执行期间,XXX信号自动被屏蔽。

  • 阻塞的常规信号不支持排队,产生多次只记录一次。(后32个实时信号支持排队)

内核实现信号捕捉过程

image.png

SIGCHLD 信号

产生条件:只要子进程状态产生变化都会产生该信号

  • 子进程终止时
  • 子进程接收到 SIGSTOP 信号停止时
  • 子进程处于停止状态,接收到 SIGCONT 后唤醒时

借助 SIGCHL 信号回收子进程

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>

void sys_err(){
	perror(str);
	return;
}

void catch_child(int signo){//当捕捉到信号时,执行这个函数(回收
	pid_t wpid;
	int status;
	while((wpid = waitpid(-1, &status,0)) != -1){//循环回收,防止僵尸进程出现
		if(WIFEXITED(status))
			printf("catch child id %d, ret = %d", wpid,WEXITSTATUS(status));
	}
	return;
}


int main(int argc, char *argv[]){
	pid_t pid;
	//阻塞
	sigset_t set;
	sigemptyset(&set);
	sigaddset(&set, SIGCHLD);
	sigprocmask(SIG_BLOCK,&set, NULL);

	int i;
	for(i = 0; i<15; i++)
		if((pid = fork()) ==0 )//循环创建多个子进程
			break;
	
	if(15 == i){
		//父进程 =======> 注册信号捕捉函数
		struct sigaction act;
		act.sa_handler = catch_child;//设置回调函数
		sigemptyset(&act.sa_mask);//设置捕捉函数期间屏蔽字
		act.sa_flags = 0;//设置默认属性,本信号自动屏蔽
		sigaction(SIGCHLD, &act, NULL);//这就是捕捉信号sigchld的信号捕捉函数啦
	//解除阻塞
		sigprocmask(SIG_UNBLOCK, &set, getpid());//如果不解除阻塞,回调函数没有执行机会
		
		while(1);//模拟父进程进行后续逻辑
	} else { //子进程,该干啥干啥,干完死了发送信号,信号被捕捉,执行handler即回收函数
	
		printf("I'm child pid = %d\n", getpid());
		return i;
	}
	return 0;
}

注意点:

  • 既然是子进程回收,那首先创建子进程循环创建子进程
  • 父进程设置信号捕捉函数
  • 子进程结束后就会死亡,父进程捕捉到信号进行回收
  • 关于阻塞和解除阻塞步骤:如果不阻塞有可能信号捕捉函数还没有注册完子进程就结束了,那就捕捉不到了,用自定义的 set 集合进行与 mask 作用进行阻塞,用sigprocmask`