内容

虚拟机运行的实验内核是第一周的u_3_0:从pflash设备读出数据,验证开头部分。
有两种处理方式:
- 模拟模式 - 为虚拟机模拟一个pflash,以file1为后备文件。当Guest读该设备时,提供file1文件的内容。
- 透传模式 - 直接把宿主物理机(即qemu)的pflash透传给虚拟机。
优劣势:模拟模式可为不同虚拟机提供不同的pflash内容,但效率低;透传模式效率高,但是捆绑了设备。
这部分实验是上部分的作业暂时略过.
实验命令:
1 | make payload |
这里看payload/hello_c/Makefile,可以看到:
1 | TARGET := hello |
可以看到我们使用的编译器和信息移除工具都是指定的版本是linux.
这张图有些害人匪浅了,

这个图是linux应用的用户栈.
但是我们从实用的角度来看,应用主函数的原型:
1 | int main(int argc, char *argv[], char *enp[]); |
我们只需要在栈里边按顺序保存:
即可,只要argc的值是对的,arg_ptr和env_ptr指向的实例是对的即可.
这里有一个疑问:到底谁是对的?
在kernel-elf-parser里的src/user_stack.rs的注释和它具体的实现是一样的:
1 | //! Initialize the user stack for the application |
形成的栈:

运行log:
1 | OpenSBI v0.9 |
可以看到运行过程中还调用了:SYS_IOCTL和SYS_SET_TID_ADDRESS两个系统调用.
这是因为:”示例m_3_0基于musl工具链以静态方式编译,工具链为应用附加的部分也会调用syscall。”

就是添加的这个_start和_exit的系统调用.
set_tid_address会设置clear_child_tid的值,在进程创建和释放的时候会用到.set_tid_address在父线程创建一个子线程的时候会把自己的tid写到这个address的区域里.clear_child_tid在释放自己线程或者锁和其它资源的时候,会把返回的值里写入到address里.
ioctl是用来设置对外输出终端属性的.
现在用的是sbi的putchar,因此可以直接跳过.
对于不同的体系结构,系统调用号不同。示例是基于riscv64的系统调用号规范。
最后总结就是我们设置好合理的syscall,把系统调用号设置好,那么就可以实现一定程度上的兼容.

像这个APP只需要提供syscall的兼容层就行了.
其余的兼容层根据APP不同也需要实现.
arceOS是通过
axfs_ramfs对procfs和sysfs提供兼容.通过axfs_devfs提供devfs的兼容.
目前用ramfs进行兼容是一个临时的方法.
也就是使用内存文件系统.访问的时候相当于访问了一个基于内存的节点,里边有一些基于内存的数据,这些数据是其它子系统填充过来的数据.
正常的Linux是你访问这个proc之类的文件的时候实际上是调用了一个回调函数去获取系统状态.
实现方法:
sys_read方法读取到文件里的内容. user space.flag.frame,并且把虚拟地址映射到frame.1 | #[allow(unused_variables)] |
这里
flags的处理还是很不到位,需要后续增加.
应用场景多样化->多种内核场景的出现
Unikernel->安全性换高效性->为一个APP适配一个内核
宏内核就是典型的Linux之类的操作系统
微内核主要是安全->用形式化证明安全性->反复切换用户态以至于很慢
虚拟机管理程序->hypervisor->多个内核每个内核认为自己独享了整个设备
关注点在于组件化场景下的异构内核的快速实现.理解概念和优势.
不同的需求对应了不同的内核->使用不同的组件实现不同的内核
使用宏内核+hypervisor的架构也可以实现这个功能,但是会产生性能瓶颈.
利用对unikernel的几个部件的连接方式的修改,加一个宏内核插件,这样就可以变成宏内核.
通过对unikernel对于hypervisor插件的调用,就可以变成hypervisor的系统.
其实上边论述的是优势所在.
BACKBONE层的重要性:把共性放在下层内容.
TASK的拓展:把任务看成是内核资源的集合.
未来工作:扩展泛型化——同时引入不同类型扩展 -> 甚至能到异构内核
很简单,定位到 axstd 里修改 println 的输出,前后加上转义字符就好了
找到 axstd 里,把 collection 替换成自己的 collection 包就可以,在自己的 collection 包实现 HashMap 结构体,只需要简单实现接口,覆盖测试用例即可。另外自己在替换 axstd 的 collection 的时候,遇到的问题是模块的导出关系没弄清导致的刚开始测试用例找不到我的代码.
做的过程中,先按照自己的想法做了一下,发现编译不过哈哈,然后看群里消息,学到了 const generic 这个知识点。然后顺便看到了 blogOS 对于 bump allocator 的介绍,就拿过来参考了一下,这个 bump allocator 确实简单的不可思议。 最后写完之后还是不过,打了日志才发现是自己初始化漏了 b_pos 和 p_pos 的初始化.
实验内容还是很简单的,但是虚拟化这块之前没有了解过,但是比较感兴趣。中间一周把宏内核实验翘掉读了虚拟化原理那本书。在听老师讲解过程中感觉,对一个不熟悉的东西,尽量简化,然后做出一个最小 work 的样板,然后不断迭代添加复杂功能,是非常好的学习方式。
stage 4 算是很扎实的一个阶段。这个阶段从头开始学习了我原来不怎么熟悉的异步运行时。整体上来说算是比较系统的学习了如何构建一个异步运行时,同时也阅读了不少tokio这种生产环境级别的异步运行时代码,受益良多。
本阶段还学习了一些关于 uring io 的知识,同时也顺便比较系统的梳理了 linux io 这块的知识点。通过每周一次的交流活动中能比较有效的调整自己的学习方向,同时也能补充很多有用的信息。很多同学水平很高,在交流过程中深感差距,还需要不断学习。
阅读以下两个链接中文档,比较系统的了解了 rust 的异步原理
####下周安排(第二周):
由于本项目最终要实现一个基于 uring io 的异步运行时,于是决定从 io 的角度切入 tokio 的源码阅读。在阅读过程中发现 tokio 的文件 io 部分都是转发给某个独立线程,是基于阻塞式的操作。为了对比文件 io ,还阅读了部分 tcp 相关的源码,证实了的确网络 io 是使用了基于 epoll 的 mio 来做管理。而且本周对 uring io 有一个粗略地了解,目前看来 uring io 在文件 io 方面可能优势会更明显。 网络 io 这块相较于 epoll 优势没那么大,那么接下来第三周可能要优先实现基于 uring io 的文件 io 异步运行时的相关工作。
此外本周还阅读了以下资料
编写一个简易的基于 uring io 的文件 io 的异步运行时。
本周首先调研了一下在 smol 中实现基于 uring io 的可能性。首先 smol 社区中已经有一个 PR
async-io PR:Integrate io_uring in the Reactor
● 简要介绍了实现思路,
● 作者说要等等 polling Expose raw handles for the Poller 这个 issuing合并.
大概粗略浏览了一下,但是由于对 uring io 不太熟悉用法,还没有看懂,暂时搁置。
然后继续阅读了 uring io 的相关资料,包括
最后是实现我自己的uring io异步运行时
我的async-fs-uring,这里实现了一个建议的基于 reactor 模式异步运行时,并实现了基于uring io的文件读。主要思想就是把 io 委托给 reactor 去提交,然后 reactor 不断轮询,如果有 io 完成了,就返回给对应的异步任务。实现过程中比较困难的点就是buf 管理,需要保证 buf 在异步读过程中一直有效。我这里做法是直接把 buf 的所有权移交给 UringReadFuture.这只是一个权宜之计,因为我这里实现的比较简单,在异步读进行过程中 UringReadFuture不会被 drop 掉。实际上后来也阅读了 tokio-uring 的相关设计文档,也了解到了一些更合理的设计方案,但是还没有时间来实现。
未来计划:通过实现一个建议的基于 uring io 的异步运行时让我对 uring io 有了基本的了解。后续可能会进一步了解生产环境级的基于 uring io 的异步运行时的实现以及与传统阻塞式和epoll结合的异步运行时的实现差异
作为一名Cpper,我之前对Rust的了解可以说是毫无了解,这次受到朋友的邀请,第一次接触Rust,刚开始以为就是像C一样的过程语言。然而,当我真正开始深入学习和使用Rust时,我发现它远比我预想的要有趣和富有挑战性。特别是在进行Rustlings练习的过程中,我收获颇丰,也深刻体会到了Rust语言的强大和魅力。
Rustlings是一个设计精良的Rust语言练习项目,通过一系列由简到难的练习题,帮助开发者逐步掌握Rust的语法和特性。在练习的过程中,我遇到了许多看似简单但实则深奥的问题,这些问题让我不断思考和探索,也让我对Rust有了更深入的理解。
通过Rustlings的练习,我只能感叹面向编译器编程的魅力。
此外,Rustlings的练习还让我认识到了自己在编程思维方面的不足总的来说,Rustlings练习是我学习Rust过程中的一个重要环节。它不仅让我掌握了Rust的基本语法和特性,还锻炼了我的编程思维和解决问题的能力。我相信,在未来的学习和工作中,我会继续利用Rustlings等资源来深化对Rust的理解和应用,并不断提升自己的编程水平。同时,我也期待在未来的项目中能够充分发挥Rust的优势,写出更加健壮、高效的程序。
在2024秋冬季开源操作系统训练营的第三阶段中,我深入研究并实现了一个高效的内存分配器,该分配器结合了Slab分配器和TLSF(Two-Level Segregated Fit)分配器的优势。本报告将详细分析每个链接中的源码,并探讨我实现的新调度器的优势。
Slab分配器的核心思想是将内存分割成固定大小的块,并将这些块组织成一个或多个列表,以便于快速分配和释放。每个Slab结构体维护了一个特定大小的块列表。
1 | pub struct Slab<const BLK_SIZE: usize> { |
Slab结构体包含一个free_block_list,这是一个双向链表,用于存储可用的块,以及total_blocks,记录总块数。Slab的实现提供了new、grow、allocate和deallocate等方法,用于初始化、扩展、分配和释放内存块。
Slab只管理固定大小的块,因此分配和释放操作可以非常快速。Slab分配器倾向于将最近释放的块重新分配给请求,这有助于提高缓存局部性。Slab分配器可以减少外部碎片。TLSF分配器实现了一种动态内存分配算法,它通过维护多个空闲块列表来优化内存分配。TLSF分配器的关键特点是其二级索引结构,它允许快速找到合适的空闲块。
1 | pub struct TlsfByteAllocator { |
TlsfByteAllocator结构体封装了rlsf::Tlsf,并提供了init、add_memory、alloc和dealloc等方法,用于初始化、添加内存、分配和释放内存。
SLLEN为2)的索引结构,可以更有效地利用内存,减少内部碎片。在我的新调度器设计中,我结合了Slab和TLSF的优点,将较小的块分配任务交给Slab分配器,而将较大的块分配任务交给TLSF分配器。这种设计允许我复用已经分配的指定大小的块,从而提高内存分配的效率。
Slab或TLSF,这使得它可以适应不同的内存分配需求。TLSF,我可以利用其高内存利用率的优势,同时Slab分配器可以快速处理小规模的分配请求。通过参与2024秋冬季开源操作系统训练营,我对内存管理有了更深入的理解。实现Slab和TLSF分配器的过程让我体会到了操作系统中内存管理的复杂性和重要性。我学到了如何设计和实现高效的内存分配策略,这对于我未来的职业生涯和学术研究都是非常宝贵的经验。此外,我也认识到了团队合作的重要性,因为在实现过程中,我与队友们进行了深入的讨论和协作,共同解决了遇到的技术难题。总的来说,这次训练营不仅提升了我的技术能力,也锻炼了我的团队合作能力。
通过实现基于Slab和TLSF的内存分配器,我成功地提高了内存分配的效率和内存利用率。新调度器的设计不仅灵活,而且能够根据请求的大小选择合适的分配器,这在操作系统的内存管理中是非常重要的。我的实现展示了如何在保证性能的同时,优化内存使用,这对于任何需要高效内存管理的系统都是至关重要的。
早就听说过大名鼎鼎的 rustlings, 这次借着 oscamp 的机会来做一做。做的过程中发现 oscamp 的 rustlings 似乎比官方的 rustlings 加了很多私货,难度大了不少,
但是也的确把编写 很多有用的 rust 特性 和 os 过程中会用得到的一些 rust 的 feature 都涵盖了。在做的过程中,也学习到了很多以前自己没有注意或者使用过的 rust 特性。
更重要的是,通过一些题目的学习,能摆脱原来写 c++ 的思维,写出一些更 rust 风格的代码,比如返回值多用 Option 和 Result,更多的使用 match 和 if let, 而不是用原来 c++/c 风格的错误判断,
以及更多的用迭代器等方法。
一阶段学完之后,尤其是经历了大量和编译器的斗智斗勇之后,我对 rust 的理解加深很多,也对 rust 带来的相比 c++ 的很多优势有了更深的体会。相信 rust 和 c++ 的学习会互相促进。
本科期间零零散散一致有学过 rust 和 rcore 的一些东西,由于各种各样的事情没能坚持下来。
读硕士之后刚好又系统学习了一下 rust ,正巧有同学来约我参加 oscamp , 于是就来参加。
如今再做 rcore 感觉要比本科期间轻松了很多。在游刃有余之后,能从 rcore 中汲取的知识也就更多了。
文件系统的组织方式、并发的实现和管理等,这些本科期间令我头晕转向的知识点,现在都能比较轻松地理解。也能从中窥探出一个真实生产环境下的操作系统是如何设计的。
本次实验中,前四个实验都比较轻松,思路清晰地拿下了。最让我头晕转向的是第五个实验,也就是死锁检测的实现。
刚开始被银行家算法迷惑,感觉很难实现。后来又做了尝试做了环路检测来检测死锁。但是做的过程中发现环路检测可能比较难处理信号量。于是又回过头来思考银行家算法。
结合前面尝试做环路检测的过程,觉得只要实现一套类似银行家算法的检测机制就好了。于是把代码全部推翻,从头来过很快就拿下了。
这一次 rcore 学习体验不错,希望接下来的三阶段能学到一些有意思、更贴近实际场景的东西。
首先感谢各位老师以及同学提供的如此丰富的Rust与操作系统相关的学习资料,令本人受益匪浅,下面是本次训练营的总结报告。
先说说为什么会参加这个训练营。
起因是在配置arch linux的过程中用到了很多rust写的工具,而且这些工具用起来非常顺手,引起了我对rust的兴趣。在看完《Rust 程序设计语言 中文版》后,我开始寻找一个rust相关的项目练手。正好在知乎上看到有人推荐rcore,加上之前写过xv6(s081),对操作系统有不小的兴趣,就这样开启了这段rocre之旅。
这一阶段主要是熟悉rust语言。
由于我有java、python的语言基础,这一阶段还是比较轻松的。
难点是在写链表时因为有所有权限制,要对node作多层包装。刚开始写时并不顺利,其他语言的基础成了障碍,总是会按python的写法往下写,导致所有权出问题。直到真正理解所有权的含义才写出正确的代码。
所有权是我第一次真切感受到rust和其他语言的不同,它把一些原来从未考虑的可能出现的数据竞争提前暴露出来,迫使编写者去思考数据关系,这种体验是从未有过的。
受益于xv6,第二阶段的实验基本没有卡点。这些实验让我深入理解了riscv的trap、页表等机制,重新梳理了操作系统的进程切换、文件系统等相关知识。
这一阶段的实验比第二阶段的实验要难一些,花费的时间也更多。
对于hashmap这个实验,我自己实现了一个固定buckets的hashmap,但看到blog中大家基本是引网上的包,感觉自己有些笨笨的,还是要多和群友交流。
后面在mmap这个实验卡了很久,一直在想怎么实现文件的lazy load和对应,最后是面向测例编程,直接文件内容读到内存中通过测例完事。