1
头图

先给工具
源码和工具都在这里自取
https://github.com/tangbu/myblob/tree/master/c/deamon
这是一个c编译而成的可运行程序,名字叫deamon,比如原本执行 /bin/test -Darg1=value1, 现在执行deamon /bin/test -Darg1=value1就可以
原理浅显易懂,java程序员不影响阅读

发生了什么事情

笔者的电脑操作系统为ubuntu, 数年以前在兴致勃勃的打开postman的时候调试,突然想整理多余的bash命令界面,突然正在工作的postman退出了工作,留下我默默发呆。经过测试,使用postman的时候必须保持bash界面打开状态,也就是我要一直面对这个黑黑的窗口无法关掉
image.png

寻找解决方法

解决办法1 nohup,

网上搜索nohup可以让进程在后台运行,会在当前目录下生成nohup.out,输出会重定向添加到nohup.out里面去,但是并不成功

解决办法2 &符号和-d

命令后面加&符号和-d ,postman -d &, 也不成功页面卡死

解决办法3 disown

使用 disown命令,执行postman的时候界面不给输入命令的机会,导致无法执行disown命令

postman退出原因

执行man bash命令看一下为什么会导致postman退出
image.png
shell退出的时候会向shell启动的jobs发送SIGHUP信号,进程在接收到SIGHUP的时候,如果进程没有提前向操作系统注册信号处理函数的时候,操作系统就会关闭该进程

守护进程

守护进程 在linux操作系统中,守护进程(deamon)是作为后台进程运行的进程,对于在bash启动的任务(job), 如果直接退出job同样会丢失。为了解决这个问题,需要使用守护进程的方式启动任务。

接下来我们看一下如何把命令行运行的程序编程守护进程

守护进程的创建

仍然以命令行直接运行postman为例,执行命令查看进程树结构

ps -ajx | egrep 'postman|bash|PPID'

image.png
可以看到,终端进程和postman命令进程在同一会话ID中,postman的父进程为终端。

从终端创建一个进程A,A进程再fork一个子进程B执行postman命令, 然后A进程退出

这样就可以创建运行postman命令的进程,本质上和直接在终端运行postman命令进程效果一样,但是我们可以通过c代码对B进程属性更改让最终的进程继承更改后的特性

在子进程中创建新会话

从终端创建一个进程A,A进程再fork一个子进程B,这时候,子进程的SID会话id和终端仍然是一个,当终端退出的时候,仍然会发送SIGHUP信号,子进程默认仍然会退出,所以子进程B仍然需要新建一个会话,就用到了下面的函数
setsid()函数
使子进程完全独立出来,脱离终端控制,可以man 3 setsid看一下使用
image.png

改变当前目录为根目录

chidr()函数
防止工作在可卸载的文件系统,文件系统的卸载后,进程也随之消亡, 所以一般更改到根目录下

重设文件权限掩码

umask()函数
image.png
本机的umask的是0002 第一个0不考虑
所以在代码中创建777,002代表写,这个umask创建的文件默认去掉写权限(位运算)775,避免影响到守护进程
umask(0);

关闭文件描述符

重定向dup2()
标准输入输出、错误都给丢掉。然后把继承下来的打开的文件描述符关掉,避免占用资源

实际运行的源码

下面是附注释的C代码


#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>


int main(int argc,  char* argv[])
{
    //pid_t int类型, a代表子进程号
    pid_t a;
    //创建子进程, 父进程中a是子进程号,子进程中a=0
    a = fork();
    if(a > 0)
    {
        // 父进程退出,子进程继承父进程代码块继续运行,a就是子进程号
        exit(0);
    }
//    子进程忽略终端退出SIGHUP信号
    signal(SIGHUP, SIG_IGN);
    
//    创建新会话,脱离原会话,脱离控制终端。
    setsid();
    //脱离原进程组,创建并进入只包含自身的进程组
    setpgrp();
    
    //关闭父辈继承下来的所有文件
    int max_fd = sysconf(_SC_OPEN_MAX);
    for (int i = 0; i < max_fd && i > 2; i++) 
    {
        close(i);
    }

    //设置掩码位
    umask(0);

    int drop_fd=open( "/dev/null", O_RDWR );
    dup2(0, drop_fd);
    dup2(1, drop_fd);
    dup2(2, drop_fd);

    //切换工作路径
    chdir("/");

// deamon postman arg1 args 变成了postman arg1 arg2

    if (argc > 2) {
        char *params[argc - 2];
        memcpy(params, argv + 1, sizeof params);
        params[argc - 3] = NULL;
// execvp支持在环境变量PATH中寻找postman命令并替换代码段,运行新进程的代码段,
        execvp(*params, params);
    } else {
        execvp(argv[1], NULL);

    }
    return 0;
}

接下来执行 deamon postman 命令查看进程关系
image.png
可以看到postman的父进程已经变成了systemd, 比较新的系统守护进程已经由systemd接管,我们的守护进程创建成功


汤卜
33 声望1 粉丝