以前把守护过程与后台任务搞混了,前面看了文章才晓得这两者的区别,写此文表白本人对守护过程的了解.

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  COMMAND10179  10179 10177 bash10263  10263 10179 ps10264  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 osimport sysdef 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.txt1hello worldchanged

附加话题

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

因为这是为了防止产生僵尸过程。

当咱们只fork()一次后,存在父过程和子过程。这时有两种办法来防止产生僵尸过程:

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

    目前先思考子过程先于父过程完结的状况:

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

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

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