Linux中创建Daemon进程的三种方法
什么是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进程的三种方法的更多相关文章
- cocos2dx中创建标签CCLabel的三种方法及特点
创建标签的三种方式:1.CCLabelTTF (True Type Font,又叫本地字体)这是最简单,也是最常用的方式,不依赖于资源文件,也不依赖于某个系统,所指定的字体如果系统没有,则会提 ...
- ubuntu/linux mint 创建proc文件的三种方法(四)
在做内核驱动开发的时候,能够使用/proc下的文件,获取对应的信息,以便调试. 大多数/proc下的文件是仅仅读的,但为了演示样例的完整性,都提供了写方法. 方法一:使用create_proc_ent ...
- ubuntu/linux mint 创建proc文件的三种方法(两)
在这样做的内核驱动程序的开发时间.可以使用/proc下档.获取相应的信息.对于调试. 大多数/proc下的文件是仅仅读的.但为了演示样例的完整性.都提供了写方法. 方法一:使用create_proc_ ...
- Linux中Kill掉进程的10种方法
常规篇: 首先,用ps查看进程,方法如下: 复制代码 代码如下: $ ps -ef……smx 1822 1 0 11:38 ? 00:00:49 gnome-terminalsmx 1823 1822 ...
- spring中创建bean对象的三种方式以及作用范围
时间:2020/02/02 一.在spring的xml配置文件中创建bean对象的三种方式: 1.使用默认构造函数创建.在spring的配置文件中使用bean标签,配以id和class属性之后,且没有 ...
- linux 环境变量PATH路径的三种方法
转:http://www.jb51.net/LINUXjishu/150167.html 总结:修改1.#PATH=$PATH:/etc/apache/bin 或者#vi /etc/profile ...
- 【转载】取得系统中网卡MAC地址的三种方法
From:http://blog.csdn.net/zhangting1987/article/details/2732135 网卡地址这个概念有点混淆不清.因为实际上有两个地址,mac地址和物理地址 ...
- .net中创建xml文件的两种方法
.net中创建xml文件的两种方法 方法1:根据xml结构一步一步构建xml文档,保存文件(动态方式) 方法2:直接加载xml结构,保存文件(固定方式) 方法1:动态创建xml文档 根据传递的值,构建 ...
- Java中获取键盘输入值的三种方法
Java中获取键盘输入值的三种方法 Java程序开发过程中,需要从键盘获取输入值是常有的事,但Java它偏偏就没有像c语言给我们提供的scanf(),C++给我们提供的cin()获取键盘输入值 ...
随机推荐
- 201521123025 <<java程序设计>>第3周学习总结
1. 本周学习总结 2. 书面作业 Q1.代码阅读 public class Test1 { private int i = 1;//这行不能修改 private static int j = 2; ...
- 201521123007《Java程序设计》第14周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自 ...
- 201521123007《Java程序设计》第13周学习总结
1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...
- 201521123010 《Java程序设计》第13周学习总结
1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...
- 201521123065《java程序设计》第12周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 1.流的划分:输入流:字节流(InputStream).字符流(reader): 输出流:字节流(Output ...
- 201521123093 JAVA程序设计
团队博客链接 /[博客链接]http://www.cnblogs.com/yayaya/p/7062197.html 课程设计---购物车系统(201521123093 赵铭) 1.个人负责模块或者任 ...
- 201521123119《Java程序设计》第11周学习总结
1. 本周学习总结 Q1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线程 Q1.互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问 ...
- Jar文件 META-INF/MANIFEST.MF文件详解
打开Java的JAR文件我们经常可以看到文件中包含着一个META-INF目录, 这个目录下会有一些文件,其中必有一个MANIFEST.MF,这个文件描述了该Jar文件的很多信息,下面将详细介绍MANI ...
- shell脚本之变量与状态码
目录: 前言 如何创建一个脚本 脚本调试 变量相关 变量的命令规则 bash中变量的种类 本地变量 环境变量 只读和位置变量 位置变量 查询变量 进程的退出状态与状态码 前言 在linux管理中,sh ...
- SpringMVC第四篇【参数绑定详讲、默认支持参数类型、自定义参数绑定、RequestParam注解】
参数绑定 我们在Controller使用方法参数接收值,就是把web端的值给接收到Controller中处理,这个过程就叫做参数绑定- 默认支持的参数类型 从上面的用法我们可以发现,我们可以使用req ...