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(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)//注释2err_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)//注释4err_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)//注释5err_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);//注释7fd1 = dup(0);//注释7fd2 = 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://www.cnblogs.com/minico/p/7702020.html
Linux中创建Daemon进程的三种方法相关推荐
- Linux中增加软路由的两种方法,Linux中增加软路由的三种方法
# route add –net IP netmask MASK eth0 # route add –net IP netmask MASK gw IP # route add –net IP/24 ...
- 网络编程(32)—— linux中销毁僵尸进程的四种方法
一.wait函数 函数原型: pid_t wait(int *status); 描述: wait可以回收任意一个僵尸进程,只要系统中存在僵尸进程,调用一次wait,就会回收一个僵尸进程. 参数说明: ...
- Unity中创建游戏物体的三种方法:
通过new GameObject(); 通过Instantiate(): 通过GameObject.CreatePrimitive(): 总结: 1)new GameObject()可通过字符串名字来 ...
- 【export】Linux中export命令介绍,三种方法设置环境变量
✨ 因为一个变量创建时,它不会自动的被在它之后创建的shell进程所知:这时,可用export命令向后面的shell传递变量的值.
- Linux 中设置环境变量的三种方法
1.export 变量名=value 2.变量名=value ; export 变量名 3.declare -x 变量名=value 自定义全局环境变量 1.编辑 /etc/profile vim / ...
- linux系统中清空文件内容的三种方法
1.使用vi/vim命令打开文件后,输入"%d"清空,后保存即可.但当文件内容较大时,处理较慢,命令如下: vim file_name :%d :wq 2.使用cat命令情况,命令 ...
- Linux中执行shell脚本的5种方法总结
Linux中执行shell脚本的4种方法总结,即在Linux中运行shell脚本的4种方法: 方法一:切换到shell脚本所在的目录(此时,称为工作目录)执行shell脚本: 复制代码 代码如下: c ...
- Struts2中action接收参数的三种方法及ModelDriven跟Preparable接口结合JAVA反射机制的灵活用法...
Struts2中action接收参数的三种方法及ModelDriven跟Preparable接口结合JAVA反射机制的灵活用法 www.MyException.Cn 发布于:2012-09-15 ...
- Linux中执行shell脚本的4种方法
这篇文章主要介绍了Linux中执行shell脚本的4种方法总结,即在Linux中运行shell脚本的4种方法,需要的朋友可以参考下. bash shell 脚本的方法有多种,现在作个小结.假设我们编写 ...
最新文章
- 报错-Unknown class in Interface Builder file
- Dockerizing a Node.js web app
- DLL基础——Windows核心编程学习手札之十九
- python输出去空格_Python3基础 print(,end=) 输出内容的末尾加入空格
- 一个鉴黄师的产品之路(11-12更新)
- java在src创建entity文件_java自动生成entity文件
- 深度神经网络DNN的多GPU数据并行框架 及其在语音识别的应用
- Net 分页功能的实现
- BHO插件操作IE浏览器,js调用C#方法
- HDU 5517---Triple(二维树状数组)
- sublime搭建C/C++编译环境
- 三行代码隐藏所有console.log
- vant ui 官方文档_转载:微信小程序UI组件库合集
- CodeForces - 245H Palindromes区间dp_记忆化搜索
- JAVA 反射(JAVA面试题)
- 无法弹出U盘的几种解决方法
- 64位平台C/C++开发注意事项
- 产品思考 - 不剪发的Tony老师
- 2021.03.06【NOIP提高B组】模拟 总结
- ——java中的反射