Skip to content

Commit d64d87a

Browse files
committed
12.14小节完成,12章完成
1 parent 5ea31e7 commit d64d87a

File tree

1 file changed

+166
-165
lines changed

1 file changed

+166
-165
lines changed

source/c12/p14_launching_daemon_process_on_unix.rst

+166-165
Original file line numberDiff line numberDiff line change
@@ -5,191 +5,192 @@
55
----------
66
问题
77
----------
8-
You would like to write a program that runs as a proper daemon process on Unix or
9-
Unix-like systems.
8+
你想编写一个作为一个在Unix或类Unix系统上面运行的守护进程运行的程序。
109

1110
|
1211
1312
----------
1413
解决方案
1514
----------
16-
Creating a proper daemon process requires a precise sequence of system calls and careful
17-
attention to detail. The following code shows how to define a daemon process along
18-
with the ability to easily stop it once launched:
19-
20-
#!/usr/bin/env python3
21-
# daemon.py
22-
23-
import os
24-
import sys
25-
26-
import atexit
27-
import signal
28-
29-
def daemonize(pidfile, *, stdin='/dev/null',
30-
stdout='/dev/null',
31-
stderr='/dev/null'):
32-
33-
if os.path.exists(pidfile):
34-
raise RuntimeError('Already running')
35-
36-
# First fork (detaches from parent)
37-
try:
38-
if os.fork() > 0:
39-
raise SystemExit(0) # Parent exit
40-
except OSError as e:
41-
raise RuntimeError('fork #1 failed.')
42-
43-
os.chdir('/')
44-
os.umask(0)
45-
os.setsid()
46-
# Second fork (relinquish session leadership)
47-
try:
48-
if os.fork() > 0:
49-
raise SystemExit(0)
50-
except OSError as e:
51-
raise RuntimeError('fork #2 failed.')
52-
53-
# Flush I/O buffers
54-
sys.stdout.flush()
55-
sys.stderr.flush()
56-
57-
# Replace file descriptors for stdin, stdout, and stderr
58-
with open(stdin, 'rb', 0) as f:
59-
os.dup2(f.fileno(), sys.stdin.fileno())
60-
with open(stdout, 'ab', 0) as f:
61-
os.dup2(f.fileno(), sys.stdout.fileno())
62-
with open(stderr, 'ab', 0) as f:
63-
os.dup2(f.fileno(), sys.stderr.fileno())
64-
65-
# Write the PID file
66-
with open(pidfile,'w') as f:
67-
print(os.getpid(),file=f)
68-
69-
# Arrange to have the PID file removed on exit/signal
70-
atexit.register(lambda: os.remove(pidfile))
71-
72-
# Signal handler for termination (required)
73-
def sigterm_handler(signo, frame):
74-
raise SystemExit(1)
75-
76-
signal.signal(signal.SIGTERM, sigterm_handler)
77-
78-
def main():
79-
import time
80-
sys.stdout.write('Daemon started with pid {}\n'.format(os.getpid()))
81-
while True:
82-
sys.stdout.write('Daemon Alive! {}\n'.format(time.ctime()))
83-
time.sleep(10)
84-
85-
if __name__ == '__main__':
86-
PIDFILE = '/tmp/daemon.pid'
87-
88-
if len(sys.argv) != 2:
89-
print('Usage: {} [start|stop]'.format(sys.argv[0]), file=sys.stderr)
90-
raise SystemExit(1)
91-
92-
if sys.argv[1] == 'start':
15+
创建一个正确的守护进程需要一个精确的系统调用序列以及对于细节的控制。
16+
下面的代码展示了怎样定义一个守护进程,可以启动后很容易的停止它。
17+
18+
.. code-block:: python
19+
20+
#!/usr/bin/env python3
21+
# daemon.py
22+
23+
import os
24+
import sys
25+
26+
import atexit
27+
import signal
28+
29+
def daemonize(pidfile, *, stdin='/dev/null',
30+
stdout='/dev/null',
31+
stderr='/dev/null'):
32+
33+
if os.path.exists(pidfile):
34+
raise RuntimeError('Already running')
35+
36+
# First fork (detaches from parent)
37+
try:
38+
if os.fork() > 0:
39+
raise SystemExit(0) # Parent exit
40+
except OSError as e:
41+
raise RuntimeError('fork #1 failed.')
42+
43+
os.chdir('/')
44+
os.umask(0)
45+
os.setsid()
46+
# Second fork (relinquish session leadership)
9347
try:
94-
daemonize(PIDFILE,
95-
stdout='/tmp/daemon.log',
96-
stderr='/tmp/dameon.log')
97-
except RuntimeError as e:
98-
print(e, file=sys.stderr)
48+
if os.fork() > 0:
49+
raise SystemExit(0)
50+
except OSError as e:
51+
raise RuntimeError('fork #2 failed.')
52+
53+
# Flush I/O buffers
54+
sys.stdout.flush()
55+
sys.stderr.flush()
56+
57+
# Replace file descriptors for stdin, stdout, and stderr
58+
with open(stdin, 'rb', 0) as f:
59+
os.dup2(f.fileno(), sys.stdin.fileno())
60+
with open(stdout, 'ab', 0) as f:
61+
os.dup2(f.fileno(), sys.stdout.fileno())
62+
with open(stderr, 'ab', 0) as f:
63+
os.dup2(f.fileno(), sys.stderr.fileno())
64+
65+
# Write the PID file
66+
with open(pidfile,'w') as f:
67+
print(os.getpid(),file=f)
68+
69+
# Arrange to have the PID file removed on exit/signal
70+
atexit.register(lambda: os.remove(pidfile))
71+
72+
# Signal handler for termination (required)
73+
def sigterm_handler(signo, frame):
9974
raise SystemExit(1)
10075
101-
main()
76+
signal.signal(signal.SIGTERM, sigterm_handler)
77+
78+
def main():
79+
import time
80+
sys.stdout.write('Daemon started with pid {}\n'.format(os.getpid()))
81+
while True:
82+
sys.stdout.write('Daemon Alive! {}\n'.format(time.ctime()))
83+
time.sleep(10)
84+
85+
if __name__ == '__main__':
86+
PIDFILE = '/tmp/daemon.pid'
87+
88+
if len(sys.argv) != 2:
89+
print('Usage: {} [start|stop]'.format(sys.argv[0]), file=sys.stderr)
90+
raise SystemExit(1)
91+
92+
if sys.argv[1] == 'start':
93+
try:
94+
daemonize(PIDFILE,
95+
stdout='/tmp/daemon.log',
96+
stderr='/tmp/dameon.log')
97+
except RuntimeError as e:
98+
print(e, file=sys.stderr)
99+
raise SystemExit(1)
100+
101+
main()
102+
103+
elif sys.argv[1] == 'stop':
104+
if os.path.exists(PIDFILE):
105+
with open(PIDFILE) as f:
106+
os.kill(int(f.read()), signal.SIGTERM)
107+
else:
108+
print('Not running', file=sys.stderr)
109+
raise SystemExit(1)
102110
103-
elif sys.argv[1] == 'stop':
104-
if os.path.exists(PIDFILE):
105-
with open(PIDFILE) as f:
106-
os.kill(int(f.read()), signal.SIGTERM)
107111
else:
108-
print('Not running', file=sys.stderr)
112+
print('Unknown command {!r}'.format(sys.argv[1]), file=sys.stderr)
109113
raise SystemExit(1)
110114
111-
else:
112-
print('Unknown command {!r}'.format(sys.argv[1]), file=sys.stderr)
113-
raise SystemExit(1)
115+
要启动这个守护进程,用户需要使用如下的命令:
116+
117+
::
114118

115-
To launch the daemon, the user would use a command like this:
119+
bash % daemon.py start
120+
bash % cat /tmp/daemon.pid
121+
2882
122+
bash % tail -f /tmp/daemon.log
123+
Daemon started with pid 2882
124+
Daemon Alive! Fri Oct 12 13:45:37 2012
125+
Daemon Alive! Fri Oct 12 13:45:47 2012
126+
...
116127

117-
bash % daemon.py start
118-
bash % cat /tmp/daemon.pid
119-
2882
120-
bash % tail -f /tmp/daemon.log
121-
Daemon started with pid 2882
122-
Daemon Alive! Fri Oct 12 13:45:37 2012
123-
Daemon Alive! Fri Oct 12 13:45:47 2012
124-
...
128+
守护进程可以完全在后台运行,因此这个命令会立即返回。
129+
不过,你可以像上面那样查看与它相关的pid文件和日志。要停止这个守护进程,使用:
125130

126-
Daemon processes run entirely in the background, so the command returns immedi‐
127-
ately. However, you can view its associated pid file and log, as just shown. To stop the
128-
daemon, use:
131+
::
129132

130-
bash % daemon.py stop
131-
bash %
133+
bash % daemon.py stop
134+
bash %
132135

133136
|
134137
135138
----------
136139
讨论
137140
----------
138-
This recipe defines a function daemonize() that should be called at program startup to
139-
make the program run as a daemon. The signature to daemonize() is using keyword-
140-
only arguments to make the purpose of the optional arguments more clear when used.
141-
This forces the user to use a call such as this:
142-
143-
daemonize('daemon.pid',
144-
stdin='/dev/null,
145-
stdout='/tmp/daemon.log',
146-
stderr='/tmp/daemon.log')
147-
148-
As opposed to a more cryptic call such as:
149-
# Illegal. Must use keyword arguments
150-
daemonize('daemon.pid',
151-
'/dev/null', '/tmp/daemon.log','/tmp/daemon.log')
152-
153-
The steps involved in creating a daemon are fairly cryptic, but the general idea is as
154-
follows. First, a daemon has to detach itself from its parent process. This is the purpose
155-
of the first os.fork() operation and immediate termination by the parent.
156-
After the child has been orphaned, the call to os.setsid() creates an entirely new
157-
process session and sets the child as the leader. This also sets the child as the leader of
158-
a new process group and makes sure there is no controlling terminal. If this all sounds
159-
a bit too magical, it has to do with getting the daemon to detach properly from the
160-
terminal and making sure that things like signals don’t interfere with its operation.
161-
The calls to os.chdir() and os.umask(0) change the current working directory and
162-
reset the file mode mask. Changing the directory is usually a good idea so that the
163-
daemon is no longer working in the directory from which it was launched.
164-
The second call to os.fork() is by far the more mysterious operation here. This step
165-
makes the daemon process give up the ability to acquire a new controlling terminal and
166-
provides even more isolation (essentially, the daemon gives up its session leadership
167-
and thus no longer has the permission to open controlling terminals). Although you
168-
could probably omit this step, it’s typically recommended.
169-
Once the daemon process has been properly detached, it performs steps to reinitialize
170-
the standard I/O streams to point at files specified by the user. This part is actually
171-
somewhat tricky. References to file objects associated with the standard I/O streams are
172-
found in multiple places in the interpreter (sys.stdout, sys.__stdout__, etc.). Simply
173-
closing sys.stdout and reassigning it is not likely to work correctly, because there’s no
174-
way to know if it will fix all uses of sys.stdout. Instead, a separate file object is opened,
175-
and the os.dup2() call is used to have it replace the file descriptor currently being used
176-
177-
by sys.stdout. When this happens, the original file for sys.stdout will be closed and
178-
the new one takes its place. It must be emphasized that any file encoding or text handling
179-
already applied to the standard I/O streams will remain in place.
180-
A common practice with daemon processes is to write the process ID of the daemon in
181-
a file for later use by other programs. The last part of the daemonize() function writes
182-
this file, but also arranges to have the file removed on program termination. The atex
183-
it.register() function registers a function to execute when the Python interpreter
184-
terminates. The definition of a signal handler for SIGTERM is also required for a graceful
185-
termination. The signal handler merely raises SystemExit() and nothing more. This
186-
might look unnecessary, but without it, termination signals kill the interpreter without
187-
performing the cleanup actions registered with atexit.register(). An example of
188-
code that kills the daemon can be found in the handling of the stop command at the
189-
end of the program.
190-
More information about writing daemon processes can be found in Advanced Pro‐
191-
gramming in the UNIX Environment, 2nd Edition, by W. Richard Stevens and Stephen
192-
A. Rago (Addison-Wesley, 2005). Although focused on C programming, all of the ma‐
193-
terial is easily adapted to Python, since all of the required POSIX functions are available
194-
in the standard library.
195-
141+
本节定义了一个函数 ``daemonize()`` ,在程序启动时被调用使得程序以一个守护进程来运行。
142+
``daemonize()`` 函数只接受关键字参数,这样的话可选参数在被使用时就更清晰了。
143+
它会强制用户像下面这样使用它:
144+
145+
::
146+
147+
daemonize('daemon.pid',
148+
stdin='/dev/null,
149+
stdout='/tmp/daemon.log',
150+
stderr='/tmp/daemon.log')
151+
152+
而不是像下面这样含糊不清的调用:
153+
::
154+
155+
# Illegal. Must use keyword arguments
156+
daemonize('daemon.pid',
157+
'/dev/null', '/tmp/daemon.log','/tmp/daemon.log')
158+
159+
创建一个守护进程的步骤看上去不是很易懂,但是大体思想是这样的,
160+
首先,一个守护进程必须要从父进程中脱离。
161+
这是由 ``os.fork()`` 操作来完成的,并立即被父进程终止。
162+
163+
在子进程变成孤儿后,调用 ``os.setsid()`` 创建了一个全新的进程会话,并设置子进程为首领。
164+
它会设置这个子进程为新的进程组的首领,并确保不会再有控制终端。
165+
如果这些听上去太魔幻,因为它需要将守护进程同终端分离开并确保信号机制对它不起作用。
166+
调用 ``os.chdir()`` 和 ``os.umask(0)`` 改变了当前工作目录并重置文件权限掩码。
167+
修改目录通常是个好主意,因为这样可以使得它不再工作在被启动时的目录。
168+
169+
另外一个调用 ``os.fork()`` 在这里更加神秘点。
170+
这一步使得守护进程失去了获取新的控制终端的能力并且让它更加独立
171+
(本质上,该daemon放弃了它的会话首领低位,因此再也没有权限去打开控制终端了)。
172+
尽管你可以忽略这一步,但是最好不要这么做。
173+
174+
一旦守护进程被正确的分离,它会重新初始化标准I/O流指向用户指定的文件。
175+
这一部分有点难懂。跟标准I/O流相关的文件对象的引用在解释器中多个地方被找到
176+
(sys.stdout, sys.__stdout__等)。
177+
仅仅简单的关闭 ``sys.stdout`` 并重新指定它是行不通的,
178+
因为没办法知道它是否全部都是用的是 ``sys.stdout`` 。
179+
这里,我们打开了一个单独的文件对象,并调用 ``os.dup2()`` ,
180+
用它来代替被 ``sys.stdout`` 使用的文件描述符。
181+
这样,``sys.stdout`` 使用的原始文件会被关闭并由新的来替换。
182+
还要强调的是任何用于文件编码或文本处理的标准I/O流还会保留原状。
183+
184+
守护进程的一个通常实践是在一个文件中写入进程ID,可以被其他程序后面使用到。
185+
``daemonize()`` 函数的最后部分写了这个文件,但是在程序终止时删除了它。
186+
``atexit.register()`` 函数注册了一个函数在Python解释器终止时执行。
187+
一个对于SIGTERM的信号处理器的定义同样需要被优雅的关闭。
188+
信号处理器简单的抛出了 ``SystemExit()`` 异常。
189+
或许这一步看上去没必要,但是没有它,
190+
终止信号会使得不执行 ``atexit.register()`` 注册的清理操作的时候就杀掉了解释器。
191+
一个杀掉进程的例子代码可以在程序最后的 ``stop`` 命令的操作中看到。
192+
193+
更多关于编写守护进程的信息可以查看《UNIX 环境高级编程》, 第二版
194+
by W. Richard Stevens and Stephen A. Rago (Addison-Wesley, 2005)。
195+
尽管它是关注与C语言编程,但是所有的内容都适用于Python,
196+
因为所有需要的POSIX函数都可以在标准库中找到。

0 commit comments

Comments
 (0)