在计算机系统的底层编程中,直接通过汇编指令操作磁盘是一项关键技能,尤其是在操作系统开发或引导加载程序编写中。本文将系统地介绍如何使用 x86 汇编指令来实现磁盘的读写操作,并解释相关的技术细节和注意事项。

硬盘控制器与I/O端口

在 x86 架构的计算机中,磁盘通常通过硬盘控制器与 CPU 进行通信。对于传统的 IDE(Integrated Drive Electronics)硬盘,硬盘控制器通过一组 I/O 端口与 CPU 进行交互。CPU 可以使用特定的 I/O 指令来读取和写入这些端口,以控制硬盘的操作。

常见的 I/O 端口地址和其功能如下:

  • 0x1F0: 数据寄存器,用于读写磁盘数据。
  • 0x1F1: 错误寄存器(读)或功能寄存器(写)。
  • 0x1F2: 扇区计数寄存器,指定要操作的扇区数。
  • 0x1F3: 扇区号寄存器,指定要操作的扇区号(LBA模式下)。
  • 0x1F4: 柱面低字节寄存器,指定扇区所在的柱面号低8位。
  • 0x1F5: 柱面高字节寄存器,指定扇区所在的柱面号高8位。
  • 0x1F6: 驱动器/磁头寄存器,用于选择驱动器和磁头。
  • 0x1F7: 状态寄存器(读)或命令寄存器(写),用于检查硬盘状态或发送命令。

通过汇编指令读写磁盘扇区

磁盘的读写操作主要包括以下几个步骤:准备数据、设置控制器寄存器、发送命令、传输数据,以及检查操作状态。

读取磁盘扇区

读取磁盘扇区的数据是通过向磁盘控制器发送读取命令,并从数据寄存器中读取指定数量的数据。以下是读取一个扇区数据的汇编代码示例:

  1. ; 设置读取的扇区数
  2. mov dx, 0x1F2
  3. mov al, 1 ; 读取1个扇区
  4. out dx, al
  5. ; 设置要读取的扇区号(LBA地址)
  6. mov dx, 0x1F3
  7. mov al, 1 ; LBA 地址低8位(第1扇区)
  8. out dx, al
  9. ; 设置柱面号(LBA地址的8-23位)
  10. mov dx, 0x1F4
  11. mov al, 0 ; LBA 地址的8-15
  12. out dx, al
  13. mov dx, 0x1F5
  14. mov al, 0 ; LBA 地址的16-23
  15. out dx, al
  16. ; 设置驱动器和LBA的高4
  17. mov dx, 0x1F6
  18. mov al, 0xE0 ; 主驱动器,LBA 地址的24-27
  19. out dx, al
  20. ; 发送读命令
  21. mov dx, 0x1F7
  22. mov al, 0x20 ; 0x20 是读取命令
  23. out dx, al
  24. ; 等待磁盘准备就绪
  25. .wait_ready:
  26. in al, dx
  27. test al, 0x80 ; 检查 BSY 位(位7
  28. jnz .wait_ready
  29. ; 读取数据
  30. mov dx, 0x1F0 ; 数据端口
  31. mov cx, 256 ; 每个扇区512字节 / 2 = 256次读取
  32. rep insw ; 从端口读取数据到内存
写入磁盘扇区

写入数据到磁盘扇区与读取操作类似,不同之处在于需要将数据写入到数据寄存器,并发送写入命令。以下是写入一个扇区数据的汇编代码示例:

  1. ; 假设数据在内存中的位置为 ds:si,大小为512字节
  2. ; 设置写入的扇区数
  3. mov dx, 0x1F2
  4. mov al, 1 ; 写入1个扇区
  5. out dx, al
  6. ; 设置要写入的扇区号(LBA地址)
  7. mov dx, 0x1F3
  8. mov al, 1 ; LBA 地址低8位(第1扇区)
  9. out dx, al
  10. ; 设置柱面号(LBA地址的8-23位)
  11. mov dx, 0x1F4
  12. mov al, 0 ; LBA 地址的8-15
  13. out dx, al
  14. mov dx, 0x1F5
  15. mov al, 0 ; LBA 地址的16-23
  16. out dx, al
  17. ; 设置驱动器和LBA的高4
  18. mov dx, 0x1F6
  19. mov al, 0xE0 ; 主驱动器,LBA 地址的24-27
  20. out dx, al
  21. ; 发送写命令
  22. mov dx, 0x1F7
  23. mov al, 0x30 ; 0x30 是写入命令
  24. out dx, al
  25. ; 等待磁盘准备就绪
  26. .wait_ready:
  27. in al, dx
  28. test al, 0x80 ; 检查 BSY 位(位7
  29. jnz .wait_ready
  30. ; 写入数据
  31. mov dx, 0x1F0 ; 数据端口
  32. mov cx, 256 ; 每个扇区512字节 / 2 = 256次写入
  33. rep outsw ; 将数据写入端口 dx (1F0)
  34. ; 检查写入状态
  35. mov dx, 0x1F7
  36. in al, dx
  37. test al, 0x08 ; 检查 DRQ 位(位3
  38. jnz .write_success
  39. jmp .write_error
  40. .write_success:
  41. ; 写入成功后的处理
  42. hlt
  43. .write_error:
  44. ; 处理写入错误
  45. hlt

注意事项

  1. 硬件权限:直接操作硬盘控制器需要在操作系统的内核模式下执行,用户态程序通常无法直接访问硬件端口。

  2. 错误处理:在实际操作中,还需处理各种可能的错误状态,如命令执行失败、磁盘忙等。

  3. 现代硬件的兼容性:现代计算机通常使用 SATA 或 NVMe 接口,这些接口的操作方式与传统 IDE 不同,因此上述方法主要适用于学习或在特定环境中使用。

现代替代方案

在现代操作系统中,通常不会直接通过汇编语言和I/O端口与硬盘交互,而是使用更高级的系统调用来进行磁盘读写操作。例如:

  • Linux: 通过 read()write() 系统调用操作磁盘设备文件(如 /dev/sda)。
  • Windows: 使用 ReadFile()WriteFile() API 操作磁盘设备。

这些高级 API 允许开发者在不直接管理硬件的情况下执行磁盘操作,提供了更高的抽象层次和更强的安全性。

总结

通过汇编指令直接操作磁盘是一项基础而重要的技能,特别是在操作系统开发中。本文介绍了如何通过 x86 汇编指令读取和写入磁盘扇区,详细讲解了相关 I/O 端口的配置和操作流程。虽然现代操作系统更倾向于使用高级 API 进行磁盘操作,但理解这些底层操作对于掌握计算机系统的工作原理是非常有价值的。