目录
信号
signal函数
sigaction函数
尝试用信号来处理僵尸进程
我们在之前学习了如何处理“僵尸进程”,但也会有疑问:调用wait和waitpid函数时我们关注的始终是在子进程上,那么父进程上的管控(对于子进程)该如何实现呢?这就引出了接下来讲的内容——信号处理。
信号
信号这个概念大家应该不陌生,诸如汽车的鸣笛、闹钟的响铃等,这些都是信号,用以告诉人们某件事情的发生。而在计算机中,同样有这个概念。在编程上常用信号来对接收到的某些数据或者指令做出与之对应的响应处理。
signal函数
让我们先来看看<signal.h>库中的signal函数,其结构如下:
#include <signal.h>
void (*signal (int signo , void (*func)(int))) (int);
// 为了在产生信号时调用,返回之前注册的函数指针。
// 函数概念:名为signal的返回类型为void型函数指针的,带有一个int参数的函数
// 参数 int signo, void (* func)(int)
// 返回类型:参数为int型,返回void型函数指针。
 
signo参数用来标记触发的条件,func则为将要调用的函数的指针。与参数所相关的宏有:
- SIGALRM: 按照已通过调用alarm函数注册的时间为信号点。
 - SIGINT: 输入CTRL+C暂停时为信号点
 - SIGCHILD: 子进程终止时为信号点。
 
补充:
alarm函数的用法及意义如下:
#include <unistd.h> unsigned int alarm (unsigned int seconds); // 返回 以秒为单位 的,距 SIGALRM 信号发生时所剩余的时间其中 seconds 参数用以传入欲设定的alarm的周期(以s为单位)。若传递0至变量中,则意味着将会取消对SIGALRM信号的预约。同时,若未指定该产生信号所对应的处理函数时,则该进程将被终止。
接下来,让我们尝试来编写 SIGALRM 和 SIGINT 两种事件下的验证demo吧。
singal.cpp
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void alarmEvent(int sig) //包括下面的keyEvent,这类函数被称为信号处理器(Handler)
{
    if (sig == SIGALRM)
    {
        printf("Time out!\n");
    }
    alarm(2);
}
void keyEvent(int sig)
{
    if (sig == SIGINT)
    {
        printf("\nCTRL+C pressed\n");
    }
}
int main(int argc, char *argv[])
{
    //注册:设置SIGGALRM为触发条件,调用alarmEvent函数
    signal(SIGALRM, alarmEvent); 
    //注册:设置SIGINT为触发条件,调用keyEvent函数
    signal(SIGINT, keyEvent);
    //设置时钟周期为2s
    alarm(2); 
    for (size_t i = 0; i < 5; i++)
    {
        printf("Wait for an event to occur...\n");
        sleep(50);  //实际操作上无法休眠50s
    }
    return 0;
} 
运行结果:

验证完毕。
Q:为什么程序在实际运行上,没有感受到在等待事件发生后的50s延迟呢?
A:信号在响应时,尽管存在有阻塞态的进程,但操作系统为了调用信号处理器,会强制唤醒进行阻塞态(sleep中的)进程,并且该进程一旦被唤醒,将不会在进入睡眠状态,因此在操作上,我们并不会感受到会有长达50s的延迟。
sigaction函数
sigaction算是对signal函数的升级,能够更好地支持不同的操作系统(signal函数在不同的操作系统中用法和效果可能会不同)。
接下来让我们来看下这个函数长什么样:
#include <signal.h>
int sigaction(int signo , const struct sigaction * act , struct sigaction *
oldact) ;
// 成功时退回0,失败时退回-1。
/* 参数含义 */
// signo:与signal函数相同,传递信号信息
// act:对应第一个参数的信号处理函数
// oldact:通过此参数获取之前注册的信号处理函数指针,不需要时则传递0。
 
对于sigaction,它的结构体定义如下:
 //结构体做了简化,实际上其中一些变量用宏和typedef做了进一步封装
 struct sigaction
    {
        void (*sa_handler)(int);
        sigset_t sa_mask;
        int sa_flags;
        void (*sa_restorer)(void);
    }; 
参数意义可以参考以下:
- sa_handler:用以保存信号处理函数的指针值
 - sa_mask:需要滤除的附加信号集,省略
 - sa_flags:特殊标记符,省略
 - sa_restorer:欲重置的(信号)处理器,省略
 
OK,让我们编写一个实例来验证这个函数的功能
sigaction.cpp
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void alarmEvent(int sig)
{
    if (sig == SIGALRM)
    {
        printf("Time out!\n");
    }
    alarm(2);
}
int main(int argc, char *argv[])
{
    //声明sigaction结构体变量act
    struct sigaction act;
    //将信号处理函数存入结构体中
    act.sa_handler = alarmEvent;
    //将sa_mask成员的所有位初始化为0
    sigemptyset(&act.sa_mask);
    //将sa_flags成员同样初始化为0
    act.sa_flags = 0;
    //注册:触发条件为SIGALARM,调用act信号处理器中的信息
    sigaction(SIGALRM, &act, 0);
    alarm(2);
    for (size_t i = 0; i < 5; i++)
    {
        printf("Wait for an event to occur...\n");
        sleep(50);
    }
    return 0;
} 
运行结果:

结果验证了之前的想法。
尝试用信号来处理僵尸进程
当我们掌握signal、sigaction两个函数后,便可更好地去管控父、子进程了。在之前我们采用的是wait、waitpid函数来销毁僵尸进程。接下来,请大家尝试一下用信号来消灭“僵尸进程”吧!
欢迎大家在评论区贴出你们的代码~















