硬盘的读写
个人计算机中的 PATA/SATA 接口,每个 PATA 和 SATA 接口分配了 8个端口。但是,ICH 芯片内部通常集成了两个 PATA/SATA 接口,分别是主硬盘接口和副硬盘接口。这样一来, 主硬盘接口分配的端口号是 0x1f0~0x1f7,副硬盘接口分配的端口号是 0x170~0x177。
硬盘读写的基本单位是扇区。就是说,至少读取或写入一个扇区,而不能仅读写一个扇区中的几个字节。
最早的逻辑扇区编址方法是LBA28,使用 28 个比特来表示逻辑扇区号,从逻辑扇区 0x0000000 到 0xFFFFFFF,共可以表示 2^28=268435456 个扇区。每个扇区有 512 字节,所以 LBA28 可以管理 128 GB 的硬盘。
下面采用LBA28访问硬盘。
1.设置要读取的扇区数量 。这个数值要写入0x1f2端口。这是一个8位端口,因此每次只能读写255个扇区:
mov dx, 0x1f2 mov al, 0x01 ;1个扇区 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
在现行的体系下,每个 PATA/SATA 接口允许挂接两块硬盘,分别是主盘(Master)和从盘(Slave)。 0x1f6 端口的低 4 位用于存放逻辑扇区号的 24~27位,第 4 位用于指示硬盘号,0 表示主盘,1 表示从盘。高 3 位是“111”,表示 LBA 模式。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
L | B | A | 硬盘 | 逻辑 | 扇 | 区 | 号 |
3.向端口0x1f7写入0x20,请求硬盘读。 这也是一个8位端口:
mov dx, 0x1f7 mov al, 0x20 ;读命令 out dx, al
4.等待读写操作完成。 端口0x1f7既是命令端口,又是状态端口。在其操作期间,0x1f7端口的第7位置为 1
一旦硬盘准备就绪,它就会将此位清零,同时将第三位设为 1
。准备发送或接受数据。实现代码如下:
mov dx, 0x1f7 .waits: in al, dx and al, 0x88 ;寄存器AL中除第七位和第三位,其他全部清0 cmp al, 0x08 ;此时,如果寄存器AL中的二进制数是0x08,说明可以退出等待状态 jnz .waits ;不忙,且硬盘已准备好数据传送
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
BSY |
| RQ |
| RR |
BSY位为1说明忙,DRQ位为1说明已准备好和主机交换数据,ERR为1表明前一个命令执行错误,具体原因可访问端口0x1f1
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
最后,0x1f1 端口是错误寄存器,包含硬盘驱动器最后一次执行命令后的状态(错误原因)。