内存映射I/O
提出内存映射I/O概念的根本原因是为了解决读写文件的效率问题。如果一个程序需要大量的磁盘IO时,内存映射IO往往能够使程序执行的速度有很大地提高,内存映射I/O也可以算作是一种空间换时间的机制。
内存映射I/O的概念
- 当用户对一个文件执行I/O操作时,通常需要使用一个内核的缓冲区。输入或者输出的内容先通过该缓冲区和外部设备发生数据交换。
注意: 文件读写函数的缓冲区的大小是由内核确定的,用户无法更改。 - 问题: 如果需要读写一个大于该缓冲区大小的文件则需要多次与外部设备发生交互,因此导致了程序运行速度的损失。Linux 系统下可以使用内存映射IO来解决该问题。
- 概念: 内存映射IO是一种高速读写文件的I/O方式,其允许将一个磁盘文件与内存中的一个缓冲区建立一个映射关系。当用户从该缓冲区中读入数据的时候,就相当于从映射的文件中读取数据;当用户向该缓冲区写入数据的时候,就相当于向映射的文件输出数据。这种方法可以在不使用read 函数和write 函数的情况下执行文件I/O操作,从而可以避免由于内核提供的缓冲区过小而造成的I/O效率的瓶颈 ,如图所示。
创建一个内存映射
linux使用mmap函数创建一个磁盘文件到内存的映射,函数原型为:
#include<sys/mman.h>
void mmap(void *addr,size_t len,int prot,int flag,int filedes,off_t off);
参数说明:
- addr:表示建立内存映射的起始地址,该参数通常设置为NULL,表示由机器自动选择;
- len:表示需要映射的磁盘文件的长度,单位是字节数;
- prot:表示该内存映射空间的访问权限,注意不能和文件的打开方式冲突
- flag:表示映射区的属性;
- filedes:表示需要进行内存映射的文件描述符,因此,进行内存映射的文件必须是一个已经打开的文件;
- off:表示磁盘文件的起始映射位置在文件中的偏移量;
- 返回值:成功创建则返回映射区的首地址,失败返回MAP_FAILED(宏);
应用实例:
功能说明:该映射区中读取文件的内容,下面实例演示了创建一个内存映射,并从该映射区中读取文件的内容, 将读入的内容输出到屏幕上。
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
int main(void)
{
int fd;
char *buf;
int i;
struct stat statbuf;
if(stat("test.txt", &statbuf) == -1){ /* 得到一个文件的状态信息,得到文件的大小 */
perror("fail to get stat");
exit(1);
}
fd = open("test.txt", O_RDONLY); /* 以只读方式打开文件 */
if(fd == -1){
perror("fail to open");
exit(1);
}
/* 建立一个内存映射,起始地址由系统为用户选择,并作为返回值返回
* 建立的映射区的大小为打开的文件的大小
* 访问权限为只读,属性为不会写到磁盘,防止对其进行写的误操作
*/
buf = (char *)mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if(buf == MAP_FAILED){
perror("fail to mmap");
exit(1);
}
i = 0;
while(i < statbuf.st_size){ /* 输出每一个字符,注意mmap函数不会添加’\0’结束符 */
printf("%c", buf[i]);
i++;
}
printf("\n");
if(munmap(buf, statbuf.st_size) == -1){ /* 撤销内存映射 */
perror("fail to munmap");
exit(1);
}
close(fd); /* 关闭文件 */
return 0;
}
撤销一个内存映射
munmap函数原型:
#include<sys/mman.h>
int munmap(caddr_t addr,size_t len);
参数说明:
- 返回值:如果成功撤销一个映射区,返回0,失败返回-1;
- 注意:munmap函数撤销一个映射区,可是并不能影响到被映射的文件。也就是说调用了munmap函数之后,并不能保证该映射区的内容被写入磁盘文件。当一个映射区被撤销后,如果其属性为MAP_ SHARED,其内容写入磁盘文件的时间依赖于系统的守护进程的调度;如果其属性为MAP_ PRIVATE,其内容在映射区被撤销后自动丢弃。
内存映射同步
问题描述: 同使用read和write函数读写一样,使用内存映射的方法同样无法保证修改的内容及时写到磁盘上,每次将修改过的内存页面写回磁盘依赖于系统内部的内存换页机制,因此,如果需要将修改的内容及时回写,需要调用特殊的系统调用才可以。
linux使用msync函数将修改过的内存页面写回到磁盘文件,函数原型为:
#include<sys/mman.h>
int msync(void *addr,size_t len,int flags);
参数说明:
-
addr:表示需要写回磁盘的内存映射区;
-
len:需要写回的字节数;
-
flags:回写的标志;
注意:MS_ASYNC标志和MS_ SYNC标志必须指定其中的一个,而MS_INVALIDATE标志则是可选的。 -
返回值:成功将制定页面写回,返回0,失败返回-1;
更改内存映射的权限
linux使用mprotect函数更改一个映射区的权限:
#include<sys/mman.h>
int mprotect(void *addr,size_t len,int PROT);
参数说明:
- PROT:表示修改后的权限
- 返回值:成功修改返回0,失败返回-1;