如何编写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判断字符串与大小写转换
Jun 08 Python
Python使用Paramiko模块编写脚本进行远程服务器操作
May 05 Python
Python常用知识点汇总
May 08 Python
浅谈python中的__init__、__new__和__call__方法
Jul 18 Python
pandas 数据结构之Series的使用方法
Jun 21 Python
利用Python的sympy包求解一元三次方程示例
Nov 22 Python
python实现画循环圆
Nov 23 Python
Python实现点云投影到平面显示
Jan 18 Python
Python基于pyecharts实现关联图绘制
Mar 27 Python
jupyter 中文乱码设置编码格式 避免控制台输出的解决
Apr 20 Python
使用 django orm 写 exists 条件过滤实例
May 20 Python
python中操作文件的模块的方法总结
Feb 04 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 加密/解密函数 dencrypt(动态密文,带压缩功能,支持中文)
2009/01/30 PHP
浅谈PHP强制类型转换,慎用!
2013/06/06 PHP
PHP中子类重载父类的方法【parent::方法名】
2016/05/06 PHP
PHP-FPM运行状态的实时查看及监控详解
2016/11/18 PHP
Yii框架参数化查询中IN查询只能查询一个的解决方法
2017/05/20 PHP
建立良好体验度的Web注册系统ajax
2007/07/09 Javascript
javascript 新浪背投广告实现代码
2009/07/07 Javascript
网站导致浏览器崩溃的原因总结(多款浏览器) 推荐
2010/04/15 Javascript
html a标签-超链接中confirm方法使用介绍
2013/01/04 Javascript
javascript date格式化示例
2013/09/25 Javascript
java和javascript获取word文档的书签位置对比
2014/06/19 Javascript
JS中使用sort结合localeCompare实现中文排序实例
2014/07/23 Javascript
JavaScript搜索字符串并将搜索结果返回到字符串的方法
2015/04/06 Javascript
JavaScript实现将xml转换成html table表格的方法
2015/04/17 Javascript
解析javascript图片懒加载与预加载的分析总结
2016/10/27 Javascript
Javascript继承机制详解
2017/05/30 Javascript
React中jquery引用的实现方法
2017/09/12 jQuery
深入理解JavaScript和TypeScript中的class
2018/04/22 Javascript
详解vue-cli 构建项目 vue-cli请求后台接口 vue-cli使用axios、sass、swiper
2018/05/28 Javascript
详解如何用typescript开发koa2的二三事
2018/11/13 Javascript
解决Vue+Electron下Vuex的Dispatch没有效果问题
2019/05/20 Javascript
在Vue中用canvas实现二维码和图片合成海报的方法
2019/06/10 Javascript
详解Python中内置的NotImplemented类型的用法
2015/03/31 Python
Python使用scrapy抓取网站sitemap信息的方法
2015/04/08 Python
Python2.x和3.x下maketrans与translate函数使用上的不同
2015/04/13 Python
Python中的浮点数原理与运算分析
2017/10/12 Python
深入理解Django-Signals信号量
2019/02/19 Python
对Python _取log的几种方式小结
2019/07/25 Python
详解Python绘图Turtle库
2019/10/12 Python
Microsoft新加坡官方网站:购买微软最新软件和技术产品
2016/10/28 全球购物
编写函数,将一个3*3矩阵转置
2013/10/09 面试题
单位提档介绍信
2014/01/17 职场文书
企业内部培训方案
2014/02/04 职场文书
整改落实自查报告
2014/11/05 职场文书
2015年教导处教学工作总结
2015/07/22 职场文书
vue如何实现关闭对话框后刷新列表
2022/04/08 Vue.js