上一节中说到BIOS会将MBR中的主引导程序(512字节)加载到内存的0x7c00处,其中这512字节的主引导程序是软件程序,是操作系统的一部分,因此也是由操作系统开发者来编写的,BIOS将其加载到内存后,会自动跳到0x7c00处去执行。接下来我们自己实现一个“主引导程序”,功能很简单,就是让它打印一串字符串到屏幕上(真正的主引导程序是加载操作系统内核用的),注意,这段程序现在是独立运行在裸机上的,我们用汇编语言来实现它。

  这个过程我们需要调用中断向量表中的“打印函数”,调用的动作是通过int 0x10来实现的。

  编写程序之前,我们先来介绍几个要用到的指令:

  mov :赋值操作,将右操作数赋值给左操作数

  例:mov ax, 0  ;将0赋值给ax寄存器

  int :触发中断

  例:int 0x10 ; 触发0x10中断,对屏幕进行操作

  hlt :停止,CPU进入暂停状态,不进行任何操作

  例:hlt ;使程序进入睡眠状态

  汇编中的地址访问形式:段地址:段内偏移地址

  例:mov byte[0xb800:0x01] ; 0xb800:0x01 -> 0xb800 + 0x01

  标签:

  用于标识后续指令的地址,(与C语言中的标签等价)

  $ 与 $$

  $表示当前指令行地址,$$表示当前汇编段起始地址

  中断调用与函数调用的对应关系如下所示:

  

  实现真正的打印之前,要向bx寄存器中写入0x0f,向ah寄存器中写入0x0e,这些都是打印函数规定好的。而al寄存器中需要存入要打印的字符,寄存器中的内容准备好了之后使用int 0x10来调用打印函数,而后,就可以将字符打印到屏幕上。

  下面直接给出用汇编语言写的“主引导程序“。

  

org 0x7c00

start:
    mov ax, cs
	mov ss, ax
	mov ds, ax
	mov es, ax
	
	mov si, msg
	
print:
    mov al, [si]
	add si, 1
	cmp al, 0x00
	je last
	mov ah, 0x0e
	mov bx, 0x0f
	int 0x10
	jmp print
	
last:
    hlt
	jmp last
	
msg:
    db 0x0a, 0x0a
	db "Hello, DTOS!"
	db 0x0a, 0x0a
	times 510-($-$$) db 0x00
	db 0x55, 0xaa

 

  主引导程序实际待的起始地址是0x7c00,因此第一行的org 0x7c00告诉编译器,这段程序的加载地址是0x7c00,这样编译器就可以对地址进行正确的处理。

  然后将ss,ds,ed寄存器分别清零,msg标签处在内存中定义了一些数据,这些数据是实实在在在内存中和磁盘文件中占用空间的。msg就代表了这片数据所占用空间的起始地址。mov si, msg就是将这个起始地址送到si寄存器中。

  接下来print标号处定义了打印相关的功能,mov al, [si]就是取si所代表的内存地址处的一个字节数据,并放到al寄存器中,然后使si加1,指向下一个数据,此时先判断al中的数据是否是0x00,也就是ASCII码‘\0’,这个字符代表字符串的结束,如果确实为0x00,则跳转到last处,让cpu停机。如果不为0x00,则开始准备打印,正如我们上面说到的将0x0e存到ah寄存器中,将0x0f存到bx寄存器中,相当于格式化参数(%c),然后通过int 10来调用中断向量表里面的打印函数,打印完一个字符后继续跳转到print处进行下一次循环,直到到达字符串的末尾。

  下面我们讲一讲msg标号处的数据的定义,db伪指令定义一个字节,其后可以是数据,也可以是字符串。db 0x0a即定义一个字节,其中的数据初始化为0x0a,也就是换行符的ASCII码,db "Hello, DTOS!"相当于定义了11个字节。

  根据前面内容知道,主引导扇区为512字节大小,而BIOS判断存储介质是否包含主引导扇区的标准就是看看它的前512个字节的最后两个字节是不是0x55和0xaa,如果是,那么BIOS就认为这个存储介质有主引导扇区,接下来就读取主引导扇区到内存中了。

  前面写的程序不足以占满512字节,而我们的需求是要将最后两个字节写入0x55和0xaa,怎么办呢?我们让编译器去判断前面程序和数据占用空间的大小,并用适当数量的数据填满前面不足510字节的部分,然后在最后两个字节我们写入0x55和0xaa。

  times  510 - ($ - $$) db 0x00这条命令的意思就是填写510 - ($ - $$)个0x00到内存中,$代表times这条伪指令的地址,$$代表当前汇编程序的起始地址(即start处,0x7c00),相减后即为以上所写程序和数据占用的空间大小,再用510减去这个值即为所需要填充的空间的大小。最后,第511字节填入0x55,第512字节填入0xaa。

  用到的工具:汇编语言编译器nasm,创建虚拟盘的工具bximage,二进制写入工具dd。由于我们不会将程序真正的刻录到软盘上,因为那样太麻烦了,而且还需要一个真正的物理机器。所以我们使用虚拟盘创建工具bximage,它可以创建一个文件,这个文件存储数据时的格式和软盘是一样的。

  下面,我们实际做实验看现象,首先打开ubuntu虚拟机,到工作目录下建立boot.asm文件,敲入上述程序,使用编译器nasm进行编译,生成boot.bin文件,如下所示:

  下面创建虚拟盘,执行过程如下图:

 

 这样,虚拟盘就创建好了,名为a.img,这个文件就相当于一个容量1.44M大小的全盘,见下图:

下面我们将boot.bin的内容刻录到这张软盘中,执行命令dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc,结果如下:

 

至此,这张软盘的前512字节就写入了主引导记录,因此,它就成为了一张启动盘了。

下面使用VmWare创建一个物理机器,详细过程不在贴出,创建好的机器如下,这是一个未安装任何操作系统的空的机器。

 

我们直接启动这个机器看看效果,如下所示:

 

从启动界面可以看到,BIOS扫描存储介质时,根本没有发现启动盘,也就是没有找到主引导扇区,因此给出Operating System not found的提示。

接下来,我们将软盘插入这台电脑中,首先将带有主引导扇区的软盘(a.img文件)拷贝到windows系统下,然后将其插入刚才创建的机器中,如下所示:

 

接下来,给这台机器加电,效果如下:

 

  可见,主引导程序成功被加载并运行了。

 

小插曲:

主引导程序部分是16位操作,因此,我们需要一个16位的汇编器,现存的汇编器大概有gas汇编器和nasm汇编器,nasm是16位的,用来编译英特尔格式汇编代码,gas是32位的用来编译AT&T格式汇编的,也是唯一支持AT&T格式的汇编器,因此,我们不能选择AT&T格式进行实验。linux的boot程序使用的是as86汇编器进行编译的,这个汇编器是16位的,但是资料比较少。as86是编写Minix操作系统的那个教授编写 的,这个汇编器支持的格式既不是标准的AT&T格式也不是Intel格式,只是和Intel格式比较接近。因此,我们选择了nasm汇编器。

本文参考狄泰软件学院操作系统教程。

posted on 2018-06-15 21:33  周伯通789  阅读(2799)  评论(0编辑  收藏  举报