7.6 KiB
在内核安装代码的第一步
#https://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-2.html
内核启动的第一步
在上一节中我们开始接触到内核启动代码,并且分析了初始化部分,最后我们停在了对main函数(main函数是第一个用C写的函数)的调用(main函数位于arch/x86/boot/main.c。
在这一节中我们将继续对内核启动过程的研究,我们将
- 认识
保护模式 - 如何从实模式进入保护模式
- 堆和字符界面初始化
- 内存检测,cpu验证,键盘初始化
- 还有更多
现在让我们开始我们的旅程
保护模式
在操作系统可以使用Intel 64位CPU的长模式之前,内核必须首先将CPU切换到保护模式运行。
什么是保护模式?保护模式于1982年被引入到Intel CPU家族,并且从那之后,知道Intel 64出现,保护模式都是Intel CPU的主要运行模式。
淘汰实模式的主要原因是因为在实模式下,系统能够访问的内存非常有限。如果你还记得我们在上一节说的,在实模式下,系统最多只能访问1M内存,而且在很多时候,实际能够访问的内存只有640K。
保护模式带来了很多的改变,不过只要的改变都集中在内存管理方法。在保护模式中,实模式的20位地址线被替换成32位地址线,因此系统可以访问多大4GB的地址空间。另外,在保护模式中引入了内存分页功能,在后面我们将介绍这个功能。
保护模式提供了2种完全不同的内存关机机制:
- 段式内存管理
- 内存分页
在这一节中,我们只介绍段式内存管理,内存分页我们将在后面的章节进行介绍。
在上一节中我们说过,在实模式下,一个物理地址是由2个部分组成的:
- 内存段的基地址
- 从基地址开始的偏移
通过这2个信息,我们可以通过下面的公式计算出对应的物理地址
PhysicalAddress = Segment * 16 + Offset
在保护模式中,内存段的定义和实模式完全不同。在保护模式中,每个内存段不再是64K大小,段的大小和起始位置是通过一个叫做段描述符的数据结构进行描述的。所有内存段的段描述符存储在一个叫做全局描述符表(GDT)的内存结构中。
全局描述符表示一个内存数据结构,但是它在内存中的位置并不是固定的,它的地址保存在一个特殊寄存器GDTR中。在后面的章节中,我们将在Linux内核代码中看到全局描述符表的地址是如何被保存到GDTR中的。具体的汇编代码看起来是这样的:
lgdt gdt
lgdt汇编代码将把全局描述符表的基地址和大小保存到GDTR寄存器中。GRTD是一个48位的寄存器,这个寄存器中的保存了2部分的内容:
- 全局描述符表的大小 (16位)
- 全局描述符表的基址 (32位)
就像前面的段落说的,全局描述符表包含了所有内存段的段描述符。每个段描述符长度是64位,结构如下图描述:
31 24 19 16 7 0
------------------------------------------------------------
| | |B| |A| | | | |0|E|W|A| |
| BASE 31:24 |G|/|L|V| LIMIT |P|DPL|S| TYPE | BASE 23:16 | 4
| | |D| |L| 19:16 | | | |1|C|R|A| |
------------------------------------------------------------
| | |
| BASE 15:0 | LIMIT 15:0 | 0
| | |
------------------------------------------------------------
粗粗一看,上面的结构非常吓人,不过实际上这个结构是非常容易理解的。比如在上图中的LIMIT 15:0表示这个数据结构的0到15位保存的内存段的大小的0到15位。相似的LIMITE 19:16表示上述数据结构的16到19位保存的是内存段大小的16到19位。从这个分析中,我们可以看出每个内存段的大小是通过20位进行描述的。下面我们将对这个数据结构进行仔细分析:
- Limit[20位]被保存在上述内存结构的0-15和16-19位。根据上述内存结构中
G位的设置,这20位内存定义的内存长度是不一样的。下面是一些具体的例子:
- 如果
G= 0, 并且Limit = 0, 那么表示段长度是1 byte - 如果
G= 1, 并且Limit = 0, 那么表示段长度是4K bytes - 如果
G= 0,并且Limit = 0xfffff,那么表示段长度是1M bytes - 如果
G= 1,并且Limit = 0xfffff,那么表示段长度是4G bytes
从上面的例子我们可以看出:
- 如果G = 0, 那么内存段的长度是按照1 byte进行增长的 ( Limit每增加1,段长度增加1 byte ),最大的内存段长度将是1M bytes;
- 如果G = 1, 那么内存段的长度是按照4K bytes ( Limit每增加1,段长度增加4K bytes )进行增长的,最大的内存段长度将是4G bytes;
- 段长度的计算公司是 base_seg_length * ( LIMIT + 1)。
-
Base[32-bits]被保存在上述地址结构的0-15, 32-39以及56-63位。Base定义了段基址。
-
Type/Attribute (40-47 bits) 定义了内存段的类型以及支持的操作。
S标记( 第44位 )定义了段的类型,S= 0说明这个内存段是一个系统段;S= 1说明这个内存段是一个代码段或者是数据段( 堆栈段是一种特使类型的数据段,堆栈段必须是可以进行读写的段 )。
在S = 1的情况下,上述内存结构的第43位决定了内存段是数据段还是代码段。如果43位 = 0,拿说明是一个数据段,否则就是一个代码段。
对于数据段和代码段,下面的表格给出了段类型定义
| Type Field | Descriptor Type | Description
|-----------------------------|-----------------|------------------
| Decimal | |
| 0 E W A | |
| 0 0 0 0 0 | Data | Read-Only
| 1 0 0 0 1 | Data | Read-Only, accessed
| 2 0 0 1 0 | Data | Read/Write
| 3 0 0 1 1 | Data | Read/Write, accessed
| 4 0 1 0 0 | Data | Read-Only, expand-down
| 5 0 1 0 1 | Data | Read-Only, expand-down, accessed
| 6 0 1 1 0 | Data | Read/Write, expand-down
| 7 0 1 1 1 | Data | Read/Write, expand-down, accessed
| C R A | |
| 8 1 0 0 0 | Code | Execute-Only
| 9 1 0 0 1 | Code | Execute-Only, accessed
| 10 1 0 1 0 | Code | Execute/Read
| 11 1 0 1 1 | Code | Execute/Read, accessed
| 12 1 1 0 0 | Code | Execute-Only, conforming
| 14 1 1 0 1 | Code | Execute-Only, conforming, accessed
| 13 1 1 1 0 | Code | Execute/Read, conforming
| 15 1 1 1 1 | Code | Execute/Read, conforming, accessed