From d1dc7056a5c03a807b73a790466e0e0eee78a116 Mon Sep 17 00:00:00 2001 From: zhangxinyu <840317537@qq.com> Date: Thu, 8 Jun 2023 17:28:06 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E7=AE=A1=E7=90=86=E7=9A=84?= =?UTF-8?q?=E6=A0=B8=E5=BF=83=E7=BB=93=E6=9E=84=E4=BB=A5=E5=8F=8A0?= =?UTF-8?q?=E5=8F=B7=E8=BF=9B=E7=A8=8B=E7=9A=84=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ch5/os/src/main.rs | 13 +- ch5/os/src/syscall/fs.rs | 5 +- ch5/os/src/syscall/process.rs | 6 +- ch5/os/src/task/manager.rs | 46 ++++++ ch5/os/src/task/mod.rs | 285 ++++++++++------------------------ ch5/os/src/task/pid.rs | 27 ++++ ch5/os/src/task/processor.rs | 170 ++++++++++++++++++++ ch5/os/src/task/task.rs | 121 +++++++++------ ch5/os/src/trap/mod.rs | 16 +- 9 files changed, 421 insertions(+), 268 deletions(-) create mode 100644 ch5/os/src/task/manager.rs create mode 100644 ch5/os/src/task/processor.rs diff --git a/ch5/os/src/main.rs b/ch5/os/src/main.rs index 59a869f..d8e9516 100644 --- a/ch5/os/src/main.rs +++ b/ch5/os/src/main.rs @@ -50,13 +50,14 @@ pub fn rust_main(){ // 初始化动态内存分配器, 使我们能在内核中使用动态大小数据类型 mm::init(); - use loader; - println!("{:?}", loader::get_app_data_by_name("00power_3")); - // trap::init(); - // trap::enable_timer_interrupt(); // 允许定时器中断 - // timer::set_next_trigger(); // 在进入用户态之前, 设置一个时钟中断, 防止第一个用户任务死循环 - // task::run_first_task(); + + trap::init(); + trap::enable_timer_interrupt(); // 允许定时器中断 + timer::set_next_trigger(); // 在进入用户态之前, 设置一个时钟中断, 防止第一个用户任务死循环 + loader::list_apps(); + task::add_initproc(); + // task::run_tasks(); panic!("Disable run here") } diff --git a/ch5/os/src/syscall/fs.rs b/ch5/os/src/syscall/fs.rs index 26ea702..d252355 100644 --- a/ch5/os/src/syscall/fs.rs +++ b/ch5/os/src/syscall/fs.rs @@ -2,7 +2,8 @@ use crate::mm::page_table::translated_byte_buffer; use crate::print; -use crate::task::TASK_MANAGER; +use crate::task::manager::TASK_MANAGER; +use crate::task::processor::current_user_token; const FD_STDOUT: usize = 1; @@ -12,7 +13,7 @@ pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize { FD_STDOUT => { // 根据当前页表 找到物理内存基地址 // 这里需要从应用的地址空间里面寻找数据 - let buffers = translated_byte_buffer(TASK_MANAGER.get_current_token(), buf, len); + let buffers = translated_byte_buffer(current_user_token(), buf, len); for buffer in buffers { print!("{}", core::str::from_utf8(buffer).unwrap()); } diff --git a/ch5/os/src/syscall/process.rs b/ch5/os/src/syscall/process.rs index a938295..7bd2fe7 100644 --- a/ch5/os/src/syscall/process.rs +++ b/ch5/os/src/syscall/process.rs @@ -1,13 +1,15 @@ //! App management syscalls // use crate::batch::run_next_app; use crate::println; -use crate::task::{change_program_brk, exit_current_and_run_next, suspend_current_and_run_next}; +use crate::task::{exit_current_and_run_next, suspend_current_and_run_next}; +use crate::task::processor::change_program_brk; +// use crate::task::{change_program_brk, exit_current_and_run_next, suspend_current_and_run_next}; use crate::timer::get_time_ms; /// 任务退出, 并立即切换任务 pub fn sys_exit(exit_code: i32) -> ! { println!("[kernel] Application exited with code {}", exit_code); - exit_current_and_run_next(); + exit_current_and_run_next(exit_code); panic!("Unreachable in sys_exit!"); } diff --git a/ch5/os/src/task/manager.rs b/ch5/os/src/task/manager.rs new file mode 100644 index 0000000..cd8e291 --- /dev/null +++ b/ch5/os/src/task/manager.rs @@ -0,0 +1,46 @@ +use alloc::collections::VecDeque; +use alloc::sync::Arc; +use lazy_static::lazy_static; +use crate::println; +use crate::task::task::TaskControlBlock; +use crate::task::UPSafeCell; + +/// 任务管理器 仅负责管理所有任务, 由ch4 TaskManager 拆分的, +/// 先入先出的双端队列 +pub struct TaskManager { + ready_queue: VecDeque>, +} + +impl TaskManager { + pub fn new() -> Self { + Self { + ready_queue: VecDeque::new(), + } + } +} + +impl TaskManager { + // 添加一个任务到 队列中 + pub fn add(&mut self, task: Arc) { + self.ready_queue.push_back(task); + } + // 如果有任务, 弹出第一个任务 + pub fn fetch(&mut self) -> Option> { + self.ready_queue.pop_front() + } +} + + + +lazy_static! { + // 全局的任务管理器 + pub static ref TASK_MANAGER: UPSafeCell = unsafe { UPSafeCell::new(TaskManager::new()) }; +} +/// 给全局任务管理器添加任务 +pub fn add_task(task: Arc) { + TASK_MANAGER.exclusive_access().add(task); +} +// 全局任务管理器 弹出任务 +pub fn pop_task() -> Option> { + TASK_MANAGER.exclusive_access().fetch() +} \ No newline at end of file diff --git a/ch5/os/src/task/mod.rs b/ch5/os/src/task/mod.rs index 98ae6b2..99c83a7 100644 --- a/ch5/os/src/task/mod.rs +++ b/ch5/os/src/task/mod.rs @@ -5,232 +5,111 @@ use crate::sync::UPSafeCell; use crate::task::task::{TaskControlBlock, TaskStatus}; use crate::loader::{get_num_app, get_app_data}; use crate::println; +use crate::sbi::shutdown; use crate::task::context::TaskContext; +use crate::task::manager::{add_task, TASK_MANAGER}; +use crate::task::processor::{schedule, take_current_task}; use crate::task::switch::__switch; use crate::timer::{get_time_ms, get_time_us}; use crate::trap::TrapContext; +use alloc::sync::Arc; +use crate::loader::get_app_data_by_name; mod context; mod switch; -mod task; +pub mod task; mod pid; +pub mod manager; +pub mod processor; +pub use processor::{run_tasks}; -// 公开到外部的一个全局任务管理器的结构体 -pub struct TaskManager { - num_app: usize, // app的数量 这个在os运行之后就不会有变化 - inner: UPSafeCell, // 这个内部TaskControlBlock 会随着系统运行发生变化 -} - -impl TaskManager { - // 即将进入用户态, 把之前使用的内核时间统计 - pub fn user_time_start(&self){ - let mut inner = self.inner.exclusive_access(); - let current_task_id = inner.current_task_id; - let kernel_use_time = inner.refresh_stop_clock(); - - inner.tasks[current_task_id].kernel_time += kernel_use_time; - } - - pub fn kernel_time_start(&self){ - let mut inner = self.inner.exclusive_access(); - let current_task_id = inner.current_task_id; - let user_use_time = inner.refresh_stop_clock(); - - inner.tasks[current_task_id].user_time += user_use_time; - } +pub const IDLE_PID: usize = 0; +lazy_static! { + /// 初始化用户shell的全局进程, 他的pid是0 + pub static ref INITPROC: Arc = Arc::new(TaskControlBlock::from(get_app_data_by_name("initproc").unwrap())); +} - fn run_first_task(&self){ - // 得到下一个需要执行的任务,的上下文 这里我们由于第一次执行, 下一个任务我们指定为0下标的任务即可 - let next_task_context_ptr = { - let mut inner = self.inner.exclusive_access(); - // 第一次掐表, 开始记录时间 - inner.refresh_stop_clock(); - - let task_0 = &mut inner.tasks[0]; - // 修改任务0的状态 - task_0.task_status = TaskStatus::Running; - - &(task_0.task_cx) as *const TaskContext - }; - - // 由于第一次切换, 我们current task context 不存在, 所以我们手动创建一个, 在调用switch的时候 - // 这里调用switch的状态会被保存在unused 这里, 但是一旦程序开始运行, 就开始在内核栈或者用户栈之间切换, 永远 - // 不会走到这里了 - let mut unused = TaskContext::new(); - - println!("start run app"); - unsafe { - __switch(&mut unused as *mut TaskContext, next_task_context_ptr) - } - - // - panic!("unreachable in run_first_task!"); - } - - // 停止当前正在运行的任务 - fn mark_current_exit(&self){ - let mut inner = self.inner.exclusive_access(); - let current_task_id = inner.current_task_id; - let kernel_use_time = inner.refresh_stop_clock(); - let current_task = &mut inner.tasks[current_task_id]; - current_task.task_status = TaskStatus::Exited; - - // 统计内核时间, 并输出这个任务 花费了多少内核时间 - current_task.kernel_time += kernel_use_time; - println!("task {:?}, exit, kernel_time: {:?}ms, user_time: {:?}ms", current_task_id, current_task.kernel_time/1000, current_task.user_time/1000); - } - - // 挂起当前任务 - fn mark_current_suspend(&self){ - let mut inner = self.inner.exclusive_access(); - let current_task_id = inner.current_task_id; - let kernel_use_time = inner.refresh_stop_clock(); - let current_task = &mut inner.tasks[current_task_id]; - current_task.task_status = TaskStatus::Ready; - - // 统计内核时间(因为内核进入到这里之前肯定是掐表了) - // 挂起任务A之后,这里也掐表了, 切到新的任务B, B开始执行, B在挂起的时候 也会走入到这里掐表, 顺便并得到任务A掐表到现在任务B掐表准备切换任务出去中间的时间 - current_task.kernel_time += kernel_use_time; - } - - // 运行下一个任务 - fn run_next_task(&self){ - if let Some(next_task_id) = self.find_next_task() { - // 得到当前任务context ptr 以及 下一个任务context的ptr - let (current_task_ptr,next_task_ptr) = { - let mut inner = self.inner.exclusive_access(); - - // 当前任务id - let current_task_id = inner.current_task_id; - - // 修改current_task_id 为 下一个任务, 因为一旦到switch就是在运行下一个任务了 - inner.current_task_id = next_task_id; - - // 修改下一个任务的状态 - inner.tasks[next_task_id].task_status = TaskStatus::Running; - - // 得到current的ptr - let current_task_ptr = &mut inner.tasks[current_task_id].task_cx as *mut TaskContext; +/// 添加初始化 Process 到 全局任务管理器 +pub fn add_initproc() { + add_task(INITPROC.clone()); +} - // 得到需要被切换的下一个任务ptr - let next_task_ptr = &inner.tasks[next_task_id].task_cx as *const TaskContext; - (current_task_ptr, next_task_ptr) - }; +// 挂起当前运行下一个, 需要把当前任务 从Process中 take 取出来, 然后再 push回 全局的任务管理器 +pub fn suspend_current_and_run_next() { + // 必须有一个程序在运行, + let task = take_current_task().unwrap(); + + // ---- access current TCB exclusively + // 当前正在执行的Process + let mut task_inner = task.inner_exclusive_access(); + let task_cx_ptr = &mut task_inner.task_cx as *mut TaskContext; + // Change status to Ready + task_inner.task_status = TaskStatus::Ready; + drop(task_inner); + // ---- release current PCB + + // 把当前任务 push回全局的任务管理器 + add_task(task); + + // jump to scheduling cycle + schedule(task_cx_ptr); +} - // 开始伟大的切换! - unsafe { - __switch(current_task_ptr, next_task_ptr); - } +pub fn exit_current_and_run_next(exit_code: i32) { + // 从 Process 管理器中 转移所有权的取出当前cpu执行的任务 + let task = take_current_task().unwrap(); + + // 当前pid + let pid = task.get_pid(); + + // 如果是idle进程 + if pid == IDLE_PID { + println!( + "[kernel] Idle process exit with exit_code {} ...", + exit_code + ); + if exit_code != 0 { + //crate::sbi::shutdown(255); //255 == -1 for err hint + shutdown() } else { - panic!("All applications completed!"); + //crate::sbi::shutdown(0); //0 for success hint + shutdown() } } - // 找到一个除当前任务之外的 是Ready状态的任务的id - fn find_next_task(&self) -> Option{ - let mut inner = self.inner.exclusive_access(); - let current = inner.current_task_id; - for task_id in current + 1..current + self.num_app + 1 { - let _tmp_id = task_id % self.num_app; - if inner.tasks[_tmp_id].task_status == TaskStatus::Ready { - return Some(_tmp_id) - } + // 改变pcb 的状态, 从当前task获取inner, 也就是当前当前pcb需要变化的值 + let mut inner = task.inner_exclusive_access(); + // 将状态更改为 僵尸进程, 供后续父进程查找回收资源 + inner.task_status = TaskStatus::Zombie; + // 写入退出码 + inner.exit_code = exit_code; + // 不需要修改父进程, 因为所有进程的父进程这里都为 initproc + + // 把当前进程, 所有的子进程, 都挂载到 initproc进程 + { + let mut initproc_inner = INITPROC.inner_exclusive_access(); + for child in inner.children.iter() { + child.inner_exclusive_access().parent = Some(Arc::downgrade(&INITPROC)); // 父进程 一个弱引用 + initproc_inner.children.push(child.clone()); // 挂载到initproc进程的childre中, 引用计数 +1, 现在是2 } - 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() - } + inner.children.clear(); // 子进程引用计数-1 现在又变成1了 - // 得到当前正在只用的用户应用的 页表 - pub fn get_current_token(&self) -> usize { - let inner = self.inner.exclusive_access(); - inner.tasks[inner.current_task_id].get_user_token() - } - - // 扩张/缩减, 当前应用的地址空间中的堆段, 指定字节的数据 - pub fn change_current_program_brk(&self, size: i32) -> Option { - let mut inner = self.inner.exclusive_access(); - let current_task_id = inner.current_task_id; - inner.tasks[current_task_id].change_program_brk(size) - } -} + // 释放当前地址空间中, 逻辑段清空, areas->MapArea->data_frames->FrameTracker 执行析构, 过程是把areas中的物理页帧 根据 RAII还给操作系统 + // 注意 页表本身占用的 三级页表的物理帧(page_table->frames), 还没有被释放, 因为我们还需要使用, 这需要父进程管理进行释放 + inner.memory_set.recycle_data_pages(); + drop(inner); + // 手动删除tcb, 后面这里永远不会执行了, 我们智能指针需要手动释放(这里需要给引用计数-1) + drop(task); -// 不公开的结构体, 有一个MAX_APP_NUM大小的数组, 用来保存TCB, 和当前任务的的TCB的下标 -struct TaskManagerInner { - tasks: Vec, - current_task_id: usize, - stop_clock_time: usize, // 记录最近一次停表时间 -} - -impl TaskManagerInner { - fn refresh_stop_clock(&mut self) -> usize { - // 上次停表的时间 - let start_time = self.stop_clock_time; - // 当前时间 - self.stop_clock_time = get_time_us(); - // 返回 当前时间 - 上次停表时间 = 时间间距 - self.stop_clock_time - start_time - } -} - -lazy_static! { - pub static ref TASK_MANAGER: TaskManager = { - println!("init TASK_MANAGER"); - let num_app = get_num_app(); - 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, - stop_clock_time: 0, - current_task_id: 0, // 从第0个启动 - }) - }, - } - }; -} - - -// 由内核调用, 在main中, 用来 作为执行用户应用的开端 -pub fn run_first_task() { - TASK_MANAGER.run_first_task(); -} - -// 挂起当前任务, 并运行下一个 -pub fn suspend_current_and_run_next(){ - TASK_MANAGER.mark_current_suspend(); - TASK_MANAGER.run_next_task(); -} - -// 退出当前任务, 并运行下一个 -pub fn exit_current_and_run_next(){ - TASK_MANAGER.mark_current_exit(); - TASK_MANAGER.run_next_task(); -} - -// 扩张/缩减, 当前应用的地址空间中的堆段, 指定字节的数据 -pub fn change_program_brk(size: i32) -> Option { - TASK_MANAGER.change_current_program_brk(size) -} + // 当前task 函数 为什么在 recycle_data_pages 之后还能执行? 函数栈帧为什么还能使用? 是因为我们虽然把 应用程序锁使用的逻辑页帧都取消映射了, 但是关于 task中的 pid 和kernel stack 还没有被释放 + // task 他在被fork的时候 parent_inner.children.push(task_control_block.clone()); 也clone了自己一份到父进程中, 所以这里删除drop只是删除了当前 Process中管理的引用计数(后续我们不会执行到这里了, 需要手动删除) -// 得到当前运行任务的 trap context 的地址 -pub fn current_trap_cx() -> &'static mut TrapContext { - TASK_MANAGER.get_current_trap_cx() + // 这个进程不会被执行了, 我们当前栈帧随便创建一个上下文就好了 + let mut _unused = TaskContext::new(); + schedule(&mut _unused as *mut _); } diff --git a/ch5/os/src/task/pid.rs b/ch5/os/src/task/pid.rs index 2f4dcd0..a2e0bb6 100644 --- a/ch5/os/src/task/pid.rs +++ b/ch5/os/src/task/pid.rs @@ -1,6 +1,7 @@ use alloc::vec::Vec; use lazy_static::lazy_static; use crate::config::{KERNEL_STACK_SIZE, PAGE_SIZE, TRAMPOLINE}; +use crate::mm::address::VirtAddr; use crate::mm::memory_set::{KERNEL_SPACE, MapPermission}; use crate::sync::UPSafeCell; @@ -16,6 +17,11 @@ lazy_static! { unsafe { UPSafeCell::new(PidAllocator::new()) }; } +pub fn pid_alloc() -> PidHandle { + PID_ALLOCATOR.exclusive_access().alloc() +} + + impl PidAllocator { pub fn new() -> Self { Self { @@ -105,7 +111,15 @@ impl KernelStack { pub fn from(pid_handle: &PidHandle) -> Self { let pid = pid_handle.0; + // 根据 pid 创建受保护的用户的任务内核栈, 并得到栈的起始地址和结束地址, 这个在次高页的下面的 某个位置 + // kernel_stack_bottom = kernel_stack_top - PAGESIZE * 2 + // kernel_stack_top 是当前栈的栈顶, 同时也是现在没有push操作也是栈底 + // 这里都是虚拟地址 let (kernel_stack_bottom, kernel_stack_top) = kernel_stack_position(pid); + + // 把用户的内核栈逻辑段, 映射并创建出来实际的物理页帧, 到内核地址空间, 供内核态可以进行寻址找到用户应用的内核栈, + // 这里使用虚拟映射 而不是恒等映射, 是因为需要动态调整? + // 这个只有在内核会用到 KERNEL_SPACE .exclusive_access() .insert_framed_area( @@ -118,5 +132,18 @@ impl KernelStack { } +// 当kernel stack 生命周期结束, 对应 new 出来的一个 应用内核栈 逻辑段也随之删除 +// 注意! 一般这个是在 wait_pid 内用的, 用于父进程回收子进程资源时, 子进程内的字段根据RAII执行 +impl Drop for KernelStack { + fn drop(&mut self) { + let (kernel_stack_bottom, _) = kernel_stack_position(self.pid); + let kernel_stack_bottom_va: VirtAddr = kernel_stack_bottom.into(); + KERNEL_SPACE + .exclusive_access() + .remove_area_with_start_vpn(kernel_stack_bottom_va.into()); + } +} + + diff --git a/ch5/os/src/task/processor.rs b/ch5/os/src/task/processor.rs new file mode 100644 index 0000000..072782b --- /dev/null +++ b/ch5/os/src/task/processor.rs @@ -0,0 +1,170 @@ +use alloc::sync::Arc; +use lazy_static::lazy_static; +use crate::task::context::TaskContext; +use crate::task::switch::__switch; +use crate::task::task::{TaskControlBlock, TaskStatus}; +use crate::trap::TrapContext; +use crate::sync::UPSafeCell; +use crate::task::manager::{pop_task, TASK_MANAGER}; +use crate::timer::get_time_us; + +///维护在一个处理器上正在执行的任务, 这在ch4 中是 TaskManager做的, 现在拆分 +pub struct Processor { + /// 当前处理器上正在执行的任务, 他是一个被option 包裹的 智能指针, 保证 TCB 要不在这个结构体中, 要不就是在全局的任务管理器上 + current: Option>, + /// 当前处理器上的 idle 控制流的任务上下文, 当任务切换的时候, 先切换到这个 + /// 这样做的主要目的是使得换入/换出进程和调度执行流在内核层各自执行在不同的内核栈上, + /// 分别是进程自身的内核栈和内核初始化时使用的启动栈 + /// 这样的话, 调度相关的数据不会出现在进程内核栈上, + /// 也使得调度机制对于换出进程的Trap执行流是不可见的 + /// 它在决定换出的时候只需调用schedule而无需操心调度的事情 + /// 从而各执行流的分工更加明确了, 虽然带来了更大的开销 + /// 虽然 永远不会执行这个 task context 的任务, 他只保存 内核的启动栈 + idle_task_cx: TaskContext, + pub clock_time: usize, // 处理器时间 +} + + +impl Processor { + ///Create an empty Processor + /// 一旦程序开始运行, 就开始在内核栈或者用户栈之间切换, 永远 不会运行到这个, 这个就是保存 内核的启动栈的 + pub fn new() -> Self { + Self { + current: None, + idle_task_cx: TaskContext::new(), + clock_time: 0 + } + } +} + +impl Processor { + // 得到当前 task context 的指针 + fn get_idle_task_cx_ptr(&mut self) -> *mut TaskContext { + &mut self.idle_task_cx as *mut _ + } + /// 取出当前正在执行的任务 + pub fn take_current(&mut self) -> Option> { + self.current.take() + } + + ///当前正在执行的任务的 copy + pub fn current(&self) -> Option> { + self.current.as_ref().map(Arc::clone) + } + + // 掐表函数 + fn refresh_stop_clock(&mut self) -> usize { + // 上次停表的时间 + let start_time = self.clock_time; + // 当前时间 + self.clock_time = get_time_us(); + // 返回 当前时间 - 上次停表时间 = 时间间距 + self.clock_time - start_time + } + + // 即将进入用户态, 把之前使用的内核时间统计 + pub fn user_time_start(&mut self) { + let use_clock_time = self.refresh_stop_clock(); + self.current.as_ref().unwrap().inner_exclusive_access().kernel_time += use_clock_time; + } + pub fn kernel_time_start(&mut self) { + let use_clock_time = self.refresh_stop_clock(); + self.current.as_ref().unwrap().inner_exclusive_access().user_time += use_clock_time; + } +} + + + +// 全局 描述cpu状态的管理器 +lazy_static! { + pub static ref PROCESSOR: UPSafeCell = unsafe { UPSafeCell::new(Processor::new()) }; +} + + +// 循环 全局的任务管理器'fetch_task'获取需要运行的下一个进程, 并通过'__switch' 和当前process切换进程, +// 这和之前的 run_first_task 差不多 +pub fn run_tasks() { + loop { + // 获得当前 进程的管理对象 + let mut processor = PROCESSOR.exclusive_access(); + + // 弹出一个任务 + if let Some(task) = pop_task() { + // 当前运行的进程(第一次运行时,这里是空的 zero, 但是不影响, 因为永远不会执行到第一次运行的任务) + let idle_task_cx_ptr = processor.get_idle_task_cx_ptr(); + + // 获得下一个需要运行的进程 的task context的指针 + let mut task_inner = task.inner_exclusive_access(); + let next_task_cx_ptr = &task_inner.task_cx as *const TaskContext; + task_inner.task_status = TaskStatus::Running; + + // 删除内部可变性的引用, 这里需要手动释放 + drop(task_inner); + + // 修改process的状态, 把下一个需要运行的进程 move到 Process中被管理, 保证引用计数为1 + processor.current = Some(task); + + // 这里需要手动释放, 同上, 因为switch跳出作用域了, 不会通过作用域自动析构 refcell + drop(processor); + + unsafe { + __switch(idle_task_cx_ptr, next_task_cx_ptr); + + // 调用 schedule 之后 内部又调用__switch, next task 也就是idle task会走入到这里, 开启新的循环获取新的任务 + // 其实 idle task 并不会执行, 他只是保存 内核的启动栈 也就是当前函数 的寄存器状态 + } + } + } +} + + +// 取出当前正在执行的任务 +pub fn take_current_task() -> Option> { + PROCESSOR.exclusive_access().take_current() +} + +// 返回当前正在执行的任务的 copy版本 +pub fn current_task() -> Option> { + PROCESSOR.exclusive_access().current() +} + +//得到当前正在执行任务的token +pub fn current_user_token() -> usize { + // 得到当前正在执行的任务 + let task = current_task().unwrap(); + // 得到当前正在执行任务的token + let token = task.inner_exclusive_access().get_user_token(); + token +} + +// 得到当前 task context 的指针 +pub fn current_trap_cx() -> &'static mut TrapContext { + current_task() + .unwrap() + .inner_exclusive_access() + .get_trap_cx() +} + +// 扩张/缩减, 当前运行进程的地址空间中的堆段, 指定字节的数据 +pub fn change_program_brk(size: i32) -> Option { + current_task().as_ref().unwrap().inner_exclusive_access().change_program_brk(size) +} + + +// 传入当前运行的任务指针, 切换到 idle 任务 +// 用尽时间片, 或者调用 yield之后会调用这个, 调用这个之后, 会回到 run_task __switch返回的位置, 开启下一轮循环 中执行 +pub fn schedule(switched_task_cx_ptr: *mut TaskContext) { + // 进程管理对象 + let mut processor = PROCESSOR.exclusive_access(); + + // 得到当前进程管理中 task context 指针 + let idle_task_cx_ptr = processor.get_idle_task_cx_ptr(); + drop(processor); + + // 切换 汇编会保存当前的寄存器状态到 switched_task_cx_ptr 中, 然后根据 idle_task_cx_ptr 继续执行 __switch 的下一行, 结束当前循环, 开启新的循环 + unsafe { + __switch(switched_task_cx_ptr, idle_task_cx_ptr); + } +} + + diff --git a/ch5/os/src/task/task.rs b/ch5/os/src/task/task.rs index 46c9eaa..346f45f 100644 --- a/ch5/os/src/task/task.rs +++ b/ch5/os/src/task/task.rs @@ -1,29 +1,44 @@ +use alloc::sync::{Arc, Weak}; +use alloc::vec::Vec; +use core::cell::RefMut; use crate::config::{TRAP_CONTEXT}; use crate::mm::address::{PhysPageNum, VirtAddr}; use crate::mm::memory_set::{KERNEL_SPACE, MapPermission, MemorySet}; use crate::println; +use crate::sync::UPSafeCell; use crate::task::context::{TaskContext}; -use crate::task::pid::kernel_stack_position; +use crate::task::pid::{kernel_stack_position, KernelStack, pid_alloc, PidHandle}; use crate::trap::{trap_handler, TrapContext}; // TCB的字段, 用来保存任务的状态 #[derive(Copy, Clone, PartialEq)] pub enum TaskStatus { - UnInit, // 未初始化 - Ready, // 准备运行 - Running, // 正在运行 - Exited, // 已退出 + Ready, + Running, + Zombie, } - -// 一个任务的主体, 用来保存或者控制一个任务所有需要的东西 +// 把 ch4 的TaskManagerInner 和 TaskControlBlock 整合为 TaskControlBlock, 现在被 TaskManager 关联和管理 +// 在内核中, 它等价与一个进程 pub struct TaskControlBlock { + // 初始化后不再变化的元数据 + pub pid: PidHandle, + pub kernel_stack: KernelStack, + + // 初始化后 需要变化的元数据 + inner: UPSafeCell, +} + +pub struct TaskControlBlockInner { pub user_time: usize, // 用户态程序用的时间 pub kernel_time: usize, // 内核态程序所用的时间 - pub task_status: TaskStatus, + pub trap_cx_ppn: PhysPageNum, // tcb访问 trap context所在的真实的物理页, 它对应逻辑页的次高页 pub task_cx: TaskContext, + pub task_status: TaskStatus, pub memory_set: MemorySet, // tcb他自己的地址空间 - pub trap_cx_ppn: PhysPageNum, // tcb访问 trap context所在的真实的物理页, 它对应逻辑页的次高页 + pub parent: Option>, // 不包含父进程引用计数的弱指针 + pub children: Vec>, // 所有的子进程 arc智能指针, 只有引用计数为0的时候, 绑定的各类资源才能回收 + pub exit_code: i32, // 该进程退出时的 退出码 pub base_size: usize, // 应用地址空间中从0x0开始到用户栈结束一共包含多少字节, 就是用户数据有多大 pub heap_bottom: usize, // 堆的起始地址 这个一开始和base_size 是一样的 pub program_brk: usize, // 这个一开始和base_size 是一样的, 表示进程当前的堆边界, 即堆的顶部地址, 它指向堆中最后一个已分配内存块的末尾, 下一个内存分配将从该地址开始 @@ -31,11 +46,21 @@ pub struct TaskControlBlock { } impl TaskControlBlock { - pub fn from(elf_data: &[u8], app_id: usize) -> Self { + // 得到具有内部可变性的字段 + pub fn inner_exclusive_access(&self) -> RefMut<'_, TaskControlBlockInner> { + self.inner.exclusive_access() + } + pub fn get_pid(&self) -> usize { + self.pid.0 + } +} + +impl TaskControlBlock { + // 根据 elf data 新建一个 TCB, 这个只在初始化 INITPROC 时候用 + pub fn from(elf_data: &[u8]) -> 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 @@ -44,38 +69,38 @@ impl TaskControlBlock { .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); + // 从全局pid分配器, 得到一个pid + let pid_handle = pid_alloc(); + + // 根据pid, 得到内核栈并得到高地址(栈底) + let kernel_stack = KernelStack::from(&pid_handle); + let kernel_stack_top = kernel_stack.get_top(); - // 把用户的内核栈逻辑段, 映射并创建出来实际的物理页帧, 到内核地址空间, 供内核态可以进行寻址找到用户应用的内核栈, - // 这里使用虚拟映射 而不是恒等映射, 是因为需要动态调整? - // 这个只有在内核会用到 - 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, // - program_brk: user_sp, // + pid: pid_handle, + kernel_stack: kernel_stack, + inner: unsafe { + UPSafeCell::new(TaskControlBlockInner{ + user_time: 0, + kernel_time: 0, + task_status:TaskStatus::Ready, + task_cx: TaskContext::goto_trap_return(kernel_stack_top), // 根据内核栈构造任务切换上下文, 并把 switch任务切换的 ra 设置为 trap_return的函数地址 + memory_set, // 新增, 当前任务的地址空间 + parent: None, + children: Vec::new(), + trap_cx_ppn, // 新增 逻辑次高页的trap context, 对应的这个是真实的物理页, 我们这里保存一份, 省的在memory_set 里面查找了 + base_size: user_sp, // 用户栈顶以下 是代码中的各种逻辑段+栈段, 应用地址空间中从0x0开始到用户栈结束一共包含多少字节, 所以大小就是截止到user_sp + heap_bottom: user_sp, // + program_brk: user_sp, // + exit_code: 0, + }) + } }; + // prepare TrapContext in user space // 根据 trap_cx_ppn 构建 陷入 trap context 的结构体 - let trap_cx = task_control_block.get_trap_cx(); // 根据trap_cx_ppn得到真实的物理页的 trap context的地址 + let trap_cx = task_control_block.inner_exclusive_access().get_trap_cx(); // 根据trap_cx_ppn得到真实的物理页的 trap context的地址 *trap_cx = TrapContext::from( // 对 tcb 的 TrapContext 的物理页进行 修改, entry_point, // 在陷入完成后, 准备返回用户态执行的, 用户代码入口地址 user_sp, // 用户栈, 这个栈的地址是虚拟地址 @@ -97,6 +122,17 @@ impl TaskControlBlock { // base_size 用户栈顶距离0x0的距离, 即用户应用已知的大小 // } +} + +impl TaskControlBlockInner{ + // 根据 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() + } // 扩张/缩减, 当前应用的地址空间中的堆段, 指定字节的数据 pub fn change_program_brk(&mut self, size: i32) -> Option { @@ -128,15 +164,4 @@ impl TaskControlBlock { None } } -} - -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/ch5/os/src/trap/mod.rs b/ch5/os/src/trap/mod.rs index 24ab220..ba0b249 100644 --- a/ch5/os/src/trap/mod.rs +++ b/ch5/os/src/trap/mod.rs @@ -8,8 +8,10 @@ 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}; +use crate::task::{exit_current_and_run_next, suspend_current_and_run_next}; use crate::timer::set_next_trigger; +use crate::task::manager::TASK_MANAGER; +use crate::task::processor::{current_trap_cx, current_user_token}; @@ -35,10 +37,10 @@ pub fn trap_handler() -> ! { set_kernel_trap_entry(); // 进入到了内核态, 需要把之前的用户消耗时间统计在用户时间上 - TASK_MANAGER.kernel_time_start(); + // TASK_MANAGER.kernel_time_start(); // 得到当前用户应用 的 trap context, 需要调用一个函数, 因为用户应用的trap context 不再内核空间, 他是在用户空间的次高地址 - let cx = TASK_MANAGER.get_current_trap_cx(); + let cx = current_trap_cx(); let scause = scause::read(); // trap 发生的原因 let stval = stval::read(); // trap的附加信息 @@ -55,12 +57,12 @@ pub fn trap_handler() -> ! { | Trap::Exception(Exception::LoadFault) | Trap::Exception(Exception::LoadPageFault) => { println!("[kernel] PageFault in application, kernel killed it."); - exit_current_and_run_next(); + exit_current_and_run_next(-2); } // 非法指令 Trap::Exception(Exception::IllegalInstruction) => { println!("[kernel] IllegalInstruction in application, kernel killed it."); - exit_current_and_run_next(); + exit_current_and_run_next(-3); } Trap::Interrupt(Interrupt::SupervisorTimer) => { // 发生时钟中断之后, 继续设置下一个时钟中断的发起时间 @@ -78,7 +80,7 @@ pub fn trap_handler() -> ! { } } // 即将进入用户态, 把内核使用的时间统计在内核时间上 - TASK_MANAGER.user_time_start(); + // TASK_MANAGER.user_time_start(); trap_return() } @@ -99,7 +101,7 @@ 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中, 等下恢复完寄存器之后 修改的用户应用自己的页表 + let user_satp = current_user_token(); // 当前用户应用的 页表所在的地址 需要在trap.S中, 等下恢复完寄存器之后 修改的用户应用自己的页表 extern "C" { fn __alltraps(); fn __restore();