笔记03.3 - Lab 1:bootmain.c

解析main.c

以下是 JOS Boot Loader 部分关于 main.c 的解析,关于 main.c 的介绍请阅读【学习笔记03 - Lab 1:Booting a PC】
以下内容摘抄自《系统的启动和初始化》一书

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include <inc/x86.h>
#include <inc/elf.h>

/**********************************************************************
* 这是一个简单的引导加载程序,它的唯一工作是从第一个 IDE 硬盘引导 ELF 格式的
* 内核镜像
*
* DISK LAYOUT
* * 本程序(boot.S and main.c)是开机引导程序
* 它必须被存储在硬盘的第一个扇区
*
* * 从第二个扇区开始存储内核镜像
*
* * 内核镜像必须是 ELF 格式
*
* 开机步骤
* * CPU 启动时加载 BIOS 到内存并执行 BIOS
*
* * BIOS 初始化设备,设置终端程序,
* 读取启动设备的第一个扇区到内存并跳转
*
* * 假设本引导加载程序存储在硬盘的第一个扇区,这段代码接管控制权
*
* * 由 boot.S 开始进行控制 -- 设置保护模式、堆栈指针,然后调用 bootmain()
* 执行 c 语言代码
*
* * bootmain() 读取内核镜像并跳转
**********************************************************************/

#define SECTSIZE 512
#define ELFHDR ((struct Elf *) 0x10000) // 定义一个指向内存中 ELF文
//件头存放位置的结构体指针。定义 ELF 文件头应该存放在内存的 0x10000 处

void readsect(void*, uint32_t); //读取磁盘上一个扇区
void readseg(uint32_t, uint32_t, uint32_t); //读取 ELF 文件中一段

void
bootmain(void)
{
struct Proghdr *ph, *eph;

// 将文件的前 4KB 读入内存,4KB 为一页,其中包括 ELF 文件头以及程序头表
readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);

// 判断该文件是否为 ELF 文件
if (ELFHDR->e_magic != ELF_MAGIC)
goto bad;

// 将指针指向程序头表的首地址 (ignores ph flags),其中的 (uint8_t *) 是为了让 ELFHDR 指针每 +1 增加 1 而不是 32 位指针默认的 +4。
ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum;// 明确文件段的个数
for (; ph < eph; ph++)
// p_pa是该段的加载地址(物理地址)
readseg(ph->p_pa, ph->p_memsz, ph->p_offset);

// 在将内核加载到内存中后转移到内核入口地址处执行,并且不会再返回(直接寻址方式)
((void (*)(void)) (ELFHDR->e_entry))();

bad:
outw(0x8A00, 0x8A00);
outw(0x8A00, 0x8E00);
while (1)
/* do nothing */;
}

// pa 代表该段的加载地址, count 代表该段在内存中所占字节数,
// offset 代表该段在磁盘文件中的相对于文件首的偏移
// 从内核的 offset 偏移处读取 count 字节到内存的 pa 地址上
// 可能实际复制的字节数多于 count
void
readseg(uint32_t pa, uint32_t count, uint32_t offset)
{
uint32_t end_pa;

end_pa = pa + count; // 找到内存中加载地址的最末端

// 由于硬盘中的每一扇区加载到内存的时候都需要 512 字节对齐,
// 于是在这里把起始加载地址向下对齐到 512 字节的倍数的地址处
pa &= ~(SECTSIZE - 1);

// 将在硬盘中的偏移由字节数转换成扇区数,由于内核可执行程序
// 是从磁盘的第二个扇区开始存储的,所以需要加 1
offset = (offset / SECTSIZE) + 1;

// 如果读取很慢,我们可以一次读取多个扇区
// 可能写入到内存的字节数多于要求的,但没有关系,因为我们是递增加载的
while (pa < end_pa) {
// 因为我们还没有启用分页,而是使用一致的段映射 (见 boot.S)
// 所以可以直接使用物理地址 一旦 JOS 允许 MMU 将不会是这种情况
readsect((uint8_t*) pa, offset);
pa += SECTSIZE;
offset++;// 用 readsect 函数一个扇区一个扇区地读取文件的这一段
}
}

void
waitdisk(void)
{
// 循环直到硬盘准备好
while ((inb(0x1F7) & 0xC0) != 0x40)
/* do nothing */;
}

void
readsect(void *dst, uint32_t offset)
{
// 等待直到硬盘准备好
waitdisk();

outb(0x1F2, 1); // count = 1
outb(0x1F3, offset);
outb(0x1F4, offset >> 8);
outb(0x1F5, offset >> 16);
outb(0x1F6, (offset >> 24) | 0xE0);
outb(0x1F7, 0x20); // cmd 0x20 - read sectors

// 等待直到硬盘准备好
waitdisk();

// 读取一个扇区的数据
insl(0x1F0, dst, SECTSIZE/4);
}
显示 Gitment 评论