0%

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

Read more »

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);

  • 要求:
  1. 修改一个组件的实现
  2. 执行make run A=exercises/print_with_color
  • 预期:字符串输出带颜色。(具体颜色不做要求)

  • 提示:在不同层次的组件上修改,影响的输出范围不同。例如,修改axstd可能只影响println!的输出;修改axhal则可能一并影响ArceOS启动信息的颜色。

  • 本次修改主要集中在 println! 宏中,修改后的代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
     macro_rules! println {
    () => { $crate::print!("\n") };
    ($($arg:tt)*) => {
    - $crate::io::__print_impl(format_args!("{}\n", format_args!($($arg)*)));
    + // $crate::io::__print_impl(format_args!("{}\n", format_args!($($arg)*)));
    + $crate::io::__print_impl(format_args!("\u{1B}[{}m{}\u{1B}[m\n", 32, format_args!($($arg)*)));
    }
    }

[support_hashmap]: 支持HashMap类型。

  • 预备:执行make run A=exercises/support_hashmap
  • 要求:

    1. 在axstd等组件中,支持collections::HashMap
    2. 再次执行make run A=exercises/support_hashmap
  • 提示:

    1. 报错的std其实是axstd,测试用例main.rs中有”extern crate axstd as std;”
    2. 在axhal中给出了一个随机数产生函数random(),可以基于它为HashMap提高随机数支持。
  • 实现思路

HashMap 使用一个 Vec<Option<(K, V)>> 来存储键值对,每个桶(即 Option<(K, V)>)存储一个键值对,空桶为 None
custom_hash 函数用于生成键的哈希值,这里采用简单的字节移位和异或方式,返回一个 u64 类型的哈希值
iter 方法返回一个自定义的迭代器 HashMapIterator,该迭代器遍历哈希表中的所有已存储的键值对
通过线性探测法处理哈希冲突,即当发生冲突时,依次检查下一个桶直到找到空桶或找到匹配的键。

  • 测试结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
           d8888                            .d88888b.   .d8888b.
    d88888 d88P" "Y88b d88P Y88b
    d88P888 888 888 Y88b.
    d88P 888 888d888 .d8888b .d88b. 888 888 "Y888b.
    d88P 888 888P" d88P" d8P Y8b 888 888 "Y88b.
    d88P 888 888 888 88888888 888 888 "888
    d8888888888 888 Y88b. Y8b. Y88b. .d88P Y88b d88P
    d88P 888 888 "Y8888P "Y8888 "Y88888P" "Y8888P"

    arch = riscv64
    platform = riscv64-qemu-virt
    target = riscv64gc-unknown-none-elf
    smp = 1
    build_mode = release
    log_level = warn

    Running memory tests...
    test_hashmap() OK!
    Memory tests run OK!

其他实验由于忙于项目,暂时未做,后面有时间再补上其他的实验。

之前总结的一个starry的实现流程也记录这里,方便以后实验查阅:
Starry启动流程

进入第三阶段当然很高兴,但时间上实在太紧,我一开始的心态是按看懂源代码中的每一步来走的,但目前只是看完了所有相关的 makefile,以及看了部分 PPT 中强调部分的源代码。实验的话也是完成了第一周的内容以及 Hypervisor 的实验一。

arceos流程小结

总体执行流程:axhal的boot.rs中先引导,用于初始化内核栈,设置初始的页表,这里映射了两个 1G 的大页,分别是低地址恒等映射以及高地址的一个映射。随后要调用 axruntime 的 rust_main 来进一步完善运行时,这里会涉及到 axfeat 里面用到的 feature 条件编译并初始化一些东西,比如启用了 paging 这个 feature 就会重新映射页表这样。调用 rust_main 的时候加了一个偏移,偏移的大小其实就是高地址映射的偏移这样。随后 axruntime 做好运行时之后,调用 main 函数。随后调用结束函数,结束整个流程。

课后练习1:支持带颜色的打印输出。

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

关于转义字符的具体解析的话:颜色定义的部分可以参考 console_codes(4) — Linux manual pageECMA-48 Select Graphic Rendition 部分。在 makefile 中的 run_cmd 函数用于辅助输出同时会以颜色输出命令,参数 1 控制颜色为白色,参数 2 控制为灰色,随后执行命令。

颜色控制部分关键如下

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
param      result
0 reset all attributes to their defaults
1 set bold
2 set half-bright (simulated with color on a color display)
3 set italic (since Linux 2.6.22; simulated with color on a color display)
4 set underscore (simulated with color on a color display) (the colors
used to simulate dim or underline are set using ESC ] ...)
5 set blink
7 set reverse video
10 reset selected mapping, display control flag, and toggle meta flag
(ECMA-48 says "primary font").
11 select null mapping, set display control flag, reset toggle meta flag
(ECMA-48 says "first alternate font").
12 select null mapping, set display control flag, set toggle meta flag
(ECMA-48 says "second alternate font"). The toggle meta flag causes the
high bit of a byte to be toggled before the mapping table translation is
done.
21 set underline; before Linux 4.17, this value set normal intensity (as is
done in many other terminals)
22 set normal intensity
23 italic off (since Linux 2.6.22)
24 underline off
25 blink off
27 reverse video off
30 set black foreground
31 set red foreground
32 set green foreground
33 set brown foreground
34 set blue foreground
35 set magenta foreground
36 set cyan foreground
37 set white foreground
38 256/24-bit foreground color follows, shoehorned into 16 basic colors
(before Linux 3.16: set underscore on, set default foreground color)
39 set default foreground color (before Linux 3.16: set underscore off, set
default foreground color)
40 set black background
41 set red background
42 set green background
43 set brown background
44 set blue background
45 set magenta background
46 set cyan background
47 set white background
48 256/24-bit background color follows, shoehorned into 8 basic colors
49 set default background color
90..97 set foreground to bright versions of 30..37
100..107 set background, same as 40..47 (bright not supported)

Commands 38 and 48 require further arguments:
;5;x 256 color: values 0..15 are IBGR (black, red, green,
... white), 16..231 a 6x6x6 color cube, 232..255 a
grayscale ramp
;2;r;g;b 24-bit color, r/g/b components are in the range 0..255

ECMA-48 SGR 序列以 ESC[ parameters m 的格式控制显示的属性。parameters 用分号隔开,\033[92;1m 为例,033 为八进制,表示 ESC,92 和 1分别是 param,参考上文的 ECMA-48 Select Graphic Rendition 92 表示绿色明亮前景色。1 表示加粗,0 表示重置属性。

课后练习2:支持 HashMap

ax_api 作为下层 module 的抽象,随后 axstd 作为我们自己定义的标准库供用户程序调用。

我的想法其实也是直接看 std 的 HashMap 怎么实现,然后改一改,做到最小的满足测试案例中的那些操作。然后慢慢改一改,后面看到官方也用了 hashbrown 作为实现,然后我就改了改,把随机数用到 hashbrown 这边大概。然后就好了。

课后练习3:实现 alt_alloc

这个分配器应当是用作早期boot之后系统初始化阶段的内存分配器,所以实现上相对简单,每次分配都向后增长。dealloc的时候尝试减掉已经alloc的空间大小,如果为0,那就重新开始从头分配这样。

挑战题目:实现特定分配算法

这个算法的话,还是要感谢万能的群友,我一开始想的也是在TLSF基础上改一改这样。后面发现有取巧的地方,主要也是循环一直进行,奇数项的内容永不释放,所以就取巧了,会记录每次奇数次申请的空间,下次申请如果比上次的小,那就返回该地址。如果比上次大,那就重新申请,释放上次的内容这样。

达到的轮次是65536应该是,也是一个非常 unsigned int 的值。

思路详细描述:由于每次申请的空间大小会加1,相比上一次。例如,delta 为1,申请的空间依次是32 + 1, 32 * 2 + 1, 。delta 为 2,申请的空间依次是32 +2, 32*2 +2,。所以,第一次循环会反复替换掉分配器中的记录。

但是,从第二次开始,每次当 index 为 13 的时候,会替换上一次的内容。因为上一次的 13 比这次的 13 申请的空间小 1。但是呢,14 不是最后一次循环吗,因为 14 是偶数所以不管。奇数就很神奇,每次都是到 index 为 13 的时候重新申请一次空间。其余时候,其实大家返回的地址都是一样的。嘿嘿。而且,每次替换的时候都会把上次的给释放了,哎,就很神奇。

首先主体部分使用特定内存分配器 tlsf。包括申请,释放等操作。

其次,定制 alloc 函数。用 indicator 记录此时调用 alloc_pass 函数的 delta值。用 index 记录 alloc_pass 函数中的循环次数。

alloc 的时候需要谨慎计算,2的多少次方啊,当index = 14 的时候,重置为 0,然后indiactor需要加 1 啊,之类的。

这里要注意一点,源码里,分配器有个special变量,为bool类型。这里用它的主要原因在于有个很神奇的点:当indiactor为32, index = 1的时候,items需要申请 0x60 的空间,而恰好,下次申请向量空间也是 0x60 的大小。就,重复了。嗯。所以,作为单独判断条件,然后重置,第一次出现先不管,然后继续。然后,bug解决。

Hypervisor课后练习1

hypervisior流程分析:这里初始化阶段我们的hyper会布置好一个现场类似我们在宏内核中设置好sstatus寄存器那样,随后通过sret到GUEST中执行,GUEST中遇到无法处理的情况又会陷入Hypervisior中,通过对不同类型的陷入执行不同的代码,处理好用户的错误设置寄存器等操作,随后返回GUEST继续执行。

这里的第一个实验比较简单:只需要在处理错误函数中设置寄存器的值,然后设置sepc += 4,让GUEST去执行出错指令的下一条指令即可。

Hypervisior课后练习2

这里会涉及到虚拟化里的地址二阶段映射。主要流程:GUEST中的虚拟地址先通过 VSATP 翻译,通过 GVA 到 GPA,然后通过 HGATP 把GPA翻译到 HPA 这样。

课后练习尚未完成,嗯。

Hypervisior课后练习3

这里涉及 GUEST 的中断。主要涉及时钟中断,一种实现方式是当 GUEST 配置 RTC 的时候会陷入 Hyper,由 Hyper 来设置时钟,当时钟触发的时候,Hyper 会触发 GUEST 的时钟中断位这样。Hyper在设置完之后应该是会关中断的,待下次GUEST设置中断的时候再打开,这样保持一致。

课后练习尚未完成,嗯。

总结

这里总结的话,三阶段实在不完美,时间上太赶了,然后各种东西都想弄明白细节,这样就很来不及。原本还准备看RISCV的指令集虚拟化扩展这样,也只看了一小部分,内容上其实不算多,但要补的内容比较多,就比较费劲。

rCore第二阶段总结报告

第二阶段回顾

​ 本以为第一阶段后将是一马平川,却不曾想第二阶段竟是噩梦的开始。本以为第二阶段也和第一阶段一样,只需断断续续抽出一周的零碎时间即可轻易完成,但只有亲身尝试过才会知道这种想法多么的错误,最后几乎是推掉了所有的学业任务,把一整周都投入在了rCore上才勉勉强强卡ddl写完了所有lab。

​ 第零章和到第二章可以说是第二阶段的环境配置和任务理解阶段,由于上一阶段仅仅是在mac电脑上轻松写代码,故在一开始的环境配置上还是耗费了小两天,在此过程中第一次接触到了windows的wsl,然后一步一步在实验指导书的指导下搭建了 vscode + wsl + ubuntu20.02 这样的开发环境,在阅读前面几章的文档内容后也对所学的知识、实验的相关方向有了大致的了解,并能够初步运行起来自己的rust-os。在第一章的学习过程中,我理解了应用程序的执行环境,对 应用程序、函数调用、标准库、系统调用、操作系统、指令集和硬件平台 这些概念又有了新的认识,有种学习汇编语言的感觉,另外也接触到了 裸机编程、目标三元组 这些比较新的东西。但也仅停留在有印象的层面,没能深入理解其中奥秘;第二章的内容比较全面,我了解到了应用程序执行过程和操作系统的特权级切换机制,了解了编写risc-v的链接脚本来控制内存布局、内嵌汇编、异常处理、上下文切换的实现,这些操作在代码中的实现,更是让我操作系统的课上所学映入了现实,第二章的批处理调度系统,也是一个很好的帮助我入门并理解整体的代码架构的案例。

​ 后面几章就没有那么多时间细细咀嚼了,通常都是扫两眼知识然后直奔作业,除了文件系统外,其他由于都在课上学过,因此在gpt的帮助下没有被卡住太久的时间。也很感谢用心设计该实验教程的学长/学姐,不仅让我们快速入门了os,还让我们快速了解了如何系统开发一个os的各个功能。

​ 在lab1实验就卡了很久——不会仔细品读知识中所蕴含的代码实现细节,也不会投机取巧地去看看测试用例和作业所给的提示,而仅仅是闭门造车,最终卡了许久才在群聊天记录中找到了问题的关键所在,也就是时间的记录问题,当然在写的过程中也遇到诸如生命周期等等问题,让语言基础不太牢固的我举步维艰。

​ lab2是虚拟存储多级页表实验,虽然在课上老听说页表的神奇和重要性,但从没有像本次实验这样深刻地接触过操作系统中的页表,最初做的时候由于考虑的太多又无法实现便导致一度停滞不前,后来在发呆的时候又仔细重新阅读了一下题目的要求,发现需要实现的东西都还挺简单的,而且测试用例也给的非常宽松因此很快的做完了,并没有想象中那么复杂。

​ lab3是有关进程的简单创建和调度,实现上并不困难,主要难度还是在于代码结构发生了较大变化,比如本来 task_manager 做的事情现在换成了 process 在做。

​ lab4是最痛苦的一次实验,在把ch5的代码移植过来后发现仅需要过三个测试文件即可便觉得它很简单,但真正想要得心应手地写出来需要对文件系统和代码实现有详细的理解,最终还是在听了ddl前的讲解才恍然大悟:linkat和create略像前一章所提到的fork和spawn,否则将根本无从下手然后白白浪费时间并放弃之前的一切努力。在给 inode 加上 inode_id 相关的方法后很快完成了这次实验。

​ lab5的内容相对比较熟悉,也是课上自认为十分简单的死锁检测问题,但代码框架阅读起来难度较大,最终将前面的 sys_get_time 搬过来后跟着题目的提示实现了资源分配图和死锁检测系统调用的实现

第二阶段收获

​ 这次的第二阶段学习就像一场不断挑战极限的马拉松,让我既感到疲惫不堪,又充满了意想不到的收获。在本阶段的学习中,我获得了关于操作系统核心机制的更深入理解,体验到了真实操作系统开发的复杂性和细致入微的编程要求,特别是在进程管理、内存管理、以及文件系统的设计上有了全新的认识。

​ 首先,通过批处理调度系统的实现,我理解了特权级切换、上下文保存与恢复等机制。这种直接操控硬件资源的编程体验帮助我更好地理解了操作系统在管理硬件与应用间的角色。其次,在实现文件系统的过程中,我也是初次了解了文件路径解析、inode 管理和文件描述符等底层概念,这不仅让我理解了文件系统的设计精髓,也磨炼了抽象和模块化编程的能力。

​ 同时,在死锁检测的实验中,资源分配图的设计让我更深刻地理解了进程间的依赖关系和并发控制策略,学习知识的最好方式绝对是动手实践。

​ 总的来说,这一阶段的收获不止是技术上的进步,更让我体会到了系统开发的全局性思维和精确性要求。这不仅提升了我的编程能力,也让我更有信心面对后续操作系统开发中的复杂问题。

rCore第一阶段总结报告

参加训练营的契机

​ 本人是大三信息安全专业学生,起因是在学期初老师在班级群中转发了本次训练营的相关推送,正好本学期在修操作系统专业课,再加上在过去的两年实际上没有学过过硬的技术,就希望能通过本次训练营收获到许多实际项目开发相关的东西,希望能做到技多不压身。

第一阶段回顾

​ 本阶段的主要任务是根据 rust语言官方圣经 来学习rust语法,并在简单的知识训练、算法实现中掌握基本的编程逻辑和rust语言的独特思维。在学习过程中最大的感受就是,这是一门非常“麻烦”(或者说拗口)、非常底层但却非常“安全”的语言。

​ 在进一步地阅读文档和代码实操的过程中,我也深深地体会到了rust语言的魅力,很多在其他语言中不需要关注的问题,都是rust使用者的家常便饭,例如所有权与借用、生命周期、模式匹配、并发和线程、内存管理、特征泛型、智能指针、宏等等全新的概念,在学习的过程中,也能十分自然地和以往所写过的语言进行对比,也能十分自然地联想到自己所学的专业知识,整个学习过程是痛苦而充满趣味和成就感的。

​ 最终也是断断续续地在国庆后完成了110分的编程题目,虽然很多地方都有囫囵吞枣、只求大概的不好处理,但还算是初步打开了rust语言的大门。

阶段目标与晋级条件

  • 从零开始构建操作系统的各个模块,不断完善操作系统核心功能

  • 完成5道rCore操作系统大实验编程题,深入领会OS重要概念,掌握必备技能

  • 排行榜积分达到500分,方可进入项目阶段学习,参与团队合作完成关键任务

阶段个人总结

从本阶段开始本人感到明显的学习吃力,各种新概念在教学过程中如泉涌般浮现在脑海,难以在短时间内完全消化完毕。 故专门抽出一整个周末以及课余时间反复研读与理解教学文档,深入洞悉各种新概念的内涵,在彻底理解相关知识点后再结合视频直播中老师的讲解理解每一行代码的实现逻辑(5W1H原则:‌Why(何因)‌、‌What(何事)‌、‌Where(何地)‌、‌When(何时)‌、‌Who(何物)‌、‌How(何法))。在确保完全弄懂文档知识点与代码的实现逻辑之后,方才着手完成每个实验的习题。

通过本阶段的学习,我也认识到学习操作系统相较于其他编程实践项目需要投入更多的精力,对于陌生的技术栈需要花更多的心思去理解其技术细节与应用方式,不能向以前一样盲目自信而心不在焉地学习。同时这几次实验也建立起我实战大型编程项目的经验,为以后参与更多更繁杂的编程项目打下基础。

阶段目标与晋级条件

  • Rust编程语言为学习操作系统设计与实现打下坚实基础

  • 通过完成100道Rustling编程题,强化训练Rust编程技能

  • 该阶段排行榜达到满分可晋级,进入专业阶段学习

阶段个人总结

Rust 语言是参加本训练营必须掌握的一门语言,因为本训练营的所有项目均基于本语言编写。由于本人已学习过 Rust 编程语言,故本阶段无太大压力,跟着晋级要求完成 rustlings习题后即达成目标。

而重点是后续专业阶段 rCore 操作系统的学习与代码理解,因操作系统相关知识早已忘记(虽然曾跟着网络教程阅读过 Linux 内核源码,但年代久远早已忘记细节),故需要一定时间重拾相关知识,并跟着实验在旧知识基础上提升自己的理解水平。

附:本人学习 Rust 语言的主要参考视频教程

第一阶段 - rust基础与算法

由于我有rust基础,没有遇到什么困难,但还是第一次完整地做一次rustlings,有一定收获

第二阶段 - 专业阶段

实验环境配置

rcore开发的环境配置比较繁琐,而我恰好比较熟悉nix,因此我用nix定义的一个完美兼容rcore的开发环境。由于不需要 rustup , 必须在 os/Makefile 中禁用 rust 环境的检测. flake.nix 如下:

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
{
description = "rCore dev flake";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
oldnixpkgs.url = "github:NixOS/nixpkgs/7cf5ccf1cdb2ba5f08f0ac29fc3d04b0b59a07e4";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};

outputs = {
nixpkgs,
oldnixpkgs,
flake-utils,
rust-overlay,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
inherit system;
overlays = [(import rust-overlay)];
};
oldpkgs = import oldnixpkgs {
inherit system;
};
in {
devShells.default = pkgs.mkShell {
packages = with pkgs;
[
(rust-bin.nightly."2024-05-02".minimal.override {
extensions = [
"rust-src"
"llvm-tools"
"rustfmt"
"rust-analyzer"
"rust-docs"
"clippy"
];
targets = ["riscv64gc-unknown-none-elf"];
})
cargo-binutils
python3
gdb
tmux
]
++ [oldpkgs.qemu];

# 进入环境后显示rust和qemu版本
shellHook = ''
rustc --version
cargo --version
qemu-system-riscv64 --version
qemu-riscv64 --version
'';
};
});
}

这份 flake.nix 也已经分享到官方问答论坛

第一/二章

阅读这两章需要先了解riscv,特别是特权级相关设计。

主要参考:

第三章:多道程序与分时多任务

学习了系统调用,陷入等知识,上下文切换过程中通用寄存器和CSR的使用加深了我对riscv特权级设计的理解。

本章练习较为简单。

第四章:地址空间

SV39的设计又引入了若干相关的寄存器,如satp, pmp csr。查阅riscv manul以加深理解。

本章练习中,为了处理请求映射已经被映射的页的错误,我使用了Result错误传递,无法想象如果不使用Result?语法糖我的代码会多么丑陋。然而很奇怪,整个rcore中极少使用Result

第五章:进程及进程管理

本章内容比较轻松,完善了TCB的设计并实现fork()exec()系统调用。

本章练习也比较简单。

第六章:文件系统

easy-fs is NOT easy!层层抽象几乎让我晕头转向!

尽管如此,easy-fs囊括了superblock、索引节点、blockcache等现代文件系统中的基础概念,似乎不能再精简了。

link和unlink操作主要是查找inode并创建/删除目录项。在inode_block里创建与删除目录项无非是一些线性序列的操作,但由于没有封装成&[DirEntry],需要手动操作,比较费劲。将来有空我会尝试改进一下。

第七章:进程间通信

本章内容较少,但进程间通信是个大话题,还需拓展学习。

第八章:并发

学习了多线程的同步与互斥。

练习:学习了死锁检测算法

第一阶段

好几年前就被 Rust 的性能和并发功能吸引,无奈日常没有运用,只能学而时习之。不过 Rusting 还是没有什么难度的,多看看文档,轻松拿下不是问题。

第二阶段

第二阶段就需要了解操作系统原理了,各类教科书都停留在原理阶段,学完了也不知道自己学会了没有。
lab1 比较简单,原理是需要了解特权级和系统调用,实践上懂得在哪里记录数据和返回即可。
lab2 难度提升不小,原理上需要了解页表机制,多级页表极其抽象,算是一个难点。实践上要注意内核态拿到的是用户态的虚拟地址,要注意转换而不是直接访问。
lab3 难度不大,原理上就是进程那些事,不算难。实践上因为基础代码都已经提供了,依样画葫芦很容易就能通过。
lab4 感觉也是不算难的,原理上就是各种数据结构的管理。实践上因为现在的文件系统比较简易,很多可以不过多考虑直接取巧实现。最大的问题是我的机子性能太差,跑全部测试必定超时,我误以为是自己代码问题。
lab5 感觉是个巨坑,原理上实验说明写的模模糊糊,完全看不懂。实践上也是感觉测例不完善,虽然顺利通过了,但是没把握实现是正确的。