简介
存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区映射,于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件,这样,就可在不不适用read和write函数的情况下,使用地址(指针)完成I/O操作。

1、使用场景

UNIX网络编程第二卷进程间通信对mmap函数进行了说明。该函数主要用途有三个:
(1) 将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能。
 (2)将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间。
 (3)为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中。

2.调用函数 介绍

#include <sys/mman.h>

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

参数:

addr: 指定映射被放置的虚拟地址,如果将addr指定为NULL,那么内核会为映射分配一个合适的地址。如果addr为一个非NULL值,则内核在选择地址映射时会将该参数值作为一个提示信息来处理。不管采用何种方式,内核会选择一个不与任何既有映射冲突的地址。在处理过程中, 内核会将指定的地址舍入到最近的一个分页边界处。
length:参数指定了映射的字节数。尽管length 无需是一个系统分页大小的倍数,但内核会以分页大小为单位来创建映射,因此实际上length会被向上提升为分页大小的下一个倍数。
prot: 参数掩码,用于指定映射上的保护信息:标记有:

值 描述
PROT_NONE 区域无法访问
PROT_READ 区域内容可读取
PROT_WRITE 区域内容可修改
PROT_EXEC 区域内容可执行

flags:用于指定映射类型
flags 描述
MAP_PRIVATE 创建一个私有映射。映射区域中内存发生变化对使用同一映射 的其他进程不可见。对文件映射来讲,所发生的变更将不会反应在底层文件上。
MAP_SHARED 区域中内容上所发生的变更对使用同一个映射区域的其他进程可见
MAP_ANONYMOUS 创建一个匿名映射
MAP_FIXED 原样解释addr
MAP_LOCKED 将映射分页锁进内存
MAP_HUGETLB 创建一个使用巨页的映射
MAP_HUGE_2MB 与MAP_HUGETLB一起使用,映射的巨页的页大小为2MB
MAP_NORESERVE 控制交换空间预留
MAP_POPULATE 填充一个映射的分页
MAP_UNINITALIZED 防止一个匿名映射被清零
MAP_32BIT 仅在x86-64系统下支持,映射空间位于前2G空间
MAP_STACK 目前在linux中没有实现,映射空间为stack 空间
MAP_SHARED_VALIDATE 与MAP_SHARED功能一样,区别就是MAP_SHARED会忽略未知flag设置,而MAP_SHARED_VALIDATE会对flags进行检查,如果是unknow flags将会返回EONNOTSUPP
MAP_SYNC 只有和MAP_SHARED_VALIDATE一起使用才有效,仅支持DAX 文件,如果是其他类型文件将会返回错误。

 

匿名映射/文件映射

mmap按照映射的类型主要可以分为文件映射和匿名映射。

    文件映射:文件映射是将一个文件的一部分直接映射到调用进程的虚拟内存中,一旦一个文件被映射之后就可以通过在相应内存区域中操作字节来访问文件内容了。映射的分页会在需要的时候从文件中(自动)加载。这种映射被称为基于文件映射或内存映射文件。
    匿名映射:一个匿名映射没有相应的文件。相反,这种映射的页面会被初始化为0.

同时又按照私有映射(MAP_PRIVATE)和共享映射分别将文件映射和匿名映射划分成不同使用用途:
 

使用方法:

 匿名映射

匿名映射没有对应文件映射,其创建方法有两种:

    在flags中指定MAP_ANONYMOUS并将fd指定为-1
    打开/dev/zero设备文件并将得到的文件描述符传递给mmap.。/dev/zero是一个虚拟设备,当从中读取数据时它总是会返回0,而写入到这个设备中的数据总是被丢弃。/dev/zero的一个常见用途使用0来组装一个文件。

文件映射

文件映射创建需要执行下面的步骤:

    获取文件的一个描述符,通常通过调用open()来完成。
    将文件描述符作为fd参数传入mmap()调用。
总结:

内存映射I/O所带来的性能优势在大型文件中执行重复随机访问时最有可能体现出来。如果顺序访问一个文件,并假设执行I/O时使用的缓冲区大小足够大以至于能够避免执行大量的I/O系统调用,那么与read()和write()相比,mmap带来的性能上的提升就非常有限或者说根本就没有带来性能上的提升。性能提升的幅度之所以非常有限的原因是不管使用何种技术,真个文件的内容在磁盘和内存之间只传输一次,效率的提高主要得益于减少了用户空间和内核空间之间得一次数据传输,并且与磁盘I/O所需得时间相比,内存使用量得降低通常是可以忽略的。
 

使用样例:

#include <sys/mman.h>  
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <error.h>
#include<string.h>

#define BUF_SIZE 100

char *mapped creatMMap(const char * filename,struct stat *sb) // failed return nullptr succ mpped;
{
char *mapped=nullptr;
/* 打开文件 */
if ((fd = open(filename, O_RDWR)) < 0) {
perror("open");
return mapped;
}

/* 获取文件的属性 */
if ((fstat(fd, sb)) == -1) {
perror("fstat");
return mapped;
}

/* 将文件映射至进程的地址空间 */
if ((mapped = (char *)mmap(NULL, sb->st_size, PROT_READ |
PROT_WRITE, MAP_SHARED, fd, 0)) == (void *)-1) {
perror("mmap");
return mapped;
}

/* 映射完后, 关闭文件也可以操纵内存 */
close(fd);

return mapped;

}

bool readMMap(char *mapped,char * dest, long start,long length) // start :开始位置,读取长度;
{


memcpy(readmap,mapped+start,length);

printf("read =========================\n");

printf("%s\n",readmap);
}

bool writeMMap(char* mapped ,char* src,long st_size,bool sysn) // src :写入数据,是否同步到本地 sysn;st_size:同步的长度;
{

memcpy(mapped,(void*)newStr,strlen(newStr));

if(sysn)
if ((msync((void *)mapped, st_size, MS_SYNC)) == -1) {
perror("msync");
return false;
}

return true;
}

int main(int argc, char **argv)
{
int fd, nread, i;
struct stat sb;
char *mapped = creatMMap("./data.txt",&sb);


// printf memory map context;
printf("111 %s\n", mapped);

// read memory map
char readmap[128]={0};
readMMap(mapped,readmap, 0,20);

printf("new =========================\n");
/* 修改,同步到磁盘文件 */
void * mpt = &mapped ;

char newStr[128]="====hello===================\n";

writeMMap(mpt,newStr,sb.Size,true);
// printf memory map context;
printf("222 %s", mapped);


while(1)
{
printf("33 %s", mapped);
sleep(2);
}
/* 释放存储映射区 */
if ((munmap((void *)mapped, sb.st_size)) == -1) {
perror("munmap");
}

return 0;
}