1. 程式人生 > >python fork()建立新的程序,daemon程序

python fork()建立新的程序,daemon程序

fork程序後的程式流程

使用fork建立子程序後,子程序會複製父程序的資料資訊,而後程式就分兩個程序繼續執行後面的程式,這也是fork(分叉)名字的含義了。在子程序內,這個方法會返回0;在父程序內,這個方法會返回子程序的編號PID。可以使用PID來區分兩個程序:

#!/usr/bin/env python
#coding=utf8
 
import os
 
#建立子程序之前宣告的變數
source = 10
 
try:
    pid = os.fork()
 
    if pid == 0: #子程序
        print "this is child process."
        #在子程序中source自減1
        source = source - 1
        sleep(3)
    else: #父程序
        print "this is parent process."
 
    print source
except OSError, e:
    pass


上面程式碼中,在子程序建立前,聲明瞭一個變數source,然後在子程序中自減1,最後打印出source的值,顯然父程序打印出來的值應該為10,子程序打印出來的值應該為9。為了明顯區分父程序和子程序,讓子程序睡3秒,就看的比較明顯了。

既然子程序是父程序建立的,那麼父程序退出之後,子程序會怎麼樣呢?此時,子程序會被PID為1的程序接管,就是init程序了。這樣子程序就不會受終端退出影響了,使用這個特性就可以建立在後臺執行的程式,俗稱守護程序(daemon)。

---------------------

import os  
  
def child():  
    print 'A new child:', os.getpid()  
    print 'Parent id is:', os.getppid()  
    os._exit(0)  
  
def parent():  
    while True:  
        newpid=os.fork()  
        print newpid  
        if newpid==0:  
            child()  
        else:  
            pids=(os.getpid(),newpid)  
            print "parent:%d,child:%d"%pids  
            print "parent parent:",os.getppid()         
        if raw_input()=='q':  
            break  
  
parent()  
  在我們載入了os模組之後,我們parent函式中fork()函式生成了一個子程序,返回值newpid有兩個,一個為0,用以表示子程序,一個是大於0的整數,用以表示父程序,這個常數正是子程序的pid. 通過print語句我們可以清晰看到兩個返回值。如果fork()返回值是一個負值,則表明子程序生成不成功(這個簡單程式中沒有考慮這種情況)。如果newpid==0,則表明我們進入到了子程序,也就是child()函式中,在子程序中我們輸出了自己的id和父程序的id。如果進入了else語句,則表明newpid>0,我們進入到父程序中,在父程序中os.getpid()得到自己的id,fork()返回值newpid表示了子程序的id,同時我們輸出了父程序的父程序的id. 通過實驗我們可以看到if和else語句的執行順序是不確定的,子、父程序的執行順序由作業系統的排程
演算法來決定。

----------

#!/usr/bin/env python
#coding=utf8
 
import os, sys, time
 
#產生子程序,而後父程序退出
pid = os.fork()
if pid > 0:
    sys.exit(0)
 
#修改子程序工作目錄
os.chdir("/")
#建立新的會話,子程序成為會話的首程序
os.setsid()
#修改工作目錄的umask
os.umask(0)
 
#建立孫子程序,而後子程序退出
pid = os.fork()
if pid > 0:
    sys.exit(0)
 
#重定向標準輸入流、標準輸出流、標準錯誤
sys.stdout.flush()
sys.stderr.flush()
si = file("/dev/null", 'r')
so = file("/dev/null", 'a+')
se = file("/dev/null", 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
 
#孫子程序的程式內容
while True:
    time.sleep(10)
    f = open('/home/test.txt', 'a')
    f.write('hello')

上面的程式沒有任何錯誤處理,但是不影響原理分析。如果要應用到專案裡,還需完善。下面筆者談下自己對每個步驟的理解。

1、fork子程序,父程序退出

通常,我們執行服務端程式的時候都會通過終端連線到伺服器,成功連線後會載入shell環境,終端和shell都是程序,shell程序是終端程序的子程序,通過ps命令可以很容易的檢視到。在這個shell環境下一開始執行的程式都是shell程序的子程序,自然會受到shell程序的影響。在程式裡fork子程序後,父程序退出,對了shell程序來說,這個父程序就算執行完了,而產生的子程序會被init程序接管,從而也就脫離了終端的控制。

2、修改子程序的工作目錄

子程序在建立的時候會繼承父程序的工作目錄,如果執行的程式是在u盤裡的,就會導致u盤不能解除安裝。

3、建立新會話

使用setsid後,子程序就會成為新會話的首程序(session leader);子程序會成為新程序組的組長程序;子程序沒有控制終端。

4、修改umask

由於umask會遮蔽許可權,所以設定為0,這樣可以避免讀寫檔案時碰到許可權問題。

5、fork孫子程序,子程序退出

經過上面幾個步驟後,子程序會成為新的程序組老大,可以重新申請開啟終端,為了避免這個問題,fork孫子程序出來。

6、重定向孫子程序的標準輸入流、標準輸出流、標準錯誤流到/dev/null

因為是守護程序,本身已經脫離了終端,那麼標準輸入流、標準輸出流、標準錯誤流就沒有什麼意義了。所以都轉向到/dev/null,就是都丟棄的意思。