zoukankan      html  css  js  c++  java
  • 如何创建守护进程?

    1. 守护进程创建步骤

    守护进程是没有终端的进程, 运行在后台, 常在系统引导时启动. 那么如何创建守护进程呢?
    参照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指向该文件
      可以有效防止产生意外效果

    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, "
    "); // buf末尾粘贴 "
    ", 并以''结束
        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
    ");
        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
    
  • 相关阅读:
    【译】.NET 的新的动态检测分析
    【译】Visual Studio 的 Razor 编辑器的改进
    【译】.NET 5. 0 中 Windows Form 的新特性
    MySQL InnoDB 索引(Index)
    MySQL 全文检索(Full-Text Search)
    MySQL 计算最大值、最小值和中位数
    MySQL 触发器(Triggers)
    MySQL 视图(View)
    MySQL基础知识:MySQL String 字符串处理
    MySQL基础知识:MySQL Connection和Session
  • 原文地址:https://www.cnblogs.com/fortunely/p/14797911.html
Copyright © 2011-2022 走看看