[3.2] 操作系统学习 (完善 MBR,进入 loader)
下列内容可能有出错的地方, 毕竟只是个人理解, 有问题评论区🥲🥲
本节将会彻底完善 mbr 扇区,进入 loader
硬盘介绍
现在硬盘大多分为 2 类
- 固态硬盘
- 机械硬盘
我们主要讲机械硬盘
机械硬盘其实就像唱片机一样, 一个磁头不断的在磁盘上读取数据
一个机械硬盘由许多的磁头和磁盘组成
而每一个磁盘的构造都是一样的
我们的 mbr 扇区就是里面最开始的扇区
这是一个简单的磁盘运行过程图
我们可以用 CHS 和 LBA 的方式来表示每一个扇区
CHS 就是以硬盘的逻辑来表示的,也就是"柱面:磁头:扇区", 一个扇区的东西为 512 字节; LBA 也就是以人的逻辑来表示扇区的, LBA 分 2 种
- LBA28 : 用 28比特(3.5 字节)来表示一个扇区都地址, 寻址范围为268435456个扇区, 也就是 128GB
LBA48 : 用 48比特(6 字节)来表示一个扇区都地址, 寻址范围为281474976710656个扇区, 也就是 128PB
那么CPU 要怎么操作硬盘呢? 当然当然是通过 IO 接口了, 有一种叫硬盘控制器的东西,
它和硬盘的关系就和 显卡和显示器 一样, CPU 通过控制硬盘控制器来操作键盘, 这个接口被称为集成设备电路(IDE), 后来基于 IDE 就出现了 ATA全球硬盘标准,但是大家还是习惯叫成 IDE 硬盘, 后来又出现了 SATA硬盘串行接口, 而以前的 ATA 接口也改名为PATA
SATA想要连接几个硬盘都可以, 这完全取决于主板的能力, 而 PATA 只能连接 2 个硬盘, 主盘,和从盘.
操作硬盘
操作硬盘主要就是通过 IO 接口
我们来看看一些需要用到的端口
我们看这个表里的命令是分Primary 通道和Secondary 通道的, 这是为什么呢?
这是因为以前的主板只能接 4 个IDE硬盘, 4个硬盘需要 2 个硬盘控制器, 他们一个叫IDE0, 一个叫 IDE1, 为了区分他们, 所以IDE0 是叫Primary 通道, 而 IDE1 叫Secondary 通道
我们输入不同的命令也就代表不同的通道, 这样我们就可以操作不同的硬盘了
在表中我们可以看到一些端口在读和写的时候用处是不同的, 这是因为以前寄存器是很宝贵的, 所以能少用一个就一个, 也就变成了一个寄存器多个功能的局面
我们要重点看一下 0x1f6 这个端口, 因为一个通道里可能会连接 2 个硬盘(主盘,从盘)所以我们要用这个端口指明要操作哪个硬盘, 这个寄存器是 8 位的,我们在第 4 位用 0,1指明是主盘还是从盘, 那么剩下 7 位是一些硬盘的信息杂项
还有就是在 读操作(注意) 的0x1f7 的 Status 的 8 位寄存器, 里面储存了硬盘的状态信息
而 在写操作 的0x1f7 这是 cmd 寄存器, 我们可以通过在里面写目录来控制硬盘
我们要使用的主要指令有
- 0xEC : 硬盘识别
- 0x20 : 读扇区
- 0x30 : 写扇区
我们是先往 0x1f7 写入命令,确定操作(读,写...),如果是读操作, 那 0x1f7 就变成status
通常的操作硬盘方法
- 选择通道, 并往该通道的sector count寄存器中写入待操作的扇区数
- 往该通道上的三个 LBA 寄存器写入扇区起始地址的低 24 位
- 往 device 寄存器中写入 LBA 地址的 24~27 位,并置第 6 位为 1,使其为 LBA 模式,设置第 4 位,选择操作的硬盘( master 硬盘或 slave 硬盘)
- 往该通道上的 command 寄存器写入操作命令
- 读取该通道上的 status寄存器,判断硬盘工作是否完成
我们获取硬盘准备好的数据的方法
当我们操作好硬盘后, 就要取出我们要的数据
通常的方法如下:
- 无条件传送 : 适用于随时准备好数据的设备, 如内存, CPU随时拿都没问题
- 查询传送 : 适用于速度慢的设备, 如硬盘 , CPU 获取数据时会先查看它工作状态, 如果 ok, CPU 才会读取
- 中断传送 : 因为 查询传送 有一个缺陷, 就是 CPU 要不断查看设备的情况, 这样就很浪费资源, 所以工程师们发明了 中断传送 ,这样当设备准备好时就触发中断来通知 CPU 可以读取了
- 直接存储器存取(DMA) : 在中断中,CPU 要压找, 执行传输指令, 最后还要恢复, 这样也会浪费 CPU 资源, DMA就解决了这一问题, 它可以直接在内存中拿数据, 需要 DMA 控制器实现
- I/O 处理机传送 : 就是 DMA 的超强升级版, 引入了I/O处理机(替代 DMA 控制器),它专门用于处理 IO,并且它其实是一种处理器,只不过用的是另一套擅长 IO 的指令系统,随时可以处理数据
让 MBR 使用硬盘
我们将在 mbr 中使用 查询传送 的方法, 因为其他方式对我们来说太难理解了
直接看代码
; 文件位置 ./mbr.S
; <<操作系统真象还原>>代码
;主引导程序
;------------------------------------------------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
mov ax,0xb800
mov gs,ax
; 清屏
;利用0x06号功能,上卷全部行,则可清屏。
; -----------------------------------------------------------
;INT 0x10 功能号:0x06 功能描述:上卷窗口
;------------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:
mov ax, 0600h
mov bx, 0700h
mov cx, 0 ; 左上角: (0, 0)
mov dx, 184fh ; 右下角: (80,25),
; 因为VGA文本模式中,一行只能容纳80个字符,共25行。
; 下标从0开始,所以0x18=24,0x4f=79
int 10h ; int 10h
; 输出字符串:MBR
mov byte [gs:0x00],'1'
mov byte [gs:0x01],0xA4
mov byte [gs:0x02],' '
mov byte [gs:0x03],0xA4
mov byte [gs:0x04],'M'
mov byte [gs:0x05],0xA4 ;A表示绿色背景闪烁,4表示前景色为红色
mov byte [gs:0x06],'B'
mov byte [gs:0x07],0xA4
mov byte [gs:0x08],'R'
mov byte [gs:0x09],0xA4
mov eax,LOADER_START_SECTOR ; 起始扇区lba地址
mov bx,LOADER_BASE_ADDR ; 写入的地址
mov cx,1 ; 待读入的扇区数
call rd_disk_m_16 ; 以下读取程序的起始部分(一个扇区)
jmp LOADER_BASE_ADDR
;-------------------------------------------------------------------------------
;功能:读取硬盘n个扇区
rd_disk_m_16:
;-------------------------------------------------------------------------------
; eax=LBA扇区号
; ebx=将数据写入的内存地址
; ecx=读入的扇区数
mov esi,eax ;备份eax
mov di,cx ;备份cx
;读写硬盘:
;第1步:设置要读取的扇区数
mov dx,0x1f2
mov al,cl
out dx,al ;读取的扇区数
mov eax,esi ;恢复ax
;第2步:将LBA地址存入0x1f3 ~ 0x1f6
;LBA地址7~0位写入端口0x1f3
mov dx,0x1f3
out dx,al
;LBA地址15~8位写入端口0x1f4
mov cl,8
shr eax,cl
mov dx,0x1f4
out dx,al
;LBA地址23~16位写入端口0x1f5
shr eax,cl
mov dx,0x1f5
out dx,al
shr eax,cl
and al,0x0f ;lba第24~27位
or al,0xe0 ; 设置7~4位为1110,表示lba模式
mov dx,0x1f6
out dx,al
;第3步:向0x1f7端口写入读命令,0x20
mov dx,0x1f7
mov al,0x20
out dx,al
;第4步:检测硬盘状态
.not_ready:
;同一端口,写时表示写入命令字,读时表示读入硬盘状态
nop
in al,dx
and al,0x88 ;第4位为1表示硬盘控制器已准备好数据传输,第7位为1表示硬盘忙
cmp al,0x08
jnz .not_ready ;若未准备好,继续等。
;第5步:从0x1f0端口读数据
mov ax, di
mov dx, 256
mul dx
mov cx, ax ; di为要读取的扇区数,一个扇区有512字节,每次读入一个字,
; 共需di*512/2次,所以di*256
mov dx, 0x1f0
.go_on_read:
in ax,dx
mov [bx],ax
add bx,2
loop .go_on_read
ret
times 510-($-$$) db 0
db 0x55,0xaa
; 文件位置 ./include/boot.inc
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
运行这个代码, 就会读0x2(LBA)扇区, 并加载到0x900, 最后跳转到0x900, 结束 mbr, 进入 loader
因为我们还没 loader,所以随便写一个来测试我们的 mbr
; 文件位置 ./loader.S
; <<操作系统真象还原>>代码
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
; 输出背景色绿色,前景色红色,并且跳动的字符串"1 MBR"
mov byte [gs:0x00],'2'
mov byte [gs:0x01],0xA4 ; A表示绿色背景闪烁,4表示前景色为红色
mov byte [gs:0x02],' '
mov byte [gs:0x03],0xA4
mov byte [gs:0x04],'L'
mov byte [gs:0x05],0xA4
mov byte [gs:0x06],'O'
mov byte [gs:0x07],0xA4
mov byte [gs:0x08],'A'
mov byte [gs:0x09],0xA4
mov byte [gs:0x0a],'D'
mov byte [gs:0x0b],0xA4
mov byte [gs:0x0c],'E'
mov byte [gs:0x0d],0xA4
mov byte [gs:0x0e],'R'
mov byte [gs:0x0f],0xA4
jmp $ ; 通过死循环使程序悬停在此
编译:
nasm -I ./include mbr.S
nasm -I ./include loader.S
# -I 指定 inc 包括目录
dd if=./mbr of=/home/fengzi/WorkSpace/Study_OS/c.img bs=512 count=1 seek=0 conv=notrunc
# seek可以指扇区序号
dd if=./loader of=/home/fengzi/WorkSpace/Study_OS/c.img bs=512 count=1 seek=2 conv=notrunc
# 这里是吧 loader 储存到第 2 个扇区
运行图: