您的位置:首页 > 其它

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

2013-01-07 23:05 323 查看
注:以下代码为原创,若发现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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: