练习实验书: https://scpointer.github.io/rcore2oscomp/docs/lab1/intro.html
1.编程作业
1.1 扩展 easy-fs-fuse
跟随前面文档的指引,扩展 easy-fs-fuse,使得它可以生成同时包含 Rust 和 C 用户程序的镜像
- 修改内核代码
此步骤参考任务书 - 修改Makefile
根据项目文档的说明, 基本上完成了生成同时包含 Rust 和 C 用户程序的镜像的需求, 但Makefile
需要进行一定的修改
此处我基于ch8
的代码进行修改主要区别在于, 此次构建1
2
3
4
5
6
7
8FS_IMG := target/fs.img
...
fs-img: $(APPS)
@make -C ../user build TEST=$(TEST) CHAPTER=$(CHAPTER) BASE=$(BASE)
@make -C ../testcases build
@rm -f $(FS_IMG)
@cp ../user/build/elf/* ../testcases/build/
@cd ../easy-fs-fuse && cargo run --release -- -s ../testcases/build -o ../os/$(FS_IMG)fs-img
之前需要先编译testcases
目录下的c程序, 并将user
目录下的elf
文件复制到testcases/build/
下, 根据testcases/build/
生成镜像时需要将任务书中的-t
参数替换为-o
参数 - 结果和测试
在os
下执行make fs-img CHAPTER=8 BASE=2
, 可以生成包含c程序的文件系统镜像。
同理,os
下执行make run CHAPTER=8 BASE=2
后运行42
和hello
程序:42
运行结果可以看出1
2>> 42
Shell: Process 2 exited with code 4242
返回值为42, 这与期望值相符合hello
运行结果可以看出,1
2
3
4
5
6>> hello xwd
Incorrect argc
Shell: Process 2 exited with code 1
>> hello
Incorrect argc
Shell: Process 2 exited with code 1hello
程序无论是否添加参数, 都会输出错误信息, 这也是接下来要解决的问题。
1.2 修改os内核支持C程序
在 usershell 里运行了 42 和 hello 两个用户程序。42 的运行结果是符合预期的,但 hello 的结果看起来不太对,你的任务是修改内核,使得 hello 测例给出正常输出(即给出一行以 my name is 开头的输出,且 exit_code为0)。
- 原因分析
阅读原rCore
文档与本实验文档
可以看出, 二者的栈分布是不同的, 先看旧的栈内存分布
可以看到在此栈的内存分布中, 实际的参数放在接近栈底的位置, 其指向的实际参数在靠近栈顶的位置, 并且通过阅读源码process.rs
中的exec
:可知参数个数1
2trap_cx.x[10] = args.len();
trap_cx.x[11] = argv_base;argc
时通过手动计算args
的长度计算得到的, 并没有存储在栈上。
阅读本实验指导书可知,C程序的栈内存分布如下:
在此栈的内存分布中, 实际的参数放在接近栈顶的位置, 其指向的实际参数在靠近栈底的位置, 并且栈指针指向了argc
而且由testcases/lib/main.c
可知:1
2
3
4
5
6
7
8int __start_main(long *p)
{
int argc = p[0];
char **argv = (void *)(p+1);
exit(main(argc, argv));
return 0;
}p
是传递的栈指针sp
, 因此argc
会被置为栈指针当前指向的数据, 而根据前述分析可知,argc
是存放于a0
寄存器的, 因此后续使用argc
时报错, 这是栈分布于C程序标准不一致导致的。 - 修改os思路
因此,需要将os中初始化栈分布相关的代码进行修改, 使之符合C语言的约定, 实际上我们要修改的代码位于:os/src/task/process.rs
中的pub fn exec(self: &Arc<Self>, elf_data: &[u8], args: Vec<String>)
修改内存分布为上图的形式, 次过程不难, 具体代码实现可参考: 我的实现 - 小bug修改: 去除文件名
观察hello.c
源码:可知其1
2
3
4
5
6
7
8
9
10
11
12
13
14
15int main(int argc, char *argv[]) {
char greeting[11] = "my name is ";
char error[15] = "Incorrect argc\n";
if (argc != 1) {
write(1, error, 15);
return 1;
}
int len = 0;
while(argv[0][len] != 0) {
len++;
}
write(1, greeting, 11);
write(1, argv[0], len);
return 0;
}argv[0]
应当为命令行输入的参数, 而非约定中的程序名, 因此需要在系统调用中对args
中去除第一个函数名参数:
具体代码实现可参考: 我的实现 - 运行结果
再次执行make run CHAPTER=8 BASE=2
后运行hello
程序1
2
3Rust user shell
>> hello xwd
my name is xwdShell: Process 2 exited with code 0
2 问答作业
elf 文件和 bin 文件有什么区别?
elf
是包含符号信息的二进制文件,bin
文件是剥离了二进制信息的符号文件
以下是各个命令的输出1
2
3
4$ file elf/ch6_file0.elf
elf/ch6_file0.elf: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), statically linked, stripped
$ file bin/ch6_file0.bin
bin/ch6_file0.bin: dataelf
文件包含的信息有:- 类型: ELF 64-bit LSB executable
- RISC-V: 表示这是一个 RISC-V 架构的可执行文件。
- RVC: 表示该文件使用了 RISC-V 的压缩指令集。
- double-float ABI: 表示该文件使用了双精度浮点数 ABI(Application Binary Interface)。
- 版本 1 (SYSV): 表示该 ELF 文件采用了 SYSV 版本 1 的格式。
- 静态链接: 表示该文件是静态链接的,即所有的库和依赖在编译时就被链接进来了。
- stripped: 表示该文件已经被剥离了符号信息。
bin
文件包含的信息有: - “data” 表示这是一个二进制数据文件
总而言之,
elf
包含了程序的代码、数据、和用于指示操作系统如何运行程序的元数据,bin
是纯二进制格式的文件,不包含元数据因此
riscv64-linux-musl-objdump -ld ch6_file0.bin > debug.S
命令会报错:
1
2$ riscv64-linux-musl-objdump -ld ch6_file0.bin > debug.S
riscv64-linux-musl-objdump: ch6_file0.bin: file format not recognized
而riscv64-linux-musl-objdump -ld ch6_file0.elf > debug.S
可以得到反汇编文件debug.S