From e52a6e0ae75361c9b31525e47df312bbe4ab0de2 Mon Sep 17 00:00:00 2001 From: Ezio Date: Wed, 9 Dec 2015 10:21:05 +0800 Subject: [PATCH 01/90] translated --- DataStructures/dlist.md | 85 +++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/DataStructures/dlist.md b/DataStructures/dlist.md index 4b4a408..bf6996c 100644 --- a/DataStructures/dlist.md +++ b/DataStructures/dlist.md @@ -1,12 +1,12 @@ -Linux内核中的数据结构 +Linux 内核里的数据结构——双向链表 ================================================================================ 双向链表 -------------------------------------------------------------------------------- -Linux kernel provides its own doubly linked list implementation which you can find in the [include/linux/list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h). We will start `Data Structures in the Linux kernel` from the doubly linked list data structure. Why? Because it is very popular in the kernel, just try to [search](http://lxr.free-electrons.com/ident?i=list_head) +Linux 内核自己实现了双向链表,可以在[include/linux/list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h)找到定义。我们将会从双向链表数据结构开始`内核的数据结构`。为什么?因为它在内核里使用的很广泛,你只需要在[free-electrons.com](http://lxr.free-electrons.com/ident?i=list_head) 检索一下就知道了。 -First of all let's look on the main structure: +首先让我们看一下在[include/linux/types.h](https://github.com/torvalds/linux/blob/master/include/linux/types.h) 里的主结构体: ```C struct list_head { @@ -14,7 +14,7 @@ struct list_head { }; ``` -You can note that it is different from many lists implementations which you have seen. For example this doubly linked list structure from the [glib](http://www.gnu.org/software/libc/): +你可能注意到这和你以前见过的双向链表的实现方法是不同的。举个例子来说,在[glib](http://www.gnu.org/software/libc/) 库里是这样实现的: ```C struct GList { @@ -24,9 +24,9 @@ struct GList { }; ``` -Usually a linked list structure contains a pointer to the item. Linux kernel implementation of the list does not. So the main question is - `where does the list store the data?`. The actual implementation of lists in the kernel is - `Intrusive list`. An intrusive linked list does not contain data in its nodes - A node just contains pointers to the next and previous node and list nodes part of the data that are added to the list. This makes the data structure generic, so it does not care about entry data type anymore. +通常来说一个链表会包含一个指向某个项目的指针。但是内核的实现并没有这样做。所以问题来了:`链表在哪里保存数据呢?`。实际上内核里实现的链表实际上是`侵入式链表`。侵入式链表并不在节点内保存数据-节点仅仅包含指向前后节点的指针,然后把数据是附加到链表的。这就使得这个数据结构是通用的,使用起来就不需要考虑节点数据的类型了。 -For example: +比如: ```C struct nmi_desc { @@ -35,13 +35,13 @@ struct nmi_desc { }; ``` -Let's look at some examples to understand how `list_head` is used in the kernel. As I already wrote about, there are many, really many different places where lists are used in the kernel. Let's look for example in miscellaneous character drivers. Misc character drivers API from the [drivers/char/misc.c](https://github.com/torvalds/linux/blob/master/drivers/char/misc.c) is used for writing small drivers for handling simple hardware or virtual devices. This drivers share major number: +让我们看几个例子来理解一下在内核里是如何使用`list_head` 的。如上所述,在内核里有实在很多不同的地方用到了链表。我们来看一个在杂项字符驱动里面的使用的例子。在 [drivers/char/misc.c](https://github.com/torvalds/linux/blob/master/drivers/char/misc.c) 的杂项字符驱动API 被用来编写处理小型硬件和虚拟设备的小驱动。这些驱动共享相同的主设备号: ```C #define MISC_MAJOR 10 ``` -but have their own minor number. For example you can see it with: +但是都有各自不同的次设备号。比如: ``` ls -l /dev | grep 10 @@ -67,7 +67,7 @@ crw------- 1 root root 10, 63 Mar 21 12:01 vga_arbiter crw------- 1 root root 10, 137 Mar 21 12:01 vhci ``` -Now let's have a close look at how lists are used in the misc device drivers. First of all let's look on `miscdevice` structure: +现在让我们看看它是如何使用链表的。首先看一下结构体`miscdevice`: ```C struct miscdevice @@ -83,32 +83,32 @@ struct miscdevice }; ``` -We can see the fourth field in the `miscdevice` structure - `list` which is a list of registered devices. In the beginning of the source code file we can see the definition of misc_list: +可以看到结构体的第四个变量`list` 是所有注册过的设备的链表。在源代码文件的开始可以看到这个链表的定义: ```C static LIST_HEAD(misc_list); ``` -which expands to definition of the variables with `list_head` type: +它实际上是对用`list_head` 类型定义的变量的扩展。 ```C #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) ``` -and initializes it with the `LIST_HEAD_INIT` macro which set previous and next entries: +然后使用宏`LIST_HEAD_INIT` 进行初始化,这会使用变量`name` 的地址来填充`prev`和`next` 结构体的两个变量。 ```C #define LIST_HEAD_INIT(name) { &(name), &(name) } ``` -Now let's look on the `misc_register` function which registers a miscellaneous device. At the start it initializes `miscdevice->list` with the `INIT_LIST_HEAD` function: +现在来看看注册杂项设备的函数`misc_register`。它在开始就用 `INIT_LIST_HEAD` 初始化了`miscdevice->list`。 ```C INIT_LIST_HEAD(&misc->list); ``` -which does the same as the `LIST_HEAD_INIT` macro: +作用和宏`LIST_HEAD_INIT`一样。 ```C static inline void INIT_LIST_HEAD(struct list_head *list) @@ -118,13 +118,14 @@ static inline void INIT_LIST_HEAD(struct list_head *list) } ``` -In the next step after device created with the `device_create` function we add it to the miscellaneous devices list with: +在函数`device_create` 创建了设备后我们就用下面的语句将设备添加到设备链表: ``` list_add(&misc->list, &misc_list); ``` -Kernel `list.h` provides this API for the addition of new entry to the list. Let's look on it's implementation: +内核文件`list.h` 提供了项链表添加新项的API 接口。我们来看看它的实现: + ```C static inline void list_add(struct list_head *new, struct list_head *head) @@ -133,13 +134,13 @@ static inline void list_add(struct list_head *new, struct list_head *head) } ``` -It just calls internal function `__list_add` with the 3 given parameters: +实际上就是使用3个指定的参数来调用了内部函数`__list_add`: -* new - new entry; -* head - list head after which the new item will be inserted -* head->next - next item after list head. +* new - 新项。 +* head - 新项将会被添加到`head`之前. +* head->next - `head` 之后的项。 -Implementation of the `__list_add` is pretty simple: +`__list_add`的实现非常简单: ```C static inline void __list_add(struct list_head *new, @@ -153,35 +154,35 @@ static inline void __list_add(struct list_head *new, } ``` -Here we set new item between `prev` and `next`. So `misc` list which we defined at the start with the `LIST_HEAD_INIT` macro will contain previous and next pointers to the `miscdevice->list`. +我们会在`prev`和`next` 之间添加一个新项。所以我们用宏`LIST_HEAD_INIT`定义的`misc` 链表会包含指向`miscdevice->list` 的向前指针和向后指针。 -There is still one question: how to get list's entry. There is a special macro: +这里有一个问题:如何得到列表的内容呢?这里有一个特殊的宏: ```C #define list_entry(ptr, type, member) \ container_of(ptr, type, member) ``` -which gets three parameters: +使用了三个参数: -* ptr - the structure list_head pointer; -* type - structure type; -* member - the name of the list_head within the structure; +* ptr - 指向链表头的指针; +* type - 结构体类型; +* member - 在结构体内类型为`list_head` 的变量的名字; -For example: +比如说: ```C const struct miscdevice *p = list_entry(v, struct miscdevice, list) ``` -After this we can access to any `miscdevice` field with `p->minor` or `p->name` and etc... Let's look on the `list_entry` implementation: - +然后我们就可以使用`p->minor` 或者 `p->name`来访问`miscdevice`。让我们来看看`list_entry` 的实现: + ```C #define list_entry(ptr, type, member) \ container_of(ptr, type, member) ``` -As we can see it just calls `container_of` macro with the same arguments. At first sight, the `container_of` looks strange: +如我们所见,它仅仅使用相同的参数调用了宏`container_of`。初看这个宏挺奇怪的: ```C #define container_of(ptr, type, member) ({ \ @@ -189,9 +190,9 @@ As we can see it just calls `container_of` macro with the same arguments. At fir (type *)( (char *)__mptr - offsetof(type,member) );}) ``` -First of all you can note that it consists of two expressions in curly brackets. Compiler will evaluate the whole block in the curly braces and use the value of the last expression. +首先你可以注意到花括号内包含两个表达式。编译器会执行花括号内的全部语句,然后返回最后的表达式的值。 -For example: +举个例子来说: ``` #include @@ -203,9 +204,9 @@ int main() { } ``` -will print `2`. +最终会打印`2` -The next point is `typeof`, it's simple. As you can understand from its name, it just returns the type of the given variable. When I first saw the implementation of the `container_of` macro, the strangest thing for me was the zero in the `((type *)0)` expression. Actually this pointer magic calculates the offset of the given field from the address of the structure, but as we have `0` here, it will be just a zero offset alongwith the field width. Let's look at a simple example: +下一点就是`typeof`,它也很简单。就如你从名字所理解的,它仅仅返回了给定变量的类型。当我第一次看到宏`container_of`的实现时,让我觉得最奇怪的就是`container_of`中的0.实际上这个指针巧妙的计算了从结构体特定变量的偏移,这里的`0`刚好就是位宽里的零偏移。让我们看一个简单的例子: ```C #include @@ -213,7 +214,7 @@ The next point is `typeof`, it's simple. As you can understand from its name, it struct s { int field1; char field2; - char field3; + char field3; }; int main() { @@ -222,17 +223,17 @@ int main() { } ``` -will print `0x5`. +结果显示`0x5`。 -The next offsetof macro calculates offset from the beginning of the structure to the given structure's field. Its implementation is very similar to the previous code: +下一个宏`offsetof` 会计算从结构体的某个变量的相对于结构体起始地址的偏移。它的实现和上面类似: ```C #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) ``` -Let's summarize all about `container_of` macro. `container_of` macro returns address of the structure by the given address of the structure's field with `list_head` type, the name of the structure field with `list_head` type and type of the container structure. At the first line this macro declares the `__mptr` pointer which points to the field of the structure that `ptr` points to and assigns `ptr` to it. Now `ptr` and `__mptr` point to the same address. Technically we don't need this line but its useful for type checking. First line ensures that that given structure (`type` parameter) has a member called `member`. In the second line it calculates offset of the field from the structure with the `offsetof` macro and subtracts it from the structure address. That's all. +现在我们来总结一下宏`container_of`。只需要知道结构体里面类型为`list_head` 的变量的名字和结构体容器的类型,它可以通过结构体的变量`list_head`获得结构体的起始地址。在宏定义的第一行,声明了一个指向结构体成员变量`ptr`的指针`__mptr`,并且把`ptr` 的地址赋给它。现在`ptr` 和`__mptr` 指向了同一个地址。从技术上讲我们并不需要这一行,但是它可以方便的进行类型检查。第一行保证了特定的结构体(参数`type`)包含成员变量`member`。第二行代码会用宏`offsetof`计算成员变量相对于结构体起始地址的偏移,然后从结构体的地址减去这个偏移,最后就得到了结构体。 -Of course `list_add` and `list_entry` is not the only functions which `` provides. Implementation of the doubly linked list provides the following API: +当然了`list_add` 和 `list_entry`不是``提供的唯一功能。双向链表的实现还提供了如下API: * list_add * list_add_tail @@ -243,5 +244,7 @@ Of course `list_add` and `list_entry` is not the only functions which ` Date: Fri, 26 Feb 2016 21:03:59 +0800 Subject: [PATCH 02/90] Paging Proofread --- Theory/Paging.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Theory/Paging.md b/Theory/Paging.md index 4fc4b43..60df812 100644 --- a/Theory/Paging.md +++ b/Theory/Paging.md @@ -4,17 +4,17 @@ 简介 -------------------------------------------------------------------------------- -在 Linux 内核启动过程中的[第五部分](http://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-5.html),我们学到了内核在最早的阶段做的工作。在接下来的步骤中,在我们明白内核如何运行第一个 init 进程,内核初始化不同的事,比如 `initrd` 加载, lockdep 初始化,以及许多许多其他的事。 +在 Linux 内核启动过程中的[第五部分](http://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-5.html),我们学到了内核在启动的最早阶段都做了哪些工作。接下来,在我们明白内核如何运行第一个 init 进程之前,内核初始化其他部分,比如加载 `initrd` ,初始化 lockdep ,以及许多许多其他的工作。 是的,那将有很多不同的事,但是还有更多更多更多关于**内存**的工作。 -在我看来,一般而言,内存管理是 Linux 内核和系统编程最复杂的部分之一。这就是为什么在我们处理内核初始化事情之前,我们需要了解分页。 +在我看来,一般而言,内存管理是 Linux 内核和系统编程最复杂的部分之一。这就是为什么在我们学习内核初始化过程之前,需要了解分页。 -分页是将线性地址转换为物理地址的机制。如果我们已经读过了这本书之前的部分,你可能记得我们在实模式下有分段机制,当时物理地址是由左移四位段寄存器加上偏移算出来的。我们也看了保护模式下的分段机制,其中我们使用描述符表和从描述符得到的基地址加上偏移得到物理地址。由于我们在 64 位模式,我们将看分页机制。 +分页是将线性地址转换为物理地址的机制。如果我们已经读过了这本书之前的部分,你可能记得我们在实模式下有分段机制,当时物理地址是由左移四位段寄存器加上偏移算出来的。我们也看了保护模式下的分段机制,其中我们使用描述符表得到描述符,进而得到基地址,然后加上偏移地址就获得了实际物理地址。由于我们在 64 位模式,我们将看分页机制。 正如 Intel 手册中说的: -> 分页机制提供一种机制,为了实现一种常见的页需求,虚拟内存系统,其中一个程序执行环境中的段将要按照需求被映射到物理地址。 +> 分页机制提供一种机制,为了实现常见的按需分页,比如虚拟内存系统就是将一个程序执行环境中的段按照需求被映射到物理地址。 所以... 在这个帖子中我将尝试解释分页背后的理论。当然它将与64位版本的 Linux 内核关系密切,但是我们将不会深入太多细节(至少在这个帖子里面)。 @@ -52,7 +52,7 @@ wrmsr 分页数据结构 -------------------------------------------------------------------------------- -分页将线性地址分为固定尺寸的页。页会被映射进入物理地址空间或外部存储设备。这个固定尺寸在 `x86_64` 内核中是 `4096` 字节。为了将线性地址转换位物理地址,特殊的结构会被用到。每个结构都是 `4096` 字节并包含 `512` 项(这只为 `PAE` 和 `IA32_EFER.LME` 模式)。分页结构是层次级的, Linux 内核在 `x86_64` 框架中使用4层的分层机制。CPU使用一部分线性地址去确定另一个分页结构中的项,这个分页结构可能在最低层,物理内存区域(页框),在这个区域的物理地址(页偏移)。最高层的分页结构的地址存储在 `cr3` 寄存器中。我们已经从 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 这个文件中已经看到了。 +分页将线性地址分为固定尺寸的页。页会被映射进入物理地址空间或外部存储设备。这个固定尺寸在 `x86_64` 内核中是 `4096` 字节。为了将线性地址转换位物理地址,需要使用到一些特殊的数据结构。每个结构都是 `4096` 字节并包含 `512` 项(这只为 `PAE` 和 `IA32_EFER.LME` 模式)。分页结构是层次级的, Linux 内核在 `x86_64` 框架中使用4层的分层机制。CPU使用一部分线性地址去确定另一个分页结构中的项,这个分页结构可能在最低层,物理内存区域(页框),在这个区域的物理地址(页偏移)。最高层的分页结构的地址存储在 `cr3` 寄存器中。我们已经从 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 这个文件中已经看到了。 ```assembly leal pgtable(%ebx), %eax @@ -86,7 +86,7 @@ movl %eax, %cr3 线性地址转换过程如下所示: -* 一个给定的线性地址到达 [MMU](http://en.wikipedia.org/wiki/Memory_management_unit) 而不是存储器总线; +* 一个给定的线性地址传递给 [MMU](http://en.wikipedia.org/wiki/Memory_management_unit) 而不是存储器总线; * 64位线性地址分为很多部分。只有低 48 位是有意义的,它意味着 `2^48` 或 256TB 的线性地址空间在任意给定时间内都可以被访问; * `cr3` 寄存器存储这个最高层分页数据结构的地址; * 给定的线性地址中的第 39 位到第 47 位存储一个第 4 级分页结构的索引,第 30 位到第 38 位存储一个第3级分页结构的索引,第 29 位到第 21 位存储一个第 2 级分页结构的索引,第 12 位到第 20 位存储一个第 1 级分页结构的索引,第 0 位到第 11 位提供物理页的字节偏移; @@ -133,12 +133,12 @@ Linux 内核中的分页结构 就如我们已经看到的那样, `x86_64`Linux 内核使用4级页表。它们的名字是: -* 页全局目录 -* 页上层目录 -* 页中间目录 +* 全局页目录 +* 上层页目录 +* 中间页目录 * 页表项 -在你已经编译和安装 Linux 内核,你可以看到存储内核中使用的函数的虚拟地址的 `System.map` 文件。例如: +在你已经编译和安装 Linux 内核之后,你可以看到保存了内核函数的虚拟地址的文件 `System.map`。例如: ``` $ grep "start_kernel" System.map @@ -193,15 +193,15 @@ ffffffffff600000 - ffffffffffdfffff (=8 MB) vsyscalls ffffffffffe00000 - ffffffffffffffff (=2 MB) unused hole ``` -这里我们可以看到用户空间,内核空间和非标准空间的内存映射。用户空间的内存映射很简单。让我们来更近地查看内核空间。我们可以看到它始于为管理程序 (hypervisor) 保留的防卫空洞 (guard hole) 。我们可以在 [arch/x86/include/asm/page_64_types.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/page_64_types.h) 这个文件中看到防卫空洞的概念! +这里我们可以看到用户空间,内核空间和非标准空间的内存映射。用户空间的内存映射很简单。让我们来更近地查看内核空间。我们可以看到它始于为管理程序 (hypervisor) 保留的防御空洞 (guard hole) 。我们可以在 [arch/x86/include/asm/page_64_types.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/page_64_types.h) 这个文件中看到防御空洞的概念! ```C #define __PAGE_OFFSET _AC(0xffff880000000000, UL) ``` -以前防卫空洞和 `__PAGE_OFFSET` 是从 `0xffff800000000000` 到 `0xffff80ffffffffff` ,来防止对非标准区域的访问,但是后来为了管理程序扩展了 3 位。 +以前防御空洞和 `__PAGE_OFFSET` 是从 `0xffff800000000000` 到 `0xffff80ffffffffff` ,用来防止对非标准区域的访问,但是后来为了管理程序扩展了 3 位。 -紧接着是内核空间中最低的可用空间 - `ffff880000000000` 。这个虚拟地址空间是为了所有的物理内存的直接映射。在这块空间之后,还是防卫空洞。它位于所有物理内存的直接映射地址和被 vmalloc 分配的地址之间。在第一个 1TB 的虚拟内存映射和无用的空洞之后,我们可以看到 `ksan` 影子内存 (shadow memory) 。它是通过 [commit](https://github.com/torvalds/linux/commit/ef7f0d6a6ca8c9e4b27d78895af86c2fbfaeedb2) 提交到内核中,并且保持内核空间无害。在紧接着的无用空洞之后,我们可以看到 `esp` 固定栈(我们会在本书其他部分讨论它)。内核代码段的开始从物理地址 - `0` 映射。我们可以在相同的文件中找到将这个地址定义为 `__PAGE_OFFSET` 。 +紧接着是内核空间中最低的可用空间 - `ffff880000000000` 。这个虚拟地址空间是为了所有的物理内存的直接映射。在这块空间之后,还是防御空洞。它位于所有物理内存的直接映射地址和被 vmalloc 分配的地址之间。在第一个 1TB 的虚拟内存映射和无用的空洞之后,我们可以看到 `ksan` 影子内存 (shadow memory) 。它是通过 [commit](https://github.com/torvalds/linux/commit/ef7f0d6a6ca8c9e4b27d78895af86c2fbfaeedb2) 提交到内核中,并且保持内核空间无害。在紧接着的无用空洞之后,我们可以看到 `esp` 固定栈(我们会在本书其他部分讨论它)。内核代码段的开始从物理地址 - `0` 映射。我们可以在相同的文件中找到将这个地址定义为 `__PAGE_OFFSET` 。 ```C #define __START_KERNEL_map _AC(0xffffffff80000000, UL) From 3368dcfb2ad0a731e71606c6af4a42319c3f9393 Mon Sep 17 00:00:00 2001 From: mudongliang Date: Fri, 26 Feb 2016 21:13:04 +0800 Subject: [PATCH 03/90] Change the status of chapter 10.1 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 881b78c..b1837f9 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Linux Insides |├9.1|[@oska874](http://github.com/oska874) [@mudongliang](https://github.com/mudongliang)|正在进行| |└9.2|[@Alick Guo](https://github.com/a1ickgu0)|正在进行| |Theory||正在进行| -|├10.1|[@mudongliang](https://github.com/mudongliang)|正在进行| +|├10.1|[@mudongliang](https://github.com/mudongliang)|已完成| |└10.2|[@mudongliang](https://github.com/mudongliang)|已完成| |Misc||正在进行| |├12.1|[@oska874](https://github.com/oska874)|已完成| From 4a12d7c9b9446d78ef2b46119709d4331111e949 Mon Sep 17 00:00:00 2001 From: hailin Date: Sat, 27 Feb 2016 09:11:14 -0500 Subject: [PATCH 04/90] pr for finish chap1_2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1837f9..ca86d7e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Linux Insides | ------------- |:-------------:| -----:| |Booting||正在进行| |├1.1|[@hailincai](https://github.com/hailincai)|正在进行| -|├1.2|[@hailincai](https://github.com/hailincai)|正在进行| +|├1.2|[@hailincai](https://github.com/hailincai)|已完成| |├1.3|[@hailincai](https://github.com/hailincai)|正在进行| |├1.4||未开始| |└1.5||未开始| From d174478400f7579ede42495a9d8c5f7f8f521907 Mon Sep 17 00:00:00 2001 From: zmj1316 Date: Mon, 29 Feb 2016 20:31:08 +0800 Subject: [PATCH 05/90] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=EF=BC=8C=E4=BF=9D=E7=95=99=E5=8E=9F=E6=96=87=E7=AD=89=E5=BE=85?= =?UTF-8?q?=E7=B2=97=E6=A0=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Booting/linux-bootstrap-4.md | 778 +++++++++++++++++++++++++++++++++++ 1 file changed, 778 insertions(+) create mode 100644 Booting/linux-bootstrap-4.md diff --git a/Booting/linux-bootstrap-4.md b/Booting/linux-bootstrap-4.md new file mode 100644 index 0000000..e1a5534 --- /dev/null +++ b/Booting/linux-bootstrap-4.md @@ -0,0 +1,778 @@ +内核引导过程. Part 4. +================================================================================ + +切换到64位模式 +-------------------------------------------------------------------------------- + +This is the fourth part of the `Kernel booting process` where we will see first steps in [protected mode](http://en.wikipedia.org/wiki/Protected_mode), like checking that cpu supports [long mode](http://en.wikipedia.org/wiki/Long_mode) and [SSE](http://en.wikipedia.org/wiki/Streaming_SIMD_Extensions), [paging](http://en.wikipedia.org/wiki/Paging), initializes the page tables and at the end we will discus the transition to [long mode](https://en.wikipedia.org/wiki/Long_mode). + +这是`内核引导过程`的第四部分,我们将会看到在[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)中的最初几步,比如检查cpu是否支持[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F),[SSE](https://zh.wikipedia.org/wiki/SSE)和[分页](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)以及页表的初始化,在这部分的最后我们还将讨论如何切换到[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)。 + +**NOTE: will be much assembly code in this part, so if you are unfaimilat you might want to consult a a book about it** + +**注意:这部分将会有大量的汇编代码,如果你不熟悉汇编,建议你找本书参考一下。** + +In the previous [part](https://github.com/0xAX/linux-insides/blob/master/Booting/linux-bootstrap-3.md) we stopped at the jump to the 32-bit entry point in [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S): + +在[上一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md),我们停在了跳转到位于[arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S)的 32 位入口点这一步: + +```assembly +jmpl *%eax +``` + +You will recall that `eax` register contains the address of the 32-bit entry point. We can read about this in the [linux kernel x86 boot protocol](https://www.kernel.org/doc/Documentation/x86/boot.txt): + +回忆一下`eax`寄存器包含了 32 位入口点的地址。我们可以在[x86 linux 内核引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt)中找到相关内容: + +``` +When using bzImage, the protected-mode kernel was relocated to 0x100000 +``` + +``` +当使用 bzImage,保护模式下的内核被重定位至 0x100000 +``` + +Let's make sure that it is true by looking at the register values at the 32-bit entry point: + +让我们检查一下 32 位入口点的寄存器值来确认这是对的: + +``` +eax 0x100000 1048576 +ecx 0x0 0 +edx 0x0 0 +ebx 0x0 0 +esp 0x1ff5c 0x1ff5c +ebp 0x0 0x0 +esi 0x14470 83056 +edi 0x0 0 +eip 0x100000 0x100000 +eflags 0x46 [ PF ZF ] +cs 0x10 16 +ss 0x18 24 +ds 0x18 24 +es 0x18 24 +fs 0x18 24 +gs 0x18 24 +``` + +We can see here that `cs` register contains - `0x10` (as you will remember from the previous part, this is the second index in the Global Descriptor Table), `eip` register is `0x100000` and base address of all segments including the code segment are zero. So we can get the physical address, it will be `0:0x100000` or just `0x100000`, as specified by the boot protocol. Now let's start with the 32-bit entry point. + +我们可以看到这里的`cs`寄存器包含了 `0x10` (在前一节我们提到,这代表全局描述符表中的第二个索引),`eip`寄存器值是 `0x100000` 并且包括代码段的所有段的基地址都为0。所以我们可以得到物理地址,是 `0:0x100000` 或者 `0x100000`,正如协议规定的一样。现在让我们从32位入口点开始。 + +32位入口点 +-------------------------------------------------------------------------------- + +We can find the definition of the 32-bit entry point in the [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) assembly source code file: + +我们可以在汇编源码 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 找到32位入口点的定义。 + +```assembly + __HEAD + .code32 +ENTRY(startup_32) +.... +.... +.... +ENDPROC(startup_32) +``` + +First of all why `compressed` directory? Actually `bzimage` is a gzipped `vmlinux + header + kernel setup code`. We saw the kernel setup code in all of the previous parts. So, the main goal of the `head_64.S` is to prepare for entering long mode, enter into it and then decompress the kernel. We will see all of the steps up to kernel decompression in this part. + +首先,为什么是`被压缩 (compressed)` 的目录?实际上`bzimage`是一个被 gzip 压缩的`vmlinux + 头文件 + 内核启动代码`。我们在前几个章节已经看到了内核启动的代码。所以,`head_64.S` 的主要目的就是为了进入长模式,进入以后解压内核。我们将在这一节看到以上直到内核解压缩所有的步骤。 + +There were two files in the `arch/x86/boot/compressed` directory: + +在`arch/x86/boot/compressed`目录下有两个文件: + +* [head_32.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_32.S) +* [head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) + +but we will see only `head_64.S` because as you may remember this book is only `x86_64` related; `head_32.S` was not used in our case. Let's look at [arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile). There we can see the following target: + +但是我们只关注`head_64.S`,因为你可能还记得我们这本书只和`x86_64`有关;在我们这里`head_32.S`没有被使用到。让我们关注 [arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile)。这里我们可以看到以下目标: + +```Makefile +vmlinux-objs-y := $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o \ + $(obj)/string.o $(obj)/cmdline.o \ + $(obj)/piggy.o $(obj)/cpuflags.o +``` + +Note `$(obj)/head_$(BITS).o`. This means that we will select which file to link based on what `$(BITS)` is set to, either head_32.o or head_64.o. `$(BITS)` is defined elsewhere in [arch/x86/kernel/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/Makefile) based on the .config file: + +注意`$(obj)/head_$(BITS).o`。这意味着我们将会选择基于`$(BITS)`所设置的文件执行链接操作,head_32.o 或者 head_64.o。`$(BITS)` 在 [arch/x86/kernel/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/Makefile) 之中被 .config 文件另外定义: + +```Makefile +ifeq ($(CONFIG_X86_32),y) + BITS := 32 + ... + ... +else + ... + ... + BITS := 64 +endif +``` + +Now we know where to start, so let's do it. + +现在我们知道从哪里开始了,那就来吧。 + +Reload the segments if needed +在必要的时候重新载入段 +-------------------------------------------------------------------------------- + +As indicated above, we start in the [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) assembly source code file. First we see the definition of the special section attribute before the `startup_32` definition: + +正如上面阐述的,我们从 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 这个汇编文件开始。首先我们看到了在`startup_32`之前的特殊段属性定义: + +```assembly + __HEAD + .code32 +ENTRY(startup_32) +``` + +The `__HEAD` is macro which is defined in [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h) header file and expands to the definition of the following section: + +这个`__HEAD`是一个在头文件 [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h)中定义的宏,展开后就是下面这个段的定义: + +```C +#define __HEAD .section ".head.text","ax" +``` + +with `.head.text` name and `ax` flags. In our case, these flags show us that this section is [executable](https://en.wikipedia.org/wiki/Executable) or in other words contains code. We can find definition of this section in the [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/vmlinux.lds.S) linker script: + +拥有`.head.text`的命名和`ax`标记。在这里,这些标记告诉我们这个段是[可执行的](https://en.wikipedia.org/wiki/Executable)或者换种说法,包含了代码。我们可以在 [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/vmlinux.lds.S) 这个链接脚本里找到这个段的定义: + +``` +SECTIONS +{ + . = 0; + .head.text : { + _head = . ; + HEAD_TEXT + _ehead = . ; + } +``` + +If you are not familiar with syntax of `GNU LD` linker scripting language, you can find more information in the [documentation](https://sourceware.org/binutils/docs/ld/Scripts.html#Scripts). In short, the `.` symbol is a special variable of linker - location counter. The value assigned to it is an offset relative to the offset of the segment. In our case we assign zero to location counter. This means that that our code is linked to run from the `0` offset in memory. Moreover, we can find this information in comments: + +如果你不熟悉`GNU LD`这个链接脚本语言的语法,你可以在[这个文档](https://sourceware.org/binutils/docs/ld/Scripts.html#Scripts)中找到更多信息。简单来说,这个`.`符号是一个链接器的特殊变量-位置计数器。其被赋值为相对于该段的偏移。在这里,我们将位置计数器赋值为0,这意味着我们的代码被链接到内存的`0`偏移处。此外,我们可以从注释找到更多信息: + +``` +Be careful parts of head_64.S assume startup_32 is at address 0. +``` + +``` +要小心 head_64.S 中一部分假设 startup_32 位于地址 0。 +``` + +Ok, now we know where we are, and now is the best time to look inside the `startup_32` function. + +好了,现在我们知道我们在哪里了,接下来就是深入`startup_32`函数的最佳时机。 + +In the beginning of the `startup_32` function, we can see the `cld` instruction which clears the `DF` bit in the [flags](https://en.wikipedia.org/wiki/FLAGS_register) register. When direction flag is clear, all string operations like [stos](http://x86.renejeschke.de/html/file_module_x86_id_306.html), [scas](http://x86.renejeschke.de/html/file_module_x86_id_287.html) and others will increment the index registers `esi` or `edi`. We need to clear direction flag because later we will use strings operations for clearing space for page tables, etc. + +在`startup_32`函数的开始,我们可以看到`cld`指令将[标志寄存器](http://baike.baidu.com/view/1845107.htm)的 `DF`(方向标志) 位清空。当方向标志被清空,所有的串操作指令像[stos](http://x86.renejeschke.de/html/file_module_x86_id_306.html), [scas](http://x86.renejeschke.de/html/file_module_x86_id_287.html)等等将会增加索引寄存器 `esi` 或者 `edi`。我们需要清空方向标志是因为接下来我们会使用汇编的串操作来为页表腾出空间等。 + +After we have cleared the `DF` bit, next step is the check of the `KEEP_SEGMENTS` flag from `loadflags` kernel setup header field. If you remember we already saw `loadflags` in the very first [part](https://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-1.html) of this book. There we checked `CAN_USE_HEAP` flag to get ability to use heap. Now we need to check the `KEEP_SEGMENTS` flag. This flags is described in the linux [boot protocol](https://www.kernel.org/doc/Documentation/x86/boot.txt) documentation: + +在我们清空`DF`标志后,下一步就是从内核加载头中的`loadflags`检查`KEEP_SEGMENTS`标志。你是否还记得在本书的[最初一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-1.md)我们已经看到过`loadflags`。在那里我们检查了`CAN_USE_HEAP`标记以使用堆。现在我们需要检查`KEEP_SEGMENTS`标记。这些标记在 linux 的[引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt)文档中有描述: + +``` +Bit 6 (write): KEEP_SEGMENTS + Protocol: 2.07+ + - If 0, reload the segment registers in the 32bit entry point. + - If 1, do not reload the segment registers in the 32bit entry point. + Assume that %cs %ds %ss %es are all set to flat segments with + a base of 0 (or the equivalent for their environment). +``` + +``` +第 6 位 (写): KEEP_SEGMENTS + 协议: 2.07+ + - 为0,在32位入口点重载段寄存器 + - 为1,不在32位入口点重载段寄存器。假设 %cs %ds %ss %es 都被设到基地址为0的普通段中(或者在他们的环境中等价的位置)。 +``` + +So, if the `KEEP_SEGMENTS` bit is not set in the `loadflags`, we need to reset `ds`, `ss` and `es` segment registers to a flat segment with base `0`. That we do: + +所以,如果`KEEP_SEGMENTS`位在`loadflags`中没有被设置,我们需要重置`ds`,`ss`和`es`段寄存器到一个基地址为`0`的普通段中。如下: + +```C + testb $(1 << 6), BP_loadflags(%esi) + jnz 1f + + cli + movl $(__BOOT_DS), %eax + movl %eax, %ds + movl %eax, %es + movl %eax, %ss +``` + +Remember that the `__BOOT_DS` is `0x18` (index of data segment in the [Global Descriptor Table](https://en.wikipedia.org/wiki/Global_Descriptor_Table)). If `KEEP_SEGMENTS` is set, we jump to the nearest `1f` label or update segment registers with `__BOOT_DS` if it is not set. It is pretty easy, but here is one interesting moment. If you've read the previous [part](https://github.com/0xAX/linux-insides/blob/master/Booting/linux-bootstrap-3.md), you may remember that we already updated these segment registers right after we switched to [protected mode](https://en.wikipedia.org/wiki/Protected_mode) in [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S). So why do we need to care about values of segment registers again? The answer is easy. The Linux kernel also has a 32-bit boot protocol and if a bootloader uses it to load the Linux kernel all code before the `startup_32` will be missed. In this case, the `startup_32` will be first entry point of the Linux kernel right after bootloader and there are no guarantees that segment registers will be in known state. + +记住`__BOOT_DS`是`0x18`(数据段的索引位于[全局描述符表](https://en.wikipedia.org/wiki/Global_Descriptor_Table))。如果设置了 `KEEP_SEGMENTS` ,我们就跳转到最近的 `1f` 标签,或者当没有 `1f` 标签,则用`__BOOT_DS`更新段寄存器。这非常简单,但是这是一个有趣的操作。如果你已经读了[前一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md),你或许还记得我们在 [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S) 中切换到[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)的时候已经更新了这些段寄存器。那么为什么我们还要去关心这些段寄存器的值呢。答案很简单,Linux 内核也有32位的引导协议,如果一个引导程序之前使用32位协议引导内核,那么在 `startup_32` 之前的代码就会被忽略。在这种情况下`startup_32`将会变成引导程序之后的第一个入口点,不保证段寄存器会不会处于未知状态。 + +After we have checked the `KEEP_SEGMENTS` flag and put the correct value to the segment registers, the next step is to calculate difference between where we loaded and compiled to run. Remember that `setup.ld.S` contains following deifnition: `. = 0` at the start of the `.head.text` section. This means that the code in this section is compiled to run from `0` address. We can see this in `objdump` output: + +在我们检查了 `KEEP_SEGMENTS` 标记并且给段寄存器设置了正确的值之后,下一步就是计算我们代码的加载和编译运行之间的位置偏差了。记住 `setup.ld.S` 包含了以下定义:在 `.head.text` 段的开始 `. = 0`。这意味着这一段代码被编译成从 `0` 地址运行。我们可以在 `objdump` 输出中看到: + +``` +arch/x86/boot/compressed/vmlinux: file format elf64-x86-64 + + +Disassembly of section .head.text: + +0000000000000000 : + 0: fc cld + 1: f6 86 11 02 00 00 40 testb $0x40,0x211(%rsi) +``` + +The `objdump` util tells us that the address of the `startup_32` is `0`. But actually it is not so. Our current goal is to know where actually we are. It is pretty simple to do in [long mode](https://en.wikipedia.org/wiki/Long_mode), because it support `rip` relative addressing, but currently we are in [protected mode](https://en.wikipedia.org/wiki/Protected_mode). We will use common pattern to know the address of the `startup_32`. We need to define a label and make a call to this label and pop the top of the stack to a register: + +`objdump` 功能告诉我们 `startup_32` 的地址是 `0`。但是实际上并不是。我们当前的目标是获知我们实际上在哪里。在[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)下,这非常简单,因为其支持 `rip` 相对寻址,但是我们当前处于[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)下。我们将会使用一个常用的方法来确定 `startup_32` 的地址。我们需要定义一个标签并且跳转到它,然后把栈顶弹出到一个寄存器: + +```assembly +call label +label: pop %reg +``` + +After this a register will contain the address of a label. Let's look to the similar code which search address of the `startup_32` in the Linux kernel: + +在这之后,一个寄存器将会包含标签的地址,让我们看看在 Linux 内核中相似的寻找 `startup_32` 地址的代码: + +```assembly + leal (BP_scratch+4)(%esi), %esp + call 1f +1: popl %ebp + subl $1b, %ebp +``` + +As you remember from the previous part, the `esi` register contains the address of the [boot_params](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/bootparam.h#L113) structure which was filled before we moved to the protected mode. The `boot_params` structure contains a special field `scratch` with offset `0x1e4`. These four bytes field will be temporary stack for `call` instruction. We are getting the address of the `scratch` field + 4 bytes and putting it in the `esp` register. We add `4` bytes to the base of the `BP_scratch` field because, as just described, it will be a temporary stack and the stack grows from top to down in `x86_64` architecture. So our stack pointer will point to the top of the stack. Next we can see the pattern that I've described above. We make a call to the `1f` label and put the address of this label to the `ebp` register, because we have return address on the top of stack after the `call` instruction will be executed. So, for now we have an address of the `1f` label and now it is easy to get address of the `startup_32`. We need just to subtract address of label from the address which we got from the stack: + +回忆前一节,`esi` 寄存器包含了 [boot_params](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/bootparam.h#L113) 结构的地址,这个结构在我们切换到保护模式之前已经被填充了。`bootparams` 这个结构体包含了一个特殊的成员 `scratch` ,其偏移量为 `0x1e4`。这个4字节的区域将会成为 `call` 指令的临时栈。我们把 `scratch`的地址加 4 存入 `esp` 寄存器。我们之所以在 `BP_scratch` 基础上加 `4` 是因为,如之前所说的,这将成为一个临时的栈,而在 `x86_64` 架构下,栈是自顶向下生长的。所以我们的栈指针就会指向栈顶。接下来我们就可以看到我上面描述的过程。我们跳转到 `1f` 标签并且把该标签的地址放入 `ebp` 寄存器,因为在执行 `call` 指令之后我们把返回地址放到了栈顶。那么,目前我们拥有 `1f` 标签的地址,也能够很容易得到 `startup_32` 的地址。我们只需要把我们从栈里得到的地址减去标签的地址: + +``` +startup_32 (0x0) +-----------------------+ + | | + | | + | | + | | + | | + | | + | | + | | +1f (0x0 + 1f offset) +-----------------------+ %ebp - real physical address + | | + | | + +-----------------------+ +``` + +The `startup_32` is linked to run at `0x0` address and this means that `1f` has `0x0 + offset to 1f` address. Actually it is something about `0x22` bytes. The `ebp` register contains the real physical address of the `1f` label. So, if we will subtract `1f` from the `ebp` we will get the real physical address of the `startup_32`. The Linux kernel [boot protocol](https://www.kernel.org/doc/Documentation/x86/boot.txt) describes that the base of the protected mode kernel is `0x100000`. We can verify this with [gdb](https://en.wikipedia.org/wiki/GNU_Debugger). Let's start debugger and put breakpoint to the `1f` address which is `0x100022`. If this is correct we will see `0x100022` in the `ebp` register: + +`startup_32` 被链接到在 `0x0` 地址运行,这意味着 `1f` 的地址为 `0x0 + 1f 的偏移`。实际上大概是 `0x22` 字节。`ebp` 寄存器包含了 `1f` 标签的实际物理地址。所以如果我们从 `ebp` 中减去 `1f`,我们就会得到 `startup_32` 的实际物理地址。Linux 内核的[引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt)描述了保护模式下的内核基地址是 `0x100000`。我们可以用 [gdb](https://zh.wikipedia.org/wiki/GNU%E4%BE%A6%E9%94%99%E5%99%A8) 来验证。让我们启动调试器并且在 `1f` 的地址 `0x100022` 添加断点。如果这是正确的,我们将会看到在 `ebp` 寄存器中为 `0x100022`: + +``` +$ gdb +(gdb)$ target remote :1234 +Remote debugging using :1234 +0x0000fff0 in ?? () +(gdb)$ br *0x100022 +Breakpoint 1 at 0x100022 +(gdb)$ c +Continuing. + +Breakpoint 1, 0x00100022 in ?? () +(gdb)$ i r +eax 0x18 0x18 +ecx 0x0 0x0 +edx 0x0 0x0 +ebx 0x0 0x0 +esp 0x144a8 0x144a8 +ebp 0x100021 0x100021 +esi 0x142c0 0x142c0 +edi 0x0 0x0 +eip 0x100022 0x100022 +eflags 0x46 [ PF ZF ] +cs 0x10 0x10 +ss 0x18 0x18 +ds 0x18 0x18 +es 0x18 0x18 +fs 0x18 0x18 +gs 0x18 0x18 +``` + +If we will execute next instruction which is `subl $1b, %ebp`, we will see: + +如果我们执行下一条指令 `subl $1b, %ebp`,我们将会看到: + +``` +nexti +... +ebp 0x100000 0x100000 +... +``` + +Ok, that's true. The address of the `startup_32` is `0x100000`. After we know the address of the `startup_32` label, we can start to prepare for the transition to [long mode](https://en.wikipedia.org/wiki/Long_mode). Our next goal is to setup the stack and verify that the CPU supports long mode and [SSE](http://en.wikipedia.org/wiki/Streaming_SIMD_Extensions). + +好了,那是对的。`startup_32` 的地址是 `0x100000`。在我们知道了 `startup_32` 的地址之后,我们可以开始准备切换到[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)了。我们的下一个目标是建立栈并且确认 CPU 对长模式和[SSE](https://zh.wikipedia.org/wiki/SSE)的支持。 + +Stack setup and CPU verification +栈的建立和CPU的确认 +-------------------------------------------------------------------------------- + +We could not setup the stack while we did not know the address of the `startup_32` label. We can imagine the stack as an array and the stack pointer register `esp` must point to the end of this array. Of course we can define an array in our code, but we need to know its actual address to configure stack pointer in a correct way. Let's look at the code: + +如果不知道 `startup_32` 标签的地址,我们无法建立栈。我们可以把栈看作是一个数组,并且栈指针寄存器 `esp` 必须指向数组的底部。当然我们可以在自己的代码里定义一个数组,但是我们需要知道其真实地址来正确配置栈指针。让我们看一下代码: + +```assembly + movl $boot_stack_end, %eax + addl %ebp, %eax + movl %eax, %esp +``` + +The `boots_stack_end` defined in the same [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) assembly source code file and located in the [.bss](https://en.wikipedia.org/wiki/.bss) section: + +`boots_stack_end` 定义在同一个汇编文件 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 中,位于 [.bss](https://en.wikipedia.org/wiki/.bss) 段: + +```assembly + .bss + .balign 4 +boot_heap: + .fill BOOT_HEAP_SIZE, 1, 0 +boot_stack: + .fill BOOT_STACK_SIZE, 1, 0 +boot_stack_end: +``` + +First of all we put the address of `boot_stack_end` into the `eax` register. From now the `eax` register will contain address of the `boot_stack_end` where it was linked or in other words `0x0 + boot_stack_end`. To get the real address of the `boot_stack_end` we need to add the real address of the `startup_32`. As you remember, we have found this address above and put it to the `ebp` register. In the end, the register `eax` will contain real address of the `boot_stack_end` and we just need to put to the stack pointer. + +首先,我们把 `boot_stack_end` 放到 `eax` 寄存器中。现在 `eax` 寄存器将包含 `boot_stack_end` 链接后的地址或者说 `0x0 + boot_stack_end`。为了得到 `boot_stack_end` 的实际地址,我们需要加上 `startup_32` 的实际地址。回忆一下,前面我们找到了这个地址并且把它存到了 `ebp` 寄存器中。最后,`eax` 寄存器将会包含 `boot_stack_end` 的实际地址,我们只需要将其加到栈指针上。 + +After we have set up the stack, next step is CPU verification. As we are going to execute transition to the `long mode`, we need to check that the CPU supports `long mode` and `SSE`. We will do it by the call of the `verify_cpu` function: + +在外面建立了栈之后,下一步是 CPU 确认。既然我们将要切换到 `长模式`,我们需要检查 CPU 是否支持 `长模式` 和 `SSE`。我们将会在跳转到 `verify_cpu` 之后执行: + +```assembly + call verify_cpu + testl %eax, %eax + jnz no_longmode +``` + +This function defined in the [arch/x86/kernel/verify_cpu.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/verify_cpu.S) assembly file and just contains a couple of calls to the [cpuid](https://en.wikipedia.org/wiki/CPUID) instruction. This instruction is used for getting information about the processor. In our case it checks `long mode` and `SSE` support and returns `0` on success or `1` on fail in the `eax` register. + +这个函数在 [arch/x86/kernel/verify_cpu.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/verify_cpu.S) 中定义,只是包含了几个对 [cpuid](https://en.wikipedia.org/wiki/CPUID) 指令的调用。该指令用于获取处理器的信息。在我们的情况下,它检查了 `长模式` 和 `SSE` 的支持,通过 `eax` 寄存器返回0表示成功,1表示失败。 + +If the value of the `eax` is not zero, we jump to the `no_longmode` label which just stops the CPU by the call of the `hlt` instruction while no hardware interrupt will not happen: + +如果 `eax` 的值不是 0 ,我们跳转到 `no_longmode` 标签,用 `hlt` 指令停止 CPU ,期间不会发生中断: + +```assembly +no_longmode: +1: + hlt + jmp 1b +``` + +If the value of the `eax` register is zero, everything is ok and we are able to continue. + +如果 `eax` 的值为0,万事大吉,我们可以继续。 + +Calculate relocation address +计算重定位地址 +-------------------------------------------------------------------------------- + +The next step is calculating relocation address for decompression if needed. First we need to know what it means for a kernel to be `relocatable`. We already know that the base address of the 32-bit entry point of the Linux kernel is `0x100000`. But that is a 32-bit entry point. Default base address of the Linux kernel is determined by the value of the `CONFIG_PHYSICAL_START` kernel configuration option and its default value is - `0x1000000` or `1 MB`. The main problem here is that if the Linux kernel crashes, a kernel developer must have a `rescue kernel` for [kdump](https://www.kernel.org/doc/Documentation/kdump/kdump.txt) which is configured to load from a different address. The Linux kernel provides special configuration option to solve this problem - `CONFIG_RELOCATABLE`. As we can read in the documentation of the Linux kernel: + +下一步是在必要的时候计算解压缩之后的地址。首先,我们需要知道内核重定位的意义。我们已经知道 Linux 内核的32位入口点地址位于 `0x100000`。但是那是一个32位的入口。默认的内核基地址由内核配置项 `CONFIG_PHYSICAL_START` 的值所确定,其默认值为 `0x100000` 或 `1 MB`。这里的主要问题是如果内核崩溃了,内核开发者需要一个配置于不同地址的 `救援内核` 来进行 [kdump](https://www.kernel.org/doc/Documentation/kdump/kdump.txt)。Linux 内核提供了特殊的配置选项以解决此问题 - `CONFIG_RELOCATABLE`。我们可以在内核文档中找到: + +``` +This builds a kernel image that retains relocation information +so it can be loaded someplace besides the default 1MB. + +Note: If CONFIG_RELOCATABLE=y, then the kernel runs from the address +it has been loaded at and the compile time physical address +(CONFIG_PHYSICAL_START) is used as the minimum location. +``` + +``` +这建立了一个保留了重定向信息的内核镜像,这样就可以在默认的 1MB 位置之外加载了。 + +注意:如果 CONFIG_RELOCATABLE=y, 那么 内核将会从其被加载的位置运行,编译时的物理地址 (CONFIG_PHYSICAL_START) 将会被作为最低地址位置的限制。 +``` + +In simple terms this means that the Linux kernel with the same configuration can be booted from different addresses. Technically, this is done by the compiling decompressor as [position independent code](https://en.wikipedia.org/wiki/Position-independent_code). If we look at [/arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile), we will see that the decompressor is indeed compiled with the `-fPIC` flag: + +简单来说,这意味着相同配置下的 Linux 内核可以从不同地址被启动。这是通过将程序以 [位置无关代码](https://zh.wikipedia.org/wiki/%E5%9C%B0%E5%9D%80%E6%97%A0%E5%85%B3%E4%BB%A3%E7%A0%81) 的形式编译来达到的。如果我们参考 [/arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile),我们将会看到解压器是用 `-fPIC` 标记编译的: + +```Makefile +KBUILD_CFLAGS += -fno-strict-aliasing -fPIC +``` + +When we are using position-independent code an address obtained by adding the address field of the command and the value of the program counter. We can load a code which is uses such addressing from any address. That's why we had to get the real physical address of `startup_32`. Now let's get back to the Linux kernel code. Our current goal is to calculate an address where we can relocate the kernel for decompression. Calculation of this address depends on `CONFIG_RELOCATABLE` kernel configuration option. Let's look at the code: + +当我们使用位置无关代码时,一段代码的地址是由一个控制地址加上程序计数器计算得到的。我们可以从任意一个地址加载使用这种方式寻址的代码。这就是为什么我们需要获得 `startup_32` 的实际地址。现在让我们回到 Linux 内核代码。我们目前的目标是计算出内核解压的地址。这个地址的计算取决于内核配置项 `CONFIG_RELOCATABLE` 。让我们看代码: + +```assembly +#ifdef CONFIG_RELOCATABLE + movl %ebp, %ebx + movl BP_kernel_alignment(%esi), %eax + decl %eax + addl %eax, %ebx + notl %eax + andl %eax, %ebx + cmpl $LOAD_PHYSICAL_ADDR, %ebx + jge 1f +#endif + movl $LOAD_PHYSICAL_ADDR, %ebx +1: + addl $z_extract_offset, %ebx +``` + +Remember that value of the `ebp` register is the physical address of the `startup_32` label. If the `CONFIG_RELOCATABLE` kernel configuration option is enabled during kernel configuration, we put this address to the `ebx` register, align it to the `2M` and compare it with the `LOAD_PHYSICAL_ADDR` value. The `LOAD_PHYSICAL_ADDR` macro defined in the [arch/x86/include/asm/boot.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/boot.h) header file and it looks like this: + +记住 `ebp` 寄存器的值就是 `startup_32` 标签的物理地址。如果在内核配置中 `CONFIG_RELOCATABLE` 内核配置项开启,我们就把这个地址放到 `ebx` 寄存器中,对齐到 `2M` ,然后和 `LOAD_PHYSICAL_ADDR` 的值比较。`LOAD_PHYSICAL_ADDR` 宏在头文件 [arch/x86/include/asm/boot.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/boot.h) 定义,如下: + +```C +#define LOAD_PHYSICAL_ADDR ((CONFIG_PHYSICAL_START \ + + (CONFIG_PHYSICAL_ALIGN - 1)) \ + & ~(CONFIG_PHYSICAL_ALIGN - 1)) +``` + +As we can see it just expands to the aligned `CONFIG_PHYSICAL_ALIGN` value which represents physical address of where to load kernel. After comparison of the `LOAD_PHYSICAL_ADDR` and value of the `ebx` register, we add offset from the `startup_32` where to decompress the compressed kernel image. If the `CONFIG_RELOCATABLE` option is not enabled during kernel configuration, we just put default address where to load kernel and add `z_extract_offset` to it. + +我们可以看到该宏只是展开成对齐的 `CONFIG_PHYSICAL_ALIGN` 值,其表示了内核加载位置的物理地址。在比较了 `LOAD_PHYSICAL_ADDR` 和 `ebx` 的值之后,我们给 `startup_32` 加上偏移来获得解压内核镜像的地址。如果 `CONFIG_RELOCATABLE` 选项在内核配置时没有开启,我们就直接将默认的地址加上 `z_extract_offset`。 + +After all of these calculations we will have `ebp` which contains the address where we loaded it and `ebx` set to the address of where kernel will be moved after decompression. + +在前面的操作之后,`ebp`包含了我们加载时的地址,`ebx` 被设为内核解压缩的目标地址。 + +Preparation before entering long mode +进入长模式前的准备 +-------------------------------------------------------------------------------- + +When we have the base address where we will relocate compressed kernel image we need to do the last preparation before we can transition to 64-bit mode. First we need to update the [Global Descriptor Table](https://en.wikipedia.org/wiki/Global_Descriptor_Table) for this: + +在我们得到了重定位内核镜像的基地址之后,我们需要做切换到64位模式之前的最后准备。首先,我们需要更新[全局描述符表](https://en.wikipedia.org/wiki/Global_Descriptor_Table): + +```assembly + leal gdt(%ebp), %eax + movl %eax, gdt+2(%ebp) + lgdt gdt(%ebp) +``` + +Here we put the base address from `ebp` register with `gdt` offset into the `eax` register. Next we put this address into `ebp` register with offset `gdt+2` and load the `Global Descriptor Table` with the `lgdt` instruction. To understand the magic with `gdt` offsets we need to look at the definition of the `Global Descriptor Table`. We can find its definition in the same source code [file](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S): + +在这里我们把 `ebp` 寄存器加上 `gdt` 偏移存到 `eax` 寄存器。接下来我们把这个地址放到 `ebp` 加上 `gdt+2` 偏移的位置上,并且用 `lgdt` 指令载入 `全局描述符表`。为了理解这个神奇的 `gdt` 偏移量,我们需要关注`全局描述符表`的定义。我们可以在同一个[源文件](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S)中找到其定义: + +```assembly + .data +gdt: + .word gdt_end - gdt + .long gdt + .word 0 + .quad 0x0000000000000000 /* NULL descriptor */ + .quad 0x00af9a000000ffff /* __KERNEL_CS */ + .quad 0x00cf92000000ffff /* __KERNEL_DS */ + .quad 0x0080890000000000 /* TS descriptor */ + .quad 0x0000000000000000 /* TS continued */ +gdt_end: +``` + +We can see that it is located in the `.data` section and contains five descriptors: `null` descriptor, kernel code segment, kernel data segment and two task descriptors. We already loaded the `Global Descriptor Table` in the previous [part](https://github.com/0xAX/linux-insides/blob/master/Booting/linux-bootstrap-3.md), and now we're doing almost the same here, but descriptors with `CS.L = 1` and `CS.D = 0` for execution in `64` bit mode. As we can see, the definition of the `gdt` starts from two bytes: `gdt_end - gdt` which represents last byte in the `gdt` table or table limit. The next four bytes contains base address of the `gdt`. Remember that the `Global Descriptor Table` is stored in the `48-bits GDTR` which consists of two parts: + +我们可以看到其位于 `.data` 段,并且包含了5个描述符: `null`、内核代码段、内核数据段和其他两个任务描述符。我们已经在[上一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md)载入了`全局描述符表`,和我们现在做的差不多,但是描述符改为 `CS.L = 1` `CS.D = 0` 从而在 `64` 位模式下执行。我们可以看到, `gdt` 的定义从两个字节开始: `gdt_end - gdt`,代表了 `gdt` 表的最后一个字节,或者说表的范围。接下来的4个字节包含了 `gdt` 的基地址。记住 `全局描述符表` 保存在 `48位 GDTR-全局描述符表寄存器`中,由两个部分组成: + +* size(16-bit) of global descriptor table; +* address(32-bit) of the global descriptor table. + +* 全局描述符表的大小 (16位) +* 全局描述符表的基址 (32位) + +So, we put address of the `gdt` to the `eax` register and then we put it to the `.long gdt` or `gdt+2` in our assembly code. From now we have formed structure for the `GDTR` register and can load the `Global Descriptor Table` with the `lgtd` instruction. + +所以,我们把 `gdt` 的地址放到 `eax`寄存器,然后存到 `.long gdt` 或者 `gdt+2`。现在我们已经建立了 `GDTR` 寄存器的结构,并且可以用 `lgdt` 指令载入`全局描述符表`了。 + +After we have loaded the `Global Descriptor Table`, we must enable [PAE](http://en.wikipedia.org/wiki/Physical_Address_Extension) mode by putting the value of the `cr4` register into `eax`, setting 5 bit in it and loading it again into `cr4`: + +在我们载入`全局描述符表`之后,我们必须启动 [PAE](http://en.wikipedia.org/wiki/Physical_Address_Extension) 模式。方法是将 `cr4` 寄存器的值传入 `eax` ,将第5位置1,然后再写回 `cr4`。 + +```assembly + movl %cr4, %eax + orl $X86_CR4_PAE, %eax + movl %eax, %cr4 +``` + +Now we are almost finished with all preparations before we can move into 64-bit mode. The last step is to build page tables, but before that, here is some information about long mode. + +现在我们已经接近完成进入64位模式前的所有准备工作了。最后一步是建立页表,但是在此之前,这里有一些关于长模式的知识。 + +Long mode +长模式 +-------------------------------------------------------------------------------- + +[Long mode](https://en.wikipedia.org/wiki/Long_mode) is the native mode for [x86_64](https://en.wikipedia.org/wiki/X86-64) processors. First let's look at some differences between `x86_64` and the `x86`. + +[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)是 [x86_64](https://en.wikipedia.org/wiki/X86-64) 系列处理器的原生模式。首先让我们看一看 `x86_64` 和 `x86` 的一些区别。 + +The `64-bit` mode provides features such as: + +* New 8 general purpose registers from `r8` to `r15` + all general purpose registers are 64-bit now; +* 64-bit instruction pointer - `RIP`; +* New operating mode - Long mode; +* 64-Bit Addresses and Operands; +* RIP Relative Addressing (we will see an example if it in the next parts). + +`64位`模式提供了一些新特性如: + +* 从 `r8` 到 `r15` 8个新的通用寄存器,并且所有通用寄存器都是64位的了。 +* 64位指令指针 - `RIP`; +* 新的操作模式 - 长模式; +* 64位地址和操作数; +* RIP 相对寻址 (我们将会在接下来的章节看到). + +Long mode is an extension of legacy protected mode. It consists of two sub-modes: + +长模式是一个传统保护模式的扩展,其由两个子模式构成: + +* 64-bit mode; +* compatibility mode. + +* 64位模式 +* 兼容模式 + +To switch into `64-bit` mode we need to do following things: + +* To enable [PAE](https://en.wikipedia.org/wiki/Physical_Address_Extension); +* To build page tables and load the address of the top level page table into the `cr3` register; +* To enable `EFER.LME`; +* To enable paging. + +为了切换到 `64位` 模式,我们需要完成以下操作: + +* 启用 [PAE](https://en.wikipedia.org/wiki/Physical_Address_Extension); +* 建立页表并且将顶级页表的地址放入 `cr3` 寄存器; +* 启用 `EFER.LME`; +* 启用分页; + + +We already enabled `PAE` by setting the `PAE` bit in the `cr4` control register. Our next goal is to build structure for [paging](https://en.wikipedia.org/wiki/Paging). We will see this in next paragraph. + +我们已经通过设置 `cr4` 控制寄存器中的 `PAE` 位启动 `PAE` 了。在下一段,我们接下来就要建立[分页](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)的结构了。 + +Early page tables initialization +初期页表初始化 +-------------------------------------------------------------------------------- + +So, we already know that before we can move into `64-bit` mode, we need to build page tables, so, let's look at the building of early `4G` boot page tables. + +现在,我们已经知道了在进入 `64位` 模式之前,我们需要先建立页表,那么就让我们看看如何建立 `4G` 启动页表。 + +**NOTE: I will not describe theory of virtual memory here, if you need to know more about it, see links in the end of this part** + +**注意:我不会在这里解释虚拟内存的理论,如果你想知道更多,查看本节最后的链接** + +The Linux kernel uses `4-level` paging, and generally we build 6 page tables: + +* One `PML4` or `Page Map Level 4` table with one entry; +* One `PDP` or `Page Directory Pointer` table with four entries; +* Four Page Directory tables with `2048` entries. + +Linux 内核使用 `4级` 页表,通常我们会建立6个页表: + +* 1个 `PML4` 或称为 `4级页映射` 表,包含1个项; +* 1个 `PDP` 或称为 `页目录指针` 表,包含4个项; +* 4个 页目录表,包含 `2048` 个项; + +Let's look at the implementation of this. First of all we clear the buffer for the page tables in memory. Every table is `4096` bytes, so we need clear `24` kilobytes buffer: + +让我们看看其实现方式。首先我们在内存中为页表清理一块缓存。每个表都是 `4096` 字节,所以我们需要 `24` KB 的空间: + +```assembly + leal pgtable(%ebx), %edi + xorl %eax, %eax + movl $((4096*6)/4), %ecx + rep stosl +``` + +We put the address of the `pgtable` relative to `ebx` (remember that `ebx` contains the address to relocate the kernel for decompression) to the `edi` register, clear `eax` register and `6144` to the `ecx` register. The `rep stosl` instruction will write value of the `eax` to the `edi`, increase value of the `edi` register on `4` and decrease value of the `ecx` register on `4`. This operation will be repeated while value of the `ecx` register will be greater than zero. That's why we put magic `6144` to the `ecx`. + +我们把和 `ebx` 相关的 `pgtable` 的地址放到 `edi` 寄存器中,清空 `eax` 寄存器,并将 `ecx` 赋值为 `6144` 。`rep stosl` 指令将会把 `eax` 的值写到 `edi` 指向的地址,然后给 `edi` 加 4 ,`ecx` 减 4 ,重复直到 `ecx` 小于等于 0 。所以我们把 `6144` 赋值给 `ecx` 。 + +The `pgtable` is defined in the end of [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) assembly file and looks: + +`pgtable` 定义在 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 的最后: + +```assembly + .section ".pgtable","a",@nobits + .balign 4096 +pgtable: + .fill 6*4096, 1, 0 +``` + +As we can see, it is located in the `.pgtable` section and its size is `24` kilobytes. + +我们可以看到,其位于 `.pgtable` 段,大小为 `24KB`。 + +After we have got buffer for the `pgtable` structure, we can start to build the top level page table - `PML4` - with: + +在我们为`pgtable`分配了空间之后,我们可以开始构建顶级页表 - `PML4` : + +```assembly + leal pgtable + 0(%ebx), %edi + leal 0x1007 (%edi), %eax + movl %eax, 0(%edi) +``` + +Here again, we put the address of the `pgtable` relative to `ebx` or in other words relative to address of the `startup_32` to the `edi` register. Next we put this address with offset `0x1007` in the `eax` register. The `0x1007` is `4096` bytes which is the size of the `PML4` plus `7`. The `7` here represents flags of the `PML4` entry. In our case, these flags are `PRESENT+RW+USER`. In the end we just write first the address of the first `PDP` entry to the `PML4`. + +还是在这里,我们把和 `ebx` 相关的,或者说和 `startup_32`相关的 `pgtable` 的地址放到 `ebi` 寄存器。接下来我们把相对此地址偏移 `0x1007` 的地址放到 `eax` 寄存器中。 `0x1007` 是 `PML4` 的大小 `4096` 加上 `7`。这里的 `7` 代表了 `PML4` 的项标记。在我们这里,这些标记是 `PRESENT+RW+USER`。在最后我们把第一个 `PDP(页目录指针)` 项的地址写到 `PML4` 中。 + +In the next step we will build four `Page Directory` entries in the `Page Directory Pointer` table with the same `PRESENT+RW+USE` flags: + +在接下来的一步,我们将会在 `页目录指针(PDP)` 表(3级页表)建立 4 个带有`PRESENT+RW+USE`标记的`Page Directory (2级页表)`项: + +```assembly + leal pgtable + 0x1000(%ebx), %edi + leal 0x1007(%edi), %eax + movl $4, %ecx +1: movl %eax, 0x00(%edi) + addl $0x00001000, %eax + addl $8, %edi + decl %ecx + jnz 1b +``` + +We put the base address of the page directory pointer which is `4096` or `0x1000` offset from the `pgtable` table in `edi` and the address of the first page directory pointer entry in `eax` register. Put `4` in the `ecx` register, it will be a counter in the following loop and write the address of the first page directory pointer table entry to the `edi` register. After this `edi` will contain the address of the first page directory pointer entry with flags `0x7`. Next we just calculate the address of following page directory pointer entries where each entry is `8` bytes, and write their addresses to `eax`. The last step of building paging structure is the building of the `2048` page table entries with `2-MByte` pages: + +我们把3级页目录指针表的基地址(从`pgtable`表偏移`4096`或者`0x1000`)放到`edi`,把第一个2级页目录指针表的首项的地址放到`eax`寄存器。把`4`赋值给`ecx`寄存器,其将会作为接下来循环的计数器,然后将第一个页目录指针项写到`edi`指向的地址。之后,`edi`将会包含带有标记`0x7`的第一个页目录指针项的地址。接下来我们就计算后面的几个页目录指针项的地址,每个占8字节,把地址赋值给`eax`,然后回到循环开头将其写入`edi`所在地址。建立页表结构的最后一步就是建立`2048`个`2MB`页表项。 + +```assembly + leal pgtable + 0x2000(%ebx), %edi + movl $0x00000183, %eax + movl $2048, %ecx +1: movl %eax, 0(%edi) + addl $0x00200000, %eax + addl $8, %edi + decl %ecx + jnz 1b +``` + +Here we do almost the same as in the previous example, all entries will be with flags - `$0x00000183` - `PRESENT + WRITE + MBZ`. In the end we will have `2048` pages with `2-MByte` page or: + +在这里我们做的几乎和上面一样,所有的表项都带着标记 - `$0x00000183` - `PRESENT + WRITE + MBZ`。最后我们将会拥有`2048`个`2MB`页的页表,或者说: + +```python +>>> 2048 * 0x00200000 +4294967296 +``` + +`4G` page table. We just finished to build our early page table structure which maps `4` gigabytes of memory and now we can put the address of the high-level page table - `PML4` - in `cr3` control register: + +`4G`页表。我们刚刚完成我们的初期页表结构,其映射了`4G`大小的内存,现在我们可以把高级页表`PML4`的地址放到`cr3`寄存器中了: + +```assembly + leal pgtable(%ebx), %eax + movl %eax, %cr3 +``` + +That's all. All preparation are finished and now we can see transition to the long mode. + +全部结束了。所有的准备工作都已经完成,我们可以开始看如何切换到长模式了。 + +Transition to the 64-bit mode +切换到长模式 +-------------------------------------------------------------------------------- + +First of all we need to set the `EFER.LME` flag in the [MSR](http://en.wikipedia.org/wiki/Model-specific_register) to `0xC0000080`: + +首先我们需要设置[MSR](http://en.wikipedia.org/wiki/Model-specific_register)中的`EFER.LME`标记为`0xC0000080`: + +```assembly + movl $MSR_EFER, %ecx + rdmsr + btsl $_EFER_LME, %eax + wrmsr +``` + +Here we put the `MSR_EFER` flag (which is defined in [arch/x86/include/uapi/asm/msr-index.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/msr-index.h#L7)) in the `ecx` register and call `rdmsr` instruction which reads the [MSR](http://en.wikipedia.org/wiki/Model-specific_register) register. After `rdmsr` executes, we will have the resulting data in `edx:eax` which depends on the `ecx` value. We check the `EFER_LME` bit with the `btsl` instruction and write data from `eax` to the `MSR` register with the `wrmsr` instruction. + +在这里我们把`MSR_EFER`标记(在 [arch/x86/include/uapi/asm/msr-index.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/msr-index.h#L7) 定义)放到`ecx`寄存器中,然后调用`rdmsr`指令读取[MSR](http://en.wikipedia.org/wiki/Model-specific_register)寄存器。在`rdmsr`执行之后,我们将会获得`edx:eax`中的结果值,其取决于`ecx`的值。我们通过`btsl`指令检查`EFER_LME`位,并且通过`wrmsr`指令将`eax`的数据写入`MSR`寄存器。 + +In the next step we push the address of the kernel segment code to the stack (we defined it in the GDT) and put the address of the `startup_64` routine in `eax`. + +下一步我们将内核段代码地址入栈(我们在 GDT 中定义了),然后将`startup_64`的地址导入`eax`。 + +```assembly + pushl $__KERNEL_CS + leal startup_64(%ebp), %eax +``` + +After this we push this address to the stack and enable paging by setting `PG` and `PE` bits in the `cr0` register: + +在这之后我们把这个地址入栈然后通过设置`cr0`寄存器中的`PG`和`PE`启用分页: + +```assembly + movl $(X86_CR0_PG | X86_CR0_PE), %eax + movl %eax, %cr0 +``` + +and execute: + +然后执行: + +```assembly +lret +``` + +instruction. Remember that we pushed the address of the `startup_64` function to the stack in the previous step, and after the `lret` instruction, the CPU extracts the address of it and jumps there. + +指令。记住前一步我们已经将`startup_64`函数的地址入栈,在`lret`指令之后,CPU 丢弃了其地址跳转到了这里。 + +After all of these steps we're finally in 64-bit mode: + +这些步骤之后我们最后来到了64位模式: + +```assembly + .code64 + .org 0x200 +ENTRY(startup_64) +.... +.... +.... +``` + +That's all! + +就是这样! + +Conclusion +总结 +-------------------------------------------------------------------------------- + +This is the end of the fourth part linux kernel booting process. If you have questions or suggestions, ping me in twitter [0xAX](https://twitter.com/0xAX), drop me [email](anotherworldofworld@gmail.com) or just create an [issue](https://github.com/0xAX/linux-insides/issues/new). + +这是 linux 内核启动流程的第4部分。如果你有任何的问题或者建议,你可以留言,也可以直接发消息给我[twitter](https://twitter.com/0xAX)或者创建一个 [issue](https://github.com/0xAX/linux-insides/issues/new)。 + +In the next part we will see kernel decompression and many more. + +下一节我们将会看到内核解压缩流程和其他更多。 + +**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](https://github.com/0xAX/linux-internals).** + +相关链接 +-------------------------------------------------------------------------------- + +* [Protected mode](http://en.wikipedia.org/wiki/Protected_mode) +* [Intel® 64 and IA-32 Architectures Software Developer’s Manual 3A](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html) +* [GNU linker](http://www.eecs.umich.edu/courses/eecs373/readings/Linker.pdf) +* [SSE](http://en.wikipedia.org/wiki/Streaming_SIMD_Extensions) +* [Paging](http://en.wikipedia.org/wiki/Paging) +* [Model specific register](http://en.wikipedia.org/wiki/Model-specific_register) +* [.fill instruction](http://www.chemie.fu-berlin.de/chemnet/use/info/gas/gas_7.html) +* [Previous part](https://github.com/0xAX/linux-insides/blob/master/Booting/linux-bootstrap-3.md) +* [Paging on osdev.org](http://wiki.osdev.org/Paging) +* [Paging Systems](https://www.cs.rutgers.edu/~pxk/416/notes/09a-paging.html) +* [x86 Paging Tutorial](http://www.cirosantilli.com/x86-paging/) From 7c05a2021d26c37fa4ce3284865b158faf6793a1 Mon Sep 17 00:00:00 2001 From: zmj1316 Date: Mon, 29 Feb 2016 22:46:06 +0800 Subject: [PATCH 06/90] clean up --- Booting/linux-bootstrap-4.md | 283 +++++++---------------------------- 1 file changed, 51 insertions(+), 232 deletions(-) diff --git a/Booting/linux-bootstrap-4.md b/Booting/linux-bootstrap-4.md index e1a5534..214038c 100644 --- a/Booting/linux-bootstrap-4.md +++ b/Booting/linux-bootstrap-4.md @@ -4,25 +4,17 @@ 切换到64位模式 -------------------------------------------------------------------------------- -This is the fourth part of the `Kernel booting process` where we will see first steps in [protected mode](http://en.wikipedia.org/wiki/Protected_mode), like checking that cpu supports [long mode](http://en.wikipedia.org/wiki/Long_mode) and [SSE](http://en.wikipedia.org/wiki/Streaming_SIMD_Extensions), [paging](http://en.wikipedia.org/wiki/Paging), initializes the page tables and at the end we will discus the transition to [long mode](https://en.wikipedia.org/wiki/Long_mode). - -这是`内核引导过程`的第四部分,我们将会看到在[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)中的最初几步,比如检查cpu是否支持[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F),[SSE](https://zh.wikipedia.org/wiki/SSE)和[分页](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)以及页表的初始化,在这部分的最后我们还将讨论如何切换到[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)。 - -**NOTE: will be much assembly code in this part, so if you are unfaimilat you might want to consult a a book about it** +这是`内核引导过程`的第四部分,我们将会看到在[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)中的最初几步,比如确认CPU支持[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F),[SSE](https://zh.wikipedia.org/wiki/SSE)和[分页](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)以及页表的初始化,在这部分的最后我们还将讨论如何切换到[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)。 **注意:这部分将会有大量的汇编代码,如果你不熟悉汇编,建议你找本书参考一下。** -In the previous [part](https://github.com/0xAX/linux-insides/blob/master/Booting/linux-bootstrap-3.md) we stopped at the jump to the 32-bit entry point in [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S): - 在[上一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md),我们停在了跳转到位于[arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S)的 32 位入口点这一步: ```assembly jmpl *%eax ``` -You will recall that `eax` register contains the address of the 32-bit entry point. We can read about this in the [linux kernel x86 boot protocol](https://www.kernel.org/doc/Documentation/x86/boot.txt): - -回忆一下`eax`寄存器包含了 32 位入口点的地址。我们可以在[x86 linux 内核引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt)中找到相关内容: +回忆一下`eax`寄存器包含了 32 位入口点的地址。我们可以在 [x86 linux 内核引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt) 中找到相关内容: ``` When using bzImage, the protected-mode kernel was relocated to 0x100000 @@ -32,9 +24,8 @@ When using bzImage, the protected-mode kernel was relocated to 0x100000 当使用 bzImage,保护模式下的内核被重定位至 0x100000 ``` -Let's make sure that it is true by looking at the register values at the 32-bit entry point: -让我们检查一下 32 位入口点的寄存器值来确认这是对的: +让我们检查一下 32 位入口点的寄存器值来确保这是对的: ``` eax 0x100000 1048576 @@ -55,16 +46,12 @@ fs 0x18 24 gs 0x18 24 ``` -We can see here that `cs` register contains - `0x10` (as you will remember from the previous part, this is the second index in the Global Descriptor Table), `eip` register is `0x100000` and base address of all segments including the code segment are zero. So we can get the physical address, it will be `0:0x100000` or just `0x100000`, as specified by the boot protocol. Now let's start with the 32-bit entry point. +我们可以看到这里的`cs`寄存器的内容 - `0x10` (在前一节我们提到,这代表全局描述符表中的第二个索引项),`eip`寄存器值是 `0x100000` 并且包括代码段的所有段的基地址都为0。所以我们可以得到物理地址: `0:0x100000` 或者 `0x100000`,正如协议规定的一样。现在让我们从 32 位入口点开始。 -我们可以看到这里的`cs`寄存器包含了 `0x10` (在前一节我们提到,这代表全局描述符表中的第二个索引),`eip`寄存器值是 `0x100000` 并且包括代码段的所有段的基地址都为0。所以我们可以得到物理地址,是 `0:0x100000` 或者 `0x100000`,正如协议规定的一样。现在让我们从32位入口点开始。 - -32位入口点 +32 位入口点 -------------------------------------------------------------------------------- -We can find the definition of the 32-bit entry point in the [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) assembly source code file: - -我们可以在汇编源码 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 找到32位入口点的定义。 +我们可以在汇编源码 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 中找到 32 位入口点的定义。 ```assembly __HEAD @@ -76,19 +63,13 @@ ENTRY(startup_32) ENDPROC(startup_32) ``` -First of all why `compressed` directory? Actually `bzimage` is a gzipped `vmlinux + header + kernel setup code`. We saw the kernel setup code in all of the previous parts. So, the main goal of the `head_64.S` is to prepare for entering long mode, enter into it and then decompress the kernel. We will see all of the steps up to kernel decompression in this part. - -首先,为什么是`被压缩 (compressed)` 的目录?实际上`bzimage`是一个被 gzip 压缩的`vmlinux + 头文件 + 内核启动代码`。我们在前几个章节已经看到了内核启动的代码。所以,`head_64.S` 的主要目的就是为了进入长模式,进入以后解压内核。我们将在这一节看到以上直到内核解压缩所有的步骤。 - -There were two files in the `arch/x86/boot/compressed` directory: +首先,为什么是`被压缩 (compressed)` 的目录?实际上`bzimage`是一个被 gzip 压缩的`vmlinux + 头文件 + 内核启动代码`。我们在前几个章节已经看到了内核启动的代码。所以,`head_64.S`的主要目的就是为了准备并进入长模式,进入以后解压内核。我们将在这一节看到以上直到内核解压缩之前的所有步骤。 在`arch/x86/boot/compressed`目录下有两个文件: * [head_32.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_32.S) * [head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) -but we will see only `head_64.S` because as you may remember this book is only `x86_64` related; `head_32.S` was not used in our case. Let's look at [arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile). There we can see the following target: - 但是我们只关注`head_64.S`,因为你可能还记得我们这本书只和`x86_64`有关;在我们这里`head_32.S`没有被使用到。让我们关注 [arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile)。这里我们可以看到以下目标: ```Makefile @@ -97,8 +78,6 @@ vmlinux-objs-y := $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o \ $(obj)/piggy.o $(obj)/cpuflags.o ``` -Note `$(obj)/head_$(BITS).o`. This means that we will select which file to link based on what `$(BITS)` is set to, either head_32.o or head_64.o. `$(BITS)` is defined elsewhere in [arch/x86/kernel/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/Makefile) based on the .config file: - 注意`$(obj)/head_$(BITS).o`。这意味着我们将会选择基于`$(BITS)`所设置的文件执行链接操作,head_32.o 或者 head_64.o。`$(BITS)` 在 [arch/x86/kernel/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/Makefile) 之中被 .config 文件另外定义: ```Makefile @@ -113,17 +92,12 @@ else endif ``` -Now we know where to start, so let's do it. - 现在我们知道从哪里开始了,那就来吧。 -Reload the segments if needed -在必要的时候重新载入段 +必要时重载内存段寄存器 -------------------------------------------------------------------------------- -As indicated above, we start in the [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) assembly source code file. First we see the definition of the special section attribute before the `startup_32` definition: - -正如上面阐述的,我们从 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 这个汇编文件开始。首先我们看到了在`startup_32`之前的特殊段属性定义: +正如上面阐述的,我们先从 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 这个汇编文件开始。首先我们看到了在`startup_32`之前的特殊段属性定义: ```assembly __HEAD @@ -131,16 +105,12 @@ As indicated above, we start in the [arch/x86/boot/compressed/head_64.S](https:/ ENTRY(startup_32) ``` -The `__HEAD` is macro which is defined in [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h) header file and expands to the definition of the following section: - -这个`__HEAD`是一个在头文件 [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h)中定义的宏,展开后就是下面这个段的定义: +这个`__HEAD`是一个定义在头文件 [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h)中的宏,展开后就是下面这个段的定义: ```C #define __HEAD .section ".head.text","ax" ``` -with `.head.text` name and `ax` flags. In our case, these flags show us that this section is [executable](https://en.wikipedia.org/wiki/Executable) or in other words contains code. We can find definition of this section in the [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/vmlinux.lds.S) linker script: - 拥有`.head.text`的命名和`ax`标记。在这里,这些标记告诉我们这个段是[可执行的](https://en.wikipedia.org/wiki/Executable)或者换种说法,包含了代码。我们可以在 [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/vmlinux.lds.S) 这个链接脚本里找到这个段的定义: ``` @@ -154,29 +124,21 @@ SECTIONS } ``` -If you are not familiar with syntax of `GNU LD` linker scripting language, you can find more information in the [documentation](https://sourceware.org/binutils/docs/ld/Scripts.html#Scripts). In short, the `.` symbol is a special variable of linker - location counter. The value assigned to it is an offset relative to the offset of the segment. In our case we assign zero to location counter. This means that that our code is linked to run from the `0` offset in memory. Moreover, we can find this information in comments: - -如果你不熟悉`GNU LD`这个链接脚本语言的语法,你可以在[这个文档](https://sourceware.org/binutils/docs/ld/Scripts.html#Scripts)中找到更多信息。简单来说,这个`.`符号是一个链接器的特殊变量-位置计数器。其被赋值为相对于该段的偏移。在这里,我们将位置计数器赋值为0,这意味着我们的代码被链接到内存的`0`偏移处。此外,我们可以从注释找到更多信息: +如果你不熟悉`GNU LD`这个链接脚本语言的语法,你可以在[这个文档](https://sourceware.org/binutils/docs/ld/Scripts.html#Scripts)中找到更多信息。简单来说,这个`.`符号是一个链接器的特殊变量 - 位置计数器。其被赋值为相对于该段的偏移。在这里,我们将位置计数器赋值为0,这意味着我们的代码被链接到内存的`0`偏移处。此外,我们可以从注释找到更多信息: ``` Be careful parts of head_64.S assume startup_32 is at address 0. ``` ``` -要小心 head_64.S 中一部分假设 startup_32 位于地址 0。 +要小心, head_64.S 中一些部分假设 startup_32 位于地址 0。 ``` -Ok, now we know where we are, and now is the best time to look inside the `startup_32` function. - 好了,现在我们知道我们在哪里了,接下来就是深入`startup_32`函数的最佳时机。 -In the beginning of the `startup_32` function, we can see the `cld` instruction which clears the `DF` bit in the [flags](https://en.wikipedia.org/wiki/FLAGS_register) register. When direction flag is clear, all string operations like [stos](http://x86.renejeschke.de/html/file_module_x86_id_306.html), [scas](http://x86.renejeschke.de/html/file_module_x86_id_287.html) and others will increment the index registers `esi` or `edi`. We need to clear direction flag because later we will use strings operations for clearing space for page tables, etc. +在`startup_32`函数的开始,我们可以看到`cld`指令将[标志寄存器](http://baike.baidu.com/view/1845107.htm)的`DF`(方向标志)位清空。当方向标志被清空,所有的串操作指令像[stos](http://x86.renejeschke.de/html/file_module_x86_id_306.html), [scas](http://x86.renejeschke.de/html/file_module_x86_id_287.html)等等将会增加索引寄存器 `esi` 或者 `edi`的值。我们需要清空方向标志是因为接下来我们会使用汇编的串操作指令来做为页表腾出空间等工作。 -在`startup_32`函数的开始,我们可以看到`cld`指令将[标志寄存器](http://baike.baidu.com/view/1845107.htm)的 `DF`(方向标志) 位清空。当方向标志被清空,所有的串操作指令像[stos](http://x86.renejeschke.de/html/file_module_x86_id_306.html), [scas](http://x86.renejeschke.de/html/file_module_x86_id_287.html)等等将会增加索引寄存器 `esi` 或者 `edi`。我们需要清空方向标志是因为接下来我们会使用汇编的串操作来为页表腾出空间等。 - -After we have cleared the `DF` bit, next step is the check of the `KEEP_SEGMENTS` flag from `loadflags` kernel setup header field. If you remember we already saw `loadflags` in the very first [part](https://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-1.html) of this book. There we checked `CAN_USE_HEAP` flag to get ability to use heap. Now we need to check the `KEEP_SEGMENTS` flag. This flags is described in the linux [boot protocol](https://www.kernel.org/doc/Documentation/x86/boot.txt) documentation: - -在我们清空`DF`标志后,下一步就是从内核加载头中的`loadflags`检查`KEEP_SEGMENTS`标志。你是否还记得在本书的[最初一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-1.md)我们已经看到过`loadflags`。在那里我们检查了`CAN_USE_HEAP`标记以使用堆。现在我们需要检查`KEEP_SEGMENTS`标记。这些标记在 linux 的[引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt)文档中有描述: +在我们清空`DF`标志后,下一步就是从内核加载头中的`loadflags`字段来检查`KEEP_SEGMENTS`标志。你是否还记得在本书的[最初一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-1.md),我们已经看到过`loadflags`。在那里我们检查了`CAN_USE_HEAP`标记以使用堆。现在我们需要检查`KEEP_SEGMENTS`标记。这些标记在 linux 的[引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt)文档中有描述: ``` Bit 6 (write): KEEP_SEGMENTS @@ -189,13 +151,11 @@ Bit 6 (write): KEEP_SEGMENTS ``` 第 6 位 (写): KEEP_SEGMENTS - 协议: 2.07+ + 协议版本: 2.07+ - 为0,在32位入口点重载段寄存器 - 为1,不在32位入口点重载段寄存器。假设 %cs %ds %ss %es 都被设到基地址为0的普通段中(或者在他们的环境中等价的位置)。 ``` -So, if the `KEEP_SEGMENTS` bit is not set in the `loadflags`, we need to reset `ds`, `ss` and `es` segment registers to a flat segment with base `0`. That we do: - 所以,如果`KEEP_SEGMENTS`位在`loadflags`中没有被设置,我们需要重置`ds`,`ss`和`es`段寄存器到一个基地址为`0`的普通段中。如下: ```C @@ -209,13 +169,9 @@ So, if the `KEEP_SEGMENTS` bit is not set in the `loadflags`, we need to reset ` movl %eax, %ss ``` -Remember that the `__BOOT_DS` is `0x18` (index of data segment in the [Global Descriptor Table](https://en.wikipedia.org/wiki/Global_Descriptor_Table)). If `KEEP_SEGMENTS` is set, we jump to the nearest `1f` label or update segment registers with `__BOOT_DS` if it is not set. It is pretty easy, but here is one interesting moment. If you've read the previous [part](https://github.com/0xAX/linux-insides/blob/master/Booting/linux-bootstrap-3.md), you may remember that we already updated these segment registers right after we switched to [protected mode](https://en.wikipedia.org/wiki/Protected_mode) in [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S). So why do we need to care about values of segment registers again? The answer is easy. The Linux kernel also has a 32-bit boot protocol and if a bootloader uses it to load the Linux kernel all code before the `startup_32` will be missed. In this case, the `startup_32` will be first entry point of the Linux kernel right after bootloader and there are no guarantees that segment registers will be in known state. +记住`__BOOT_DS`是`0x18`(位于[全局描述符表](https://en.wikipedia.org/wiki/Global_Descriptor_Table)中数据段的索引)。如果设置了 `KEEP_SEGMENTS` ,我们就跳转到最近的 `1f` 标签,或者当没有`1f`标签,则用`__BOOT_DS`更新段寄存器。这非常简单,但是这是一个有趣的操作。如果你已经读了[前一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md),你或许还记得我们在 [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S) 中切换到[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)的时候已经更新了这些段寄存器。那么为什么我们还要去关心这些段寄存器的值呢。答案很简单,Linux 内核也有32位的引导协议,如果一个引导程序之前使用32位协议引导内核,那么在`startup_32`之前的代码就会被忽略。在这种情况下`startup_32`将会变成引导程序之后的第一个入口点,不保证段寄存器会不会处于未知状态。 -记住`__BOOT_DS`是`0x18`(数据段的索引位于[全局描述符表](https://en.wikipedia.org/wiki/Global_Descriptor_Table))。如果设置了 `KEEP_SEGMENTS` ,我们就跳转到最近的 `1f` 标签,或者当没有 `1f` 标签,则用`__BOOT_DS`更新段寄存器。这非常简单,但是这是一个有趣的操作。如果你已经读了[前一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md),你或许还记得我们在 [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S) 中切换到[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)的时候已经更新了这些段寄存器。那么为什么我们还要去关心这些段寄存器的值呢。答案很简单,Linux 内核也有32位的引导协议,如果一个引导程序之前使用32位协议引导内核,那么在 `startup_32` 之前的代码就会被忽略。在这种情况下`startup_32`将会变成引导程序之后的第一个入口点,不保证段寄存器会不会处于未知状态。 - -After we have checked the `KEEP_SEGMENTS` flag and put the correct value to the segment registers, the next step is to calculate difference between where we loaded and compiled to run. Remember that `setup.ld.S` contains following deifnition: `. = 0` at the start of the `.head.text` section. This means that the code in this section is compiled to run from `0` address. We can see this in `objdump` output: - -在我们检查了 `KEEP_SEGMENTS` 标记并且给段寄存器设置了正确的值之后,下一步就是计算我们代码的加载和编译运行之间的位置偏差了。记住 `setup.ld.S` 包含了以下定义:在 `.head.text` 段的开始 `. = 0`。这意味着这一段代码被编译成从 `0` 地址运行。我们可以在 `objdump` 输出中看到: +在我们检查了`KEEP_SEGMENTS`标记并且给段寄存器设置了正确的值之后,下一步就是计算我们代码的加载和编译运行之间的位置偏差了。记住`setup.ld.S`包含了以下定义:在`.head.text`段的开始`. = 0`。这意味着这一段代码被编译成从`0`地址运行。我们可以在`objdump`输出中看到: ``` arch/x86/boot/compressed/vmlinux: file format elf64-x86-64 @@ -228,18 +184,14 @@ Disassembly of section .head.text: 1: f6 86 11 02 00 00 40 testb $0x40,0x211(%rsi) ``` -The `objdump` util tells us that the address of the `startup_32` is `0`. But actually it is not so. Our current goal is to know where actually we are. It is pretty simple to do in [long mode](https://en.wikipedia.org/wiki/Long_mode), because it support `rip` relative addressing, but currently we are in [protected mode](https://en.wikipedia.org/wiki/Protected_mode). We will use common pattern to know the address of the `startup_32`. We need to define a label and make a call to this label and pop the top of the stack to a register: - -`objdump` 功能告诉我们 `startup_32` 的地址是 `0`。但是实际上并不是。我们当前的目标是获知我们实际上在哪里。在[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)下,这非常简单,因为其支持 `rip` 相对寻址,但是我们当前处于[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)下。我们将会使用一个常用的方法来确定 `startup_32` 的地址。我们需要定义一个标签并且跳转到它,然后把栈顶弹出到一个寄存器: +`objdump`告诉我们`startup_32`的地址是`0`。但是实际上并不是。我们当前的目标是获知我们实际上在哪里。在[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)下,这非常简单,因为其支持`rip`相对寻址,但是我们当前处于[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)下。我们将会使用一个常用的方法来确定`startup_32`的地址。我们需要定义一个标签并且跳转到它,然后把栈顶抛出到一个寄存器中: ```assembly call label label: pop %reg ``` -After this a register will contain the address of a label. Let's look to the similar code which search address of the `startup_32` in the Linux kernel: - -在这之后,一个寄存器将会包含标签的地址,让我们看看在 Linux 内核中相似的寻找 `startup_32` 地址的代码: +在这之后,那个寄存器将会包含标签的地址,让我们看看在 Linux 内核中相似的寻找`startup_32`地址的代码: ```assembly leal (BP_scratch+4)(%esi), %esp @@ -248,9 +200,7 @@ After this a register will contain the address of a label. Let's look to the sim subl $1b, %ebp ``` -As you remember from the previous part, the `esi` register contains the address of the [boot_params](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/bootparam.h#L113) structure which was filled before we moved to the protected mode. The `boot_params` structure contains a special field `scratch` with offset `0x1e4`. These four bytes field will be temporary stack for `call` instruction. We are getting the address of the `scratch` field + 4 bytes and putting it in the `esp` register. We add `4` bytes to the base of the `BP_scratch` field because, as just described, it will be a temporary stack and the stack grows from top to down in `x86_64` architecture. So our stack pointer will point to the top of the stack. Next we can see the pattern that I've described above. We make a call to the `1f` label and put the address of this label to the `ebp` register, because we have return address on the top of stack after the `call` instruction will be executed. So, for now we have an address of the `1f` label and now it is easy to get address of the `startup_32`. We need just to subtract address of label from the address which we got from the stack: - -回忆前一节,`esi` 寄存器包含了 [boot_params](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/bootparam.h#L113) 结构的地址,这个结构在我们切换到保护模式之前已经被填充了。`bootparams` 这个结构体包含了一个特殊的成员 `scratch` ,其偏移量为 `0x1e4`。这个4字节的区域将会成为 `call` 指令的临时栈。我们把 `scratch`的地址加 4 存入 `esp` 寄存器。我们之所以在 `BP_scratch` 基础上加 `4` 是因为,如之前所说的,这将成为一个临时的栈,而在 `x86_64` 架构下,栈是自顶向下生长的。所以我们的栈指针就会指向栈顶。接下来我们就可以看到我上面描述的过程。我们跳转到 `1f` 标签并且把该标签的地址放入 `ebp` 寄存器,因为在执行 `call` 指令之后我们把返回地址放到了栈顶。那么,目前我们拥有 `1f` 标签的地址,也能够很容易得到 `startup_32` 的地址。我们只需要把我们从栈里得到的地址减去标签的地址: +回忆前一节,`esi`寄存器包含了 [boot_params](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/bootparam.h#L113) 结构的地址,这个结构在我们切换到保护模式之前已经被填充了。`bootparams` 这个结构体包含了一个特殊的成员`scratch`,其偏移量为`0x1e4`。这个 4 字节的区域将会成为`call`指令的临时栈。我们把`scratch`的地址加 4 存入`esp`寄存器。我们之所以在`BP_scratch`基础上加`4`是因为,如之前所说的,这将成为一个临时的栈,而在`x86_64`架构下,栈是自顶向下生长的。所以我们的栈指针就会指向栈顶。接下来我们就可以看到我上面描述的过程。我们跳转到 `1f` 标签并且把该标签的地址放入`ebp`寄存器,因为在执行`call`指令之后我们把返回地址放到了栈顶。那么,目前我们拥有`1f`标签的地址,也能够很容易得到`startup_32`的地址。我们只需要把我们从栈里得到的地址减去标签的地址: ``` startup_32 (0x0) +-----------------------+ @@ -268,9 +218,7 @@ startup_32 (0x0) +-----------------------+ +-----------------------+ ``` -The `startup_32` is linked to run at `0x0` address and this means that `1f` has `0x0 + offset to 1f` address. Actually it is something about `0x22` bytes. The `ebp` register contains the real physical address of the `1f` label. So, if we will subtract `1f` from the `ebp` we will get the real physical address of the `startup_32`. The Linux kernel [boot protocol](https://www.kernel.org/doc/Documentation/x86/boot.txt) describes that the base of the protected mode kernel is `0x100000`. We can verify this with [gdb](https://en.wikipedia.org/wiki/GNU_Debugger). Let's start debugger and put breakpoint to the `1f` address which is `0x100022`. If this is correct we will see `0x100022` in the `ebp` register: - -`startup_32` 被链接到在 `0x0` 地址运行,这意味着 `1f` 的地址为 `0x0 + 1f 的偏移`。实际上大概是 `0x22` 字节。`ebp` 寄存器包含了 `1f` 标签的实际物理地址。所以如果我们从 `ebp` 中减去 `1f`,我们就会得到 `startup_32` 的实际物理地址。Linux 内核的[引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt)描述了保护模式下的内核基地址是 `0x100000`。我们可以用 [gdb](https://zh.wikipedia.org/wiki/GNU%E4%BE%A6%E9%94%99%E5%99%A8) 来验证。让我们启动调试器并且在 `1f` 的地址 `0x100022` 添加断点。如果这是正确的,我们将会看到在 `ebp` 寄存器中为 `0x100022`: +`startup_32`被链接为在`0x0`地址运行,这意味着`1f`的地址为`0x0 + 1f 的偏移量`。实际上偏移量大概是`0x22`字节。`ebp`寄存器包含了`1f`标签的实际物理地址。所以如果我们从`ebp`中减去`1f`,我们就会得到`startup_32`的实际物理地址。Linux 内核的[引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt)描述了保护模式下的内核基地址是 `0x100000`。我们可以用 [gdb](https://zh.wikipedia.org/wiki/GNU%E4%BE%A6%E9%94%99%E5%99%A8) 来验证。让我们启动调试器并且在`1f`的地址`0x100022`添加断点。如果这是正确的,我们将会看到在`ebp`寄存器中值为`0x100022`: ``` $ gdb @@ -302,9 +250,7 @@ fs 0x18 0x18 gs 0x18 0x18 ``` -If we will execute next instruction which is `subl $1b, %ebp`, we will see: - -如果我们执行下一条指令 `subl $1b, %ebp`,我们将会看到: +如果我们执行下一条指令`subl $1b, %ebp`,我们将会看到: ``` nexti @@ -313,17 +259,12 @@ ebp 0x100000 0x100000 ... ``` -Ok, that's true. The address of the `startup_32` is `0x100000`. After we know the address of the `startup_32` label, we can start to prepare for the transition to [long mode](https://en.wikipedia.org/wiki/Long_mode). Our next goal is to setup the stack and verify that the CPU supports long mode and [SSE](http://en.wikipedia.org/wiki/Streaming_SIMD_Extensions). - 好了,那是对的。`startup_32` 的地址是 `0x100000`。在我们知道了 `startup_32` 的地址之后,我们可以开始准备切换到[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)了。我们的下一个目标是建立栈并且确认 CPU 对长模式和[SSE](https://zh.wikipedia.org/wiki/SSE)的支持。 -Stack setup and CPU verification -栈的建立和CPU的确认 +栈的建立和 CPU 的确认 -------------------------------------------------------------------------------- -We could not setup the stack while we did not know the address of the `startup_32` label. We can imagine the stack as an array and the stack pointer register `esp` must point to the end of this array. Of course we can define an array in our code, but we need to know its actual address to configure stack pointer in a correct way. Let's look at the code: - -如果不知道 `startup_32` 标签的地址,我们无法建立栈。我们可以把栈看作是一个数组,并且栈指针寄存器 `esp` 必须指向数组的底部。当然我们可以在自己的代码里定义一个数组,但是我们需要知道其真实地址来正确配置栈指针。让我们看一下代码: +如果不知道 `startup_32` 标签的地址,我们无法建立栈。我们可以把栈看作是一个数组,并且栈指针寄存器`esp`必须指向数组的底部。当然我们可以在自己的代码里定义一个数组,但是我们需要知道其真实地址来正确配置栈指针。让我们看一下代码: ```assembly movl $boot_stack_end, %eax @@ -331,9 +272,7 @@ We could not setup the stack while we did not know the address of the `startup_3 movl %eax, %esp ``` -The `boots_stack_end` defined in the same [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) assembly source code file and located in the [.bss](https://en.wikipedia.org/wiki/.bss) section: - -`boots_stack_end` 定义在同一个汇编文件 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 中,位于 [.bss](https://en.wikipedia.org/wiki/.bss) 段: +`boots_stack_end` 被定义在同一个汇编文件 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 中,位于 [.bss](https://en.wikipedia.org/wiki/.bss) 段: ```assembly .bss @@ -345,13 +284,9 @@ boot_stack: boot_stack_end: ``` -First of all we put the address of `boot_stack_end` into the `eax` register. From now the `eax` register will contain address of the `boot_stack_end` where it was linked or in other words `0x0 + boot_stack_end`. To get the real address of the `boot_stack_end` we need to add the real address of the `startup_32`. As you remember, we have found this address above and put it to the `ebp` register. In the end, the register `eax` will contain real address of the `boot_stack_end` and we just need to put to the stack pointer. +首先,我们把 `boot_stack_end` 放到 `eax` 寄存器中。现在`eax`寄存器将包含`boot_stack_end`链接后的地址或者说`0x0 + boot_stack_end`。为了得到`boot_stack_end`的实际地址,我们需要加上 `startup_32`的实际地址。回忆一下,前面我们找到了这个地址并且把它存到了`ebp`寄存器中。最后,`eax`寄存器将会包含`boot_stack_end`的实际地址,我们只需要将其加到栈指针上。 -首先,我们把 `boot_stack_end` 放到 `eax` 寄存器中。现在 `eax` 寄存器将包含 `boot_stack_end` 链接后的地址或者说 `0x0 + boot_stack_end`。为了得到 `boot_stack_end` 的实际地址,我们需要加上 `startup_32` 的实际地址。回忆一下,前面我们找到了这个地址并且把它存到了 `ebp` 寄存器中。最后,`eax` 寄存器将会包含 `boot_stack_end` 的实际地址,我们只需要将其加到栈指针上。 - -After we have set up the stack, next step is CPU verification. As we are going to execute transition to the `long mode`, we need to check that the CPU supports `long mode` and `SSE`. We will do it by the call of the `verify_cpu` function: - -在外面建立了栈之后,下一步是 CPU 确认。既然我们将要切换到 `长模式`,我们需要检查 CPU 是否支持 `长模式` 和 `SSE`。我们将会在跳转到 `verify_cpu` 之后执行: +在外面建立了栈之后,下一步是 CPU 的确认。既然我们将要切换到`长模式`,我们需要检查 CPU 是否支持 `长模式` 和 `SSE`。我们将会在跳转到`verify_cpu`函数之后执行: ```assembly call verify_cpu @@ -359,13 +294,9 @@ After we have set up the stack, next step is CPU verification. As we are going t jnz no_longmode ``` -This function defined in the [arch/x86/kernel/verify_cpu.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/verify_cpu.S) assembly file and just contains a couple of calls to the [cpuid](https://en.wikipedia.org/wiki/CPUID) instruction. This instruction is used for getting information about the processor. In our case it checks `long mode` and `SSE` support and returns `0` on success or `1` on fail in the `eax` register. +这个函数定义在 [arch/x86/kernel/verify_cpu.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/verify_cpu.S) 中,只是包含了几个对 [cpuid](https://en.wikipedia.org/wiki/CPUID) 指令的调用。该指令用于获取处理器的信息。在我们的情况下,它检查了对`长模式`和`SSE`的支持,通过`eax`寄存器返回0表示成功,1表示失败。 -这个函数在 [arch/x86/kernel/verify_cpu.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/verify_cpu.S) 中定义,只是包含了几个对 [cpuid](https://en.wikipedia.org/wiki/CPUID) 指令的调用。该指令用于获取处理器的信息。在我们的情况下,它检查了 `长模式` 和 `SSE` 的支持,通过 `eax` 寄存器返回0表示成功,1表示失败。 - -If the value of the `eax` is not zero, we jump to the `no_longmode` label which just stops the CPU by the call of the `hlt` instruction while no hardware interrupt will not happen: - -如果 `eax` 的值不是 0 ,我们跳转到 `no_longmode` 标签,用 `hlt` 指令停止 CPU ,期间不会发生中断: +如果`eax`的值不是 0 ,我们跳转到`no_longmode`标签,用`hlt`指令停止 CPU ,期间不会发生硬件中断: ```assembly no_longmode: @@ -374,17 +305,12 @@ no_longmode: jmp 1b ``` -If the value of the `eax` register is zero, everything is ok and we are able to continue. - 如果 `eax` 的值为0,万事大吉,我们可以继续。 -Calculate relocation address 计算重定位地址 -------------------------------------------------------------------------------- -The next step is calculating relocation address for decompression if needed. First we need to know what it means for a kernel to be `relocatable`. We already know that the base address of the 32-bit entry point of the Linux kernel is `0x100000`. But that is a 32-bit entry point. Default base address of the Linux kernel is determined by the value of the `CONFIG_PHYSICAL_START` kernel configuration option and its default value is - `0x1000000` or `1 MB`. The main problem here is that if the Linux kernel crashes, a kernel developer must have a `rescue kernel` for [kdump](https://www.kernel.org/doc/Documentation/kdump/kdump.txt) which is configured to load from a different address. The Linux kernel provides special configuration option to solve this problem - `CONFIG_RELOCATABLE`. As we can read in the documentation of the Linux kernel: - -下一步是在必要的时候计算解压缩之后的地址。首先,我们需要知道内核重定位的意义。我们已经知道 Linux 内核的32位入口点地址位于 `0x100000`。但是那是一个32位的入口。默认的内核基地址由内核配置项 `CONFIG_PHYSICAL_START` 的值所确定,其默认值为 `0x100000` 或 `1 MB`。这里的主要问题是如果内核崩溃了,内核开发者需要一个配置于不同地址的 `救援内核` 来进行 [kdump](https://www.kernel.org/doc/Documentation/kdump/kdump.txt)。Linux 内核提供了特殊的配置选项以解决此问题 - `CONFIG_RELOCATABLE`。我们可以在内核文档中找到: +下一步是在必要的时候计算解压缩之后的地址。首先,我们需要知道内核重定位的意义。我们已经知道 Linux 内核的32位入口点地址位于`0x100000`。但是那是一个32位的入口。默认的内核基地址由内核配置项`CONFIG_PHYSICAL_START`的值所确定,其默认值为`0x100000` 或`1 MB`。这里的主要问题是如果内核崩溃了,内核开发者需要一个配置于不同地址加载的`救援内核`来进行 [kdump](https://www.kernel.org/doc/Documentation/kdump/kdump.txt)。Linux 内核提供了特殊的配置选项以解决此问题 - `CONFIG_RELOCATABLE`。我们可以在内核文档中找到: ``` This builds a kernel image that retains relocation information @@ -401,17 +327,13 @@ it has been loaded at and the compile time physical address 注意:如果 CONFIG_RELOCATABLE=y, 那么 内核将会从其被加载的位置运行,编译时的物理地址 (CONFIG_PHYSICAL_START) 将会被作为最低地址位置的限制。 ``` -In simple terms this means that the Linux kernel with the same configuration can be booted from different addresses. Technically, this is done by the compiling decompressor as [position independent code](https://en.wikipedia.org/wiki/Position-independent_code). If we look at [/arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile), we will see that the decompressor is indeed compiled with the `-fPIC` flag: - -简单来说,这意味着相同配置下的 Linux 内核可以从不同地址被启动。这是通过将程序以 [位置无关代码](https://zh.wikipedia.org/wiki/%E5%9C%B0%E5%9D%80%E6%97%A0%E5%85%B3%E4%BB%A3%E7%A0%81) 的形式编译来达到的。如果我们参考 [/arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile),我们将会看到解压器是用 `-fPIC` 标记编译的: +简单来说,这意味着相同配置下的 Linux 内核可以从不同地址被启动。这是通过将程序以 [位置无关代码](https://zh.wikipedia.org/wiki/%E5%9C%B0%E5%9D%80%E6%97%A0%E5%85%B3%E4%BB%A3%E7%A0%81) 的形式编译来达到的。如果我们参考 [/arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile),我们将会看到解压器的确是用`-fPIC`标记编译的: ```Makefile KBUILD_CFLAGS += -fno-strict-aliasing -fPIC ``` -When we are using position-independent code an address obtained by adding the address field of the command and the value of the program counter. We can load a code which is uses such addressing from any address. That's why we had to get the real physical address of `startup_32`. Now let's get back to the Linux kernel code. Our current goal is to calculate an address where we can relocate the kernel for decompression. Calculation of this address depends on `CONFIG_RELOCATABLE` kernel configuration option. Let's look at the code: - -当我们使用位置无关代码时,一段代码的地址是由一个控制地址加上程序计数器计算得到的。我们可以从任意一个地址加载使用这种方式寻址的代码。这就是为什么我们需要获得 `startup_32` 的实际地址。现在让我们回到 Linux 内核代码。我们目前的目标是计算出内核解压的地址。这个地址的计算取决于内核配置项 `CONFIG_RELOCATABLE` 。让我们看代码: +当我们使用位置无关代码时,一段代码的地址是由一个控制地址加上程序计数器计算得到的。我们可以从任意一个地址加载使用这种方式寻址的代码。这就是为什么我们需要获得`startup_32`的实际地址。现在让我们回到 Linux 内核代码。我们目前的目标是计算出内核解压的地址。这个地址的计算取决于内核配置项`CONFIG_RELOCATABLE`。让我们看代码: ```assembly #ifdef CONFIG_RELOCATABLE @@ -429,9 +351,7 @@ When we are using position-independent code an address obtained by adding the ad addl $z_extract_offset, %ebx ``` -Remember that value of the `ebp` register is the physical address of the `startup_32` label. If the `CONFIG_RELOCATABLE` kernel configuration option is enabled during kernel configuration, we put this address to the `ebx` register, align it to the `2M` and compare it with the `LOAD_PHYSICAL_ADDR` value. The `LOAD_PHYSICAL_ADDR` macro defined in the [arch/x86/include/asm/boot.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/boot.h) header file and it looks like this: - -记住 `ebp` 寄存器的值就是 `startup_32` 标签的物理地址。如果在内核配置中 `CONFIG_RELOCATABLE` 内核配置项开启,我们就把这个地址放到 `ebx` 寄存器中,对齐到 `2M` ,然后和 `LOAD_PHYSICAL_ADDR` 的值比较。`LOAD_PHYSICAL_ADDR` 宏在头文件 [arch/x86/include/asm/boot.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/boot.h) 定义,如下: +记住`ebp`寄存器的值就是`startup_32`标签的物理地址。如果在内核配置中`CONFIG_RELOCATABLE`内核配置项开启,我们就把这个地址放到`ebx`寄存器中,对齐到`2M`,然后和`LOAD_PHYSICAL_ADDR`的值比较。`LOAD_PHYSICAL_ADDR`宏在头文件 [arch/x86/include/asm/boot.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/boot.h) 定义,如下: ```C #define LOAD_PHYSICAL_ADDR ((CONFIG_PHYSICAL_START \ @@ -439,20 +359,13 @@ Remember that value of the `ebp` register is the physical address of the `startu & ~(CONFIG_PHYSICAL_ALIGN - 1)) ``` -As we can see it just expands to the aligned `CONFIG_PHYSICAL_ALIGN` value which represents physical address of where to load kernel. After comparison of the `LOAD_PHYSICAL_ADDR` and value of the `ebx` register, we add offset from the `startup_32` where to decompress the compressed kernel image. If the `CONFIG_RELOCATABLE` option is not enabled during kernel configuration, we just put default address where to load kernel and add `z_extract_offset` to it. - -我们可以看到该宏只是展开成对齐的 `CONFIG_PHYSICAL_ALIGN` 值,其表示了内核加载位置的物理地址。在比较了 `LOAD_PHYSICAL_ADDR` 和 `ebx` 的值之后,我们给 `startup_32` 加上偏移来获得解压内核镜像的地址。如果 `CONFIG_RELOCATABLE` 选项在内核配置时没有开启,我们就直接将默认的地址加上 `z_extract_offset`。 - -After all of these calculations we will have `ebp` which contains the address where we loaded it and `ebx` set to the address of where kernel will be moved after decompression. +我们可以看到该宏只是展开成对齐的`CONFIG_PHYSICAL_ALIGN`值,其表示了内核加载位置的物理地址。在比较了`LOAD_PHYSICAL_ADDR`和`ebx`的值之后,我们给`startup_32`加上偏移来获得解压内核镜像的地址。如果`CONFIG_RELOCATABLE`选项在内核配置时没有开启,我们就直接将默认的地址加上`z_extract_offset`。 在前面的操作之后,`ebp`包含了我们加载时的地址,`ebx` 被设为内核解压缩的目标地址。 -Preparation before entering long mode -进入长模式前的准备 +进入长模式前的准备工作 -------------------------------------------------------------------------------- -When we have the base address where we will relocate compressed kernel image we need to do the last preparation before we can transition to 64-bit mode. First we need to update the [Global Descriptor Table](https://en.wikipedia.org/wiki/Global_Descriptor_Table) for this: - 在我们得到了重定位内核镜像的基地址之后,我们需要做切换到64位模式之前的最后准备。首先,我们需要更新[全局描述符表](https://en.wikipedia.org/wiki/Global_Descriptor_Table): ```assembly @@ -461,9 +374,7 @@ When we have the base address where we will relocate compressed kernel image we lgdt gdt(%ebp) ``` -Here we put the base address from `ebp` register with `gdt` offset into the `eax` register. Next we put this address into `ebp` register with offset `gdt+2` and load the `Global Descriptor Table` with the `lgdt` instruction. To understand the magic with `gdt` offsets we need to look at the definition of the `Global Descriptor Table`. We can find its definition in the same source code [file](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S): - -在这里我们把 `ebp` 寄存器加上 `gdt` 偏移存到 `eax` 寄存器。接下来我们把这个地址放到 `ebp` 加上 `gdt+2` 偏移的位置上,并且用 `lgdt` 指令载入 `全局描述符表`。为了理解这个神奇的 `gdt` 偏移量,我们需要关注`全局描述符表`的定义。我们可以在同一个[源文件](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S)中找到其定义: +在这里我们把`ebp`寄存器加上`gdt`偏移存到`eax`寄存器。接下来我们把这个地址放到`ebp`加上`gdt+2`偏移的位置上,并且用`lgdt`指令载入`全局描述符表`。为了理解这个神奇的`gdt` 偏移量,我们需要关注`全局描述符表`的定义。我们可以在同一个[源文件](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S)中找到其定义: ```assembly .data @@ -479,23 +390,14 @@ gdt: gdt_end: ``` -We can see that it is located in the `.data` section and contains five descriptors: `null` descriptor, kernel code segment, kernel data segment and two task descriptors. We already loaded the `Global Descriptor Table` in the previous [part](https://github.com/0xAX/linux-insides/blob/master/Booting/linux-bootstrap-3.md), and now we're doing almost the same here, but descriptors with `CS.L = 1` and `CS.D = 0` for execution in `64` bit mode. As we can see, the definition of the `gdt` starts from two bytes: `gdt_end - gdt` which represents last byte in the `gdt` table or table limit. The next four bytes contains base address of the `gdt`. Remember that the `Global Descriptor Table` is stored in the `48-bits GDTR` which consists of two parts: - -我们可以看到其位于 `.data` 段,并且包含了5个描述符: `null`、内核代码段、内核数据段和其他两个任务描述符。我们已经在[上一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md)载入了`全局描述符表`,和我们现在做的差不多,但是描述符改为 `CS.L = 1` `CS.D = 0` 从而在 `64` 位模式下执行。我们可以看到, `gdt` 的定义从两个字节开始: `gdt_end - gdt`,代表了 `gdt` 表的最后一个字节,或者说表的范围。接下来的4个字节包含了 `gdt` 的基地址。记住 `全局描述符表` 保存在 `48位 GDTR-全局描述符表寄存器`中,由两个部分组成: - -* size(16-bit) of global descriptor table; -* address(32-bit) of the global descriptor table. +我们可以看到其位于`.data`段,并且包含了5个描述符:`null`、内核代码段、内核数据段和其他两个任务描述符。我们已经在[上一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md)载入了`全局描述符表`,和我们现在做的差不多,但是描述符改为`CS.L = 1` `CS.D = 0` 从而在`64`位模式下执行。我们可以看到,`gdt`的定义从两个字节开始:`gdt_end - gdt`,代表了`gdt`表的最后一个字节,或者说表的范围。接下来的4个字节包含了`gdt`的基地址。记住`全局描述符表`保存在`48位 GDTR-全局描述符表寄存器`中,由两个部分组成: * 全局描述符表的大小 (16位) * 全局描述符表的基址 (32位) -So, we put address of the `gdt` to the `eax` register and then we put it to the `.long gdt` or `gdt+2` in our assembly code. From now we have formed structure for the `GDTR` register and can load the `Global Descriptor Table` with the `lgtd` instruction. +所以,我们把`gdt`的地址放到`eax`寄存器,然后存到 `.long gdt` 或者 `gdt+2`。现在我们已经建立了 `GDTR` 寄存器的结构,并且可以用 `lgdt` 指令载入`全局描述符表`了。 -所以,我们把 `gdt` 的地址放到 `eax`寄存器,然后存到 `.long gdt` 或者 `gdt+2`。现在我们已经建立了 `GDTR` 寄存器的结构,并且可以用 `lgdt` 指令载入`全局描述符表`了。 - -After we have loaded the `Global Descriptor Table`, we must enable [PAE](http://en.wikipedia.org/wiki/Physical_Address_Extension) mode by putting the value of the `cr4` register into `eax`, setting 5 bit in it and loading it again into `cr4`: - -在我们载入`全局描述符表`之后,我们必须启动 [PAE](http://en.wikipedia.org/wiki/Physical_Address_Extension) 模式。方法是将 `cr4` 寄存器的值传入 `eax` ,将第5位置1,然后再写回 `cr4`。 +在我们载入`全局描述符表`之后,我们必须启用 [PAE](http://en.wikipedia.org/wiki/Physical_Address_Extension) 模式。方法是将`cr4`寄存器的值传入`eax`,将第5位置1,然后再写回`cr4`。 ```assembly movl %cr4, %eax @@ -503,51 +405,26 @@ After we have loaded the `Global Descriptor Table`, we must enable [PAE](http:// movl %eax, %cr4 ``` -Now we are almost finished with all preparations before we can move into 64-bit mode. The last step is to build page tables, but before that, here is some information about long mode. - 现在我们已经接近完成进入64位模式前的所有准备工作了。最后一步是建立页表,但是在此之前,这里有一些关于长模式的知识。 -Long mode 长模式 -------------------------------------------------------------------------------- -[Long mode](https://en.wikipedia.org/wiki/Long_mode) is the native mode for [x86_64](https://en.wikipedia.org/wiki/X86-64) processors. First let's look at some differences between `x86_64` and the `x86`. +[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)是 [x86_64](https://en.wikipedia.org/wiki/X86-64) 系列处理器的原生模式。首先让我们看一看`x86_64`和`x86`的一些区别。 -[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)是 [x86_64](https://en.wikipedia.org/wiki/X86-64) 系列处理器的原生模式。首先让我们看一看 `x86_64` 和 `x86` 的一些区别。 +`64位`模式提供了一些新特性,比如: -The `64-bit` mode provides features such as: - -* New 8 general purpose registers from `r8` to `r15` + all general purpose registers are 64-bit now; -* 64-bit instruction pointer - `RIP`; -* New operating mode - Long mode; -* 64-Bit Addresses and Operands; -* RIP Relative Addressing (we will see an example if it in the next parts). - -`64位`模式提供了一些新特性如: - -* 从 `r8` 到 `r15` 8个新的通用寄存器,并且所有通用寄存器都是64位的了。 +* 从`r8`到`r15`8个新的通用寄存器,并且所有通用寄存器都是64位的了。 * 64位指令指针 - `RIP`; * 新的操作模式 - 长模式; * 64位地址和操作数; -* RIP 相对寻址 (我们将会在接下来的章节看到). - -Long mode is an extension of legacy protected mode. It consists of two sub-modes: +* RIP 相对寻址 (我们将会在接下来的章节看到一个例子). 长模式是一个传统保护模式的扩展,其由两个子模式构成: -* 64-bit mode; -* compatibility mode. - * 64位模式 * 兼容模式 -To switch into `64-bit` mode we need to do following things: - -* To enable [PAE](https://en.wikipedia.org/wiki/Physical_Address_Extension); -* To build page tables and load the address of the top level page table into the `cr3` register; -* To enable `EFER.LME`; -* To enable paging. - 为了切换到 `64位` 模式,我们需要完成以下操作: * 启用 [PAE](https://en.wikipedia.org/wiki/Physical_Address_Extension); @@ -556,37 +433,22 @@ To switch into `64-bit` mode we need to do following things: * 启用分页; -We already enabled `PAE` by setting the `PAE` bit in the `cr4` control register. Our next goal is to build structure for [paging](https://en.wikipedia.org/wiki/Paging). We will see this in next paragraph. +我们已经通过设置`cr4`控制寄存器中的`PAE`位启动`PAE`了。在下一个段落,我们就要建立[分页](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)的结构了。 -我们已经通过设置 `cr4` 控制寄存器中的 `PAE` 位启动 `PAE` 了。在下一段,我们接下来就要建立[分页](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)的结构了。 - -Early page tables initialization 初期页表初始化 -------------------------------------------------------------------------------- -So, we already know that before we can move into `64-bit` mode, we need to build page tables, so, let's look at the building of early `4G` boot page tables. - -现在,我们已经知道了在进入 `64位` 模式之前,我们需要先建立页表,那么就让我们看看如何建立 `4G` 启动页表。 - -**NOTE: I will not describe theory of virtual memory here, if you need to know more about it, see links in the end of this part** +现在,我们已经知道了在进入`64位`模式之前,我们需要先建立页表,那么就让我们看看如何建立初期的`4G`启动页表。 **注意:我不会在这里解释虚拟内存的理论,如果你想知道更多,查看本节最后的链接** -The Linux kernel uses `4-level` paging, and generally we build 6 page tables: - -* One `PML4` or `Page Map Level 4` table with one entry; -* One `PDP` or `Page Directory Pointer` table with four entries; -* Four Page Directory tables with `2048` entries. - -Linux 内核使用 `4级` 页表,通常我们会建立6个页表: +Linux 内核使用`4级`页表,通常我们会建立6个页表: * 1个 `PML4` 或称为 `4级页映射` 表,包含1个项; * 1个 `PDP` 或称为 `页目录指针` 表,包含4个项; * 4个 页目录表,包含 `2048` 个项; -Let's look at the implementation of this. First of all we clear the buffer for the page tables in memory. Every table is `4096` bytes, so we need clear `24` kilobytes buffer: - -让我们看看其实现方式。首先我们在内存中为页表清理一块缓存。每个表都是 `4096` 字节,所以我们需要 `24` KB 的空间: +让我们看看其实现方式。首先我们在内存中为页表清理一块缓存。每个表都是 `4096` 字节,所以我们需要 `24`KB 的空间: ```assembly leal pgtable(%ebx), %edi @@ -595,11 +457,7 @@ Let's look at the implementation of this. First of all we clear the buffer for t rep stosl ``` -We put the address of the `pgtable` relative to `ebx` (remember that `ebx` contains the address to relocate the kernel for decompression) to the `edi` register, clear `eax` register and `6144` to the `ecx` register. The `rep stosl` instruction will write value of the `eax` to the `edi`, increase value of the `edi` register on `4` and decrease value of the `ecx` register on `4`. This operation will be repeated while value of the `ecx` register will be greater than zero. That's why we put magic `6144` to the `ecx`. - -我们把和 `ebx` 相关的 `pgtable` 的地址放到 `edi` 寄存器中,清空 `eax` 寄存器,并将 `ecx` 赋值为 `6144` 。`rep stosl` 指令将会把 `eax` 的值写到 `edi` 指向的地址,然后给 `edi` 加 4 ,`ecx` 减 4 ,重复直到 `ecx` 小于等于 0 。所以我们把 `6144` 赋值给 `ecx` 。 - -The `pgtable` is defined in the end of [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) assembly file and looks: +我们把和`ebx`相关的`pgtable`的地址放到`edi`寄存器中,清空`eax`寄存器,并将`ecx`赋值为`6144`。`rep stosl`指令将会把`eax`的值写到`edi`指向的地址,然后给`edi`加 4 ,`ecx`减 4 ,重复直到`ecx`小于等于 0 。所以我们才把`6144`赋值给 `ecx` 。 `pgtable` 定义在 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 的最后: @@ -610,12 +468,8 @@ pgtable: .fill 6*4096, 1, 0 ``` -As we can see, it is located in the `.pgtable` section and its size is `24` kilobytes. - 我们可以看到,其位于 `.pgtable` 段,大小为 `24KB`。 -After we have got buffer for the `pgtable` structure, we can start to build the top level page table - `PML4` - with: - 在我们为`pgtable`分配了空间之后,我们可以开始构建顶级页表 - `PML4` : ```assembly @@ -624,11 +478,7 @@ After we have got buffer for the `pgtable` structure, we can start to build the movl %eax, 0(%edi) ``` -Here again, we put the address of the `pgtable` relative to `ebx` or in other words relative to address of the `startup_32` to the `edi` register. Next we put this address with offset `0x1007` in the `eax` register. The `0x1007` is `4096` bytes which is the size of the `PML4` plus `7`. The `7` here represents flags of the `PML4` entry. In our case, these flags are `PRESENT+RW+USER`. In the end we just write first the address of the first `PDP` entry to the `PML4`. - -还是在这里,我们把和 `ebx` 相关的,或者说和 `startup_32`相关的 `pgtable` 的地址放到 `ebi` 寄存器。接下来我们把相对此地址偏移 `0x1007` 的地址放到 `eax` 寄存器中。 `0x1007` 是 `PML4` 的大小 `4096` 加上 `7`。这里的 `7` 代表了 `PML4` 的项标记。在我们这里,这些标记是 `PRESENT+RW+USER`。在最后我们把第一个 `PDP(页目录指针)` 项的地址写到 `PML4` 中。 - -In the next step we will build four `Page Directory` entries in the `Page Directory Pointer` table with the same `PRESENT+RW+USE` flags: +还是在这里,我们把和`ebx`相关的,或者说和`startup_32`相关的`pgtable`的地址放到`ebi`寄存器。接下来我们把相对此地址偏移`0x1007`的地址放到`eax`寄存器中。`0x1007`是`PML4`的大小 `4096` 加上 `7`。这里的`7`代表了`PML4`的项标记。在我们这里,这些标记是`PRESENT+RW+USER`。在最后我们把第一个 `PDP(页目录指针)` 项的地址写到 `PML4` 中。 在接下来的一步,我们将会在 `页目录指针(PDP)` 表(3级页表)建立 4 个带有`PRESENT+RW+USE`标记的`Page Directory (2级页表)`项: @@ -643,9 +493,7 @@ In the next step we will build four `Page Directory` entries in the `Page Direct jnz 1b ``` -We put the base address of the page directory pointer which is `4096` or `0x1000` offset from the `pgtable` table in `edi` and the address of the first page directory pointer entry in `eax` register. Put `4` in the `ecx` register, it will be a counter in the following loop and write the address of the first page directory pointer table entry to the `edi` register. After this `edi` will contain the address of the first page directory pointer entry with flags `0x7`. Next we just calculate the address of following page directory pointer entries where each entry is `8` bytes, and write their addresses to `eax`. The last step of building paging structure is the building of the `2048` page table entries with `2-MByte` pages: - -我们把3级页目录指针表的基地址(从`pgtable`表偏移`4096`或者`0x1000`)放到`edi`,把第一个2级页目录指针表的首项的地址放到`eax`寄存器。把`4`赋值给`ecx`寄存器,其将会作为接下来循环的计数器,然后将第一个页目录指针项写到`edi`指向的地址。之后,`edi`将会包含带有标记`0x7`的第一个页目录指针项的地址。接下来我们就计算后面的几个页目录指针项的地址,每个占8字节,把地址赋值给`eax`,然后回到循环开头将其写入`edi`所在地址。建立页表结构的最后一步就是建立`2048`个`2MB`页表项。 +我们把3级页目录指针表的基地址(从`pgtable`表偏移`4096`或者`0x1000`)放到`edi`,把第一个2级页目录指针表的首项的地址放到`eax`寄存器。把`4`赋值给`ecx`寄存器,其将会作为接下来循环的计数器,然后将第一个页目录指针项写到`edi`指向的地址。之后,`edi`将会包含带有标记`0x7`的第一个页目录指针项的地址。接下来我们就计算后面的几个页目录指针项的地址,每个占8字节,把地址赋值给`eax`,然后回到循环开头将其写入`edi`所在地址。建立页表结构的最后一步就是建立`2048`个`2MB`页的页表项。 ```assembly leal pgtable + 0x2000(%ebx), %edi @@ -658,8 +506,6 @@ We put the base address of the page directory pointer which is `4096` or `0x1000 jnz 1b ``` -Here we do almost the same as in the previous example, all entries will be with flags - `$0x00000183` - `PRESENT + WRITE + MBZ`. In the end we will have `2048` pages with `2-MByte` page or: - 在这里我们做的几乎和上面一样,所有的表项都带着标记 - `$0x00000183` - `PRESENT + WRITE + MBZ`。最后我们将会拥有`2048`个`2MB`页的页表,或者说: ```python @@ -667,8 +513,6 @@ Here we do almost the same as in the previous example, all entries will be with 4294967296 ``` -`4G` page table. We just finished to build our early page table structure which maps `4` gigabytes of memory and now we can put the address of the high-level page table - `PML4` - in `cr3` control register: - `4G`页表。我们刚刚完成我们的初期页表结构,其映射了`4G`大小的内存,现在我们可以把高级页表`PML4`的地址放到`cr3`寄存器中了: ```assembly @@ -676,16 +520,11 @@ Here we do almost the same as in the previous example, all entries will be with movl %eax, %cr3 ``` -That's all. All preparation are finished and now we can see transition to the long mode. - 全部结束了。所有的准备工作都已经完成,我们可以开始看如何切换到长模式了。 -Transition to the 64-bit mode 切换到长模式 -------------------------------------------------------------------------------- -First of all we need to set the `EFER.LME` flag in the [MSR](http://en.wikipedia.org/wiki/Model-specific_register) to `0xC0000080`: - 首先我们需要设置[MSR](http://en.wikipedia.org/wiki/Model-specific_register)中的`EFER.LME`标记为`0xC0000080`: ```assembly @@ -695,12 +534,8 @@ First of all we need to set the `EFER.LME` flag in the [MSR](http://en.wikipedia wrmsr ``` -Here we put the `MSR_EFER` flag (which is defined in [arch/x86/include/uapi/asm/msr-index.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/msr-index.h#L7)) in the `ecx` register and call `rdmsr` instruction which reads the [MSR](http://en.wikipedia.org/wiki/Model-specific_register) register. After `rdmsr` executes, we will have the resulting data in `edx:eax` which depends on the `ecx` value. We check the `EFER_LME` bit with the `btsl` instruction and write data from `eax` to the `MSR` register with the `wrmsr` instruction. - 在这里我们把`MSR_EFER`标记(在 [arch/x86/include/uapi/asm/msr-index.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/msr-index.h#L7) 定义)放到`ecx`寄存器中,然后调用`rdmsr`指令读取[MSR](http://en.wikipedia.org/wiki/Model-specific_register)寄存器。在`rdmsr`执行之后,我们将会获得`edx:eax`中的结果值,其取决于`ecx`的值。我们通过`btsl`指令检查`EFER_LME`位,并且通过`wrmsr`指令将`eax`的数据写入`MSR`寄存器。 -In the next step we push the address of the kernel segment code to the stack (we defined it in the GDT) and put the address of the `startup_64` routine in `eax`. - 下一步我们将内核段代码地址入栈(我们在 GDT 中定义了),然后将`startup_64`的地址导入`eax`。 ```assembly @@ -708,8 +543,6 @@ In the next step we push the address of the kernel segment code to the stack (we leal startup_64(%ebp), %eax ``` -After this we push this address to the stack and enable paging by setting `PG` and `PE` bits in the `cr0` register: - 在这之后我们把这个地址入栈然后通过设置`cr0`寄存器中的`PG`和`PE`启用分页: ```assembly @@ -717,19 +550,13 @@ After this we push this address to the stack and enable paging by setting `PG` a movl %eax, %cr0 ``` -and execute: - 然后执行: ```assembly lret ``` -instruction. Remember that we pushed the address of the `startup_64` function to the stack in the previous step, and after the `lret` instruction, the CPU extracts the address of it and jumps there. - -指令。记住前一步我们已经将`startup_64`函数的地址入栈,在`lret`指令之后,CPU 丢弃了其地址跳转到了这里。 - -After all of these steps we're finally in 64-bit mode: +指令。记住前一步我们已经将`startup_64`函数的地址入栈,在`lret`指令之后,CPU 取出了其地址跳转到那里。 这些步骤之后我们最后来到了64位模式: @@ -742,24 +569,16 @@ ENTRY(startup_64) .... ``` -That's all! 就是这样! -Conclusion 总结 -------------------------------------------------------------------------------- -This is the end of the fourth part linux kernel booting process. If you have questions or suggestions, ping me in twitter [0xAX](https://twitter.com/0xAX), drop me [email](anotherworldofworld@gmail.com) or just create an [issue](https://github.com/0xAX/linux-insides/issues/new). - 这是 linux 内核启动流程的第4部分。如果你有任何的问题或者建议,你可以留言,也可以直接发消息给我[twitter](https://twitter.com/0xAX)或者创建一个 [issue](https://github.com/0xAX/linux-insides/issues/new)。 -In the next part we will see kernel decompression and many more. - 下一节我们将会看到内核解压缩流程和其他更多。 -**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](https://github.com/0xAX/linux-internals).** 相关链接 From 9cad5ee74f928282e7953e44376fb6e1b37a3c77 Mon Sep 17 00:00:00 2001 From: zmj1316 Date: Mon, 29 Feb 2016 22:59:54 +0800 Subject: [PATCH 07/90] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Booting/linux-bootstrap-4.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Booting/linux-bootstrap-4.md b/Booting/linux-bootstrap-4.md index 214038c..ecb253f 100644 --- a/Booting/linux-bootstrap-4.md +++ b/Booting/linux-bootstrap-4.md @@ -8,7 +8,7 @@ **注意:这部分将会有大量的汇编代码,如果你不熟悉汇编,建议你找本书参考一下。** -在[上一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md),我们停在了跳转到位于[arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S)的 32 位入口点这一步: +在[上一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md),我们停在了跳转到位于 [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S) 的 32 位入口点这一步: ```assembly jmpl *%eax @@ -259,7 +259,7 @@ ebp 0x100000 0x100000 ... ``` -好了,那是对的。`startup_32` 的地址是 `0x100000`。在我们知道了 `startup_32` 的地址之后,我们可以开始准备切换到[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)了。我们的下一个目标是建立栈并且确认 CPU 对长模式和[SSE](https://zh.wikipedia.org/wiki/SSE)的支持。 +好了,那是对的。`startup_32` 的地址是 `0x100000`。在我们知道了 `startup_32` 的地址之后,我们可以开始准备切换到[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)了。我们的下一个目标是建立栈并且确认 CPU 对长模式和 [SSE](https://zh.wikipedia.org/wiki/SSE) 的支持。 栈的建立和 CPU 的确认 -------------------------------------------------------------------------------- @@ -444,9 +444,9 @@ gdt_end: Linux 内核使用`4级`页表,通常我们会建立6个页表: -* 1个 `PML4` 或称为 `4级页映射` 表,包含1个项; -* 1个 `PDP` 或称为 `页目录指针` 表,包含4个项; -* 4个 页目录表,包含 `2048` 个项; +* 1 个 `PML4` 或称为 `4级页映射` 表,包含 1 个项; +* 1 个 `PDP` 或称为 `页目录指针` 表,包含 4 个项; +* 4 个 页目录表,包含 `2048` 个项; 让我们看看其实现方式。首先我们在内存中为页表清理一块缓存。每个表都是 `4096` 字节,所以我们需要 `24`KB 的空间: @@ -493,7 +493,7 @@ pgtable: jnz 1b ``` -我们把3级页目录指针表的基地址(从`pgtable`表偏移`4096`或者`0x1000`)放到`edi`,把第一个2级页目录指针表的首项的地址放到`eax`寄存器。把`4`赋值给`ecx`寄存器,其将会作为接下来循环的计数器,然后将第一个页目录指针项写到`edi`指向的地址。之后,`edi`将会包含带有标记`0x7`的第一个页目录指针项的地址。接下来我们就计算后面的几个页目录指针项的地址,每个占8字节,把地址赋值给`eax`,然后回到循环开头将其写入`edi`所在地址。建立页表结构的最后一步就是建立`2048`个`2MB`页的页表项。 +我们把 3 级页目录指针表的基地址(从`pgtable`表偏移`4096`或者`0x1000`)放到`edi`,把第一个 2 级页目录指针表的首项的地址放到`eax`寄存器。把`4`赋值给`ecx`寄存器,其将会作为接下来循环的计数器,然后将第一个页目录指针项写到`edi`指向的地址。之后,`edi`将会包含带有标记`0x7`的第一个页目录指针项的地址。接下来我们就计算后面的几个页目录指针项的地址,每个占 8 字节,把地址赋值给`eax`,然后回到循环开头将其写入`edi`所在地址。建立页表结构的最后一步就是建立`2048`个`2MB`页的页表项。 ```assembly leal pgtable + 0x2000(%ebx), %edi @@ -520,12 +520,12 @@ pgtable: movl %eax, %cr3 ``` -全部结束了。所有的准备工作都已经完成,我们可以开始看如何切换到长模式了。 +这样就全部结束了。所有的准备工作都已经完成,我们可以开始看如何切换到长模式了。 切换到长模式 -------------------------------------------------------------------------------- -首先我们需要设置[MSR](http://en.wikipedia.org/wiki/Model-specific_register)中的`EFER.LME`标记为`0xC0000080`: +首先我们需要设置 [MSR](http://en.wikipedia.org/wiki/Model-specific_register) 中的`EFER.LME`标记为`0xC0000080`: ```assembly movl $MSR_EFER, %ecx @@ -534,7 +534,7 @@ pgtable: wrmsr ``` -在这里我们把`MSR_EFER`标记(在 [arch/x86/include/uapi/asm/msr-index.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/msr-index.h#L7) 定义)放到`ecx`寄存器中,然后调用`rdmsr`指令读取[MSR](http://en.wikipedia.org/wiki/Model-specific_register)寄存器。在`rdmsr`执行之后,我们将会获得`edx:eax`中的结果值,其取决于`ecx`的值。我们通过`btsl`指令检查`EFER_LME`位,并且通过`wrmsr`指令将`eax`的数据写入`MSR`寄存器。 +在这里我们把`MSR_EFER`标记(在 [arch/x86/include/uapi/asm/msr-index.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/msr-index.h#L7) 中定义)放到`ecx`寄存器中,然后调用`rdmsr`指令读取 [MSR](http://en.wikipedia.org/wiki/Model-specific_register) 寄存器。在`rdmsr`执行之后,我们将会获得`edx:eax`中的结果值,其取决于`ecx`的值。我们通过`btsl`指令检查`EFER_LME`位,并且通过`wrmsr`指令将`eax`的数据写入`MSR`寄存器。 下一步我们将内核段代码地址入栈(我们在 GDT 中定义了),然后将`startup_64`的地址导入`eax`。 @@ -575,11 +575,11 @@ ENTRY(startup_64) 总结 -------------------------------------------------------------------------------- -这是 linux 内核启动流程的第4部分。如果你有任何的问题或者建议,你可以留言,也可以直接发消息给我[twitter](https://twitter.com/0xAX)或者创建一个 [issue](https://github.com/0xAX/linux-insides/issues/new)。 +这是 linux 内核启动流程的第4部分。如果你有任何的问题或者建议,你可以留言,也可以直接发消息给我 [twitter](https://twitter.com/0xAX) 或者创建一个 [issue](https://github.com/0xAX/linux-insides/issues/new)。 下一节我们将会看到内核解压缩流程和其他更多。 -**英文不是我的母语。如果你发现我的英文描述有任何问题,请提交一个PR到[linux-insides](https://github.com/0xAX/linux-internals).** +**英文不是我的母语。如果你发现我的英文描述有任何问题,请提交一个PR到 [linux-insides](https://github.com/0xAX/linux-internals)。** 相关链接 -------------------------------------------------------------------------------- From cd08e900adc8794707814ab9fbad027ac56853e6 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Mon, 29 Feb 2016 14:57:04 -0500 Subject: [PATCH 08/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 181 ++++++++++++++++------------------- 1 file changed, 83 insertions(+), 98 deletions(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 5d32381..4d83948 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -55,24 +55,24 @@ PhysicalAddress = Segment * 16 + Offset '0x10ffef' ``` -这超出1MB65519字节。既然只有1MB在实模式中可以访问,`0x10ffef` 变成有[A20](https://en.wikipedia.org/wiki/A20_line)缺陷的 `0x00ffef`。 +这超出1MB 65519字节。既然只有1MB在实模式中可以访问,`0x10ffef` 变成有[A20](https://en.wikipedia.org/wiki/A20_line)缺陷的 `0x00ffef`。 我们知道实模式和内存地址。回到复位后的寄存器值。 -`CS` register consists of two parts: the visible segment selector and hidden base address. We know predefined `CS` base and `IP` value, logical address will be: +`CS` 寄存器包含两个部分:可视段选择器和隐含基址。 结合之前定义的 `CS` 基址和 `IP` 值,逻辑地址应该是: ``` 0xffff0000:0xfff0 ``` -In this way starting address formed by adding the base address to the value in the EIP register: +这种形式的起始地址为EIP寄存器里的值加上基址地址: ```python >>> 0xffff0000 + 0xfff0 '0xfffffff0' ``` -We get `0xfffffff0` which is 4GB - 16 bytes. This point is the [Reset vector](http://en.wikipedia.org/wiki/Reset_vector). This is the memory location at which CPU expects to find the first instruction to execute after reset. It contains a [jump](http://en.wikipedia.org/wiki/JMP_%28x86_instruction%29) instruction which usually points to the BIOS entry point. For example, if we look in [coreboot](http://www.coreboot.org/) source code, we will see it: +得到的 `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" @@ -84,7 +84,8 @@ reset_vector: ... ``` -We can see here the jump instruction [opcode](http://ref.x86asm.net/coder32.html#xE9) - 0xe9 to the address `_start - ( . + 2)`. And we can see that `reset` section is 16 bytes and starts at `0xfffffff0`: +跳转指令 [opcode](http://ref.x86asm.net/coder32.html#xE9) - 0xe9 到地址 `_start - ( . + 2)` 。 `reset` 段是16字节, 起始于 +`0xfffffff0` : ``` SECTIONS { @@ -98,7 +99,7 @@ SECTIONS { } ``` -Now the BIOS has started to work. After initializing and checking the hardware, it needs to find a bootable device. A boot order is stored in the BIOS configuration. The function of boot order is to control which devices the kernel attempts to boot. In the case of attempting to boot a hard drive, the BIOS tries to find a boot sector. On hard drives partitioned with an MBR partition layout, the boot sector is stored in the first 446 bytes of the first sector (512 bytes). The final two bytes of the first sector are `0x55` and `0xaa` which signals the BIOS that the device is bootable. For example: +现在BIOS已经开始工作了。在初始化和检查硬件之后,需要寻找可引导设备。引导顺序储存在BIOS配置中。引导顺序的功能是控制内核尝试引导的设备。对于尝试引导一个硬件,BIOS尝试寻找引导扇区。在硬盘分区上有一个MBR分区布局,引导扇区储存在第一个扇区(512字节)的起始446字节。剩下的第一个扇区的两个字节是 `0x55` 和 `0xaa` ,发出设备可引导的信号给BIOS。举个例子: ```assembly ; @@ -122,45 +123,45 @@ db 0x55 db 0xaa ``` -Build and run it with: +构建并运行: ``` nasm -f bin boot.nasm && qemu-system-x86_64 boot ``` -This will instruct [QEMU](http://qemu.org) to use the `boot` binary we just built as a disk image. Since the binary generated by the assembly code above fulfills the requirements of the boot sector (the origin is set to `0x7c00`, and we end with the magic sequence). QEMU will treat the binary as the master boot record(MBR) of a disk image. +这让 [QEMU](http://qemu.org) 使用刚才新建的 `boot` 二进制文件作为磁盘镜像。由于这个二进制文件是由上述汇编语言产生,它满足引导扇区(起始设为 `0x7c00`, 用Magic Sequence结束)的需求。QEMU将这个二进制文件作为磁盘镜像的主引导记录(MBR)。 -We will see: +将看到: ![Simple bootloader which prints only `!`](http://oi60.tinypic.com/2qbwup0.jpg) -In this example we can see that this code will be executed in 16 bit real mode and will start at 0x7c00 in memory. After the start it calls the [0x10](http://www.ctyme.com/intr/rb-0106.htm) interrupt which just prints `!` symbol. It fills rest of 510 bytes with zeros and finish with two magic bytes `0xaa` and `0x55`. +在这个例子中,这段代码被执行在16位的实模式,起始于内存0x7c00。之后调用 [0x10](http://www.ctyme.com/intr/rb-0106.htm) 中断打印 `!` 符号。用0填充剩余的510字节并用两个Magic Bytes `0xaa` 和 `0x55` 结束。 -You can see binary dump of it with `objdump` util: +可以使用 `objdump` 工具来查看转储信息: ``` nasm -f bin boot.nasm objdump -D -b binary -mi386 -Maddr16,data16,intel boot ``` -A real-world boot sector has code for continuing the boot process and the partition table instead of a bunch of 0's and an exclamation point :) Ok so, from this point onwards BIOS hands over the control to the bootloader and we can go ahead. +一个真实的启动扇区包含了分区表,已经用来启动系统的指令,而不是像我们上面的程序,只是输出了一个感叹号就结束了。从启动扇区的代码被执行开始,BIOS 就将系统的控制权转移给了引导程序,让我们继续往下看看引导程序都做了些什么。 -**NOTE**: As you can read above the CPU is in real mode. In real mode, calculating the physical address in memory is done as following: +**NOTE**: 强调一点,上面的引导程序是运行在实模式下的,因此 CPU 是使用下面的公式进行物理地址的计算的: ``` PhysicalAddress = Segment * 16 + Offset ``` -Same as I mentioned before. But we have only 16 bit general purpose registers. The maximum value of 16 bit register is: `0xffff`; So if we take the biggest values the result will be: +而且正如我前面所说的,在实模式下,CPU 只能使用16位的通用寄存器。16位寄存器能够表达的最大数值是:`0xffff` ,所以按照上面的公式计算出的最大物理地址是: ```python >>> hex((0xffff * 16) + 0xffff) '0x10ffef' ``` -Where `0x10ffef` is equal to `1MB + 64KB - 16b`. But a [8086](https://en.wikipedia.org/wiki/Intel_8086) processor, which was the first processor with real mode. It had 20 bit address line and `2^20 = 1048576.0` is 1MB. So, it means that the actual memory available is 1MB. +这个地址在 [8086](https://en.wikipedia.org/wiki/Intel_8086) 处理器下,将被转换成地址 `0x0ffef`, 原因是因为,8086 cpu 只有20位地址线,只能表示 ``2^20 = 1MB` 的地址,而上面这个地址已经超出了 1MB 地址的范围,所以 CPU 就舍弃了最高位。 -General real mode's memory map is: +实模式下的 1MB 地址空间分配表: ``` 0x00000000 - 0x000003FF - Real Mode Interrupt Vector Table @@ -176,24 +177,24 @@ General real mode's memory map is: 0x000F0000 - 0x000FFFFF - System BIOS ``` -But stop, at the beginning of post I wrote that first instruction executed by the CPU is located at the address `0xFFFFFFF0`, which is much bigger than `0xFFFFF` (1MB). How can CPU access it in real mode? As I write about it and you can read in [coreboot](http://www.coreboot.org/Developer_Manual/Memory_map) documentation: +如果你的记性不错,在看到这张表的时候,一定会跳出来一个问题。在上面的章节中,我说了 CPU 执行的第一条指令是在地址 `0xFFFFFFF0` 处,这个地址远远大于 `0xFFFFF` ( 1MB )。那么实模式下的 CPU 是如何访问到这个地址的呢?文档 [coreboot](http://www.coreboot.org/Developer_Manual/Memory_map) 给出了答案: ``` 0xFFFE_0000 - 0xFFFF_FFFF: 128 kilobyte ROM mapped into address space ``` -At the start of execution BIOS is not in RAM, it is located in the ROM. +`0xFFFFFFF0` 这个地址被映射到了 ROM,因此 CPU 执行的第一条指令来自于 ROM,而不是 RAM。 -Bootloader +引导程序 -------------------------------------------------------------------------------- -There are a number of bootloaders which can boot Linux, such as [GRUB 2](https://www.gnu.org/software/grub/) and [syslinux](http://www.syslinux.org/wiki/index.php/The_Syslinux_Project). The Linux kernel has a [Boot protocol](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt) which specifies the requirements for bootloaders to implement Linux support. This example will describe GRUB 2. +在现实世界中,要启动 Linux 系统,可以有多种引导程序可以选择。比如 [GRUB 2](https://www.gnu.org/software/grub/) 和 [syslinux](http://www.syslinux.org/wiki/index.php/The_Syslinux_Project)。Linux内核通过 [Boot protocol](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt) 来定义应该如何实现引导程序。在这里我们将之介绍 GRUB 2。 -Now that the BIOS has chosen a boot device and transferred control to the boot sector code, execution starts from [boot.img](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/boot/i386/pc/boot.S;hb=HEAD). This code is very simple due to the limited amount of space available, and contains a pointer that it uses to jump to the location of GRUB 2's core image. The core image begins with [diskboot.img](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/boot/i386/pc/diskboot.S;hb=HEAD), which is usually stored immediately after the first sector in the unused space before the first partition. The above code loads the rest of the core image into memory, which contains GRUB 2's kernel and drivers for handling filesystems. After loading the rest of the core image, it executes [grub_main](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/kern/main.c). +现在 BIOS 已经选择了一个启动设备,并且将控制权转移给了启动扇区中的代码,在我们的例子中,启动扇区代码是 [boot.img](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/boot/i386/pc/boot.S;hb=HEAD)。因为这段代码只能占用一个扇区,因此非常简单,只做一些必要的初始化,然后就跳转到 GRUB 2's core image 去执行。 Core image 的代码请参考 [diskboot.img](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/boot/i386/pc/diskboot.S;hb=HEAD),一般来说 core image 在磁盘上是存储在启动扇区之后到第一个可用分区之前。core image 的初始化代码会把整个 core image (包括 GRUB 2的内核代码和文件系统驱动) 引导到内存中。 引导完成之后,[grub_main](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/kern/main.c)将被调用。 -`grub_main` initializes console, gets base address for modules, sets root device, loads/parses grub configuration file, loads modules etc. At the end of execution, `grub_main` moves grub to normal mode. `grub_normal_execute` (from `grub-core/normal/main.c`) completes last preparation and shows a menu for selecting an operating system. When we select one of grub menu entries, `grub_menu_execute_entry` begins to be executed, which executes grub `boot` command. It starts to boot the selected operating system. +`grub_main` 初始化控制套,计算模块基地址,设置 root 设备,读取 grub 配置文件,加载模块。最后,将 GRUB 置于 normal 模式,在这个模式中,`grub_normal_execute` (from `grub-core/normal/main.c`) 将被调用以往成最后的准备工作,然后显示一个菜单列出所用可用的操作系统。当某个操作系统被选择之后,`grub_menu_execute_entry` 开始执行,它将调用 GRUB 的 `boot` 命令,来引导被选中的操作系统。 -As we can read in the kernel boot protocol, the bootloader must read and fill some fields of kernel setup header which starts at `0x01f1` offset from the kernel setup code. Kernel header [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S) starts from: +就像 kernel boot protocol 所描述的,引导程序必须填充 kernel setup header (位于 kernel setup code 偏移 `0x01f1` 处) 的必要字段。kernel setup header的定义开始于 [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S): ```assembly .globl hdr @@ -207,9 +208,9 @@ hdr: boot_flag: .word 0xAA55 ``` -The bootloader must fill this and the rest of the headers (only marked as `write` in the Linux boot protocol, for example [this](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt#L354)) with values which it either got from command line or calculated. We will not see description and explanation of all fields of kernel setup header, we will get back to it when kernel uses it. Anyway, you can find description of any field in the [boot protocol](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt#L156). +bootloader必须填充在 Linux boot protocol 中标记为 `write` 的头信息,比如 [type_of_loader](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt#L354),这些头信息可能来自命令行,或者通过计算得到。在这里我们不会详细介绍所有的 kernel setup header,我们将在需要的时候逐个介绍。不过,你可以自己通过 [boot protocol](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt#L156) 来了解这些设置。 -As we can see in kernel boot protocol, the memory map will be the following after kernel loading: +通过阅读 kernel boot protocol,在内核被引导如内存后,内存使用情况将入下表所示: ```shell | Protected-mode kernel | @@ -236,34 +237,32 @@ X+08000 +------------------------+ ``` -So after the bootloader transferred control to the kernel, it starts somewhere at: +所以当 bootloader 完成任务,将执行权移交给 kernel,kernel 的代码从以下地址开始执行: ``` 0x1000 + X + sizeof(KernelBootSector) + 1 ``` -where `X` is the address of kernel bootsector loaded. In my case `X` is `0x10000`, we can see it in memory dump: +上面的公式中, `X` 是 kernel bootsector 被引导如内存的位置。在我的机器上, `X` 的值是 `0x10000`,我们可以通过 memory dump 来检查这个地址: ![kernel first address](http://oi57.tinypic.com/16bkco2.jpg) -Ok, now the bootloader has loaded Linux kernel into the memory, filled header fields and jumped to it. Now we can move directly to the kernel setup code. +到这里,引导程序完成它的使命,并将控制权移交给了 Linux kernel。下面我们就来看看 kernel setup code 都做了些什么。 -Start of Kernel Setup +内核设置 -------------------------------------------------------------------------------- -Finally we are in the kernel. Technically kernel didn't run yet, first of all we need to setup kernel, memory manager, process manager etc. Kernel setup execution starts from [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S) at the [_start](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L293). It is a little strange at the first look, there are many instructions before it. +经过上面的一系列操作,我们终于进入到内核了。不过从技术上说,内核还没有被运行起来,因为首先我们需要正确设置内核,启动内存管理,进程管理等等。内核设置代码的运行起点是 [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S) 中定义的 [_start](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L293) 函数。 在_start函数开始之前,还有很多的代码,那这些代码是做什么的呢? -Actually Long time ago Linux kernel had its own bootloader, but now if you run for example: +实际上_start开始之前的代码是 kenerl 自带的 bootloader。在很久以前,是可以使用这个 bootloader 来启动 Linux 的。不过在新的 Linux 中,这个 bootloader 代码已经不再启动 Linux 内核,而只是输出一个错误信息。 如果你运行下面的命令,直接使用 Linux 内核来启动,你会开到下图所示的错误: ``` qemu-system-x86_64 vmlinuz-3.18-generic ``` -You will see: - ![Try vmlinuz in qemu](http://oi60.tinypic.com/r02xkz.jpg) -Actually `header.S` starts from [MZ](https://en.wikipedia.org/wiki/DOS_MZ_executable) (see image above), error message printing and following [PE](https://en.wikipedia.org/wiki/Portable_Executable) header: +为了能够作为 bootloader 来使用, `header.S` 开始出定义了 [MZ] [MZ](https://en.wikipedia.org/wiki/DOS_MZ_executable) 魔术数字, 并且定义了 [PE](https://en.wikipedia.org/wiki/Portable_Executable) 头,在 PE 头中定义了输出的字符串: ```assembly #ifdef CONFIG_EFI_STUB @@ -279,9 +278,9 @@ pe_header: .word 0 ``` -It needs this for loading the operating system with [UEFI](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface). Here we will not see how it works (we will these later in the next parts). +之所以代码需要这样写,这个是因为遵从 [UEFI](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface) 的硬件需要这样的结构才能正常引导操作系统。 -So the actual kernel setup entry point is: +去除这些作为 bootloader 使用的代码,真正的内核代码就从 `_start` 开始了: ``` // header.S line 292 @@ -289,7 +288,7 @@ So the actual kernel setup entry point is: _start: ``` -Bootloader (grub2 and others) knows about this point (`0x200` offset from `MZ`) and makes a jump directly to this point, despite the fact that `header.S` starts from `.bstext` section which prints error message: +其他的 bootloader (grub2 and others) 知道 _start 所在的位置( 从 `MZ` 头开始便宜 `0x200` 字节 ),所以这些 bootloader 就会忽略所有在这个位置前的代码(这些之前的代码位于 `.bstext` 段中), 直接跳转到这个位置启动内核。 ``` // @@ -300,8 +299,6 @@ Bootloader (grub2 and others) knows about this point (`0x200` offset from `MZ`) .bsdata : { *(.bsdata) } ``` -So kernel setup entry point is: - ```assembly .globl _start _start: @@ -313,37 +310,33 @@ _start: // ``` -Here we can see `jmp` instruction opcode - `0xeb` to the `start_of_setup-1f` point. `Nf` notation means following: `2f` refers to the next local `2:` label. In our case it is label `1` which goes right after jump. It contains rest of setup [header](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt#L156) and right after setup header we can see `.entrytext` section which starts at `start_of_setup` label. +`_start` 开始就是一个 `jmp` 语句(`jmp` 语句的 opcode 是 `0xeb` ),这个跳转语句是一个短跳转,跟在后面的是一个相对地址 ( `start_of_setup - 1f ` )。在汇编代码中 `Nf` 代表了当天代码之后第一个标号为 `N` 的代码段的地址。回到我们的代码,在 `_start` 标号之后的第一个标号为 `1` 的代码段中包含了剩下的 setup header 结构。在标号为 `1` 的代码段结束之后,进阶这就是标号为 `start_of_setup` 的代码段 (这个代码段位于 `.entrytext` 代码区,这个代码段中的第一条指令实际上是内核开始执行之后的第一条指令) 。 -Actually it's the first code which starts to execute besides previous jump instruction. After kernel setup got the control from bootloader, first `jmp` instruction is located at `0x200` (first 512 bytes) offset from the start of kernel real mode. This we can read in Linux kernel boot protocol and also see in grub2 source code: +下面让我们来看一下 GRUB2 的代码是如何跳转到 `_start` 标号处的。从 Linux 内核代码中,我们知道 `_start` 标号的代码位于偏移 `0x200` 处。在 GRUB2 的源代码中我们可以看到下面的代码: ```C state.gs = state.fs = state.es = state.ds = state.ss = segment; state.cs = segment + 0x20; ``` -It means that segment registers will have following values after kernel setup starts to work: +在我的机器上,因为我的内核代码被加载到了内存地址 `0x10000` 处,所以在上面的代码执行完成之后 `cs = 0x1020` ( 一次第一条指令的内存地址将是 `cs << 4 + 0 = 0x10200`,刚好是 `0x10000` 开始后的 `0x200` 处的指令): ``` fs = es = ds = ss = 0x1000 cs = 0x1020 ``` -for my case when kernel loaded at `0x10000`. +从 `start_of_setup` 标号开是的代码需要完成下面这些事情: -After jump to `start_of_setup`, it needs to do the following things: +* 将所有段寄存器的值设置成一样的内容 +* 设置堆栈 +* 设置 [bss](https://en.wikipedia.org/wiki/.bss) (静态变量区) +* 跳转到 [main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c) 开始执行代码 -* Be sure that all values of all segment registers are equal -* Setup correct stack if needed -* Setup [bss](https://en.wikipedia.org/wiki/.bss) -* Jump to C code at [main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c) - -Let's look at implementation. - -Segment registers align +段寄存器设置 -------------------------------------------------------------------------------- -First of all it ensures that `ds` and `es` segment registers point to the same address and enables interrupts with `sti` instruction: +在代码的一开始,就将 `ds` 和 `es` 段寄存器的内容设置成一样,并且使用指令 `sti` 来允许中断发生: ```assembly movw %ds, %ax @@ -351,15 +344,7 @@ First of all it ensures that `ds` and `es` segment registers point to the same a sti ``` -As I wrote above, grub2 loads kernel setup code at `0x10000` address and `cs` at `0x1020` because execution doesn't start from the start of file, but from: - -``` -_start: - .byte 0xeb - .byte start_of_setup-1f -``` - -`jump`, which is 512 bytes offset from the [4d 5a](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L47). Also need to align `cs` from `0x10200` to `0x10000` as all other segment registers. After that we setup the stack: +就像我在上面一节中所写的, 为了能够跳转到 `_start` 标号出执行代码,grub2 将 `cs` 段寄存器的值设置成了 `0x1020`,这个值和其他段寄存器都是不一样的,因此接下来的代码就是将 `cs` 段寄存器的值和其他段寄存器一致: ```assembly pushw %ds @@ -367,12 +352,12 @@ _start: lretw ``` -push `ds` value to stack, and address of [6](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L494) label and execute `lretw` instruction. When we call `lretw`, it loads address of label `6` to [instruction pointer](https://en.wikipedia.org/wiki/Program_counter) register and `cs` with value of `ds`. After it we will have `ds` and `cs` with the same values. +上面的代码使用了一个小小的技巧来重置 `cs` 寄存器的内容,下面我们就来仔细份额系。 这段代码首先将 `ds`寄存器的值入栈,然后将标号为 `6` 的代码段地址入栈 [6](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L494),接着执行 `lretw` 指令,这条指令,将把标号为 `6` 的内存地址放入 `ip` 寄存器 [instruction pointer](https://en.wikipedia.org/wiki/Program_counter),将 `ds` 寄存器的值放入 `cs` 寄存器。 这样一来 `ds` 和 `cs` 段寄存器就拥有了相同的值。 -Stack Setup +设置堆栈 -------------------------------------------------------------------------------- -Actually, almost all of the setup code is preparation for C language environment in the real mode. The next [step](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L467) is checking of `ss` register value and making of correct stack if `ss` is wrong: +绝大部分的 setup 代码都是在为 C 语言运行环境做准备。在设置了 `ds` 和 `es` 寄存器之后,接下来 [step](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L467) 的代码将检查 `ss` 寄存器的内容,如果寄存器的内容不对,那么将进行更正: ```assembly movw %ss, %dx @@ -381,15 +366,15 @@ Actually, almost all of the setup code is preparation for C language environment je 2f ``` -Generally, it can be 3 different cases: +当进入这段代码的时候, `ss` 寄存器的值可能是一下三种情况之一: -* `ss` has valid value 0x10000 (as all other segment registers beside `cs`) -* `ss` is invalid and `CAN_USE_HEAP` flag is set (see below) -* `ss` is invalid and `CAN_USE_HEAP` flag is not set (see below) +* `ss` 寄存器的值是 0x10000 ( 和其他除了 `cs` 寄存器之外的所有寄存器的一样) +* `ss` 寄存器的值不是 0x10000,但是 `CAN_USE_HEAP` 标志被设置了 +* `ss` 寄存器的值不是 0x10000,同时 `CAN_USE_HEAP` 标志没有被设置 -Let's look at all of these cases: +下面我们就来分析在这三中情况下,代码都是如何工作的: -1. `ss` has a correct address (0x10000). In this case we go to label [2](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L481): +1. `ss` 寄存器的值是 0x10000,在这种情况下,代码将直接调准到标号为 `2` 的代码处执行: ``` 2: andw $~3, %dx @@ -400,20 +385,11 @@ Let's look at all of these cases: sti ``` -Here we can see aligning of `dx` (contains `sp` given by bootloader) to 4 bytes and checking that it is not zero. If it is zero we put `0xfffc` (4 byte aligned address before maximum segment size - 64 KB) to `dx`. If it is not zero we continue to use `sp` given by bootloader (0xf7f4 in my case). After this we put `ax` value to `ss` which stores correct segment address `0x10000` and set up correct `sp`. After it we have correct stack: +这段代码首先将 `dx` 寄存器的值(就是当前`sp` 寄存器的值)4字节对齐,然后检查是否为0(如果是0,堆栈就不对了,因为堆栈是从大地址向小地址发展的),如果是0,那么就将 `dx` 寄存器的值设置成 `0xfffc` (64KB地址段的最后一个4字节地址)。如果不是0,那么就保持当前值不变。接下来,就将 `ax` 寄存器的值( 0x10000 )设置到 `ss` 寄存器,并根据 `bx` 寄存器的值设置正确的 `sp`。这样我们就得到了正确的堆栈设置,具体请参考下图: ![stack](http://oi58.tinypic.com/16iwcis.jpg) -2. In the second case (`ss` != `ds`), first of all put [_end](https://github.com/torvalds/linux/blob/master/arch/x86/boot/setup.ld#L52) (address of end of setup code) value in `dx`. And check `loadflags` header field with `testb` instruction too see if we can use heap or not. [loadflags](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L321) is a bitmask header which is defined as: - -```C -#define LOADED_HIGH (1<<0) -#define QUIET_FLAG (1<<5) -#define KEEP_SEGMENTS (1<<6) -#define CAN_USE_HEAP (1<<7) -``` - -And as we can read in the boot protocol: +2. 下面让我们来看 `ss` != `ds`的情况,首先将 setup code 的结束地址 [_end](https://github.com/torvalds/linux/blob/master/arch/x86/boot/setup.ld#L52) 写入 `dx` 寄存器。然后检查 `loadflags` 中是否设置了 `CAN_USE_HEAP` 标志。 根据 kernel boot protocol 的定义,[loadflags](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L321) 是一个标志字段。这个字段的 `Bit 7` 就是 `CAN_USE_HEAP` 标志: ``` Field name: loadflags @@ -426,29 +402,36 @@ Field name: loadflags functionality will be disabled. ``` -If `CAN_USE_HEAP` bit is set, put `heap_end_ptr` to `dx` which points to `_end` and add `STACK_SIZE` (minimal stack size - 512 bytes) to it. After this if `dx` is not carry, jump to `2` (it will not be carry, dx = _end + 512) label as in previous case and make correct stack. +`loadflags` 字段其他可以设置的标志包括: + +```C +#define LOADED_HIGH (1<<0) +#define QUIET_FLAG (1<<5) +#define KEEP_SEGMENTS (1<<6) +#define CAN_USE_HEAP (1<<7) +``` + +如果 `CAN_USE_HEAP` 被置位,那么将 `heap_end_ptr` 放入 `dx` 寄存器,然后加上 `STACK_SIZE` (最小堆栈大小是 512 bytes)。在加法完成之后,如果结果没有溢出(CF flag 没有置位),那么就跳转到标号为 `2` 的代码处继续执行(这段代码的逻辑在1中已经详细介绍了),接着我们就得到了如下图所示的堆栈: ![stack](http://oi62.tinypic.com/dr7b5w.jpg) -3. The last case when `CAN_USE_HEAP` is not set, we just use minimal stack from `_end` to `_end + STACK_SIZE`: +3. 最后一种情况就是 `CAN_USE_HEAP` 没有置位, 那么我们就将 `dx` 寄存器的值加上 `STACK_SIZE`,然后跳转到标号为 `2` 的代码处继续执行,接着我们就得到了如下图所示的堆栈: ![minimal stack](http://oi60.tinypic.com/28w051y.jpg) -BSS Setup +BSS段设置 -------------------------------------------------------------------------------- -The last two steps that need to happen before we can jump to the main C code, are that we need to set up the [BSS](https://en.wikipedia.org/wiki/.bss) area, and check the "magic" signature. Firstly, signature checking: +在我们正式执行 C 代码之前,我们还有2件事情需要完成。1)设置正确的 `BSS` 段 [BSS](https://en.wikipedia.org/wiki/.bss);2)检查 `magic` 签名。接下来的代码,首先检查 `magic` 签名 [setup_sig](https://github.com/torvalds/linux/blob/master/arch/x86/boot/setup.ld#L39),如果签名不对,直接跳转到 `setup_bad` 部分执行代码: ```assembly cmpl $0x5a5aaa55, setup_sig jne setup_bad ``` -This simply consists of comparing the [setup_sig](https://github.com/torvalds/linux/blob/master/arch/x86/boot/setup.ld#L39) against the magic number `0x5a5aaa55`. If they are not equal, a fatal error is reported. +如果 `magic` 签名是对的, 那么我们只要设置好 `BSS` 段,就可以开始执行 C 代码了。 -But if the magic number matches, knowing we have a set of correct segment registers, and a stack, we need only setup the BSS section before jumping into the C code. - -The BSS section is used for storing statically allocated, uninitialized, data. Linux carefully ensures this area of memory is first blanked, using the following code: +BSS 段用来存储那些没有被初始化的静态变量。对于这个段使用的内存, Linux 首先使用下面的代码将其全部清零: ```assembly movw $__bss_start, %di @@ -459,29 +442,31 @@ The BSS section is used for storing statically allocated, uninitialized, data. L rep; stosl ``` -First of all the [__bss_start](https://github.com/torvalds/linux/blob/master/arch/x86/boot/setup.ld#L47) address is moved into `di`, and the `_end + 3` address (+3 - aligns to 4 bytes) is moved into `cx`. The `eax` register is cleared (using an `xor` instruction), and the bss section size (`cx`-`di`) is calculated and put into `cx`. Then, `cx` is divided by four (the size of a 'word'), and the `stosl` instruction is repeatedly used, storing the value of `eax` (zero) into the address pointed to by `di`, and automatically increasing `di` by four (this occurs until `cx` reaches zero). The net effect of this code, is that zeros are written through all words in memory from `__bss_start` to `_end`: +在这段代码中,首先将 [__bss_start](https://github.com/torvalds/linux/blob/master/arch/x86/boot/setup.ld#L47) 地址放入 `di` 寄存器,然后将 `_end + 3` (4字节对齐) 地址放入 `cx`,接着使用 `xor` 指令将 `ax` 寄存器清零,接着计算 BSS 段的大小 ( `cx` - `di` ),让后将大小放入 `cx` 寄存器。接下来将 `cx` 寄存器除4,最后使用 `rep; stosl` 指令将 `ax` 寄存器的值(0)写入 寄存器整个 BSS 段。 代码执行完成之后,我们将得到如下图所示的 BSS 段: ![bss](http://oi59.tinypic.com/29m2eyr.jpg) -Jump to main +跳转到 main 函数 -------------------------------------------------------------------------------- -That's all, we have the stack, BSS and now we can jump to the `main()` C function: +大目前为止,我们拥有了堆栈和 BSS,现在我们可以跳入 `main()` 函数来执行 C 代码了: ```assembly calll main ``` -The `main()` function is located in [arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c). What will be there? We will see it in the next part. +`main()` 函数定义在 [arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c),我们将在下一章详细介绍这个函数做了什么事情。 -Conclusion +结束语 -------------------------------------------------------------------------------- -This is the end of the first part about Linux kernel internals. If you have questions or suggestions, ping me in twitter [0xAX](https://twitter.com/0xAX), drop me [email](anotherworldofworld@gmail.com) or just create [issue](https://github.com/0xAX/linux-internals/issues/new). In the next part we will see first C code which executes in Linux kernel setup, implementation of memory routines as `memset`, `memcpy`, `earlyprintk` implementation and early console initialization and many more. +本章到此结束了,在下一章中我们将详细介绍在 Linux 内核设置过程中调用的第一个 C 代码( `main()` ),也将介绍诸如 `memset`, `memcpy`, `earlyprintk` 这些底层函数的实现,敬请期待。 -**Please note that English is not my first language and I am really sorry for any inconvenience. If you found any mistakes please send me PR to [linux-internals](https://github.com/0xAX/linux-internals).** +如果你有任何的问题或者建议,你可以留言,也可以直接发消息给我[twitter](https://twitter.com/0xAX)。 -Links +**英文不是我的母语。如果你发现我的英文描述有任何问题,请提交一个PR到[linux-insides](https://github.com/0xAX/linux-internals).** + +相关链接 -------------------------------------------------------------------------------- * [Intel 80386 programmer's reference manual 1986](http://css.csail.mit.edu/6.858/2014/readings/i386.pdf) From 27ea5bc33dc6ade4b1ef637f27e205bf6c7d97d4 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 29 Feb 2016 14:57:32 -0500 Subject: [PATCH 09/90] finish chapter 1_1 --- Booting/linux-bootstrap-1.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Booting/linux-bootstrap-1.md diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md old mode 100644 new mode 100755 From 9b6305122e93391ade50a0b4455d40df1f725e45 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 08:35:16 -0800 Subject: [PATCH 10/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) mode change 100755 => 100644 Booting/linux-bootstrap-1.md diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md old mode 100755 new mode 100644 index 4d83948..f61ab1f --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -4,7 +4,7 @@ 从引导加载程序内核 -------------------------------------------------------------------------------- -如果你已经看过我之前的[文章](http://0xax.blogspot.com/search/label/asm),就知道之前我开始和底层编程打交道。我写了一些关于Linux x86_64 汇编的文章。同时,我开始深入研究Linux源代码。底层是如果工作的,程序是如何在电脑上运行的,他们是如何在内存中定位的,内核是如何管理进程和内存,网络堆栈是如何在底层工作的等等,这些我都非常感兴趣。因此,我决定去写另外的一系列文章关于**x86_64**框架的Linux内核。 +如果你已经看过我之前的[文章](http://0xax.blogspot.com/search/label/asm),就知道之前我开始和底层编程打交道。我写了一些关于 Linux x86_64 汇编的文章。同时,我开始深入研究 Linux 源代码。底层是如果工作的,程序是如何在电脑上运行的,他们是如何在内存中定位的,内核是如何管理进程和内存,网络堆栈是如何在底层工作的等等,这些我都非常感兴趣。因此,我决定去写另外的一系列文章关于 **x86_64** 框架的 Linux 内核。 值得注意的是我不是一个专业的内核黑客并且我的工作不是为内核贡献代码。这只是小兴趣。我只是喜欢底层的东西,底层是如何工作的让我产生了很大的兴趣。如果你发现任何迷惑的地方或者你有任何问题/备注,[twitter](https://twitter.com/0xAX),[email](anotherworldofworld@gmail.com)我或者提一个[issue](https://github.com/0xAX/linux-insides).(PS:翻译上的问题请mail我:xinqiu.94@gmail.com或github上@xinqiu)。我会很高兴。所有的文章也可以在[linux-insides](https://github.com/0xAX/linux-insides)上看,如果你发现哪里英文或内容错误,随意提个PR。(PS:中文版地址:https://github.com/xinqiu/linux-insides) @@ -23,7 +23,7 @@ 神奇的电源按钮,接下来会发生什么? -------------------------------------------------------------------------------- -尽管这一系列文章关于 Linux 内核,我们还没有从内核代码(至少在这一章)开始。好了,当你按下你笔记本或台式机的神奇电源按钮,它开始工作。在主板发送一个信号给[电源](https://en.wikipedia.org/wiki/Power_supply),电源提供电脑适当量的电力。一旦主板收到了[电源备妥信号](https://en.wikipedia.org/wiki/Power_good_signal),它会尝试运行 CPU 。CPU 复位寄存器里的所有剩余数据,设置预定义的值给每个寄存器。 +尽管这一系列文章关于 Linux 内核,我们还没有从内核代码(至少在这一章)开始。好了,当你按下你笔记本或台式机的神奇电源按钮,它开始工作。在主板发送一个信号给[电源](https://en.wikipedia.org/wiki/Power_supply),电源提供电脑适当量的电力。一旦主板收到了[电源备妥信号](https://en.wikipedia.org/wiki/Power_good_signal),它会尝试启动 CPU 。CPU 复位寄存器里的所有剩余数据,设置预定义的值给每个寄存器。 [80386](https://en.wikipedia.org/wiki/Intel_80386) @@ -35,7 +35,7 @@ CS selector 0xf000 CS base 0xffff0000 ``` -处理器开始在[实模式](https://en.wikipedia.org/wiki/Real_mode)工作,我们需要退回一点去理解在这种模式下的内存分割。所有 x86兼容处理器都支持实模式,从[8086](https://en.wikipedia.org/wiki/Intel_8086)到现在的Intel 64位 CPU。8086处理器有一个20位寻址总线,这意味着它可以对0到2^20 位地址空间进行操作(1Mb).不过它只有16位的寄存器,通过这个16位寄存器最大寻址是2^16即 0xffff(64 Kb)。[内存分配](http://en.wikipedia.org/wiki/Memory_segmentation) 被用来充分利用所有空闲地址空间。所有内存被分成固定的65535 字节或64 KB大小的小块。由于我们不能用16位寄存器寻址小于64KB的内存,一种替代的方法被设计出来了。一个地址包括两个部分:数据段起始地址和从该数据段起的偏移量。为了得到内存中的物理地址,我们要让数据段乘16并加上偏移量: +处理器开始在[实模式](https://en.wikipedia.org/wiki/Real_mode)工作,我们需要退回一点去理解在这种模式下的内存分割。所有 x86兼容处理器都支持实模式,从[8086](https://en.wikipedia.org/wiki/Intel_8086)到现在的Intel 64位 CPU。8086处理器有一个20位寻址总线,这意味着它可以对0到2^20 位地址空间进行操作(1Mb).不过它只有16位的寄存器,通过这个16位寄存器最大寻址是 2^16即 0xffff (64 Kb)。[内存分配](http://en.wikipedia.org/wiki/Memory_segmentation) 被用来充分利用所有空闲地址空间。所有内存被分成固定的 64KB 大小的小块。由于我们不能用16位寄存器寻址小于 64KB 的内存,一种替代的方法被设计出来了。一个地址包括两个部分:数据段起始地址和从该数据段起的偏移量。为了得到内存中的物理地址,我们要让数据段乘16并加上偏移量: ``` PhysicalAddress = Segment * 16 + Offset From 0d57f65eb90c36140c2a8972e675233694241d07 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:00:11 -0800 Subject: [PATCH 11/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index f61ab1f..44115e0 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -6,7 +6,7 @@ 如果你已经看过我之前的[文章](http://0xax.blogspot.com/search/label/asm),就知道之前我开始和底层编程打交道。我写了一些关于 Linux x86_64 汇编的文章。同时,我开始深入研究 Linux 源代码。底层是如果工作的,程序是如何在电脑上运行的,他们是如何在内存中定位的,内核是如何管理进程和内存,网络堆栈是如何在底层工作的等等,这些我都非常感兴趣。因此,我决定去写另外的一系列文章关于 **x86_64** 框架的 Linux 内核。 -值得注意的是我不是一个专业的内核黑客并且我的工作不是为内核贡献代码。这只是小兴趣。我只是喜欢底层的东西,底层是如何工作的让我产生了很大的兴趣。如果你发现任何迷惑的地方或者你有任何问题/备注,[twitter](https://twitter.com/0xAX),[email](anotherworldofworld@gmail.com)我或者提一个[issue](https://github.com/0xAX/linux-insides).(PS:翻译上的问题请mail我:xinqiu.94@gmail.com或github上@xinqiu)。我会很高兴。所有的文章也可以在[linux-insides](https://github.com/0xAX/linux-insides)上看,如果你发现哪里英文或内容错误,随意提个PR。(PS:中文版地址:https://github.com/xinqiu/linux-insides) +请注意我不是一个专业的内核黑客并且我的工作不是为内核贡献代码。这只是小兴趣。我只是喜欢底层的东西,底层是如何工作的让我产生了很大的兴趣。如果你发现任何迷惑的地方或者你有任何问题/备注,[twitter](https://twitter.com/0xAX),[email](anotherworldofworld@gmail.com)我或者提一个[issue](https://github.com/0xAX/linux-insides).(PS:翻译上的问题请mail我:xinqiu.94@gmail.com或github上@xinqiu)。我会很高兴。所有的文章也可以在[linux-insides](https://github.com/0xAX/linux-insides)上看,如果你发现哪里英文或内容错误,随意提个PR。(PS:中文版地址:https://github.com/xinqiu/linux-insides) *注意这不是官方文档,只是学习和分享知识* From 9555043dbcf39ee5aa1c301f9c34e50de5e6e8f2 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:08:18 -0800 Subject: [PATCH 12/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 44115e0..29e8531 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -18,7 +18,7 @@ 不管怎样,如果你才开始学一些,我会在这些文章中尝试去解释一些部分。好了,小的介绍结束,我们开始深入内核和底层。 -所有的代码实际上是内核 - 3.18.如果有任何改变,我将会做相应的更新。 +我们的文章是基于 Linux 内核 3.18 版本进行的,如果后续的内核版本有任何改变,我将作出相应的更新。 神奇的电源按钮,接下来会发生什么? -------------------------------------------------------------------------------- @@ -35,7 +35,7 @@ CS selector 0xf000 CS base 0xffff0000 ``` -处理器开始在[实模式](https://en.wikipedia.org/wiki/Real_mode)工作,我们需要退回一点去理解在这种模式下的内存分割。所有 x86兼容处理器都支持实模式,从[8086](https://en.wikipedia.org/wiki/Intel_8086)到现在的Intel 64位 CPU。8086处理器有一个20位寻址总线,这意味着它可以对0到2^20 位地址空间进行操作(1Mb).不过它只有16位的寄存器,通过这个16位寄存器最大寻址是 2^16即 0xffff (64 Kb)。[内存分配](http://en.wikipedia.org/wiki/Memory_segmentation) 被用来充分利用所有空闲地址空间。所有内存被分成固定的 64KB 大小的小块。由于我们不能用16位寄存器寻址小于 64KB 的内存,一种替代的方法被设计出来了。一个地址包括两个部分:数据段起始地址和从该数据段起的偏移量。为了得到内存中的物理地址,我们要让数据段乘16并加上偏移量: +处理器开始在[实模式](https://en.wikipedia.org/wiki/Real_mode)工作,我们需要退回一点去理解在这种模式下的内存分割。所有 x86兼容处理器都支持实模式,从 [8086](https://en.wikipedia.org/wiki/Intel_8086)到现在的 Intel 64 位 CPU。8086 处理器有一个20位寻址总线,这意味着它可以对0到 2^20 位地址空间进行操作( 1Mb ).不过它只有16位的寄存器,通过这个16位寄存器最大寻址是 2^16 即 0xffff (64 Kb)。实模式使用[段式内存管理](http://en.wikipedia.org/wiki/Memory_segmentation) 来管理整个内存空间。所有内存被分成固定的 64KB 大小的小块。由于我们不能用16位寄存器寻址大于 64KB 的内存,一种替代的方法被设计出来了。一个地址包括两个部分:数据段起始地址和从该数据段起的偏移量。为了得到内存中的物理地址,我们要让数据段乘16并加上偏移量: ``` PhysicalAddress = Segment * 16 + Offset @@ -48,14 +48,14 @@ PhysicalAddress = Segment * 16 + Offset '0x20010' ``` -不过如果我们让最大端进行偏移:`0xffff:0xffff`,将会是: +不过如果我们使用16位2进制能表示的最大值进行寻址:`0xffff:0xffff`,根据上面的公式,结果将会是: ```python >>> hex((0xffff << 4) + 0xffff) '0x10ffef' ``` -这超出1MB 65519字节。既然只有1MB在实模式中可以访问,`0x10ffef` 变成有[A20](https://en.wikipedia.org/wiki/A20_line)缺陷的 `0x00ffef`。 +这超出 1MB 65519 字节。既然实模式下, CPU 只能访问 1MB 地址空间,`0x10ffef` 变成有 [A20](https://en.wikipedia.org/wiki/A20_line) 缺陷的 `0x00ffef`。 我们知道实模式和内存地址。回到复位后的寄存器值。 From 4d7fe9678600752819769c7bec349c0ac972a996 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:09:42 -0800 Subject: [PATCH 13/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 29e8531..ca02f9c 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -57,7 +57,7 @@ PhysicalAddress = Segment * 16 + Offset 这超出 1MB 65519 字节。既然实模式下, CPU 只能访问 1MB 地址空间,`0x10ffef` 变成有 [A20](https://en.wikipedia.org/wiki/A20_line) 缺陷的 `0x00ffef`。 -我们知道实模式和内存地址。回到复位后的寄存器值。 +我们了解了实模式和在实模式下的内存寻址方式,让我们来回头继续来看复位后的寄存器值。 `CS` 寄存器包含两个部分:可视段选择器和隐含基址。 结合之前定义的 `CS` 基址和 `IP` 值,逻辑地址应该是: From 11c7aaf21e45227b4916e0fd1040cf20f680fa30 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:23:38 -0800 Subject: [PATCH 14/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index ca02f9c..01830b0 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -72,7 +72,7 @@ PhysicalAddress = Segment * 16 + Offset '0xfffffff0' ``` -得到的 `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/) 源代码,将看到: +得到的 `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" @@ -84,8 +84,8 @@ reset_vector: ... ``` -跳转指令 [opcode](http://ref.x86asm.net/coder32.html#xE9) - 0xe9 到地址 `_start - ( . + 2)` 。 `reset` 段是16字节, 起始于 -`0xfffffff0` : +上面的跳转( [opcode](http://ref.x86asm.net/coder32.html#xE9) - 0xe9)跳转到地址 `_start - ( . + 2)` 去执行代码。 `reset` 段是16字节代码段, 起始于地址 +`0xfffffff0`,因此 CPU 复位之后,就会跳到这个地址来执行相应的代码 : ``` SECTIONS { @@ -99,7 +99,7 @@ SECTIONS { } ``` -现在BIOS已经开始工作了。在初始化和检查硬件之后,需要寻找可引导设备。引导顺序储存在BIOS配置中。引导顺序的功能是控制内核尝试引导的设备。对于尝试引导一个硬件,BIOS尝试寻找引导扇区。在硬盘分区上有一个MBR分区布局,引导扇区储存在第一个扇区(512字节)的起始446字节。剩下的第一个扇区的两个字节是 `0x55` 和 `0xaa` ,发出设备可引导的信号给BIOS。举个例子: +现在BIOS已经开始工作了。在初始化和检查硬件之后,需要寻找到一个可引导设备。可引导设备列表存储在在 BIOS 配置中, BIOS 将根据其中配置的顺序,尝试从不同的设备上寻找引导程序。对于硬盘,BIOS 将尝试寻找引导扇区。如果在硬盘上存在一个MBR分区,那么引导扇区储存在第一个扇区(512字节)的头446字节,引导扇区的最后必须是 `0x55` 和 `0xaa` ,这2个字节称为魔术字节,如果 BIOS 看到这2个字节,就知道这个设备是一个可引导设备。举个例子: ```assembly ; @@ -129,7 +129,7 @@ db 0xaa nasm -f bin boot.nasm && qemu-system-x86_64 boot ``` -这让 [QEMU](http://qemu.org) 使用刚才新建的 `boot` 二进制文件作为磁盘镜像。由于这个二进制文件是由上述汇编语言产生,它满足引导扇区(起始设为 `0x7c00`, 用Magic Sequence结束)的需求。QEMU将这个二进制文件作为磁盘镜像的主引导记录(MBR)。 +这让 [QEMU](http://qemu.org) 使用刚才新建的 `boot` 二进制文件作为磁盘镜像。由于这个二进制文件是由上述汇编语言产生,它满足引导扇区(起始设为 `0x7c00`, 用Magic Bytes结束)的需求。QEMU将这个二进制文件作为磁盘镜像的主引导记录(MBR)。 将看到: @@ -159,7 +159,7 @@ PhysicalAddress = Segment * 16 + Offset '0x10ffef' ``` -这个地址在 [8086](https://en.wikipedia.org/wiki/Intel_8086) 处理器下,将被转换成地址 `0x0ffef`, 原因是因为,8086 cpu 只有20位地址线,只能表示 ``2^20 = 1MB` 的地址,而上面这个地址已经超出了 1MB 地址的范围,所以 CPU 就舍弃了最高位。 +这个地址在 [8086](https://en.wikipedia.org/wiki/Intel_8086) 处理器下,将被转换成地址 `0x0ffef`, 原因是因为,8086 cpu 只有20位地址线,只能表示 `2^20 = 1MB` 的地址,而上面这个地址已经超出了 1MB 地址的范围,所以 CPU 就舍弃了最高位。 实模式下的 1MB 地址空间分配表: @@ -188,7 +188,7 @@ PhysicalAddress = Segment * 16 + Offset 引导程序 -------------------------------------------------------------------------------- -在现实世界中,要启动 Linux 系统,可以有多种引导程序可以选择。比如 [GRUB 2](https://www.gnu.org/software/grub/) 和 [syslinux](http://www.syslinux.org/wiki/index.php/The_Syslinux_Project)。Linux内核通过 [Boot protocol](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt) 来定义应该如何实现引导程序。在这里我们将之介绍 GRUB 2。 +在现实世界中,要启动 Linux 系统,有多种引导程序可以选择。比如 [GRUB 2](https://www.gnu.org/software/grub/) 和 [syslinux](http://www.syslinux.org/wiki/index.php/The_Syslinux_Project)。Linux内核通过 [Boot protocol](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt) 来定义应该如何实现引导程序。在这里我们将只介绍 GRUB 2。 现在 BIOS 已经选择了一个启动设备,并且将控制权转移给了启动扇区中的代码,在我们的例子中,启动扇区代码是 [boot.img](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/boot/i386/pc/boot.S;hb=HEAD)。因为这段代码只能占用一个扇区,因此非常简单,只做一些必要的初始化,然后就跳转到 GRUB 2's core image 去执行。 Core image 的代码请参考 [diskboot.img](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/boot/i386/pc/diskboot.S;hb=HEAD),一般来说 core image 在磁盘上是存储在启动扇区之后到第一个可用分区之前。core image 的初始化代码会把整个 core image (包括 GRUB 2的内核代码和文件系统驱动) 引导到内存中。 引导完成之后,[grub_main](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/kern/main.c)将被调用。 From ecfa30e1d28665334b1a6dc3b67bfb976d4ce063 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:25:52 -0800 Subject: [PATCH 15/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 01830b0..96cbc6a 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -190,9 +190,9 @@ PhysicalAddress = Segment * 16 + Offset 在现实世界中,要启动 Linux 系统,有多种引导程序可以选择。比如 [GRUB 2](https://www.gnu.org/software/grub/) 和 [syslinux](http://www.syslinux.org/wiki/index.php/The_Syslinux_Project)。Linux内核通过 [Boot protocol](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt) 来定义应该如何实现引导程序。在这里我们将只介绍 GRUB 2。 -现在 BIOS 已经选择了一个启动设备,并且将控制权转移给了启动扇区中的代码,在我们的例子中,启动扇区代码是 [boot.img](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/boot/i386/pc/boot.S;hb=HEAD)。因为这段代码只能占用一个扇区,因此非常简单,只做一些必要的初始化,然后就跳转到 GRUB 2's core image 去执行。 Core image 的代码请参考 [diskboot.img](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/boot/i386/pc/diskboot.S;hb=HEAD),一般来说 core image 在磁盘上是存储在启动扇区之后到第一个可用分区之前。core image 的初始化代码会把整个 core image (包括 GRUB 2的内核代码和文件系统驱动) 引导到内存中。 引导完成之后,[grub_main](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/kern/main.c)将被调用。 +现在 BIOS 已经选择了一个启动设备,并且将控制权转移给了启动扇区中的代码,在我们的例子中,启动扇区代码是 [boot.img](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/boot/i386/pc/boot.S;hb=HEAD)。因为这段代码只能占用一个扇区,因此非常简单,只做一些必要的初始化,然后就跳转到 GRUB 2's core image 去执行。 Core image 的代码请参考 [diskboot.img](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/boot/i386/pc/diskboot.S;hb=HEAD),一般来说 core image 在磁盘上存储在启动扇区之后到第一个可用分区之前。core image 的初始化代码会把整个 core image (包括 GRUB 2的内核代码和文件系统驱动) 引导到内存中。 引导完成之后,[grub_main](http://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/kern/main.c)将被调用。 -`grub_main` 初始化控制套,计算模块基地址,设置 root 设备,读取 grub 配置文件,加载模块。最后,将 GRUB 置于 normal 模式,在这个模式中,`grub_normal_execute` (from `grub-core/normal/main.c`) 将被调用以往成最后的准备工作,然后显示一个菜单列出所用可用的操作系统。当某个操作系统被选择之后,`grub_menu_execute_entry` 开始执行,它将调用 GRUB 的 `boot` 命令,来引导被选中的操作系统。 +`grub_main` 初始化控制台,计算模块基地址,设置 root 设备,读取 grub 配置文件,加载模块。最后,将 GRUB 置于 normal 模式,在这个模式中,`grub_normal_execute` (from `grub-core/normal/main.c`) 将被调用以完成最后的准备工作,然后显示一个菜单列出所用可用的操作系统。当某个操作系统被选择之后,`grub_menu_execute_entry` 开始执行,它将调用 GRUB 的 `boot` 命令,来引导被选中的操作系统。 就像 kernel boot protocol 所描述的,引导程序必须填充 kernel setup header (位于 kernel setup code 偏移 `0x01f1` 处) 的必要字段。kernel setup header的定义开始于 [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S): @@ -241,6 +241,7 @@ X+08000 +------------------------+ ``` 0x1000 + X + sizeof(KernelBootSector) + 1 +个人以为应该是 X + sizeof(KernelBootSector) + 1 因为 X 已经是一个具体的物理地址了,不是一个偏移 ``` 上面的公式中, `X` 是 kernel bootsector 被引导如内存的位置。在我的机器上, `X` 的值是 `0x10000`,我们可以通过 memory dump 来检查这个地址: From 154f68343f59d024ec9688d188be0d8f5e6635d6 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:27:50 -0800 Subject: [PATCH 16/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 96cbc6a..2581f90 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -253,9 +253,9 @@ X+08000 +------------------------+ 内核设置 -------------------------------------------------------------------------------- -经过上面的一系列操作,我们终于进入到内核了。不过从技术上说,内核还没有被运行起来,因为首先我们需要正确设置内核,启动内存管理,进程管理等等。内核设置代码的运行起点是 [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S) 中定义的 [_start](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L293) 函数。 在_start函数开始之前,还有很多的代码,那这些代码是做什么的呢? +经过上面的一系列操作,我们终于进入到内核了。不过从技术上说,内核还没有被运行起来,因为首先我们需要正确设置内核,启动内存管理,进程管理等等。内核设置代码的运行起点是 [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S) 中定义的 [_start](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L293) 函数。 在 `_start` 函数开始之前,还有很多的代码,那这些代码是做什么的呢? -实际上_start开始之前的代码是 kenerl 自带的 bootloader。在很久以前,是可以使用这个 bootloader 来启动 Linux 的。不过在新的 Linux 中,这个 bootloader 代码已经不再启动 Linux 内核,而只是输出一个错误信息。 如果你运行下面的命令,直接使用 Linux 内核来启动,你会开到下图所示的错误: +实际上 `_start` 开始之前的代码是 kenerl 自带的 bootloader。在很久以前,是可以使用这个 bootloader 来启动 Linux 的。不过在新的 Linux 中,这个 bootloader 代码已经不再启动 Linux 内核,而只是输出一个错误信息。 如果你运行下面的命令,直接使用 Linux 内核来启动,你会看到下图所示的错误: ``` qemu-system-x86_64 vmlinuz-3.18-generic @@ -263,7 +263,7 @@ qemu-system-x86_64 vmlinuz-3.18-generic ![Try vmlinuz in qemu](http://oi60.tinypic.com/r02xkz.jpg) -为了能够作为 bootloader 来使用, `header.S` 开始出定义了 [MZ] [MZ](https://en.wikipedia.org/wiki/DOS_MZ_executable) 魔术数字, 并且定义了 [PE](https://en.wikipedia.org/wiki/Portable_Executable) 头,在 PE 头中定义了输出的字符串: +为了能够作为 bootloader 来使用, `header.S` 开始处定义了 [MZ] [MZ](https://en.wikipedia.org/wiki/DOS_MZ_executable) 魔术数字, 并且定义了 [PE](https://en.wikipedia.org/wiki/Portable_Executable) 头,在 PE 头中定义了输出的字符串: ```assembly #ifdef CONFIG_EFI_STUB @@ -289,7 +289,7 @@ pe_header: _start: ``` -其他的 bootloader (grub2 and others) 知道 _start 所在的位置( 从 `MZ` 头开始便宜 `0x200` 字节 ),所以这些 bootloader 就会忽略所有在这个位置前的代码(这些之前的代码位于 `.bstext` 段中), 直接跳转到这个位置启动内核。 +其他的 bootloader (grub2 and others) 知道 _start 所在的位置( 从 `MZ` 头开始偏移 `0x200` 字节 ),所以这些 bootloader 就会忽略所有在这个位置前的代码(这些之前的代码位于 `.bstext` 段中), 直接跳转到这个位置启动内核。 ``` // From 0e9494f0c3c427e8c2e72084dc5be66988787afe Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:28:14 -0800 Subject: [PATCH 17/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 2581f90..1b4e1bf 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -311,7 +311,7 @@ _start: // ``` -`_start` 开始就是一个 `jmp` 语句(`jmp` 语句的 opcode 是 `0xeb` ),这个跳转语句是一个短跳转,跟在后面的是一个相对地址 ( `start_of_setup - 1f ` )。在汇编代码中 `Nf` 代表了当天代码之后第一个标号为 `N` 的代码段的地址。回到我们的代码,在 `_start` 标号之后的第一个标号为 `1` 的代码段中包含了剩下的 setup header 结构。在标号为 `1` 的代码段结束之后,进阶这就是标号为 `start_of_setup` 的代码段 (这个代码段位于 `.entrytext` 代码区,这个代码段中的第一条指令实际上是内核开始执行之后的第一条指令) 。 +`_start` 开始就是一个 `jmp` 语句(`jmp` 语句的 opcode 是 `0xeb` ),这个跳转语句是一个短跳转,跟在后面的是一个相对地址 ( `start_of_setup - 1f ` )。在汇编代码中 `Nf` 代表了当前代码之后第一个标号为 `N` 的代码段的地址。回到我们的代码,在 `_start` 标号之后的第一个标号为 `1` 的代码段中包含了剩下的 setup header 结构。在标号为 `1` 的代码段结束之后,紧接着就是标号为 `start_of_setup` 的代码段 (这个代码段位于 `.entrytext` 代码区,这个代码段中的第一条指令实际上是内核开始执行之后的第一条指令) 。 下面让我们来看一下 GRUB2 的代码是如何跳转到 `_start` 标号处的。从 Linux 内核代码中,我们知道 `_start` 标号的代码位于偏移 `0x200` 处。在 GRUB2 的源代码中我们可以看到下面的代码: From 641fc57f7329cf63b3722748ee33de4d37481162 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:36:52 -0800 Subject: [PATCH 18/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 1b4e1bf..339c6b4 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -320,7 +320,7 @@ _start: state.cs = segment + 0x20; ``` -在我的机器上,因为我的内核代码被加载到了内存地址 `0x10000` 处,所以在上面的代码执行完成之后 `cs = 0x1020` ( 一次第一条指令的内存地址将是 `cs << 4 + 0 = 0x10200`,刚好是 `0x10000` 开始后的 `0x200` 处的指令): +在我的机器上,因为我的内核代码被加载到了内存地址 `0x10000` 处,所以在上面的代码执行完成之后 `cs = 0x1020` ( 因此第一条指令的内存地址将是 `cs << 4 + 0 = 0x10200`,刚好是 `0x10000` 开始后的 `0x200` 处的指令): ``` fs = es = ds = ss = 0x1000 @@ -345,7 +345,7 @@ cs = 0x1020 sti ``` -就像我在上面一节中所写的, 为了能够跳转到 `_start` 标号出执行代码,grub2 将 `cs` 段寄存器的值设置成了 `0x1020`,这个值和其他段寄存器都是不一样的,因此接下来的代码就是将 `cs` 段寄存器的值和其他段寄存器一致: +就像我在上面一节中所写的, 为了能够跳转到 `_start` 标号出执行代码,grub2 将 `cs` 段寄存器的值设置成了 `0x1020`,这个值和其他段寄存器都是不一样的,因此下面的代码就是将 `cs` 段寄存器的值和其他段寄存器一致: ```assembly pushw %ds @@ -353,12 +353,12 @@ cs = 0x1020 lretw ``` -上面的代码使用了一个小小的技巧来重置 `cs` 寄存器的内容,下面我们就来仔细份额系。 这段代码首先将 `ds`寄存器的值入栈,然后将标号为 `6` 的代码段地址入栈 [6](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L494),接着执行 `lretw` 指令,这条指令,将把标号为 `6` 的内存地址放入 `ip` 寄存器 [instruction pointer](https://en.wikipedia.org/wiki/Program_counter),将 `ds` 寄存器的值放入 `cs` 寄存器。 这样一来 `ds` 和 `cs` 段寄存器就拥有了相同的值。 +上面的代码使用了一个小小的技巧来重置 `cs` 寄存器的内容,下面我们就来仔细分析。 这段代码首先将 `ds`寄存器的值入栈,然后将标号为 [6](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L494) 的代码段地址入栈 ,接着执行 `lretw` 指令,这条指令,将把标号为 `6` 的内存地址放入 `ip` 寄存器 ([instruction pointer](https://en.wikipedia.org/wiki/Program_counter)),将 `ds` 寄存器的值放入 `cs` 寄存器。 这样一来 `ds` 和 `cs` 段寄存器就拥有了相同的值。 设置堆栈 -------------------------------------------------------------------------------- -绝大部分的 setup 代码都是在为 C 语言运行环境做准备。在设置了 `ds` 和 `es` 寄存器之后,接下来 [step](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L467) 的代码将检查 `ss` 寄存器的内容,如果寄存器的内容不对,那么将进行更正: +绝大部分的 setup 代码都是为 C 语言运行环境做准备。在设置了 `ds` 和 `es` 寄存器之后,接下来 [step](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L467) 的代码将检查 `ss` 寄存器的内容,如果寄存器的内容不对,那么将进行更正: ```assembly movw %ss, %dx @@ -375,7 +375,7 @@ cs = 0x1020 下面我们就来分析在这三中情况下,代码都是如何工作的: -1. `ss` 寄存器的值是 0x10000,在这种情况下,代码将直接调准到标号为 `2` 的代码处执行: +1. `ss` 寄存器的值是 0x10000,在这种情况下,代码将直接跳转到标号为 `2` 的代码处执行: ``` 2: andw $~3, %dx @@ -386,7 +386,7 @@ cs = 0x1020 sti ``` -这段代码首先将 `dx` 寄存器的值(就是当前`sp` 寄存器的值)4字节对齐,然后检查是否为0(如果是0,堆栈就不对了,因为堆栈是从大地址向小地址发展的),如果是0,那么就将 `dx` 寄存器的值设置成 `0xfffc` (64KB地址段的最后一个4字节地址)。如果不是0,那么就保持当前值不变。接下来,就将 `ax` 寄存器的值( 0x10000 )设置到 `ss` 寄存器,并根据 `bx` 寄存器的值设置正确的 `sp`。这样我们就得到了正确的堆栈设置,具体请参考下图: +这段代码首先将 `dx` 寄存器的值(就是当前`sp` 寄存器的值)4字节对齐,然后检查是否为0(如果是0,堆栈就不对了,因为堆栈是从大地址向小地址发展的),如果是0,那么就将 `dx` 寄存器的值设置成 `0xfffc` (64KB地址段的最后一个4字节地址)。如果不是0,那么就保持当前值不变。接下来,就将 `ax` 寄存器的值( 0x10000 )设置到 `ss` 寄存器,并根据 `dx` 寄存器的值设置正确的 `sp`。这样我们就得到了正确的堆栈设置,具体请参考下图: ![stack](http://oi58.tinypic.com/16iwcis.jpg) @@ -412,7 +412,7 @@ Field name: loadflags #define CAN_USE_HEAP (1<<7) ``` -如果 `CAN_USE_HEAP` 被置位,那么将 `heap_end_ptr` 放入 `dx` 寄存器,然后加上 `STACK_SIZE` (最小堆栈大小是 512 bytes)。在加法完成之后,如果结果没有溢出(CF flag 没有置位),那么就跳转到标号为 `2` 的代码处继续执行(这段代码的逻辑在1中已经详细介绍了),接着我们就得到了如下图所示的堆栈: +如果 `CAN_USE_HEAP` 被置位,那么将 `heap_end_ptr` 放入 `dx` 寄存器,然后加上 `STACK_SIZE` (最小堆栈大小是 512 bytes)。在加法完成之后,如果结果没有溢出(CF flag 没有置位,如果置位那么程序就出错了),那么就跳转到标号为 `2` 的代码处继续执行(这段代码的逻辑在1中已经详细介绍了),接着我们就得到了如下图所示的堆栈: ![stack](http://oi62.tinypic.com/dr7b5w.jpg) From 68dc8383f20b0df6402e2b3e2a76b9735b4eb463 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:37:09 -0800 Subject: [PATCH 19/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 339c6b4..cc0ff2d 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -423,7 +423,7 @@ Field name: loadflags BSS段设置 -------------------------------------------------------------------------------- -在我们正式执行 C 代码之前,我们还有2件事情需要完成。1)设置正确的 `BSS` 段 [BSS](https://en.wikipedia.org/wiki/.bss);2)检查 `magic` 签名。接下来的代码,首先检查 `magic` 签名 [setup_sig](https://github.com/torvalds/linux/blob/master/arch/x86/boot/setup.ld#L39),如果签名不对,直接跳转到 `setup_bad` 部分执行代码: +在我们正式执行 C 代码之前,我们还有2件事情需要完成。1)设置正确的 [BSS](https://en.wikipedia.org/wiki/.bss)段 ;2)检查 `magic` 签名。接下来的代码,首先检查 `magic` 签名 [setup_sig](https://github.com/torvalds/linux/blob/master/arch/x86/boot/setup.ld#L39),如果签名不对,直接跳转到 `setup_bad` 部分执行代码: ```assembly cmpl $0x5a5aaa55, setup_sig From 25655d87c6b83e06f12cd7bd7e8e43b3747c6665 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:37:17 -0800 Subject: [PATCH 20/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index cc0ff2d..89baba9 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -450,7 +450,7 @@ BSS 段用来存储那些没有被初始化的静态变量。对于这个段使 跳转到 main 函数 -------------------------------------------------------------------------------- -大目前为止,我们拥有了堆栈和 BSS,现在我们可以跳入 `main()` 函数来执行 C 代码了: +到目前为止,我们拥有了堆栈和 BSS,现在我们可以跳入 `main()` 函数来执行 C 代码了: ```assembly calll main From c19cdb9c980867de687b7401f20fbc5bfea83e30 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:37:30 -0800 Subject: [PATCH 21/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 89baba9..de73bef 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -450,7 +450,7 @@ BSS 段用来存储那些没有被初始化的静态变量。对于这个段使 跳转到 main 函数 -------------------------------------------------------------------------------- -到目前为止,我们拥有了堆栈和 BSS,现在我们可以跳入 `main()` 函数来执行 C 代码了: +到目前为止,我们完成了堆栈和 BSS 的设置,现在我们可以正式跳入 `main()` 函数来执行 C 代码了: ```assembly calll main From e30d9c04af9df98ab16d1ba389daec440ec9194a Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:42:07 -0800 Subject: [PATCH 22/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index de73bef..653b4b2 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -1,4 +1,4 @@ -内核引导过程. Part 1. + 内核引导过程. Part 1. ================================================================================ 从引导加载程序内核 From 892046ad2a7db9f9dc1ff1239293b7290f502bfd Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:42:11 -0800 Subject: [PATCH 23/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 653b4b2..de73bef 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -1,4 +1,4 @@ - 内核引导过程. Part 1. +内核引导过程. Part 1. ================================================================================ 从引导加载程序内核 From 6e2eb9f9aab8f89f5d693385a6ea3fa126d48021 Mon Sep 17 00:00:00 2001 From: "hailin.cai" Date: Tue, 1 Mar 2016 12:42:34 -0500 Subject: [PATCH 24/90] refine the translation --- Booting/linux-bootstrap-1.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Booting/linux-bootstrap-1.md diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md old mode 100644 new mode 100755 From 33b7db8f82db4ab33a3d30c045545cd07c44b4f8 Mon Sep 17 00:00:00 2001 From: "hailin.cai" Date: Tue, 1 Mar 2016 12:43:08 -0500 Subject: [PATCH 25/90] remove x for md file --- Booting/linux-bootstrap-1.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 Booting/linux-bootstrap-1.md diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md old mode 100755 new mode 100644 From 7db9835611ca01818406bf6987cdf6b6b02a3e3b Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:43:59 -0800 Subject: [PATCH 26/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index de73bef..61f636b 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -84,7 +84,7 @@ reset_vector: ... ``` -上面的跳转( [opcode](http://ref.x86asm.net/coder32.html#xE9) - 0xe9)跳转到地址 `_start - ( . + 2)` 去执行代码。 `reset` 段是16字节代码段, 起始于地址 +上面的跳转指令( [opcode](http://ref.x86asm.net/coder32.html#xE9) - 0xe9)跳转到地址 `_start - ( . + 2)` 去执行代码。 `reset` 段是16字节代码段, 起始于地址 `0xfffffff0`,因此 CPU 复位之后,就会跳到这个地址来执行相应的代码 : ``` From 02039e6744608e5383443285b57ffafbc1df9a27 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:44:43 -0800 Subject: [PATCH 27/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 61f636b..d3b78b3 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -99,7 +99,7 @@ SECTIONS { } ``` -现在BIOS已经开始工作了。在初始化和检查硬件之后,需要寻找到一个可引导设备。可引导设备列表存储在在 BIOS 配置中, BIOS 将根据其中配置的顺序,尝试从不同的设备上寻找引导程序。对于硬盘,BIOS 将尝试寻找引导扇区。如果在硬盘上存在一个MBR分区,那么引导扇区储存在第一个扇区(512字节)的头446字节,引导扇区的最后必须是 `0x55` 和 `0xaa` ,这2个字节称为魔术字节,如果 BIOS 看到这2个字节,就知道这个设备是一个可引导设备。举个例子: + 现在BIOS已经开始工作了。在初始化和检查硬件之后,需要寻找到一个可引导设备。可引导设备列表存储在在 BIOS 配置中, BIOS 将根据其中配置的顺序,尝试从不同的设备上寻找引导程序。对于硬盘,BIOS 将尝试寻找引导扇区。如果在硬盘上存在一个MBR分区,那么引导扇区储存在第一个扇区(512字节)的头446字节,引导扇区的最后必须是 `0x55` 和 `0xaa` ,这2个字节称为魔术字节,如果 BIOS 看到这2个字节,就知道这个设备是一个可引导设备。举个例子: ```assembly ; From 225165807edb8c024048d930a203614b944dff36 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 1 Mar 2016 09:44:48 -0800 Subject: [PATCH 28/90] Update Booting/linux-bootstrap-1.md --- Booting/linux-bootstrap-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index d3b78b3..61f636b 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -99,7 +99,7 @@ SECTIONS { } ``` - 现在BIOS已经开始工作了。在初始化和检查硬件之后,需要寻找到一个可引导设备。可引导设备列表存储在在 BIOS 配置中, BIOS 将根据其中配置的顺序,尝试从不同的设备上寻找引导程序。对于硬盘,BIOS 将尝试寻找引导扇区。如果在硬盘上存在一个MBR分区,那么引导扇区储存在第一个扇区(512字节)的头446字节,引导扇区的最后必须是 `0x55` 和 `0xaa` ,这2个字节称为魔术字节,如果 BIOS 看到这2个字节,就知道这个设备是一个可引导设备。举个例子: +现在BIOS已经开始工作了。在初始化和检查硬件之后,需要寻找到一个可引导设备。可引导设备列表存储在在 BIOS 配置中, BIOS 将根据其中配置的顺序,尝试从不同的设备上寻找引导程序。对于硬盘,BIOS 将尝试寻找引导扇区。如果在硬盘上存在一个MBR分区,那么引导扇区储存在第一个扇区(512字节)的头446字节,引导扇区的最后必须是 `0x55` 和 `0xaa` ,这2个字节称为魔术字节,如果 BIOS 看到这2个字节,就知道这个设备是一个可引导设备。举个例子: ```assembly ; From a3690086052f3002bf932883c57c351a959df62c Mon Sep 17 00:00:00 2001 From: "hailin.cai" Date: Tue, 1 Mar 2016 12:46:38 -0500 Subject: [PATCH 29/90] change the mod for file --- Booting/linux-bootstrap-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 61f636b..7400692 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -1,4 +1,4 @@ -内核引导过程. Part 1. +内核引导过程. 第一部分. ================================================================================ 从引导加载程序内核 From 0ac173057b384d83e402a149da4ab1641428b8e1 Mon Sep 17 00:00:00 2001 From: hailin Date: Tue, 1 Mar 2016 13:47:01 -0500 Subject: [PATCH 30/90] fix one issue in para --- Booting/linux-bootstrap-1.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 7400692..f323173 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -375,7 +375,7 @@ cs = 0x1020 下面我们就来分析在这三中情况下,代码都是如何工作的: -1. `ss` 寄存器的值是 0x10000,在这种情况下,代码将直接跳转到标号为 `2` 的代码处执行: +* `ss` 寄存器的值是 0x10000,在这种情况下,代码将直接跳转到标号为 `2` 的代码处执行: ``` 2: andw $~3, %dx @@ -390,7 +390,7 @@ cs = 0x1020 ![stack](http://oi58.tinypic.com/16iwcis.jpg) -2. 下面让我们来看 `ss` != `ds`的情况,首先将 setup code 的结束地址 [_end](https://github.com/torvalds/linux/blob/master/arch/x86/boot/setup.ld#L52) 写入 `dx` 寄存器。然后检查 `loadflags` 中是否设置了 `CAN_USE_HEAP` 标志。 根据 kernel boot protocol 的定义,[loadflags](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L321) 是一个标志字段。这个字段的 `Bit 7` 就是 `CAN_USE_HEAP` 标志: +* 下面让我们来看 `ss` != `ds`的情况,首先将 setup code 的结束地址 [_end](https://github.com/torvalds/linux/blob/master/arch/x86/boot/setup.ld#L52) 写入 `dx` 寄存器。然后检查 `loadflags` 中是否设置了 `CAN_USE_HEAP` 标志。 根据 kernel boot protocol 的定义,[loadflags](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L321) 是一个标志字段。这个字段的 `Bit 7` 就是 `CAN_USE_HEAP` 标志: ``` Field name: loadflags @@ -416,7 +416,7 @@ Field name: loadflags ![stack](http://oi62.tinypic.com/dr7b5w.jpg) -3. 最后一种情况就是 `CAN_USE_HEAP` 没有置位, 那么我们就将 `dx` 寄存器的值加上 `STACK_SIZE`,然后跳转到标号为 `2` 的代码处继续执行,接着我们就得到了如下图所示的堆栈: +* 最后一种情况就是 `CAN_USE_HEAP` 没有置位, 那么我们就将 `dx` 寄存器的值加上 `STACK_SIZE`,然后跳转到标号为 `2` 的代码处继续执行,接着我们就得到了如下图所示的堆栈: ![minimal stack](http://oi60.tinypic.com/28w051y.jpg) From cde4ec9ac037ef77b2e215c39f4ac80acecbded3 Mon Sep 17 00:00:00 2001 From: Ezio Date: Sat, 5 Mar 2016 22:25:57 +0800 Subject: [PATCH 31/90] Update dlist.md --- DataStructures/dlist.md | 54 ++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/DataStructures/dlist.md b/DataStructures/dlist.md index bf6996c..46168f1 100644 --- a/DataStructures/dlist.md +++ b/DataStructures/dlist.md @@ -4,9 +4,9 @@ Linux 内核里的数据结构——双向链表 双向链表 -------------------------------------------------------------------------------- -Linux 内核自己实现了双向链表,可以在[include/linux/list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h)找到定义。我们将会从双向链表数据结构开始`内核的数据结构`。为什么?因为它在内核里使用的很广泛,你只需要在[free-electrons.com](http://lxr.free-electrons.com/ident?i=list_head) 检索一下就知道了。 +Linux 内核自己实现了双向链表,可以在 [include/linux/list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h) 找到定义。我们将会从双向链表数据结构开始`内核的数据结构`。为什么?因为它在内核里使用的很广泛,你只需要在 [free-electrons.com](http://lxr.free-electrons.com/ident?i=list_head) 检索一下就知道了。 -首先让我们看一下在[include/linux/types.h](https://github.com/torvalds/linux/blob/master/include/linux/types.h) 里的主结构体: +首先让我们看一下在 [include/linux/types.h](https://github.com/torvalds/linux/blob/master/include/linux/types.h) 里的主结构体: ```C struct list_head { @@ -14,7 +14,7 @@ struct list_head { }; ``` -你可能注意到这和你以前见过的双向链表的实现方法是不同的。举个例子来说,在[glib](http://www.gnu.org/software/libc/) 库里是这样实现的: +你可能注意到这和你以前见过的双向链表的实现方法是不同的。举个例子来说,在 [glib](http://www.gnu.org/software/libc/) 库里是这样实现的: ```C struct GList { @@ -35,7 +35,7 @@ struct nmi_desc { }; ``` -让我们看几个例子来理解一下在内核里是如何使用`list_head` 的。如上所述,在内核里有实在很多不同的地方用到了链表。我们来看一个在杂项字符驱动里面的使用的例子。在 [drivers/char/misc.c](https://github.com/torvalds/linux/blob/master/drivers/char/misc.c) 的杂项字符驱动API 被用来编写处理小型硬件和虚拟设备的小驱动。这些驱动共享相同的主设备号: +让我们看几个例子来理解一下在内核里是如何使用 `list_head` 的。如上所述,在内核里有实在很多不同的地方用到了链表。我们以杂项字符驱动为例来说明双向链表的使用。在 [drivers/char/misc.c](https://github.com/torvalds/linux/blob/master/drivers/char/misc.c) 的杂项字符驱动API 被用来编写处理小型硬件和虚拟设备的小驱动。这些驱动共享相同的主设备号: ```C #define MISC_MAJOR 10 @@ -67,7 +67,7 @@ crw------- 1 root root 10, 63 Mar 21 12:01 vga_arbiter crw------- 1 root root 10, 137 Mar 21 12:01 vhci ``` -现在让我们看看它是如何使用链表的。首先看一下结构体`miscdevice`: +现在让我们看看它是如何使用链表的。首先看一下结构体 `miscdevice` : ```C struct miscdevice @@ -83,32 +83,32 @@ struct miscdevice }; ``` -可以看到结构体的第四个变量`list` 是所有注册过的设备的链表。在源代码文件的开始可以看到这个链表的定义: +我们可以看到结构体的第四个变量 `list` 是所有注册过的设备的链表。在源代码文件的开始可以看到这个链表的定义: ```C static LIST_HEAD(misc_list); ``` -它实际上是对用`list_head` 类型定义的变量的扩展。 +它扩展开来实际上就是定义了一个 `list_head` 类型的变量: ```C #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) ``` -然后使用宏`LIST_HEAD_INIT` 进行初始化,这会使用变量`name` 的地址来填充`prev`和`next` 结构体的两个变量。 +然后使用宏 `LIST_HEAD_INIT` 进行初始化,这会使用变量 `name` 的地址来填充 `prev` 和 `next` 结构体的两个变量。 ```C #define LIST_HEAD_INIT(name) { &(name), &(name) } ``` -现在来看看注册杂项设备的函数`misc_register`。它在开始就用 `INIT_LIST_HEAD` 初始化了`miscdevice->list`。 +现在来看看注册杂项设备的函数 `misc_register` 。它在开始就用 `INIT_LIST_HEAD` 初始化了`miscdevice->list`。 ```C INIT_LIST_HEAD(&misc->list); ``` -作用和宏`LIST_HEAD_INIT`一样。 +作用和宏 `LIST_HEAD_INIT`一样。 ```C static inline void INIT_LIST_HEAD(struct list_head *list) @@ -118,13 +118,13 @@ static inline void INIT_LIST_HEAD(struct list_head *list) } ``` -在函数`device_create` 创建了设备后我们就用下面的语句将设备添加到设备链表: +下一步在函数 `device_create` 创建了设备后我们就用下面的语句将设备添加到设备链表: ``` list_add(&misc->list, &misc_list); ``` -内核文件`list.h` 提供了项链表添加新项的API 接口。我们来看看它的实现: +内核文件 `list.h` 提供了向链表添加新项的接口函数。我们来看看它的实现: ```C @@ -134,10 +134,10 @@ static inline void list_add(struct list_head *new, struct list_head *head) } ``` -实际上就是使用3个指定的参数来调用了内部函数`__list_add`: +实际上就是使用3个指定的参数来调用了内部函数 `__list_add`: * new - 新项。 -* head - 新项将会被添加到`head`之前. +* head - 新项将会被添加到`head` 之后. * head->next - `head` 之后的项。 `__list_add`的实现非常简单: @@ -154,9 +154,9 @@ static inline void __list_add(struct list_head *new, } ``` -我们会在`prev`和`next` 之间添加一个新项。所以我们用宏`LIST_HEAD_INIT`定义的`misc` 链表会包含指向`miscdevice->list` 的向前指针和向后指针。 +我们会在 `prev` 和 `next` 之间添加一个新项。所以我们用宏 `LIST_HEAD_INIT` 定义的 `misc` 链表会包含指向 `miscdevice->list` 的向前指针和向后指针。 -这里有一个问题:如何得到列表的内容呢?这里有一个特殊的宏: +这里仍有一个问题:如何得到列表的内容呢?这里有一个特殊的宏: ```C #define list_entry(ptr, type, member) \ @@ -167,7 +167,7 @@ static inline void __list_add(struct list_head *new, * ptr - 指向链表头的指针; * type - 结构体类型; -* member - 在结构体内类型为`list_head` 的变量的名字; +* member - 在结构体内类型为 `list_head` 的变量的名字; 比如说: @@ -175,14 +175,14 @@ static inline void __list_add(struct list_head *new, const struct miscdevice *p = list_entry(v, struct miscdevice, list) ``` -然后我们就可以使用`p->minor` 或者 `p->name`来访问`miscdevice`。让我们来看看`list_entry` 的实现: +然后我们就可以使用 `p->minor` 或者 `p->name`来访问 `miscdevice`。让我们来看看 `list_entry` 的实现: ```C #define list_entry(ptr, type, member) \ container_of(ptr, type, member) ``` -如我们所见,它仅仅使用相同的参数调用了宏`container_of`。初看这个宏挺奇怪的: +如我们所见,它仅仅使用相同的参数调用了宏 `container_of`。初看这个宏挺奇怪的: ```C #define container_of(ptr, type, member) ({ \ @@ -204,9 +204,9 @@ int main() { } ``` -最终会打印`2` +最终会打印 `2` -下一点就是`typeof`,它也很简单。就如你从名字所理解的,它仅仅返回了给定变量的类型。当我第一次看到宏`container_of`的实现时,让我觉得最奇怪的就是`container_of`中的0.实际上这个指针巧妙的计算了从结构体特定变量的偏移,这里的`0`刚好就是位宽里的零偏移。让我们看一个简单的例子: +下一点就是 `typeof`,它也很简单。就如你从名字所理解的,它仅仅返回了给定变量的类型。当我第一次看到宏 `container_of` 的实现时,让我觉得最奇怪的就是 `container_of` 中的 0 。实际上这个指针巧妙的计算了从结构体特定变量的偏移,这里的 `0` 刚好就是位宽里的零偏移。让我们看一个简单的例子: ```C #include @@ -214,7 +214,7 @@ int main() { struct s { int field1; char field2; - char field3; + char field3; }; int main() { @@ -223,17 +223,17 @@ int main() { } ``` -结果显示`0x5`。 +结果显示 `0x5`。 -下一个宏`offsetof` 会计算从结构体的某个变量的相对于结构体起始地址的偏移。它的实现和上面类似: +下一个宏 `offsetof` 会计算从结构体的某个变量的相对于结构体起始地址的偏移。它的实现和上面类似: ```C #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) ``` -现在我们来总结一下宏`container_of`。只需要知道结构体里面类型为`list_head` 的变量的名字和结构体容器的类型,它可以通过结构体的变量`list_head`获得结构体的起始地址。在宏定义的第一行,声明了一个指向结构体成员变量`ptr`的指针`__mptr`,并且把`ptr` 的地址赋给它。现在`ptr` 和`__mptr` 指向了同一个地址。从技术上讲我们并不需要这一行,但是它可以方便的进行类型检查。第一行保证了特定的结构体(参数`type`)包含成员变量`member`。第二行代码会用宏`offsetof`计算成员变量相对于结构体起始地址的偏移,然后从结构体的地址减去这个偏移,最后就得到了结构体。 +现在我们来总结一下宏 `container_of`。只需要知道结构体里面类型为 `list_head` 的变量的名字和结构体容器的类型,它可以通过结构体的变量 `list_head` 获得结构体的起始地址。在宏定义的第一行,声明了一个指向结构体成员变量 `ptr` 的指针 `__mptr` ,并且把 `ptr` 的地址赋给它。现在 `ptr` 和 `__mptr` 指向了同一个地址。从技术上讲我们并不需要这一行,但是它可以方便的进行类型检查。第一行保证了特定的结构体(参数 `type`)包含成员变量 `member`。第二行代码会用宏 `offsetof` 计算成员变量相对于结构体起始地址的偏移,然后从结构体的地址减去这个偏移,最后就得到了结构体的起始地址。 -当然了`list_add` 和 `list_entry`不是``提供的唯一功能。双向链表的实现还提供了如下API: +当然了 `list_add` 和 `list_entry` 不是 `` 提供的唯一函数。双向链表的实现还提供了如下API: * list_add * list_add_tail @@ -247,4 +247,4 @@ int main() { * list_for_each * list_for_each_entry -等等很多其它API。 +等等很多其它 API。 From 9233f90e1d992326d0a006a316612a0af0ee84aa Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 7 Mar 2016 06:59:50 -0800 Subject: [PATCH 32/90] update chap1_1 to finish --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ca86d7e..fbd6617 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Linux Insides | 章节|译者|翻译进度| | ------------- |:-------------:| -----:| |Booting||正在进行| -|├1.1|[@hailincai](https://github.com/hailincai)|正在进行| +|├1.1|[@hailincai](https://github.com/hailincai)|已完成| |├1.2|[@hailincai](https://github.com/hailincai)|已完成| |├1.3|[@hailincai](https://github.com/hailincai)|正在进行| |├1.4||未开始| @@ -53,7 +53,7 @@ Linux Insides * 在确认想翻译的章节没有被翻译之后,开一个 issue ,告诉大家你想翻译哪一章或者哪一节,同时提交申请翻译的 PR ,将 [README.md](https://github.com/MintCN/linux-insides-zh/blob/master/README.md) 中的翻译状态修改为“正在进行”; * 开始翻译你认领的章节; * 完成翻译之后,提交翻译内容的 PR ,待 PR 被 merge 之后,关闭 issue ; -* 最后,将 [README.md](https://github.com/MintCN/linux-insides-zh/blob/master/README.md) 中的翻译状态修改为“已完成”,同时不要忘记把自己添加到 [contributors.md](https://github.com/MintCN/linux-insides-zh/blob/master/contributors.md) 中。 +* 最后,将 [README.md](https://github.com/MintCN/linux-insides-zh/blob/master/README.md) 中的翻译状态修改为“已完成”,同时不要忘记把自己添加到 [contributors.md](https://github.com/MintCN/linux-insides-zh/blob/master/contributors.md) 中。 翻译前建议看 [TRANSLATION_NOTES.md](https://github.com/MintCN/linux-insides-zh/blob/master/TRANSLATION_NOTES.md) 。 关于翻译约定,大家有任何问题或建议也请开 issue 讨论。 @@ -65,9 +65,9 @@ Linux Insides ##中文维护者 - + [@xinqiu](https://github.com/xinqiu) - + [@mudongliang](https://github.com/mudongliang) ##中文贡献者 @@ -78,4 +78,3 @@ Linux Insides ##LICENSE Licensed [BY-NC-SA Creative Commons](http://creativecommons.org/licenses/by-nc-sa/4.0/). - From d926237eb1858f918e96eb22bd591ec979c9662c Mon Sep 17 00:00:00 2001 From: mudongliang Date: Mon, 7 Mar 2016 10:06:08 -0500 Subject: [PATCH 33/90] change the state of chapter 9.1 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fbd6617..8465123 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Linux Insides |Memory management|[@choleraehyq](https://github.com/choleraehyq)|正在进行| |Concepts||未开始| |DataStructures||正在进行| -|├9.1|[@oska874](http://github.com/oska874) [@mudongliang](https://github.com/mudongliang)|正在进行| +|├9.1|[@oska874](http://github.com/oska874) [@mudongliang](https://github.com/mudongliang)|已完成| |└9.2|[@Alick Guo](https://github.com/a1ickgu0)|正在进行| |Theory||正在进行| |├10.1|[@mudongliang](https://github.com/mudongliang)|已完成| From 981d1d345e2c88b453b55325afae8951de7c5549 Mon Sep 17 00:00:00 2001 From: zmj1316 Date: Mon, 7 Mar 2016 23:40:33 +0800 Subject: [PATCH 34/90] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Booting/linux-bootstrap-4.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Booting/linux-bootstrap-4.md b/Booting/linux-bootstrap-4.md index ecb253f..579443a 100644 --- a/Booting/linux-bootstrap-4.md +++ b/Booting/linux-bootstrap-4.md @@ -14,7 +14,7 @@ jmpl *%eax ``` -回忆一下`eax`寄存器包含了 32 位入口点的地址。我们可以在 [x86 linux 内核引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt) 中找到相关内容: +你应该还记得,`eax`寄存器包含了 32 位入口点的地址。我们可以在 [x86 linux 内核引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt) 中找到相关内容: ``` When using bzImage, the protected-mode kernel was relocated to 0x100000 @@ -46,7 +46,7 @@ fs 0x18 24 gs 0x18 24 ``` -我们可以看到这里的`cs`寄存器的内容 - `0x10` (在前一节我们提到,这代表全局描述符表中的第二个索引项),`eip`寄存器值是 `0x100000` 并且包括代码段的所有段的基地址都为0。所以我们可以得到物理地址: `0:0x100000` 或者 `0x100000`,正如协议规定的一样。现在让我们从 32 位入口点开始。 +我们在这里可以看到`cs`寄存器包含了 - `0x10` (回忆前一章节,这代表了全局描述符表中的第二个索引项),`eip`寄存器的值是 `0x100000` ,并且包括代码段在内的所有内存段的基地址都为0。所以我们可以得到物理地址: `0:0x100000` 或者 `0x100000`,和协议规定的一样。现在让我们从 32 位入口点开始。 32 位入口点 -------------------------------------------------------------------------------- @@ -63,14 +63,14 @@ ENTRY(startup_32) ENDPROC(startup_32) ``` -首先,为什么是`被压缩 (compressed)` 的目录?实际上`bzimage`是一个被 gzip 压缩的`vmlinux + 头文件 + 内核启动代码`。我们在前几个章节已经看到了内核启动的代码。所以,`head_64.S`的主要目的就是为了准备并进入长模式,进入以后解压内核。我们将在这一节看到以上直到内核解压缩之前的所有步骤。 +首先,为什么目录名叫做`被压缩的 (compressed)`?实际上`bzimage`是由`vmlinux + 头文件 + 内核启动代码`被 gzip 压缩之后获得的。我们在前几个章节已经看到了内核启动的代码。所以,`head_64.S`的主要目的就是为了做好进入长模式的准备然后进入长模式,进入以后再解压内核。在这一节,我们将会看到直到内核解压缩之前的所有步骤。 在`arch/x86/boot/compressed`目录下有两个文件: * [head_32.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_32.S) * [head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) -但是我们只关注`head_64.S`,因为你可能还记得我们这本书只和`x86_64`有关;在我们这里`head_32.S`没有被使用到。让我们关注 [arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile)。这里我们可以看到以下目标: +但是,你可能还记得我们这本书只和`x86_64`有关,所以我们只会关注`head_64.S`;在我们这里`head_32.S`没有被用到。让我们看一下 [arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile)。在那里我们可以看到以下目标: ```Makefile vmlinux-objs-y := $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o \ From 62d6ae430eec3839d4668e7c27f6131afa7b3282 Mon Sep 17 00:00:00 2001 From: zmj1316 Date: Tue, 8 Mar 2016 21:33:44 +0800 Subject: [PATCH 35/90] =?UTF-8?q?=E7=BB=99=E5=8D=95=E8=AF=8D=E8=A1=A5?= =?UTF-8?q?=E4=B8=8A=E7=A9=BA=E6=A0=BC=20&=20=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Booting/linux-bootstrap-4.md | 126 +++++++++++++++++------------------ 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/Booting/linux-bootstrap-4.md b/Booting/linux-bootstrap-4.md index 579443a..408485b 100644 --- a/Booting/linux-bootstrap-4.md +++ b/Booting/linux-bootstrap-4.md @@ -1,10 +1,10 @@ -内核引导过程. Part 4. +内核引导过程. Part 4. ================================================================================ 切换到64位模式 -------------------------------------------------------------------------------- -这是`内核引导过程`的第四部分,我们将会看到在[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)中的最初几步,比如确认CPU支持[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F),[SSE](https://zh.wikipedia.org/wiki/SSE)和[分页](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)以及页表的初始化,在这部分的最后我们还将讨论如何切换到[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)。 +这是 `内核引导过程` 的第四部分,我们将会看到在[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)中的最初几步,比如确认CPU支持[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F),[SSE](https://zh.wikipedia.org/wiki/SSE)和[分页](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)以及页表的初始化,在这部分的最后我们还将讨论如何切换到[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)。 **注意:这部分将会有大量的汇编代码,如果你不熟悉汇编,建议你找本书参考一下。** @@ -14,7 +14,7 @@ jmpl *%eax ``` -你应该还记得,`eax`寄存器包含了 32 位入口点的地址。我们可以在 [x86 linux 内核引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt) 中找到相关内容: +回忆一下, `eax` 寄存器包含了 32 位入口点的地址。我们可以在 [x86 linux 内核引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt) 中找到相关内容: ``` When using bzImage, the protected-mode kernel was relocated to 0x100000 @@ -46,7 +46,7 @@ fs 0x18 24 gs 0x18 24 ``` -我们在这里可以看到`cs`寄存器包含了 - `0x10` (回忆前一章节,这代表了全局描述符表中的第二个索引项),`eip`寄存器的值是 `0x100000` ,并且包括代码段在内的所有内存段的基地址都为0。所以我们可以得到物理地址: `0:0x100000` 或者 `0x100000`,和协议规定的一样。现在让我们从 32 位入口点开始。 +我们在这里可以看到 `cs` 寄存器包含了 - `0x10` (回忆前一章节,这代表了全局描述符表中的第二个索引项), `eip` 寄存器的值是 `0x100000`,并且包括代码段在内的所有内存段的基地址都为0。所以我们可以得到物理地址: `0:0x100000` 或者 `0x100000`,和协议规定的一样。现在让我们从 32 位入口点开始。 32 位入口点 -------------------------------------------------------------------------------- @@ -63,14 +63,14 @@ ENTRY(startup_32) ENDPROC(startup_32) ``` -首先,为什么目录名叫做`被压缩的 (compressed)`?实际上`bzimage`是由`vmlinux + 头文件 + 内核启动代码`被 gzip 压缩之后获得的。我们在前几个章节已经看到了内核启动的代码。所以,`head_64.S`的主要目的就是为了做好进入长模式的准备然后进入长模式,进入以后再解压内核。在这一节,我们将会看到直到内核解压缩之前的所有步骤。 +首先,为什么目录名叫做 `被压缩的 (compressed)` ?实际上 `bzimage` 是由 `vmlinux + 头文件 + 内核启动代码` 被 gzip 压缩之后获得的。我们在前几个章节已经看到了内核启动的代码。所以, `head_64.S` 的主要目的就是为了做好进入长模式的准备然后进入长模式,进入以后再解压内核。在这一节,我们将会看到直到内核解压缩之前的所有步骤。 -在`arch/x86/boot/compressed`目录下有两个文件: +在 `arch/x86/boot/compressed` 目录下有两个文件: * [head_32.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_32.S) * [head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) -但是,你可能还记得我们这本书只和`x86_64`有关,所以我们只会关注`head_64.S`;在我们这里`head_32.S`没有被用到。让我们看一下 [arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile)。在那里我们可以看到以下目标: +但是,你可能还记得我们这本书只和 `x86_64` 有关,所以我们只会关注 `head_64.S` ;在我们这里 `head_32.S` 没有被用到。让我们看一下 [arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile)。在那里我们可以看到以下目标: ```Makefile vmlinux-objs-y := $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o \ @@ -78,7 +78,7 @@ vmlinux-objs-y := $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o \ $(obj)/piggy.o $(obj)/cpuflags.o ``` -注意`$(obj)/head_$(BITS).o`。这意味着我们将会选择基于`$(BITS)`所设置的文件执行链接操作,head_32.o 或者 head_64.o。`$(BITS)` 在 [arch/x86/kernel/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/Makefile) 之中被 .config 文件另外定义: +注意 `$(obj)/head_$(BITS).o` 。这意味着我们将会选择基于 `$(BITS)` 所设置的文件执行链接操作,head_32.o 或者 head_64.o。`$(BITS)` 在 [arch/x86/kernel/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/Makefile) 之中被 .config 文件另外定义: ```Makefile ifeq ($(CONFIG_X86_32),y) @@ -97,7 +97,7 @@ endif 必要时重载内存段寄存器 -------------------------------------------------------------------------------- -正如上面阐述的,我们先从 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 这个汇编文件开始。首先我们看到了在`startup_32`之前的特殊段属性定义: +正如上面阐述的,我们先从 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 这个汇编文件开始。首先我们看到了在 `startup_32` 之前的特殊段属性定义: ```assembly __HEAD @@ -105,13 +105,13 @@ endif ENTRY(startup_32) ``` -这个`__HEAD`是一个定义在头文件 [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h)中的宏,展开后就是下面这个段的定义: +这个 `__HEAD` 是一个定义在头文件 [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h)中的宏,展开后就是下面这个段的定义: ```C #define __HEAD .section ".head.text","ax" ``` -拥有`.head.text`的命名和`ax`标记。在这里,这些标记告诉我们这个段是[可执行的](https://en.wikipedia.org/wiki/Executable)或者换种说法,包含了代码。我们可以在 [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/vmlinux.lds.S) 这个链接脚本里找到这个段的定义: +拥有 `.head.text` 的命名和 `ax` 标记。在这里,这些标记告诉我们这个段是[可执行的](https://en.wikipedia.org/wiki/Executable)或者换种说法,包含了代码。我们可以在 [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/vmlinux.lds.S) 这个链接脚本里找到这个段的定义: ``` SECTIONS @@ -124,7 +124,7 @@ SECTIONS } ``` -如果你不熟悉`GNU LD`这个链接脚本语言的语法,你可以在[这个文档](https://sourceware.org/binutils/docs/ld/Scripts.html#Scripts)中找到更多信息。简单来说,这个`.`符号是一个链接器的特殊变量 - 位置计数器。其被赋值为相对于该段的偏移。在这里,我们将位置计数器赋值为0,这意味着我们的代码被链接到内存的`0`偏移处。此外,我们可以从注释找到更多信息: +如果你不熟悉 `GNU LD` 这个链接脚本语言的语法,你可以在[这个文档](https://sourceware.org/binutils/docs/ld/Scripts.html#Scripts)中找到更多信息。简单来说,这个 `.` 符号是一个链接器的特殊变量 - 位置计数器。其被赋值为相对于该段的偏移。在这里,我们将位置计数器赋值为0,这意味着我们的代码被链接到内存的 `0` 偏移处。此外,我们可以从注释找到更多信息: ``` Be careful parts of head_64.S assume startup_32 is at address 0. @@ -134,11 +134,11 @@ Be careful parts of head_64.S assume startup_32 is at address 0. 要小心, head_64.S 中一些部分假设 startup_32 位于地址 0。 ``` -好了,现在我们知道我们在哪里了,接下来就是深入`startup_32`函数的最佳时机。 +好了,现在我们知道我们在哪里了,接下来就是深入 `startup_32` 函数的最佳时机。 -在`startup_32`函数的开始,我们可以看到`cld`指令将[标志寄存器](http://baike.baidu.com/view/1845107.htm)的`DF`(方向标志)位清空。当方向标志被清空,所有的串操作指令像[stos](http://x86.renejeschke.de/html/file_module_x86_id_306.html), [scas](http://x86.renejeschke.de/html/file_module_x86_id_287.html)等等将会增加索引寄存器 `esi` 或者 `edi`的值。我们需要清空方向标志是因为接下来我们会使用汇编的串操作指令来做为页表腾出空间等工作。 +在 `startup_32` 函数的开始,我们可以看到 `cld` 指令将[标志寄存器](http://baike.baidu.com/view/1845107.htm)的 `DF` (方向标志)位清空。当方向标志被清空,所有的串操作指令像[stos](http://x86.renejeschke.de/html/file_module_x86_id_306.html), [scas](http://x86.renejeschke.de/html/file_module_x86_id_287.html)等等将会增加索引寄存器 `esi` 或者 `edi` 的值。我们需要清空方向标志是因为接下来我们会使用汇编的串操作指令来做为页表腾出空间等工作。 -在我们清空`DF`标志后,下一步就是从内核加载头中的`loadflags`字段来检查`KEEP_SEGMENTS`标志。你是否还记得在本书的[最初一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-1.md),我们已经看到过`loadflags`。在那里我们检查了`CAN_USE_HEAP`标记以使用堆。现在我们需要检查`KEEP_SEGMENTS`标记。这些标记在 linux 的[引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt)文档中有描述: +在我们清空 `DF` 标志后,下一步就是从内核加载头中的 `loadflags` 字段来检查 `KEEP_SEGMENTS` 标志。你是否还记得在本书的[最初一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-1.md),我们已经看到过 `loadflags` 。在那里我们检查了 `CAN_USE_HEAP` 标记以使用堆。现在我们需要检查 `KEEP_SEGMENTS` 标记。这些标记在 linux 的[引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt)文档中有描述: ``` Bit 6 (write): KEEP_SEGMENTS @@ -156,7 +156,7 @@ Bit 6 (write): KEEP_SEGMENTS - 为1,不在32位入口点重载段寄存器。假设 %cs %ds %ss %es 都被设到基地址为0的普通段中(或者在他们的环境中等价的位置)。 ``` -所以,如果`KEEP_SEGMENTS`位在`loadflags`中没有被设置,我们需要重置`ds`,`ss`和`es`段寄存器到一个基地址为`0`的普通段中。如下: +所以,如果 `KEEP_SEGMENTS` 位在 `loadflags` 中没有被设置,我们需要重置 `ds` , `ss` 和 `es` 段寄存器到一个基地址为 `0` 的普通段中。如下: ```C testb $(1 << 6), BP_loadflags(%esi) @@ -169,9 +169,9 @@ Bit 6 (write): KEEP_SEGMENTS movl %eax, %ss ``` -记住`__BOOT_DS`是`0x18`(位于[全局描述符表](https://en.wikipedia.org/wiki/Global_Descriptor_Table)中数据段的索引)。如果设置了 `KEEP_SEGMENTS` ,我们就跳转到最近的 `1f` 标签,或者当没有`1f`标签,则用`__BOOT_DS`更新段寄存器。这非常简单,但是这是一个有趣的操作。如果你已经读了[前一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md),你或许还记得我们在 [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S) 中切换到[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)的时候已经更新了这些段寄存器。那么为什么我们还要去关心这些段寄存器的值呢。答案很简单,Linux 内核也有32位的引导协议,如果一个引导程序之前使用32位协议引导内核,那么在`startup_32`之前的代码就会被忽略。在这种情况下`startup_32`将会变成引导程序之后的第一个入口点,不保证段寄存器会不会处于未知状态。 +记住 `__BOOT_DS` 是 `0x18` (位于[全局描述符表](https://en.wikipedia.org/wiki/Global_Descriptor_Table)中数据段的索引)。如果设置了 `KEEP_SEGMENTS` ,我们就跳转到最近的 `1f` 标签,或者当没有 `1f` 标签,则用 `__BOOT_DS` 更新段寄存器。这非常简单,但是这是一个有趣的操作。如果你已经读了[前一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md),你或许还记得我们在 [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S) 中切换到[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)的时候已经更新了这些段寄存器。那么为什么我们还要去关心这些段寄存器的值呢。答案很简单,Linux 内核也有32位的引导协议,如果一个引导程序之前使用32位协议引导内核,那么在 `startup_32` 之前的代码就会被忽略。在这种情况下 `startup_32` 将会变成引导程序之后的第一个入口点,不保证段寄存器会不会处于未知状态。 -在我们检查了`KEEP_SEGMENTS`标记并且给段寄存器设置了正确的值之后,下一步就是计算我们代码的加载和编译运行之间的位置偏差了。记住`setup.ld.S`包含了以下定义:在`.head.text`段的开始`. = 0`。这意味着这一段代码被编译成从`0`地址运行。我们可以在`objdump`输出中看到: +在我们检查了 `KEEP_SEGMENTS` 标记并且给段寄存器设置了正确的值之后,下一步就是计算我们代码的加载和编译运行之间的位置偏差了。记住 `setup.ld.S` 包含了以下定义:在 `.head.text` 段的开始 `. = 0` 。这意味着这一段代码被编译成从 `0` 地址运行。我们可以在 `objdump` 输出中看到: ``` arch/x86/boot/compressed/vmlinux: file format elf64-x86-64 @@ -184,14 +184,14 @@ Disassembly of section .head.text: 1: f6 86 11 02 00 00 40 testb $0x40,0x211(%rsi) ``` -`objdump`告诉我们`startup_32`的地址是`0`。但是实际上并不是。我们当前的目标是获知我们实际上在哪里。在[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)下,这非常简单,因为其支持`rip`相对寻址,但是我们当前处于[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)下。我们将会使用一个常用的方法来确定`startup_32`的地址。我们需要定义一个标签并且跳转到它,然后把栈顶抛出到一个寄存器中: + `objdump` 告诉我们 `startup_32` 的地址是 `0` 。但是实际上并不是。我们当前的目标是获知我们实际上在哪里。在[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)下,这非常简单,因为其支持 `rip` 相对寻址,但是我们当前处于[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)下。我们将会使用一个常用的方法来确定 `startup_32` 的地址。我们需要定义一个标签并且跳转到它,然后把栈顶抛出到一个寄存器中: ```assembly call label label: pop %reg ``` -在这之后,那个寄存器将会包含标签的地址,让我们看看在 Linux 内核中相似的寻找`startup_32`地址的代码: +在这之后,那个寄存器将会包含标签的地址,让我们看看在 Linux 内核中相似的寻找 `startup_32` 地址的代码: ```assembly leal (BP_scratch+4)(%esi), %esp @@ -200,7 +200,7 @@ label: pop %reg subl $1b, %ebp ``` -回忆前一节,`esi`寄存器包含了 [boot_params](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/bootparam.h#L113) 结构的地址,这个结构在我们切换到保护模式之前已经被填充了。`bootparams` 这个结构体包含了一个特殊的成员`scratch`,其偏移量为`0x1e4`。这个 4 字节的区域将会成为`call`指令的临时栈。我们把`scratch`的地址加 4 存入`esp`寄存器。我们之所以在`BP_scratch`基础上加`4`是因为,如之前所说的,这将成为一个临时的栈,而在`x86_64`架构下,栈是自顶向下生长的。所以我们的栈指针就会指向栈顶。接下来我们就可以看到我上面描述的过程。我们跳转到 `1f` 标签并且把该标签的地址放入`ebp`寄存器,因为在执行`call`指令之后我们把返回地址放到了栈顶。那么,目前我们拥有`1f`标签的地址,也能够很容易得到`startup_32`的地址。我们只需要把我们从栈里得到的地址减去标签的地址: +回忆前一节, `esi` 寄存器包含了 [boot_params](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/bootparam.h#L113) 结构的地址,这个结构在我们切换到保护模式之前已经被填充了。`bootparams` 这个结构体包含了一个特殊的成员 `scratch` ,其偏移量为 `0x1e4` 。这个 4 字节的区域将会成为 `call` 指令的临时栈。我们把 `scratch` 的地址加 4 存入 `esp` 寄存器。我们之所以在 `BP_scratch` 基础上加 `4` 是因为,如之前所说的,这将成为一个临时的栈,而在 `x86_64` 架构下,栈是自顶向下生长的。所以我们的栈指针就会指向栈顶。接下来我们就可以看到我上面描述的过程。我们跳转到 `1f` 标签并且把该标签的地址放入 `ebp` 寄存器,因为在执行 `call` 指令之后我们把返回地址放到了栈顶。那么,目前我们拥有 `1f` 标签的地址,也能够很容易得到 `startup_32` 的地址。我们只需要把我们从栈里得到的地址减去标签的地址: ``` startup_32 (0x0) +-----------------------+ @@ -218,7 +218,7 @@ startup_32 (0x0) +-----------------------+ +-----------------------+ ``` -`startup_32`被链接为在`0x0`地址运行,这意味着`1f`的地址为`0x0 + 1f 的偏移量`。实际上偏移量大概是`0x22`字节。`ebp`寄存器包含了`1f`标签的实际物理地址。所以如果我们从`ebp`中减去`1f`,我们就会得到`startup_32`的实际物理地址。Linux 内核的[引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt)描述了保护模式下的内核基地址是 `0x100000`。我们可以用 [gdb](https://zh.wikipedia.org/wiki/GNU%E4%BE%A6%E9%94%99%E5%99%A8) 来验证。让我们启动调试器并且在`1f`的地址`0x100022`添加断点。如果这是正确的,我们将会看到在`ebp`寄存器中值为`0x100022`: + `startup_32` 被链接为在 `0x0` 地址运行,这意味着 `1f` 的地址为 `0x0 + 1f 的偏移量` 。实际上偏移量大概是 `0x22` 字节。 `ebp` 寄存器包含了 `1f` 标签的实际物理地址。所以如果我们从 `ebp` 中减去 `1f` ,我们就会得到 `startup_32` 的实际物理地址。Linux 内核的[引导协议](https://www.kernel.org/doc/Documentation/x86/boot.txt)描述了保护模式下的内核基地址是 `0x100000` 。我们可以用 [gdb](https://zh.wikipedia.org/wiki/GNU%E4%BE%A6%E9%94%99%E5%99%A8) 来验证。让我们启动调试器并且在 `1f` 的地址 `0x100022` 添加断点。如果这是正确的,我们将会看到在 `ebp` 寄存器中值为 `0x100022` : ``` $ gdb @@ -250,7 +250,7 @@ fs 0x18 0x18 gs 0x18 0x18 ``` -如果我们执行下一条指令`subl $1b, %ebp`,我们将会看到: +如果我们执行下一条指令 `subl $1b, %ebp` ,我们将会看到: ``` nexti @@ -259,12 +259,12 @@ ebp 0x100000 0x100000 ... ``` -好了,那是对的。`startup_32` 的地址是 `0x100000`。在我们知道了 `startup_32` 的地址之后,我们可以开始准备切换到[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)了。我们的下一个目标是建立栈并且确认 CPU 对长模式和 [SSE](https://zh.wikipedia.org/wiki/SSE) 的支持。 +好了,那是对的。`startup_32` 的地址是 `0x100000` 。在我们知道了 `startup_32` 的地址之后,我们可以开始准备切换到[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)了。我们的下一个目标是建立栈并且确认 CPU 对长模式和 [SSE](https://zh.wikipedia.org/wiki/SSE) 的支持。 栈的建立和 CPU 的确认 -------------------------------------------------------------------------------- -如果不知道 `startup_32` 标签的地址,我们无法建立栈。我们可以把栈看作是一个数组,并且栈指针寄存器`esp`必须指向数组的底部。当然我们可以在自己的代码里定义一个数组,但是我们需要知道其真实地址来正确配置栈指针。让我们看一下代码: +如果不知道 `startup_32` 标签的地址,我们无法建立栈。我们可以把栈看作是一个数组,并且栈指针寄存器 `esp` 必须指向数组的底部。当然我们可以在自己的代码里定义一个数组,但是我们需要知道其真实地址来正确配置栈指针。让我们看一下代码: ```assembly movl $boot_stack_end, %eax @@ -272,7 +272,7 @@ ebp 0x100000 0x100000 movl %eax, %esp ``` -`boots_stack_end` 被定义在同一个汇编文件 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 中,位于 [.bss](https://en.wikipedia.org/wiki/.bss) 段: + `boots_stack_end` 被定义在同一个汇编文件 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 中,位于 [.bss](https://en.wikipedia.org/wiki/.bss) 段: ```assembly .bss @@ -284,9 +284,9 @@ boot_stack: boot_stack_end: ``` -首先,我们把 `boot_stack_end` 放到 `eax` 寄存器中。现在`eax`寄存器将包含`boot_stack_end`链接后的地址或者说`0x0 + boot_stack_end`。为了得到`boot_stack_end`的实际地址,我们需要加上 `startup_32`的实际地址。回忆一下,前面我们找到了这个地址并且把它存到了`ebp`寄存器中。最后,`eax`寄存器将会包含`boot_stack_end`的实际地址,我们只需要将其加到栈指针上。 +首先,我们把 `boot_stack_end` 放到 `eax` 寄存器中。现在 `eax` 寄存器将包含 `boot_stack_end` 链接后的地址或者说 `0x0 + boot_stack_end` 。为了得到 `boot_stack_end` 的实际地址,我们需要加上 `startup_32` 的实际地址。回忆一下,前面我们找到了这个地址并且把它存到了 `ebp` 寄存器中。最后,`eax` 寄存器将会包含 `boot_stack_end` 的实际地址,我们只需要将其加到栈指针上。 -在外面建立了栈之后,下一步是 CPU 的确认。既然我们将要切换到`长模式`,我们需要检查 CPU 是否支持 `长模式` 和 `SSE`。我们将会在跳转到`verify_cpu`函数之后执行: +在外面建立了栈之后,下一步是 CPU 的确认。既然我们将要切换到 `长模式` ,我们需要检查 CPU 是否支持 `长模式` 和 `SSE`。我们将会在跳转到 `verify_cpu` 函数之后执行: ```assembly call verify_cpu @@ -294,9 +294,9 @@ boot_stack_end: jnz no_longmode ``` -这个函数定义在 [arch/x86/kernel/verify_cpu.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/verify_cpu.S) 中,只是包含了几个对 [cpuid](https://en.wikipedia.org/wiki/CPUID) 指令的调用。该指令用于获取处理器的信息。在我们的情况下,它检查了对`长模式`和`SSE`的支持,通过`eax`寄存器返回0表示成功,1表示失败。 +这个函数定义在 [arch/x86/kernel/verify_cpu.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/verify_cpu.S) 中,只是包含了几个对 [cpuid](https://en.wikipedia.org/wiki/CPUID) 指令的调用。该指令用于获取处理器的信息。在我们的情况下,它检查了对 `长模式` 和 `SSE` 的支持,通过 `eax` 寄存器返回0表示成功,1表示失败。 -如果`eax`的值不是 0 ,我们跳转到`no_longmode`标签,用`hlt`指令停止 CPU ,期间不会发生硬件中断: +如果 `eax` 的值不是 0 ,我们跳转到 `no_longmode` 标签,用 `hlt` 指令停止 CPU ,期间不会发生硬件中断: ```assembly no_longmode: @@ -310,7 +310,7 @@ no_longmode: 计算重定位地址 -------------------------------------------------------------------------------- -下一步是在必要的时候计算解压缩之后的地址。首先,我们需要知道内核重定位的意义。我们已经知道 Linux 内核的32位入口点地址位于`0x100000`。但是那是一个32位的入口。默认的内核基地址由内核配置项`CONFIG_PHYSICAL_START`的值所确定,其默认值为`0x100000` 或`1 MB`。这里的主要问题是如果内核崩溃了,内核开发者需要一个配置于不同地址加载的`救援内核`来进行 [kdump](https://www.kernel.org/doc/Documentation/kdump/kdump.txt)。Linux 内核提供了特殊的配置选项以解决此问题 - `CONFIG_RELOCATABLE`。我们可以在内核文档中找到: +下一步是在必要的时候计算解压缩之后的地址。首先,我们需要知道内核重定位的意义。我们已经知道 Linux 内核的32位入口点地址位于 `0x100000` 。但是那是一个32位的入口。默认的内核基地址由内核配置项 `CONFIG_PHYSICAL_START` 的值所确定,其默认值为 `0x100000` 或 `1 MB` 。这里的主要问题是如果内核崩溃了,内核开发者需要一个配置于不同地址加载的 `救援内核` 来进行 [kdump](https://www.kernel.org/doc/Documentation/kdump/kdump.txt)。Linux 内核提供了特殊的配置选项以解决此问题 - `CONFIG_RELOCATABLE` 。我们可以在内核文档中找到: ``` This builds a kernel image that retains relocation information @@ -327,13 +327,13 @@ it has been loaded at and the compile time physical address 注意:如果 CONFIG_RELOCATABLE=y, 那么 内核将会从其被加载的位置运行,编译时的物理地址 (CONFIG_PHYSICAL_START) 将会被作为最低地址位置的限制。 ``` -简单来说,这意味着相同配置下的 Linux 内核可以从不同地址被启动。这是通过将程序以 [位置无关代码](https://zh.wikipedia.org/wiki/%E5%9C%B0%E5%9D%80%E6%97%A0%E5%85%B3%E4%BB%A3%E7%A0%81) 的形式编译来达到的。如果我们参考 [/arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile),我们将会看到解压器的确是用`-fPIC`标记编译的: +简单来说,这意味着相同配置下的 Linux 内核可以从不同地址被启动。这是通过将程序以 [位置无关代码](https://zh.wikipedia.org/wiki/%E5%9C%B0%E5%9D%80%E6%97%A0%E5%85%B3%E4%BB%A3%E7%A0%81) 的形式编译来达到的。如果我们参考 [/arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/Makefile),我们将会看到解压器的确是用 `-fPIC` 标记编译的: ```Makefile KBUILD_CFLAGS += -fno-strict-aliasing -fPIC ``` -当我们使用位置无关代码时,一段代码的地址是由一个控制地址加上程序计数器计算得到的。我们可以从任意一个地址加载使用这种方式寻址的代码。这就是为什么我们需要获得`startup_32`的实际地址。现在让我们回到 Linux 内核代码。我们目前的目标是计算出内核解压的地址。这个地址的计算取决于内核配置项`CONFIG_RELOCATABLE`。让我们看代码: +当我们使用位置无关代码时,一段代码的地址是由一个控制地址加上程序计数器计算得到的。我们可以从任意一个地址加载使用这种方式寻址的代码。这就是为什么我们需要获得 `startup_32` 的实际地址。现在让我们回到 Linux 内核代码。我们目前的目标是计算出内核解压的地址。这个地址的计算取决于内核配置项 `CONFIG_RELOCATABLE` 。让我们看代码: ```assembly #ifdef CONFIG_RELOCATABLE @@ -351,7 +351,7 @@ KBUILD_CFLAGS += -fno-strict-aliasing -fPIC addl $z_extract_offset, %ebx ``` -记住`ebp`寄存器的值就是`startup_32`标签的物理地址。如果在内核配置中`CONFIG_RELOCATABLE`内核配置项开启,我们就把这个地址放到`ebx`寄存器中,对齐到`2M`,然后和`LOAD_PHYSICAL_ADDR`的值比较。`LOAD_PHYSICAL_ADDR`宏在头文件 [arch/x86/include/asm/boot.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/boot.h) 定义,如下: +记住 `ebp` 寄存器的值就是 `startup_32` 标签的物理地址。如果在内核配置中 `CONFIG_RELOCATABLE` 内核配置项开启,我们就把这个地址放到 `ebx` 寄存器中,对齐到 `2M` ,然后和 `LOAD_PHYSICAL_ADDR` 的值比较。 `LOAD_PHYSICAL_ADDR` 宏在头文件 [arch/x86/include/asm/boot.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/boot.h) 定义,如下: ```C #define LOAD_PHYSICAL_ADDR ((CONFIG_PHYSICAL_START \ @@ -359,9 +359,9 @@ KBUILD_CFLAGS += -fno-strict-aliasing -fPIC & ~(CONFIG_PHYSICAL_ALIGN - 1)) ``` -我们可以看到该宏只是展开成对齐的`CONFIG_PHYSICAL_ALIGN`值,其表示了内核加载位置的物理地址。在比较了`LOAD_PHYSICAL_ADDR`和`ebx`的值之后,我们给`startup_32`加上偏移来获得解压内核镜像的地址。如果`CONFIG_RELOCATABLE`选项在内核配置时没有开启,我们就直接将默认的地址加上`z_extract_offset`。 +我们可以看到该宏只是展开成对齐的 `CONFIG_PHYSICAL_ALIGN` 值,其表示了内核加载位置的物理地址。在比较了 `LOAD_PHYSICAL_ADDR` 和 `ebx` 的值之后,我们给 `startup_32` 加上偏移来获得解压内核镜像的地址。如果 `CONFIG_RELOCATABLE` 选项在内核配置时没有开启,我们就直接将默认的地址加上 `z_extract_offset` 。 -在前面的操作之后,`ebp`包含了我们加载时的地址,`ebx` 被设为内核解压缩的目标地址。 +在前面的操作之后,`ebp` 包含了我们加载时的地址,`ebx` 被设为内核解压缩的目标地址。 进入长模式前的准备工作 -------------------------------------------------------------------------------- @@ -374,7 +374,7 @@ KBUILD_CFLAGS += -fno-strict-aliasing -fPIC lgdt gdt(%ebp) ``` -在这里我们把`ebp`寄存器加上`gdt`偏移存到`eax`寄存器。接下来我们把这个地址放到`ebp`加上`gdt+2`偏移的位置上,并且用`lgdt`指令载入`全局描述符表`。为了理解这个神奇的`gdt` 偏移量,我们需要关注`全局描述符表`的定义。我们可以在同一个[源文件](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S)中找到其定义: +在这里我们把 `ebp` 寄存器加上 `gdt` 偏移存到 `eax` 寄存器。接下来我们把这个地址放到 `ebp` 加上 `gdt+2` 偏移的位置上,并且用 `lgdt` 指令载入 `全局描述符表` 。为了理解这个神奇的 `gdt` 偏移量,我们需要关注 `全局描述符表` 的定义。我们可以在同一个[源文件](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S)中找到其定义: ```assembly .data @@ -390,14 +390,14 @@ gdt: gdt_end: ``` -我们可以看到其位于`.data`段,并且包含了5个描述符:`null`、内核代码段、内核数据段和其他两个任务描述符。我们已经在[上一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md)载入了`全局描述符表`,和我们现在做的差不多,但是描述符改为`CS.L = 1` `CS.D = 0` 从而在`64`位模式下执行。我们可以看到,`gdt`的定义从两个字节开始:`gdt_end - gdt`,代表了`gdt`表的最后一个字节,或者说表的范围。接下来的4个字节包含了`gdt`的基地址。记住`全局描述符表`保存在`48位 GDTR-全局描述符表寄存器`中,由两个部分组成: +我们可以看到其位于 `.data` 段,并且包含了5个描述符: `null` 、内核代码段、内核数据段和其他两个任务描述符。我们已经在[上一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md)载入了 `全局描述符表` ,和我们现在做的差不多,但是描述符改为 `CS.L = 1` `CS.D = 0` 从而在 `64` 位模式下执行。我们可以看到, `gdt` 的定义从两个字节开始: `gdt_end - gdt` ,代表了 `gdt` 表的最后一个字节,或者说表的范围。接下来的4个字节包含了 `gdt` 的基地址。记住 `全局描述符表` 保存在 `48位 GDTR-全局描述符表寄存器` 中,由两个部分组成: * 全局描述符表的大小 (16位) * 全局描述符表的基址 (32位) -所以,我们把`gdt`的地址放到`eax`寄存器,然后存到 `.long gdt` 或者 `gdt+2`。现在我们已经建立了 `GDTR` 寄存器的结构,并且可以用 `lgdt` 指令载入`全局描述符表`了。 +所以,我们把 `gdt` 的地址放到 `eax` 寄存器,然后存到 `.long gdt` 或者 `gdt+2`。现在我们已经建立了 `GDTR` 寄存器的结构,并且可以用 `lgdt` 指令载入 `全局描述符表` 了。 -在我们载入`全局描述符表`之后,我们必须启用 [PAE](http://en.wikipedia.org/wiki/Physical_Address_Extension) 模式。方法是将`cr4`寄存器的值传入`eax`,将第5位置1,然后再写回`cr4`。 +在我们载入 `全局描述符表` 之后,我们必须启用 [PAE](http://en.wikipedia.org/wiki/Physical_Address_Extension) 模式。方法是将 `cr4` 寄存器的值传入 `eax` ,将第5位置1,然后再写回 `cr4` 。 ```assembly movl %cr4, %eax @@ -410,12 +410,12 @@ gdt_end: 长模式 -------------------------------------------------------------------------------- -[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)是 [x86_64](https://en.wikipedia.org/wiki/X86-64) 系列处理器的原生模式。首先让我们看一看`x86_64`和`x86`的一些区别。 +[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)是 [x86_64](https://en.wikipedia.org/wiki/X86-64) 系列处理器的原生模式。首先让我们看一看 `x86_64` 和 `x86` 的一些区别。 -`64位`模式提供了一些新特性,比如: + `64位` 模式提供了一些新特性,比如: -* 从`r8`到`r15`8个新的通用寄存器,并且所有通用寄存器都是64位的了。 -* 64位指令指针 - `RIP`; +* 从 `r8` 到 `r15` 8个新的通用寄存器,并且所有通用寄存器都是64位的了。 +* 64位指令指针 - `RIP` ; * 新的操作模式 - 长模式; * 64位地址和操作数; * RIP 相对寻址 (我们将会在接下来的章节看到一个例子). @@ -429,26 +429,26 @@ gdt_end: * 启用 [PAE](https://en.wikipedia.org/wiki/Physical_Address_Extension); * 建立页表并且将顶级页表的地址放入 `cr3` 寄存器; -* 启用 `EFER.LME`; +* 启用 `EFER.LME` ; * 启用分页; -我们已经通过设置`cr4`控制寄存器中的`PAE`位启动`PAE`了。在下一个段落,我们就要建立[分页](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)的结构了。 +我们已经通过设置 `cr4` 控制寄存器中的 `PAE` 位启动 `PAE` 了。在下一个段落,我们就要建立[分页](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)的结构了。 初期页表初始化 -------------------------------------------------------------------------------- -现在,我们已经知道了在进入`64位`模式之前,我们需要先建立页表,那么就让我们看看如何建立初期的`4G`启动页表。 +现在,我们已经知道了在进入 `64位` 模式之前,我们需要先建立页表,那么就让我们看看如何建立初期的 `4G` 启动页表。 **注意:我不会在这里解释虚拟内存的理论,如果你想知道更多,查看本节最后的链接** -Linux 内核使用`4级`页表,通常我们会建立6个页表: +Linux 内核使用 `4级` 页表,通常我们会建立6个页表: * 1 个 `PML4` 或称为 `4级页映射` 表,包含 1 个项; * 1 个 `PDP` 或称为 `页目录指针` 表,包含 4 个项; * 4 个 页目录表,包含 `2048` 个项; -让我们看看其实现方式。首先我们在内存中为页表清理一块缓存。每个表都是 `4096` 字节,所以我们需要 `24`KB 的空间: +让我们看看其实现方式。首先我们在内存中为页表清理一块缓存。每个表都是 `4096` 字节,所以我们需要 `24` KB 的空间: ```assembly leal pgtable(%ebx), %edi @@ -457,9 +457,9 @@ Linux 内核使用`4级`页表,通常我们会建立6个页表: rep stosl ``` -我们把和`ebx`相关的`pgtable`的地址放到`edi`寄存器中,清空`eax`寄存器,并将`ecx`赋值为`6144`。`rep stosl`指令将会把`eax`的值写到`edi`指向的地址,然后给`edi`加 4 ,`ecx`减 4 ,重复直到`ecx`小于等于 0 。所以我们才把`6144`赋值给 `ecx` 。 +我们把和 `ebx` 相关的 `pgtable` 的地址放到 `edi` 寄存器中,清空 `eax` 寄存器,并将 `ecx` 赋值为 `6144` 。 `rep stosl` 指令将会把 `eax` 的值写到 `edi` 指向的地址,然后给 `edi` 加 4 , `ecx` 减 4 ,重复直到 `ecx` 小于等于 0 。所以我们才把 `6144` 赋值给 `ecx` 。 -`pgtable` 定义在 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 的最后: + `pgtable` 定义在 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 的最后: ```assembly .section ".pgtable","a",@nobits @@ -468,9 +468,9 @@ pgtable: .fill 6*4096, 1, 0 ``` -我们可以看到,其位于 `.pgtable` 段,大小为 `24KB`。 +我们可以看到,其位于 `.pgtable` 段,大小为 `24KB` 。 -在我们为`pgtable`分配了空间之后,我们可以开始构建顶级页表 - `PML4` : +在我们为 `pgtable` 分配了空间之后,我们可以开始构建顶级页表 - `PML4` : ```assembly leal pgtable + 0(%ebx), %edi @@ -478,9 +478,9 @@ pgtable: movl %eax, 0(%edi) ``` -还是在这里,我们把和`ebx`相关的,或者说和`startup_32`相关的`pgtable`的地址放到`ebi`寄存器。接下来我们把相对此地址偏移`0x1007`的地址放到`eax`寄存器中。`0x1007`是`PML4`的大小 `4096` 加上 `7`。这里的`7`代表了`PML4`的项标记。在我们这里,这些标记是`PRESENT+RW+USER`。在最后我们把第一个 `PDP(页目录指针)` 项的地址写到 `PML4` 中。 +还是在这里,我们把和 `ebx` 相关的,或者说和 `startup_32` 相关的 `pgtable` 的地址放到 `ebi` 寄存器。接下来我们把相对此地址偏移 `0x1007` 的地址放到 `eax` 寄存器中。 `0x1007` 是 `PML4` 的大小 `4096` 加上 `7` 。这里的 `7` 代表了 `PML4` 的项标记。在我们这里,这些标记是 `PRESENT+RW+USER` 。在最后我们把第一个 `PDP(页目录指针)` 项的地址写到 `PML4` 中。 -在接下来的一步,我们将会在 `页目录指针(PDP)` 表(3级页表)建立 4 个带有`PRESENT+RW+USE`标记的`Page Directory (2级页表)`项: +在接下来的一步,我们将会在 `页目录指针(PDP)` 表(3级页表)建立 4 个带有 `PRESENT+RW+USE` 标记的 `Page Directory (2级页表)` 项: ```assembly leal pgtable + 0x1000(%ebx), %edi @@ -493,7 +493,7 @@ pgtable: jnz 1b ``` -我们把 3 级页目录指针表的基地址(从`pgtable`表偏移`4096`或者`0x1000`)放到`edi`,把第一个 2 级页目录指针表的首项的地址放到`eax`寄存器。把`4`赋值给`ecx`寄存器,其将会作为接下来循环的计数器,然后将第一个页目录指针项写到`edi`指向的地址。之后,`edi`将会包含带有标记`0x7`的第一个页目录指针项的地址。接下来我们就计算后面的几个页目录指针项的地址,每个占 8 字节,把地址赋值给`eax`,然后回到循环开头将其写入`edi`所在地址。建立页表结构的最后一步就是建立`2048`个`2MB`页的页表项。 +我们把 3 级页目录指针表的基地址(从 `pgtable` 表偏移 `4096` 或者 `0x1000` )放到 `edi` ,把第一个 2 级页目录指针表的首项的地址放到 `eax` 寄存器。把 `4` 赋值给 `ecx` 寄存器,其将会作为接下来循环的计数器,然后将第一个页目录指针项写到 `edi` 指向的地址。之后, `edi` 将会包含带有标记 `0x7` 的第一个页目录指针项的地址。接下来我们就计算后面的几个页目录指针项的地址,每个占 8 字节,把地址赋值给 `eax` ,然后回到循环开头将其写入 `edi` 所在地址。建立页表结构的最后一步就是建立 `2048` 个 `2MB` 页的页表项。 ```assembly leal pgtable + 0x2000(%ebx), %edi @@ -506,14 +506,14 @@ pgtable: jnz 1b ``` -在这里我们做的几乎和上面一样,所有的表项都带着标记 - `$0x00000183` - `PRESENT + WRITE + MBZ`。最后我们将会拥有`2048`个`2MB`页的页表,或者说: +在这里我们做的几乎和上面一样,所有的表项都带着标记 - `$0x00000183` - `PRESENT + WRITE + MBZ` 。最后我们将会拥有 `2048` 个 `2MB` 页的页表,或者说: ```python >>> 2048 * 0x00200000 4294967296 ``` -`4G`页表。我们刚刚完成我们的初期页表结构,其映射了`4G`大小的内存,现在我们可以把高级页表`PML4`的地址放到`cr3`寄存器中了: + `4G` 页表。我们刚刚完成我们的初期页表结构,其映射了 `4G` 大小的内存,现在我们可以把高级页表 `PML4` 的地址放到 `cr3` 寄存器中了: ```assembly leal pgtable(%ebx), %eax @@ -525,7 +525,7 @@ pgtable: 切换到长模式 -------------------------------------------------------------------------------- -首先我们需要设置 [MSR](http://en.wikipedia.org/wiki/Model-specific_register) 中的`EFER.LME`标记为`0xC0000080`: +首先我们需要设置 [MSR](http://en.wikipedia.org/wiki/Model-specific_register) 中的 `EFER.LME` 标记为 `0xC0000080` : ```assembly movl $MSR_EFER, %ecx @@ -534,16 +534,16 @@ pgtable: wrmsr ``` -在这里我们把`MSR_EFER`标记(在 [arch/x86/include/uapi/asm/msr-index.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/msr-index.h#L7) 中定义)放到`ecx`寄存器中,然后调用`rdmsr`指令读取 [MSR](http://en.wikipedia.org/wiki/Model-specific_register) 寄存器。在`rdmsr`执行之后,我们将会获得`edx:eax`中的结果值,其取决于`ecx`的值。我们通过`btsl`指令检查`EFER_LME`位,并且通过`wrmsr`指令将`eax`的数据写入`MSR`寄存器。 +在这里我们把 `MSR_EFER` 标记(在 [arch/x86/include/uapi/asm/msr-index.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/msr-index.h#L7) 中定义)放到 `ecx` 寄存器中,然后调用 `rdmsr` 指令读取 [MSR](http://en.wikipedia.org/wiki/Model-specific_register) 寄存器。在 `rdmsr` 执行之后,我们将会获得 `edx:eax` 中的结果值,其取决于 `ecx` 的值。我们通过 `btsl` 指令检查 `EFER_LME` 位,并且通过 `wrmsr` 指令将 `eax` 的数据写入 `MSR` 寄存器。 -下一步我们将内核段代码地址入栈(我们在 GDT 中定义了),然后将`startup_64`的地址导入`eax`。 +下一步我们将内核段代码地址入栈(我们在 GDT 中定义了),然后将 `startup_64` 的地址导入 `eax` 。 ```assembly pushl $__KERNEL_CS leal startup_64(%ebp), %eax ``` -在这之后我们把这个地址入栈然后通过设置`cr0`寄存器中的`PG`和`PE`启用分页: +在这之后我们把这个地址入栈然后通过设置 `cr0` 寄存器中的 `PG` 和 `PE` 启用分页: ```assembly movl $(X86_CR0_PG | X86_CR0_PE), %eax @@ -556,7 +556,7 @@ pgtable: lret ``` -指令。记住前一步我们已经将`startup_64`函数的地址入栈,在`lret`指令之后,CPU 取出了其地址跳转到那里。 +指令。记住前一步我们已经将 `startup_64` 函数的地址入栈,在 `lret` 指令之后,CPU 取出了其地址跳转到那里。 这些步骤之后我们最后来到了64位模式: From f581e3c3315050f92b4b420101476caed0464238 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 8 Mar 2016 09:47:51 -0800 Subject: [PATCH 36/90] Create Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Booting/linux-bootstrap-3.md diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md new file mode 100644 index 0000000..3bbf880 --- /dev/null +++ b/Booting/linux-bootstrap-3.md @@ -0,0 +1,2 @@ +# 视频模式初始化和转换到保护模式 + From 5b615f0da386d435d6585eb322f598fe83c69d58 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 8 Mar 2016 09:47:57 -0800 Subject: [PATCH 37/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 588 ++++++++++++++++++++++++++++++++++- 1 file changed, 587 insertions(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index 3bbf880..54abbb3 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -1,2 +1,588 @@ -# 视频模式初始化和转换到保护模式 +内核启动过程,第三部分 +================================================================================ + +显示模式初始化和进入保护模式 +-------------------------------------------------------------------------------- + +这一章是`内核启动过程`的第三部分,在[前一章](linux-bootstrap-2.md#kernel-booting-process-part-2)中,我们的内核启动过程之旅停在了对 `set_video` 函数的调用(这个函数定义在 [main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c#L181))。在着一章中,我们将接着上一章继续我们的内核启动之旅。在这一章你将读到下面的内容: +- 显示模式的初始化, +- 在进入保护模式之前的准备工作, +- 正式进入保护模式 + +**注意** 如果你对保护模式一无所知,你可以查看[前一章](linux-bootstrap-2.md#protected-mode) 的相关内容。另外,你也可以查看下面这些[链接](linux-bootstrap-2.md#links) 以了解更多关于保护模式的内容。 + +就像我们前面所说的,我们将从 `set_video` 函数开始我们这章的内容,你可以在 [arch/x86/boot/video.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/video.c#L315) 找到这个函数的定义。 这个函数首先从 `boot_params.hdr` 数据结构获取显示模式设置: + +```C +u16 mode = boot_params.hdr.vid_mode; +``` + +至于 `boot_params.hdr` 数据结构中的内容,是通过 `copy_boot_params` 函数实现的 (关于这个函数的实现细节请查看上一章的内容),`boot_params.hdr` 中的 `vid_mode` 是引导程序必须填入的字段。你可以在 `kernel boot protocol` 文档中找到关于 `vid_mode` 的详细信息: + +``` +Offset Proto Name Meaning +/Size +01FA/2 ALL vid_mode Video mode control +``` + +而在 `linux kernel boot protocol` 文档中定义了如何通过命令行参数的方式为 `vid_mode` 字段传入相应的值: + +``` +**** SPECIAL COMMAND LINE OPTIONS +vga= + here is either an integer (in C notation, either + decimal, octal, or hexadecimal) or one of the strings + "normal" (meaning 0xFFFF), "ext" (meaning 0xFFFE) or "ask" + (meaning 0xFFFD). This value should be entered into the + vid_mode field, as it is used by the kernel before the command + line is parsed. +``` + +所以我们可以通过将 `vga` 选项写入 grub 或者起到引导程序的配置文件将,从而让内核命令行得到相应的显示模式设置信息。就像上面所描述的那样,这个选项可以接受不同类型的值来表示相同的意思。比如你可以传入 `0XFFFD` 或者 `ask`,这2个值都表示需要显示一个菜单让用户选择想要的显示模式。下面的链接就给出了这个菜单: + +![video mode setup menu](http://oi59.tinypic.com/ejcz81.jpg) + +通过这个菜单,用户可以选择想要进入的显示模式。不过再我们进一步了解显示模式的设置过程之前,让我们先回头了解一些重要的概念。 + +内核数据类型 +-------------------------------------------------------------------------------- + +在前面的章节中,我们已经接触到了一个类似于 `u16` 的内核数据类型。下面列出了更多内核支持的数据类型: + + +| Type | char | short | int | long | u8 | u16 | u32 | u64 | +|------|------|-------|-----|------|----|-----|-----|-----| +| Size | 1 | 2 | 4 | 8 | 1 | 2 | 4 | 8 | + +如果你尝试阅读内核代码,最好能够牢记这些数据类型。 them. + +堆操作 API +-------------------------------------------------------------------------------- + +在 `set_video` 函数将 `vid_mod` 的值设置完成之后,将调用 `RESET_HEAP` 宏将 HEAP 头指向 `_end` 符号。`RESET_HEAP` 宏定义在 [boot.h](https://github.com/torvalds/linux/blob/master/arch/x86/boot/boot.h#L199): + +```C +#define RESET_HEAP() ((void *)( HEAP = _end )) +``` + +如果你阅读过第二部分,你应该还记得在第二部分中,我们通过 [`init_heap`](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c#L116) 函数完成了 heap 的初始化。在 `boot.h` 中定义了一系列的方法来操作被初始化之后的 heap。这些操作包括: + +```C +#define RESET_HEAP() ((void *)( HEAP = _end )) +``` + +就像我们在前面看到的,这个宏只是简单的将 HEAP 头设置到 `_end` 标号。在上一章中我们已经说明了 `_end` 标号的,在 `boot.h` 中通过 `extern char _end[];` 来引用(从这里可以看出,在内核初始化的时候堆和栈是共享内存空间的,详细的信息可以查看第一章的堆初始化和第二章的堆初始化): + +下面一个是 `GET_HEAP` 宏: + +```C +#define GET_HEAP(type, n) \ + ((type *)__get_heap(sizeof(type),__alignof__(type),(n))) +``` + +可以看出这个宏调用了 `__get_heap` 函数来进行内存的分配。`__get_heap` 需要下面3个参数来进行内存分配参数: + +* 某个数据类型所占用的字节数 +* `__alignof__(type)` 返回对于请求的数据类型需要怎样的对齐方式 ( 根据我的了解这个是 gcc 提供的一个功能 ) +* `n` 需要分配对少个对应数据类型的对象 + +Implementation of `__get_heap` is: + +```C +static inline char *__get_heap(size_t s, size_t a, size_t n) +{ + char *tmp; + + HEAP = (char *)(((size_t)HEAP+(a-1)) & ~(a-1)); + tmp = HEAP; + HEAP += s*n; + return tmp; +} +``` + +and further we will see its usage, something like: + +```C +saved.data = GET_HEAP(u16, saved.x * saved.y); +``` + +Let's try to understand how `__get_heap` works. We can see here that `HEAP` (which is equal to `_end` after `RESET_HEAP()`) is the address of aligned memory according to the `a` parameter. After this we save the memory address from `HEAP` to the `tmp` variable, move `HEAP` to the end of the allocated block and return `tmp` which is the start address of allocated memory. + +And the last function is: + +```C +static inline bool heap_free(size_t n) +{ + return (int)(heap_end - HEAP) >= (int)n; +} +``` + +which subtracts value of the `HEAP` from the `heap_end` (we calculated it in the previous [part](linux-bootstrap-2.md)) and returns 1 if there is enough memory for `n`. + +That's all. Now we have a simple API for heap and can setup video mode. + +Set up video mode +-------------------------------------------------------------------------------- + +Now we can move directly to video mode initialization. We stopped at the `RESET_HEAP()` call in the `set_video` function. Next is the call to `store_mode_params` which stores video mode parameters in the `boot_params.screen_info` structure which is defined in [include/uapi/linux/screen_info.h](https://github.com/0xAX/linux/blob/master/include/uapi/linux/screen_info.h). + +If we look at the `store_mode_params` function, we can see that it starts with the call to the `store_cursor_position` function. As you can understand from the function name, it gets information about cursor and stores it. + +First of all `store_cursor_position` initializes two variables which have type `biosregs` with `AH = 0x3`, and calls `0x10` BIOS interruption. After the interruption is successfully executed, it returns row and column in the `DL` and `DH` registers. Row and column will be stored in the `orig_x` and `orig_y` fields from the the `boot_params.screen_info` structure. + +After `store_cursor_position` is executed, the `store_video_mode` function will be called. It just gets the current video mode and stores it in `boot_params.screen_info.orig_video_mode`. + +After this, it checks the current video mode and sets the `video_segment`. After the BIOS transfers control to the boot sector, the following addresses are for video memory: + +``` +0xB000:0x0000 32 Kb Monochrome Text Video Memory +0xB800:0x0000 32 Kb Color Text Video Memory +``` + +So we set the `video_segment` variable to `0xB000` if the current video mode is MDA, HGC, or VGA in monochrome mode and to `0xB800` if the current video mode is in color mode. After setting up the address of the video segment, font size needs to be stored in `boot_params.screen_info.orig_video_points` with: + +```C +set_fs(0); +font_size = rdfs16(0x485); +boot_params.screen_info.orig_video_points = font_size; +``` + +First of all we put 0 in the `FS` register with the `set_fs` function. We already saw functions like `set_fs` in the previous part. They are all defined in [boot.h](https://github.com/0xAX/linux/blob/master/arch/x86/boot/boot.h). Next we read the value which is located at address `0x485` (this memory location is used to get the font size) and save the font size in `boot_params.screen_info.orig_video_points`. + +``` + x = rdfs16(0x44a); + y = (adapter == ADAPTER_CGA) ? 25 : rdfs8(0x484)+1; +``` + +Next we get the amount of columns by address `0x44a` and rows by address `0x484` and store them in `boot_params.screen_info.orig_video_cols` and `boot_params.screen_info.orig_video_lines`. After this, execution of `store_mode_params` is finished. + +Next we can see the `save_screen` function which just saves screen content to the heap. This function collects all data which we got in the previous functions like rows and columns amount etc. and stores it in the `saved_screen` structure, which is defined as: + +```C +static struct saved_screen { + int x, y; + int curx, cury; + u16 *data; +} saved; +``` + +It then checks whether the heap has free space for it with: + +```C +if (!heap_free(saved.x*saved.y*sizeof(u16)+512)) + return; +``` + +and allocates space in the heap if it is enough and stores `saved_screen` in it. + +The next call is `probe_cards(0)` from [arch/x86/boot/video-mode.c](https://github.com/0xAX/linux/blob/master/arch/x86/boot/video-mode.c#L33). It goes over all video_cards and collects the number of modes provided by the cards. Here is the interesting moment, we can see the loop: + +```C +for (card = video_cards; card < video_cards_end; card++) { + /* collecting number of modes here */ +} +``` + +but `video_cards` is not declared anywhere. Answer is simple: Every video mode presented in the x86 kernel setup code has definition like this: + +```C +static __videocard video_vga = { + .card_name = "VGA", + .probe = vga_probe, + .set_mode = vga_set_mode, +}; +``` + +where `__videocard` is a macro: + +```C +#define __videocard struct card_info __attribute__((used,section(".videocards"))) +``` + +which means that `card_info` structure: + +```C +struct card_info { + const char *card_name; + int (*set_mode)(struct mode_info *mode); + int (*probe)(void); + struct mode_info *modes; + int nmodes; + int unsafe; + u16 xmode_first; + u16 xmode_n; +}; +``` + +is in the `.videocards` segment. Let's look in the [arch/x86/boot/setup.ld](https://github.com/0xAX/linux/blob/master/arch/x86/boot/setup.ld) linker file, we can see there: + +``` + .videocards : { + video_cards = .; + *(.videocards) + video_cards_end = .; + } +``` + +It means that `video_cards` is just a memory address and all `card_info` structures are placed in this segment. It means that all `card_info` structures are placed between `video_cards` and `video_cards_end`, so we can use it in a loop to go over all of it. After `probe_cards` executes we have all structures like `static __videocard video_vga` with filled `nmodes` (number of video modes). + +After `probe_cards` execution is finished, we move to the main loop in the `set_video` function. There is an infinite loop which tries to set up video mode with the `set_mode` function or prints a menu if we passed `vid_mode=ask` to the kernel command line or video mode is undefined. + +The `set_mode` function is defined in [video-mode.c](https://github.com/0xAX/linux/blob/master/arch/x86/boot/video-mode.c#L147) and gets only one parameter, `mode`, which is the number of video modes (we got it from the menu or in the start of `setup_video`, from the kernel setup header). + +The `set_mode` function checks the `mode` and calls the `raw_set_mode` function. The `raw_set_mode` calls the `set_mode` function for the selected card i.e. `card->set_mode(struct mode_info*)`. We can get access to this function from the `card_info` structure. Every video mode defines this structure with values filled depending upon the video mode (for example for `vga` it is the `video_vga.set_mode` function. See above example of `card_info` structure for `vga`). `video_vga.set_mode` is `vga_set_mode`, which checks the vga mode and calls the respective function: + +```C +static int vga_set_mode(struct mode_info *mode) +{ + vga_set_basic_mode(); + + force_x = mode->x; + force_y = mode->y; + + switch (mode->mode) { + case VIDEO_80x25: + break; + case VIDEO_8POINT: + vga_set_8font(); + break; + case VIDEO_80x43: + vga_set_80x43(); + break; + case VIDEO_80x28: + vga_set_14font(); + break; + case VIDEO_80x30: + vga_set_80x30(); + break; + case VIDEO_80x34: + vga_set_80x34(); + break; + case VIDEO_80x60: + vga_set_80x60(); + break; + } + return 0; +} +``` + +Every function which sets up video mode just calls the `0x10` BIOS interrupt with a certain value in the `AH` register. + +After we have set video mode, we pass it to `boot_params.hdr.vid_mode`. + +Next `vesa_store_edid` is called. This function simply stores the [EDID](https://en.wikipedia.org/wiki/Extended_Display_Identification_Data) (**E**xtended **D**isplay **I**dentification **D**ata) information for kernel use. After this `store_mode_params` is called again. Lastly, if `do_restore` is set, the screen is restored to an earlier state. + +After this we have set video mode and now we can switch to the protected mode. + +Last preparation before transition into protected mode +-------------------------------------------------------------------------------- + +We can see the last function call - `go_to_protected_mode` - in [main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c#L184). As the comment says: `Do the last things and invoke protected mode`, so let's see these last things and switch into protected mode. + +`go_to_protected_mode` is defined in [arch/x86/boot/pm.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pm.c#L104). It contains some functions which make the last preparations before we can jump into protected mode, so let's look at it and try to understand what they do and how it works. + +First is the call to the `realmode_switch_hook` function in `go_to_protected_mode`. This function invokes the real mode switch hook if it is present and disables [NMI](http://en.wikipedia.org/wiki/Non-maskable_interrupt). Hooks are used if the bootloader runs in a hostile environment. You can read more about hooks in the [boot protocol](https://www.kernel.org/doc/Documentation/x86/boot.txt) (see **ADVANCED BOOT LOADER HOOKS**). + +The `realmode_switch` hook presents a pointer to the 16-bit real mode far subroutine which disables non-maskable interrupts. After `realmode_switch` hook (it isn't present for me) is checked, disabling of Non-Maskable Interrupts(NMI) occurs: + +```assembly +asm volatile("cli"); +outb(0x80, 0x70); /* Disable NMI */ +io_delay(); +``` + +At first there is an inline assembly instruction with a `cli` instruction which clears the interrupt flag (`IF`). After this, external interrupts are disabled. The next line disables NMI (non-maskable interrupt). + +An interrupt is a signal to the CPU which is emitted by hardware or software. After getting the signal, the CPU suspends the current instruction sequence, saves its state and transfers control to the interrupt handler. After the interrupt handler has finished it's work, it transfers control to the interrupted instruction. Non-maskable interrupts (NMI) are interrupts which are always processed, independently of permission. It cannot be ignored and is typically used to signal for non-recoverable hardware errors. We will not dive into details of interrupts now, but will discuss it in the next posts. + +Let's get back to the code. We can see that second line is writing `0x80` (disabled bit) byte to `0x70` (CMOS Address register). After that, a call to the `io_delay` function occurs. `io_delay` causes a small delay and looks like: + +```C +static inline void io_delay(void) +{ + const u16 DELAY_PORT = 0x80; + asm volatile("outb %%al,%0" : : "dN" (DELAY_PORT)); +} +``` + +Outputting any byte to the port `0x80` should delay exactly 1 microsecond. So we can write any value (value from `AL` register in our case) to the `0x80` port. After this delay `realmode_switch_hook` function has finished execution and we can move to the next function. + +The next function is `enable_a20`, which enables [A20 line](http://en.wikipedia.org/wiki/A20_line). This function is defined in [arch/x86/boot/a20.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/a20.c) and it tries to enable the A20 gate with different methods. The first is the `a20_test_short` function which checks if A20 is already enabled or not with the `a20_test` function: + +```C +static int a20_test(int loops) +{ + int ok = 0; + int saved, ctr; + + set_fs(0x0000); + set_gs(0xffff); + + saved = ctr = rdfs32(A20_TEST_ADDR); + + while (loops--) { + wrfs32(++ctr, A20_TEST_ADDR); + io_delay(); /* Serialize and make delay constant */ + ok = rdgs32(A20_TEST_ADDR+0x10) ^ ctr; + if (ok) + break; + } + + wrfs32(saved, A20_TEST_ADDR); + return ok; +} +``` + +First of all we put `0x0000` in the `FS` register and `0xffff` in the `GS` register. Next we read the value in address `A20_TEST_ADDR` (it is `0x200`) and put this value into the `saved` variable and `ctr`. + +Next we write an updated `ctr` value into `fs:gs` with the `wrfs32` function, then delay for 1ms, and then read the value from the `GS` register by address `A20_TEST_ADDR+0x10`, if it's not zero we already have enabled the A20 line. If A20 is disabled, we try to enable it with a different method which you can find in the `a20.c`. For example with call of `0x15` BIOS interrupt with `AH=0x2041` etc. + +If the `enabled_a20` function finished with fail, print an error message and call function `die`. You can remember it from the first source code file where we started - [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S): + +```assembly +die: + hlt + jmp die + .size die, .-die +``` + +After the A20 gate is successfully enabled, the `reset_coprocessor` function is called: + ```C +outb(0, 0xf0); +outb(0, 0xf1); +``` +This function clears the Math Coprocessor by writing `0` to `0xf0` and then resets it by writing `0` to `0xf1`. + +After this, the `mask_all_interrupts` function is called: +```C +outb(0xff, 0xa1); /* Mask all interrupts on the secondary PIC */ +outb(0xfb, 0x21); /* Mask all but cascade on the primary PIC */ +``` +This masks all interrupts on the secondary PIC (Programmable Interrupt Controller) and primary PIC except for IRQ2 on the primary PIC. + +And after all of these preparations, we can see the actual transition into protected mode. + +Set up Interrupt Descriptor Table +-------------------------------------------------------------------------------- + +Now we set up the Interrupt Descriptor table (IDT). `setup_idt`: + +```C +static void setup_idt(void) +{ + static const struct gdt_ptr null_idt = {0, 0}; + asm volatile("lidtl %0" : : "m" (null_idt)); +} +``` + +which sets up the Interrupt Descriptor Table (describes interrupt handlers and etc.). For now the IDT is not installed (we will see it later), but now we just the load IDT with the `lidtl` instruction. `null_idt` contains address and size of IDT, but now they are just zero. `null_idt` is a `gdt_ptr` structure, it as defined as: +```C +struct gdt_ptr { + u16 len; + u32 ptr; +} __attribute__((packed)); +``` + +where we can see the 16-bit length(`len`) of the IDT and the 32-bit pointer to it (More details about the IDT and interruptions will be seen in the next posts). ` __attribute__((packed))` means that the size of `gdt_ptr` is the minimum required size. So the size of the `gdt_ptr` will be 6 bytes here or 48 bits. (Next we will load the pointer to the `gdt_ptr` to the `GDTR` register and you might remember from the previous post that it is 48-bits in size). + +Set up Global Descriptor Table +-------------------------------------------------------------------------------- + +Next is the setup of the Global Descriptor Table (GDT). We can see the `setup_gdt` function which sets up GDT (you can read about it in the [Kernel booting process. Part 2.](linux-bootstrap-2.md#protected-mode)). There is a definition of the `boot_gdt` array in this function, which contains the definition of the three segments: + +```C + static const u64 boot_gdt[] __attribute__((aligned(16))) = { + [GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff), + [GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff), + [GDT_ENTRY_BOOT_TSS] = GDT_ENTRY(0x0089, 4096, 103), + }; +``` + +For code, data and TSS (Task State Segment). We will not use the task state segment for now, it was added there to make Intel VT happy as we can see in the comment line (if you're interested you can find commit which describes it - [here](https://github.com/torvalds/linux/commit/88089519f302f1296b4739be45699f06f728ec31)). Let's look at `boot_gdt`. First of all note that it has the `__attribute__((aligned(16)))` attribute. It means that this structure will be aligned by 16 bytes. Let's look at a simple example: +```C +#include + +struct aligned { + int a; +}__attribute__((aligned(16))); + +struct nonaligned { + int b; +}; + +int main(void) +{ + struct aligned a; + struct nonaligned na; + + printf("Not aligned - %zu \n", sizeof(na)); + printf("Aligned - %zu \n", sizeof(a)); + + return 0; +} +``` + +Technically a structure which contains one `int` field must be 4 bytes, but here `aligned` structure will be 16 bytes: + +``` +$ gcc test.c -o test && test +Not aligned - 4 +Aligned - 16 +``` + +`GDT_ENTRY_BOOT_CS` has index - 2 here, `GDT_ENTRY_BOOT_DS` is `GDT_ENTRY_BOOT_CS + 1` and etc. It starts from 2, because first is a mandatory null descriptor (index - 0) and the second is not used (index - 1). + +`GDT_ENTRY` is a macro which takes flags, base and limit and builds GDT entry. For example let's look at the code segment entry. `GDT_ENTRY` takes following values: + +* base - 0 +* limit - 0xfffff +* flags - 0xc09b + +What does this mean? The segment's base address is 0, and the limit (size of segment) is - `0xffff` (1 MB). Let's look at the flags. It is `0xc09b` and it will be: + +``` +1100 0000 1001 1011 +``` + +in binary. Let's try to understand what every bit means. We will go through all bits from left to right: + +* 1 - (G) granularity bit +* 1 - (D) if 0 16-bit segment; 1 = 32-bit segment +* 0 - (L) executed in 64 bit mode if 1 +* 0 - (AVL) available for use by system software +* 0000 - 4 bit length 19:16 bits in the descriptor +* 1 - (P) segment presence in memory +* 00 - (DPL) - privilege level, 0 is the highest privilege +* 1 - (S) code or data segment, not a system segment +* 101 - segment type execute/read/ +* 1 - accessed bit + +You can read more about every bit in the previous [post](linux-bootstrap-2.md) or in the [Intel® 64 and IA-32 Architectures Software Developer's Manuals 3A](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html). + +After this we get the length of the GDT with: + +```C +gdt.len = sizeof(boot_gdt)-1; +``` + +We get the size of `boot_gdt` and subtract 1 (the last valid address in the GDT). + +Next we get a pointer to the GDT with: + +```C +gdt.ptr = (u32)&boot_gdt + (ds() << 4); +``` + +Here we just get the address of `boot_gdt` and add it to the address of the data segment left-shifted by 4 bits (remember we're in the real mode now). + +Lastly we execute the `lgdtl` instruction to load the GDT into the GDTR register: + +```C +asm volatile("lgdtl %0" : : "m" (gdt)); +``` + +Actual transition into protected mode +-------------------------------------------------------------------------------- + +This is the end of the `go_to_protected_mode` function. We loaded IDT, GDT, disable interruptions and now can switch the CPU into protected mode. The last step is calling the `protected_mode_jump` function with two parameters: + +```C +protected_mode_jump(boot_params.hdr.code32_start, (u32)&boot_params + (ds() << 4)); +``` + +which is defined in [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S#L26). It takes two parameters: + +* address of protected mode entry point +* address of `boot_params` + +Let's look inside `protected_mode_jump`. As I wrote above, you can find it in `arch/x86/boot/pmjump.S`. The first parameter will be in the `eax` register and second is in `edx`. + +First of all we put the address of `boot_params` in the `esi` register and the address of code segment register `cs` (0x1000) in `bx`. After this we shift `bx` by 4 bits and add the address of label `2` to it (we will have the physical address of label `2` in the `bx` after this) and jump to label `1`. Next we put data segment and task state segment in the `cs` and `di` registers with: + +```assembly +movw $__BOOT_DS, %cx +movw $__BOOT_TSS, %di +``` + +As you can read above `GDT_ENTRY_BOOT_CS` has index 2 and every GDT entry is 8 byte, so `CS` will be `2 * 8 = 16`, `__BOOT_DS` is 24 etc. + +Next we set the `PE` (Protection Enable) bit in the `CR0` control register: + +```assembly +movl %cr0, %edx +orb $X86_CR0_PE, %dl +movl %edx, %cr0 +``` + +and make a long jump to protected mode: + +```assembly + .byte 0x66, 0xea +2: .long in_pm32 + .word __BOOT_CS +``` + +where +* `0x66` is the operand-size prefix which allows us to mix 16-bit and 32-bit code, +* `0xea` - is the jump opcode, +* `in_pm32` is the segment offset +* `__BOOT_CS` is the code segment. + +After this we are finally in the protected mode: + +```assembly +.code32 +.section ".text32","ax" +``` + +Let's look at the first steps in protected mode. First of all we set up the data segment with: + +```assembly +movl %ecx, %ds +movl %ecx, %es +movl %ecx, %fs +movl %ecx, %gs +movl %ecx, %ss +``` + +If you paid attention, you can remember that we saved `$__BOOT_DS` in the `cx` register. Now we fill it with all segment registers besides `cs` (`cs` is already `__BOOT_CS`). Next we zero out all general purpose registers besides `eax` with: + +```assembly +xorl %ecx, %ecx +xorl %edx, %edx +xorl %ebx, %ebx +xorl %ebp, %ebp +xorl %edi, %edi +``` + +And jump to the 32-bit entry point in the end: + +``` +jmpl *%eax +``` + +Remember that `eax` contains the address of the 32-bit entry (we passed it as first parameter into `protected_mode_jump`). + +That's all. We're in the protected mode and stop at it's entry point. We will see what happens next in the next part. + +Conclusion +-------------------------------------------------------------------------------- + +This is the end of the third part about linux kernel insides. In next part we will see first steps in the protected mode and transition into the [long mode](http://en.wikipedia.org/wiki/Long_mode). + +If you have any questions or suggestions write me a comment or ping me at [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 a PR with corrections at [linux-insides](https://github.com/0xAX/linux-internals).** + +Links +-------------------------------------------------------------------------------- + +* [VGA](http://en.wikipedia.org/wiki/Video_Graphics_Array) +* [VESA BIOS Extensions](http://en.wikipedia.org/wiki/VESA_BIOS_Extensions) +* [Data structure alignment](http://en.wikipedia.org/wiki/Data_structure_alignment) +* [Non-maskable interrupt](http://en.wikipedia.org/wiki/Non-maskable_interrupt) +* [A20](http://en.wikipedia.org/wiki/A20_line) +* [GCC designated inits](https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Designated-Inits.html) +* [GCC type attributes](https://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html) +* [Previous part](linux-bootstrap-2.md) From 79618d948805b39b36217ab449e57fe560f1459a Mon Sep 17 00:00:00 2001 From: hailin Date: Tue, 8 Mar 2016 11:44:09 -0800 Subject: [PATCH 38/90] add bootstrap 3 --- Booting/linux-bootstrap-3.md | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) mode change 100644 => 100755 Booting/linux-bootstrap-3.md diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md old mode 100644 new mode 100755 index 54abbb3..11a231b --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -86,7 +86,7 @@ vga= * `__alignof__(type)` 返回对于请求的数据类型需要怎样的对齐方式 ( 根据我的了解这个是 gcc 提供的一个功能 ) * `n` 需要分配对少个对应数据类型的对象 -Implementation of `__get_heap` is: +下面是 `__get_heap` 函数的实现: ```C static inline char *__get_heap(size_t s, size_t a, size_t n) @@ -100,15 +100,9 @@ static inline char *__get_heap(size_t s, size_t a, size_t n) } ``` -and further we will see its usage, something like: +现在让我们来了解这个函数是如何工作的。 这个函数首先根据对齐方式要求(参数 `a` )调整 `HEAP` 的值,然后将 `HEAP` 值赋值给一个临时变量 `tmp`。接下来根据需要分配的对象的个数(参数 `n` ),预留出所需要的内存,然后将 `tmp` 返回给调用端。 -```C -saved.data = GET_HEAP(u16, saved.x * saved.y); -``` - -Let's try to understand how `__get_heap` works. We can see here that `HEAP` (which is equal to `_end` after `RESET_HEAP()`) is the address of aligned memory according to the `a` parameter. After this we save the memory address from `HEAP` to the `tmp` variable, move `HEAP` to the end of the allocated block and return `tmp` which is the start address of allocated memory. - -And the last function is: +最后一个关于 heap 的操作是: ```C static inline bool heap_free(size_t n) @@ -117,11 +111,11 @@ static inline bool heap_free(size_t n) } ``` -which subtracts value of the `HEAP` from the `heap_end` (we calculated it in the previous [part](linux-bootstrap-2.md)) and returns 1 if there is enough memory for `n`. +这个函数简单做了一个减法 `heap_end - HEAP`,如果相减的结果大于请求的内存,那么就返回真,否则返回假。 -That's all. Now we have a simple API for heap and can setup video mode. +我们已经看到了所有可以对 heap 进行操作,下面让我们继续显示模式设置过程。 -Set up video mode +设置显示模式 -------------------------------------------------------------------------------- Now we can move directly to video mode initialization. We stopped at the `RESET_HEAP()` call in the `set_video` function. Next is the call to `store_mode_params` which stores video mode parameters in the `boot_params.screen_info` structure which is defined in [include/uapi/linux/screen_info.h](https://github.com/0xAX/linux/blob/master/include/uapi/linux/screen_info.h). @@ -130,7 +124,7 @@ If we look at the `store_mode_params` function, we can see that it starts with t First of all `store_cursor_position` initializes two variables which have type `biosregs` with `AH = 0x3`, and calls `0x10` BIOS interruption. After the interruption is successfully executed, it returns row and column in the `DL` and `DH` registers. Row and column will be stored in the `orig_x` and `orig_y` fields from the the `boot_params.screen_info` structure. -After `store_cursor_position` is executed, the `store_video_mode` function will be called. It just gets the current video mode and stores it in `boot_params.screen_info.orig_video_mode`. +After `store_cursor_position` is executed, the `store_video_mode` function will be called. It just gets the current video mode and stores it in `boot_params.screen_info.orig_video_mode`. After this, it checks the current video mode and sets the `video_segment`. After the BIOS transfers control to the boot sector, the following addresses are for video memory: @@ -226,9 +220,9 @@ is in the `.videocards` segment. Let's look in the [arch/x86/boot/setup.ld](http It means that `video_cards` is just a memory address and all `card_info` structures are placed in this segment. It means that all `card_info` structures are placed between `video_cards` and `video_cards_end`, so we can use it in a loop to go over all of it. After `probe_cards` executes we have all structures like `static __videocard video_vga` with filled `nmodes` (number of video modes). -After `probe_cards` execution is finished, we move to the main loop in the `set_video` function. There is an infinite loop which tries to set up video mode with the `set_mode` function or prints a menu if we passed `vid_mode=ask` to the kernel command line or video mode is undefined. +After `probe_cards` execution is finished, we move to the main loop in the `set_video` function. There is an infinite loop which tries to set up video mode with the `set_mode` function or prints a menu if we passed `vid_mode=ask` to the kernel command line or video mode is undefined. -The `set_mode` function is defined in [video-mode.c](https://github.com/0xAX/linux/blob/master/arch/x86/boot/video-mode.c#L147) and gets only one parameter, `mode`, which is the number of video modes (we got it from the menu or in the start of `setup_video`, from the kernel setup header). +The `set_mode` function is defined in [video-mode.c](https://github.com/0xAX/linux/blob/master/arch/x86/boot/video-mode.c#L147) and gets only one parameter, `mode`, which is the number of video modes (we got it from the menu or in the start of `setup_video`, from the kernel setup header). The `set_mode` function checks the `mode` and calls the `raw_set_mode` function. The `raw_set_mode` calls the `set_mode` function for the selected card i.e. `card->set_mode(struct mode_info*)`. We can get access to this function from the `card_info` structure. Every video mode defines this structure with values filled depending upon the video mode (for example for `vga` it is the `video_vga.set_mode` function. See above example of `card_info` structure for `vga`). `video_vga.set_mode` is `vga_set_mode`, which checks the vga mode and calls the respective function: @@ -585,4 +579,3 @@ Links * [GCC designated inits](https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Designated-Inits.html) * [GCC type attributes](https://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html) * [Previous part](linux-bootstrap-2.md) - From 76b27f891fef3b73d13e7b6bf6888288fafb267f Mon Sep 17 00:00:00 2001 From: hailin cai Date: Wed, 9 Mar 2016 10:48:44 -0500 Subject: [PATCH 39/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 74 ++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 28 deletions(-) mode change 100755 => 100644 Booting/linux-bootstrap-3.md diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md old mode 100755 new mode 100644 index 11a231b..38322c0 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -118,39 +118,40 @@ static inline bool heap_free(size_t n) 设置显示模式 -------------------------------------------------------------------------------- -Now we can move directly to video mode initialization. We stopped at the `RESET_HEAP()` call in the `set_video` function. Next is the call to `store_mode_params` which stores video mode parameters in the `boot_params.screen_info` structure which is defined in [include/uapi/linux/screen_info.h](https://github.com/0xAX/linux/blob/master/include/uapi/linux/screen_info.h). +在我们分析了内核数据类型以及和 HEAP 相关的操作之后,让我们回来继续分析显示模式的初始化。在 `RESET_HEAP()` 函数被调用之后,`set_video` 函数接着调用 `store_mode_params` 函数将对应显示模式的相关参数写入 `boot_params.screen_info` 字段。这个字段的结构定义可以在 [include/uapi/linux/screen_info.h](https://github.com/0xAX/linux/blob/master/include/uapi/linux/screen_info.h) 中找到。 -If we look at the `store_mode_params` function, we can see that it starts with the call to the `store_cursor_position` function. As you can understand from the function name, it gets information about cursor and stores it. +`store_mode_params` 函数将调用 `store_cursor_position` 函数将当前屏幕上光标的位置保存起来。下面让我们来看 `store_cursor_poistion` 函数是如何实现的。 -First of all `store_cursor_position` initializes two variables which have type `biosregs` with `AH = 0x3`, and calls `0x10` BIOS interruption. After the interruption is successfully executed, it returns row and column in the `DL` and `DH` registers. Row and column will be stored in the `orig_x` and `orig_y` fields from the the `boot_params.screen_info` structure. +首先函数初始化一个类型为 `biosregs` 的变量,将其中的 `AH` 寄存器内容设置成 `0x3`,然后调用 `0x10` bios 中断。当中断调用返回之后,`DL` 和 `DH` 寄存器分别包含了当前按光标的行和列信息。接着,这2个信息将被保存如 `boot_params.screen_info` 字段的 `orig_x` 和 `orig_y`字段。 -After `store_cursor_position` is executed, the `store_video_mode` function will be called. It just gets the current video mode and stores it in `boot_params.screen_info.orig_video_mode`. +在 `store_cursor_position` 函数执行完毕之后,`store_mode_params` 函数将调用 `store_vide_mode` 函数将当前使用的现实模式保存到 `boot_params.screen_info.orig_video_mode`。 -After this, it checks the current video mode and sets the `video_segment`. After the BIOS transfers control to the boot sector, the following addresses are for video memory: +接下來 `store_mode_params` 函数将根据当前显示模式的设定,给 `video_segment` 变量设置正确的值(实际上就是设置显示内存的起始地址)。在 BIOS 将控制权转移到引导扇区的时候,显示内存地址和显示模式的对应关系如下表所示: ``` 0xB000:0x0000 32 Kb Monochrome Text Video Memory 0xB800:0x0000 32 Kb Color Text Video Memory ``` -So we set the `video_segment` variable to `0xB000` if the current video mode is MDA, HGC, or VGA in monochrome mode and to `0xB800` if the current video mode is in color mode. After setting up the address of the video segment, font size needs to be stored in `boot_params.screen_info.orig_video_points` with: +根据上表,如果当前显示模式是 MDA, HGC 或者单色 VGA 模式,那么 `video_sgement` 的值将被设置成 `0xB000`;如果当前显示模式是彩色模式,那么 `video_segment` 的值将被设置成 `0xB800`。在这之后,`store_mode_params` 函数将保存字体大小信息到 `boot_params.screen_info.orig_video_points`: ```C +//保存字体大小信息 set_fs(0); font_size = rdfs16(0x485); boot_params.screen_info.orig_video_points = font_size; ``` -First of all we put 0 in the `FS` register with the `set_fs` function. We already saw functions like `set_fs` in the previous part. They are all defined in [boot.h](https://github.com/0xAX/linux/blob/master/arch/x86/boot/boot.h). Next we read the value which is located at address `0x485` (this memory location is used to get the font size) and save the font size in `boot_params.screen_info.orig_video_points`. +这段代码首先调用 `set_fs` 函数(在 [boot.h](https://github.com/0xAX/linux/blob/master/arch/x86/boot/boot.h) 中定义了许多类似的函数进行寄存器操作)将数字 `0` 放入 `FS` 寄存器。接着从内存地址 `0x485` 处获取字体大小信息并保存到 `boot_params.screen_info.orig_video_points`。 ``` x = rdfs16(0x44a); y = (adapter == ADAPTER_CGA) ? 25 : rdfs8(0x484)+1; ``` -Next we get the amount of columns by address `0x44a` and rows by address `0x484` and store them in `boot_params.screen_info.orig_video_cols` and `boot_params.screen_info.orig_video_lines`. After this, execution of `store_mode_params` is finished. +接下来代码将从地址 `0x44a` 处获得屏幕列信息,从地址 `0x484` 处获得屏幕行信息,并将它们保存到 `boot_params.screen_info.orig_video_cols` 和 `boot_params.screen_info.orig_video_lines`。到这里,`store_mode_params` 的执行就结束了。 -Next we can see the `save_screen` function which just saves screen content to the heap. This function collects all data which we got in the previous functions like rows and columns amount etc. and stores it in the `saved_screen` structure, which is defined as: +接下来,`set_video` 函数将条用 `save_screen` 函数将当前屏幕上的所有信息保存到 HEAP 中。这个函数首先获得当前屏幕的所有信息(包括屏幕大小,当前光标位置,屏幕上的字符信息),并且保存到 `saved_screen` 结构体中。这个结构体的定义如下所示: ```C static struct saved_screen { @@ -160,24 +161,37 @@ static struct saved_screen { } saved; ``` -It then checks whether the heap has free space for it with: +接下来函数将检查 HEAP 中是否有足够的空间保存这个结构体的数据: ```C if (!heap_free(saved.x*saved.y*sizeof(u16)+512)) return; ``` -and allocates space in the heap if it is enough and stores `saved_screen` in it. +如果 HEAP 有足够的空间,代码将在 HEAP 中分配相应的空间并且将 `saved_screen` 保存到 HEAP。 -The next call is `probe_cards(0)` from [arch/x86/boot/video-mode.c](https://github.com/0xAX/linux/blob/master/arch/x86/boot/video-mode.c#L33). It goes over all video_cards and collects the number of modes provided by the cards. Here is the interesting moment, we can see the loop: +接下来 `set_video` 函数将调用 `probe_cards(0)`(这个函数定义在 [arch/x86/boot/video-mode.c](https://github.com/0xAX/linux/blob/master/arch/x86/boot/video-mode.c#L33))。 这个函数简单遍历所有的显卡,并通过调用驱动程序设置显卡所支持的显示模式: ```C for (card = video_cards; card < video_cards_end; card++) { - /* collecting number of modes here */ + if (card->unsafe == unsafe) { + if (card->probe) + card->nmodes = card->probe(); + else + card->nmodes = 0; + } } ``` -but `video_cards` is not declared anywhere. Answer is simple: Every video mode presented in the x86 kernel setup code has definition like this: +如果你仔细看上面的代码,你会发现 `video_cards` 这个变量并没有被声明,那么程序怎么能够正常编译执行呢?实际上很简单,它指向了一个在 [arch/x86/boot/setup.ld](https://github.com/0xAX/linux/blob/master/arch/x86/boot/setup.ld) 中定义的叫做 `.videocards` 的内存段: +``` + .videocards : { + video_cards = .; + *(.videocards) + video_cards_end = .; + } +``` +那么这段内存里面存放的数据是什么呢,下面我们就来详细分析。在内核初始化代码中,对于每个支持的现实模式都是使用下面的代码进行定义的: ```C static __videocard video_vga = { @@ -187,13 +201,13 @@ static __videocard video_vga = { }; ``` -where `__videocard` is a macro: +`__videocard` 是一个宏定义,如下所示: ```C #define __videocard struct card_info __attribute__((used,section(".videocards"))) ``` -which means that `card_info` structure: +因此 `__videocard` 是一个 `card_info` 结构,这个结构定义如下: ```C struct card_info { @@ -208,21 +222,24 @@ struct card_info { }; ``` -is in the `.videocards` segment. Let's look in the [arch/x86/boot/setup.ld](https://github.com/0xAX/linux/blob/master/arch/x86/boot/setup.ld) linker file, we can see there: +在 `.videocards` 内存段实际上存放的就是所有被内核初始化代码定义的 `card_info` 结构(可以看成是一个数组),所以 `probe_cards` 函数可以使用 `video_cards`,通过循环遍历所有的 `card_info`。 -``` - .videocards : { - video_cards = .; - *(.videocards) - video_cards_end = .; - } +在 `probe_cards` 执行完成之后,我们终于进入 `set_video` 函数的主循环了。在这个循环中,如果 `vid_mode=ask`,那么将显示一个菜单让用户选择想要的显示模式,然后代码将根据用户的选择或者 `vid_mod` ,通过调用 `set_mode` 函数来设置正确的现实模式。如果设置成功,循环结束,否则显示菜单让用户选择显示模式,继续进行设置显示模式的尝试。 + +```c +for (;;) { + if (mode == ASK_VGA) + mode = mode_menu(); + + if (!set_mode(mode)) + break; + + printf("Undefined video mode number: %x\n", mode); + mode = ASK_VGA; + } ``` -It means that `video_cards` is just a memory address and all `card_info` structures are placed in this segment. It means that all `card_info` structures are placed between `video_cards` and `video_cards_end`, so we can use it in a loop to go over all of it. After `probe_cards` executes we have all structures like `static __videocard video_vga` with filled `nmodes` (number of video modes). - -After `probe_cards` execution is finished, we move to the main loop in the `set_video` function. There is an infinite loop which tries to set up video mode with the `set_mode` function or prints a menu if we passed `vid_mode=ask` to the kernel command line or video mode is undefined. - -The `set_mode` function is defined in [video-mode.c](https://github.com/0xAX/linux/blob/master/arch/x86/boot/video-mode.c#L147) and gets only one parameter, `mode`, which is the number of video modes (we got it from the menu or in the start of `setup_video`, from the kernel setup header). +The `set_mode` function is defined in [video-mode.c](https://github.com/0xAX/linux/blob/master/arch/x86/boot/video-mode.c#L147) and gets only one parameter, `mode`, which is the number of video modes (we got it from the menu or in the start of `setup_video`, from the kernel setup header). The `set_mode` function checks the `mode` and calls the `raw_set_mode` function. The `raw_set_mode` calls the `set_mode` function for the selected card i.e. `card->set_mode(struct mode_info*)`. We can get access to this function from the `card_info` structure. Every video mode defines this structure with values filled depending upon the video mode (for example for `vga` it is the `video_vga.set_mode` function. See above example of `card_info` structure for `vga`). `video_vga.set_mode` is `vga_set_mode`, which checks the vga mode and calls the respective function: @@ -579,3 +596,4 @@ Links * [GCC designated inits](https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Designated-Inits.html) * [GCC type attributes](https://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html) * [Previous part](linux-bootstrap-2.md) + From 3933ea1ac7fbd5e2876883c728cf8026369bdb0b Mon Sep 17 00:00:00 2001 From: hailin cai Date: Wed, 9 Mar 2016 10:49:16 -0500 Subject: [PATCH 40/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index 38322c0..73c3bde 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -576,7 +576,7 @@ Remember that `eax` contains the address of the 32-bit entry (we passed it as fi That's all. We're in the protected mode and stop at it's entry point. We will see what happens next in the next part. -Conclusion +结论 -------------------------------------------------------------------------------- This is the end of the third part about linux kernel insides. In next part we will see first steps in the protected mode and transition into the [long mode](http://en.wikipedia.org/wiki/Long_mode). @@ -585,7 +585,7 @@ If you have any questions or suggestions write me a comment or ping me at [twitt **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 a PR with corrections at [linux-insides](https://github.com/0xAX/linux-internals).** -Links +链接 -------------------------------------------------------------------------------- * [VGA](http://en.wikipedia.org/wiki/Video_Graphics_Array) From c7e5cc62bcdd4daa68a9a90380d76d9bed2b4c6d Mon Sep 17 00:00:00 2001 From: hailin Date: Wed, 9 Mar 2016 10:50:06 -0500 Subject: [PATCH 41/90] add more update for chap1_3 --- Booting/linux-bootstrap-3.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Booting/linux-bootstrap-3.md diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md old mode 100644 new mode 100755 From f87d5ac390dbd7ef56d85886e9d20dc28db90809 Mon Sep 17 00:00:00 2001 From: morning <515866323@qq.com> Date: Fri, 11 Mar 2016 21:19:41 +0800 Subject: [PATCH 42/90] =?UTF-8?q?=E7=94=B3=E8=AF=B7=E7=BF=BB=E8=AF=91=201-?= =?UTF-8?q?5=20#23?= 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 8465123..ab19786 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Linux Insides |├1.2|[@hailincai](https://github.com/hailincai)|已完成| |├1.3|[@hailincai](https://github.com/hailincai)|正在进行| |├1.4||未开始| -|└1.5||未开始| +|├1.5|[@chengong](https://github.com/chengong)|正在进行| |Initialization|[@lijiangsheng1](https://github.com/lijiangsheng1)|正在进行| |Interrupts||正在进行| |├3.1|[@littleneko](https://github.com/littleneko)|正在进行| From b482cd8de64d95b7929595e43f256fbdd0ff259a Mon Sep 17 00:00:00 2001 From: hailin cai Date: Fri, 11 Mar 2016 07:27:47 -0800 Subject: [PATCH 43/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) mode change 100755 => 100644 Booting/linux-bootstrap-3.md diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md old mode 100755 new mode 100644 index 73c3bde..cce111e --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -239,9 +239,9 @@ for (;;) { } ``` -The `set_mode` function is defined in [video-mode.c](https://github.com/0xAX/linux/blob/master/arch/x86/boot/video-mode.c#L147) and gets only one parameter, `mode`, which is the number of video modes (we got it from the menu or in the start of `setup_video`, from the kernel setup header). +你可以在 [video-mode.c](https://github.com/0xAX/linux/blob/master/arch/x86/boot/video-mode.c#L147) 中找到 `set_mode` 函数的定义。这个函数只接受一个参数,这个参数是对应的现实模式的数字表示(这个数字来自于显示模式选择菜单,或者从内核命令行参数获得)。 -The `set_mode` function checks the `mode` and calls the `raw_set_mode` function. The `raw_set_mode` calls the `set_mode` function for the selected card i.e. `card->set_mode(struct mode_info*)`. We can get access to this function from the `card_info` structure. Every video mode defines this structure with values filled depending upon the video mode (for example for `vga` it is the `video_vga.set_mode` function. See above example of `card_info` structure for `vga`). `video_vga.set_mode` is `vga_set_mode`, which checks the vga mode and calls the respective function: +`set_mode` 函数首先检查传入的 `mode` 参数,然后调用 `raw_set_mode` 函数。而后者将遍历内核知道的所有 `card_info` 信息,如果发现某张显卡支持传入的模式,这调用 `card_info` 结构中保存的 `set_mode` 函数地址进行显卡显示模式的设置。已 `video_vga` 这个 `card_info` 结构来说,保存在其中的 `set_mode` 函数就指向了 `vga_set_mode` 函数。下面的代码就是 `vga_set_mode` 函数的实现,这个函数根据输入的 vga 显示模式,调用不同的函数完成显示模式的设置: ```C static int vga_set_mode(struct mode_info *mode) @@ -277,7 +277,7 @@ static int vga_set_mode(struct mode_info *mode) } ``` -Every function which sets up video mode just calls the `0x10` BIOS interrupt with a certain value in the `AH` register. +在上面的代码中,每个 `vga_set***` 函数只是简单调用 `0x10` BIOS 中断来进行显示模式的设置。 After we have set video mode, we pass it to `boot_params.hdr.vid_mode`. From 1191b698602cd9928f7b5be1029dbe2877918211 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Fri, 11 Mar 2016 07:48:00 -0800 Subject: [PATCH 44/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index cce111e..ca43674 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -279,13 +279,13 @@ static int vga_set_mode(struct mode_info *mode) 在上面的代码中,每个 `vga_set***` 函数只是简单调用 `0x10` BIOS 中断来进行显示模式的设置。 -After we have set video mode, we pass it to `boot_params.hdr.vid_mode`. +在显卡的显示模式被正确设置之后,这个最终的显示模式被写回 `boot_params.hdr.vid_mode`。 -Next `vesa_store_edid` is called. This function simply stores the [EDID](https://en.wikipedia.org/wiki/Extended_Display_Identification_Data) (**E**xtended **D**isplay **I**dentification **D**ata) information for kernel use. After this `store_mode_params` is called again. Lastly, if `do_restore` is set, the screen is restored to an earlier state. +接下来 `set_video` 函数将调用 `vesa_store_edid` 函数, 这个函数只是简单的将 [EDID](https://en.wikipedia.org/wiki/Extended_Display_Identification_Data) (**E**xtended **D**isplay **I**dentification **D**ata) 写入内存,以便于内核访问。最后, `set_video` 将调用 `do_restore` 函数将前面保存的当前屏幕信息还原到屏幕上。 -After this we have set video mode and now we can switch to the protected mode. +到这里位置,显示模式的设置完成,接下来我们可以切换到保护模式了。 -Last preparation before transition into protected mode +在切换到保护模式之前的最后的准备工作 -------------------------------------------------------------------------------- We can see the last function call - `go_to_protected_mode` - in [main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c#L184). As the comment says: `Do the last things and invoke protected mode`, so let's see these last things and switch into protected mode. From 5f8a0701b0162c2ed5676e765e336b7eebac2bc9 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Fri, 11 Mar 2016 08:17:49 -0800 Subject: [PATCH 45/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index ca43674..e97bedc 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -288,13 +288,32 @@ static int vga_set_mode(struct mode_info *mode) 在切换到保护模式之前的最后的准备工作 -------------------------------------------------------------------------------- -We can see the last function call - `go_to_protected_mode` - in [main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c#L184). As the comment says: `Do the last things and invoke protected mode`, so let's see these last things and switch into protected mode. +在进入保护模式之前的最后一个函数调用发生在 [main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c#L184) 中的 `go_to_protected_mode` 函数,就像这个函数的注释说的,这个函数将进行最后的准备工作然后进入保护模式,线面就让我们来具体看看这个最后的准备工作是什么,以及如何切换如保护模式的。 -`go_to_protected_mode` is defined in [arch/x86/boot/pm.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pm.c#L104). It contains some functions which make the last preparations before we can jump into protected mode, so let's look at it and try to understand what they do and how it works. +`go_to_protected_mode` 函数本身定义在 [arch/x86/boot/pm.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pm.c#L104)。 这个函数调用了一些其他的函数进行最后的准备工作,下面就让我们来看看这些函数是如何工作的。 -First is the call to the `realmode_switch_hook` function in `go_to_protected_mode`. This function invokes the real mode switch hook if it is present and disables [NMI](http://en.wikipedia.org/wiki/Non-maskable_interrupt). Hooks are used if the bootloader runs in a hostile environment. You can read more about hooks in the [boot protocol](https://www.kernel.org/doc/Documentation/x86/boot.txt) (see **ADVANCED BOOT LOADER HOOKS**). +`go_to_protected_mode` 函数首先调用的 `realmode_switch_hook` 函数,后者如果发现 `realmode_swtch` hook, 那么将调用它并禁止 [NMI](http://en.wikipedia.org/wiki/Non-maskable_interrupt) 中断,反之将直接禁止 NMI 中断。只有当 bootloader 运行在特殊的环境下(比如 DOS )才会被使用。你可以在 [boot protocol](https://www.kernel.org/doc/Documentation/x86/boot.txt) (see **ADVANCED BOOT LOADER HOOKS**) 中详细了解 hook 函数的信息。 -The `realmode_switch` hook presents a pointer to the 16-bit real mode far subroutine which disables non-maskable interrupts. After `realmode_switch` hook (it isn't present for me) is checked, disabling of Non-Maskable Interrupts(NMI) occurs: +```c +/* + * Invoke the realmode switch hook if present; otherwise + * disable all interrupts. + */ +static void realmode_switch_hook(void) +{ + if (boot_params.hdr.realmode_swtch) { + asm volatile("lcallw *%0" + : : "m" (boot_params.hdr.realmode_swtch) + : "eax", "ebx", "ecx", "edx"); + } else { + asm volatile("cli"); + outb(0x80, 0x70); /* Disable NMI */ + io_delay(); + } +} +``` + +`realmode_switch` 指向了一个16 位实模式代码地址(远跳转指针),这个16位代码将禁止 NMI 中断。所以在上述代码中,如果 `realmode_swtch` hook 存在,代码是用了 `lcallw` 指令进行远函数调用。在我的环境中,因为不存在这个 hook ,所以代码是直接进入 `else` 部分进行了 NMI 的禁止: ```assembly asm volatile("cli"); @@ -302,7 +321,7 @@ outb(0x80, 0x70); /* Disable NMI */ io_delay(); ``` -At first there is an inline assembly instruction with a `cli` instruction which clears the interrupt flag (`IF`). After this, external interrupts are disabled. The next line disables NMI (non-maskable interrupt). +上面的代码首先调用 `cli` 汇编指令清楚了中断标志 `IF`,这条指令执行之后,外部中断就被禁止了,紧接着的下一行代码就禁止了 NMI 中断。 An interrupt is a signal to the CPU which is emitted by hardware or software. After getting the signal, the CPU suspends the current instruction sequence, saves its state and transfers control to the interrupt handler. After the interrupt handler has finished it's work, it transfers control to the interrupted instruction. Non-maskable interrupts (NMI) are interrupts which are always processed, independently of permission. It cannot be ignored and is typically used to signal for non-recoverable hardware errors. We will not dive into details of interrupts now, but will discuss it in the next posts. From 2ac01a5d8f65a049788891ca82154b9147fa43d3 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Fri, 11 Mar 2016 09:31:09 -0800 Subject: [PATCH 46/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index e97bedc..2cd453c 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -323,9 +323,9 @@ io_delay(); 上面的代码首先调用 `cli` 汇编指令清楚了中断标志 `IF`,这条指令执行之后,外部中断就被禁止了,紧接着的下一行代码就禁止了 NMI 中断。 -An interrupt is a signal to the CPU which is emitted by hardware or software. After getting the signal, the CPU suspends the current instruction sequence, saves its state and transfers control to the interrupt handler. After the interrupt handler has finished it's work, it transfers control to the interrupted instruction. Non-maskable interrupts (NMI) are interrupts which are always processed, independently of permission. It cannot be ignored and is typically used to signal for non-recoverable hardware errors. We will not dive into details of interrupts now, but will discuss it in the next posts. +这里简单介绍一下中断。中断是由硬件或者软件产生的,当中断产生的时候, CPU 将得到通知。这个时候, CPU 将停止当前指令的执行,保存当前代码的环境,然后将控制权移交到中断处理程序。当中断处理程序完成之后,将恢复中断之前的运行环境,那么被中断的代码将继续运行。 NMI 中断是一类特殊的中断,往往预示着系统发生了不可恢复的错误,所以在正常运行的操作系统中,NMI 中断是不会被禁止的,但是在进入保护模式之前,由于特殊需求,代码禁止了这类中断。我们将在后续的章节中对中断做更多的介绍,这里就不展开了。 -Let's get back to the code. We can see that second line is writing `0x80` (disabled bit) byte to `0x70` (CMOS Address register). After that, a call to the `io_delay` function occurs. `io_delay` causes a small delay and looks like: +现在让我们回到上面的代码,在 NMI 中断被禁止之后(通过写 `0x80` 进 CMOS 地址寄存器 `0x70` ),函数接着调用了 `io_delay` 函数进行了短暂的延时以等待 I/O 操作完成。下面就是 `io_delay` 函数的实现: ```C static inline void io_delay(void) @@ -335,9 +335,9 @@ static inline void io_delay(void) } ``` -Outputting any byte to the port `0x80` should delay exactly 1 microsecond. So we can write any value (value from `AL` register in our case) to the `0x80` port. After this delay `realmode_switch_hook` function has finished execution and we can move to the next function. +对 I/O 端口 `0x80` 写入任何的字节都将得到 1 ms 的延时。在上面的代码中,代码将 `al` 寄存器中的值写到了这个端口。在这个 `io_delay` 调用完成之后, `realmode_switch_hook` 函数就完成了所有工作,下面让我们进入下一个函数。 -The next function is `enable_a20`, which enables [A20 line](http://en.wikipedia.org/wiki/A20_line). This function is defined in [arch/x86/boot/a20.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/a20.c) and it tries to enable the A20 gate with different methods. The first is the `a20_test_short` function which checks if A20 is already enabled or not with the `a20_test` function: +下一个函数调用是 `enable_a20`,这个函数使能 [A20 line](http://en.wikipedia.org/wiki/A20_line),你可以在 [arch/x86/boot/a20.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/a20.c) 找到这个函数的定义,这个函数会尝试使用不同的方式来使能 A20 地址线。首先这个函数将调用 `a20_test_short`(该函数将调用 `a20_test` 函数) 来检测 A20 地址线是否已经被激活了: ```C static int a20_test(int loops) @@ -363,11 +363,11 @@ static int a20_test(int loops) } ``` -First of all we put `0x0000` in the `FS` register and `0xffff` in the `GS` register. Next we read the value in address `A20_TEST_ADDR` (it is `0x200`) and put this value into the `saved` variable and `ctr`. +这个函数首先将 `0x0000` 放入 `FS` 寄存器,将 `0xffff` 放入 `GS` 寄存器。然后通过 `rdfs32` 函数调用,将 `A20_TEST_ADDR` 内存地址的内容放入 `saved` 和 `ctr` 变量。 -Next we write an updated `ctr` value into `fs:gs` with the `wrfs32` function, then delay for 1ms, and then read the value from the `GS` register by address `A20_TEST_ADDR+0x10`, if it's not zero we already have enabled the A20 line. If A20 is disabled, we try to enable it with a different method which you can find in the `a20.c`. For example with call of `0x15` BIOS interrupt with `AH=0x2041` etc. +接下来我们使用 `wrfs32` 函数将更新过的 `ctr` 的值写入 `fs:gs` ,然后延时 1ms,接着从Next we write an updated `ctr` value into `fs:gs` with the `wrfs32` function, then delay for 1ms, 接着从 `GS:A20_TEST_ADDR+0x10` 读取内容,如果该地址内容不为0,那么 A20 已经被激活。如果 A20 没有被激活,代码将尝试使用多种方法进行 A20 地址激活。其中的一种方法就是调用 BIOS `0X15` 中断激活 A20 地址线。 -If the `enabled_a20` function finished with fail, print an error message and call function `die`. You can remember it from the first source code file where we started - [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S): +如果 `enabled_a20` 函数调用失败,显示一个错误消息并且调用 `die` 函数结束操作系统运行。`die` 函数定义在 [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S): ```assembly die: @@ -376,21 +376,25 @@ die: .size die, .-die ``` -After the A20 gate is successfully enabled, the `reset_coprocessor` function is called: +A20 地址线被激活之后,`reset_coprocessor` 函数被调用: + ```C outb(0, 0xf0); outb(0, 0xf1); ``` -This function clears the Math Coprocessor by writing `0` to `0xf0` and then resets it by writing `0` to `0xf1`. -After this, the `mask_all_interrupts` function is called: +这个函数非常简单,通过将 `0` 写入 I/O 端口 `0xf0` 和 `0xf1` 以清除数字协处理器。 + +接下来 `mask_all_interrupts` 函数将被调用: + ```C outb(0xff, 0xa1); /* Mask all interrupts on the secondary PIC */ outb(0xfb, 0x21); /* Mask all but cascade on the primary PIC */ ``` -This masks all interrupts on the secondary PIC (Programmable Interrupt Controller) and primary PIC except for IRQ2 on the primary PIC. -And after all of these preparations, we can see the actual transition into protected mode. +这个函数调用激活主和从中断控制器 (Programmable Interrupt Controller)上的中断,唯一的例外是主中断控制器上的级联中断(所有从中断控制器的中断将通过这个级联中断报告给 CPU )。 + +到这里位置,我们就完成了所有的准备工作,下面我们就将正式开始从实模式转换到保护模式。 Set up Interrupt Descriptor Table -------------------------------------------------------------------------------- From 762ea11e9d8d377dcb9b1241ba56746b9893ef36 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Fri, 11 Mar 2016 09:32:15 -0800 Subject: [PATCH 47/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index 2cd453c..35187cb 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -396,7 +396,7 @@ outb(0xfb, 0x21); /* Mask all but cascade on the primary PIC */ 到这里位置,我们就完成了所有的准备工作,下面我们就将正式开始从实模式转换到保护模式。 -Set up Interrupt Descriptor Table +设置中断描述符表 -------------------------------------------------------------------------------- Now we set up the Interrupt Descriptor table (IDT). `setup_idt`: From 6c7578e452e8bc232858af4627dfcef8e8b3496d Mon Sep 17 00:00:00 2001 From: zmj1316 Date: Sat, 12 Mar 2016 20:14:19 +0800 Subject: [PATCH 48/90] Change status --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ca86d7e..a1b7ebf 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Linux Insides |├1.1|[@hailincai](https://github.com/hailincai)|正在进行| |├1.2|[@hailincai](https://github.com/hailincai)|已完成| |├1.3|[@hailincai](https://github.com/hailincai)|正在进行| -|├1.4||未开始| +|├1.4|[@zmj1316](https://github.com/zmj1316)|正在进行| |└1.5||未开始| |Initialization|[@lijiangsheng1](https://github.com/lijiangsheng1)|正在进行| |Interrupts||正在进行| From 96183c78bfb60b9a2bf86faddbc01d263b6b4967 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Mon, 14 Mar 2016 14:26:34 -0400 Subject: [PATCH 49/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 70 ++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index 35187cb..026d2de 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -399,7 +399,7 @@ outb(0xfb, 0x21); /* Mask all but cascade on the primary PIC */ 设置中断描述符表 -------------------------------------------------------------------------------- -Now we set up the Interrupt Descriptor table (IDT). `setup_idt`: +现在内核将调用 `setup_idt` 方法来设置中断描述符表( IDT ): ```C static void setup_idt(void) @@ -409,7 +409,8 @@ static void setup_idt(void) } ``` -which sets up the Interrupt Descriptor Table (describes interrupt handlers and etc.). For now the IDT is not installed (we will see it later), but now we just the load IDT with the `lidtl` instruction. `null_idt` contains address and size of IDT, but now they are just zero. `null_idt` is a `gdt_ptr` structure, it as defined as: +上面的代码使用 `lidtl` 指令将 `null_idt` 所指向的中断描述符表引入寄存器 IDT。由于 `null_idt` 没有设定中断描述符表的长度( 0 ),所以这段指令执行之后,实际上没有任何中断调用被设置成功(所有中断调用都是空的),在后面的章节中我们将看到正确的设置。`null_idt` 是一个 `gdt_ptr` 机构的数据,这个结构的定义如下所示: + ```C struct gdt_ptr { u16 len; @@ -417,12 +418,12 @@ struct gdt_ptr { } __attribute__((packed)); ``` -where we can see the 16-bit length(`len`) of the IDT and the 32-bit pointer to it (More details about the IDT and interruptions will be seen in the next posts). ` __attribute__((packed))` means that the size of `gdt_ptr` is the minimum required size. So the size of the `gdt_ptr` will be 6 bytes here or 48 bits. (Next we will load the pointer to the `gdt_ptr` to the `GDTR` register and you might remember from the previous post that it is 48-bits in size). +在上面的定义中,我们可以看到上面这个结构包含一个 16 bit 的长度字段,和一个 32 bit 的指针字段。`__attribute__((packed))` 意味着这个结构就只包含 48 bit 信息(没有字节对齐优化)。在下面一节中,我们将看到相同的结构将被导入 `GDTR` 寄存器(如果你还记得上一章的内容,应该记得 GDTR 寄存器是 48 bit 长度的)。 -Set up Global Descriptor Table +设置全局描述符表 -------------------------------------------------------------------------------- -Next is the setup of the Global Descriptor Table (GDT). We can see the `setup_gdt` function which sets up GDT (you can read about it in the [Kernel booting process. Part 2.](linux-bootstrap-2.md#protected-mode)). There is a definition of the `boot_gdt` array in this function, which contains the definition of the three segments: +在设置完中断描述符表之后,我们将使用 `setup_gdt` 函数来设置全部描述符表(关于全局描述符表,大家可以参考[上一章](linux-bootstrap-2.md#protected-mode) 的内容)。在 `setup_gdt` 函数中,使用 `boot_gdt` 数组定义了需要引入 GDTR 寄存器的段描述符信息: ```C static const u64 boot_gdt[] __attribute__((aligned(16))) = { @@ -432,7 +433,8 @@ Next is the setup of the Global Descriptor Table (GDT). We can see the `setup_gd }; ``` -For code, data and TSS (Task State Segment). We will not use the task state segment for now, it was added there to make Intel VT happy as we can see in the comment line (if you're interested you can find commit which describes it - [here](https://github.com/torvalds/linux/commit/88089519f302f1296b4739be45699f06f728ec31)). Let's look at `boot_gdt`. First of all note that it has the `__attribute__((aligned(16)))` attribute. It means that this structure will be aligned by 16 bytes. Let's look at a simple example: +在上面的 `boot_gdt` 数组中,我们定义了代码,数据和 TSS 段的段描述符,因为我们并没有设置任何的中断调用(记得上面所的 `null_idt`吗?),所以 TSS 段并不会被使用到。TSS 段存在的唯一目的就是让 Intel 处理器能够正确进入保护模式。下面让我们详细了解一下 `boot_gdt` 这个数组,首先,这个数组被 `__attribute__((aligned(16)))` 修饰,这就意味着这个数组将以 16 字节为单位对齐。让我们通过下面的例子来了解一下什么叫 16 字节对齐: + ```C #include @@ -456,7 +458,7 @@ int main(void) } ``` -Technically a structure which contains one `int` field must be 4 bytes, but here `aligned` structure will be 16 bytes: +上面的代码可以看出,一旦指定了 16 字节对齐,即使结构中只有一个 `int` 类型的字段,整个结构也将占用 16 个字节: ``` $ gcc test.c -o test && test @@ -464,70 +466,68 @@ Not aligned - 4 Aligned - 16 ``` -`GDT_ENTRY_BOOT_CS` has index - 2 here, `GDT_ENTRY_BOOT_DS` is `GDT_ENTRY_BOOT_CS + 1` and etc. It starts from 2, because first is a mandatory null descriptor (index - 0) and the second is not used (index - 1). +因为我们 `boot_gdt` 要求16字节对齐,而我们在这个数组中只定义了 3 个段描述符(一共是 8 *3 = 24字节 ),所以在开头将出现一个空的 8 字节。这样一来这个数组就满足了段描述符表的结构要求(第一项必须是空描述符)。在我们这个数组中,由于前面说的 16 字节对齐的原因,`GDT_ENTRY_BOOT_CS` 的索引将是 1, 而不是 0 ( 当进入保护模式之后,GDT 还会被重新 load )。 -`GDT_ENTRY` is a macro which takes flags, base and limit and builds GDT entry. For example let's look at the code segment entry. `GDT_ENTRY` takes following values: +上面代码中出现的 `GDT_ENTRY` 是一个宏定义,这个宏接受 3 个参数(标志,基地址,段长度)来产生段描述符结构。让我们来具体分析上面数组中的代码段描述符来看看这个宏是如何工作的,对于这个段,`GDT_ENTRY` 接受了下面 3 个参数: -* base - 0 -* limit - 0xfffff -* flags - 0xc09b +* 基地址 - 0 +* 段长度 - 0xfffff +* 标志 - 0xc09b -What does this mean? The segment's base address is 0, and the limit (size of segment) is - `0xffff` (1 MB). Let's look at the flags. It is `0xc09b` and it will be: +上面这些数字表明,这个段的基地址是 0, 段长度是 `0xfffff` ( 1 MB ),而标志字段展开之后是下面的二进制数据: ``` 1100 0000 1001 1011 ``` -in binary. Let's try to understand what every bit means. We will go through all bits from left to right: +这些二进制数据的具体含义如下: -* 1 - (G) granularity bit -* 1 - (D) if 0 16-bit segment; 1 = 32-bit segment -* 0 - (L) executed in 64 bit mode if 1 -* 0 - (AVL) available for use by system software +* 1 - (G) 这里为 1,表示段的实际长度是 `0xfffff * 4kb ` = `4GB` +* 1 - (D) 表示这个段是一个32位段 +* 0 - (L) 这个代码段没有运行在 long mode +* 0 - (AVL) Linux 没有使用 * 0000 - 4 bit length 19:16 bits in the descriptor -* 1 - (P) segment presence in memory -* 00 - (DPL) - privilege level, 0 is the highest privilege -* 1 - (S) code or data segment, not a system segment -* 101 - segment type execute/read/ -* 1 - accessed bit +* 1 - (P) 段已经位于内存中 +* 00 - (DPL) - 段优先级为0 +* 1 - (S) 说明这个段是一个代码或者数据段 +* 101 - 段类型为可执行/可读 +* 1 - 段可访问 -You can read more about every bit in the previous [post](linux-bootstrap-2.md) or in the [Intel® 64 and IA-32 Architectures Software Developer's Manuals 3A](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html). +关于段描述符的更详细的信息你可以从上一章中获得 [post](linux-bootstrap-2.md),你也可以阅读 [Intel® 64 and IA-32 Architectures Software Developer's Manuals 3A](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html)获取全部信息。 -After this we get the length of the GDT with: +在定义了数组之后,代码将获取 GDT 的长度: ```C gdt.len = sizeof(boot_gdt)-1; ``` -We get the size of `boot_gdt` and subtract 1 (the last valid address in the GDT). - -Next we get a pointer to the GDT with: +接下来是将 GDT 的地址放入 gdt.ptr 中: ```C gdt.ptr = (u32)&boot_gdt + (ds() << 4); ``` -Here we just get the address of `boot_gdt` and add it to the address of the data segment left-shifted by 4 bits (remember we're in the real mode now). +这里的地址计算很简单,因为我们还在实模式,所以就是 ( ds << 4 + 数组起始地址)。 -Lastly we execute the `lgdtl` instruction to load the GDT into the GDTR register: +最后通过执行 `lgdtl` 指令将 GDT 信息写入 GDTR 寄存器: ```C asm volatile("lgdtl %0" : : "m" (gdt)); ``` -Actual transition into protected mode +切换进入保护模式 -------------------------------------------------------------------------------- -This is the end of the `go_to_protected_mode` function. We loaded IDT, GDT, disable interruptions and now can switch the CPU into protected mode. The last step is calling the `protected_mode_jump` function with two parameters: +`go_to_protected_mode` 函数在完成 IDT, GDT 初始化,并禁止了 NMI 中断之后,将调用 `protected_mode_jump` 函数完成从实模式到保护模式的跳转: ```C protected_mode_jump(boot_params.hdr.code32_start, (u32)&boot_params + (ds() << 4)); ``` -which is defined in [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S#L26). It takes two parameters: +`protected_mode_jump` 函数定义在 [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S#L26),它接受下面2个参数: -* address of protected mode entry point -* address of `boot_params` +* 保护模式代码的入口 +* `boot_params` 结构的地址 Let's look inside `protected_mode_jump`. As I wrote above, you can find it in `arch/x86/boot/pmjump.S`. The first parameter will be in the `eax` register and second is in `edx`. From 0a862d9545b4d8a12f37b0d262cd0dc9333a38ed Mon Sep 17 00:00:00 2001 From: hailin cai Date: Mon, 14 Mar 2016 15:15:21 -0400 Subject: [PATCH 50/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 46 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index 026d2de..607d31c 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -426,6 +426,7 @@ struct gdt_ptr { 在设置完中断描述符表之后,我们将使用 `setup_gdt` 函数来设置全部描述符表(关于全局描述符表,大家可以参考[上一章](linux-bootstrap-2.md#protected-mode) 的内容)。在 `setup_gdt` 函数中,使用 `boot_gdt` 数组定义了需要引入 GDTR 寄存器的段描述符信息: ```C + //GDT_ENTRY_BOOT_CS 定义在http://lxr.free-electrons.com/source/arch/x86/include/asm/segment.h#L19 = 2 static const u64 boot_gdt[] __attribute__((aligned(16))) = { [GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff), [GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff), @@ -466,7 +467,7 @@ Not aligned - 4 Aligned - 16 ``` -因为我们 `boot_gdt` 要求16字节对齐,而我们在这个数组中只定义了 3 个段描述符(一共是 8 *3 = 24字节 ),所以在开头将出现一个空的 8 字节。这样一来这个数组就满足了段描述符表的结构要求(第一项必须是空描述符)。在我们这个数组中,由于前面说的 16 字节对齐的原因,`GDT_ENTRY_BOOT_CS` 的索引将是 1, 而不是 0 ( 当进入保护模式之后,GDT 还会被重新 load )。 +因为我们 `boot_gdt` 的定义中, `GDT_ENTRY_BOOT_CS = 2`,所以在数组中有2个空项,第一项是一个空的描述符,第二项在代码中没有使用。在没有 `align 16` 之前,整个结构占用了(8*5=40)个字节,加了 `align 16` 之后,结构就占用了 48 字节 。 上面代码中出现的 `GDT_ENTRY` 是一个宏定义,这个宏接受 3 个参数(标志,基地址,段长度)来产生段描述符结构。让我们来具体分析上面数组中的代码段描述符来看看这个宏是如何工作的,对于这个段,`GDT_ENTRY` 接受了下面 3 个参数: @@ -529,18 +530,18 @@ protected_mode_jump(boot_params.hdr.code32_start, (u32)&boot_params + (ds() << 4 * 保护模式代码的入口 * `boot_params` 结构的地址 -Let's look inside `protected_mode_jump`. As I wrote above, you can find it in `arch/x86/boot/pmjump.S`. The first parameter will be in the `eax` register and second is in `edx`. +第一个参数保存在 `eax` 寄存器,而第二个参数保存在 `edx` 寄存器。 -First of all we put the address of `boot_params` in the `esi` register and the address of code segment register `cs` (0x1000) in `bx`. After this we shift `bx` by 4 bits and add the address of label `2` to it (we will have the physical address of label `2` in the `bx` after this) and jump to label `1`. Next we put data segment and task state segment in the `cs` and `di` registers with: +代码首先在 `boot_params` 地址放入 `esi` 寄存器,然后将 `cs` 寄存器内容放入 `bx` 寄存器,接着执行 `bx << 4 + 标号为2的代码的地址`,这样一来 `bx` 寄存器就包含了标号为2的代码的地址。接下来代码将把数据段索引放入 `cx` 寄存器,将 TSS 段索引放入 `di` 寄存器: ```assembly movw $__BOOT_DS, %cx movw $__BOOT_TSS, %di ``` -As you can read above `GDT_ENTRY_BOOT_CS` has index 2 and every GDT entry is 8 byte, so `CS` will be `2 * 8 = 16`, `__BOOT_DS` is 24 etc. +就像前面我们看到的 `GDT_ENTRY_BOOT_CS` 的值为2,每个段描述符都是 8 字节,所以 `cx` 寄存器的值将是 `2*8 = 16`,`di` 寄存器的值将是 `4*8 =32`。 -Next we set the `PE` (Protection Enable) bit in the `CR0` control register: +接下来,我们通过设置 `CR0` 寄存器相应的为使 CPU 进入保护模式: ```assembly movl %cr0, %edx @@ -548,30 +549,31 @@ orb $X86_CR0_PE, %dl movl %edx, %cr0 ``` -and make a long jump to protected mode: +在进入保护模式之后,通过一个长跳转进入 32 位代码: ```assembly .byte 0x66, 0xea 2: .long in_pm32 - .word __BOOT_CS + .word __BOOT_CS ;(GDT_ENTRY_BOOT_CS*8) = 16,段描述符表索引 ``` -where -* `0x66` is the operand-size prefix which allows us to mix 16-bit and 32-bit code, -* `0xea` - is the jump opcode, -* `in_pm32` is the segment offset -* `__BOOT_CS` is the code segment. +这段代码中 +* `0x66` 操作符前缀允许我们混合执行 16 位和 32 位代码 +* `0xea` - 跳转指令的操作符 +* `in_pm32` 跳转地址偏移 +* `__BOOT_CS` 代码段描述符索引 -After this we are finally in the protected mode: +在执行了这个跳转命令之后,我们就在保护模式下执行代码了: ```assembly .code32 .section ".text32","ax" ``` -Let's look at the first steps in protected mode. First of all we set up the data segment with: +保护模式代码的第一步就是重置所有的段寄存器: ```assembly +GLOBAL(in_pm32) movl %ecx, %ds movl %ecx, %es movl %ecx, %fs @@ -579,7 +581,7 @@ movl %ecx, %gs movl %ecx, %ss ``` -If you paid attention, you can remember that we saved `$__BOOT_DS` in the `cx` register. Now we fill it with all segment registers besides `cs` (`cs` is already `__BOOT_CS`). Next we zero out all general purpose registers besides `eax` with: +还记得我们在实模式代码中将 `$__BOOT_DS` (数据段描述符索引)放入了 `cx` 寄存器。,所以上面的代码甚至所有段寄存器(除了 `CS` 寄存器)指向数据段。接下来代码将所有的通用寄存器清 0 : ```assembly xorl %ecx, %ecx @@ -589,24 +591,22 @@ xorl %ebp, %ebp xorl %edi, %edi ``` -And jump to the 32-bit entry point in the end: +最后使用长跳转跳入正在的 32 为代码(通过参数传入的地址) ``` -jmpl *%eax +jmpl *%eax ;?jmpl cs:eax? ``` -Remember that `eax` contains the address of the 32-bit entry (we passed it as first parameter into `protected_mode_jump`). - -That's all. We're in the protected mode and stop at it's entry point. We will see what happens next in the next part. +到这里,我们就进入了保护模式开始执行代码了,下一章我们将分析这段 32 位代码到底做了些什么。 结论 -------------------------------------------------------------------------------- -This is the end of the third part about linux kernel insides. In next part we will see first steps in the protected mode and transition into the [long mode](http://en.wikipedia.org/wiki/Long_mode). +这章到这里就结束了,在下一章中我们将具体介绍这章最后跳转到的 32 位代码,并且了解系统是如何进入 [long mode](http://en.wikipedia.org/wiki/Long_mode)的。 -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 a PR with corrections at [linux-insides](https://github.com/0xAX/linux-internals).** +**英文不是我的母语。如果你发现我的英文描述有任何问题,请提交一个PR到[linux-insides](https://github.com/0xAX/linux-internals).** 链接 -------------------------------------------------------------------------------- From 803cd50a312dfe476a02da6367b86020e4b3cd37 Mon Sep 17 00:00:00 2001 From: "hailin.cai" Date: Tue, 15 Mar 2016 15:00:08 -0400 Subject: [PATCH 51/90] finish chap 1_3 --- Booting/linux-bootstrap-3.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Booting/linux-bootstrap-3.md diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md old mode 100644 new mode 100755 From 9e38b6c8f79c1797e92a154a74f7b1a5fc291955 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Tue, 15 Mar 2016 16:16:39 -0400 Subject: [PATCH 52/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 58 ++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 29 deletions(-) mode change 100755 => 100644 Booting/linux-bootstrap-3.md diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md old mode 100755 new mode 100644 index 607d31c..57536d9 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -4,7 +4,7 @@ 显示模式初始化和进入保护模式 -------------------------------------------------------------------------------- -这一章是`内核启动过程`的第三部分,在[前一章](linux-bootstrap-2.md#kernel-booting-process-part-2)中,我们的内核启动过程之旅停在了对 `set_video` 函数的调用(这个函数定义在 [main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c#L181))。在着一章中,我们将接着上一章继续我们的内核启动之旅。在这一章你将读到下面的内容: +这一章是`内核启动过程`的第三部分,在[前一章](linux-bootstrap-2.md#kernel-booting-process-part-2)中,我们的内核启动过程之旅停在了对 `set_video` 函数的调用(这个函数定义在 [main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c#L181))。在这一章中,我们将接着上一章继续我们的内核启动之旅。在这一章你将读到下面的内容: - 显示模式的初始化, - 在进入保护模式之前的准备工作, - 正式进入保护模式 @@ -38,7 +38,7 @@ vga= line is parsed. ``` -所以我们可以通过将 `vga` 选项写入 grub 或者起到引导程序的配置文件将,从而让内核命令行得到相应的显示模式设置信息。就像上面所描述的那样,这个选项可以接受不同类型的值来表示相同的意思。比如你可以传入 `0XFFFD` 或者 `ask`,这2个值都表示需要显示一个菜单让用户选择想要的显示模式。下面的链接就给出了这个菜单: +根据上面的描述,我们可以通过将 `vga` 选项写入 grub 或者起到引导程序的配置文件,从而让内核命令行得到相应的显示模式设置信息。这个选项可以接受不同类型的值来表示相同的意思。比如你可以传入 `0XFFFD` 或者 `ask`,这2个值都表示需要显示一个菜单让用户选择想要的显示模式。下面的链接就给出了这个菜单: ![video mode setup menu](http://oi59.tinypic.com/ejcz81.jpg) @@ -54,7 +54,7 @@ vga= |------|------|-------|-----|------|----|-----|-----|-----| | Size | 1 | 2 | 4 | 8 | 1 | 2 | 4 | 8 | -如果你尝试阅读内核代码,最好能够牢记这些数据类型。 them. +如果你尝试阅读内核代码,最好能够牢记这些数据类型。 堆操作 API -------------------------------------------------------------------------------- @@ -65,13 +65,13 @@ vga= #define RESET_HEAP() ((void *)( HEAP = _end )) ``` -如果你阅读过第二部分,你应该还记得在第二部分中,我们通过 [`init_heap`](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c#L116) 函数完成了 heap 的初始化。在 `boot.h` 中定义了一系列的方法来操作被初始化之后的 heap。这些操作包括: +如果你阅读过第二部分,你应该还记得在第二部分中,我们通过 [`init_heap`](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c#L116) 函数完成了 HEAP 的初始化。在 `boot.h` 中定义了一系列的方法来操作被初始化之后的 HEAP。这些操作包括: ```C #define RESET_HEAP() ((void *)( HEAP = _end )) ``` -就像我们在前面看到的,这个宏只是简单的将 HEAP 头设置到 `_end` 标号。在上一章中我们已经说明了 `_end` 标号的,在 `boot.h` 中通过 `extern char _end[];` 来引用(从这里可以看出,在内核初始化的时候堆和栈是共享内存空间的,详细的信息可以查看第一章的堆初始化和第二章的堆初始化): +就像我们在前面看到的,这个宏只是简单的将 HEAP 头设置到 `_end` 标号。在上一章中我们已经说明了 `_end` 标号,在 `boot.h` 中通过 `extern char _end[];` 来引用(从这里可以看出,在内核初始化的时候堆和栈是共享内存空间的,详细的信息可以查看第一章的堆栈初始化和第二章的堆初始化): 下面一个是 `GET_HEAP` 宏: @@ -80,7 +80,7 @@ vga= ((type *)__get_heap(sizeof(type),__alignof__(type),(n))) ``` -可以看出这个宏调用了 `__get_heap` 函数来进行内存的分配。`__get_heap` 需要下面3个参数来进行内存分配参数: +可以看出这个宏调用了 `__get_heap` 函数来进行内存的分配。`__get_heap` 需要下面3个参数来进行内存分配操作: * 某个数据类型所占用的字节数 * `__alignof__(type)` 返回对于请求的数据类型需要怎样的对齐方式 ( 根据我的了解这个是 gcc 提供的一个功能 ) @@ -102,7 +102,7 @@ static inline char *__get_heap(size_t s, size_t a, size_t n) 现在让我们来了解这个函数是如何工作的。 这个函数首先根据对齐方式要求(参数 `a` )调整 `HEAP` 的值,然后将 `HEAP` 值赋值给一个临时变量 `tmp`。接下来根据需要分配的对象的个数(参数 `n` ),预留出所需要的内存,然后将 `tmp` 返回给调用端。 -最后一个关于 heap 的操作是: +最后一个关于 HEAP 的操作是: ```C static inline bool heap_free(size_t n) @@ -113,7 +113,7 @@ static inline bool heap_free(size_t n) 这个函数简单做了一个减法 `heap_end - HEAP`,如果相减的结果大于请求的内存,那么就返回真,否则返回假。 -我们已经看到了所有可以对 heap 进行操作,下面让我们继续显示模式设置过程。 +我们已经看到了所有可以对 HEAP 进行操作,下面让我们继续显示模式设置过程。 设置显示模式 -------------------------------------------------------------------------------- @@ -122,9 +122,9 @@ static inline bool heap_free(size_t n) `store_mode_params` 函数将调用 `store_cursor_position` 函数将当前屏幕上光标的位置保存起来。下面让我们来看 `store_cursor_poistion` 函数是如何实现的。 -首先函数初始化一个类型为 `biosregs` 的变量,将其中的 `AH` 寄存器内容设置成 `0x3`,然后调用 `0x10` bios 中断。当中断调用返回之后,`DL` 和 `DH` 寄存器分别包含了当前按光标的行和列信息。接着,这2个信息将被保存如 `boot_params.screen_info` 字段的 `orig_x` 和 `orig_y`字段。 +首先函数初始化一个类型为 `biosregs` 的变量,将其中的 `AH` 寄存器内容设置成 `0x3`,然后调用 `0x10` BIOS 中断。当中断调用返回之后,`DL` 和 `DH` 寄存器分别包含了当前光标的行和列信息。接着,这2个信息将被保存到 `boot_params.screen_info` 字段的 `orig_x` 和 `orig_y`字段。 -在 `store_cursor_position` 函数执行完毕之后,`store_mode_params` 函数将调用 `store_vide_mode` 函数将当前使用的现实模式保存到 `boot_params.screen_info.orig_video_mode`。 +在 `store_cursor_position` 函数执行完毕之后,`store_mode_params` 函数将调用 `store_vide_mode` 函数将当前使用的显示模式保存到 `boot_params.screen_info.orig_video_mode`。 接下來 `store_mode_params` 函数将根据当前显示模式的设定,给 `video_segment` 变量设置正确的值(实际上就是设置显示内存的起始地址)。在 BIOS 将控制权转移到引导扇区的时候,显示内存地址和显示模式的对应关系如下表所示: @@ -151,7 +151,7 @@ boot_params.screen_info.orig_video_points = font_size; 接下来代码将从地址 `0x44a` 处获得屏幕列信息,从地址 `0x484` 处获得屏幕行信息,并将它们保存到 `boot_params.screen_info.orig_video_cols` 和 `boot_params.screen_info.orig_video_lines`。到这里,`store_mode_params` 的执行就结束了。 -接下来,`set_video` 函数将条用 `save_screen` 函数将当前屏幕上的所有信息保存到 HEAP 中。这个函数首先获得当前屏幕的所有信息(包括屏幕大小,当前光标位置,屏幕上的字符信息),并且保存到 `saved_screen` 结构体中。这个结构体的定义如下所示: +接下来,`set_video` 函数将调用 `save_screen` 函数将当前屏幕上的所有信息保存到 HEAP 中。这个函数首先获得当前屏幕的所有信息(包括屏幕大小,当前光标位置,屏幕上的字符信息),并且保存到 `saved_screen` 结构体中。这个结构体的定义如下所示: ```C static struct saved_screen { @@ -191,7 +191,7 @@ for (card = video_cards; card < video_cards_end; card++) { video_cards_end = .; } ``` -那么这段内存里面存放的数据是什么呢,下面我们就来详细分析。在内核初始化代码中,对于每个支持的现实模式都是使用下面的代码进行定义的: +那么这段内存里面存放的数据是什么呢,下面我们就来详细分析。在内核初始化代码中,对于每个支持的显示模式都是使用下面的代码进行定义的: ```C static __videocard video_vga = { @@ -224,7 +224,7 @@ struct card_info { 在 `.videocards` 内存段实际上存放的就是所有被内核初始化代码定义的 `card_info` 结构(可以看成是一个数组),所以 `probe_cards` 函数可以使用 `video_cards`,通过循环遍历所有的 `card_info`。 -在 `probe_cards` 执行完成之后,我们终于进入 `set_video` 函数的主循环了。在这个循环中,如果 `vid_mode=ask`,那么将显示一个菜单让用户选择想要的显示模式,然后代码将根据用户的选择或者 `vid_mod` ,通过调用 `set_mode` 函数来设置正确的现实模式。如果设置成功,循环结束,否则显示菜单让用户选择显示模式,继续进行设置显示模式的尝试。 +在 `probe_cards` 执行完成之后,我们终于进入 `set_video` 函数的主循环了。在这个循环中,如果 `vid_mode=ask`,那么将显示一个菜单让用户选择想要的显示模式,然后代码将根据用户的选择或者 `vid_mod` 的值 ,通过调用 `set_mode` 函数来设置正确的显示模式。如果设置成功,循环结束,否则显示菜单让用户选择显示模式,继续进行设置显示模式的尝试。 ```c for (;;) { @@ -239,9 +239,9 @@ for (;;) { } ``` -你可以在 [video-mode.c](https://github.com/0xAX/linux/blob/master/arch/x86/boot/video-mode.c#L147) 中找到 `set_mode` 函数的定义。这个函数只接受一个参数,这个参数是对应的现实模式的数字表示(这个数字来自于显示模式选择菜单,或者从内核命令行参数获得)。 +你可以在 [video-mode.c](https://github.com/0xAX/linux/blob/master/arch/x86/boot/video-mode.c#L147) 中找到 `set_mode` 函数的定义。这个函数只接受一个参数,这个参数是对应的显示模式的数字表示(这个数字来自于显示模式选择菜单,或者从内核命令行参数获得)。 -`set_mode` 函数首先检查传入的 `mode` 参数,然后调用 `raw_set_mode` 函数。而后者将遍历内核知道的所有 `card_info` 信息,如果发现某张显卡支持传入的模式,这调用 `card_info` 结构中保存的 `set_mode` 函数地址进行显卡显示模式的设置。已 `video_vga` 这个 `card_info` 结构来说,保存在其中的 `set_mode` 函数就指向了 `vga_set_mode` 函数。下面的代码就是 `vga_set_mode` 函数的实现,这个函数根据输入的 vga 显示模式,调用不同的函数完成显示模式的设置: +`set_mode` 函数首先检查传入的 `mode` 参数,然后调用 `raw_set_mode` 函数。而后者将遍历内核知道的所有 `card_info` 信息,如果发现某张显卡支持传入的模式,这调用 `card_info` 结构中保存的 `set_mode` 函数地址进行显卡显示模式的设置。以 `video_vga` 这个 `card_info` 结构来说,保存在其中的 `set_mode` 函数就指向了 `vga_set_mode` 函数。下面的代码就是 `vga_set_mode` 函数的实现,这个函数根据输入的 vga 显示模式,调用不同的函数完成显示模式的设置: ```C static int vga_set_mode(struct mode_info *mode) @@ -283,16 +283,16 @@ static int vga_set_mode(struct mode_info *mode) 接下来 `set_video` 函数将调用 `vesa_store_edid` 函数, 这个函数只是简单的将 [EDID](https://en.wikipedia.org/wiki/Extended_Display_Identification_Data) (**E**xtended **D**isplay **I**dentification **D**ata) 写入内存,以便于内核访问。最后, `set_video` 将调用 `do_restore` 函数将前面保存的当前屏幕信息还原到屏幕上。 -到这里位置,显示模式的设置完成,接下来我们可以切换到保护模式了。 +到这里为止,显示模式的设置完成,接下来我们可以切换到保护模式了。 在切换到保护模式之前的最后的准备工作 -------------------------------------------------------------------------------- -在进入保护模式之前的最后一个函数调用发生在 [main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c#L184) 中的 `go_to_protected_mode` 函数,就像这个函数的注释说的,这个函数将进行最后的准备工作然后进入保护模式,线面就让我们来具体看看这个最后的准备工作是什么,以及如何切换如保护模式的。 +在进入保护模式之前的最后一个函数调用发生在 [main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c#L184) 中的 `go_to_protected_mode` 函数,就像这个函数的注释说的,这个函数将进行最后的准备工作然后进入保护模式,下面就让我们来具体看看最后的准备工作是什么,以及系统是如何切换如保护模式的。 -`go_to_protected_mode` 函数本身定义在 [arch/x86/boot/pm.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pm.c#L104)。 这个函数调用了一些其他的函数进行最后的准备工作,下面就让我们来看看这些函数是如何工作的。 +`go_to_protected_mode` 函数本身定义在 [arch/x86/boot/pm.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pm.c#L104)。 这个函数调用了一些其他的函数进行最后的准备工作,下面就让我们来具体看看这些函数。 -`go_to_protected_mode` 函数首先调用的 `realmode_switch_hook` 函数,后者如果发现 `realmode_swtch` hook, 那么将调用它并禁止 [NMI](http://en.wikipedia.org/wiki/Non-maskable_interrupt) 中断,反之将直接禁止 NMI 中断。只有当 bootloader 运行在特殊的环境下(比如 DOS )才会被使用。你可以在 [boot protocol](https://www.kernel.org/doc/Documentation/x86/boot.txt) (see **ADVANCED BOOT LOADER HOOKS**) 中详细了解 hook 函数的信息。 +`go_to_protected_mode` 函数首先调用的是 `realmode_switch_hook` 函数,后者如果发现 `realmode_switch` hook, 那么将调用它并禁止 [NMI](http://en.wikipedia.org/wiki/Non-maskable_interrupt) 中断,反之将直接禁止 NMI 中断。只有当 bootloader 运行在宿主环境下(比如在 DOS 下运行 ), hook 才会被使用。你可以在 [boot protocol](https://www.kernel.org/doc/Documentation/x86/boot.txt) (see **ADVANCED BOOT LOADER HOOKS**) 中详细了解 hook 函数的信息。 ```c /* @@ -323,7 +323,7 @@ io_delay(); 上面的代码首先调用 `cli` 汇编指令清楚了中断标志 `IF`,这条指令执行之后,外部中断就被禁止了,紧接着的下一行代码就禁止了 NMI 中断。 -这里简单介绍一下中断。中断是由硬件或者软件产生的,当中断产生的时候, CPU 将得到通知。这个时候, CPU 将停止当前指令的执行,保存当前代码的环境,然后将控制权移交到中断处理程序。当中断处理程序完成之后,将恢复中断之前的运行环境,那么被中断的代码将继续运行。 NMI 中断是一类特殊的中断,往往预示着系统发生了不可恢复的错误,所以在正常运行的操作系统中,NMI 中断是不会被禁止的,但是在进入保护模式之前,由于特殊需求,代码禁止了这类中断。我们将在后续的章节中对中断做更多的介绍,这里就不展开了。 +这里简单介绍一下中断。中断是由硬件或者软件产生的,当中断产生的时候, CPU 将得到通知。这个时候, CPU 将停止当前指令的执行,保存当前代码的环境,然后将控制权移交到中断处理程序。当中断处理程序完成之后,将恢复中断之前的运行环境,从而被中断的代码将继续运行。 NMI 中断是一类特殊的中断,往往预示着系统发生了不可恢复的错误,所以在正常运行的操作系统中,NMI 中断是不会被禁止的,但是在进入保护模式之前,由于特殊需求,代码禁止了这类中断。我们将在后续的章节中对中断做更多的介绍,这里就不展开了。 现在让我们回到上面的代码,在 NMI 中断被禁止之后(通过写 `0x80` 进 CMOS 地址寄存器 `0x70` ),函数接着调用了 `io_delay` 函数进行了短暂的延时以等待 I/O 操作完成。下面就是 `io_delay` 函数的实现: @@ -365,7 +365,7 @@ static int a20_test(int loops) 这个函数首先将 `0x0000` 放入 `FS` 寄存器,将 `0xffff` 放入 `GS` 寄存器。然后通过 `rdfs32` 函数调用,将 `A20_TEST_ADDR` 内存地址的内容放入 `saved` 和 `ctr` 变量。 -接下来我们使用 `wrfs32` 函数将更新过的 `ctr` 的值写入 `fs:gs` ,然后延时 1ms,接着从Next we write an updated `ctr` value into `fs:gs` with the `wrfs32` function, then delay for 1ms, 接着从 `GS:A20_TEST_ADDR+0x10` 读取内容,如果该地址内容不为0,那么 A20 已经被激活。如果 A20 没有被激活,代码将尝试使用多种方法进行 A20 地址激活。其中的一种方法就是调用 BIOS `0X15` 中断激活 A20 地址线。 +接下来我们使用 `wrfs32` 函数将更新过的 `ctr` 的值写入 `fs:gs` ,接着延时 1ms, 然后从 `GS:A20_TEST_ADDR+0x10` 读取内容,如果该地址内容不为0,那么 A20 已经被激活。如果 A20 没有被激活,代码将尝试使用多种方法进行 A20 地址激活。其中的一种方法就是调用 BIOS `0X15` 中断激活 A20 地址线。 如果 `enabled_a20` 函数调用失败,显示一个错误消息并且调用 `die` 函数结束操作系统运行。`die` 函数定义在 [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S): @@ -383,7 +383,7 @@ outb(0, 0xf0); outb(0, 0xf1); ``` -这个函数非常简单,通过将 `0` 写入 I/O 端口 `0xf0` 和 `0xf1` 以清除数字协处理器。 +这个函数非常简单,通过将 `0` 写入 I/O 端口 `0xf0` 和 `0xf1` 以复位数字协处理器。 接下来 `mask_all_interrupts` 函数将被调用: @@ -409,7 +409,7 @@ static void setup_idt(void) } ``` -上面的代码使用 `lidtl` 指令将 `null_idt` 所指向的中断描述符表引入寄存器 IDT。由于 `null_idt` 没有设定中断描述符表的长度( 0 ),所以这段指令执行之后,实际上没有任何中断调用被设置成功(所有中断调用都是空的),在后面的章节中我们将看到正确的设置。`null_idt` 是一个 `gdt_ptr` 机构的数据,这个结构的定义如下所示: +上面的代码使用 `lidtl` 指令将 `null_idt` 所指向的中断描述符表引入寄存器 IDT。由于 `null_idt` 没有设定中断描述符表的长度(长度为 0 ),所以这段指令执行之后,实际上没有任何中断调用被设置成功(所有中断调用都是空的),在后面的章节中我们将看到正确的设置。`null_idt` 是一个 `gdt_ptr` 机构的数据,这个结构的定义如下所示: ```C struct gdt_ptr { @@ -467,9 +467,9 @@ Not aligned - 4 Aligned - 16 ``` -因为我们 `boot_gdt` 的定义中, `GDT_ENTRY_BOOT_CS = 2`,所以在数组中有2个空项,第一项是一个空的描述符,第二项在代码中没有使用。在没有 `align 16` 之前,整个结构占用了(8*5=40)个字节,加了 `align 16` 之后,结构就占用了 48 字节 。 +因为在 `boot_gdt` 的定义中, `GDT_ENTRY_BOOT_CS = 2`,所以在数组中有2个空项,第一项是一个空的描述符,第二项在代码中没有使用。在没有 `align 16` 之前,整个结构占用了(8*5=40)个字节,加了 `align 16` 之后,结构就占用了 48 字节 。 -上面代码中出现的 `GDT_ENTRY` 是一个宏定义,这个宏接受 3 个参数(标志,基地址,段长度)来产生段描述符结构。让我们来具体分析上面数组中的代码段描述符来看看这个宏是如何工作的,对于这个段,`GDT_ENTRY` 接受了下面 3 个参数: +上面代码中出现的 `GDT_ENTRY` 是一个宏定义,这个宏接受 3 个参数(标志,基地址,段长度)来产生段描述符结构。让我们来具体分析上面数组中的代码段描述符( `GDT_ENTRY_BOOT_CS` )来看看这个宏是如何工作的,对于这个段,`GDT_ENTRY` 接受了下面 3 个参数: * 基地址 - 0 * 段长度 - 0xfffff @@ -487,14 +487,14 @@ Aligned - 16 * 1 - (D) 表示这个段是一个32位段 * 0 - (L) 这个代码段没有运行在 long mode * 0 - (AVL) Linux 没有使用 -* 0000 - 4 bit length 19:16 bits in the descriptor +* 0000 - 段长度的4个位 * 1 - (P) 段已经位于内存中 * 00 - (DPL) - 段优先级为0 * 1 - (S) 说明这个段是一个代码或者数据段 * 101 - 段类型为可执行/可读 * 1 - 段可访问 -关于段描述符的更详细的信息你可以从上一章中获得 [post](linux-bootstrap-2.md),你也可以阅读 [Intel® 64 and IA-32 Architectures Software Developer's Manuals 3A](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html)获取全部信息。 +关于段描述符的更详细的信息你可以从上一章中获得 [上一章](linux-bootstrap-2.md),你也可以阅读 [Intel® 64 and IA-32 Architectures Software Developer's Manuals 3A](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html)获取全部信息。 在定义了数组之后,代码将获取 GDT 的长度: @@ -541,7 +541,7 @@ movw $__BOOT_TSS, %di 就像前面我们看到的 `GDT_ENTRY_BOOT_CS` 的值为2,每个段描述符都是 8 字节,所以 `cx` 寄存器的值将是 `2*8 = 16`,`di` 寄存器的值将是 `4*8 =32`。 -接下来,我们通过设置 `CR0` 寄存器相应的为使 CPU 进入保护模式: +接下来,我们通过设置 `CR0` 寄存器相应的位使 CPU 进入保护模式: ```assembly movl %cr0, %edx @@ -591,7 +591,7 @@ xorl %ebp, %ebp xorl %edi, %edi ``` -最后使用长跳转跳入正在的 32 为代码(通过参数传入的地址) +最后使用长跳转跳入正在的 32 位代码(通过参数传入的地址) ``` jmpl *%eax ;?jmpl cs:eax? From 20bba49f08527fc0ee5a82a7595abcc1f1a4d1ba Mon Sep 17 00:00:00 2001 From: hailin Date: Tue, 15 Mar 2016 16:17:11 -0400 Subject: [PATCH 53/90] fix some issues --- Booting/linux-bootstrap-3.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Booting/linux-bootstrap-3.md diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md old mode 100644 new mode 100755 From 6293c658cf777fe123aef36972e6ac29f3bef6f7 Mon Sep 17 00:00:00 2001 From: hailin Date: Tue, 15 Mar 2016 16:17:45 -0400 Subject: [PATCH 54/90] remove x --- Booting/linux-bootstrap-3.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 Booting/linux-bootstrap-3.md diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md old mode 100755 new mode 100644 From 7fd25c0bb7158709b5f307312d5d8896253b36c8 Mon Sep 17 00:00:00 2001 From: zmj1316 Date: Wed, 16 Mar 2016 17:00:20 +0800 Subject: [PATCH 55/90] pollishing --- Booting/linux-bootstrap-4.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Booting/linux-bootstrap-4.md b/Booting/linux-bootstrap-4.md index 408485b..f827b65 100644 --- a/Booting/linux-bootstrap-4.md +++ b/Booting/linux-bootstrap-4.md @@ -4,11 +4,11 @@ 切换到64位模式 -------------------------------------------------------------------------------- -这是 `内核引导过程` 的第四部分,我们将会看到在[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)中的最初几步,比如确认CPU支持[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F),[SSE](https://zh.wikipedia.org/wiki/SSE)和[分页](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)以及页表的初始化,在这部分的最后我们还将讨论如何切换到[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)。 +这是 `内核引导过程` 的第四部分,我们将会看到在[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)中的最初几步,比如确认CPU是否支持[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F),[SSE](https://zh.wikipedia.org/wiki/SSE)和[分页](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)以及页表的初始化,在这部分的最后我们还将讨论如何切换到[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)。 **注意:这部分将会有大量的汇编代码,如果你不熟悉汇编,建议你找本书参考一下。** -在[上一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md),我们停在了跳转到位于 [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S) 的 32 位入口点这一步: +在[前一章节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md),我们停在了跳转到位于 [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S) 的 32 位入口点这一步: ```assembly jmpl *%eax @@ -21,7 +21,7 @@ When using bzImage, the protected-mode kernel was relocated to 0x100000 ``` ``` -当使用 bzImage,保护模式下的内核被重定位至 0x100000 +当使用 bzImage 时,保护模式下的内核被重定位至 0x100000 ``` @@ -46,7 +46,7 @@ fs 0x18 24 gs 0x18 24 ``` -我们在这里可以看到 `cs` 寄存器包含了 - `0x10` (回忆前一章节,这代表了全局描述符表中的第二个索引项), `eip` 寄存器的值是 `0x100000`,并且包括代码段在内的所有内存段的基地址都为0。所以我们可以得到物理地址: `0:0x100000` 或者 `0x100000`,和协议规定的一样。现在让我们从 32 位入口点开始。 +我们在这里可以看到 `cs` 寄存器包含了 - `0x10` (回忆前一章节,这代表了全局描述符表中的第二个索引项), `eip` 寄存器的值是 `0x100000`,并且包括代码段在内的所有内存段的基地址都为0。所以我们可以得到物理地址: `0:0x100000` 或者 `0x100000`,这和协议规定的一样。现在让我们从 32 位入口点开始。 32 位入口点 -------------------------------------------------------------------------------- @@ -63,7 +63,7 @@ ENTRY(startup_32) ENDPROC(startup_32) ``` -首先,为什么目录名叫做 `被压缩的 (compressed)` ?实际上 `bzimage` 是由 `vmlinux + 头文件 + 内核启动代码` 被 gzip 压缩之后获得的。我们在前几个章节已经看到了内核启动的代码。所以, `head_64.S` 的主要目的就是为了做好进入长模式的准备然后进入长模式,进入以后再解压内核。在这一节,我们将会看到直到内核解压缩之前的所有步骤。 +首先,为什么目录名叫做 `被压缩的 (compressed)` ?实际上 `bzimage` 是由 `vmlinux + 头文件 + 内核启动代码` 被 gzip 压缩之后获得的。我们在前几个章节已经看到了启动内核的代码。所以, `head_64.S` 的主要目的就是做好进入长模式的准备之后进入长模式,进入以后再解压内核。在这一章节,我们将会看到直到内核解压缩之前的所有步骤。 在 `arch/x86/boot/compressed` 目录下有两个文件: @@ -78,7 +78,7 @@ vmlinux-objs-y := $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o \ $(obj)/piggy.o $(obj)/cpuflags.o ``` -注意 `$(obj)/head_$(BITS).o` 。这意味着我们将会选择基于 `$(BITS)` 所设置的文件执行链接操作,head_32.o 或者 head_64.o。`$(BITS)` 在 [arch/x86/kernel/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/Makefile) 之中被 .config 文件另外定义: +注意 `$(obj)/head_$(BITS).o` 。这意味着我们将会选择基于 `$(BITS)` 所设置的文件执行链接操作,即 head_32.o 或者 head_64.o。`$(BITS)` 在 [arch/x86/kernel/Makefile](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/Makefile) 之中根据 .config 文件另外定义: ```Makefile ifeq ($(CONFIG_X86_32),y) @@ -94,7 +94,7 @@ endif 现在我们知道从哪里开始了,那就来吧。 -必要时重载内存段寄存器 +必要时重新加载内存段寄存器 -------------------------------------------------------------------------------- 正如上面阐述的,我们先从 [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) 这个汇编文件开始。首先我们看到了在 `startup_32` 之前的特殊段属性定义: @@ -105,13 +105,13 @@ endif ENTRY(startup_32) ``` -这个 `__HEAD` 是一个定义在头文件 [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h)中的宏,展开后就是下面这个段的定义: +这个 `__HEAD` 是一个定义在头文件 [include/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h) 中的宏,展开后就是下面这个段的定义: ```C #define __HEAD .section ".head.text","ax" ``` -拥有 `.head.text` 的命名和 `ax` 标记。在这里,这些标记告诉我们这个段是[可执行的](https://en.wikipedia.org/wiki/Executable)或者换种说法,包含了代码。我们可以在 [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/vmlinux.lds.S) 这个链接脚本里找到这个段的定义: +其拥有 `.head.text` 的命名和 `ax` 标记。在这里,这些标记告诉我们这个段是[可执行的](https://en.wikipedia.org/wiki/Executable)或者换种说法,包含了代码。我们可以在 [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/vmlinux.lds.S) 这个链接脚本里找到这个段的定义: ``` SECTIONS @@ -124,7 +124,7 @@ SECTIONS } ``` -如果你不熟悉 `GNU LD` 这个链接脚本语言的语法,你可以在[这个文档](https://sourceware.org/binutils/docs/ld/Scripts.html#Scripts)中找到更多信息。简单来说,这个 `.` 符号是一个链接器的特殊变量 - 位置计数器。其被赋值为相对于该段的偏移。在这里,我们将位置计数器赋值为0,这意味着我们的代码被链接到内存的 `0` 偏移处。此外,我们可以从注释找到更多信息: +如果你不熟悉 `GNU LD` 这个链接脚本语言的语法,你可以在[这个文档](https://sourceware.org/binutils/docs/ld/Scripts.html#Scripts)中找到更多信息。简单来说,这个 `.` 符号是一个链接器的特殊变量 - 位置计数器。其被赋值为相对于该段的偏移。在这里,我们将位置计数器赋值为0,这意味着我们的代码被链接到内存的 `0` 偏移处。此外,我们可以从注释里找到更多信息: ``` Be careful parts of head_64.S assume startup_32 is at address 0. @@ -169,9 +169,9 @@ Bit 6 (write): KEEP_SEGMENTS movl %eax, %ss ``` -记住 `__BOOT_DS` 是 `0x18` (位于[全局描述符表](https://en.wikipedia.org/wiki/Global_Descriptor_Table)中数据段的索引)。如果设置了 `KEEP_SEGMENTS` ,我们就跳转到最近的 `1f` 标签,或者当没有 `1f` 标签,则用 `__BOOT_DS` 更新段寄存器。这非常简单,但是这是一个有趣的操作。如果你已经读了[前一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md),你或许还记得我们在 [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S) 中切换到[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)的时候已经更新了这些段寄存器。那么为什么我们还要去关心这些段寄存器的值呢。答案很简单,Linux 内核也有32位的引导协议,如果一个引导程序之前使用32位协议引导内核,那么在 `startup_32` 之前的代码就会被忽略。在这种情况下 `startup_32` 将会变成引导程序之后的第一个入口点,不保证段寄存器会不会处于未知状态。 +记住 `__BOOT_DS` 是 `0x18` (位于[全局描述符表](https://en.wikipedia.org/wiki/Global_Descriptor_Table)中数据段的索引)。如果设置了 `KEEP_SEGMENTS` ,我们就跳转到最近的 `1f` 标签,或者当没有 `1f` 标签,则用 `__BOOT_DS` 更新段寄存器。这非常简单,但是这是一个有趣的操作。如果你已经读了[前一章节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md),你或许还记得我们在 [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S) 中切换到[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)的时候已经更新了这些段寄存器。那么为什么我们还要去关心这些段寄存器的值呢?答案很简单,Linux 内核也有32位的引导协议,如果一个引导程序之前使用32位协议引导内核,那么在 `startup_32` 之前的代码就会被忽略。在这种情况下 `startup_32` 将会变成引导程序之后的第一个入口点,不保证段寄存器会不会处于未知状态。 -在我们检查了 `KEEP_SEGMENTS` 标记并且给段寄存器设置了正确的值之后,下一步就是计算我们代码的加载和编译运行之间的位置偏差了。记住 `setup.ld.S` 包含了以下定义:在 `.head.text` 段的开始 `. = 0` 。这意味着这一段代码被编译成从 `0` 地址运行。我们可以在 `objdump` 输出中看到: +在我们检查了 `KEEP_SEGMENTS` 标记并且给段寄存器设置了正确的值之后,下一步就是计算我们代码的加载和编译运行之间的位置偏差了。记住 `setup.ld.S` 包含了以下定义:在 `.head.text` 段的开始 `. = 0` 。这意味着这一段代码被编译成从 `0` 地址运行。我们可以在 `objdump` 工具的输出中看到: ``` arch/x86/boot/compressed/vmlinux: file format elf64-x86-64 @@ -184,14 +184,14 @@ Disassembly of section .head.text: 1: f6 86 11 02 00 00 40 testb $0x40,0x211(%rsi) ``` - `objdump` 告诉我们 `startup_32` 的地址是 `0` 。但是实际上并不是。我们当前的目标是获知我们实际上在哪里。在[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)下,这非常简单,因为其支持 `rip` 相对寻址,但是我们当前处于[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)下。我们将会使用一个常用的方法来确定 `startup_32` 的地址。我们需要定义一个标签并且跳转到它,然后把栈顶抛出到一个寄存器中: + `objdump` 工具告诉我们 `startup_32` 的地址是 `0` 。但实际上并不是。我们当前的目标是获知我们实际上在哪里。在[长模式](https://zh.wikipedia.org/wiki/%E9%95%BF%E6%A8%A1%E5%BC%8F)下,这非常简单,因为其支持 `rip` 相对寻址,但是我们当前处于[保护模式](https://zh.wikipedia.org/wiki/%E4%BF%9D%E8%AD%B7%E6%A8%A1%E5%BC%8F)下。我们将会使用一个常用的方法来确定 `startup_32` 的地址。我们需要定义一个标签并且跳转到它,然后把栈顶抛出到一个寄存器中: ```assembly call label label: pop %reg ``` -在这之后,那个寄存器将会包含标签的地址,让我们看看在 Linux 内核中相似的寻找 `startup_32` 地址的代码: +在这之后,那个寄存器将会包含标签的地址,让我们看看在 Linux 内核中类似的寻找 `startup_32` 地址的代码: ```assembly leal (BP_scratch+4)(%esi), %esp @@ -200,7 +200,7 @@ label: pop %reg subl $1b, %ebp ``` -回忆前一节, `esi` 寄存器包含了 [boot_params](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/bootparam.h#L113) 结构的地址,这个结构在我们切换到保护模式之前已经被填充了。`bootparams` 这个结构体包含了一个特殊的成员 `scratch` ,其偏移量为 `0x1e4` 。这个 4 字节的区域将会成为 `call` 指令的临时栈。我们把 `scratch` 的地址加 4 存入 `esp` 寄存器。我们之所以在 `BP_scratch` 基础上加 `4` 是因为,如之前所说的,这将成为一个临时的栈,而在 `x86_64` 架构下,栈是自顶向下生长的。所以我们的栈指针就会指向栈顶。接下来我们就可以看到我上面描述的过程。我们跳转到 `1f` 标签并且把该标签的地址放入 `ebp` 寄存器,因为在执行 `call` 指令之后我们把返回地址放到了栈顶。那么,目前我们拥有 `1f` 标签的地址,也能够很容易得到 `startup_32` 的地址。我们只需要把我们从栈里得到的地址减去标签的地址: +回忆前一节, `esi` 寄存器包含了 [boot_params](https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/bootparam.h#L113) 结构的地址,这个结构在我们切换到保护模式之前已经被填充了。`bootparams` 这个结构体包含了一个特殊的字段 `scratch` ,其偏移量为 `0x1e4` 。这个 4 字节的区域将会成为 `call` 指令的临时栈。我们把 `scratch` 的地址加 4 存入 `esp` 寄存器。我们之所以在 `BP_scratch` 基础上加 `4` 是因为,如之前所说的,这将成为一个临时的栈,而在 `x86_64` 架构下,栈是自顶向下生长的。所以我们的栈指针就会指向栈顶。接下来我们就可以看到我上面描述的过程。我们跳转到 `1f` 标签并且把该标签的地址放入 `ebp` 寄存器,因为在执行 `call` 指令之后我们把返回地址放到了栈顶。那么,目前我们拥有 `1f` 标签的地址,也能够很容易得到 `startup_32` 的地址。我们只需要把我们从栈里得到的地址减去标签的地址: ``` startup_32 (0x0) +-----------------------+ @@ -212,7 +212,7 @@ startup_32 (0x0) +-----------------------+ | | | | | | -1f (0x0 + 1f offset) +-----------------------+ %ebp - real physical address +1f (0x0 + 1f offset) +-----------------------+ %ebp - 实际物理地址 | | | | +-----------------------+ @@ -264,7 +264,7 @@ ebp 0x100000 0x100000 栈的建立和 CPU 的确认 -------------------------------------------------------------------------------- -如果不知道 `startup_32` 标签的地址,我们无法建立栈。我们可以把栈看作是一个数组,并且栈指针寄存器 `esp` 必须指向数组的底部。当然我们可以在自己的代码里定义一个数组,但是我们需要知道其真实地址来正确配置栈指针。让我们看一下代码: +如果不知道 `startup_32` 标签的地址,我们就无法建立栈。我们可以把栈看作是一个数组,并且栈指针寄存器 `esp` 必须指向数组的底部。当然我们可以在自己的代码里定义一个数组,但是我们需要知道其真实地址来正确配置栈指针。让我们看一下代码: ```assembly movl $boot_stack_end, %eax From 0c0669ece990b4f19bbe1e55c12bbe121c353c2b Mon Sep 17 00:00:00 2001 From: zmj1316 Date: Wed, 16 Mar 2016 17:40:27 +0800 Subject: [PATCH 56/90] polish --- Booting/linux-bootstrap-4.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Booting/linux-bootstrap-4.md b/Booting/linux-bootstrap-4.md index f827b65..695c8d6 100644 --- a/Booting/linux-bootstrap-4.md +++ b/Booting/linux-bootstrap-4.md @@ -296,7 +296,7 @@ boot_stack_end: 这个函数定义在 [arch/x86/kernel/verify_cpu.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/verify_cpu.S) 中,只是包含了几个对 [cpuid](https://en.wikipedia.org/wiki/CPUID) 指令的调用。该指令用于获取处理器的信息。在我们的情况下,它检查了对 `长模式` 和 `SSE` 的支持,通过 `eax` 寄存器返回0表示成功,1表示失败。 -如果 `eax` 的值不是 0 ,我们跳转到 `no_longmode` 标签,用 `hlt` 指令停止 CPU ,期间不会发生硬件中断: +如果 `eax` 的值不是 0 ,我们就跳转到 `no_longmode` 标签,用 `hlt` 指令停止 CPU ,期间不会发生硬件中断: ```assembly no_longmode: @@ -351,7 +351,7 @@ KBUILD_CFLAGS += -fno-strict-aliasing -fPIC addl $z_extract_offset, %ebx ``` -记住 `ebp` 寄存器的值就是 `startup_32` 标签的物理地址。如果在内核配置中 `CONFIG_RELOCATABLE` 内核配置项开启,我们就把这个地址放到 `ebx` 寄存器中,对齐到 `2M` ,然后和 `LOAD_PHYSICAL_ADDR` 的值比较。 `LOAD_PHYSICAL_ADDR` 宏在头文件 [arch/x86/include/asm/boot.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/boot.h) 定义,如下: +记住 `ebp` 寄存器的值就是 `startup_32` 标签的物理地址。如果在内核配置中 `CONFIG_RELOCATABLE` 内核配置项开启,我们就把这个地址放到 `ebx` 寄存器中,对齐到 `2M` ,然后和 `LOAD_PHYSICAL_ADDR` 的值比较。 `LOAD_PHYSICAL_ADDR` 宏定义在头文件 [arch/x86/include/asm/boot.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/boot.h) 中,如下: ```C #define LOAD_PHYSICAL_ADDR ((CONFIG_PHYSICAL_START \ @@ -374,7 +374,7 @@ KBUILD_CFLAGS += -fno-strict-aliasing -fPIC lgdt gdt(%ebp) ``` -在这里我们把 `ebp` 寄存器加上 `gdt` 偏移存到 `eax` 寄存器。接下来我们把这个地址放到 `ebp` 加上 `gdt+2` 偏移的位置上,并且用 `lgdt` 指令载入 `全局描述符表` 。为了理解这个神奇的 `gdt` 偏移量,我们需要关注 `全局描述符表` 的定义。我们可以在同一个[源文件](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S)中找到其定义: +在这里我们把 `ebp` 寄存器加上 `gdt` 的偏移存到 `eax` 寄存器。接下来我们把这个地址放到 `ebp` 加上 `gdt+2` 偏移的位置上,并且用 `lgdt` 指令载入 `全局描述符表` 。为了理解这个神奇的 `gdt` 偏移量,我们需要关注 `全局描述符表` 的定义。我们可以在同一个[源文件](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S)中找到其定义: ```assembly .data @@ -390,7 +390,7 @@ gdt: gdt_end: ``` -我们可以看到其位于 `.data` 段,并且包含了5个描述符: `null` 、内核代码段、内核数据段和其他两个任务描述符。我们已经在[上一节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md)载入了 `全局描述符表` ,和我们现在做的差不多,但是描述符改为 `CS.L = 1` `CS.D = 0` 从而在 `64` 位模式下执行。我们可以看到, `gdt` 的定义从两个字节开始: `gdt_end - gdt` ,代表了 `gdt` 表的最后一个字节,或者说表的范围。接下来的4个字节包含了 `gdt` 的基地址。记住 `全局描述符表` 保存在 `48位 GDTR-全局描述符表寄存器` 中,由两个部分组成: +我们可以看到其位于 `.data` 段,并且包含了5个描述符: `null` 、内核代码段、内核数据段和其他两个任务描述符。我们已经在[上一章节](https://github.com/MintCN/linux-insides-zh/blob/master/Booting/linux-bootstrap-3.md)载入了 `全局描述符表` ,和我们现在做的差不多,但是将描述符改为 `CS.L = 1` `CS.D = 0` 从而在 `64` 位模式下执行。我们可以看到, `gdt` 的定义从两个字节开始: `gdt_end - gdt` ,代表了 `gdt` 表的最后一个字节,或者说表的范围。接下来的4个字节包含了 `gdt` 的基地址。记住 `全局描述符表` 保存在 `48位 GDTR-全局描述符表寄存器` 中,由两个部分组成: * 全局描述符表的大小 (16位) * 全局描述符表的基址 (32位) @@ -433,7 +433,7 @@ gdt_end: * 启用分页; -我们已经通过设置 `cr4` 控制寄存器中的 `PAE` 位启动 `PAE` 了。在下一个段落,我们就要建立[分页](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)的结构了。 +我们已经通过设置 `cr4` 控制寄存器中的 `PAE` 位启动 `PAE` 了。在下一个段落,我们就要建立[页表](https://zh.wikipedia.org/wiki/%E5%88%86%E9%A0%81)的结构了。 初期页表初始化 -------------------------------------------------------------------------------- From 410b2e17bf6012ed4a0e565a9cd30563a025a763 Mon Sep 17 00:00:00 2001 From: zmj1316 Date: Wed, 16 Mar 2016 17:44:15 +0800 Subject: [PATCH 57/90] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=86=B2=E7=AA=81?= 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 ab19786..485e9f3 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Linux Insides |├1.1|[@hailincai](https://github.com/hailincai)|已完成| |├1.2|[@hailincai](https://github.com/hailincai)|已完成| |├1.3|[@hailincai](https://github.com/hailincai)|正在进行| -|├1.4||未开始| +|├1.4|[@zmj1316](https://github.com/zmj1316)|正在进行| |├1.5|[@chengong](https://github.com/chengong)|正在进行| |Initialization|[@lijiangsheng1](https://github.com/lijiangsheng1)|正在进行| |Interrupts||正在进行| From 82dd248a27fefa7e268da35931675e3fb03c4426 Mon Sep 17 00:00:00 2001 From: zmj1316 Date: Wed, 16 Mar 2016 17:56:01 +0800 Subject: [PATCH 58/90] =?UTF-8?q?=E9=A1=B5=E8=A1=A8=E9=83=A8=E5=88=86?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Booting/linux-bootstrap-4.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Booting/linux-bootstrap-4.md b/Booting/linux-bootstrap-4.md index 695c8d6..10f7ced 100644 --- a/Booting/linux-bootstrap-4.md +++ b/Booting/linux-bootstrap-4.md @@ -506,14 +506,14 @@ pgtable: jnz 1b ``` -在这里我们做的几乎和上面一样,所有的表项都带着标记 - `$0x00000183` - `PRESENT + WRITE + MBZ` 。最后我们将会拥有 `2048` 个 `2MB` 页的页表,或者说: +在这里我们做的几乎和上面一样,所有的表项都带着标记 - `$0x00000183` - `PRESENT + WRITE + MBZ` 。最后我们将会拥有 `2048` 个 `2MB` 大的页,或者说: ```python >>> 2048 * 0x00200000 4294967296 ``` - `4G` 页表。我们刚刚完成我们的初期页表结构,其映射了 `4G` 大小的内存,现在我们可以把高级页表 `PML4` 的地址放到 `cr3` 寄存器中了: +一个 `4G` 页表。我们刚刚完成我们的初期页表结构,其映射了 `4G` 大小的内存,现在我们可以把高级页表 `PML4` 的地址放到 `cr3` 寄存器中了: ```assembly leal pgtable(%ebx), %eax From f9fc0e0a5d6f600ac43fed40b428e7bec9b11243 Mon Sep 17 00:00:00 2001 From: The Gitter Badger Date: Fri, 18 Mar 2016 18:25:54 +0000 Subject: [PATCH 59/90] Add Gitter badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 485e9f3..7748388 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ Linux Insides =============== +[![Join the chat at https://gitter.im/MintCN/linux-insides-zh](https://badges.gitter.im/MintCN/linux-insides-zh.svg)](https://gitter.im/MintCN/linux-insides-zh?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + 一系列关于Linux内核和其内在机理的帖子。 **目的很简单** - 分享我对Linux内核内在机理的一点知识,帮助对linux内核内在机理感兴趣的人,和其他低级话题。 From 68404401e86799e416846b60528994ce16d24b3e Mon Sep 17 00:00:00 2001 From: hailin cai Date: Thu, 24 Mar 2016 15:47:07 -0400 Subject: [PATCH 60/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index 57536d9..9f334f8 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -38,7 +38,7 @@ vga= line is parsed. ``` -根据上面的描述,我们可以通过将 `vga` 选项写入 grub 或者起到引导程序的配置文件,从而让内核命令行得到相应的显示模式设置信息。这个选项可以接受不同类型的值来表示相同的意思。比如你可以传入 `0XFFFD` 或者 `ask`,这2个值都表示需要显示一个菜单让用户选择想要的显示模式。下面的链接就给出了这个菜单: +根据上面的描述,我们可以通过将 `vga` 选项写入 grub 或者起到引导程序的配置文件,从而让内核命令行得到相应的显示模式设置信息。这个选项可以接受不同类型的值来表示相同的意思。比如你可以传入 0XFFFD 或者 ask,这2个值都表示需要显示一个菜单让用户选择想要的显示模式。下面的链接就给出了这个菜单: ![video mode setup menu](http://oi59.tinypic.com/ejcz81.jpg) @@ -581,7 +581,7 @@ movl %ecx, %gs movl %ecx, %ss ``` -还记得我们在实模式代码中将 `$__BOOT_DS` (数据段描述符索引)放入了 `cx` 寄存器。,所以上面的代码甚至所有段寄存器(除了 `CS` 寄存器)指向数据段。接下来代码将所有的通用寄存器清 0 : +还记得我们在实模式代码中将 `$__BOOT_DS` (数据段描述符索引)放入了 `cx` 寄存器。,所以上面的代码设置所有段寄存器(除了 `CS` 寄存器)指向数据段。接下来代码将所有的通用寄存器清 0 : ```assembly xorl %ecx, %ecx From 58c56d5a5294e312c9d2a6a4f7fff27bdd3e1e74 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Thu, 24 Mar 2016 15:47:40 -0400 Subject: [PATCH 61/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index 9f334f8..248a1ba 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -570,7 +570,7 @@ movl %edx, %cr0 .section ".text32","ax" ``` -保护模式代码的第一步就是重置所有的段寄存器: +保护模式代码的第一步就是重置所有的段寄存器(除了 `CS` 寄存器): ```assembly GLOBAL(in_pm32) From 20df3f425305d42c58fa625ff2507b778e2300b4 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Thu, 24 Mar 2016 15:48:57 -0400 Subject: [PATCH 62/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index 248a1ba..7e9f316 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -1,4 +1,4 @@ -内核启动过程,第三部分 + 内核启动过程,第三部分 ================================================================================ 显示模式初始化和进入保护模式 From f67ede336c826a29416db9c058279ef70c27cf59 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Thu, 24 Mar 2016 15:49:00 -0400 Subject: [PATCH 63/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index 7e9f316..248a1ba 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -1,4 +1,4 @@ - 内核启动过程,第三部分 +内核启动过程,第三部分 ================================================================================ 显示模式初始化和进入保护模式 From ad8ad343e3aebfffb47850cf192c1e745cd787c5 Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 24 Mar 2016 15:49:44 -0400 Subject: [PATCH 64/90] update chap 1_3 --- Booting/linux-bootstrap-3.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Booting/linux-bootstrap-3.md diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md old mode 100644 new mode 100755 From 95524ed469144cd38ab356052cf71543bef326cc Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 24 Mar 2016 15:50:15 -0400 Subject: [PATCH 65/90] remove x --- Booting/linux-bootstrap-3.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 Booting/linux-bootstrap-3.md diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md old mode 100755 new mode 100644 From beb7b278b57671d8512b1ce224199218248e51d8 Mon Sep 17 00:00:00 2001 From: hailin cai Date: Thu, 24 Mar 2016 15:51:08 -0400 Subject: [PATCH 66/90] Update Booting/linux-bootstrap-3.md --- Booting/linux-bootstrap-3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index 248a1ba..e71aab2 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -581,7 +581,7 @@ movl %ecx, %gs movl %ecx, %ss ``` -还记得我们在实模式代码中将 `$__BOOT_DS` (数据段描述符索引)放入了 `cx` 寄存器。,所以上面的代码设置所有段寄存器(除了 `CS` 寄存器)指向数据段。接下来代码将所有的通用寄存器清 0 : +还记得我们在实模式代码中将 `$__BOOT_DS` (数据段描述符索引)放入了 `cx` 寄存器,所以上面的代码设置所有段寄存器(除了 `CS` 寄存器)指向数据段。接下来代码将所有的通用寄存器清 0 : ```assembly xorl %ecx, %ecx From 4636085f316f9f36d221ad95f5cd08eaeff4adf0 Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 24 Mar 2016 15:51:33 -0400 Subject: [PATCH 67/90] fix one small thing --- Booting/linux-bootstrap-3.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Booting/linux-bootstrap-3.md diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md old mode 100644 new mode 100755 From 9618b23ba6655774eec95d1f6722d8b8d0ed2963 Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 24 Mar 2016 15:51:46 -0400 Subject: [PATCH 68/90] remove x --- Booting/linux-bootstrap-3.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 Booting/linux-bootstrap-3.md diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md old mode 100755 new mode 100644 From ccce4ad411902352f12b6b0f7c603f2c75330eaf Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sat, 16 Apr 2016 20:58:15 -0400 Subject: [PATCH 69/90] Add the README.md document translation status --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 7748388..240db99 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Linux Insides | 章节|译者|翻译进度| | ------------- |:-------------:| -----:| |Booting||正在进行| +|├1.0|[@xinqiu](https://github.com/xinqiu)|已完成| |├1.1|[@hailincai](https://github.com/hailincai)|已完成| |├1.2|[@hailincai](https://github.com/hailincai)|已完成| |├1.3|[@hailincai](https://github.com/hailincai)|正在进行| @@ -22,6 +23,7 @@ Linux Insides |├1.5|[@chengong](https://github.com/chengong)|正在进行| |Initialization|[@lijiangsheng1](https://github.com/lijiangsheng1)|正在进行| |Interrupts||正在进行| +|├3.0|[@littleneko](https://github.com/littleneko)|正在进行| |├3.1|[@littleneko](https://github.com/littleneko)|正在进行| |├3.2|[@cloudusers](https://github.com/cloudusers)|正在进行| |├3.3|[@cloudusers](https://github.com/cloudusers)|正在进行| @@ -37,12 +39,15 @@ Linux Insides |Memory management|[@choleraehyq](https://github.com/choleraehyq)|正在进行| |Concepts||未开始| |DataStructures||正在进行| +|├9.0|[@mudongliang](https://github.com/mudongliang)|已完成| |├9.1|[@oska874](http://github.com/oska874) [@mudongliang](https://github.com/mudongliang)|已完成| |└9.2|[@Alick Guo](https://github.com/a1ickgu0)|正在进行| |Theory||正在进行| +|├10.0|[@mudongliang](https://github.com/mudongliang)|已完成| |├10.1|[@mudongliang](https://github.com/mudongliang)|已完成| |└10.2|[@mudongliang](https://github.com/mudongliang)|已完成| |Misc||正在进行| +|├12.0||未开始| |├12.1|[@oska874](https://github.com/oska874)|已完成| |├12.2||未开始| |└12.3||未开始| From 3ce921eebe2fd72ca5f028eb25fb8defcdbc5a07 Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sat, 16 Apr 2016 21:12:18 -0400 Subject: [PATCH 70/90] Translate README.md and change the translation status --- Misc/README.md | 3 +++ README.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 Misc/README.md diff --git a/Misc/README.md b/Misc/README.md new file mode 100644 index 0000000..3c7ff69 --- /dev/null +++ b/Misc/README.md @@ -0,0 +1,3 @@ +# 杂项 + +这个章节包含不直接涉及到内核源码的部分以及各个子系统的实现。 diff --git a/README.md b/README.md index 240db99..a34a299 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Linux Insides |├10.1|[@mudongliang](https://github.com/mudongliang)|已完成| |└10.2|[@mudongliang](https://github.com/mudongliang)|已完成| |Misc||正在进行| -|├12.0||未开始| +|├12.0|[@mudongliang](https://github.com/mudongliang)|已完成| |├12.1|[@oska874](https://github.com/oska874)|已完成| |├12.2||未开始| |└12.3||未开始| From c25456a234ff3ceb755f020206aca4c762152209 Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sat, 16 Apr 2016 21:23:45 -0400 Subject: [PATCH 71/90] Change the translation status for Theory Chapter --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a34a299..a8cb984 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Linux Insides |├9.0|[@mudongliang](https://github.com/mudongliang)|已完成| |├9.1|[@oska874](http://github.com/oska874) [@mudongliang](https://github.com/mudongliang)|已完成| |└9.2|[@Alick Guo](https://github.com/a1ickgu0)|正在进行| -|Theory||正在进行| +|Theory||已完成| |├10.0|[@mudongliang](https://github.com/mudongliang)|已完成| |├10.1|[@mudongliang](https://github.com/mudongliang)|已完成| |└10.2|[@mudongliang](https://github.com/mudongliang)|已完成| From 9abaac4e998df0eac05df1eab6345bba77935b98 Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sat, 16 Apr 2016 21:45:19 -0400 Subject: [PATCH 72/90] Update README from upstream --- README.md | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index a8cb984..90494cc 100644 --- a/README.md +++ b/README.md @@ -14,15 +14,15 @@ Linux Insides | 章节|译者|翻译进度| | ------------- |:-------------:| -----:| -|Booting||正在进行| +|1. Booting||正在进行| |├1.0|[@xinqiu](https://github.com/xinqiu)|已完成| |├1.1|[@hailincai](https://github.com/hailincai)|已完成| |├1.2|[@hailincai](https://github.com/hailincai)|已完成| |├1.3|[@hailincai](https://github.com/hailincai)|正在进行| |├1.4|[@zmj1316](https://github.com/zmj1316)|正在进行| |├1.5|[@chengong](https://github.com/chengong)|正在进行| -|Initialization|[@lijiangsheng1](https://github.com/lijiangsheng1)|正在进行| -|Interrupts||正在进行| +|2. Initialization|[@lijiangsheng1](https://github.com/lijiangsheng1)|正在进行| +|3. Interrupts||正在进行| |├3.0|[@littleneko](https://github.com/littleneko)|正在进行| |├3.1|[@littleneko](https://github.com/littleneko)|正在进行| |├3.2|[@cloudusers](https://github.com/cloudusers)|正在进行| @@ -34,23 +34,26 @@ Linux Insides |├3.8|[@cloudusers](https://github.com/cloudusers)|正在进行| |├3.9||未开始| |└3.10||未开始| -|System calls|[@qianmoke](https://github.com/qianmoke)|正在进行| -|Timers and time management|[@icecoobe](https://github.com/icecoobe)|正在进行| -|Memory management|[@choleraehyq](https://github.com/choleraehyq)|正在进行| -|Concepts||未开始| -|DataStructures||正在进行| -|├9.0|[@mudongliang](https://github.com/mudongliang)|已完成| -|├9.1|[@oska874](http://github.com/oska874) [@mudongliang](https://github.com/mudongliang)|已完成| -|└9.2|[@Alick Guo](https://github.com/a1ickgu0)|正在进行| -|Theory||已完成| +|4. System calls|[@qianmoke](https://github.com/qianmoke)|正在进行| +|5. Timers and time management|[@icecoobe](https://github.com/icecoobe)|正在进行| +|6. Synchronization primitives||未开始| +|7. Memory management|[@choleraehyq](https://github.com/choleraehyq)|正在进行| +|8. SMP||未开始| +|9. Concepts||未开始| +|10. DataStructures||正在进行| |├10.0|[@mudongliang](https://github.com/mudongliang)|已完成| -|├10.1|[@mudongliang](https://github.com/mudongliang)|已完成| -|└10.2|[@mudongliang](https://github.com/mudongliang)|已完成| +|├10.1|[@oska874](http://github.com/oska874) [@mudongliang](https://github.com/mudongliang)|已完成| +|└10.2|[@Alick Guo](https://github.com/a1ickgu0)|正在进行| +|11. Theory||已完成| +|├11.0|[@mudongliang](https://github.com/mudongliang)|已完成| +|├11.1|[@mudongliang](https://github.com/mudongliang)|已完成| +|└11.2|[@mudongliang](https://github.com/mudongliang)|已完成| +|12. Initial ram disk||未开始| |Misc||正在进行| -|├12.0|[@mudongliang](https://github.com/mudongliang)|已完成| -|├12.1|[@oska874](https://github.com/oska874)|已完成| -|├12.2||未开始| -|└12.3||未开始| +|├13.0|[@mudongliang](https://github.com/mudongliang)|已完成| +|├13.1|[@oska874](https://github.com/oska874)|已完成| +|├13.2||未开始| +|└13.3||未开始| ##翻译认领规则 From 93d401b5ffed6aa6be9548dfddf245d7a8a65931 Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sat, 16 Apr 2016 21:51:06 -0400 Subject: [PATCH 73/90] Update the translation status in the README --- README.md | 80 +++++++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 90494cc..4f26bb1 100644 --- a/README.md +++ b/README.md @@ -14,46 +14,46 @@ Linux Insides | 章节|译者|翻译进度| | ------------- |:-------------:| -----:| -|1. Booting||正在进行| -|├1.0|[@xinqiu](https://github.com/xinqiu)|已完成| -|├1.1|[@hailincai](https://github.com/hailincai)|已完成| -|├1.2|[@hailincai](https://github.com/hailincai)|已完成| -|├1.3|[@hailincai](https://github.com/hailincai)|正在进行| -|├1.4|[@zmj1316](https://github.com/zmj1316)|正在进行| -|├1.5|[@chengong](https://github.com/chengong)|正在进行| -|2. Initialization|[@lijiangsheng1](https://github.com/lijiangsheng1)|正在进行| -|3. Interrupts||正在进行| -|├3.0|[@littleneko](https://github.com/littleneko)|正在进行| -|├3.1|[@littleneko](https://github.com/littleneko)|正在进行| -|├3.2|[@cloudusers](https://github.com/cloudusers)|正在进行| -|├3.3|[@cloudusers](https://github.com/cloudusers)|正在进行| -|├3.4|[@cloudusers](https://github.com/cloudusers)|正在进行| -|├3.5|[@cloudusers](https://github.com/cloudusers)|正在进行| -|├3.6|[@cloudusers](https://github.com/cloudusers)|正在进行| -|├3.7|[@cloudusers](https://github.com/cloudusers)|正在进行| -|├3.8|[@cloudusers](https://github.com/cloudusers)|正在进行| -|├3.9||未开始| -|└3.10||未开始| -|4. System calls|[@qianmoke](https://github.com/qianmoke)|正在进行| -|5. Timers and time management|[@icecoobe](https://github.com/icecoobe)|正在进行| -|6. Synchronization primitives||未开始| -|7. Memory management|[@choleraehyq](https://github.com/choleraehyq)|正在进行| -|8. SMP||未开始| -|9. Concepts||未开始| -|10. DataStructures||正在进行| -|├10.0|[@mudongliang](https://github.com/mudongliang)|已完成| -|├10.1|[@oska874](http://github.com/oska874) [@mudongliang](https://github.com/mudongliang)|已完成| -|└10.2|[@Alick Guo](https://github.com/a1ickgu0)|正在进行| -|11. Theory||已完成| -|├11.0|[@mudongliang](https://github.com/mudongliang)|已完成| -|├11.1|[@mudongliang](https://github.com/mudongliang)|已完成| -|└11.2|[@mudongliang](https://github.com/mudongliang)|已完成| -|12. Initial ram disk||未开始| -|Misc||正在进行| -|├13.0|[@mudongliang](https://github.com/mudongliang)|已完成| -|├13.1|[@oska874](https://github.com/oska874)|已完成| -|├13.2||未开始| -|└13.3||未开始| +| 1. Booting||正在进行| +|├ 1.0|[@xinqiu](https://github.com/xinqiu)|已完成| +|├ 1.1|[@hailincai](https://github.com/hailincai)|已完成| +|├ 1.2|[@hailincai](https://github.com/hailincai)|已完成| +|├ 1.3|[@hailincai](https://github.com/hailincai)|正在进行| +|├ 1.4|[@zmj1316](https://github.com/zmj1316)|正在进行| +|└ 1.5|[@chengong](https://github.com/chengong)|正在进行| +| 2. Initialization|[@lijiangsheng1](https://github.com/lijiangsheng1)|正在进行| +| 3. Interrupts||正在进行| +|├ 3.0|[@littleneko](https://github.com/littleneko)|正在进行| +|├ 3.1|[@littleneko](https://github.com/littleneko)|正在进行| +|├ 3.2|[@cloudusers](https://github.com/cloudusers)|正在进行| +|├ 3.3|[@cloudusers](https://github.com/cloudusers)|正在进行| +|├ 3.4|[@cloudusers](https://github.com/cloudusers)|正在进行| +|├ 3.5|[@cloudusers](https://github.com/cloudusers)|正在进行| +|├ 3.6|[@cloudusers](https://github.com/cloudusers)|正在进行| +|├ 3.7|[@cloudusers](https://github.com/cloudusers)|正在进行| +|├ 3.8|[@cloudusers](https://github.com/cloudusers)|正在进行| +|├ 3.9||未开始| +|└ 3.10||未开始| +| 4. System calls|[@qianmoke](https://github.com/qianmoke)|正在进行| +| 5. Timers and time management|[@icecoobe](https://github.com/icecoobe)|正在进行| +| 6. Synchronization primitives||未开始| +| 7. Memory management|[@choleraehyq](https://github.com/choleraehyq)|正在进行| +| 8. SMP||未开始| +| 9. Concepts||未开始| +| 10. DataStructures||正在进行| +|├ 10.0|[@mudongliang](https://github.com/mudongliang)|已完成| +|├ 10.1|[@oska874](http://github.com/oska874) [@mudongliang](https://github.com/mudongliang)|已完成| +|└ 10.2|[@Alick Guo](https://github.com/a1ickgu0)|正在进行| +| 11. Theory||已完成| +|├ 11.0|[@mudongliang](https://github.com/mudongliang)|已完成| +|├ 11.1|[@mudongliang](https://github.com/mudongliang)|已完成| +|└ 11.2|[@mudongliang](https://github.com/mudongliang)|已完成| +| 12. Initial ram disk||未开始| +| 13. Misc||正在进行| +|├ 13.0|[@mudongliang](https://github.com/mudongliang)|已完成| +|├ 13.1|[@oska874](https://github.com/oska874)|已完成| +|├ 13.2||未开始| +|└ 13.3||未开始| ##翻译认领规则 From 5a64b1f33b85c048f7a6dc6955a8514cb570422b Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sat, 16 Apr 2016 22:08:59 -0400 Subject: [PATCH 74/90] update the links in README of Theory --- Theory/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Theory/README.md b/Theory/README.md index d45e744..86ffc70 100644 --- a/Theory/README.md +++ b/Theory/README.md @@ -2,5 +2,5 @@ 这一章描述各种理论性概念和那些不直接涉及实践,但是知道了会很有用的概念。 -* [分页](http://xinqiu.gitbooks.io/linux-insides/content/Theory/Paging.html) -* [Elf64 格式](http://xinqiu.gitbooks.io/linux-insides/content/Theory/ELF.html) +* [分页](https://github.com/MintCN/linux-insides-zh/blob/master/Theory/Paging.md) +* [Elf64 格式](https://github.com/MintCN/linux-insides-zh/blob/master/Theory/ELF.md) From e0bcd37f9e3a1f8ccdfc6bbb29e51fbb0f4c4c5c Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sat, 16 Apr 2016 22:20:31 -0400 Subject: [PATCH 75/90] Update the links in README of DataStructures, and change the translation status for this chapter --- DataStructures/README.md | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DataStructures/README.md b/DataStructures/README.md index be40530..052035b 100644 --- a/DataStructures/README.md +++ b/DataStructures/README.md @@ -5,5 +5,5 @@ Linux内核对很多数据结构提供不同的实现方法,比如,双向链 这部分考虑这些数据结构和算法。 - * [双向链表](https://github.com/MintCN/linux-insides/blob/master/DataStructures/dlist.md) - * [基数树](https://github.com/MintCN/linux-insides/blob/master/DataStructures/radix-tree.md) + * [双向链表](https://github.com/MintCN/linux-insides-zh/blob/master/DataStructures/dlist.md) + * [基数树](https://github.com/MintCN/linux-insides-zh/blob/master/DataStructures/radix-tree.md) diff --git a/README.md b/README.md index 4f26bb1..c2106eb 100644 --- a/README.md +++ b/README.md @@ -40,10 +40,10 @@ Linux Insides | 7. Memory management|[@choleraehyq](https://github.com/choleraehyq)|正在进行| | 8. SMP||未开始| | 9. Concepts||未开始| -| 10. DataStructures||正在进行| +| 10. DataStructures||已完成| |├ 10.0|[@mudongliang](https://github.com/mudongliang)|已完成| |├ 10.1|[@oska874](http://github.com/oska874) [@mudongliang](https://github.com/mudongliang)|已完成| -|└ 10.2|[@Alick Guo](https://github.com/a1ickgu0)|正在进行| +|└ 10.2|[@oska874](https://github.com/oska874)|已完成| | 11. Theory||已完成| |├ 11.0|[@mudongliang](https://github.com/mudongliang)|已完成| |├ 11.1|[@mudongliang](https://github.com/mudongliang)|已完成| From ad5720fcbc090440218a134bf36ff234b9afc768 Mon Sep 17 00:00:00 2001 From: xinqiu Date: Sun, 17 Apr 2016 10:32:05 +0800 Subject: [PATCH 76/90] =?UTF-8?q?=E5=8E=9F=E4=BD=9C=E4=B8=AD=E6=B2=A1vsysc?= =?UTF-8?q?alls=20and=20vdso=E8=BF=99=E4=B8=AA=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SUMMARY.md | 1 - 1 file changed, 1 deletion(-) diff --git a/SUMMARY.md b/SUMMARY.md index c09f05e..53d487f 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -30,7 +30,6 @@ * [Memory management](mm/README.md) * [Memblock](mm/linux-mm-1.md) * [Fixmaps and ioremap](mm/linux-mm-2.md) -* vsyscalls and vdso * SMP * [Concepts](Concepts/README.md) * [Per-CPU variables](Concepts/per-cpu.md) From 44331dd7e8d4eeed47a0d25c4220623979bebf71 Mon Sep 17 00:00:00 2001 From: xinqiu Date: Sun, 17 Apr 2016 10:35:45 +0800 Subject: [PATCH 77/90] =?UTF-8?q?=E4=B8=8E=E5=8E=9F=E4=BD=9C=E7=9A=84SUMMA?= =?UTF-8?q?RY=E8=BF=9B=E8=A1=8C=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SUMMARY.md | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/SUMMARY.md b/SUMMARY.md index 53d487f..b8883b0 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -27,27 +27,48 @@ * [Handling Non-Maskable interrupts](interrupts/interrupts-6.md) * [Dive into external hardware interrupts](interrupts/interrupts-7.md) * [Initialization of external hardware interrupts structures](interrupts/interrupts-8.md) + * [Softirq, Tasklets and Workqueues](interrupts/interrupts-9.md) + * [Last part](interrupts/interrupts-10.md) +* [System calls](SysCall/README.md) + * [Introduction to system calls](SysCall/syscall-1.md) + * [How the Linux kernel handles a system call](SysCall/syscall-2.md) + * [vsyscall and vDSO](SysCall/syscall-3.md) + * [How the Linux kernel runs a program](SysCall/syscall-4.md) +* [Timers and time management](Timers/README.md) + * [Introduction](Timers/timers-1.md) + * [Clocksource framework](Timers/timers-2.md) + * [The tick broadcast framework and dyntick](Timers/timers-3.md) + * [Introduction to timers](Timers/timers-4.md) + * [Clockevents framework](Timers/timers-5.md) + * [x86 related clock sources](Timers/timers-6.md) + * [Time related system calls](Timers/timers-7.md) +* [Synchronization primitives](SyncPrim/README.md) + * [Introduction to spinlocks](SyncPrim/sync-1.md) * [Memory management](mm/README.md) * [Memblock](mm/linux-mm-1.md) * [Fixmaps and ioremap](mm/linux-mm-2.md) -* SMP +* [SMP]() * [Concepts](Concepts/README.md) * [Per-CPU variables](Concepts/per-cpu.md) * [Cpumasks](Concepts/cpumask.md) + * [The initcall mechanism](Concepts/initcall.md) * [Data Structures in the Linux Kernel](DataStructures/README.md) * [Doubly linked list](DataStructures/dlist.md) * [Radix tree](DataStructures/radix-tree.md) + * [Bit arrays](DataStructures/bitmap.md) * [理论](Theory/README.md) * [分页](Theory/Paging.md) * [Elf64 格式](Theory/ELF.md) - * CPUID - * MSR + * [CPUID]() + * [MSR]() * Initial ram disk - * initrd + * [initrd]() * [Misc](Misc/README.md) - * How kernel compiled - * Write and Submit your first Linux kernel Patch - * Data types in the kernel + * [How the kernel is compiled](Misc/how_kernel_compiled.md) + * [Linkers](Misc/linkers.md) + * [Linux kernel development](Misc/contribute.md) + * [Write and Submit your first Linux kernel Patch]() + * [Data types in the kernel]() * [Useful links](LINKS.md) * [Contributors](contributors.md) From 24cbf2ad6e2f6bf35af041e9459389c25e9e4f78 Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sat, 16 Apr 2016 23:02:43 -0400 Subject: [PATCH 78/90] fix some small problems --- README.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c2106eb..b822d47 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ Linux Insides [![Join the chat at https://gitter.im/MintCN/linux-insides-zh](https://badges.gitter.im/MintCN/linux-insides-zh.svg)](https://gitter.im/MintCN/linux-insides-zh?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -一系列关于Linux内核和其内在机理的帖子。 +一系列关于 Linux 内核和其内在机理的帖子。 -**目的很简单** - 分享我对Linux内核内在机理的一点知识,帮助对linux内核内在机理感兴趣的人,和其他低级话题。 +**目的很简单** - 分享我对 Linux 内核内在机理的一点知识,帮助对 Linux 内核内在机理感兴趣的人,和其他低级话题。 -**问题/建议**: 通过在twitter上[@0xAX](https://twitter.com/0xAX),直接添加[issue](https://github.com/0xAX/linux-insides/issues/new)或者直接给我发[邮件](mailto:anotherworldofworld@gmail.com),请自由地向我提出任何问题或者建议。 +**问题/建议**: 通过在 twitter 上 [@0xAX](https://twitter.com/0xAX) ,直接添加 [issue](https://github.com/0xAX/linux-insides/issues/new) 或者直接给我发[邮件](mailto:anotherworldofworld@gmail.com),请自由地向我提出任何问题或者建议。 ##翻译进度 @@ -57,23 +57,20 @@ Linux Insides ##翻译认领规则 -为了避免多个译者同时翻译相同章节的情况出现, 请按照以下规则认领自己要翻译的章节: +为了避免多个译者同时翻译相同章节的情况出现,请按照以下规则认领自己要翻译的章节: -* 在 [README.md](https://github.com/MintCN/linux-insides-zh/blob/master/README.md) 查看你想翻译的章节的状态; -* 在确认想翻译的章节没有被翻译之后,开一个 issue ,告诉大家你想翻译哪一章或者哪一节,同时提交申请翻译的 PR ,将 [README.md](https://github.com/MintCN/linux-insides-zh/blob/master/README.md) 中的翻译状态修改为“正在进行”; +* 在 [README.md](https://github.com/MintCN/linux-insides-zh/blob/master/README.md) 中查看你想翻译的章节的状态; +* 在确认想翻译的章节没有被翻译之后,开一个 issue ,告诉大家你想翻译哪一章节,同时提交申请翻译的 PR ,将 [README.md](https://github.com/MintCN/linux-insides-zh/blob/master/README.md) 中的翻译状态修改为“正在进行”; * 开始翻译你认领的章节; -* 完成翻译之后,提交翻译内容的 PR ,待 PR 被 merge 之后,关闭 issue ; +* 完成翻译之后,提交翻译内容的 PR ,待 PR 被 merge 之后,关闭 issue ; * 最后,将 [README.md](https://github.com/MintCN/linux-insides-zh/blob/master/README.md) 中的翻译状态修改为“已完成”,同时不要忘记把自己添加到 [contributors.md](https://github.com/MintCN/linux-insides-zh/blob/master/contributors.md) 中。 -翻译前建议看 [TRANSLATION_NOTES.md](https://github.com/MintCN/linux-insides-zh/blob/master/TRANSLATION_NOTES.md) 。 关于翻译约定,大家有任何问题或建议也请开 issue 讨论。 - - +翻译前建议看 [TRANSLATION_NOTES.md](https://github.com/MintCN/linux-insides-zh/blob/master/TRANSLATION_NOTES.md) 。关于翻译约定,大家有任何问题或建议也请开 issue 讨论。 ##作者 [@0xAX](https://twitter.com/0xAX) - ##中文维护者 [@xinqiu](https://github.com/xinqiu) @@ -84,7 +81,6 @@ Linux Insides 详见 [contributors.md](https://github.com/MintCN/linux-insides-zh/blob/master/contributors.md) - ##LICENSE Licensed [BY-NC-SA Creative Commons](http://creativecommons.org/licenses/by-nc-sa/4.0/). From 0cf0591634f7227267c58df41dbe746232ec4430 Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sat, 16 Apr 2016 23:10:44 -0400 Subject: [PATCH 79/90] update links in README to gitbook --- Booting/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Booting/README.md b/Booting/README.md index 86747e6..95956e9 100644 --- a/Booting/README.md +++ b/Booting/README.md @@ -2,9 +2,8 @@ 本章介绍了Linux内核引导过程。你将在这看到一些描述内核加载过程的整个周期的相关文章: -* [从引导加载程序内核](http://xinqiu.gitbooks.io/linux-inside-zh/content/Booting/index.html/linux-bootstrap-1.html) - 介绍了从启动计算机到内核执行第一条指令之前的所有阶段; -* [在内核安装代码的第一步](http://xinqiu.gitbooks.io/linux-inside-zh/content/Booting/linux-bootstrap-2.html) - 介绍了在内核设置代码的第一个步骤。你会看到堆的初始化,查询不同的参数,如EDD,IST和等... -* [视频模式初始化和转换到保护模式](http://xinqiu.gitbooks.io/linux-inside-zh/content/Booting/linux-bootstrap-3.html) - 介绍了视频模式初始化内核设置代码并过渡到保护模式。 -* [过渡到64位模式](http://xinqiu.gitbooks.io/linux-inside-zh/content/Booting/linux-bootstrap-4.html) - 介绍了过渡到64位模式的准备并过渡到64位。 +* [从引导加载程序内核](https://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-1.html) - 介绍了从启动计算机到内核执行第一条指令之前的所有阶段; +* [在内核安装代码的第一步](https://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html) - 介绍了在内核设置代码的第一个步骤。你会看到堆的初始化,查询不同的参数,如EDD,IST和等... +* [视频模式初始化和转换到保护模式](https://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-3.html) - 介绍了视频模式初始化内核设置代码并过渡到保护模式。 +* [过渡到64位模式](https://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-4.html) - 介绍了过渡到64位模式的准备并过渡到64位。 * [内核解压缩](http://xinqiu.gitbooks.io/linux-inside-zh/content/Booting/linux-bootstrap-5.html) - 介绍了内核解压缩之前的准备然后直接解压缩。 - From 7844eaf792b23b5240c4ea77fd229db3ade6805f Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sat, 16 Apr 2016 23:12:17 -0400 Subject: [PATCH 80/90] remove the executable flag for linux-bootstrap-2.md --- Booting/linux-bootstrap-2.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 Booting/linux-bootstrap-2.md diff --git a/Booting/linux-bootstrap-2.md b/Booting/linux-bootstrap-2.md old mode 100755 new mode 100644 From 63d71daf810a454a4961d36347475edc7722adf4 Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sat, 16 Apr 2016 23:15:30 -0400 Subject: [PATCH 81/90] Update the translation status for Chapter One --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b822d47..da7858b 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ Linux Insides |├ 1.0|[@xinqiu](https://github.com/xinqiu)|已完成| |├ 1.1|[@hailincai](https://github.com/hailincai)|已完成| |├ 1.2|[@hailincai](https://github.com/hailincai)|已完成| -|├ 1.3|[@hailincai](https://github.com/hailincai)|正在进行| -|├ 1.4|[@zmj1316](https://github.com/zmj1316)|正在进行| +|├ 1.3|[@hailincai](https://github.com/hailincai)|已完成| +|├ 1.4|[@zmj1316](https://github.com/zmj1316)|已完成| |└ 1.5|[@chengong](https://github.com/chengong)|正在进行| | 2. Initialization|[@lijiangsheng1](https://github.com/lijiangsheng1)|正在进行| | 3. Interrupts||正在进行| From 4b65a2e2ee4321724f0dbf036ca563070a0f8d5d Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sat, 16 Apr 2016 23:55:20 -0400 Subject: [PATCH 82/90] update links in README of DataStructures to gitbook --- DataStructures/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DataStructures/README.md b/DataStructures/README.md index 052035b..87af433 100644 --- a/DataStructures/README.md +++ b/DataStructures/README.md @@ -5,5 +5,5 @@ Linux内核对很多数据结构提供不同的实现方法,比如,双向链 这部分考虑这些数据结构和算法。 - * [双向链表](https://github.com/MintCN/linux-insides-zh/blob/master/DataStructures/dlist.md) - * [基数树](https://github.com/MintCN/linux-insides-zh/blob/master/DataStructures/radix-tree.md) + * [双向链表](https://xinqiu.gitbooks.io/linux-insides-cn/content/DataStructures/dlist.html) + * [基数树](https://xinqiu.gitbooks.io/linux-insides-cn/content/DataStructures/radix-tree.html) From 41598aafa76e36ded3dfb9b31de6cd3358846819 Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sat, 16 Apr 2016 23:57:57 -0400 Subject: [PATCH 83/90] update links in README of Theory to gitbook --- Theory/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Theory/README.md b/Theory/README.md index 86ffc70..aae7171 100644 --- a/Theory/README.md +++ b/Theory/README.md @@ -2,5 +2,5 @@ 这一章描述各种理论性概念和那些不直接涉及实践,但是知道了会很有用的概念。 -* [分页](https://github.com/MintCN/linux-insides-zh/blob/master/Theory/Paging.md) -* [Elf64 格式](https://github.com/MintCN/linux-insides-zh/blob/master/Theory/ELF.md) +* [分页](https://xinqiu.gitbooks.io/linux-insides-cn/content/Theory/Paging.html) +* [Elf64 格式](https://xinqiu.gitbooks.io/linux-insides-cn/content/Theory/ELF.html) From 9bb89525c9f01afdd2ff5fb143b82358d7c7b2dc Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sun, 17 Apr 2016 00:02:02 -0400 Subject: [PATCH 84/90] Change some parts to Chinese --- SUMMARY.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SUMMARY.md b/SUMMARY.md index b8883b0..ba0039f 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -52,9 +52,9 @@ * [Per-CPU variables](Concepts/per-cpu.md) * [Cpumasks](Concepts/cpumask.md) * [The initcall mechanism](Concepts/initcall.md) -* [Data Structures in the Linux Kernel](DataStructures/README.md) - * [Doubly linked list](DataStructures/dlist.md) - * [Radix tree](DataStructures/radix-tree.md) +* [Linux内核中的数据结构](DataStructures/README.md) + * [双向链表](DataStructures/dlist.md) + * [基数树](DataStructures/radix-tree.md) * [Bit arrays](DataStructures/bitmap.md) * [理论](Theory/README.md) * [分页](Theory/Paging.md) @@ -69,6 +69,6 @@ * [Linux kernel development](Misc/contribute.md) * [Write and Submit your first Linux kernel Patch]() * [Data types in the kernel]() -* [Useful links](LINKS.md) -* [Contributors](contributors.md) +* [有帮助的链接](LINKS.md) +* [贡献者](contributors.md) From e13f3dbe806a10da9ca5784cc3f2eb1ba0a2493c Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sun, 17 Apr 2016 00:08:59 -0400 Subject: [PATCH 85/90] Add LINKS Document --- LINKS.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 LINKS.md diff --git a/LINKS.md b/LINKS.md new file mode 100644 index 0000000..4f1d178 --- /dev/null +++ b/LINKS.md @@ -0,0 +1,51 @@ +有帮助的链接 +======================== + +Linux 启动 +------------------------ + +* [Linux/x86 boot protocol](https://www.kernel.org/doc/Documentation/x86/boot.txt) +* [Linux kernel parameters](https://github.com/torvalds/linux/blob/master/Documentation/kernel-parameters.txt) + +保护模式 +------------------------ + +* [64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html) + +串口编程 +------------------------ + +* [8250 UART Programming](http://en.wikibooks.org/wiki/Serial_Programming/8250_UART_Programming#UART_Registers) +* [Serial ports on OSDEV](http://wiki.osdev.org/Serial_Ports) + +VGA +------------------------ + +* [Video Graphics Array (VGA)](http://en.wikipedia.org/wiki/Video_Graphics_Array) + +IO +------------------------ + +* [IO port programming](http://www.tldp.org/HOWTO/text/IO-Port-Programming) + +GCC and GAS +------------------------ + +* [GCC type attributes](https://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html) +* [Assembler Directives](http://www.chemie.fu-berlin.de/chemnet/use/info/gas/gas_toc.html#TOC65) + +重要的数据结构 +-------------------------- + +* [task_struct definition](http://lxr.free-electrons.com/source/include/linux/sched.h#L1274) + +其他框架 +------------------------ + +* [PowerPC and Linux Kernel Inside](http://www.systemcomputing.org/ppc/) + +有帮助的链接 +------------------------ + +* [Linux x86 Program Start Up](http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html) +* [Memory Layout in Program Execution (32 bits)](http://fgiasson.com/articles/memorylayout.txt) From aa33d0f5702febe06d1f16ff517ff50b90bc5548 Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sun, 17 Apr 2016 00:20:07 -0400 Subject: [PATCH 86/90] Translate some parts of SUMMARY to Chinese --- SUMMARY.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/SUMMARY.md b/SUMMARY.md index ba0039f..a25b742 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,11 +1,11 @@ # Summary -* [Introduction](README.md) +* [简介](README.md) * [引导](Booting/README.md) * [从引导加载程序内核](Booting/linux-bootstrap-1.md) * [在内核安装代码的第一步](Booting/linux-bootstrap-2.md) * [视频模式初始化和转换到保护模式](Booting/linux-bootstrap-3.md) - * [过渡到64位模式](Booting/linux-bootstrap-4.md) + * [过渡到 64 位模式](Booting/linux-bootstrap-4.md) * [内核解压缩](Booting/linux-bootstrap-5.md) * [Initialization](Initialization/README.md) * [First steps in the kernel](Initialization/linux-initialization-1.md) @@ -52,10 +52,10 @@ * [Per-CPU variables](Concepts/per-cpu.md) * [Cpumasks](Concepts/cpumask.md) * [The initcall mechanism](Concepts/initcall.md) -* [Linux内核中的数据结构](DataStructures/README.md) +* [Linux 内核中的数据结构](DataStructures/README.md) * [双向链表](DataStructures/dlist.md) * [基数树](DataStructures/radix-tree.md) - * [Bit arrays](DataStructures/bitmap.md) + * [Bit arrays]() * [理论](Theory/README.md) * [分页](Theory/Paging.md) * [Elf64 格式](Theory/ELF.md) @@ -63,12 +63,11 @@ * [MSR]() * Initial ram disk * [initrd]() -* [Misc](Misc/README.md) - * [How the kernel is compiled](Misc/how_kernel_compiled.md) - * [Linkers](Misc/linkers.md) - * [Linux kernel development](Misc/contribute.md) +* [杂项](Misc/README.md) + * [内核编译方法](Misc/how_kernel_compiled.md) + * [链接器](Misc/linkers.md) + * [Linux 内核开发](Misc/contribute.md) * [Write and Submit your first Linux kernel Patch]() * [Data types in the kernel]() * [有帮助的链接](LINKS.md) * [贡献者](contributors.md) - From 7b214d31033c6966c9731542c1027a49292fcc5f Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sun, 17 Apr 2016 01:04:13 -0400 Subject: [PATCH 87/90] Change link - 0xax.gitbook.io to xinqiu.gitbooks.io --- Booting/linux-bootstrap-2.md | 2 +- Theory/Paging.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Booting/linux-bootstrap-2.md b/Booting/linux-bootstrap-2.md index 049e8f0..21f8931 100644 --- a/Booting/linux-bootstrap-2.md +++ b/Booting/linux-bootstrap-2.md @@ -3,7 +3,7 @@ 内核启动的第一步 -------------------------------------------------------------------------------- -在[上一节中](https://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-1.html)我们开始接触到内核启动代码,并且分析了初始化部分,最后我们停在了对`main`函数(`main`函数是第一个用C写的函数)的调用(`main`函数位于[arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c))。 +在[上一节中](https://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-1.html)我们开始接触到内核启动代码,并且分析了初始化部分,最后我们停在了对`main`函数(`main`函数是第一个用C写的函数)的调用(`main`函数位于[arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c))。 在这一节中我们将继续对内核启动过程的研究,我们将 * 认识`保护模式` diff --git a/Theory/Paging.md b/Theory/Paging.md index 60df812..bfe6c1b 100644 --- a/Theory/Paging.md +++ b/Theory/Paging.md @@ -4,7 +4,7 @@ 简介 -------------------------------------------------------------------------------- -在 Linux 内核启动过程中的[第五部分](http://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-5.html),我们学到了内核在启动的最早阶段都做了哪些工作。接下来,在我们明白内核如何运行第一个 init 进程之前,内核初始化其他部分,比如加载 `initrd` ,初始化 lockdep ,以及许多许多其他的工作。 +在 Linux 内核启动过程中的[第五部分](https://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-5.html),我们学到了内核在启动的最早阶段都做了哪些工作。接下来,在我们明白内核如何运行第一个 init 进程之前,内核初始化其他部分,比如加载 `initrd` ,初始化 lockdep ,以及许多许多其他的工作。 是的,那将有很多不同的事,但是还有更多更多更多关于**内存**的工作。 @@ -207,7 +207,7 @@ ffffffffffe00000 - ffffffffffffffff (=2 MB) unused hole #define __START_KERNEL_map _AC(0xffffffff80000000, UL) ``` -通常内核的 `.text` 段开始于 `CONFIG_PHYSICAL_START` 偏移。我们已经在 [ELF64](https://github.com/0xAX/linux-insides/blob/master/Theory/ELF.md) 相关帖子中看见。 +通常内核的 `.text` 段开始于 `CONFIG_PHYSICAL_START` 偏移。我们已经在 [ELF64](https://github.com/MintCN/linux-insides-zh/blob/master/Theory/ELF.md) 相关帖子中看见。 ``` readelf -s vmlinux | grep ffffffff81000000 From 41da3d435c2972cb721b949282ea5b9c858c1fe7 Mon Sep 17 00:00:00 2001 From: mudongliang Date: Sun, 17 Apr 2016 01:09:58 -0400 Subject: [PATCH 88/90] Add new contributors --- contributors.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contributors.md b/contributors.md index f6e32ba..28f44f8 100644 --- a/contributors.md +++ b/contributors.md @@ -17,3 +17,7 @@ [@oska874](https://github.com/oska874) [@cloudusers](https://github.com/cloudusers) + +[@hailincai](https://github.com/hailincai) + +[@zmj1316](https://github.com/zmj1316) From 9c91c2129fc7a36611aaf93f296d0c0c1f1f0e73 Mon Sep 17 00:00:00 2001 From: Yangjing Zhang Date: Thu, 5 May 2016 11:57:16 +0800 Subject: [PATCH 89/90] =?UTF-8?q?=E7=94=B3=E8=AF=B7=E7=BF=BB=E8=AF=913.9?= =?UTF-8?q?=20#55?= 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 da7858b..d2edaf4 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Linux Insides |├ 3.6|[@cloudusers](https://github.com/cloudusers)|正在进行| |├ 3.7|[@cloudusers](https://github.com/cloudusers)|正在进行| |├ 3.8|[@cloudusers](https://github.com/cloudusers)|正在进行| -|├ 3.9||未开始| +|├ 3.9|[@zhangyangjing](https://github.com/zhangyangjing)|正在进行| |└ 3.10||未开始| | 4. System calls|[@qianmoke](https://github.com/qianmoke)|正在进行| | 5. Timers and time management|[@icecoobe](https://github.com/icecoobe)|正在进行| From b388006326910133a7db6ffda59fee29644718f4 Mon Sep 17 00:00:00 2001 From: senhtry Date: Sat, 14 May 2016 11:46:20 +0800 Subject: [PATCH 90/90] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=94=99=E5=88=AB?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Booting/linux-bootstrap-2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Booting/linux-bootstrap-2.md b/Booting/linux-bootstrap-2.md index 21f8931..906f609 100644 --- a/Booting/linux-bootstrap-2.md +++ b/Booting/linux-bootstrap-2.md @@ -22,7 +22,7 @@ 淘汰[实模式](http://wiki.osdev.org/Real_Mode)的主要原因是因为在实模式下,系统能够访问的内存非常有限。如果你还记得我们在上一节说的,在实模式下,系统最多只能访问1M内存,而且在很多时候,实际能够访问的内存只有640K。 -保护模式带来了很多的改变,不过主要的改变都集中在内存管理方法。在保护模式中,实模式的20位地址线被替换成32位地址线,因此系统可以访问多大4GB的地址空间。另外,在保护模式中引入了[内存分页](http://en.wikipedia.org/wiki/Paging)功能,在后面的章节中我们将介绍这个功能。 +保护模式带来了很多的改变,不过主要的改变都集中在内存管理方法。在保护模式中,实模式的20位地址线被替换成32位地址线,因此系统可以访问多达4GB的地址空间。另外,在保护模式中引入了[内存分页](http://en.wikipedia.org/wiki/Paging)功能,在后面的章节中我们将介绍这个功能。 保护模式提供了2种完全不同的内存管理机制: