内存映射
通过 mmap 系统调用,可以将文件映射到进程地址空间进行读写。
这样一来,文件读写就跟内存操作一样直观,无需繁琐的 IO 操作代码。内核负责在内存和磁盘之间同步数据,应用程序开发者完全不用关心其中的细节。此外,IO 系统调用的避免,或多或少提高了程序的执行效率。
接下来,我们以文件存储结构体为例,演示 mmap 系统调用的用法。
结构体定义如下:
#define NAME_LEN_LIMIT 32
struct student_info
{
char name[NAME_LEN_LIMIT];
int age;
int score;
};
读写映射
例子代码是一个函数,将结构体写入到给定文件中,文件路径以及结构体字段均由函数参数给出:
/**
* Use system call mmap to write struct to file
*
* Arguments
* path: path of the file for writing
*
* Returns
* 0 if success, -1 if some error happened.
**/
int write_info(char *path, char *name, int age, int score)
{
// open file in read-write mode
// if exists, truncate it; else, create with 644
int fd = open(
path,
O_RDWR|O_CREAT|O_TRUNC,
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH
);
if (fd == -1) {
perror("fail to open file");
return -1;
}
// if no enough space, expand it
if (expand_file(fd, sizeof(struct student_info)) == -1) {
return -1;
}
// map the opened file to memory area
struct student_info *infop = (struct student_info *)mmap(
NULL,
sizeof(struct student_info),
PROT_WRITE,
MAP_SHARED,
fd,
0
);
if (infop == MAP_FAILED) {
perror("fail to map file");
return -1;
}
// copy name string
strncpy(infop->name, name, NAME_LEN_LIMIT-1);
infop->name[NAME_LEN_LIMIT-1] = '\0';
// set other fields
infop->age = age;
infop->score = score;
return 0;
}
该函数先调用 open 系统调用以 读写模式 打开文件并清空(第 14 行处),文件不存在则创建,权限位为 644 。
然后调用 expand_file 函数扩展文件长度,确保至少能容纳一个 student_info 结构体( 第 25 行处 )。expand_file 设计细节不再深入讨论,请自行查看代码:mmio.c
接着调用 mmap 系统调用将文件映射进内存中(第 30 行处)。 mmap 各个参数说明如下:
- addr ,内存起始地址,指定为
NULL
则由内核自行分配; - len ,内存映射区区长度;
- prot ,内存区访问权限;
- flags , 映射类型 标志位;
- fd ,被映射文件描述符;
- offset ,被映射文件偏移量;
其中,内存区访问权限可以是以下值的或操作组合:
PROT_NONE
,内存页 不可访问 ;PROT_READ
,内存页 可读 ;PROT_WRITE
,内存页 可写 ;PROT_EXEC
,内存页 可执行 ;
映射类型 决定内存区修改是否对其他进程可见,以及是否应用至原文件:
MAP_SHARED
,修改对其他进程可见,并同步到文件;MAPPRIVATE
,创建 私有写复制 ( _copy-on-write )映射,因此修改对其他进程不可见,也不同步至文件;
映射完毕后,对结构体的操作将被应用到文件(第 44-49 行)。
只读映射
只读映射与读写映射类似:
/**
* Use mmap to read struct from file
*
* Arguments
* path: path of the file for reading
*
* Returns
* 0 if success, -1 if some error happened.
**/
int read_info(char *path)
{
// open file in read only mode
int fd = open(path, O_RDONLY);
if (fd == -1) {
perror("fail to open file");
return -1;
}
// map the opened file to memory area
struct student_info *infop = (struct student_info *)mmap(
NULL,
sizeof(struct student_info),
PROT_READ,
MAP_SHARED,
fd,
0
);
if (infop == NULL) {
perror("fail to map file");
return -1;
}
// print info
printf("Name: %s\n", infop->name);
printf("Age: %d\n", infop->age);
printf("Score: %d\n", infop->score);
return 0;
}
不同的地方在于:
- 文件可以以只读模式打开(第 13 行处);
- mmap 系统调用 prot 参数指定为
PROT_READ
;文件映射完毕后,即可直接访问结构体字段并输出,非常直观、便捷!
程序演示
下面,运行 mmio 程序,以读写模式映射文件并写入数据记录:
$ ./mmio fasion.data 'Fasion Chan' 28 8
注意到,当前目录确实出现了我们写入的文件:
$ ls
fasion.data mmio mmio.c
运行 hexdump 命令查看文件内容:
$ hexdump -C fasion.data
00000000 46 61 73 69 6f 6e 20 43 68 61 6e 00 00 00 00 00 |Fasion Chan.....|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000020 1c 00 00 00 08 00 00 00 |........|
00000028
前两行为 name 字段,总共 32 字节,为以 0 结尾的字符串;最后一行为 age 以及 score 字段,均是 4 字节长整型,二进制内容表明这正是我们写入的。
接着,我们运行 mmio 程序,以只读模式映射文件并输出内容:
$ ./mmio fasion.data
Name: Fasion Chan
Age: 28
Score: 8
下一步
订阅更新,获取更多学习资料,请关注我们的 微信公众号 :