数据管理(三)
文件锁
文件锁是多用户,多任务操作系统的重要一部分。程序经常需要共享数据,通常是通过文件,所以对于这些程序来说有一种可以建立文件控制的方法是十分重要的。这样文件就可以安全的进行更新,或者当一个程序在写入文件时,另一个程序会阻止自己试图由文件中读取。
Linux有许多我们可以用于文件锁的特性。最简单的方法就是以原子方式创建文件锁的技术,从而当创建了这个锁以后可以阻止其他任何事情的发生。这样就赋予程序一种方法,从而可以创建单一的文件并且不可以被其他程序同时创建。
第二种方法要更为高级;他允许程序加锁文件的一部分进行排他访问。有两种方法可以做到这种加锁方式。我们只会详细的讨论其中的一种,因为第二种方法很简单,只是具有一个略微不同的程序接口。
创建锁文件
许多程序只需要可以为资源创建一个锁文件。其他的程序可以进行检测以确定他们是否允许访问这些资源。
通常,这些锁文件位于一个特殊的位置,并且其名字与正在被控制的资源相关。例如,当一个调制解调器正在使用时,Linux创建一个锁文件,通常是使用/usr/spool或是/var/spool目录中的一个目录。
记住锁文件的作用只是标识符;程序需要进行合作来使用他们。他们被称之为建议锁(advisory lock),从而与强制锁相区别(mandatory lock),而后一种锁系统会强制锁的行为。
要创建一个使用锁标识符的文件,我们使用定义在fcntl.h中是的open系统调用,并且使用O_CREAT与O_EXCL标志位。这会允许我们检测这个文件是否已经存在,并且以单一原子操作的方式创建这个文件。
试验--创建一个锁文件
下面我们使用lock1.c程序来了解一下创建锁的动作:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
int main()
{
int file_desc;
int save_errno;
file_desc = open(“/tmp/LCK.test”, O_RDWR | O_CREAT | O_EXCL, 0444);
if (file_desc == -1) {
save_errno = errno;
printf(“Open failed with error %d/n”, save_errno);
}
else {
printf(“Open succeeded/n”);
}
exit(EXIT_SUCCESS);
}
当我们第一次运行这个程序时,其输出结果如下:
$ lock1
Open succeeded
但是当我们再次运行这个程序时,我们会得到下面的信息:
$ lock1
Open failed with error 17
工作原理
程序调用open系统调用,并且使用O_CREAT与O_EXCL标记来创建名为/tmp/LCK.test的文件。我们第一次运行这个程序时,文件并不存在,所以open调用是成功的。接下来的程序调用失败是因为这个文件已经存在了。要使得这个程序再次运行成功,我们必须移除这个锁文件。
至少在Linux系统中,错误17指EEXIST,是用来表明文件已经存在的一个错误。错误号定义在errno.h头文件或是更多的由其所包含的文件中。在这种情况下,这个定义实际上位于/usr/include/asm/errno.h文件中:
#define EEXIST 17 /* File exists */
这是对应于open(O_EXCL | O_CREAT)失败的错误。
如果一个程序只是其运行期间的短暂时间内需要排他的使用一个资源,通常将这种情况称之为临界部分(critical section),程序应该在进行临界区之前创建锁文件,并且在退出临界区时使用unlink来删除这个锁文件。
我们可以通过编写一个示例文件并且在同时运行这个程序的两份拷贝来演示程序如何通过组合使用这个锁机制。我们将会使用getpid调用,这个调用我们已经在第四章了解过了,他会返回一个进程标识,通常是为每一个当前正在执行的程序返回一个唯一的标号。
试验--组合文件锁
1 下面是我们的测试程序lock2.c的源代码:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
const char *lock_file = “/tmp/LCK.test2”;
int main()
{
int file_desc;
int tries = 10;
while (tries--) {
file_desc = open(lock_file, O_RDWR | O_CREAT | O_EXCL, 0444);
if (file_desc == -1) {
printf(“%d - Lock already present/n”, getpid());
sleep(3);
}
else {
2 下面是临界区开始的部分:
printf(“%d - I have exclusive access/n”, getpid());
sleep(1);
(void)close(file_desc);
(void)unlink(lock_file);
3 下面是程序的结束部分:
sleep(2);
}
}
exit(EXIT_SUCCESS);
}
要运行这个程序,我们应首先使用下面的命令来保证锁文件不存在:
$ rm -f /tmp/LCK.test2
然后我们用下面的命令来运行程序的两份拷贝:
$ ./lock2 & ./lock2
这会在后端运行lock2的一份拷贝并且在前端运行一份拷贝。下面是我们得到的程序输出:
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
前面的例子演示了同一个程序的两次调用是如何进行合作的。如果我们尝试这个程序,我们在输出中所看到的只是不同的进程标识符,而程序的行为是相同的。
工作原理
为了程序演示的目的,我们使用一个while循环使得程序运行10次。然后程序会通过创建一个唯一的锁文件/tmp/LCK.test2来尝试访问临界资源。如果因为文件已存在而失败,程序会等待一段时间然后再次尝试。如果成功,程序可以访问这个资源,并且在标识为临界区的部分实现排他访问所需要的处理。
因为这只是一个演示,我们只是等待了一小段时间。当程序结束资源的访问时,程序会通过删除锁文件来释放锁。程序可以在重新获得锁之前执行一些其他的操作。锁的作用相当于一个二进制信号量,对于"我可以使用这个资源吗?"的问题给出是或否的回答。我们将会在第14章了解更多有关信号量的内容。
需要注意的很重要的一点就是这是一个合作的任务,我们必须正确的编写程序从而使其工作。如果一个程序创建锁文件失败,他可以简单的删除这个文件并且再次尝试。他也许会创建锁文件,但是创建了锁文件的其他们程序并没有办法知道他已经不再是排他的使用资源了。
文件锁是多用户,多任务操作系统的重要一部分。程序经常需要共享数据,通常是通过文件,所以对于这些程序来说有一种可以建立文件控制的方法是十分重要的。这样文件就可以安全的进行更新,或者当一个程序在写入文件时,另一个程序会阻止自己试图由文件中读取。
Linux有许多我们可以用于文件锁的特性。最简单的方法就是以原子方式创建文件锁的技术,从而当创建了这个锁以后可以阻止其他任何事情的发生。这样就赋予程序一种方法,从而可以创建单一的文件并且不可以被其他程序同时创建。
第二种方法要更为高级;他允许程序加锁文件的一部分进行排他访问。有两种方法可以做到这种加锁方式。我们只会详细的讨论其中的一种,因为第二种方法很简单,只是具有一个略微不同的程序接口。
创建锁文件
许多程序只需要可以为资源创建一个锁文件。其他的程序可以进行检测以确定他们是否允许访问这些资源。
通常,这些锁文件位于一个特殊的位置,并且其名字与正在被控制的资源相关。例如,当一个调制解调器正在使用时,Linux创建一个锁文件,通常是使用/usr/spool或是/var/spool目录中的一个目录。
记住锁文件的作用只是标识符;程序需要进行合作来使用他们。他们被称之为建议锁(advisory lock),从而与强制锁相区别(mandatory lock),而后一种锁系统会强制锁的行为。
要创建一个使用锁标识符的文件,我们使用定义在fcntl.h中是的open系统调用,并且使用O_CREAT与O_EXCL标志位。这会允许我们检测这个文件是否已经存在,并且以单一原子操作的方式创建这个文件。
试验--创建一个锁文件
下面我们使用lock1.c程序来了解一下创建锁的动作:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
int main()
{
int file_desc;
int save_errno;
file_desc = open(“/tmp/LCK.test”, O_RDWR | O_CREAT | O_EXCL, 0444);
if (file_desc == -1) {
save_errno = errno;
printf(“Open failed with error %d/n”, save_errno);
}
else {
printf(“Open succeeded/n”);
}
exit(EXIT_SUCCESS);
}
当我们第一次运行这个程序时,其输出结果如下:
$ lock1
Open succeeded
但是当我们再次运行这个程序时,我们会得到下面的信息:
$ lock1
Open failed with error 17
工作原理
程序调用open系统调用,并且使用O_CREAT与O_EXCL标记来创建名为/tmp/LCK.test的文件。我们第一次运行这个程序时,文件并不存在,所以open调用是成功的。接下来的程序调用失败是因为这个文件已经存在了。要使得这个程序再次运行成功,我们必须移除这个锁文件。
至少在Linux系统中,错误17指EEXIST,是用来表明文件已经存在的一个错误。错误号定义在errno.h头文件或是更多的由其所包含的文件中。在这种情况下,这个定义实际上位于/usr/include/asm/errno.h文件中:
#define EEXIST 17 /* File exists */
这是对应于open(O_EXCL | O_CREAT)失败的错误。
如果一个程序只是其运行期间的短暂时间内需要排他的使用一个资源,通常将这种情况称之为临界部分(critical section),程序应该在进行临界区之前创建锁文件,并且在退出临界区时使用unlink来删除这个锁文件。
我们可以通过编写一个示例文件并且在同时运行这个程序的两份拷贝来演示程序如何通过组合使用这个锁机制。我们将会使用getpid调用,这个调用我们已经在第四章了解过了,他会返回一个进程标识,通常是为每一个当前正在执行的程序返回一个唯一的标号。
试验--组合文件锁
1 下面是我们的测试程序lock2.c的源代码:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
const char *lock_file = “/tmp/LCK.test2”;
int main()
{
int file_desc;
int tries = 10;
while (tries--) {
file_desc = open(lock_file, O_RDWR | O_CREAT | O_EXCL, 0444);
if (file_desc == -1) {
printf(“%d - Lock already present/n”, getpid());
sleep(3);
}
else {
2 下面是临界区开始的部分:
printf(“%d - I have exclusive access/n”, getpid());
sleep(1);
(void)close(file_desc);
(void)unlink(lock_file);
3 下面是程序的结束部分:
sleep(2);
}
}
exit(EXIT_SUCCESS);
}
要运行这个程序,我们应首先使用下面的命令来保证锁文件不存在:
$ rm -f /tmp/LCK.test2
然后我们用下面的命令来运行程序的两份拷贝:
$ ./lock2 & ./lock2
这会在后端运行lock2的一份拷贝并且在前端运行一份拷贝。下面是我们得到的程序输出:
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
前面的例子演示了同一个程序的两次调用是如何进行合作的。如果我们尝试这个程序,我们在输出中所看到的只是不同的进程标识符,而程序的行为是相同的。
工作原理
为了程序演示的目的,我们使用一个while循环使得程序运行10次。然后程序会通过创建一个唯一的锁文件/tmp/LCK.test2来尝试访问临界资源。如果因为文件已存在而失败,程序会等待一段时间然后再次尝试。如果成功,程序可以访问这个资源,并且在标识为临界区的部分实现排他访问所需要的处理。
因为这只是一个演示,我们只是等待了一小段时间。当程序结束资源的访问时,程序会通过删除锁文件来释放锁。程序可以在重新获得锁之前执行一些其他的操作。锁的作用相当于一个二进制信号量,对于"我可以使用这个资源吗?"的问题给出是或否的回答。我们将会在第14章了解更多有关信号量的内容。
需要注意的很重要的一点就是这是一个合作的任务,我们必须正确的编写程序从而使其工作。如果一个程序创建锁文件失败,他可以简单的删除这个文件并且再次尝试。他也许会创建锁文件,但是创建了锁文件的其他们程序并没有办法知道他已经不再是排他的使用资源了。