30天自制操作系统(day3)

第3天:进入32位模式并导入C语言

一、实验主要内容
**1、内容1:**制作真正的IPL
因为磁盘最初的512字节是启动区,因此要装载下一个512字节的内容,于是在IPL.nas文件中添加以下指令:
30天自制操作系统(day3)
其中JC为jump if carry”意思是如果进位标志(carry flag)是1的话,就跳转。INT 0x13表示调用BIOS的0x13号函数。这里有BIOS的各个函数:
01H 读取磁盘系统状态 02H 读扇区
03H 写扇区 04H 检验扇区
05H 格式化磁道 06H 格式化坏磁道
07H 格式化驱动器 08H 读取驱动器参数
09H 初始化硬盘参数 0AH 读长扇区
0BH 写长扇区 0CH 查寻
0DH 硬盘系统复位 0EH 读扇区缓冲区
0FH 写扇区缓冲区 10H 读取驱动器状态
11H 校准驱动器 12H 控制器RAM诊断
13H 控制器驱动诊断 14H 控制器内部诊断
15H 读取磁盘类型 16H 读取磁盘变化状态
17H 设置磁盘类型 18H 设置格式化媒体类型
19H 磁头保护 1AH 格式化ESDI驱动器
每个具体的功能与使用可查看该网站https://blog.****.net/jackailson/article/details/84109450
AH=0x02;(读盘) —这次用的是这个功能
AH=0x03;(写盘)
AH=0x04;(校验)
AH=0x0c;(寻道)
AL=处理对象的扇区数;(只能同时处理连续的扇区)
CH=柱面号 & 0xff;
CL=扇区号(0-5位)|(柱面号&0x300)>>2;
DH=磁头号;
DL=驱动器号;
ES:BX=缓冲地址;(校验及寻道时不使用)其代表着ESx16+BX的内存地址。也可以理解为先用ES寄存器指定一个大致的地址,再用BX来指定其中一个具体地址。
因此
MOV CH,0
MOV DH,0
MOV CL,2
代表柱面0磁头0扇区2。
MOV AH,0x02
代表着读盘。
MOV AL,1
代表处理1个扇区。
MOV DL,0x00
代表着调用A驱动器。
INT 0x13
代表着调用磁盘BIOS的0x13号函数。
MOV AX,0x0820
MOV ES,AX
因为不能对ES直接赋值,只能先通过AX再赋给ES。
那为什么赋给AX的是0x0820呢?
因为在实际表示内存地址时,ES需要乘上16再加BX,在16进制中乘上16,即向高位移动一位,因此是0x0820。实际上,装载程序的内存地址起始地址是0x8200。
为什么规定装载程序的内存起始地址是0x8200?
因为0x8000-0x81ff的512字节是留给启动区的,0x8000以后的内存区域没有人使用,因此便将程序装载到该处。
返回值:
FLACS:CF0: 没有错误,AH0
FLAGS.GF==1: 有错误,错误号码存入AH内(与重置(reset)功能一样)
进位标志:FLAGS.CF,调用此函数之后,如果没错,进位标志是0,如果有错,进位标志就是1。(本次是用来报告BIOS函数调用是否有错)
进位标志是一个只能存储1位信息的寄存器,除此之外CPU还有其他几个只有1位的寄存器,这种1位寄存器我们称为标志。
含有IPL的启动区位于C0-H0-S1(即柱面0,磁头0,扇区1),下一个扇区是C0-H0-S2,也就是我们程序所装载的扇区。
要注意的是柱面的编号是0号到79号,而扇区的编号是1号到18号。
当前的运行结果如下;
30天自制操作系统(day3)

**2、内容2:**试错:
为什么要试错?
软盘有时候会发生不能读数据的状况,这时候只需让其重读即可。但是若是磁盘真的损坏,程序便会进入死循环。因此,我们需要规定其出错的次数,如果重试次数超过该次数,就立即停止。在该程序中,设置的读数据失败次数为5次,此时将程序更改为:
30天自制操作系统(day3)
MOV SI,0
代表记录失败次数的寄存器,初始值为0
JNC fin
如果JNC=0,即读磁盘没有出错,就跳转到fin继续执行,否则,SI就加1,并与5进行比较,如果SI<=5,即失败次数没有达到5次,那么就重置驱动器,再读一次,否则报错。
重新读盘之前,需要做一次“系统复位”,AH=0x00,DL=0x00,INT 0x13复位软盘状态,再读一次。结果如下:
30天自制操作系统(day3)

**3、内容3:**读到18扇区:
因为1个柱面有18个扇区,因此读扇区时只需依次扇区加1直至第18号扇区即可。前面读盘的内容没有变化,因此只看程序变化部分。
30天自制操作系统(day3)
ADD AX,0x0020
代表AX加上0x0020,因为ES是通过AX来赋值的,即ES加上0x20.
为什么是加上0x20扇区就会加1?
0x20是十六进制下512除以16的结果,一个扇区是512字节,如果想要读下一个扇区,需要再加上512的字节的内存。实际上,下一个扇区的内存地址及为0x8400-0x85ff。
JBE readloop
代表:CL表示扇区号,当CL<=18,代表着扇区还未读到18号,因此跳转到readloop继续读,当超过18时就停止。运行结果如下:
30天自制操作系统(day3)

**4、内容4:**读入10个柱面
前面读盘的指令没有变化,只需增加读18个扇区,2个磁头和10个柱面的指令即可。
30天自制操作系统(day3)
先读入18个扇区,如果扇区号超过18,那么就磁头号加1,当磁头号超过2时,那么就柱面加1,如果读入柱面超过10时,就停止。这里的CYLS其实在开始处就已经声明为10了。
通过读入10个柱面,已经将软盘最初的10x2x18x512=183 320Byte=180KB的内容装载到内存中了。结果如下:
30天自制操作系统(day3)

**5、内容5:**着手开发操作系统:
编写程序,使cpu暂时处于待机状态
fin:
HLT
JMP fin
将其保存为haribote.nas,用nask编译,输出成hanbote.sys。将该文件保存到磁盘映像haribote.img中。用二进制编辑器打开该映像文件,查看haribote.sys文件在磁盘中是什么样的。
可以注意到在0x002600附近,磁盘保存着文件名,在0x004200附近,可以看到F4 EB FD的内容。
30天自制操作系统(day3)
在0x004200附近看到的内容其实就是haribote.sys中的内容。
30天自制操作系统(day3)
因此可以总结为:一般向一个空软盘保存文件时,文件名会写在0x002600以后的地方,文件内容会写在0x004200以后的地方。
执行结果如下:
30天自制操作系统(day3)

**6、内容6:**从启动区执行操作系统及确定操作系统执行情况:
如何才能执行磁盘映像上位于0x004200号地址的程序?
现在的程序是从启动区开始,将磁盘上的内容装载到内存0x8000号地址,因此磁盘0x4200处的内容应该位于内存0x8000+0x4200=0xc200号地址。因此只需在haribote.nas中加上跳转。
30天自制操作系统(day3)
ORG 0xc200 ; 这个程序将要被装载到内存的什么地方
MOV AL,0x13; VGA显卡,代表设置0x13的画面模式,也就是320x200x8的彩色模式
运行结果如下:
30天自制操作系统(day3)
为什么ORG设为0xc200: 执行磁盘映像上位于0x004200号地址的程序,从启动区开始把磁盘内容装载到0x8000号地址故0x4200处内容应该位于内存0x8000+0x4200=0xc200号地址。
设定AH=0x00,调用显卡BIOS的函数,可以切换显示模式:
AH=0x00,AL=模式:(省略了一些不重要的画面模式)
0x13 16色字符模式,80x25
0x12 VGA图形模式,640x480x4位彩色模式,独特的4面存储模式
0x13 VGA图形模式,320x200x8位彩色模式,调色板模式
0x6a 扩展VGA图形模式,800x600x4位彩色模式,独特的4面存储模式(有的显 卡不支持这个模式)
返回值:无
这次选择的是0x13的画面模式,8位彩色模式可以用256种颜色,如果画面模式切换正常,画面应该是一片漆黑。图形模式光标会消失。

7、内容7: 32位模式前期准备:
用32位模式就不能调用BIOS功能,因为BIOS是用16位机器语言写的,如果要用,就全部都放在开头先做。设置画面模式,并将画面模式的信息保存在内存中。
30天自制操作系统(day3)
CYLS EQU 0x0ff0 设定启动区
LEDS EQU 0x0ff1 不知道干啥的
VMODE EQU 0x0ff2 关于颜色数目的信息。颜色的位数赋值为8
SCRNX EQU 0x0ff4 分辨率的X (screen X)
SCRNY EQU 0x0ff6 分辨率的Y (screen Y)
VRAM EQU 0x0ff8 图像缓冲区的开始地址
VMODE:颜色数目即颜色的位数,赋值为8,赋值为0x000a0000。
为什么VRAM的值是0xa0000?
因为0x13号图像模式下,VRAM是0xa0000-0xaffff的64KB。
ORG 0xc200 ; 这个程序将要被装载到内存的什么地方
MOV AL,0x13 ; VGA显卡、320x200x8位彩色
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8 ; 记录画面模式
MOV WORD [SCRNX],320
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000
设置画面模式之后,把画面模式保存在内存里,暂时将启动时的信息称为BOOT_INFO。[VRAM]里保存的是0xa0000,VRam指的是显卡内存,用来显示画面的内存,里面的各个地址对应着画面上的像素,参考(AT)BIOS支持网页在INT 0x10的说明最后写“VRam是0xa0000~0xaffff的64KB”,INT 0x16是记录LED灯的状态,我们把画面的像素数、颜色数、以及从BIOS取得的键盘信息都保存起来,位置在内存的0x0ff0附近。结果如下:
30天自制操作系统(day3)

**8、内容8:**开始导入c语言:
修改程序,在haribote.sys中用汇编语言编写前半部分,用C语言编写后半部分。并且为了调用C语言写的程序,添加了100行左右的汇编代码。
C语言部分的程序文件为bootpack.c
30天自制操作系统(day3)
Goto指令相当于JMP指令。
Bootpack.c变成机器语言的过程:
首先,使用ccl.exe从bootpack.c生成bootpack.gas。cc1是C编译器,可以将c语言程序编译成汇编语言源程序,输出是gas用的源程序。
第二步,使用Gas2nask.exe从bootpack.gas生成 bootpack.nas。gas2nask是能将gas文件转换成nask文件的程序。
第三步,使用Nask.exe从bootpack.nas生成bootpack.obj。用nask制作obk文件,即目标文件,但目标文件必须与其他文件链接后才能变成真正可以执行的机器语言。
第四步,使用Obi2bim.exe从bootpack.obj生成bootpack.bim。obj2bim是将必要的目标文件全部连接上,bim是一个二进制映像文件。映像文件其实不是文件本来的状态,是一种代替。
最后,使用Bim2hrb.exe从bootpack.bim生成bootpack.hrb。
这样就做成了机器语言,再使用copy指令将asmhead.bin与bootpack.hrb单纯结合到起来,就成了haribote.sys。需要注意的是,函数名HariMain非常重要,程序就是从以其命名的函数开始运行的,因此函数名不能更改。具体的makefile程序:
30天自制操作系统(day3)

9、内容9:实现HLT:
为了能使计算机处于HALT状态,但C语言不能用HLT,因此先用汇编语言编写io_hlt函数,实现HLT功能,实现链接之后,便可以直接调用该函数,实现HALT功能。
30天自制操作系统(day3)
[FORMAT “WCOFF”] 制作目标文件的模式
[BITS 32] 制作32位模式用的机器语言
[FILE “naskfunc.nas”] 源文件名信息
GLOBAL io_hlt 程序中包含的函数名
[SECTION .text] 目标文件中写了这些之后再写程序
用汇编写的函数,之后还要与bootpack.obj链接,所以也要编译成目标文件。在nask目标文件的模式下,必须设定文件名信息,然后再写明下面程序的函数名,注意要在函数名的前面加上“_”,否则就不能很好地与C语言函数链接,需要链接的函数名都要用GLOBAL指令声明,如上代码所示,先写一个与用GLOBAL声明的函数名相同的标号(label)再从此往下写代码。
30天自制操作系统(day3)
告诉C编译器,有一个函数在别的文件里 */是函数声明却不用{ },而用;,这表示的意思是:函数是在别的文件中,你自己找一下 。实验结果如下:
30天自制操作系统(day3)

**二、遇到的问题及解决方法
1、描述问题1:**在nas文件中用汇编语言编写好了函数,但是在c语言程序中无法正常调用:
30天自制操作系统(day3)
解决方法:因为没有在nas文件中的global部分添加头文件,所以无法找到,修改后成功。
30天自制操作系统(day3)
三、程序设计创新点
画点不同的东西?
或者实现动态的画图?