首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux 守護進程編程完整範例:雙重 Fork、會話分離、資源清理

     編輯:關於C語言

什麼是守護進程?

守護進程是運行在後台、無控制終端、獨立於用戶登入會話的長期運行程式。典型例子:systemd, nginx, mysqld, crond。

標準化守護化流程(雙重 Fork 法)

  1. 第一次 fork:父進程退出,子進程成為孤兒進程,由 init 收養,脫離終端控制
  2. setsid():子進程建立新會話,成為會話首領、進程組長,失去控制終端
  3. 第二次 fork:會話首領再次 fork,父進程(首領)退出,孫進程不再是會話首領,無法重新獲得控制終端(關鍵步驟)
  4. 修改工作目錄:chdir("/") 避免掛載點無法卸載
  5. 重設文件模式創建掩碼:umask(0) 確保創建文件權限可控
  6. 關閉/重定向標準文件描述符:0,1,2 重定向到 /dev/null 或日誌文件
  7. 信號處理:忽略終端相關信號
  8. 資源限制調整:setrlimit 提高文件描述符上限

完整 C 實作代碼

#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?

單次 fork + setsid() 後,進程是會話首領,**仍可通過打開終端設備重新獲得控制終端**。第二次 fork 讓孫進程非會話首領,**永久失去獲得控制終端的能力**。

文件描述符處理策略

  • 關閉 0,1,2 以外所有繼承的描述符(避免洩漏)
  • 將 0,1,2 重定向到 /dev/null(或日誌文件),防止讀寫失敗
  • 使用 getrlimit(RLIMIT_NOFILE) 動態獲取最大描述符數

信號處理原則

現代替代方案:systemd 服務單元

現代 Linux 發行版推薦使用 systemd 管理守護進程,無需手寫雙重 fork:

# /etc/systemd/system/mydaemon.service
[Unit]
Description=My Daemon
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/mydaemon
Restart=on-failure
User=daemonuser
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target

systemd 自動處理:進程監管、日誌、重啟、依賴、資源限制、權限降級。

總結

守護進程編程是 Linux 系統程式設計的核心技能。雖然 systemd 簡化了部署,但理解雙重 fork、會話管理、文件描述符處理、信號語義等底層機制,對排查疑難雜症、開發高可靠系統軟體至關重要。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
信號處理原因
SIGHUPSIG_IGN終端掛斷,防止進程退出
SIGINT/SIGQUITSIG_IGN鍵盤中斷/退出,無終端不適用
SIGTSTP/SIGTTIN/SIGTTOUSIG_IGN終端停止/輸入/輸出,無終端不適用
SIGTERM自定義處理優雅關閉:釋放資源、寫日誌、退出