Android中mmap原理及应用简析

1.mmap介绍

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

网上很多文章都说 mmap 完全绕开了页缓存机制,其实这并不正确。我们最终映射的物理内存依然在页缓存中,它可以带来的好处有:

  • 减少系统调用。我们只需要一次 mmap() 系统调用,后续所有的调用像操作内存一样,而不会出现大量的 read/write 系统调用。
  • 减少数据拷贝。普通的 read() 调用,数据需要经过两次拷贝;而 mmap 只需要从磁盘拷贝一次就可以了,并且由于做过内存映射,也不需要再拷贝回用户空间。
  • 可靠性高。mmap 把数据写入页缓存后,跟缓存 I/O 的延迟写机制一样,可以依靠内核线程定期写回磁盘。

mmap

从上面的图看来,我们使用 mmap 仅仅只需要一次数据拷贝。

2.mmap使用场景

mmap 比较适合于对同一块区域频繁读写的情况,推荐也使用线程来操作。

  • 用户日志、数据上报都满足这种场景,微信开源的 mars 框架中的 xlog模块也是基于 mmap 特性实现的。
  • 需要跨进程同步的时候,mmap 也是一个不错的选择,Android 跨进程通信有自己独有的 Binder 机制,它内部也是使用 mmap 实现。

3.具体实现

在Android中可以将文件通过Java提供的MappedByteBuffer映射到内存,然后进行读写。(微信的xlog模块mmap实现是基于C++代码实现)

MappedByteBuffer 位于 Java NIO 包下,用于将文件内容映射到缓冲区,使用的即是 mmap 技术。通过 FileChannel 的 map 方法可以创建缓冲区。

RandomAccessFile raf = new RandomAccessFile(file, "rw");
//position映射文件的起始位置,size映射文件的大小
MappedByteBuffer buffer = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, position, size);
//往缓冲区里写入字节数据
buffer.put(log);

有一点比较坑,Java 虽然提供了 map 方法,但是并没有提供 unmap 方法,通过 Google 得知 unmap 方法是有的,不过是私有的,我们可以通过反射调用获取unmap方法(Android 9.0以上对反射做了限制,可以参考这篇博文绕过限制)

    /**
     * 解除内存与文件的映射
     * */
    private void unmap(MappedByteBuffer mbbi) {
        if (mbbi == null) {
            return;
        }
        try {
            Class<?> clazz = Class.forName("sun.nio.ch.FileChannelImpl");
            Method m = clazz.getDeclaredMethod("unmap", MappedByteBuffer.class);
            m.setAccessible(true);
            m.invoke(null, mbbi);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

4.原理

普通文件mmap原理

普通文件的访问方式有两种:第一种是通过read/write系统调访问,先在用户空间分配一段buffer,然后,进入内核,将内容从磁盘读取到内核缓冲,最后,拷贝到用户进程空间,至少牵扯到两次数据拷贝;同时,多个进程同时访问一个文件,每个进程都有一个副本,存在资源浪费的问题。另一种是通过mmap来访问文件,mmap()将文件直接映射到用户空间,文件在mmap的时候,内存并未真正分配,只有在第一次读取/写入的时候才会触发,这个时候,会引发缺页中断,在处理缺页中断的时候,完成内存也分配,同时也完成文件数据的拷贝。并且,修改用户空间对应的页表,完成到物理内存到用户空间的映射,这种方式只存在一次数据拷贝,效率更高。同时多进程间通过mmap共享文件数据的时候,仅需要一块物理内存就够了。

匿名共享内存原理

Android匿名共享内存是基于Linux共享内存的,都是在tmpfs文件系统上新建文件,并将其映射到不同的进程空间,从而达到共享内存的目的,只是,Android在Linux的基础上进行了改造,并借助Binder+fd文件描述符实现了共享内存的传递。

 

参考:https://www.jianshu.com/p/71c9b73d788e

           https://blog.csdn.net/u014602917/article/details/105390368

           https://www.jianshu.com/p/d9bc9c668ba6

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Linux Cmmap的数据传输应用实例可以通过以下代码实现: #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> void mmap_transfer(const char* src_file, const char* dest_file) { int src_fd, dest_fd; struct stat src_sb; void* src_start; void* dest_start; src_fd = open(src_file, O_RDONLY); // 打开源文件 dest_fd = open(dest_file, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); // 创建目标文件 fstat(src_fd, &src_sb); // 获取源文件状态 src_start = mmap(NULL, src_sb.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0); // 建立源文件的内存映射 dest_start = mmap(NULL, src_sb.st_size, PROT_WRITE, MAP_SHARED, dest_fd, 0); // 建立目标文件的内存映射 memcpy(dest_start, src_start, src_sb.st_size); // 将源文件数据复制到目标文件 munmap(src_start, src_sb.st_size); // 解除源文件内存映射 munmap(dest_start, src_sb.st_size); // 解除目标文件内存映射 close(src_fd); // 关闭源文件 close(dest_fd); // 关闭目标文件 } int main() { mmap_transfer("source.txt", "destination.txt"); // 调用mmap传输数据的函数,将source.txt文件内容复制到destination.txt文件 return 0; } 以上代码mmap_transfer函数接受源文件名和目标文件名作为参数,打开源文件和创建目标文件。然后,通过mmap函数建立源文件和目标文件的内存映射。接着,使用memcpy函数将源文件的数据复制到目标文件。最后,使用munmap函数解除内存映射并关闭文件。通过调用mmap_transfer函数,可以实现将一个文件的内容传输到另一个文件。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值