首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

那些永不消逝的进程-4

那些永不消逝的进程-4

永不消逝的进程 v2:总而言之,有了上一章的理论铺垫,笔者将清单 5 中的例程进化了一次,如清单 7 所示:
清单 7 永不消逝的进程 v2
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
41
42
43
44
45
46
47
48
#! /usr/bin/python

import time
import sys
import os
import logging


def child_process():
    logging.info("child process's pid: %d" % os.getpid())
    while (1):
        logging.info("child's still alive.")
        time.sleep(1)


def fork_and_exit_parent_proc():
    # 因为 multiprocessing.Process 的既定设计是只在子进程中运行 target 参数所指向的函数对象
     # 因此这里我们必须回归传统的 fork
    pid = os.fork()
    if pid > 0:
        os._exit(0)


def become_daemon(target):
    # 1. 第一次 fork
    fork_and_exit_parent_proc()
    # 2. 创建新会话
    os.setsid()
    # 3. 第二次 fork
    fork_and_exit_parent_proc()
    # 4. 将工作目录切换至 '/'
    os.chdir('/')
    # 5. 重定向标准输入、输出、错误至/dev/null
    fd = os.open(os.devnull, os.O_RDWR)
    os.dup2(fd, sys.stdin.fileno())
    os.dup2(fd, sys.stdout.fileno())
    os.dup2(fd, sys.stderr.fileno())
    # 6. 因为标准输出不可用,这里笔者又额外定义了一个 log 文件以接收守护进程的输出
    logging.basicConfig(filename='/var/log/mylog.log', level=logging.INFO)

    target()


def main():
    becomeDaemon(target=child_process)

if __name__ == '__main__':
    main()




在清单 7 中,笔者并未直接调用 daemon(),这主要是因为 python 的标准库中并未包含对 daemon()的直接封装(其实 python                中也提供了其他方案,笔者下文中会有提及)。此外,由于上述实现中有很多调用都需要系统管理员权限,因此必须要以 root 或者 sudoer 的身份才可以执行。
读到这里让我们再回到上一章末尾处的思考:服务级的守护进程纵有千般好,毕竟需要用到系统管理员级的权限;而屏蔽 SIGHUP                纵有千般不是,一个的普通用户权限即可驱动——故而技术无贵贱,不同技术适用于不同场合而已。
最后,除去上述步骤之外,还有一些上述代码中并未实现,但对于守护进程大有裨益的工作,笔者也罗列如下:
  • 利用 umask 限制进程新建文件的权限位,以确保运行时创建的文件不会被外界非法修改,从而改变守护进程的运行时行为。例如 openssh                    的服务进程就禁止了新建文件的用户组和其他用户的写行为;
  • 关闭所有非必需的自父进程继承而来的文件描述符。诚然,这一部分工作不一定必须由 daemon 之类的 API                    直接实现,但考虑到守护进程长期运行的特性,又是必须完成的;
  • 实现一个外界同守护进程交互的方案。显而易见,守护进程一般游离于终端之外,除开 ps 或查询/proc                    之类的方法之外难觅其踪。自然用户会需要一个接口来对其进行控制。顺便一提,纵然 Linux                    下的进程间交互手段五花八门,不过由于许多约定俗成的习惯,信号量始终是最为行之有效的控制守护进程方案之一。例如上文中提到过的 SIGHUP                    就通常用于通知守护进程重新载入配置文件(准确的说,是用于通知守护进程重新初始化),绝大部分传统的守护进程实现都会支持这一功能;
  • 实现一个守护进程输出自身运行时状态的方案,理由同上。很多笔者可能会说这部分工作可以利用系统的 log                    服务来完成。其实这里还有一个约定俗成的做法,就是大多数守护进程都会在/var/run 下创建一个 xxx.pid                    文件以方便利用脚本来追踪进程的运行状态。例如,如果想要查询当前系统的 rsyslogd 进程的 pid,直接在/var/run 下查询 rsyslogd.pid                    文件的内容即可;
总而言之守护进程的实现要考虑到非常多的因素,毕竟你实现的是一个长时间在操作系统的后台蹦跶的程序,哪怕有一点点小的要素没有考虑到都会导致系统运行效率的降低、死机甚至被居心叵测的黑客实施攻击。虽然                daemon()这样的通用 API 一定程度上减轻了我们的工作量,但是前面还是会有很多额外的坑在等待着我们,所谓路漫漫其修远兮……
回到                python现在让我们再回到本文最开始的地方:笔者最初的目的其实只是实现一个通用且稳定的守护进程,但是现在,看看清单 5 中简陋的的 v1 版和清单 7 中偷工减料的 v2                版,不禁仰声长叹:难道就没有一个能让人乐得逍遥且又面面俱到的 v3 版么?
Python 的标准库中并没有封装 glibc 中的 daemon()函数,这个笔者在前文有提到过,但这不代表社区中没人考虑过这个问题:PEP-3143就详细探讨了一个守护进程库的解决方案。
PEP-3143 的设计非常简明扼要:一个 DaemonContext 类就可以基本可以提供开发者们需要的一切,其主要接口和属性定义如下表所示:
表 1 DaemonContext                    类的定义一切看起来都挺美好的,唯一的缺陷是:这个 PEP 被 defer 了,直到今天也没有进入标准库。不过不要紧,因为这个 PEP 还有一个参考实现                    python-daemon,在pip中很容易找到源码。
终章:永不消逝的进程 v3凭借 python-daemon,我们的永不消逝的进程终又可以再进化一层,笔者将修改后的源码列于清单 8 之中,作为本章的结尾以飨读者:
清单 8 永不消逝的进程 v3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#! /usr/bin/python

import time
import os
import logging
import daemon


def child_process():
    logging.info("child process's pid: %d" % os.getpid())
    while (1):
        logging.info("child's still alive.")
        time.sleep(1)


def main():
    # DaemonContext 实现了__enter__() 和__exit__(),因此我们可以一句话搞定整个 daemon context
    with daemon.DaemonContext():
        # daemon 目前不支持 log,所以这部分工作只能我们手动初始化
        logging.basicConfig(filename='/var/log/mylog.log', level=logging.INFO)
        child_process()

if __name__ == '__main__':
    main()




结束语后台守护进程是 Linux/Unix 系统中非常重要的"地下工作者"。本文从 Linux/Unix                的进程组和会话的机制入手,详细的介绍了基于这些机制之上的两种截然不同的实现守护进程的手法。在深入解读这些奇淫巧技的同时,笔者也更希望读完本文的朋友们能够触类旁通,对                Linux/Unix 系统的进程间关系能有更深一层的认识。
返回列表