[Linux系統] 編寫一個守護進程


一、簡單創建守護進程

daemon.c文件:

// daemon.c
#include<stdio.h>
#include<signal.h>
#include<sys/param.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<stdlib.h>

int init_daemon(void)
{
    pid_t pid;
    int i;

    pid = fork();
    if(pid > 0){
        //第一步,結束父進程,使得子進程成為后台
        exit(0);
    }else if(pid < 0){
        return -1;
    }
    /*第二步建立一個新的進程組,在這個新的進程組中,子進程成為這個進程組的首進程,以使該進程脫離所用終端*/
    setsid();
    /*再次新建一個子進程,退出父進程,保證該進程不是進程組長,同時讓該進程無法再打開一個新的終端*/
    pid = fork();
    if(pid > 0){
        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;
}

 

test.c文件:

 

// test.c
#include<stdio.h>
#include<time.h>
#include<syslog.h>
extern int init_daemon(void);

int main()
{
    time_t now;
    init_daemon(); //初始化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    ));
    }
}

二、nginx代碼例子

參考博客:https://www.cnblogs.com/wuchanming/p/4037530.html

ngx_int_t
ngx_daemon(ngx_log_t *log)
{
    int  fd;
 
    // 讓init進程成為新產生進程的父進程:
    // 調用fork函數創建子進程后,使父進程立即退出。這樣,產生的子進程將變成孤兒進程,並被init進程接管,
    // 同時,所產生的新進程將變為在后台運行。
    switch (fork()) {
    case -1:
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
        return NGX_ERROR;
 
    case 0:
        break;
 
    default:
        exit(0);
    }
 
    ngx_pid = ngx_getpid();
 
    // 調用setsid()函數脫離控制終端和進程組,使該進程成為會話組長,並與原來的登錄會話和進程組脫離。
    // 此時進程已經成為無終端的會話組長,但它可以重新申請打開一個控制終端。
    // 為了避免這種情況,可以通過使進程不再成為會話組長來禁止進程重新打開控制終端,
    // 這就需要第二次調用fork()函數(nginx沒有做這一步)。父進程(會話組長)退出,子進程繼續執行,
    // 並不再擁有打開控制終端的能力。在正在執行的進程中調用INIT_DAEMON后,進程將成為守護進程,
    // 脫離控制終端進入后台執行。
    if (setsid() == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
        return NGX_ERROR;
    }
 
    // 重設文檔創建掩模
    // 很多情況下,守護進程會創建一些臨時文件。出於安全性的考慮,往往不希望這些文件被別的用戶查看。
    // 這時,可以使用umask函數修改文件權限,創建掩碼的取值,以滿足守護進程的要求。
    umask(0);
 
    // 關閉打開的文檔描述符:
    // 新產生的進程從父進程繼承了某些打開的文件描述符,如果不使用這些文件描述符,則需要關閉它們。
    // 守護進程是運行在系統后台的,不應該在終端有任何的輸出信息。
    // 可以使用dup函數將標准輸入、輸出和錯誤輸出重定向到/dev/null設備上
    //(/dev/null是一個空設備,向其寫入數據不會有任何輸出)。
    fd = open("/dev/null", O_RDWR);
    if (fd == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "open(\"/dev/null\") failed");
        return NGX_ERROR;
    }
 
    if (dup2(fd, STDIN_FILENO) == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDIN) failed");
        return NGX_ERROR;
    }
 
    if (dup2(fd, STDOUT_FILENO) == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDOUT) failed");
        return NGX_ERROR;
    }
 
#if 0
    if (dup2(fd, STDERR_FILENO) == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDERR) failed");
        return NGX_ERROR;
    }
#endif
 
    if (fd > STDERR_FILENO) {
        if (close(fd) == -1) {
            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "close() failed");
            return NGX_ERROR;
        }
    }
 
    // 改變當前工作目錄(nginx沒有做)
    // 使用fork函數產生的子進程將繼承父進程的當前工作目錄。
    // 當進程沒有結束時,其工作目錄是不能被卸載的。
    // 為了防止這種問題發生,守護進程一般會將其工作目錄更改到根目錄下(/目錄)。
 
    return NGX_OK;
}

setsid相關知識:

通過調用setsid函數,使得新創建的進程脫離控制終端,同時創建新的進程組,並成為該進程組的首進程。為了使讀者更好地理解這一步驟,下面介紹進程組、會話(session)的基本概念。

在Linux系統中,所有的進程都屬於各自的進程組。進程組是一個或多個進程的集合。打個比方,可以認為某個班級是一個進程組,而其中成員就是進程。一個班級至少有一個成員。當一個班級的最后一個成員不存在的時候,這個班級也就不存在了,也就是進程組消亡了。

每個進程組都有類似於進程號的標識,稱為進程組ID。進程組ID是由領頭進程的進程號決定的,每個進程組都存在一個領頭進程。進程組的存在與否與領頭進程是否存在沒有關系。

會話是一個或多個進程組的集合。與進程組類似,每個會話都存在一個領頭進程。Linux是一個多用戶的操作系統,在同一時刻系統中會存在屬於不同用戶的多個進程。如果用戶在某個終端上發送了某個信號,例如,按下“Ctrl+C”發送SIGINT信號,如何確保信號被正確地發送到對應的進程,同時不會影響使用其他終端的用戶的進程?

會話和進程組是Linux內核用於管理多用戶情況下用戶進程的方法。每個進程都屬於一個進程組,而進程組又屬於某個會話。當用戶從終端登錄系統(不管是終端還是偽終端),系統會創建一個新的會話。在該終端上啟動的進程都會被系統划歸到會話的進程組中。

會話中的進程通過該會話中的領頭進程(常稱其為控制進程)與一個終端相連。該終端是會話的控制終端。一個會話只能有一個控制終端,反之一樣。如果會話存在一個控制終端,則它必然擁有一個前台進程組。屬於該組的進程可以從控制終端獲得輸入。這時,其他的進程組都為后台進程組。圖8.3所示為會話、進程組、進程與控制終端之間的關系。

由於守護進程沒有控制終端,而使用fork函數創建的子進程繼承了父進程的控制終端、會話和進程組,因此,必須創建新的會話,以脫離父進程的影響。Linux系統提供了setsid函數用於創建新的會話。

setsid函數將創建新的會話,並使得調用setsid函數的進程成為新會話的領頭進程。調用setsid函數的進程是新創建會話中的惟一的進程組,進程組ID為調用進程的進程號。setsid函數產生這一結果還有個條件,即調用進程不為一個進程的領頭進程。由於在第一步中調用fork的父進程退出,使得子進程不可能是進程組的領頭進程。該會話的領頭進程沒有控制終端與其相連。至此,滿足了守護進程沒有控制終端的要求。

參考:http://www.cnblogs.com/xuxm2007/archive/2011/07/29/2121280.html

 

 

(✿◠‿◠) 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM