近日小温下APUE,发现Linux下的 fcntl 实现强制锁的功能好像都没试验过,简单做个测试。
首先用 fcntl 实现建议锁(Advisory locking),比较简单,贴个最简单的代码:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char ** argv) { int fd = 0, ret = 0; struct flock lock = {0}; lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; fd = open("test.file", O_RDWR | O_CREAT); printf("before flock\n"); ret = fcntl(fd, F_SETLKW, &lock); printf("after flock, ret: %d\n", ret); getchar(); close(fd); return 0; }
关键的几句已高亮显示,用 F_SETLKW 等待写的锁,很好测试,分别开一个终端运行一次就可以测试出来。
Linux 同时实现了 POSIX 的 fcntl 锁函数,BSD 的 flock 函数,SVR4 的 lockf 函数,这些默认都是建议锁。
这个简单的实现只是建议锁,需要每个对文件的操作的进程都遵循同样的锁操作才能起作用,这些进程称为合作进程(Cooperative processes),但实际使用时会有例外的情况,如果另外开一个终端窗口直接写 test.file 文件,会发现可以直接写,因为这种写没有锁操作,不是合作进程,这种情况就需要用强制锁(Mandatory locking)了。
强制锁的实际代码和上面的完全一样,不过在 Linux 上需要做一些改动:
- 挂载文件系统时需要加 mand 参数在文件系统上启用强制锁支持,比较新的 Linux kernel 里已经基本在所有文件系统上都实现了;
- 去掉程序的组执行权限;
- 增加程序的设置组ID权限。
第二项和第三项在普通情况下实际上是自相矛盾的,所以 Linux 就用这种特殊情况就表示启用强制锁(Mandatory locking)了。
备注
- 使用 BSD 的 flock 函数时不能使用强制锁,见 Linux kernel 源码下 mandatory-locking.txt 中的说明:
Mandatory locks can only be applied via the fcntl()/lockf() locking
interface - in other words the System V/POSIX interface. BSD style
locks using flock() never result in a mandatory lock.- 如果一个文件被某进程强制锁住,另一个进程通过 creat 函数或者 open 时加 O_TRUNC 参数,都会导致函数调用失败;
- 强制锁在不同的 UNIX 及 类UNIX 系统中实现不同,第二个备注中的 O_TRUNC 参数便是 Linux kernel 和 HP-UX 不同的地方;
- tmpfs、nfs 也支持强制锁,可以用这两个文件系统方便测试;
- 删除文件(unlink操作)再重新创建文件,会导致强制锁失效;
- Linux kernel 的强制锁的实现并不完全可靠(感觉白写了,哈哈,实际情况还要做处理),见 man fcntl 的说明。
强制锁的实际操作:
mount -t tmpfs -o size=10m,mand tmpfs /mnt cp test /mnt cd /mnt chmod g-x test chmod g+s test
运行 test 程序,同时开另一个终端,直接用 echo 读写 test 文件,会发现强制锁已经起作用。