嵌入式linux設備中創(chuàng)建一個守護進程,用于保護系統(tǒng)中的主進程,防止某些不可預期的意外導致主進程異常結束后,系統(tǒng)完全宕機沒有任何反應,破壞用戶體驗感。但是,查閱諸多資料之后發(fā)現(xiàn),大部分人都只講述了如何在x86平臺上創(chuàng)建和實現(xiàn)守護進程,而并沒有人介紹過如何在嵌入式平臺上創(chuàng)建和實現(xiàn)守護進程。于是,經過一番摸索之后,從原理到代碼,都進行了一些大致的了解,我自己提出了一些想法。下面就進行一下簡單的總結和整理。
1、技術原理
下面是網上摘抄的,關于x86的linux系統(tǒng)中對于守護進程的介紹和描述。
守護進程(Daemon)是一種運行在后臺的一種特殊的進程,它獨立于控制終端并且周期性的執(zhí)行某種任務或等待處理某些發(fā)生的事件。
守護進程是個特殊的孤兒進程,這種進程脫離終端,為什么要脫離終端呢?之所以脫離于終端是為了避免進程被任何終端所產生的信息所打斷,其在執(zhí)行過程中的信息也不在任何終端上顯示。由于在 Linux 中,每一個系統(tǒng)與用戶進行交流的界面稱為終端,每一個從此終端開始運行的進程都會依附于這個終端,這個終端就稱為這些進程的控制終端,當控制終端被關閉時,相應的進程都會自動關閉。但是守護進程卻能突破這種限制,它脫離于終端并且在后臺運行,并且它脫離終端的目的是為了避免進程在運行的過程中的信息在任何終端中顯示并且進程也不會被任何終端所產生的終端信息所打斷。它從被執(zhí)行的時候開始運轉,知道整個系統(tǒng)關閉才退出(當然可以認為的殺死相應的守護進程)。如果想讓某個進程不因為用戶或中斷或其他變化而影響,那么就必須把這個進程變成一個守護進程。
2、設計步驟
對于x86平臺的linux系統(tǒng),理論上來說,要想實現(xiàn)上述的效果,守護進程具有一套嚴格的實現(xiàn)步驟。也就是說,守護進程必須在啟動伊始,就去除掉一些系統(tǒng)相關的限制,這樣才能穩(wěn)定的在后臺運行,而不至于被其他任務所干擾和影響。
下面是在x86平臺編寫守護進程的基本過程:
屏蔽一些控制終端操作的信號。這是為了防止守護進行在沒有運行起來前,控制終端受到干擾退出或掛起。關于信號的更詳細用法,請看《信號中斷處理》。
在后臺運行。這是為避免掛起控制終端將守護進程放入后臺執(zhí)行。方法是在進程中調用 fork() 使父進程終止, 讓守護進行在子進程中后臺執(zhí)行。
脫離控制終端、登錄會話和進程組。有必要先介紹一下 Linux 中的進程與控制終端,登錄會話和進程組之間的關系:進程屬于一個進程組,進程組號(GID)就是進程組長的進程號(PID)。登錄會話可以包含多個進程組。這些進程組共享一個控制終端。這個控制終端通常是創(chuàng)建進程的 shell 登錄終端。 控制終端、登錄會話和進程組通常是從父進程繼承下來的。我們的目的就是要擺脫它們 ,使之不受它們的影響。因此需要調用 setsid() 使子進程成為新的會話組長。setsid() 調用成功后,進程成為新的會話組長和新的進程組長,并與原來的登錄會話和進程組脫離。由于會話過程對控制終端的獨占性,進程同時與控制終端脫離。
禁止進程重新打開控制終端。現(xiàn)在,進程已經成為無終端的會話組長,但它可以重新申請打開一個控制終端。可以通過使進程不再成為會話組長來禁止進程重新打開控制終端,采用的方法是再次創(chuàng)建一個子進程。
關閉打開的文件描述符。進程從創(chuàng)建它的父進程那里繼承了打開的文件描述符。如不關閉,將會浪費系統(tǒng)資源,造成進程所在的文件系統(tǒng)無法卸下以及引起無法預料的錯誤。
改變當前工作目錄。進程活動時,其工作目錄所在的文件系統(tǒng)不能卸下。一般需要將工作目錄改變到根目錄。對于需要轉儲核心,寫運行日志的進程將工作目錄改變到特定目錄如 /tmp。
重設文件創(chuàng)建掩模。進程從創(chuàng)建它的父進程那里繼承了文件創(chuàng)建掩模。它可能修改守護進程所創(chuàng)建的文件的存取權限。為防止這一點,必須將文件創(chuàng)建掩模清除。
處理 SIGCHLD 信號。對于某些進程,特別是服務器進程往往在請求到來時生成子進程處理請求。如果父進程不等待子進程結束,子進程將成為僵尸進程(zombie)從而占用系統(tǒng)資源(關于僵尸進程的更多詳情,請看《僵尸進程》)。如果父進程等待子進程結束,將增加父進程的負擔,影響服務器進程的并發(fā)性能。在 Linux 下可以簡單地將 SIGCHLD 信號的操作設為 SIG_IGN 。這樣,內核在子進程結束時才不會產生僵尸進程。
-?
下面就是摘自某前輩的博客上的全套源碼:
#include #include #include #include #include #include #include #include #include #include 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);//結束父進程,子進程繼續(xù) }else if(pid< 0){// 出錯 perror("fork"); exit(EXIT_FAILURE); } // 3)脫離控制終端、登錄會話和進程組 setsid(); // 4)禁止進程重新打開控制終端 if( pid=fork() ){// 父進程 exit(0);// 結束第一子進程,第二子進程繼續(xù)(第二子進程不再是會話組長) }else if(pid< 0){// 出錯 perror("fork"); exit(EXIT_FAILURE); } // 5)關閉打開的文件描述符 // NOFILE 為 的宏定義 // NOFILE 為文件描述符最大個數(shù),不同系統(tǒng)有不同限制 for(i=0; i< NOFILE; ++i){ close(i); } // 6)改變當前工作目錄 chdir("/tmp"); // 7)重設文件創(chuàng)建掩模 umask(0); // 8)處理 SIGCHLD 信號 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、實際情況
從上面的流程邏輯和實際代碼可以看出,x86平臺的守護進程,其實還是比較復雜的,需要進行一堆比較繁瑣的初始化過程。然而,對于嵌入式平臺而言,流程似乎可以簡化一些,不用這么復雜的處理。因為,在本次嵌入式系統(tǒng)中啟用守護進程。其目的只是簡單的利用這個守護進程來啟動另一個被守護的進程,然后定時監(jiān)控該進程是否仍在正常運行,一旦發(fā)現(xiàn)其運行異常,則立即重啟該進程就好。
所以,我對上述的流程進行了簡化,得到如下的流程:
在守護進程中啟動需要被監(jiān)視的進程。
在守護進程中創(chuàng)建一個線程,用來定時監(jiān)測被守護的進程的運行狀態(tài)
守護進程判斷被守護的進程是否仍在正常運行,一旦發(fā)現(xiàn)其運行異常,則立即重啟該進程。
-?4、實際源碼
以下就是在本嵌入式系統(tǒng)項目中所設計的守護進程模塊的全套代碼。
/**************************************************************************************************** 函數(shù)名稱: lockfile** 功能描述: 對文件加鎖/解鎖** 輸入參數(shù): lock: 1表示進行加鎖處理,0表示進行解鎖處理** 輸出參數(shù): 無** 返回參數(shù): 無**************************************************************************************************/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** 功能描述: 獲取進程運行狀態(tài)** 輸入參數(shù): 無** 輸出參數(shù): 無** 返回參數(shù): 返回-1表示路徑錯誤** 返回參數(shù): 返回0表示進程從未運行過,返回1表示進程曾經運行過但是現(xiàn)在停止運行了,返回2表示進程正在運行中**************************************************************************************************/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) { /* 文件不存在,表示進程從未運行過 */ return 0; } if (tryto_lockfile(fd, 1) == -1) { /* 文件加鎖失敗,表示進程在運行中 */ close(fd); return 2; } else { /* 文件加鎖成功,表示進程已經消失 */ tryto_lockfile(fd, 0); /* 此處要注意記得解鎖和關閉文件 */ close(fd); return 1; }}/**************************************************************************************************** 函數(shù)名稱: proc_watch** 功能描述: 檢測進程是否有在運行,沒有運行則重新啟動之** 輸入參數(shù): procname: 進程名** 輸出參數(shù): 無** 返回參數(shù): 返回-1表示進程從未運行過;返回0表示進程當前運行正常;** 返回參數(shù): 返回其他非零值表示進程不存在且已被重新啟動,返回的值是新的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** 功能描述: 啟動進程開始運行** 輸入參數(shù): 無** 輸出參數(shù): 無** 返回參數(shù): 進程的ID號,若啟動失敗則返回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一個進程出來 */ if (pid < 0) { /* 創(chuàng)建進程失敗 */ return 0; } else if (pid == 0) { /* 創(chuàng)建進程成功,此處是子進程的代碼 */ if (execl(filename, procname, (char *)NULL) != -1) { return 1; } else { return 0; } } else { /* 創(chuàng)建進程成功,此處是父進程代碼 */ child_pid = pid; } return (int)child_pid;}/**************************************************************************************************** 函數(shù)名稱: thread_client_hdl** 功能描述: client進程監(jiān)視線程** 輸入參數(shù): 無** 輸出參數(shù): 無** 返回參數(shù): 無**************************************************************************************************/static void *thread_client_hdl(void *pdata){ int result; pdata = pdata; sleep(10); /* 第一次要進行延時 */ 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ù)** 輸入參數(shù): 無** 輸出參數(shù): 無** 返回參數(shù): 無**************************************************************************************************/int main(int argc, char *argv[]){ int client_para; char *p, *process_name; pthread_t thread_client; process_name = argv[0]; /* 獲取進程名稱 */ 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;}
?
評論
查看更多