硬盘读写的基本单位是扇区。就是说,要读就至少读一个扇区,要写就至少写一个扇区,不可能仅读写一个扇区中的几个字节。这样一来,就使得主机和硬盘之间的数据交换是成块的,所以硬盘是典型的块设备。
从硬盘读写数据,最经典的方式是向硬盘控制器分别发送磁头号、柱面号和扇区号(扇区在某个柱面上的编号),这称为 CHS 模式。这种方法最原始,最自然,也最容易理解。
最早的逻辑扇区编址方法是LBA28,使用 28 个比特来表示逻辑扇区号,从逻辑扇区 0x0000000 到 0xFFFFFFF,共可以表示 2 ^28=268435456个扇区。每个扇区有512 字节,所以 LBA28 可以管理 128 GB 的硬盘。
但是硬盘技术发展得非常快,最新的硬盘已经达到几百个吉字节的容量,LBA28 已经落后了。在这种情况下,业界又共同推出了 LBA48,采用 48 个比特来表示逻辑扇区号。如此一来,就可以管理131072 TB 的硬盘容量了。在这里我们采用将采用 LBA28 来访问硬盘。
第 1 步:
设置要读取的扇区数量。这个数值要写入 0x1f2 端口。这是个 8 位端口,因此每次只能读写 255 个扇区:
mov dx,0x1f2
out dx,al
注意:如果写入的值为 0,则表示要读取 256 个扇区。每读一个扇区,这个数值就减一。因此,如果在读写过程中发生错误,该端口包含着尚未读取的扇区数。
第 2 步:
设置起始 LBA 扇区号。扇区的读写是连续的,因此只需要给出第一个扇区的编号就可以了。28 位的扇区号太长,需要将其分成 4 段,分别写入端口 0x1f3、0x1f4、0x1f5 和 0x1f6 号端口。其中,0x1f3 号端口存放的是 0~7 位;0x1f4 号端口存放的是 8~15 位;0x1f5 号端口存放的是 16~23 位,最后 4 位在 0x1f6 号端口。假定我们要读写的起始逻辑扇区号为 0x02,可编写代码
如下:
mov dx,0x1f3
mov al,0x02
out dx,al ;LBA 地址 7~0
inc dx ;0x1f4
mov al,0x00
out dx,al ;LBA 地址 15~8
inc dx ;0x1f5
out dx,al ;LBA 地址 23~16
inc dx ;0x1f6
mov al,0xe0 ;LBA模式,主硬盘,以及 LBA 地址 27~24
out dx,al
第 3 步:
向端口 0x1f7 写入 0x20,请求硬盘读。这也是一个8 位端口:
mov dx,0x1f7
mov al,0x20 ;读命令
out dx,al
第 4 步:
等待读写操作完成。端口0x1f7 既是命令端口,又是状态端口。在通过这个端口发送读写命令之后,硬盘就忙乎开了。如图 8-12 所示,在它内部操作期间,它将 0x1f7 端口的第 7 位置“1”,表明自己很忙。一旦硬盘系统准备就绪,它再将此位清零,说明自己已经忙完了,同时将第 3位置“1”,意思是准备好了,请求主机发送或者接收数据(图 8-12)。完成这一步的典型代码如下:
mov dx,0x1f7
.waits:
in al,dx
and al,0x88
cmp al,0x08
jnz .waits
第 5 步:
连续取出数据。0x1f0 是硬盘接口的数据端口,而且还是一个 16 位端口。一旦硬盘控制器空闲,且准备就绪,就可以连续从这个端口写入或者读取数据。下面的代码假定是从硬盘读一个扇区(512 字节,或者 256 字节),读取的数据存放到由段寄存器 DS 指定的数据段,偏移地址由寄存器 BX 指定:
mov cx,256 ;总共要读取的字数
mov dx,0x1f0
.readw:
in ax,dx
mov [bx],ax
add bx,2
loop .readw