1. 简介
使用 fcntl()
能够在一个文件的任意一个部分上放置一把锁,这个文件部分既可以是一个字节范围,也可以是整个文件。
2. API
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
使用 fcntl()
操作记录锁时,cmd
可以取值如下:
F_SETLK
:设置锁,可以是加锁或解锁,取决于arg
参数;如果无法立马加锁,则fcntl()
会立马返回 -1,并将 errno 设为EAGAIN
或EACCES
,而不阻塞。F_SETLKW
:与F_SETLK
类似,但当无法立马加锁时,此操作会导致fcntl()
阻塞。
arg
参数是一个指向 flock
结构体类型的指针:
struct flock {
...
short l_type; /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Starting offset for lock */
off_t l_len; /* Number of bytes to lock */
pid_t l_pid; /* PID of process blocking our lock (set by F_GETLK and F_OFD_GETLK) */
...
};
-
l_type
的取值可以为F_RDLCK, F_WRLCK, F_UNLCK
,分别表示加读锁、加写锁、解锁。 -
l_whence
用于指定如何解释锁的起始偏移量l_start
,其含义与lseek()
相同;l_len
指定了加锁范围的长度。 -
将
l_whence
设为SEEK_SET
,并将l_start
和l_len
都设为 0,可以锁住整个文件。 -
解锁一个没有被锁住的区域不会导致出错。
-
对于一个区域,一个进程只能同时持有一种类型的锁;如果在该区域上放置一把类型不同的锁,则会原子地将锁类型转换为新类型。
-
在已有锁的区域中间放置一把类型不同的锁会导致产生三把锁,即分裂了原先被锁住的区域,区域的两端是旧类型的锁,中间是新类型的锁。
-
在紧挨着已有锁的区域放置一把类型相同的锁会导致锁合并,即该类型的锁(此时只有一把)会锁住之前的区域和紧挨着的新区域。
-
由
fork()
创建的子进程不会继承父进程的记录锁。 -
记录锁会在
exec()
后得到保留,除非设置了close-on-exec
标志。 -
一个进程中的所有线程会共享同一组记录锁。
-
记录锁同时与一个进程和一个 i-node 关联;当进程终止之后,该进程持有的所有记录锁将会被释放;此外,当进程关闭了一个文件描述符后,该进程持有的对应文件上的所有记录锁会被释放掉,而不管这些锁是通过哪个文件描述符获得的(如,通过不同的
open()
调用或dup()
调用)。 -
每个打开的文件都关联着一个链表,链表中保存着该文件上的锁。链表中的锁会先按照进程 ID 再按照起始偏移量来排序,如下图所示。所以,添加或删除一个锁所需要的时间与该文件上已有的锁的数量之间大概是一个线性关系。
3. 例子
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
const char* FILENAME = "test.txt";
void test_record_lock() {
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 4;
int fd = open(FILENAME, O_RDWR);
fcntl(fd, F_SETLKW, &lock);
pid_t pid = fork();
if (pid < 0) {
perror("fork()");
exit(EXIT_FAILURE);
}
if (pid == 0) {
close(fd);
fd = open(FILENAME, O_RDWR);
lock.l_type = F_RDLCK;
fcntl(fd, F_SETLKW, &lock);
char buf[5] = {0};
read(fd, buf, sizeof(buf)-1);
std::cout << buf << '\n';
close(fd);
_exit(EXIT_SUCCESS);
}
write(fd, "ABCD", 4);
lock.l_type = F_UNLCK;
fcntl(fd, F_SETLKW, &lock);
close(fd);
wait(NULL);
}
int main() {
test_record_lock();
return 0;
}