概述
当同一个文件被多进程同时打开,操作时,为防止一致性的问题,需要使用文件锁。Unix系统使用fcntl
函数来操作文件锁。文件锁与线程锁不同,线程锁是把文件作为临界资源,防止多个线程同时打开一个文件,文件锁是防止多个并行进程同时访问同一个文件的同一块内容,《linux程序设计》中将其称为区域锁。
如果一个数据块被加了读锁(read lock),那其他进程不能再对该块加写锁(write lock),但是与读有关的锁操作可以进行,所以读锁也称为共享锁。而一个数据块被加了写锁(write lock),那其他进程不能再对该块加任何锁,写锁也称为独占锁。注意这里说的是锁之间的互斥关系,而不是读写操作的互斥关系,为什么说的这么拗口呢,这是因为Unix关于读写操作的函数并不去检查文件块是否加锁,文件锁只能限制锁操作而不限制读写操作,所以为了达到保证文件内容一致性的目的,对于文件内的临界数据,在读写之前要用锁操作去限制后面的读写函数的调用,也就是说加锁和检测锁是程序猿的工作:(。比如写一个临界数据,先用锁操作检查有没有锁(因为写操作本身不检查锁),如果有锁就不写。而且使用了文件锁后就必须用内核态的读写函数read , write
而不能用fread , fwrite
,因为fread
属于buffered I/O 会在用户态内存中缓存数据,影响锁的正确使用。
一个进程一次对一个文件的某一个字节内容只能加一种类型的锁,锁随进程退出而被释放,并且不会被其子进程继承。
锁操作
文件锁操作通过函数:fcntl (fd, cmd, lock_p)
来完成。
struct flock
其中第三个参数lock_p
是描述文件锁的 , lock_p
结构类型为struct flock
,定义在fcntl.h
中。
1 |
|
short int l_type: 指明锁类型,值为:F_RDLCK
(读锁), F_WRLCK
(写锁), F_UNLCK
(释放锁)。
short int l_whence: 文件指针从何处开始,类似seek
函数,值为:SEEK_SET
, SEEK_CUR
, SEEK_END
。
off_t l_start: 指明文件块的起始偏移地址(相对于l_whence
)。
off_t l_len: 指明文件块大小。0表示从l_start
到文件结尾。
pid_t l_pid: 指明拥有锁的进程ID。当cmd
为F_GETLK
时会返回拥有锁的进程号。
Macro
fcntl (fd, cmd, lock_p)
第二个参数cmd
是一个宏定义,代表具体的锁操作,值为:F_GETLK
,F_SETLK
,F_SETLKW
。
int F_GETLK: 获取指定文件区域的锁信息,调用形式为fcntl(fd, F_GETLK, lock_p)
,如果该区域存在与要检查类型冲突的锁,则该锁信息会被更新到lock_p
中。例如你想测试某一块区域能否加一个F_RDLCK
,若该区域存在一个F_RDLCK
锁,此时加锁是可行的,因此fcntl
调用返回非-1,表示可加锁,且lock_p
不会被更新,若你想加的是F_WRLCK
,那fcntl
返回-1,表示不能加锁,lock_p
的信息被更新为与F_WRLCK
相冲突的那个锁的信息(五个字段全部被更新)。如果该区域没有锁,则l_type
被更新为F_UNLCK
。根据上面的描述,F_WRLCK
可以检测读锁和写锁,F_RDLCK
只能检测写锁。
其他的错误信息,在errno
中,其值为:
EBADF:参数fd不正确。
EINVAL:参数lock_p不正确或者文件不支持锁。
int F_SETLK:用来进行加锁或解锁(解锁时l_type
是F_UNLCK
)操作,调用形式为fcntl(fd, F_SETLK, lock_p)
。如果有上述类型的锁冲突,加锁会失败,函数会立即返回-1。进程只能解自己的锁,在读写操作结束后,要有释放锁的习惯。
errno
信息为:
EAGAIN 或者 EACCES:有其他的锁阻碍加锁操作
EBADF: 参数fd不正确。
EINVAL:参数lock_p不正确或者文件不支持锁
ENOLCK:锁资源不足。
int F_SETLKW:该命令与F_SETLK
功能一样,只不过在加锁失败时不是立即返回,而是阻塞等待冲突的锁被释放并且加锁成功。
errno
信息为:
EINTR:阻塞过程被中断。
EDEADLK:出现死锁。
例子
请参考《linux程序设计(第四版)》P228。