守护(Daemon)进程是linux中的一种重要的系统管理手段,一般名称都以d结尾,运行在系统的后台,完成一些监控或者服务的工作。在Python中也是可以实现这样的功能,不过现在好像也有外部库支持了,其实也不难。

实现原理

我们的任务就是需要将自己的python进程从shell中抽离出来,首先其父进程不能是shell否则会随着shell的结束而被清除,其次需要将自己的输入输出重定向一下。Linux中的setsid命令就可以让一个进程独立到后台运行,这里使用os模块的setsid函数达到同样的效果。

不过既然是守护进程还要能够结束才行,除了自己手动kill的方法,还可以使用pidfile这种方式,在生成的时候将自己的进程id写入一个pidfile中,这样如果再次运行就可以直接杀死之前的守护进程了。

代码示例

下面是我自己写的一个小例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/env python
# coding: utf-8
# timestampd.py

import os, sys
import time
import signal

def write_files():
while True:
with open('stamp-record.txt','a') as f:
f.write(time.strftime('[%Y-%m-%d %H:%M:%S]\n'))
time.sleep(3)

def write_pid_file():
with open('timestamp.pid','w') as f:
f.write('%s' % os.getpid())

def start():
pid = os.fork()
if pid != 0:
os._exit(0)
os.close(0)
sys.stdin = open('/dev/null')
os.close(1)
sys.stdout = open('/dev/null', 'w')
os.close(2)
sys.stderr = open('/dev/null', 'w')
os.setsid()
os.umask(0)
write_pid_file()
write_files()

def end():
pid = int(open('timestamp.pid','r').read())
os.kill(pid, signal.SIGTERM)
os.remove('timestamp.pid')

if __name__ == '__main__':
if len(sys.argv) < 2 or sys.argv[1] == 'start':
start()
else:
end()

一个简单的时间戳写入程序,运行./timestampd start,可以启动守护进程,进程首先fork一份,然后父进程自己退出。子进程关闭自己的0,1,2标准输入输出流并将它们重新定向到/dev/null,同时设置umask为0,这样即使shell结束程序仍然在后台运行。可以用tail -f stamp-record.txt查看文件的增长( 这是tail的一个经典用法:) )

结束它的方法就是再次运行./timestampd end,根据之前记录的pidfile中的pid杀死守护进程即可。

疑惑

其实我还有一些不太明白的地方。

网上找到的很多教程都是需要fork两次,最后的孙子进程才能成为守护进程。对此,有博主给出的解释是如果fork一次出来的进程“访问终端文件时会失去控制权”,对于这一层由于我自己对linux的终端控制机制也不太了解,不能给出很合理的解释。

上面的代码是我看了supervisor(python写的进程监控工具)的源码之后把其中的守护进程部分抽取出来简化而成的,它的代码也就fork了一次就完成了任务,为什么网上那么多教程都是写着要fork两次呢,这两者之间有什么区别,还有待进一步研究…