什么是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. 201521123025 <<java程序设计>>第3周学习总结

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

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

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

  3. 201521123007《Java程序设计》第13周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...

  4. 201521123010 《Java程序设计》第13周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...

  5. 201521123065《java程序设计》第12周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 1.流的划分:输入流:字节流(InputStream).字符流(reader): 输出流:字节流(Output ...

  6. 201521123093 JAVA程序设计

    团队博客链接 /[博客链接]http://www.cnblogs.com/yayaya/p/7062197.html 课程设计---购物车系统(201521123093 赵铭) 1.个人负责模块或者任 ...

  7. 201521123119《Java程序设计》第11周学习总结

    1. 本周学习总结 Q1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线程 Q1.互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问 ...

  8. Jar文件 META-INF/MANIFEST.MF文件详解

    打开Java的JAR文件我们经常可以看到文件中包含着一个META-INF目录, 这个目录下会有一些文件,其中必有一个MANIFEST.MF,这个文件描述了该Jar文件的很多信息,下面将详细介绍MANI ...

  9. shell脚本之变量与状态码

    目录: 前言 如何创建一个脚本 脚本调试 变量相关 变量的命令规则 bash中变量的种类 本地变量 环境变量 只读和位置变量 位置变量 查询变量 进程的退出状态与状态码 前言 在linux管理中,sh ...

  10. SpringMVC第四篇【参数绑定详讲、默认支持参数类型、自定义参数绑定、RequestParam注解】

    参数绑定 我们在Controller使用方法参数接收值,就是把web端的值给接收到Controller中处理,这个过程就叫做参数绑定- 默认支持的参数类型 从上面的用法我们可以发现,我们可以使用req ...