1.简介:
做个假设,如果有多个进程,共同编辑一个文件,那个这个文件安最后的结果是什么?在普通的Unix环境下,并没有限制多个进程共同读写一个文件。但是,如果这种情况出现在数据库中,怎么办。数据库要严格限制数据的一致性。
记录锁(Record locking)是用来描述一个进程限制其他进程来修改其在文件读写部位数据的概念。其实记录(Record)这个概念并不准确,因为在Unix下,任何文件只是字节流。
记录锁的实现方式有多种,早期的Berkeley系列仅仅用flock方法,这个方法锁住整个文件,不是一个区域。System V Release 3通过fcntl方法,增加了记录锁的概念。这个方法提供达到整个文件安,小道一个字节的记录锁。flock建立在fcntl之上,提供了一个简单的接口。
2.fcntl函数原型:
#include <fcntl.h>
int fcntl(int filedes, int cmd, ... /* struct flock *flockptr */ );
Returns: depends on cmd if OK (see following), 1 on error
fileds是要锁定的文件描述符
对于cmd参数,可以使用的有: F_GETLK, F_SETLK, or F_SETLKW
第三个参数(flockptr),指向一个flock结构指针,flock的结构如下:
struct flock
{
short l_type;/*F_RDLCK, F_WRLCK, or F_UNLCK*/
off_t l_start;/*相对于l_whence的偏移值,字节为单位*/
short l_whence;/*从哪里开始:SEEK_SET, SEEK_CUR, or SEEK_END*/
off_t l_len;/*长度, 字节为单位; 0 意味着缩到文件结尾*/
pid_t l_pid;/*returned with F_GETLK*/
};
结构体描述
锁类型: F_RDLCK(读共享锁), F_WRLCK(写互斥锁),和F_UNLCK(对一个区域解锁)
锁开始: 锁位置(l_whence),相对于l_whence要锁或者解锁的区域开始位置(l_start)
锁长度: 要锁的长度,字节计数(l_len)
锁拥有者:记录锁的拥有进程ID,这个进程可以阻塞当前进程,仅F_GETLK形式返回
3.对于锁区域要注意的几点
3.1 锁可以开始或者超过文件当前结束位置,但是不可以开始或者超过文件的开始位置
3.2 如果l_len为0,意味着锁的区域为可以到达的最大文件偏移位置。这个类型,可以让我们锁住一个文件的任意开始位置,结束的区域可以到达任意的文件结尾,并且以append方式追加文件时,也会同样上锁。
3.3 如果要锁住整个文件,设置l_start 和 l_whence为文件的开始位置(l_start为0 l_whence 为 SEEK_SET ),并且l_len为0。
3.4 如果有多个读共享锁(l_type of F_RDLCK),其他的读共享锁可以接受,但是写互斥锁(type ofF_WRLCK)拒绝
3.5 如果有一个写互斥锁(type ofF_WRLCK),其他的读共享锁(l_type of F_RDLCK)拒绝,其他的写互斥锁拒绝。
3.6 如果要取得读锁,这个文件描述符必须被打开可以去读;如果要或者写锁,这个文件的描述符必须可以被打开可以去写。
4.fcntl的cmd参数
F_GETLK:决定以flockptr描述的锁,是否被其他的锁阻塞。
F_SETLK :设置锁。
F_SETLKW:对应着F_GETLK的可以阻塞的版本。w意味着wait
5.一个去写锁的例子
#include<stdio.h>
#include<fcntl.h>
#define read_lock(fd,offset,whence,len) \
lock_register((fd),F_SETLK,F_RDLCK,(offset),(whence),(len))
#define readw_lock(fd,offset,whence,len) \
lock_register((fd),F_SETLKW,F_RDLCK,(offset),(whence),(len))
#define write_lock(fd,offset,whence,len) \
lock_register((fd),F_SETLK,F_WRLCK,(offset),(whence),(len))
#define writew_lock(fd,offset,whence,len) \
lock_register((fd),F_SETLKW,F_WRLCK,(offset),(whence),(len))
#define un_lock(fd, offset, whence, len) \
lock_register((fd), F_SETLK, F_UNLCK, (offset), (whence), (len))
int lock_register(int fd,int cmd,int type,off_t offset,int whence,off_t len);
int main(int argc, char * argv[])
{
int fd ;
int val;
int val1;
pid_t pid;
fd= open("./tmp/truncate.c",O_RDWR);
pid = fork();
if(pid < 0)/*fork error,and exit.*/
{
printf("fork error.\n");
exit(1);
}
if(pid == 0)/*In the child, try read lock.*/
{
printf("In the child try to retain read lock ");
val1 = read_lock(fd,0,SEEK_SET,0);
if(val ==0)
{
printf("success,pid is %d.\n",getpid());
}
else
{
printf("failed,pid is %d.\n",getpid());
}
}
else
{/*In the parent,sets write lock to prevent others to read/write*/
printf("Parent sets write lock,pid is %d.\n",getpid());
val = write_lock(fd,0,SEEK_SET,0);
sleep(1);
}
return 0;
}
/*register read/write lock*/
int lock_register(int fd,int cmd,int type,off_t offset,int whence,off_t len)
{
int val = 0;
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
val = fcntl(fd,cmd,&lock);
}
Fedora14 执行结果:
Parent sets write lock,pid is 2792.
In the child try to retain read lock failed,pid is 2793.
因为,在父进程中,设置了写互斥锁,所以子进程试图读共享锁时,返回失败。