學習nginx已經有一個多月了,覺得越來越喫力了,主要原因自己總結了一下:1平臺是基於linux的,以前幾乎沒有接觸過linux,而nginx使用了很多linux的函數;2就是進程,這個東西接觸的也很少,linux的多進程更不用說,而現在正好看到這裏,覺得異常的喫力,這不看到nginx守護進程的建立,就找資料好好學習一下,所以本文已學習fork爲主要內容。
好了,先看一下nginx的守護進程的建立,然後在學習fork。
http://blog.csdn.net/xiaoliangsky/article/details/39998373
1nginx的守護進程
直接看代碼:
ngx_int_t ngx_daemon(ngx_log_t *log)
{
int fd;
switch (fork()) {//用fork創建守護進程
case -1://fork返回-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) {//建立新的會話,然後子進程稱爲會話組長
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
return NGX_ERROR;
}
umask(0);//重設文件創建掩模
/*重定向標準輸入、輸出到/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) {//輸入重定向到fd,即從/dev/null輸入
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDIN) failed");
return NGX_ERROR;
}
if (dup2(fd, STDOUT_FILENO) == -1) {//輸出重定向到fd,即所有輸出到/dev/null
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;
}
這裏不多解釋,看了下面的內容,就知道上面的代碼很簡單。
2fork函數
由fork創建的新進程被稱爲子進程(child process)。該函數被調用一次,但返回兩次。兩次返回的區別是子進程的返回值是0,而父進程的返回值則是新進程(子進程)的進程 id。將子進程id返回給父進程的理由是:因爲一個進程的子進程可以多於一個,沒有一個函數使一個進程可以獲得其所有子進程的進程id。對子進程來說,之所以fork返回0給它,是因爲它隨時可以調用getpid()來獲取自己的pid;也可以調用getppid()來獲取父進程的id。(進程id
0總是由交換進程使用,所以一個子進程的進程id不可能爲0 )。
fork之後,操作系統會複製一個與父進程完全相同的子進程,雖說是父子關係,但是在操作系統看來,他們更像兄弟關係,這2個進程共享代碼空間,但是數據空間是互相獨立的,子進程數據空間中的內容是父進程的完整拷貝,指令指針也完全相同,子進程擁有父進程當前運行到的位置(兩進程的程序計數器pc值相同,也就是說,子進程是從fork返回處開始執行的),但有一點不同,如果fork成功,子進程中fork的返回值是0,父進程中fork的返回值是子進程的進程號,如果fork不成功,父進程會返回錯誤。
來個列子:
int main()
{
int count;
int flag;
pid_t pid;
pid = fork();
if (pid > 0)
{
printf("parent process is run\n");
flag = 1;
}
else if (pid < 0)
{
printf("fork is error\n");
exit(-1);
}
else
{
printf("child is run \n");
flag = 0;
}
count = 0;
if (flag)
{
printf("count in parent is : %d\n", ++count);
}
else
{
printf("count in child is : %d\n", ++count);
}
return 0;
}
運行結果如下:
parent process is run
count in parent is : 1
child is run
count in child is : 1
從這個例子,可以知道:
1父進程和子進程執行了相同的代碼
2父進程和子進程不共享數據空間,否則count的值不可能一樣。
3fork進程返回了兩次,且父進程返回時pid大於0,子進程返回時pid=0,且子進程從返回處開始執行。
下面是《高級編程》詳細介紹的父子進程之間的關係。
fork出來的子進程,基本上除了進程號之外父進程的所有東西都有一份拷貝,基本就意味着不是全部,下面我們要說的是子進程從父進程那裏繼承了什麼東西,什麼東西沒有繼承。還有一點需要注意,子進程得到的只是父進程的拷貝,而不是父進程資源的本身。
由子進程自父進程繼承到:
1.進程的資格(真實(real)/有效(effective)/已保存(saved)用戶號(UIDs)和組號(GIDs))
2.環境(environment)
3.堆棧
4.內存
5.打開文件的描述符(注意對應的文件的位置由父子進程共享,這會引起含糊情況)
6.執行時關閉(close-on-exec) 標誌 (譯者注:close-on-exec標誌可通過fnctl()對文件描述符設置,POSIX.1要求所有目錄流都必須在exec函數調用時關閉。更詳細說明,參見《UNIX環境高級編程》 W. R. Stevens, 1993, 尤晉元等譯(以下簡稱《高級編程》), 3.13節和8.9節)
7.信號(signal)控制設定
8.nice值 (譯者注:nice值由nice函數設定,該值表示進程的優先級,數值越小,優先級越高)
進程調度類別(scheduler class)(譯者注:進程調度類別指進程在系統中被調度時所屬的類別,不同類別有不同優先級,根據進程調度類別和nice值,進程調度程序可計算出每個進程的全局優先級(Global process prority),優先級高的進程優先執行)
8.進程組號
9.對話期ID(Session ID) (譯者注:譯文取自《高級編程》,指:進程所屬的對話期(session)ID, 一個對話期包括一個或多個進程組, 更詳細說明參見《高級編程》9.5節)
10.當前工作目錄
11.根目錄 (譯者注:根目錄不一定是“/”,它可由chroot函數改變)
12.文件方式創建屏蔽字(file mode creation mask (umask))(譯者注:譯文取自《高級編程》,指:創建新文件的缺省屏蔽字)
13.資源限制
14.控制終端
子進程所獨有:
進程號
1.不同的父進程號(譯者注:即子進程的父進程號與父進程的父進程號不同, 父進程號可由getppid函數得到)
2.自己的文件描述符和目錄流的拷貝(譯者注:目錄流由opendir函數創建,因其爲順序讀取,顧稱“目錄流”)
3.子進程不繼承父進程的進程,正文(text), 數據和其它鎖定內存(memory locks)(譯者注:鎖定內存指被鎖定的虛擬內存頁,鎖定後,4.不允許內核將其在必要時換出(page out),詳細說明參見《The GNU C Library Reference Manual》 2.2版, 1999, 3.4.2節)
5.在tms結構中的系統時間(譯者注:tms結構可由times函數獲得,它保存四個數據用於記錄進程使用中央處理器 (CPU:Central Processing Unit)的時間,包括:用戶時間,系統時間, 用戶各子進程合計時間,系統各子進程合計時間)
6.資源使用(resource utilizations)設定爲0
8.阻塞信號集初始化爲空集(譯者注:原文此處不明確,譯文根據fork函數手冊頁稍做修改)
9.不繼承由timer_create函數創建的計時器
10.不繼承異步輸入和輸出
3守護進程的創建
守護進程(Daemon)是運行在後臺的一種特殊進程。它獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。其次,守護進程必須與其運行前的環境隔離開來。這些環 境包括未關閉的文件描述符,控制終端,會話和進程組,工作目錄以及文件創建掩模等。這些環境通常是守護進程從執行它的父進程(特別是shell)中繼承下 來的。最後,守護進程的啓動方式有其特殊之處。它可以在Linux系統啓動時從啓動腳本/etc/rc.d中啓動,可以由作業規劃進程crond啓動,還 可以由用戶終端(通常是 shell)執行。
守護進程創建的步驟:
1)在後臺運行
爲避免掛起控制終端將Daemon放入後臺執行。方法是在進程中調用fork使父進程終止,讓daemon在子進程中後臺執行:
pid = fork();
if (pid > 0)
{
printf("parent is exit\n");
exit(0);//父進程退出
}
2)脫離控制終端,登錄會話和進程組
Linux中的進程與控制終端,登錄會話和進程組之間的關係:進程屬於一個進程組,進程組號(GID)就是進程組長的進程(PID)。登錄會話可以包含多個進程組。這些進程組共享一個控制終端。這個控制終端通常是創建進程的登錄終端。 控制終端,登錄會話和進程組通常是從父進程繼承下來的。我們的目的就是要擺脫它們,使之不受它們的影響。方法是在第1點的基礎上,調用setsid()使 進程成爲會話組長:
if (setsid() == -1)
{
printf("setsid is failed\n");
}
setsid()調用成功後,進程成爲新的會話組長和新的進程組長,並與原來的登錄會話和進程組脫離。由於會話過程對控制終端的獨佔性,進程同時與控制終端脫離。3)禁止進程重新打開控制終端(這個步驟可有可無,看情況)
現在,進程已經成爲無終端的會話組長。但它可以重新申請打開一個控制終端。可以通過使進程不再成爲會話組長來禁止進程重新打開控制終端:
if(pid=fork()) exit(0); //結束第一子進程,第二子進程繼續(第二子進程不再是會話組長)
4)如果有打開的文件,就關閉打開的文件描述符進程從創建它的父進程那裏繼承了打開的文件描述符。如不關閉,將會浪費系統資源,造成進程所在的文件系統無法卸下以及引起無法預料的錯誤。
for(i=0;i<=getdtablesize();i++)
close(i)
5)改變當前工作目錄(看情況)
進程活動時,其工作目錄所在的文件系統不能卸下。一般需要將工作目錄改變到根目錄。對於需要轉儲核心,寫運行日誌的進程將工作目錄改變到特定目錄。
chdir("/tmp")
6)重設文件創建掩模
進程從創建它的父進程那裏繼承了文件創建掩模。它可能修改守護進程所創建的文件的存取位。爲防止這一點,將文件創建掩模清除:
umask(0);
ok,基本步驟已經完成。nginx的damon進程只有步驟1,2,6,一般這3步就行了,不過複雜情況還是按步驟來寫。
下面我們在看一個例子:
在子進程中打開一個文件,並向文件中寫入數據,在滿足一定條件時,守護進程退出。
void daemon_fork()
{
pid_t pid;
pid = fork();
if (pid > 0)
{
printf("parent is exit\n");
exit(0);//第1步
}
else if (pid < 0)
{
printf("fork is failed\n");
exit(-1);
}
else
{
if (setsid() == -1)//第2步
{
printf("setsid is failed\n");
}
umask(000);//第6步
printf("child is working\n");
FILE *fp = fopen("test.txt", "a");
if (fp == NULL)
{
kill(pid, SIGTERM);
}
//do something
int i = 0;
for (;;)
{
fprintf(fp, "%s", (u_char*)("I am the deamon two\n"));
fprintf(fp, "i = %d\n", ++i);
sleep(10);
if (i > 10)
{
fclose(fp);
kill(pid, SIGTERM);
}
}
}
}
http://blog.csdn.net/xiaoliangsky/article/details/39998373
參考:
http://blog.csdn.net/theone10211024/article/details/13774669
http://blog.chinaunix.net/uid-25365622-id-3055635.html