|
5 | 5 | ----------
|
6 | 6 | 问题
|
7 | 7 | ----------
|
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系统上面运行的守护进程运行的程序。 |
10 | 9 |
|
11 | 10 | |
|
12 | 11 |
|
13 | 12 | ----------
|
14 | 13 | 解决方案
|
15 | 14 | ----------
|
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) |
93 | 47 | 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): |
99 | 74 | raise SystemExit(1)
|
100 | 75 |
|
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) |
102 | 110 |
|
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 | 111 | else:
|
108 |
| - print('Not running', file=sys.stderr) |
| 112 | + print('Unknown command {!r}'.format(sys.argv[1]), file=sys.stderr) |
109 | 113 | raise SystemExit(1)
|
110 | 114 |
|
111 |
| - else: |
112 |
| - print('Unknown command {!r}'.format(sys.argv[1]), file=sys.stderr) |
113 |
| - raise SystemExit(1) |
| 115 | +要启动这个守护进程,用户需要使用如下的命令: |
| 116 | + |
| 117 | +:: |
114 | 118 |
|
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 | + ... |
116 | 127 |
|
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文件和日志。要停止这个守护进程,使用: |
125 | 130 |
|
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 | +:: |
129 | 132 |
|
130 |
| -bash % daemon.py stop |
131 |
| -bash % |
| 133 | + bash % daemon.py stop |
| 134 | + bash % |
132 | 135 |
|
133 | 136 | |
|
134 | 137 |
|
135 | 138 | ----------
|
136 | 139 | 讨论
|
137 | 140 | ----------
|
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