铁雪资源网 Design By www.gsvan.com

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

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:会话组

"htmlcode">

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,daemon程序,python,守护进程,python,编写daemon

铁雪资源网 Design By www.gsvan.com
广告合作:本站广告合作请联系QQ:858582 申请时备注:广告合作(否则不回)
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
铁雪资源网 Design By www.gsvan.com

评论“如何编写python的daemon程序”

暂无如何编写python的daemon程序的评论...

稳了!魔兽国服回归的3条重磅消息!官宣时间再确认!

昨天有一位朋友在大神群里分享,自己亚服账号被封号之后居然弹出了国服的封号信息对话框。

这里面让他访问的是一个国服的战网网址,com.cn和后面的zh都非常明白地表明这就是国服战网。

而他在复制这个网址并且进行登录之后,确实是网易的网址,也就是我们熟悉的停服之后国服发布的暴雪游戏产品运营到期开放退款的说明。这是一件比较奇怪的事情,因为以前都没有出现这样的情况,现在突然提示跳转到国服战网的网址,是不是说明了简体中文客户端已经开始进行更新了呢?