babyos (十) —— 通过IO端口读取硬盘扇区

来源:互联网 发布:天气预报js代码 编辑:程序博客网 时间:2024/05/21 08:03

注:以下代码为原创,若发现bug,万望指出,若有问题,欢迎交流,转载请指明出处。若能有助于一二访客,幸甚。

欢迎关注babyos 的成长历程和宝贝计划:https://github.com/guzhoudiaoke/babyos/wiki


上次实现了显示bitmap的功能,却只显示了几个按钮,很不爽,但引导软盘只有1.44M,实在装不下太大的图片。而要想做个桌面背景,需要读取800x600的图片。所以是时候让baby 学会读硬盘扇区了。当然只是通过IO端口读取硬盘扇区,而不会设计文件的概念。


1.硬盘长什么样

硬盘拆开盖,如下图所示:


2.物理结构

一个简单的物理模型:


3.磁头、磁道、柱面、扇区

1)磁头:如上图所示,它是硬盘中最贵的部分了,读写操作就靠它,它通过感应旋转的盘片上磁场的变化来读取数据,通过改变盘片上的磁场来写入数据。磁头从0开始编号。
一个硬盘有很多个盘片(一个盘片有两个盘面),它们垂直排列成圆柱,所以也有很多个磁头来读写不同的盘面。
2)磁道:硬盘转动时磁头是不动的,动的是盘片。则磁头会在盘片上划出一个个圆形轨迹,这些轨迹就是磁道。磁道由外向内编号,编号从0开始。
3)柱面:硬盘有很多个盘片,从上到下排成柱状,不同盘片,相同磁道,组成的就叫柱面(cylinder)。


柱面从0开始编号,柱面号即磁道号。
4)扇区:磁盘上每个磁道被等分成若干个弧段,这些弧段就是扇区。
一个扇区通常512byte,扇区从1开始编号。


4.寻址

如何找到我们想要的数据呢?即如何在硬盘上找到任意一个物理地址。
1)CHS模式(Cylinder/Head/Sector)
就是给定柱面号、磁头号、扇区号。柱面号给定在哪一个圆环上,磁头号指定了在哪一层,扇区号指定了圆上的位置,于是就定位到了一个准确的扇区了。
2)LBA(Logical Block Addressing,逻辑块寻址)
就是只给一个逻辑号码,根据硬盘的柱面数、每个柱面的磁头数、每个磁道的扇区数来计算柱面号、磁头号、扇区号。
编号方法:
按照柱面、磁头、扇区顺序来编,即编完0号柱面0号磁头所在磁道的若干扇区后,再编0号柱面1号磁头所在磁道的所以扇区,编完0号柱面的所有磁头后再编1号柱面。

3)相互转换
LBA = (柱面号 * 一个柱面的磁头数 + 磁头号) * 一个磁道上的扇区数 + (扇区号-1)

柱面号 = LBA / (一个柱面的磁头数 * 每个磁道扇区数)
令   x = LBA % (一个柱面的磁头数 * 每个磁道扇区数)
磁头号 = x / 每个磁道上的扇区数
扇区号 = x % 每个磁道上的扇区数 + 1

5.硬盘操作

CPU与外设、存储器的连接和数据交换都需要通过接口设备来实现。
每个连接到I/O总线上的设备都由自己的I/O地址集,即所谓的I/O端口(I/O port)。
每个设备的I/O端口都被组织成一组专用的寄存器,CPU可给控制寄存器发命令对设备进行控制、从状态寄存器读取设备状态、可以向输出寄存器写入数据来把数据输出到设备、可通过读取输入寄存器的内容来从设备取得数据。
总之就是通过读写端口来控制设备。

一个普通的PC主板上通常有两个IDE口,分别对应两个IDE通道:primary和secondary有时也成IDE0和IDE1。
每个IDE通道又能连接两个设备,称为主设备(Master)和从设备(Slave),对不同的IDE通道的访问是通过I/O端口来区分的。
IDE(integrated drive electronics)即电子集成驱动器,主要接硬盘和光驱。
接到主设备上的硬盘称为0号硬盘。

与0号硬盘有关的I/O端口:
1F0H   0号硬盘数据寄存器
1F1H   0号硬盘错误寄存器(读时)、0号硬盘Features寄存器(写时)
1F2H   0号硬盘数据扇区计数

1F3H   0号硬盘扇区数
1F4H   0号硬盘柱面(低字节)
1F5H   0号硬盘柱面(高字节)
1F6H   0号硬盘驱动器/磁头寄存器

1F7H   0号硬盘状态寄存器(读时)、0号硬盘命令寄存器(写时)

注:下图来自于渊《Orange‘s 一个操作系统的实现》,他使用的是LBA方式,babyos暂时使用CHS方式,故bit0~bit3表示磁头号,bit4为驱动器号为0,bit6为0,表示使用CHS方式。

/*|  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |+-----+-----+-----+-----+-----+-----+-----+-----+|  1  |  L  |  1  | DRV | HS3 | HS2 | HS1 | HS0 |+-----+-----+-----+-----+-----+-----+-----+-----+|           |   \_____________________/|           |              ||           |              `------------ If L=0, Head Select.        |           |                                   These four bits select the head number.        |           |                                   HS0 is the least significant.        |           |                            If L=1, HS0 through HS3 contain bit 24-27 of the LBA.        |           `--------------------------- Drive. When DRV=0, drive 0 (master) is selected.         |                                               When DRV=1, drive 1 (slave) is selected.        `--------------------------------------- LBA mode. This bit selects the mode of operation.                                                        When L=0, addressing is by 'CHS' mode.                                                        When L=1, addressing is by 'LBA' mode.*/

即:
可以从端口0x1F0读取数据;
若发生错误可以从0x1F1读取错误;
若要从硬盘读数据可以从0x1F2指定读取的扇区数,0x1F3、0x1F4、0x1F5、0x1F6指定CHS(也可以是LBA,此处暂时不研究)
可以从0x1F7读取硬盘状态或向硬盘发送命令。

接口控制方式:
1)程序查询方式:CPU通过I/O指令询问指定外设当前状态,如果外设准备就绪,进行操作,否则CPU等待,循环查询。
2)中断处理方式:外设数据交换准备就绪,向CPU提出服务请求。
3)DMA(直接存储器存取):采用一个专门的控制器来控制内存与外设直接的数据交流,无须CPU介入。

下面将要使用的方式是程序查询方式,因为babyos 还没有实现中断。

6.读取若干个扇区

现在只学习最简单的硬盘操作——从硬盘读取若干个扇区,其他功能以后学习文件系统时再研究。
步骤:
1)通过状态寄存器查询硬盘状态,看是否空闲,若忙,则等待
2)把读取扇区的个数、CHS写入相应端口
3)通过命令寄存器向硬盘发送读命令
4)从数据寄存器读取数据

注:
babyos目前只想从硬盘读若干个扇区,不想实现高级的硬盘操作功能和文件系统;
只考虑主硬盘;
暂时使用CHS方式,但会实现一个LBA到CHS的转换方法;

/*************************************************************************> File:harddisk.c> Describe: 实现基本硬盘操作功能> Author:孤舟钓客> Mail:guzhoudiaoke@126.com > Time:2013年01月06日 星期日 17时40分32秒 ************************************************************************/#include <harddisk.h>#include <io.h>#include <font.h>#include <graphics.h>BOOL harddisk_read(u32 lba, u32 sects_to_read, u8* buffer){u32 cylinder_no, head_no, sect_no, temp;u32 num_of_dwords;cylinder_no = lba / (HD0_HEAD_PER_CYLINDER * HD0_SECT_PER_TRACK);temp= lba % (HD0_HEAD_PER_CYLINDER * HD0_SECT_PER_TRACK);head_no= temp / HD0_SECT_PER_TRACK;sect_no= temp % HD0_SECT_PER_TRACK + 1;/* 检查硬盘是否忙,忙则等待 */while ((inb(HD_PORT_STATUS) & 0x80) != 0);/* 设置读取的扇区数和CHS,   HD_PORT_DRIVE_HEAD端口bit7、bit5需要为1,bit6为0时bit0~bit3表示磁头号,   bit4为驱动器号,0 表示HD0,故下面head_no要或操作10100000即0xa0 */outb(sects_to_read, HD_PORT_SECT_COUNT);outb(sect_no, HD_PORT_SECT_NO);outb(cylinder_no, HD_PORT_CYLINDER_LOW);cylinder_no >>= 8;outb((cylinder_no), HD_PORT_CYLINDER_HIGH);head_no |= 0xa0;outb((head_no), HD_PORT_DRIVE_HEAD);/* 发送读命令 */outb(HD_CMD_READ, HD_PORT_COMMAND);num_of_dwords = (sects_to_read << 7);/* 从HD_PORT_DATA读取数据,每个扇区512字节,即sects_to_read << 7个双字 */insl(HD_PORT_DATA, buffer, num_of_dwords);return TRUE;}