内存映射

    这样一来,文件读写就跟内存操作一样直观,无需繁琐的 IO 操作代码。内核负责在内存和磁盘之间同步数据,应用程序开发者完全不用关心其中的细节。此外,IO 系统调用的避免,或多或少提高了程序的执行效率。

    接下来,我们以文件存储结构体为例,演示 mmap 系统调用的用法。

    结构体定义如下:

    student_info

    例子代码是一个函数,将结构体写入到给定文件中,文件路径以及结构体字段均由函数参数给出:

    write_info

    1. * Use system call mmap to write struct to file
    2. *
    3. * Arguments
    4. * path: path of the file for writing
    5. *
    6. * Returns
    7. * 0 if success, -1 if some error happened.
    8. **/
    9. int write_info(char *path, char *name, int age, int score)
    10. {
    11. // open file in read-write mode
    12. // if exists, truncate it; else, create with 644
    13. int fd = open(
    14. path,
    15. O_RDWR|O_CREAT|O_TRUNC,
    16. S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH
    17. );
    18. if (fd == -1) {
    19. perror("fail to open file");
    20. }
    21. // if no enough space, expand it
    22. if (expand_file(fd, sizeof(struct student_info)) == -1) {
    23. return -1;
    24. }
    25. // map the opened file to memory area
    26. struct student_info *infop = (struct student_info *)mmap(
    27. NULL,
    28. sizeof(struct student_info),
    29. PROT_WRITE,
    30. MAP_SHARED,
    31. fd,
    32. 0
    33. );
    34. if (infop == MAP_FAILED) {
    35. perror("fail to map file");
    36. return -1;
    37. }
    38. // copy name string
    39. strncpy(infop->name, name, NAME_LEN_LIMIT-1);
    40. infop->age = age;
    41. infop->score = score;
    42. return 0;
    43. }

    该函数先调用 open 系统调用以 读写模式 打开文件并清空(第 14 行处),文件不存在则创建,权限位为 644

    然后调用 expand_file 函数扩展文件长度,确保至少能容纳一个 student_info 结构体( 第 25 行处 )。expand_file 设计细节不再深入讨论,请自行查看代码:

    接着调用 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 行)。

    只读映射与读写映射类似:

    mmio.c

    不同的地方在于:

    • 文件可以以只读模式打开(第 13 行处);
    • mmap 系统调用 prot 参数指定为 PROT_READ ;文件映射完毕后,即可直接访问结构体字段并输出,非常直观、便捷!

    下面,运行 mmio 程序,以读写模式映射文件并写入数据记录:

    1. $ ./mmio fasion.data 'Fasion Chan' 28 8

    注意到,当前目录确实出现了我们写入的文件:

    运行 hexdump 命令查看文件内容:

    1. $ hexdump -C fasion.data
    2. 00000000 46 61 73 69 6f 6e 20 43 68 61 6e 00 00 00 00 00 |Fasion Chan.....|
    3. 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
    4. 00000020 1c 00 00 00 08 00 00 00 |........|
    5. 00000028

    前两行为 name 字段,总共 32 字节,为以 0 结尾的字符串;最后一行为 age 以及 score 字段,均是 4 字节长整型,二进制内容表明这正是我们写入的。

    接着,我们运行 mmio 程序,以只读模式映射文件并输出内容:

    订阅更新,获取更多学习资料,请关注我们的 微信公众号

    小菜学编程