一.概述
前文(【操作系统】优化MBR程序:让MBR调用显存吧)中的MBR程序仅有512字节大小,完全不能将内核成功加载到内存并且运行,所以我们需要在另一个程序中完成初始化环境以及加载内核的任务,这个程序称之为Loader加载器,本文我们就将实现MBR与Loader的交接部分,从硬盘上把loader加载到内存,并且将接力棒交付给它。再次之前,我们需要明白怎么调用硬盘。
二.硬盘控制器端口概述
想要调用硬盘,则需要通过读写硬盘控制器的端口(关于硬盘的相关硬件知识,可以参考之前学过的一篇文章:【计算机组成原理】磁盘的基本结构,不在此赘述。),硬盘控制器属于IO端口,端口是位于IO控制器上的寄存器。关于硬盘的端口有很多,以下仅列出部分需要用到的端口:
首先端口被分为两组:
- Command Block registers:用于向硬盘驱动器写入命令或者从硬盘控制器中获取硬盘状态
- Control Block registers:用于控制硬盘工作状态
其次图上表格已经将大部分的指令进行精减,下面重点介绍Command Block registers组的寄存器,在讲解前需要说明一点,“端口用途”处将“读操作”和“写操作”分成了两个,比如“0X1F1”在读操作时为“ERROR”,而在写操作时则为“Features”,这两个其实都是同一个寄存器,只不过在读写操作时作用也不同,所以分成了两个不同的名称,并不是说它们是两个寄存器的意思。
(一)0X1F0和0X170为data寄存器,用于读取或者写入数据。在读取数据时,硬盘控制器将准备好的数据放入内部的数据缓冲区中,我们循环读取缓冲区数据即可。写入数据时,我们需要将数据循环存入数据缓冲区中,硬盘控制器监控到缓冲区有了数据,便将数据写入对应的扇区中去。
(二)端口0X171和0X1F1在读数据失败时,会将错误信息保存在寄存器中,所以叫ERROR寄存器。在写数据时,有些命令需要额外的参数,这些参数就保存在Feature寄存器中。
(三)端口0X1F2和0X172端口的Sector count寄存器用于指定待读取或写入的扇区数。每完成一个扇区,寄存器的值就会自减一,如果中间失败了,那么此时寄存器的值就是尚未完成的扇区。
(四)端口0X1F3~0X1F5用于存放扇区的起始地址。在说明这几个寄存器之前,我们先来补充一些扇区地址的知识,用来描述扇区的起始地址有两种方法:
一种是使用扇区的实际物理结构“柱面-磁头-扇区”来定义,叫Cylinder Head Sector,简称CHS,也就是我们在使用前都要先算出扇区是哪个盘面、哪个柱面上的。
另外一种是扇区从0开始依次递增编号,不考虑扇区的实际物理结构,这是一种逻辑上为扇区编制的方法,叫Logical Block Address逻辑块地址,简称LBA。本篇文章我们使用LBA方法来定义扇区的起始地址。
(五)LBA也有两种,一个是LBA28,另外一个是LBA48
LBA28用28bit来描述一个扇区的地址,所以最大的寻址范围是2的28次方,即268435456个扇区,按照每个扇区512个字节来计算。最大可以支持128GB。本篇文章我们使用LBA28方法来定义扇区的起始地址。
LBA48用48bit来描述一个扇区的地址,所以最大的寻址范围是2的48次方,即281474976710656个扇区,按照每个扇区512个字节来计算。最大可以支持131072TB,即128PB。
(六)补充完以上知识,我们继续回来端口0X1F3~0X1F5,LBA寄存器分成了三个(low、mid、high),它们都是8位宽度的,所以LOW、MID和HIGH分别存储地址的0~7位、8~15位以及16~23位,但是距离28位还差4位,剩余的4位就存在下一个寄存器device寄存器中。
(七)端口0X1F6为device寄存器,用以存储一些杂项数据,其中0~4位存储第六点中的LBA28的24~27位,其余的位数分别代表:
- 第4位用来指定通道上的主盘或从盘,0代表主盘,1代表从盘。
- 第6位设置是否启用LBA模式,1代表LBA模式,0代表CHS模式。
- 第5位和第7位默认为1,称之为MBS位,不用关注。
(八)0X1F7和0X177端口的status寄存器在进行读硬盘时用于存储硬盘的硬件信息,详细用法参考以下表格,没有写出来的可以不用关注,暂时用不到:
而在写硬盘时,command寄存器则用于存储让硬盘执行的命令,只要把命令写进此寄存器,硬盘就开始工作了,本篇文章中主要使用了三个命令:
- Identify:0xEC,即硬盘识别。
- Read sector:0x20,即读扇区。
- Write sector:0x30,即写扇区。
三.调用硬盘步骤
有了以上的基础,我们就可以知道每个端口的作用了,但是知道作用还不行,我们在调用时还需要有一定的顺序步骤,不然乱调用的话可是会出大问题的,比如command寄存器就是要最后才写入数据,因为command寄存器一旦有了数据,硬盘就会立马开始干活,其他寄存器不管有没有数据也开始干活,有问题报错就好了。所以我们就按照以下的步骤进行,可以确保万无一失:
- 选择通道,往该通道的sector count寄存器中写入代操作的扇区数。
- 往三个LBA寄存器写入扇区起始地址的低24位。
- 往device寄存器的低4位写入LBA的24~27位,设置第6位为1,设置第4位为实际操作的硬盘。
- 往command寄存器写入操作命令。
- 读取status寄存器,判断硬盘工作是否完成。
- 如果以上步骤是读硬盘。进入下一个步骤,否则结束进程。
- 将硬盘数据读出。
有了以上的理论支撑,我们就可以开始敲代码了,下篇我们再继续说明MBR代码的优化以及loader加载器怎么写