Linux 提供多種文件鎖定機制,最簡單且廣泛適用的是「建議性鎖」——通過原子操作創建鎖文件作為協作指示器。本文深度解析 O_CREAT | O_EXCL 組合的原子語義,提供完整 C 語言實現與錯誤碼分析,適用於單機多進程互斥場景。
open() 系統調用同時指定 O_CREAT(不存在則創建)和 O_EXCL(存在則失敗)標誌時,內核保證「檢查文件是否存在」與「創建文件」兩步合併為單一不可分割的原子操作。這消除了「檢查-然後創建」的競態條件(TOCTOU)。
// file: lock.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
int main(void) {
int fd;
int saved_errno;
// 嘗試以原子方式創建鎖文件,權限 0444(只讀)
fd = open("/tmp/LockFile.test", O_RDWR | O_CREAT | O_EXCL, 0444);
if (fd < 0) {
saved_errno = errno;
printf("Open failed with error %d\n", saved_errno);
if (saved_errno == EEXIST) {
printf("Lock already held by another process.\n");
}
} else {
printf("Open succeeded - lock acquired.\n");
// 業務邏輯區
// ...
// 釋放鎖
close(fd);
unlink("/tmp/LockFile.test");
}
return EXIT_SUCCESS;
}
$ ./lock
Open succeeded - lock acquired.
$ ./lock
Open failed with error 17
Lock already held by another process.
錯誤碼 17 對應 EEXIST(文件已存在),定義於 <errno.h>。
| 特性 | 說明 |
|---|---|
| 建議性 | 依賴進程協作遵守,不強制阻止違規訪問 |
| 自動釋放 | 進程崩潰時文件描述符關閉,但鎖文件殘留需額外清理機制 |
| 無死鎖檢測 | 應用層需自行實現超時/重試邏輯 |
| 跨文件系統 | 僅在同一掛載點有效,NFS 需支援 O_EXCL 語義 |
getpid() 寫入鎖文件,便於診斷持有者usleep() 輪詢,避免忙等消耗 CPUSIGTERM/SIGINT 處理器調用 unlink()mkdir() 同樣具備原子性,且天然支援 NFS,rmdir() 釋放若需內核強制阻塞違規進程,應使用 fcntl(F_SETLKW)(記錄鎖,支援局部鎖定)或 flock()(整文件鎖,BSD 風格)。兩者均為建議性預設,需掛載 mand 選項並設置文件 setgid 位才啟用強制性語義,生產環境較少使用。
最佳實踐:單機簡單互斥首選 mkdir() 目錄鎖(無殘留風險、NFS 友好);需鎖定文件局部區域或跨進程讀寫協調時使用 fcntl() 記錄鎖;分布式場景請轉向 Redis Redlock、etcd、Consul、ZooKeeper 等分布式鎖服務。