0%

OS Lab1

思考题

Thinking 1.1

向objdump传入参数 -DS,-D代表反汇编所有内容,-S代表在结果中将十六进制汇编码和反汇编结果共同显示。

使用MIPS交叉编译工具链得到的对应main.c的反汇编对应main部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
00000000 <main>:
0: 27bdffe0 addiu sp,sp,-32
4: afbf001c sw ra,28(sp)
8: afbe0018 sw s8,24(sp)
c: 03a0f025 move s8,sp
10: 3c1c0000 lui gp,0x0
14: 279c0000 addiu gp,gp,0
18: afbc0010 sw gp,16(sp)
1c: 3c020000 lui v0,0x0
20: 24440000 addiu a0,v0,0
24: 8f820000 lw v0,0(gp)
28: 0040c825 move t9,v0
2c: 0320f809 jalr t9
30: 00000000 nop
34: 8fdc0010 lw gp,16(s8)
38: 00001025 move v0,zero
3c: 03c0e825 move sp,s8
40: 8fbf001c lw ra,28(sp)
44: 8fbe0018 lw s8,24(sp)
48: 27bd0020 addiu sp,sp,32
4c: 03e00008 jr ra
50: 00000000 nop
...

Thinking 1.2

使用我们编写的readelf 解析 target/mos 得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0:0x0
1:0x80010000
2:0x80011d10
3:0x80011d28
4:0x80011d40
5:0x0
6:0x0
7:0x0
8:0x0
9:0x0
10:0x0
11:0x0
12:0x0
13:0x0
14:0x0
15:0x0
16:0x0

我们编写的 readelf 不能解析自身的原因是:hello在编译时加入了 -m32编译选项,而readelf没有,代表hello是按32位程序编译的,而readelf没有指定,按默认的64位编译。使用 objdump -h也能发现:hello 的类别是 ELF32,readelf的类别是 ELF64。

Thinking 1.3

由指导书附录A.1部分可知,操作系统上电启动需要调用 bootloader 先由 ROM 中刻好的 stage1 初始化硬件,再将存储设备中的 stage2 部分代码放入 RAM 并调用。显然这里的启动入口地址对应 bootloader 的 stage2 所在的地址,而不是操作系统的内核入口。待 stage2 载入内核,设置好启动参数后,才会跳转到在固定的内核地址的内核部分。

实验中,GXemul 本身支持加载 ELF 格式内核,所以启动流程被简化为直接加载内核到内存并跳转到内核的入口(具体内核各节的加载地址也可以由更改 Linker Script 文件来更改),因此只需要将内核放在规定好的内核入口处即可正常启动内核。

难点分析

指针基础

Exercise 1.1中我和其他同学发现一个共性问题,如下代码所示:

1
2
sh_table = ehdr + ehdr->e_shoff; // wrong
sh_table = binary + ehdr->e_shoff; // right

前者错误,后者正确。主要是因为C语言的指针运算特性:某type类型的指针+1实际上会让指针地址值增加sizeof(type),在本题情况下就是sizeof(Elf32_Ehdr),但这显然不是offset对应的单位,因此应该更换成类型为void *的binary指针,或者采用下列这种写法:

1
sh_table = (void *)ehdr + ehdr->e_shoff;

Linker Script

Exercise 1.2中,需要注意空格和示例内容一样,不能少空格,不然会syntax error。(冒号左右和左右大括号的左右都要加空格)

printk()

对于Exercise1.4,首先是涉及指针操作,不能搞混fmt和*fmt,即避免出现将fmt作为字符处理或进行类似*fmt++的操作。

其次是注意每次循环间width, long_flag, neg_flag, ladjust, padc五个变量值要记得重置回默认值(可以在循环开头直接赋值默认值,也可以通过保证所有分支结构都写else的方式解决,我使用的是后者)。

最后有一个需要略微阅读其他部分代码的地方:有符号整数对负数的处理。这里如果只是对负值进行判断并赋值neg_flag并传入的话是不够的,需要将num取负,因为print_num接收到的neg_flag仅用于指示是否输出负号,实际打印时永远按无符号数打印,所以需要取负。

实验体会

刚开始阅读Lab1部分的时候还有种不知所云的感受,但是采取“先记住,往后慢慢读”的方式逐渐的也就理解了神奇的操作系统(很小的一部分)。Lab1整体能学到不少东西,不过代码部分还是相对简单,个人认为相比CO略显轻松,期待后续内容。