内存管理
Linux 为应用程序提供了一个简洁的视图,它能反映一个巨大的可直接寻址的内存空间。此外, Linux 还提供了内存保护机制,它避免了不同的应用程序之间的互相干扰。
我们使用 malloc 和 free 函数来完成动态内存的分配和释放。与 DOS 下的程序不能访问超过 640K 的内存相比,在 Linux 系统上使用 malloc 可以开辟兆字节的内存空间。实际上,在 Linux 系统上使用 malloc 可以申请的内存也是有限的:当申请的内存空间足够大时,物理内存将耗尽,此时内核将会开始使用所谓的交换空间( swap space );当物理内存和交换空间都耗尽时,或者栈超出了最大长度时,内核将拒绝内存申请并可能提前终止程序。
提示 : Linux 擅长管理内存,它允许应用程序使用数量非常巨大的内存,甚至使用一个单独的非常大的内存块。但是,必须要记住的是:分配了两块内存并不见得肯定能够得到一个单独的可以连续寻址的内存块,而很有可能是两个分开的内存块。
注意 :由于 malloc 函数返回的是一个 void* 指针,因此我们需要通过类型转换,将其转换至我们需要的指针类型。实际上, malloc 函数可以保证其返回的内存是地址对齐 的(很有可能是 4 字节对齐),因此它返回的指针可以转换为任何类型。
空指针 :当使用 malloc 开辟内存时,往往需要测试返回值是否是空指针。这里要说的是:空指针并非等价于 (void *)0 ,实际上“空指针的内部(或运行期)表达形式很可能并不是全零,而且对不同的指针类型可能不一样”。因此,不要想当然地把 NULL 看成零值,也尽量不要再使用 if(!ptr) 方式来测试 malloc 函数的返回值,应该使用 if(ptr==NULL) 。
文件锁定
Linux 提供了多种特性来实现文件锁定。最简单的方法是以原子操作的方式创建锁文件,所谓“原子操作”就是在创建锁文件时,系统将不允许任何其他的事情发生。
锁文件仅仅只是充当一个指示器的角色,程序间需要通过相互协作来使用它们。为了创建一个用作锁指示器的文件,使用在 fcntl.h 文件中定义的带 O_CREAT 和 O_EXCL 标志的 open 系统调用。
注意 :由上述锁文件方式建立的文件严格仅属于创建它的进程,也就是说,即使使用 open 系统调用创建该锁文件的进程调用 close 关闭了该文件,其他进程也再无法使用 open 系统调用打开该文件。
实际上,用创建锁文件的办法来控制诸如串行口之类的资源的独占式访问时一个不错的选择,但它并不适用于大型的共享文件。这种情况下,我们可以通过文件中的锁定区域来解决这个问题:文件的一部分被锁定,但其他程序可以访问这个文件的其他部分。这杯称为文件段锁定 或文件区锁定。
Linux 提供了至少两种方式来完成这一工作:使用 fcntl 系统调用和使用 lockf 调用。其中 fcntl 系统调用时最常使用的方式。而使用 fcntl 对文件锁定的操作在《精通 UNIX 下 C 语言编程与项目实践》的 学习笔记 3 中有详细的解释。
注意 : fcntl 和 lockf 的锁定机制不能同时工作。它们使用不同的底层实现,因此你决不能混合使用两种类型的调用,而应该坚持使用其中的一种。
提示 :当对文件的区域加锁之后,访问文件中的数据应该使用底层的 read 和 write 调用,而不要使用高级的 fread 和 fwrite 函数,这一点是非常重要的。
dbm 数据库
所有版本的 Linux 以及大多数的 UNIX 版本都随系统带有一个基本的,但却非常高效的数据存储的例程集,称为 dbm 数据库。 dbm 数据库适合于存储相对比较静态的索引化数据。
dbm 数据库的优点 是它非常容易被编译进一个可发布的二进制可执行程序,因为它无需安装独立的服务器,而且即使它需要的底层库文件还未被安装,也不会有什么危险。
dbm 数据允许通过使用索引来存储可变长的数据结构,然后通过索引或简单的顺序扫描数据库来检索结构。 dbm 数据适用于处理那些被频繁访问但却很少被更新的数据,因为它创建数据项时非常慢,而检索时却非常快。
dbm 数据有各种各样不同的版本,这里重点介绍 ndbm 接口。
dbm 数据库的基本元素是需要储存的数据块以及与它关联的在检索数据时用作关键字的数据块。每个 dbm 数据库必须有一个针对每个要处处的数据块的唯一的关键字。为了操纵这些数据块,头文件 ndbm.h 定义了一个名为 datum 的新数据类型。该类型确切的定义依赖于具体实现,但至少包含下面这些成员:
void *dptr; // 数据的起点 size_t dsize; // 数据的长度 |
当我们打开一个 dbm 数据库时,将创建两个物理文件,它们的后缀分别是 .pag 和 .dir ,而仅仅返回一个 DBM 类型指针,它被用来访问这两个文件。这两个文件不应该被直接读写,对它们的访问一定要通过 dbm 例程来进行。类似于 FILE 类型, DBM 类型用来访问数据库的结构。下面给出一个典型的 dbm 使用例程:
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <ndbm.h> #include <assert.h> #include <string.h>
#define DB_FILE "/tmp/dbm_test" // 定义了 dbm 数据库实际存在的位置和名字 #define ITEMS 2
struct test_data{ // 自定义的数据块结构 char misc[15]; int value; char more[21]; };
int main() { struct test_data items[ITEMS]; struct test_data item_got;
char key_to_use[20]; int i, result;
datum key_datum; datum data_datum;
DBM *pDbm;
pDbm = dbm_open(DB_FILE, O_RDWR | O_CREAT, 0666); // 打开 dbm 数据库 assert(pDbm != (DBM *)0);
memset(items, '/0', sizeof(items)); // 预先安排好需要写入的数据块 strncpy(items[0].misc, "First!", 15); items[0].value = 47; strncpy(items[0].more, "foo", 21);
strncpy(items[1].misc, "Second!", 15); items[1].value = 13; strncpy(items[1].more, "bar", 21);
for(i=0;i<ITEMS;i++){ sprintf(key_to_use, "%c%c%d", items[i].misc[0], items[i].more[0], items[i].value); // 自行定义关键字的模式 key_datum.dptr = (void *)key_to_use; key_datum.dsize = strlen(key_to_use); data_datum.dptr = (void *)&items[i]; data_datum.dsize = sizeof(struct test_data); // 写入数据块 result = dbm_store(pDbm, key_datum, data_datum, DBM_REPLACE); assert(result == 0); }
sprintf(key_to_use, "Ff%d", 47); key_datum.dptr = (void *)key_to_use; key_datum.dsize = strlen(key_to_use); data_datum = dbm_fetch(pDbm, key_datum); // 读取数据块 if(data_datum.dptr){ printf("Data got/n"); memcpy(&item_got, data_datum.dptr, data_datum.dsize); printf("Get item - %s %d %s/n", item_got.misc, item_got.value, item_got.more); } else printf("No data found for key %s/n", key_to_use);
dbm_close(pDbm); // 关闭 dbm 数据库
exit(0); } |
上面程序中,我们先存储了两个数据块,继而按关键字读取一个数据块。
首先,我们将需要存储的数据定义为 test_data 结构。 dbm 数据库的打开和关闭使用 dbm_open 和 dbm_close 函数来完成。 dbm_store 函数用于向一个打开的数据库写入一个数据块,第一个参数指定了打开的 dbm 数据库对应的 DBM 指针,第二、三个参数指明了数据块及其关键字对应的 datum 结构变量,第四个参数则指明了操作类型(常用 DBM_REPLACE )。每个数据块对应的关键字可以是任意形式的,但在实际编程中最好统一模式。函数 dbm_fetch 用来从打开的 dbm 数据库中提取一个数据块,第二个参数指明了需要提取数据块的关键字。
至于其他的 dbm 函数可以自行查看手册。本章最后展示了 CD 唱片应用程序的完整代码。它详细地描述了如何使用 dbm 数据库来完成数据的存储和访问,为我们自己设计使用 dbm 数据库带来了很大的帮助。