diff --git "a/source/_posts/\345\274\200\346\272\220\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265-\350\264\272\347\220\250\351\271\217.md" "b/source/_posts/\345\274\200\346\272\220\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265-\350\264\272\347\220\250\351\271\217.md" new file mode 100644 index 00000000000..c45ff9b7765 --- /dev/null +++ "b/source/_posts/\345\274\200\346\272\220\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265-\350\264\272\347\220\250\351\271\217.md" @@ -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 { + 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> { + 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, _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 { + 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 + +虚拟机时钟中断 +为了支持抢占式调度 + +问题:宿主环境失效问题 +通过中断注入 + + + + +