这两个名词非常陌生,如果你直接看 apue,可能会懵圈,不知道所云。没事,你已经找到了这一篇文章,相信你能学会。
1. 提出问题
假设有一个文件 b.txt,进程 A 对其进行了加锁。另外,还有一个进程 B,如果它打开了文件 b.txt,一上来就直接读写 b.txt,会不会出问题?
一般来说,如果进程 A 和进程 B 都是你自己写的,你肯定会遵守规则,两个进程你都会加上加锁和解锁的代码。
但是,如果进程 A 是你写的,进程 B 是另一个陌生人写的,它并不知道进程 A 会给 b.txt 加锁。索性就直接读写吧。
2. 建议性锁和强制性锁
- 建议性锁(Advisory Locking,注意这是形容词)
所谓的建议性锁,并不是一把真正的锁,而是文件系统本身具备的性质,一种属性。所以英文术语使用的是 Advisory Locking 而不是 Advisory Lock. 在中文里,真的是找不到对应的名词。。。
对遵守规则的人来说的。比方刚刚说进程 A 和进程 B 都是你自己的作品,你肯定是按规则办事的,如果进程 B 不是你写的,人家也不按规则办事,你完全没办法,即使再没有获得锁的情况下进程 B 也可以自由读写文件 b.txt.
- 强制性锁(Mandatory Locking)
如果文件系统开启了强制性锁属性,同时文件也开启了强制性锁机制,那么进程 B 试图在不获得锁的情况读写 b.txt,要么阻塞,要么返回 EAGAIN(看描述符是阻塞的还是非阻塞的).
根据上面的叙述,可以举个例子:
比如路口的红绿灯就是一种建议性设置,遵守还是不遵守完全在于个人。如果红绿灯是强制性的,就不在于个人意愿了,红灯情况下,强制把你拦住。
3. 打开强制性锁属性
3.1 打开文件强制性锁属性
默认情况下,文件系统的 Mandatory Locking 属性是关闭的,需要使用 mount -o mand 命令打开。
比如:
mount -o remount,mand /dev/sda1 /
表示重新将硬盘 /dev/sda1 以 Mandatory Locking 方式挂载到根文件系统。
如果你不知道你当前的文件系统上挂载的是哪个设备,可以通过 df -h
查看。
3.2 打开文件本身的强制性锁属性
即使你打开了文件系统的 Mandatory Locking 属性,你不打开文件本身的 Mandatory Locking 属性也是不行的。
有两种方法可以打开文件的 Mandatory Locking 属性:
- 打开文件前,给文件加上Mandatory Locking 属性。可以使用下面的命令:
chmod g+s b.txt
chmod g-x b.txt
说的通俗点,就是去掉组执行属性,加上设置组 ID 属性。这种事情也完全可以在程序里去做,也就是第二种办法:
- 打开文件后,使用 fchmod 函数修改 x 和 s 属性:
struct stat statbuf;
fstat(fd, &statbuf);
fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID);
千万不要问为什么这样做就可打开 Mandatory Locking 属性,这是设计者制定的规则,可能这看起来毫无根据……
4. 实验
程序 readfile 尝试在对文件加锁的情况下读取数据,该程序需要从命令行传入数,表示读取哪个文件。
./readfile <filename>
readfile 会在读取文件内容前对锁进行测试。
4.1 代码
// readfile.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define PERR(err, msg) do { errno = err; perror(msg); exit(-1); } while(0);
int testlock(int fd, int start, int len) {
struct flock flk;
int err;
flk.l_type = F_WRLCK;
flk.l_start = start;
flk.l_whence = SEEK_SET;
flk.l_len = len;
err = fcntl(fd, F_GETLK, &flk);
if (err < 0) PERR(errno, "getlock");
if (flk.l_type == F_UNLCK) return 0;
return flk.l_pid;
}
int main(int argc, char *argv[]) {
char *filename = argv[1];
char buf[64] = { 0 };
int err;
int fd = open(filename, O_RDONLY | O_NONBLOCK);
if (testlock(fd, 0, 0) != 0)
printf("%s is locked\n", filename);
err = read(fd, buf, 2);
if (err < 0)
printf("errno = %d, msg = %s\n", errno, strerror(errno));
else
printf("content: %s\n", buf);
close(fd);
return 0;
}
4.2 编译
$ gcc readfile.c -o readfile
4.3 运行
- 测试一
不开启 Mandatory Locking 属性。
图1 不开启 Mandatory Locking 属性时的运行结果
- 测试二
开启 Mandatory Locking 属性。
图2 开启 Mandatory Locking 属性时的运行结果
可以看到,在 Mandatory Locking 开启的情况下, readfile 进程尝试以 NONBLOCK 方式读取数据时产生错误。errno = 11 在这里实际上就是 EAGAIN.
5. 总结
- 理解 Advisory Locking 和 Mandatory Locking 的作用
- 掌握打开 Mandatory Locking 的方法