在APUE中的第12节中提到了记录锁,按照文中提到的示例自己尝试运行了一遍,最后发现结果不是预期的:
/************************************************************************* > File Name: file_lock.c > Author: liuxingen > Mail: [email protected] > Created Time: 2014年07月24日 星期四 21时08分32秒 ************************************************************************/ #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; lock.l_start = offset; lock.l_whence = whence; lock.l_len = len; return fcntl(fd, cmd, &lock); } /* * 测试锁 * @retval: * -1:error * 0:无锁 * >0:锁进程ID */ pid_t test_lock(int fd, int type, off_t offset, int whence, off_t len) { struct flock lock; lock.l_type = type; lock.l_start = offset; lock.l_whence = whence; lock.l_len = len; if(fcntl(fd, F_GETLK, &lock) < 0) { return (pid_t)-1; } if(lock.l_type == F_UNLCK) { return (pid_t)0; } return lock.l_pid; }
/************************************************************************* > File Name: file_lock.h > Author: liuxingen > Mail: [email protected] > Created Time: 2014年08月02日 星期六 21时39分07秒 ************************************************************************/ #include<stdio.h> #include<stdlib.h> #include<sys/types.h> #include<fcntl.h> #include<unistd.h> #include<string.h> #include<errno.h> pid_t test_lock(int fd, int type, off_t offset, int whence, off_t len); int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len); #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) #define is_readlock(fd, offset, whence, len)\ test_lock(fd, F_RDLCK, offset, whence, len) #define is_writelock(fd, offset, whence, len)\ test_lock(fd, F_WRLCK, offset, whence, len)
/************************************************************************* > File Name: test_lock.c > Author: liuxingen > Mail: [email protected] > Created Time: 2014年07月24日 星期四 21时08分32秒 ************************************************************************/ #include "file_lock.h" int rflag = 0, wflag = 0, tflag = 0; void file_lock(int fd) { pid_t pid; if(wflag && write_lock(fd, 0, SEEK_SET, 1) != -1) //独占写锁 { fprintf(stderr, "write_lock success.\n"); }else if(wflag) { fprintf(stderr, "write_lock error:%s\n", strerror(errno)); } if(rflag && read_lock(fd, 0, SEEK_SET, 1) != -1) //共享读锁 { fprintf(stderr, "read_lock success.\n"); }else if(rflag) { fprintf(stderr, "read_lock error:%s\n", strerror(errno)); } if(tflag && (pid = is_writelock(fd, 0, SEEK_SET, 1)) > 0) //测试写锁 { fprintf(stderr, "cannot add write lock(PID %d have add write or read lock on it)\n", pid); }else if(tflag && pid == 0) { fprintf(stderr, "can add write lock\n"); } if(tflag && (pid = is_readlock(fd, 0, SEEK_SET, 1)) > 0) //测试读锁 { fprintf(stderr, "cannot add read lock(PID %d have add write lock on it)\n", pid); }else if(pid == 0) { fprintf(stderr, "can add read lock\n"); } } int main(int argc, char *argv[]) { int fd, opt; while((opt = getopt(argc, argv, "rwt")) != -1) { switch(opt) { case 'r': rflag = 1; //read lock break; case 'w': wflag = 1; //write lock break; case 't': tflag = 1; //test lock break; default: fprintf(stderr, "Usage: %s -r -w -t\n", argv[0]); return 1; } } if((fd = open("/tmp/file_lock", O_RDWR | O_CREAT, S_IRWXU)) == -1) { fprintf(stderr, "creat error:%s\n", strerror(errno)); return 1; } file_lock(fd); close(fd); return 0; }
llxg@remoter:~/station$ ./lock2 -rwt write_lock success. read_lock success. can add write lock can add read lock
从上面输出的结果来看F_WRLCK貌似并不是独占锁了,因为后面的读锁F_RDLCK成功了,更诡异的是我们竟然看到没有任何进程锁住文件.这是为什么呢?
对结果百思不得其解,反复尝试了好几遍,并且检查了代码都木有发现可疑之处,这个时候不得不又重新看书,试图从中找出点思路.在APUE中关于记录锁是这样描述的"记录锁的功能是:一个进程正在读或者修改文件的某个部分时,可疑阻止其他进程修改同一文件区.",当重新读完这句话的时候我突然明白上面demo的输出为什么不符合预期了,因为我是在同一个进程中进行的写锁,读锁,测试锁. shit
下面是一个稍微修改后的demo:
/************************************************************************* > File Name: test_lock.c > Author: liuxingen > Mail: [email protected] > Created Time: 2014年07月24日 星期四 21时08分32秒 ************************************************************************/ #include "file_lock.h" int rflag = 0, wflag = 0, tflag = 0; void file_lock(int fd) { pid_t pid; if(wflag && write_lock(fd, 0, SEEK_SET, 1) != -1) //独占写锁 { fprintf(stderr, "write_lock success.\n"); }else if(wflag) { fprintf(stderr, "write_lock error:%s\n", strerror(errno)); } if(rflag && read_lock(fd, 0, SEEK_SET, 1) != -1) //共享读锁 { fprintf(stderr, "read_lock success.\n"); }else if(rflag) { fprintf(stderr, "read_lock error:%s\n", strerror(errno)); } if(tflag && (pid = is_writelock(fd, 0, SEEK_SET, 1)) > 0) //测试写锁 { fprintf(stderr, "cannot add write lock(PID %d have add write or read lock on it)\n", pid); }else if(tflag && pid == 0) { fprintf(stderr, "can add write lock\n"); } if(tflag && (pid = is_readlock(fd, 0, SEEK_SET, 1)) > 0) //测试读锁 { fprintf(stderr, "cannot add read lock(PID %d have add write lock on it)\n", pid); }else if(pid == 0) { fprintf(stderr, "can add read lock\n"); } } int main(int argc, char *argv[]) { int fd, opt; while((opt = getopt(argc, argv, "rwt")) != -1) { switch(opt) { case 'r': rflag = 1; //read lock break; case 'w': wflag = 1; //write lock break; case 't': tflag = 1; //test lock break; default: fprintf(stderr, "Usage: %s -r -w -t\n", argv[0]); return 1; } } if((fd = open("/tmp/file_lock", O_RDWR | O_CREAT, S_IRWXU)) == -1) { fprintf(stderr, "creat error:%s\n", strerror(errno)); return 1; } file_lock(fd); sleep(15); close(fd); return 0; }输出的结果如下:
lxg@remoter:~/station$ ./lock2 -w write_lock success. 同时另外一个进程也进行同样的操作 lxg@remoter:~/station$ ./lock2 -rw write_lock error:Resource temporarily unavailable read_lock error:Resource temporarily unavailable上面的结果来看完全符合预期,当一个进程调用F_WRLCK锁住了文件区域以后其他的进程就不能获取到锁了.
在APUE中提到"F_GETLK主要用来检测是否有某个已存在锁会妨碍将新锁授予调用进程,如果没有这样的锁,lock所指向的flock结构的l_type成员就会被置成F_UNLCK,否则已存在的锁的信息将会写入lock所指向的flock结构中",我一开始的理解是只要有进程已经用F_SETLK or F_SETLKW锁住了文件区域那么调用F_GETLK就能获取到对应的进程PID,其实这个理解是错误的.F_GETLK是一个能否获得对应类型锁的能力检测,并不是获得已经上锁的进程信息.
lxg@remoter:~/station$ ./lock2 -r read_lock success. lxg@remoter:~/station$ ./lock2 -t cannot add write lock(PID 18900 have add write or read lock on it) can add read lock从上面的输出来看,明明已经有进程F_RDLCK锁住了文件区域但是另外一个进程却可以再次获得读锁.关于记录锁的规则可以参考APUE并自己调用demo进行验证.