fcntl()
对已打开的文件描述符进行操作,并根据命令参数的不同能够执行不同的任务。关于文件锁的几个命令选项如下:
F_GETLKF_SETLKF_SETLKW当使用这
3 个参数时必须在 fcntl() 函数中指定一个指向
struct
flock 结构的指针,所以有效的原型为:
引用
int fcntl(int fileds, int
command,
struct flock *flock_structure)
flock
结构体在不同的版本里有不同的实现方法,但相同的是,它至少会包含以下的一些成员:
- short l_type;
- short l_whence;
- off_t l_start;
- off_t l_len;
- pid_t l_pid;
上面,
l_type 又有以下几个值,这定义在 fcntl.h
中。这几个值的含义分别是:
F_GETLK:
F_RDLCKF_RDLCK
表示共享锁( 读操作 )。许多不同的进程可以在一个文件中的同一个区域处有一个“共享锁”。任一个进程在一块区域处设了“共享锁”,则其他进程就不能在这里设置
“独占锁”。如果需要得到一个共享锁,则文件就必须打开 ”读“ 或者 "读/写“
访问权限
F_UNLCKF_UNLCK
作用是解除锁定。
F_WRLCKF_WRLCK 表示独占锁( 写操作
)。仅能有一个进程可以在一个特定区域拥有一个独占锁。则其它进程就无法在这块区域获得任何类型锁。若需要获得一个独占锁,文件必须打开 “写” 或 “读/写“
访问权限。
l_whence,
l_start
和
l_len
这三个成员是用来定义一个区域的--即文件中一个连续的字节组合。
l_whence 必须是以下几个值之一( 在 unistd.h
中定义):
SEEK_SET : 文件开始位置
SEEK_CUR:
文件当前位置
SEEK_END: 文件末尾位置
这几个位置都是相对
l_start
的相对偏移而言。一般情况下,使用 SEEK_SET 比较多,所以此时 l_start 就是从文件头开始算起了。
l_len
参数定义了区域中的字节数( 偏移量,长度 )。
l_pid 参数用来指明是哪个进程正在实施封锁---参考
F_GETLK
描述。
文件中每一个字节在任何一个相同的时间里只能有一种类型锁,即不可能同时拥有共享锁,独占锁和解除锁定。
F_SETLK这个命令用来实施 “锁定” 或 “解锁”
动作( 由文件描述符指定文件 )。flock 结构中的值( 不同于从 F_GETLK 中的值) 如下:
l_type 有三个值:
F_RDLCK
: 共享锁,只读用
F_WRLCK : 独占锁(写操作锁)
F_UNLCK :
解除锁定
和
F_GETLK 一样,
区域锁定同样用
l_start, l_whence 和 l_len 这三个值( 在 flock 结构体中 )来定义。如果锁操作成功, fcntl() 返回一个值(
执行了马上就能返回 ),否则返回 -1 。
F_SETLKWF_SETLKW 的作用和上面的
F_SETLK
一样,不同的是如果无法锁定那它就会一直等待直到可以锁定为止;一旦进入等待状态,则这个函数只有在被可以进行锁定或者是接收到信号时才会返回。
封锁状态下的读写操作:
如
果对对一块锁区域进行读写的话,一定要注意,得使用底层读写函数 read() 和 write() 来访问其中数据,而不是上层的高级 fread() 和
fwrite() 函数。这是因为: fread() 和 fwrite() 会把数据缓冲到函数库里。所以执行一次 fread() 函数去读取一个文件中的 100
个字节数据可能(
事实上绝大部分也是肯定如此 ) 会读到多于
100 个字节的数据并且把多出的部分数据缓冲进函数库里。如果程序使用 fread() 来读下一个 100
字节数据,它实际上是将函数库里缓冲好了的数据读出,但不允许一个底层 read() 从文件里取出更多的数据。
假 设有一个文件,这个文件由 200
个字节数据组成,而且这些数据都是 0 。假设有一程序A,先封锁了文件中前 100 个字节的数据,然后它就用 fread() 来读取这 100
个字节的数据。当然,这个 100 个字节的数据肯定能被读出,但是与此同,fread() 还把剩下来的 100
个字节还读到缓冲区中去,为的是方便下次读取时更快速的拿出来,所以实际上程序A读取的是整个文件的内容。
现在又假设有一个程序B,它封 锁了后 100
个字节的数据,然后把 2 写到了这个 100 字节空间去,也就是说,这后面的 100 字节数据都从原来的 0 变成了 2;然后程序B
在解锁后便离开。接着,程序A 过来锁住后 100 字节数据,然后读取,然而结果是---
读到的仍然是 0 ,而不是 2!这是因为,fread()
缓冲了数据的缘故,这些被缓冲的数据在函数库里;而100个字节 2 是在文件中,也就是在磁盘上。
这个问题在使用 read() 和 write()
函数不会产生!
下面两个程序,一个用来锁定文件,一个用来测试。
程序一(用来锁定):
引用
#include
<unistd.h>
#include
<stdlib.h>
#include
<stdio.h>
#include
<fcntl.h>
const char
*test_file =
"/tmp/test_lock";
int main()
{
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);
}
/* Put some data in the
file*/
for(
byte_count =
0; byte_count
< 100;
byte_count++)
{
(void)write(file_desc,
byte_to_write,
1);
}
/*
Set up region 1 with a shared lock, from bytes 10 to 30:
*/
region_1.l_type = F_RDLCK;
region_1.l_whence =
SEEK_SET;
region_1.l_start = 10;
region_1.l_len = 20;
/* Set up region 2 with an exclusive lock, from
bytes 40 to 50: */
region_2.l_type = F_WRLCK;
region_2.l_whence =
SEEK_SET;
region_2.l_start = 40;
region_2.l_len = 10;
/* Now lock the file
*/
printf("Process
%d locking file\n", getpid());
res = fcntl(file_desc,
F_SETLK,
®ion_1);
if (res
== -1) fprintf(stderr, "Failed to lock region 1\n");
res
= fcntl(file_desc,
F_SETLK,
®ion_2);
if (res
== -1) fprintf(stderr, "Failed to lock region 2\n");
/* Wait for a while
*/
sleep(60);
printf("Process
%d closing file\n", getpid());
close(file_desc);
exit(EXIT_SUCCESS);
}
程序说明:
先创建文件
/tmp/test_lock,然后写 100 个 A 到 文件中( 共
100 字节)。
然后对从第 10 个字节 到第 30 个字节的这部分数据区进行共享锁设定;对从第 40 个字节到第 50
这部分数据区进行独占锁设定。
然后休息 1 分钟,即保持这种设定状态 1
分钟,最后程序关闭文件描述符后退出
-------------------------------------------------------------------------------------------
程序二(
用来测试 ):
引用
#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 file_desc;
int res;
struct flock region_to_test;
int start_byte;
/* Open a file descriptor
*/
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);
/*
Now test lock on the file
*/
res
= fcntl(file_desc,
F_GETLK,
®ion_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(®ion_to_test);
}
else {
printf("F_WRLCK
- Lock would succeed\n");
}
/* Now repeat the test with a
shared(read)lock. Set up the region you wish to test again
*/
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);
/* Test the lock on the file again
*/
res
= fcntl(file_desc,
F_GETLK,
®ion_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 return: \n");
show_lock_info(®ion_to_test);
}
else {
printf("F_RDLCK
- Lock would secceed\n");
}
}
close(file_desc);
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);
}
程序说明:
程序一次对
5 个字节的区域进行锁定测试。如果被测试区域之前已经被别的程序锁定(共享锁,独占锁),那么锁定测试就会失败,至于被锁定的类型可以从元素 l_type
中看出:l_type 为 0 时,是共享锁;l_ytpe 为 1 时是独占锁。
其实在 /usr/include/bits/fcntl.h
文件中其实定义了这里的 0 和 1 所代表的值:
引用
/* For posix
fcntl() and `l_type‘ field of a `struct flock‘ for lockf().
*/
#define
F_RDLCK
0 /*
Read lock. */
#define
F_WRLCK
1 /*
Write lock. */
#define
F_UNLCK
2 /*
Remove lock.
*/
假如被测试区域没有被别的程序实施锁定操作,则返回可以对其锁定的提示信息。
l_pid
一开始被设置为 -1,这是非法值。如果被测试的区域被一个进程锁定,那么 l_pid 就会被修改为这个进程的标识符,反之就不会改变而仍然为
-1。
程序运行与输出:
1、先在后台运行./lock3.exe &
2、运行./lock4.exe
输出结果:
引用
beyes@linux-beyes:~/C> ./lock4.exe
Testing F_WRLCK on
region from 0 to 5
F_WRLCK - Lock would succeed
Testing F_RDLCK on
region from 0 to 5
F_RDLCK - Lock would secceed
Testing F_WRLCK on
region from 5 to 10
F_WRLCK - Lock would succeed
Testing F_RDLCK on
region from 5 to 10
F_RDLCK - Lock would secceed
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
15916
Testing F_RDLCK on region from 10 to 15
F_RDLCK - Lock would
secceed
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 15916
Testing F_RDLCK on region from 15 to 20
F_RDLCK - Lock would
secceed
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 15916
Testing F_RDLCK on region from 20 to 25
F_RDLCK - Lock would
secceed
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 15916
Testing F_RDLCK on region from 25 to 30
F_RDLCK - Lock would
secceed
Testing F_WRLCK on region from 30 to 35
F_WRLCK - Lock would
succeed
Testing F_RDLCK on region from 30 to 35
F_RDLCK - Lock would
secceed
Testing F_WRLCK on region from 35 to 40
F_WRLCK - Lock would
succeed
Testing F_RDLCK on region from 35 to 40
F_RDLCK - Lock would
secceed
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 15916
Testing F_RDLCK on region from 40 to 45
Lock would fail.
F_GETLK return:
l_type 1, l_whence 0, l_start 40, l_len
10, l_pid 15916
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 15916
Testing F_RDLCK on region from 45 to 50
Lock would
fail. F_GETLK return:
l_type 1, l_whence 0, l_start 40,
l_len 10, l_pid 15916
Testing F_WRLCK on region from 50 to 55
F_WRLCK -
Lock would succeed
Testing F_RDLCK on region from 50 to 55
F_RDLCK -
Lock would secceed
Testing F_WRLCK on region from 55 to 60
F_WRLCK -
Lock would succeed
Testing F_RDLCK on region from 55 to 60
F_RDLCK -
Lock would secceed
Testing F_WRLCK on region from 60 to 65
F_WRLCK -
Lock would succeed
Testing F_RDLCK on region from 60 to 65
F_RDLCK -
Lock would secceed
Testing F_WRLCK on region from 65 to 70
F_WRLCK -
Lock would succeed
Testing F_RDLCK on region from 65 to 70
F_RDLCK -
Lock would secceed
Testing F_WRLCK on region from 70 to 75
F_WRLCK -
Lock would succeed
Testing F_RDLCK on region from 70 to 75
F_RDLCK -
Lock would secceed
Testing F_WRLCK on region from 75 to 80
F_WRLCK -
Lock would succeed
Testing F_RDLCK on region from 75 to 80
F_RDLCK -
Lock would secceed
Testing F_WRLCK on region from 80 to 85
F_WRLCK -
Lock would succeed
Testing F_RDLCK on region from 80 to 85
F_RDLCK -
Lock would secceed
Testing F_WRLCK on region from 85 to 90
F_WRLCK -
Lock would succeed
Testing F_RDLCK on region from 85 to 90
F_RDLCK -
Lock would secceed
Testing F_WRLCK on region from 90 to 95
F_WRLCK -
Lock would succeed
Testing F_RDLCK on region from 90 to 95
F_RDLCK -
Lock would secceed
Testing F_WRLCK on region from 95 to 100
F_WRLCK -
Lock would succeed
Testing F_RDLCK on region from 95 to 100
F_RDLCK -
Lock would secceed