什么是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();//注释1   /* * Get maximum number of file descriptors. */
  if (getrlimit(RLIMIT_NOFILE, &rl) < )
    err_quit("%s: can't get file limit", cmd);   /* * Become a session leader to lose controlling TTY. */
  if ((pid = fork()) < )//注释2
    err_quit("%s: can't fork", cmd);
  else if (pid != ) /* parent */
    exit();
  setsid();//注释3   /* * Ensure future opens won't allocate controlling TTYs. */
  sa.sa_handler = SIG_IGN;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = ;
  if (sigaction(SIGHUP, &sa, NULL) < )
    err_quit("%s: can't ignore SIGHUP", cmd);
  if ((pid = fork()) < )//注释4
    err_quit("%s: can't fork", cmd);
  else if (pid != ) /* parent */
    exit();   /* * Change the current working directory to the root so * we won't prevent file systems from being unmounted. */
  if (chdir("/") < )//注释5
    err_quit("%s: can't change directory to /", cmd);   /* * Close all open file descriptors. */
  if (rl.rlim_max == RLIM_INFINITY)
    rl.rlim_max = ;
  for (i = ; 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();//注释7
  fd2 = dup();//注释7   /* * Initialize the log file. */
  openlog(cmd, LOG_CONS, LOG_DAEMON);
  if (fd0 != || fd1 != || fd2 != ) {
    syslog(LOG_ERR, "unexpected file descriptors %d %d %d",fd0, fd1, fd2);
    exit();
  }
}

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

* 注释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(,) == -)
  exit(EXIT_FAILURE);   while()
  {
    sleep();
  }
  return ;
}

3. 使用第三方工具supervisor

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

参考资料

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

Linux中创建Daemon进程的三种方法的更多相关文章

  1. cocos2dx中创建标签CCLabel的三种方法及特点

    创建标签的三种方式:1.CCLabelTTF     (True Type Font,又叫本地字体)这是最简单,也是最常用的方式,不依赖于资源文件,也不依赖于某个系统,所指定的字体如果系统没有,则会提 ...

  2. ubuntu/linux mint 创建proc文件的三种方法(四)

    在做内核驱动开发的时候,能够使用/proc下的文件,获取对应的信息,以便调试. 大多数/proc下的文件是仅仅读的,但为了演示样例的完整性,都提供了写方法. 方法一:使用create_proc_ent ...

  3. ubuntu/linux mint 创建proc文件的三种方法(两)

    在这样做的内核驱动程序的开发时间.可以使用/proc下档.获取相应的信息.对于调试. 大多数/proc下的文件是仅仅读的.但为了演示样例的完整性.都提供了写方法. 方法一:使用create_proc_ ...

  4. Linux中Kill掉进程的10种方法

    常规篇: 首先,用ps查看进程,方法如下: 复制代码 代码如下: $ ps -ef……smx 1822 1 0 11:38 ? 00:00:49 gnome-terminalsmx 1823 1822 ...

  5. spring中创建bean对象的三种方式以及作用范围

    时间:2020/02/02 一.在spring的xml配置文件中创建bean对象的三种方式: 1.使用默认构造函数创建.在spring的配置文件中使用bean标签,配以id和class属性之后,且没有 ...

  6. linux 环境变量PATH路径的三种方法

    转:http://www.jb51.net/LINUXjishu/150167.html 总结:修改1.#PATH=$PATH:/etc/apache/bin  或者#vi /etc/profile ...

  7. 【转载】取得系统中网卡MAC地址的三种方法

    From:http://blog.csdn.net/zhangting1987/article/details/2732135 网卡地址这个概念有点混淆不清.因为实际上有两个地址,mac地址和物理地址 ...

  8. .net中创建xml文件的两种方法

    .net中创建xml文件的两种方法 方法1:根据xml结构一步一步构建xml文档,保存文件(动态方式) 方法2:直接加载xml结构,保存文件(固定方式) 方法1:动态创建xml文档 根据传递的值,构建 ...

  9. Java中获取键盘输入值的三种方法

    Java中获取键盘输入值的三种方法     Java程序开发过程中,需要从键盘获取输入值是常有的事,但Java它偏偏就没有像c语言给我们提供的scanf(),C++给我们提供的cin()获取键盘输入值 ...

随机推荐

  1. 201521123007《Java程序设计》第8周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 2. 书面作业 本次作业题集集合 1. List中指定元素的删除(题目4-1) private static ...

  2. 201521123025<<java程序设计>>第4周学习总结

    Q1. 本周学习总结 Q2.书面作业 1.注释的应用 使用类的注释与方法的注释为前面编写的类与方法进行注释,并在Eclipse中查看.(截图) 2.面向对象设计(大作业1,非常重要) 2.1 将在网上 ...

  3. 201521123023《java程序设计》第三周学习总结

    1. 本周学习总结 2. 书面作业 1.代码阅读 public class Test1 { private int i = 1;//这行不能修改 private static int j = 2; p ...

  4. Java内部类的总结

    内部类是指在一个外部类的内部再定义一个类.类名不需要和文件夹相同. 内部类分为: 成员内部类.局部内部类.静态嵌套类.匿名内部类 . 1.成员内部类 成员内部类是最普通的内部类,它的定义为位于另一个类 ...

  5. 短视频图像处理 OpenGL ES 实践

    2017年,短视频正以其丰富的内容表现力和时间碎片化的特点,快速崛起,而短视频最具可玩性之处就在支持人脸识别的动态贴图和各种不同效果的美颜.滤镜等.那短视频动态贴纸.滤镜.美颜等功能究竟是如何实现的呢 ...

  6. 个人理解---在开发中何时加入日志记录功能[java]

    是这样的:俩个月前做的一个小功能,今天经理突然问我这个'清除复投记录'功能是不是我做的,我说是,很久以前了.他说昨天一个客户找过来了,后台把人家的复投记录清除掉了,不知道何时清除的,我记得当时做的时候 ...

  7. 在ZABBIX平台上通过SNMP协议监控网络设备

    在ZABBIX平台上通过SNMP协议监控网络设备 方法一:自动发现监控项 ZABBIX自带模板Template SNMP Interfaces中有"自动发现规则"这一选项,在主机选 ...

  8. php显示距当前多长时间

    <?php header("Content-type: text/html; charset=utf-8");date_default_timezone_set('PRC') ...

  9. java集合系列——Set之HashSet和TreeSet介绍(十)

    一.Set的简介 Set是一个不包含重复元素的 collection.更确切地讲,set 不包含满足 e1.equals(e2) 的元素.对 e1 和 e2,并且最多包含一个为 null 的元素. S ...

  10. Corn Fields poj3254(状态压缩DP)

    Corn Fields Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 6081   Accepted: 3226 Descr ...