1. 问题:OS引导代码为什么需要org 07c00h?
在前几天在知乎上的的一个回答《想带着学生做一个操作系统,可行性有多大?》中,我们引用了一段主引导扇区MBR中的操作系统加载代码:
org 07c00h ; 告诉编译器程序加载到7c00处
mov ax, cs
mov ds, ax
mov es, ax
call DispStr ; 调用显示字符串例程
jmp $ ; 无限循环
DispStr:
mov ax, BootMessage
mov bp, ax ; ES:BP = 串地址
mov cx, 16 ; CX = 串长度
mov ax, 01301h ; AH = 13, AL = 01h
mov bx, 000ch ; 页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮)
mov dl, 0
int 10h ; 10h 号中断
ret
BootMessage: db "Hello, OS world!"
times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw 0xaa55 ; 结束标志
本来想着过了就算了,未曾想有个关注我的网友提问说:
弱弱的问一句,代码里面最开头为什么要org 07c00h?注释没看懂,是代表指示将这部分代码加载到对应内存?
本来想着在评论中解析清楚的,后来写着写着发现内容太多,且一些地方需要配合相应的一些截图才能解析清楚,所以这里干脆独立成为一篇文章来解释这个问题算了。
要解析这个问题,首先,这里我们得先了解一下操作系统启动的过程,以及相应的寄存器和内存的关系。
2. 电脑开机后是怎么一步步把操作系统引导代码加载到07c00h的
当我们按下电脑的重启按钮,加电时,我们cpu的寄存器会复位,其中大部分的寄存器都会清0,除了我们的代码段寄存器cs,将会被复位为FFFF。
同时,因为我们开机时,cpu是工作在实模式下的。所以我们的内存访问模型是
物理地址 = 段地址 x 16 + 偏移地址
所以开机时取得第一条指令将会来自地址:
0xFFFF x 16 + 0x0000 = 0xFFFF0
那么0xFFFF0指向的存储地址是什么内容呢?
继续之前,我们需要看下内存地址空间的分配。
我们知道8086有20根地址线,能访问的存储空间是0x00000到0xFFFFF。但是,这里需要知道的是这部分存储空间并不是全部属于我们内存条的。这里的地址空间分配是这样的:
- 0x00000 ~ 0x9FFFF:内存
- 0xA0000 ~ 0xEFFFF:显卡显存等外围设备
- 0xF0000 ~ 0xFFFFF:BIOS
而我们的0xFFFF0正好是落在bios的地址空间中的。也就是说,当把0xFFFF0这个地址放到地址总线后,经过地址分配电路,将会去bios读取对应的内容来执行。当然,因为bios读取速度没有内存快,所以也有可能会将bios的代码直接映射到内存条的对应区域:
那么bios中0xFFFF0的内容是什么呢?
事实上,里面是一条跳转指令。
- EA: jmp跳转指令的机器码
- E05B: 偏移地址
- F000: 段地址
参考我们上面的实模式下的内存访问模型,跳转的目的地址将会是0xFE05B这个地址。
而这个地址同样是落在bios的地址空间范畴。其实做的事情就是bios为我们提供的进行硬件自检的一系列操作。执行完这些操作后,bios最后要做的一个事情就是将我们启动盘的主引导扇区的代码给加载到内存的0x07C00开始的位置,然后执行一个跳转指令,跳转到该地址,开始执行我们的引导代码。
那么,这里的07c00h和我们的org 07c00h有什么关系呢?
3. org 07c00h的真实作用
假如我们想跳到代码指定一个位置进行执行,比如跳到我们代码在内存位置偏移0x0003的位置,有代码如下:
jmp 0003H
这里的跳转指令,根据我们上面的内存访问模型,实模式下,这里的目标地址将会是cs的段起始地址加上0x0003这个段内偏移地址。
假如我们现在的代码是加载到0x0000这个段,即现在的cs里面的段是0x0000,对应的段开始地址就是:
0x0000 x 16 = 0x00000
那么最终跳转jmp的物理内存目标地址就是:
0x00000 + 0x0003 = 0x00003
那最终的地址也就是我们的代码相对段起始地址偏移0x0003的位置,达到了我们预期的目标。
但是,如果我们的代码不是加载到0x00000这个段的开始位置上,而是加载到0x00000这个段偏移0x7C000即我们上面的0x07C00上呢?
那么此时的jmp 0x0003跳转的还是0x00000 + 0x0003 = 0x00003这个地址,但问题是我们现在的代码是加载到了0x7C000开始的位置,所以跳到的已经是一个我们代码范围之外的地址了,这就不是我们想要的了。
那么这个问题怎么解决呢?
其实就是利用org伪汇编指令就可以了。
org 7c00H
jmp 0003H
它的作用就是在汇编编译时告诉编译器:
我们的代码将会被加载到了区里段起始位置0x7c00这个位置执行,所以请将所有相关的偏移地址加上0x7c00
然后在编译后,jmp 0x0003会变成
jmp 0x7c03
即上图中的0x7c00 + 0x0003这个位置,而这,就和我们预期的目标相一致了。
4. 鸣谢
NASM Manual
https://www.bilibili.com/video/BV1xE411N74T
我是@天地会珠海分舵,能力一般,水平有限,觉得我说的还有那么点道理的不妨点个赞关注下!