02.27 Linux 守護進程創建原理及簡易方法

1:什麼是Linux下的守護進程

  Linux daemon是運行於後臺常駐內存的一種特殊進程,週期性的執行或者等待trigger執行某個任務,與用戶交互斷開,獨立於控制終端。一個守護進程的父進程是init進程,它是一個孤兒進程,沒有控制終端,所以任何輸出,無論是向標準輸出設備stdout還是標準出錯設備stderr的輸出都被丟到了/dev/null中。守護進程一般用作服務器進程,如httpd,syslogd等。

2:進程,進程組,會話,控制終端之間的關係

  因為守護進程的創建需要改變這些環境參數,所以瞭解它們之間的關係很重要:

Linux 守護進程創建原理及簡易方法

  上圖就描述了它們之間的聯繫:

  2.1 進程組:它是由一個或多個進程組成,進程組號(GID)就是這些進程中的進程組長的PID。

  2.2 會話:其實叫做會話期(session),它包括了期間所有的進程組,一般一個會話期開始於用戶login,一般login的是shell終端,所以shell終端又是此次會話期的首進程,會話一般結束於logout。對於非進程組長,它可以調用setsid()創建一個新的會話。

  2.3 控制終端(tty):一般就是指shell終端,它在會話期中可有也可以沒有。

3:創建一個daemon的幾個步驟

  3.1 實例(創建一個daemon,每隔10秒向/mydaemon.log文件寫入當前時間一共三次)

<code>void mydaemon(void){        pid_t pid;    int fd, i, nfiles;    struct rlimit rl;     pid = fork();    if(pid < 0)        ERROR_EXIT("First fork failed!");     if(pid > 0)        exit(EXIT_SUCCESS);// father exit     if(setsid() == -1)        ERROR_EXIT("setsid failed!");     pid = fork();    if(pid < 0)        ERROR_EXIT("Second fork failed!");     if(pid > 0)// father exit        exit(EXIT_SUCCESS);    #ifdef RLIMIT_NOFILE    /* 關閉從父進程繼承來的文件描述符 */    if (getrlimit(RLIMIT_NOFILE, &rl) == -1)        ERROR_EXIT("getrlimit failed!");    nfiles = rl.rlim_cur = rl.rlim_max;    setrlimit(RLIMIT_NOFILE, &rl);    for(i=3; i<nfiles> 2) close(fd);    /* 改變工作目錄和文件掩碼常量 */    chdir("/");    umask(0);}/<nfiles>/<code>

  3.2 解讀創建daemon的過程

  A(7~12行):成為後臺進程

    用fork創建子進程,父進程退出,子進程成為孤兒進程被init接管,子進程變為後臺進程。

  B(14~15行):脫離父進程的控制終端,登陸會話和進程組

    調用setsid()讓子進程成為新會話的組長,脫離父進程的會話期。setsid()在調用者是某進程組組長時會失敗,但是A已經保證了子進程不會是組長,B之後子進程變成了新會話組的組長。

  C(17~22行):禁止進程重新開啟控制終端

    因為會話組的組長有權限重新打開控制終端,所以這裡第二次fork將子進程結束,留著孫進程,孫進程不是會話組的組長所以沒有權利再打開控制終端,這樣整個程序就與控制終端隔離了。

  D(23~31行):關閉文件描述符

    進程從創建它的父進程那裡繼承了打開的文件描述符。如不關閉,將會浪費系統資源,造成進程所在的文件系統無法卸下以及引起無法預料的錯誤。

  E(32~36行):重定向0,1,2標準文件描述符

    將三個標準文件描述符定向到/dev/null中

  F(38~40行):改變工作目錄和文件掩碼

    進程活動時,其工作目錄所在的文件系統不能卸下(比如工作目錄在一個NFS中,運行一個daemon會導致umount無法成功)。一般需要將工作目錄改變到根目錄。對於需要轉儲核心,寫運行日誌的進程將工作目錄改變到特定目錄如chdir("/tmp"),進程從創建它的父進程那裡繼承了文件創建掩模。它可能修改守護進程所創建的文件的存取位。為防止這一點,將文件創建掩模清除:umask(0);

  注:D,E,F三步是對當前工作環境的修改,可以先做,因為這些修改都會被子進程繼承下來

4:實例運行

<code>#define ERROR_EXIT(m)\\do\\{\\    perror(m);\\    exit(EXIT_FAILURE);\\}\\while(0) int main(int argc, char **argv){    time_t t;    int fd, i;    mydaemon();    fd = open("./mydaemon.log", O_RDWR|O_CREAT, 0644);    if(fd < 0)        ERROR_EXIT("open /mydaemon.log failed!");    for(i=0; i<3; i++)    {        t = time(0);        char *buf = asctime(localtime(&t));        write(fd, buf, strlen(buf));        sleep(10);    }    close(fd);    return 0;}/<code>

  上圖是main函數,運行結果如下圖:

Linux 守護進程創建原理及簡易方法

  有圖可知,在open /mydaemon.log文件沒有權限,而切換到root權限後執行成功,文件的內容也是每10秒間隔寫入一次時間。因為我創建的mydaemon程序的工作目錄已經切換到了根目錄,所以普通用戶沒有在根目錄下創建文件的權限。如果這裡將文件創建在當前目錄的話就不用切換到root權限。

5:函數daemon()

  其實在linux下已經有函數daemon函數用於創建一個後臺程序了,所以上面的工作已經被加入了函數庫,直接使用輪子。

  5.1 daemon函數原型及描述

<code>#include <unistd.h>int daemon(int nochdir, int noclose); DESCRIPTION  The daemon() function is for programs wishing to detach themselves from the controlling terminal and run in the background as system daemons.  If nochdir is zero, daemon() changes the process's current working directory to the root directory ("/"); otherwise,the current working directory is left unchanged.  If noclose is zero, daemon() redirects standard input, standard output and standard error to /dev/null; otherwise, no changes are mad to these file descriptors./<unistd.h>/<code>

  通過man手冊的描述可知,函數daemon接收兩個參數:

  nochdir:如果是0,將當前工作目錄切換到根目錄"/",否則工作目錄不改變。

  noclose:如果是0,將0,1,2重定向到/dev/null,否則不變。

  5.2 mydaemon和daemon

  其實可以將mydeamon函數稍加修改符合daemon函數

<code>void mydaemon(int nochdir, int noclose){        pid_t pid;    int fd, i, nfiles;    struct rlimit rl;     pid = fork();    if(pid < 0)        ERROR_EXIT("First fork failed!");     if(pid > 0)        exit(EXIT_SUCCESS);// father exit     if(setsid() == -1)        ERROR_EXIT("setsid failed!");     pid = fork();    if(pid < 0)        ERROR_EXIT("Second fork failed!");     if(pid > 0)// father exit        exit(EXIT_SUCCESS);   #ifdef RLIMIT_NOFILE    /* 關閉從父進程繼承來的文件描述符 */    if (getrlimit(RLIMIT_NOFILE, &rl) == -1)        ERROR_EXIT("getrlimit failed!");    nfiles = rl.rlim_cur = rl.rlim_max;    setrlimit(RLIMIT_NOFILE, &rl);    for(i=3; i<nfiles> 2)            close(fd);    }     /* 改變工作目錄和文件掩碼常量 */    if(!nochdir)        chdir("/");    umask(0);}/<nfiles>/<code>

  問題:這樣普通用戶調用mydaemon(0,0)函數時還是會在console提示open mydaemon.log failed!: Permission denied,但是在37~39行已經將它們重定向到了/dev/null了,很疑惑??當你close(0,1,2)之後普通用戶才不會提示錯誤信息。

感謝你耐心的看完了這篇文章,希望能夠幫助到你需要C/C++ Linux服務器架構師學習資料私信“資料”(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享


分享到:


相關文章: