一.概述
有了上一篇文章:【操作系统】调用硬盘并且实现MBR与Loader的过渡——原理篇的理论支持,我们就可以开始代码实操了,接下来我们将优化MBR程序,使其从扇区中读取出loader加载器,并将其存放到内存处,将权限交付给loader加载器。
二.MBR代码优化
在开始之前我们还需要注意一些细节问题:
- 由于MBR程序已经占据了0扇区,所以loader加载器只能放在其他位置,本文就将加载器放在2扇区,如果你想放其他地方也可以,只要保证不被覆盖就行。
- 读取出来的加载器应该放在内存的什么位置?答案可以参考之前写过的一篇文章:【操作系统】BIOS开机自检,在内存布局中我们得知了“可用区域”0x500~0x7BFF和0X7E00~0X9FBFF,所以我们只要把加载器放在这两个区间就可以,本文就将加载器放在内存的0x900的位置处。
万事俱备,我们就可以开始编写代码了:
1 SECTION MBR vstart=0x7c002 mov ax,cs3 mov ds,ax4 mov es,ax5 mov ss,ax6 mov fs,ax7 mov sp,0x7c008 9 mov ax,0xb80010 mov gs,ax11 12 mov ax,0x60013 mov bx,0x70014 mov cx,015 mov dx,0x184f16 17 int 0x1018 19 mov byte [gs:0x00],'1'20 mov byte [gs:0x01],0xA421 22 mov byte [gs:0x02],' '23 mov byte [gs:0x03],0xA424 25 mov byte [gs:0x04],'M'26 mov byte [gs:0x05],0xA427 28 mov byte [gs:0x06],'B'29 mov byte [gs:0x07],0xA430 31 mov byte [gs:0x08],'R'32 mov byte [gs:0x09],0xA433 34 mov eax,LOADER_START_SECTOR35 mov bx,LOADER_BASE_ADDR36 mov cx,137 call rd_disk_m_1638 jmp LOADER_BASE_ADDR39 40 rd_disk_m_16:41 mov esi,eax42 mov di,cx43 44 mov dx,0x1f245 mov al,cl46 out dx,al47 48 mov eax,esi49 50 mov dx,0x1f351 out dx,al52 53 mov cl,854 shr eax,cl55 mov dx,0x1f456 out dx,al57 58 shr eax,cl59 mov dx,0x1f560 out dx,al61 62 shr eax,cl63 and al,0x0f64 or al,0xe065 mov dx,0x1f666 out dx,al67 68 mov dx,0x1f769 mov al,0x2070 out dx,al71 72 .not_ready:73 nop74 in al,dx75 and al,0x8876 cmp al,0x0877 jnz .not_ready78 79 mov ax,di80 mov dx,25681 mul dx82 mov cx,ax83 84 mov dx,0x1f085 86 .go_on_read:87 in ax,dx88 mov [bx],ax89 add bx,290 loop .go_on_read91 ret92 93 times 510-($-$$) db 094 db 0x55,0xaa
三.MBR代码解析
在第一行的“%include”是我们的配置文件,我们可以将加载器的配置信息填写在里面,目前配置信息就只有两个:
LOADER_BASE_ADDR equ 0x900 //定义loader在内存的位置
LOADER_START_SECTOR equ 0x2 //定义loader在硬盘上的LBA地址
接下来的第2行~第32行是上一篇文章(【操作系统】优化MBR程序:让MBR调用显存吧)的原始代码,就不在此赘述了。第34行~第35行使用了上述配置文件的信息,是第40行函数rd_disk_m_16的传参,这个函数需要三个参数,用eax\bx\cx三个寄存器来传递参数。
寄存器eax中保存了待读取的扇区起始地址,根据第四节的要求,加载器放在了2扇区的位置。寄存器cx中保存了读取扇区的数量,此处要读入1个扇区。寄存器bx保存从硬盘中读取出来的数据放在内存的什么位置,同样按照第四节的要求,将其放在0x900的位置。紧接着开始调用函数rd_disk_m_16,该函数的功能为读取硬盘的指定扇区数据。
首先我们将eax和cx的值备份在esi中,因为后面al寄存器需要用到,所以为了避免数据覆盖进行备份操作。
按照上一篇文章第三节的调用步骤,我们先来选择通道,往该通道的sector count寄存器中写入代操作的扇区数。根据之前写过的一篇文章:【操作系统】Bochs安装和配置,我们的模拟硬件的配置信息为:
ata0是Primary通道,所以sector count寄存器是端口0x1F2来调用。Out指令用于往端口写入数据,dx保存端口信息,al保存cx的信息也就是待读取的扇区数。
使用完al之后,我们回复一下eax的值,让其重新保存待读取的扇区起始地址。
本段主要实现上一篇文章第三节的第二和第三个步骤,将LBA地址分别写入到三个LBA寄存器中,端口名0x1f3~0x1f5的作用在第一节已经给出,不在赘述。Shr指令为右移指令,作用是将一个寄存器或内存单元中的数据向右移指定位数,并将溢出标志 CF 作为进位标志。此处主要通过右移置换出地址写入相应的LBA寄存器。“and al ,0x0f”将LBA的第24~27位放进device寄存器的低四位中,“or al,0xe0”把“0xe0”进行了或运算,拼出device寄存器的值,将高四位设置为“1110”,具体作用参考上一篇文章第一节内容。
这段代码实现上一篇文章第三小节的第四个步骤,对2扇区进行读取操作。通过out指令写入command端口0x1F7,硬盘开始工作。
本段代码主要用于检测status寄存器的BSY状态,“nop”指令的功能与sleep函数相当,延迟一下从而减少打扰硬盘的工作。“in al,dx”将status寄存器的值读入到al寄存器中,通过“cmp al,0x88”的与操作,保留第4位和第7位,第4位为1表示硬盘控制器已经准备好数据传输,第7位为1表示硬盘在忙,所以我们只要判断第4位是否为1即可,顾使用“cmp al ,0x08”对第四位进行判断。最后使用“jnz not_ready”来判断结果是否等于0,若为0,则表示可以读数据了,反之则不断循环读取状态。
该段代码用于从0x1F0端口中读取数据,di寄存器中的值为要读取的扇区数,一个扇区有512个字节,data寄存器位16位,每次in操作读入2字节,所以需要指令di*512/2次,也就是di*256次。
这段代码用于把从扇区读取到的loader加载器写入到bx寄存器所指的内存(0x900)中,每读入2个字节,bx所致的地址便+2,一直循环。待执行完成后,“ret”跳出函数回到上面的第38行中去:
也就是跳转到内存0x900的位置,loader加载器的位置,自此MBR的任务到此结束,完成了MBR与loader的过渡,并且将权限交付给loader。
四.LOADER加载器代码实现
接下来我们来编写一个简易版的loader加载器,这个加载器主要是为了验证MBR是否能够顺利过渡到Loader这里,所以就让它显示一些信息出来就好了,至于如何从实模式到保护模式的过渡,并最终在保护模式下加载内核,等我们继续深入学习到其他东西后再进行代码的优化。
话不多说,直接上代码:
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDRmov byte [gs:0x00],'A'mov byte [gs:0x01],0xA4mov byte [gs:0x02],'K'mov byte [gs:0x03],0xA4mov byte [gs:0x04],'A'mov byte [gs:0x05],0xA4mov byte [gs:0x06],'H'mov byte [gs:0x07],0xA4mov byte [gs:0x08],'A'mov byte [gs:0x09],0xA4mov byte [gs:0x0a],'!'mov byte [gs:0x0b],0xA4jmp $
这段代码其实不用太多的解析,因为之前和上一篇MBR程序(参考:【操作系统】优化MBR程序:让MBR调用显存吧)是一样的,只需要注意一下第2行中将逻辑编译地址(也即是动态重定向,有兴趣可以参考:【计算机组成】实模式/保护模式下地址分段(基段地址+偏移地址)的原因)定义在了LOADER_BASE_ADDR,也就是0x900处。
五.编译和运行
到目前为止,我们整理下已经编写完成的代码并且着手开始编译:
- 首先是改造版的MBR程序,为了方便记忆,本文命名为MBR.S
- 然后是boot.inc配置信息文件
- 再来就是新编写的loader程序,为了方便记忆命名为loader.S
- 最后是虚拟硬盘hd60M.img,这个在之前的文章(参考:【操作系统】Bochs安装和配置)中有说明怎么生成,就不在次赘述了。
我们首先编译MBR程序,使用以下指令即可进行简单的编译(注意你的硬盘、程序路径可能与我的不一样,请选择正确的路径):
需要注意的是,Nasm的参数“-I”,意思是指定库目录的路径,现阶段也就是boot.inc的路径。假如如果你的boot.inc在AAA文件夹下,那么就写“nasm –I /AAA(文件夹的路径) 其他省略”,如果boot.inc和MBR在同一个路径下,就写“nasm -I ./ 其他省略”,以此类推。
接下来按照同样的方式编译loader加载器(注意你的硬盘、程序路径可能与我的不一样,请选择正确的路径):
然后将MBR的二进制文件写入到0扇区中去(注意你的硬盘、程序路径可能与我的不一样,请选择正确的路径):
最后再将loader加载器也写入到2扇区中去(注意你的硬盘、程序路径可能与我的不一样,请选择正确的路径):
将代码写入到镜像后,我们再将镜像文件加入到Bochs的模拟硬件环境配置文件中,根据文章中所描述的,我们直接将新生成的镜像写入配置文件的以下位置即可:
保存一下修改后的配置文件,我们开始运行Bochs模拟器进行模拟操作:
./Bochs –f boch.disk(你自己的硬件配置文件名)
运行成功后,会显示以下信息,并且默认为【6】:
此时我们再按一次回车,即可开始模拟:
我们在控制台中输入“c”(具体含义请查看上面所说的文章),继续往下运行,就能看到弹出的窗口中出现了我们所要的字符串: