在了解守護進程之前,需要先知道什么是什么是終端?什么是作業?什么是進程組?什么是會話?
在 Linux 中,每一個系統與用戶進行交流的界面稱為終端,每一個從此終端開始運行的進程都會依附於這個終端,這個終端就稱為這些進程的控制終端,當控制終端被關閉時,相應的進程都會自動關閉。
守護進程就是一個生存周期較長,獨立於控制終端並且周期性執行某種任務的進程。之所以要脫離終端,就是為了防止進程運行過程中被任何終端信息所打斷。
所以,要創建守護進程,我們就要將這個進程脫離終端。
shell分前后台作業來控制的不是進程而是作業。一個作業由多個進程組成。Shell可以運行一個前台作業和任意多個后台作業,稱為作業控制。bash就是一個獨立的作業。
進程組是一個或多個進程的集合,每個進程除了有一個PID以外,還有一個PGID。PGID就是組長的PID。進程組通常和一個作業相關聯,可以接收來自同一個終端的信號。
當然,進程組和作業也並不是完全等價的兩個概念:如果作業中某個進程有創建了新的子進程,該子進程不屬於作業,但屬於該進程組。
會話(Session)是一個或多個進程組的集合。一個會話可以有一個控制終端。一個會話中,有一個前台作業和若干個后台作業。會話SID是會話手進程的PID。
為什么只能運行一個前台作業?當我們在前台新起了一個作業,shell就被提到了后台,因此shell就沒有辦法再繼續接受我們的指令並且解析運行了。但是如果前台進程退出了,shell就會有被提到前台來,就可以繼續接受我們的命令並且解析運行。
那么,如何來切斷進程和終端的關系呢?
首先,調用 setsid() 使子進程成為新的會話組長。setsid() 調用成功后,進程成為新的會話組長和新的進程組長,並與原來的登錄會話和進程組脫離。
調用setsid()有一個前提,就是該進程不能是一個組長進程,因此需要先fork並且殺死父進程,setsid ()的調用者是子進程。
接下來,要禁止進程重新打開控制終端。能打開控制終端的進程一定是進程組組長,因此我們需要再次fork(),並且殺死父進程,留下的子進程就不再是話首進程和進程組組長。於是,這個子進程也不再擁有打開終端的權限,至此,我們徹底切斷了該進程和終端的聯系。
最后,要關閉打開的文件描述符,或者對打開的文件描述符進行重定向。因為進程會繼承從父進程那里的文件描述符,如果不關閉,會浪費系統的資源。
如果想改變該進程的所在目錄,可以調用chdir("/") 將該守護進程轉移到根目錄。
如果該守護進程有子進程,那么守護進程需要等待子進程退出,否則子進程會變成僵屍進程。為了減少該守護進程的負擔,防止其回收子進程對服務器並發性能的影響,可以使用signal(SIGCHLD, SIG_IGN) 對SIGCHLD忽略。這樣就可以防止僵屍進程產生。
#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)屏蔽一些控制終端操作的信號
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
signal(SIGHUP ,SIG_IGN);
// 2)在后台運行
if( pid=fork() ){ // 父進程
exit(0); //結束父進程,子進程繼續
}else if(pid< 0){ // 出錯
perror("fork");
exit(EXIT_FAILURE);
}
// 3)脫離控制終端、登錄會話和進程組
setsid();
// 4)禁止進程重新打開控制終端,這是一種防御性編程,是可選的一步
if( pid=fork() ){ // 父進程
exit(0); // 結束第一子進程,第二子進程繼續(第二子進程不再是會話組長)
}else if(pid< 0){ // 出錯
perror("fork");
exit(EXIT_FAILURE);
}
// 5)關閉打開的文件描述符
// NOFILE 為 <sys/param.h> 的宏定義
// NOFILE 為文件描述符最大個數,不同系統有不同限制
for(i=0; i< NOFILE; ++i){
close(i);
}
// 6)改變當前工作目錄
chdir("/tmp");
// 7)重設文件創建掩模,因為進程從創建它的父進程那里繼承了文件創建掩模。它可能修改守護進程所創建的文件的存取權限。
umask(0);
// 8)處理 SIGCHLD 信號
signal(SIGCHLD,SIG_IGN);
return 0;
}
int main(int argc, char *argv[])
{
init_daemon();
while(1);
return 0;
}