------文件锁定-------
当一个程序正在对文件进行写操作时,文件就会进入暂时状态,在这个状态下,如果另外一个程序尝试读这个文件,它就会自动停下等待这个状态的结束。Linux 提供了多种特性来实现文件的锁定。
Linux的常见锁定文件有两种:
第一种:以原子操作的方式创建锁文件。所谓“原子操作”就是创建锁文件时,系统将不允许其他的事情发生。这就给程序一种方式来确保它创建的文件是唯一的,而且这个文件不可能被其他程序在同一时刻创建。
第二种:是用文件锁的方式,锁定文件的一部分,从而可以独享文件这一部分读写的访问。
锁文件只是建议锁,不是强制锁。
1.创建锁文件
实验一:
///lock1.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
int main()
{
int fd;
fd = open("./LCK.test",O_RDWR | O_CREAT | O_EXCL,0444); "tmp/LCK.test",可以把创建的文件保存
到临时文件目录里面,系统会自动删除的
if(fd == -1) printf("Open error:%m\n"),exit(-1);
else
printf("Open succeeded\n!");
exit(0);
}
实验解析:
第一次运行,打开文件成功,再次运行,文件已经存在。上程序不是很完美。
实验:协调性锁文件
///lock2.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <error.h>
const char *lock_file = "/tmp/LCK.test2";
int main()
{
int fd;
int i = 10;
while(i--)
{
fd = open(lock_file,O_RDWR | O_CREAT | O_EXCL, 0444);
if( fd == -1)
{
printf("%d - Lock already present \n",getpid());
sleep(3);
}
else
{ // 临界区从这里开始
printf("%d -I have exclusive access \n",getpid());
sleep(1);
close(fd);
unlink(lock_file);
sleep(1); /// 在这里结束
}
}
exit(0);
}
$ rm -f /tmp/LCK.test2 --------- 确保锁文件不存在
$ ./main & ./main ----- 这个命令在后台运行main 的一个副本,前台运行另一个副本。
实验解析: 通过创建一个唯一的锁文件/tmp/LCK.test2来访问临界资源。当一个进程访问这个文件时,另外一个进程访问这个文件肯定是失败的。如果感觉两个不明显的话,你可以试试多个,
$ ./main & ./main & ./main & ./main & ./main ----------------哈哈结果可想而知,你要按好多次Ctrl + C
2. 区域锁定
区域锁定出现,是因为锁文件方式并不适用于访问大型的共享文件。如果一个大文件,由一个程序写入数据,但却由不同的程序同时对这个文件进行更新。处理程序不能等待记录程序结束,所以需要一些协调方法来提供对同一个文件的并发访问。linux提供了2种方法来实现:一是fcntl系统调用和lockf调用。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
说明:fcntl 对一个打开的文件描述符进行操作,并能根据command参数的设置完成不同的任务。
cmd:
F_GETLK --------------- 获取fd(第一个参数),打开文件锁信息。
F_SETLK ----------------对fd指向的文件的某个区域就 加锁 或者 解锁
F_SETLKW ------------这个命令与SETTLK作用相同,但是在无法获取锁时,等待直到获取成功为止。
这个函数的全称是:
int fcntl(
int fd; ///被加锁的文件描述副
int cmd; /// 锁的操作方式
struct flock *lk; // 锁的描述
)
返回值:
0:加锁成功
-1:加锁失败
---------------------------------------------------------------------------
struct flock {
...
short l_type; /* Type of lock: F_RDLCK,F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start:SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Starting offset for lock */
off_t l_len; /* Number of bytes to lock */
pid_t l_pid; /* PID of process blocking our lock(F_GETLK only) */
...
};
--------------------------------------------------------------------------------
l_type成员的取值:
F_RDLCK ----- 共享(或读)锁。
F_UNLCK ----------- 解锁,用来清楚锁。
F_WRLCK -----------独占(或写)锁。
l_whence的取值必须是:(l_whence 定义了 l_start 的相对偏移量)
SEEK_SET ----文件头
SEEK_CUR --- 当前位置
SEEK_END ---- 文件尾
l_start 是该区域的第一个字节,l_len 参数定义了该区域的字节数。
实验: 使用fcntl 锁定文件
//lock3.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
const char *test_file = "/tmp/test_lock";
int main()
{
int fd;
int i;
char *byte_to_write = "A";
struct flock region_1;
struct flock region_2;
int res;
// 打开一个文件描述符
fd = open(test_file,O_RDWR | O_CREAT,0666);
if(! fd)
{
fprintf(stderr,"Unable to open %s for read/write \n",test_file);
exit(EXIT_FAILURE);
}
// 给文件添加一些数据
for(i=0; i<100; ++i)
{
write(fd,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 正在给文件加锁\n",getpid());
res = fcntl(fd,F_SETLK,®ion_1);
if(res == -1)
fprintf(stderr,"区域1加锁失败 \n");
res = fcntl(fd,F_SETLK,®ion_2);
if(res == -1)
fprintf(stderr,"区域2加锁失败 \n");
sleep(60);
printf("Process %d c正在关闭文件 \n",getpid());
close(fd);
exit(EXIT_SUCCESS);
}
实验解析:程序首先创建一个文件,并以可读可写方式打开它。如下如所示:
实验:测试文件上的锁
// lock4.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
const char * test_file = "/tmp/test_lock";
#define SIZE_TO_TRY 5
void show_lock_info(struct flock * to_show);
int main()
{
int fd;
int res;
struct flock region_to_test;
int i;
// 打开一个文件描述符
fd = open(test_file,O_RDWR | O_CREAT, 0666);
if( !fd)
{
fprintf(stderr," %s 不能打开文件去读去写",test_file);
exit(EXIT_FAILURE);
}
for(i=0; i<99; i +=SIZE_TO_TRY)
{
// 设置希望测试的文件区域
region_to_test.l_type = F_WRLCK;
region_to_test.l_whence = SEEK_SET;
region_to_test.l_start = i;
region_to_test.l_len = SIZE_TO_TRY;
region_to_test.l_pid = -1; ///持有锁的进程标识符
printf("正在测试的F_WRLCK的区域从 %d 到 %d\n",i,i+SIZE_TO_TRY);
/// 现在测试文件上的锁
res = fcntl(fd, F_GETLK,®ion_to_test);// 读锁方式操作
if( res == -1)
{
fprintf(stderr, "读锁 失败\n");
exit(EXIT_FAILURE);
}
if(region_to_test.l_pid != -1)
{
printf("锁定失败。F_GETLK returned :\n");
show_lock_info(®ion_to_test);
}
else
{
printf("F_WRLCK - 锁定将会成功\n");
}
// 用共享(读)锁重复测试一次,再次设置希望测试的文件区域
region_to_test.l_type = F_RDLCK;
region_to_test.l_whence = SEEK_SET;
region_to_test.l_start = i;
region_to_test.l_len = SIZE_TO_TRY;
printf("测试F_RDLCK 的区域从%d到 %d \n",i,i+SIZE_TO_TRY);
// 再次测试文件上的锁
res = fcntl(fd,F_GETLK, ®ion_to_test);
if( res == -1)
{
fprintf(stderr,"F_GETLK 失败!\n");
exit(EXIT_FAILURE);
}
if( region_to_test.l_pid != -1)
{
printf("加锁失败.F_GETLK 返回:\n");
show_lock_info(®ion_to_test);
}
else
{
printf("F_RDLCK-加锁成功!\n");
}
}
close(fd);
exit(EXIT_SUCCESS);
}
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, ",(int)to_show->l_start);
printf("l_len %d, ",(int)to_show->l_len);
printf("l_pid %d\n",to_show->l_pid);
}
说明: lock4程序是测试lock3的,所以先运行lock4,再运行lock4 来测试锁。
命令:
--------------
$ gcc lock3.c -olock3
$ gcc lock4.c -olock4
$ ./lock3 &
$ ./lock4
--------------
总结:文件的锁定使文件的读取更加安全,主要涉及的函数有fcntl .这个函数主要对文件区域的锁定。适合与大型共享文件的锁定。对于此函数的应用,需要注意参数的灵活使用。