并发编程(四)

前言

上篇博客介绍了进程的创建,进程的相关属性以及三种特殊进程:僵尸进程,孤儿进程和守护进程.

守护进程是一个在后台运行并且不受任何终端控制的进程,用于执行特定的系统任务.很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭.另一些只在需要的时候才启动,完成任务后就自动结束.

本篇博客详细介绍守护进程的产生原因以及作用.(因为我觉得守护进程对操作系统的正常运行还蛮重要的)

守护进程介绍

以下介绍摘自百度百科

守护进程是一个在后台运行并且不受任何终端控制的进程. Unix 操作系统有很多典型的守护进程,他们在后台运行,执行不同的管理任务.

用户使守护进程独立于所有终端是因为,在守护进程从一个终端启动的情况下,这同一个终端可能被其他的用户使用.例如,用户从一个终端启动守护进程后退出,然后另一个人也登陆到这个终端.用户不希望后者在使用该终端的过程中,接收到守护进程的任何错误信息.同样,由终端键入的任何信号(例如中断信号)也不应该影响先前在该终端启动的任何守护进程的运行.虽然让服务器后台运行很容易(只要shell 命令行以&结尾),但用户还应该做些工作,让程序本身能够自动进入后台,且不依赖于任何终端.

守护进程没有控制终端,因此当某些情况发生时,不管是一般的报告性信息,还是由管理员处理的紧急信息,都需要以某种方式输出. Syslog 函数就是输出这些信息的标准方法,它把信息发送给 syslogd 守护进程.

创建守护进程步骤

在不同 Unix环境下,守护进程的具体变成细节并不一致.但所幸的是,守护进程的编程原则都一样,区别仅在于具体的实现细节不同,这个原则就是要满足守护进程的特性.编程规则如下:

在后台运行

为避免挂起控制终端,要将守护进程放入后台运行,其方法是在父进程中调用 fork 使父进程终止,让子进程在后台执行,使得程序在 shell 终端里造成一个已经运行完毕的假象.之后所有的工作都在子进程中完成,而用户在 shell 终端里则可以执行其他命令,从而使得程序以僵尸进程运行,在形式上做到了与控制终端的隔离.具体就是 fork 产生子进程后,调用 exit 使父进程终止.

脱离控制终端,登陆会话和进程组

登陆回话可以包含多个进程组,这些进程组共享一个控制终端,这个控制终端通常是创建进程的登陆终端,控制终端,登陆回话和进程组通常都是从父进程继承下来的.目的就是要脱离这些继承的东西,不受它们的影响.

这就要使用 setsid 函数使进程成为会话组长.

setsid

setsid 命令 子进程从父进程继承了: Session ID, 进程组 ID 合打开的终端.子进程如果要脱离这些,代码中可通过调用 setsid 来实现.而命令行或脚本中可以通过命令setsid 来实现. setsid 帮助一个进程脱离从父进程继承而来的已打开的终端,会话和进程组.

需要说明的是,当进程是会话组长时, setsid 调用会失败,大第一点已经保证进程不是会话组长.setsid 调用成功后,进程会成为新的会话组长和新的进程组长,并与原来的登陆会话和进程组脱离,由于会话过程对控制终端的独占性,进程同时与控制终端脱离.

具体操作就是:

  1. 成为新会话的首进程;
  2. 成为一个新进程组的首进程;
  3. 没有控制终端.

禁止进程重新打开控制终端

现在进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端.可以通过是进程不再成为会话组长来禁止进程重新打开控制终端.

关闭打开的文件描述符

进程从创建它的父进程那里继承了打开的文件描述符.如果不关闭,会造成资源浪给,造成进程所在地文件系统无法卸下以及产生无法预料的错误.一般来说,必要的是关闭0,1,2三个文件描述符,即标准输入,标准输出,标准错误.因为一般希望守护进程自己有一套信息输出,输入的体系,而不是把所有的东西都发送到终端屏幕上.所以需要调用 fclose 来关闭文件描述符.

改变当前工作目录

将当前工作目录更改为根目录,从父进程继承过来的当前工作目录可能在一个装配的文件系统中.因为守护进程通常在系统引导之前是一直存在的,所以如果守护进程的当前工作目录在一个装配文件系统中,那么该文件系统就不能被拆卸.

另外,某些守护进程可能会把当前工作目录更改到某个指定位置,在此位置做它们的工作.例如,行式打印机守护进程常常将工作目录更改到 spool 目录上.可以调用 chdir 更改工作目录.

重设文件创建掩码

文件创建掩码是指屏蔽掉文件创建时的对应为.由于使用 fork 后函数新建的子进程继承了父进程的文件创建掩码,这就给该子进程使用文件带来了诸多麻烦.因此,把文件创建掩码设置为0,可以大大增强该守护进程的灵活性.设置文件创建掩码的函数是 umask, 通常的使用方法为umask(0).

处理 SIGCHLD 信号

处理 SIGCHLD 信号并不是必需的.但是对于某些守护进程,特别是服务器进程往往在请求到来时生产子进程出来请求.如果父进程不等待子进程结束,子进程将成为僵尸进程而占用系统资源.如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能.在系统下可以简单的将 SIGCHLD 信号的操作设为 SIG-IGN:

signal(SIGCHLD,SIG_IGN)

这样,内核在子进程结束后不会产生僵尸进程,这一点与 BSD4不同,杂 iBSD4下必须显式等待子进程结束后才能释放僵尸进程.

python守护进程

import time
import os
import sys

def initDaemon(stdoutfd, stderrfd=None, basePath=None):
    '''
    初始化为 daemon 进程
    '''
    basePath = '/' if basePath is None else basePath
    curTimeStr = time.strftime('%Y-%m-%d %H:%M:%S')
    try:
        stdoutfd.write('Start on %s, ' % curTimeStr)
        stdoutfd.flush()
    except:
        raise
    try:
        if os.fork() > 0:
            os._exit(0)
    except OSError:
        raise OSError('fork error')
        
    os.chdir(basePath)
    os.setsid()
    sys.stdout = stdoutfd
    sys.stderr = stdoutfd if stderrfd is None else stderrfd
    sys.stdin = open('/dev/nu,,', 'r')
    
    try:
        if os.fork() > 0:
            os._exit(0)
    except OSError:
        raise OSError('fork 2 error')
        
    pid = int(os.getpid())
    stdoutfd.write('pid=%s\n' % pid)
    stdoutfd.flush()
    return

守护进程运行方式

独立运行的守护进程

独立运行的守护进程有 init 脚本负责管理,所有独立运行的守护进程的脚本在/etc/rc和/etc/init.d 目录下.系统服务都是独立运行的守护进程包括 syslogd 和 cron 等.服务器监听一个特定的端口上等待客户端的连接.如果客户端产生一个连接请求,守护进程就创建一个子服务器响应这个连接,而主服务器继续监听.以保持多个子服务器池等待下一个客户端请求连接.

由 xinetd 管理的守护进程

从守护进程的概念可以看出,系统所运行的每一种服务,都必须运行一个监听某个端口连接所发生的守护进程,这通常意味着资源浪费.为了解决这个问题, Linux 引进了'网络守护进程服务程序'的概念. CentOS6.4使用的网络守护进程是 xinted(eXtendedInterNET services daemon).

上图是 ubuntu18.04使用的网络守护进程服务程序, xinted 能够同时监听多个指定的端口,在接受用户请求时,它能够根据用户请求的端口不同,启动不同的网络服务进程来处理这些用户请求.可以把 xinted 看做一个管理启动服务的管理服务器,它决定把一个客户请求交给那个程序处理,然后启动相应的守护进程.

守护进程分类

按照服务类型分为如下几个:

  • 系统守护进程: syslogd,login,crond,at 等
  • 网络守护进程: sendmail,httpd,xinetd 等
  • 独立启动的守护进程: httpd,named,xinetd 等
  • 被动守护进程(由 xinetd 启动):telnet,finger,ktalk 等

总结

对于一个操作系统来说,为了执行不同的管理任务,需要守护进程来协助管理.对于守护进程最重要的来说是需要在后台运行.其次,守护进程必须与其运行前的环境隔离开来.这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩码等.这些环境通常是守护进程从执行它的父进程(特别是 shell)继承下来的.最后守护进程的启动方式有其特殊之处.它可以在 Linux 系统启动时从启动脚本/etc/rc. d中启动,也可以由作业控制进程 crond 启动,还可以由用户终端(通常是 shell)执行.

除这几点之外,守护进程与普通进程基本上没什么区别.因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程.

posted @ 2018-11-17 17:24  rsuxwvilc  阅读(176)  评论(0编辑  收藏  举报