引导期间Linux内核空间中的页表

时间:2013-05-22 09:42:23

标签: linux-kernel mmu

我对Linux内核中的页表管理感到困惑吗?

在Linux内核空间中,在打开页面表之前。内核将使用1-1映射机制在虚拟内存中运行。打开页表后,内核会查询页表以将虚拟地址转换为物理内存地址。 问题是:

  1. 此时,打开页面表后,内核空间仍为1GB(从0xC0000000 - 0xFFFFFFFF开始)?

  2. 在内核进程的页表中,只映射了0xC0000000 - 0xFFFFFFFF范围内的页表项(PTE)? PTE超出此范围将不会映射,因为内核代码永远不会跳转到那里?

  3. 打开页面表之前和之后的映射地址是否相同?

    例如。在打开页表之前,虚拟地址0xC00000FF映射到物理地址0x000000FF,然后在打开页表后,上面的映射不会改变。虚拟地址0xC00000FF仍然映射到物理地址0x000000FF。不同的是,只有在打开页表后,CPU才会查询页表,将虚拟地址转换为以前无需执行的物理地址。

  4. 内核空间中的页表是全局的,将在系统中的所有进程(包括用户进程)之间共享吗?

  5. 此机制在x86 32位和ARM中相同?

2 个答案:

答案 0 :(得分:14)

以下讨论基于32位ARM Linux,内核源代码版本为3.9
如果您完成设置初始页面表(稍后将被函数paging_init覆盖)并打开MMU的过程,则可以解决所有问题。

当bootloader首次启动内核时,汇编函数stext(在arch \ arm \ kernel \ head.s中)是第一个运行的函数。请注意,此时MMU尚未开启。

除此之外,此函数stext完成的两个导入作业是:

  • 创建初始页面tabel(稍后会被覆盖 函数paging_init
  • 开启MMU
  • 跳转到C部分内核初始化代码并继续

在深入研究你的问题之前,了解一下是有益的:

  • 在开启MMU之前,CPU发出的每个地址都是物理地址 地址
  • MMU开启后,CPU发出的每个地址都是虚拟地址
  • 在打开MMU之前,应该设置一个合适的页面表,否则你的代码将会被吹走"
  • 按照惯例,Linux内核使用较高的1GB部分虚拟地址,用户使用较低的3GB部分

现在棘手的部分:
第一招:使用与位置无关的代码。 汇编函数stext链接到地址" PAGE_OFFSET + TEXT_OFFSET"(0xCxxxxxxx),这是一个虚拟地址,但是,因为MMU还没有打开,所以汇编函数stext正在运行的实际地址是" PHYS_OFFSET + TEXT_OFFSET"(实际值取决于您的实际硬件),这是一个物理地址。

所以,事情就是这样:函数程序stext"认为"它在0xCxxxxxxx这样的地址运行,但实际上是在地址(0x00000000 + some_offeset)中运行(比如你的硬件配置0x00000000作为RAM的起点)。因此,在打开MMU之前,需要非常仔细地编写汇编代码,以确保在执行过程中没有任何问题。事实上,使用了一种称为位置无关代码(PIC)的技术。

为了进一步解释上述内容,我提取了几个汇编代码片段:

ldr r13, =__mmap_switched    @ address to jump to after MMU has been enabled

b   __enable_mmu             @ jump to function "__enable_mmu" to turn on MMU

注意上面的" ldr"指令是伪指令,表示"获取函数__mmap_switched的(虚拟)地址并将其放入r13"

函数__enable_mmu依次调用函数__turn_mmu_on: (请注意,我从函数__turn_mmu_on中删除了几条指令,它们是函数的基本指令,但不符合我们的兴趣)

ENTRY(__turn_mmu_on)
    mcr p15, 0, r0, c1, c0, 0       @ write control reg to enable MMU====> This is where MMU is turned on, after this instruction, every address issued by CPU is "virtual address" which will be translated by MMU
    mov r3, r13   @ r13 stores the (virtual) address to jump to after MMU has been enabled, which is (0xC0000000 + some_offset)
    mov pc, r3    @ a long jump
ENDPROC(__turn_mmu_on)

第二招:在开启MMU之前设置初始页表时的相同映射。 更具体地说,运行内核代码的相同地址范围被映射两次。

  • 第一个映射,如预期的那样,映射地址范围0x00000000(再次, 这个地址取决于硬件配置)到(0x00000000 + 偏移)到0xCxxxxxxx到(0xCxxxxxxx +偏移)
  • 有趣的是,第二个映射映射地址范围0x00000000 通过(0x00000000 +偏移)到它自己(即:0x00000000 - > (0x00000000 +偏移))

为什么这样做? 请记住,在MMU打开之前,CPU发出的每个地址都是物理地址(从0x00000000开始),MMU打开后,CPU发出的每个地址都是虚拟地址(从0xC0000000开始)。 因为ARM是一个流水线结构,所以在MMU打开的那一刻,ARM的管道中仍然有指令使用在MMU打开之前由CPU生成的(物理)地址!为了避免这些指令被炸毁,必须设置相同的映射来满足它们。

现在回到你的问题:

  
      
  1. 此时,打开页面表后,内核空间仍为1GB(从0xC0000000 - 0xFFFFFFFF开始)?
  2.   
答:我猜你的意思是打开MMU。答案是肯定的,内核空间是1GB(实际上它也占用了0xC0000000以下的几兆字节,但这不是我们感兴趣的)

  
      
  1. 在内核进程的页表中,只映射了0xC0000000 - 0xFFFFFFFF范围内的页表项(PTE)? PTE出局了   因为内核代码永远不会跳转,所以不会映射此范围   ?
  2.   

答:虽然这个问题的答案非常复杂,因为它涉及到有关特定内核配置的大量细节  要完全回答这个问题,您需要阅读设置初始页表(汇编函数__create_page_tables)的内核源代码部分以及设置最终页表(C函数paging_init)的函数。 /> 简单来说,ARM中有两级页表,第一页表是PGD,占用16KB。内核在初始化过程中首先将此PGD清零,并在汇编函数__create_page_tables中执行初始映射。在函数__create_page_tables中,只映射了很小一部分地址空间  之后,最终页表在函数paging_init中设置,并且在此函数中,映射了相当大部分的地址空间。假如你只有512M RAM,对于大多数常见配置,这个512M-RAM将按内核代码逐段映射(1节为1MB)。如果您的RAM非常大(例如2GB),则只会直接映射一部分RAM。 (我将在此停止,因为关于问题2的细节太多了)

  
      
  1. 打开页面表之前和之后的映射地址是一样的吗?
  2.   

答:我想我已经在第二个技巧的解释中回答了这个问题:在打开MMU之前设置初始页面时的相同映射。"

  

4。内核空间中的页表是全局的,将在其中共享   系统中的所有进程,包括用户进程?

答:是的,不是。是的,因为所有进程共享内核页表的相同副本(内容)(更高的1GB部分)。不,因为每个进程使用自己的16KB内存来存储内核页表(尽管每个进程的1GB部分的页表内容相同)。

  

5。这个机制在x86 32bit和ARM中是一样的吗?

不同的架构使用不同的机制

答案 1 :(得分:9)

当Linux启用MMU时,只需要映射内核空间的虚拟地址。这种情况在启动早期发生非常。此时,没有用户空间。 MMU可以将多个虚拟地址映射到相同的物理地址没有任何限制。因此,在启用MMU时,最简单的方法是为内核代码空间和映射virt==phys 0xC0000000 映射设置link==phys映射。

  
      
  1. 打开页面表之前和之后的映射地址是一样的吗?
  2.   

如果物理代码地址为 Oxff 且最终链接地址为 0xc00000FF ,则打开MMU时会出现重复映射。 0xff 0xc00000ff 都映射到同一物理页面。简单的jmp(跳转)或b(分支)将从一个地址空间移动到另一个地址空间。此时,当我们在最终目标地址执行时,virt==phys映射可以被删除。

我认为以上应该通过 3 回答 1 点。基本上,引导页表不是最终页表。

  

4。内核空间中的页表是全局的,将在系统中的所有进程中共享,包括用户进程?

是的,这是一个带有 VIVT 缓存的巨大胜利,还有很多其他原因。

  

5。这个机制在x86 32bit和ARM中是一样的吗?

当然,潜在的机制是不同的。即使是这些家庭中的不同处理器,它们也不同; 486 vs P4 vs Amd-K6; ARM926 vs Cortex-A5 vs Cortex-A8等。但是,语义非常相似。

请参阅:Bootmem@lwn.net - 关于早期Linux内存阶段的文章。

根据版本的不同,内存池页表映射在引导期间处于活动状态。在init运行之前,我们熟悉的映射不需要就绪。