当前位置: 首页 > 科技观察

ARM64Linux内核页表的块映射

时间:2023-03-14 23:50:06 科技观察

内核文档Documentation/arm64/memory.rst描述了ARM64Linux内核空间的内存映射,应该是这方面最权威的文档。以一个典型的4K页和48位虚拟地址为例,整个内核空间的虚拟地址分布如下:从ffff000000000000到ffff7fffffffffff是物理地址的线性映射区域,最大支持128TB的物理地址空间.这个地址很像ARM32的低位内存映射区。让我们看看这种情况下的页表。我们可以使用最后的[20:12]对应的PTE映射项,以4K为单位将虚拟地址映射到物理地址;我们也可以使用[29:21]对应的PMD映射项,以2M为单位,进行虚拟地址到物理地址的映射。对于用户空间的虚拟地址,当我们进行PMD映射时,得到的是HugePage,ARM64的2MB巨页,在虚拟和物理上都是连续的。它在实际工程中的好处是,可以减少TLBmiss,因为如果进行2MB的映射,整个2MB就不再需要PTE了,映射关系大大减少。对于内核空间,如果从ffff000000000000到ffff7fffffffffff的虚拟地址是物理地址的PMD映射,显然可以达到同样的效果。但是,这并不意味着它们是HugePages。众所周知,内核开始将物理地址线性映射到虚拟地址,并不是说这块内存被内核拿走了,它只是进行了一种映射,使得API申请的内存比如kmalloc()和get_free_pages()以后可以直接调用有虚实映射。因此,即使内核进行PMD映射,内存的划分仍然可以以4K为单位:所以,即使我们在内核空间进行PMD映射,里面的每个蓝色圆圈(一个4K页)仍然可以单独分配,这allocation可以是kmalloc、vmalloc、用户态的malloc等。在内核态进行的PMD映射并不意味着相关的2MB变成了hugepage,纯粹是为了内核访问物理地址时提供线性映射的服务虚拟地址(我们认为内核大部分时间使用这种线性映射虚拟地址),减少TLB未命中。当然,更强大的情况下,内核应该也可以直接使用[38:30]PUD进行映射,这样映射关系就是1GB,当整个1GB后TLB被占用时,只有一个entry是必须的。当然,如果用户态的虚实映射是这样的话,用户实际得到的是一个1GB的hugepage。但是对于内核的线性映射区域,即使我们进行1GB的PUD映射,这1G也可以进一步划分为4KB的页面或者2MB的大页面。记住:内核态线性映射区的映射只是映射关系,不是分配关系。比如下面的1GB内核线性映射的1GB区域还是可以按4K分配,或者用户在大页中以2MB为单位分配:我们需要一个真正的调试方法来验证我们的想法,而这个调试方法就是PTDUMP(PageTableDump),相关代码在ARM64内核中:arch/arm64/mm/ptdump.c和ptdump_debugfs.c我们全部选中,这样就可以得到一个debugfs接口:/sys/kernel/debug/kernel_page_tables了解内核模式页表。我用qemu启动了一个4GB内存的ARM64虚拟机。可以看到虚拟地址空间的前1GB大部分是PMD和PTE映射,后3GB都是PUD映射:我在内核启动参数中加入了rodata=0:$cat/proc/cmdlineroot=/dev/vda2rwconsole=ttyAMA0ip=dhcprodata=0原因是内核在几种情况下不会做这种PMD和PUD映射。相关代码见:rodata_full在默认情况下始终为真,对应内核的一个Config选项CONFIG_RODATA_FULL_DEFAULT_ENABLED,“Applyr/opermissionsofVMareasalsototheirlinearaliases”,该选项提高了安全性的内核,但降低了内核的性能。我在内核启动参数中加入的rodata=0,实际上让rodata_full为false。如果去掉这个内核启动选项,得到的内核页表就完全不一样了,线性映射区都是PTE映射:最后值得一提的是,不仅线性映射区可以使用PMD映射,vmemmap也可以mappingarea在4K页的情况下,默认用PMD进行映射:字节跳动的宋木纯童鞋发了一个patchset,试图删除大页内部4KB小页占用的pagestruct内存消耗。用户分配了一个hugepage,这个patchset是在圣诞节前发到V11的:https://lore.kernel.org/linux-mm/20201222142440.28930-1-songmuchun@bytedance.com/在这个patchset中,需要拆分PMDmappingofvmemmapMappingforPTE:这个patchset的原理是基于当kernelpages在4KB时,每个page需要一个64-byte的pagestruct。但是,当用户将其分配为大页面时,我们不再需要使用页面结构来单独描述每个4KB。对于这个复合页,我们应该可以直接释放后续页结构的内存。因为情况完全一样,这样可以留下很多记忆。本文转载自微信公众号“Linux代码阅读领域”,可通过以下二维码关注。转载本文请联系Linux代码阅读领域公众号。宋宝华