在上一文简单的了解了下什么是记录锁以及掌握它所必备的前修知识。本文重点探讨如果使用 fcntl 对文件部分字节加锁。
1. 加锁解锁方法
1.1 加锁
void wlock(int fd, int start, int len) {
struct flock flk;
// 加写锁,如果是加读锁,这里就写 F_RDLCK
flk.l_type = F_WRLCK;
// 设置参考点,参考 lseek;这里表示参考点为第 0 字节处。
flk.l_whence = SEEK_SET;
// 相对于参考点什么位置;
flk.l_start = start;
// 要加锁的字节数。
flk.l_len = len;
// 获得锁。如果该区域有任何一个字节被其他进程加锁,则阻塞。如果设置 cmd 参数为 F_SETLK,获得所失败会返回 -1,同时 errno 置为 EAGAIN,成功返回 0.
fcntl(fd, F_SETLKW, &flk);
}
关于加锁有几点与读写锁有所不同:
- 同一个进程重复加锁不会阻塞,已最后一次加的锁为准
- 加锁范围
- 如果 len > 0, [whence+start,whence+start+len)
- 如果 len = 0, [whence+start,∞)
- 欲加锁的范围中,有任何一个字节被加锁,则等待
1.2 解锁
void wlock(int fd, int start, int len) {
struct flock flk;
// 解锁
flk.l_type = F_UNLCK;
flk.l_whence = SEEK_SET;
flk.l_start = start;
// 要解锁的字节数。
flk.l_len = len;
fcntl(fd, F_SETLKW, &flk);
}
解锁的程序与加锁区别就在于 flock 的 type 字段。
接下来,我们用实验来观察记录锁是如何互斥的。
2. 实验
程序 lockfile 用来对文件进行加写锁,需要从命令行传参数,格式如下:
./lockfile <filename> <start> <len>
- filename 表示想对哪个文件加锁
- start 表示从第几个字节开始
- len 表示加锁多少字节
2.1 代码
// lockfile.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#define PERR(err, msg) do { errno = err; perror(msg); exit(-1); } while(0)
int lock(int fd, int start, int len) {
puts("locking...");
struct flock flk;
int err;
flk.l_type = F_WRLCK;
flk.l_whence = SEEK_SET;
flk.l_start = start;
flk.l_len = len;
err = fcntl(fd, F_SETLKW, &flk);
if (err < 0) PERR(errno, "lock");
puts("locked...");
return err;
}
int unlock(int fd, int start, int len) {
puts("unlocking...");
struct flock flk;
int err;
flk.l_type = F_UNLCK;
flk.l_whence = SEEK_SET;
flk.l_start = start;
flk.l_len = len;
err = fcntl(fd, F_SETLKW, &flk);
if (err < 0) PERR(errno, "unlock");
puts("unlocked...");
return err;
}
int main(int argc, char* argv[]) {
if (argc < 4) {
printf("Usage: %s <filename> <start> <len>\n", argv[0]);
return -1;
}
char *filename = argv[1];
int start = atoi(argv[2]);
int len = atoi(argv[3]);
printf("pid: %d\n", getpid());
int fd = open(filename, O_WRONLY);
lock(fd, start, len);
sleep(10);
unlock(fd, start, len);
sleep(10);
return 0;
}
2.2 编译
$ gcc lockfile.c -o lockfile
2.3 运行
测试文件 a.txt 内容(11 字节):
hello world
可以通过命令 echo -n 'hello world' > a.txt
获得 a.txt.
- 测试一
进程 A 和 B 对 [2, 5) 范围加锁。
图1 运行结果
- 测试二
进程 A 对 [2, 3) 范围加锁,进程 B 对 [2, 5) 范围加锁。
图2 运行结果
- 测试三
进程 A 对 [2, 3) 范围加锁,进程 B 对 [3, 5) 范围加锁。
图3 运行结果
- 测试四
进程 A 对整个文件加锁,进程 B 对 [3, 5) 范围加锁。
图4 运行结果
3. 总结
- 掌握用 fcntl 加锁解锁的方法
- 注意 flock 结构的 l_len 字段
练习:运行本文中的实验,观察结果。