Python编写守护进程程序

Python编写守护进程程序思路

1. fork子进程,父进程退出
通常,我们执行服务端程序的时候都会通过终端连接到服务器,成功连接后会加载shell环境,终端和shell都是进程,shell进程是终端进程的子进程,通过ps命令可以很容易的查看到。在这个shell环境下一开始执行的程序都是shell进程的子进程,自然会受到shell进程的影响。在程序里fork子进程后,父进程退出,对了shell进程来说,这个父进程就算执行完了,而产生的子进程会被init进程接管,从而也就脱离了终端的控制。

2-4步骤的意义
守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录以及文件创建掩码等。
这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。
2、修改子进程的工作目录
子进程在创建的时候会继承父进程的工作目录,如果执行的程序是在u盘里的,就会导致u盘不能卸载。比如Nginx就有它的默认工作目录 /etc/nginx/conf.d/default.conf

3、创建进程组
使用setsid后,子进程就会成为新会话的首进程(session leader);子进程会成为新进程组的组长进程;子进程没有控制终端。

4、修改umask
由于umask会屏蔽权限,所以设定为0,这样可以避免读写文件时碰到权限问题。

5、fork孙子进程,子进程退出
经过上面几个步骤后,子进程会成为新的进程组老大,可以重新申请打开终端,为了避免这个问题,fork孙子进程出来。

6、重定向孙子进程的标准输入流、标准输出流、标准错误流到/dev/null
因为是守护进程,本身已经脱离了终端,那么标准输入流、标准输出流、标准错误流就没有什么意义了。所以都转向到/dev/null,就是都丢弃的意思。

守护进程的启动方式有其特殊之处。它可以在系统启动时从启动脚本/etc/rc.d中启动,可以由inetd守护进程启动,可以有作业规划进程crond启动,
还可以由用户终端(通常是shell)执行。

总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。
因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。如果大家对进程的认识比较深入,就对守护进程容易理解和编程了。

Linux系统进程的一些概念

这里主要是回答针对下面代码的疑问,为什么要FORK?为什么要设置SID等。

这个“1”号进程就是所有进程的父进程,因为这是CentOS7它得启动机制变化了,如果在CentOS6中那么1号进程则是INIT进程。但不管怎么作用是一样的。

我们平时所理解的守护进程就是你在命令行执行一个程序它自己就在后台运行了,你退出了终端再进去它依然在运行就像Nginx那样。首先我们要知道几个概念

进程ID(PID):就是这个进程的进程号

父进程ID(PPID):该进程的父进程ID号

进程组ID(PGID):进程所在进程组ID,每一个进程都属于一个进程组,一个进程组可以包含多个进程同时包含一个组长进程(如果进程ID和其对应的进程组ID相同则表示该进程是该组的组长)。比如一个程序是多进程的,运行该程序就会启动多个进程,那么这些进程都属于一个进程组,因为你可以针对组来发送信号,其实也就是管理。

会话ID(SID):当有新的用户登录Linux时,登录进程会为这个用户创建一个会话。用户的登录shell就是会话的首进程。会话的首进程ID会作为整个会话的ID。会话是一个或多个进程组的集合,囊括了登录用户的所有活动。

ps -axo pid,ppid,pgid,sid,tty,comm

pts/0是绑定到会话的一个终端设备,这里之所有有pts/1是因为我开了两个连接到Linux的终端,都是通过SSH进行登录的。

pts/0的进程ID是29641,它得PPID和PGID都是一样的,说明它就是进程组29641的组长,为什么呢?因为我通过SSH登录,登录后运行的第一个就是bash也就是和我进行命令交互的程序,所以你可以看到29641的父进程ID是29639它是一个sshd服务。

这里为什么有这么多1172,上面的1172是守护进程,下面的29639是sshd服务派生出来的一个子进程用于负责一个用户的连接,进程ID为1172的sshd它的父进程就是1.

会话组

通常我们执行的命令属于前端任务,也就是和会话绑定,如果会话消失了任务也就是消失了。我这里执行一个ping操作,它会一直执行

我们在另外一个终端查看

它的父进程是29641,不就是我们上面的bash么,而且它的SID也就是会话ID也是29641,因为它属于哪个会话,如果哪个会话消失了,这个ping操作也可以叫做作业,也就是消失了。我们把那个执行ping命令的终端直接关闭,然后在另外的终端上查看,不一会你就看不到那个ping任务了。所以这就是会话。

其实无论是进程组还是会话都属于作业控制。会话ID相同的进程只要会话消失,这些进程也就消失了,也就是结束了。

下面我们来说一下进程组

上面这一条命令其实运行了是两个进程。我们在另外一个终端查看

bash的进程ID是30150,所以由它派生的子进程的父进程ID都是30150,就像下面的tailf和grep.这个不用多数,因为都是在那个会话也就是终端上执行的,所以他们三个的会话ID相同。大家可以看到tailf和grep的进程组ID相同,都是30374说明他们是在一个进程组中,而组长就是tailf的进程其ID为30374。

进程组ID相同我们就可以给进程组发信号比如去结束这个组里所有的进程。这还是作业管理的内容。如下面操作:

kill -SIGTERM 30374

另外一个终端的任务自动就结束了

如何判断你自己当前是哪个终端呢?

关闭终端为什么有些进程不退出呢?

通过SID的演示我们知道,命令行里运行的进程会依赖当前会话,所以进程的运行不受会话影响那么肯定就要脱离之前的会话。另外还需要让进程脱离当前进程可以理解为当前的bash也就是完全隔断父子关系,因为毕竟我们是通过bash来运行的程序,bash又依赖终端pts/N这种,如果bash没了,进程也没了。看下图

还是这个命令这回我们放到后台运行,

可以看到它俩的SID和bash的并不相同

但是这时候如果你关闭这个终端,这个任务也就没了。你可以试一下。

完整代码

# !/usr/bin/env python
# coding: utf-8

# python模拟linux的守护进程

import sys, os, time, atexit, string
from signal import SIGTERM

__metaclass__ = type

class Daemon:
    def __init__(self, pidfile="/tmp/Daemon.pid", stdin=‘/dev/null‘, stdout=‘/dev/null‘, stderr=‘/dev/null‘):
        # 需要获取调试信息,改为stdin=‘/dev/stdin‘, stdout=‘/dev/stdout‘, stderr=‘/dev/stderr‘,以root身份运行。
        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        self.pidfile = pidfile
        self.applicationName = "Application"
        self._homeDir = "/"
        # 调试模式是否开启
        self._verbose = False
        # 用户掩码,默认为0
        self._umask = 0

    # 获取守护进程掩码
    @property
    def umask(self):
        return self._umask

    # 设置守护进程掩码
    @umask.setter
    def umask(self, umask):
        self._umask = umask

    # 获取当前是否是调试模式
    @property
    def VerboseMode(self):
        return self._verbose

    # 调试模式开关,默认不是调试模式
    @VerboseMode.setter
    def VerboseMode(self, verboseMode):
        self._verbose = verboseMode

    # 调试模式和非调试模式设置
    def _verbosSwitch(self):
        # 调试模式是输出日志到指定文件,这些文件在对象初始化时指定
        if self._verbose:
            pass
            # self.stdin = ‘/dev/stdin‘
            # self.stdout = ‘/dev/stdout‘
            # self.stderr = ‘/dev/stderr‘
        else:
            self.stdin = ‘/dev/null‘
            self.stdout = ‘/dev/null‘
            self.stderr = ‘/dev/null‘

    def setApplicationName(self, appName):
        self.applicationName = appName

    # 获取和设置进程住目录
    @property
    def HomeDir(self):
        return self._homeDir

    @HomeDir.setter
    def HomeDir(self, homeDir):
        self._homeDir = homeDir

    # 这个方法的主要目的就是脱离主体,为进程创造环境
    def _daemonize(self):
        # 第一步
        try:
            # 第一次fork,生成子进程,脱离父进程,它会返回两次,PID如果等于0说明是在子进程里面,如果大于0说明当前是在父进程里
            pid = os.fork()
            # 如果PID大于0,说明当前在父进程里,然后sys.exit(0),则是退出父进程,此时子进程还在运行。
            if pid > 0:
                # 退出父进程,此时linux系统的init将会接管子进程
                sys.exit(0)
        except OSError, e:
            sys.stderr.write(‘fork #1 failed: %d (%s)\n‘ % (e.errno, e.strerror))
            sys.exit(1)

        # 第二、三、四步
        os.chdir("/")  # 修改进程工作目录
        os.setsid()  # 设置新的会话,子进程会成为新会话的首进程,同时也产生一个新的进程组,该进程组ID与会话ID相同
        os.umask(self._umask)  # 重新设置文件创建权限,也就是工作目录的umask

        # 第五步
        try:
            # 第二次fork,禁止进程打开终端,相当于是子进程有派生一个子进程
            pid = os.fork()
            if pid > 0:
                # 子进程退出,孙子进程运行,此时孙子进程由init进程接管,在CentOS 7中是Systemed。
                sys.exit(0)
        except OSError, e:
            sys.stderr.write(‘fork #2 failed: %d (%s)\n‘ % (e.errno, e.strerror))
            sys.exit(1)

        # 第六步
        # 把之前的刷到硬盘上
        sys.stdout.flush()
        sys.stderr.flush()
        # 重定向标准文件描述符
        si = file(self.stdin, ‘r‘)
        so = file(self.stdout, ‘a+‘)
        se = file(self.stderr, ‘a+‘, 0)
        # os.dup2可以原子化的打开和复制描述符,功能是复制文件描述符fd到fd2, 如果有需要首先关闭fd2. 在unix,Windows中有效。
        # File的 fileno() 方法返回一个整型的文件描述符(file descriptor FD 整型)
        os.dup2(si.fileno(), sys.stdin.fileno())
        os.dup2(so.fileno(), sys.stdout.fileno())
        os.dup2(se.fileno(), sys.stderr.fileno())

        # 注册退出函数,根据文件pid判断是否存在进程
        atexit.register(self.delpid)
        pid = str(os.getpid())
        file(self.pidfile, ‘w+‘).write(‘%s\n‘ % pid)

    # 程序退出后移除PID文件
    def delpid(self):
        os.remove(self.pidfile)

    def start(self, *args, **kwargs):
        # 检查pid文件是否存在以探测是否存在进程
        try:
            pid = self._getPid()
        except IOError:
            pid = None

        # 如果PID存在,则说明进程没有关闭。
        if pid:
            message = ‘pidfile %s already exist. Process already running!\n‘
            sys.stderr.write(message % self.pidfile)
            # 程序退出
            sys.exit(1)

        # 构造进程环境
        self._daemonize()
        # 执行具体任务
        self._run(*args, **kwargs)

    def stop(self):
        # 从pid文件中获取pid
        try:
            pid = self._getPid()
        except IOError:
            pid = None

        # 如果程序没有启动就直接返回不在执行
        if not pid:
            message = ‘pidfile %s does not exist. Process not running!\n‘
            sys.stderr.write(message % self.pidfile)
            return

        # 杀进程
        try:
            while 1:
                # 发送信号,杀死进程
                os.kill(pid, SIGTERM)
                time.sleep(0.1)
                message = ‘Process is stopped.\n‘
                sys.stderr.write(message)
        except OSError, err:
            err = str(err)
            if err.find(‘No such process‘) > 0:
                if os.path.exists(self.pidfile):
                    os.remove(self.pidfile)
            else:
                print str(err)
                sys.exit(1)

    # 获取PID
    def _getPid(self):
        try:
            # 读取保存PID的文件
            pf = file(self.pidfile, ‘r‘)
            # 转换成整数
            pid = int(pf.read().strip())
            # 关闭文件
            pf.close()
        except IOError:
            pid = None
        except SystemExit:
            pid = None
        return pid

    # 重启的功能就是杀死之前的进程,然后再运行一个
    def restart(self, *args, **kwargs):
        self.stop()
        self.start(*args, **kwargs)

    # 获取守护程序运行状态
    def status(self):
        try:
            pid = self._getPid()
        except IOError:
            pid = None

        if not pid:
            message = "No such a process running.\n"
            sys.stderr.write(message)
        else:
            message = "The process is running, PID is %s .\n"
            sys.stderr.write(message % str(pid))

    def _run(self, *args, **kwargs):
        """
        这里是孙子进程需要做的事情,你可以继承这个类,然后重写这里的代码,上面其他的都可以不做修改
        """
        while True:
            """
            print 等于调用 sys.stdout.write(), sys.stdout.flush()是立即刷新输出。正常情况下如果是输出到控制台那么会立即输出
            但是重定向到一个文件就不会了,因为等于写文件,所以需要进行刷新进行立即输出。 下面使用print 还是 write都是一样的。
            """
            # print ‘%s:hello world\n‘ % (time.ctime(),)
            sys.stdout.write(‘%s:hello world\n‘ % (time.ctime(),))
            sys.stdout.flush()
            time.sleep(2)

if __name__ == ‘__main__‘:
    daemon = Daemon(‘/tmp/watch_process.pid‘, stdout=‘/tmp/watch_stdout.log‘)
    if len(sys.argv) == 2:
        if ‘start‘ == sys.argv[1]:
            daemon.setApplicationName(sys.argv[0])
            daemon.start()
        elif ‘stop‘ == sys.argv[1]:
            daemon.stop()
        elif ‘restart‘ == sys.argv[1]:
            daemon.restart()
        elif ‘status‘ == sys.argv[1]:
            daemon.status()
        else:
            print ‘unknown command‘
            sys.exit(2)
        sys.exit(0)
    else:
        print ‘usage: %s start|stop|restart|status‘ % sys.argv[0]
        sys.exit(2)

  

原文地址:https://www.cnblogs.com/yunxizhujing/p/9382342.html

时间: 2024-10-18 07:11:38

Python编写守护进程程序的相关文章

Python编写简易木马程序(转载乌云)

Python编写简易木马程序 light · 2015/01/26 10:07 0x00 准备 文章内容仅供学习研究.切勿用于非法用途! 这次我们使用Python编写一个具有键盘记录.截屏以及通信功能的简易木马.依然选用Sublime text2 +JEDI(python自动补全插件)来撸代码,安装配置JEDI插件可以参照这里: /tips/?id=4413 首先准备好我们需要的依赖库,python hook和pythoncom. 下载安装python hook 下载安装pythoncom模块:

python—day29 守护进程、互斥锁、模拟抢票、IPC通信机制、生产者消费者模型

1.守护进程: 什么是守护进程,假如你是皇帝,每日每夜守护你的就是太监,守护进程就相当于太监,当皇帝驾崩后太监也需要陪葬,所以守护进程当父进程销毁时就一起销毁: 1 from multiprocessing import Process 2 3 import time 4 5 def task(name): 6 7 time.sleep(0.5) 8 print('%s' %name) 9 10 11 if __name__ == '__main__': 12 p = Process(targe

Python setdaemon守护进程

setdaemon守护进程 #_*_coding:utf-8_*_ __author__ = 'gaogd' import time import threading ''' 守护进程,如果主线程down了,子线程也就没有了. 下面先通过主进程生成main主线程,之后main主线程再生成10个子线程. ''' ''' def run(num):     if not num == 5:         time.sleep(1)     print 'Hi, I am thread %s..la

python之守护进程

主进程创建子进程,然后将该进程设置成守护自己的进程,守护进程就好比崇祯皇帝身边的老太监,崇祯皇帝已死老太监就跟着殉葬了. 关于守护进程需要强调两点: 其一:守护进程会在主进程代码执行结束后就终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children 如果我们有两个任务需要并发执行,那么开一个主进程和一个子进程分别去执行就ok了,如果子进程的任务在主进程任务结束后就没有

编写守护进程

1)创建子进程,父进程退出 2)在子进程中创建新会话 setsid()函数,用于创建一个新会话,并担任会话组的组长.调用setsid()函数有三个作用: 让进程摆脱原会话的控制 让进程摆脱原进程组的控制 让进程摆脱原控制终端的控制 3)   用chdir()函数改变工作目录 4)用umask()设置文件权限掩码 5)   关闭文件描述符 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include

利用python编写第一个程序

print("Hello world") 原文地址:https://www.cnblogs.com/Run1997/p/10230418.html

编写一个守护进程

编写守护进程程序时需遵循一些基本规则,以防止产生不必要的交互作用,下面先说明这些规则,然后给出一个按照这些规则编写的函数deamonize. (1)首先,调用umask将文件创建屏蔽字设置为一个已知值(通常为0). (2)调用fork,然后是父进程exit. (3)调用setsid创建一个新会话. (4)将当前工作目录更改为根目录. (5)关闭不需要的文件描述符. (6)将文件描述符0,1,2指向 /dev/null 以下是一个记录日志的守护进程. 1 #include "apue.h"

《APUE》读书笔记第十三章-守护进程

守护进程 守护进程是生存期较长的一种进程,它们常常在系统自举时启动,仅在系统关闭时才终止.因为它们没有控制终端,所以说它们是在后台运行的.UNIX系统由很多守护进程,它们执行日常事务活动. 本章主要介绍守护进程的结构,以及如何编写守护进程程序和守护进程如何报告错误情况. 一.守护进程的编程规则 (1)首先要做的是调用umask将文件模式创建屏蔽字设置为0.这是由于继承得来的文件模式创建屏蔽字可能会拒绝设置某些权限. (2)调用fork,然后使父进程退出(exit). (3)调用setsid以创建

《Unix环境高级编程》读书笔记 第13章-守护进程

1. 引言 守护进程是生存期长的一种进程.它们常常在系统引导装入时启动,仅在系统关闭时才终止.它们没有控制终端,在后台运行. 本章说明守护进程结构.如何编写守护进程程序.守护进程如何报告出错情况. 2. 守护进程的特征 基于BSD的系统下执行:ps -axj -a 显示由其他用户所拥有的进程的状态:-x 显示没有控制终端的进程状态:-j 显示与作业有关的信息 基于System V的系统下执行:ps -efj Linux下执行以上两个命令输出一致 常见的守护进程: kswapd,内存换页守护进程.