文件锁定

文件锁定

当多个进程需要同时操作一个文件的时候,为了互相协调,防止出现文件不一致的情况,就需要使用系统提供的文件锁定特性。

创建锁文件

创建锁文件的方式类似操作系统中的二进制信号量机制。这个锁文件是一个空文件,它的作用仅仅是作为一个标志,表示当前有一个进程正在使用文件。

注意这种机制只是一种建议锁,而不是强制锁。一个应用程序在明知道锁文件存在(有一个进程正在访问文件)的时候还是可以强行访问文件,这种行为不会引起程序异常,但是会使文件混乱。这种进程之间的协调需要程序员来完成。

因为这个锁文件是用来控制进程之间协调的,所以在创建这个文件的时候必须使用原子操作。这个行为可以通过open系统调用接受O_CREAT | O_EXCL参数实现。

下面是一个利用锁文件实现进程之间协调的例子:

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>

const char * lock_file = "LOCK.test";

int main(void){
    int file_desc;  //表示锁文件是否创建成功
    int tries = 10; //让进程尝试10次

    while (tries--){
        file_desc = open(lock_file, O_RDWR | O_CREAT | O_EXCL, 0444);
        if(file_desc == -1){
            printf("%d - Lock already exists\n", getpid());
            sleep(3);
        }else{
            //临界区
            printf("%d - I have exclusive access\n", getpid());
            sleep(1);

            //删除锁文件以退出临界区
            (void)close(file_desc);
            (void)unlink(lock_file);
            
            sleep(2);
        }
    }

    return 0;
}

使用Linux命令gcc test.c -o a./a & ./a来执行该程序的两个副本,让两个程序同时运行。

区域锁定

用创建锁文件的方法可以让一个进程独占整个文件。但是有些时候不能这样实现,有些时候一个进程可能只需要对文件的一个部分进行访问,而其他没有访问的部分可以让其他的进程访问。这就是区域锁定实现的效果。

这种方式通过fcntl系统调用来实现,它对一个打开的文件描述符进行操作,并能根据command参数的设置完成不同的任务,其中command可以是:

  • F_GETLK: 查询当前文件的某一区域是否有锁,该区域由flock结构体指定,如果该区域有锁,函数就会修改flock结构体中的相关信息,将它修改成当前区域的锁信息。并且该函数执行成功之后,会返回一个非-1的值,而失败则返回-1。如果函数执行成功,就可以通过检查函数执行前后flock结构体是否被修改来判断当前区域是否有锁。通常比较方便的做法是检查flock结构体中l_pid成员的值。
  • F_SETLK:向文件的某一区域加锁,锁的信息由flock结构体提。如果加锁成功,返回一个非-1的值,加锁失败则返回-1
  • F_SETLKW:该命令跟F_SETLK一样,不同的是如果它加锁失败,就会一直等待,直到加锁成功才会使函数返回。

当使用这些选项的时候,fcntl的第三个参数必须是一个指向flock结构的指针,所以实际的函数原型为:

int fcntl(int fildes, int command, struck flock * flock_structure);

其中flock结构的成员依赖具体实现,但是至少包含以下成员:

  • short l_type:锁的类型,可以是F_RDLCK共享锁(或读锁),F_UNLCK解锁和F_WRLCK独占锁(或写锁)。
  • short l_whence:可以是SEEK_SET文件开头,SEEK_CUR当前位置和SEEL_END文件结尾。
  • off_t l_start:锁定区域的第一个字节位置。
  • off_t l_len:锁定区域的长度。
  • pid_t l_pid:当前持有锁的进程。

读写加锁文件

当我们对加锁之后的文件进行读写的时候,必须要使用系统调用的readwrite来进行读写,而不能使用C语言库中更高级的freadfwrite。因为在C语言库中,为了提高读写效率,减少I/O操作,设置了缓存机制。当你读了文件中的100KB的内容时,函数库可能会利用这一次I/O的机会读取200KB的内容,以减少I/O操作,提高运行效率。同时,对于写操作,调用了fwrite之后,函数库并不会立即使用I/O操作将数据写入文件,而是将其写入缓冲区,当缓冲区的内容达到一定数量之后,才会执行I/O操作,将多次调用fwrite写入的文件一次性全写入文件。

这种机制确实可以减少I/O操作执行的次数,但是对于加锁的文件,这样就会产生问题,导致文件不一致。

下面是一个例子:

该程序为文件的两个区域加锁

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

const char * test_file = "test_lock";

int main(void){
	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, "Can not open file %s for read / write\n", test_file);
		return 1;
	}

	// 给文件添加一些数据
	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 region1\n");
	res = fcntl(file_desc, F_SETLK, &region_2);
	if(res == -1) fprintf(stderr, "Failed to lock region1\n");

	// 加锁之后睡眠一会
	sleep(60);

	// 睡眠结束之后关闭文件
	printf("Process %d closing file\n", getpid());
	close(file_desc);
	return 0;
}

在为文件加上锁之后,用下面的程序来对整个文件的各个区域进行锁查询:

#include <unistd.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(void){
	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 file $s for read / write\n", test_file);
		return 1;
	}

	//测试锁
	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");
			return 1;
		}
		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 secceed\n");
		}

		//重新设置struct flock结构体的值,以便后续使用
		printf("Now testing F_RDLCK on region from %d to %d\n", start_byte, 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;

		//这次用共享锁(读锁)测试文件
		res = fcntl(file_desc, F_GETLK , &region_to_test);
		if(res == -1){
			fprintf(stderr, "F_GETLK failed\n");
			return 1;
		}
		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 secceed\n");
		}
	}

	//测试完毕,关闭文件
	close(file_desc);
	return 0;
}

void show_lock_info(struct flock * to_show){
	printf("\tl_type %d ", to_show->l_type);
	printf("l_whence %d ", to_show->l_whence);
	printf("l_start %d ", to_show->l_start);
	printf("l_len %d ", to_show->l_len);
	printf("l_pid %d ", to_show->l_pid);
}

使用Linux命令编译文件

gcc test_lock.c -o test
gcc set_lock.c -o set

先让set程序后台运行:./set &
再让test程序查询锁:./test

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值