硬盘读写的基本单位是扇区。就是说,要读就至少读一个扇区,要写就至少写一个扇区,不可能仅读写一个扇区中的几个字节。这样一来,就使得主机和硬盘之间的数据交换是成块的,所以硬盘是典型的块设备。

     从硬盘读写数据,最经典的方式是向硬盘控制器分别发送磁头号、柱面号和扇区号(扇区在某个柱面上的编号),这称为 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