4-主引导程序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/king_qg/article/details/86511753

主引导程序

主引导程序的用处应该是通过一个中间程序加载操作系统或者直接加载操作系统,但此处只做学习,所以此处主引导程序的作用是打印一些文本内容。

主引导区用来存储主引导程序,但是主引导区的大小只有512字节,难道主引导程序只有这么大?那就看看吧。

  • 位置:位于存储介质最开始处,大小512字节
  • 特点:前512字节最后两个字节分别为0x55aa
  • 数据:前510个字节这些数据被称为主引导程序

加载位置

bios遇到的第一个有效的主引导程序后,会将主引导程序加载到0x0000:0x7c00处,然后将控制权转移到0x0000:0x7c00。至于为什么是这个地址,不用纠结,这个问题就如同为什么one的中文是1。

显卡

需要在屏幕上显示数据,必须需要了解显示器和显卡。显示器负责显示数据,而显卡用来控制显示器。

显卡如何控制显示器

显卡通过管理显示器上的像素来控制显示器。那么显卡中必然存在像素的信息。

显存中的信息如何解释

显卡有两种最基本的模式:

  • 文本模式
  • 图像模式

设置不同的模式,对显存中的数据的解释是不同的。计算机上电后会将显卡初始化为80*25的文本模式。

在文本模式下,数据都是被当做需要显示的字符所对应的ASCII码,一个字符对应显存中的两个字节,前一个字节是ASCII码,后一个字节是对前景色和背景色的描述信息。
例如显示字符A:显存的数据是0x41,0x07。(0x07代表黑底白字)

显存映射

如果让CPU通过显卡控制器来操作显存,那么显然效率会降低。
内存的三部分有一部分是外围板卡,这部分内存是特意给硬件映射用的。而显存映射后对应的地址范围是0xB8000-0xBFFFF。

实验程序

在理解了怎么利用显示器来显示数据后,那么目标就转换为向显存对应的内存空间中写入待显示的数据和控制信息。

代码

start:
	mov ax, 0x07c0
	mov ds, ax

	mov ax, 0xb800
	mov es, ax
	
	mov si, msg
	mov di, 0x00
	
print:
	mov al, [si]
	add si, 1
	cmp al, 0x00
	je last
	mov byte [es:di], al
	mov byte [es:di+0x01], 0x07
	add di, 0x02
	jmp print
	
last:
    hlt
	jmp last
	
msg:
	db "control memory, not bios interrupt!"
	times 510-($-$$) db 0x00
	db 0x55, 0xaa

对于不懂汇编的我来说很多代码不知道什么意思,所以刚开始都会一点点讲解。

	mov ax, 0x07c0
	mov ds, ax

不能直接像段寄存器中直接写入立即数,所以借助寄存器。 0x7c0初始化ds段,那么ds段的初始地址就是0x7c00,这个地址是不是很熟悉,因为主引导程序就是被加载到内存地址起始位0x7c00地址然后跳转到这个地址执行。
如果不把ds的地址指向0x7c00,那么数据就无法正确被寻址到。为什么呢?
对于这个程序,没有定义段,所以整个程序被当做一个段处理,在经过编译后每条指令都有一个地址,称为汇编地址,程序加载到物理内存,还会有一个段内偏移地址,汇编地址的值与段内偏移地址的值完全相同,如果这个程序是在起始地址为0的内存中执行的,完全不需要上面两句代码,但是这个程序会被加载到0x7c00的位置,而且会从0x7c00出执行代码,所以我们寻址需要重定位。因为对于代码来说不会出错,但是对于数据就会出错。
现在的地址和原来的地址都是差0x7c00,所以把段的地址初始化为0x07c0,指向0x7c00位置,这样段内偏移地址+基地址正好是重定位后的地址。

	mov ax, 0xb800
	mov es, ax

将es附加段起始地址指向0xb8000,这个地址正是显存的映射地址。如果不知道为什么可以直接向这个内存写显卡写,可以去看一下上一篇笔记。

	mov si, msg

msg这个标号代表着程序存储数据的其实地址,之后用si来表示。

	mov di, 0x00

因为要像显存中写入一串数据,所以用一个来寄存器来充当指针,指向将要写入数据的内存。

	mov al, [si]
	add si, 1
	cmp al, 0x00
	je last

si是指向字符串的指针,那么[si]就是待显示的数据,将数据送入到al寄存器。然后si自增,那么si就指向了下一个待处显示的数据。因为数据区有效数据被设计为0,所以如果al寄存器内容为0说明没有需要显示的数据了。如果没有了那么跳到last地址执行,last处的地址就是暂停cpu。

    mov byte [es:di], al
	mov byte [es:di+0x01], 0x07
	add di, 0x02
	jmp print

首先将需要显示的字符写入显存,这个字符后面跟着的是控制信息,这里0x07代表黑底白字无闪烁。di自增2,指向下一个需要写入的地址。jmp print代表了循环处理其它字符。

msg:
	db "control memory, not bios interrupt!"
	times 510-($-$$) db 0x00
	db 0x55, 0xaa

这是该程序的数据区,首先是定义了需要显示的字符串, 代表了当前指令地址, $代表当前段的段地址,times代表重复后面的指令多少次,这里的意思代表的是从当前地址到第510个字节全出填充0,目的是将511,512字节写入0x55aa,这样才会被bios当做主引导程序。

疑问

代码中数据重定位了,代码没有重定位,但是jmp却可以执行,jmp后面是编号,是一个汇编地址,而主引导程序从主引导区加载到07c00位置了,这里为什么可以正确执行呢?是不是也需要将cs段的起始地址初始化为0x07c0?
当然不是了,BIOS搬移完主引导程序后,会通过jmp 0x0000:0x7c00,将cs初始化为0x0000,ip初始化为0x7c00。而jmp label,并不是将label代表的地址传送给ip寄存器,而是将当前jmp指令的地址减去标号的汇编地址,减去jmp指令的长度,得到的结果是一个偏移量,然后使ip寄存器自增偏移量的大小。所以jmp label是位置无关的。
也许你会疑惑为什么还需要减去当前指令的长度,因为ip寄存器始终是指向当前指令的下一条指令的。

实战操作

本文并没有展示如何实际操作的过程,作者本人利用的是虚拟机+虚拟软盘。结果如下:
image
这里操作细节不再讨论,可以在群中讨论或者自行查询。

关于作者

大四学生一枚,分享数据结构,面试题,golang,C语言等知识。QQ交流群:521625004。微信公众号:后台技术栈。

image

猜你喜欢

转载自blog.csdn.net/king_qg/article/details/86511753
今日推荐