加载中...

12.14 在Unix系统上面启动守护进程


问题

You would like to write a program that runs as a proper daemon process on Unix orUnix-like systems.

解决方案

Creating a proper daemon process requires a precise sequence of system calls and carefulattention to detail. The following code shows how to define a daemon process alongwith the ability to easily stop it once launched:

!/usr/bin/env python3# daemon.py

import osimport sys

import atexitimport signal

def daemonize(pidfile, *, stdin='/dev/null',> stdout='/dev/null',stderr='/dev/null'):

if os.path.exists(pidfile):raise RuntimeError(‘Already running')

First fork (detaches from parent)try:

if os.fork() > 0:raise SystemExit(0) # Parent exit

except OSError as e:raise RuntimeError(‘fork #1 failed.')
os.chdir(‘/')os.umask(0)os.setsid()# Second fork (relinquish session leadership)try:

if os.fork() > 0:raise SystemExit(0)

except OSError as e:raise RuntimeError(‘fork #2 failed.')

Flush I/O bufferssys.stdout.flush()sys.stderr.flush()

Replace file descriptors for stdin, stdout, and stderrwith open(stdin, ‘rb', 0) as f:

os.dup2(f.fileno(), sys.stdin.fileno())

with open(stdout, ‘ab', 0) as f:os.dup2(f.fileno(), sys.stdout.fileno())with open(stderr, ‘ab', 0) as f:os.dup2(f.fileno(), sys.stderr.fileno())

Write the PID filewith open(pidfile,'w') as f:

print(os.getpid(),file=f)

Arrange to have the PID file removed on exit/signalatexit.register(lambda: os.remove(pidfile))

Signal handler for termination (required)def sigterm_handler(signo, frame):

raise SystemExit(1)

signal.signal(signal.SIGTERM, sigterm_handler)

def main():
import timesys.stdout.write(‘Daemon started with pid {}n'.format(os.getpid()))while True:

sys.stdout.write(‘Daemon Alive! {}n'.format(time.ctime()))time.sleep(10)

if name == ‘main':
PIDFILE = ‘/tmp/daemon.pid'

if len(sys.argv) != 2:print(‘Usage: {} [start|stop]'.format(sys.argv[0]), file=sys.stderr)raise SystemExit(1)if sys.argv[1] == ‘start':try:daemonize(PIDFILE,stdout='/tmp/daemon.log',stderr='/tmp/dameon.log')except RuntimeError as e:print(e, file=sys.stderr)raise SystemExit(1)
main()

elif sys.argv[1] == ‘stop':if os.path.exists(PIDFILE):with open(PIDFILE) as f:os.kill(int(f.read()), signal.SIGTERM)else:print(‘Not running', file=sys.stderr)raise SystemExit(1)else:print(‘Unknown command {!r}'.format(sys.argv[1]), file=sys.stderr)raise SystemExit(1)
To launch the daemon, the user would use a command like this:

bash % daemon.py startbash % cat /tmp/daemon.pid2882bash % tail -f /tmp/daemon.logDaemon started with pid 2882Daemon Alive! Fri Oct 12 13:45:37 2012Daemon Alive! Fri Oct 12 13:45:47 2012...

Daemon processes run entirely in the background, so the command returns immedi‐ately. However, you can view its associated pid file and log, as just shown. To stop thedaemon, use:

bash % daemon.py stopbash %

讨论

This recipe defines a function daemonize() that should be called at program startup tomake the program run as a daemon. The signature to daemonize() is using keyword-only arguments to make the purpose of the optional arguments more clear when used.This forces the user to use a call such as this:

daemonize(‘daemon.pid',stdin='/dev/null,stdout='/tmp/daemon.log',stderr='/tmp/daemon.log')
As opposed to a more cryptic call such as:# Illegal. Must use keyword argumentsdaemonize(‘daemon.pid',

‘/dev/null', ‘/tmp/daemon.log','/tmp/daemon.log')

The steps involved in creating a daemon are fairly cryptic, but the general idea is asfollows. First, a daemon has to detach itself from its parent process. This is the purposeof the first os.fork() operation and immediate termination by the parent.After the child has been orphaned, the call to os.setsid() creates an entirely newprocess session and sets the child as the leader. This also sets the child as the leader ofa new process group and makes sure there is no controlling terminal. If this all soundsa bit too magical, it has to do with getting the daemon to detach properly from theterminal and making sure that things like signals don’t interfere with its operation.The calls to os.chdir() and os.umask(0) change the current working directory andreset the file mode mask. Changing the directory is usually a good idea so that thedaemon is no longer working in the directory from which it was launched.The second call to os.fork() is by far the more mysterious operation here. This stepmakes the daemon process give up the ability to acquire a new controlling terminal andprovides even more isolation (essentially, the daemon gives up its session leadershipand thus no longer has the permission to open controlling terminals). Although youcould probably omit this step, it’s typically recommended.Once the daemon process has been properly detached, it performs steps to reinitializethe standard I/O streams to point at files specified by the user. This part is actuallysomewhat tricky. References to file objects associated with the standard I/O streams arefound in multiple places in the interpreter (sys.stdout, sys.stdout, etc.). Simplyclosing sys.stdout and reassigning it is not likely to work correctly, because there’s noway to know if it will fix all uses of sys.stdout. Instead, a separate file object is opened,and the os.dup2() call is used to have it replace the file descriptor currently being used

by sys.stdout. When this happens, the original file for sys.stdout will be closed andthe new one takes its place. It must be emphasized that any file encoding or text handlingalready applied to the standard I/O streams will remain in place.A common practice with daemon processes is to write the process ID of the daemon ina file for later use by other programs. The last part of the daemonize() function writesthis file, but also arranges to have the file removed on program termination. The atexit.register() function registers a function to execute when the Python interpreterterminates. The definition of a signal handler for SIGTERM is also required for a gracefultermination. The signal handler merely raises SystemExit() and nothing more. Thismight look unnecessary, but without it, termination signals kill the interpreter withoutperforming the cleanup actions registered with atexit.register(). An example ofcode that kills the daemon can be found in the handling of the stop command at theend of the program.More information about writing daemon processes can be found in Advanced Pro‐gramming in the UNIX Environment, 2nd Edition, by W. Richard Stevens and StephenA. Rago (Addison-Wesley, 2005). Although focused on C programming, all of the ma‐terial is easily adapted to Python, since all of the required POSIX functions are availablein the standard library.


还没有评论.