30天自制操作系统day03-进入32位模式并导入C语言

30天自制操作系统

进入32位模式并导入C语言

制作真正的IPL

该部分主要是对第二天结尾内容的深入。上一次简单介绍了如何制作启动区,但只是制作了一个空的框架,其中并未装载任何程序,在这一部分中,会给这个“空壳”真正装载进程序。因为最初的512字节是启动区,所以要从下一个512字节的内容开始装载程序。提供的harib00a中的文件打开,可以发现添加的内容仅有如下一段代码:
30天自制操作系统day03-进入32位模式并导入C语言
1、指令与标志:
 JC:跳转指令的一种,“jump if carry”,即如果进位标志(carry flag)为1,就跳转。
 “INT 0x13”:调用BIOS的0x13号函数,经过查找可得到如下内容:
磁盘读、写,扇区校验,以及寻道
AH=0x02;(读盘)
AH=0x03;(写盘)
AH=0x04;(校验)
AH=0x0c;(寻道)
AL=处理对象的扇区数;(只能同时处理连续的扇区)
CH=柱面号&0xff;
CL=扇区号(0-5位)|(柱面号&0x300)>>2;
DH=磁头号;
DL=驱动器号;
ES:BX=缓冲地址;(校验及寻道时不使用)
返回值:
FLACS.CF=0:没有错误,AH==0
FLAGS.CF=1:有错误,错误号码存入AH内(与重置reset功能一样)
由代码可知,此处使用的是AH=0x02,所以这里是“读盘”操作。
 FLAGS.CF:进位标志,如果调用函数后无错误,则进位标志为0;若有错,则进位标志为1;
进位标志:一个只能存储1位信息的寄存器,本来表示有无进位,这里用来报告BIOS函数调用是否有错误;
标志:一位寄存器;
2、软盘结构
软盘结构示意图如下:
30天自制操作系统day03-进入32位模式并导入C语言
 柱面:由外向内(0-79)的一圈一圈圆环状的区域。这并非软盘的生产方式,而是我们将它作为数据存储媒体,这样组织它的数据存储方式。
 磁头:针状的磁性设备。区别与光盘,软盘磁盘是两面都能记录数据的。因此有正反两面两个磁头,其中正面是磁头0号,反面是磁头1号。
 扇区:由于柱面作为单位进行读写过大,所以我们将“圆环”等分成18份(软盘),每一份称为一个扇区(1-18)。
综上,1个软盘有80个柱面,2个磁头,18个扇区,一个扇区512字节,因此软盘容量:
80218512=1440KB
含IPL的启动区位于C0-H0-S1,因此我们想要装载的扇区是C0-H0-S2。
3、缓冲区地址
即为从软盘上读出的数据装载到内存中的内存地址。在EBX出现之前,使用的是辅助作用的段寄存器。
段寄存器地址表示方式:
MOV AL,[ES:BX],代表:ES
16+BX的内存地址(先用ES指定大致地址,再用BX确定其中一个具体地址);
在内存地址中,0x7c00-0x7dff用于启动区,0x7e00之后直到0x9fbff为止,操作系统可随意使用。
4、段寄存器
只要指定内存地址,必须同时指定段寄存器。如果省略的话会把“DS:”作为默认段寄存器。之前出现过的“MOV AL,[SI]”其实是“MOV AL,[DS:SI]”的意思。因此,DS必须被预先指定为0,否则会引起混乱,读写到其他地方。(不是0的话需要加上这个非零数的16倍)。
5、程序测试
将文件夹harib00a复制到tolset中,运行前文件夹文件如下:
30天自制操作系统day03-进入32位模式并导入C语言
打开!cons_nt文件,键入命令如下:
30天自制操作系统day03-进入32位模式并导入C语言
命令实现后会调用模拟器,并出现以下结果:
30天自制操作系统day03-进入32位模式并导入C语言
证明程序运行成功,无错误发生。此时观察文件夹,我们没有输入make img的指令,但文件夹中出现了img文件,这也证实了day02中Makefile一部分中提到的,直接输入make run指令会默认先生成img文件后再执行下一行操作。若想恢复到原来文件夹的样子,可以选择打开!cons_nt文件并键入make src_only指令后,即可恢复至源文件。

试错

 背景情况:软盘不可靠,时常会发生无法读盘的情况,这时再读几次就可以了,但如果尝试读取的次数太多,磁盘损坏,程序就会陷入死循环,因此我们设置为只读取五次,如果不成功则放弃。
 改进代码:(harib00b)
30天自制操作系统day03-进入32位模式并导入C语言
 代码解读:
JNC:一种跳转指令,“jump if not carry”的缩写,即当进位标志为0则跳转;
JAE:一种跳转指令,“jump if above or equal”的缩写,即当大于等于时跳转;
在retry部分,重新读盘之前,进行了如下操作:
AH=0x00,DL=0x00,INT=0x13,
根据BIOS可知,该操作为“系统复位”,其功能为:将软盘恢复初始状态,准备下一次的重新读取。

读到18扇区

 新的代码
这一部分我们选择继续读取软盘中的内容,新的读取代码如下:
30天自制操作系统day03-进入32位模式并导入C语言
 代码解读
JBE:一种跳转指令,“jump if below or equal”的缩写,即小于等于的时候跳转。
程序要读取到18扇区,那么就在代码中设置一个一个按顺序读取。其中,CL指扇区号,ES指读取地址,因此只需将CL+1,ES+0x20即可。ES的读取地址加0x20,其中的0x20是16进制下的512位。(这里仅仅为了练习ES寄存器的加法操作,但其实用BX加减更加方便)
使用循环的原因:如果不使用循环,直接将AL赋值为17,则可以直接将2-18扇区进行连续的读取,结果不会发生变化,但这种不采用循环的方法会在之后的程序中出现问题,因此为了成功过渡,这里采用循环的模式进行编写。
 运行结果:
运行的最终结果看起来毫无变化,但我们已经把软盘中C0-H0-S2到C0-H0-S18的8704个字节写入到0x8200-0x83ff中。结果如下:
30天自制操作系统day03-进入32位模式并导入C语言
30天自制操作系统day03-进入32位模式并导入C语言

读入10个柱面

 新的代码:
上一部分我们读取了18个扇区,这一部分我们继续读取,通过调整代码来实现对1-9号柱面的读取,代码增加部分如下:
30天自制操作系统day03-进入32位模式并导入C语言
 代码解读:
JB:一种跳转指令,“jump if below”的缩写,即小于时跳转。
EQU:用来声明常数,相当于C语言中的#define,代码中的语句“CYLS EQU 10”相当于“CYLS=10”,是“equal”的缩写。(CYLS代表cylinders)
程序运行后与之前无异,但程序已经用从软盘读取的数据填充了内存0x08200-0x34fff的范围。
 跳转语句分析:
第一个跳转语句:“CMP CL,18 JBE readloop”
这一语句代表在CL(扇区)小于等于18时跳转到readloop继续循环,程序开头是从0-0-2(三个数字分别代表柱面、磁头和扇区,下同;扇区从2开始是因为0-0-1中是启动区的部分,启动区代码开机自动写入磁盘,该部分的代码就是启动区代码,因此从存储其他内容的第二扇区开始)开始读取,读到0-0-18跳出循环。
第二个跳转语句:“CMP DH,2 JB readloop”
这一语句代表DH(磁头)小于2时跳转到readloop继续循环,这一语句是控制正反两个磁头的读取,因为当前循环之前还经历了第一个循环,且上一个循环结束时读到0-0-18,所以DH=0+1=1反转到另一面,即从0-1-1开始读取,到0-1-18跳出循环。
第三个跳转语句:“CMP CH,CYLS JB readloop”
这一语句代表CH(柱面)小于CYLS(10)时跳转到readloop继续循环,这里是控制读取的柱面数量(通过定义变量CYLS的大小来控制)。当前循环前包含了前两个完整的循环,且上一个循环结束时读取到0-1-18,即0号柱面已经读取完毕,所以当前循环是从1-0-1开始读取,读到9-1-18跳出循环。
综上,这一部分代码的跳转情况主要分为三个阶段:
第一阶段:控制扇区 0-0-2到0-0-18
第二阶段:控制磁头 0-1-1到0-1-18
第三阶段:控制扇区 1-0-1到9-1-18

着手开发操作系统

之前我们已经完成了启动区的制作,接下来就开始开发操作系统,首先编写一个短小的程序,只让它HLT,程序代码如下:
30天自制操作系统day03-进入32位模式并导入C语言
写好之后保存为haribote.nas,用nask编译后输出成为haribote.sys,并将这个文件保存到磁盘映像haribote.img中,这一步的具体操作如下:
1、 使用make install指令,将磁盘映像文件写入磁盘;
2、 在Windows中打开磁盘,将haribote.sys保存到磁盘上;
3、 使用工具将磁盘备份为磁盘映像。
这些操作的起始点都是磁盘映像,就考虑不借助磁盘和Windows,直接将haribote.sys保存到磁盘映像中,可以完成这样工作的工具有很多,如edimg.exe即可。
 具体执行
1、 make img制作映像文件后并用二进制编辑器打开
2、 找到0x002600和0x004200附近,截图如下:
0x002600附近:(文件名)
30天自制操作系统day03-进入32位模式并导入C语言
0x004200附近:(F4 EB FD)
30天自制操作系统day03-进入32位模式并导入C语言
3、 二进制编辑器打开haribote.sys,查看内容如下:
30天自制操作系统day03-进入32位模式并导入C语言
发现0x004200附近的内容刚好是haribote.sys文件中保存的内容。
综上,可以总结出:
向一个空软盘保存文件时:
1、 文件名会写在0x002600以后的地方;
2、 文件的内容会写在0x004200以后的地方。
下面要做的任务就是将操作系统本身的内容写到名为haribote.sys文件中,保存到磁盘映像里,再从启动区执行这个文件即可。

从启动区执行操作系统

因为是将磁盘上的内容装载到内存中0x8000号地址,因此要执行磁盘中0x4200位置的内容,就应该位于内存中的0x8000+0x4200=0xc200号地址。
这样就在haribote.nas中加上ORG 0xc200(把程序装载到内存中的指定地址),然后在ipl.nas处理最后加上JMP 0xc200(启动区跳转到指定程序)。
运行结果无变化,下面即测试haribote.sys是否真的执行了。

确认系统的执行情况

本次的文件通过切换页面模式来验证,本次的代码如下:
30天自制操作系统day03-进入32位模式并导入C语言
设置AH=0x00,调用BIOS函数,这样来切换显示模式。因为查找网站可知,当AL=0x13时,是调用了VGA图形模式,3202008位彩色模式,调色板模式,这里8位色彩模式可以使用256种颜色。
 预期结果:切换正常的话,可以看到画面变成一片漆黑,则证明画面发生变化,程序可以正常运行。
 修改解释:
1、 ipl.nas变为ipl10.nas:提示柱面读取数量为10
2、 将CYLS的值写入内存地址0x0ff0:将磁盘装载内容的结束地址告诉haribote.sys,于是在“JMP 0xc200”前写入该命令。
 运行结果:证明程序运行无误。

30天自制操作系统day03-进入32位模式并导入C语言

32位模式前期准备

因为32位模式存在很多的优点与便利性,因此后续的C语言编译会采用32位模式而非16位模式,但32位不能调用BIOS,所以要在进入32位模式前进行前期准备,将BIOS有关的部分全部放在开头,即我们的32位模式前期准备(因32位返回16位过于麻烦,所以不予使用)。
画面模式设定结束后,还要从BIOS中得到键盘状态(指NumLock是ON还是OFF等这些状态)。修改后的haribote.nas程序如下:
30天自制操作系统day03-进入32位模式并导入C语言
该段程序不但对画面模式进行了设置,还把画面模式的信息保存在了内存里面(以备后用)。
VRAM:指的是显卡内存,即用来显示画面的内存。VRAM的各个地址都对应着画面上的像素,可利用绘制图案。不同的画面模式可以使用的内存也不一样,因此我们要预先把要使用的VRAM地址保存在BOOT_INFO内备用。(具体查看说明中当前画面模式下的地址)
另外我们还把画面的像素数、颜色数以及从BIOS取得的键盘信息都保存起来,位置为内存0x0ff0附近。(之后切换为32位模式后无法调用BIOS函数,因此要在切换之前将所有内容存储,以备后用,0x0ff0处做内核中断,因此为切换的末尾处,用来存储这些数据。)
9、开始导入C语言
准备就绪后直接切换到32位模式,然后运行C语言的程序
 Asmhead.nas:该文件是haribote.nas修改后得到的,因为现在的代码,前半部分是汇编语言写成的,后半部分则是用C语言写成的,因此修改文件名加以区分。并且为了调用C语言写的程序,添加了100行左右的汇编代码。
 C语言程序:bookpack.c,具体内容如下:

30天自制操作系统day03-进入32位模式并导入C语言
goto指令相当于汇编语言中的JMP,实际上也是被编译成JMP。
 文档结构图(详见第一次节点考核)
 各类文件解释:
.c:C语言文件
.gas:gas汇编语言用的源文件
.nas:nask汇编语言用的源文件
.obj:目标文件(机器语言)
.bim:二进制映像文件
 各种文件类型转换工具:
“cll.exe”:C编译器,将C语言程序编译成汇编语言源程序;
“gas2nask.exe”:把gas文件转换为nask文件的程序(其他的2.exe文件解释同上)
10、实现HLT(harib00j)
为了解决程序耗电的问题,需要想办法让程序处于HALT状态,首先编写了下面的程序:
30天自制操作系统day03-进入32位模式并导入C语言
在nask目标文件模式下,必须设定文件名信息,然后写明下面程序的函数名,要在函数名前面加上“_”,否则不能很好的与C语言函数链接,需要链接的函数名,都要用GLOBAL指令声明。下面写个实际的函数:
30天自制操作系统day03-进入32位模式并导入C语言
make run之后发现程序运行正常,功能实现。