0%

给第一周做一个总结

  • 参加这个lab的起初目的有两个:一是有可能会有实习机会,但是现在看到这么多大佬估计也挺难了哈,二是想考研408,加深自己对于操作系统的理解。另外可以丰富自己的项目知识的积累,过去一个星期了,发现自己的这个选择真是无比的正确,在学校待的两年发现什么实操都不会,学到了很多工具的使用(非常感激群里的大佬们的帮助),并且在学习的过程中也对rust产生了浓厚的兴趣,非常想去学习。
  • 第一周在学习rust的语法,主要是跟着B站的一个up主(软件工艺师)在学习,讲解的非常的好,而且没有废话,很细节,结合rust中文圣经补充一些知识。
  • 不过没学多久就准备蓝桥杯了,刚好学到生命周期那个部分,在第一周做rustlings写到48题,刷到现在感到收获很大,深刻体会到要学会一门语言是要多加练习的。
  • 接触到rust的语言之后,我对它产生了浓厚的兴趣,虽然这门语言很难,但是也很有趣,思想也很先进,不同于初学python时不用敲代码就可以掌握,学习rust经常一个语法要写好几遍才堪堪掌握。
  • 比较开心的是,学完了trait和生命周期,这两个点大家都说难,但是我认为比较好理解,看来我果然还是很有天赋的吧哈哈哈哈哈哈哈哈!!!
  • 好吧,这也没什么要记录的了,群里很多大佬在第一二天就已经将rustlings写完,而我连github还没整明白,在第二周补补知识,把rustlings写完(话说这个博客提交又要研究一个晚上了wuwuwu)。
  • 参考的rust语言圣经:https://course.rs/basic/compound-type/string-slice.html
  • 讲解的超棒的B站视频:【Rust编程语言入门教程(Rust语言/Rust权威指南配套)【已完结】-哔哩哔哩】 https://b23.tv/AaXsgZd

第二周

  • 这一周一开始把rust剩下的智能指针,多线程,闭包等全部学完了,但是直到开始写剩下的rustlings时,才发现实际写起来困难重重,经过反复的查阅官方文档以及GPT的帮助之下,才弄懂了接下来的题。
  • 但是到了algorithm的十道题,每啃一道都会花费不少的时间,需要反复检查所有权和trait的使用,
  • 建议还是反复多练习才能更好的掌握,这次新增的这十道算法题也确实帮助我更好的打好了基础。
  • 最后,非常感谢能有机会参加这次活动,希望在第二阶段可以有更多收获,同时也为社区回馈更多。

code-debug是一个支持跨特权级调试的VSCode插件。在这篇文章中,我将介绍利用这个调试插件在VSCode上对ArceOS进行源代码级调试的过程。

首先我们需要下载 gdb-multiarch :

1
sudo apt install gdb-multiarch

接着我们运行一下 ArceOS,从而生成 bin 和 elf 文件. 这里以RISC-V上的单核arceos-helloworld为例:

1
make A=apps/helloworld/ ARCH=riscv64 LOG=info SMP=1 run

在 ArceOS 的输出中,我们发现了 QEMU的启动参数:

1
qemu-system-riscv64 -m 128M -smp 1 -machine virt -bios default -kernel apps/helloworld//helloworld_riscv64-qemu-virt.bin -nographic

我们将这些启动参数转移到配置文件 launch.json 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
   //launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "gdb",
"request": "launch",
"name": "Attach to Qemu",
"executable": "${userHome}/arceos/apps/helloworld/helloworld_riscv64-qemu-virt.elf",
"target": ":1234",
"remote": true,
"cwd": "${workspaceRoot}",
"valuesFormatting": "parseText",
"gdbpath": "gdb-multiarch",
"showDevDebugOutput":true,
"internalConsoleOptions": "openOnSessionStart",
"printCalls": true,
"stopAtConnect": true,
"qemuPath": "qemu-system-riscv64",
"qemuArgs": [
"-M",
"128m",
"-smp",
"1",
"-machine",
"virt",
"-bios",
"default",
"-kernel",
"apps/helloworld/helloworld_riscv64-qemu-virt.bin",
"-nographic",
"-s",
"-S"
],

"KERNEL_IN_BREAKPOINTS_LINE":65, // src/trap/mod.rs中内核入口行号。可能要修改
"KERNEL_OUT_BREAKPOINTS_LINE":124, // src/trap/mod.rs中内核出口行号。可能要修改
"GO_TO_KERNEL_LINE":30, // src/trap/mod.rs中,用于从用户态返回内核的断点行号。在rCore-Tutorial-v3中,这是set_user_trap_entry函数中的stvec::write(TRAMPOLINE as usize, TrapMode::Direct);语句。
},
]
}

我们在qemuArgs中添加了 -s -S 参数,这样qemu在启动的时候会打开gdb调试功能并且停在第一条指令处,方便我们设置断点.

此外,应当注意executable参数指向包含符号表的elf文件,而不是去除符号表后的bin文件。

由于ArceOS是unikernel,没有用到用户态,因此以下这三个参数不需要填写:

1
2
3
"KERNEL_IN_BREAKPOINTS_LINE":65, // src/trap/mod.rs中内核入口行号。可能要修改
"KERNEL_OUT_BREAKPOINTS_LINE":124, // src/trap/mod.rs中内核出口行号。可能要修改
"GO_TO_KERNEL_LINE":30, // src/trap/mod.rs中,用于从用户态返回内核的断点行号。在rCore-Tutorial-v3中,这是set_user_trap_entry函数中的stvec::write(TRAMPOLINE as usize, TrapMode::Direct);语句。

最后我们再次按f5开始调试ArceOS. 我们发现Qemu虚拟机启动,ArceOS停在了第一条指令

1
oslab@oslab:~/arceos$  qemu-system-riscv64 -M 128m -smp 1 -machine virt -bios default -kernel apps/helloworld/helloworld_riscv64-qemu-virt.bin -nographic -s -S

接下来我们设置断点。比如我们在Hello, World输出语句打一个断点,然后按”▶️”.我们会发现断点触发了:

以上,通过一些简单的设置,我们就得以用code-debug调试器插件调试一个新OS.

Unikernel 学习心得

近期学习了一些关于Unikernel的知识,以下是一些心得体会:

  • Unikernel是基于组件化的思想设计的,由各种模块构成组件,再由各种组件构成最终的操作系统。
  • 而对于各种模块是如何被选择的,则是采用feature机制,指定最终所需要的模块

ArceOS实验

  • 这周进行了ArceOS的三个实验,在练习一中学习了用println!进行彩色打印。练习二主要学会了对ArceOS进行扩展开发,在axstd中加入了Hashmap,在axhal模块中加入rng生成器,初步了解了ArceOS的调用结构。
  • 练习三修改了axalloc模块中allocator的算法,改为early算法,同时用lock()和unimplemented!()禁用其他算法

学习流程

第一天的接触就是修改CI/CD的错误,我的认知里面是写构建的yml文件,实际上是需要解决starry跑在x86的问题,第一天就大概看了starry的设计文档了解了它和arceos的承接关系和相关的模块图,于是在和杨金博同学沟通后对编译选项和copy.S的代码进行了x86的改造之后发现pci等还有问题,于是问题交到金博哥手里,这时候进行了unikernel的学习,石磊老师ppt讲解和训练营的录课大概看过之后对unikernel有了大概的认识,之后就边处理strace那边的数据边学习和做lab。

进展

目前在做第三周的实验的lab1,将c程序的镜像打包到文件系统实现了,目前对hello的报错进行修改,目前的思考是hello的命令行参数处理有问题,需要内核提供支持,目前定位到的代码如下

1
2
3
4
5
6
7
8
9
10
11
if let Some(app_inode) = open_file(path.as_str(), OpenFlags::WRONLY) {
let all_data = app_inode.read_all();
let process = current_process();
let argc = args_vec.len();
process.exec(all_data.as_slice(), args_vec);
// return argc because cx.x[10] will be covered with it later
println!("exec argc is {}", argc as isize);
argc as isize
} else {
-1
}

执行后可在shell中看到初始为0执行hello程序后为1但是此时的参数处理是输出了argc为0的错误输出,目前在继续看代码准备把后续都尽快跑通。

#Unikernel 总结报告

经过若干天关于unikernel的学习,对于单一特权级的操作系统有了全新认识完成了练习1-5,练习分别为:支持彩色打印、支持HashMap、修改内存分配算法、增加一个axdtb的模块组件、修改fifo算法通过ex5测试应用

练习心得

练习了修改代码、不断检错纠错能力和熟悉了unikernel启动流程、单一特权级、单一应用、单一地址空间的操作系统的工作流程、练习一完成了axstd::println的修改、练习二;通过阅读标准库HashMap加以修改、连续三实现earlyallocator的内存分配方法、修改协作式调度为抢占式调度运行ex5程序、

##实验心得

实验一和二,将程序数据按照一定格式保存到app.bin文件中修改汇编指令,加载外部应用,实现app loader的外部应用加载器;实验三为公国ABI调用ArceOS功能,两个医用一个是nop、一个是wfi;实验四、实现sys_hellosys_putcharsys_terminate功能调用,通过std::process::exit退出ArceOS;实验五为改造应用hello_app,建立独立页表,把系统调用包装成函数,通过初始化和切换函数实现程序

本次主要采用是通过类似于在GNU/LD链接器的方式来完成整个任务。主要的参照依据是csapp中有关链接的相关说明。
目前的实际完成进度在于完成了符号的解析。但是后面的重定包括段重定位以及符号的重定位,目前还没有完成。

目前操作上来说,对于后续任务影响最大的内容在于之前任务的不正确完成,包括但不限于之前任务中埋藏的坑,比如说文件不能正确识别,将 1B 认定成为 一个16进制数而导致对于加载的文件中的align的错误判断,甚至还将用于解释rust内部结构体的自动align用于解释这里所出现的人为导致的align。

由于其他因素的影响,我在前半段时间其实没有过多的参与到其中来,近几天才加入到课题中。在参加课题之前其实对于libc,musl,甚至是elf文件没有什么过多的了解。简单的了解了下整个过程,本来看了下还想先直接用ld将对应的库链接好之后再放到arceos上读取(这是郝淼同学的实现思路),但是想到在 ld 将各种对象文件链接起来之后,实际上最后在使用的时候还是需要调用链接器 [–dynamic-linker可以指定对应链接器的地址] (当时实际上没想到可以直接实现链接器最后被调用的功能)。后面就按照实现一个链接器的想法继续实验。

目前主要使用 elf crates 实现了如下效果:

  1. 能够对于脚本中的符号进行重定位
    目前对于重定位的操作实现尚未完成,根据要求,每一步实现实际上都需要遍历多次,更别提最原始的实现中由于对象文件的顺序引起的问题可能也需要往复遍历,正在尝试优化算法以及修改elf crates使之有着更好的体验。

未来工作可能没有特别大的必要开展,原因如下,在相同情况下,就算最终能够实现了一个能够在arceos上运行的链接器也是没有必要的。

  1. 现有的基于GNU/LD的链接器可以比较好的行使其职能,重复实现一个实际上没有必要实现的链接器会丢失原先所实现的优化,同时因为测试相对于前者而言更少,反而可能会导致更多错误,然而在这种实现的链接器面前,除非同时支持大量的编译工具,否则意义实际上不太大,只能算是个展示玩具。

/// BLUE PRINT
///
/// 1. LOAD APPLICATION
/// 2. SYMBOL RESOLUTION: make sure all symbol has been meet.
/// 3. RELOCATION:
/// [1] RELOCATION SECTION:
/// merges all sections of the same type into a new aggregate section of the same typecombine
/// BC we only need SHT_PROGBITS thus not care about others (copy).
/// [2] RELOCATE SYMBOL REFERENCES IN SECTIONS:
/// Modify references to each symbol in code and data sections so that they point to the correct run-time address

下面的伪代码实际完成了符号加载工作,重定向部分的代码还没有开始实际测试,就算未来有按照这个方法实现的必要,类似郝淼同学重新实现 elf crates 甚至在原先基础上进行修改的行为是必要的,现有的调用过程太长,使用起来不太舒服。

直接通过 shdr.e_type 实现对于符号的定位,根据CSAPP的说明,使用多个集合对于符号进行处理,实现符号匹配效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
match ehdr.e_type {
ET_REL => { //
foreach symtabs {
if sym.is_undefined() {
// 加入 U 未定义符号组
} else {|
// 加入 D 已解析符号组
}
}
// 加入总管辖 elf 文件组 TODO 目前只实现了添加,并没有实现基于往复判断的剪枝
},
ET_DYN => {
foreach symbol in U {
if contains undefined symbols {
// 从 U 未定义符号组删除本符号
// 加入 D 已解析符号组
// 加入总管辖 elf 文件组 TODO 目前只实现了添加,并没有实现基于往复判断的剪枝
}
}
}

}

2023开源操作系统训练营第三阶段项目一总结报告

练习1,2:

该练习实现了一个 app 名为 loader 的外部应用加载器,然后实现了两个外部应用,熟悉了编译生成和封装二进制文件的流程.

该练习的重点在于头结构的构造与实现,我将应用的头结构大致分为三部分,第一部分为应用数量,第二部分为各应用长度,第三部分为应用数据.

大致实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type BinNumType = u32;
const BIN_NUM_TYPE_BYTES: usize = 4;
type BinSizeType = u32;
const BIN_SIZE_TYPE_BYTES: usize = 4;
let apps_num = unsafe {
(*(PLASH_START as *const BinNumType)).to_be()
};
let mut size_offset = PLASH_START + BIN_NUM_TYPE_BYTES;
let mut start_offset = size_offset + BIN_SIZE_TYPE_BYTES * apps_num as usize;
for i in 0 .. apps_num {
let load_size = unsafe {
(*(size_offset as *const BinSizeType)).to_be() as usize
};
let load_code = unsafe {
core::slice::from_raw_parts(start_offset as *const u8, load_size)
};
println!("load code {:?}; address [{:?}]", load_code, load_code.as_ptr());
let run_code = unsafe {
core::slice::from_raw_parts_mut(RUN_START as *mut u8, load_size)
};
run_code.copy_from_slice(load_code);
println!("run code {:?}; address [{:?}] size [{}]", run_code, run_code.as_ptr(), load_size);
size_offset += BIN_SIZE_TYPE_BYTES;
start_offset += load_size;
}

下面是构造image文件的大致代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/bin/bash
# 获取当前目录下所有的 .bin 文件
apps=(hello_app.bin putd_app.bin)
# 计算应用数量
num_apps=${#apps[@]}
# 创建临时文件用于存储应用大小
size_file="temp_sizes.bin"
> "$size_file"
# 创建一个新的文件用于存储合并后的应用内容
content_file="temp_content.bin"
> "$content_file"
# 写入应用数量(4 字节)
printf "%08x" $num_apps | xxd -r -p > "$size_file"
# 循环遍历每个应用并写入其大小到临时文件
for app in "${apps[@]}"; do
# 获取并写入当前应用大小(4 字节)
app_size=$(stat -c%s "$app")
printf "%08x" $app_size | xxd -r -p >> "$size_file"
# 追加应用内容到另一个临时文件
cat "$app" >> "$content_file"
done
combined_file="combined.bin"
# 合并大小信息和应用内容
cat "$size_file" "$content_file" > "$combined_file"
output_file="apps.bin"
dd if=/dev/zero of="$output_file" bs=1M count=32
dd if="$combined_file" of="$output_file" conv=notrunc
mkdir -p ../arceos/payload
mv "$output_file" ../arceos/payload/apps.bin
# 清理临时文件
rm "$size_file" "$content_file" "$combined_file"
echo "合并完成,输出文件:$output_file"

练习3

本实验较为简单,在原来的基础上修改外部应用的汇编指令以及然后在遍历程序时将其执行即可.

练习4

本实验也比较简单,仿照前面的系统调用写一个即可,关机方法:std::process::exit.

练习5

因为多次系统调用a7寄存器的值会被修改,导致报错,所以我们需要将其存入变量中防止abi_table被修改,而且还要声明clobber_abi(“C”)保持Rust代码与内联汇编之间的一致性.然后在我写的过程中,出现了冒用 in(reg) 和 out(reg) 的错误,导致传入的 ABI 接口的地址导致调用函数的时候接口地址丢失,在第二次调用的访问未映射的地址导致错误发生.

练习6

在练习5的基础上,初始化地址空间以后,对于每一个外部应用调用 RUN_START 前切换地址空间即可.但此时应用并无法正确返回,是因为外部引用主动阻塞导致的,外部应用不能进行阻塞,我们需要修改 _start 函数的返回值为 ();

ArceOS Unikernel 总结报告

回顾学习ArceOS Unikernel的这两周,我学到了很多相关知识。通过石磊老师的教导,我先是从第一个星期的组件化教程开始,实现了彩色输出,然后完成了对HashMap的移植,在用early算法实现了内存分配器后又完成了 dtb 文件的解析和输出,最后修改原有的协作式调度算法fifo为抢占式调度。这一步步的任务让我逐渐掌握了 Unikernel 的用法,对 ArceOS 的整体架构、内存分配及调度算法有了更深入的了解。
然后是第二周的基础任务,分别是:从外部加载应用、将应用拷贝到执行区域并执行、通过 ABI 调用 ArceOS 功能、在 App 中正式调用 ABI 以及支持内核和应用分离的地址空间及切换,基本都是更着做就可以了,通过这些练习,我们也更加深入地了解 ArceOS 系统的运行机制和应用开发技巧。下面本别讲解以下各练习的具体做法及思路。

练习题思路总结

练习 1 和 2

本实验实现加载器 loader ,从外部加载 bin 应用到 ArceOS 地址空间,对头结构的设计要让loader可以读取应用信息。
头结构的设计:
App总数 App1大小 App1 内容 App2 大小 App2 内容 … AppN 大小 AppN 内容
这样就可以很好地管理各个应用程序,从而实现应用程序的加载。

练习 3

在之前实验的基础上修改汇编指令以及 loader 改用批处理的方式加载外部应用即可。

练习 4

本实验只需要添加 SYS_TERMINATE 系统调用,然后让 SYS_TERMINATE 直接调用 axstd::process::exit 即可。

练习 5

这个练习的前两个要求都不难,难的是第三个要求,每一次ABI调用返回到loader的时候都需要返回到应用上一条指令的状态,所以ABI调用的时候需要保存上下文。但在实现时发现a7会一直变化,所以需要使用clobber_abi(“C”) 并额外用临时寄存器存放和加载 abi_table 。最终实现上下文的保存。

练习 6

基于之前的 hello_app 应用,仅保留 putchar 函数。先将 _start 函数的返回值为 (),为 APP 建立独立的页表,实现初始化和切换函数,然后将练习5中等待中断的wfi指令去除再加上独立的页表就可以了。

几个核心问题

  1. 目前 ArceOS 已经实现了“线程”组件 axtask,如何利用它转变为进程?
    • 独立的地址空间
  2. 如何保持 unikernel 的特性?
    • 仅保留单一特权级

ArceOS 作为动态链接库

目前成果

在保持 ArceOS 单特权级的情况下,支持一些简单的动态链接程序的二进制兼容:

  • 编写加载器 loader 支持加载动态链接的 ELF
  • 为每个应用创建虚拟地址空间(页表),将其绑定在任务控制块上
  • 每个进程通过任务控制块参与调度,在 switch_to() 时切换地址空间

需要实现的功能

  1. 动态链接的原理:
    • pltgot 如何配合工作:
      1. .rela.plt:存储了加载器在动态链接时需要填写的 .got 表项,以及动态链接函数的符号信息,用于索引 .dynsym中的一项:
        1
        2
        #define ELF64_R_SYM(i) ((i) >> 32)
        ELF64_R_SYM(Elf64_Rela.r_info)
      2. .dynsym:可以查找到和动态链接符号的相关属性,以及符号名称(位于 .dynstr
    • ABI 是如何保持的:函数签名保持一致即可
  2. ArceOS 需要能加载 ELF
  3. 加载时如何填写 .got
  4. ArceOS 的编译脚本需要修改,
    • 编译 axlibc,导出符号表
    • 自身能够读取 libc 的符号表
    • 最好能直接将 libc 映射到一个地址段,所有进程共享

实现路径

  1. mmap():难度较大,因为目前没有进程,也就没有其对应的地址空间
  2. ELF 解析
  3. 动态链接

日志记录

11 月 23 日

  1. ArceOS 的 axstd 和 axlibc 不能同时链接

    • 如果强制链接的话会出现重复的符号

解决方法:将 rust_libc 和 app 链接在一起,c_libc 再和 rust 代码链接

  • rust loader 可以调用 libc 的函数
  • ArceOS 需要实现 mmap()

11 月 27 日

KISS 原则实现了加载时的动态链接:先不实现 mmap 和 ELF 解析,采用硬编码的方式加载 hello

ELF 解析加载过程:

  1. 读取 Program Headers(在 host 上用 readelf),将其中类型为 LOAD 的 segment 从文件中加载到内存。

11 月 28 日

实现了根据 ELF 头获取需要进行动态链接函数的填充地址和填充值,并进行动态链接。

ELF 解析加载过程:

  1. 获取 ELF 头,找到 section header table
  2. 根据 .shstrtab 找到:
    • .rela.plt:找到外部链接符号
    • .rela.dyn:链接内部符号
    • .dynsym:动态链接符号表
    • .dynstr:动态链接符号名的字符串表
  3. 外部动态链接
    • rela.r_sym() 索引 .dynsym,获取符号项 dynsym
    • 根据 dynsym 判断符号属性,符合动态链接的用 dynsym.st_name 索引 dynstr 获取符号名 func_name
    • 根据符号名查找 loader 初始化时填写的链接表,获取链接虚地址 link_vaddr
    • 将虚地址 rela.r_offset 处的 8 字节填写为 link_vaddr
  4. 内部动态链接
    • rela.r_addend 的值即为链接虚地址 link_vaddr
    • 将虚地址 rela.r_offset 处的 8 字节填写为 link_vaddr

11 月 29 日

根据 ELF 头读取 segment 信息,将其中属性为 LOAD 的段加载到内存。此时对 kernel 而言,hello app 不具有单独的进程(或者线程)属性,而是与 loader 为一体。

在 loader 的 Cargo.toml 开启 axstd 的 multitask 属性,可以正常编译,但内核 panic 退出,信息为 current task is uninitialized,定位到 axtask::task::CurrentTask::get() 函数。显然,在初始化时,已经设置了 current task,这里为何报错?

经过进一步阅读源码,发现 current task 的指针通过 axhal::cpu::current_task_ptr() 获取,而 axtask/multitask 开启了 percpu 功能,也就是说此时的 current task 指针是一个 percpu 变量。目前,ArceOS 中通过 gp 实现 percpu,这与 riscv 的 ABI 是不符的。具体体现为 hello app 一开始就重新加载了 gp,把内核里维护 percpu 的值冲掉了。

因此,为了保持 ABI,我将 percpu 改回了使用 tp 维护(前一个修改的 commit 将 tp 改为 gp)。此时在打开 axstd/multitask 后可以正常退出。

11 月 30 日

  • 实现 axtask::spawn_from_ptr(),专门用于创建从 ELF 加载任务的 TCB
  • 在 riscv 的任务上下文中加入 satpcontext_switch() 时同时切换页表
  • 目前实现了动态分配的页表,但未实现进程退出后页的释放

12 月 1 日

通过复用 axhal::paging::PageTable 的接口,实现了完整的页表分配、回收功能,并且修改 TaskInner 结构体,将页表绑定到上面。这样就实现了类似于 rCore 中的 RAII 风格页表。

总结

目前的实现充分利用了 ArceOS 中已经存在的 axlibc 和页表,不足之处:

  • loader 对于 ArceOS 而言也是一个应用程序,但是其中跨层级调用了很多底层接口,不符合 ArceOS 的设计原则
  • 不支持静态链接的 app,即程序中的系统调用指令 目前 ArceOS 无法处理
  • 修改了一些 ArceOS 中的现有模块,使得原有模块的实现发生改变,如利用 gp 保存 percpu 变量,为了验证思路的可行性,将其改为了 tp

第三阶段总结报告

基本任务体验

第三阶段从十一月出开始到12月2号结束,历时1个月。就个人情况来说,由于杂事比较多,从第一周周六(11.11)开始学习Arceos到第三周周三(11.22)完成了ArceOS unikernel项目的基本任务,后面的一周半没能抽出时间探索一下最终任务还是有些遗憾(不过我希望在本次训练营结束后再花点时间实现一下最终任务的若干个Idea)。

就难度来说,个人认为ArceOS比rCore更为复杂,刚打开ArceOS工作目录就感受到了很强的压迫感,但是得益于ArceOS优秀的组件化设计思路(点赞)将内核开发的难度局限再一个模块之内大大降低了实验过程的复杂度。

第一周的early内存分配器和dtb解析比较麻烦,首先实现early内存分配器时没有考虑到需要对分配指针的地址和分配的内存块大小对齐,这个造成了许多bug,对于dtb解析需要额外学习dtb的数据格式和解析库。

第二周的主要工作量在于设计img的头部用于加载多个应用,然后在练习5遇到了一些问题卡了一段时间,主要是内联汇编的某些写法有问题导致编译器直接没有生成这部分代码(一步一步调试找到了相对靠谱的写法)。

总得来说,由于ArceOS的模块化设计,对其其中一个修改造成的影响也尽量地限制在模块内,对于其他的模块可以不用了解其实现细节而使用其暴露的api接口(通过在Cargo.toml中指定依赖路径),这样简化了开发难度。

最后感谢全体rCore开发者的努力,创造了我认为最棒的OS入门教程╰(°▽°)╯。