竞争锁
现在我们已经了解了如何测试文件上已经存在的锁,下面我们来看一下当两个程序在文件的同一块区域竞争锁时会出现什么情况。我们将会使用我们的lock3程序在文件的第一个位置进行加锁操作,然而一个新的程序在同样的位置尝试加锁。要完成这个例子,我们需要添加一些解锁操作。
试验--竞争锁
下面是程序lock5.c,试图在文件中已被加锁的区域进行加锁操作,而不是测试文件不同部分的锁状态。
1 在#include以及声明之后,打开一个文件描述符:
#include
#include
#include
#include
const char *test_file = “/tmp/test_lock”;
int main()
{
int file_desc;
struct flock region_to_lock;
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);
}
2 程序的其余部分指定文件的不同区域,并且在其上尝试不同的锁操作:
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, ®ion_to_lock);
if (res == -1) {
printf(“Process %d - failed to lock region\n”, getpid());
} else {
printf(“Process %d - obtained 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, ®ion_to_lock);
if (res == -1) {
printf(“Process %d - failed to unlock 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, ®ion_to_lock);
if (res == -1) {
printf(“Process %d - failed to unlock 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, ®ion_to_lock);
if (res == -1) {
printf(“Process %d - failed 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, ®ion_to_lock);
if (res == -1) {
printf(“Process %d - failed 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 with wait, 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, ®ion_to_lock);
if (res == -1) {
printf(“Process %d - failed 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);
}
如果我们首先在后端运行lock3程序,然后立即运行这个新程序:
$ ./lock3 &
$ process 1845 locking file
$ ./lock
我们得到的程序输出结果如下所示:
Process 227 locking file
Process 228, trying F_RDLCK, region 10 to 15
Process 228 - obtained lock on region
Process 228, trying F_UNLCK, region 10 to 15
Process 228 - unlocked region
Process 228, trying F_UNLCK, region 0 to 50
Process 228 - unlocked region
Process 228, trying F_WRLCK, region 16 to 21
Process 228 - failed to lock on region
Process 228, trying F_RDLCK, region 40 to 50
Process 228 - failed to lock on region
Process 228, trying F_WRLCK with wait, region 16 to 21
Process 227 closing file
Process 228 - obtained lock on region
Process 228 ending
工作原理
首先,程序试图使用一个共享锁锁住文件中由10到15字节的区域。这个区域已经存在一个共享锁,但是允许后续的共享锁,并且加锁操作成功。
然后程序在此区域解除共享锁,这个操作也是成功的。程序然后试图对文件的前50个字节进行解锁操作,尽管此处并没有任何锁设置。这个操作也是成功的,因为尽管这个程序在第一个位置并没有锁,解锁请求的结果就是程序在前50个字节上并没有加任何锁。
接下来,程序试图使用一个排他锁在16到21字节的区域上加锁。这个区域已经具有了一个共享锁,所以这次的新锁操作失败,因为不能创建排他锁。
此后,程序在40到50字节的区域上试图加共享锁。此区域已经存在排他锁,所以加锁操作失败。
最后,程序试图在16到21字节的区域上获得排他锁,但是这次他使用F_SETLKW命令进行等待直到获得锁。在程序的输出结果中就会出现长时的暂停,直到已经锁住此区域的lock3程序关闭文件并释放所获得的所有锁。lock5程序恢复执行,在其退出之前,成功锁住此区域。
其他的锁命令
对文件加锁还有第二个方法:lockf函数。这个函数也使用文件描述符进行操作。其函数原型如下:
#include
int lockf(int fildes, int function, off_t size_to_lock);
function参数的值如下:
F_ULOCK:解锁
F_LOCK:排他锁
F_TLOCK:测试并加排他锁
F_TEST:测试其他进程的锁
size_to_lock参数是要在其上进行操作的字节数,由文件中的当前偏移算起。
lockf比起fcntl接口具有更为简单的接口,主要是因为他具有更少的功能与灵活性。要使用这个函数,我们必须设置我们希望加锁的区域的起始处,然后使用要加锁的字节数进行调用。
与文件加锁的fcntl方法相类似,所有的锁都只是建议锁;他们并不会真正的阻止对于文件的读写操作。检测锁是程序的职责。在Linux中并没有定义混合使用fcntl锁与lockf锁的效果,所以我们必须决定我们希望使用哪种类型的锁,并且坚持只使用一种锁。
死锁
如果不指现死锁的危险,那么对于锁的讨论就是不完整的。假设两个程序希望更新同一个文件。他们都需要同时更新1到2的字节区域。程序A选择先更新字节2,然后更新字节1。程序B试图首先更新字节1,然后更新字节2。
两个程序同时走去。程序A锁住字节2,而程序B锁住字节1。程序A试图在字节1加锁。因为这已经被程序B锁住,程序A进行等待。程序B试图锁住字节2。因为这已经被程序A锁住了,程序B也等待。
当任何一个程序都不能进行处理的形势就称之为死锁。这在数据库程序中是一个很常见的问题,因为此时大量的用户会同时访问相同的数据。大多数的商业关系型数据会检测死锁并且会自动打破死锁;而Linux内核不会。为了处理死锁这样的情况需要一些外部的介入,例如终止其中一个程序。
程序员必须清楚这种情况。当我们有多个程序在等待锁时,我们必须十分小心以避免死锁。在我们这个例子中很容易避免死锁:两个程序以相同的顺序锁住他们所需要的字节,或者是锁住更大的区域。