TCP/IP网络编程笔记Chapter I -8进程与僵尸进程2

TCP/IP网络编程笔记Chapter I -8进程与僵尸进程2

  • 1.信号与signal()函数
  • 2.信号与sigaction()函数
  • 3.利用信号处理技术消灭僵尸进程

在上一篇中,我们知道了进程的创建以及销毁方法,但是还有一个问题困扰着我们:子进程究竟什么时候终止,调用waitpid()函数后需要无休止的等待吗?
子进程终止时识别的主体是操作系统,能不能让操作系统告诉父进程子进程终止了,让父进程暂时先处理子进程再继续工作呢?当然是可以的,我们引入信号处理机制。此时的 信号是在特定事件发生时由操作系统向进程发送的消息。而为了响应消息,也需要执行某些自定义的处理。

1.信号与signal()函数

首先我们需要注册信号,即当进程发现它的子进程结束时,请求操作系统调用的特定函数。信号注册函数原型如下:

#include
void (*signal(int signo,void (*func)(int)))(int);
  • 为了在产生信号时调用,返回之前注册的函数指针
  • 上述函数返回值类型为void函数指针
  • 函数名为signal,参数一为int signo,第二参数为void (*func)(int)
  • 返回类型:参数类型为int型,返回void型函数指针
  • 调用上述函数,发生第一个参数代表的情况时,调用第二个参数所指的函数

signal函数注册了部分特殊情况(第一参数)

  • SIGALRM:已到通过alarm函数注册的时间
  • SIGINT:输入CTRL+C
  • SIGCHILD:子进程终止

eg:
①子进程终止调用mychild函数

signal(SIGCHILD,mychild);

②到达alarm注册时间调用timeout函数

signal(SIGALRM,timeout);

③输入CTRL+C调用keycontrol函数

signal(SIGINT,keycontrol);

以上就是信号注册的过程,在验证前我们介绍下alarm函数。

#include
unsigned int alarm(unsigned int seconds);
  • 返回0或以秒为单位距SIGALRM信号发生所剩的时间
  • 传入正整数seconds,在相应时间后产生SIGALRM信号;传递0则取消预约;未指定信号对应的处理函数则调用会signal终止进程。

下面将验证信号处理的相关实例
首先定义两个信号处理函数timeout、keycontrol并注册SIGALRM和SIGINT信号,之后提供100*3的等待时间,每隔两秒就会触发超时。

#include 
#include 
#include 
 
void timeout(int sig)
{
     
    if (sig == SIGALRM)
        puts("Time out!");
    alarm(2);
}
void keycontrol(int sig)
{
     
    if (sig == SIGINT)
        puts("CTRL+C pressed");
}
 
int main(int argc, char *argv[])
{
     
    signal(SIGALRM, timeout);
    signal(SIGINT, keycontrol);
    alarm(2);
 
    for (int i = 0; i < 3; i++)
    {
     
        puts("wait...");
        sleep(100);
    }
    return 0;
}

结果并不如人意,只输出了三次Time out,理论上每两秒触发超时处理,应该输出上百次。这是因为触发某个信号将唤醒由于sleep函数而进入阻塞的进程后,进程一旦唤醒就不会再进入睡眠状态。所以for循环立即结束开始下一次循环,共输出3次超时。
TCP/IP网络编程笔记Chapter I -8进程与僵尸进程2_第1张图片
同理连按下三次CTRL+C程序同样会结束
TCP/IP网络编程笔记Chapter I -8进程与僵尸进程2_第2张图片

2.信号与sigaction()函数

sigaction()函数可以完全代替signal()函数而且在不同UNIX系统中完全相同,更安全稳定。下面将介绍sigaction()函数可替换signal()函数的功能。

#include  
int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact);
  • signo传递信号信息
  • act对应信号处理函数的信息
  • oldact获取之前注册的信号处理函数指针,不需要则传递0

其中struct sigaction声明如下

struct sigaction
{
     
    void (*sa_handler)(int);//信号处理函数的地址
    sigset_t sa_mask;		//初始化为0
    int sa_flags;			//初始化为0
}

eg
注册sigaction()前需要做如下操作

void timeout(int sig)int main(int argc, char *argv[])
{
     
    struct sigaction act;
    act.sa_handler = timeout;
    sigemptyset(&act.sa_mask);//sa_mask初始化为0
    act.sa_flags = 0;
    sigaction(SIGALRM, &act, 0);
    
    .....

下面用一段完整代码说明sigaction()函数的使用方法

#include 
#include 
#include 
 
void timeout(int sig)
{
     
    if (sig == SIGALRM)
        puts("Time out!");
    alarm(2);
}
 
int main(int argc, char *argv[])
{
     
    int i;
    struct sigaction act;
    act.sa_handler = timeout;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGALRM, &act, 0);
 
    alarm(2);
 
    for (i = 0; i < 3; i++)
    {
     
        puts("wait...");
        sleep(100);
    }
    return 0;
}

可以看出输出和signal()函数相同,可以完全替代signal()函数。
TCP/IP网络编程笔记Chapter I -8进程与僵尸进程2_第3张图片

3.利用信号处理技术消灭僵尸进程

下面我们将以sigaction函数为例,说明信号处理技术消灭僵尸进程的流程。
当子进程终止时将产生SIGCHLD信号,我们通过sigaction函数调用waitpid函数来回收子进程,并打印信息在控制台。waitpid函数在上一节有介绍。

#include 
#include 
#include 
#include 
#include 
 
void read_childproc(int sig)
{
     
    int status;
    pid_t id = waitpid(-1, &status, WNOHANG);//1表示可以回收任意子进程
    if (WIFEXITED(status))					 //回收成功输出进程ID以及返回值
    {
     
        printf("Removed proc id: %d \n", id);
        printf("Child send: %d \n", WEXITSTATUS(status));
    }
}
 
int main(int argc, char *argv[])
{
     
    pid_t pid;
    struct sigaction act;
    act.sa_handler = read_childproc;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGCHLD, &act, 0);
 
    pid = fork();
    if (pid == 0)		//子进程1
    {
     
        puts("Hi! I'm child process");
        sleep(10);
        return 12;
    }
    else
    {
     
        printf("Child proc id: %d \n", pid);
        pid = fork();
        if (pid == 0)	//子进程2
        {
     
            puts("Hi! I'm child process");
            sleep(10);
            exit(24);
        }
        else			//父进程
        {
     
            int i;
            printf("Child proc id: %d \n", pid);
            for (i = 0; i < 5; i++)//父进程每5秒输出wait...
            {
                    
                sleep(5);
                puts("wait...");
            }
        }
    }
    return 0;
}

输出如下,注意sigaction唤醒sleep后不会继续阻塞,所以wait输出的时机需要仔细思考。由于sleep(10)子进程运行共10秒,所以父进程先输出两次wait,之后回收两个子进程并唤醒sleep,唤醒一次立即输出一次wait;最后等待5秒输出最后一次wait程序结束。TCP/IP网络编程笔记Chapter I -8进程与僵尸进程2_第4张图片

你可能感兴趣的:(TCP/IP网络编程,linux,网络,c++)