004-写一个真正的启动盘

       上次说到了使用软盘启动计算机的注意事项,那么,我们就来实际的操作一下,写一个真正可以用于启动计算机的启动盘。

       首先,我们打开BZ,创建一个文件,然后按照以下方式来书写:

后面就全是0了,因为还没写东西,注意要一直写满1440KB


       当然,这样的话还不能真正启动计算机,因为指令不对,我们说,当计算机检查了磁盘格式,以及510和511字节的内容后,会执行1到3字节的指令,所以我们的程序就从这里开始。不过应该能看出来,这直接往下写是不可能的,因为从第4字节开始就是数据了。图灵计算机有一大特点,就是指令和数据共同存储,按照不同的解释方式来决定执行什么样的动作,那这里就显示得淋漓尽致了。我们为了能让它运行更多的指令,所以只能在这一条宝贵的地方加一个跳转语句,然后把我们的代码写到后面比较宽敞的位置。

       明白了这个道理以后,我们前面使用机器语言编程的部分就差不多了,如果继续写下去那真的有点非人类了,不过这也能体会到以前的程序员有多辛苦。

       那么,要想让我们更好的来书写代码,有能够很方便的在计算机上运行,我们引入了编译器。这里我们使用的是nask编译器(这也是川合先生写的,nasm为该编译器的原型)。机器语言不方便人类阅读和记忆,于是,针对于这些机器码要做的事情,编写了助记符,再通过添加一些辅助符号,使得程序更好理解,这样就诞生了汇编语言,汇编语言绝大多数都是机器语言的直接助记符,所以非常容易被编译成机器代码,当然,也要配合一些其他的辅助命令来更方便我们使用,然后再由编译器把它翻译成机器指令。

       刚开始阅读汇编代码的时候非常不习惯,最主要的原因就在于,数据和指令的混合书写,所以这里必须明白,数据和指令原本没有区别,只是我们解释方法不同而已,如果要看指令,我们要摸清计算机执行指令的顺序,而这个时候就不要被数据所干扰。再次强调,指令和数据是混合书写的,所以很多时候感觉比较乱,我们整理出以下汇编代码:

     1                                          	org 0x7c00			; 指定程序的装载位置
     2 00007C00 EB 4B                           	jmp entry			; 转到entry标签下(7c4d)
     3 00007C02 90                              	db 0x90
     4 00007C03 48 45 4C 4C 4F 49 50 4C         	db "HELLOIPL"
     5 00007C0B 0200                            	dw 512
     6 00007C0D 01                              	db 1
     7 00007C0E 0001                            	dw 1
     8 00007C10 02                              	db 2
     9 00007C11 00E0                            	dw 224
    10 00007C13 0B40                            	dw 2880
    11 00007C15 F0                              	db 0xf0
    12 00007C16 0009                            	dw 9
    13 00007C18 0012                            	dw 18
    14 00007C1A 0002                            	dw 2
    15 00007C1C 00000000                        	dd 0
    16 00007C20 00000B40                        	dd 2880
    17 00007C24 00 00 29                        	db 0, 0, 0x29
    18 00007C27 FF                              	db 0xffffffff
    19 00007C28 48 45 4C 4C 4F 2D 4F 53 20 20   	db "HELLO-OS   "
       00007C32 20 
    20 00007C33 46 41 54 31 32 20 20 20         	db "FAT12   "
    21 00007C3B 00 00 00 00 00 00 00 00 00 00   	resb 18
       00007C45 00 00 00 00 00 00 00 00 
    22 00007C4D                                 	
    23 00007C4D                                 entry:
    24 00007C4D B8 0000                         	mov ax, 0
    25 00007C50 8E D8                           	mov ds, ax
    26 00007C52 BE 7C6A                         	mov si, msg
    27 00007C55                                 putloop:
    28 00007C55 8A 04                           	mov al, [si]
    29 00007C57 83 C6 01                        	add si, 1
    30 00007C5A 3C 00                           	cmp al, 0
    31 00007C5C 74 09                           	je  fin
    32 00007C5E B4 0E                           	mov ah, 0x0e
    33 00007C60 BB 000F                         	mov bx, 15
    34 00007C63 CD 10                           	int 0x10
    35 00007C65 EB EE                           	jmp putloop
    36 00007C67                                 	
    37 00007C67                                 fin:
    38 00007C67 F4                              	hlt
    39 00007C68 EB FD                           	jmp fin
    40 00007C6A                                 	
    41 00007C6A                                 msg:
    42 00007C6A 0D 0A                           	db 0x0d, 0x0a
    43 00007C6C 48 65 6C 6C 6F 20 57 6F 72 6C   	db "Hello World!"
       00007C76 64 21 
    44 00007C78 0D 0A 00                        	db 0x0d, 0x0a, 0x00
    45 00007C7B                                 	
    46 00007C7B 00 00 00 00 00 00 00 00 00 00   	resb 0x7dfe-$
       00007C85 00 00 00 00 00 00 00 00 00 00 
       00007C8F 00 00 00 00 00 00 00 00 00 00 
       00007C99 00 00 00 00 00 00 00 00 00 00 
       00007CA3 00 00 00 00 00 00 00 00 00 00 
       00007CAD 00 00 00 00 00 00 00 00 00 00 
       00007CB7 00 00 00 00 00 00 00 00 00 00 
       00007CC1 00 00 00 00 00 00 00 00 00 00 
       00007CCB 00 00 00 00 00 00 00 00 00 00 
       00007CD5 00 00 00 00 00 00 00 00 00 00 
       00007CDF 00 00 00 00 00 00 00 00 00 00 
       00007CE9 00 00 00 00 00 00 00 00 00 00 
       00007CF3 00 00 00 00 00 00 00 00 00 00 
       00007CFD 00 00 00 00 00 00 00 00 00 00 
       00007D07 00 00 00 00 00 00 00 00 00 00 
       00007D11 00 00 00 00 00 00 00 00 00 00 
       00007D1B 00 00 00 00 00 00 00 00 00 00 
       00007D25 00 00 00 00 00 00 00 00 00 00 
       00007D2F 00 00 00 00 00 00 00 00 00 00 
       00007D39 00 00 00 00 00 00 00 00 00 00 
       00007D43 00 00 00 00 00 00 00 00 00 00 
       00007D4D 00 00 00 00 00 00 00 00 00 00 
       00007D57 00 00 00 00 00 00 00 00 00 00 
       00007D61 00 00 00 00 00 00 00 00 00 00 
       00007D6B 00 00 00 00 00 00 00 00 00 00 
       00007D75 00 00 00 00 00 00 00 00 00 00 
       00007D7F 00 00 00 00 00 00 00 00 00 00 
       00007D89 00 00 00 00 00 00 00 00 00 00 
       00007D93 00 00 00 00 00 00 00 00 00 00 
       00007D9D 00 00 00 00 00 00 00 00 00 00 
       00007DA7 00 00 00 00 00 00 00 00 00 00 
       00007DB1 00 00 00 00 00 00 00 00 00 00 
       00007DBB 00 00 00 00 00 00 00 00 00 00 
       00007DC5 00 00 00 00 00 00 00 00 00 00 
       00007DCF 00 00 00 00 00 00 00 00 00 00 
       00007DD9 00 00 00 00 00 00 00 00 00 00 
       00007DE3 00 00 00 00 00 00 00 00 00 00 
       00007DED 00 00 00 00 00 00 00 00 00 00 
       00007DF7 00 00 00 00 00 00 00 
    47 00007DFE 55 AA                           	db 0x55, 0xaa
    48 00007E00  

上面给出的lst文件,左半部分是把汇编语言编译成的机器代码,也就是是说,我们实际上写的应该是左边的部分,把左边这些机器码直接写进img里也是没问题的。第1行表示该程序在内存中的加载位置,至于为什么选择7c00,这是由于内存分布规定的,由于计算机硬件的原因(包括BIOS的加载),规定启动区就只能放到0x7c00-0x7dff这512字节的位置,这也就是为什么我们的启动区部分只能写512字节。第2行是一个跳转指令,因为紧接着的都是一些数据,这些数据是不能当做指令来执行的,所以这里做一次跳转,跳转到0x7c50处,由于我们已经给这个地址跳转位置起了名字,所以写entry就可以了。第3行的这个数据其实是个废弃的,因为jmp指令只占2个字节,留这一个字节是因为,如果你写的是一个3字节的指令,那就必须用到这一个字节了,所以现在的情况,写什么数据都随便。24行和25行其实是为了实现给ds寄存器赋值为0,由于CPU电路设计的问题,不存在直接给ds赋值的机器指令,自然也就不存在类似于mov ds, 0这样的汇编语句(所以,汇编语言虽然更加符合人类的语言习惯,但是,你还是必须用计算机的思路来思考问题,否则的话,很多问题是根本无解的),我们必须通过ax间接给ds赋值,这个ax是一个通用寄存器,虽然说刚诞生的时候,它叫做加法寄存器,与其他的三个通用寄存器(bx基址寄存器,cx计数寄存器,dx数据寄存器)都有着其独特的用途,但是之后由于硬件的改进以及汇编语言的发展,现在很少再去对这4个寄存器的具体通途进行区分,尤其是在保护模式下更是如此(我们现在仍然是实模式),所以在绝大多数情况下就认为它是一个通用寄存器就好了。ds是数据段寄存器,由于Intel 8086中,CPU拥有16位寻址线(虽然我们现在使用的386以上的32位CPU,但是在实模式下仍然只能使用16位),寻址范围太小,于是添加了段的概念,使用20位寻址,可以支持1M的内存空间,而物理地址的计算方法就是段地址<<4+偏移地址,也就是用段的这16位来填充物理地址的高16位,在和偏移地址相加(个人认为,在考虑二进制问题的时候,用非常简单的左移右移去考虑问题是最好的方法,所以这里就是左移4位,不要考虑什么乘16的问题,不然容易被搞懵)。我们现在无论是数据还是指令,都是前0x0200(512)个,所以段地址只能为0。由于半导体材料自身特点,在刚通电的时候,各触发器的初始电位是杂乱的,所以初始情况下,寄存器里的数值自然也就是乱的,所以,我们需要人为给ds赋值为0,否则后面的数据无法正常读取。26行,把0x7c6a赋值给si寄存器,si寄存器是源变址寄存器,这是变址寻址方式需要使用的,变址寻址方式就是,我们给出一个基址,这个基址可能是某一段数据的开头,或者是某一段指令的第一条的地址,然后以这个为基础依次向下读取(或执行)。由于我们在41-44行存放了要输出的数据,而这些数据的地址起始就是0x7c6a,所以,要把这个值(也就是msg表示的地址)赋值给si以方便接下来使用。27-35行实现的是文字的输出。首先来看一下34行,int是调用中断指令,这里调用的是BIOS中断,所谓的中断,简单解释一下。CPU、内存等这些速度都非常快,但是,在他们执行过程中不可避免的会等待类似于键盘鼠标的输入以及显示器打印机等的输出,这些设备的速度相比而言就非常慢了(CPU每秒怎么也能进行上亿次运算,键盘打字的吉尼斯世界纪录也不过每分钟803次,平均每秒13次,到底不是一个数量级的),所以,如果CPU一直在等待这些事情,那么它等于说有很长时间都不能工作,因而有了中断机制,CPU不会因为需要输入或者输出就直接停下来等待,而是当设备需要进行输入或输出时,给CPU发出一个信号,CPU会停下手中的工作转来处理,处理完之后继续进行自己的工作,换句话说,CPU不需要实时监控,而是在需要他的时候“中断”一下,所以就把这种机制叫做中断机制。这里的BIOS中断就是这样,CPU需要让BIOS调用显卡,然后把对应的内容打印到屏幕上,这一过程对于CPU来说就是对它的中断。而要进行中断,自然也就需要相应的数据传递,CPU先把要处理的数据放到相对应的寄存器上,然后其他的设备(这里自然就是BIOS了)再读取寄存器中的数据,进行对应的工作,然后再返还一定的数据到寄存器,CPU再通过寄存机来回去返回数据(其实这么说起来,蛮像函数调用的,其实多数时候就可以理解成函数调用)。这里就是调用了0x10号中断,这个中断需要BIOS响应,当ah(就是ax的高8位)是0xe的时候,BIOS会进行字符打印处理,它会调用显卡,把al(就是ax的低8位)所表示的字符打印在屏幕上,与此同时,bx中的内容决定了文字的颜色(但是这个颜色在多数BIOS上是不支持的,打印出来的都会是白色)。28行就把si的值进行寻址,把对应内存地址处的数据赋值给al,32-33行把其他的寄存器的值赋好,然后34行调用0x10号中断,就可以在屏幕上打印出一个字符了。但是,我们要的不是只打印一个字符,是要打印一系列的字符(字符串),所以,就需要一个循环,而有循环就需要一个循环的结束条件(除非你确实想做一个死循环),35行的跳转指令制造一个循环,而30和31行就是判断是否跳出循环。这里是将字符和0去比较(当然了,这玩意你自己定,你非要用别的字符当字符串结尾符自然也没什么问题,只是大家都很习惯0结尾的字符串,你看那些用匈牙利命名法明明的字符串不都是sz前缀嘛,s表示字符串,z表示0结尾,貌似也没见过其他非主流的字符串),这样的话,就会把msg标签下的内容作为数据,一个字符一个字符打印到屏幕上,直到找到'\0'字符为止。

       后面的fin标签下就制造了一个死循环,而hlt命令是让CPU待机,这样减少能耗,是一个好的变成习惯,由于之后我们不做什么事情了,所以就让CPU一直待机就可以了。

       当然了,这仅仅有前512字节,要制作一个img,还需要把后面全部补0直至1440KB,用BZ也行,在汇编代码后面添加resb命令也可以。把制作好的img放到VMware上跑一下,顺利地看到了这一行字。


       我们已经成功的把一台什么都没有的裸机运行起来了,还输出了一行字。由于一开始只能加载512字节,所以这512字节里的程序一般都用来加载磁盘,然后转到别的程序,基本上起了一个加载和引导的作用,我们把这一段内容就叫做ipl。下次将继续完善ipl程序并实现更多的功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章