0%

2024年开源操作系统训练营总结-winddevil/ArceOS第四节课笔记

回顾与展望

之前就是做了一系列的实验建立了unikernel的框架.

通过unikernel的形式通过增加一些组件来跨过这个边界,来实现一个宏内核.

从Unikernel到宏内核

通过跨越模式边界,弄一个简单的系统调用的操作.

增加用户特权级和特权级上下文切换是变成宏内核的关键.

实验1

rust-analyzer不能正常解析代码的原因,需要在.vscode/settings.json里加入"rust-analyzer.cargo.target": "riscv64gc-unknown-none-elf"

实验命令行:

1
2
3
make payload
./update_disk.sh ./payload/origin/origin
make run A=tour/m_1_0 BLK=y

如果不能执行payload说明代码版本太老了,需要先git fetch origin然后再git merge origin到当前的分支

这里注意如果make payload报错Error,那么一定是因为没有配置好musl的环境变量,注意看一下~/.bashrc,记得更新完~/.bashrc要进行狠狠的source ~/.bashrc

对于./update_disk.sh ./payload/origin/origin的操作对于我这种没操作过的人来说是非常神奇的操作.这一步实际上是把disk.img挂载在linux的文件系统里,然后在直接用linux的指令直接往里边拷贝应用文件的数据.

然后make run A=tour/m_1_0 BLK=y就和上一节课的实验一样了.

跑出来的结果是:

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
OpenSBI v0.9
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|

Platform Name : riscv-virtio,qemu
Platform Features : timer,mfdeleg
Platform HART Count : 1
Firmware Base : 0x80000000
Firmware Size : 100 KB
Runtime SBI Version : 0.2

Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000080000000-0x000000008001ffff ()
Domain0 Region01 : 0x0000000000000000-0xffffffffffffffff (R,W,X)
Domain0 Next Address : 0x0000000080200000
Domain0 Next Arg1 : 0x0000000087000000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes

Boot HART ID : 0
Boot HART Domain : root
Boot HART ISA : rv64imafdcsu
Boot HART Features : scounteren,mcounteren,time
Boot HART PMP Count : 16
Boot HART PMP Granularity : 4
Boot HART PMP Address Bits: 54
Boot HART MHPM Count : 0
Boot HART MHPM Count : 0
Boot HART MIDELEG : 0x0000000000000222
Boot HART MEDELEG : 0x000000000000b109

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

[ 21.794824 0 fatfs::dir:139] Is a directory
[ 22.065035 0 fatfs::dir:139] Is a directory
[ 22.359963 0 fatfs::dir:139] Is a directory
[ 22.490439 0 fatfs::dir:139] Is a directory
app: /sbin/origin
paddr: PA:0x80642000
Mapping user stack: VA:0x3fffff0000 -> VA:0x4000000000
New user address space: AddrSpace {
va_range: VA:0x0..VA:0x4000000000,
page_table_root: PA:0x80641000,
}
Enter user space: entry=0x1000, ustack=0x4000000000, kstack=VA:0xffffffc080697010
handle_syscall ...
[SYS_EXIT]: process is exiting ..
monolithic kernel exit [Some(0)] normally!

让我们看一下orgin的app内容:

1
2
3
4
5
6
7
8
9
10
#[no_mangle]
unsafe extern "C" fn _start() -> ! {
core::arch::asm!(
"addi sp, sp, -4",
"sw a0, (sp)",
"li a7, 93",
"ecall",
options(noreturn)
)
}

很容易懂的,就是调用了第93syscall.

课后练习

主要是要理解AddrSpacemap_alloc的时候的populating选项.

根据在rCore中学到的经验,去查看源码,我们的结构是这样的.

|800

就是在创建MemoryArea的时候要传入一个泛型Backend.

应该就是和这边页的懒加载有关的内容.

调用到最后调用的是modules/axmm/src/backend/alloc.rs这个文件里的map_alloc,因为层层抽象,这里各个参数都还原成了最开始tour/m_1_0/src/main.rs里的变量名称.

|800

然后关键代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if populate {
// allocate all possible physical frames for populated mapping.
for addr in PageIter4K::new(start, start + size).unwrap() {
if let Some(frame) = alloc_frame(true) {
if let Ok(tlb) = pt.map(addr, frame, PageSize::Size4K, flags) {
tlb.ignore(); // TLB flush on map is unnecessary, as there are no outdated mappings.
} else {
return false;
}
}
}
true
} else {
// Map to a empty entry for on-demand mapping.
let flags = MappingFlags::empty();
pt.map_region(start, |_| 0.into(), size, flags, false, false)
.map(|tlb| tlb.ignore())
.is_ok()
}

这里假如我们的poplulate是选定的true,那么就会立即根据4k一个大小的frame进行内存申请,然后把这个虚拟地址和刚刚申请到的framepage_table中映射起来.

但是如果我们选定populatefalse,那么直接把虚拟地址和0这个错误的物理地址映射起来.

那么这时候实际上就需要我们在访问到这个物理地址的时候,再进行物理页申请.

那么在访问到这个地址的时候会发生缺页异常.

这时候我们运行一下应用:

1
2
3
make payload
./update_disk.sh payload/origin/origin
make run A=tour/m_1_0/ BLK=y

这是对应的log:

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
OpenSBI v0.9
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|

Platform Name : riscv-virtio,qemu
Platform Features : timer,mfdeleg
Platform HART Count : 1
Firmware Base : 0x80000000
Firmware Size : 100 KB
Runtime SBI Version : 0.2

Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000080000000-0x000000008001ffff ()
Domain0 Region01 : 0x0000000000000000-0xffffffffffffffff (R,W,X)
Domain0 Next Address : 0x0000000080200000
Domain0 Next Arg1 : 0x0000000087000000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes

Boot HART ID : 0
Boot HART Domain : root
Boot HART ISA : rv64imafdcsu
Boot HART Features : scounteren,mcounteren,time
Boot HART PMP Count : 16
Boot HART PMP Granularity : 4
Boot HART PMP Address Bits: 54
Boot HART MHPM Count : 0
Boot HART MHPM Count : 0
Boot HART MIDELEG : 0x0000000000000222
Boot HART MEDELEG : 0x000000000000b109

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

[ 21.690418 0 fatfs::dir:139] Is a directory
[ 21.963457 0 fatfs::dir:139] Is a directory
[ 22.252957 0 fatfs::dir:139] Is a directory
[ 22.383790 0 fatfs::dir:139] Is a directory
app: /sbin/origin
paddr: PA:0x80642000
Mapping user stack: VA:0x3fffff0000 -> VA:0x4000000000
New user address space: AddrSpace {
va_range: VA:0x0..VA:0x4000000000,
page_table_root: PA:0x80641000,
}
Enter user space: entry=0x1000, ustack=0x4000000000, kstack=VA:0xffffffc080687010
[ 23.235085 0:4 axhal::arch::riscv::trap:24] No registered handler for trap PAGE_FAULT
[ 23.319751 0:4 axruntime::lang_items:5] panicked at modules/axhal/src/arch/riscv/trap.rs:25:9:
Unhandled User Page Fault @ 0x1002, fault_vaddr=VA:0x3ffffffffc (WRITE | USER):
TrapFrame {
regs: GeneralRegisters {
ra: 0x0,
sp: 0x3ffffffffc,
gp: 0x0,
tp: 0x0,
t0: 0x0,
t1: 0x0,
t2: 0x0,
s0: 0x0,
s1: 0x0,
a0: 0x0,
a1: 0x0,
a2: 0x0,
a3: 0x0,
a4: 0x0,
a5: 0x0,
a6: 0x0,
a7: 0x0,
s2: 0x0,
s3: 0x0,
s4: 0x0,
s5: 0x0,
s6: 0x0,
s7: 0x0,
s8: 0x0,
s9: 0x0,
s10: 0x0,
s11: 0x0,
t3: 0x0,
t4: 0x0,
t5: 0x0,
t6: 0x0,
},
sepc: 0x1002,
sstatus: 0x40020,
}

实现方法tour/m_2_0里的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#[register_trap_handler(PAGE_FAULT)]
fn handle_page_fault(vaddr: VirtAddr, access_flags: MappingFlags, is_user: bool) -> bool {
if is_user {
if !axtask::current()
.task_ext()
.aspace
.lock()
.handle_page_fault(vaddr, access_flags)
{
ax_println!("{}: segmentation fault, exit!", axtask::current().id_name());
axtask::exit(-1);
} else {
ax_println!("{}: handle page fault OK!", axtask::current().id_name());
}
true
} else {
false
}
}

这里主要是调用了aspace也即当前任务地址空间中处理缺页故障的方法.

就像我们之前在上一节分析到的Backendmap方法一样,还是调用了Backendremap方法.

就是当即分配一个frame,然后把当前出问题的va虚拟地址重新映射到frame.