linux内存-x86-64页表初始化

2024-03-17 01:30

本文主要是介绍linux内存-x86-64页表初始化,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

页表存储着虚拟地址到物理地址的映射关系,同时为了减少页表的内存消耗发明了多级页表,更多基础内容可以看浅析linux内存管理.

一个虚拟地址到物理地址通过页表的转换过程如下,<深入理解LINUX内核>的经典图:
页表和地址转换过程
32bit系统上一般只有PGD(Page Global Directory)和pte(page table entry),32bit虚拟地址划分成三段: 10:10:12,高10bit是PGD中偏移,中间10bit是pte数组中的偏移,低12bit是页内偏移.在x86 arch中cr3负责加载页表,它属于MMU组件部分,它看到的是物理地址空间,页表存储在进程的task_struct->active_mm->pgd中,不过它是个虚拟地址,经过pa转换才传给寄存器,具体可以看switch_mm->load_cr3.

页帧的大小是预先设定好的一组值,不是随意设定的,桌面版上一般是4k,它还能提供页帧配置的选项,对于服务器有特别的意义,在x86上如果pte上设置了PSE,则page的大小就是4M.
本来是一整块物理内存,现在分成页帧来管理,这样必然会有一些管理数据,在linux中对应的数据结构就是page,页帧为4k时8G内存需要128M的page区域,而页帧为4M只需要128K的page区域,所以在服务器上4k的页帧设置已经不适宜了;此外页帧越小,相同的虚拟地址空间大小页表项所需越少,TLB miss事件会更频繁.不过更大的内存会有更多页内碎片,造成页内浪费.

在32bit系统中,每个地址需要4byte表示,经典的10:10:12划分中,每个地址空间PGD中有需要2^10=1024项,所以每个进程PGD占据4k,每个PGD指向的pte也是占据4k,这样刚好不浪费空间.

页地址是4k对齐的,低12bit全是0,即PGD中和PTE中低12bit是冗余的,所以通常用作他途,下面是PGD中冗余位中存储一些flag.
pgd extra flag

  • S 标识page size,如果置位则页大小是4M,此时pte中PSE位也需要置位;如果是0,则页大小是4k
  • A 标识是否访问过是否访问过范围的页
  • D 标识是否Cache Disable,如果置位这个范围的页则不会cache,每次都要从memory中读写
  • W 标识Write through策略,如果设置则是write-through,如果是0则是write-back
  • U 标识范围的页属于用户空间还是内核空间,页的访问控制基于特权级别.如果设置,这个页属于用户空间,没有限制;如果没有设置,页属于内核空间,只有内核能够访问.
  • R 标识R/W, 1:可读可写 0:只读
  • P 标识Present,如果置位则映射有物理页,如果是0可能是还没分配物理页或者是swap out了.
    pte flag

关于pte的flag详细用途:https://blog.csdn.net/faxiang1230/article/details/106112857

里面的有些flag和PGD中的flag作用是相同的,下面只列出了不同项:

  • C 标识是否Cache Disable,和pgd中的 ‘D’ 位作用相同
  • G 标识全局属性,如果置位,如果CR3重新设置,它仍然在TLB中保持这部分页表项,需要CR4中使能这个功能
  • D 标识页是否被写过,这个是由MMU访问时自动置位的,但是需要CPU在回写完成后清除flag

x86-64的地址空间

64bit地址空间是对32bit的有效扩充,不过地址空间实在太大了,没有机器的内存能够达到这种级别.
目前64bit系统中地址空间只使用了低48bit,即256TB大小.intel规划了下一步可以扩充到57bit的地址空间方案,128PB大小,目前看虚拟地址资源短时间内应该不是瓶颈.

和32bit系统中有限的虚拟地址空间相比,64bit基本上可以随意使用虚拟地址空间,它去除了一些概念:HIGHMEM区域的物理内存,pkmap区域.不过地址空间划分继续保持了对32bit程序的兼容性.

  1. 兼容32bit程序是当时64位设计时必然要考虑的问题,32bit系统存在的时代有很多优秀的软件,这也是一种财富,不能到了64bit就不能继续使用了,而且最大限度的保持兼容,不需要重新编译即可运行.
    所以32bit程序的用户空间是0-3G,在64bit空间中用户空间地址是0-128TB,32bit程序运行时只占据了它最低端的一部分空间,运行时空间的兼容性使得不需要重新编译.另外是系统调用的兼容性,这里不在赘述,看linux系统调用过程剖析

  2. 在用户空间地址和内核空间中间有个大大的hole,它利用高16bit不参与寻址的特点,制造了这么大一个hole,只能说有钱任性.

  3. 64TB的空间来做直接映射,也就是最大能够支持64TB的线性映射内存,高于64TB估计也得在vmalloc中动态使用了,目前64TB的内存支持是足够大了

  4. ffffffff80000000 - ffffffffa0000000这块空间用来映射内核的文本段,数据段等,最大512M,物理内存区域和直接映射区域是交叉的,不过直接映射区域不访问它,页表映射一块物理内存区域多次又有什么关系呢?

  5. 其他区域就不再赘述了

https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt

0000000000000000 - 00007fffffffffff (=47 bits) user space, different per mm        
hole caused by [48:63] sign extension                                           
ffff800000000000 - ffff87ffffffffff (=43 bits) guard hole, reserved for hypervisor
ffff880000000000 - ffffc7ffffffffff (=64 TB) direct mapping of all phys. memory 
ffffc80000000000 - ffffc8ffffffffff (=40 bits) hole                             
ffffc90000000000 - ffffe8ffffffffff (=45 bits) vmalloc/ioremap space            
ffffe90000000000 - ffffe9ffffffffff (=40 bits) hole                             
ffffea0000000000 - ffffeaffffffffff (=40 bits) virtual memory map (1TB)         
... unused hole ...                                                             
ffffec0000000000 - fffffc0000000000 (=44 bits) kasan shadow memory (16TB)          
... unused hole ...                                                             
ffffff0000000000 - ffffff7fffffffff (=39 bits) %esp fixup stacks                
... unused hole ...                                                             
ffffffff80000000 - ffffffffa0000000 (=512 MB)  kernel text mapping, from phys 0 
ffffffffa0000000 - fffffffffeffffff (=1520 MB) module mapping space
ffffffffff000000 - FIXADDR_START unused hole
FIXADDR_START - ffffffffff9fffff (~0.5 MB) kernel-internal fixmap range, variable size and offset       
ffffffffffa00000 - ffffffffffdfffff (=8 MB) vsyscalls                           
ffffffffffe00000 - ffffffffffffffff (=2 MB) unused hole 

x86_64 地址空间
对于虚拟地址空间来说,这样的划分不是固定的,从2004年x86_64的address map文档合并到内核Documentation/x86/x86_64/mm.txt一直到最新的内核文档来说,变化真的很大,虚拟地址空间的规划只是众多约定的一种

x86-64的页表初始化

64bit地址需要占据8byte,所以如果是4k的页大小,则每个页只能允许512项,即2^9,每一组PGD,PUD等都占据一页的大小,页内偏移仍然是12bit.

PGDPUDPMDPTEpage offset
999912
  1. 初始化状态

目前x86的内核镜像基本都是经过压缩的,这能减少load内核镜像花费的IO时间,将经过压缩的镜像load到内核之后,头部包含自解压代码,将解压后的内核放到约定的地址CONFIG_PHYSICAL_START.另外前期bootloader已经开启了MMU功能,创建了部分页表,但是内核是一个独立的系统,它不能依赖于bootloader的工作,所以虽然它自己现在可以运行,仍需初始化内存管理数据并创建加载自己的页表.
32bit和64bit平台上加载内核的方式是保持兼容的,内核加载后的地址布局和32bit中仍然是相同的,查看/proc/iomem获取详细信息
内核加载之后的地址布局
2.内核的页表初始化

内核的入口地址.head.text,在链接脚本vmlinux.ldS中指定链接顺序,在System.map中也可以观察到入口函数是startup_64

ffffffff81000000 T _text                                                                                                                       
ffffffff81000000 T startup_64                                                   
ffffffff81000110 T secondary_startup_64

64bit中使用4级页表,在初始化的时候分别是:early_level4_pgt, level3_kernel_pgt,level2_kernel_pgt,level2_fixmap_pgt,level1_fixmap_pgt,在编译的时候,进行了页表初始化.
在地址空间规划中,内核镜像映射地址为ffffffff80000000 - ffffffffa0000000 kernel text mapping, from phys 0,下面计算一下它在各级页表中对应的哪些项,在早期页表中内核镜像的页帧设置成了2M的大小,只需要三级页表就可以完成映射

#define pgd_index(address) (((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))            ==> (0xffffffff80000000 >> 39) &(512-1) = 511
#define pud_index(address) (((address) >> PUD_SHIFT) & (PTRS_PER_PUD - 1)                 ==> (0xffffffff80000000 >> 30) &(512-1) = 510
#define pmd_index(address) (((address) >> PMD_SHIFT) & (PTRS_PER_PMD - 1)                ==> (0xffffffff80000000 >> 21) &(512-1) = 0

而除了内核的text和data段的映射关系,还有fixmap的映射关系,计算方法类似.

  1. 编译时初始化页表的结果如下,在运行时会进行一些偏移校准

arch/x86/kernel/head_64.S

early_level4_pgt[511] -> level3_kernel_pgt[0]
level3_kernel_pgt[510] -> level2_kernel_pgt[0]
level3_kernel_pgt[511] -> level2_fixmap_pgt[0]
level2_kernel_pgt[0]   -> 512 MB kernel mapping
level2_fixmap_pgt[507] -> level1_fixmap_pgt
  1. 内核启动早期的页表初始化,在4.0内核中位于arch/x86/kernel/head_64.S中,在编译期间已经做完了初始化的工作,运行的时候进行偏移校准并且加载到CR3寄存器中生效。

下面是编译时页表初始化的代码注释

    leaq    _text(%rip), %rbp                                                   subq    $_text - __START_KERNEL_map, %rbp   //rbp中存储编译地址和运行地址的偏移/** 校准内核镜像映射区域页表项的物理地址偏移*/addq    %rbp, early_level4_pgt + (L4_START_KERNEL*8)(%rip)                     addq    %rbp, level3_kernel_pgt + (510*8)(%rip)                                addq    %rbp, level3_kernel_pgt + (511*8)(%rip)                                //校准固定映射区域页表项的物理地址偏移                         addq    %rbp, level2_fixmap_pgt + (506*8)(%rip)/* Fixup phys_base */                                                       addq %rbp,phys_base(%rip)                                                                                     movq    $(early_level4_pgt - __START_KERNEL_map), %rax                      jmp 1f
1:                                                                      /* 使能PGE即大页模式 */                                               movl    $(X86_CR4_PAE | X86_CR4_PGE), %ecx                                  movq    %rcx, %cr4                                                                           /* Setup early boot stage 4 level pagetables. */                            addq    phys_base(%rip), %rax                                               movq    %rax, %cr3      //load cr3
NEXT_PAGE(early_level4_pgt) 
//前面511个地址全部清零,没有进一步设置页表之前,访问这部分地址是非法的          .fill   511,8,0 
//__START_KERNEL_map即kernel mapping区域的基地址,level3_kernel_pgt代表符号的加载地址,
//他们的差就是三级页表的物理地址;地址低位存储标志位                                       .quad   level3_kernel_pgt - __START_KERNEL_map + _PAGE_TABLE                        
NEXT_PAGE(level3_kernel_pgt).fill   L3_START_KERNEL,8,0 //kernel mapping区域之前的页表项清零/* (2^48-(2*1024*1024*1024)-((2^39)*511))/(2^30) = 510 */ .quad   level2_kernel_pgt - __START_KERNEL_map + _KERNPG_TABLE //指向二级页表.quad   level2_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE   //fixmap区域的页表NEXT_PAGE(level2_kernel_pgt)                                                    /*                                                                          * 512 MB kernel mapping. We spend a full page on this pagetable            * anyway.                                                                  *                                                                          * The kernel code+data+bss must not be bigger than that.                   *                                                                          * (NOTE: at +512MB starts the module area, see MODULES_VADDR.              *  If you want to increase this then increase MODULES_VADDR                *  too.)                                                                   */
//除了设置页表项之外,它还设置了PSE标志,内核早期的页帧的大小为2M,level2_kernel_pgt就是这块区域的最后一级页表                                                                         PMDS(0, __PAGE_KERNEL_LARGE_EXEC, KERNEL_IMAGE_SIZE/PMD_SIZE) //内核默认最大512M,这段空间直接映射到从0开始的物理内存,即虚拟地址0xffffffff80000000对应物理地址0
NEXT_PAGE(level2_fixmap_pgt).fill   506,8,0	//二级页表项每项管理2M的空间.quad   level1_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE //fixmap最多2M的空间/* 8MB reserved for vsyscalls + a 2MB hole = 4 + 1 entries */.fill   5,8,0    //最后2M空间是个hole,还有8M给vsyscalls预留的空间NEXT_PAGE(level1_fixmap_pgt).fill   512,8,0       //固定映射只是初始化了,但是present没有设置,是不能使用的

附录

asm中fill的用法为:

.fill repeat , size , value  //在该地址处重复repeat次,每次迭代地址增加size字节,填充值为value
.quad value				//在该地址放置4个字的数值,即8个字节

这篇关于linux内存-x86-64页表初始化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/817379

相关文章

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc

Linux安装MySQL的教程

《Linux安装MySQL的教程》:本文主要介绍Linux安装MySQL的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux安装mysql1.Mysql官网2.我的存放路径3.解压mysql文件到当前目录4.重命名一下5.创建mysql用户组和用户并修

Linux上设置Ollama服务配置(常用环境变量)

《Linux上设置Ollama服务配置(常用环境变量)》本文主要介绍了Linux上设置Ollama服务配置(常用环境变量),Ollama提供了多种环境变量供配置,如调试模式、模型目录等,下面就来介绍一... 目录在 linux 上设置环境变量配置 OllamPOgxSRJfa手动安装安装特定版本查看日志在

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用