如何编写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对象的深拷贝和浅拷贝详解
Aug 25 Python
python实现逆波兰计算表达式实例详解
May 06 Python
python二分查找算法的递归实现方法
May 12 Python
Python正则抓取新闻标题和链接的方法示例
Apr 24 Python
让Python更加充分的使用Sqlite3
Dec 11 Python
PyQt5每天必学之关闭窗口
Apr 19 Python
opencv python 基于KNN的手写体识别的实例
Aug 03 Python
自学python的建议和周期预算
Jan 30 Python
用python3 返回鼠标位置的实现方法(带界面)
Jul 05 Python
Python根据指定文件生成XML的方法
Jun 29 Python
详解Django中 render() 函数的使用方法
Apr 22 Python
PyTorch 实现L2正则化以及Dropout的操作
May 27 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
攻克CakePHP系列二 表单数据显示
2008/10/22 PHP
php使用百度ping服务代码实例
2014/06/19 PHP
PHP SPL标准库之数据结构堆(SplHeap)简单使用实例
2015/05/12 PHP
Jquery 选中表格一列并对表格排序实现原理
2012/12/15 Javascript
jQuery实现图片放大预览实现原理及代码
2013/09/12 Javascript
js中top的作用深入剖析
2014/03/04 Javascript
JS实现的通用表单验证插件完整实例
2015/08/20 Javascript
node.js从数据库获取数据
2016/05/08 Javascript
javascript实现复选框全选或反选
2017/02/04 Javascript
JS如何实现在页面上快速定位(锚点跳转问题)
2017/08/14 Javascript
详解如何解决Vue和vue-template-compiler版本之间的问题
2018/09/17 Javascript
通过cordova将vue项目打包为webapp的方法
2019/02/02 Javascript
IE11下CKEditor在Bootstrap Modal中下拉问题的解决
2019/09/25 Javascript
js实现圆形显示鼠标单击位置
2020/02/11 Javascript
原生js实现俄罗斯方块
2020/10/20 Javascript
python网络编程学习笔记(四):域名系统
2014/06/09 Python
Python制作数据导入导出工具
2015/07/31 Python
Windows下Anaconda的安装和简单使用方法
2018/01/04 Python
redis之django-redis的简单缓存使用
2018/06/07 Python
对python中的高效迭代器函数详解
2018/10/18 Python
[原创]Python入门教程3. 列表基本操作【定义、运算、常用函数】
2018/10/30 Python
使用python搭建服务器并实现Android端与之通信的方法
2019/06/28 Python
Python paramiko 模块浅谈与SSH主要功能模拟解析
2020/02/29 Python
德国百年厨具品牌WMF美国站:WMF美国
2016/09/12 全球购物
ECCO爱步美国官网:来自丹麦的鞋履品牌
2016/11/23 全球购物
美国性感内衣店:Yandy
2018/06/12 全球购物
美国浴缸、水槽和水龙头购物网站:Vintage Tub & Bath
2019/11/05 全球购物
什么是类的返射机制
2016/02/06 面试题
企业厂长岗位职责
2013/12/17 职场文书
工程安全生产协议书
2014/11/21 职场文书
2016党员学习《反对自由主义》心得体会
2016/01/22 职场文书
2016年社区党支部公开承诺书
2016/03/25 职场文书
2019年大学毕业生个人自我鉴定范文大全
2019/03/21 职场文书
读《钢铁是怎样炼成的》有感:百炼方成钢
2019/11/05 职场文书
Python turtle编写简单的球类小游戏
2022/03/31 Python
一文简单了解MySQL前缀索引
2022/04/03 MySQL