linux下的进程控制

我们的一个系统在父进程退出后子进程偶尔出现不能正常的退出问题,这篇文章就是记录解决这个问题的过程。在unix系统上我们通过fork函数产生一个新的进程,这个新产生的进程我们称为子进程,调用fork函数的进程则是父进程。

父进程获取子进程的状态

父进程有时需要获取子进程的状态,这可以实现一些有趣的功能,例如秒起。posix标准里提供了 waitpid函数,通过waitpid父进程可以获取特定pid进程的状态。这个函数的原型如下:

pid_t waitpid(pid_t pid, int *wstatus, int options)
pid < -1   表示wait进程组id是pid绝对值这个组中的所有子进程
pid = -1   表示wait所有子进程
pid = 0    表示wait当前进程组中的所有子进程
pid  > 0   表示wait进程id等于pid的子进程

option参数默认填0就可以了, 如果所有进程都运行,函数默认会处于阻塞状态,如果有进程终止,则会返回终止进程的pid。如果没有任何子进程,该函数就会报错。进程的状态会保存在wstatus参数中,我们通过以下宏来查看

WIFEXITED(wstatus)  进程正常终止返回真,可以进一步通过 WEXITSTATUS(wstatus)获取推出状态
WIFSIGNALED(wstatus) 若为异常退出,返回真,这时可以通过 +(wstatus) 进一步获取让进程退出的信号
WCOREDUMP(wstatus) 判断子进程是否产生了coredump文件。 

通过这个函数我们来看看如何实现简单的进程秒起,下面是示例代码:

#include <cstdio>
#include <cstdlib>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>

int child_is_dead(){
    int stat;
    while(1) {
        pid_t pid = waitpid(0, &stat, 0);
        if(WIFEXITED(stat)) {
            printf("child :%d is dead, exit(%d) \n", pid, WEXITSTATUS(stat));
            return 1;
        } else if(WIFSIGNALED(stat)) {
            printf("child :%d is dead, recv sign:%d \n", pid, WTERMSIG(stat));
            return 1;
        }
    }
    return 0;
}

void start_worker() {
    printf("worker start, pid:%d , ppid:%d , gid:%d \n", getpid(), getppid(), getgid());
    sleep(10);
}

int main(int argc, char **argv) {
    while(1) {
        pid_t pid = fork();
        if(pid == 0){
            start_worker();
            return 0;
        } else if(pid > 0) {
            child_is_dead();
        } else {
            perror("fork error");
            break;
        }
    }
    return 0;
}

运行时结果如下图所示,worker退出时,master进程总能感知到,并且重新拉起worker进程

process_alive.png

在上面的实现中有一个问题就是父进程异常退出后,子进程变成孤儿进程,不能及时退出。 程序重启后可能会出现多个worker进程,导致服务异常。

子进程如何感知父进程退出?

一种简单的方法是在master进程中捕捉导致进程退出的信号,然后在进程退出时向worker进程发送一个SIGTERM信号, 这种实现在大多数情况下都能很好的工作,但是我们发现当我们用 kill -9 master_pid,来杀死master进程,worker进程并不会退出。 而这里的原因很简单,SIGKILL 是两个不能被捕获的信号之一(另一个是SIGSTOP),系统收到这个信号后,会立即终止该进程。所以上面的处理方法在一些特定情况下会有问题。

另外一种思路,当master进程异常退出,worker进程就会变成孤儿进程,被系统的INIT进程给收养。此时如果我们通过getppid()来查询父进程id,会发现父进程id变成了1. 此时认为当前worker进程已经变成了孤儿进程,需要退出, 这种方法的缺点就是需要轮训父进程的id,效率较低。 类似的,还可以通过一个pipe 实现这样的功能。 首先介绍一下pipe(管道)。 pipe是linux下一种很基础也很古老的IPC形式,它只能用于父子进程或者兄弟进程之间进行通信。 并且只有pipe的读端(fd0)存在的情况下, 向写端(fd1)写入数据时才能成功,否则内核会触发SIGPIPE信号,我们可以捕捉SIGPIPE信号。 利用这一特性,我们也能及时的感知父进程的状态。 以下是示例代码,当父进程退出后,write操作会触发SIGPIPE信号,并引起worker终止执行:

#include <cstdio>
#include <cstdlib>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, char **argv) {
    int pipe_fd[2];
    char w_buf[10];

    if(pipe(pipe_fd) < 0) {
        perror("pipe create error");
        return -1;
    }

    pid_t pid = fork();
    if(pid == 0){
        int ret = write(pipe_fd[1], w_buf, 4); 
        if(ret < 0) {
            perror("write to pipe error");
        }
    } else {
        sleep(1000);
    }
    return 0;
}

这是一种相对较好,也比较通用的的方法,幸运的是,如果我们的程序之运行在linux平台中,则可以使用linux提供了一个函数prctl,函数原型如下:

#include <sys/prctl.h>
int prctl(int option, unsigned long arg2, unsigned long arg3,
            unsigned long arg4, unsigned long arg5);

这个系统调用是操作系统进程的相关参数的,功能受option配置。当 option设置成 PR_SET_PDEATHSIG 时,创建父进程退出时会向子进程发送一个信号,不过如果父进程有多个线程,当创建当前进程的线程退出时,就会触发这个信号。 下面是简化后的代码。

#include <cstdlib>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/prctl.h>

int main(int argc, char **argv) {
    pid_t pid = fork();
    if(pid == 0){
        prctl(PR_SET_PDEATHSIG, SIGKILL);
    } else {
        sleep(1000);
    }
    return 0;
}

由于我们的程序只会运行在linux平台下, 最终我们采用这种prctl结束时触发SIGKILL信号来结束子进程,这种方案代码少,也更好维护。

本文在我blog的地址

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Laoqi's Linux运维专列

FTP服务搭建和配置

55570
来自专栏云计算教程系列

如何在Debian 9上安装最新的MySQL

MySQL是一个着名的开源数据库管理系统,用于存储和检索各种流行应用程序的数据。MySQL是LAMP堆栈中的M,是一组常用的开源软件,也包括Linux,Apac...

87420
来自专栏流柯技术学院

LR常见问题整理

  当一台主机上安装多个浏览器时,LoadRunner录制脚本经常遇到不能打开浏览器的情况,可以用下面的方法来解决。

60440
来自专栏杂烩

redis3.2.8 linux集群安装 原

每台机器在opt/redis下创建一个文件夹,文件夹名称redis-cluster,在这个文件夹下面再创建三个文件夹6379、6380、6381,然后分别在这三...

9030
来自专栏张戈的专栏

Nginx在线服务状态下平滑升级或新增模块的详细操作记录

今天,产品那边发来需求,说有个 APP 的 IOS 版本下载包需要新增 https 协议,在景安购买了免费的 SSL 证书。当我往 nginx 上新增 ssl ...

57570
来自专栏耕耘实录

Linux(Centos7.4及RHEL7.4)环境下NTP服务器的构建

[root@Geeklp201 etc]# cat /etc/redhat-release CentOS Linux release 7.4.1708 (Co...

43020
来自专栏封碎

ubuntu升级之后启动不了的解决 博客分类: Linux UbuntuLinuxDebianAndroidEclipse

我的ubuntu是用wubi安装的,今天准备搞android的ndk,所以把开发环境给配置了一下,jdk、android的sdk、ndk,还有eclipse...

10620
来自专栏开源优测

渗透测试 - kali Linux

渗透测试操作系统 - kali 什么是kali Kali Linux是基于Debian的Linux发行版, 设计用于数字取证操作系统。 由Offensive S...

58240
来自专栏BestSDK

REST API 安全设计指南

REST API 安全设计指南。REST的全称是REpresentational State Transfer,它利用传统Web特点,提出提出一个既适于客户端应...

1K80
来自专栏散尽浮华

kvm虚拟化管理平台WebVirtMgr部署-完整记录(1)

公司机房有一台2U的服务器(64G内存,32核),由于近期新增业务比较多,测试机也要新增,服务器资源十分有限。所以打算在这台2U服务器上部署kvm虚拟化,虚出多...

599100

扫码关注云+社区

领取腾讯云代金券