数字营销 · Web开发 · 基础设施

用Python给Linux编写守护进程

通过 Python 在 Linux 中创建简单的守护进程,了解 fork、setsid、文件描述符和信号处理的基本过程。

守护进程(daemon)是指在 UNIX 或其他多任务操作系统中在后台执行的电脑程序,并不会接受电脑用户的直接操控。此类程序会被以进程的形式初始化。通常,守护进程没有任何存在的父进程(即 PPID=1),且在 UNIX 系统进程层级中直接位于 init 之下。守护进程程序通常通过如下方法使自己成为守护进程:对一个子进程调用 fork,然后使其父进程立即终止,使得这个子进程能在 init 下运行。——维基百科

守护进程区别于普通用户登录系统后运行的进程。

守护进程直接由系统初始化,和系统用户没有直接关系,而用户自己开启的进程则依赖于用户连接的终端。当终端退出或者断开时,进程也可能随着终止。

来看一下我 Linux 试验机上的进程状态:

[root@home tmp]# ping www.baidu.com > /dev/null &

[1] 2759

[root@home tmp]# pstree -p

systemd(1)-+-agetty(157)
|-agetty(163)
|-avahi-daemon(129)---avahi-daemon(134)
|-avahi-dnsconfd(125)
|-crond(121)
|-dbus-daemon(130)
|-haveged(128)
|-ifplugd(126)
|-nginx(226)---nginx(227)
|-ntpd(223)
|-python(2727)
|-rngd(124)
|-sshd(216)---sshd(2683)---bash(2690)-+-ping(2759)
| `-pstree(2760)
|-systemd(2687)---(sd-pam)(2688)
|-systemd-journal(76)
|-systemd-logind(127)
|-systemd-udevd(89)
`-wpa_supplicant(153)

可以看到,当前有一个 ping 程序在后台运行。如果断开连接,再次登录之后,发现 ping 程序已经终止了。也就是说,普通进程和用户会话之间存在关联。那么,如何编写一个和用户会话无关、一直运行在后台的进程呢?大家可能注意到了上面 PID 为 2727 的 Python 进程。如果只是正常打开 Python,它应该运行在 bash 下面,而这里却直接运行在 systemd 下面。实际上,它就是一个守护进程。

下面来看一下使用 Python 编写 Linux 守护进程的一个简单实现:

#!/usr/bin/env python

import os
import signal
import time

logfile = "/tmp/daemon.log"

pid = os.fork()

# 退出父进程
if pid:
exit()

# 获取子进程 PID
daeid = os.getpid()

os.setsid()
os.umask(0)
os.chdir("/")

# 重定向文件描述符
fd = open("/dev/null", "a+")

os.dup2(fd.fileno(), 0)
os.dup2(fd.fileno(), 1)
os.dup2(fd.fileno(), 2)

fd.close()

log = open(logfile, "a")

log.write(
"Daemon start up at %s\n" %
time.strftime(
"%Y:%m:%d",
time.localtime(time.time())
)
)

log.close()


def reload(a, b):

log = open(logfile, "a")

log.write(
"Daemon reload at %s\n" %
time.strftime(
"%Y:%m:%d",
time.localtime(time.time())
)
)

log.close()


while True:

signal.signal(
signal.SIGHUP,
reload
)

time.sleep(2)

这里面的关键思路,是利用 Linux 中进程父子关系的特点。

当一个进程的父进程终止之后,系统会接管这个进程,使其父进程发生变化,这样它就可以脱离原来的用户会话继续运行。

这里还需要注意几个操作:

os.setsid()
os.umask(0)
os.chdir("/")

它们分别涉及会话、文件创建掩码以及工作目录的设置。

另外,还需要关闭或者重定向原来的文件描述符:

os.dup2(fd.fileno(), 0)
os.dup2(fd.fileno(), 1)
os.dup2(fd.fileno(), 2)

这里把标准输入、标准输出和标准错误都重定向到了:

/dev/null

这样,程序就不会继续依赖原来的终端。

把上面的代码保存下来,增加运行权限之后直接使用 Python 运行,就可以看到一个新的后台进程在运行,并且能够处理系统发送过来的 SIGHUP 信号。

以上程序主要用于测试,只处理了 SIGHUP 信号。

结束进程时,可以直接使用:

kill PID

评论0

欢迎分享你的看法,也欢迎补充不同的实践经验。