gpt4 book ai didi

linux-kernel - 启动期间Linux内核空间中的页表

转载 作者:行者123 更新时间:2023-12-03 14:42:26 27 4
gpt4 key购买 nike

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

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


这时,打开页表后,内核空间仍然是1GB(从0xC0000000-0xFFFFFFFF)?
并且在内核进程的页表中,仅映射范围为0xC0000000-0xFFFFFFFF的页表项(PTE)。超出此范围的PTE将不会被映射,因为内核代码永远不会跳到那里?
打开页表前后的映射地址是否相同?
例如。在打开页表之前,将虚拟地址0xC00000FF映射到物理地址0x000000FF,然后在打开页表之后,上面的映射不会更改。虚拟地址0xC00000FF仍映射到物理地址0x000000FF。唯一不同的是,打开页表后,CPU查阅了页表将虚拟地址转换为物理地址,而以前不需要这样做。
内核空间中的页表是全局的,将在系统中的所有进程(包括用户进程)之间共享。
这种机制在x86 32bit和ARM中是相同的。

最佳答案

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

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

其中,此功能stext完成的两个导入作业是:


创建初始页面表格(稍后将被忽略)
函数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生成的(物理)地址!为避免这些说明被弄乱,必须设置相同的映射来满足这些要求。

现在回到您的问题:



这时,打开页表后,内核空间仍然是1GB(从0xC0000000-0xFFFFFFFF)?



答:我想你的意思是打开MMU。答案是肯定的,内核空间为1GB(实际上,它还占用0xC0000000以下的几兆字节,但这与我们无关)



并且在内核进程的页表中,仅映射范围为0xC0000000-0xFFFFFFFF的页表项(PTE)。 PTE出局
此范围的值不会被映射,因为内核代码永远不会跳到那里




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



打开页表前后的映射地址是否相同?



答:我认为我已经在“第二个技巧:打开MMU之前设置初始页表时使用相同的映射”的解释中回答了这个问题。


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


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


5。这种机制在x86 32bit和ARM中是相同的。


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

关于linux-kernel - 启动期间Linux内核空间中的页表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16688540/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com