Paging Proofread

This commit is contained in:
mudongliang
2016-02-26 21:03:59 +08:00
parent 6d10b231f0
commit be299b86ef

View File

@@ -4,17 +4,17 @@
简介
--------------------------------------------------------------------------------
在 Linux 内核启动过程中的[第五部分](http://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-5.html),我们学到了内核在最早阶段做的工作。接下来的步骤中,在我们明白内核如何运行第一个 init 进程,内核初始化不同的事,比如 `initrd` 加载 lockdep 初始化,以及许多许多其他的
在 Linux 内核启动过程中的[第五部分](http://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-5.html),我们学到了内核在启动的最早阶段都做了哪些工作。接下来,在我们明白内核如何运行第一个 init 进程之前,内核初始化其他部分,比如加载 `initrd` 初始化 lockdep ,以及许多许多其他的工作
是的,那将有很多不同的事,但是还有更多更多更多关于**内存**的工作。
在我看来,一般而言,内存管理是 Linux 内核和系统编程最复杂的部分之一。这就是为什么在我们处理内核初始化事情之前,我们需要了解分页。
在我看来,一般而言,内存管理是 Linux 内核和系统编程最复杂的部分之一。这就是为什么在我们学习内核初始化过程之前,需要了解分页。
分页是将线性地址转换为物理地址的机制。如果我们已经读过了这本书之前的部分,你可能记得我们在实模式下有分段机制,当时物理地址是由左移四位段寄存器加上偏移算出来的。我们也看了保护模式下的分段机制,其中我们使用描述符表和从描述符得到基地址加上偏移得到物理地址。由于我们在 64 位模式,我们将看分页机制。
分页是将线性地址转换为物理地址的机制。如果我们已经读过了这本书之前的部分,你可能记得我们在实模式下有分段机制,当时物理地址是由左移四位段寄存器加上偏移算出来的。我们也看了保护模式下的分段机制,其中我们使用描述符表得到描述符,进而得到基地址,然后加上偏移地址就获得了实际物理地址。由于我们在 64 位模式,我们将看分页机制。
正如 Intel 手册中说的:
> 分页机制提供一种机制,为了实现一种常见的页需求,虚拟内存系统,其中一个程序执行环境中的段将要按照需求被映射到物理地址。
> 分页机制提供一种机制,为了实现常见的按需分页,比如虚拟内存系统就是将一个程序执行环境中的段按照需求被映射到物理地址。
所以... 在这个帖子中我将尝试解释分页背后的理论。当然它将与64位版本的 Linux 内核关系密切,但是我们将不会深入太多细节(至少在这个帖子里面)。
@@ -52,7 +52,7 @@ wrmsr
分页数据结构
--------------------------------------------------------------------------------
分页将线性地址分为固定尺寸的页。页会被映射进入物理地址空间或外部存储设备。这个固定尺寸在 `x86_64` 内核中是 `4096` 字节。为了将线性地址转换位物理地址,特殊的结构会被用到。每个结构都是 `4096` 字节并包含 `512` 项(这只为 `PAE``IA32_EFER.LME` 模式)。分页结构是层次级的, Linux 内核在 `x86_64` 框架中使用4层的分层机制。CPU使用一部分线性地址去确定另一个分页结构中的项这个分页结构可能在最低层物理内存区域页框在这个区域的物理地址页偏移。最高层的分页结构的地址存储在 `cr3` 寄存器中。我们已经从 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 这个文件中已经看到了。
分页将线性地址分为固定尺寸的页。页会被映射进入物理地址空间或外部存储设备。这个固定尺寸在 `x86_64` 内核中是 `4096` 字节。为了将线性地址转换位物理地址,需要使用到一些特殊的数据结构。每个结构都是 `4096` 字节并包含 `512` 项(这只为 `PAE``IA32_EFER.LME` 模式)。分页结构是层次级的, Linux 内核在 `x86_64` 框架中使用4层的分层机制。CPU使用一部分线性地址去确定另一个分页结构中的项这个分页结构可能在最低层物理内存区域页框在这个区域的物理地址页偏移。最高层的分页结构的地址存储在 `cr3` 寄存器中。我们已经从 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 这个文件中已经看到了。
```assembly
leal pgtable(%ebx), %eax
@@ -86,7 +86,7 @@ movl %eax, %cr3
线性地址转换过程如下所示:
* 一个给定的线性地址到达 [MMU](http://en.wikipedia.org/wiki/Memory_management_unit) 而不是存储器总线;
* 一个给定的线性地址传递给 [MMU](http://en.wikipedia.org/wiki/Memory_management_unit) 而不是存储器总线;
* 64位线性地址分为很多部分。只有低 48 位是有意义的,它意味着 `2^48` 或 256TB 的线性地址空间在任意给定时间内都可以被访问;
* `cr3` 寄存器存储这个最高层分页数据结构的地址;
* 给定的线性地址中的第 39 位到第 47 位存储一个第 4 级分页结构的索引,第 30 位到第 38 位存储一个第3级分页结构的索引第 29 位到第 21 位存储一个第 2 级分页结构的索引,第 12 位到第 20 位存储一个第 1 级分页结构的索引,第 0 位到第 11 位提供物理页的字节偏移;
@@ -133,12 +133,12 @@ Linux 内核中的分页结构
就如我们已经看到的那样, `x86_64`Linux 内核使用4级页表。它们的名字是
* 全局目录
* 上层目录
* 中间目录
* 全局目录
* 上层目录
* 中间目录
* 页表项
在你已经编译和安装 Linux 内核,你可以看到存储内核中使用的函数的虚拟地址的 `System.map` 文件。例如:
在你已经编译和安装 Linux 内核之后,你可以看到保存了内核函数的虚拟地址的文件 `System.map`。例如:
```
$ grep "start_kernel" System.map
@@ -193,15 +193,15 @@ ffffffffff600000 - ffffffffffdfffff (=8 MB) vsyscalls
ffffffffffe00000 - ffffffffffffffff (=2 MB) unused hole
```
这里我们可以看到用户空间,内核空间和非标准空间的内存映射。用户空间的内存映射很简单。让我们来更近地查看内核空间。我们可以看到它始于为管理程序 (hypervisor) 保留的防空洞 (guard hole) 。我们可以在 [arch/x86/include/asm/page_64_types.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/page_64_types.h) 这个文件中看到防空洞的概念!
这里我们可以看到用户空间,内核空间和非标准空间的内存映射。用户空间的内存映射很简单。让我们来更近地查看内核空间。我们可以看到它始于为管理程序 (hypervisor) 保留的防空洞 (guard hole) 。我们可以在 [arch/x86/include/asm/page_64_types.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/page_64_types.h) 这个文件中看到防空洞的概念!
```C
#define __PAGE_OFFSET _AC(0xffff880000000000, UL)
```
以前防空洞和 `__PAGE_OFFSET` 是从 `0xffff800000000000``0xffff80ffffffffff` ,来防止对非标准区域的访问,但是后来为了管理程序扩展了 3 位。
以前防空洞和 `__PAGE_OFFSET` 是从 `0xffff800000000000``0xffff80ffffffffff` 来防止对非标准区域的访问,但是后来为了管理程序扩展了 3 位。
紧接着是内核空间中最低的可用空间 - `ffff880000000000` 。这个虚拟地址空间是为了所有的物理内存的直接映射。在这块空间之后,还是防空洞。它位于所有物理内存的直接映射地址和被 vmalloc 分配的地址之间。在第一个 1TB 的虚拟内存映射和无用的空洞之后,我们可以看到 `ksan` 影子内存 (shadow memory) 。它是通过 [commit](https://github.com/torvalds/linux/commit/ef7f0d6a6ca8c9e4b27d78895af86c2fbfaeedb2) 提交到内核中,并且保持内核空间无害。在紧接着的无用空洞之后,我们可以看到 `esp` 固定栈(我们会在本书其他部分讨论它)。内核代码段的开始从物理地址 - `0` 映射。我们可以在相同的文件中找到将这个地址定义为 `__PAGE_OFFSET`
紧接着是内核空间中最低的可用空间 - `ffff880000000000` 。这个虚拟地址空间是为了所有的物理内存的直接映射。在这块空间之后,还是防空洞。它位于所有物理内存的直接映射地址和被 vmalloc 分配的地址之间。在第一个 1TB 的虚拟内存映射和无用的空洞之后,我们可以看到 `ksan` 影子内存 (shadow memory) 。它是通过 [commit](https://github.com/torvalds/linux/commit/ef7f0d6a6ca8c9e4b27d78895af86c2fbfaeedb2) 提交到内核中,并且保持内核空间无害。在紧接着的无用空洞之后,我们可以看到 `esp` 固定栈(我们会在本书其他部分讨论它)。内核代码段的开始从物理地址 - `0` 映射。我们可以在相同的文件中找到将这个地址定义为 `__PAGE_OFFSET`
```C
#define __START_KERNEL_map _AC(0xffffffff80000000, UL)