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.
因为,在父进程中,设置了写互斥锁,所以子进程试图读共享锁时,返回失败

//====================================================================================================================

文件锁定flock结构
linux下的文件锁定有几种方式,在这里主要说fcntl系统调用实现的锁定
fcntl的原型我们可以在头文件中看到是这样定义的

#include <unistd.h>
         #include <fcntl.h>
         int fcntl(int fd, int cmd);
         int fcntl(int fd, int cmd, long arg);
         int fcntl(int fd, int cmd, struct flock *lock);

在这里我们主要用的是第三种型式即:

int fcntl(int fd, int cmd, struct flock *lock);

flock的结构如下

/usr/include/bits/types.h
struct flock
    {
      short int l_type;    
      short int l_whence;
#ifndef __USE_FILE_OFFSET64
      __off_t l_start;     
      __off_t l_len;   
#else
      __off64_t l_start;   
      __off64_t l_len;     
#endif
      __pid_t l_pid;   
    };

结构体中l_type 是文件锁定的类型 有F_RDLCK共享性读锁定,F_WRLCK独占性写锁定和F_UNLCK释放锁定成员 l_whence 和lseek类似,有几个可选的参数 SEEK_SET 文件头 SEEK_CUR当前位置SEEK_END文件末尾 该字段设定锁定的区域的启始地址区域的长度 l_len 也就是锁定的长度 若该值为0则表示锁定的区域从其起点开始(由l_start和l_whence决定)开始直至最大可能位置为址! 该值不能在文件的起始位置之前开始或越过该超始位置。
为了锁定整个文件,通常的做法是将l_start说明为0,l_whence说明为SEEK_SET,l_len说明为0
小试牛刀:程序只作演示测试,并没有作太多的错误处理

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
void die(char *s)
{
fprintf(stderr,s);
exit(1);
}
int main(void)
{
FILE *fp;
int fd;
struct flock lock;
fp = fopen("a.txt","w");
if (!fp)
    die("open file error\n");
fd = fileno(fp);

lock.l_type=F_WRLCK;
lock.l_whence=SEEK_SET;
lock.l_start=0;
lock.l_len=0;
if(fcntl(fd,F_SETLK,&lock)<0)
{
    die("fcntl set error\n");
}
while(1) pause();
}