Linux 提供三種文件鎖介面:flock(2) 系統調用、fcntl(2) 系統調用 (記錄鎖)、lockf(3) 庫函數 (fcntl 封裝)。三者在**鎖類型、粒度、遞歸性、fork/dup 語義、NFS 支持、強制鎖**等關鍵維度差異巨大,選型失誤易導致併發 Bug、死鎖或數據不一致。
| 維度 | flock | fcntl (F_SETLK/W) | lockf |
|---|---|---|---|
| 類型 | 系統調用 | 系統調用 | 庫函數 (封裝 fcntl) |
| 鎖粒度 | **整個文件** | **字節範圍** (可鎖文件任意區間) | 字節範圍 (從當前偏移起) |
| 鎖模式 | 共享 (LOCK_SH) / 排他 (LOCK_EX) | 讀鎖 (F_RDLCK) / 寫鎖 (F_WRLCK) | **僅排他鎖** (F_LOCK/TLOCK) |
| 阻塞控制 | LOCK_NB 非阻塞標誌 | F_SETLK 非阻塞 / F_SETLKW 阻塞 | F_TLOCK 非阻塞 / F_LOCK 阻塞 |
| 遞歸性 (同進程重複加鎖) | **支持** (同文件描述符表項) | **支持** (新鎖替換舊鎖) | 支持 (同 fcntl) |
| fork() 子進程繼承 | **繼承** (共享文件表項) | **不繼承** (POSIX 規定) | 不繼承 |
| dup()/dup2() 複製描述符 | **共享鎖** (同文件表項) | **共享鎖** (同文件表項) | 共享鎖 |
| close() 任一描述符釋放鎖 | **否** (需關閉所有複製描述符) | **是** (進程持有的所有該文件鎖釋放) | 是 |
| exec() 後保留鎖 | 是 (除非 FD_CLOEXEC) | 是 (除非 FD_CLOEXEC) | 是 |
| NFS 支持 | **不支持** (本地語義) | **支持** (需啟用 NFS 鎖管理器) | 支持 (同 fcntl) |
| 強制鎖 | 不支持 | **支持** (掛載掛載 mand + 文件 setgid + 組執行位關閉) | 支持 (同 fcntl) |
| 查看 /proc/locks 類型標識 | FLOCK | POSIX | POSIX |
struct file* (打開文件表項),而非文件描述符 fd。fork()、dup() 複製的是 fd 指向同一 struct file,**父子進程、複製描述符共享同一把鎖**。fd 只減少引用計數,struct file 仍存在,**必須關閉所有指向該文件表項的 fd 才釋放鎖**。open() 產生兩個獨立 struct file,**互不影響**,第一把鎖不阻擋第二次加鎖 (除非協作進程檢查)。LOCK_EX 不會死鎖。fd1=open(); lock(fd1); fd2=dup(fd1); close(fd2); → 鎖釋放!rpc.statd/rpc.lockd,掛載參數無 nolock。mand,文件 chmod g+s, g-x (setgid + 組執行位關閉) 啟用。內核強制阻擋違規讀寫,無需進程協作。lockf(fd, cmd, len) 從**當前文件偏移量** 起鎖定 len 字節 (len=0 到文件尾)。同一進程對同一文件同時持有 flock 排他鎖和 fcntl 寫鎖,**互不阻塞**,/proc/locks 顯示獨立的 FLOCK 和 POSIX 記錄。切勿混用兩套 API 保護同一臨界區,會產生虛假安全感。
$ cat /proc/locks
1: POSIX ADVISORY WRITE 12345 08:02:123456 0 EOF ← fcntl/lockf 寫鎖
2: FLOCK ADVISORY WRITE 12345 08:02:123456 0 EOF ← flock 排他鎖
字段: 序號 | 類型(POSIX/FLOCK) | 模式(ADVISORY/MANDATORY) | 讀/寫 | PID | 設備號:inode | 起始偏移 | 結束偏移
掌握三者語義邊界,才能在高併發文件操作中構建正確、高效的同步機制!