飞凌嵌入式ElfBoard-文件I/O的深入学习之存储映射I/O

存储映射I/O(memory-mapped I/O)是一种基于内存区域的高级I/O操作,它能将一个文件映射到进程地址空间中的一块内存区域中,当从这段内存中读数据时,就相当于读文件中的数据(对文件进行read操作),将数据写入这段内存时,则相当于将数据直接写入文件中(对文件进行write操作)。这样就可以在不使用系统I/O操作函数read和write的情况下执行I/O操作。

普通I/O方式一般是通过调用read和write函数来实现对文件的读写,使用read和 write读写文件时,函数经过层层的调用后,才能够最终操作到文件,中间涉及到很多的函数调用过程,数据需要在不同的缓存间传递,效率会比较低。

对于存储映射I/O来说,由于源文件和目标文件都已映射到了应用层的内存区域中,所以直接操作映射区来实现文件复制。使用存储映射I/O减少了数据的复制操作,所以在效率上会比普通I/O要高。

然而只有当数据量比较大时,效率的影响才会比较明显,如果数据量比较小,影响并不大,使用普通的I/O方式还是非常方便的。

1.4.4.1 mmap

用于将一个给定的文件映射到进程地址空间中的一块内存区域中。

1. 头文件

#include <sys/mman.h>

2. 函数原型

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

3. 参数

1)addr:用于指定映射到内存区域的起始地址。通常将其设置为NULL,这表示由系统选择该映射区的起始地址,这是最常见的设置方式;如果参数addr不为NULL,则表示由自己指定映射区的起始地址,此函数的返回值是该映射区的起始地址。

2)length:指定映射长度,表示将文件中的多大部分映射到内存区域中,以字节为单位。

3)prot:指定了映射区的保护要求,可取值如下:

⚫PROT_EXEC:映射区可执行;

⚫PROT_READ:映射区可读;

⚫PROT_WRITE:映射区可写;

⚫PROT_NONE:映射区不可访问。

可将 prot 指定为为 PROT_NONE,也可将其设置为PROT_EXEC、PROT_READ、PROT_WRITE 中一个或多个(通过按位或运算符任意组合)。对指定映射区的保护要求不能超过文件 open()时的访问权限。

4)flags:可影响映射区的多种属性,参数flags必须要指定以下两种标志之一:

⚫MAP_SHARED:此标志指定当对映射区写入数据时,数据会写入到文件中,也就是会将写入到映射区中的数据更新到文件中,并且允许其它进程共享。

⚫MAP_PRIVATE:此标志指定当对映射区写入数据时,会创建映射文件的一个私人副本(copy-onwrite),对映射区的任何操作都不会更新到文件中,仅仅只是对文件副本进行读写。

除此之外,还可将以下标志中的0个或多个组合到参数flags中,通过按位或运算符进行组合:

⚫MAP_FIXED:在未指定该标志的情况下,如果参数addr不等于 NULL,表示由调用者自己指定映射区的起始地址,但这只是一种建议、而并非强制,所以内核并不会保证使用参数 addr 指定的值作为映射区的起始地址;如果指定了MAP_FIXED标志,则表示要求必须使用参数addr指定的值作为起始地址,如果使用指定值无法成功建立映射时,则放弃。通常,不建议使用此标志,因为这不利于移植。

⚫MAP_ANONYMOUS:建立匿名映射,此时会忽略参数fd和 offset,不涉及文件,而且映射区域无法和其它进程共享。

⚫MAP_ANON:与MAP_ANONYMOUS 标志同义,不建议使用。

⚫MAP_LOCKED:对映射区域进行上锁。

5)fd:指定要映射到内存区域中的文件。

6)offset:文件映射的偏移量,通常将其置为0,表示从文件头部开始映射。

所以参数 offset 和参数 length 就确定了文件的起始位置和长度,将文件的这部分映射到内存区域中。

参数addr和offset在不为NULL和0的情况下,addr和offset的值通常被要求是系统页大小的整数倍,可通过sysconf函数获取页大小:

sysconf(_SC_PAGE_SIZE) 或 sysconf(_SC_PAGESIZE)

需要注意参数length的值不能大于文件大小,即文件被映射的部分不能超出文件。

4. 返回值

成功情况下,函数的返回值便是映射区的起始地址;发生错误时,返回(void *)-1,通常使用MAP_FAILED来表示,并且会设置errno来指示错误原因。

1.4.4.2 munmap

用于解除存储映射。

1. 头文件

#include <sys/mman.h>

2. 函数原型

int munmap(void *addr, size_t length);

3. 参数

addr:指定待解除映射地址范围的起始地址,它必须是系统页大小的整数倍。

length:指定了待解除映射区域的大小(字节数)。

需要注意的是,当进程终止时也会自动解除映射(如果程序中没有显式调用munmap),但调用close关闭文件时并不会解除映射。

通常将参数addr设置为mmap函数的返回值,将参数length设置为mmap函数的参数length,表示解除整个由mmap函数所创建的映射。

4. 返回值

会返回一个指向描述该错误的字符串的指针。

5. 示例

基于前面2.1.7练习的程序,将其改为存储映射的方式。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #include <stdio.h> #include <stdlib.h> #include <string.h> //strlen的头文件 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> int main() { int fda, fdb; char buf[30] = "123456\n"; size_t length = strlen(buf); void *addrA, *addrB; fda = open("./testA", O_RDWR | O_CREAT | O_TRUNC, 0777); if ( fda < 0 ) { printf("error: A open\n"); return -1; } if ( write(fda, buf, strlen(buf)) != strlen(buf) ) { //通过strlen计算buf的实际字节长度 printf("error: testA write\n"); close(fda); return -1; } fdb = open("./testB", O_RDWR | O_CREAT | O_TRUNC, 0777); if ( fdb < 0 ) { printf("error: testB open\n"); close(fda); return -1; } ftruncate(fdb,length); //由于是存储映射拷贝,需要设置testB文件的长度 addrA = mmap(NULL, length, PROT_READ, MAP_SHARED, fda, 0); if (addrA == MAP_FAILED) { perror("mmap A"); close(fda); close(fdb); return -1; } addrB = mmap(NULL, length, PROT_WRITE, MAP_SHARED, fdb, 0); if (addrB == MAP_FAILED) { perror("mmap B"); close(fda); close(fdb); munmap(addrA, length); return -1; } memcpy(addrB, addrA, length); //内存数据拷贝 munmap(addrA, length); munmap(addrB, length); close(fda); close(fdb); return 0; } |

6.编译运行并查看测试结果

|---------------------------------------|
| cat testA 123456 cat testB 123456 |

存储映射 I/O 方式并不是完美的,它所映射的文件只能是固定大小,因为文件所映射的区域已经在调用mmap函数时通过length参数指定了。另外,文件映射的内存区域的大小必须是系统页大小的整数倍,比如映射文件的大小为100字节,假定系统页大小为1000字节,那么剩余的900字节全部填充为0,虽然可以通过映射地址访问剩余的这些字节数据,但不能在映射文件中反应出来,由此可知,使用存储映射I/O在进行大数据量操作时比较有效;对于少量数据,使用普通I/O方式更加方便。

相关推荐
是一个Bug9 小时前
源码学习方法论
学习
沐风。569 小时前
Object方法
开发语言·前端·javascript
lx7416026989 小时前
change clip架构学习
人工智能·学习·计算机视觉
ChinaRainbowSea9 小时前
github 仓库主页美化定制
java·后端·github
做一道光9 小时前
电机控制——电流采样(三电阻)
单片机·嵌入式硬件·学习·电机控制
程序猿小蒜9 小时前
基于springboot的医院资源管理系统开发与设计
java·前端·spring boot·后端·spring
程序员-周李斌9 小时前
ConcurrentHashMap 源码分析
java·开发语言·哈希算法·散列表·开源软件
JS_GGbond9 小时前
JavaScript入门学习路线图
开发语言·javascript·学习
失败才是人生常态9 小时前
算法题归类学习
学习·算法