操作系统真象还原之实验三:让mbr使用硬盘
对应书中129页 3.6.1 让mbr使用硬盘
1.相关基础知识总结
1.1 硬盘与硬盘控制器
硬盘控制器是针对硬盘的I/O接口,与显卡与显示器不同的是,显卡与显示器是分开的,硬盘与硬盘控制器是连接在一起的。
硬盘:
0盘0道1扇区(CHS方式从1开始编号)与0盘0道0扇区(LBA方式从0开始编号)
硬盘的并行和串行接口:
以前的接口为并行,叫做PATA(Parallel ATA)。现在刚出道的接口为串行接口,叫做SATA(serial ATA)
连接硬盘与PATA接口的线缆成为IDE线,一根IDE线可以插两块硬盘,一个叫主盘,一个叫从盘。
一个主板有两个IDE插槽,可以插4块支持PATA的硬盘。其中IDE0又叫做Primary通道,IDE1又叫做Secondary通道。
即使现在的SATA也支持PATA编程。向上兼容是计算机源源不断向上发展的根基。
所以后面的代码按下图的PATA接口中寄存器端口号来也没啥问题。
硬盘控制器的端口寄存器:
功能的描述太多了见书P126
在SATA技术中,每个SATA接口都配备了一套独立的控制和状态寄存器集合。这些寄存器集合广义上可以定义为命令/控制寄存器、状态寄存器、以及用于配置和管理数据传输的其他寄存器,它们通过内存映射I/O(MMIO)的方式被系统访问,而非传统PATA接口采用的I/O端口地址。
因此,当主板提供了多个SATA接口时,实质上意味着系统能够通过这些独立的SATA接口内部的SATA端口集合(上述广义的寄存器集合),各自控制和通信与之相连的SATA设备,从而实现对多个硬盘的并行读写操作。每个SATA接口上的寄存器集合确保了对单一连接设备的全面管理,而无需像PATA那样通过主通道和副通道的概念来区分不同的设备连接。
数据传送方式
(1)无条件传送方式
(2)查询传送方式
(3)中断传送方式
(4)DMA传送方式
(5)I/O处理机传送方式
本次实验的代码采用的是第二种软件查询的方式。
2.实验记录
2.1实验目的
1.编写loader.s,该程序功能是在显示屏上打印“2 LOADER”字段,然后将该文件编译后使用dd命令刻入0盘0道2扇区(LBA方式)
2.改写mbr.s,该程序功能是可以将硬盘中0盘0道2扇区的内容写入内存,然后跳转执行写好的loader.s。
3.编写boot.inc,里面写配置信息
2.2实验代码
2.2.1 mbr.s
; mbr.S
; 主引导程序
; --------------------------------------------------
%include "boot.inc";
;LOADER_BASE_ADDR equ 0x900 将loader加载到内存0x900
;LOADER_START_SECTOR equ 0x2 loader位于磁盘第2块扇区
SECTION MBR vstart=0x7c00 ; 把起始地址编译为 0x7c00
mov ax, cs ; cs 代码段寄存器
mov ds, ax ; dx 数据段寄存器
mov es, ax ; es 附加段寄存器
mov ss, ax ; ss 堆栈段寄存器
mov fs, ax ; fs 80386 后添加的寄存器,无全称
mov sp, 0x7c00 ; sp 堆栈指针寄存器
mov ax, 0xb800;
mov gs, ax;
; 清屏
; --------------------------------------------------
; INT 0x10 功能号: 0x06 功能描述:上卷窗口
; --------------------------------------------------
; 输入:
; AH 功能号 = 0x06
; AL = 上卷的行数(如果为0,表示全部)
; BH = 上卷行属性
; (CL, CH) = 窗口左上角的 (X, Y) 位置
; (DL, DH) = 窗口右下角的 (X, Y) 位置
; 无返回值:
mov ax, 0x600
mov bx, 0x700
mov cx, 0
mov dx, 0x184f ; 右下角: (80, 25)
; VGA 文本模式种,一行只能容纳 80 个字符,共 25 行
; 下标从 0 开始,所以 0x18=24, 0x4f=79
int 0x10 ; int 0x10
;;;;;下面代码是新增功能;;;;;;;;;;;;;;;;;;;;
mov eax, LOADER_START_SECTOR; 磁盘中loader的LBA地址
mov bx, LOADER_BASE_ADDR; loader加入内存的起始地址
mov cx, 1; 待读入内存的扇区数
call rd_disk_m_16;
jmp LOADER_BASE_ADDR;
;--------------------------------------------------------------
;功能:读取硬盘n个扇区
rd_disk_m_16:
;----------------------------------------------------------
mov esi, eax; 备份eax al在in/out指令会被使用
mov di, cx; 备份cx cl会在接下来代码中频繁使用
;读写硬盘:
;第一步:设置要读取的扇区数 1
mov dx, 0x1f2; 配置的硬盘是ata0-master 是Primary通道 主盘
;sector count寄存器是0x1f2端口
mov al, cl;
out dx, al; 从内存把扇区数1输出到端口号0x1f2;
mov eax, esi; 恢复eax
;第二步 将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;
;LBA地址24~27位写入端口0x1f6
shr eax, cl;
and al, 0x0f; 与运算al中低四位为LBA地址24~27位
or al, 0xe0; 或运算拼出0x1f6的高四位1110 第6位为1表示LBA
mov dx, 0x1f6;
out dx, al;
;第三步 向0x1f7端口写入读命令,0x20
mov dx, 0x1f7;
mov al, 0x20;
out dx, al; 命令 写入端口0x1f7后,硬盘立即开始工作,将数据放入硬
; 盘控制器的缓冲区
;第四步 检测硬盘状态,判断loader是否已经读入0x1f0端口中
.not_ready:
nop;
in al, dx; 将端口0x1f7的status写入al;
and al, 0x88; 获得status的第3位和第7位;
cmp al, 0x08; 与第3位相减作比较 会影响ZF CF PF
jnz .not_ready; ZF不等于0就跳,相当于循环等缓冲区中的数据准备好为止
;第5步 将0x1f0端口的数据搬向内存
;5.1 计算搬运次数
mov ax, di;
mov dx, 256; 0x1f0端口是16比特
mul dx; di*512字节/2字节=搬运次数 16位乘法乘积32位
;高16位在dx,低16位在ax;
mov cx, ax; dx=1 乘积高16位是0,故把低16位移入cx
;5.2 循环搬运至内存
mov dx, 0x1f0;
.go_on_read: ;我们的loader只有一个扇区512字节
in ax, dx; ;bx的寻址范围位64KB 65536字节
mov [bx], ax; 该循环不能加载大于64KB的程序于内存
add bx, 2;
loop .go_on_read; cx不等于0 就回到循环处继续搬
ret ;搬运完loader 回到LOADER_BASE_ADDR;
times 510-($-$$) db 0 ; 填充文件末尾的魔数 0xaa55 和当前位置之间的空间
; 保证编译后生成的文件大小为 512 字节(硬盘一个扇区的大小)
db 0x55, 0xaa
代码功能总结:代码第1、2、3步将LBA地址、扇区数放入相应I/O端口,再将读命令0x20放入Command端口,硬盘控制器就会自动将硬盘的数据读入Data端口,再存入硬盘控制器的缓冲区。
代码的第四步就是利用查询传输方式,这段代码一直循环,也就是一直占用cpu查询Status端口。直到规定好的硬盘数据全部读入缓冲区,Status第3位被置为1,程序才会继续执行第五步。
第五步就是将缓冲区数据以2个字节为单位循环先读入Data端口,再用in指令读入内存。
2.2.2 loader.s
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
mov byte [gs:0x00], '2';
mov byte [gs:0x01], 0xA4;
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 $;
2.2.3 boot.inc
;----------------------loader AND kernel-----------------------------
LOADER_BASE_ADDR equ 0x900 ;loader.s加载到内存地址0x900
LOADER_START_SECTOR equ 0x2 ;loader.s刻入硬盘0盘0道2扇区(LBA)
2.3实验记录
1.编译loader.s
nasm -o loader.bin loader.s
2.编译mbr.s
nasm -o mbr.bin mbr.s
3.将mbr.bin刻入第0扇区
dd if=/home/Seven/bochs2.68/bin/mbr.bin of=/home/Seven/bochs2.68/bin/Seven.img bs=512 count=1 seek=0 conv=notrunc
4.将loader.bin刻入第2扇区
dd if=/home/Seven/bochs2.68/bin/loader.bin of=/home/Seven/bochs2.68/bin/Seven.img bs=512 count=1 seek=2 conv=notrunc
5.模拟bochs
效果图
2.4实验结果
闪烁的2 loader