在接入日志组件xlog的工作中,对mmap内存映射加深了了解,分享一下学习心得。
1.一个Linux进程的虚拟内存
如图展示了一个Linux进程的虚拟内存。 虚拟的意思是进程以为自己有这么一大块内存,实际上物理内存可能还没有分配给它,等到缺页异常是系统才会分配,通过这种以时间换空间的方式提高了内存利用效率。从虚拟内存到物理内存的映射过程需要一个专门的硬件单元MMU来完成。 系统调用的代码和数据就在内核虚拟内存中, 因为在保护模式下,用户态进程无法访问到这里,必须要通过系统调用的方式陷入到内核态才行。
2.Linux是如何组织虚拟内存的
task_structtask_structmm_structvm_area_structvm_area_structvm_area_struct
3.缺页处理
mm_struct
4.内存映射
Linux通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射。 Linux进程可以使用mmap函数来创建新的虚拟内存区域,并将对象映射到这些区域中。 mmap函数定义在libc中:
man 2 mmap
mmap原理
在调用mmap实现这样的映射关系后,它只是在进程的虚拟空间中分配了一段空间,真实的物理地址还不会分配的,当进程第一次访问这段空间(当作内存一样),CPU陷入OS内核执行异常处理,然后异常处理会在这个时间分配物理内存,并用文件的内容填充这片内存,然后才返回进程的上下文,这时进程才会感知到这片内存里有数据。 之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。
mmap 的回写时机: * 内存不足 * 进程退出 * 调用 msync 或者 munmap * 不设置 MAP_NOSYNC 情况下 30s-60s(仅限FreeBSD)
程序的加载
Linux执行一个ELF格式的程序,这个程序在磁盘上,为了执行这个程序,需要把程序加载到内存中,这时采用的就是mmap,mmap让虚拟空间和文件的内容组成的空间(文件空间)对应。因为ELF格式是区分代码、数据段的,这里的就不是简单的整个文件的映射了,需要将文件的分段区域映射到内存的不同位置。OS加载ELF文件的过程非常复杂这里就不展开了,具体内容可以看《程序员的自我修养》。 当CPU真的在这个地址上发起读写执行等操作时,因为文件的内容在磁盘上是不能被CPU访问的,所以OS会进入异常,系统的缺页处理程序会调用文件系统把一页或者多页的文件内容加载到物理内存中。
cat /proc//mapsvm_area_struct
/system/bin/linkeroffset/storage/emulated/0/log.mmap2b35b7000-b35dd000
5.为什么mmap()可以节约IO读写时间
常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制,这是由OS控制的。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。
而使用mmap操作文件中,由于不需要经过内核空间的数据缓存,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。 mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。
xlog对mmap的效率做了验证
为了验证 mmap 是否真的有直接写内存的效率,通过一个简单的测试用例进行验证:把512 Byte的数据分别写入150 kb大小的内存和 mmap,以及磁盘文件100w次并统计耗时
从上图看出mmap几乎和直接写内存一样的性能,而且 mmap 既不会丢日志,回写时机又基本可控。
参考
- 《深入理解计算机操作系统》
- 《程序员的自我修养》