QOS 开发 3
QOS 开发日记 3: 硬盘、内核和加载器
前情提要:写了个简陋的 MBR,然后通过显存写了字符串。
然而用显存写太麻烦了,于是后来又改回用系统中断写了。修改之后:
1 | ; 设置光标位置 |
众所周知,程序是储存在硬盘里的一段指令,而内核也是一种程序。那么为了运行真正的内核,我们需要先从硬盘加载它。因此我们先来看硬盘。
硬盘
根据 ATA 规范,所有符合 ATA 的驱动器必须始终支持 PIO 模式作为默认的数据传输机制。
现在比较流行的 SATA 其实就是一种 ATA,所以肯定也支持 PIO 啦~
嗯不过现在系统为了效率,通常会使用更复杂的读写模式,甚至走 PCIe 直接和 CPU 连起来。这里就不管那么多了……
端口问题
这边对这些端口做一些解释。
读
0x1f1
返回错误信息,同样是按 bit 存储。结构大概是:
1 | 0 1 2 3 4 5 6 7 |
0x1f6
也是按 bit 存储的:
1 | 0 1 2 3 4 5 6 7 |
0x1f7
是状态寄存器端口,同样按 bit:
1 | 0 1 2 3 4 5 6 7 |
写
此时有一些寄存器有别的功能。
0x1f1
是参数端口,传入写硬盘的参数。
0x1f7
是指令端口。这里主要用到的指令是:
0xEC
: 硬盘识别0x20
: 读扇区0x30
: 写扇区
具体操作
和硬件交互的方法,除了上次直接写到内存的某个指定区域(统一编址),还可以用 in
和 out
命令:
1 | ; 从 dx 输入数据 |
而在与硬盘的交互中,如果写入了 Command
寄存器(即 0x1f7
端口),那么硬盘就开始执行命令了。所以应当保证 Command
最后写入。
所以~ 给出一个大概得操作顺序(primary 通道为例):
- 往
0x1f2
写入操作的扇区数量; - 往
0x1f3
到0x1f5
写入操作的 LBA 地址的低 24 位; - 往
0x1f6
中 0-3 位写入 LBA 的高 4 位,设置第 6 位为 1 表示使用 LBA 模式,再设置第 4 位选择主盘或从盘; - 最后写入
0x1f7
表示命令; - 然后读取
0x1f7
的Status
寄存器,判断硬盘工作是否完成; - 如果之前是在读硬盘,转到 7;否则
ret
走人; - 从
0x1f0
读数据(2B)。
加载 Loader
为了运行内核,我们需要先从硬盘加载它。这句话好像哪里见过
因此我们需要一个 Loader 加载它。
但是这个 Loader 也需要从硬盘里加载… 套娃呢
Loader 的功能:
- 加载内核
- 检查并确认内核完整性
- 设置环境
- 启动内核
不过这个 Loader 总得放在哪里吧…
众所周知在实模式下有两块可用的区域:0x07e00
到 0x9fbff
(总共 622080B 可用)和 0x00500
到 0x07bff
(总共 30464B 可用)。随便挑一块,就 0x8000
开始的这一块吧。
以及 MBR 占据了硬盘的第 0 扇区(LBA,如果是 CHS 的话就是第 1 扇区),那么 Loader 就放在第 1 扇区好了。
于是先在 src/boot.inc
里加一些宏定义:
1 | LOADER_ADDR equ 0x08000 ; loader 加载地址 |
然后重写一下 src/mbr.s
:
1 | ; src/mbr.s |
当然我们还得写 loader。
1 | ; src/loader.s |
那么汇编命令也需要修改。
1 | # 汇编,-I 表示包括这个目录 |
运行,成功。