python 守護進程


在linux環境上,使用守護進程保護python程序在后台運行,不受會話控制終端影響。

0x01 守護進程和后台運行的區別:

1、讓程序在后台運行,只用在程序啟動的時候在結尾加&,這樣在控制終端進行輸入不會影響程序的運行。

如python main.py&  程序啟動后,可以在該控制繼續進行輸入,不影響main.py的運行。但是如果關閉會話連接,main.py進程就關閉了。

2、后台運行的程序有控制終端,守護進程沒有。即如果main.py運行的會話連接斷開,不影響main.py進程的運行。

     父進程退出后,子進程被pid為1的進程(init進程)接管,時候子進程不受終端退出的影響。

0x02 守護進程編程原理:

1、創建子進程,父進程退出

使用os.fork創建進程,並將父進程退出。  

目的:讓子進程脫離父進程控制,被init進程接管,即變成pid為1的進程的子進程。

2、在子進程中創建新的會話

使用setid創建新會話,並擔任該會話組的組長。

目的:

  • 讓進程擺脫原會話的控制
  • 讓進程擺脫原進程組的控制
  • 讓進程擺脫原控制終端的控制

由於創建守護進程的第一步調用了fork函數來創建子進程,再將父進程退出。由於在調用了fork函數時,子進程全盤拷貝了父進程的會話期、進程組、控制終端等,雖然父進程退出了,但會話期、進程組、控制終端等並沒有改變,因此,這還不是真正意義上的獨立開來,而setsid函數能夠使進程完全獨立出來,從而擺脫其他進程的控制。

3、重設文件權限掩碼

使用umask(0)重新設置文件權限,是為了去掉父進程遺留的文件權限設置。

4、禁止進程重新打開控制終端

在基於SystemV的系統中,有建議再一次調用fork 並使父進程退出。而新產生的進程將會成為真正的守護進程。這一步驟將保證守護進程不是一個sessionleader,進而阻止它獲取一個控制終端。

第二次fork不是必須的,打開一個控制終端的條件是該進程必須是session leader。第一次fork,setsid之后,子進程成為session leader,進程可以打開終端;第二次fork產生的進程,不再是session leader,進程則無法打開終端。

5、改變當前工作目錄

防止占用別的路徑的working dir的fd,導致一些block不能unmount。但注意這里修改了以后,守護進程下面的子進程log打印就都在被修改的路徑下了。

 

0x03 DEMO

#!/usr/bin/env python
#coding: utf-8
#pythonlinux的守護進程

import sys
import os
import time
import string
import ctypes
import datetime
from logger import *

logyyx = Logger('tsl.log', logging.ERROR, logging.DEBUG)

class Daemon:
    def __init__(self, findCmd, runCmd, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
        self.findCmd = findCmd
        self.runCmd = runCmd
        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        #self.logger = logging.getLogger()
    '''
    def LoggerInit(self):
        logfile = '/home/***/log/tsl.log'
        hdlr=logging.FileHandler(logfile)
        formatter = logging.Formatter('\n%(asctime)s   %(filename)s[line:%(lineno)d]   %(levelname)s\n%(message)s')
        hdlr.setFormatter(formatter)
        self.logger.addHandler(hdlr)
        self.logger.setLevel(logging.NOTSET)
        return
    '''
    def daemonize(self):
        try:
            #第一次fork,生成子進程,脫離父進程
            if os.fork() > 0:
                raise SystemExit(0)      #退出主進程
        except OSError as e:
            logyyx.error("fork #1 failed:\n")
            #sys.exit(1)
            raise RuntimeError('fork #1 faild: {0} ({1})\n'.format(e.errno, e.strerror))

        os.setsid()        #設置新的會話連接
        os.umask(0)        #重新設置文件創建權限
        try:
            #第二次fork,禁止進程打開終端
            if os.fork() > 0:
                raise SystemExit(0)
        except OSError as e:
            logyyx.error("fork #2 failed:\n")
            #sys.exit(1)
            raise RuntimeError('fork #2 faild: {0} ({1})\n'.format(e.errno, e.strerror))
        os.chdir("/")  # 修改工作目錄
        # Flush I/O buffers
        sys.stdout.flush()
        sys.stderr.flush()

        # Replace file descriptors for stdin, stdout, and stderr
        with open(self.stdin, 'rb', 0) as f:
            os.dup2(f.fileno(), sys.stdin.fileno())
        with open(self.stdout, 'ab', 0) as f:
            os.dup2(f.fileno(), sys.stdout.fileno())
        with open(self.stderr, 'ab', 0) as f:
            os.dup2(f.fileno(), sys.stderr.fileno())

        return

    def start(self):
        #檢查pid文件是否存在以探測是否存在進程
        esb = os.popen(self.findCmd).read().strip()
        if not (esb == '0'):
            print"the deamon is already running!!!"
            return
        else:
            #啟動監控
            self.daemonize()
            self.run()

    def run(self):
        now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        while True:
            try:
                esb = os.popen(self.findCmd).read().strip()
                if (esb == '0'):
                    logyyx.info("deamon on  %s" %now)
                    os.system(self.runCmd)
            except:
                pass
            time.sleep(10)

    def KillPid(self,name):
        ps_str = 'ps aux |grep '+name+' | grep -v grep'
        x= os.popen(ps_str).read()
        if x:
            proc = x.split('\n')
            for line in proc:
                print line
                try:
                    proc_id = line.split()[1]
                    os.system('kill -9 %s' % proc_id)
                except:
                    pass
        else:
            return

    def checkpid(self, name):
        findCmd='ps -fe |grep '+name+' | grep -v grep | wc -l'
        esb = os.popen(findCmd).read().strip()
        if not (esb == '0'):
            #殺進程
            try:
                self.KillPid(name)
            except:
                print"kill %s failed!!!" % name
                logyyx.error("the deamon  %s  kill failed" % name)
                return
        return
    def stop(self):
        self.checkpid('main.py')
        self.checkpid('deamon.py')
        return

    def restart(self):
        self.stop()
        self.start()

if __name__ == "__main__":
    findCmd = 'ps -fe |grep main.py | grep -v grep | wc -l'
    runCmd = 'python /home/***/main.py'
    LOG = './tsl.log'
    daemon = Daemon(findCmd, runCmd, stdout=LOG, stderr=LOG)

    #daemon.start()
    if len(sys.argv) != 2:
        print('Usage: {} [start|stop]'.format(sys.argv[0]))
        raise SystemExit(1)
    if 'start' == sys.argv[1]:
        daemon.start()
    elif 'stop' == sys.argv[1]:
        daemon.stop()
    elif 'restart' == sys.argv[1]:
        daemon.restart()
    else:
        print('Unknown command {0}'.format(sys.argv[1]))
        raise SystemExit(1)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM