加载内核
程序猿实现的一段代码,比如下:
1 | int main(void) { while(1); } |
我们首先将他编译成一个可重定位文件,也就是 .o
文件。使用:gcc -c -o main.o main.c
此时,目标文件(可重定位文件)中的符号(变量、函数名)的地址没有确定(可以使用 nm 查看)。我们使用 ld 进行链接,可以使用参数:-Ttext
来指定最终生成的可执行文件的起始虚拟地址。如:ld main.o -Ttext 0xc0001500 -e main -o kernel.bin
其中 -e
参数用于指定程序的起始地址,不仅可以是数字形式的地址,也可以是符号名。如果不加 -e
参数,默认以 _start
作为入口符号,默认地址为:0xc0001500
1. 方式一:在 loader 中解析 ELF 格式的 kernel.bin,生成内核映像
内核被加载到内存后,loader 要通过分析其 ELF 结构将其展开到新的位置。因此,内核在内存中有两份拷贝,一份是 ELF 格式的原文件:kernel.bin,另一份是 loader 解析 ELF 格式的 kernel.bin 后在内存中生成的内核映像(也就是程序中的各种段 segment 复制到内存后的程序体),这个映像才是真正运行的内核。
内核文件经过 loader 解析后就没用了,这样内核映像将来往高地址处扩展时,也可以覆盖原来的内核文件 kernel.bin。所以可以在 0x7E00 - 0x9FBFF
这片区域的高地址中找一块地址存储 kernel.bin。目前选用的是 0x70000
,也就是:0x70000 - 0x9fbff
一共有 190KB 的空间。
物理内存中 0x900
是 loader.bin
加载的地址,在 loader.bin
的开始部分是 GDT,他必须保留,不能覆盖。预计 loader.bin
不会超过 2000 字节,所以 0x900 + 2000 = 0x10d0
,我们凑个整数,使用 0x1500 作为内核映像的入口地址。根据页表,低端 1MB 的虚拟内存与物理内存是一一对应的,所以物理地址是 0x1500
对应的虚拟地址是 0xc0001500
。
此方式的缺点:
- 需要通过在 loader 中使用汇编编码实现,解析 ELF 格式的 kernel.bin,生成内核映像
- kernel.bin 本身可能很大,写到磁盘占用大量磁盘资源,并且全部加载到内存在解析,也要占用大量内存资源
2. 方式二:使用 objcopy 直接生成内核映像
通过 gcc 编译的 ELF 格式 kernel.bin,可以使用 objcopy 将需要的段拷贝出来,比如称为:kernel_text.bin。那就只需要将 kernel_text.bin
写入磁盘,并且加载到指定内存即可。不需要进行解析 ELF 文件了。