E-moss,程序员,爱好阅读和撸狗,主要从事iOS开发工做,公众号:知本集。
主要分享和编写技术方面文章,不按期分享读书笔记,亦可访问“知本集”Git地址:https://github.com/knowtheroot/KnowTheRoot_iOS,欢迎提出问题和讨论。
复制代码
所谓内存映射,就是将文件的磁盘扇区映射到进程的虚拟内存空间的过程。node
1.进程发起一个读文件请求;git
2.内核经过查找进程文件符表,定位到内核已打开的文件集上的文件信息,从而找到对应文件的inode;程序员
3.inode在地址空间(address_space)上查找要请求的文件是否已经缓存在内核页的高速缓存中,若是存在,则直接放回该文件的内容;github
4.若是文件不存在高速缓存中,则经过inode定位到文件的磁盘地址,将数据从磁盘复制到内核页高速缓存。以后再次范圣琦读页面的过程,将内核高速缓存中的数据发送给用户进程;缓存
什么是inode?
全称为index node,既存储文件元信息的区域,中文译名“索引节点”。
例如包含:文件权限、文件拥有者的UID、文件的大小等等。bash
1.系统在read/write的时候是很耗时的,例如在读文件的时候,将文件内容从硬盘拷贝到内核空间的一个缓冲区,而后再将这些数据拷贝到用户空间,实际上完成了两次数据拷贝;
2.同理,写入操做一样耗时,待写入的buffer在内核空间不能直接访问,必需要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是须要两次数据拷贝;
3.若是两个进程都对磁盘中的一个文件内容进行访问,那么这个内容在物理内存中有三份:进程A的地址空间 + 进程B的地址空间 + 内核页高速缓冲空间;微信
此时咱们找到了文件读取的痛点:两次拷贝致使效率太低数据结构
“映射”这个词,就和数学课上说的“一一映射”是一个意思,就是创建一种一一对应关系,在这里主要是指硬盘上文件 的位置与进程逻辑地址空间 中一块大小相同的区域之间的一一对应。微信开发
注意:这种对应关系纯属是逻辑上的概念,物理上是不存在的,缘由是进程的逻辑地址空间自己就是不存在的。函数
具体到代码,就是创建并初始化了相关的数据结构(struct address_space),这个过程有系统调用mmap()实现,因此创建内存映射的效率很高。
1.mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,这样之后,进程无需再调用read或write对文件进行读写,而只须要经过ptr就可以操做文件;
2.可是ptr所指向的是一个逻辑地址,要操做其中的数据,必须经过MMU将逻辑地址转换成物理地址;
3.创建内存映射并无实际拷贝数据,这时,MMU在地址映射表中是没法找到与ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断,缺页中断的中断响应函数会在swap中寻找相对应的页面,若是找不到(也就是该文件历来没有被读入内存的状况),则会经过mmap()创建的映射关系,从硬盘上将文件读取到物理内存中;
4.若是在拷贝数据时,发现物理内存不够用,则会经过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上;
MMU是Memory Management Unit的缩写,中文名是内存管理单元,它是中央处理器(CPU)中用来管理虚拟存储器、物理存储器的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问受权,多用户多进程操做系统。
mmap内存映射的实现过程,总的来讲能够分为三个阶段:
1.进程启动映射过程,并在虚拟地址空间中为映射建立虚拟映射区域;
2.调用内核空间的系统调用函数mmap(不一样于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系;
3.进程发起对这片映射空间的访问,引起缺页异常,实现文件内容到物理内存(主存)的拷贝;
前两个阶段仅在于建立虚拟区间并完成地址映射,可是并无将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操做时。
以前说过,常规文件操做为了提升读写效率和保护磁盘,使用了页缓存机制,因为页缓存处在内核空间,不能被用户进程直接寻址,这样就出现了两次拷贝的过程,这也是常规文件操做的性能限制。
使用mmap操做文件中,建立新的虚拟内存区域和创建文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操做。
以后访问数据时发现内存中并没有数据而发起的缺页异常过程,能够经过已经创建好的映射关系,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。
对硬盘上一个名为“mmap_test”的文件进行操做,文件中存有10000个整数,程序两次使用不一样的方法将它们读出,加1,再写回硬盘。
gettimeofday( &tv1, NULL );
fd = open( "mmap_test", O_RDWR );
array = mmap( NULL, sizeof(int)*MAX, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 );
for( i=0; i<MAX; ++i )
++array[ i ];
munmap( array, sizeof(int)*MAX );
msync( array, sizeof(int)*MAX, MS_SYNC );
free( array );
close( fd );
gettimeofday( &tv2, NULL );
复制代码
如下摘自《微信开发团队guoling的技术分享》
什么是MMKV? MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。 MMKV的实现 内存准备
经过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由 iOS 负责将内存回写到文件,没必要担忧 crash 致使数据丢失。
数据处理
数据序列化方面咱们选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。考虑到咱们要提供的是通用 kv 组件,key 能够限定是 string 字符串类型,value 则多种多样(int/bool/double等)。要作到通用的话,考虑将 value 经过 protobuf 协议序列化成统一的内存块(buffer),而后就能够将这些 KV 对象序列化到内存中。
关于MMKV的原理以后将会专门新开一篇文章来详解和应用。