OS_lab1
思考题
Thinking 1.1
请阅读 附录中的编译链接详解,尝试分别使用实验环境中的原生 x86 工具 链(gcc、ld、readelf、objdump 等)和 MIPS 交叉编译工具链(带有 mips-linux-gnu前缀),重复其中的编译和解析过程,观察相应的结果,并解释其中向 objdump 传入的参数 的含义。
使用x86工具链
命令行代码如下
1 | touch hello.c |
只进行预处理gcc -E hello.c -o hello.i
,结果如下(截取hello.i中部分内容)

生成hello.o
并进行反汇编之后的文件如下:

直接编译成可执行文件hello
后,进行反汇编结果如下(截取main部分):

使用MIPS交叉编译工具链
命令行代码如下:
1 | mips-linux-gnu-gcc -E hello.c -o hello.i |
只进行预处理mips-linux-gnu-gcc -E hello.c -o hello.i
,结果如下(截取hello.i中部分内容)

hello.o
反汇编后如下

hello
反汇编如下:

向objdump
传入参数的含义
在指令中,使用了objdump -DS
。
-S
:将代码段反汇编的同时,将反汇编代码与源代码交替显示-D
:反汇编所有的section
Thinking 1.2
尝试使用我们编写的 readelf 程序,解析之前在 target 目录下生成的内核 ELF 文件。
也许你会发现我们编写的 readelf 程序是不能解析 readelf 文件本身的,而我们刚才介绍的系统工具 readelf 则可以解析,这是为什么呢?(提示:尝试使用 readelf -h,并阅读 tools/readelf 目录下的 Makefile,观察 readelf 与 hello 的不同)
先说结论:
- 我们实现的
readelf
是一个简易的对32-bit little-endian
ELF 文件的解析程序 - 但
readelf
文件本身是一个64-bit little-endian
ELF 文件 - 我们编写的
readelf
不支持对64-bit
文件的解析,而系统工具是支持的
下面是题目要求的解析过程:
解析在 target 目录下生成的内核 ELF 文件 mos:

下面尝试解析readelf文件。发现./readelf readelf
指令没有反应,而readelf -S readelf
指令打印出了readelf文件的信息。

使用readelf -h hello
和readelf -h readelf
分别观察结果,发现hello
的类别是ELF32
,而readelf
的类别是ELF64
;除此之外,两文件的ABI
、类型和系统架构也都不一样。


阅读Makefile文件,发现两者的编译指令也有区别,具体内容如下:

可以看到hello.c
被指定为编译为32-bit
的目标文件,因此我们的readelf
可以解析
Thinking 1.3
在理论课上我们了解到,MIPS 体系结构上电时,启动入口地址为 0xBFC00000 (其实启动入口地址是根据具体型号而定的,由硬件逻辑确定,也有可能不是这个地址,但一定是一个确定的地址),但实验操作系统的内核入口并没有放在上电启动地址,而是按照内存布局图放置。思考为什么这样放置内核还能保证内核入口被正确跳转到? (提示:思考实验中启动过程的两阶段分别由谁执行。)
实验中的启动过程
由于QEMU支持加载ELF格式内核,所以启动流程被简化为 加载内核到内存,之后跳转到内核入口。在链接过程中,目标文件被看成节的集合。而链接脚本,也就是Linker Script记录了各个节应该如何映射到段,以及各个段应该被加载到的位置。
实验中,我们的实现方式是:
- 通过编写
Linker Script
,来控制ELF文件各节被加载的位置,实现将内核ELF文件加载到内存指定位置 kernel.lds
中通过ENTRY(_start)
来设置程序入口为_start
,也就是说:链接后的程序从_start
开始执行
难点分析
最大的难点应该是对整体架构的理解。
由于实验对不同部分进行了拆分,所以在基础知识的学习方面不存在什么问题,但是要理解各个模块之间是怎么联系起来的,还是有些困难。(特别是由于lab0对于Makefile的指导书教学比较基础,要看懂lab1的顶层Makefile还是需要去查阅不少资料,而且在不理解每个模块的具体功能的时候,就算看明白了语法可能也不会很明白整个项目是干什么的)
按照具体题目来讲的话,我认为主要难点在EX4,也就是实现printk函数。首先得理解变长参数,然后得搞明白machine.c
、printk.c
和print.c
这几个文件之间的调用关系,看懂每个函数的具体功能和调用格式,然后才能对printk进行补全。
而且由于后几个部分只有一个测试点,bug的定位也是一个问题,得学会看报错信息,然后定位bug去debug。我是在填写kernel.lds
时出现了语法错误,某个地方少了一个分号,当时不会看报错信息还苍蝇乱撞了半天。
实验体会
虽然根据提示填写代码并不是特别困难,但是要理解整个过程还是需要花费一定时间的。因为涉及到的内容比较多,如何很好的理解每个细节,并最终将这些细节串联在一起,在脑内形成对整个流程的主体框架,是一个有一定挑战性的过程。
但是在通过指导书和查阅资料,逐渐理解之后,再回过头去看实验代码,有一种豁然开朗的感觉。特别是好好研究过思考题之后 x 感觉搞明白了很多,如果只是单纯完成实验可能不会搞的很明白的地方。