如何创建守护进程?

Posted 明明1109

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何创建守护进程?相关的知识,希望对你有一定的参考价值。

守护进程创建步骤

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

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

  2. 调用fork, 父进程exit
    因为要调用setsid创建会话, 需要确保调用进程(子进程)不是进程组组长, fork子进程可以确保这点.

  3. 调用setsid创建新会话
    子进程调用setsit, 称为新会话首进程, 新进程组组长, 断开终端连接

  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指向该文件
    可以有效防止产生意外效果

创建守护进程代码

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 *cmd) {
    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", cmd);
    }

    // 2. 调用fork, 父进程退出, 子进程称为孤儿被init进程收养
    if ((pid = fork()) < 0)
        err_quit("%s: fork error", cmd);
    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", cmd);

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

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

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

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

    // 初始化日志文件
    openlog(cmd, LOG_CONS, LOG_DAEMON); // 打开一个到系统日志的连接
    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命令

kill -9 12596

以上是关于如何创建守护进程?的主要内容,如果未能解决你的问题,请参考以下文章

linux 如何实现java守护进程编程开发

守护进程守护进程创建代码实现获取系统时间

主进程创建守护进程

ubuntu12.04下创建了一个守护进程,生成了一个可执行文件,如何让这个可执行文件开机自动运行?

NodeJS在windows下怎么启动守护进程

守护进程模型创建思路及详细实现代码