在 C 語言中從另一個程序控制守護程序
本文將介紹有關如何從 C 語言中的另一個程序控制守護程序的多種方法。
使用 fork
和 setsid
函式建立守護程序
守護程式程序具有多個特徵,例如它們是長時間執行的程序,並且守護程式可以在系統啟動時啟動。守護程序可以從使用者命令中進行控制,這些命令可以強制守護程序終止或暫停,甚至在啟動時禁用。雖然,常見的情況是需要使用某些特定於系統的指令碼的守護程式在系統關閉時終止。守護程式通常在沒有控制終端的情況下在後臺執行,採用這種功能的程式應實現各種訊號處理程式以處理外部中斷。
有多種方法可以建立守護程序並對其進行監視,但是在此示例中,我們演示了建立階段,在此階段中,我們呼叫 fork
函式來建立子程序。然後,父程序退出,子程序繼續成為 init
程序的子程序(在 Linux 系統上,init
程序是啟動時的第一個程序),子程序繼續執行。子程序呼叫 setsid
函式以啟動新會話並從控制終端中刪除該程序。最後,我們再次呼叫 fork
並退出父程序,以確保我們的守護程式不會獲得控制終端。現在我們正在執行守護程序;重要的是註冊一個 SIGTERM
訊號處理程式,以便在系統或使用者傳遞相應的中斷時進行任何資源清除和正常退出。
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define errExit(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
void cleanupRoutine(int signal_number) {
write(STDERR_FILENO, "hello", 5);
_exit(EXIT_SUCCESS);
}
int main(int argc, char *argv[]) {
fprintf(stderr, "[pid - %d] running...\n", getpid());
switch (fork()) {
case -1:
errExit("fork");
case 0:
break;
default:
_exit(EXIT_SUCCESS);
}
fprintf(stderr, "[pid - %d] running...\n", getpid());
if (setsid() == -1) errExit("setsid");
switch (fork()) {
case -1:
errExit("fork");
case 0:
break;
default:
_exit(EXIT_SUCCESS);
}
fprintf(stderr, "[pid - %d] running...\n", getpid());
struct sigaction sigterm_action;
memset(&sigterm_action, 0, sizeof(sigterm_action));
sigterm_action.sa_handler = &cleanupRoutine;
sigterm_action.sa_flags = 0;
// Mask other signals from interrupting SIGTERM handler
if (sigfillset(&sigterm_action.sa_mask) != 0) {
perror("sigfillset");
exit(EXIT_FAILURE);
}
// Register SIGTERM handler
if (sigaction(SIGTERM, &sigterm_action, NULL) != 0) {
perror("sigaction SIGTERM");
exit(EXIT_FAILURE);
}
while (1) {
getpid();
}
exit(EXIT_SUCCESS);
}
使用 daemon
函式建立守護程序
即使前面的示例演示了看似正確的程式碼,也缺少確保關閉從父級繼承的所有開啟檔案描述符的步驟;工作目錄已更改,將標準輸入重定向到/dev/null
,依此類推。這些步驟可確保某些功能在守護程式呼叫時不會失敗,並且不會觀察到某些奇怪的行為。
例如,如果你從終端視窗啟動了先前的程式,然後將 SIGTERM
訊號傳送到守護程序,那麼 cleanupRoutine
訊號處理程式中的 write
函式即使在顯示新的提示後也會列印到同一個終端。因此,GNU C 庫提供了 daemon
函式。它會自動執行上述步驟,以確保新建立的守護程序具有乾淨的上下文。daemon
函式採用兩個整數引數:第一個(如果等於零)指定是否應將當前工作目錄更改為根目錄。第二個整數(如果等於零)指示是否將標準 I/O 流重定向到/dev/null
。
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define errExit(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
void cleanupRoutine(int signal_number) {
write(STDERR_FILENO, "hello", 5);
_exit(EXIT_SUCCESS);
}
int main(int argc, char *argv[]) {
fprintf(stderr, "[pid - %d] running...\n", getpid());
if (daemon(0, 0) == -1) errExit("daemon");
fprintf(stderr, "[pid - %d] running...\n", getpid());
struct sigaction sigterm_action;
memset(&sigterm_action, 0, sizeof(sigterm_action));
sigterm_action.sa_handler = &cleanupRoutine;
sigterm_action.sa_flags = 0;
// Mask other signals from interrupting SIGTERM handler
if (sigfillset(&sigterm_action.sa_mask) != 0) errExit("sigfillset");
// Register SIGTERM handler
if (sigaction(SIGTERM, &sigterm_action, NULL) != 0) errExit("sigaction");
while (1) {
getpid();
}
exit(EXIT_SUCCESS);
}