如何创建守护进程?

1. 守护进程创建步骤

守护进程是没有终端的进程, 运行在后台, 常在系统引导时启动. 那么如何创建守护进程呢?
参照APUE 13.3, 创建守护进程步骤:

  1. 调用umask设置进程创建文件的权限屏蔽字(umask), 便于守护进程创建文件
    umask通常设为0, 如果调用库函数创建文件, 可设置为007

  2. 调用fork, 父进程exit
    因为要调用setsid创建会话, 需要确保调用进程(子进程)不是进程组组长, fork子进程可以确保这点.
    PS:不确定程序以何种方式启动,有可能是进程组组长,也有可能不是。

  3. 调用setsid创建新会话
    子进程调用setsid, 成为新会话首进程, 新进程组组长, 断开终端连接。
    PS:对进程组组长调用setsid,会调用失败,返回-1,errno被设置。因此,必须先通过fork+父进程exit,这样子进程会成为孤儿进程,被init收养,从而确保子进程不是进程组长。

  4. 再次调用fork, 父进程exit(可选)
    非必须, 主要是为了确保进程无法通过open /dev/tty 再次获得终端, 因为调用open时, 系统会默认为会话首进程创建控制终端。

  5. 调用chdir, 将当前工作目录更改为根目录
    守护进程一般长期存在, 守护进程存在时, 无法卸载工作目录. 为避免这种情况, 更改当前工作目录为根目录("/").

  6. 调用close, 关闭所有不需要的文件描述符
    open_max, getrlimit, sysconf(_SC_OPEN_MAX) 这3个函数都可以获得文件描述符最高值

  7. 打开/dev/null文件, 让文件描述符0,1,2指向该文件
    可以有效防止产生意外效果

2. 创建守护进程代码

自定义函数daemonize将一个进程转化为守护进程

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdarg.h>

#define MAXLINE 200

static void err_doit(int errnoflag, int error, const char *fmt, va_list ap) {
    char buf[MAXLINE];

    // 将格式化串fmt (参数ap) 转换成字符串存放到buf
    vsnprintf(buf, MAXLINE - 1, fmt, ap);
    if (errnoflag) {
        // snprintf最后一个参数是const char*, vsnprintf最后一个参数是va_list, 功能一样
        snprintf(buf + strlen(buf), MAXLINE - strlen(buf) - 1, ": %s", strerror(error));
    }

    strcat(buf, "\n"); // buf末尾粘贴 "\n", 并以'\0'结束
    fflush(stdout); // 以防stdout, stderr是相同设备, 先冲刷stdout
    fputs(buf, stderr);
}

/**
 * 与系统调用相关的致命错误
 * 打印消息和终止程序
 */
static void err_quit (const char *fmt, ...) {
    va_list ap;

    // va_start, va_end 配对获取可变参数..., 存放到va_list ap中
    va_start(ap, fmt);
    err_doit(0, 0, fmt, ap);
    va_end(ap);

    exit(1);
}

/**
* 将一个进程转换为守护进程
* 步骤:
* 1. 调用umask设置创建文件权限的umask
* 2. 调用fork, 让父进程退出, 子进程成为孤儿进程
* 3. 调用setsid, 创建新会话, 子进程成为新会话首进程以及进进程组组长, 断开控制终端
* 4. 再次调用fork, 并让父进程退出, 子进程不是会话首进程(防止再次获得控制终端)
* 5. 将当前工作目录修改为根目录, 防止无法卸载目录
* 6. 关闭不需要的文件描述符
* 7. 打开/dev/null, 使其具有文件描述符0,1,2
*/
void daemonize(const char *pname) {
    int i, fd0, fd1, fd2;
    pid_t pid;
    struct rlimit rl;
    struct sigaction sa;

    // 1. 调用umask修改创建文件权限的umask
    umask(0);

    if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
        // <=> sysconf(_SC_OPEN_MAX);
        err_quit("%s: getrlimit error", pname);
    }

    // 2. 调用fork, 父进程退出, 子进程称为孤儿被init进程收养
    if ((pid = fork()) < 0)
        err_quit("%s: fork error", pname);
    else if (pid > 0) { // 父进程
        exit(0);
    }

    // 3. 调用setsit, 创建新会话, 子进程成为首进程
    setsid();
   /* 发送SIGHUP信号情形:
    1)终端关闭时, 信号被发送到session首进程, 以及作为job提交的进程(shell 以&方式运行的进程)
    2)session首进程退出时, 该信号被发送到同session的所有前台进程
    3)若父进程退出, 导致进程组成为孤儿进程组, 且该进程组中有进程处于停止状态(收到SIGSTOP信号或SIGSTP), 该信号会被发送到进>程组每个成员
    */
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    if (sigaction(SIGHUP, &sa, NULL) < 0)
        err_quit("%s: can't ignore SIGHUP", pname);

    // 4. 再次调用fork
    if ((pid = fork()) < 0)
        err_quit("%s: fork error", pname);
    else if (pid > 0)
        exit(0);

    // 5. 改变工作目录为 "/"(根目录)
    if (chdir("/") < 0)
        err_quit("%s: can't change directory to /", pname);

    // 6. 关闭不需要的文件描述符
    if (rl.rlim_max == RLIM_INFINITY)
        rl.rlim_max = 1024;
    for (i = 0; i < rl.rlim_max; ++i)
        close(i);

#if 1
    // 7. 附加文件描述符0,1,2到 /dev/null
    // 因为前面已经关闭了所有文件描述符, 因此重新open, dup得到的文件描述符是递增的
    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);
#else
    open("/dev/null", O_RDONLY);
    open("/dev/null", O_RDWR);
    open("/dev/null", O_RDWR);
#endif

    /* 建立到syslog的连接
       LOG_CONS: 若无法发送到syslogd守护进程则登记到控制台
       LOG_DAEMON: 标识消息发送进程的类型为 系统守护进程 */
    openlog(pname, LOG_CONS, LOG_DAEMON);

    /* 文件描述符异常检查: 正常情况fd0, fd1, fd2应该分别是0,1,2 */
    if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
        syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
        exit(1);
    }
}

main函数

#include <stdio.h>
#include <unistd.h>

#include "daemonize.h"

int main() {
    char *s = "mydaemonize";

    printf("ready to convert a normal process into a daemonize process\n");
    daemonize(s);

    while(1)
        sleep(1);
    return 0;
}

检查守护进程

编译, 运行新建的守护进程a.out

$ ./a.out
$ ps -efj
UID        PID  PPID  PGID   SID  C STIME TTY          TIME CMD
martin   12596  1614 12595 12595  0 23:27 ?        00:00:00 ./a.out
$ ps -efj | grep 12595
martin   12596  1614 12595 12595  0 23:27 ?        00:00:00 ./a.out

终结守护进程

用kill -9命令

$ kill -9 12596
posted @ 2021-05-22 00:45  明明1109  阅读(692)  评论(0编辑  收藏  举报