多进程共享文件时需要互斥。Linux 提供三代文件锁:flock(BSD)、fcntl POSIX 锁、OFD 锁。前两代各有语义地雷。
一、先看图
flowchart TD
FLOCK[flock 锁<br/>per-file 粒度] --> BSD[BSD 语义<br/>fork 后子进程<br/>不继承]
POSIX[POSIX fcntl 锁<br/>byte-range] --> POSIXSEM[per-process 语义<br/>close 任一 fd<br/>释放所有锁!]
OFD[OFD 锁<br/>F_OFD_SETLK<br/>byte-range] --> OFDSEM[per-open-file 语义<br/>安全]
classDef safe fill:#3fb95022,stroke:#3fb950,color:#adbac7;
classDef danger fill:#f8514922,stroke:#f85149,color:#adbac7;
classDef ok fill:#388bfd22,stroke:#388bfd,color:#adbac7;
class FLOCK ok
class POSIX,POSIXSEM danger
class OFD,OFDSEM safe
二、flock(BSD 锁)
int fd = open("/tmp/data", O_RDWR);
flock(fd, LOCK_EX); // 独占锁
// ... 操作文件
flock(fd, LOCK_UN); // 解锁特点:
- 粒度:整个文件(无 byte-range)
- 关联:per-open-file-description(dup 后同一把锁,fork 后不继承)
- 简单、语义清晰
缺点:无 byte-range → 只能锁整个文件。
NFS:早期 flock 在 NFS 上是 noop!现代 NFS v4 已修复。
三、POSIX fcntl 锁
struct flock fl = {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 1024, // 锁前 1024 字节
};
fcntl(fd, F_SETLK, &fl);特点:
- 粒度:byte-range
- 关联:per-process(!)
致命陷阱
close 任何一个指向同一文件的 fd 都会释放该进程持有的所有锁。
int fd1 = open("/tmp/data", O_RDWR);
fcntl(fd1, F_SETLK, &lock); // 拿锁
int fd2 = open("/tmp/data", O_RDONLY);
close(fd2); // 锁被释放了!这违反直觉,但是 POSIX 标准规定。
fork 后子进程不继承锁(因为是 per-process)。
四、OFD 锁(Open File Description Lock)
3.15+ 引入,修复 POSIX 锁的问题:
struct flock fl = {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 1024,
.l_pid = 0, // OFD 锁忽略 pid
};
fcntl(fd, F_OFD_SETLK, &fl);特点:
- 粒度:byte-range ✓
- 关联:per-open-file-description(像 flock)
- close 另一个 fd 不影响 ✓
- fork 后子进程继承(因为共享 struct file)
推荐:新代码应该用 OFD 锁,不用 POSIX fcntl 锁。
五、lease(租约)
fcntl(fd, F_SETLEASE, F_WRLCK);含义:当其他进程打开这个文件时,内核给 lease 持有者发 SIGIO → 有时间窗做清理(如 Samba 的 oplock)。
用途:
- Samba/NFS 的 oplock / delegation
- 防止竞争打开
六、mandatory lock(已退出)
mount -o mand /dev/sda1 /mnt
chmod g+s,g-x /mnt/file // 设置 mandatory lock 标志强制锁——没拿锁的进程 read/write 会被阻塞。
Linux 5.15 已删除 mandatory lock 支持:
- 性能差
- 语义怪异
- 安全隐患(DoS)
七、NFS 上的文件锁
NFS v3:lockd + rpc.statd
管理锁状态。客户端重启后需要 reclaim。
NFS v4:锁集成到协议里。lease-based。
NFSv4 的 delegation 类似 lease——server 委托客户端独占 cache。
八、死锁检测
POSIX 锁支持死锁检测:
fcntl(fd, F_SETLKW, &fl); // 等待模式 + 死锁检测
// 如果检测到死锁返回 EDEADLKflock 和 OFD 锁不做死锁检测。
九、选择指南
| 需求 | 推荐 |
|---|---|
| 简单互斥 | flock |
| byte-range、多线程 | OFD 锁 |
| NFS 兼容 | fcntl POSIX(NFS 原生支持) |
| 遗留代码 | fcntl POSIX(注意陷阱) |
| 新代码 | OFD 锁 |
十、观察
# 查看进程持有的锁
cat /proc/locks
# 1: POSIX ADVISORY WRITE 1234 08:01:12345 0 1023
# 2: FLOCK ADVISORY WRITE 5678 08:01:12345 0 EOF
# lslocks 友好版
lslocks十一、小结
- flock 简单但只能锁整文件
- POSIX fcntl 锁有 close 陷阱——close 任一同文件 fd 释放所有锁
- OFD 锁修复了 POSIX 的设计缺陷,推荐新代码使用
- mandatory lock 已删除
- NFS 锁有专门机制(lockd / NFSv4 delegation)
参考文献
Documentation/filesystems/locking.rst- Jeff Layton, “File locking: the current state.” LWN.net 2014
man 2 flock、man 2 fcntl(F_OFD_SETLK 段落)fs/locks.c
工具
/proc/lockslslocksstrace -e flock,fcntlflock(1)shell 工具
上一篇:fd 表与 struct file 下一篇:xattr/ACL/capabilities
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。
【操作系统百科】Slab/SLUB 分配器
buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。
【操作系统百科】用户态分配器
glibc malloc、tcmalloc、jemalloc、mimalloc 各有哲学。本文讲 arena、thread cache、size class、madvise 返还策略、碎片与 RSS 膨胀、如何根据负载选分配器。