如何编写python的daemon程序

以前把守护进程与后台任务搞混了,后面看了文章才知道这两者的区别,写此文表达自己对守护进程的理解.

1:什么是守护进程?

所谓守护进程是一种是 Linux 的一种长期运行的后台服务进程,httpd、named、sshd 等服务都是以守护进程 Daemon 方式运行的,通常服务名称以字母d结尾,也就是 Daemon 第一个字母.

  1. 无需控制终端(不需要与用户交互)
  2. 在后台运行
  3. 生命周期比较长,一般是随系统启动和关闭

2:守护进程必要性

通常我们执行任务时是在前台执行,占领了当前终端,此时无法进行操作,就算我们添加了 &符号,将程序放到后台,但也就因为终端断网等问题,导致程序中断。

所要知道的是:在目前的linux上,有了systemd这个服务,这个服务管理工具可以方便我们写在后台运行的程序,甚至可以代替这种守护进程。通过把写服务的配置文件,让systemd监控我们的程序,可以随系统启动而运行,可以设定启动条件,及其的方便。

3:进程组

$ ps -o pid,pgid,ppid,comm | cat
 PID PGID PPID COMMAND
10179 10179 10177 bash
10263 10263 10179 ps
10264 10263 10179 cat
  1. bash:进程和进程组ID都是 10179,父进程其实是 sshd(10177)
  2. ps:进程和进程组ID都是 10263,父进程是 bash(10179),因为是在 Shell 上执行的命令
  3. cat:进程组 ID 与 ps 的进程组 ID 相同,父进程同样是 bash(10179)

4:会话组

​ 多个进程构成一个进程组,而会话组是由多个进程组构建而。而进程组又被称为job,会话有前台作业,也会有后台作业;一个会话可以有一个控制终端,当控制终端有输入和输出时都会传递给前台进程组,比如Ctrl + Z。会话的意义在于能将多个作业通过一个终端控制,一个前台操作,其它后台运行。

那么如何编写守护进程呢?

其实编写守护进程很简单,只需要遵循一下几点即可

1:创建子进程,父进程退出

PPID  PID PGID  SID TTY   TPGID STAT  UID  TIME COMMAND
  0  49  49  49 pts/2    70 Ss    0  0:00 /bin/bash
  49  70  70  49 pts/2    70 R+    0  0:00 \_ ps axjf
  0  17  17  17 pts/1    68 Ss    0  0:00 /bin/bash
  17  68  68  17 pts/1    68 S+    0  0:00 \_ python hello.py
  68  69  68  17 pts/1    68 S+    0  0:00   \_ python hello.py
  0   1   1   1 pts/0    1 Ss+   0  0:00 /bin/bash

进程 fork 后,父进程退出。这么做的原因有 2 点:

如果守护进程是通过 Shell 启动,父进程退出,Shell 就会认为任务执行完毕,这时子进程由 init 收养
子进程继承父进程的进程组 ID,保证了子进程不是进程组组长,因为后边调用setsid()要求必须不是进程组长
PGID就是进程所属的Group的Leader的PID,如果PGID=PID,那么该进程是Group Leader

2、子进程创建新会话

调用setsid()创建一个新的会话,并成为新会话组长。这个步骤主要是要与继承父进程的会话、进程组、终端脱离关系。

那么问题来了,为什么进程组组长无法调用setsid()呢?

对于进程组长来说,进程组 ID 已经和 PID 相同了,如果它被允许调用setsid()的话,它的进程组 ID 会保持不变,会出现:

1:进程组长属于新的会话;

2:老的进程组成员属于旧的会话。

这样情况变成了一个进程组的成员属于不同的会话,Linux想要禁止这种情况的发生。

3、禁止子进程重新打开终端

此刻子进程是会话组长,为了防止子进程重新打开终端,再次 fork 后退出父进程,也就是此子进程。这时子进程 2 不再是会话组长,无法再打开终端。其实这一步骤不是必须的,不过加上这一步骤会显得更加严谨。

4、设置当前目录为根目录

如果守护进程的当前工作目录是/usr/home目录,那么管理员在卸载/usr分区时会报错的。为了避免这个问题,可以调用chdir()函数将工作目录设置为根目录/。

5、设置文件权限掩码

文件权限掩码是指屏蔽掉文件权限中的对应位。由于使用 fork()函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为 0,可以大大增强该守护进程的灵活性。通常使用方法是umask(0)。

6、关闭文件描述符

子进程会继承已经打开的文件,它们占用系统资源,且可能导致所在文件系统无法卸载。此时守护进程与终端脱离,常说的输入、输出、错误描述符也应该关闭,毕竟这个时候也不会使用终端了。

守护进程的出错处理

由于守护进程脱离了终端,不能将错误信息输出到控制终端,即使 gdb 也无法正常调试。常用的方法是使用 syslog 服务,将错误信息输入到/var/log/messages中。

syslog 是 Linux 中的系统日志管理服务,通过守护进程 syslogd 来维护。该守护进程在启动时会读一个配置文件/etc/syslog.conf。该文件决定了不同种类的消息会发送向何处。

代码展示

import os
import sys

def daemonize(pid_file=None):
  pid = os.fork()
  if pid:
    sys.exit(0)
  os.setsid()

  _pid = os.fork()
  if _pid:
    sys.exit(0)

  os.umask(0)
  os.chdir('/')
  sys.stdout.flush()
  sys.stderr.flush()

  with open('/dev/null') as read_null, open('/dev/null','w') as write_null:
    os.dup2(read_null.fileno(), sys.stdin.fileno())
    os.dup2(write_null.fileno(), sys.stdout.fileno())
    os.dup2(write_null.fileno(), sys.stderr.fileno())

  if pid_file:
    with open(pid_file,'w+') as f:
      f.write(str(os.getpid()))

if __name__ == "__main__":
  daemonize('test.txt')

关于os.dup2这个函数

os.dup2() 方法用于将一个文件描述符 fd 复制到另一个 fd2。
Unix, Windows 上可用。

>>> import os
>>> f = open("hello.txt","a")
>>> os.dup2(f.fileno(),1)
>>> f.close()
>>> print("hello world")
>>> print("changed")
cat hello.txt
1
hello world
changed

附加话题

为什么服务器端常常fork两次呢?

因为这是为了避免产生僵尸进程。

当我们只fork()一次后,存在父进程和子进程。这时有两种方法来避免产生僵尸进程:

  • 父进程调用waitpid()等函数来接收子进程退出状态。
  • 父进程先结束,子进程则自动托管到Init进程(pid = 1)。

目前先考虑子进程先于父进程结束的情况:

  • 若父进程未处理子进程退出状态,在父进程退出前,子进程一直处于僵尸进程状态。
  • 若父进程调用waitpid()(这里使用阻塞调用确保子进程先于父进程结束)来等待子进程结束,将会使父进程在调用waitpid()后进入睡眠状态,只有子进程结束父进程的waitpid()才会返回。 如果存在子进程结束,但父进程还未执行到waitpid()的情况,那么这段时期子进程也将处于僵尸进程状态。

由此,可以看出父进程与子进程有父子关系,除非保证父进程先于子进程结束或者保证父进程在子进程结束前执行waitpid(),子进程均有机会成为僵尸进程。那么如何使父进程更方便地创建不会成为僵尸进程的子进程呢?这就要用两次fork()了。

父进程一次fork()后产生一个子进程随后立即执行waitpid(子进程pid, NULL, 0)来等待子进程结束,然后子进程fork()后产生孙子进程随后立即exit(0)。这样子进程顺利终止(父进程仅仅给子进程收尸,并不需要子进程的返回值),然后父进程继续执行。这时的孙子进程由于失去了它的父进程(即是父进程的子进程),将被转交给Init进程托管。于是父进程与孙子进程无继承关系了,它们的父进程均为Init,Init进程在其子进程结束时会自动收尸,这样也就不会产生僵尸进程了。

以上就是如何编写python的daemon程序的详细内容,更多关于python的daemon程序的资料请关注我们其它相关文章!

(0)

相关推荐

  • python并发编程多进程之守护进程原理解析

    守护进程 主进程创建子进程目的是:主进程有一个任务需要并发执行,那开启子进程帮我并发执行任务 主进程创建子进程,然后将该进程设置成守护自己的进程 关于守护进程需要强调两点: 其一:守护进程会在主进程代码执行结束后就终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children 如果我们有两个任务需要并发执行,那么开一个主进程和一个子进程分别去执行就ok了,如果子进程的任务

  • Python守护进程用法实例分析

    本文实例讲述了Python守护进程用法.分享给大家供大家参考.具体分析如下: 守护进程是可以一直运行而不阻塞主程序退出.要标志一个守护进程,可以将Process实例的daemon属性设置为True.代码如下: import os import time import random import sys from multiprocessing import Process,current_process def daemon(): p = current_process() print "sta

  • python使用fork实现守护进程的方法

    os模块中的fork方法可以创建一个子进程.相当于克隆了父进程 os.fork() 子进程运行时,os.fork方法会返回0: 而父进程运行时,os.fork方法会返回子进程的PID号. 所以可以使用PID来区分两个进程: #!/usr/bin/env python #coding=utf8 from time import sleep import os try: pid = os.fork() except OSError, e: pass sleep(30) 运行代码,查看进程: [roo

  • Python 创建守护进程的示例

    考虑如下场景:你编写了一个python服务程序,并且在命令行下启动,而你的命令行会话又被终端所控制,python服务成了终端程序的一个子进程.因此如果你关闭了终端,这个命令行程序也会随之关闭. 要使你的python服务不受终端影响而常驻系统,就需要将它变成守护进程. 守护进程就是Daemon程序,是一种在系统后台执行的程序,它独立于控制终端并且执行一些周期任务或触发事件,通常被命名为"d"字母结尾,如常见的httpd.syslogd.systemd和dockerd等. 代码实现 pyt

  • Python守护进程和脚本单例运行详解

    本篇文章主要介绍了Python守护进程和脚本单例运行,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 一.简介 守护进程最重要的特性是后台运行:它必须与其运行前的环境隔离开来,这些环境包括未关闭的文件描述符.控制终端.会话和进程组.工作目录以及文件创建掩码等:它可以在系统启动时从启动脚本/etc/rc.d中启动,可以由inetd守护进程启动,也可以有作业规划进程crond启动,还可以由用户终端(通常是shell)执行. Python有时需要保证只运行一个脚本实例,以避

  • python实现的守护进程(Daemon)用法实例

    本文实例讲述了python实现的守护进程(Daemon)用法.分享给大家供大家参考.具体如下: def createDaemon(): "'Funzione che crea un demone per eseguire un determinato programma-"' import os # create - fork 1 try: if os.fork() > 0: os._exit(0) # exit father- except OSError, error: pr

  • Python守护进程实现过程详解

    这篇文章主要介绍了Python守护进程实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 如果你设置一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出.如果你的主线程在退出的时候,不用等待那些子线程完成,那就设置这些线程的daemon属性.即在线程开始(thread.start())之前,调用setDeamon()函数,设定线程的daemon标志.(thread.setDaemon(True))就

  • python 如何设置守护进程

    上一篇文章 介绍 join 在多进程中的作用,本文继续学习设置守护进程的对程序的影响.(Python大牛可以绕行) 我们通过两个例子说明 # encoding: utf-8 """ author: yangyi@youzan.com time: 2019/7/30 11:20 AM func: """ from multiprocessing import Process import os import time def now(): retu

  • python daemon守护进程实现

    假如写一段服务端程序,如果ctrl+c退出或者关闭终端,那么服务端程序就会退出,于是就想着让这个程序成为守护进程,像httpd一样,一直在后端运行,不会受终端影响. 守护进程英文为daemon,像httpd,mysqld,最后一个字母d其实就是表示daemon的意思. 守护进程的编写步骤: 1.fork子进程,然后父进程退出,此时子进程会被init进程接管. 2.修改子进程的工作目录,创建新进程组合新会话,修改umask. 3.子进程再次fork一个进程,这个进程可以称为孙子进程,然后子进程退出

  • Python如何实现守护进程的方法示例

    场景设置: 你编写了一个python服务程序,并且在命令行下启动,而你的命令行会话又被终端所控制,python服务成了终端程序的一个子进程.因此如果你关闭了终端,这个命令行程序也会随之关闭. 要使你的python服务不受终端影响而常驻系统,就需要将它变成守护进程. 守护进程就是Daemon程序,是一种在系统后台执行的程序,它独立于控制终端并且执行一些周期任务或触发事件,通常被命名为"d"字母结尾,如常见的httpd.syslogd.systemd和dockerd等. 代码实现 pyth

  • python实现守护进程、守护线程、守护非守护并行

    守护进程 1.守护子进程 主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to havechildren 注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止 我们来看一个例子 from multiprocessing import Process import os,time,random def task(): p

随机推荐