守護進程是在背景長期運行、無控制終端機、通常隨系統啟動的服務型程式。本文提供標準的 C 語言實作範例,涵蓋雙重 Fork 脫離終端機、建立新會話、關閉檔案描述符、改變工作目錄、重設 umask、信號處理(重載配置/優雅退出)、PID 記錄與 /etc/rc.local 自動啟動。
init_daemon()#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
void init_daemon(void) {
pid_t pid;
int i;
/* 第一次 fork:父進程退出,子進程成為背景進程 */
if ((pid = fork()) > 0) exit(0); // 父進程結束
else if (pid < 0) exit(1); // fork 失敗
/* 子進程:建立新會話,脫離控制終端機,成為會話組長 */
setsid();
/* 第二次 fork:確保進程不再是會話組長,防止重新獲得控制終端機 */
if ((pid = fork()) > 0) exit(0);
else if (pid < 0) exit(1);
/* 第二子進程:關閉所有繼承的檔案描述符 */
for (i = 0; i < NOFILE; ++i) close(i);
/* 改變工作目錄到根目錄,避免掛載點無法卸載 */
chdir("/tmp"); // 或 "/"
/* 重設檔案建立掩碼 */
umask(0);
}
daemontest.c#include <signal.h>
#include <time.h>
void init_daemon(void);
void signal_reload(int sig); // SIGUSR1: 重載配置
void signal_handle(int sig); // SIGUSR2: 優雅退出
int main(int argc, char **argv) {
FILE *fp;
time_t t;
init_daemon(); // 變身守護進程
signal(SIGCHLD, SIG_IGN); // 忽略子進程退出,避免殭屍進程
signal(SIGUSR1, signal_reload); // 註冊重載信號
signal(SIGUSR2, signal_handle); // 註冊退出信號
while (1) {
sleep(60); // 主迴圈:每分鐘記錄心跳
if ((fp = fopen("test.log", "a")) != NULL) {
t = time(0);
fprintf(fp, "I'm here at %s\n", asctime(localtime(&t)));
fclose(fp);
}
}
return 0;
}
void signal_reload(int sig) {
FILE *fp; time_t t;
if ((fp = fopen("test.log", "a")) != NULL) {
t = time(0);
fprintf(fp, "I received signal(%d), reload all parameters at %s\n", sig, asctime(localtime(&t)));
fclose(fp);
}
/* 實際重載配置邏輯 */
}
void signal_handle(int sig) {
FILE *fp; time_t t;
if ((fp = fopen("test.log", "a")) != NULL) {
t = time(0);
fprintf(fp, "I received signal(%d), exit at %s\n", sig, asctime(localtime(&t)));
fclose(fp);
}
exit(0);
}
gcc init_daemon.c daemontest.c -o mydaemon
./mydaemon
進程將在背景運行,每分鐘在 /tmp/test.log 寫入心跳(因 chdir("/tmp"))。
編輯 /etc/rc.d/rc.local(或 systemd 服務單元),加入:
su - username -c "/path/to/mydaemon"
以指定用戶身分啟動,避免 root 權限過大。
ps -ef | grep mydaemonkill -s SIGUSR1 <PID>kill -s SIGUSR2 <PID>kill -9 <PID>(最後手段)[Unit]
Description=My Custom Daemon
After=network.target
[Service]
Type=forking
ExecStart=/path/to/mydaemon
User=username
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
存為 /etc/systemd/system/mydaemon.service,執行 systemctl enable --now mydaemon 即可納入系統服務管理,支援依賴、日誌整合、自動重啟等企業級特性。
setsid() 徹底脫離終端機。chdir("/") 防止檔案系統無法卸載;umask(0) 確保建檔權限可控。SIGCHLD 避免殭屍進程;自訂信號實現熱重載/優雅退出。