概念
- 虚拟时钟中断支持;虚拟机外设的支持。
- 虚拟机中运行宏内核和通过透传pflash加载应用。
实验
H_3_0
和H_4_0
还没有上传做不了基础实验.
课后作业
可以尝试跑
rcore-ch1
群里有大佬已经实现了.
介绍地址空间和页表相关的内容.任务调度下次课说.
调用是这样实现的:
首先是Makefile
里的:
1 | run: build justrun |
它需要有build
和justrun
这两个虚拟文件.
再去看build
:
1 | build: $(OUT_DIR) $(OUT_BIN) |
需要的是OUT_DIR
和OUT_BIN
这两个实体文件.
创建它们两个的文件在scripts/make/build.mk
:
1 | $(OUT_DIR): |
这里调用的run_cmd
在scripts/make/utils.mk
:
1 | GREEN_C := \033[92;1m |
这里$(1)
和$(2)
表示接受的是两个参数.
这个是两个操作,
$@
是代表这个虚拟文件本身其实这一套操作下来就是创建这个OUT_DIR
这个名字的文件夹.
在Makefile
中:
1 | A ?= tour/u_1_0 |
这时候把目光转回OUT_BIN
.它在scripts/make/build.mk
中:
1 | $(OUT_BIN): _cargo_build $(OUT_ELF) |
那么它的构建需要虚拟文件_cargo_build
和实体文件OUT_ELF
.
那么_cargo_build
的功能也是先进行输出,随后调用cargo_build
:
1 | _cargo_build: |
那么cargo_build
在scripts/make/cargo.mk
里:
1 | define cargo_build |
由于我们知道run_cmd
是什么套路了,因此这边就是执行cargo
来构建一个elf
文件.
回到OUT_BIN
这边,得到两个所需文件之后,通过OBJCOPY ?= rust-objcopy --binary-architecture=$(ARCH)
(在Makefile
)中定义,把elf
多余的信息头去掉,只留下可执行二进制文件:
1 | $(OUT_BIN): _cargo_build $(OUT_ELF) |
最后回到justrun
,它调用了run_qemu
(在scripts/make/qemu.mk
),
1 | define run_qemu |
这里调用了QEMU
,是依赖于ARCH ?= riscv64
(在Makefile
):
1 | QEMU := qemu-system-$(ARCH) |
这里调用了qemu_args-y
,同样是依赖于ARCH
的这里不赘述:
1 | qemu_args-x86_64 := \ |
本节目标:
希望能从PFlash
把应用的数据加载进来,以为运行后边的程序做基础.
paging
时的情况1 | OpenSBI v0.9 |
pfld
在哪?
在scripts/make/utils.mk
中,在pflash
中写入了pfld
:
1 | define mk_pflash |
那么这个在哪里调用呢?答案是没有调用.
我们是直接pull
下来的,如果调用make pflash_img
就会重新生成它.
在scripts/make/qemu.mk
里:
1 | qemu_args-y := -m 128M -smp $(SMP) $(qemu_args-$(ARCH)) |
而在Makefile
里PFLASH
被指定为y
.这样就会在运行qemu
的时候加上pflash.img
这个文件.
paging
的情况代码:
1 | #![cfg_attr(feature = "axstd", no_std)] |
这里需要看下一节关于PFlash
的部分,因为这时候需要访问外设,在没有remap
的时候是1G
的恒等映射,外设没有映射到地址空间中,因此报错.
一开始只映射了物理空间的0x8000_0000
到0xC000_0000
.
这里访问的物理地址是0x2200_0000
,本来就不属于刚刚提到的物理空间,因此通过恒等映射平移也得不到恒等映射之后的虚拟地址.
产生的log:
1 | OpenSBI v0.9 |
分支名称:
tour_u_3_0_no_paging
ArceOS Unikernel包括两阶段地址空间映射,
Boot阶段默认开启1G空间的恒等映射;
如果需要支持设备MMIO区间,通过指定一个feature - “paging”来实现重映射。
上一节说了启动之后需要remap
,这样才可以实现重映射.
那么就需要打开paging
.
其实是创建了两个映射,相当于拿前一个恒等映射做了跳板,因为要求开启MMU之后仍然可以以原来的物理地址正常访问.
#TODO
#TODO
是一个模拟闪存磁盘.QEMU
启动的时候会自动从内存中加载内容到固定的MMIO
区域.
读操作是不需要驱动的,但是写是需要驱动的.
目前我们只需要读,只要加载成功即可.
外设被映射到一个物理地址空间里边.
注意:linker_riscv64-qemu-virt.lds,段布局都是听的它的.
类似于rCore
的三级页表,我们实验中用的也是SV39
.
我们希望sbi
和kernel
都保存在高地址空间.
pc
栈寄存器sp
加偏移,这里的偏移是0xffff_ffc0_0000_0000
.
sbi
还是放在0x8000_0000
,kernel
还是放在0x8020_0000
.
那么在这个情况下其实已经是物理地址了,就是一个线性偏移的操作实现虚拟地址和物理地址的映射.
如果不需要访问物理设备,现在就可以完成了.
重映射的时候干脆在虚拟地址空间里把sbi
去掉,因为不应该继续访问sbi
了.
不同的数据段的权限不一样,比如READ
,WRITE
,EXECUTE
不一样.比如代码段就只能读和运行不能写.在重建的时候就不需要给它这些权限.
这样设备的地址空间也可以被映射进来,权限粒度也更细.
这里似乎仍然是线性映射.
需求:启用多任务,开一个子任务,让子任务替主任务完成一些具体工作,然后回到主任务.
并发是多个任务同时在等待使用CPU而不是同时运行.并行是真的需要同时运行.
调度的一个很好的描述:一个是保存现场,一个是任务无感知.
make run A=tour/u_4_0
任务代码解析:
1 | #![cfg_attr(feature = "axstd", no_std)] |
和上一节的内容一样,同样是访问了我们预导入的pflash.img
的前几个字符pfld
.
只不过用了spawn
的方法生成,并且用join
的方法等待.
有一个关键点在于
task_ext
,是任务的拓展属性,是面向宏内核和Hypervisor的关键.
分层,并且实现同样的接口,这样就可以自己决定是什么样的调度机制.
event
“(自己取的名)那么就会马上响应.本节目标:
执行make run A=tour/u_5_0
:
1 | OpenSBI v0.9 |
分析代码,worker1
是尝试获取Arc
中的这个双端队列,然后尝试在队列的最后放东西.
由于是协作式调度,worker1
每次放入的之后都会yield
,因此worker2
就会接手,然后尝试把队列里所有的内容都打出来,如果队列为空就报告worker2: nothing to do!
,然后再由worker1
接手CPU.
与rCore
不同,现在使用的是List
而不是一个数组,原理上就不设置任务的个数了.
自旋锁可能是我们脑子中的那个锁,每次访问资源需要访问这个锁,如果没办法访问那你这个任务要处理这种情况.
互斥锁则是自己加了一个等待队列,如果有任务在等待这个资源,那么这个任务被加入等待队列之后不会参与调度,这样就节省了很多任务切换时的资源.
#TODO
initialize global allocator at: [0xffffffc08026f000, 0xffffffc088000000)
5376*2
align是8的是有关于Vec的内存的分配
131,665,920
1048576
524288+8000
#TODO
很简单,定位到 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与操作系统相关的学习资料,令本人受益匪浅,下面是本次训练营的总结报告。
赶着入职前进入到了第四阶段,留给我的时间不多,都不到一周,但是陈老师没有放弃我,仍然耐心的给与了我如何开始学习Starry的指导,我非常感动。四阶段项目一群中的群友也给与了我很大的帮助,让我感受到了开源社区的魅力。
在四阶段学习中,我收获良多,由于不是计算机专业的学生,有许许多多的第一次:
闲聊了这么多,还是因为我入门尚浅,翻来覆去看代码也只知道还要学习的有很多,难以下手,留给我的时间又只有一周。还好陈老师给我指了一条明路——完善文档,接下来我就介绍一下这一周我的工作以及将来的计划。
我的工作主要是分析starry-next,并阅读 starry-next tutorial book,从各个层面改进starry-next tutorial book,帮助自己和其他初学者更好地学习操作系统开发。
在开始完善文档之前,我阅读了郑友捷同学的综合论文训练——《组件化操作系统 ArceOS 的异构实践》,宏观的了解了 StarryOS 的设计理念和目标。在完善文档的过程中,郑友捷同学也给予了我很多建议和指导,在此表示感谢。
我的工作具体如下:
PR#23 修复了指导书不能切换主题的bug, 可以切换到深色模式,便于完善文档时的调试(一边黑一边亮容易被吸引注意力)。
PR#24 在阅读完郑同学的论文之后,修改了文档的欢迎页,说明了 StarryOS 和 ArceOS 之间的关系和差别。因为从第三阶段到第四阶段,作为初学者的我一开始是一头雾水,不知道 Starry 要实现一个什么样的目标,所以我也在欢迎页添加了设计目标的说明(仍需完善)。在我与郑同学的交流中感觉到 Starry 的目标可能是:
在 crate 层中希望能够开发一些独立的组件,一部分就像 ArceOS 一样,能够被任意类型内核复用, 一部分则是能够被其他的宏内核使用。
使 Starry 能够兼容 Linux 程序,即提供 POSIX API 接口。
完成作为一个宏内核该有的功能,包括进程管理、信号处理等。
作为一个第一次接触 Starry 的开发者,我觉得可能还需要一个更宏大的目标,或者更明确的商用的可能性来吸引更多的人加入我们的开源社区,并做出贡献。
pr#25 在进入到第四阶段的时候,配置完环境后,只是按照 README.md 中给的指令运行了一遍,但是依然没有理解具体 Starry 究竟做了些什么,他的目标是什么。而且我在创建镜像的过程中遇到了一些问题——loop设备满了导致无法加载,我也是通过读了一遍Makefile的流程才定位到这个错误。因而我写了一个一个案例快速上手 Starry,介绍了这些指令内部的一些细节,帮助初学者快速理解。PS: 今天我再读的时候发现了其中的一些错误(镜像文件应该是给QEMU加载的,不是ArceOS的文件系统),而且没有介绍案例 nimbos 的主要作用,可能对于学习过操作系统的同学来说,不用说也知道是一个测试操作系统功能的测试集,从一个门外汉的角度来看,他就是测试了几个testcase而已。我会在后续的工作中对文档进行修正。
pr#26 可能对于大佬来说,各种 git 指令都已经理解透彻,烂熟于心,但对于初学者来说,只会用一个
1 | git add . |
在这之前我都没有创建过分支来提交PR,导致了一些混乱,因此在附录中添加了一个创建分支提交PR的标准流程。此外对于理解StarryOS究竟在做什么,理解他的工作流很有必要,因此我也在附录中添加了对于工作流相关的说明。
由于时间不多,我的水平有限,相比其他同学对社区的贡献,我的工作可能微不足道。如果社区的大佬们不嫌弃,入职工作之后我也愿意继续帮助完成文档(主要因为这个项目里面用到的Rust语法很全面,我想要学习rust, 还包含了很多汇编和c,将来也能对用rust驱动硬件做为一个参考)。
可能有一部分同学和我一样,在阶段一到阶段三主要注重于做题(阅读测例->知道预期的结果是什么->查看相关的接口和需要用到的函数->实现功能),完成任务即可。没有特别理解整体的设计和原理,因而到了阶段四之后没有了题目之后感到迷茫。
因而为了帮助第三阶段的同学能够丝滑的进入到第四阶段,我觉得当务之急是需要完善ArceOS的文档(我第三阶段完全就是参考PPT完成的,对于很多细节没有掌握)我接下来的工作就是理解ArceOS的细节,说明Starry如何使用了ArceOS的接口和模块。然后首先实现文档整体框架的从无到有,最后在掌握了细节和整体设计思路之后再完善指导书并修正其中的错误。也希望有大佬能够一起加入到完善文档队伍中,一起交流。
解析`WebGPU`、`Virtio1.2`规范,以及`WGPU`、`Vortex`源码,探索异步操作系统与内核态GPU资源管理的结合方案。