1.引言
有些时候进程需要确保其在单独地写一个文件,所以unix提供了记录锁功能。记录锁的兼容性和读写锁类似,就是可以有多个进程同时读,但是只能有一个进程单独写。
2.fcntl记录锁
回忆fcntl函数,其可以有dup的功能,并且打开文件的open函数在头文件fcntl.h中定义。这里再列出fcntl的原型:
#include <fcntl.h>
int fcntl(int filedes, int cmd, …/* struct flock *flockptr*/);
//若成功返回依赖于cmd,若出错返回-1
对于记录锁cmd是F_GETLK、F_SETLK或F_SETLKW分别代表:
F_GETLCK——判定由flockptr所描述的锁是否会被另一把锁排斥(阻塞),如果存在则组织创建,并将现有的锁的信息写入flockptr;如果并存在将flockptr中的l_type设置为F_UNLCK。F_GETLCK的功能有点鸡肋,所以也不是常用!
F_SETLCK——设置由flockptr所描述的锁。如果试图建立的锁不允许,并不阻塞进程(下面有阻塞版本)而是fcntl立即出错返回,此时errno设置为EACCES或EAGAIN。
F_SETLCKW——这个是F_SETLCK的阻塞版本(命令中的W表示wait),如果需创建的锁被阻止,则该进程休眠,直到需创建的锁可用,或休眠由信号中断。
注意!!F_GETLCK测试一把锁和F_SETLCK(W)建立一把锁并不是原子操作,而是两个操作。所以————
三个参数是一个指向flock结构的指针。
struct flock
{
short l_type; /* F_RDLCK, F_WRLCK, or F_UNLCK */
off_t l_start; * offset in bytes, relative to l_whence */
short l_whence; /* SEEK_SET, SEEK_CUR, or SEEK_END */
off_t l_len; /* length, in bytes; 0 means lock to EOF */
pid_t l_pid; /* returned with F_GETLK */
};
//F_RDLCK表示共享读锁,F_WRLCK表示独占写锁,F_UNLCK表示解锁一个区域。
//记录锁是要锁住以个区域的,而不一定锁住整个文件,所以提供了和seek类似的参数
3.锁的隐含继承和释放
关于记录锁的自动继承和释放有三条规则:
(1)锁与进程和文件描述符两方面有关
与进程相关很好理解,如果进程退出的话,那么这个进程所占有的锁都释放掉。与文件相关就不太好理解了。和文件描述符相关就是指在任何时候关闭一个文件描述符,则该进程通过该文件描述符可以引用的文件上的任何一把锁都被释放(这些锁都是该进程设置的)。注意并不是文件关闭,而是文件描述符。看下面的代码:
fd1=open(pathname,…);
read_lock(fd1,…);//自定义的宏
fd2=dup(fd1);
close(fd2);
你能想象在关闭fd2时此时fd1还开放的时候对fd1设置的锁全部释放吗?现实就是这个样子,fd2和fd1指向同一个文件,但只要关闭其中的一个,那么进程对该文件设置的所有的锁就都释放了。不可思议!不知道为什么这么设计unix。
看一下FreeBSD的实现:
一个进程执行下列语句:
fd1=open(pathname, …);
write_lock(fd1, 0, SEEK_SET, 1);/*父进程锁住字节0*/
if((pid=fork()) > 0)/*父进程*/
{
fd2=dup(fd1);
fd3=open(pathname, …);
}else if(pid == 0)
{
readlock(fd1,1,SEEK_SET,1);/*子进程锁住字节1*/
}
经过这段程序之后,系统的文件结构如下图:
由于锁的信息是记录在v节点中的所以,当关闭某一描述符的时候,系统就会在锁链表中搜索与该进程相关的锁并释放掉。而不管进程是以哪一个描述符创建的锁。
(2)由fork产生的子进程不继承父进程所设置的锁,这意味这子进程视为另一个进程,子进程需要调用fcntl才能获得其自己的锁。
(3)在执行exec后,新程序可以继承原执行程序的锁。但是注意,如果对一个文件描述符设置了close-on-exec标志,那么当作为exec的一部分关闭该文件描述符时,对相应文件的所有所都被释放了。