BUAAOS-Lab1 实验报告
思考题
Thinking 1.1
请阅读附录中的编译链接详解,尝试分别使用实验环境中的原生 x86 工具链(
gcc
、ld
、readelf
、objdump
等)和 MIPS 交叉编译工具链(带有mips-linux-gnu-
前缀),重复其中的编译和解析过程,观察相应的结果,并解释其中向objdump
传入的参数的含义。
首先,我们使用objdump --help
命令来查看-DS
参数的含义,输出结果如下:
1 | -D, --disassemble-all Display assembler contents of all sections |
也就是说,这里参数的含义是:展示二进制文件所有section的反汇编结果,并同时显示机器源码和反汇编得到的汇编代码。
使用原生x86工具链的操作流程与结果在指导书中已经十分清晰了,于是我在此不做复述,仅对使用mips
交叉编译工具链的实验过程进行记录。
首先我在空文件夹下创建C文件hello.c
,内容如下:
1 |
|
对其使用命令mips-linux-gnu-gcc -E hello.c > cache1
,得到的预处理后的文件里只是出现了头文件的内容(只包含printf()的函数头),并没有出现printf()
函数本尊的身影,部分文件内容摘录如下:
1 | extern int printf (const char *__restrict __format, ...); |
再键入命令mips-linux-gnu-gcc -c hello.c
对hello.c
文件进行编译(不链接),使用mips-linux-gnu-objdump -DS hello.o > cache1
进行反汇编,cache1
中的main
函数反汇编结果为:
1 | 7 00000000 <main>: |
而键入命令mips-linux-gnu-gcc -o hello hello.c
,对源文件hello.c
进行编译和链接。再执行命令mips-linux-gnu-objdump -DS hello > cache3
,其中的main
函数结果反汇编结果如下:
1 | 380 004006e0 <main>: |
可以观察到:首先,main
函数入口在链接后被分配了在0x4006e0
的地址,其次,被用于存储跳转到printf()
函数的地址的t9
寄存器的值也不再是默认值了。
Thinking 1.2
思考下述问题:
-尝试使用我们编写的
readelf
程序,解析之前在target
目录下生成的内核ELF文件。-也许你会发现我们编写的
readelf
程序是不能解析readelf
文件本身的,而我们刚才介绍的系统工具readelf
则可以解析,这是为什么呢?(hint: 尝试使用readelf -h
,并阅读tools/readelf
目录下的Makefile
,观察readelf
与hello
的不同)
使用命令./readelf ../../target/mos
对内核ELF文件进行解析,得到如下结果:
1 | 0:0x0 |
对于我们编写的readelf
程序无法解析readelf
文件本身的原因,我先放结论:这是因为我们编写的readelf程序只能够解析32位的ELF文件,而我们编写的readelf程序本身是64位的ELF文件,因此无法被解析。
首先,我们使用readelf -h
命令分别对hello
和readelf
文件进行解析,得到的结果如下:
1 | // hello |
而目录下Makefile
中对hello
的编译命令为:
1 | hello: hello.c |
其中-m32
参数的作用是在64位操作系统下编译生成32位可执行文件,这更加佐证了我的观点:hello是一个32位的可执行文件。
为了进一步佐证我的观点,我编写了一个完全相同的hello2.c
文件,并使用gcc -o hello2 hello2.c
,接着用./readelf hello2
命令尝试对hello2
文件进行解析,未果。再次使用readelf -h hello2
,得到如下结果:
1 | ELF 头: |
可知,我们编写的readelf
程序无法解析自身的原因是该程序只能解析32位的elf文件。
Thinking 1.3
在理论课上我们了解到,MIPS 体系结构上电时,启动入口地址为 0xBFC00000(其实启动入口地址是根据具体型号而定的,由硬件逻辑确定,也有可能不是这个地址,但一定是一个确定的地址),但实验操作系统的内核入口并没有放在上电启动地址,而是按照内存布局图放置。思考为什么这样放置内核还能保证内核入口被正确跳转到?(Hint: 思考实验中启动过程的两阶段分别由谁执行)
根据指导书附录,我们可以知道,真实操作系统启动的时候,在内核被加载运行之前是会经过两个由bootloader
控制进行的阶段(stage1,stage2)的。其中,stage1完成的工作为:基础的硬件初始化,为stage2初始化RAM,载入stage2到RAM,设置堆栈并跳转到stage2入口;而stage2完成的工作为:初始化该阶段所需的硬件设备,载入内核和根文件系统,最后才会为内核设置启动参数并跳转到内核入口。
也就是说,真实的操作系统的启动流程中,内核并不是在一开始就直接被加载到内存上,而是先由bootloader
逐步对硬件设备进行控制与初始化,在硬件初始化完成后再将硬件的控制权交给操作系统的。
而我们的实验编写的MOS操作系统的运行和现实不同的一点是,它是在GXemul仿真器上运行的。而GXemul仿真器支持直接加载ELF格式的内核文件,也就是说我们在实验中,因为仿真器已经提供了本由bootloader
的引导功能(即硬件已经被初始化),更通俗点讲,就是需要bootloader
程序去做的 “盘活硬件”的脏活儿 已经不用做了,启动的时候也不需要从 bootloader
程序的入口 开始运行了。所以在启动时我们只需要通过Linker Script
保证将内核ELF文件加载到内存的合适位置上就好。
本次实验的难点
我认为本次实验就代码实现上并不算困难。那么难点在哪里呢?我想难主要难在 “带着镣铐跳舞”。本次实验让我明白操作系统实验不是像以往课程实验那样,自己从头到尾实现一个程序,而是在别人的代码框架上做补全。这就要求我设计程序的基础不是我的架构设计,而是这套代码本身的架构设计。我的程序设计是建立在对涉及到的相关代码提供的“基础设施”以及设计思路的基础之上的,而不是我想怎么来就怎么来。
我的心得体会
跨多个代码文件进行阅读并总结思考编码方案,这对我这样的没有接触过大项目的人而言是一个很有挑战性的过程,希望我的工程设计和把控能力能在本学期的操作系统课程中取得锻炼和进步。