操作系统从0到1
一 部署工作环境
开发环境:Ubuntu 20.04 LTS
首先是需要安装的东西:
1 | sudo apt install build-essential |
下载Bochs https://udomain.dl.sourceforge.net/project/bochs/bochs/2.6.8/bochs-2.6.8.tar.gz
下载完毕之后将其移动至虚拟机中想要的位置,然后解压,命令:tar -zxvf bochs-2.6.8.tar.gz
为即将要安装的bochs创建一个空目录
1 | mkdir bochs |
进入解压后的bochs-2.6.8文件夹 cd bochs-2.6.8
配置bochs的config文件(–prefix这后面填的是你想要安装bochs的目录),编译,安装
./configure –prefix=/home/ubuntu/Desktop/bochs –enable-debugger –enable-disasm –enable-iodebug –enable-x86-debugger –with-x –with-x11 LDFLAGS=’-pthread’
1 | make |
进入安装好bochs的目录 cd .. cd bochs创建bochsrc.disk 命令: touch bochsrc.disk,在其中写下配置信息(修改romimage:、romimage:、 keyboard:后面路径信息的前部分为自己的bochs安装路径)
1 | megs : 32 |
创建启动磁盘
1 | bin/bximage |
然后在输入框依次输入以下,输入一个,按一次回车
1 | 1 |
测试代码,以下内容用作测试:
1 | cd .. |
1 | SECTION MBR vstart=0x7c00 |
安装编译器nasm 命令:
1 | sudo apt install nasm |
编译
1 | nasm -o test mbr.s |
写入虚拟机启动磁盘
1 | dd if=/home/ubuntu/Desktop/test of=/home/ubuntu/Desktop/bochs/hd60M.img bs=512 count=1 conv=notrunc |
启动虚拟机查看效果(在bochs目录下)
1 | cd bochs |
启动之后,输入c即可看见Hello world!
二 编写 MBR 主引导记录,让我们开始掌权
2.1 计算机的启动过程
任何程序执行必须先载入内存,所谓的载入内存,大概上分为两个部分。
(1)程序被加载器(软件或硬件)加载到内存某个区域。
(2)CPU 的 cs:ip 寄存器被指向这个程序的起始地址
从按下主机上的 power 键后,第一个运行的软件是 BIOS。
2.2 软件接力第一棒,BIOS
BIOS 全称叫 Base Input & Output System,即基本输入输出系统。
2.2.1 实模式下的 1MB 内存布局
这里我们举例Intel 8086在实模式下的内存布局
先从低地址看,地址 0~0x9FFFF 处是 DRAM(Dynamic Random Access Memory),即动态随机访问 内存,我们所装的物理内存就是 DRAM。
看顶部的 0xF0000~0xFFFFF,这 64KB 的内 存是 ROM。这里面存的就是 BIOS 的代码。BIOS 的主要工作是检测、初始化硬件,怎么初始化的?硬件自己提 供了一些初始化的功能调用,BIOS 直接调用就好了。BIOS 还做了一件伟大的事情,建立了中断向量表,这样 就可以通过“int 中断号”来实现相关的硬件调用,当然 BIOS 建立的这些功能就是对硬件的 IO 操作,也就是输 入输出,但由于就 64KB 大小的空间,不可能把所有硬件的 IO 操作实现得面面俱到,而且也没必要实现那么多, 毕竟是在实模式之下,对硬件支持得再丰富也白搭,精彩的世界是在进入保护模式以后才开始,所以挑一些重要 的、保证计算机能运行的那些硬件的基本 IO 操作,就行了。这就是 BIOS 称为基本输入输出系统的原因。
在CPU眼里,插在主板上的物理内存并不是它眼里的“全部的内存”,归根结底的原因是这样的:在计算机中,并不是只有咱们插在主板上的内存条需要通过地址总线访问, 还有一些外设同样是需要通过地址总线来访问的,这类设备还很多呢。若把全部的地址总线都指向物理内 存,那其他设备该如何访问呢?由于这个原因,只好在地址总线上提前预留出来一些地址空间给这些外设 用,这片连续的地址给显存,这片连续的地址给硬盘控制器等。留够了以后,地址总线上其余的可用地址 再指向 DRAM,也就是指插在主板上的内存条、我们眼中的物理内存。
2.2.2 BIOS 是如何苏醒的
BIOS 本身是个程序,程序要执行,就要有个入口地址才行,此入口地址便是 0xFFFF0。在开机的一瞬间,也就是接电的一瞬间,CPU 的 cs:ip 寄存器被强制初始化为 0xF000:0xFFF0。由于 开机的时候处于实模式,再重复一遍加深印象,在实模式下的段基址要乘以16,也就是左移4位,于是0xF000: 0xFFF0 的等效地址将是 0xFFFF0。上面说过了,此地址便是 BIOS 的入口地址。但是此处并不是真正的代码,因为由于实模式下的寄存器宽度是 16 位,0xFFFF0+16 已经超过了其最大值 0xFFFFF。溢出的部分就会回卷到 0,又会重新开始,即 0xFFFF0+16 等于 0,0xFFFF0+17 等于 1。所以此处的代码为跳转代码。真正的代码储存在跳转代码的所跳转的位置,接下来 BIOS 便马不停蹄地检测内存、显卡等外设信息,当检测通过,并初始化好硬件后,开始在内 存中 0x000~0x3FF 处建立数据结构,中断向量表 IVT 并填写中断例程。
2.2.3 为什么是 0x7c00
计算机执行到这份上,BIOS 也即将完成自己的历史使命了,BIOS 最后一项工作校验启动盘中位于 0 盘 0 道 1 扇区的内容。如果此扇区末尾的两个字节分别是魔数 0x55 和 0xaa,BIOS 便认为此扇区中确实存在可执 行的程序(在此先剧透一下,此程序便是久闻大名的主引导记录 MBR),便加载到物理地址 0x7c00,随 后跳转到此地址,继续执行。至于为什么是 0x7c00,8086CPU 要求物理地址 0x0~0x3FF 存放中断向量表,所以此处不能动了,再选新的地方看看。 按 DOS 1.0 要求的最小内存 32KB 来说,MBR 希望给人家尽可能多的预留空间,这样也是保全自己 的作法,免得过早被覆盖。所以 MBR 只能放在 32KB 的末尾。 MBR 本身也是程序,是程序就要用到栈,栈也是在内存中的,MBR 虽然本身只有 512 字节,但还要为其 所用的栈分配点空间,所以其实际所用的内存空间要大于 512 字节,估计 1KB 内存够用了。 结合以上三点,选择32KB中的最后1KB最为合适,那此地址是多少呢?32KB换算为十六进制为0x8000, 减去 1KB(0x400)的话,等于 0x7c00。这就是倍受质疑的 0x7c00 的由来,这下清楚了。 可见,加载 MBR 的位置取决于操作系统本身所占内存大小和内存布局。
2.3 让 MBR 先飞一会儿
MBR 的大小必须是 512 字节,这是为了保证 0x55 和 0xaa 这两个 魔数恰好出现在该扇区的最后两个字节处,即第 510 字节处和第 511 字节处,这是按起始偏移为 0 算起的。 由于我们的 bochs 模拟的是 x86 平台,所以是小端字节序,故其最后两个字节内容是 0xaa55
1 | ;主引导程序 |
注:使用dd命令时需要重新创建硬盘
创建启动磁盘
1 | bin/bximage |
然后在输入框依次输入以下,输入一个,按一次回车
1 | 1 |
1 | nasm -o mbr mbr.s |
然后用dd命令写入bochs的虚拟硬盘,命令:
1 | dd if=/home/ubuntu/Desktop/test of=/home/ubuntu/Desktop/bochs/hd60M.img bs=512 count=1 conv=notrunc |
启动,之后默认为6输入c
1 | bin/bochs –f bochsrc.disk |
可以看到有结果出现
三 完善MBR
3.1 地址,section,vstart浅尝辄止
3.1.1 什么是地址
地址只是数字,描述各种符号在源程序中的位置,它是源代码文件中各符号偏移文件开头的距离。由 于指令和变量所占内存大小不同,故它们相对于文件开头的偏移量参差不齐。源文件就像旅店一样,里面 的符号(指令、变量等)就像旅店里的房间,有单人间、双人间,虽然大小不同,但它们也需要被旅店管 理员编号,也就是每个房间都有房间号,这样房客通过房间号便能找到自己的住所。
3.1.2 什么是section
简单点来说就是方便开发人员理清程序用的,关键字 section 并没有对程序中的地址产生任何影响,即在默 认情况下,有没有 section 都一个样,section 中数据的地址依然是相对于整个文件的顺延,仅仅是在逻辑 上让开发人员梳理程序之用
3.1.3 什么是vstart
vstart 的作用是为 section 内的数据指定 个虚拟的起始地址,也就是根据此地址,在文件中是找不到 相关数据的,是虚拟的,假的,文件中的所有符号都不在这个地址上。vstart 只是告诉编译器以新的数字作为后面数据的地址的起始值,它本身没改变数据本 身在文件中的地址(相对于文件开头的偏移〉