• Linux中创建Daemon进程的三种方法【转】


    转自:https://www.cnblogs.com/minico/p/7702020.html

    什么是daemon进程?

    Unix/Linux中的daemon进程类似于Windows中的后台服务进程,一直在后台运行运行,例如http服务进程nginx,ssh服务进程sshd等。
    注意,其英文拼写为daemon而不是deamon。

    为什么daemon进程需要特殊的编写步骤?

    daemon进程和普通进程不一样吗?为什么要单独提出如何编写daemon进程呢?
    不知道你是否有过这样的经历,在Linux上面打开一个terminal,输入编译命令进行编译,编译的时间可能比较长,
    这时候你不小心关闭了这个terminal,编译就中断了。因为编译脚本是作为当前terminal的一个子进程来执行的,当terminal退出后,
    子进程也就退出了。而作为daemon进程,我们希望一旦启动就能在后台一直运行,不会随着terminal的退出而结束。
    那么如何能做到这一点呢?有人说用下面的命令行吗?

    > make &

    让编译命令make到后台执行,这样只是造成了make在后台一直运行的假象,它依然没有脱离和terminal之间的父子关系;
    当terminal退出后,make依然会退出。所以针对daemon进程就要用特殊的步骤来编写,以保证在terminal中执行后,
    即使terminal退出,daemon进程仍然在后台运行。

    如何编写daemon进程?

    对于可以用多种方法解决的问题,我们一般只需熟练掌握其中一种最适合自己的即可;
    但是需要知道还有其它的方法,以备不时之需,这里我将介绍三种创建daemon进程的方法。

    1. 首先给出经典名著APUE中的方法:

    复制代码
    #include "apue.h"
    #include <syslog.h>
    #include <fcntl.h>
    #include <sys/resource.h>
    
    void daemonize(const char *cmd){
      int i, fd0, fd1, fd2;
      pid_t pid;
      struct rlimit rl;
      struct sigaction sa;
    
      /* * Clear file creation mask. */
      umask(0);//注释1
    
      /* * Get maximum number of file descriptors. */
      if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
        err_quit("%s: can't get file limit", cmd);
    
      /* * Become a session leader to lose controlling TTY. */
      if ((pid = fork()) < 0)//注释2
        err_quit("%s: can't fork", cmd);
      else if (pid != 0) /* parent */
        exit(0);
      setsid();//注释3
    
      /* * Ensure future opens won't allocate controlling TTYs. */
      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);
      if ((pid = fork()) < 0)//注释4
        err_quit("%s: can't fork", cmd);
      else if (pid != 0) /* parent */
        exit(0);
    
      /* * Change the current working directory to the root so * we won't prevent file systems from being unmounted. */
      if (chdir("/") < 0)//注释5
        err_quit("%s: can't change directory to /", cmd);
    
      /* * Close all open file descriptors. */
      if (rl.rlim_max == RLIM_INFINITY)
        rl.rlim_max = 1024;
      for (i = 0; i < rl.rlim_max; i++)
        close(i);//注释6
    
      /* * Attach file descriptors 0, 1, and 2 to /dev/null. */
      fd0 = open("/dev/null", O_RDWR);//注释7
      fd1 = dup(0);//注释7
      fd2 = dup(0);//注释7
    
      /* * Initialize the log file. */
      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);
      }
    }
    复制代码

    下面是针对上面例子的详细解释:

    * 注释1:因为我们从shell创建的daemon子进程,所以daemon子进程会继承shell的umask,如果不清除的话,会导致daemon进程创建文件时屏蔽某些权限。
    * 注释2:fork后让父进程退出,子进程获得新的pid,肯定不为进程组组长,这是setsid前提。
    * 注释3:调用setsid来创建新的进程会话。这使得daemon进程成为会话首进程,脱离和terminal的关联。
    * 注释4:最好在这里再次fork。这样使得daemon进程不再是会话首进程,那么永远没有机会获得控制终端。如果这里不fork的话,会话首进程依然可能打开控制终端。
    * 注释5:将当前工作目录切换到根目录。父进程继承过来的当前目录可能mount在一个文件系统上,如果不切换到根目录,那么这个文件系统不允许unmount。
    * 注释6:在子进程中关闭从父进程中继承过来的那些不需要的文件描述符。可以通过_SC_OPEN_MAX来判断最高文件描述符(不是很必须).
    * 注释7:打开/dev/null复制到0,1,2,因为dameon进程已经和terminal脱离了,所以需要重新定向标准输入,标准输出和标准错误(不是很必须).

    针对这个例子,首先要说明的是,不管在Unix还是Linux上按照这个例子写的daemon肯定没问题。
    不过我对其中的一些步骤的必要性一直持怀疑态度:

    1) 第二个fork是必须的吗?
    根据APUE中的说法是,这是为了防止后面打开终端的时候又关联到了daemon进程上,这样当终端关闭后,daemon进程就退出了,
    不过个人感觉这种说法有可能已经不再适用了,毕竟大名鼎鼎的nginx也没有fork两次。不过目前我还不知道怎么用实验来证明这个结论。
    2) setsid()是必须的吗?
    按照书上说的是每个进程都属于一个进程组(Process Group),每个进程组都属于一个进程会话(Process Session)。
    这三者的关系如下图所示,当terminal退出的时候,以最初login shell为首的进程回话就结束了。
    这时候,属于这个session的所有进程都会收到SIGHUP信号,导致进程退出。
    执行了第一次fork(),父进程退出了,子进程变成孤儿进程过继给了1号init进程,但是它仍然属于当前登录shell所控制的session,
    调用setsid()的目的是让daemon进程形成独立的Session,这样当terminal退出的时候就影响不到这个daemon进程了。
    但是我在各种Unix,Linux系统上做了实验,不调用setsid(), 并且只fork()一次,然后将当前终端关闭,重新打开一个新的终端,
    发现daemon进程仍然存在,并没有像书中所说会随着terminal的退出而退出,请高人指点迷津。

     

     2. 利用系统库函数daemon()创建daemon进程

    Linux系统还专门提供了一个用来创建daemon进程的系统函数:

    int daemon(int nochdir, int noclose);

    从api的文档描述看该api也调用了fork(),估计内部实现和上面的代码逻辑类似,从其参数作用也可以看出这一点,
    这个api有两个参数,其作用分别对应上面代码中的注释5和注释7。下面是用这个api创建daemon进程的简单示例:

    复制代码
    #include <unistd.h>
    #include <stdlib.h>
    
    int main(void)
    {
      if(daemon(0,0) == -1)
      exit(EXIT_FAILURE);
    
      while(1)
      {
        sleep(60);
      }
      return 0;
    }
    复制代码

    3. 使用第三方工具supervisor

    简单的说supervisor是一个python工具,可以通过编写配置文件来对指定的进程进行管理,比如启动进程,停止进程以及进程退出后自动重启等;
    这样一来,即使一个普通进程通过supervisor管理之后也会变成和daemon进程一样的行为,不会随着terminal的关闭而退出。
    后续我会写一篇关于supervisor的文章专门介绍其具体使用方法。

    参考资料

    http://www.cnblogs.com/mickole/p/3188321.html
    https://dirtysalt.github.io/apue.html#orgheadline174

    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    string类
    设计模式--策略模式
    设计模式--单例模式
    Python 3.7的安装过程
    为什么学习Python?
    折半查找、选择排序在一位数组中的应用
    三天打鱼两天晒网
    字符串逆序
    求字符串长度函数实现的三种方法
    Spring学习笔记一 IOC、AOP原理
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/13609276.html
Copyright © 2020-2023  润新知