【淺談守護程序】Demo:後臺監控程式-- Python實現
前言
最近在做的專案需要定期檢測某個程序是否執行,若掛了自動重啟,腦袋一拍覺得需要這樣一個守護程序 來進行監控,於是順便複習了一下守護程序。
正文
什麼是守護程序?
守護程序(daemon)是生存期長的一種程序。它們常常在系統引導裝入時啟動,僅在系統關閉時才終止。因為它們沒有控制終端,所以說它們是在後臺執行的。
—–《APUE》
我們需求就是默默地定期執行任務 與守護程序非常的匹配。
編寫一個守護程序
守護程序需要
- 設定檔案模式建立遮蔽字。
- 沒有控制終端。
- 確定工作目錄。
- 關閉不再需要的檔案描述符。
- 告別標準輸入輸出
如何實現
檔案模式遮蔽字
只需要通過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 所以不是會話首程序