守護進程 是在後台運行、脫離控制終端、獨立於用戶會話的長生命周期進程。系統啟動時啟動的服務(如 systemd、cron、sshd、nginx)均為守護進程。其核心特徵:
tty)Linux 遵循 System V Release 4 (SVR4) 並兼容 POSIX,以下步驟為標準流程:
pid_t pid = fork(); if (pid < 0) exit(EXIT_FAILURE); // fork 失敗 if (pid > 0) exit(EXIT_SUCCESS); // 父進程退出,子進程繼續(成為孤兒進程,被 init 收養)
目的:使進程在後台運行,讓 Shell 以為命令已結束。
setsid() 創建新會話 → 脫離控制終端if (setsid() < 0) exit(EXIT_FAILURE);
效果:
pid = fork(); if (pid < 0) exit(EXIT_FAILURE); if (pid > 0) exit(EXIT_SUCCESS); // 第一子進程退出,第二子進程繼續
原因:會話首進程若打開終端設備,可能重新獲得控制終端。二次 Fork 後的進程不再是會話首進程,杜絕此風險。
chdir("/"); // 或特定目錄如 /var/log、/tmp
若工作目錄在掛載文件系統上,該文件系統無法 umount。
for (int i = 0; i < sysconf(_SC_OPEN_MAX); i++) close(i);
或使用 close_range(0, ~0U, 0) (Linux 5.9+)。守護進程通常不需要標準輸入/輸出/錯誤。
umask → 確保權限可控umask(0);
繼承自父進程的 umask 可能限制日志/文件權限,歸零由代碼顯式控制權限。
SIGCHLD 信號 → 避免僵屍進程signal(SIGCHLD, SIG_IGN);
Linux 內核見 SIG_IGN 會自動回收子進程,無需 wait()。這與 BSD 行為不同(BSD 需顯式 wait)。
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/param.h> // NOFILE
#include <sys/types.h>
#include <sys/stat.h>
void init_daemon(void) {
pid_t pid;
int i;
// 1. Fork 父進程退出
if ((pid = fork()) > 0) exit(0);
else if (pid < 0) exit(1);
// 2. 創建新會話,脫離終端
setsid();
// 3. 二次 Fork,不再是會話首進程
if ((pid = fork()) > 0) exit(0);
else if (pid < 0) exit(1);
// 4. 關閉所有繼承的文件描述符
for (i = 0; i < NOFILE; ++i) close(i);
// 5. 改變工作目錄
chdir("/tmp"); // 示例用 /tmp,生產建議 / 或 /var/empty
// 6. 重設 umask
umask(0);
// 7. 忽略 SIGCHLD
signal(SIGCHLD, SIG_IGN);
}
#include <stdio.h>
#include <time.h>
#include <unistd.h>
void init_daemon(void);
int main() {
FILE *fp;
time_t t;
init_daemon(); // 變身守護進程
while (1) {
sleep(60); // 休眠 60 秒
fp = fopen("test.log", "a");
if (fp) {
t = time(NULL);
fprintf(fp, "I'm here at %s", ctime(&t)); // ctime 自帶換行
fclose(fp);
}
}
return 0; // 永不執行到此
}
gcc -Wall -o test_daemon init_daemon.c test_daemon.c ./test_daemon
ps -ef | grep test_daemon # 應看到進程在後台運行,PPID 為 1 (systemd/init) tail -f /tmp/test.log # 每分鐘追加一行時間戳
在採用 systemd 的現代 Linux 發行版中,無需手寫守護進程代碼。只需編寫 .service 單元文件,systemd 自動處理後台化、日志、重啟、依賴等:
# /etc/systemd/system/my-daemon.service [Unit] Description=My Custom Daemon After=network.target [Service] Type=simple ExecStart=/usr/local/bin/my-daemon-program Restart=on-failure StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target
然後 systemctl enable --now my-daemon 即可。
| 步驟 | 關鍵調用 | 核心目的 |
|---|---|---|
| 1. 後台化 | fork() + exit() | 脫離 Shell 控制 |
| 2. 新會話 | setsid() | 失去控制終端,成為會話首進程 |
| 3. 防終端 | fork() + exit() | 不再是會話首進程,永不獲取控制終端 |
| 4. 工作目錄 | chdir("/") | 不阻礙文件系統卸載 |
| 5. 關閉描述符 | close()/close_range() | 釋放資源,避免洩漏 |
| 6. umask | umask(0) | 權限由程序決定 |
| 7. SIGCHLD | signal(SIGCHLD, SIG_IGN) | 自動清理僵屍進程 |
掌握這七步,即掌握了 Linux/Unix 守護進程編程的精髓。現代項目優先考慮 systemd,但理解底層機制對排查問題、移植舊代碼至關重要。