Chinaunix首页 | 论坛 | 博客

fx

  • 博客访问: 1377026
  • 博文数量: 115
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 3964
  • 用 户 组: 普通用户
  • 注册时间: 2013-05-02 14:36
文章分类
文章存档

2022年(2)

2019年(2)

2018年(10)

2017年(1)

2016年(50)

2015年(12)

2014年(9)

2013年(29)

分类: C/C++

2013-05-08 19:29:36

对于进程间对同一文件访问的同步问题。fcntl提供了一种记录锁的方法

int fcntl(int filedes,int cmd,.../*arg*/)

对于记录锁,cmd是F_GETLK ,F_SETLK和 F_SETLKW。第三个参数是一个指向flock结构的指针
flock依赖于具体的实现,但它至少包含以下成员
struct flock{
short l_type; /* F_RDLCK  F_WRLCK  F_UNLCK 中的一个*/
off_t l_start; /*需要加锁或解锁的第一个位置的相对位置(相对于 l_whence的位置)*/
short l_whence; /*SEEK_SET(文件头) ,SEEK_CUR(当前文件位置),SEEK_END(文件尾) 中的一个*/
off_t l_len; /*要加锁或解锁的长度*/
pid_t l_pid; /*持有锁的进程(仅有F_GETLK返回,后面会有解释)*/
}


对于flock结构我们需要做一些说明。
l_type成员的取值:
F_RDLCK: 共享锁(或读锁),可以有多个进程拥有文件上同一区域的共享锁,只要任意进程拥有一把共享锁,那么就
没有进程可以在获得该区域上的独占锁。为了获得共享锁,文件需以读或读写方式打开
F_WRLCK:  独占锁(写锁),只有一个进程可以在文件的某个区域获得一把独占锁。一旦一个进程拥有了一个区域的独占锁。
任何其他进程都无法在改区域上再获得任何锁,为了获得一把独占锁,文件需以写或读写方式打开
F_UNLCK: 解锁,用来清除锁。
以上为不同锁之间的兼容性。
注意:F_UNLCK 他只能接触自己加的锁,无法解除别的进程在某个区域加的锁。

l_whence和l_start的取值:
这两个成员共同决定欲加锁或解锁的区域的起始位置。比如l_whence=SEEK_SET  l_start=5
那么加锁区域从距文件头五个位置开始。  


l_len的取值:
l_len决定了加锁或解锁区域的长度,需要注意的是  如l_len=0.则加锁区域从加锁起始位置一直到文件尾,并且以后文件中添加的数据都在锁的范围内



现在我们说明三个命令的作用


F_GETLK:
它用于获取filedes(第一个参数)打开的文件的锁的信息,调用进程吧自己想创建的锁的类型信息写到flock中再传给fcntl函数,则使用F_GETLK命令
的fcntl就会返回阻止获取锁的任何信息(返回信息会覆盖flock参数中的值),如果没有阻止则只将flock中的l_type设置为F_UNLCK,其他值不变(注意它不会尝试去锁定文件,相当于是测试的意思)。
举个例子,比如我想查看当前某个区域的锁状态,我把读锁写入flock参数再传入fcntl函数调用(即测试这个区域还能不能加读锁,但只是测试,不会真的加读锁)
如果这个区域本来有写锁存在根据上面介绍的兼容性,则不能再加读锁(即阻止调用加读锁),那么fcntl中的参数flock的值就会被阻止这个读锁的锁的信息覆盖(这里就是写锁的信息会被写入flock,覆盖掉原来读锁的信息)


从上面的信息我们就可以有两种测试方法。上面我们说过l_pid仅有F_GETLCK命令返回。那么比如我现在是测试在这个区域上能不能加读锁
那么第一种方法:我把l_pid赋值为 -1(没有进程的pid为-1).然后fcntl返回后我在查看flock中的l_pid是不是等于-1.不是就说明flock被覆盖了
就说明当前区域不能加读锁
第二中方法: 测试l_type  如果是F_UNLCK就说明测试读锁成功。如果是其他值,就说明flock被覆盖了。
说明当前区域不能加读锁。
下面的测试代码。我们将用第一种方法。


F_SETLK:
设置有flcok所描述的锁。如果该区域不能加期望的锁(对照上面说的兼容性)。那么fcntl出错返回。
若l_type为F_UNLCK则清楚本进程在该区域所加的锁。
注意 改命令比不使用 l_pid成员


F_SETLKW:
这是F_SETLK命令的阻塞版本。当改区域不能加期望的锁时。则该进程进入休眠。当请求的锁被允许添加或者休眠由信号中断。则进程被唤醒



下面我们来写几个测试程序来理解上面的几个命令的作用


我们用a 程序创建一个文件然后写入五十个数据。然后在10-20区域上设置读锁 40-50区域上设置写锁。
用   b 进程 每10个字节( 五十个字节分五次测试)做一次能否加 读锁 和能否加写锁的测试。
 
 a进程代码
  1 #include
  2 #include
  3 #include
  4 #include
  5 int main(void){
  6         int fd;
  /*创建文件,并以读写打开*/
  7         fd=open("test",O_RDWR | O_CREAT,0666); 
  8         if(fd==-1){
  9                 perror("open error");
 10                 exit(1);
 11         }
 12 
 13         int i;
  /*写入50个数据到文件中*/
 14         for(i=0;i<50;i++){
 15                 if(1!=write(fd,"a",1)){
 16                         perror("write error");
 17                         exit(1);
 18                 }
 19         }
 20 
 21         struct flock flock1,flock2;
 22 /*锁 1  用来在10-20区域设置读锁*/
 23         flock1.l_type=F_RDLCK;
 24         flock1.l_start=10;
 25         flock1.l_whence=SEEK_SET;
 26         flock1.l_len=10;
 27 
 28 /*锁 2 用来在40-50区域上设置写锁*/
 29         flock2.l_type=F_WRLCK;
 30         flock2.l_start=40;
 31         flock2.l_whence=SEEK_SET;
 32         flock2.l_len=10;
 33 /*调用fcntl 来设置锁*/
 34         if(fcntl(fd,F_SETLK,&flock1)==-1 || fcntl(fd,F_SETLK,&flock2)==-1){
 35                 perror("fcntl error");
 36                 exit(1);
 37         }
 38
 39         sleep(10);
 40         exit(0);
 41 
 42 }


注意: 在a进程中 我们要让a 退出前 睡眠一会。这样好让b进程可以有时间运行。如果a进程在b进程运行之前结束了。
那么a 进程中的打开文件就会关闭,自然所加的锁也就被释放了,那么会导致测试任何加锁都会成功。


b进程代码:
 1 #include
  2 #include
  3 #include
  4 #include
  5 
  6 #define FILE_SIZE 50
  7 
  8 int main(void){
  9         int fd;
 10         struct flock test_flock;
 11 
 12         int start_address=0;
 13         int region_size=10;
 14 
 15         fd=open("test",O_RDWR);
 16 /* 下面的循环从文件头开始 每10个字节测试一次 能否加读锁和写锁*/
 17         while(start_address+region_size<=FILE_SIZE){
 18                 /*测试能否加读锁*/
  test_flock.l_type=F_RDLCK;
 19                 test_flock.l_start=start_address;
 20                 test_flock.l_whence=SEEK_SET;
 21                 test_flock.l_len=region_size;
 22                 test_flock.l_pid=-1;
 23 
 24                 fcntl(fd,F_GETLK,&test_flock);
 25                /*如果l_pid改变,说明test_flock被覆盖了。即不能加锁测试的锁*/
  if(test_flock.l_pid!=-1){
 26                         printf("can't get read_lock form %d to %d\n",
 27                                 test_flock.l_start,
 28                                 test_flock.l_start+test_flock.l_len);
 29                 }else{
 30                         printf("test read_lock from %d to %d succeed\n",
 31                                 test_flock.l_start,
 32                                 test_flock.l_start+test_flock.l_len);
 33                 }
 34 
 35 /*测试能否写锁*/
 36                 test_flock.l_type=F_WRLCK;
 37                 test_flock.l_start=start_address;
 38                 test_flock.l_whence=SEEK_SET;
 39                 test_flock.l_len=region_size;
 40                 test_flock.l_pid=-1;
 41 
 42                 fcntl(fd,F_GETLK,&test_flock);
 43                 if(test_flock.l_pid!=-1){
 44                         printf("can't get write_lock from %d to %d\n",
 45                                 test_flock.l_start,
 46                                 test_flock.l_start+test_flock.l_len);
 47                 }else{
 48                         printf("test write_lock form %d to %d succeed\n",
 49                                 test_flock.l_start,
 50                                 test_flock.l_start+test_flock.l_len);
 51                 }
 52 /*测试完一个区域后 开始测试下一个区域*/
 53                 start_address += region_size;
 54         }
 55 
 56         exit(0);
 57 
 58 }
让a进程后台运行,然后运行b进程输出如下:
  feng@ubuntu:~/learn_linux_c_second/chapter_14$ ./a &
[1] 4906
feng@ubuntu:~/learn_linux_c_second/chapter_14$ ./b
test read_lock from 0 to 10 succeed
test write_lock form 0 to 10 succeed
test read_lock from 10 to 20 succeed
can't get write_lock from 10 to 20
test read_lock from 20 to 30 succeed
test write_lock form 20 to 30 succeed
test read_lock from 30 to 40 succeed
test write_lock form 30 to 40 succeed
can't get read_lock form 40 to 50
can't get write_lock from 40 to 50


从输出我们不难看出没加锁的区域,测试读锁,写锁都成成功。但是10-20区域被a 加了读锁。所以b测试读锁成功了
测试写锁出错。
40-50区域被a添加了写锁,锁以b进程测试读锁写锁都失败。
这也 验证了我们上面所说的锁的兼容性。


那么说了那么多读写锁有什么用呢,他可以使两个进程协作运行,就是说比如a进程现在正在写一个文件的某个区域,那么a进程没写完
之前就不能有另一个进程去读,不然会造成数据错乱,同样一个进程没完读某个区域是就不能有别的进程去写这个区域,不然也会造成错乱
那么我们就可以用记录锁来协同这两个进程。
a进程想 写某个区域的时候就先加写锁,然后写完以后再解锁。在未解锁之前如果别的进程想读或写这个区域,那么就要先加读锁或写锁
如果加锁不能成功就说明有别的进程在该区域持有另一把互斥锁。


最后需要注意的一点是这里说的记录所是建议锁,就是说只用这个锁的前提的两个(或多个)进程都是合作进程,就是他们
都遵循规则,在写或读之前,先加锁。看能不能成功,能加锁说明现在能执行他要求的操作,加锁失败则说明不能执行他现在要求的操作
也就是说。比如 a进程现在在某区域加了一个写锁,b进程并不调用fcntl()执行加锁,而是直接去改或读这个区域的数据,那么些事可以读写成功的。(只是可能会造成数据错乱而已)
阅读(1857) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~