一、簡單創建守護進程
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
(✿◠‿◠)