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

Linux 守護進程實現單實例運行:文件鎖與雙重 Fork 技術詳解

     編輯:關於C語言

需求背景

許多系統服務(如資料庫、Web Server、監控代理)要求**同一時間僅能有一個實例運行**。常見解決方案包括:PID 文件鎖、Unix Domain Socket、Systemd 單實例模式、共享記憶體旗標等。本文實作基於**文件獨佔建立(O_EXCL)**與**標準雙重 Fork 守護進程化**的組合方案。

核心機制:O_EXCL 原子性檢查與建立

#define LOCK_FILE "/var/cxl_single_file"

int fd = open(LOCK_FILE, O_RDWR | O_CREAT | O_EXCL, 0664);
if (fd < 0) {
    if (errno == EEXIST) {
        printf("%s is running...\n", argv[0]);
        _exit(-1);
    }
}
// 成功建立 → 取得鎖,繼續執行

O_EXCL 配合 O_CREAT 保證「檢查存在性 + 建立檔案」的原子性,避免競態條件。程式啟動時嘗試建立鎖檔,成功則持有鎖;失敗且 errno == EEXIST 表示已有實例運行。

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

void create_daemon(void) {
    pid_t pid = fork();
    if (pid < 0) { perror("fork"); _exit(-1); }
    if (pid > 0) _exit(0);  // 父進程退出,子進程成為孤兒進程,由 init 收養

    // 子進程:建立新會話,脫離控制終端
    if (setsid() < 0) { perror("setsid"); _exit(-1); }

    // 忽略 SIGHUP,防止終端關閉導致進程退出
    signal(SIGHUP, SIG_IGN);

    // 再次 fork,確保進程不是會話組長,無法重新獲得控制終端
    pid = fork();
    if (pid < 0) { perror("fork2"); _exit(-1); }
    if (pid > 0) _exit(0);

    // 孫進程:標準守護進程環境設定
    chdir("/");              // 切換工作目錄至根目錄,避免掛載點無法卸載
    umask(0);                // 重設檔案權限遮罩

    // 關閉所有繼承的檔案描述符
    int maxfd = sysconf(_SC_OPEN_MAX);
    for (int i = 0; i < maxfd; i++) close(i);

    // 重新導向標準輸入/輸出/錯誤至 /dev/null
    open("/dev/null", O_RDWR);  // fd 0
    open("/dev/null", O_RDWR);  // fd 1
    open("/dev/null", O_RDWR);  // fd 2
}

完整程式碼整合

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define LOCK_FILE "/var/cxl_single_file"

void create_daemon(void);
void delete_file(void);

int main(int argc, char **argv) {
    int fd = open(LOCK_FILE, O_RDWR | O_CREAT | O_EXCL, 0664);
    if (fd < 0) {
        if (errno == EEXIST) {
            printf("%s is running...\n", argv[0]);
            _exit(-1);
        }
    }

    openlog(argv[0], LOG_PID | LOG_CONS, LOG_USER);
    syslog(LOG_INFO, "Create lock file success!");

    atexit(delete_file);  // 正常退出時刪除鎖檔(SIGKILL 不會執行)
    close(fd);
    closelog();

    create_daemon();
    while (1) sleep(1);  // 實際業務邏輯
    return 0;
}

void delete_file(void) { remove(LOCK_FILE); }

關鍵細節與避坑指南

議題說明
鎖檔路徑放置於 /var 或 /run(tmpfs),重啟自動清理;避免放在使用者家目錄。
權限問題非 root 使用者無法寫入 /var,需預先建立目錄並授權,或改用 /tmp/<user>_<app>.lock。
atexit 限制kill -9 不會觸發 atexit 註冊函式,鎖檔會殘留。下次啟動會誤判為「已在運行」。
改進方案啟動時檢查鎖檔對應 PID 是否存活(kill(pid, 0)),若不存活則強制清理鎖檔並重試。
Systemd 整合現代 Linux 建議直接使用 Systemd Type=simple + ExecStart,由 init 系統管理單實例與守護化,無需自行實作。

總結

「O_EXCL 檔案鎖 + 雙重 Fork 守護進程」是傳統 Unix 系統實現單實例後台服務的經典組合。雖然現代部署多改用 Systemd/Docker/K8s 等編排工具管理進程生命週期,但理解底層機制對於除錯遺留系統、開發輕量級嵌入式服務、面試考核仍具核心價值。

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