文件锁

Posted 韩搏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了文件锁相关的知识,希望对你有一定的参考价值。

      文件锁定是多用户、多任务操作系统中一个非常重要的组成部分。程序经常需要共享数据,而这通常是通过文件

来实现的。因此,对于这些程序来说,建立某种控制文件的方式就非常重要了。只有这样,文件才可以通过一种安全

的方式更新,或者说,当一个程序正在对文件进行写操作时,文件就会进入一个暂时状态在这个状态下,如果另外

一个程序尝试读这个文件,它就会自动停下来等待这个状态的结束。

      Linux提供了多种特性来实现文件锁。其中最简单的方法就是以原子操作方式创建锁文件,所谓"原子操作"就是

在创建锁文件时,系统将不允许其他的事情发生。这就给程序提供了一种方式来确保它所创建是文件是唯一的,而且

这个文件不可能被其它程序在同一时刻创建。

第二种方法更高级一些,它允许程序锁定文件的一部分,从而可以独享对这一部分内容的访问。有这两种不同的方式

可以实现第二种形式的文件锁定。我们将只对其中的一种做详细介绍,因为两种方式非常相似——第二种方式不过是

程序接口稍微不同而已。

一、这些锁文件通常都被放置在一个特定位置,并带有一个与控制资源相关的文件名。例如,当一个调制解调器正在

被使用时,Linux通常会在/var/spool目录下创建一个锁文件。

注意:锁文件仅仅只是充当一个指示器的角色,程序间需要通过相互协作来使用它们。

二、区域锁定

      用创建锁文件的方法来控制对诸如串行口或者不经常访问的文件之类的资源的独占式访问,是一个不错的选择,

但它并不适用于访问大型的共享文件。假设你有一个大文件,它有一个程序写入数据,但却有许多不同的程序同时

对这个文件进行更新。当一个程序负责记录长期以来连续收集到的数据,而其他一些程序将一直不停地运行,所以

它们需要一些协调方法来提供对一个文件的并发访问。可以通过锁定文件区域的方法来解决这个问题,文件中的某

个特定部分被锁定了,但是其他程序可以访问这个文件中的其它部分。这被称为文件段锁定或文件区域锁定。Linux

提供了至少两种方式来实现这一功能:它们使用不同的底层实现,因此决不要混合使用这两种类型的调用,而应坚

持使用其中的一种。

三、文件锁功能函数

      int fcntl(int fileds, int command, struct flock *flock_structure);

      fcntl对一个打开的文件描述符进行操作,并根据command参数的设置完成不同的任务。

      command参数:F_GETLK、F_SETLK、F_SETLKW;

      flock结构体:short l_type、short l_whence、off_t l_shart、off_t l_len、pid_t l_pid。

      1、F_GETLK:用于获取files(第一个参数)打开的文件的锁信息。它不会尝试去锁定文件。调用进程把自己想创

          建的锁类型信息传递给fcntl,使用F_GETLK命令的fcntl就会返回将会阻止获取锁的任何信息。

          short l_type:如果是共享(只读)锁则取值为F_RDLCK,如果是独占(写)锁则取值为F_WRLCK

          short l_whence:SEEK_SET、SEEK_CUR、SEEK_END中的一个

          off_t l_shart:感兴趣的文件区域的第一个字节的相对位置

          off_t l_len:感兴趣的文件区域的字节数

          pid_t l_pid:持有锁的进程的标识符

               进程可能使用F_GETLK调用来查看文件中某个区域的当前锁状态。它应该设置flock结构体来表明它需要

          的锁类型,并定义它感兴趣的文件区域。fcntl调用如果成功就返回非-1的值。如果文件已被锁定从而阻止锁

          请求成功执行,fcntl会用相关信息覆盖flock结构。如果锁请求可以成功执行,flock结构将保持不变。如果

          F_GETLK调用无法获得信息,它将返回-1表明失败。如果F_GETLK调用成功,调用程序就必须检查flock结

         构的内容来判断其是否被修改过。因为l_pid的值被设置成持有锁的进程(若有的话)的标识符,多以通过检查这

         个字段就可以很方便地判断出flock结构是否被修改过。

      2、F_SETLK:对files指向的文件的某个区域加锁或解锁。flock结构中使用的值(与F_GETLK命令中用到的不同之

         处)如下:

         l_type:如果是只读或共享锁则取值为F_RDLCK,如果是独占或写锁则取值为F_WRLCK,如果是解锁则取值

                      为F_UNLK

         l_pid:不使用

              与F_GETLK一样,要加锁的区域由flock结构中的l_start、l_whence和l_en的值定义。如果加锁成功,fcntl

         将返回一个非-1的值;如果失败,则返回-1.这个函数总是立刻返回。

      3、F_SETLKW:与介绍的F_SETLK作用相同,但在无法获取锁时,这个调用将等待直到可以为止。一旦这个调用

          开始等待,只有在可以获取锁或收到一个信号时它才会返回。

      程序对某个文件拥有的所有锁都将在响应的文件描述符被关闭时自动清除。在程序结束时也会自动清除各种锁。

四、锁定状态下的读写操作

      当对文件区域加锁之后,必须使用底层的read和write调用来访问文件中的数据,而不要使用更高级的fread和

fwrite调用,这是因为fread和fwrite会对读写的数据进行缓存,所以执行一次fread调用来读取文件中的头100个字节可

能(事实上,是几乎肯定如此)会读取超过100个字节的数据,并将多余的数据在函数库中进行缓存。如果程序再次使

用fread来读取下100个字节的数据,它实际上将读取已缓存在函数库中的数据,而不会引发一个底层的read调用来从

文件中读取更多的数据。

      例1:测试文件上的锁 

              lock.c -->lock

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

const char *test_file="test_lock";
int main(int argc, char *argv[])
{
	int file_desc;
	int byte_count;
	char *byte_to_write="A";
	struct flock region_1;
	struct flock region_2;
	int res;
	file_desc=open(test_file,O_RDWR|O_CREAT,0666);
	if(!file_desc)
	{
		fprintf(stderr,"Unable to open %s for read/write\n",test_file);
		exit(EXIT_FAILURE);
	}
	for(byte_count=0;byte_count<100;byte_count++)
	{
		write(file_desc,byte_to_write,1);
	}
	//把文件的10~30字节设为区域1,并在其上设置共享锁
	region_1.l_type=F_RDLCK;
	region_1.l_whence=SEEK_SET;
	region_1.l_start=10;
	region_1.l_len=20;
	//把文件的40~50字节设为区域2,并在其上设置独占锁
	region_2.l_type=F_WRLCK;
	region_2.l_whence=SEEK_SET;
	region_2.l_start=40;
	region_2.l_len=10;
	//锁定文件
	printf("Process %d locking file\n",getpid());
	res=fcntl(file_desc,F_SETLK,&region_1);
	if(res==-1)fprintf(stderr,"Failed to lock region_1\n");
	res=fcntl(file_desc,F_SETLK,&region_2);
	if(res==-1)fprintf(stderr,"Failed to lock region_2\n");

	sleep(60);
	printf("Process %d closing file\n",getpid());
	close(file_desc);
	exit(EXIT_SUCCESS);
	
	return 0;
}

 

              lock2.c -->lock2

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

const char *test_file="test_lock";
#define SIZE_TO_TRY 5

void show_lock_info(struct flock *to_show);
int main(int argc, char *argv[])
{
	int file_desc;
	int res;
	struct flock region_to_test;
	int start_byte;

	file_desc=open(test_file,O_RDWR|O_CREAT,0666);
	if(!file_desc)
	{
		fprintf(stderr,"Unable to open %s for read/write",test_file);
		exit(EXIT_FAILURE);
	}
	for(start_byte=0;start_byte<99;start_byte+=SIZE_TO_TRY)
	{
		//设置希望测试的文件区域
		region_to_test.l_type=F_WRLCK;
		region_to_test.l_whence=SEEK_SET;
		region_to_test.l_start=start_byte;
		region_to_test.l_len=SIZE_TO_TRY;
		region_to_test.l_pid=-1;

		printf("Testing F_WRLCK on region from %d to %d\n",start_byte,start_byte+SIZE_TO_TRY);
		//测试文件上的锁
		res=fcntl(file_desc,F_GETLK,&region_to_test);
		if(res==-1)
		{
			fprintf(stderr,"F_GETLK failed\n");
			exit(EXIT_FAILURE);
		}
		if(region_to_test.l_pid!=-1)
		{
			printf("Lock would fail. F_GETLK returned:\n");
			show_lock_info(&region_to_test);	
		}
		else
		{
			printf("F_WRLCK - Lock would succed\n");
		}
		//用共享(读)锁重复测试一次,再次设置希望测试的文件区域
		region_to_test.l_type=F_RDLCK;
		region_to_test.l_whence=SEEK_SET;
		region_to_test.l_start=start_byte;
		region_to_test.l_len=SIZE_TO_TRY;
		region_to_test.l_pid=-1;
		printf("Testing F_RDLCK on region from %d to %d\n",start_byte,start_byte+SIZE_TO_TRY);
		//再次测试文件上的锁
		res=fcntl(file_desc,F_GETLK,&region_to_test);
		if(res==-1)
		{
			fprintf(stderr,"F_GETLK faild\n");
			exit(EXIT_FAILURE);
		}
		if(region_to_test.l_pid!=-1)
		{
			printf("Lock would fail. F_GETLK returned:\n");
			show_lock_info(&region_to_test);
		}
		else
		{
			printf("F_RDLCK - Lock would succeed\n");
		}
	}
	close(file_desc);
	return 0;
}
void show_lock_info(struct flock *to_show)
{
	printf("\t l_type %d, ",to_show->l_type);
	printf("l_whence %d, ",to_show->l_whence);
	printf("l_start %d, ",(int)to_show->l_start);
	printf("l_len %d, ",(int)to_show->l_len);
	printf("l_pid %d\n",to_show->l_pid);
}


       为了测试首先运行./lock &(后台运行),然后运行./lock2

       下面为输出内容:

 

Testing F_WRLCK on region from 0 to 5
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 0 to 5
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 5 to 10
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 5 to 10
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 10 to 15
Lock would fail. F_GETLK returned:
	 l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 6448
Testing F_RDLCK on region from 10 to 15
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 15 to 20
Lock would fail. F_GETLK returned:
	 l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 6448
Testing F_RDLCK on region from 15 to 20
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 20 to 25
Lock would fail. F_GETLK returned:
	 l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 6448
Testing F_RDLCK on region from 20 to 25
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 25 to 30
Lock would fail. F_GETLK returned:
	 l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 6448
Testing F_RDLCK on region from 25 to 30
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 30 to 35
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 30 to 35
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 35 to 40
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 35 to 40
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 40 to 45
Lock would fail. F_GETLK returned:
	 l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 6448
Testing F_RDLCK on region from 40 to 45
Lock would fail. F_GETLK returned:
	 l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 6448
Testing F_WRLCK on region from 45 to 50
Lock would fail. F_GETLK returned:
	 l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 6448
Testing F_RDLCK on region from 45 to 50
Lock would fail. F_GETLK returned:
	 l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 6448
Testing F_WRLCK on region from 50 to 55
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 50 to 55
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 55 to 60
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 55 to 60
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 60 to 65
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 60 to 65
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 65 to 70
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 65 to 70
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 70 to 75
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 70 to 75
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 75 to 80
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 75 to 80
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 80 to 85
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 80 to 85
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 85 to 90
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 85 to 90
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 90 to 95
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 90 to 95
F_RDLCK - Lock would succeed
Testing F_WRLCK on region from 95 to 100
F_WRLCK - Lock would succed
Testing F_RDLCK on region from 95 to 100
F_RDLCK - Lock would succeed


 解析:

       lock2程序把文件中的每5个字节分成一组,为每个组设置一个区域结构来测试锁,然后通过使用这些结构来

判断对应区域是否可以被加写锁或读锁。返回信息将显示造成锁请求失败的区域字节数和从字节0开始的偏移量。

因为返回结构中的l_pid元素包含当前拥有文件锁的程序的进程标识符,所以程序先把它设置为-1(一个无效值),然后

在fcntl调用返回后检测其值是否被修改过。如果该区域当前未被锁定,l_pid的值就不会被改变。

      为了理解程序的输出含义,需要查看程序中包含的头文件fcntl.h(通常是/usr/include/fcntl.h),l_type的值为1对应

的定义为F_WRLCK:表明锁失败的原因是已经存在一个写锁了,l_type的值为0对应的定义为F_RDLCK:已经存在

一个读锁了。在文件中未被lock2程序锁定的区域上,无论是共享锁还是独占锁都将会成功。

      可以看到10~30字节上可以设置一个共享锁,因为程序lock2在该区域上设置的是共享锁而不是独占锁。而在

40~50字节的区域上,两种锁都将失败,因为lock2已经在该区域上设置了一个独占锁(F_WRLCK)。

      例2:文件锁的竞争
             lock.c -->lock  与上诉代码相同


             lock3.c -->lock3

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

const char *test_file="test_lock";

int main(int argc, char *argv[])
{
	int file_desc;
	int res;
	struct flock region_to_lock;

	file_desc=open(test_file,O_RDWR|O_CREAT,0666);
	if(!file_desc)
	{
		fprintf(stderr,"Unable to open %s for read/write",test_file);
		exit(EXIT_FAILURE);
	}
	//程序的其余部分指定文件的不同区域,并尝试在它们之上执行不同的锁操作
	region_to_lock.l_type=F_RDLCK;
	region_to_lock.l_whence=SEEK_SET;
	region_to_lock.l_start=10;
	region_to_lock.l_len=5;

	printf("Process %d,trying F_RDLCK, region %d to %d\n",getpid(),(int)region_to_lock.l_start,(int)(region_to_lock.l_start+region_to_lock.l_len));
	res=fcntl(file_desc,F_SETLK,&region_to_lock);
	if(res==-1)
	{
		printf("Process %d - faild to lock region\n",getpid());
	}
	else
	{
		printf("Process %d - obtained to lock region\n",getpid());
	}

	region_to_lock.l_type=F_UNLCK;
	region_to_lock.l_whence=SEEK_SET;
	region_to_lock.l_start=10;
	region_to_lock.l_len=5;

	printf("Process %d,trying F_UNLCK, region %d to %d\n",getpid(),(int)region_to_lock.l_start,(int)(region_to_lock.l_start+region_to_lock.l_len));
	res=fcntl(file_desc,F_SETLK,&region_to_lock);
	if(res==-1)
	{
		printf("Process %d - faild to lock region\n",getpid());
	}
	else
	{
		printf("Process %d - unlocked region\n",getpid());
	}

	region_to_lock.l_type=F_UNLCK;
	region_to_lock.l_whence=SEEK_SET;
	region_to_lock.l_start=0;
	region_to_lock.l_len=50;

	printf("Process %d,trying F_UNLCK, region %d to %d\n",getpid(),(int)region_to_lock.l_start,(int)(region_to_lock.l_start+region_to_lock.l_len));
	res=fcntl(file_desc,F_SETLK,&region_to_lock);
	if(res==-1)
	{
		printf("Process %d - faild to lock region\n",getpid());
	}
	else
	{
		printf("Process %d - unlocked region\n",getpid());
	}

	region_to_lock.l_type=F_WRLCK;
	region_to_lock.l_whence=SEEK_SET;
	region_to_lock.l_start=16;
	region_to_lock.l_len=5;

	printf("Process %d,trying F_WRLCK, region %d to %d\n",getpid(),(int)region_to_lock.l_start,(int)(region_to_lock.l_start+region_to_lock.l_len));
	res=fcntl(file_desc,F_SETLK,&region_to_lock);
	if(res==-1)
	{
		printf("Process %d - faild to lock region\n",getpid());
	}
	else
	{
		printf("Process %d - obtained lock on region\n",getpid());
	}
	
	region_to_lock.l_type=F_RDLCK;
	region_to_lock.l_whence=SEEK_SET;
	region_to_lock.l_start=40;
	region_to_lock.l_len=10;

	printf("Process %d,trying F_RDLCK, region %d to %d\n",getpid(),(int)region_to_lock.l_start,(int)(region_to_lock.l_start+region_to_lock.l_len));
	res=fcntl(file_desc,F_SETLK,&region_to_lock);
	if(res==-1)
	{
		printf("Process %d - faild to lock region\n",getpid());
	}
	else
	{
		printf("Process %d - obtained lock on region\n",getpid());
	}

	region_to_lock.l_type=F_WRLCK;
	region_to_lock.l_whence=SEEK_SET;
	region_to_lock.l_start=16;
	region_to_lock.l_len=5;

	printf("Process %d,trying F_WRLCK, region %d to %d\n",getpid(),(int)region_to_lock.l_start,(int)(region_to_lock.l_start+region_to_lock.l_len));
	res=fcntl(file_desc,F_SETLKW,&region_to_lock);
	if(res==-1)
	{
		printf("Process %d - faild to lock region\n",getpid());
	}
	else
	{
		printf("Process %d - obtained lock on region\n",getpid());
	}

	printf("Process %d ending\n",getpid());
	close(file_desc);
	exit(EXIT_SUCCESS);
	return 0;
}


       为了测试首先运行./lock &(后台运行),然后运行./lock3

      下面为输出内容:
 

Process 26333,trying F_RDLCK, region 10 to 15
Process 26333 - obtained to lock region
Process 26333,trying F_UNLCK, region 10 to 15
Process 26333 - unlocked region
Process 26333,trying F_UNLCK, region 0 to 50
Process 26333 - unlocked region
Process 26333,trying F_WRLCK, region 16 to 21
Process 26333 - faild to lock region
Process 26333,trying F_RDLCK, region 40 to 50
Process 26333 - faild to lock region
Process 26333,trying F_WRLCK, region 16 to 21

Process 26323 closing file
Process 26333 - obtained lock on region
Process 26333 ending


解析:

      首先,这个程序尝试试用共享锁来锁定文件中10~15字节的区域。这块区域已被一个共享锁锁定,但共享锁允许

同时使用,因此加锁成功。然后解除它自己对这块区域的共享锁,这也成功了。接下来,这个程序视图解除这个文件

前50字节上的锁,虽然它实际上未对这块区域进行锁定,但也成功了,因为虽然这个程序并未对这个区域加锁,但

解锁请求最终的结果取决于这个程序在文件的头50个字节上并没有设置任何锁。

      这个程序接下来视图用一把独占锁来锁定文件中16~21字节的区域。由于这个区域上已有了一把共享锁,独占锁

无法创建,因此这个锁定操作失败了。然后,程序又尝试试用一把共享锁来锁定文件中40~59字节的区域。由于这个

区域上已经有了一把独占锁,因此这个锁定操作也失败了。最后,程序再次尝试在文件中16~21字节的区域上获得一

把独占锁,但这次它用F_SETLKW命令来等待直到它可以获得一把锁为止。于是程序的输出就会遇到一个很长的停

顿,直到已锁住这块区域的lock程序因为完成sleep调用、关闭文件而释放了它先前获得的所有锁为止。lock3程序继

续执行,成功锁定了这块区域,最后它也退出了运行。

 

以上是关于文件锁的主要内容,如果未能解决你的问题,请参考以下文章

LockSupport.java 中的 FIFO 互斥代码片段

读写锁 与 互斥锁

互斥锁 & 共享锁

并发技术12线程锁技术的使用

java并发线程锁技术的使用

ReentrantReadWriteLock场景应用