mirror of
https://github.com/MintCN/linux-insides-zh.git
synced 2026-04-24 18:50:42 +08:00
Fix broken links in MM
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
|
||||
本章描述 Linux 内核中的内存管理。在本章中你会看到一系列描述 Linux 内核内存管理框架的不同部分的帖子。
|
||||
|
||||
* [Memblock](https://github.com/0xAX/linux-insides/blob/master/mm/linux-mm-1.md) - 描述早期的 `memblock` 分配器。
|
||||
* [固定映射地址和 ioremap ](https://github.com/0xAX/linux-insides/blob/master/mm/linux-mm-2.md) - 描述固定映射的地址和早期的 `ioremap` 。
|
||||
* [Memblock](https://github.com/MintCN/linux-insides-zh/blob/master/MM/linux-mm-1.md) - 描述早期的 `memblock` 分配器。
|
||||
* [固定映射地址和 ioremap ](https://github.com/MintCN/linux-insides-zh/blob/master/MM/linux-mm-2.md) - 描述固定映射的地址和早期的 `ioremap` 。
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
简介
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
内存管理是操作系统内核中最复杂的部分之一(我认为没有之一)。在[讲解内核进入点之前的准备工作](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-3.html)时,我们在调用 `start_kernel` 函数前停止了讲解。`start_kernel` 函数在内核启动第一个 `init` 进程前初始化了所有的内核特性(包括那些依赖于架构的特性)。你也许还记得在引导时建立了初期页表、识别页表和固定映射页表,但是复杂的内存管理部分还没有开始工作。当 `start_kernel` 函数被调用时,我们会看到从初期内存管理到更复杂的内存管理数据结构和技术的转变。为了更好地理解内核的初始化过程,我们需要对这些技术有更清晰的理解。本章节是内存管理框架和 API 的不同部分的概述,从 `memblock` 开始。
|
||||
内存管理是操作系统内核中最复杂的部分之一(我认为没有之一)。在[讲解内核进入点之前的准备工作](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-3.html)时,我们在调用 `start_kernel` 函数前停止了讲解。`start_kernel` 函数在内核启动第一个 `init` 进程前初始化了所有的内核特性(包括那些依赖于架构的特性)。你也许还记得在引导时建立了初期页表、识别页表和固定映射页表,但是复杂的内存管理部分还没有开始工作。当 `start_kernel` 函数被调用时,我们会看到从初期内存管理到更复杂的内存管理数据结构和技术的转变。为了更好地理解内核的初始化过程,我们需要对这些技术有更清晰的理解。本章节是内存管理框架和 API 的不同部分的概述,从 `memblock` 开始。
|
||||
|
||||
内存块
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
内存块是在引导初期,泛用内核内存分配器还没有开始工作时对内存区域进行管理的方法之一。以前它被称为 `逻辑内存块`,但是内核接纳了 [Yinghai Lu 提供的补丁](https://lkml.org/lkml/2010/7/13/68)后改名为 `memblock` 。`x86_64` 架构上的内核会使用这个方法。我们已经在[讲解内核进入点之前的准备工作](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-3.html)时遇到过了它。现在是时候对它更加熟悉了。我们会看到它是被怎样实现的。
|
||||
内存块是在引导初期,泛用内核内存分配器还没有开始工作时对内存区域进行管理的方法之一。以前它被称为 `逻辑内存块`,但是内核接纳了 [Yinghai Lu 提供的补丁](https://lkml.org/lkml/2010/7/13/68)后改名为 `memblock` 。`x86_64` 架构上的内核会使用这个方法。我们已经在[讲解内核进入点之前的准备工作](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-3.html)时遇到过了它。现在是时候对它更加熟悉了。我们会看到它是被怎样实现的。
|
||||
|
||||
我们首先会学习 `memblock` 的数据结构。以下所有的数据结构都在 [include/linux/memblock.h](https://github.com/torvalds/linux/blob/master/include/linux/memblock.h) 头文件中定义。
|
||||
|
||||
@@ -160,7 +160,7 @@ On this step the initialization of the `memblock` structure has been finished an
|
||||
memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
|
||||
```
|
||||
|
||||
函数。我们将内存块类型 - `memory`,内存基址和内存区域大小,节点的最大数目和标志传进去。如果 `CONFIG_NODES_SHIFT` 没有被设置,最大节点数目就是 1,否则是 `1 << CONFIG_NODES_SHIFT`。`memblock_add_range` 函数将新的内存区域加到了内存块中,它首先检查传入内存区域的大小,如果是 0 就直接返回。然后,这个函数会用 `memblock_type` 来检查 `memblock` 中的内存区域是否存在。如果不存在,我们就简单地用给定的值填充一个新的 `memory_region` 然后返回(我们已经在[对内核内存管理框架的初览](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-3.html)中看到了它的实现)。如果 `memblock_type` 不为空,我们就会使用提供的 `memblock_type` 将新的内存区域加到 `memblock` 中。
|
||||
函数。我们将内存块类型 - `memory`,内存基址和内存区域大小,节点的最大数目和标志传进去。如果 `CONFIG_NODES_SHIFT` 没有被设置,最大节点数目就是 1,否则是 `1 << CONFIG_NODES_SHIFT`。`memblock_add_range` 函数将新的内存区域加到了内存块中,它首先检查传入内存区域的大小,如果是 0 就直接返回。然后,这个函数会用 `memblock_type` 来检查 `memblock` 中的内存区域是否存在。如果不存在,我们就简单地用给定的值填充一个新的 `memory_region` 然后返回(我们已经在[对内核内存管理框架的初览](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-3.html)中看到了它的实现)。如果 `memblock_type` 不为空,我们就会使用提供的 `memblock_type` 将新的内存区域加到 `memblock` 中。
|
||||
|
||||
首先,我们获取了内存区域的结束点:
|
||||
|
||||
@@ -415,4 +415,4 @@ memblock_dbg("memblock_reserve: [%#016llx-%#016llx] flags %#02lx %pF\n",
|
||||
* [e820](http://en.wikipedia.org/wiki/E820)
|
||||
* [numa](http://en.wikipedia.org/wiki/Non-uniform_memory_access)
|
||||
* [debugfs](http://en.wikipedia.org/wiki/Debugfs)
|
||||
* [对内核内存管理框架的初览](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-3.html)
|
||||
* [对内核内存管理框架的初览](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-3.html)
|
||||
@@ -4,7 +4,7 @@
|
||||
固定映射地址和输入输出重映射
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
固定映射地址是一组特殊的编译时确定的地址,它们与物理地址不一定具有减 `__START_KERNEL_map` 的线性映射关系。每一个固定映射的地址都会映射到一个内存页,内核会像指针一样使用它们,但是绝不会修改它们的地址。这是这种地址的主要特点。就像注释所说的那样,“在编译期就获得一个常量地址,只有在引导阶段才会被设定上物理地址。”你在本书的[前面部分](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-1.html)可以看到,我们已经设定了 `level2_fixmap_pgt` :
|
||||
固定映射地址是一组特殊的编译时确定的地址,它们与物理地址不一定具有减 `__START_KERNEL_map` 的线性映射关系。每一个固定映射的地址都会映射到一个内存页,内核会像指针一样使用它们,但是绝不会修改它们的地址。这是这种地址的主要特点。就像注释所说的那样,“在编译期就获得一个常量地址,只有在引导阶段才会被设定上物理地址。”你在本书的[前面部分](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-1.html)可以看到,我们已经设定了 `level2_fixmap_pgt` :
|
||||
|
||||
```assembly
|
||||
NEXT_PAGE(level2_fixmap_pgt)
|
||||
@@ -79,7 +79,7 @@ static inline unsigned long virt_to_fix(const unsigned long vaddr)
|
||||
|
||||
一个 PFN 是一块页大小物理内存的下标。一个物理地址的 PFN 可以简单地定义为 (page_phys_addr >> PAGE_SHIFT);
|
||||
|
||||
`__virt_to_fix` 会清空给定地址的前 12 位,然后用固定映射区域的末地址(`FIXADDR_TOP`)减去它并右移 `PAGE_SHIFT` 即 12 位。让我们来解释它的工作原理。就像我已经写的那样,这个宏会使用 `x & PAGE_MASK` 来清空前 12 位。然后我们用 `FIXADDR_TOP` 减去它,就会得到 `FIXADDR_TOP` 的后 12 位。我们知道虚拟地址的前 12 位代表这个页的偏移量,当我们右移 `PAGE_SHIFT` 后就会得到 `Page frame number` ,即虚拟地址的所有位,包括最开始的 12 个偏移位。固定映射地址在[内核中多处使用](http://lxr.free-electrons.com/ident?i=fix_to_virt)。 `IDT` 描述符保存在这里,[英特尔可信赖执行技术](http://en.wikipedia.org/wiki/Trusted_Execution_Technology) UUID 储存在固定映射区域,以 `FIX_TBOOT_BASE` 下标开始。另外, [Xen](http://en.wikipedia.org/wiki/Xen) 引导映射等也储存在这个区域。我们已经在[内核初始化的第五部分](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-5.html)看到了一部分关于固定映射地址的知识。接下来让我们看看什么是 `ioremap`,看看它是怎样实现的,与固定映射地址又有什么关系呢?
|
||||
`__virt_to_fix` 会清空给定地址的前 12 位,然后用固定映射区域的末地址(`FIXADDR_TOP`)减去它并右移 `PAGE_SHIFT` 即 12 位。让我们来解释它的工作原理。就像我已经写的那样,这个宏会使用 `x & PAGE_MASK` 来清空前 12 位。然后我们用 `FIXADDR_TOP` 减去它,就会得到 `FIXADDR_TOP` 的后 12 位。我们知道虚拟地址的前 12 位代表这个页的偏移量,当我们右移 `PAGE_SHIFT` 后就会得到 `Page frame number` ,即虚拟地址的所有位,包括最开始的 12 个偏移位。固定映射地址在[内核中多处使用](http://lxr.free-electrons.com/ident?i=fix_to_virt)。 `IDT` 描述符保存在这里,[英特尔可信赖执行技术](http://en.wikipedia.org/wiki/Trusted_Execution_Technology) UUID 储存在固定映射区域,以 `FIX_TBOOT_BASE` 下标开始。另外, [Xen](http://en.wikipedia.org/wiki/Xen) 引导映射等也储存在这个区域。我们已经在[内核初始化的第五部分](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html)看到了一部分关于固定映射地址的知识。接下来让我们看看什么是 `ioremap`,看看它是怎样实现的,与固定映射地址又有什么关系呢?
|
||||
|
||||
输入输出重映射
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -133,7 +133,7 @@ $ cat /proc/ioports
|
||||
* `n` - 区域的长度;
|
||||
* `name` - 区域需求者的名字。
|
||||
|
||||
`request_region` 分配 I/O 端口区域。通常在 `request_region` 之前会调用 `check_region` 来检查传入的地址区间是否可用,然后 `release_region` 会释放这个内存区域。`request_region` 返回指向 `resource` 结构体的指针。 `resource` 结构体是对系统资源的树状子集的抽象。我们已经在[内核初始化的第五部分](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-5.html)见到过它了,它的定义是这样的:
|
||||
`request_region` 分配 I/O 端口区域。通常在 `request_region` 之前会调用 `check_region` 来检查传入的地址区间是否可用,然后 `release_region` 会释放这个内存区域。`request_region` 返回指向 `resource` 结构体的指针。 `resource` 结构体是对系统资源的树状子集的抽象。我们已经在[内核初始化的第五部分](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html)见到过它了,它的定义是这样的:
|
||||
|
||||
```C
|
||||
struct resource {
|
||||
@@ -258,13 +258,13 @@ static inline const char *e820_type_to_string(int e820_type)
|
||||
|
||||
我们可以在 `/proc/iomem` 中看到它们。
|
||||
|
||||
现在让我们尝试着理解 `ioremap` 是如何工作的。我们已经了解了一部分 `ioremap` 的知识,我们在[内核初始化的第五部分](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-5.html)见过它。如果你读了那个章节,你就会记得 [arch/x86/mm/ioremap.c](https://github.com/torvalds/linux/blob/master/arch/x86/mm/ioremap.c) 文件中对 `early_ioremap_init` 函数的调用。对 `ioremap` 的初始化分为两个部分:有一部分在我们正常使用 `ioremap` 之前,但是要首先进行 `vmalloc` 的初始化并调用 `paging_init` 才能进行正常的 `ioremap` 调用。我们现在还不了解 `vmalloc` 的知识,先看看第一部分的初始化。首先 `early_ioremap_init` 会检查固定映射是否与页中部目录对齐:
|
||||
现在让我们尝试着理解 `ioremap` 是如何工作的。我们已经了解了一部分 `ioremap` 的知识,我们在[内核初始化的第五部分](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html)见过它。如果你读了那个章节,你就会记得 [arch/x86/mm/ioremap.c](https://github.com/torvalds/linux/blob/master/arch/x86/mm/ioremap.c) 文件中对 `early_ioremap_init` 函数的调用。对 `ioremap` 的初始化分为两个部分:有一部分在我们正常使用 `ioremap` 之前,但是要首先进行 `vmalloc` 的初始化并调用 `paging_init` 才能进行正常的 `ioremap` 调用。我们现在还不了解 `vmalloc` 的知识,先看看第一部分的初始化。首先 `early_ioremap_init` 会检查固定映射是否与页中部目录对齐:
|
||||
|
||||
```C
|
||||
BUILD_BUG_ON((fix_to_virt(0) + PAGE_SIZE) & ((1 << PMD_SHIFT) - 1));
|
||||
```
|
||||
|
||||
更多关于 `BUILD_BUG_ON` 的内容你可以在[内核初始化的第一部分](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-1.html)看到。如果给定的表达式为真,`BUILD_BUG_ON` 宏就会抛出一个编译时错误。在检查后的下一步,我们可以看到对 `early_ioremap_setup` 函数的调用,这个函数定义在 [mm/early_ioremap.c](https://github.com/torvalds/linux/blob/master/mm/early_ioremap.c) 文件中。这个函数代表了对 `ioremap` 的大体初始化。`early_ioremap_setup` 函数用初期固定映射的地址填充了 `slot_virt` 数组。所有初期固定映射地址在内存中都在 `__end_of_permanent_fixed_addresses` 后面,它们从 `FIX_BITMAP_BEGIN` 开始,到 `FIX_BITMAP_END` 结束。实际上初期 `ioremap` 会使用 `512` 个临时引导时映射:
|
||||
更多关于 `BUILD_BUG_ON` 的内容你可以在[内核初始化的第一部分](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-1.html)看到。如果给定的表达式为真,`BUILD_BUG_ON` 宏就会抛出一个编译时错误。在检查后的下一步,我们可以看到对 `early_ioremap_setup` 函数的调用,这个函数定义在 [mm/early_ioremap.c](https://github.com/torvalds/linux/blob/master/mm/early_ioremap.c) 文件中。这个函数代表了对 `ioremap` 的大体初始化。`early_ioremap_setup` 函数用初期固定映射的地址填充了 `slot_virt` 数组。所有初期固定映射地址在内存中都在 `__end_of_permanent_fixed_addresses` 后面,它们从 `FIX_BITMAP_BEGIN` 开始,到 `FIX_BITMAP_END` 结束。实际上初期 `ioremap` 会使用 `512` 个临时引导时映射:
|
||||
|
||||
```
|
||||
#define NR_FIX_BTMAPS 64
|
||||
@@ -319,7 +319,7 @@ pmd_populate_kernel(&init_mm, pmd, bm_pte);
|
||||
|
||||
`pmd_populate_kernel` 函数有三个参数:
|
||||
|
||||
* `init_mm` - `init` 进程的内存描述符 (你可以在[前文](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-5.html)中看到);
|
||||
* `init_mm` - `init` 进程的内存描述符 (你可以在[前文](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html)中看到);
|
||||
* `pmd` - `ioremap` 固定映射开始处的页中部目录;
|
||||
* `bm_pte` - 初期 `ioremap` 页表入口数组定义为:
|
||||
|
||||
@@ -519,5 +519,5 @@ prev_map[slot] = NULL;
|
||||
* [e820](http://en.wikipedia.org/wiki/E820)
|
||||
* [Memory management unit](http://en.wikipedia.org/wiki/Memory_management_unit)
|
||||
* [TLB](http://en.wikipedia.org/wiki/Translation_lookaside_buffer)
|
||||
* [Paging](http://0xax.gitbooks.io/linux-insides/content/Theory/Paging.html)
|
||||
* [内核内存管理第一部分](http://0xax.gitbooks.io/linux-insides/content/mm/linux-mm-1.html)
|
||||
* [Paging](http://xinqiu.gitbooks.io/linux-insides-cn/content/Theory/Paging.html)
|
||||
* [内核内存管理第一部分](http://xinqiu.gitbooks.io/linux-insides-cn/content/MM/linux-mm-1.html)
|
||||
Reference in New Issue
Block a user