一:普通進程運行觀察
//ps -eo pid,ppid,sid,tty,pgrp,comm,stat,cmd | grep -E 'bash|PID|nginx'
//a)進程有對應的終端,如果終端退出,那麼對應的進程也就消失了;它的父進程是一個bash
//b)終端被佔住了,你輸入各種命令這個終端都沒有反應;
二:守護進程基本概念
//守護進程 一種長期運行的進程:這種進程在後臺運行,並且不跟任何的控制終端關聯;
//基本特點:
//a)生存期長[不是必須,但一般應該這樣做],一般是操作系統啓動的時候他就啓動,操作系統關閉的時候他才關閉;
//b)守護進程跟終端無關聯,也就是說他們沒有控制終端,所以你控制終端退出,也不會導致守護進程退出;
//c)守護進程是在後臺運行,不會佔着終端,終端可以執行其他命令
//linux操作系統本身是有很多的守護進程在默默的運行,維持着系統的日常活動。大概30-50個;
//a)ppid = 0:內核進程,跟隨系統啓動而啓動,聲明週期貫穿整個系統;
//b)cmd列名字帶[]這種,叫內核守護進程;
//c)老祖init:也是系統守護進程,它負責啓動各運行層次特定的系統服務;所以很多進程的PPID是init。而且這個init也負責收養孤兒進程;
//d)cmd列中名字不帶[]的普通守護進程(用戶級守護進程)
//共同點總結:
//a)大多數守護進程都是以超級 用戶特權運行的;
//b)守護進程沒有控制終端,TT這列顯示?
//內核守護進程以無控制終端方式啓動
//普通守護進程可能是守護進程調用了setsid的結果(無控制端);
三:守護進程編寫規則
//(1)調用umask(0);
//umask是個函數,用來限制(屏蔽)一些文件權限的。
//(2)fork()一個子進程(脫離終端)出來,然後父進程退出( 把終端空出來,不讓終端卡住);固定套路
//fork()的目的是想成功調用setsid()來建立新會話,目的是
//子進程有單獨的sid;而且子進程也成爲了一個新進程組的組長進程;同時,子進程不關聯任何終端了;
一些概念
(3.1)文件描述符:正數,用來標識一個文件。
//linux中三個特殊的文件描述符,數字分別爲0,1,2
//0:標準輸入【鍵盤】,對應的符號常量叫STDIN_FILENO
//1:標準輸出【屏幕】,對應的符號常量叫STDOUT_FILENO
//2:標準錯誤【屏幕】,對應的符號常量叫STDERR_FILENO
//類Unix操作系統,默認從STDIN_FILENO讀數據,向STDOUT_FILENO來寫數據,向STDERR_FILENO來寫錯誤;
//類Unix操作系統有個說法:一切皆文件,所以它把標準輸入,標準輸出,標準錯誤 都看成文件。
//與其說 把 標準輸入,標準輸出,標準錯誤 都看成文件 到不如說
//象看待文件一樣看待 標準輸入,標準輸出,標準錯誤
//象操作文件一樣操作 標準輸入,標準輸出,標準錯誤
//同時,你程序一旦運行起來,這三個文件描述符0,1,2會被自動打開(自動指向對應的設備);
//文件描述符雖然是數字,但是,如果我們把文件描述符直接理解成指針(指針裏邊保存的是地址——地址說白了也是個數字);
//write(STDOUT_FILENO,"aaaabbb",6);
(3.2)輸入輸出重定向
//輸出重定向:我標準輸出文件描述符,不指向屏幕了,假如我指向(重定向)一個文件
//重定向,在命令行中用 >即可;
//輸入重定向 <
(3.3)空設備(黑洞)
// /dev/null :是一個特殊的設備文件,它丟棄一切寫入其中的數據(象黑洞一樣);
//----
//守護進程雖然可以通過終端啓動,但是和終端不掛鉤。
//守護進程是在後臺運行,它不應該從鍵盤上接收任何東西,也不應該把輸出結果打印到屏幕或者終端上來
//所以,一般按照江湖規矩,我們要把守護進程的 標準輸入,標準輸出,重定向到 空設備(黑洞);
//從而確保守護進程不從鍵盤接收任何東西,也不把輸出結果打印到屏幕;
//int fd;
//fd = open("/dev/null",O_RDWR) ;//打開空設備
//dup2(fd,STDIN_FILENO); //複製文件描述符 ,像個指針賦值,把第一個參數指向的內容賦給了第二個參數;
//dup2(fd,STDOUT_FILENO);
//if(fd > STDERR_FILENO)
// close(fd); //等價於fd = null;
(3.4)實現範例
//守護進程可以用命令啓動,如果想開機啓動,則需要藉助 系統初始化腳本來啓動。
#include <stdio.h>
#include <stdlib.h> //malloc
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
//創建守護進程
//創建成功則返回1,否則返回-1
int ngx_daemon()
{
int fd;
switch (fork()) //fork()子進程
{
case -1:
//創建子進程失敗,這裏可以寫日誌......
return -1;
case 0:
//子進程,走到這裏,直接break;
break;
default:
//父進程,直接退出
exit(0);
}
//只有子進程流程才能走到這裏
if (setsid() == -1) //脫離終端,終端關閉,將跟此子進程無關
{
//記錄錯誤日誌......
return -1;
}
umask(0); //設置爲0,不要讓它來限制文件權限,以免引起混亂
fd = open("/dev/null", O_RDWR); //打開黑洞設備,以讀寫方式打開
if (fd == -1)
{
//記錄錯誤日誌......
return -1;
}
if (dup2(fd, STDIN_FILENO) == -1) //先關閉STDIN_FILENO[這是規矩,已經打開的描述符,動他之前,先close],類似於指針指向null,讓/dev/null成爲標準輸入;
{
//記錄錯誤日誌......
return -1;
}
if (dup2(fd, STDOUT_FILENO) == -1) //先關閉STDIN_FILENO,類似於指針指向null,讓/dev/null成爲標準輸出;
{
//記錄錯誤日誌......
return -1;
}
if (fd > STDERR_FILENO) //fd應該是3,這個應該成立
{
if (close(fd) == -1) //釋放資源這樣這個文件描述符就可以被複用;不然這個數字【文件描述符】會被一直佔着;
{
//記錄錯誤日誌......
return -1;
}
}
return 1;
}
int main(int argc, char *const *argv)
{
if(ngx_daemon() != 1)
{
//創建守護進程失敗,可以做失敗後的處理比如寫日誌等等
return 1;
}
else
{
//創建守護進程成功,執行守護進程中要乾的活
for(;;)
{
sleep(1); //休息1秒
printf("休息1秒,進程id=%d!\n",getpid()); //你就算打印也沒用,現在標準輸出指向黑洞(/dev/null),打印不出任何結果【不顯示任何結果】
}
}
return 0;
}
四:守護進程不會收到的信號:內核發給你,另外的進程發給你的;
(4.1)SIGHUP信號
//守護進程不會收到來自內核的 SIGHUP 信號; 潛臺詞就是 如果守護進程收到了 SIGHUP信號,那麼肯定是另外的進程發給你的;
//很多守護進程把這個信號作爲通知信號,表示配置文件已經發生改動,守護進程應該重新讀入其配置文件;
(4.2)SIGINT、SIGWINCH信號
//守護進程不會收到來自內核的 SIGINT(ctrl+C),SIGWINCH(終端窗口大小改變) 信號;
五:守護進程和後臺進程的區別
//(1)守護進程和終端不掛鉤;後臺進程能往終端上輸出東西(和終端掛鉤);
//(2)守護進程關閉終端時不受影響,守護進程會隨着終端的退出而退出;
//(3)......其他的,大家自己總結;