实验日期 | 实验项目 |
---|---|
2020.10.15 | 第3天 进入32位模式并导入C语言 |
文章目录
一、实验主要内容
1、 内容1 制作真正的IPL
(1).内容概要
- 实验内容:认识软盘的结构,并使用IPL来装载程序;优化Makefile内容,使用变量定义去简化Makefile的写法。
- 实验重点:能够使用汇编去读取磁盘,清楚调用0x13号BIOS的相关值的设置。使用IPL来装载程序。
软盘的结构:软盘为多层环状磁条组成。一共有80个柱面(0-79号),每个柱面有18个扇区(1-18号),读取时区分正反面,其中正面为磁头0,反面为磁头1。描述磁盘的某个位置时 可以用Cx-Hy-Sz表示,例如本次实验中含有IPL的启动区就位于C0-H0-S1(柱面0,磁头0,扇区1)磁盘结构示意图如下所示:
Makefile文件中的变量表示:在Makefile文件中将一些经常用到的路径或者命令设置为变量,定义在文件开头,当编写命令时,用到这些变量时,只需要将变量名外加上括号和$即可。简化了Makefile的书写。
内存地址的装载:启动区在内存地址0x7c00 ~ 0x7dff,用于启动引导程序,软盘的启动区在0x8000~0x81ff,软盘的数据从0x8200开始装载,即第2扇区开始,每个扇区所占空间为512个字节。
(2).关键代码分析
;读取磁盘
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
MOV AH,0x02 ; AH=0x02 : 表示读取磁盘操作
MOV AL,1 ; 1个磁盘
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ;调用磁盘BIOS
JC error ;jump if carry 如果进位值为1,说明出现了错误跳转到error输出错误信息
这部分代码是在第2天内容的基础上新增的读取磁盘的操作,读取磁盘相关的寄存器的设置根据笔者的书总结如下:
寄存器 | 设置的值 | 表示的含义 |
---|---|---|
AH | 0x02 | 读取磁盘 |
AH | 0x03 | 写入磁盘 |
AH | 0x04 | 校验 |
AH | 0x0c | 寻道 |
AL | x | x表示处理的扇区数,这里只能是连续的扇区 |
CH | 柱面号&0xff | 软盘的柱面号 |
CL | 扇区号(0-5位) | (柱面号&0x300)>>2 |
DH | 0或1 | 表示软盘的磁头的正面或者反面 |
DL | 驱动器号 | 驱动器号 |
ES:BX | 缓冲地址 | 表示的实际地址为ES*16+BX |
上述寄存器的值在每次读取磁盘时都需要进行相应的设置。另外,返回值在这里是用来检测是否有错误的,如果FLAGS.CF为1说明有错误,错误号码存入AH内,否则AH=0。
其余代码部分和第2天的代码一致。
2、 内容2 试错
(1).内容概要
- 实验内容:软盘在读取过程中可能会出现一些意料之外的错误,本次实验将重复读取磁盘多次,避免偶尔磁盘出错带来的影响。
(2).关键代码分析
;读取磁盘
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
MOV SI,0 ; 记录读取失败的次数,如果次数超过5次,则输出错误信息
retry:
MOV AH,0x02 ; AH=0x02 : 读入磁盘
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JNC fin ; 没有出错则跳转到fin
ADD SI,1 ; 出错了,SI记录的次数+1
CMP SI,5 ; SI和5比较
JAE error ; SI >= 5 跳转到error
MOV AH,0x00
MOV DL,0x00 ; A驱动器
INT 0x13 ; 重置驱动器
JMP retry ;继续尝试
这部分代码设置了多次读取磁盘的操作,如果没有读取成功,则一直重复retry部分的代码。JNC指令表示进位标志位是0就跳转;JAE表示大于或等于时跳转。在重新读取磁盘前,需要进行系统复位,即“AH=0x00,DL=0x00,INT 0x13”
3、 内容3 读到18扇区和读入10个柱面
(1).内容概要
- 实验内容: 学会读入18个扇区和10个柱面的方法。
指定处理的扇区数,范围在0x01~0xff(指定0x02以上数值时,需要特别注意能够处理多个扇区的条件。如果是FD的话,就不能跨越多个磁道,也不能超过64KB的界限。
(2).关键代码分析
- readloop部分和retry部分
readloop:
MOV SI ,0 ; 记录失败次数
retry:
MOV AH,0x02
MOV Al ,1 ;一个扇区,这里是一个扇区一个扇区读取
MOV BX,0
MOV DL,0x00 ;驱动器A
INT 0x13 ;调用BIOS
JNC next ;没有出错就跳转到next,next是读取磁盘
ADD SI ,1 ;SI 加1,记录失败次数
CMP SI ,5 ;和试错允许次数5比较
JAE error ;超过允许的范围,则跳转到error,输出错误信息
MOV AH, 0x00
MOV DL ,0x00
INT 0x13 ;这三句表示重置驱动器
JMP retry
这部分代码是多次读取磁盘,防止偶尔磁盘出错带来的问题。如果没有出错,则跳转到next读取下一个扇区。
- next部分
next:
MOV AX,ES
ADD AX ,0x0020
MOV ES ,AX ;将内存地址后移0x200,相当于到下一个扇区起始地址
ADD CL,1 ;下一个扇区
CMP CL ,18 ;比较CL和18,即判断是否读到18扇区
JBE readloop ;每次读取新的扇区前需要将记录失败次数的寄存器重复值为0
MOV CL,1 ;从扇区1开始
ADD DH,1 ;DH+1,表示从反面读
CMP DH ,2
JB readloop ;
MOV DH,0 ;从正面开始读
ADD CH,1 ;下一个柱面
CMP CH,CYLS ;比较CH和CYLS,即判断是否读完10个柱面
JB readloop
这部分代码的实现逻辑是先判断是否读到18个扇区,读完18个扇区则从反面继续读,正反面读取完成就开始读取下一个柱面,直到读取到指定的柱面。
4、 内容4 着手开发操作系统
(1).内容概要
- 实验内容: 学会将自己编写的小程序编译后得到的sys文件保存映像文件中;从启动区执行操作系统;确认操作系统的执行情况。
- 实验重点:了解从启动区执行操作系统的简单流程,并学会调用BOIS确认操作系统的实际执行情况。
保存到映像中的步骤:
a.使用make install指令,将磁盘映像文件写入磁盘。
b.在Windows中打开磁盘,将haribote.sys保存到磁盘上。
c.使用工具将磁盘备份为磁盘映像。
一般向一个空软盘中保存文件时,文件名会写在0x002600以后的地方,文件的内容会写在0x004200以后的地方。当我们将操作系统本身的内容写到名为haribote.sys的文件中,再将其保存到磁盘映像里,确定好磁盘映像0x4200在内存中对应的地址位置,即可从启动区执行操作系统。例如将我们编写的nas文件制作映像文件后打开结果如下:
显卡模式设置:设定AH=0x00,AL根据具体的数值设置相应的模式。其中0x03表示16色字符模式,8025;0x12表示VGA模式,6404804位彩色模式,独特的4面存储模式;0x13表示VGA图形模式,320200*8为彩色模式,调色板模式;0x6a表示扩展的VGA图形模式;无返回值。
内存的地址分布,下图是实验过程中找的内存的地址分区图,对于理解笔者汇编的涉及到的地址有一定的帮助。
(2).关键代码分析
这部分代码的功能是使得CPU进行待机功能的小程序。ORG表示将磁盘的内容装载到0xc200处。在ipl.nas中读取磁盘完成后加上JMP 0xc200表示跳转到0xc200。当装载启动区执行时,读取磁盘完成后,就会跳转到0xc200处执行我们预先使用ORG指令装载好的程序内容。INT 0x10表示调用显卡,对应的寄存器AH设置为0x00,AL 设置为对应模式。
5、 内容5 进入32位模式,并导入C语言,实现HLT指令
(1).内容概要
- 实验内容: 了解进入32位模式下需要做哪些前期准备;学会将编写的C语言程序导入sys文件;编写实现特定功能的C语言程序,如HLT指令。
- 实验重点:32位模式和16位模式下的区别;如何将一个C程序文件变成一个汇编程序,并导入sys文件。
32位模式和16位模式:32位模式,指的是CPU的模式。CPU有32位和16两种模式。如果通过16位模式启动,使用AX和CX等寄存器会十分方便,而EAX和ECX等32位的寄存器就会比较麻烦。在不同的模式下机器语言的命令代码不一样,解释方法不一样,故16位模式的机器语言在32位模式下不能运行。另外,32位下可以使用CPU的自我保护功能(识别出可疑的机器语言并进行屏蔽,以避免破坏系统)。
C语言文件变成机器语言,并生成haribote.sys的基本操作步骤:
a.使用ccl.exe将bootpack.c生成bootpack.gas
b.使用gas2nask.exe将bootpack.gas生成bootpack.nas
c.使用nask.exe将bootpack.nas生成bootpack.obj
d.使用obi2bim.exe将bootpack.obj生成bootpack.bim
e.使用bim2hrb.exe将bootpack.bim生成bootpack.hrb
f.使用copy指令将asmhead.bin与bootpack.hrb结合生成baribote.sys文件
整个过程的流程图如下所示:
编写c程序和汇编程序时需要注意,在nask目标文件的模式下,必须设定文件名信息,写明程序的函数名。特别在函数名前需要加上“_”,否则就不能很好地与C语言函数链接,链接的函数名必须使用GLOBAL声明。
(2).关键代码分析
- haribote.nas代码
; BOOT_INFO
CYLS EQU 0x0ff0 ;设定启动区
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ; 关于颜色数目的信息。颜色的位数
SCRNX EQU 0x0ff4 ; 分辨率X
SCRNY EQU 0x0ff6 ; 分辨率Y
VRAM EQU 0x0ff8 ; 图形缓冲区的开始地址
ORG 0xc200 ; 程序被装载到内容中的地址
MOV AL,0x13 ; VGA显卡,320*200*8位彩色
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8 ; 记录画面模式
MOV WORD [SCRNX],320
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000
; 用BIOS取得键盘上各种LED指示灯的状态
MOV AH,0x02
INT 0x16 ; keyboard BIOS
MOV [LEDS],AL
fin:
HLT
JMP fin
这部分代码主要是设置画面模式,画面的像素数,颜色数和从BIOS取得的键盘信息都保存起来,保存在内存中0x0ff0的位置处。
- naskfunc.nas代码
[FORMAT "WCOFF"] ; 制作目标文件的模式
[BITS 32] ; 制作32位模式用的机器语言
; 制作目标文件的信息
[FILE "naskfunc.nas"] ; 程序中包含的函数名
GLOBAL _io_hlt ;
; 以下是实际的函数
[SECTION .text]
_io_hlt: ; void io_hlt(void);
HLT
RET
这部分代码是汇编写的HLT指令的实现函数
- bootpack.c的代码
void io_hlt(void);/* 函数声明用;表示函数在别的文件中 */
void HariMain(void)
{
fin:
io_hlt(); /* 执行naskfunc.nas里面的_io_hlt函数 */
goto fin;
}
这部分代码是用C语言写的具有停机功能的程序,里面调用了汇编代码写的HLT指令。
二、遇到的问题及解决方法
1、 描述问题1
- 问题描述
在笔者书中提到,将软盘内容读到内存0x8200,内存结构中0x7c00 ~ 0x7dff用于载入ipl,0x7e00~0x7fff用于引导区运行时栈,为什么计算0x4200对应位置时是从0x8000开始?
- 解决方法
通过上网查阅资料,BIOS将磁盘0位置的内容拷贝到了内存0x7c00处,执行启动区的内容后,会将软盘内容读入第2个扇区即0x8200的位置,而0x4200是相对于0号地址中第1个扇区的起始地址,所以0x4200对应内存中0x8200+0x4200-0x200=0x8000+0x4200。
2、 描述问题2
- 问题描述:
- 解决方法
make –r img时没有规则生成ipl10.bin文件。仔细检查后发现笔者Makefile文件中使用的工具是去上一级目录中z_tools里面找,而我新建了一个文件夹,导致没有工具生成ipl10.bin文件。
修改Makefile文件中所有对应路径;将路径修改为Makefile中对应的路径,前者需要修改的地方太多,还是选择后者吧。
三、程序设计创新点
1、 描述创新点1,关键代码及结果截图
- 创新点1
根据教材58页上VRAM存储着画面的像素,而每个像素在地址0xa0000~0xaffff中,改变地址里边的像素即可改变显示画面。
- 关键代码
bootpack.c代码
void change(int s,int d);
void HariMain(void)
{
int i;
for(i=0xa0000;i<=0xaffff;i+=0x200)
{
if(i>0xaffff||i+10>0xaffff||i+20>0xaffff) break;
change(i,45);
change(i+10,10);
change(i+20,8);
}
}
这部分代码是用来修改制定地址中像素,change函数用汇编代码编写。
change代码
[FORMAT "WCOFF"] ; 制作目标文件的模式
[INSTRSET "i486p"]
[BITS 32] ; 制作32位模式用的机器语言
; 制作目标文件的信息
[FILE "naskfunc.nas"] ; 程序中包含的函数名
GLOBAL _change ;
[SECTION .text]
_change: ; void change(int s,int d);
MOV ECX,[ESP++4]
MOV AL,[ESP+8]
MOV [ECX],AL
RET
函数调用过程中参数1,参数2放在栈中esp+4和esp+8的位置,故只需要将esp+8处的数据放到esp+4的地址中即可。
- 结果截图
四、实验心得体会
- 本次实验是自制操作系统的第3天,学习的内容比较多,包括继续介绍汇编语言的相关指令和BIOS的调用;讲解读取磁盘的汇编程序;从启动区开始执行操作系统,并确认其执行情况;进入32位模式导入C语言等内容。有了之前汇编语言的基础,在阅读教材的时候比较容易理解笔者所写的汇编代码,比较不清楚的是关于软盘的结构,磁盘和内存地址的对应关系,通过查阅相关资料,解决了自己遇到的问题。总体来说,这次实验内容多,但是都不难理解,跟着笔者的讲解一步步深入,实验很容易完成。
- 在这次实验中梳理C程序转化为汇编代码的步骤时,用到了流程图的方式描述,清晰简洁,可以在以后学习过程中对繁杂步骤梳理,以便学习和理解。实验中受到教材58页VRAM存储着画面的像素,于是想通过改变不同地址中像素的大小来绘制简单的图案,这就是本次实验我想到的创新点。另外,是否能用C程序实现其他功能,比如简单加法输出。