数据管理2(Beginning Linux Programming 笔记11)
p297 文件锁定
有两种类型的文件锁定,最常见的是非内核实现的劝告式锁定(advisory locking),其方法是所有进程遵循一个约定,也就是下面说的文件锁。另外一种是强制锁(mandatory locking),它是由内核强制实施的,当一个锁被创建时,其他进程对被锁文件进行读写操作时会被挂起,椅子后到锁定释放,因为锁定在 read 和 write 上,会降低系统调用的性能。
文件锁定对多用户多任务操作系统是非常重要的,进程之间经常需要通过文件来共享数据。linux 下的文件锁不仅仅可以通过创建锁定文件(lock file)的原子操作来保证操作唯一性,还能针对文件的某一部分做排他性访问的锁操作。有一种说法,分别称这俩种锁为文件锁和记录锁(record lock)。
如果程序需要排他性的访问某个资源,可以通过先创建一个约定的文件锁的方式,约定其他访问该资源的程序都先检查该文件是否存在。处理完某个资源后,把该锁文件 unlink ,以便其他进程访问该资源。linux 系统一般把文件锁创建在 /var/spool 下。使用 open 创建文件锁时,需要使用 O_CREAT 和 O_EXCL 标识,这样才能以原子操作的方式保证创建有效锁,下面的程序中,只创建了锁,没有释放锁:
#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);
}
linux 下第二次执行上面的程序,会返回错误码为 17 ,是因为文件存在的错误码 EEXIST 的值被定义为 17 ,这也正是由于指定了 O_CREAT | O_EXCL 。一种常见创建文件锁的方法是把发起锁的进程的 id 写到文件中,这样可以通过检查进程 id 是否存在,防止锁被创建以后,在删除文件锁之前程序意外终止导致文件琐一直存在。
#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 {
/* critical region */
printf("%d - I have exclusive access\n", getpid());
sleep(1);
(void)close(file_desc);
(void)unlink(lock_file);
/* non-critical region */
sleep(2);
}
} /* while */
exit(EXIT_SUCCESS);
}
p301 局部锁(locking regions)
PS: 这里很多笔记都来自<
就是别的书上说的记录锁。需要访问一个大的共享文件时,文件锁就显得局限了。linux 允许锁定文件的某些部分(regions),其他进程可以访问文件的其他没有锁定的部分。记录锁可以锁定文件的任意部分,比如进程 A 可以锁定一个文件从 50 字节 200 字节的部分,另一个进程 B 则可以锁定 300 字节到 350 字节的部分,这两个锁之间并不冲突。记录锁的另外一个优点是被内核保存,而不是文件系统实现的。当一个进程终止后,它所保存的所有锁都会被自动释放。这种锁也是劝告式的。linux 有提记录锁的强制式变体,但是使用不是很方便。
记录锁有两种类型,读锁和写锁,读锁实际上是共享锁,因为多个进程可以同时对一个区域保持读锁。但是对一个记录只能有一个进程保持写锁,写锁必须的胡斥的。进程在需要写文件时,必须得到一个写锁,写锁存在时,这个记录应该也应该没有读锁存在。保证读写不冲突。
至少有两种方法能实现局部锁: fcntl 和 lockf ,下面主要看用 fcntl 来实现的方式,注意两者虽然比较相似,但是不能同时奏效,对同一个文件,不能混合使用两种锁定方法。
int fcntl(int fildes, int command, struct flock *flock_structure);
第一个参数为需要操作的文件 fd , 第二个参数为需要执行任务或者说命令: F_GETLK, F_SETLK, F_SETLKW 后面分别详细介绍。第三个参数为 一个文件结构体 flock ,包含以下成员:
- l_type 锁的类型,可以是 F_RDLCK 设置为读锁(共享锁);F_WRLCK 设置为写锁(互斥锁);F_UNLCK 删除锁
- l_start l_whence 配合使用指定锁定区域, 可以是 SEEK_SET, SEEK_CUR, SEEK_END ,具体 lseek 函数
- l_len 指定锁定长度
- l_pid 只有当查询锁的时候才会使用,它被设置为拥有被查询锁的进程的 pid
command 取值:
- F_SETLK 设置 flock 结构描述的锁,如果因为与另外一个进程的锁冲突而使得锁无法创建,则返回 EAGAIN 。l_type 为 F_UNLCK 则删除锁
- F_SETLKW 和 F_SETLK 类似,但是一直阻塞锁直到锁被创建创建成功,如果在进程被阻塞时有信号发生, fcntl 调用返回 EAGAIN
- F_GETLK 检查所描述的锁是否被创建成功,如果已经创建,flock 结构除了 l_type 以外不会改变: l_type 被改成 F_UNLCK (还不大明白);如果创建锁失败,l_pid 被设置为保持这个锁的进程 pid 。
#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) {
/* set up the region we wish to test */
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 the 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 we wish to test */
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);
/* now test the 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_RDLCK - Lock would succeed\n");
}
} /* for */
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);
}
书中还讲述了 竞争锁以及另外一种记录锁实现 lockf ,暂不细看。
p314 数据库
大多数 unix 系的操作系统都支持一种高效的数据库管理系统 dbm 。其实有些人怀疑它是否可以作为一种数据库,因为它只具备很简单的数据库功能,可以说就是一个文件索引存储系统。RPM 打包系统就使用 dbm 保存已经安装包的信息的存储介质。开源的 Open LDAP 以及 apache , sendmail 也使用 dbm 保存数据。相比 mysql 等数据库系统, dbm 非常轻量,不需要安装独立的数据库 server 。dbm 非常适合读很读多但是写很少的数据。linux 系统一般带 dbm 的 GNU 版,也就是 gdbm 。下面是个操作实例:
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <ndbm.h>
/* On some systems you need to replace the above with
#include <gdbm-ndbm.h>
*/
#include <string.h>
#define TEST_DB_FILE "/tmp/dbm1_test"
#define ITEMS_USED 3
/* A struct to use to test dbm */
struct test_data {
char misc_chars[15];
int any_integer;
char more_chars[21];
};
int main() {
struct test_data items_to_store[ITEMS_USED];
struct test_data item_retrieved;
char key_to_use[20];
int i, result;
datum key_datum;
datum data_datum;
DBM *dbm_ptr;
dbm_ptr = dbm_open(TEST_DB_FILE, O_RDWR | O_CREAT, 0666);
if (!dbm_ptr) {
fprintf(stderr, "Failed to open database\n");
exit(EXIT_FAILURE);
}
/* put some data in the structures */
memset(items_to_store, '\0', sizeof(items_to_store));
strcpy(items_to_store[0].misc_chars, "First!");
items_to_store[0].any_integer = 47;
strcpy(items_to_store[0].more_chars, "foo");
strcpy(items_to_store[1].misc_chars, "bar");
items_to_store[1].any_integer = 13;
strcpy(items_to_store[1].more_chars, "unlucky?");
strcpy(items_to_store[2].misc_chars, "Third");
items_to_store[2].any_integer = 3;
strcpy(items_to_store[2].more_chars, "baz");
for (i = 0; i < ITEMS_USED; i++) {
/* build a key to use */
sprintf(key_to_use, "%c%c%d",
items_to_store[i].misc_chars[0],
items_to_store[i].more_chars[0],
items_to_store[i].any_integer);
/* build the key datum strcture */
key_datum.dptr = (void *)key_to_use;
key_datum.dsize = strlen(key_to_use);
data_datum.dptr = (void *)&items_to_store[i];
data_datum.dsize = sizeof(struct test_data);
result = dbm_store(dbm_ptr, key_datum, data_datum, DBM_REPLACE);
if (result != 0) {
fprintf(stderr, "dbm_store failed on key %s\n", key_to_use);
exit(2);
}
} /* for */
/* now try and retrieve some data */
sprintf(key_to_use, "bu%d", 13); /* this is the key for the second item */
key_datum.dptr = key_to_use;
key_datum.dsize = strlen(key_to_use);
data_datum = dbm_fetch(dbm_ptr, key_datum);
if (data_datum.dptr) {
printf("Data retrieved\n");
memcpy(&item_retrieved, data_datum.dptr, data_datum.dsize);
printf("Retrieved item - %s %d %s\n",
item_retrieved.misc_chars,
item_retrieved.any_integer,
item_retrieved.more_chars);
}
else {
printf("No data found for key %s\n", key_to_use);
}
dbm_close(dbm_ptr);
exit(EXIT_SUCCESS);
}
dbm 以及 mysql 更具体详细介绍暂不看。后面几章也不是我关注的点,暂时忽略不看。直接跳到 第十一章 进程和信号。

已然开始linux编程了阿!!
牛!
瞎折腾折腾而已… 学点东西充实自己 :)
-
确实是不错~~~~~~
确实是不错~~~~~~