守護進程是運行在後台、無控制終端、獨立於用戶登入會話的長期運行程式。典型例子:systemd, nginx, mysqld, crond。
chdir("/") 避免掛載點無法卸載umask(0) 確保創建文件權限可控/dev/null 或日誌文件setrlimit 提高文件描述符上限#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/resource.h>
#include <sys/stat.h>
void DaemonInit(void) {
pid_t pid;
/* 第一次 fork:父進程退出 */
if((pid = fork()) < 0) {
perror("fork 1 failed");
exit(1);
} else if(pid > 0) {
exit(0); // 父進程退出
}
/* 子進程:建立新會話,脫離控制終端 */
if(setsid() < 0) {
perror("setsid failed");
exit(1);
}
/* 第二次 fork:防止重新獲得控制終端 */
if((pid = fork()) < 0) {
perror("fork 2 failed");
exit(1);
} else if(pid > 0) {
exit(0); // 第一子進程退出
}
/* 現在是孫進程,非會話首領,無控制終端 */
/* 關閉所有繼承的文件描述符 */
struct rlimit rl;
getrlimit(RLIMIT_NOFILE, &rl);
int maxfd = (rl.rlim_max == RLIM_INFINITY) ? 1024 : rl.rlim_max;
for(int fd = 0; fd < maxfd; fd++) close(fd);
/* 重定向標準輸入/輸出/錯誤到 /dev/null */
int fd0 = open("/dev/null", O_RDWR);
int fd1 = dup(0);
int fd2 = dup(0);
if(fd0 != 0 || fd1 != 1 || fd2 != 2) {
fprintf(stderr, "標準流重定向失敗\n");
exit(1);
}
/* 重設 umask */
umask(0);
/* 忽略終端相關信號 */
signal(SIGHUP, SIG_IGN);
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
/* 可選:修改工作目錄到根目錄 */
chdir("/");
/* 可選:設定資源限制 */
struct rlimit lim = {65535, 65535};
setrlimit(RLIMIT_NOFILE, &lim);
}
/* 守護進程主邏輯範例 */
void daemon_work() {
while(1) {
// 實際業務邏輯:定時任務、網路監聽、日誌記錄等
sleep(60);
}
}
int main() {
DaemonInit();
daemon_work();
return 0;
}
單次 fork + setsid() 後,進程是會話首領,**仍可通過打開終端設備重新獲得控制終端**。第二次 fork 讓孫進程非會話首領,**永久失去獲得控制終端的能力**。
/dev/null(或日誌文件),防止讀寫失敗getrlimit(RLIMIT_NOFILE) 動態獲取最大描述符數| 信號 | 處理 | 原因 |
|---|---|---|
| SIGHUP | SIG_IGN | 終端掛斷,防止進程退出 |
| SIGINT/SIGQUIT | SIG_IGN | 鍵盤中斷/退出,無終端不適用 |
| SIGTSTP/SIGTTIN/SIGTTOU | SIG_IGN | 終端停止/輸入/輸出,無終端不適用 |
| SIGTERM | 自定義處理 | 優雅關閉:釋放資源、寫日誌、退出 |