当前位置:首页 > 技术学院 > 技术前线
[导读]在64位Linux系统的内核开发、嵌入式驱动调试、高性能内存优化这类场景中,很多开发者都会遇到一个经典的困惑:明明按照芯片手册查到了外设的物理寄存器地址,驱动里打印出来的虚拟指针值却和这个物理地址完全对不上。这个困惑的根源,就在于64位Linux的虚拟地址与物理地址之间,存在一套极其复杂的分层映射体系,不同内存区域的映射规则完全不同。理清这套映射机制,再掌握可落地的验证方法,你就能彻底打通虚实地址转换的全链路,解决内核调试、内存性能优化中的各类疑难问题。

在64位Linux系统的内核开发、嵌入式驱动调试、高性能内存优化这类场景中,很多开发者都会遇到一个经典的困惑:明明按照芯片手册查到了外设的物理寄存器地址,驱动里打印出来的虚拟指针值却和这个物理地址完全对不上。这个困惑的根源,就在于64位Linux的虚拟地址与物理地址之间,存在一套极其复杂的分层映射体系,不同内存区域的映射规则完全不同。理清这套映射机制,再掌握可落地的验证方法,你就能彻底打通虚实地址转换的全链路,解决内核调试、内存性能优化中的各类疑难问题。

64位Linux系统的虚拟地址空间,从设计之初就做了明确的用户态与内核态隔离,和32位系统的地址布局有本质区别。32位系统受限于地址总线宽度,只能给用户态分配3GB左右的虚拟地址空间,剩余1GB留给内核,地址资源极其紧张。而64位系统的虚拟地址寻址范围极其广阔,主流发行版普遍采用4级页表的地址划分规则,用户态虚拟地址全部分布在低128TB的地址区间内,内核态虚拟地址则全部分布在高128TB的地址区间内,两者的地址范围完全没有重叠,从虚拟地址的数值区间就可以直接区分出它属于用户态还是内核态。这种布局彻底解决了32位系统地址空间不足的痛点,用户进程可以拥有完全独立的超大虚拟地址空间,内核也拥有充足的虚拟地址资源来映射所有物理内存。

内核态虚拟地址的映射体系,是整个虚实映射机制中最核心的部分。为了保证内核访问物理内存的效率,Linux在内核虚拟地址空间的最底部,专门划分出了一块连续的线性映射区域,所有物理内存从物理地址0开始,依次和这块内核虚拟地址区域建立一一对应的线性偏移映射。这种映射关系是直接通过固定偏移量计算出来的,完全不需要走复杂的多级页表查询流程,内核访问普通物理内存页时,只需要把物理地址加上一个固定的偏移值,就能直接得到对应的内核虚拟地址,不需要额外的页表查询开销,性能极高。所有内核自己申请的普通内存页,几乎都落在这个线性映射区域中,这也是内核访问绝大多数物理内存的默认映射方式。

但内核虚拟地址空间里,除了线性映射区域之外,还单独划分出了一块动态映射区域。当内核需要把外设的物理寄存器地址映射到内核虚拟地址空间,或者需要把不连续的物理页组合成一段连续的虚拟地址区间时,就会使用ioremap或者vmalloc这类接口,在动态映射区域中分配一段全新的虚拟地址区间,建立自定义的虚实映射关系。这类动态映射的虚拟地址,完全不遵循线性映射的固定偏移规则,它的映射关系是内核动态维护的,必须通过查询页表才能得到对应的物理地址,这也是很多驱动开发者遇到的“虚拟地址和物理地址对不上”问题的核心根源:你拿到的虚拟地址是ioremap动态分配的,根本不属于线性映射区域,自然不能用固定偏移的规则去反推物理地址。

用户态进程的虚拟地址映射规则,和内核态完全不同。每个用户进程都拥有独立的页表,用户态的虚拟地址完全没有任何固定的映射规则,进程每一次调用mmap申请虚拟地址区间,内核都会从进程的虚拟地址空间中分配一段空闲的区间,后续访问时触发缺页异常,动态分配物理页建立映射。同一个物理页可以被多个不同进程的虚拟地址映射到不同的位置,不同进程中指向同一个物理页的虚拟地址数值可以完全不同,用户态进程永远无法直接通过固定规则计算出虚拟地址对应的物理地址,必须借助内核提供的特殊接口才能查询到真实的物理地址。

掌握映射机制之后,我们可以通过多种可落地的方法,直接验证虚实地址的映射关系。最常用的用户态验证方法,是借助Linux系统提供的pagemap接口,这个接口位于proc文件系统下,每个进程都有自己对应的pagemap文件,内核通过这个文件向用户态暴露每个虚拟页对应的物理页帧号信息。我们只需要把目标虚拟地址转换成对应的pagemap文件内的偏移量,读取这个偏移位置的8字节数据,从中提取出物理页帧号,把页帧号乘以页大小,就可以得到对应的物理地址。这种方法完全不需要修改内核代码,普通用户只要拥有对应进程的访问权限,就可以直接完成虚实地址的转换验证,是用户态场景下最便捷的验证方式。

在内核态场景下,验证虚实映射关系的方法更加直接。对于线性映射区域内的虚拟地址,直接调用内核提供的virt_to_phys接口,就可以立刻得到对应的物理地址,这个接口内部只是做了简单的偏移减法运算,执行效率极高。对于动态ioremap出来的虚拟地址,我们可以遍历当前进程的页表项,从顶级页目录开始,逐级往下查找对应的页表项,最终从页表项中提取出物理页帧号,组合得到物理地址。内核调试时还可以直接借助crash调试工具,输入虚拟地址直接查询对应的物理地址,不需要编写任何额外代码,就能快速验证映射关系的正确性。

在实际工程场景中,验证虚实映射关系的能力有着极高的实用价值:排查驱动中外设寄存器读写异常的问题、验证大页内存的映射是否正确、分析内存共享场景下多个进程的虚拟地址是否指向同一份物理页,都依赖这套虚实映射的验证方法。彻底理清64位Linux的虚实地址映射规则,熟练掌握对应的验证手段,你就可以穿透虚拟地址的抽象层,直接掌控物理内存的真实分布。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭