操作系统——内核雏形

实验目的:

如何生成一个内核,能引导该内核,并进行扩展

实验内容:

  1. 汇编和C的互相调用方法
  2. ELF文件格式
  3. 使用Loader加载ELF文件
  4. 如何加载并扩展内核
  5. 设计题:修改启动代码,在引导过程中在屏幕上画出一个你喜欢的ASCII图案,并将第三章的内存管理功能代码、你自己设计的中断代码集成到你的kernel文件目录管理中,并建立makefile文件,编译成内核,并引导

实验环境:

VMware+Ubuntu32位

实验步骤:

1.汇编和C的互相调用方法

操作系统——内核雏形
调用关系示意图如上,我们来看一下代码。
操作系统——内核雏形
操作系统——内核雏形
将foo.asm中定义的函数设为global模式,使得可以被调用,同时导入choose函数 int choose(int a ,int b)。
操作系统——内核雏形
在bar.c中调用myprint函数,并定义choose函数。
编译,链接,运行:
操作系统——内核雏形
可以发现调用是成功的!

2.ELF文件格式

操作系统——内核雏形
格式图如上所示,其中我们来分别分析每一个部分的含义和数据结构。
ELF header:
操作系统——内核雏形
Program header:
操作系统——内核雏形
Program header描述的是一个段在文件中的位置、大小以及它被放进内存后所在的位置和大
小。如果我们想把一个文件加载进内存的话,需要的正是这些信息。

3.使用Loader加载ELF文件

我们的期望是使用Loader加载ELF文件,在之后肯定是要加载内核文件的。加载一个文件无非就是寻找文件、定位文件、读入内存三个步骤,所以在这次的loader中也是遵循这个步骤。
操作系统——内核雏形
和之前实验的思路是类似的,只不过我们这次寻找的是kernel.bin文件。
在这里我们先随便写一个简单的kernel.asm文件来测试一下:
操作系统——内核雏形
就是,现实一个字母K罢了。
我们来跑一跑这个loader程序
操作系统——内核雏形
操作系统——内核雏形
可以看见是成功的,因为出现了ready. 字符

4.如何加载并扩展内核

在上一步骤中,我们已经把kernel加载进了内存,但要想使内核运行并移交控制权,我们还需要先进入保护模式。
进入保护模式的代码已经很常见了,如下所示:
操作系统——内核雏形
但与之前不一样的是,之前大多数描述符的段基址都是运行时计算后填入相应位置的,此时我们不需要这样了,因为我们自己加载了loader,已经确定了段地址并定义为了BaseOfLoader,所以在Loader中出现的变量的物理地址可以由下公式计算:
标号物理地址 = BaseOfLoader * 10h + 标号的偏移
于是我们将BaseOfLoader定义在一个文件里:
操作系统——内核雏形
我们直接用了一个宏BaseOfLoaderPhyAddr来表示BaseOfLoader * 10h,当然是为了方便啦。
保护模式的代码如下,仅仅只是打印一个字符P
操作系统——内核雏形
运行一下:
操作系统——内核雏形
看到P,证明跳入保护模式成功。
成功跳入保护模式之后,我们获得内存信息、开启分页操作、最终整理内核并移交控制权。
获得内存信息是我们之前实验的内容,我们使用15h中断来完成,代码如下:
操作系统——内核雏形
当然我们不能比之前的实验差,所以我们也把他加载进显存并打印:
操作系统——内核雏形
和之前的代码基本就是从第三章中拿来直接使用的,别忘了将调用的函数DispInt,DispStr等包含在32位代码段中。
得到内存信息后,启动分页。
操作系统——内核雏形
运行:
操作系统——内核雏形
在之前的基础上出现了第三章那样的内存信息。
内存信息获取成功,分页开启成功,接下来我们将内存中的kernel程序整理,并移交控制权。
别忘了,我们的内核程序是一个elf程序,elf程序的program header table字段是有重要含义的,让我再来复习一下。
操作系统——内核雏形
操作系统——内核雏形
操作系统——内核雏形
之前说到,我们要整理kernel程序,也就是说我们要把之前导入内存的代码整理到一个指定位置,这里我们需要使用memcpy函数。
操作系统——内核雏形
具体实现如下:
操作系统——内核雏形

但是,由于ld生成的可执行文件中p_vaddr的值较大,在这里超过我们的内存范围,所以我们需要修改一下ld指令时的参数。
操作系统——内核雏形
解决了内存的问题之后,我们就可以向内核交出控制权了。
操作系统——内核雏形
运行一下试试:
操作系统——内核雏形
可以看到,出现了K字符。证明此时内核成功运行了!
接下来我们来进行内核扩展,先跑一下代码看看结果:
操作系统——内核雏形
可以看到最下面一行出现一行字符串,但其实并没有那么简单。
操作系统——内核雏形
首先,在Kliba.asm中定义了一个函数disp_str,用来打印字符串,并且将这个函数导出。
然后在kernel.asm中引入刚刚定义的函数,并将kernel,kliba,string,start链接成kernel.bin并挂载,作为真正的内核程序。
这个代码不仅定义了函数,还使用memcpy函数切换了堆栈和GDT,和之前仅仅打印一个字符的代码相比复杂了许多,但并没有花费很大精力,可见内核的扩展在C语言的帮助下已经简单了许多了。

5. 设计题:修改启动代码,在引导过程中在屏幕上画出一个你喜欢的ASCII图案,并将第三章的内存管理功能代码、你自己设计的中断代码集成到你的kernel文件目录管理中,并建立makefile文件,编译成内核,并引导

1)画出一个ASCII图案
在引导的时候,让他打印两个由字符构成的表情,调用一个打印函数打印如下定义的字符串:
操作系统——内核雏形
函数还是使用的例子程序中提供的,结果如下:
操作系统——内核雏形
打印其他的应该是差不多的,就是要记得构造好这个字符的形状。

2)Kernel扩展
扩展原理很简单,主要是中断处理程序的编写问题。之前我们的实验中有过类似的步骤,我们可以直接调用当时的函数。
操作系统——内核雏形
当然,这里我们不再使用键盘中断,而是使用时钟中断,所以记得修改初始化的值:
操作系统——内核雏形
其他地方与例子程序没什么很大差别,我们来看一下中断处理程序部分:
操作系统——内核雏形
为了减少代码修改,我直接把整个函数复制进来了。。。
也就是说,遇到时钟中断的时候,我们的屏幕将根据时钟中断的“节奏”来打印字符串,在一段时间之后的截图如下:
操作系统——内核雏形
看上去其实挺吓人的,但是从代码实现的角度来说应该是成功了吧。

实验问题:

1.汇编和C的调用方法是怎样的?

将汇编中定义的函数定义为global形式进行导出,将需要使用的函数进行导入;C语言在使用函数之前进行定义。在编译过程中先生成.o文件,并链接所有.o文件成为新文件,作为可执行程序,这个程序既包含汇编文件中的函数也包含C语言中的参数。

2.描述ELF文件格式以及作用

操作系统——内核雏形
ELF header:用来定义ELF文件各信息,例如Program header table个数与偏移地址。
Program header table:
操作系统——内核雏形
操作系统——内核雏形
描述的是一个段在文件中的位置,位置、大小以及它被放进内存后所在的位置和大小。如果我们想把一个文件加载进内存的话,需要的正是这些信息。

3.如何从Loader引导ELF的原理

从Loader引导ELF文件需要经过几个步骤:寻找文件、定位文件、读入内存、转交控制权,其中前三个步骤与是否为ELF文件关系不大,对于要读入内存的文件来说都是需要经过这三个步骤的;最后一个步骤转交控制权则是根据ELF文件头与Program header table中的信息,将内存中的段进行整合并执行、转交控制权。

4.一个内核要能基本使用应该扩展哪些功能,怎么扩展

一个内核是在保护模式中被引导、运行,从实验的角度上说,内核需要接管电脑的控制权,管理计算机上运行的所有程序、进行的所有操作,所以至少应该具有中断管理,线程管理,地址空间和进程间通信等功能。

5.怎么管理内核文件目录?

在之前实验中我们的文件都是散落在一个文件夹里,编译生成的文件也是很混乱,最痛苦的是每次编译、挂载都有很多指令要打。所以我们需要使用make指令来管理内核文件目录。
目前为止我们可以整理成如下所示的结构:
操作系统——内核雏形
然后,在a.img的路径下添加makefile文件,完成编译、链接、挂载的任务,最后启动bochs则可以达到目的。
操作系统——内核雏形
Makefile如上所示,使用如上所述的方法和工具则可以很好的管理内核文件和目录。

相关文章:

  • 2021-12-20
  • 2022-02-13
  • 2022-02-11
  • 2021-11-17
  • 2022-01-02
  • 2021-10-28
  • 2021-08-20
  • 2021-12-25
猜你喜欢
  • 2022-01-24
  • 2021-09-19
  • 2021-07-13
  • 2021-12-15
  • 2021-06-16
相关资源
相似解决方案