首頁 > 軟體

Linux守護行程解析

2020-06-16 16:55:19
首先說一下後台進程與守護行程的區別

最大的區別有以下幾點:

(a)守護行程已經完全脫離終端控制台了,而後台程式並未完全脫離終端(在終端未關閉前還是會往終端輸出結果);
(b)守護行程在關閉終端控制台時不會受影響,而後台程式會隨使用者退出而停止,需要在以nohup command & 格式執行才能避免影響;
(c)守護行程的對談組和當前目錄,檔案描述符都是獨立的。後台執行只是終端進行了一次fork,讓程式在後台執行,這些都沒改變; 
守護行程的特點

守護行程(Daemon)是在後台執行的一種特殊進程,它脫離於終端,從而這可避免進程被任何終端所產生的信號打斷,它在執行進程中的產生資訊也不在任何終端上顯示。守護行程週期性地執行某種任務或等待處理某些發生的事件,Linux的大多數伺服器就是用守護行程實現的。

守護行程程式設計要點

1.遮蔽一些有關控制終端操作的信號,是為了防止在守護行程沒有正常啟動起來前,控制終端受到干擾退出或掛起。程式碼如下:

/* 處理可能的終端信號 */
signal(SIGTTOU, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
signal(SIGHUP , SIG_IGN);

2.在後台執行。

/* 是父進程,結束父進程,子進程繼續 */
if(fork())
    exit(0);

3.脫離控制終端和行程群組:

(1)一個進程屬於一個行程群組,行程群組號(PGID)就是行程群組長的進程號(PID)

(2)同行程群組中的進程共用一個控制終端,這個控制終端預設是建立進程的終端

(3)一個進程關聯的控制終端和行程群組通常是從父進程繼承下來的,因此,這個子進程仍然受到父親進程終端的影響,因為終端產生的信號會傳送給前台行程群組的所有進程。

基於以上原因,需要讓為個子進程徹底擺脫該終端的影響,需要呼叫setsid()使子進程成為新的對談組長,程式碼如下:

setsid();

setsid()呼叫成功後,呼叫此函數的進程成為新的對談組長和新的行程群組長,並與原來的行程群組脫離關係。由於對談過程對控制終端的獨占性,進程同時與控制終端脫離。

4.禁止進程重新開啟控制終端,採用的辦法是再次建立一個子進程,並讓父親進程退出,該子進程不再是對談組長,從而達到目的。程式碼如下:

/* 結束第一子進程,第二子進程繼續 */
if(fork())
    exit(0);

5.關閉開啟的檔案描述符。因為進程從建立它的父進程那裡繼承了開啟的檔案描述符,一般情況下不再需要。如不關閉,將會浪費系統資源。程式碼如下:

#define NOFILE  256

for(i=0; i<NOFILE; i++)
    close(i);

6.改變當前工作目錄。進程活動時,其工作目錄所在的檔案系統不能解除安裝。因此需要將守護行程的工作目錄改變到合適的目錄。程式碼如下:

chdir("/tmp");

7.重設檔案建立掩碼。進程從建立它的父進程那裡繼承了檔案建立掩碼。它可能修改守護行程所建立的檔案的存取許可權。程式碼如下:

umask(0);

8.處理SIGCHLD信號(子進程退出信號)。如果不等待子進程結束,子進程將成為殭屍進程從而佔用系統核心資源。

/* 將子進程退出信號設為SIG_IGN,讓系統幫助回收進程資源 */
signal(SIGCHLD, SIG_IGN);
整體程式碼如下:
#define NOFILE      256

void DaemonMode()
{
    int num = 0;
    int fd0, fd1, fd2;

    /* 遮蔽可能的信號 */
    signal(SIGTTOU, SIG_IGN);
    signal(SIGTTIN, SIG_IGN);
    signal(SIGTSTP, SIG_IGN);
    signal(SIGHUP , SIG_IGN);

    if(fork())
        exit(0);

    setsid();

    if(fork())
        exit(0);

    chdir("/tmp/httpd");

    umask(0);

    for(; num<NOFILE; num++)
        close(num);
    
    /* 將輸入、輸出重定向。因為之前描述符都關閉了,所以新開啟值為0、1、2 */
    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);

    signal(SIGCHLD, SIG_IGN);
}
補充 setsid() 函數功能:
如果呼叫進程已經是一個行程群組的組長,則此函數返回錯誤。為了杜絕這種情況,通常先呼叫fork()建立子進程,
然後使其父進程終止,而子進程繼續,在子進程中呼叫此函數。

如果呼叫此函數的進程不是一個行程群組組長,則此函數會建立一個新對談,呼叫setsid()函數的進程成為新的對談的領頭進程,
並與其父進程的對談組和行程群組脫離。由於對談對控制終端的獨占性,進程同時與控制終端脫離。

本文永久更新連結地址http://www.linuxidc.com/Linux/2018-02/150773.htm


IT145.com E-mail:sddin#qq.com