原文:http://www.cnblogs.com/zhenjing/archive/2011/07/04/filelock.html
因项目需要,自行设计一套通用的文件读写锁,要求该机制能用于本地文件系统和NFS文件系统。
内核中有3个数据结构和文件直接相关,分别是:file descriptor table, file table and i-node table。其中file descriptor table是进程私有的;file table和inode table是独立于进程的。进程内部的文件描述符fd只在该进程有才有意义。File table可在进程内共享,也可在进程间共享。I-node table则在整个系统中共享。
文件描述符fd可由Open,dup和fork等操作创建;file table只能有open创建;i-node table在创建真实文件是创建。
摘自Apue-3.12
摘自Apue-14.3
摘自TLPI-5.4
对TLPI-5.4的图作如下说明:
1) A中的fd 1和20共享file table记录,可认为fd 20复制于fd 1(可也反过来)
2) A和B的fd 2共享file table记录,可认为B由A fork产生,B重定向fd 0和1。
3) File table中的0和86指向相同的文件,这意味着0和86由open相同的文件。
理解这个3个数据结构的关系大体可知道linux是如何管理文件。Stat和fcntl可获取或者修改相关的文件信息,结合这两个接口将有助于理解这3个文件结构。
文件记录锁位于i-node table这个结构中,使用PID作为锁拥有者的标识。这使其拥有如下特点:
1) 记录锁采用(PID, start,end)三元组作为锁标识,一个文件可拥有多个记录锁,同一区域只允许有一个记录锁。
2) 当进程终止(正常/不正常),该进程拥有的所有记录锁都将释放。
3) 同一个进程中,指向同一文件(i-node)的fd都可以操作该文件上的记录锁:如释放、修改等。显式调用F_UNLCK和close(fd)都将释放锁,close将释放整个文件中该进程拥有的所有记录锁。
4) 记录锁不被fork的子进程继承(PID不同)。
5) 记录锁的类型转换、改变锁范围等操作均为原子的。
6) 未设置FD_CLOEXEC时,记录锁将被exec后的进程继承(PID相同)。
7) 记录锁对文件打开mode有要求:加读锁要求fd有读权限;加写锁要求fd有写权限。
fcntl的使用示例和测试代码:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int
lock_reg( int , int , int , off_t, int , off_t);
#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))
pid_t lock_test(
int
,
int
, off_t,
int
, off_t);
#define is_read_lockable(fd, offset, whence, len) \
(lock_test((fd), F_RDLCK, (offset), (whence), (len)) == 0)
#define is_write_lockable(fd, offset, whence, len) \
(lock_test((fd), F_WRLCK, (offset), (whence), (len)) == 0)
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));
}
/* Note: lock_test always success with the obtained lock process */
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)
perror
(
"fcntl error"
);
/* printf("F_RDLCK=%d, F_WRLCK=%d, F_UNLCK=%d\n", F_RDLCK, F_WRLCK, F_UNLCK); */
printf
(
" l_type=%d, l_start=%lu, l_where=%d, l_len=%lu, l_pid=%d\n"
, lock.l_type, lock.l_start, lock.l_whence, lock.l_len, lock.l_type);
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 */
}
/* ======================================================== */
void
Usage( const char * program)
{
fprintf
(stderr,
"Usage: %s read/read2/write/rd-wr/wr-rd/fork/dup file time\n"
, program);
exit
(-1);
}
int
main( int argc, char *argv[])
{
if
(argc < 3){
Usage(argv[0]);
}
int
interval = 10;
if
(argc > 3) {
interval =
atoi
(argv[3]);
}
printf
(
"PID=%d\n"
, getpid());
char
* path = argv[2];
if
(
strcmp
(argv[1],
"read"
) == 0 ) {
int
fd = open(path, O_RDONLY);
if
(fd == -1) {
perror
(
"open"
);
exit
(0);
}
printf
(
"read lock try\n"
);
/* read lock entire file */
if
( read_lock(fd, 0, SEEK_SET, 0) < 0){
perror
(
"read_lock: "
);
printf
(
"Write Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
exit
(0);
}
printf
(
"read lock success.\n"
);
printf
(
"Read Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
sleep(interval);
exit
(0);
}
else
if ( strcmp (argv[1], "read2" ) == 0 ){
int
fd = open(path, O_RDONLY);
if
(fd == -1) {
perror
(
"open"
);
exit
(0);
}
printf
(
"read lock try\n"
);
/* read lock entire file */
if
( read_lock(fd, 0, SEEK_SET, 0) < 0){
perror
(
"read_lock: "
);
printf
(
"Write Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
exit
(0);
}
printf
(
"read lock success.\n"
);
printf
(
"Read Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
if
( read_lock(fd, 0, SEEK_SET, 1) < 0){
perror
(
"read_lock: "
);
printf
(
"Write Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
exit
(0);
}
printf
(
"read lock 2 success.\n"
);
sleep(interval);
exit
(0);
}
else
if ( strcmp (argv[1], "write" ) == 0) {
int
fd = open(path, O_WRONLY );
printf
(
"file: %s, fd=%d\n"
, path, fd);
if
(fd == -1) {
perror
(
"open"
);
exit
(0);
}
printf
(
"write lock try\n"
);
/* write lock entire file */
if
( write_lock(fd, 0, SEEK_SET, 0) < 0){
perror
(
"write_lock: "
);
printf
(
"Write Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
printf
(
"Read Lock by pid=%d\n"
, lock_test(fd, F_RDLCK, 0, SEEK_SET, 0 ));
exit
(0);
}
printf
(
"write lock success.\n"
);
printf
(
"Write Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
int
ret = write(fd, path, strlen (path) );
if
(ret !=
strlen
(path)){
perror
(
"write"
);
}
sleep(interval);
close(fd);
exit
(0);
}
else
if ( strcmp (argv[1], "rd-wr" ) == 0){
int
fd = open(path, O_RDWR); /* O_RDWR */
if
(fd == -1) {
perror
(
"open"
);
exit
(0);
}
/* read lock entire file */
if
( read_lock(fd, 0, SEEK_SET, 0) < 0){
perror
(
"read_lock: "
);
printf
(
"Write Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
exit
(0);
}
printf
(
"read lock success.\n"
);
printf
(
"Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
/* if( un_lock(fd, 0, SEEK_SET, 0) < 0){
perror("un-lock:");
} */
if
( write_lock(fd, 0, SEEK_SET, 1) < 0){
perror
(
"write_lock: "
);
printf
(
"Write Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
exit
(0);
}
printf
(
"write lock success.\n"
);
printf
(
"Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
sleep(interval);
exit
(0);
}
else
if ( strcmp (argv[1], "wr-rd" ) == 0){
int
fd = open(path, O_RDWR); /* O_RDWR */
if
(fd == -1) {
perror
(
"open"
);
exit
(0);
}
/* write lock entire file */
if
( write_lock(fd, 0, SEEK_SET, 0) < 0){
perror
(
"write_lock: "
);
printf
(
"Write Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
exit
(0);
}
printf
(
"write lock success.\n"
);
printf
(
"Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
/* if( un_lock(fd, 0, SEEK_SET, 0) < 0){
perror("un-lock:");
} */
if
( read_lock(fd, 0, SEEK_SET, 0) < 0){
perror
(
"read_lock: "
);
printf
(
"Write Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
exit
(0);
}
printf
(
"read lock success.\n"
);
printf
(
"Lock by pid=%d\n"
, lock_test(fd, F_WRLCK, 0, SEEK_SET, 0 ));
sleep(interval);
exit
(0);
}
else
if ( strcmp (argv[1], "fork" ) == 0){
int
fd = open(path, O_RDWR);
|