最近在學習linux的一些開源代碼,nginx是我想要學習的第一個目標,因爲它十分小巧相對於它的強大功能來說.
我使用過http服務端,文件目錄瀏覽.除此之外我知道的還有反向代理,負載均衡,等功能.最主要的還是他代碼量比較少,和我以前使用的項目結構相似有着一定的親切感.
我主要想了解的幾個主要功能模塊包括:內存池,守護進程變化,以及http業務解析等,今天先看到了守護進程的代碼比較少,先來了解這塊代碼.
二話不說上代碼
ngx_int_t ngx_daemon(ngx_log_t *log)
{
int fd;
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();
if (setsid() == -1) { //setsid創建一個新會話
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
return NGX_ERROR;
}
umask(0);
//打開文件/dev/null,使得其擁有守護進程的0,1,2。這樣防止守護進程在終端設備上顯示輸出
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;
}
}
return NGX_OK;
}
在這段代碼裏面一開始的frok函數很好理解,創建子進程將會作爲守護進程使用,接下里是使用一個switch進行父進程退出.使子進程成爲孤兒進程,接着子進程調用setsid創建新的會話,保證在登錄會話斷掉之後,進程能夠一直存在.
在這塊有個細節,就是如果是以庫的形式提供這個函數的話,爲了保險起見一般來說會去frok兩次.原因是因爲作爲庫函數的代碼不知道接下來的代碼會不會有ioctl(TIOCSCTTY)這種調用,如果有的話那麼子進程就有當前的session,這時候調用setsid()是不會成功的,所以說這時候就算創建守護進程失敗了,但是由於是編寫的一整個程序,所以不會有上面之類的調用.
接下來脫離原進程文件權限使用umask,進程從創建它的父進程那裏繼承了文件創建掩模。它可能修改守護進程所創建的文件的存取位。爲防止這一點,將文件創建掩模清除.
然後將三個默認fd重定向到/dev/null上,分別爲標準輸入,標準輸出,錯誤輸出.
這樣nginx的守護進程就寫完了.但是這個不完全是一個完整的daemon的寫法,一個完整的daemon的寫法裏面還會包括chdir('/'),切換工作目錄,防止卸載.