0%

Linux 0.11: Boot

平台要求:80x86 CPU

  1. CPU 复位
  2. BIOS 代码的入口应当在 0xFFFF0
    1. 调用 0x19 中断:将 0x07c00 - 0x07E00 代码从磁盘中复制到内存中
    2. 跳转到 0x07c00: boot/bootsect.s
    • 引导硬盘将操作系统代码加载到内存中
    • 检查记录机器硬件状态信息/系统数据

x86 CPU 复位:

  • 实地址模式(寻址空间 1MB)
  • PC: 0xFFFF0 (CS: 0xFFFF, IP:0x0000)

实地址模式下的内存布局:

  • 0x00000 - 0x003FF: 存放中断向量表
    • 最多存放 256 个中断向量 (1KB * 16b)
    • 16 位实模式地址规定,便于中断处理查找中断向量
    • 由 BIOS 建立
  • 0x00400 - 0x004FF: 存放 BIOS 数据
    • 软件规定
  • 0x07C00 - 0x07E00: 存放 Boot 扇区
    • 硬件规定,与操作系统无关
    • boot/bootsect.s 生成
  • 0x0E05B - 0x0FFFE: 存放中断服务程序
    • 每个程序都由中断向量表中的地址对应
  • 0x10000: system
  • 0x90000 - 0x90200: init segment
    • 由 boot segment 将自己拷贝到此处后跳转到此处继续执行

boot/bootsect.s : Boot 扇区(每个扇区 512B), 存放在 0 盘面 0 磁道 1 扇区

  1. entry: start: 将 boot segment 复制到 init segment
    1. 设置段寄存器 ds, es
    2. 偏移置0 si, di
    3. 循环 256 次:movw 复制 word (16b)
    4. 跳转到 init segment 中的 go 偏移处
  2. go: 设置栈的段寄存器
  3. load_setup: 将 setup.s 从磁盘中复制到内存中的 0x90200 后 4 个扇区
    1. 调用 0x13 中断实现拷贝
    2. 拷贝结束后跳转到 ok_load_setup
  4. ok_load_setup
    1. 调用 0x13 中断:将 head.s 和 kernel 从磁盘中复制到内存中 0x10000 后大约 240 个扇区
      • 调用 read_itkill_motor
    2. 检查 root_dev
    3. 跳转到 setup.sentry: start

boot/setup.s : 打开保护模式

  1. 获取机器系统数据
    • 0x10 中断:读机器系统数据,写到 0x90000 - 0x901FD 的位置(还有 2 字节未覆盖)
    • INITSEG 的位置,此时 bootsect 执行完毕,不再使用
  2. 检查磁盘设备等
  3. 关闭中断:cli
    • EFLAGS 标志寄存器的 IF 位(interrupt flag)置 0
    • 不再响应中断
    • 目的:之后要移动 kernel 到 0x00000,会覆盖实地址模式的中断向量表,无法正确响应中断
    • 下一次打开中断将在 main()

x86 EFLAGS Register:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
                 System Flags of EFLAGS Register
31 23 15 7 0
+---------------+-----------+-+-+++-+----+-+-+-+-+++-+-+-+-+-+-+-+
|###########################|V|R|#|N|ID |O|D|I|T|S|Z|#|A|#|P|#|C|
|0 0 0 0 0 0 0 0 0 0 0 0 0 0| | |0| | |#|#| |#|#|#|0|#|0|#|1|#|
|###########################|M|F|#|T| PL|F|F|F|F|F|F|#|F|#|F|#|F|
+---------------+-----------+++++++++-+--+-+-+++-+++-+-+-+-+-+-+-+
| | | | |
Virtual 8086 Mode----+ | | | |
Resume Flag------+ | | |
Nested Task Flag----------+ | |
I/O Privilege Level-------------+ |
Interrupt Enable---------------------+
----------------------------------------------------------------------------
NOTE
0 or 1 Indicates Intel Reserved. DO NOT DEFINE.
----------------------------------------------------------------------------
  1. 将系统(head.s + kernel)从 0x10000 处移位到 0x00000 处 : do_move 循环
    • 覆盖实地址模式的中断向量表: 实地址模式下的中断机制将不再被使用
  2. 建立描述符表
    • lidt idt_48 指令:赋值 IDTR 寄存器, 指定中断描述符表(目标是取代实地址模式下的中断向量表)
    • lgdt gdt_48 指令:赋值 GDTR 寄存器, 指定全局描述符表(目标是取代实地址模式下的段寄存器)
      • gdt_48 设置 GDT 地址在 gdt, gdt limit 设置为 256 个 entry
      • gdt 中第 0 项是空; 第 1 项是 0 特权级的代码段; 第 2 项是 0 特权级的数据段; …

Segement Descriptor Format:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
        DESCRIPTORS USED FOR APPLICATIONS CODE AND DATA SEGMENTS

31 23 15 7 0
+-----------------+-+-+-+-+---------+-+-----+-+-----+-+-----------------+
| | | | |A| | | | | | | |
| BASE 31..24 |G|X|O|V| LIMIT |P| DPL |1| TYPE|A| BASE 23..16 | 4
| | | | |L| 19..16 | | | | | | |
|-----------------+-+-+-+-+---------+-+-----+-+-----+-+-----------------|
| | |
| SEGMENT BASE 15..0 | SEGMENT LIMIT 15..0 | 0
| | |
+-----------------+-----------------+-----------------+-----------------+

DESCRIPTORS USED FOR SPECIAL SYSTEM SEGMENTS

31 23 15 7 0
+-----------------+-+-+-+-+---------+-+-----+-+-------+-----------------+
| | | | |A| | | | | | |
| BASE 31..24 |G|X|O|V| LIMIT |P| DPL |0| TYPE | BASE 23..16 | 4
| | | | |L| 19..16 | | | | | |
|-----------------+-+-+-+-+---------+-+-----+-+-------+-----------------|
| | |
| SEGMENT BASE 15..0 | SEGMENT LIMIT 15..0 | 0
| | |
+-----------------+-----------------+-----------------+-----------------+

Not-Present Descriptor
31 23 15 7 0
+-----------------+-----------------+-+-----+-+-------+-----------------+
| | | | | | |
| AVAILABLE |O| DPL |S| TYPE | AVAILABLE | 4
| | | | | | |
|-----------------------------------+-+-----+-+-------+-----------------|
| |
| AVAILABLE | 0
| |
+-----------------+-----------------+-----------------+-----------------+

A - Accessed
AVL - Available For Use By Systems Programmers
DPL - Descriptor Privilege Level
G - Granularity
P - Segment Present
  • GDT 中第 0 项永远是空
  • GDT 中每个 entry 为 64 bits
  1. 扩展地址范围为 32 位
    • 北桥 打开 A20: out + empty_8042, 总线 32 位已打开
    • 重新编程 8259A 中断控制器:建立新的中断映射
    • CPU 打开 PE: Protection mode (控制寄存器 CR0 中的 PE bit)
      • lmsw 指令:将 CRO 的 PE bit 置 1
      • CPU 开始 32 位寻址
  2. 跳转到 head.s : jmpi 0,8
    • 0: offset
    • 8(0b1000): selector
      • 低 2 位: 表示特权级为 0
      • 第 3 位: 0 表示 GDT
      • 其余位 : 指向 GDT 的第 2 项 内核代码段

保护模式下的 selector:

1
2
3
4
5
6
15                        3 2   0
+-------------------------+-+---+
| |T| |
| INDEX | |RPL|
| |I| |
+-------------------------+-+---+
  • RPL - Requestor’s Privilege Level
  • TI - Table Indicator (0 表示 GDT, 1 表示 LDT)
  • 其余位 : 描述符表的索引

boot/head.s: 建立分页

__pg_dir: 页目录表未来将存放在 head.s 的头部 (0x0 地址处)

  1. startup_32:
    • 赋值 ds 寄存器:selctor 0x10 指向 GDT 的第 2 项: 内核数据段
    • es, fs, gs 对齐到 ds
    • lss: 加载 _stack_start 到 ss (stack_start 定义于 sched.c)
  2. 设置 IDT : setup_idt
    • 构建一个 IDT Gate Descriptor: 存放在 eax(低 32 位) 和 edx(高 32 位)
      • 31-16 bits (selector): 0x0008 = cs
      • 63-48, 15-0 bits (offset) : ignore_init 的地址
        • ignore_init 是一个中断服务程序: 哑中断,通过 printk 打印 “Unknown interrupt” 后 iret
      • 32-47 bits (0x8E00) : interrupt gate, dpl=0, present
    • 将该 IDT Gate Descriptors 加载到 IDT 的 256 个 entry (所有 entry 都相同): rp_sidt 循环
    • idt_descr 设置到 IDTR: lidt idt_descr

80306 IDT Gate Descriptors:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
                              80386 TASK GATE
31 23 15 7 0
+-----------------+-----------------+---+---+---------+-----------------+
|#############(NOT USED)############| P |DPL|0 0 1 0 1|###(NOT USED)####|4
|-----------------------------------+---+---+---------+-----------------|
| SELECTOR |#############(NOT USED)############|0
+-----------------+-----------------+-----------------+-----------------+

80386 INTERRUPT GATE
31 23 15 7 0
+-----------------+-----------------+---+---+---------+-----+-----------+
| OFFSET 31..16 | P |DPL|0 1 1 1 0|0 0 0|(NOT USED) |4
|-----------------------------------+---+---+---------+-----+-----------|
| SELECTOR | OFFSET 15..0 |0
+-----------------+-----------------+-----------------+-----------------+

80386 TRAP GATE
31 23 15 7 0
+-----------------+-----------------+---+---+---------+-----+-----------+
| OFFSET 31..16 | P |DPL|0 1 1 1 1|0 0 0|(NOT USED) |4
|-----------------------------------+---+---+---------+-----+-----------|
| SELECTOR | OFFSET 15..0 |0
+-----------------+-----------------+-----------------+-----------------+
  • IDT 每个 entry 为 64 bits
  • IDT 中存有中断向量表的历史包袱(低 32 位)
  • IDT 中的 Selector 关联着 GDT entry
  1. 设置 GDT : setup_gdt: lgdt gdt_descr
    • gdt_descr: GDT 地址设置为 _gdt,gdt limit 设置为 256 个 entry
    • _gdt 是 GDT 地址,存放 256 项:
      • 第 0 项:空
      • 第 1 项:0 特权代码段
      • 第 2 项:0 特权数据段
      • 第 3 项:空: Temporary
      • 第 4 项 - 第 255 项:目前填充为空,提供给用户进程
  2. 重新设置所有的 ds,es,fs,gs 寄存器到 cs 段, 重新设置栈
    因为重新设置过 GDT (GDT 地址变化,段限长从 8M 变为 16M) ,所以需要重新装载
  3. 检查 A20 是否打开
    未打开则进入死循环: 1 是一个死循环: eax 不可能等于 0x100000
  4. 检查数学协处理器 x87 是否存在
  5. 跳转到 after_page_tables
    • main() 函数的参数压栈
    • main() 函数的返回地址(L6)压栈
      • L6 是死循环(main() 函数死机后返回到此)
    • main() 函数的地址压栈
      • 实际上在后续 setup_pagingret 处会跳转(返回)到 main() 函数
  6. 跳转到 setup_paging
    • 清空 5 个页 (1 个用作页目录,4 个用作一级页表)
    • 将 pg0 到 pg3 的 4 个页的 PTE 写入 __pg_dir
      • 内核中这些页的虚拟地址和物理地址实际上是一样的,以便于管理
    • 将这 4 个页面对应的所有物理页的 entry 全部填充 0x00fff007
    • 设置 CR3 寄存器到 0x0 (__pg_dir)
    • 设置分页标志: CR0 寄存器的 PG 位置 1
    • ret 指令:刷新指令预取队列并跳转到 main() 函数

x86 Paging:

  • CR3 寄存器:保存页目录基地址
  • 两级页表
  • 支持页大小:2MB, 4KB

x86 Page Table Entry:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
31                                  12 11     9 8 7 6 5 4 3 2 1 0
+--------------------------------------+-------+---+-+-+---+-+-+-+
| | | | | | |U|R| |
| PAGE FRAME ADDRESS 31..12 | AVAIL |0 0|D|A|0 0|/|/|P|
| | | | | | |S|W| |
+--------------------------------------+-------+---+-+-+---+-+-+-+

P - Present
R/W - Read/Write
U/S - User/Supervisor
D - Dirty
AVAIL - Available For Systems Programmer Use

NOTE: 0 Indicates Intel Reserved. Do Not Define.

x86 Controll Register:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 31                23                15                7               0
+-----------------+-----------------+--------+--------+-----------------+
| | |
| PAGE DIRECTORY BASE REGISTER (PDBR) | RESERVED | CR3
|--------------------------------------------+--------------------------|
| |
| PAGE FAULT LINEAR ADDRESS | CR2
|-----------------------------------------------------------------------|
| |
| RESERVED | CR1
|-+-----------------------------------------------------------+-+-+-+-+-|
|P| |E|T|E|M|P|
|G| RESERVED |T|S|M|P|E| CR0
+-+---------------+-----------------+-----------------+-------+-+-+-+-+-+

PG - Paging
ET - Extension Type (80x87 Present)
TS - Task Switching
EM - Emulation (80x87 Emulation)
MP - Math Present (Controll WAIT Instruction)
PE - Protection Enable

x86 Address Translation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        15           0      31                           0
Logical +---------------+ +------------------------------+
Address | SELECTOR | | OFFSET |
+---------------+ +---+--------------------------+
!
+------------------------------+
| SEGMENT TRANSLATION |
+--------------+---------------+
+--+-+ PAGING DISABLED
|PG ?|--------------------+
+--+-+ |
31 PAGING ! ENABLED 0 |
Linear +-----------+-----------+-----------+ |
Address | DIR | PAGE | OFFSET | |
+-----------+-----+-----+-----------+ |
! |
+------------------------------+ |
| PAGE TRANSLATION | |
+--------------+---------------+ |
|<---------------------+
31 ! 0
Physical +------------------------------+
Address | |
+------------------------------+