前言: KVM的设备虚拟化,除了前文《PIO技术分析》,还有另外一个核心概念---MMIO。原计划这里分析一下KVM的MMIO虚拟化。考虑到MMIO比PIO复杂很多,涉及更多的概念,作者打算先分析几篇基本的Linux的内存管理概念,再来分析MMIO。 作者大概想了一下,主要由这几篇构成: 1,虚拟内存管理和内存映射。 2,物理内存管理。 3,内存回收。 分析: 1,虚拟内存概念 x86的CPU有两种运行模式---real mode和protected mode。在real mode下,CPU访问的是物理地址,也就是说,比如说CPU访问的地址是0x1234,那么访问的物理内存的地址就是0x1234。在protected mode下,访问的虚拟内存,如果访问0x1234这个虚拟地址,那么可能物理上是0xabcde234;从虚拟地址到物理的转换,是通过MMU完成的。 使用虚拟内存后的好处是什么呢?作者认为做大的好处是可以做到进程隔离,也就是说,每个进程都可以有自己的地址空间,互相之间不干扰。当然,还有很多高级的内存特性,例如COW(copy on write)等。 2,进程的地址空间 查看一个进程的地址空间:cat /proc/PID/maps
如图,一个进程的地址空间有多个虚拟内存区域(VMA,virtual memory area)构成,每段VMA包括:起始地址和结束地址(例如例子中的十六进制00400000-004f4000);访问权限(rwx代表读、写、运行);最后那段例如/bin/bash,代表文件映射,如果是空的,则是匿名映射。 如果访问的内存,不在区间内(比如说访问NULL,0明显不在区间内),或者权限不对(比如写了没有w权限的地址),都会发生segmentation fault。 3,VMA管理 linux-4.0.4/include/linux/mm_types.h中定义了VMA的内核态数据结构:
这个数据非常非常非常重要!!!理解Linux内核的虚拟内存管理的关键。从注释中也可以看到,vm_start就是当前这个VMA的起始虚拟地址,vm_end是结束地址后的一个byte的地址,vm_page_prot是访问权限等。 vm_next和vm_prev可以把当前进程的所有的VMA链接起来,vm_rb是给红黑树使用的。同时用链表和红黑树把进程的VMA组织起来,好处在于:可以使用链表进行遍历,可以使用红黑树进行快速查找地址是不是在VMA中。
linux-4.0.4/mm/mmap.c中实现了brk、mmap、munmap、mremap系统调用。结合上面的数据结构,仔细阅读这几个系统调用的实现,大致可以看懂vma的申请、释放、合并、拆分管理。 4,物理内存 在shell中敲dmesg:
可以看到类似的log,BIOS通过e820数据结构告诉Linux物理内存的layout情况。可能你会觉得,为什么插入的一条8G的内存条,会变成这个样子呢?据一位懂BIOS的人和我说,BIOS中也可以配置一次,再做一次映射~ 5,内存映射 看上面例子中的虚拟地址空间,和物理地址范围,二者其实不是对应的。 如上文,CPU在进入了protected mode之后,就会使用虚拟内存了。linux会组织起来一个数据叫做page table(传说中的页表),把虚拟内存和物理内存之间的映射关系保存到page table中,再把page table的地址告诉MMU,MMU就可以在CPU访问虚拟地址的时候,自动转换到物理地址了。
大概转换关系如上图(选自https://en.wikipedia.org/wiki/Page_table)。 6,/dev/mem busybox提供了一个命令---devmem。可以通过devmem直接查看或者修改物理内存。作者在这里在唠叨一下,busybox的代码比较短小精悍,非常不错,值得阅读! devmem的主要是通过/dev/mem做映射实现的。/dev/mem的代码实现在linux-4.0.4/drivers/char/mem.c:
remap_pfn_range是关键函数:函数中实现了pud、pmd、pte的运算,并把物理内存的地址填入pte中。 仔细,完整的阅读remap_pfn_range函数,大概就了解内存映射了。 后记: 因为这里主要是给后面的MMIO做铺垫,所以在这里就没有详细介绍Linux的内存映射技术。当然,还是列举了几处关键代码,如果按照上述的过程,仔细阅读代码,并尝试printk一下,用用提到的命令,作者相信应该比较容易理解。 Good Luck~