什么是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. 201521123102 《Java程序设计》第5周学习总结

    1. 本周学习总结 2. 书面作业 Q1.代码阅读:Child压缩包内源代码 1.1 com.parent包中Child.java文件能否编译通过?哪句会出现错误?试改正该错误.并分析输出结果. 不能 ...

  2. 201521123003《Java程序设计》第14周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自 ...

  3. 201521123022 《Java程序设计》 第九周学习总结

    1.本章学习总结 2. 书面作业 本次PTA作业题集异常 1.常用异常 题目5-1 Q1.1 截图你的提交结果(出现学号) Q1.2 自己以前编写的代码中经常出现什么异常.需要捕获吗(为什么)?应如何 ...

  4. 运算符(C# 参考)

    reference:https://msdn.microsoft.com/zh-cn/library/zkacc7k1(v=vs.140).aspx

  5. 语音传输之RTP/RTCP/UDP及软件实现关键点

    语音通信是实时通信,一定要保证实时性,不然用户体验会很糟糕.IETF设计了RTP来承载语音等实时性要求很高的数据,同时设计了RTCP来保证服务质量(RTP不保证服务质量).在传输层,一般选用UDP而不 ...

  6. oracle pl/sql 控制结构(分支,循环,控制)

    一.pl/sql的进阶--控制结构在任何计算机语言(c,java,pascal)都有各种控制语句(条件语句,循环结构,顺序控制结构...),在pl/sql中也存在这样的控制结构.在本部分学习完成后,希 ...

  7. Microsoft Visual Studio调试监视器(MSVSMON.EXE)未能启动

    在启动VS2010项目时,遇到如图所示"Microsoft Visual Studio调试监视器(MSVSMON.EXE)未能启动"的问题. 原因是VS2010安装路径(我的是D: ...

  8. XCode消除警告、错误

    1.集成支付宝SDK后,报一堆warning: (arm64) /Users/scmbuild/workspace/standard-pay/.....警告 解决方法: 1)  Go to Build ...

  9. Apache Spark 2.2.0 中文文档 - 集群模式概述 | ApacheCN

    集群模式概述 该文档给出了 Spark 如何在集群上运行.使之更容易来理解所涉及到的组件的简短概述.通过阅读 应用提交指南 来学习关于在集群上启动应用. 组件 Spark 应用在集群上作为独立的进程组 ...

  10. 变量的声明和定义以及extern的用法

    变量的声明和定义以及extern的用法                                          变量的声明不同于变量的定义,这一点往往容易让人混淆. l         变量 ...