我们在昨天做了一个简单的输出一句话的小扇区,现在我们需要继续学习。
这个我们就不写了,我推荐VSCode,听说notepad++的作者是港独,鄙视他所以不用np++,不多谈了sublime也不错的。
接下来,作者想让我们继续学习他写的代码,我们把这些代码直接po上来(我根据我昨天写的代码进行了一些更改,与作者的并不完全相同):
; hello-os
; TAB=4
ORG 0x7c00 ; 指明程序的装载地址
; 以下这段是标准FAT12格式软盘专用的代码
JMP entry
DB "HELLOSON" ; 启动区的名称可以是任意的字符串(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
DD 2880 ; 重写一次磁盘大小
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
putloop:
MOV AL,[SI]
ADD SI,1 ; 给SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop
fin:
HLT ; 让CPU停止,等待指令
JMP fin ; 无限循环
msg:
DB 0x0a, 0x0a ; 换行2次
DB "wo shi ni die"
DB 0x0a ; 换行
DB 0
RESB 0x7dfe-$ ; 补全
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
我们将它与昨天的程序做一下对比,我们不难发现,它和昨天的是一样的,只不过中间原本直接书写二进制的部分换成了一些指令罢了。那么接下来我们就来看一下这些指令都是干啥的。
ORG 0x7c00
:告诉nask(也就是编译器)把程序装载到指定的地址,也就是说这个程序会被装载到内存中0x7c00
这个地方,至于为啥,等后面再看吧。entry:
:学过一点点汇编的都知道,这就是个标识符,用来做跳转之类的MOV rs rd
:就是简单的赋值语句,MOV AX,0
表示把0赋给AX
寄存器下面呢就可以介绍一下一些常用的寄存器了(在这个nask指令集架构下的):
AX
——accumulator,累加寄存器CX
——counter,计数寄存器DX
——data,数据寄存器BX
——base,基址寄存器SP
——stack pointer,栈指针寄存器BP
——base pointer,基址指针寄存器SI
——source index,源变址寄存器DI
——destination index,目的变址寄存器这些寄存器都是十六位寄存器,可以用于储存、操作16位的二进制数。
除此之外,还有八个八位寄存器:
AL
——累加寄存器低位(accumulator low)
CL
——计数寄存器低位(counter low)
DL
——数据寄存器低位(data low)
BL
——基址寄存器低位(base low)
AH
——累加寄存器高位(accumulator high)
CH
——计数寄存器高位(counter high)
DH
——数据寄存器高位(data high)
BH
——基址寄存器高位(base high)
大家可能注意到了,这里的ABCD都在上面出现过,没错,这里的AL和AH共同组成了上面的AX,也就是说你更改AL的值,上面的AX也会随之改变。
除此之外,还有三十二位的寄存器:
EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI
,正如刚才所讲,这个实质上就是拓展后的AX等等,AX也是他们的一部分,但是不幸的是,这些寄存器的高位并不能进行直接的读取、操作。我们如果想要知道高16位是什么,就需要右移16位,使用高16位的内容覆盖掉低16位的内容才能看到高16位是什么。
当然,除了这八种之外,还有一种叫做段寄存器,下面是名字,具体的内容作者还没有讲述:
ES
——附加段寄存器(extra segment)
CS
——代码段寄存器(code segment)
SS
——栈段寄存器(stack segment)
DS
——数据段寄存器(data segment)
FS
——没有名称(segment part 2)
GS
——没有名称(segment part 3)
接下来,我们需要回到我们的程序,来看看我们的程序在执行过程中发生了什么。在此之前,我们先来顺序解释一下,我们看的懂的部分:
JMP entry
语句,JMP就是jump的缩写,通过这条语句,我们跳转到了entry:
标签所在的代码段
entry
实质上就是一个助记符,如果我们使用MOV AX, entry
那么我们就会将entry所在的地址赋值给AXMOV AX msg
就是如此,他将这段代码的起始地址赋给了AXentry
,开始了一系列的初始化接下来我们需要理解的是:MOV SS,AX
这样的语句的作用。我们先来看一个其他的语句:MOV BYTE [678],123
,首先BYTE、WORD、DWORD等英文词也都是汇编语言的保留字。这个指令是要用内存的“678”号地址来保存“123”这个数值。之所以是八位,这是因为指令里指定了“BYTE”。
当然,我们来看这样的语句:MOV WORD [678],123
,这里就相当于将123这个数字,以16位的形式存储在678开头的内存区域中,如下图所示
如果大家自己手写过mips的cpu的话,应该非常容易理解这个,这里实际上就是SB、SH、SW的区别。那么返回我们刚才的语句**MOV SS,AX
,这个实质上,就是把AX
中的值赋值给了SS
,但是如果语句是MOV AL, BYTE [BX]
**,就是将内存BX所在的块的值赋值给AL。当然,需要注意的是:只有BX、BP、SI、DI这几个寄存器可以在这个过程中用于指定地址,也就是说如果我们想把DX内存中的值赋值给AL,需要这样写:
MOV BX, DX
MOV AL, BYTE [BX]
再往后看,
ADD SI,1
就是SI = SI + 1
CMP AL,0
就是将AL中的值与0进行比较
CMP后面紧跟着JE fin
这两句语句共同组成了一个判断语句,用C语言来写就是:if(AL == 0) goto fin;
再往后看INT 0x10
,这里的int就是软件中断,在电脑中有个名为BIOS的程序,出厂时就组装在电脑主板上的ROM中,里面有一些程序,这里我们使用INT 0x10
实际上做的就是停止掉我们现在在干的事情,转到BIOS中的16号程序。
关于上面的INT,我们可以通过这种方法来现实一些文字:
INT x10
到此为止,如果大家用心读的话那么这个程序就非常好懂了:
首先我们来看:
msg:
DB 0x0a, 0x0a ; 换行2次
DB "wo shi ni die"
DB 0x0a ; 换行
DB 0
这里是msg部分,这里就是在给他附了一堆二进制数字,这里实际上不是程序,而是一些存储字符的地方。我们需要注意的是,这里以纯0结束.
进入putloop之前,我们执行了MOV SI,msg
也就是说,我们把msg的首地址赋值给了SI
接下来就进入了PUTLOOP:
putloop:
MOV AL,[SI]
ADD SI,1 ; 给SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop
这里我们会把对应内存部分(也就是代码段中的内容存储到AL中),然后再进行输出的例程,当然,如果我们的内容为0的话,就会退出,这也说明,如果是有字符的话,这里不会全0的。
如果全0的话,就会进入fin:
fin:
HLT ; 让CPU停止,等待指令
JMP fin ; 无限循环
这里的HLT就是halt,这个就是停止的意思,这条语句会让计算机基本处于睡眠状态,可以省很多电。如果不加的话,计算机就会疯狂执行jump,这样负荷非常高。
最后再来解释ORG 0x7c00
,这里是要把我们的程序装配到这个地方,之所以是0x7c00
是因为在前面的部分还装配着bios的程序。
0x00007c00-0x00007dff :启动区内容的装载地址
如果你真正的理解这个程序的话,你就会发现我前面写的那个长的代码有错,具体来说是多了一块:
; hello-os
; TAB=4
ORG 0x7c00 ; 指明程序的装载地址
; 以下这段是标准FAT12格式软盘专用的代码
JMP entry
DB "HELLOSON" ; 启动区的名称可以是任意的字符串(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
DD 2880 ; 重写一次磁盘大小
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
putloop:
MOV AL,[SI]
ADD SI,1 ; 给SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop
fin:
HLT ; 让CPU停止,等待指令
JMP fin ; 无限循环
msg:
DB 0x0a, 0x0a ; 换行2次
DB "wo shi ni die"
DB 0x0a ; 换行
DB 0
RESB 0x7dfe-$ ; 补全
DB 0x55, 0xaa
接下来是第二部分。
这里作者做的事情就是把这个文件改了个名,然后改了一下所谓的编译方式,我们来看一下他这个改后的编译方式是怎么样的。
首先:
..\z_tools\nask.exe ipl.nas ipl.bin ipl.lst
这里同时将这个nas 编译成了 .bin
和.lst
文件。这个.lst
是一个文本文件,可以用来简单地确认每个指令是怎样翻译成机器语言的。
所以我们改一下我们的asm.bat
C:\Users\wangsy\Desktop\WSY-OS\REF\tolset\z_tools\nask.exe .\ipl.nas .\ipl.bin .\ipl.lst
我们看一下这个ipl.lst
1 00000000 ; hello-os
2 00000000 ; TAB=4
3 00000000
4 ORG 0x7c00 ; 指明程序的装载地址
5 00007C00 ; 以下这段是标准FAT12格式软盘专用的代码
6 00007C00
7 00007C00 EB 4D JMP entry
8 00007C02 48 45 4C 4C 4F 53 4F 4E DB "HELLOSON" ; 启动区的名称可以是任意的字符串(8字节)
9 00007C0A 0200 DW 512 ; 每个扇区(sector)的大小(必须为512字节)
10 00007C0C 01 DB 1 ; 簇(cluster)的大小(必须为1个扇区)
11 00007C0D 0001 DW 1 ; FAT的起始位置(一般从第一个扇区开始)
12 00007C0F 02 DB 2 ; FAT的个数(必须为2)
13 00007C10 00E0 DW 224 ; 根目录的大小(一般设成224项)
14 00007C12 0B40 DW 2880 ; 该磁盘的大小(必须是2880扇区)
15 00007C14 F0 DB 0xf0 ; 磁盘的种类(必须是0xf0)
16 00007C15 0009 DW 9 ; FAT的长度(必须是9扇区)
17 00007C17 0012 DW 18 ; 1个磁道(track)有几个扇区(必须是18)
18 00007C19 0002 DW 2 ; 磁头数(必须是2)
19 00007C1B 00000000 DD 0 ; 不使用分区,必须是0
20 00007C1F 00000B40 DD 2880 ; 重写一次磁盘大小
21 00007C23 00 00 29 DB 0,0,0x29 ; 意义不明,固定
22 00007C26 FFFFFFFF DD 0xffffffff ;(可能是)卷标号码
23 00007C2A 48 45 4C 4C 4F 2D 4F 53 20 20 DB "HELLO-OS " ; 磁盘的名称(11字节)
00007C34 20
24 00007C35 46 41 54 31 32 20 20 20 DB "FAT12 " ; 磁盘格式名称(8字节)
25 00007C3D 00 00 00 00 00 00 00 00 00 00 RESB 18 ; 先空出18字节
00007C47 00 00 00 00 00 00 00 00
26 00007C4F
27 00007C4F ; 程序主体
28 00007C4F entry:
29 00007C4F B8 0000 MOV AX,0 ; 初始化寄存器
30 00007C52 8E D0 MOV SS,AX
31 00007C54 BC 7C00 MOV SP,0x7c00
32 00007C57 8E D8 MOV DS,AX
33 00007C59 8E C0 MOV ES,AX
34 00007C5B
35 00007C5B BE 7C73 MOV SI,msg
36 00007C5E putloop:
37 00007C5E 8A 04 MOV AL,[SI]
38 00007C60 83 C6 01 ADD SI,1 ; 给SI加1
39 00007C63 3C 00 CMP AL,0
40 00007C65
41 00007C65 74 09 JE fin
42 00007C67 B4 0E MOV AH,0x0e ; 显示一个文字
43 00007C69 BB 000F MOV BX,15 ; 指定字符颜色
44 00007C6C CD 10 INT 0x10 ; 调用显卡BIOS
45 00007C6E EB EE JMP putloop
46 00007C70 fin:
47 00007C70 F4 HLT ; 让CPU停止,等待指令
48 00007C71 EB FD JMP fin ; 无限循环
49 00007C73
50 00007C73 msg:
51 00007C73 0A 0A DB 0x0a, 0x0a ; 换行2次
52 00007C75 77 6F 20 73 68 69 20 6E 69 20 DB "wo shi ni die"
00007C7F 64 69 65
53 00007C82 0A DB 0x0a ; 换行
54 00007C83 00 DB 0
55 00007C84
56 00007C84 00 00 00 00 00 00 00 00 00 00 RESB 0x7dfe-$ ; 补全
00007C8E 00 00 00 00 00 00 00 00 00 00
00007C98 00 00 00 00 00 00 00 00 00 00
00007CA2 00 00 00 00 00 00 00 00 00 00
00007CAC 00 00 00 00 00 00 00 00 00 00
00007CB6 00 00 00 00 00 00 00 00 00 00
00007CC0 00 00 00 00 00 00 00 00 00 00
00007CCA 00 00 00 00 00 00 00 00 00 00
00007CD4 00 00 00 00 00 00 00 00 00 00
00007CDE 00 00 00 00 00 00 00 00 00 00
00007CE8 00 00 00 00 00 00 00 00 00 00
00007CF2 00 00 00 00 00 00 00 00 00 00
00007CFC 00 00 00 00 00 00 00 00 00 00
00007D06 00 00 00 00 00 00 00 00 00 00
00007D10 00 00 00 00 00 00 00 00 00 00
00007D1A 00 00 00 00 00 00 00 00 00 00
00007D24 00 00 00 00 00 00 00 00 00 00
00007D2E 00 00 00 00 00 00 00 00 00 00
00007D38 00 00 00 00 00 00 00 00 00 00
00007D42 00 00 00 00 00 00 00 00 00 00
00007D4C 00 00 00 00 00 00 00 00 00 00
00007D56 00 00 00 00 00 00 00 00 00 00
00007D60 00 00 00 00 00 00 00 00 00 00
00007D6A 00 00 00 00 00 00 00 00 00 00
00007D74 00 00 00 00 00 00 00 00 00 00
00007D7E 00 00 00 00 00 00 00 00 00 00
00007D88 00 00 00 00 00 00 00 00 00 00
00007D92 00 00 00 00 00 00 00 00 00 00
00007D9C 00 00 00 00 00 00 00 00 00 00
00007DA6 00 00 00 00 00 00 00 00 00 00
00007DB0 00 00 00 00 00 00 00 00 00 00
00007DBA 00 00 00 00 00 00 00 00 00 00
00007DC4 00 00 00 00 00 00 00 00 00 00
00007DCE 00 00 00 00 00 00 00 00 00 00
00007DD8 00 00 00 00 00 00 00 00 00 00
00007DE2 00 00 00 00 00 00 00 00 00 00
00007DEC 00 00 00 00 00 00 00 00 00 00
00007DF6 00 00 00 00 00 00 00 00
57 00007DFE
58 00007DFE 55 AA DB 0x55, 0xaa
可以看到,左边的是二进制,右边的是相应的代码
接下来有一个用这个bin来做一个img的文件:
C:\Users\wangsy\Desktop\WSY-OS\REF\tolset\z_tools\edimg.exe imgin:C:\Users\wangsy\Desktop\WSY-OS\REF\tolset\z_tools\fdimg0at.tek wbinimg src:ipl.bin len:512 from:0 to:0 imgout:helloos.img
然后启动部分不变就可以了。
我嫌每次写路径都太麻烦了,于是我就将z_tools
这个文件夹加到环境变量里面了,这样我们就可以不用再去写路径,而是直接make
或是直接nask
就可以调用我们之前的那个.exe
文件了。
接下来我们学习一下作者的这个文件:
#文件生成规则
ipl.bin : ipl.nas Makefile
nask ipl.nas ipl.bin ipl.lst
helloos.img : ipl.bin Makefile
edimg.exe imgin:fdimg0at.tek \
wbinimg src:ipl.bin len:512 from:0 to:0 imgout:helloos.img
这里两个高亮的,就是我们想要生成的东西也就是ipl.bin
和helloos.img
那么需要生成ipl.bin
需要先检查是否有ipl.nas
和Makefile两个文件,如果没有的话会报错,为了生成它执行的语句是nask ipl.nas ipl.bin ipl.lst
。
为了方便删除,我们将这个文件改成这样:
#文件生成规则
helloos.img : ipl.bin Makefile
C:\Users\wangsy\Desktop\WSY-OS\REF\tolset\z_tools\edimg.exe imgin:C:\Users\wangsy\Desktop\WSY-OS\REF\tolset\z_tools\fdimg0at.tek wbinimg src:ipl.bin len:512 from:0 to:0 imgout:helloos.img
ipl.bin : ipl.nas Makefile
nask.exe .\ipl.nas .\ipl.bin .\ipl.lst
clean:
rm ipl.bin ipl.lst helloos.img
我们可以输入make
来生成,同时可以输入make clean
来删除输入的东西,接下来,我们将原有的功能进行整合,做一个新的文件就可以了。
当然,在此之前,我们需要先来研究一下这个makefile文件的格式,第一行是我们的这个最终的生成目标,或者是叫做默认的生成目标,它需要依赖的是ipl.bin,如果这个文件夹下有ipl.bin,那么它就拿来直接用了,如果没有他就会从其他的目标中去寻找ipl.bin,并且执行相应语句进行生成。当然除此之外还可以像clean一样,这种不生成目标而执行语句的,我们可以使用make clean
的方法来调用下面相应的语句。
那么我们现在希望能够达到的效果是:
make clean
make
make run
输入这三句话,第一句话负责将原来的东西删除掉
第二句话负责生成相应的文件
第三句话进行装配与启动,我们现在还需要来写一个run,说白了就是直接把原来的run.bat
直接搬运过来就好了
最后整完之后,我们调整了一下,变成了这个样子:
#文件生成规则
TOOLS_DIR=C:\Users\wangsy\Desktop\WSY-OS\REF\tolset\z_tools
QEMU_DIR=$(TOOLS_DIR)\qemu
helloos.img : ipl.bin Makefile
edimg.exe imgin:$(TOOLS_DIR)\fdimg0at.tek wbinimg src:ipl.bin len:512 from:0 to:0 imgout:helloos.img
ipl.bin : ipl.nas Makefile
nask.exe .\ipl.nas .\ipl.bin .\ipl.lst
clean:
rm ipl.bin ipl.lst helloos.img
run:
rm $(QEMU_DIR)\fdimage0.bin
cp helloos.img $(QEMU_DIR)\fdimage0.bin
make.exe -C $(QEMU_DIR)
这里面涉及到了一点点变量的概念,聪明如你,肯定能看得出来