记录锁
《UNIX环境高级编程》--14章13节,P358///P372
1. 记录锁 record locking
功能:当一个进程正在读或修改文件的某个部分时,它可以阻止其它进程修改同一文件区。
字节范围锁 byte-range locking
2. 历史
flock函数,可以锁整个文件,不能锁文件中的一部分。
fcntl函数,增加了记录锁的功能。
lockf函数,在fcntl基础上构造了lockf函数,提供一个简化的接口。可以锁文件中任意字节数的区域
3. fcntl 记录锁
函数原型
#include <fcntl.h>
int fcntl(int fd, int cmd, .../* struct flock *flockptr */); // 针对记录锁:成功返回0,出错返回-1
struct flock
{
short l_type; // F_RDLCK, F_WRLCK, F_UNLCK: 共享读锁,独占性写锁,解锁
off_t l_start; // offset in bytes, relative to l_whence:起始字节偏移量,相对于l_whence
short l_whence; // SEEK_SET, SEEK_CUR, SEEK_END: 起始位置
off_t l_len; // length in bytes; 0 means lock to EOF:锁定长度
pid_t l_pid; // returned with F_GETLK: 具有能阻塞当前进程的锁,其进程id存放于此
}
4. 不同进程锁请求的读写锁规则:
------------------- 加读锁 加写锁
无锁 允许 允许
一个或多个读锁 允许 拒绝
一个写锁 拒绝 拒绝
注意:上面这个规则适用于不同进程提出的锁请求,并不适用于单个进程提出的多个锁请求。
单个进程提出多个锁请求的时候,以最后一次锁作为标准,即新锁替换旧锁
对于同一文件,如果一直有不同的进程连续的对其添加读锁,则其它欲对其添加阻塞写锁的进程有可能延长等待时间。
5. 在设置和释放锁的时候,由于记录锁是对文件的某个部分加锁和解锁的,则系统会按要求组合或裂开锁区域。
6. 死锁:A进程锁住a区,B进程锁住b区,A要求对b区加锁,B要求对a区加锁,则进入死锁状态。
检测到死锁时,内核会选择一个进程接收出错返回。
7. 锁的隐含继承和释放
1). 锁与进程和文件两方面有关。
当一个进程终止时,它所建立的锁就全部释放。
任何时候关闭一个文件描述符时,则该进程通过这一文件描述符引用的的文件上的任何一把锁都被释放掉。
注:同一个进程中,对于同一个文件,若被多个文件描述符引用,任一描述符的close调用,都会释放该进程加在该文件上的所有锁。
例如:
fd1 = open(filename, ...);
read_lock(fd1, ...);
fd2 = dup(fd1); // 或者 fd2 = open(filename, ...);
close(fd2);
2). 由fork产生的子进程不继承父进程所设置的锁。
3). 在执行exec之后的新程序,可以继承原执行程序的锁。
注:如果对一文件描述符设置了close-on-exec标志,那么当做为exec的一部分关闭该文件描述符时,对应文件的所有锁都会被释放掉。
8. 文件尾端加锁注意。
由于锁具有组合和裂开的特点。可能会造成不被期望的结果。P366P380 图14-3
9. 建议性锁和强制性锁
合作进程概念。
建议性锁不能阻止对文件有写权限的其它进程对该文件的随意写操作。
强制性锁使内核对每一个open、read、write系统调用都进行检查。检查调用进程对正在访问的文件是否违背了某一把锁的作用。
10. 代码测试:
【代码】---参考《UNIX环境高级编程》的例子
测试总结:创建一个普通的文件,两个不同的进程同时对文件加锁并写入数据,然后解锁。
用F_SETLK设置锁,不会出现预期的加锁阻塞机制。
用F_SETLKW设置锁,两个不同的进程会被阻塞的向文件写入数据。
(测试结果,仅为个人测试,有待考察)。