|
|
|
@ -1,4 +1,8 @@
|
|
|
|
|
use crate::config::{kernel_stack_position, TRAP_CONTEXT};
|
|
|
|
|
use crate::mm::address::{PhysPageNum, VirtAddr};
|
|
|
|
|
use crate::mm::memory_set::{KERNEL_SPACE, MapPermission, MemorySet};
|
|
|
|
|
use crate::task::context::{TaskContext};
|
|
|
|
|
use crate::trap::{trap_handler, TrapContext};
|
|
|
|
|
|
|
|
|
|
// TCB的字段, 用来保存任务的状态
|
|
|
|
|
#[derive(Copy, Clone, PartialEq)]
|
|
|
|
@ -11,10 +15,94 @@ pub enum TaskStatus {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 一个任务的主体, 用来保存或者控制一个任务所有需要的东西
|
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
|
pub struct TaskControlBlock {
|
|
|
|
|
pub user_time: usize, // 用户态程序用的时间
|
|
|
|
|
pub kernel_time: usize, // 内核态程序所用的时间
|
|
|
|
|
pub task_status: TaskStatus,
|
|
|
|
|
pub task_cx: TaskContext,
|
|
|
|
|
pub memory_set: MemorySet, // tcb他自己的地址空间
|
|
|
|
|
pub trap_cx_ppn: PhysPageNum, // tcb访问 trap context所在的真实的物理页, 它对应逻辑页的次高页
|
|
|
|
|
pub base_size: usize, // 应用地址空间中从0x0开始到用户栈结束一共包含多少字节, 就是用户数据有多大
|
|
|
|
|
pub heap_bottom: usize, // 堆底
|
|
|
|
|
pub program_brk: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TaskControlBlock {
|
|
|
|
|
pub fn from(elf_data: &[u8], app_id: usize) -> Self {
|
|
|
|
|
// memory_set with elf program headers/trampoline/trap context/user stack
|
|
|
|
|
// 返回 内存空间, 用户栈的栈底和当前栈顶, 和入口地址
|
|
|
|
|
let (memory_set, user_sp, entry_point) = MemorySet::from_elf(elf_data);
|
|
|
|
|
|
|
|
|
|
// 在当前的地址空间中, 找到 trap_context 逻辑页 所在的真实物理页, 他逻辑地址在次高页, 真实物理地址是我们在 from_elf 申请映射的未知地址
|
|
|
|
|
// 这个真实的物理地址, 我们后续再陷入的时候会用到, 进行强写数据
|
|
|
|
|
let trap_cx_ppn = memory_set
|
|
|
|
|
.page_table
|
|
|
|
|
.get_pte(VirtAddr::from(TRAP_CONTEXT).into()) // 虚拟地址次高页, 所在对应的物理内存的pte,
|
|
|
|
|
.unwrap()
|
|
|
|
|
.ppn(); // pte 内的ppn, 得到实际 虚拟内存次高页 所在的物理页号
|
|
|
|
|
|
|
|
|
|
// 初始化任务状态
|
|
|
|
|
let task_status = TaskStatus::Ready;
|
|
|
|
|
// map a kernel-stack in kernel space
|
|
|
|
|
// 根据 app_id 创建受保护的用户的任务内核栈, 并得到栈的起始地址和结束地址, 这个在次高页的下面的 某个位置
|
|
|
|
|
// kernel_stack_bottom = kernel_stack_top - PAGESIZE * 2
|
|
|
|
|
// kernel_stack_top 是当前栈的栈顶, 同时也是现在没有push操作也是栈底
|
|
|
|
|
// 这里都是虚拟地址
|
|
|
|
|
let (kernel_stack_bottom, kernel_stack_top) = kernel_stack_position(app_id);
|
|
|
|
|
|
|
|
|
|
// 把用户的内核栈逻辑段, 映射并创建出来实际的物理页帧, 到内核地址空间, 供内核态可以进行寻址找到用户应用的内核栈,
|
|
|
|
|
// 这里使用虚拟映射 而不是恒等映射, 是因为需要动态调整?
|
|
|
|
|
// 这个只有在内核会用到
|
|
|
|
|
KERNEL_SPACE.exclusive_access().insert_framed_area(
|
|
|
|
|
kernel_stack_bottom.into(),
|
|
|
|
|
kernel_stack_top.into(),
|
|
|
|
|
MapPermission::R | MapPermission::W,
|
|
|
|
|
);
|
|
|
|
|
// 构建 任务控制块
|
|
|
|
|
let task_control_block = Self {
|
|
|
|
|
user_time: 0,
|
|
|
|
|
kernel_time: 0,
|
|
|
|
|
task_status,
|
|
|
|
|
task_cx: TaskContext::goto_trap_return(kernel_stack_top), // 根据内核栈构造任务切换上下文, 并把 switch任务切换的 ra 设置为 trap_return的函数地址
|
|
|
|
|
memory_set, // 新增, 当前任务的地址空间
|
|
|
|
|
trap_cx_ppn, // 新增 逻辑次高页的trap context, 对应的这个是真实的物理页, 我们这里保存一份, 省的在memory_set 里面查找了
|
|
|
|
|
base_size: user_sp, // 用户栈顶以下 是代码中的各种逻辑段+栈段, 应用地址空间中从0x0开始到用户栈结束一共包含多少字节, 所以大小就是截止到user_sp
|
|
|
|
|
heap_bottom: user_sp, // todo 这里书里没写干嘛的 但是 user_sp 这个是用户空间的初始栈顶位置 指向最后的用户段中用户栈的高地址栈底的位置 在往上是空的空间了
|
|
|
|
|
program_brk: user_sp, // 同上
|
|
|
|
|
};
|
|
|
|
|
// prepare TrapContext in user space
|
|
|
|
|
// 根据 trap_cx_ppn 构建 陷入 trap context 的结构体
|
|
|
|
|
let trap_cx = task_control_block.get_trap_cx(); // 根据trap_cx_ppn得到真实的物理页的 trap context的地址
|
|
|
|
|
*trap_cx = TrapContext::from( // 对 tcb 的 TrapContext 的物理页进行 修改,
|
|
|
|
|
entry_point, // 在陷入完成后, 准备返回用户态执行的, 用户代码入口地址
|
|
|
|
|
user_sp, // 用户栈, 这个栈的地址是虚拟地址
|
|
|
|
|
KERNEL_SPACE.exclusive_access().token(), // 内核 satp 页表的寄存器信息
|
|
|
|
|
kernel_stack_top, // 用户应用的内核栈顶 在内核空间的位置 (这个是根据appid 计算出来的, 也只能在内核的地址空间中才能根据这个地址 看到相关的栈)
|
|
|
|
|
trap_handler as usize, // 内核中 trap handler的入口点虚拟地址(恒等映射所以 这里也能找到)
|
|
|
|
|
);
|
|
|
|
|
task_control_block
|
|
|
|
|
// 以上:
|
|
|
|
|
// 任务控制块和 trap控制块, 分别多了一些字段
|
|
|
|
|
// trap控制块(这个每次陷入都会被 sscratch 寄存器临时保存):
|
|
|
|
|
// 多的三个字段 首次写入后, 后续只会读取和恢复,不会被再次写入新值, 这是为了
|
|
|
|
|
// kernel_satp 内核空间页表所在位置的实际物理地址, 这个值以后会在切换的时候替换到satp寄存器
|
|
|
|
|
// kernel_sp, 这个保存这个应用 他 根据appid计算出来 所持有的任务内核栈, 在jump context之前 会恢复
|
|
|
|
|
// trap_handler 这是在内核在陷入的时候, 我们自定义的 trap_handle 的地址, 在陷入保存完寄存器之后, 需要 jmp 到这个函数地址
|
|
|
|
|
// task 控制块 TCB, 这个是在内核进行 switch时的上下文结构:
|
|
|
|
|
// memory_set 表示当前任务他的地址空间
|
|
|
|
|
// trap_cx_ppn 用来保存 上方 trap控制块的 实际的物理页帧, 在用户空间中都被应设在了TRAP_CONTEXT相关的次高位置虚拟地址处, 在内核空间我们这保存一份实际的物理地址, 在内核空间读取这个通过恒等映射就得到任务的 trap context
|
|
|
|
|
// base_size 用户栈顶距离0x0的距离, 即用户应用已知的大小
|
|
|
|
|
//
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TaskControlBlock {
|
|
|
|
|
// 根据 trap context 的实际的物理地址, 强转为 TrapContext 结构体
|
|
|
|
|
pub fn get_trap_cx(&self) -> &'static mut TrapContext {
|
|
|
|
|
self.trap_cx_ppn.get_mut()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_user_token(&self) -> usize {
|
|
|
|
|
self.memory_set.token()
|
|
|
|
|
}
|
|
|
|
|
}
|