Linux 下的守護程式:如何撰寫並使用簡單的守護程式
守護程式是 Linux 系統中一種特殊的進程,它是在背景運行的,沒有控制終端,不受使用者的干擾,負責執行一些系統或應用相關的任務和功能。守護程式的作用是提高系統的穩定性和效率,以應對一些不可預期的意外或異常。在嵌入式 Linux 設備中,守護程序可以用來保護系統中的主進程,防止其異常結束後,導致系統完全宕機,破壞使用者體驗。但是,你真的了解 Linux 下的守護程式嗎?你知道如何在 Linux 下編寫和使用簡單的守護程式嗎?本文將為你詳細介紹 Linux 下的守護程序的相關知識,讓你在 Linux 下更好地使用和理解這個強大的進程類型。
#式linux設備中建立一個守護進程,用於保護系統中的主進程,防止某些不可預期的意外導致主進程異常結束後,系統完全宕機沒有任何反應,破壞用戶體驗感。但是,查閱許多資料之後發現,大部分人都只講述瞭如何在x86平台上建立和實現守護進程,而並沒有人介紹過如何在嵌入式平台上建立和實現守護進程。於是,經過一番摸索之後,從原理到程式碼,都做了一些大致的了解,我自己提出了一些想法。下面就進行一下簡單的總結與整理。
1、技術原理
#以下是網路上摘抄的,關於x86的linux系統中對於守護程式的介紹和描述。
守護程式(Daemon)是一種運行在後台的一種特殊的進程,它獨立於控制終端並且週期性的執行某種任務或等待處理某些發生的事件。
守護程式是個特殊的孤兒進程,這種進程脫離終端,為什麼要脫離終端呢?之所以脫離於終端是為了避免進程被任何終端所產生的資訊所打斷,其在執行過程中的資訊也不在任何終端上顯示。由於在Linux 中,每一個系統與使用者溝通的介面稱為終端,每一個從此終端開始運作的進程都會依附於這個終端,這個終端就稱為這些進程的控制終端,當控制終端被關閉時,對應的進程都會自動關閉。但是守護進程卻能突破這種限制,它脫離於終端並且在後台運行,並且它脫離終端的目的是為了避免進程在運行的過程中的信息在任何終端中顯示並且進程也不會被任何終端所產生的終端資訊所打斷。它從被執行的時候開始運轉,知道整個系統關閉才退出(當然可以認為的殺死相應的守護進程)。如果想讓某個行程不因為使用者或中斷或其他變化而影響,那麼就必須把這個行程變成一個守護程式。
2、設計步驟
#對於x86平台的linux系統,理論上來說,要實現上述的效果,守護程式具有一套嚴格的實現步驟。也就是說,守護程式必須在啟動伊始,就去除一些系統相關的限制,這樣才能穩定的在後台運行,而不至於被其他任務所干擾和影響。
以下是在x86平台編寫守護程式的基本流程:
- 屏蔽一些控制終端操作的訊號。這是為了防止守護進行在沒有運作起來之前,控制終端受到干擾退出或掛起。關於訊號的更詳細用法,請看《訊號中斷處理》。
- 在後台運行。這是為避免掛起控制終端將守護程式放入背景執行。方法是在程式中呼叫 fork() 使父程序終止, 讓守護在子程序中進行後台執行。
- 脫離控制終端機、登入工作階段和進程組。有必要先介紹 Linux 中的進程與控制終端,登入會話和進程組之間的關係:進程屬於一個進程組,進程組號(GID)就是進程組長的進程號(PID)。登入會話可以包含多個進程組。這些進程組共用一個控制終端。這個控制終端通常是創建進程的 shell 登入終端。控制終端機、登入會話和進程組通常是從父進程繼承下來的。我們的目的就是要擺脫它們 ,使之不受它們的影響。因此需要呼叫 setsid() 使子進程成為新的會話組長。 setsid() 呼叫成功後,進程成為新的會話組長和新的進程組長,並與原來的登入會話和進程組脫離。由於會話過程對控制終端的獨佔性,進程同時與控制終端脫離。
- 禁止進程重新開啟控制終端。現在,進程已經成為無終端機的會話組長,但它可以重新申請開啟一個控制終端。可以透過使進程不再成為會話組長來禁止進程重新開啟控制終端,採用的方法是再次建立子進程。
- 關閉開啟的文件描述符。進程從創建它的父進程繼承了開啟的檔案描述符。如不關閉,將會浪費系統資源,造成進程所在的檔案系統無法卸下以及造成無法預料的錯誤。
- 改變目前工作目錄。進程活動時,其工作目錄所在的檔案系統不能卸下。一般需要將工作目錄改變到根目錄。對於需要轉儲核心,寫入運行日誌的進程將工作目錄改變到特定目錄如 /tmp。
- 重設檔案建立遮罩。進程從創建它的父進程繼承了檔案創建掩模。它可能修改守護程式所建立的檔案的存取權限。為防止這一點,必須將檔案建立遮罩清除。
- 處理 SIGCHLD 訊號。對於某些進程,特別是伺服器進程往往在請求到來時產生子進程處理請求。如果父進程不等待子進程結束,子進程將成為殭屍進程(zombie)從而佔用系統資源(關於殭屍進程的更多詳情,請看《殭屍進程》)。如果父進程等待子進程結束,將增加父進程的負擔,影響伺服器進程的並發效能。在 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(SIGHU P ,SIG_IGN); // 2)在后台运行 if( pid=fork() ){// 父进程 exit(0);//结束父进程,子进程继 续 }else if(pid 的宏定义 // NOFILE 为文件描述符最大个数,不同系统有不同限 制 for(i=0; i
3、實際狀況
#從上面的流程邏輯和實際程式碼可以看出,x86平台的守護進程,其實還是比較複雜的,需要進行一堆比較繁瑣的初始化過程。然而,對於嵌入式平台而言,流程似乎可以簡化一些,不用這麼複雜的處理。因為,在本次嵌入式系統中啟用守護程式。其目的只是簡單的利用這個守護進程來啟動另一個被守護的進程,然後定時監控該進程是否仍在正常運行,一旦發現其運行異常,則立即重啟該進程就好。
所以,我對上述的流程做了簡化,得到如下的流程:
- 在守護程式中啟動需要被監視的進程。
- 在守護程式中建立一個線程,用來定時監測被守護的程序的運行狀態
- 守護程序判斷被守護的程序是否仍在正常運行,一旦發現其運行異常,則立即重新啟動該進程。
– 4、實際原始碼
#以下就是在本嵌入式系統專案中所設計的守護程式模組的全套程式碼。
/****************************************************************************************** ******** ** 函数名称: lockfile ** 功能描述: 对文件加锁/解锁 ** 输入参数: lock: 1表示进行加锁处理, 0表示进行解锁处理 ** 输出参数: 无 ** 返回参 数: 无 ************************************************************************************* *************/ 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 (f cntl(fd, F_SETLK, &fl)); } /*************************************************************** *********************************** ** 函数名称: get_proc_running_state ** 功能描述: 获取进程 运行状态 ** 输入参数: 无 ** 输出参数: 无 ** 返回参数: 返回-1表示路径错误 ** 返回参数: 返回0表示进程 从未运行过,返回1表示进程曾经运行过但是现在停止运行了,返回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)); i f (fd 0) { /* 文件不存在,表示进程从未运行 过 */ return 0; } if (tryto_lockfile(fd, 1) == -1) { /* 文件加锁失败,表示进程在运行 中 */ close(fd); return 2; } else { /* 文件加锁成功,表示进程已经消 失 */ tryto_lockfile(fd, 0); /* 此处要注意记得解锁和关闭文 件 */ close(fd); return 1; } } /*********************************************************** *************************************** ** 函数名称: proc_watch ** 功能描述: 检测进程是否有在运 行,没有运行则重新启动之 ** 输入参数: procname: 进程名 ** 输出参数: 无 ** 返回参数: 返回-1表示进程从 未运行过;返回0表示进程当前运行正常; ** 返回参数: 返回其他非零值表示进程不存在且已被重新启动,返回的值 是新的pid值 *************************************************************************** ***********************/ int proc_watch(const char *procname) { int result, state; char fi lename[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 = sta rt_proc_by_name(procname); break; case 2: result = 0; break; default: break; } return resu lt; } /************************************************************************************ ************** ** 函数名称: start_proc ** 功能描述: 启动进程开始运行 ** 输入参数: 无 ** 输出参 数: 无 ** 返回参数: 进程的ID号,若启动失败则返回 0 ***************************************************************************************** *********/ int start_proc_by_name(const char* procname) { pid_t pid, child_pid; char filen ame[100]; sprintf(filename, "%s%s", PROC_FILE_PATH, procname); child_pid = 0; if (access(f ilename, X_OK | F_OK) != 0) { /* 如果文件存在,并且可执行 */ return 0; } pid = fork(); /* 首 先要fork一个进程出来 */ if (pid 0) { /* 创建进程失 败 */ return 0; } else if (pid == 0) { /* 创建进程成功,此处是子进程的代 码 */ if (execl(filename, procname, (char *)NULL) != -1) { return 1; } else { return 0; } } else { /* 创建进程成功,此处是父进程代 ******************************************************************* ** 函数名 称: thread_client_hdl ** 功能描述: client进程监视线程 ** 输入参数: 无 ** 输出参数: 无 ** 返回参 数: 无 ************************************************************************************* *************/ static void *thread_client_hdl(void *pdata) { int result; pdata = pdata; sl eep(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; } /************************************* ************************************************************* ** 函数名称: main ** 功能描 述: 入口主函数 ** 输入参数: 无 ** 输出参数: 无 ** 返回参 数: 无 ************************************************************************************* *************/ 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; }
通过本文,你应该对 Linux 下的守护进程有了一个基本的了解,知道了它的定义、特点和用途。你也应该明白了如何在 Linux 下编写和使用简单的守护进程,以及使用守护进程时需要注意的一些问题和技巧。我们建议你在使用 Linux 系统时,使用守护进程来提高系统的稳定性和效率。同时,我们也提醒你在使用守护进程时要注意一些潜在的问题和挑战,如信号处理、日志记录、资源管理等。希望本文能够帮助你更好地使用 Linux 系统,让你在 Linux 下掌握守护进程的编写和使用。
以上是Linux 下的守護程式:如何撰寫並使用簡單的守護程式的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

VS Code 系統要求:操作系統:Windows 10 及以上、macOS 10.12 及以上、Linux 發行版處理器:最低 1.6 GHz,推薦 2.0 GHz 及以上內存:最低 512 MB,推薦 4 GB 及以上存儲空間:最低 250 MB,推薦 1 GB 及以上其他要求:穩定網絡連接,Xorg/Wayland(Linux)

Linux系統的五個基本組件是:1.內核,2.系統庫,3.系統實用程序,4.圖形用戶界面,5.應用程序。內核管理硬件資源,系統庫提供預編譯函數,系統實用程序用於系統管理,GUI提供可視化交互,應用程序利用這些組件實現功能。

雖然 Notepad 無法直接運行 Java 代碼,但可以通過借助其他工具實現:使用命令行編譯器 (javac) 編譯代碼,生成字節碼文件 (filename.class)。使用 Java 解釋器 (java) 解釋字節碼,執行代碼並輸出結果。

VS Code擴展安裝失敗的原因可能包括:網絡不穩定、權限不足、系統兼容性問題、VS Code版本過舊、殺毒軟件或防火牆干擾。通過檢查網絡連接、權限、日誌文件、更新VS Code、禁用安全軟件以及重啟VS Code或計算機,可以逐步排查和解決問題。

vscode 內置終端是一個開發工具,允許在編輯器內運行命令和腳本,以簡化開發流程。如何使用 vscode 終端:通過快捷鍵 (Ctrl/Cmd ) 打開終端。輸入命令或運行腳本。使用熱鍵 (如 Ctrl L 清除終端)。更改工作目錄 (如 cd 命令)。高級功能包括調試模式、代碼片段自動補全和交互式命令歷史。

要查看 Git 倉庫地址,請執行以下步驟:1. 打開命令行並導航到倉庫目錄;2. 運行 "git remote -v" 命令;3. 查看輸出中的倉庫名稱及其相應的地址。

在 Visual Studio Code(VSCode)中編寫代碼簡單易行,只需安裝 VSCode、創建項目、選擇語言、創建文件、編寫代碼、保存並運行即可。 VSCode 的優點包括跨平台、免費開源、強大功能、擴展豐富,以及輕量快速。

VS Code 可以在 Mac 上使用。它具有強大的擴展功能、Git 集成、終端和調試器,同時還提供了豐富的設置選項。但是,對於特別大型項目或專業性較強的開發,VS Code 可能會有性能或功能限制。
