From 27f9bbc1605b15449d9667c707520d70c9b82afa Mon Sep 17 00:00:00 2001 From: Iru Cai Date: Fri, 16 Mar 2018 11:08:03 +0800 Subject: [PATCH 01/14] translating 1.5,1.6 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9331119..8c1af50 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ |├ [1.2](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-2.md)|[@hailincai](https://github.com/hailincai)|已完成| |├ [1.3](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md)|[@hailincai](https://github.com/hailincai)|已完成| |├ [1.4](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-4.md)|[@zmj1316](https://github.com/zmj1316)|已完成| -|├ [1.5](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-5.md)||未开始| -|└ [1.6](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-6.md)||未开始| +|├ [1.5](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-5.md)||正在进行| +|└ [1.6](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-6.md)||正在进行| | 2. [Initialization](https://github.com/MintCN/linux-insides-zh/tree/master/Initialization)||正在进行| |├ [2.0](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/README.md)|[@mudongliang](https://github.com/mudongliang)|更新至[44017507](https://github.com/0xAX/linux-insides/commit/4401750766f7150dcd16f579026f5554541a6ab9)| |├ [2.1](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-1.md)|[@dontpanic92](https://github.com/dontpanic92)|更新至[44017507](https://github.com/0xAX/linux-insides/commit/4401750766f7150dcd16f579026f5554541a6ab9)| From ba1d5b325217f436745a5c8de0651c869e082155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=87=86=E5=A4=87=E6=89=BE=E5=B7=A5=E4=BD=9C=E4=B8=AD?= Date: Wed, 21 Mar 2018 09:56:54 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E4=B8=8A=E6=B8=B8=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit update to the latest coreboot code --- Booting/linux-bootstrap-1.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index a7606ac..3f1cbd1 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -72,25 +72,27 @@ PhysicalAddress = Segment * 16 + Offset 得到的 `0xfffffff0` 是 4GB - 16 字节。 这个地方是 [复位向量(Reset vector)](http://en.wikipedia.org/wiki/Reset_vector) 。 这是CPU在重置后期望执行的第一条指令的内存地址。它包含一个 [jump](http://en.wikipedia.org/wiki/JMP_%28x86_instruction%29) 指令,这个指令通常指向BIOS入口点。举个例子,如果访问 [coreboot](http://www.coreboot.org/) 源代码,将看到: ```assembly - .section ".reset" + .section ".reset", "ax", %progbits .code16 -.globl reset_vector -reset_vector: +.globl _start +_start: .byte 0xe9 - .int _start - ( . + 2 ) + .int _start16bit - ( . + 2 ) ... ``` -上面的跳转指令( [opcode](http://ref.x86asm.net/coder32.html#xE9) - 0xe9)跳转到地址 `_start - ( . + 2)` 去执行代码。 `reset` 段是16字节代码段, 起始于地址 -`0xfffffff0`,因此 CPU 复位之后,就会跳到这个地址来执行相应的代码 : +上面的跳转指令( [opcode](http://ref.x86asm.net/coder32.html#xE9) - 0xe9)跳转到地址  `_start16bit - ( . + 2)` 去执行代码。 `reset` 段是 `16` 字节代码段, 起始于地址 +`0xfffffff0`(`src/cpu/x86/16bit/reset16.ld`),因此 CPU 复位之后,就会跳到这个地址来执行相应的代码 : ``` SECTIONS { + /* Trigger an error if I have an unuseable start address */ + _bogus = ASSERT(_start16bit >= 0xffff0000, "_start16bit too low. Please report."); _ROMTOP = 0xfffffff0; . = _ROMTOP; .reset . : { - *(.reset) - . = 15 ; + *(.reset); + . = 15; BYTE(0x00); } } From 1a98426f360d7aba0a19a381bff9593bb4a764bd Mon Sep 17 00:00:00 2001 From: kele1997 <2363618563@qq.com> Date: Wed, 21 Mar 2018 10:23:24 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=E7=BF=BB=E8=AF=912.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c1af50..74de413 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ |├ [2.3](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-3.md)|[@dontpanic92](https://github.com/dontpanic92)|更新至[44017507](https://github.com/0xAX/linux-insides/commit/4401750766f7150dcd16f579026f5554541a6ab9)| |├ [2.4](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-4.md)|[@bjwrkj](https://github.com/bjwrkj)|已完成| |├ [2.5](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-5.md)|[@NeoCui](https://github.com/NeoCui)|更新至[cf32dc6c](https://github.com/0xAX/linux-insides/commit/cf32dc6c81abce567af330c480afc3d58678443d)| -|├ [2.6](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-6.md)||未开始| +|├ [2.6](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-6.md)|[@kele1997](https://github.com/kele1997)|正在进行| |├ [2.7](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-7.md)||未开始| |├ [2.8](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-8.md)||未开始| |├ [2.9](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-9.md)||未开始| From ef39b87f3053e0d9c9d9792a07fcc41f3cda3ecd Mon Sep 17 00:00:00 2001 From: kele1997 <2363618563@qq.com> Date: Wed, 21 Mar 2018 20:04:35 +0800 Subject: [PATCH 04/14] 2018-3-21-100-lines --- Initialization/linux-initialization-6-zh.md | 100 ++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 Initialization/linux-initialization-6-zh.md diff --git a/Initialization/linux-initialization-6-zh.md b/Initialization/linux-initialization-6-zh.md new file mode 100644 index 0000000..ce14af8 --- /dev/null +++ b/Initialization/linux-initialization-6-zh.md @@ -0,0 +1,100 @@ +内核初始化 第六部分 +=========================================================== + +仍旧是与系统架构有关的初始化 +=========================================================== + +在之前的[章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html)我们从 [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c)了解了特定系统架构 (在我们的例子中是 `x86_64` )的初始化内容,并且通过 `x86_configure_nx` 函数根据对[NX bit](http://en.wikipedia.org/wiki/NX_bit)的支持设置 `_PAGE_NX` 标志。正如我之前写的, `setup_arch` 函数和 `start_kernel` 都非常庞大,所以在这个和下个章节我们将继续学习关于系统架构初始化进程的内容。`x86_configure_nx` 函数的下面是 `parse_early_param`。这个函数的定义在 [init/main.c](https://github.com/torvalds/linux/blob/master/init/main.c) 并且你可以从它的名字中了解到,这个函数解析内核命令行并且基于给定的参数 (所有的内核命令行参数你都可以在 [Documentation/kernel-parameters.txt](https://github.com/torvalds/linux/blob/master/Documentation/kernel-parameters.txt) 找到)。 你可能记得在最前面的 [章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html) 我们怎样安装 `earlyprintk`。在早期我们在 [arch/x86/boot/cmdline.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/cmdline.c)里面的 `cmdline_find_option` 函数和 `__cmdline_find_option`, `__cmdline_find_option_bool` 的帮助下寻找内核参数和他们的值。我们在通用内核部分不依赖于系统架构,在这里我们使用另一种方法。 如果你正在阅读linux内核源代码,你可能注意到这样的调用: + +```C +early_param("gbpages", parse_direct_gbpages_on); +``` + +`early_param` 宏需要两个参数: + +* 命令行参数的名称 +* 如果给定的参数通过,函数将被调用 + +并且定义如下: + +```C +#define early_param(str, fn) \ + __setup_param(str, fn, fn, 1) +``` + +这个定义可以在 [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h)中可以找到. +正如你所看到的 `early_param` 宏只是调用了 `__setup_param` 宏: + +```C +#define __setup_param(str, unique_id, fn, early) \ + static const char __setup_str_##unique_id[] __initconst \ + __aligned(1) = str; \ + static struct obs_kernel_param __setup_##unique_id \ + __used __section(.init.setup) \ + __attribute__((aligned((sizeof(long))))) \ + = { __setup_str_##unique_id, fn, early } +``` + +这个宏定义了 `__setup_str_*_id` 变量 (这里的 `*` 取决于被给定的函数名称) 并且把给定的命令行参数赋值给这个变量。在下一行中,我们可以看到类型为 `obs_kernel_param` 的变量 `__setup_ *` 的定义及其初始化。 + +`obs_kernel_param` 结构体定义如下: + +```C +struct obs_kernel_param { + const char *str; + int (*setup_func)(char *); + int early; +}; +``` + +这个结构体包含三个字段: + +* 内核参数的名称 +* 功能取决于参数的函数 +* 决定参数是否为 early 的标记位 (译者注:这个参数是个标记位,有0和1两种值,两种值的后续调用是不一样的) + +注意 `__set_param` 宏定义有 `__section(.init.setup)` 属性。这意味着所有 `__setup_str_ *` 将被放置在 `.init.setup` 区段中,此外正如我们在 [include/asm-generic/vmlinux.lds.h](https://github.com/torvalds/linux/blob/master/include/asm-generic/vmlinux.lds.h) 中看到的,他们将被放置在 `__setup_start` 和 `__setup_end` 之间: + +``` +#define INIT_SETUP(initsetup_align) \ + . = ALIGN(initsetup_align); \ + VMLINUX_SYMBOL(__setup_start) = .; \ + *(.init.setup) \ + VMLINUX_SYMBOL(__setup_end) = .; +``` + +现在我们知道了参数是怎样定义的,让我们一起回到 `parse_early_param` 的实现: + +```C +void __init parse_early_param(void) +{ + static int done __initdata; + static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata; + + if (done) + return; + + /* All fall through to do_early_param. */ + strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE); + parse_early_options(tmp_cmdline); + done = 1; +} +``` +`parse_early_param` 函数定义了两个静态变量。首先第一个变量 `done` 用来检查 `parse_early_param` 函数是否已经被调用,第二个变量是用来临时存储内核命令行的。在这之后我们把 `boot_command_line` 赋值到我们刚才定义的临时命令行变量中( `tmp_cmdline` ) 并且从相同的源代码文件 `main.c` 中调用 `parse_early_options` 函数。 `parse_early_options`函数从 [kernel/params.c](https://github.com/torvalds/linux/blob/master/) 中调用 `parse_args` 函数, `parse_args` 解析传入的命令行并且调用 `do_early_param` 函数。 这个 [函数](https://github.com/torvalds/linux/blob/master/init/main.c#L413) 从 ` __setup_start` 循环到 `__setup_end` ,并且如果 `obs_kernel_param` 中的 `early` 字段值为early(1) ,从 `obs_kernel_param` 调用函数(译者注:调用结构体中的第二个参数,这个参数是个函数)。在这之后所有基于早期命令行参数的服务都被创建,在 `parse_early_param` 之后的下一个调用是 `x86_report_nx` 。 正如我在这章开头所写的,我们已经用 `x86_configure_nx` 配置了 `NX-bit` 位。接下来的 [arch/x86/mm/setup_nx.c](https://github.com/torvalds/linux/blob/master/arch/x86/mm/setup_nx.c)中的 `x86_report_nx`函数仅仅打印出关于 `NX` 的信息。注意我们 `x86_report_nx` 不一定在 `x86_configure_nx` 之后调用,但是一定在 `parse_early_param` 之后调用。答案很简单: 因为内核支持 `noexec` 参数,所以我们在 `parse_early_param` 之后调用 `x86_report_nx` : + +``` +noexec [X86] + On X86-32 available only on PAE configured kernels. + noexec=on: enable non-executable mappings (default) + noexec=off: disable non-executable mappings +``` + +我们可以在启动的时候看到: + +![NX](http://oi62.tinypic.com/swwxhy.jpg) + +在这之后我们可以看到下面函数的调用: + +```C + memblock_x86_reserve_range_setup_data(); +``` From 5478d8e9719b53cf5e4997b23f48784c59274aed Mon Sep 17 00:00:00 2001 From: kele1997 <2363618563@qq.com> Date: Sun, 25 Mar 2018 23:29:14 +0800 Subject: [PATCH 05/14] update-400-lines --- Initialization/linux-initialization-6-zh.md | 312 ++++++++++++++++++++ 1 file changed, 312 insertions(+) diff --git a/Initialization/linux-initialization-6-zh.md b/Initialization/linux-initialization-6-zh.md index ce14af8..bdebcc3 100644 --- a/Initialization/linux-initialization-6-zh.md +++ b/Initialization/linux-initialization-6-zh.md @@ -98,3 +98,315 @@ noexec [X86] ```C memblock_x86_reserve_range_setup_data(); ``` + +这个函数被定义在相同的源代码文件 [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c) 中,并且为 `setup_data` 重新映射内存,保留内存块(你可以阅读之前的 [章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html) 了解关于 `setup_data` 的更多内容,你也可以在[Linux kernel memory management](http://xinqiu.gitbooks.io/linux-insides-cn/content/MM/index.html) 中阅读到关于 `ioremap` and `memblock` 的内容)。 + +接下来我们来看看下面的条件语句: + +```C + if (acpi_mps_check()) { +#ifdef CONFIG_X86_LOCAL_APIC + disable_apic = 1; +#endif + setup_clear_cpu_cap(X86_FEATURE_APIC); + } +``` + +[arch/x86/kernel/acpi/boot.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/acpi/boot.c) 中的函数 `acpi_mps_check` 取决于 `CONFIG_X86_LOCAL_APIC` 和 `CONFIG_x86_MPPARSE` 配置选项: + +```C +int __init acpi_mps_check(void) +{ +#if defined(CONFIG_X86_LOCAL_APIC) && !defined(CONFIG_X86_MPPARSE) + /* mptable code is not built-in*/ + if (acpi_disabled || acpi_noirq) { + printk(KERN_WARNING "MPS support code is not built-in.\n" + "Using acpi=off or acpi=noirq or pci=noacpi " + "may have problem\n"); + return 1; + } +#endif + return 0; +} +``` + +这个函数检查内置的 `MPS` 又称 [多重处理器规范]((http://en.wikipedia.org/wiki/MultiProcessor_Specification)) 表。如果设置了 ` CONFIG_X86_LOCAL_APIC` 但未设置 `CONFIG_x86_MPPAARSE` ,并且传递给内核的命令行参数是 `acpi=off`、`acpi=noirq` 或者 `pci=noacpi`,那么`acpi_mps_check` 就会打印出警告信息。如果 `acpi_mps_check` 返回1,这表示我们警用了本地 [APIC](http://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller) +,而且通过 `setup_clear_cpu_cap` 宏清除了当前CPU中的 `X86_FEATURE_APIC` 位。(你可以阅读[CPU masks](https://xinqiu.gitbooks.io/linux-insides-cn/content/Concepts/linux-cpu-2.html)了解更多关于CPU mask的内容)。 + +早期的PCI转储 +-------------------------------------------------------------------------------- + +接下来我们使用下面的代码来转储 [PCI](http://en.wikipedia.org/wiki/Conventional_PCI) 设备: + +```C +#ifdef CONFIG_PCI + if (pci_early_dump_regs) + early_dump_pci_devices(); +#endif +``` + +`pci_early_dump_regs` 定义在 [arch/x86/pci/common.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/common.c) 中,并且他的值是取决于内核命令行参数:`pci=earlydump` 。我们可以在[drivers/pci/pci.c](https://github.com/torvalds/linux/blob/master/arch) 中看到这个参数的定义: + +```C +early_param("pci", pci_setup); +``` + +`pci_setup` 函数获得 `pci=` 之后的字符串,然后进行分析。这个函数调用了在 [drivers/pci/pci.c](https://github.com/torvalds/linux/blob/master/arch) 中用 `_weak` 修饰符定义的 `pcibios_setup` 函数,并且每种架构都重写了 `_weak` 修饰过的函数。 例如 依赖于 `x86_64` 架构的版本写在[arch/x86/pci/common.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/common.c)中: + +```C +char *__init pcibios_setup(char *str) { + ... + ... + ... + } else if (!strcmp(str, "earlydump")) { + pci_early_dump_regs = 1; + return NULL; + } + ... + ... + ... +} +``` + +所以,如果 `CONFIG_PCI` 选项被设置,而且我们向内核命令行传递了 `pci=earlydump` 选项,下一个被调用的函数是在 [arch/x86/pci/early.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/early.c)中的 `early_dump_pci_devices` 。这个函数使用下面代码检查 `noearly` pci 参数: + +```C +if (!early_pci_allowed()) + return; +``` + +同时如果条件通过则返回。每个PCI域可以承载多达 `256` 条总线,并且每条总线可以承载多达32个设备。那么接下来我们进行下面的循环: + +```C +for (bus = 0; bus < 256; bus++) { + for (slot = 0; slot < 32; slot++) { + for (func = 0; func < 8; func++) { + ... + ... + ... + } + } +} +``` + +并且通过 `read_pci_config` 函数读取 `pci` 配置。 + +这就是 pci 加载的全部了。我们在这里不会深入研究 `pci` 的细节,不过我们会在 `Drivers/PCI` 章节看到更多的细节。 + +完成内存解析 +-------------------------------------------------------------------------------- + + +在 `early_dump_pci_devices` 之后,有一些与可用内存和[e820](http://en.wikipedia.org/wiki/E820)相关的函数并且 [e820](http://en.wikipedia.org/wiki/E820) ,其中 [e820](http://en.wikipedia.org/wiki/E820) 我们在 [内核安装的第一步](http://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html) 章节中整理过。 +```C + /* update the e820_saved too */ + e820_reserve_setup_data(); + finish_e820_parsing(); + ... + ... + ... + e820_add_kernel_range(); + trim_bios_range(void); + max_pfn = e820_end_of_ram_pfn(); + early_reserve_e820_mpc_new(); +``` + +让我们来一起看看上面的代码。正如你所看到的,第一个函数是 `e820_reserve_setup_data` 。这个函数和我们前面看到的 `memblock_x86_reserve_range_setup_data` 函数做的事情几乎是相同的,但是这个函数同时还会调用 `e820_update_range` 函数,向 `e820map` 中用给定的类型添加新的区域,在我们的例子中,使用的是 `E820_RESERVED_KERN` 类型。接下来的函数是 `finish_e820_parsing` 。除了这两个函数之外,我们还可以看到一些与 [e820](http://en.wikipedia.org/wiki/E820) 有关的函数。你可以在上面的清单中看到这些函数。`e820_add_kernel_range` 函数需要内核开始和结束的物理地址: + +```C +u64 start = __pa_symbol(_text); +u64 size = __pa_symbol(_end) - start; +``` + +函数会检查在 `e820map` 中被标记成 `E820RAM` 的 `.text` `.data` 和 `.bss` 段,如果没有这些字段,那么就会输出错误信息。接下来的 `trm_bios_range` 函数把 `e820Map` 中的前4096个字节更新为 `E820_RESERVED` 并且调用函数 `sanitize_e820_map` 进行处理。在这之后我们使用 `e820_end_of_ram_pfn` 得到最后一个页面帧的编号,每个内存页面都有一个唯一的编号 - `页面帧编号`, `e820_end_of_ram_pfn` 函数调用 `e820_end_pfn` 函数返回这个最大的页面帧编号: +```C +unsigned long __init e820_end_of_ram_pfn(void) +{ + return e820_end_pfn(MAX_ARCH_PFN); +} +``` + +`e820_end_pfn` 函数读取当前系统架构的最大页面帧编号(对于 `x86_64` 架构来说 `MAX_ARCH_PFN` 是 `0x400000000` )。在 `e820_end_pfn` 函数中我们遍历整个 `e820` 插槽,并且检查 `e820` 入口是否有 `E820_RAM` 或者 `E820_PRAM` 类型,因为我们只能对这些类型计算页面帧编码,得到当前 `e820` 入口页面帧的基地址和结束地址,同时对这些地址进行检查: + +```C +for (i = 0; i < e820.nr_map; i++) { + struct e820entry *ei = &e820.map[i]; + unsigned long start_pfn; + unsigned long end_pfn; + + if (ei->type != E820_RAM && ei->type != E820_PRAM) + continue; + + start_pfn = ei->addr >> PAGE_SHIFT; + end_pfn = (ei->addr + ei->size) >> PAGE_SHIFT; + + if (start_pfn >= limit_pfn) + continue; + if (end_pfn > limit_pfn) { + last_pfn = limit_pfn; + break; + } + if (end_pfn > last_pfn) + last_pfn = end_pfn; +} +``` + +```C + if (last_pfn > max_arch_pfn) + last_pfn = max_arch_pfn; + + printk(KERN_INFO "e820: last_pfn = %#lx max_arch_pfn = %#lx\n", + last_pfn, max_arch_pfn); + return last_pfn; +``` + +接下来我们检查在循环中得到的 `last_pfn` 不得大于特定系统架构的最大页面帧编号(在我们的例子中是 `x86_64` 系统架构),输出关于最大页面帧编码的信息,并且返回它。我们可以在 `dmesg` 的输出中看到 `last_pfn` : + +``` +... +[ 0.000000] e820: last_pfn = 0x41f000 max_arch_pfn = 0x400000000 +... +``` + +在这之后,我们计算出了最大的页面帧编号,我们要计算 `max_low_pfn` ,这是 `小内存` 或者低于第一个4GB中的的的最大页面帧。如果安装了超过4GB的内存RAM,`max_low_pfn` 将会是 `e820_end_of_low_ram_pfn` 函数的结果,这个函数和 `e820_end_of_ram_pfn` 相似,但是有4GB自己的限制,换句话说 `max_low_pfn` 和 `max_pfn` 的值是一样的: + +```C +if (max_pfn > (1UL<<(32 - PAGE_SHIFT))) + max_low_pfn = e820_end_of_low_ram_pfn(); +else + max_low_pfn = max_pfn; + +high_memory = (void *)__va(max_pfn * PAGE_SIZE - 1) + 1; +``` + +接下来我们通过 `__va` 宏计算 `大内存` 中的最大页面帧编号(有更高的直接内存映射上界),这个宏根据给定的物理内存返回一个虚拟地址。 + + +直接媒体结构扫描 +------------------------------------------------------------------------------- + +在处理完不同内存区域和 `e820` 插槽之后的下一步就是收集有关计算机的信息。我们将获得与 [桌面管理接口](http://en.wikipedia.org/wiki/Desktop_Management_Interface) 和下面函数的所有信息: + +```C +dmi_scan_machine(); +dmi_memdev_walk(); +``` + +首先是定义在 [drivers/firmware/dmi_scan.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/drivers/firmware/dmi_scan.c) 中的 `dmi_scan_machine` 函数。这个函数浏览 [System Management BIOS](http://en.wikipedia.org/wiki/System_Management_BIOS) 结构,从中提取信息。制定了两种方法来获得 `SMBIOS` 表的访问权: 第一种是从 [EFI](http://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface) 的配置表获得指向 `SMBIOS` 表的指针;第二种是扫描 `0xF0000` 和 `0x10000` 地址之间的物理地址。让我们一起看看第二种方法。`dmi_scan_machine` 函数通过 `dmi_early_remap` 函数将 `0xf0000` 和 `0x10000` 之间的内存重新映射到 `early_ioremap`: + +```C +void __init dmi_scan_machine(void) +{ + char __iomem *p, *q; + char buf[32]; + ... + ... + ... + p = dmi_early_remap(0xF0000, 0x10000); + if (p == NULL) + goto error; +``` +然后遍历所有的 `DMI` 头地址,并且查找 `_SM_` 字符串: + +```C +memset(buf, 0, 16); +for (q = p; q < p + 0x10000; q += 16) { + memcpy_fromio(buf + 16, q, 16); + if (!dmi_smbios3_present(buf) || !dmi_present(buf)) { + dmi_available = 1; + dmi_early_unmap(p, 0x10000); + goto out; + } + memcpy(buf, buf + 16, 16); +} +``` +`_SM_` 字符串一定在 `000F0000h` 和 `0x000FFFFF` 地址之间。在这里我们用 `memcpy_fromio` 函数向 `buf` 里面复制16个字节,这个函数和 `memcpy` 函数的作用是一样的。然后对这个缓冲区( `buf` ) 执行`dmi_smbios3_present` 和 `dmi_present` 函数。这些函数检查 `buf`的前4个字节是否是 `__SM__` 字符串,得到 `SMBIOS` 的版本号,并且获得 `_DMI_` 的属性例如 `_DMI_` 结构表长度、表的地址等等...每当其中的一个函数完成之后,你就会在 `dmesg` 的输出中看到函数的运行结果: +``` +[ 0.000000] SMBIOS 2.7 present. +[ 0.000000] DMI: Gigabyte Technology Co., Ltd. Z97X-UD5H-BK/Z97X-UD5H-BK, BIOS F6 06/17/2014 +``` +在 `dmi_scan_machine` 函数的最后,我们取消之前重新映射的内存: + +```C +dmi_early_unmap(p, 0x10000); +``` + +第二个函数是 - `dmi_memdev_walk`。正如你理解的那样,这个函数遍历整个内存设备。让我们一起看看这个函数: + +```C +void __init dmi_memdev_walk(void) +{ + if (!dmi_available) + return; + + if (dmi_walk_early(count_mem_devices) == 0 && dmi_memdev_nr) { + dmi_memdev = dmi_alloc(sizeof(*dmi_memdev) * dmi_memdev_nr); + if (dmi_memdev) + dmi_walk_early(save_mem_devices); + } +} +``` + +这个函数检查 `DMI` 是否可用(我们之前在 `dmi_scan_machine` 函数中得到了这个结果)并且使用 `dmi_walk_early` 和 `dmi_alloc` 函数收集关于内存设备的信息,其中 `dmi_alloc` 的定义如下: + +``` +#ifdef CONFIG_DMI +RESERVE_BRK(dmi_alloc, 65536); +#endif +``` + +`RESERVE_BRK` 定义在 [arch/x86/include/asm/setup.h](http://en.wikipedia.org/wiki/Desktop_Management_Interface)中,并且这个函数在 `brk` 段中保留给定大小的空间: + +------------------------- + init_hypervisor_platform(); + x86_init.resources.probe_roms(); + insert_resource(&iomem_resource, &code_resource); + insert_resource(&iomem_resource, &data_resource); + insert_resource(&iomem_resource, &bss_resource); + early_gart_iommu_check(); + + +均衡多处理(SMP)的配置 +-------------------------------------------------------------------------------- + +接下来的一步是解析 [SMP](http://en.wikipedia.org/wiki/Symmetric_multiprocessing) 的配置信息。我们调用 `find_smp_config` 函数来完成这个任务,这个函数只是调用另一个函数: + +```C +static inline void find_smp_config(void) +{ + x86_init.mpparse.find_smp_config(); +} +``` + +在函数的内部,`x86_init.mpparse.find_smp_config` 就是 [arch/x86/kernel/mpparse.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/mpparse.c) 中的 `default_find_smp_config` 函数。在 `default_find_smp_config` 函数中我们扫描一些内存区域来寻找 `SMP` 的配置信息,并在找到它们的时候返回: + +```C +if (smp_scan_config(0x0, 0x400) || + smp_scan_config(639 * 0x400, 0x400) || + smp_scan_config(0xF0000, 0x10000)) + return; +``` + +首先 `smp_scan_config` 函数定义了一些变量: + +```C +unsigned int *bp = phys_to_virt(base); +struct mpf_intel *mpf; +``` + +第一个变量是我们用来扫描 `SMP` 配置内存区域的虚拟地址;第二个变量是指向 `mpf_intel` 结构体的指针。让我们一起试着去理解 `mpf_intel` 是什么吧。所有的信息都存储在多处理器配置数据结构中。`mpf_intel` 呈现了这个结构,看下来像是下面这样: + +```C +struct mpf_intel { + char signature[4]; + unsigned int physptr; + unsigned char length; + unsigned char specification; + unsigned char checksum; + unsigned char feature1; + unsigned char feature2; + unsigned char feature3; + unsigned char feature4; + unsigned char feature5; +}; +``` \ No newline at end of file From 6e0ac547ee989d5cd641ebb98f03428edbde3839 Mon Sep 17 00:00:00 2001 From: Iru Cai Date: Sun, 18 Mar 2018 14:46:11 +0800 Subject: [PATCH 06/14] 1.5 --- Booting/linux-bootstrap-5.md | 128 +++++++++++++++++------------------ 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/Booting/linux-bootstrap-5.md b/Booting/linux-bootstrap-5.md index 46f1e32..0efadba 100644 --- a/Booting/linux-bootstrap-5.md +++ b/Booting/linux-bootstrap-5.md @@ -1,15 +1,15 @@ -Kernel booting process. Part 5. +内核引导过程. Part 5. ================================================================================ -Kernel decompression +内核解压 -------------------------------------------------------------------------------- -This is the fifth part of the `Kernel booting process` series. We saw transition to the 64-bit mode in the previous [part](https://github.com/0xAX/linux-insides/blob/master/Booting/linux-bootstrap-4.md#transition-to-the-long-mode) and we will continue from this point in this part. We will see the last steps before we jump to the kernel code as preparation for kernel decompression, relocation and directly kernel decompression. So... let's start to dive in the kernel code again. +这是`内核引导过程`系列文章的第五部分。在[前一部分](linux-bootstrap-4.md#transition-to-the-long-mode)我们看到了切换到64位模式的过程,在这一部分我们会从这里继续。我们会看到跳进内核代码的最后步骤:内核解压前的准备、重定位和直接内核解压。所以...让我们再次深入内核源码。 -Preparation before kernel decompression +内核解压前的准备 -------------------------------------------------------------------------------- -We stopped right before the jump on the `64-bit` entry point - `startup_64` which is located in the [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S) source code file. We already saw the jump to the `startup_64` in the `startup_32`: +我们停在了跳转到`64位`入口点——`startup_64`的跳转之前,它在源文件 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S) 里面。在之前的部分,我们已经在`startup_32`里面看到了到`startup_64`的跳转: ```assembly pushl $__KERNEL_CS @@ -24,7 +24,7 @@ We stopped right before the jump on the `64-bit` entry point - `startup_64` whic lret ``` -in the previous part. Since we loaded the new `Global Descriptor Table` and there was CPU transition in other mode (`64-bit` mode in our case), we can see the setup of the data segments: +由于我们加载了新的`全局描述符表`并且在其他模式有CPU的模式转换(在我们这里是`64位`模式),我们可以在`startup_64`的开头看到数据段的建立: ```assembly .code64 @@ -38,9 +38,9 @@ ENTRY(startup_64) movl %eax, %gs ``` -in the beginning of the `startup_64`. All segment registers besides `cs` register now reseted as we joined into the `long mode`. +除`cs`之外的段寄存器在我们进入`长模式`时已经重置。 -The next step is computation of difference between where the kernel was compiled and where it was loaded: +下一步是计算内核编译时的位置和它被加载的位置的差: ```assembly #ifdef CONFIG_RELOCATABLE @@ -60,9 +60,9 @@ The next step is computation of difference between where the kernel was compiled addq %rbp, %rbx ``` -The `rbp` contains the decompressed kernel start address and after this code executes `rbx` register will contain address to relocate the kernel code for decompression. We already saw code like this in the `startup_32` ( you can read about it in the previous part - [Calculate relocation address](https://github.com/0xAX/linux-insides/blob/master/Booting/linux-bootstrap-4.md#calculate-relocation-address)), but we need to do this calculation again because the bootloader can use 64-bit boot protocol and `startup_32` just will not be executed in this case. +`rbp`包含了解压后内核的起始地址,在这段代码执行之后`rbx`会包含用于解压的重定位内核代码的地址。我们已经在`startup_32`看到类似的代码(你可以看之前的部分[计算重定位地址](https://github.com/0xAX/linux-insides/blob/master/Booting/linux-bootstrap-4.md#calculate-relocation-address)),但是我们需要再做这个计算,因为引导加载器可以用64位引导协议,而`startup_32`在这种情况下不会执行。 -In the next step we can see setup of the stack pointer and resetting of the flags register: +下一步,我们可以看到栈指针的设置和标志寄存器的重置: ```assembly leaq boot_stack_end(%rbx), %rsp @@ -71,7 +71,7 @@ In the next step we can see setup of the stack pointer and resetting of the flag popfq ``` -As you can see above, the `rbx` register contains the start address of the kernel decompressor code and we just put this address with `boot_stack_end` offset to the `rsp` register which represents pointer to the top of the stack. After this step, the stack will be correct. You can find definition of the `boot_stack_end` in the end of [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S) assembly source code file: +如上所述,`rbx`寄存器包含了内核解压代码的起始地址,我们把这个地址的`boot_stack_entry`偏移地址相加放到表示栈顶指针的`rsp`寄存器。在这一步之后,栈就是正确的。你可以在汇编源码文件 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S) 的末尾找到`boot_stack_end`的定义: ```assembly .bss @@ -83,9 +83,9 @@ boot_stack: boot_stack_end: ``` -It located in the end of the `.bss` section, right before the `.pgtable`. If you will look into [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/vmlinux.lds.S) linker script, you will find Definition of the `.bss` and `.pgtable` there. +它在`.bss`节的末尾,就在`.pgtable`前面。如果你查看 [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/vmlinux.lds.S) 链接脚本,你会找到`.bss`和`.pgtable`的定义。 -As we set the stack, now we can copy the compressed kernel to the address that we got above, when we calculated the relocation address of the decompressed kernel. Before details, let's look at this assembly code: +由于我们设置了栈,在我们计算了解压了的内核的重定位地址后,我们可以复制压缩了的内核到以上地址。在查看细节之前,我们先看这段汇编代码: ```assembly pushq %rsi @@ -99,9 +99,9 @@ As we set the stack, now we can copy the compressed kernel to the address that w popq %rsi ``` -First of all we push `rsi` to the stack. We need preserve the value of `rsi`, because this register now stores a pointer to the `boot_params` which is real mode structure that contains booting related data (you must remember this structure, we filled it in the start of kernel setup). In the end of this code we'll restore the pointer to the `boot_params` into `rsi` again. +首先我们把`rsi`压进栈。我们需要保存`rsi`的值,因为这个寄存器现在存放指向`boot_params`的指针,这是包含引导相关数据的实模式结构体(你一定记得这个结构体,我们在开始设置内核的时候就填充了它)。在代码的结尾,我们会重新恢复指向`boot_params`的指针到`rsi`. -The next two `leaq` instructions calculates effective addresses of the `rip` and `rbx` with `_bss - 8` offset and put it to the `rsi` and `rdi`. Why do we calculate these addresses? Actually the compressed kernel image is located between this copying code (from `startup_32` to the current code) and the decompression code. You can verify this by looking at the linker script - [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/vmlinux.lds.S): +接下来两个`leaq`指令用`_bss - 8`偏移和`rip`和`rbx`计算有效地址并存放到`rsi`和`rdi`. 我们为什么要计算这些地址?实际上,压缩了的代码镜像存放在这份复制了的代码(从`startup_32`到当前的代码)和解压了的代码之间。你可以通过查看链接脚本 [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/vmlinux.lds.S) 验证: ``` . = 0; @@ -121,7 +121,7 @@ The next two `leaq` instructions calculates effective addresses of the `rip` and } ``` -Note that `.head.text` section contains `startup_32`. You may remember it from the previous part: +注意`.head.text`节包含了`startup_32`. 你可以从之前的部分回忆起它: ```assembly __HEAD @@ -132,7 +132,7 @@ ENTRY(startup_32) ... ``` -The `.text` section contains decompression code: +`.text`节包含解压代码: ```assembly .text @@ -146,21 +146,21 @@ relocated: ... ``` -And `.rodata..compressed` contains the compressed kernel image. So `rsi` will contain the absolute address of `_bss - 8`, and `rdi` will contain the relocation relative address of `_bss - 8`. As we store these addresses in registers, we put the address of `_bss` in the `rcx` register. As you can see in the `vmlinux.lds.S` linker script, it's located at the end of all sections with the setup/kernel code. Now we can start to copy data from `rsi` to `rdi`, `8` bytes at the time, with the `movsq` instruction. +`.rodata..compressed`包含了压缩了的内核镜像。所以`rsi`包含`_bss - 8`的绝对地址,`rdi`包含`_bss - 8`的重定位的相对地址。在我们把这些地址放入寄存器时,我们把`_bss`的地址放到了`rcx`寄存器。正如你在`vmlinux.lds.S`链接脚本中看到了一样,它和设置/内核代码一起在所有节的末尾。现在我们可以开始用`movsq`指令每次8字节地从`rsi`到`rdi`复制代码。 -Note that there is an `std` instruction before data copying: it sets the `DF` flag, which means that `rsi` and `rdi` will be decremented. In other words, we will copy the bytes backwards. At the end, we clear the `DF` flag with the `cld` instruction, and restore `boot_params` structure to `rsi`. +注意在数据复制前有`std`指令:它设置`DF`标志,意味着`rsi`和`rdi`会递减。换句话说,我们会从后往前复制这些字节。最后,我们用`cld`指令清除`DF`标志,并恢复`boot_params`到`rsi`. -Now we have the address of the `.text` section address after relocation, and we can jump to it: +现在我们有`.text`节的重定位后的地址,我们可以跳到那里: ```assembly leaq relocated(%rbx), %rax jmp *%rax ``` -Last preparation before kernel decompression +在内核解压前的最后准备 -------------------------------------------------------------------------------- -In the previous paragraph we saw that the `.text` section starts with the `relocated` label. The first thing it does is clearing the `bss` section with: +在上一段我们看到了`.text`节从`relocated`标签开始。它做的第一件事是清空`.bss`节: ```assembly xorl %eax, %eax @@ -171,9 +171,9 @@ In the previous paragraph we saw that the `.text` section starts with the `reloc rep stosq ``` -We need to initialize the `.bss` section, because we'll soon jump to [C](https://en.wikipedia.org/wiki/C_%28programming_language%29) code. Here we just clear `eax`, put the address of `_bss` in `rdi` and `_ebss` in `rcx`, and fill it with zeros with the `rep stosq` instruction. +我们要初始化`.bss`节,因为我们很快要跳转到[C](https://en.wikipedia.org/wiki/C_%28programming_language%29)代码。这里我们就清空`eax`,把`_bss`的地址放到`rdi`,把`_ebss`放到`rcx`,然后用`rep stosq`填零。 -At the end, we can see the call to the `extract_kernel` function: +最后,我们可以调用`extract_kernel`函数: ```assembly pushq %rsi @@ -187,49 +187,49 @@ At the end, we can see the call to the `extract_kernel` function: popq %rsi ``` -Again we set `rdi` to a pointer to the `boot_params` structure and preserve it on the stack. In the same time we set `rsi` to point to the area which should be usedd for kernel uncompression. The last step is preparation of the `extract_kernel` parameters and call of this function which will uncompres the kernel. The `extract_kernel` function is defined in the [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/misc.c) source code file and takes six arguments: +我们再一次设置`rdi`为指向`boot_params`结构体的指针并把它保存到栈中。同时我们设置`rsi`指向用于内核解压的区域。最后一步是准备`extract_kernel`的参数并调用这个解压内核的函数。`extract_kernel`函数在 [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/misc.c) 源文件定义并有六个参数: -* `rmode` - pointer to the [boot_params](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973//arch/x86/include/uapi/asm/bootparam.h#L114) structure which is filled by bootloader or during early kernel initialization; -* `heap` - pointer to the `boot_heap` which represents start address of the early boot heap; -* `input_data` - pointer to the start of the compressed kernel or in other words pointer to the `arch/x86/boot/compressed/vmlinux.bin.bz2`; -* `input_len` - size of the compressed kernel; -* `output` - start address of the future decompressed kernel; -* `output_len` - size of decompressed kernel; +* `rmode` - 指向 [boot_params](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973//arch/x86/include/uapi/asm/bootparam.h#L114) 结构体的指针,`boot_params`被引导加载器填充或在早期内核初始化时填充 +* `heap` - 指向早期启动堆的起始地址 `boot_heap` 的指针 +* `input_data` - 指向压缩的内核,即 `arch/x86/boot/compressed/vmlinux.bin.bz2` 的指针 +* `input_len` - 压缩的内核的大小 +* `output` - 解压后内核的起始地址 +* `output_len` - 解压后内核的大小 -All arguments will be passed through the registers according to [System V Application Binary Interface](http://www.x86-64.org/documentation/abi.pdf). We've finished all preparation and can now look at the kernel decompression. +所有参数根据 [System V Application Binary Interface](http://www.x86-64.org/documentation/abi.pdf) 通过寄存器传递。我们已经完成了所有的准备工作,现在我们可以看内核解压的过程。 -Kernel decompression +内核解压 -------------------------------------------------------------------------------- -As we saw in previous paragraph, the `extract_kernel` function is defined in the [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/misc.c) source code file and takes six arguments. This function starts with the video/console initialization that we already saw in the previous parts. We need to do this again because we don't know if we started in [real mode](https://en.wikipedia.org/wiki/Real_mode) or a bootloader was used, or whether the bootloader used the `32` or `64-bit` boot protocol. +就像我们在之前的段落中看到了那样,`extract_kernel`函数在源文件 [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/misc.c) 定义并有六个参数。正如我们在之前的部分看到的,这个函数从图形/控制台初始化开始。我们要再次做这件事,因为我们不知道我们是不是从[实模式](https://en.wikipedia.org/wiki/Real_mode)开始,或者是使用了引导加载器,或者引导加载器用了32位还是64位启动协议。 -After the first initialization steps, we store pointers to the start of the free memory and to the end of it: +在最早的初始化步骤后,我们保存空闲内存的起始和末尾地址。 ```C free_mem_ptr = heap; free_mem_end_ptr = heap + BOOT_HEAP_SIZE; ``` -where the `heap` is the second parameter of the `extract_kernel` function which we got in the [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S): +在这里 `heap` 是我们在 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S) 得到的 `extract_kernel` 函数的第二个参数: ```assembly leaq boot_heap(%rip), %rsi ``` -As you saw above, the `boot_heap` is defined as: +如上所述,`boot_heap`定义为: ```assembly boot_heap: .fill BOOT_HEAP_SIZE, 1, 0 ``` -where the `BOOT_HEAP_SIZE` is macro which expands to `0x10000` (`0x400000` in a case of `bzip2` kernel) and represents the size of the heap. +在这里`BOOT_HEAP_SIZE`是一个展开为`0x10000`(对`bzip2`内核是`0x400000`)的宏,代表堆的大小。 -After heap pointers initialization, the next step is the call of the `choose_random_location` function from [arch/x86/boot/compressed/kaslr.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/kaslr.c#L425) source code file. As we can guess from the function name, it chooses the memory location where the kernel image will be decompressed. It may look weird that we need to find or even `choose` location where to decompress the compressed kernel image, but the Linux kernel supports [kASLR](https://en.wikipedia.org/wiki/Address_space_layout_randomization) which allows decompression of the kernel into a random address, for security reasons. +在堆指针初始化后,下一步是从 [arch/x86/boot/compressed/kaslr.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/kaslr.c#L425) 调用`choose_random_location`函数。我们可以从函数名猜到,它选择内核镜像解压到的内存地址。看起来很奇怪,我们要寻找甚至是`选择`内核解压的地址,但是Linux内核支持[kASLR](https://en.wikipedia.org/wiki/Address_space_layout_randomization),为了安全,它允许解压内核到随机的地址。 -We will not consider randomization of the Linux kernel load address in this part, but will do it in the next part. +在这一部分,我们不会考虑Linux内核的加载地址的随机化,我们会在下一部分讨论。 -Now let's back to [misc.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/misc.c#L404). After getting the address for the kernel image, there need to be some checks to be sure that the retrieved random address is correctly aligned and address is not wrong: +现在我们回头看 [misc.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/misc.c#L404). 在获得内核镜像的地址后,需要有一些检查以确保获得的随机地址是正确对齐的,并且地址没有错误: ```C if ((unsigned long)output & (MIN_KERNEL_ALIGN - 1)) @@ -251,19 +251,19 @@ if (virt_addr != LOAD_PHYSICAL_ADDR) error("Destination virtual address changed when not relocatable"); ``` -After all these checks we will see the familiar message: +在所有这些检查后,我们可以看到熟悉的消息: ``` Decompressing Linux... ``` -and call the `__decompress` function: +然后调用解压内核的`__decompress`函数: ```C __decompress(input_data, input_len, NULL, NULL, output, output_len, NULL, error); ``` -which will decompress the kernel. The implementation of the `__decompress` function depends on what decompression algorithm was chosen during kernel compilation: +`__decompress`函数的实现取决于在内核编译期间选择什么压缩算法: ```C #ifdef CONFIG_KERNEL_GZIP @@ -291,7 +291,7 @@ which will decompress the kernel. The implementation of the `__decompress` funct #endif ``` -After kernel is decompressed, the last two functions are `parse_elf` and `handle_relocations`. The main point of these functions is to move the uncompressed kernel image to the correct memory place. The fact is that the decompression will decompress [in-place](https://en.wikipedia.org/wiki/In-place_algorithm), and we still need to move kernel to the correct address. As we already know, the kernel image is an [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) executable, so the main goal of the `parse_elf` function is to move loadable segments to the correct address. We can see loadable segments in the output of the `readelf` program: +在内核解压之后,最后两个函数是`parse_elf`和`handle_relocations`.这些函数的主要用途是把解压后的内核移动到正确的位置。事实上,解压过程会[原地](https://en.wikipedia.org/wiki/In-place_algorithm)解压,我们还是要把内核移动到正确的地址。我们已经知道,内核镜像是一个[ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format)可执行文件,所以`parse_elf`的主要目标是移动可加载的段到正确的地址。我们可以在`readelf`的输出看到可加载的段: ``` readelf -l vmlinux @@ -313,7 +313,7 @@ Program Headers: 0x0000000000138000 0x000000000029b000 RWE 200000 ``` -The goal of the `parse_elf` function is to load these segments to the `output` address we got from the `choose_random_location` function. This function starts with checking the [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) signature: +`parse_elf`函数的目标是加载这些段到从`choose_random_location`函数得到的`output`地址。这个函数从检查ELF签名标志开始: ```C Elf64_Ehdr ehdr; @@ -330,7 +330,7 @@ if (ehdr.e_ident[EI_MAG0] != ELFMAG0 || } ``` -and if it's not valid, it prints an error message and halts. If we got a valid `ELF` file, we go through all program headers from the given `ELF` file and copy all loadable segments with correct address to the output buffer: +如果是无效的,它会打印一条错误消息并停机。如果我们得到一个有效的`ELF`文件,我们从给定的`ELF`文件遍历所有程序头,并用正确的地址复制所有可加载的段到输出缓冲区: ```C for (i = 0; i < ehdr.e_phnum; i++) { @@ -352,41 +352,41 @@ and if it's not valid, it prints an error message and halts. If we got a valid ` } ``` -That's all. +这就是全部的工作。 -From this moment, all loadable segments are in the correct place. +从现在开始,所有可加载的段都在正确的位置。 -The next step after the `parse_elf` function is the call of the `handle_relocations` function. Implementation of this function depends on the `CONFIG_X86_NEED_RELOCS` kernel configuration option and if it is enabled, this function adjusts addresses in the kernel image, and is called only if the `CONFIG_RANDOMIZE_BASE` configuration option was enabled during kernel configuration. Implementation of the `handle_relocations` function is easy enough. This function subtracts value of the `LOAD_PHYSICAL_ADDR` from the value of the base load address of the kernel and thus we obtain the difference between where the kernel was linked to load and where it was actually loaded. After this we can perform kernel relocation as we know actual address where the kernel was loaded, its address where it was linked to run and relocation table which is in the end of the kernel image. +在`parse_elf`函数之后是调用`handle_relocations`函数。这个函数的实现依赖于`CONFIG_X86_NEED_RELOCS`内核配置选项,如果它被启用,这个函数调整内核镜像的地址,只有在内核配置时启用了`CONFIG_RANDOMIZE_BASE`配置选项才会调用。`handle_relocations`函数的实现足够简单。这个函数从基准内核加载地址的值减掉`LOAD_PHYSICAL_ADDR`的值,从而我们获得内核链接后要加载的地址和实际加载地址的差值。在这之后我们可以进行内核重定位,因为我们知道内核加载的实际地址、它被链接的运行的地址和内核镜像末尾的重定位表。 -After the kernel is relocated, we return back from the `extract_kernel` to [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S). +在内核重定位后,我们从`extract_kernel`回来,到 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S). -The address of the kernel will be in the `rax` register and we jump to it: +内核的地址在`rax`寄存器,我们跳到那里: ```assembly jmp *%rax ``` -That's all. Now we are in the kernel! +就是这样。现在我们就在内核里! -Conclusion +结论 -------------------------------------------------------------------------------- -This is the end of the fifth part about linux kernel booting process. We will not see posts about kernel booting anymore (maybe updates to this and previous posts), but there will be many posts about other kernel internals. +这是关于内核引导过程的第五部分的结尾。我们不会再看到关于内核引导的文章(可能有这篇和前面的文章的更新),但是会有关于其他内核内部细节的很多文章。 -Next chapter will describe more advanced details about linux kernel booting process, like a load address randomization and etc. +下一章会描述更高级的关于内核引导过程的细节,如加载地址随机化等等。 -If you have any questions or suggestions write me a comment or ping me in [twitter](https://twitter.com/0xAX). +如果你有什么问题或建议,写个评论或在 [twitter](https://twitter.com/0xAX) 找我。 -**Please note that English is not my first language, And I am really sorry for any inconvenience. If you find any mistakes please send me PR to [linux-insides](https://github.com/0xAX/linux-internals).** +**如果你发现文中描述有任何问题,请提交一个 PR 到 [linux-insides-zh](https://github.com/MintCN/linux-insides-zh) 。** -Links +链接 -------------------------------------------------------------------------------- * [address space layout randomization](https://en.wikipedia.org/wiki/Address_space_layout_randomization) -* [initrd](http://en.wikipedia.org/wiki/Initrd) -* [long mode](http://en.wikipedia.org/wiki/Long_mode) +* [initrd](https://en.wikipedia.org/wiki/Initrd) +* [long mode](https://en.wikipedia.org/wiki/Long_mode) * [bzip2](http://www.bzip.org/) -* [RDdRand instruction](http://en.wikipedia.org/wiki/RdRand) -* [Time Stamp Counter](http://en.wikipedia.org/wiki/Time_Stamp_Counter) -* [Programmable Interval Timers](http://en.wikipedia.org/wiki/Intel_8253) +* [RDRand instruction](https://en.wikipedia.org/wiki/RdRand) +* [Time Stamp Counter](https://en.wikipedia.org/wiki/Time_Stamp_Counter) +* [Programmable Interval Timers](https://en.wikipedia.org/wiki/Intel_8253) * [Previous part](https://github.com/0xAX/linux-insides/blob/master/Booting/linux-bootstrap-4.md) From 433735ead23423d56f740154f0075e3a486c541e Mon Sep 17 00:00:00 2001 From: Iru Cai Date: Mon, 19 Mar 2018 10:56:47 +0800 Subject: [PATCH 07/14] 1.6 --- Booting/linux-bootstrap-6.md | 139 ++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 69 deletions(-) diff --git a/Booting/linux-bootstrap-6.md b/Booting/linux-bootstrap-6.md index 5168995..c2de183 100644 --- a/Booting/linux-bootstrap-6.md +++ b/Booting/linux-bootstrap-6.md @@ -1,12 +1,12 @@ -Kernel booting process. Part 6. +内核引导过程. Part 6. ================================================================================ -Introduction +简介 -------------------------------------------------------------------------------- -This is the sixth part of the `Kernel booting process` series. In the [previous part](https://github.com/0xAX/linux-insides/blob/master/Booting/linux-bootstrap-5.md) we have seen the end of the kernel boot process. But we have skipped some important advanced parts. +这是`内核引导过程`系列文章的第六部分。在[前一部分](linux-bootstrap-5.md),我们已经看到了内核引导过程的结尾,但是我们跳过了一些高级部分。 -As you may remember the entry point of the Linux kernel is the `start_kernel` function from the [main.c](https://github.com/torvalds/linux/blob/master/init/main.c) source code file started to execute at `LOAD_PHYSICAL_ADDR` address. This address depends on the `CONFIG_PHYSICAL_START` kernel configuration option which is `0x1000000` by default: +你可能还记得,Linux内核的入口点是 [main.c](https://github.com/torvalds/linux/blob/master/init/main.c) 的`start_kernel`函数,它在`LOAD_PHYSICAL_ADDR`地址开始执行。这个地址依赖于`CONFIG_PHYSICAL_START`内核配置选项,默认为`0x1000000`: ``` config PHYSICAL_START @@ -19,18 +19,18 @@ config PHYSICAL_START ... ``` -This value may be changed during kernel configuration, but also load address can be selected as a random value. For this purpose the `CONFIG_RANDOMIZE_BASE` kernel configuration option should be enabled during kernel configuration. +这个选项在内核配置时可以修改,但是加载地址可以选择为一个随机值。为此,`CONFIG_RANDOMIZE_BASE`内核配置选项在内核配置时应该启用。 -In this case a physical address at which Linux kernel image will be decompressed and loaded will be randomized. This part considers the case when this option is enabled and load address of the kernel image will be randomized for [security reasons](https://en.wikipedia.org/wiki/Address_space_layout_randomization). +在这种情况下,Linux内核镜像解压和加载的物理地址会被随机化。我们在这一部分考虑这个选项被启用,并且为了[安全原因](https://en.wikipedia.org/wiki/Address_space_layout_randomization),内核镜像的加载地址被随机化的情况。 -Initialization of page tables +页表的初始化 -------------------------------------------------------------------------------- -Before the kernel decompressor will start to find random memory range where the kernel will be decompressed and loaded, the identity mapped page tables should be initialized. If a [bootloader](https://en.wikipedia.org/wiki/Booting) used [16-bit or 32-bit boot protocol](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt), we already have page tables. But in any case, we may need new pages by demand if the kernel decompressor selects memory range outside of them. That's why we need to build new identity mapped page tables. +在内核解压器要开始找随机的内核解压和加载地址之前,应该初始化恒等映射(identity mapped,虚拟地址和物理地址相同)页表。如果[引导加载器](https://en.wikipedia.org/wiki/Booting)使用[16位或32位引导协议](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt),那么我们已经有了页表。但在任何情况下,如果内核解压器选择它们之外的内存区域,我们需要新的页。这就是为什么我们需要建立新的恒等映射页表。 -Yes, building of identity mapped page tables is the one of the first step during randomization of load address. But before we will consider it, let's try to remember where did we come from to this point. +是的,建立恒等映射页表是随机化加载地址的最早的步骤之一。但是在此之前,让我们回忆一下我们是怎么来到这里的。 -In the [previous part](https://github.com/0xAX/linux-insides/blob/master/Booting/linux-bootstrap-5.md), we saw transition to [long mode](https://en.wikipedia.org/wiki/Long_mode) and jump to the kernel decompressor entry point - `extract_kernel` function. The randomization stuff starts here from the call of the: +在[前一部分](linux-bootstrap-5.md),我们看到了到[长模式](https://en.wikipedia.org/wiki/Long_mode)的转换,并跳转到了内核解压器的入口点——`extract_kernel`函数。随机化从调用这个函数开始: ```C void choose_random_location(unsigned long input, @@ -41,7 +41,7 @@ void choose_random_location(unsigned long input, {} ``` -function. As you may see, this function takes following five parameters: +你可以看到,这个函数有五个参数: * `input`; * `input_size`; @@ -49,7 +49,7 @@ function. As you may see, this function takes following five parameters: * `output_isze`; * `virt_addr`. -Let's try to understand what these parameters are. The first `input` parameter came from parameters of the `extract_kernel` function from the [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/misc.c) source code file: +让我们试着理解一下这些参数是什么。第一个`input`参数来自源文件 [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/misc.c) 里的`extract_kernel`函数: ```C asmlinkage __visible void *extract_kernel(void *rmode, memptr heap, @@ -71,13 +71,13 @@ asmlinkage __visible void *extract_kernel(void *rmode, memptr heap, } ``` -This parameter is passed from assembler code: +这个参数由 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 的汇编代码传递: ```C leaq input_data(%rip), %rdx ``` -from the [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S). The `input_data` is generated by the little [mkpiggy](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/mkpiggy.c) program. If you have compiled linux kernel source code under your hands, you may find the generated file by this program which should be placed in the `linux/arch/x86/boot/compressed/piggy.S`. In my case this file looks: +`input_data`由 [mkpiggy](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/mkpiggy.c) 程序生成。如果你亲手编译过Linux内核源码,你会找到这个程序生成的文件,它应该位于 `linux/arch/x86/boot/compressed/piggy.S`. 在我这里,这个文件是这样的: ```assembly .section ".rodata..compressed","a",@progbits @@ -91,21 +91,21 @@ input_data: input_data_end: ``` -As you may see it contains four global symbols. The first two `z_input_len` and `z_output_len` which are sizes of compressed and uncompressed `vmlinux.bin.gz`. The third is our `input_data` and as you may see it points to linux kernel image in raw binary format (all debugging symbols, comments and relocation information are stripped). And the last `input_data_end` points to the end of the compressed linux image. +你能看到它有四个全局符号。前两个`z_input_len`和`z_output_len`是压缩的和解压后的`vmlinux.bin.gz`的大小。第三个是我们的`input_data`,你可以看到,它指向二进制格式(去掉所有调试符号、注释和重定位信息)的Linux内核镜像。最后的`input_data_end`指向压缩的Linux镜像的末尾。 -So, our first parameter of the `choose_random_location` function is the pointer to the compressed kernel image that is embedded into the `piggy.o` object file. +所以我们`choose_random_location`函数的第一个参数是指向嵌入在`piggy.o`目标文件的压缩的内核镜像的指针。 -The second parameter of the `choose_random_location` function is the `z_input_len` that we have seen just now. +`choose_random_location`函数的第二个参数是我们刚刚看到的`z_input_len`. -The third and fourth parameters of the `choose_random_location` function are address where to place decompressed kernel image and the length of decompressed kernel image respectively. The address where to put decompressed kernel came from [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) and it is address of the `startup_32` aligned to 2 megabytes boundary. The size of the decompressed kernel came from the same `piggy.S` and it is `z_output_len`. +`choose_random_location`函数的第三和第四个参数分别是解压后的内核镜像的位置和长度。放置解压后内核的地址来自 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S),并且它是`startup_32`对齐到 2MB 边界的地址。解压后的内核的大小来自同样的`piggy.S`,并且它是`z_output_len`. -The last parameter of the `choose_random_location` function is the virtual address of the kernel load address. As we may see, by default it coincides with the default physical load address: +`choose_random_location`函数的最后一个参数是内核加载地址的虚拟地址。我们可以看到,它和默认的物理加载地址相同: ```C unsigned long virt_addr = LOAD_PHYSICAL_ADDR; ``` -which depends on kernel configuration: +它依赖于内核配置: ```C #define LOAD_PHYSICAL_ADDR ((CONFIG_PHYSICAL_START \ @@ -113,7 +113,7 @@ which depends on kernel configuration: & ~(CONFIG_PHYSICAL_ALIGN - 1)) ``` -Now, as we considered parameters of the `choose_random_location` function, let's look at implementation of it. This function starts from the checking of `nokaslr` option in the kernel command line: +现在,由于我们考虑`choose_random_location`函数的参数,让我们看看它的实现。这个函数从检查内核命令行的`nokaslr`选项开始: ```C if (cmdline_find_option_bool("nokaslr")) { @@ -122,7 +122,7 @@ if (cmdline_find_option_bool("nokaslr")) { } ``` -and if the options was given we exit from the `choose_random_location` function ad kernel load address will not be randomized. Related command line options can be found in the [kernel documentation](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/Documentation/kernel-parameters.txt): +如果有这个选项,那么我们就退出`choose_random_location`函数,并且内核的加载地址不会随机化。相关的命令行选项可以在[内核文档](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/Documentation/kernel-parameters.txt)找到: ``` kaslr/nokaslr [X86] @@ -134,15 +134,15 @@ kASLR is disabled by default. When kASLR is enabled, hibernation will be disabled. ``` -Let's assume that we didn't pass `nokaslr` to the kernel command line and the `CONFIG_RANDOMIZE_BASE` kernel configuration option is enabled. +假设我们没有把`nokaslr`传到内核命令行,并且`CONFIG_RANDOMIZE_BASE`启用了内核配置选项。 -The next step is the call of the: +下一步是以下函数的调用: ```C initialize_identity_maps(); ``` -function which is defined in the [arch/x86/boot/compressed/pagetable.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/pagetable.c) source code file. This function starts from initialization of `mapping_info` an instance of the `x86_mapping_info` structure: +它在 [arch/x86/boot/compressed/pagetable.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/pagetable.c) 源码文件定义。这个函数从初始化`mapping_info`,`x86_mapping_info`结构体的一个实例开始。 ```C mapping_info.alloc_pgt_page = alloc_pgt_page; @@ -151,7 +151,7 @@ mapping_info.page_flag = __PAGE_KERNEL_LARGE_EXEC | sev_me_mask; mapping_info.kernpg_flag = _KERNPG_TABLE | sev_me_mask; ``` -The `x86_mapping_info` structure is defined in the [arch/x86/include/asm/init.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/init.h) header file and looks: +`x86_mapping_info`结构体在 [arch/x86/include/asm/init.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/init.h) 头文件定义: ```C struct x86_mapping_info { @@ -164,18 +164,19 @@ struct x86_mapping_info { }; ``` -This structure provides information about memory mappings. As you may remember from the previous part, we already setup'ed initial page tables from 0 up to `4G`. For now we may need to access memory above `4G` to load kernel at random position. So, the `initialize_identity_maps` function executes initialization of a memory region for a possible needed new page table. First of all let's try to look at the definition of the `x86_mapping_info` structure. +这个结构体提供了关于内存映射的信息。你可能还记得,在前面的部分,我们已经建立了初始的从0到`4G`的页表。现在我们可能需要访问`4G`以上的内存来在随机的位置加载内核。所以,`initialize_identity_maps`函数初始化一个内存区域,它用于可能需要的新页表。首先,让我们尝试查看`x86_mapping_info`结构体的定义。 -The `alloc_pgt_page` is a callback function that will be called to allocate space for a page table entry. The `context` field is an instance of the `alloc_pgt_data` structure in our case which will be used to track allocated page tables. The `page_flag` and `kernpg_flag` fields are page flags. The first represents flags for `PMD` or `PUD` entries. The second `kernpg_flag` field represents flags for kernel pages which can be overridden later. The `direct_gbpages` field represents support for huge pages and the last `offset` field represents offset between kernel virtual addresses and physical addresses up to `PMD` level. +`alloc_pgt_page`是一个会在为一个页表项分配空间时调用的回调函数。`context`域是一个用于跟踪已分配页表的`alloc_pgt_data`结构体的实例。`page_flag`和`kernpg_flag`是页标志。第一个代表`PMD`或`PUD`表项的标志。第二个`kernpg_flag`域代表会在之后被覆盖的内核页的标志。`direct_gbpages`域代表对大页的支持。最后的`offset`域代表内核虚拟地址到`PMD`级物理地址的偏移。 + +`alloc_pgt_page`回调函数检查有一个新页的空间,从缓冲区分配新页并返回新页的地址: -The `alloc_pgt_page` callback just validates that there is space for a new page, allocates new page: ```C entry = pages->pgt_buf + pages->pgt_buf_offset; pages->pgt_buf_offset += PAGE_SIZE; ``` -in the buffer from the: +缓冲区在此结构体中: ```C struct alloc_pgt_data { @@ -185,36 +186,36 @@ struct alloc_pgt_data { }; ``` -structure and returns address of a new page. The last goal of the `initialize_identity_maps` function is to initialize `pgdt_buf_size` and `pgt_buf_offset`. As we are only in initialization phase, the `initialze_identity_maps` function sets `pgt_buf_offset` to zero: +`initialize_identity_maps`函数最后的目标是初始化`pgdt_buf_size`和`pgt_buf_offset`. 由于我们只是在初始化阶段,`initialize_identity_maps`函数设置`pgt_buf_offset`为0: ```C pgt_data.pgt_buf_offset = 0; ``` -and the `pgt_data.pgt_buf_size` will be set to `77824` or `69632` depends on which boot protocol will be used by bootloader (64-bit or 32-bit). The same is for `pgt_data.pgt_buf`. If a bootloader loaded the kernel at `startup_32`, the `pgdt_data.pgdt_buf` will point to the end of the page table which already was initialzed in the [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S): +而`pgt_data.pgt_buf_size`会根据引导加载器所用的引导协议(64位或32位)被设置为`77824`或`69632`. `pgt_data.pgt_buf`也是一样。如果引导加载器在`startup_32`引导内核,`pgdt_data.pgdt_buf`会指向已经在 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 初始化的页表的末尾: ```C pgt_data.pgt_buf = _pgtable + BOOT_INIT_PGT_SIZE; ``` -where `_pgtable` points to the beginning of this page table [_pgtable](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/vmlinux.lds.S). In other way, if a bootloader have used 64-bit boot protocol and loaded the kernel at `startup_64`, early page tables should be built by bootloader itself and `_pgtable` will be just overwrote: +其中`_pgtable`指向这个页表 [_pgtable](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/vmlinux.lds.S) 的开头。另一方面,如果引导加载器用64位引导协议并在`startup_64`加载内核,早期页表应该由引导加载器建立,并且`_pgtable`会被重写: ```C pgt_data.pgt_buf = _pgtable ``` -As the buffer for new page tables is initialized, we may return back to the `choose_random_location` function. +在新页表的缓冲区被初始化之下,我们回到`choose_random_location`函数。 -Avoid reserved memory ranges +避开保留的内存范围 -------------------------------------------------------------------------------- -After the stuff related to identity page tables is initilized, we may start to choose random location where to put decompressed kernel image. But as you may guess, we can't choose any address. There are some reseved addresses in memory ranges. Such addresses occupied by important things, like [initrd](https://en.wikipedia.org/wiki/Initial_ramdisk), kernel command line and etc. The +在恒等映射页表相关的数据被初始化之后,我们可以开始选择放置解压后内核的随机位置。但是正如你猜的那样,我们不能选择任意地址。在内存的范围中,有一些保留的地址。这些地址被重要的东西占用,如[initrd](https://en.wikipedia.org/wiki/Initial_ramdisk), 内核命令行等等。这个函数: ```C mem_avoid_init(input, input_size, *output); ``` -function will help us to do this. All non-safe memory regions will be collected in the: +会帮我们做这件事。所有不安全的内存区域会收集到: ```C struct mem_vector { @@ -225,7 +226,7 @@ struct mem_vector { static struct mem_vector mem_avoid[MEM_AVOID_MAX]; ``` -array. Where `MEM_AVOID_MAX` is from `mem_avoid_index` [enum](https://en.wikipedia.org/wiki/Enumerated_type#C) which represents different types of reserved memory regions: +数组。其中`MEM_AVOID_MAX`来自[枚举类型](https://en.wikipedia.org/wiki/Enumerated_type#C)`mem_avoid_index`, 它代表不同类型的保留内存区域: ```C enum mem_avoid_index { @@ -239,9 +240,9 @@ enum mem_avoid_index { }; ``` -Both are defined in the [arch/x86/boot/compressed/kaslr.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/kaslr.c) source code file. +它们都定义在源文件 [arch/x86/boot/compressed/kaslr.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/kaslr.c) 中。 -Let's look at the implementation of the `mem_avoid_init` function. The main goal of this function is to store information about reseved memory regions described by the `mem_avoid_index` enum in the `mem_avoid` array and create new pages for such regions in our new identity mapped buffer. Numerous parts fo the `mem_avoid_index` function are similar, but let's take a look at the one of them: +让我们看看`mem_avoid_init`函数的实现。这个函数的主要目标是在`mem_avoid`数组存放关于被`mem_avoid_index`枚举类型描述的保留内存区域的信息,并且在我们新的恒等映射缓冲区为这样的区域创建新页。`mem_avoid_index`函数的几个部分很相似,但是先看看其中一个: ```C mem_avoid[MEM_AVOID_ZO_RANGE].start = input; @@ -250,7 +251,7 @@ add_identity_map(mem_avoid[MEM_AVOID_ZO_RANGE].start, mem_avoid[MEM_AVOID_ZO_RANGE].size); ``` -At the beginning of the `mem_avoid_init` function tries to avoid memory region that is used for current kernel decompression. We fill an entry from the `mem_avoid` array with the start and size of such region and call the `add_identity_map` function which should build identity mapped pages for this region. The `add_identity_map` function is defined in the [arch/x86/boot/compressed/kaslr.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/kaslr.c) source code file and looks: +`mem_avoid_init`函数的开头尝试避免用于当前内核解压的内存区域。我们用这个区域的起始地址和大小填写`mem_avoid`数组的一项,并调用`add_identity_map`函数,它会为这个区域建立恒等映射页。`add_identity_map`函数在源文件 [arch/x86/boot/compressed/kaslr.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/kaslr.c) 定义: ```C void add_identity_map(unsigned long start, unsigned long size) @@ -267,18 +268,18 @@ void add_identity_map(unsigned long start, unsigned long size) } ``` -As you may see it aligns memory region to 2 megabytes boundary and checks given start and end addresses. +你可以看到,它对齐内存到 2MB 边界并检查给定的起始地址和终止地址。 -In the end it just calls the `kernel_ident_mapping_init` function from the [arch/x86/mm/ident_map.c](https://github.com/torvalds/linux/blob/master/arch/x86/mm/ident_map.c) source code file and pass `mapping_info` instance that was initilized above, address of the top level page table and addresses of memory region for which new identity mapping should be built. +最后它调用`kernel_ident_mapping_init`函数,它在源文件 [arch/x86/mm/ident_map.c](https://github.com/torvalds/linux/blob/master/arch/x86/mm/ident_map.c) 中,并传入以上初始化好的`mapping_info`实例、顶层页表的地址和建立新的恒等映射的内存区域的地址。 -The `kernel_ident_mapping_init` function sets default flags for new pages if they were not given: +`kernel_ident_mapping_init`函数为新页设置默认的标志,如果它们没有被给出: ```C if (!info->kernpg_flag) info->kernpg_flag = _KERNPG_TABLE; ``` -and starts to build new 2-megabytes (because of `PSE` bit in the `mapping_info.page_flag`) page entries (`PGD -> P4D -> PUD -> PMD` in a case of [five-level page tables](https://lwn.net/Articles/717293/) or `PGD -> PUD -> PMD` in a case of [four-level page tables](https://lwn.net/Articles/117749/)) related to the given addresses. +并且开始建立新的2MB (因为`mapping_info.page_flag`中的`PSE`位) 给定地址相关的页表项([五级页表](https://lwn.net/Articles/717293/)中的`PGD -> P4D -> PUD -> PMD`或者[四级页表](https://lwn.net/Articles/117749/)中的`PGD -> PUD -> PMD`)。 ```C for (; addr < end; addr = next) { @@ -295,32 +296,32 @@ for (; addr < end; addr = next) { } ``` -First of all here we find next entry of the `Page Global Directory` for the given address and if it is greater than `end` of the given memory region, we set it to `end`. After this we allocater a new page with our `x86_mapping_info` callback that we already considered above and call the `ident_p4d_init` function. The `ident_p4d_init` function will do the same, but for low-level page directories (`p4d` -> `pud` -> `pmd`). +首先我们找给定地址在 `页全局目录` 的下一项,如果它大于给定的内存区域的末地址`end`,我们把它设为`end`.之后,我们用之前看过的`x86_mapping_info`回调函数分配一个新页,然后调用`ident_p4d_init`函数。`ident_p4d_init`函数做同样的事情,但是用于低层的页目录 (`p4d` -> `pud` -> `pmd`). -That's all. +就是这样。 -New page entries related to reserved addresses are in our page tables. This is not the end of the `mem_avoid_init` function, but other parts are similar. It just build pages for [initrd](https://en.wikipedia.org/wiki/Initial_ramdisk), kernel command line and etc. +和保留地址相关的新页表项已经在我们的页表中。这不是`mem_avoid_init`函数的末尾,但是其他部分类似。它建立用于 [initrd](https://en.wikipedia.org/wiki/Initial_ramdisk)、内核命令行等数据的页。 -Now we may return back to `choose_random_location` function. +现在我们可以回到`choose_random_location`函数。 -Physical address randomization +物理地址随机化 -------------------------------------------------------------------------------- -After the reserved memory regions were stored in the `mem_avoid` array and identity mapping pages were built for them, we select minimal available address to choose random memory region to decompress the kernel: +在保留内存区域存储在`mem_avoid`数组并且为它们建立了恒等映射页之后,我们选择最小可用的地址作为解压内核的随机内存区域: ```C min_addr = min(*output, 512UL << 20); ``` -As you may see it should be smaller than `512` megabytes. This `512` megabytes value was selected just to avoid unknown things in lower memory. +你可以看到,它应该小于512MB. 选择这个512MB的值只是避免低内存区域中未知的东西。 -The next step is to select random physical and virtual addresses to load kernel. The first is physical addresses: +下一步是选择随机的物理和虚拟地址来加载内核。首先是物理地址: ```C random_addr = find_random_phys_addr(min_addr, output_size); ``` -The `find_random_phys_addr` function is defined in the [same](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/kaslr.c) source code file: +`find_random_phys_addr`函数在[同一个](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/kaslr.c)源文件中定义: ``` static unsigned long find_random_phys_addr(unsigned long minimum, @@ -336,7 +337,7 @@ static unsigned long find_random_phys_addr(unsigned long minimum, } ``` -The main goal of `process_efi_entries` function is to find all suitable memory ranges in full accessible memory to load kernel. If the kernel compiled and runned on the system without [EFI](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface) support, we continue to search such memory regions in the [e820](https://en.wikipedia.org/wiki/E820) regions. All founded memory regions will be stored in the +`process_efi_entries`函数的主要目标是在整个可用的内存找到所有的合适的内存区域来加载内核。如果内核没有在支持[EFI](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface)的系统中编译和运行,我们继续在[e820](https://en.wikipedia.org/wiki/E820)区域中找这样的内存区域。所有找到的内存区域会存储在 ```C struct slot_area { @@ -349,20 +350,20 @@ struct slot_area { static struct slot_area slot_areas[MAX_SLOT_AREA]; ``` -array. The kernel decompressor should select random index of this array and it will be random place where kernel will be decompressed. This selection will be executed by the `slots_fetch_random` function. The main goal of the `slots_fetch_random` function is to select random memory range from the `slot_areas` array via `kaslr_get_random_long` function: +数组中。内核解压器应该选择这个数组随机的索引,并且它会是内核解压的随机位置。这个选择会被`slots_fetch_random`函数执行。`slots_fetch_random`函数的主要目标是通过`kaslr_get_random_long`函数从`slot_areas`数组选择随机的内存范围: ```C slot = kaslr_get_random_long("Physical") % slot_max; ``` -The `kaslr_get_random_long` function is defined in the [arch/x86/lib/kaslr.c](https://github.com/torvalds/linux/blob/master/arch/x86/lib/kaslr.c) source code file and it just returns random number. Note that the random number will be get via different ways depends on kernel configuration and system opportunities (select random number base on [time stamp counter](https://en.wikipedia.org/wiki/Time_Stamp_Counter), [rdrand](https://en.wikipedia.org/wiki/RdRand) and so on). +`kaslr_get_random_long`函数在源文件 [arch/x86/lib/kaslr.c](https://github.com/torvalds/linux/blob/master/arch/x86/lib/kaslr.c) 中定义,它返回一个随机数。注意这个随机数会通过不同的方式得到,取决于内核配置、系统机会(基于[时间戳计数器](https://en.wikipedia.org/wiki/Time_Stamp_Counter)的随机数、[rdrand](https://en.wikipedia.org/wiki/RdRand)等等)。 -That's all from this point random memory range will be selected. +这就是随机内存范围的选择方法。 -Virtual address randomization +虚拟地址随机化 -------------------------------------------------------------------------------- -After random memory region was selected by the kernel decompressor, new identity mapped pages will be built for this region by demand: +在内核解压器选择了随机内存区域后,新的恒等映射页会为这个区域按需建立: ```C random_addr = find_random_phys_addr(min_addr, output_size); @@ -373,7 +374,7 @@ if (*output != random_addr) { } ``` -From this time `output` will store the base address of a memory region where kernel will be decompressed. But for this moment, as you may remember we randomized only physical address. Virtual address should be randomized too in a case of [x86_64](https://en.wikipedia.org/wiki/X86-64) architecture: +这时,`output`会存放内核将会解压的一个内存区域的基地址。但是现在,正如你还记得的那样,我们只是随机化了物理地址。在[x86_64](https://en.wikipedia.org/wiki/X86-64)架构,虚拟地址也应该被随机化: ```C if (IS_ENABLED(CONFIG_X86_64)) @@ -382,22 +383,22 @@ if (IS_ENABLED(CONFIG_X86_64)) *virt_addr = random_addr; ``` -As you may see in a case of non `x86_64` architecture, randomzed virtual address will coincide with randomized physical address. The `find_random_virt_addr` function calculates amount of virtual memory ranges that may hold kernel image and calls the `kaslr_get_random_long` that we already saw in a previous case when we tried to find random `physical` address. +正如你所看到的,对于非`x86_64`架构,随机化的虚拟地址和随机化的物理地址相同。`find_random_virt_addr`函数计算可以保存内存镜像的虚拟内存范围的数量并且调用我们在尝试找到随机的`物理`地址的时候,之前已经看到的`kaslr_get_random_long`函数。 -From this moment we have both randomized base physical (`*output`) and virtual (`*virt_addr`) addresses for decompressed kernel. +这时,我们同时有了用于解压内核的随机化的物理(`*output`)和虚拟(`*virt_addr`)基地址。 -That's all. +就是这样。 -Conclusion +结论 -------------------------------------------------------------------------------- -This is the end of the sixth and the last part about linux kernel booting process. We will not see posts about kernel booting anymore (maybe updates to this and previous posts), but there will be many posts about other kernel internals. +这是关于Linux内核引导过程的第六,并且是最后一部分的结尾。我们不再会看到关于内核引导的帖子(可能有对这篇和之前文章的更新),但是会有很多关于其他内核内部细节的文章。 -Next chapter will be about kernel initialization and we will see the first steps in the Linux kernel initialization code. +下一章是关于内核初始化的,我们会看到Linux内核初始化代码的早期步骤。 -If you have any questions or suggestions write me a comment or ping me in [twitter](https://twitter.com/0xAX). +如果你有什么问题或建议,写个评论或在 [twitter](https://twitter.com/0xAX) 找我。 -**Please note that English is not my first language, And I am really sorry for any inconvenience. If you find any mistakes please send me PR to [linux-insides](https://github.com/0xAX/linux-internals).** +**如果你发现文中描述有任何问题,请提交一个 PR 到 [linux-insides-zh](https://github.com/MintCN/linux-insides-zh) 。** Links -------------------------------------------------------------------------------- From 1417246b2b8d0ee1bc392c9da75a8afa832acd8f Mon Sep 17 00:00:00 2001 From: Iru Cai Date: Mon, 19 Mar 2018 10:00:34 +0800 Subject: [PATCH 08/14] README.md: finish translating --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 74de413..032ef08 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ |├ [1.2](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-2.md)|[@hailincai](https://github.com/hailincai)|已完成| |├ [1.3](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md)|[@hailincai](https://github.com/hailincai)|已完成| |├ [1.4](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-4.md)|[@zmj1316](https://github.com/zmj1316)|已完成| -|├ [1.5](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-5.md)||正在进行| -|└ [1.6](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-6.md)||正在进行| +|├ [1.5](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-5.md)|[@mytbk](https://github.com/mytbk)|更新至[31998d14](https://github.com/0xAX/linux-insides/commit/31998d14320f25399d67d4fff446a65178931e90)| +|└ [1.6](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-6.md)|[@mytbk](https://github.com/mytbk)|更新至[31998d14](https://github.com/0xAX/linux-insides/commit/31998d14320f25399d67d4fff446a65178931e90)| | 2. [Initialization](https://github.com/MintCN/linux-insides-zh/tree/master/Initialization)||正在进行| |├ [2.0](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/README.md)|[@mudongliang](https://github.com/mudongliang)|更新至[44017507](https://github.com/0xAX/linux-insides/commit/4401750766f7150dcd16f579026f5554541a6ab9)| |├ [2.1](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-1.md)|[@dontpanic92](https://github.com/dontpanic92)|更新至[44017507](https://github.com/0xAX/linux-insides/commit/4401750766f7150dcd16f579026f5554541a6ab9)| From e2412e2c93e7a328facfe855b151cc0ed99ef0c3 Mon Sep 17 00:00:00 2001 From: kele1997 <2363618563@qq.com> Date: Tue, 27 Mar 2018 08:34:29 +0800 Subject: [PATCH 09/14] finish-%50 --- Initialization/linux-initialization-6-zh.md | 135 +++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/Initialization/linux-initialization-6-zh.md b/Initialization/linux-initialization-6-zh.md index bdebcc3..b46b7f6 100644 --- a/Initialization/linux-initialization-6-zh.md +++ b/Initialization/linux-initialization-6-zh.md @@ -409,4 +409,137 @@ struct mpf_intel { unsigned char feature4; unsigned char feature5; }; -``` \ No newline at end of file +``` + +正如我们在文档中看到的那样 - 系统 BIOS的主要功能之一就是创建MP指针结构和MP配置表。然后操作系统必须拥有访问多处理器配置信息,并且 `mpf_intel` 存储了多处理器配置表的物理地址(看结构体的第二个参数),然后,`smp_scan_config` 在指定的内存区域中循环查找 `MP floating pointer structure` 。它检查当前浮动指针是否指向 `SMP` 签名,并且检查他的校验和,在循环中检查 `mpf->specification` 是1还是4(这个值只能是1或者是4): + +```C +while (length > 0) { +if ((*bp == SMP_MAGIC_IDENT) && + (mpf->length == 1) && + !mpf_checksum((unsigned char *)bp, 16) && + ((mpf->specification == 1) + || (mpf->specification == 4))) { + + mem = virt_to_phys(mpf); + memblock_reserve(mem, sizeof(*mpf)); + if (mpf->physptr) + smp_reserve_memory(mpf); + } +} +``` + +其他的早期内存初始化程序 +-------------------------------------------------------------------------------- + +在 `setup_arch` 的下一步,我们可以看到 `early_alloc_pgt_buf` 函数的调用,这个函数为早期阶段分配页表缓冲区。页表缓冲区将被防止在 `brk` 段中。让我们一起看看这个函数的实现: + + +```C +void __init early_alloc_pgt_buf(void) +{ + unsigned long tables = INIT_PGT_BUF_SIZE; + phys_addr_t base; + + base = __pa(extend_brk(tables, PAGE_SIZE)); + + pgt_buf_start = base >> PAGE_SHIFT; + pgt_buf_end = pgt_buf_start; + pgt_buf_top = pgt_buf_start + (tables >> PAGE_SHIFT); +} +``` + +首先这个函数获得页表缓冲区的大小,它的值是 `INIT_PGT_BUF_SIZE` ,这个值在目前的linux 4.0 内核中是 `(6 * PAGE_SIZE)`。因为我们已经得到了页表缓冲区的大小,我们调用 `extend_brk` 函数并且传入两个参数: size和align。你可以从他们的名称中了解到,这个函数扩展 `brk` 段。正如我们在linux 内核链接脚本中看到的,`brk`在内存中的位置恰好在 [BSS](http://en.wikipedia.org/wiki/.bss) 后面: + +```C + . = ALIGN(PAGE_SIZE); + .brk : AT(ADDR(.brk) - LOAD_OFFSET) { + __brk_base = .; + . += 64 * 1024; /* 64k alignment slop space */ + *(.brk_reservation) /* areas brk users have reserved */ + __brk_limit = .; + } +``` + +或者我们可以使用 `readelf` 工具来找到它: + +![brk area](http://oi61.tinypic.com/71lkeu.jpg) + +之后我们用 `_pa` 宏得到了新的 `brk`段的物理地址,我们计算页表缓冲区的基地址和结束地址。下一步因为我们之前创建了页面缓冲区,我们使用 `reserve_brk` 函数为 brk 段保留内存块: + +```C +static void __init reserve_brk(void) +{ + if (_brk_end > _brk_start) + memblock_reserve(__pa_symbol(_brk_start), + _brk_end - _brk_start); + + _brk_start = 0; +} +``` +注意在 `reserve_brk` 的最后,我们把 `_brk_start` 设置为0,因为在这之后我们不会再为它分配内存了,我们需要使用 `cleanup_highmap` 函数来取消内核映射中越界的内存区域。请记住内核映射是 `__START_KERNEL_map` 和 `_end - _text` 或者 `level2_kernel_pgt` 对内核 `_text`、`data` 和 `bss` 的映射。在 `clean_high_map` 的开始部分我们定义这些参数: + +```C +unsigned long vaddr = __START_KERNEL_map; +unsigned long end = roundup((unsigned long)_end, PMD_SIZE) - 1; +pmd_t *pmd = level2_kernel_pgt; +pmd_t *last_pmd = pmd + PTRS_PER_PMD; +``` + +现在,因为我们已经定义了内核映射的开始和结束部分,我们在循环中遍历所有内核页中间目录调墨油, 并清除不在 `_text` 和 `end` 段之间的条目: + +```C +for (; pmd < last_pmd; pmd++, vaddr += PMD_SIZE) { + if (pmd_none(*pmd)) + continue; + if (vaddr < (unsigned long) _text || vaddr > end) + set_pmd(pmd, __pmd(0)); +} +``` + +在这之后,我们使用 `memblock_set_current_limit` (你可以在[linux 内存管理 第二章节](https://github.com/MintCN/linux-insides-zh/blob/master/MM/linux-mm-2.md) 阅读关于 `memblock` 的更多内容) 函数来设置 `memblock` 分配的限制,它将是`ISA_END_ADDRESS` 或者 `0x100000` 并且它会调用 `memblock_x86_fill` 函数根据 `e820` 来填充 `memblock` 信息。你可以在内核初始化的时候看到这个函数运行的结果: + +``` +MEMBLOCK configuration: + memory size = 0x1fff7ec00 reserved size = 0x1e30000 + memory.cnt = 0x3 + memory[0x0] [0x00000000001000-0x0000000009efff], 0x9e000 bytes flags: 0x0 + memory[0x1] [0x00000000100000-0x000000bffdffff], 0xbfee0000 bytes flags: 0x0 + memory[0x2] [0x00000100000000-0x0000023fffffff], 0x140000000 bytes flags: 0x0 + reserved.cnt = 0x3 + reserved[0x0] [0x0000000009f000-0x000000000fffff], 0x61000 bytes flags: 0x0 + reserved[0x1] [0x00000001000000-0x00000001a57fff], 0xa58000 bytes flags: 0x0 + reserved[0x2] [0x0000007ec89000-0x0000007fffffff], 0x1377000 bytes flags: 0x0 +``` +在 `memblock_x86_fill` 之后的其他函数: `early_reserve_e820_mpc_new` 在 `e820map` 中为多处理器规格表分配额外的槽, `reserve_real_mode` - 保留从 `0x0` 到1M的低内存用作实模式的跳板(用于重启等...),`trim_platform_memory_ranges` 函数删除掉以 `0x20050000`, `0x20110000` 等地址开头的内存空间。这些内存区域必须被排除在外,因为 [Sandy Bridge](http://en.wikipedia.org/wiki/Sandy_Bridge) 会在这些区域出现一些故障, `trim_low_memory_range` 保留 `memblock` 中的前4KB页面,`init_mem_mapping` 函数重新创建直接内存映射并且建立在 `PAGE_OFFSET` 处物理内存的直接映射, `early_trap_pf_init` 建立了 `#PF` 处理函数(我们将会在有关中断的章节看到它),然后 `setup_real_mode` 函数建立到 [实模式]http://en.wikipedia.org/wiki/Real_mode) 代码的跳板。 + +这就是本章的全部内容了。您可能注意到这部分并没有包括 `setup_arch` 中的所有函数 (如 "early_gart_iommu_check"、[mtrr](http://en.wikipedia.org/wiki/Memory_type_range_register) 初始化等...)。正如我已经写了很多次的, `setup_arch` 很复杂 linux 内核也很复杂。这就是为什么我不能囊括 linux 内核中的每一行。我认为我们并没有错过重要的东西, 但是你可以这样说: 每行代码都很重要。是的, 这是真的, 但是不管怎样我错过了他们, 因为我认为对于整个linux内核面面俱到是不现实的。无论如何, 我们会经常回到我们已经看到的想法, 如果有什么不熟悉的, 我们将讨论这个主题。 + +结束语 +-------------------------------------------------------------------------------- + +这里是linux 内核初始化进程第六章节的结尾。在这一章节中,我们再次深入研究了 `setup_arch` 函数,然而这是个很长的部分,我们目前还没有完成它。没错, `setup_arch`很复杂,希望下个章节将会是这个函数的最后一个部分。(译者注:假的)。 + +如果你有任何的疑问或者建议,你可以留言,也可以直接发消息给我[twitter](https://twitter.com/0xAX)。 + +**很抱歉,英语并不是我的母语,非常抱歉给您阅读带来不便,如果你发现文中描述有任何问题,请提交一个 PR 到 [linux-insides](https://github.com/MintCN/linux-insides-zh).** + +链接 +-------------------------------------------------------------------------------- + +* [MultiProcessor Specification](http://en.wikipedia.org/wiki/MultiProcessor_Specification) +* [NX bit](http://en.wikipedia.org/wiki/NX_bit) +* [Documentation/kernel-parameters.txt](https://github.com/torvalds/linux/blob/master/Documentation/kernel-parameters.txt) +* [APIC](http://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller) +* [CPU masks](http://0xax.gitbooks.io/linux-insides/content/Concepts/cpumask.html) +* [Linux kernel memory management](http://xinqiu.gitbooks.io/linux-insides-cn/content/MM/index.html) +* [PCI](http://en.wikipedia.org/wiki/Conventional_PCI) +* [e820](http://en.wikipedia.org/wiki/E820) +* [System Management BIOS](http://en.wikipedia.org/wiki/System_Management_BIOS) +* [System Management BIOS](http://en.wikipedia.org/wiki/System_Management_BIOS) +* [EFI](http://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface) +* [SMP](http://en.wikipedia.org/wiki/Symmetric_multiprocessing) +* [MultiProcessor Specification](http://www.intel.com/design/pentium/datashts/24201606.pdf) +* [BSS](http://en.wikipedia.org/wiki/.bss) +* [SMBIOS specification](http://www.dmtf.org/sites/default/files/standards/documents/DSP0134v2.5Final.pdf) +* [前一个章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html) From cf9aecc3b5208b415604cb01b4a6dd1d2f385852 Mon Sep 17 00:00:00 2001 From: kele1997 <2363618563@qq.com> Date: Thu, 29 Mar 2018 23:07:50 +0800 Subject: [PATCH 10/14] complete --- Initialization/linux-initialization-6-zh.md | 106 +++++++++++--------- 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/Initialization/linux-initialization-6-zh.md b/Initialization/linux-initialization-6-zh.md index b46b7f6..b1a15a8 100644 --- a/Initialization/linux-initialization-6-zh.md +++ b/Initialization/linux-initialization-6-zh.md @@ -4,7 +4,7 @@ 仍旧是与系统架构有关的初始化 =========================================================== -在之前的[章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html)我们从 [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c)了解了特定系统架构 (在我们的例子中是 `x86_64` )的初始化内容,并且通过 `x86_configure_nx` 函数根据对[NX bit](http://en.wikipedia.org/wiki/NX_bit)的支持设置 `_PAGE_NX` 标志。正如我之前写的, `setup_arch` 函数和 `start_kernel` 都非常庞大,所以在这个和下个章节我们将继续学习关于系统架构初始化进程的内容。`x86_configure_nx` 函数的下面是 `parse_early_param`。这个函数的定义在 [init/main.c](https://github.com/torvalds/linux/blob/master/init/main.c) 并且你可以从它的名字中了解到,这个函数解析内核命令行并且基于给定的参数 (所有的内核命令行参数你都可以在 [Documentation/kernel-parameters.txt](https://github.com/torvalds/linux/blob/master/Documentation/kernel-parameters.txt) 找到)。 你可能记得在最前面的 [章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html) 我们怎样安装 `earlyprintk`。在早期我们在 [arch/x86/boot/cmdline.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/cmdline.c)里面的 `cmdline_find_option` 函数和 `__cmdline_find_option`, `__cmdline_find_option_bool` 的帮助下寻找内核参数和他们的值。我们在通用内核部分不依赖于系统架构,在这里我们使用另一种方法。 如果你正在阅读linux内核源代码,你可能注意到这样的调用: +在之前的[章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html)我们从 [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c)了解了特定系统架构的初始化内容(在我们的例子中是 `x86_64` ),并且通过 `x86_configure_nx` 函数根据对[NX bit](http://en.wikipedia.org/wiki/NX_bit)的支持配置了 `_PAGE_NX` 标志位。正如我之前写的, `setup_arch` 函数和 `start_kernel` 都非常复杂,所以在这个和下个章节我们将继续学习关于系统架构初始化进程的内容。`x86_configure_nx` 函数的下面是 `parse_early_param`函数。这个函数定义在 [init/main.c](https://github.com/torvalds/linux/blob/master/init/main.c) 中并且你可以从它的名字中了解到,这个函数解析内核命令行并且基于给定的参数 (所有的内核命令行参数你都可以在 [Documentation/kernel-parameters.txt](https://github.com/torvalds/linux/blob/master/Documentation/kernel-parameters.txt) 找到)。 你可能记得在最前面的 [章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html) 我们是怎样安装 `earlyprintk`。在前面我们在 [arch/x86/boot/cmdline.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/cmdline.c)里面的 `cmdline_find_option` 和 `__cmdline_find_option`, `__cmdline_find_option_bool` 函数的帮助下寻找内核参数及其值。我们在通用内核部分不依赖于特定的系统架构,在这里我们使用另一种方法。 如果你正在阅读linux内核源代码,你可能注意到这样的调用: ```C early_param("gbpages", parse_direct_gbpages_on); @@ -23,7 +23,7 @@ early_param("gbpages", parse_direct_gbpages_on); ``` 这个定义可以在 [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h)中可以找到. -正如你所看到的 `early_param` 宏只是调用了 `__setup_param` 宏: +正如你所看到的, `early_param` 宏只是调用了 `__setup_param` 宏: ```C #define __setup_param(str, unique_id, fn, early) \ @@ -35,7 +35,7 @@ early_param("gbpages", parse_direct_gbpages_on); = { __setup_str_##unique_id, fn, early } ``` -这个宏定义了 `__setup_str_*_id` 变量 (这里的 `*` 取决于被给定的函数名称) 并且把给定的命令行参数赋值给这个变量。在下一行中,我们可以看到类型为 `obs_kernel_param` 的变量 `__setup_ *` 的定义及其初始化。 +这个宏内部定义了 `__setup_str_*_id` 变量 (这里的 `*` 取决于给定的函数名称),然后把给定的命令行参数赋值给这个变量。在下一行中,我们可以看到定义了一个`obs_kernel_param` 类型的变量 `__setup_ *` 并进行初始化。 `obs_kernel_param` 结构体定义如下: @@ -50,10 +50,10 @@ struct obs_kernel_param { 这个结构体包含三个字段: * 内核参数的名称 -* 功能取决于参数的函数 +* 根据不同的参数,选取对应的处理函数 * 决定参数是否为 early 的标记位 (译者注:这个参数是个标记位,有0和1两种值,两种值的后续调用是不一样的) -注意 `__set_param` 宏定义有 `__section(.init.setup)` 属性。这意味着所有 `__setup_str_ *` 将被放置在 `.init.setup` 区段中,此外正如我们在 [include/asm-generic/vmlinux.lds.h](https://github.com/torvalds/linux/blob/master/include/asm-generic/vmlinux.lds.h) 中看到的,他们将被放置在 `__setup_start` 和 `__setup_end` 之间: +注意 `__set_param` 宏定义有 `__section(.init.setup)` 属性。这意味着所有 `__setup_str_ *` 将被放置在 `.init.setup` 区段中,此外正如我们在 [include/asm-generic/vmlinux.lds.h](https://github.com/torvalds/linux/blob/master/include/asm-generic/vmlinux.lds.h) 中看到的,`.init.setup` 区段被放置在 `__setup_start` 和 `__setup_end` 之间: ``` #define INIT_SETUP(initsetup_align) \ @@ -63,7 +63,7 @@ struct obs_kernel_param { VMLINUX_SYMBOL(__setup_end) = .; ``` -现在我们知道了参数是怎样定义的,让我们一起回到 `parse_early_param` 的实现: +现在我们知道了参数是怎样定义的,让我们一起回到 `parse_early_param` 的实现上来: ```C void __init parse_early_param(void) @@ -80,26 +80,29 @@ void __init parse_early_param(void) done = 1; } ``` -`parse_early_param` 函数定义了两个静态变量。首先第一个变量 `done` 用来检查 `parse_early_param` 函数是否已经被调用,第二个变量是用来临时存储内核命令行的。在这之后我们把 `boot_command_line` 赋值到我们刚才定义的临时命令行变量中( `tmp_cmdline` ) 并且从相同的源代码文件 `main.c` 中调用 `parse_early_options` 函数。 `parse_early_options`函数从 [kernel/params.c](https://github.com/torvalds/linux/blob/master/) 中调用 `parse_args` 函数, `parse_args` 解析传入的命令行并且调用 `do_early_param` 函数。 这个 [函数](https://github.com/torvalds/linux/blob/master/init/main.c#L413) 从 ` __setup_start` 循环到 `__setup_end` ,并且如果 `obs_kernel_param` 中的 `early` 字段值为early(1) ,从 `obs_kernel_param` 调用函数(译者注:调用结构体中的第二个参数,这个参数是个函数)。在这之后所有基于早期命令行参数的服务都被创建,在 `parse_early_param` 之后的下一个调用是 `x86_report_nx` 。 正如我在这章开头所写的,我们已经用 `x86_configure_nx` 配置了 `NX-bit` 位。接下来的 [arch/x86/mm/setup_nx.c](https://github.com/torvalds/linux/blob/master/arch/x86/mm/setup_nx.c)中的 `x86_report_nx`函数仅仅打印出关于 `NX` 的信息。注意我们 `x86_report_nx` 不一定在 `x86_configure_nx` 之后调用,但是一定在 `parse_early_param` 之后调用。答案很简单: 因为内核支持 `noexec` 参数,所以我们在 `parse_early_param` 之后调用 `x86_report_nx` : +`parse_early_param` 函数内部定义了两个静态变量。首先第一个变量 `done` 用来检查 `parse_early_param` 函数是否已经被调用过,第二个变量是用来临时存储内核命令行的。然后我们把 `boot_command_line` 的值赋值给刚刚定义的临时命令行变量中( `tmp_cmdline` ) 并且从相同的源代码文件 `main.c` 中调用 `parse_early_options` 函数。 `parse_early_options`函数从 [kernel/params.c](https://github.com/torvalds/linux/blob/master/) 中调用 `parse_args` 函数, `parse_args` 解析传入的命令行然后调用 `do_early_param` 函数。 `do_early_param` [函数](https://github.com/torvalds/linux/blob/master/init/main.c#L413) 从 ` __setup_start` 循环到 `__setup_end` ,如果循环中 `obs_kernel_param` 中的 `early` 字段值为1 ,就调用 `obs_kernel_param` 中的第二个函数 `setup_func`。在这之后所有基于早期命令行参数的服务都已经被创建,在 `parse_early_param` 之后的下一个函数调用是 `x86_report_nx` 。 正如我在这章开头所写的,我们已经用 `x86_configure_nx` 函数配置了 `NX-bit` 位。接下来 [arch/x86/mm/setup_nx.c](https://github.com/torvalds/linux/blob/master/arch/x86/mm/setup_nx.c)中的 `x86_report_nx`函数打印出关于 `NX` 的信息。注意`x86_report_nx` 函数不一定在 `x86_configure_nx` 函数之后调用,但是一定在 `parse_early_param` 之后调用。答案很简单: 因为内核支持 `noexec` 参数,所以我们一定在 `parse_early_param` 调用之后才能调用 `x86_report_nx` : ``` noexec [X86] On X86-32 available only on PAE configured kernels. + //在X86-32架构上,仅在配置PAE的内核上可用。 noexec=on: enable non-executable mappings (default) + //noexec=on:开启不可执行文件的映射(默认) noexec=off: disable non-executable mappings + //noexec=off: 禁用不可执行文件的映射 ``` 我们可以在启动的时候看到: ![NX](http://oi62.tinypic.com/swwxhy.jpg) -在这之后我们可以看到下面函数的调用: +之后我们可以看到下面函数的调用: ```C memblock_x86_reserve_range_setup_data(); ``` -这个函数被定义在相同的源代码文件 [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c) 中,并且为 `setup_data` 重新映射内存,保留内存块(你可以阅读之前的 [章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html) 了解关于 `setup_data` 的更多内容,你也可以在[Linux kernel memory management](http://xinqiu.gitbooks.io/linux-insides-cn/content/MM/index.html) 中阅读到关于 `ioremap` and `memblock` 的内容)。 +这个函数的定义在相同的源代码文件 [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c) 中,然后这个函数为 `setup_data` 重新映射内存并保留内存块(你可以阅读之前的 [章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html) 了解关于 `setup_data` 的更多内容,你也可以在[Linux kernel memory management](http://xinqiu.gitbooks.io/linux-insides-cn/content/MM/index.html) 中阅读到关于 `ioremap` and `memblock` 的内容)。 接下来我们来看看下面的条件语句: @@ -112,7 +115,7 @@ noexec [X86] } ``` -[arch/x86/kernel/acpi/boot.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/acpi/boot.c) 中的函数 `acpi_mps_check` 取决于 `CONFIG_X86_LOCAL_APIC` 和 `CONFIG_x86_MPPARSE` 配置选项: +[arch/x86/kernel/acpi/boot.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/acpi/boot.c) 中的函数 `acpi_mps_check` 的结果取决于 `CONFIG_X86_LOCAL_APIC` 和 `CONFIG_x86_MPPARSE` 配置选项: ```C int __init acpi_mps_check(void) @@ -130,13 +133,13 @@ int __init acpi_mps_check(void) } ``` -这个函数检查内置的 `MPS` 又称 [多重处理器规范]((http://en.wikipedia.org/wiki/MultiProcessor_Specification)) 表。如果设置了 ` CONFIG_X86_LOCAL_APIC` 但未设置 `CONFIG_x86_MPPAARSE` ,并且传递给内核的命令行参数是 `acpi=off`、`acpi=noirq` 或者 `pci=noacpi`,那么`acpi_mps_check` 就会打印出警告信息。如果 `acpi_mps_check` 返回1,这表示我们警用了本地 [APIC](http://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller) -,而且通过 `setup_clear_cpu_cap` 宏清除了当前CPU中的 `X86_FEATURE_APIC` 位。(你可以阅读[CPU masks](https://xinqiu.gitbooks.io/linux-insides-cn/content/Concepts/linux-cpu-2.html)了解更多关于CPU mask的内容)。 +`acpi_mps_check` 函数检查内建的 `MPS` 又称 [多重处理器规范]((http://en.wikipedia.org/wiki/MultiProcessor_Specification)) 表。如果设置了 ` CONFIG_X86_LOCAL_APIC` 但未设置 `CONFIG_x86_MPPAARSE` ,而且传递给内核的命令行选项中有 `acpi=off`、`acpi=noirq` 或者 `pci=noacpi`,那么`acpi_mps_check` 函数就会输出出警告信息。如果 `acpi_mps_check` 返回了1,这就表示我们禁用了本地 [APIC](http://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller) +,而且 `setup_clear_cpu_cap` 宏清除了当前CPU中的 `X86_FEATURE_APIC` 位。(你可以阅读[CPU masks](https://xinqiu.gitbooks.io/linux-insides-cn/content/Concepts/linux-cpu-2.html)了解关于CPU mask的更多内容)。 早期的PCI转储 -------------------------------------------------------------------------------- -接下来我们使用下面的代码来转储 [PCI](http://en.wikipedia.org/wiki/Conventional_PCI) 设备: +接下来我们通过下面的代码来转储 [PCI](http://en.wikipedia.org/wiki/Conventional_PCI) 设备: ```C #ifdef CONFIG_PCI @@ -145,13 +148,13 @@ int __init acpi_mps_check(void) #endif ``` -`pci_early_dump_regs` 定义在 [arch/x86/pci/common.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/common.c) 中,并且他的值是取决于内核命令行参数:`pci=earlydump` 。我们可以在[drivers/pci/pci.c](https://github.com/torvalds/linux/blob/master/arch) 中看到这个参数的定义: +变量 `pci_early_dump_regs` 定义在 [arch/x86/pci/common.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/common.c) 中,他的值取决于内核命令行参数:`pci=earlydump` 。我们可以在[drivers/pci/pci.c](https://github.com/torvalds/linux/blob/master/arch) 中看到这个参数的定义: ```C early_param("pci", pci_setup); ``` -`pci_setup` 函数获得 `pci=` 之后的字符串,然后进行分析。这个函数调用了在 [drivers/pci/pci.c](https://github.com/torvalds/linux/blob/master/arch) 中用 `_weak` 修饰符定义的 `pcibios_setup` 函数,并且每种架构都重写了 `_weak` 修饰过的函数。 例如 依赖于 `x86_64` 架构的版本写在[arch/x86/pci/common.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/common.c)中: +`pci_setup` 函数取出 `pci=` 之后的字符串,然后进行解析。这个函数调用 [drivers/pci/pci.c](https://github.com/torvalds/linux/blob/master/arch) 中用 `_weak` 修饰符定义的 `pcibios_setup` 函数,并且每种架构都重写了 `_weak` 修饰过的函数。 例如, `x86_64` 架构上的该函数版本在[arch/x86/pci/common.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/common.c)中: ```C char *__init pcibios_setup(char *str) { @@ -168,14 +171,14 @@ char *__init pcibios_setup(char *str) { } ``` -所以,如果 `CONFIG_PCI` 选项被设置,而且我们向内核命令行传递了 `pci=earlydump` 选项,下一个被调用的函数是在 [arch/x86/pci/early.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/early.c)中的 `early_dump_pci_devices` 。这个函数使用下面代码检查 `noearly` pci 参数: +如果 `CONFIG_PCI` 选项被设置,而且我们向内核命令行传递了 `pci=earlydump` 选项,那么 [arch/x86/pci/early.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/early.c)中的 `early_dump_pci_devices` 函数将会被调用。这个函数像下面这样来检查pci参数 `noearly` : ```C if (!early_pci_allowed()) return; ``` -同时如果条件通过则返回。每个PCI域可以承载多达 `256` 条总线,并且每条总线可以承载多达32个设备。那么接下来我们进行下面的循环: +如果条件则返回。每个PCI域可以承载多达 `256` 条总线,并且每条总线可以承载多达32个设备。那么接下来我们进入下面的循环: ```C for (bus = 0; bus < 256; bus++) { @@ -193,7 +196,7 @@ for (bus = 0; bus < 256; bus++) { 这就是 pci 加载的全部了。我们在这里不会深入研究 `pci` 的细节,不过我们会在 `Drivers/PCI` 章节看到更多的细节。 -完成内存解析 +内存解析的完成 -------------------------------------------------------------------------------- @@ -211,14 +214,15 @@ for (bus = 0; bus < 256; bus++) { early_reserve_e820_mpc_new(); ``` -让我们来一起看看上面的代码。正如你所看到的,第一个函数是 `e820_reserve_setup_data` 。这个函数和我们前面看到的 `memblock_x86_reserve_range_setup_data` 函数做的事情几乎是相同的,但是这个函数同时还会调用 `e820_update_range` 函数,向 `e820map` 中用给定的类型添加新的区域,在我们的例子中,使用的是 `E820_RESERVED_KERN` 类型。接下来的函数是 `finish_e820_parsing` 。除了这两个函数之外,我们还可以看到一些与 [e820](http://en.wikipedia.org/wiki/E820) 有关的函数。你可以在上面的清单中看到这些函数。`e820_add_kernel_range` 函数需要内核开始和结束的物理地址: +让我们来一起看看上面的代码。正如你所看到的,第一个函数是 `e820_reserve_setup_data` 。这个函数和我们前面看到的 `memblock_x86_reserve_range_setup_data` 函数做的事情几乎是相同的,但是这个函数同时还会调用 `e820_update_range` 函数,向 `e820map` 中用给定的类型添加新的区域,在我们的例子中,使用的是 `E820_RESERVED_KERN` 类型。接下来的函数是 `finish_e820_parsing`,这个函数使用 `sanitize_e820_map` 函数对 `e820map` 进行清理。除了这两个函数之外,我们还可以看到一些与 [e820](http://en.wikipedia.org/wiki/E820) 有关的函数。你可以在上面的列表中看到这些函数。`e820_add_kernel_range` 函数需要内核开始和结束的物理地址: ```C u64 start = __pa_symbol(_text); u64 size = __pa_symbol(_end) - start; ``` -函数会检查在 `e820map` 中被标记成 `E820RAM` 的 `.text` `.data` 和 `.bss` 段,如果没有这些字段,那么就会输出错误信息。接下来的 `trm_bios_range` 函数把 `e820Map` 中的前4096个字节更新为 `E820_RESERVED` 并且调用函数 `sanitize_e820_map` 进行处理。在这之后我们使用 `e820_end_of_ram_pfn` 得到最后一个页面帧的编号,每个内存页面都有一个唯一的编号 - `页面帧编号`, `e820_end_of_ram_pfn` 函数调用 `e820_end_pfn` 函数返回这个最大的页面帧编号: +函数会检查在 `e820map` 中被标记成 `E820RAM` 的 `.text` `.data` 和 `.bss` 区段,如果没有这些字段,那么就会输出错误信息。接下来的 `trm_bios_range` 函数把 `e820Map` 中的前4096个字节修改为 `E820_RESERVED` 并且再次调用函数 `sanitize_e820_map` 进行清理。在这之后我们使用 `e820_end_of_ram_pfn` 函数得到最后一个页帧的编号,每个内存页面都有一个唯一的编号 - `页帧号`, `e820_end_of_ram_pfn` 函数调用 `e820_end_pfn` 函数返回最大的页面帧号: + ```C unsigned long __init e820_end_of_ram_pfn(void) { @@ -226,7 +230,7 @@ unsigned long __init e820_end_of_ram_pfn(void) } ``` -`e820_end_pfn` 函数读取当前系统架构的最大页面帧编号(对于 `x86_64` 架构来说 `MAX_ARCH_PFN` 是 `0x400000000` )。在 `e820_end_pfn` 函数中我们遍历整个 `e820` 插槽,并且检查 `e820` 入口是否有 `E820_RAM` 或者 `E820_PRAM` 类型,因为我们只能对这些类型计算页面帧编码,得到当前 `e820` 入口页面帧的基地址和结束地址,同时对这些地址进行检查: +`e820_end_pfn` 函数读取特定于系统架构的最大页帧号(对于 `x86_64` 架构来说 `MAX_ARCH_PFN` 是 `0x400000000` )。在 `e820_end_pfn` 函数中我们遍历整个 `e820` 槽,并且检查 `e820` 入口是否有 `E820_RAM` 或者 `E820_PRAM` 类型,因为我们只能对这些类型计算页面帧码,得到当前 `e820` 入口页面帧的基地址和结束地址,同时对这些地址进行检查: ```C for (i = 0; i < e820.nr_map; i++) { @@ -260,7 +264,7 @@ for (i = 0; i < e820.nr_map; i++) { return last_pfn; ``` -接下来我们检查在循环中得到的 `last_pfn` 不得大于特定系统架构的最大页面帧编号(在我们的例子中是 `x86_64` 系统架构),输出关于最大页面帧编码的信息,并且返回它。我们可以在 `dmesg` 的输出中看到 `last_pfn` : +接下来我们检查在循环中得到的 `last_pfn`,`last_pfn` 不得大于特定于系统架构的最大页帧号(在我们的例子中是 `x86_64` 系统架构),输出关于最大页帧号的信息,并且返回它。我们可以在 `dmesg` 的输出中看到 `last_pfn` : ``` ... @@ -268,7 +272,7 @@ for (i = 0; i < e820.nr_map; i++) { ... ``` -在这之后,我们计算出了最大的页面帧编号,我们要计算 `max_low_pfn` ,这是 `小内存` 或者低于第一个4GB中的的的最大页面帧。如果安装了超过4GB的内存RAM,`max_low_pfn` 将会是 `e820_end_of_low_ram_pfn` 函数的结果,这个函数和 `e820_end_of_ram_pfn` 相似,但是有4GB自己的限制,换句话说 `max_low_pfn` 和 `max_pfn` 的值是一样的: +在这之后,我们计算出了最大的页面帧号,我们要计算 `max_low_pfn` ,这是 `低端内存` 或者低于第一个4GB中的最大页面帧。如果系统安装了超过4GB的内存RAM,`e820_end_of_low_ram_pfn` 函数的结果将会是 `max_low_pfn` ,这个函数和 `e820_end_of_ram_pfn` 相似,但是有4GB自己的限制,换句话说 `max_low_pfn` 和 `max_pfn` 的值是一样的: ```C if (max_pfn > (1UL<<(32 - PAGE_SHIFT))) @@ -279,20 +283,20 @@ else high_memory = (void *)__va(max_pfn * PAGE_SIZE - 1) + 1; ``` -接下来我们通过 `__va` 宏计算 `大内存` 中的最大页面帧编号(有更高的直接内存映射上界),这个宏根据给定的物理内存返回一个虚拟地址。 +接下来我们通过 `__va` 宏计算 `高端内存` (有更高的内存直接映射上界)中的最大页帧号,并且这个宏会根据给定的物理内存返回一个虚拟地址。 -直接媒体结构扫描 +桌面管理接口 ------------------------------------------------------------------------------- -在处理完不同内存区域和 `e820` 插槽之后的下一步就是收集有关计算机的信息。我们将获得与 [桌面管理接口](http://en.wikipedia.org/wiki/Desktop_Management_Interface) 和下面函数的所有信息: +在处理完不同内存区域和 `e820` 槽之后,接下来就该是收集计算机的相关信息了。我们将用下面的函数收集与 [桌面管理接口](http://en.wikipedia.org/wiki/Desktop_Management_Interface) 有关的所有信息: ```C dmi_scan_machine(); dmi_memdev_walk(); ``` -首先是定义在 [drivers/firmware/dmi_scan.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/drivers/firmware/dmi_scan.c) 中的 `dmi_scan_machine` 函数。这个函数浏览 [System Management BIOS](http://en.wikipedia.org/wiki/System_Management_BIOS) 结构,从中提取信息。制定了两种方法来获得 `SMBIOS` 表的访问权: 第一种是从 [EFI](http://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface) 的配置表获得指向 `SMBIOS` 表的指针;第二种是扫描 `0xF0000` 和 `0x10000` 地址之间的物理地址。让我们一起看看第二种方法。`dmi_scan_machine` 函数通过 `dmi_early_remap` 函数将 `0xf0000` 和 `0x10000` 之间的内存重新映射到 `early_ioremap`: +首先是定义在 [drivers/firmware/dmi_scan.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/drivers/firmware/dmi_scan.c) 中的 `dmi_scan_machine` 函数。这个函数遍历 [System Management BIOS](http://en.wikipedia.org/wiki/System_Management_BIOS) 结构,从中提取信息。这里有两种方法来访问 `SMBIOS` 表: 第一种是从 [EFI](http://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface) 的配置表获得指向 `SMBIOS` 表的指针;第二种是扫描 `0xF0000` 和 `0x10000` 地址之间的物理地址。让我们一起看看第二种方法。`dmi_scan_machine` 函数通过 `dmi_early_remap` 函数将 `0xf0000` 和 `0x10000` 之间的内存重新映射到 `early_ioremap`: ```C void __init dmi_scan_machine(void) @@ -306,7 +310,7 @@ void __init dmi_scan_machine(void) if (p == NULL) goto error; ``` -然后遍历所有的 `DMI` 头地址,并且查找 `_SM_` 字符串: +然后迭代所有的 `DMI` 头部地址,并且查找 `_SM_` 字符串: ```C memset(buf, 0, 16); @@ -320,18 +324,20 @@ for (q = p; q < p + 0x10000; q += 16) { memcpy(buf, buf + 16, 16); } ``` -`_SM_` 字符串一定在 `000F0000h` 和 `0x000FFFFF` 地址之间。在这里我们用 `memcpy_fromio` 函数向 `buf` 里面复制16个字节,这个函数和 `memcpy` 函数的作用是一样的。然后对这个缓冲区( `buf` ) 执行`dmi_smbios3_present` 和 `dmi_present` 函数。这些函数检查 `buf`的前4个字节是否是 `__SM__` 字符串,得到 `SMBIOS` 的版本号,并且获得 `_DMI_` 的属性例如 `_DMI_` 结构表长度、表的地址等等...每当其中的一个函数完成之后,你就会在 `dmesg` 的输出中看到函数的运行结果: +`_SM_` 字符串一定在 `000F0000h` 和 `0x000FFFFF` 地址之间。在这里我们用 `memcpy_fromio` 函数向 `buf` 里面拷贝16个字节,这个函数和 `memcpy` 函数的作用是一样的。然后对这个缓冲区( `buf` ) 执行`dmi_smbios3_present` 和 `dmi_present` 函数。这些函数检查 `buf` 的前4个字节是否是 `__SM__` 字符串,获得 `SMBIOS` 的版本和 `_DMI_` 的属性例如 `_DMI_` 的结构表长度、结构表的地址等等... 在其中的一个函数完成之后,你就可以在 `dmesg` 的输出中看到它的运行结果: + ``` [ 0.000000] SMBIOS 2.7 present. [ 0.000000] DMI: Gigabyte Technology Co., Ltd. Z97X-UD5H-BK/Z97X-UD5H-BK, BIOS F6 06/17/2014 ``` -在 `dmi_scan_machine` 函数的最后,我们取消之前重新映射的内存: + +在 `dmi_scan_machine` 函数的最后,我们释放之前重新映射的内存: ```C dmi_early_unmap(p, 0x10000); ``` -第二个函数是 - `dmi_memdev_walk`。正如你理解的那样,这个函数遍历整个内存设备。让我们一起看看这个函数: +第二个函数是 - `dmi_memdev_walk`。和你想的一样,这个函数遍历整个内存设备。让我们一起看看这个函数: ```C void __init dmi_memdev_walk(void) @@ -347,7 +353,7 @@ void __init dmi_memdev_walk(void) } ``` -这个函数检查 `DMI` 是否可用(我们之前在 `dmi_scan_machine` 函数中得到了这个结果)并且使用 `dmi_walk_early` 和 `dmi_alloc` 函数收集关于内存设备的信息,其中 `dmi_alloc` 的定义如下: +这个函数检查 `DMI` 是否可用(我们之前在 `dmi_scan_machine` 函数中得到了这个结果,并且保存在 `dmi_available` 中),然后使用 `dmi_walk_early` 和 `dmi_alloc` 函数收集内存设备的有关信息,其中 `dmi_alloc` 的调用如下: ``` #ifdef CONFIG_DMI @@ -355,7 +361,7 @@ RESERVE_BRK(dmi_alloc, 65536); #endif ``` -`RESERVE_BRK` 定义在 [arch/x86/include/asm/setup.h](http://en.wikipedia.org/wiki/Desktop_Management_Interface)中,并且这个函数在 `brk` 段中保留给定大小的空间: +定义在 [arch/x86/include/asm/setup.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/setup.h) 中的 `RESERVE_BRK` 函数在 `brk` 段中预留给定大小的空间: ------------------------- init_hypervisor_platform(); @@ -369,7 +375,7 @@ RESERVE_BRK(dmi_alloc, 65536); 均衡多处理(SMP)的配置 -------------------------------------------------------------------------------- -接下来的一步是解析 [SMP](http://en.wikipedia.org/wiki/Symmetric_multiprocessing) 的配置信息。我们调用 `find_smp_config` 函数来完成这个任务,这个函数只是调用另一个函数: +接下来的一步是解析 [SMP](http://en.wikipedia.org/wiki/Symmetric_multiprocessing) 的配置信息。我们调用 `find_smp_config` 函数来完成这个任务,这个函数内部调用另一个函数: ```C static inline void find_smp_config(void) @@ -378,7 +384,7 @@ static inline void find_smp_config(void) } ``` -在函数的内部,`x86_init.mpparse.find_smp_config` 就是 [arch/x86/kernel/mpparse.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/mpparse.c) 中的 `default_find_smp_config` 函数。在 `default_find_smp_config` 函数中我们扫描一些内存区域来寻找 `SMP` 的配置信息,并在找到它们的时候返回: +在函数的内部,`x86_init.mpparse.find_smp_config` 函数就是 [arch/x86/kernel/mpparse.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/mpparse.c) 中的 `default_find_smp_config` 函数。我们调用 `default_find_smp_config` 函数扫描内存中的一些区域来寻找 `SMP` 的配置信息,并在找到它们的时候返回: ```C if (smp_scan_config(0x0, 0x400) || @@ -387,14 +393,14 @@ if (smp_scan_config(0x0, 0x400) || return; ``` -首先 `smp_scan_config` 函数定义了一些变量: +首先 `smp_scan_config` 函数内部定义了一些变量: ```C unsigned int *bp = phys_to_virt(base); struct mpf_intel *mpf; ``` -第一个变量是我们用来扫描 `SMP` 配置内存区域的虚拟地址;第二个变量是指向 `mpf_intel` 结构体的指针。让我们一起试着去理解 `mpf_intel` 是什么吧。所有的信息都存储在多处理器配置数据结构中。`mpf_intel` 呈现了这个结构,看下来像是下面这样: +第一个变量是我们用来扫描 `SMP` 配置内存区域的虚拟地址;第二个变量是指向 `mpf_intel` 结构体的指针。让我们一起试着去理解 `mpf_intel` 是什么吧。所有的信息都存储在多处理器配置数据结构中。`mpf_intel` 代表了这个结构,看下来像是下面这样: ```C struct mpf_intel { @@ -411,9 +417,9 @@ struct mpf_intel { }; ``` -正如我们在文档中看到的那样 - 系统 BIOS的主要功能之一就是创建MP指针结构和MP配置表。然后操作系统必须拥有访问多处理器配置信息,并且 `mpf_intel` 存储了多处理器配置表的物理地址(看结构体的第二个参数),然后,`smp_scan_config` 在指定的内存区域中循环查找 `MP floating pointer structure` 。它检查当前浮动指针是否指向 `SMP` 签名,并且检查他的校验和,在循环中检查 `mpf->specification` 是1还是4(这个值只能是1或者是4): +正如我们在文档中看到的那样 - 系统 BIOS的主要功能之一就是创建MP指针结构和MP配置表。然后操作系统必须可以访问关于多处理器配置的有关信息, `mpf_intel` 中存储了多处理器配置表的物理地址(看结构体的第二个变量),然后,`smp_scan_config` 在指定的内存区域中循环查找 `MP floating pointer structure` 。它检查当前字节是否指向 `SMP` 签名,然后检查签名的校验和,并且在循环中检查 `mpf->specification` 的值是1还是4(这个值只能是1或者是4): -```C +```C7 while (length > 0) { if ((*bp == SMP_MAGIC_IDENT) && (mpf->length == 1) && @@ -432,7 +438,7 @@ if ((*bp == SMP_MAGIC_IDENT) && 其他的早期内存初始化程序 -------------------------------------------------------------------------------- -在 `setup_arch` 的下一步,我们可以看到 `early_alloc_pgt_buf` 函数的调用,这个函数为早期阶段分配页表缓冲区。页表缓冲区将被防止在 `brk` 段中。让我们一起看看这个函数的实现: +在 `setup_arch` 的下一步,我们可以看到 `early_alloc_pgt_buf` 函数的调用,这个函数在早期阶段分配页表缓冲区。页表缓冲区将被放置在 `brk` 区段中。让我们一起看看这个功能的实现: ```C @@ -449,7 +455,7 @@ void __init early_alloc_pgt_buf(void) } ``` -首先这个函数获得页表缓冲区的大小,它的值是 `INIT_PGT_BUF_SIZE` ,这个值在目前的linux 4.0 内核中是 `(6 * PAGE_SIZE)`。因为我们已经得到了页表缓冲区的大小,我们调用 `extend_brk` 函数并且传入两个参数: size和align。你可以从他们的名称中了解到,这个函数扩展 `brk` 段。正如我们在linux 内核链接脚本中看到的,`brk`在内存中的位置恰好在 [BSS](http://en.wikipedia.org/wiki/.bss) 后面: +首先这个函数获得页表缓冲区的大小,它的值是 `INIT_PGT_BUF_SIZE` ,这个值在目前的linux 4.0 内核中是 `(6 * PAGE_SIZE)`。因为我们已经得到了页表缓冲区的大小,现在我们调用 `extend_brk` 函数并且传入两个参数: size和align。你可以从他们的名称中猜到,这个函数扩展 `brk` 段。正如我们在linux内核链接脚本中看到的,`brk` 区段在内存中的位置恰好就在 [BSS](http://en.wikipedia.org/wiki/.bss) 区段后面: ```C . = ALIGN(PAGE_SIZE); @@ -461,11 +467,11 @@ void __init early_alloc_pgt_buf(void) } ``` -或者我们可以使用 `readelf` 工具来找到它: +我们也可以使用 `readelf` 工具来找到它: ![brk area](http://oi61.tinypic.com/71lkeu.jpg) -之后我们用 `_pa` 宏得到了新的 `brk`段的物理地址,我们计算页表缓冲区的基地址和结束地址。下一步因为我们之前创建了页面缓冲区,我们使用 `reserve_brk` 函数为 brk 段保留内存块: +之后我们用 `_pa` 宏得到了新的 `brk` 区段的物理地址,我们计算页表缓冲区的基地址和结束地址。因为我们之前已经创建好了页面缓冲区,所以现在我们使用 `reserve_brk` 函数为 `brk` 区段保留内存块: ```C static void __init reserve_brk(void) @@ -477,7 +483,8 @@ static void __init reserve_brk(void) _brk_start = 0; } ``` -注意在 `reserve_brk` 的最后,我们把 `_brk_start` 设置为0,因为在这之后我们不会再为它分配内存了,我们需要使用 `cleanup_highmap` 函数来取消内核映射中越界的内存区域。请记住内核映射是 `__START_KERNEL_map` 和 `_end - _text` 或者 `level2_kernel_pgt` 对内核 `_text`、`data` 和 `bss` 的映射。在 `clean_high_map` 的开始部分我们定义这些参数: + +注意在 `reserve_brk` 的最后,我们把 `_brk_start` 赋值为0,因为在这之后我们不会再为 `brk` 分配内存了,我们需要使用 `cleanup_highmap` 函数来释放内核映射中越界的内存区域。请记住内核映射是 `__START_KERNEL_map` 和 `_end - _text` 或者 `level2_kernel_pgt` 对内核 `_text`、`data` 和 `bss` 区段的映射。在 `clean_high_map` 的开始部分我们定义这些参数: ```C unsigned long vaddr = __START_KERNEL_map; @@ -486,7 +493,7 @@ pmd_t *pmd = level2_kernel_pgt; pmd_t *last_pmd = pmd + PTRS_PER_PMD; ``` -现在,因为我们已经定义了内核映射的开始和结束部分,我们在循环中遍历所有内核页中间目录调墨油, 并清除不在 `_text` 和 `end` 段之间的条目: +现在,因为我们已经定义了内核映射的开始和结束位置,所以我们在循环中遍历所有内核页中间目录条目, 并且清除不在 `_text` 和 `end` 区段中的条目: ```C for (; pmd < last_pmd; pmd++, vaddr += PMD_SIZE) { @@ -497,7 +504,7 @@ for (; pmd < last_pmd; pmd++, vaddr += PMD_SIZE) { } ``` -在这之后,我们使用 `memblock_set_current_limit` (你可以在[linux 内存管理 第二章节](https://github.com/MintCN/linux-insides-zh/blob/master/MM/linux-mm-2.md) 阅读关于 `memblock` 的更多内容) 函数来设置 `memblock` 分配的限制,它将是`ISA_END_ADDRESS` 或者 `0x100000` 并且它会调用 `memblock_x86_fill` 函数根据 `e820` 来填充 `memblock` 信息。你可以在内核初始化的时候看到这个函数运行的结果: +在这之后,我们使用 `memblock_set_current_limit` (你可以在[linux 内存管理 第二章节](https://github.com/MintCN/linux-insides-zh/blob/master/MM/linux-mm-2.md) 阅读关于 `memblock` 的更多内容) 函数来为 `memblock` 分配内存设置一个界限,这个界限是`ISA_END_ADDRESS` 或者 `0x100000` ,然后调用 `memblock_x86_fill` 函数根据 `e820` 来填充 `memblock` 信息。你可以在内核初始化的时候看到这个函数运行的结果: ``` MEMBLOCK configuration: @@ -511,14 +518,15 @@ MEMBLOCK configuration: reserved[0x1] [0x00000001000000-0x00000001a57fff], 0xa58000 bytes flags: 0x0 reserved[0x2] [0x0000007ec89000-0x0000007fffffff], 0x1377000 bytes flags: 0x0 ``` -在 `memblock_x86_fill` 之后的其他函数: `early_reserve_e820_mpc_new` 在 `e820map` 中为多处理器规格表分配额外的槽, `reserve_real_mode` - 保留从 `0x0` 到1M的低内存用作实模式的跳板(用于重启等...),`trim_platform_memory_ranges` 函数删除掉以 `0x20050000`, `0x20110000` 等地址开头的内存空间。这些内存区域必须被排除在外,因为 [Sandy Bridge](http://en.wikipedia.org/wiki/Sandy_Bridge) 会在这些区域出现一些故障, `trim_low_memory_range` 保留 `memblock` 中的前4KB页面,`init_mem_mapping` 函数重新创建直接内存映射并且建立在 `PAGE_OFFSET` 处物理内存的直接映射, `early_trap_pf_init` 建立了 `#PF` 处理函数(我们将会在有关中断的章节看到它),然后 `setup_real_mode` 函数建立到 [实模式]http://en.wikipedia.org/wiki/Real_mode) 代码的跳板。 -这就是本章的全部内容了。您可能注意到这部分并没有包括 `setup_arch` 中的所有函数 (如 "early_gart_iommu_check"、[mtrr](http://en.wikipedia.org/wiki/Memory_type_range_register) 初始化等...)。正如我已经写了很多次的, `setup_arch` 很复杂 linux 内核也很复杂。这就是为什么我不能囊括 linux 内核中的每一行。我认为我们并没有错过重要的东西, 但是你可以这样说: 每行代码都很重要。是的, 这是真的, 但是不管怎样我错过了他们, 因为我认为对于整个linux内核面面俱到是不现实的。无论如何, 我们会经常回到我们已经看到的想法, 如果有什么不熟悉的, 我们将讨论这个主题。 +除了 `memblock_x86_fill` 之外的其他函数有: `early_reserve_e820_mpc_new` 在 `e820map` 中为多处理器规格表分配额外的槽, `reserve_real_mode` - 保留从 `0x0` 到1M的低端内存用作到实模式的跳板(用于重启等...),`trim_platform_memory_ranges` 函数清除掉以 `0x20050000`, `0x20110000` 等地址开头的内存空间。这些内存区域必须被排除在外,因为 [Sandy Bridge](http://en.wikipedia.org/wiki/Sandy_Bridge) 会在这些内存区域出现一些问题, `trim_low_memory_range` 保留 `memblock` 中的前4KB页面,`init_mem_mapping` 函数重新创建内存的直接映射,然后在 `PAGE_OFFSET` 处建立物理内存的直接映射, `early_trap_pf_init` 建立了 `#PF` 处理函数(我们将会在有关中断的章节看到它),然后 `setup_real_mode` 函数建立了到 [实模式]http://en.wikipedia.org/wiki/Real_mode) 代码的跳板。 + +这就是本章的全部内容了。您可能注意到这部分并没有包括 `setup_arch` 中的所有函数 (如 `early_gart_iommu_check`、[mtrr](http://en.wikipedia.org/wiki/Memory_type_range_register) 的初始化等...)。正如我已经说了很多次的, `setup_arch` 函数很复杂,linux内核也很复杂。这就是为什么我不能对于linux 内核中的每一行面面俱到。我认为我们并没有错过重要的东西, 但是你可能会说: 每行代码都很重要。是的, 这没错, 但不管怎样我略过了他们, 因为我认为对于整个linux内核面面俱到是不现实的。无论如何, 我们会经常复习我们已经了解的, 如果有什么不熟悉的, 我们将覆盖这个主题。 结束语 -------------------------------------------------------------------------------- -这里是linux 内核初始化进程第六章节的结尾。在这一章节中,我们再次深入研究了 `setup_arch` 函数,然而这是个很长的部分,我们目前还没有完成它。没错, `setup_arch`很复杂,希望下个章节将会是这个函数的最后一个部分。(译者注:假的)。 +这里是linux 内核初始化进程第六章节的结尾。在这一章节中,我们再次深入研究了 `setup_arch` 函数,然而这是个很长的部分,我们目前还没有学习完。的确, `setup_arch`很复杂,希望下个章节将会是这个函数的最后一个部分。。 如果你有任何的疑问或者建议,你可以留言,也可以直接发消息给我[twitter](https://twitter.com/0xAX)。 From 8e7587484130baa29b0ddb0ba0f5f8f936ca4a89 Mon Sep 17 00:00:00 2001 From: kele1997 <2363618563@qq.com> Date: Fri, 30 Mar 2018 22:45:32 +0800 Subject: [PATCH 11/14] complete2.0 --- Initialization/linux-initialization-6-zh.md | 553 -------------------- Initialization/linux-initialization-6.md | 176 ++++--- 2 files changed, 91 insertions(+), 638 deletions(-) delete mode 100644 Initialization/linux-initialization-6-zh.md diff --git a/Initialization/linux-initialization-6-zh.md b/Initialization/linux-initialization-6-zh.md deleted file mode 100644 index b1a15a8..0000000 --- a/Initialization/linux-initialization-6-zh.md +++ /dev/null @@ -1,553 +0,0 @@ -内核初始化 第六部分 -=========================================================== - -仍旧是与系统架构有关的初始化 -=========================================================== - -在之前的[章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html)我们从 [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c)了解了特定系统架构的初始化内容(在我们的例子中是 `x86_64` ),并且通过 `x86_configure_nx` 函数根据对[NX bit](http://en.wikipedia.org/wiki/NX_bit)的支持配置了 `_PAGE_NX` 标志位。正如我之前写的, `setup_arch` 函数和 `start_kernel` 都非常复杂,所以在这个和下个章节我们将继续学习关于系统架构初始化进程的内容。`x86_configure_nx` 函数的下面是 `parse_early_param`函数。这个函数定义在 [init/main.c](https://github.com/torvalds/linux/blob/master/init/main.c) 中并且你可以从它的名字中了解到,这个函数解析内核命令行并且基于给定的参数 (所有的内核命令行参数你都可以在 [Documentation/kernel-parameters.txt](https://github.com/torvalds/linux/blob/master/Documentation/kernel-parameters.txt) 找到)。 你可能记得在最前面的 [章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html) 我们是怎样安装 `earlyprintk`。在前面我们在 [arch/x86/boot/cmdline.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/cmdline.c)里面的 `cmdline_find_option` 和 `__cmdline_find_option`, `__cmdline_find_option_bool` 函数的帮助下寻找内核参数及其值。我们在通用内核部分不依赖于特定的系统架构,在这里我们使用另一种方法。 如果你正在阅读linux内核源代码,你可能注意到这样的调用: - -```C -early_param("gbpages", parse_direct_gbpages_on); -``` - -`early_param` 宏需要两个参数: - -* 命令行参数的名称 -* 如果给定的参数通过,函数将被调用 - -并且定义如下: - -```C -#define early_param(str, fn) \ - __setup_param(str, fn, fn, 1) -``` - -这个定义可以在 [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h)中可以找到. -正如你所看到的, `early_param` 宏只是调用了 `__setup_param` 宏: - -```C -#define __setup_param(str, unique_id, fn, early) \ - static const char __setup_str_##unique_id[] __initconst \ - __aligned(1) = str; \ - static struct obs_kernel_param __setup_##unique_id \ - __used __section(.init.setup) \ - __attribute__((aligned((sizeof(long))))) \ - = { __setup_str_##unique_id, fn, early } -``` - -这个宏内部定义了 `__setup_str_*_id` 变量 (这里的 `*` 取决于给定的函数名称),然后把给定的命令行参数赋值给这个变量。在下一行中,我们可以看到定义了一个`obs_kernel_param` 类型的变量 `__setup_ *` 并进行初始化。 - -`obs_kernel_param` 结构体定义如下: - -```C -struct obs_kernel_param { - const char *str; - int (*setup_func)(char *); - int early; -}; -``` - -这个结构体包含三个字段: - -* 内核参数的名称 -* 根据不同的参数,选取对应的处理函数 -* 决定参数是否为 early 的标记位 (译者注:这个参数是个标记位,有0和1两种值,两种值的后续调用是不一样的) - -注意 `__set_param` 宏定义有 `__section(.init.setup)` 属性。这意味着所有 `__setup_str_ *` 将被放置在 `.init.setup` 区段中,此外正如我们在 [include/asm-generic/vmlinux.lds.h](https://github.com/torvalds/linux/blob/master/include/asm-generic/vmlinux.lds.h) 中看到的,`.init.setup` 区段被放置在 `__setup_start` 和 `__setup_end` 之间: - -``` -#define INIT_SETUP(initsetup_align) \ - . = ALIGN(initsetup_align); \ - VMLINUX_SYMBOL(__setup_start) = .; \ - *(.init.setup) \ - VMLINUX_SYMBOL(__setup_end) = .; -``` - -现在我们知道了参数是怎样定义的,让我们一起回到 `parse_early_param` 的实现上来: - -```C -void __init parse_early_param(void) -{ - static int done __initdata; - static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata; - - if (done) - return; - - /* All fall through to do_early_param. */ - strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE); - parse_early_options(tmp_cmdline); - done = 1; -} -``` -`parse_early_param` 函数内部定义了两个静态变量。首先第一个变量 `done` 用来检查 `parse_early_param` 函数是否已经被调用过,第二个变量是用来临时存储内核命令行的。然后我们把 `boot_command_line` 的值赋值给刚刚定义的临时命令行变量中( `tmp_cmdline` ) 并且从相同的源代码文件 `main.c` 中调用 `parse_early_options` 函数。 `parse_early_options`函数从 [kernel/params.c](https://github.com/torvalds/linux/blob/master/) 中调用 `parse_args` 函数, `parse_args` 解析传入的命令行然后调用 `do_early_param` 函数。 `do_early_param` [函数](https://github.com/torvalds/linux/blob/master/init/main.c#L413) 从 ` __setup_start` 循环到 `__setup_end` ,如果循环中 `obs_kernel_param` 中的 `early` 字段值为1 ,就调用 `obs_kernel_param` 中的第二个函数 `setup_func`。在这之后所有基于早期命令行参数的服务都已经被创建,在 `parse_early_param` 之后的下一个函数调用是 `x86_report_nx` 。 正如我在这章开头所写的,我们已经用 `x86_configure_nx` 函数配置了 `NX-bit` 位。接下来 [arch/x86/mm/setup_nx.c](https://github.com/torvalds/linux/blob/master/arch/x86/mm/setup_nx.c)中的 `x86_report_nx`函数打印出关于 `NX` 的信息。注意`x86_report_nx` 函数不一定在 `x86_configure_nx` 函数之后调用,但是一定在 `parse_early_param` 之后调用。答案很简单: 因为内核支持 `noexec` 参数,所以我们一定在 `parse_early_param` 调用之后才能调用 `x86_report_nx` : - -``` -noexec [X86] - On X86-32 available only on PAE configured kernels. - //在X86-32架构上,仅在配置PAE的内核上可用。 - noexec=on: enable non-executable mappings (default) - //noexec=on:开启不可执行文件的映射(默认) - noexec=off: disable non-executable mappings - //noexec=off: 禁用不可执行文件的映射 -``` - -我们可以在启动的时候看到: - -![NX](http://oi62.tinypic.com/swwxhy.jpg) - -之后我们可以看到下面函数的调用: - -```C - memblock_x86_reserve_range_setup_data(); -``` - -这个函数的定义在相同的源代码文件 [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c) 中,然后这个函数为 `setup_data` 重新映射内存并保留内存块(你可以阅读之前的 [章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html) 了解关于 `setup_data` 的更多内容,你也可以在[Linux kernel memory management](http://xinqiu.gitbooks.io/linux-insides-cn/content/MM/index.html) 中阅读到关于 `ioremap` and `memblock` 的内容)。 - -接下来我们来看看下面的条件语句: - -```C - if (acpi_mps_check()) { -#ifdef CONFIG_X86_LOCAL_APIC - disable_apic = 1; -#endif - setup_clear_cpu_cap(X86_FEATURE_APIC); - } -``` - -[arch/x86/kernel/acpi/boot.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/acpi/boot.c) 中的函数 `acpi_mps_check` 的结果取决于 `CONFIG_X86_LOCAL_APIC` 和 `CONFIG_x86_MPPARSE` 配置选项: - -```C -int __init acpi_mps_check(void) -{ -#if defined(CONFIG_X86_LOCAL_APIC) && !defined(CONFIG_X86_MPPARSE) - /* mptable code is not built-in*/ - if (acpi_disabled || acpi_noirq) { - printk(KERN_WARNING "MPS support code is not built-in.\n" - "Using acpi=off or acpi=noirq or pci=noacpi " - "may have problem\n"); - return 1; - } -#endif - return 0; -} -``` - -`acpi_mps_check` 函数检查内建的 `MPS` 又称 [多重处理器规范]((http://en.wikipedia.org/wiki/MultiProcessor_Specification)) 表。如果设置了 ` CONFIG_X86_LOCAL_APIC` 但未设置 `CONFIG_x86_MPPAARSE` ,而且传递给内核的命令行选项中有 `acpi=off`、`acpi=noirq` 或者 `pci=noacpi`,那么`acpi_mps_check` 函数就会输出出警告信息。如果 `acpi_mps_check` 返回了1,这就表示我们禁用了本地 [APIC](http://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller) -,而且 `setup_clear_cpu_cap` 宏清除了当前CPU中的 `X86_FEATURE_APIC` 位。(你可以阅读[CPU masks](https://xinqiu.gitbooks.io/linux-insides-cn/content/Concepts/linux-cpu-2.html)了解关于CPU mask的更多内容)。 - -早期的PCI转储 --------------------------------------------------------------------------------- - -接下来我们通过下面的代码来转储 [PCI](http://en.wikipedia.org/wiki/Conventional_PCI) 设备: - -```C -#ifdef CONFIG_PCI - if (pci_early_dump_regs) - early_dump_pci_devices(); -#endif -``` - -变量 `pci_early_dump_regs` 定义在 [arch/x86/pci/common.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/common.c) 中,他的值取决于内核命令行参数:`pci=earlydump` 。我们可以在[drivers/pci/pci.c](https://github.com/torvalds/linux/blob/master/arch) 中看到这个参数的定义: - -```C -early_param("pci", pci_setup); -``` - -`pci_setup` 函数取出 `pci=` 之后的字符串,然后进行解析。这个函数调用 [drivers/pci/pci.c](https://github.com/torvalds/linux/blob/master/arch) 中用 `_weak` 修饰符定义的 `pcibios_setup` 函数,并且每种架构都重写了 `_weak` 修饰过的函数。 例如, `x86_64` 架构上的该函数版本在[arch/x86/pci/common.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/common.c)中: - -```C -char *__init pcibios_setup(char *str) { - ... - ... - ... - } else if (!strcmp(str, "earlydump")) { - pci_early_dump_regs = 1; - return NULL; - } - ... - ... - ... -} -``` - -如果 `CONFIG_PCI` 选项被设置,而且我们向内核命令行传递了 `pci=earlydump` 选项,那么 [arch/x86/pci/early.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/early.c)中的 `early_dump_pci_devices` 函数将会被调用。这个函数像下面这样来检查pci参数 `noearly` : - -```C -if (!early_pci_allowed()) - return; -``` - -如果条件则返回。每个PCI域可以承载多达 `256` 条总线,并且每条总线可以承载多达32个设备。那么接下来我们进入下面的循环: - -```C -for (bus = 0; bus < 256; bus++) { - for (slot = 0; slot < 32; slot++) { - for (func = 0; func < 8; func++) { - ... - ... - ... - } - } -} -``` - -并且通过 `read_pci_config` 函数读取 `pci` 配置。 - -这就是 pci 加载的全部了。我们在这里不会深入研究 `pci` 的细节,不过我们会在 `Drivers/PCI` 章节看到更多的细节。 - -内存解析的完成 --------------------------------------------------------------------------------- - - -在 `early_dump_pci_devices` 之后,有一些与可用内存和[e820](http://en.wikipedia.org/wiki/E820)相关的函数并且 [e820](http://en.wikipedia.org/wiki/E820) ,其中 [e820](http://en.wikipedia.org/wiki/E820) 我们在 [内核安装的第一步](http://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html) 章节中整理过。 -```C - /* update the e820_saved too */ - e820_reserve_setup_data(); - finish_e820_parsing(); - ... - ... - ... - e820_add_kernel_range(); - trim_bios_range(void); - max_pfn = e820_end_of_ram_pfn(); - early_reserve_e820_mpc_new(); -``` - -让我们来一起看看上面的代码。正如你所看到的,第一个函数是 `e820_reserve_setup_data` 。这个函数和我们前面看到的 `memblock_x86_reserve_range_setup_data` 函数做的事情几乎是相同的,但是这个函数同时还会调用 `e820_update_range` 函数,向 `e820map` 中用给定的类型添加新的区域,在我们的例子中,使用的是 `E820_RESERVED_KERN` 类型。接下来的函数是 `finish_e820_parsing`,这个函数使用 `sanitize_e820_map` 函数对 `e820map` 进行清理。除了这两个函数之外,我们还可以看到一些与 [e820](http://en.wikipedia.org/wiki/E820) 有关的函数。你可以在上面的列表中看到这些函数。`e820_add_kernel_range` 函数需要内核开始和结束的物理地址: - -```C -u64 start = __pa_symbol(_text); -u64 size = __pa_symbol(_end) - start; -``` - -函数会检查在 `e820map` 中被标记成 `E820RAM` 的 `.text` `.data` 和 `.bss` 区段,如果没有这些字段,那么就会输出错误信息。接下来的 `trm_bios_range` 函数把 `e820Map` 中的前4096个字节修改为 `E820_RESERVED` 并且再次调用函数 `sanitize_e820_map` 进行清理。在这之后我们使用 `e820_end_of_ram_pfn` 函数得到最后一个页帧的编号,每个内存页面都有一个唯一的编号 - `页帧号`, `e820_end_of_ram_pfn` 函数调用 `e820_end_pfn` 函数返回最大的页面帧号: - -```C -unsigned long __init e820_end_of_ram_pfn(void) -{ - return e820_end_pfn(MAX_ARCH_PFN); -} -``` - -`e820_end_pfn` 函数读取特定于系统架构的最大页帧号(对于 `x86_64` 架构来说 `MAX_ARCH_PFN` 是 `0x400000000` )。在 `e820_end_pfn` 函数中我们遍历整个 `e820` 槽,并且检查 `e820` 入口是否有 `E820_RAM` 或者 `E820_PRAM` 类型,因为我们只能对这些类型计算页面帧码,得到当前 `e820` 入口页面帧的基地址和结束地址,同时对这些地址进行检查: - -```C -for (i = 0; i < e820.nr_map; i++) { - struct e820entry *ei = &e820.map[i]; - unsigned long start_pfn; - unsigned long end_pfn; - - if (ei->type != E820_RAM && ei->type != E820_PRAM) - continue; - - start_pfn = ei->addr >> PAGE_SHIFT; - end_pfn = (ei->addr + ei->size) >> PAGE_SHIFT; - - if (start_pfn >= limit_pfn) - continue; - if (end_pfn > limit_pfn) { - last_pfn = limit_pfn; - break; - } - if (end_pfn > last_pfn) - last_pfn = end_pfn; -} -``` - -```C - if (last_pfn > max_arch_pfn) - last_pfn = max_arch_pfn; - - printk(KERN_INFO "e820: last_pfn = %#lx max_arch_pfn = %#lx\n", - last_pfn, max_arch_pfn); - return last_pfn; -``` - -接下来我们检查在循环中得到的 `last_pfn`,`last_pfn` 不得大于特定于系统架构的最大页帧号(在我们的例子中是 `x86_64` 系统架构),输出关于最大页帧号的信息,并且返回它。我们可以在 `dmesg` 的输出中看到 `last_pfn` : - -``` -... -[ 0.000000] e820: last_pfn = 0x41f000 max_arch_pfn = 0x400000000 -... -``` - -在这之后,我们计算出了最大的页面帧号,我们要计算 `max_low_pfn` ,这是 `低端内存` 或者低于第一个4GB中的最大页面帧。如果系统安装了超过4GB的内存RAM,`e820_end_of_low_ram_pfn` 函数的结果将会是 `max_low_pfn` ,这个函数和 `e820_end_of_ram_pfn` 相似,但是有4GB自己的限制,换句话说 `max_low_pfn` 和 `max_pfn` 的值是一样的: - -```C -if (max_pfn > (1UL<<(32 - PAGE_SHIFT))) - max_low_pfn = e820_end_of_low_ram_pfn(); -else - max_low_pfn = max_pfn; - -high_memory = (void *)__va(max_pfn * PAGE_SIZE - 1) + 1; -``` - -接下来我们通过 `__va` 宏计算 `高端内存` (有更高的内存直接映射上界)中的最大页帧号,并且这个宏会根据给定的物理内存返回一个虚拟地址。 - - -桌面管理接口 -------------------------------------------------------------------------------- - -在处理完不同内存区域和 `e820` 槽之后,接下来就该是收集计算机的相关信息了。我们将用下面的函数收集与 [桌面管理接口](http://en.wikipedia.org/wiki/Desktop_Management_Interface) 有关的所有信息: - -```C -dmi_scan_machine(); -dmi_memdev_walk(); -``` - -首先是定义在 [drivers/firmware/dmi_scan.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/drivers/firmware/dmi_scan.c) 中的 `dmi_scan_machine` 函数。这个函数遍历 [System Management BIOS](http://en.wikipedia.org/wiki/System_Management_BIOS) 结构,从中提取信息。这里有两种方法来访问 `SMBIOS` 表: 第一种是从 [EFI](http://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface) 的配置表获得指向 `SMBIOS` 表的指针;第二种是扫描 `0xF0000` 和 `0x10000` 地址之间的物理地址。让我们一起看看第二种方法。`dmi_scan_machine` 函数通过 `dmi_early_remap` 函数将 `0xf0000` 和 `0x10000` 之间的内存重新映射到 `early_ioremap`: - -```C -void __init dmi_scan_machine(void) -{ - char __iomem *p, *q; - char buf[32]; - ... - ... - ... - p = dmi_early_remap(0xF0000, 0x10000); - if (p == NULL) - goto error; -``` -然后迭代所有的 `DMI` 头部地址,并且查找 `_SM_` 字符串: - -```C -memset(buf, 0, 16); -for (q = p; q < p + 0x10000; q += 16) { - memcpy_fromio(buf + 16, q, 16); - if (!dmi_smbios3_present(buf) || !dmi_present(buf)) { - dmi_available = 1; - dmi_early_unmap(p, 0x10000); - goto out; - } - memcpy(buf, buf + 16, 16); -} -``` -`_SM_` 字符串一定在 `000F0000h` 和 `0x000FFFFF` 地址之间。在这里我们用 `memcpy_fromio` 函数向 `buf` 里面拷贝16个字节,这个函数和 `memcpy` 函数的作用是一样的。然后对这个缓冲区( `buf` ) 执行`dmi_smbios3_present` 和 `dmi_present` 函数。这些函数检查 `buf` 的前4个字节是否是 `__SM__` 字符串,获得 `SMBIOS` 的版本和 `_DMI_` 的属性例如 `_DMI_` 的结构表长度、结构表的地址等等... 在其中的一个函数完成之后,你就可以在 `dmesg` 的输出中看到它的运行结果: - -``` -[ 0.000000] SMBIOS 2.7 present. -[ 0.000000] DMI: Gigabyte Technology Co., Ltd. Z97X-UD5H-BK/Z97X-UD5H-BK, BIOS F6 06/17/2014 -``` - -在 `dmi_scan_machine` 函数的最后,我们释放之前重新映射的内存: - -```C -dmi_early_unmap(p, 0x10000); -``` - -第二个函数是 - `dmi_memdev_walk`。和你想的一样,这个函数遍历整个内存设备。让我们一起看看这个函数: - -```C -void __init dmi_memdev_walk(void) -{ - if (!dmi_available) - return; - - if (dmi_walk_early(count_mem_devices) == 0 && dmi_memdev_nr) { - dmi_memdev = dmi_alloc(sizeof(*dmi_memdev) * dmi_memdev_nr); - if (dmi_memdev) - dmi_walk_early(save_mem_devices); - } -} -``` - -这个函数检查 `DMI` 是否可用(我们之前在 `dmi_scan_machine` 函数中得到了这个结果,并且保存在 `dmi_available` 中),然后使用 `dmi_walk_early` 和 `dmi_alloc` 函数收集内存设备的有关信息,其中 `dmi_alloc` 的调用如下: - -``` -#ifdef CONFIG_DMI -RESERVE_BRK(dmi_alloc, 65536); -#endif -``` - -定义在 [arch/x86/include/asm/setup.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/setup.h) 中的 `RESERVE_BRK` 函数在 `brk` 段中预留给定大小的空间: - -------------------------- - init_hypervisor_platform(); - x86_init.resources.probe_roms(); - insert_resource(&iomem_resource, &code_resource); - insert_resource(&iomem_resource, &data_resource); - insert_resource(&iomem_resource, &bss_resource); - early_gart_iommu_check(); - - -均衡多处理(SMP)的配置 --------------------------------------------------------------------------------- - -接下来的一步是解析 [SMP](http://en.wikipedia.org/wiki/Symmetric_multiprocessing) 的配置信息。我们调用 `find_smp_config` 函数来完成这个任务,这个函数内部调用另一个函数: - -```C -static inline void find_smp_config(void) -{ - x86_init.mpparse.find_smp_config(); -} -``` - -在函数的内部,`x86_init.mpparse.find_smp_config` 函数就是 [arch/x86/kernel/mpparse.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/mpparse.c) 中的 `default_find_smp_config` 函数。我们调用 `default_find_smp_config` 函数扫描内存中的一些区域来寻找 `SMP` 的配置信息,并在找到它们的时候返回: - -```C -if (smp_scan_config(0x0, 0x400) || - smp_scan_config(639 * 0x400, 0x400) || - smp_scan_config(0xF0000, 0x10000)) - return; -``` - -首先 `smp_scan_config` 函数内部定义了一些变量: - -```C -unsigned int *bp = phys_to_virt(base); -struct mpf_intel *mpf; -``` - -第一个变量是我们用来扫描 `SMP` 配置内存区域的虚拟地址;第二个变量是指向 `mpf_intel` 结构体的指针。让我们一起试着去理解 `mpf_intel` 是什么吧。所有的信息都存储在多处理器配置数据结构中。`mpf_intel` 代表了这个结构,看下来像是下面这样: - -```C -struct mpf_intel { - char signature[4]; - unsigned int physptr; - unsigned char length; - unsigned char specification; - unsigned char checksum; - unsigned char feature1; - unsigned char feature2; - unsigned char feature3; - unsigned char feature4; - unsigned char feature5; -}; -``` - -正如我们在文档中看到的那样 - 系统 BIOS的主要功能之一就是创建MP指针结构和MP配置表。然后操作系统必须可以访问关于多处理器配置的有关信息, `mpf_intel` 中存储了多处理器配置表的物理地址(看结构体的第二个变量),然后,`smp_scan_config` 在指定的内存区域中循环查找 `MP floating pointer structure` 。它检查当前字节是否指向 `SMP` 签名,然后检查签名的校验和,并且在循环中检查 `mpf->specification` 的值是1还是4(这个值只能是1或者是4): - -```C7 -while (length > 0) { -if ((*bp == SMP_MAGIC_IDENT) && - (mpf->length == 1) && - !mpf_checksum((unsigned char *)bp, 16) && - ((mpf->specification == 1) - || (mpf->specification == 4))) { - - mem = virt_to_phys(mpf); - memblock_reserve(mem, sizeof(*mpf)); - if (mpf->physptr) - smp_reserve_memory(mpf); - } -} -``` - -其他的早期内存初始化程序 --------------------------------------------------------------------------------- - -在 `setup_arch` 的下一步,我们可以看到 `early_alloc_pgt_buf` 函数的调用,这个函数在早期阶段分配页表缓冲区。页表缓冲区将被放置在 `brk` 区段中。让我们一起看看这个功能的实现: - - -```C -void __init early_alloc_pgt_buf(void) -{ - unsigned long tables = INIT_PGT_BUF_SIZE; - phys_addr_t base; - - base = __pa(extend_brk(tables, PAGE_SIZE)); - - pgt_buf_start = base >> PAGE_SHIFT; - pgt_buf_end = pgt_buf_start; - pgt_buf_top = pgt_buf_start + (tables >> PAGE_SHIFT); -} -``` - -首先这个函数获得页表缓冲区的大小,它的值是 `INIT_PGT_BUF_SIZE` ,这个值在目前的linux 4.0 内核中是 `(6 * PAGE_SIZE)`。因为我们已经得到了页表缓冲区的大小,现在我们调用 `extend_brk` 函数并且传入两个参数: size和align。你可以从他们的名称中猜到,这个函数扩展 `brk` 段。正如我们在linux内核链接脚本中看到的,`brk` 区段在内存中的位置恰好就在 [BSS](http://en.wikipedia.org/wiki/.bss) 区段后面: - -```C - . = ALIGN(PAGE_SIZE); - .brk : AT(ADDR(.brk) - LOAD_OFFSET) { - __brk_base = .; - . += 64 * 1024; /* 64k alignment slop space */ - *(.brk_reservation) /* areas brk users have reserved */ - __brk_limit = .; - } -``` - -我们也可以使用 `readelf` 工具来找到它: - -![brk area](http://oi61.tinypic.com/71lkeu.jpg) - -之后我们用 `_pa` 宏得到了新的 `brk` 区段的物理地址,我们计算页表缓冲区的基地址和结束地址。因为我们之前已经创建好了页面缓冲区,所以现在我们使用 `reserve_brk` 函数为 `brk` 区段保留内存块: - -```C -static void __init reserve_brk(void) -{ - if (_brk_end > _brk_start) - memblock_reserve(__pa_symbol(_brk_start), - _brk_end - _brk_start); - - _brk_start = 0; -} -``` - -注意在 `reserve_brk` 的最后,我们把 `_brk_start` 赋值为0,因为在这之后我们不会再为 `brk` 分配内存了,我们需要使用 `cleanup_highmap` 函数来释放内核映射中越界的内存区域。请记住内核映射是 `__START_KERNEL_map` 和 `_end - _text` 或者 `level2_kernel_pgt` 对内核 `_text`、`data` 和 `bss` 区段的映射。在 `clean_high_map` 的开始部分我们定义这些参数: - -```C -unsigned long vaddr = __START_KERNEL_map; -unsigned long end = roundup((unsigned long)_end, PMD_SIZE) - 1; -pmd_t *pmd = level2_kernel_pgt; -pmd_t *last_pmd = pmd + PTRS_PER_PMD; -``` - -现在,因为我们已经定义了内核映射的开始和结束位置,所以我们在循环中遍历所有内核页中间目录条目, 并且清除不在 `_text` 和 `end` 区段中的条目: - -```C -for (; pmd < last_pmd; pmd++, vaddr += PMD_SIZE) { - if (pmd_none(*pmd)) - continue; - if (vaddr < (unsigned long) _text || vaddr > end) - set_pmd(pmd, __pmd(0)); -} -``` - -在这之后,我们使用 `memblock_set_current_limit` (你可以在[linux 内存管理 第二章节](https://github.com/MintCN/linux-insides-zh/blob/master/MM/linux-mm-2.md) 阅读关于 `memblock` 的更多内容) 函数来为 `memblock` 分配内存设置一个界限,这个界限是`ISA_END_ADDRESS` 或者 `0x100000` ,然后调用 `memblock_x86_fill` 函数根据 `e820` 来填充 `memblock` 信息。你可以在内核初始化的时候看到这个函数运行的结果: - -``` -MEMBLOCK configuration: - memory size = 0x1fff7ec00 reserved size = 0x1e30000 - memory.cnt = 0x3 - memory[0x0] [0x00000000001000-0x0000000009efff], 0x9e000 bytes flags: 0x0 - memory[0x1] [0x00000000100000-0x000000bffdffff], 0xbfee0000 bytes flags: 0x0 - memory[0x2] [0x00000100000000-0x0000023fffffff], 0x140000000 bytes flags: 0x0 - reserved.cnt = 0x3 - reserved[0x0] [0x0000000009f000-0x000000000fffff], 0x61000 bytes flags: 0x0 - reserved[0x1] [0x00000001000000-0x00000001a57fff], 0xa58000 bytes flags: 0x0 - reserved[0x2] [0x0000007ec89000-0x0000007fffffff], 0x1377000 bytes flags: 0x0 -``` - -除了 `memblock_x86_fill` 之外的其他函数有: `early_reserve_e820_mpc_new` 在 `e820map` 中为多处理器规格表分配额外的槽, `reserve_real_mode` - 保留从 `0x0` 到1M的低端内存用作到实模式的跳板(用于重启等...),`trim_platform_memory_ranges` 函数清除掉以 `0x20050000`, `0x20110000` 等地址开头的内存空间。这些内存区域必须被排除在外,因为 [Sandy Bridge](http://en.wikipedia.org/wiki/Sandy_Bridge) 会在这些内存区域出现一些问题, `trim_low_memory_range` 保留 `memblock` 中的前4KB页面,`init_mem_mapping` 函数重新创建内存的直接映射,然后在 `PAGE_OFFSET` 处建立物理内存的直接映射, `early_trap_pf_init` 建立了 `#PF` 处理函数(我们将会在有关中断的章节看到它),然后 `setup_real_mode` 函数建立了到 [实模式]http://en.wikipedia.org/wiki/Real_mode) 代码的跳板。 - -这就是本章的全部内容了。您可能注意到这部分并没有包括 `setup_arch` 中的所有函数 (如 `early_gart_iommu_check`、[mtrr](http://en.wikipedia.org/wiki/Memory_type_range_register) 的初始化等...)。正如我已经说了很多次的, `setup_arch` 函数很复杂,linux内核也很复杂。这就是为什么我不能对于linux 内核中的每一行面面俱到。我认为我们并没有错过重要的东西, 但是你可能会说: 每行代码都很重要。是的, 这没错, 但不管怎样我略过了他们, 因为我认为对于整个linux内核面面俱到是不现实的。无论如何, 我们会经常复习我们已经了解的, 如果有什么不熟悉的, 我们将覆盖这个主题。 - -结束语 --------------------------------------------------------------------------------- - -这里是linux 内核初始化进程第六章节的结尾。在这一章节中,我们再次深入研究了 `setup_arch` 函数,然而这是个很长的部分,我们目前还没有学习完。的确, `setup_arch`很复杂,希望下个章节将会是这个函数的最后一个部分。。 - -如果你有任何的疑问或者建议,你可以留言,也可以直接发消息给我[twitter](https://twitter.com/0xAX)。 - -**很抱歉,英语并不是我的母语,非常抱歉给您阅读带来不便,如果你发现文中描述有任何问题,请提交一个 PR 到 [linux-insides](https://github.com/MintCN/linux-insides-zh).** - -链接 --------------------------------------------------------------------------------- - -* [MultiProcessor Specification](http://en.wikipedia.org/wiki/MultiProcessor_Specification) -* [NX bit](http://en.wikipedia.org/wiki/NX_bit) -* [Documentation/kernel-parameters.txt](https://github.com/torvalds/linux/blob/master/Documentation/kernel-parameters.txt) -* [APIC](http://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller) -* [CPU masks](http://0xax.gitbooks.io/linux-insides/content/Concepts/cpumask.html) -* [Linux kernel memory management](http://xinqiu.gitbooks.io/linux-insides-cn/content/MM/index.html) -* [PCI](http://en.wikipedia.org/wiki/Conventional_PCI) -* [e820](http://en.wikipedia.org/wiki/E820) -* [System Management BIOS](http://en.wikipedia.org/wiki/System_Management_BIOS) -* [System Management BIOS](http://en.wikipedia.org/wiki/System_Management_BIOS) -* [EFI](http://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface) -* [SMP](http://en.wikipedia.org/wiki/Symmetric_multiprocessing) -* [MultiProcessor Specification](http://www.intel.com/design/pentium/datashts/24201606.pdf) -* [BSS](http://en.wikipedia.org/wiki/.bss) -* [SMBIOS specification](http://www.dmtf.org/sites/default/files/standards/documents/DSP0134v2.5Final.pdf) -* [前一个章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html) diff --git a/Initialization/linux-initialization-6.md b/Initialization/linux-initialization-6.md index 5bc4110..e8609e4 100644 --- a/Initialization/linux-initialization-6.md +++ b/Initialization/linux-initialization-6.md @@ -1,28 +1,29 @@ -Kernel initialization. Part 6. -================================================================================ +内核初始化 第六部分 +=========================================================== -Architecture-specific initialization, again... -================================================================================ +仍旧是与系统架构有关的初始化 +=========================================================== -In the previous [part](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html) we saw architecture-specific (`x86_64` in our case) initialization stuff from the [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c) and finished on `x86_configure_nx` function which sets the `_PAGE_NX` flag depends on support of [NX bit](http://en.wikipedia.org/wiki/NX_bit). As I wrote before `setup_arch` function and `start_kernel` are very big, so in this and in the next part we will continue to learn about architecture-specific initialization process. The next function after `x86_configure_nx` is `parse_early_param`. This function is defined in the [init/main.c](https://github.com/torvalds/linux/blob/master/init/main.c) and as you can understand from its name, this function parses kernel command line and setups different services depends on the given parameters (all kernel command line parameters you can find are in the [Documentation/kernel-parameters.txt](https://github.com/torvalds/linux/blob/master/Documentation/kernel-parameters.txt)). You may remember how we setup `earlyprintk` in the earliest [part](http://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html). On the early stage we looked for kernel parameters and their value with the `cmdline_find_option` function and `__cmdline_find_option`, `__cmdline_find_option_bool` helpers from the [arch/x86/boot/cmdline.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/cmdline.c). There we're in the generic kernel part which does not depend on architecture and here we use another approach. If you are reading linux kernel source code, you already note calls like this: +在之前的[章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html)我们从 [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c)了解了特定于系统架构的初始化事务(在我们的例子中是 `x86_64` 架构),并且通过 `x86_configure_nx` 函数根据对[NX bit](http://en.wikipedia.org/wiki/NX_bit)的支持配置了 `_PAGE_NX` 标志位。正如我之前写的, `setup_arch` 函数和 `start_kernel` 都非常复杂,所以在这个和下个章节我们将继续学习关于系统架构初始化进程的内容。`x86_configure_nx` 函数的下面是 `parse_early_param` 函数。这个函数定义在 [init/main.c](https://github.com/torvalds/linux/blob/master/init/main.c) 中并且你可以从它的名字中了解到,这个函数解析内核命令行并且基于给定的参数创建不同的服务 (所有的内核命令行参数你都可以在 [Documentation/kernel-parameters.txt](https://github.com/torvalds/linux/blob/master/Documentation/kernel-parameters.txt) 找到)。 你可能记得在最前面的 [章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html) 我们是怎样创建 `earlyprintk`地。在前面我们用 [arch/x86/boot/cmdline.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/cmdline.c) 里面的 `cmdline_find_option` 和 `__cmdline_find_option`, `__cmdline_find_option_bool` 函数的帮助下寻找内核参数及其值。我们在通用内核部分不依赖于特定的系统架构,在这里我们使用另一种方法。 如果你正在阅读linux内核源代码,你可能注意到这样的调用: ```C early_param("gbpages", parse_direct_gbpages_on); ``` -`early_param` macro takes two parameters: +`early_param` 宏需要两个参数: -* command line parameter name; -* function which will be called if given parameter is passed. +* 命令行参数的名称 +* 如果给定的参数通过,函数将被调用 -and defined as: +函数定义如下: ```C #define early_param(str, fn) \ __setup_param(str, fn, fn, 1) ``` -in the [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h). As you can see `early_param` macro just makes call of the `__setup_param` macro: +这个定义可以在 [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h) 中可以找到. +正如你所看到的, `early_param` 宏只是调用了 `__setup_param` 宏: ```C #define __setup_param(str, unique_id, fn, early) \ @@ -34,7 +35,9 @@ in the [include/linux/init.h](https://github.com/torvalds/linux/blob/master/incl = { __setup_str_##unique_id, fn, early } ``` -This macro defines `__setup_str_*_id` variable (where `*` depends on given function name) and assigns it to the given command line parameter name. In the next line we can see definition of the `__setup_*` variable which type is `obs_kernel_param` and its initialization. `obs_kernel_param` structure defined as: +这个宏内部定义了 `__setup_str_*_id` 变量 (这里的 `*` 取决于给定的函数名称),然后把给定的命令行参数赋值给这个变量。在下一行中,我们可以看到定义了一个`obs_kernel_param` 类型的变量 `__setup_ *` 并对其进行初始化。 + +`obs_kernel_param` 结构体定义如下: ```C struct obs_kernel_param { @@ -44,13 +47,13 @@ struct obs_kernel_param { }; ``` -and contains three fields: +这个结构体包含三个字段: -* name of the kernel parameter; -* function which setups something depend on parameter; -* field determines is parameter early (1) or not (0). +* 内核参数的名称 +* 根据不同的参数,选取对应的处理函数 +* 决定参数是否为 early 的标记位 -Note that `__set_param` macro defines with `__section(.init.setup)` attribute. It means that all `__setup_str_*` will be placed in the `.init.setup` section, moreover, as we can see in the [include/asm-generic/vmlinux.lds.h](https://github.com/torvalds/linux/blob/master/include/asm-generic/vmlinux.lds.h), they will be placed between `__setup_start` and `__setup_end`: +注意 `__set_param` 宏定义有 `__section(.init.setup)` 属性。这意味着所有 `__setup_str_ *` 都将被放置在 `.init.setup` 区段中,此外正如我们在 [include/asm-generic/vmlinux.lds.h](https://github.com/torvalds/linux/blob/master/include/asm-generic/vmlinux.lds.h) 中看到的,`.init.setup` 区段被放置在 `__setup_start` 和 `__setup_end` 之间: ``` #define INIT_SETUP(initsetup_align) \ @@ -60,7 +63,7 @@ Note that `__set_param` macro defines with `__section(.init.setup)` attribute. I VMLINUX_SYMBOL(__setup_end) = .; ``` -Now we know how parameters are defined, let's back to the `parse_early_param` implementation: +现在我们知道了参数是怎样定义的,让我们一起回到 `parse_early_param` 的实现上来: ```C void __init parse_early_param(void) @@ -77,29 +80,31 @@ void __init parse_early_param(void) done = 1; } ``` - -The `parse_early_param` function defines two static variables. First `done` check that `parse_early_param` already called and the second is temporary storage for kernel command line. After this we copy `boot_command_line` to the temporary command line which we just defined and call the `parse_early_options` function from the same source code `main.c` file. `parse_early_options` calls the `parse_args` function from the [kernel/params.c](https://github.com/torvalds/linux/blob/master/) where `parse_args` parses given command line and calls `do_early_param` function. This [function](https://github.com/torvalds/linux/blob/master/init/main.c#L413) goes from the ` __setup_start` to `__setup_end`, and calls the function from the `obs_kernel_param` if a parameter is early. After this all services which are depend on early command line parameters were setup and the next call after the `parse_early_param` is `x86_report_nx`. As I wrote in the beginning of this part, we already set `NX-bit` with the `x86_configure_nx`. The next `x86_report_nx` function from the [arch/x86/mm/setup_nx.c](https://github.com/torvalds/linux/blob/master/arch/x86/mm/setup_nx.c) just prints information about the `NX`. Note that we call `x86_report_nx` not right after the `x86_configure_nx`, but after the call of the `parse_early_param`. The answer is simple: we call it after the `parse_early_param` because the kernel support `noexec` parameter: +`parse_early_param` 函数内部定义了两个静态变量。首先第一个变量 `done` 用来检查 `parse_early_param` 函数是否已经被调用过,第二个变量是用来临时存储内核命令行的。然后我们把 `boot_command_line` 的值赋值给刚刚定义的临时命令行变量中( `tmp_cmdline` ) 并且从相同的源代码文件 `main.c` 中调用 `parse_early_options` 函数。 `parse_early_options`函数从 [kernel/params.c](https://github.com/torvalds/linux/blob/master/) 中调用 `parse_args` 函数, `parse_args` 解析传入的命令行然后调用 `do_early_param` 函数。 [do_early_param](https://github.com/torvalds/linux/blob/master/init/main.c#L413) 函数 从 ` __setup_start` 循环到 `__setup_end` ,如果循环中 `obs_kernel_param` 实例中的 `early` 字段值为1 ,就调用 `obs_kernel_param` 中的第二个函数 `setup_func`。在这之后所有基于早期命令行参数的服务都已经被创建,在 `parse_early_param` 之后的下一个函数调用是 `x86_report_nx` 。 正如我在这章开头所写的,我们已经用 `x86_configure_nx` 函数配置了 `NX-bit` 位。接下来我们使用 [arch/x86/mm/setup_nx.c](https://github.com/torvalds/linux/blob/master/arch/x86/mm/setup_nx.c) 中的 `x86_report_nx`函数打印出关于 `NX` 的信息。注意`x86_report_nx` 函数不一定在 `x86_configure_nx` 函数之后调用,但是一定在 `parse_early_param` 之后调用。答案很简单: 因为内核支持 `noexec` 参数,所以我们一定在 `parse_early_param` 调用并且解析 `noexec` 参数之后才能调用 `x86_report_nx` : ``` noexec [X86] On X86-32 available only on PAE configured kernels. + //在X86-32架构上,仅在配置PAE的内核上可用。 noexec=on: enable non-executable mappings (default) + //noexec=on:开启非可执行文件的映射(默认) noexec=off: disable non-executable mappings + //noexec=off: 禁用非可执行文件的映射 ``` -We can see it in the booting time: +我们可以在启动的时候看到: ![NX](http://oi62.tinypic.com/swwxhy.jpg) -After this we can see call of the: +之后我们可以看到下面函数的调用: ```C memblock_x86_reserve_range_setup_data(); ``` -function. This function is defined in the same [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c) source code file and remaps memory for the `setup_data` and reserved memory block for the `setup_data` (more about `setup_data` you can read in the previous [part](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html) and about `ioremap` and `memblock` you can read in the [Linux kernel memory management](http://xinqiu.gitbooks.io/linux-insides-cn/content/MM/index.html)). +这个函数的定义也在 [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c) 中,然后这个函数为 `setup_data` 重新映射内存并保留内存块(你可以阅读之前的 [章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html) 了解关于 `setup_data` 的更多内容,你也可以在 [Linux kernel memory management](http://xinqiu.gitbooks.io/linux-insides-cn/content/MM/index.html) 中阅读到关于 `ioremap` and `memblock` 的更多内容)。 -In the next step we can see following conditional statement: +接下来我们来看看下面的条件语句: ```C if (acpi_mps_check()) { @@ -110,7 +115,7 @@ In the next step we can see following conditional statement: } ``` -The first `acpi_mps_check` function from the [arch/x86/kernel/acpi/boot.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/acpi/boot.c) depends on `CONFIG_X86_LOCAL_APIC` and `CONFIG_x86_MPPARSE` configuration options: +`acpi_mps_check` 函数来自于 [arch/x86/kernel/acpi/boot.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/acpi/boot.c) ,它的结果取决于 `CONFIG_X86_LOCAL_APIC` 和 `CONFIG_x86_MPPARSE` 配置选项: ```C int __init acpi_mps_check(void) @@ -128,12 +133,13 @@ int __init acpi_mps_check(void) } ``` -It checks the built-in `MPS` or [MultiProcessor Specification](http://en.wikipedia.org/wiki/MultiProcessor_Specification) table. If `CONFIG_X86_LOCAL_APIC` is set and `CONFIG_x86_MPPAARSE` is not set, `acpi_mps_check` prints warning message if the one of the command line options: `acpi=off`, `acpi=noirq` or `pci=noacpi` passed to the kernel. If `acpi_mps_check` returns `1` it means that we disable local [APIC](http://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller) and clear `X86_FEATURE_APIC` bit in the of the current CPU with the `setup_clear_cpu_cap` macro. (more about CPU mask you can read in the [CPU masks](https://xinqiu.gitbooks.io/linux-insides-cn/content/Concepts/linux-cpu-2.html)). +`acpi_mps_check` 函数检查内置的 `MPS` 又称 [多重处理器规范]((http://en.wikipedia.org/wiki/MultiProcessor_Specification)) 表。如果设置了 ` CONFIG_X86_LOCAL_APIC` 但未设置 `CONFIG_x86_MPPAARSE` ,而且传递给内核的命令行选项中有 `acpi=off`、`acpi=noirq` 或者 `pci=noacpi` 参数,那么`acpi_mps_check` 函数就会输出警告信息。如果 `acpi_mps_check` 返回了1,这就表示我们禁用了本地 [APIC](http://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller) +,而且 `setup_clear_cpu_cap` 宏清除了当前CPU中的 `X86_FEATURE_APIC` 位。(你可以阅读 [CPU masks](https://xinqiu.gitbooks.io/linux-insides-cn/content/Concepts/linux-cpu-2.html) 了解关于CPU mask的更多内容)。 -Early PCI dump --------------------------------------------------------------------------------- +早期的PCI转储 +-------------------------------------------------------------------------------- -In the next step we make a dump of the [PCI](http://en.wikipedia.org/wiki/Conventional_PCI) devices with the following code: +接下来我们通过下面的代码来转储 [PCI](http://en.wikipedia.org/wiki/Conventional_PCI) 设备: ```C #ifdef CONFIG_PCI @@ -142,13 +148,13 @@ In the next step we make a dump of the [PCI](http://en.wikipedia.org/wiki/Conven #endif ``` -`pci_early_dump_regs` variable defined in the [arch/x86/pci/common.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/common.c) and its value depends on the kernel command line parameter: `pci=earlydump`. We can find definition of this parameter in the [drivers/pci/pci.c](https://github.com/torvalds/linux/blob/master/arch): +变量 `pci_early_dump_regs` 定义在 [arch/x86/pci/common.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/common.c) 中,他的值取决于内核命令行参数:`pci=earlydump` 。我们可以在[drivers/pci/pci.c](https://github.com/torvalds/linux/blob/master/arch) 中看到这个参数的定义: ```C early_param("pci", pci_setup); ``` -`pci_setup` function gets the string after the `pci=` and analyzes it. This function calls `pcibios_setup` which defined as `__weak` in the [drivers/pci/pci.c](https://github.com/torvalds/linux/blob/master/arch) and every architecture defines the same function which overrides `__weak` analog. For example `x86_64` architecture-dependent version is in the [arch/x86/pci/common.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/common.c): +`pci_setup` 函数取出 `pci=` 之后的字符串,然后进行解析。这个函数调用 [drivers/pci/pci.c](https://github.com/torvalds/linux/blob/master/arch) 中用 `_weak` 修饰符定义的 `pcibios_setup` 函数,并且每种架构都重写了 `_weak` 修饰过的函数。 例如, `x86_64` 架构上的该函数版本在 [arch/x86/pci/common.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/common.c) 中: ```C char *__init pcibios_setup(char *str) { @@ -165,14 +171,14 @@ char *__init pcibios_setup(char *str) { } ``` -So, if `CONFIG_PCI` option is set and we passed `pci=earlydump` option to the kernel command line, next function which will be called - `early_dump_pci_devices` from the [arch/x86/pci/early.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/early.c). This function checks `noearly` pci parameter with: +如果我们设置了 `CONFIG_PCI` 选项,而且向内核命令行传递了 `pci=earlydump` 选项,那么 [arch/x86/pci/early.c](https://github.com/torvalds/linux/blob/master/arch/x86/pci/early.c) 中的 `early_dump_pci_devices` 函数将会被调用。这个函数像下面这样来检查pci参数 `noearly` : ```C if (!early_pci_allowed()) return; ``` -and returns if it was passed. Each PCI domain can host up to `256` buses and each bus hosts up to 32 devices. So, we goes in a loop: +如果条件不成立则返回。每个PCI域可以承载多达 `256` 条总线,并且每条总线可以承载多达32个设备。那么接下来我们进入下面的循环: ```C for (bus = 0; bus < 256; bus++) { @@ -186,15 +192,15 @@ for (bus = 0; bus < 256; bus++) { } ``` -and read the `pci` config with the `read_pci_config` function. +然后我们通过 `read_pci_config` 函数来读取 `pci` 配置。 -That's all. We will not go deep in the `pci` details, but will see more details in the special `Drivers/PCI` part. +这就是 pci 加载的全部过程了。我们在这里不会深入研究 `pci` 的细节,不过我们会在 `Drivers/PCI` 章节看到更多的细节。 -Finish with memory parsing --------------------------------------------------------------------------------- +内存解析的完成 +-------------------------------------------------------------------------------- -After the `early_dump_pci_devices`, there are a couple of function related with available memory and [e820](http://en.wikipedia.org/wiki/E820) which we collected in the [First steps in the kernel setup](http://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html) part: +在 `early_dump_pci_devices` 函数后面,有一些与可用内存和[e820](http://en.wikipedia.org/wiki/E820)相关的函数,其中 [e820](http://en.wikipedia.org/wiki/E820) 的相关信息我们在 [内核安装的第一步](http://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html) 章节中整理过。 ```C /* update the e820_saved too */ e820_reserve_setup_data(); @@ -208,14 +214,14 @@ After the `early_dump_pci_devices`, there are a couple of function related with early_reserve_e820_mpc_new(); ``` -Let's look on it. As you can see the first function is `e820_reserve_setup_data`. This function does almost the same as `memblock_x86_reserve_range_setup_data` which we saw above, but it also calls `e820_update_range` which adds new regions to the `e820map` with the given type which is `E820_RESERVED_KERN` in our case. The next function is `finish_e820_parsing` which sanitizes `e820map` with the `sanitize_e820_map` function. Besides this two functions we can see a couple of functions related to the [e820](http://en.wikipedia.org/wiki/E820). You can see it in the listing above. `e820_add_kernel_range` function takes the physical address of the kernel start and end: +让我们来一起看看上面的代码。正如你所看到的,第一个函数是 `e820_reserve_setup_data` 。这个函数和我们前面看到的 `memblock_x86_reserve_range_setup_data` 函数做的事情几乎是相同的,但是这个函数同时还会调用 `e820_update_range` 函数,向 `e820map` 中用给定的类型添加新的区域,在我们的例子中,使用的是 `E820_RESERVED_KERN` 类型。接下来的函数是 `finish_e820_parsing`,这个函数使用 `sanitize_e820_map` 函数对 `e820map` 进行清理。除了这两个函数之外,我们还可以看到一些与 [e820](http://en.wikipedia.org/wiki/E820) 有关的函数。你可以在上面的列表中看到这些函数。`e820_add_kernel_range` 函数需要内核开始和结束的物理地址: ```C u64 start = __pa_symbol(_text); u64 size = __pa_symbol(_end) - start; -``` +``` -checks that `.text` `.data` and `.bss` marked as `E820RAM` in the `e820map` and prints the warning message if not. The next function `trm_bios_range` update first 4096 bytes in `e820Map` as `E820_RESERVED` and sanitizes it again with the call of the `sanitize_e820_map`. After this we get the last page frame number with the call of the `e820_end_of_ram_pfn` function. Every memory page has an unique number - `Page frame number` and `e820_end_of_ram_pfn` function returns the maximum with the call of the `e820_end_pfn`: +函数会检查在 `e820map` 中被标记成 `E820RAM` 的 `.text` `.data` 和 `.bss` 区段,如果没有这些区段,那么就会输出错误信息。接下来的 `trm_bios_range` 函数把 `e820Map` 中的前4096个字节修改为 `E820_RESERVED` 并且再次调用函数 `sanitize_e820_map` 清理 `e820map`。在这之后我们使用 `e820_end_of_ram_pfn` 函数得到最后一个页帧的编号,每个内存页面都有一个唯一的编号 - `页帧号`, `e820_end_of_ram_pfn` 函数调用 `e820_end_pfn` 函数返回最大的页面帧号: ```C unsigned long __init e820_end_of_ram_pfn(void) @@ -224,7 +230,7 @@ unsigned long __init e820_end_of_ram_pfn(void) } ``` -where `e820_end_pfn` takes maximum page frame number on the certain architecture (`MAX_ARCH_PFN` is `0x400000000` for `x86_64`). In the `e820_end_pfn` we go through the all `e820` slots and check that `e820` entry has `E820_RAM` or `E820_PRAM` type because we calculate page frame numbers only for these types, gets the base address and end address of the page frame number for the current `e820` entry and makes some checks for these addresses: +`e820_end_pfn` 函数读取特定于系统架构的最大页帧号(对于 `x86_64` 架构来说 `MAX_ARCH_PFN` 是 `0x400000000` )。在 `e820_end_pfn` 函数中我们遍历整个 `e820` 槽,并且检查 `e820` 中是否有 `E820_RAM` 或者 `E820_PRAM` 类型条目,因为我们只能对这些类型计算页面帧号,然后我们得到当前 `e820` 页面帧的基地址和结束地址,同时对这些地址进行检查: ```C for (i = 0; i < e820.nr_map; i++) { @@ -258,7 +264,7 @@ for (i = 0; i < e820.nr_map; i++) { return last_pfn; ``` -After this we check that `last_pfn` which we got in the loop is not greater that maximum page frame number for the certain architecture (`x86_64` in our case), print information about last page frame number and return it. We can see the `last_pfn` in the `dmesg` output: +接下来我们检查在循环中得到的 `last_pfn`,`last_pfn` 不得大于特定于系统架构的最大页帧号(在我们的例子中是 `x86_64` 系统架构),然后输出关于最大页帧号的信息,并且返回 `last_pfn`。我们可以在 `dmesg` 的输出中看到 `last_pfn` : ``` ... @@ -266,7 +272,7 @@ After this we check that `last_pfn` which we got in the loop is not greater that ... ``` -After this, as we have calculated the biggest page frame number, we calculate `max_low_pfn` which is the biggest page frame number in the `low memory` or bellow first `4` gigabytes. If installed more than 4 gigabytes of RAM, `max_low_pfn` will be result of the `e820_end_of_low_ram_pfn` function which does the same `e820_end_of_ram_pfn` but with 4 gigabytes limit, in other way `max_low_pfn` will be the same as `max_pfn`: +在这之后,我们计算出了最大的页帧号,我们要计算 `max_low_pfn` ,这是 `低端内存` 或者低于第一个4GB中的最大页面帧。如果系统安装了超过4GB的内存RAM,`max_low_pfn` 将会是`e820_end_of_low_ram_pfn` 函数的结果,这个函数和 `e820_end_of_ram_pfn` 相似,但是有4GB限制,换句话说 `max_low_pfn` 和 `max_pfn` 的值是一样的: ```C if (max_pfn > (1UL<<(32 - PAGE_SHIFT))) @@ -277,19 +283,20 @@ else high_memory = (void *)__va(max_pfn * PAGE_SIZE - 1) + 1; ``` -Next we calculate `high_memory` (defines the upper bound on direct map memory) with `__va` macro which returns a virtual address by the given physical memory. +接下来我们通过 `__va` 宏计算 `高端内存` (有更高的内存直接映射上界)中的最大页帧号,并且这个宏会根据给定的物理内存返回一个虚拟地址。 -DMI scanning + +桌面管理接口 ------------------------------------------------------------------------------- -The next step after manipulations with different memory regions and `e820` slots is collecting information about computer. We will get all information with the [Desktop Management Interface](http://en.wikipedia.org/wiki/Desktop_Management_Interface) and following functions: +在处理完不同内存区域和 `e820` 槽之后,接下来就该收集计算机的相关信息了。我们将用下面的函数收集与 [桌面管理接口](http://en.wikipedia.org/wiki/Desktop_Management_Interface) 有关的所有信息: ```C dmi_scan_machine(); dmi_memdev_walk(); ``` -First is `dmi_scan_machine` defined in the [drivers/firmware/dmi_scan.c](https://github.com/torvalds/linux/blob/master/drivers/firmware/dmi_scan.c). This function goes through the [System Management BIOS](http://en.wikipedia.org/wiki/System_Management_BIOS) structures and extracts information. There are two ways specified to gain access to the `SMBIOS` table: get the pointer to the `SMBIOS` table from the [EFI](http://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface)'s configuration table and scanning the physical memory between `0xF0000` and `0x10000` addresses. Let's look on the second approach. `dmi_scan_machine` function remaps memory between `0xf0000` and `0x10000` with the `dmi_early_remap` which just expands to the `early_ioremap`: +首先是定义在 [drivers/firmware/dmi_scan.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/drivers/firmware/dmi_scan.c) 中的 `dmi_scan_machine` 函数。这个函数遍历 [System Management BIOS](http://en.wikipedia.org/wiki/System_Management_BIOS) 结构,并从中提取信息。这里有两种方法来访问 `SMBIOS` 表: 第一种是从 [EFI](http://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface) 的配置表获得指向 `SMBIOS` 表的指针;第二种是扫描 `0xF0000` 和 `0x10000` 地址之间的物理内存。让我们一起看看第二种方法。`dmi_scan_machine` 函数通过 `dmi_early_remap` 函数将 `0xf0000` 和 `0x10000` 之间的内存重新映射并追加到 `early_ioremap` 上: ```C void __init dmi_scan_machine(void) @@ -303,8 +310,7 @@ void __init dmi_scan_machine(void) if (p == NULL) goto error; ``` - -and iterates over all `DMI` header address and find search `_SM_` string: +然后迭代所有的 `DMI` 头部地址,并且查找 `_SM_` 字符串: ```C memset(buf, 0, 16); @@ -317,22 +323,21 @@ for (q = p; q < p + 0x10000; q += 16) { } memcpy(buf, buf + 16, 16); } -``` - -`_SM_` string must be between `000F0000h` and `0x000FFFFF`. Here we copy 16 bytes to the `buf` with `memcpy_fromio` which is the same `memcpy` and execute `dmi_smbios3_present` and `dmi_present` on the buffer. These functions check that first 4 bytes is `_SM_` string, get `SMBIOS` version and gets `_DMI_` attributes as `DMI` structure table length, table address and etc... After one of these functions finish, you will see the result of it in the `dmesg` output: +``` +`_SM_` 字符串一定在 `000F0000h` 和 `0x000FFFFF` 地址之间。在这里我们用 `memcpy_fromio` 函数向 `buf` 里面拷贝16个字节,这个函数和 `memcpy` 函数的作用是一样的。然后对这个缓冲区( `buf` ) 执行`dmi_smbios3_present` 和 `dmi_present` 函数。这些函数检查 `buf` 的前4个字节是否是 `__SM__` 字符串,并且获得 `SMBIOS` 的版本和 `_DMI_` 的属性例如 `_DMI_` 的结构表长度、结构表的地址等等... 在其中的一个函数完成之后,你就可以在 `dmesg` 的输出中看到它的运行结果: ``` [ 0.000000] SMBIOS 2.7 present. [ 0.000000] DMI: Gigabyte Technology Co., Ltd. Z97X-UD5H-BK/Z97X-UD5H-BK, BIOS F6 06/17/2014 ``` -In the end of the `dmi_scan_machine`, we unmap the previously remapped memory: +在 `dmi_scan_machine` 函数的最后,我们取消之前映射的内存: ```C dmi_early_unmap(p, 0x10000); ``` -The second function is - `dmi_memdev_walk`. As you can understand it goes over memory devices. Let's look on it: +第二个函数是 - `dmi_memdev_walk`。和你想的一样,这个函数遍历整个内存设备。让我们一起看看这个函数: ```C void __init dmi_memdev_walk(void) @@ -348,7 +353,7 @@ void __init dmi_memdev_walk(void) } ``` -It checks that `DMI` available (we got it in the previous function - `dmi_scan_machine`) and collects information about memory devices with `dmi_walk_early` and `dmi_alloc` which defined as: +这个函数检查 `DMI` 是否可用(我们之前在 `dmi_scan_machine` 函数中得到了这个结果,并且保存在 `dmi_available` 变量中),然后使用 `dmi_walk_early` 和 `dmi_alloc` 函数收集内存设备的有关信息,其中 `dmi_alloc` 的定义如下: ``` #ifdef CONFIG_DMI @@ -356,7 +361,7 @@ RESERVE_BRK(dmi_alloc, 65536); #endif ``` -`RESERVE_BRK` defined in the [arch/x86/include/asm/setup.h](http://en.wikipedia.org/wiki/Desktop_Management_Interface) and reserves space with given size in the `brk` section. +定义在 [arch/x86/include/asm/setup.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/setup.h) 中的 `RESERVE_BRK` 函数会在 `brk` 段中预留给定大小的空间: ------------------------- init_hypervisor_platform(); @@ -364,13 +369,13 @@ RESERVE_BRK(dmi_alloc, 65536); insert_resource(&iomem_resource, &code_resource); insert_resource(&iomem_resource, &data_resource); insert_resource(&iomem_resource, &bss_resource); - early_gart_iommu_check(); + early_gart_iommu_check(); -SMP config --------------------------------------------------------------------------------- +均衡多处理(SMP)的配置 +-------------------------------------------------------------------------------- -The next step is parsing of the [SMP](http://en.wikipedia.org/wiki/Symmetric_multiprocessing) configuration. We do it with the call of the `find_smp_config` function which just calls function: +接下来的一步是解析 [SMP](http://en.wikipedia.org/wiki/Symmetric_multiprocessing) 的配置信息。我们调用 `find_smp_config` 函数来完成这个任务,这个函数内部调用另一个函数: ```C static inline void find_smp_config(void) @@ -379,7 +384,7 @@ static inline void find_smp_config(void) } ``` -inside. `x86_init.mpparse.find_smp_config` is the `default_find_smp_config` function from the [arch/x86/kernel/mpparse.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/mpparse.c). In the `default_find_smp_config` function we are scanning a couple of memory regions for `SMP` config and return if they are found: +在函数的内部,`x86_init.mpparse.find_smp_config` 函数就是 [arch/x86/kernel/mpparse.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/mpparse.c) 中的 `default_find_smp_config` 函数。我们调用 `default_find_smp_config` 函数扫描内存中的一些区域来寻找 `SMP` 的配置信息,并在找到它们的时候返回: ```C if (smp_scan_config(0x0, 0x400) || @@ -388,14 +393,14 @@ if (smp_scan_config(0x0, 0x400) || return; ``` -First of all `smp_scan_config` function defines a couple of variables: +首先 `smp_scan_config` 函数内部定义了一些变量: ```C unsigned int *bp = phys_to_virt(base); struct mpf_intel *mpf; -``` +``` -First is virtual address of the memory region where we will scan `SMP` config, second is the pointer to the `mpf_intel` structure. Let's try to understand what is it `mpf_intel`. All information stores in the multiprocessor configuration data structure. `mpf_intel` presents this structure and looks: +第一个变量是我们用来扫描 `SMP` 配置的内存区域的虚拟地址;第二个变量是指向 `mpf_intel` 结构体的指针。让我们一起试着去理解 `mpf_intel` 是什么吧。所有的信息都存储在多处理器配置数据结构中。`mpf_intel` 就是这个结构,看下来像是下面这样: ```C struct mpf_intel { @@ -412,9 +417,9 @@ struct mpf_intel { }; ``` -As we can read in the documentation - one of the main functions of the system BIOS is to construct the MP floating pointer structure and the MP configuration table. And operating system must have access to this information about the multiprocessor configuration and `mpf_intel` stores the physical address (look at second parameter) of the multiprocessor configuration table. So, `smp_scan_config` going in a loop through the given memory range and tries to find `MP floating pointer structure` there. It checks that current byte points to the `SMP` signature, checks checksum, checks if `mpf->specification` is 1 or 4(it must be `1` or `4` by specification) in the loop: +正如我们在文档中看到的那样 - 系统 BIOS的主要功能之一就是创建MP浮点型指针结构和MP配置表。而且操作系统必须可以访问关于多处理器配置的有关信息, `mpf_intel` 中存储了多处理器配置表的物理地址(看结构体的第二个变量),然后,`smp_scan_config` 函数在指定的内存区域中循环查找 `MP floating pointer structure` 。这个函数还会检查当前字节是否指向 `SMP` 签名,然后检查签名的校验和,并且检查循环中的 `mpf->specification` 的值是1还是4(这个值只能是1或者是4): -```C +```C7 while (length > 0) { if ((*bp == SMP_MAGIC_IDENT) && (mpf->length == 1) && @@ -430,12 +435,13 @@ if ((*bp == SMP_MAGIC_IDENT) && } ``` -reserves given memory block if search is successful with `memblock_reserve` and reserves physical address of the multiprocessor configuration table. You can find documentation about this in the - [MultiProcessor Specification](http://www.intel.com/design/pentium/datashts/24201606.pdf). You can read More details in the special part about `SMP`. +如果搜索成功,就调用 `memblock_reserve` 函数保留一定的内存块,并且为多处理器配置表保留物理地址。你可以在 [MultiProcessor Specification](http://www.intel.com/design/pentium/datashts/24201606.pdf) 中找到相关的文档。你也可以在 `SMP` 的特定章节阅读更多细节。 -Additional early memory initialization routines +其他的早期内存初始化程序 -------------------------------------------------------------------------------- -In the next step of the `setup_arch` we can see the call of the `early_alloc_pgt_buf` function which allocates the page table buffer for early stage. The page table buffer will be placed in the `brk` area. Let's look on its implementation: +在 `setup_arch` 的下一步,我们可以看到 `early_alloc_pgt_buf` 函数的调用,这个函数在早期阶段分配页表缓冲区。页表缓冲区将被放置在 `brk` 区段中。让我们一起看看这个功能的实现: + ```C void __init early_alloc_pgt_buf(void) @@ -451,7 +457,7 @@ void __init early_alloc_pgt_buf(void) } ``` -First of all it get the size of the page table buffer, it will be `INIT_PGT_BUF_SIZE` which is `(6 * PAGE_SIZE)` in the current linux kernel 4.0. As we got the size of the page table buffer, we call `extend_brk` function with two parameters: size and align. As you can understand from its name, this function extends the `brk` area. As we can see in the linux kernel linker script `brk` is in memory right after the [BSS](http://en.wikipedia.org/wiki/.bss): +首先这个函数获得页表缓冲区的大小,它的值是 `INIT_PGT_BUF_SIZE` ,这个值在目前的linux 4.0 内核中是 `(6 * PAGE_SIZE)`。因为我们已经得到了页表缓冲区的大小,现在我们调用 `extend_brk` 函数并且传入两个参数: size和align。你可以从他们的名称中猜到,这个函数扩展 `brk` 区段。正如我们在linux内核链接脚本中看到的,`brk` 区段在内存中的位置恰好就在 [BSS](http://en.wikipedia.org/wiki/.bss) 区段后面: ```C . = ALIGN(PAGE_SIZE); @@ -463,11 +469,11 @@ First of all it get the size of the page table buffer, it will be `INIT_PGT_BUF_ } ``` -Or we can find it with `readelf` util: +我们也可以使用 `readelf` 工具来找到它: -![brk area](http://oi61.tinypic.com/71lkeu.jpg) +![brk area](http://oi61.tinypic.com/71lkeu.jpg) -After that we got physical address of the new `brk` with the `__pa` macro, we calculate the base address and the end of the page table buffer. In the next step as we got page table buffer, we reserve memory block for the brk area with the `reserve_brk` function: +之后我们用 `_pa` 宏得到了新的 `brk` 区段的物理地址,我们计算页表缓冲区的基地址和结束地址。因为我们之前已经创建好了页面缓冲区,所以现在我们使用 `reserve_brk` 函数为 `brk` 区段保留内存块: ```C static void __init reserve_brk(void) @@ -480,7 +486,7 @@ static void __init reserve_brk(void) } ``` -Note that in the end of the `reserve_brk`, we set `brk_start` to zero, because after this we will not allocate it anymore. The next step after reserving memory block for the `brk`, we need to unmap out-of-range memory areas in the kernel mapping with the `cleanup_highmap` function. Remember that kernel mapping is `__START_KERNEL_map` and `_end - _text` or `level2_kernel_pgt` maps the kernel `_text`, `data` and `bss`. In the start of the `clean_high_map` we define these parameters: +注意在 `reserve_brk` 的最后,我们把 `_brk_start` 赋值为0,因为在这之后我们不会再为 `brk` 分配内存了,我们需要使用 `cleanup_highmap` 函数来释放内核映射中越界的内存区域。请记住内核映射是 `__START_KERNEL_map` 和 `_end - _text` 或者 `level2_kernel_pgt` 对内核 `_text`、`data` 和 `bss` 区段的映射。在 `clean_high_map` 的开始部分我们定义下面这些参数: ```C unsigned long vaddr = __START_KERNEL_map; @@ -489,7 +495,7 @@ pmd_t *pmd = level2_kernel_pgt; pmd_t *last_pmd = pmd + PTRS_PER_PMD; ``` -Now, as we defined start and end of the kernel mapping, we go in the loop through the all kernel page middle directory entries and clean entries which are not between `_text` and `end`: +现在,因为我们已经定义了内核映射的开始和结束位置,所以我们在循环中遍历所有内核页中间目录条目, 并且清除不在 `_text` 和 `end` 区段中的条目: ```C for (; pmd < last_pmd; pmd++, vaddr += PMD_SIZE) { @@ -500,7 +506,7 @@ for (; pmd < last_pmd; pmd++, vaddr += PMD_SIZE) { } ``` -After this we set the limit for the `memblock` allocation with the `memblock_set_current_limit` function (read more about `memblock` you can in the [Linux kernel memory management Part 2](https://github.com/MintCN/linux-insides-zh/blob/master/MM/linux-mm-2.md)), it will be `ISA_END_ADDRESS` or `0x100000` and fill the `memblock` information according to `e820` with the call of the `memblock_x86_fill` function. You can see the result of this function in the kernel initialization time: +在这之后,我们使用 `memblock_set_current_limit` (你可以在[linux 内存管理 第二章节](https://github.com/MintCN/linux-insides-zh/blob/master/MM/linux-mm-2.md) 阅读关于 `memblock` 的更多内容) 函数来为 `memblock` 分配内存设置一个界限,这个界限可以是 `ISA_END_ADDRESS` 或者 `0x100000` ,然后调用 `memblock_x86_fill` 函数根据 `e820` 来填充 `memblock` 相关信息。你可以在内核初始化的时候看到这个函数运行的结果: ``` MEMBLOCK configuration: @@ -515,20 +521,20 @@ MEMBLOCK configuration: reserved[0x2] [0x0000007ec89000-0x0000007fffffff], 0x1377000 bytes flags: 0x0 ``` -The rest functions after the `memblock_x86_fill` are: `early_reserve_e820_mpc_new` allocates additional slots in the `e820map` for MultiProcessor Specification table, `reserve_real_mode` - reserves low memory from `0x0` to 1 megabyte for the trampoline to the real mode (for rebooting, etc.), `trim_platform_memory_ranges` - trims certain memory regions started from `0x20050000`, `0x20110000`, etc. these regions must be excluded because [Sandy Bridge](http://en.wikipedia.org/wiki/Sandy_Bridge) has problems with these regions, `trim_low_memory_range` reserves the first 4 kilobyte page in `memblock`, `init_mem_mapping` function reconstructs direct memory mapping and setups the direct mapping of the physical memory at `PAGE_OFFSET`, `early_trap_pf_init` setups `#PF` handler (we will look on it in the chapter about interrupts) and `setup_real_mode` function setups trampoline to the [real mode](http://en.wikipedia.org/wiki/Real_mode) code. +除了 `memblock_x86_fill` 之外的其他函数还有: `early_reserve_e820_mpc_new` 函数在 `e820map` 中为多处理器规格表分配额外的槽, `reserve_real_mode` - 用于保留从 `0x0` 到1M的低端内存用作到实模式的跳板(用于重启等...),`trim_platform_memory_ranges` 函数用于清除掉以 `0x20050000`, `0x20110000` 等地址开头的内存空间。这些内存区域必须被排除在外,因为 [Sandy Bridge](http://en.wikipedia.org/wiki/Sandy_Bridge) 会在这些内存区域出现一些问题, `trim_low_memory_range` 函数用于保留 `memblock` 中的前4KB页面,`init_mem_mapping` 函数用于在 `PAGE_OFFSET` 处重建物理内存的直接映射, `early_trap_pf_init` 函数用于建立 `#PF` 处理函数(我们将会在有关中断的章节看到它), `setup_real_mode` 函数用于建立到 [实模式](http://en.wikipedia.org/wiki/Real_mode) 代码的跳板。 -That's all. You can note that this part will not cover all functions which are in the `setup_arch` (like `early_gart_iommu_check`, [mtrr](http://en.wikipedia.org/wiki/Memory_type_range_register) initialization, etc.). As I already wrote many times, `setup_arch` is big, and linux kernel is big. That's why I can't cover every line in the linux kernel. I don't think that we missed something important, but you can say something like: each line of code is important. Yes, it's true, but I missed them anyway, because I think that it is not realistic to cover full linux kernel. Anyway we will often return to the idea that we have already seen, and if something is unfamiliar, we will cover this theme. +这就是本章的全部内容了。您可能注意到这部分并没有包括 `setup_arch` 中的所有函数 (如 `early_gart_iommu_check`、[mtrr](http://en.wikipedia.org/wiki/Memory_type_range_register) 的初始化函数等...)。正如我已经说了很多次的, `setup_arch` 函数很复杂,linux内核也很复杂。这就是为什么我不能包括linux内核中的每一行代码。我认为我们并没有错过重要的东西, 但是你可能会说: 每行代码都很重要。是的, 这没错, 但不管怎样我略过了他们, 因为我认为对于整个linux内核面面俱到是不现实的。无论如何, 我们会经常复习所学的内容, 如果有什么不熟悉的内容, 我们将会深入研究这些内容。 -Conclusion --------------------------------------------------------------------------------- +结束语 +-------------------------------------------------------------------------------- -It is the end of the sixth part about linux kernel initialization process. In this part we continued to dive in the `setup_arch` function again and it was long part, but we are not finished with it. Yes, `setup_arch` is big, hope that next part will be the last part about this function. +这里是linux 内核初始化进程第六章节的结尾。在这一章节中,我们再次深入研究了 `setup_arch` 函数,然而这是个很长的部分,我们目前还没有学习完。的确, `setup_arch`很复杂,希望下个章节将会是这个函数的最后一个部分。。 -If you have any questions or suggestions write me a comment or ping me at [twitter](https://twitter.com/0xAX). +如果你有任何的疑问或者建议,你可以留言,也可以直接发消息给我[twitter](https://twitter.com/0xAX)。 -**Please note that English is not my first language, And I am really sorry for any inconvenience. If you find any mistakes please send me PR to [linux-insides](https://github.com/0xAX/linux-insides).** +**很抱歉,英语并不是我的母语,非常抱歉给您阅读带来不便,如果你发现文中描述有任何问题,请提交一个 PR 到 [linux-insides](https://github.com/MintCN/linux-insides-zh).** -Links +链接 -------------------------------------------------------------------------------- * [MultiProcessor Specification](http://en.wikipedia.org/wiki/MultiProcessor_Specification) @@ -546,4 +552,4 @@ Links * [MultiProcessor Specification](http://www.intel.com/design/pentium/datashts/24201606.pdf) * [BSS](http://en.wikipedia.org/wiki/.bss) * [SMBIOS specification](http://www.dmtf.org/sites/default/files/standards/documents/DSP0134v2.5Final.pdf) -* [Previous part](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html) +* [前一个章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-5.html) From b8ebbf9b950b58e06b0d6fa4ab4afa8325545278 Mon Sep 17 00:00:00 2001 From: kele1997 <2363618563@qq.com> Date: Fri, 30 Mar 2018 22:48:34 +0800 Subject: [PATCH 12/14] udpate readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74de413..4c61852 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ |├ [2.3](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-3.md)|[@dontpanic92](https://github.com/dontpanic92)|更新至[44017507](https://github.com/0xAX/linux-insides/commit/4401750766f7150dcd16f579026f5554541a6ab9)| |├ [2.4](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-4.md)|[@bjwrkj](https://github.com/bjwrkj)|已完成| |├ [2.5](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-5.md)|[@NeoCui](https://github.com/NeoCui)|更新至[cf32dc6c](https://github.com/0xAX/linux-insides/commit/cf32dc6c81abce567af330c480afc3d58678443d)| -|├ [2.6](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-6.md)|[@kele1997](https://github.com/kele1997)|正在进行| +|├ [2.6](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-6.md)|[@kele1997](https://github.com/kele1997)|更新至[e896e56](https://github.com/0xAX/linux-insides/commit/e896e56c867876397ef78da58d5e2a31b2e690b6)| |├ [2.7](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-7.md)||未开始| |├ [2.8](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-8.md)||未开始| |├ [2.9](https://github.com/MintCN/linux-insides-zh/blob/master/Initialization/linux-initialization-9.md)||未开始| From 446f311f4a4c768f933cd5e321c5dc9a66113e2c Mon Sep 17 00:00:00 2001 From: narcijie Date: Tue, 10 Apr 2018 22:45:40 +0800 Subject: [PATCH 13/14] Update readme.MD for claiming chapter 3.2 claim chapter 3.2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74de413..adcf5f8 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ | 3. [Interrupts](https://github.com/MintCN/linux-insides-zh/tree/master/Interrupts)||正在进行| |├ [3.0](https://github.com/MintCN/linux-insides-zh/blob/master/Interrupts/README.md)|[@littleneko](https://github.com/littleneko)|更新至[57279321](https://github.com/0xAX/linux-insides/commit/5727932167a2ff6a1e647081c85d081d4ed8b508)| |├ [3.1](https://github.com/MintCN/linux-insides-zh/blob/master/Interrupts/linux-interrupts-1.md)||未开始| -|├ [3.2](https://github.com/MintCN/linux-insides-zh/blob/master/Interrupts/linux-interrupts-2.md)||未开始| +|├ [3.2](https://github.com/MintCN/linux-insides-zh/blob/master/Interrupts/linux-interrupts-2.md)|[@narcijie](https://github.com/narcijie)|正在进行| |├ [3.3](https://github.com/MintCN/linux-insides-zh/blob/master/Interrupts/linux-interrupts-3.md)||未开始| |├ [3.4](https://github.com/MintCN/linux-insides-zh/blob/master/Interrupts/linux-interrupts-4.md)||未开始| |├ [3.5](https://github.com/MintCN/linux-insides-zh/blob/master/Interrupts/linux-interrupts-5.md)||未开始| From 79472f14e9b4b396e5d8835ac0f3780adb658ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=87=86=E5=A4=87=E6=89=BE=E5=B7=A5=E4=BD=9C=E4=B8=AD?= Date: Tue, 10 Apr 2018 22:53:18 +0800 Subject: [PATCH 14/14] Update url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eccf974..fa3eb48 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ | 章节|译者|翻译进度| | ------------- |:-------------:| -----:| | 1. [Booting](https://github.com/MintCN/linux-insides-zh/tree/master/Booting)||正在进行| -|├ [1.0](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/README.md)|[@xinqiu](https://github.com/xinqiu)|更新至[28a39fe6](https://github.com/0xAX/linux-insides/commit/28a39fe6653e780641e80ab6e37c79ffafca07b0#diff-2e735523a4c3de2e72adcedac13682c7)| +|├ [1.0](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/README.md)|[@xinqiu](https://github.com/xinqiu)|更新至[527b2b8](https://github.com/0xAX/linux-insides/commit/527b2b8921c3d9c043bd914c5990d6a991e3035b)| |├ [1.1](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-1.md)|[@hailincai](https://github.com/hailincai)|已完成| |├ [1.2](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-2.md)|[@hailincai](https://github.com/hailincai)|已完成| |├ [1.3](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md)|[@hailincai](https://github.com/hailincai)|已完成|