UNIX再学习 -- 守护进程(转)
參看:守護(hù)進(jìn)程
一、什么是守護(hù)進(jìn)程
守護(hù)進(jìn)程(Daemon Process),也就是通常說的 Daemon 進(jìn)程(精靈進(jìn)程),是 Linux 中的后臺(tái)服務(wù)進(jìn)程。它是一個(gè)生存期較長的進(jìn)程,通常獨(dú)立于控制終端并且周期性地執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。
守護(hù)進(jìn)程是個(gè)特殊的孤兒進(jìn)程,這種進(jìn)程脫離終端,為什么要脫離終端呢?之所以脫離于終端是為了避免進(jìn)程被任何終端所產(chǎn)生的信息所打斷,其在執(zhí)行過程中的信息也不在任何終端上顯示。由于在 Linux 中,每一個(gè)系統(tǒng)與用戶進(jìn)行交流的界面稱為終端,每一個(gè)從此終端開始運(yùn)行的進(jìn)程都會(huì)依附于這個(gè)終端,這個(gè)終端就稱為這些進(jìn)程的控制終端,當(dāng)控制終端被關(guān)閉時(shí),相應(yīng)的進(jìn)程都會(huì)自動(dòng)關(guān)閉。
Linux 的大多數(shù)服務(wù)器就是用守護(hù)進(jìn)程實(shí)現(xiàn)的。比如,Internet 服務(wù)器 inetd,Web 服務(wù)器 httpd 等。
二、如何查看守護(hù)進(jìn)程
我們之前有講過 ps命令,參看:UNIX再學(xué)習(xí) -- ps、top、kill 指令
1、在終端執(zhí)行:ps axj
a 表示不僅列當(dāng)前用戶的進(jìn)程,也列出所有其他用戶的進(jìn)程
x 表示不僅列有控制終端的進(jìn)程,也列出所有無控制終端的進(jìn)程
j 表示列出與作業(yè)控制相關(guān)的信息
# ps axjPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND0 1 1 1 ? -1 Ss 0 0:02 /sbin/init0 2 0 0 ? -1 S 0 0:00 [kthreadd]2 3 0 0 ? -1 S 0 0:00 [migration/0]2 4 0 0 ? -1 S 0 0:01 [ksoftirqd/0]2 5 0 0 ? -1 S 0 0:00 [watchdog/0]2 6 0 0 ? -1 S 0 0:00 [events/0]2 7 0 0 ? -1 S 0 0:00 [cpuset]2 8 0 0 ? -1 S 0 0:00 [khelper]2 9 0 0 ? -1 S 0 0:00 [netns]2 10 0 0 ? -1 S 0 0:00 [async/mgr]2 11 0 0 ? -1 S 0 0:00 [pm]2 12 0 0 ? -1 S 0 0:00 [sync_supers]2 13 0 0 ? -1 S 0 0:00 [bdi-default]2 14 0 0 ? -1 S 0 0:00 [kintegrityd/0]2 15 0 0 ? -1 S 0 0:00 [kblockd/0]2 16 0 0 ? -1 S 0 0:00 [kacpid]
2、解析
從上圖可以看出守護(hù)進(jìn)行的一些特點(diǎn):守護(hù)進(jìn)程基本上都是以超級(jí)用戶啟動(dòng)( UID 為 0 )
沒有控制終端( TTY 為 ?)
終端進(jìn)程組 ID 為 -1 ( TPGID 表示終端進(jìn)程組 ID)
一般情況下,守護(hù)進(jìn)程可以通過以下方式啟動(dòng):
在系統(tǒng)啟動(dòng)時(shí)由啟動(dòng)腳本啟動(dòng),這些啟動(dòng)腳本通常放在 /etc/rc.d 目錄下;
利用 inetd 超級(jí)服務(wù)器啟動(dòng),如 telnet 等;
由 cron 定時(shí)啟動(dòng)以及在終端用 nohup 啟動(dòng)的進(jìn)程也是守護(hù)進(jìn)程。
三、編寫規(guī)則
下面是編寫守護(hù)進(jìn)程的基本過程
1、屏蔽一些控制終端操作的信號(hào)
屏蔽一些控制終端操作的信號(hào),這是為了防止守護(hù)進(jìn)行在沒有運(yùn)行起來前,控制終端受到干擾退出或掛起。
默認(rèn)會(huì)導(dǎo)致進(jìn)程停止的信號(hào)有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
參看:UNIX再學(xué)習(xí) -- 信號(hào)
signal(SIGTTOU,SIG_IGN); signal(SIGTTIN,SIG_IGN); signal(SIGTSTP,SIG_IGN); signal(SIGHUP ,SIG_IGN);
2、在后臺(tái)運(yùn)行
這是為避免掛起控制終端將守護(hù)進(jìn)程放入后臺(tái)執(zhí)行。方法是在進(jìn)程中調(diào)用 fork() 使父進(jìn)程終止, 讓守護(hù)進(jìn)行在子進(jìn)程中后臺(tái)執(zhí)行。if( pid = fork() ){ // 父進(jìn)程 exit(0); //結(jié)束父進(jìn)程,子進(jìn)程繼續(xù) }
3、脫離控制終端、登陸會(huì)話和進(jìn)程組
控制終端、登陸會(huì)話和進(jìn)程組我們講過,參看:UNIX再學(xué)習(xí) -- 進(jìn)程關(guān)系我們?cè)俳榻B一下 Linux 中的進(jìn)程與控制終端,登錄會(huì)話和進(jìn)程組之間的關(guān)系:進(jìn)程屬于一個(gè)進(jìn)程組,進(jìn)程組號(hào)(GID)就是進(jìn)程組長的進(jìn)程號(hào)(PID)。登錄會(huì)話可以包含多個(gè)進(jìn)程組。這些進(jìn)程組共享一個(gè)控制終端。這個(gè)控制終端通常是創(chuàng)建進(jìn)程的 shell 登錄終端。 控制終端、登錄會(huì)話和進(jìn)程組通常是從父進(jìn)程繼承下來的。我們的目的就是要擺脫它們 ,使之不受它們的影響。因此需要調(diào)用 setsid() 使子進(jìn)程成為新的會(huì)話組長,示例代碼如下:
4、禁止進(jìn)程重新打開控制終端
現(xiàn)在,進(jìn)程已經(jīng)成為無終端的會(huì)話組長,但它可以重新申請(qǐng)打開一個(gè)控制終端。可以通過使進(jìn)程不再成為會(huì)話組長來禁止進(jìn)程重新打開控制終端,采用的方法是再次創(chuàng)建一個(gè)子進(jìn)程,示例代碼如下:
if( pid=fork() ){ // 父進(jìn)程 exit(0); // 結(jié)束第一子進(jìn)程,第二子進(jìn)程繼續(xù)(第二子進(jìn)程不再是會(huì)話組長) }
5、關(guān)閉打開的文件描述符
進(jìn)程從創(chuàng)建它的父進(jìn)程那里繼承了打開的文件描述符。如不關(guān)閉,將會(huì)浪費(fèi)系統(tǒng)資源,造成進(jìn)程所在的文件系統(tǒng)無法卸下以及引起無法預(yù)料的錯(cuò)誤。按如下方法關(guān)閉它們:
// NOFILE 為 <sys/param.h> 的宏定義 // NOFILE 為文件描述符最大個(gè)數(shù),不同系統(tǒng)有不同限制 for(i=0; i< NOFILE; ++i){// 關(guān)閉打開的文件描述符 close(i); }
6、改變當(dāng)前工作目錄
進(jìn)程活動(dòng)時(shí),其工作目錄所在的文件系統(tǒng)不能卸下。一般需要將工作目錄改變到根目錄。對(duì)于需要轉(zhuǎn)儲(chǔ)核心,寫運(yùn)行日志的進(jìn)程將工作目錄改變到特定目錄如 /tmp。示例代碼如下:
chdir("/");
7、重設(shè)文件創(chuàng)建掩模
進(jìn)程從創(chuàng)建它的父進(jìn)程那里繼承了文件創(chuàng)建掩模。它可能修改守護(hù)進(jìn)程所創(chuàng)建的文件的存取權(quán)限。為防止這一點(diǎn),將文件創(chuàng)建掩模清除:
參看:UNIX再學(xué)習(xí) -- 文件和目錄
umask(0);
8、處理 SIGCHLD 信號(hào)
但對(duì)于某些進(jìn)程,特別是服務(wù)器進(jìn)程往往在請(qǐng)求到來時(shí)生成子進(jìn)程處理請(qǐng)求。如果父進(jìn)程不等待子進(jìn)程結(jié)束,子進(jìn)程將成為僵尸進(jìn)程(zombie)從而占用系統(tǒng)資源(關(guān)于僵尸進(jìn)程的更多詳情,請(qǐng)看《僵尸進(jìn)程》)。如果父進(jìn)程等待子進(jìn)程結(jié)束,將增加父進(jìn)程的負(fù)擔(dān),影響服務(wù)器進(jìn)程的并發(fā)性能。在 Linux 下可以簡單地將 SIGCHLD 信號(hào)的操作設(shè)為 SIG_IGN 。signal(SIGCHLD, SIG_IGN);
這樣,內(nèi)核在子進(jìn)程結(jié)束時(shí)不會(huì)產(chǎn)生僵尸進(jìn)程。
四、示例說明
#include <unistd.h> #include <signal.h> #include <fcntl.h> #include <sys/syslog.h> #include <sys/param.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <stdlib.h> #include <time.h> int init_daemon(void) { int pid; int i; // 1)屏蔽一些控制終端操作的信號(hào) signal(SIGTTOU,SIG_IGN); signal(SIGTTIN,SIG_IGN); signal(SIGTSTP,SIG_IGN); signal(SIGHUP ,SIG_IGN); // 2)在后臺(tái)運(yùn)行 if( pid=fork() ){ // 父進(jìn)程 exit(0); //結(jié)束父進(jìn)程,子進(jìn)程繼續(xù) }else if(pid< 0){ // 出錯(cuò) perror("fork"); exit(EXIT_FAILURE); } // 3)脫離控制終端、登錄會(huì)話和進(jìn)程組 setsid(); // 4)禁止進(jìn)程重新打開控制終端 if( pid=fork() ){ // 父進(jìn)程 exit(0); // 結(jié)束第一子進(jìn)程,第二子進(jìn)程繼續(xù)(第二子進(jìn)程不再是會(huì)話組長) }else if(pid< 0){ // 出錯(cuò) perror("fork"); exit(EXIT_FAILURE); } // 5)關(guān)閉打開的文件描述符 // NOFILE 為 <sys/param.h> 的宏定義 // NOFILE 為文件描述符最大個(gè)數(shù),不同系統(tǒng)有不同限制 for(i=0; i< NOFILE; ++i){ close(i); } // 6)改變當(dāng)前工作目錄 chdir("/tmp"); // 7)重設(shè)文件創(chuàng)建掩模 umask(0); // 8)處理 SIGCHLD 信號(hào) signal(SIGCHLD,SIG_IGN); return 0; } int main(int argc, char *argv[]) { init_daemon(); while(1); return 0; } 編譯:# gcc test.c -o test 執(zhí)行:sudo ./test 查看:# ps axj | grep test1 4164 4163 4163 ? -1 R 0 0:04 ./test2229 4168 4167 2229 pts/0 4167 S+ 0 0:00 grep --color=auto test
五、稍后再講部分
單實(shí)例守護(hù)進(jìn)程 守護(hù)進(jìn)程的慣例總結(jié)
以上是生活随笔為你收集整理的UNIX再学习 -- 守护进程(转)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UNIX再学习 -- 线程控制
- 下一篇: UNIX再学习 -- 记录锁