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

add yck's three-stage-blog #651

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
---
title: 2024-秋冬季开源操作系统训练营第三阶段总结-yck
date: 2024-12-02 02:00:28
tags:
- author: AoligeiY
---

# Areos引导机制

- 首先进入`axhal`中平台架构的boot程序的`_start`函数,参数为OpenSBI传入的两个参数,一个是`hartid`用于识别CPU,一个是`dtb_ptr`传入DTB的指针。
- 然后会建立栈用于函数调用,准备页表启动MMU分页机制,后即可进入Rust。

然后进入axruntime初始化应用运行时的环境,即根据feature条件编译各组件框架



# 启用页表机制

SBI配置FW_TEXT_START,BIOS负责把SBI加载到内存的起始位置

0x8000 0000存放SBI,SBI会跳转到以0x8020 0000起始的内核代码,存放.bss、.data、.rodata、.text段。

axhal中的linker_riscv64-qemu-virt.lds指导rust的链接器来实现段布局


一阶段:即上面提到`_start`函数中的初始化MMU,将物理地址空间**恒等映射**,再添偏移量。

二阶段:重建映射,一阶段的映射并**没有添加权限**进行管理。若启用"paging"的feature,首先会调用axmm中的`init_memory_management()`,进行创建内核空间(`new_kernel_aspace()`),将根页表地址写入stap寄存器(`axhal::paging::set_kernel_page_table_root`)。


# 多任务

启用"multitask"的feature,执行`axtask::init_scheduler()`,会进行就绪队列的初始化,以及系统timers的初始化。

- 就绪队列的初始化:初始化系统任务idle、main、gc
- main为主任务,其他任务均为其子任务
- idle,其他任务都阻塞时会执行它
- gc,除main之外的任务退出后,将由gc负责回收清理



## 调度算法

### CFS

"init_vruntime"在os启动调度的时候决定调度顺序,"nice"则是优先级,优先级越大"vruntime"增长越慢,从而达到高优先。

当时间片结束后,当前任务将被为设为可抢占,当前任务释放控制权,加入就绪队列。并且如果外部条件满足抢占,则会将就绪队列中的队首弹出并切换到该任务


# 宏内核

### m_1_0启动

- 首先为应用创建独立的用户地址空间。

`axmm::new_user_aspace`

- 将应用程序代码加载到地址空间。

`load_user_app`

- 初始化用户栈

`init_user_stack`

- spawn创建用户任务,以用户地址空间及应用入口、用户栈为参数

`task::spawn_user_task`

- 当前任务释放CPU,使用户任务运行

`user_task.join`



### 用户空间


页表管理内核地址空间和用户地址空间

内核地址空间只有高端(高地址)存放内核代码数据

用户地址空间高端存放内核,但这部分的页表项设为用户不可见,只有陷入内核态之后才能访问。低端地址存放应用

每个用户地址空间高端的内核均相同,只有存放应用的区域不同




地址空间后端Backend:

Linear:目标物理地址空间已经存在,直接建立映射关系,物理页帧必须连续

Alloc:仅建立空映射,当真正被访问时将会触发缺页异常,然后在缺页响应函数内部完成物理页帧的申请和补齐映射,也就是Lazy方式。物理页帧通常情况下不连续



### ELF格式应用加载


代码段加载无偏移。

数据段加载到虚拟地址空间分为.data、.bss。但.bss在ELF文件中为节省空间紧凑存储不做存放,仅做标记位置和长度。内核直接预留空间并清零



### 缺页异常

当发生缺页异常时,由aspace的`handle_page_fault`来完成对物理页帧的申请与映射

首先检查发生页面错误的虚拟地址 `vaddr` 是否在当前虚拟地址范围 `va_range` 内。

然后找到`vaddr`的区域area,进入area的后端,缺页异常通常由后端Alloc的Lazy策略造成,故进入Alloc分支调用`handle_page_fault_alloc`对页表`remap`完成页表映射



# Hypervisor

虚拟化基于RISCV64的S特权级的H扩展,Host将在HS特权级下进行`VM_ENTRY`以及`VM_EXIT`



S特权模式进行H扩展后,原有的s[xxx]寄存器组作用不变,将新增hs[xxx]和vs[xxx]

- hs[xxx]寄存器组的作用:面向Guest进行路径控制
- vs[xxx]寄存器组的作用:直接操纵Guset域中的VS,为其准备或设置状态



设置hstatus的SPV位(指示特权级模式的来源,1为VS,0为U)SPVP位(指示HS对V模式下地址空间是否由操作权限)



启动Guest之前,设置Guest的sstatus,设置初始特权级为Supervisor

设置sepc为OS启动入口地址VM_ENTRY,地址为0x8020 0000



run_guest切换:保存Host上下文,将Guest上下文(第一次切换为伪造的上下文)载入寄存器组



VM_EXIT返回Host:保存Guest上下文,将Host上下文载入寄存器组



### Guest和Host地址空间

Hypervisor负责基于Host物理地址空间HPA面向Guest映射Guest物理地址空间GPA

Guest会认为GPA是实际的物理地址空间,它基于satp映射内部的GVA虚拟空间


启用RISC64指令集的G扩展:

- 引入vsatp用于第一阶段的页表翻译,即将Guest的虚拟地址空间映射为Guest的物理地址空间
- 引入hgatp用于第二阶段的页表翻译,即将Guest的物理地址空间映射到Host的物理地址空间



### 虚拟机物理地址空间布局


低端区域留给设备空间和DMA

0x8000 0000在Host中是存放SBI的,但是虚拟机没有M模式,是无法访问SBI,所以这部分进行保留

0x8020 0000存放内核

高于内核的地址用作物理内存区



### Hypervisor主逻辑

- 准备VM的资源:VM地址空间和单个vCPU
- 切换到进入Guest的代码
- 响应VM_EXIT各种原因的代码


实现流程:

1. 创建地址空间 为目标虚拟机创建物理地址空间 axmm
2. 建立区域布局 在地址空间中为Guest内核和设备建立区域 axmm
3. 加载内核 把Guest内核Image加载到内核区域 axmm, axfs
4. 初始化vCPU 把vCPU的启动入口设置为Guest内核入口,同时设置EPT页表地址

最后在Host和Guest环境循环切换,支持虚拟机持续运行



VCPU的准备:

- 通过`set_entry(VM_ENTRY)`设置sepc来配置入口地址0x8020 0000
- 通过`set_ept_root`向hgatp设置模式(SV39)和根页表的页帧




### axruntime和axhal时钟中断处理

`axhal::irq::register_handler` 通过`update_timer`在中断向量表中注册对应的中断响应函数,更新时钟。然后`axtask::on_timer_tick()`触发定时任务