本文转自:http://blog.csdn.net/chenhanzhun/article/details/41142905
当多个进程在编辑同一个文件时,在 UNIX 系统中,文件的最后状态取决于写该文件的最后一个进程,但是进程必须要确保它正在单独写一个文件,所以需要用到记录锁机制。记录锁的功能:当一个进程在读或修改文件的某一部分时,它可以阻止其他进程修改同一个文件区,记录锁也称为字节范围锁,因为它锁定的只是文件中的一个区域或整个文件。
fcntl 记录锁
记录锁可以通过 fcntl 函数进行控制,该函数的基本形式如下:
/* fcntl记录锁 */
/*
* 函数功能:记录锁;
* 返回值:若成功则依赖于cmd的值,若出错则返回-1;
* 函数原型:
*/
#include <fcntl.h>
int fcntl(int fd, int cmd, .../* struct flock *flockptr */);
/*
* 说明:
* cmd的取值可以如下:
* F_GETLK 获取文件锁
* F_SETLK、F_SETLKW 设置文件锁
* 第三个参数flockptr是一个结构指针,如下:
*/
struct flock
{
short l_type; /* F_RDLCK, F_WRLCK, F_UNLCK */
off_t l_start; /* offset in bytes, relative to 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 */
};
对 flock 结构如下说明:
- 锁的类型:F_RDLCK (共享读锁)、F_WRLCK(独占性写锁)或 F_UNLCK(解锁);
- 加锁或解锁的区域的起始字节偏移量由 l_start 和 l_whence 两者决定,其中 l_whenc 的参数和 lseek 函数的 whence 参数一样;l_start 是相对偏移量,l_whenc 是相对偏移量的起点,SEEK_SET(文件起始位置), SEEK_CUR(文件当前位置), SEEK_END(文件尾端);
- 区域的长度由 l_len 决定;若 l_len 为0,则表示锁的区域从其起点(由 l_start 和 l_whence 两者决定)开始直到最大可能偏移量为止;
- 该区域可以在当前文件的尾端处开始或越过尾端处开始,但是不能在文件的起始端之前开始;
共享读锁和独占性写锁的基本规则是:多个进程在一个给定的字节上可以有一把共享读锁,但是在一个给定的字节上只能有一个进程独用一把写锁。若在一个给定的字节上已经有一把或多把读锁,则不能在该字节上再加上写锁;若在一个给定的字节上已经有一把独占性的写锁,则不能再对其加任何的读锁。这些规则如下表所示:
上面的兼容性规则适用于不同进程提出的请求,并不适用于单个进程提出的多个锁请求;若是一个进程对一个文件区间已经加上一把锁,后来该进程又企图在同一个文件取件加上另一把锁,那么新锁会替换掉老锁。
加读锁时文件描述符必须是以读打开的,加写锁时文件描述符必须是写打开的。以下是 fcntl 函数的三种命令:
- F_GETLK:读取锁信息。判断由 flockptr 所描述的锁是否会被另外一把锁所排斥。如果存在一把锁,它阻止创建由 flockptr 所描述的锁,则把该现存锁的信息写到 flockptr 指向的结构中,如果不存在这中情况,则除了将 l_type 设置为 UNLCK 之外,flockptr 所指向结构的其他信息保持不变。
- F_SETLK:设置锁,在锁已经被占用的情况下,马上返回错误。设置由 flockptr 所描述的锁,如果试图建立一把锁(读锁或者写锁),而按上述兼容性规则不能允许,则 fcntl立 即出错返回,此时 errno 设置为 EACCES 或 EAGAIN。此命令也用来消除由 flockpt r说明的锁(l_type 为 UNLOCK)。
- F_SETLKW: 设置锁,如果锁被其他进程占用,则阻塞。这是 F_SETLK 的阻塞版本,如果因为当前在所请求区间的某个部分另一个进程已经有一把锁,因而按兼容性规则由 flockptr 所请求的锁不能被创建,则使调用进程休眠,如果请求创建的锁已经可用或者休眠由信号终端,则该进程被唤醒。
锁的隐含继承和释放
关于记录锁的自动继承和释放有以下三条规则:
- 锁与进程和文件两方面相关:当一个进程终止时,它所建立的锁全部释放;第二:任何时候关闭一个描述符时,则该进程通过这一描述符可以引用的文件上的任何一把锁都释放;则必须执行以下步骤:
-
fd1 = open(pathname, ...); read_lock(fd1,...); fd2 = dup(fd1); close(fd2);
- 由 fork 产生的子进程不继承父进程所设置的锁;
- 在执行 exec 后,新程序可以继承原执行程序的锁;但是,如果对一个文件描述符设置了 close-on-exec 标志,那么当作为exec 的一部分关闭该文件描述符时,对相应文件的所有锁都被释放了。
测试程序:
#include <fcntl.h>
#include "apue.h"
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len);
int lock_test(int fd, int type, off_t offset, int whence, off_t len);
int main(void)
{
int fd, tmp;
pid_t pid;
pid = getpid();
printf("pid: %d\n", pid);
fd = open("lock.txt", O_RDWR);
if(fd < 0)
err_sys("open file error");
tmp = lock_reg(fd, F_SETLK, F_WRLCK, 4, SEEK_SET, 1);
if(tmp < 0)
err_sys("F_SETLK error");
else
printf("F_SETLK success\n");
sleep(3);
tmp = lock_test(fd, F_WRLCK, 3, SEEK_SET, 2);
if(tmp == 0)
printf("not lock\n");
else
printf("locked\n");
close(fd);
exit(0);
}
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_len = len;
lock.l_start = offset;
lock.l_type = type;
lock.l_whence = whence;
return(fcntl(fd, cmd, &lock));
}
int lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_len = len;
lock.l_start = offset;
lock.l_type = type;
lock.l_whence = whence;
if(fcntl(fd, F_GETLK, &lock) < 0)
err_sys("fcntl error");
if(lock.l_type == F_UNLCK)
return(0);
return(lock.l_pid);
}