From 501ce6c4be996d620e4caf4ac3330f51a517ea54 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 21 Sep 2018 16:00:48 +0800 Subject: [PATCH 01/58] Fix memory crate test compile. --- crate/memory/src/paging/mock_page_table.rs | 2 +- crate/memory/src/swap/mock_swapper.rs | 2 +- crate/memory/src/swap/mod.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crate/memory/src/paging/mock_page_table.rs b/crate/memory/src/paging/mock_page_table.rs index fcba287b..d26b7af7 100644 --- a/crate/memory/src/paging/mock_page_table.rs +++ b/crate/memory/src/paging/mock_page_table.rs @@ -133,7 +133,7 @@ impl MockPageTable { #[cfg(test)] mod test { use super::*; - use alloc::arc::Arc; + use alloc::sync::Arc; use core::cell::RefCell; #[test] diff --git a/crate/memory/src/swap/mock_swapper.rs b/crate/memory/src/swap/mock_swapper.rs index c6b323c9..1db77085 100644 --- a/crate/memory/src/swap/mock_swapper.rs +++ b/crate/memory/src/swap/mock_swapper.rs @@ -1,5 +1,5 @@ use super::Swapper; -use alloc::btree_map::BTreeMap; +use alloc::collections::BTreeMap; use core::mem::uninitialized; const PAGE_SIZE: usize = 4096; diff --git a/crate/memory/src/swap/mod.rs b/crate/memory/src/swap/mod.rs index 5dcc7ae2..6456e198 100644 --- a/crate/memory/src/swap/mod.rs +++ b/crate/memory/src/swap/mod.rs @@ -135,7 +135,7 @@ impl DerefMut for SwapExt { mod test { use super::*; use super::mock_swapper::MockSwapper; - use alloc::{arc::Arc, boxed::Box}; + use alloc::{sync::Arc, boxed::Box}; use core::cell::RefCell; use paging::MockPageTable; From 7dd9494389259ad75e1f6cefee501375f8dc1c9d Mon Sep 17 00:00:00 2001 From: WangRunji Date: Sat, 22 Sep 2018 15:54:09 +0800 Subject: [PATCH 02/58] Add `Scheduler.move_to_head(pid)` to replace `Processor.next` Rename `set_reschedule` to `yield_now` --- crate/process/src/processor.rs | 21 ++++++++---------- crate/process/src/scheduler.rs | 40 ++++++++++++++++++++++++++++------ crate/process/src/thread.rs | 2 +- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/crate/process/src/processor.rs b/crate/process/src/processor.rs index 9083dad6..7db17f6a 100644 --- a/crate/process/src/processor.rs +++ b/crate/process/src/processor.rs @@ -33,8 +33,6 @@ pub struct Processor_ { procs: BTreeMap>, current_pid: Pid, event_hub: EventHub, - /// Choose what on next schedule ? - next: Option, // WARNING: if MAX_PROCESS_NUM is too large, will cause stack overflow scheduler: S, } @@ -65,7 +63,6 @@ impl Processor_ { }, current_pid: 0, event_hub: EventHub::new(), - next: None, scheduler, } } @@ -74,7 +71,7 @@ impl Processor_ { self.scheduler.set_priority(self.current_pid, priority); } - pub fn set_reschedule(&mut self) { + pub fn yield_now(&mut self) { let pid = self.current_pid; self.set_status(pid, Status::Ready); } @@ -108,7 +105,7 @@ impl Processor_ { pub fn tick(&mut self) { let current_pid = self.current_pid; if self.scheduler.tick(current_pid) { - self.set_reschedule(); + self.yield_now(); } self.event_hub.tick(); while let Some(event) = self.event_hub.pop() { @@ -116,12 +113,12 @@ impl Processor_ { match event { Event::Schedule => { self.event_hub.push(10, Event::Schedule); - self.set_reschedule(); + self.yield_now(); }, Event::Wakeup(pid) => { self.set_status(pid, Status::Ready); - self.set_reschedule(); - self.next = Some(pid); + self.yield_now(); + self.scheduler.move_to_head(pid); }, } } @@ -150,13 +147,13 @@ impl Processor_ { if self.get(self.current_pid).status == Status::Running { return; } - let pid = self.next.take().unwrap_or_else(|| self.scheduler.select().unwrap()); + let pid = self.scheduler.select().unwrap(); self.switch_to(pid); } - /// Switch process to `pid`, switch page table if necessary. - /// Store `rsp` and point it to target kernel stack. + /// Switch to process `pid`. /// The current status must be set before, and not be `Running`. + /// The target status must be `Ready`. fn switch_to(&mut self, pid: Pid) { // for debug print let pid0 = self.current_pid; @@ -203,7 +200,7 @@ impl Processor_ { if let Some(waiter) = self.find_waiter(pid) { info!(" then wakeup {}", waiter); self.set_status(waiter, Status::Ready); - self.next = Some(waiter); + self.scheduler.move_to_head(waiter); } } diff --git a/crate/process/src/scheduler.rs b/crate/process/src/scheduler.rs index 4d8e6964..9ab52fa3 100644 --- a/crate/process/src/scheduler.rs +++ b/crate/process/src/scheduler.rs @@ -9,6 +9,7 @@ pub trait Scheduler { fn select(&mut self) -> Option; fn tick(&mut self, current: Pid) -> bool; // need reschedule? fn set_priority(&mut self, pid: Pid, priority: u8); + fn move_to_head(&mut self, pid: Pid); } pub use self::rr::RRScheduler; @@ -79,6 +80,14 @@ mod rr { fn set_priority(&mut self, pid: usize, priority: u8) { } + + fn move_to_head(&mut self, pid: usize) { + let pid = pid + 1; + assert!(self.infos[pid].present); + self._list_remove(pid); + self._list_add_after(pid, 0); + trace!("rr move_to_head {}", pid - 1); + } } impl RRScheduler { @@ -95,6 +104,10 @@ mod rr { self.infos[prev].next = i; self.infos[at].prev = i; } + fn _list_add_after(&mut self, i: Pid, at: Pid) { + let next = self.infos[at].next; + self._list_add_before(i, next); + } fn _list_remove(&mut self, i: Pid) { let next = self.infos[i].next; let prev = self.infos[i].prev; @@ -156,13 +169,17 @@ mod stride { let info = &mut self.infos[pid]; assert!(info.present); info.present = false; - // BinaryHeap only support pop the top. - // So in order to remove an arbitrary element, - // we have to take all elements into a Vec, - // then push the rest back. - let rest: Vec<_> = self.queue.drain().filter(|&p| p.1 != pid).collect(); - use core::iter::FromIterator; - self.queue = BinaryHeap::from_iter(rest.into_iter()); + if self.queue.peek().is_some() && self.queue.peek().unwrap().1 == pid { + self.queue.pop(); + } else { + // BinaryHeap only support pop the top. + // So in order to remove an arbitrary element, + // we have to take all elements into a Vec, + // then push the rest back. + let rest: Vec<_> = self.queue.drain().filter(|&p| p.1 != pid).collect(); + use core::iter::FromIterator; + self.queue = BinaryHeap::from_iter(rest.into_iter()); + } trace!("stride remove {}", pid); } @@ -195,6 +212,15 @@ mod stride { self.infos[pid].priority = priority; trace!("stride {} priority = {}", pid, priority); } + + fn move_to_head(&mut self, pid: Pid) { + if self.queue.peek().is_some() { + let stride = -self.queue.peek().unwrap().0; + self.remove(pid); + self.infos[pid].stride = stride; + self.insert(pid); + } + } } impl StrideScheduler { diff --git a/crate/process/src/thread.rs b/crate/process/src/thread.rs index 1a93dd8c..8786cc8f 100644 --- a/crate/process/src/thread.rs +++ b/crate/process/src/thread.rs @@ -107,7 +107,7 @@ impl ThreadMod { pub fn yield_now() { info!("yield:"); let mut processor = S::processor(); - processor.set_reschedule(); + processor.yield_now(); processor.schedule(); } From fc2fd18c368f1c11decfc2c15b9e18b3841e26fe Mon Sep 17 00:00:00 2001 From: WangRunji Date: Sat, 22 Sep 2018 17:29:20 +0800 Subject: [PATCH 03/58] Add docs for thread::spawn() --- crate/process/src/thread.rs | 42 ++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/crate/process/src/thread.rs b/crate/process/src/thread.rs index 8786cc8f..818a28ad 100644 --- a/crate/process/src/thread.rs +++ b/crate/process/src/thread.rs @@ -73,34 +73,61 @@ impl ThreadMod { } /// Spawns a new thread, returning a JoinHandle for it. + /// + /// `F`: Type of the function `f` + /// `T`: Type of the return value of `f` pub fn spawn(f: F) -> JoinHandle where F: Send + 'static + FnOnce() -> T, T: Send + 'static, { info!("spawn:"); - let f = Box::into_raw(Box::new(f)); - let pid = S::processor().add(Context::new_kernel(kernel_thread_entry::, f as usize)); - return JoinHandle { - thread: Thread { pid, mark: PhantomData }, - mark: PhantomData, - }; + // 注意到下面的问题: + // Processor只能从入口地址entry+参数arg创建新线程 + // 而我们现在需要让它执行一个未知类型的(闭包)函数f + + // 首先把函数本体(代码数据)置于堆空间中 + let f = Box::into_raw(Box::new(f)); + + // 定义一个静态函数作为新线程的入口点 + // 其参数是函数f在堆上的指针 + // 这样我们就把函数f传到了一个静态函数内部 + // + // 注意到它具有泛型参数,因此对每一次spawn调用, + // 由于F类型是独特的,因此都会生成一个新的kernel_thread_entry extern fn kernel_thread_entry(f: usize) -> ! where S: ThreadSupport, F: Send + 'static + FnOnce() -> T, T: Send + 'static, { + // 在静态函数内部: + // 根据传进来的指针,恢复f let f = unsafe { Box::from_raw(f as *mut F) }; + // 调用f,并将其返回值也放在堆上 let ret = Box::new(f()); -// unsafe { LocalKey::::get_map() }.clear(); + // 清理本地线程存储 + // unsafe { LocalKey::::get_map() }.clear(); + // 让Processor退出当前线程 + // 把f返回值在堆上的指针,以线程返回码的形式传递出去 let mut processor = S::processor(); let pid = processor.current_pid(); processor.exit(pid, Box::into_raw(ret) as usize); processor.schedule(); + // 再也不会被调度回来了 unreachable!() } + + // 在Processor中创建新的线程 + let pid = S::processor().add(Context::new_kernel(kernel_thread_entry::, f as usize)); + + // 接下来看看`JoinHandle::join()`的实现 + // 了解是如何获取f返回值的 + return JoinHandle { + thread: Thread { pid, mark: PhantomData }, + mark: PhantomData, + }; } /// Cooperatively gives up a timeslice to the OS scheduler. @@ -155,6 +182,7 @@ impl JoinHandle { let mut processor = S::processor(); match processor.current_wait_for(self.thread.pid) { WaitResult::Ok(_, exit_code) => unsafe { + // Find return value on the heap from the exit code. Ok(*Box::from_raw(exit_code as *mut T)) } WaitResult::NotExist => Err(()), From 6cdf505f62d5467544d3a89ebdf535fc7d8ea63d Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 10 Oct 2018 00:50:13 +0800 Subject: [PATCH 04/58] Add OSLab/exp2 report --- docs/OSLab/exp2.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 docs/OSLab/exp2.md diff --git a/docs/OSLab/exp2.md b/docs/OSLab/exp2.md new file mode 100644 index 00000000..d346b23e --- /dev/null +++ b/docs/OSLab/exp2.md @@ -0,0 +1,48 @@ +# 2018操作系统专题训练 + +# 实验2:方案设计文档 + +计53 王润基 2015011279 + +## 实验目标 + +**基于RustOS,参考sv6完成多核实现和优化。** + +分为以下三个子任务: + +1. 实现x86_64和RISCV下的多核启动和通信 +2. 拓展线程管理模块,使之支持多核调度 +3. 学习sv6进行多核优化 + +## 相关工作和实验方案 + +1. 实现x86_64和RISCV下的多核启动和通信 + + x86_64下的多核启动已经完成,下面计划将其移植到Rust-OSDev项目的Bootloader中。 + + RISCV下尚未实现,这块第3组同学们有丰富经验。 + + 这部分计划与第3组合作,在第4周内完成。 + +2. 拓展线程管理模块,使之支持多核调度 + + 参照xv6 / ucore SMP实现一个可工作的版本。 + + 计划在第5周内完成。 + +3. 学习sv6进行多核优化 + + 已经完成[sv6 for RV64](https://github.com/twd2/sv6)在macOS上的复现。 + + 正在研究代码,并准备日后与twd2交流。 + + 计划在第6周移植一两个简单的实现到RustOS,并在之后视时间精力将其它部分逐渐移植过来。 + + + + 参考论文: + + * [The Scalable Commutativity Rule: Designing Scalable Software for Multicore Processors](https://pdos.csail.mit.edu/papers/commutativity:sosp13.pdf):Commuter项目论文,如何定量测试OS的并行度。鉴于时间有限,将其应用到RustOS应该无法在本学期完成。 + * [RadixVM: Scalable Address Spaces for Multithreaded Applications](http://pdos.csail.mit.edu/papers/radixvm:eurosys13.pdf):内存管理相关 + * [Scaling a file system to many cores using an operation log](http://delivery.acm.org/10.1145/3140000/3132779/p69-bhat.pdf?ip=183.172.124.170&id=3132779&acc=OA&key=BF85BBA5741FDC6E%2E587F3204F5B62A59%2E4D4702B0C3E38B35%2EEE2C838055815368&__acm__=1539103199_b0979df5a4432f0766f604f7a6e4809b):文件系统相关 + From 6cf0d6db237f1297ae916c50c4d3acec005aeb93 Mon Sep 17 00:00:00 2001 From: maoyuchaxue Date: Sun, 14 Oct 2018 13:50:59 +0800 Subject: [PATCH 05/58] fixed setting in riscv-pk to enable rv32ia, added smp option in Makefile --- kernel/Makefile | 10 ++++++---- kernel/src/arch/riscv32/boot/entry.asm | 11 ++++++++--- kernel/src/arch/riscv32/mod.rs | 5 +++-- riscv-pk/configure | 4 ++-- riscv-pk/configure.ac | 4 ++-- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/kernel/Makefile b/kernel/Makefile index 00531848..acca445d 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -12,11 +12,13 @@ # d = int | in_asm | ... QEMU debug info # mode = debug | release # LOG = off | error | warn | info | debug | trace +# smp SMP core number # board Only available on riscv32, build without bbl, run on board arch ?= riscv32 mode ?= debug LOG ?= debug +smp ?= 1 target := $(arch)-blog_os kernel := target/$(target)/$(mode)/ucore @@ -31,12 +33,12 @@ ifeq ($(arch), x86_64) qemu_opts := \ -drive format=raw,file=$(bootimage) \ -drive format=raw,file=$(SFSIMG),media=disk,cache=writeback \ - -smp 4 \ + -smp $(smp) \ -serial mon:stdio \ -device isa-debug-exit endif ifeq ($(arch), riscv32) -qemu_opts := -machine virt -kernel $(bin) -nographic +qemu_opts := -machine virt -kernel $(bin) -nographic -smp cpus=$(smp) endif ifdef board @@ -69,7 +71,7 @@ ifeq ($(uname), Darwin) prefix := x86_64-elf- endif ifeq ($(arch), riscv32) -prefix := riscv64-unknown-elf- +prefix := riscv32-unknown-elf- endif ld := $(prefix)ld @@ -118,7 +120,7 @@ else --enable-32bit \ --enable-logo \ --disable-fp-emulation \ - --host=riscv64-unknown-elf \ + --host=riscv32-unknown-elf \ --with-payload=$(abspath $(kernel)) && \ make && \ cp bbl ../../kernel/$@ diff --git a/kernel/src/arch/riscv32/boot/entry.asm b/kernel/src/arch/riscv32/boot/entry.asm index 8bb92685..0485f0ef 100644 --- a/kernel/src/arch/riscv32/boot/entry.asm +++ b/kernel/src/arch/riscv32/boot/entry.asm @@ -1,14 +1,19 @@ .section .text.entry .globl _start _start: - lui sp, %hi(bootstacktop) - addi sp, sp, %lo(bootstacktop) + add t0, a0, 1 + slli t0, t0, 16 + + lui sp, %hi(bootstack) + addi sp, sp, %lo(bootstack) + add sp, sp, t0 + call rust_main .section .bss .align 12 #PGSHIFT .global bootstack bootstack: - .space 4096 * 16 #KSTACKSIZE + .space 4096 * 16 * 8 .global bootstacktop bootstacktop: diff --git a/kernel/src/arch/riscv32/mod.rs b/kernel/src/arch/riscv32/mod.rs index 13d9ad30..298c8148 100644 --- a/kernel/src/arch/riscv32/mod.rs +++ b/kernel/src/arch/riscv32/mod.rs @@ -7,10 +7,11 @@ pub mod timer; pub mod paging; pub mod memory; pub mod compiler_rt; +pub mod smp; #[no_mangle] -pub extern fn rust_main() -> ! { - println!("Hello RISCV! {}", 123); +pub extern fn rust_main(hartid: usize) -> ! { + println!("Hello RISCV! {}", hartid); ::logging::init(); interrupt::init(); memory::init(); diff --git a/riscv-pk/configure b/riscv-pk/configure index 49dddf36..6b316dba 100755 --- a/riscv-pk/configure +++ b/riscv-pk/configure @@ -4084,8 +4084,8 @@ fi case "${BUILD_32BIT}" in yes|default) echo "Building 32-bit pk" - CFLAGS="$default_CFLAGS -march=rv32i -mabi=ilp32" - LDFLAGS="-march=rv32i -mabi=ilp32" + CFLAGS="$default_CFLAGS -march=rv32ia -mabi=ilp32" + LDFLAGS="-march=rv32ia -mabi=ilp32" install_subdir="riscv32-unknown-elf" ;; *) diff --git a/riscv-pk/configure.ac b/riscv-pk/configure.ac index 107a3f2d..5a06896d 100644 --- a/riscv-pk/configure.ac +++ b/riscv-pk/configure.ac @@ -88,8 +88,8 @@ AC_ARG_ENABLE([32bit], case "${BUILD_32BIT}" in yes|default) echo "Building 32-bit pk" - CFLAGS="$default_CFLAGS -march=rv32i -mabi=ilp32" - LDFLAGS="-march=rv32i -mabi=ilp32" + CFLAGS="$default_CFLAGS -march=rv32ia -mabi=ilp32" + LDFLAGS="-march=rv32ia -mabi=ilp32" install_subdir="riscv32-unknown-elf" ;; *) From 49cd04dce35cdc00c3ece0c39e9293a98fff345f Mon Sep 17 00:00:00 2001 From: maoyuchaxue Date: Sun, 14 Oct 2018 22:28:01 +0800 Subject: [PATCH 06/58] added rv32 smp booting, with slight modification to bbl --- kernel/src/arch/riscv32/consts.rs | 14 ++++ kernel/src/arch/riscv32/memory.rs | 2 + kernel/src/arch/riscv32/mod.rs | 23 +++++- kernel/src/arch/riscv32/smp.rs | 31 ++++++++ kernel/src/arch/x86_64/consts.rs | 97 +++++++++++++++++++++++ kernel/src/arch/x86_64/mod.rs | 1 + kernel/src/consts.rs | 124 +----------------------------- kernel/src/smp.rs | 4 + riscv-pk/bbl/bbl.c | 2 +- riscv-pk/machine/minit.c | 5 +- riscv-pk/machine/mtrap.h | 2 +- 11 files changed, 176 insertions(+), 129 deletions(-) create mode 100644 kernel/src/arch/riscv32/consts.rs create mode 100644 kernel/src/arch/riscv32/smp.rs create mode 100644 kernel/src/arch/x86_64/consts.rs create mode 100644 kernel/src/smp.rs diff --git a/kernel/src/arch/riscv32/consts.rs b/kernel/src/arch/riscv32/consts.rs new file mode 100644 index 00000000..f2adbcb7 --- /dev/null +++ b/kernel/src/arch/riscv32/consts.rs @@ -0,0 +1,14 @@ +// Physical address available on THINPAD: +// [0x80000000, 0x80800000] +const P2_SIZE: usize = 1 << 22; +const P2_MASK: usize = 0x3ff << 22; +pub const RECURSIVE_PAGE_PML4: usize = 0x3fe; +pub const KERNEL_OFFSET: usize = 0; +pub const KERNEL_PML4: usize = 0x8000_0000 >> 22; +pub const KERNEL_HEAP_OFFSET: usize = 0x8020_0000; +pub const KERNEL_HEAP_SIZE: usize = 0x0020_0000; +pub const MEMORY_OFFSET: usize = 0x8000_0000; +pub const MEMORY_END: usize = 0x8080_0000; +pub const USER_STACK_OFFSET: usize = 0x70000000; +pub const USER_STACK_SIZE: usize = 0x10000; +pub const USER32_STACK_OFFSET: usize = USER_STACK_OFFSET; \ No newline at end of file diff --git a/kernel/src/arch/riscv32/memory.rs b/kernel/src/arch/riscv32/memory.rs index 0f9cfb61..f9b55977 100644 --- a/kernel/src/arch/riscv32/memory.rs +++ b/kernel/src/arch/riscv32/memory.rs @@ -3,6 +3,8 @@ use memory::{active_table, FRAME_ALLOCATOR, init_heap, MemoryArea, MemoryAttr, M use super::riscv::{addr::*, register::sstatus}; use ucore_memory::PAGE_SIZE; +// static mut KERNEL_MS: Option = None; + pub fn init() { #[repr(align(4096))] struct PageData([u8; PAGE_SIZE]); diff --git a/kernel/src/arch/riscv32/mod.rs b/kernel/src/arch/riscv32/mod.rs index 298c8148..aadbdaf1 100644 --- a/kernel/src/arch/riscv32/mod.rs +++ b/kernel/src/arch/riscv32/mod.rs @@ -7,15 +7,34 @@ pub mod timer; pub mod paging; pub mod memory; pub mod compiler_rt; +pub mod consts; pub mod smp; +use self::smp::*; + +fn others_main(hartid: usize) -> ! { + println!("hart {} is booting", hartid); + loop { } +} + #[no_mangle] -pub extern fn rust_main(hartid: usize) -> ! { - println!("Hello RISCV! {}", hartid); +pub extern fn rust_main(hartid: usize, dtb: usize, hart_mask: usize) -> ! { + unsafe { set_cpu_id(hartid); } + + if hartid != 0 { + while unsafe { !has_started(hartid) } { } + others_main(hartid); + // others_main should not return + } + + println!("Hello RISCV! in hart {}, {}, {}", hartid, dtb, hart_mask); + ::logging::init(); interrupt::init(); memory::init(); timer::init(); + + unsafe { start_others(hart_mask); } ::kmain(); } diff --git a/kernel/src/arch/riscv32/smp.rs b/kernel/src/arch/riscv32/smp.rs new file mode 100644 index 00000000..aab6e4ea --- /dev/null +++ b/kernel/src/arch/riscv32/smp.rs @@ -0,0 +1,31 @@ +use consts::MAX_CPU_NUM; +use core::ptr::{read_volatile, write_volatile}; +use memory::*; + +static mut STARTED: [bool; MAX_CPU_NUM] = [false; MAX_CPU_NUM]; + +pub unsafe fn set_cpu_id(cpu_id: usize) { + unsafe { + asm!("mv tp, $0" : : "r"(cpu_id)); + } +} + +pub unsafe fn get_cpu_id() -> usize { + let mut cpu_id = 0; + unsafe { + asm!("mv $0, tp" : : "r" (cpu_id)); + } + cpu_id +} + +pub unsafe fn has_started(cpu_id: usize) -> bool { + read_volatile(&STARTED[cpu_id]) +} + +pub unsafe fn start_others(hart_mask: usize) { + for cpu_id in 0..MAX_CPU_NUM { + if (hart_mask >> cpu_id) & 1 != 0 { + write_volatile(&mut STARTED[cpu_id], true); + } + } +} \ No newline at end of file diff --git a/kernel/src/arch/x86_64/consts.rs b/kernel/src/arch/x86_64/consts.rs new file mode 100644 index 00000000..c2426c56 --- /dev/null +++ b/kernel/src/arch/x86_64/consts.rs @@ -0,0 +1,97 @@ +// Copy from Redox consts.rs: + +// Because the memory map is so important to not be aliased, it is defined here, in one place +// The lower 256 PML4 entries are reserved for userspace +// Each PML4 entry references up to 512 GB of memory +// The top (511) PML4 is reserved for recursive mapping +// The second from the top (510) PML4 is reserved for the kernel +/// The size of a single PML4 +pub const PML4_SIZE: usize = 0x0000_0080_0000_0000; +pub const PML4_MASK: usize = 0x0000_ff80_0000_0000; + +/// Offset of recursive paging +pub const RECURSIVE_PAGE_OFFSET: usize = (-(PML4_SIZE as isize)) as usize; +pub const RECURSIVE_PAGE_PML4: usize = (RECURSIVE_PAGE_OFFSET & PML4_MASK) / PML4_SIZE; + +/// Offset of kernel +pub const KERNEL_OFFSET: usize = RECURSIVE_PAGE_OFFSET - PML4_SIZE; +pub const KERNEL_PML4: usize = (KERNEL_OFFSET & PML4_MASK) / PML4_SIZE; + +pub const KERNEL_SIZE: usize = PML4_SIZE; + +/// Offset to kernel heap +pub const KERNEL_HEAP_OFFSET: usize = KERNEL_OFFSET - PML4_SIZE; +pub const KERNEL_HEAP_PML4: usize = (KERNEL_HEAP_OFFSET & PML4_MASK) / PML4_SIZE; +/// Size of kernel heap +pub const KERNEL_HEAP_SIZE: usize = 8 * 1024 * 1024; // 8 MB + +pub const MEMORY_OFFSET: usize = 0; + +/// Offset to kernel percpu variables +//TODO: Use 64-bit fs offset to enable this pub const KERNEL_PERCPU_OFFSET: usize = KERNEL_HEAP_OFFSET - PML4_SIZE; +pub const KERNEL_PERCPU_OFFSET: usize = 0xC000_0000; +/// Size of kernel percpu variables +pub const KERNEL_PERCPU_SIZE: usize = 64 * 1024; // 64 KB + +/// Offset to user image +pub const USER_OFFSET: usize = 0; +pub const USER_PML4: usize = (USER_OFFSET & PML4_MASK) / PML4_SIZE; + +/// Offset to user TCB +pub const USER_TCB_OFFSET: usize = 0xB000_0000; + +/// Offset to user arguments +pub const USER_ARG_OFFSET: usize = USER_OFFSET + PML4_SIZE / 2; + +/// Offset to user heap +pub const USER_HEAP_OFFSET: usize = USER_OFFSET + PML4_SIZE; +pub const USER_HEAP_PML4: usize = (USER_HEAP_OFFSET & PML4_MASK) / PML4_SIZE; + +/// Offset to user grants +pub const USER_GRANT_OFFSET: usize = USER_HEAP_OFFSET + PML4_SIZE; +pub const USER_GRANT_PML4: usize = (USER_GRANT_OFFSET & PML4_MASK) / PML4_SIZE; + +/// Offset to user stack +pub const USER_STACK_OFFSET: usize = USER_GRANT_OFFSET + PML4_SIZE; +pub const USER32_STACK_OFFSET: usize = 0xB000_0000; +pub const USER_STACK_PML4: usize = (USER_STACK_OFFSET & PML4_MASK) / PML4_SIZE; +/// Size of user stack +pub const USER_STACK_SIZE: usize = 1024 * 1024; // 1 MB + +/// Offset to user sigstack +pub const USER_SIGSTACK_OFFSET: usize = USER_STACK_OFFSET + PML4_SIZE; +pub const USER_SIGSTACK_PML4: usize = (USER_SIGSTACK_OFFSET & PML4_MASK) / PML4_SIZE; +/// Size of user sigstack +pub const USER_SIGSTACK_SIZE: usize = 256 * 1024; // 256 KB + +/// Offset to user TLS +pub const USER_TLS_OFFSET: usize = USER_SIGSTACK_OFFSET + PML4_SIZE; +pub const USER_TLS_PML4: usize = (USER_TLS_OFFSET & PML4_MASK) / PML4_SIZE; + +/// Offset to user temporary image (used when cloning) +pub const USER_TMP_OFFSET: usize = USER_TLS_OFFSET + PML4_SIZE; +pub const USER_TMP_PML4: usize = (USER_TMP_OFFSET & PML4_MASK) / PML4_SIZE; + +/// Offset to user temporary heap (used when cloning) +pub const USER_TMP_HEAP_OFFSET: usize = USER_TMP_OFFSET + PML4_SIZE; +pub const USER_TMP_HEAP_PML4: usize = (USER_TMP_HEAP_OFFSET & PML4_MASK) / PML4_SIZE; + +/// Offset to user temporary page for grants +pub const USER_TMP_GRANT_OFFSET: usize = USER_TMP_HEAP_OFFSET + PML4_SIZE; +pub const USER_TMP_GRANT_PML4: usize = (USER_TMP_GRANT_OFFSET & PML4_MASK) / PML4_SIZE; + +/// Offset to user temporary stack (used when cloning) +pub const USER_TMP_STACK_OFFSET: usize = USER_TMP_GRANT_OFFSET + PML4_SIZE; +pub const USER_TMP_STACK_PML4: usize = (USER_TMP_STACK_OFFSET & PML4_MASK) / PML4_SIZE; + +/// Offset to user temporary sigstack (used when cloning) +pub const USER_TMP_SIGSTACK_OFFSET: usize = USER_TMP_STACK_OFFSET + PML4_SIZE; +pub const USER_TMP_SIGSTACK_PML4: usize = (USER_TMP_SIGSTACK_OFFSET & PML4_MASK) / PML4_SIZE; + +/// Offset to user temporary tls (used when cloning) +pub const USER_TMP_TLS_OFFSET: usize = USER_TMP_SIGSTACK_OFFSET + PML4_SIZE; +pub const USER_TMP_TLS_PML4: usize = (USER_TMP_TLS_OFFSET & PML4_MASK) / PML4_SIZE; + +/// Offset for usage in other temporary pages +pub const USER_TMP_MISC_OFFSET: usize = USER_TMP_TLS_OFFSET + PML4_SIZE; +pub const USER_TMP_MISC_PML4: usize = (USER_TMP_MISC_OFFSET & PML4_MASK) / PML4_SIZE; \ No newline at end of file diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs index d1868f78..ba682c1a 100644 --- a/kernel/src/arch/x86_64/mod.rs +++ b/kernel/src/arch/x86_64/mod.rs @@ -12,6 +12,7 @@ pub mod idt; //pub mod smp; pub mod memory; pub mod io; +pub mod consts; /// The entry point of kernel #[no_mangle] // don't mangle the name of this function diff --git a/kernel/src/consts.rs b/kernel/src/consts.rs index 4dddae09..ea22abd7 100644 --- a/kernel/src/consts.rs +++ b/kernel/src/consts.rs @@ -1,128 +1,6 @@ #![allow(dead_code)] -#[cfg(target_arch = "riscv32")] -pub use self::riscv::*; -#[cfg(target_arch = "x86_64")] -pub use self::x86_64::*; +pub use arch::consts::*; pub const MAX_CPU_NUM: usize = 8; pub const MAX_PROCESS_NUM: usize = 48; - -#[cfg(target_arch = "riscv32")] -mod riscv { - // Physical address available on THINPAD: - // [0x80000000, 0x80800000] - const P2_SIZE: usize = 1 << 22; - const P2_MASK: usize = 0x3ff << 22; - pub const RECURSIVE_PAGE_PML4: usize = 0x3fe; - pub const KERNEL_OFFSET: usize = 0; - pub const KERNEL_PML4: usize = 0x8000_0000 >> 22; - pub const KERNEL_HEAP_OFFSET: usize = 0x8020_0000; - pub const KERNEL_HEAP_SIZE: usize = 0x0020_0000; - pub const MEMORY_OFFSET: usize = 0x8000_0000; - pub const MEMORY_END: usize = 0x8080_0000; - pub const USER_STACK_OFFSET: usize = 0x70000000; - pub const USER_STACK_SIZE: usize = 0x10000; - pub const USER32_STACK_OFFSET: usize = USER_STACK_OFFSET; -} - -#[cfg(target_arch = "x86_64")] -mod x86_64 { - // Copy from Redox consts.rs: - - // Because the memory map is so important to not be aliased, it is defined here, in one place - // The lower 256 PML4 entries are reserved for userspace - // Each PML4 entry references up to 512 GB of memory - // The top (511) PML4 is reserved for recursive mapping - // The second from the top (510) PML4 is reserved for the kernel - /// The size of a single PML4 - pub const PML4_SIZE: usize = 0x0000_0080_0000_0000; - pub const PML4_MASK: usize = 0x0000_ff80_0000_0000; - - /// Offset of recursive paging - pub const RECURSIVE_PAGE_OFFSET: usize = (-(PML4_SIZE as isize)) as usize; - pub const RECURSIVE_PAGE_PML4: usize = (RECURSIVE_PAGE_OFFSET & PML4_MASK) / PML4_SIZE; - - /// Offset of kernel - pub const KERNEL_OFFSET: usize = RECURSIVE_PAGE_OFFSET - PML4_SIZE; - pub const KERNEL_PML4: usize = (KERNEL_OFFSET & PML4_MASK) / PML4_SIZE; - - pub const KERNEL_SIZE: usize = PML4_SIZE; - - /// Offset to kernel heap - pub const KERNEL_HEAP_OFFSET: usize = KERNEL_OFFSET - PML4_SIZE; - pub const KERNEL_HEAP_PML4: usize = (KERNEL_HEAP_OFFSET & PML4_MASK) / PML4_SIZE; - /// Size of kernel heap - pub const KERNEL_HEAP_SIZE: usize = 8 * 1024 * 1024; // 8 MB - - pub const MEMORY_OFFSET: usize = 0; - - /// Offset to kernel percpu variables - //TODO: Use 64-bit fs offset to enable this pub const KERNEL_PERCPU_OFFSET: usize = KERNEL_HEAP_OFFSET - PML4_SIZE; - pub const KERNEL_PERCPU_OFFSET: usize = 0xC000_0000; - /// Size of kernel percpu variables - pub const KERNEL_PERCPU_SIZE: usize = 64 * 1024; // 64 KB - - /// Offset to user image - pub const USER_OFFSET: usize = 0; - pub const USER_PML4: usize = (USER_OFFSET & PML4_MASK) / PML4_SIZE; - - /// Offset to user TCB - pub const USER_TCB_OFFSET: usize = 0xB000_0000; - - /// Offset to user arguments - pub const USER_ARG_OFFSET: usize = USER_OFFSET + PML4_SIZE / 2; - - /// Offset to user heap - pub const USER_HEAP_OFFSET: usize = USER_OFFSET + PML4_SIZE; - pub const USER_HEAP_PML4: usize = (USER_HEAP_OFFSET & PML4_MASK) / PML4_SIZE; - - /// Offset to user grants - pub const USER_GRANT_OFFSET: usize = USER_HEAP_OFFSET + PML4_SIZE; - pub const USER_GRANT_PML4: usize = (USER_GRANT_OFFSET & PML4_MASK) / PML4_SIZE; - - /// Offset to user stack - pub const USER_STACK_OFFSET: usize = USER_GRANT_OFFSET + PML4_SIZE; - pub const USER32_STACK_OFFSET: usize = 0xB000_0000; - pub const USER_STACK_PML4: usize = (USER_STACK_OFFSET & PML4_MASK) / PML4_SIZE; - /// Size of user stack - pub const USER_STACK_SIZE: usize = 1024 * 1024; // 1 MB - - /// Offset to user sigstack - pub const USER_SIGSTACK_OFFSET: usize = USER_STACK_OFFSET + PML4_SIZE; - pub const USER_SIGSTACK_PML4: usize = (USER_SIGSTACK_OFFSET & PML4_MASK) / PML4_SIZE; - /// Size of user sigstack - pub const USER_SIGSTACK_SIZE: usize = 256 * 1024; // 256 KB - - /// Offset to user TLS - pub const USER_TLS_OFFSET: usize = USER_SIGSTACK_OFFSET + PML4_SIZE; - pub const USER_TLS_PML4: usize = (USER_TLS_OFFSET & PML4_MASK) / PML4_SIZE; - - /// Offset to user temporary image (used when cloning) - pub const USER_TMP_OFFSET: usize = USER_TLS_OFFSET + PML4_SIZE; - pub const USER_TMP_PML4: usize = (USER_TMP_OFFSET & PML4_MASK) / PML4_SIZE; - - /// Offset to user temporary heap (used when cloning) - pub const USER_TMP_HEAP_OFFSET: usize = USER_TMP_OFFSET + PML4_SIZE; - pub const USER_TMP_HEAP_PML4: usize = (USER_TMP_HEAP_OFFSET & PML4_MASK) / PML4_SIZE; - - /// Offset to user temporary page for grants - pub const USER_TMP_GRANT_OFFSET: usize = USER_TMP_HEAP_OFFSET + PML4_SIZE; - pub const USER_TMP_GRANT_PML4: usize = (USER_TMP_GRANT_OFFSET & PML4_MASK) / PML4_SIZE; - - /// Offset to user temporary stack (used when cloning) - pub const USER_TMP_STACK_OFFSET: usize = USER_TMP_GRANT_OFFSET + PML4_SIZE; - pub const USER_TMP_STACK_PML4: usize = (USER_TMP_STACK_OFFSET & PML4_MASK) / PML4_SIZE; - - /// Offset to user temporary sigstack (used when cloning) - pub const USER_TMP_SIGSTACK_OFFSET: usize = USER_TMP_STACK_OFFSET + PML4_SIZE; - pub const USER_TMP_SIGSTACK_PML4: usize = (USER_TMP_SIGSTACK_OFFSET & PML4_MASK) / PML4_SIZE; - - /// Offset to user temporary tls (used when cloning) - pub const USER_TMP_TLS_OFFSET: usize = USER_TMP_SIGSTACK_OFFSET + PML4_SIZE; - pub const USER_TMP_TLS_PML4: usize = (USER_TMP_TLS_OFFSET & PML4_MASK) / PML4_SIZE; - - /// Offset for usage in other temporary pages - pub const USER_TMP_MISC_OFFSET: usize = USER_TMP_TLS_OFFSET + PML4_SIZE; - pub const USER_TMP_MISC_PML4: usize = (USER_TMP_MISC_OFFSET & PML4_MASK) / PML4_SIZE; -} \ No newline at end of file diff --git a/kernel/src/smp.rs b/kernel/src/smp.rs new file mode 100644 index 00000000..3439d176 --- /dev/null +++ b/kernel/src/smp.rs @@ -0,0 +1,4 @@ +pub struct cpu { + pub id: usize +} + diff --git a/riscv-pk/bbl/bbl.c b/riscv-pk/bbl/bbl.c index 1b96a9d5..dbb92778 100644 --- a/riscv-pk/bbl/bbl.c +++ b/riscv-pk/bbl/bbl.c @@ -48,7 +48,7 @@ void boot_other_hart(uintptr_t unused __attribute__((unused))) } } - enter_supervisor_mode(entry, hartid, dtb_output()); + enter_supervisor_mode(entry, hartid, dtb_output(), ~disabled_hart_mask & hart_mask); } void boot_loader(uintptr_t dtb) diff --git a/riscv-pk/machine/minit.c b/riscv-pk/machine/minit.c index c3fce3d8..ca6f43b3 100644 --- a/riscv-pk/machine/minit.c +++ b/riscv-pk/machine/minit.c @@ -172,7 +172,7 @@ void init_other_hart(uintptr_t hartid, uintptr_t dtb) boot_other_hart(dtb); } -void enter_supervisor_mode(void (*fn)(uintptr_t), uintptr_t arg0, uintptr_t arg1) +void enter_supervisor_mode(void (*fn)(uintptr_t), uintptr_t arg0, uintptr_t arg1, uintptr_t arg2) { // Set up a PMP to permit access to all of memory. // Ignore the illegal-instruction trap if PMPs aren't supported. @@ -194,6 +194,7 @@ void enter_supervisor_mode(void (*fn)(uintptr_t), uintptr_t arg0, uintptr_t arg1 register uintptr_t a0 asm ("a0") = arg0; register uintptr_t a1 asm ("a1") = arg1; - asm volatile ("mret" : : "r" (a0), "r" (a1)); + register uintptr_t a2 asm ("a2") = arg2; + asm volatile ("mret" : : "r" (a0), "r" (a1), "r" (a2)); __builtin_unreachable(); } diff --git a/riscv-pk/machine/mtrap.h b/riscv-pk/machine/mtrap.h index eafdb14e..b4390887 100644 --- a/riscv-pk/machine/mtrap.h +++ b/riscv-pk/machine/mtrap.h @@ -63,7 +63,7 @@ void putstring(const char* s); #define assert(x) ({ if (!(x)) die("assertion failed: %s", #x); }) #define die(str, ...) ({ printm("%s:%d: " str "\n", __FILE__, __LINE__, ##__VA_ARGS__); poweroff(-1); }) -void enter_supervisor_mode(void (*fn)(uintptr_t), uintptr_t arg0, uintptr_t arg1) +void enter_supervisor_mode(void (*fn)(uintptr_t), uintptr_t arg0, uintptr_t arg1, uintptr_t arg2) __attribute__((noreturn)); void boot_loader(uintptr_t dtb); void boot_other_hart(uintptr_t dtb); From d55eae71226176da057c95c836a5be92f33f10fb Mon Sep 17 00:00:00 2001 From: maoyuchaxue Date: Tue, 16 Oct 2018 17:42:50 +0800 Subject: [PATCH 07/58] a late proposal.md --- docs/catfish-proposal.md | 72 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/catfish-proposal.md diff --git a/docs/catfish-proposal.md b/docs/catfish-proposal.md new file mode 100644 index 00000000..d738e4af --- /dev/null +++ b/docs/catfish-proposal.md @@ -0,0 +1,72 @@ +# Rust OS多核移植与基于PARD框架的线程级Label管理 方案设计文档 + +2015011251 王纪霆 + +## 实验目标 + ++ 完成RustOS在riscv32上的多核开启 ++ 使RustOS可在中科院计算所PARD RISCV硬件环境下运行 ++ 使RustOS能够在PARD上开启smp ++ 添加控制功能,使得RustOS可以控制/查看PARD的寄存器 + +如果以上这些要全部实现,可以预料地将无法在八周内完成。 + +## 实验背景 + +[LvNA/PARD](https://github.com/LvNA-system/labeled-RISC-V)是一个用于进行内存隔离的硬件系统。它基于[rocket-chip](https://github.com/freechipsproject/rocket-chip)实现,在通常的多核以外,在核与各存储设备间增加了寄存器和额外的模块,并且对总线访问添加了标签,两者相结合下,可以完成对总线访问的控制,即对各核能够使用的缓存大小、磁盘流量进行限制等。 + +但目前为止,这项工作还有一些问题。首先是作为控制流量的关键——control plane,并未暴露给各核,而需要通过硬件的JTAG机制与板子上的控制模块prm(内含一个linux系统)沟通,并且在prm上实现控制脚本。而prm又和各核无法直接沟通,这样,运行在各核上的OS不仅无法修改寄存器,也无法知晓自己所分配到的资源大小,与PARD系统完全隔离。 + +如此一来,这个系统仅能进行核间的隔离,而无法完成进程间的隔离。这是因为,为了区分进程、给两个进程打上不同的标签,就必须让OS可以主动修改和设置control plane。 + +解决这个问题,实现进程级的label管理就是这个项目的主要目的。 + +## 实验设计 + +实际上,本项目的两个方向,即多核移植和PARD移植,是可以独立完成的。因为也可以退而求其次,考虑在PARD上只运行单核的线程级标签管理。但最终的目的还是让RustOS接手PARD的所有核,作为一个多核OS运行,对整个系统的运行进行管理。 + +最理想的方法是让硬件把control plane映射到内存,作为各核的一个设备。但硬件非常复杂,并且中科院方也没有实现这一功能。所以还是对硬件不做修改,依旧通过prm管理control plane,而将prm用串口等和各核连接,让其作为核的一个设备,在核的控制下修改底层配置。 + +为此,需要做的具体来说是: + ++ 让RustOS可以在核上运行; ++ 在prm上写脚本(运行在完善的Linux环境,且有现成范例),控制control plane; ++ 在RustOS上写驱动,使其可以与prm交互完成控制; ++ 在RustOS中添加系统调用等,使得操作系统可以管理驱动; + +另一边,多核移植所要做的是: + ++ 开启多核,实现原子操作等,为内核中可能的竞争加锁(Rust已经帮我们做了很多); ++ 核间进程调度,load balance; + +多核这边打算做的简单一些,基本照搬uCore+ on rv64来做,因为主要的设计和优化工作应该交给wrj同学。 + +## 目前进度 + +PARD端: + ++ 完成了实现机理的调研 ++ 正在服务器上编译复现(工程太大,本地vivado编译不了) + +多核端: + ++ 完成了ucore+ smp的初步学习 ++ 学习了bbl,完成了RustOS on rv32的多核开启 + +## 暂时计划 + +如果必须前八周结束,估计是无法做完的。 + +根据能否在前八周过后继续做,有以下远近期的计划: + +### 短期 + ++ 完成rv32 on smp的移植(第5-6周) ++ 完成多核Rust OS在PARD上的运行(第7-8周) + +### 长期 + ++ 完成RustOS与PARD的交互协议构建(第9-11周) ++ 实现基于RustOS的进程级标签管理(第12周后) + +以上均基于只有本人一人工作的前提,若有其他人协助/加入则根据实际情况而定。 \ No newline at end of file From a34783c277043625ec85b92dd5dc75d66c74456f Mon Sep 17 00:00:00 2001 From: maoyuchaxue Date: Tue, 16 Oct 2018 18:05:16 +0800 Subject: [PATCH 08/58] a late proposal.pptx --- docs/catfish-proposal.pptx | Bin 0 -> 196065 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/catfish-proposal.pptx diff --git a/docs/catfish-proposal.pptx b/docs/catfish-proposal.pptx new file mode 100644 index 0000000000000000000000000000000000000000..e79c50410e0578073d03b95dcf5b9a5ca7b22629 GIT binary patch literal 196065 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Ν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 literal 0 HcmV?d00001 From cfda03a0f2767e0b6aed0a636410d50ea03e4531 Mon Sep 17 00:00:00 2001 From: maoyuchaxue Date: Wed, 17 Oct 2018 00:21:18 +0800 Subject: [PATCH 09/58] trying to add atomic implementations in rv32, but still buggy --- kernel/riscv32-blog_os.json | 2 +- kernel/src/arch/riscv32/compiler_rt.rs | 38 ++++++++++++++++++-------- kernel/src/logging.rs | 5 ++++ 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/kernel/riscv32-blog_os.json b/kernel/riscv32-blog_os.json index ab160b8d..e7d7429b 100644 --- a/kernel/riscv32-blog_os.json +++ b/kernel/riscv32-blog_os.json @@ -9,7 +9,7 @@ "cpu": "generic-rv32", "features": "", "max-atomic-width": "32", - "linker": "riscv64-unknown-elf-ld", + "linker": "riscv32-unknown-elf-ld", "linker-flavor": "ld", "pre-link-args": { "ld": [ diff --git a/kernel/src/arch/riscv32/compiler_rt.rs b/kernel/src/arch/riscv32/compiler_rt.rs index 80dbdf8a..8b0a6851 100644 --- a/kernel/src/arch/riscv32/compiler_rt.rs +++ b/kernel/src/arch/riscv32/compiler_rt.rs @@ -28,37 +28,51 @@ use core::ptr::{read, write}; #[no_mangle] pub unsafe extern fn __atomic_load_1(src: *const u8) -> u8 { - read(src) + let mut res: u8 = 0; + asm!("amoadd.w.rl $0, zero, ($1)" : "=r"(res) : "r"(src) : "memory" : "volatile"); + res } #[no_mangle] pub unsafe extern fn __atomic_load_2(src: *const u16) -> u16 { - read(src) + let mut res: u16 = 0; + asm!("amoadd.w.rl $0, zero, ($1)" : "=r"(res) : "r"(src) : "memory" : "volatile"); + res } #[no_mangle] pub unsafe extern fn __atomic_load_4(src: *const u32) -> u32 { - read(src) + let mut res: u32 = 0; + asm!("amoadd.w.rl $0, zero, ($1)" : "=r"(res) : "r"(src) : "memory" : "volatile"); + res } #[no_mangle] pub unsafe extern fn __atomic_store_1(dst: *mut u8, val: u8) { - write(dst, val) + asm!("amoswap.w.aq zero, $0, ($1)" :: "r"(val), "r"(dst) : "memory" : "volatile"); } #[no_mangle] pub unsafe extern fn __atomic_store_4(dst: *mut u32, val: u32) { - write(dst, val) + asm!("amoswap.w.aq zero, $0, ($1)" :: "r"(val), "r"(dst) : "memory" : "volatile"); } unsafe fn __atomic_compare_exchange(dst: *mut T, expected: *mut T, desired: T) -> bool { - use super::interrupt; - let flags = interrupt::disable_and_store(); - let val = read(dst); - let success = val == read(expected); - write(dst, if success {desired} else {val}); - interrupt::restore(flags); - success + // use super::interrupt; + // let flags = interrupt::disable_and_store(); + // let val = read(dst); + // let success = val == read(expected); + // write(dst, if success {desired} else {val}); + // interrupt::restore(flags); + // success + let mut val: T; + asm!("lr.w $0, ($1)" : "=r"(val) : "r"(dst) : "memory" : "volatile"); + if val == *expected { + let mut sc_ret = 0; + asm!("sc.w $0, $1, ($2)" : "=r"(sc_ret) : "r"(desired), "r"(dst) : "memory" : "volatile"); + return sc_ret == 0 + } + false } #[no_mangle] diff --git a/kernel/src/logging.rs b/kernel/src/logging.rs index a007cb4b..0aeb89a4 100644 --- a/kernel/src/logging.rs +++ b/kernel/src/logging.rs @@ -1,5 +1,8 @@ use core::fmt; use log::{self, Level, LevelFilter, Log, Metadata, Record}; +use spin::Mutex; + +static log_mutex: Mutex<()> = Mutex::new(()); pub fn init() { static LOGGER: SimpleLogger = SimpleLogger; @@ -38,11 +41,13 @@ macro_rules! with_color { fn print_in_color(args: fmt::Arguments, color: Color) { use arch::io; + let mutex = log_mutex.lock(); io::putfmt(with_color!(args, color)); } pub fn print(args: fmt::Arguments) { use arch::io; + let mutex = log_mutex.lock(); io::putfmt(args); } From f7b7b1bcd6187f47e974674cd9d47d37150a4f0e Mon Sep 17 00:00:00 2001 From: maoyuchaxue Date: Wed, 17 Oct 2018 19:37:53 +0800 Subject: [PATCH 10/58] added workaround for atomic ops --- kernel/Makefile | 1 + kernel/build.rs | 6 ++ kernel/src/arch/riscv32/compiler_rt.c | 57 ++++++++++++++++++ kernel/src/arch/riscv32/compiler_rt.rs | 65 +++++++++++---------- kernel/src/logging.rs | 6 +- kernel/src/sync/arch/riscv32/atomic_lock.rs | 40 +++++++++++++ kernel/src/sync/arch/x86_64/atomic_lock.rs | 30 ++++++++++ kernel/src/sync/mod.rs | 9 +++ kernel/src/sync/mutex.rs | 16 ++--- 9 files changed, 190 insertions(+), 40 deletions(-) create mode 100644 kernel/src/arch/riscv32/compiler_rt.c create mode 100644 kernel/src/sync/arch/riscv32/atomic_lock.rs create mode 100644 kernel/src/sync/arch/x86_64/atomic_lock.rs diff --git a/kernel/Makefile b/kernel/Makefile index acca445d..ae797945 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -77,6 +77,7 @@ endif ld := $(prefix)ld objdump := $(prefix)objdump cc := $(prefix)gcc +CC := $(cc) as := $(prefix)as .PHONY: all clean run build asm doc justrun kernel diff --git a/kernel/build.rs b/kernel/build.rs index af15f66d..0cd473a8 100644 --- a/kernel/build.rs +++ b/kernel/build.rs @@ -12,6 +12,12 @@ fn main() { // .compile("cobj"); gen_vector_asm().unwrap(); } + if std::env::var("TARGET").unwrap().find("riscv32").is_some() { + cc::Build::new() + .file("src/arch/riscv32/compiler_rt.c") + .flag("-march=rv32ima") + .compile("atomic_rt"); + } } fn gen_vector_asm() -> Result<()> { diff --git a/kernel/src/arch/riscv32/compiler_rt.c b/kernel/src/arch/riscv32/compiler_rt.c new file mode 100644 index 00000000..8aa0e99c --- /dev/null +++ b/kernel/src/arch/riscv32/compiler_rt.c @@ -0,0 +1,57 @@ + + +// fn __atomic_load_1_workaround(src: *const u8) -> u8; +// fn __atomic_load_2_workaround(src: *const u16) -> u16; +// fn __atomic_load_4_workaround(src: *const u32) -> u32; +// fn __atomic_store_1_workaround(dst: *mut u8, val: u8); +// fn __atomic_store_4_workaround(dst: *mut u32, val: u32); +// fn __atomic_compare_exchange_1_workaround(dst: *mut u8, expected: *mut u8, desired: u8) -> bool; +// fn __atomic_compare_exchange_4_workaround(dst: *mut u32, expected: *mut u32, desired: u32) -> bool; + +char __atomic_load_1_workaround(char *src) { + char res = 0; + __asm__ __volatile__("amoadd.w.rl %0, zero, (%1)" : "=r"(res) : "r"(src) : "memory"); + return res; +} + +short __atomic_load_2_workaround(short *src) { + short res = 0; + __asm__ __volatile__("amoadd.w.rl %0, zero, (%1)" : "=r"(res) : "r"(src) : "memory"); + return res; +} + +int __atomic_load_4_workaround(int *src) { + int res = 0; + __asm__ __volatile__("amoadd.w.rl %0, zero, (%1)" : "=r"(res) : "r"(src) : "memory"); + return res; +} + +char __atomic_store_1_workaround(char *dst, char val) { + __asm__ __volatile__("amoswap.w.aq zero, %0, (%1)" :: "r"(val), "r"(dst) : "memory"); +} + +int __atomic_store_4_workaround(int *dst, int val) { + __asm__ __volatile__("amoswap.w.aq zero, %0, (%1)" :: "r"(val), "r"(dst) : "memory"); +} + +char __atomic_compare_exchange_1_workaround(char* dst, char* expected, char desired) { + char val = 0; + __asm__ __volatile__("lr.w %0, (%1)" : "=r"(val) : "r"(dst) : "memory"); + if (val == *expected) { + int sc_ret = 0; + __asm__ __volatile__("sc.w %0, %1, (%2)" : "=r"(sc_ret) : "r"(desired), "r"(dst) : "memory"); + return sc_ret == 0; + } + return 0; +} + +char __atomic_compare_exchange_4_workaround(int* dst, int* expected, int desired) { + int val = 0; + __asm__ __volatile__("lr.w %0, (%1)" : "=r"(val) : "r"(dst) : "memory"); + if (val == *expected) { + int sc_ret = 0; + __asm__ __volatile__("sc.w %0, %1, (%2)" : "=r"(sc_ret) : "r"(desired), "r"(dst) : "memory"); + return sc_ret == 0; + } + return 0; +} \ No newline at end of file diff --git a/kernel/src/arch/riscv32/compiler_rt.rs b/kernel/src/arch/riscv32/compiler_rt.rs index 8b0a6851..bb94eb05 100644 --- a/kernel/src/arch/riscv32/compiler_rt.rs +++ b/kernel/src/arch/riscv32/compiler_rt.rs @@ -2,6 +2,17 @@ //! //! [atomic](http://llvm.org/docs/Atomics.html#libcalls-atomic) +#[link(name = "atomic_rt")] +extern { + fn __atomic_load_1_workaround(src: *const u8) -> u8; + fn __atomic_load_2_workaround(src: *const u16) -> u16; + fn __atomic_load_4_workaround(src: *const u32) -> u32; + fn __atomic_store_1_workaround(dst: *mut u8, val: u8); + fn __atomic_store_4_workaround(dst: *mut u32, val: u32); + fn __atomic_compare_exchange_1_workaround(dst: *mut u8, expected: *mut u8, desired: u8) -> bool; + fn __atomic_compare_exchange_4_workaround(dst: *mut u32, expected: *mut u32, desired: u32) -> bool; +} + /// Copy from: /// https://github.com/rust-lang-nursery/compiler-builtins/blob/master/src/riscv32.rs #[no_mangle] @@ -28,59 +39,53 @@ use core::ptr::{read, write}; #[no_mangle] pub unsafe extern fn __atomic_load_1(src: *const u8) -> u8 { - let mut res: u8 = 0; - asm!("amoadd.w.rl $0, zero, ($1)" : "=r"(res) : "r"(src) : "memory" : "volatile"); - res + __atomic_load_1_workaround(src) } #[no_mangle] pub unsafe extern fn __atomic_load_2(src: *const u16) -> u16 { - let mut res: u16 = 0; - asm!("amoadd.w.rl $0, zero, ($1)" : "=r"(res) : "r"(src) : "memory" : "volatile"); - res + __atomic_load_2_workaround(src) } #[no_mangle] pub unsafe extern fn __atomic_load_4(src: *const u32) -> u32 { - let mut res: u32 = 0; - asm!("amoadd.w.rl $0, zero, ($1)" : "=r"(res) : "r"(src) : "memory" : "volatile"); - res + __atomic_load_4_workaround(src) } #[no_mangle] pub unsafe extern fn __atomic_store_1(dst: *mut u8, val: u8) { - asm!("amoswap.w.aq zero, $0, ($1)" :: "r"(val), "r"(dst) : "memory" : "volatile"); + __atomic_store_1_workaround(dst, val); } #[no_mangle] pub unsafe extern fn __atomic_store_4(dst: *mut u32, val: u32) { - asm!("amoswap.w.aq zero, $0, ($1)" :: "r"(val), "r"(dst) : "memory" : "volatile"); + __atomic_store_4_workaround(dst, val); } -unsafe fn __atomic_compare_exchange(dst: *mut T, expected: *mut T, desired: T) -> bool { - // use super::interrupt; - // let flags = interrupt::disable_and_store(); - // let val = read(dst); - // let success = val == read(expected); - // write(dst, if success {desired} else {val}); - // interrupt::restore(flags); - // success - let mut val: T; - asm!("lr.w $0, ($1)" : "=r"(val) : "r"(dst) : "memory" : "volatile"); - if val == *expected { - let mut sc_ret = 0; - asm!("sc.w $0, $1, ($2)" : "=r"(sc_ret) : "r"(desired), "r"(dst) : "memory" : "volatile"); - return sc_ret == 0 - } - false -} +// unsafe fn __atomic_compare_exchange(dst: *mut T, expected: *mut T, desired: T) -> bool { +// // use super::interrupt; +// // let flags = interrupt::disable_and_store(); +// // let val = read(dst); +// // let success = val == read(expected); +// // write(dst, if success {desired} else {val}); +// // interrupt::restore(flags); +// // success +// // let mut val: T; +// // asm!("lr.w $0, ($1)" : "=r"(val) : "r"(dst) : "memory" : "volatile"); +// // if val == *expected { +// // let mut sc_ret = 0; +// // asm!("sc.w $0, $1, ($2)" : "=r"(sc_ret) : "r"(desired), "r"(dst) : "memory" : "volatile"); +// // return sc_ret == 0 +// // } +// false +// } #[no_mangle] pub unsafe extern fn __atomic_compare_exchange_1(dst: *mut u8, expected: *mut u8, desired: u8) -> bool { - __atomic_compare_exchange(dst, expected, desired) + __atomic_compare_exchange_1_workaround(dst, expected, desired) } #[no_mangle] pub unsafe extern fn __atomic_compare_exchange_4(dst: *mut u32, expected: *mut u32, desired: u32) -> bool { - __atomic_compare_exchange(dst, expected, desired) + __atomic_compare_exchange_4_workaround(dst, expected, desired) } \ No newline at end of file diff --git a/kernel/src/logging.rs b/kernel/src/logging.rs index 0aeb89a4..c1f2241a 100644 --- a/kernel/src/logging.rs +++ b/kernel/src/logging.rs @@ -1,8 +1,10 @@ use core::fmt; use log::{self, Level, LevelFilter, Log, Metadata, Record}; -use spin::Mutex; +use sync::SpinLock as Mutex; -static log_mutex: Mutex<()> = Mutex::new(()); +lazy_static! { + static ref log_mutex: Mutex<()> = Mutex::new(()); +} pub fn init() { static LOGGER: SimpleLogger = SimpleLogger; diff --git a/kernel/src/sync/arch/riscv32/atomic_lock.rs b/kernel/src/sync/arch/riscv32/atomic_lock.rs new file mode 100644 index 00000000..0ee4c916 --- /dev/null +++ b/kernel/src/sync/arch/riscv32/atomic_lock.rs @@ -0,0 +1,40 @@ +//! RISCV atomic is not currently supported by Rust. +//! This is a ugly workaround. + + +use arch::compiler_rt::{__atomic_compare_exchange_4, __atomic_store_4, __atomic_load_4}; +use core::cell::UnsafeCell; + +pub struct AtomicLock +{ + lock: UnsafeCell +} + +impl AtomicLock +{ + pub fn new() -> Self { + AtomicLock { + lock: UnsafeCell::new(0) + } + } + + /// Returns 1 if lock is acquired + pub fn try_lock(&self) -> bool { + let mut expected: u32 = 0; + unsafe { + __atomic_compare_exchange_4(self.lock.get(), &mut expected as *mut u32, 1) + } + } + + pub fn load(&self) -> bool { + unsafe { + __atomic_load_4(self.lock.get()) == 1 + } + } + + pub fn store(&self) { + unsafe { + __atomic_store_4(self.lock.get(), 0); + } + } +} diff --git a/kernel/src/sync/arch/x86_64/atomic_lock.rs b/kernel/src/sync/arch/x86_64/atomic_lock.rs new file mode 100644 index 00000000..ddd4b236 --- /dev/null +++ b/kernel/src/sync/arch/x86_64/atomic_lock.rs @@ -0,0 +1,30 @@ + +pub struct AtomicLock +{ + lock: usize +} + +impl AtomicLock +{ + pub fn new() -> AtomicLock { + AtomicLock { + lock: ATOMIC_BOOL_INIT + } + } + + pub fn try_lock(&self) -> bool { + self.lock.compare_and_swap(false, true, Ordering::Acquire) == false + } + + pub fn load(&self) -> bool { + self.lock.load(Ordering::Relaxed) + } + + pub fn store(&self) { + self.lock.store(false, Ordering::Release); + } +} + +pub const ATOMIC_LOCK_INIT: AtomicLock = AtomicLock { + lock: ATOMIC_BOOL_INIT +}; \ No newline at end of file diff --git a/kernel/src/sync/mod.rs b/kernel/src/sync/mod.rs index cfa5071b..c2bae784 100644 --- a/kernel/src/sync/mod.rs +++ b/kernel/src/sync/mod.rs @@ -53,6 +53,15 @@ pub use self::condvar::*; pub use self::mutex::*; pub use self::semaphore::*; +#[allow(dead_code)] +#[cfg(target_arch = "x86_64")] +#[path = "arch/x86_64/atomic_lock.rs"] +pub mod atomic_lock; + +#[cfg(target_arch = "riscv32")] +#[path = "arch/riscv32/atomic_lock.rs"] +pub mod atomic_lock; + mod mutex; mod condvar; mod semaphore; diff --git a/kernel/src/sync/mutex.rs b/kernel/src/sync/mutex.rs index c8e62ea4..d542e763 100644 --- a/kernel/src/sync/mutex.rs +++ b/kernel/src/sync/mutex.rs @@ -30,8 +30,8 @@ use arch::interrupt; use core::cell::UnsafeCell; use core::fmt; use core::ops::{Deref, DerefMut}; -use core::sync::atomic::{ATOMIC_BOOL_INIT, AtomicBool, Ordering}; use super::Condvar; +use super::atomic_lock::AtomicLock; pub type SpinLock = Mutex; pub type SpinNoIrqLock = Mutex; @@ -39,7 +39,7 @@ pub type ThreadLock = Mutex; pub struct Mutex { - lock: AtomicBool, + lock: AtomicLock, support: S, data: UnsafeCell, } @@ -78,7 +78,7 @@ impl Mutex /// ``` pub fn new(user_data: T) -> Mutex { Mutex { - lock: ATOMIC_BOOL_INIT, + lock: AtomicLock::new(), data: UnsafeCell::new(user_data), support: S::new(), } @@ -96,9 +96,9 @@ impl Mutex impl Mutex { fn obtain_lock(&self) { - while self.lock.compare_and_swap(false, true, Ordering::Acquire) != false { + while !self.lock.try_lock() { // Wait until the lock looks unlocked before retrying - while self.lock.load(Ordering::Relaxed) { + while self.lock.load() { self.support.cpu_relax(); } } @@ -137,14 +137,14 @@ impl Mutex /// /// If the lock isn't held, this is a no-op. pub unsafe fn force_unlock(&self) { - self.lock.store(false, Ordering::Release); + self.lock.store(); } /// Tries to lock the mutex. If it is already locked, it will return None. Otherwise it returns /// a guard within Some. pub fn try_lock(&self) -> Option> { let support_guard = S::before_lock(); - if self.lock.compare_and_swap(false, true, Ordering::Acquire) == false { + if self.lock.try_lock() { Some(MutexGuard { mutex: self, support_guard, @@ -186,7 +186,7 @@ impl<'a, T: ?Sized, S: MutexSupport> Drop for MutexGuard<'a, T, S> { /// The dropping of the MutexGuard will release the lock it was created from. fn drop(&mut self) { - self.mutex.lock.store(false, Ordering::Release); + self.mutex.lock.store(); self.mutex.support.after_unlock(); } } From f27fd37d828f484431c4c3b5d5fef2751f9596fa Mon Sep 17 00:00:00 2001 From: maoyuchaxue Date: Wed, 17 Oct 2018 21:34:15 +0800 Subject: [PATCH 11/58] replaced spin::Mutex with sync::SpinLock, now spinlock works well. --- kernel/src/arch/x86_64/driver/vga.rs | 2 +- kernel/src/fs.rs | 2 +- kernel/src/memory.rs | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/kernel/src/arch/x86_64/driver/vga.rs b/kernel/src/arch/x86_64/driver/vga.rs index 7ef8720d..a5b2feb9 100644 --- a/kernel/src/arch/x86_64/driver/vga.rs +++ b/kernel/src/arch/x86_64/driver/vga.rs @@ -1,7 +1,7 @@ use consts::KERNEL_OFFSET; use core::ptr::Unique; use core::fmt; -use spin::Mutex; +use sync::SpinLock as Mutex; use volatile::Volatile; use x86_64::instructions::port::Port; use logging::Color; diff --git a/kernel/src/fs.rs b/kernel/src/fs.rs index fe20c6c7..6c9c9582 100644 --- a/kernel/src/fs.rs +++ b/kernel/src/fs.rs @@ -2,7 +2,7 @@ use simple_filesystem::*; use alloc::boxed::Box; #[cfg(target_arch = "x86_64")] use arch::driver::ide; -use spin::Mutex; +use sync::SpinLock as Mutex; // Hard link user program #[cfg(target_arch = "riscv32")] diff --git a/kernel/src/memory.rs b/kernel/src/memory.rs index 0f203f37..dec17fb8 100644 --- a/kernel/src/memory.rs +++ b/kernel/src/memory.rs @@ -1,7 +1,8 @@ pub use arch::paging::*; use bit_allocator::{BitAlloc, BitAlloc4K, BitAlloc64K}; use consts::MEMORY_OFFSET; -use spin::{Mutex, MutexGuard}; +use sync::{MutexGuard, Spin}; +use sync::SpinLock as Mutex; use super::HEAP_ALLOCATOR; use ucore_memory::{*, paging::PageTable}; use ucore_memory::cow::CowExt; @@ -48,7 +49,7 @@ lazy_static! { } /// The only way to get active page table -pub fn active_table() -> MutexGuard<'static, CowExt> { +pub fn active_table() -> MutexGuard<'static, CowExt, Spin> { ACTIVE_TABLE.lock() } From f1771f8ef26ee8e6666627a40fcfa3892207982b Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 19 Oct 2018 01:14:21 +0800 Subject: [PATCH 12/58] Finish x86 SMP startup. Support timer & IPI. - Remove smp, apic mod. Instead, use new bootloader & apic crate. - Disable PIC & PIT. Instead, use IOAPIC & APIC Timer. --- kernel/Cargo.lock | 91 ++++++++-- kernel/Cargo.toml | 9 +- kernel/Makefile | 2 +- kernel/src/arch/x86_64/cpu.rs | 17 ++ kernel/src/arch/x86_64/driver/apic/ioapic.rs | 98 ----------- kernel/src/arch/x86_64/driver/apic/lapic.c | 165 ------------------ kernel/src/arch/x86_64/driver/apic/lapic.rs | 46 ----- .../src/arch/x86_64/driver/apic/local_apic.rs | 116 ------------ kernel/src/arch/x86_64/driver/apic/mod.rs | 17 -- kernel/src/arch/x86_64/driver/mod.rs | 14 +- kernel/src/arch/x86_64/gdt.rs | 5 +- kernel/src/arch/x86_64/interrupt/handler.rs | 8 +- kernel/src/arch/x86_64/interrupt/mod.rs | 16 +- kernel/src/arch/x86_64/memory.rs | 13 +- kernel/src/arch/x86_64/mod.rs | 30 +++- kernel/src/arch/x86_64/smp.rs | 41 ----- 16 files changed, 151 insertions(+), 537 deletions(-) delete mode 100644 kernel/src/arch/x86_64/driver/apic/ioapic.rs delete mode 100644 kernel/src/arch/x86_64/driver/apic/lapic.c delete mode 100644 kernel/src/arch/x86_64/driver/apic/lapic.rs delete mode 100644 kernel/src/arch/x86_64/driver/apic/local_apic.rs delete mode 100644 kernel/src/arch/x86_64/driver/apic/mod.rs delete mode 100644 kernel/src/arch/x86_64/smp.rs diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index d8de26e6..83938d60 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -1,3 +1,13 @@ +[[package]] +name = "apic" +version = "0.1.0" +source = "git+https://github.com/wangrunji0408/APIC-Rust#5002b7de801178bef0635e7fe399ce513f04cbf9" +dependencies = [ + "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "x86 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bare-metal" version = "0.2.3" @@ -32,11 +42,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bootloader" version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" +source = "git+https://github.com/wangrunji0408/bootloader#d53b01de2fb8c0b3793ebf2d8ec1d56180dc71d4" dependencies = [ + "apic 0.1.0 (git+https://github.com/wangrunji0408/APIC-Rust)", "fixedvec 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -86,7 +97,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "spin 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -135,7 +146,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "raw-cpuid" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -148,7 +169,7 @@ name = "remove_dir_all" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -160,10 +181,31 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "simple-filesystem" version = "0.0.1" -source = "git+https://github.com/wangrunji0408/SimpleFileSystem-Rust#ca10d11264c85932e95e6c7c29e8b10c91ae0720" +source = "git+https://github.com/wangrunji0408/SimpleFileSystem-Rust#e26f14c55f2b5e767ac7055bab874d039fbe1f05" dependencies = [ "bit-vec 0.5.0 (git+https://github.com/AltSysrq/bit-vec.git)", "static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -203,23 +245,25 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ucore" version = "0.1.0" dependencies = [ + "apic 0.1.0 (git+https://github.com/wangrunji0408/APIC-Rust)", "bbl 0.1.0", "bit-allocator 0.1.0", "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "bootloader 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bootloader 0.3.1 (git+https://github.com/wangrunji0408/bootloader)", "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "linked_list_allocator 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "once 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "raw-cpuid 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", "riscv 0.3.0", "simple-filesystem 0.0.1 (git+https://github.com/wangrunji0408/SimpleFileSystem-Rust)", @@ -228,7 +272,7 @@ dependencies = [ "ucore-memory 0.1.0", "ucore-process 0.1.0", "volatile 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -260,7 +304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "version_check" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -270,7 +314,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -287,9 +331,18 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "x86" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "raw-cpuid 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "x86_64" -version = "0.2.11" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -313,11 +366,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] +"checksum apic 0.1.0 (git+https://github.com/wangrunji0408/APIC-Rust)" = "" "checksum bare-metal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1bdcf9294ed648c7cd29b11db06ea244005aeef50ae8f605b1a3af2940bf8f92" "checksum bit-vec 0.5.0 (git+https://github.com/AltSysrq/bit-vec.git)" = "" "checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum bootloader 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f1721ced9efc102309bc218c7934d642f60567858faf8d5dd90c0cc6722d97b9" +"checksum bootloader 0.3.1 (git+https://github.com/wangrunji0408/bootloader)" = "" "checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" "checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" "checksum fixedvec 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c6c16d316ccdac21a4dd648e314e76facbbaf316e83ca137d0857a9c07419d0" @@ -332,8 +386,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "66481dbeb5e773e7bd85b63cd6042c30786f834338288c5ec4f3742673db360a" "checksum pulldown-cmark 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8361e81576d2e02643b04950e487ec172b687180da65c731c03cf336784e6c07" "checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd" +"checksum raw-cpuid 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41219962ecab392f1e68db9e7ebd972800d4045a128cc23462b384e8c312cde1" "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum simple-filesystem 0.0.1 (git+https://github.com/wangrunji0408/SimpleFileSystem-Rust)" = "" "checksum skeptic 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "061203a849117b0f7090baf8157aa91dac30545208fbb85166ac58b4ca33d89c" "checksum spin 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "37b5646825922b96b5d7d676b5bb3458a54498e96ed7b0ce09dc43a07038fea4" @@ -343,11 +401,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f70329e2cbe45d6c97a5112daad40c34cd9a4e18edb5a2a18fefeb584d8d25e5" "checksum ux 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53d8df5dd8d07fedccd202de1887d94481fadaea3db70479f459e8163a1fab41" -"checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum volatile 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37c5d76c0f40ba4f8ac10ec4717d4e98ce3e58c5607eea36e9464226fc5e0a95" -"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum x86_64 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "466c2002e38edde7ebbaae6656793d4f71596634971c7e8cbf7afa4827968445" +"checksum x86 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "841e1ca5a87068718a2a26f2473c6f93cf3b8119f9778fa0ae4b39b664d9e66a" +"checksum x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "a7e95a2813e20d24546c2b29ecc6df55cfde30c983df69eeece0b179ca9d68ac" "checksum xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22678df5df766e8d1e5d609da69f0c3132d794edf6ab5e75e7abcd2270d4cf58" "checksum zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f1bc8a6b2005884962297587045002d8cfb8dcec9db332f4ca216ddc5de82c5" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 0afa030f..1027eb3f 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" authors = ["Runji Wang "] [features] -use_apic = [] link_user_program = [] no_bbl = [] @@ -32,8 +31,12 @@ ucore-process = { path = "../crate/process" } simple-filesystem = { git = "https://github.com/wangrunji0408/SimpleFileSystem-Rust" } [target.'cfg(target_arch = "x86_64")'.dependencies] -bootloader = "0.3" -x86_64 = "0.2.11" +bootloader = { git = "https://github.com/wangrunji0408/bootloader" } +apic = { git = "https://github.com/wangrunji0408/APIC-Rust" } +#bootloader = { path = "../crate/bootloader" } +#apic = { path = "../crate/apic" } +x86_64 = "0.2" +raw-cpuid = "6.0" redox_syscall = "0.1" uart_16550 = "0.1" diff --git a/kernel/Makefile b/kernel/Makefile index 00531848..919ab750 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -36,7 +36,7 @@ qemu_opts := \ -device isa-debug-exit endif ifeq ($(arch), riscv32) -qemu_opts := -machine virt -kernel $(bin) -nographic +qemu_opts := -machine virt -kernel $(bin) -nographic -smp 4 endif ifdef board diff --git a/kernel/src/arch/x86_64/cpu.rs b/kernel/src/arch/x86_64/cpu.rs index 8e93ccaa..18a63d2d 100644 --- a/kernel/src/arch/x86_64/cpu.rs +++ b/kernel/src/arch/x86_64/cpu.rs @@ -1,3 +1,6 @@ +use super::apic::{LocalApic, XApic}; +use super::raw_cpuid::CpuId; + /// Exit qemu /// See: https://wiki.osdev.org/Shutdown /// Must run qemu with `-device isa-debug-exit` @@ -7,4 +10,18 @@ pub unsafe fn exit_in_qemu(error_code: u8) -> ! { assert_eq!(error_code & 1, 1, "error code should be odd"); Port::new(0x501).write((error_code - 1) / 2); unreachable!() +} + +pub fn id() -> usize { + CpuId::new().get_feature_info().unwrap().initial_local_apic_id() as usize +} + +pub fn send_ipi(cpu_id: u8) { + let mut lapic = unsafe { XApic::new(0xffffff00_fee00000) }; + unsafe { lapic.send_ipi(cpu_id, 0x30); } // TODO: Find a IPI trap num +} + +pub fn init() { + let mut lapic = unsafe { XApic::new(0xffffff00_fee00000) }; + lapic.cpu_init(); } \ No newline at end of file diff --git a/kernel/src/arch/x86_64/driver/apic/ioapic.rs b/kernel/src/arch/x86_64/driver/apic/ioapic.rs deleted file mode 100644 index 48e7d357..00000000 --- a/kernel/src/arch/x86_64/driver/apic/ioapic.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Migrate from xv6 ioapic.c - -/// The I/O APIC manages hardware interrupts for an SMP system. -/// http://www.intel.com/design/chipsets/datashts/29056601.pdf -/// See also picirq.c. - -use super::super::redox_syscall::io::{Io, Mmio}; -use bit_field::BitField; -use arch::interrupt::consts::T_IRQ0; -use spin::Mutex; - -pub fn init() -{ - let mut ioapic = IOAPIC.lock(); - - // Mark all interrupts edge-triggered, active high, disabled, - // and not routed to any CPUs. - for i in 0.. ioapic.maxintr() + 1 { - ioapic.write_irq(i, RedirectionEntry::DISABLED, 0); - } - info!("ioapic: init end"); -} - -const IOAPIC_ADDRESS : u32 = 0xFEC00000; // Default physical address of IO APIC - -const REG_ID : u8 = 0x00; // Register index: ID -const REG_VER : u8 = 0x01; // Register index: version -const REG_TABLE : u8 = 0x10; // Redirection table base - -// The redirection table starts at REG_TABLE and uses -// two registers to configure each interrupt. -// The first (low) register in a pair contains configuration bits. -// The second (high) register contains a bitmask telling which -// CPUs can serve that interrupt. - -bitflags! { - struct RedirectionEntry: u32 { - const DISABLED = 0x00010000; // Interrupt disabled - const LEVEL = 0x00008000; // Level-triggered (vs edge-) - const ACTIVELOW = 0x00002000; // Active low (vs high) - const LOGICAL = 0x00000800; // Destination is CPU id (vs APIC ID) - const NONE = 0x00000000; - } -} - -lazy_static! { - pub static ref IOAPIC: Mutex = Mutex::new(unsafe{IoApic::new()}); -} - -// IO APIC MMIO structure: write reg, then read or write data. -#[repr(C)] -struct IoApicMmio { - reg: Mmio, - pad: [Mmio; 3], - data: Mmio, -} - -pub struct IoApic { - mmio: &'static mut IoApicMmio -} - -impl IoApic { - unsafe fn new() -> Self { - IoApic { mmio: &mut *(IOAPIC_ADDRESS as *mut IoApicMmio) } - } - fn read(&mut self, reg: u8) -> u32 - { - self.mmio.reg.write(reg as u32); - self.mmio.data.read() - } - fn write(&mut self, reg: u8, data: u32) - { - self.mmio.reg.write(reg as u32); - self.mmio.data.write(data); - } - fn write_irq(&mut self, irq: u8, flags: RedirectionEntry, dest: u8) - { - self.write(REG_TABLE+2*irq, (T_IRQ0 + irq) as u32 | flags.bits()); - self.write(REG_TABLE+2*irq+1, (dest as u32) << 24); - } - pub fn enable(&mut self, irq: u8, cpunum: u8) - { - info!("ioapic: enable irq {} @ cpu{}", irq, cpunum); - // Mark interrupt edge-triggered, active high, - // enabled, and routed to the given cpunum, - // which happens to be that cpu's APIC ID. - self.write_irq(irq, RedirectionEntry::NONE, cpunum); - } - fn id(&mut self) -> u8 { - self.read(REG_ID).get_bits(24..28) as u8 - } - fn version(&mut self) -> u8 { - self.read(REG_VER).get_bits(0..8) as u8 - } - fn maxintr(&mut self) -> u8 { - self.read(REG_VER).get_bits(16..24) as u8 - } -} \ No newline at end of file diff --git a/kernel/src/arch/x86_64/driver/apic/lapic.c b/kernel/src/arch/x86_64/driver/apic/lapic.c deleted file mode 100644 index b01b1b89..00000000 --- a/kernel/src/arch/x86_64/driver/apic/lapic.c +++ /dev/null @@ -1,165 +0,0 @@ -// The local APIC manages internal (non-I/O) interrupts. -// See Chapter 8 & Appendix C of Intel processor manual volume 3. - -typedef unsigned int uint; -typedef unsigned char uchar; -typedef unsigned short ushort; - -static inline void -outb(ushort port, uchar data) -{ - asm volatile("out %0,%1" : : "a" (data), "d" (port)); -} - -#define KERNBASE 0xFFFFFF0000000000 // First kernel virtual address -#define P2V(a) (((void *) (a)) + KERNBASE) - -#define T_IRQ0 32 // IRQ 0 corresponds to int T_IRQ - -#define IRQ_TIMER 0 -#define IRQ_KBD 1 -#define IRQ_COM1 4 -#define IRQ_IDE 14 -#define IRQ_ERROR 19 -#define IRQ_SPURIOUS 31 - -// Local APIC registers, divided by 4 for use as uint[] indices. -#define ID (0x0020/4) // ID -#define VER (0x0030/4) // Version -#define TPR (0x0080/4) // Task Priority -#define EOI (0x00B0/4) // EOI -#define SVR (0x00F0/4) // Spurious Interrupt Vector - #define ENABLE 0x00000100 // Unit Enable -#define ESR (0x0280/4) // Error Status -#define ICRLO (0x0300/4) // Interrupt Command - #define INIT 0x00000500 // INIT/RESET - #define STARTUP 0x00000600 // Startup IPI - #define DELIVS 0x00001000 // Delivery status - #define ASSERT 0x00004000 // Assert interrupt (vs deassert) - #define DEASSERT 0x00000000 - #define LEVEL 0x00008000 // Level triggered - #define BCAST 0x00080000 // Send to all APICs, including self. - #define BUSY 0x00001000 - #define FIXED 0x00000000 -#define ICRHI (0x0310/4) // Interrupt Command [63:32] -#define TIMER (0x0320/4) // Local Vector Table 0 (TIMER) - #define X1 0x0000000B // divide counts by 1 - #define PERIODIC 0x00020000 // Periodic -#define PCINT (0x0340/4) // Performance Counter LVT -#define LINT0 (0x0350/4) // Local Vector Table 1 (LINT0) -#define LINT1 (0x0360/4) // Local Vector Table 2 (LINT1) -#define ERROR (0x0370/4) // Local Vector Table 3 (ERROR) - #define MASKED 0x00010000 // Interrupt masked -#define TICR (0x0380/4) // Timer Initial Count -#define TCCR (0x0390/4) // Timer Current Count -#define TDCR (0x03E0/4) // Timer Divide Configuration - -volatile uint *lapic; // Initialized in mp.c - -static void -lapicw(int index, int value) -{ - lapic[index] = value; - lapic[ID]; // wait for write to finish, by reading -} -//PAGEBREAK! - -void -lapicinit(void) -{ - if(!lapic) - return; - - // Enable local APIC; set spurious interrupt vector. - lapicw(SVR, ENABLE | (T_IRQ0 + IRQ_SPURIOUS)); - - // The timer repeatedly counts down at bus frequency - // from lapic[TICR] and then issues an interrupt. - // If xv6 cared more about precise timekeeping, - // TICR would be calibrated using an external time source. - lapicw(TDCR, X1); - lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER)); - lapicw(TICR, 10000000); - - // Disable logical interrupt lines. - lapicw(LINT0, MASKED); - lapicw(LINT1, MASKED); - - // Disable performance counter overflow interrupts - // on machines that provide that interrupt entry. - if(((lapic[VER]>>16) & 0xFF) >= 4) - lapicw(PCINT, MASKED); - - // Map error interrupt to IRQ_ERROR. - lapicw(ERROR, T_IRQ0 + IRQ_ERROR); - - // Clear error status register (requires back-to-back writes). - lapicw(ESR, 0); - lapicw(ESR, 0); - - // Ack any outstanding interrupts. - lapicw(EOI, 0); - - // Send an Init Level De-Assert to synchronise arbitration ID's. - lapicw(ICRHI, 0); - lapicw(ICRLO, BCAST | INIT | LEVEL); - while(lapic[ICRLO] & DELIVS) - ; - - // Enable interrupts on the APIC (but not on the processor). - lapicw(TPR, 0); -} - -// Acknowledge interrupt. -void -lapiceoi(void) -{ - if(lapic) - lapicw(EOI, 0); -} - -// Spin for a given number of microseconds. -// On real hardware would want to tune this dynamically. -void -microdelay(int us) -{ -} - -#define CMOS_PORT 0x70 -#define CMOS_RETURN 0x71 - -// Start additional processor running entry code at addr. -// See Appendix B of MultiProcessor Specification. -void -lapicstartap(uchar apicid, uint addr) -{ - int i; - ushort *wrv; - - // "The BSP must initialize CMOS shutdown code to 0AH - // and the warm reset vector (DWORD based at 40:67) to point at - // the AP startup code prior to the [universal startup algorithm]." - outb(CMOS_PORT, 0xF); // offset 0xF is shutdown code - outb(CMOS_PORT+1, 0x0A); - wrv = (ushort*)P2V((0x40<<4 | 0x67)); // Warm reset vector - wrv[0] = 0; - wrv[1] = addr >> 4; - - // "Universal startup algorithm." - // Send INIT (level-triggered) interrupt to reset other CPU. - lapicw(ICRHI, apicid<<24); - lapicw(ICRLO, INIT | LEVEL | ASSERT); - microdelay(200); - lapicw(ICRLO, INIT | LEVEL); - microdelay(10000); - - // Send startup IPI (twice!) to enter code. - // Regular hardware is supposed to only accept a STARTUP - // when it is in the halted state due to an INIT. So the second - // should be ignored, but it is part of the official Intel algorithm. - for(i = 0; i < 2; i++){ - lapicw(ICRHI, apicid<<24); - lapicw(ICRLO, STARTUP | (addr>>12)); - microdelay(200); - } -} \ No newline at end of file diff --git a/kernel/src/arch/x86_64/driver/apic/lapic.rs b/kernel/src/arch/x86_64/driver/apic/lapic.rs deleted file mode 100644 index b70e776e..00000000 --- a/kernel/src/arch/x86_64/driver/apic/lapic.rs +++ /dev/null @@ -1,46 +0,0 @@ -extern { - //noinspection RsStaticConstNaming - static mut lapic: *const (); - fn lapicinit(); // must set `lapic` first - fn lapiceoi(); // ack - fn lapicstartap(apicid: u8, addr: u32); -} - -pub fn set_addr(lapic_addr: usize) { - unsafe { -// lapic = lapic_addr; - } -} - -pub fn init() { - warn!("lapic::init use C lib"); - unsafe { -// lapicinit(); - } - info!("lapic: init end"); -} - -pub fn ack(_irq: u8) { - unsafe { -// lapiceoi(); - } -} - -pub fn start_ap(apicid: u8, addr: u32) { - warn!("lapic::start_ap use C lib"); - unsafe { -// lapicstartap(apicid, addr); - } -} - -pub fn lapic_id() -> u8 { - 0 -// unsafe{ -// if lapic.is_null() { -// warn!("lapic is null. return lapic id = 0"); -// return 0; -// } -// let ptr = (lapic as *const u32).offset(0x0020 / 4); -// (ptr.read_volatile() >> 24) as u8 -// } -} \ No newline at end of file diff --git a/kernel/src/arch/x86_64/driver/apic/local_apic.rs b/kernel/src/arch/x86_64/driver/apic/local_apic.rs deleted file mode 100644 index edb228b7..00000000 --- a/kernel/src/arch/x86_64/driver/apic/local_apic.rs +++ /dev/null @@ -1,116 +0,0 @@ -use core::intrinsics::{volatile_load, volatile_store}; -use x86::cpuid::CpuId; -use x86::msr::*; - -use memory::Frame; -use paging::{ActivePageTable, PhysAddr, Page, VirtualAddress}; -use paging::entry::EntryFlags; - -pub static mut LOCAL_APIC: LocalApic = LocalApic { - address: 0, - x2: false -}; - -pub unsafe fn init(active_table: &mut ActivePageTable) { - LOCAL_APIC.init(active_table); -} - -pub unsafe fn init_ap() { - LOCAL_APIC.init_ap(); -} - -/// Local APIC -pub struct LocalApic { - pub address: usize, - pub x2: bool -} - -impl LocalApic { - unsafe fn init(&mut self, active_table: &mut ActivePageTable) { - self.address = (rdmsr(IA32_APIC_BASE) as usize & 0xFFFF_0000) + ::KERNEL_OFFSET; - self.x2 = CpuId::new().get_feature_info().unwrap().has_x2apic(); - - if ! self.x2 { - let page = Page::containing_address(VirtualAddress::new(self.address)); - let frame = Frame::containing_address(PhysAddr::new(self.address - ::KERNEL_OFFSET)); - let result = active_table.map_to(page, frame, EntryFlags::PRESENT | EntryFlags::WRITABLE | EntryFlags::NO_EXECUTE); - result.flush(active_table); - } - - self.init_ap(); - } - - unsafe fn init_ap(&mut self) { - if self.x2 { - wrmsr(IA32_APIC_BASE, rdmsr(IA32_APIC_BASE) | 1 << 10); - wrmsr(IA32_X2APIC_SIVR, 0x100); - } else { - self.write(0xF0, 0x100); - } - } - - unsafe fn read(&self, reg: u32) -> u32 { - volatile_load((self.address + reg as usize) as *const u32) - } - - unsafe fn write(&mut self, reg: u32, value: u32) { - volatile_store((self.address + reg as usize) as *mut u32, value); - } - - pub fn id(&self) -> u32 { - if self.x2 { - unsafe { rdmsr(IA32_X2APIC_APICID) as u32 } - } else { - unsafe { self.read(0x20) } - } - } - - pub fn version(&self) -> u32 { - if self.x2 { - unsafe { rdmsr(IA32_X2APIC_VERSION) as u32 } - } else { - unsafe { self.read(0x30) } - } - } - - pub fn icr(&self) -> u64 { - if self.x2 { - unsafe { rdmsr(IA32_X2APIC_ICR) } - } else { - unsafe { - (self.read(0x310) as u64) << 32 | self.read(0x300) as u64 - } - } - } - - pub fn set_icr(&mut self, value: u64) { - if self.x2 { - unsafe { wrmsr(IA32_X2APIC_ICR, value); } - } else { - unsafe { - while self.read(0x300) & 1 << 12 == 1 << 12 {} - self.write(0x310, (value >> 32) as u32); - self.write(0x300, value as u32); - while self.read(0x300) & 1 << 12 == 1 << 12 {} - } - } - } - - pub fn ipi(&mut self, apic_id: usize) { - let mut icr = 0x4040; - if self.x2 { - icr |= (apic_id as u64) << 32; - } else { - icr |= (apic_id as u64) << 56; - } - self.set_icr(icr); - } - - pub unsafe fn eoi(&mut self) { - if self.x2 { - wrmsr(IA32_X2APIC_EOI, 0); - } else { - self.write(0xB0, 0); - } - } -} diff --git a/kernel/src/arch/x86_64/driver/apic/mod.rs b/kernel/src/arch/x86_64/driver/apic/mod.rs deleted file mode 100644 index 3cf6af8b..00000000 --- a/kernel/src/arch/x86_64/driver/apic/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub use self::ioapic::IOAPIC; -pub use self::lapic::{ack, start_ap, lapic_id}; - -mod lapic; -mod ioapic; - -pub fn init() { - assert_has_not_been_called!("apic::init must be called only once"); - use consts::KERNEL_OFFSET; - self::lapic::set_addr(KERNEL_OFFSET + 0xfee00000); - self::lapic::init(); - self::ioapic::init(); -} - -pub fn other_init() { - self::lapic::init(); -} \ No newline at end of file diff --git a/kernel/src/arch/x86_64/driver/mod.rs b/kernel/src/arch/x86_64/driver/mod.rs index 50643530..e2450c64 100644 --- a/kernel/src/arch/x86_64/driver/mod.rs +++ b/kernel/src/arch/x86_64/driver/mod.rs @@ -1,7 +1,6 @@ extern crate syscall as redox_syscall; pub mod vga; -pub mod apic; pub mod serial; pub mod pic; pub mod keyboard; @@ -11,13 +10,12 @@ pub mod ide; pub fn init() { assert_has_not_been_called!(); - if cfg!(feature = "use_apic") { - pic::disable(); - apic::init(); - } else { - pic::init(); - } - pit::init(); + // Use IOAPIC instead of PIC + pic::disable(); + + // Use APIC Timer instead of PIT + // pit::init(); + serial::init(); keyboard::init(); } \ No newline at end of file diff --git a/kernel/src/arch/x86_64/gdt.rs b/kernel/src/arch/x86_64/gdt.rs index 20d00341..620e5434 100644 --- a/kernel/src/arch/x86_64/gdt.rs +++ b/kernel/src/arch/x86_64/gdt.rs @@ -1,5 +1,4 @@ use alloc::boxed::Box; -use arch::driver::apic::lapic_id; use consts::MAX_CPU_NUM; use core::fmt; use core::fmt::Debug; @@ -50,7 +49,7 @@ pub fn init() { load_tss(TSS_SELECTOR); } - CPUS[lapic_id() as usize].call_once(|| + CPUS[super::cpu::id() as usize].call_once(|| Mutex::new(Cpu { gdt, tss: unsafe { &mut *tss } })); } @@ -67,7 +66,7 @@ pub struct Cpu { impl Cpu { pub fn current() -> MutexGuard<'static, Cpu> { - CPUS[lapic_id() as usize].try().unwrap().lock() + CPUS[super::cpu::id()].try().unwrap().lock() } /// 设置从Ring3跳到Ring0时,自动切换栈的地址 diff --git a/kernel/src/arch/x86_64/interrupt/handler.rs b/kernel/src/arch/x86_64/interrupt/handler.rs index 9d5b5aa0..a0369a46 100644 --- a/kernel/src/arch/x86_64/interrupt/handler.rs +++ b/kernel/src/arch/x86_64/interrupt/handler.rs @@ -72,7 +72,7 @@ global_asm!(include_str!("vector.asm")); #[no_mangle] pub extern fn rust_trap(tf: &mut TrapFrame) { - trace!("Interrupt: {:#x}", tf.trap_num); + trace!("Interrupt: {:#x} @ CPU{}", tf.trap_num, super::super::cpu::id()); // Dispatch match tf.trap_num as u8 { T_BRKPT => breakpoint(), @@ -88,11 +88,7 @@ pub extern fn rust_trap(tf: &mut TrapFrame) { IRQ_IDE => ide(), _ => panic!("Invalid IRQ number: {}", irq), } - #[cfg(feature = "use_apic")] - use arch::driver::apic::ack; - #[cfg(not(feature = "use_apic"))] - use arch::driver::pic::ack; - ack(irq); + super::ack(irq); } T_SWITCH_TOK => to_kernel(tf), T_SWITCH_TOU => to_user(tf), diff --git a/kernel/src/arch/x86_64/interrupt/mod.rs b/kernel/src/arch/x86_64/interrupt/mod.rs index a1b9ecbd..2e62cdb7 100644 --- a/kernel/src/arch/x86_64/interrupt/mod.rs +++ b/kernel/src/arch/x86_64/interrupt/mod.rs @@ -1,5 +1,4 @@ use x86_64; -use arch::driver::{apic::IOAPIC, pic}; pub mod consts; mod handler; @@ -7,6 +6,8 @@ mod trapframe; pub use self::trapframe::*; pub use self::handler::*; +use super::apic::*; +use consts::KERNEL_OFFSET; #[inline(always)] pub unsafe fn enable() { @@ -39,9 +40,12 @@ pub fn no_interrupt(f: impl FnOnce()) { #[inline(always)] pub fn enable_irq(irq: u8) { - if cfg!(feature = "use_apic") { - IOAPIC.lock().enable(irq, 0); - } else { - pic::enable_irq(irq); - } + let mut ioapic = unsafe { IoApic::new(KERNEL_OFFSET + IOAPIC_ADDR as usize) }; + ioapic.enable(irq, 0); +} + +#[inline(always)] +pub fn ack(irq: u8) { + let mut lapic = unsafe { XApic::new(KERNEL_OFFSET + LAPIC_ADDR) }; + lapic.eoi(); } \ No newline at end of file diff --git a/kernel/src/arch/x86_64/memory.rs b/kernel/src/arch/x86_64/memory.rs index 54bd2838..e0fcf30b 100644 --- a/kernel/src/arch/x86_64/memory.rs +++ b/kernel/src/arch/x86_64/memory.rs @@ -1,14 +1,15 @@ use bit_allocator::BitAlloc; use consts::KERNEL_OFFSET; // Depends on kernel -use memory::{FRAME_ALLOCATOR, init_heap}; +use memory::{FRAME_ALLOCATOR, init_heap, active_table}; use super::{BootInfo, MemoryRegionType}; use ucore_memory::PAGE_SIZE; -use ucore_memory::paging::PageTable; +use ucore_memory::paging::*; pub fn init(boot_info: &BootInfo) { assert_has_not_been_called!("memory::init must be called only once"); init_frame_allocator(boot_info); + init_device_vm_map(); init_heap(); info!("memory: init end"); } @@ -22,3 +23,11 @@ fn init_frame_allocator(boot_info: &BootInfo) { } } } + +fn init_device_vm_map() { + let mut page_table = active_table(); + // IOAPIC + page_table.map(KERNEL_OFFSET + 0xfec00000, 0xfec00000).update(); + // LocalAPIC + page_table.map(KERNEL_OFFSET + 0xfee00000, 0xfee00000).update(); +} diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs index d1868f78..64b66af2 100644 --- a/kernel/src/arch/x86_64/mod.rs +++ b/kernel/src/arch/x86_64/mod.rs @@ -1,6 +1,10 @@ extern crate bootloader; +extern crate apic; +extern crate raw_cpuid; use self::bootloader::bootinfo::{BootInfo, MemoryRegionType}; +use core::sync::atomic::*; +use consts::KERNEL_OFFSET; pub mod driver; pub mod cpu; @@ -8,14 +12,22 @@ pub mod interrupt; pub mod paging; pub mod gdt; pub mod idt; -// TODO: Move multi-core init to bootloader -//pub mod smp; pub mod memory; pub mod io; +static AP_CAN_INIT: AtomicBool = ATOMIC_BOOL_INIT; + /// The entry point of kernel #[no_mangle] // don't mangle the name of this function pub extern "C" fn _start(boot_info: &'static BootInfo) -> ! { + let cpu_id = cpu::id(); + println!("Hello world! from CPU {}!", cpu_id); + + if cpu_id != 0 { + while !AP_CAN_INIT.load(Ordering::Relaxed) {} + other_start(); + } + // First init log mod, so that we can print log info. ::logging::init(); info!("Hello world!"); @@ -30,20 +42,20 @@ pub extern "C" fn _start(boot_info: &'static BootInfo) -> ! { // Now heap is available gdt::init(); + cpu::init(); + driver::init(); + AP_CAN_INIT.store(true, Ordering::Relaxed); + ::kmain(); } -/// The entry point for another processors -#[no_mangle] -pub extern "C" fn other_main() -> ! { +/// The entry point for other processors +fn other_start() -> ! { idt::init(); gdt::init(); - driver::apic::other_init(); - let cpu_id = driver::apic::lapic_id(); -// let ms = unsafe { smp::notify_started(cpu_id) }; - println!("Hello world! from CPU {}!", cpu_id); + cpu::init(); // unsafe{ let a = *(0xdeadbeaf as *const u8); } // Page fault loop {} } \ No newline at end of file diff --git a/kernel/src/arch/x86_64/smp.rs b/kernel/src/arch/x86_64/smp.rs deleted file mode 100644 index 1d39a7b9..00000000 --- a/kernel/src/arch/x86_64/smp.rs +++ /dev/null @@ -1,41 +0,0 @@ -use arch::driver::{acpi::AcpiResult, apic::start_ap}; -use consts::MAX_CPU_NUM; -use core::ptr::{read_volatile, write_volatile}; -use memory::*; -use x86_64::registers::control::Cr3; - -pub const ENTRYOTHER_ADDR: usize = 0x7000; - -pub fn start_other_cores(acpi: &AcpiResult) { - let args = unsafe { &mut *(0x8000 as *mut EntryArgs).offset(-1) }; - for i in 1 .. acpi.cpu_num { - let apic_id = acpi.cpu_acpi_ids[i as usize]; - let ms = MemorySet::new(); - *args = EntryArgs { - kstack: ms.kstack_top() as u64, - page_table: Cr3::read().0.start_address().as_u64() as u32, - stack: args as *const _ as u32, // just enough stack to get us to entry64mp - }; - unsafe { MS = Some(ms); } - start_ap(apic_id, ENTRYOTHER_ADDR as u32); - while unsafe { !read_volatile(&STARTED[i as usize]) } {} - } -} - -#[repr(C)] -#[derive(Debug)] -struct EntryArgs { - kstack: u64, - page_table: u32, - stack: u32, -} - -static mut STARTED: [bool; MAX_CPU_NUM] = [false; MAX_CPU_NUM]; -static mut MS: Option = None; - -pub unsafe fn notify_started(cpu_id: u8) -> MemorySet { - write_volatile(&mut STARTED[cpu_id as usize], true); - let ms = MS.take().unwrap(); - ms.activate(); - ms -} \ No newline at end of file From 72e92c07f94093543bfb410e487a78391a735539 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 19 Oct 2018 17:11:08 +0800 Subject: [PATCH 13/58] Switch to RV64 GNU toolchain. Simplify compiler_rt. --- kernel/Makefile | 14 +++-- kernel/build.rs | 3 +- kernel/riscv32-blog_os.json | 2 +- kernel/src/arch/riscv32/compiler_rt.c | 30 ++++------ kernel/src/arch/riscv32/compiler_rt.rs | 66 --------------------- kernel/src/sync/arch/riscv32/atomic_lock.rs | 8 ++- kernel/src/sync/arch/x86_64/atomic_lock.rs | 7 ++- riscv-pk/configure | 4 +- riscv-pk/configure.ac | 4 +- 9 files changed, 36 insertions(+), 102 deletions(-) diff --git a/kernel/Makefile b/kernel/Makefile index ae797945..a32456b0 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -18,7 +18,7 @@ arch ?= riscv32 mode ?= debug LOG ?= debug -smp ?= 1 +smp ?= 4 target := $(arch)-blog_os kernel := target/$(target)/$(mode)/ucore @@ -71,13 +71,12 @@ ifeq ($(uname), Darwin) prefix := x86_64-elf- endif ifeq ($(arch), riscv32) -prefix := riscv32-unknown-elf- +prefix := riscv64-unknown-elf- endif ld := $(prefix)ld objdump := $(prefix)objdump cc := $(prefix)gcc -CC := $(cc) as := $(prefix)as .PHONY: all clean run build asm doc justrun kernel @@ -107,9 +106,12 @@ endif asm: @$(objdump) -dS $(kernel) | less -elf-h: +header: @$(objdump) -h $(kernel) +sym: + @$(objdump) -t $(kernel) | less + $(bin): kernel ifdef board @cp $(kernel) $@ @@ -121,7 +123,7 @@ else --enable-32bit \ --enable-logo \ --disable-fp-emulation \ - --host=riscv32-unknown-elf \ + --host=riscv64-unknown-elf \ --with-payload=$(abspath $(kernel)) && \ make && \ cp bbl ../../kernel/$@ @@ -131,7 +133,7 @@ kernel: ifeq ($(arch), x86_64) @bootimage build $(build_args) else - @cargo xbuild $(build_args) + @CC=$(cc) cargo xbuild $(build_args) endif # make user.o from binary files diff --git a/kernel/build.rs b/kernel/build.rs index 0cd473a8..ec94d4eb 100644 --- a/kernel/build.rs +++ b/kernel/build.rs @@ -15,7 +15,8 @@ fn main() { if std::env::var("TARGET").unwrap().find("riscv32").is_some() { cc::Build::new() .file("src/arch/riscv32/compiler_rt.c") - .flag("-march=rv32ima") + .flag("-march=rv32ia") + .flag("-mabi=ilp32") .compile("atomic_rt"); } } diff --git a/kernel/riscv32-blog_os.json b/kernel/riscv32-blog_os.json index e7d7429b..ab160b8d 100644 --- a/kernel/riscv32-blog_os.json +++ b/kernel/riscv32-blog_os.json @@ -9,7 +9,7 @@ "cpu": "generic-rv32", "features": "", "max-atomic-width": "32", - "linker": "riscv32-unknown-elf-ld", + "linker": "riscv64-unknown-elf-ld", "linker-flavor": "ld", "pre-link-args": { "ld": [ diff --git a/kernel/src/arch/riscv32/compiler_rt.c b/kernel/src/arch/riscv32/compiler_rt.c index 8aa0e99c..c2f2fb8d 100644 --- a/kernel/src/arch/riscv32/compiler_rt.c +++ b/kernel/src/arch/riscv32/compiler_rt.c @@ -1,40 +1,32 @@ +// http://llvm.org/docs/Atomics.html#libcalls-atomic - -// fn __atomic_load_1_workaround(src: *const u8) -> u8; -// fn __atomic_load_2_workaround(src: *const u16) -> u16; -// fn __atomic_load_4_workaround(src: *const u32) -> u32; -// fn __atomic_store_1_workaround(dst: *mut u8, val: u8); -// fn __atomic_store_4_workaround(dst: *mut u32, val: u32); -// fn __atomic_compare_exchange_1_workaround(dst: *mut u8, expected: *mut u8, desired: u8) -> bool; -// fn __atomic_compare_exchange_4_workaround(dst: *mut u32, expected: *mut u32, desired: u32) -> bool; - -char __atomic_load_1_workaround(char *src) { +char __atomic_load_1(char *src) { char res = 0; __asm__ __volatile__("amoadd.w.rl %0, zero, (%1)" : "=r"(res) : "r"(src) : "memory"); - return res; + return res; } -short __atomic_load_2_workaround(short *src) { +short __atomic_load_2(short *src) { short res = 0; __asm__ __volatile__("amoadd.w.rl %0, zero, (%1)" : "=r"(res) : "r"(src) : "memory"); - return res; + return res; } -int __atomic_load_4_workaround(int *src) { +int __atomic_load_4(int *src) { int res = 0; __asm__ __volatile__("amoadd.w.rl %0, zero, (%1)" : "=r"(res) : "r"(src) : "memory"); - return res; + return res; } -char __atomic_store_1_workaround(char *dst, char val) { +char __atomic_store_1(char *dst, char val) { __asm__ __volatile__("amoswap.w.aq zero, %0, (%1)" :: "r"(val), "r"(dst) : "memory"); } -int __atomic_store_4_workaround(int *dst, int val) { +int __atomic_store_4(int *dst, int val) { __asm__ __volatile__("amoswap.w.aq zero, %0, (%1)" :: "r"(val), "r"(dst) : "memory"); } -char __atomic_compare_exchange_1_workaround(char* dst, char* expected, char desired) { +char __atomic_compare_exchange_1(char* dst, char* expected, char desired) { char val = 0; __asm__ __volatile__("lr.w %0, (%1)" : "=r"(val) : "r"(dst) : "memory"); if (val == *expected) { @@ -45,7 +37,7 @@ char __atomic_compare_exchange_1_workaround(char* dst, char* expected, char desi return 0; } -char __atomic_compare_exchange_4_workaround(int* dst, int* expected, int desired) { +char __atomic_compare_exchange_4(int* dst, int* expected, int desired) { int val = 0; __asm__ __volatile__("lr.w %0, (%1)" : "=r"(val) : "r"(dst) : "memory"); if (val == *expected) { diff --git a/kernel/src/arch/riscv32/compiler_rt.rs b/kernel/src/arch/riscv32/compiler_rt.rs index bb94eb05..e8fff841 100644 --- a/kernel/src/arch/riscv32/compiler_rt.rs +++ b/kernel/src/arch/riscv32/compiler_rt.rs @@ -2,17 +2,6 @@ //! //! [atomic](http://llvm.org/docs/Atomics.html#libcalls-atomic) -#[link(name = "atomic_rt")] -extern { - fn __atomic_load_1_workaround(src: *const u8) -> u8; - fn __atomic_load_2_workaround(src: *const u16) -> u16; - fn __atomic_load_4_workaround(src: *const u32) -> u32; - fn __atomic_store_1_workaround(dst: *mut u8, val: u8); - fn __atomic_store_4_workaround(dst: *mut u32, val: u32); - fn __atomic_compare_exchange_1_workaround(dst: *mut u8, expected: *mut u8, desired: u8) -> bool; - fn __atomic_compare_exchange_4_workaround(dst: *mut u32, expected: *mut u32, desired: u32) -> bool; -} - /// Copy from: /// https://github.com/rust-lang-nursery/compiler-builtins/blob/master/src/riscv32.rs #[no_mangle] @@ -34,58 +23,3 @@ pub extern fn __mulsi3(mut a: u32, mut b: u32) -> u32 { pub extern fn abort() { loop {} } - -use core::ptr::{read, write}; - -#[no_mangle] -pub unsafe extern fn __atomic_load_1(src: *const u8) -> u8 { - __atomic_load_1_workaround(src) -} - -#[no_mangle] -pub unsafe extern fn __atomic_load_2(src: *const u16) -> u16 { - __atomic_load_2_workaround(src) -} - -#[no_mangle] -pub unsafe extern fn __atomic_load_4(src: *const u32) -> u32 { - __atomic_load_4_workaround(src) -} - -#[no_mangle] -pub unsafe extern fn __atomic_store_1(dst: *mut u8, val: u8) { - __atomic_store_1_workaround(dst, val); -} - -#[no_mangle] -pub unsafe extern fn __atomic_store_4(dst: *mut u32, val: u32) { - __atomic_store_4_workaround(dst, val); -} - -// unsafe fn __atomic_compare_exchange(dst: *mut T, expected: *mut T, desired: T) -> bool { -// // use super::interrupt; -// // let flags = interrupt::disable_and_store(); -// // let val = read(dst); -// // let success = val == read(expected); -// // write(dst, if success {desired} else {val}); -// // interrupt::restore(flags); -// // success -// // let mut val: T; -// // asm!("lr.w $0, ($1)" : "=r"(val) : "r"(dst) : "memory" : "volatile"); -// // if val == *expected { -// // let mut sc_ret = 0; -// // asm!("sc.w $0, $1, ($2)" : "=r"(sc_ret) : "r"(desired), "r"(dst) : "memory" : "volatile"); -// // return sc_ret == 0 -// // } -// false -// } - -#[no_mangle] -pub unsafe extern fn __atomic_compare_exchange_1(dst: *mut u8, expected: *mut u8, desired: u8) -> bool { - __atomic_compare_exchange_1_workaround(dst, expected, desired) -} - -#[no_mangle] -pub unsafe extern fn __atomic_compare_exchange_4(dst: *mut u32, expected: *mut u32, desired: u32) -> bool { - __atomic_compare_exchange_4_workaround(dst, expected, desired) -} \ No newline at end of file diff --git a/kernel/src/sync/arch/riscv32/atomic_lock.rs b/kernel/src/sync/arch/riscv32/atomic_lock.rs index 0ee4c916..ee19bfa5 100644 --- a/kernel/src/sync/arch/riscv32/atomic_lock.rs +++ b/kernel/src/sync/arch/riscv32/atomic_lock.rs @@ -1,10 +1,14 @@ //! RISCV atomic is not currently supported by Rust. //! This is a ugly workaround. - -use arch::compiler_rt::{__atomic_compare_exchange_4, __atomic_store_4, __atomic_load_4}; use core::cell::UnsafeCell; +extern { + fn __atomic_load_4(src: *const u32) -> u32; + fn __atomic_store_4(dst: *mut u32, val: u32); + fn __atomic_compare_exchange_4(dst: *mut u32, expected: *mut u32, desired: u32) -> bool; +} + pub struct AtomicLock { lock: UnsafeCell diff --git a/kernel/src/sync/arch/x86_64/atomic_lock.rs b/kernel/src/sync/arch/x86_64/atomic_lock.rs index ddd4b236..06550c80 100644 --- a/kernel/src/sync/arch/x86_64/atomic_lock.rs +++ b/kernel/src/sync/arch/x86_64/atomic_lock.rs @@ -1,14 +1,15 @@ +use core::sync::atomic::{AtomicBool, Ordering}; pub struct AtomicLock { - lock: usize + lock: AtomicBool } impl AtomicLock { pub fn new() -> AtomicLock { AtomicLock { - lock: ATOMIC_BOOL_INIT + lock: AtomicBool::new(false) } } @@ -26,5 +27,5 @@ impl AtomicLock } pub const ATOMIC_LOCK_INIT: AtomicLock = AtomicLock { - lock: ATOMIC_BOOL_INIT + lock: AtomicBool::new(false) }; \ No newline at end of file diff --git a/riscv-pk/configure b/riscv-pk/configure index 6b316dba..a41f9082 100755 --- a/riscv-pk/configure +++ b/riscv-pk/configure @@ -4084,8 +4084,8 @@ fi case "${BUILD_32BIT}" in yes|default) echo "Building 32-bit pk" - CFLAGS="$default_CFLAGS -march=rv32ia -mabi=ilp32" - LDFLAGS="-march=rv32ia -mabi=ilp32" + CFLAGS="$default_CFLAGS -march=rv32iac -mabi=ilp32" + LDFLAGS="-march=rv32iac -mabi=ilp32" install_subdir="riscv32-unknown-elf" ;; *) diff --git a/riscv-pk/configure.ac b/riscv-pk/configure.ac index 5a06896d..20cd6d19 100644 --- a/riscv-pk/configure.ac +++ b/riscv-pk/configure.ac @@ -88,8 +88,8 @@ AC_ARG_ENABLE([32bit], case "${BUILD_32BIT}" in yes|default) echo "Building 32-bit pk" - CFLAGS="$default_CFLAGS -march=rv32ia -mabi=ilp32" - LDFLAGS="-march=rv32ia -mabi=ilp32" + CFLAGS="$default_CFLAGS -march=rv32iac -mabi=ilp32" + LDFLAGS="-march=rv32iac -mabi=ilp32" install_subdir="riscv32-unknown-elf" ;; *) From ff18852c56e7581e820c0f113c4c1e2d2ef2079b Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 19 Oct 2018 23:37:10 +0800 Subject: [PATCH 14/58] Reorganize docs. --- docs/{ => 1_OS}/FinalPresentation.pdf | Bin docs/{ => 1_OS}/FinalReport.md | 0 docs/{ => 1_OS}/Log.md | 0 docs/{ => 1_OS}/MidPresentation.md | 0 docs/{ => 1_OS}/MidPresentation.pdf | Bin docs/{ => 1_OS}/MidReport.md | 0 docs/{ => 1_OS}/RISCV.md | 0 docs/{ => 2_OSLab/g3}/catfish-proposal.md | 0 docs/{ => 2_OSLab/g3}/catfish-proposal.pptx | Bin docs/{OSLab => 2_OSLab/g5}/exp2.md | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename docs/{ => 1_OS}/FinalPresentation.pdf (100%) rename docs/{ => 1_OS}/FinalReport.md (100%) rename docs/{ => 1_OS}/Log.md (100%) rename docs/{ => 1_OS}/MidPresentation.md (100%) rename docs/{ => 1_OS}/MidPresentation.pdf (100%) rename docs/{ => 1_OS}/MidReport.md (100%) rename docs/{ => 1_OS}/RISCV.md (100%) rename docs/{ => 2_OSLab/g3}/catfish-proposal.md (100%) rename docs/{ => 2_OSLab/g3}/catfish-proposal.pptx (100%) rename docs/{OSLab => 2_OSLab/g5}/exp2.md (100%) diff --git a/docs/FinalPresentation.pdf b/docs/1_OS/FinalPresentation.pdf similarity index 100% rename from docs/FinalPresentation.pdf rename to docs/1_OS/FinalPresentation.pdf diff --git a/docs/FinalReport.md b/docs/1_OS/FinalReport.md similarity index 100% rename from docs/FinalReport.md rename to docs/1_OS/FinalReport.md diff --git a/docs/Log.md b/docs/1_OS/Log.md similarity index 100% rename from docs/Log.md rename to docs/1_OS/Log.md diff --git a/docs/MidPresentation.md b/docs/1_OS/MidPresentation.md similarity index 100% rename from docs/MidPresentation.md rename to docs/1_OS/MidPresentation.md diff --git a/docs/MidPresentation.pdf b/docs/1_OS/MidPresentation.pdf similarity index 100% rename from docs/MidPresentation.pdf rename to docs/1_OS/MidPresentation.pdf diff --git a/docs/MidReport.md b/docs/1_OS/MidReport.md similarity index 100% rename from docs/MidReport.md rename to docs/1_OS/MidReport.md diff --git a/docs/RISCV.md b/docs/1_OS/RISCV.md similarity index 100% rename from docs/RISCV.md rename to docs/1_OS/RISCV.md diff --git a/docs/catfish-proposal.md b/docs/2_OSLab/g3/catfish-proposal.md similarity index 100% rename from docs/catfish-proposal.md rename to docs/2_OSLab/g3/catfish-proposal.md diff --git a/docs/catfish-proposal.pptx b/docs/2_OSLab/g3/catfish-proposal.pptx similarity index 100% rename from docs/catfish-proposal.pptx rename to docs/2_OSLab/g3/catfish-proposal.pptx diff --git a/docs/OSLab/exp2.md b/docs/2_OSLab/g5/exp2.md similarity index 100% rename from docs/OSLab/exp2.md rename to docs/2_OSLab/g5/exp2.md From 5bc392f3880bb8adfe4badfebb6af9300d9636a0 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Sun, 21 Oct 2018 21:35:28 +0800 Subject: [PATCH 15/58] Enable RV32 IPI. --- crate/bbl/src/sbi.rs | 16 ++++++------ kernel/Makefile | 8 ++++-- kernel/src/arch/riscv32/{smp.rs => cpu.rs} | 16 ++++++------ kernel/src/arch/riscv32/interrupt.rs | 8 ++++++ kernel/src/arch/riscv32/mod.rs | 29 +++++++++++----------- kernel/src/arch/x86_64/cpu.rs | 4 +-- 6 files changed, 46 insertions(+), 35 deletions(-) rename kernel/src/arch/riscv32/{smp.rs => cpu.rs} (70%) diff --git a/crate/bbl/src/sbi.rs b/crate/bbl/src/sbi.rs index b8782b7b..3942fc25 100644 --- a/crate/bbl/src/sbi.rs +++ b/crate/bbl/src/sbi.rs @@ -36,20 +36,20 @@ pub fn clear_ipi() { sbi_call(SBI_CLEAR_IPI, 0, 0, 0); } -pub fn send_ipi(hart_mask: *const usize) { - sbi_call(SBI_SEND_IPI, hart_mask as usize, 0, 0); +pub fn send_ipi(hart_mask: usize) { + sbi_call(SBI_SEND_IPI, &hart_mask as *const _ as usize, 0, 0); } -pub fn remote_fence_i(hart_mask: *const usize) { - sbi_call(SBI_REMOTE_FENCE_I, hart_mask as usize, 0, 0); +pub fn remote_fence_i(hart_mask: usize) { + sbi_call(SBI_REMOTE_FENCE_I, &hart_mask as *const _ as usize, 0, 0); } -pub fn remote_sfence_vma(hart_mask: *const usize, _start: usize, _size: usize) { - sbi_call(SBI_REMOTE_SFENCE_VMA, hart_mask as usize, 0, 0); +pub fn remote_sfence_vma(hart_mask: usize, _start: usize, _size: usize) { + sbi_call(SBI_REMOTE_SFENCE_VMA, &hart_mask as *const _ as usize, 0, 0); } -pub fn remote_sfence_vma_asid(hart_mask: *const usize, _start: usize, _size: usize, _asid: usize) { - sbi_call(SBI_REMOTE_SFENCE_VMA_ASID, hart_mask as usize, 0, 0); +pub fn remote_sfence_vma_asid(hart_mask: usize, _start: usize, _size: usize, _asid: usize) { + sbi_call(SBI_REMOTE_SFENCE_VMA_ASID, &hart_mask as *const _ as usize, 0, 0); } const SBI_SET_TIMER: usize = 0; diff --git a/kernel/Makefile b/kernel/Makefile index a32456b0..0b49f677 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -33,12 +33,16 @@ ifeq ($(arch), x86_64) qemu_opts := \ -drive format=raw,file=$(bootimage) \ -drive format=raw,file=$(SFSIMG),media=disk,cache=writeback \ - -smp $(smp) \ + -smp cores=$(smp) \ -serial mon:stdio \ -device isa-debug-exit endif ifeq ($(arch), riscv32) -qemu_opts := -machine virt -kernel $(bin) -nographic -smp cpus=$(smp) +qemu_opts := \ + -machine virt \ + -kernel $(bin) \ + -nographic \ + -smp cores=$(smp) endif ifdef board diff --git a/kernel/src/arch/riscv32/smp.rs b/kernel/src/arch/riscv32/cpu.rs similarity index 70% rename from kernel/src/arch/riscv32/smp.rs rename to kernel/src/arch/riscv32/cpu.rs index aab6e4ea..0b95107b 100644 --- a/kernel/src/arch/riscv32/smp.rs +++ b/kernel/src/arch/riscv32/cpu.rs @@ -5,19 +5,19 @@ use memory::*; static mut STARTED: [bool; MAX_CPU_NUM] = [false; MAX_CPU_NUM]; pub unsafe fn set_cpu_id(cpu_id: usize) { - unsafe { - asm!("mv tp, $0" : : "r"(cpu_id)); - } + asm!("mv tp, $0" : : "r"(cpu_id)); } -pub unsafe fn get_cpu_id() -> usize { - let mut cpu_id = 0; - unsafe { - asm!("mv $0, tp" : : "r" (cpu_id)); - } +pub fn id() -> usize { + let cpu_id; + unsafe { asm!("mv $0, tp" : "=r"(cpu_id)); } cpu_id } +pub fn send_ipi(cpu_id: usize) { + super::bbl::sbi::send_ipi(1 << cpu_id); +} + pub unsafe fn has_started(cpu_id: usize) -> bool { read_volatile(&STARTED[cpu_id]) } diff --git a/kernel/src/arch/riscv32/interrupt.rs b/kernel/src/arch/riscv32/interrupt.rs index 58ed6728..cee4bc17 100644 --- a/kernel/src/arch/riscv32/interrupt.rs +++ b/kernel/src/arch/riscv32/interrupt.rs @@ -14,6 +14,8 @@ pub fn init() { sscratch::write(0); // Set the exception vector address stvec::write(__alltraps as usize, stvec::TrapMode::Direct); + // Enable IPI + sie::set_ssoft(); } info!("interrupt: init end"); } @@ -42,6 +44,7 @@ pub extern fn rust_trap(tf: &mut TrapFrame) { use super::riscv::register::scause::{Trap, Interrupt as I, Exception as E}; trace!("Interrupt: {:?}", tf.scause.cause()); match tf.scause.cause() { + Trap::Interrupt(I::SupervisorSoft) => ipi(), Trap::Interrupt(I::SupervisorTimer) => timer(), Trap::Exception(E::IllegalInstruction) => illegal_inst(tf), Trap::Exception(E::UserEnvCall) => syscall(tf), @@ -51,6 +54,11 @@ pub extern fn rust_trap(tf: &mut TrapFrame) { trace!("Interrupt end"); } +fn ipi() { + debug!("IPI"); + super::bbl::sbi::clear_ipi(); +} + fn timer() { ::trap::timer(); super::timer::set_next(); diff --git a/kernel/src/arch/riscv32/mod.rs b/kernel/src/arch/riscv32/mod.rs index aadbdaf1..c6ccc1c9 100644 --- a/kernel/src/arch/riscv32/mod.rs +++ b/kernel/src/arch/riscv32/mod.rs @@ -8,36 +8,35 @@ pub mod paging; pub mod memory; pub mod compiler_rt; pub mod consts; -pub mod smp; - -use self::smp::*; - -fn others_main(hartid: usize) -> ! { - println!("hart {} is booting", hartid); - loop { } -} +pub mod cpu; #[no_mangle] pub extern fn rust_main(hartid: usize, dtb: usize, hart_mask: usize) -> ! { - unsafe { set_cpu_id(hartid); } + unsafe { cpu::set_cpu_id(hartid); } + println!("Hello RISCV! in hart {}, {}, {}", hartid, dtb, hart_mask); if hartid != 0 { - while unsafe { !has_started(hartid) } { } - others_main(hartid); - // others_main should not return + while unsafe { !cpu::has_started(hartid) } { } + others_main(); + unreachable!(); } - println!("Hello RISCV! in hart {}, {}, {}", hartid, dtb, hart_mask); - ::logging::init(); interrupt::init(); memory::init(); timer::init(); - unsafe { start_others(hart_mask); } + unsafe { cpu::start_others(hart_mask); } ::kmain(); } +fn others_main() -> ! { + interrupt::init(); + timer::init(); + cpu::send_ipi(0); + loop { } +} + #[cfg(feature = "no_bbl")] global_asm!(include_str!("boot/boot.asm")); global_asm!(include_str!("boot/entry.asm")); diff --git a/kernel/src/arch/x86_64/cpu.rs b/kernel/src/arch/x86_64/cpu.rs index 18a63d2d..b8957dce 100644 --- a/kernel/src/arch/x86_64/cpu.rs +++ b/kernel/src/arch/x86_64/cpu.rs @@ -16,9 +16,9 @@ pub fn id() -> usize { CpuId::new().get_feature_info().unwrap().initial_local_apic_id() as usize } -pub fn send_ipi(cpu_id: u8) { +pub fn send_ipi(cpu_id: usize) { let mut lapic = unsafe { XApic::new(0xffffff00_fee00000) }; - unsafe { lapic.send_ipi(cpu_id, 0x30); } // TODO: Find a IPI trap num + unsafe { lapic.send_ipi(cpu_id as u8, 0x30); } // TODO: Find a IPI trap num } pub fn init() { From 6741ba399b99692fdfff101fddd9e61ce1b9a102 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 24 Oct 2018 00:05:13 +0800 Subject: [PATCH 16/58] Add arch::cpu::halt(). Halt when panic. --- kernel/src/arch/riscv32/cpu.rs | 5 +++++ kernel/src/arch/x86_64/cpu.rs | 5 +++++ kernel/src/lang.rs | 5 +++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/kernel/src/arch/riscv32/cpu.rs b/kernel/src/arch/riscv32/cpu.rs index 0b95107b..fedd8532 100644 --- a/kernel/src/arch/riscv32/cpu.rs +++ b/kernel/src/arch/riscv32/cpu.rs @@ -28,4 +28,9 @@ pub unsafe fn start_others(hart_mask: usize) { write_volatile(&mut STARTED[cpu_id], true); } } +} + +pub fn halt() { + use super::riscv::asm::wfi; + unsafe { wfi() } } \ No newline at end of file diff --git a/kernel/src/arch/x86_64/cpu.rs b/kernel/src/arch/x86_64/cpu.rs index b8957dce..ae427e91 100644 --- a/kernel/src/arch/x86_64/cpu.rs +++ b/kernel/src/arch/x86_64/cpu.rs @@ -24,4 +24,9 @@ pub fn send_ipi(cpu_id: usize) { pub fn init() { let mut lapic = unsafe { XApic::new(0xffffff00_fee00000) }; lapic.cpu_init(); +} + +pub fn halt() { + use x86_64::instructions::hlt; + hlt(); } \ No newline at end of file diff --git a/kernel/src/lang.rs b/kernel/src/lang.rs index a289ccc6..c4596d24 100644 --- a/kernel/src/lang.rs +++ b/kernel/src/lang.rs @@ -1,4 +1,4 @@ -// Rust language features implementions +// Rust language features implementations use core::panic::PanicInfo; use core::alloc::Layout; @@ -13,7 +13,8 @@ pub fn panic(info: &PanicInfo) -> ! { let location = info.location().unwrap(); let message = info.message().unwrap(); error!("\n\nPANIC in {} at line {}\n {}", location.file(), location.line(), message); - loop { } + use arch::cpu::halt; + loop { halt() } } #[lang = "oom"] From f7eb09e85625cb7b605bc74e2bd1cf1991292741 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 24 Oct 2018 00:27:25 +0800 Subject: [PATCH 17/58] Multicore processing WORKS! Basically ... - Rewrite processor.rs Refactor to `Processor` & `ProcessManager` - Use Box instead of generic. - Wait/sleep/wakeup is not supported yet. I'm considering to implement them with WaitQueue. --- crate/process/Cargo.toml | 1 + crate/process/src/lib.rs | 1 + crate/process/src/processor.rs | 403 ++++++++++++++------------------- crate/process/src/thread.rs | 49 ++-- kernel/Cargo.lock | 1 + kernel/src/arch/x86_64/mod.rs | 3 +- kernel/src/fs.rs | 4 +- kernel/src/lib.rs | 11 +- kernel/src/process/context.rs | 56 +++-- kernel/src/process/mod.rs | 56 ++--- kernel/src/syscall.rs | 33 +-- kernel/src/trap.rs | 23 +- 12 files changed, 277 insertions(+), 364 deletions(-) diff --git a/crate/process/Cargo.toml b/crate/process/Cargo.toml index c73e482d..72a3883d 100644 --- a/crate/process/Cargo.toml +++ b/crate/process/Cargo.toml @@ -5,3 +5,4 @@ authors = ["WangRunji "] [dependencies] log = "0.4" +spin = "0.4" \ No newline at end of file diff --git a/crate/process/src/lib.rs b/crate/process/src/lib.rs index e3d25540..625e2b67 100644 --- a/crate/process/src/lib.rs +++ b/crate/process/src/lib.rs @@ -5,6 +5,7 @@ extern crate alloc; #[macro_use] extern crate log; +extern crate spin; // To use `println!` in test #[cfg(test)] diff --git a/crate/process/src/processor.rs b/crate/process/src/processor.rs index 7db17f6a..8de30751 100644 --- a/crate/process/src/processor.rs +++ b/crate/process/src/processor.rs @@ -1,273 +1,208 @@ -use alloc::{boxed::Box, collections::BTreeMap}; -use scheduler::*; -use event_hub::EventHub; -use util::GetMut2; -use core::fmt::Debug; +use alloc::boxed::Box; +use alloc::vec::Vec; +use alloc::sync::Arc; +use spin::Mutex; +use scheduler::Scheduler; +use core::cell::UnsafeCell; -#[derive(Debug)] -pub struct Process { - pid: Pid, - parent: Pid, - status: Status, - context: T, +/// Process executor +/// +/// Per-CPU struct. Defined at global. +/// Only accessed by associated CPU with interrupt disabled. +#[derive(Default)] +pub struct Processor { + inner: UnsafeCell>, } -pub type Pid = usize; -pub type ErrorCode = usize; +unsafe impl Sync for Processor {} + +struct ProcessorInner { + id: usize, + proc: Option<(Pid, Box)>, + loop_context: Box, + manager: Arc, +} + +impl Processor { + pub const fn new() -> Self { + Processor { inner: UnsafeCell::new(None) } + } + + pub unsafe fn init(&self, id: usize, context: Box, manager: Arc) { + unsafe { + *self.inner.get() = Some(ProcessorInner { + id, + proc: None, + loop_context: context, + manager, + }); + } + } + + fn inner(&self) -> &mut ProcessorInner { + unsafe { &mut *self.inner.get() }.as_mut() + .expect("Processor is not initialized") + } + + /// Begin running processes after CPU setup. + /// + /// This function never returns. It loops, doing: + /// - choose a process to run + /// - switch to start running that process + /// - eventually that process transfers control + /// via switch back to the scheduler. + pub fn run(&self) -> ! { + let inner = self.inner(); + loop { + let proc = inner.manager.run(inner.id); + trace!("CPU{} begin running process {}", inner.id, proc.0); + inner.proc = Some(proc); + unsafe { + inner.loop_context.switch_to(&mut *inner.proc.as_mut().unwrap().1); + } + let (pid, context) = inner.proc.take().unwrap(); + trace!("CPU{} stop running process {}", inner.id, pid); + inner.manager.stop(pid, context); + } + } + + /// Called by process running on this Processor. + /// Yield and reschedule. + pub fn yield_now(&self) { + let inner = self.inner(); + unsafe { + inner.proc.as_mut().unwrap().1.switch_to(&mut *inner.loop_context); + } + } + + pub fn pid(&self) -> Pid { + self.inner().proc.as_ref().unwrap().0 + } + + pub fn manager(&self) -> &ProcessManager { + &*self.inner().manager + } + + pub fn tick(&self) { + let need_reschedule = self.manager().tick(self.pid()); + if need_reschedule { + self.yield_now(); + } + } +} + +struct Process { + id: Pid, + status: Status, + status_after_stop: Status, + context: Option>, +} + +type Pid = usize; +type ExitCode = usize; +const MAX_PROC_NUM: usize = 32; #[derive(Debug, Clone, Eq, PartialEq)] pub enum Status { Ready, - Running, + Running(usize), Waiting(Pid), Sleeping, - Exited(ErrorCode), + Exited(ExitCode), } -pub trait Context: Debug { - unsafe fn switch(&mut self, target: &mut Self); - fn new_kernel(entry: extern fn(usize) -> !, arg: usize) -> Self; +pub trait Context { + unsafe fn switch_to(&mut self, target: &mut Context); } -pub struct Processor_ { - procs: BTreeMap>, - current_pid: Pid, - event_hub: EventHub, - // WARNING: if MAX_PROCESS_NUM is too large, will cause stack overflow - scheduler: S, +pub struct ProcessManager { + procs: [Mutex>; MAX_PROC_NUM], + scheduler: Mutex>, } -impl Process { - fn exit_code(&self) -> Option { - match self.status { - Status::Exited(code) => Some(code), - _ => None, +impl ProcessManager { + + pub fn new(scheduler: Box) -> Self { + ProcessManager { + procs: Default::default(), + scheduler: Mutex::new(scheduler), } } -} - -// TODO: 除schedule()外的其它函数,应该只设置进程状态,不应调用schedule -impl Processor_ { - pub fn new(init_context: T, scheduler: S) -> Self { - let init_proc = Process { - pid: 0, - parent: 0, - status: Status::Running, - context: init_context, - }; - Processor_ { - procs: { - let mut map = BTreeMap::>::new(); - map.insert(0, init_proc); - map - }, - current_pid: 0, - event_hub: EventHub::new(), - scheduler, - } - } - - pub fn set_priority(&mut self, priority: u8) { - self.scheduler.set_priority(self.current_pid, priority); - } - - pub fn yield_now(&mut self) { - let pid = self.current_pid; - self.set_status(pid, Status::Ready); - } fn alloc_pid(&self) -> Pid { - let mut next: Pid = 0; - for &i in self.procs.keys() { - if i != next { - return next; - } else { - next = i + 1; + for i in 0..MAX_PROC_NUM { + if self.procs[i].lock().is_none() { + return i; } } - return next; + panic!("Process number exceeded"); } - fn set_status(&mut self, pid: Pid, status: Status) { - let status0 = self.get(pid).status.clone(); - match (&status0, &status) { - (&Status::Ready, &Status::Ready) => return, - (&Status::Ready, _) => self.scheduler.remove(pid), - (_, &Status::Ready) => self.scheduler.insert(pid), - _ => {} - } - trace!("process {} {:?} -> {:?}", pid, status0, status); - self.get_mut(pid).status = status; - } - - /// Called by timer. - /// Handle events. - pub fn tick(&mut self) { - let current_pid = self.current_pid; - if self.scheduler.tick(current_pid) { - self.yield_now(); - } - self.event_hub.tick(); - while let Some(event) = self.event_hub.pop() { - debug!("event {:?}", event); - match event { - Event::Schedule => { - self.event_hub.push(10, Event::Schedule); - self.yield_now(); - }, - Event::Wakeup(pid) => { - self.set_status(pid, Status::Ready); - self.yield_now(); - self.scheduler.move_to_head(pid); - }, - } - } - } - - pub fn get_time(&self) -> usize { - self.event_hub.get_time() - } - - pub fn add(&mut self, context: T) -> Pid { + /// Add a new process + pub fn add(&self, context: Box) -> Pid { let pid = self.alloc_pid(); - let process = Process { - pid, - parent: self.current_pid, + // TODO: check parent + *self.procs[pid].lock() = Some(Process { + id: pid, status: Status::Ready, - context, - }; - self.scheduler.insert(pid); - self.procs.insert(pid, process); + status_after_stop: Status::Ready, + context: Some(context), + }); + self.scheduler.lock().insert(pid); pid } - /// Called every interrupt end - /// Do schedule ONLY IF current status != Running - pub fn schedule(&mut self) { - if self.get(self.current_pid).status == Status::Running { - return; - } - let pid = self.scheduler.select().unwrap(); - self.switch_to(pid); + /// Make process `pid` time slice -= 1. + /// Return true if time slice == 0. + /// Called by timer interrupt handler. + pub fn tick(&self, pid: Pid) -> bool { + self.scheduler.lock().tick(pid) } - /// Switch to process `pid`. - /// The current status must be set before, and not be `Running`. - /// The target status must be `Ready`. - fn switch_to(&mut self, pid: Pid) { - // for debug print - let pid0 = self.current_pid; - - if pid == self.current_pid { - if self.get(self.current_pid).status != Status::Running { - self.set_status(pid, Status::Running); - } - return; - } - self.current_pid = pid; - - let (from, to) = self.procs.get_mut2(pid0, pid); - - assert_ne!(from.status, Status::Running); - assert_eq!(to.status, Status::Ready); - to.status = Status::Running; - self.scheduler.remove(pid); - - info!("switch from {} to {} {:x?}", pid0, pid, to.context); - unsafe { from.context.switch(&mut to.context); } + /// Called by Processor to get a process to run. + /// The manager first mark it `Running`, + /// then take out and return its Context. + pub fn run(&self, cpu_id: usize) -> (Pid, Box) { + let mut scheduler = self.scheduler.lock(); + let pid = scheduler.select() + .expect("failed to select a runnable process"); + scheduler.remove(pid); + let mut proc_lock = self.procs[pid].lock(); + let mut proc = proc_lock.as_mut().unwrap(); + proc.status = Status::Running(cpu_id); + (pid, proc.context.take().unwrap()) } - fn get(&self, pid: Pid) -> &Process { - self.procs.get(&pid).unwrap() - } - fn get_mut(&mut self, pid: Pid) -> &mut Process { - self.procs.get_mut(&pid).unwrap() - } - pub fn current_context(&self) -> &T { - &self.get(self.current_pid).context - } - pub fn current_pid(&self) -> Pid { - self.current_pid - } - - pub fn kill(&mut self, pid: Pid) { - self.exit(pid, 0x1000); // TODO: error code for killed - } - - pub fn exit(&mut self, pid: Pid, error_code: ErrorCode) { - info!("{} exit, code: {}", pid, error_code); - self.set_status(pid, Status::Exited(error_code)); - if let Some(waiter) = self.find_waiter(pid) { - info!(" then wakeup {}", waiter); - self.set_status(waiter, Status::Ready); - self.scheduler.move_to_head(waiter); + /// Called by Processor to finish running a process + /// and give its context back. + pub fn stop(&self, pid: Pid, context: Box) { + let mut proc_lock = self.procs[pid].lock(); + let mut proc = proc_lock.as_mut().unwrap(); + proc.status = proc.status_after_stop.clone(); + proc.status_after_stop = Status::Ready; + proc.context = Some(context); + if proc.status == Status::Ready { + self.scheduler.lock().insert(pid); } } - pub fn sleep(&mut self, pid: Pid, time: usize) { - self.set_status(pid, Status::Sleeping); - self.event_hub.push(time, Event::Wakeup(pid)); - } - pub fn sleep_(&mut self, pid: Pid) { - self.set_status(pid, Status::Sleeping); - } - pub fn wakeup_(&mut self, pid: Pid) { - self.set_status(pid, Status::Ready); - } - - /// Let current process wait for another - pub fn current_wait_for(&mut self, pid: Pid) -> WaitResult { - info!("current {} wait for {:?}", self.current_pid, pid); - if self.procs.values().filter(|&p| p.parent == self.current_pid).next().is_none() { - return WaitResult::NotExist; + /// Switch the status of a process. + /// Insert/Remove it to/from scheduler if necessary. + pub fn set_status(&self, pid: Pid, status: Status) { + let mut scheduler = self.scheduler.lock(); + let mut proc_lock = self.procs[pid].lock(); + let mut proc = proc_lock.as_mut().unwrap(); + match (&proc.status, &status) { + (Status::Ready, Status::Ready) => return, + (Status::Ready, _) => scheduler.remove(pid), + (Status::Running(_), _) => {}, + (_, Status::Ready) => scheduler.insert(pid), + _ => {} } - let pid = self.try_wait(pid).unwrap_or_else(|| { - let current_pid = self.current_pid; - self.set_status(current_pid, Status::Waiting(pid)); - self.schedule(); // yield - self.try_wait(pid).unwrap() - }); - let exit_code = self.get(pid).exit_code().unwrap(); - info!("{} wait end and remove {}", self.current_pid, pid); - self.procs.remove(&pid); - WaitResult::Ok(pid, exit_code) - } - - /// Try to find a exited wait target - fn try_wait(&mut self, pid: Pid) -> Option { - match pid { - 0 => self.procs.values() - .find(|&p| p.parent == self.current_pid && p.exit_code().is_some()) - .map(|p| p.pid), - _ => self.get(pid).exit_code().map(|_| pid), + trace!("process {} {:?} -> {:?}", pid, proc.status, status); + match proc.status { + Status::Running(_) => proc.status_after_stop = status, + _ => proc.status = status, } } - - fn find_waiter(&self, pid: Pid) -> Option { - self.procs.values().find(|&p| { - p.status == Status::Waiting(pid) || - (p.status == Status::Waiting(0) && self.get(pid).parent == p.pid) - }).map(|ref p| p.pid) - } -} - -#[derive(Debug)] -pub enum WaitResult { - /// The target process is exited with `ErrorCode`. - Ok(Pid, ErrorCode), - /// The target process is not exist. - NotExist, -} - -#[derive(Debug)] -enum Event { - Schedule, - Wakeup(Pid), -} - -impl GetMut2 for BTreeMap> { - type Output = Process; - fn get_mut(&mut self, id: Pid) -> &mut Process { - self.get_mut(&id).unwrap() - } } \ No newline at end of file diff --git a/crate/process/src/thread.rs b/crate/process/src/thread.rs index 818a28ad..b72ebfe8 100644 --- a/crate/process/src/thread.rs +++ b/crate/process/src/thread.rs @@ -38,10 +38,8 @@ use scheduler::Scheduler; /// All dependencies for thread mod. pub trait ThreadSupport { - type Context: Context; - type Scheduler: Scheduler; - type ProcessorGuard: DerefMut>; - fn processor() -> Self::ProcessorGuard; + fn processor() -> &'static Processor; + fn new_kernel(entry: extern fn(usize) -> !, arg: usize) -> Box; } /// Root structure served as thread mod @@ -53,7 +51,7 @@ impl ThreadMod { /// Gets a handle to the thread that invokes it. pub fn current() -> Thread { Thread { - pid: S::processor().current_pid(), + pid: S::processor().pid(), mark: PhantomData, } } @@ -62,10 +60,8 @@ impl ThreadMod { pub fn sleep(dur: Duration) { let time = dur_to_ticks(dur); info!("sleep: {:?} ticks", time); - let mut processor = S::processor(); - let pid = processor.current_pid(); - processor.sleep(pid, time); - processor.schedule(); + // TODO: register wakeup + Self::park(); fn dur_to_ticks(dur: Duration) -> usize { return dur.as_secs() as usize * 100 + dur.subsec_nanos() as usize / 10_000_000; @@ -111,16 +107,17 @@ impl ThreadMod { // unsafe { LocalKey::::get_map() }.clear(); // 让Processor退出当前线程 // 把f返回值在堆上的指针,以线程返回码的形式传递出去 - let mut processor = S::processor(); - let pid = processor.current_pid(); - processor.exit(pid, Box::into_raw(ret) as usize); - processor.schedule(); + let pid = S::processor().pid(); + let exit_code = Box::into_raw(ret) as usize; + S::processor().manager().set_status(pid, Status::Exited(exit_code)); + S::processor().yield_now(); // 再也不会被调度回来了 unreachable!() } // 在Processor中创建新的线程 - let pid = S::processor().add(Context::new_kernel(kernel_thread_entry::, f as usize)); + let context = S::new_kernel(kernel_thread_entry::, f as usize); + let pid = S::processor().manager().add(context); // 接下来看看`JoinHandle::join()`的实现 // 了解是如何获取f返回值的 @@ -133,18 +130,15 @@ impl ThreadMod { /// Cooperatively gives up a timeslice to the OS scheduler. pub fn yield_now() { info!("yield:"); - let mut processor = S::processor(); - processor.yield_now(); - processor.schedule(); + S::processor().yield_now(); } /// Blocks unless or until the current thread's token is made available. pub fn park() { info!("park:"); - let mut processor = S::processor(); - let pid = processor.current_pid(); - processor.sleep_(pid); - processor.schedule(); + let pid = S::processor().pid(); + S::processor().manager().set_status(pid, Status::Sleeping); + S::processor().yield_now(); } } @@ -157,8 +151,7 @@ pub struct Thread { impl Thread { /// Atomically makes the handle's token available if it is not already. pub fn unpark(&self) { - let mut processor = S::processor(); - processor.wakeup_(self.pid); + S::processor().manager().set_status(self.pid, Status::Ready); } /// Gets the thread's unique identifier. pub fn id(&self) -> usize { @@ -179,14 +172,8 @@ impl JoinHandle { } /// Waits for the associated thread to finish. pub fn join(self) -> Result { - let mut processor = S::processor(); - match processor.current_wait_for(self.thread.pid) { - WaitResult::Ok(_, exit_code) => unsafe { - // Find return value on the heap from the exit code. - Ok(*Box::from_raw(exit_code as *mut T)) - } - WaitResult::NotExist => Err(()), - } + // TODO: wait for the thread + unimplemented!() } } diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index 83938d60..6d9f4456 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -285,6 +285,7 @@ name = "ucore-process" version = "0.1.0" dependencies = [ "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs index db30cf99..b9a57f89 100644 --- a/kernel/src/arch/x86_64/mod.rs +++ b/kernel/src/arch/x86_64/mod.rs @@ -57,6 +57,5 @@ fn other_start() -> ! { idt::init(); gdt::init(); cpu::init(); -// unsafe{ let a = *(0xdeadbeaf as *const u8); } // Page fault - loop {} + ::kmain(); } \ No newline at end of file diff --git a/kernel/src/fs.rs b/kernel/src/fs.rs index 6c9c9582..41954c03 100644 --- a/kernel/src/fs.rs +++ b/kernel/src/fs.rs @@ -46,8 +46,8 @@ pub fn shell() { if let Ok(file) = root.borrow().lookup(name.as_str()) { use process::*; let len = file.borrow().read_at(0, &mut *buf).unwrap(); - let pid = processor().add(Context::new_user(&buf[..len])); - processor().current_wait_for(pid); + processor().manager().add(ContextImpl::new_user(&buf[..len])); + // TODO: wait for new process } else { println!("Program not exist"); } diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 4a23294a..73c6dbbc 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -9,6 +9,7 @@ #![feature(panic_info_message)] #![feature(global_asm)] #![feature(compiler_builtins_lib)] +#![feature(raw)] #![no_std] @@ -61,18 +62,18 @@ pub mod arch; pub mod arch; pub fn kmain() -> ! { - process::init(); - unsafe { arch::interrupt::enable(); } + if arch::cpu::id() == 0 { + process::init(); + thread::spawn(fs::shell); + } - fs::shell(); + process::processor().run(); // thread::test::local_key(); // thread::test::unpack(); // sync::test::philosopher_using_mutex(); // sync::test::philosopher_using_monitor(); // sync::mpsc::test::test_all(); - - loop {} } /// Global heap allocator diff --git a/kernel/src/process/context.rs b/kernel/src/process/context.rs index d4ebd96a..eb311e4a 100644 --- a/kernel/src/process/context.rs +++ b/kernel/src/process/context.rs @@ -2,39 +2,45 @@ use arch::interrupt::{TrapFrame, Context as ArchContext}; use memory::{MemoryArea, MemoryAttr, MemorySet}; use xmas_elf::{ElfFile, header, program::{Flags, ProgramHeader, Type}}; use core::fmt::{Debug, Error, Formatter}; +use ucore_process::processor::Context; +use alloc::boxed::Box; -pub struct Context { +pub struct ContextImpl { arch: ArchContext, memory_set: MemorySet, } -impl ::ucore_process::processor::Context for Context { - unsafe fn switch(&mut self, target: &mut Self) { - super::PROCESSOR.try().unwrap().force_unlock(); - self.arch.switch(&mut target.arch); - use core::mem::forget; - forget(super::processor()); - } +impl Context for ContextImpl { + unsafe fn switch_to(&mut self, target: &mut Context) { + use core::raw::TraitObject; + use core::mem::transmute; - fn new_kernel(entry: extern fn(usize) -> !, arg: usize) -> Self { - let ms = MemorySet::new(); - Context { - arch: unsafe { ArchContext::new_kernel_thread(entry, arg, ms.kstack_top(), ms.token()) }, - memory_set: ms, - } + // Cast &mut Context -> &mut ContextImpl + let raw: TraitObject = transmute(target); + let target = &mut *(raw.data as *mut ContextImpl); + + self.arch.switch(&mut target.arch); } } -impl Context { - pub unsafe fn new_init() -> Self { - Context { +impl ContextImpl { + pub unsafe fn new_init() -> Box { + Box::new(ContextImpl { arch: ArchContext::null(), memory_set: MemorySet::new(), - } + }) + } + + pub fn new_kernel(entry: extern fn(usize) -> !, arg: usize) -> Box { + let ms = MemorySet::new(); + Box::new(ContextImpl { + arch: unsafe { ArchContext::new_kernel_thread(entry, arg, ms.kstack_top(), ms.token()) }, + memory_set: ms, + }) } /// Make a new user thread from ELF data - pub fn new_user(data: &[u8]) -> Self { + pub fn new_user(data: &[u8]) -> Box { // Parse elf let elf = ElfFile::new(data).expect("failed to read elf"); let is32 = match elf.header.pt2 { @@ -81,17 +87,17 @@ impl Context { }); } - Context { + Box::new(ContextImpl { arch: unsafe { ArchContext::new_user_thread( entry_addr, user_stack_top - 8, memory_set.kstack_top(), is32, memory_set.token()) }, memory_set, - } + }) } /// Fork - pub fn fork(&self, tf: &TrapFrame) -> Self { + pub fn fork(&self, tf: &TrapFrame) -> Box { // Clone memory set, make a new page table let memory_set = self.memory_set.clone(); @@ -110,14 +116,14 @@ impl Context { }); } - Context { + Box::new(ContextImpl { arch: unsafe { ArchContext::new_fork(tf, memory_set.kstack_top(), memory_set.token()) }, memory_set, - } + }) } } -impl Debug for Context { +impl Debug for ContextImpl { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { write!(f, "{:x?}", self.arch) } diff --git a/kernel/src/process/mod.rs b/kernel/src/process/mod.rs index 569f3d2a..5bc17a34 100644 --- a/kernel/src/process/mod.rs +++ b/kernel/src/process/mod.rs @@ -1,36 +1,41 @@ use spin::Once; use sync::{SpinNoIrqLock, Mutex, MutexGuard, SpinNoIrq}; -pub use self::context::Context; -pub use ucore_process::processor::{*, Context as _whatever}; +pub use self::context::ContextImpl; +pub use ucore_process::processor::*; pub use ucore_process::scheduler::*; pub use ucore_process::thread::*; +use alloc::boxed::Box; +use consts::MAX_CPU_NUM; +use arch::cpu; +use alloc::sync::Arc; +use alloc::vec::Vec; mod context; -type Processor = Processor_; - pub fn init() { - PROCESSOR.call_once(|| - SpinNoIrqLock::new({ - let mut processor = Processor::new( - unsafe { Context::new_init() }, - // NOTE: max_time_slice <= 5 to ensure 'priority' test pass - StrideScheduler::new(5), - ); - extern fn idle(arg: usize) -> ! { - loop {} - } - processor.add(Context::new_kernel(idle, 0)); - processor - }) - ); + // NOTE: max_time_slice <= 5 to ensure 'priority' test pass + let scheduler = Box::new(RRScheduler::new(5)); + let manager = Arc::new(ProcessManager::new(scheduler)); + + extern fn idle(_arg: usize) -> ! { + loop { cpu::halt(); } + } + for i in 0..MAX_CPU_NUM { + manager.add(ContextImpl::new_kernel(idle, i)); + } + + unsafe { + for cpu_id in 0..MAX_CPU_NUM { + PROCESSORS[cpu_id].init(cpu_id, ContextImpl::new_init(), manager.clone()); + } + } info!("process init end"); } -pub static PROCESSOR: Once> = Once::new(); +static PROCESSORS: [Processor; MAX_CPU_NUM] = [Processor::new(), Processor::new(), Processor::new(), Processor::new(), Processor::new(), Processor::new(), Processor::new(), Processor::new()]; -pub fn processor() -> MutexGuard<'static, Processor, SpinNoIrq> { - PROCESSOR.try().unwrap().lock() +pub fn processor() -> &'static Processor { + &PROCESSORS[cpu::id()] } #[allow(non_camel_case_types)] @@ -43,11 +48,10 @@ pub mod thread_ { pub struct ThreadSupportImpl; impl ThreadSupport for ThreadSupportImpl { - type Context = Context; - type Scheduler = StrideScheduler; - type ProcessorGuard = MutexGuard<'static, Processor, SpinNoIrq>; - - fn processor() -> Self::ProcessorGuard { + fn processor() -> &'static Processor { processor() } + fn new_kernel(entry: extern fn(usize) -> !, arg: usize) -> Box { + ContextImpl::new_kernel(entry, arg) + } } \ No newline at end of file diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 80a170cb..5c48ecf4 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -58,26 +58,13 @@ fn sys_close(fd: usize) -> i32 { /// Fork the current process. Return the child's PID. fn sys_fork(tf: &TrapFrame) -> i32 { - let mut processor = processor(); - let context = processor.current_context().fork(tf); - let pid = processor.add(context); - info!("fork: {} -> {}", processor.current_pid(), pid); - pid as i32 + unimplemented!(); } /// Wait the process exit. /// Return the PID. Store exit code to `code` if it's not null. fn sys_wait(pid: usize, code: *mut i32) -> i32 { - let mut processor = processor(); - match processor.current_wait_for(pid) { - WaitResult::Ok(pid, error_code) => { - if !code.is_null() { - unsafe { *code = error_code as i32 }; - } - 0 - } - WaitResult::NotExist => -1, - } + unimplemented!(); } fn sys_yield() -> i32 { @@ -87,7 +74,7 @@ fn sys_yield() -> i32 { /// Kill the process fn sys_kill(pid: usize) -> i32 { - processor().kill(pid); + processor().manager().set_status(pid, Status::Exited(0x100)); 0 } @@ -97,10 +84,10 @@ fn sys_getpid() -> i32 { } /// Exit the current process -fn sys_exit(error_code: usize) -> i32 { - let mut processor = processor(); - let pid = processor.current_pid(); - processor.exit(pid, error_code); +fn sys_exit(exit_code: usize) -> i32 { + let pid = processor().pid(); + processor().manager().set_status(pid, Status::Exited(exit_code)); + processor().yield_now(); 0 } @@ -111,13 +98,11 @@ fn sys_sleep(time: usize) -> i32 { } fn sys_get_time() -> i32 { - let processor = processor(); - processor.get_time() as i32 + unimplemented!(); } fn sys_lab6_set_priority(priority: usize) -> i32 { - let mut processor = processor(); - processor.set_priority(priority as u8); + unimplemented!(); 0 } diff --git a/kernel/src/trap.rs b/kernel/src/trap.rs index 42c7abcc..6cd4556a 100644 --- a/kernel/src/trap.rs +++ b/kernel/src/trap.rs @@ -1,26 +1,19 @@ use process::*; use arch::interrupt::TrapFrame; +use arch::cpu; pub fn timer() { - let mut processor = processor(); - processor.tick(); + processor().tick(); } pub fn before_return() { - if let Some(processor) = PROCESSOR.try() { - processor.lock().schedule(); - } } pub fn error(tf: &TrapFrame) -> ! { - if let Some(processor) = PROCESSOR.try() { - let mut processor = processor.lock(); - let pid = processor.current_pid(); - error!("Process {} error:\n{:#x?}", pid, tf); - processor.exit(pid, 0x100); // TODO: Exit code for error - processor.schedule(); - unreachable!(); - } else { - panic!("Exception when processor not inited\n{:#x?}", tf); - } + let pid = processor().pid(); + error!("On CPU{} Process {}:\n{:#x?}", cpu::id(), pid, tf); + + processor().manager().set_status(pid, Status::Exited(0x100)); + processor().yield_now(); + unreachable!(); } \ No newline at end of file From 5db908b1c5729e1f6f3b4964ee933f9178b6b5dd Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 24 Oct 2018 00:38:22 +0800 Subject: [PATCH 18/58] Separate ProcessManager to a mod. --- crate/process/src/lib.rs | 6 +- crate/process/src/process_manager.rs | 120 +++++++++++++++++++++++++++ crate/process/src/processor.rs | 119 +------------------------- crate/process/src/thread.rs | 3 +- kernel/src/process/context.rs | 2 +- kernel/src/process/mod.rs | 5 +- 6 files changed, 130 insertions(+), 125 deletions(-) create mode 100644 crate/process/src/process_manager.rs diff --git a/crate/process/src/lib.rs b/crate/process/src/lib.rs index 625e2b67..11415181 100644 --- a/crate/process/src/lib.rs +++ b/crate/process/src/lib.rs @@ -12,8 +12,12 @@ extern crate spin; #[macro_use] extern crate std; -pub mod processor; +mod process_manager; +mod processor; pub mod scheduler; pub mod thread; mod util; mod event_hub; + +pub use process_manager::*; +pub use processor::Processor; diff --git a/crate/process/src/process_manager.rs b/crate/process/src/process_manager.rs new file mode 100644 index 00000000..dd8dab54 --- /dev/null +++ b/crate/process/src/process_manager.rs @@ -0,0 +1,120 @@ +use alloc::boxed::Box; +use alloc::sync::Arc; +use spin::Mutex; +use scheduler::Scheduler; +use core::cell::UnsafeCell; + +struct Process { + id: Pid, + status: Status, + status_after_stop: Status, + context: Option>, +} + +pub type Pid = usize; +type ExitCode = usize; +const MAX_PROC_NUM: usize = 32; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Status { + Ready, + Running(usize), + Waiting(Pid), + Sleeping, + Exited(ExitCode), +} + +pub trait Context { + unsafe fn switch_to(&mut self, target: &mut Context); +} + +pub struct ProcessManager { + procs: [Mutex>; MAX_PROC_NUM], + scheduler: Mutex>, +} + +impl ProcessManager { + pub fn new(scheduler: Box) -> Self { + ProcessManager { + procs: Default::default(), + scheduler: Mutex::new(scheduler), + } + } + + fn alloc_pid(&self) -> Pid { + for i in 0..MAX_PROC_NUM { + if self.procs[i].lock().is_none() { + return i; + } + } + panic!("Process number exceeded"); + } + + /// Add a new process + pub fn add(&self, context: Box) -> Pid { + let pid = self.alloc_pid(); + // TODO: check parent + *self.procs[pid].lock() = Some(Process { + id: pid, + status: Status::Ready, + status_after_stop: Status::Ready, + context: Some(context), + }); + self.scheduler.lock().insert(pid); + pid + } + + /// Make process `pid` time slice -= 1. + /// Return true if time slice == 0. + /// Called by timer interrupt handler. + pub fn tick(&self, pid: Pid) -> bool { + self.scheduler.lock().tick(pid) + } + + /// Called by Processor to get a process to run. + /// The manager first mark it `Running`, + /// then take out and return its Context. + pub fn run(&self, cpu_id: usize) -> (Pid, Box) { + let mut scheduler = self.scheduler.lock(); + let pid = scheduler.select() + .expect("failed to select a runnable process"); + scheduler.remove(pid); + let mut proc_lock = self.procs[pid].lock(); + let mut proc = proc_lock.as_mut().unwrap(); + proc.status = Status::Running(cpu_id); + (pid, proc.context.take().unwrap()) + } + + /// Called by Processor to finish running a process + /// and give its context back. + pub fn stop(&self, pid: Pid, context: Box) { + let mut proc_lock = self.procs[pid].lock(); + let mut proc = proc_lock.as_mut().unwrap(); + proc.status = proc.status_after_stop.clone(); + proc.status_after_stop = Status::Ready; + proc.context = Some(context); + if proc.status == Status::Ready { + self.scheduler.lock().insert(pid); + } + } + + /// Switch the status of a process. + /// Insert/Remove it to/from scheduler if necessary. + pub fn set_status(&self, pid: Pid, status: Status) { + let mut scheduler = self.scheduler.lock(); + let mut proc_lock = self.procs[pid].lock(); + let mut proc = proc_lock.as_mut().unwrap(); + match (&proc.status, &status) { + (Status::Ready, Status::Ready) => return, + (Status::Ready, _) => scheduler.remove(pid), + (Status::Running(_), _) => {}, + (_, Status::Ready) => scheduler.insert(pid), + _ => {} + } + trace!("process {} {:?} -> {:?}", pid, proc.status, status); + match proc.status { + Status::Running(_) => proc.status_after_stop = status, + _ => proc.status = status, + } + } +} diff --git a/crate/process/src/processor.rs b/crate/process/src/processor.rs index 8de30751..4fc3e18d 100644 --- a/crate/process/src/processor.rs +++ b/crate/process/src/processor.rs @@ -1,9 +1,8 @@ use alloc::boxed::Box; -use alloc::vec::Vec; use alloc::sync::Arc; use spin::Mutex; -use scheduler::Scheduler; use core::cell::UnsafeCell; +use process_manager::*; /// Process executor /// @@ -90,119 +89,3 @@ impl Processor { } } } - -struct Process { - id: Pid, - status: Status, - status_after_stop: Status, - context: Option>, -} - -type Pid = usize; -type ExitCode = usize; -const MAX_PROC_NUM: usize = 32; - -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum Status { - Ready, - Running(usize), - Waiting(Pid), - Sleeping, - Exited(ExitCode), -} - -pub trait Context { - unsafe fn switch_to(&mut self, target: &mut Context); -} - -pub struct ProcessManager { - procs: [Mutex>; MAX_PROC_NUM], - scheduler: Mutex>, -} - -impl ProcessManager { - - pub fn new(scheduler: Box) -> Self { - ProcessManager { - procs: Default::default(), - scheduler: Mutex::new(scheduler), - } - } - - fn alloc_pid(&self) -> Pid { - for i in 0..MAX_PROC_NUM { - if self.procs[i].lock().is_none() { - return i; - } - } - panic!("Process number exceeded"); - } - - /// Add a new process - pub fn add(&self, context: Box) -> Pid { - let pid = self.alloc_pid(); - // TODO: check parent - *self.procs[pid].lock() = Some(Process { - id: pid, - status: Status::Ready, - status_after_stop: Status::Ready, - context: Some(context), - }); - self.scheduler.lock().insert(pid); - pid - } - - /// Make process `pid` time slice -= 1. - /// Return true if time slice == 0. - /// Called by timer interrupt handler. - pub fn tick(&self, pid: Pid) -> bool { - self.scheduler.lock().tick(pid) - } - - /// Called by Processor to get a process to run. - /// The manager first mark it `Running`, - /// then take out and return its Context. - pub fn run(&self, cpu_id: usize) -> (Pid, Box) { - let mut scheduler = self.scheduler.lock(); - let pid = scheduler.select() - .expect("failed to select a runnable process"); - scheduler.remove(pid); - let mut proc_lock = self.procs[pid].lock(); - let mut proc = proc_lock.as_mut().unwrap(); - proc.status = Status::Running(cpu_id); - (pid, proc.context.take().unwrap()) - } - - /// Called by Processor to finish running a process - /// and give its context back. - pub fn stop(&self, pid: Pid, context: Box) { - let mut proc_lock = self.procs[pid].lock(); - let mut proc = proc_lock.as_mut().unwrap(); - proc.status = proc.status_after_stop.clone(); - proc.status_after_stop = Status::Ready; - proc.context = Some(context); - if proc.status == Status::Ready { - self.scheduler.lock().insert(pid); - } - } - - /// Switch the status of a process. - /// Insert/Remove it to/from scheduler if necessary. - pub fn set_status(&self, pid: Pid, status: Status) { - let mut scheduler = self.scheduler.lock(); - let mut proc_lock = self.procs[pid].lock(); - let mut proc = proc_lock.as_mut().unwrap(); - match (&proc.status, &status) { - (Status::Ready, Status::Ready) => return, - (Status::Ready, _) => scheduler.remove(pid), - (Status::Running(_), _) => {}, - (_, Status::Ready) => scheduler.insert(pid), - _ => {} - } - trace!("process {} {:?} -> {:?}", pid, proc.status, status); - match proc.status { - Status::Running(_) => proc.status_after_stop = status, - _ => proc.status = status, - } - } -} \ No newline at end of file diff --git a/crate/process/src/thread.rs b/crate/process/src/thread.rs index b72ebfe8..1833af36 100644 --- a/crate/process/src/thread.rs +++ b/crate/process/src/thread.rs @@ -28,12 +28,11 @@ use alloc::boxed::Box; use alloc::collections::BTreeMap; -use core::any::Any; use core::marker::PhantomData; use core::ptr; use core::time::Duration; -use core::ops::DerefMut; use processor::*; +use process_manager::*; use scheduler::Scheduler; /// All dependencies for thread mod. diff --git a/kernel/src/process/context.rs b/kernel/src/process/context.rs index eb311e4a..e078219c 100644 --- a/kernel/src/process/context.rs +++ b/kernel/src/process/context.rs @@ -2,7 +2,7 @@ use arch::interrupt::{TrapFrame, Context as ArchContext}; use memory::{MemoryArea, MemoryAttr, MemorySet}; use xmas_elf::{ElfFile, header, program::{Flags, ProgramHeader, Type}}; use core::fmt::{Debug, Error, Formatter}; -use ucore_process::processor::Context; +use ucore_process::Context; use alloc::boxed::Box; pub struct ContextImpl { diff --git a/kernel/src/process/mod.rs b/kernel/src/process/mod.rs index 5bc17a34..0e13b338 100644 --- a/kernel/src/process/mod.rs +++ b/kernel/src/process/mod.rs @@ -1,8 +1,7 @@ use spin::Once; use sync::{SpinNoIrqLock, Mutex, MutexGuard, SpinNoIrq}; pub use self::context::ContextImpl; -pub use ucore_process::processor::*; -pub use ucore_process::scheduler::*; +pub use ucore_process::*; pub use ucore_process::thread::*; use alloc::boxed::Box; use consts::MAX_CPU_NUM; @@ -14,7 +13,7 @@ mod context; pub fn init() { // NOTE: max_time_slice <= 5 to ensure 'priority' test pass - let scheduler = Box::new(RRScheduler::new(5)); + let scheduler = Box::new(scheduler::RRScheduler::new(5)); let manager = Arc::new(ProcessManager::new(scheduler)); extern fn idle(_arg: usize) -> ! { From 80b161db98171d62ac341f2c3a723607a48832ad Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 24 Oct 2018 21:29:31 +0800 Subject: [PATCH 19/58] Recover set_priority and fork --- crate/process/src/process_manager.rs | 5 +++++ crate/process/src/processor.rs | 4 ++++ kernel/src/process/context.rs | 7 +------ kernel/src/process/mod.rs | 2 +- kernel/src/syscall.rs | 11 ++++++++--- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/crate/process/src/process_manager.rs b/crate/process/src/process_manager.rs index dd8dab54..37a96528 100644 --- a/crate/process/src/process_manager.rs +++ b/crate/process/src/process_manager.rs @@ -71,6 +71,11 @@ impl ProcessManager { self.scheduler.lock().tick(pid) } + /// Set the priority of process `pid` + pub fn set_priority(&self, pid: Pid, priority: u8) { + self.scheduler.lock().set_priority(pid, priority); + } + /// Called by Processor to get a process to run. /// The manager first mark it `Running`, /// then take out and return its Context. diff --git a/crate/process/src/processor.rs b/crate/process/src/processor.rs index 4fc3e18d..c1432caa 100644 --- a/crate/process/src/processor.rs +++ b/crate/process/src/processor.rs @@ -78,6 +78,10 @@ impl Processor { self.inner().proc.as_ref().unwrap().0 } + pub fn context(&self) -> &Context { + &*self.inner().proc.as_ref().unwrap().1 + } + pub fn manager(&self) -> &ProcessManager { &*self.inner().manager } diff --git a/kernel/src/process/context.rs b/kernel/src/process/context.rs index e078219c..dcc53f55 100644 --- a/kernel/src/process/context.rs +++ b/kernel/src/process/context.rs @@ -12,13 +12,8 @@ pub struct ContextImpl { impl Context for ContextImpl { unsafe fn switch_to(&mut self, target: &mut Context) { - use core::raw::TraitObject; use core::mem::transmute; - - // Cast &mut Context -> &mut ContextImpl - let raw: TraitObject = transmute(target); - let target = &mut *(raw.data as *mut ContextImpl); - + let (target, _): (&mut ContextImpl, *const ()) = transmute(target); self.arch.switch(&mut target.arch); } } diff --git a/kernel/src/process/mod.rs b/kernel/src/process/mod.rs index 0e13b338..3f143dab 100644 --- a/kernel/src/process/mod.rs +++ b/kernel/src/process/mod.rs @@ -19,7 +19,7 @@ pub fn init() { extern fn idle(_arg: usize) -> ! { loop { cpu::halt(); } } - for i in 0..MAX_CPU_NUM { + for i in 0..4 { manager.add(ContextImpl::new_kernel(idle, i)); } diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 5c48ecf4..6dbd056b 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -58,7 +58,11 @@ fn sys_close(fd: usize) -> i32 { /// Fork the current process. Return the child's PID. fn sys_fork(tf: &TrapFrame) -> i32 { - unimplemented!(); + use core::mem::transmute; + let (context, _): (&ContextImpl, *const ()) = unsafe { transmute(processor().context()) }; + let pid = processor().manager().add(context.fork(tf)); + info!("fork: {} -> {}", thread::current().id(), pid); + pid as i32 } /// Wait the process exit. @@ -85,7 +89,7 @@ fn sys_getpid() -> i32 { /// Exit the current process fn sys_exit(exit_code: usize) -> i32 { - let pid = processor().pid(); + let pid = thread::current().id(); processor().manager().set_status(pid, Status::Exited(exit_code)); processor().yield_now(); 0 @@ -102,7 +106,8 @@ fn sys_get_time() -> i32 { } fn sys_lab6_set_priority(priority: usize) -> i32 { - unimplemented!(); + let pid = thread::current().id(); + processor().manager().set_priority(pid, priority as u8); 0 } From 85a1dca6846cbc17e6a0f8ef7d400dbd4d43feb2 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 24 Oct 2018 21:32:23 +0800 Subject: [PATCH 20/58] Use weak linkage to provide dependencies for process::thread. --- crate/process/src/lib.rs | 1 + crate/process/src/thread.rs | 219 ++++++++++++++++-------------------- kernel/src/lib.rs | 4 +- kernel/src/process/mod.rs | 24 ++-- kernel/src/sync/condvar.rs | 3 +- 5 files changed, 110 insertions(+), 141 deletions(-) diff --git a/crate/process/src/lib.rs b/crate/process/src/lib.rs index 11415181..dde6feee 100644 --- a/crate/process/src/lib.rs +++ b/crate/process/src/lib.rs @@ -1,6 +1,7 @@ #![no_std] #![feature(alloc)] #![feature(const_fn)] +#![feature(linkage)] extern crate alloc; #[macro_use] diff --git a/crate/process/src/thread.rs b/crate/process/src/thread.rs index 1833af36..c9e8a6fb 100644 --- a/crate/process/src/thread.rs +++ b/crate/process/src/thread.rs @@ -1,30 +1,10 @@ //! Thread std-like interface //! -//! Based on Processor. -//! Used in the kernel. +//! Based on Processor. Used in kernel. //! -//! # Example -//! -//! ``` -//! // Define a support implementation struct -//! pub struct ThreadSupportImpl; -//! -//! // Impl `ThreadSupport` trait -//! impl ThreadSupport for ThreadSupportImpl { ... } -//! -//! // Export the full struct as `thread`. -//! #[allow(non_camel_case_types)] -//! pub type thread = ThreadMod; -//! ``` -//! -//! ``` -//! // Use it just like `std::thread` -//! use thread; -//! let t = thread::current(); -//! -//! // But the other struct is not available ... -//! let t: thread::Thread; // ERROR! -//! ``` +//! You need to implement the following functions before use: +//! - `processor`: Get a reference of the current `Processor` +//! - `new_kernel_context`: Construct a `Context` of the new kernel thread use alloc::boxed::Box; use alloc::collections::BTreeMap; @@ -35,122 +15,121 @@ use processor::*; use process_manager::*; use scheduler::Scheduler; -/// All dependencies for thread mod. -pub trait ThreadSupport { - fn processor() -> &'static Processor; - fn new_kernel(entry: extern fn(usize) -> !, arg: usize) -> Box; +#[linkage = "weak"] +#[no_mangle] +/// Get a reference of the current `Processor` +fn processor() -> &'static Processor { + unimplemented!("thread: Please implement and export `processor`") } -/// Root structure served as thread mod -pub struct ThreadMod { - mark: PhantomData +#[linkage = "weak"] +#[no_mangle] +/// Construct a `Context` of the new kernel thread +fn new_kernel_context(entry: extern fn(usize) -> !, arg: usize) -> Box { + unimplemented!("thread: Please implement and export `new_kernel_context`") } -impl ThreadMod { - /// Gets a handle to the thread that invokes it. - pub fn current() -> Thread { - Thread { - pid: S::processor().pid(), - mark: PhantomData, - } + +/// Gets a handle to the thread that invokes it. +pub fn current() -> Thread { + Thread { + pid: processor().pid(), } +} - /// Puts the current thread to sleep for the specified amount of time. - pub fn sleep(dur: Duration) { - let time = dur_to_ticks(dur); - info!("sleep: {:?} ticks", time); - // TODO: register wakeup - Self::park(); +/// Puts the current thread to sleep for the specified amount of time. +pub fn sleep(dur: Duration) { + let time = dur_to_ticks(dur); + info!("sleep: {:?} ticks", time); + // TODO: register wakeup + park(); - fn dur_to_ticks(dur: Duration) -> usize { - return dur.as_secs() as usize * 100 + dur.subsec_nanos() as usize / 10_000_000; - } + fn dur_to_ticks(dur: Duration) -> usize { + return dur.as_secs() as usize * 100 + dur.subsec_nanos() as usize / 10_000_000; } +} - /// Spawns a new thread, returning a JoinHandle for it. - /// - /// `F`: Type of the function `f` - /// `T`: Type of the return value of `f` - pub fn spawn(f: F) -> JoinHandle +/// Spawns a new thread, returning a JoinHandle for it. +/// +/// `F`: Type of the function `f` +/// `T`: Type of the return value of `f` +pub fn spawn(f: F) -> JoinHandle + where + F: Send + 'static + FnOnce() -> T, + T: Send + 'static, +{ + info!("spawn:"); + + // 注意到下面的问题: + // Processor只能从入口地址entry+参数arg创建新线程 + // 而我们现在需要让它执行一个未知类型的(闭包)函数f + + // 首先把函数本体(代码数据)置于堆空间中 + let f = Box::into_raw(Box::new(f)); + + // 定义一个静态函数作为新线程的入口点 + // 其参数是函数f在堆上的指针 + // 这样我们就把函数f传到了一个静态函数内部 + // + // 注意到它具有泛型参数,因此对每一次spawn调用, + // 由于F类型是独特的,因此都会生成一个新的kernel_thread_entry + extern fn kernel_thread_entry(f: usize) -> ! where F: Send + 'static + FnOnce() -> T, T: Send + 'static, { - info!("spawn:"); - - // 注意到下面的问题: - // Processor只能从入口地址entry+参数arg创建新线程 - // 而我们现在需要让它执行一个未知类型的(闭包)函数f - - // 首先把函数本体(代码数据)置于堆空间中 - let f = Box::into_raw(Box::new(f)); - - // 定义一个静态函数作为新线程的入口点 - // 其参数是函数f在堆上的指针 - // 这样我们就把函数f传到了一个静态函数内部 - // - // 注意到它具有泛型参数,因此对每一次spawn调用, - // 由于F类型是独特的,因此都会生成一个新的kernel_thread_entry - extern fn kernel_thread_entry(f: usize) -> ! - where - S: ThreadSupport, - F: Send + 'static + FnOnce() -> T, - T: Send + 'static, - { - // 在静态函数内部: - // 根据传进来的指针,恢复f - let f = unsafe { Box::from_raw(f as *mut F) }; - // 调用f,并将其返回值也放在堆上 - let ret = Box::new(f()); - // 清理本地线程存储 - // unsafe { LocalKey::::get_map() }.clear(); - // 让Processor退出当前线程 - // 把f返回值在堆上的指针,以线程返回码的形式传递出去 - let pid = S::processor().pid(); - let exit_code = Box::into_raw(ret) as usize; - S::processor().manager().set_status(pid, Status::Exited(exit_code)); - S::processor().yield_now(); - // 再也不会被调度回来了 - unreachable!() - } - - // 在Processor中创建新的线程 - let context = S::new_kernel(kernel_thread_entry::, f as usize); - let pid = S::processor().manager().add(context); - - // 接下来看看`JoinHandle::join()`的实现 - // 了解是如何获取f返回值的 - return JoinHandle { - thread: Thread { pid, mark: PhantomData }, - mark: PhantomData, - }; + // 在静态函数内部: + // 根据传进来的指针,恢复f + let f = unsafe { Box::from_raw(f as *mut F) }; + // 调用f,并将其返回值也放在堆上 + let ret = Box::new(f()); + // 清理本地线程存储 + // unsafe { LocalKey::::get_map() }.clear(); + // 让Processor退出当前线程 + // 把f返回值在堆上的指针,以线程返回码的形式传递出去 + let pid = processor().pid(); + let exit_code = Box::into_raw(ret) as usize; + processor().manager().set_status(pid, Status::Exited(exit_code)); + processor().yield_now(); + // 再也不会被调度回来了 + unreachable!() } - /// Cooperatively gives up a timeslice to the OS scheduler. - pub fn yield_now() { - info!("yield:"); - S::processor().yield_now(); - } + // 在Processor中创建新的线程 + let context = new_kernel_context(kernel_thread_entry::, f as usize); + let pid = processor().manager().add(context); - /// Blocks unless or until the current thread's token is made available. - pub fn park() { - info!("park:"); - let pid = S::processor().pid(); - S::processor().manager().set_status(pid, Status::Sleeping); - S::processor().yield_now(); - } + // 接下来看看`JoinHandle::join()`的实现 + // 了解是如何获取f返回值的 + return JoinHandle { + thread: Thread { pid }, + mark: PhantomData, + }; +} + +/// Cooperatively gives up a timeslice to the OS scheduler. +pub fn yield_now() { + info!("yield:"); + processor().yield_now(); +} + +/// Blocks unless or until the current thread's token is made available. +pub fn park() { + info!("park:"); + let pid = processor().pid(); + processor().manager().set_status(pid, Status::Sleeping); + processor().yield_now(); } /// A handle to a thread. -pub struct Thread { +pub struct Thread { pid: usize, - mark: PhantomData, } -impl Thread { +impl Thread { /// Atomically makes the handle's token available if it is not already. pub fn unpark(&self) { - S::processor().manager().set_status(self.pid, Status::Ready); + processor().manager().set_status(self.pid, Status::Ready); } /// Gets the thread's unique identifier. pub fn id(&self) -> usize { @@ -159,14 +138,14 @@ impl Thread { } /// An owned permission to join on a thread (block on its termination). -pub struct JoinHandle { - thread: Thread, +pub struct JoinHandle { + thread: Thread, mark: PhantomData, } -impl JoinHandle { +impl JoinHandle { /// Extracts a handle to the underlying thread. - pub fn thread(&self) -> &Thread { + pub fn thread(&self) -> &Thread { &self.thread } /// Waits for the associated thread to finish. diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 73c6dbbc..b8614ecb 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -35,6 +35,8 @@ extern crate volatile; extern crate x86_64; extern crate xmas_elf; +pub use process::{processor, new_kernel_context}; +use ucore_process::thread; use linked_list_allocator::LockedHeap; #[macro_use] // print! @@ -46,8 +48,6 @@ mod consts; mod process; mod syscall; mod fs; - -use process::{thread, thread_}; mod sync; mod trap; mod console; diff --git a/kernel/src/process/mod.rs b/kernel/src/process/mod.rs index 3f143dab..3ec4a43d 100644 --- a/kernel/src/process/mod.rs +++ b/kernel/src/process/mod.rs @@ -2,7 +2,6 @@ use spin::Once; use sync::{SpinNoIrqLock, Mutex, MutexGuard, SpinNoIrq}; pub use self::context::ContextImpl; pub use ucore_process::*; -pub use ucore_process::thread::*; use alloc::boxed::Box; use consts::MAX_CPU_NUM; use arch::cpu; @@ -33,24 +32,15 @@ pub fn init() { static PROCESSORS: [Processor; MAX_CPU_NUM] = [Processor::new(), Processor::new(), Processor::new(), Processor::new(), Processor::new(), Processor::new(), Processor::new(), Processor::new()]; + +// Implement dependencies for std::thread + +#[no_mangle] pub fn processor() -> &'static Processor { &PROCESSORS[cpu::id()] } -#[allow(non_camel_case_types)] -pub type thread = ThreadMod; - -pub mod thread_ { - pub type Thread = super::Thread; -} - -pub struct ThreadSupportImpl; - -impl ThreadSupport for ThreadSupportImpl { - fn processor() -> &'static Processor { - processor() - } - fn new_kernel(entry: extern fn(usize) -> !, arg: usize) -> Box { - ContextImpl::new_kernel(entry, arg) - } +#[no_mangle] +pub fn new_kernel_context(entry: extern fn(usize) -> !, arg: usize) -> Box { + ContextImpl::new_kernel(entry, arg) } \ No newline at end of file diff --git a/kernel/src/sync/condvar.rs b/kernel/src/sync/condvar.rs index 4dbbc78f..5e14d2e6 100644 --- a/kernel/src/sync/condvar.rs +++ b/kernel/src/sync/condvar.rs @@ -1,11 +1,10 @@ use alloc::collections::VecDeque; use super::*; use thread; -use thread_; #[derive(Default)] pub struct Condvar { - wait_queue: SpinNoIrqLock>, + wait_queue: SpinNoIrqLock>, } impl Condvar { From c734f7969996f191f11dd170d7eda974cf0e0a84 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Thu, 25 Oct 2018 01:04:31 +0800 Subject: [PATCH 21/58] Drop context when process exit. Remove util mod. --- crate/process/src/lib.rs | 2 +- crate/process/src/process_manager.rs | 12 +++++++++--- crate/process/src/util.rs | 15 --------------- 3 files changed, 10 insertions(+), 19 deletions(-) delete mode 100644 crate/process/src/util.rs diff --git a/crate/process/src/lib.rs b/crate/process/src/lib.rs index dde6feee..823c334f 100644 --- a/crate/process/src/lib.rs +++ b/crate/process/src/lib.rs @@ -2,6 +2,7 @@ #![feature(alloc)] #![feature(const_fn)] #![feature(linkage)] +#![feature(nll)] extern crate alloc; #[macro_use] @@ -17,7 +18,6 @@ mod process_manager; mod processor; pub mod scheduler; pub mod thread; -mod util; mod event_hub; pub use process_manager::*; diff --git a/crate/process/src/process_manager.rs b/crate/process/src/process_manager.rs index 37a96528..b7e5d478 100644 --- a/crate/process/src/process_manager.rs +++ b/crate/process/src/process_manager.rs @@ -19,8 +19,8 @@ const MAX_PROC_NUM: usize = 32; pub enum Status { Ready, Running(usize), - Waiting(Pid), Sleeping, + /// aka ZOMBIE. Its context was dropped. Exited(ExitCode), } @@ -98,8 +98,10 @@ impl ProcessManager { proc.status = proc.status_after_stop.clone(); proc.status_after_stop = Status::Ready; proc.context = Some(context); - if proc.status == Status::Ready { - self.scheduler.lock().insert(pid); + match proc.status { + Status::Ready => self.scheduler.lock().insert(pid), + Status::Exited(_) => proc.context = None, + _ => {} } } @@ -121,5 +123,9 @@ impl ProcessManager { Status::Running(_) => proc.status_after_stop = status, _ => proc.status = status, } + match proc.status { + Status::Exited(_) => proc.context = None, + _ => {} + } } } diff --git a/crate/process/src/util.rs b/crate/process/src/util.rs deleted file mode 100644 index 11a8d3c0..00000000 --- a/crate/process/src/util.rs +++ /dev/null @@ -1,15 +0,0 @@ -use core::fmt::Debug; - -/// Get values by 2 diff keys at the same time -pub trait GetMut2 { - type Output; - fn get_mut(&mut self, id: Idx) -> &mut Self::Output; - fn get_mut2(&mut self, id1: Idx, id2: Idx) -> (&mut Self::Output, &mut Self::Output) { - assert_ne!(id1, id2); - let self1 = self as *mut Self; - let self2 = self1; - let p1 = unsafe { &mut *self1 }.get_mut(id1); - let p2 = unsafe { &mut *self2 }.get_mut(id2); - (p1, p2) - } -} From 0680023e359285d09d3a73669076441207ee5ecd Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 26 Oct 2018 00:49:19 +0800 Subject: [PATCH 22/58] Recover wait/sleep --- crate/process/src/process_manager.rs | 64 ++++++++++++++++++++++++++-- crate/process/src/thread.rs | 25 +++++++---- kernel/src/fs.rs | 5 ++- kernel/src/syscall.rs | 25 ++++++++--- kernel/src/trap.rs | 7 ++- 5 files changed, 107 insertions(+), 19 deletions(-) diff --git a/crate/process/src/process_manager.rs b/crate/process/src/process_manager.rs index b7e5d478..f4ed2746 100644 --- a/crate/process/src/process_manager.rs +++ b/crate/process/src/process_manager.rs @@ -3,6 +3,8 @@ use alloc::sync::Arc; use spin::Mutex; use scheduler::Scheduler; use core::cell::UnsafeCell; +use alloc::vec::Vec; +use event_hub::EventHub; struct Process { id: Pid, @@ -20,10 +22,15 @@ pub enum Status { Ready, Running(usize), Sleeping, + Waiting(Pid), /// aka ZOMBIE. Its context was dropped. Exited(ExitCode), } +enum Event { + Wakeup(Pid), +} + pub trait Context { unsafe fn switch_to(&mut self, target: &mut Context); } @@ -31,6 +38,8 @@ pub trait Context { pub struct ProcessManager { procs: [Mutex>; MAX_PROC_NUM], scheduler: Mutex>, + wait_queue: [Mutex>; MAX_PROC_NUM], + event_hub: Mutex>, } impl ProcessManager { @@ -38,6 +47,8 @@ impl ProcessManager { ProcessManager { procs: Default::default(), scheduler: Mutex::new(scheduler), + wait_queue: Default::default(), + event_hub: Mutex::new(EventHub::new()), } } @@ -53,7 +64,6 @@ impl ProcessManager { /// Add a new process pub fn add(&self, context: Box) -> Pid { let pid = self.alloc_pid(); - // TODO: check parent *self.procs[pid].lock() = Some(Process { id: pid, status: Status::Ready, @@ -68,6 +78,13 @@ impl ProcessManager { /// Return true if time slice == 0. /// Called by timer interrupt handler. pub fn tick(&self, pid: Pid) -> bool { + let mut event_hub = self.event_hub.lock(); + event_hub.tick(); + while let Some(event) = event_hub.pop() { + match event { + Event::Wakeup(pid) => self.set_status(pid, Status::Ready), + } + } self.scheduler.lock().tick(pid) } @@ -107,18 +124,22 @@ impl ProcessManager { /// Switch the status of a process. /// Insert/Remove it to/from scheduler if necessary. - pub fn set_status(&self, pid: Pid, status: Status) { + fn set_status(&self, pid: Pid, status: Status) { let mut scheduler = self.scheduler.lock(); let mut proc_lock = self.procs[pid].lock(); let mut proc = proc_lock.as_mut().unwrap(); + trace!("process {} {:?} -> {:?}", pid, proc.status, status); match (&proc.status, &status) { (Status::Ready, Status::Ready) => return, (Status::Ready, _) => scheduler.remove(pid), (Status::Running(_), _) => {}, + (Status::Exited(_), _) => panic!("can not set status for a exited process"), + (Status::Waiting(target), Status::Exited(_)) => + self.wait_queue[*target].lock().retain(|&i| i != pid), + // TODO: Sleep -> Exited Remove wakeup event. (_, Status::Ready) => scheduler.insert(pid), _ => {} } - trace!("process {} {:?} -> {:?}", pid, proc.status, status); match proc.status { Status::Running(_) => proc.status_after_stop = status, _ => proc.status = status, @@ -128,4 +149,41 @@ impl ProcessManager { _ => {} } } + + + pub fn get_status(&self, pid: Pid) -> Option { + self.procs[pid].lock().as_ref().map(|p| p.status.clone()) + } + + pub fn remove(&self, pid: Pid) { + let mut proc_lock = self.procs[pid].lock(); + let proc = proc_lock.as_ref().unwrap(); + match proc.status { + Status::Exited(_) => *proc_lock = None, + _ => panic!("can not remove non-exited process"), + } + } + + pub fn sleep(&self, pid: Pid, time: usize) { + self.set_status(pid, Status::Sleeping); + if time != 0 { + self.event_hub.lock().push(time, Event::Wakeup(pid)); + } + } + + pub fn wakeup(&self, pid: Pid) { + self.set_status(pid, Status::Ready); + } + + pub fn wait(&self, pid: Pid, target: Pid) { + self.set_status(pid, Status::Waiting(target)); + self.wait_queue[target].lock().push(pid); + } + + pub fn exit(&self, pid: Pid, code: ExitCode) { + self.set_status(pid, Status::Exited(code)); + for waiter in self.wait_queue[pid].lock().drain(..) { + self.wakeup(waiter); + } + } } diff --git a/crate/process/src/thread.rs b/crate/process/src/thread.rs index c9e8a6fb..1c8b1ade 100644 --- a/crate/process/src/thread.rs +++ b/crate/process/src/thread.rs @@ -41,7 +41,7 @@ pub fn current() -> Thread { pub fn sleep(dur: Duration) { let time = dur_to_ticks(dur); info!("sleep: {:?} ticks", time); - // TODO: register wakeup + processor().manager().sleep(current().id(), time); park(); fn dur_to_ticks(dur: Duration) -> usize { @@ -87,9 +87,8 @@ pub fn spawn(f: F) -> JoinHandle // unsafe { LocalKey::::get_map() }.clear(); // 让Processor退出当前线程 // 把f返回值在堆上的指针,以线程返回码的形式传递出去 - let pid = processor().pid(); let exit_code = Box::into_raw(ret) as usize; - processor().manager().set_status(pid, Status::Exited(exit_code)); + processor().manager().exit(current().id(), exit_code); processor().yield_now(); // 再也不会被调度回来了 unreachable!() @@ -116,8 +115,7 @@ pub fn yield_now() { /// Blocks unless or until the current thread's token is made available. pub fn park() { info!("park:"); - let pid = processor().pid(); - processor().manager().set_status(pid, Status::Sleeping); + processor().manager().sleep(current().id(), 0); processor().yield_now(); } @@ -129,7 +127,7 @@ pub struct Thread { impl Thread { /// Atomically makes the handle's token available if it is not already. pub fn unpark(&self) { - processor().manager().set_status(self.pid, Status::Ready); + processor().manager().wakeup(self.pid); } /// Gets the thread's unique identifier. pub fn id(&self) -> usize { @@ -150,8 +148,19 @@ impl JoinHandle { } /// Waits for the associated thread to finish. pub fn join(self) -> Result { - // TODO: wait for the thread - unimplemented!() + loop { + match processor().manager().get_status(self.thread.pid) { + Some(Status::Exited(exit_code)) => { + processor().manager().remove(self.thread.pid); + // Find return value on the heap from the exit code. + return Ok(unsafe { *Box::from_raw(exit_code as *mut T) }); + } + None => return Err(()), + _ => {} + } + processor().manager().wait(current().id(), self.thread.pid); + processor().yield_now(); + } } } diff --git a/kernel/src/fs.rs b/kernel/src/fs.rs index 41954c03..27a2b748 100644 --- a/kernel/src/fs.rs +++ b/kernel/src/fs.rs @@ -46,8 +46,9 @@ pub fn shell() { if let Ok(file) = root.borrow().lookup(name.as_str()) { use process::*; let len = file.borrow().read_at(0, &mut *buf).unwrap(); - processor().manager().add(ContextImpl::new_user(&buf[..len])); - // TODO: wait for new process + let pid = processor().manager().add(ContextImpl::new_user(&buf[..len])); + processor().manager().wait(thread::current().id(), pid); + processor().yield_now(); } else { println!("Program not exist"); } diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 6dbd056b..f1216887 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -68,7 +68,22 @@ fn sys_fork(tf: &TrapFrame) -> i32 { /// Wait the process exit. /// Return the PID. Store exit code to `code` if it's not null. fn sys_wait(pid: usize, code: *mut i32) -> i32 { - unimplemented!(); + assert_ne!(pid, 0, "wait for 0 is not supported yet"); + loop { + match processor().manager().get_status(pid) { + Some(Status::Exited(exit_code)) => { + if !code.is_null() { + unsafe { code.write(exit_code as i32); } + } + processor().manager().remove(pid); + return 0; + } + None => return -1, + _ => {} + } + processor().manager().wait(thread::current().id(), pid); + processor().yield_now(); + } } fn sys_yield() -> i32 { @@ -78,7 +93,7 @@ fn sys_yield() -> i32 { /// Kill the process fn sys_kill(pid: usize) -> i32 { - processor().manager().set_status(pid, Status::Exited(0x100)); + processor().manager().exit(pid, 0x100); 0 } @@ -90,9 +105,9 @@ fn sys_getpid() -> i32 { /// Exit the current process fn sys_exit(exit_code: usize) -> i32 { let pid = thread::current().id(); - processor().manager().set_status(pid, Status::Exited(exit_code)); + processor().manager().exit(pid, exit_code); processor().yield_now(); - 0 + unreachable!(); } fn sys_sleep(time: usize) -> i32 { @@ -102,7 +117,7 @@ fn sys_sleep(time: usize) -> i32 { } fn sys_get_time() -> i32 { - unimplemented!(); + unsafe { ::trap::TICK as i32 } } fn sys_lab6_set_priority(priority: usize) -> i32 { diff --git a/kernel/src/trap.rs b/kernel/src/trap.rs index 6cd4556a..7ed9e218 100644 --- a/kernel/src/trap.rs +++ b/kernel/src/trap.rs @@ -2,8 +2,13 @@ use process::*; use arch::interrupt::TrapFrame; use arch::cpu; +pub static mut TICK: usize = 0; + pub fn timer() { processor().tick(); + if cpu::id() == 0 { + unsafe { TICK += 1; } + } } pub fn before_return() { @@ -13,7 +18,7 @@ pub fn error(tf: &TrapFrame) -> ! { let pid = processor().pid(); error!("On CPU{} Process {}:\n{:#x?}", cpu::id(), pid, tf); - processor().manager().set_status(pid, Status::Exited(0x100)); + processor().manager().exit(pid, 0x100); processor().yield_now(); unreachable!(); } \ No newline at end of file From 95ab9caba1f2b47c708ba69e4fd54df4003c3e4a Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 26 Oct 2018 02:31:05 +0800 Subject: [PATCH 23/58] Add impl of atomic_fetch_* --- kernel/src/arch/riscv32/compiler_rt.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/kernel/src/arch/riscv32/compiler_rt.c b/kernel/src/arch/riscv32/compiler_rt.c index c2f2fb8d..1d41cf69 100644 --- a/kernel/src/arch/riscv32/compiler_rt.c +++ b/kernel/src/arch/riscv32/compiler_rt.c @@ -46,4 +46,16 @@ char __atomic_compare_exchange_4(int* dst, int* expected, int desired) { return sc_ret == 0; } return 0; +} + +int __atomic_fetch_add_4(int* ptr, int val) { + int res; + __asm__ __volatile__("amoadd.w.rl %0, %1, (%2)" : "=r"(res) : "r"(val), "r"(ptr) : "memory"); + return res; +} + +int __atomic_fetch_sub_4(int* ptr, int val) { + int res; + __asm__ __volatile__("amoadd.w.rl %0, %1, (%2)" : "=r"(res) : "r"(-val), "r"(ptr) : "memory"); + return res; } \ No newline at end of file From 925a08f9ae9c8d1ef5d59e55a1537242198247dc Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 26 Oct 2018 02:32:28 +0800 Subject: [PATCH 24/58] Add OSLab/exp3 report --- docs/2_OSLab/g5/exp3.md | 106 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 docs/2_OSLab/g5/exp3.md diff --git a/docs/2_OSLab/g5/exp3.md b/docs/2_OSLab/g5/exp3.md new file mode 100644 index 00000000..1b369023 --- /dev/null +++ b/docs/2_OSLab/g5/exp3.md @@ -0,0 +1,106 @@ +# 2018操作系统专题训练 + +# 实验3:实现和测试报告 + +计53 王润基 2015011279 + +2018.10.25 + +## 实验目标 + +**基于RustOS,参考sv6完成多核实现和优化。** + +分为以下三个子任务: + +1. 实现x86_64和RISCV下的多核启动和通信 +2. 拓展线程管理模块,使之支持多核调度 +3. 学习sv6进行多核优化 + +## 实验进展 + +基本完成前两项子任务,即:**实现了多核线程调度执行**。 + +具体的任务节点是: + +1. 到10月19日,我完成了x86下的多核启动和IPI。与此同时,王纪霆完成了riscv32下的多核启动,并针对编译器原子操作缺失问题给出了临时解决方案。我合并了他的成果,至此多核的底层支持实现完毕。 +2. 到10月25日,我完成了线程管理模块针对多处理机模型的重构,原来正常的用户程序都能在多核环境下正确执行。 + +## 实现方案 + +### x86的多核启动 + +实际上这部分工作已经在上学期OS课大作业中实现完毕。不过由于之后替换了x86下的bootloader,多核启动特性被暂时关闭。因此实际的任务是:**把原来Kernel中的多核启动代码,移植到新bootloader中**。 + +简单描述多核启动流程: + +* 将其它核的启动代码放置于物理地址0x8000处 + + 这个靠修改linker.ld,在链接阶段完成。物理地址必须4K对齐。 + +* 在主核初始化完毕后,调用一段操作LocalAPIC的代码(借用自xv6),向其它核发送启动IPI,让它从0x8000开始执行。主核需依次启动其它核,因为它们使用相同的栈空间,不能同时执行。 + +* 启动后,首先进行boot,在16位下设置段寄存器、页表、开启LongMode,直接进入64位,跳到Rust代码,再跳到Kernel入口点。 + +目前还未实现的特性是: + +* 应该首先读取ACPI中的LocalAPIC信息,确定核的个数。 + + 我原本计划使用RustOSDev的[ACPI库](https://github.com/rust-osdev/acpi),但它仍在开发中,功能尚不完全。 + +### x86的中断管理 + +在原来的单核环境中,系统使用PIC中断控制器和PIT时钟。 + +而在多核环境中,它们被IOAPIC和APIC Timer替代。 + +为此,我参考了xv6/sv6/Redox在这方面的实现,其中xv6实现最为简单,sv6进行了更好的对象包装,Redox提供了Rust下实现的参考。我发现这部分功能相对独立,而还没有人发布过Rust crate实现APIC功能,因此我自己实现了一个[APIC crate](https://github.com/wangrunji0408/APIC-Rust),综合参考了以上三个项目的代码。并将它用在了bootloader和kernel中。 + +### 合并RV工作 + +riscv32下的多核启动由王纪霆同学完成。我之后为它开启了IPI。 + +由于Rust编译器对riscv支持还不完全,不能生成原子指令,且核心库中的原子操作也没有为rv适配,具体体现为:rv下只有32位数据的原子操作,因此没有AtomicBool;此外即使用AtomicUsize实现的Mutex也不能正常工作,陈秋昊同学经过实验推测是Rust编译器内部实现的问题。 + +针对上述问题,王纪霆的解决方案是修改Kernel中Mutex的实现。而我在考虑直接修改核心库的实现,这样上层不用做修改,spin库也能正常使用。不过这一想法还没付诸实施。 + +### 多核线程管理 + +以上几个任务完成了平台相关的工作,接下来的任务都是平台无关的。 + +这部分主要工作是重构了process模块,使之支持多处理机模型。 + +在原来的设计中,用一个Processor类维护所有线程的状态,同时负责线程切换,对外提供exit/wait/sleep等控制接口。它是全局唯一的,用一个大锁包起来。 + +在新的设计中,线程状态和实际执行被分开。其中线程状态由[ProcessManager](http://os.cs.tsinghua.edu.cn/oscourse/ProcessManager)类管理,另外的Processor类负责线程的实际执行。前者是全局唯一的,后者是CPU核的抽象,每个核对应一个。由于它们都需要定义为全局变量,因此对外接口都是&self,内部用[SpinLock](http://os.cs.tsinghua.edu.cn/oscourse/SpinLock)或[UnsafeCell](http://os.cs.tsinghua.edu.cn/oscourse/UnsafeCell)维护可变状态。 + +参考xv6/ucore的设计,每个CPU核在初始化完毕后都进入自己的“调度线程”,无限重复以下操作: + +- 从[ProcessManager](http://os.cs.tsinghua.edu.cn/oscourse/ProcessManager)中取出一个可执行线程的上下文(委托内部的Scheduler决定选哪个) +- 执行上下文切换运行该线程 +- 线程运行时(主动地,或在时钟中断时被动地)调用yield切换回调度线程 +- 将运行完的线程上下文交还[ProcessManager](http://os.cs.tsinghua.edu.cn/oscourse/ProcessManager) + + + +上述内容实现后,就得到了一个最基础的线程执行框架: + +* 每个进程有3个状态:Ready等待执行,Running正在执行,Sleep不可执行 +* 若干处理机并发地从一个公共线程池里取出、执行、放回 + +注意这个实现不包括: + +* 维护线程间的父子关系(所有线程是平级的) +* 维护线程间的等待关系(等待和唤醒应该由等待队列完成) + +这部分工作的前期大量时间投入在构思设计上:如何界定类的指责和它们的交互关系?更具体的——哪里用锁?哪里可变?每个需求如何实现?受制于Rust类型系统和安全机制的约束,要设计一个编译通过的方案并不容易。 + +前期设计断断续续用了几天时间,而编写代码只用了一天,而其中相当一部分时间在与编译器作斗争。不过在编译通过后,仅修复了几个明显的Bug就能正常运行了!完全没有数据竞争问题!不得不承认Rust语言在处理并发问题上有很大优势。 + +## 未来计划 + +1. 在HiFive开发版上运行RustOS +2. 更深入研究sv6,移植其中的多核优化特性 +3. 完善系统调用,争取能跑起来sv6的用户程序,方便进行性能对比 + + + From 81196729e45f46f7d20849534e39f31b2b981e44 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 26 Oct 2018 14:56:06 +0800 Subject: [PATCH 25/58] Fix RV32 atomic. - Fix __atomic_compare_exchange_4(). - Add patch for core::sync::atomic. - Revert kernel Mutex. --- kernel/Makefile | 3 ++ kernel/src/arch/riscv32/atomic.patch | 57 +++++++++++++++++++++ kernel/src/arch/riscv32/compiler_rt.c | 39 +++----------- kernel/src/arch/x86_64/driver/vga.rs | 2 +- kernel/src/fs.rs | 2 +- kernel/src/logging.rs | 2 +- kernel/src/memory.rs | 5 +- kernel/src/sync/arch/riscv32/atomic_lock.rs | 44 ---------------- kernel/src/sync/arch/x86_64/atomic_lock.rs | 31 ----------- kernel/src/sync/mod.rs | 9 ---- kernel/src/sync/mutex.rs | 20 ++++---- 11 files changed, 83 insertions(+), 131 deletions(-) create mode 100644 kernel/src/arch/riscv32/atomic.patch delete mode 100644 kernel/src/sync/arch/riscv32/atomic_lock.rs delete mode 100644 kernel/src/sync/arch/x86_64/atomic_lock.rs diff --git a/kernel/Makefile b/kernel/Makefile index 0b49f677..e344ad18 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -137,6 +137,9 @@ kernel: ifeq ($(arch), x86_64) @bootimage build $(build_args) else + @-patch -p0 -N -b \ + $(shell rustc --print sysroot)/lib/rustlib/src/rust/src/libcore/sync/atomic.rs \ + src/arch/riscv32/atomic.patch @CC=$(cc) cargo xbuild $(build_args) endif diff --git a/kernel/src/arch/riscv32/atomic.patch b/kernel/src/arch/riscv32/atomic.patch new file mode 100644 index 00000000..ec7ca3af --- /dev/null +++ b/kernel/src/arch/riscv32/atomic.patch @@ -0,0 +1,57 @@ +--- atomic_backup.rs 2018-10-06 19:59:14.000000000 +0800 ++++ atomic.rs 2018-10-26 14:34:31.000000000 +0800 +@@ -125,6 +125,9 @@ + #[cfg(target_has_atomic = "8")] + #[stable(feature = "rust1", since = "1.0.0")] + pub struct AtomicBool { ++ #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] ++ v: UnsafeCell, ++ #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))] + v: UnsafeCell, + } + +@@ -265,6 +268,44 @@ + pub const ATOMIC_BOOL_INIT: AtomicBool = AtomicBool::new(false); + + #[cfg(target_has_atomic = "8")] ++#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] ++impl AtomicBool { ++ /// ++ #[inline] ++ #[stable(feature = "rust1", since = "1.0.0")] ++ pub const fn new(v: bool) -> AtomicBool { ++ AtomicBool { v: UnsafeCell::new(v as u32) } ++ } ++ ++ /// ++ #[inline] ++ #[stable(feature = "rust1", since = "1.0.0")] ++ pub fn load(&self, order: Ordering) -> bool { ++ unsafe { atomic_load(self.v.get(), order) != 0 } ++ } ++ ++ /// ++ #[inline] ++ #[stable(feature = "rust1", since = "1.0.0")] ++ pub fn store(&self, val: bool, order: Ordering) { ++ unsafe { atomic_store(self.v.get(), val as u32, order); } ++ } ++ ++ /// ++ #[inline] ++ #[stable(feature = "rust1", since = "1.0.0")] ++ #[cfg(target_has_atomic = "cas")] ++ pub fn compare_and_swap(&self, current: bool, new: bool, order: Ordering) -> bool { ++ loop { ++ if let Ok(val) = unsafe { atomic_compare_exchange(self.v.get(), current as u32, new as u32, order, order) } { ++ return val != 0; ++ } ++ } ++ } ++} ++ ++#[cfg(target_has_atomic = "8")] ++#[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))] + impl AtomicBool { + /// Creates a new `AtomicBool`. + /// diff --git a/kernel/src/arch/riscv32/compiler_rt.c b/kernel/src/arch/riscv32/compiler_rt.c index 1d41cf69..95c871f9 100644 --- a/kernel/src/arch/riscv32/compiler_rt.c +++ b/kernel/src/arch/riscv32/compiler_rt.c @@ -1,50 +1,27 @@ // http://llvm.org/docs/Atomics.html#libcalls-atomic -char __atomic_load_1(char *src) { - char res = 0; - __asm__ __volatile__("amoadd.w.rl %0, zero, (%1)" : "=r"(res) : "r"(src) : "memory"); - return res; -} - -short __atomic_load_2(short *src) { - short res = 0; - __asm__ __volatile__("amoadd.w.rl %0, zero, (%1)" : "=r"(res) : "r"(src) : "memory"); - return res; -} - int __atomic_load_4(int *src) { int res = 0; __asm__ __volatile__("amoadd.w.rl %0, zero, (%1)" : "=r"(res) : "r"(src) : "memory"); return res; } -char __atomic_store_1(char *dst, char val) { - __asm__ __volatile__("amoswap.w.aq zero, %0, (%1)" :: "r"(val), "r"(dst) : "memory"); -} - int __atomic_store_4(int *dst, int val) { __asm__ __volatile__("amoswap.w.aq zero, %0, (%1)" :: "r"(val), "r"(dst) : "memory"); } -char __atomic_compare_exchange_1(char* dst, char* expected, char desired) { - char val = 0; - __asm__ __volatile__("lr.w %0, (%1)" : "=r"(val) : "r"(dst) : "memory"); - if (val == *expected) { - int sc_ret = 0; - __asm__ __volatile__("sc.w %0, %1, (%2)" : "=r"(sc_ret) : "r"(desired), "r"(dst) : "memory"); - return sc_ret == 0; - } - return 0; -} - char __atomic_compare_exchange_4(int* dst, int* expected, int desired) { - int val = 0; + int val; + // val = *dst __asm__ __volatile__("lr.w %0, (%1)" : "=r"(val) : "r"(dst) : "memory"); if (val == *expected) { - int sc_ret = 0; - __asm__ __volatile__("sc.w %0, %1, (%2)" : "=r"(sc_ret) : "r"(desired), "r"(dst) : "memory"); - return sc_ret == 0; + int result; + // Try: *dst = desired. If success, result = 0, otherwise result != 0. + __asm__ __volatile__("sc.w %0, %1, (%2)" : "=r"(result) : "r"(desired), "r"(dst) : "memory"); + return result == 0; } + // *expected should always equal to the previous value of *dst + *expected = val; return 0; } diff --git a/kernel/src/arch/x86_64/driver/vga.rs b/kernel/src/arch/x86_64/driver/vga.rs index a5b2feb9..7ef8720d 100644 --- a/kernel/src/arch/x86_64/driver/vga.rs +++ b/kernel/src/arch/x86_64/driver/vga.rs @@ -1,7 +1,7 @@ use consts::KERNEL_OFFSET; use core::ptr::Unique; use core::fmt; -use sync::SpinLock as Mutex; +use spin::Mutex; use volatile::Volatile; use x86_64::instructions::port::Port; use logging::Color; diff --git a/kernel/src/fs.rs b/kernel/src/fs.rs index 27a2b748..f1db3834 100644 --- a/kernel/src/fs.rs +++ b/kernel/src/fs.rs @@ -2,7 +2,7 @@ use simple_filesystem::*; use alloc::boxed::Box; #[cfg(target_arch = "x86_64")] use arch::driver::ide; -use sync::SpinLock as Mutex; +use spin::Mutex; // Hard link user program #[cfg(target_arch = "riscv32")] diff --git a/kernel/src/logging.rs b/kernel/src/logging.rs index c1f2241a..b860de67 100644 --- a/kernel/src/logging.rs +++ b/kernel/src/logging.rs @@ -1,6 +1,6 @@ use core::fmt; use log::{self, Level, LevelFilter, Log, Metadata, Record}; -use sync::SpinLock as Mutex; +use spin::Mutex; lazy_static! { static ref log_mutex: Mutex<()> = Mutex::new(()); diff --git a/kernel/src/memory.rs b/kernel/src/memory.rs index dec17fb8..0f203f37 100644 --- a/kernel/src/memory.rs +++ b/kernel/src/memory.rs @@ -1,8 +1,7 @@ pub use arch::paging::*; use bit_allocator::{BitAlloc, BitAlloc4K, BitAlloc64K}; use consts::MEMORY_OFFSET; -use sync::{MutexGuard, Spin}; -use sync::SpinLock as Mutex; +use spin::{Mutex, MutexGuard}; use super::HEAP_ALLOCATOR; use ucore_memory::{*, paging::PageTable}; use ucore_memory::cow::CowExt; @@ -49,7 +48,7 @@ lazy_static! { } /// The only way to get active page table -pub fn active_table() -> MutexGuard<'static, CowExt, Spin> { +pub fn active_table() -> MutexGuard<'static, CowExt> { ACTIVE_TABLE.lock() } diff --git a/kernel/src/sync/arch/riscv32/atomic_lock.rs b/kernel/src/sync/arch/riscv32/atomic_lock.rs deleted file mode 100644 index ee19bfa5..00000000 --- a/kernel/src/sync/arch/riscv32/atomic_lock.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! RISCV atomic is not currently supported by Rust. -//! This is a ugly workaround. - -use core::cell::UnsafeCell; - -extern { - fn __atomic_load_4(src: *const u32) -> u32; - fn __atomic_store_4(dst: *mut u32, val: u32); - fn __atomic_compare_exchange_4(dst: *mut u32, expected: *mut u32, desired: u32) -> bool; -} - -pub struct AtomicLock -{ - lock: UnsafeCell -} - -impl AtomicLock -{ - pub fn new() -> Self { - AtomicLock { - lock: UnsafeCell::new(0) - } - } - - /// Returns 1 if lock is acquired - pub fn try_lock(&self) -> bool { - let mut expected: u32 = 0; - unsafe { - __atomic_compare_exchange_4(self.lock.get(), &mut expected as *mut u32, 1) - } - } - - pub fn load(&self) -> bool { - unsafe { - __atomic_load_4(self.lock.get()) == 1 - } - } - - pub fn store(&self) { - unsafe { - __atomic_store_4(self.lock.get(), 0); - } - } -} diff --git a/kernel/src/sync/arch/x86_64/atomic_lock.rs b/kernel/src/sync/arch/x86_64/atomic_lock.rs deleted file mode 100644 index 06550c80..00000000 --- a/kernel/src/sync/arch/x86_64/atomic_lock.rs +++ /dev/null @@ -1,31 +0,0 @@ -use core::sync::atomic::{AtomicBool, Ordering}; - -pub struct AtomicLock -{ - lock: AtomicBool -} - -impl AtomicLock -{ - pub fn new() -> AtomicLock { - AtomicLock { - lock: AtomicBool::new(false) - } - } - - pub fn try_lock(&self) -> bool { - self.lock.compare_and_swap(false, true, Ordering::Acquire) == false - } - - pub fn load(&self) -> bool { - self.lock.load(Ordering::Relaxed) - } - - pub fn store(&self) { - self.lock.store(false, Ordering::Release); - } -} - -pub const ATOMIC_LOCK_INIT: AtomicLock = AtomicLock { - lock: AtomicBool::new(false) -}; \ No newline at end of file diff --git a/kernel/src/sync/mod.rs b/kernel/src/sync/mod.rs index c2bae784..cfa5071b 100644 --- a/kernel/src/sync/mod.rs +++ b/kernel/src/sync/mod.rs @@ -53,15 +53,6 @@ pub use self::condvar::*; pub use self::mutex::*; pub use self::semaphore::*; -#[allow(dead_code)] -#[cfg(target_arch = "x86_64")] -#[path = "arch/x86_64/atomic_lock.rs"] -pub mod atomic_lock; - -#[cfg(target_arch = "riscv32")] -#[path = "arch/riscv32/atomic_lock.rs"] -pub mod atomic_lock; - mod mutex; mod condvar; mod semaphore; diff --git a/kernel/src/sync/mutex.rs b/kernel/src/sync/mutex.rs index d542e763..6cddc9cd 100644 --- a/kernel/src/sync/mutex.rs +++ b/kernel/src/sync/mutex.rs @@ -30,8 +30,8 @@ use arch::interrupt; use core::cell::UnsafeCell; use core::fmt; use core::ops::{Deref, DerefMut}; +use core::sync::atomic::{ATOMIC_BOOL_INIT, AtomicBool, Ordering}; use super::Condvar; -use super::atomic_lock::AtomicLock; pub type SpinLock = Mutex; pub type SpinNoIrqLock = Mutex; @@ -39,7 +39,7 @@ pub type ThreadLock = Mutex; pub struct Mutex { - lock: AtomicLock, + lock: AtomicBool, support: S, data: UnsafeCell, } @@ -78,7 +78,7 @@ impl Mutex /// ``` pub fn new(user_data: T) -> Mutex { Mutex { - lock: AtomicLock::new(), + lock: ATOMIC_BOOL_INIT, data: UnsafeCell::new(user_data), support: S::new(), } @@ -96,9 +96,9 @@ impl Mutex impl Mutex { fn obtain_lock(&self) { - while !self.lock.try_lock() { + while self.lock.compare_and_swap(false, true, Ordering::Acquire) != false { // Wait until the lock looks unlocked before retrying - while self.lock.load() { + while self.lock.load(Ordering::Relaxed) { self.support.cpu_relax(); } } @@ -137,14 +137,14 @@ impl Mutex /// /// If the lock isn't held, this is a no-op. pub unsafe fn force_unlock(&self) { - self.lock.store(); + self.lock.store(false, Ordering::Release); } /// Tries to lock the mutex. If it is already locked, it will return None. Otherwise it returns /// a guard within Some. pub fn try_lock(&self) -> Option> { let support_guard = S::before_lock(); - if self.lock.try_lock() { + if self.lock.compare_and_swap(false, true, Ordering::Acquire) == false { Some(MutexGuard { mutex: self, support_guard, @@ -174,19 +174,19 @@ impl Default for Mutex { impl<'a, T: ?Sized, S: MutexSupport> Deref for MutexGuard<'a, T, S> { type Target = T; - fn deref<'b>(&'b self) -> &'b T { unsafe { &*self.mutex.data.get() } } + fn deref(&self) -> &T { unsafe { &*self.mutex.data.get() } } } impl<'a, T: ?Sized, S: MutexSupport> DerefMut for MutexGuard<'a, T, S> { - fn deref_mut<'b>(&'b mut self) -> &'b mut T { unsafe { &mut *self.mutex.data.get() } } + fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.mutex.data.get() } } } impl<'a, T: ?Sized, S: MutexSupport> Drop for MutexGuard<'a, T, S> { /// The dropping of the MutexGuard will release the lock it was created from. fn drop(&mut self) { - self.mutex.lock.store(); + self.mutex.lock.store(false, Ordering::Release); self.mutex.support.after_unlock(); } } From a42d6086c6bb6320185b89e3a0cc49c704b0dfcf Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 26 Oct 2018 18:18:11 +0800 Subject: [PATCH 26/58] Simplify IDE code. --- kernel/src/arch/x86_64/driver/ide.rs | 333 +++++++-------------------- 1 file changed, 80 insertions(+), 253 deletions(-) diff --git a/kernel/src/arch/x86_64/driver/ide.rs b/kernel/src/arch/x86_64/driver/ide.rs index 475a95bb..356978ef 100644 --- a/kernel/src/arch/x86_64/driver/ide.rs +++ b/kernel/src/arch/x86_64/driver/ide.rs @@ -5,267 +5,120 @@ use spin::Mutex; lazy_static! { - pub static ref DISK0: LockedIde = LockedIde(Mutex::new(DmaController::new(0))); - pub static ref DISK1: LockedIde = LockedIde(Mutex::new(DmaController::new(1))); + pub static ref DISK0: LockedIde = LockedIde(Mutex::new(IDE::new(0))); + pub static ref DISK1: LockedIde = LockedIde(Mutex::new(IDE::new(1))); } pub const BLOCK_SIZE: usize = 512; -pub struct LockedIde(pub Mutex); +pub struct LockedIde(pub Mutex); -pub struct DmaController { +pub struct IDE { num: u8, + /// I/O Base + base: u16, + /// Control Base + ctrl: u16, } -impl DmaController -{ - /// Read ATA DMA. Block size = 512 bytes. - pub fn read(&self, blockidx: u64, count: usize, dst: &mut [u32]) -> Result { - assert_eq!(dst.len(), count * SECTOR_SIZE); - let dst = if count > MAX_DMA_SECTORS { &mut dst[..MAX_DMA_SECTORS * SECTOR_SIZE] } else { dst }; - //self.do_dma(blockidx, DMABuffer::new_mut(dst, 32), disk, false); - self.ide_read_secs(self.num, blockidx, dst, count as u8) - } - /// Write ATA DMA. Block size = 512 bytes. - pub fn write(&self, blockidx: u64, count: usize, dst: &[u32]) -> Result { - assert_eq!(dst.len(), count * SECTOR_SIZE); - let dst = if count > MAX_DMA_SECTORS { &dst[..MAX_DMA_SECTORS * SECTOR_SIZE] } else { dst }; - //println!("ide_write_secs: disk={},blockidx={},count={}",disk,blockidx,count); - self.ide_write_secs(self.num, blockidx, dst, count as u8) - } - /// Create structure and init - fn new(num: u8) -> Self { - assert!(num < MAX_IDE as u8); - let ide = DmaController { num }; - ide.ide_init(); +impl IDE { + pub fn new(num: u8) -> Self { + let ide = match num { + 0 => IDE { num: 0, base: 0x1f0, ctrl: 0x3f4 }, + 1 => IDE { num: 1, base: 0x1f0, ctrl: 0x3f4 }, + 2 => IDE { num: 2, base: 0x170, ctrl: 0x374 }, + 3 => IDE { num: 3, base: 0x170, ctrl: 0x374 }, + _ => panic!("ide number should be 0,1,2,3"), + }; + ide.init(); ide } - fn ide_wait_ready(&self, iobase: u16, check_error: usize) -> usize { + /// Read ATA DMA. Block size = 512 bytes. + pub fn read(&self, sector: u64, count: usize, data: &mut [u32]) -> Result<(), ()> { + assert_eq!(data.len(), count * SECTOR_SIZE); + self.wait(); unsafe { - let mut r = port::inb(iobase + ISA_STATUS); - //println!("iobase:{} ready:{}",iobase,r); - while (r & IDE_BSY) > 0 { - r = port::inb(iobase + ISA_STATUS); - //println!("busy"); - } - /* nothing */ - if check_error == 1 && (r & (IDE_DF | IDE_ERR)) != 0 { - return 1; + self.select(sector, count as u8); + port::outb(self.base + ISA_COMMAND, IDE_CMD_READ); + for i in 0..count { + let ptr = &data[(i as usize) * SECTOR_SIZE]; + if self.wait_error() { + return Err(()); + } + asm!("rep insl" :: "{dx}"(self.base), "{rdi}"(ptr), "{cx}"(SECTOR_SIZE) : "rdi" : "volatile"); } } - return 0; + Ok(()) + } + /// Write ATA DMA. Block size = 512 bytes. + pub fn write(&self, sector: u64, count: usize, data: &[u32]) -> Result<(), ()> { + assert_eq!(data.len(), count * SECTOR_SIZE); + self.wait(); + unsafe { + self.select(sector, count as u8); + port::outb(self.base + ISA_COMMAND, IDE_CMD_WRITE); + for i in 0..count { + let ptr = &data[(i as usize) * SECTOR_SIZE]; + if self.wait_error() { + return Err(()); + } + asm!("rep outsl" :: "{dx}"(self.base), "{rsi}"(ptr), "{cx}"(SECTOR_SIZE) : "rsi" : "volatile"); + } + } + Ok(()) } - fn ide_init(&self) { - //static_assert((SECTSIZE % 4) == 0); - let ideno = self.num; - //println!("ideno:{}",ideno); - /* assume that no device here */ - //ide_devices[ideno].valid = 0; + fn wait(&self) { + while unsafe { port::inb(self.base + ISA_STATUS) } & IDE_BUSY != 0 {} + } - //let iobase = IO_BASE(ideno); - let iobase = CHANNELS[if ideno > 2 { 1 } else { 0 }].0; + fn wait_error(&self) -> bool { + self.wait(); + let status = unsafe { port::inb(self.base + ISA_STATUS) }; + status & (IDE_DF | IDE_ERR) != 0 + } - /* wait device ready */ - self.ide_wait_ready(iobase, 0); - //println!("ide_wait_ready"); + fn init(&self) { + self.wait(); unsafe { - /* step1: select drive */ - //println!("outb"); - port::outb(iobase + ISA_SDH, (0xE0 | ((ideno & 1) << 4)) as u8); - self.ide_wait_ready(iobase, 0); + // step1: select drive + port::outb(self.base + ISA_SDH, (0xE0 | ((self.num & 1) << 4)) as u8); + self.wait(); - /* step2: send ATA identify command */ - //println!("outb"); - port::outb(iobase + ISA_COMMAND, IDE_CMD_IDENTIFY); - self.ide_wait_ready(iobase, 0); + // step2: send ATA identify command + port::outb(self.base + ISA_COMMAND, IDE_CMD_IDENTIFY); + self.wait(); - /* step3: polling */ - //println!("inb"); - if port::inb(iobase + ISA_STATUS) == 0 || self.ide_wait_ready(iobase, 1) != 0 { + // step3: polling + if port::inb(self.base + ISA_STATUS) == 0 || self.wait_error() { return; } - //println!("insl"); - let mut buffer: [u32; 128] = [0; 128]; - for i in 0..buffer.len() { - buffer[i] = i as u32; - if i == 1 { - //println!("{:#x}",&buffer[i] as *const u32 as usize - ::consts::KERNEL_OFFSET) - } - } - //println!("insl {:#x}",&buffer as *const u32 as usize - ::consts::KERNEL_OFFSET); - - //println!("insl {:#x}",buffer.as_ptr() as usize - ::consts::KERNEL_OFFSET); - //port::insl(iobase + ISA_DATA, &mut buffer); - let port = iobase + ISA_DATA; - //let buf=&mut buffer; - for i in 0..buffer.len() { - asm!("insl %dx, (%rdi)" - :: "{dx}"(port), "{rdi}"(&buffer[i]) - : "rdi" : "volatile"); - } - //println!("insl"); - for i in 0..4 { - info!("ide init: {}", buffer[i]); - } + // ??? + let mut data = [0; SECTOR_SIZE]; + asm!("rep insl" :: "{dx}"(self.base + ISA_DATA), "{rdi}"(data.as_ptr()), "{cx}"(SECTOR_SIZE) : "rdi" : "volatile"); } - /* device is ok */ - //ide_devices[ideno].valid = 1; - - /* read identification space of the device */ - /*let buffer[128]; - insl(iobase + ISA_DATA, buffer, sizeof(buffer) / sizeof(unsigned int)); - - unsigned char *ident = (unsigned char *)buffer; - unsigned int sectors; - unsigned int cmdsets = *(unsigned int *)(ident + IDE_IDENT_CMDSETS); - /* device use 48-bits or 28-bits addressing */ - if (cmdsets & (1 << 26)) { - sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA_EXT); - } - else { - sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA); - } - ide_devices[ideno].sets = cmdsets; - ide_devices[ideno].size = sectors; - - /* check if supports LBA */ - assert((*(unsigned short *)(ident + IDE_IDENT_CAPABILITIES) & 0x200) != 0); - - unsigned char *model = ide_devices[ideno].model, *data = ident + IDE_IDENT_MODEL; - unsigned int i, length = 40; - for (i = 0; i < length; i += 2) { - model[i] = data[i + 1], model[i + 1] = data[i]; - } - do { - model[i] = '\0'; - } while (i -- > 0 && model[i] == ' '); - - cprintf("ide %d: %10u(sectors), '%s'.\n", ideno, ide_devices[ideno].size, ide_devices[ideno].model);*/ - - // enable ide interrupt - //pic_enable(IRQ_IDE1); - //pic_enable(IRQ_IDE2); - - info!("ide {} init end", self.num); - } - fn ide_read_secs<'a>(&'a self, ideno: u8, secno: u64, dst: &'a mut [u32], nsecs: u8) -> Result { - //assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); - //assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); - let iobase = CHANNELS[if ideno > 2 { 1 } else { 0 }].0; - let ioctrl = CHANNELS[if ideno > 2 { 1 } else { 0 }].1; - - //ide_wait_ready(iobase, 0); - - self.ide_wait_ready(iobase, 0); - - let ret = 0; - // generate interrupt - unsafe { - port::outb(ioctrl + ISA_CTRL, 0); - port::outb(iobase + ISA_SECCNT, nsecs); - port::outb(iobase + ISA_SECTOR, (secno & 0xFF) as u8); - port::outb(iobase + ISA_CYL_LO, ((secno >> 8) & 0xFF) as u8); - port::outb(iobase + ISA_CYL_HI, ((secno >> 16) & 0xFF) as u8); - port::outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | (((secno >> 24) & 0xF) as u8)); - //port::outb(iobase + ISA_SDH, (0xE0 | ((ideno & 1) << 4)) as u8); - //self.ide_wait_ready(iobase, 0); - port::outb(iobase + ISA_COMMAND, IDE_CMD_READ); - //self.ide_wait_ready(iobase, 0); - // if port::inb(iobase + ISA_STATUS) == 0 || self.ide_wait_ready(iobase, 1) != 0 { - // println!("error?"); - // } - for i in 0..nsecs { - //dst = dst + SECTSIZE; - let tmp = &mut dst[(i as usize) * SECTOR_SIZE..((i + 1) as usize) * SECTOR_SIZE]; - if self.ide_wait_ready(iobase, 1) != 0 { - println!("wait ready error"); - } - //self.ide_wait_ready(iobase, 1); - //port::insl(iobase, tmp); - let port = iobase; - //let buf=&mut buffer; - for i in 0..tmp.len() { - asm!("insl %dx, (%rdi)" - :: "{dx}"(port), "{rdi}"(&tmp[i]) - : "rdi" : "volatile"); - } - //println!("read :{}",i); - } - } - Ok(ret) } - fn ide_write_secs<'a>(&'a self, ideno: u8, secno: u64, src: &'a [u32], nsecs: u8) -> Result { - //assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); - //assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); - let iobase = CHANNELS[if ideno > 2 { 1 } else { 0 }].0; - let ioctrl = CHANNELS[if ideno > 2 { 1 } else { 0 }].1; - - //ide_wait_ready(iobase, 0); - - self.ide_wait_ready(iobase, 0); - - let ret = 0; - // generate interrupt + fn select(&self, sector: u64, count: u8) { + assert_ne!(count, 0); + self.wait(); unsafe { - port::outb(ioctrl + ISA_CTRL, 0); - port::outb(iobase + ISA_SECCNT, nsecs); - port::outb(iobase + ISA_SECTOR, (secno & 0xFF) as u8); - port::outb(iobase + ISA_CYL_LO, ((secno >> 8) & 0xFF) as u8); - port::outb(iobase + ISA_CYL_HI, ((secno >> 16) & 0xFF) as u8); - port::outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | (((secno >> 24) & 0xF) as u8)); - port::outb(iobase + ISA_COMMAND, IDE_CMD_WRITE); - //println!("{}",nsecs); - for i in 0..nsecs { - //dst = dst + SECTSIZE; - // if ((ret = ide_wait_ready(iobase, 1)) != 0) { - // goto out; - // } - //port::insb(iobase, dst); - //println!("i={}",i); - let tmp = &src[(i as usize) * SECTOR_SIZE..((i + 1) as usize) * SECTOR_SIZE]; - if self.ide_wait_ready(iobase, 1) != 0 { - println!("wait ready error"); - } - //println!("write {}:{}",i,src[i as usize]); - //println!("outsl"); - //port::outsl(iobase, tmp); - let port = iobase; - //let buf=&mut buffer; - for i in 0..tmp.len() { - asm!("outsl (%rsi), %dx" - :: "{dx}"(port), "{rsi}"(&tmp[i]) - : "rsi"); - } - //println!("write :{}",i); - // for i in 0..4 { - // println!("{}",src[i as usize]); - // } - //port::outb(iobase, src[i as usize]); - } + // generate interrupt + port::outb(self.ctrl + ISA_CTRL, 0); + port::outb(self.base + ISA_SECCNT, count); + port::outb(self.base + ISA_SECTOR, (sector & 0xFF) as u8); + port::outb(self.base + ISA_CYL_LO, ((sector >> 8) & 0xFF) as u8); + port::outb(self.base + ISA_CYL_HI, ((sector >> 16) & 0xFF) as u8); + port::outb(self.base + ISA_SDH, 0xE0 | ((self.num & 1) << 4) | (((sector >> 24) & 0xF) as u8)); } - Ok(ret) } } const SECTOR_SIZE: usize = 128; -//const MAX_DMA_SECTORS: usize = 0x2_0000 / SECTOR_SIZE; // Limited by sector count (and PRDT entries) const MAX_DMA_SECTORS: usize = 0x1F_F000 / SECTOR_SIZE; // Limited by sector count (and PRDT entries) // 512 PDRT entries, assume maximum fragmentation = 512 * 4K max = 2^21 = 2MB per transfer -const HDD_PIO_W28: u8 = 0x30; -const HDD_PIO_R28: u8 = 0x20; -const HDD_PIO_W48: u8 = 0x34; -const HDD_PIO_R48: u8 = 0x24; -const HDD_IDENTIFY: u8 = 0xEC; - -const HDD_DMA_R28: u8 = 0xC8; -const HDD_DMA_W28: u8 = 0xCA; -const HDD_DMA_R48: u8 = 0x25; -const HDD_DMA_W48: u8 = 0x35; - const ISA_DATA: u16 = 0x00; const ISA_ERROR: u16 = 0x01; const ISA_PRECOMP: u16 = 0x01; @@ -278,7 +131,7 @@ const ISA_SDH: u16 = 0x06; const ISA_COMMAND: u16 = 0x07; const ISA_STATUS: u16 = 0x07; -const IDE_BSY: u8 = 0x80; +const IDE_BUSY: u8 = 0x80; const IDE_DRDY: u8 = 0x40; const IDE_DF: u8 = 0x20; const IDE_DRQ: u8 = 0x08; @@ -288,33 +141,7 @@ const IDE_CMD_READ: u8 = 0x20; const IDE_CMD_WRITE: u8 = 0x30; const IDE_CMD_IDENTIFY: u8 = 0xEC; -const IDE_IDENT_SECTORS: usize = 20; -const IDE_IDENT_MODEL: usize = 54; -const IDE_IDENT_CAPABILITIES: usize = 98; -const IDE_IDENT_CMDSETS: usize = 164; -const IDE_IDENT_MAX_LBA: usize = 120; -const IDE_IDENT_MAX_LBA_EXT: usize = 200; - -const IO_BASE0: u16 = 0x1F0; -const IO_BASE1: u16 = 0x170; -const IO_CTRL0: u16 = 0x3F4; -const IO_CTRL1: u16 = 0x374; - -const MAX_IDE: usize = 4; const MAX_NSECS: usize = 128; -//const MAX_DISK_NSECS 0x10000000U; -//const VALID_IDE(ideno) (((ideno) >= 0) && ((ideno) < MAX_IDE) && (ide_devices[ideno].valid)) - -struct Channels { - base: u16, - // I/O Base - ctrl: u16, // Control Base -} - -const CHANNELS: [(u16, u16); 2] = [(IO_BASE0, IO_CTRL0), (IO_BASE1, IO_CTRL1)]; - -//const IO_BASE(ideno) (CHANNELS[(ideno) >> 1].base) -//const IO_CTRL(ideno) (CHANNELS[(ideno) >> 1].ctrl) mod port { use x86_64::instructions::port::Port; From 438e290b6d4afb9cb0bfe18d34f73beb9bcb7059 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 26 Oct 2018 23:43:12 +0800 Subject: [PATCH 27/58] Fix PageTable::get_entry -> Option. --- crate/memory/src/cow.rs | 47 +++++++++++----------- crate/memory/src/lib.rs | 1 + crate/memory/src/memory_set.rs | 2 +- crate/memory/src/paging/mock_page_table.rs | 45 +++++++++++---------- crate/memory/src/paging/mod.rs | 2 +- crate/memory/src/swap/enhanced_clock.rs | 2 +- crate/memory/src/swap/mod.rs | 31 +++++++------- kernel/Cargo.lock | 18 ++++----- kernel/Cargo.toml | 1 - kernel/src/arch/riscv32/consts.rs | 4 +- kernel/src/arch/riscv32/paging.rs | 35 ++++++++-------- kernel/src/arch/x86_64/paging.rs | 17 ++++++-- kernel/src/trap.rs | 3 +- 13 files changed, 112 insertions(+), 96 deletions(-) diff --git a/crate/memory/src/cow.rs b/crate/memory/src/cow.rs index a5d27f37..5a5e8ae8 100644 --- a/crate/memory/src/cow.rs +++ b/crate/memory/src/cow.rs @@ -48,33 +48,34 @@ impl CowExt { } } pub fn unmap_shared(&mut self, addr: VirtAddr) { - { - let entry = self.page_table.get_entry(addr); - let frame = entry.target() / PAGE_SIZE; - if entry.readonly_shared() { - self.rc_map.read_decrease(&frame); - } else if entry.writable_shared() { - self.rc_map.write_decrease(&frame); - } + let entry = self.page_table.get_entry(addr) + .expect("entry not exist"); + let frame = entry.target() / PAGE_SIZE; + if entry.readonly_shared() { + self.rc_map.read_decrease(&frame); + } else if entry.writable_shared() { + self.rc_map.write_decrease(&frame); } self.page_table.unmap(addr); } /// This function must be called whenever PageFault happens. /// Return whether copy-on-write happens. pub fn page_fault_handler(&mut self, addr: VirtAddr, alloc_frame: impl FnOnce() -> PhysAddr) -> bool { - { - let entry = self.page_table.get_entry(addr); - if !entry.readonly_shared() && !entry.writable_shared() { - return false; - } - let frame = entry.target() / PAGE_SIZE; - if self.rc_map.read_count(&frame) == 0 && self.rc_map.write_count(&frame) == 1 { - entry.clear_shared(); - entry.set_writable(true); - entry.update(); - self.rc_map.write_decrease(&frame); - return true; - } + let entry = self.page_table.get_entry(addr); + if entry.is_none() { + return false; + } + let entry = entry.unwrap(); + if !entry.readonly_shared() && !entry.writable_shared() { + return false; + } + let frame = entry.target() / PAGE_SIZE; + if self.rc_map.read_count(&frame) == 0 && self.rc_map.write_count(&frame) == 1 { + entry.clear_shared(); + entry.set_writable(true); + entry.update(); + self.rc_map.write_decrease(&frame); + return true; } use core::mem::uninitialized; let mut temp_data: [u8; PAGE_SIZE] = unsafe { uninitialized() }; @@ -186,7 +187,7 @@ pub mod test { pt.write(0x1000, 2); assert_eq!(pt.rc_map.read_count(&frame), 1); assert_eq!(pt.rc_map.write_count(&frame), 1); - assert_ne!(pt.get_entry(0x1000).target(), target); + assert_ne!(pt.get_entry(0x1000).unwrap().target(), target); assert_eq!(pt.read(0x1000), 2); assert_eq!(pt.read(0x2000), 1); assert_eq!(pt.read(0x3000), 1); @@ -199,7 +200,7 @@ pub mod test { pt.write(0x2000, 3); assert_eq!(pt.rc_map.read_count(&frame), 0); assert_eq!(pt.rc_map.write_count(&frame), 0); - assert_eq!(pt.get_entry(0x2000).target(), target, + assert_eq!(pt.get_entry(0x2000).unwrap().target(), target, "The last write reference should not allocate new frame."); assert_eq!(pt.read(0x1000), 2); assert_eq!(pt.read(0x2000), 3); diff --git a/crate/memory/src/lib.rs b/crate/memory/src/lib.rs index 808d9836..be2dfd73 100644 --- a/crate/memory/src/lib.rs +++ b/crate/memory/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] #![feature(alloc)] +#![feature(nll)] extern crate alloc; diff --git a/crate/memory/src/memory_set.rs b/crate/memory/src/memory_set.rs index ca3e7dc7..5a80f014 100644 --- a/crate/memory/src/memory_set.rs +++ b/crate/memory/src/memory_set.rs @@ -85,7 +85,7 @@ impl MemoryArea { for page in Page::range_of(self.start_addr, self.end_addr) { let addr = page.start_address(); if self.phys_start_addr.is_none() { - let target = pt.get_entry(addr).target(); + let target = pt.get_entry(addr).unwrap().target(); T::dealloc_frame(target); } pt.unmap(addr); diff --git a/crate/memory/src/paging/mock_page_table.rs b/crate/memory/src/paging/mock_page_table.rs index d26b7af7..fa0d6252 100644 --- a/crate/memory/src/paging/mock_page_table.rs +++ b/crate/memory/src/paging/mock_page_table.rs @@ -70,8 +70,8 @@ impl PageTable for MockPageTable { assert!(entry.present); entry.present = false; } - fn get_entry(&mut self, addr: VirtAddr) -> &mut ::Entry { - &mut self.entries[addr / PAGE_SIZE] + fn get_entry(&mut self, addr: VirtAddr) -> Option<&mut Self::Entry> { + Some(&mut self.entries[addr / PAGE_SIZE]) } fn get_page_slice_mut<'a,'b>(&'a mut self, addr: VirtAddr) -> &'b mut [u8] { self._read(addr); @@ -156,34 +156,35 @@ mod test { fn entry() { let mut pt = MockPageTable::new(); pt.map(0x0, 0x1000); - { - let entry = pt.get_entry(0); - assert!(entry.present()); - assert!(entry.writable()); - assert!(!entry.accessed()); - assert!(!entry.dirty()); - assert_eq!(entry.target(), 0x1000); - } + + let entry = pt.get_entry(0).unwrap(); + assert!(entry.present()); + assert!(entry.writable()); + assert!(!entry.accessed()); + assert!(!entry.dirty()); + assert_eq!(entry.target(), 0x1000); pt.read(0x0); - assert!(pt.get_entry(0).accessed()); - assert!(!pt.get_entry(0).dirty()); + let entry = pt.get_entry(0).unwrap(); + assert!(entry.accessed()); + assert!(!entry.dirty()); - pt.get_entry(0).clear_accessed(); - assert!(!pt.get_entry(0).accessed()); + entry.clear_accessed(); + assert!(!entry.accessed()); pt.write(0x1, 1); - assert!(pt.get_entry(0).accessed()); - assert!(pt.get_entry(0).dirty()); + let entry = pt.get_entry(0).unwrap(); + assert!(entry.accessed()); + assert!(entry.dirty()); - pt.get_entry(0).clear_dirty(); - assert!(!pt.get_entry(0).dirty()); + entry.clear_dirty(); + assert!(!entry.dirty()); - pt.get_entry(0).set_writable(false); - assert!(!pt.get_entry(0).writable()); + entry.set_writable(false); + assert!(!entry.writable()); - pt.get_entry(0).set_present(false); - assert!(!pt.get_entry(0).present()); + entry.set_present(false); + assert!(!entry.present()); } #[test] diff --git a/crate/memory/src/paging/mod.rs b/crate/memory/src/paging/mod.rs index 0a3d4c0a..1a878c73 100644 --- a/crate/memory/src/paging/mod.rs +++ b/crate/memory/src/paging/mod.rs @@ -13,7 +13,7 @@ pub trait PageTable { type Entry: Entry; fn map(&mut self, addr: VirtAddr, target: PhysAddr) -> &mut Self::Entry; fn unmap(&mut self, addr: VirtAddr); - fn get_entry(&mut self, addr: VirtAddr) -> &mut Self::Entry; + fn get_entry(&mut self, addr: VirtAddr) -> Option<&mut Self::Entry>; // For testing with mock fn get_page_slice_mut<'a,'b>(&'a mut self, addr: VirtAddr) -> &'b mut [u8]; fn read(&mut self, addr: VirtAddr) -> u8; diff --git a/crate/memory/src/swap/enhanced_clock.rs b/crate/memory/src/swap/enhanced_clock.rs index 63069f3e..b1980a3f 100644 --- a/crate/memory/src/swap/enhanced_clock.rs +++ b/crate/memory/src/swap/enhanced_clock.rs @@ -38,7 +38,7 @@ impl SwapManager for EnhancedClockSwapManager { // The reason may be `get_page_slice_mut()` contains unsafe operation, // which lead the compiler to do a wrong optimization. // let slice = page_table.get_page_slice_mut(addr); - let entry = page_table.get_entry(addr); + let entry = page_table.get_entry(addr).unwrap(); // println!("{:#x} , {}, {}", addr, entry.accessed(), entry.dirty()); match (entry.accessed(), entry.dirty()) { diff --git a/crate/memory/src/swap/mod.rs b/crate/memory/src/swap/mod.rs index 6456e198..1d0f78c4 100644 --- a/crate/memory/src/swap/mod.rs +++ b/crate/memory/src/swap/mod.rs @@ -68,7 +68,8 @@ impl SwapExt { /// Swap out page of `addr`, return the origin map target. fn swap_out(&mut self, addr: VirtAddr) -> Result { let data = self.page_table.get_page_slice_mut(addr); - let entry = self.page_table.get_entry(addr); + let entry = self.page_table.get_entry(addr) + .ok_or(SwapError::NotMapped)?; if entry.swapped() { return Err(SwapError::AlreadySwapped); } @@ -82,26 +83,25 @@ impl SwapExt { } /// Map page of `addr` to `target`, then swap in the data. fn swap_in(&mut self, addr: VirtAddr, target: PhysAddr) -> Result<(), SwapError> { - let token = { - let entry = self.page_table.get_entry(addr); - if !entry.swapped() { - return Err(SwapError::NotSwapped); - } - let token = entry.target() / PAGE_SIZE; - entry.set_target(target); - entry.set_swapped(false); - entry.set_present(true); - entry.update(); - token - }; + let entry = self.page_table.get_entry(addr) + .ok_or(SwapError::NotMapped)?; + if !entry.swapped() { + return Err(SwapError::NotSwapped); + } + let token = entry.target() / PAGE_SIZE; + entry.set_target(target); + entry.set_swapped(false); + entry.set_present(true); + entry.update(); let data = self.page_table.get_page_slice_mut(addr); self.swapper.swap_in(token, data).map_err(|_| SwapError::IOError)?; self.swap_manager.push(addr); Ok(()) } pub fn page_fault_handler(&mut self, addr: VirtAddr, alloc_frame: impl FnOnce() -> Option) -> bool { - if !self.page_table.get_entry(addr).swapped() { - return false; + match self.page_table.get_entry(addr) { + None => return false, + Some(entry) => if !entry.swapped() { return false; }, } // Allocate a frame, if failed, swap out a page let frame = alloc_frame().unwrap_or_else(|| self.swap_out_any().ok().unwrap()); @@ -112,6 +112,7 @@ impl SwapExt { pub enum SwapError { AlreadySwapped, + NotMapped, NotSwapped, NoSwapped, IOError, diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index 6d9f4456..7e1f1486 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -42,12 +42,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bootloader" version = "0.3.1" -source = "git+https://github.com/wangrunji0408/bootloader#d53b01de2fb8c0b3793ebf2d8ec1d56180dc71d4" +source = "git+https://github.com/wangrunji0408/bootloader#b03d826d6591f392cd824bbd350e82b5f17f21f3" dependencies = [ "apic 0.1.0 (git+https://github.com/wangrunji0408/APIC-Rust)", "fixedvec 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -58,7 +58,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -118,7 +118,7 @@ name = "log" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -245,7 +245,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -272,7 +272,7 @@ dependencies = [ "ucore-memory 0.1.0", "ucore-process 0.1.0", "volatile 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -343,7 +343,7 @@ dependencies = [ [[package]] name = "x86_64" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -374,7 +374,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum bootloader 0.3.1 (git+https://github.com/wangrunji0408/bootloader)" = "" "checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" -"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" +"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" "checksum fixedvec 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c6c16d316ccdac21a4dd648e314e76facbbaf316e83ca137d0857a9c07419d0" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" @@ -408,6 +408,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum x86 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "841e1ca5a87068718a2a26f2473c6f93cf3b8119f9778fa0ae4b39b664d9e66a" -"checksum x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "a7e95a2813e20d24546c2b29ecc6df55cfde30c983df69eeece0b179ca9d68ac" +"checksum x86_64 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2bd647af1614659e1febec1d681231aea4ebda4818bf55a578aff02f3e4db4b4" "checksum xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22678df5df766e8d1e5d609da69f0c3132d794edf6ab5e75e7abcd2270d4cf58" "checksum zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f1bc8a6b2005884962297587045002d8cfb8dcec9db332f4ca216ddc5de82c5" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 1027eb3f..bbc77038 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -9,7 +9,6 @@ no_bbl = [] [profile.dev] # MUST >= 1 : Enable RVO to avoid stack overflow -# MUST <= 1 : Avoid double fault at -O2 T_T opt-level = 1 [profile.release] diff --git a/kernel/src/arch/riscv32/consts.rs b/kernel/src/arch/riscv32/consts.rs index f2adbcb7..d60b6e25 100644 --- a/kernel/src/arch/riscv32/consts.rs +++ b/kernel/src/arch/riscv32/consts.rs @@ -2,9 +2,9 @@ // [0x80000000, 0x80800000] const P2_SIZE: usize = 1 << 22; const P2_MASK: usize = 0x3ff << 22; -pub const RECURSIVE_PAGE_PML4: usize = 0x3fe; +pub const RECURSIVE_INDEX: usize = 0x3fe; pub const KERNEL_OFFSET: usize = 0; -pub const KERNEL_PML4: usize = 0x8000_0000 >> 22; +pub const KERNEL_P2_INDEX: usize = 0x8000_0000 >> 22; pub const KERNEL_HEAP_OFFSET: usize = 0x8020_0000; pub const KERNEL_HEAP_SIZE: usize = 0x0020_0000; pub const MEMORY_OFFSET: usize = 0x8000_0000; diff --git a/kernel/src/arch/riscv32/paging.rs b/kernel/src/arch/riscv32/paging.rs index 680448b5..ec1d03bd 100644 --- a/kernel/src/arch/riscv32/paging.rs +++ b/kernel/src/arch/riscv32/paging.rs @@ -1,4 +1,4 @@ -use consts::{KERNEL_PML4, RECURSIVE_PAGE_PML4}; +use consts::{KERNEL_P2_INDEX, RECURSIVE_INDEX}; // Depends on kernel use memory::{active_table, alloc_frame, alloc_stack, dealloc_frame}; use super::riscv::addr::*; @@ -14,14 +14,14 @@ use ucore_memory::paging::*; pub fn setup_page_table(frame: Frame) { let p2 = unsafe { &mut *(frame.start_address().as_u32() as *mut RvPageTable) }; p2.zero(); - p2.set_recursive(RECURSIVE_PAGE_PML4, frame.clone()); + p2.set_recursive(RECURSIVE_INDEX, frame.clone()); // Set kernel identity map // 0x10000000 ~ 1K area p2.map_identity(0x40, EF::VALID | EF::READABLE | EF::WRITABLE); // 0x80000000 ~ 8K area - p2.map_identity(KERNEL_PML4, EF::VALID | EF::READABLE | EF::WRITABLE | EF::EXECUTABLE); - p2.map_identity(KERNEL_PML4 + 1, EF::VALID | EF::READABLE | EF::WRITABLE | EF::EXECUTABLE); + p2.map_identity(KERNEL_P2_INDEX, EF::VALID | EF::READABLE | EF::WRITABLE | EF::EXECUTABLE); + p2.map_identity(KERNEL_P2_INDEX + 1, EF::VALID | EF::READABLE | EF::WRITABLE | EF::EXECUTABLE); use super::riscv::register::satp; unsafe { satp::set(satp::Mode::Sv32, 0, frame); } @@ -42,7 +42,7 @@ impl PageTable for ActivePageTable { let frame = Frame::of_addr(PhysAddr::new(target as u32)); self.0.map_to(page, frame, flags, &mut FrameAllocatorForRiscv) .unwrap().flush(); - self.get_entry(addr) + self.get_entry(addr).unwrap() } fn unmap(&mut self, addr: usize) { @@ -51,11 +51,14 @@ impl PageTable for ActivePageTable { flush.flush(); } - fn get_entry(&mut self, addr: usize) -> &mut PageEntry { + fn get_entry(&mut self, addr: usize) -> Option<&mut PageEntry> { + if unsafe { !(*ROOT_PAGE_TABLE)[addr >> 22].flags().contains(EF::VALID) } { + return None; + } let page = Page::of_addr(VirtAddr::new(addr)); let _ = self.0.translate_page(page); - let entry_addr = ((addr >> 10) & 0x003ffffc) | (RECURSIVE_PAGE_PML4 << 22); - unsafe { &mut *(entry_addr as *mut PageEntry) } + let entry_addr = ((addr >> 10) & ((1 << 22) - 4)) | (RECURSIVE_INDEX << 22); + unsafe { Some(&mut *(entry_addr as *mut PageEntry)) } } fn get_page_slice_mut<'a, 'b>(&'a mut self, addr: usize) -> &'b mut [u8] { @@ -73,7 +76,7 @@ impl PageTable for ActivePageTable { } const ROOT_PAGE_TABLE: *mut RvPageTable = - (((RECURSIVE_PAGE_PML4 << 10) | (RECURSIVE_PAGE_PML4 + 1)) << 12) as *mut RvPageTable; + (((RECURSIVE_INDEX << 10) | (RECURSIVE_INDEX + 1)) << 12) as *mut RvPageTable; impl ActivePageTable { pub unsafe fn new() -> Self { @@ -153,24 +156,24 @@ impl InactivePageTable for InactivePageTable0 { .expect("failed to allocate frame"); active_table().with_temporary_map(&frame, |_, table: &mut RvPageTable| { table.zero(); - table.set_recursive(RECURSIVE_PAGE_PML4, frame.clone()); + table.set_recursive(RECURSIVE_INDEX, frame.clone()); }); InactivePageTable0 { p2_frame: frame } } fn edit(&mut self, f: impl FnOnce(&mut Self::Active)) { active_table().with_temporary_map(&satp::read().frame(), |active_table, p2_table: &mut RvPageTable| { - let backup = p2_table[RECURSIVE_PAGE_PML4].clone(); + let backup = p2_table[RECURSIVE_INDEX].clone(); // overwrite recursive mapping - p2_table[RECURSIVE_PAGE_PML4].set(self.p2_frame.clone(), EF::VALID); + p2_table[RECURSIVE_INDEX].set(self.p2_frame.clone(), EF::VALID); sfence_vma_all(); // execute f in the new context f(active_table); - // restore recursive mapping to original p4 table - p2_table[RECURSIVE_PAGE_PML4] = backup; + // restore recursive mapping to original p2 table + p2_table[RECURSIVE_INDEX] = backup; sfence_vma_all(); }); } @@ -222,12 +225,12 @@ impl InactivePageTable0 { fn map_kernel(&mut self) { let table = unsafe { &mut *ROOT_PAGE_TABLE }; let e0 = table[0x40]; - let e1 = table[KERNEL_PML4]; + let e1 = table[KERNEL_P2_INDEX]; assert!(!e1.is_unused()); self.edit(|_| { table[0x40] = e0; - table[KERNEL_PML4].set(e1.frame(), EF::VALID | EF::GLOBAL); + table[KERNEL_P2_INDEX].set(e1.frame(), EF::VALID | EF::GLOBAL); }); } } diff --git a/kernel/src/arch/x86_64/paging.rs b/kernel/src/arch/x86_64/paging.rs index 87177032..0a3d8123 100644 --- a/kernel/src/arch/x86_64/paging.rs +++ b/kernel/src/arch/x86_64/paging.rs @@ -49,7 +49,7 @@ impl PageTable for ActivePageTable { let flags = EF::PRESENT | EF::WRITABLE | EF::NO_EXECUTE; self.0.map_to(Page::of_addr(addr), Frame::of_addr(target), flags, &mut FrameAllocatorForX86) .unwrap().flush(); - self.get_entry(addr) + unsafe { &mut *(get_entry_ptr(addr, 1)) } } fn unmap(&mut self, addr: usize) { @@ -57,9 +57,12 @@ impl PageTable for ActivePageTable { flush.flush(); } - fn get_entry(&mut self, addr: usize) -> &mut PageEntry { - let entry_addr = ((addr >> 9) & 0o777_777_777_7770) | 0xffffff80_00000000; - unsafe { &mut *(entry_addr as *mut PageEntry) } + fn get_entry(&mut self, addr: usize) -> Option<&mut PageEntry> { + for level in 0..3 { + let entry = get_entry_ptr(addr, 4 - level); + if unsafe { !(*entry).present() } { return None; } + } + unsafe { Some(&mut *(get_entry_ptr(addr, 1))) } } fn get_page_slice_mut<'a, 'b>(&'a mut self, addr: usize) -> &'b mut [u8] { @@ -140,6 +143,12 @@ impl Entry for PageEntry { fn set_execute(&mut self, value: bool) { self.as_flags().set(EF::NO_EXECUTE, !value); } } +fn get_entry_ptr(addr: usize, level: u8) -> *mut PageEntry { + debug_assert!(level <= 4); + let entry_addr = ((addr >> (level * 9)) & !0x7) | !((1 << (48 - level * 9)) - 1); + entry_addr as *mut PageEntry +} + impl PageEntry { fn as_flags(&mut self) -> &mut EF { unsafe { &mut *(self as *mut _ as *mut EF) } diff --git a/kernel/src/trap.rs b/kernel/src/trap.rs index 7ed9e218..749a0b26 100644 --- a/kernel/src/trap.rs +++ b/kernel/src/trap.rs @@ -15,8 +15,9 @@ pub fn before_return() { } pub fn error(tf: &TrapFrame) -> ! { + error!("{:#x?}", tf); let pid = processor().pid(); - error!("On CPU{} Process {}:\n{:#x?}", cpu::id(), pid, tf); + error!("On CPU{} Process {}", cpu::id(), pid); processor().manager().exit(pid, 0x100); processor().yield_now(); From 72dc3f62adc777e09ea63afe8a87e33877b61953 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Sat, 27 Oct 2018 15:17:15 +0800 Subject: [PATCH 28/58] Remove kernel stack from MemorySet. --- crate/memory/src/memory_set.rs | 21 ++------------------- kernel/src/arch/riscv32/memory.rs | 13 +++---------- kernel/src/arch/riscv32/paging.rs | 6 +----- kernel/src/arch/x86_64/paging.rs | 6 +----- kernel/src/memory.rs | 28 ++++++++++++++++++++-------- kernel/src/process/context.rs | 22 ++++++++++++++++------ 6 files changed, 43 insertions(+), 53 deletions(-) diff --git a/crate/memory/src/memory_set.rs b/crate/memory/src/memory_set.rs index 5a80f014..49e98969 100644 --- a/crate/memory/src/memory_set.rs +++ b/crate/memory/src/memory_set.rs @@ -15,7 +15,6 @@ pub trait InactivePageTable { fn alloc_frame() -> Option; fn dealloc_frame(target: PhysAddr); - fn alloc_stack() -> Stack; } /// 一片连续内存空间,有相同的访问权限 @@ -132,7 +131,6 @@ impl MemoryAttr { pub struct MemorySet { areas: Vec, page_table: T, - kstack: Stack, } impl MemorySet { @@ -140,17 +138,12 @@ impl MemorySet { MemorySet { areas: Vec::::new(), page_table: T::new(), - kstack: T::alloc_stack(), } } - /// Used for remap_kernel() where heap alloc is unavailable - pub unsafe fn new_from_raw_space(slice: &mut [u8], kstack: Stack) -> Self { - use core::mem::size_of; - let cap = slice.len() / size_of::(); + pub fn new_bare() -> Self { MemorySet { - areas: Vec::::from_raw_parts(slice.as_ptr() as *mut MemoryArea, 0, cap), + areas: Vec::::new(), page_table: T::new_bare(), - kstack, } } pub fn find_area(&self, addr: VirtAddr) -> Option<&MemoryArea> { @@ -175,9 +168,6 @@ impl MemorySet { pub fn token(&self) -> usize { self.page_table.token() } - pub fn kstack_top(&self) -> usize { - self.kstack.top - } pub fn clear(&mut self) { let Self { ref mut page_table, ref mut areas, .. } = self; page_table.edit(|pt| { @@ -200,7 +190,6 @@ impl Clone for MemorySet { MemorySet { areas: self.areas.clone(), page_table, - kstack: T::alloc_stack(), } } } @@ -218,9 +207,3 @@ impl Debug for MemorySet { .finish() } } - -#[derive(Debug)] -pub struct Stack { - pub top: usize, - pub bottom: usize, -} \ No newline at end of file diff --git a/kernel/src/arch/riscv32/memory.rs b/kernel/src/arch/riscv32/memory.rs index f9b55977..7e0c08cc 100644 --- a/kernel/src/arch/riscv32/memory.rs +++ b/kernel/src/arch/riscv32/memory.rs @@ -1,10 +1,8 @@ use core::slice; -use memory::{active_table, FRAME_ALLOCATOR, init_heap, MemoryArea, MemoryAttr, MemorySet, Stack}; +use memory::{active_table, FRAME_ALLOCATOR, init_heap, MemoryArea, MemoryAttr, MemorySet}; use super::riscv::{addr::*, register::sstatus}; use ucore_memory::PAGE_SIZE; -// static mut KERNEL_MS: Option = None; - pub fn init() { #[repr(align(4096))] struct PageData([u8; PAGE_SIZE]); @@ -14,8 +12,8 @@ pub fn init() { let frame = Frame::of_addr(PhysAddr::new(&PAGE_TABLE_ROOT as *const _ as u32)); super::paging::setup_page_table(frame); init_frame_allocator(); - remap_the_kernel(); init_heap(); + remap_the_kernel(); } fn init_frame_allocator() { @@ -37,12 +35,7 @@ fn init_frame_allocator() { fn remap_the_kernel() { use consts::{KERNEL_HEAP_OFFSET, KERNEL_HEAP_SIZE}; - let kstack = Stack { - top: bootstacktop as usize, - bottom: bootstack as usize + PAGE_SIZE, - }; - static mut SPACE: [u8; 0x1000] = [0; 0x1000]; - let mut ms = unsafe { MemorySet::new_from_raw_space(&mut SPACE, kstack) }; + let mut ms = MemorySet::new_bare(); ms.push(MemoryArea::new_identity(0x10000000, 0x10000008, MemoryAttr::default(), "serial")); ms.push(MemoryArea::new_identity(stext as usize, etext as usize, MemoryAttr::default().execute().readonly(), "text")); ms.push(MemoryArea::new_identity(sdata as usize, edata as usize, MemoryAttr::default(), "data")); diff --git a/kernel/src/arch/riscv32/paging.rs b/kernel/src/arch/riscv32/paging.rs index ec1d03bd..68efb454 100644 --- a/kernel/src/arch/riscv32/paging.rs +++ b/kernel/src/arch/riscv32/paging.rs @@ -1,6 +1,6 @@ use consts::{KERNEL_P2_INDEX, RECURSIVE_INDEX}; // Depends on kernel -use memory::{active_table, alloc_frame, alloc_stack, dealloc_frame}; +use memory::{active_table, alloc_frame, dealloc_frame}; use super::riscv::addr::*; use super::riscv::asm::{sfence_vma, sfence_vma_all}; use super::riscv::paging::{Mapper, PageTable as RvPageTable, PageTableEntry, PageTableFlags as EF, RecursivePageTable}; @@ -215,10 +215,6 @@ impl InactivePageTable for InactivePageTable0 { fn dealloc_frame(target: usize) { dealloc_frame(target) } - - fn alloc_stack() -> Stack { - alloc_stack() - } } impl InactivePageTable0 { diff --git a/kernel/src/arch/x86_64/paging.rs b/kernel/src/arch/x86_64/paging.rs index 0a3d8123..8838dce8 100644 --- a/kernel/src/arch/x86_64/paging.rs +++ b/kernel/src/arch/x86_64/paging.rs @@ -1,6 +1,6 @@ use bit_allocator::{BitAlloc, BitAlloc64K}; // Depends on kernel -use memory::{active_table, alloc_frame, alloc_stack, dealloc_frame}; +use memory::{active_table, alloc_frame, dealloc_frame}; use spin::{Mutex, MutexGuard}; use ucore_memory::cow::CowExt; use ucore_memory::memory_set::*; @@ -231,10 +231,6 @@ impl InactivePageTable for InactivePageTable0 { fn dealloc_frame(target: usize) { dealloc_frame(target) } - - fn alloc_stack() -> Stack { - alloc_stack() - } } impl InactivePageTable0 { diff --git a/kernel/src/memory.rs b/kernel/src/memory.rs index 0f203f37..f69b5bad 100644 --- a/kernel/src/memory.rs +++ b/kernel/src/memory.rs @@ -5,7 +5,7 @@ use spin::{Mutex, MutexGuard}; use super::HEAP_ALLOCATOR; use ucore_memory::{*, paging::PageTable}; use ucore_memory::cow::CowExt; -pub use ucore_memory::memory_set::{MemoryArea, MemoryAttr, MemorySet as MemorySet_, Stack}; +pub use ucore_memory::memory_set::{MemoryArea, MemoryAttr, MemorySet as MemorySet_}; pub type MemorySet = MemorySet_; @@ -32,13 +32,25 @@ pub fn dealloc_frame(target: usize) { FRAME_ALLOCATOR.lock().dealloc((target - MEMORY_OFFSET) / PAGE_SIZE); } -// alloc from heap -pub fn alloc_stack() -> Stack { - use alloc::alloc::{alloc, Layout}; - const STACK_SIZE: usize = 0x8000; - let bottom = unsafe{ alloc(Layout::from_size_align(STACK_SIZE, 0x8000).unwrap()) } as usize; - let top = bottom + STACK_SIZE; - Stack { top, bottom } +pub struct KernelStack(usize); +const STACK_SIZE: usize = 0x8000; + +impl KernelStack { + pub fn new() -> Self { + use alloc::alloc::{alloc, Layout}; + let bottom = unsafe{ alloc(Layout::from_size_align(STACK_SIZE, STACK_SIZE).unwrap()) } as usize; + KernelStack(bottom) + } + pub fn top(&self) -> usize { + self.0 + STACK_SIZE + } +} + +impl Drop for KernelStack { + fn drop(&mut self) { + use alloc::alloc::{dealloc, Layout}; + unsafe{ dealloc(self.0 as _, Layout::from_size_align(STACK_SIZE, STACK_SIZE).unwrap()); } + } } lazy_static! { diff --git a/kernel/src/process/context.rs b/kernel/src/process/context.rs index dcc53f55..95da8426 100644 --- a/kernel/src/process/context.rs +++ b/kernel/src/process/context.rs @@ -1,5 +1,5 @@ use arch::interrupt::{TrapFrame, Context as ArchContext}; -use memory::{MemoryArea, MemoryAttr, MemorySet}; +use memory::{MemoryArea, MemoryAttr, MemorySet, KernelStack}; use xmas_elf::{ElfFile, header, program::{Flags, ProgramHeader, Type}}; use core::fmt::{Debug, Error, Formatter}; use ucore_process::Context; @@ -8,6 +8,7 @@ use alloc::boxed::Box; pub struct ContextImpl { arch: ArchContext, memory_set: MemorySet, + kstack: KernelStack, } impl Context for ContextImpl { @@ -23,14 +24,17 @@ impl ContextImpl { Box::new(ContextImpl { arch: ArchContext::null(), memory_set: MemorySet::new(), + kstack: KernelStack::new(), }) } pub fn new_kernel(entry: extern fn(usize) -> !, arg: usize) -> Box { - let ms = MemorySet::new(); + let memory_set = MemorySet::new(); + let kstack = KernelStack::new(); Box::new(ContextImpl { - arch: unsafe { ArchContext::new_kernel_thread(entry, arg, ms.kstack_top(), ms.token()) }, - memory_set: ms, + arch: unsafe { ArchContext::new_kernel_thread(entry, arg, kstack.top(), memory_set.token()) }, + memory_set, + kstack, }) } @@ -82,12 +86,15 @@ impl ContextImpl { }); } + let kstack = KernelStack::new(); + Box::new(ContextImpl { arch: unsafe { ArchContext::new_user_thread( - entry_addr, user_stack_top - 8, memory_set.kstack_top(), is32, memory_set.token()) + entry_addr, user_stack_top - 8, kstack.top(), is32, memory_set.token()) }, memory_set, + kstack, }) } @@ -111,9 +118,12 @@ impl ContextImpl { }); } + let kstack = KernelStack::new(); + Box::new(ContextImpl { - arch: unsafe { ArchContext::new_fork(tf, memory_set.kstack_top(), memory_set.token()) }, + arch: unsafe { ArchContext::new_fork(tf, kstack.top(), memory_set.token()) }, memory_set, + kstack, }) } } From 7229b49eb809b2661112cb8f38ae4543fb9b65ba Mon Sep 17 00:00:00 2001 From: WangRunji Date: Tue, 30 Oct 2018 13:29:00 +0800 Subject: [PATCH 29/58] Use rust-lld for RV32. Remove riscv git submodule. --- .gitmodules | 3 --- README.md | 2 +- crate/riscv | 1 - kernel/Cargo.lock | 4 +++- kernel/Cargo.toml | 2 +- kernel/riscv32-blog_os.json | 9 ++++----- user/riscv32-ucore.json | 9 ++------- 7 files changed, 11 insertions(+), 19 deletions(-) delete mode 100644 .gitmodules delete mode 160000 crate/riscv diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index d697db22..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "crate/riscv"] - path = crate/riscv - url = https://github.com/riscv-and-rust-and-decaf/riscv.git diff --git a/README.md b/README.md index c919f81e..27462caf 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ cargo install cargo-xbuild bootimage ``` ```bash -git clone https://github.com/wangrunji0408/RustOS.git --recursive +git clone https://github.com/wangrunji0408/RustOS.git cd RustOS/kernel rustup override set nightly-2018-09-18 make run arch=riscv32|x86_64 diff --git a/crate/riscv b/crate/riscv deleted file mode 160000 index f358204a..00000000 --- a/crate/riscv +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f358204af01f2374ab6ed6ea059f724cd5f2fe6f diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index 7e1f1486..1e0ee5c0 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -175,6 +175,7 @@ dependencies = [ [[package]] name = "riscv" version = "0.3.0" +source = "git+https://github.com/riscv-and-rust-and-decaf/riscv#f358204af01f2374ab6ed6ea059f724cd5f2fe6f" dependencies = [ "bare-metal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -265,7 +266,7 @@ dependencies = [ "once 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "raw-cpuid 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "riscv 0.3.0", + "riscv 0.3.0 (git+https://github.com/riscv-and-rust-and-decaf/riscv)", "simple-filesystem 0.0.1 (git+https://github.com/wangrunji0408/SimpleFileSystem-Rust)", "spin 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", "uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -390,6 +391,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum raw-cpuid 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41219962ecab392f1e68db9e7ebd972800d4045a128cc23462b384e8c312cde1" "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" +"checksum riscv 0.3.0 (git+https://github.com/riscv-and-rust-and-decaf/riscv)" = "" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index bbc77038..1c86afd2 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -40,7 +40,7 @@ redox_syscall = "0.1" uart_16550 = "0.1" [target.'cfg(target_arch = "riscv32")'.dependencies] -riscv = { path = "../crate/riscv" } +riscv = { git = "https://github.com/riscv-and-rust-and-decaf/riscv" } bbl = { path = "../crate/bbl" } [package.metadata.bootimage] diff --git a/kernel/riscv32-blog_os.json b/kernel/riscv32-blog_os.json index ab160b8d..33e9156e 100644 --- a/kernel/riscv32-blog_os.json +++ b/kernel/riscv32-blog_os.json @@ -9,12 +9,11 @@ "cpu": "generic-rv32", "features": "", "max-atomic-width": "32", - "linker": "riscv64-unknown-elf-ld", - "linker-flavor": "ld", + "linker": "rust-lld", + "linker-flavor": "ld.lld", "pre-link-args": { - "ld": [ - "-Tsrc/arch/riscv32/boot/linker.ld", - "-melf32lriscv" + "ld.lld": [ + "-Tsrc/arch/riscv32/boot/linker.ld" ] }, "executables": true, diff --git a/user/riscv32-ucore.json b/user/riscv32-ucore.json index 621b0da7..37dac6fe 100644 --- a/user/riscv32-ucore.json +++ b/user/riscv32-ucore.json @@ -9,13 +9,8 @@ "cpu": "generic-rv32", "features": "", "max-atomic-width": "32", - "linker": "riscv64-unknown-elf-ld", - "linker-flavor": "ld", - "pre-link-args": { - "ld": [ - "-melf32lriscv" - ] - }, + "linker": "rust-lld", + "linker-flavor": "ld.lld", "executables": true, "panic-strategy": "abort", "relocation-model": "static", From 250f1385d326dc1014c5e37c30c508ae20ace691 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 31 Oct 2018 11:39:42 +0800 Subject: [PATCH 30/58] Better debug print for TrapFrame on RV32 --- kernel/src/arch/riscv32/context.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/kernel/src/arch/riscv32/context.rs b/kernel/src/arch/riscv32/context.rs index 4a00cfa1..d1a6d61a 100644 --- a/kernel/src/arch/riscv32/context.rs +++ b/kernel/src/arch/riscv32/context.rs @@ -1,6 +1,6 @@ use super::super::riscv::register::*; -#[derive(Debug, Clone)] +#[derive(Clone)] #[repr(C)] pub struct TrapFrame { pub x: [usize; 32], @@ -34,8 +34,29 @@ impl TrapFrame { tf.sstatus.set_spp(sstatus::SPP::User); tf } - pub fn is_user(&self) -> bool { - unimplemented!() +} + +use core::fmt::{Debug, Formatter, Error}; +impl Debug for TrapFrame { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + struct Regs<'a>(&'a [usize; 32]); + impl<'a> Debug for Regs<'a> { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + const REG_NAME: [&str; 32] = [ + "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", + "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", + "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", + "t3", "t4", "t5", "t6"]; + f.debug_map().entries(REG_NAME.iter().zip(self.0)).finish() + } + } + f.debug_struct("TrapFrame") + .field("regs", &Regs(&self.x)) + .field("sstatus", &self.sstatus) + .field("sepc", &self.sepc) + .field("sbadaddr", &self.sbadaddr) + .field("scause", &self.scause) + .finish() } } From 182c595a20eb9bef51eb1bc45140a6fccf8cd103 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 31 Oct 2018 11:45:25 +0800 Subject: [PATCH 31/58] Enable multi-core on RV32. --- kernel/src/arch/riscv32/boot/trap.asm | 5 +++-- kernel/src/arch/riscv32/interrupt.rs | 3 +-- kernel/src/arch/riscv32/mod.rs | 6 ++++-- kernel/src/arch/x86_64/interrupt/handler.rs | 1 - kernel/src/arch/x86_64/mod.rs | 3 +++ kernel/src/lib.rs | 5 ----- kernel/src/trap.rs | 3 --- 7 files changed, 11 insertions(+), 15 deletions(-) diff --git a/kernel/src/arch/riscv32/boot/trap.asm b/kernel/src/arch/riscv32/boot/trap.asm index d4008266..37afaf11 100644 --- a/kernel/src/arch/riscv32/boot/trap.asm +++ b/kernel/src/arch/riscv32/boot/trap.asm @@ -13,7 +13,8 @@ _save_context: # save x registers except x2 (sp) sw x1, 1*4(sp) sw x3, 3*4(sp) - sw x4, 4*4(sp) + # tp(x4) = hartid. DON'T change. + # sw x4, 4*4(sp) sw x5, 5*4(sp) sw x6, 6*4(sp) sw x7, 7*4(sp) @@ -73,7 +74,7 @@ _restore_context: # restore x registers except x2 (sp) lw x1, 1*4(sp) lw x3, 3*4(sp) - lw x4, 4*4(sp) + # lw x4, 4*4(sp) lw x5, 5*4(sp) lw x6, 6*4(sp) lw x7, 7*4(sp) diff --git a/kernel/src/arch/riscv32/interrupt.rs b/kernel/src/arch/riscv32/interrupt.rs index cee4bc17..bad1812e 100644 --- a/kernel/src/arch/riscv32/interrupt.rs +++ b/kernel/src/arch/riscv32/interrupt.rs @@ -42,7 +42,7 @@ pub unsafe fn restore(flags: usize) { #[no_mangle] pub extern fn rust_trap(tf: &mut TrapFrame) { use super::riscv::register::scause::{Trap, Interrupt as I, Exception as E}; - trace!("Interrupt: {:?}", tf.scause.cause()); + trace!("Interrupt @ CPU{}: {:?} ", super::cpu::id(), tf.scause.cause()); match tf.scause.cause() { Trap::Interrupt(I::SupervisorSoft) => ipi(), Trap::Interrupt(I::SupervisorTimer) => timer(), @@ -50,7 +50,6 @@ pub extern fn rust_trap(tf: &mut TrapFrame) { Trap::Exception(E::UserEnvCall) => syscall(tf), _ => ::trap::error(tf), } - ::trap::before_return(); trace!("Interrupt end"); } diff --git a/kernel/src/arch/riscv32/mod.rs b/kernel/src/arch/riscv32/mod.rs index c6ccc1c9..de43a259 100644 --- a/kernel/src/arch/riscv32/mod.rs +++ b/kernel/src/arch/riscv32/mod.rs @@ -26,6 +26,9 @@ pub extern fn rust_main(hartid: usize, dtb: usize, hart_mask: usize) -> ! { memory::init(); timer::init(); + ::process::init(); + ::thread::spawn(::fs::shell); + unsafe { cpu::start_others(hart_mask); } ::kmain(); } @@ -33,8 +36,7 @@ pub extern fn rust_main(hartid: usize, dtb: usize, hart_mask: usize) -> ! { fn others_main() -> ! { interrupt::init(); timer::init(); - cpu::send_ipi(0); - loop { } + ::kmain(); } #[cfg(feature = "no_bbl")] diff --git a/kernel/src/arch/x86_64/interrupt/handler.rs b/kernel/src/arch/x86_64/interrupt/handler.rs index a0369a46..2b8b1889 100644 --- a/kernel/src/arch/x86_64/interrupt/handler.rs +++ b/kernel/src/arch/x86_64/interrupt/handler.rs @@ -97,7 +97,6 @@ pub extern fn rust_trap(tf: &mut TrapFrame) { T_DIVIDE | T_GPFLT | T_ILLOP => error(tf), _ => panic!("Unhandled interrupt {:x}", tf.trap_num), } - ::trap::before_return(); } fn breakpoint() { diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs index b9a57f89..b739925d 100644 --- a/kernel/src/arch/x86_64/mod.rs +++ b/kernel/src/arch/x86_64/mod.rs @@ -47,6 +47,9 @@ pub extern "C" fn _start(boot_info: &'static BootInfo) -> ! { driver::init(); + ::process::init(); + ::thread::spawn(::fs::shell); + AP_CAN_INIT.store(true, Ordering::Relaxed); ::kmain(); diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index b8614ecb..1e5d83cd 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -62,11 +62,6 @@ pub mod arch; pub mod arch; pub fn kmain() -> ! { - if arch::cpu::id() == 0 { - process::init(); - thread::spawn(fs::shell); - } - process::processor().run(); // thread::test::local_key(); diff --git a/kernel/src/trap.rs b/kernel/src/trap.rs index 749a0b26..529fd203 100644 --- a/kernel/src/trap.rs +++ b/kernel/src/trap.rs @@ -11,9 +11,6 @@ pub fn timer() { } } -pub fn before_return() { -} - pub fn error(tf: &TrapFrame) -> ! { error!("{:#x?}", tf); let pid = processor().pid(); From 74facd8e87a0ca86a8aaff87d03561d6ec36b460 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Thu, 1 Nov 2018 00:16:32 +0800 Subject: [PATCH 32/58] Use Vec to replace array in ProcessManager. --- .travis.yml | 2 +- crate/process/src/lib.rs | 1 + crate/process/src/process_manager.rs | 25 ++++++++++++++++--------- kernel/src/process/mod.rs | 11 ++++------- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index d7f343ef..66262b62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ env: install: - if [ $ARCH = riscv32 ]; then - export FILE="riscv64-unknown-elf-gcc-2018.07.0-x86_64-linux-ubuntu14"; + export FILE="riscv64-unknown-elf-gcc-20180928-x86_64-linux-ubuntu14"; wget https://static.dev.sifive.com/dev-tools/$FILE.tar.gz; tar xf $FILE.tar.gz; export PATH=$PATH:$PWD/$FILE/bin; diff --git a/crate/process/src/lib.rs b/crate/process/src/lib.rs index 823c334f..07c2e2c9 100644 --- a/crate/process/src/lib.rs +++ b/crate/process/src/lib.rs @@ -3,6 +3,7 @@ #![feature(const_fn)] #![feature(linkage)] #![feature(nll)] +#![feature(vec_resize_default)] extern crate alloc; #[macro_use] diff --git a/crate/process/src/process_manager.rs b/crate/process/src/process_manager.rs index f4ed2746..beca345d 100644 --- a/crate/process/src/process_manager.rs +++ b/crate/process/src/process_manager.rs @@ -15,7 +15,6 @@ struct Process { pub type Pid = usize; type ExitCode = usize; -const MAX_PROC_NUM: usize = 32; #[derive(Debug, Clone, Eq, PartialEq)] pub enum Status { @@ -36,25 +35,33 @@ pub trait Context { } pub struct ProcessManager { - procs: [Mutex>; MAX_PROC_NUM], + procs: Vec>>, scheduler: Mutex>, - wait_queue: [Mutex>; MAX_PROC_NUM], + wait_queue: Vec>>, event_hub: Mutex>, } impl ProcessManager { - pub fn new(scheduler: Box) -> Self { + pub fn new(scheduler: Box, max_proc_num: usize) -> Self { ProcessManager { - procs: Default::default(), + procs: { + let mut vec = Vec::new(); + vec.resize_default(max_proc_num); + vec + }, scheduler: Mutex::new(scheduler), - wait_queue: Default::default(), + wait_queue: { + let mut vec = Vec::new(); + vec.resize_default(max_proc_num); + vec + }, event_hub: Mutex::new(EventHub::new()), } } fn alloc_pid(&self) -> Pid { - for i in 0..MAX_PROC_NUM { - if self.procs[i].lock().is_none() { + for (i, proc) in self.procs.iter().enumerate() { + if proc.lock().is_none() { return i; } } @@ -64,7 +71,7 @@ impl ProcessManager { /// Add a new process pub fn add(&self, context: Box) -> Pid { let pid = self.alloc_pid(); - *self.procs[pid].lock() = Some(Process { + *(&self.procs[pid]).lock() = Some(Process { id: pid, status: Status::Ready, status_after_stop: Status::Ready, diff --git a/kernel/src/process/mod.rs b/kernel/src/process/mod.rs index 3ec4a43d..5aaa6b37 100644 --- a/kernel/src/process/mod.rs +++ b/kernel/src/process/mod.rs @@ -1,19 +1,16 @@ -use spin::Once; -use sync::{SpinNoIrqLock, Mutex, MutexGuard, SpinNoIrq}; +use spin::Mutex; pub use self::context::ContextImpl; pub use ucore_process::*; -use alloc::boxed::Box; -use consts::MAX_CPU_NUM; +use consts::{MAX_CPU_NUM, MAX_PROCESS_NUM}; use arch::cpu; -use alloc::sync::Arc; -use alloc::vec::Vec; +use alloc::{boxed::Box, sync::Arc, vec::Vec}; mod context; pub fn init() { // NOTE: max_time_slice <= 5 to ensure 'priority' test pass let scheduler = Box::new(scheduler::RRScheduler::new(5)); - let manager = Arc::new(ProcessManager::new(scheduler)); + let manager = Arc::new(ProcessManager::new(scheduler, MAX_PROCESS_NUM)); extern fn idle(_arg: usize) -> ! { loop { cpu::halt(); } From 6fc23e1134f782d7592c135b01a3402133526ff3 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Thu, 1 Nov 2018 00:45:02 +0800 Subject: [PATCH 33/58] Ugly impl sys_wait(0) --- kernel/src/lib.rs | 1 + kernel/src/process/mod.rs | 51 ++++++++++++++++++++++++++++++++++++++ kernel/src/sync/condvar.rs | 3 +++ kernel/src/syscall.rs | 51 ++++++++++++++++++++++++++------------ 4 files changed, 90 insertions(+), 16 deletions(-) diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 1e5d83cd..9c540648 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -10,6 +10,7 @@ #![feature(global_asm)] #![feature(compiler_builtins_lib)] #![feature(raw)] +#![feature(vec_resize_default)] #![no_std] diff --git a/kernel/src/process/mod.rs b/kernel/src/process/mod.rs index 5aaa6b37..85312787 100644 --- a/kernel/src/process/mod.rs +++ b/kernel/src/process/mod.rs @@ -4,6 +4,8 @@ pub use ucore_process::*; use consts::{MAX_CPU_NUM, MAX_PROCESS_NUM}; use arch::cpu; use alloc::{boxed::Box, sync::Arc, vec::Vec}; +use sync::Condvar; +use core::sync::atomic::*; mod context; @@ -29,6 +31,55 @@ pub fn init() { static PROCESSORS: [Processor; MAX_CPU_NUM] = [Processor::new(), Processor::new(), Processor::new(), Processor::new(), Processor::new(), Processor::new(), Processor::new(), Processor::new()]; +/// Ugly solution for sys_wait(0) (wait for any child) +#[derive(Default)] +pub struct Process { + parent: AtomicUsize, + children: Mutex>, + subproc_exit: Condvar, // Trigger parent's when exit +} + +impl Process { + pub fn new_fork(pid: usize, parent: usize) { + PROCESS[pid].parent.store(parent, Ordering::Relaxed); + PROCESS[pid].subproc_exit._clear(); + PROCESS[parent].children.lock().push(pid); + } + pub fn proc_exit(pid: usize) { + let parent = PROCESS[pid].parent.load(Ordering::Relaxed); + PROCESS[parent].subproc_exit.notify_all(); + } + pub fn wait_child() { + Self::current().subproc_exit._wait(); + } + pub fn get_children() -> Vec { + Self::current().children.lock().clone() + } + pub fn do_wait(pid: usize) { + Self::current().children.lock().retain(|&p| p != pid); + } + fn current() -> &'static Self { + &PROCESS[thread::current().id()] + } +} + +lazy_static! { + pub static ref PROCESS: Vec = { + let mut vec = Vec::new(); + vec.resize_default(MAX_PROCESS_NUM); + vec + }; +} + +/// Get current thread struct +pub fn process() -> &'static mut ContextImpl { + use core::mem::transmute; + let (process, _): (&mut ContextImpl, *const ()) = unsafe { + transmute(processor().context()) + }; + process +} + // Implement dependencies for std::thread diff --git a/kernel/src/sync/condvar.rs b/kernel/src/sync/condvar.rs index 5e14d2e6..98d5675f 100644 --- a/kernel/src/sync/condvar.rs +++ b/kernel/src/sync/condvar.rs @@ -33,4 +33,7 @@ impl Condvar { t.unpark(); } } + pub fn _clear(&self) { + self.wait_queue.lock().clear(); + } } \ No newline at end of file diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index f1216887..79c4ea45 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -58,9 +58,9 @@ fn sys_close(fd: usize) -> i32 { /// Fork the current process. Return the child's PID. fn sys_fork(tf: &TrapFrame) -> i32 { - use core::mem::transmute; - let (context, _): (&ContextImpl, *const ()) = unsafe { transmute(processor().context()) }; - let pid = processor().manager().add(context.fork(tf)); + let mut context = process().fork(tf); + let pid = processor().manager().add(context); + Process::new_fork(pid, thread::current().id()); info!("fork: {} -> {}", thread::current().id(), pid); pid as i32 } @@ -68,21 +68,35 @@ fn sys_fork(tf: &TrapFrame) -> i32 { /// Wait the process exit. /// Return the PID. Store exit code to `code` if it's not null. fn sys_wait(pid: usize, code: *mut i32) -> i32 { - assert_ne!(pid, 0, "wait for 0 is not supported yet"); loop { - match processor().manager().get_status(pid) { - Some(Status::Exited(exit_code)) => { - if !code.is_null() { - unsafe { code.write(exit_code as i32); } - } - processor().manager().remove(pid); - return 0; - } - None => return -1, - _ => {} + let wait_procs = match pid { + 0 => Process::get_children(), + _ => vec![pid], + }; + if wait_procs.is_empty() { + return -1; + } + for pid in wait_procs { + match processor().manager().get_status(pid) { + Some(Status::Exited(exit_code)) => { + if !code.is_null() { + unsafe { code.write(exit_code as i32); } + } + processor().manager().remove(pid); + Process::do_wait(pid); + info!("wait: {} -> {}", thread::current().id(), pid); + return 0; + } + None => return -1, + _ => {} + } + } + if pid == 0 { + Process::wait_child(); + } else { + processor().manager().wait(thread::current().id(), pid); + processor().yield_now(); } - processor().manager().wait(thread::current().id(), pid); - processor().yield_now(); } } @@ -94,6 +108,10 @@ fn sys_yield() -> i32 { /// Kill the process fn sys_kill(pid: usize) -> i32 { processor().manager().exit(pid, 0x100); + Process::proc_exit(pid); + if pid == thread::current().id() { + processor().yield_now(); + } 0 } @@ -106,6 +124,7 @@ fn sys_getpid() -> i32 { fn sys_exit(exit_code: usize) -> i32 { let pid = thread::current().id(); processor().manager().exit(pid, exit_code); + Process::proc_exit(pid); processor().yield_now(); unreachable!(); } From ed20aa45fd171ef9e5e81ce08cbe55f2c5739adb Mon Sep 17 00:00:00 2001 From: WangRunji Date: Thu, 1 Nov 2018 19:53:30 +0800 Subject: [PATCH 34/58] Fix user process bug on RV32. Set sstatus.SIE = 0 on the initial TrapFrame, to prevent interrupt on switching. --- kernel/src/arch/riscv32/boot/trap.asm | 2 +- kernel/src/arch/riscv32/context.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/kernel/src/arch/riscv32/boot/trap.asm b/kernel/src/arch/riscv32/boot/trap.asm index 37afaf11..bf4aba54 100644 --- a/kernel/src/arch/riscv32/boot/trap.asm +++ b/kernel/src/arch/riscv32/boot/trap.asm @@ -62,7 +62,7 @@ _save_context: lw s1, 32*4(sp) # s1 = sstatus lw s2, 33*4(sp) # s2 = sepc andi s0, s1, 1 << 8 - bnez s0, _restore_context # back to U-mode? (sstatus.SPP = 1) + bnez s0, _restore_context # back to S-mode? (sstatus.SPP = 1) _save_kernel_sp: addi s0, sp, 36*4 csrw 0x140, s0 # sscratch = kernel-sp diff --git a/kernel/src/arch/riscv32/context.rs b/kernel/src/arch/riscv32/context.rs index d1a6d61a..0d412989 100644 --- a/kernel/src/arch/riscv32/context.rs +++ b/kernel/src/arch/riscv32/context.rs @@ -30,7 +30,8 @@ impl TrapFrame { tf.x[2] = sp; tf.sepc = entry_addr; tf.sstatus = sstatus::read(); - tf.sstatus.set_spie(false); // Enable interrupt + tf.sstatus.set_spie(true); + tf.sstatus.set_sie(false); tf.sstatus.set_spp(sstatus::SPP::User); tf } From b7d6b2989dd7d9587129acbb8fb9d15680c0bbb8 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Thu, 1 Nov 2018 21:10:19 +0800 Subject: [PATCH 35/58] Fix bugs. Pass test 'spin' - Fix ACK IRQ on x86. - Add process exit handler. --- crate/process/src/process_manager.rs | 32 ++++++++++++--------- kernel/src/arch/x86_64/interrupt/handler.rs | 2 +- kernel/src/process/mod.rs | 2 +- kernel/src/syscall.rs | 5 ++-- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/crate/process/src/process_manager.rs b/crate/process/src/process_manager.rs index beca345d..b03bff1e 100644 --- a/crate/process/src/process_manager.rs +++ b/crate/process/src/process_manager.rs @@ -39,23 +39,17 @@ pub struct ProcessManager { scheduler: Mutex>, wait_queue: Vec>>, event_hub: Mutex>, + exit_handler: fn(Pid), } impl ProcessManager { - pub fn new(scheduler: Box, max_proc_num: usize) -> Self { + pub fn new(scheduler: Box, max_proc_num: usize, exit_handler: fn(Pid)) -> Self { ProcessManager { - procs: { - let mut vec = Vec::new(); - vec.resize_default(max_proc_num); - vec - }, + procs: new_vec_default(max_proc_num), scheduler: Mutex::new(scheduler), - wait_queue: { - let mut vec = Vec::new(); - vec.resize_default(max_proc_num); - vec - }, + wait_queue: new_vec_default(max_proc_num), event_hub: Mutex::new(EventHub::new()), + exit_handler, } } @@ -124,7 +118,7 @@ impl ProcessManager { proc.context = Some(context); match proc.status { Status::Ready => self.scheduler.lock().insert(pid), - Status::Exited(_) => proc.context = None, + Status::Exited(_) => self.exit_handler(pid, proc), _ => {} } } @@ -152,7 +146,7 @@ impl ProcessManager { _ => proc.status = status, } match proc.status { - Status::Exited(_) => proc.context = None, + Status::Exited(_) => self.exit_handler(pid, proc), _ => {} } } @@ -189,8 +183,20 @@ impl ProcessManager { pub fn exit(&self, pid: Pid, code: ExitCode) { self.set_status(pid, Status::Exited(code)); + } + + /// Called when a process exit + fn exit_handler(&self, pid: Pid, proc: &mut Process) { for waiter in self.wait_queue[pid].lock().drain(..) { self.wakeup(waiter); } + proc.context = None; + (self.exit_handler)(pid); } } + +fn new_vec_default(size: usize) -> Vec { + let mut vec = Vec::new(); + vec.resize_default(size); + vec +} diff --git a/kernel/src/arch/x86_64/interrupt/handler.rs b/kernel/src/arch/x86_64/interrupt/handler.rs index 2b8b1889..a9e9ecf0 100644 --- a/kernel/src/arch/x86_64/interrupt/handler.rs +++ b/kernel/src/arch/x86_64/interrupt/handler.rs @@ -80,6 +80,7 @@ pub extern fn rust_trap(tf: &mut TrapFrame) { T_PGFLT => page_fault(tf), T_IRQ0...63 => { let irq = tf.trap_num as u8 - T_IRQ0; + super::ack(irq); // must ack before switching match irq { IRQ_TIMER => ::trap::timer(), IRQ_KBD => keyboard(), @@ -88,7 +89,6 @@ pub extern fn rust_trap(tf: &mut TrapFrame) { IRQ_IDE => ide(), _ => panic!("Invalid IRQ number: {}", irq), } - super::ack(irq); } T_SWITCH_TOK => to_kernel(tf), T_SWITCH_TOU => to_user(tf), diff --git a/kernel/src/process/mod.rs b/kernel/src/process/mod.rs index 85312787..c5b3413a 100644 --- a/kernel/src/process/mod.rs +++ b/kernel/src/process/mod.rs @@ -12,7 +12,7 @@ mod context; pub fn init() { // NOTE: max_time_slice <= 5 to ensure 'priority' test pass let scheduler = Box::new(scheduler::RRScheduler::new(5)); - let manager = Arc::new(ProcessManager::new(scheduler, MAX_PROCESS_NUM)); + let manager = Arc::new(ProcessManager::new(scheduler, MAX_PROCESS_NUM, Process::proc_exit)); extern fn idle(_arg: usize) -> ! { loop { cpu::halt(); } diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 79c4ea45..80037c87 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -91,6 +91,7 @@ fn sys_wait(pid: usize, code: *mut i32) -> i32 { _ => {} } } + info!("wait: {} -> {}, sleep", thread::current().id(), pid); if pid == 0 { Process::wait_child(); } else { @@ -107,8 +108,8 @@ fn sys_yield() -> i32 { /// Kill the process fn sys_kill(pid: usize) -> i32 { + info!("kill: {}", pid); processor().manager().exit(pid, 0x100); - Process::proc_exit(pid); if pid == thread::current().id() { processor().yield_now(); } @@ -123,8 +124,8 @@ fn sys_getpid() -> i32 { /// Exit the current process fn sys_exit(exit_code: usize) -> i32 { let pid = thread::current().id(); + info!("exit: {}", pid); processor().manager().exit(pid, exit_code); - Process::proc_exit(pid); processor().yield_now(); unreachable!(); } From 5852881611076f5b77f41c0f8a96b2e343d7e4fe Mon Sep 17 00:00:00 2001 From: WangRunji Date: Thu, 1 Nov 2018 23:45:48 +0800 Subject: [PATCH 36/58] unwrap -> expect --- crate/process/src/process_manager.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crate/process/src/process_manager.rs b/crate/process/src/process_manager.rs index b03bff1e..5ebcda11 100644 --- a/crate/process/src/process_manager.rs +++ b/crate/process/src/process_manager.rs @@ -103,16 +103,16 @@ impl ProcessManager { .expect("failed to select a runnable process"); scheduler.remove(pid); let mut proc_lock = self.procs[pid].lock(); - let mut proc = proc_lock.as_mut().unwrap(); + let mut proc = proc_lock.as_mut().expect("process not exist"); proc.status = Status::Running(cpu_id); - (pid, proc.context.take().unwrap()) + (pid, proc.context.take().expect("context not exist")) } /// Called by Processor to finish running a process /// and give its context back. pub fn stop(&self, pid: Pid, context: Box) { let mut proc_lock = self.procs[pid].lock(); - let mut proc = proc_lock.as_mut().unwrap(); + let mut proc = proc_lock.as_mut().expect("process not exist"); proc.status = proc.status_after_stop.clone(); proc.status_after_stop = Status::Ready; proc.context = Some(context); @@ -128,7 +128,7 @@ impl ProcessManager { fn set_status(&self, pid: Pid, status: Status) { let mut scheduler = self.scheduler.lock(); let mut proc_lock = self.procs[pid].lock(); - let mut proc = proc_lock.as_mut().unwrap(); + let mut proc = proc_lock.as_mut().expect("process not exist"); trace!("process {} {:?} -> {:?}", pid, proc.status, status); match (&proc.status, &status) { (Status::Ready, Status::Ready) => return, @@ -158,7 +158,7 @@ impl ProcessManager { pub fn remove(&self, pid: Pid) { let mut proc_lock = self.procs[pid].lock(); - let proc = proc_lock.as_ref().unwrap(); + let proc = proc_lock.as_ref().expect("process not exist"); match proc.status { Status::Exited(_) => *proc_lock = None, _ => panic!("can not remove non-exited process"), From e5a196c00f37bd1f7e9e4a813869613258ac52c1 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 2 Nov 2018 10:23:40 +0800 Subject: [PATCH 37/58] Fix processor. Disable interrupt on switching. --- crate/process/src/interrupt.rs | 29 +++++++++++++++++++++++++++++ crate/process/src/lib.rs | 2 ++ crate/process/src/processor.rs | 4 ++++ 3 files changed, 35 insertions(+) create mode 100644 crate/process/src/interrupt.rs diff --git a/crate/process/src/interrupt.rs b/crate/process/src/interrupt.rs new file mode 100644 index 00000000..f75102eb --- /dev/null +++ b/crate/process/src/interrupt.rs @@ -0,0 +1,29 @@ +#[inline(always)] +#[cfg(target_arch = "x86_64")] +pub unsafe fn disable_and_store() -> usize { + let rflags: usize; + asm!("pushfq; popq $0; cli" : "=r"(rflags)); + rflags & (1 << 9) +} + +#[inline(always)] +#[cfg(target_arch = "riscv32")] +pub unsafe fn disable_and_store() -> usize { + let sstatus: usize; + asm!("csrrci $0, 0x100, 1" : "=r"(sstatus)); + sstatus & 1 +} + +#[inline(always)] +#[cfg(target_arch = "x86_64")] +pub unsafe fn restore(flags: usize) { + if flags != 0 { + asm!("sti"); + } +} + +#[inline(always)] +#[cfg(target_arch = "riscv32")] +pub unsafe fn restore(flags: usize) { + asm!("csrs 0x100, $0" :: "r"(flags)); +} \ No newline at end of file diff --git a/crate/process/src/lib.rs b/crate/process/src/lib.rs index 07c2e2c9..feb5f637 100644 --- a/crate/process/src/lib.rs +++ b/crate/process/src/lib.rs @@ -4,6 +4,7 @@ #![feature(linkage)] #![feature(nll)] #![feature(vec_resize_default)] +#![feature(asm)] extern crate alloc; #[macro_use] @@ -20,6 +21,7 @@ mod processor; pub mod scheduler; pub mod thread; mod event_hub; +mod interrupt; pub use process_manager::*; pub use processor::Processor; diff --git a/crate/process/src/processor.rs b/crate/process/src/processor.rs index c1432caa..f0b3a4e7 100644 --- a/crate/process/src/processor.rs +++ b/crate/process/src/processor.rs @@ -3,6 +3,7 @@ use alloc::sync::Arc; use spin::Mutex; use core::cell::UnsafeCell; use process_manager::*; +use interrupt; /// Process executor /// @@ -52,6 +53,7 @@ impl Processor { /// via switch back to the scheduler. pub fn run(&self) -> ! { let inner = self.inner(); + unsafe { interrupt::disable_and_store(); } loop { let proc = inner.manager.run(inner.id); trace!("CPU{} begin running process {}", inner.id, proc.0); @@ -70,7 +72,9 @@ impl Processor { pub fn yield_now(&self) { let inner = self.inner(); unsafe { + let flags = interrupt::disable_and_store(); inner.proc.as_mut().unwrap().1.switch_to(&mut *inner.loop_context); + interrupt::restore(flags); } } From ba4a24ba3be63cb1bacfa2f4e7bb7b4a501f8774 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 2 Nov 2018 16:09:39 +0800 Subject: [PATCH 38/58] Fix RV32 multi-core: Setup page table for other cores. --- kernel/src/arch/riscv32/memory.rs | 17 ++++++++++++++--- kernel/src/arch/riscv32/mod.rs | 1 + 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/kernel/src/arch/riscv32/memory.rs b/kernel/src/arch/riscv32/memory.rs index 7e0c08cc..bd3be704 100644 --- a/kernel/src/arch/riscv32/memory.rs +++ b/kernel/src/arch/riscv32/memory.rs @@ -1,4 +1,4 @@ -use core::slice; +use core::{slice, mem}; use memory::{active_table, FRAME_ALLOCATOR, init_heap, MemoryArea, MemoryAttr, MemorySet}; use super::riscv::{addr::*, register::sstatus}; use ucore_memory::PAGE_SIZE; @@ -16,6 +16,13 @@ pub fn init() { remap_the_kernel(); } +pub fn init_other() { + unsafe { + sstatus::set_sum(); // Allow user memory access + asm!("csrw 0x180, $0; sfence.vma" :: "r"(SATP) :: "volatile"); + } +} + fn init_frame_allocator() { use bit_allocator::BitAlloc; use core::ops::Range; @@ -42,11 +49,15 @@ fn remap_the_kernel() { ms.push(MemoryArea::new_identity(srodata as usize, erodata as usize, MemoryAttr::default().readonly(), "rodata")); ms.push(MemoryArea::new_identity(sbss as usize, ebss as usize, MemoryAttr::default(), "bss")); unsafe { ms.activate(); } - use core::mem::forget; - forget(ms); + unsafe { SATP = ms.token(); } + mem::forget(ms); info!("kernel remap end"); } +// First core stores its SATP here. +// Other cores load it later. +static mut SATP: usize = 0; + // Symbols provided by linker script extern { fn stext(); diff --git a/kernel/src/arch/riscv32/mod.rs b/kernel/src/arch/riscv32/mod.rs index de43a259..963faeae 100644 --- a/kernel/src/arch/riscv32/mod.rs +++ b/kernel/src/arch/riscv32/mod.rs @@ -35,6 +35,7 @@ pub extern fn rust_main(hartid: usize, dtb: usize, hart_mask: usize) -> ! { fn others_main() -> ! { interrupt::init(); + memory::init_other(); timer::init(); ::kmain(); } From 0a6b4fb8f2ba81b238abb1364ad6835f0d134a1a Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 2 Nov 2018 10:54:08 +0800 Subject: [PATCH 39/58] Modify const. Update cargo. --- kernel/Cargo.lock | 29 ++++++++++++++++------------- kernel/src/consts.rs | 2 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index 1e0ee5c0..485a52c2 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -10,8 +10,11 @@ dependencies = [ [[package]] name = "bare-metal" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "bbl" @@ -96,7 +99,7 @@ name = "lazy_static" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "spin 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -110,12 +113,12 @@ name = "linked_list_allocator" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "spin 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "log" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -177,7 +180,7 @@ name = "riscv" version = "0.3.0" source = "git+https://github.com/riscv-and-rust-and-decaf/riscv#f358204af01f2374ab6ed6ea059f724cd5f2fe6f" dependencies = [ - "bare-metal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "bare-metal 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -223,7 +226,7 @@ dependencies = [ [[package]] name = "spin" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -262,13 +265,13 @@ dependencies = [ "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "linked_list_allocator 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "once 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "raw-cpuid 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", "riscv 0.3.0 (git+https://github.com/riscv-and-rust-and-decaf/riscv)", "simple-filesystem 0.0.1 (git+https://github.com/wangrunji0408/SimpleFileSystem-Rust)", - "spin 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "ucore-memory 0.1.0", "ucore-process 0.1.0", @@ -285,8 +288,8 @@ version = "0.1.0" name = "ucore-process" version = "0.1.0" dependencies = [ - "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "spin 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -369,7 +372,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum apic 0.1.0 (git+https://github.com/wangrunji0408/APIC-Rust)" = "" -"checksum bare-metal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1bdcf9294ed648c7cd29b11db06ea244005aeef50ae8f605b1a3af2940bf8f92" +"checksum bare-metal 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a3caf393d93b2d453e80638d0674597020cef3382ada454faacd43d1a55a735a" "checksum bit-vec 0.5.0 (git+https://github.com/AltSysrq/bit-vec.git)" = "" "checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" @@ -383,7 +386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" "checksum linked_list_allocator 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "655d57c71827fe0891ce72231b6aa5e14033dae3f604609e6a6f807267c1678d" -"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f" +"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum once 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "931fb7a4cf34610cf6cbe58d52a8ca5ef4c726d4e2e178abd0dc13a6551c6d73" "checksum os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "66481dbeb5e773e7bd85b63cd6042c30786f834338288c5ec4f3742673db360a" "checksum pulldown-cmark 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8361e81576d2e02643b04950e487ec172b687180da65c731c03cf336784e6c07" @@ -397,7 +400,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum simple-filesystem 0.0.1 (git+https://github.com/wangrunji0408/SimpleFileSystem-Rust)" = "" "checksum skeptic 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "061203a849117b0f7090baf8157aa91dac30545208fbb85166ac58b4ca33d89c" -"checksum spin 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "37b5646825922b96b5d7d676b5bb3458a54498e96ed7b0ce09dc43a07038fea4" +"checksum spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f" "checksum static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5" "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" "checksum uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "269f953d8de3226f7c065c589c7b4a3e83d10a419c7c3b5e2e0f197e6acc966e" diff --git a/kernel/src/consts.rs b/kernel/src/consts.rs index ea22abd7..8c25dd55 100644 --- a/kernel/src/consts.rs +++ b/kernel/src/consts.rs @@ -3,4 +3,4 @@ pub use arch::consts::*; pub const MAX_CPU_NUM: usize = 8; -pub const MAX_PROCESS_NUM: usize = 48; +pub const MAX_PROCESS_NUM: usize = 128; From 16fb733497522e1f8a5f3e6183c21629679f9e19 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 2 Nov 2018 16:36:46 +0800 Subject: [PATCH 40/58] Blocking getchar --- crate/process/src/thread.rs | 8 ++--- kernel/src/arch/riscv32/interrupt.rs | 7 ++++ kernel/src/arch/x86_64/driver/serial.rs | 1 - kernel/src/arch/x86_64/interrupt/handler.rs | 1 + kernel/src/console.rs | 38 +++++++++++++++++++-- kernel/src/trap.rs | 6 +++- riscv-pk/machine/uart16550.c | 2 ++ 7 files changed, 54 insertions(+), 9 deletions(-) diff --git a/crate/process/src/thread.rs b/crate/process/src/thread.rs index 1c8b1ade..7ffa0e6b 100644 --- a/crate/process/src/thread.rs +++ b/crate/process/src/thread.rs @@ -40,7 +40,7 @@ pub fn current() -> Thread { /// Puts the current thread to sleep for the specified amount of time. pub fn sleep(dur: Duration) { let time = dur_to_ticks(dur); - info!("sleep: {:?} ticks", time); + trace!("sleep: {:?} ticks", time); processor().manager().sleep(current().id(), time); park(); @@ -58,7 +58,7 @@ pub fn spawn(f: F) -> JoinHandle F: Send + 'static + FnOnce() -> T, T: Send + 'static, { - info!("spawn:"); + trace!("spawn:"); // 注意到下面的问题: // Processor只能从入口地址entry+参数arg创建新线程 @@ -108,13 +108,13 @@ pub fn spawn(f: F) -> JoinHandle /// Cooperatively gives up a timeslice to the OS scheduler. pub fn yield_now() { - info!("yield:"); + trace!("yield:"); processor().yield_now(); } /// Blocks unless or until the current thread's token is made available. pub fn park() { - info!("park:"); + trace!("park:"); processor().manager().sleep(current().id(), 0); processor().yield_now(); } diff --git a/kernel/src/arch/riscv32/interrupt.rs b/kernel/src/arch/riscv32/interrupt.rs index bad1812e..2229845d 100644 --- a/kernel/src/arch/riscv32/interrupt.rs +++ b/kernel/src/arch/riscv32/interrupt.rs @@ -16,6 +16,8 @@ pub fn init() { stvec::write(__alltraps as usize, stvec::TrapMode::Direct); // Enable IPI sie::set_ssoft(); + // Enable serial interrupt + sie::set_sext(); } info!("interrupt: init end"); } @@ -44,6 +46,7 @@ pub extern fn rust_trap(tf: &mut TrapFrame) { use super::riscv::register::scause::{Trap, Interrupt as I, Exception as E}; trace!("Interrupt @ CPU{}: {:?} ", super::cpu::id(), tf.scause.cause()); match tf.scause.cause() { + Trap::Interrupt(I::SupervisorExternal) => serial(), Trap::Interrupt(I::SupervisorSoft) => ipi(), Trap::Interrupt(I::SupervisorTimer) => timer(), Trap::Exception(E::IllegalInstruction) => illegal_inst(tf), @@ -53,6 +56,10 @@ pub extern fn rust_trap(tf: &mut TrapFrame) { trace!("Interrupt end"); } +fn serial() { + ::trap::serial(super::io::getchar()); +} + fn ipi() { debug!("IPI"); super::bbl::sbi::clear_ipi(); diff --git a/kernel/src/arch/x86_64/driver/serial.rs b/kernel/src/arch/x86_64/driver/serial.rs index 182e7339..fb398993 100644 --- a/kernel/src/arch/x86_64/driver/serial.rs +++ b/kernel/src/arch/x86_64/driver/serial.rs @@ -29,7 +29,6 @@ impl SerialRead for SerialPort { let ports = self as *mut _ as *mut [Pio; 6]; let line_sts = &(*ports)[5]; let data = &(*ports)[0]; - while line_sts.read() & 1 != 1 {} data.read() } } diff --git a/kernel/src/arch/x86_64/interrupt/handler.rs b/kernel/src/arch/x86_64/interrupt/handler.rs index a9e9ecf0..7c727d7b 100644 --- a/kernel/src/arch/x86_64/interrupt/handler.rs +++ b/kernel/src/arch/x86_64/interrupt/handler.rs @@ -131,6 +131,7 @@ fn keyboard() { fn com1() { use arch::driver::serial::*; trace!("\nInterupt: COM1"); + ::trap::serial(COM1.lock().receive() as char); } fn com2() { diff --git a/kernel/src/console.rs b/kernel/src/console.rs index 9f6efbd7..178b2680 100644 --- a/kernel/src/console.rs +++ b/kernel/src/console.rs @@ -1,11 +1,13 @@ use core::ops::Deref; use alloc::string::String; -use arch::io::getchar; +use alloc::collections::VecDeque; +use sync::Condvar; +use sync::SpinNoIrqLock as Mutex; pub fn get_line() -> String { let mut s = String::new(); loop { - let c = getchar(); + let c = get_char(); match c { '\u{7f}' /* '\b' */ => { if s.pop().is_some() { @@ -23,4 +25,34 @@ pub fn get_line() -> String { _ => {} } } -} \ No newline at end of file +} + +#[derive(Default)] +pub struct InputQueue { + buf: Mutex>, + pushed: Condvar, +} + +impl InputQueue { + pub fn push(&self, c: char) { + self.buf.lock().push_back(c); + self.pushed.notify_one(); + } + pub fn pop(&self) -> char { + loop { + let ret = self.buf.lock().pop_front(); + match ret { + Some(c) => return c, + None => self.pushed._wait(), + } + } + } +} + +lazy_static! { + pub static ref CONSOLE_INPUT: InputQueue = InputQueue::default(); +} + +pub fn get_char() -> char { + CONSOLE_INPUT.pop() +} diff --git a/kernel/src/trap.rs b/kernel/src/trap.rs index 529fd203..acfd17ab 100644 --- a/kernel/src/trap.rs +++ b/kernel/src/trap.rs @@ -5,10 +5,10 @@ use arch::cpu; pub static mut TICK: usize = 0; pub fn timer() { - processor().tick(); if cpu::id() == 0 { unsafe { TICK += 1; } } + processor().tick(); } pub fn error(tf: &TrapFrame) -> ! { @@ -19,4 +19,8 @@ pub fn error(tf: &TrapFrame) -> ! { processor().manager().exit(pid, 0x100); processor().yield_now(); unreachable!(); +} + +pub fn serial(c: char) { + ::console::CONSOLE_INPUT.push(c); } \ No newline at end of file diff --git a/riscv-pk/machine/uart16550.c b/riscv-pk/machine/uart16550.c index fe1ba99a..93fbb43a 100644 --- a/riscv-pk/machine/uart16550.c +++ b/riscv-pk/machine/uart16550.c @@ -57,6 +57,8 @@ static void uart16550_done(const struct fdt_scan_node *node, void *extra) uart16550[1] = 0x00; // (hi byte) uart16550[3] = 0x03; // 8 bits, no parity, one stop bit uart16550[2] = 0xC7; // Enable FIFO, clear them, with 14-byte threshold + uart16550[4] = 0x0B; + uart16550[1] = 0x01; // Enable interrupt } void query_uart16550(uintptr_t fdt) From 91bd411a8f0ceb6af7b98aac0f0194fc428c32d0 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 7 Nov 2018 02:00:40 +0800 Subject: [PATCH 41/58] fit for multi-thread sfs - use global root inode - remove global IDE on x86 --- kernel/Cargo.lock | 7 ++-- kernel/Cargo.toml | 2 +- kernel/src/arch/x86_64/driver/ide.rs | 7 ---- kernel/src/fs.rs | 52 ++++++++++++++++------------ 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index 485a52c2..439601e4 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -209,9 +209,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "simple-filesystem" version = "0.0.1" -source = "git+https://github.com/wangrunji0408/SimpleFileSystem-Rust#e26f14c55f2b5e767ac7055bab874d039fbe1f05" +source = "git+https://github.com/wangrunji0408/SimpleFileSystem-Rust?branch=multi-thread#d75aab77d685791d6d919f901cc8ec8f29075ff6" dependencies = [ "bit-vec 0.5.0 (git+https://github.com/AltSysrq/bit-vec.git)", + "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -270,7 +271,7 @@ dependencies = [ "raw-cpuid 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", "riscv 0.3.0 (git+https://github.com/riscv-and-rust-and-decaf/riscv)", - "simple-filesystem 0.0.1 (git+https://github.com/wangrunji0408/SimpleFileSystem-Rust)", + "simple-filesystem 0.0.1 (git+https://github.com/wangrunji0408/SimpleFileSystem-Rust?branch=multi-thread)", "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "ucore-memory 0.1.0", @@ -398,7 +399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum simple-filesystem 0.0.1 (git+https://github.com/wangrunji0408/SimpleFileSystem-Rust)" = "" +"checksum simple-filesystem 0.0.1 (git+https://github.com/wangrunji0408/SimpleFileSystem-Rust?branch=multi-thread)" = "" "checksum skeptic 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "061203a849117b0f7090baf8157aa91dac30545208fbb85166ac58b4ca33d89c" "checksum spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f" "checksum static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 1c86afd2..eb1b2f2f 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -27,7 +27,7 @@ lazy_static = { version = "1.0.0", features = ["spin_no_std"] } bit-allocator = { path = "../crate/bit-allocator" } ucore-memory = { path = "../crate/memory" } ucore-process = { path = "../crate/process" } -simple-filesystem = { git = "https://github.com/wangrunji0408/SimpleFileSystem-Rust" } +simple-filesystem = { git = "https://github.com/wangrunji0408/SimpleFileSystem-Rust", branch = "multi-thread" } [target.'cfg(target_arch = "x86_64")'.dependencies] bootloader = { git = "https://github.com/wangrunji0408/bootloader" } diff --git a/kernel/src/arch/x86_64/driver/ide.rs b/kernel/src/arch/x86_64/driver/ide.rs index 356978ef..6b26feb7 100644 --- a/kernel/src/arch/x86_64/driver/ide.rs +++ b/kernel/src/arch/x86_64/driver/ide.rs @@ -2,16 +2,9 @@ //! //! Borrow from Rucore project. Thanks GWord! //! Port from ucore C code. -use spin::Mutex; -lazy_static! { - pub static ref DISK0: LockedIde = LockedIde(Mutex::new(IDE::new(0))); - pub static ref DISK1: LockedIde = LockedIde(Mutex::new(IDE::new(1))); -} pub const BLOCK_SIZE: usize = 512; -pub struct LockedIde(pub Mutex); - pub struct IDE { num: u8, /// I/O Base diff --git a/kernel/src/fs.rs b/kernel/src/fs.rs index f1db3834..3a946a55 100644 --- a/kernel/src/fs.rs +++ b/kernel/src/fs.rs @@ -1,7 +1,7 @@ use simple_filesystem::*; -use alloc::boxed::Box; +use alloc::{boxed::Box, sync::Arc}; #[cfg(target_arch = "x86_64")] -use arch::driver::ide; +use arch::driver::ide::IDE; use spin::Mutex; // Hard link user program @@ -9,25 +9,33 @@ use spin::Mutex; global_asm!(r#" .section .rodata .align 12 -_binary_user_riscv_img_start: + .global _user_img_start + .global _user_img_end +_user_img_start: .incbin "../user/user-riscv.img" -_binary_user_riscv_img_end: +_user_img_end: "#); -pub fn shell() { - #[cfg(target_arch = "riscv32")] - let device = { - extern { - fn _binary_user_riscv_img_start(); - fn _binary_user_riscv_img_end(); - } - Box::new(unsafe { MemBuf::new(_binary_user_riscv_img_start, _binary_user_riscv_img_end) }) +lazy_static! { + static ref ROOT_INODE: Arc = { + #[cfg(target_arch = "riscv32")] + let device = { + extern { + fn _user_img_start(); + fn _user_img_end(); + } + Box::new(unsafe { MemBuf::new(_user_img_start, _user_img_end) }) + }; + #[cfg(target_arch = "x86_64")] + let device = Box::new(IDE::new(1)); + + let sfs = SimpleFileSystem::open(device).expect("failed to open SFS"); + sfs.root_inode() }; - #[cfg(target_arch = "x86_64")] - let device = Box::new(&ide::DISK1); - let sfs = SimpleFileSystem::open(device).expect("failed to open SFS"); - let root = sfs.root_inode(); - let files = root.borrow().list().unwrap(); +} + +pub fn shell() { + let files = ROOT_INODE.list().unwrap(); println!("Available programs: {:?}", files); // Avoid stack overflow in release mode @@ -43,9 +51,9 @@ pub fn shell() { if name == "" { continue; } - if let Ok(file) = root.borrow().lookup(name.as_str()) { + if let Ok(file) = ROOT_INODE.lookup(name.as_str()) { use process::*; - let len = file.borrow().read_at(0, &mut *buf).unwrap(); + let len = file.read_at(0, &mut *buf).unwrap(); let pid = processor().manager().add(ContextImpl::new_user(&buf[..len])); processor().manager().wait(thread::current().id(), pid); processor().yield_now(); @@ -80,16 +88,16 @@ impl Device for MemBuf { use core::slice; #[cfg(target_arch = "x86_64")] -impl BlockedDevice for &'static ide::DISK1 { +impl BlockedDevice for IDE { const BLOCK_SIZE_LOG2: u8 = 9; fn read_at(&mut self, block_id: usize, buf: &mut [u8]) -> bool { assert!(buf.len() >= ide::BLOCK_SIZE); let buf = unsafe { slice::from_raw_parts_mut(buf.as_ptr() as *mut u32, ide::BLOCK_SIZE / 4) }; - self.0.lock().read(block_id as u64, 1, buf).is_ok() + self.read(block_id as u64, 1, buf).is_ok() } fn write_at(&mut self, block_id: usize, buf: &[u8]) -> bool { assert!(buf.len() >= ide::BLOCK_SIZE); let buf = unsafe { slice::from_raw_parts(buf.as_ptr() as *mut u32, ide::BLOCK_SIZE / 4) }; - self.0.lock().write(block_id as u64, 1, buf).is_ok() + self.write(block_id as u64, 1, buf).is_ok() } } \ No newline at end of file From 200a574a1f09eb2293dee16dffc83b7877231ea1 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 7 Nov 2018 13:16:52 +0800 Subject: [PATCH 42/58] fix physical memory range on RV32 --- kernel/src/arch/riscv32/consts.rs | 1 - kernel/src/arch/riscv32/memory.rs | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/kernel/src/arch/riscv32/consts.rs b/kernel/src/arch/riscv32/consts.rs index d60b6e25..f411a4d9 100644 --- a/kernel/src/arch/riscv32/consts.rs +++ b/kernel/src/arch/riscv32/consts.rs @@ -5,7 +5,6 @@ const P2_MASK: usize = 0x3ff << 22; pub const RECURSIVE_INDEX: usize = 0x3fe; pub const KERNEL_OFFSET: usize = 0; pub const KERNEL_P2_INDEX: usize = 0x8000_0000 >> 22; -pub const KERNEL_HEAP_OFFSET: usize = 0x8020_0000; pub const KERNEL_HEAP_SIZE: usize = 0x0020_0000; pub const MEMORY_OFFSET: usize = 0x8000_0000; pub const MEMORY_END: usize = 0x8080_0000; diff --git a/kernel/src/arch/riscv32/memory.rs b/kernel/src/arch/riscv32/memory.rs index bd3be704..882b5428 100644 --- a/kernel/src/arch/riscv32/memory.rs +++ b/kernel/src/arch/riscv32/memory.rs @@ -29,8 +29,7 @@ fn init_frame_allocator() { use consts::{MEMORY_OFFSET, MEMORY_END}; let mut ba = FRAME_ALLOCATOR.lock(); - use consts::{KERNEL_HEAP_OFFSET, KERNEL_HEAP_SIZE}; - ba.insert(to_range(KERNEL_HEAP_OFFSET + KERNEL_HEAP_SIZE, MEMORY_END)); + ba.insert(to_range(end as usize + PAGE_SIZE, MEMORY_END)); info!("FrameAllocator init end"); fn to_range(start: usize, end: usize) -> Range { @@ -41,8 +40,8 @@ fn init_frame_allocator() { } fn remap_the_kernel() { - use consts::{KERNEL_HEAP_OFFSET, KERNEL_HEAP_SIZE}; let mut ms = MemorySet::new_bare(); + #[cfg(feature = "no_bbl")] ms.push(MemoryArea::new_identity(0x10000000, 0x10000008, MemoryAttr::default(), "serial")); ms.push(MemoryArea::new_identity(stext as usize, etext as usize, MemoryAttr::default().execute().readonly(), "text")); ms.push(MemoryArea::new_identity(sdata as usize, edata as usize, MemoryAttr::default(), "data")); From e27aea47e1ff103336582875689f5d79ddd957f4 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 7 Nov 2018 13:34:31 +0800 Subject: [PATCH 43/58] impl file syscalls, without test --- kernel/Cargo.lock | 2 +- kernel/src/fs.rs | 8 +- kernel/src/process/context.rs | 8 +- kernel/src/syscall.rs | 161 +++++++++++++++++++++++----------- 4 files changed, 123 insertions(+), 56 deletions(-) diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index 439601e4..06549839 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -209,7 +209,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "simple-filesystem" version = "0.0.1" -source = "git+https://github.com/wangrunji0408/SimpleFileSystem-Rust?branch=multi-thread#d75aab77d685791d6d919f901cc8ec8f29075ff6" +source = "git+https://github.com/wangrunji0408/SimpleFileSystem-Rust?branch=multi-thread#80d72a9853aa8d64c3dc5f8117fc78a50b6f6ca3" dependencies = [ "bit-vec 0.5.0 (git+https://github.com/AltSysrq/bit-vec.git)", "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/kernel/src/fs.rs b/kernel/src/fs.rs index 3a946a55..63ca9e6d 100644 --- a/kernel/src/fs.rs +++ b/kernel/src/fs.rs @@ -1,7 +1,7 @@ use simple_filesystem::*; use alloc::{boxed::Box, sync::Arc}; #[cfg(target_arch = "x86_64")] -use arch::driver::ide::IDE; +use arch::driver::ide; use spin::Mutex; // Hard link user program @@ -17,7 +17,7 @@ _user_img_end: "#); lazy_static! { - static ref ROOT_INODE: Arc = { + pub static ref ROOT_INODE: Arc = { #[cfg(target_arch = "riscv32")] let device = { extern { @@ -27,7 +27,7 @@ lazy_static! { Box::new(unsafe { MemBuf::new(_user_img_start, _user_img_end) }) }; #[cfg(target_arch = "x86_64")] - let device = Box::new(IDE::new(1)); + let device = Box::new(ide::IDE::new(1)); let sfs = SimpleFileSystem::open(device).expect("failed to open SFS"); sfs.root_inode() @@ -88,7 +88,7 @@ impl Device for MemBuf { use core::slice; #[cfg(target_arch = "x86_64")] -impl BlockedDevice for IDE { +impl BlockedDevice for ide::IDE { const BLOCK_SIZE_LOG2: u8 = 9; fn read_at(&mut self, block_id: usize, buf: &mut [u8]) -> bool { assert!(buf.len() >= ide::BLOCK_SIZE); diff --git a/kernel/src/process/context.rs b/kernel/src/process/context.rs index 95da8426..da29ffb3 100644 --- a/kernel/src/process/context.rs +++ b/kernel/src/process/context.rs @@ -3,12 +3,14 @@ use memory::{MemoryArea, MemoryAttr, MemorySet, KernelStack}; use xmas_elf::{ElfFile, header, program::{Flags, ProgramHeader, Type}}; use core::fmt::{Debug, Error, Formatter}; use ucore_process::Context; -use alloc::boxed::Box; +use simple_filesystem::file::File; +use alloc::{boxed::Box, collections::BTreeMap}; pub struct ContextImpl { arch: ArchContext, memory_set: MemorySet, kstack: KernelStack, + pub files: BTreeMap>, } impl Context for ContextImpl { @@ -25,6 +27,7 @@ impl ContextImpl { arch: ArchContext::null(), memory_set: MemorySet::new(), kstack: KernelStack::new(), + files: BTreeMap::default(), }) } @@ -35,6 +38,7 @@ impl ContextImpl { arch: unsafe { ArchContext::new_kernel_thread(entry, arg, kstack.top(), memory_set.token()) }, memory_set, kstack, + files: BTreeMap::default(), }) } @@ -95,6 +99,7 @@ impl ContextImpl { }, memory_set, kstack, + files: BTreeMap::default(), }) } @@ -124,6 +129,7 @@ impl ContextImpl { arch: unsafe { ArchContext::new_fork(tf, kstack.top(), memory_set.token()) }, memory_set, kstack, + files: BTreeMap::default(), }) } } diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 80037c87..b5b81006 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -1,4 +1,4 @@ -//! 系统调用解析执行模块 +//! System call #![allow(unused)] @@ -6,25 +6,45 @@ use arch::interrupt::TrapFrame; use process::*; use thread; use util; +use simple_filesystem::{INode, file::File}; +use core::{slice, str}; +use alloc::boxed::Box; -/// 系统调用入口点 -/// -/// 当发生系统调用中断时,中断服务例程将控制权转移到这里。 +/// System call dispatcher pub fn syscall(id: usize, args: [usize; 6], tf: &TrapFrame) -> i32 { match id { - SYS_WRITE => sys_write(args[0], args[1] as *const u8, args[2]), - SYS_OPEN => sys_open(args[0] as *const u8, args[1]), - SYS_CLOSE => sys_close(args[0]), - SYS_WAIT => sys_wait(args[0], args[1] as *mut i32), - SYS_FORK => sys_fork(tf), - SYS_KILL => sys_kill(args[0]), - SYS_EXIT => sys_exit(args[0]), - SYS_YIELD => sys_yield(), - SYS_GETPID => sys_getpid(), - SYS_SLEEP => sys_sleep(args[0]), - SYS_GETTIME => sys_get_time(), - SYS_LAB6_SET_PRIORITY => sys_lab6_set_priority(args[0]), - SYS_PUTC => sys_putc(args[0] as u8 as char), + // file + 100 => sys_open(args[0] as *const u8, args[1]), + 101 => sys_close(args[0]), + 102 => sys_read(args[0], args[1] as *mut u8, args[2]), + 103 => sys_write(args[0], args[1] as *const u8, args[2]), + 030 => sys_putc(args[0] as u8 as char), +// 104 => sys_seek(), +// 110 => sys_fstat(), +// 111 => sys_fsync(), +// 121 => sys_getcwd(), +// 128 => sys_getdirentry(), +// 128 => sys_dup(), + + // process + 001 => sys_exit(args[0]), + 002 => sys_fork(tf), + 003 => sys_wait(args[0], args[1] as *mut i32), +// 004 => sys_exec(), +// 005 => sys_clone(), + 010 => sys_yield(), + 011 => sys_sleep(args[0]), + 012 => sys_kill(args[0]), + 017 => sys_get_time(), + 018 => sys_getpid(), + 255 => sys_lab6_set_priority(args[0]), + + // memory +// 020 => sys_mmap(), +// 021 => sys_munmap(), +// 022 => sys_shmem(), +// 031 => sys_pgdir(), + _ => { error!("unknown syscall id: {:#x?}, args: {:x?}", id, args); ::trap::error(tf); @@ -32,28 +52,73 @@ pub fn syscall(id: usize, args: [usize; 6], tf: &TrapFrame) -> i32 { } } +fn sys_read(fd: usize, base: *mut u8, len: usize) -> i32 { + info!("read: fd: {}, base: {:?}, len: {:#x}", fd, base, len); + let slice = unsafe { slice::from_raw_parts_mut(base, len) }; + match fd { + 0 => unimplemented!(), + 1 | 2 => return -1, + _ => { + let mut file = process().files.get_mut(&fd); + if file.is_none() { + return -1; + } + let file = file.as_mut().unwrap(); + file.read(slice).unwrap(); + } + } + 0 +} + fn sys_write(fd: usize, base: *const u8, len: usize) -> i32 { info!("write: fd: {}, base: {:?}, len: {:#x}", fd, base, len); - use core::slice; - use core::str; let slice = unsafe { slice::from_raw_parts(base, len) }; - print!("{}", str::from_utf8(slice).unwrap()); + match fd { + 0 => return -1, + 1 | 2 => print!("{}", str::from_utf8(slice).unwrap()), + _ => { + let mut file = process().files.get_mut(&fd); + if file.is_none() { + return -1; + } + let file = file.as_mut().unwrap(); + file.write(slice).unwrap(); + } + } 0 } fn sys_open(path: *const u8, flags: usize) -> i32 { let path = unsafe { util::from_cstr(path) }; + let flags = VfsFlags::from_ucore_flags(flags); info!("open: path: {:?}, flags: {:?}", path, flags); match path { - "stdin:" => 0, - "stdout:" => 1, - _ => -1, + "stdin:" => return 0, + "stdout:" => return 1, + "stderr:" => return 2, + _ => {} } + let inode = ::fs::ROOT_INODE.lookup(path); + if inode.is_err() { + return -1; + } + let inode = inode.unwrap(); + let files = &mut process().files; + let fd = (3..).find(|i| !files.contains_key(i)).unwrap(); + let file = File::new(inode, flags.contains(VfsFlags::READABLE), flags.contains(VfsFlags::WRITABLE)); + files.insert(fd, Box::new(file)); + fd as i32 } fn sys_close(fd: usize) -> i32 { info!("close: fd: {:?}", fd); - 0 + if fd < 3 { + return 0; + } + match process().files.remove(&fd) { + Some(_) => 0, + None => -1, + } } /// Fork the current process. Return the child's PID. @@ -151,29 +216,25 @@ fn sys_putc(c: char) -> i32 { 0 } -const SYS_EXIT: usize = 1; -const SYS_FORK: usize = 2; -const SYS_WAIT: usize = 3; -const SYS_EXEC: usize = 4; -const SYS_CLONE: usize = 5; -const SYS_YIELD: usize = 10; -const SYS_SLEEP: usize = 11; -const SYS_KILL: usize = 12; -const SYS_GETTIME: usize = 17; -const SYS_GETPID: usize = 18; -const SYS_MMAP: usize = 20; -const SYS_MUNMAP: usize = 21; -const SYS_SHMEM: usize = 22; -const SYS_PUTC: usize = 30; -const SYS_PGDIR: usize = 31; -const SYS_OPEN: usize = 100; -const SYS_CLOSE: usize = 101; -const SYS_READ: usize = 102; -const SYS_WRITE: usize = 103; -const SYS_SEEK: usize = 104; -const SYS_FSTAT: usize = 110; -const SYS_FSYNC: usize = 111; -const SYS_GETCWD: usize = 121; -const SYS_GETDIRENTRY: usize = 128; -const SYS_DUP: usize = 130; -const SYS_LAB6_SET_PRIORITY: usize = 255; +bitflags! { + struct VfsFlags: usize { + // WARNING: different from origin uCore + const READABLE = 1 << 0; + const WRITABLE = 1 << 1; + /// create file if it does not exist + const CREATE = 1 << 2; + /// error if O_CREAT and the file exists + const EXCLUSIVE = 1 << 3; + /// truncate file upon open + const TRUNCATE = 1 << 4; + /// append on each write + const APPEND = 1 << 5; + } +} + +impl VfsFlags { + fn from_ucore_flags(f: usize) -> Self { + assert_ne!(f & 0b11, 0b11); + Self::from_bits_truncate(f + 1) + } +} From 128257c395d818bc6b56e136c7f32ba200ae481f Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 7 Nov 2018 22:52:39 +0800 Subject: [PATCH 44/58] impl argc & argv for user process --- kernel/src/fs.rs | 9 +++---- kernel/src/process/context.rs | 44 ++++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/kernel/src/fs.rs b/kernel/src/fs.rs index 63ca9e6d..b7b85d37 100644 --- a/kernel/src/fs.rs +++ b/kernel/src/fs.rs @@ -47,14 +47,15 @@ pub fn shell() { loop { print!(">> "); use console::get_line; - let name = get_line(); - if name == "" { + let cmd = get_line(); + if cmd == "" { continue; } - if let Ok(file) = ROOT_INODE.lookup(name.as_str()) { + let name = cmd.split(' ').next().unwrap(); + if let Ok(file) = ROOT_INODE.lookup(name) { use process::*; let len = file.read_at(0, &mut *buf).unwrap(); - let pid = processor().manager().add(ContextImpl::new_user(&buf[..len])); + let pid = processor().manager().add(ContextImpl::new_user(&buf[..len], cmd.as_str())); processor().manager().wait(thread::current().id(), pid); processor().yield_now(); } else { diff --git a/kernel/src/process/context.rs b/kernel/src/process/context.rs index da29ffb3..ccc02bb5 100644 --- a/kernel/src/process/context.rs +++ b/kernel/src/process/context.rs @@ -4,7 +4,7 @@ use xmas_elf::{ElfFile, header, program::{Flags, ProgramHeader, Type}}; use core::fmt::{Debug, Error, Formatter}; use ucore_process::Context; use simple_filesystem::file::File; -use alloc::{boxed::Box, collections::BTreeMap}; +use alloc::{boxed::Box, collections::BTreeMap, vec::Vec}; pub struct ContextImpl { arch: ArchContext, @@ -43,7 +43,7 @@ impl ContextImpl { } /// Make a new user thread from ELF data - pub fn new_user(data: &[u8]) -> Box { + pub fn new_user(data: &[u8], cmd: &str) -> Box { // Parse elf let elf = ElfFile::new(data).expect("failed to read elf"); let is32 = match elf.header.pt2 { @@ -54,14 +54,14 @@ impl ContextImpl { // User stack use consts::{USER_STACK_OFFSET, USER_STACK_SIZE, USER32_STACK_OFFSET}; - let (user_stack_buttom, user_stack_top) = match is32 { + let (ustack_buttom, mut ustack_top) = match is32 { true => (USER32_STACK_OFFSET, USER32_STACK_OFFSET + USER_STACK_SIZE), false => (USER_STACK_OFFSET, USER_STACK_OFFSET + USER_STACK_SIZE), }; // Make page table let mut memory_set = memory_set_from(&elf); - memory_set.push(MemoryArea::new(user_stack_buttom, user_stack_top, MemoryAttr::default().user(), "user_stack")); + memory_set.push(MemoryArea::new(ustack_buttom, ustack_top, MemoryAttr::default().user(), "user_stack")); trace!("{:#x?}", memory_set); let entry_addr = elf.header.pt2.entry_point() as usize; @@ -80,13 +80,7 @@ impl ContextImpl { let target = unsafe { slice::from_raw_parts_mut(virt_addr as *mut u8, file_size) }; target.copy_from_slice(&data[offset..offset + file_size]); } - if is32 { - unsafe { - // TODO: full argc & argv - *(user_stack_top as *mut u32).offset(-1) = 0; // argv - *(user_stack_top as *mut u32).offset(-2) = 0; // argc - } - } + ustack_top = push_args_at_stack(cmd, ustack_top); }); } @@ -95,7 +89,7 @@ impl ContextImpl { Box::new(ContextImpl { arch: unsafe { ArchContext::new_user_thread( - entry_addr, user_stack_top - 8, kstack.top(), is32, memory_set.token()) + entry_addr, ustack_top, kstack.top(), is32, memory_set.token()) }, memory_set, kstack, @@ -118,7 +112,7 @@ impl ContextImpl { unsafe { memory_set.with(|| { for (area, data) in memory_set.iter().zip(datas.iter()) { - unsafe { area.as_slice_mut() }.copy_from_slice(data.as_slice()) + area.as_slice_mut().copy_from_slice(data.as_slice()) } }); } @@ -140,6 +134,30 @@ impl Debug for ContextImpl { } } +/// Push a slice at the stack. Return the new sp. +unsafe fn push_slice(mut sp: usize, vs: &[T]) -> usize { + use core::{mem::{size_of, align_of}, slice}; + sp -= vs.len() * size_of::(); + sp -= sp % align_of::(); + slice::from_raw_parts_mut(sp as *mut T, vs.len()) + .copy_from_slice(vs); + sp +} + +unsafe fn push_args_at_stack(cmd: &str, stack_top: usize) -> usize { + use core::{ptr, slice}; + let mut sp = stack_top; + let mut argv = Vec::new(); + for arg in cmd.split(' ') { + sp = push_slice(sp, &[0u8]); + sp = push_slice(sp, arg.as_bytes()); + argv.push(sp); + } + sp = push_slice(sp, argv.as_slice()); + sp = push_slice(sp, &[argv.len()]); + sp +} + fn memory_set_from<'a>(elf: &'a ElfFile<'a>) -> MemorySet { let mut set = MemorySet::new(); for ph in elf.program_iter() { From 16be8283700378d31dc898ce70e87bec077b11f1 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 7 Nov 2018 23:32:22 +0800 Subject: [PATCH 45/58] impl sys_fstat --- kernel/Cargo.lock | 2 +- kernel/src/syscall.rs | 64 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index 06549839..d7155391 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -209,7 +209,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "simple-filesystem" version = "0.0.1" -source = "git+https://github.com/wangrunji0408/SimpleFileSystem-Rust?branch=multi-thread#80d72a9853aa8d64c3dc5f8117fc78a50b6f6ca3" +source = "git+https://github.com/wangrunji0408/SimpleFileSystem-Rust?branch=multi-thread#fe473bc987a9a5dfd7ffe91883f8781b910ed45e" dependencies = [ "bit-vec 0.5.0 (git+https://github.com/AltSysrq/bit-vec.git)", "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index b5b81006..d687602d 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -6,7 +6,7 @@ use arch::interrupt::TrapFrame; use process::*; use thread; use util; -use simple_filesystem::{INode, file::File}; +use simple_filesystem::{INode, file::File, FileInfo, FileType}; use core::{slice, str}; use alloc::boxed::Box; @@ -20,7 +20,7 @@ pub fn syscall(id: usize, args: [usize; 6], tf: &TrapFrame) -> i32 { 103 => sys_write(args[0], args[1] as *const u8, args[2]), 030 => sys_putc(args[0] as u8 as char), // 104 => sys_seek(), -// 110 => sys_fstat(), + 110 => sys_fstat(args[0], args[1] as *mut Stat), // 111 => sys_fsync(), // 121 => sys_getcwd(), // 128 => sys_getdirentry(), @@ -63,7 +63,7 @@ fn sys_read(fd: usize, base: *mut u8, len: usize) -> i32 { if file.is_none() { return -1; } - let file = file.as_mut().unwrap(); + let file = file.unwrap(); file.read(slice).unwrap(); } } @@ -81,7 +81,7 @@ fn sys_write(fd: usize, base: *const u8, len: usize) -> i32 { if file.is_none() { return -1; } - let file = file.as_mut().unwrap(); + let file = file.unwrap(); file.write(slice).unwrap(); } } @@ -121,6 +121,18 @@ fn sys_close(fd: usize) -> i32 { } } +fn sys_fstat(fd: usize, stat_ptr: *mut Stat) -> i32 { + let mut file = process().files.get(&fd); + if file.is_none() { + return -1; + } + let file = file.unwrap(); + let stat = Stat::from(file.info().unwrap()); + // TODO: check ptr + unsafe { stat_ptr.write(stat); } + 0 +} + /// Fork the current process. Return the child's PID. fn sys_fork(tf: &TrapFrame) -> i32 { let mut context = process().fork(tf); @@ -145,6 +157,7 @@ fn sys_wait(pid: usize, code: *mut i32) -> i32 { match processor().manager().get_status(pid) { Some(Status::Exited(exit_code)) => { if !code.is_null() { + // TODO: check ptr unsafe { code.write(exit_code as i32); } } processor().manager().remove(pid); @@ -238,3 +251,46 @@ impl VfsFlags { Self::from_bits_truncate(f + 1) } } + +#[repr(C)] +struct Stat { + /// protection mode and file type + mode: StatMode, + /// number of hard links + nlinks: u32, + /// number of blocks file is using + blocks: u32, + /// file size (bytes) + size: u32, +} + +bitflags! { + struct StatMode: u32 { + const NULL = 0; + /// ordinary regular file + const FILE = 0o10000; + /// directory + const DIR = 0o20000; + /// symbolic link + const LINK = 0o30000; + /// character device + const CHAR = 0o40000; + /// block device + const BLOCK = 0o50000; + } +} + +impl From for Stat { + fn from(info: FileInfo) -> Self { + Stat { + mode: match info.type_ { + FileType::File => StatMode::FILE, + FileType::Dir => StatMode::DIR, + _ => StatMode::NULL, + }, + nlinks: info.nlinks as u32, + blocks: info.blocks as u32, + size: info.size as u32, + } + } +} From 124a5e0d5de2788159c94c0e3134299989e91134 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Thu, 8 Nov 2018 00:21:20 +0800 Subject: [PATCH 46/58] impl sys_dup - Fix exit_code: i32 - Convert Box to Arc> in Context --- kernel/src/process/context.rs | 10 +++++-- kernel/src/syscall.rs | 51 ++++++++++++++++++++++++----------- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/kernel/src/process/context.rs b/kernel/src/process/context.rs index ccc02bb5..61712022 100644 --- a/kernel/src/process/context.rs +++ b/kernel/src/process/context.rs @@ -4,13 +4,15 @@ use xmas_elf::{ElfFile, header, program::{Flags, ProgramHeader, Type}}; use core::fmt::{Debug, Error, Formatter}; use ucore_process::Context; use simple_filesystem::file::File; -use alloc::{boxed::Box, collections::BTreeMap, vec::Vec}; +use alloc::{boxed::Box, collections::BTreeMap, vec::Vec, sync::Arc, string::String}; +use spin::Mutex; pub struct ContextImpl { arch: ArchContext, memory_set: MemorySet, kstack: KernelStack, - pub files: BTreeMap>, + pub files: BTreeMap>>, + pub cwd: String, } impl Context for ContextImpl { @@ -28,6 +30,7 @@ impl ContextImpl { memory_set: MemorySet::new(), kstack: KernelStack::new(), files: BTreeMap::default(), + cwd: String::new(), }) } @@ -39,6 +42,7 @@ impl ContextImpl { memory_set, kstack, files: BTreeMap::default(), + cwd: String::new(), }) } @@ -94,6 +98,7 @@ impl ContextImpl { memory_set, kstack, files: BTreeMap::default(), + cwd: String::new(), }) } @@ -124,6 +129,7 @@ impl ContextImpl { memory_set, kstack, files: BTreeMap::default(), + cwd: String::new(), }) } } diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index d687602d..d9f916cb 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -8,7 +8,8 @@ use thread; use util; use simple_filesystem::{INode, file::File, FileInfo, FileType}; use core::{slice, str}; -use alloc::boxed::Box; +use alloc::sync::Arc; +use spin::Mutex; /// System call dispatcher pub fn syscall(id: usize, args: [usize; 6], tf: &TrapFrame) -> i32 { @@ -23,11 +24,11 @@ pub fn syscall(id: usize, args: [usize; 6], tf: &TrapFrame) -> i32 { 110 => sys_fstat(args[0], args[1] as *mut Stat), // 111 => sys_fsync(), // 121 => sys_getcwd(), -// 128 => sys_getdirentry(), -// 128 => sys_dup(), +// 128 => sys_getdirentry(args[0], args[1]), + 130 => sys_dup(args[0], args[1]), // process - 001 => sys_exit(args[0]), + 001 => sys_exit(args[0] as i32), 002 => sys_fork(tf), 003 => sys_wait(args[0], args[1] as *mut i32), // 004 => sys_exec(), @@ -53,42 +54,45 @@ pub fn syscall(id: usize, args: [usize; 6], tf: &TrapFrame) -> i32 { } fn sys_read(fd: usize, base: *mut u8, len: usize) -> i32 { + // TODO: check ptr info!("read: fd: {}, base: {:?}, len: {:#x}", fd, base, len); let slice = unsafe { slice::from_raw_parts_mut(base, len) }; match fd { 0 => unimplemented!(), 1 | 2 => return -1, _ => { - let mut file = process().files.get_mut(&fd); + let file = process().files.get_mut(&fd); if file.is_none() { return -1; } let file = file.unwrap(); - file.read(slice).unwrap(); + file.lock().read(slice).unwrap(); } } 0 } fn sys_write(fd: usize, base: *const u8, len: usize) -> i32 { + // TODO: check ptr info!("write: fd: {}, base: {:?}, len: {:#x}", fd, base, len); let slice = unsafe { slice::from_raw_parts(base, len) }; match fd { 0 => return -1, 1 | 2 => print!("{}", str::from_utf8(slice).unwrap()), _ => { - let mut file = process().files.get_mut(&fd); + let file = process().files.get_mut(&fd); if file.is_none() { return -1; } let file = file.unwrap(); - file.write(slice).unwrap(); + file.lock().write(slice).unwrap(); } } 0 } fn sys_open(path: *const u8, flags: usize) -> i32 { + // TODO: check ptr let path = unsafe { util::from_cstr(path) }; let flags = VfsFlags::from_ucore_flags(flags); info!("open: path: {:?}, flags: {:?}", path, flags); @@ -106,7 +110,7 @@ fn sys_open(path: *const u8, flags: usize) -> i32 { let files = &mut process().files; let fd = (3..).find(|i| !files.contains_key(i)).unwrap(); let file = File::new(inode, flags.contains(VfsFlags::READABLE), flags.contains(VfsFlags::WRITABLE)); - files.insert(fd, Box::new(file)); + files.insert(fd, Arc::new(Mutex::new(file))); fd as i32 } @@ -122,17 +126,32 @@ fn sys_close(fd: usize) -> i32 { } fn sys_fstat(fd: usize, stat_ptr: *mut Stat) -> i32 { - let mut file = process().files.get(&fd); + // TODO: check ptr + info!("fstat: {}", fd); + let file = process().files.get(&fd); if file.is_none() { return -1; } let file = file.unwrap(); - let stat = Stat::from(file.info().unwrap()); - // TODO: check ptr + let stat = Stat::from(file.lock().info().unwrap()); unsafe { stat_ptr.write(stat); } 0 } +fn sys_dup(fd1: usize, fd2: usize) -> i32 { + info!("dup: {} {}", fd1, fd2); + let file = process().files.get(&fd1); + if file.is_none() { + return -1; + } + let file = file.unwrap(); + if process().files.contains_key(&fd2) { + return -1; + } + process().files.insert(fd2, file.clone()); + 0 +} + /// Fork the current process. Return the child's PID. fn sys_fork(tf: &TrapFrame) -> i32 { let mut context = process().fork(tf); @@ -145,6 +164,7 @@ fn sys_fork(tf: &TrapFrame) -> i32 { /// Wait the process exit. /// Return the PID. Store exit code to `code` if it's not null. fn sys_wait(pid: usize, code: *mut i32) -> i32 { + // TODO: check ptr loop { let wait_procs = match pid { 0 => Process::get_children(), @@ -157,7 +177,6 @@ fn sys_wait(pid: usize, code: *mut i32) -> i32 { match processor().manager().get_status(pid) { Some(Status::Exited(exit_code)) => { if !code.is_null() { - // TODO: check ptr unsafe { code.write(exit_code as i32); } } processor().manager().remove(pid); @@ -200,10 +219,10 @@ fn sys_getpid() -> i32 { } /// Exit the current process -fn sys_exit(exit_code: usize) -> i32 { +fn sys_exit(exit_code: i32) -> i32 { let pid = thread::current().id(); - info!("exit: {}", pid); - processor().manager().exit(pid, exit_code); + info!("exit: {}, code: {}", pid, exit_code); + processor().manager().exit(pid, exit_code as usize); processor().yield_now(); unreachable!(); } From beb653305967b62210eb3fbf635587445b2fbc6f Mon Sep 17 00:00:00 2001 From: WangRunji Date: Thu, 8 Nov 2018 16:56:01 +0800 Subject: [PATCH 47/58] impl sys_getdirentry. 'ls' ok. --- kernel/Cargo.lock | 2 +- kernel/src/syscall.rs | 44 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index d7155391..0e42a9c5 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -209,7 +209,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "simple-filesystem" version = "0.0.1" -source = "git+https://github.com/wangrunji0408/SimpleFileSystem-Rust?branch=multi-thread#fe473bc987a9a5dfd7ffe91883f8781b910ed45e" +source = "git+https://github.com/wangrunji0408/SimpleFileSystem-Rust?branch=multi-thread#978c3a70ca7bd3fdd536cc91b8d2f2f3b2dcb29b" dependencies = [ "bit-vec 0.5.0 (git+https://github.com/AltSysrq/bit-vec.git)", "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index d9f916cb..1d79e66f 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -1,7 +1,5 @@ //! System call -#![allow(unused)] - use arch::interrupt::TrapFrame; use process::*; use thread; @@ -24,7 +22,7 @@ pub fn syscall(id: usize, args: [usize; 6], tf: &TrapFrame) -> i32 { 110 => sys_fstat(args[0], args[1] as *mut Stat), // 111 => sys_fsync(), // 121 => sys_getcwd(), -// 128 => sys_getdirentry(args[0], args[1]), + 128 => sys_getdirentry(args[0], args[1] as *mut DirEntry), 130 => sys_dup(args[0], args[1]), // process @@ -138,6 +136,26 @@ fn sys_fstat(fd: usize, stat_ptr: *mut Stat) -> i32 { 0 } +/// entry_id = dentry.offset / 256 +/// dentry.name = entry_name +/// dentry.offset += 256 +fn sys_getdirentry(fd: usize, dentry_ptr: *mut DirEntry) -> i32 { + // TODO: check ptr + info!("getdirentry: {}", fd); + let file = process().files.get(&fd); + if file.is_none() { return -1; } + let file = file.unwrap(); + let dentry = unsafe { &mut *dentry_ptr }; + if !dentry.check() { return -1; } + let info = file.lock().info().unwrap(); + if info.type_ != FileType::Dir || info.size <= dentry.entry_id() { return -1; } + let name = file.lock().get_entry(dentry.entry_id()); + if name.is_err() { return -1; } + let name = name.unwrap(); + dentry.set_name(name.as_str()); + 0 +} + fn sys_dup(fd1: usize, fd2: usize) -> i32 { info!("dup: {} {}", fd1, fd2); let file = process().files.get(&fd1); @@ -271,6 +289,26 @@ impl VfsFlags { } } +#[repr(C)] +struct DirEntry { + offset: u32, + name: [u8; 256], +} + +impl DirEntry { + fn check(&self) -> bool { + self.offset % 256 == 0 + } + fn entry_id(&self) -> usize { + (self.offset / 256) as usize + } + fn set_name(&mut self, name: &str) { + self.name[..name.len()].copy_from_slice(name.as_bytes()); + self.name[name.len()] = 0; + self.offset += 256; + } +} + #[repr(C)] struct Stat { /// protection mode and file type From a589ae90f33e75400d1ae5a059b15f81e8dbaaec Mon Sep 17 00:00:00 2001 From: WangRunji Date: Thu, 8 Nov 2018 17:32:44 +0800 Subject: [PATCH 48/58] use SysError in syscalls --- kernel/src/syscall.rs | 172 +++++++++++++++++++++--------------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 1d79e66f..61d8d80c 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -11,7 +11,7 @@ use spin::Mutex; /// System call dispatcher pub fn syscall(id: usize, args: [usize; 6], tf: &TrapFrame) -> i32 { - match id { + let ret = match id { // file 100 => sys_open(args[0] as *const u8, args[1]), 101 => sys_close(args[0]), @@ -48,140 +48,121 @@ pub fn syscall(id: usize, args: [usize; 6], tf: &TrapFrame) -> i32 { error!("unknown syscall id: {:#x?}, args: {:x?}", id, args); ::trap::error(tf); } + }; + match ret { + Ok(code) => code, + Err(_) => -1, } } -fn sys_read(fd: usize, base: *mut u8, len: usize) -> i32 { +fn sys_read(fd: usize, base: *mut u8, len: usize) -> SysResult { // TODO: check ptr info!("read: fd: {}, base: {:?}, len: {:#x}", fd, base, len); let slice = unsafe { slice::from_raw_parts_mut(base, len) }; match fd { 0 => unimplemented!(), - 1 | 2 => return -1, - _ => { - let file = process().files.get_mut(&fd); - if file.is_none() { - return -1; - } - let file = file.unwrap(); - file.lock().read(slice).unwrap(); - } + 1 | 2 => return Err(SysError::InvalidFile), + _ => { get_file(fd)?.lock().read(slice)?; } } - 0 + Ok(0) } -fn sys_write(fd: usize, base: *const u8, len: usize) -> i32 { +fn sys_write(fd: usize, base: *const u8, len: usize) -> SysResult { // TODO: check ptr info!("write: fd: {}, base: {:?}, len: {:#x}", fd, base, len); let slice = unsafe { slice::from_raw_parts(base, len) }; match fd { - 0 => return -1, - 1 | 2 => print!("{}", str::from_utf8(slice).unwrap()), - _ => { - let file = process().files.get_mut(&fd); - if file.is_none() { - return -1; - } - let file = file.unwrap(); - file.lock().write(slice).unwrap(); - } + 0 => return Err(SysError::InvalidFile), + 1 | 2 => { + let s = str::from_utf8(slice).map_err(|_| SysError::InvalidArgument)?; + print!("{}", s); + }, + _ => { get_file(fd)?.lock().write(slice)?; } } - 0 + Ok(0) } -fn sys_open(path: *const u8, flags: usize) -> i32 { +fn sys_open(path: *const u8, flags: usize) -> SysResult { // TODO: check ptr let path = unsafe { util::from_cstr(path) }; let flags = VfsFlags::from_ucore_flags(flags); info!("open: path: {:?}, flags: {:?}", path, flags); match path { - "stdin:" => return 0, - "stdout:" => return 1, - "stderr:" => return 2, + "stdin:" => return Ok(0), + "stdout:" => return Ok(1), + "stderr:" => return Ok(2), _ => {} } - let inode = ::fs::ROOT_INODE.lookup(path); - if inode.is_err() { - return -1; - } - let inode = inode.unwrap(); + let inode = ::fs::ROOT_INODE.lookup(path)?; let files = &mut process().files; let fd = (3..).find(|i| !files.contains_key(i)).unwrap(); let file = File::new(inode, flags.contains(VfsFlags::READABLE), flags.contains(VfsFlags::WRITABLE)); files.insert(fd, Arc::new(Mutex::new(file))); - fd as i32 + Ok(fd as i32) } -fn sys_close(fd: usize) -> i32 { +fn sys_close(fd: usize) -> SysResult { info!("close: fd: {:?}", fd); if fd < 3 { - return 0; + return Ok(0); } match process().files.remove(&fd) { - Some(_) => 0, - None => -1, + Some(_) => Ok(0), + None => Err(SysError::InvalidFile), } } -fn sys_fstat(fd: usize, stat_ptr: *mut Stat) -> i32 { +fn sys_fstat(fd: usize, stat_ptr: *mut Stat) -> SysResult { // TODO: check ptr info!("fstat: {}", fd); - let file = process().files.get(&fd); - if file.is_none() { - return -1; - } - let file = file.unwrap(); - let stat = Stat::from(file.lock().info().unwrap()); + let file = get_file(fd)?; + let stat = Stat::from(file.lock().info()?); unsafe { stat_ptr.write(stat); } - 0 + Ok(0) } /// entry_id = dentry.offset / 256 /// dentry.name = entry_name /// dentry.offset += 256 -fn sys_getdirentry(fd: usize, dentry_ptr: *mut DirEntry) -> i32 { +fn sys_getdirentry(fd: usize, dentry_ptr: *mut DirEntry) -> SysResult { // TODO: check ptr info!("getdirentry: {}", fd); - let file = process().files.get(&fd); - if file.is_none() { return -1; } - let file = file.unwrap(); + let file = get_file(fd)?; let dentry = unsafe { &mut *dentry_ptr }; - if !dentry.check() { return -1; } - let info = file.lock().info().unwrap(); - if info.type_ != FileType::Dir || info.size <= dentry.entry_id() { return -1; } - let name = file.lock().get_entry(dentry.entry_id()); - if name.is_err() { return -1; } - let name = name.unwrap(); + if !dentry.check() { + return Err(SysError::InvalidArgument); + } + let info = file.lock().info()?; + if info.type_ != FileType::Dir || info.size <= dentry.entry_id() { + return Err(SysError::InvalidArgument); + } + let name = file.lock().get_entry(dentry.entry_id())?; dentry.set_name(name.as_str()); - 0 + Ok(0) } -fn sys_dup(fd1: usize, fd2: usize) -> i32 { +fn sys_dup(fd1: usize, fd2: usize) -> SysResult { info!("dup: {} {}", fd1, fd2); - let file = process().files.get(&fd1); - if file.is_none() { - return -1; - } - let file = file.unwrap(); + let file = get_file(fd1)?; if process().files.contains_key(&fd2) { - return -1; + return Err(SysError::InvalidFile); } process().files.insert(fd2, file.clone()); - 0 + Ok(0) } /// Fork the current process. Return the child's PID. -fn sys_fork(tf: &TrapFrame) -> i32 { +fn sys_fork(tf: &TrapFrame) -> SysResult { let mut context = process().fork(tf); let pid = processor().manager().add(context); Process::new_fork(pid, thread::current().id()); info!("fork: {} -> {}", thread::current().id(), pid); - pid as i32 + Ok(pid as i32) } /// Wait the process exit. /// Return the PID. Store exit code to `code` if it's not null. -fn sys_wait(pid: usize, code: *mut i32) -> i32 { +fn sys_wait(pid: usize, code: *mut i32) -> SysResult { // TODO: check ptr loop { let wait_procs = match pid { @@ -189,7 +170,7 @@ fn sys_wait(pid: usize, code: *mut i32) -> i32 { _ => vec![pid], }; if wait_procs.is_empty() { - return -1; + return Ok(-1); } for pid in wait_procs { match processor().manager().get_status(pid) { @@ -200,9 +181,9 @@ fn sys_wait(pid: usize, code: *mut i32) -> i32 { processor().manager().remove(pid); Process::do_wait(pid); info!("wait: {} -> {}", thread::current().id(), pid); - return 0; + return Ok(0); } - None => return -1, + None => return Ok(-1), _ => {} } } @@ -216,28 +197,28 @@ fn sys_wait(pid: usize, code: *mut i32) -> i32 { } } -fn sys_yield() -> i32 { +fn sys_yield() -> SysResult { thread::yield_now(); - 0 + Ok(0) } /// Kill the process -fn sys_kill(pid: usize) -> i32 { +fn sys_kill(pid: usize) -> SysResult { info!("kill: {}", pid); processor().manager().exit(pid, 0x100); if pid == thread::current().id() { processor().yield_now(); } - 0 + Ok(0) } /// Get the current process id -fn sys_getpid() -> i32 { - thread::current().id() as i32 +fn sys_getpid() -> SysResult { + Ok(thread::current().id() as i32) } /// Exit the current process -fn sys_exit(exit_code: i32) -> i32 { +fn sys_exit(exit_code: i32) -> SysResult { let pid = thread::current().id(); info!("exit: {}, code: {}", pid, exit_code); processor().manager().exit(pid, exit_code as usize); @@ -245,25 +226,44 @@ fn sys_exit(exit_code: i32) -> i32 { unreachable!(); } -fn sys_sleep(time: usize) -> i32 { +fn sys_sleep(time: usize) -> SysResult { use core::time::Duration; thread::sleep(Duration::from_millis(time as u64 * 10)); - 0 + Ok(0) } -fn sys_get_time() -> i32 { - unsafe { ::trap::TICK as i32 } +fn sys_get_time() -> SysResult { + unsafe { Ok(::trap::TICK as i32) } } -fn sys_lab6_set_priority(priority: usize) -> i32 { +fn sys_lab6_set_priority(priority: usize) -> SysResult { let pid = thread::current().id(); processor().manager().set_priority(pid, priority as u8); - 0 + Ok(0) } -fn sys_putc(c: char) -> i32 { +fn sys_putc(c: char) -> SysResult { print!("{}", c); - 0 + Ok(0) +} + +fn get_file(fd: usize) -> Result<&'static Arc>, SysError> { + process().files.get(&fd).ok_or(SysError::InvalidFile) +} + +pub type SysResult = Result; + +#[repr(i32)] +pub enum SysError { + VfsError, + InvalidFile, + InvalidArgument, +} + +impl From<()> for SysError { + fn from(_: ()) -> Self { + SysError::VfsError + } } bitflags! { From ec0b1973a02cb58d13a0edbeba0c8327ff002623 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Sat, 10 Nov 2018 21:53:07 +0800 Subject: [PATCH 49/58] Update travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 66262b62..dc787f99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ sudo: required language: rust -rust: nightly-2018-09-18 +rust: nightly cache: cargo: true @@ -19,7 +19,7 @@ env: install: - if [ $ARCH = riscv32 ]; then - export FILE="riscv64-unknown-elf-gcc-20180928-x86_64-linux-ubuntu14"; + export FILE="riscv64-unknown-elf-gcc-20181030-x86_64-linux-ubuntu14"; wget https://static.dev.sifive.com/dev-tools/$FILE.tar.gz; tar xf $FILE.tar.gz; export PATH=$PATH:$PWD/$FILE/bin; From f97e8458b8181b5e09f933d034a0188d663dacbc Mon Sep 17 00:00:00 2001 From: WangRunji Date: Sun, 11 Nov 2018 23:08:59 +0800 Subject: [PATCH 50/58] impl stdin & stdout as INode --- kernel/src/console.rs | 30 +----------------- kernel/src/fs.rs | 74 +++++++++++++++++++++++++++++++++++++++++-- kernel/src/logging.rs | 2 +- kernel/src/syscall.rs | 42 ++++++++---------------- kernel/src/trap.rs | 2 +- 5 files changed, 89 insertions(+), 61 deletions(-) diff --git a/kernel/src/console.rs b/kernel/src/console.rs index 178b2680..01798ade 100644 --- a/kernel/src/console.rs +++ b/kernel/src/console.rs @@ -1,8 +1,6 @@ use core::ops::Deref; use alloc::string::String; use alloc::collections::VecDeque; -use sync::Condvar; -use sync::SpinNoIrqLock as Mutex; pub fn get_line() -> String { let mut s = String::new(); @@ -27,32 +25,6 @@ pub fn get_line() -> String { } } -#[derive(Default)] -pub struct InputQueue { - buf: Mutex>, - pushed: Condvar, -} - -impl InputQueue { - pub fn push(&self, c: char) { - self.buf.lock().push_back(c); - self.pushed.notify_one(); - } - pub fn pop(&self) -> char { - loop { - let ret = self.buf.lock().pop_front(); - match ret { - Some(c) => return c, - None => self.pushed._wait(), - } - } - } -} - -lazy_static! { - pub static ref CONSOLE_INPUT: InputQueue = InputQueue::default(); -} - pub fn get_char() -> char { - CONSOLE_INPUT.pop() + ::fs::STDIN.pop() } diff --git a/kernel/src/fs.rs b/kernel/src/fs.rs index b7b85d37..bb8c965d 100644 --- a/kernel/src/fs.rs +++ b/kernel/src/fs.rs @@ -1,8 +1,10 @@ use simple_filesystem::*; -use alloc::{boxed::Box, sync::Arc}; +use alloc::{boxed::Box, sync::Arc, string::String, collections::VecDeque}; #[cfg(target_arch = "x86_64")] use arch::driver::ide; -use spin::Mutex; +use sync::Condvar; +use sync::SpinNoIrqLock as Mutex; +use core::any::Any; // Hard link user program #[cfg(target_arch = "riscv32")] @@ -101,4 +103,72 @@ impl BlockedDevice for ide::IDE { let buf = unsafe { slice::from_raw_parts(buf.as_ptr() as *mut u32, ide::BLOCK_SIZE / 4) }; self.write(block_id as u64, 1, buf).is_ok() } +} + +#[derive(Default)] +pub struct Stdin { + buf: Mutex>, + pushed: Condvar, +} + +impl Stdin { + pub fn push(&self, c: char) { + self.buf.lock().push_back(c); + self.pushed.notify_one(); + } + pub fn pop(&self) -> char { + loop { + let ret = self.buf.lock().pop_front(); + match ret { + Some(c) => return c, + None => self.pushed._wait(), + } + } + } +} + +#[derive(Default)] +pub struct Stdout; + +lazy_static! { + pub static ref STDIN: Arc = Arc::new(Stdin::default()); + pub static ref STDOUT: Arc = Arc::new(Stdout::default()); +} + +// TODO: better way to provide default impl? +macro_rules! impl_inode { + () => { + fn info(&self) -> Result { unimplemented!() } + fn sync(&self) -> Result<()> { unimplemented!() } + fn resize(&self, len: usize) -> Result<()> { unimplemented!() } + fn create(&self, name: &str, type_: FileType) -> Result> { unimplemented!() } + fn unlink(&self, name: &str) -> Result<()> { unimplemented!() } + fn link(&self, name: &str, other: &Arc) -> Result<()> { unimplemented!() } + fn rename(&self, old_name: &str, new_name: &str) -> Result<()> { unimplemented!() } + fn move_(&self, old_name: &str, target: &Arc, new_name: &str) -> Result<()> { unimplemented!() } + fn find(&self, name: &str) -> Result> { unimplemented!() } + fn get_entry(&self, id: usize) -> Result { unimplemented!() } + fn fs(&self) -> Arc { unimplemented!() } + fn as_any_ref(&self) -> &Any { self } + }; +} + +impl INode for Stdin { + fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result { + buf[0] = self.pop() as u8; + Ok(1) + } + fn write_at(&self, offset: usize, buf: &[u8]) -> Result { unimplemented!() } + impl_inode!(); +} + +impl INode for Stdout { + fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result { unimplemented!() } + fn write_at(&self, offset: usize, buf: &[u8]) -> Result { + use core::str; + let s = str::from_utf8(buf).map_err(|_| ())?; + print!("{}", s); + Ok(buf.len()) + } + impl_inode!(); } \ No newline at end of file diff --git a/kernel/src/logging.rs b/kernel/src/logging.rs index b860de67..33edd254 100644 --- a/kernel/src/logging.rs +++ b/kernel/src/logging.rs @@ -78,7 +78,7 @@ impl From for Color { Level::Error => Color::Red, Level::Warn => Color::Yellow, Level::Info => Color::Blue, - Level::Debug => Color::LightRed, + Level::Debug => Color::Green, Level::Trace => Color::DarkGray, } } diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 61d8d80c..6a330e0a 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -59,27 +59,16 @@ fn sys_read(fd: usize, base: *mut u8, len: usize) -> SysResult { // TODO: check ptr info!("read: fd: {}, base: {:?}, len: {:#x}", fd, base, len); let slice = unsafe { slice::from_raw_parts_mut(base, len) }; - match fd { - 0 => unimplemented!(), - 1 | 2 => return Err(SysError::InvalidFile), - _ => { get_file(fd)?.lock().read(slice)?; } - } - Ok(0) + let len = get_file(fd)?.lock().read(slice)?; + Ok(len as i32) } fn sys_write(fd: usize, base: *const u8, len: usize) -> SysResult { // TODO: check ptr info!("write: fd: {}, base: {:?}, len: {:#x}", fd, base, len); let slice = unsafe { slice::from_raw_parts(base, len) }; - match fd { - 0 => return Err(SysError::InvalidFile), - 1 | 2 => { - let s = str::from_utf8(slice).map_err(|_| SysError::InvalidArgument)?; - print!("{}", s); - }, - _ => { get_file(fd)?.lock().write(slice)?; } - } - Ok(0) + let len = get_file(fd)?.lock().write(slice)?; + Ok(len as i32) } fn sys_open(path: *const u8, flags: usize) -> SysResult { @@ -87,25 +76,22 @@ fn sys_open(path: *const u8, flags: usize) -> SysResult { let path = unsafe { util::from_cstr(path) }; let flags = VfsFlags::from_ucore_flags(flags); info!("open: path: {:?}, flags: {:?}", path, flags); - match path { - "stdin:" => return Ok(0), - "stdout:" => return Ok(1), - "stderr:" => return Ok(2), - _ => {} - } - let inode = ::fs::ROOT_INODE.lookup(path)?; - let files = &mut process().files; - let fd = (3..).find(|i| !files.contains_key(i)).unwrap(); + let (fd, inode) = match path { + "stdin:" => (0, ::fs::STDIN.clone() as Arc), + "stdout:" => (1, ::fs::STDOUT.clone() as Arc), + _ => { + let fd = (3..).find(|i| !process().files.contains_key(i)).unwrap(); + let inode = ::fs::ROOT_INODE.lookup(path)?; + (fd, inode) + } + }; let file = File::new(inode, flags.contains(VfsFlags::READABLE), flags.contains(VfsFlags::WRITABLE)); - files.insert(fd, Arc::new(Mutex::new(file))); + process().files.insert(fd, Arc::new(Mutex::new(file))); Ok(fd as i32) } fn sys_close(fd: usize) -> SysResult { info!("close: fd: {:?}", fd); - if fd < 3 { - return Ok(0); - } match process().files.remove(&fd) { Some(_) => Ok(0), None => Err(SysError::InvalidFile), diff --git a/kernel/src/trap.rs b/kernel/src/trap.rs index acfd17ab..99ba7b18 100644 --- a/kernel/src/trap.rs +++ b/kernel/src/trap.rs @@ -22,5 +22,5 @@ pub fn error(tf: &TrapFrame) -> ! { } pub fn serial(c: char) { - ::console::CONSOLE_INPUT.push(c); + ::fs::STDIN.push(c); } \ No newline at end of file From 44bf3fb07a3b6816ee444af5832b2da70f5818a9 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Thu, 15 Nov 2018 23:41:22 +0800 Subject: [PATCH 51/58] impl sys_exec --- kernel/src/arch/riscv32/context.rs | 5 +++ kernel/src/arch/x86_64/interrupt/trapframe.rs | 5 +++ kernel/src/fs.rs | 11 ++--- kernel/src/process/context.rs | 19 +++++---- kernel/src/syscall.rs | 42 ++++++++++++++++++- 5 files changed, 66 insertions(+), 16 deletions(-) diff --git a/kernel/src/arch/riscv32/context.rs b/kernel/src/arch/riscv32/context.rs index 0d412989..3faef9e7 100644 --- a/kernel/src/arch/riscv32/context.rs +++ b/kernel/src/arch/riscv32/context.rs @@ -180,4 +180,9 @@ impl Context { }, }.push_at(kstack_top) } + /// Called at a new user context + /// To get the init TrapFrame in sys_exec + pub unsafe fn get_init_tf(&self) -> TrapFrame { + (*(self.0 as *const InitStack)).tf.clone() + } } \ No newline at end of file diff --git a/kernel/src/arch/x86_64/interrupt/trapframe.rs b/kernel/src/arch/x86_64/interrupt/trapframe.rs index 6f143499..05001343 100644 --- a/kernel/src/arch/x86_64/interrupt/trapframe.rs +++ b/kernel/src/arch/x86_64/interrupt/trapframe.rs @@ -183,4 +183,9 @@ impl Context { }, }.push_at(kstack_top) } + /// Called at a new user context + /// To get the init TrapFrame in sys_exec + pub unsafe fn get_init_tf(&self) -> TrapFrame { + (*(self.0 as *const InitStack)).tf.clone() + } } \ No newline at end of file diff --git a/kernel/src/fs.rs b/kernel/src/fs.rs index bb8c965d..3ffb3e6b 100644 --- a/kernel/src/fs.rs +++ b/kernel/src/fs.rs @@ -40,12 +40,9 @@ pub fn shell() { let files = ROOT_INODE.list().unwrap(); println!("Available programs: {:?}", files); - // Avoid stack overflow in release mode - // Equal to: `buf = Box::new([0; 64 << 12])` - use alloc::alloc::{alloc, dealloc, Layout}; const BUF_SIZE: usize = 0x40000; - let layout = Layout::from_size_align(BUF_SIZE, 0x1000).unwrap(); - let buf = unsafe{ slice::from_raw_parts_mut(alloc(layout), BUF_SIZE) }; + let mut buf = Vec::with_capacity(BUF_SIZE); + unsafe { buf.set_len(BUF_SIZE); } loop { print!(">> "); use console::get_line; @@ -57,14 +54,13 @@ pub fn shell() { if let Ok(file) = ROOT_INODE.lookup(name) { use process::*; let len = file.read_at(0, &mut *buf).unwrap(); - let pid = processor().manager().add(ContextImpl::new_user(&buf[..len], cmd.as_str())); + let pid = processor().manager().add(ContextImpl::new_user(&buf[..len], cmd.split(' '))); processor().manager().wait(thread::current().id(), pid); processor().yield_now(); } else { println!("Program not exist"); } } - unsafe { dealloc(buf.as_mut_ptr(), layout) }; } struct MemBuf(&'static [u8]); @@ -89,6 +85,7 @@ impl Device for MemBuf { } use core::slice; +use alloc::vec::Vec; #[cfg(target_arch = "x86_64")] impl BlockedDevice for ide::IDE { diff --git a/kernel/src/process/context.rs b/kernel/src/process/context.rs index 61712022..a2e9127f 100644 --- a/kernel/src/process/context.rs +++ b/kernel/src/process/context.rs @@ -7,10 +7,11 @@ use simple_filesystem::file::File; use alloc::{boxed::Box, collections::BTreeMap, vec::Vec, sync::Arc, string::String}; use spin::Mutex; +// TODO: avoid pub pub struct ContextImpl { - arch: ArchContext, - memory_set: MemorySet, - kstack: KernelStack, + pub arch: ArchContext, + pub memory_set: MemorySet, + pub kstack: KernelStack, pub files: BTreeMap>>, pub cwd: String, } @@ -47,7 +48,9 @@ impl ContextImpl { } /// Make a new user thread from ELF data - pub fn new_user(data: &[u8], cmd: &str) -> Box { + pub fn new_user<'a, Iter>(data: &[u8], args: Iter) -> Box + where Iter: Iterator + { // Parse elf let elf = ElfFile::new(data).expect("failed to read elf"); let is32 = match elf.header.pt2 { @@ -84,7 +87,7 @@ impl ContextImpl { let target = unsafe { slice::from_raw_parts_mut(virt_addr as *mut u8, file_size) }; target.copy_from_slice(&data[offset..offset + file_size]); } - ustack_top = push_args_at_stack(cmd, ustack_top); + ustack_top = push_args_at_stack(args, ustack_top); }); } @@ -150,11 +153,13 @@ unsafe fn push_slice(mut sp: usize, vs: &[T]) -> usize { sp } -unsafe fn push_args_at_stack(cmd: &str, stack_top: usize) -> usize { +unsafe fn push_args_at_stack<'a, Iter>(args: Iter, stack_top: usize) -> usize + where Iter: Iterator +{ use core::{ptr, slice}; let mut sp = stack_top; let mut argv = Vec::new(); - for arg in cmd.split(' ') { + for arg in args { sp = push_slice(sp, &[0u8]); sp = push_slice(sp, arg.as_bytes()); argv.push(sp); diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 6a330e0a..3d2a7f79 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -8,9 +8,11 @@ use simple_filesystem::{INode, file::File, FileInfo, FileType}; use core::{slice, str}; use alloc::sync::Arc; use spin::Mutex; +use alloc::vec::Vec; +use alloc::string::String; /// System call dispatcher -pub fn syscall(id: usize, args: [usize; 6], tf: &TrapFrame) -> i32 { +pub fn syscall(id: usize, args: [usize; 6], tf: &mut TrapFrame) -> i32 { let ret = match id { // file 100 => sys_open(args[0] as *const u8, args[1]), @@ -29,7 +31,7 @@ pub fn syscall(id: usize, args: [usize; 6], tf: &TrapFrame) -> i32 { 001 => sys_exit(args[0] as i32), 002 => sys_fork(tf), 003 => sys_wait(args[0], args[1] as *mut i32), -// 004 => sys_exec(), + 004 => sys_exec(args[0] as *const u8, args[1] as usize, args[2] as *const *const u8, tf), // 005 => sys_clone(), 010 => sys_yield(), 011 => sys_sleep(args[0]), @@ -183,6 +185,42 @@ fn sys_wait(pid: usize, code: *mut i32) -> SysResult { } } +fn sys_exec(name: *const u8, argc: usize, argv: *const *const u8, tf: &mut TrapFrame) -> SysResult { + // TODO: check ptr + let name = if name.is_null() { "" } else { unsafe { util::from_cstr(name) } }; + info!("exec: {:?}, argc: {}, argv: {:?}", name, argc, argv); + // Copy args to kernel + let args: Vec = unsafe { + slice::from_raw_parts(argv, argc).iter() + .map(|&arg| String::from(util::from_cstr(arg))) + .collect() + }; + + // Read program file + let path = args[0].as_str(); + let inode = ::fs::ROOT_INODE.lookup(path)?; + let size = inode.info()?.size; + let mut buf = Vec::with_capacity(size); + unsafe { buf.set_len(size); } + inode.read_at(0, buf.as_mut_slice())?; + + // Make new Context + let iter = args.iter().map(|s| s.as_str()); + let mut context = ContextImpl::new_user(buf.as_slice(), iter); + + // Activate new page table + unsafe { context.memory_set.activate(); } + + // Modify the TrapFrame + *tf = unsafe { context.arch.get_init_tf() }; + + // Swap Context but keep KStack + ::core::mem::swap(&mut process().kstack, &mut context.kstack); + ::core::mem::swap(process(), &mut *context); + + Ok(0) +} + fn sys_yield() -> SysResult { thread::yield_now(); Ok(0) From 40b099ed79c0a1a1df3fd127a86dd13b0f1b5211 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Thu, 15 Nov 2018 23:42:44 +0800 Subject: [PATCH 52/58] clean riscv-pk in Makefile --- kernel/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/Makefile b/kernel/Makefile index e344ad18..471585c4 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -89,6 +89,7 @@ all: $(kernel) clean: @cargo clean + @rm -rf ../riscv-pk/build doc: @cargo rustdoc -- --document-private-items From 9b3294a5f21583841c1655f22f95e8dc67598392 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 16 Nov 2018 00:17:05 +0800 Subject: [PATCH 53/58] fix clear bss when load user program. 'sh' works! --- kernel/src/process/context.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/kernel/src/process/context.rs b/kernel/src/process/context.rs index a2e9127f..57df1194 100644 --- a/kernel/src/process/context.rs +++ b/kernel/src/process/context.rs @@ -80,12 +80,11 @@ impl ContextImpl { let virt_addr = ph.virtual_addr() as usize; let offset = ph.offset() as usize; let file_size = ph.file_size() as usize; - if file_size == 0 { - return; - } - use core::slice; - let target = unsafe { slice::from_raw_parts_mut(virt_addr as *mut u8, file_size) }; - target.copy_from_slice(&data[offset..offset + file_size]); + let mem_size = ph.mem_size() as usize; + + let target = unsafe { ::core::slice::from_raw_parts_mut(virt_addr as *mut u8, mem_size) }; + target[..file_size].copy_from_slice(&data[offset..offset + file_size]); + target[file_size..].iter_mut().for_each(|x| *x = 0); } ustack_top = push_args_at_stack(args, ustack_top); }); From b3e5d1987e9ec541537a18be926c99a6aa05c58f Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 16 Nov 2018 00:30:15 +0800 Subject: [PATCH 54/58] update Cargo.lock --- kernel/Cargo.lock | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index 0e42a9c5..fb1e8768 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -96,11 +96,10 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -154,7 +153,7 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "6.0.0" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -209,7 +208,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "simple-filesystem" version = "0.0.1" -source = "git+https://github.com/wangrunji0408/SimpleFileSystem-Rust?branch=multi-thread#978c3a70ca7bd3fdd536cc91b8d2f2f3b2dcb29b" +source = "git+https://github.com/wangrunji0408/SimpleFileSystem-Rust?branch=multi-thread#24a51faff266744d83c1ef60328b28906d2b525c" dependencies = [ "bit-vec 0.5.0 (git+https://github.com/AltSysrq/bit-vec.git)", "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -264,11 +263,11 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "bootloader 0.3.1 (git+https://github.com/wangrunji0408/bootloader)", "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "linked_list_allocator 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "once 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "raw-cpuid 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", "riscv 0.3.0 (git+https://github.com/riscv-and-rust-and-decaf/riscv)", "simple-filesystem 0.0.1 (git+https://github.com/wangrunji0408/SimpleFileSystem-Rust?branch=multi-thread)", @@ -308,11 +307,6 @@ name = "ux" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "volatile" version = "0.1.0" @@ -343,7 +337,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "raw-cpuid 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -384,7 +378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797" -"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" +"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" "checksum linked_list_allocator 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "655d57c71827fe0891ce72231b6aa5e14033dae3f604609e6a6f807267c1678d" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" @@ -392,7 +386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "66481dbeb5e773e7bd85b63cd6042c30786f834338288c5ec4f3742673db360a" "checksum pulldown-cmark 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8361e81576d2e02643b04950e487ec172b687180da65c731c03cf336784e6c07" "checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd" -"checksum raw-cpuid 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41219962ecab392f1e68db9e7ebd972800d4045a128cc23462b384e8c312cde1" +"checksum raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "30a9d219c32c9132f7be513c18be77c9881c7107d2ab5569d205a6a0f0e6dc7d" "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" "checksum riscv 0.3.0 (git+https://github.com/riscv-and-rust-and-decaf/riscv)" = "" @@ -408,7 +402,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f70329e2cbe45d6c97a5112daad40c34cd9a4e18edb5a2a18fefeb584d8d25e5" "checksum ux 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53d8df5dd8d07fedccd202de1887d94481fadaea3db70479f459e8163a1fab41" -"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum volatile 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37c5d76c0f40ba4f8ac10ec4717d4e98ce3e58c5607eea36e9464226fc5e0a95" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" From 21b0bdcbca216c6c80a5c65eab4a4d0153bf8e39 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 16 Nov 2018 00:49:42 +0800 Subject: [PATCH 55/58] separate kernel shell to a mod, remove console mod --- kernel/src/arch/riscv32/mod.rs | 2 +- kernel/src/arch/x86_64/mod.rs | 2 +- kernel/src/console.rs | 30 ----------------- kernel/src/fs.rs | 31 +----------------- kernel/src/lib.rs | 2 +- kernel/src/shell.rs | 59 ++++++++++++++++++++++++++++++++++ 6 files changed, 63 insertions(+), 63 deletions(-) delete mode 100644 kernel/src/console.rs create mode 100644 kernel/src/shell.rs diff --git a/kernel/src/arch/riscv32/mod.rs b/kernel/src/arch/riscv32/mod.rs index 963faeae..22c2bf03 100644 --- a/kernel/src/arch/riscv32/mod.rs +++ b/kernel/src/arch/riscv32/mod.rs @@ -27,7 +27,7 @@ pub extern fn rust_main(hartid: usize, dtb: usize, hart_mask: usize) -> ! { timer::init(); ::process::init(); - ::thread::spawn(::fs::shell); + ::thread::spawn(::shell::shell); unsafe { cpu::start_others(hart_mask); } ::kmain(); diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs index b739925d..3f7fda51 100644 --- a/kernel/src/arch/x86_64/mod.rs +++ b/kernel/src/arch/x86_64/mod.rs @@ -48,7 +48,7 @@ pub extern "C" fn _start(boot_info: &'static BootInfo) -> ! { driver::init(); ::process::init(); - ::thread::spawn(::fs::shell); + ::thread::spawn(::shell::shell); AP_CAN_INIT.store(true, Ordering::Relaxed); diff --git a/kernel/src/console.rs b/kernel/src/console.rs deleted file mode 100644 index 01798ade..00000000 --- a/kernel/src/console.rs +++ /dev/null @@ -1,30 +0,0 @@ -use core::ops::Deref; -use alloc::string::String; -use alloc::collections::VecDeque; - -pub fn get_line() -> String { - let mut s = String::new(); - loop { - let c = get_char(); - match c { - '\u{7f}' /* '\b' */ => { - if s.pop().is_some() { - print!("\u{7f}"); - } - } - ' '...'\u{7e}' => { - s.push(c); - print!("{}", c); - } - '\n' | '\r' => { - print!("\n"); - return s; - } - _ => {} - } - } -} - -pub fn get_char() -> char { - ::fs::STDIN.pop() -} diff --git a/kernel/src/fs.rs b/kernel/src/fs.rs index 3ffb3e6b..8ea584f6 100644 --- a/kernel/src/fs.rs +++ b/kernel/src/fs.rs @@ -5,6 +5,7 @@ use arch::driver::ide; use sync::Condvar; use sync::SpinNoIrqLock as Mutex; use core::any::Any; +use core::slice; // Hard link user program #[cfg(target_arch = "riscv32")] @@ -36,33 +37,6 @@ lazy_static! { }; } -pub fn shell() { - let files = ROOT_INODE.list().unwrap(); - println!("Available programs: {:?}", files); - - const BUF_SIZE: usize = 0x40000; - let mut buf = Vec::with_capacity(BUF_SIZE); - unsafe { buf.set_len(BUF_SIZE); } - loop { - print!(">> "); - use console::get_line; - let cmd = get_line(); - if cmd == "" { - continue; - } - let name = cmd.split(' ').next().unwrap(); - if let Ok(file) = ROOT_INODE.lookup(name) { - use process::*; - let len = file.read_at(0, &mut *buf).unwrap(); - let pid = processor().manager().add(ContextImpl::new_user(&buf[..len], cmd.split(' '))); - processor().manager().wait(thread::current().id(), pid); - processor().yield_now(); - } else { - println!("Program not exist"); - } - } -} - struct MemBuf(&'static [u8]); impl MemBuf { @@ -84,9 +58,6 @@ impl Device for MemBuf { } } -use core::slice; -use alloc::vec::Vec; - #[cfg(target_arch = "x86_64")] impl BlockedDevice for ide::IDE { const BLOCK_SIZE_LOG2: u8 = 9; diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 9c540648..1c8ae2f6 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -51,7 +51,7 @@ mod syscall; mod fs; mod sync; mod trap; -mod console; +mod shell; #[allow(dead_code)] #[cfg(target_arch = "x86_64")] diff --git a/kernel/src/shell.rs b/kernel/src/shell.rs new file mode 100644 index 00000000..c227d9f0 --- /dev/null +++ b/kernel/src/shell.rs @@ -0,0 +1,59 @@ +//! Kernel shell + +use alloc::string::String; +use alloc::vec::Vec; +use fs::ROOT_INODE; +use process::*; + + +pub fn shell() { + let files = ROOT_INODE.list().unwrap(); + println!("Available programs: {:?}", files); + + const BUF_SIZE: usize = 0x40000; + let mut buf = Vec::with_capacity(BUF_SIZE); + unsafe { buf.set_len(BUF_SIZE); } + loop { + print!(">> "); + let cmd = get_line(); + if cmd == "" { + continue; + } + let name = cmd.split(' ').next().unwrap(); + if let Ok(file) = ROOT_INODE.lookup(name) { + let len = file.read_at(0, &mut *buf).unwrap(); + let pid = processor().manager().add(ContextImpl::new_user(&buf[..len], cmd.split(' '))); + processor().manager().wait(thread::current().id(), pid); + processor().yield_now(); + } else { + println!("Program not exist"); + } + } +} + +fn get_line() -> String { + let mut s = String::new(); + loop { + let c = get_char(); + match c { + '\u{7f}' /* '\b' */ => { + if s.pop().is_some() { + print!("\u{7f}"); + } + } + ' '...'\u{7e}' => { + s.push(c); + print!("{}", c); + } + '\n' | '\r' => { + print!("\n"); + return s; + } + _ => {} + } + } +} + +fn get_char() -> char { + ::fs::STDIN.pop() +} From 5ce7d0a9c04fc8dc919ea26d8b5fddeb44fdc04e Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 16 Nov 2018 01:22:47 +0800 Subject: [PATCH 56/58] use user shell by default. fix kernel shell removing user thread. --- crate/process/src/thread.rs | 11 ++++++++--- kernel/src/arch/riscv32/mod.rs | 2 +- kernel/src/arch/x86_64/mod.rs | 2 +- kernel/src/fs.rs | 16 +++++++++++++++- kernel/src/shell.rs | 17 +++++++++-------- 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/crate/process/src/thread.rs b/crate/process/src/thread.rs index 7ffa0e6b..e2735df8 100644 --- a/crate/process/src/thread.rs +++ b/crate/process/src/thread.rs @@ -32,9 +32,7 @@ fn new_kernel_context(entry: extern fn(usize) -> !, arg: usize) -> Box /// Gets a handle to the thread that invokes it. pub fn current() -> Thread { - Thread { - pid: processor().pid(), - } + Thread { pid: processor().pid() } } /// Puts the current thread to sleep for the specified amount of time. @@ -162,6 +160,13 @@ impl JoinHandle { processor().yield_now(); } } + /// Force construct a JoinHandle struct + pub unsafe fn _of(pid: Pid) -> JoinHandle { + JoinHandle { + thread: Thread { pid }, + mark: PhantomData, + } + } } //pub struct LocalKey { diff --git a/kernel/src/arch/riscv32/mod.rs b/kernel/src/arch/riscv32/mod.rs index 22c2bf03..ffdb0887 100644 --- a/kernel/src/arch/riscv32/mod.rs +++ b/kernel/src/arch/riscv32/mod.rs @@ -27,7 +27,7 @@ pub extern fn rust_main(hartid: usize, dtb: usize, hart_mask: usize) -> ! { timer::init(); ::process::init(); - ::thread::spawn(::shell::shell); + ::thread::spawn(::shell::run_user_shell); unsafe { cpu::start_others(hart_mask); } ::kmain(); diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs index 3f7fda51..5d03d9ca 100644 --- a/kernel/src/arch/x86_64/mod.rs +++ b/kernel/src/arch/x86_64/mod.rs @@ -48,7 +48,7 @@ pub extern "C" fn _start(boot_info: &'static BootInfo) -> ! { driver::init(); ::process::init(); - ::thread::spawn(::shell::shell); + ::thread::spawn(::shell::run_user_shell); AP_CAN_INIT.store(true, Ordering::Relaxed); diff --git a/kernel/src/fs.rs b/kernel/src/fs.rs index 8ea584f6..b70e89b0 100644 --- a/kernel/src/fs.rs +++ b/kernel/src/fs.rs @@ -1,5 +1,5 @@ use simple_filesystem::*; -use alloc::{boxed::Box, sync::Arc, string::String, collections::VecDeque}; +use alloc::{boxed::Box, sync::Arc, string::String, collections::VecDeque, vec::Vec}; #[cfg(target_arch = "x86_64")] use arch::driver::ide; use sync::Condvar; @@ -139,4 +139,18 @@ impl INode for Stdout { Ok(buf.len()) } impl_inode!(); +} + +pub trait INodeExt { + fn read_as_vec(&self) -> Result>; +} + +impl INodeExt for INode { + fn read_as_vec(&self) -> Result> { + let size = self.info()?.size; + let mut buf = Vec::with_capacity(size); + unsafe { buf.set_len(size); } + self.read_at(0, buf.as_mut_slice())?; + Ok(buf) + } } \ No newline at end of file diff --git a/kernel/src/shell.rs b/kernel/src/shell.rs index c227d9f0..4f487328 100644 --- a/kernel/src/shell.rs +++ b/kernel/src/shell.rs @@ -2,17 +2,19 @@ use alloc::string::String; use alloc::vec::Vec; -use fs::ROOT_INODE; +use fs::{ROOT_INODE, INodeExt}; use process::*; +pub fn run_user_shell() { + let inode = ROOT_INODE.lookup("sh").unwrap(); + let data = inode.read_as_vec().unwrap(); + processor().manager().add(ContextImpl::new_user(data.as_slice(), "sh".split(' '))); +} pub fn shell() { let files = ROOT_INODE.list().unwrap(); println!("Available programs: {:?}", files); - const BUF_SIZE: usize = 0x40000; - let mut buf = Vec::with_capacity(BUF_SIZE); - unsafe { buf.set_len(BUF_SIZE); } loop { print!(">> "); let cmd = get_line(); @@ -21,10 +23,9 @@ pub fn shell() { } let name = cmd.split(' ').next().unwrap(); if let Ok(file) = ROOT_INODE.lookup(name) { - let len = file.read_at(0, &mut *buf).unwrap(); - let pid = processor().manager().add(ContextImpl::new_user(&buf[..len], cmd.split(' '))); - processor().manager().wait(thread::current().id(), pid); - processor().yield_now(); + let data = file.read_as_vec().unwrap(); + let pid = processor().manager().add(ContextImpl::new_user(data.as_slice(), cmd.split(' '))); + unsafe { thread::JoinHandle::<()>::_of(pid) }.join().unwrap(); } else { println!("Program not exist"); } From d27ac65df3a4127c0470da9762d1a6d4729055cd Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 16 Nov 2018 01:33:25 +0800 Subject: [PATCH 57/58] fix load program slice error --- kernel/src/process/context.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/src/process/context.rs b/kernel/src/process/context.rs index 57df1194..e6810b5a 100644 --- a/kernel/src/process/context.rs +++ b/kernel/src/process/context.rs @@ -83,7 +83,9 @@ impl ContextImpl { let mem_size = ph.mem_size() as usize; let target = unsafe { ::core::slice::from_raw_parts_mut(virt_addr as *mut u8, mem_size) }; - target[..file_size].copy_from_slice(&data[offset..offset + file_size]); + if file_size != 0 { + target[..file_size].copy_from_slice(&data[offset..offset + file_size]); + } target[file_size..].iter_mut().for_each(|x| *x = 0); } ustack_top = push_args_at_stack(args, ustack_top); From e06f6b8bc5e7d58d83ee7a14c965b74b49d304a7 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 16 Nov 2018 15:17:25 +0800 Subject: [PATCH 58/58] update Cargo.toml & Cargo.lock --- kernel/Cargo.lock | 39 ++++++++++++++++++++++++++++++++++----- kernel/Cargo.toml | 12 ++++++------ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index fb1e8768..f882af13 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -8,6 +8,14 @@ dependencies = [ "x86 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "array-init" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bare-metal" version = "0.2.4" @@ -44,8 +52,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bootloader" -version = "0.3.1" -source = "git+https://github.com/wangrunji0408/bootloader#b03d826d6591f392cd824bbd350e82b5f17f21f3" +version = "0.3.4" +source = "git+https://github.com/wangrunji0408/bootloader#dd5723c3d7d50856073a5003e0a355ea0fc3d46c" dependencies = [ "apic 0.1.0 (git+https://github.com/wangrunji0408/APIC-Rust)", "fixedvec 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -123,6 +131,11 @@ dependencies = [ "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nodrop" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "once" version = "0.3.3" @@ -261,7 +274,7 @@ dependencies = [ "bit-allocator 0.1.0", "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "bootloader 0.3.1 (git+https://github.com/wangrunji0408/bootloader)", + "bootloader 0.3.4 (git+https://github.com/wangrunji0408/bootloader)", "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "linked_list_allocator 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -276,7 +289,7 @@ dependencies = [ "ucore-memory 0.1.0", "ucore-process 0.1.0", "volatile 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -352,6 +365,19 @@ dependencies = [ "ux 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "x86_64" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "array-init 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ux 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "xmas-elf" version = "0.6.2" @@ -367,11 +393,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum apic 0.1.0 (git+https://github.com/wangrunji0408/APIC-Rust)" = "" +"checksum array-init 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "23589ecb866b460d3a0f1278834750268c607e8e28a1b982c907219f3178cd72" "checksum bare-metal 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a3caf393d93b2d453e80638d0674597020cef3382ada454faacd43d1a55a735a" "checksum bit-vec 0.5.0 (git+https://github.com/AltSysrq/bit-vec.git)" = "" "checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum bootloader 0.3.1 (git+https://github.com/wangrunji0408/bootloader)" = "" +"checksum bootloader 0.3.4 (git+https://github.com/wangrunji0408/bootloader)" = "" "checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" "checksum fixedvec 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c6c16d316ccdac21a4dd648e314e76facbbaf316e83ca137d0857a9c07419d0" @@ -382,6 +409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" "checksum linked_list_allocator 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "655d57c71827fe0891ce72231b6aa5e14033dae3f604609e6a6f807267c1678d" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" +"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum once 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "931fb7a4cf34610cf6cbe58d52a8ca5ef4c726d4e2e178abd0dc13a6551c6d73" "checksum os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "66481dbeb5e773e7bd85b63cd6042c30786f834338288c5ec4f3742673db360a" "checksum pulldown-cmark 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8361e81576d2e02643b04950e487ec172b687180da65c731c03cf336784e6c07" @@ -408,5 +436,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum x86 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "841e1ca5a87068718a2a26f2473c6f93cf3b8119f9778fa0ae4b39b664d9e66a" "checksum x86_64 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2bd647af1614659e1febec1d681231aea4ebda4818bf55a578aff02f3e4db4b4" +"checksum x86_64 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b14609bad1c2c6a41113a1054f94394154e99bcb4b24cd7051109113c656c5a9" "checksum xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22678df5df766e8d1e5d609da69f0c3132d794edf6ab5e75e7abcd2270d4cf58" "checksum zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f1bc8a6b2005884962297587045002d8cfb8dcec9db332f4ca216ddc5de82c5" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index eb1b2f2f..d37a0d15 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -16,14 +16,14 @@ debug = true [dependencies] log = "0.4" -spin = "0.4.9" -once = "0.3.3" +spin = "0.4" +once = "0.3" xmas-elf = "0.6" bitflags = "1.0" -bit_field = "0.9.0" -volatile = "0.1.0" +bit_field = "0.9" +volatile = "0.1" linked_list_allocator = "0.6" -lazy_static = { version = "1.0.0", features = ["spin_no_std"] } +lazy_static = { version = "1.2", features = ["spin_no_std"] } bit-allocator = { path = "../crate/bit-allocator" } ucore-memory = { path = "../crate/memory" } ucore-process = { path = "../crate/process" } @@ -34,7 +34,7 @@ bootloader = { git = "https://github.com/wangrunji0408/bootloader" } apic = { git = "https://github.com/wangrunji0408/APIC-Rust" } #bootloader = { path = "../crate/bootloader" } #apic = { path = "../crate/apic" } -x86_64 = "0.2" +x86_64 = "0.3" raw-cpuid = "6.0" redox_syscall = "0.1" uart_16550 = "0.1"