mmap技术研究及应用

一、简介:

mmap是一种内存映射文件的方法。即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享

二、BIO(常规文件操作或传统IO)和mmap区别:

总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。

image.png

三、优缺点

优点:(高性能,操作文件就像操作内存一下,适合对较大文件的读写)
①对文件的读写操作跨国也页缓存,减少数据的拷贝次数,用内存读写取代IO流读写,提高了文件读写效率(Andorid加载.dex文件也通过使用此技术);
②实现用户空间和内核空间的高效交互方式;
③提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。
④实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助磁盘空间协助操作,补充内存的不足。但是进一步会照成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好解决,需要用磁盘空间替代内存的时候,mmap都可以发挥其功效

缺点:①文件如果很小,比如小于4K的,比如60bytes,由于在内存当中的组织都是按页组织的,将文件调入到内存当中是一个页4K,相当于4096-60=4036bytes的内存空间浪费掉了;②文件无法完成拓展,因为mmap到内存的时候,你所能操作的范围就已经确定了,无法增加文件长度。
使用场景:
①对同一块区域频繁读写操作;
②用户日志、数据上报等,微信开源mars框架中的xlog模块就是基于mmap特性实现;
③跨进程同步的时候,mmap是个不错的选择,Android跨进程通信有自己独有的Binder机制,内部使用mmap实现;
Java层面使用:MappedByteBuffer已经封装好
C++代码实现:mmap

四、内存映射原理

  • 进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域
  • 调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系
  • 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝

五、mmap相关函数

①建立映射关系函数:
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
成功执行时,mmap()返回被映射区的指针地址。失败时,mmap()返回MAP_FAILED[其值为(void *)-1]
②解除映射关系函数:
int munmap( void * addr, size_t len )
成功执行时,munmap()返回0。失败时,munmap返回-1,error返回标志和mmap一致;
addr是调用mmap()时返回的地址,len是映射区的大小;
③实时同步写入:
int msync( void *addr, size_t len, int flags )
一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。
可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

六、mmap使用细节

mmap映射区域大小必须是物理页大小(page_size)的倍数(32位系统中通常是4k字节),原因是内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位;
②内核可以跟踪被内存映射的底层对象(文件)的大小,进程可以合法的访问在当前文件大小以内又在内存映射区以内的那些字节
③映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关。

七、使用:

①C++使用mmap读写文件:

//-------------------------------------------读取文件----------------------------------
// 打开文件
int fd = open("input.txt", O_RDONLY);  
// 读取文件长度
int len = lseek(fd,0,SEEK_END);  
// 建立内存映射
char *addr = (char *) mmap(NULL, len, PROT_READ, MAP_PRIVATE,fd, 0);      
close(fd);
// data用于保存读取的数据
char* data; 
// 复制过来
memcpy(data, addr, len);
// 解除映射
munmap(addr, len)

//-------------------------------------------写入文件----------------------------------
//假设写入的数据放在char* data中
int len = data.length();
// 打开文件
int fd=open("output.txt", O_RDWR|O_CREAT, 00777);
// lseek将文件指针往后移动file_size-1位
lseek(fd,len-1,SEEK_END);  
// 从指针处写入一个空字符;mmap不能扩展文件长度,这里相当于预先给文件长度,准备一个空架子
write(fd, "", 1);
// 使用mmap函数建立内存映射
char* addr = (char*)mmap(NULL, len, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
// 内存映射建立好了,此时可以关闭文件了
close(fd);
// 把data复制到addr里
memcpy(addr, data, len);
// 解除映射
munmap(addr, len)

①Java使用NIO库中的MappedByteBuffer实现mmap读写文件:

try {
           byte[] originalByte = "你好奥,我是好人".getBytes();
           RandomAccessFile raf = new RandomAccessFile(file, "rw");
           ////position映射文件的起始位置,size映射文件的大小
           MappedByteBuffer map = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 1024);
           //写入数据
           map.put(originalByte);

           byte[] newData= new byte[originalByte.length];
           //获取数据
           map.get(newData);
           Log.d(TAG, "data:" + String.valueOf(newData));
       } catch (FileNotFoundException e) {
           e.printStackTrace();
       } catch (IOException e) {
           e.printStackTrace();
       }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Linux进程通信实现机制有很多,也有各自优缺点和适用场景,关于她们之间的对比,等各种通信机制一一介绍后,再来一个...
    batbattle阅读 9,552评论 3 13
  • ​通信是Android开发必不可少的一部分,不管是我们做应用App开发,还是Android系统,都使用了大量的通信...
    子者不语阅读 3,335评论 0 1
  • 1.目前很多博客说的五种模型都是从read角度来描述的。 2.我们也常会说Direct IO,或者其他文件IO。他...
    简书徐小耳阅读 5,532评论 0 5
  • C++虚函数: 多态: 静态多态(重载)、动态多态(虚函数) 虚函数 虚函数表:编译器为每个类创建了一个虚函数表...
    Supreme_DJK阅读 4,410评论 0 0
  • 前言 看这篇文章之前需要知道一个概念 虚拟内存系统通过将虚拟内存分割为称作虚拟页(Virtual Page,VP)...
    叫我不矜持阅读 64,190评论 7 29