Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端而且周期性地执行某种任务或等待处理某些发生的事件。它不须要用户输入就能运行并且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是经过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。html
首先咱们要了解一些基本概念:python
会话期:mysql
会话期(session)是一个或多个进程组的集合,当集合中只有一个进程组时,sid与该进程组 组长的pid相同,这些进程组共享一个控制终端。这个控制终端一般是建立进程的登陆终端,控制终端,登陆会话和进程组一般是从父进程继承下来的。linux
setsid()函数能够创建一个对话期:web
若是,调用setsid的进程不是一个进程组的组长,此函数建立一个新的会话期;若是是该进程组的组长,则此函数返回错误sql
(1)此进程变成该对话期的首进程shell
(2)此进程变成一个新进程组的组长进程数据库
(3)此进程没有控制终端,若是在调用setsid前,该进程有控制终端,那么与该终端的联系被解除服务器
(4)为了保证这一点,咱们先调用fork()而后exit(),此时只有子进程在运行,fork后的子进程pid是从新分配的,即保证了此时运行的进程永远不会是进程组的组长session
如今咱们来给出建立守护进程所需步骤:
编写守护进程的通常步骤步骤:
(1)在父进程中执行fork并exit推出;
(2)在子进程中调用setsid函数建立新的会话;
(3)在子进程中调用chdir函数,让根目录 ”/” 成为子进程的工做目录;
(4)在子进程中调用umask函数,设置进程的umask为0;
(5)在子进程中关闭任何不须要的文件描述符
以图解两次fork的过程:
前提,会话中只有一个进程组,进程组中只有一个进程;
pid | ppid | pgid(组id) | sid(session id) | 组长(y/n) |
100 | 10 | 100 | 100 | y |
第一次fork时:
pid | ppid | pgid(组id) | sid(session id) | 组长(y/n) |
100 | 10 | 100 | 100 | y |
101 | 100 | 100(extends parent) | 100 | n |
exit父进程:
pid | ppid | pgid(组id) | sid(session id) | 组长(y/n) |
101 | 100 | 100 | 100 | n |
init进程接管:
pid | ppid | pgid(组id) | sid(session id) | 组长(y/n) |
101 | 1 | 100 | 100 | n |
执行setsid后:
pid | ppid | pgid(组id) | sid(session id) | 组长(y/n) |
101 | 1 | 101 | 101 | y |
第二次fork:
pid | ppid | pgid(组id) | sid(session id) | 组长(y/n) |
101 | 1 | 101 | 101 | y |
102 | 101 | 101 | 101 | n |
exit父进程:
pid | ppid | pgid(组id) | sid(session id) | 组长(y/n) |
102 | 101 | 101 | 101 | n |
由init进程接管:
pid | ppid | pgid(组id) | sid(session id) | 组长(y/n) |
102 | 1 | 101 | 101 | n |
完成上面的4个步骤,那么最终的孙子进程就称为守护进程。先看下代码,后面再分析下每一个步骤的缘由。
#!/usr/bin/env python #coding=utf8 import os, sys, time #产生子进程,然后父进程退出 pid = os.fork() if pid > 0: sys.exit(0) #修改子进程工做目录 os.chdir("/") #建立新的会话,子进程成为会话的首进程 os.setsid() #修改工做目录的umask os.umask(0) #建立孙子进程,然后子进程退出 pid = os.fork() if pid > 0: sys.exit(0) #重定向标准输入流、标准输出流、标准错误 sys.stdout.flush() sys.stderr.flush() si = file("/dev/null", 'r') so = file("/dev/null", 'a+') se = file("/dev/null", 'a+', 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) #孙子进程的程序内容 while True: time.sleep(10) f = open('/home/test.txt', 'a') f.write('hello')
上面的程序没有任何错误处理,可是不影响原理分析。若是要应用到项目里,还需完善。下面笔者谈下本身对每一个步骤的理解。
一、fork子进程,父进程退出
一般,咱们执行服务端程序的时候都会经过终端链接到服务器,成功链接后会加载shell环境,终端和shell都是进程,shell进程是终端进程的子进程,经过ps命令能够很容易的查看到。在这个shell环境下一开始执行的程序都是shell进程的子进程,天然会受到shell进程的影响。在程序里fork子进程后,父进程退出,对了shell进程来讲,这个父进程就算执行完了,而产生的子进程会被init进程接管,从而也就脱离了终端的控制。
二、关闭打开的文件描述符和修改子进程的工做目录
进程从建立它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,形成进程所在的文件系统没法卸下以及引发没法预料的错误,子进程在建立的时候会继承父进程的工做目录,若是执行的程序是在u盘里的,就会致使u盘不能卸载。
三、建立新会话
使用setsid后,子进程就会成为新会话的首进程(session leader);子进程会成为新进程组的组长进程;子进程没有控制终端。
四、修改umask
因为umask会屏蔽权限,因此设定为0,这样能够避免读写文件时碰到权限问题。
五、fork孙子进程,子进程退出
通过上面几个步骤后,子进程会成为新的进程组老大,能够从新申请打开终端,为了不这个问题,fork孙子进程出来。
六、重定向孙子进程的标准输入流、标准输出流、标准错误流到/dev/null
由于是守护进程,自己已经脱离了终端,那么标准输入流、标准输出流、标准错误流就没有什么意义了。因此都转向到/dev/null,就是都丢弃的意思。
7. 处理SIGCHLD信号
处理SIGCHLD信号并非必须的。但对于某些进程,特别是服务器进程每每在请求到来时生成子进程处理请求。若是父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。若是父进程等待子进程结束,将增长父进程的负担,影响服务器进程的并发性能。在Linux下能够简单地将SIGCHLD信号的操做设为SIG_IGN。
signal(SIGCHLD,SIG_IGN);
参考资料:
http://www.01happy.com/linux-python-daemon/
http://www.cnblogs.com/mickole/p/3188321.html
http://blog.csdn.net/jason314/article/details/5640969
针对守护这个概念延伸到python,python也有守护线程的概念
python daemon理解:
守护进程只与主进程有相互做用关系;主线程结束为前提,守护进程保证全部的子线程都执行完后就所有退出,即便守护进程没有执行完进程也会退出;
参考资料:
http://www.dongwm.com/archives/guanyuthreadingyanjiuer/