轉自:http://www.CUOXin.com/xuxm2007/archive/2011/07/29/2121280.html
#include <unistd.h> int daemon(int nochdir,int noclose) 在創建精靈進程的時候,往往需要將精靈進程的工作目錄修改為"/"根目錄 并且將標準輸入,輸出和錯誤輸出重定向到/dev/null daemon的作用就是當參數nochdir為0時,將根目錄修改為工作目錄 noclose為0時,做輸入,輸出以及錯誤輸出重定向到/dev/null 執行成功返回0 錯誤返回-1
<----------------------------------------->
淺析daemon精靈守護進程創建背后的故事 #include <unistd.h> int main(int argc, char *argv[]) { ... if (daemon(0, 0)) {//調用glibc庫函數daemon,創建daemon守護進程 perror("daemon"); return -1; } 好了執行到這里的就是daemon的子進程了[luther.gliethttp]. ... } ========================================================= int daemon( int nochdir, int noclose ) { pid_t pid; if ( !nochdir && chdir("/") != 0 )//如果nochdir=0,那么改變到"/"根目錄 return -1; if ( !noclose )//如果沒有close { int fd = open("/dev/null", O_RDWR);//打開空洞文件. if ( fd < 0 ) return -1; //對于每個進程,它的fds文件描述符表中:0,1和2文件句柄位置對應的fops文件操作函數集, //fdt->fd[0],fdt->fd[1],fdt->fd[2], //規定將分別與標準輸入,標準輸出,標準錯誤輸出相關聯[luther.gliethttp]. //所以用戶應用程序調用open函數打開文件時,默認都是以3索引為開始句柄,故當前open返回的文件句柄最小值為3[luther.gliethttp]. //dup2(unsigned int oldfd, unsigned int newfd)系統調用就是用oldfd的fops操作文件集file,復制到newfd所在處 //即:fdt->fd[newfd] = fdt->fd[oldfd]; if ( dup2( fd, 0 ) < 0 || //使用字符設備/dev/null的fops函數操作集,替換0句柄對應的文件操作集. dup2( fd, 1 ) < 0 || //使用字符設備/dev/null的fops函數操作集,替換1句柄對應的文件操作集. dup2( fd, 2 ) < 0 ) //使用字符設備/dev/null的fops函數操作集,替換2句柄對應的文件操作集. { close(fd); return -1; } //如果上面替換成功,那么鍵盤的任何操作將不會對該進程產生任何影響,因為0,1,2句柄所在處的fops文件操作集已經都變成了,
//被重定向為"/dev/null"空洞設備的fops.所以對0,1,2句柄的讀寫操作,也就是在對/dev/null設備作讀寫操作. close(fd);//關閉打開的/dev/null } pid = fork();//創建子進程. if (pid < 0) return -1; if (pid > 0) _exit(0);//返回執行的是父進程,那么父進程退出,讓子進程變成真正的孤兒進程. //ok,我們期望的daemon子進程執行到這里了. if ( setsid() < 0 )//設置session id. return -1; return 0;//成功創建daemon子進程[luther.gliethttp]. }
http://docs.linuxtone.org/ebooks/C&CPP/c/ch34s03.html
http://blog.csdn.net/yyyzlf/article/details/5267954
由于守護進程的特點,編寫守護進程程序必須遵守一定的規則。本節將闡述這些規則的要點,并給出相關代碼。
8.2.1 實現守護進程的步驟在Linux系統中,要編程實現一個守護進程必須遵守如下的步驟。
1.讓init進程成為新產生進程的父進程。調用fork函數創建子進程后,使父進程立即退出。這樣,產生的子進程將變成孤兒進程,并被init進程接管,同時,所產生的新進程將變為在后臺運行。
2.調用setsid函數通過調用setsid函數,使得新創建的進程脫離控制終端,同時創建新的進程組,并成為該進程組的首進程。為了使讀者更好地理解這一步驟,下面介紹進程組、會話(session)的基本概念。
在Linux系統中,所有的進程都屬于各自的進程組。進程組是一個或多個進程的集合。打個比方,可以認為某個班級是一個進程組,而其中成員就是進程。一個班級至少有一個成員。當一個班級的最后一個成員不存在的時候,這個班級也就不存在了,也就是進程組消亡了。
每個進程組都有類似于進程號的標識,稱為進程組ID。進程組ID是由領頭進程的進程號決定的,每個進程組都存在一個領頭進程。進程組的存在與否與領頭進程是否存在沒有關系。
會話是一個或多個進程組的集合。與進程組類似,每個會話都存在一個領頭進程。Linux是一個多用戶的操作系統,在同一時刻系統中會存在屬于不同用戶的多個進程。如果用戶在某個終端上發送了某個信號,例如,按下“Ctrl+C”發送SIGINT信號,如何確保信號被正確地發送到對應的進程,同時不會影響使用其他終端的用戶的進程?
會話和進程組是Linux內核用于管理多用戶情況下用戶進程的方法。每個進程都屬于一個進程組,而進程組又屬于某個會話。當用戶從終端登錄系統(不管是終端還是偽終端),系統會創建一個新的會話。在該終端上啟動的進程都會被系統劃歸到會話的進程組中。
會話中的進程通過該會話中的領頭進程(常稱其為控制進程)與一個終端相連。該終端是會話的控制終端。一個會話只能有一個控制終端,反之一樣。如果會話存在一個控制終端,則它必然擁有一個前臺進程組。屬于該組的進程可以從控制終端獲得輸入。這時,其他的進程組都為后臺進程組。圖8.3所示為會話、進程組、進程與控制終端之間的關系。

圖8.3 會話、進程組、進程與控制終端的關系
由于守護進程沒有控制終端,而使用fork函數創建的子進程繼承了父進程的控制終端、會話和進程組,因此,必須創建新的會話,以脫離父進程的影響。Linux系統提供了setsid函數用于創建新的會話。setsid函數的信息如表8.1所示。
表8.1 setsid函數
頭文件
<unistd.h>
函數形式
pid_t setsid(void);
返回值
成功
失敗
是否設置errno
調用進程的會話ID
?1
是
setsid函數將創建新的會話,并使得調用setsid函數的進程成為新會話的領頭進程。調用setsid函數的進程是新創建會話中的惟一的進程組,進程組ID為調用進程的進程號。setsid函數產生這一結果還有個條件,即調用進程不為一個進程的領頭進程。由于在第一步中調用fork的父進程退出,使得子進程不可能是進程組的領頭進程。該會話的領頭進程沒有控制終端與其相連。至此,滿足了守護進程沒有控制終端的要求。
3.更改當前工作目錄使用fork函數產生的子進程將繼承父進程的當前工作目錄。當進程沒有結束時,其工作目錄是不能被卸載的。為了防止這種問題發生,守護進程一般會將其工作目錄更改到根目錄下(/目錄)。更改工作目錄使用的函數是chdir。
4.關閉文件描述符,并重定向標準輸入、輸出和錯誤輸出新產生的進程從父進程繼承了某些打開的文件描述符,如果不使用這些文件描述符,則需要關閉它們。守護進程是運行在系統后臺的,不應該在終端有任何的輸出信息。可以使用dup函數將標準輸入、輸出和錯誤輸出重定向到/dev/null設備上(/dev/null是一個空設備,向其寫入數據不會有任何輸出)。下面給出具體的代碼:
…
int fd;
//將標準輸入輸出重定向到空設備
fd = open ("/dev/null", O_RDWR, 0);
if (fd != -1)
{
dup2 (fd, STDIN_FILENO);
dup2 (fd, STDOUT_FILENO);
dup2 (fd, STDERR_FILENO);
if (fd > 2)
close (fd);
}
…
5.設置守護進程的文件權限創建掩碼很多情況下,守護進程會創建一些臨時文件。出于安全性的考慮,往往不希望這些文件被別的用戶查看。這時,可以使用umask函數修改文件權限,創建掩碼的取值,以滿足守護進程的要求。
8.2.2 守護進程具體實現本節給出一個守護進程創建的實例。程序p8.1.c中定義了daemon函數,用于實現對守護進程的創建。其創建思想在8.2.1中有詳細的介紹,程序的具體代碼如下:
//p8.1.c 守護進程的實現
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/* daemon函數用于將調用函數的進程轉化為守護進程 */
int
daemon (int nochdir, int noclose)
{
pid_t pid;
pid = fork ();
/* 如果創建進程失敗 */
if (pid < 0)
{
perror ("fork");
return -1;
}
/* 父進程退出運行 */
if (pid != 0)
exit (0);
/* 成為會話領頭進程 */
pid = setsid();
if (pid < -1)
{
perror ("setsid");
return -1;
}
/* 將工作目錄修改成根目錄 */
if (! nochdir)
chdir ("/");
/* 將標準輸入輸出重定向到空設備 */
if (! noclose)
{
int fd;
fd = open ("/dev/null", O_RDWR, 0);
if (fd != -1)
{
dup2 (fd, STDIN_FILENO);
dup2 (fd, STDOUT_FILENO);
dup2 (fd, STDERR_FILENO);
if (fd > 2)
close (fd);
}
}
umask (0027);
return 0;
}
int main(void)
{
daemon(0,0);
sleep(1000);
return 0;
}
使用gcc編譯p8.1.c,得到名為p8.1的可執行文件。執行該程序,程序將以守護進程的狀態運行,如圖8.4所示。
新聞熱點
疑難解答