Lab1实验报告
一、思考题
Thinking1.1
请查阅并给出前述objdump中使用的参数的含义。使用其它体系结构的编译器(如课程平台的MIPS交叉编译器)重复上述各步编译过程,观察并在实验报告中提交相应结果。
objdump
中参数:-D
:从objfile中反汇编所有的section;-S
:尽可能反汇编出源代码,尤其当编译的时候指定了-g这种调试参数时,效果比较明显。
交叉编译:
对于一个文件gpa.c:
int main ()
{
int point;
double sum_1, sum_2, sum_3;
double gpa;
sum_1 = 100 - point;
sum_2 = sum_1 * sum_1 * 3;
sum_3 = sum_2 / 1600;
gpa = 4 - sum_3;
return 0;
}
执行以下指令:
git@20373512:~/20373512$ /OSLAB/compiler/usr/bin/mips_4KC-gcc -c gpa.c
git@20373512:~/20373512$ /OSLAB/compiler/usr/bin/mips_4KC-ld -o gpa gpa.o
git@20373512:~/20373512$ /OSLAB/compiler/usr/bin/mips_4KC-objdump -DS gpa > gpa.txt
输出结果:
004000b0 <main>:
4000b0: 3c1c0fc0 lui gp,0xfc0
4000b4: 279c7f40 addiu gp,gp,32576
4000b8: 0399e021 addu gp,gp,t9
4000bc: 27bdffc8 addiu sp,sp,-56
4000c0: afbe0030 sw s8,48(sp)
4000c4: 03a0f021 move s8,sp
4000c8: 24030064 li v1,100
4000cc: 8fc20028 lw v0,40(s8)
4000d0: 00621823 subu v1,v1,v0
4000d4: 44830000 mtc1 v1,$f0
4000d8: 46800021 cvt.d.w $f0,$f0
4000dc: f7c00020 sdc1 $f0,32(s8)
4000e0: d7c20020 ldc1 $f2,32(s8)
4000e4: d7c00020 ldc1 $f0,32(s8)
4000e8: 46201082 mul.d $f2,$f2,$f0
4000ec: 8f828018 lw v0,-32744(gp)
4000f0: d4400140 ldc1 $f0,320(v0)
4000f4: 46201002 mul.d $f0,$f2,$f0
4000f8: f7c00018 sdc1 $f0,24(s8)
4000fc: d7c20018 ldc1 $f2,24(s8)
400100: 8f828018 lw v0,-32744(gp)
400104: d4400148 ldc1 $f0,328(v0)
400108: 46201003 div.d $f0,$f2,$f0
40010c: f7c00010 sdc1 $f0,16(s8)
400110: 8f828018 lw v0,-32744(gp)
400114: d4420150 ldc1 $f2,336(v0)
400118: d7c00010 ldc1 $f0,16(s8)
40011c: 46201001 sub.d $f0,$f2,$f0
400120: f7c00008 sdc1 $f0,8(s8)
400124: 00001021 move v0,zero
400128: 03c0e821 move sp,s8
40012c: 8fbe0030 lw s8,48(sp)
400130: 27bd0038 addiu sp,sp,56
400134: 03e00008 jr ra
400138: 00000000 nop
40013c: 00000000 nop
Thinking1.2
也许你会发现我们的readelf程序是不能解析之前生成的内核文件(内核文件是可执行文件)的,而我们之后将要介绍的工具readelf则可以解析,这是为什么呢?(提示:尝试使用readelf -h,观察不同)
原因:内核文件是大端存储,而我们的readelf程序解析的文件是小端存储的文件。
Thinking1.3
在理论课上我们了解到,MIPS 体系结构上电时,启动入口地址为0xBFC00000(其实启动入口地址是根据具体型号而定的,由硬件逻辑确定,也有可能不是这个地址,但一定是一个确定的地址),但实验操作系统的内核入口并没有放在上电启动地址,而是按照内存布局图放置。思考为什么这样放置内核还能保证内核入口被正确跳转到?
(提示:思考实验中启动过程的两阶段分别由谁执行。)
操作系统启动时,首先启动bootloader程序,在stage1阶段初始化硬件设备。此时对于MIPS处理器来说,MIPS体系结构上电时,启动入口的地址为0xBFC00000(或为某一个确定的地址),为一个虚拟地址,在kseg1中。将虚拟地址的高三位清零之后,对应的物理地址为0x1FC00000,从这里开始MIPS的第一条指令,完成初始化基本的硬件设备的工作和为stage2做准备。
之后进入stage2阶段的入口函数,BIOS从MBR中读取开机信息,以Linux中的GRUB开机管理程序为例,BIOS加载MBR中的GRUB代码后把CPU交给GRUB,GRUB一步一步加载自身代码,从而识别文件系统,然后将文件系统中的内核镜像文件加载到内存中,此时会有一个内存移动的操作,最后BIOS跳到指定的地址运行,这样便可以保证内核入口被正确跳转到。
Thinking1.4
与内核相比,普通进程的sg_size 和bin_size 的区别在于它的开始加载位置并非页对齐,同时bin_size的结束位置(va+i,其中i为计算出的该段在ELF文件中的大小)也并非页对齐,最终整个段加载完毕的sg_size 末尾的位置也并非页对齐。请思考,为了保证页面不冲突(不重复为同一地址申请多个页,以及页上数据尽可能减少冲突),这样一个程序段应该怎样加载内存空间中。
可以采用段式存储的方式,变线性地址为二维的地址,以便于将不同的页在空间上分隔开。
Thinking1.5
内核入口在什么地方?main 函数在什么地方?我们是怎么让内核进入到想要的 main 函数的呢?又是怎么进行跨文件调用函数的呢?
内核入口位于0x80000000处;
main函数位于0x80010000处;
在内核的入口处修改跳转指令的地址,再利用jal指令跳转,就可以让内核 进入到想要的main函数;
在跨文件调用函数时,先将原来的变量和返回地址入栈,之后跳转到想调用的函数的地址,函数调用结束后再将要返回的地址出栈并返回,之后使原来的变量也出栈并恢复。
Thinking1.6
查阅《See MIPS Run Linux》一书相关章节,解释boot/start.S 中下面几行对CP0 协处理器寄存器进行读写的意义。具体而言,它们分别读/写了哪些寄存器的哪些特定位,从而达到什么目的?
/* Disable interrupts */ mtc0 zero, CP0_STATUS ...... /* disable kernel mode cache */ mfc0 t0, CP0_CONFIG and t0, ~0x7 ori t0, 0x2 mtc0 t0, CP0_CONFIG
mtc0 zero, CP0_STATUS
写CP0寄存器,将CP0寄存器初始化为零;
mfc0 t0, CP0_CONFIG
读CP0寄存器,并将值存入t0中;
and t0, ~0x7
将后三位清零;
ori t0, 0x2
将后三位变为010,010可以决定固定kseg0的域是否进入缓存;
mtc0 t0, CP0_CONFIG
写CP0寄存器,将t0的值写入。
通过以上操作可以禁用内核缓存模式。
二、实验难点图示
本次实验难点主要有两点。
第一点在于exercise1.2中ELF文件的结构:
- 要了解ELF文件在编译和运行两种视角下的不同结构及他们之间的关系
- 要理解ELF文件之中的三个结构体并利用文件头地址和偏移量求地址
第二点在于exercise1.5中print.c中lp_Print函数的流程:
- 注意是按照
%[flags][width][.precision][length]specifier
这样的顺序进行的,不能够打乱 - 要重点理解代码中出现的函数的作用,搞清楚重要变量的作用
- 注意多个输出的即
% XXXX % XXXX
这种功能,也就是说可以多个输出,直到读到’\0’后才结束 - 注意要将padc,ladjust等标志作用的变量都赋给初值,尤其是padc初值为’ ’
- 我的一个bug是检验*fmt是否为%时没有同时确保fmt不是’\0’以至于出错
三、体会与感想
lab1的实验进行了两周,第一周考查的是elf文件相关的知识,因为这个对我来说比较陌生,所以花费了不少的时间去理解。可能是被计组搞得有点ptsd,以至于课下花了大约有七八小时的时间学习这一部分,上机前心里还是特别慌,但是没想到五分钟就把exam做出来了,然后花两个小时做extra还没做出来Orz。
第二周考察的是写一个printf,这部分我学习的时间倒是没有elf长,花费六小时左右。MIPS的内容在学计组时就接触过很多,因此不难理解,printf相当于考察C语言指针的使用,加上注释写的比较详细,因此完成也不困难。这一部分对操作系统内核的理解很重要,但是却不是难点,我觉得难点在于C语言指针的使用,上机考试因为对结构体指针的不熟悉再加上对那几个.c文件的不理解所以挂掉了wuwu…
这次实验也学到了很多,第一是关于Makefile的使用以及make和make clean的操作;第二是关于内核地址与MIPS的结合,.data和.text在上学期学计组时就一直存疑,学的不是很明白,经过lab1的学习明朗了很多;第三是关于MIPS与栈,MIPS中函数的调用与返回,以及相关的进栈出栈操作,也是上学期计组留下的坑🕳;第四就是对大段代码的理解能力有所提升,对C语言之间各个.c文件的相互调用,以及.h头文件有了新的认识。
总的来说,lab1给我的教训就是,还是要重视C语言,尤其重视C语言指针的使用。怎么说呢,其实之前就听过学长的建议,说C语言指针对os学习很重要,其实考前也有去复习,但是由于大一C语言基础确实很差所以一时半会确实很难学精,加上oo夹击所以对这次上机有点轻视了,很惨痛的教训,而且lab1就挂掉了说实话打击还是挺大的,希望以后的lab更加努力吧。
四、指导书反馈
有一点有个小小的疑惑,在exercise1.5写printf的时候,注释里把检查是否是long放的比较靠前,但是按照printf的格式化描述,应该是%[flags][width][.precision][length]specifier
这样的顺序,所以检查是否为long int 的位置应该比较靠后。
对于像我这种几乎是看着注释写代码的,就因为判断的顺序问题而找了很久的bug。不知道这是助教gg们的小失误,还是有意而为之,警告我们不要看着注释写代码嘛。
五、残留难点
第一次上机extra有遇到memsz和filesz,不太懂两者区别;
ELF文件内容很多,仅仅教程里讲到的可能还是不足以让我完全理解,不过课程网站上给的ELF参考资料很有用,由于时间原因还没有看完,以后会接着理解;
其次是目前还是看不太懂几个print.c、printf.c、print.h、printf.h几个文件之间的关系;
对于gxemul的使用还是不太会,因为我是按照main函数里改printf的方法调试的,几乎没有用过gxemul。