如何编写python的daemon程序


Posted in Python onJanuary 07, 2021

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

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程序的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python连接mysql调用存储过程示例
Mar 05 Python
python测试驱动开发实例
Oct 08 Python
Mac下Supervisor进程监控管理工具的安装与配置
Dec 16 Python
python的socket编程入门
Jan 29 Python
PyQt5每天必学之单行文本框
Apr 19 Python
用于业余项目的8个优秀Python库
Sep 21 Python
python字符串Intern机制详解
Jul 01 Python
使用python对多个txt文件中的数据进行筛选的方法
Jul 10 Python
python3获取当前目录的实现方法
Jul 29 Python
对Django中内置的User模型实例详解
Aug 16 Python
Django 解决阿里云部署同步数据库报错的问题
May 14 Python
详解Python模块化编程与装饰器
Jan 16 Python
python+selenium+chrome实现淘宝购物车秒杀自动结算
Jan 07 #Python
详解Python遍历列表时删除元素的正确做法
Jan 07 #Python
五分钟学会怎么用Pygame做一个简单的贪吃蛇
Jan 06 #Python
python绕过图片滑动验证码实现爬取PTA所有题目功能 附源码
Jan 06 #Python
python 获取谷歌浏览器保存的密码
Jan 06 #Python
python实现PolynomialFeatures多项式的方法
Jan 06 #Python
pytorch中index_select()的用法详解
Jan 06 #Python
You might like
php DOS攻击实现代码(附如何防范)
2012/05/29 PHP
PHP 面向对象程序设计(oop)学习笔记(三) - 单例模式和工厂模式
2014/06/12 PHP
购物车实现的几种方式优缺点对比
2018/05/02 PHP
PHP INT类型在内存中占字节详解
2019/07/20 PHP
解析jQuery与其它js(Prototype)库兼容共存
2013/07/04 Javascript
javascript移动设备Web开发中对touch事件的封装实例
2014/06/05 Javascript
元素绑定click点击事件方法
2015/06/08 Javascript
javascript中 try catch用法
2015/08/16 Javascript
Vue2.0实现1.0的搜索过滤器功能实例代码
2017/03/20 Javascript
JavaScript中undefined和null的区别
2017/05/03 Javascript
微信小程序日历组件calendar详解及实例
2017/06/08 Javascript
基于JS实现移动端左滑删除功能
2017/07/28 Javascript
JScript实现表格的简单操作
2017/08/15 Javascript
在React 组件中使用Echarts的示例代码
2017/11/08 Javascript
详解如何实现一个简单的Node.js脚手架
2017/12/04 Javascript
vue.js2.0点击获取自己的属性和jquery方法
2018/02/23 jQuery
vue 中swiper的使用教程
2018/05/22 Javascript
详解VUE中常用的几种import(模块、文件)引入方式
2018/07/03 Javascript
微信小程序在ios下Echarts图表不能滑动的问题解决
2019/07/10 Javascript
[02:44]重置世界,颠覆未来——DOTA2 7.23版本震撼上线
2019/12/01 DOTA
python连接oracle数据库实例
2014/10/17 Python
Python读写Json涉及到中文的处理方法
2016/09/12 Python
Python使用cx_Oracle模块操作Oracle数据库详解
2018/05/07 Python
Django中ORM外键和表的关系详解
2019/05/20 Python
python实现屏保程序(适用于背单词)
2019/07/30 Python
python实现淘宝购物系统
2019/10/25 Python
10行Python代码实现Web自动化管控的示例代码
2020/08/14 Python
Python 操作 MySQL数据库
2020/09/18 Python
聊聊python在linux下与windows下导入模块的区别说明
2021/03/03 Python
固特异美国在线轮胎店:Goodyear Tire
2019/02/23 全球购物
Java程序员常见面试题
2015/07/16 面试题
校庆筹备方案
2014/03/30 职场文书
离婚协议书范文2015
2015/01/26 职场文书
出生证明范本
2015/06/15 职场文书
女性健康讲座主持词
2015/07/04 职场文书
浅谈Python列表嵌套字典转化的问题
2021/04/07 Python