记录锁至关于线程同步中读写锁的一种扩展类型,能够用来对有亲缘或无亲缘关系的进程进行文件读与写的同步,经过fcntl函数来执行上锁操做。尽管读写锁也能够经过在共享内存区来进行进程的同步,可是fcntl记录上锁每每更容易使用,且效率更高。函数
记录锁的功能:当一个进程正在读或修改文件的某个部分是,它能够阻止其余进程修改同一文件区。对于这个功能阐述我认为有三点要解释的:测试
记录上锁的POSIX接口函数fcntl以下:spa
/* Do the file control operation described by CMD on FD. The remaining arguments are interpreted depending on CMD. */ int fcntl (int __fd, int __cmd, ...); //根据cmd的不一样有如下三种类型的调用 int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); int fcntl(int fd, int cmd, struct flock *lock);
由函数名称可知fcntl的功能是对文件的控制操做,根据传入不一样的操做类型命令cmd,fcntl会执行不一样的操做,fcnt根据cmd不一样,接收可变的参数。具体有如下五种类型的操做:.net
/* cmd = F_DUPFD,复制一个文件描述符; */ int fcntl(int fd, int cmd); /* cmd = F_GETFD,得到文件描述符标志; cmd = F_SETFD,设置文件描述符标志;arg = 描述符标志的值,目前只定义了一个标志: FD_CLOEXEC int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); */ /* cmd = F_GETFL,得到文件状态标志; cmd = F_SETFL,设置文件状态标志;arg = 状态标志的值 int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); */ /* cmd = F_GETOWN,得到当前接收SIGIO和SIGURG信号的进程ID或进程组ID cmd = F_SETOWN,设置接收SIGIO和SIGURG信号的进程ID或进程组ID;arg = 进程ID或进程组ID int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); */ /* Return value: 对于成功的调用,根据操做类型cmd不一样,有如下几种状况: F_DUPFD 返回新的文件描述符 F_GETFD 返回文件描述符标志 F_GETFL 返回文件状态标志 F_GETOWN 进程ID或进程组ID All other commands 返回0 调用失败, 返回-1,并设置errno。 */
上面四个功能都是fcntl提供的很经常使用的操做,关于记录锁的功能就是fcntl提供的第五个功能,具体使用以下:线程
int fcntl(int fd, int cmd, struct flock *lock); /* cmd = F_GETLK,测试可否创建一把锁 cmd = F_SETLK,设置锁 cmd = F_SETLKW, 阻塞设置一把锁 */ //POSIX只定义fock结构中必须有如下的数据成员,具体实现能够增长 struct flock { short l_type; /* 锁的类型: F_RDLCK, F_WRLCK, F_UNLCK */ short l_whence; /* 加锁的起始位置:SEEK_SET, SEEK_CUR, SEEK_END */ off_t l_start; /* 加锁的起始偏移,相对于l_whence */ off_t l_len; /* 上锁的字节数*/ pid_t l_pid; /* 已经占用锁的PID(只对F_GETLK 命令有效) */ /*...*/ }; //Return value: 前面已经说明;
l F_SETLK:获取(l_type为F_RDLCK或F_WRLCK)或释放由lock指向flock结构所描述的锁,若是没法获取锁时,该函数会当即返回一个EACCESS或EAGAIN错误,而不会阻塞。code
l F_SETLKW:F_SETLKW和F_SETLK的区别是,没法设置锁的时候,调用线程会阻塞到该锁可以受权位置。blog
l F_GETLK:IF_GETLK主要用来检测是否有某个已存在锁会妨碍将新锁授予调用进程,若是没有这样的锁,lock所指向的flock结构的l_type成员就会被置成F_UNLCK,不然已存在的锁的信息将会写入lock所指向的flock结构中继承
这里须要注意的是,用F_GETLK测试可否创建一把锁,而后接着用F_SETLK或F_SETLKW企图创建一把锁,因为这二者不是一个原子操做,因此不能保证两次fcntl之间不会有另一个进程插入并创建一把相关的锁,从而使一开始的测试状况无效。因此通常不但愿上锁时阻塞,会直接经过调用F_SETLK,并对返回结果进行测试,以判断是否成功创建所要求的锁。接口
前面咱们说了记录锁至关于读写锁的一种扩展类型,记录锁和读写锁同样也有两种锁:共享读锁(F_RDLCK)和独占写锁(F_WRLCK)。在使用规则上和读写锁也基本同样:进程
以下表所示:
须要说明的是:上面所阐述的规则只适用于不一样进程提出的锁请求,并不适用于单个进程提出的多个锁请求。即若是一个进程对一个文件区间已经有了一把锁,后来该进程又试图在同一文件区间再加一把锁,那么新锁将会覆盖老锁。
下面进行测试;第一个程序是在同一进程中测试可否在加写锁后,继续加读写锁。第二个程序是在在父进程中加写锁后,而后再子进程中测试可否继续加读写锁。
//调用的函数,在文章末尾贴出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); cout<<lock_test(fd, F_WRLCK, SEEK_SET, 0, 0)<<endl; cout<<lock_test(fd, F_RDLCK, SEEK_SET, 0, 0)<<endl; unlock(fd); return 0; }
执行结果为:
0 0
代表同一进程能够对已加锁的同一文件区间,仍然能得到加锁权限;
//调用的函数,在文章末尾贴出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); if (fork() == 0) { cout<<lock_test(fd, F_WRLCK, SEEK_SET, 0, 0)<<endl; cout<<lock_test(fd, F_RDLCK, SEEK_SET, 0, 0)<<endl; exit(0); } sleep(3); unlock(fd); return 0; }
执行结果为:
24791 24791
代表不一样进程不能对已加写锁的同一文件区间,得到加锁权限;
还有就是:加锁时,该进程必须对该文件有相应的文件访问权限,即加读锁,该文件必须是读打开,加写锁时,该文件必须是写打开。
这里要提到两个概念:记录上锁和文件上锁。
记录上锁:对于UNIX系统而言,“记录”这一词是一种误用,由于UNIX系统内核根本没有使用文件记录这种概念,更适合的术语应该是字节范围锁,由于它锁住的只是文件的一个区域。用粒度来表示被锁住文件的字节数目。对于记录上锁,粒度最大是整个文件。
文件上锁:是记录上锁的一种特殊状况,即记录上锁的粒度是整个文件的大小。
之因此有文件上锁的概念是由于有些UNIX系统支持对整个文件上锁,但没有给文件内的字节范围上锁的能力。
关于记录锁的继承和释放有三条规则,以下:
(1)锁与进程和文件两方面有关,体如今:
对于第一个方面,能够创建以下测试代码:
//调用的函数,在文章末尾贴出 //process 1 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); cout<<"process 1 get write lock..."<<endl; sleep(10); cout<<"process 1 exit..."<<endl; return 0; } //process 2 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); cout<<"process 2 get write lock..."<<endl; unlock(fd); return 0; }
先启动进程1,而后当即启动进程2,执行结果以下:
process 1 get write lock... process 1 exit... process 2 get write lock...
对于第二个方面,能够进行以下测试:
//调用的函数,在文章末尾贴出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); if (fork() == 0) { int fd_1 = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); readw_lock(fd_1); cout<<"child get read lock..."<<endl; sleep(3); close(fd_1); cout<<"close the file descriptor..."<<endl; pause(); } sleep(1); writew_lock(fd); cout<<"parent get write lock..."<<endl; unlock(fd); return 0; }
程序的执行结果以下:
child get read lock... close the file descriptor... parent get write lock...
可见,当关闭文件描述符时,与该文件描述符有关的锁都被释放,一样经过dup拷贝获得的文件描述符也会致使这种状况;
(2)由fork产生的子进程不继承父进程所设置的锁。即对于父进程创建的锁而言,子进程被视为另外一个进程。记录锁自己就是用来同步不一样进程对同一文件区进行操做,若是子进程继承了父进程的锁,那么父子进程就能够同时对同一文件区进行操做,这有违记录锁的规则,因此存在这么一条规则。
下面是测试代码(上面已经用过该代码进行测试):
//调用的函数,在文章末尾贴出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); if (fork() == 0) { cout<<lock_test(fd, F_WRLCK, SEEK_SET, 0, 0)<<endl; cout<<lock_test(fd, F_RDLCK, SEEK_SET, 0, 0)<<endl; exit(0); } sleep(3); unlock(fd); return 0; }
咱们知道在前面已经说过,同一个进程能够重复对同一个文件区间加锁,后加的锁将覆盖前面加的锁。那么再假设若是子进程继承了父进程的锁,那么子进程能够对该锁进行覆盖,那么在子进程内对该锁是否能得到权限的测试应该是能够,但测试结果为:
24791 24791
代表已经进程24791已经占用该锁,因此假设不成立,子进程不会继承父进程的锁;
(3)执行exec后,新程序能够继承原执行程序的锁。可是,若是一个文件描述符设置了close-on-exec标志,在执行exec时,会关闭该文件描述符,因此对应的锁也就被释放了,也就无所谓继承了。
在读写锁中,我曾经测试过Linux 2.6.18中提供的读写锁函数是优先考虑等待读模式占用锁的线程,这种实现的一个很大缺陷就是出现写入线程饿死的状况。 那么在记录锁中是什么样的规则呢,须要说明的是这在POSIX标准中是没有说明的,要看具体实现。
具体进行如下2个方面测试:
测试1:父进程得到对文件的读锁,而后子进程1请求加写锁,随即进入睡眠,而后子进程2请求读锁,看进程2是否可以得到读锁。
//调用的函数,在文章末尾贴出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); readw_lock(fd); //child 1 if (fork() == 0) { cout<<"child 1 try to get write lock..."<<endl; writew_lock(fd); cout<<"child 1 get write lock..."<<endl; unlock(fd); cout<<"child 1 release write lock..."<<endl; exit(0); } //child 2 if (fork() == 0) { sleep(3); cout<<"child 2 try to get read lock..."<<endl; readw_lock(fd); cout<<"child 2 get read lock..."<<endl; unlock(fd); cout<<"child 2 release read lock..."<<endl; exit(0); } sleep(10); unlock(fd); return 0; }
在Linux 2.6.18下执行结果以下:
child 1 try to get write lock... child 2 try to get read lock... child 2 get read lock... child 2 release read lock... child 1 get write lock... child 1 release write lock...
可知在有写入进程等待的状况下,对于读出进程的请求,系统会一直给予的。那么这也就可能致使写入进程饿死的局面。
测试2:父进程得到写入锁,而后子进程1和子进程2分别请求得到写入锁和读写锁,看二者的响应顺序;
//调用的函数,在文章末尾贴出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); //child 1 if (fork() == 0) { sleep(3); cout<<"child 1 try to get write lock..."<<endl; writew_lock(fd); cout<<"child 1 get write lock..."<<endl; unlock(fd); cout<<"child 1 release write lock..."<<endl; exit(0); } //child 2 if (fork() == 0) { cout<<"child 2 try to get read lock..."<<endl; readw_lock(fd); cout<<"child 2 get read lock..."<<endl; unlock(fd); cout<<"child 2 release read lock..."<<endl; exit(0); } sleep(10); unlock(fd); return 0; }
在Linux 2.6.18下执行结果:
child 2 try to get read lock... child 1 try to get write lock... child 2 get read lock... child 2 release read lock... child 1 get write lock... child 1 release write lock...
将上面代码该成child2 sleep 3s,child1不sleep
//调用的函数,在文章末尾贴出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); //child 1 if (fork() == 0) { cout<<"child 1 try to get write lock..."<<endl; writew_lock(fd); cout<<"child 1 get write lock..."<<endl; unlock(fd); cout<<"child 1 release write lock..."<<endl; exit(0); } //child 2 if (fork() == 0) { sleep(3); cout<<"child 2 try to get read lock..."<<endl; readw_lock(fd); cout<<"child 2 get read lock..."<<endl; unlock(fd); cout<<"child 2 release read lock..."<<endl; exit(0); } sleep(10); unlock(fd); return 0; }
在Linux 2.6.18下执行结果以下:
child 1 try to get write lock... child 2 try to get read lock... child 1 get write lock... child 1 release write lock... child 2 get read lock... child 2 release read lock...
由上可知在Linux 2.6.18下,等待的写入锁进程和读出锁进程的优先级由FIFO的请求顺序进程响应。
void lock_init(flock *lock, short type, short whence, off_t start, off_t len) { if (lock == NULL) return; lock->l_type = type; lock->l_whence = whence; lock->l_start = start; lock->l_len = len; } int readw_lock(int fd) { if (fd < 0) { return -1; } struct flock lock; lock_init(&lock, F_RDLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0) { return -1; } return 0; } int writew_lock(int fd) { if (fd < 0) { return -1; } struct flock lock; lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0) { return -1; } return 0; } int unlock(int fd) { if (fd < 0) { return -1; } struct flock lock; lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0) { return -1; } return 0; } pid_t lock_test(int fd, short type, short whence, off_t start, off_t len) { flock lock; lock_init(&lock, type, whence, start, len); if (fcntl(fd, F_GETLK, &lock) == -1) { return -1; } if(lock.l_type == F_UNLCK) return 0; return lock.l_pid; }
Jun 28, 2013 PM16:06 @lab 困呀。。。