嵌入式linux設(shè)備中創(chuàng)建一個(gè)守護(hù)進(jìn)程,用于保護(hù)系統(tǒng)中的主進(jìn)程,防止某些不可預(yù)期的意外導(dǎo)致主進(jìn)程異常結(jié)束后,系統(tǒng)完全宕機(jī)沒(méi)有任何反應(yīng),破壞用戶體驗(yàn)感。但是,查閱諸多資料之后發(fā)現(xiàn),大部分人都只講述了如何在x86平臺(tái)上創(chuàng)建和實(shí)現(xiàn)守護(hù)進(jìn)程,而并沒(méi)有人介紹過(guò)如何在嵌入式平臺(tái)上創(chuàng)建和實(shí)現(xiàn)守護(hù)進(jìn)程。于是,經(jīng)過(guò)一番摸索之后,從原理到代碼,都進(jìn)行了一些大致的了解,我自己提出了一些想法。下面就進(jìn)行一下簡(jiǎn)單的總結(jié)和整理。
1、技術(shù)原理
下面是網(wǎng)上摘抄的,關(guān)于x86的linux系統(tǒng)中對(duì)于守護(hù)進(jìn)程的介紹和描述。
守護(hù)進(jìn)程(Daemon)是一種運(yùn)行在后臺(tái)的一種特殊的進(jìn)程,它獨(dú)立于控制終端并且周期性的執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。
守護(hù)進(jìn)程是個(gè)特殊的孤兒進(jìn)程,這種進(jìn)程脫離終端,為什么要脫離終端呢?之所以脫離于終端是為了避免進(jìn)程被任何終端所產(chǎn)生的信息所打斷,其在執(zhí)行過(guò)程中的信息也不在任何終端上顯示。由于在 Linux 中,每一個(gè)系統(tǒng)與用戶進(jìn)行交流的界面稱為終端,每一個(gè)從此終端開(kāi)始運(yùn)行的進(jìn)程都會(huì)依附于這個(gè)終端,這個(gè)終端就稱為這些進(jìn)程的控制終端,當(dāng)控制終端被關(guān)閉時(shí),相應(yīng)的進(jìn)程都會(huì)自動(dòng)關(guān)閉。但是守護(hù)進(jìn)程卻能突破這種限制,它脫離于終端并且在后臺(tái)運(yùn)行,并且它脫離終端的目的是為了避免進(jìn)程在運(yùn)行的過(guò)程中的信息在任何終端中顯示并且進(jìn)程也不會(huì)被任何終端所產(chǎn)生的終端信息所打斷。它從被執(zhí)行的時(shí)候開(kāi)始運(yùn)轉(zhuǎn),知道整個(gè)系統(tǒng)關(guān)閉才退出(當(dāng)然可以認(rèn)為的殺死相應(yīng)的守護(hù)進(jìn)程)。如果想讓某個(gè)進(jìn)程不因?yàn)橛脩艋蛑袛嗷蚱渌兓绊懀敲淳捅仨毎堰@個(gè)進(jìn)程變成一個(gè)守護(hù)進(jìn)程。
2、設(shè)計(jì)步驟
對(duì)于x86平臺(tái)的linux系統(tǒng),理論上來(lái)說(shuō),要想實(shí)現(xiàn)上述的效果,守護(hù)進(jìn)程具有一套嚴(yán)格的實(shí)現(xiàn)步驟。也就是說(shuō),守護(hù)進(jìn)程必須在啟動(dòng)伊始,就去除掉一些系統(tǒng)相關(guān)的限制,這樣才能穩(wěn)定的在后臺(tái)運(yùn)行,而不至于被其他任務(wù)所干擾和影響。
下面是在x86平臺(tái)編寫(xiě)守護(hù)進(jìn)程的基本過(guò)程:
屏蔽一些控制終端操作的信號(hào)。這是為了防止守護(hù)進(jìn)行在沒(méi)有運(yùn)行起來(lái)前,控制終端受到干擾退出或掛起。關(guān)于信號(hào)的更詳細(xì)用法,請(qǐng)看《信號(hào)中斷處理》。
在后臺(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í)行。
脫離控制終端、登錄會(huì)話和進(jìn)程組。有必要先介紹一下 Linux 中的進(jìn)程與控制終端,登錄會(huì)話和進(jìn)程組之間的關(guān)系:進(jìn)程屬于一個(gè)進(jìn)程組,進(jìn)程組號(hào)(GID)就是進(jìn)程組長(zhǎng)的進(jìn)程號(hào)(PID)。登錄會(huì)話可以包含多個(gè)進(jìn)程組。這些進(jìn)程組共享一個(gè)控制終端。這個(gè)控制終端通常是創(chuàng)建進(jìn)程的 shell 登錄終端。 控制終端、登錄會(huì)話和進(jìn)程組通常是從父進(jìn)程繼承下來(lái)的。我們的目的就是要擺脫它們 ,使之不受它們的影響。因此需要調(diào)用 setsid() 使子進(jìn)程成為新的會(huì)話組長(zhǎng)。setsid() 調(diào)用成功后,進(jìn)程成為新的會(huì)話組長(zhǎng)和新的進(jìn)程組長(zhǎng),并與原來(lái)的登錄會(huì)話和進(jìn)程組脫離。由于會(huì)話過(guò)程對(duì)控制終端的獨(dú)占性,進(jìn)程同時(shí)與控制終端脫離。
禁止進(jìn)程重新打開(kāi)控制終端。現(xiàn)在,進(jìn)程已經(jīng)成為無(wú)終端的會(huì)話組長(zhǎng),但它可以重新申請(qǐng)打開(kāi)一個(gè)控制終端。可以通過(guò)使進(jìn)程不再成為會(huì)話組長(zhǎng)來(lái)禁止進(jìn)程重新打開(kāi)控制終端,采用的方法是再次創(chuàng)建一個(gè)子進(jìn)程。
關(guān)閉打開(kāi)的文件描述符。進(jìn)程從創(chuàng)建它的父進(jìn)程那里繼承了打開(kāi)的文件描述符。如不關(guān)閉,將會(huì)浪費(fèi)系統(tǒng)資源,造成進(jìn)程所在的文件系統(tǒng)無(wú)法卸下以及引起無(wú)法預(yù)料的錯(cuò)誤。
改變當(dāng)前工作目錄。進(jìn)程活動(dòng)時(shí),其工作目錄所在的文件系統(tǒng)不能卸下。一般需要將工作目錄改變到根目錄。對(duì)于需要轉(zhuǎn)儲(chǔ)核心,寫(xiě)運(yùn)行日志的進(jìn)程將工作目錄改變到特定目錄如 /tmp。
重設(shè)文件創(chuàng)建掩模。進(jìn)程從創(chuàng)建它的父進(jìn)程那里繼承了文件創(chuàng)建掩模。它可能修改守護(hù)進(jìn)程所創(chuàng)建的文件的存取權(quán)限。為防止這一點(diǎn),必須將文件創(chuàng)建掩模清除。
處理 SIGCHLD 信號(hào)。對(duì)于某些進(jìn)程,特別是服務(wù)器進(jìn)程往往在請(qǐng)求到來(lái)時(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 下可以簡(jiǎn)單地將 SIGCHLD 信號(hào)的操作設(shè)為 SIG_IGN 。這樣,內(nèi)核在子進(jìn)程結(jié)束時(shí)才不會(huì)產(chǎn)生僵尸進(jìn)程。
-?
下面就是摘自某前輩的博客上的全套源碼:
#include #include #include #include #include #include #include #include #include #include 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)程重新打開(kāi)控制終端 if( pid=fork() ){// 父進(jìn)程 exit(0);// 結(jié)束第一子進(jìn)程,第二子進(jìn)程繼續(xù)(第二子進(jìn)程不再是會(huì)話組長(zhǎng)) }else if(pid< 0){// 出錯(cuò) perror("fork"); exit(EXIT_FAILURE); } // 5)關(guān)閉打開(kāi)的文件描述符 // NOFILE 為 的宏定義 // 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; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
3、實(shí)際情況
從上面的流程邏輯和實(shí)際代碼可以看出,x86平臺(tái)的守護(hù)進(jìn)程,其實(shí)還是比較復(fù)雜的,需要進(jìn)行一堆比較繁瑣的初始化過(guò)程。然而,對(duì)于嵌入式平臺(tái)而言,流程似乎可以簡(jiǎn)化一些,不用這么復(fù)雜的處理。因?yàn)椋诒敬?a href="http://www.nxhydt.com/v/tag/2447/" target="_blank">嵌入式系統(tǒng)中啟用守護(hù)進(jìn)程。其目的只是簡(jiǎn)單的利用這個(gè)守護(hù)進(jìn)程來(lái)啟動(dòng)另一個(gè)被守護(hù)的進(jìn)程,然后定時(shí)監(jiān)控該進(jìn)程是否仍在正常運(yùn)行,一旦發(fā)現(xiàn)其運(yùn)行異常,則立即重啟該進(jìn)程就好。
所以,我對(duì)上述的流程進(jìn)行了簡(jiǎn)化,得到如下的流程:
在守護(hù)進(jìn)程中啟動(dòng)需要被監(jiān)視的進(jìn)程。
在守護(hù)進(jìn)程中創(chuàng)建一個(gè)線程,用來(lái)定時(shí)監(jiān)測(cè)被守護(hù)的進(jìn)程的運(yùn)行狀態(tài)
守護(hù)進(jìn)程判斷被守護(hù)的進(jìn)程是否仍在正常運(yùn)行,一旦發(fā)現(xiàn)其運(yùn)行異常,則立即重啟該進(jìn)程。
-?4、實(shí)際源碼
以下就是在本嵌入式系統(tǒng)項(xiàng)目中所設(shè)計(jì)的守護(hù)進(jìn)程模塊的全套代碼。
/**************************************************************************************************** 函數(shù)名稱: lockfile** 功能描述: 對(duì)文件加鎖/解鎖** 輸入參數(shù): lock: 1表示進(jìn)行加鎖處理,0表示進(jìn)行解鎖處理** 輸出參數(shù): 無(wú)** 返回參數(shù): 無(wú)**************************************************************************************************/int tryto_lockfile(int fd, int lock){ struct flock fl; fl.l_type = (lock == 1) ? F_WRLCK : F_UNLCK; fl.l_start = 0; fl.l_whence = SEEK_SET; fl.l_len = 0; return (fcntl(fd, F_SETLK, &fl));}/**************************************************************************************************** 函數(shù)名稱: get_proc_running_state** 功能描述: 獲取進(jìn)程運(yùn)行狀態(tài)** 輸入?yún)?shù): 無(wú)** 輸出參數(shù): 無(wú)** 返回參數(shù): 返回-1表示路徑錯(cuò)誤** 返回參數(shù): 返回0表示進(jìn)程從未運(yùn)行過(guò),返回1表示進(jìn)程曾經(jīng)運(yùn)行過(guò)但是現(xiàn)在停止運(yùn)行了,返回2表示進(jìn)程正在運(yùn)行中**************************************************************************************************/static int get_proc_running_state(const char* filename){ int fd; if (filename == NULL) { /* 文件名為空 */ return -1; } fd = open(filename, O_RDWR, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)); if (fd < 0) { /* 文件不存在,表示進(jìn)程從未運(yùn)行過(guò) */ return 0; } if (tryto_lockfile(fd, 1) == -1) { /* 文件加鎖失敗,表示進(jìn)程在運(yùn)行中 */ close(fd); return 2; } else { /* 文件加鎖成功,表示進(jìn)程已經(jīng)消失 */ tryto_lockfile(fd, 0); /* 此處要注意記得解鎖和關(guān)閉文件 */ close(fd); return 1; }}/**************************************************************************************************** 函數(shù)名稱: proc_watch** 功能描述: 檢測(cè)進(jìn)程是否有在運(yùn)行,沒(méi)有運(yùn)行則重新啟動(dòng)之** 輸入?yún)?shù): procname: 進(jìn)程名** 輸出參數(shù): 無(wú)** 返回參數(shù): 返回-1表示進(jìn)程從未運(yùn)行過(guò);返回0表示進(jìn)程當(dāng)前運(yùn)行正常;** 返回參數(shù): 返回其他非零值表示進(jìn)程不存在且已被重新啟動(dòng),返回的值是新的pid值**************************************************************************************************/int proc_watch(const char *procname){ int result, state; char filename[100]; result = 0; sprintf(filename, "/var/run/%s.pid", procname); state = get_proc_running_state(filename); switch (state) { case 0: result = -1; break; case 1: result = start_proc_by_name(procname); break; case 2: result = 0; break; default: break; } return result;}/**************************************************************************************************** 函數(shù)名稱: start_proc** 功能描述: 啟動(dòng)進(jìn)程開(kāi)始運(yùn)行** 輸入?yún)?shù): 無(wú)** 輸出參數(shù): 無(wú)** 返回參數(shù): 進(jìn)程的ID號(hào),若啟動(dòng)失敗則返回0**************************************************************************************************/int start_proc_by_name(const char* procname){ pid_t pid, child_pid; char filename[100]; sprintf(filename, "%s%s", PROC_FILE_PATH, procname); child_pid = 0; if (access(filename, X_OK | F_OK) != 0) { /* 如果文件存在,并且可執(zhí)行 */ return 0; } pid = fork(); /* 首先要fork一個(gè)進(jìn)程出來(lái) */ if (pid < 0) { /* 創(chuàng)建進(jìn)程失敗 */ return 0; } else if (pid == 0) { /* 創(chuàng)建進(jìn)程成功,此處是子進(jìn)程的代碼 */ if (execl(filename, procname, (char *)NULL) != -1) { return 1; } else { return 0; } } else { /* 創(chuàng)建進(jìn)程成功,此處是父進(jìn)程代碼 */ child_pid = pid; } return (int)child_pid;}/**************************************************************************************************** 函數(shù)名稱: thread_client_hdl** 功能描述: client進(jìn)程監(jiān)視線程** 輸入?yún)?shù): 無(wú)** 輸出參數(shù): 無(wú)** 返回參數(shù): 無(wú)**************************************************************************************************/static void *thread_client_hdl(void *pdata){ int result; pdata = pdata; sleep(10); /* 第一次要進(jìn)行延時(shí) */ for (;;) { printf("time to check thread_client...\n"); result = proc_watch(PROC_NAME_CLIENT); if (result == -1) { printf("thread_client never exist...\n"); } else if (result == 0) { printf("thread_client running ok...\n"); } else { printf("thread_client has gone! but restarted...\n"); } sleep(10); } return NULL;}/**************************************************************************************************** 函數(shù)名稱: main** 功能描述: 入口主函數(shù)** 輸入?yún)?shù): 無(wú)** 輸出參數(shù): 無(wú)** 返回參數(shù): 無(wú)**************************************************************************************************/int main(int argc, char *argv[]){ int client_para; char *p, *process_name; pthread_t thread_client; process_name = argv[0]; /* 獲取進(jìn)程名稱 */ p = process_name + strlen(process_name); while (*p != '/' && p != process_name) { p--; } if (*p == '/') { process_name = p + 1; } printf("\"%s\" starting...\n", process_name); client_para = 0x01; if (pthread_create(&thread_client, NULL, thread_client_hdl, &client_para) != 0) { printf("create thread_client failed!\n"); return 1; } if (start_proc_by_name(PROC_NAME_CLIENT) == 0) { printf("start thread_client failed!\n"); return 1; } for (;;) { sleep(60); printf("i am still alive...\n"); } return 0;}
?
評(píng)論
查看更多