如何編寫一個守護進程daemon

來源:互聯網
上載者:User

標籤:linux核心

大體步驟如下

1.   調用fork()函數建立子進程後,讓父進程立即exit(),這樣產生的子進程變成孤兒進程,由init進程接管。

2.   調用setsid()函數,使得新建立的進程脫離控制終端,同時建立新的進程組,並成為該進程組的首進程。在linux系統中,所有的進程都屬於各自的進程組,進程組是一個或多個進程的集合,一個進程組中至少有一個進程成員,否則就消亡了。每個進程組都有一個進程組ID,是由領頭進程的進程號決定的,會話則是一個或多個進程組的集合,每個會話都有一個領頭進程,會話和進程組是linux核心用於管理多使用者情況下使用者進程的方法,每個進程都屬於一個進程組,而進程組又屬於某個會話,當使用者從終端登入系統時,系統會建立一個新的會話,在該終端上啟動的進程都會被系統劃歸到會話的進程組中。會話中的進程通過該會話中的領頭進程與一個終端相連,該終端是會話的控制終端,一個會話只能有一個控制終端,如果會話存在一個控制終端時,則它必然擁有一個前台進程組,屬於該組的進程可以從從控制終端獲得輸入。由於守護進程沒有控制終端,而使用fork()函數建立的子進程會繼承父進程的控制終端,會話和進程組,因此,必須用setsid()建立新的會話,以脫離父進程的影響。Setsid函數將建立新的會話,並使得調用setsid函數的進程成為新會話的領頭進程。調用setsid函數的進程是新建立會話中的唯一的進程組,進程組ID為調用進程的進程號。Setsid函數產生這一結果還有個條件,即調用進程不為一個進程的領頭進程。由於在第一步調用fork的父進程退出,使得子進程不可能是進程組的領頭進程。該會話的領頭進程沒有控制終端與其相連,至此,滿足了守護進程沒有控制終端的要求

3.   更改當前工作目錄

使用fork()函數建立的子進程會繼承父進程的當前工作目錄,當進程工作沒有結束時,其工作目錄是不能被卸載的.為了防止此問題,守護進程一般要chdir()了數將其工作目錄更改到別的目錄下(一般為/根目錄,因為根目錄是永遠不會被卸載的,除非關機).

4.   關閉檔案描述符,並重新導向標準輸入,輸出和錯誤

子進程會繼承父進程某些開啟了的檔案描述符,如果不使用這些檔案描述符,則需要關閉它們.守護進程是運行在系統背景,不應該在終端有任何的輸出資訊,可以使用dup()函數將其重新導向到/dev/null空裝置上.

5.   設定守護進程的檔案許可權建立掩碼

守護進程會建立一些臨時檔案,出於性的考慮,往往不希望這些檔案被別的使用者查看,這時可以用umask()函數修改檔案許可權,建立掩碼的取值.

上面說的是建立守護進程的大體步驟,解釋其中的兩點:

1、setsid的作用

一、讓進程擺脫原會話的控制

二. 讓進程擺脫原進程組的控制三. 讓進程擺脫原控制終端的控制
setsid()就是將進程和它當前的對話過程和進程組分離開,並且把它設定成一個新的對話過程的領頭進程。也就是說讓調用setsid()函數的這個進程脫離原有的進程組,自立門戶。同時需要注意一點就是,調用這個函數的進程不能是一個進程組的進程組長,不然進程組長自立門戶一戶,這個進程組的其他進程也就會消亡(這個和平時自己建立父子進程,然後父子進程各自做各自的事情還不太一樣)。這也就解釋了上面紅色字型的那句話。在子進程中調用setsid,不管這個時候父進程退出與否,子進程都不會是它當前所在進程組的進程組長。所以調用這個函數的是子進程,下面這個例子就是在父進程中調用setsid的副作用,會使父進程所在的進程組的其他子進程銷毀
pid_t pid = fork();
if (pid == 0) {
        ...
        int result = execl(path, "adb", "fork-server", "server", NULL);
} else {
        // run a program in a new session
        setsid();//之前parent和child運行在同一個session裡,而且parent是session頭,
        //所以作為session頭的parent如果exit結束執行的話,那麼會話session組中的所有進程將都被殺死;
        //所以執行setsid()之後,parent將重新獲得一個新的會話session組id,child將仍持有原有的會話session組,
        //這時parent退出之後,將不會影響到child了。
}
      會話session是一個或多個進程組的集合。進程調用setsid函數建立一個新會話。如果調用此函數的進程不是一個進程組的組長,則此函數就會建立一個新會話,該進程變成會話的首進程,然後該進程成為一個新進程組的組長進程,該進程沒有控制終端。因為會話首進程是具有唯一進程ID的單個進程,所以可以將會話首進程的進程ID視為會話Id。
2、其實建立daemon進程是調用fork()兩次的。
一、如果開啟daemon進程的父進程本身可能不單單是為了建立daemon,假如父進程在某個地方一直阻塞,這時守護進程一直在運行,父進程卻沒有正常退出。如果守護進程因為正常或非正常原因退出了,就會變成ZOMBIE進程。

如果fork兩次呢?父進程先fork出一個兒子進程,兒子進程再fork出孫子進程做為守護進程,然後兒子進程立刻退出,守護進程被init進程接管,這樣無論父進程做什麼事,無論怎麼被阻塞,都與守護進程無關了。所以,fork兩次的守護進程很安全,避免了殭屍進程出現的可能性。

二、第一個子進程調用setsid()函數,調用以後第一個子進程才成為了新的進程組的進程組長(注意,調用setsid()函數的第一個子進程在調用這個函數之前不是進程組的進程組長)第一子進程成為新的交談群組長和進程組長進程組長有權利申請開啟一個控制終端,第二次fork的意義就在此,關閉掉第一子進程,使第二子進程成為守護進程,並且因為沒有進程組長,  所以守護進程不會被關閉。
下面是個程式碼範例:
    #include <stdio.h>    #include <stdlib.h>    #include <sys/types.h>    #include <sys/stat.h>    #include <time.h>    #include <syslog.h>    #include <signal.h>    #include <sys/param.h>    #include <fcntl.h>    int init_daemon(void)    {        int pid;        int i;        int fd;        /*忽略終端I/O訊號,STOP訊號*/        signal (SIGTTOU, SIG_IGN);        signal (SIGTTIN, SIG_IGN);        signal (SIGTSTP, SIG_IGN);        signal (SIGHUP, SIG_IGN);        printf(“ppid = %d\n”,getppid());        pid = fork();        fd = open(“/dev/tty”,O_RDONLY);        printf(“!fd = %d\n”,fd);        close(fd);        if (pid > 0)        {            printf(“Parent process pid = %d\n”,getpid());            exit(0);  //結束父進程,使得子進程成為後台進程        }        else if (pid < 0)            return -1;        printf(“First Child process pid = %d\n”,getpid());        //當前進程為第一子進程        //建立一個新的進程組,在這個新的進程組中,子進程成為這個進程組的首進程,以使該進程脫離所有終端        printf(“pgid = %d\n”,getpgid(getpid()));        //printf(“pid = %d\n”,getpid());        setsid();        printf(“pgid = %d\n”,getpgid(getpid()));        //printf(“pid = %d\n”,getpid());        fd = open(“/dev/tty”,O_RDONLY);        printf(“fd = %d\n”,fd);        close(fd);        //再次建立一個子進程,退出父進程(第一子進程),保證該進程不是進程組長,同時讓該進程無法再開啟一個新的終端        pid = fork();        fd = open(“/dev/tty”,O_RDONLY);        printf(“#fd = %d\n”,fd);        close(fd);        if (pid > 0)        {            printf(“First Child process pid = %d\n”,getpid());            exit(0);        }        else if (pid < 0)          return -1;        //關閉所有從父進程繼承的不再需要的檔案描述符        for (i=0; i < NOFILE; close(i++));        //改變工作目錄,使得進程不與任何檔案系統聯絡        chdir(“/”);        //將檔案檢測字設定為0        umask(0);        //忽略SIGCHLD訊號        signal(SIGCHLD,SIG_IGN);        return 0;    }    int main(int argc, char *argv[])    {        time_t now;        init_daemon();        syslog(LOG_USER|LOG_INFO,”測試守護進程!\n”);        while(1)        {            sleep(8);            time(&now);            syslog(LOG_USER|LOG_INFO,”系統時間:\t%s\t\t\n”,ctime(&now));        }    }



如何編寫一個守護進程daemon

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.