翻阅参考资料,你会发现文件锁可以进行很多的分类,最常见的主要有读锁与写锁,前者也叫共享锁,后者也叫排斥锁,值得注意的是,多个读锁之间是不会相互干扰的,多个进程可以在同一时刻对同一个文件加读锁;但是,如果已经有一个进程对该文件加了写锁,那么其他进程则不能对该文件加读锁或者写锁,直到这个进程将写锁释放,因此可以总结为:对于同一个文件而言,它可以同时拥有多个读者,但是在某一时刻,他只能拥有一个写者。
根据内核行为来分,文件锁可以分成劝告锁与强制锁两大类:
劝告锁讲究的是一种协同工作,内核仅负责对文件加锁以及检查文件是否已经上锁等操作,而不亲自去参与文件锁的控制与协调,而这些都需要程序员首先要检查所要访问的文件之前是否已经被其他进程加锁来实现并发控制,劝告锁不仅可以对文件的任一部分加锁,也可以对整个文件加锁。下面是加锁规则:
强制锁则是内核强制使用的一种文件锁,每当有进程违反锁规则,内核将会进行阻止,具体的加锁规则如下:
(1)若一个文件已经加上共享锁,那么其他进程在对这个文件进行写操作时将会被内核阻止;
(2)若一个文件已经加上了排他锁,那么其他进程对这个文件的读取与写操作都将被阻止;
下表总结了进程试图访问已经加有强制锁的文件,进程行为如下:
从上表可以看出,若进程要访问文件的锁类型与要进行的操作存在冲突,那么若操作时在阻塞时进行,则进程将会阻塞;若操作时在非阻塞时进程,则进程将会立即返回EAGIN错误(PS:表示资源临时不可达)。
根据加锁区域范围,可以分成整个文件锁与区域文件锁(记录锁),二者很好区分,前者可以锁定整个文件,而后者则可以锁定文件中的某一区域,甚至是某几个字节。
值得注意的是,在给文件加锁之前,一定要保证文件以相应的访问模式打开,例如要对一个文件加上共享锁,一定要首先按读模式打开文件,若要给文件加上排他锁,则首先要按写模式打开对应文件若想加两种锁,则需要按读写模式打开.
int fcntl(int fd, int cmd, struct flock*lock)
fcntl函数专门用来对文件描述符操作的,具体的操作行为取决于cmd值,与本文文件锁相关的cmd值主要有:
F_GETLK:获取文件锁
F_SETLK:设置文件锁(非阻塞版)
F_SETLKW:设置文件锁(阻塞版)
值得注意的是,调用F_SETLKW命令去设置文件锁的请求不能完成,则进程将会进入休眠状态,直至所要求的锁被释放。其它更多的cmd值可以参考《UNIX环境高级编程》或者”man fcntl”。
lock参数主要是用来实现指定文件锁类型、所锁定的文件范围以及正在锁定文件的进程ID(只是在获取文件锁时才会用到),详细结构如下:
struct flock {
short l_type; /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Starting offset for lock */
off_t l_len; /* Number of bytes to lock */
pid_t l_pid; /* PID of process blocking our lock (F_GETLK only) */
};
其中l_type定义所的类型,F_RDLCK表示共享锁,F_WRLCK表示排他锁,F_UNLCK表示释放掉之前已经建立的锁;l_whence, l_start与l_len共同作用设置所加锁的范围,其中l_whence设置锁的参照起始点,SEEK_SET表示文件开头,SEEK_CUR表示文件当前位置(fseek可以移动文件指针位置),SEEK_END表示文件结尾;l_start与l_whence相结合确定了锁的绝对起始点,l_len则表示从绝对起始点开始需要锁定的字节数,其值可正可负,锁的范围则是[l_start, l_start+l_len-1],若其值为0,则有特殊的含义,表示锁的区域从绝对起始点开始到最大可能的偏移量为止,这种情况可用于锁定整个文件,此时只需将锁的绝对起始点设置为文件开始位置即可。
函数的返回值是:若成功则返回0,否则返回-1.
int flock(int fd, int operation)
相对于fcntl函数,flock显得更加简单,因为所加的锁会影响整个文件,其中operation参数规定了所加锁的类型:
LOCK_SH:表示加共享锁
LOCK_EX:表示排他锁
LOCK_UN:表示释放锁
LOCK_MAND:表示强制锁
1. 锁与进程和文件紧密相连,若进程终止,则有它创建的所有锁将会自动释放掉;若关闭文件描述符,则进程由此描述符引用的文件上的任何锁也将会被释放;
2. 由fork产生的子进程不会继承父进程的文件锁;
3. 在执行exec之后,新程序可以继承原来程序的文件锁。
跟锁有关的封装函数(来自于《高级UNIX环境编程》如下测试例子:
/***************************************file_lock.h*******************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
int lock_reg(int, int, int, off_t, int, off_t); //register lock
pid_t lock_test(int, int, off_t, int, off_t); //test lockable
//set lock
#define read_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len))
#define readw_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLKW, F_RDLCK, (offset), (whence), (len))
#define write_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len))
#define writew_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLKW, F_WRLCK, (offset), (whence), (len))
#define un_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_UNLCK, (offset), (whence), (len))
//Test lock
#define read_lock_pid(fd, offset, whence, len) \
lock_test((fd), F_RDLCK, (offset), (whence), (len))
#define write_lock_pid(fd, offset, whence, len) \
lock_test((fd), F_WRLCK, (offset), (whence), (len))
#define is_read_lockable(fd, offset, whence, len) \
(read_lock_pid == 0)
#define is_write_lockable(fd, offset, whence, len) \
(write_lock_pid == 0)
/******************************************file_lock.c****************************************/
#include "file_lock.h"
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type; /* F_RDLCK, F_WRLCK, F_UNLCK */
lock.l_start = offset; /* byte offset, relative to l_whence */
lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
lock.l_len = len; /* #bytes (0 means to EOF) */
return(fcntl(fd, cmd, &lock));
}
pid_t lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type; /* F_RDLCK or F_WRLCK */
lock.l_start = offset; /* byte offset, relative to l_whence */
lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
lock.l_len = len; /* #bytes (0 means to EOF) */
if (fcntl(fd, F_GETLK, &lock) < 0)
{
printf("fcntl error for %s.\n", strerror(errno));
return (-1);
}
if (lock.l_type == F_UNLCK)
{
return(0); /* false, region isn't locked by another proc */
}
return(lock.l_pid); /* true, return pid of lock owner */
}
/*****************************************file_lock.c*****************************************/
#include "file_lock.h"
static void lock_set(int fd, int type) ;
int main(int argc, char **argv)
{
int fd ;
//First open file and choose right mode by lock type
fd = open("/tmp/hello", O_RDWR | O_CREAT, 0666) ;
if (fd < 0)
{
printf("open error ! \n") ;
exit(1) ;
}
//lock
printf("press ENTER to add lock:\n");
getchar() ;
lock_set(fd, F_WRLCK) ;
printf("press ENTER and exit, lock release:\n");
getchar() ;
//release lock
lock_set(fd, F_UNLCK) ; //useless code, lock release if close fd
if (close(fd) < 0)
{
printf("\n close file error ! \n") ;
exit(1) ;
}
return 0 ;
}
void lock_set(int fd, int type)
{
pid_t read_lock_id = 0;
pid_t write_lock_id = 0;
while (1)
{
//set lock according to lock type
switch (type)
{
case F_RDLCK:
if (read_lock(fd, 0, SEEK_SET, 0) == 0)
{
printf("read lock set by %d \n", getpid());
return;
}
break;
case F_WRLCK:
if (write_lock(fd, 0, SEEK_SET, 0) == 0)
{
printf("write lock set by %d \n", getpid());
return;
}
break;
case F_UNLCK:
if (un_lock(fd, 0, SEEK_SET, 0) == 0)
{
printf("release lock by %d \n", getpid());
return;
}
break;
}
//test lock owner
if (type == F_RDLCK)
{
if ((read_lock_id = read_lock_pid(fd, 0, SEEK_SET, 0)) != 0)
{
printf("read lock already set by %d \n", read_lock_id);
}
}
else if (type == F_WRLCK)
{
if ((write_lock_id = read_lock_pid(fd, 0, SEEK_SET, 0)) != 0)
{
printf("write lock already set by %d \n", write_lock_id);
}
}
}
}
/****************************************Makefile*********************************************/
all: file_lock_test
CC = gcc
file_lock_test:
$(CC) file_syn.c file_lock.c -o file_lock_test
clean:
rm -rf *.o file_lock_test