第三阶段总结
前期实验
第三阶段前期老师带领我们基于Arceos做了两周的实验
- 练习1:支持彩色打印 println!
较为简单,且有多种解法,可以在项目不同的层次进行修改来支持彩色打印 - 练习2:支持HashMap数据类型
在官网找到HashMap的源码然后进行一定增删就可以实现,因为初学rust,有些高级用法很生疏,改起来有点费劲。 - 练习3:为内存分配器实现新的内存算法
我的实现比较粗暴,有些内存空隙直接不要了 - 练习4:解析dtb并打印
在crate.io中有现成的解析dtb的crate,但是需要进行一定修改才能满足任务要求 - 练习5:抢占式调度算法
这个任务比较简单,改一下crates/scheduler/src/fifo.rs
就好了
第二周的五个实验如下:
- 实验一:从外部加载应用
- 实验2:把应用拷贝到执行区域并执行
- 实验3:通过 ABI 调用 ArceOS 功能
- 实验4:正式在 App 中调用 ABI
- 实验5:支持内核和应用分离的地址空间及切换
对于第二周的五个实验总体来说就是把外部应用APP写入PFLASH中,然后在内核中写一个APP加载器,为APP初始好寄存器后跳转到APP执行,同时支持多地址空间以及给APP传递内核API_TABLE
的地址。第二周让我印象深刻的是跳转到APP执行前对寄存器的初始化很重要,如果初始化不到位可能会导致运行APP的时候出现各种错误。同时实验4也让我学到了如何以通过传参的方式支持APP调用内核API。
之后就是进入真正的项目阶段了,项目阶段是在lkmodel上开发,lkmodel相比Arceos我个人感觉难度还是有一定提升,且前两周刚熟悉完Arceos的代码和模块(T_T),一开始还是挺畏难的。不过最后在做的过程中慢慢熟悉lkmodel后也就不那么害怕了。
进入真正的项目阶段
musl入手
由于musl编译出来的app系统调用要比glibc简单的多且少得多,因此我先尝试支持用musl-gcc编译的hello_world程序。
经历大概如下:
- 支持加载hello_world.bin程序并执行
- 支持对elf的解析
- 支持对hello_world.elf程序的执行
支持glibc
要支持glibc要实现和空实现一些syscall,让我印象比较深的是writev_syscall
原本lkmodel的writev_syscall实现如下:
1 | pub fn writev(fd: usize, iov_array: &[iovec]) -> usize { |
但是运行的时候发现没有输出,于是我简单的在writev里面加了一个early_console::write_bytes(bytes);
。但发现输出的时候遇到了奇怪的现象,例如输出一个Hello_world
,他会进行多次输出,并且输出的字符个数逐渐减少。如:
1 | 修改后: |
pub fn writev(fd: usize, iov_array: &[iovec]) -> usize {
assert!(fd == 1 || fd == 2);
let mut total_bytes_written = 0;
for iov in iov_array {
//debug!(“iov: {:#X} {:#X}”, iov.iov_base, iov.iov_len);
let bytes = unsafe { core::slice::from_raw_parts(iov.iov_base as *const _, iov.iov_len) };
let s = String::from_utf8(bytes.into());
early_console::write_bytes(bytes);
total_bytes_written += iov.iov_len;
}
total_bytes_written
}
1 |
|
pub fn pt_regs_addr(&self) -> usize {
self.kstack.as_ref().unwrap().top() - align_down(TRAPFRAME_SIZE, STACK_ALIGN)
}
1 | 但是由于align_down导致减去的值不是TRAPFRAME_SIZE |
.Ltrap_entry_s:
addi sp, sp, -{trapframe_size}
SAVE_REGS 0
mv a0, sp
auipc a1, 0 # Load the upper 20 bits of the PC into a1
addi a1, a1, 12
call riscv_trap_handler
RESTORE_REGS 0
sret
.Ltrap_entry_u:
addi sp, sp, -{trapframe_size}
SAVE_REGS 1
mv a0, sp
li a1, 1
call riscv_trap_handler
addi t0, sp, {trapframe_size} // put supervisor sp to scratch
csrw sscratch, t0
RESTORE_REGS 1
sret
1 | 因此在rust中是用pt_regs_addr()获取trap_frame地址然后进行读写操作是和汇编里面的存储位置不匹配。 |
高地址
0
…
envp[1]
envp[0]
0
…
argv[1]
argv[0]
argc
–
低地址
```
- 由于要支持多地址空间,因此我需要给每个app重新分配一个页表,并在这个基础上为app分配内存。同时我为了复用lkmodel下的代码,我采用
user_mode_thread
来创建一个新的进程,因此我需要对user_mode_thread
代码进行修改,最重要的是修改地址空间那块,我需要为app准备一个新的地址空间,而不是和内核共享同一个地址空间。于是我考虑更改mm_copy模块,但后面想到不能直接改这里,因为mm_copy这个函数会被多个模块调用,我改了可能其他模块就跑不通了。于是我决定在user_mode_thread
外面为app的task.mm重新赋值(即建立新的地址空间),但神奇的事情发生了,赋值后会导致内核的页表(即之前task.mm指向的区域)被回收(Arc指针计数并不为0),之后为了验证是否是我修改了其他模块导致该问题,于是我重新git clone下来了原仓库并在原仓库基础上进行测试,发现还是会出现相同的问题。最后我通过其他手段绕过了该问题。总结
这一个月的项目实习使我debug能力又有了提升同时更加深刻的理解了musl-gcc,glib-gcc的区别,同时也更加深刻的了解了Unikernel。同时也跟同学老师们学到了很多东西。