From 26ca0de95044154d942934a762d5dc807e0112f2 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sat, 20 Nov 2021 15:31:39 +0800 Subject: [PATCH 01/56] update .gitignore README.md dev-env-info.md --- .gitignore | 1 + README.md | 67 +++++++++++++++++++++++++++++++++++++++++++------ dev-env-info.md | 18 +++++++++++++ 3 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 dev-env-info.md diff --git a/.gitignore b/.gitignore index 489e9b7..500b763 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ easy-fs/target/* easy-fs-fuse/Cargo.lock easy-fs-fuse/target/* tools/ +pushall.sh \ No newline at end of file diff --git a/README.md b/README.md index e97de71..e2dc4e3 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,67 @@ # rCore-Tutorial-v3 -rCore-Tutorial version 3.x +rCore-Tutorial version 3.5. See the [Documentation in Chinese](https://rcore-os.github.io/rCore-Tutorial-Book-v3/). -## Dependency +## news +- 2021.11.20: Now we are updating our labs. Please checkout chX-dev Branches for our current new labs. (Notice: please see the [Dependency] section in the end of this doc) -### Binaries +## Overview -* rustc 1.56.0-nightly (08095fc1f 2021-07-26) +This project aims to show how to write an **Unix-like OS** running on **RISC-V** platforms **from scratch** in **[Rust](https://www.rust-lang.org/)** for **beginners** without any background knowledge about **computer architectures, assembly languages or operating systems**. -* qemu: 5.0.0 +## Features -* rustsbi-lib: 0.2.0-alpha.4 +* Platform supported: `qemu-system-riscv64` simulator or dev boards based on [Kendryte K210 SoC](https://canaan.io/product/kendryteai) such as [Maix Dock](https://www.seeedstudio.com/Sipeed-MAIX-Dock-p-4815.html) +* OS + * concurrency of multiple processes + * preemptive scheduling(Round-Robin algorithm) + * dynamic memory management in kernel + * virtual memory + * a simple file system with a block cache + * an interactive shell in the userspace +* **only 4K+ LoC** +* [A detailed documentation in Chinese](https://rcore-os.github.io/rCore-Tutorial-Book-v3/) in spite of the lack of comments in the code(English version is not available at present) - rustsbi-qemu: d4968dd2 +## Run our project - rustsbi-k210: b689314e +TODO: + +## Working in progress + +Now we are still updating our project, you can find latest changes on branches `chX-dev` such as `ch1-dev`. We are intended to publish first release 3.5.0 after completing most of the tasks mentioned below. + +Overall progress: ch7 + +### Completed + +* [x] automatically clean up and rebuild before running our project on a different platform +* [x] fix `power` series application in early chapters, now you can find modulus in the output +* [x] use `UPSafeCell` instead of `RefCell` or `spin::Mutex` in order to access static data structures and adjust its API so that it cannot be borrowed twice at a time(mention `& .exclusive_access().task[0]` in `run_first_task`) +* [x] move `TaskContext` into `TaskControlBlock` instead of restoring it in place on kernel stack(since ch3), eliminating annoying `task_cx_ptr2` +* [x] replace `llvm_asm!` with `asm!` +* [x] expand the fs image size generated by `rcore-fs-fuse` to 128MiB +* [x] add a new test named `huge_write` which evaluates the fs performance(qemu\~500KiB/s k210\~50KiB/s) +* [x] flush all block cache to disk after a fs transaction which involves write operation +* [x] replace `spin::Mutex` with `UPSafeCell` before SMP chapter +* [x] add codes for a new chapter about synchronization & mutual exclusion(uniprocessor only) + +### Todo(High priority) + +* [ ] support Allwinner's RISC-V D1 chip +* [ ] bug fix: we should call `find_pte` rather than `find_pte_create` in `PageTable::unmap` +* [ ] bug fix: check validity of level-3 pte in `find_pte` instead of checking it outside this function +* [ ] use old fs image optionally, do not always rebuild the image +* [ ] add new system calls: getdents64/fstat +* [ ] shell functionality improvement(to be continued...) +* [ ] give every non-zero process exit code an unique and clear error type +* [ ] effective error handling of mm module + +### Todo(Low priority) + +* [ ] rewrite practice doc and remove some inproper questions +* [ ] provide smooth debug experience at a Rust source code level +* [ ] format the code using official tools + + +### Crates + +We will add them later. diff --git a/dev-env-info.md b/dev-env-info.md new file mode 100644 index 0000000..f76470a --- /dev/null +++ b/dev-env-info.md @@ -0,0 +1,18 @@ +# rCore-Tutorial-v3 +rCore-Tutorial version 3.x + +## Dependency + +### Binaries + +* rustc: 1.57.0-nightly (e1e9319d9 2021-10-14) + +* cargo-binutils: 0.3.3 + +* qemu: 5.0.0 + +* rustsbi-lib: 0.2.0-alpha.4 + + rustsbi-qemu: d4968dd2 + + rustsbi-k210: b689314e From f8f03d2b74a557fbbd39a41f274cdd949f40a95e Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Sat, 27 Nov 2021 01:57:11 -0800 Subject: [PATCH 02/56] Update os/Makefile, rm ... -f -> rm -f ... --- os/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/os/Makefile b/os/Makefile index e3434de..0c1fb7c 100644 --- a/os/Makefile +++ b/os/Makefile @@ -36,9 +36,9 @@ build: env switch-check $(KERNEL_BIN) fs-img switch-check: ifeq ($(BOARD), qemu) - (which last-qemu) || (rm last-k210 -f && touch last-qemu && make clean) + (which last-qemu) || (rm -f last-k210 && touch last-qemu && make clean) else ifeq ($(BOARD), k210) - (which last-k210) || (rm last-qemu -f && touch last-k210 && make clean) + (which last-k210) || (rm -f last-qemu && touch last-k210 && make clean) endif env: @@ -57,7 +57,7 @@ $(KERNEL_BIN): kernel fs-img: $(APPS) @cd ../user && make build - @rm $(FS_IMG) -f + @rm -f $(FS_IMG) @cd ../easy-fs-fuse && cargo run --release -- -s ../user/src/bin/ -t ../user/target/riscv64gc-unknown-none-elf/release/ $(APPS): From 6c45c33b419650760a601834f1ef522db1fe4ead Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sun, 5 Dec 2021 16:49:37 +0800 Subject: [PATCH 03/56] add user app: race_adder with arg --- user/src/bin/race_adder_arg.rs | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 user/src/bin/race_adder_arg.rs diff --git a/user/src/bin/race_adder_arg.rs b/user/src/bin/race_adder_arg.rs new file mode 100644 index 0000000..463589c --- /dev/null +++ b/user/src/bin/race_adder_arg.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; +extern crate alloc; + +use alloc::vec::Vec; +use crate::alloc::string::ToString; +use user_lib::{exit, get_time, thread_create, waittid}; + + +static mut A: usize = 0; +const PER_THREAD: usize = 1000; +const THREAD_COUNT: usize = 16; + +unsafe fn f(count:usize) -> ! { + let mut t = 2usize; + for _ in 0..PER_THREAD { + let a = &mut A as *mut usize; + let cur = a.read_volatile(); + for _ in 0..count { + t = t * t % 10007; + } + a.write_volatile(cur + 1); + } + exit(t as i32) +} + +#[no_mangle] +pub fn main(argc: usize, argv: &[&str]) -> i32 { + let mut count = 0; + + if argc == 1 { + count = THREAD_COUNT; + } else if argc == 2 { + count = argv[1].to_string().parse::().unwrap(); + } else { + println!("ERROR in argv"); + exit(-1); + } + + let start = get_time(); + let mut v = Vec::new(); + for _ in 0..THREAD_COUNT { + v.push(thread_create(f as usize, count) as usize); + } + let mut time_cost = Vec::new(); + for tid in v.iter() { + time_cost.push(waittid(*tid)); + } + println!("time cost is {}ms", get_time() - start); + assert_eq!(unsafe { A }, PER_THREAD * THREAD_COUNT); + 0 +} From 1a7420ac9758040290fb97a52d0a0b78b98ba7a8 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Thu, 9 Dec 2021 08:46:08 -0800 Subject: [PATCH 04/56] MutexBlocking works correctly. --- os/src/sync/mutex.rs | 3 ++- os/src/syscall/sync.rs | 15 ++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/os/src/sync/mutex.rs b/os/src/sync/mutex.rs index ef45bbf..fa13e8e 100644 --- a/os/src/sync/mutex.rs +++ b/os/src/sync/mutex.rs @@ -79,9 +79,10 @@ impl Mutex for MutexBlocking { fn unlock(&self) { let mut mutex_inner = self.inner.exclusive_access(); assert_eq!(mutex_inner.locked, true); - mutex_inner.locked = false; if let Some(waking_task) = mutex_inner.wait_queue.pop_front() { add_task(waking_task); + } else { + mutex_inner.locked = false; } } } diff --git a/os/src/syscall/sync.rs b/os/src/syscall/sync.rs index 370af84..4a2ac1d 100644 --- a/os/src/syscall/sync.rs +++ b/os/src/syscall/sync.rs @@ -1,5 +1,5 @@ use crate::task::{current_task, current_process, block_current_and_run_next}; -use crate::sync::{MutexSpin, MutexBlocking, Semaphore}; +use crate::sync::{Mutex, MutexSpin, MutexBlocking, Semaphore}; use crate::timer::{get_time_ms, add_timer}; use alloc::sync::Arc; @@ -13,6 +13,11 @@ pub fn sys_sleep(ms: usize) -> isize { pub fn sys_mutex_create(blocking: bool) -> isize { let process = current_process(); + let mutex: Option> = if !blocking { + Some(Arc::new(MutexSpin::new())) + } else { + Some(Arc::new(MutexBlocking::new())) + }; let mut process_inner = process.inner_exclusive_access(); if let Some(id) = process_inner .mutex_list @@ -20,14 +25,10 @@ pub fn sys_mutex_create(blocking: bool) -> isize { .enumerate() .find(|(_, item)| item.is_none()) .map(|(id, _)| id) { - process_inner.mutex_list[id] = if !blocking { - Some(Arc::new(MutexSpin::new())) - } else { - Some(Arc::new(MutexBlocking::new())) - }; + process_inner.mutex_list[id] = mutex; id as isize } else { - process_inner.mutex_list.push(Some(Arc::new(MutexSpin::new()))); + process_inner.mutex_list.push(mutex); process_inner.mutex_list.len() as isize - 1 } } From 7171f92972fa353a1eaba91309e5dcc73eb5baa0 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sat, 11 Dec 2021 21:11:10 +0800 Subject: [PATCH 05/56] fix typo of sys_semaphore_create --- os/src/syscall/mod.rs | 2 +- os/src/syscall/sync.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/os/src/syscall/mod.rs b/os/src/syscall/mod.rs index eac72aa..c19c3fd 100644 --- a/os/src/syscall/mod.rs +++ b/os/src/syscall/mod.rs @@ -54,7 +54,7 @@ pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize { SYSCALL_MUTEX_CREATE => sys_mutex_create(args[0] == 1), SYSCALL_MUTEX_LOCK => sys_mutex_lock(args[0]), SYSCALL_MUTEX_UNLOCK => sys_mutex_unlock(args[0]), - SYSCALL_SEMAPHORE_CREATE => sys_semaphore_creare(args[0]), + SYSCALL_SEMAPHORE_CREATE => sys_semaphore_create(args[0]), SYSCALL_SEMAPHORE_UP => sys_semaphore_up(args[0]), SYSCALL_SEMAPHORE_DOWN => sys_semaphore_down(args[0]), _ => panic!("Unsupported syscall_id: {}", syscall_id), diff --git a/os/src/syscall/sync.rs b/os/src/syscall/sync.rs index 4a2ac1d..f49f122 100644 --- a/os/src/syscall/sync.rs +++ b/os/src/syscall/sync.rs @@ -53,7 +53,7 @@ pub fn sys_mutex_unlock(mutex_id: usize) -> isize { 0 } -pub fn sys_semaphore_creare(res_count: usize) -> isize { +pub fn sys_semaphore_create(res_count: usize) -> isize { let process = current_process(); let mut process_inner = process.inner_exclusive_access(); let id = if let Some(id) = process_inner From a3ec64749614e30f3ee21fb141e829d7105a491a Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sat, 11 Dec 2021 21:40:04 +0800 Subject: [PATCH 06/56] add user app: sync_sem.rs --- user/src/bin/sync_sem.rs | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 user/src/bin/sync_sem.rs diff --git a/user/src/bin/sync_sem.rs b/user/src/bin/sync_sem.rs new file mode 100644 index 0000000..d72d520 --- /dev/null +++ b/user/src/bin/sync_sem.rs @@ -0,0 +1,45 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +extern crate alloc; + +use user_lib::{semaphore_create, semaphore_up, semaphore_down}; +use user_lib::{thread_create, waittid, sleep}; +use user_lib::exit; +use alloc::vec::Vec; + +const SEM_SYNC: usize = 0; + + +unsafe fn first() -> ! { + sleep(10); + println!("First work and wakeup Second"); + semaphore_up(SEM_SYNC); + exit(0) +} + +unsafe fn second() -> ! { + println!("Second want to continue,but need to wait first"); + semaphore_down(SEM_SYNC); + println!("Second can work now"); + exit(0) +} + +#[no_mangle] +pub fn main() -> i32 { + // create semaphores + assert_eq!(semaphore_create(0) as usize, SEM_SYNC); + // create threads + let mut threads = Vec::new(); + threads.push(thread_create(first as usize, 0)); + threads.push(thread_create(second as usize, 0)); + // wait for all threads to complete + for thread in threads.iter() { + waittid(*thread as usize); + } + println!("sync_sem passed!"); + 0 +} From b3bce3bef2447fe41334be709875279340708a49 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Mon, 13 Dec 2021 15:30:27 +0800 Subject: [PATCH 07/56] add condvar in kernel and app --- os/src/sync/condvar.rs | 39 ++++++++++++ os/src/sync/mod.rs | 2 + os/src/syscall/mod.rs | 6 ++ os/src/syscall/sync.rs | 40 +++++++++++- os/src/task/process.rs | 5 +- user/src/bin/test_condvar.rs | 56 +++++++++++++++++ user/src/lib.rs | 114 +++++++++++++++++++++++++---------- user/src/syscall.rs | 15 +++++ 8 files changed, 243 insertions(+), 34 deletions(-) create mode 100644 os/src/sync/condvar.rs create mode 100644 user/src/bin/test_condvar.rs diff --git a/os/src/sync/condvar.rs b/os/src/sync/condvar.rs new file mode 100644 index 0000000..0b8f567 --- /dev/null +++ b/os/src/sync/condvar.rs @@ -0,0 +1,39 @@ +use alloc::{sync::Arc, collections::VecDeque}; +use crate::task::{add_task, TaskControlBlock, current_task, block_current_and_run_next}; +use crate::sync::{Mutex, UPSafeCell}; + +pub struct Condvar { + pub inner: UPSafeCell, +} + +pub struct CondvarInner { + pub wait_queue: VecDeque>, +} + +impl Condvar { + pub fn new() -> Self { + Self { + inner: unsafe { UPSafeCell::new( + CondvarInner { + wait_queue: VecDeque::new(), + } + )}, + } + } + + pub fn signal(&self) { + let mut inner = self.inner.exclusive_access(); + if let Some(task) = inner.wait_queue.pop_front() { + add_task(task); + } + } + + pub fn wait(&self, mutex:Arc) { + mutex.unlock(); + let mut inner = self.inner.exclusive_access(); + inner.wait_queue.push_back(current_task().unwrap()); + drop(inner); + block_current_and_run_next(); + mutex.lock(); + } +} diff --git a/os/src/sync/mod.rs b/os/src/sync/mod.rs index ed39a63..8cf47c2 100644 --- a/os/src/sync/mod.rs +++ b/os/src/sync/mod.rs @@ -1,7 +1,9 @@ mod up; mod mutex; mod semaphore; +mod condvar; pub use up::UPSafeCell; pub use mutex::{Mutex, MutexSpin, MutexBlocking}; pub use semaphore::Semaphore; +pub use condvar::Condvar; \ No newline at end of file diff --git a/os/src/syscall/mod.rs b/os/src/syscall/mod.rs index c19c3fd..5184abe 100644 --- a/os/src/syscall/mod.rs +++ b/os/src/syscall/mod.rs @@ -21,6 +21,9 @@ const SYSCALL_MUTEX_UNLOCK: usize = 1012; const SYSCALL_SEMAPHORE_CREATE: usize = 1020; const SYSCALL_SEMAPHORE_UP: usize = 1021; const SYSCALL_SEMAPHORE_DOWN: usize = 1022; +const SYSCALL_CONDVAR_CREATE: usize = 1030; +const SYSCALL_CONDVAR_SIGNAL: usize = 1031; +const SYSCALL_CONDVAR_WAIT: usize = 1032; mod fs; mod process; @@ -57,6 +60,9 @@ pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize { SYSCALL_SEMAPHORE_CREATE => sys_semaphore_create(args[0]), SYSCALL_SEMAPHORE_UP => sys_semaphore_up(args[0]), SYSCALL_SEMAPHORE_DOWN => sys_semaphore_down(args[0]), + SYSCALL_CONDVAR_CREATE => sys_condvar_create(args[0]), + SYSCALL_CONDVAR_SIGNAL => sys_condvar_signal(args[0]), + SYSCALL_CONDVAR_WAIT => sys_condvar_wait(args[0], args[1]), _ => panic!("Unsupported syscall_id: {}", syscall_id), } } diff --git a/os/src/syscall/sync.rs b/os/src/syscall/sync.rs index f49f122..e92cf79 100644 --- a/os/src/syscall/sync.rs +++ b/os/src/syscall/sync.rs @@ -1,5 +1,5 @@ use crate::task::{current_task, current_process, block_current_and_run_next}; -use crate::sync::{Mutex, MutexSpin, MutexBlocking, Semaphore}; +use crate::sync::{Mutex, MutexSpin, MutexBlocking, Semaphore, Condvar}; use crate::timer::{get_time_ms, add_timer}; use alloc::sync::Arc; @@ -88,3 +88,41 @@ pub fn sys_semaphore_down(sem_id: usize) -> isize { sem.down(); 0 } + + +pub fn sys_condvar_create(_arg: usize) -> isize { + let process = current_process(); + let mut process_inner = process.inner_exclusive_access(); + let id = if let Some(id) = process_inner + .condvar_list + .iter() + .enumerate() + .find(|(_, item)| item.is_none()) + .map(|(id, _)| id) { + process_inner.condvar_list[id] = Some(Arc::new(Condvar::new())); + id + } else { + process_inner.condvar_list.push(Some(Arc::new(Condvar::new()))); + process_inner.condvar_list.len() - 1 + }; + id as isize +} + +pub fn sys_condvar_signal(condvar_id: usize) -> isize { + let process = current_process(); + let process_inner = process.inner_exclusive_access(); + let condvar = Arc::clone(process_inner.condvar_list[condvar_id].as_ref().unwrap()); + drop(process_inner); + condvar.signal(); + 0 +} + +pub fn sys_condvar_wait(condvar_id: usize, mutex_id: usize) -> isize { + let process = current_process(); + let process_inner = process.inner_exclusive_access(); + let condvar = Arc::clone(process_inner.condvar_list[condvar_id].as_ref().unwrap()); + let mutex = Arc::clone(process_inner.mutex_list[mutex_id].as_ref().unwrap()); + drop(process_inner); + condvar.wait(mutex); + 0 +} \ No newline at end of file diff --git a/os/src/task/process.rs b/os/src/task/process.rs index af48f21..43001b1 100644 --- a/os/src/task/process.rs +++ b/os/src/task/process.rs @@ -4,7 +4,7 @@ use crate::mm::{ translated_refmut, }; use crate::trap::{TrapContext, trap_handler}; -use crate::sync::{UPSafeCell, Mutex, Semaphore}; +use crate::sync::{UPSafeCell, Mutex, Semaphore, Condvar}; use core::cell::RefMut; use super::id::RecycleAllocator; use super::TaskControlBlock; @@ -34,6 +34,7 @@ pub struct ProcessControlBlockInner { pub task_res_allocator: RecycleAllocator, pub mutex_list: Vec>>, pub semaphore_list: Vec>>, + pub condvar_list: Vec>>, } impl ProcessControlBlockInner { @@ -99,6 +100,7 @@ impl ProcessControlBlock { task_res_allocator: RecycleAllocator::new(), mutex_list: Vec::new(), semaphore_list: Vec::new(), + condvar_list: Vec::new(), })} }); // create a main thread, we should allocate ustack and trap_cx here @@ -213,6 +215,7 @@ impl ProcessControlBlock { task_res_allocator: RecycleAllocator::new(), mutex_list: Vec::new(), semaphore_list: Vec::new(), + condvar_list: Vec::new(), })} }); // add child diff --git a/user/src/bin/test_condvar.rs b/user/src/bin/test_condvar.rs new file mode 100644 index 0000000..6aa49ef --- /dev/null +++ b/user/src/bin/test_condvar.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +extern crate alloc; + +use user_lib::{condvar_create, condvar_signal, condvar_wait, mutex_blocking_create, mutex_lock, mutex_unlock}; +use user_lib::{thread_create, waittid, sleep}; +use user_lib::exit; +use alloc::vec::Vec; + +static mut A: usize = 0; + +const CONDVAR_ID: usize = 0; +const MUTEX_ID: usize = 0; + +unsafe fn first() -> ! { + sleep(10); + println!("First work, Change A --> 1 and wakeup Second"); + mutex_lock(MUTEX_ID); + A=1; + condvar_signal(CONDVAR_ID); + mutex_unlock(MUTEX_ID); + exit(0) +} + +unsafe fn second() -> ! { + println!("Second want to continue,but need to wait A=1"); + mutex_lock(MUTEX_ID); + while A==0 { + println!("Second: A is {}", A); + condvar_wait(CONDVAR_ID, MUTEX_ID); + } + mutex_unlock(MUTEX_ID); + println!("A is {}, Second can work now", A); + exit(0) +} + +#[no_mangle] +pub fn main() -> i32 { + // create condvar & mutex + assert_eq!(condvar_create() as usize, CONDVAR_ID); + assert_eq!(mutex_blocking_create() as usize, MUTEX_ID); + // create threads + let mut threads = Vec::new(); + threads.push(thread_create(first as usize, 0)); + threads.push(thread_create(second as usize, 0)); + // wait for all threads to complete + for thread in threads.iter() { + waittid(*thread as usize); + } + println!("test_condvar passed!"); + 0 +} diff --git a/user/src/lib.rs b/user/src/lib.rs index afde2ec..4fe0e2a 100644 --- a/user/src/lib.rs +++ b/user/src/lib.rs @@ -6,16 +6,16 @@ #[macro_use] pub mod console; -mod syscall; mod lang_items; +mod syscall; extern crate alloc; #[macro_use] extern crate bitflags; -use syscall::*; -use buddy_system_allocator::LockedHeap; use alloc::vec::Vec; +use buddy_system_allocator::LockedHeap; +use syscall::*; const USER_HEAP_SIZE: usize = 32768; @@ -38,16 +38,16 @@ pub extern "C" fn _start(argc: usize, argv: usize) -> ! { } let mut v: Vec<&'static str> = Vec::new(); for i in 0..argc { - let str_start = unsafe { - ((argv + i * core::mem::size_of::()) as *const usize).read_volatile() - }; - let len = (0usize..).find(|i| unsafe { - ((str_start + *i) as *const u8).read_volatile() == 0 - }).unwrap(); + let str_start = + unsafe { ((argv + i * core::mem::size_of::()) as *const usize).read_volatile() }; + let len = (0usize..) + .find(|i| unsafe { ((str_start + *i) as *const u8).read_volatile() == 0 }) + .unwrap(); v.push( core::str::from_utf8(unsafe { core::slice::from_raw_parts(str_start as *const u8, len) - }).unwrap() + }) + .unwrap(), ); } exit(main(argc, v.as_slice())); @@ -69,22 +69,48 @@ bitflags! { } } -pub fn dup(fd: usize) -> isize { sys_dup(fd) } -pub fn open(path: &str, flags: OpenFlags) -> isize { sys_open(path, flags.bits) } -pub fn close(fd: usize) -> isize { sys_close(fd) } -pub fn pipe(pipe_fd: &mut [usize]) -> isize { sys_pipe(pipe_fd) } -pub fn read(fd: usize, buf: &mut [u8]) -> isize { sys_read(fd, buf) } -pub fn write(fd: usize, buf: &[u8]) -> isize { sys_write(fd, buf) } -pub fn exit(exit_code: i32) -> ! { sys_exit(exit_code); } -pub fn yield_() -> isize { sys_yield() } -pub fn get_time() -> isize { sys_get_time() } -pub fn getpid() -> isize { sys_getpid() } -pub fn fork() -> isize { sys_fork() } -pub fn exec(path: &str, args: &[*const u8]) -> isize { sys_exec(path, args) } +pub fn dup(fd: usize) -> isize { + sys_dup(fd) +} +pub fn open(path: &str, flags: OpenFlags) -> isize { + sys_open(path, flags.bits) +} +pub fn close(fd: usize) -> isize { + sys_close(fd) +} +pub fn pipe(pipe_fd: &mut [usize]) -> isize { + sys_pipe(pipe_fd) +} +pub fn read(fd: usize, buf: &mut [u8]) -> isize { + sys_read(fd, buf) +} +pub fn write(fd: usize, buf: &[u8]) -> isize { + sys_write(fd, buf) +} +pub fn exit(exit_code: i32) -> ! { + sys_exit(exit_code); +} +pub fn yield_() -> isize { + sys_yield() +} +pub fn get_time() -> isize { + sys_get_time() +} +pub fn getpid() -> isize { + sys_getpid() +} +pub fn fork() -> isize { + sys_fork() +} +pub fn exec(path: &str, args: &[*const u8]) -> isize { + sys_exec(path, args) +} pub fn wait(exit_code: &mut i32) -> isize { loop { match sys_waitpid(-1, exit_code as *mut _) { - -2 => { yield_(); } + -2 => { + yield_(); + } // -1 or a real pid exit_pid => return exit_pid, } @@ -94,7 +120,9 @@ pub fn wait(exit_code: &mut i32) -> isize { pub fn waitpid(pid: usize, exit_code: &mut i32) -> isize { loop { match sys_waitpid(pid as isize, exit_code as *mut _) { - -2 => { yield_(); } + -2 => { + yield_(); + } // -1 or a real pid exit_pid => return exit_pid, } @@ -104,21 +132,35 @@ pub fn sleep(sleep_ms: usize) { sys_sleep(sleep_ms); } -pub fn thread_create(entry: usize, arg: usize) -> isize { sys_thread_create(entry, arg) } -pub fn gettid() -> isize { sys_gettid() } +pub fn thread_create(entry: usize, arg: usize) -> isize { + sys_thread_create(entry, arg) +} +pub fn gettid() -> isize { + sys_gettid() +} pub fn waittid(tid: usize) -> isize { loop { match sys_waittid(tid) { - -2 => { yield_(); } + -2 => { + yield_(); + } exit_code => return exit_code, } } } -pub fn mutex_create() -> isize { sys_mutex_create(false) } -pub fn mutex_blocking_create() -> isize { sys_mutex_create(true) } -pub fn mutex_lock(mutex_id: usize) { sys_mutex_lock(mutex_id); } -pub fn mutex_unlock(mutex_id: usize) { sys_mutex_unlock(mutex_id); } +pub fn mutex_create() -> isize { + sys_mutex_create(false) +} +pub fn mutex_blocking_create() -> isize { + sys_mutex_create(true) +} +pub fn mutex_lock(mutex_id: usize) { + sys_mutex_lock(mutex_id); +} +pub fn mutex_unlock(mutex_id: usize) { + sys_mutex_unlock(mutex_id); +} pub fn semaphore_create(res_count: usize) -> isize { sys_semaphore_create(res_count) } @@ -128,4 +170,12 @@ pub fn semaphore_up(sem_id: usize) { pub fn semaphore_down(sem_id: usize) { sys_semaphore_down(sem_id); } - +pub fn condvar_create() -> isize { + sys_condvar_create(0) +} +pub fn condvar_signal(condvar_id: usize) { + sys_condvar_signal(condvar_id); +} +pub fn condvar_wait(condvar_id: usize, mutex_id: usize) { + sys_condvar_wait(condvar_id, mutex_id); +} diff --git a/user/src/syscall.rs b/user/src/syscall.rs index 64f738d..8d5b6f5 100644 --- a/user/src/syscall.rs +++ b/user/src/syscall.rs @@ -21,6 +21,9 @@ const SYSCALL_MUTEX_UNLOCK: usize = 1012; const SYSCALL_SEMAPHORE_CREATE: usize = 1020; const SYSCALL_SEMAPHORE_UP: usize = 1021; const SYSCALL_SEMAPHORE_DOWN: usize = 1022; +const SYSCALL_CONDVAR_CREATE: usize = 1030; +const SYSCALL_CONDVAR_SIGNAL: usize = 1031; +const SYSCALL_CONDVAR_WAIT: usize = 1032; fn syscall(id: usize, args: [usize; 3]) -> isize { let mut ret: isize; @@ -128,3 +131,15 @@ pub fn sys_semaphore_up(sem_id: usize) -> isize { pub fn sys_semaphore_down(sem_id: usize) -> isize { syscall(SYSCALL_SEMAPHORE_DOWN, [sem_id, 0, 0]) } + +pub fn sys_condvar_create(_arg: usize) -> isize { + syscall(SYSCALL_CONDVAR_CREATE, [_arg, 0, 0]) +} + +pub fn sys_condvar_signal(condvar_id: usize) -> isize { + syscall(SYSCALL_CONDVAR_SIGNAL, [condvar_id, 0, 0]) +} + +pub fn sys_condvar_wait(condvar_id: usize, mutex_id:usize) -> isize { + syscall(SYSCALL_CONDVAR_WAIT, [condvar_id, mutex_id, 0]) +} From 98a7b866b5f0407b16339145cad385e365883eef Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Wed, 22 Dec 2021 03:53:24 -0800 Subject: [PATCH 08/56] Bump to Rust nightly 2021-12-15 --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index fd47275..c9f8682 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2021-10-15 +nightly-2021-12-15 From 3f0c3f6dfd5b9c5085b6285647b8048595ab27b9 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Sat, 1 Jan 2022 03:07:09 -0800 Subject: [PATCH 09/56] Bump to rust nightly-2022-01-01, feature global_asm,asm->stable --- os/src/lang_items.rs | 1 + os/src/main.rs | 4 ++-- os/src/mm/memory_set.rs | 1 + os/src/sbi.rs | 2 ++ os/src/task/switch.rs | 5 +++-- os/src/trap/mod.rs | 1 + rust-toolchain | 2 +- user/src/lib.rs | 1 - user/src/syscall.rs | 2 ++ 9 files changed, 13 insertions(+), 6 deletions(-) diff --git a/os/src/lang_items.rs b/os/src/lang_items.rs index eedbd83..daf8632 100644 --- a/os/src/lang_items.rs +++ b/os/src/lang_items.rs @@ -1,4 +1,5 @@ use core::panic::PanicInfo; +use core::arch::asm; use crate::sbi::shutdown; use crate::task::current_kstack_top; diff --git a/os/src/main.rs b/os/src/main.rs index d4b0934..3a960a6 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -1,7 +1,5 @@ #![no_std] #![no_main] -#![feature(global_asm)] -#![feature(asm)] #![feature(panic_info_message)] #![feature(alloc_error_handler)] @@ -24,6 +22,8 @@ mod mm; mod fs; mod drivers; +use core::arch::global_asm; + global_asm!(include_str!("entry.asm")); fn clear_bss() { diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index 575f5a3..30bc768 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -14,6 +14,7 @@ use crate::config::{ TRAMPOLINE, MMIO, }; +use core::arch::asm; extern "C" { fn stext(); diff --git a/os/src/sbi.rs b/os/src/sbi.rs index 276c199..c8ecbf1 100644 --- a/os/src/sbi.rs +++ b/os/src/sbi.rs @@ -1,5 +1,7 @@ #![allow(unused)] +use core::arch::asm; + const SBI_SET_TIMER: usize = 0; const SBI_CONSOLE_PUTCHAR: usize = 1; const SBI_CONSOLE_GETCHAR: usize = 2; diff --git a/os/src/task/switch.rs b/os/src/task/switch.rs index fa75be1..dd3a2d3 100644 --- a/os/src/task/switch.rs +++ b/os/src/task/switch.rs @@ -1,6 +1,7 @@ -global_asm!(include_str!("switch.S")); - use super::TaskContext; +use core::arch::global_asm; + +global_asm!(include_str!("switch.S")); extern "C" { pub fn __switch( diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index 5fd95f5..f0f60af 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -22,6 +22,7 @@ use crate::task::{ }; use crate::timer::{set_next_trigger, check_timer}; use crate::config::TRAMPOLINE; +use core::arch::{global_asm, asm}; global_asm!(include_str!("trap.S")); diff --git a/rust-toolchain b/rust-toolchain index c9f8682..a8a6159 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2021-12-15 +nightly-2022-01-01 diff --git a/user/src/lib.rs b/user/src/lib.rs index 4fe0e2a..50b6439 100644 --- a/user/src/lib.rs +++ b/user/src/lib.rs @@ -1,5 +1,4 @@ #![no_std] -#![feature(asm)] #![feature(linkage)] #![feature(panic_info_message)] #![feature(alloc_error_handler)] diff --git a/user/src/syscall.rs b/user/src/syscall.rs index 8d5b6f5..e85fbbb 100644 --- a/user/src/syscall.rs +++ b/user/src/syscall.rs @@ -1,3 +1,5 @@ +use core::arch::asm; + const SYSCALL_DUP: usize = 24; const SYSCALL_OPEN: usize = 56; const SYSCALL_CLOSE: usize = 57; From 94156ab1f7b93b6082af3d83d5d6fdd7d3d5c9a4 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Mon, 3 Jan 2022 19:40:30 -0800 Subject: [PATCH 10/56] Kernel cannot dump now. --- os/src/trap/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index f0f60af..f57ca70 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -69,7 +69,7 @@ pub fn trap_handler() -> ! { Trap::Exception(Exception::LoadFault) | Trap::Exception(Exception::LoadPageFault) => { println!( - "[kernel] {:?} in application, bad addr = {:#x}, bad instruction = {:#x}, core dumped.", + "[kernel] {:?} in application, bad addr = {:#x}, bad instruction = {:#x}, kernel killed it.", scause.cause(), stval, current_trap_cx().sepc, @@ -78,7 +78,7 @@ pub fn trap_handler() -> ! { exit_current_and_run_next(-2); } Trap::Exception(Exception::IllegalInstruction) => { - println!("[kernel] IllegalInstruction in application, core dumped."); + println!("[kernel] IllegalInstruction in application, kernel killed it."); // illegal instruction exit code exit_current_and_run_next(-3); } From b535f5ba987cb2da288ec89fcaf508ea137ccb72 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Thu, 13 Jan 2022 17:24:19 -0800 Subject: [PATCH 11/56] Now PageTable::unmap calls PageTable::find_pte instead of PageTable::find_pte_create. --- os/src/mm/page_table.rs | 8 ++++---- user/src/bin/race_adder_arg.rs | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/os/src/mm/page_table.rs b/os/src/mm/page_table.rs index d1bc645..0ce485c 100644 --- a/os/src/mm/page_table.rs +++ b/os/src/mm/page_table.rs @@ -102,12 +102,12 @@ impl PageTable { } result } - fn find_pte(&self, vpn: VirtPageNum) -> Option<&PageTableEntry> { + fn find_pte(&self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> { let idxs = vpn.indexes(); let mut ppn = self.root_ppn; - let mut result: Option<&PageTableEntry> = None; + let mut result: Option<&mut PageTableEntry> = None; for i in 0..3 { - let pte = &ppn.get_pte_array()[idxs[i]]; + let pte = &mut ppn.get_pte_array()[idxs[i]]; if i == 2 { result = Some(pte); break; @@ -127,7 +127,7 @@ impl PageTable { } #[allow(unused)] pub fn unmap(&mut self, vpn: VirtPageNum) { - let pte = self.find_pte_create(vpn).unwrap(); + let pte = self.find_pte(vpn).unwrap(); assert!(pte.is_valid(), "vpn {:?} is invalid before unmapping", vpn); *pte = PageTableEntry::empty(); } diff --git a/user/src/bin/race_adder_arg.rs b/user/src/bin/race_adder_arg.rs index 463589c..dfb3fd0 100644 --- a/user/src/bin/race_adder_arg.rs +++ b/user/src/bin/race_adder_arg.rs @@ -29,8 +29,6 @@ unsafe fn f(count:usize) -> ! { #[no_mangle] pub fn main(argc: usize, argv: &[&str]) -> i32 { - let mut count = 0; - if argc == 1 { count = THREAD_COUNT; } else if argc == 2 { From a32e35d48d3f1bea7c4e3c854ac971444a4eb6e2 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Thu, 13 Jan 2022 17:27:09 -0800 Subject: [PATCH 12/56] Now PageTable::unmap calls PageTable::find_pte instead of PageTable::find_pte_create. --- os/src/mm/page_table.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/os/src/mm/page_table.rs b/os/src/mm/page_table.rs index d1bc645..0ce485c 100644 --- a/os/src/mm/page_table.rs +++ b/os/src/mm/page_table.rs @@ -102,12 +102,12 @@ impl PageTable { } result } - fn find_pte(&self, vpn: VirtPageNum) -> Option<&PageTableEntry> { + fn find_pte(&self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> { let idxs = vpn.indexes(); let mut ppn = self.root_ppn; - let mut result: Option<&PageTableEntry> = None; + let mut result: Option<&mut PageTableEntry> = None; for i in 0..3 { - let pte = &ppn.get_pte_array()[idxs[i]]; + let pte = &mut ppn.get_pte_array()[idxs[i]]; if i == 2 { result = Some(pte); break; @@ -127,7 +127,7 @@ impl PageTable { } #[allow(unused)] pub fn unmap(&mut self, vpn: VirtPageNum) { - let pte = self.find_pte_create(vpn).unwrap(); + let pte = self.find_pte(vpn).unwrap(); assert!(pte.is_valid(), "vpn {:?} is invalid before unmapping", vpn); *pte = PageTableEntry::empty(); } From 838d0a7b2f5d586afb734bbcdb7e3f1b2bf93d5f Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Thu, 13 Jan 2022 17:38:25 -0800 Subject: [PATCH 13/56] Update README.md. --- README.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6f9e843..c35ea02 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This project aims to show how to write an **Unix-like OS** running on **RISC-V** * Platform supported: `qemu-system-riscv64` simulator or dev boards based on [Kendryte K210 SoC](https://canaan.io/product/kendryteai) such as [Maix Dock](https://www.seeedstudio.com/Sipeed-MAIX-Dock-p-4815.html) * OS - * concurrency of multiple processes + * concurrency of multiple processes each of which contains mutiple native threads * preemptive scheduling(Round-Robin algorithm) * dynamic memory management in kernel * virtual memory @@ -29,9 +29,15 @@ TODO: ## Working in progress -Now we are still updating our project, you can find latest changes on branches `chX-dev` such as `ch1-dev`. We are intended to publish first release 3.5.0 after completing most of the tasks mentioned below. +Our first release 3.5.0 (chapter 1-7) has been published. -Overall progress: ch7 +There will be 9 chapters in our next release 3.6.0, where 2 new chapters will be added: +* chapter 8: synchronization on a uniprocessor +* chapter 9: I/O devices + +Current version is 3.6.0-alpha.1 and we are still working on it. + +Here are the updates since 3.5.0: ### Completed @@ -45,12 +51,15 @@ Overall progress: ch7 * [x] flush all block cache to disk after a fs transaction which involves write operation * [x] replace `spin::Mutex` with `UPSafeCell` before SMP chapter * [x] add codes for a new chapter about synchronization & mutual exclusion(uniprocessor only) +* [x] bug fix: we should call `find_pte` rather than `find_pte_create` in `PageTable::unmap` +* [x] clarify: "check validity of level-3 pte in `find_pte` instead of checking it outside this function" should not be a bug +* [x] code of chapter 8: synchronization on a uniprocessor ### Todo(High priority) -* [ ] support Allwinner's RISC-V D1 chip -* [ ] bug fix: we should call `find_pte` rather than `find_pte_create` in `PageTable::unmap` -* [ ] bug fix: check validity of level-3 pte in `find_pte` instead of checking it outside this function +* [ ] review documentation, current progress: 5/9 +* [ ] switch the code of chapter 6 and chapter 7 +* [ ] code of chapter 9: device drivers based on interrupts, including UART and block devices * [ ] use old fs image optionally, do not always rebuild the image * [ ] add new system calls: getdents64/fstat * [ ] shell functionality improvement(to be continued...) @@ -62,6 +71,7 @@ Overall progress: ch7 * [ ] rewrite practice doc and remove some inproper questions * [ ] provide smooth debug experience at a Rust source code level * [ ] format the code using official tools +* [ ] support other platforms ### Crates From 916442b57e090f648c8155de6b167370a81dfc30 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Mon, 17 Jan 2022 17:38:24 -0800 Subject: [PATCH 14/56] Update README.md. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c35ea02..d665dcc 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Here are the updates since 3.5.0: ### Todo(High priority) * [ ] review documentation, current progress: 5/9 +* [ ] support signal mechanism in chapter 7 * [ ] switch the code of chapter 6 and chapter 7 * [ ] code of chapter 9: device drivers based on interrupts, including UART and block devices * [ ] use old fs image optionally, do not always rebuild the image From f6ebd1ac68d1629e088d8f7391c587e282746368 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Tue, 18 Jan 2022 04:40:07 -0800 Subject: [PATCH 15/56] Merge recent updates from ch7. --- os/src/loader.rs | 62 ---------------------------------- user/src/bin/cat.rs | 7 ++-- user/src/bin/huge_write.rs | 4 +-- user/src/bin/race_adder_arg.rs | 1 + 4 files changed, 5 insertions(+), 69 deletions(-) delete mode 100644 os/src/loader.rs diff --git a/os/src/loader.rs b/os/src/loader.rs deleted file mode 100644 index bfdc397..0000000 --- a/os/src/loader.rs +++ /dev/null @@ -1,62 +0,0 @@ -use alloc::vec::Vec; -use lazy_static::*; - -pub fn get_num_app() -> usize { - extern "C" { fn _num_app(); } - unsafe { (_num_app as usize as *const usize).read_volatile() } -} - -pub fn get_app_data(app_id: usize) -> &'static [u8] { - extern "C" { fn _num_app(); } - let num_app_ptr = _num_app as usize as *const usize; - let num_app = get_num_app(); - let app_start = unsafe { - core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1) - }; - assert!(app_id < num_app); - unsafe { - core::slice::from_raw_parts( - app_start[app_id] as *const u8, - app_start[app_id + 1] - app_start[app_id] - ) - } -} - -lazy_static! { - static ref APP_NAMES: Vec<&'static str> = { - let num_app = get_num_app(); - extern "C" { fn _app_names(); } - let mut start = _app_names as usize as *const u8; - let mut v = Vec::new(); - unsafe { - for _ in 0..num_app { - let mut end = start; - while end.read_volatile() != '\0' as u8 { - end = end.add(1); - } - let slice = core::slice::from_raw_parts(start, end as usize - start as usize); - let str = core::str::from_utf8(slice).unwrap(); - v.push(str); - start = end.add(1); - } - } - v - }; -} - - -#[allow(unused)] -pub fn get_app_data_by_name(name: &str) -> Option<&'static [u8]> { - let num_app = get_num_app(); - (0..num_app) - .find(|&i| APP_NAMES[i] == name) - .map(|i| get_app_data(i)) -} - -pub fn list_apps() { - println!("/**** APPS ****"); - for app in APP_NAMES.iter() { - println!("{}", app); - } - println!("**************/"); -} \ No newline at end of file diff --git a/user/src/bin/cat.rs b/user/src/bin/cat.rs index 988164d..4091878 100644 --- a/user/src/bin/cat.rs +++ b/user/src/bin/cat.rs @@ -11,7 +11,6 @@ use user_lib::{ close, read, }; -use alloc::string::String; #[no_mangle] pub fn main(argc: usize, argv: &[&str]) -> i32 { @@ -22,13 +21,11 @@ pub fn main(argc: usize, argv: &[&str]) -> i32 { } let fd = fd as usize; let mut buf = [0u8; 16]; - let mut s = String::new(); loop { let size = read(fd, &mut buf) as usize; if size == 0 { break; } - s.push_str(core::str::from_utf8(&buf[..size]).unwrap()); + println!("{}", core::str::from_utf8(&buf[..size]).unwrap()); } - println!("{}", s); close(fd); 0 -} \ No newline at end of file +} diff --git a/user/src/bin/huge_write.rs b/user/src/bin/huge_write.rs index f9dafa1..b00ca17 100644 --- a/user/src/bin/huge_write.rs +++ b/user/src/bin/huge_write.rs @@ -18,7 +18,7 @@ pub fn main() -> i32 { for i in 0..buffer.len() { buffer[i] = i as u8; } - let f = open("testf", OpenFlags::CREATE | OpenFlags::WRONLY); + let f = open("testf\0", OpenFlags::CREATE | OpenFlags::WRONLY); if f < 0 { panic!("Open test file failed!"); } @@ -33,4 +33,4 @@ pub fn main() -> i32 { let speed_kbs = size_mb * 1000000 / time_ms; println!("{}MiB written, time cost = {}ms, write speed = {}KiB/s", size_mb, time_ms, speed_kbs); 0 -} \ No newline at end of file +} diff --git a/user/src/bin/race_adder_arg.rs b/user/src/bin/race_adder_arg.rs index dfb3fd0..8d7758c 100644 --- a/user/src/bin/race_adder_arg.rs +++ b/user/src/bin/race_adder_arg.rs @@ -29,6 +29,7 @@ unsafe fn f(count:usize) -> ! { #[no_mangle] pub fn main(argc: usize, argv: &[&str]) -> i32 { + let mut count = 0; if argc == 1 { count = THREAD_COUNT; } else if argc == 2 { From 2cad6d291e84936e929ee2be61b46f63ceb44036 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Tue, 18 Jan 2022 04:44:05 -0800 Subject: [PATCH 16/56] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d665dcc..590409a 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,12 @@ Here are the updates since 3.5.0: * [x] bug fix: we should call `find_pte` rather than `find_pte_create` in `PageTable::unmap` * [x] clarify: "check validity of level-3 pte in `find_pte` instead of checking it outside this function" should not be a bug * [x] code of chapter 8: synchronization on a uniprocessor +* [x] switch the code of chapter 6 and chapter 7 ### Todo(High priority) * [ ] review documentation, current progress: 5/9 * [ ] support signal mechanism in chapter 7 -* [ ] switch the code of chapter 6 and chapter 7 * [ ] code of chapter 9: device drivers based on interrupts, including UART and block devices * [ ] use old fs image optionally, do not always rebuild the image * [ ] add new system calls: getdents64/fstat From a3e665d6f4b8beb75ad164371c30ee340bda1411 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Wed, 19 Jan 2022 05:08:21 -0800 Subject: [PATCH 17/56] Update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 500b763..7fb857d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ os/target/* os/.idea/* os/src/link_app.S +os/src/linker.ld os/last-* os/Cargo.lock user/target/* @@ -12,4 +13,4 @@ easy-fs/target/* easy-fs-fuse/Cargo.lock easy-fs-fuse/target/* tools/ -pushall.sh \ No newline at end of file +pushall.sh From 5e8cf728a74cbe410c4fe4506d85d99287ffa146 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Wed, 19 Jan 2022 16:40:21 -0800 Subject: [PATCH 18/56] Maximum concurrent processes from 40/35->30. --- user/src/bin/forktest.rs | 4 ++-- user/src/bin/matrix.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/user/src/bin/forktest.rs b/user/src/bin/forktest.rs index fea6967..a7d523a 100644 --- a/user/src/bin/forktest.rs +++ b/user/src/bin/forktest.rs @@ -6,7 +6,7 @@ extern crate user_lib; use user_lib::{fork, wait, exit}; -const MAX_CHILD: usize = 40; +const MAX_CHILD: usize = 30; #[no_mangle] pub fn main() -> i32 { @@ -31,4 +31,4 @@ pub fn main() -> i32 { } println!("forktest pass."); 0 -} \ No newline at end of file +} diff --git a/user/src/bin/matrix.rs b/user/src/bin/matrix.rs index 8ef2c04..8f1357e 100644 --- a/user/src/bin/matrix.rs +++ b/user/src/bin/matrix.rs @@ -6,7 +6,7 @@ extern crate user_lib; use user_lib::{fork, wait, yield_, exit, getpid, get_time}; -static NUM: usize = 35; +static NUM: usize = 30; const N: usize = 10; static P: i32 = 10007; type Arr = [[i32; N]; N]; @@ -65,4 +65,4 @@ pub fn main() -> i32 { assert!(wait(&mut exit_code) < 0); println!("matrix passed."); 0 -} \ No newline at end of file +} From 737340b0a02dec24bd69047a21ab3212d7e1a7f2 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Thu, 20 Jan 2022 16:20:03 -0800 Subject: [PATCH 19/56] Bump Rust to nightly-2022-01-19 --- rust-toolchain | 2 +- user/src/bin/race_adder_arg.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust-toolchain b/rust-toolchain index a8a6159..925656d 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2022-01-01 +nightly-2022-01-19 diff --git a/user/src/bin/race_adder_arg.rs b/user/src/bin/race_adder_arg.rs index 8d7758c..4c386be 100644 --- a/user/src/bin/race_adder_arg.rs +++ b/user/src/bin/race_adder_arg.rs @@ -29,7 +29,7 @@ unsafe fn f(count:usize) -> ! { #[no_mangle] pub fn main(argc: usize, argv: &[&str]) -> i32 { - let mut count = 0; + let count: usize; if argc == 1 { count = THREAD_COUNT; } else if argc == 2 { From f495dbb1e68c7219f841b36d0017aba170aef0fa Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Fri, 21 Jan 2022 10:11:07 -0800 Subject: [PATCH 20/56] Ref asm&global_asm from core::arch. --- os/src/lang_items.rs | 1 + os/src/main.rs | 4 ++-- os/src/mm/memory_set.rs | 1 + os/src/sbi.rs | 2 +- os/src/task/switch.rs | 2 ++ os/src/trap/mod.rs | 1 + rust-toolchain | 2 +- user/src/syscall.rs | 2 +- 8 files changed, 10 insertions(+), 5 deletions(-) diff --git a/os/src/lang_items.rs b/os/src/lang_items.rs index 1c1b65c..21c7c51 100644 --- a/os/src/lang_items.rs +++ b/os/src/lang_items.rs @@ -1,4 +1,5 @@ use core::panic::PanicInfo; +use core::arch::asm; use crate::sbi::shutdown; use crate::task::current_kstack_top; diff --git a/os/src/main.rs b/os/src/main.rs index d4b0934..6019212 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -1,7 +1,5 @@ #![no_std] #![no_main] -#![feature(global_asm)] -#![feature(asm)] #![feature(panic_info_message)] #![feature(alloc_error_handler)] @@ -10,6 +8,8 @@ extern crate alloc; #[macro_use] extern crate bitflags; +use core::arch::global_asm; + #[macro_use] mod console; mod lang_items; diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index 575f5a3..30bc768 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -14,6 +14,7 @@ use crate::config::{ TRAMPOLINE, MMIO, }; +use core::arch::asm; extern "C" { fn stext(); diff --git a/os/src/sbi.rs b/os/src/sbi.rs index 276c199..6231c9c 100644 --- a/os/src/sbi.rs +++ b/os/src/sbi.rs @@ -14,7 +14,7 @@ const SBI_SHUTDOWN: usize = 8; fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize { let mut ret; unsafe { - asm!( + core::arch::asm!( "ecall", inlateout("x10") arg0 => ret, in("x11") arg1, diff --git a/os/src/task/switch.rs b/os/src/task/switch.rs index fa75be1..5947681 100644 --- a/os/src/task/switch.rs +++ b/os/src/task/switch.rs @@ -1,3 +1,5 @@ +use core::arch::global_asm; + global_asm!(include_str!("switch.S")); use super::TaskContext; diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index 136de92..964c904 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -1,5 +1,6 @@ mod context; +use core::arch::{asm, global_asm}; use riscv::register::{ mtvec::TrapMode, stvec, diff --git a/rust-toolchain b/rust-toolchain index fd47275..925656d 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2021-10-15 +nightly-2022-01-19 diff --git a/user/src/syscall.rs b/user/src/syscall.rs index 64f738d..ddd0e1b 100644 --- a/user/src/syscall.rs +++ b/user/src/syscall.rs @@ -25,7 +25,7 @@ const SYSCALL_SEMAPHORE_DOWN: usize = 1022; fn syscall(id: usize, args: [usize; 3]) -> isize { let mut ret: isize; unsafe { - asm!( + core::arch::asm!( "ecall", inlateout("x10") args[0] => ret, in("x11") args[1], From f4fd3ee9a7dc28d37a9bf9ea6544c1228865f3b7 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Fri, 21 Jan 2022 11:53:42 -0800 Subject: [PATCH 21/56] Update README.md --- README.md | 161 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 590409a..7868584 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,168 @@ This project aims to show how to write an **Unix-like OS** running on **RISC-V** * **only 4K+ LoC** * [A detailed documentation in Chinese](https://rcore-os.github.io/rCore-Tutorial-Book-v3/) in spite of the lack of comments in the code(English version is not available at present) +## Prerequisites + +### Install Rust + +See [official guide](https://www.rust-lang.org/tools/install). + +Install some tools: + +```sh +$ rustup target add riscv64gc-unknown-none-elf +$ cargo install cargo-binutils --vers =0.3.3 +$ rustup component add llvm-tools-preview +$ rustup component add rust-src +``` + +### Install Qemu + +Here we manually compile and install Qemu 5.0.0. For example, on Ubuntu 18.04: + +```sh +# install dependency packages +$ sudo apt install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev \ + gawk build-essential bison flex texinfo gperf libtool patchutils bc \ + zlib1g-dev libexpat-dev pkg-config libglib2.0-dev libpixman-1-dev git tmux python3 python3-pip +# download Qemu source code +$ wget https://download.qemu.org/qemu-5.0.0.tar.xz +# extract to qemu-5.0.0/ +$ tar xvJf qemu-5.0.0.tar.xz +$ cd qemu-5.0.0 +# build +$ ./configure --target-list=riscv64-softmmu,riscv64-linux-user +$ make -j$(nproc) +``` + +Then, add following contents to `~/.bashrc`(please adjust these paths according to your environment): + +``` +export PATH=$PATH:/home/shinbokuow/Downloads/built/qemu-5.0.0 +export PATH=$PATH:/home/shinbokuow/Downloads/built/qemu-5.0.0/riscv64-softmmu +export PATH=$PATH:/home/shinbokuow/Downloads/built/qemu-5.0.0/riscv64-linux-user +``` + +Finally, update the current shell: + +```sh +$ source ~/.bashrc +``` + +Now we can check the version of Qemu: + +```sh +$ qemu-system-riscv64 --version +QEMU emulator version 5.0.0 +Copyright (c) 2003-2020 Fabrice Bellard and the QEMU Project developers +``` + +### Install RISC-V GNU Embedded Toolchain(including GDB) + +Download the compressed file according to your platform From [Sifive website](https://www.sifive.com/software)(Ctrl+F 'toolchain'). + +Extract it and append the location of the 'bin' directory under its root directory to `$PATH`. + +For example, we can check the version of GDB: + +```sh +$ riscv64-unknown-elf-gdb --version +GNU gdb (SiFive GDB-Metal 10.1.0-2020.12.7) 10.1 +Copyright (C) 2020 Free Software Foundation, Inc. +License GPLv3+: GNU GPL version 3 or later +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. +``` + +### Install serial tools(Optional, if you want to run on K210) + +```sh +$ pip3 install pyserial +$ sudo apt install python3-serial +``` + ## Run our project -TODO: +### Qemu + +```sh +$ git clone https://github.com/rcore-os/rCore-Tutorial-v3.git +$ cd rCore-Tutorial-v3/os +$ make run +``` + +After outputing some debug messages, the kernel lists all the applications available and enter the user shell: + +``` +/**** APPS **** +mpsc_sem +usertests +pipetest +forktest2 +cat +initproc +race_adder_loop +threads_arg +race_adder_mutex_spin +race_adder_mutex_blocking +forktree +user_shell +huge_write +race_adder +race_adder_atomic +threads +stack_overflow +filetest_simple +forktest_simple +cmdline_args +run_pipe_test +forktest +matrix +exit +fantastic_text +sleep_simple +yield +hello_world +pipe_large_test +sleep +phil_din_mutex +**************/ +Rust user shell +>> +``` + +You can run any application except for `initproc` and `user_shell` itself. To run an application, just input its filename and hit enter. `usertests` can run a bunch of applications, thus it is recommended. + +Type `Ctrl+a` then `x` to exit Qemu. + +### K210 + +Before chapter 6, you do not need a SD card: + +```sh +$ git clone https://github.com/rcore-os/rCore-Tutorial-v3.git +$ cd rCore-Tutorial-v3/os +$ make run BOARD=k210 +``` + +From chapter 6, before running the kernel, we should insert a SD card into PC and manually write the filesystem image to it: + +```sh +$ cd rCore-Tutorial-v3/os +$ make sdcard +``` + +By default it will overwrite the device `/dev/sdb` which is the SD card, but you can provide another location. For example, `make sdcard SDCARD=/dev/sdc`. + +After that, remove the SD card from PC and insert it to the slot of K210. Connect the K210 to PC and then: + +```sh +$ git clone https://github.com/rcore-os/rCore-Tutorial-v3.git +$ cd rCore-Tutorial-v3/os +$ make run BOARD=k210 +``` + +Type `Ctrl+]` to disconnect from K210. ## Working in progress From c9583b0f53d86f6efb5d9952b31a6433ed692665 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Sat, 22 Jan 2022 12:32:36 -0800 Subject: [PATCH 22/56] Merge recent update from ch7 && cargo clippy --- easy-fs-fuse/src/main.rs | 2 +- easy-fs/src/bitmap.rs | 4 +- easy-fs/src/layout.rs | 43 +++--- easy-fs/src/vfs.rs | 5 +- os/src/fs/pipe.rs | 10 +- os/src/mm/frame_allocator.rs | 12 +- user/src/bin/cmdline_args.rs | 6 +- user/src/bin/huge_write.rs | 4 +- user/src/bin/initproc.rs | 5 +- user/src/bin/matrix.rs | 1 + user/src/bin/mpsc_sem.rs | 1 + user/src/bin/phil_din_mutex.rs | 9 +- user/src/bin/pipe_large_test.rs | 6 +- user/src/bin/run_pipe_test.rs | 4 +- user/src/bin/sync_sem.rs | 9 +- user/src/bin/test_condvar.rs | 9 +- user/src/bin/threads.rs | 11 +- user/src/bin/threads_arg.rs | 4 +- user/src/bin/user_shell.rs | 250 +++++++++++++++++++++----------- user/src/bin/usertests.rs | 4 +- 20 files changed, 236 insertions(+), 163 deletions(-) diff --git a/easy-fs-fuse/src/main.rs b/easy-fs-fuse/src/main.rs index cf07b61..09e9345 100644 --- a/easy-fs-fuse/src/main.rs +++ b/easy-fs-fuse/src/main.rs @@ -61,7 +61,7 @@ fn easy_fs_pack() -> std::io::Result<()> { }))); // 16MiB, at most 4095 files let efs = EasyFileSystem::create( - block_file.clone(), + block_file, 16 * 2048, 1, ); diff --git a/easy-fs/src/bitmap.rs b/easy-fs/src/bitmap.rs index 4feaa9c..5474e74 100644 --- a/easy-fs/src/bitmap.rs +++ b/easy-fs/src/bitmap.rs @@ -17,7 +17,7 @@ pub struct Bitmap { /// Return (block_pos, bits64_pos, inner_pos) fn decomposition(mut bit: usize) -> (usize, usize, usize) { let block_pos = bit / BLOCK_BITS; - bit = bit % BLOCK_BITS; + bit %= BLOCK_BITS; (block_pos, bit / 64, bit % 64) } @@ -70,4 +70,4 @@ impl Bitmap { pub fn maximum(&self) -> usize { self.blocks * BLOCK_BITS } -} \ No newline at end of file +} diff --git a/easy-fs/src/layout.rs b/easy-fs/src/layout.rs index c6f7b31..b5295de 100644 --- a/easy-fs/src/layout.rs +++ b/easy-fs/src/layout.rs @@ -289,35 +289,26 @@ impl DiskInode { .lock() .modify(0, |indirect2: &mut IndirectBlock| { // full indirect1 blocks - for i in 0..a1 { - v.push(indirect2[i]); - get_block_cache( - indirect2[i] as usize, - Arc::clone(block_device), - ) - .lock() - .modify(0, |indirect1: &mut IndirectBlock| { - for j in 0..INODE_INDIRECT1_COUNT { - v.push(indirect1[j]); - //indirect1[j] = 0; - } - }); - //indirect2[i] = 0; + for entry in indirect2.iter_mut().take(a1) { + v.push(*entry); + get_block_cache(*entry as usize, Arc::clone(block_device)) + .lock() + .modify(0, |indirect1: &mut IndirectBlock| { + for entry in indirect1.iter() { + v.push(*entry); + } + }); } // last indirect1 block if b1 > 0 { v.push(indirect2[a1]); - get_block_cache( - indirect2[a1] as usize, - Arc::clone(block_device), - ) - .lock() - .modify(0, |indirect1: &mut IndirectBlock| { - for j in 0..b1 { - v.push(indirect1[j]); - //indirect1[j] = 0; - } - }); + get_block_cache(indirect2[a1] as usize, Arc::clone(block_device)) + .lock() + .modify(0, |indirect1: &mut IndirectBlock| { + for entry in indirect1.iter().take(b1) { + v.push(*entry); + } + }); //indirect2[a1] = 0; } }); @@ -445,4 +436,4 @@ impl DirEntry { pub fn inode_number(&self) -> u32 { self.inode_number } -} \ No newline at end of file +} diff --git a/easy-fs/src/vfs.rs b/easy-fs/src/vfs.rs index 9534c39..4dfd31b 100644 --- a/easy-fs/src/vfs.rs +++ b/easy-fs/src/vfs.rs @@ -110,12 +110,13 @@ impl Inode { pub fn create(&self, name: &str) -> Option> { let mut fs = self.fs.lock(); - if self.modify_disk_inode(|root_inode| { + let op = |root_inode: &mut DiskInode| { // assert it is a directory assert!(root_inode.is_dir()); // has the file been created? self.find_inode_id(name, root_inode) - }).is_some() { + }; + if self.modify_disk_inode(op).is_some() { return None; } // create a new file diff --git a/os/src/fs/pipe.rs b/os/src/fs/pipe.rs index ed2dde1..08d51da 100644 --- a/os/src/fs/pipe.rs +++ b/os/src/fs/pipe.rs @@ -78,12 +78,10 @@ impl PipeRingBuffer { pub fn available_read(&self) -> usize { if self.status == RingBufferStatus::EMPTY { 0 + } else if self.tail > self.head { + self.tail - self.head } else { - if self.tail > self.head { - self.tail - self.head - } else { - self.tail + RING_BUFFER_SIZE - self.head - } + self.tail + RING_BUFFER_SIZE - self.head } } pub fn available_write(&self) -> usize { @@ -165,4 +163,4 @@ impl File for Pipe { } } } -} \ No newline at end of file +} diff --git a/os/src/mm/frame_allocator.rs b/os/src/mm/frame_allocator.rs index 357db70..3afe336 100644 --- a/os/src/mm/frame_allocator.rs +++ b/os/src/mm/frame_allocator.rs @@ -62,13 +62,11 @@ impl FrameAllocator for StackFrameAllocator { fn alloc(&mut self) -> Option { if let Some(ppn) = self.recycled.pop() { Some(ppn.into()) + } else if self.current == self.end { + None } else { - if self.current == self.end { - None - } else { - self.current += 1; - Some((self.current - 1).into()) - } + self.current += 1; + Some((self.current - 1).into()) } } fn dealloc(&mut self, ppn: PhysPageNum) { @@ -131,4 +129,4 @@ pub fn frame_allocator_test() { } drop(v); println!("frame_allocator_test passed!"); -} \ No newline at end of file +} diff --git a/user/src/bin/cmdline_args.rs b/user/src/bin/cmdline_args.rs index b49ec33..9a22745 100644 --- a/user/src/bin/cmdline_args.rs +++ b/user/src/bin/cmdline_args.rs @@ -9,8 +9,8 @@ extern crate user_lib; #[no_mangle] pub fn main(argc: usize, argv: &[&str]) -> i32 { println!("argc = {}", argc); - for i in 0..argc { - println!("argv[{}] = {}", i, argv[i]); + for (i, arg) in argv.iter().enumerate() { + println!("argv[{}] = {}", i, arg); } 0 -} \ No newline at end of file +} diff --git a/user/src/bin/huge_write.rs b/user/src/bin/huge_write.rs index b00ca17..4b0daaf 100644 --- a/user/src/bin/huge_write.rs +++ b/user/src/bin/huge_write.rs @@ -15,8 +15,8 @@ use user_lib::{ #[no_mangle] pub fn main() -> i32 { let mut buffer = [0u8; 1024]; // 1KiB - for i in 0..buffer.len() { - buffer[i] = i as u8; + for (i, ch) in buffer.iter_mut().enumerate() { + *ch = i as u8; } let f = open("testf\0", OpenFlags::CREATE | OpenFlags::WRONLY); if f < 0 { diff --git a/user/src/bin/initproc.rs b/user/src/bin/initproc.rs index cf8840f..fba348c 100644 --- a/user/src/bin/initproc.rs +++ b/user/src/bin/initproc.rs @@ -1,7 +1,6 @@ #![no_std] #![no_main] -#[macro_use] extern crate user_lib; use user_lib::{ @@ -14,7 +13,7 @@ use user_lib::{ #[no_mangle] fn main() -> i32 { if fork() == 0 { - exec("user_shell\0", &[0 as *const u8]); + exec("user_shell\0", &[core::ptr::null::()]); } else { loop { let mut exit_code: i32 = 0; @@ -23,11 +22,13 @@ fn main() -> i32 { yield_(); continue; } + /* println!( "[initproc] Released a zombie process, pid={}, exit_code={}", pid, exit_code, ); + */ } } 0 diff --git a/user/src/bin/matrix.rs b/user/src/bin/matrix.rs index 8f1357e..c2461cf 100644 --- a/user/src/bin/matrix.rs +++ b/user/src/bin/matrix.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +#![allow(clippy::needless_range_loop)] #[macro_use] extern crate user_lib; diff --git a/user/src/bin/mpsc_sem.rs b/user/src/bin/mpsc_sem.rs index 71141e4..a60c14b 100644 --- a/user/src/bin/mpsc_sem.rs +++ b/user/src/bin/mpsc_sem.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +#![allow(clippy::println_empty_string)] #[macro_use] extern crate user_lib; diff --git a/user/src/bin/phil_din_mutex.rs b/user/src/bin/phil_din_mutex.rs index 97a4a8c..62baab6 100644 --- a/user/src/bin/phil_din_mutex.rs +++ b/user/src/bin/phil_din_mutex.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +#![allow(clippy::println_empty_string)] #[macro_use] extern crate user_lib; @@ -73,17 +74,17 @@ pub fn main() -> i32 { print!("#{}:", id); for j in 0..time_cost/GRAPH_SCALE { let current_time = j * GRAPH_SCALE + start; - if (0..ROUND).find(|round| unsafe { + if (0..ROUND).any(|round| unsafe { let start_thinking = THINK[id][2 * round]; let end_thinking = THINK[id][2 * round + 1]; start_thinking <= current_time && current_time <= end_thinking - }).is_some() { + }) { print!("-"); - } else if (0..ROUND).find(|round| unsafe { + } else if (0..ROUND).any(|round| unsafe { let start_eating = EAT[id][2 * round]; let end_eating = EAT[id][2 * round + 1]; start_eating <= current_time && current_time <= end_eating - }).is_some() { + }) { print!("x"); } else { print!(" "); diff --git a/user/src/bin/pipe_large_test.rs b/user/src/bin/pipe_large_test.rs index 121987b..6f4f500 100644 --- a/user/src/bin/pipe_large_test.rs +++ b/user/src/bin/pipe_large_test.rs @@ -40,8 +40,8 @@ pub fn main() -> i32 { // close write end of up pipe close(up_pipe_fd[1]); // generate a long random string - for i in 0..LENGTH { - random_str[i] = get_time() as u8; + for ch in random_str.iter_mut() { + *ch = get_time() as u8; } // send it assert_eq!(write(down_pipe_fd[1], &random_str) as usize, random_str.len()); @@ -66,4 +66,4 @@ pub fn main() -> i32 { println!("pipe_large_test passed!"); 0 } -} \ No newline at end of file +} diff --git a/user/src/bin/run_pipe_test.rs b/user/src/bin/run_pipe_test.rs index 000b82d..f0f95b0 100644 --- a/user/src/bin/run_pipe_test.rs +++ b/user/src/bin/run_pipe_test.rs @@ -10,7 +10,7 @@ use user_lib::{fork, exec, wait}; pub fn main() -> i32 { for i in 0..1000 { if fork() == 0 { - exec("pipe_large_test\0", &[0 as *const u8]); + exec("pipe_large_test\0", &[core::ptr::null::()]); } else { let mut _unused: i32 = 0; wait(&mut _unused); @@ -18,4 +18,4 @@ pub fn main() -> i32 { } } 0 -} \ No newline at end of file +} diff --git a/user/src/bin/sync_sem.rs b/user/src/bin/sync_sem.rs index d72d520..66b25fb 100644 --- a/user/src/bin/sync_sem.rs +++ b/user/src/bin/sync_sem.rs @@ -9,7 +9,7 @@ extern crate alloc; use user_lib::{semaphore_create, semaphore_up, semaphore_down}; use user_lib::{thread_create, waittid, sleep}; use user_lib::exit; -use alloc::vec::Vec; +use alloc::vec; const SEM_SYNC: usize = 0; @@ -33,9 +33,10 @@ pub fn main() -> i32 { // create semaphores assert_eq!(semaphore_create(0) as usize, SEM_SYNC); // create threads - let mut threads = Vec::new(); - threads.push(thread_create(first as usize, 0)); - threads.push(thread_create(second as usize, 0)); + let threads = vec![ + thread_create(first as usize, 0), + thread_create(second as usize, 0), + ]; // wait for all threads to complete for thread in threads.iter() { waittid(*thread as usize); diff --git a/user/src/bin/test_condvar.rs b/user/src/bin/test_condvar.rs index 6aa49ef..6caa53e 100644 --- a/user/src/bin/test_condvar.rs +++ b/user/src/bin/test_condvar.rs @@ -9,7 +9,7 @@ extern crate alloc; use user_lib::{condvar_create, condvar_signal, condvar_wait, mutex_blocking_create, mutex_lock, mutex_unlock}; use user_lib::{thread_create, waittid, sleep}; use user_lib::exit; -use alloc::vec::Vec; +use alloc::vec; static mut A: usize = 0; @@ -44,9 +44,10 @@ pub fn main() -> i32 { assert_eq!(condvar_create() as usize, CONDVAR_ID); assert_eq!(mutex_blocking_create() as usize, MUTEX_ID); // create threads - let mut threads = Vec::new(); - threads.push(thread_create(first as usize, 0)); - threads.push(thread_create(second as usize, 0)); + let threads = vec![ + thread_create(first as usize, 0), + thread_create(second as usize, 0), + ]; // wait for all threads to complete for thread in threads.iter() { waittid(*thread as usize); diff --git a/user/src/bin/threads.rs b/user/src/bin/threads.rs index 97ce43f..851766d 100644 --- a/user/src/bin/threads.rs +++ b/user/src/bin/threads.rs @@ -6,7 +6,7 @@ extern crate user_lib; extern crate alloc; use user_lib::{thread_create, waittid, exit}; -use alloc::vec::Vec; +use alloc::vec; pub fn thread_a() -> ! { for _ in 0..1000 { print!("a"); } @@ -25,10 +25,11 @@ pub fn thread_c() -> ! { #[no_mangle] pub fn main() -> i32 { - let mut v = Vec::new(); - v.push(thread_create(thread_a as usize, 0)); - v.push(thread_create(thread_b as usize, 0)); - v.push(thread_create(thread_c as usize, 0)); + let v = vec![ + thread_create(thread_a as usize, 0), + thread_create(thread_b as usize, 0), + thread_create(thread_c as usize, 0), + ]; for tid in v.iter() { let exit_code = waittid(*tid as usize); println!("thread#{} exited with code {}", tid, exit_code); diff --git a/user/src/bin/threads_arg.rs b/user/src/bin/threads_arg.rs index a29b23d..c5b3b30 100644 --- a/user/src/bin/threads_arg.rs +++ b/user/src/bin/threads_arg.rs @@ -27,8 +27,8 @@ pub fn main() -> i32 { Argument { ch: 'b', rc: 2, }, Argument { ch: 'c', rc: 3, }, ]; - for i in 0..3 { - v.push(thread_create(thread_print as usize, &args[i] as *const _ as usize)); + for arg in args.iter() { + v.push(thread_create(thread_print as usize, arg as *const _ as usize)); } for tid in v.iter() { let exit_code = waittid(*tid as usize); diff --git a/user/src/bin/user_shell.rs b/user/src/bin/user_shell.rs index b2c33f2..5da878c 100644 --- a/user/src/bin/user_shell.rs +++ b/user/src/bin/user_shell.rs @@ -1,5 +1,7 @@ + #![no_std] #![no_main] +#![allow(clippy::println_empty_string)] extern crate alloc; @@ -10,116 +12,191 @@ const LF: u8 = 0x0au8; const CR: u8 = 0x0du8; const DL: u8 = 0x7fu8; const BS: u8 = 0x08u8; +const LINE_START: &str = ">> "; use alloc::string::String; use alloc::vec::Vec; -use user_lib::{ - fork, - exec, - waitpid, - open, - OpenFlags, - close, - dup, -}; use user_lib::console::getchar; +use user_lib::{close, dup, exec, fork, open, pipe, waitpid, OpenFlags}; + +#[derive(Debug)] +struct ProcessArguments { + input: String, + output: String, + args_copy: Vec, + args_addr: Vec<*const u8>, +} + +impl ProcessArguments { + pub fn new(command: &str) -> Self { + let args: Vec<_> = command.split(' ').collect(); + let mut args_copy: Vec = args + .iter() + .filter(|&arg| !arg.is_empty()) + .map(|&arg| { + let mut string = String::new(); + string.push_str(arg); + string.push('\0'); + string + }) + .collect(); + + // redirect input + let mut input = String::new(); + if let Some((idx, _)) = args_copy + .iter() + .enumerate() + .find(|(_, arg)| arg.as_str() == "<\0") + { + input = args_copy[idx + 1].clone(); + args_copy.drain(idx..=idx + 1); + } + + // redirect output + let mut output = String::new(); + if let Some((idx, _)) = args_copy + .iter() + .enumerate() + .find(|(_, arg)| arg.as_str() == ">\0") + { + output = args_copy[idx + 1].clone(); + args_copy.drain(idx..=idx + 1); + } + + let mut args_addr: Vec<*const u8> = args_copy.iter().map(|arg| arg.as_ptr()).collect(); + args_addr.push(core::ptr::null::()); + + Self { + input, + output, + args_copy, + args_addr, + } + } +} #[no_mangle] pub fn main() -> i32 { println!("Rust user shell"); let mut line: String = String::new(); - print!(">> "); + print!("{}", LINE_START); loop { let c = getchar(); match c { LF | CR => { println!(""); if !line.is_empty() { - let args: Vec<_> = line.as_str().split(' ').collect(); - let mut args_copy: Vec = args - .iter() - .map(|&arg| { - let mut string = String::new(); - string.push_str(arg); - string - }) - .collect(); - - args_copy - .iter_mut() - .for_each(|string| { - string.push('\0'); - }); - - // redirect input - let mut input = String::new(); - if let Some((idx, _)) = args_copy - .iter() - .enumerate() - .find(|(_, arg)| arg.as_str() == "<\0") { - input = args_copy[idx + 1].clone(); - args_copy.drain(idx..=idx + 1); - } - - // redirect output - let mut output = String::new(); - if let Some((idx, _)) = args_copy - .iter() - .enumerate() - .find(|(_, arg)| arg.as_str() == ">\0") { - output = args_copy[idx + 1].clone(); - args_copy.drain(idx..=idx + 1); - } - - let mut args_addr: Vec<*const u8> = args_copy + let splited: Vec<_> = line.as_str().split('|').collect(); + let process_arguments_list: Vec<_> = splited .iter() - .map(|arg| arg.as_ptr()) + .map(|&cmd| ProcessArguments::new(cmd)) .collect(); - args_addr.push(0 as *const u8); - let pid = fork(); - if pid == 0 { - // input redirection - if !input.is_empty() { - let input_fd = open(input.as_str(), OpenFlags::RDONLY); - if input_fd == -1 { - println!("Error when opening file {}", input); - return -4; + let mut valid = true; + for (i, process_args) in process_arguments_list.iter().enumerate() { + if i == 0 { + if !process_args.output.is_empty() { + valid = false; } - let input_fd = input_fd as usize; - close(0); - assert_eq!(dup(input_fd), 0); - close(input_fd); - } - // output redirection - if !output.is_empty() { - let output_fd = open( - output.as_str(), - OpenFlags::CREATE | OpenFlags::WRONLY - ); - if output_fd == -1 { - println!("Error when opening file {}", output); - return -4; + } else if i == process_arguments_list.len() - 1 { + if !process_args.input.is_empty() { + valid = false; } - let output_fd = output_fd as usize; - close(1); - assert_eq!(dup(output_fd), 1); - close(output_fd); + } else if !process_args.output.is_empty() || !process_args.input.is_empty() + { + valid = false; } - // child process - if exec(args_copy[0].as_str(), args_addr.as_slice()) == -1 { - println!("Error when executing!"); - return -4; - } - unreachable!(); + } + if process_arguments_list.len() == 1 { + valid = true; + } + if !valid { + println!("Invalid command: Inputs/Outputs cannot be correctly binded!"); } else { + // create pipes + let mut pipes_fd: Vec<[usize; 2]> = Vec::new(); + if !process_arguments_list.is_empty() { + for _ in 0..process_arguments_list.len() - 1 { + let mut pipe_fd = [0usize; 2]; + pipe(&mut pipe_fd); + pipes_fd.push(pipe_fd); + } + } + let mut children: Vec<_> = Vec::new(); + for (i, process_argument) in process_arguments_list.iter().enumerate() { + let pid = fork(); + if pid == 0 { + let input = &process_argument.input; + let output = &process_argument.output; + let args_copy = &process_argument.args_copy; + let args_addr = &process_argument.args_addr; + // redirect input + if !input.is_empty() { + let input_fd = open(input.as_str(), OpenFlags::RDONLY); + if input_fd == -1 { + println!("Error when opening file {}", input); + return -4; + } + let input_fd = input_fd as usize; + close(0); + assert_eq!(dup(input_fd), 0); + close(input_fd); + } + // redirect output + if !output.is_empty() { + let output_fd = open( + output.as_str(), + OpenFlags::CREATE | OpenFlags::WRONLY, + ); + if output_fd == -1 { + println!("Error when opening file {}", output); + return -4; + } + let output_fd = output_fd as usize; + close(1); + assert_eq!(dup(output_fd), 1); + close(output_fd); + } + // receive input from the previous process + if i > 0 { + close(0); + let read_end = pipes_fd.get(i - 1).unwrap()[0]; + assert_eq!(dup(read_end), 0); + } + // send output to the next process + if i < process_arguments_list.len() - 1 { + close(1); + let write_end = pipes_fd.get(i).unwrap()[1]; + assert_eq!(dup(write_end), 1); + } + // close all pipe ends inherited from the parent process + for pipe_fd in pipes_fd.iter() { + close(pipe_fd[0]); + close(pipe_fd[1]); + } + // execute new application + if exec(args_copy[0].as_str(), args_addr.as_slice()) == -1 { + println!("Error when executing!"); + return -4; + } + unreachable!(); + } else { + children.push(pid); + } + } + for pipe_fd in pipes_fd.iter() { + close(pipe_fd[0]); + close(pipe_fd[1]); + } let mut exit_code: i32 = 0; - let exit_pid = waitpid(pid as usize, &mut exit_code); - assert_eq!(pid, exit_pid); - println!("Shell: Process {} exited with code {}", pid, exit_code); + for pid in children.into_iter() { + let exit_pid = waitpid(pid as usize, &mut exit_code); + assert_eq!(pid, exit_pid); + //println!("Shell: Process {} exited with code {}", pid, exit_code); + } } line.clear(); } - print!(">> "); + print!("{}", LINE_START); } BS | DL => { if !line.is_empty() { @@ -135,4 +212,5 @@ pub fn main() -> i32 { } } } -} \ No newline at end of file +} + diff --git a/user/src/bin/usertests.rs b/user/src/bin/usertests.rs index e8be6c4..83da4d5 100644 --- a/user/src/bin/usertests.rs +++ b/user/src/bin/usertests.rs @@ -26,7 +26,7 @@ pub fn main() -> i32 { println!("Usertests: Running {}", test); let pid = fork(); if pid == 0 { - exec(*test, &[0 as *const u8]); + exec(*test, &[core::ptr::null::()]); panic!("unreachable!"); } else { let mut exit_code: i32 = Default::default(); @@ -37,4 +37,4 @@ pub fn main() -> i32 { } println!("Usertests passed!"); 0 -} \ No newline at end of file +} From ae3ba9c26fa198be16295bf22f76cac4b694a893 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Sat, 22 Jan 2022 12:40:54 -0800 Subject: [PATCH 23/56] Small Fix && cargo fmt --- easy-fs-fuse/src/main.rs | 61 +++---- easy-fs/src/bitmap.rs | 28 ++- easy-fs/src/block_cache.rs | 58 +++--- easy-fs/src/block_dev.rs | 2 +- easy-fs/src/efs.rs | 100 +++++------ easy-fs/src/layout.rs | 206 +++++++++------------- easy-fs/src/lib.rs | 14 +- easy-fs/src/vfs.rs | 65 +++---- os/src/config.rs | 32 ++-- os/src/console.rs | 4 +- os/src/drivers/block/mod.rs | 10 +- os/src/drivers/block/sdcard.rs | 43 +++-- os/src/drivers/block/virtio_blk.rs | 50 +++--- os/src/drivers/mod.rs | 2 +- os/src/fs/inode.rs | 68 +++---- os/src/fs/mod.rs | 8 +- os/src/fs/pipe.rs | 28 +-- os/src/fs/stdio.rs | 26 ++- os/src/lang_items.rs | 23 ++- os/src/main.rs | 22 ++- os/src/mm/address.rs | 136 +++++++++----- os/src/mm/frame_allocator.rs | 27 ++- os/src/mm/heap_allocator.rs | 2 +- os/src/mm/memory_set.rs | 205 ++++++++++++--------- os/src/mm/mod.rs | 28 ++- os/src/mm/page_table.rs | 54 +++--- os/src/sbi.rs | 1 - os/src/sync/condvar.rs | 14 +- os/src/sync/mod.rs | 10 +- os/src/sync/mutex.rs | 6 +- os/src/sync/semaphore.rs | 12 +- os/src/sync/up.rs | 6 +- os/src/syscall/fs.rs | 24 +-- os/src/syscall/mod.rs | 7 +- os/src/syscall/process.rs | 43 ++--- os/src/syscall/sync.rs | 30 ++-- os/src/syscall/thread.rs | 20 ++- os/src/task/context.rs | 1 - os/src/task/id.rs | 94 +++++----- os/src/task/manager.rs | 11 +- os/src/task/mod.rs | 36 ++-- os/src/task/process.rs | 112 ++++++------ os/src/task/processor.rs | 36 ++-- os/src/task/switch.rs | 5 +- os/src/task/task.rs | 20 +-- os/src/timer.rs | 26 ++- os/src/trap/context.rs | 6 +- os/src/trap/mod.rs | 50 +++--- user/src/bin/cat.rs | 11 +- user/src/bin/exit.rs | 7 +- user/src/bin/fantastic_text.rs | 2 +- user/src/bin/filetest_simple.rs | 15 +- user/src/bin/forktest.rs | 2 +- user/src/bin/forktest2.rs | 7 +- user/src/bin/forktest_simple.rs | 2 +- user/src/bin/forktree.rs | 2 +- user/src/bin/hello_world.rs | 2 +- user/src/bin/huge_write.rs | 15 +- user/src/bin/initproc.rs | 7 +- user/src/bin/matrix.rs | 2 +- user/src/bin/mpsc_sem.rs | 11 +- user/src/bin/phil_din_mutex.rs | 29 ++- user/src/bin/pipe_large_test.rs | 12 +- user/src/bin/pipetest.rs | 4 +- user/src/bin/race_adder.rs | 8 +- user/src/bin/race_adder_arg.rs | 5 +- user/src/bin/race_adder_atomic.rs | 13 +- user/src/bin/race_adder_loop.rs | 12 +- user/src/bin/race_adder_mutex_blocking.rs | 10 +- user/src/bin/race_adder_mutex_spin.rs | 10 +- user/src/bin/run_pipe_test.rs | 2 +- user/src/bin/sleep.rs | 6 +- user/src/bin/sleep_simple.rs | 8 +- user/src/bin/stack_overflow.rs | 4 +- user/src/bin/sync_sem.rs | 7 +- user/src/bin/test_condvar.rs | 12 +- user/src/bin/threads.rs | 16 +- user/src/bin/threads_arg.rs | 19 +- user/src/bin/user_shell.rs | 2 - user/src/bin/usertests.rs | 5 +- user/src/bin/yield.rs | 2 +- user/src/lang_items.rs | 9 +- user/src/syscall.rs | 12 +- 83 files changed, 1085 insertions(+), 1079 deletions(-) diff --git a/easy-fs-fuse/src/main.rs b/easy-fs-fuse/src/main.rs index 09e9345..cac1b79 100644 --- a/easy-fs-fuse/src/main.rs +++ b/easy-fs-fuse/src/main.rs @@ -1,12 +1,9 @@ -use easy_fs::{ - BlockDevice, - EasyFileSystem, -}; -use std::fs::{File, OpenOptions, read_dir}; -use std::io::{Read, Write, Seek, SeekFrom}; -use std::sync::Mutex; +use clap::{App, Arg}; +use easy_fs::{BlockDevice, EasyFileSystem}; +use std::fs::{read_dir, File, OpenOptions}; +use std::io::{Read, Seek, SeekFrom, Write}; use std::sync::Arc; -use clap::{Arg, App}; +use std::sync::Mutex; const BLOCK_SZ: usize = 512; @@ -34,17 +31,19 @@ fn main() { fn easy_fs_pack() -> std::io::Result<()> { let matches = App::new("EasyFileSystem packer") - .arg(Arg::with_name("source") - .short("s") - .long("source") - .takes_value(true) - .help("Executable source dir(with backslash)") + .arg( + Arg::with_name("source") + .short("s") + .long("source") + .takes_value(true) + .help("Executable source dir(with backslash)"), ) - .arg(Arg::with_name("target") - .short("t") - .long("target") - .takes_value(true) - .help("Executable target dir(with backslash)") + .arg( + Arg::with_name("target") + .short("t") + .long("target") + .takes_value(true) + .help("Executable target dir(with backslash)"), ) .get_matches(); let src_path = matches.value_of("source").unwrap(); @@ -60,11 +59,7 @@ fn easy_fs_pack() -> std::io::Result<()> { f }))); // 16MiB, at most 4095 files - let efs = EasyFileSystem::create( - block_file, - 16 * 2048, - 1, - ); + let efs = EasyFileSystem::create(block_file, 16 * 2048, 1); let root_inode = Arc::new(EasyFileSystem::root_inode(&efs)); let apps: Vec<_> = read_dir(src_path) .unwrap() @@ -103,11 +98,7 @@ fn efs_test() -> std::io::Result<()> { f.set_len(8192 * 512).unwrap(); f }))); - EasyFileSystem::create( - block_file.clone(), - 4096, - 1, - ); + EasyFileSystem::create(block_file.clone(), 4096, 1); let efs = EasyFileSystem::open(block_file.clone()); let root_inode = EasyFileSystem::root_inode(&efs); root_inode.create("filea"); @@ -121,17 +112,11 @@ fn efs_test() -> std::io::Result<()> { //let mut buffer = [0u8; 512]; let mut buffer = [0u8; 233]; let len = filea.read_at(0, &mut buffer); - assert_eq!( - greet_str, - core::str::from_utf8(&buffer[..len]).unwrap(), - ); + assert_eq!(greet_str, core::str::from_utf8(&buffer[..len]).unwrap(),); let mut random_str_test = |len: usize| { filea.clear(); - assert_eq!( - filea.read_at(0, &mut buffer), - 0, - ); + assert_eq!(filea.read_at(0, &mut buffer), 0,); let mut str = String::new(); use rand; // random digit @@ -148,9 +133,7 @@ fn efs_test() -> std::io::Result<()> { break; } offset += len; - read_str.push_str( - core::str::from_utf8(&read_buffer[..len]).unwrap() - ); + read_str.push_str(core::str::from_utf8(&read_buffer[..len]).unwrap()); } assert_eq!(str, read_str); }; diff --git a/easy-fs/src/bitmap.rs b/easy-fs/src/bitmap.rs index 5474e74..2cea613 100644 --- a/easy-fs/src/bitmap.rs +++ b/easy-fs/src/bitmap.rs @@ -1,9 +1,5 @@ +use super::{get_block_cache, BlockDevice, BLOCK_SZ}; use alloc::sync::Arc; -use super::{ - BlockDevice, - BLOCK_SZ, - get_block_cache, -}; type BitmapBlock = [u64; 64]; @@ -34,14 +30,15 @@ impl Bitmap { let pos = get_block_cache( block_id + self.start_block_id as usize, Arc::clone(block_device), - ).lock().modify(0, |bitmap_block: &mut BitmapBlock| { + ) + .lock() + .modify(0, |bitmap_block: &mut BitmapBlock| { if let Some((bits64_pos, inner_pos)) = bitmap_block .iter() .enumerate() .find(|(_, bits64)| **bits64 != u64::MAX) - .map(|(bits64_pos, bits64)| { - (bits64_pos, bits64.trailing_ones() as usize) - }) { + .map(|(bits64_pos, bits64)| (bits64_pos, bits64.trailing_ones() as usize)) + { // modify cache bitmap_block[bits64_pos] |= 1u64 << inner_pos; Some(block_id * BLOCK_BITS + bits64_pos * 64 + inner_pos as usize) @@ -58,13 +55,12 @@ impl Bitmap { pub fn dealloc(&self, block_device: &Arc, bit: usize) { let (block_pos, bits64_pos, inner_pos) = decomposition(bit); - get_block_cache( - block_pos + self.start_block_id, - Arc::clone(block_device) - ).lock().modify(0, |bitmap_block: &mut BitmapBlock| { - assert!(bitmap_block[bits64_pos] & (1u64 << inner_pos) > 0); - bitmap_block[bits64_pos] -= 1u64 << inner_pos; - }); + get_block_cache(block_pos + self.start_block_id, Arc::clone(block_device)) + .lock() + .modify(0, |bitmap_block: &mut BitmapBlock| { + assert!(bitmap_block[bits64_pos] & (1u64 << inner_pos) > 0); + bitmap_block[bits64_pos] -= 1u64 << inner_pos; + }); } pub fn maximum(&self) -> usize { diff --git a/easy-fs/src/block_cache.rs b/easy-fs/src/block_cache.rs index ba945b1..4b90294 100644 --- a/easy-fs/src/block_cache.rs +++ b/easy-fs/src/block_cache.rs @@ -1,7 +1,4 @@ -use super::{ - BLOCK_SZ, - BlockDevice, -}; +use super::{BlockDevice, BLOCK_SZ}; use alloc::collections::VecDeque; use alloc::sync::Arc; use lazy_static::*; @@ -16,10 +13,7 @@ pub struct BlockCache { impl BlockCache { /// Load a new BlockCache from disk. - pub fn new( - block_id: usize, - block_device: Arc - ) -> Self { + pub fn new(block_id: usize, block_device: Arc) -> Self { let mut cache = [0u8; BLOCK_SZ]; block_device.read_block(block_id, &mut cache); Self { @@ -34,14 +28,20 @@ impl BlockCache { &self.cache[offset] as *const _ as usize } - pub fn get_ref(&self, offset: usize) -> &T where T: Sized { + pub fn get_ref(&self, offset: usize) -> &T + where + T: Sized, + { let type_size = core::mem::size_of::(); assert!(offset + type_size <= BLOCK_SZ); let addr = self.addr_of_offset(offset); - unsafe { &*(addr as *const T) } + unsafe { &*(addr as *const T) } } - pub fn get_mut(&mut self, offset: usize) -> &mut T where T: Sized { + pub fn get_mut(&mut self, offset: usize) -> &mut T + where + T: Sized, + { let type_size = core::mem::size_of::(); assert!(offset + type_size <= BLOCK_SZ); self.modified = true; @@ -53,7 +53,7 @@ impl BlockCache { f(self.get_ref(offset)) } - pub fn modify(&mut self, offset:usize, f: impl FnOnce(&mut T) -> V) -> V { + pub fn modify(&mut self, offset: usize, f: impl FnOnce(&mut T) -> V) -> V { f(self.get_mut(offset)) } @@ -79,7 +79,9 @@ pub struct BlockCacheManager { impl BlockCacheManager { pub fn new() -> Self { - Self { queue: VecDeque::new() } + Self { + queue: VecDeque::new(), + } } pub fn get_block_cache( @@ -87,27 +89,28 @@ impl BlockCacheManager { block_id: usize, block_device: Arc, ) -> Arc> { - if let Some(pair) = self.queue - .iter() - .find(|pair| pair.0 == block_id) { - Arc::clone(&pair.1) + if let Some(pair) = self.queue.iter().find(|pair| pair.0 == block_id) { + Arc::clone(&pair.1) } else { // substitute if self.queue.len() == BLOCK_CACHE_SIZE { // from front to tail - if let Some((idx, _)) = self.queue + if let Some((idx, _)) = self + .queue .iter() .enumerate() - .find(|(_, pair)| Arc::strong_count(&pair.1) == 1) { + .find(|(_, pair)| Arc::strong_count(&pair.1) == 1) + { self.queue.drain(idx..=idx); } else { panic!("Run out of BlockCache!"); } } // load block into mem and push back - let block_cache = Arc::new(Mutex::new( - BlockCache::new(block_id, Arc::clone(&block_device)) - )); + let block_cache = Arc::new(Mutex::new(BlockCache::new( + block_id, + Arc::clone(&block_device), + ))); self.queue.push_back((block_id, Arc::clone(&block_cache))); block_cache } @@ -115,16 +118,17 @@ impl BlockCacheManager { } lazy_static! { - pub static ref BLOCK_CACHE_MANAGER: Mutex = Mutex::new( - BlockCacheManager::new() - ); + pub static ref BLOCK_CACHE_MANAGER: Mutex = + Mutex::new(BlockCacheManager::new()); } pub fn get_block_cache( block_id: usize, - block_device: Arc + block_device: Arc, ) -> Arc> { - BLOCK_CACHE_MANAGER.lock().get_block_cache(block_id, block_device) + BLOCK_CACHE_MANAGER + .lock() + .get_block_cache(block_id, block_device) } pub fn block_cache_sync_all() { diff --git a/easy-fs/src/block_dev.rs b/easy-fs/src/block_dev.rs index 7a28275..8a01edd 100644 --- a/easy-fs/src/block_dev.rs +++ b/easy-fs/src/block_dev.rs @@ -1,6 +1,6 @@ use core::any::Any; -pub trait BlockDevice : Send + Sync + Any { +pub trait BlockDevice: Send + Sync + Any { fn read_block(&self, block_id: usize, buf: &mut [u8]); fn write_block(&self, block_id: usize, buf: &[u8]); } diff --git a/easy-fs/src/efs.rs b/easy-fs/src/efs.rs index 8b7adf2..82e95ae 100644 --- a/easy-fs/src/efs.rs +++ b/easy-fs/src/efs.rs @@ -1,16 +1,10 @@ -use alloc::sync::Arc; -use spin::Mutex; use super::{ - BlockDevice, - Bitmap, + block_cache_sync_all, get_block_cache, Bitmap, BlockDevice, DiskInode, DiskInodeType, Inode, SuperBlock, - DiskInode, - DiskInodeType, - Inode, - get_block_cache, - block_cache_sync_all, }; use crate::BLOCK_SZ; +use alloc::sync::Arc; +use spin::Mutex; pub struct EasyFileSystem { pub block_device: Arc, @@ -50,39 +44,36 @@ impl EasyFileSystem { }; // clear all blocks for i in 0..total_blocks { - get_block_cache( - i as usize, - Arc::clone(&block_device) - ) - .lock() - .modify(0, |data_block: &mut DataBlock| { - for byte in data_block.iter_mut() { *byte = 0; } - }); + get_block_cache(i as usize, Arc::clone(&block_device)) + .lock() + .modify(0, |data_block: &mut DataBlock| { + for byte in data_block.iter_mut() { + *byte = 0; + } + }); } // initialize SuperBlock - get_block_cache(0, Arc::clone(&block_device)) - .lock() - .modify(0, |super_block: &mut SuperBlock| { - super_block.initialize( - total_blocks, - inode_bitmap_blocks, - inode_area_blocks, - data_bitmap_blocks, - data_area_blocks, - ); - }); + get_block_cache(0, Arc::clone(&block_device)).lock().modify( + 0, + |super_block: &mut SuperBlock| { + super_block.initialize( + total_blocks, + inode_bitmap_blocks, + inode_area_blocks, + data_bitmap_blocks, + data_area_blocks, + ); + }, + ); // write back immediately // create a inode for root node "/" assert_eq!(efs.alloc_inode(), 0); let (root_inode_block_id, root_inode_offset) = efs.get_disk_inode_pos(0); - get_block_cache( - root_inode_block_id as usize, - Arc::clone(&block_device) - ) - .lock() - .modify(root_inode_offset, |disk_inode: &mut DiskInode| { - disk_inode.initialize(DiskInodeType::Directory); - }); + get_block_cache(root_inode_block_id as usize, Arc::clone(&block_device)) + .lock() + .modify(root_inode_offset, |disk_inode: &mut DiskInode| { + disk_inode.initialize(DiskInodeType::Directory); + }); block_cache_sync_all(); Arc::new(Mutex::new(efs)) } @@ -97,10 +88,7 @@ impl EasyFileSystem { super_block.inode_bitmap_blocks + super_block.inode_area_blocks; let efs = Self { block_device, - inode_bitmap: Bitmap::new( - 1, - super_block.inode_bitmap_blocks as usize - ), + inode_bitmap: Bitmap::new(1, super_block.inode_bitmap_blocks as usize), data_bitmap: Bitmap::new( (1 + inode_total_blocks) as usize, super_block.data_bitmap_blocks as usize, @@ -117,19 +105,17 @@ impl EasyFileSystem { // acquire efs lock temporarily let (block_id, block_offset) = efs.lock().get_disk_inode_pos(0); // release efs lock - Inode::new( - block_id, - block_offset, - Arc::clone(efs), - block_device, - ) + Inode::new(block_id, block_offset, Arc::clone(efs), block_device) } pub fn get_disk_inode_pos(&self, inode_id: u32) -> (u32, usize) { let inode_size = core::mem::size_of::(); let inodes_per_block = (BLOCK_SZ / inode_size) as u32; let block_id = self.inode_area_start_block + inode_id / inodes_per_block; - (block_id, (inode_id % inodes_per_block) as usize * inode_size) + ( + block_id, + (inode_id % inodes_per_block) as usize * inode_size, + ) } pub fn get_data_block_id(&self, data_block_id: u32) -> u32 { @@ -146,18 +132,16 @@ impl EasyFileSystem { } pub fn dealloc_data(&mut self, block_id: u32) { - get_block_cache( - block_id as usize, - Arc::clone(&self.block_device) - ) - .lock() - .modify(0, |data_block: &mut DataBlock| { - data_block.iter_mut().for_each(|p| { *p = 0; }) - }); + get_block_cache(block_id as usize, Arc::clone(&self.block_device)) + .lock() + .modify(0, |data_block: &mut DataBlock| { + data_block.iter_mut().for_each(|p| { + *p = 0; + }) + }); self.data_bitmap.dealloc( &self.block_device, - (block_id - self.data_area_start_block) as usize + (block_id - self.data_area_start_block) as usize, ) } - -} \ No newline at end of file +} diff --git a/easy-fs/src/layout.rs b/easy-fs/src/layout.rs index b5295de..618484c 100644 --- a/easy-fs/src/layout.rs +++ b/easy-fs/src/layout.rs @@ -1,11 +1,7 @@ -use core::fmt::{Debug, Formatter, Result}; -use super::{ - BLOCK_SZ, - BlockDevice, - get_block_cache, -}; +use super::{get_block_cache, BlockDevice, BLOCK_SZ}; use alloc::sync::Arc; use alloc::vec::Vec; +use core::fmt::{Debug, Formatter, Result}; const EFS_MAGIC: u32 = 0x3b800001; const INODE_DIRECT_COUNT: usize = 28; @@ -115,7 +111,8 @@ impl DiskInode { if data_blocks > INDIRECT1_BOUND { total += 1; // sub indirect1 - total += (data_blocks - INDIRECT1_BOUND + INODE_INDIRECT1_COUNT - 1) / INODE_INDIRECT1_COUNT; + total += + (data_blocks - INDIRECT1_BOUND + INODE_INDIRECT1_COUNT - 1) / INODE_INDIRECT1_COUNT; } total as u32 } @@ -135,22 +132,16 @@ impl DiskInode { }) } else { let last = inner_id - INDIRECT1_BOUND; - let indirect1 = get_block_cache( - self.indirect2 as usize, - Arc::clone(block_device) - ) - .lock() - .read(0, |indirect2: &IndirectBlock| { - indirect2[last / INODE_INDIRECT1_COUNT] - }); - get_block_cache( - indirect1 as usize, - Arc::clone(block_device) - ) - .lock() - .read(0, |indirect1: &IndirectBlock| { - indirect1[last % INODE_INDIRECT1_COUNT] - }) + let indirect1 = get_block_cache(self.indirect2 as usize, Arc::clone(block_device)) + .lock() + .read(0, |indirect2: &IndirectBlock| { + indirect2[last / INODE_INDIRECT1_COUNT] + }); + get_block_cache(indirect1 as usize, Arc::clone(block_device)) + .lock() + .read(0, |indirect1: &IndirectBlock| { + indirect1[last % INODE_INDIRECT1_COUNT] + }) } } pub fn increase_size( @@ -169,7 +160,7 @@ impl DiskInode { current_blocks += 1; } // alloc indirect1 - if total_blocks > INODE_DIRECT_COUNT as u32{ + if total_blocks > INODE_DIRECT_COUNT as u32 { if current_blocks == INODE_DIRECT_COUNT as u32 { self.indirect1 = new_blocks.next().unwrap(); } @@ -179,17 +170,14 @@ impl DiskInode { return; } // fill indirect1 - get_block_cache( - self.indirect1 as usize, - Arc::clone(block_device) - ) - .lock() - .modify(0, |indirect1: &mut IndirectBlock| { - while current_blocks < total_blocks.min(INODE_INDIRECT1_COUNT as u32) { - indirect1[current_blocks as usize] = new_blocks.next().unwrap(); - current_blocks += 1; - } - }); + get_block_cache(self.indirect1 as usize, Arc::clone(block_device)) + .lock() + .modify(0, |indirect1: &mut IndirectBlock| { + while current_blocks < total_blocks.min(INODE_INDIRECT1_COUNT as u32) { + indirect1[current_blocks as usize] = new_blocks.next().unwrap(); + current_blocks += 1; + } + }); // alloc indirect2 if total_blocks > INODE_INDIRECT1_COUNT as u32 { if current_blocks == INODE_INDIRECT1_COUNT as u32 { @@ -206,33 +194,27 @@ impl DiskInode { let a1 = total_blocks as usize / INODE_INDIRECT1_COUNT; let b1 = total_blocks as usize % INODE_INDIRECT1_COUNT; // alloc low-level indirect1 - get_block_cache( - self.indirect2 as usize, - Arc::clone(block_device) - ) - .lock() - .modify(0, |indirect2: &mut IndirectBlock| { - while (a0 < a1) || (a0 == a1 && b0 < b1) { - if b0 == 0 { - indirect2[a0] = new_blocks.next().unwrap(); + get_block_cache(self.indirect2 as usize, Arc::clone(block_device)) + .lock() + .modify(0, |indirect2: &mut IndirectBlock| { + while (a0 < a1) || (a0 == a1 && b0 < b1) { + if b0 == 0 { + indirect2[a0] = new_blocks.next().unwrap(); + } + // fill current + get_block_cache(indirect2[a0] as usize, Arc::clone(block_device)) + .lock() + .modify(0, |indirect1: &mut IndirectBlock| { + indirect1[b0] = new_blocks.next().unwrap(); + }); + // move to next + b0 += 1; + if b0 == INODE_INDIRECT1_COUNT { + b0 = 0; + a0 += 1; + } } - // fill current - get_block_cache( - indirect2[a0] as usize, - Arc::clone(block_device) - ) - .lock() - .modify(0, |indirect1: &mut IndirectBlock| { - indirect1[b0] = new_blocks.next().unwrap(); - }); - // move to next - b0 += 1; - if b0 == INODE_INDIRECT1_COUNT { - b0 = 0; - a0 += 1; - } - } - }); + }); } /// Clear size to zero and return blocks that should be deallocated. @@ -258,18 +240,15 @@ impl DiskInode { return v; } // indirect1 - get_block_cache( - self.indirect1 as usize, - Arc::clone(block_device), - ) - .lock() - .modify(0, |indirect1: &mut IndirectBlock| { - while current_blocks < data_blocks.min(INODE_INDIRECT1_COUNT) { - v.push(indirect1[current_blocks]); - //indirect1[current_blocks] = 0; - current_blocks += 1; - } - }); + get_block_cache(self.indirect1 as usize, Arc::clone(block_device)) + .lock() + .modify(0, |indirect1: &mut IndirectBlock| { + while current_blocks < data_blocks.min(INODE_INDIRECT1_COUNT) { + v.push(indirect1[current_blocks]); + //indirect1[current_blocks] = 0; + current_blocks += 1; + } + }); self.indirect1 = 0; // indirect2 block if data_blocks > INODE_INDIRECT1_COUNT { @@ -282,36 +261,33 @@ impl DiskInode { assert!(data_blocks <= INODE_INDIRECT2_COUNT); let a1 = data_blocks / INODE_INDIRECT1_COUNT; let b1 = data_blocks % INODE_INDIRECT1_COUNT; - get_block_cache( - self.indirect2 as usize, - Arc::clone(block_device), - ) - .lock() - .modify(0, |indirect2: &mut IndirectBlock| { - // full indirect1 blocks - for entry in indirect2.iter_mut().take(a1) { - v.push(*entry); - get_block_cache(*entry as usize, Arc::clone(block_device)) - .lock() - .modify(0, |indirect1: &mut IndirectBlock| { - for entry in indirect1.iter() { - v.push(*entry); - } - }); - } - // last indirect1 block - if b1 > 0 { - v.push(indirect2[a1]); - get_block_cache(indirect2[a1] as usize, Arc::clone(block_device)) - .lock() - .modify(0, |indirect1: &mut IndirectBlock| { - for entry in indirect1.iter().take(b1) { - v.push(*entry); - } - }); - //indirect2[a1] = 0; - } - }); + get_block_cache(self.indirect2 as usize, Arc::clone(block_device)) + .lock() + .modify(0, |indirect2: &mut IndirectBlock| { + // full indirect1 blocks + for entry in indirect2.iter_mut().take(a1) { + v.push(*entry); + get_block_cache(*entry as usize, Arc::clone(block_device)) + .lock() + .modify(0, |indirect1: &mut IndirectBlock| { + for entry in indirect1.iter() { + v.push(*entry); + } + }); + } + // last indirect1 block + if b1 > 0 { + v.push(indirect2[a1]); + get_block_cache(indirect2[a1] as usize, Arc::clone(block_device)) + .lock() + .modify(0, |indirect1: &mut IndirectBlock| { + for entry in indirect1.iter().take(b1) { + v.push(*entry); + } + }); + //indirect2[a1] = 0; + } + }); self.indirect2 = 0; v } @@ -346,7 +322,9 @@ impl DiskInode { }); read_size += block_read_size; // move to next block - if end_current_block == end { break; } + if end_current_block == end { + break; + } start_block += 1; start = end_current_block; } @@ -372,7 +350,7 @@ impl DiskInode { let block_write_size = end_current_block - start; get_block_cache( self.get_block_id(start_block as u32, block_device) as usize, - Arc::clone(block_device) + Arc::clone(block_device), ) .lock() .modify(0, |data_block: &mut DataBlock| { @@ -382,7 +360,9 @@ impl DiskInode { }); write_size += block_write_size; // move to next block - if end_current_block == end { break; } + if end_current_block == end { + break; + } start_block += 1; start = end_current_block; } @@ -414,20 +394,10 @@ impl DirEntry { } } pub fn as_bytes(&self) -> &[u8] { - unsafe { - core::slice::from_raw_parts( - self as *const _ as usize as *const u8, - DIRENT_SZ, - ) - } + unsafe { core::slice::from_raw_parts(self as *const _ as usize as *const u8, DIRENT_SZ) } } pub fn as_bytes_mut(&mut self) -> &mut [u8] { - unsafe { - core::slice::from_raw_parts_mut( - self as *mut _ as usize as *mut u8, - DIRENT_SZ, - ) - } + unsafe { core::slice::from_raw_parts_mut(self as *mut _ as usize as *mut u8, DIRENT_SZ) } } pub fn name(&self) -> &str { let len = (0usize..).find(|i| self.name[*i] == 0).unwrap(); diff --git a/easy-fs/src/lib.rs b/easy-fs/src/lib.rs index afb957a..fa36e6b 100644 --- a/easy-fs/src/lib.rs +++ b/easy-fs/src/lib.rs @@ -2,17 +2,17 @@ extern crate alloc; -mod block_dev; -mod layout; -mod efs; mod bitmap; -mod vfs; mod block_cache; +mod block_dev; +mod efs; +mod layout; +mod vfs; pub const BLOCK_SZ: usize = 512; +use bitmap::Bitmap; +use block_cache::{block_cache_sync_all, get_block_cache}; pub use block_dev::BlockDevice; pub use efs::EasyFileSystem; -pub use vfs::Inode; use layout::*; -use bitmap::Bitmap; -use block_cache::{get_block_cache, block_cache_sync_all}; \ No newline at end of file +pub use vfs::Inode; diff --git a/easy-fs/src/vfs.rs b/easy-fs/src/vfs.rs index 4dfd31b..7290fa4 100644 --- a/easy-fs/src/vfs.rs +++ b/easy-fs/src/vfs.rs @@ -1,15 +1,9 @@ use super::{ - BlockDevice, - DiskInode, - DiskInodeType, - DirEntry, - EasyFileSystem, - DIRENT_SZ, - get_block_cache, - block_cache_sync_all, + block_cache_sync_all, get_block_cache, BlockDevice, DirEntry, DiskInode, DiskInodeType, + EasyFileSystem, DIRENT_SZ, }; -use alloc::sync::Arc; use alloc::string::String; +use alloc::sync::Arc; use alloc::vec::Vec; use spin::{Mutex, MutexGuard}; @@ -37,35 +31,25 @@ impl Inode { } fn read_disk_inode(&self, f: impl FnOnce(&DiskInode) -> V) -> V { - get_block_cache( - self.block_id, - Arc::clone(&self.block_device) - ).lock().read(self.block_offset, f) + get_block_cache(self.block_id, Arc::clone(&self.block_device)) + .lock() + .read(self.block_offset, f) } fn modify_disk_inode(&self, f: impl FnOnce(&mut DiskInode) -> V) -> V { - get_block_cache( - self.block_id, - Arc::clone(&self.block_device) - ).lock().modify(self.block_offset, f) + get_block_cache(self.block_id, Arc::clone(&self.block_device)) + .lock() + .modify(self.block_offset, f) } - fn find_inode_id( - &self, - name: &str, - disk_inode: &DiskInode, - ) -> Option { + fn find_inode_id(&self, name: &str, disk_inode: &DiskInode) -> Option { // assert it is a directory assert!(disk_inode.is_dir()); let file_count = (disk_inode.size as usize) / DIRENT_SZ; let mut dirent = DirEntry::empty(); for i in 0..file_count { assert_eq!( - disk_inode.read_at( - DIRENT_SZ * i, - dirent.as_bytes_mut(), - &self.block_device, - ), + disk_inode.read_at(DIRENT_SZ * i, dirent.as_bytes_mut(), &self.block_device,), DIRENT_SZ, ); if dirent.name() == name { @@ -78,8 +62,7 @@ impl Inode { pub fn find(&self, name: &str) -> Option> { let fs = self.fs.lock(); self.read_disk_inode(|disk_inode| { - self.find_inode_id(name, disk_inode) - .map(|inode_id| { + self.find_inode_id(name, disk_inode).map(|inode_id| { let (block_id, block_offset) = fs.get_disk_inode_pos(inode_id); Arc::new(Self::new( block_id, @@ -123,14 +106,12 @@ impl Inode { // alloc a inode with an indirect block let new_inode_id = fs.alloc_inode(); // initialize inode - let (new_inode_block_id, new_inode_block_offset) - = fs.get_disk_inode_pos(new_inode_id); - get_block_cache( - new_inode_block_id as usize, - Arc::clone(&self.block_device) - ).lock().modify(new_inode_block_offset, |new_inode: &mut DiskInode| { - new_inode.initialize(DiskInodeType::File); - }); + let (new_inode_block_id, new_inode_block_offset) = fs.get_disk_inode_pos(new_inode_id); + get_block_cache(new_inode_block_id as usize, Arc::clone(&self.block_device)) + .lock() + .modify(new_inode_block_offset, |new_inode: &mut DiskInode| { + new_inode.initialize(DiskInodeType::File); + }); self.modify_disk_inode(|root_inode| { // append file in the dirent let file_count = (root_inode.size as usize) / DIRENT_SZ; @@ -166,11 +147,7 @@ impl Inode { for i in 0..file_count { let mut dirent = DirEntry::empty(); assert_eq!( - disk_inode.read_at( - i * DIRENT_SZ, - dirent.as_bytes_mut(), - &self.block_device, - ), + disk_inode.read_at(i * DIRENT_SZ, dirent.as_bytes_mut(), &self.block_device,), DIRENT_SZ, ); v.push(String::from(dirent.name())); @@ -181,9 +158,7 @@ impl Inode { pub fn read_at(&self, offset: usize, buf: &mut [u8]) -> usize { let _fs = self.fs.lock(); - self.read_disk_inode(|disk_inode| { - disk_inode.read_at(offset, buf, &self.block_device) - }) + self.read_disk_inode(|disk_inode| disk_inode.read_at(offset, buf, &self.block_device)) } pub fn write_at(&self, offset: usize, buf: &[u8]) -> usize { diff --git a/os/src/config.rs b/os/src/config.rs index 44ecc0e..6033b71 100644 --- a/os/src/config.rs +++ b/os/src/config.rs @@ -17,26 +17,24 @@ pub const CLOCK_FREQ: usize = 403000000 / 62; pub const CLOCK_FREQ: usize = 12500000; #[cfg(feature = "board_qemu")] -pub const MMIO: &[(usize, usize)] = &[ - (0x10001000, 0x1000), -]; +pub const MMIO: &[(usize, usize)] = &[(0x10001000, 0x1000)]; #[cfg(feature = "board_k210")] pub const MMIO: &[(usize, usize)] = &[ // we don't need clint in S priv when running // we only need claim/complete for target0 after initializing - (0x0C00_0000, 0x3000), /* PLIC */ - (0x0C20_0000, 0x1000), /* PLIC */ - (0x3800_0000, 0x1000), /* UARTHS */ - (0x3800_1000, 0x1000), /* GPIOHS */ - (0x5020_0000, 0x1000), /* GPIO */ - (0x5024_0000, 0x1000), /* SPI_SLAVE */ - (0x502B_0000, 0x1000), /* FPIOA */ - (0x502D_0000, 0x1000), /* TIMER0 */ - (0x502E_0000, 0x1000), /* TIMER1 */ - (0x502F_0000, 0x1000), /* TIMER2 */ - (0x5044_0000, 0x1000), /* SYSCTL */ - (0x5200_0000, 0x1000), /* SPI0 */ - (0x5300_0000, 0x1000), /* SPI1 */ - (0x5400_0000, 0x1000), /* SPI2 */ + (0x0C00_0000, 0x3000), /* PLIC */ + (0x0C20_0000, 0x1000), /* PLIC */ + (0x3800_0000, 0x1000), /* UARTHS */ + (0x3800_1000, 0x1000), /* GPIOHS */ + (0x5020_0000, 0x1000), /* GPIO */ + (0x5024_0000, 0x1000), /* SPI_SLAVE */ + (0x502B_0000, 0x1000), /* FPIOA */ + (0x502D_0000, 0x1000), /* TIMER0 */ + (0x502E_0000, 0x1000), /* TIMER1 */ + (0x502F_0000, 0x1000), /* TIMER2 */ + (0x5044_0000, 0x1000), /* SYSCTL */ + (0x5200_0000, 0x1000), /* SPI0 */ + (0x5300_0000, 0x1000), /* SPI1 */ + (0x5400_0000, 0x1000), /* SPI2 */ ]; diff --git a/os/src/console.rs b/os/src/console.rs index 9198888..c8a5cd4 100644 --- a/os/src/console.rs +++ b/os/src/console.rs @@ -1,5 +1,5 @@ -use core::fmt::{self, Write}; use crate::sbi::console_putchar; +use core::fmt::{self, Write}; struct Stdout; @@ -29,5 +29,3 @@ macro_rules! println { $crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?)) } } - - diff --git a/os/src/drivers/block/mod.rs b/os/src/drivers/block/mod.rs index f79b8b8..67436fd 100644 --- a/os/src/drivers/block/mod.rs +++ b/os/src/drivers/block/mod.rs @@ -1,9 +1,9 @@ -mod virtio_blk; mod sdcard; +mod virtio_blk; -use lazy_static::*; use alloc::sync::Arc; use easy_fs::BlockDevice; +use lazy_static::*; #[cfg(feature = "board_qemu")] type BlockDeviceImpl = virtio_blk::VirtIOBlock; @@ -21,10 +21,12 @@ pub fn block_device_test() { let mut write_buffer = [0u8; 512]; let mut read_buffer = [0u8; 512]; for i in 0..512 { - for byte in write_buffer.iter_mut() { *byte = i as u8; } + for byte in write_buffer.iter_mut() { + *byte = i as u8; + } block_device.write_block(i as usize, &write_buffer); block_device.read_block(i as usize, &mut read_buffer); assert_eq!(write_buffer, read_buffer); } println!("block device test passed!"); -} \ No newline at end of file +} diff --git a/os/src/drivers/block/sdcard.rs b/os/src/drivers/block/sdcard.rs index e550cc0..8603b2f 100644 --- a/os/src/drivers/block/sdcard.rs +++ b/os/src/drivers/block/sdcard.rs @@ -2,21 +2,21 @@ #![allow(non_camel_case_types)] #![allow(unused)] -use k210_pac::{Peripherals, SPI0}; +use super::BlockDevice; +use crate::sync::UPSafeCell; +use core::convert::TryInto; use k210_hal::prelude::*; +use k210_pac::{Peripherals, SPI0}; use k210_soc::{ + fpioa::{self, io}, //dmac::{dma_channel, DMAC, DMACExt}, gpio, gpiohs, - spi::{aitm, frame_format, tmod, work_mode, SPI, SPIExt, SPIImpl}, - fpioa::{self, io}, - sysctl, sleep::usleep, + spi::{aitm, frame_format, tmod, work_mode, SPIExt, SPIImpl, SPI}, + sysctl, }; -use crate::sync::UPSafeCell; use lazy_static::*; -use super::BlockDevice; -use core::convert::TryInto; pub struct SDCard { spi: SPI, @@ -160,7 +160,11 @@ pub struct SDCardInfo { } impl SDCard { - pub fn new(spi: X, spi_cs: u32, cs_gpionum: u8/*, dmac: &'a DMAC, channel: dma_channel*/) -> Self { + pub fn new( + spi: X, + spi_cs: u32, + cs_gpionum: u8, /*, dmac: &'a DMAC, channel: dma_channel*/ + ) -> Self { Self { spi, spi_cs, @@ -606,7 +610,7 @@ impl SDCard { } let mut error = false; //let mut dma_chunk = [0u32; SEC_LEN]; - let mut tmp_chunk= [0u8; SEC_LEN]; + let mut tmp_chunk = [0u8; SEC_LEN]; for chunk in data_buf.chunks_mut(SEC_LEN) { if self.get_response() != SD_START_DATA_SINGLE_BLOCK_READ { error = true; @@ -616,7 +620,7 @@ impl SDCard { //self.read_data_dma(&mut dma_chunk); self.read_data(&mut tmp_chunk); /* Place the data received as u32 units from DMA into the u8 target buffer */ - for (a, b) in chunk.iter_mut().zip(/*dma_chunk*/tmp_chunk.iter()) { + for (a, b) in chunk.iter_mut().zip(/*dma_chunk*/ tmp_chunk.iter()) { //*a = (b & 0xff) as u8; *a = *b; } @@ -675,7 +679,7 @@ impl SDCard { /* Send the data token to signify the start of the data */ self.write_data(&frame); /* Write the block data to SD : write count data by block */ - for (a, &b) in /*dma_chunk*/tmp_chunk.iter_mut().zip(chunk.iter()) { + for (a, &b) in /*dma_chunk*/ tmp_chunk.iter_mut().zip(chunk.iter()) { //*a = b.into(); *a = b; } @@ -711,9 +715,8 @@ fn io_init() { } lazy_static! { - static ref PERIPHERALS: UPSafeCell = unsafe { - UPSafeCell::new(Peripherals::take().unwrap()) - }; + static ref PERIPHERALS: UPSafeCell = + unsafe { UPSafeCell::new(Peripherals::take().unwrap()) }; } fn init_sdcard() -> SDCard> { @@ -747,9 +750,15 @@ impl SDCardWrapper { impl BlockDevice for SDCardWrapper { fn read_block(&self, block_id: usize, buf: &mut [u8]) { - self.0.exclusive_access().read_sector(buf,block_id as u32).unwrap(); + self.0 + .exclusive_access() + .read_sector(buf, block_id as u32) + .unwrap(); } fn write_block(&self, block_id: usize, buf: &[u8]) { - self.0.exclusive_access().write_sector(buf,block_id as u32).unwrap(); + self.0 + .exclusive_access() + .write_sector(buf, block_id as u32) + .unwrap(); } -} \ No newline at end of file +} diff --git a/os/src/drivers/block/virtio_blk.rs b/os/src/drivers/block/virtio_blk.rs index 6b38619..cca839d 100644 --- a/os/src/drivers/block/virtio_blk.rs +++ b/os/src/drivers/block/virtio_blk.rs @@ -1,20 +1,12 @@ - -use virtio_drivers::{VirtIOBlk, VirtIOHeader}; -use crate::mm::{ - PhysAddr, - VirtAddr, - frame_alloc, - frame_dealloc, - PhysPageNum, - FrameTracker, - StepByOne, - PageTable, - kernel_token, -}; use super::BlockDevice; +use crate::mm::{ + frame_alloc, frame_dealloc, kernel_token, FrameTracker, PageTable, PhysAddr, PhysPageNum, + StepByOne, VirtAddr, +}; use crate::sync::UPSafeCell; use alloc::vec::Vec; use lazy_static::*; +use virtio_drivers::{VirtIOBlk, VirtIOHeader}; #[allow(unused)] const VIRTIO0: usize = 0x10001000; @@ -22,21 +14,21 @@ const VIRTIO0: usize = 0x10001000; pub struct VirtIOBlock(UPSafeCell>); lazy_static! { - static ref QUEUE_FRAMES: UPSafeCell> = unsafe { - UPSafeCell::new(Vec::new()) - }; + static ref QUEUE_FRAMES: UPSafeCell> = unsafe { UPSafeCell::new(Vec::new()) }; } impl BlockDevice for VirtIOBlock { fn read_block(&self, block_id: usize, buf: &mut [u8]) { - self.0.exclusive_access() - .read_block(block_id, buf) - .expect("Error when reading VirtIOBlk"); + self.0 + .exclusive_access() + .read_block(block_id, buf) + .expect("Error when reading VirtIOBlk"); } fn write_block(&self, block_id: usize, buf: &[u8]) { - self.0.exclusive_access() - .write_block(block_id, buf) - .expect("Error when writing VirtIOBlk"); + self.0 + .exclusive_access() + .write_block(block_id, buf) + .expect("Error when writing VirtIOBlk"); } } @@ -44,9 +36,9 @@ impl VirtIOBlock { #[allow(unused)] pub fn new() -> Self { unsafe { - Self(UPSafeCell::new(VirtIOBlk::new( - &mut *(VIRTIO0 as *mut VirtIOHeader) - ).unwrap())) + Self(UPSafeCell::new( + VirtIOBlk::new(&mut *(VIRTIO0 as *mut VirtIOHeader)).unwrap(), + )) } } } @@ -56,7 +48,9 @@ pub extern "C" fn virtio_dma_alloc(pages: usize) -> PhysAddr { let mut ppn_base = PhysPageNum(0); for i in 0..pages { let frame = frame_alloc().unwrap(); - if i == 0 { ppn_base = frame.ppn; } + if i == 0 { + ppn_base = frame.ppn; + } assert_eq!(frame.ppn.0, ppn_base.0 + i); QUEUE_FRAMES.exclusive_access().push(frame); } @@ -80,5 +74,7 @@ pub extern "C" fn virtio_phys_to_virt(paddr: PhysAddr) -> VirtAddr { #[no_mangle] pub extern "C" fn virtio_virt_to_phys(vaddr: VirtAddr) -> PhysAddr { - PageTable::from_token(kernel_token()).translate_va(vaddr).unwrap() + PageTable::from_token(kernel_token()) + .translate_va(vaddr) + .unwrap() } diff --git a/os/src/drivers/mod.rs b/os/src/drivers/mod.rs index 54c0a2c..43a6f54 100644 --- a/os/src/drivers/mod.rs +++ b/os/src/drivers/mod.rs @@ -1,3 +1,3 @@ mod block; -pub use block::BLOCK_DEVICE; \ No newline at end of file +pub use block::BLOCK_DEVICE; diff --git a/os/src/fs/inode.rs b/os/src/fs/inode.rs index f03225b..19e4953 100644 --- a/os/src/fs/inode.rs +++ b/os/src/fs/inode.rs @@ -1,15 +1,12 @@ -use easy_fs::{ - EasyFileSystem, - Inode, -}; +use super::File; use crate::drivers::BLOCK_DEVICE; +use crate::mm::UserBuffer; use crate::sync::UPSafeCell; use alloc::sync::Arc; -use lazy_static::*; -use bitflags::*; use alloc::vec::Vec; -use super::File; -use crate::mm::UserBuffer; +use bitflags::*; +use easy_fs::{EasyFileSystem, Inode}; +use lazy_static::*; pub struct OSInode { readable: bool, @@ -23,18 +20,11 @@ pub struct OSInodeInner { } impl OSInode { - pub fn new( - readable: bool, - writable: bool, - inode: Arc, - ) -> Self { + pub fn new(readable: bool, writable: bool, inode: Arc) -> Self { Self { readable, writable, - inner: unsafe { UPSafeCell::new(OSInodeInner { - offset: 0, - inode, - })}, + inner: unsafe { UPSafeCell::new(OSInodeInner { offset: 0, inode }) }, } } pub fn read_all(&self) -> Vec { @@ -98,40 +88,30 @@ pub fn open_file(name: &str, flags: OpenFlags) -> Option> { if let Some(inode) = ROOT_INODE.find(name) { // clear size inode.clear(); - Some(Arc::new(OSInode::new( - readable, - writable, - inode, - ))) + Some(Arc::new(OSInode::new(readable, writable, inode))) } else { // create file - ROOT_INODE.create(name) - .map(|inode| { - Arc::new(OSInode::new( - readable, - writable, - inode, - )) - }) + ROOT_INODE + .create(name) + .map(|inode| Arc::new(OSInode::new(readable, writable, inode))) } } else { - ROOT_INODE.find(name) - .map(|inode| { - if flags.contains(OpenFlags::TRUNC) { - inode.clear(); - } - Arc::new(OSInode::new( - readable, - writable, - inode - )) - }) + ROOT_INODE.find(name).map(|inode| { + if flags.contains(OpenFlags::TRUNC) { + inode.clear(); + } + Arc::new(OSInode::new(readable, writable, inode)) + }) } } impl File for OSInode { - fn readable(&self) -> bool { self.readable } - fn writable(&self) -> bool { self.writable } + fn readable(&self) -> bool { + self.readable + } + fn writable(&self) -> bool { + self.writable + } fn read(&self, mut buf: UserBuffer) -> usize { let mut inner = self.inner.exclusive_access(); let mut total_read_size = 0usize; @@ -156,4 +136,4 @@ impl File for OSInode { } total_write_size } -} \ No newline at end of file +} diff --git a/os/src/fs/mod.rs b/os/src/fs/mod.rs index c015702..8ae0418 100644 --- a/os/src/fs/mod.rs +++ b/os/src/fs/mod.rs @@ -1,16 +1,16 @@ +mod inode; mod pipe; mod stdio; -mod inode; use crate::mm::UserBuffer; -pub trait File : Send + Sync { +pub trait File: Send + Sync { fn readable(&self) -> bool; fn writable(&self) -> bool; fn read(&self, buf: UserBuffer) -> usize; fn write(&self, buf: UserBuffer) -> usize; } -pub use pipe::{Pipe, make_pipe}; +pub use inode::{list_apps, open_file, OSInode, OpenFlags}; +pub use pipe::{make_pipe, Pipe}; pub use stdio::{Stdin, Stdout}; -pub use inode::{OSInode, open_file, OpenFlags, list_apps}; \ No newline at end of file diff --git a/os/src/fs/pipe.rs b/os/src/fs/pipe.rs index 08d51da..ed71495 100644 --- a/os/src/fs/pipe.rs +++ b/os/src/fs/pipe.rs @@ -1,7 +1,7 @@ use super::File; -use alloc::sync::{Arc, Weak}; -use crate::sync::UPSafeCell; use crate::mm::UserBuffer; +use crate::sync::UPSafeCell; +use alloc::sync::{Arc, Weak}; use crate::task::suspend_current_and_run_next; @@ -98,22 +98,20 @@ impl PipeRingBuffer { /// Return (read_end, write_end) pub fn make_pipe() -> (Arc, Arc) { - let buffer = Arc::new(unsafe { - UPSafeCell::new(PipeRingBuffer::new()) - }); - let read_end = Arc::new( - Pipe::read_end_with_buffer(buffer.clone()) - ); - let write_end = Arc::new( - Pipe::write_end_with_buffer(buffer.clone()) - ); + let buffer = Arc::new(unsafe { UPSafeCell::new(PipeRingBuffer::new()) }); + let read_end = Arc::new(Pipe::read_end_with_buffer(buffer.clone())); + let write_end = Arc::new(Pipe::write_end_with_buffer(buffer.clone())); buffer.exclusive_access().set_write_end(&write_end); (read_end, write_end) } impl File for Pipe { - fn readable(&self) -> bool { self.readable } - fn writable(&self) -> bool { self.writable } + fn readable(&self) -> bool { + self.readable + } + fn writable(&self) -> bool { + self.writable + } fn read(&self, buf: UserBuffer) -> usize { assert_eq!(self.readable(), true); let mut buf_iter = buf.into_iter(); @@ -132,7 +130,9 @@ impl File for Pipe { // read at most loop_read bytes for _ in 0..loop_read { if let Some(byte_ref) = buf_iter.next() { - unsafe { *byte_ref = ring_buffer.read_byte(); } + unsafe { + *byte_ref = ring_buffer.read_byte(); + } read_size += 1; } else { return read_size; diff --git a/os/src/fs/stdio.rs b/os/src/fs/stdio.rs index e8df795..7c74d3e 100644 --- a/os/src/fs/stdio.rs +++ b/os/src/fs/stdio.rs @@ -1,5 +1,5 @@ use super::File; -use crate::mm::{UserBuffer}; +use crate::mm::UserBuffer; use crate::sbi::console_getchar; use crate::task::suspend_current_and_run_next; @@ -8,8 +8,12 @@ pub struct Stdin; pub struct Stdout; impl File for Stdin { - fn readable(&self) -> bool { true } - fn writable(&self) -> bool { false } + fn readable(&self) -> bool { + true + } + fn writable(&self) -> bool { + false + } fn read(&self, mut user_buf: UserBuffer) -> usize { assert_eq!(user_buf.len(), 1); // busy loop @@ -24,7 +28,9 @@ impl File for Stdin { } } let ch = c as u8; - unsafe { user_buf.buffers[0].as_mut_ptr().write_volatile(ch); } + unsafe { + user_buf.buffers[0].as_mut_ptr().write_volatile(ch); + } 1 } fn write(&self, _user_buf: UserBuffer) -> usize { @@ -33,9 +39,13 @@ impl File for Stdin { } impl File for Stdout { - fn readable(&self) -> bool { false } - fn writable(&self) -> bool { true } - fn read(&self, _user_buf: UserBuffer) -> usize{ + fn readable(&self) -> bool { + false + } + fn writable(&self) -> bool { + true + } + fn read(&self, _user_buf: UserBuffer) -> usize { panic!("Cannot read from stdout!"); } fn write(&self, user_buf: UserBuffer) -> usize { @@ -44,4 +54,4 @@ impl File for Stdout { } user_buf.len() } -} \ No newline at end of file +} diff --git a/os/src/lang_items.rs b/os/src/lang_items.rs index daf8632..6788148 100644 --- a/os/src/lang_items.rs +++ b/os/src/lang_items.rs @@ -1,16 +1,23 @@ -use core::panic::PanicInfo; -use core::arch::asm; use crate::sbi::shutdown; use crate::task::current_kstack_top; +use core::arch::asm; +use core::panic::PanicInfo; #[panic_handler] fn panic(info: &PanicInfo) -> ! { if let Some(location) = info.location() { - println!("[kernel] Panicked at {}:{} {}", location.file(), location.line(), info.message().unwrap()); + println!( + "[kernel] Panicked at {}:{} {}", + location.file(), + location.line(), + info.message().unwrap() + ); } else { println!("[kernel] Panicked: {}", info.message().unwrap()); } - unsafe { backtrace(); } + unsafe { + backtrace(); + } shutdown() } @@ -20,9 +27,11 @@ unsafe fn backtrace() { asm!("mv {}, s0", out(reg) fp); println!("---START BACKTRACE---"); for i in 0..10 { - if fp == stop { break; } - println!("#{}:ra={:#x}", i, *((fp-8) as *const usize)); - fp = *((fp-16) as *const usize); + if fp == stop { + break; + } + println!("#{}:ra={:#x}", i, *((fp - 8) as *const usize)); + fp = *((fp - 16) as *const usize); } println!("---END BACKTRACE---"); } diff --git a/os/src/main.rs b/os/src/main.rs index 3a960a6..23b9725 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -10,17 +10,17 @@ extern crate bitflags; #[macro_use] mod console; -mod lang_items; -mod sbi; -mod syscall; -mod trap; mod config; +mod drivers; +mod fs; +mod lang_items; +mod mm; +mod sbi; +mod sync; +mod syscall; mod task; mod timer; -mod sync; -mod mm; -mod fs; -mod drivers; +mod trap; use core::arch::global_asm; @@ -32,10 +32,8 @@ fn clear_bss() { fn ebss(); } unsafe { - core::slice::from_raw_parts_mut( - sbss as usize as *mut u8, - ebss as usize - sbss as usize, - ).fill(0); + core::slice::from_raw_parts_mut(sbss as usize as *mut u8, ebss as usize - sbss as usize) + .fill(0); } } diff --git a/os/src/mm/address.rs b/os/src/mm/address.rs index 107f35c..7faceaa 100644 --- a/os/src/mm/address.rs +++ b/os/src/mm/address.rs @@ -1,5 +1,5 @@ -use crate::config::{PAGE_SIZE, PAGE_SIZE_BITS}; use super::PageTableEntry; +use crate::config::{PAGE_SIZE, PAGE_SIZE_BITS}; use core::fmt::{self, Debug, Formatter}; const PA_WIDTH_SV39: usize = 56; @@ -52,35 +52,59 @@ impl Debug for PhysPageNum { /// usize -> T: usize.into() impl From for PhysAddr { - fn from(v: usize) -> Self { Self(v & ( (1 << PA_WIDTH_SV39) - 1 )) } + fn from(v: usize) -> Self { + Self(v & ((1 << PA_WIDTH_SV39) - 1)) + } } impl From for PhysPageNum { - fn from(v: usize) -> Self { Self(v & ( (1 << PPN_WIDTH_SV39) - 1 )) } + fn from(v: usize) -> Self { + Self(v & ((1 << PPN_WIDTH_SV39) - 1)) + } } impl From for VirtAddr { - fn from(v: usize) -> Self { Self(v & ( (1 << VA_WIDTH_SV39) - 1 )) } + fn from(v: usize) -> Self { + Self(v & ((1 << VA_WIDTH_SV39) - 1)) + } } impl From for VirtPageNum { - fn from(v: usize) -> Self { Self(v & ( (1 << VPN_WIDTH_SV39) - 1 )) } + fn from(v: usize) -> Self { + Self(v & ((1 << VPN_WIDTH_SV39) - 1)) + } } impl From for usize { - fn from(v: PhysAddr) -> Self { v.0 } + fn from(v: PhysAddr) -> Self { + v.0 + } } impl From for usize { - fn from(v: PhysPageNum) -> Self { v.0 } + fn from(v: PhysPageNum) -> Self { + v.0 + } } impl From for usize { - fn from(v: VirtAddr) -> Self { v.0 } + fn from(v: VirtAddr) -> Self { + v.0 + } } impl From for usize { - fn from(v: VirtPageNum) -> Self { v.0 } + fn from(v: VirtPageNum) -> Self { + v.0 + } } impl VirtAddr { - pub fn floor(&self) -> VirtPageNum { VirtPageNum(self.0 / PAGE_SIZE) } - pub fn ceil(&self) -> VirtPageNum { VirtPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE) } - pub fn page_offset(&self) -> usize { self.0 & (PAGE_SIZE - 1) } - pub fn aligned(&self) -> bool { self.page_offset() == 0 } + pub fn floor(&self) -> VirtPageNum { + VirtPageNum(self.0 / PAGE_SIZE) + } + pub fn ceil(&self) -> VirtPageNum { + VirtPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE) + } + pub fn page_offset(&self) -> usize { + self.0 & (PAGE_SIZE - 1) + } + pub fn aligned(&self) -> bool { + self.page_offset() == 0 + } } impl From for VirtPageNum { fn from(v: VirtAddr) -> Self { @@ -89,13 +113,23 @@ impl From for VirtPageNum { } } impl From for VirtAddr { - fn from(v: VirtPageNum) -> Self { Self(v.0 << PAGE_SIZE_BITS) } + fn from(v: VirtPageNum) -> Self { + Self(v.0 << PAGE_SIZE_BITS) + } } impl PhysAddr { - pub fn floor(&self) -> PhysPageNum { PhysPageNum(self.0 / PAGE_SIZE) } - pub fn ceil(&self) -> PhysPageNum { PhysPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE) } - pub fn page_offset(&self) -> usize { self.0 & (PAGE_SIZE - 1) } - pub fn aligned(&self) -> bool { self.page_offset() == 0 } + pub fn floor(&self) -> PhysPageNum { + PhysPageNum(self.0 / PAGE_SIZE) + } + pub fn ceil(&self) -> PhysPageNum { + PhysPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE) + } + pub fn page_offset(&self) -> usize { + self.0 & (PAGE_SIZE - 1) + } + pub fn aligned(&self) -> bool { + self.page_offset() == 0 + } } impl From for PhysPageNum { fn from(v: PhysAddr) -> Self { @@ -104,7 +138,9 @@ impl From for PhysPageNum { } } impl From for PhysAddr { - fn from(v: PhysPageNum) -> Self { Self(v.0 << PAGE_SIZE_BITS) } + fn from(v: PhysPageNum) -> Self { + Self(v.0 << PAGE_SIZE_BITS) + } } impl VirtPageNum { @@ -121,28 +157,20 @@ impl VirtPageNum { impl PhysAddr { pub fn get_ref(&self) -> &'static T { - unsafe { - (self.0 as *const T).as_ref().unwrap() - } + unsafe { (self.0 as *const T).as_ref().unwrap() } } pub fn get_mut(&self) -> &'static mut T { - unsafe { - (self.0 as *mut T).as_mut().unwrap() - } + unsafe { (self.0 as *mut T).as_mut().unwrap() } } } impl PhysPageNum { pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] { let pa: PhysAddr = self.clone().into(); - unsafe { - core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512) - } + unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512) } } pub fn get_bytes_array(&self) -> &'static mut [u8] { let pa: PhysAddr = self.clone().into(); - unsafe { - core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096) - } + unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096) } } pub fn get_mut(&self) -> &'static mut T { let pa: PhysAddr = self.clone().into(); @@ -165,41 +193,57 @@ impl StepByOne for PhysPageNum { } #[derive(Copy, Clone)] -pub struct SimpleRange where - T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { +pub struct SimpleRange +where + T: StepByOne + Copy + PartialEq + PartialOrd + Debug, +{ l: T, r: T, } -impl SimpleRange where - T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { +impl SimpleRange +where + T: StepByOne + Copy + PartialEq + PartialOrd + Debug, +{ pub fn new(start: T, end: T) -> Self { assert!(start <= end, "start {:?} > end {:?}!", start, end); Self { l: start, r: end } } - pub fn get_start(&self) -> T { self.l } - pub fn get_end(&self) -> T { self.r } + pub fn get_start(&self) -> T { + self.l + } + pub fn get_end(&self) -> T { + self.r + } } -impl IntoIterator for SimpleRange where - T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { +impl IntoIterator for SimpleRange +where + T: StepByOne + Copy + PartialEq + PartialOrd + Debug, +{ type Item = T; type IntoIter = SimpleRangeIterator; fn into_iter(self) -> Self::IntoIter { SimpleRangeIterator::new(self.l, self.r) } } -pub struct SimpleRangeIterator where - T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { +pub struct SimpleRangeIterator +where + T: StepByOne + Copy + PartialEq + PartialOrd + Debug, +{ current: T, end: T, } -impl SimpleRangeIterator where - T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { +impl SimpleRangeIterator +where + T: StepByOne + Copy + PartialEq + PartialOrd + Debug, +{ pub fn new(l: T, r: T) -> Self { - Self { current: l, end: r, } + Self { current: l, end: r } } } -impl Iterator for SimpleRangeIterator where - T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { +impl Iterator for SimpleRangeIterator +where + T: StepByOne + Copy + PartialEq + PartialOrd + Debug, +{ type Item = T; fn next(&mut self) -> Option { if self.current == self.end { diff --git a/os/src/mm/frame_allocator.rs b/os/src/mm/frame_allocator.rs index 3afe336..e0db1d4 100644 --- a/os/src/mm/frame_allocator.rs +++ b/os/src/mm/frame_allocator.rs @@ -1,9 +1,9 @@ use super::{PhysAddr, PhysPageNum}; -use alloc::vec::Vec; -use crate::sync::UPSafeCell; use crate::config::MEMORY_END; -use lazy_static::*; +use crate::sync::UPSafeCell; +use alloc::vec::Vec; use core::fmt::{self, Debug, Formatter}; +use lazy_static::*; pub struct FrameTracker { pub ppn: PhysPageNum, @@ -72,10 +72,7 @@ impl FrameAllocator for StackFrameAllocator { fn dealloc(&mut self, ppn: PhysPageNum) { let ppn = ppn.0; // validity check - if ppn >= self.current || self.recycled - .iter() - .find(|&v| {*v == ppn}) - .is_some() { + if ppn >= self.current || self.recycled.iter().find(|&v| *v == ppn).is_some() { panic!("Frame ppn={:#x} has not been allocated!", ppn); } // recycle @@ -86,18 +83,18 @@ impl FrameAllocator for StackFrameAllocator { type FrameAllocatorImpl = StackFrameAllocator; lazy_static! { - pub static ref FRAME_ALLOCATOR: UPSafeCell = unsafe { - UPSafeCell::new(FrameAllocatorImpl::new()) - }; + pub static ref FRAME_ALLOCATOR: UPSafeCell = + unsafe { UPSafeCell::new(FrameAllocatorImpl::new()) }; } pub fn init_frame_allocator() { extern "C" { fn ekernel(); } - FRAME_ALLOCATOR - .exclusive_access() - .init(PhysAddr::from(ekernel as usize).ceil(), PhysAddr::from(MEMORY_END).floor()); + FRAME_ALLOCATOR.exclusive_access().init( + PhysAddr::from(ekernel as usize).ceil(), + PhysAddr::from(MEMORY_END).floor(), + ); } pub fn frame_alloc() -> Option { @@ -108,9 +105,7 @@ pub fn frame_alloc() -> Option { } pub fn frame_dealloc(ppn: PhysPageNum) { - FRAME_ALLOCATOR - .exclusive_access() - .dealloc(ppn); + FRAME_ALLOCATOR.exclusive_access().dealloc(ppn); } #[allow(unused)] diff --git a/os/src/mm/heap_allocator.rs b/os/src/mm/heap_allocator.rs index 2c7468f..b802bbd 100644 --- a/os/src/mm/heap_allocator.rs +++ b/os/src/mm/heap_allocator.rs @@ -1,5 +1,5 @@ -use buddy_system_allocator::LockedHeap; use crate::config::KERNEL_HEAP_SIZE; +use buddy_system_allocator::LockedHeap; #[global_allocator] static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty(); diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index 30bc768..2ebc6b6 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -1,20 +1,15 @@ -use super::{PageTable, PageTableEntry, PTEFlags}; -use super::{VirtPageNum, VirtAddr, PhysPageNum, PhysAddr}; -use super::{FrameTracker, frame_alloc}; -use super::{VPNRange, StepByOne}; -use alloc::collections::BTreeMap; -use alloc::vec::Vec; -use riscv::register::satp; -use alloc::sync::Arc; -use lazy_static::*; +use super::{frame_alloc, FrameTracker}; +use super::{PTEFlags, PageTable, PageTableEntry}; +use super::{PhysAddr, PhysPageNum, VirtAddr, VirtPageNum}; +use super::{StepByOne, VPNRange}; +use crate::config::{MEMORY_END, MMIO, PAGE_SIZE, TRAMPOLINE}; use crate::sync::UPSafeCell; -use crate::config::{ - MEMORY_END, - PAGE_SIZE, - TRAMPOLINE, - MMIO, -}; +use alloc::collections::BTreeMap; +use alloc::sync::Arc; +use alloc::vec::Vec; use core::arch::asm; +use lazy_static::*; +use riscv::register::satp; extern "C" { fn stext(); @@ -30,9 +25,8 @@ extern "C" { } lazy_static! { - pub static ref KERNEL_SPACE: Arc> = Arc::new(unsafe { - UPSafeCell::new(MemorySet::new_kernel()) - }); + pub static ref KERNEL_SPACE: Arc> = + Arc::new(unsafe { UPSafeCell::new(MemorySet::new_kernel()) }); } pub fn kernel_token() -> usize { @@ -55,17 +49,24 @@ impl MemorySet { self.page_table.token() } /// Assume that no conflicts. - pub fn insert_framed_area(&mut self, start_va: VirtAddr, end_va: VirtAddr, permission: MapPermission) { - self.push(MapArea::new( - start_va, - end_va, - MapType::Framed, - permission, - ), None); + pub fn insert_framed_area( + &mut self, + start_va: VirtAddr, + end_va: VirtAddr, + permission: MapPermission, + ) { + self.push( + MapArea::new(start_va, end_va, MapType::Framed, permission), + None, + ); } pub fn remove_area_with_start_vpn(&mut self, start_vpn: VirtPageNum) { - if let Some((idx, area)) = self.areas.iter_mut().enumerate() - .find(|(_, area)| area.vpn_range.get_start() == start_vpn) { + if let Some((idx, area)) = self + .areas + .iter_mut() + .enumerate() + .find(|(_, area)| area.vpn_range.get_start() == start_vpn) + { area.unmap(&mut self.page_table); self.areas.remove(idx); } @@ -94,50 +95,71 @@ impl MemorySet { println!(".text [{:#x}, {:#x})", stext as usize, etext as usize); println!(".rodata [{:#x}, {:#x})", srodata as usize, erodata as usize); println!(".data [{:#x}, {:#x})", sdata as usize, edata as usize); - println!(".bss [{:#x}, {:#x})", sbss_with_stack as usize, ebss as usize); + println!( + ".bss [{:#x}, {:#x})", + sbss_with_stack as usize, ebss as usize + ); println!("mapping .text section"); - memory_set.push(MapArea::new( - (stext as usize).into(), - (etext as usize).into(), - MapType::Identical, - MapPermission::R | MapPermission::X, - ), None); + memory_set.push( + MapArea::new( + (stext as usize).into(), + (etext as usize).into(), + MapType::Identical, + MapPermission::R | MapPermission::X, + ), + None, + ); println!("mapping .rodata section"); - memory_set.push(MapArea::new( - (srodata as usize).into(), - (erodata as usize).into(), - MapType::Identical, - MapPermission::R, - ), None); + memory_set.push( + MapArea::new( + (srodata as usize).into(), + (erodata as usize).into(), + MapType::Identical, + MapPermission::R, + ), + None, + ); println!("mapping .data section"); - memory_set.push(MapArea::new( - (sdata as usize).into(), - (edata as usize).into(), - MapType::Identical, - MapPermission::R | MapPermission::W, - ), None); - println!("mapping .bss section"); - memory_set.push(MapArea::new( - (sbss_with_stack as usize).into(), - (ebss as usize).into(), - MapType::Identical, - MapPermission::R | MapPermission::W, - ), None); - println!("mapping physical memory"); - memory_set.push(MapArea::new( - (ekernel as usize).into(), - MEMORY_END.into(), - MapType::Identical, - MapPermission::R | MapPermission::W, - ), None); - println!("mapping memory-mapped registers"); - for pair in MMIO { - memory_set.push(MapArea::new( - (*pair).0.into(), - ((*pair).0 + (*pair).1).into(), + memory_set.push( + MapArea::new( + (sdata as usize).into(), + (edata as usize).into(), MapType::Identical, MapPermission::R | MapPermission::W, - ), None); + ), + None, + ); + println!("mapping .bss section"); + memory_set.push( + MapArea::new( + (sbss_with_stack as usize).into(), + (ebss as usize).into(), + MapType::Identical, + MapPermission::R | MapPermission::W, + ), + None, + ); + println!("mapping physical memory"); + memory_set.push( + MapArea::new( + (ekernel as usize).into(), + MEMORY_END.into(), + MapType::Identical, + MapPermission::R | MapPermission::W, + ), + None, + ); + println!("mapping memory-mapped registers"); + for pair in MMIO { + memory_set.push( + MapArea::new( + (*pair).0.into(), + ((*pair).0 + (*pair).1).into(), + MapType::Identical, + MapPermission::R | MapPermission::W, + ), + None, + ); } memory_set } @@ -161,26 +183,31 @@ impl MemorySet { let end_va: VirtAddr = ((ph.virtual_addr() + ph.mem_size()) as usize).into(); let mut map_perm = MapPermission::U; let ph_flags = ph.flags(); - if ph_flags.is_read() { map_perm |= MapPermission::R; } - if ph_flags.is_write() { map_perm |= MapPermission::W; } - if ph_flags.is_execute() { map_perm |= MapPermission::X; } - let map_area = MapArea::new( - start_va, - end_va, - MapType::Framed, - map_perm, - ); + if ph_flags.is_read() { + map_perm |= MapPermission::R; + } + if ph_flags.is_write() { + map_perm |= MapPermission::W; + } + if ph_flags.is_execute() { + map_perm |= MapPermission::X; + } + let map_area = MapArea::new(start_va, end_va, MapType::Framed, map_perm); max_end_vpn = map_area.vpn_range.get_end(); memory_set.push( map_area, - Some(&elf.input[ph.offset() as usize..(ph.offset() + ph.file_size()) as usize]) + Some(&elf.input[ph.offset() as usize..(ph.offset() + ph.file_size()) as usize]), ); } } let max_end_va: VirtAddr = max_end_vpn.into(); let mut user_stack_base: usize = max_end_va.into(); user_stack_base += PAGE_SIZE; - (memory_set, user_stack_base, elf.header.pt2.entry_point() as usize) + ( + memory_set, + user_stack_base, + elf.header.pt2.entry_point() as usize, + ) } pub fn from_existed_user(user_space: &MemorySet) -> MemorySet { let mut memory_set = Self::new_bare(); @@ -194,7 +221,9 @@ impl MemorySet { for vpn in area.vpn_range { let src_ppn = user_space.translate(vpn).unwrap().ppn(); let dst_ppn = memory_set.translate(vpn).unwrap().ppn(); - dst_ppn.get_bytes_array().copy_from_slice(src_ppn.get_bytes_array()); + dst_ppn + .get_bytes_array() + .copy_from_slice(src_ppn.get_bytes_array()); } } memory_set @@ -227,7 +256,7 @@ impl MapArea { start_va: VirtAddr, end_va: VirtAddr, map_type: MapType, - map_perm: MapPermission + map_perm: MapPermission, ) -> Self { let start_vpn: VirtPageNum = start_va.floor(); let end_vpn: VirtPageNum = end_va.ceil(); @@ -326,15 +355,27 @@ pub fn remap_test() { let mid_rodata: VirtAddr = ((srodata as usize + erodata as usize) / 2).into(); let mid_data: VirtAddr = ((sdata as usize + edata as usize) / 2).into(); assert_eq!( - kernel_space.page_table.translate(mid_text.floor()).unwrap().writable(), + kernel_space + .page_table + .translate(mid_text.floor()) + .unwrap() + .writable(), false ); assert_eq!( - kernel_space.page_table.translate(mid_rodata.floor()).unwrap().writable(), + kernel_space + .page_table + .translate(mid_rodata.floor()) + .unwrap() + .writable(), false, ); assert_eq!( - kernel_space.page_table.translate(mid_data.floor()).unwrap().executable(), + kernel_space + .page_table + .translate(mid_data.floor()) + .unwrap() + .executable(), false, ); println!("remap_test passed!"); diff --git a/os/src/mm/mod.rs b/os/src/mm/mod.rs index 85e8c16..34220c4 100644 --- a/os/src/mm/mod.rs +++ b/os/src/mm/mod.rs @@ -1,28 +1,22 @@ -mod heap_allocator; mod address; mod frame_allocator; -mod page_table; +mod heap_allocator; mod memory_set; +mod page_table; -use page_table::PTEFlags; use address::VPNRange; -pub use address::{PhysAddr, VirtAddr, PhysPageNum, VirtPageNum, StepByOne}; -pub use frame_allocator::{FrameTracker, frame_alloc, frame_dealloc,}; -pub use page_table::{ - PageTable, - PageTableEntry, - translated_byte_buffer, - translated_str, - translated_ref, - translated_refmut, - UserBuffer, - UserBufferIterator, -}; -pub use memory_set::{MemorySet, KERNEL_SPACE, MapPermission, kernel_token}; +pub use address::{PhysAddr, PhysPageNum, StepByOne, VirtAddr, VirtPageNum}; +pub use frame_allocator::{frame_alloc, frame_dealloc, FrameTracker}; pub use memory_set::remap_test; +pub use memory_set::{kernel_token, MapPermission, MemorySet, KERNEL_SPACE}; +use page_table::PTEFlags; +pub use page_table::{ + translated_byte_buffer, translated_ref, translated_refmut, translated_str, PageTable, + PageTableEntry, UserBuffer, UserBufferIterator, +}; pub fn init() { heap_allocator::init_heap(); frame_allocator::init_frame_allocator(); KERNEL_SPACE.exclusive_access().activate(); -} \ No newline at end of file +} diff --git a/os/src/mm/page_table.rs b/os/src/mm/page_table.rs index 0ce485c..9de77af 100644 --- a/os/src/mm/page_table.rs +++ b/os/src/mm/page_table.rs @@ -1,15 +1,7 @@ -use super::{ - frame_alloc, - PhysPageNum, - FrameTracker, - VirtPageNum, - VirtAddr, - PhysAddr, - StepByOne -}; -use alloc::vec::Vec; -use alloc::vec; +use super::{frame_alloc, FrameTracker, PhysAddr, PhysPageNum, StepByOne, VirtAddr, VirtPageNum}; use alloc::string::String; +use alloc::vec; +use alloc::vec::Vec; use bitflags::*; bitflags! { @@ -38,9 +30,7 @@ impl PageTableEntry { } } pub fn empty() -> Self { - PageTableEntry { - bits: 0, - } + PageTableEntry { bits: 0 } } pub fn ppn(&self) -> PhysPageNum { (self.bits >> 10 & ((1usize << 44) - 1)).into() @@ -132,17 +122,15 @@ impl PageTable { *pte = PageTableEntry::empty(); } pub fn translate(&self, vpn: VirtPageNum) -> Option { - self.find_pte(vpn) - .map(|pte| {pte.clone()}) + self.find_pte(vpn).map(|pte| pte.clone()) } pub fn translate_va(&self, va: VirtAddr) -> Option { - self.find_pte(va.clone().floor()) - .map(|pte| { - let aligned_pa: PhysAddr = pte.ppn().into(); - let offset = va.page_offset(); - let aligned_pa_usize: usize = aligned_pa.into(); - (aligned_pa_usize + offset).into() - }) + self.find_pte(va.clone().floor()).map(|pte| { + let aligned_pa: PhysAddr = pte.ppn().into(); + let offset = va.page_offset(); + let aligned_pa_usize: usize = aligned_pa.into(); + (aligned_pa_usize + offset).into() + }) } pub fn token(&self) -> usize { 8usize << 60 | self.root_ppn.0 @@ -157,10 +145,7 @@ pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Vec<& while start < end { let start_va = VirtAddr::from(start); let mut vpn = start_va.floor(); - let ppn = page_table - .translate(vpn) - .unwrap() - .ppn(); + let ppn = page_table.translate(vpn).unwrap().ppn(); vpn.step(); let mut end_va: VirtAddr = vpn.into(); end_va = end_va.min(VirtAddr::from(end)); @@ -180,7 +165,10 @@ pub fn translated_str(token: usize, ptr: *const u8) -> String { let mut string = String::new(); let mut va = ptr as usize; loop { - let ch: u8 = *(page_table.translate_va(VirtAddr::from(va)).unwrap().get_mut()); + let ch: u8 = *(page_table + .translate_va(VirtAddr::from(va)) + .unwrap() + .get_mut()); if ch == 0 { break; } @@ -192,13 +180,19 @@ pub fn translated_str(token: usize, ptr: *const u8) -> String { pub fn translated_ref(token: usize, ptr: *const T) -> &'static T { let page_table = PageTable::from_token(token); - page_table.translate_va(VirtAddr::from(ptr as usize)).unwrap().get_ref() + page_table + .translate_va(VirtAddr::from(ptr as usize)) + .unwrap() + .get_ref() } pub fn translated_refmut(token: usize, ptr: *mut T) -> &'static mut T { let page_table = PageTable::from_token(token); let va = ptr as usize; - page_table.translate_va(VirtAddr::from(va)).unwrap().get_mut() + page_table + .translate_va(VirtAddr::from(va)) + .unwrap() + .get_mut() } pub struct UserBuffer { diff --git a/os/src/sbi.rs b/os/src/sbi.rs index c8ecbf1..2e2804b 100644 --- a/os/src/sbi.rs +++ b/os/src/sbi.rs @@ -43,4 +43,3 @@ pub fn shutdown() -> ! { sbi_call(SBI_SHUTDOWN, 0, 0, 0); panic!("It should shutdown!"); } - diff --git a/os/src/sync/condvar.rs b/os/src/sync/condvar.rs index 0b8f567..f96cd91 100644 --- a/os/src/sync/condvar.rs +++ b/os/src/sync/condvar.rs @@ -1,6 +1,6 @@ -use alloc::{sync::Arc, collections::VecDeque}; -use crate::task::{add_task, TaskControlBlock, current_task, block_current_and_run_next}; use crate::sync::{Mutex, UPSafeCell}; +use crate::task::{add_task, block_current_and_run_next, current_task, TaskControlBlock}; +use alloc::{collections::VecDeque, sync::Arc}; pub struct Condvar { pub inner: UPSafeCell, @@ -13,11 +13,11 @@ pub struct CondvarInner { impl Condvar { pub fn new() -> Self { Self { - inner: unsafe { UPSafeCell::new( - CondvarInner { + inner: unsafe { + UPSafeCell::new(CondvarInner { wait_queue: VecDeque::new(), - } - )}, + }) + }, } } @@ -28,7 +28,7 @@ impl Condvar { } } - pub fn wait(&self, mutex:Arc) { + pub fn wait(&self, mutex: Arc) { mutex.unlock(); let mut inner = self.inner.exclusive_access(); inner.wait_queue.push_back(current_task().unwrap()); diff --git a/os/src/sync/mod.rs b/os/src/sync/mod.rs index 8cf47c2..7e948aa 100644 --- a/os/src/sync/mod.rs +++ b/os/src/sync/mod.rs @@ -1,9 +1,9 @@ -mod up; +mod condvar; mod mutex; mod semaphore; -mod condvar; +mod up; -pub use up::UPSafeCell; -pub use mutex::{Mutex, MutexSpin, MutexBlocking}; +pub use condvar::Condvar; +pub use mutex::{Mutex, MutexBlocking, MutexSpin}; pub use semaphore::Semaphore; -pub use condvar::Condvar; \ No newline at end of file +pub use up::UPSafeCell; diff --git a/os/src/sync/mutex.rs b/os/src/sync/mutex.rs index fa13e8e..dee0850 100644 --- a/os/src/sync/mutex.rs +++ b/os/src/sync/mutex.rs @@ -1,8 +1,8 @@ use super::UPSafeCell; -use crate::task::{block_current_and_run_next, suspend_current_and_run_next}; use crate::task::TaskControlBlock; use crate::task::{add_task, current_task}; -use alloc::{sync::Arc, collections::VecDeque}; +use crate::task::{block_current_and_run_next, suspend_current_and_run_next}; +use alloc::{collections::VecDeque, sync::Arc}; pub trait Mutex: Sync + Send { fn lock(&self); @@ -77,7 +77,7 @@ impl Mutex for MutexBlocking { } fn unlock(&self) { - let mut mutex_inner = self.inner.exclusive_access(); + let mut mutex_inner = self.inner.exclusive_access(); assert_eq!(mutex_inner.locked, true); if let Some(waking_task) = mutex_inner.wait_queue.pop_front() { add_task(waking_task); diff --git a/os/src/sync/semaphore.rs b/os/src/sync/semaphore.rs index 2f66b87..7f3870f 100644 --- a/os/src/sync/semaphore.rs +++ b/os/src/sync/semaphore.rs @@ -1,6 +1,6 @@ -use alloc::{sync::Arc, collections::VecDeque}; -use crate::task::{add_task, TaskControlBlock, current_task, block_current_and_run_next}; use crate::sync::UPSafeCell; +use crate::task::{add_task, block_current_and_run_next, current_task, TaskControlBlock}; +use alloc::{collections::VecDeque, sync::Arc}; pub struct Semaphore { pub inner: UPSafeCell, @@ -14,12 +14,12 @@ pub struct SemaphoreInner { impl Semaphore { pub fn new(res_count: usize) -> Self { Self { - inner: unsafe { UPSafeCell::new( - SemaphoreInner { + inner: unsafe { + UPSafeCell::new(SemaphoreInner { count: res_count as isize, wait_queue: VecDeque::new(), - } - )}, + }) + }, } } diff --git a/os/src/sync/up.rs b/os/src/sync/up.rs index 642668c..c7b2c9e 100644 --- a/os/src/sync/up.rs +++ b/os/src/sync/up.rs @@ -18,10 +18,12 @@ impl UPSafeCell { /// User is responsible to guarantee that inner struct is only used in /// uniprocessor. pub unsafe fn new(value: T) -> Self { - Self { inner: RefCell::new(value) } + Self { + inner: RefCell::new(value), + } } /// Panic if the data has been borrowed. pub fn exclusive_access(&self) -> RefMut<'_, T> { self.inner.borrow_mut() } -} \ No newline at end of file +} diff --git a/os/src/syscall/fs.rs b/os/src/syscall/fs.rs index c8c476f..2758825 100644 --- a/os/src/syscall/fs.rs +++ b/os/src/syscall/fs.rs @@ -1,11 +1,6 @@ -use crate::mm::{ - UserBuffer, - translated_byte_buffer, - translated_refmut, - translated_str, -}; -use crate::task::{current_user_token, current_process}; -use crate::fs::{make_pipe, OpenFlags, open_file}; +use crate::fs::{make_pipe, open_file, OpenFlags}; +use crate::mm::{translated_byte_buffer, translated_refmut, translated_str, UserBuffer}; +use crate::task::{current_process, current_user_token}; use alloc::sync::Arc; pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize { @@ -22,9 +17,7 @@ pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize { let file = file.clone(); // release current task TCB manually to avoid multi-borrow drop(inner); - file.write( - UserBuffer::new(translated_byte_buffer(token, buf, len)) - ) as isize + file.write(UserBuffer::new(translated_byte_buffer(token, buf, len))) as isize } else { -1 } @@ -44,9 +37,7 @@ pub fn sys_read(fd: usize, buf: *const u8, len: usize) -> isize { } // release current task TCB manually to avoid multi-borrow drop(inner); - file.read( - UserBuffer::new(translated_byte_buffer(token, buf, len)) - ) as isize + file.read(UserBuffer::new(translated_byte_buffer(token, buf, len))) as isize } else { -1 } @@ -56,10 +47,7 @@ pub fn sys_open(path: *const u8, flags: u32) -> isize { let process = current_process(); let token = current_user_token(); let path = translated_str(token, path); - if let Some(inode) = open_file( - path.as_str(), - OpenFlags::from_bits(flags).unwrap() - ) { + if let Some(inode) = open_file(path.as_str(), OpenFlags::from_bits(flags).unwrap()) { let mut inner = process.inner_exclusive_access(); let fd = inner.alloc_fd(); inner.fd_table[fd] = Some(inode); diff --git a/os/src/syscall/mod.rs b/os/src/syscall/mod.rs index 5184abe..9f2b247 100644 --- a/os/src/syscall/mod.rs +++ b/os/src/syscall/mod.rs @@ -27,17 +27,17 @@ const SYSCALL_CONDVAR_WAIT: usize = 1032; mod fs; mod process; -mod thread; mod sync; +mod thread; use fs::*; use process::*; -use thread::*; use sync::*; +use thread::*; pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize { match syscall_id { - SYSCALL_DUP=> sys_dup(args[0]), + SYSCALL_DUP => sys_dup(args[0]), SYSCALL_OPEN => sys_open(args[0] as *const u8, args[1] as u32), SYSCALL_CLOSE => sys_close(args[0]), SYSCALL_PIPE => sys_pipe(args[0] as *mut usize), @@ -66,4 +66,3 @@ pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize { _ => panic!("Unsupported syscall_id: {}", syscall_id), } } - diff --git a/os/src/syscall/process.rs b/os/src/syscall/process.rs index d9c98a9..4184e23 100644 --- a/os/src/syscall/process.rs +++ b/os/src/syscall/process.rs @@ -1,23 +1,13 @@ +use crate::fs::{open_file, OpenFlags}; +use crate::mm::{translated_ref, translated_refmut, translated_str}; use crate::task::{ + current_process, current_task, current_user_token, exit_current_and_run_next, suspend_current_and_run_next, - exit_current_and_run_next, - current_task, - current_process, - current_user_token, }; use crate::timer::get_time_ms; -use crate::mm::{ - translated_str, - translated_refmut, - translated_ref, -}; -use crate::fs::{ - open_file, - OpenFlags, -}; +use alloc::string::String; use alloc::sync::Arc; use alloc::vec::Vec; -use alloc::string::String; pub fn sys_exit(exit_code: i32) -> ! { exit_current_and_run_next(exit_code); @@ -61,7 +51,9 @@ pub fn sys_exec(path: *const u8, mut args: *const usize) -> isize { break; } args_vec.push(translated_str(token, arg_str_ptr as *const u8)); - unsafe { args = args.add(1); } + unsafe { + args = args.add(1); + } } if let Some(app_inode) = open_file(path.as_str(), OpenFlags::RDONLY) { let all_data = app_inode.read_all(); @@ -82,21 +74,20 @@ pub fn sys_waitpid(pid: isize, exit_code_ptr: *mut i32) -> isize { // find a child process let mut inner = process.inner_exclusive_access(); - if inner.children + if inner + .children .iter() - .find(|p| {pid == -1 || pid as usize == p.getpid()}) - .is_none() { + .find(|p| pid == -1 || pid as usize == p.getpid()) + .is_none() + { return -1; // ---- release current PCB } - let pair = inner.children - .iter() - .enumerate() - .find(|(_, p)| { - // ++++ temporarily access child PCB exclusively - p.inner_exclusive_access().is_zombie && (pid == -1 || pid as usize == p.getpid()) - // ++++ release child PCB - }); + let pair = inner.children.iter().enumerate().find(|(_, p)| { + // ++++ temporarily access child PCB exclusively + p.inner_exclusive_access().is_zombie && (pid == -1 || pid as usize == p.getpid()) + // ++++ release child PCB + }); if let Some((idx, _)) = pair { let child = inner.children.remove(idx); // confirm that child will be deallocated after being removed from children list diff --git a/os/src/syscall/sync.rs b/os/src/syscall/sync.rs index e92cf79..dd0e856 100644 --- a/os/src/syscall/sync.rs +++ b/os/src/syscall/sync.rs @@ -1,6 +1,6 @@ -use crate::task::{current_task, current_process, block_current_and_run_next}; -use crate::sync::{Mutex, MutexSpin, MutexBlocking, Semaphore, Condvar}; -use crate::timer::{get_time_ms, add_timer}; +use crate::sync::{Condvar, Mutex, MutexBlocking, MutexSpin, Semaphore}; +use crate::task::{block_current_and_run_next, current_process, current_task}; +use crate::timer::{add_timer, get_time_ms}; use alloc::sync::Arc; pub fn sys_sleep(ms: usize) -> isize { @@ -24,7 +24,8 @@ pub fn sys_mutex_create(blocking: bool) -> isize { .iter() .enumerate() .find(|(_, item)| item.is_none()) - .map(|(id, _)| id) { + .map(|(id, _)| id) + { process_inner.mutex_list[id] = mutex; id as isize } else { @@ -61,11 +62,14 @@ pub fn sys_semaphore_create(res_count: usize) -> isize { .iter() .enumerate() .find(|(_, item)| item.is_none()) - .map(|(id, _)| id) { - process_inner.semaphore_list[id] = Some(Arc::new(Semaphore::new(res_count))); + .map(|(id, _)| id) + { + process_inner.semaphore_list[id] = Some(Arc::new(Semaphore::new(res_count))); id } else { - process_inner.semaphore_list.push(Some(Arc::new(Semaphore::new(res_count)))); + process_inner + .semaphore_list + .push(Some(Arc::new(Semaphore::new(res_count)))); process_inner.semaphore_list.len() - 1 }; id as isize @@ -89,7 +93,6 @@ pub fn sys_semaphore_down(sem_id: usize) -> isize { 0 } - pub fn sys_condvar_create(_arg: usize) -> isize { let process = current_process(); let mut process_inner = process.inner_exclusive_access(); @@ -98,11 +101,14 @@ pub fn sys_condvar_create(_arg: usize) -> isize { .iter() .enumerate() .find(|(_, item)| item.is_none()) - .map(|(id, _)| id) { - process_inner.condvar_list[id] = Some(Arc::new(Condvar::new())); + .map(|(id, _)| id) + { + process_inner.condvar_list[id] = Some(Arc::new(Condvar::new())); id } else { - process_inner.condvar_list.push(Some(Arc::new(Condvar::new()))); + process_inner + .condvar_list + .push(Some(Arc::new(Condvar::new()))); process_inner.condvar_list.len() - 1 }; id as isize @@ -125,4 +131,4 @@ pub fn sys_condvar_wait(condvar_id: usize, mutex_id: usize) -> isize { drop(process_inner); condvar.wait(mutex); 0 -} \ No newline at end of file +} diff --git a/os/src/syscall/thread.rs b/os/src/syscall/thread.rs index fa3b181..3955d9d 100644 --- a/os/src/syscall/thread.rs +++ b/os/src/syscall/thread.rs @@ -1,5 +1,9 @@ +use crate::{ + mm::kernel_token, + task::{add_task, current_task, TaskControlBlock}, + trap::{trap_handler, TrapContext}, +}; use alloc::sync::Arc; -use crate::{mm::kernel_token, task::{TaskControlBlock, add_task, current_task}, trap::{TrapContext, trap_handler}}; pub fn sys_thread_create(entry: usize, arg: usize) -> isize { let task = current_task().unwrap(); @@ -7,7 +11,11 @@ pub fn sys_thread_create(entry: usize, arg: usize) -> isize { // create a new thread let new_task = Arc::new(TaskControlBlock::new( Arc::clone(&process), - task.inner_exclusive_access().res.as_ref().unwrap().ustack_base, + task.inner_exclusive_access() + .res + .as_ref() + .unwrap() + .ustack_base, true, )); // add new task to scheduler @@ -35,7 +43,13 @@ pub fn sys_thread_create(entry: usize, arg: usize) -> isize { } pub fn sys_gettid() -> isize { - current_task().unwrap().inner_exclusive_access().res.as_ref().unwrap().tid as isize + current_task() + .unwrap() + .inner_exclusive_access() + .res + .as_ref() + .unwrap() + .tid as isize } /// thread does not exist, return -1 diff --git a/os/src/task/context.rs b/os/src/task/context.rs index d25cc2c..e4f59d8 100644 --- a/os/src/task/context.rs +++ b/os/src/task/context.rs @@ -23,4 +23,3 @@ impl TaskContext { } } } - diff --git a/os/src/task/id.rs b/os/src/task/id.rs index f94488c..476fc22 100644 --- a/os/src/task/id.rs +++ b/os/src/task/id.rs @@ -1,9 +1,12 @@ -use alloc::{vec::Vec, sync::{Arc, Weak}}; -use lazy_static::*; -use crate::sync::UPSafeCell; -use crate::mm::{KERNEL_SPACE, MapPermission, PhysPageNum, VirtAddr}; -use crate::config::{KERNEL_STACK_SIZE, PAGE_SIZE, TRAMPOLINE, TRAP_CONTEXT_BASE, USER_STACK_SIZE}; use super::ProcessControlBlock; +use crate::config::{KERNEL_STACK_SIZE, PAGE_SIZE, TRAMPOLINE, TRAP_CONTEXT_BASE, USER_STACK_SIZE}; +use crate::mm::{MapPermission, PhysPageNum, VirtAddr, KERNEL_SPACE}; +use crate::sync::UPSafeCell; +use alloc::{ + sync::{Arc, Weak}, + vec::Vec, +}; +use lazy_static::*; pub struct RecycleAllocator { current: usize, @@ -29,20 +32,18 @@ impl RecycleAllocator { assert!(id < self.current); assert!( self.recycled.iter().find(|i| **i == id).is_none(), - "id {} has been deallocated!", id + "id {} has been deallocated!", + id ); self.recycled.push(id); } } lazy_static! { - static ref PID_ALLOCATOR: UPSafeCell = unsafe { - UPSafeCell::new(RecycleAllocator::new()) - }; - - static ref KSTACK_ALLOCATOR: UPSafeCell = unsafe { - UPSafeCell::new(RecycleAllocator::new()) - }; + static ref PID_ALLOCATOR: UPSafeCell = + unsafe { UPSafeCell::new(RecycleAllocator::new()) }; + static ref KSTACK_ALLOCATOR: UPSafeCell = + unsafe { UPSafeCell::new(RecycleAllocator::new()) }; } pub struct PidHandle(pub usize); @@ -69,13 +70,11 @@ pub struct KernelStack(pub usize); pub fn kstack_alloc() -> KernelStack { let kstack_id = KSTACK_ALLOCATOR.exclusive_access().alloc(); let (kstack_bottom, kstack_top) = kernel_stack_position(kstack_id); - KERNEL_SPACE - .exclusive_access() - .insert_framed_area( - kstack_bottom.into(), - kstack_top.into(), - MapPermission::R | MapPermission::W, - ); + KERNEL_SPACE.exclusive_access().insert_framed_area( + kstack_bottom.into(), + kstack_top.into(), + MapPermission::R | MapPermission::W, + ); KernelStack(kstack_id) } @@ -91,11 +90,15 @@ impl Drop for KernelStack { impl KernelStack { #[allow(unused)] - pub fn push_on_top(&self, value: T) -> *mut T where - T: Sized, { + pub fn push_on_top(&self, value: T) -> *mut T + where + T: Sized, + { let kernel_stack_top = self.get_top(); let ptr_mut = (kernel_stack_top - core::mem::size_of::()) as *mut T; - unsafe { *ptr_mut = value; } + unsafe { + *ptr_mut = value; + } ptr_mut } pub fn get_top(&self) -> usize { @@ -142,23 +145,19 @@ impl TaskUserRes { // alloc user stack let ustack_bottom = ustack_bottom_from_tid(self.ustack_base, self.tid); let ustack_top = ustack_bottom + USER_STACK_SIZE; - process_inner - .memory_set - .insert_framed_area( - ustack_bottom.into(), - ustack_top.into(), - MapPermission::R | MapPermission::W | MapPermission::U, - ); + process_inner.memory_set.insert_framed_area( + ustack_bottom.into(), + ustack_top.into(), + MapPermission::R | MapPermission::W | MapPermission::U, + ); // alloc trap_cx let trap_cx_bottom = trap_cx_bottom_from_tid(self.tid); let trap_cx_top = trap_cx_bottom + PAGE_SIZE; - process_inner - .memory_set - .insert_framed_area( - trap_cx_bottom.into(), - trap_cx_top.into(), - MapPermission::R | MapPermission::W, - ); + process_inner.memory_set.insert_framed_area( + trap_cx_bottom.into(), + trap_cx_top.into(), + MapPermission::R | MapPermission::W, + ); } fn dealloc_user_res(&self) { @@ -167,10 +166,14 @@ impl TaskUserRes { let mut process_inner = process.inner_exclusive_access(); // dealloc ustack manually let ustack_bottom_va: VirtAddr = ustack_bottom_from_tid(self.ustack_base, self.tid).into(); - process_inner.memory_set.remove_area_with_start_vpn(ustack_bottom_va.into()); + process_inner + .memory_set + .remove_area_with_start_vpn(ustack_bottom_va.into()); // dealloc trap_cx manually let trap_cx_bottom_va: VirtAddr = trap_cx_bottom_from_tid(self.tid).into(); - process_inner.memory_set.remove_area_with_start_vpn(trap_cx_bottom_va.into()); + process_inner + .memory_set + .remove_area_with_start_vpn(trap_cx_bottom_va.into()); } #[allow(unused)] @@ -197,12 +200,18 @@ impl TaskUserRes { let process = self.process.upgrade().unwrap(); let process_inner = process.inner_exclusive_access(); let trap_cx_bottom_va: VirtAddr = trap_cx_bottom_from_tid(self.tid).into(); - process_inner.memory_set.translate(trap_cx_bottom_va.into()).unwrap().ppn() + process_inner + .memory_set + .translate(trap_cx_bottom_va.into()) + .unwrap() + .ppn() } - pub fn ustack_base(&self) -> usize { self.ustack_base } + pub fn ustack_base(&self) -> usize { + self.ustack_base + } pub fn ustack_top(&self) -> usize { - ustack_bottom_from_tid(self.ustack_base, self.tid) + USER_STACK_SIZE + ustack_bottom_from_tid(self.ustack_base, self.tid) + USER_STACK_SIZE } } @@ -212,4 +221,3 @@ impl Drop for TaskUserRes { self.dealloc_user_res(); } } - diff --git a/os/src/task/manager.rs b/os/src/task/manager.rs index 82c2d1e..542a0b8 100644 --- a/os/src/task/manager.rs +++ b/os/src/task/manager.rs @@ -1,5 +1,5 @@ -use crate::sync::UPSafeCell; use super::TaskControlBlock; +use crate::sync::UPSafeCell; use alloc::collections::VecDeque; use alloc::sync::Arc; use lazy_static::*; @@ -11,7 +11,9 @@ pub struct TaskManager { /// A simple FIFO scheduler. impl TaskManager { pub fn new() -> Self { - Self { ready_queue: VecDeque::new(), } + Self { + ready_queue: VecDeque::new(), + } } pub fn add(&mut self, task: Arc) { self.ready_queue.push_back(task); @@ -22,9 +24,8 @@ impl TaskManager { } lazy_static! { - pub static ref TASK_MANAGER: UPSafeCell = unsafe { - UPSafeCell::new(TaskManager::new()) - }; + pub static ref TASK_MANAGER: UPSafeCell = + unsafe { UPSafeCell::new(TaskManager::new()) }; } pub fn add_task(task: Arc) { diff --git a/os/src/task/mod.rs b/os/src/task/mod.rs index c426d6f..1e20540 100644 --- a/os/src/task/mod.rs +++ b/os/src/task/mod.rs @@ -1,38 +1,26 @@ mod context; +mod id; +mod manager; +mod process; +mod processor; mod switch; mod task; -mod manager; -mod processor; -mod id; -mod process; use crate::fs::{open_file, OpenFlags}; -use switch::__switch; use alloc::sync::Arc; -use manager::fetch_task; use lazy_static::*; +use manager::fetch_task; use process::ProcessControlBlock; +use switch::__switch; pub use context::TaskContext; +pub use id::{kstack_alloc, pid_alloc, KernelStack, PidHandle}; +pub use manager::add_task; pub use processor::{ - run_tasks, - current_task, - current_process, - current_user_token, - current_trap_cx_user_va, - current_trap_cx, - current_kstack_top, - take_current_task, - schedule, + current_kstack_top, current_process, current_task, current_trap_cx, current_trap_cx_user_va, + current_user_token, run_tasks, schedule, take_current_task, }; pub use task::{TaskControlBlock, TaskStatus}; -pub use manager::add_task; -pub use id::{ - PidHandle, - pid_alloc, - KernelStack, - kstack_alloc, -}; pub fn suspend_current_and_run_next() { // There must be an application running. @@ -86,7 +74,7 @@ pub fn exit_current_and_run_next(exit_code: i32) { // move all child processes under init process let mut initproc_inner = INITPROC.inner_exclusive_access(); for child in process_inner.children.iter() { - child.inner_exclusive_access().parent = Some(Arc::downgrade(&INITPROC)); + child.inner_exclusive_access().parent = Some(Arc::downgrade(&INITPROC)); initproc_inner.children.push(child.clone()); } } @@ -103,6 +91,8 @@ pub fn exit_current_and_run_next(exit_code: i32) { process_inner.children.clear(); // deallocate other data in user space i.e. program code/data section process_inner.memory_set.recycle_data_pages(); + // drop file descriptors + process_inner.fd_table.clear(); } drop(process); // we do not have to save task context diff --git a/os/src/task/process.rs b/os/src/task/process.rs index 43001b1..c393544 100644 --- a/os/src/task/process.rs +++ b/os/src/task/process.rs @@ -1,20 +1,16 @@ -use crate::mm::{ - MemorySet, - KERNEL_SPACE, - translated_refmut, -}; -use crate::trap::{TrapContext, trap_handler}; -use crate::sync::{UPSafeCell, Mutex, Semaphore, Condvar}; -use core::cell::RefMut; +use super::add_task; use super::id::RecycleAllocator; use super::TaskControlBlock; -use super::{PidHandle, pid_alloc}; -use super::add_task; -use alloc::sync::{Weak, Arc}; +use super::{pid_alloc, PidHandle}; +use crate::fs::{File, Stdin, Stdout}; +use crate::mm::{translated_refmut, MemorySet, KERNEL_SPACE}; +use crate::sync::{Condvar, Mutex, Semaphore, UPSafeCell}; +use crate::trap::{trap_handler, TrapContext}; +use alloc::string::String; +use alloc::sync::{Arc, Weak}; use alloc::vec; use alloc::vec::Vec; -use alloc::string::String; -use crate::fs::{File, Stdin, Stdout}; +use core::cell::RefMut; pub struct ProcessControlBlock { // immutable @@ -34,7 +30,7 @@ pub struct ProcessControlBlockInner { pub task_res_allocator: RecycleAllocator, pub mutex_list: Vec>>, pub semaphore_list: Vec>>, - pub condvar_list: Vec>>, + pub condvar_list: Vec>>, } impl ProcessControlBlockInner { @@ -44,8 +40,7 @@ impl ProcessControlBlockInner { } pub fn alloc_fd(&mut self) -> usize { - if let Some(fd) = (0..self.fd_table.len()) - .find(|fd| self.fd_table[*fd].is_none()) { + if let Some(fd) = (0..self.fd_table.len()).find(|fd| self.fd_table[*fd].is_none()) { fd } else { self.fd_table.push(None); @@ -57,7 +52,7 @@ impl ProcessControlBlockInner { self.task_res_allocator.alloc() } - pub fn dealloc_tid(&mut self, tid: usize){ + pub fn dealloc_tid(&mut self, tid: usize) { self.task_res_allocator.dealloc(tid) } @@ -82,26 +77,28 @@ impl ProcessControlBlock { let pid_handle = pid_alloc(); let process = Arc::new(Self { pid: pid_handle, - inner: unsafe { UPSafeCell::new(ProcessControlBlockInner { - is_zombie: false, - memory_set, - parent: None, - children: Vec::new(), - exit_code: 0, - fd_table: vec![ - // 0 -> stdin - Some(Arc::new(Stdin)), - // 1 -> stdout - Some(Arc::new(Stdout)), - // 2 -> stderr - Some(Arc::new(Stdout)), - ], - tasks: Vec::new(), - task_res_allocator: RecycleAllocator::new(), - mutex_list: Vec::new(), - semaphore_list: Vec::new(), - condvar_list: Vec::new(), - })} + inner: unsafe { + UPSafeCell::new(ProcessControlBlockInner { + is_zombie: false, + memory_set, + parent: None, + children: Vec::new(), + exit_code: 0, + fd_table: vec![ + // 0 -> stdin + Some(Arc::new(Stdin)), + // 1 -> stdout + Some(Arc::new(Stdout)), + // 2 -> stderr + Some(Arc::new(Stdout)), + ], + tasks: Vec::new(), + task_res_allocator: RecycleAllocator::new(), + mutex_list: Vec::new(), + semaphore_list: Vec::new(), + condvar_list: Vec::new(), + }) + }, }); // create a main thread, we should allocate ustack and trap_cx here let task = Arc::new(TaskControlBlock::new( @@ -154,7 +151,7 @@ impl ProcessControlBlock { .map(|arg| { translated_refmut( new_token, - (argv_base + arg * core::mem::size_of::()) as *mut usize + (argv_base + arg * core::mem::size_of::()) as *mut usize, ) }) .collect(); @@ -201,29 +198,37 @@ impl ProcessControlBlock { new_fd_table.push(None); } } - // create child process pcb + // create child process pcb let child = Arc::new(Self { pid, - inner: unsafe { UPSafeCell::new(ProcessControlBlockInner { - is_zombie: false, - memory_set, - parent: Some(Arc::downgrade(self)), - children: Vec::new(), - exit_code: 0, - fd_table: new_fd_table, - tasks: Vec::new(), - task_res_allocator: RecycleAllocator::new(), - mutex_list: Vec::new(), - semaphore_list: Vec::new(), - condvar_list: Vec::new(), - })} + inner: unsafe { + UPSafeCell::new(ProcessControlBlockInner { + is_zombie: false, + memory_set, + parent: Some(Arc::downgrade(self)), + children: Vec::new(), + exit_code: 0, + fd_table: new_fd_table, + tasks: Vec::new(), + task_res_allocator: RecycleAllocator::new(), + mutex_list: Vec::new(), + semaphore_list: Vec::new(), + condvar_list: Vec::new(), + }) + }, }); // add child parent.children.push(Arc::clone(&child)); // create main thread of child process let task = Arc::new(TaskControlBlock::new( Arc::clone(&child), - parent.get_task(0).inner_exclusive_access().res.as_ref().unwrap().ustack_base(), + parent + .get_task(0) + .inner_exclusive_access() + .res + .as_ref() + .unwrap() + .ustack_base(), // here we do not allocate trap_cx or ustack again // but mention that we allocate a new kstack here false, @@ -246,4 +251,3 @@ impl ProcessControlBlock { self.pid.0 } } - diff --git a/os/src/task/processor.rs b/os/src/task/processor.rs index cb83319..e321086 100644 --- a/os/src/task/processor.rs +++ b/os/src/task/processor.rs @@ -1,10 +1,10 @@ -use super::{TaskContext, TaskControlBlock, ProcessControlBlock}; +use super::__switch; +use super::{fetch_task, TaskStatus}; +use super::{ProcessControlBlock, TaskContext, TaskControlBlock}; +use crate::sync::UPSafeCell; +use crate::trap::TrapContext; use alloc::sync::Arc; use lazy_static::*; -use super::{fetch_task, TaskStatus}; -use super::__switch; -use crate::trap::TrapContext; -use crate::sync::UPSafeCell; pub struct Processor { current: Option>, @@ -30,9 +30,7 @@ impl Processor { } lazy_static! { - pub static ref PROCESSOR: UPSafeCell = unsafe { - UPSafeCell::new(Processor::new()) - }; + pub static ref PROCESSOR: UPSafeCell = unsafe { UPSafeCell::new(Processor::new()) }; } pub fn run_tasks() { @@ -50,13 +48,10 @@ pub fn run_tasks() { // release processor manually drop(processor); unsafe { - __switch( - idle_task_cx_ptr, - next_task_cx_ptr, - ); + __switch(idle_task_cx_ptr, next_task_cx_ptr); } } else { - println!("no tasks available in run_tasks"); + println!("no tasks available in run_tasks"); } } } @@ -80,7 +75,10 @@ pub fn current_user_token() -> usize { } pub fn current_trap_cx() -> &'static mut TrapContext { - current_task().unwrap().inner_exclusive_access().get_trap_cx() + current_task() + .unwrap() + .inner_exclusive_access() + .get_trap_cx() } pub fn current_trap_cx_user_va() -> usize { @@ -94,10 +92,7 @@ pub fn current_trap_cx_user_va() -> usize { } pub fn current_kstack_top() -> usize { - current_task() - .unwrap() - .kstack - .get_top() + current_task().unwrap().kstack.get_top() } pub fn schedule(switched_task_cx_ptr: *mut TaskContext) { @@ -105,9 +100,6 @@ pub fn schedule(switched_task_cx_ptr: *mut TaskContext) { let idle_task_cx_ptr = processor.get_idle_task_cx_ptr(); drop(processor); unsafe { - __switch( - switched_task_cx_ptr, - idle_task_cx_ptr, - ); + __switch(switched_task_cx_ptr, idle_task_cx_ptr); } } diff --git a/os/src/task/switch.rs b/os/src/task/switch.rs index dd3a2d3..59f8b1a 100644 --- a/os/src/task/switch.rs +++ b/os/src/task/switch.rs @@ -4,8 +4,5 @@ use core::arch::global_asm; global_asm!(include_str!("switch.S")); extern "C" { - pub fn __switch( - current_task_cx_ptr: *mut TaskContext, - next_task_cx_ptr: *const TaskContext - ); + pub fn __switch(current_task_cx_ptr: *mut TaskContext, next_task_cx_ptr: *const TaskContext); } diff --git a/os/src/task/task.rs b/os/src/task/task.rs index b7e1079..99900de 100644 --- a/os/src/task/task.rs +++ b/os/src/task/task.rs @@ -1,8 +1,8 @@ -use alloc::sync::{Arc, Weak}; -use crate::{mm::PhysPageNum, sync::UPSafeCell}; -use crate::trap::TrapContext; use super::id::TaskUserRes; -use super::{KernelStack, ProcessControlBlock, TaskContext, kstack_alloc}; +use super::{kstack_alloc, KernelStack, ProcessControlBlock, TaskContext}; +use crate::trap::TrapContext; +use crate::{mm::PhysPageNum, sync::UPSafeCell}; +use alloc::sync::{Arc, Weak}; use core::cell::RefMut; pub struct TaskControlBlock { @@ -37,7 +37,7 @@ impl TaskControlBlockInner { pub fn get_trap_cx(&self) -> &'static mut TrapContext { self.trap_cx_ppn.get_mut() } - + #[allow(unused)] fn get_status(&self) -> TaskStatus { self.task_status @@ -48,7 +48,7 @@ impl TaskControlBlock { pub fn new( process: Arc, ustack_base: usize, - alloc_user_res: bool + alloc_user_res: bool, ) -> Self { let res = TaskUserRes::new(Arc::clone(&process), ustack_base, alloc_user_res); let trap_cx_ppn = res.trap_cx_ppn(); @@ -57,15 +57,15 @@ impl TaskControlBlock { Self { process: Arc::downgrade(&process), kstack, - inner: unsafe { UPSafeCell::new( - TaskControlBlockInner { + inner: unsafe { + UPSafeCell::new(TaskControlBlockInner { res: Some(res), trap_cx_ppn, task_cx: TaskContext::goto_trap_return(kstack_top), task_status: TaskStatus::Ready, exit_code: None, - } - )}, + }) + }, } } } diff --git a/os/src/timer.rs b/os/src/timer.rs index 70c5d72..efd87da 100644 --- a/os/src/timer.rs +++ b/os/src/timer.rs @@ -1,13 +1,13 @@ use core::cmp::Ordering; -use riscv::register::time; -use crate::sbi::set_timer; use crate::config::CLOCK_FREQ; -use crate::task::{TaskControlBlock, add_task}; +use crate::sbi::set_timer; use crate::sync::UPSafeCell; +use crate::task::{add_task, TaskControlBlock}; use alloc::collections::BinaryHeap; use alloc::sync::Arc; use lazy_static::*; +use riscv::register::time; const TICKS_PER_SEC: usize = 100; const MSEC_PER_SEC: usize = 1000; @@ -39,28 +39,24 @@ impl PartialOrd for TimerCondVar { fn partial_cmp(&self, other: &Self) -> Option { let a = -(self.expire_ms as isize); let b = -(other.expire_ms as isize); - Some(a.cmp(&b)) + Some(a.cmp(&b)) } } impl Ord for TimerCondVar { fn cmp(&self, other: &Self) -> Ordering { - self.partial_cmp(other).unwrap() + self.partial_cmp(other).unwrap() } } lazy_static! { - static ref TIMERS: UPSafeCell> = unsafe { UPSafeCell::new( - BinaryHeap::::new() - )}; + static ref TIMERS: UPSafeCell> = + unsafe { UPSafeCell::new(BinaryHeap::::new()) }; } pub fn add_timer(expire_ms: usize, task: Arc) { let mut timers = TIMERS.exclusive_access(); - timers.push(TimerCondVar { - expire_ms, - task, - }); + timers.push(TimerCondVar { expire_ms, task }); } pub fn check_timer() { @@ -68,9 +64,11 @@ pub fn check_timer() { let mut timers = TIMERS.exclusive_access(); while let Some(timer) = timers.peek() { if timer.expire_ms <= current_ms { - add_task(Arc::clone(&timer.task)); + add_task(Arc::clone(&timer.task)); drop(timer); timers.pop(); - } else { break; } + } else { + break; + } } } diff --git a/os/src/trap/context.rs b/os/src/trap/context.rs index 16f8141..011b7fb 100644 --- a/os/src/trap/context.rs +++ b/os/src/trap/context.rs @@ -1,4 +1,4 @@ -use riscv::register::sstatus::{Sstatus, self, SPP}; +use riscv::register::sstatus::{self, Sstatus, SPP}; #[repr(C)] #[derive(Debug)] @@ -12,7 +12,9 @@ pub struct TrapContext { } impl TrapContext { - pub fn set_sp(&mut self, sp: usize) { self.x[2] = sp; } + pub fn set_sp(&mut self, sp: usize) { + self.x[2] = sp; + } pub fn app_init_context( entry: usize, sp: usize, diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index f57ca70..443f330 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -1,28 +1,18 @@ mod context; -use riscv::register::{ - mtvec::TrapMode, - stvec, - scause::{ - self, - Trap, - Exception, - Interrupt, - }, - stval, - sie, -}; +use crate::config::TRAMPOLINE; use crate::syscall::syscall; use crate::task::{ - exit_current_and_run_next, + current_trap_cx, current_trap_cx_user_va, current_user_token, exit_current_and_run_next, suspend_current_and_run_next, - current_user_token, - current_trap_cx, - current_trap_cx_user_va, }; -use crate::timer::{set_next_trigger, check_timer}; -use crate::config::TRAMPOLINE; -use core::arch::{global_asm, asm}; +use crate::timer::{check_timer, set_next_trigger}; +use core::arch::{asm, global_asm}; +use riscv::register::{ + mtvec::TrapMode, + scause::{self, Exception, Interrupt, Trap}, + sie, stval, stvec, +}; global_asm!(include_str!("trap.S")); @@ -43,7 +33,9 @@ fn set_user_trap_entry() { } pub fn enable_timer_interrupt() { - unsafe { sie::set_stimer(); } + unsafe { + sie::set_stimer(); + } } #[no_mangle] @@ -62,12 +54,12 @@ pub fn trap_handler() -> ! { cx = current_trap_cx(); cx.x[10] = result as usize; } - Trap::Exception(Exception::StoreFault) | - Trap::Exception(Exception::StorePageFault) | - Trap::Exception(Exception::InstructionFault) | - Trap::Exception(Exception::InstructionPageFault) | - Trap::Exception(Exception::LoadFault) | - Trap::Exception(Exception::LoadPageFault) => { + Trap::Exception(Exception::StoreFault) + | Trap::Exception(Exception::StorePageFault) + | Trap::Exception(Exception::InstructionFault) + | Trap::Exception(Exception::InstructionPageFault) + | Trap::Exception(Exception::LoadFault) + | Trap::Exception(Exception::LoadPageFault) => { println!( "[kernel] {:?} in application, bad addr = {:#x}, bad instruction = {:#x}, kernel killed it.", scause.cause(), @@ -88,7 +80,11 @@ pub fn trap_handler() -> ! { suspend_current_and_run_next(); } _ => { - panic!("Unsupported trap {:?}, stval = {:#x}!", scause.cause(), stval); + panic!( + "Unsupported trap {:?}, stval = {:#x}!", + scause.cause(), + stval + ); } } trap_return(); diff --git a/user/src/bin/cat.rs b/user/src/bin/cat.rs index 4091878..05ab353 100644 --- a/user/src/bin/cat.rs +++ b/user/src/bin/cat.rs @@ -5,12 +5,7 @@ extern crate user_lib; extern crate alloc; -use user_lib::{ - open, - OpenFlags, - close, - read, -}; +use user_lib::{close, open, read, OpenFlags}; #[no_mangle] pub fn main(argc: usize, argv: &[&str]) -> i32 { @@ -23,7 +18,9 @@ pub fn main(argc: usize, argv: &[&str]) -> i32 { let mut buf = [0u8; 16]; loop { let size = read(fd, &mut buf) as usize; - if size == 0 { break; } + if size == 0 { + break; + } println!("{}", core::str::from_utf8(&buf[..size]).unwrap()); } close(fd); diff --git a/user/src/bin/exit.rs b/user/src/bin/exit.rs index 5bde550..60510c9 100644 --- a/user/src/bin/exit.rs +++ b/user/src/bin/exit.rs @@ -3,7 +3,7 @@ #[macro_use] extern crate user_lib; -use user_lib::{fork, yield_, waitpid, exit, wait}; +use user_lib::{exit, fork, wait, waitpid, yield_}; const MAGIC: i32 = -0x10384; @@ -13,7 +13,9 @@ pub fn main() -> i32 { let pid = fork(); if pid == 0 { println!("I am the child."); - for _ in 0..7 { yield_(); } + for _ in 0..7 { + yield_(); + } exit(MAGIC); } else { println!("I am parent, fork a child pid {}", pid); @@ -26,4 +28,3 @@ pub fn main() -> i32 { println!("exit pass."); 0 } - diff --git a/user/src/bin/fantastic_text.rs b/user/src/bin/fantastic_text.rs index bb51db3..a3402ff 100644 --- a/user/src/bin/fantastic_text.rs +++ b/user/src/bin/fantastic_text.rs @@ -41,4 +41,4 @@ pub fn main() -> i32 { println!("{}", color_text!(text, i)); } 0 -} \ No newline at end of file +} diff --git a/user/src/bin/filetest_simple.rs b/user/src/bin/filetest_simple.rs index 60fda6a..3406d55 100644 --- a/user/src/bin/filetest_simple.rs +++ b/user/src/bin/filetest_simple.rs @@ -4,13 +4,7 @@ #[macro_use] extern crate user_lib; -use user_lib::{ - open, - close, - read, - write, - OpenFlags, -}; +use user_lib::{close, open, read, write, OpenFlags}; #[no_mangle] pub fn main() -> i32 { @@ -29,10 +23,7 @@ pub fn main() -> i32 { let read_len = read(fd, &mut buffer) as usize; close(fd); - assert_eq!( - test_str, - core::str::from_utf8(&buffer[..read_len]).unwrap(), - ); + assert_eq!(test_str, core::str::from_utf8(&buffer[..read_len]).unwrap(),); println!("file_test passed!"); 0 -} \ No newline at end of file +} diff --git a/user/src/bin/forktest.rs b/user/src/bin/forktest.rs index a7d523a..5374a56 100644 --- a/user/src/bin/forktest.rs +++ b/user/src/bin/forktest.rs @@ -4,7 +4,7 @@ #[macro_use] extern crate user_lib; -use user_lib::{fork, wait, exit}; +use user_lib::{exit, fork, wait}; const MAX_CHILD: usize = 30; diff --git a/user/src/bin/forktest2.rs b/user/src/bin/forktest2.rs index d08a412..c91ce15 100644 --- a/user/src/bin/forktest2.rs +++ b/user/src/bin/forktest2.rs @@ -4,7 +4,7 @@ #[macro_use] extern crate user_lib; -use user_lib::{fork, wait, getpid, exit, sleep, get_time}; +use user_lib::{exit, fork, get_time, getpid, sleep, wait}; static NUM: usize = 30; @@ -14,7 +14,8 @@ pub fn main() -> i32 { let pid = fork(); if pid == 0 { let current_time = get_time(); - let sleep_length = (current_time as i32 as isize) * (current_time as i32 as isize) % 1000 + 1000; + let sleep_length = + (current_time as i32 as isize) * (current_time as i32 as isize) % 1000 + 1000; println!("pid {} sleep for {} ms", getpid(), sleep_length); sleep(sleep_length as usize); println!("pid {} OK!", getpid()); @@ -30,4 +31,4 @@ pub fn main() -> i32 { assert!(wait(&mut exit_code) < 0); println!("forktest2 test passed!"); 0 -} \ No newline at end of file +} diff --git a/user/src/bin/forktest_simple.rs b/user/src/bin/forktest_simple.rs index 821fba6..29a624b 100644 --- a/user/src/bin/forktest_simple.rs +++ b/user/src/bin/forktest_simple.rs @@ -25,4 +25,4 @@ pub fn main() -> i32 { println!("child process pid = {}, exit code = {}", pid, exit_code); 0 } -} \ No newline at end of file +} diff --git a/user/src/bin/forktree.rs b/user/src/bin/forktree.rs index 2debf6c..bfcfc4c 100644 --- a/user/src/bin/forktree.rs +++ b/user/src/bin/forktree.rs @@ -4,7 +4,7 @@ #[macro_use] extern crate user_lib; -use user_lib::{sleep, getpid, fork, exit, yield_}; +use user_lib::{exit, fork, getpid, sleep, yield_}; const DEPTH: usize = 4; diff --git a/user/src/bin/hello_world.rs b/user/src/bin/hello_world.rs index de4a6a9..10d3f26 100644 --- a/user/src/bin/hello_world.rs +++ b/user/src/bin/hello_world.rs @@ -8,4 +8,4 @@ extern crate user_lib; pub fn main() -> i32 { println!("Hello world from user mode program!"); 0 -} \ No newline at end of file +} diff --git a/user/src/bin/huge_write.rs b/user/src/bin/huge_write.rs index 4b0daaf..e93f8b8 100644 --- a/user/src/bin/huge_write.rs +++ b/user/src/bin/huge_write.rs @@ -4,13 +4,7 @@ #[macro_use] extern crate user_lib; -use user_lib::{ - OpenFlags, - open, - close, - write, - get_time, -}; +use user_lib::{close, get_time, open, write, OpenFlags}; #[no_mangle] pub fn main() -> i32 { @@ -25,12 +19,15 @@ pub fn main() -> i32 { let f = f as usize; let start = get_time(); let size_mb = 1usize; - for _ in 0..1024*size_mb { + for _ in 0..1024 * size_mb { write(f, &buffer); } close(f); let time_ms = (get_time() - start) as usize; let speed_kbs = size_mb * 1000000 / time_ms; - println!("{}MiB written, time cost = {}ms, write speed = {}KiB/s", size_mb, time_ms, speed_kbs); + println!( + "{}MiB written, time cost = {}ms, write speed = {}KiB/s", + size_mb, time_ms, speed_kbs + ); 0 } diff --git a/user/src/bin/initproc.rs b/user/src/bin/initproc.rs index fba348c..d25aee1 100644 --- a/user/src/bin/initproc.rs +++ b/user/src/bin/initproc.rs @@ -3,12 +3,7 @@ extern crate user_lib; -use user_lib::{ - fork, - wait, - exec, - yield_, -}; +use user_lib::{exec, fork, wait, yield_}; #[no_mangle] fn main() -> i32 { diff --git a/user/src/bin/matrix.rs b/user/src/bin/matrix.rs index c2461cf..9ebf48f 100644 --- a/user/src/bin/matrix.rs +++ b/user/src/bin/matrix.rs @@ -5,7 +5,7 @@ #[macro_use] extern crate user_lib; -use user_lib::{fork, wait, yield_, exit, getpid, get_time}; +use user_lib::{exit, fork, get_time, getpid, wait, yield_}; static NUM: usize = 30; const N: usize = 10; diff --git a/user/src/bin/mpsc_sem.rs b/user/src/bin/mpsc_sem.rs index a60c14b..7b92b9b 100644 --- a/user/src/bin/mpsc_sem.rs +++ b/user/src/bin/mpsc_sem.rs @@ -7,10 +7,10 @@ extern crate user_lib; extern crate alloc; -use user_lib::{semaphore_create, semaphore_up, semaphore_down}; -use user_lib::{thread_create, waittid}; -use user_lib::exit; use alloc::vec::Vec; +use user_lib::exit; +use user_lib::{semaphore_create, semaphore_down, semaphore_up}; +use user_lib::{thread_create, waittid}; const SEM_MUTEX: usize = 0; const SEM_EMPTY: usize = 1; @@ -58,7 +58,10 @@ pub fn main() -> i32 { let ids: Vec<_> = (0..PRODUCER_COUNT).collect(); let mut threads = Vec::new(); for i in 0..PRODUCER_COUNT { - threads.push(thread_create(producer as usize, &ids.as_slice()[i] as *const _ as usize)); + threads.push(thread_create( + producer as usize, + &ids.as_slice()[i] as *const _ as usize, + )); } threads.push(thread_create(consumer as usize, 0)); // wait for all threads to complete diff --git a/user/src/bin/phil_din_mutex.rs b/user/src/bin/phil_din_mutex.rs index 62baab6..8e7b566 100644 --- a/user/src/bin/phil_din_mutex.rs +++ b/user/src/bin/phil_din_mutex.rs @@ -6,10 +6,10 @@ extern crate user_lib; extern crate alloc; +use alloc::vec::Vec; +use user_lib::{exit, get_time, sleep}; use user_lib::{mutex_blocking_create, mutex_lock, mutex_unlock}; use user_lib::{thread_create, waittid}; -use user_lib::{sleep, exit, get_time}; -use alloc::vec::Vec; const N: usize = 5; const ROUND: usize = 4; @@ -39,16 +39,24 @@ fn philosopher_dining_problem(id: *const usize) { let max = left + right - min; for round in 0..ROUND { // thinking - unsafe { THINK[id][2 * round] = get_time_u(); } + unsafe { + THINK[id][2 * round] = get_time_u(); + } sleep(ARR[id][2 * round]); - unsafe { THINK[id][2 * round + 1] = get_time_u(); } + unsafe { + THINK[id][2 * round + 1] = get_time_u(); + } // wait for forks mutex_lock(min); mutex_lock(max); // eating - unsafe { EAT[id][2 * round] = get_time_u(); } + unsafe { + EAT[id][2 * round] = get_time_u(); + } sleep(ARR[id][2 * round + 1]); - unsafe { EAT[id][2 * round + 1] = get_time_u(); } + unsafe { + EAT[id][2 * round + 1] = get_time_u(); + } mutex_unlock(max); mutex_unlock(min); } @@ -62,7 +70,10 @@ pub fn main() -> i32 { let start = get_time_u(); for i in 0..N { assert_eq!(mutex_blocking_create(), i as isize); - v.push(thread_create(philosopher_dining_problem as usize, &ids.as_slice()[i] as *const _ as usize)); + v.push(thread_create( + philosopher_dining_problem as usize, + &ids.as_slice()[i] as *const _ as usize, + )); } for tid in v.iter() { waittid(*tid as usize); @@ -71,8 +82,8 @@ pub fn main() -> i32 { println!("time cost = {}", time_cost); println!("'-' -> THINKING; 'x' -> EATING; ' ' -> WAITING "); for id in (0..N).into_iter().chain(0..=0) { - print!("#{}:", id); - for j in 0..time_cost/GRAPH_SCALE { + print!("#{}:", id); + for j in 0..time_cost / GRAPH_SCALE { let current_time = j * GRAPH_SCALE + start; if (0..ROUND).any(|round| unsafe { let start_thinking = THINK[id][2 * round]; diff --git a/user/src/bin/pipe_large_test.rs b/user/src/bin/pipe_large_test.rs index 6f4f500..e85d0a6 100644 --- a/user/src/bin/pipe_large_test.rs +++ b/user/src/bin/pipe_large_test.rs @@ -6,8 +6,8 @@ extern crate user_lib; extern crate alloc; -use user_lib::{fork, close, pipe, read, write, wait, get_time}; use alloc::format; +use user_lib::{close, fork, get_time, pipe, read, wait, write}; const LENGTH: usize = 3000; #[no_mangle] @@ -44,7 +44,10 @@ pub fn main() -> i32 { *ch = get_time() as u8; } // send it - assert_eq!(write(down_pipe_fd[1], &random_str) as usize, random_str.len()); + assert_eq!( + write(down_pipe_fd[1], &random_str) as usize, + random_str.len() + ); // close write end of down pipe close(down_pipe_fd[1]); // calculate sum(parent) @@ -57,9 +60,8 @@ pub fn main() -> i32 { // check assert_eq!( sum, - str::parse::( - core::str::from_utf8(&child_result[..result_len]).unwrap() - ).unwrap() + str::parse::(core::str::from_utf8(&child_result[..result_len]).unwrap()) + .unwrap() ); let mut _unused: i32 = 0; wait(&mut _unused); diff --git a/user/src/bin/pipetest.rs b/user/src/bin/pipetest.rs index c151fbd..5436c63 100644 --- a/user/src/bin/pipetest.rs +++ b/user/src/bin/pipetest.rs @@ -4,7 +4,7 @@ #[macro_use] extern crate user_lib; -use user_lib::{fork, close, pipe, read, write, wait}; +use user_lib::{close, fork, pipe, read, wait, write}; static STR: &str = "Hello, world!"; @@ -41,4 +41,4 @@ pub fn main() -> i32 { println!("pipetest passed!"); 0 } -} \ No newline at end of file +} diff --git a/user/src/bin/race_adder.rs b/user/src/bin/race_adder.rs index bae009e..c7b6747 100644 --- a/user/src/bin/race_adder.rs +++ b/user/src/bin/race_adder.rs @@ -5,8 +5,8 @@ extern crate user_lib; extern crate alloc; -use user_lib::{exit, thread_create, waittid, get_time}; use alloc::vec::Vec; +use user_lib::{exit, get_time, thread_create, waittid}; static mut A: usize = 0; const PER_THREAD: usize = 1000; @@ -17,7 +17,9 @@ unsafe fn f() -> ! { for _ in 0..PER_THREAD { let a = &mut A as *mut usize; let cur = a.read_volatile(); - for _ in 0..500 { t = t * t % 10007; } + for _ in 0..500 { + t = t * t % 10007; + } a.write_volatile(cur + 1); } exit(t as i32) @@ -26,7 +28,7 @@ unsafe fn f() -> ! { #[no_mangle] pub fn main() -> i32 { let start = get_time(); - let mut v = Vec::new(); + let mut v = Vec::new(); for _ in 0..THREAD_COUNT { v.push(thread_create(f as usize, 0) as usize); } diff --git a/user/src/bin/race_adder_arg.rs b/user/src/bin/race_adder_arg.rs index 4c386be..7c8b707 100644 --- a/user/src/bin/race_adder_arg.rs +++ b/user/src/bin/race_adder_arg.rs @@ -5,16 +5,15 @@ extern crate user_lib; extern crate alloc; -use alloc::vec::Vec; use crate::alloc::string::ToString; +use alloc::vec::Vec; use user_lib::{exit, get_time, thread_create, waittid}; - static mut A: usize = 0; const PER_THREAD: usize = 1000; const THREAD_COUNT: usize = 16; -unsafe fn f(count:usize) -> ! { +unsafe fn f(count: usize) -> ! { let mut t = 2usize; for _ in 0..PER_THREAD { let a = &mut A as *mut usize; diff --git a/user/src/bin/race_adder_atomic.rs b/user/src/bin/race_adder_atomic.rs index dfbe490..2feaed0 100644 --- a/user/src/bin/race_adder_atomic.rs +++ b/user/src/bin/race_adder_atomic.rs @@ -5,9 +5,9 @@ extern crate user_lib; extern crate alloc; -use user_lib::{exit, thread_create, waittid, get_time, yield_}; use alloc::vec::Vec; use core::sync::atomic::{AtomicBool, Ordering}; +use user_lib::{exit, get_time, thread_create, waittid, yield_}; static mut A: usize = 0; static OCCUPIED: AtomicBool = AtomicBool::new(false); @@ -17,12 +17,17 @@ const THREAD_COUNT: usize = 16; unsafe fn f() -> ! { let mut t = 2usize; for _ in 0..PER_THREAD { - while OCCUPIED.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed).is_err() { + while OCCUPIED + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_err() + { yield_(); } let a = &mut A as *mut usize; let cur = a.read_volatile(); - for _ in 0..500 { t = t * t % 10007; } + for _ in 0..500 { + t = t * t % 10007; + } a.write_volatile(cur + 1); OCCUPIED.store(false, Ordering::Relaxed); } @@ -32,7 +37,7 @@ unsafe fn f() -> ! { #[no_mangle] pub fn main() -> i32 { let start = get_time(); - let mut v = Vec::new(); + let mut v = Vec::new(); for _ in 0..THREAD_COUNT { v.push(thread_create(f as usize, 0) as usize); } diff --git a/user/src/bin/race_adder_loop.rs b/user/src/bin/race_adder_loop.rs index 035772e..0e4fe83 100644 --- a/user/src/bin/race_adder_loop.rs +++ b/user/src/bin/race_adder_loop.rs @@ -5,8 +5,8 @@ extern crate user_lib; extern crate alloc; -use user_lib::{exit, thread_create, waittid, get_time, yield_}; use alloc::vec::Vec; +use user_lib::{exit, get_time, thread_create, waittid, yield_}; static mut A: usize = 0; static mut OCCUPIED: bool = false; @@ -16,12 +16,16 @@ const THREAD_COUNT: usize = 16; unsafe fn f() -> ! { let mut t = 2usize; for _ in 0..PER_THREAD { - while OCCUPIED { yield_(); } + while OCCUPIED { + yield_(); + } OCCUPIED = true; // enter critical section let a = &mut A as *mut usize; let cur = a.read_volatile(); - for _ in 0..500 { t = t * t % 10007; } + for _ in 0..500 { + t = t * t % 10007; + } a.write_volatile(cur + 1); // exit critical section OCCUPIED = false; @@ -33,7 +37,7 @@ unsafe fn f() -> ! { #[no_mangle] pub fn main() -> i32 { let start = get_time(); - let mut v = Vec::new(); + let mut v = Vec::new(); for _ in 0..THREAD_COUNT { v.push(thread_create(f as usize, 0) as usize); } diff --git a/user/src/bin/race_adder_mutex_blocking.rs b/user/src/bin/race_adder_mutex_blocking.rs index 0424eba..e5affc4 100644 --- a/user/src/bin/race_adder_mutex_blocking.rs +++ b/user/src/bin/race_adder_mutex_blocking.rs @@ -5,9 +5,9 @@ extern crate user_lib; extern crate alloc; -use user_lib::{exit, thread_create, waittid, get_time}; -use user_lib::{mutex_blocking_create, mutex_lock, mutex_unlock}; use alloc::vec::Vec; +use user_lib::{exit, get_time, thread_create, waittid}; +use user_lib::{mutex_blocking_create, mutex_lock, mutex_unlock}; static mut A: usize = 0; const PER_THREAD: usize = 1000; @@ -19,7 +19,9 @@ unsafe fn f() -> ! { mutex_lock(0); let a = &mut A as *mut usize; let cur = a.read_volatile(); - for _ in 0..500 { t = t * t % 10007; } + for _ in 0..500 { + t = t * t % 10007; + } a.write_volatile(cur + 1); mutex_unlock(0); } @@ -30,7 +32,7 @@ unsafe fn f() -> ! { pub fn main() -> i32 { let start = get_time(); assert_eq!(mutex_blocking_create(), 0); - let mut v = Vec::new(); + let mut v = Vec::new(); for _ in 0..THREAD_COUNT { v.push(thread_create(f as usize, 0) as usize); } diff --git a/user/src/bin/race_adder_mutex_spin.rs b/user/src/bin/race_adder_mutex_spin.rs index fb16866..ed3bcec 100644 --- a/user/src/bin/race_adder_mutex_spin.rs +++ b/user/src/bin/race_adder_mutex_spin.rs @@ -5,9 +5,9 @@ extern crate user_lib; extern crate alloc; -use user_lib::{exit, thread_create, waittid, get_time}; -use user_lib::{mutex_create, mutex_lock, mutex_unlock}; use alloc::vec::Vec; +use user_lib::{exit, get_time, thread_create, waittid}; +use user_lib::{mutex_create, mutex_lock, mutex_unlock}; static mut A: usize = 0; const PER_THREAD: usize = 1000; @@ -19,7 +19,9 @@ unsafe fn f() -> ! { mutex_lock(0); let a = &mut A as *mut usize; let cur = a.read_volatile(); - for _ in 0..500 { t = t * t % 10007; } + for _ in 0..500 { + t = t * t % 10007; + } a.write_volatile(cur + 1); mutex_unlock(0); } @@ -30,7 +32,7 @@ unsafe fn f() -> ! { pub fn main() -> i32 { let start = get_time(); assert_eq!(mutex_create(), 0); - let mut v = Vec::new(); + let mut v = Vec::new(); for _ in 0..THREAD_COUNT { v.push(thread_create(f as usize, 0) as usize); } diff --git a/user/src/bin/run_pipe_test.rs b/user/src/bin/run_pipe_test.rs index f0f95b0..ea99b6a 100644 --- a/user/src/bin/run_pipe_test.rs +++ b/user/src/bin/run_pipe_test.rs @@ -4,7 +4,7 @@ #[macro_use] extern crate user_lib; -use user_lib::{fork, exec, wait}; +use user_lib::{exec, fork, wait}; #[no_mangle] pub fn main() -> i32 { diff --git a/user/src/bin/sleep.rs b/user/src/bin/sleep.rs index bd1e220..641a4f9 100644 --- a/user/src/bin/sleep.rs +++ b/user/src/bin/sleep.rs @@ -4,10 +4,10 @@ #[macro_use] extern crate user_lib; -use user_lib::{sleep, exit, get_time, fork, waitpid}; +use user_lib::{exit, fork, get_time, sleep, waitpid}; fn sleepy() { - let time: usize = 1000; + let time: usize = 100; for i in 0..5 { sleep(time); println!("sleep {} x {} msecs.", i + 1, time); @@ -27,4 +27,4 @@ pub fn main() -> i32 { println!("use {} msecs.", get_time() - current_time); println!("sleep pass."); 0 -} \ No newline at end of file +} diff --git a/user/src/bin/sleep_simple.rs b/user/src/bin/sleep_simple.rs index 4c058f8..7015a3d 100644 --- a/user/src/bin/sleep_simple.rs +++ b/user/src/bin/sleep_simple.rs @@ -13,7 +13,11 @@ pub fn main() -> i32 { println!("current time_msec = {}", start); sleep(100); let end = get_time(); - println!("time_msec = {} after sleeping 100 ticks, delta = {}ms!", end, end - start); + println!( + "time_msec = {} after sleeping 100 ticks, delta = {}ms!", + end, + end - start + ); println!("r_sleep passed!"); 0 -} \ No newline at end of file +} diff --git a/user/src/bin/stack_overflow.rs b/user/src/bin/stack_overflow.rs index e0ea471..5d365f5 100644 --- a/user/src/bin/stack_overflow.rs +++ b/user/src/bin/stack_overflow.rs @@ -5,7 +5,7 @@ extern crate user_lib; fn f(d: usize) { - println!("d = {}",d); + println!("d = {}", d); f(d + 1); } @@ -14,4 +14,4 @@ pub fn main() -> i32 { println!("It should trigger segmentation fault!"); f(0); 0 -} \ No newline at end of file +} diff --git a/user/src/bin/sync_sem.rs b/user/src/bin/sync_sem.rs index 66b25fb..b8d1f79 100644 --- a/user/src/bin/sync_sem.rs +++ b/user/src/bin/sync_sem.rs @@ -6,14 +6,13 @@ extern crate user_lib; extern crate alloc; -use user_lib::{semaphore_create, semaphore_up, semaphore_down}; -use user_lib::{thread_create, waittid, sleep}; -use user_lib::exit; use alloc::vec; +use user_lib::exit; +use user_lib::{semaphore_create, semaphore_down, semaphore_up}; +use user_lib::{sleep, thread_create, waittid}; const SEM_SYNC: usize = 0; - unsafe fn first() -> ! { sleep(10); println!("First work and wakeup Second"); diff --git a/user/src/bin/test_condvar.rs b/user/src/bin/test_condvar.rs index 6caa53e..2db9d8a 100644 --- a/user/src/bin/test_condvar.rs +++ b/user/src/bin/test_condvar.rs @@ -6,10 +6,12 @@ extern crate user_lib; extern crate alloc; -use user_lib::{condvar_create, condvar_signal, condvar_wait, mutex_blocking_create, mutex_lock, mutex_unlock}; -use user_lib::{thread_create, waittid, sleep}; -use user_lib::exit; use alloc::vec; +use user_lib::exit; +use user_lib::{ + condvar_create, condvar_signal, condvar_wait, mutex_blocking_create, mutex_lock, mutex_unlock, +}; +use user_lib::{sleep, thread_create, waittid}; static mut A: usize = 0; @@ -20,7 +22,7 @@ unsafe fn first() -> ! { sleep(10); println!("First work, Change A --> 1 and wakeup Second"); mutex_lock(MUTEX_ID); - A=1; + A = 1; condvar_signal(CONDVAR_ID); mutex_unlock(MUTEX_ID); exit(0) @@ -29,7 +31,7 @@ unsafe fn first() -> ! { unsafe fn second() -> ! { println!("Second want to continue,but need to wait A=1"); mutex_lock(MUTEX_ID); - while A==0 { + while A == 0 { println!("Second: A is {}", A); condvar_wait(CONDVAR_ID, MUTEX_ID); } diff --git a/user/src/bin/threads.rs b/user/src/bin/threads.rs index 851766d..3d374fe 100644 --- a/user/src/bin/threads.rs +++ b/user/src/bin/threads.rs @@ -5,21 +5,27 @@ extern crate user_lib; extern crate alloc; -use user_lib::{thread_create, waittid, exit}; use alloc::vec; +use user_lib::{exit, thread_create, waittid}; pub fn thread_a() -> ! { - for _ in 0..1000 { print!("a"); } + for _ in 0..1000 { + print!("a"); + } exit(1) } pub fn thread_b() -> ! { - for _ in 0..1000 { print!("b"); } - exit(2) + for _ in 0..1000 { + print!("b"); + } + exit(2) } pub fn thread_c() -> ! { - for _ in 0..1000 { print!("c"); } + for _ in 0..1000 { + print!("c"); + } exit(3) } diff --git a/user/src/bin/threads_arg.rs b/user/src/bin/threads_arg.rs index c5b3b30..c5b26ed 100644 --- a/user/src/bin/threads_arg.rs +++ b/user/src/bin/threads_arg.rs @@ -5,8 +5,8 @@ extern crate user_lib; extern crate alloc; -use user_lib::{thread_create, waittid, exit}; use alloc::vec::Vec; +use user_lib::{exit, thread_create, waittid}; struct Argument { pub ch: char, @@ -15,7 +15,9 @@ struct Argument { fn thread_print(arg: *const Argument) -> ! { let arg = unsafe { &*arg }; - for _ in 0..1000 { print!("{}", arg.ch); } + for _ in 0..1000 { + print!("{}", arg.ch); + } exit(arg.rc) } @@ -23,12 +25,15 @@ fn thread_print(arg: *const Argument) -> ! { pub fn main() -> i32 { let mut v = Vec::new(); let args = [ - Argument { ch: 'a', rc: 1, }, - Argument { ch: 'b', rc: 2, }, - Argument { ch: 'c', rc: 3, }, - ]; + Argument { ch: 'a', rc: 1 }, + Argument { ch: 'b', rc: 2 }, + Argument { ch: 'c', rc: 3 }, + ]; for arg in args.iter() { - v.push(thread_create(thread_print as usize, arg as *const _ as usize)); + v.push(thread_create( + thread_print as usize, + arg as *const _ as usize, + )); } for tid in v.iter() { let exit_code = waittid(*tid as usize); diff --git a/user/src/bin/user_shell.rs b/user/src/bin/user_shell.rs index 5da878c..4288248 100644 --- a/user/src/bin/user_shell.rs +++ b/user/src/bin/user_shell.rs @@ -1,4 +1,3 @@ - #![no_std] #![no_main] #![allow(clippy::println_empty_string)] @@ -213,4 +212,3 @@ pub fn main() -> i32 { } } } - diff --git a/user/src/bin/usertests.rs b/user/src/bin/usertests.rs index 83da4d5..4fd49a4 100644 --- a/user/src/bin/usertests.rs +++ b/user/src/bin/usertests.rs @@ -32,7 +32,10 @@ pub fn main() -> i32 { let mut exit_code: i32 = Default::default(); let wait_pid = waitpid(pid as usize, &mut exit_code); assert_eq!(pid, wait_pid); - println!("\x1b[32mUsertests: Test {} in Process {} exited with code {}\x1b[0m", test, pid, exit_code); + println!( + "\x1b[32mUsertests: Test {} in Process {} exited with code {}\x1b[0m", + test, pid, exit_code + ); } } println!("Usertests passed!"); diff --git a/user/src/bin/yield.rs b/user/src/bin/yield.rs index 55032e4..78b1468 100644 --- a/user/src/bin/yield.rs +++ b/user/src/bin/yield.rs @@ -14,4 +14,4 @@ pub fn main() -> i32 { } println!("yield pass."); 0 -} \ No newline at end of file +} diff --git a/user/src/lang_items.rs b/user/src/lang_items.rs index b5b98e0..c65aa42 100644 --- a/user/src/lang_items.rs +++ b/user/src/lang_items.rs @@ -4,9 +4,14 @@ use super::exit; fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! { let err = panic_info.message().unwrap(); if let Some(location) = panic_info.location() { - println!("Panicked at {}:{}, {}", location.file(), location.line(), err); + println!( + "Panicked at {}:{}, {}", + location.file(), + location.line(), + err + ); } else { println!("Panicked: {}", err); } exit(-1); -} \ No newline at end of file +} diff --git a/user/src/syscall.rs b/user/src/syscall.rs index e85fbbb..ce30d00 100644 --- a/user/src/syscall.rs +++ b/user/src/syscall.rs @@ -58,7 +58,10 @@ pub fn sys_pipe(pipe: &mut [usize]) -> isize { } pub fn sys_read(fd: usize, buffer: &mut [u8]) -> isize { - syscall(SYSCALL_READ, [fd, buffer.as_mut_ptr() as usize, buffer.len()]) + syscall( + SYSCALL_READ, + [fd, buffer.as_mut_ptr() as usize, buffer.len()], + ) } pub fn sys_write(fd: usize, buffer: &[u8]) -> isize { @@ -91,7 +94,10 @@ pub fn sys_fork() -> isize { } pub fn sys_exec(path: &str, args: &[*const u8]) -> isize { - syscall(SYSCALL_EXEC, [path.as_ptr() as usize, args.as_ptr() as usize, 0]) + syscall( + SYSCALL_EXEC, + [path.as_ptr() as usize, args.as_ptr() as usize, 0], + ) } pub fn sys_waitpid(pid: isize, exit_code: *mut i32) -> isize { @@ -142,6 +148,6 @@ pub fn sys_condvar_signal(condvar_id: usize) -> isize { syscall(SYSCALL_CONDVAR_SIGNAL, [condvar_id, 0, 0]) } -pub fn sys_condvar_wait(condvar_id: usize, mutex_id:usize) -> isize { +pub fn sys_condvar_wait(condvar_id: usize, mutex_id: usize) -> isize { syscall(SYSCALL_CONDVAR_WAIT, [condvar_id, mutex_id, 0]) } From 59f13cb5367a82a46aeb547c4c48cc3580745d96 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Sat, 22 Jan 2022 12:54:00 -0800 Subject: [PATCH 24/56] Fix cat && add count_lines --- user/src/bin/cat.rs | 2 +- user/src/bin/count_lines.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 user/src/bin/count_lines.rs diff --git a/user/src/bin/cat.rs b/user/src/bin/cat.rs index 05ab353..424f44f 100644 --- a/user/src/bin/cat.rs +++ b/user/src/bin/cat.rs @@ -21,7 +21,7 @@ pub fn main(argc: usize, argv: &[&str]) -> i32 { if size == 0 { break; } - println!("{}", core::str::from_utf8(&buf[..size]).unwrap()); + print!("{}", core::str::from_utf8(&buf[..size]).unwrap()); } close(fd); 0 diff --git a/user/src/bin/count_lines.rs b/user/src/bin/count_lines.rs new file mode 100644 index 0000000..276e12e --- /dev/null +++ b/user/src/bin/count_lines.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use user_lib::read; + +#[no_mangle] +pub fn main(_argc: usize, _argv: &[&str]) -> i32 { + let mut buf = [0u8; 256]; + let mut lines = 0usize; + let mut total_size = 0usize; + loop { + let len = read(0, &mut buf) as usize; + if len == 0 { + break; + } + total_size += len; + let string = core::str::from_utf8(&buf[..len]).unwrap(); + lines += string + .chars() + .fold(0, |acc, c| acc + if c == '\n' { 1 } else { 0 }); + } + if total_size > 0 { + lines += 1; + } + println!("{}", lines); + 0 +} From 5c721b17f753bd763d35d825ffb8d69b753b0ae3 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Sun, 23 Jan 2022 01:44:10 -0800 Subject: [PATCH 25/56] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7868584..856f2cb 100644 --- a/README.md +++ b/README.md @@ -214,11 +214,12 @@ Here are the updates since 3.5.0: * [x] clarify: "check validity of level-3 pte in `find_pte` instead of checking it outside this function" should not be a bug * [x] code of chapter 8: synchronization on a uniprocessor * [x] switch the code of chapter 6 and chapter 7 +* [x] support signal mechanism in chapter 7 ### Todo(High priority) * [ ] review documentation, current progress: 5/9 -* [ ] support signal mechanism in chapter 7 +* [ ] support signal mechanism in chapter 8 * [ ] code of chapter 9: device drivers based on interrupts, including UART and block devices * [ ] use old fs image optionally, do not always rebuild the image * [ ] add new system calls: getdents64/fstat From 26bc01f3bc661fdb5c45253d25f41166e1c8d72d Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Sun, 23 Jan 2022 13:14:56 -0800 Subject: [PATCH 26/56] Support signal mechanism for ch8(only works on signal-thread apps) --- os/src/syscall/mod.rs | 2 ++ os/src/syscall/process.rs | 17 +++++++++++-- os/src/task/manager.rs | 22 +++++++++++++++-- os/src/task/mod.rs | 17 ++++++++++++- os/src/task/process.rs | 8 +++++- os/src/task/signal.rs | 29 ++++++++++++++++++++++ os/src/trap/mod.rs | 18 ++++++++------ user/Cargo.toml | 3 ++- user/src/bin/cat.rs | 2 +- user/src/bin/infloop.rs | 10 ++++++++ user/src/bin/priv_csr.rs | 17 +++++++++++++ user/src/bin/priv_inst.rs | 17 +++++++++++++ user/src/bin/store_fault.rs | 15 ++++++++++++ user/src/bin/until_timeout.rs | 46 +++++++++++++++++++++++++++++++++++ user/src/lang_items.rs | 5 ++-- user/src/lib.rs | 19 +++++++++++++++ user/src/syscall.rs | 5 ++++ 17 files changed, 235 insertions(+), 17 deletions(-) create mode 100644 os/src/task/signal.rs create mode 100644 user/src/bin/infloop.rs create mode 100644 user/src/bin/priv_csr.rs create mode 100644 user/src/bin/priv_inst.rs create mode 100644 user/src/bin/store_fault.rs create mode 100644 user/src/bin/until_timeout.rs diff --git a/os/src/syscall/mod.rs b/os/src/syscall/mod.rs index 9f2b247..fa4a4cf 100644 --- a/os/src/syscall/mod.rs +++ b/os/src/syscall/mod.rs @@ -7,6 +7,7 @@ const SYSCALL_WRITE: usize = 64; const SYSCALL_EXIT: usize = 93; const SYSCALL_SLEEP: usize = 101; const SYSCALL_YIELD: usize = 124; +const SYSCALL_KILL: usize = 129; const SYSCALL_GET_TIME: usize = 169; const SYSCALL_GETPID: usize = 172; const SYSCALL_FORK: usize = 220; @@ -46,6 +47,7 @@ pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize { SYSCALL_EXIT => sys_exit(args[0] as i32), SYSCALL_SLEEP => sys_sleep(args[0]), SYSCALL_YIELD => sys_yield(), + SYSCALL_KILL => sys_kill(args[0], args[1] as u32), SYSCALL_GET_TIME => sys_get_time(), SYSCALL_GETPID => sys_getpid(), SYSCALL_FORK => sys_fork(), diff --git a/os/src/syscall/process.rs b/os/src/syscall/process.rs index 4184e23..a0c6659 100644 --- a/os/src/syscall/process.rs +++ b/os/src/syscall/process.rs @@ -1,8 +1,8 @@ use crate::fs::{open_file, OpenFlags}; use crate::mm::{translated_ref, translated_refmut, translated_str}; use crate::task::{ - current_process, current_task, current_user_token, exit_current_and_run_next, - suspend_current_and_run_next, + current_process, current_task, current_user_token, exit_current_and_run_next, pid2process, + suspend_current_and_run_next, SignalFlags, }; use crate::timer::get_time_ms; use alloc::string::String; @@ -103,3 +103,16 @@ pub fn sys_waitpid(pid: isize, exit_code_ptr: *mut i32) -> isize { } // ---- release current PCB automatically } + +pub fn sys_kill(pid: usize, signal: u32) -> isize { + if let Some(process) = pid2process(pid) { + if let Some(flag) = SignalFlags::from_bits(signal) { + process.inner_exclusive_access().signals |= flag; + 0 + } else { + -1 + } + } else { + -1 + } +} diff --git a/os/src/task/manager.rs b/os/src/task/manager.rs index 542a0b8..fe37632 100644 --- a/os/src/task/manager.rs +++ b/os/src/task/manager.rs @@ -1,6 +1,6 @@ -use super::TaskControlBlock; +use super::{ProcessControlBlock, TaskControlBlock}; use crate::sync::UPSafeCell; -use alloc::collections::VecDeque; +use alloc::collections::{BTreeMap, VecDeque}; use alloc::sync::Arc; use lazy_static::*; @@ -26,6 +26,8 @@ impl TaskManager { lazy_static! { pub static ref TASK_MANAGER: UPSafeCell = unsafe { UPSafeCell::new(TaskManager::new()) }; + pub static ref PID2PCB: UPSafeCell>> = + unsafe { UPSafeCell::new(BTreeMap::new()) }; } pub fn add_task(task: Arc) { @@ -35,3 +37,19 @@ pub fn add_task(task: Arc) { pub fn fetch_task() -> Option> { TASK_MANAGER.exclusive_access().fetch() } + +pub fn pid2process(pid: usize) -> Option> { + let map = PID2PCB.exclusive_access(); + map.get(&pid).map(|task| Arc::clone(task)) +} + +pub fn insert_into_pid2process(pid: usize, process: Arc) { + PID2PCB.exclusive_access().insert(pid, process); +} + +pub fn remove_from_pid2process(pid: usize) { + let mut map = PID2PCB.exclusive_access(); + if map.remove(&pid).is_none() { + panic!("cannot find pid {} in pid2task!", pid); + } +} diff --git a/os/src/task/mod.rs b/os/src/task/mod.rs index 1e20540..7c16d82 100644 --- a/os/src/task/mod.rs +++ b/os/src/task/mod.rs @@ -3,6 +3,7 @@ mod id; mod manager; mod process; mod processor; +mod signal; mod switch; mod task; @@ -15,11 +16,12 @@ use switch::__switch; pub use context::TaskContext; pub use id::{kstack_alloc, pid_alloc, KernelStack, PidHandle}; -pub use manager::add_task; +pub use manager::{add_task, pid2process, remove_from_pid2process}; pub use processor::{ current_kstack_top, current_process, current_task, current_trap_cx, current_trap_cx_user_va, current_user_token, run_tasks, schedule, take_current_task, }; +pub use signal::SignalFlags; pub use task::{TaskControlBlock, TaskStatus}; pub fn suspend_current_and_run_next() { @@ -64,6 +66,7 @@ pub fn exit_current_and_run_next(exit_code: i32) { // however, if this is the main thread of current process // the process should terminate at once if tid == 0 { + remove_from_pid2process(process.getpid()); let mut process_inner = process.inner_exclusive_access(); // mark this process as a zombie process process_inner.is_zombie = true; @@ -111,3 +114,15 @@ lazy_static! { pub fn add_initproc() { let _initproc = INITPROC.clone(); } + +pub fn check_signals_of_current() -> Option<(i32, &'static str)> { + let process = current_process(); + let process_inner = process.inner_exclusive_access(); + process_inner.signals.check_error() +} + +pub fn current_add_signal(signal: SignalFlags) { + let process = current_process(); + let mut process_inner = process.inner_exclusive_access(); + process_inner.signals |= signal; +} diff --git a/os/src/task/process.rs b/os/src/task/process.rs index c393544..ffff14f 100644 --- a/os/src/task/process.rs +++ b/os/src/task/process.rs @@ -1,6 +1,7 @@ -use super::add_task; use super::id::RecycleAllocator; +use super::manager::insert_into_pid2process; use super::TaskControlBlock; +use super::{add_task, SignalFlags}; use super::{pid_alloc, PidHandle}; use crate::fs::{File, Stdin, Stdout}; use crate::mm::{translated_refmut, MemorySet, KERNEL_SPACE}; @@ -26,6 +27,7 @@ pub struct ProcessControlBlockInner { pub children: Vec>, pub exit_code: i32, pub fd_table: Vec>>, + pub signals: SignalFlags, pub tasks: Vec>>, pub task_res_allocator: RecycleAllocator, pub mutex_list: Vec>>, @@ -92,6 +94,7 @@ impl ProcessControlBlock { // 2 -> stderr Some(Arc::new(Stdout)), ], + signals: SignalFlags::empty(), tasks: Vec::new(), task_res_allocator: RecycleAllocator::new(), mutex_list: Vec::new(), @@ -123,6 +126,7 @@ impl ProcessControlBlock { let mut process_inner = process.inner_exclusive_access(); process_inner.tasks.push(Some(Arc::clone(&task))); drop(process_inner); + insert_into_pid2process(process.getpid(), Arc::clone(&process)); // add main thread to scheduler add_task(task); process @@ -209,6 +213,7 @@ impl ProcessControlBlock { children: Vec::new(), exit_code: 0, fd_table: new_fd_table, + signals: SignalFlags::empty(), tasks: Vec::new(), task_res_allocator: RecycleAllocator::new(), mutex_list: Vec::new(), @@ -242,6 +247,7 @@ impl ProcessControlBlock { let trap_cx = task_inner.get_trap_cx(); trap_cx.kernel_sp = task.kstack.get_top(); drop(task_inner); + insert_into_pid2process(child.getpid(), Arc::clone(&child)); // add this thread to scheduler add_task(task); child diff --git a/os/src/task/signal.rs b/os/src/task/signal.rs new file mode 100644 index 0000000..46f1ad9 --- /dev/null +++ b/os/src/task/signal.rs @@ -0,0 +1,29 @@ +use bitflags::*; + +bitflags! { + pub struct SignalFlags: u32 { + const SIGINT = 1 << 2; + const SIGILL = 1 << 4; + const SIGABRT = 1 << 6; + const SIGFPE = 1 << 8; + const SIGSEGV = 1 << 11; + } +} + +impl SignalFlags { + pub fn check_error(&self) -> Option<(i32, &'static str)> { + if self.contains(Self::SIGINT) { + Some((-2, "Killed, SIGINT=2")) + } else if self.contains(Self::SIGILL) { + Some((-4, "Illegal Instruction, SIGILL=4")) + } else if self.contains(Self::SIGABRT) { + Some((-6, "Aborted, SIGABRT=6")) + } else if self.contains(Self::SIGFPE) { + Some((-8, "Erroneous Arithmetic Operation, SIGFPE=8")) + } else if self.contains(Self::SIGSEGV) { + Some((-11, "Segmentation Fault, SIGSEGV=11")) + } else { + None + } + } +} diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index 443f330..6397811 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -3,8 +3,8 @@ mod context; use crate::config::TRAMPOLINE; use crate::syscall::syscall; use crate::task::{ - current_trap_cx, current_trap_cx_user_va, current_user_token, exit_current_and_run_next, - suspend_current_and_run_next, + check_signals_of_current, current_add_signal, current_trap_cx, current_trap_cx_user_va, + current_user_token, exit_current_and_run_next, suspend_current_and_run_next, SignalFlags, }; use crate::timer::{check_timer, set_next_trigger}; use core::arch::{asm, global_asm}; @@ -60,19 +60,18 @@ pub fn trap_handler() -> ! { | Trap::Exception(Exception::InstructionPageFault) | Trap::Exception(Exception::LoadFault) | Trap::Exception(Exception::LoadPageFault) => { + /* println!( "[kernel] {:?} in application, bad addr = {:#x}, bad instruction = {:#x}, kernel killed it.", scause.cause(), stval, current_trap_cx().sepc, ); - // page fault exit code - exit_current_and_run_next(-2); + */ + current_add_signal(SignalFlags::SIGSEGV); } Trap::Exception(Exception::IllegalInstruction) => { - println!("[kernel] IllegalInstruction in application, kernel killed it."); - // illegal instruction exit code - exit_current_and_run_next(-3); + current_add_signal(SignalFlags::SIGILL); } Trap::Interrupt(Interrupt::SupervisorTimer) => { set_next_trigger(); @@ -87,6 +86,11 @@ pub fn trap_handler() -> ! { ); } } + // check signals + if let Some((errno, msg)) = check_signals_of_current() { + println!("[kernel] {}", msg); + exit_current_and_run_next(errno); + } trap_return(); } diff --git a/user/Cargo.toml b/user/Cargo.toml index d50a0ba..f522c9e 100644 --- a/user/Cargo.toml +++ b/user/Cargo.toml @@ -8,4 +8,5 @@ edition = "2018" [dependencies] buddy_system_allocator = "0.6" -bitflags = "1.2.1" \ No newline at end of file +bitflags = "1.2.1" +riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] } diff --git a/user/src/bin/cat.rs b/user/src/bin/cat.rs index 424f44f..9cbed23 100644 --- a/user/src/bin/cat.rs +++ b/user/src/bin/cat.rs @@ -15,7 +15,7 @@ pub fn main(argc: usize, argv: &[&str]) -> i32 { panic!("Error occured when opening file"); } let fd = fd as usize; - let mut buf = [0u8; 16]; + let mut buf = [0u8; 256]; loop { let size = read(fd, &mut buf) as usize; if size == 0 { diff --git a/user/src/bin/infloop.rs b/user/src/bin/infloop.rs new file mode 100644 index 0000000..1f24853 --- /dev/null +++ b/user/src/bin/infloop.rs @@ -0,0 +1,10 @@ +#![no_std] +#![no_main] +#![allow(clippy::empty_loop)] + +extern crate user_lib; + +#[no_mangle] +pub fn main(_argc: usize, _argv: &[&str]) -> ! { + loop {} +} diff --git a/user/src/bin/priv_csr.rs b/user/src/bin/priv_csr.rs new file mode 100644 index 0000000..fbd678f --- /dev/null +++ b/user/src/bin/priv_csr.rs @@ -0,0 +1,17 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use riscv::register::sstatus::{self, SPP}; + +#[no_mangle] +fn main() -> i32 { + println!("Try to access privileged CSR in U Mode"); + println!("Kernel should kill this application!"); + unsafe { + sstatus::set_spp(SPP::User); + } + 0 +} diff --git a/user/src/bin/priv_inst.rs b/user/src/bin/priv_inst.rs new file mode 100644 index 0000000..04dac37 --- /dev/null +++ b/user/src/bin/priv_inst.rs @@ -0,0 +1,17 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use core::arch::asm; + +#[no_mangle] +fn main() -> i32 { + println!("Try to execute privileged instruction in U Mode"); + println!("Kernel should kill this application!"); + unsafe { + asm!("sret"); + } + 0 +} diff --git a/user/src/bin/store_fault.rs b/user/src/bin/store_fault.rs new file mode 100644 index 0000000..f8023eb --- /dev/null +++ b/user/src/bin/store_fault.rs @@ -0,0 +1,15 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +#[no_mangle] +fn main() -> i32 { + println!("Into Test store_fault, we will insert an invalid store operation..."); + println!("Kernel should kill this application!"); + unsafe { + core::ptr::null_mut::().write_volatile(0); + } + 0 +} diff --git a/user/src/bin/until_timeout.rs b/user/src/bin/until_timeout.rs new file mode 100644 index 0000000..a0007cb --- /dev/null +++ b/user/src/bin/until_timeout.rs @@ -0,0 +1,46 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use user_lib::{exec, fork, get_time, kill, waitpid, waitpid_nb, SignalFlags}; + +#[no_mangle] +pub fn main(argc: usize, argv: &[&str]) -> i32 { + assert_eq!(argc, 3, "argc must be 3!"); + let timeout_ms = argv[2] + .parse::() + .expect("Error when parsing timeout!"); + let pid = fork() as usize; + if pid == 0 { + if exec(argv[1], &[core::ptr::null::()]) != 0 { + println!("Error when executing '{}'", argv[1]); + return -4; + } + } else { + let start_time = get_time(); + let mut child_exited = false; + let mut exit_code: i32 = 0; + loop { + if get_time() - start_time > timeout_ms { + break; + } + if waitpid_nb(pid, &mut exit_code) as usize == pid { + child_exited = true; + println!( + "child exited in {}ms, exit_code = {}", + get_time() - start_time, + exit_code, + ); + } + } + if !child_exited { + println!("child has run for {}ms, kill it!", timeout_ms); + kill(pid, SignalFlags::SIGINT.bits()); + assert_eq!(waitpid(pid, &mut exit_code) as usize, pid); + println!("exit code of the child is {}", exit_code); + } + } + 0 +} diff --git a/user/src/lang_items.rs b/user/src/lang_items.rs index c65aa42..df0467c 100644 --- a/user/src/lang_items.rs +++ b/user/src/lang_items.rs @@ -1,4 +1,4 @@ -use super::exit; +use super::{getpid, kill, SignalFlags}; #[panic_handler] fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! { @@ -13,5 +13,6 @@ fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! { } else { println!("Panicked: {}", err); } - exit(-1); + kill(getpid() as usize, SignalFlags::SIGABRT.bits()); + unreachable!() } diff --git a/user/src/lib.rs b/user/src/lib.rs index 50b6439..60f39ff 100644 --- a/user/src/lib.rs +++ b/user/src/lib.rs @@ -127,6 +127,25 @@ pub fn waitpid(pid: usize, exit_code: &mut i32) -> isize { } } } + +pub fn waitpid_nb(pid: usize, exit_code: &mut i32) -> isize { + sys_waitpid(pid as isize, exit_code as *mut _) +} + +bitflags! { + pub struct SignalFlags: i32 { + const SIGINT = 1 << 2; + const SIGILL = 1 << 4; + const SIGABRT = 1 << 6; + const SIGFPE = 1 << 8; + const SIGSEGV = 1 << 11; + } +} + +pub fn kill(pid: usize, signal: i32) -> isize { + sys_kill(pid, signal) +} + pub fn sleep(sleep_ms: usize) { sys_sleep(sleep_ms); } diff --git a/user/src/syscall.rs b/user/src/syscall.rs index ce30d00..4cd714e 100644 --- a/user/src/syscall.rs +++ b/user/src/syscall.rs @@ -9,6 +9,7 @@ const SYSCALL_WRITE: usize = 64; const SYSCALL_EXIT: usize = 93; const SYSCALL_SLEEP: usize = 101; const SYSCALL_YIELD: usize = 124; +const SYSCALL_KILL: usize = 129; const SYSCALL_GET_TIME: usize = 169; const SYSCALL_GETPID: usize = 172; const SYSCALL_FORK: usize = 220; @@ -81,6 +82,10 @@ pub fn sys_yield() -> isize { syscall(SYSCALL_YIELD, [0, 0, 0]) } +pub fn sys_kill(pid: usize, signal: i32) -> isize { + syscall(SYSCALL_KILL, [pid, signal as usize, 0]) +} + pub fn sys_get_time() -> isize { syscall(SYSCALL_GET_TIME, [0, 0, 0]) } From 9159035767fa43347521ba5c10c7b4d67df8e07d Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Sun, 23 Jan 2022 13:18:47 -0800 Subject: [PATCH 27/56] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 856f2cb..e58af4e 100644 --- a/README.md +++ b/README.md @@ -214,12 +214,12 @@ Here are the updates since 3.5.0: * [x] clarify: "check validity of level-3 pte in `find_pte` instead of checking it outside this function" should not be a bug * [x] code of chapter 8: synchronization on a uniprocessor * [x] switch the code of chapter 6 and chapter 7 -* [x] support signal mechanism in chapter 7 +* [x] support signal mechanism in chapter 7/8(only works for apps with a single thread) ### Todo(High priority) * [ ] review documentation, current progress: 5/9 -* [ ] support signal mechanism in chapter 8 +* [ ] support user-level sync primitives in chapter 8 * [ ] code of chapter 9: device drivers based on interrupts, including UART and block devices * [ ] use old fs image optionally, do not always rebuild the image * [ ] add new system calls: getdents64/fstat @@ -234,7 +234,6 @@ Here are the updates since 3.5.0: * [ ] format the code using official tools * [ ] support other platforms - ### Crates We will add them later. From 60143939d4fead7b32c999de306109dbd26e9452 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Mon, 24 Jan 2022 23:23:03 -0800 Subject: [PATCH 28/56] Add boards/ && clippy --- os/src/boards/k210.rs | 23 +++++++++++++++++++++++ os/src/boards/qemu.rs | 6 ++++++ os/src/config.rs | 28 +--------------------------- os/src/drivers/block/mod.rs | 10 ++++------ os/src/drivers/block/sdcard.rs | 14 +++++++------- os/src/drivers/mod.rs | 2 +- os/src/fs/pipe.rs | 24 ++++++++++++------------ os/src/main.rs | 7 +++++++ os/src/mm/address.rs | 6 +++--- os/src/mm/frame_allocator.rs | 4 ++-- os/src/mm/heap_allocator.rs | 4 ++-- os/src/mm/memory_set.rs | 22 ++++++++-------------- os/src/mm/page_table.rs | 10 +++++----- os/src/sync/mutex.rs | 2 +- os/src/syscall/process.rs | 5 ++--- os/src/task/id.rs | 2 +- os/src/task/manager.rs | 2 +- os/src/task/mod.rs | 1 + os/src/task/processor.rs | 5 ++--- os/src/timer.rs | 1 - 20 files changed, 89 insertions(+), 89 deletions(-) create mode 100644 os/src/boards/k210.rs create mode 100644 os/src/boards/qemu.rs diff --git a/os/src/boards/k210.rs b/os/src/boards/k210.rs new file mode 100644 index 0000000..4b2fd44 --- /dev/null +++ b/os/src/boards/k210.rs @@ -0,0 +1,23 @@ +pub const CLOCK_FREQ: usize = 403000000 / 62; + +pub const MMIO: &[(usize, usize)] = &[ + // we don't need clint in S priv when running + // we only need claim/complete for target0 after initializing + (0x0C00_0000, 0x3000), /* PLIC */ + (0x0C20_0000, 0x1000), /* PLIC */ + (0x3800_0000, 0x1000), /* UARTHS */ + (0x3800_1000, 0x1000), /* GPIOHS */ + (0x5020_0000, 0x1000), /* GPIO */ + (0x5024_0000, 0x1000), /* SPI_SLAVE */ + (0x502B_0000, 0x1000), /* FPIOA */ + (0x502D_0000, 0x1000), /* TIMER0 */ + (0x502E_0000, 0x1000), /* TIMER1 */ + (0x502F_0000, 0x1000), /* TIMER2 */ + (0x5044_0000, 0x1000), /* SYSCTL */ + (0x5200_0000, 0x1000), /* SPI0 */ + (0x5300_0000, 0x1000), /* SPI1 */ + (0x5400_0000, 0x1000), /* SPI2 */ +]; + +pub type BlockDeviceImpl = crate::drivers::block::SDCardWrapper; + diff --git a/os/src/boards/qemu.rs b/os/src/boards/qemu.rs new file mode 100644 index 0000000..b349252 --- /dev/null +++ b/os/src/boards/qemu.rs @@ -0,0 +1,6 @@ +pub const CLOCK_FREQ: usize = 12500000; + +pub const MMIO: &[(usize, usize)] = &[(0x10001000, 0x1000)]; + +pub type BlockDeviceImpl = crate::drivers::block::VirtIOBlock; + diff --git a/os/src/config.rs b/os/src/config.rs index 6033b71..c1b2fa4 100644 --- a/os/src/config.rs +++ b/os/src/config.rs @@ -10,31 +10,5 @@ pub const PAGE_SIZE_BITS: usize = 0xc; pub const TRAMPOLINE: usize = usize::MAX - PAGE_SIZE + 1; pub const TRAP_CONTEXT_BASE: usize = TRAMPOLINE - PAGE_SIZE; -#[cfg(feature = "board_k210")] -pub const CLOCK_FREQ: usize = 403000000 / 62; +pub use crate::board::{CLOCK_FREQ, MMIO}; -#[cfg(feature = "board_qemu")] -pub const CLOCK_FREQ: usize = 12500000; - -#[cfg(feature = "board_qemu")] -pub const MMIO: &[(usize, usize)] = &[(0x10001000, 0x1000)]; - -#[cfg(feature = "board_k210")] -pub const MMIO: &[(usize, usize)] = &[ - // we don't need clint in S priv when running - // we only need claim/complete for target0 after initializing - (0x0C00_0000, 0x3000), /* PLIC */ - (0x0C20_0000, 0x1000), /* PLIC */ - (0x3800_0000, 0x1000), /* UARTHS */ - (0x3800_1000, 0x1000), /* GPIOHS */ - (0x5020_0000, 0x1000), /* GPIO */ - (0x5024_0000, 0x1000), /* SPI_SLAVE */ - (0x502B_0000, 0x1000), /* FPIOA */ - (0x502D_0000, 0x1000), /* TIMER0 */ - (0x502E_0000, 0x1000), /* TIMER1 */ - (0x502F_0000, 0x1000), /* TIMER2 */ - (0x5044_0000, 0x1000), /* SYSCTL */ - (0x5200_0000, 0x1000), /* SPI0 */ - (0x5300_0000, 0x1000), /* SPI1 */ - (0x5400_0000, 0x1000), /* SPI2 */ -]; diff --git a/os/src/drivers/block/mod.rs b/os/src/drivers/block/mod.rs index 67436fd..7c1bb55 100644 --- a/os/src/drivers/block/mod.rs +++ b/os/src/drivers/block/mod.rs @@ -1,15 +1,13 @@ mod sdcard; mod virtio_blk; +pub use virtio_blk::VirtIOBlock; +pub use sdcard::SDCardWrapper; + use alloc::sync::Arc; use easy_fs::BlockDevice; use lazy_static::*; - -#[cfg(feature = "board_qemu")] -type BlockDeviceImpl = virtio_blk::VirtIOBlock; - -#[cfg(feature = "board_k210")] -type BlockDeviceImpl = sdcard::SDCardWrapper; +use crate::board::BlockDeviceImpl; lazy_static! { pub static ref BLOCK_DEVICE: Arc = Arc::new(BlockDeviceImpl::new()); diff --git a/os/src/drivers/block/sdcard.rs b/os/src/drivers/block/sdcard.rs index 8603b2f..a74accc 100644 --- a/os/src/drivers/block/sdcard.rs +++ b/os/src/drivers/block/sdcard.rs @@ -314,7 +314,7 @@ impl SDCard { timeout -= 1; } /* After time out */ - return 0xFF; + 0xFF } /* @@ -341,7 +341,7 @@ impl SDCard { self.read_data(response); } /* Return response */ - return 0; + 0 } /* @@ -371,7 +371,7 @@ impl SDCard { self.read_data(&mut csd_tab); self.end_cmd(); /* see also: https://cdn-shop.adafruit.com/datasheets/TS16GUSDHC6.pdf */ - return Ok(SDCardCSD { + Ok(SDCardCSD { /* Byte 0 */ CSDStruct: (csd_tab[0] & 0xC0) >> 6, SysSpecVersion: (csd_tab[0] & 0x3C) >> 2, @@ -424,7 +424,7 @@ impl SDCard { CSD_CRC: (csd_tab[15] & 0xFE) >> 1, Reserved4: 1, /* Return the reponse */ - }); + }) } /* @@ -453,7 +453,7 @@ impl SDCard { /* Get CRC bytes (not really needed by us, but required by SD) */ self.read_data(&mut cid_tab); self.end_cmd(); - return Ok(SDCardCID { + Ok(SDCardCID { /* Byte 0 */ ManufacturerID: cid_tab[0], /* Byte 1, 2 */ @@ -478,7 +478,7 @@ impl SDCard { /* Byte 15 */ CID_CRC: (cid_tab[15] & 0xFE) >> 1, Reserved2: 1, - }); + }) } /* @@ -684,7 +684,7 @@ impl SDCard { *a = b; } //self.write_data_dma(&mut dma_chunk); - self.write_data(&mut tmp_chunk); + self.write_data(&tmp_chunk); /* Put dummy CRC bytes */ self.write_data(&[0xff, 0xff]); /* Read data response */ diff --git a/os/src/drivers/mod.rs b/os/src/drivers/mod.rs index 43a6f54..c2dea36 100644 --- a/os/src/drivers/mod.rs +++ b/os/src/drivers/mod.rs @@ -1,3 +1,3 @@ -mod block; +pub mod block; pub use block::BLOCK_DEVICE; diff --git a/os/src/fs/pipe.rs b/os/src/fs/pipe.rs index ed71495..75caac5 100644 --- a/os/src/fs/pipe.rs +++ b/os/src/fs/pipe.rs @@ -32,9 +32,9 @@ const RING_BUFFER_SIZE: usize = 32; #[derive(Copy, Clone, PartialEq)] enum RingBufferStatus { - FULL, - EMPTY, - NORMAL, + Full, + Empty, + Normal, } pub struct PipeRingBuffer { @@ -51,7 +51,7 @@ impl PipeRingBuffer { arr: [0; RING_BUFFER_SIZE], head: 0, tail: 0, - status: RingBufferStatus::EMPTY, + status: RingBufferStatus::Empty, write_end: None, } } @@ -59,24 +59,24 @@ impl PipeRingBuffer { self.write_end = Some(Arc::downgrade(write_end)); } pub fn write_byte(&mut self, byte: u8) { - self.status = RingBufferStatus::NORMAL; + self.status = RingBufferStatus::Normal; self.arr[self.tail] = byte; self.tail = (self.tail + 1) % RING_BUFFER_SIZE; if self.tail == self.head { - self.status = RingBufferStatus::FULL; + self.status = RingBufferStatus::Full; } } pub fn read_byte(&mut self) -> u8 { - self.status = RingBufferStatus::NORMAL; + self.status = RingBufferStatus::Normal; let c = self.arr[self.head]; self.head = (self.head + 1) % RING_BUFFER_SIZE; if self.head == self.tail { - self.status = RingBufferStatus::EMPTY; + self.status = RingBufferStatus::Empty; } c } pub fn available_read(&self) -> usize { - if self.status == RingBufferStatus::EMPTY { + if self.status == RingBufferStatus::Empty { 0 } else if self.tail > self.head { self.tail - self.head @@ -85,7 +85,7 @@ impl PipeRingBuffer { } } pub fn available_write(&self) -> usize { - if self.status == RingBufferStatus::FULL { + if self.status == RingBufferStatus::Full { 0 } else { RING_BUFFER_SIZE - self.available_read() @@ -113,7 +113,7 @@ impl File for Pipe { self.writable } fn read(&self, buf: UserBuffer) -> usize { - assert_eq!(self.readable(), true); + assert!(self.readable()); let mut buf_iter = buf.into_iter(); let mut read_size = 0usize; loop { @@ -141,7 +141,7 @@ impl File for Pipe { } } fn write(&self, buf: UserBuffer) -> usize { - assert_eq!(self.writable(), true); + assert!(self.writable()); let mut buf_iter = buf.into_iter(); let mut write_size = 0usize; loop { diff --git a/os/src/main.rs b/os/src/main.rs index 23b9725..cbc4ab4 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -8,6 +8,13 @@ extern crate alloc; #[macro_use] extern crate bitflags; +#[cfg(feature = "board_k210")] +#[path = "boards/k210.rs"] +mod board; +#[cfg(not(any(feature = "board_k210")))] +#[path = "boards/qemu.rs"] +mod board; + #[macro_use] mod console; mod config; diff --git a/os/src/mm/address.rs b/os/src/mm/address.rs index 7faceaa..dc5d08a 100644 --- a/os/src/mm/address.rs +++ b/os/src/mm/address.rs @@ -165,15 +165,15 @@ impl PhysAddr { } impl PhysPageNum { pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] { - let pa: PhysAddr = self.clone().into(); + let pa: PhysAddr = (*self).into(); unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512) } } pub fn get_bytes_array(&self) -> &'static mut [u8] { - let pa: PhysAddr = self.clone().into(); + let pa: PhysAddr = (*self).into(); unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096) } } pub fn get_mut(&self) -> &'static mut T { - let pa: PhysAddr = self.clone().into(); + let pa: PhysAddr = (*self).into(); pa.get_mut() } } diff --git a/os/src/mm/frame_allocator.rs b/os/src/mm/frame_allocator.rs index e0db1d4..4bc7db2 100644 --- a/os/src/mm/frame_allocator.rs +++ b/os/src/mm/frame_allocator.rs @@ -72,7 +72,7 @@ impl FrameAllocator for StackFrameAllocator { fn dealloc(&mut self, ppn: PhysPageNum) { let ppn = ppn.0; // validity check - if ppn >= self.current || self.recycled.iter().find(|&v| *v == ppn).is_some() { + if ppn >= self.current || self.recycled.iter().any(|&v| v == ppn) { panic!("Frame ppn={:#x} has not been allocated!", ppn); } // recycle @@ -101,7 +101,7 @@ pub fn frame_alloc() -> Option { FRAME_ALLOCATOR .exclusive_access() .alloc() - .map(|ppn| FrameTracker::new(ppn)) + .map(FrameTracker::new) } pub fn frame_dealloc(ppn: PhysPageNum) { diff --git a/os/src/mm/heap_allocator.rs b/os/src/mm/heap_allocator.rs index b802bbd..42a6d76 100644 --- a/os/src/mm/heap_allocator.rs +++ b/os/src/mm/heap_allocator.rs @@ -36,8 +36,8 @@ pub fn heap_test() { for i in 0..500 { v.push(i); } - for i in 0..500 { - assert_eq!(v[i], i); + for (i, val) in v.iter().take(500).enumerate() { + assert_eq!(*val, i); } assert!(bss_range.contains(&(v.as_ptr() as usize))); drop(v); diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index 2ebc6b6..0d0b0f1 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -291,11 +291,8 @@ impl MapArea { page_table.map(vpn, ppn, pte_flags); } pub fn unmap_one(&mut self, page_table: &mut PageTable, vpn: VirtPageNum) { - match self.map_type { - MapType::Framed => { - self.data_frames.remove(&vpn); - } - _ => {} + if self.map_type == MapType::Framed { + self.data_frames.remove(&vpn); } page_table.unmap(vpn); } @@ -354,29 +351,26 @@ pub fn remap_test() { let mid_text: VirtAddr = ((stext as usize + etext as usize) / 2).into(); let mid_rodata: VirtAddr = ((srodata as usize + erodata as usize) / 2).into(); let mid_data: VirtAddr = ((sdata as usize + edata as usize) / 2).into(); - assert_eq!( - kernel_space + assert!( + !kernel_space .page_table .translate(mid_text.floor()) .unwrap() .writable(), - false ); - assert_eq!( - kernel_space + assert!( + !kernel_space .page_table .translate(mid_rodata.floor()) .unwrap() .writable(), - false, ); - assert_eq!( - kernel_space + assert!( + !kernel_space .page_table .translate(mid_data.floor()) .unwrap() .executable(), - false, ); println!("remap_test passed!"); } diff --git a/os/src/mm/page_table.rs b/os/src/mm/page_table.rs index 9de77af..dfaf4b6 100644 --- a/os/src/mm/page_table.rs +++ b/os/src/mm/page_table.rs @@ -77,8 +77,8 @@ impl PageTable { let idxs = vpn.indexes(); let mut ppn = self.root_ppn; let mut result: Option<&mut PageTableEntry> = None; - for i in 0..3 { - let pte = &mut ppn.get_pte_array()[idxs[i]]; + for (i, idx) in idxs.iter().enumerate() { + let pte = &mut ppn.get_pte_array()[*idx]; if i == 2 { result = Some(pte); break; @@ -96,8 +96,8 @@ impl PageTable { let idxs = vpn.indexes(); let mut ppn = self.root_ppn; let mut result: Option<&mut PageTableEntry> = None; - for i in 0..3 { - let pte = &mut ppn.get_pte_array()[idxs[i]]; + for (i, idx) in idxs.iter().enumerate() { + let pte = &mut ppn.get_pte_array()[*idx]; if i == 2 { result = Some(pte); break; @@ -122,7 +122,7 @@ impl PageTable { *pte = PageTableEntry::empty(); } pub fn translate(&self, vpn: VirtPageNum) -> Option { - self.find_pte(vpn).map(|pte| pte.clone()) + self.find_pte(vpn).map(|pte| *pte) } pub fn translate_va(&self, va: VirtAddr) -> Option { self.find_pte(va.clone().floor()).map(|pte| { diff --git a/os/src/sync/mutex.rs b/os/src/sync/mutex.rs index dee0850..be58f79 100644 --- a/os/src/sync/mutex.rs +++ b/os/src/sync/mutex.rs @@ -78,7 +78,7 @@ impl Mutex for MutexBlocking { fn unlock(&self) { let mut mutex_inner = self.inner.exclusive_access(); - assert_eq!(mutex_inner.locked, true); + assert!(mutex_inner.locked); if let Some(waking_task) = mutex_inner.wait_queue.pop_front() { add_task(waking_task); } else { diff --git a/os/src/syscall/process.rs b/os/src/syscall/process.rs index a0c6659..7d5b67a 100644 --- a/os/src/syscall/process.rs +++ b/os/src/syscall/process.rs @@ -74,11 +74,10 @@ pub fn sys_waitpid(pid: isize, exit_code_ptr: *mut i32) -> isize { // find a child process let mut inner = process.inner_exclusive_access(); - if inner + if !inner .children .iter() - .find(|p| pid == -1 || pid as usize == p.getpid()) - .is_none() + .any(|p| pid == -1 || pid as usize == p.getpid()) { return -1; // ---- release current PCB diff --git a/os/src/task/id.rs b/os/src/task/id.rs index 476fc22..178a09f 100644 --- a/os/src/task/id.rs +++ b/os/src/task/id.rs @@ -31,7 +31,7 @@ impl RecycleAllocator { pub fn dealloc(&mut self, id: usize) { assert!(id < self.current); assert!( - self.recycled.iter().find(|i| **i == id).is_none(), + !self.recycled.iter().any(|i| *i == id), "id {} has been deallocated!", id ); diff --git a/os/src/task/manager.rs b/os/src/task/manager.rs index fe37632..5ed68ae 100644 --- a/os/src/task/manager.rs +++ b/os/src/task/manager.rs @@ -40,7 +40,7 @@ pub fn fetch_task() -> Option> { pub fn pid2process(pid: usize) -> Option> { let map = PID2PCB.exclusive_access(); - map.get(&pid).map(|task| Arc::clone(task)) + map.get(&pid).map(Arc::clone) } pub fn insert_into_pid2process(pid: usize, process: Arc) { diff --git a/os/src/task/mod.rs b/os/src/task/mod.rs index 7c16d82..1bd7b2a 100644 --- a/os/src/task/mod.rs +++ b/os/src/task/mod.rs @@ -5,6 +5,7 @@ mod process; mod processor; mod signal; mod switch; +#[allow(clippy::module_inception)] mod task; use crate::fs::{open_file, OpenFlags}; diff --git a/os/src/task/processor.rs b/os/src/task/processor.rs index e321086..3c1a22f 100644 --- a/os/src/task/processor.rs +++ b/os/src/task/processor.rs @@ -25,7 +25,7 @@ impl Processor { self.current.take() } pub fn current(&self) -> Option> { - self.current.as_ref().map(|task| Arc::clone(task)) + self.current.as_ref().map(Arc::clone) } } @@ -70,8 +70,7 @@ pub fn current_process() -> Arc { pub fn current_user_token() -> usize { let task = current_task().unwrap(); - let token = task.get_user_token(); - token + task.get_user_token() } pub fn current_trap_cx() -> &'static mut TrapContext { diff --git a/os/src/timer.rs b/os/src/timer.rs index efd87da..3baed0f 100644 --- a/os/src/timer.rs +++ b/os/src/timer.rs @@ -65,7 +65,6 @@ pub fn check_timer() { while let Some(timer) = timers.peek() { if timer.expire_ms <= current_ms { add_task(Arc::clone(&timer.task)); - drop(timer); timers.pop(); } else { break; From 7b1ef5f8e782fdc1622ebfcdfa8e1ac8073af394 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Mon, 24 Jan 2022 23:32:13 -0800 Subject: [PATCH 29/56] Update README.md --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e58af4e..91dc226 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ rCore-Tutorial version 3.5. See the [Documentation in Chinese](https://rcore-os. Official QQ group number: 735045051 ## news -- 2021.11.20: Now we are updating our labs. Please checkout chX-dev Branches for our current new labs. (Notice: please see the [Dependency] section in the end of this doc) +- 25/01/2022: Version 3.6.0 is on the way! Now we directly update the code on chX branches, please periodically check if there are any updates. ## Overview @@ -186,6 +186,12 @@ $ make run BOARD=k210 Type `Ctrl+]` to disconnect from K210. +## Rustdoc + +Currently it can only help you view the code since only a tiny part of the code has been documented. + +You can open a doc html of `os` using `cargo doc --no-deps --open` under `os` directory. + ## Working in progress Our first release 3.5.0 (chapter 1-7) has been published. @@ -215,6 +221,7 @@ Here are the updates since 3.5.0: * [x] code of chapter 8: synchronization on a uniprocessor * [x] switch the code of chapter 6 and chapter 7 * [x] support signal mechanism in chapter 7/8(only works for apps with a single thread) +* [x] Add boards/ directory and support rustdoc, for example you can use `cargo doc --no-deps --open` to view the documentation of a crate ### Todo(High priority) From 017758bf2b5699ce6d9cf94b4435ce2bf90f70ba Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Sat, 5 Feb 2022 09:44:40 -0800 Subject: [PATCH 30/56] Update rustsbi-qemu --- bootloader/rustsbi-qemu.bin | Bin 161528 -> 165883 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bootloader/rustsbi-qemu.bin b/bootloader/rustsbi-qemu.bin index ddbf336ac1071bdfed06c14e22430dcea70069d8..5f319c3dad0af5fdf3f21151a3c5cd9718f04d78 100755 GIT binary patch literal 165883 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%0D){{#HSSA-Vg8Rf4pP&Ob_im`~PW(wq3b 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� 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 ICPvr!UzPdW-~a#s From c6db34e2c795de6e8752e88118bfbc9eb35cd96a Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Mon, 7 Feb 2022 14:13:30 -0800 Subject: [PATCH 31/56] IRQ-based VirtIOBlk Access. Plz wait for the virtio-drivers crate to be updated. --- easy-fs-fuse/src/main.rs | 2 + easy-fs/src/block_dev.rs | 1 + os/Cargo.toml | 5 +- os/src/boards/k210.rs | 7 ++ os/src/boards/qemu.rs | 39 ++++++++- os/src/drivers/block/sdcard.rs | 1 + os/src/drivers/block/virtio_blk.rs | 68 +++++++++++---- os/src/drivers/mod.rs | 1 + os/src/drivers/plic.rs | 127 +++++++++++++++++++++++++++++ os/src/main.rs | 15 +++- os/src/sync/condvar.rs | 9 +- os/src/syscall/sync.rs | 2 +- os/src/trap/mod.rs | 3 + 13 files changed, 258 insertions(+), 22 deletions(-) create mode 100644 os/src/drivers/plic.rs diff --git a/easy-fs-fuse/src/main.rs b/easy-fs-fuse/src/main.rs index cac1b79..d332016 100644 --- a/easy-fs-fuse/src/main.rs +++ b/easy-fs-fuse/src/main.rs @@ -23,6 +23,8 @@ impl BlockDevice for BlockFile { .expect("Error when seeking!"); assert_eq!(file.write(buf).unwrap(), BLOCK_SZ, "Not a complete block!"); } + + fn handle_irq(&self) { unimplemented!(); } } fn main() { diff --git a/easy-fs/src/block_dev.rs b/easy-fs/src/block_dev.rs index 8a01edd..eb39fbd 100644 --- a/easy-fs/src/block_dev.rs +++ b/easy-fs/src/block_dev.rs @@ -3,4 +3,5 @@ use core::any::Any; pub trait BlockDevice: Send + Sync + Any { fn read_block(&self, block_id: usize, buf: &mut [u8]); fn write_block(&self, block_id: usize, buf: &[u8]); + fn handle_irq(&self); } diff --git a/os/Cargo.toml b/os/Cargo.toml index b07fa03..18d6884 100644 --- a/os/Cargo.toml +++ b/os/Cargo.toml @@ -12,7 +12,8 @@ lazy_static = { version = "1.4.0", features = ["spin_no_std"] } buddy_system_allocator = "0.6" bitflags = "1.2.1" xmas-elf = "0.7.0" -virtio-drivers = { git = "https://github.com/rcore-os/virtio-drivers" } +#virtio-drivers = { git = "https://github.com/wyfcyx/virtio-drivers" } +virtio-drivers = { path = "../../virtio-drivers" } k210-pac = { git = "https://github.com/wyfcyx/k210-pac" } k210-hal = { git = "https://github.com/wyfcyx/k210-hal" } k210-soc = { git = "https://github.com/wyfcyx/k210-soc" } @@ -20,4 +21,4 @@ easy-fs = { path = "../easy-fs" } [features] board_qemu = [] -board_k210 = [] \ No newline at end of file +board_k210 = [] diff --git a/os/src/boards/k210.rs b/os/src/boards/k210.rs index 4b2fd44..249e49f 100644 --- a/os/src/boards/k210.rs +++ b/os/src/boards/k210.rs @@ -21,3 +21,10 @@ pub const MMIO: &[(usize, usize)] = &[ pub type BlockDeviceImpl = crate::drivers::block::SDCardWrapper; +pub fn device_init() { + unimplemented!(); +} + +pub fn irq_handler() { + unimplemented!(); +} diff --git a/os/src/boards/qemu.rs b/os/src/boards/qemu.rs index b349252..bf5841b 100644 --- a/os/src/boards/qemu.rs +++ b/os/src/boards/qemu.rs @@ -1,6 +1,43 @@ pub const CLOCK_FREQ: usize = 12500000; -pub const MMIO: &[(usize, usize)] = &[(0x10001000, 0x1000)]; +pub const MMIO: &[(usize, usize)] = &[ + (0x10001000, 0x1000), + (0xC00_0000, 0x40_0000), +]; pub type BlockDeviceImpl = crate::drivers::block::VirtIOBlock; +pub const VIRT_PLIC: usize = 0xC00_0000; + +use crate::drivers::plic::{PLIC, IntrTargetPriority}; + +pub fn device_init() { + use riscv::register::sie; + let mut plic = unsafe { PLIC::new(VIRT_PLIC) }; + let hart_id: usize = 0; + let supervisor = IntrTargetPriority::Supervisor; + let machine = IntrTargetPriority::Machine; + plic.set_threshold(hart_id, supervisor, 0); + plic.set_threshold(hart_id, machine, 1); + for intr_src_id in [1usize, 10] { + plic.enable(hart_id, supervisor, intr_src_id); + plic.set_priority(intr_src_id, 1); + } + crate::println!("Hart0M threshold = {}", plic.get_threshold(hart_id, IntrTargetPriority::Machine)); + crate::println!("Hart0S threshold = {}", plic.get_threshold(hart_id, IntrTargetPriority::Supervisor)); + crate::println!("1 prio = {}", plic.get_priority(1)); + crate::println!("10 prio = {}", plic.get_priority(10)); + unsafe { sie::set_sext(); } +} + +use crate::drivers::block::BLOCK_DEVICE; + +pub fn irq_handler() { + let mut plic = unsafe { PLIC::new(VIRT_PLIC) }; + let intr_src_id = plic.claim(0, IntrTargetPriority::Supervisor); + match intr_src_id { + 1 => BLOCK_DEVICE.handle_irq(), + _ => panic!("unsupported IRQ {}", intr_src_id), + } + plic.complete(0, IntrTargetPriority::Supervisor, intr_src_id); +} diff --git a/os/src/drivers/block/sdcard.rs b/os/src/drivers/block/sdcard.rs index a74accc..f7c640b 100644 --- a/os/src/drivers/block/sdcard.rs +++ b/os/src/drivers/block/sdcard.rs @@ -761,4 +761,5 @@ impl BlockDevice for SDCardWrapper { .write_sector(buf, block_id as u32) .unwrap(); } + fn handle_irq(&self) { unimplemented!(); } } diff --git a/os/src/drivers/block/virtio_blk.rs b/os/src/drivers/block/virtio_blk.rs index cca839d..170416c 100644 --- a/os/src/drivers/block/virtio_blk.rs +++ b/os/src/drivers/block/virtio_blk.rs @@ -3,15 +3,20 @@ use crate::mm::{ frame_alloc, frame_dealloc, kernel_token, FrameTracker, PageTable, PhysAddr, PhysPageNum, StepByOne, VirtAddr, }; -use crate::sync::UPSafeCell; -use alloc::vec::Vec; +use crate::sync::{UPSafeCell, Condvar}; use lazy_static::*; use virtio_drivers::{VirtIOBlk, VirtIOHeader}; +use crate::DEV_NON_BLOCKING_ACCESS; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; #[allow(unused)] const VIRTIO0: usize = 0x10001000; -pub struct VirtIOBlock(UPSafeCell>); +pub struct VirtIOBlock { + virtio_blk: UPSafeCell>, + condvars: BTreeMap, +} lazy_static! { static ref QUEUE_FRAMES: UPSafeCell> = unsafe { UPSafeCell::new(Vec::new()) }; @@ -19,26 +24,61 @@ lazy_static! { impl BlockDevice for VirtIOBlock { fn read_block(&self, block_id: usize, buf: &mut [u8]) { - self.0 - .exclusive_access() - .read_block(block_id, buf) - .expect("Error when reading VirtIOBlk"); + let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access(); + if nb { + let mut blk = self.virtio_blk.exclusive_access(); + let mut resp = 0xffu8; + let token = blk.read_block_nb(block_id, buf, &mut resp).unwrap(); + drop(blk); + self.condvars.get(&token).unwrap().wait(); + assert_eq!(resp, 0x0, "Error when reading VirtIOBlk"); + } else { + self.virtio_blk + .exclusive_access() + .read_block(block_id, buf) + .expect("Error when reading VirtIOBlk"); + } } fn write_block(&self, block_id: usize, buf: &[u8]) { - self.0 - .exclusive_access() - .write_block(block_id, buf) - .expect("Error when writing VirtIOBlk"); + let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access(); + if nb { + let mut blk = self.virtio_blk.exclusive_access(); + let mut resp = 0xffu8; + let token = blk.write_block_nb(block_id, buf, &mut resp).unwrap(); + drop(blk); + self.condvars.get(&token).unwrap().wait(); + assert_eq!(resp, 0x0, "Error when reading VirtIOBlk"); + } else { + self.virtio_blk + .exclusive_access() + .write_block(block_id, buf) + .expect("Error when writing VirtIOBlk"); + } + } + fn handle_irq(&self) { + let mut blk = self.virtio_blk.exclusive_access(); + while let Ok(token) = blk.pop_used() { + self.condvars.get(&token).unwrap().signal(); + } } } impl VirtIOBlock { - #[allow(unused)] pub fn new() -> Self { unsafe { - Self(UPSafeCell::new( + let virtio_blk = UPSafeCell::new( VirtIOBlk::new(&mut *(VIRTIO0 as *mut VirtIOHeader)).unwrap(), - )) + ); + let mut condvars = BTreeMap::new(); + let channels = virtio_blk.exclusive_access().virt_queue_size(); + for i in 0..channels { + let condvar = Condvar::new(); + condvars.insert(i, condvar); + } + Self { + virtio_blk, + condvars, + } } } } diff --git a/os/src/drivers/mod.rs b/os/src/drivers/mod.rs index c2dea36..7a2e4fb 100644 --- a/os/src/drivers/mod.rs +++ b/os/src/drivers/mod.rs @@ -1,3 +1,4 @@ pub mod block; +pub mod plic; pub use block::BLOCK_DEVICE; diff --git a/os/src/drivers/plic.rs b/os/src/drivers/plic.rs new file mode 100644 index 0000000..fd6aefc --- /dev/null +++ b/os/src/drivers/plic.rs @@ -0,0 +1,127 @@ +pub struct PLIC { + base_addr: usize, +} + +#[derive(Copy, Clone)] +pub enum IntrTargetPriority { + Machine = 0, + Supervisor = 1, +} + +impl IntrTargetPriority { + pub fn supported_number() -> usize { + 2 + } +} + +impl PLIC { + fn priority_ptr(&self, intr_source_id: usize) -> *mut u32 { + assert!(intr_source_id > 0 && intr_source_id <= 132); + (self.base_addr + intr_source_id * 4) as *mut u32 + } + fn hart_id_with_priority( + hart_id: usize, + target_priority: IntrTargetPriority) -> usize { + let priority_num = IntrTargetPriority::supported_number(); + hart_id * priority_num + target_priority as usize + } + fn enable_ptr( + &self, + hart_id: usize, + target_priority: IntrTargetPriority, + intr_source_id: usize, + ) -> (*mut u32, usize) { + let id = Self::hart_id_with_priority(hart_id, target_priority); + let (reg_id, reg_shift) = (intr_source_id / 32, intr_source_id % 32); + ((self.base_addr + 0x2000 + 0x80 * id + 0x4 * reg_id) as *mut u32, reg_shift) + } + fn threshold_ptr_of_hart_with_priority( + &self, + hart_id: usize, + target_priority: IntrTargetPriority, + ) -> *mut u32 { + let id = Self::hart_id_with_priority(hart_id, target_priority); + (self.base_addr + 0x20_0000 + 0x1000 * id) as *mut u32 + } + fn claim_comp_ptr_of_hart_with_priority( + &self, + hart_id: usize, + target_priority: IntrTargetPriority, + ) -> *mut u32 { + let id = Self::hart_id_with_priority(hart_id, target_priority); + (self.base_addr + 0x20_0004 + 0x1000 * id) as *mut u32 + } + pub unsafe fn new(base_addr: usize) -> Self { + Self { + base_addr, + } + } + pub fn set_priority(&mut self, intr_source_id: usize, priority: u32) { + assert!(priority < 8); + unsafe { + self.priority_ptr(intr_source_id).write_volatile(priority); + } + } + pub fn get_priority(&mut self, intr_source_id: usize) -> u32 { + unsafe { + self.priority_ptr(intr_source_id).read_volatile() & 7 + } + } + pub fn enable( + &mut self, + hart_id: usize, + target_priority: IntrTargetPriority, + intr_source_id: usize, + ) { + let (reg_ptr, shift) = self.enable_ptr(hart_id, target_priority, intr_source_id); + unsafe { + reg_ptr.write_volatile(reg_ptr.read_volatile() | 1 << shift); + } + } + pub fn disable( + &mut self, + hart_id: usize, + target_priority: IntrTargetPriority, + intr_source_id: usize, + ) { + let (reg_ptr, shift) = self.enable_ptr(hart_id, target_priority, intr_source_id); + unsafe { + reg_ptr.write_volatile(reg_ptr.read_volatile() & (!(1u32 << shift))); + } + } + pub fn set_threshold( + &mut self, + hart_id: usize, + target_priority: IntrTargetPriority, + threshold: u32, + ) { + assert!(threshold < 8); + let threshold_ptr = self.threshold_ptr_of_hart_with_priority(hart_id, target_priority); + unsafe { threshold_ptr.write_volatile(threshold); } + } + pub fn get_threshold( + &mut self, + hart_id: usize, + target_priority: IntrTargetPriority, + ) -> u32 { + let threshold_ptr = self.threshold_ptr_of_hart_with_priority(hart_id, target_priority); + unsafe { threshold_ptr.read_volatile() & 7 } + } + pub fn claim( + &mut self, + hart_id: usize, + target_priority: IntrTargetPriority, + ) -> u32 { + let claim_comp_ptr = self.claim_comp_ptr_of_hart_with_priority(hart_id, target_priority); + unsafe { claim_comp_ptr.read_volatile() } + } + pub fn complete( + &mut self, + hart_id: usize, + target_priority: IntrTargetPriority, + completion: u32, + ) { + let claim_comp_ptr = self.claim_comp_ptr_of_hart_with_priority(hart_id, target_priority); + unsafe { claim_comp_ptr.write_volatile(completion); } + } +} diff --git a/os/src/main.rs b/os/src/main.rs index cbc4ab4..a506d1a 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -29,9 +29,7 @@ mod task; mod timer; mod trap; -use core::arch::global_asm; - -global_asm!(include_str!("entry.asm")); +core::arch::global_asm!(include_str!("entry.asm")); fn clear_bss() { extern "C" { @@ -44,6 +42,15 @@ fn clear_bss() { } } +use lazy_static::*; +use sync::UPSafeCell; + +lazy_static! { + pub static ref DEV_NON_BLOCKING_ACCESS: UPSafeCell = unsafe { + UPSafeCell::new(false) + }; +} + #[no_mangle] pub fn rust_main() -> ! { clear_bss(); @@ -53,8 +60,10 @@ pub fn rust_main() -> ! { trap::init(); trap::enable_timer_interrupt(); timer::set_next_trigger(); + board::device_init(); fs::list_apps(); task::add_initproc(); + *DEV_NON_BLOCKING_ACCESS.exclusive_access() = true; task::run_tasks(); panic!("Unreachable in rust_main!"); } diff --git a/os/src/sync/condvar.rs b/os/src/sync/condvar.rs index f96cd91..fc3f586 100644 --- a/os/src/sync/condvar.rs +++ b/os/src/sync/condvar.rs @@ -28,7 +28,14 @@ impl Condvar { } } - pub fn wait(&self, mutex: Arc) { + pub fn wait(&self) { + let mut inner =self.inner.exclusive_access(); + inner.wait_queue.push_back(current_task().unwrap()); + drop(inner); + block_current_and_run_next(); + } + + pub fn wait_with_mutex(&self, mutex: Arc) { mutex.unlock(); let mut inner = self.inner.exclusive_access(); inner.wait_queue.push_back(current_task().unwrap()); diff --git a/os/src/syscall/sync.rs b/os/src/syscall/sync.rs index dd0e856..e08f329 100644 --- a/os/src/syscall/sync.rs +++ b/os/src/syscall/sync.rs @@ -129,6 +129,6 @@ pub fn sys_condvar_wait(condvar_id: usize, mutex_id: usize) -> isize { let condvar = Arc::clone(process_inner.condvar_list[condvar_id].as_ref().unwrap()); let mutex = Arc::clone(process_inner.mutex_list[mutex_id].as_ref().unwrap()); drop(process_inner); - condvar.wait(mutex); + condvar.wait_with_mutex(mutex); 0 } diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index 6397811..287afd3 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -78,6 +78,9 @@ pub fn trap_handler() -> ! { check_timer(); suspend_current_and_run_next(); } + Trap::Interrupt(Interrupt::SupervisorExternal) => { + crate::board::irq_handler(); + } _ => { panic!( "Unsupported trap {:?}, stval = {:#x}!", From fef12c79f164614f763fabe1e39482bb4c05c72e Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Tue, 8 Feb 2022 10:53:21 -0800 Subject: [PATCH 32/56] Use latest virtio-drivers && add huge_write_mt but it cannot work now --- os/Cargo.toml | 3 +- os/src/drivers/block/virtio_blk.rs | 18 ++++++---- user/src/bin/huge_write.rs | 2 +- user/src/bin/huge_write_mt.rs | 54 ++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 user/src/bin/huge_write_mt.rs diff --git a/os/Cargo.toml b/os/Cargo.toml index 18d6884..5652cd2 100644 --- a/os/Cargo.toml +++ b/os/Cargo.toml @@ -12,8 +12,7 @@ lazy_static = { version = "1.4.0", features = ["spin_no_std"] } buddy_system_allocator = "0.6" bitflags = "1.2.1" xmas-elf = "0.7.0" -#virtio-drivers = { git = "https://github.com/wyfcyx/virtio-drivers" } -virtio-drivers = { path = "../../virtio-drivers" } +virtio-drivers = { git = "https://github.com/rcore-os/virtio-drivers" } k210-pac = { git = "https://github.com/wyfcyx/k210-pac" } k210-hal = { git = "https://github.com/wyfcyx/k210-hal" } k210-soc = { git = "https://github.com/wyfcyx/k210-soc" } diff --git a/os/src/drivers/block/virtio_blk.rs b/os/src/drivers/block/virtio_blk.rs index 170416c..19f64f6 100644 --- a/os/src/drivers/block/virtio_blk.rs +++ b/os/src/drivers/block/virtio_blk.rs @@ -5,7 +5,7 @@ use crate::mm::{ }; use crate::sync::{UPSafeCell, Condvar}; use lazy_static::*; -use virtio_drivers::{VirtIOBlk, VirtIOHeader}; +use virtio_drivers::{VirtIOBlk, VirtIOHeader, BlkResp, RespStatus}; use crate::DEV_NON_BLOCKING_ACCESS; use alloc::collections::BTreeMap; use alloc::vec::Vec; @@ -27,11 +27,13 @@ impl BlockDevice for VirtIOBlock { let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access(); if nb { let mut blk = self.virtio_blk.exclusive_access(); - let mut resp = 0xffu8; - let token = blk.read_block_nb(block_id, buf, &mut resp).unwrap(); + let mut resp = BlkResp::default(); + let token = unsafe { + blk.read_block_nb(block_id, buf, &mut resp).unwrap() + }; drop(blk); self.condvars.get(&token).unwrap().wait(); - assert_eq!(resp, 0x0, "Error when reading VirtIOBlk"); + assert_eq!(resp.status(), RespStatus::Ok, "Error when reading VirtIOBlk"); } else { self.virtio_blk .exclusive_access() @@ -43,11 +45,13 @@ impl BlockDevice for VirtIOBlock { let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access(); if nb { let mut blk = self.virtio_blk.exclusive_access(); - let mut resp = 0xffu8; - let token = blk.write_block_nb(block_id, buf, &mut resp).unwrap(); + let mut resp = BlkResp::default(); + let token = unsafe { + blk.write_block_nb(block_id, buf, &mut resp).unwrap() + }; drop(blk); self.condvars.get(&token).unwrap().wait(); - assert_eq!(resp, 0x0, "Error when reading VirtIOBlk"); + assert_eq!(resp.status(), RespStatus::Ok, "Error when writing VirtIOBlk"); } else { self.virtio_blk .exclusive_access() diff --git a/user/src/bin/huge_write.rs b/user/src/bin/huge_write.rs index e93f8b8..2a977c9 100644 --- a/user/src/bin/huge_write.rs +++ b/user/src/bin/huge_write.rs @@ -24,7 +24,7 @@ pub fn main() -> i32 { } close(f); let time_ms = (get_time() - start) as usize; - let speed_kbs = size_mb * 1000000 / time_ms; + let speed_kbs = (size_mb << 20) / time_ms; println!( "{}MiB written, time cost = {}ms, write speed = {}KiB/s", size_mb, time_ms, speed_kbs diff --git a/user/src/bin/huge_write_mt.rs b/user/src/bin/huge_write_mt.rs new file mode 100644 index 0000000..04717c8 --- /dev/null +++ b/user/src/bin/huge_write_mt.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; +extern crate alloc; + +use alloc::vec::Vec; +use user_lib::{exit, thread_create, waittid}; +use user_lib::{close, get_time, open, write, OpenFlags}; + +fn worker(size_kib: usize) { + let mut buffer = [0u8; 1024]; // 1KiB + for (i, ch) in buffer.iter_mut().enumerate() { + *ch = i as u8; + } + for _ in 0..size_kib { + write(3, &buffer); + } + exit(0) +} + +#[no_mangle] +pub fn main(argc: usize, argv: &[&str]) -> i32 { + let f = open("testf\0", OpenFlags::CREATE | OpenFlags::WRONLY); + if f < 0 { + panic!("Open test file failed!"); + } + let f = f as usize; + assert_eq!(f, 3); + assert_eq!(argc, 2, "wrong argument"); + let workers = argv[1].parse::().expect("wrong argument"); + assert!(workers >= 1 && 1024 % workers == 0, "wrong argument"); + + let start = get_time(); + + let mut v = Vec::new(); + let size_mb = 1usize; + for i in 0..workers { + v.push(thread_create(worker as usize, size_mb * 1024 / workers)); + } + for tid in v.iter() { + assert_eq!(0, waittid(*tid as usize)); + } + + close(f); + let time_ms = (get_time() - start) as usize; + let speed_kbs = (size_mb << 20) / time_ms; + println!( + "{}MiB written, time cost = {}ms, write speed = {}KiB/s", + size_mb, time_ms, speed_kbs + ); + 0 +} From 704eae3bb051cf271ec77447814b0e04b509d934 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Fri, 18 Feb 2022 13:09:03 -0800 Subject: [PATCH 33/56] Add ns16550a --- os/Cargo.toml | 1 + os/src/boards/qemu.rs | 30 +++-- os/src/config.rs | 1 - os/src/console.rs | 4 +- os/src/drivers/block/mod.rs | 4 +- os/src/drivers/block/sdcard.rs | 4 +- os/src/drivers/block/virtio_blk.rs | 54 ++++----- os/src/drivers/chardev/mod.rs | 17 +++ os/src/drivers/chardev/ns16550a.rs | 174 +++++++++++++++++++++++++++++ os/src/drivers/mod.rs | 2 + os/src/drivers/plic.rs | 52 ++++----- os/src/fs/stdio.rs | 17 +-- os/src/main.rs | 6 +- os/src/mm/memory_set.rs | 36 +++--- os/src/sync/condvar.rs | 2 +- user/src/bin/huge_write_mt.rs | 36 +++--- 16 files changed, 311 insertions(+), 129 deletions(-) create mode 100644 os/src/drivers/chardev/mod.rs create mode 100644 os/src/drivers/chardev/ns16550a.rs diff --git a/os/Cargo.toml b/os/Cargo.toml index 5652cd2..b832716 100644 --- a/os/Cargo.toml +++ b/os/Cargo.toml @@ -12,6 +12,7 @@ lazy_static = { version = "1.4.0", features = ["spin_no_std"] } buddy_system_allocator = "0.6" bitflags = "1.2.1" xmas-elf = "0.7.0" +volatile = "0.3" virtio-drivers = { git = "https://github.com/rcore-os/virtio-drivers" } k210-pac = { git = "https://github.com/wyfcyx/k210-pac" } k210-hal = { git = "https://github.com/wyfcyx/k210-hal" } diff --git a/os/src/boards/qemu.rs b/os/src/boards/qemu.rs index bf5841b..8d509a4 100644 --- a/os/src/boards/qemu.rs +++ b/os/src/boards/qemu.rs @@ -1,42 +1,54 @@ pub const CLOCK_FREQ: usize = 12500000; pub const MMIO: &[(usize, usize)] = &[ - (0x10001000, 0x1000), + (0x1000_0000, 0x1000), + (0x1000_1000, 0x1000), (0xC00_0000, 0x40_0000), ]; pub type BlockDeviceImpl = crate::drivers::block::VirtIOBlock; +pub type CharDeviceImpl = crate::drivers::chardev::NS16550a; pub const VIRT_PLIC: usize = 0xC00_0000; +pub const VIRT_UART: usize = 0x1000_0000; -use crate::drivers::plic::{PLIC, IntrTargetPriority}; +use crate::drivers::block::BLOCK_DEVICE; +use crate::drivers::chardev::{CharDevice, UART}; +use crate::drivers::plic::{IntrTargetPriority, PLIC}; pub fn device_init() { use riscv::register::sie; let mut plic = unsafe { PLIC::new(VIRT_PLIC) }; let hart_id: usize = 0; let supervisor = IntrTargetPriority::Supervisor; - let machine = IntrTargetPriority::Machine; + let machine = IntrTargetPriority::Machine; plic.set_threshold(hart_id, supervisor, 0); plic.set_threshold(hart_id, machine, 1); for intr_src_id in [1usize, 10] { - plic.enable(hart_id, supervisor, intr_src_id); + plic.enable(hart_id, supervisor, intr_src_id); plic.set_priority(intr_src_id, 1); } - crate::println!("Hart0M threshold = {}", plic.get_threshold(hart_id, IntrTargetPriority::Machine)); - crate::println!("Hart0S threshold = {}", plic.get_threshold(hart_id, IntrTargetPriority::Supervisor)); + crate::println!( + "Hart0M threshold = {}", + plic.get_threshold(hart_id, IntrTargetPriority::Machine) + ); + crate::println!( + "Hart0S threshold = {}", + plic.get_threshold(hart_id, IntrTargetPriority::Supervisor) + ); crate::println!("1 prio = {}", plic.get_priority(1)); crate::println!("10 prio = {}", plic.get_priority(10)); - unsafe { sie::set_sext(); } + unsafe { + sie::set_sext(); + } } -use crate::drivers::block::BLOCK_DEVICE; - pub fn irq_handler() { let mut plic = unsafe { PLIC::new(VIRT_PLIC) }; let intr_src_id = plic.claim(0, IntrTargetPriority::Supervisor); match intr_src_id { 1 => BLOCK_DEVICE.handle_irq(), + 10 => UART.handle_irq(), _ => panic!("unsupported IRQ {}", intr_src_id), } plic.complete(0, IntrTargetPriority::Supervisor, intr_src_id); diff --git a/os/src/config.rs b/os/src/config.rs index c1b2fa4..41fe977 100644 --- a/os/src/config.rs +++ b/os/src/config.rs @@ -11,4 +11,3 @@ pub const TRAMPOLINE: usize = usize::MAX - PAGE_SIZE + 1; pub const TRAP_CONTEXT_BASE: usize = TRAMPOLINE - PAGE_SIZE; pub use crate::board::{CLOCK_FREQ, MMIO}; - diff --git a/os/src/console.rs b/os/src/console.rs index c8a5cd4..31614c6 100644 --- a/os/src/console.rs +++ b/os/src/console.rs @@ -1,4 +1,4 @@ -use crate::sbi::console_putchar; +use crate::drivers::chardev::{CharDevice, UART}; use core::fmt::{self, Write}; struct Stdout; @@ -6,7 +6,7 @@ struct Stdout; impl Write for Stdout { fn write_str(&mut self, s: &str) -> fmt::Result { for c in s.chars() { - console_putchar(c as usize); + UART.write(c as u8); } Ok(()) } diff --git a/os/src/drivers/block/mod.rs b/os/src/drivers/block/mod.rs index 7c1bb55..7361ec8 100644 --- a/os/src/drivers/block/mod.rs +++ b/os/src/drivers/block/mod.rs @@ -1,13 +1,13 @@ mod sdcard; mod virtio_blk; -pub use virtio_blk::VirtIOBlock; pub use sdcard::SDCardWrapper; +pub use virtio_blk::VirtIOBlock; +use crate::board::BlockDeviceImpl; use alloc::sync::Arc; use easy_fs::BlockDevice; use lazy_static::*; -use crate::board::BlockDeviceImpl; lazy_static! { pub static ref BLOCK_DEVICE: Arc = Arc::new(BlockDeviceImpl::new()); diff --git a/os/src/drivers/block/sdcard.rs b/os/src/drivers/block/sdcard.rs index f7c640b..32d9443 100644 --- a/os/src/drivers/block/sdcard.rs +++ b/os/src/drivers/block/sdcard.rs @@ -761,5 +761,7 @@ impl BlockDevice for SDCardWrapper { .write_sector(buf, block_id as u32) .unwrap(); } - fn handle_irq(&self) { unimplemented!(); } + fn handle_irq(&self) { + unimplemented!(); + } } diff --git a/os/src/drivers/block/virtio_blk.rs b/os/src/drivers/block/virtio_blk.rs index 19f64f6..67eb0c3 100644 --- a/os/src/drivers/block/virtio_blk.rs +++ b/os/src/drivers/block/virtio_blk.rs @@ -3,12 +3,12 @@ use crate::mm::{ frame_alloc, frame_dealloc, kernel_token, FrameTracker, PageTable, PhysAddr, PhysPageNum, StepByOne, VirtAddr, }; -use crate::sync::{UPSafeCell, Condvar}; -use lazy_static::*; -use virtio_drivers::{VirtIOBlk, VirtIOHeader, BlkResp, RespStatus}; +use crate::sync::{Condvar, UPSafeCell}; use crate::DEV_NON_BLOCKING_ACCESS; use alloc::collections::BTreeMap; use alloc::vec::Vec; +use lazy_static::*; +use virtio_drivers::{BlkResp, RespStatus, VirtIOBlk, VirtIOHeader}; #[allow(unused)] const VIRTIO0: usize = 0x10001000; @@ -28,12 +28,14 @@ impl BlockDevice for VirtIOBlock { if nb { let mut blk = self.virtio_blk.exclusive_access(); let mut resp = BlkResp::default(); - let token = unsafe { - blk.read_block_nb(block_id, buf, &mut resp).unwrap() - }; + let token = unsafe { blk.read_block_nb(block_id, buf, &mut resp).unwrap() }; drop(blk); self.condvars.get(&token).unwrap().wait(); - assert_eq!(resp.status(), RespStatus::Ok, "Error when reading VirtIOBlk"); + assert_eq!( + resp.status(), + RespStatus::Ok, + "Error when reading VirtIOBlk" + ); } else { self.virtio_blk .exclusive_access() @@ -46,12 +48,14 @@ impl BlockDevice for VirtIOBlock { if nb { let mut blk = self.virtio_blk.exclusive_access(); let mut resp = BlkResp::default(); - let token = unsafe { - blk.write_block_nb(block_id, buf, &mut resp).unwrap() - }; + let token = unsafe { blk.write_block_nb(block_id, buf, &mut resp).unwrap() }; drop(blk); self.condvars.get(&token).unwrap().wait(); - assert_eq!(resp.status(), RespStatus::Ok, "Error when writing VirtIOBlk"); + assert_eq!( + resp.status(), + RespStatus::Ok, + "Error when writing VirtIOBlk" + ); } else { self.virtio_blk .exclusive_access() @@ -60,7 +64,7 @@ impl BlockDevice for VirtIOBlock { } } fn handle_irq(&self) { - let mut blk = self.virtio_blk.exclusive_access(); + let mut blk = self.virtio_blk.exclusive_access(); while let Ok(token) = blk.pop_used() { self.condvars.get(&token).unwrap().signal(); } @@ -69,20 +73,18 @@ impl BlockDevice for VirtIOBlock { impl VirtIOBlock { pub fn new() -> Self { - unsafe { - let virtio_blk = UPSafeCell::new( - VirtIOBlk::new(&mut *(VIRTIO0 as *mut VirtIOHeader)).unwrap(), - ); - let mut condvars = BTreeMap::new(); - let channels = virtio_blk.exclusive_access().virt_queue_size(); - for i in 0..channels { - let condvar = Condvar::new(); - condvars.insert(i, condvar); - } - Self { - virtio_blk, - condvars, - } + let virtio_blk = unsafe { + UPSafeCell::new(VirtIOBlk::new(&mut *(VIRTIO0 as *mut VirtIOHeader)).unwrap()) + }; + let mut condvars = BTreeMap::new(); + let channels = virtio_blk.exclusive_access().virt_queue_size(); + for i in 0..channels { + let condvar = Condvar::new(); + condvars.insert(i, condvar); + } + Self { + virtio_blk, + condvars, } } } diff --git a/os/src/drivers/chardev/mod.rs b/os/src/drivers/chardev/mod.rs new file mode 100644 index 0000000..2866576 --- /dev/null +++ b/os/src/drivers/chardev/mod.rs @@ -0,0 +1,17 @@ +mod ns16550a; + +pub use ns16550a::NS16550a; + +use crate::board::CharDeviceImpl; +use alloc::sync::Arc; +use lazy_static::*; + +pub trait CharDevice { + fn read(&self) -> u8; + fn write(&self, ch: u8); + fn handle_irq(&self); +} + +lazy_static! { + pub static ref UART: Arc = Arc::new(CharDeviceImpl::new()); +} diff --git a/os/src/drivers/chardev/ns16550a.rs b/os/src/drivers/chardev/ns16550a.rs new file mode 100644 index 0000000..df3fd80 --- /dev/null +++ b/os/src/drivers/chardev/ns16550a.rs @@ -0,0 +1,174 @@ +use super::CharDevice; +use crate::sync::{Condvar, UPSafeCell}; +use alloc::collections::VecDeque; +use bitflags::*; +use volatile::{ReadOnly, Volatile, WriteOnly}; + +///! Ref: https://www.lammertbies.nl/comm/info/serial-uart +///! Ref: ns16550a datasheet: https://datasheetspdf.com/pdf-file/605590/NationalSemiconductor/NS16550A/1 +///! Ref: ns16450 datasheet: https://datasheetspdf.com/pdf-file/1311818/NationalSemiconductor/NS16450/1 + +bitflags! { + /// InterruptEnableRegister + pub struct IER: u8 { + const RX_AVALIABLE = 1 << 0; + const TX_EMPTY = 1 << 1; + } + + /// LineStatusRegister + pub struct LSR: u8 { + const DATA_AVAILABLE = 1 << 0; + const THR_EMPTY = 1 << 5; + } + + /// Model Control Register + pub struct MCR: u8 { + const DATA_TERMINAL_READY = 1 << 0; + const REQUEST_TO_SEND = 1 << 1; + const AUX_OUTPUT1 = 1 << 2; + const AUX_OUTPUT2 = 1 << 3; + } +} + +#[repr(C)] +#[allow(dead_code)] +struct ReadWithoutDLAB { + /// receiver buffer register + pub rbr: ReadOnly, + /// interrupt enable register + pub ier: Volatile, + /// interrupt identification register + pub iir: ReadOnly, + /// line control register + pub lcr: Volatile, + /// model control register + pub mcr: Volatile, + /// line status register + pub lsr: ReadOnly, + /// ignore MSR + _padding1: ReadOnly, + /// ignore SCR + _padding2: ReadOnly, +} + +#[repr(C)] +#[allow(dead_code)] +struct WriteWithoutDLAB { + /// transmitter holding register + pub thr: WriteOnly, + /// interrupt enable register + pub ier: Volatile, + /// ignore FCR + _padding0: ReadOnly, + /// line control register + pub lcr: Volatile, + /// modem control register + pub mcr: Volatile, + /// line status register + pub lsr: ReadOnly, + /// ignore other registers + _padding1: ReadOnly, +} + +pub struct NS16550aRaw { + base_addr: usize, +} + +impl NS16550aRaw { + fn read_end(&mut self) -> &mut ReadWithoutDLAB { + unsafe { &mut *(self.base_addr as *mut ReadWithoutDLAB) } + } + + fn write_end(&mut self) -> &mut WriteWithoutDLAB { + unsafe { &mut *(self.base_addr as *mut WriteWithoutDLAB) } + } + + pub fn new(base_addr: usize) -> Self { + Self { base_addr } + } + + pub fn init(&mut self) { + let read_end = self.read_end(); + let mut mcr = MCR::empty(); + mcr |= MCR::DATA_TERMINAL_READY; + mcr |= MCR::REQUEST_TO_SEND; + mcr |= MCR::AUX_OUTPUT2; + read_end.mcr.write(mcr); + let ier = IER::RX_AVALIABLE; + read_end.ier.write(ier); + } + + pub fn read(&mut self) -> Option { + let read_end = self.read_end(); + let lsr = read_end.lsr.read(); + if lsr.contains(LSR::DATA_AVAILABLE) { + Some(read_end.rbr.read()) + } else { + None + } + } + + pub fn write(&mut self, ch: u8) { + let write_end = self.write_end(); + loop { + if write_end.lsr.read().contains(LSR::THR_EMPTY) { + write_end.thr.write(ch); + break; + } + } + } +} + +struct NS16550aInner { + ns16550a: NS16550aRaw, + read_buffer: VecDeque, +} + +pub struct NS16550a { + inner: UPSafeCell, + condvar: Condvar, +} + +impl NS16550a { + pub fn new() -> Self { + let mut inner = NS16550aInner { + ns16550a: NS16550aRaw::new(BASE_ADDR), + read_buffer: VecDeque::new(), + }; + inner.ns16550a.init(); + Self { + inner: unsafe { UPSafeCell::new(inner) }, + condvar: Condvar::new(), + } + } +} + +impl CharDevice for NS16550a { + fn read(&self) -> u8 { + loop { + let mut inner = self.inner.exclusive_access(); + if let Some(ch) = inner.read_buffer.pop_front() { + return ch; + } else { + drop(inner); + self.condvar.wait(); + } + } + } + fn write(&self, ch: u8) { + let mut inner = self.inner.exclusive_access(); + inner.ns16550a.write(ch); + } + fn handle_irq(&self) { + let mut inner = self.inner.exclusive_access(); + let mut count = 0; + while let Some(ch) = inner.ns16550a.read() { + count += 1; + inner.read_buffer.push_back(ch); + } + drop(inner); + if count > 0 { + self.condvar.signal(); + } + } +} diff --git a/os/src/drivers/mod.rs b/os/src/drivers/mod.rs index 7a2e4fb..c1b1231 100644 --- a/os/src/drivers/mod.rs +++ b/os/src/drivers/mod.rs @@ -1,4 +1,6 @@ pub mod block; +pub mod chardev; pub mod plic; pub use block::BLOCK_DEVICE; +pub use chardev::UART; diff --git a/os/src/drivers/plic.rs b/os/src/drivers/plic.rs index fd6aefc..fa71dbf 100644 --- a/os/src/drivers/plic.rs +++ b/os/src/drivers/plic.rs @@ -1,5 +1,5 @@ pub struct PLIC { - base_addr: usize, + base_addr: usize, } #[derive(Copy, Clone)] @@ -10,7 +10,7 @@ pub enum IntrTargetPriority { impl IntrTargetPriority { pub fn supported_number() -> usize { - 2 + 2 } } @@ -19,9 +19,7 @@ impl PLIC { assert!(intr_source_id > 0 && intr_source_id <= 132); (self.base_addr + intr_source_id * 4) as *mut u32 } - fn hart_id_with_priority( - hart_id: usize, - target_priority: IntrTargetPriority) -> usize { + fn hart_id_with_priority(hart_id: usize, target_priority: IntrTargetPriority) -> usize { let priority_num = IntrTargetPriority::supported_number(); hart_id * priority_num + target_priority as usize } @@ -31,13 +29,16 @@ impl PLIC { target_priority: IntrTargetPriority, intr_source_id: usize, ) -> (*mut u32, usize) { - let id = Self::hart_id_with_priority(hart_id, target_priority); + let id = Self::hart_id_with_priority(hart_id, target_priority); let (reg_id, reg_shift) = (intr_source_id / 32, intr_source_id % 32); - ((self.base_addr + 0x2000 + 0x80 * id + 0x4 * reg_id) as *mut u32, reg_shift) + ( + (self.base_addr + 0x2000 + 0x80 * id + 0x4 * reg_id) as *mut u32, + reg_shift, + ) } fn threshold_ptr_of_hart_with_priority( &self, - hart_id: usize, + hart_id: usize, target_priority: IntrTargetPriority, ) -> *mut u32 { let id = Self::hart_id_with_priority(hart_id, target_priority); @@ -45,27 +46,23 @@ impl PLIC { } fn claim_comp_ptr_of_hart_with_priority( &self, - hart_id: usize, + hart_id: usize, target_priority: IntrTargetPriority, ) -> *mut u32 { let id = Self::hart_id_with_priority(hart_id, target_priority); (self.base_addr + 0x20_0004 + 0x1000 * id) as *mut u32 } pub unsafe fn new(base_addr: usize) -> Self { - Self { - base_addr, - } + Self { base_addr } } pub fn set_priority(&mut self, intr_source_id: usize, priority: u32) { - assert!(priority < 8); + assert!(priority < 8); unsafe { self.priority_ptr(intr_source_id).write_volatile(priority); } } pub fn get_priority(&mut self, intr_source_id: usize) -> u32 { - unsafe { - self.priority_ptr(intr_source_id).read_volatile() & 7 - } + unsafe { self.priority_ptr(intr_source_id).read_volatile() & 7 } } pub fn enable( &mut self, @@ -78,6 +75,7 @@ impl PLIC { reg_ptr.write_volatile(reg_ptr.read_volatile() | 1 << shift); } } + #[allow(unused)] pub fn disable( &mut self, hart_id: usize, @@ -86,7 +84,7 @@ impl PLIC { ) { let (reg_ptr, shift) = self.enable_ptr(hart_id, target_priority, intr_source_id); unsafe { - reg_ptr.write_volatile(reg_ptr.read_volatile() & (!(1u32 << shift))); + reg_ptr.write_volatile(reg_ptr.read_volatile() & (!(1u32 << shift))); } } pub fn set_threshold( @@ -97,21 +95,15 @@ impl PLIC { ) { assert!(threshold < 8); let threshold_ptr = self.threshold_ptr_of_hart_with_priority(hart_id, target_priority); - unsafe { threshold_ptr.write_volatile(threshold); } + unsafe { + threshold_ptr.write_volatile(threshold); + } } - pub fn get_threshold( - &mut self, - hart_id: usize, - target_priority: IntrTargetPriority, - ) -> u32 { + pub fn get_threshold(&mut self, hart_id: usize, target_priority: IntrTargetPriority) -> u32 { let threshold_ptr = self.threshold_ptr_of_hart_with_priority(hart_id, target_priority); unsafe { threshold_ptr.read_volatile() & 7 } } - pub fn claim( - &mut self, - hart_id: usize, - target_priority: IntrTargetPriority, - ) -> u32 { + pub fn claim(&mut self, hart_id: usize, target_priority: IntrTargetPriority) -> u32 { let claim_comp_ptr = self.claim_comp_ptr_of_hart_with_priority(hart_id, target_priority); unsafe { claim_comp_ptr.read_volatile() } } @@ -122,6 +114,8 @@ impl PLIC { completion: u32, ) { let claim_comp_ptr = self.claim_comp_ptr_of_hart_with_priority(hart_id, target_priority); - unsafe { claim_comp_ptr.write_volatile(completion); } + unsafe { + claim_comp_ptr.write_volatile(completion); + } } } diff --git a/os/src/fs/stdio.rs b/os/src/fs/stdio.rs index 7c74d3e..7a3aded 100644 --- a/os/src/fs/stdio.rs +++ b/os/src/fs/stdio.rs @@ -1,10 +1,8 @@ use super::File; +use crate::drivers::chardev::{CharDevice, UART}; use crate::mm::UserBuffer; -use crate::sbi::console_getchar; -use crate::task::suspend_current_and_run_next; pub struct Stdin; - pub struct Stdout; impl File for Stdin { @@ -16,18 +14,7 @@ impl File for Stdin { } fn read(&self, mut user_buf: UserBuffer) -> usize { assert_eq!(user_buf.len(), 1); - // busy loop - let mut c: usize; - loop { - c = console_getchar(); - if c == 0 { - suspend_current_and_run_next(); - continue; - } else { - break; - } - } - let ch = c as u8; + let ch = UART.read(); unsafe { user_buf.buffers[0].as_mut_ptr().write_volatile(ch); } diff --git a/os/src/main.rs b/os/src/main.rs index a506d1a..77ebbbb 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -46,17 +46,13 @@ use lazy_static::*; use sync::UPSafeCell; lazy_static! { - pub static ref DEV_NON_BLOCKING_ACCESS: UPSafeCell = unsafe { - UPSafeCell::new(false) - }; + pub static ref DEV_NON_BLOCKING_ACCESS: UPSafeCell = unsafe { UPSafeCell::new(false) }; } #[no_mangle] pub fn rust_main() -> ! { clear_bss(); - println!("[kernel] Hello, world!"); mm::init(); - mm::remap_test(); trap::init(); trap::enable_timer_interrupt(); timer::set_next_trigger(); diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index 0d0b0f1..aba1ae5 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -351,26 +351,20 @@ pub fn remap_test() { let mid_text: VirtAddr = ((stext as usize + etext as usize) / 2).into(); let mid_rodata: VirtAddr = ((srodata as usize + erodata as usize) / 2).into(); let mid_data: VirtAddr = ((sdata as usize + edata as usize) / 2).into(); - assert!( - !kernel_space - .page_table - .translate(mid_text.floor()) - .unwrap() - .writable(), - ); - assert!( - !kernel_space - .page_table - .translate(mid_rodata.floor()) - .unwrap() - .writable(), - ); - assert!( - !kernel_space - .page_table - .translate(mid_data.floor()) - .unwrap() - .executable(), - ); + assert!(!kernel_space + .page_table + .translate(mid_text.floor()) + .unwrap() + .writable(),); + assert!(!kernel_space + .page_table + .translate(mid_rodata.floor()) + .unwrap() + .writable(),); + assert!(!kernel_space + .page_table + .translate(mid_data.floor()) + .unwrap() + .executable(),); println!("remap_test passed!"); } diff --git a/os/src/sync/condvar.rs b/os/src/sync/condvar.rs index fc3f586..d7943e1 100644 --- a/os/src/sync/condvar.rs +++ b/os/src/sync/condvar.rs @@ -29,7 +29,7 @@ impl Condvar { } pub fn wait(&self) { - let mut inner =self.inner.exclusive_access(); + let mut inner = self.inner.exclusive_access(); inner.wait_queue.push_back(current_task().unwrap()); drop(inner); block_current_and_run_next(); diff --git a/user/src/bin/huge_write_mt.rs b/user/src/bin/huge_write_mt.rs index 04717c8..63146a5 100644 --- a/user/src/bin/huge_write_mt.rs +++ b/user/src/bin/huge_write_mt.rs @@ -5,50 +5,52 @@ extern crate user_lib; extern crate alloc; -use alloc::vec::Vec; +use alloc::{vec::Vec, string::String, fmt::format}; use user_lib::{exit, thread_create, waittid}; -use user_lib::{close, get_time, open, write, OpenFlags}; +use user_lib::{close, get_time, open, write, OpenFlags, gettid}; fn worker(size_kib: usize) { let mut buffer = [0u8; 1024]; // 1KiB for (i, ch) in buffer.iter_mut().enumerate() { *ch = i as u8; } - for _ in 0..size_kib { - write(3, &buffer); + let filename = format(format_args!("testf{}\0", gettid())); + let f = open(filename.as_str(), OpenFlags::CREATE | OpenFlags::WRONLY); + if f < 0 { + panic!("Open test file failed!"); } + let f = f as usize; + for _ in 0..size_kib { + write(f, &buffer); + } + close(f); exit(0) } #[no_mangle] pub fn main(argc: usize, argv: &[&str]) -> i32 { - let f = open("testf\0", OpenFlags::CREATE | OpenFlags::WRONLY); - if f < 0 { - panic!("Open test file failed!"); - } - let f = f as usize; - assert_eq!(f, 3); assert_eq!(argc, 2, "wrong argument"); + let size_mb = 1usize; + let size_kb = size_mb << 10; let workers = argv[1].parse::().expect("wrong argument"); - assert!(workers >= 1 && 1024 % workers == 0, "wrong argument"); + assert!(workers >= 1 && size_kb % workers == 0, "wrong argument"); let start = get_time(); let mut v = Vec::new(); let size_mb = 1usize; - for i in 0..workers { - v.push(thread_create(worker as usize, size_mb * 1024 / workers)); + for _ in 0..workers { + v.push(thread_create(worker as usize, size_kb / workers)); } for tid in v.iter() { assert_eq!(0, waittid(*tid as usize)); } - close(f); let time_ms = (get_time() - start) as usize; - let speed_kbs = (size_mb << 20) / time_ms; + let speed_kbs = size_kb * 1000 / time_ms; println!( - "{}MiB written, time cost = {}ms, write speed = {}KiB/s", - size_mb, time_ms, speed_kbs + "{}MiB written by {} threads, time cost = {}ms, write speed = {}KiB/s", + size_mb, workers, time_ms, speed_kbs ); 0 } From 26f44233f6237a92d6016d12066f2b36516621df Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Fri, 4 Mar 2022 09:02:32 -0800 Subject: [PATCH 34/56] Still a lot of bugs :( --- os/Makefile | 2 +- os/src/boards/qemu.rs | 3 + os/src/console.rs | 4 +- os/src/drivers/block/sdcard.rs | 10 +-- os/src/drivers/block/virtio_blk.rs | 39 ++++++----- os/src/drivers/chardev/ns16550a.rs | 24 ++++--- os/src/drivers/plic.rs | 1 + os/src/fs/inode.rs | 6 +- os/src/fs/pipe.rs | 10 +-- os/src/fs/stdio.rs | 1 + os/src/main.rs | 4 +- os/src/mm/frame_allocator.rs | 6 +- os/src/mm/memory_set.rs | 6 +- os/src/sync/condvar.rs | 15 +++-- os/src/sync/mod.rs | 2 +- os/src/sync/mutex.rs | 10 +-- os/src/sync/semaphore.rs | 6 +- os/src/sync/up.rs | 105 ++++++++++++++++++++++++++++- os/src/task/id.rs | 10 +-- os/src/task/manager.rs | 10 +-- os/src/task/mod.rs | 10 ++- os/src/task/process.rs | 11 ++- os/src/task/processor.rs | 4 +- os/src/task/task.rs | 9 ++- os/src/timer.rs | 6 +- os/src/trap/mod.rs | 54 +++++++++++++-- os/src/trap/trap.S | 35 ++++++++++ 27 files changed, 308 insertions(+), 95 deletions(-) diff --git a/os/Makefile b/os/Makefile index 0c1fb7c..3e5caad 100644 --- a/os/Makefile +++ b/os/Makefile @@ -76,7 +76,7 @@ disasm: kernel disasm-vim: kernel @$(OBJDUMP) $(DISASM) $(KERNEL_ELF) > $(DISASM_TMP) - @vim $(DISASM_TMP) + @nvim $(DISASM_TMP) @rm $(DISASM_TMP) run: run-inner diff --git a/os/src/boards/qemu.rs b/os/src/boards/qemu.rs index 8d509a4..38de344 100644 --- a/os/src/boards/qemu.rs +++ b/os/src/boards/qemu.rs @@ -44,12 +44,15 @@ pub fn device_init() { } pub fn irq_handler() { + //crate::println!("->irq_handler"); let mut plic = unsafe { PLIC::new(VIRT_PLIC) }; let intr_src_id = plic.claim(0, IntrTargetPriority::Supervisor); + //crate::println!("intr_src={}", intr_src_id); match intr_src_id { 1 => BLOCK_DEVICE.handle_irq(), 10 => UART.handle_irq(), _ => panic!("unsupported IRQ {}", intr_src_id), } plic.complete(0, IntrTargetPriority::Supervisor, intr_src_id); + //crate::println!("irq_handler->"); } diff --git a/os/src/console.rs b/os/src/console.rs index 31614c6..9f27b4b 100644 --- a/os/src/console.rs +++ b/os/src/console.rs @@ -1,12 +1,14 @@ use crate::drivers::chardev::{CharDevice, UART}; use core::fmt::{self, Write}; +use crate::sbi::console_putchar; struct Stdout; impl Write for Stdout { fn write_str(&mut self, s: &str) -> fmt::Result { for c in s.chars() { - UART.write(c as u8); + //UART.write(c as u8); + console_putchar(c as usize); } Ok(()) } diff --git a/os/src/drivers/block/sdcard.rs b/os/src/drivers/block/sdcard.rs index 32d9443..9b12ffd 100644 --- a/os/src/drivers/block/sdcard.rs +++ b/os/src/drivers/block/sdcard.rs @@ -3,7 +3,7 @@ #![allow(unused)] use super::BlockDevice; -use crate::sync::UPSafeCell; +use crate::sync::UPIntrFreeCell; use core::convert::TryInto; use k210_hal::prelude::*; use k210_pac::{Peripherals, SPI0}; @@ -715,8 +715,8 @@ fn io_init() { } lazy_static! { - static ref PERIPHERALS: UPSafeCell = - unsafe { UPSafeCell::new(Peripherals::take().unwrap()) }; + static ref PERIPHERALS: UPIntrFreeCell = + unsafe { UPIntrFreeCell::new(Peripherals::take().unwrap()) }; } fn init_sdcard() -> SDCard> { @@ -740,11 +740,11 @@ fn init_sdcard() -> SDCard> { sd } -pub struct SDCardWrapper(UPSafeCell>>); +pub struct SDCardWrapper(UPIntrFreeCell>>); impl SDCardWrapper { pub fn new() -> Self { - unsafe { Self(UPSafeCell::new(init_sdcard())) } + unsafe { Self(UPIntrFreeCell::new(init_sdcard())) } } } diff --git a/os/src/drivers/block/virtio_blk.rs b/os/src/drivers/block/virtio_blk.rs index 67eb0c3..7993fb6 100644 --- a/os/src/drivers/block/virtio_blk.rs +++ b/os/src/drivers/block/virtio_blk.rs @@ -3,7 +3,8 @@ use crate::mm::{ frame_alloc, frame_dealloc, kernel_token, FrameTracker, PageTable, PhysAddr, PhysPageNum, StepByOne, VirtAddr, }; -use crate::sync::{Condvar, UPSafeCell}; +use crate::sync::{Condvar, UPIntrFreeCell}; +use crate::task::schedule; use crate::DEV_NON_BLOCKING_ACCESS; use alloc::collections::BTreeMap; use alloc::vec::Vec; @@ -14,23 +15,24 @@ use virtio_drivers::{BlkResp, RespStatus, VirtIOBlk, VirtIOHeader}; const VIRTIO0: usize = 0x10001000; pub struct VirtIOBlock { - virtio_blk: UPSafeCell>, + virtio_blk: UPIntrFreeCell>, condvars: BTreeMap, } lazy_static! { - static ref QUEUE_FRAMES: UPSafeCell> = unsafe { UPSafeCell::new(Vec::new()) }; + static ref QUEUE_FRAMES: UPIntrFreeCell> = unsafe { UPIntrFreeCell::new(Vec::new()) }; } impl BlockDevice for VirtIOBlock { fn read_block(&self, block_id: usize, buf: &mut [u8]) { let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access(); if nb { - let mut blk = self.virtio_blk.exclusive_access(); let mut resp = BlkResp::default(); - let token = unsafe { blk.read_block_nb(block_id, buf, &mut resp).unwrap() }; - drop(blk); - self.condvars.get(&token).unwrap().wait(); + let task_cx_ptr = self.virtio_blk.exclusive_session(|blk| { + let token = unsafe { blk.read_block_nb(block_id, buf, &mut resp).unwrap() }; + self.condvars.get(&token).unwrap().wait_no_sched() + }); + schedule(task_cx_ptr); assert_eq!( resp.status(), RespStatus::Ok, @@ -46,11 +48,12 @@ impl BlockDevice for VirtIOBlock { fn write_block(&self, block_id: usize, buf: &[u8]) { let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access(); if nb { - let mut blk = self.virtio_blk.exclusive_access(); let mut resp = BlkResp::default(); - let token = unsafe { blk.write_block_nb(block_id, buf, &mut resp).unwrap() }; - drop(blk); - self.condvars.get(&token).unwrap().wait(); + let task_cx_ptr = self.virtio_blk.exclusive_session(|blk| { + let token = unsafe { blk.write_block_nb(block_id, buf, &mut resp).unwrap() }; + self.condvars.get(&token).unwrap().wait_no_sched() + }); + schedule(task_cx_ptr); assert_eq!( resp.status(), RespStatus::Ok, @@ -64,17 +67,21 @@ impl BlockDevice for VirtIOBlock { } } fn handle_irq(&self) { - let mut blk = self.virtio_blk.exclusive_access(); - while let Ok(token) = blk.pop_used() { - self.condvars.get(&token).unwrap().signal(); - } + //println!("into handle_irq"); + self.virtio_blk.exclusive_session(|blk| { + //println!("not panic here"); + while let Ok(token) = blk.pop_used() { + //println!("wakeup virtio.token {}", token); + self.condvars.get(&token).unwrap().signal(); + } + }); } } impl VirtIOBlock { pub fn new() -> Self { let virtio_blk = unsafe { - UPSafeCell::new(VirtIOBlk::new(&mut *(VIRTIO0 as *mut VirtIOHeader)).unwrap()) + UPIntrFreeCell::new(VirtIOBlk::new(&mut *(VIRTIO0 as *mut VirtIOHeader)).unwrap()) }; let mut condvars = BTreeMap::new(); let channels = virtio_blk.exclusive_access().virt_queue_size(); diff --git a/os/src/drivers/chardev/ns16550a.rs b/os/src/drivers/chardev/ns16550a.rs index df3fd80..4613999 100644 --- a/os/src/drivers/chardev/ns16550a.rs +++ b/os/src/drivers/chardev/ns16550a.rs @@ -1,13 +1,14 @@ -use super::CharDevice; -use crate::sync::{Condvar, UPSafeCell}; -use alloc::collections::VecDeque; -use bitflags::*; -use volatile::{ReadOnly, Volatile, WriteOnly}; - ///! Ref: https://www.lammertbies.nl/comm/info/serial-uart ///! Ref: ns16550a datasheet: https://datasheetspdf.com/pdf-file/605590/NationalSemiconductor/NS16550A/1 ///! Ref: ns16450 datasheet: https://datasheetspdf.com/pdf-file/1311818/NationalSemiconductor/NS16450/1 +use super::CharDevice; +use crate::sync::{Condvar, UPIntrFreeCell}; +use crate::task::schedule; +use alloc::collections::VecDeque; +use bitflags::*; +use volatile::{ReadOnly, Volatile, WriteOnly}; + bitflags! { /// InterruptEnableRegister pub struct IER: u8 { @@ -125,7 +126,7 @@ struct NS16550aInner { } pub struct NS16550a { - inner: UPSafeCell, + inner: UPIntrFreeCell, condvar: Condvar, } @@ -137,7 +138,7 @@ impl NS16550a { }; inner.ns16550a.init(); Self { - inner: unsafe { UPSafeCell::new(inner) }, + inner: unsafe { UPIntrFreeCell::new(inner) }, condvar: Condvar::new(), } } @@ -145,13 +146,16 @@ impl NS16550a { impl CharDevice for NS16550a { fn read(&self) -> u8 { + println!("NS16550a::read"); loop { let mut inner = self.inner.exclusive_access(); if let Some(ch) = inner.read_buffer.pop_front() { return ch; } else { + println!("no ch yet!"); + let task_cx_ptr = self.condvar.wait_no_sched(); drop(inner); - self.condvar.wait(); + schedule(task_cx_ptr); } } } @@ -163,10 +167,12 @@ impl CharDevice for NS16550a { let mut inner = self.inner.exclusive_access(); let mut count = 0; while let Some(ch) = inner.ns16550a.read() { + println!("got {}", ch as char); count += 1; inner.read_buffer.push_back(ch); } drop(inner); + //assert_eq!(count, 1); if count > 0 { self.condvar.signal(); } diff --git a/os/src/drivers/plic.rs b/os/src/drivers/plic.rs index fa71dbf..b1f662f 100644 --- a/os/src/drivers/plic.rs +++ b/os/src/drivers/plic.rs @@ -1,3 +1,4 @@ +#[allow(clippy::upper_case_acronyms)] pub struct PLIC { base_addr: usize, } diff --git a/os/src/fs/inode.rs b/os/src/fs/inode.rs index 19e4953..321338d 100644 --- a/os/src/fs/inode.rs +++ b/os/src/fs/inode.rs @@ -1,7 +1,7 @@ use super::File; use crate::drivers::BLOCK_DEVICE; use crate::mm::UserBuffer; -use crate::sync::UPSafeCell; +use crate::sync::UPIntrFreeCell; use alloc::sync::Arc; use alloc::vec::Vec; use bitflags::*; @@ -11,7 +11,7 @@ use lazy_static::*; pub struct OSInode { readable: bool, writable: bool, - inner: UPSafeCell, + inner: UPIntrFreeCell, } pub struct OSInodeInner { @@ -24,7 +24,7 @@ impl OSInode { Self { readable, writable, - inner: unsafe { UPSafeCell::new(OSInodeInner { offset: 0, inode }) }, + inner: unsafe { UPIntrFreeCell::new(OSInodeInner { offset: 0, inode }) }, } } pub fn read_all(&self) -> Vec { diff --git a/os/src/fs/pipe.rs b/os/src/fs/pipe.rs index 75caac5..18c74d5 100644 --- a/os/src/fs/pipe.rs +++ b/os/src/fs/pipe.rs @@ -1,6 +1,6 @@ use super::File; use crate::mm::UserBuffer; -use crate::sync::UPSafeCell; +use crate::sync::UPIntrFreeCell; use alloc::sync::{Arc, Weak}; use crate::task::suspend_current_and_run_next; @@ -8,18 +8,18 @@ use crate::task::suspend_current_and_run_next; pub struct Pipe { readable: bool, writable: bool, - buffer: Arc>, + buffer: Arc>, } impl Pipe { - pub fn read_end_with_buffer(buffer: Arc>) -> Self { + pub fn read_end_with_buffer(buffer: Arc>) -> Self { Self { readable: true, writable: false, buffer, } } - pub fn write_end_with_buffer(buffer: Arc>) -> Self { + pub fn write_end_with_buffer(buffer: Arc>) -> Self { Self { readable: false, writable: true, @@ -98,7 +98,7 @@ impl PipeRingBuffer { /// Return (read_end, write_end) pub fn make_pipe() -> (Arc, Arc) { - let buffer = Arc::new(unsafe { UPSafeCell::new(PipeRingBuffer::new()) }); + let buffer = Arc::new(unsafe { UPIntrFreeCell::new(PipeRingBuffer::new()) }); let read_end = Arc::new(Pipe::read_end_with_buffer(buffer.clone())); let write_end = Arc::new(Pipe::write_end_with_buffer(buffer.clone())); buffer.exclusive_access().set_write_end(&write_end); diff --git a/os/src/fs/stdio.rs b/os/src/fs/stdio.rs index 7a3aded..99d07c2 100644 --- a/os/src/fs/stdio.rs +++ b/os/src/fs/stdio.rs @@ -14,6 +14,7 @@ impl File for Stdin { } fn read(&self, mut user_buf: UserBuffer) -> usize { assert_eq!(user_buf.len(), 1); + println!("before UART.read() in Stdin::read()"); let ch = UART.read(); unsafe { user_buf.buffers[0].as_mut_ptr().write_volatile(ch); diff --git a/os/src/main.rs b/os/src/main.rs index 77ebbbb..a9323be 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -43,10 +43,10 @@ fn clear_bss() { } use lazy_static::*; -use sync::UPSafeCell; +use sync::UPIntrFreeCell; lazy_static! { - pub static ref DEV_NON_BLOCKING_ACCESS: UPSafeCell = unsafe { UPSafeCell::new(false) }; + pub static ref DEV_NON_BLOCKING_ACCESS: UPIntrFreeCell = unsafe { UPIntrFreeCell::new(false) }; } #[no_mangle] diff --git a/os/src/mm/frame_allocator.rs b/os/src/mm/frame_allocator.rs index 4bc7db2..3c62324 100644 --- a/os/src/mm/frame_allocator.rs +++ b/os/src/mm/frame_allocator.rs @@ -1,6 +1,6 @@ use super::{PhysAddr, PhysPageNum}; use crate::config::MEMORY_END; -use crate::sync::UPSafeCell; +use crate::sync::UPIntrFreeCell; use alloc::vec::Vec; use core::fmt::{self, Debug, Formatter}; use lazy_static::*; @@ -83,8 +83,8 @@ impl FrameAllocator for StackFrameAllocator { type FrameAllocatorImpl = StackFrameAllocator; lazy_static! { - pub static ref FRAME_ALLOCATOR: UPSafeCell = - unsafe { UPSafeCell::new(FrameAllocatorImpl::new()) }; + pub static ref FRAME_ALLOCATOR: UPIntrFreeCell = + unsafe { UPIntrFreeCell::new(FrameAllocatorImpl::new()) }; } pub fn init_frame_allocator() { diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index aba1ae5..0862dc7 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -3,7 +3,7 @@ use super::{PTEFlags, PageTable, PageTableEntry}; use super::{PhysAddr, PhysPageNum, VirtAddr, VirtPageNum}; use super::{StepByOne, VPNRange}; use crate::config::{MEMORY_END, MMIO, PAGE_SIZE, TRAMPOLINE}; -use crate::sync::UPSafeCell; +use crate::sync::UPIntrFreeCell; use alloc::collections::BTreeMap; use alloc::sync::Arc; use alloc::vec::Vec; @@ -25,8 +25,8 @@ extern "C" { } lazy_static! { - pub static ref KERNEL_SPACE: Arc> = - Arc::new(unsafe { UPSafeCell::new(MemorySet::new_kernel()) }); + pub static ref KERNEL_SPACE: Arc> = + Arc::new(unsafe { UPIntrFreeCell::new(MemorySet::new_kernel()) }); } pub fn kernel_token() -> usize { diff --git a/os/src/sync/condvar.rs b/os/src/sync/condvar.rs index d7943e1..8435a7d 100644 --- a/os/src/sync/condvar.rs +++ b/os/src/sync/condvar.rs @@ -1,9 +1,9 @@ -use crate::sync::{Mutex, UPSafeCell}; -use crate::task::{add_task, block_current_and_run_next, current_task, TaskControlBlock}; +use crate::sync::{Mutex, UPIntrFreeCell}; +use crate::task::{add_task, block_current_task, block_current_and_run_next, current_task, TaskControlBlock, TaskContext}; use alloc::{collections::VecDeque, sync::Arc}; pub struct Condvar { - pub inner: UPSafeCell, + pub inner: UPIntrFreeCell, } pub struct CondvarInner { @@ -14,7 +14,7 @@ impl Condvar { pub fn new() -> Self { Self { inner: unsafe { - UPSafeCell::new(CondvarInner { + UPIntrFreeCell::new(CondvarInner { wait_queue: VecDeque::new(), }) }, @@ -35,6 +35,13 @@ impl Condvar { block_current_and_run_next(); } + pub fn wait_no_sched(&self) -> *mut TaskContext { + self.inner.exclusive_session(|inner| { + inner.wait_queue.push_back(current_task().unwrap()); + }); + block_current_task() + } + pub fn wait_with_mutex(&self, mutex: Arc) { mutex.unlock(); let mut inner = self.inner.exclusive_access(); diff --git a/os/src/sync/mod.rs b/os/src/sync/mod.rs index 7e948aa..d9a00b5 100644 --- a/os/src/sync/mod.rs +++ b/os/src/sync/mod.rs @@ -6,4 +6,4 @@ mod up; pub use condvar::Condvar; pub use mutex::{Mutex, MutexBlocking, MutexSpin}; pub use semaphore::Semaphore; -pub use up::UPSafeCell; +pub use up::{UPIntrFreeCell, UPIntrRefMut}; diff --git a/os/src/sync/mutex.rs b/os/src/sync/mutex.rs index be58f79..463638b 100644 --- a/os/src/sync/mutex.rs +++ b/os/src/sync/mutex.rs @@ -1,4 +1,4 @@ -use super::UPSafeCell; +use super::UPIntrFreeCell; use crate::task::TaskControlBlock; use crate::task::{add_task, current_task}; use crate::task::{block_current_and_run_next, suspend_current_and_run_next}; @@ -10,13 +10,13 @@ pub trait Mutex: Sync + Send { } pub struct MutexSpin { - locked: UPSafeCell, + locked: UPIntrFreeCell, } impl MutexSpin { pub fn new() -> Self { Self { - locked: unsafe { UPSafeCell::new(false) }, + locked: unsafe { UPIntrFreeCell::new(false) }, } } } @@ -43,7 +43,7 @@ impl Mutex for MutexSpin { } pub struct MutexBlocking { - inner: UPSafeCell, + inner: UPIntrFreeCell, } pub struct MutexBlockingInner { @@ -55,7 +55,7 @@ impl MutexBlocking { pub fn new() -> Self { Self { inner: unsafe { - UPSafeCell::new(MutexBlockingInner { + UPIntrFreeCell::new(MutexBlockingInner { locked: false, wait_queue: VecDeque::new(), }) diff --git a/os/src/sync/semaphore.rs b/os/src/sync/semaphore.rs index 7f3870f..354db29 100644 --- a/os/src/sync/semaphore.rs +++ b/os/src/sync/semaphore.rs @@ -1,9 +1,9 @@ -use crate::sync::UPSafeCell; +use crate::sync::UPIntrFreeCell; use crate::task::{add_task, block_current_and_run_next, current_task, TaskControlBlock}; use alloc::{collections::VecDeque, sync::Arc}; pub struct Semaphore { - pub inner: UPSafeCell, + pub inner: UPIntrFreeCell, } pub struct SemaphoreInner { @@ -15,7 +15,7 @@ impl Semaphore { pub fn new(res_count: usize) -> Self { Self { inner: unsafe { - UPSafeCell::new(SemaphoreInner { + UPIntrFreeCell::new(SemaphoreInner { count: res_count as isize, wait_queue: VecDeque::new(), }) diff --git a/os/src/sync/up.rs b/os/src/sync/up.rs index c7b2c9e..c5db9e5 100644 --- a/os/src/sync/up.rs +++ b/os/src/sync/up.rs @@ -1,4 +1,7 @@ -use core::cell::{RefCell, RefMut}; +use core::cell::{RefCell, RefMut, UnsafeCell}; +use core::ops::{Deref, DerefMut}; +use riscv::register::sstatus; +use lazy_static::*; /// Wrap a static data structure inside it so that we are /// able to access it without any `unsafe`. @@ -27,3 +30,103 @@ impl UPSafeCell { self.inner.borrow_mut() } } + +pub struct UPSafeCellRaw { + inner: UnsafeCell, +} + +unsafe impl Sync for UPSafeCellRaw {} + +impl UPSafeCellRaw { + pub unsafe fn new(value: T) -> Self { + Self { + inner: UnsafeCell::new(value), + } + } + pub fn get_mut(&self) -> &mut T { + unsafe { &mut (*self.inner.get()) } + } +} + +pub struct IntrMaskingInfo { + nested_level: usize, + sie_before_masking: bool, +} + +lazy_static! { + static ref INTR_MASKING_INFO: UPSafeCellRaw = unsafe { + UPSafeCellRaw::new(IntrMaskingInfo::new()) + }; +} + +impl IntrMaskingInfo { + pub fn new() -> Self { + Self { + nested_level: 0, + sie_before_masking: false, + } + } + + pub fn enter(&mut self) { + let sie = sstatus::read().sie(); + unsafe { sstatus::clear_sie(); } + if self.nested_level == 0 { + self.sie_before_masking = sie; + } + self.nested_level += 1; + } + + pub fn exit(&mut self) { + self.nested_level -= 1; + if self.nested_level == 0 && self.sie_before_masking { + unsafe { sstatus::set_sie(); } + } + } +} + +pub struct UPIntrFreeCell { + /// inner data + inner: RefCell, +} + +unsafe impl Sync for UPIntrFreeCell {} + +pub struct UPIntrRefMut<'a, T>(Option>); + +impl UPIntrFreeCell { + pub unsafe fn new(value: T) -> Self { + Self { + inner: RefCell::new(value), + } + } + /// Panic if the data has been borrowed. + pub fn exclusive_access(&self) -> UPIntrRefMut<'_, T> { + INTR_MASKING_INFO.get_mut().enter(); + UPIntrRefMut(Some(self.inner.borrow_mut())) + } + + pub fn exclusive_session(&self, f: F) -> V where F: FnOnce(&mut T) -> V { + let mut inner = self.exclusive_access(); + f(inner.deref_mut()) + } +} + +impl<'a, T> Drop for UPIntrRefMut<'a, T> { + fn drop(&mut self) { + self.0 = None; + INTR_MASKING_INFO.get_mut().exit(); + } +} + +impl<'a, T> Deref for UPIntrRefMut<'a, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + self.0.as_ref().unwrap().deref() + } +} +impl<'a, T> DerefMut for UPIntrRefMut<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.as_mut().unwrap().deref_mut() + } +} + diff --git a/os/src/task/id.rs b/os/src/task/id.rs index 178a09f..e336c50 100644 --- a/os/src/task/id.rs +++ b/os/src/task/id.rs @@ -1,7 +1,7 @@ use super::ProcessControlBlock; use crate::config::{KERNEL_STACK_SIZE, PAGE_SIZE, TRAMPOLINE, TRAP_CONTEXT_BASE, USER_STACK_SIZE}; use crate::mm::{MapPermission, PhysPageNum, VirtAddr, KERNEL_SPACE}; -use crate::sync::UPSafeCell; +use crate::sync::UPIntrFreeCell; use alloc::{ sync::{Arc, Weak}, vec::Vec, @@ -40,10 +40,10 @@ impl RecycleAllocator { } lazy_static! { - static ref PID_ALLOCATOR: UPSafeCell = - unsafe { UPSafeCell::new(RecycleAllocator::new()) }; - static ref KSTACK_ALLOCATOR: UPSafeCell = - unsafe { UPSafeCell::new(RecycleAllocator::new()) }; + static ref PID_ALLOCATOR: UPIntrFreeCell = + unsafe { UPIntrFreeCell::new(RecycleAllocator::new()) }; + static ref KSTACK_ALLOCATOR: UPIntrFreeCell = + unsafe { UPIntrFreeCell::new(RecycleAllocator::new()) }; } pub struct PidHandle(pub usize); diff --git a/os/src/task/manager.rs b/os/src/task/manager.rs index 5ed68ae..168ba32 100644 --- a/os/src/task/manager.rs +++ b/os/src/task/manager.rs @@ -1,5 +1,5 @@ use super::{ProcessControlBlock, TaskControlBlock}; -use crate::sync::UPSafeCell; +use crate::sync::UPIntrFreeCell; use alloc::collections::{BTreeMap, VecDeque}; use alloc::sync::Arc; use lazy_static::*; @@ -24,10 +24,10 @@ impl TaskManager { } lazy_static! { - pub static ref TASK_MANAGER: UPSafeCell = - unsafe { UPSafeCell::new(TaskManager::new()) }; - pub static ref PID2PCB: UPSafeCell>> = - unsafe { UPSafeCell::new(BTreeMap::new()) }; + pub static ref TASK_MANAGER: UPIntrFreeCell = + unsafe { UPIntrFreeCell::new(TaskManager::new()) }; + pub static ref PID2PCB: UPIntrFreeCell>> = + unsafe { UPIntrFreeCell::new(BTreeMap::new()) }; } pub fn add_task(task: Arc) { diff --git a/os/src/task/mod.rs b/os/src/task/mod.rs index 1bd7b2a..858848a 100644 --- a/os/src/task/mod.rs +++ b/os/src/task/mod.rs @@ -43,12 +43,16 @@ pub fn suspend_current_and_run_next() { schedule(task_cx_ptr); } -pub fn block_current_and_run_next() { +/// This function must be followed by a schedule +pub fn block_current_task() -> *mut TaskContext { let task = take_current_task().unwrap(); let mut task_inner = task.inner_exclusive_access(); - let task_cx_ptr = &mut task_inner.task_cx as *mut TaskContext; task_inner.task_status = TaskStatus::Blocking; - drop(task_inner); + &mut task_inner.task_cx as *mut TaskContext +} + +pub fn block_current_and_run_next() { + let task_cx_ptr = block_current_task(); schedule(task_cx_ptr); } diff --git a/os/src/task/process.rs b/os/src/task/process.rs index ffff14f..b3976b7 100644 --- a/os/src/task/process.rs +++ b/os/src/task/process.rs @@ -5,19 +5,18 @@ use super::{add_task, SignalFlags}; use super::{pid_alloc, PidHandle}; use crate::fs::{File, Stdin, Stdout}; use crate::mm::{translated_refmut, MemorySet, KERNEL_SPACE}; -use crate::sync::{Condvar, Mutex, Semaphore, UPSafeCell}; +use crate::sync::{Condvar, Mutex, Semaphore, UPIntrFreeCell, UPIntrRefMut}; use crate::trap::{trap_handler, TrapContext}; use alloc::string::String; use alloc::sync::{Arc, Weak}; use alloc::vec; use alloc::vec::Vec; -use core::cell::RefMut; pub struct ProcessControlBlock { // immutable pub pid: PidHandle, // mutable - inner: UPSafeCell, + inner: UPIntrFreeCell, } pub struct ProcessControlBlockInner { @@ -68,7 +67,7 @@ impl ProcessControlBlockInner { } impl ProcessControlBlock { - pub fn inner_exclusive_access(&self) -> RefMut<'_, ProcessControlBlockInner> { + pub fn inner_exclusive_access(&self) -> UPIntrRefMut<'_, ProcessControlBlockInner> { self.inner.exclusive_access() } @@ -80,7 +79,7 @@ impl ProcessControlBlock { let process = Arc::new(Self { pid: pid_handle, inner: unsafe { - UPSafeCell::new(ProcessControlBlockInner { + UPIntrFreeCell::new(ProcessControlBlockInner { is_zombie: false, memory_set, parent: None, @@ -206,7 +205,7 @@ impl ProcessControlBlock { let child = Arc::new(Self { pid, inner: unsafe { - UPSafeCell::new(ProcessControlBlockInner { + UPIntrFreeCell::new(ProcessControlBlockInner { is_zombie: false, memory_set, parent: Some(Arc::downgrade(self)), diff --git a/os/src/task/processor.rs b/os/src/task/processor.rs index 3c1a22f..347d372 100644 --- a/os/src/task/processor.rs +++ b/os/src/task/processor.rs @@ -1,7 +1,7 @@ use super::__switch; use super::{fetch_task, TaskStatus}; use super::{ProcessControlBlock, TaskContext, TaskControlBlock}; -use crate::sync::UPSafeCell; +use crate::sync::UPIntrFreeCell; use crate::trap::TrapContext; use alloc::sync::Arc; use lazy_static::*; @@ -30,7 +30,7 @@ impl Processor { } lazy_static! { - pub static ref PROCESSOR: UPSafeCell = unsafe { UPSafeCell::new(Processor::new()) }; + pub static ref PROCESSOR: UPIntrFreeCell = unsafe { UPIntrFreeCell::new(Processor::new()) }; } pub fn run_tasks() { diff --git a/os/src/task/task.rs b/os/src/task/task.rs index 99900de..7b65ee6 100644 --- a/os/src/task/task.rs +++ b/os/src/task/task.rs @@ -1,20 +1,19 @@ use super::id::TaskUserRes; use super::{kstack_alloc, KernelStack, ProcessControlBlock, TaskContext}; use crate::trap::TrapContext; -use crate::{mm::PhysPageNum, sync::UPSafeCell}; +use crate::{mm::PhysPageNum, sync::{UPIntrFreeCell, UPIntrRefMut}}; use alloc::sync::{Arc, Weak}; -use core::cell::RefMut; pub struct TaskControlBlock { // immutable pub process: Weak, pub kstack: KernelStack, // mutable - inner: UPSafeCell, + inner: UPIntrFreeCell, } impl TaskControlBlock { - pub fn inner_exclusive_access(&self) -> RefMut<'_, TaskControlBlockInner> { + pub fn inner_exclusive_access(&self) -> UPIntrRefMut<'_, TaskControlBlockInner> { self.inner.exclusive_access() } @@ -58,7 +57,7 @@ impl TaskControlBlock { process: Arc::downgrade(&process), kstack, inner: unsafe { - UPSafeCell::new(TaskControlBlockInner { + UPIntrFreeCell::new(TaskControlBlockInner { res: Some(res), trap_cx_ppn, task_cx: TaskContext::goto_trap_return(kstack_top), diff --git a/os/src/timer.rs b/os/src/timer.rs index 3baed0f..06a70de 100644 --- a/os/src/timer.rs +++ b/os/src/timer.rs @@ -2,7 +2,7 @@ use core::cmp::Ordering; use crate::config::CLOCK_FREQ; use crate::sbi::set_timer; -use crate::sync::UPSafeCell; +use crate::sync::UPIntrFreeCell; use crate::task::{add_task, TaskControlBlock}; use alloc::collections::BinaryHeap; use alloc::sync::Arc; @@ -50,8 +50,8 @@ impl Ord for TimerCondVar { } lazy_static! { - static ref TIMERS: UPSafeCell> = - unsafe { UPSafeCell::new(BinaryHeap::::new()) }; + static ref TIMERS: UPIntrFreeCell> = + unsafe { UPIntrFreeCell::new(BinaryHeap::::new()) }; } pub fn add_timer(expire_ms: usize, task: Arc) { diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index 287afd3..2175540 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -11,7 +11,7 @@ use core::arch::{asm, global_asm}; use riscv::register::{ mtvec::TrapMode, scause::{self, Exception, Interrupt, Trap}, - sie, stval, stvec, + sie, stval, stvec, sstatus, sscratch, }; global_asm!(include_str!("trap.S")); @@ -21,8 +21,14 @@ pub fn init() { } fn set_kernel_trap_entry() { + extern "C" { + fn __alltraps(); + fn __alltraps_k(); + } + let __alltraps_k_va = __alltraps_k as usize - __alltraps as usize + TRAMPOLINE; unsafe { - stvec::write(trap_from_kernel as usize, TrapMode::Direct); + stvec::write(__alltraps_k_va, TrapMode::Direct); + sscratch::write(trap_from_kernel as usize); } } @@ -38,16 +44,28 @@ pub fn enable_timer_interrupt() { } } +fn enable_supervisor_interrupt() { + unsafe { + sstatus::set_sie(); + } +} + #[no_mangle] pub fn trap_handler() -> ! { set_kernel_trap_entry(); let scause = scause::read(); let stval = stval::read(); + //println!("into {:?}", scause.cause()); match scause.cause() { Trap::Exception(Exception::UserEnvCall) => { // jump to next instruction anyway let mut cx = current_trap_cx(); cx.sepc += 4; + + //println!("syscall id={}", cx.x[17]); + //println!("after setting sstatus.sie"); + enable_supervisor_interrupt(); + // get system call return value let result = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]); // cx is changed during sys_exec, so we have to call it again @@ -107,6 +125,7 @@ pub fn trap_return() -> ! { fn __restore(); } let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE; + //println!("before return"); unsafe { asm!( "fence.i", @@ -120,10 +139,37 @@ pub fn trap_return() -> ! { } #[no_mangle] -pub fn trap_from_kernel() -> ! { +pub fn trap_from_kernel(trap_cx: &TrapContext) { + /* use riscv::register::sepc; + println!("a trap {:?} from kernel!", scause::read().cause()); println!("stval = {:#x}, sepc = {:#x}", stval::read(), sepc::read()); - panic!("a trap {:?} from kernel!", scause::read().cause()); + //panic!("a trap {:?} from kernel!", scause::read().cause()); + */ + //panic!("a trap {:?} from kernel!", scause::read().cause()); + //println!("->trap_from_kernel"); + //println!("a trap {:?} from kernel!", scause::read().cause()); + //println!("sepc = {:#x}", trap_cx.sepc); + let scause = scause::read(); + let stval = stval::read(); + match scause.cause() { + Trap::Interrupt(Interrupt::SupervisorExternal) => { + crate::board::irq_handler(); + }, + Trap::Interrupt(Interrupt::SupervisorTimer) => { + set_next_trigger(); + check_timer(); + // do not schedule now + }, + _ => { + panic!( + "Unsupported trap from kernel: {:?}, stval = {:#x}!", + scause.cause(), + stval + ); + }, + } + //println!("trap_from_kernel->"); } pub use context::TrapContext; diff --git a/os/src/trap/trap.S b/os/src/trap/trap.S index c0e2d15..407307c 100644 --- a/os/src/trap/trap.S +++ b/os/src/trap/trap.S @@ -8,6 +8,8 @@ .section .text.trampoline .globl __alltraps .globl __restore + .globl __alltraps_k + .globl __restore_k .align 2 __alltraps: csrrw sp, sscratch, sp @@ -67,3 +69,36 @@ __restore: # back to user stack ld sp, 2*8(sp) sret + + .align 2 +__alltraps_k: + addi sp, sp, -34*8 + sd x1, 1*8(sp) + sd x3, 3*8(sp) + .set n, 5 + .rept 27 + SAVE_GP %n + .set n, n+1 + .endr + csrr t0, sstatus + csrr t1, sepc + sd t0, 32*8(sp) + sd t1, 33*8(sp) + mv a0, sp + csrr t2, sscratch + jalr t2 + +__restore_k: + ld t0, 32*8(sp) + ld t1, 33*8(sp) + csrw sstatus, t0 + csrw sepc, t1 + ld x1, 1*8(sp) + ld x3, 3*8(sp) + .set n, 5 + .rept 27 + LOAD_GP %n + .set n, n+1 + .endr + addi sp, sp, 34*8 + sret From ba611a1458753796c61e6785daeba2eaba12d54c Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Thu, 10 Mar 2022 16:27:05 -0800 Subject: [PATCH 35/56] We should disable sie before trapping back to user. --- os/src/boards/qemu.rs | 13 ------------- os/src/console.rs | 4 ++-- os/src/drivers/block/virtio_blk.rs | 20 ++++++++++++++++++++ os/src/drivers/chardev/ns16550a.rs | 6 +++--- os/src/fs/stdio.rs | 2 +- os/src/sync/condvar.rs | 5 +++++ os/src/sync/up.rs | 4 ++++ os/src/trap/mod.rs | 22 ++++++++-------------- 8 files changed, 43 insertions(+), 33 deletions(-) diff --git a/os/src/boards/qemu.rs b/os/src/boards/qemu.rs index 38de344..0c3ba66 100644 --- a/os/src/boards/qemu.rs +++ b/os/src/boards/qemu.rs @@ -28,31 +28,18 @@ pub fn device_init() { plic.enable(hart_id, supervisor, intr_src_id); plic.set_priority(intr_src_id, 1); } - crate::println!( - "Hart0M threshold = {}", - plic.get_threshold(hart_id, IntrTargetPriority::Machine) - ); - crate::println!( - "Hart0S threshold = {}", - plic.get_threshold(hart_id, IntrTargetPriority::Supervisor) - ); - crate::println!("1 prio = {}", plic.get_priority(1)); - crate::println!("10 prio = {}", plic.get_priority(10)); unsafe { sie::set_sext(); } } pub fn irq_handler() { - //crate::println!("->irq_handler"); let mut plic = unsafe { PLIC::new(VIRT_PLIC) }; let intr_src_id = plic.claim(0, IntrTargetPriority::Supervisor); - //crate::println!("intr_src={}", intr_src_id); match intr_src_id { 1 => BLOCK_DEVICE.handle_irq(), 10 => UART.handle_irq(), _ => panic!("unsupported IRQ {}", intr_src_id), } plic.complete(0, IntrTargetPriority::Supervisor, intr_src_id); - //crate::println!("irq_handler->"); } diff --git a/os/src/console.rs b/os/src/console.rs index 9f27b4b..f95963c 100644 --- a/os/src/console.rs +++ b/os/src/console.rs @@ -7,8 +7,8 @@ struct Stdout; impl Write for Stdout { fn write_str(&mut self, s: &str) -> fmt::Result { for c in s.chars() { - //UART.write(c as u8); - console_putchar(c as usize); + UART.write(c as u8); + //console_putchar(c as usize); } Ok(()) } diff --git a/os/src/drivers/block/virtio_blk.rs b/os/src/drivers/block/virtio_blk.rs index 7993fb6..26967f7 100644 --- a/os/src/drivers/block/virtio_blk.rs +++ b/os/src/drivers/block/virtio_blk.rs @@ -28,10 +28,17 @@ impl BlockDevice for VirtIOBlock { let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access(); if nb { let mut resp = BlkResp::default(); + /* let task_cx_ptr = self.virtio_blk.exclusive_session(|blk| { let token = unsafe { blk.read_block_nb(block_id, buf, &mut resp).unwrap() }; self.condvars.get(&token).unwrap().wait_no_sched() }); + */ + let mut blk = self.virtio_blk.exclusive_access(); + let token = unsafe { blk.read_block_nb(block_id, buf, &mut resp).unwrap() }; + //println!("waiting on token {}", token); + let task_cx_ptr = self.condvars.get(&token).unwrap().wait_no_sched(); + drop(blk); schedule(task_cx_ptr); assert_eq!( resp.status(), @@ -49,10 +56,16 @@ impl BlockDevice for VirtIOBlock { let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access(); if nb { let mut resp = BlkResp::default(); + /* let task_cx_ptr = self.virtio_blk.exclusive_session(|blk| { let token = unsafe { blk.write_block_nb(block_id, buf, &mut resp).unwrap() }; self.condvars.get(&token).unwrap().wait_no_sched() }); + */ + let mut blk = self.virtio_blk.exclusive_access(); + let token = unsafe { blk.write_block_nb(block_id, buf, &mut resp).unwrap() }; + let task_cx_ptr = self.condvars.get(&token).unwrap().wait_no_sched(); + drop(blk); schedule(task_cx_ptr); assert_eq!( resp.status(), @@ -68,6 +81,7 @@ impl BlockDevice for VirtIOBlock { } fn handle_irq(&self) { //println!("into handle_irq"); + /* self.virtio_blk.exclusive_session(|blk| { //println!("not panic here"); while let Ok(token) = blk.pop_used() { @@ -75,6 +89,12 @@ impl BlockDevice for VirtIOBlock { self.condvars.get(&token).unwrap().signal(); } }); + */ + let mut blk = self.virtio_blk.exclusive_access(); + while let Ok(token) = blk.pop_used() { + //println!("wakeup virtio.token {}", token); + self.condvars.get(&token).unwrap().signal(); + } } } diff --git a/os/src/drivers/chardev/ns16550a.rs b/os/src/drivers/chardev/ns16550a.rs index 4613999..40c1b47 100644 --- a/os/src/drivers/chardev/ns16550a.rs +++ b/os/src/drivers/chardev/ns16550a.rs @@ -146,15 +146,15 @@ impl NS16550a { impl CharDevice for NS16550a { fn read(&self) -> u8 { - println!("NS16550a::read"); + //println!("NS16550a::read"); loop { let mut inner = self.inner.exclusive_access(); if let Some(ch) = inner.read_buffer.pop_front() { return ch; } else { - println!("no ch yet!"); let task_cx_ptr = self.condvar.wait_no_sched(); drop(inner); + //println!("before scheduling"); schedule(task_cx_ptr); } } @@ -167,7 +167,7 @@ impl CharDevice for NS16550a { let mut inner = self.inner.exclusive_access(); let mut count = 0; while let Some(ch) = inner.ns16550a.read() { - println!("got {}", ch as char); + //println!("got {}", ch as char); count += 1; inner.read_buffer.push_back(ch); } diff --git a/os/src/fs/stdio.rs b/os/src/fs/stdio.rs index 99d07c2..7326822 100644 --- a/os/src/fs/stdio.rs +++ b/os/src/fs/stdio.rs @@ -14,7 +14,7 @@ impl File for Stdin { } fn read(&self, mut user_buf: UserBuffer) -> usize { assert_eq!(user_buf.len(), 1); - println!("before UART.read() in Stdin::read()"); + //println!("before UART.read() in Stdin::read()"); let ch = UART.read(); unsafe { user_buf.buffers[0].as_mut_ptr().write_volatile(ch); diff --git a/os/src/sync/condvar.rs b/os/src/sync/condvar.rs index 8435a7d..b23d22e 100644 --- a/os/src/sync/condvar.rs +++ b/os/src/sync/condvar.rs @@ -36,9 +36,14 @@ impl Condvar { } pub fn wait_no_sched(&self) -> *mut TaskContext { + /* self.inner.exclusive_session(|inner| { inner.wait_queue.push_back(current_task().unwrap()); }); + */ + let mut inner = self.inner.exclusive_access(); + inner.wait_queue.push_back(current_task().unwrap()); + drop(inner); block_current_task() } diff --git a/os/src/sync/up.rs b/os/src/sync/up.rs index c5db9e5..f8579ea 100644 --- a/os/src/sync/up.rs +++ b/os/src/sync/up.rs @@ -3,6 +3,7 @@ use core::ops::{Deref, DerefMut}; use riscv::register::sstatus; use lazy_static::*; +/* /// Wrap a static data structure inside it so that we are /// able to access it without any `unsafe`. /// @@ -30,6 +31,7 @@ impl UPSafeCell { self.inner.borrow_mut() } } +*/ pub struct UPSafeCellRaw { inner: UnsafeCell, @@ -105,10 +107,12 @@ impl UPIntrFreeCell { UPIntrRefMut(Some(self.inner.borrow_mut())) } + /* pub fn exclusive_session(&self, f: F) -> V where F: FnOnce(&mut T) -> V { let mut inner = self.exclusive_access(); f(inner.deref_mut()) } + */ } impl<'a, T> Drop for UPIntrRefMut<'a, T> { diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index 2175540..5eb417d 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -50,6 +50,12 @@ fn enable_supervisor_interrupt() { } } +fn disable_supervisor_interrupt() { + unsafe { + sstatus::clear_sie(); + } +} + #[no_mangle] pub fn trap_handler() -> ! { set_kernel_trap_entry(); @@ -62,8 +68,6 @@ pub fn trap_handler() -> ! { let mut cx = current_trap_cx(); cx.sepc += 4; - //println!("syscall id={}", cx.x[17]); - //println!("after setting sstatus.sie"); enable_supervisor_interrupt(); // get system call return value @@ -117,6 +121,7 @@ pub fn trap_handler() -> ! { #[no_mangle] pub fn trap_return() -> ! { + disable_supervisor_interrupt(); set_user_trap_entry(); let trap_cx_user_va = current_trap_cx_user_va(); let user_satp = current_user_token(); @@ -139,17 +144,7 @@ pub fn trap_return() -> ! { } #[no_mangle] -pub fn trap_from_kernel(trap_cx: &TrapContext) { - /* - use riscv::register::sepc; - println!("a trap {:?} from kernel!", scause::read().cause()); - println!("stval = {:#x}, sepc = {:#x}", stval::read(), sepc::read()); - //panic!("a trap {:?} from kernel!", scause::read().cause()); - */ - //panic!("a trap {:?} from kernel!", scause::read().cause()); - //println!("->trap_from_kernel"); - //println!("a trap {:?} from kernel!", scause::read().cause()); - //println!("sepc = {:#x}", trap_cx.sepc); +pub fn trap_from_kernel(_trap_cx: &TrapContext) { let scause = scause::read(); let stval = stval::read(); match scause.cause() { @@ -169,7 +164,6 @@ pub fn trap_from_kernel(trap_cx: &TrapContext) { ); }, } - //println!("trap_from_kernel->"); } pub use context::TrapContext; From fb196d35a9b6f71f2894e2d031023bf08d1a3f53 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Thu, 10 Mar 2022 16:41:06 -0800 Subject: [PATCH 36/56] use exclusive_session to eliminate some explicit drops. --- os/src/console.rs | 2 -- os/src/drivers/block/virtio_blk.rs | 23 ----------------------- os/src/drivers/chardev/ns16550a.rs | 16 ++++++---------- os/src/drivers/plic.rs | 2 ++ os/src/sync/condvar.rs | 13 +++++-------- os/src/sync/up.rs | 2 -- os/src/task/processor.rs | 15 +++++++-------- os/src/task/task.rs | 2 +- 8 files changed, 21 insertions(+), 54 deletions(-) diff --git a/os/src/console.rs b/os/src/console.rs index f95963c..31614c6 100644 --- a/os/src/console.rs +++ b/os/src/console.rs @@ -1,6 +1,5 @@ use crate::drivers::chardev::{CharDevice, UART}; use core::fmt::{self, Write}; -use crate::sbi::console_putchar; struct Stdout; @@ -8,7 +7,6 @@ impl Write for Stdout { fn write_str(&mut self, s: &str) -> fmt::Result { for c in s.chars() { UART.write(c as u8); - //console_putchar(c as usize); } Ok(()) } diff --git a/os/src/drivers/block/virtio_blk.rs b/os/src/drivers/block/virtio_blk.rs index 26967f7..a30e3bf 100644 --- a/os/src/drivers/block/virtio_blk.rs +++ b/os/src/drivers/block/virtio_blk.rs @@ -28,17 +28,10 @@ impl BlockDevice for VirtIOBlock { let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access(); if nb { let mut resp = BlkResp::default(); - /* let task_cx_ptr = self.virtio_blk.exclusive_session(|blk| { let token = unsafe { blk.read_block_nb(block_id, buf, &mut resp).unwrap() }; self.condvars.get(&token).unwrap().wait_no_sched() }); - */ - let mut blk = self.virtio_blk.exclusive_access(); - let token = unsafe { blk.read_block_nb(block_id, buf, &mut resp).unwrap() }; - //println!("waiting on token {}", token); - let task_cx_ptr = self.condvars.get(&token).unwrap().wait_no_sched(); - drop(blk); schedule(task_cx_ptr); assert_eq!( resp.status(), @@ -56,16 +49,10 @@ impl BlockDevice for VirtIOBlock { let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access(); if nb { let mut resp = BlkResp::default(); - /* let task_cx_ptr = self.virtio_blk.exclusive_session(|blk| { let token = unsafe { blk.write_block_nb(block_id, buf, &mut resp).unwrap() }; self.condvars.get(&token).unwrap().wait_no_sched() }); - */ - let mut blk = self.virtio_blk.exclusive_access(); - let token = unsafe { blk.write_block_nb(block_id, buf, &mut resp).unwrap() }; - let task_cx_ptr = self.condvars.get(&token).unwrap().wait_no_sched(); - drop(blk); schedule(task_cx_ptr); assert_eq!( resp.status(), @@ -80,21 +67,11 @@ impl BlockDevice for VirtIOBlock { } } fn handle_irq(&self) { - //println!("into handle_irq"); - /* self.virtio_blk.exclusive_session(|blk| { - //println!("not panic here"); while let Ok(token) = blk.pop_used() { - //println!("wakeup virtio.token {}", token); self.condvars.get(&token).unwrap().signal(); } }); - */ - let mut blk = self.virtio_blk.exclusive_access(); - while let Ok(token) = blk.pop_used() { - //println!("wakeup virtio.token {}", token); - self.condvars.get(&token).unwrap().signal(); - } } } diff --git a/os/src/drivers/chardev/ns16550a.rs b/os/src/drivers/chardev/ns16550a.rs index 40c1b47..667a660 100644 --- a/os/src/drivers/chardev/ns16550a.rs +++ b/os/src/drivers/chardev/ns16550a.rs @@ -146,7 +146,6 @@ impl NS16550a { impl CharDevice for NS16550a { fn read(&self) -> u8 { - //println!("NS16550a::read"); loop { let mut inner = self.inner.exclusive_access(); if let Some(ch) = inner.read_buffer.pop_front() { @@ -154,7 +153,6 @@ impl CharDevice for NS16550a { } else { let task_cx_ptr = self.condvar.wait_no_sched(); drop(inner); - //println!("before scheduling"); schedule(task_cx_ptr); } } @@ -164,15 +162,13 @@ impl CharDevice for NS16550a { inner.ns16550a.write(ch); } fn handle_irq(&self) { - let mut inner = self.inner.exclusive_access(); let mut count = 0; - while let Some(ch) = inner.ns16550a.read() { - //println!("got {}", ch as char); - count += 1; - inner.read_buffer.push_back(ch); - } - drop(inner); - //assert_eq!(count, 1); + self.inner.exclusive_session(|inner| { + while let Some(ch) = inner.ns16550a.read() { + count += 1; + inner.read_buffer.push_back(ch); + } + }); if count > 0 { self.condvar.signal(); } diff --git a/os/src/drivers/plic.rs b/os/src/drivers/plic.rs index b1f662f..77bad5f 100644 --- a/os/src/drivers/plic.rs +++ b/os/src/drivers/plic.rs @@ -62,6 +62,7 @@ impl PLIC { self.priority_ptr(intr_source_id).write_volatile(priority); } } + #[allow(unused)] pub fn get_priority(&mut self, intr_source_id: usize) -> u32 { unsafe { self.priority_ptr(intr_source_id).read_volatile() & 7 } } @@ -100,6 +101,7 @@ impl PLIC { threshold_ptr.write_volatile(threshold); } } + #[allow(unused)] pub fn get_threshold(&mut self, hart_id: usize, target_priority: IntrTargetPriority) -> u32 { let threshold_ptr = self.threshold_ptr_of_hart_with_priority(hart_id, target_priority); unsafe { threshold_ptr.read_volatile() & 7 } diff --git a/os/src/sync/condvar.rs b/os/src/sync/condvar.rs index b23d22e..c50a53e 100644 --- a/os/src/sync/condvar.rs +++ b/os/src/sync/condvar.rs @@ -28,30 +28,27 @@ impl Condvar { } } + /* pub fn wait(&self) { let mut inner = self.inner.exclusive_access(); inner.wait_queue.push_back(current_task().unwrap()); drop(inner); block_current_and_run_next(); } + */ pub fn wait_no_sched(&self) -> *mut TaskContext { - /* self.inner.exclusive_session(|inner| { inner.wait_queue.push_back(current_task().unwrap()); }); - */ - let mut inner = self.inner.exclusive_access(); - inner.wait_queue.push_back(current_task().unwrap()); - drop(inner); block_current_task() } pub fn wait_with_mutex(&self, mutex: Arc) { mutex.unlock(); - let mut inner = self.inner.exclusive_access(); - inner.wait_queue.push_back(current_task().unwrap()); - drop(inner); + self.inner.exclusive_session(|inner| { + inner.wait_queue.push_back(current_task().unwrap()); + }); block_current_and_run_next(); mutex.lock(); } diff --git a/os/src/sync/up.rs b/os/src/sync/up.rs index f8579ea..55c8d40 100644 --- a/os/src/sync/up.rs +++ b/os/src/sync/up.rs @@ -107,12 +107,10 @@ impl UPIntrFreeCell { UPIntrRefMut(Some(self.inner.borrow_mut())) } - /* pub fn exclusive_session(&self, f: F) -> V where F: FnOnce(&mut T) -> V { let mut inner = self.exclusive_access(); f(inner.deref_mut()) } - */ } impl<'a, T> Drop for UPIntrRefMut<'a, T> { diff --git a/os/src/task/processor.rs b/os/src/task/processor.rs index 347d372..f196f68 100644 --- a/os/src/task/processor.rs +++ b/os/src/task/processor.rs @@ -39,11 +39,10 @@ pub fn run_tasks() { if let Some(task) = fetch_task() { let idle_task_cx_ptr = processor.get_idle_task_cx_ptr(); // access coming task TCB exclusively - let mut task_inner = task.inner_exclusive_access(); - let next_task_cx_ptr = &task_inner.task_cx as *const TaskContext; - task_inner.task_status = TaskStatus::Running; - drop(task_inner); - // release coming task TCB manually + let next_task_cx_ptr = task.inner.exclusive_session(|task_inner| { + task_inner.task_status = TaskStatus::Running; + &task_inner.task_cx as *const TaskContext + }); processor.current = Some(task); // release processor manually drop(processor); @@ -95,9 +94,9 @@ pub fn current_kstack_top() -> usize { } pub fn schedule(switched_task_cx_ptr: *mut TaskContext) { - let mut processor = PROCESSOR.exclusive_access(); - let idle_task_cx_ptr = processor.get_idle_task_cx_ptr(); - drop(processor); + let idle_task_cx_ptr = PROCESSOR.exclusive_session(|processor| { + processor.get_idle_task_cx_ptr() + }); unsafe { __switch(switched_task_cx_ptr, idle_task_cx_ptr); } diff --git a/os/src/task/task.rs b/os/src/task/task.rs index 7b65ee6..85d949f 100644 --- a/os/src/task/task.rs +++ b/os/src/task/task.rs @@ -9,7 +9,7 @@ pub struct TaskControlBlock { pub process: Weak, pub kstack: KernelStack, // mutable - inner: UPIntrFreeCell, + pub inner: UPIntrFreeCell, } impl TaskControlBlock { From 13cd4e15621db85973dbbc1b053256e4b3dba979 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sun, 20 Mar 2022 21:27:57 +0800 Subject: [PATCH 37/56] add CI for build-doc --- .github/workflows/build-doc.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/build-doc.yml diff --git a/.github/workflows/build-doc.yml b/.github/workflows/build-doc.yml new file mode 100644 index 0000000..a48f147 --- /dev/null +++ b/.github/workflows/build-doc.yml @@ -0,0 +1,25 @@ +name: Build Rust Doc + +on: [push] + +env: + CARGO_TERM_COLOR: always + +jobs: + build-doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build doc + run: | + rustup target add riscv64gc-unknown-none-elf + rustup component add llvm-tools-preview + rustup component add rust-src + cd os + cargo doc --no-deps --verbose + - name: Deploy to Github Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./os/target/riscv64gc-unknown-none-elf/doc + destination_dir: ${{ github.ref_name }} \ No newline at end of file From 500c0d9e587d677cb2233c913b7189771f355b9c Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sun, 20 Mar 2022 22:36:35 +0800 Subject: [PATCH 38/56] add index.html --- README.md | 2 ++ index.html | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 index.html diff --git a/README.md b/README.md index 7cc57d4..00a94f1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # rCore-Tutorial-v3 rCore-Tutorial version 3.5. See the [Documentation in Chinese](https://rcore-os.github.io/rCore-Tutorial-Book-v3/). +rCore-Tutorial API Docs. See the [API Docs of Ten OSes ](https://rcore-os.github.io/rCore-Tutorial-v3/index.html) + Official QQ group number: 735045051 ## news diff --git a/index.html b/index.html new file mode 100644 index 0000000..7aa1475 --- /dev/null +++ b/index.html @@ -0,0 +1,30 @@ + + + + + + + + +        + +       rcore-tutorial v3 + + + + +

Ten rCore Tutorial OSes

+
+
+

2. BatchOS API doc

+

3.1 MultiProgOS API doc

+

3.2 TimeSharingOS API doc

+

4. AddrSpaceOS API doc

+

5. ProcessOS API doc

+

6. FileSystemOS API doc

+

7. IPCOS API doc

+

8. SyncMutexOS API doc

+

9. DeviceOS API doc

+ + + From 2f42401b48eea79b02213f4c1482a4da71343663 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sun, 20 Mar 2022 22:55:05 +0800 Subject: [PATCH 39/56] update CI for build api doc --- .github/workflows/build-doc.yml | 8 +++++++- index.html => public/index.html | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) rename index.html => public/index.html (93%) diff --git a/.github/workflows/build-doc.yml b/.github/workflows/build-doc.yml index a48f147..536e738 100644 --- a/.github/workflows/build-doc.yml +++ b/.github/workflows/build-doc.yml @@ -22,4 +22,10 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./os/target/riscv64gc-unknown-none-elf/doc - destination_dir: ${{ github.ref_name }} \ No newline at end of file + destination_dir: ./public/${{ github.ref_name }} + - name: Deploy index.html to Github Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public + destination_dir: ./public \ No newline at end of file diff --git a/index.html b/public/index.html similarity index 93% rename from index.html rename to public/index.html index 7aa1475..28d7554 100644 --- a/index.html +++ b/public/index.html @@ -25,6 +25,7 @@

7. IPCOS API doc

8. SyncMutexOS API doc

9. DeviceOS API doc

+

9. main-branch OS API doc

From 9732599114b0d6cfbec97b70013d8ca401f2c193 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sun, 20 Mar 2022 22:55:05 +0800 Subject: [PATCH 40/56] fix typo in CI for build api doc --- .github/workflows/build-doc.yml | 8 +++++++- index.html => public/index.html | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) rename index.html => public/index.html (93%) diff --git a/.github/workflows/build-doc.yml b/.github/workflows/build-doc.yml index a48f147..536e738 100644 --- a/.github/workflows/build-doc.yml +++ b/.github/workflows/build-doc.yml @@ -22,4 +22,10 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./os/target/riscv64gc-unknown-none-elf/doc - destination_dir: ${{ github.ref_name }} \ No newline at end of file + destination_dir: ./public/${{ github.ref_name }} + - name: Deploy index.html to Github Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public + destination_dir: ./public \ No newline at end of file diff --git a/index.html b/public/index.html similarity index 93% rename from index.html rename to public/index.html index 7aa1475..de9ebd4 100644 --- a/index.html +++ b/public/index.html @@ -25,6 +25,7 @@

7. IPCOS API doc

8. SyncMutexOS API doc

9. DeviceOS API doc

+

10. main-branch OS API doc

From b8e368bf399653b86d1ae4b9e225d1f014196877 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sun, 20 Mar 2022 23:06:34 +0800 Subject: [PATCH 41/56] update index.html --- public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index de9ebd4..f64ce52 100644 --- a/public/index.html +++ b/public/index.html @@ -13,7 +13,7 @@ -

Ten rCore Tutorial OSes

+

The API docs for Ten rCore Tutorial OSes


1. LibOS API doc

2. BatchOS API doc

From 3995a58fb11f3fc57b63728c6df28b0bc6200435 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sun, 20 Mar 2022 23:11:22 +0800 Subject: [PATCH 42/56] update CI for build api doc --- .github/workflows/build-doc.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-doc.yml b/.github/workflows/build-doc.yml index 536e738..f559e3d 100644 --- a/.github/workflows/build-doc.yml +++ b/.github/workflows/build-doc.yml @@ -22,10 +22,10 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./os/target/riscv64gc-unknown-none-elf/doc - destination_dir: ./public/${{ github.ref_name }} + destination_dir: ./docs/${{ github.ref_name }} - name: Deploy index.html to Github Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./public - destination_dir: ./public \ No newline at end of file + destination_dir: ./docs \ No newline at end of file From 10cd895fb8e4a96f172325f23eb93f1085fdb5c4 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sun, 20 Mar 2022 23:20:09 +0800 Subject: [PATCH 43/56] update CI for build api doc --- .github/workflows/build-doc.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-doc.yml b/.github/workflows/build-doc.yml index f559e3d..7de1bed 100644 --- a/.github/workflows/build-doc.yml +++ b/.github/workflows/build-doc.yml @@ -15,15 +15,12 @@ jobs: rustup target add riscv64gc-unknown-none-elf rustup component add llvm-tools-preview rustup component add rust-src + rm -rf public/${{ github.ref_name }} + mkdir -p public/${{ github.ref_name }} cd os cargo doc --no-deps --verbose + cp -r ./target/riscv64gc-unknown-none-elf/doc ../public/${{ github.ref_name }} - name: Deploy to Github Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./os/target/riscv64gc-unknown-none-elf/doc - destination_dir: ./docs/${{ github.ref_name }} - - name: Deploy index.html to Github Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} From 5e4296d1415a86005dbf9fe29f008685933b04ca Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sun, 20 Mar 2022 23:45:23 +0800 Subject: [PATCH 44/56] update CI for build-doc --- .github/workflows/build-doc.yml | 7 ++----- README.md | 15 ++++++++++++++- public/index.html | 31 ------------------------------- 3 files changed, 16 insertions(+), 37 deletions(-) delete mode 100644 public/index.html diff --git a/.github/workflows/build-doc.yml b/.github/workflows/build-doc.yml index 7de1bed..a48f147 100644 --- a/.github/workflows/build-doc.yml +++ b/.github/workflows/build-doc.yml @@ -15,14 +15,11 @@ jobs: rustup target add riscv64gc-unknown-none-elf rustup component add llvm-tools-preview rustup component add rust-src - rm -rf public/${{ github.ref_name }} - mkdir -p public/${{ github.ref_name }} cd os cargo doc --no-deps --verbose - cp -r ./target/riscv64gc-unknown-none-elf/doc ../public/${{ github.ref_name }} - name: Deploy to Github Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./public - destination_dir: ./docs \ No newline at end of file + publish_dir: ./os/target/riscv64gc-unknown-none-elf/doc + destination_dir: ${{ github.ref_name }} \ No newline at end of file diff --git a/README.md b/README.md index 00a94f1..5f27d55 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # rCore-Tutorial-v3 rCore-Tutorial version 3.5. See the [Documentation in Chinese](https://rcore-os.github.io/rCore-Tutorial-Book-v3/). -rCore-Tutorial API Docs. See the [API Docs of Ten OSes ](https://rcore-os.github.io/rCore-Tutorial-v3/index.html) +rCore-Tutorial API Docs. See the [API Docs of Ten OSes ](#OS-API-DOCS) Official QQ group number: 735045051 @@ -194,6 +194,19 @@ Currently it can only help you view the code since only a tiny part of the code You can open a doc html of `os` using `cargo doc --no-deps --open` under `os` directory. +### OS-API-DOCS +The API Docs for Ten OS +1. [Lib-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch1/os/index.html) +1. [Batch-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch2/os/index.html) +1. [MultiProg-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch3-coop/os/index.html) +1. [TimeSharing-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch3/os/index.html) +1. [AddrSpace-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch4/os/index.html) +1. [Process-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch5/os/index.html) +1. [FileSystem-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch6/os/index.html) +1. [IPC-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch7/os/index.html) +1. [SyncMutex-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch8/os/index.html) +1. [IODevice-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch9/os/index.html) + ## Working in progress Our first release 3.5.0 (chapter 1-7) has been published. diff --git a/public/index.html b/public/index.html deleted file mode 100644 index f64ce52..0000000 --- a/public/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - -        - -       rcore-tutorial v3 - - - - -

The API docs for Ten rCore Tutorial OSes

-
-

1. LibOS API doc

-

2. BatchOS API doc

-

3.1 MultiProgOS API doc

-

3.2 TimeSharingOS API doc

-

4. AddrSpaceOS API doc

-

5. ProcessOS API doc

-

6. FileSystemOS API doc

-

7. IPCOS API doc

-

8. SyncMutexOS API doc

-

9. DeviceOS API doc

-

10. main-branch OS API doc

- - - From 9c51d69727fd0995a81054c54c3cd1612de833f8 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Mon, 4 Apr 2022 21:17:49 +0800 Subject: [PATCH 45/56] update README for debug info of OS --- README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/README.md b/README.md index 5f27d55..2dff8d7 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,44 @@ $ make run BOARD=k210 Type `Ctrl+]` to disconnect from K210. +## Show runtime debug info of OS kernel version +The branch of ch9-log contains a lot of debug info. You could try to run rcore tutorial +for understand the internal behavior of os kernel. + +```sh +$ git clone https://github.com/rcore-os/rCore-Tutorial-v3.git +$ cd rCore-Tutorial-v3/os +$ git checkout ch9-log +$ make run +...... +[rustsbi] RustSBI version 0.2.0-alpha.10, adapting to RISC-V SBI v0.3 +.______ __ __ _______.___________. _______..______ __ +| _ \ | | | | / | | / || _ \ | | +| |_) | | | | | | (----`---| |----`| (----`| |_) || | +| / | | | | \ \ | | \ \ | _ < | | +| |\ \----.| `--' |.----) | | | .----) | | |_) || | +| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__| + +[rustsbi] Implementation: RustSBI-QEMU Version 0.0.2 +[rustsbi-dtb] Hart count: cluster0 with 1 cores +[rustsbi] misa: RV64ACDFIMSU +[rustsbi] mideleg: ssoft, stimer, sext (0x222) +[rustsbi] medeleg: ima, ia, bkpt, la, sa, uecall, ipage, lpage, spage (0xb1ab) +[rustsbi] pmp0: 0x10000000 ..= 0x10001fff (rw-) +[rustsbi] pmp1: 0x2000000 ..= 0x200ffff (rw-) +[rustsbi] pmp2: 0xc000000 ..= 0xc3fffff (rw-) +[rustsbi] pmp3: 0x80000000 ..= 0x8fffffff (rwx) +[rustsbi] enter supervisor 0x80200000 +[KERN] rust_main() begin +[KERN] clear_bss() begin +[KERN] clear_bss() end +[KERN] mm::init() begin +[KERN] mm::init_heap() begin +[KERN] mm::init_heap() end +[KERN] mm::init_frame_allocator() begin +[KERN] mm::frame_allocator::lazy_static!FRAME_ALLOCATOR begin +...... +``` ## Rustdoc Currently it can only help you view the code since only a tiny part of the code has been documented. From db89fef127e06b29671f1f4cac50f16befc61495 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Sat, 16 Apr 2022 15:59:10 -0700 Subject: [PATCH 46/56] Bump Rust to nightly-2022-04-11 && support debugging in release mode --- .gitignore | 1 + easy-fs/Cargo.toml | 5 ++++- os/Cargo.toml | 3 +++ os/Makefile | 14 +++++++++++++- rust-toolchain | 2 +- user/Cargo.toml | 3 +++ 6 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index b99cdbb..80614cf 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ os/src/linker.ld os/last-* os/Cargo.lock os/last-* +os/.gdb_history user/target/* user/.idea/* user/Cargo.lock diff --git a/easy-fs/Cargo.toml b/easy-fs/Cargo.toml index 6e7cd92..c969077 100644 --- a/easy-fs/Cargo.toml +++ b/easy-fs/Cargo.toml @@ -8,4 +8,7 @@ edition = "2018" [dependencies] spin = "0.7.0" -lazy_static = { version = "1.4.0", features = ["spin_no_std"] } \ No newline at end of file +lazy_static = { version = "1.4.0", features = ["spin_no_std"] } + +[profile.release] +debug = true diff --git a/os/Cargo.toml b/os/Cargo.toml index b832716..752d717 100644 --- a/os/Cargo.toml +++ b/os/Cargo.toml @@ -22,3 +22,6 @@ easy-fs = { path = "../easy-fs" } [features] board_qemu = [] board_k210 = [] + +[profile.release] +debug = true diff --git a/os/Makefile b/os/Makefile index 3e5caad..741c81d 100644 --- a/os/Makefile +++ b/os/Makefile @@ -14,6 +14,11 @@ SBI ?= rustsbi BOOTLOADER := ../bootloader/$(SBI)-$(BOARD).bin K210_BOOTLOADER_SIZE := 131072 +# Building mode argument +ifeq ($(MODE), release) + MODE_ARG := --release +endif + # KERNEL ENTRY ifeq ($(BOARD), qemu) KERNEL_ENTRY_PA := 0x80200000 @@ -106,4 +111,11 @@ debug: build tmux split-window -h "riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234'" && \ tmux -2 attach-session -d -.PHONY: build env kernel clean disasm disasm-vim run-inner switch-check fs-img + +gdbserver: build + @qemu-system-riscv64 -machine virt -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) -s -S + +gdbclient: + @riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234' + +.PHONY: build env kernel clean disasm disasm-vim run-inner switch-check fs-img gdbserver gdbclient diff --git a/rust-toolchain b/rust-toolchain index 925656d..abcacd9 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2022-01-19 +nightly-2022-04-11 diff --git a/user/Cargo.toml b/user/Cargo.toml index f522c9e..542a624 100644 --- a/user/Cargo.toml +++ b/user/Cargo.toml @@ -10,3 +10,6 @@ edition = "2018" buddy_system_allocator = "0.6" bitflags = "1.2.1" riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] } + +[profile.release] +debug = true From 564212b68567e3709108672042144078230c7a61 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Fri, 22 Apr 2022 21:01:53 +0800 Subject: [PATCH 47/56] fix some typos Signed-off-by: cuishuang --- os/src/drivers/block/sdcard.rs | 2 +- os/src/drivers/chardev/ns16550a.rs | 4 ++-- user/src/bin/cat.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/os/src/drivers/block/sdcard.rs b/os/src/drivers/block/sdcard.rs index d736632..756e9a0 100644 --- a/os/src/drivers/block/sdcard.rs +++ b/os/src/drivers/block/sdcard.rs @@ -423,7 +423,7 @@ impl SDCard { /* Byte 15 */ CSD_CRC: (csd_tab[15] & 0xFE) >> 1, Reserved4: 1, - /* Return the reponse */ + /* Return the response */ }) } diff --git a/os/src/drivers/chardev/ns16550a.rs b/os/src/drivers/chardev/ns16550a.rs index 667a660..81e3878 100644 --- a/os/src/drivers/chardev/ns16550a.rs +++ b/os/src/drivers/chardev/ns16550a.rs @@ -12,7 +12,7 @@ use volatile::{ReadOnly, Volatile, WriteOnly}; bitflags! { /// InterruptEnableRegister pub struct IER: u8 { - const RX_AVALIABLE = 1 << 0; + const RX_AVAILABLE = 1 << 0; const TX_EMPTY = 1 << 1; } @@ -95,7 +95,7 @@ impl NS16550aRaw { mcr |= MCR::REQUEST_TO_SEND; mcr |= MCR::AUX_OUTPUT2; read_end.mcr.write(mcr); - let ier = IER::RX_AVALIABLE; + let ier = IER::RX_AVAILABLE; read_end.ier.write(ier); } diff --git a/user/src/bin/cat.rs b/user/src/bin/cat.rs index 9cbed23..91cc453 100644 --- a/user/src/bin/cat.rs +++ b/user/src/bin/cat.rs @@ -12,7 +12,7 @@ pub fn main(argc: usize, argv: &[&str]) -> i32 { assert!(argc == 2); let fd = open(argv[1], OpenFlags::RDONLY); if fd == -1 { - panic!("Error occured when opening file"); + panic!("Error occurred when opening file"); } let fd = fd as usize; let mut buf = [0u8; 256]; From ae6afe93cb989785f12115bf14833c99c8ce607c Mon Sep 17 00:00:00 2001 From: RunOS Date: Sat, 30 Apr 2022 21:07:37 +0800 Subject: [PATCH 48/56] virtaddr -> usize high 256GB addrspace bug fix --- os/src/mm/address.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/os/src/mm/address.rs b/os/src/mm/address.rs index dc5d08a..cf147a0 100644 --- a/os/src/mm/address.rs +++ b/os/src/mm/address.rs @@ -83,7 +83,11 @@ impl From for usize { } impl From for usize { fn from(v: VirtAddr) -> Self { - v.0 + if v.0 >= (1 << (VA_WIDTH_SV39 - 1)) { + v.0 | (!((1 << VA_WIDTH_SV39) - 1)) + } else { + v.0 + } } } impl From for usize { From 8d6a1bcc7945cf187509fee870557f606ebf183e Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Thu, 12 May 2022 23:14:42 -0700 Subject: [PATCH 49/56] Fix #71. --- os/src/task/mod.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/os/src/task/mod.rs b/os/src/task/mod.rs index 858848a..c3053f0 100644 --- a/os/src/task/mod.rs +++ b/os/src/task/mod.rs @@ -8,8 +8,9 @@ mod switch; #[allow(clippy::module_inception)] mod task; +use self::id::TaskUserRes; use crate::fs::{open_file, OpenFlags}; -use alloc::sync::Arc; +use alloc::{sync::Arc, vec::Vec}; use lazy_static::*; use manager::fetch_task; use process::ProcessControlBlock; @@ -90,12 +91,21 @@ pub fn exit_current_and_run_next(exit_code: i32) { // deallocate user res (including tid/trap_cx/ustack) of all threads // it has to be done before we dealloc the whole memory_set // otherwise they will be deallocated twice + let mut recycle_res = Vec::::new(); for task in process_inner.tasks.iter().filter(|t| t.is_some()) { let task = task.as_ref().unwrap(); let mut task_inner = task.inner_exclusive_access(); - task_inner.res = None; + if let Some(res) = task_inner.res.take() { + recycle_res.push(res); + } } + // dealloc_tid and dealloc_user_res require access to PCB inner, so we + // need to collect those user res first, then release process_inner + // for now to avoid deadlock/double borrow problem. + drop(process_inner); + recycle_res.clear(); + let mut process_inner = process.inner_exclusive_access(); process_inner.children.clear(); // deallocate other data in user space i.e. program code/data section process_inner.memory_set.recycle_data_pages(); From 040bba5f408cf699de7b86b49c317931753c0205 Mon Sep 17 00:00:00 2001 From: DeathWish5 Date: Fri, 13 May 2022 18:19:17 +0800 Subject: [PATCH 50/56] feat: CI run tests --- .github/workflows/build-doc.yml | 25 ----------------- .github/workflows/doc-and-test.yml | 45 ++++++++++++++++++++++++++++++ os/Makefile | 5 +++- os/src/sync/up.rs | 1 + os/src/task/id.rs | 2 ++ os/src/task/mod.rs | 10 +++++-- rust-toolchain | 2 +- user/Makefile | 6 ++++ user/src/bin/usertests.rs | 19 +++++++++---- 9 files changed, 81 insertions(+), 34 deletions(-) delete mode 100644 .github/workflows/build-doc.yml create mode 100644 .github/workflows/doc-and-test.yml diff --git a/.github/workflows/build-doc.yml b/.github/workflows/build-doc.yml deleted file mode 100644 index a48f147..0000000 --- a/.github/workflows/build-doc.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Build Rust Doc - -on: [push] - -env: - CARGO_TERM_COLOR: always - -jobs: - build-doc: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Build doc - run: | - rustup target add riscv64gc-unknown-none-elf - rustup component add llvm-tools-preview - rustup component add rust-src - cd os - cargo doc --no-deps --verbose - - name: Deploy to Github Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./os/target/riscv64gc-unknown-none-elf/doc - destination_dir: ${{ github.ref_name }} \ No newline at end of file diff --git a/.github/workflows/doc-and-test.yml b/.github/workflows/doc-and-test.yml new file mode 100644 index 0000000..2652471 --- /dev/null +++ b/.github/workflows/doc-and-test.yml @@ -0,0 +1,45 @@ +name: Build Rust Doc And Run tests + +on: [push] + +env: + CARGO_TERM_COLOR: always + +jobs: + build-doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build doc + run: | + rustup target add riscv64gc-unknown-none-elf + rustup component add llvm-tools-preview + rustup component add rust-src + cd os + cargo doc --no-deps --verbose + - name: Deploy to Github Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./os/target/riscv64gc-unknown-none-elf/doc + destination_dir: ${{ github.ref_name }} + run-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install QEMU + run: | + sudo apt-get update + sudo apt-get install ninja-build -y + [ ! -d qemu-6.1.0 ] && wget https://download.qemu.org/qemu-6.1.0.tar.xz \ + && tar xJf qemu-6.1.0.tar.xz > /dev/null \ + && cd qemu-6.1.0 && ./configure --target-list=riscv64-softmmu && cd .. + cd qemu-6.1.0 && sudo make install -j + qemu-system-riscv64 --version + - name: Run usertests + run: | + cd os && make run TEST=1 + + + + \ No newline at end of file diff --git a/os/Makefile b/os/Makefile index 741c81d..0601e39 100644 --- a/os/Makefile +++ b/os/Makefile @@ -37,6 +37,9 @@ OBJCOPY := rust-objcopy --binary-architecture=riscv64 # Disassembly DISASM ?= -x +# Run usertests or usershell +TEST ?= + build: env switch-check $(KERNEL_BIN) fs-img switch-check: @@ -61,7 +64,7 @@ $(KERNEL_BIN): kernel @$(OBJCOPY) $(KERNEL_ELF) --strip-all -O binary $@ fs-img: $(APPS) - @cd ../user && make build + @cd ../user && make build TEST=$(TEST) @rm -f $(FS_IMG) @cd ../easy-fs-fuse && cargo run --release -- -s ../user/src/bin/ -t ../user/target/riscv64gc-unknown-none-elf/release/ diff --git a/os/src/sync/up.rs b/os/src/sync/up.rs index 55c8d40..3600271 100644 --- a/os/src/sync/up.rs +++ b/os/src/sync/up.rs @@ -101,6 +101,7 @@ impl UPIntrFreeCell { inner: RefCell::new(value), } } + /// Panic if the data has been borrowed. pub fn exclusive_access(&self) -> UPIntrRefMut<'_, T> { INTR_MASKING_INFO.get_mut().enter(); diff --git a/os/src/task/id.rs b/os/src/task/id.rs index e336c50..c177181 100644 --- a/os/src/task/id.rs +++ b/os/src/task/id.rs @@ -46,6 +46,8 @@ lazy_static! { unsafe { UPIntrFreeCell::new(RecycleAllocator::new()) }; } +pub const IDLE_PID: usize = 0; + pub struct PidHandle(pub usize); pub fn pid_alloc() -> PidHandle { diff --git a/os/src/task/mod.rs b/os/src/task/mod.rs index c3053f0..6b4082b 100644 --- a/os/src/task/mod.rs +++ b/os/src/task/mod.rs @@ -17,7 +17,7 @@ use process::ProcessControlBlock; use switch::__switch; pub use context::TaskContext; -pub use id::{kstack_alloc, pid_alloc, KernelStack, PidHandle}; +pub use id::{kstack_alloc, pid_alloc, KernelStack, PidHandle, IDLE_PID}; pub use manager::{add_task, pid2process, remove_from_pid2process}; pub use processor::{ current_kstack_top, current_process, current_task, current_trap_cx, current_trap_cx_user_va, @@ -26,6 +26,7 @@ pub use processor::{ pub use signal::SignalFlags; pub use task::{TaskControlBlock, TaskStatus}; + pub fn suspend_current_and_run_next() { // There must be an application running. let task = take_current_task().unwrap(); @@ -72,7 +73,12 @@ pub fn exit_current_and_run_next(exit_code: i32) { // however, if this is the main thread of current process // the process should terminate at once if tid == 0 { - remove_from_pid2process(process.getpid()); + let pid = process.getpid(); + if pid == IDLE_PID { + println!("[kernel] Idle process exit ..."); + crate::sbi::shutdown(); + } + remove_from_pid2process(pid); let mut process_inner = process.inner_exclusive_access(); // mark this process as a zombie process process_inner.is_zombie = true; diff --git a/rust-toolchain b/rust-toolchain index abcacd9..281657a 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2022-04-11 +nightly-2022-04-12 diff --git a/user/Makefile b/user/Makefile index e2eaf99..c09b5e9 100644 --- a/user/Makefile +++ b/user/Makefile @@ -8,9 +8,15 @@ BINS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%.bin, $(APPS)) OBJDUMP := rust-objdump --arch-name=riscv64 OBJCOPY := rust-objcopy --binary-architecture=riscv64 +CP := cp + +TEST ?= elf: $(APPS) @cargo build --release +ifeq ($(TEST), 1) + @$(CP) $(TARGET_DIR)/usertests $(TARGET_DIR)/initproc +endif binary: elf $(foreach elf, $(ELFS), $(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf));) diff --git a/user/src/bin/usertests.rs b/user/src/bin/usertests.rs index 4fd49a4..89f3ce5 100644 --- a/user/src/bin/usertests.rs +++ b/user/src/bin/usertests.rs @@ -4,7 +4,7 @@ #[macro_use] extern crate user_lib; -static TESTS: &[&str] = &[ +static SUCC_TESTS: &[&str] = &[ "exit\0", "fantastic_text\0", "forktest\0", @@ -14,15 +14,17 @@ static TESTS: &[&str] = &[ "matrix\0", "sleep\0", "sleep_simple\0", - "stack_overflow\0", "yield\0", ]; +static FAIL_TESTS: &[&str] = &[ + "stack_overflow\0", +]; + use user_lib::{exec, fork, waitpid}; -#[no_mangle] -pub fn main() -> i32 { - for test in TESTS { +fn run_tests(tests: &[&str], judge: F) { + for test in tests { println!("Usertests: Running {}", test); let pid = fork(); if pid == 0 { @@ -32,12 +34,19 @@ pub fn main() -> i32 { let mut exit_code: i32 = Default::default(); let wait_pid = waitpid(pid as usize, &mut exit_code); assert_eq!(pid, wait_pid); + judge(exit_code); println!( "\x1b[32mUsertests: Test {} in Process {} exited with code {}\x1b[0m", test, pid, exit_code ); } } +} + +#[no_mangle] +pub fn main() -> i32 { + run_tests(SUCC_TESTS, |code| assert!(code == 0)); + run_tests(FAIL_TESTS, |code| assert!(code != 0)); println!("Usertests passed!"); 0 } From babcd45c9cdb89c688a44cce45b5f41b781a4263 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sat, 14 May 2022 22:53:05 +0800 Subject: [PATCH 51/56] rust-toolchain nightly-2020-04-11 --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index 281657a..abcacd9 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2022-04-12 +nightly-2022-04-11 From 4e2436f757e6a7710fc44f43baf122040ab2ba21 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sat, 14 May 2022 22:53:45 +0800 Subject: [PATCH 52/56] cargo fmt --- os/src/drivers/block/virtio_blk.rs | 3 ++- os/src/drivers/chardev/ns16550a.rs | 1 - os/src/main.rs | 3 ++- os/src/sync/condvar.rs | 5 ++++- os/src/sync/up.rs | 23 ++++++++++++++--------- os/src/task/mod.rs | 3 +-- os/src/task/processor.rs | 8 ++++---- os/src/task/task.rs | 5 ++++- os/src/trap/mod.rs | 14 +++++++------- user/src/bin/huge_write_mt.rs | 16 ++++++++-------- user/src/bin/usertests.rs | 4 +--- 11 files changed, 47 insertions(+), 38 deletions(-) diff --git a/os/src/drivers/block/virtio_blk.rs b/os/src/drivers/block/virtio_blk.rs index a30e3bf..75e69c1 100644 --- a/os/src/drivers/block/virtio_blk.rs +++ b/os/src/drivers/block/virtio_blk.rs @@ -20,7 +20,8 @@ pub struct VirtIOBlock { } lazy_static! { - static ref QUEUE_FRAMES: UPIntrFreeCell> = unsafe { UPIntrFreeCell::new(Vec::new()) }; + static ref QUEUE_FRAMES: UPIntrFreeCell> = + unsafe { UPIntrFreeCell::new(Vec::new()) }; } impl BlockDevice for VirtIOBlock { diff --git a/os/src/drivers/chardev/ns16550a.rs b/os/src/drivers/chardev/ns16550a.rs index 81e3878..da29063 100644 --- a/os/src/drivers/chardev/ns16550a.rs +++ b/os/src/drivers/chardev/ns16550a.rs @@ -1,7 +1,6 @@ ///! Ref: https://www.lammertbies.nl/comm/info/serial-uart ///! Ref: ns16550a datasheet: https://datasheetspdf.com/pdf-file/605590/NationalSemiconductor/NS16550A/1 ///! Ref: ns16450 datasheet: https://datasheetspdf.com/pdf-file/1311818/NationalSemiconductor/NS16450/1 - use super::CharDevice; use crate::sync::{Condvar, UPIntrFreeCell}; use crate::task::schedule; diff --git a/os/src/main.rs b/os/src/main.rs index a9323be..3c23069 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -46,7 +46,8 @@ use lazy_static::*; use sync::UPIntrFreeCell; lazy_static! { - pub static ref DEV_NON_BLOCKING_ACCESS: UPIntrFreeCell = unsafe { UPIntrFreeCell::new(false) }; + pub static ref DEV_NON_BLOCKING_ACCESS: UPIntrFreeCell = + unsafe { UPIntrFreeCell::new(false) }; } #[no_mangle] diff --git a/os/src/sync/condvar.rs b/os/src/sync/condvar.rs index c50a53e..714782c 100644 --- a/os/src/sync/condvar.rs +++ b/os/src/sync/condvar.rs @@ -1,5 +1,8 @@ use crate::sync::{Mutex, UPIntrFreeCell}; -use crate::task::{add_task, block_current_task, block_current_and_run_next, current_task, TaskControlBlock, TaskContext}; +use crate::task::{ + add_task, block_current_and_run_next, block_current_task, current_task, TaskContext, + TaskControlBlock, +}; use alloc::{collections::VecDeque, sync::Arc}; pub struct Condvar { diff --git a/os/src/sync/up.rs b/os/src/sync/up.rs index 3600271..6c7a0f4 100644 --- a/os/src/sync/up.rs +++ b/os/src/sync/up.rs @@ -1,7 +1,7 @@ use core::cell::{RefCell, RefMut, UnsafeCell}; use core::ops::{Deref, DerefMut}; -use riscv::register::sstatus; use lazy_static::*; +use riscv::register::sstatus; /* /// Wrap a static data structure inside it so that we are @@ -56,9 +56,8 @@ pub struct IntrMaskingInfo { } lazy_static! { - static ref INTR_MASKING_INFO: UPSafeCellRaw = unsafe { - UPSafeCellRaw::new(IntrMaskingInfo::new()) - }; + static ref INTR_MASKING_INFO: UPSafeCellRaw = + unsafe { UPSafeCellRaw::new(IntrMaskingInfo::new()) }; } impl IntrMaskingInfo { @@ -71,7 +70,9 @@ impl IntrMaskingInfo { pub fn enter(&mut self) { let sie = sstatus::read().sie(); - unsafe { sstatus::clear_sie(); } + unsafe { + sstatus::clear_sie(); + } if self.nested_level == 0 { self.sie_before_masking = sie; } @@ -81,7 +82,9 @@ impl IntrMaskingInfo { pub fn exit(&mut self) { self.nested_level -= 1; if self.nested_level == 0 && self.sie_before_masking { - unsafe { sstatus::set_sie(); } + unsafe { + sstatus::set_sie(); + } } } } @@ -101,14 +104,17 @@ impl UPIntrFreeCell { inner: RefCell::new(value), } } - + /// Panic if the data has been borrowed. pub fn exclusive_access(&self) -> UPIntrRefMut<'_, T> { INTR_MASKING_INFO.get_mut().enter(); UPIntrRefMut(Some(self.inner.borrow_mut())) } - pub fn exclusive_session(&self, f: F) -> V where F: FnOnce(&mut T) -> V { + pub fn exclusive_session(&self, f: F) -> V + where + F: FnOnce(&mut T) -> V, + { let mut inner = self.exclusive_access(); f(inner.deref_mut()) } @@ -132,4 +138,3 @@ impl<'a, T> DerefMut for UPIntrRefMut<'a, T> { self.0.as_mut().unwrap().deref_mut() } } - diff --git a/os/src/task/mod.rs b/os/src/task/mod.rs index 6b4082b..9f8e3b1 100644 --- a/os/src/task/mod.rs +++ b/os/src/task/mod.rs @@ -26,7 +26,6 @@ pub use processor::{ pub use signal::SignalFlags; pub use task::{TaskControlBlock, TaskStatus}; - pub fn suspend_current_and_run_next() { // There must be an application running. let task = take_current_task().unwrap(); @@ -54,7 +53,7 @@ pub fn block_current_task() -> *mut TaskContext { } pub fn block_current_and_run_next() { - let task_cx_ptr = block_current_task(); + let task_cx_ptr = block_current_task(); schedule(task_cx_ptr); } diff --git a/os/src/task/processor.rs b/os/src/task/processor.rs index f196f68..dfa7d4c 100644 --- a/os/src/task/processor.rs +++ b/os/src/task/processor.rs @@ -30,7 +30,8 @@ impl Processor { } lazy_static! { - pub static ref PROCESSOR: UPIntrFreeCell = unsafe { UPIntrFreeCell::new(Processor::new()) }; + pub static ref PROCESSOR: UPIntrFreeCell = + unsafe { UPIntrFreeCell::new(Processor::new()) }; } pub fn run_tasks() { @@ -94,9 +95,8 @@ pub fn current_kstack_top() -> usize { } pub fn schedule(switched_task_cx_ptr: *mut TaskContext) { - let idle_task_cx_ptr = PROCESSOR.exclusive_session(|processor| { - processor.get_idle_task_cx_ptr() - }); + let idle_task_cx_ptr = + PROCESSOR.exclusive_session(|processor| processor.get_idle_task_cx_ptr()); unsafe { __switch(switched_task_cx_ptr, idle_task_cx_ptr); } diff --git a/os/src/task/task.rs b/os/src/task/task.rs index 85d949f..c620890 100644 --- a/os/src/task/task.rs +++ b/os/src/task/task.rs @@ -1,7 +1,10 @@ use super::id::TaskUserRes; use super::{kstack_alloc, KernelStack, ProcessControlBlock, TaskContext}; use crate::trap::TrapContext; -use crate::{mm::PhysPageNum, sync::{UPIntrFreeCell, UPIntrRefMut}}; +use crate::{ + mm::PhysPageNum, + sync::{UPIntrFreeCell, UPIntrRefMut}, +}; use alloc::sync::{Arc, Weak}; pub struct TaskControlBlock { diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index 5eb417d..ce2aae8 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -11,7 +11,7 @@ use core::arch::{asm, global_asm}; use riscv::register::{ mtvec::TrapMode, scause::{self, Exception, Interrupt, Trap}, - sie, stval, stvec, sstatus, sscratch, + sie, sscratch, sstatus, stval, stvec, }; global_asm!(include_str!("trap.S")); @@ -23,7 +23,7 @@ pub fn init() { fn set_kernel_trap_entry() { extern "C" { fn __alltraps(); - fn __alltraps_k(); + fn __alltraps_k(); } let __alltraps_k_va = __alltraps_k as usize - __alltraps as usize + TRAMPOLINE; unsafe { @@ -52,7 +52,7 @@ fn enable_supervisor_interrupt() { fn disable_supervisor_interrupt() { unsafe { - sstatus::clear_sie(); + sstatus::clear_sie(); } } @@ -67,7 +67,7 @@ pub fn trap_handler() -> ! { // jump to next instruction anyway let mut cx = current_trap_cx(); cx.sepc += 4; - + enable_supervisor_interrupt(); // get system call return value @@ -150,19 +150,19 @@ pub fn trap_from_kernel(_trap_cx: &TrapContext) { match scause.cause() { Trap::Interrupt(Interrupt::SupervisorExternal) => { crate::board::irq_handler(); - }, + } Trap::Interrupt(Interrupt::SupervisorTimer) => { set_next_trigger(); check_timer(); // do not schedule now - }, + } _ => { panic!( "Unsupported trap from kernel: {:?}, stval = {:#x}!", scause.cause(), stval ); - }, + } } } diff --git a/user/src/bin/huge_write_mt.rs b/user/src/bin/huge_write_mt.rs index 63146a5..8ca7578 100644 --- a/user/src/bin/huge_write_mt.rs +++ b/user/src/bin/huge_write_mt.rs @@ -5,9 +5,9 @@ extern crate user_lib; extern crate alloc; -use alloc::{vec::Vec, string::String, fmt::format}; +use alloc::{fmt::format, string::String, vec::Vec}; +use user_lib::{close, get_time, gettid, open, write, OpenFlags}; use user_lib::{exit, thread_create, waittid}; -use user_lib::{close, get_time, open, write, OpenFlags, gettid}; fn worker(size_kib: usize) { let mut buffer = [0u8; 1024]; // 1KiB @@ -17,11 +17,11 @@ fn worker(size_kib: usize) { let filename = format(format_args!("testf{}\0", gettid())); let f = open(filename.as_str(), OpenFlags::CREATE | OpenFlags::WRONLY); if f < 0 { - panic!("Open test file failed!"); + panic!("Open test file failed!"); } let f = f as usize; for _ in 0..size_kib { - write(f, &buffer); + write(f, &buffer); } close(f); exit(0) @@ -34,18 +34,18 @@ pub fn main(argc: usize, argv: &[&str]) -> i32 { let size_kb = size_mb << 10; let workers = argv[1].parse::().expect("wrong argument"); assert!(workers >= 1 && size_kb % workers == 0, "wrong argument"); - + let start = get_time(); - + let mut v = Vec::new(); let size_mb = 1usize; for _ in 0..workers { - v.push(thread_create(worker as usize, size_kb / workers)); + v.push(thread_create(worker as usize, size_kb / workers)); } for tid in v.iter() { assert_eq!(0, waittid(*tid as usize)); } - + let time_ms = (get_time() - start) as usize; let speed_kbs = size_kb * 1000 / time_ms; println!( diff --git a/user/src/bin/usertests.rs b/user/src/bin/usertests.rs index 89f3ce5..32b28f6 100644 --- a/user/src/bin/usertests.rs +++ b/user/src/bin/usertests.rs @@ -17,9 +17,7 @@ static SUCC_TESTS: &[&str] = &[ "yield\0", ]; -static FAIL_TESTS: &[&str] = &[ - "stack_overflow\0", -]; +static FAIL_TESTS: &[&str] = &["stack_overflow\0"]; use user_lib::{exec, fork, waitpid}; From e3467d391d0c669de1174bf04267d3530f4147fd Mon Sep 17 00:00:00 2001 From: YdrMaster Date: Sat, 14 May 2022 23:44:09 +0800 Subject: [PATCH 53/56] ci: cache qemu update: .gitignore, .vscode/settings.json --- .github/workflows/doc-and-test.yml | 56 ++++++++++++++++++++---------- .gitignore | 19 ++++------ .vscode/settings.json | 10 ++++++ 3 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.github/workflows/doc-and-test.yml b/.github/workflows/doc-and-test.yml index 2652471..91927a3 100644 --- a/.github/workflows/doc-and-test.yml +++ b/.github/workflows/doc-and-test.yml @@ -9,37 +9,57 @@ jobs: build-doc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2022-04-11 + components: rust-src, llvm-tools-preview + target: riscv64gc-unknown-none-elf - name: Build doc - run: | - rustup target add riscv64gc-unknown-none-elf - rustup component add llvm-tools-preview - rustup component add rust-src - cd os - cargo doc --no-deps --verbose + run: cd os && cargo doc --no-deps --verbose - name: Deploy to Github Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./os/target/riscv64gc-unknown-none-elf/doc destination_dir: ${{ github.ref_name }} + run-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2022-04-11 + components: rust-src, llvm-tools-preview + target: riscv64gc-unknown-none-elf + - uses: actions-rs/install@v0.1 + with: + crate: cargo-binutils + version: latest + use-tool-cache: true + - name: Cache QEMU + uses: actions/cache@v3 + with: + path: qemu-7.0.0 + key: qemu-7.0.0-x86_64-riscv64 - name: Install QEMU run: | sudo apt-get update sudo apt-get install ninja-build -y - [ ! -d qemu-6.1.0 ] && wget https://download.qemu.org/qemu-6.1.0.tar.xz \ - && tar xJf qemu-6.1.0.tar.xz > /dev/null \ - && cd qemu-6.1.0 && ./configure --target-list=riscv64-softmmu && cd .. - cd qemu-6.1.0 && sudo make install -j + if [ ! -d qemu-7.0.0 ]; then + wget https://download.qemu.org/qemu-7.0.0.tar.xz + tar -xf qemu-7.0.0.tar.xz + cd qemu-7.0.0 + ./configure --target-list=riscv64-softmmu + make -j + else + cd qemu-7.0.0 + fi + sudo make install qemu-system-riscv64 --version - - name: Run usertests - run: | - cd os && make run TEST=1 - - - \ No newline at end of file + - name: Run usertests + run: cd os && make run TEST=1 diff --git a/.gitignore b/.gitignore index 80614cf..f411b22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,13 @@ -.idea/* -os/target/* -os/.idea/* +.*/* +!.github/* +!.vscode/settings.json + +**/target/ +**/Cargo.lock + os/src/link_app.S os/src/linker.ld os/last-* -os/Cargo.lock -os/last-* os/.gdb_history -user/target/* -user/.idea/* -user/Cargo.lock -easy-fs/Cargo.lock -easy-fs/target/* -easy-fs-fuse/Cargo.lock -easy-fs-fuse/target/* tools/ pushall.sh diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..11de111 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + // Prevent "can't find crate for `test`" error on no_std + // Ref: https://github.com/rust-lang/vscode-rust/issues/729 + // For vscode-rust plugin users: + "rust.target": "riscv64gc-unknown-none-elf", + "rust.all_targets": false, + // For Rust Analyzer plugin users: + "rust-analyzer.cargo.target": "riscv64gc-unknown-none-elf", + "rust-analyzer.checkOnSave.allTargets": false +} From a67aa60ceb15181a27df39925e96e6606c6233b1 Mon Sep 17 00:00:00 2001 From: yuoo655 Date: Sun, 15 May 2022 08:29:27 +0800 Subject: [PATCH 54/56] add ci tests update usertests --- .github/workflows/doc-and-test.yml | 57 ++++++++++++++++++++---------- .gitignore | 19 ++++------ .vscode/settings.json | 10 ++++++ user/src/bin/usertests.rs | 35 +++++++++++++++--- 4 files changed, 87 insertions(+), 34 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.github/workflows/doc-and-test.yml b/.github/workflows/doc-and-test.yml index 2652471..e065b69 100644 --- a/.github/workflows/doc-and-test.yml +++ b/.github/workflows/doc-and-test.yml @@ -9,37 +9,58 @@ jobs: build-doc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2022-04-11 + components: rust-src, llvm-tools-preview + target: riscv64gc-unknown-none-elf - name: Build doc - run: | - rustup target add riscv64gc-unknown-none-elf - rustup component add llvm-tools-preview - rustup component add rust-src - cd os - cargo doc --no-deps --verbose + run: cd os && cargo doc --no-deps --verbose - name: Deploy to Github Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./os/target/riscv64gc-unknown-none-elf/doc destination_dir: ${{ github.ref_name }} + run-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2022-04-11 + components: rust-src, llvm-tools-preview + target: riscv64gc-unknown-none-elf + - uses: actions-rs/install@v0.1 + with: + crate: cargo-binutils + version: latest + use-tool-cache: true + - name: Cache QEMU + uses: actions/cache@v3 + with: + path: qemu-7.0.0 + key: qemu-7.0.0-x86_64-riscv64 - name: Install QEMU run: | sudo apt-get update sudo apt-get install ninja-build -y - [ ! -d qemu-6.1.0 ] && wget https://download.qemu.org/qemu-6.1.0.tar.xz \ - && tar xJf qemu-6.1.0.tar.xz > /dev/null \ - && cd qemu-6.1.0 && ./configure --target-list=riscv64-softmmu && cd .. - cd qemu-6.1.0 && sudo make install -j + if [ ! -d qemu-7.0.0 ]; then + wget https://download.qemu.org/qemu-7.0.0.tar.xz + tar -xf qemu-7.0.0.tar.xz + cd qemu-7.0.0 + ./configure --target-list=riscv64-softmmu + make -j + else + cd qemu-7.0.0 + fi + sudo make install qemu-system-riscv64 --version - - name: Run usertests - run: | - cd os && make run TEST=1 - - - \ No newline at end of file + - name: Run usertests + run: cd os && make run TEST=1 + timeout-minutes: 10 diff --git a/.gitignore b/.gitignore index 80614cf..f411b22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,13 @@ -.idea/* -os/target/* -os/.idea/* +.*/* +!.github/* +!.vscode/settings.json + +**/target/ +**/Cargo.lock + os/src/link_app.S os/src/linker.ld os/last-* -os/Cargo.lock -os/last-* os/.gdb_history -user/target/* -user/.idea/* -user/Cargo.lock -easy-fs/Cargo.lock -easy-fs/target/* -easy-fs-fuse/Cargo.lock -easy-fs-fuse/target/* tools/ pushall.sh diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..11de111 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + // Prevent "can't find crate for `test`" error on no_std + // Ref: https://github.com/rust-lang/vscode-rust/issues/729 + // For vscode-rust plugin users: + "rust.target": "riscv64gc-unknown-none-elf", + "rust.all_targets": false, + // For Rust Analyzer plugin users: + "rust-analyzer.cargo.target": "riscv64gc-unknown-none-elf", + "rust-analyzer.checkOnSave.allTargets": false +} diff --git a/user/src/bin/usertests.rs b/user/src/bin/usertests.rs index 32b28f6..7d104d2 100644 --- a/user/src/bin/usertests.rs +++ b/user/src/bin/usertests.rs @@ -5,19 +5,46 @@ extern crate user_lib; static SUCC_TESTS: &[&str] = &[ + "matrix\0", "exit\0", "fantastic_text\0", + "filetest_simple\0", + "forktest_simple\0", "forktest\0", "forktest2\0", - "forktest_simple\0", + "forktree\0", "hello_world\0", - "matrix\0", + "huge_write\0", + "mpsc_sem\0", + "phil_din_mutex\0", + "pipe_large_test\0", + "pipetest\0", + "race_adder_atomic\0", + "race_adder_mutex_blocking\0", + "race_adder_mutex_spin\0", + "race_adder_arg\0", + "sleep_simple\0", "sleep\0", "sleep_simple\0", + "sync_sem\0", + "test_condvar\0", + "threads_arg\0", + "threads\0", "yield\0", + "run_pipe_test\0", +]; + +static FAIL_TESTS: &[&str] = &[ + "stack_overflow\0", + "race_adder_loop\0", + "priv_csr\0", + "priv_inst\0", + "store_fault\0", + "until_timeout\0", + "stack_overflow\0", + "race_adder\0", + "huge_write_mt\0", ]; - -static FAIL_TESTS: &[&str] = &["stack_overflow\0"]; use user_lib::{exec, fork, waitpid}; From 80503e80eef4ee83752c6eab067c020d54339ad5 Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sun, 15 May 2022 12:03:05 +0800 Subject: [PATCH 55/56] update github CI for autotest --- os/src/lang_items.rs | 2 +- os/src/sbi.rs | 2 +- os/src/task/mod.rs | 11 ++- user/src/bin/race_adder_arg.rs | 5 +- user/src/bin/run_pipe_test.rs | 2 +- user/src/bin/usertests.rs | 148 ++++++++++++++++++++++----------- 6 files changed, 115 insertions(+), 55 deletions(-) diff --git a/os/src/lang_items.rs b/os/src/lang_items.rs index 6788148..a33943a 100644 --- a/os/src/lang_items.rs +++ b/os/src/lang_items.rs @@ -18,7 +18,7 @@ fn panic(info: &PanicInfo) -> ! { unsafe { backtrace(); } - shutdown() + shutdown(255) } unsafe fn backtrace() { diff --git a/os/src/sbi.rs b/os/src/sbi.rs index 75b2f43..a402b91 100644 --- a/os/src/sbi.rs +++ b/os/src/sbi.rs @@ -39,7 +39,7 @@ pub fn console_getchar() -> usize { sbi_call(SBI_CONSOLE_GETCHAR, 0, 0, 0) } -pub fn shutdown() -> ! { +pub fn shutdown(exit_code: usize) -> ! { sbi_call(SBI_SHUTDOWN, 0, 0, 0); panic!("It should shutdown!"); } diff --git a/os/src/task/mod.rs b/os/src/task/mod.rs index 9f8e3b1..07ebc81 100644 --- a/os/src/task/mod.rs +++ b/os/src/task/mod.rs @@ -74,8 +74,15 @@ pub fn exit_current_and_run_next(exit_code: i32) { if tid == 0 { let pid = process.getpid(); if pid == IDLE_PID { - println!("[kernel] Idle process exit ..."); - crate::sbi::shutdown(); + println!( + "[kernel] Idle process exit with exit_code {} ...", + exit_code + ); + if exit_code != 0 { + crate::sbi::shutdown(255); //255 == -1 for err hint + } else { + crate::sbi::shutdown(0); //0 for success hint + } } remove_from_pid2process(pid); let mut process_inner = process.inner_exclusive_access(); diff --git a/user/src/bin/race_adder_arg.rs b/user/src/bin/race_adder_arg.rs index 7c8b707..ba99b62 100644 --- a/user/src/bin/race_adder_arg.rs +++ b/user/src/bin/race_adder_arg.rs @@ -34,7 +34,10 @@ pub fn main(argc: usize, argv: &[&str]) -> i32 { } else if argc == 2 { count = argv[1].to_string().parse::().unwrap(); } else { - println!("ERROR in argv"); + println!( + "ERROR in argv, argc is {}, argv[0] {} , argv[1] {} , argv[2] {}", + argc, argv[0], argv[1], argv[2] + ); exit(-1); } diff --git a/user/src/bin/run_pipe_test.rs b/user/src/bin/run_pipe_test.rs index ea99b6a..5f50b0d 100644 --- a/user/src/bin/run_pipe_test.rs +++ b/user/src/bin/run_pipe_test.rs @@ -8,7 +8,7 @@ use user_lib::{exec, fork, wait}; #[no_mangle] pub fn main() -> i32 { - for i in 0..1000 { + for i in 0..50 { if fork() == 0 { exec("pipe_large_test\0", &[core::ptr::null::()]); } else { diff --git a/user/src/bin/usertests.rs b/user/src/bin/usertests.rs index 7d104d2..0a8ca1c 100644 --- a/user/src/bin/usertests.rs +++ b/user/src/bin/usertests.rs @@ -3,75 +3,125 @@ #[macro_use] extern crate user_lib; - -static SUCC_TESTS: &[&str] = &[ - "matrix\0", - "exit\0", - "fantastic_text\0", - "filetest_simple\0", - "forktest_simple\0", - "forktest\0", - "forktest2\0", - "forktree\0", - "hello_world\0", - "huge_write\0", - "mpsc_sem\0", - "phil_din_mutex\0", - "pipe_large_test\0", - "pipetest\0", - "race_adder_atomic\0", - "race_adder_mutex_blocking\0", - "race_adder_mutex_spin\0", - "race_adder_arg\0", - "sleep_simple\0", - "sleep\0", - "sleep_simple\0", - "sync_sem\0", - "test_condvar\0", - "threads_arg\0", - "threads\0", - "yield\0", - "run_pipe_test\0", +// item of TESTS : app_name(argv_0), argv_1, argv_2, argv_3, exit_code +static SUCC_TESTS: &[(&str, &str, &str, &str, i32)] = &[ + ("cmdline_args\0", "1\0", "2\0", "\0", 0), + ("cmdline_args\0", "1\0", "2\0", "3\0", 0), + ("matrix\0", "\0", "\0", "\0", 0), + ("exit\0", "\0", "\0", "\0", 0), + ("fantastic_text\0", "\0", "\0", "\0", 0), + ("filetest_simple\0", "\0", "\0", "\0", 0), + ("forktest_simple\0", "\0", "\0", "\0", 0), + ("forktest\0", "\0", "\0", "\0", 0), + ("forktest2\0", "\0", "\0", "\0", 0), + ("forktree\0", "\0", "\0", "\0", 0), + ("hello_world\0", "\0", "\0", "\0", 0), + ("huge_write\0", "\0", "\0", "\0", 0), + ("mpsc_sem\0", "\0", "\0", "\0", 0), + ("phil_din_mutex\0", "\0", "\0", "\0", 0), + ("pipe_large_test\0", "\0", "\0", "\0", 0), + ("pipetest\0", "\0", "\0", "\0", 0), + ("race_adder_atomic\0", "\0", "\0", "\0", 0), + ("race_adder_mutex_blocking\0", "\0", "\0", "\0", 0), + ("race_adder_mutex_spin\0", "\0", "\0", "\0", 0), + ("race_adder_arg\0", "3\0", "\0", "\0", 0), + ("sleep_simple\0", "\0", "\0", "\0", 0), + ("sleep\0", "\0", "\0", "\0", 0), + ("sleep_simple\0", "\0", "\0", "\0", 0), + ("sync_sem\0", "\0", "\0", "\0", 0), + ("test_condvar\0", "\0", "\0", "\0", 0), + ("threads_arg\0", "\0", "\0", "\0", 0), + ("threads\0", "\0", "\0", "\0", 0), + ("yield\0", "\0", "\0", "\0", 0), + ("run_pipe_test\0", "\0", "\0", "\0", 0), ]; - -static FAIL_TESTS: &[&str] = &[ - "stack_overflow\0", - "race_adder_loop\0", - "priv_csr\0", - "priv_inst\0", - "store_fault\0", - "until_timeout\0", - "stack_overflow\0", - "race_adder\0", - "huge_write_mt\0", + +static FAIL_TESTS: &[(&str, &str, &str, &str, i32)] = &[ + ("stack_overflow\0", "\0", "\0", "\0", -11), + ("race_adder_loop\0", "\0", "\0", "\0", -6), + ("priv_csr\0", "\0", "\0", "\0", -4), + ("priv_inst\0", "\0", "\0", "\0", -4), + ("store_fault\0", "\0", "\0", "\0", -11), + ("until_timeout\0", "\0", "\0", "\0", -6), + ("race_adder\0", "\0", "\0", "\0", -6), + ("huge_write_mt\0", "\0", "\0", "\0", -6), ]; use user_lib::{exec, fork, waitpid}; -fn run_tests(tests: &[&str], judge: F) { +fn run_tests(tests: &[(&str, &str, &str, &str, i32)]) -> i32 { + let mut pass_num = 0; + let mut arr: [*const u8; 3] = [ + core::ptr::null::(), + core::ptr::null::(), + core::ptr::null::(), + ]; + for test in tests { - println!("Usertests: Running {}", test); + println!("Usertests: Running {}", test.0); + + if test.1 != "\0" { + arr[0] = test.1.as_ptr(); + if test.2 != "\0" { + arr[1] = test.2.as_ptr(); + if test.3 != "\0" { + arr[2] = test.3.as_ptr(); + } else { + arr[2] = core::ptr::null::(); + } + } else { + arr[1] = core::ptr::null::(); + arr[2] = core::ptr::null::(); + } + } else { + arr[0] = core::ptr::null::(); + arr[1] = core::ptr::null::(); + arr[2] = core::ptr::null::(); + } + let pid = fork(); if pid == 0 { - exec(*test, &[core::ptr::null::()]); + exec(test.0, &arr[..]); panic!("unreachable!"); } else { let mut exit_code: i32 = Default::default(); let wait_pid = waitpid(pid as usize, &mut exit_code); assert_eq!(pid, wait_pid); - judge(exit_code); + if exit_code == test.4 { + // summary apps with exit_code + pass_num = pass_num + 1; + } println!( "\x1b[32mUsertests: Test {} in Process {} exited with code {}\x1b[0m", - test, pid, exit_code + test.0, pid, exit_code ); } } + pass_num } #[no_mangle] pub fn main() -> i32 { - run_tests(SUCC_TESTS, |code| assert!(code == 0)); - run_tests(FAIL_TESTS, |code| assert!(code != 0)); - println!("Usertests passed!"); - 0 + let succ_num = run_tests(SUCC_TESTS); + let err_num = run_tests(FAIL_TESTS); + if succ_num == SUCC_TESTS.len() as i32 && err_num == FAIL_TESTS.len() as i32 { + println!("Usertests passed!"); + return 0; + } + if succ_num != SUCC_TESTS.len() as i32 { + println!( + "all successed app_num is {} , but only passed {}", + SUCC_TESTS.len(), + succ_num + ); + } + if err_num != FAIL_TESTS.len() as i32 { + println!( + "all failed app_num is {} , but only passed {}", + FAIL_TESTS.len(), + err_num + ); + } + println!("Usertests failed!"); + return -1; } From 56d3444fc6ac07303b6806ca84c34951df9e1cfd Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Sun, 15 May 2022 12:18:39 +0800 Subject: [PATCH 56/56] fix bug in sbi_shutdown --- os/src/sbi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os/src/sbi.rs b/os/src/sbi.rs index a402b91..45f2811 100644 --- a/os/src/sbi.rs +++ b/os/src/sbi.rs @@ -40,6 +40,6 @@ pub fn console_getchar() -> usize { } pub fn shutdown(exit_code: usize) -> ! { - sbi_call(SBI_SHUTDOWN, 0, 0, 0); + sbi_call(SBI_SHUTDOWN, exit_code, 0, 0); panic!("It should shutdown!"); }

1. LibOS API doc