cpu指令如何读写硬盘
我们提到cpu的主要作用之一就是控制设备之间的数据交互。这其中自然也包括了硬盘。系统的所有数据基本都在硬盘中,所以知道怎么读写硬盘,对程序来说非常重要,所以我们先来探索下传说中的pio模式。
cpu要想直接访问设备里的数据,必须对设备存储空间进行编址。而硬盘数据数据太大,直接编址数据线成本太高,于是设计上在这类设备和总线之间加了一个控制器。这个控制器里有少量寄存器可以被cpu访问,而这个控制器又能控制硬盘驱动器读写数据,所以,cpu通过对硬盘控制器里的少量io寄存器的读写来控制对整个硬盘的读写。cpu告诉磁盘控制器读哪些地址的数据,磁盘控制器读好之后放入能被 cpu访问的数据寄存器中,再将这里的数据传给内存,这确实是个不错的办法。
首先,我们先找到硬盘控制器io端口地址的分配,0×170 — 0×177 IDE 硬盘控制器 1
0x1F0 — 0x1F7 IDE 硬盘控制器 0。更详细的说明如下:
端口号 读还是写 具体含义
1F0H 读/写 用来传送读/写的数据(其内容是正在传输的一个字节的数据)
1F1H 读 用来读取错误码
1F2H 读/写 用来放入要读写的扇区数量
1F3H 读/写 用来放入要读写的扇区号码
1F4H 读/写 用来存放读写柱面的低8位字节
1F5H 读/写 用来存放读写柱面的高2位字节(其高6位恒为0)
1F6H 读/写 用来存放要读/写的磁盘号及磁头号
第7位 恒为1
第6位 恒为0
第5位 恒为1
第4位 为0代表第一块硬盘、为1代表第二块硬盘
第3~0位 用来存放要读/写的磁头号
1f7H 读 用来存放读操作后的状态
第7位 控制器忙碌
第6位 磁盘驱动器准备好了
第5位 写入错误
第4位 搜索完成
第3位 为1时扇区缓冲区没有准备好
第2位 是否正确读取磁盘数据
第1位 磁盘每转一周将此位设为1,
第0位 之前的命令因发生错误而结束
写 该位端口为命令端口,用来发出指定命令
为50h 格式化磁道
为20h 尝试读取扇区
为21h 无须验证扇区是否准备好而直接读扇区
为22h 尝试读取长扇区(用于早期的硬盘,每扇可能不是512字节,而是128字节到1024之间的值)
为23h 无须验证扇区是否准备好而直接读长扇区
为30h 尝试写扇区
为31h 无须验证扇区是否准备好而直接写扇区
为32h 尝试写长扇区
为33h 无须验证扇区是否准备好而直接写长扇区
当然看完这个表你会发现,这种读写端口的方法其实是基于磁头、柱面、扇区的硬盘读写方法,也就是你要告诉硬盘控制器你要读硬盘里的哪个磁头下的哪个柱面里的哪个扇区,然后下达读命令,硬盘控制器将扇区内容缓存起来,然后依次将每16位的数据放入一个寄存器(1F0H)中, cpu就可以通过io指令来读取这个数据了。
我们可以再看一段示例代码:
mov dx,1f6h ; 要读入的磁盘号及磁头号
mov al,0a0h ;磁盘0,磁头0
out dx,al
mov dx,1f2h ;要读入的扇区数量
mov al,1 ;读一个扇区
out dx,al
mov dx,1f3h ;要读的扇区号
mov al,1 ;扇区号为1
out dx,al
mov dx,1f4h ;要读的柱面的低8位
mov al,0 ; 柱面低8位为0
out dx,al
mov dx,1f5h ; 柱面高2位
mov al,0 ; 柱面高2位为0(通过1F4H和1F5H端口我们可以确定
; 用来读的柱面号是0)
out dx,al
mov dx,1f7h ;命令端口
mov al,20h ; 尝试读取扇区
out dx,al
still_going:
in al,dx
test al,8 ;扇区缓冲是否准备好
jz still_going ;如果扇区缓冲没有准备好的话则跳转,直到准备好才向下执行。
mov cx,512/2 ;设置循环次数(512/2次)
mov di,offset buffer
mov dx,1f0h ;将要传输的一个字节的数据
rep insw ;传输数据
有成就感吧,一切尽在掌握中。其实我们可以直接调用bios里的int 13h中断,就可以直接读写扇区,只不过通过更底层的代码,我们可以对计算机的体系结构有一个更深刻的认识。知道cpu和其他设备是如何交互数据的。
注意到上面的指令rep insw,这里解释一下。INS指令可从DX指出的外设端口输入一个字节或字到由ES: DI指定的存储器中。 输入字节还是字,则由ES: DI目标操作数的属性决定,且根据方向标志位DF和目标操作数的属性来修改DI的值:若(DF)= 0,则DI加1(或加2);否则DI减1(或减2)。与INS指令相似,INSB 和INSW指令也分别从DX指出的外设端口输入一个字节或字到由ES: DI指定的存储器中,且根据方 向标志位DF和串操作的类型来修改DI的值。上述三种格式的指令均可加重复前缀REP,以实现连续的串操 。此时CX寄存器中的内容为重复操作的次数。
这里我们看到,cpu读取硬盘数据不是读入到cpu内部,而是读入到内存中,这可能是因为在cpu中根本就没有这么大空间来存储硬盘里的数据,所以在指令设计中就直接让其往内存中放,并且考虑到带宽限制,硬盘一个最小的数据单元–扇区就要分很多次才能读入内存,所以设计了想rep insw这样的指令来提高效率。磁盘控制器每次从扇区数据缓冲区中取出1个字长的数据放入1F0h(也称作pio数据端口)供insw指令获取,循环执行insw,直到读完整个缓冲区。
CPU读取磁盘数据的操作时序(CPU和磁盘控制器)
CPU
检查ready,确认磁盘控制器空闲,可以接受新的I/O命令;
将接收操作结果的内存单元的起始地址送入内存地址寄存器;
数据在内存中的起始扇区号送入数据起始地址寄存器,将待传送的数据的长度(以字节为单位)送入数据长度寄存器;
置命令/状态寄存器:
go置1;
r/w置1;(r/w置1表示读取数据,置0表示写入数据)
ready置0;
磁盘控制器
磁盘控制器定位到指定扇区(通过数据起始地址寄存器),然后将该扇区中的所有内容送入数据缓冲区;
从数据缓冲区中取一个字节的数据存入内存地址寄存器所指向的内存单元;内存地址寄存器的值+1;数据长度寄存器的值-1;
重复第二步,直到将数据缓冲区中的所有数据全部送入指定内存单元;如果此时数据长度寄存器的值非零,则重复上述第一步,继续读取下一个扇区,直到数据长度寄存器的值为零为止;
最后,所有的数据全部送入内存中,置ready为1,向CPU发送中断信号;
中断控制程序
中断控制程序在接收到中断信号后,唤醒等待IO结束的进程,该进程上台后,它所需要访问的所有的文件内容就已经出现在内存中了!