0%

组件化内核

组件化内核具有灵活,增量构造等优点。
组件化内核最启发我的是组件化可以用来快速构造应对不同场景的各种模式内核。
不同的功能组件,不同的内核特权级,不同的隔离要求…
组件化内核完全可以快速满足这些要求。

Unikernel

特点

单一特权级,简单高效。

实验

实验一

实验要点在于了解在不同层级下修改对应输出语句产生的变化,了解输出都来自于哪些层级。

实验二

该实验完成对std::hashmap的支持,通过查阅rust的std源码发现rust基于hashbrown。因此引入hashbrown作为hashmap。

实验三

本次实验完成Bump分配算法,此算法较为简单。重点在于实现对应trait。

练习四

该实验重点在于了解文件系统对应函数,调用对应函数即可。

Monolithic

实验

m_1

注册handle_page_fualt(), 通过aspace中的handle_page_fualt()来处理异常。

mmap

该实验是通过仿照linux来完成mmap:
1,阅读linux中的mmap,确定各参数意义:
2,分配内存buf,注意点为分配的空间需要4K对齐;
3,读取文件内容至buf

Hypervisor

特点

侧重于虚拟而非模拟,同时保持高效和安全

实验

h_1_0

按实验要求设置对应寄存器值,同时增加spec使guest运行下一条指令。
但不知为何,程序运行会经常卡死,代码未改动情况下运行结果不确定。

h_2_0

将pflash内容写入disk作为文件,与加载guest文件内容类似处理即可。

lab1

用户程序特征

该lab中的用户程序基本特征为申请n次倍增的内存,之后释放偶数次申请的内存,以此循环。

内存分配

1,由于物理内存大小为128m,初始即分配全部内存给内存分配器。
2,由于奇偶关系,将内存切分为两个区域。
3,由于倍增关系,将两个区域大小配置为2:1。

rCore2024第三阶段总结

非常遗憾也非常懊悔,我这个大三狗由于近两周实在是俗务缠身且不可脱身,因此并未太多参与到rCore的学习中,也仅仅是在第三阶段结束前几天勉强抽空看了一下前几个(前3个)ppt。本人作为一名大三学生,学习这几个 PPT 中的组件化内核设计与实践,感受颇深且困难重重。从基础概念到复杂的技术细节,如内存管理的分页机制、多任务调度算法,再到设备管理框架等,知识量剧增且难度攀升。但我相信如果能跟下去一定是收获满满,从中不仅能拓展操作系统内核知识视野,更能极大提升代码能力,后面也是报名了操作系统内核设计的华东区预赛,希望能抽出更多时间补上第三第四阶段的内容,并在比赛中取得好成绩吧。

课后练习:bump内存分配算法

主要代码在alt_axalloc里,与axalloc里不同,它在初始化时内部只有一个分配器,并把所有物理内存都分配给它。这个分配器同时实现字节分配器和页分配器的trait,但分配内存时只使用字节分配器里的alloc,页分配器里的alloc_pages没用。

1
2
3
// modules\alt_axalloc\src\lib.rs
#[cfg_attr(all(target_os = "none", not(test)), global_allocator)]
static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new();

要想正常实现动态内存分配,首先要用#[global_allocator]属实告诉编译器使用哪个分配器实例作为全局分配器。

1
2
3
4
// modules\alt_axalloc\src\lib.rs
unsafe impl GlobalAlloc for GlobalAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {...}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {...}

然后需要为自定义的分配器实现GlobalAlloc trait里的alloc和dealloc方法。查找调用关系:GlonalAlloc::alloc -> GlobalAllocator::alloc -> EarlyAllocator:alloc -> ByteAllocator::alloc,dealloc同理。因此主要修改的代码为modules/bump_allocator里的EarlyAllocator里的ByteAllocator trait的alloca和dealloc方法。

bump的实现参考Writing an OS in Rust : Allocator Designs 分配器设计与实现-CSDN博客

lab1:针对应用场景,优化内存分配器组件

分配器内存的初始化和申请

总共可用于分配的堆内存大小是多少?
根据axalloc::global_init(start_vaddr, size) -> GLOBAL_ALLOCATOR.init(start_vaddr, size) 可得size参数的大小即为分配的堆内存大小。查找global_init的使用得axruntime::init_allocator

1
2
3
4
5
6
7
8
9
10
11
12
13
// axruntime::init_allocator
...
for r in memory_regions() {
if r.flags.contains(MemRegionFlags::FREE) && r.paddr == max_region_paddr {
axalloc::global_init(phys_to_virt(r.paddr).as_usize(), r.size);
break;
}
}
for r in memory_regions() {
if r.flags.contains(MemRegionFlags::FREE) && r.paddr != max_region_paddr { axalloc::global_add_memory(phys_to_virt(r.paddr).as_usize(), r.size)
.expect("add heap memory region failed");
}
}

由上述代码可得标记为free的内存区域都分配给内存分配器使用。

memory_regions -> platform_regions,当前平台为riscv64

1
2
3
4
5
//riscv64_qemu_virt_/mem.rs
/// Returns platform-specific memory regions.
pub(crate) fn platform_regions() -> impl Iterator<Item = MemRegion> {
crate::mem::default_free_regions().chain(crate::mem::default_mmio_regions())
}

default_free_regions里标记为free,块起始地址为_ekernel,结束地址为PHYS_MEMORY_END。

1
pub const PHYS_MEMORY_END: usize = PHYS_MEMORY_BASE + PHYS_MEMORY_SIZE;

定义在config.rs里,查找可得物理内存大小为128m,根据128m查找可得由来为qemu启动时设置的物理内存参数128m。运行时添加LOG=debug参加也可以在输出信息里直观看到分配给分配器的内存,initialize global allocator at: [0xf
fffffc080270000, 0xffffffc088000000), size:131661824。

1
2
3
4
5
6
7
8
9
// GlobalAllocator::alloc
let old_size = balloc.total_bytes();
let expand_size = old_size
.max(layout.size())
.next_power_of_two()
.max(PAGE_SIZE);
let heap_ptr = match self.alloc_pages(expand_size / PAGE_SIZE, PAGE_SIZE) {
Ok(ptr) => ptr,
Err(e) => panic!("Bumb: {:?}.", e),}

初始分给字节分配器32KB,当alloc内存不够时,由页分配器再分配页倍数大小的内存。注意分配时要求的内存大小和字节分配器的total_bytes函数返回的值有关,若total_bytes实现不当返回值过大,则一次要求的内存会远远超过实际需要的内存,造成字节分配器分配内存失败,提前终止迭代(我实现的total_bytes返回的是可分配的内存大小,这样每次申请的内存就和需要的内存接近了)。

应用程序内存的申请和释放

通过在alloc和dealloc函数里log,分析堆上内存的分配和释放时机,主要和三个变量有关:pool、items和a。a类型是Vec<u8>,可以简单看作是一个内部地址连续的内存块。items和pool类型是Vec<Vec<u8>>,可以看成是存储Vec胖指针的集合。

在主循环里的每一轮先调用alloc_pass申请大量内存,连续申请$a_n\quad (n = 0, 1, 2, \ldots, 14)+\delta$大小的内存块,其中$\delta$表示轮数,$a_n$是首项为32,最大项为524288的以2为公比等比数列,用等比数列的求和公式可得一轮申请的内存块之和大约为1MB。每申请一个内存块便push进items里,items扩容是翻倍,初始申请$4\times32$B大小的内存,扩容的时候先申请$8\times32$B大小的内存,再释放之前申请的。因为一轮循环里总共申请15块内存,items最大容量为$16\times32=384$B。

alloc_pass结束后通过free_pass函数连续释放掉偶数项的内存块。然后将items append进pool里,每一轮循环结束pool就增加$7\times24=168$B大小,pool每次扩容时同样会释放掉之前占用的内存。pool的作用域是整个循环,pool本身及对应奇数项占用的内存在循环结束后才释放,随着循环次数的增加,占用内存越来越大,直到耗光128MB总内存。

我们可以粗略进行分析,如果忽视掉pool和items变量本身的大小(使用bumb算法,items变量最多占用$96+192+384=672$B,pool最多占用$168\times\sum_{n=1}^\delta k$ B)和每次循环递增的$\delta$,每一轮循环释放掉偶数项的总和约为$\frac{2}{3}$ MB,那么理论上最大循环次数约为$128\times3=384$。注意这是在保留奇数项内容正确性,不对奇数项所占内存进行覆写的情况下。

自定义内存分配器的设计

根据上面的分析可知,items能占用的最大空间是有限的,且在每轮循环结束后会全部释放,适合在固定大小的内存区域里使用bumb算法管理。偶数项所占用的内存空间随着$\delta$变化非常缓慢,且同样也会在每轮循环结束前全部释放,同样适合在固定固定大小的内存区域里使用bumb算法管理。同时,items和偶数项都会在全部释放完后再重新分配,所以items和偶数项可以在一块内存里用bumb算法管理。pool和奇数项占用空间是持续增加的,pool会释放,奇数项不会,但为了简单处理一样用bumb算法管理(pool占用内存在轮数较大时存在较大的优化空间)。

分配器初始可用内存为32KB,当分配不够时再向页分配器申请扩容。为了方便划分区域进行管理,在申请第0轮第0项大小的内存时,我们在alloc函数里返回nomemory错误,并在total_bytes函数返回items和偶数项之和的大小来申请足够的内存。在不覆写奇数项情况下,理论最大轮数大约为384,偶数项之和每一项增加15,所以items和偶数项之和为$672+699040+384\times15=704932$B大小,这样分配器初始就能申请向上取整2的n次方和4096的倍数1048576B大小的内存使用。

由初始地址开始,大小为704932B大小的内存区域用来进行items和偶数项的分配和释放。剩下的区域用来进行pool和奇数项的分配和释放,随着奇数项的增加再向页分配器申请新的扩容,直至最终内存耗尽,所有区域内部均使用bumb算法进行管理。注意,每一轮循环里items和偶数项都会在申请完毕后全部释放,奇数项在整个循环期间不释放,所以用bumb管理是合适的。但pool在循环内是会释放的,且随着轮数的增加,空闲内存大小不容忽视,未来可以用更高效的算法来管理pool的内存,还能进一步增加轮数。

课后练习: 实现最简单的Hypervisor,响应VM_EXIT常见情况

先从simple_hv/src/main.rs里的main函数开始运行,此时处于host域。用new_user_aspace创建地址空间,load_vm_image加载要在guest域里运行的内核文件。prepare_guest_context伪造guest上下文件,设置初始特权级为S,sepc值为VM_ENTRY(内核文件的入口地址)。

1
2
3
4
5
6
7
8
9
10
// Kick off vm and wait for it to exit.
while !run_guest(&mut ctx) {}

fn run_guest(ctx: &mut VmCpuRegisters) -> bool {
unsafe {
_run_guest(ctx);
}

vmexit_handler(ctx)
}

while循环里将会启动guest并等待它退出。_run_guest在guest.S里,主要功能是保存host上下文和恢复guest上下文,具体细节下次课会讲,最后sret跳转到sepc里的地址,切换到了guest里的内核执行。触发中断后会跳转到_guest_exit里执行,执行完后会进入vmexit_handler函数里执行(执行完_guest_exit后会接着执行_restore_csrs,里面恢复了ra寄存器的值,并在最后使用ret返回到调用_run_guest的下一行)。

1
2
3
4
5
6
7
8
9
10
//payload/skernel/src/main.rs
unsafe extern "C" fn _start() -> ! {
core::arch::asm!(
"csrr a1, mhartid",
"ld a0, 64(zero)",
"li a7, 8",
"ecall",
options(noreturn)
)
}

内核程序如上所示,当执行csrr a1, mhartid时,VS态写入了M态的寄存器,触发非法指令异常。ld 10, 64(zero),写入了非法内存地址,触发页错误异常。ecall指令超出VS态执行权限,触发异常。

先触发非法指令异常,在异常函数处理中,我们先需要将sepc+=4(一条指令长度为4字节),以便下次_run_guest里的sret能正确跳转到下条指令执行。然后返回false,以便while循环不退出,继续执行run_guest函数。

总结

这个阶段学得不是很扎实,很多内容都只看了视频,没有完成课后练习,等后面还要进一步回锅。

2024三阶段总结

由于考试和双十一的影响,导致我实际学习三阶段的时间不到一周,到目前为止,勉强把实验做完了(除了最后一个套娃arceos的实验还没有看)。考试自不必多说,至于双十一嘛,是由于外存不够了,要升级一下,然后迁移系统、配置环境什么的。

由于时间较少,而且刚考完一场试,变懒了,所以对ArceOS还没有仔细研究,下面的内容可能较空泛、可能有错误。

Unikernel

在我看来,Unikernel是一个与业务无关的裸机应用,分层、模块化且可扩展,根据业务的需要可以选取需要模块或对模块进行扩展,可以像正常开发linux c应用或std rust应用一样迅速开发裸机应用。

在这一阶段,被迫看了一些关于feature、attribute、条件编译的一些知识,头一次知道原来rust里面还有workspace。

  • print_with_color

就加一些特殊的字符

  • support_hashmap

看群内大佬讨论,就引了一个库

  • alt_alloc

对于这个,一开始还想最初的页是怎么分配来的,后来才想明白这些页已经固定下来了,只要考虑怎么管理就行了。

  • shell

对于mv操作,我一开始想的是仅移动指向inode的指针,不拷贝文件,但发现实现有些难,就没有这样做。最终rename和mv都用了rename实现,感觉有些奇怪,但起码测例能过,不愧是arceOS,就是通用。

宏内核

如果按照上面的理解的话,宏内核是Unikernel上的应用,但是又有些不太对,相比于实现业务目的,它更倾向于是扩展Unikernel,看起来有些像“内核之上不只有应用,还有内核的微内核”。

  • page_fault

相比于实现page_fault,我更关注的是linkme这种骚操作。

  • sys_map

就find_free_area然后read进去,虽然感觉用find_free_area找到的地方有些不符合man mmap的说明。

Hypervisor

在三阶段的实现中,看起来Hypervisor最没用,在裸机之上实现裸机,大部分实现都是透传的,相比于直接运行在裸机上,不仅功能减少了、性能变差了,就连能源的使用也不只是烧电,还烧头发,悲。

还有,感觉可不可以在ArceOS上同时运行宏内核或其他应用和Hypervisor,ASA(ArceOS Subsystem for ArceOS)1.0就在眼前

  • simple_hv

改一下guest的sepc,设置一下a0、a1的值

  • pflash设备模拟方式

一开始没有搞清gpa映射到hpa时,没有经过host的satp,导致在host中拿着pa当va来用,出现了问题。另外,在完成后,修改了一下pflash的内容,想要读u128转string输出,但是没想到,在对* u128解引用时,它居然会先读u128的高64位,导致映射时页面没对齐。

主要看了下hypervisor部分

tour/h_1_0部分的vmexit_handler只处理了Shutdown vm
exercise/simplehv处理了IllegalInstruction, LoadGuestPageFault,VirtualSupervisorEnvCall

IllegalInstruction处理部分

1
2
3
4
5
6
7
8
9
Trap::Exception(Exception::IllegalInstruction) => {
ax_println!("Bad instruction: {:#x} sepc: {:#x}",
stval::read(),
ctx.guest_regs.sepc
);
ctx.guest_regs.sepc += 4;
ctx.guest_regs.gprs.set_reg(A0, 0x6688);
ctx.guest_regs.gprs.set_reg(A1, 0x1234);
},

guest_regs.sepc代表返回guest模式后的pc值
guest_regs.gprs.set_reg设置guest模式的reg值设置guest模式的reg值
hyper模式可以接管guest模式的寄存器并在相应事件发生时设置回调函

由于 11 月事情较为繁忙(比赛和研一课程等),因此三阶段实际花的时间并不是很多,加之 ArceOS 项目的复杂性较高,导致这一阶段个人的学习效果不算太理想,对项目的代码框架还不算很熟悉。后续准备深入啃一啃各模块的源代码,并在四阶段深入学习 Hypervisor 虚拟化方向。以下是我在三阶段过程中的学习日志。

Read more »

ArceOS入门

依本人的拙见,三阶段其实是为了让大家在正式开始项目实习前,先弄明白我们接下来要开发或者要完善的这个OS到底是一个什么样的OS。换句话说,它到底是基于一个什么样的思想去构建的?它跟市面上已有的那些微内核或者宏内核有哪些区别?或者更近一点,它跟我们之前做的rcore又有哪些异同?这应该都是我们在ArceOS入门阶段需要了解的一些事情,弄明白了这些概念是为了给接下来的开发和项目实习打下一个良好的基础。

组件化内核的概念

研究和实践基于组件构造内核的方法,尝试构造应对不同场景各种模式内核。

前两天才考完操作系统,在书本上,对操作系统的(其中一种)定义是这样的:

操作系统是一组能有效地组织和管理计算机硬件和软件资源,合理地对各类作业进行调度,以及方便用户使用的程序的集合

感觉有一种异曲同工之妙。即,我们也许不用将操作系统看作一个完整的整体,而是一块块组件,我们通过“胶水”进行粘合,将这些组件组合成一个程序的集合,来实现诸如计算机软硬件资源管理等功能。

在组件化内核领域:所有内核实例都可以基于组合组件的方式,从简单到复杂逐级迭代的进行构建。所有内核模式都可以看作以Unikernel模式为基础,朝向特定方向的组件化扩展。

那么什么是unikernel?

Unikernel

Unikernel模式下应用与内核处于同一特权级(均为内核态),共享地址空间。Unikernel既是内核又是应用,二者合为一体。

转换到实际场景中,我们可以理解成就是一个运行在开发板上的程序,只是这个程序直接操控了这个开发板的所有硬件资源,且它只做一个任务,比如U1.0,负责向屏幕上输出信息,随后终止,退出。

而从上面的例子中,我们又可以将一个简单的输出字符串的程序,分成几个部分。比如输出部分,架构相关的部分,给分隔开,并且各自作为组件再完善,成为了组件化内核的前身。

优势:二者之间无隔离无切换,简单高效。

劣势:二者之间无隔离无切换,安全性低。

小结:通过U_1_0:helloworld,建立最基本的框架:核心组件axhal、axruntime、api、ulib以及上层应用组件。

后续版本在该基本框架的基础上,通过扩展功能组件,扩展OS能力特性。

这里扩展的部分,就是axalloc, axtask, axsync等

从Unikernel到宏内核

相对于Unikernel,宏内核的特点:

(1) 增加一个权限较低的用户特权级来运行应用。

(2) 为应用创建独立的用户地址空间,与内核隔离。

(3) 内核运行时可以随时加载应用Image投入运行。

(4) 应用与内核界限分明。

为了将我们的工作进一步推进到宏内核,我们还需要实现几个组件,其中就是axmm,用来构造用户地址空间和axfs,加载应用程序代码到地址空间。

Hypervisor

这部分暂时没有做更多的了解,因为本人想参与宏内核的开发,更多的去学习宏内核的相关知识了。此外,软件所的任务也一直在做,于是还没来得及看。

课后练习

主要是在于bump_allocator以及对挑战题目第1题的理解。因为之前也一直在做内存分配这里的工作。

bump_allocator主要还是根据bump分配器本身的一些思想去填充对应的功能即可。

而挑战题目通过优化内存块的分配来提升内存空间的使用效率,使得Indicator这个指标有显性的提升。我的实现是基于slab的,准确来说,只是修改了某个内存块的大小限制,所以优化也没有那么明显。

祝接下来项目阶段好运!

操作系统秋季训练营第三阶段学习总结博客

大家好,我是参加这次操作系统秋季训练营的一名学员。最近我们刚刚结束了第三阶段的学习,这个阶段主要围绕组件化内核设计与实践展开,包括了从Unikernel到宏内核的过渡,以及Hypervisor扩展等内容。
虽然由于时间关系我没能亲自动手完成所有的实验,但通过PPT和一些额外的挑战任务,我还是学到了不少东西。下面我就来分享一下这段时间的学习体会。

一、课程概述

组件化内核的概念与意义

“If you don’t need it, you don’t pay for it”

组件化内核是这次训练营的核心思想,它强调的是将内核的功能分解成一系列独立的组件,这些组件可以灵活地组合起来形成不同规模和功能的内核。这种构建方式不仅提高了开发效率,还降低了维护难度,使得团队成员能够更有效地协作。
相比传统的内核设计,组件化方法允许开发者根据具体需求定制内核,从而更好地满足应用场景的需求。

主流内核模式对比

在训练营中,我们讨论了几种主流的内核模式,比如Unikernel和宏内核等。Unikernel是一种非常轻量级的内核形式,它将应用程序和内核紧密结合在一起,
共享同一地址空间,并且运行在同一个特权级别上。这使得Unikernel非常适合于需要高性能和低资源消耗的应用场景。而宏内核则提供了更加丰富的功能,支持多用户态进程,
每个进程都有自己的地址空间,这增强了系统的安全性和稳定性。但是,宏内核也因此变得更加复杂,对资源的需求也更高。

二、学习内容回顾

Unikernel模式

第一周我们从最基础的Unikernel模式开始,逐步了解了如何通过增加或替换组件来构建出一个简单的内核。
在Unikernel模式的学习过程中,我们深入探讨了如何从最基础的层面构建一个极简的操作系统环境,该环境仅包含应用程序运行所必需的核心组件。
与传统Unix内核相比,Unikernel以其高度定制化和轻量化的特点脱颖而出,通过将操作系统功能直接编译进应用代码中,实现了更小的内存占用、
更快的启动时间和更高的安全性。

宏内核扩展

第二周的内容转向了宏内核的设计,这是从Unikernel到更加复杂的操作系统的桥梁。
在M.1 UserPrivilege实验中,我们探索了如何让应用程序在较低的用户特权级别下运行,同时保持内核的安全性。这一转变意味着我们需要管理更多的地址空间,
并处理由此带来的复杂性问题。通过这样的练习,我对Unix操作系统中的用户空间与内核空间分离有了更深的理解——这种方式虽然增加了系统设计的难度,
但却极大地提高了系统的灵活性和安全性。

Hypervisor扩展

最后一周专注于Hypervisor技术,这也是当前云计算领域的一个热点话题。
通过对H.1 VirtualMode等实验的学习,我了解到Hypervisor是如何模拟出多个虚拟机环境,让它们各自拥有独立的操作系统和硬件资源。
与KVM相比,本课程中的Hypervisor实现以简化模型为tradeoff,通过直接绑定vCPU、忽略多任务并发以及省略复杂的设备模拟等方式,降低了实现复杂度,
但牺牲了一定的灵活性和性能效率。

三、字节分配器设计挑战

尽管大部分时间都在理论学习上度过,我还是尝试着完成了11.15发布的字节分配器设计挑战。虽然最终并没有完全按照题目要求找到一种理想的字节分配算法实现,
却意外发现了测试用例中存在的一个小漏洞。这对我来说也是一个不小的收获,至少证明了自己具备一定的调试能力和批判性思维Xb。
此外,这个过程也加深了我对内存管理机制的理解,尤其是对于像TLSF这样复杂的分配策略有了初步的认识。
img.png

四、个人感悟

整个训练营期间,我深刻体会到组件化设计给操作系统开发带来的巨大便利。
它可以让我们专注于特定领域的功能实现,而不必担心与其他部分的耦合度太高。然而,这也要求开发者具备较强的整体规划能力,否则很容易导致系统结构混乱。
相比之下,传统Unix风格的操作系统就在某些方面可能显得不够灵活,唉唉,历史包袱。

总之,这次训练营给了我一次宝贵的机会去深入了解现代操作系统的设计理念和技术细节。
虽然还有很多知识需要进一步消化吸收,但我相信这段经历将对我未来的职业发展产生积极影响。
希望以后还能有机会参与更多类似的活动,继续充实自己的技术栈!

五、最后

十分感谢所有的讲师和同学的付出,让我有机会接触到如此优秀的技术,也给我带来了非常丰富的学习资源。

week 1

思路:直接修改 axstd 里面的 println!宏,在输出的内容之前加上ansi的控制字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
commit 29c8b52df3644c27ea1f2cb26191e7a45c2c40a4 (HEAD -> exercise)
Author: PandaJiang <3160104094@zju.edu.cn>
Date: Wed Nov 13 22:15:34 2024 +0800

with color

Signed-off-by: PandaJiang <3160104094@zju.edu.cn>

diff --git a/arceos/ulib/axstd/src/macros.rs b/arceos/ulib/axstd/src/macros.rs
index c7cd653..760739b 100644
--- a/arceos/ulib/axstd/src/macros.rs
+++ b/arceos/ulib/axstd/src/macros.rs
@@ -18,6 +18,6 @@ macro_rules! print {
macro_rules! println {
() => { $crate::print!("\n") };
($($arg:tt)*) => {
- $crate::io::__print_impl(format_args!("{}\n", format_args!($($arg)*)));
+ $crate::io::__print_impl(format_args!("\x1b[31m{}\x1b[0m\n", format_args!($($arg)*)));
}
}

support hashmap

思路:axstd 中新增collections模块,将 alloc::collections 中的容器导入。此外,注意到std::collection::hashmap是在hashbrown作为base进行使用。尝试与std做一样的事情,发现可以直接将hashbrown模块引入作为hashmap使用,randomState就采用了默认的。

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
42
43
44
45
46
47
48
49
50
51
52
commit c2cab71c6640a9c9f984144f2e12f701cf347f31
Author: PandaJiang <3160104094@zju.edu.cn>
Date: Wed Nov 13 22:21:01 2024 +0800

support hash

Signed-off-by: PandaJiang <3160104094@zju.edu.cn>

diff --git a/arceos/ulib/axstd/Cargo.toml b/arceos/ulib/axstd/Cargo.toml
index c2322c5..d3a3fe8 100644
--- a/arceos/ulib/axstd/Cargo.toml
+++ b/arceos/ulib/axstd/Cargo.toml
@@ -80,3 +80,4 @@ arceos_api = { workspace = true }
axio = "0.1"
axerrno = "0.1"
kspin = "0.1"
+hashbrown = "0.15"
\ No newline at end of file
diff --git a/arceos/ulib/axstd/src/collections/mod.rs b/arceos/ulib/axstd/src/collections/mod.rs
new file mode 100644
index 0000000..6978165
--- /dev/null
+++ b/arceos/ulib/axstd/src/collections/mod.rs
@@ -0,0 +1,6 @@
+// collections
+
+extern crate alloc;
+
+pub use alloc::collections::*;
+pub use hashbrown::*;
\ No newline at end of file
diff --git a/arceos/ulib/axstd/src/lib.rs b/arceos/ulib/axstd/src/lib.rs
index 5ab0517..49784bb 100644
--- a/arceos/ulib/axstd/src/lib.rs
+++ b/arceos/ulib/axstd/src/lib.rs
@@ -55,7 +55,7 @@ extern crate alloc;

#[cfg(feature = "alloc")]
#[doc(no_inline)]
-pub use alloc::{boxed, collections, format, string, vec};
+pub use alloc::{boxed, format, string, vec};

#[doc(no_inline)]
pub use core::{arch, cell, cmp, hint, marker, mem, ops, ptr, slice, str};
@@ -70,6 +70,7 @@ pub mod process;
pub mod sync;
pub mod thread;
pub mod time;
+pub mod collections;

#[cfg(feature = "fs")]
pub mod fs;

bump 内存分配算法

  • 分配bytes

    实现较为简单,用b_pos记录当前内存bytes分配到的地方。 唯一需要注意的是

    1. 要根据aligment返回起始地址。并将分配计数+1
    2. 要检查 b_pos + size是否到达了 p_pos,是的话说明内存已经被耗尽。
  • 分配pages
    实现也较为简单,用p_pos记录当前pages分配的地方。需要注意的是

    1. p_pos是递减的,返回的地址会越来越小
    2. 要检查p_pos - size是否到达了b_pos,是的话说明内存已经被耗尽。
  • 释放bytes

    • 引用计数减1,然后检查是否为0即可,为0的话就将b——pos重置。

mv和rename操作

  • mv

    分解为 create + del 两个动作,在新的地址上写入原文件,然后删除原文件

  • rename

    文件系统有相关api,直接调用即可。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
commit adabd01bad84c50a172a5a43af4d7cb77d75c4d7
Author: Panda Jiang <3160104094@zju.edu.cn>
Date: Mon Nov 18 08:00:23 2024 +0000

mv and rename

Signed-off-by: Panda Jiang <3160104094@zju.edu.cn>

diff --git a/arceos/examples/shell/src/cmd.rs b/arceos/examples/shell/src/cmd.rs
index 23f087f..f81723c 100644
--- a/arceos/examples/shell/src/cmd.rs
+++ b/arceos/examples/shell/src/cmd.rs
@@ -1,5 +1,6 @@
use std::fs::{self, File, FileType};
use std::io::{self, prelude::*};
+use std::string::ToString;
use std::{string::String, vec::Vec};

#[cfg(all(not(feature = "axstd"), unix))]
@@ -27,6 +28,8 @@ const CMD_TABLE: &[(&str, CmdHandler)] = &[
("pwd", do_pwd),
("rm", do_rm),
("uname", do_uname),
+ ("rename", do_rename),
+ ("mv", do_mv),
];

fn file_type_to_char(ty: FileType) -> char {
@@ -195,6 +198,84 @@ fn do_mkdir(args: &str) {
}
}

+fn do_rename(args: &str) {
+ let mut arg_v = args.split(" ");
+ let old_name = arg_v.next();
+ let new_name = arg_v.next();
+ if old_name.is_none() || new_name.is_none() || arg_v.next().is_some() {
+ println!("rename: invalid args");
+ println!("usage: rename $old_name $new_name");
+ return;
+ }
+ let old_name_s = old_name.unwrap();
+ let new_name_s = new_name.unwrap();
+ println!("try to rename {old_name_s} to {new_name_s}");
+ if let Err(e) = fs::rename(old_name_s, new_name_s) {
+ print_err!("rename", format_args!("cannot rename '{old_name_s}' to '{new_name_s}'"), e);
+ }
+ return;
+}
+
+fn do_mv(args: &str) {
+ let mut arg_v = args.split(" ");
+ let old_file_path = arg_v.next();
+ let new_file_path = arg_v.next();
+ if old_file_path.is_none() || new_file_path.is_none() || arg_v.next().is_some() {
+ println!("mv: invalid args");
+ println!("usage: mv $old_file_path $new_path_path");
+ return;
+ }
+ fn rm_one(path: &str, rm_dir: bool) -> io::Result<()> {
+ if rm_dir && fs::metadata(path)?.is_dir() {
+ fs::remove_dir(path)
+ } else {
+ fs::remove_file(path)
+ }
+ }
+ fn write_file(fname: &str, bufs: &Vec<Vec<u8>>) -> io::Result<()> {
+ let mut file = File::create(fname)?;
+ for buf in bufs {
+ file.write_all(&buf[..])?;
+ }
+ Ok(())
+ }
+ fn read_file(fname: &str, all_data: &mut Vec<Vec<u8>>) -> io::Result<()> {
+ let mut file = File::open(fname)?;
+ loop {
+ let mut buf=vec![0u8;1024];
+ let n = file.read(&mut buf)?;
+ if n > 0 {
+ all_data.push(buf);
+ } else {
+ return Ok(());
+ }
+ }
+ }
+ let old_path = old_file_path.unwrap();
+ let last_separator_pos = old_path.rfind(|c| c == '/' || c == '\\');
+ // 提取文件名
+ let filename = match last_separator_pos {
+ Some(pos) => &old_path[pos + 1..],
+ None => old_path,
+ };
+ let mut new_path = new_file_path.unwrap().to_string();
+ new_path.push('/');
+ new_path.push_str(filename);
+ let mut data:Vec<Vec<u8>> = Vec::new();
+ if let Err(e) = read_file(old_path, &mut data) {
+ print_err!("mv", format_args!("cannot read '{old_path}'"), e);
+ return;
+ }
+ if let Err(e) = write_file(&new_path, &data) {
+ print_err!("mv", format_args!("cannot open '{new_path}'"), e);
+ return;
+ }
+ if let Err(e) = rm_one(old_path, false) {
+ print_err!("mv", format_args!("cannot rm '{old_path}'"), e);
+ return;
+ }
+}
+
fn do_rm(args: &str) {
if args.is_empty() {
print_err!("rm", "missing operand");

week2

pagefault 缺页处理

populate参数为false时,uspace模块只会为将该块虚拟内存地址记录下来,实际的访问会导致 pagefault, 在该handler中处理这一错误

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
commit c0ed66e6fd0ad5af24f7ae6b2e683c43df03228a
Author: PandaJiang <3160104094@zju.edu.cn>
Date: Wed Nov 20 22:19:20 2024 +0800

add page fault func

Signed-off-by: PandaJiang <3160104094@zju.edu.cn>

diff --git a/arceos/payload/fileops_c/fileops b/arceos/payload/fileops_c/fileops
new file mode 100755
index 0000000..a457c8c
Binary files /dev/null and b/arceos/payload/fileops_c/fileops differ
diff --git a/arceos/payload/hello_c/hello b/arceos/payload/hello_c/hello
new file mode 100755
index 0000000..a44f4c6
Binary files /dev/null and b/arceos/payload/hello_c/hello differ
diff --git a/arceos/payload/origin/Makefile b/arceos/payload/origin/Makefile
new file mode 100644
index 0000000..15ba26d
--- /dev/null
+++ b/arceos/payload/origin/Makefile
@@ -0,0 +1,18 @@
+TARGET := origin
+TARGET_ELF := ../../target/riscv64gc-unknown-none-elf/release/$(TARGET)
+
+all: $(TARGET) FORCE
+
+$(TARGET): $(TARGET_ELF)
+ @rust-objcopy --binary-architecture=riscv64 --strip-all -O binary $< $@
+
+$(TARGET_ELF): src/main.rs
+ @cargo build -p origin --target riscv64gc-unknown-none-elf --release
+
+clean:
+ @rm -rf ./$(TARGET)
+ @cargo clean -p origin --target riscv64gc-unknown-none-elf --release
+
+FORCE:
+
+.PHONY: FORCE
diff --git a/arceos/payload/origin/origin b/arceos/payload/origin/origin
new file mode 100755
index 0000000..da2037d
Binary files /dev/null and b/arceos/payload/origin/origin differ
diff --git a/arceos/tour/m_1_0/src/main.rs b/arceos/tour/m_1_0/src/main.rs
index 89ef91d..f8c6077 100644
--- a/arceos/tour/m_1_0/src/main.rs
+++ b/arceos/tour/m_1_0/src/main.rs
@@ -12,6 +12,8 @@ mod task;
mod syscall;
mod loader;

+use std::println;
+
use axstd::io;
use axhal::paging::MappingFlags;
use axhal::arch::UspaceContext;
@@ -19,12 +21,27 @@ use axhal::mem::VirtAddr;
use axsync::Mutex;
use alloc::sync::Arc;
use axmm::AddrSpace;
+use axtask::TaskExtRef;
use loader::load_user_app;
+use axhal::trap::{register_trap_handler, PAGE_FAULT};

const USER_STACK_SIZE: usize = 0x10000;
const KERNEL_STACK_SIZE: usize = 0x40000; // 256 KiB
const APP_ENTRY: usize = 0x1000;

+#[register_trap_handler(PAGE_FAULT)]
+fn page_fault(vaddr: VirtAddr, access_flags: MappingFlags, is_user: bool) -> bool {
+ if !is_user {
+ panic!("page fault from kernel!!!");
+ }
+ println!("handler user page fault");
+ axtask::current()
+ .task_ext()
+ .aspace
+ .lock()
+ .handle_page_fault(vaddr, access_flags)
+}
+
#[cfg_attr(feature = "axstd", no_mangle)]
fn main() {
// A new address space for user app.
@@ -36,7 +53,7 @@ fn main() {
}

// Init user stack.
- let ustack_top = init_user_stack(&mut uspace, true).unwrap();
+ let ustack_top = init_user_stack(&mut uspace, false).unwrap();
ax_println!("New user address space: {:#x?}", uspace);

// Let's kick off the user process.

mmap syscall

与page fault处理类似,有相应api可以调用

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
commit daef0f232f4ae709afe1ef19974afa29b4594c95
Author: Panda Jiang <3160104094@zju.edu.cn>
Date: Thu Nov 21 09:22:08 2024 +0000

mmap

Signed-off-by: Panda Jiang <3160104094@zju.edu.cn>

diff --git a/arceos/exercises/sys_map/src/syscall.rs b/arceos/exercises/sys_map/src/syscall.rs
index 83b98d5..ac6624d 100644
--- a/arceos/exercises/sys_map/src/syscall.rs
+++ b/arceos/exercises/sys_map/src/syscall.rs
@@ -1,13 +1,16 @@
#![allow(dead_code)]

use core::ffi::{c_void, c_char, c_int};
+use core::usize;
use axhal::arch::TrapFrame;
use axhal::trap::{register_trap_handler, SYSCALL};
use axerrno::LinuxError;
+use axmm::{USER_ASPACE_BASE, USER_ASPACE_SIZE};
use axtask::current;
use axtask::TaskExtRef;
use axhal::paging::MappingFlags;
-use arceos_posix_api as api;
+use arceos_posix_api::{self as api, get_file_like};
+use memory_addr::{MemoryAddr, VirtAddr, VirtAddrRange};

const SYS_IOCTL: usize = 29;
const SYS_OPENAT: usize = 56;
@@ -140,7 +143,53 @@ fn sys_mmap(
fd: i32,
_offset: isize,
) -> isize {
- unimplemented!("no sys_mmap!");
+ let file = get_file_like(fd);
+ if file.is_err() {
+ return 0;
+ }
+ let file = file.unwrap();
+ let ss = file.stat();
+ if ss.is_err() {
+ return 0;
+ }
+ let size = ss.unwrap().st_size as usize;
+ if length > size {
+ debug!("incoming length is too long!!");
+ }
+ let prot_f = MmapProt::from_bits(prot);
+ if prot_f.is_none() {
+ return 0;
+ }
+ let mpflag:MappingFlags = prot_f.unwrap().into();
+ let mut buf = alloc::vec![0u8; size];
+ if let Err(_) = file.read(&mut buf) {
+ return 0;
+ }
+ let c = current();
+ let mut uspace = c
+ .task_ext()
+ .aspace
+ .lock();
+ let mut vaddr = VirtAddr::from(addr as usize);
+ if let Some(va) = uspace.find_free_area(vaddr, length.align_up_4k(), VirtAddrRange::from_start_size(USER_ASPACE_BASE.into(), USER_ASPACE_SIZE.into())) {
+ debug!("old vaddr {:?}", vaddr);
+ vaddr = va;
+ debug!("new vaddr {:?}", vaddr);
+ } else {
+ ax_println!("can't find free vaddr");
+ return 0;
+ }
+
+ if let Err(e) = uspace.map_alloc(vaddr, length.align_up_4k(), mpflag, true) {
+ ax_println!("error while mmap, {:?}", e);
+ return 0;
+ }
+ if let Ok(_) = uspace.write(vaddr, &buf) {
+ let ret:usize = vaddr.into();
+ ret as isize
+ } else {
+ 0
+ }
}

fn sys_openat(dfd: c_int, fname: *const c_char, flags: c_int, mode: api::ctypes::mode_t) -> isize {
diff --git a/arceos/modules/axhal/src/trap.rs b/arceos/modules/axhal/src/trap.rs
index a38f7e5..5fa9499 100644
--- a/arceos/modules/axhal/src/trap.rs
+++ b/arceos/modules/axhal/src/trap.rs
@@ -41,5 +41,6 @@ macro_rules! handle_trap {
/// Call the external syscall handler.
#[cfg(feature = "uspace")]
pub(crate) fn handle_syscall(tf: &TrapFrame, syscall_num: usize) -> isize {
+ debug!("handle syscall in axhal");
SYSCALL[0](tf, syscall_num)
}
diff --git a/arceos/modules/axmm/src/lib.rs b/arceos/modules/axmm/src/lib.rs
index ed23c71..e0ace44 100644
--- a/arceos/modules/axmm/src/lib.rs
+++ b/arceos/modules/axmm/src/lib.rs
@@ -19,8 +19,8 @@ use lazyinit::LazyInit;
use memory_addr::{va, PhysAddr, VirtAddr};
use memory_set::MappingError;

-const USER_ASPACE_BASE: usize = 0x0000;
-const USER_ASPACE_SIZE: usize = 0x40_0000_0000;
+pub const USER_ASPACE_BASE: usize = 0x0000;
+pub const USER_ASPACE_SIZE: usize = 0x40_0000_0000;

static KERNEL_ASPACE: LazyInit<SpinNoIrq<AddrSpace>> = LazyInit::new();

week 3

simple hv

这个题是面对用例编程,分析下面的程序

1
2
3
4
5
6
7
8
9
unsafe extern "C" fn _start() -> ! {
core::arch::asm!(
"csrr a1, mhartid", // 获取mhardid的值,按照最后
"ld a0, 64(zero)", // 访问0x40处的值
"li a7, 8",
"ecall", // shutdown
options(noreturn)
)
}

结合下面的代码,知道是需要我们将GUEST的a0设置为0x6688, a1设置为0x1234。

1
2
3
4
5
6
7

fn vmexit_handler(ctx: &mut VmCpuRegisters) -> bool {
...
assert_eq!(a0, 0x6688);
assert_eq!(a1, 0x1234);
...
}

最终实现如下:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
commit 325d23818ae08961b4cf8e8d500b90f04dbd98d3
Author: PandaJiang <3160104094@zju.edu.cn>
Date: Tue Nov 26 23:31:54 2024 +0800

simple hv

Signed-off-by: PandaJiang <3160104094@zju.edu.cn>

diff --git a/arceos/exercises/simple_hv/src/main.rs b/arceos/exercises/simple_hv/src/main.rs
index 788ad8e..5809a64 100644
--- a/arceos/exercises/simple_hv/src/main.rs
+++ b/arceos/exercises/simple_hv/src/main.rs
@@ -3,29 +3,29 @@
#![feature(asm_const)]
#![feature(riscv_ext_intrinsics)]

+extern crate alloc;
#[cfg(feature = "axstd")]
extern crate axstd as std;
-extern crate alloc;
#[macro_use]
extern crate axlog;

-mod task;
-mod vcpu;
-mod regs;
mod csrs;
-mod sbi;
mod loader;
+mod regs;
+mod sbi;
+mod task;
+mod vcpu;

-use vcpu::VmCpuRegisters;
-use riscv::register::{scause, sstatus, stval};
+use crate::regs::GprIndex::{A0, A1};
+use axhal::mem::PhysAddr;
use csrs::defs::hstatus;
-use tock_registers::LocalRegisterCopy;
use csrs::{RiscvCsrTrait, CSR};
-use vcpu::_run_guest;
-use sbi::SbiMessage;
use loader::load_vm_image;
-use axhal::mem::PhysAddr;
-use crate::regs::GprIndex::{A0, A1};
+use riscv::register::{scause, sstatus, stval};
+use sbi::SbiMessage;
+use tock_registers::LocalRegisterCopy;
+use vcpu::VmCpuRegisters;
+use vcpu::_run_guest;

const VM_ENTRY: usize = 0x8020_0000;

@@ -50,8 +50,7 @@ fn main() {
prepare_vm_pgtable(ept_root);

// Kick off vm and wait for it to exit.
- while !run_guest(&mut ctx) {
- }
+ while !run_guest(&mut ctx) {}

panic!("Hypervisor ok!");
}
@@ -68,10 +67,11 @@ fn prepare_vm_pgtable(ept_root: PhysAddr) {
}

fn run_guest(ctx: &mut VmCpuRegisters) -> bool {
+ // ax_println!("re-run guest");
unsafe {
_run_guest(ctx);
}
-
+ // ax_println!("vm_exit");
vmexit_handler(ctx)
}

@@ -94,25 +94,34 @@ fn vmexit_handler(ctx: &mut VmCpuRegisters) -> bool {
assert_eq!(a1, 0x1234);
ax_println!("Shutdown vm normally!");
return true;
- },
+ }
_ => todo!(),
}
} else {
panic!("bad sbi message! ");
}
- },
+ }
Trap::Exception(Exception::IllegalInstruction) => {
- panic!("Bad instruction: {:#x} sepc: {:#x}",
- stval::read(),
- ctx.guest_regs.sepc
- );
- },
+ // ax_println!(
+ // "Bad instruction: {:#x} sepc: {:#x}",
+ // stval::read(),
+ // ctx.guest_regs.sepc
+ // );
+ ctx.guest_regs.gprs.set_reg(A1, 0x1234);
+ ctx.guest_regs.sepc += 4;
+ // ax_println!("next instruction {:#x}", ctx.guest_regs.sepc);
+ return false;
+ }
Trap::Exception(Exception::LoadGuestPageFault) => {
- panic!("LoadGuestPageFault: stval{:#x} sepc: {:#x}",
- stval::read(),
- ctx.guest_regs.sepc
- );
- },
+ // panic!(
+ // "LoadGuestPageFault: stval{:#x} sepc: {:#x}",
+ // stval::read(),
+ // ctx.guest_regs.sepc
+ // );
+ ctx.guest_regs.gprs.set_reg(A0, 0x6688);
+ ctx.guest_regs.sepc += 4;
+ return false;
+ }
_ => {
panic!(
"Unhandled trap: {:?}, sepc: {:#x}, stval: {:#x}",
@@ -127,9 +136,8 @@ fn vmexit_handler(ctx: &mut VmCpuRegisters) -> bool {

fn prepare_guest_context(ctx: &mut VmCpuRegisters) {
// Set hstatus
- let mut hstatus = LocalRegisterCopy::<usize, hstatus::Register>::new(
- riscv::register::hstatus::read().bits(),
- );
+ let mut hstatus =
+ LocalRegisterCopy::<usize, hstatus::Register>::new(riscv::register::hstatus::read().bits());
// Set Guest bit in order to return to guest mode.
hstatus.modify(hstatus::spv::Guest);
// Set SPVP bit in order to accessing VS-mode memory from HS-mode.

hv_pflash

思路: 用upload img脚本将pflash传到 disk.img 中,然后在处理异常地址时读取该文件,将该文件的内容映射到 pflash 所在的地址即可。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
commit d3959ba050e8d4fcbd764415b0bac71be5a2013b
Author: Panda Jiang <3160104094@zju.edu.cn>
Date: Fri Nov 29 07:43:01 2024 +0000

read pflash.img ok

Signed-off-by: Panda Jiang <3160104094@zju.edu.cn>

diff --git a/arceos/tour/h_2_0/src/main.rs b/arceos/tour/h_2_0/src/main.rs
index 0ae2f0e..d7b6d6a 100644
--- a/arceos/tour/h_2_0/src/main.rs
+++ b/arceos/tour/h_2_0/src/main.rs
@@ -6,14 +6,14 @@ extern crate log;
#[macro_use]
extern crate alloc;
extern crate axstd as std;
+use alloc::string::String;
use alloc::string::ToString;
-use riscv_vcpu::AxVCpuExitReason;
use axerrno::{ax_err_type, AxResult};
use memory_addr::VirtAddr;
-use alloc::string::String;
-use std::fs::File;
-use riscv_vcpu::RISCVVCpu;
+use riscv_vcpu::AxVCpuExitReason;
use riscv_vcpu::AxVCpuExitReason::NestedPageFault;
+use riscv_vcpu::RISCVVCpu;
+use std::fs::File;

const VM_ASPACE_BASE: usize = 0x0;
const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000;
@@ -21,8 +21,8 @@ const PHY_MEM_START: usize = 0x8000_0000;
const PHY_MEM_SIZE: usize = 0x100_0000;
const KERNEL_BASE: usize = 0x8020_0000;

-use axmm::AddrSpace;
use axhal::paging::MappingFlags;
+use axmm::AddrSpace;

#[no_mangle]
fn main() {
@@ -36,40 +36,47 @@ fn main() {

// Physical memory region. Full access flags.
let mapping_flags = MappingFlags::from_bits(0xf).unwrap();
- aspace.map_alloc(PHY_MEM_START.into(), PHY_MEM_SIZE, mapping_flags, true).unwrap();
+ aspace
+ .map_alloc(PHY_MEM_START.into(), PHY_MEM_SIZE, mapping_flags, true)
+ .unwrap();

// Load corresponding images for VM.
info!("VM created success, loading images...");
let image_fname = "/sbin/u_3_0_riscv64-qemu-virt.bin";
- load_vm_image(image_fname.to_string(), KERNEL_BASE.into(), &aspace).expect("Failed to load VM images");
+ load_vm_image(image_fname.to_string(), KERNEL_BASE.into(), &aspace)
+ .expect("Failed to load VM images");

// Create VCpus.
let mut arch_vcpu = RISCVVCpu::init();

// Setup VCpus.
- info!("bsp_entry: {:#x}; ept: {:#x}", KERNEL_BASE, aspace.page_table_root());
+ info!(
+ "bsp_entry: {:#x}; ept: {:#x}",
+ KERNEL_BASE,
+ aspace.page_table_root()
+ );
arch_vcpu.set_entry(KERNEL_BASE.into()).unwrap();
arch_vcpu.set_ept_root(aspace.page_table_root()).unwrap();

loop {
match vcpu_run(&mut arch_vcpu) {
Ok(exit_reason) => match exit_reason {
- AxVCpuExitReason::Nothing => {},
- NestedPageFault{addr, access_flags} => {
+ AxVCpuExitReason::Nothing => {}
+ NestedPageFault { addr, access_flags } => {
debug!("addr {:#x} access {:#x}", addr, access_flags);
assert_eq!(addr, 0x2200_0000.into(), "Now we ONLY handle pflash#2.");
- let mapping_flags = MappingFlags::from_bits(0xf).unwrap();
- // Passthrough-Mode
- let _ = aspace.map_linear(addr, addr.as_usize().into(), 4096, mapping_flags);
+ // let mapping_flags = MappingFlags::from_bits(0xf).unwrap();
+ // // Passthrough-Mode
+ // let _ = aspace.map_linear(addr, addr.as_usize().into(), 4096, mapping_flags);

/*
// Emulator-Mode
// Pretend to load file to fill buffer.
- let buf = "pfld";
+ */
+ // let buf = "pfld";
aspace.map_alloc(addr, 4096, mapping_flags, true);
- aspace.write(addr, buf.as_bytes());
- */
- },
+ load_file("/sbin/pflash.img", addr, &aspace).unwrap()
+ }
_ => {
panic!("Unhandled VM-Exit: {:?}", exit_reason);
}
@@ -81,6 +88,24 @@ fn main() {
}
}

+fn load_file(file_path: &str, image_file_load_addr: VirtAddr, aspace: &AddrSpace) -> AxResult {
+ use std::io::{BufReader, Read};
+ let (ffile, size) = open_image_file(file_path)?;
+ let mut file = BufReader::new(ffile);
+ let load_region = aspace
+ .translated_byte_buffer(image_file_load_addr, 4096)
+ .expect("failed to translate kernel image load address");
+ for buffer in load_region {
+ file.read_exact(buffer).map_err(|err| {
+ ax_err_type!(
+ Io,
+ format!("Failed in reading from file {}, err {:?}", file_path, err)
+ )
+ })?
+ }
+ Ok(())
+}
+
fn load_vm_image(image_path: String, image_load_gpa: VirtAddr, aspace: &AddrSpace) -> AxResult {
use std::io::{BufReader, Read};
let (image_file, image_size) = open_image_file(image_path.as_str())?;
@@ -103,7 +128,7 @@ fn load_vm_image(image_path: String, image_load_gpa: VirtAddr, aspace: &AddrSpac
}

fn vcpu_run(arch_vcpu: &mut RISCVVCpu) -> AxResult<AxVCpuExitReason> {
- use axhal::arch::{local_irq_save_and_disable, local_irq_restore};
+ use axhal::arch::{local_irq_restore, local_irq_save_and_disable};
let flags = local_irq_save_and_disable();
let ret = arch_vcpu.run();
local_irq_restore(flags);

很高兴能够进入第三阶段,本来想着好好跟进一下,但是很遗憾与考试科目以及组里的项目冲突里,仅仅完成了前三次课程的实验,后面大体了解了一下宏内核与Hypervisor

第一次课 HelloWorld

image-20241111222207439

什么是组件化操作系统? 上面是课件里很形象的一张图。在裸机程序中,Bootloader阶段后,需要初始化寄存器、栈指针等。之后初始化串口驱动,并在此基础上打印HelloWorld,这是一套连贯的操作流程。在层次化中,可以分为Hal层和runtime层,Hal层对于对应一些基本的初始化,runtime层对应的是驱动的初始化,为上层的应用提供运行时环境。如果迭代为组件化结构,那么优势就很明显了,在hal层可以抽象出不同架构的初始化,runtime层分为了不同组件,在构建不同的裸机应用时,可以有选择的使用相关的组件,有利于系统的裁剪和定制化。

一个简单的helloword程序使用的组件大致如图所示

image-20241111223112760

课后练习

为了实现带颜色的输出,一个最简单的实现是在输出字符串前后加入相应的转义字符。

修改axstd组件中println的实现即可

1
2
3
4
5
6
7
#[macro_export]
macro_rules! println {
() => { $crate::print!("\n") };
($($arg:tt)*) => {
$crate::io::__print_impl(format_args!("\x1b[34m{}\x1b[0m\n", format_args!($($arg)*)));
}
}

image-20241111223334733

或者修改axhal中putchar的实现,在打印每一个字符时都加入相应的转义字符

1
2
3
4
5
6
7
8
9
10
11
12
13
pub fn putchar(c: u8) {
#[allow(deprecated)]
sbi_rt::legacy::console_putchar('\x1b' as usize);
sbi_rt::legacy::console_putchar('[' as usize);
sbi_rt::legacy::console_putchar('3' as usize);
sbi_rt::legacy::console_putchar('4' as usize);
sbi_rt::legacy::console_putchar('m' as usize);
sbi_rt::legacy::console_putchar(c as usize);
sbi_rt::legacy::console_putchar('\x1b' as usize);
sbi_rt::legacy::console_putchar('[' as usize);
sbi_rt::legacy::console_putchar('0' as usize);
sbi_rt::legacy::console_putchar('m' as usize);
}

image-20241111224510977

第二次课 Collections

引入了新的组件axalloc,以便于实现动态的内存分配。

课后练习

支持HashMap

使用hashbrown作为HashMap实现

image-20241115210910430

第三次课

课后练习

image-20241115211654916

实现一个简单的bump内存分配算法,比较简单,维护相应的trait即可。Page不考虑回收,回收Bytes的时候记录剩余的Bytes计数,在count为0的时候将byte_pos归零。

image-20241115212923154

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
pub struct EarlyAllocator<const PAGE_SIZE: usize> {
start: usize,
b_pos: usize,
p_pos: usize,
end: usize,
count: usize,
}

impl<const PAGE_SIZE: usize> EarlyAllocator<PAGE_SIZE> {
pub const fn new() -> Self {
Self {
start: 0,
b_pos: 0,
p_pos: 0,
end: 0,
count: 0,
}
}
}

impl<const PAGE_SIZE: usize> BaseAllocator for EarlyAllocator<PAGE_SIZE> {
fn init(&mut self, start: usize, size: usize) {
self.start = start;
self.end = start + size;
self.b_pos = start;
self.p_pos = self.end;
self.count = 0;
}
fn add_memory(&mut self, _start: usize, _size: usize) -> AllocResult {
Err(AllocError::NoMemory)
}
}

impl<const PAGE_SIZE: usize> ByteAllocator for EarlyAllocator<PAGE_SIZE> {
fn alloc(&mut self, layout: Layout) -> AllocResult<NonNull<u8>> {
let size = layout.size();
let align = layout.align();
let align_mask = align - 1;
let new_pos = (self.b_pos + align_mask) & !align_mask;
if new_pos + size > self.p_pos {
return Err(AllocError::NoMemory);
}
self.b_pos = new_pos + size;
self.count += 1;
Ok(NonNull::new(new_pos as *mut u8).unwrap())
}
fn dealloc(&mut self, _ptr: NonNull<u8>, _layout: Layout) {
if self.count > 0 {
self.count -= 1;
}
if self.count == 0 {
self.b_pos = self.start;
}
// Do nothing
}
fn total_bytes(&self) -> usize {
self.end - self.start
}
fn available_bytes(&self) -> usize {
self.p_pos - self.b_pos
}
fn used_bytes(&self) -> usize {
self.b_pos - self.start
}
}

impl<const PAGE_SIZE: usize> PageAllocator for EarlyAllocator<PAGE_SIZE> {
const PAGE_SIZE: usize = PAGE_SIZE;
fn alloc_pages(&mut self, num_pages: usize, align_pow2: usize) -> AllocResult<usize> {
if align_pow2 % Self::PAGE_SIZE != 0 {
return Err(AllocError::InvalidParam);
}
let align_pow2 = align_pow2 / Self::PAGE_SIZE;
if !align_pow2.is_power_of_two() {
return Err(AllocError::InvalidParam);
}
let p_pos = self.p_pos - num_pages * Self::PAGE_SIZE;
if p_pos < self.b_pos {
return Err(AllocError::NoMemory);
}

self.p_pos -= num_pages * Self::PAGE_SIZE;
Ok(self.p_pos)
}
fn dealloc_pages(&mut self, _pos: usize, _num_pages: usize) {
// Do nothing
}
fn total_pages(&self) -> usize {
(self.end - self.start) / Self::PAGE_SIZE
}
fn used_pages(&self) -> usize {
(self.end - self.p_pos) / Self::PAGE_SIZE
}
fn available_pages(&self) -> usize {
self.p_pos / Self::PAGE_SIZE
}
}

感悟

第三节阶段的代码相对于第二阶段清晰了不少,很后悔没有持续跟进下去,其实本人一直想学习的是基于协程异步机制的操作系统/驱动这一部分,不知道为什么没有相应的前置课程,希望第四阶段能够坚持下去,学习一下协程、异步机制以及操作系统的一些新实践。