- 硬盘控制器端口
- 常用硬盘操作方法
- 操纵硬盘
- 参考文献
写在前面:自制操作系统Gos 第二章第四篇:主要内容是如何操纵外设,如何操纵硬盘
关于硬盘的原理我在Linux —— 文件系统及相关操作命令其实是已经简单讲了一下了,这里就不再赘述了。
在上一篇中我们讲到了CPU和显示器交互的接口其实就是显存。那CPU和硬盘打交道也是同样的道理:硬盘控制器
硬盘控制器端口注:
硬盘控制器是专门驱动外部设备的模块电路,CPU只同它们交互,由它们将信息传递给外部设备
让硬盘工作,我们需要通过读写硬盘控制器的端口。下面列出了部分的端口,也是我们开发Gos需要用到的端口:
| Primary通道端口 | Secondary通道端口 | 读操作时用途 | 写操作时用途 |
|---|
首先是命令寄存器:Command register
| 0x1F0 | 0x170 | data | data |
|---|---|---|---|
| 0x1F1 | 0x171 | error | features |
| 0x1F2 | 0x172 | sector count | sector count |
| 0x1F3 | 0x173 | LBA low | LBA low |
| 0x1F4 | 0x174 | LBA mid | LBA mid |
| 0x1F5 | 0x175 | LBA high | LBA high |
| 0x1F6 | 0x176 | device | device |
| 0x1F7 | 0x177 | status | command |
除此之外是控制寄存器:Control register
| 0x3F6 | 0x376 | alternate status | device control |
|---|
注:
电脑主板上有两个硬盘串行接口(STAT),每个接口可以插一块硬盘。其中第一个STAT被称之为Primary通道,其连接主盘master;第二个STAT称之为Secondary通道,其连接从盘slave
但是这里要注意一个问题:端口是按照通道给出的
也就是说:一个通道的主、从两块硬盘都用这些端口号。想要操作某通道所对应的某块硬盘,直接单独指定device寄存器的第四位(硬盘标志位)为1就可以了。
其中命令寄存器Command register 用于向硬盘驱动器写入命令或者从硬盘控制器获得硬盘状态
控制寄存器Control register 用于控制硬盘的工作状态。
下面我们具体讲一下这些寄存器的作用:
- data:负责管理数据。在读硬盘时,硬盘准备好的数据后,硬盘控制器就将其放在内存的缓冲区中,不断读此寄存器便是读出缓冲区中的全部数据;写硬盘时,我们要把数据源源不断地输送到此端口,之后便存入缓冲区,之后再写入相应的扇区。
- error:当0x171或者0x1F1读硬盘失败的时候才有用,里面会记录着失败的信息。其中尚未读取的扇区在sector count寄存器中。在写硬盘的时候,其则被称之为feature寄存器。
- sector count:用来指定待读取或待写入的扇区数。硬盘每完成一个扇区,就会将此寄存器的值-1.如果失败,那么其就存储着尚未读取的扇区数
- LBA:逻辑块地址(Logical block address),一共有两种:第一种时LBA28,用28为来描述一个扇区的地址。可以这次228*512=128GB大小的空间,Gos用LBA28就可以了;第二种是LBA48,顾名思义,其可以支持128PB的空间
- LBA low:存储28位地址的0~7位
- LBA middle:存储28位地址的8~15位
- LBA high:存储28位地址和的16~23位
- device:0~3位用来存储28位地址的剩下4位,第4位表示是主盘还是从盘,0表示主盘,1代表从盘。第六位表示是否开启LBA,1代表启用。第五位和第七位表示MBS位。
- status:用来给出给出硬盘的状态信息。第0位表示error位;第三位表示data request,如果此位为1,表示数据准备好了;第6位表示drdy,表示硬盘就绪;第七位是BSY,表示硬盘是否繁忙。
注:
在Gos中,我们主要使用三个命令:
identify:0xEC,即硬盘识别
read sector:0x20,即读扇区
write sector:0x30,即写扇区
下面是device status两个寄存器的示意图:
硬盘的指令很多,用法也有很多。有的直接往command寄存器中写,有的则是在feature寄存器中写入参数,可谓是多种多样。但是不管是哪种写法,command寄存器都是最后写。因为其一旦一开始就写,那么整个硬盘就轰隆隆的启动了。
而我们的操作顺序如下:
- 先选择通道,往该通道的sector count寄存器 写入待操作的扇区数
- 往该通道上的三个LBA寄存器写入扇区起始地址的低24位
- 往device寄存器写入剩下四位,之后置第6位为1;设置第4位,选择要操作的硬盘
- 往该通道的command寄存器写入操作命令
- 读取该通道上的status寄存器,判断硬盘工作是否完成
- 如果是读硬盘,进入下一个步骤。否则,完成。
- 将硬盘数据读出
操纵硬盘注:
我们使用的读取方式是以下两种结合:
无条件传送方式:数据随时随地准备好,直接拿来吧你
查询传送方式:取之前先查一查数据准备好了没有,然后再拿来吧你
上硬菜咯!
# 文件 mbr.S 中的内容
; MBR引导程序,从BIOS接过权力,交接给loader内核加载器
;*************************
%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 功能号:0x60 功能描述:上卷窗口
; 输入:
; 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,0x184f ;右下角(80,25)
;VAG文本模式中,一行只能容纳80个字符,总共25行
;下标从0开始,所以0x18=24,0x4f=79
int 10h
;*************************
;输出字符串:hello,Gos!
mov byte [gs:0x00],'h'
mov byte [gs:0x01],0x0F ;黑底亮白不闪烁
mov byte [gs:0x02],'e'
mov byte [gs:0x03],0x0F
mov byte [gs:0x04],'l'
mov byte [gs:0x05],0x0F
mov byte [gs:0x06],'l'
mov byte [gs:0x07],0x0F
mov byte [gs:0x08],'o'
mov byte [gs:0x09],0x0F
mov byte [gs:0x0a],','
mov byte [gs:0x0b],0x0F
mov byte [gs:0x0c],'G'
mov byte [gs:0x0d],0x0F
mov byte [gs:0x0e],'o'
mov byte [gs:0x0f],0x0F
mov byte [gs:0x10],'s'
mov byte [gs:0x11],0x0F
mov byte [gs:0x12],'!'
mov byte [gs:0x13],0x0F
;*************************
;初始化磁盘信息
mov eax,LOADER_START_SECTOR ;起始扇区LBA地址
mov bx,LOADER_base_ADDR ;写入的地址
mov cx,1 ;待读入的扇区数
call read_disk_m_16 ;调用读取程序起始部分的函数
jmp LOADER_base_ADDR
;*************************
;读取磁盘的n个扇区
read_disk_m_16:
;eax=LBA扇区号
;bx=将数据写入的内存地址
;cx=读入的扇区数
mov esi,eax ;备份eax
mov di,cx ;备份cx
;读写硬盘
;1.设置要读取的扇区数量
mov dx,0x1f2
mov al,cl
out dx,al ;读取的扇区数
mov eax,esi ;恢复eax
;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 ;第3位为1表示硬盘控制器已经准备号数据传输了
;第7位为1表示硬盘忙
cmp al,0x08
jnz .not_ready ;若未准备号,继续等
;5.从0x1f0端口读数据
mov ax,di
mov dx,256 ;一个扇区512字节,1字=2字节,所以时256
mul dx
mov cx,ax
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
可以看到,我们在这里包含了一个头文件boot.inc,这个文件中的内容很简单,只是定义了两个常量:
# boot.inc 文件中的内容 LOADER_base_ADDR equ 0x900 LOADER_START_SECTOR equ 0x2 ;第二扇区
之后,我们开始编译这个文件:
# -I 指定头文件搜索路径 # boot.inc 文件在同级目录include下 nasm -I ../include/ mbr.bin mbr.S
编译成功之后,会在目录中生成mbr.bin文件:
之后,我们使用dd工具将其写入我们之前创建好的磁盘hd60.img中:
# 记得换成自己的路径 sudo dd if=./mbr.bin of=/bochs/bo_tmp/bin/hd60M.img bs=512 count=1 conv=notrunc
然后,我们转到bochs的bin目录下,使用以下命令开启bochs:
sudo ./bochs -f boch.conf
进入到如下的界面了,默认选项是6,我们直接回车就好了:
之后,在bochs模拟的机器上便会打印出:hello Gos!(虽然这个和操纵硬盘没有关系,硬盘操纵是为了我们之后进入保护模式做铺垫用的)
[1] 操作系统真相还原 3.6 让MBR使用硬盘



