第2天 汇编语言学习与Makefile入门
2019.12.28-2019.12.29
1. helloos.nas节选详解
helloos.nas
; hello-os ; TAB=4 ORG 0x7c00 ; 指明程序的装载地址 ; 以下的记述用于标准的FAT12格式的软盘 JMP entry DB 0x90 ---(中略)--- ; 程序核心 entry: ; 0x7c50 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: ; 0x7c74 DB 0x0a, 0x0a ; 换行2次 DB "hello, world" DB 0x0a ; 换行 DB 0ORG指令:这个指令会告诉nask,再开始执行的时候,把这些机器语言指令装载到内存的哪个地址。如果没有它,有几个指令就不能正确地被翻译和执行。另外,有了这条指令的话,美元符
$的含义也随之变化,它不再是输出文件的第几个字节,而是代表将要读入的内存地址。
ORG指令源自英文origin。它会告诉nask,程序要从指定的这个地址开始,也就是要把程序装载到内存中指定的地址。
这里指定的地址为0x7c00。这是规定,原因和1+1为什么等于2一样。JMP指令:来源于英文jump,相当于C语言的goto语句。
entry:这是标签的声明,用于指定JMP指令的跳转目的地等。
MOV指令:最常用的指令。功能:赋值。虽然简单,但是要是完全理解了MOV指令,汇编语言也就理解了一大半了。
MOV AX,0相当于AX=0;MOV SS,AX相当于SS=AX。后者赋值给前者。- MOV指令的另一种理解是COPY。执行
MOV SS,AX以后AX的值并没有空,还是保持原来的值不变。
CPU中的寄存器
- AX:accumulator,累加寄存器
- CX:counter,计数寄存器
- DX:data,数据寄存器
- BX:base,基址寄存器
- SP:stack pointer,栈指针寄存器
- BP:base pointer,基址指针寄存器
- SI:source index,源变址寄存器
- DI:destination index,目的变址寄存器
以上寄存器都是16位寄存器,因此可以存储16位的二进制数。
CX是为方便计数而设计的;BX适合作为计算内存地址的基点。
另外,CPU中还有8个8位寄存器:- AL:累加寄存器低位(accumulation low)
- CL:计数寄存器低位(counter low)
- DL:数据寄存器低位(data low)
- BL:基址寄存器低位(base low)
- AH: 累加寄存器高位(accumulation high)
- CH:计数寄存器高位(counter high)
- DH:数据寄存器高位(data high)
- BH: 基址寄存器高位(base high)
AX寄存器有16位,其中0~7位的低8位称为
AL,8~15位的高8位称为AH
因此,CPU还是那个CPU,依然只能存储16个字节。CPU的存储能力真的是太有限了。为什么BP、SP、SI和DI没有分H和L呢?
比如,要分别取SI的高8位和低8位。嘤特尔的设计人员是这样想的:先用>MOV AX,SI,将SI赋值到AX里面去,再用AH和AL取值。32位CPU寄存器
- EAX
- ECX
- EDX
- EBX
- ESP
- EBP
- ESI
- EDI
EAX的低16位是AX,高16位没有名字也没有寄存器编号。因此,我们可以把EAX当做2个16位寄存器来用,但是只有低16位用起来方便;我们想要用高16位的话,需要使用移位命令,将高16位移到低16位使用。
32位CPU的存储能力也只有32字节,同样小的可怜。
对于64位CPU,我们这次不使用64位模式。段寄存器
ES: 附加段寄存器(extra segment)
CS: 代码段寄存器(code segment)
SS: 栈段寄存器(stack segment)
DS: 数据段寄存器(data segment)
FS: 没有名称(segment part 2)
GS: 没有名称(segment part 3)以上段寄存器都是16位寄存器
MOV SI,msg
意思是SI=msg,msg是此行代码下面即将要出现的标号。这句话的意思是将标号赋值给寄存器么?
前面有
JMP entry,其实将它写成JMP 0x7c50是一样的。因为entry代表的就是0x7c50。这句指令的意思就是让CPU去执行内存地址为0x7c50的程序。在汇编语言中,所有的标号仅仅都是单纯的数字。
每个标号对应的数字,都是由汇编语言编译器根据ORG指令计算出来的。ORG指令:这个指令会告诉编译器,在开始执行的时候,把这些机器语言指令装载到内存的哪个地址。编译器计算出来的“标号的地方对应的内存地址”就是那个标号的值。
PS: 可以理解成,标号就是一个数字,这个数字是内存地址。因此,标号就是内存地址。
在这里msg的地址是
0x7c74,因此MOV SI,msg的含义就是,把内存地址0x7c74放入SI寄存器中。MOV AL,[SI]在汇编语言中,这个
[]代表内存。CPU的存储能力很差,因此想要让CPU处理大量的信息,就得必须给CPU另外准备一套用于存储的电路。
因为即便是32位的CPU,把所有的普通寄存器都加在一起,最多也只能存储32个字节,就算把所有的段寄存器也都用上,也才只有44个字节(6个16位的段寄存器,共12字节,加上普通寄存器32字节,共44字节)。内存
- 内存是CPU的外部存储器。
- CPU通过自己的一部分管脚(引线)向内存发送电信号,以读写内存中的数据。(严格来讲,CPU和内存之间还有称为芯片的控制单元)
- CPU与内存之间的电信号交换,并不仅仅是为了存取数据。因为从根本上来讲,程序本身也保存在内存里。程序一般都大于44字节,因此不可能存储在CPU中,所以规定程序必须放在内存里。
- CPU在执行机器语言时,必须从内存中一个命令一个命令地读取程序,顺序执行
- CPU访问内存的速度比访问寄存器的速度慢好几倍。记住这一点,我们才能开发出执行速度快的程序来。
MOV指令的数据传送源和传送目的地不仅可以是寄存器或常数,也可以是内存地址。这个时候,我们使用方括号
[]来表示内存地址。举个栗子
MOV BYTE [678], 123
这个指令是要用内存的“678”号地址来保存123这个数值。虽然指令里面有数字,看起来像那么回事,但实际上内存和CPU一样,根本没有什么是数值的概念。所谓的“678”不过就是一大串开(ON)或者关(OFF)的电信号而已。当内存收到这样的一串电信号时,电路中的某8个存储单元就会响应,这8个存储单元会记住代表“123”的开(ON)或关(OFF)的电信号。为什么是8位呢?因为指令里制定了“BYTE”(汇编语言保留字)。同样我们还可以写成:
MOV WORD [678],123
在这种情况下,内存地址中678号和旁边的679号都会做出反应,一共是16位。这时,123会被解释成为一个16位的数值(WORD是保留字,16位),也就是0000000001111011,低位的01111011保存在678号,高位的00000000保存在旁边的679号。高位高地址:小端模式
大小端模式数据大小 [地址]
这时一个固定的组合。如果我们指定“数据大小”为BYTE,那么使用的存储单元就只是地址所指定的字节。如果我们指定数据大小为WORD,则相邻(相邻的意思是指地址增加方向的相邻)的一个字节也会变成这个指令的操作对象。如果指定DWORD,则与WORD相邻的两个字节(加上这两个,共四个字节)也都会成为指令的操作对象。
至于内存地址的指定方法,我们不仅可以使用常数,还可以使用寄存器。比如
BYTE [SI],如果SI中保存的是987,那么BYTE [SI]会被解释成BYTE [987],即指定地址为987的内存。虽然我们可以使用寄存器来指定内存地址,但是只有
BX、BP、SI和DI。剩下的AX、CX、DX和SP都不能用来指定内存地址(嘤特尔大叔们的奇怪设计)。因此,我们想把DX内存里的内容赋值给AL的时候,应该这样写:
MOV BX, DX MOV AL, BYTE [BX]根据以上,我们可以用下面的指令将
SI地址的1字节内容写到AL中。
MOV AL, BYTE [SI]
同时,MOV指令有一个规则,那就是源数据和目的数据的位数必须相同。也就是说,能向AL里带入的只有BYTE,这样可以改写成:
MOV AL, [SI]
这个指令的意思是:把SI地址的一个字节的内容读入AL中。
这个规则的含义是:
[SI]的含义是一个地址,这个地址是8位的。AL也是8位的,所以是可以简写的。但是,MOV AX, CL就会编译出错,AX是16位,CL是8位。ADD SI, 1
ADD是加法指令。这句指令的意思是:SI = SI + 1,即让SI自加1。CMP AL, 0
CMP是比较指令。这句指令的意思是将AL中的值和0进行比较。一般来讲,CMP指令的后面都会有JE或其他指令。JE fin
JE指令是跳转指令之一。跳转指令:根据比较的结果决定跳转或者不跳转。
JE指令:如果比比较结果相等,就跳转;如果比较结果不等,就不跳转,继续执行下一条指令。
因此,CMP AL, 0 JE fin这两条指令在一起相当于,
if (AL == 0){ goto fin; }其实,
JE指令来源于英语“Jump if Equal”,意思是如果相等就跳转。
fin是一个标号,表示结束(finish)的意思。INT 0x10
INT指令是中断指令。中断指令以后再讲(PS:挖坑)。电脑里有个名叫
BIOS的程序,出厂时就组装在电脑主板上的ROM单元里。ROM:只读存储器,"Read Only Memory"的缩写。**切开单电源以后内容不会消失。电脑厂家在
BIOS中预先写入了操作系统开发人员经常会用到的一些程序,非常方便。
BIOS的全称是"Base Input Output System"。直译过来就是“基本输入输出系统(程序)”。
现在电脑的BIOS的功能非常多,甚至包括了电脑设定的画面。不够它的本质正如其名,就是为操作系统开发人员准备的各种函数的集合。INT指令就是用来调用这些函数的指令。INT指令后面紧跟着一个数字,使用不同的数字可以调用不同的函数。比如,0x10(即16)号函数,它的功能是控制显卡。关于BIOS的网址 PS:不知为啥访问不了qwq
比如,现在要显示文字,假设先显示一个字。
首先,显示文字,及得查看你与显卡有关的函数,即16号函数。查找有关16号函数的信息:显示一个字符
AH = 0x0e; AL = character code; BH = 0; BL = color code;无返回值
注:beep、退格(back space)、CR和LF都会当成控制字符处理。现在,
AH=0x0e,因为有代码:MOV AH, 0x0e。
AL=[SI],因为有代码:MOV AL, [SI]。
BH=0,BL=15因为有代码:MOV BX, 15。HLT
HLT是“halt”的缩写,意思是停止。HLT指令是让CPU停止动作的指令,不过不是彻底停止(如果要彻底停止CPU的动作,只能切断电源),而是让CPU进入待机状态。只要外部发生变化,比如按下键盘,或是移动鼠标,CPU就会醒过来,继续执行程序。仔细观察程序,就会发现,其实有没有
HLT指令,也是进入无限循环。
但是,如果没有HLT指令,那么CPU就会不停地去执行JMP指令,让CPU的负荷达到100%。因此,使用HLT指令,既环保又节约电费(PS:手动狗头)。(PS:终于把这个程序从头到尾讲完了)
用C语言改写之后的helloos.nas程序节选
entry: AX = 0; SS = AX; SP = 0x7c00; DS = AX; ES = AX; SI = msg; putloop: AL = BYTE [SI]; SI = SI + 1; if (AL == 0){ goto fin; } AH = 0x0e; BX = 15; INT 0x10; goto putloop; fin: HLT; goto fin;有了这个程序,我们才能把
msg中写入的数据,一个字符一个字符地显示出来,并且当数据编程0的时候,HLT命令就会让程序进入无限循环。神奇的
0x7c00内存中的所有地址我们都可以用么?
显然,不可能。比如,内存的0号地址,也就是最开始的地方,是
BIOS程序用来实现不同功能的地方,如果我们使用的话,就会和BIOS发生冲突,这时,结果不但是BIOS会出错,我们的程序也会出错。另外,在内存的0xf0000号地址附近还放着BIOS程序本身,那里我们也不能使用。
内存中有不少地方是不能用的。详见"内存地图"。PS:不知道为啥访问不了
其中,
0x00007c00-0x00007dff:启动区内容的装载地址。
0x00007c00是嘤特尔大叔们规定的。只有程序的装载地址是0x7c00的时候,我们的程序才能正确地启动。
2. 制作启动区
projects/02_day的helloos4文件夹
考虑到以后的开发,我们不要一下纸就用nask来做整个磁盘映像,而是先只用它来制作512字节的启动区,剩下的部分我们用磁盘映像管理工具来做,这样以后用起来就方便了。
首先,我们把helloos.nas的后半部分裁掉了,这是因为启动区只需要最初的512字节。现在这个程序就仅仅是用来制作启动区的,所以我们把这个文件名也改成了ipl.nas。
ipl.nas
asm.bat..\z_tools\nask.exe ipl.nas ipl.bin ipl.lstmaking.bat..\z_tools\edimg.exe imgin:../z_tools/fdimg0at.tek wbinimg src:ipl.bin len:512 from:0 to:0 imgout:helloos.imgmaking.bat的大致意思是:用磁盘管理工具edimg.exe,先读入一个空白的磁盘映像文件,然后在开头写入ipl.nas的内容,最后将结果输出为名为helloos.img的磁盘映像文件。
这样,从编译到测试的步骤就变得非常简单了:打开终端,依次输入asm.bat->making.bat->run.bat。helloos4文件夹

打开终端进入目录,依次输入命令:
会看到在QEMU中运行:
3. Makefile入门
Makefile的写法
首先生成一个不带拓展名的文件Makefile,然后用文本编辑器写入如下内容:ipl.bin : ipl.nas Makefile ../z_tools/nask.exe ipl.nas ipl.bin ipl.lst helloos.img : ipl.bin Makefile ../z_tools/edimg imgin:../z_tools/fdimg0at.tek \ wbinimg src:ipl.bin len:512 from:0 to:0 imgout:helloos.img#代表注释。ipl.bin : ipl.nas Makefile的意思是,如果想要制作文件ipl.bin,就先检查一下ipl.nas和Makefile这两个文件是否都准备好了。如果这两个文件都有了,Make工具就会自动执行Makefile的下一行。同理,
helloos.img的生成方法类似,其中\的含义是续行符号,表示这一行写不下了,跳到下一行继续写。我们需要调用工具
make.exe来让这个Makefile发挥作用。为了方便地从命令行窗口运行这个工具,我们需要写一个make.bat。make.bat..\z_tools\make.exe %1 %2 %3 %4 %5 %6 %7 %8 %9打开命令行,然后输入
make -r ipl.bin,这样make.exe就启动了,它首先读取Makefile文件,寻找制作ipl.bin的方法。因为ipl.bin的制作方法就写在Makefile里面,make.exe找到了这一行就去执行其中的命令,顺利生成ipl.bin。然后我们再输入make -r helloos.img,同理,会启动make.exe,并按照的Makefile中制作helloos.img的方法去执行对应的命令,最终成功生成helloos.img。我们把
ipl.bin和helloos.img删除以后。在命令行输入make -r helloos.img,make首先很听话地去试图生成helloos.img,但是发现ipl.bin还不存在,于是make又去找生成ipl.bin的方法,找到以后,先生成ipl.bin然后又生成helloos.img。(PS:睿智的make)下面,我么在此基础上不删除任何文件,在命令行输入
make -r helloos.img,就会发现,命令行输出为:
会有'helloos.img' is up to date.的输出,什么命令都不会执行,也就是说,make知道helloos.img已经存在,没必要重新做一遍了。(PS:睿智的make*2)下面,我们编辑ipl.nas的内容,把它改成任意你喜欢的内容并保存起来。而ipl.nas和helloos.img保持原来的样子不变,在这种情况下,我们再次执行代码
make -r helloos.img。结果发现,make.exe又重新开始生成输出文件。也就是说,make.exe不仅仅判断输入文件是否存在,还会判断文件的更新时间,并根据此来决定是否重新生成输出文件。(PS:睿智的make*3)helloos5文件夹下的
Makefiledefault : ../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输入
make img即可生成helloos.img
输入make run即可生成相应文件,并出现QEMU。
输入make clean即可删除ipl.bin和ipl.lst。
输入make,即不带参数的make,执行默认(写在第一行),即执行make img。PS:make yes!
4. 今天是我的好哥们SX的生日,今晚我们大家吃了蛋糕,祝他生日快乐!
