From 6d9e06efa6858ad8c0fe78d8aa5810d969dec797 Mon Sep 17 00:00:00 2001 From: zhangxinyu <840317537@qq.com> Date: Thu, 1 Jun 2023 15:39:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=9C=B0=E5=9D=80=E7=A9=BA?= =?UTF-8?q?=E9=97=B4,=20"=E4=BC=A4=E9=BD=BF=E9=BE=99"=E7=8E=B0=E5=9C=A8?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=8F=AF=E4=BB=A5=E6=AD=A3=E5=B8=B8=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E4=BA=86,?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ch4/os/Cargo.toml | 3 +- ch4/os/Makefile | 2 +- ch4/os/build.rs | 2 +- ch4/os/src/loader.rs | 81 +++--------------------- ch4/os/src/main.rs | 8 ++- ch4/os/src/mm/address.rs | 2 +- ch4/os/src/mm/memory_set.rs | 119 ++++++++++++++++++++++++++++++++++-- ch4/os/src/mm/mod.rs | 43 ++++++++++++- ch4/os/src/mm/page_table.rs | 6 ++ ch4/os/src/syscall/fs.rs | 6 +- ch4/os/src/syscall/mod.rs | 3 + ch4/os/src/task/context.rs | 11 +++- ch4/os/src/task/mod.rs | 75 ++++++++++++----------- ch4/os/src/task/switch.S | 1 + ch4/os/src/task/task.rs | 90 ++++++++++++++++++++++++++- ch4/os/src/trap/context.rs | 11 +++- ch4/os/src/trap/mod.rs | 89 ++++++++++++++++++++++----- ch4/os/src/trap/trap.S | 2 +- 18 files changed, 414 insertions(+), 140 deletions(-) diff --git a/ch4/os/Cargo.toml b/ch4/os/Cargo.toml index f6a6e0b..9d61c8d 100644 --- a/ch4/os/Cargo.toml +++ b/ch4/os/Cargo.toml @@ -14,4 +14,5 @@ riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] } lazy_static = { version = "1.4.0", features = ["spin_no_std"] } sbi-rt = { version = "0.0.2", features = ["legacy"] } buddy_system_allocator = "0.6" -bitflags = "1.2.1" \ No newline at end of file +bitflags = "1.2.1" +xmas-elf = "0.7.0" \ No newline at end of file diff --git a/ch4/os/Makefile b/ch4/os/Makefile index 1a7acf0..237abd7 100644 --- a/ch4/os/Makefile +++ b/ch4/os/Makefile @@ -43,7 +43,7 @@ run:build_elf $(KERNEL_BIN) $(SYMBOL_MAP) kill -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY) clean: - #rm -rf ./target* && rm -rf ./src/link_app.S +# rm -rf ./target* && rm -rf ./src/link_app.S kill: -pkill -9 qemu diff --git a/ch4/os/build.rs b/ch4/os/build.rs index 0d4cdb2..d09ceea 100644 --- a/ch4/os/build.rs +++ b/ch4/os/build.rs @@ -61,7 +61,7 @@ _num_app: .global app_{0}_start .global app_{0}_end app_{0}_start: - .incbin "{2}{1}.bin" + .incbin "{2}{1}" app_{0}_end:"#, idx, app, TARGET_PATH )?; diff --git a/ch4/os/src/loader.rs b/ch4/os/src/loader.rs index 0afcb64..75611bc 100644 --- a/ch4/os/src/loader.rs +++ b/ch4/os/src/loader.rs @@ -17,79 +17,18 @@ pub fn get_num_app() -> usize{ } } -// 把 app一次性都加载到内存指定位置 -pub fn load_app() { - // 得到符号位 +pub fn get_app_data(app_id: usize) -> &'static [u8] { + extern "C" { + fn _num_app(); + } let num_app_ptr = _num_app as usize as *const usize; - - // 得到 符号开始的前8个字节, 这里保存的app的数量 let num_app = get_num_app(); - - // 得到 app数组的起始位置 num_app_ptr.add(1)跳过上方的 metadata, num_app+1 是确保切免得长度足够, 因为后面还有一个符号在linker.ld中 .quad app_2_end 表示结束的内存地址 - let app_start = unsafe { - core::slice::from_raw_parts(num_app_ptr.add(1), num_app+1) - }; - - // 清除缓存 - unsafe { asm!("fence.i"); } - - // 加载app - for app_id in 0..num_app { - // 得到每个app的起始位置 - let base_ptr = APP_BASE_ADDRESS + app_id * APP_SIZE_LIMIT; - - // 清理这个应用可以占用的内存(好像可以不用做吧? 下面直接dst.copy_from_slice全部覆盖了) - (base_ptr..base_ptr + APP_SIZE_LIMIT).for_each(|addr| unsafe { - (addr as *mut u8).write_volatile(0) - }); - - // 加载 app_start 处二进制数据到内存里 - let src = unsafe { - let app_size = app_start[app_id + 1] - app_start[app_id]; // 下一个app的起始位置, 减去当前的起始位置就是app的大小 - core::slice::from_raw_parts(app_start[app_id] as *const u8, app_size) - }; - - // 得到app占用的内存, 后面需要把app 二进制数据加载到这里 - let dst = unsafe { - core::slice::from_raw_parts_mut(base_ptr as *mut u8, src.len()) - }; - dst.copy_from_slice(src); - } -} - - -// 初始化用户应用栈, 并返回用户应用栈在内核栈中的trap_context的地址 -// 这个函数根据 app_id得到 USER_STACK的所在的位置, 初始化之后, 再构造 KERNEL_STACK -pub fn init_app_cx(app_id: usize) -> usize { - // 得到 用户栈 + let app_start = unsafe { core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1) }; + assert!(app_id < num_app); unsafe { - // 构造 trap_context - let user_trap_context = { - // 初始化用户栈顶 - let user_stack_top = { - let _tmp_stack = USER_STACK.as_ptr() as usize + USER_STACK_SIZE * app_id; // 用户栈开始 + (每个用户栈的大小 * 指定用户栈的索引) 得到栈顶位置, - _tmp_stack + USER_STACK_SIZE // 加上栈大小, 得到新的栈顶位置 - }; - - // 用户应用的开始位置 - let user_entry = APP_BASE_ADDRESS + APP_SIZE_LIMIT * app_id; - - TrapContext::from(user_entry, user_stack_top) - }; - - // 把构造的 user_trap_context 放到 KERNEL_STACK指定应用的内核栈中 - let kernel_app_stack_ptr = { - // 栈顶位置 + 栈大小 = 栈底 - let _tme_stack_low = KERNEL_STACK.as_ptr() as usize + KERNEL_STACK_SIZE * app_id + KERNEL_STACK_SIZE; - - // 给TrapContext 分配栈空间, 得到新的栈顶 - (_tme_stack_low - core::mem::size_of::()) as * mut TrapContext - }; - - // copy 数据到内核栈中 - *kernel_app_stack_ptr = user_trap_context; - - // 返回内核栈的地址 - kernel_app_stack_ptr as usize + core::slice::from_raw_parts( + app_start[app_id] as *const u8, + app_start[app_id + 1] - app_start[app_id], + ) } } \ No newline at end of file diff --git a/ch4/os/src/main.rs b/ch4/os/src/main.rs index 95bcf95..4f0f622 100644 --- a/ch4/os/src/main.rs +++ b/ch4/os/src/main.rs @@ -50,12 +50,14 @@ pub fn rust_main(){ // 初始化动态内存分配器, 使我们能在内核中使用动态大小数据类型 mm::init(); - + println!("main 0"); trap::init(); - loader::load_app(); + println!("main 1"); trap::enable_timer_interrupt(); // 允许定时器中断 + println!("main 2"); timer::set_next_trigger(); // 在进入用户态之前, 设置一个时钟中断, 防止第一个用户任务死循环 - // task::run_first_task(); + println!("main 3"); + task::run_first_task(); panic!("Disable run here") } diff --git a/ch4/os/src/mm/address.rs b/ch4/os/src/mm/address.rs index 94bd43e..6e0a72f 100644 --- a/ch4/os/src/mm/address.rs +++ b/ch4/os/src/mm/address.rs @@ -126,7 +126,7 @@ impl VirtAddr{ // 把usize 转为 虚拟地址, 只保留39位 impl From for VirtAddr { fn from(value: usize) -> Self { - Self(value & ((1 << PPN_WIDTH_SV39) - 1 )) + Self(value & ((1 << VA_WIDTH_SV39) - 1)) } } diff --git a/ch4/os/src/mm/memory_set.rs b/ch4/os/src/mm/memory_set.rs index 926fd10..0c8b54d 100644 --- a/ch4/os/src/mm/memory_set.rs +++ b/ch4/os/src/mm/memory_set.rs @@ -5,8 +5,9 @@ use core::arch::asm; use bitflags::bitflags; use lazy_static::lazy_static; use riscv::register::satp; +use xmas_elf; use crate::sync::UPSafeCell; -use crate::config::{MEMORY_END, PAGE_SIZE, TRAMPOLINE}; +use crate::config::{MEMORY_END, PAGE_SIZE, TRAMPOLINE, TRAP_CONTEXT, USER_STACK_SIZE}; use crate::mm::address::{PhysAddr, PhysPageNum, VirtAddr, VirtPageNum, VPNRange}; use crate::mm::frame_allocator::{frame_alloc, FrameTracker}; use crate::mm::page_table::{PageTable, PTEFlags}; @@ -65,7 +66,7 @@ impl MapArea { // 截取抛弃内存碎片 let start_vpn: VirtPageNum = start_va.floor(); let end_vpn: VirtPageNum = end_va.ceil(); - + println!("start va: {} start vpn {}", start_va.0, start_vpn.0); Self { vpn_range: VPNRange {l:start_vpn, r: end_vpn}, data_frames: BTreeMap::new(), @@ -110,7 +111,9 @@ impl MapArea { // 映射 当前逻辑段中的每一个虚拟页, 到页表中 pub fn map(&mut self, page_table: &mut PageTable) { + println!("self.vpn_range.get_start {} {}", self.vpn_range.l.0, self.vpn_range.r.0); for vpn in self.vpn_range { + println!("range vpn :{}", vpn.0); self.map_one(page_table, vpn); } } @@ -155,8 +158,8 @@ impl MapArea { // 表示一个地址空间, 它是一个页表和 包含所有段信息的页表组成 pub struct MemorySet { - page_table: PageTable, - areas: Vec, + pub page_table: PageTable, + pub areas: Vec, } impl MemorySet { @@ -166,6 +169,114 @@ impl MemorySet { areas: Vec::new(), } } + + + pub fn from_elf(elf_data: &[u8]) -> (Self, usize, usize) { + let mut memory_set = Self::new(); + // map trampoline + // 创建一个地址空间 + memory_set.map_trampoline(); + // map program headers of elf, with U flag + // 分析外部传进来的 elf文件数据 + println!("enf len: {}", elf_data.len()); + let elf = xmas_elf::ElfFile::new(elf_data).unwrap(); + let elf_header = elf.header; + let magic = elf_header.pt1.magic; + assert_eq!(magic, [0x7f, 0x45, 0x4c, 0x46], "invalid elf!"); + let ph_count = elf_header.pt2.ph_count(); + // 得到所有的段数量 + let mut max_end_vpn = VirtPageNum(0); + // 循环所有的段 + for i in 0..ph_count { + // 得到当前段 + let ph = elf.program_header(i).unwrap(); + // 确定有加载的必要 + if ph.get_type().unwrap() == xmas_elf::program::Type::Load { + // 当前段开始位置 + let start_va: VirtAddr = (ph.virtual_addr() as usize).into(); + // 当前段结束位置 (开始位置+大小) + let end_va: VirtAddr = ((ph.virtual_addr() + ph.mem_size()) as usize).into(); + println!("start_va {} end_va {}", start_va.0, end_va.0); + // 当前段权限 + let mut map_perm = MapPermission::U; + let ph_flags = ph.flags(); + if ph_flags.is_read() { + map_perm |= MapPermission::R; + } + if ph_flags.is_write() { + map_perm |= MapPermission::W; + } + if ph_flags.is_execute() { + map_perm |= MapPermission::X; + } + // 创建一个 逻辑段 + let map_area = MapArea::from(start_va, end_va, MapType::Framed, map_perm); + // 每次都更新最后的逻辑段的最后结束最大的逻辑地址 + max_end_vpn = map_area.vpn_range.r; + + // 当前逻辑段, 添加到地址空间, 并copy数据进去 + memory_set.push( + map_area, + Some(&elf.input[ph.offset() as usize..(ph.offset() + ph.file_size()) as usize]), + ); + + } + } + + // map user stack with U flags + // 将最后一个最大的逻辑段的结束的页, 转为逻辑地址 左移12位 得到39位的VirtAddr + let max_end_va: VirtAddr = max_end_vpn.into(); + // 根据riscv 的规则, 转成一个 合法的地址 (todo 为什么这里需要转成合法的地址:将高位全部设置为1, 上面的start_va 不转就传?, 而且不直接可以使用end_va 吗? 这里还创建一个max_end_vpn?) + let mut user_stack_bottom: usize = max_end_va.into(); + + // guard page 放置 一页 保护页隔离用户栈 + user_stack_bottom += PAGE_SIZE; + + // 保护页之后 再增加 用户栈大小,得到栈底和当前的栈顶 + // 此时 user_stack_top 距离 max_end_vpn 三个页 + let user_stack_top = user_stack_bottom + USER_STACK_SIZE; + + // 添加 用户逻辑栈段并映射物理地址页, 到当前的地址空间 + memory_set.push( + MapArea::from( + user_stack_bottom.into(), + user_stack_top.into(), + MapType::Framed, + MapPermission::R | MapPermission::W | MapPermission::U, + ), + None, + ); + + // used in sbrk + memory_set.push( + MapArea::from( + user_stack_top.into(), + user_stack_top.into(), + MapType::Framed, + MapPermission::R | MapPermission::W | MapPermission::U, + ), + None, + ); + + // 设置 TrapContext为统一的虚拟地址 + // 将 虚拟地址的 次高页添加到用户空间, 后面会被内核强行找到物理地址并修改为 trap context + println!("TRAP_CONTEXT: {}, TRAMPOLINE: {}", TRAP_CONTEXT, TRAMPOLINE); + memory_set.push( + MapArea::from( + TRAP_CONTEXT.into(), + TRAMPOLINE.into(), + MapType::Framed, + MapPermission::R | MapPermission::W, + ), + None, + ); + println!(3); + ( + memory_set, // 地址空间 + user_stack_top, // 用户栈底和当前的栈顶, 他在地址空间各个逻辑段的上面, 同时 + elf.header.pt2.entry_point() as usize, // elf文件的入口地址 + ) + } } impl MemorySet { diff --git a/ch4/os/src/mm/mod.rs b/ch4/os/src/mm/mod.rs index 5787c05..35c1fac 100644 --- a/ch4/os/src/mm/mod.rs +++ b/ch4/os/src/mm/mod.rs @@ -1,4 +1,6 @@ +use crate::mm::address::VirtAddr; use crate::mm::memory_set::KERNEL_SPACE; +use crate::{etext, println, stext}; pub mod heap_allocator; pub mod address; @@ -15,4 +17,43 @@ pub fn init(){ frame_allocator::frame_allocator_test(); // 激活内核的地址空间 KERNEL_SPACE.exclusive_access().activate(); -} \ No newline at end of file + + remap_test(); +} + + +pub fn remap_test() { + extern "C" { + fn stext(); + fn etext(); + fn srodata(); + fn erodata(); + fn sdata(); + fn edata(); + fn sbss_with_stack(); + fn ebss(); + fn ekernel(); + fn strampoline(); // 这个符号 在linker.ld 中定义 + } + + let mut kernel_space = KERNEL_SPACE.exclusive_access(); + let mid_text: VirtAddr = ((stext as usize + etext as usize) / 2).into(); + let mid_rodata: VirtAddr = ((srodata as usize + erodata as usize) / 2).into(); + let mid_data: VirtAddr = ((sdata as usize + edata as usize) / 2).into(); + assert!(!kernel_space + .page_table + .get_pte(mid_text.floor()) + .unwrap() + .writable(),); + assert!(!kernel_space + .page_table + .get_pte(mid_rodata.floor()) + .unwrap() + .writable(),); + assert!(!kernel_space + .page_table + .get_pte(mid_data.floor()) + .unwrap() + .executable(),); + println!("remap_test passed!"); +} diff --git a/ch4/os/src/mm/page_table.rs b/ch4/os/src/mm/page_table.rs index ea5ff9b..54d6723 100644 --- a/ch4/os/src/mm/page_table.rs +++ b/ch4/os/src/mm/page_table.rs @@ -1,8 +1,11 @@ use alloc::vec; use alloc::vec::Vec; use bitflags::*; +use crate::config::TRAMPOLINE; use crate::mm::address::{PhysPageNum, VirtAddr, VirtPageNum}; use crate::mm::frame_allocator::{frame_alloc, FrameTracker}; +use crate::println; +use crate::task::TASK_MANAGER; bitflags! { @@ -155,6 +158,9 @@ impl PageTable { pub fn map(&mut self, vpn: VirtPageNum, ppn: PhysPageNum, flags: PTEFlags) { // 查找vpn 没有中间节点就创建出来 let pte = self.find_pte_create(vpn).unwrap(); + // let a = self.get_pte(vpn).unwrap().ppn().0; + println!("is_valid: {}", pte.is_valid()); + println!("vpn: {:?} pte.ppn:{}", vpn.0, pte.ppn().0); assert!(!pte.is_valid(), "vpn {:?} is mapped before mapping", vpn.0); *pte = PageTableEntry::from(ppn, flags | PTEFlags::V); } diff --git a/ch4/os/src/syscall/fs.rs b/ch4/os/src/syscall/fs.rs index eeb9aeb..f50d860 100644 --- a/ch4/os/src/syscall/fs.rs +++ b/ch4/os/src/syscall/fs.rs @@ -8,9 +8,9 @@ const FD_STDOUT: usize = 1; pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize { match fd { FD_STDOUT => { - let slice = unsafe { core::slice::from_raw_parts(buf, len) }; - let str = core::str::from_utf8(slice).unwrap(); - print!("{}", str); + // let slice = unsafe { core::slice::from_raw_parts(buf, len) }; + // let str = core::str::from_utf8(slice).unwrap(); + // print!("{}", str); len as isize } _ => { diff --git a/ch4/os/src/syscall/mod.rs b/ch4/os/src/syscall/mod.rs index f88bcfd..9d4c66d 100644 --- a/ch4/os/src/syscall/mod.rs +++ b/ch4/os/src/syscall/mod.rs @@ -8,9 +8,12 @@ mod process; use fs::*; use process::*; +use crate::println; /// 根据syscall_id 进行分发 pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize { + println!("syscall_id......{} {:?}", syscall_id, args); + match syscall_id { SYSCALL_WRITE => sys_write(args[0], args[1] as *const u8, args[2]), SYSCALL_EXIT => sys_exit(args[0] as i32), diff --git a/ch4/os/src/task/context.rs b/ch4/os/src/task/context.rs index 7552b75..cd51335 100644 --- a/ch4/os/src/task/context.rs +++ b/ch4/os/src/task/context.rs @@ -1,4 +1,4 @@ - +use crate::trap::trap_return; // TCB的字段 用来保存cpu 在内核切换人物的时候 寄存器还有栈顶的信息, 这个结构体将来会传到 switch.S 中的汇编中 #[derive(Copy, Clone)] @@ -32,4 +32,13 @@ impl TaskContext{ s: [0; 12], } } + + /// 根据用户当前内核栈做的栈顶 构造一个 任务上下文, 并把 trap_return 设置为 switch切换 ret的返回地址 + pub fn goto_trap_return(kstack_ptr: usize) -> Self { + Self { + ra: trap_return as usize, // 这里是 在内核空间的 函数地址 (当然 task context 只有在内核态才是可见的) + sp: kstack_ptr, + s: [0; 12], + } + } } diff --git a/ch4/os/src/task/mod.rs b/ch4/os/src/task/mod.rs index b30c303..a289804 100644 --- a/ch4/os/src/task/mod.rs +++ b/ch4/os/src/task/mod.rs @@ -1,12 +1,14 @@ use lazy_static::lazy_static; +use alloc::vec::Vec; use crate::config::MAX_APP_NUM; use crate::sync::UPSafeCell; use crate::task::task::{TaskControlBlock, TaskStatus}; -use crate::loader::{get_num_app, init_app_cx}; +use crate::loader::{get_num_app, get_app_data}; use crate::println; use crate::task::context::TaskContext; use crate::task::switch::__switch; use crate::timer::{get_time_ms, get_time_us}; +use crate::trap::TrapContext; mod context; mod switch; @@ -57,6 +59,7 @@ impl TaskManager { // 不会走到这里了 let mut unused = TaskContext::new(); + println!("start run app"); unsafe { __switch(&mut unused as *mut TaskContext, next_task_context_ptr) } @@ -136,12 +139,24 @@ impl TaskManager { } None } + + // 得到当前正在执行用户应用的 trap context + pub fn get_current_trap_cx(&self) -> &'static mut TrapContext { + let inner = self.inner.exclusive_access(); + inner.tasks[inner.current_task_id].get_trap_cx() + } + + // 得到当前正在只用的用户应用的 页表 + pub fn get_current_token(&self) -> usize { + let inner = self.inner.exclusive_access(); + inner.tasks[inner.current_task_id].get_user_token() + } } // 不公开的结构体, 有一个MAX_APP_NUM大小的数组, 用来保存TCB, 和当前任务的的TCB的下标 struct TaskManagerInner { - tasks: [TaskControlBlock; MAX_APP_NUM], + tasks: Vec, current_task_id: usize, stop_clock_time: usize, // 记录最近一次停表时间 } @@ -157,47 +172,29 @@ impl TaskManagerInner { } } -lazy_static!{ +lazy_static! { pub static ref TASK_MANAGER: TaskManager = { - // 得到app的总数 + println!("init TASK_MANAGER"); let num_app = get_num_app(); - - // 初始化内核中的任务列表 - let mut tasks = [ - TaskControlBlock { - task_cx: TaskContext::new(), - // UnInit 这个tcb 还没用户应用填充加载 - task_status: TaskStatus::UnInit, - kernel_time: 0, - user_time: 0, - }; - MAX_APP_NUM - ]; - - // 初始化用户的应用对应的 TCB - for app_idx in 0..num_app { - // 初始化用户应用的内核栈 - let kernel_app_stack_ptr = init_app_cx(app_idx); - - // 从 初始内核栈的栈顶(保存 trap_context的位置) 创建 任务context - let task_context = TaskContext::from(kernel_app_stack_ptr); - - // 把内核栈的信息保存到 TCB中 - tasks[app_idx].task_cx = task_context; - - // 初始为 准备好的状态 - tasks[app_idx].task_status = TaskStatus::Ready - }; - - TaskManager { // 直接返回即可, 所有字段都是sync的, 那这个结构体也是sync + println!("num_app = {}", num_app); + let mut tasks: Vec = Vec::new(); + + for i in 0..num_app { + // 得到elf 的二进制数据 + let app_elf_data = get_app_data(i); + // 根据二进制数据创建 TCB + let tcb = TaskControlBlock::from(app_elf_data, i); + tasks.push(tcb); + } + TaskManager { num_app, inner: unsafe { UPSafeCell::new(TaskManagerInner { tasks, - current_task_id: 0, - stop_clock_time: 0 + stop_clock_time: 0, + current_task_id: 0, // 从第0个启动 }) - } + }, } }; } @@ -219,3 +216,9 @@ pub fn exit_current_and_run_next(){ TASK_MANAGER.mark_current_exit(); TASK_MANAGER.run_next_task(); } + + +// 得到当前运行任务的 trap context 的地址 +pub fn current_trap_cx() -> &'static mut TrapContext { + TASK_MANAGER.get_current_trap_cx() +} diff --git a/ch4/os/src/task/switch.S b/ch4/os/src/task/switch.S index a451f88..57c77bd 100644 --- a/ch4/os/src/task/switch.S +++ b/ch4/os/src/task/switch.S @@ -40,4 +40,5 @@ __switch: # restore kernel stack of next task ld sp, 8(a1) # 从下一个任务的上下文中加载内核栈栈顶的值 # 阶段 [4] +__switch_ret: ret # 返回到下一个任务的执行点 diff --git a/ch4/os/src/task/task.rs b/ch4/os/src/task/task.rs index 4f65803..aa1ec83 100644 --- a/ch4/os/src/task/task.rs +++ b/ch4/os/src/task/task.rs @@ -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() + } } \ No newline at end of file diff --git a/ch4/os/src/trap/context.rs b/ch4/os/src/trap/context.rs index 3cd7482..04d2a49 100644 --- a/ch4/os/src/trap/context.rs +++ b/ch4/os/src/trap/context.rs @@ -18,6 +18,12 @@ pub struct TrapContext { pub sstatus: Sstatus, /// CSR sepc 保存的是用户态ecall时 所在的那一行的代码 pub sepc: usize, + /// Addr of Page Table + pub kernel_satp: usize, // 内核页的起始物理地址, 首次写入后, 后续只会读取和恢复,不会被再次写入新值 + /// kernel stack + pub kernel_sp: usize, // 当前任务的内核栈顶的虚拟地址 + /// Addr of trap_handler function + pub trap_handler: usize, // 陷入处理函数的地址虚拟地址 } impl TrapContext { @@ -27,13 +33,16 @@ impl TrapContext { } // 根据entry和sp构造一个 trap_context - pub fn from(entry: usize, sp: usize) -> Self { + pub fn from(entry: usize, sp: usize, kernel_satp: usize, kernel_sp: usize, trap_handler: usize,) -> Self { let mut sstatus = sstatus::read(); // 读取CSR sstatus sstatus.set_spp(SPP::User); // 设置特权级为用户模式 let mut cx = Self { x: [0; 32], sstatus, sepc: entry, // 设置代码执行的开头, 将来条入到这里执行 + kernel_satp, + kernel_sp, + trap_handler, }; cx.set_sp(sp); // 设置用户栈的栈顶 cx // 返回, 外面会恢复完 x[0; 32] 之后最后 sret 根据entry和sstatus 返回 diff --git a/ch4/os/src/trap/mod.rs b/ch4/os/src/trap/mod.rs index 3b372f9..7542e8c 100644 --- a/ch4/os/src/trap/mod.rs +++ b/ch4/os/src/trap/mod.rs @@ -1,10 +1,11 @@ mod context; -use core::arch::global_asm; +use core::arch::{asm, global_asm}; use riscv::register::{mtvec::TrapMode, scause::{self, Exception, Trap}, sie, stval, stvec}; use riscv::register::scause::Interrupt; pub use context::TrapContext; +use crate::config::{TRAMPOLINE, TRAP_CONTEXT}; use crate::println; use crate::syscall::syscall; use crate::task::{exit_current_and_run_next, suspend_current_and_run_next, TASK_MANAGER}; @@ -17,12 +18,7 @@ global_asm!(include_str!("trap.S")); /// 初始化stvec 寄存器, 这个寄存器保存陷入时, 入口函数的地址 pub fn init() { - extern "C" { - fn __alltraps(); - } - unsafe { - stvec::write(__alltraps as usize, TrapMode::Direct); - } + set_kernel_trap_entry(); } // 设置riscv 允许定时器中断 @@ -34,23 +30,31 @@ pub fn enable_timer_interrupt() { // 这个函数是 trap.S 中__alltraps 保存完所有寄存器在内核栈 之后会进入到这里 #[no_mangle] -pub fn trap_handler(trap_context: &mut TrapContext) -> &mut TrapContext { +pub fn trap_handler() -> ! { + // 已经进入内核, 如果再发生中断我们进行的设置, 目前是直接跳转到一个引发panic 的函数 + println!("trap_handler......"); + set_kernel_trap_entry(); + // 进入到了内核态, 需要把之前的用户消耗时间统计在用户时间上 TASK_MANAGER.kernel_time_start(); - + // 得到当前用户应用 的 trap context, 需要调用一个函数, 因为用户应用的trap context 不再内核空间, 他是在用户空间的次高地址 + let cx = TASK_MANAGER.get_current_trap_cx(); let scause = scause::read(); // trap 发生的原因 let stval = stval::read(); // trap的附加信息 - + println!("trap_handler......1"); // 根据发生的原因判断是那种类别 match scause.cause() { // 用户态发出的系统调用 Trap::Exception(Exception::UserEnvCall) => { - trap_context.sepc += 4; // +4 是因为我们需要返回 ecall指令的下一个指令 - trap_context.x[10] = syscall(trap_context.x[17], [trap_context.x[10], trap_context.x[11], trap_context.x[12]]) as usize; + cx.sepc += 4; // +4 是因为我们需要返回 ecall指令的下一个指令 + cx.x[10] = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]) as usize; } // 访问不允许的内存 - Trap::Exception(Exception::StoreFault) | Trap::Exception(Exception::StorePageFault) => { + Trap::Exception(Exception::StoreFault) + | Trap::Exception(Exception::StorePageFault) + | Trap::Exception(Exception::LoadFault) + | Trap::Exception(Exception::LoadPageFault) => { println!("[kernel] PageFault in application, kernel killed it."); exit_current_and_run_next(); } @@ -76,8 +80,65 @@ pub fn trap_handler(trap_context: &mut TrapContext) -> &mut TrapContext { } // 即将进入用户态, 把内核使用的时间统计在内核时间上 TASK_MANAGER.user_time_start(); - trap_context + trap_return() +} + + +#[no_mangle] +/// Unimplement: traps/interrupts/exceptions from kernel mode +/// Todo: Chapter 9: I/O device +pub fn trap_from_kernel() -> ! { + panic!("a trap from kernel!"); +} + +#[no_mangle] +/// set the new addr of __restore asm function in TRAMPOLINE page, +/// set the reg a0 = trap_cx_ptr, reg a1 = phy addr of usr page table, +/// finally, jump to new addr of __restore asm function +pub fn trap_return() -> ! { + // 设置 用户态的中断处理函数 + set_user_trap_entry(); + + let trap_cx_ptr = TRAP_CONTEXT; // 用户空间虚拟地址的次高页, 保存了trap context, 在返回用户空间的时候, 需要恢复trap context中保存的寄存器信息 + let user_satp = TASK_MANAGER.get_current_token(); // 当前用户应用的 页表所在的地址 需要在trap.S中, 等下恢复完寄存器之后 修改的用户应用自己的页表 + extern "C" { + fn __alltraps(); + fn __restore(); + } + // TRAMPOLINE + (__restore - __alltraps), 得到的就是用户空间中 虚拟地址跳板的位置, 增加一个 (__alltraps低地址 到 __restore高地址的偏移值), 就得到了 strampoline -> __restore 的虚拟地址 + let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE; + + println!("restore_va: {:?}", restore_va); + println!("trap_cx_ptr :{:?}", trap_cx_ptr); + println!("user_satp :{:?}", user_satp); + + unsafe { + asm!( + "fence.i", // 清空 i-cache + "jr {restore_va}", // 跳转到 strampoline -> __restore 地址 + restore_va = in(reg) restore_va, + in("a0") trap_cx_ptr, // a0 = virt addr of Trap Context + in("a1") user_satp, // a1 = phy addr of usr page table + options(noreturn) + ) + } } +fn set_kernel_trap_entry() { + // 设置内核法中 trap 的处理 + // 当前我们直接panic + unsafe { + println!("set_kernel_trap_entry"); + stvec::write(trap_from_kernel as usize, TrapMode::Direct); + } +} + +fn set_user_trap_entry() { + // 设置 用户态的中断处理函数, 为我们的跳板, 这个跳板在用户空间的虚拟地址最高页, 虚拟地址最高页被映射在了十几物理内存的trap 处理函数所在的段执行__alltraps + unsafe { + println!("set_user_trap_entry"); + stvec::write(TRAMPOLINE as usize, TrapMode::Direct); + } +} diff --git a/ch4/os/src/trap/trap.S b/ch4/os/src/trap/trap.S index 01348ae..dbd3d3c 100644 --- a/ch4/os/src/trap/trap.S +++ b/ch4/os/src/trap/trap.S @@ -9,7 +9,7 @@ ld x\n, \n*8(sp) # 从栈空间中加载寄存器 x_n 的值 .endm -.section .text # 进入 .text 段 +.section .text.trampoline # 进入 .text.trampoline 段 .globl __alltraps # 声明全局符号 __alltraps .globl __restore # 声明全局符号 __restore .align 2 # 对齐到 2^2 = 4 字节