如何編寫python的daemon程序
以前把守護(hù)進(jìn)程與后臺(tái)任務(wù)搞混了,后面看了文章才知道這兩者的區(qū)別,寫此文表達(dá)自己對(duì)守護(hù)進(jìn)程的理解.
1:什么是守護(hù)進(jìn)程?
所謂守護(hù)進(jìn)程是一種是 Linux 的一種長(zhǎng)期運(yùn)行的后臺(tái)服務(wù)進(jìn)程,httpd、named、sshd 等服務(wù)都是以守護(hù)進(jìn)程 Daemon 方式運(yùn)行的,通常服務(wù)名稱以字母d結(jié)尾,也就是 Daemon 第一個(gè)字母.
- 無需控制終端(不需要與用戶交互)
- 在后臺(tái)運(yùn)行
- 生命周期比較長(zhǎng),一般是隨系統(tǒng)啟動(dòng)和關(guān)閉
2:守護(hù)進(jìn)程必要性
通常我們執(zhí)行任務(wù)時(shí)是在前臺(tái)執(zhí)行,占領(lǐng)了當(dāng)前終端,此時(shí)無法進(jìn)行操作,就算我們添加了 &符號(hào),將程序放到后臺(tái),但也就因?yàn)榻K端斷網(wǎng)等問題,導(dǎo)致程序中斷。
所要知道的是:在目前的linux上,有了systemd這個(gè)服務(wù),這個(gè)服務(wù)管理工具可以方便我們寫在后臺(tái)運(yùn)行的程序,甚至可以代替這種守護(hù)進(jìn)程。通過把寫服務(wù)的配置文件,讓systemd監(jiān)控我們的程序,可以隨系統(tǒng)啟動(dòng)而運(yùn)行,可以設(shè)定啟動(dòng)條件,及其的方便。
3:進(jìn)程組
$ ps -o pid,pgid,ppid,comm | cat PID PGID PPID COMMAND 10179 10179 10177 bash 10263 10263 10179 ps 10264 10263 10179 cat
- bash:進(jìn)程和進(jìn)程組ID都是 10179,父進(jìn)程其實(shí)是 sshd(10177)
- ps:進(jìn)程和進(jìn)程組ID都是 10263,父進(jìn)程是 bash(10179),因?yàn)槭窃?Shell 上執(zhí)行的命令
- cat:進(jìn)程組 ID 與 ps 的進(jìn)程組 ID 相同,父進(jìn)程同樣是 bash(10179)
4:會(huì)話組
多個(gè)進(jìn)程構(gòu)成一個(gè)進(jìn)程組,而會(huì)話組是由多個(gè)進(jìn)程組構(gòu)建而。而進(jìn)程組又被稱為job,會(huì)話有前臺(tái)作業(yè),也會(huì)有后臺(tái)作業(yè);一個(gè)會(huì)話可以有一個(gè)控制終端,當(dāng)控制終端有輸入和輸出時(shí)都會(huì)傳遞給前臺(tái)進(jìn)程組,比如Ctrl + Z。會(huì)話的意義在于能將多個(gè)作業(yè)通過一個(gè)終端控制,一個(gè)前臺(tái)操作,其它后臺(tái)運(yùn)行。
那么如何編寫守護(hù)進(jìn)程呢?
其實(shí)編寫守護(hù)進(jìn)程很簡(jiǎn)單,只需要遵循一下幾點(diǎn)即可
1:創(chuàng)建子進(jìn)程,父進(jìn)程退出
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 0 49 49 49 pts/2 70 Ss 0 0:00 /bin/bash 49 70 70 49 pts/2 70 R+ 0 0:00 \_ ps axjf 0 17 17 17 pts/1 68 Ss 0 0:00 /bin/bash 17 68 68 17 pts/1 68 S+ 0 0:00 \_ python hello.py 68 69 68 17 pts/1 68 S+ 0 0:00 \_ python hello.py 0 1 1 1 pts/0 1 Ss+ 0 0:00 /bin/bash
進(jìn)程 fork 后,父進(jìn)程退出。這么做的原因有 2 點(diǎn):
如果守護(hù)進(jìn)程是通過 Shell 啟動(dòng),父進(jìn)程退出,Shell 就會(huì)認(rèn)為任務(wù)執(zhí)行完畢,這時(shí)子進(jìn)程由 init 收養(yǎng)
子進(jìn)程繼承父進(jìn)程的進(jìn)程組 ID,保證了子進(jìn)程不是進(jìn)程組組長(zhǎng),因?yàn)楹筮呎{(diào)用setsid()要求必須不是進(jìn)程組長(zhǎng)
PGID就是進(jìn)程所屬的Group的Leader的PID,如果PGID=PID,那么該進(jìn)程是Group Leader
2、子進(jìn)程創(chuàng)建新會(huì)話
調(diào)用setsid()創(chuàng)建一個(gè)新的會(huì)話,并成為新會(huì)話組長(zhǎng)。這個(gè)步驟主要是要與繼承父進(jìn)程的會(huì)話、進(jìn)程組、終端脫離關(guān)系。
那么問題來了,為什么進(jìn)程組組長(zhǎng)無法調(diào)用setsid()呢?
對(duì)于進(jìn)程組長(zhǎng)來說,進(jìn)程組 ID 已經(jīng)和 PID 相同了,如果它被允許調(diào)用setsid()的話,它的進(jìn)程組 ID 會(huì)保持不變,會(huì)出現(xiàn):
1:進(jìn)程組長(zhǎng)屬于新的會(huì)話;
2:老的進(jìn)程組成員屬于舊的會(huì)話。
這樣情況變成了一個(gè)進(jìn)程組的成員屬于不同的會(huì)話,Linux想要禁止這種情況的發(fā)生。
3、禁止子進(jìn)程重新打開終端
此刻子進(jìn)程是會(huì)話組長(zhǎng),為了防止子進(jìn)程重新打開終端,再次 fork 后退出父進(jìn)程,也就是此子進(jìn)程。這時(shí)子進(jìn)程 2 不再是會(huì)話組長(zhǎng),無法再打開終端。其實(shí)這一步驟不是必須的,不過加上這一步驟會(huì)顯得更加嚴(yán)謹(jǐn)。
4、設(shè)置當(dāng)前目錄為根目錄
如果守護(hù)進(jìn)程的當(dāng)前工作目錄是/usr/home目錄,那么管理員在卸載/usr分區(qū)時(shí)會(huì)報(bào)錯(cuò)的。為了避免這個(gè)問題,可以調(diào)用chdir()函數(shù)將工作目錄設(shè)置為根目錄/。
5、設(shè)置文件權(quán)限掩碼
文件權(quán)限掩碼是指屏蔽掉文件權(quán)限中的對(duì)應(yīng)位。由于使用 fork()函數(shù)新建的子進(jìn)程繼承了父進(jìn)程的文件權(quán)限掩碼,這就給該子進(jìn)程使用文件帶來了諸多的麻煩。因此,把文件權(quán)限掩碼設(shè)置為 0,可以大大增強(qiáng)該守護(hù)進(jìn)程的靈活性。通常使用方法是umask(0)。
6、關(guān)閉文件描述符
子進(jìn)程會(huì)繼承已經(jīng)打開的文件,它們占用系統(tǒng)資源,且可能導(dǎo)致所在文件系統(tǒng)無法卸載。此時(shí)守護(hù)進(jìn)程與終端脫離,常說的輸入、輸出、錯(cuò)誤描述符也應(yīng)該關(guān)閉,畢竟這個(gè)時(shí)候也不會(huì)使用終端了。
守護(hù)進(jìn)程的出錯(cuò)處理
由于守護(hù)進(jìn)程脫離了終端,不能將錯(cuò)誤信息輸出到控制終端,即使 gdb 也無法正常調(diào)試。常用的方法是使用 syslog 服務(wù),將錯(cuò)誤信息輸入到/var/log/messages中。
syslog 是 Linux 中的系統(tǒng)日志管理服務(wù),通過守護(hù)進(jìn)程 syslogd 來維護(hù)。該守護(hù)進(jìn)程在啟動(dòng)時(shí)會(huì)讀一個(gè)配置文件/etc/syslog.conf。該文件決定了不同種類的消息會(huì)發(fā)送向何處。
代碼展示
import os import sys def daemonize(pid_file=None): pid = os.fork() if pid: sys.exit(0) os.setsid() _pid = os.fork() if _pid: sys.exit(0) os.umask(0) os.chdir('/') sys.stdout.flush() sys.stderr.flush() with open('/dev/null') as read_null, open('/dev/null','w') as write_null: os.dup2(read_null.fileno(), sys.stdin.fileno()) os.dup2(write_null.fileno(), sys.stdout.fileno()) os.dup2(write_null.fileno(), sys.stderr.fileno()) if pid_file: with open(pid_file,'w+') as f: f.write(str(os.getpid())) if __name__ == "__main__": daemonize('test.txt')
關(guān)于os.dup2這個(gè)函數(shù)
os.dup2() 方法用于將一個(gè)文件描述符 fd 復(fù)制到另一個(gè) fd2。
Unix, Windows 上可用。
>>> import os >>> f = open("hello.txt","a") >>> os.dup2(f.fileno(),1) >>> f.close() >>> print("hello world") >>> print("changed") cat hello.txt 1 hello world changed
附加話題
為什么服務(wù)器端常常fork兩次呢?
因?yàn)檫@是為了避免產(chǎn)生僵尸進(jìn)程。
當(dāng)我們只fork()一次后,存在父進(jìn)程和子進(jìn)程。這時(shí)有兩種方法來避免產(chǎn)生僵尸進(jìn)程:
- 父進(jìn)程調(diào)用waitpid()等函數(shù)來接收子進(jìn)程退出狀態(tài)。
- 父進(jìn)程先結(jié)束,子進(jìn)程則自動(dòng)托管到Init進(jìn)程(pid = 1)。
目前先考慮子進(jìn)程先于父進(jìn)程結(jié)束的情況:
- 若父進(jìn)程未處理子進(jìn)程退出狀態(tài),在父進(jìn)程退出前,子進(jìn)程一直處于僵尸進(jìn)程狀態(tài)。
- 若父進(jìn)程調(diào)用waitpid()(這里使用阻塞調(diào)用確保子進(jìn)程先于父進(jìn)程結(jié)束)來等待子進(jìn)程結(jié)束,將會(huì)使父進(jìn)程在調(diào)用waitpid()后進(jìn)入睡眠狀態(tài),只有子進(jìn)程結(jié)束父進(jìn)程的waitpid()才會(huì)返回。 如果存在子進(jìn)程結(jié)束,但父進(jìn)程還未執(zhí)行到waitpid()的情況,那么這段時(shí)期子進(jìn)程也將處于僵尸進(jìn)程狀態(tài)。
由此,可以看出父進(jìn)程與子進(jìn)程有父子關(guān)系,除非保證父進(jìn)程先于子進(jìn)程結(jié)束或者保證父進(jìn)程在子進(jìn)程結(jié)束前執(zhí)行waitpid(),子進(jìn)程均有機(jī)會(huì)成為僵尸進(jìn)程。那么如何使父進(jìn)程更方便地創(chuàng)建不會(huì)成為僵尸進(jìn)程的子進(jìn)程呢?這就要用兩次fork()了。
父進(jìn)程一次fork()后產(chǎn)生一個(gè)子進(jìn)程隨后立即執(zhí)行waitpid(子進(jìn)程pid, NULL, 0)來等待子進(jìn)程結(jié)束,然后子進(jìn)程fork()后產(chǎn)生孫子進(jìn)程隨后立即exit(0)。這樣子進(jìn)程順利終止(父進(jìn)程僅僅給子進(jìn)程收尸,并不需要子進(jìn)程的返回值),然后父進(jìn)程繼續(xù)執(zhí)行。這時(shí)的孫子進(jìn)程由于失去了它的父進(jìn)程(即是父進(jìn)程的子進(jìn)程),將被轉(zhuǎn)交給Init進(jìn)程托管。于是父進(jìn)程與孫子進(jìn)程無繼承關(guān)系了,它們的父進(jìn)程均為Init,Init進(jìn)程在其子進(jìn)程結(jié)束時(shí)會(huì)自動(dòng)收尸,這樣也就不會(huì)產(chǎn)生僵尸進(jìn)程了。
以上就是如何編寫python的daemon程序的詳細(xì)內(nèi)容,更多關(guān)于python的daemon程序的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- python 如何設(shè)置守護(hù)進(jìn)程
- Python 創(chuàng)建守護(hù)進(jìn)程的示例
- Python守護(hù)進(jìn)程實(shí)現(xiàn)過程詳解
- python并發(fā)編程多進(jìn)程之守護(hù)進(jìn)程原理解析
- python實(shí)現(xiàn)守護(hù)進(jìn)程、守護(hù)線程、守護(hù)非守護(hù)并行
- python使用fork實(shí)現(xiàn)守護(hù)進(jìn)程的方法
- Python如何實(shí)現(xiàn)守護(hù)進(jìn)程的方法示例
- Python守護(hù)進(jìn)程和腳本單例運(yùn)行詳解
- python daemon守護(hù)進(jìn)程實(shí)現(xiàn)
- Python守護(hù)進(jìn)程用法實(shí)例分析
- python實(shí)現(xiàn)的守護(hù)進(jìn)程(Daemon)用法實(shí)例
相關(guān)文章
python如何控制進(jìn)程或者線程的個(gè)數(shù)
這篇文章主要介紹了python如何控制進(jìn)程或者線程的個(gè)數(shù),幫助大家更好的理解和使用python,感興趣的朋友可以了解下2020-10-10python里對(duì)list中的整數(shù)求平均并排序
本文主要記述了使用Python將list重點(diǎn)整數(shù)求平均值之后在進(jìn)行排列的過程,并把代碼分享給大家,希望大家能給鼓鼓掌~~~2014-09-09利用python數(shù)據(jù)分析處理進(jìn)行炒股實(shí)戰(zhàn)行情
這篇文章主要介紹了利用python數(shù)據(jù)分析進(jìn)行炒股實(shí)戰(zhàn)行情,本文主要介紹三部分:數(shù)據(jù)采集,數(shù)據(jù)預(yù)處理,利用SVM算法進(jìn)行建模,本文僅供參考借鑒2021-08-08django框架cookie和session用法實(shí)例詳解
這篇文章主要介紹了django框架cookie和session用法,結(jié)合實(shí)例形式詳細(xì)分析了Django框架cookie和session的功能、原理、使用方法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2019-12-12Python Flask全棧項(xiàng)目實(shí)戰(zhàn)構(gòu)建在線書店流程
這篇文章主要為大家介紹了Python Flask全流程全棧項(xiàng)目實(shí)戰(zhàn)之在線書店構(gòu)建實(shí)現(xiàn)過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11python+Selenium自動(dòng)化測(cè)試——輸入,點(diǎn)擊操作
這篇文章主要介紹了python+Selenium自動(dòng)化測(cè)試——輸入,點(diǎn)擊操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03