9.加载内核

加载内核

程序猿实现的一段代码,比如下:

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 的空间。

物理内存中 0x900loader.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 文件了。