参考《Linux/Unix 系统编程手册》
变成daemon,一个程序需要完成以下步骤:
1、执行一个fork(),父进程退出,子进程继续执行。(daemon成为了init进程的子进程)
--假设daemon是从命令行启动,父进程的终止会被shell发现,之后shell会显示出另一个shell提示符并让子进程在后台运行;
--子进程被确保不会成为一个进程组的首进程(它从父进程继承了进程组ID,并拥有了自己唯一的进程ID,该进程ID与继承来的进程组ID是不同的,这样才能成功执行下面一个步骤)
2、子进程调用setsid()开启一个新会话并释放它与控制终端之间的所有关联;
3、对于终端设备的处理:
--daemon从未打开过终端设备,不需做任何处理;
--daemon后面可能会打开一个终端设备,必须采取措施确保该设备不会成为控制终端,具体措施:
1、在所有可能应用到一个终端设备上的open() 调用中指定O_NOCTTY标记;
2、在setsid()调用之后执行第二个fork(),再次让父进程退出并让孙子进程继续执行,(这样确保了子进程不会成为会话组长,进程永远不会重新请求一个控制终端--根据SystemV中获取终端规则)
4、清除进程的umask(确保daemon创建文件和目录时拥有必要的权限)
5、修改进程当前工作目录(通常改为根目录 /,保证根目录的文件系统不会被卸载即可,如:cron会将自身放在/var/spool/cron 目录下)
6、关闭daemon从父进程继承而来的所有打开着的文件描述符(由于daemon失去了控制终端,对0,1,2描述符完全可以关闭;无法卸载长时间运行的daemon打开的文件所在的文件系统----文件描述符是一种有限资源)
7、关闭0,1,2文件描述符后,daemon通常会打开/dev/null,并让所有描述符都指向该设备
--这样确保了当daemon调用在这些文件描述符上执行IO的库函数时不会出乎意料的失败;
实例:
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
|
import
sys,os,time
def
main():
""" A demo daemon main routine, write a datestamp to /tmp/daemon-log every 10 seconds."""
f
=
open
(
"/tmp/daemon-log"
,
"w"
)
while
1
:
f.write(
'%s/n'
%
time.ctime(time.time()))
f.flush()
time.sleep(
10
)
if
__name__
=
=
"__main__"
:
# do the UNIX double-fork magic
try
:
pid
=
os.fork()
if
pid >
0
:
# exit first parent
sys.exit(
0
)
except
OSError, e:
print
>>sys.stderr,
"fork #1 failed: %d (%s)"
%
(e.errno, e.strerror)
sys.exit(
1
)
# decouple from parent environment
os.chdir(
"/"
)
os.setsid()
os.umask(
0
)
# do second fork
try
:
pid
=
os.fork()
if
pid >
0
:
# exit from second parent, print eventual PID before
print
"Daemon PID %d"
%
pid
sys.exit(
0
)
except
OSError, e:
print
>>sys.stderr,
"fork #2 failed: %d (%s)"
%
(e.errno, e.strerror)
sys.exit(
1
)
# start the daemon main loop
main()
|