第二天,我们将之前的程序进一步翻译成汇编语言,进一步了解启动程序到底做了些啥。
还记得第一天中有一部分程序主体么,以及最开头的代码,我们把它们翻译成汇编看看。
; hello-os
; TAB=4
ORG 0x7c00 ; 指名程序的装载地址
; 以下一段是标准FAT12格式软盘专用的代码
JMP entry
DB 0x90
DB "HELLOIPL" ; 引导扇区的名称,随意写,8字节
DW 512 ; 每个扇区(sector)的大小,必须是512
DB 1 ; 簇(cluster)的大小,必须为1
DW 1 ; FAT起始位置,一般从第一个扇区开始
DB 2 ; FAT的个数,必须为2
DW 224 ; 根目录大小,一般设置成224
DW 2880 ; 磁盘的大小,必须为2880扇区
DB 0xf0 ; 磁盘的种类,必须为0xf0
DW 9 ; FAT的长度,必须是9扇区
DW 18 ; 1个磁道(track)有几个扇区,必?是18
DW 2 ; 磁头数,必须为2
DD 0 ; 因为不使用分区,所以必须是0,4字?
DD 2880 ; 重写一次磁盘大小,4字节
DB 0,0,0x29 ; 意义不明,固定
DD 0xffffffff ; 卷标号
DB "HELLO-OS " ; 磁盘名称,11字节
DB "FAT12 " ; 磁盘格式名称,8字节
RESB 18 ; 空出18字节
; 程序主体
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,AX
MOV SI,msg ;将msg的地址存放到SI
putloop:
MOV AL,[SI] ;将SI中的地址存放到AL
ADD SI,1 ; SI+1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,14 ; 指定颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop
fin:
HLT ; 让CPU停止,等待指令
JMP fin ; 无限循环
; 信息显示
msg:
DB 0x0a, 0x0a ; 2个换行
DB "Hello!Beauty!"
DB 0x0a ; 换行
DB 0
RESB 0x1fe-$ ; 填写0x00,直到0x001fe
DB 0x55, 0xaa
; 以下是启动区以外部分的输出
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432
在解释这些语句之前,我们先来了解一下CPU以及内存的基本知识。
寄存器是CPU中的一个临时存储数据的地方,由于其就在CPU内部,因而速度极快。我们通常所说的CPU指令,其实是CPU操作寄存器的指令。CPU完成各类运算都是借助寄存器而实现的。因而在底层的语言中(如机器语言、汇编语言),其实都是控制CPU进行寄存器的操作。我们的操作系统程序也不例外,目前已经完成的程序就是使用指令操作寄存器。
目前计算机已经到了32位、64位,其寄存器容量各不相同,但是原理都是相似的。本书中以16位的寄存器为例,其主要有以下几个:
每个不同的寄存器除了可以存放数据以外,还有着各自不同的作用。比如要让计算机在屏幕上显示一个字符,就需要把0x0e存进AH中,然后再调用10号中断指令。这些寄存器的特殊所用,会在日后的课程中不断介绍。
了解完了寄存器,我们就来了解一下汇编语言是如何对寄存器进行操作的。
Mov来自于英语的Move,意为移动,但在这里其实用“赋值”来形容它更为恰当。它是一个赋值语句,语法如下:
Mov 目标地址,原始数据
比如:
Mov AX,0
其实等效于我们在高级语言中的赋值语句
AX=0
其作用就是将0存到AX寄存器中
除了指定寄存器以外,还可以指定地址。比如:
Mov [123],0
在123两边加上了方括号,就表示这个数字是一个内存地址。因而这句语句的含义就是将内存地址为123的内存设置为0,但是这样的语句一般要配合BYTE、WORD、DWORD使用,以确定需要操作多少大小的内存。比如:
Mov WORD [123],0
这句语句就是将内存地址为123开始的两个字节的内存设置为0。
当然还可以这样用:
Mov AX,[BX]
[BX]就表示BX寄存器中的数值所代表的内存地址。比如BX中存放的值为222,那么[BX]就等价于[222],也就是内存中222地址中的内容,将它赋值给AX寄存器。
这个很简单。作用是指明程序装在的地址,也就是把程序装载到内存的哪里。
JMP是英文JUMP就是跳转的意思,这个语句相当于高级语言中的GOTO语句。也就是可以在程序中指定标签,然后跳转到某个标签中。
CMP就是Compare比较的意思,是用来比较两个值是否相等。JE是配合CMP一起用的,就是JUMP IF EQUALS也就是如果相等就执行JUMP指令。在CMP比较后,就直接用JE来判断是否相等,然后相应跳转。相应的还有JNE指令,就是不相等的时候跳转。
加法操作,如下列语句就是将AX中的值加1:
ADD AX,1
让CPU停止,等待指令
调用中断指令
学会了所有的指令,我们就可以梳理所有的程序以及流程了。
1、首先还是定义了系统的一些基本参数
2、在Entry中初始化了一些寄存器:AX,SS,DS,ES,SP,并将msg的地址存入SI中
3、不断累加SI中的地址,并从中读取内容。当内容不等于0时,调用中断显示字符,然后重复本步骤;当内容为0时,跳转到fin
4、fin中让CPU停止,进入等待模式,并不断循环
经过上述步骤,即可显示hello world并让计算机进入一个死循环的等待中。
此处的死循环完全不用担心,因为CPU不会做任何事,只是静静等待。不像我们在程序中如果写了死循环,那可讨厌了,因为程序中始终让CPU在工作,所以会让程序完全无法响应并消耗计算机大量资源。
最初的系统引导,只需要512字节,所以这里把最后的那些DB删除了(其实前面部分的代码已经足够可以完成启动系统了)。
首先使用asm.bat(helloos04)可以得到一个ipl.lst文件,这个文件展示了汇编语言是如何分配到每个存储空间的,看看还挺有意思。
然后用MAKEFILE吧,这个机制设计很有意思。随意引用一段:
ipl.bin : ipl.nas Makefile
../z_tools/nask.exe ipl.nas ipl.bin ipl.lst
这句表示,要制作ipl.bin文件需要ipl.nas以及makefile两个文件,如果两个文件都有了,那就执行第二行的语句生成ipl.bin文件。
这个文件编写好之后,在DOS中只需要输入 MAKE ipl.bin指令,就会按照上述的方法生成了。
然后利用这个特性,就可以将很多东西串起来,然后一步步生成最终所需要的文件并启动操作系统。
本章的MAKEFILE如下:
# 默认动作
default :
../z_tools/make.exe img
# 文件生成规则
ipl.bin : ipl.nas Makefile
../z_tools/nask.exe ipl.nas ipl.bin ipl.lst
helloos.img : ipl.bin Makefile
../z_tools/edimg.exe imgin:../z_tools/fdimg0at.tek \
wbinimg src:ipl.bin len:512 from:0 to:0 imgout:helloos.img
# 命令
asm :
../z_tools/make.exe -r ipl.bin
img :
../z_tools/make.exe -r helloos.img
run :
../z_tools/make.exe img
copy helloos.img ..\z_tools\qemu\fdimage0.bin
../z_tools/make.exe -C ../z_tools/qemu
install :
../z_tools/make.exe img
../z_tools/imgtol.com w a: helloos.img
clean :
-del ipl.bin
-del ipl.lst
src_only :
../z_tools/make.exe clean
-del helloos.img
这个东西到底怎么来的。这是让计算机把这个程序装载到内存地址为0x7c00的地方,这个存储区域只有512字节。为什么是0x7c00呢,这是一个约定。详情可以参见这个网站(原书中的链接已经没用了)。
http://wiki.osdev.org/Memory_Map_(x86)
中断到底是个啥,这个有点复杂。简而言之就是发送一个指令,让CPU执行相应的操作。与之相对的是查询,那个模式下,CPU始终在询问你要干啥,类似于一个死循环,而中断则是CPU平时在睡觉,直到执行了中断指令然后CPU开始工作。
基本的一些中断可以参考这个
http://wiki.osdev.org/BIOS