Skip to content

Commit e92004b

Browse files
Merge pull request #843 from Blankke/master
blankke_blog
2 parents 065b6a6 + fd81617 commit e92004b

File tree

1 file changed

+232
-0
lines changed

1 file changed

+232
-0
lines changed
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
---
2+
title: arceos-summary-Blankke
3+
date: 2025-11-02 15:05:13
4+
categories:
5+
- handnote
6+
tags:
7+
- author: Blankke
8+
- repo : https://github.com/LearningOS/2025a-arceos-Blankke
9+
10+
11+
---
12+
<!-- more -->
13+
14+
# 学习总结 - Blankke
15+
rcore与arceOS可以理解成两种不同思路编写而来的内核,而内核基本原理上估计是不会有太大区别的。
16+
抱着这样的想法,三阶段只给了3周的时间,我就直接上了。
17+
因为内核赛的时候基本上是整个内核都写了一遍,除了文件系统是调ext4库了没有太管里面怎么实现的,其他的原理明白了做起来还是挺快的。
18+
我不知道有什么荣誉准则要求,这话说起来其实挺不好的,但是我的准则是不管copy还是llm生成,只要我觉得下一次遇到一样的问题我能一眼看出来用这个方法解决,那我就觉得这个学习是有效的。
19+
所以这次的arceOS学习我也是抱着这样的态度去做的,并且内核的学习实际上最重要的部分可能更是在于解决问题,也就是在针对特定问题的排查思路上。
20+
# rcore
21+
rcore部分我本身没有记录很多,只记了一些学习rust的时候的笔记,毕竟rcore的代码量实在是太大了,想要全部理解需要花费大量时间。然后在完成练习的时候我还不知道要写blog,所以只有一点点感悟和笔记。
22+
## ch3
23+
### 为什么在 TaskManager 中添加方法而不是直接返回 TaskControlBlock
24+
这确实是 Rust 所有权系统的限制,与 C++ 有本质区别:
25+
**Rust 所有权问题:**
26+
```rust
27+
// 这样的设计在 Rust 中是不可能的:
28+
pub fn get_current_task_mut(&self) -> &mut TaskControlBlock {
29+
let mut inner = self.inner.exclusive_access();
30+
let current = inner.current_task;
31+
&mut inner.tasks[current] // ❌ 编译错误!
32+
}
33+
```
34+
- inner 是一个临时变量,当函数返回时会被销毁
35+
- 返回的 `&mut TaskControlBlock` 引用了inner的内容
36+
- Rust 编译器检测到"悬垂引用"(dangling reference)问题
37+
**C++ vs Rust**
38+
```cpp
39+
// C++ 可以这样做(自己管理)
40+
TaskControlBlock& TaskManager::getCurrentTask() {
41+
auto lock = inner.lock();
42+
return tasks[current_task]; // 返回引用,但锁可能已释放
43+
}
44+
```
45+
```rust
46+
// Rust 强制我们使用更安全的封装方法
47+
impl TaskManager {
48+
pub fn increment_current_syscall_count(&self, syscall_id: usize) {
49+
let mut inner = self.inner.exclusive_access(); // 获取锁
50+
let current = inner.current_task;
51+
inner.tasks[current].increment_syscall_count(syscall_id);
52+
// 锁在这里自动释放
53+
}
54+
}
55+
```
56+
57+
### Clone trait 和 new 方法的关系
58+
- **Copy**:浅拷贝,按位复制,用于简单类型(如整数)
59+
- **Clone**:深拷贝,可能涉及堆内存分配,用于复杂类型
60+
但添加 Vec<(usize, usize)> 后:
61+
```rust
62+
pub struct TaskControlBlock {
63+
pub syscall_counts: Vec<(usize, usize)>, // Vec 不能实现 Copy
64+
}
65+
let vec1 = vec![1, 2, 3]; // 在堆上分配内存
66+
let vec2 = vec1; // 如果是 Copy,会有两个指针指向同一块内存
67+
// 当 vec1 和 vec2 都被销毁时,会导致 double free!
68+
```
69+
原来的初始化方式不再适用:
70+
```rust
71+
// 旧代码 - 数组字面量初始化
72+
let tasks = [TaskControlBlock {
73+
task_cx: TaskContext::zero_init(),
74+
task_status: TaskStatus::UnInit,
75+
}; MAX_APP_NUM]; // ❌ 需要 Copy trait
76+
```
77+
新的初始化方式:
78+
```rust
79+
// 新代码 - 使用 core::array::from_fn
80+
let tasks: [TaskControlBlock; MAX_APP_NUM] = core::array::from_fn(|_| {
81+
TaskControlBlock::new(TaskContext::zero_init(), TaskStatus::UnInit)
82+
});
83+
```
84+
85+
## ch4
86+
主要新建的函数有
87+
```rust
88+
/// Translate a user pointer to a mutable reference
89+
pub fn translate_user_ptr<T>(ptr: *mut T) -> Option<&'static mut T> {
90+
TASK_MANAGER.translate_user_ptr(ptr)
91+
}
92+
93+
/// Translate a user pointer to a reference
94+
pub fn translate_user_ptr_readonly<T>(ptr: *const T) -> Option<&'static T> {
95+
TASK_MANAGER.translate_user_ptr_readonly(ptr)
96+
}
97+
98+
```
99+
内部使用页表进行翻译,获得的(可变)引用可以用unsafe的类指针操作直接修改内存。
100+
mmap的实现与cpp的方法无异,只是对应的层级是task层,由taskmanager调用获取当前的task,当前的task使用mmap,所以mmap是task的类方法。
101+
102+
103+
# ArceOS
104+
## Unikernel
105+
### T1 print-with-color
106+
这个看了一下,可以在log层打印,也可以直接改std。
107+
```bash
108+
arch = riscv64
109+
platform = riscv64-qemu-virt
110+
target = riscv64gc-unknown-none-elf
111+
smp = 1
112+
build_mode = release
113+
log_level = warn
114+
```
115+
像这种信息就是在log层里打出来的,如果修改axlog模块的lib.rs,那么这些打印信息就会变色
116+
```rust
117+
/// axlog/lib.rs
118+
/// Prints to the console, with a newline.
119+
#[macro_export]
120+
macro_rules! ax_println {
121+
() => { $crate::ax_print!("\n") };
122+
($($arg:tt)*) => {
123+
$crate::__print_impl($crate::with_color!(
124+
$crate::ColorCode::BrightGreen,
125+
"{}\n",
126+
format_args!($($arg)*)
127+
));
128+
}
129+
}
130+
```
131+
但是根据题目要求,我们打印的那句话其实是axstd里面的,所以我其实只在这个macro.rs里添加了色号就可以了。
132+
```rust
133+
/// Prints to the standard output, with a newline.
134+
#[macro_export]
135+
macro_rules! println {
136+
() => { $crate::print!("\n") };
137+
($($arg:tt)*) => {
138+
$crate::io::__print_impl(format_args!("\u{1B}[92m{}\u{1B}[m\n", format_args!($($arg)*)));
139+
}
140+
}
141+
142+
```
143+
这是绿色
144+
### T2 support-hashmap
145+
在axstd等组件中,支持collections::HashMap
146+
先读了一下axstd,原本的情况是这样的
147+
```rust
148+
pub use alloc::{boxed, collections, format, string, vec};
149+
```
150+
这里有一个collections,是从alloc模块过来的,那么实际上是标准库里的(我认为就是内核环境不支持标准库的哈希表),所以要替换成一个自己实现的HashMap。
151+
上网查了一下hashbrown是一个常用的哈希表实现(hashbrown n. 薯饼),所以添加了依赖,用这现成的模块。
152+
```toml
153+
axhal = { workspace = true }
154+
hashbrown = { version = "0.14", default-features = false }
155+
```
156+
接下来就是在axstd/src里面添加一个collection.rs然后将对应使用过的函数都用hashbrown进行对应实现就可以了。注意new()一定需要有对应的实现否则报错找不到。
157+
### T3 bump-allocator
158+
这个很简单。
159+
当时内核赛的时候瞎装了一万个分配器到自己的内核中,经过痛苦的阅读代码后了解过buddy、slab、liballocator的分配原理,这个bump分配器简单看一下原理似乎是堆分配器。然后需要实现页分配以及细粒度的字节分配,也就是多层级的分配。那就跟linux的slab&buddy的做法差不多了。
160+
代码中todo写的很明确,每一步需要干什么,不会漏掉隐秘的细节,不像当初写内核一样自己出一堆找不到的bug在后面回来找。
161+
### T4 rename-for-ramfs
162+
ramfs就是一个最基础的文件系统,不需要回写,不需要驱动,基本上意思就是在内存里进行书写,关机后不会存下来,这个rename也不会再下次开机后保存下来。
163+
学习正常的rename,以前从没看过底层的inode操作,都是直接调用`ext_rename()`就完工了,所以这次对照着加抄袭整了个版本。
164+
明确这个操作是在ramfs模块下的就简单了,这是个结点操作,所以要在两个地方添加操作(这是我的做法),一个是impl VfsNodeOps for DirNode,一个是DirNode内部。
165+
```rust
166+
/// Renames a node from `src_name` to `dst_name` in this directory.
167+
pub fn rename_node(&self, src_name: &str, dst_name: &str) -> VfsResult {
168+
let mut children = self.children.write();
169+
170+
let node = children.get(src_name).ok_or(VfsError::NotFound)?.clone();
171+
172+
if children.contains_key(dst_name) {
173+
return Err(VfsError::AlreadyExists);
174+
}
175+
176+
// Remove from old name and insert with new name
177+
children.remove(src_name);
178+
children.insert(dst_name.into(), node);
179+
180+
Ok(())
181+
}
182+
```
183+
在    `fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult {`中可以照着别的函数写法形成模板,最后一步调用上面的rename就好了
184+
## Macro
185+
宏内核部分比较熟悉也简单,就略写了。
186+
### T1 Page-fault
187+
这也是老朋友了,这个提示很明显,在axhal/trap里面,很多异常的处理方法都写在其中了。
188+
### T2 mmap
189+
这更是老朋友,xv6就做过这个实验,rcore也是有。根据posix标准从堆内存找到空闲位置,扩大堆空间。
190+
这里评测环境错误很久没过差点以为是我的问题,所以自己添加了一个`get_brk()`函数,结果又在本地爆了,这个实验似乎就是让我们使用`find_free_area`就可以了,并没有按照posix标准去处理那么多flag,也没有匿名映射。
191+
后面所以我又改成了最简单的版本
192+
```rust
193+
let start_addr = if addr.is_null() {
194+
// Use find_free_area to find a suitable address
195+
let hint = VirtAddr::from(0x10000000usize);
196+
let limit = VirtAddrRange::from_start_size(aspace.base(), aspace.size());
197+
aspace.find_free_area(hint, aligned_length, limit)
198+
.ok_or(LinuxError::ENOMEM)?
199+
} else {
200+
VirtAddr::from(addr as usize)
201+
};
202+
203+
// Map memory in user address space
204+
aspace.map_alloc(start_addr, aligned_length, mapping_flags, true)
205+
.map_err(|_| LinuxError::ENOMEM)?;
206+
```
207+
这属于有点ltp后遗症,写了linux的标准错误号。然后后面其实也处理了fd是-1且不是MAP_ANONYMOUS的情况。
208+
## Hypervisor
209+
这个虚拟化有点超纲了,以前确实没见过这种虚拟机的做法。我想理解成用户进程,这样的话自己有一个cpu对象,用户进程的地址空间也是连续的,但是在内核中不连续。
210+
211+
### T1 simple-hv
212+
这个有两个退出原因(据悉是这样):
213+
IllegalInstruction (非法指令异常)和 LoadGuestPageFault (Guest 页面错误),需要在vm_exit的时候判断这几次错误并处理。实际上操作的方法有点像写cpu,直接对epc等寄存器进行+4这样。这个错误原因估计还要下到trap模块才能判断,就是csr寄存器里会记录错误原因。
214+
为了调试这几种原因,我先添了几句调试输出,没想到其实本来它的打印就是有输出的。
215+
可以读到代码中期望的输出就是这两个寄存器要放正确的值,而这之前就不要有vmexit
216+
```rust
217+
let a0 = ctx.guest_regs.gprs.reg(A0);
218+
let a1 = ctx.guest_regs.gprs.reg(A1);
219+
ax_println!("a0 = {:#x}, a1 = {:#x}", a0, a1);
220+
assert_eq!(a0, 0x6688);
221+
assert_eq!(a1, 0x1234);
222+
ax_println!("Shutdown vm normally!");
223+
ctx.guest_regs.sepc += 4;
224+
return true;
225+
```
226+
所以就在对应的错误处理处改0x6688和0x1234就可以,对应指令sepc+4跳过
227+
228+
### T2 pflash
229+
从上面为止练习题其实就做完了。说来非常惭愧,我参加训练营有点面向做题的学习,从rcore开始都是学习的目标就是做完所有练习题就收工了,觉得解决问题才是做这个训练营的精华。这个练习也是个示例,不用我做自己就是好的。所以后面学起来有点没有动力。
230+
分析了一下运行指令,其实是先编译了u_3_0的内核,然后把内核的bin文件写进了disk.img里,最后make一个虚拟机出来。运行之后里面也是显示了两次ArceOS(虚拟机里运行了内核),然后从guest里面试图去读host
231+
很好看的一点是在make u_3_0的时候直接编译,而运行虚拟机h_2_0的时候可以把log开成info,这样的话就可以看到那句 Starting virtualization...,以及是如何装载虚拟机到虚拟地址的。
232+
内核赛的时候很惊讶,因为听说第一名的Starry Mix可以在里面运行xv6,让我直接震惊了。后面了解到StarryOS就是在arceOS基础上改的,现在我才知道原来就是基于了这样的Hypervisor模式,真的长知识了。

0 commit comments

Comments
 (0)