从0创建一个OS (四) 电脑存储的组织形式
本节将学习boot sector以及其所在内存的相关知识
学习完本节后,要明确一个概念: 代码的存储区域和运行区域有可能不同
关键字:memory offsets; pointers
目标:学习电脑存储的组织形式
在进行学习之前,先看一下这幅图
这幅图为16bit实模式下电脑启动后的内存存储结构,读者请尤其注意粉色椭圆勾选的区域.
实验一
我们想要通过boot_sector的代码将大写字母"X"打印在屏幕上,为此设计了一组实验,实验设计见源码.
源码
; =============================================================== ; 文件名: boot_sect_memory.asm ; 本程序尝试打印一个"X",使用的方法为向AH传入0x0E,向AL传入待打印内容,即"X" ; 待打印内容定义为标签"the_secret"的形式 ; =============================================================== mov ah, 0x0e ; 尝试一 ; 将标签赋值给AL,试图打印内容 ; 结果:当然会失败,稍有汇编知识的人都知道,the_secret只是 ; 一个地址,无法用地址代替内容 mov al, "1" int 0x10 mov al, the_secret int 0x10 ; 尝试二 ; 将标签指向的内容赋值给AL,试图打印内容 ; 结果:失败 ; 失败原因:BIOS将我们的boot_sector(本程序)编译出的二进制文件 ; 放置在0x7C00的位置上,而[the_secret]只表示该标签指向的内容相对于地址0的 ; 地址 mov al, "2" int 0x10 mov al, [the_secret] int 0x10 ; 尝试三 ; 在尝试二的基础上,获得the_secret指向的内容的绝对地址 ; 绝对地址的形式为: 0x7c00 + 相对于地址0的地址(即[the_secret]) ; 结果:成功 mov al, "3" int 0x10 mov bx, the_secret add bx, 0x7c00 mov al, [bx] int 0x10 ; 尝试四 ; 在已知"X"存储在本程序编译完成的二进制文件的第0x2d个字节上(怎么知道的呢?数的呗)的情况下,直接 ; 使用数值地址代替变量地址 ; 译者曰:这个尝试四非常蠢,无法应用到实际当中 ; 当然原作者只是为了展示the_secret的实际位置,给出这个示例无可厚非,但是实际中不应该这样使用 mov al, "4" int 0x10 mov al, [0x7c2d] int 0x10 jmp $ the_secret: db "X" times 510 - ($ - $$) db 0 dw 0xAA55
编译并Boot
具体编译并Boot方法参见从0创建一个OS (二) boot_sector的"裸骨架"的编译、Boot部分
试验结果
可以看到尝试三和尝试四取得了成功,因为通过不同的方式,都取得了"X"的绝对地址.
新知识
如果每次对变量进行寻址都需要加上0x7C00,那岂不是太麻烦了,因此人们设计了汇编语法
org 段地址
使用该语法,使后续的寻址基地址都变为段地址.
实验二
接下来我们将"org 段地址"写入我们的源码第一行中,看看会有什么样的结果.
; ================================================================ ; 文件名: boot_sect_memory_org.asm ; 本文件除开头的[org 0x7C00]外,其余与boot_sect_memory.asm一样 ; ; 本程序尝试打印一个"X",使用的方法为向AH传入0x0E,向AL传入待打印内容,即"X" ; 待打印内容定义为标签"the_secret"的形式 ; ================================================================ [org 0x7C00] mov ah, 0x0e ; 尝试一 ; 将标签赋值给AL,试图打印内容 ; 结果:当然会失败,稍有汇编知识的人都知道,the_secret只是 ; 一个地址,无法用地址代替内容 mov al, "1" int 0x10 mov al, the_secret int 0x10 ; 尝试二 ; 将标签指向的内容赋值给AL,试图打印内容 ; 结果:成功 ; 结果改变原因:BIOS将我们的boot_sector(本程序)编译出的二进制文件 ; 放置在0x7C00的位置上,但这一次[the_secret]表示该标签指向的内容相对于地址0x7C00的 ; 地址 mov al, "2" int 0x10 mov al, [the_secret] int 0x10 ; 尝试三 ; 在尝试二的基础上,获得the_secret指向的内容的绝对地址 ; 绝对地址的形式为: 0x7c00 + 相对于地址0x7C00的地址(即[the_secret]) ; 结果:失败 ; 结果改变原因:这里的绝对地址中多加了一个0x7C00,导致目标地址从正确变为错误 mov al, "3" int 0x10 mov bx, the_secret add bx, 0x7c00 mov al, [bx] int 0x10 ; 尝试四 ; 在已知"X"存储在本程序编译完成的二进制文件的第0x2d个字节上(怎么知道的呢?数的呗)的情况下,直接 ; 使用数值地址代替变量地址 ; 结果: 成功 ; 译者曰:这个尝试四仍旧非常蠢,无法应用到实际当中 ; 当然原作者只是为了展示the_secret的实际位置,给出这个示例无可厚非,但是实际中不应该这样使用 mov al, "4" int 0x10 mov al, [0x7c2d] int 0x10 jmp $ the_secret: db "X" times 510 - ($ - $$) db 0 dw 0xAA55
编译并Boot
具体编译并Boot方法参见从0创建一个OS (二) boot_sector的"裸骨架"的编译、Boot部分
实验结果
尝试二从失败变为成功,尝试三从成功变为失败,尝试四仍旧成功,具体原因参见源码中的注释.
总结
本节进行了诸多尝试,解释了boot sector中的变量存储方式,以后对boot sector的程序编写,首行一定要加上 org 0x7C00.
boot sector虽然存储在磁盘的第一个512个字节范围,但是运行时被load到了0x7C00进行运行.
番外. 查看二进制文件内容
一定有读者想要问上面源码中的0x2d 是怎么数出来的,在这里给大家演示以下,以证明我在尝试四中写的所谓的"愚蠢"是有道理的.
我们由上面的两个汇编源文件boot_sect_memory.asm和boot_sect_memory_org.asm编译链接生成了boot_sect_memory.bin和boot_sect_memory_org.bin两个二进制文件,使用二进制文件查看工具可以看到其中的内容,本文这里使用的二进制文件查看工具为: vscode的插件"hexdump for vscode".
两个源码之间的区别只有首行的"org 0x7C00", 让我们来看一下这两个二进制文件的异同.
每个文件中标黄部分,相同的是16进制58,本质为"X"的ASCII码,不同的是,加上org 0x7C00后,第二行第一列的00变为了7C.
那么地址怎么看呢,请看下图
在**vscode的插件"hexdump for vscode"**中将鼠标放置需要查看的字节上,将自动显示该字节在该文件中的地址,由上图可以看出,"X"的地址为0x2d