Gos —— 加载内核

16 篇文章 0 订阅

写在前面:自制操作系统Gos 第二章第八篇:主要内容是如何加载内核
Gos完整代码:Github

前面我们不是在MBR引导程序工作就是在Loader内核加载器中工作。在开启分页机制之后,其实我们下一步做的工作也就是加载内核了。
这里提前剧透一下加载内核的步骤:

  1. 加载内核:其实也就是把内核文件从硬盘上加载到内存中
  2. 初始化内核:在开启分页机制之后,将加载进来的elf内核文件放入相应的虚拟内存地址,然后跳过去执行就可以了

加载内核

第一步就是加载内核了。但是在加载之前,我们需要考虑把内核加载到哪里呢?

首先,我们要明白,实际我们现在还是在实模式下的1M空间,并不是开启分页后拥有1GB那么大的空间。所以其实内核的安身立命的物理地址其实还在这1M空间,我们先来看看这个空间都是怎么分布的吧:
在这里插入图片描述
可用看到,相对空间大一点且没有用的空间就是0x7C00~0x9FBFF这段空间啦,取个整,选0x7000这段空间就好了。选好空间之后,我们直接调用之前写好的rd_disk_m_32函数从磁盘读取文件就好了。

注:MBR加载区的内容已经没有了,我们可以直接覆盖啦

KERNEL_BIN_BASE_ADDR equ 0x70000    ;kernel.bin被加载的内存位置
KERNEL_START_SECTOR equ 0x9         ;内核程序在磁盘中的扇区号起始位置
; -------------------------   加载kernel  ----------------------
;这里起始就是从9号扇区开始读取200个扇区,读到KERNEL_BIN_BASE_ADDR       
   mov eax, KERNEL_START_SECTOR        ; kernel.bin所在的扇区号
   mov ebx, KERNEL_BIN_BASE_ADDR       ; 从磁盘读出后,写入到ebx指定的地址
   mov ecx, 200			       ; 读入的扇区数

   call rd_disk_m_32

初始化内核

内核kernel.bin文件呢,其实本身也就是而执行可执行文件。其包含了内核程序中代码段、数据段、文件头等等信息的集合。还记得我说过守约是操作系统设计的精髓了么?

我们现在要做的事情就是根据ELF约定去吧内核文件中的段复制到内存的相应位置就可以了。

:ELF文件的详细信息我放到博客目标文件详解了,没有相关知识的同学可以查阅一下

在正式初始化内核之前,我们要确定一个点,那就是我们的内核初始化执行的入口地址在哪里呢?
一开始我们的设置loader.bin的位置是0x900,再加上GDT部分的大小,那么我们把地址设置到0x1500没有问题吧。同时,我们设计低端1M的虚拟内存和物理内存是一 一对应的,所以物理内存的0x1500其实对应的是0xc0001500

所以我们进行如下定义:

KERNEL_ENTRY_POINT equ 0xc0001500

这个点其实就是我们内核的执行位置(内核在0x70000)了,最终我们需要通过绝对跳转jmp过去:

enter_kernel: 
   call kernel_init				;按照elf格式初始化内核bin文件
   mov esp, 0xc009f000			;初始化栈指针
   jmp KERNEL_ENTRY_POINT 		;跳转过去,执行内核程序

好了,我们现在已经知道了怎么去执行内核程序了。但是还有一个点没有做:
  1. 函数kernel_init到底做了什么

函数kernel_init

内核kernel.bin是一个可执行程序,对于可执行程序来说,我们只对其中的段感兴趣,因为它们才是程序运行的实质指令和数据所在的地方。所以这个函数的主要作用就是把段加载到内存中该加载的位置。

内核在KERNEL_BIN_BASE_ADDR这个位置,按照 elf 文件的约定,其实这个地方就是 elf 文件头。从这个位置开始就是各个段了,那我们首先就是要获得这个起始位置:

   xor ebx, ebx		;ebx记录程序头表地址
   mov ebx, [KERNEL_BIN_BASE_ADDR + 28]   ; 偏移文件开始部分28字节的地方是e_phoff,表示第1 个program header在文件中的偏移量
   add ebx, KERNEL_BIN_BASE_ADDR

而我们要加载其他段的方式是每次增加一个段头的大小,之后分析其段类型是不是PT_NULL,如果不是就将其拷贝到编译的地址中:

//PT_NULL equ 0
   mov dx, [KERNEL_BIN_BASE_ADDR + 42]	  ; 偏移文件42字节处的属性是e_phentsize,表示program header大小
   cmp byte [ebx + 0], PT_NULL		  ; 若p_type等于 PT_NULL,说明此program header未使用。
   je .PTNULL
   
   ;为函数memcpy压入参数,参数是从右往左依然压入.函数原型类似于 memcpy(dst,src,size)
   push dword [ebx + 16]		  ; program header中偏移16字节的地方是p_filesz,压入函数memcpy的第三个参数:size
   mov eax, [ebx + 4]			  ; 距程序头偏移量为4字节的位置是p_offset
   add eax, KERNEL_BIN_BASE_ADDR	  ; 加上kernel.bin被加载到的物理地址,eax为该段的物理地址
   push eax				  ; 压入函数memcpy的第二个参数:源地址
   push dword [ebx + 8]			  ; 压入函数memcpy的第一个参数:目的地址,偏移程序头8字节的位置是p_vaddr,这就是目的地址
   call mem_cpy				  ; 调用mem_cpy完成段复制
   add esp,12				  ; 清理栈中压入的三个参数

mem_cpy函数做的就是逐字节拷贝,逻辑比较简单这里就不放代码了。以上汇编代码其实就完成了一个段的拷贝,那么我们就可以接着比较下个段了:

   add ebx, edx				  ; edx为program header大小,即e_phentsize,在此ebx指向下一个program header 
   loop .each_segment

这样咱们就完成了kernel.bin文件的内存拷贝工作,它现在正式的存在在了我们的内存中!

至此,我们的内存中的内容就变成了下图:
在这里插入图片描述

:内核文件kernel.bin和mbr在初始化之后是可以覆盖的

参考文件

[1] 操作系统真相还原
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shenmingik

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值