歡迎您光臨本站 註冊首頁

Linux下調用fork或system啟動子進程的信號和資源釋放

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  

最近一段時間,公司的網管系統二期優化需要新增功能,實現對網管客戶端程序進行保護的監控腳本的自動更新及保護進程的監控告警。網管客戶端程序分為兩部分:客戶端GatherClient及保護進程gatherclient_daemon.py,其中保護腳本由Python編寫,主要功能是實現客戶端進程崩潰或意外被殺死後的自動恢復。目前網管系統支持Windows和Linux平台。下面主要講述在Linux下實現遇到的問題。

為了實現上述的兩功能,需要解決以下兩方面問題:

1、獲取保護腳本進程是否存在。

對Python腳本保護進程的實時監控 ,需要獲取當前系統的進程信息,以判定該進程是否存活。由於py腳本均是由Python二進位模塊以參數的形式載入併產生進程 ,而在系統中的進程名均為python,這樣若系統存在其他py腳本在運行,則無法區分,不過這個問題可能通過獲取進程的參數信息來區分所對應的py腳本(Windows和Linux均可獲取進程參數信息),此處不細述。

2、py腳本的自動更新及載入。

當網管系統服務端有腳本gatherclient_daemon.py的更新時,需要客戶端程序能夠自動下載腳本,並完成對更新后的py腳本的載入啟動。而我們的客戶端由於公司各遊戲業務的伺服器物理上分佈很分散,均為遠程操作,當客戶端程序有版本更新時,會自動下載新程序文件,然後客戶端自動退出,由保護進程gatherclient_daemon.py負責啟動更新后的客戶端進程,因此優先更新py文件並保證正常重新啟動對GatherClient的自動更新功能顯得尤為關鍵。

Linux下啟動一個新進程的方法有:調用fork( ),system( )以啟動子進程的方式實現,其中system( )的系統內部實現也是調用fork( )及Waitpid等。這裡就涉及到Linux下的父子進程產生及資源的繼承關係的問題。

關於fork( )產生子進程的理解,可看下面一段示例及解析:

要搞清楚fork的執行過程,就必須先講清楚操作系統中的“進程(process)”概念。一個進程,主要包含三個元素:

o. 一個可以執行的程序;

o. 和該進程相關聯的全部數據(包括變數,內存空間,緩衝區等等);

o. 程序的執行上下文(execution context)。

不妨簡單理解為,一個進程表示的,就是一個可執行程序的一次執行過程中的一個狀態。操作系統對進程的管理,典型的情況,是通過進程表完成的。進程表中的每一個表項,記錄的是當前操作系統中一個進程的情況。對於單 CPU的情況而言,每一特定時刻只有一個進程佔用 CPU,但是系統中可能同時存在多個活動的(等待執行或繼續執行的)進程。

一個稱為“程序計數器(program counter, pc)”的寄存器,指出當前佔用 CPU的進程要執行的下一條指令的位置。

當分給某個進程的 CPU時間已經用完,操作系統將該進程相關的寄存器的值,保存到該進程在進程表中對應的表項裡面;把將要接替這個進程佔用 CPU的那個進程的上下文,從進程表中讀出,並更新相應的寄存器(這個過程稱為“上下文交換(process context switch)”,實際的上下文交換需要涉及到更多的數據,那和fork無關,不再多說,主要要記住程序寄存器pc指出程序當前已經執行到哪裡,是進程上下文的重要內容,換出 CPU的進程要保存這個寄存器的值,換入CPU的進程,也要根據進程表中保存的本進程執行上下文信息,更新這個寄存器)。

好了,有這些概念打底,可以說fork了。當你的程序執行到下面的語句:

pid=fork(); 

操作系統創建一個新的進程(子進程),並且在進程表中相應為它建立一個新的表項。www.linuxidc.com 新進程和原有進程的可執行程序是同一個程序;上下文和數據,絕大部分就是原進程(父進程)的拷貝,但它們是兩個相互獨立的進程!此時程序寄存器pc,在父、子進程的上下文中都聲稱,這個進程目前執行到fork調用即將返回(此時子進程不佔有CPU,子進程的pc不是真正保存在寄存器中,而是作為進程上下文保存在進程表中的對應表項內)。問題是怎麼返回,在父子進程中就分道揚鑣。

父進程繼續執行,操作系統對fork的實現,使這個調用在父進程中返回剛剛創建的子進程的pid(一個正整數),所以下面的if語句中pid<0, pid==0的兩個分支都不會執行。所以輸出i am the parent process...

子進程在之後的某個時候得到調度,它的上下文被換入,佔據 CPU,操作系統對fork的實現,使得子進程中fork調用返回0。所以在這個進程(注意這不是父進程了哦,雖然是同一個程序,但是這是同一個程序的另外一次執行,在操作系統中這次執行是由另外一個進程表示的,從執行的角度說和父進程相互獨立)中pid=0。這個進程繼續執行的過程中,if語句中pid<0不滿足,但是pid==0是true。所以輸出i am the child process...

我想你比較困惑的就是,為什麼看上去程序中互斥的兩個分支都被執行了。在一個程序的一次執行中,這當然是不可能的;但是你看到的兩行輸出是來自兩個進程,這兩個進程來自同一個程序的兩次執行。

在以system( )啟動py腳本的實現過程中,遇到了兩個問題:

      (1)調用system啟動py腳本創建保護子進程時,Python報下列錯誤:

       [root@otctest bin]$ Traceback (most recent call last):
       File "gatherclient_daemon.py", line 295, in ?
       main(sys.argv)
      File "gatherclient_daemon.py", line 273, in main
       Run()
      File "gatherclient_daemon.py", line 243, in Run
       daemon = Daemon()
     File "gatherclient_daemon.py", line 30, in __init__
       self.system = platform.system()
     File "/usr/lib/python2.4/platform.py", line 1035, in system
       return uname()[0]
     File "/usr/lib/python2.4/platform.py", line 1007, in uname
       processor = _syscmd_uname('-p','')
     File "/usr/lib/python2.4/platform.py", line 794, in _syscmd_uname
       rc = f.close()
    IOError: [Errno 10] No child processes

       可以發現原因是找不到子進程。排查gatherclient C程序,發現原來是因為我這個python程序本身是由gatherclient C程序調用的,gatherclient及py腳本均是以守護進程的模式在後台運行,而調用它的gatherclient程序中將SIGCLD信號忽略了(這表明system( )是根據子進程退出時產生的信號來獲取返回值的),所以主進程gatherclient將不再接收子進程的返回值。為了解決這個問題且不造成子進程變成殭屍進程,修改代碼:

        signal(SIGCHLD,SIG_DFL);                //默認處理方式,是接收子進程的返回值。

       從下面的源碼分析可看出 system( )的實現:

      

int system(const char * cmdstring)

{

pid_t pid;

int status;

if(cmdstring == NULL)

{

return (1);

}

if((pid = fork())<0)

{

status = -1;

}

else if(pid == 0)

{

execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);

            _exit(127);

}

else

{

while(waitpid(pid, &status, 0) < 0)

{

if(errno != EINTR)

{

status = -1;

break;

}

}

}

return status;

}

       2.6內核下當父進程未調用wait系列函數等待子進程結束且未顯式地忽略SIGCHLD信號,則子進程將成為僵死進程如果顯示忽略,則子進程不僵死因而SIGCHLD信號被顯式忽略的情況下,2.6內核子進程將直接退出並釋放資源,等父進程調用waitpid時,發現對應的pid進程已不存在,因而返回-1錯誤,errno10No child  processes);   

       (2)py腳本及gatherclient文件更新后(gatherclient自動退出),py腳本自動拉起gatherclient程序時,父進程gatherclient擁有的資源在子進程退出前未釋放的問題。

         分析后發現是由於gatherclient中的一處文件鎖未釋放導致。gatherclient實現時,保證客戶端正常地收集系統運行狀態信息及告警,要確保在系統中只有一個客戶端進程存在,程序初始化時創建了一個排他性的進程鎖(文件鎖),而py腳本是gatherclient進程中以system調用產生子進程的方式啟動,即py子進程繼承了父進程gatherclient的所有資源,包括文件鎖,socket套接字資源等。根據上面的fork的理解:

        新進程和原有進程的可執行程序是同一個程序;上下文和數據,絕大部分就是原進程(父進程)的拷貝,但它們是兩個相互獨立的進程!

         父進程gatherclient退出時,它所持有的文件鎖和socket資源自動釋放,但問題在於父進程和子進程是兩個獨立的進程,py子進程並未退出,會一直持有先前父進程打開的文件鎖和socket資源。這樣當py腳本啟動gatherclient時,就會出現文件鎖已打開,無法啟動的問題。解決辦法是自己重寫system(),在其中調用fork( )之後產生的子進程中關閉父進程打開的文件鎖,socket套接字以釋放資源。(實際調試測試表明,子進程未退出前,先前打開的socket套接字不會關閉且有效,程序中因此出現了socket超時才被關閉的情況部分代碼如下:

            

          在Linux下進行多進程程序的開發,要十分注意父子進程的資源的繼承關係。



[火星人 ] Linux下調用fork或system啟動子進程的信號和資源釋放已經有413次圍觀

http://coctec.com/docs/program/show-post-71484.html