From 81f38f58c5545763dd306b5485acccaac741c650 Mon Sep 17 00:00:00 2001 From: zhangxinyu <840317537@qq.com> Date: Fri, 9 Jun 2023 00:04:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0exec,=20fork=20=E7=AD=89?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E8=B0=83=E7=94=A8,=20=E7=8E=B0=E5=9C=A8?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=BA=94=E7=94=A8=E5=8F=AF=E4=BB=A5=E5=92=8C?= =?UTF-8?q?os=E4=BA=A4=E4=BA=92=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ch5/os/src/main.rs | 2 +- ch5/os/src/mm/address.rs | 5 ++ ch5/os/src/mm/memory_set.rs | 40 +++++++++++++++ ch5/os/src/mm/page_table.rs | 58 ++++++++++++++++++++- ch5/os/src/syscall/fs.rs | 35 +++++++++++++ ch5/os/src/syscall/mod.rs | 11 ++++ ch5/os/src/syscall/process.rs | 97 ++++++++++++++++++++++++++++++++++- ch5/os/src/task/task.rs | 94 +++++++++++++++++++++++++++++++++ ch5/user/src/bin/initproc.rs | 1 + 9 files changed, 340 insertions(+), 3 deletions(-) diff --git a/ch5/os/src/main.rs b/ch5/os/src/main.rs index d8e9516..d54541f 100644 --- a/ch5/os/src/main.rs +++ b/ch5/os/src/main.rs @@ -57,7 +57,7 @@ pub fn rust_main(){ timer::set_next_trigger(); // 在进入用户态之前, 设置一个时钟中断, 防止第一个用户任务死循环 loader::list_apps(); task::add_initproc(); - // task::run_tasks(); + task::run_tasks(); panic!("Disable run here") } diff --git a/ch5/os/src/mm/address.rs b/ch5/os/src/mm/address.rs index 6e0a72f..6ea5178 100644 --- a/ch5/os/src/mm/address.rs +++ b/ch5/os/src/mm/address.rs @@ -80,6 +80,11 @@ impl PhysAddr { pub fn ceil(&self) -> PhysPageNum { PhysPageNum((self.0 + PAGE_SIZE - 1) / PAGE_SIZE) } + + // 把当前虚拟地址映射为指定类型T + pub fn get_mut(&self) -> &'static mut T { + unsafe { (self.0 as *mut T).as_mut().unwrap() } + } } diff --git a/ch5/os/src/mm/memory_set.rs b/ch5/os/src/mm/memory_set.rs index bee9770..62de02f 100644 --- a/ch5/os/src/mm/memory_set.rs +++ b/ch5/os/src/mm/memory_set.rs @@ -74,6 +74,15 @@ impl MapArea { map_perm, } } + // 根据当前逻辑段的信息, 创建另一个一样的逻辑段 + pub fn from_another(another: &Self) -> Self { + Self { + vpn_range: VPNRange{l:another.vpn_range.l, r:another.vpn_range.r}, + data_frames: BTreeMap::new(), + map_type: another.map_type, + map_perm: another.map_perm, + } + } } impl MapArea { @@ -460,4 +469,35 @@ impl MemorySet { false } } + + // clone 当前的 地址空间 并返回一个新的, 注意虽然地址空间是相同的, 但是 他们的物理地址并不相同 + pub fn from_existed_user(user_space: &Self) -> Self { + // 一个空的地址空间 + let mut memory_set = Self::new(); + // 映射跳板 + memory_set.map_trampoline(); + + // copy data sections/trap_context/user_stack + // 拷贝数据 + for area in user_space.areas.iter() { + // 根据 当前逻辑段的信息, 创建一个新的数据段 + let new_area = MapArea::from_another(area); + + // 把逻辑段信息 添加到当前地址空间(顺便也映射ppn) + memory_set.push(new_area, None); + + // copy data from another space + // 把 当前逻辑段从开始到结束, ppn的数据, copy 到 新的 ppn + for vpn in area.vpn_range { + let src_ppn = user_space.page_table.get_pte(vpn).unwrap().ppn(); + let dst_ppn = memory_set.page_table.get_pte(vpn).unwrap().ppn(); + dst_ppn + .get_bytes_array() + .copy_from_slice(src_ppn.get_bytes_array()); + } + } + memory_set + } + + } diff --git a/ch5/os/src/mm/page_table.rs b/ch5/os/src/mm/page_table.rs index c2aa4e3..16aaaf7 100644 --- a/ch5/os/src/mm/page_table.rs +++ b/ch5/os/src/mm/page_table.rs @@ -1,8 +1,9 @@ +use alloc::string::String; use alloc::vec; use alloc::vec::Vec; use bitflags::*; use crate::config::TRAMPOLINE; -use crate::mm::address::{PhysPageNum, VirtAddr, VirtPageNum}; +use crate::mm::address::{PhysAddr, PhysPageNum, VirtAddr, VirtPageNum}; use crate::mm::frame_allocator::{frame_alloc, FrameTracker}; use crate::println; @@ -172,6 +173,28 @@ impl PageTable { pub fn get_pte(&self, vpn: VirtPageNum) -> Option { self.find_pte(vpn).map(|pte| *pte) } + + // 根据虚拟地址, 找到页表中对应的地址 + pub fn get_pa(&self, va:VirtAddr) -> Option{ + // vp对应的vpn + let vpn = va.floor(); + + // 找到对应vpn对应的pte + if let Some(pte) = self.get_pte(vpn) { + // 从pte找到 真实的物理页帧 + let ppn = pte.ppn(); + + // 得到ppn的地址 + let pa: PhysAddr = ppn.into(); + let pa_usize: usize = pa.into(); + + // 虚拟地址对应的页内偏移 + let va_offset = va.page_offset(); + Some((pa_usize+va_offset).into()) + } else { + None + } + } } // 根据token得到用户应用的页表, 并根据ptr 和len 在页表中找到指定的数据 @@ -214,4 +237,37 @@ pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Vec<& v } +// 根据token, 转换指定 虚拟地址空间中的指针ptr 为 指定 物理页帧内真实地址的 引用 &T +pub fn translated_refmut(token: usize, ptr: *mut T) -> &'static mut T { + let page_table = PageTable::from_token(token); + let va = ptr as usize; + + page_table + .get_pa(VirtAddr::from(va)) + .unwrap() + .get_mut() +} + +// 从token中映射虚拟地址页表, 并根据 虚拟地址ptr 逐个累加地址, 在物理页帧真实地址获得ptr后面的每个字符, 遇到 0 截止 +pub fn translated_str(token: usize, ptr: *const u8) -> String { + let page_table = PageTable::from_token(token); + let mut string = String::new(); + let mut va = ptr as usize; + loop { + let ch: u8 = *(page_table + .get_pa(VirtAddr::from(va)) + .unwrap() + .get_mut() + ); + if ch == 0 { + break; + } else { + string.push(ch as char); + va += 1; + } + } + string +} + + diff --git a/ch5/os/src/syscall/fs.rs b/ch5/os/src/syscall/fs.rs index d252355..b6739b8 100644 --- a/ch5/os/src/syscall/fs.rs +++ b/ch5/os/src/syscall/fs.rs @@ -1,12 +1,47 @@ //! File and filesystem-related syscalls +use sbi_rt::legacy::console_getchar; use crate::mm::page_table::translated_byte_buffer; use crate::print; use crate::task::manager::TASK_MANAGER; use crate::task::processor::current_user_token; +use crate::task::suspend_current_and_run_next; const FD_STDOUT: usize = 1; +// 从标准输入读取 指定长度的字符到 虚拟地址buffer中, 目前只支持长度为1 +pub fn sys_read(fd: usize, buf: *const u8, len: usize) -> isize { + match fd { + FD_STDIN => { + assert_eq!(len, 1, "Only support len = 1 in sys_read!"); + let mut c: usize; + loop { + // 利用sbi 从键盘获取输入的 + c = console_getchar(); + // 如果是0 说明还没有输入 + if c == 0 { + // 切换进程 + suspend_current_and_run_next(); + continue; + } else { + break; + } + } + // 把读取的字符 写入到应用的地址空间 + let ch = c as u8; + // 由于目前长度只能为1, 这里直接获取第0个slice 写入 + let mut buffers = translated_byte_buffer(current_user_token(), buf, len); + unsafe { + buffers[0].as_mut_ptr().write_volatile(ch); + } + 1 + } + _ => { + panic!("Unsupported fd in sys_read!"); + } + } +} + /// write buf of length `len` to a file with `fd` pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize { match fd { diff --git a/ch5/os/src/syscall/mod.rs b/ch5/os/src/syscall/mod.rs index 41d7c4e..c610315 100644 --- a/ch5/os/src/syscall/mod.rs +++ b/ch5/os/src/syscall/mod.rs @@ -1,7 +1,13 @@ +const SYSCALL_READ: usize = 63; const SYSCALL_WRITE: usize = 64; const SYSCALL_EXIT: usize = 93; const SYSCALL_YIELD: usize = 124; const SYSCALL_GET_TIME: usize = 169; +const SYSCALL_GETPID: usize = 172; +const SYSCALL_SBRK: usize = 214; +const SYSCALL_FORK: usize = 220; +const SYSCALL_EXEC: usize = 221; +const SYSCALL_WAITPID: usize = 260; mod fs; mod process; @@ -14,11 +20,16 @@ use crate::println; pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize { match syscall_id { + SYSCALL_READ => sys_read(args[0], args[1] as *const u8, args[2]), SYSCALL_WRITE => sys_write(args[0], args[1] as *const u8, args[2]), SYSCALL_EXIT => sys_exit(args[0] as i32), SYSCALL_YIELD => sys_yield(), SYSCALL_GET_TIME => sys_get_time(), SYSCALL_SBRK => sys_sbrk(args[0] as i32), + SYSCALL_GETPID => sys_getpid(), + SYSCALL_FORK => sys_fork(), + SYSCALL_EXEC => sys_exec(args[0] as *const u8), + SYSCALL_WAITPID => sys_waitpid(args[0] as isize, args[1] as *mut i32), _ => panic!("Unsupported syscall_id: {}", syscall_id), } } \ No newline at end of file diff --git a/ch5/os/src/syscall/process.rs b/ch5/os/src/syscall/process.rs index 7bd2fe7..f0cc4a0 100644 --- a/ch5/os/src/syscall/process.rs +++ b/ch5/os/src/syscall/process.rs @@ -1,8 +1,13 @@ //! App management syscalls +use alloc::sync::Arc; +use crate::loader::get_app_data_by_name; +use crate::mm::page_table::{translated_refmut, translated_str}; // use crate::batch::run_next_app; use crate::println; use crate::task::{exit_current_and_run_next, suspend_current_and_run_next}; -use crate::task::processor::change_program_brk; +use crate::task::manager::add_task; +use crate::task::processor::{change_program_brk, current_task, current_user_token}; +use crate::task::task::TaskStatus; // use crate::task::{change_program_brk, exit_current_and_run_next, suspend_current_and_run_next}; use crate::timer::get_time_ms; @@ -29,4 +34,94 @@ pub fn sys_sbrk(size: i32) -> isize { } else { -1 } +} + +pub fn sys_getpid() -> isize { + current_task().unwrap().pid.0 as isize +} + +pub fn sys_fork() -> isize { + // 创建一个会 跳到trap_return的子进程 + let current_task = current_task().unwrap(); + let new_task = current_task.fork(); + let new_pid = new_task.pid.0; + + // 修改子进程 trap_context 中的返回值, 因为父进程在调用sys_call中之后会立即返回 + let trap_cx = new_task.inner_exclusive_access().get_trap_cx(); + // 我们之前 进入这个函数的时候, 已经对子进程的 结束陷入的进行返回的地址 +4了 + // 所以现在只需要改变子进程 陷入之后的返回值即可, 将来子进程会直接从 trap_return运行 + trap_cx.x[10] = 0; + // 添加子任务到全局的 任务管理器 + add_task(new_task); + + // 返回 子进程的pid 给父进程 + new_pid as isize +} + +// 根据 文件名, 创建替换当前执行的任务 +pub fn sys_exec(path: *const u8) -> isize { + // 当前token + let token = current_user_token(); + // 得到 文件名 + let path = translated_str(token, path); + // 根据文件名对应的二进制数据, 调用exec 替换 + if let Some(data) = get_app_data_by_name(path.as_str()) { + let task = current_task().unwrap(); + task.exec(data); + 0 + } else { + // 不存在 返回-1 + -1 + } +} + +pub fn sys_waitpid(pid: isize, exit_code_ptr: *mut i32) -> isize { + // 当前进程 这里是父进程 + let task = current_task().unwrap(); + // find a child process + + // 如果当前进程内没有对应pid的子进程 则返回-1, 如果传进来的pid是-1, 则表示任何一个子进程都是符合要求的 + let mut inner = task.inner_exclusive_access(); + if !inner + .children + .iter() + .any(|p| pid == -1 || pid as usize == p.get_pid()) + { + return -1; + // ---- release current PCB + } + + // 找到 指定进程且进程为 僵尸进程或者是任意进程 + let pair = inner.children.iter().enumerate().find(|(_, p)| { + // ++++ temporarily access child PCB lock exclusively + p.inner_exclusive_access().task_status == TaskStatus::Zombie && (pid == -1 || pid as usize == p.get_pid()) + // ++++ release child PCB + }); + + // 如果找到 + if let Some((idx, _)) = pair { + // 从子列表中删除并取出 + let child = inner.children.remove(idx); + // confirm that child will be deallocated after removing from children list + // 保证chlid当前是唯一存在的引用, 而不会出现在某个进程的子进程向量中, 更不会出现在处理器监控器或者任务管理器中, child在当前作用于结束后引用计数为0, 彻底删除, + // 作用域结束后, 内部的pid 和kernel stack 执行他们自己的drop方法(返还pid给pid管理器, 返还kernel stack的资源还给 KERNEL_SPACE, ), 还有 inner->memory_set->page_table->frames 三级页表本身所占的物理帧 + + assert_eq!(Arc::strong_count(&child), 1); + let found_pid = child.get_pid(); + // ++++ temporarily access child TCB exclusively + // 找到子进程的退出码 + let exit_code = child.inner_exclusive_access().exit_code; + // ++++ release child PCB + + // 将退出码, 写入到当前进程的地址空间 + let exit_code_ref = translated_refmut(inner.memory_set.token(), exit_code_ptr); + *exit_code_ref = exit_code; + + // 返回回收的子进程的pid + found_pid as isize + } else { + // 如果制定进程不是僵尸进程 返回-2, 上层用户应用调用的 wait pid 会继续等待 + -2 + } + // ---- release current PCB lock automatically } \ No newline at end of file diff --git a/ch5/os/src/task/task.rs b/ch5/os/src/task/task.rs index 346f45f..eaa3fa9 100644 --- a/ch5/os/src/task/task.rs +++ b/ch5/os/src/task/task.rs @@ -122,6 +122,100 @@ impl TaskControlBlock { // base_size 用户栈顶距离0x0的距离, 即用户应用已知的大小 // } + + // 从当前进程copy 一个子进程, 他和父进程有相同的地址空间, 但是它拥有自己的内核栈 用来存放 trap_context + // 子进程后续运行会 条入到 trap_return + pub fn fork(self: &Arc) -> Arc { + // ---- 当前进程的内容 + let mut parent_inner = self.inner_exclusive_access(); + + // 根据当前地址空间, copy 当前进程地址空间的数据到 一个新的地址空间 + let memory_set = MemorySet::from_existed_user(&parent_inner.memory_set); + + // 找到新的进程地址空间中 TRAP_CONTEXT 的物理页 + let trap_cx_ppn = memory_set + .page_table + .get_pte(VirtAddr::from(TRAP_CONTEXT).into()) + .unwrap() + .ppn(); + + // 分配一个pid + let pid_handle = pid_alloc(); + // 根据pid 分配一个 子进程自己的内核栈 + let kernel_stack = KernelStack::from(&pid_handle); + + // 创建tcb + let kernel_stack_top = kernel_stack.get_top(); + let task_control_block = Arc::new(TaskControlBlock { + pid: pid_handle, + kernel_stack, + inner: unsafe { + UPSafeCell::new(TaskControlBlockInner { + user_time: 0, + kernel_time: 0, + trap_cx_ppn, + base_size: parent_inner.base_size, + task_cx: TaskContext::goto_trap_return(kernel_stack_top), + task_status: TaskStatus::Ready, + memory_set, + parent: Some(Arc::downgrade(self)), // 将父进程的弱引用计数 放到子进程的进程控制块中 + children: Vec::new(), + exit_code: 0, + heap_bottom: 0, + program_brk: 0, + }) + }, + }); + + + // add child + // 添加子进程信息 到父进程中 + parent_inner.children.push(task_control_block.clone()); + + // 得到 tcb 当中 trap context 的可变引用, 上面复制时还是复制的父进程的 + // 现在修改其栈顶为我们新创建的 子进程自己的栈顶, 其他字段则是完全和父进程一样 + let trap_cx = task_control_block.inner_exclusive_access().get_trap_cx(); + trap_cx.kernel_sp = kernel_stack_top; + + // return + task_control_block + // ---- release parent PCB automatically + // **** release children PCB automatically + } + // TCB 加载一个新的应用, 从新的elf的 代码和数据替换 原有的旧的 应用地址空间的内容, 并开始执行 + // pid 和 kernel_stack 不变 + // 也不需要修改任务上下文环境, 因为只有被暂停的应用才需要再内核中保留一个任务上下文, 修改trap context 即可因 + pub fn exec(&self, elf_data: &[u8]) { + // memory_set with elf program headers/trampoline/trap context/user stack + // 根据 elf data 创建一个地址空间 + let (memory_set, user_sp, entry_point) = MemorySet::from_elf(elf_data); + let trap_cx_ppn = memory_set + .page_table + .get_pte(VirtAddr::from(TRAP_CONTEXT).into()) + .unwrap() + .ppn(); + + // **** access inner exclusively + let mut inner = self.inner_exclusive_access(); + // substitute memory_set + // 原有地址空间生命周期结束, 全部被回收 + inner.memory_set = memory_set; + // 更新trap ppn + inner.trap_cx_ppn = trap_cx_ppn; + // 更新应用大小 + inner.base_size = user_sp; + // 得到trap context 真实内存的指针 + let trap_cx = inner.get_trap_cx(); + *trap_cx = TrapContext::from( + entry_point, + user_sp, + KERNEL_SPACE.exclusive_access().token(), + self.kernel_stack.get_top(), + trap_handler as usize, + ); + // 作用域结束 释放inner + // **** release inner automatically + } } impl TaskControlBlockInner{ diff --git a/ch5/user/src/bin/initproc.rs b/ch5/user/src/bin/initproc.rs index 01012b5..2293ec1 100644 --- a/ch5/user/src/bin/initproc.rs +++ b/ch5/user/src/bin/initproc.rs @@ -6,6 +6,7 @@ use user_lib::{println, wait}; #[no_mangle] fn main() -> i32 { + println!("1"); if sys_fork() == 0 { // 注意需要手动添加\0 转为 c_str sys_exec("user_shell\0");