1. 程式人生 > >【淺談守護程序】Demo:後臺監控程式-- Python實現

【淺談守護程序】Demo:後臺監控程式-- Python實現

前言

最近在做的專案需要定期檢測某個程序是否執行,若掛了自動重啟,腦袋一拍覺得需要這樣一個守護程序 來進行監控,於是順便複習了一下守護程序。

正文

什麼是守護程序?

守護程序(daemon)是生存期長的一種程序。它們常常在系統引導裝入時啟動,僅在系統關閉時才終止。因為它們沒有控制終端,所以說它們是在後臺執行的。
—–《APUE》

我們需求就是默默地定期執行任務 與守護程序非常的匹配。

編寫一個守護程序

守護程序需要

  1. 設定檔案模式建立遮蔽字。
  2. 沒有控制終端。
  3. 確定工作目錄。
  4. 關閉不再需要的檔案描述符。
  5. 告別標準輸入輸出

如何實現

檔案模式遮蔽字

只需要通過umask() 即可,由繼承(至於為什麼是繼承,請看後面)得到的檔案模式建立遮蔽字可能已經被修改為拒絕某些許可權,所以我們最好根據需要進行重新設定。

甩掉控制終端

我們希望守護程序在後臺默默執行不受控制終端的控制。這裡是通過setsid() 函式來實現。呼叫這個函式的效果是:

1)建立一個新會話(session)
2)建立一個新程序組。
3)呼叫程序成為 新會話 的首程序
4)呼叫程序成為 新程序組 的組長程序
5)呼叫程序失去控制終端

最後一條正是我們需要的效果,但這個函式也有前提條件。那就是呼叫函式不能是程序組的組長程序

,為了達到這個條件,我們通常的做法是,首先呼叫fork() 然後父程序退出,由子程序來setsid()。因為,子程序獲得的程序組ID和自己的PID必然不同,也就是子程序必然不是程序組的組長程序,可以順利呼叫setsid()

潛在的BUG

在基於System V的系統如Linux,存在以下的實現

當會話首程序開啟第一個尚未與一個會話相關聯的終端裝置時,只要在呼叫open() 時沒有指定O_NOCTTY標誌,那麼System V派生的系統將此作為控制終端分配給此會話。 —《APUE》

為了消除這個BUG,我們在通常的做法(先呼叫fork() 然後父程序退出,由子程序來setsid()

)之後,再呼叫一次fork(),然後再次讓父程序退出,使用子程序,這樣子程序不是會話(組)首程序,就消除了這個BUG。
當然也可以在之後每次open終端裝置時加上O_NOCTTY標誌。。。。

同時,在glibc中的daemon() 就因為其實現只是fork() 一次,存在這個BUG(來自man)

確定工作目錄

這裡主要是因為守護程序其長期工作的特性,如果當前目錄為掛載的檔案系統,會導致其檔案系統不能被解除安裝。所以我們用chdir()顯式地設定工作目錄。

關閉不再需要的檔案描述符

還是由於fork() 繼承的原因可能具有不需要一些檔案描述符,所以我們要顯式的關閉。

告別標準輸入輸出

。。。其實前面都關了,不過考慮到某些和標準輸出輸入錯誤扯上關係的庫函式,我們就把0,1,2設定為/dev/null

Demo:後臺監控程式—Python實現

#!/usr/bin/env python
# coding=utf-8
# Jack Kang
import os
import time
import sys

# 檢測port對應節點是否存活
def check(port):
    isAlive = "ps -ef | grep \"" + port + " \[cluster\]\"" 
    recovery = "redis-server ./" + port + "/redis.conf"  
    if os.system(isAlive):  #判斷
        os.system(recovery) #程序不存在,重啟節點



#設定為守護程序 

stdin = '/dev/null'
stdout = '/dev/null'
stderr = '/dev/null'

try:
    pid = os.fork()
    if pid > 0 :
        sys.exit(0)
except OSError, e:
    print "error #1"
    sys.exit(1)

os.chdir("./redis_cluster") # 切換工作目錄
os.umask(0)                 # 設定檔案模式建立遮蔽字
os.setsid()                 # 甩掉控制終端

# 第二次fork 保證子程序不是會話首程序
try:
    pid = os.fork()
    if pid > 0:
        sys.exit(0)        #父程序退出
except OSError:
    print "error #2"
    sys.exit(1)

for f in sys.stdout, sys.stderr: 
    f.flush()              # 重新整理緩衝區

#將標準輸出輸入錯誤改為/dev/null   
si = open(stdin, 'r')
so = open(stdout, 'a+')
se = open(stderr, 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())

#主迴圈 每5s檢測port程序是否存活
port = sys.argv[1]  
while True:
    check(port)
    time.sleep(5)

其實C實現和Python很像啦。這裡就偷懶不寫出C實現了。。。有空補上。

兩次fork的結果截圖

結果

可以看到pid 不等於sid 所以不是會話首程序

引用及參考

才疏學淺,不足之處,歡迎指正。