Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hkp #684

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

hkp #684

Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
322 changes: 322 additions & 0 deletions source/_posts/开源系统训练营第三阶段-贺琨鹏.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
---
title: 开源系统训练营第三阶段-贺琨鹏
date: 2024-12-06 16:06:15
tags:
---
### 课后练习1:`print_with_color`

在`axstd`中的wirte方法中,利用终端控制序列(ANSI 转义码)进行有颜色的输出
```rust
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let color_prefix = b"\x1b[32m";
let color_suffix = b"\x1b[0m";


if let Err(e) = arceos_api::stdio::ax_console_write_bytes(color_prefix) {
return Err(e);
}

let result = arceos_api::stdio::ax_console_write_bytes(buf);
if let Err(e) = result {
return Err(e);
}

if let Err(e) = arceos_api::stdio::ax_console_write_bytes(color_suffix) {
return Err(e);
}

result
}
```


MMIO:将设备寄存器直接映射到内存空间上,并具有独立的地址
`u_3_0`模拟闪存磁盘,启动时自动从文件加载内容到固定的MMIO区域。(对读操作不需要驱动,直接访问)

paging特征决定了是否启动后期重建完成的空间映射


### 课后练习3:实现bump内存分配算法

**引导问题(bootstrap problem)**,也被称为“先有鸡还是先有蛋”的问题。内核在刚启动时,需要分配内存来建立自己的数据结构(例如内存映射、页表等),但此时还不知道系统中有多少物理内存,也没有初始化内存分配器。然而,要调用库函数(如用于解析设备树、初始化驱动等),可能需要动态内存分配。这就导致了一个悖论:**需要分配内存来初始化内存管理,但又需要内存管理来分配内存**。
**练习:在引导阶段实现bump内存分配**
分别为`BaseAllocator`,`ByteAllocator`,`PageAllocator`实现特征即可


实现相关特征即可,注意在页分配的时候进行对齐,字节分配器下的`dealloc`只需对count进行操作即可
```rust
pub struct EarlyAllocator {
start: usize,
end: usize,
b_pos: usize,
p_pos: usize,
count: usize,
}

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


impl BaseAllocator for EarlyAllocator {
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 {
self.end += size;
Ok(())
}
}

impl ByteAllocator for EarlyAllocator {
fn alloc(&mut self, layout: Layout) -> AllocResult<NonNull<u8>> {
let size = layout.size();

if self.b_pos + size > self.p_pos {
return Err(allocator::AllocError::NoMemory);
}

let ptr = self.b_pos;
self.b_pos = self.b_pos + size;
self.count += 1;

unsafe { Ok(NonNull::new_unchecked(ptr as *mut u8)) }
}

fn dealloc(&mut self, _pos: NonNull<u8>, _layout: Layout) {
if self.count > 0 {
self.count -= 1;
}
if self.count == 0 {
self.b_pos = self.start;
}
}

fn total_bytes(&self) -> usize {
self.end - self.start
}

fn used_bytes(&self) -> usize {
self.b_pos - self.start
}

fn available_bytes(&self) -> usize {
self.p_pos - self.b_pos
}
}



impl PageAllocator for EarlyAllocator {
const PAGE_SIZE: usize = 0x1000;

fn alloc_pages(&mut self, num_pages: usize, align_pow2: usize) -> AllocResult<usize> {
let size = num_pages * Self::PAGE_SIZE;

let new_p_pos = (self.p_pos - size) & !(align_pow2 - 1);

if new_p_pos < self.b_pos {
return Err(allocator::AllocError::NoMemory);
}

self.p_pos = new_p_pos;
Ok(self.p_pos)
}

fn dealloc_pages(&mut self, _pos: usize, _num_pages: usize) {
}

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.b_pos) / Self::PAGE_SIZE
}
}

```



**增加`axsync`**
抢占式调度机制
触发优先级高的任务及时获得调用机会,避免个别任务长期不合理占据CPU

支持从磁盘块设备读数据,替换PFlash
管理方式:`static` or `dyn`

块设备驱动Trait:`BlockDriverOps`
### 课后练习4:为shell增加文件操作命令

底层以fatfs为框架实现好了,我们只需调用api即可
```rust
fn do_mv(args: &str) {
let args = args.trim();
if args.is_empty() {
print_err!("mv", "missing operand");
return;
}

let mut paths = args.split_whitespace();
let src = match paths.next() {
Some(p) => p,
None => {
print_err!("mv", "missing source file");
return;
}
};

let dest_dir = match paths.next() {
Some(p) => p,
None => {
print_err!("mv", "missing destination directory");
return;
}
};


let dest_path = format!("{}/{}", dest_dir, src);

if let Err(e) = fs::rename(src, &dest_path) {
print_err!("mv", format!("failed to move {} to {}", src, dest_dir), e);
}
}

fn do_rename(args: &str) {
let args = args.trim();
if args.is_empty() {
print_err!("rename", "missing operand");
return;
}

let mut paths = args.split_whitespace();
let old_name = match paths.next() {
Some(p) => p,
None => {
print_err!("rename", "missing old file name");
return;
}
};

let new_name = match paths.next() {
Some(p) => p,
None => {
print_err!("rename", "missing new file name");
return;
}
};
if let Err(e) = fs::rename(old_name, new_name) {
print_err!("rename", format!("failed to rename {} to {}", old_name, new_name), e);
}
}
```


`ArceOS Unikernel`包括两阶段的地址映射
Boot阶段默认开启1G空间的恒等映射
如果需要支持设备的MMIO区间,通过指定一个`feature- "paging"`来实现重映射

可选的重建映射
1. 为了管理更大范围的地址空间,包括设备的MMIO范围
2. 分类和权限的细粒度控制



## 宏内核

- 增加了用户特权级
- 创建独立的用户地址空间
- 内核运行运行时可以随时加载应用Image投入运行
- 应用与内核界限分明

高端内核空间,低端用户应用空间

---

**宏内核的地址空间管理**

地址空间区域映射后端
1. linear:对应物理地址必须连续
2. alloc:按页映射,可能不连续

多级调用`handle_page_fault`

populate为true时,申请页帧后直接完成映射;为false时仅建立空映射

**如何让Linux的原始应用直接在我们宏内核上直接运行**

实现兼容页面,包含`syscall`,`procfs & sysfs`等伪文件系统,`awareness of aspace`

**ELF格式应用的加载**


>需要注意文件内偏移和预定的虚拟地址空间内的偏移可能不一致,特别是数据段的部分

存零,清零

**应用的用户栈的初始化**
支持系统调用的层次结构

系统调用号与体系结构相关

对Linux常用文件系统的支持

**课后练习:实现mmap系统调用**

## Hypervisor

建立最简化的`Hypervisor
过程
新建地址空间,加载内核镜像
准备上下文
设置两级页表
切换V模式
OS内核只有一条指令:调用`sbi-call`的关机操作。但由于这种操作超出了V模式的权限,导致`VM_EXIT`退出虚拟机,切换回Hypervisor
Hypervisor响应`VM_EXIT`的函数(`vmexit_handler`)检查退出原因和参数,进行处理,由于时请求关机,清理虚拟机后退出

进入虚拟化模式准备的条件
ISA寄存器misa第7位代表Hypervisor扩展的启用/禁止。
进入V模式路径的控制:hstatus第7位SPV记录上一次进入HS特权级前的模式,1代表来自虚拟化模式。
执行sret时,根据SPV决定是返回用户态,还是返回虚拟化模式。
Hypervisor首次启动Guest的内核之前,伪造上下文作准备:
设置Guest的sstatus,让其初始特权级为Supervisor;
设置Guest的sepc为OS启动入口地址VM_ENTRY,VM_ENTRY值就是内核启动的入口地址,
对于Riscv64,其地址是0x8020_0000。


**如何从pflash设备读出数据**
1. 模拟模式:为虚拟机模拟一个`pflash`
2. 透传模式:直接把qumu的pflash透传给虚拟机

**地址映射的支持**
两阶段的地址映射
GVA -> GPA -> HPA

虚拟机时钟中断
为了支持抢占式调度

问题:宿主环境失效问题
通过中断注入