linux之Deamon進程建立及其進程exit,_exit,return之間的區別

Dameon進程又被稱作守護進程,通常來講他有如下2個特色:
1.生命週期很是長,一旦啓動,通常不會終止,直到系統推出,不過dameon進程能夠經過stop或者發送信號將其殺死
2.在後臺執行,不跟任何控制終端關聯,終端信號好比:SIGINT,SIGQUIT,SIGTSTP,以及關閉終端都不會影響deamon
如何編寫Daemon進程,須要遵循如下規則:
(1) 執行fork()函數,父進程退出,子進程繼續
執行這一步,緣由有二:
·父進程有多是進程組的組長(在命令行啓動的狀況下),從而不可以執行後面要執行的setsid函數,子進程繼承了父進程的進程組ID,而且擁有本身的進程ID,必定不會是進程組的組長,因此子進程必定能夠執行後面要執行的setsid函數。
·若是daemon是從終端命令行啓動的,那麼父進程退出會被shell檢測到,shell會顯示shell提示符,讓子進程在後臺執行。
(2) 子進程執行以下三個步驟,以擺脫與環境的關係
1) 修改進程的當前目錄爲根目錄(/)。
這樣作是有緣由的,由於daemon一直在運行,若是當前工做路徑上包含有根文件系統之外的其餘文件系統,那麼這些文件系統將沒法卸載。所以,常規是將當前工做目錄切換成根目錄,固然也能夠是其餘目錄,只要確保該目錄所在的文件系統不會被卸載便可。
chdir("/"
2) 調用setsid函數。這個函數的目的是切斷與控制終端的全部關係,而且建立一個新的會話。
這一步比較關鍵,由於這一步確保了子進程再也不歸屬於控制終端所關聯的會話。所以不管終端是否發送SIGINT、SIGQUIT或SIGTSTP信號,也不管終端是否斷開,都與要建立的daemon進程無關,不會影響到daemon進程的繼續執行。
3) 設置文件模式建立掩碼爲0。
     
     
     
     
umask(0)
這是爲了讓daemon進程建立的文件權限屬性跟shell脫離關係,由於默認狀況下,進程的umask來源於父進程shell的umask.若是不執行umask(0),那麼父進程shell的umask就會影響daemon進程的umask.若是用戶改變了shell的umask,那麼也就改變了dameon的umask,就會使得daemon進程每次執行的umask信息可能不一致
(3) 再次執行fork,父進程退出,子進程繼續
執行完前面兩步以後,能夠說已經比較圓滿了:新建會話,進程是會話的首進程,也是進程組的首進程。進程ID、進程組ID和會話ID,三者的值相同,進程和終端無關聯。那麼這裏爲什麼還要再執行一次fork函數呢?
緣由是,daemon進程有可能會打開一個終端設備,即daemon進程可能會根據須要,執行相似以下的代碼:
     
     
     
     
int fd = open("/dev/console", O_RDWR);
這個打開的終端設備是否會成爲daemon進程的控制終端,取決於兩點:
·daemon進程是否是會話的首進程。
·系統實現。(BSD風格的實現不會成爲daemon進程的控制終端,可是POSIX標準說這由具體實現來決定)。
既然如此,爲了確保萬無一失,只有確保daemon進程不是會話的首進程,才能保證打開的終端設備不會自動成爲控制終端。所以,不得不執行第二次fork,fork以後,父進程退出,子進程繼續。這時,子進程再也不是會話的首進程,也不是進程組的首進程了。
(4) 關閉標準輸入(stdin)、標準輸出(stdout)和標準錯誤(stderr)
由於文件描述符0、1和2指向的就是控制終端。daemon進程已經再也不與任意控制終端相關聯,所以這三者都沒有意義。通常來說,關閉了以後,會打開/dev/null,並執行dup2函數,將0、1和2重定向到/dev/null。這個重定向是有意義的,防止了後面的程序在文件描述符0、1和2上執行I/O庫函數而致使報錯。
至此,即完成了daemon進程的建立,進程能夠開始本身真正的工做了。
上述步驟比較繁瑣,對於C語言而言,glibc提供了daemon函數,從而幫咱們將程序轉化成daemon進程。
     
     
     
     
#include <unistd.h>int daemon(int nochdir, int noclose);
該函數有兩個入參,分別控制一種行爲,具體以下。
其中的 nochdir,用來控制是否將當前工做目錄切換到根目錄。
·0:將當前工做目錄切換到/。
·1:保持當前工做目錄不變。
noclose,用來控制是否將標準輸入、標準輸出和標準錯誤重定向到/dev/null。
·0:將標準輸入、標準輸出和標準錯誤重定向到/dev/null。
·1:保持標準輸入、標準輸出和標準錯誤不變。
通常狀況下,這兩個入參都要爲0。
     
     
     
     
ret = daemon(0,0)
成功時,daemon函數返回0;失敗時,返回-1,並置errno。由於daemon函數內部會調用fork函數和setsid函數,因此出錯時errno能夠查看fork函數和setsid函數的出錯情形。
glibc的daemon函數作的事情,和前面討論的大致一致,可是作得並不完全,沒有執行第二次的fork。


進程的終止
在不考慮線程的狀況下,進程的退出有如下5種方式。
正常退出有3種:
·從main函數return返回
·調用exit
·調用_exit
異常退出有兩種:
· 調用abort
·接收到信號,由信號終止


_exit函數的接口定義以下:
     
     
     
     
#include <unistd.h>void _exit(int status);
用戶調用_exit函數,本質上是調用exit_group系統調用。這點在前面已經詳細介紹過,在此就再也不贅述了。
exit函數
exit函數更常見一些,其接口定義以下:
     
     
     
     
#include <stdlib.h>void exit(int status);
exit()函數的最後也會調用_exit()函數,可是exit在調用_exit以前,還作了其餘工做:
1)執行用戶經過調用atexit函數或on_exit定義的清理函數。
2)關閉全部打開的流(stream),全部緩衝的數據均被寫入(flush),經過tmpfile建立的臨時文件都會被刪除。
3)調用_exit。
圖4-11給出了exit函數和_exit函數的差別。
 

下面介紹exit函數和_exit函數的不一樣之處。
首先是exit函數會執行用戶註冊的清理函數。用戶能夠經過調用atexit()函數或on_exit()函數來定義清理函數。這些清理函數在調用return或調用exit時會被執行。執行順序與函數註冊的順序相反。當進程收到致命信號而退出時,註冊的清理函數不會被執行;當進程調用_exit退出時,註冊的清理函數不會被執行;當執行到某個清理函數時,若收到致命信號或清理函數調用了_exit()函數,那麼該清理函數不會返回,從而致使排在後面的須要執行的清理函數都會被丟棄。
其次是exit函數會沖刷(flush)標準I/O庫的緩衝並關閉流。glibc提供的不少與I/O相關的函數都提供了緩衝區,用於緩存大塊數據。
緩衝有三種方式:無緩衝(_IONBF)、行緩衝(_IOLBF)和全緩衝(_IOFBF)。
·無緩衝:就是沒有緩衝區,每次調用stdio庫函數都會馬上調用read/write系統調用。
·行緩衝:對於輸出流,收到換行符以前,一概緩衝數據,除非緩衝區滿了。對於輸入流,每次讀取一行數據。
·全緩衝:就是緩衝區滿以前,不會調用read/write系統調用來進行讀寫操做。
對於後兩種緩衝,可能會出現這種狀況:進程退出時,緩衝區裏面可能還有未沖刷的數據。若是不沖刷緩衝區,緩衝區的數據就會丟失。好比行緩衝遲遲沒有等到換行符,又或者全緩衝沒有等到緩衝區滿。尤爲是後者,很容易出現,由於glibc的緩衝區默認是8192字節。exit函數在關閉流以前,會沖刷緩衝區的數據,確保緩衝區裏的數據不會丟失。
     
     
     
     
  1. }
#include <stdio.h>#include <stdlib.h>#include <unistd.h>void foo(){ fprintf(stderr,"foo says bye.\n");}void bar(){ fprintf(stderr,"bar says bye.\n");}int main(int argc, char **argv){ atexit(foo); atexit(bar); fprintf(stdout,"Oops ... forgot a newline!"); sleep(2); if (argc > 1 && strcmp(argv[1],"exit") == 0) exit(0); if (argc > 1 && strcmp(argv[1],"_exit") == 0) _exit(0);
    return 0;
注意上面的示例代碼,fprintf打印的字符串是沒有換行符的,對於標準輸出流stdout,採用的是行緩衝,收到換行符以前是不會有輸出的。輸出狀況以下:
     
     
     
     
manu@manu-hacks:exit$ ./test exit //調用exit結束,輸出了緩衝區的字符bar says bye.foo says bye.Oops ... forgot a newline!manu@manu-hacks:exit$ //調用return 輸出了緩衝區字符manu@manu-hacks:exit$manu@manu-hacks:exit$ ./testbar says bye.foo says bye.Oops ... forgot a newline!manu@manu-hacks:exit$ //直接調用_exit沒有輸出緩衝區的字符manu@manu-hacks:exit$manu@manu-hacks:exit$ ./test _exitmanu@manu-hacks:~/code/self/c/exit$
儘管緩衝區裏的數據沒有等到換行符,可是不管是調用return返回仍是調用exit返回,緩衝區裏的數據都會被沖刷,「Oops...forgot a newline!」都會被輸出。由於exit()函數會負責此事。從測試代碼的輸出也能夠看出,exit()函數首先執行的是用戶註冊的清理函數,而後才執行了緩衝區的沖刷。
第三,存在臨時文件,exit函數會負責將臨時文件刪除.
exit函數的最後調用了_exit()函數,最終異曲同工,走向內核清理。

return退出
return是一種更常見的終止進程的方法。執行return(n)等同於執行exit(n),由於調用main()的運行時函數會將main的返回值看成exit的參數。




 





相關文章
相關標籤/搜索