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

Linux 守護進程編程全攻略:特性、步驟詳解與完整實例代碼

     編輯:關於C語言

什麼是守護進程?

守護進程 是在後台運行、脫離控制終端、獨立於用戶會話的長生命周期進程。系統啟動時啟動的服務(如 systemd、cron、sshd、nginx)均為守護進程。其核心特徵:

  • 後台運行,無控制終端(tty)
  • 與父進程環境隔離(文件描述符、工作目錄、umask、信號處理等)
  • 通常以 root 或專用用戶身份運行,生命周期隨系統啟動/關閉

守護進程編程七步法(System V / POSIX 標準)

Linux 遵循 System V Release 4 (SVR4) 並兼容 POSIX,以下步驟為標準流程:

1. Fork 並讓父進程退出 → 進入後台

pid_t pid = fork();
if (pid < 0) exit(EXIT_FAILURE); // fork 失敗
if (pid > 0) exit(EXIT_SUCCESS); // 父進程退出,子進程繼續(成為孤兒進程,被 init 收養)

目的:使進程在後台運行,讓 Shell 以為命令已結束。

2. 調用 setsid() 創建新會話 → 脫離控制終端

if (setsid() < 0) exit(EXIT_FAILURE);

效果:

  • 進程成為新會話首進程(Session Leader)
  • 成為新進程組組長(Process Group Leader)
  • 無控制終端(Controlling Terminal)

3. 二次 Fork 確保不再是會話首進程 → 防止重新獲得控制終端

pid = fork();
if (pid < 0) exit(EXIT_FAILURE);
if (pid > 0) exit(EXIT_SUCCESS); // 第一子進程退出,第二子進程繼續

原因:會話首進程若打開終端設備,可能重新獲得控制終端。二次 Fork 後的進程不再是會話首進程,杜絕此風險。

4. 修改工作目錄 → 避免掛載點無法卸載

chdir("/"); // 或特定目錄如 /var/log、/tmp

若工作目錄在掛載文件系統上,該文件系統無法 umount。

5. 關閉繼承的文件描述符 → 釋放資源、防止干擾

for (int i = 0; i < sysconf(_SC_OPEN_MAX); i++) close(i);

或使用 close_range(0, ~0U, 0) (Linux 5.9+)。守護進程通常不需要標準輸入/輸出/錯誤。

6. 重設文件創建掩模 umask → 確保權限可控

umask(0);

繼承自父進程的 umask 可能限制日志/文件權限,歸零由代碼顯式控制權限。

7. 處理 SIGCHLD 信號 → 避免僵屍進程

signal(SIGCHLD, SIG_IGN);

Linux 內核見 SIG_IGN 會自動回收子進程,無需 wait()。這與 BSD 行為不同(BSD 需顯式 wait)。

完整可運行實例:每分鐘寫入日志的守護進程

init_daemon.c —— 守護進程初始化庫

#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);
}

test_daemon.c —— 業務邏輯

#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 服務單元

在採用 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. umaskumask(0)權限由程序決定
7. SIGCHLDsignal(SIGCHLD, SIG_IGN)自動清理僵屍進程

掌握這七步,即掌握了 Linux/Unix 守護進程編程的精髓。現代項目優先考慮 systemd,但理解底層機制對排查問題、移植舊代碼至關重要。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved