├── .gitattributes ├── src ├── device.rs ├── thread │ ├── scheduler │ │ └── fcfs.rs │ ├── scheduler.rs │ └── switch.rs ├── error.rs ├── fs │ ├── disk │ │ ├── path.rs │ │ ├── swap.rs │ │ └── dir.rs │ └── inmem.rs ├── sync │ ├── spin.rs │ ├── sleep.rs │ ├── intr.rs │ ├── lazy.rs │ ├── sema.rs │ ├── mutex.rs │ ├── once.rs │ └── condvar.rs ├── linker.ld ├── trap │ ├── syscall.rs │ └── pagefault.rs ├── sync.rs ├── sbi │ ├── interrupt.rs │ ├── timer.rs │ └── console.rs ├── mem │ ├── layout.rs │ ├── userbuf.rs │ ├── utils │ │ └── list.rs │ ├── pagetable │ │ └── entry.rs │ └── utils.rs ├── boot.rs ├── thread.rs ├── sbi.rs ├── mem.rs └── userproc.rs ├── test ├── unit │ ├── fs.rs │ ├── sync.rs │ ├── virtio.rs │ ├── thread.rs │ ├── thread │ │ ├── bomb.rs │ │ ├── spin_interrupt.rs │ │ ├── spin_yield.rs │ │ ├── block.rs │ │ └── adder.rs │ ├── virtio │ │ ├── simple.rs │ │ └── repeat.rs │ ├── fs │ │ ├── disk.rs │ │ ├── disk │ │ │ ├── readimg.rs │ │ │ ├── simple.rs │ │ │ ├── chlen.rs │ │ │ └── sync.rs │ │ └── inmem.rs │ └── sync │ │ ├── condvar.rs │ │ └── sema_fifo.rs ├── schedule │ ├── alarm.rs │ ├── donation.rs │ ├── priority.rs │ ├── alarm │ │ ├── boundary.rs │ │ ├── simultaneous.rs │ │ └── multiple.rs │ ├── priority │ │ ├── preempt.rs │ │ ├── change.rs │ │ ├── fifo.rs │ │ ├── sema.rs │ │ ├── condvar.rs │ │ └── alarm.rs │ ├── mod.rs │ └── donation │ │ ├── one.rs │ │ ├── lower.rs │ │ ├── two.rs │ │ ├── nest.rs │ │ ├── three.rs │ │ └── chain.rs ├── mod.rs ├── user.rs └── unit.rs ├── fw_jump.bin ├── user ├── vm │ ├── page-merge-mm.c │ ├── page-merge-par.c │ ├── page-merge-stk.c │ ├── pt-bad-addr.c │ ├── pt-write-code.c │ ├── sample.txt │ ├── mmap-bad-fd.c │ ├── mmap-null.c │ ├── pt-bad-read.c │ ├── pt-grow-bad.c │ ├── pt-write-code2.c │ ├── sample.inc │ ├── mmap-misalign.c │ ├── child-inherit.c │ ├── mmap-over-stk.c │ ├── mmap-over-code.c │ ├── mmap-over-data.c │ ├── page-parallel.c │ ├── pt-grow-stack.c │ ├── pt-big-stk-obj.c │ ├── child-qsort-mm.c │ ├── mmap-unmap.c │ ├── mmap-overlap.c │ ├── child-mm-wrt.c │ ├── mmap-exit.c │ ├── mmap-close.c │ ├── child-qsort.c │ ├── mmap-twice.c │ ├── mmap-zero.c │ ├── child-linear.c │ ├── mmap-read.c │ ├── pt-grow-stk-sc.c │ ├── page-shuffle.c │ ├── child-sort.c │ ├── mmap-write.c │ ├── page-linear.c │ ├── mmap-shuffle.c │ ├── mmap-inherit.c │ ├── README.md │ ├── mmap-remove.c │ ├── mmap-clean.c │ └── page-merge-seq.c ├── lib │ ├── fcntl.h │ ├── cksum.h │ ├── random.h │ ├── arc4.h │ ├── fstat.h │ ├── types.h │ ├── usys.pl │ ├── user.ld │ ├── syscall.h │ ├── arc4.c │ ├── user.h │ ├── random.c │ └── printf.c ├── userprogs │ ├── exit.c │ ├── wait-badpid.c │ ├── halt.c │ ├── read-normal.c │ ├── wait-simple.c │ ├── close-normal.c │ ├── exec-once.c │ ├── read-stdout.c │ ├── exec-arg.c │ ├── write-stdin.c │ ├── sample.txt │ ├── args-none.c │ ├── bad-store.c │ ├── bad-write.c │ ├── bad-load.c │ ├── bad-read.c │ ├── bad-store2.c │ ├── bad-write2.c │ ├── close-badfd.c │ ├── exec-invalid.c │ ├── sample.inc │ ├── bad-jump2.c │ ├── write-zero.c │ ├── bad-load2.c │ ├── bad-read2.c │ ├── write-normal.c │ ├── open-invalid.c │ ├── bad-jump.c │ ├── open-create.c │ ├── args-many.c │ ├── sc-bad-sp.c │ ├── open-many.c │ ├── child-simple.c │ ├── close-twice.c │ ├── child-bad.c │ ├── close-stdio.c │ ├── wait-twice.c │ ├── exec-multiple.c │ ├── child-close.c │ ├── wait-killed.c │ ├── read-zero.c │ ├── multi-recurse.c │ ├── write-badfd.c │ ├── read-badfd.c │ ├── recurse.c │ ├── rox-simple.c │ ├── open-trunc.c │ ├── close-by-child.c │ ├── boundary-bad.c │ ├── boundary-normal.c │ ├── rox-child.c │ ├── rox-multichild.c │ ├── child-rox.c │ ├── open-rdwr.c │ ├── sc-bad-args.c │ └── README.md └── utests.mk ├── .clang-format ├── README.md ├── .cargo └── config.toml ├── tool ├── .cargo │ └── config.toml ├── src │ ├── main.rs │ ├── build.rs │ └── cli.rs ├── .gitignore ├── bookmarks │ ├── unit.toml │ ├── lab1.toml │ ├── lab3.toml │ └── lab2.toml └── Cargo.toml ├── .gdbinit ├── .gitignore ├── .vscode └── launch.json ├── tacos ├── makefile ├── .devcontainer └── devcontainer.json ├── doc ├── lab0.md └── lab1.md ├── Dockerfile └── Cargo.toml /.gitattributes: -------------------------------------------------------------------------------- 1 | * test=auto eol=lf 2 | -------------------------------------------------------------------------------- /src/device.rs: -------------------------------------------------------------------------------- 1 | pub mod plic; 2 | pub mod virtio; 3 | -------------------------------------------------------------------------------- /test/unit/fs.rs: -------------------------------------------------------------------------------- 1 | pub mod disk; 2 | pub mod inmem; 3 | -------------------------------------------------------------------------------- /test/unit/sync.rs: -------------------------------------------------------------------------------- 1 | pub mod condvar; 2 | pub mod sema_fifo; 3 | -------------------------------------------------------------------------------- /test/unit/virtio.rs: -------------------------------------------------------------------------------- 1 | pub mod repeat; 2 | pub mod simple; 3 | -------------------------------------------------------------------------------- /fw_jump.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKU-OS/Tacos/HEAD/fw_jump.bin -------------------------------------------------------------------------------- /user/vm/page-merge-mm.c: -------------------------------------------------------------------------------- 1 | #include "parallel-merge.h" 2 | 3 | void main() { parallel_merge("child-qsort-mm", 80); } 4 | -------------------------------------------------------------------------------- /user/vm/page-merge-par.c: -------------------------------------------------------------------------------- 1 | #include "parallel-merge.h" 2 | 3 | void main() { parallel_merge("child-sort", 123); } 4 | -------------------------------------------------------------------------------- /user/vm/page-merge-stk.c: -------------------------------------------------------------------------------- 1 | #include "parallel-merge.h" 2 | 3 | void main() { parallel_merge("child-qsort", 72); } 4 | -------------------------------------------------------------------------------- /test/unit/thread.rs: -------------------------------------------------------------------------------- 1 | pub mod adder; 2 | pub mod block; 3 | pub mod bomb; 4 | pub mod spin_interrupt; 5 | pub mod spin_yield; 6 | -------------------------------------------------------------------------------- /user/lib/fcntl.h: -------------------------------------------------------------------------------- 1 | #define O_RDONLY 0x000 2 | #define O_WRONLY 0x001 3 | #define O_RDWR 0x002 4 | #define O_CREATE 0x200 5 | #define O_TRUNC 0x400 6 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 4 3 | UseTab: Never 4 | ColumnLimit: 100 5 | DerivePointerAlignment: false 6 | PointerAlignment: Left 7 | -------------------------------------------------------------------------------- /user/lib/cksum.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_CKSUM_H 2 | #define __LIB_CKSUM_H 3 | 4 | #include "types.h" 5 | 6 | uint32 cksum(const void*, size_t); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /test/schedule/alarm.rs: -------------------------------------------------------------------------------- 1 | pub mod boundary; 2 | pub mod multiple; 3 | pub mod simultaneous; 4 | 5 | use super::pass; 6 | use crate::sbi::timer; 7 | use crate::thread; 8 | -------------------------------------------------------------------------------- /user/userprogs/exit.c: -------------------------------------------------------------------------------- 1 | /** Tests the exit system call. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | exit(0); 7 | panic("exit should not return"); 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tacos 2 | 3 | > Pintos reimplemented in Rust for riscv64. 4 | 5 | This repo contains skeleton code for undergraduate Operating System course honor track at Peking University. 6 | -------------------------------------------------------------------------------- /user/userprogs/wait-badpid.c: -------------------------------------------------------------------------------- 1 | /** Waits for an invalid pid. This should fail and return with -1. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { assert(wait(0xbbaadd) == -1, "invalid pid"); } 6 | -------------------------------------------------------------------------------- /user/vm/pt-bad-addr.c: -------------------------------------------------------------------------------- 1 | /* Accesses a bad address. 2 | The process must be terminated with -1 exit code. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { panic("bad addr read as %d", *(int*)0x04000000); } 7 | -------------------------------------------------------------------------------- /user/userprogs/halt.c: -------------------------------------------------------------------------------- 1 | /** Tests the halt system call. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | printf("Goodbye, World!\n"); 7 | halt(); 8 | panic("halt should not return"); 9 | } 10 | -------------------------------------------------------------------------------- /user/userprogs/read-normal.c: -------------------------------------------------------------------------------- 1 | /** Try reading a file in the most normal way. */ 2 | 3 | #include "sample.inc" 4 | #include "user.h" 5 | 6 | void main() { 7 | check_file("sample.txt", sample, sizeof(sample) - 1); 8 | } 9 | -------------------------------------------------------------------------------- /user/userprogs/wait-simple.c: -------------------------------------------------------------------------------- 1 | /** Wait for a subprocess to finish. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | const char* argv[] = {"child-simple", 0}; 7 | assert(wait(exec(argv[0], argv)) == 81); 8 | } 9 | -------------------------------------------------------------------------------- /user/userprogs/close-normal.c: -------------------------------------------------------------------------------- 1 | /** Opens a file and then closes it. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | int fd; 7 | assert((fd = open("sample.txt", 0)) > 2); 8 | assert(close(fd) == 0); 9 | } 10 | -------------------------------------------------------------------------------- /user/userprogs/exec-once.c: -------------------------------------------------------------------------------- 1 | /** Executes and waits for a single child process. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | const char* args[] = {"child-simple", 0}; 7 | assert(wait(exec(args[0], args)) == 81); 8 | } 9 | -------------------------------------------------------------------------------- /user/userprogs/read-stdout.c: -------------------------------------------------------------------------------- 1 | /** Try reading from fd 1 (stdout), which should fail with return value -1. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | char buf; 7 | assert(read(1, &buf, 1) == -1, "reading from the stdout"); 8 | } 9 | -------------------------------------------------------------------------------- /user/userprogs/exec-arg.c: -------------------------------------------------------------------------------- 1 | /** Tests argument passing to child processes. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | const char* args[] = {"child-simple", "arg for child-simple", 0}; 7 | assert(wait(exec(args[0], args)) == 81); 8 | } 9 | -------------------------------------------------------------------------------- /user/vm/pt-write-code.c: -------------------------------------------------------------------------------- 1 | /* Try to write to the code segment. 2 | The process must be terminated with -1 exit code. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | *(int*)main = 0; 8 | panic("writing the code segment succeeded"); 9 | } 10 | -------------------------------------------------------------------------------- /user/vm/sample.txt: -------------------------------------------------------------------------------- 1 | "Amazing Electronic Fact: If you scuffed your feet long enough without 2 | touching anything, you would build up so many electrons that your 3 | finger would explode! But this is nothing to worry about unless you 4 | have carpeting." --Dave Barry 5 | -------------------------------------------------------------------------------- /user/userprogs/write-stdin.c: -------------------------------------------------------------------------------- 1 | /** Try writing to fd 0 (stdin), which should just fail with return value -1. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | char buf = 123; 7 | assert(write(0, &buf, 1) == -1, "writing to stdin is an invalid action"); 8 | } 9 | -------------------------------------------------------------------------------- /user/userprogs/sample.txt: -------------------------------------------------------------------------------- 1 | "Amazing Electronic Fact: If you scuffed your feet long enough without 2 | touching anything, you would build up so many electrons that your 3 | finger would explode! But this is nothing to worry about unless you 4 | have carpeting." --Dave Barry 5 | -------------------------------------------------------------------------------- /user/vm/mmap-bad-fd.c: -------------------------------------------------------------------------------- 1 | /* Tries to mmap an invalid fd, 2 | which must either fail silently or terminate the process with 3 | exit code -1. */ 4 | 5 | #include "user.h" 6 | 7 | void main() { assert(mmap(0x5678, (void*)0x10000000) == MAP_FAILED, "try to mmap invalid fd"); } 8 | -------------------------------------------------------------------------------- /test/schedule/donation.rs: -------------------------------------------------------------------------------- 1 | pub mod chain; 2 | pub mod lower; 3 | pub mod nest; 4 | pub mod one; 5 | pub mod sema; 6 | pub mod three; 7 | pub mod two; 8 | 9 | use alloc::sync::Arc; 10 | 11 | use super::pass; 12 | use crate::sync::{Lock, Sleep}; 13 | use crate::thread::*; 14 | -------------------------------------------------------------------------------- /test/schedule/priority.rs: -------------------------------------------------------------------------------- 1 | pub mod alarm; 2 | pub mod change; 3 | pub mod condvar; 4 | pub mod fifo; 5 | pub mod preempt; 6 | pub mod sema; 7 | 8 | use alloc::sync::Arc; 9 | 10 | use crate::sbi::timer::TICKS_PER_SEC; 11 | use crate::thread::*; 12 | 13 | use super::pass; 14 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "riscv64gc-unknown-none-elf" 3 | 4 | [target.riscv64gc-unknown-none-elf] 5 | runner = "tacos" 6 | rustflags = [ 7 | "-Clink-arg=-Tsrc/linker.ld", 8 | "-Cforce-frame-pointers=yes", 9 | "--verbose", 10 | "-Csave-temps=yes", 11 | ] 12 | -------------------------------------------------------------------------------- /user/userprogs/args-none.c: -------------------------------------------------------------------------------- 1 | /** Receives no additional command line arguments, 2 | except for its own executable path. */ 3 | 4 | #include "user.h" 5 | 6 | void main(int argc, char* argv[]) { 7 | assert(argc == 1); 8 | assert(strcmp("args-none", argv[0]) == 0); 9 | } 10 | -------------------------------------------------------------------------------- /user/lib/random.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_RANDOM_H 2 | #define __LIB_RANDOM_H 3 | 4 | #include "types.h" 5 | 6 | void random_init(unsigned seed); 7 | void random_bytes(void*, size_t); 8 | unsigned long random_ulong(void); 9 | void shuffle(void*, size_t cnt, size_t size); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /test/unit/thread/bomb.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | pub fn main() { 3 | bomb(100); 4 | unreachable!("should overflow before you run 2^100 cycles!") 5 | } 6 | 7 | fn bomb(x: usize) { 8 | kprintln!("{}", x); 9 | 10 | if x > 0 { 11 | bomb(x - 1); 12 | bomb(x - 1); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /user/userprogs/bad-store.c: -------------------------------------------------------------------------------- 1 | /** This program attempts to write to memory at an address that is not mapped. 2 | The kernel should kill this process after this bad attempt. */ 3 | 4 | #include "user.h" 5 | 6 | void main(void) { 7 | *(volatile int*)NULL = 42; 8 | panic("should have exited"); 9 | } 10 | -------------------------------------------------------------------------------- /user/userprogs/bad-write.c: -------------------------------------------------------------------------------- 1 | /** This program attempts to write to memory at an address that is not mapped. 2 | The kernel should kill this process after this bad attempt. */ 3 | 4 | #include "user.h" 5 | 6 | void main(void) { 7 | *(volatile int*)NULL = 42; 8 | panic("should have exited"); 9 | } 10 | -------------------------------------------------------------------------------- /user/vm/mmap-null.c: -------------------------------------------------------------------------------- 1 | /* Verifies that memory mappings at address 0 are disallowed. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | int fd; 7 | 8 | assert((fd = open("sample.txt", 0)) > 2, "open \"sample.txt\""); 9 | assert(mmap(fd, NULL) == MAP_FAILED, "try to mmap at address 0"); 10 | } 11 | -------------------------------------------------------------------------------- /user/vm/pt-bad-read.c: -------------------------------------------------------------------------------- 1 | /* Reads from a file into a bad address. 2 | The process must be terminated with -1 exit code. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | int fd; 8 | 9 | assert((fd = open("sample.txt", 0)) > 2); 10 | assert(read(fd, (char*)&fd - 4096, 1) == -1); 11 | } 12 | -------------------------------------------------------------------------------- /user/userprogs/bad-load.c: -------------------------------------------------------------------------------- 1 | /** Tries to dereference an invalid pointer, which will result 2 | in a segmentation fault. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | printf("Congratulations - you have successfully dereferenced NULL: %d\n", *(int*)NULL); 8 | panic("should have exited"); 9 | } 10 | -------------------------------------------------------------------------------- /user/userprogs/bad-read.c: -------------------------------------------------------------------------------- 1 | /** Tries to dereference an invalid pointer, which will result 2 | in a segmentation fault. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | printf("Congratulations - you have successfully dereferenced NULL: %d", *(int*)NULL); 8 | panic("should have exited"); 9 | } 10 | -------------------------------------------------------------------------------- /user/userprogs/bad-store2.c: -------------------------------------------------------------------------------- 1 | /** This program attempts to write to kernel memory. The kernel should 2 | kill this process after this malicious write operation. */ 3 | 4 | #include "user.h" 5 | 6 | void main(void) { 7 | *(volatile int*)0xffffffc030000000 = 42; 8 | panic("should have exited"); 9 | } 10 | -------------------------------------------------------------------------------- /user/userprogs/bad-write2.c: -------------------------------------------------------------------------------- 1 | /** This program attempts to write to kernel memory. The kernel should 2 | kill this process after this malicious write operation. */ 3 | 4 | #include "user.h" 5 | 6 | void main(void) { 7 | *(volatile int*)0xffffffc030000000 = 42; 8 | panic("should have exited"); 9 | } 10 | -------------------------------------------------------------------------------- /user/userprogs/close-badfd.c: -------------------------------------------------------------------------------- 1 | /** Tries to close an invalid fd, which must either fail silently 2 | or terminate with exit code -1. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | int bad_fd = 0xdeadbeaf; 8 | assert(close(bad_fd) == -1, "%d isn't a valid open file descriptor.", bad_fd); 9 | } 10 | -------------------------------------------------------------------------------- /user/vm/pt-grow-bad.c: -------------------------------------------------------------------------------- 1 | /* Read from an address 4,096 bytes below the stack pointer. 2 | The process must be terminated with -1 exit code. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | asm volatile( 8 | "li t0, -4096\n" 9 | "add t1, sp, t0\n" 10 | "ld t1, 0(t1)"); 11 | } 12 | -------------------------------------------------------------------------------- /user/vm/pt-write-code2.c: -------------------------------------------------------------------------------- 1 | /* Try to write to the code segment using a system call. 2 | The process must be terminated with -1 exit code. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | int fd; 8 | 9 | assert((fd = open("sample.txt", 0)) > 2); 10 | assert(read(fd, (void*)main, 1) == -1); 11 | } 12 | -------------------------------------------------------------------------------- /user/vm/sample.inc: -------------------------------------------------------------------------------- 1 | char sample[] = { 2 | "\"Amazing Electronic Fact: If you scuffed your feet long enough without\n" 3 | " touching anything, you would build up so many electrons that your\n" 4 | " finger would explode! But this is nothing to worry about unless you\n" 5 | " have carpeting.\" --Dave Barry\n"}; 6 | -------------------------------------------------------------------------------- /test/unit/virtio/simple.rs: -------------------------------------------------------------------------------- 1 | use crate::device::virtio::{self, Virtio}; 2 | 3 | pub fn main() { 4 | let mut buf3 = [0; virtio::SECTOR_SIZE]; 5 | 6 | Virtio::read_sector(1, &mut buf3); 7 | for i in buf3 { 8 | kprint!("{:#x} ", i); 9 | } 10 | 11 | kprintln!("Virtio simple test done."); 12 | } 13 | -------------------------------------------------------------------------------- /tool/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "x86_64-unknown-linux-gnu" 3 | 4 | [alias] 5 | bd = "run -q --release -- build" 6 | tt = "run -q --release -- test" 7 | bk = "run -q --release -- book" 8 | gdb = "run -q --release -- test --gdb" 9 | grade = "run -q --release -- test --grade" 10 | h = "run -q --release -- help" 11 | -------------------------------------------------------------------------------- /user/userprogs/exec-invalid.c: -------------------------------------------------------------------------------- 1 | /** Passes invalid arguments to system call `exec`. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | assert(exec("non-exist", NULL) == -1); 7 | 8 | const char* args[] = {"sample.txt", NULL}; 9 | assert(exec(args[0], args) == -1, "\"sample.txt\" is not executable"); 10 | } 11 | -------------------------------------------------------------------------------- /user/userprogs/sample.inc: -------------------------------------------------------------------------------- 1 | char sample[] = { 2 | "\"Amazing Electronic Fact: If you scuffed your feet long enough without\n" 3 | " touching anything, you would build up so many electrons that your\n" 4 | " finger would explode! But this is nothing to worry about unless you\n" 5 | " have carpeting.\" --Dave Barry\n"}; 6 | -------------------------------------------------------------------------------- /user/userprogs/bad-jump2.c: -------------------------------------------------------------------------------- 1 | /** This program attempts to execute code at a kernel virtual address. */ 2 | 3 | #include "user.h" 4 | 5 | void main(void) { 6 | printf("Congratulations - you have successfully called kernel code: %d", 7 | ((int (*)(void))0xffffffc030000000)()); 8 | panic("should have exited"); 9 | } 10 | -------------------------------------------------------------------------------- /user/userprogs/write-zero.c: -------------------------------------------------------------------------------- 1 | /** Try a 0-byte write, which should return 0 without writing 2 | anything. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | int fd; 8 | char buf = 123; 9 | 10 | assert((fd = open("sample.txt", O_WRONLY)) > 2); 11 | assert(write(fd, &buf, 0) == 0); 12 | close(fd); 13 | } 14 | -------------------------------------------------------------------------------- /user/vm/mmap-misalign.c: -------------------------------------------------------------------------------- 1 | /* Verifies that misaligned memory mappings are disallowed. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | int fd; 7 | 8 | assert((fd = open("sample.txt", 0)) > 2, "open \"sample.txt\""); 9 | assert(mmap(fd, (void*)0x10001234) == MAP_FAILED, "try to mmap at misaligned address"); 10 | } 11 | -------------------------------------------------------------------------------- /user/lib/arc4.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_ARC4_H 2 | #define __LIB_ARC4_H 3 | 4 | #include "types.h" 5 | 6 | /* Alleged RC4 algorithm encryption state. */ 7 | typedef struct arc4 { 8 | uint8 s[256]; 9 | uint8 i, j; 10 | } arc4; 11 | 12 | void arc4_init(arc4*, const void*, size_t); 13 | void arc4_crypt(arc4*, void*, size_t); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /user/vm/child-inherit.c: -------------------------------------------------------------------------------- 1 | /* Child process for mmap-inherit test. 2 | Tries to write to a mapping present in the parent. 3 | The process must be terminated with -1 exit code. */ 4 | 5 | #include "user.h" 6 | 7 | void main() { 8 | memset((char*)0x54321000, 0, 4096); 9 | panic("child can modify parent's memory mappings"); 10 | } 11 | -------------------------------------------------------------------------------- /user/lib/fstat.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_FSTAT_H 2 | #define __LIB_FSTAT_H 3 | 4 | #include "types.h" 5 | 6 | #define T_DIR 1 // Directory 7 | #define T_FILE 2 // File 8 | #define T_DEVICE 3 // Device 9 | 10 | typedef struct { 11 | uint ino; // Inode number 12 | uint64 size; // Size of file in bytes 13 | } stat; 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /user/userprogs/bad-load2.c: -------------------------------------------------------------------------------- 1 | /** This program attempts to read kernel memory, which makes the 2 | process killed by the kernel. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | printf("Congratulations - you have successfully read kernel memory: %d", 8 | *(int*)0xffffffc030000000); 9 | panic("should have exited"); 10 | } 11 | -------------------------------------------------------------------------------- /user/userprogs/bad-read2.c: -------------------------------------------------------------------------------- 1 | /** This program attempts to read kernel memory, which makes the 2 | process killed by the kernel. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | printf("Congratulations - you have successfully read kernel memory: %d", 8 | *(int*)0xffffffc030000000); 9 | panic("should have exited"); 10 | } 11 | -------------------------------------------------------------------------------- /user/userprogs/write-normal.c: -------------------------------------------------------------------------------- 1 | /** Try writing a file in the most normal way. */ 2 | 3 | #include "sample.inc" 4 | #include "user.h" 5 | 6 | void main() { 7 | int fd; 8 | 9 | assert((fd = open("test.txt", O_CREATE | O_WRONLY)) > 2); 10 | assert(write(fd, sample, sizeof sample - 1) == sizeof sample - 1); 11 | close(fd); 12 | } 13 | -------------------------------------------------------------------------------- /user/userprogs/open-invalid.c: -------------------------------------------------------------------------------- 1 | /** Passes invalid arguments to system call `open`. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | assert(open(NULL, O_CREATE | O_TRUNC | O_WRONLY) == -1, "null pointer"); 7 | assert(open("", O_CREATE | O_RDWR) == -1, "empty string"); 8 | assert(open("non-exist.txt", 0) == -1, "file doesn't exist"); 9 | } 10 | -------------------------------------------------------------------------------- /user/userprogs/bad-jump.c: -------------------------------------------------------------------------------- 1 | /** This program attempts to execute code at address 0, which is not mapped. */ 2 | 3 | #include "user.h" 4 | 5 | typedef int (*volatile functionptr)(void); 6 | 7 | void main(void) { 8 | functionptr fp = NULL; 9 | printf("Congratulations - you have successfully called NULL: %d", fp()); 10 | panic("should have exited"); 11 | } 12 | -------------------------------------------------------------------------------- /tool/src/main.rs: -------------------------------------------------------------------------------- 1 | mod book; 2 | mod build; 3 | mod cli; 4 | mod test; 5 | 6 | fn main() -> std::io::Result<()> { 7 | let cli = cli::Cli::parse(); 8 | match cli.command { 9 | cli::Commands::Build(args) => build::main(args), 10 | cli::Commands::Test(args) => test::main(args), 11 | cli::Commands::Book(args) => book::main(args), 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /user/userprogs/open-create.c: -------------------------------------------------------------------------------- 1 | /** Creates a file by forwarding the O_CREATE flag. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | int fd; 7 | char* path = "non-exist.txt"; 8 | assert(open(path, O_RDONLY) == -1, "file doesn't exist"); 9 | assert((fd = open(path, O_CREATE)) > 2, "open should create a file with flag O_CREATE"); 10 | close(fd); 11 | } 12 | -------------------------------------------------------------------------------- /user/vm/mmap-over-stk.c: -------------------------------------------------------------------------------- 1 | /* Verifies that mapping over the stack segment is disallowed. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | int fd; 7 | uint64 fd_page = ROUND_DOWN(&fd, 4096); 8 | 9 | assert((fd = open("sample.txt", 0)) > 2, "open \"sample.txt\""); 10 | assert(mmap(fd, (void*)fd_page) == MAP_FAILED, "try to mmap over stack segment"); 11 | } 12 | -------------------------------------------------------------------------------- /tool/.gitignore: -------------------------------------------------------------------------------- 1 | # MACOS # 2 | ######### 3 | .DS_Store 4 | 5 | # Editor # 6 | .helix/* 7 | 8 | # VSC # 9 | ######### 10 | .vscode/* 11 | !.vscode/launch.json 12 | 13 | # CARGO # 14 | ######### 15 | /target/* 16 | /Cargo.lock 17 | 18 | # Test bookmarks 19 | 20 | bookmarks/* 21 | !bookmarks/unit.toml 22 | !bookmarks/lab1.toml 23 | !bookmarks/lab2.toml 24 | !bookmarks/lab3.toml 25 | -------------------------------------------------------------------------------- /user/userprogs/args-many.c: -------------------------------------------------------------------------------- 1 | /** Prints many command line arguments. */ 2 | 3 | #include "user.h" 4 | 5 | void main(int argc, char* argv[]) { 6 | assert(argc == 23); 7 | assert(strcmp("args-many", argv[0]) == 0); 8 | 9 | char arg[] = "a"; 10 | for (int i = 1; i < argc; ++i) { 11 | assert(strcmp(arg, argv[i]) == 0); 12 | ++arg[0]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /user/userprogs/sc-bad-sp.c: -------------------------------------------------------------------------------- 1 | /** Invokes a system call with `sp` set to a bad address. 2 | The process can still exit gracefully as long as it doesn't 3 | use sp. */ 4 | 5 | #include "user.h" 6 | 7 | void main(void) { 8 | uint64 sp = r_sp(); 9 | sp -= 16 * 4096; 10 | asm volatile("mv %0, sp; li a0, 0; j exit" ::"r"(sp)); 11 | 12 | panic("unreachable"); 13 | } 14 | -------------------------------------------------------------------------------- /test/unit/fs/disk.rs: -------------------------------------------------------------------------------- 1 | mod chlen; 2 | mod readimg; 3 | mod simple; 4 | mod sync; 5 | 6 | pub fn main() { 7 | #[cfg(feature = "test-fs-disk-simple")] 8 | { 9 | simple::main(); 10 | readimg::main().unwrap(); 11 | } 12 | #[cfg(not(feature = "test-fs-disk-simple"))] 13 | { 14 | // chlen::main().unwrap(); 15 | sync::main(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /user/userprogs/open-many.c: -------------------------------------------------------------------------------- 1 | /** Opens 512 file handlers, which has to succeed. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | int fd1, fd2; 7 | 8 | fd1 = open("sample.txt", 0); 9 | for (int i = 0; i < 512 - 1; ++i) { 10 | assert(fd1 > 2); 11 | fd2 = open("sample.txt", 0); 12 | assert(fd1 != fd2); 13 | fd1 = fd2; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /user/vm/mmap-over-code.c: -------------------------------------------------------------------------------- 1 | /* Verifies that mapping over the code segment is disallowed. */ 2 | 3 | #include "user.h" 4 | 5 | void main(void) { 6 | uint64 main_page = ROUND_DOWN(main, 4096); 7 | int fd; 8 | 9 | assert((fd = open("sample.txt", 0)) > 2, "open \"sample.txt\""); 10 | assert(mmap(fd, (void*)main_page) == MAP_FAILED, "try to mmap over code segment"); 11 | } 12 | -------------------------------------------------------------------------------- /user/userprogs/child-simple.c: -------------------------------------------------------------------------------- 1 | /** Child process run by exec-multiple, exec-one, wait-simple, and 2 | wait-twice tests. 3 | Just prints a single message and terminates. */ 4 | 5 | #include "user.h" 6 | 7 | void main(int argc, char* argv[]) { 8 | assert(argv[argc] == NULL); 9 | assert(strcmp("child-simple", argv[0]) == 0); 10 | printf("run"); 11 | 12 | exit(81); 13 | } 14 | -------------------------------------------------------------------------------- /user/userprogs/close-twice.c: -------------------------------------------------------------------------------- 1 | /** Closes a open file twice. The first close should be successful, 2 | whereas the second call should return -1 instead. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | int fd = open("sample.txt", 0); 8 | assert(close(fd) == 0, "%d is a valid open file descriptor", fd); 9 | assert(close(fd) == -1, "%d is closed and not valid", fd); 10 | } 11 | -------------------------------------------------------------------------------- /tool/bookmarks/unit.toml: -------------------------------------------------------------------------------- 1 | # case_name = ["args", option] 2 | sync = [""] 3 | sync-condvar = [""] 4 | sync-sema_fifo = [""] 5 | thread-adder = [""] 6 | thread-block = [""] 7 | thread-bomb = [""] 8 | thread-spin_yield = [""] 9 | thread-spin_interrupt = [""] 10 | mem-malloc = [""] 11 | fs-inmem = [""] 12 | fs-disk = [""] 13 | fs-disk-simple = [""] 14 | virtio = [""] 15 | virtio-simple = [""] 16 | -------------------------------------------------------------------------------- /user/userprogs/child-bad.c: -------------------------------------------------------------------------------- 1 | /** Child process run by wait-killed test. 2 | Sets the stack pointer (%esp) to an invalid value and invokes 3 | a system call, which should then terminate the process with a 4 | -1 exit code. */ 5 | 6 | #include "user.h" 7 | 8 | void main(void) { 9 | int* bad_ptr = (void*)0xdddaaabbb; 10 | *bad_ptr = 12345; 11 | 12 | panic("unreachable"); 13 | } 14 | -------------------------------------------------------------------------------- /user/vm/mmap-over-data.c: -------------------------------------------------------------------------------- 1 | /* Verifies that mapping over the data segment is disallowed. */ 2 | 3 | #include "user.h" 4 | 5 | static char x; 6 | 7 | void main() { 8 | uint64 x_page = ROUND_DOWN(&x, 4096); 9 | int fd; 10 | 11 | assert((fd = open("sample.txt", 0)) > 2, "open \"sample.txt\""); 12 | assert(mmap(fd, (void*)x_page) == MAP_FAILED, "try to mmap over data segment"); 13 | } 14 | -------------------------------------------------------------------------------- /user/userprogs/close-stdio.c: -------------------------------------------------------------------------------- 1 | /** Tries to close standard io streams. This is a dangerous action, 2 | but should not fail. */ 3 | 4 | #include "user.h" 5 | 6 | void check_close(int fd) { 7 | if (close(fd) != 0) { 8 | exit(1); 9 | } 10 | } 11 | 12 | void main() { 13 | check_close(0); // close stdin 14 | check_close(1); // close stdout 15 | check_close(2); // close stderr 16 | } 17 | -------------------------------------------------------------------------------- /user/userprogs/wait-twice.c: -------------------------------------------------------------------------------- 1 | /** Wait for a subprocess to finish, twice. 2 | The first call must wait in the usual way and return the exit code. 3 | The second wait call must return -1 immediately. */ 4 | 5 | #include "user.h" 6 | 7 | void main() { 8 | const char* argv[] = {"child-simple", 0}; 9 | int pid = exec(argv[0], argv); 10 | assert(wait(pid) == 81); 11 | assert(wait(pid) == -1); 12 | } 13 | -------------------------------------------------------------------------------- /user/userprogs/exec-multiple.c: -------------------------------------------------------------------------------- 1 | /** Executes and waits for multiple child processes. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | const char* args[] = {"child-simple", 0}; 7 | assert(wait(exec(args[0], args)) == 81); 8 | assert(wait(exec(args[0], args)) == 81); 9 | assert(wait(exec(args[0], args)) == 81); 10 | assert(wait(exec(args[0], args)) == 81); 11 | assert(wait(exec(args[0], args)) == 81); 12 | } 13 | -------------------------------------------------------------------------------- /user/vm/page-parallel.c: -------------------------------------------------------------------------------- 1 | /* Runs 4 child-linear processes at once. */ 2 | 3 | #include "user.h" 4 | 5 | #define CHILD_CNT 4 6 | 7 | void main() { 8 | int children[CHILD_CNT]; 9 | int i; 10 | 11 | const char* args[] = {"child-linear", 0}; 12 | for (i = 0; i < CHILD_CNT; i++) assert((children[i] = exec(args[0], args)) != -1); 13 | for (i = 0; i < CHILD_CNT; i++) assert(wait(children[i]) == 0, "wait for child %d", i); 14 | } 15 | -------------------------------------------------------------------------------- /tool/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tool" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4.0.*", features = ["derive"] } 10 | colored = "2.0.4" 11 | ctrlc = { version = "3.4.1", features = ["termination"] } 12 | once_cell = "1.18.0" 13 | serde = { version = "1.0.*", features = ["derive"] } 14 | toml = "0.8.2" 15 | -------------------------------------------------------------------------------- /user/vm/pt-grow-stack.c: -------------------------------------------------------------------------------- 1 | /* Demonstrate that the stack can grow. 2 | This must succeed. */ 3 | 4 | #include "arc4.h" 5 | #include "cksum.h" 6 | #include "user.h" 7 | 8 | void main() { 9 | char stack_obj[4096]; 10 | arc4 arc4; 11 | 12 | arc4_init(&arc4, "foobar", 6); 13 | memset(stack_obj, 0, sizeof stack_obj); 14 | arc4_crypt(&arc4, stack_obj, sizeof stack_obj); 15 | 16 | assert(cksum(stack_obj, sizeof stack_obj) == 3424492700); 17 | } 18 | -------------------------------------------------------------------------------- /user/userprogs/child-close.c: -------------------------------------------------------------------------------- 1 | /** Child process run by close-by-child test. 2 | 3 | Attempts to close the file descriptor passed as the first 4 | command-line argument. This is invalid, because file 5 | descriptors are not inherited through `exec`. */ 6 | 7 | #include "user.h" 8 | 9 | int main(int argc, char* argv[]) { 10 | assert(argc == 2); 11 | assert(atoi(argv[1]) > 2); 12 | assert(close(atoi(argv[1])) == -1); 13 | 14 | return 64; 15 | } 16 | -------------------------------------------------------------------------------- /user/vm/pt-big-stk-obj.c: -------------------------------------------------------------------------------- 1 | /* Allocates and writes to a 64 kB object on the stack. 2 | This must succeed. */ 3 | 4 | #include "arc4.h" 5 | #include "cksum.h" 6 | #include "user.h" 7 | 8 | void main() { 9 | char stk_obj[65536]; 10 | arc4 arc4; 11 | 12 | arc4_init(&arc4, "foobar", 6); 13 | memset(stk_obj, 0, sizeof stk_obj); 14 | arc4_crypt(&arc4, stk_obj, sizeof stk_obj); 15 | 16 | assert(cksum(stk_obj, sizeof stk_obj) == 3256410166); 17 | } 18 | -------------------------------------------------------------------------------- /src/thread/scheduler/fcfs.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::VecDeque; 2 | use alloc::sync::Arc; 3 | 4 | use crate::thread::{Schedule, Thread}; 5 | 6 | /// FIFO scheduler. 7 | #[derive(Default)] 8 | pub struct Fcfs(VecDeque>); 9 | 10 | impl Schedule for Fcfs { 11 | fn register(&mut self, thread: Arc) { 12 | self.0.push_front(thread) 13 | } 14 | 15 | fn schedule(&mut self) -> Option> { 16 | self.0.pop_back() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /user/vm/child-qsort-mm.c: -------------------------------------------------------------------------------- 1 | /* Mmaps a 128 kB file "sorts" the bytes in it, using quick sort, 2 | a multi-pass divide and conquer algorithm. */ 3 | 4 | #include "user.h" 5 | #include "qsort.h" 6 | 7 | void main(int argc, char* argv[]) { 8 | int fd; 9 | unsigned char* p = (unsigned char*)0x10000000; 10 | 11 | assert((fd = open(argv[1], O_RDWR)) > 2); 12 | assert(mmap(fd, p) != MAP_FAILED); 13 | qsort_bytes(p, 1024 * 128); 14 | 15 | exit(80); 16 | } 17 | -------------------------------------------------------------------------------- /user/lib/types.h: -------------------------------------------------------------------------------- 1 | typedef unsigned int uint; 2 | typedef unsigned short ushort; 3 | typedef unsigned char uchar; 4 | 5 | typedef unsigned char uint8; 6 | typedef unsigned short uint16; 7 | typedef unsigned int uint32; 8 | typedef unsigned long uint64; 9 | typedef unsigned long size_t; 10 | 11 | /* Process identifier. */ 12 | typedef int pid_t; 13 | #define PID_ERROR ((pid_t)-1) 14 | 15 | /* Map region identifier. */ 16 | typedef int mapid_t; 17 | #define MAP_FAILED ((mapid_t)-1) 18 | -------------------------------------------------------------------------------- /user/userprogs/wait-killed.c: -------------------------------------------------------------------------------- 1 | /** Wait for a process that will be killed for bad behavior. 2 | No matter the child process was killed or exited gracefully, 3 | the first wait should succeed and the second one should fail. */ 4 | 5 | #include "user.h" 6 | 7 | void main() { 8 | int pid; 9 | const char* args[] = {"child-bad", 0}; 10 | 11 | assert((pid = exec(args[0], args)) >= 0); 12 | assert(wait(pid) == -1, "child-bad should exit with a non-zero code"); 13 | } 14 | -------------------------------------------------------------------------------- /user/userprogs/read-zero.c: -------------------------------------------------------------------------------- 1 | /** Try a 0-byte read, which should return 0 without reading 2 | anything. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | int fd, byte_cnt; 8 | char buf; 9 | 10 | assert((fd = open("sample.txt", O_RDONLY))); 11 | 12 | buf = 123; 13 | byte_cnt = read(fd, &buf, 0); 14 | assert(byte_cnt == 0, "read() returned %d instead of 0", byte_cnt); 15 | assert(buf == 123, "0-byte read() modified buffer"); 16 | close(fd); 17 | } 18 | -------------------------------------------------------------------------------- /.gdbinit: -------------------------------------------------------------------------------- 1 | define file-debug 2 | file ./target/riscv64gc-unknown-none-elf/debug/tacos 3 | end 4 | document file-debug 5 | add symbol file of debug target 6 | end 7 | 8 | define file-release 9 | file ./target/riscv64gc-unknown-none-elf/release/tacos 10 | end 11 | document file-release 12 | add symbol file of release target 13 | end 14 | 15 | define debug-qemu 16 | target remote localhost:1234 17 | end 18 | document debug-qemu 19 | attach to qemu (localhost:1234) and debug the kernel 20 | end 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # MACOS # 2 | ######### 3 | .DS_Store 4 | 5 | # Editor # 6 | .helix/* 7 | 8 | # VSC # 9 | ######### 10 | .vscode/* 11 | !.vscode/launch.json 12 | 13 | # CARGO # 14 | ######### 15 | /target/* 16 | /Cargo.lock 17 | 18 | *.img 19 | 20 | # Prerequisites 21 | *.d 22 | *.S 23 | 24 | # Object files 25 | build/ 26 | *.o 27 | 28 | # Executables 29 | *.out 30 | _* 31 | mkfs 32 | 33 | # Debug files 34 | *.asm 35 | *.sym 36 | 37 | # Submissions 38 | *.tar.bz2 39 | 40 | # Zed 41 | .zed 42 | -------------------------------------------------------------------------------- /user/vm/mmap-unmap.c: -------------------------------------------------------------------------------- 1 | /* Maps and unmaps a file and verifies that the mapped region is 2 | inaccessible afterward. */ 3 | 4 | #include "sample.inc" 5 | #include "user.h" 6 | 7 | #define ACTUAL ((void*)0x10000000) 8 | 9 | void main() { 10 | int fd; 11 | mapid_t map; 12 | 13 | assert((fd = open("sample.txt", 0)) > 2); 14 | assert((map = mmap(fd, ACTUAL)) != MAP_FAILED); 15 | 16 | munmap(map); 17 | 18 | panic("unmapped memory is readable (%d)", *(int*)ACTUAL); 19 | } 20 | -------------------------------------------------------------------------------- /test/schedule/alarm/boundary.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | fn single(ticks: i64) { 4 | let current = timer::timer_ticks(); 5 | 6 | thread::sleep(ticks); 7 | assert_eq!(current, timer::timer_ticks()); 8 | } 9 | 10 | pub mod negative { 11 | use super::*; 12 | 13 | pub fn main() { 14 | single(-100); 15 | pass(); 16 | } 17 | } 18 | 19 | pub mod zero { 20 | use super::*; 21 | 22 | pub fn main() { 23 | single(0); 24 | pass(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/unit/thread/spin_interrupt.rs: -------------------------------------------------------------------------------- 1 | use crate::thread; 2 | 3 | #[allow(unused)] 4 | pub fn main() { 5 | thread::spawn("t1", spin); 6 | thread::spawn("t2", spin); 7 | 8 | for iter in 0..20 { 9 | kprintln!("Yield {} from initial thread", iter); 10 | thread::schedule(); 11 | } 12 | } 13 | 14 | fn spin() { 15 | for iter in 0..15 { 16 | kprintln!("Schedule {} thread {}", iter, thread::current().name()); 17 | unsafe { core::arch::asm!("wfi") }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use alloc::sync::Arc; 4 | 5 | use crate::sync::Semaphore; 6 | 7 | mod schedule; 8 | mod unit; 9 | pub mod user; 10 | 11 | pub fn main(sema: Arc, _bootargs: &str) { 12 | #[cfg(feature = "test-unit")] 13 | unit::main(); 14 | 15 | #[cfg(feature = "test-user")] 16 | user::main(_bootargs); 17 | 18 | #[cfg(feature = "test-schedule")] 19 | schedule::main(_bootargs); 20 | 21 | kprintln!("Leaving test..."); 22 | sema.up(); 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "gdb", 5 | "request": "attach", 6 | "name": "Attach to gdbserver", 7 | "executable": "${workspaceFolder}/target/riscv64gc-unknown-none-elf/debug/tacos", 8 | "target": ":1234", 9 | "remote": true, 10 | "cwd": "${workspaceRoot}", 11 | "gdbpath": "/usr/bin/gdb-multiarch", 12 | "valuesFormatting": "prettyPrinters" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Os Error Handling. 2 | //! 3 | 4 | /// Possible errors in the OS 5 | #[repr(i32)] 6 | #[derive(Debug, PartialEq)] 7 | pub enum OsError { 8 | BadPtr = -1, 9 | UnexpectedEOF = -2, 10 | NoSuchFile = -3, 11 | UnknownFormat = -4, 12 | UserError = -5, 13 | CreateExistInode = -6, 14 | OpenInvalidInode = -7, 15 | DiskSectorAllocFail = -8, 16 | RootDirFull = -9, 17 | CstrFormatErr = -10, 18 | ArgumentTooLong = -11, 19 | InvalidFileMode = -12, 20 | FileNotOpened = -13, 21 | } 22 | -------------------------------------------------------------------------------- /user/userprogs/multi-recurse.c: -------------------------------------------------------------------------------- 1 | /** Executes itself recursively to the depth indicated by the 2 | first command-line argument. */ 3 | 4 | #include "user.h" 5 | 6 | void main(int argc, char* argv[]) { 7 | assert(argc > 1); 8 | int n = atoi(argv[1]); 9 | assert(n > 0); 10 | 11 | char buf[10]; 12 | itoa(buf, n - 1); 13 | 14 | int child; 15 | const char* child_argv[] = {"recurse", buf, 0}; 16 | 17 | assert((child = exec(child_argv[0], child_argv)) != -1); 18 | assert(wait(child) == n - 1); 19 | } 20 | -------------------------------------------------------------------------------- /user/userprogs/write-badfd.c: -------------------------------------------------------------------------------- 1 | /** Tries to write to an invalid fd, which must fail with return value -1. */ 2 | 3 | #include 4 | 5 | #include "user.h" 6 | 7 | void main() { 8 | char buf = 123; 9 | assert(write(0x20230823, &buf, 1) == -1); 10 | assert(write(7, &buf, 1) == -1); 11 | assert(write(2489, &buf, 1) == -1); 12 | assert(write(-11, &buf, 1) == -1); 13 | assert(write(-89, &buf, 1) == -1); 14 | assert(write(INT_MIN + 1, &buf, 1) == -1); 15 | assert(write(INT_MAX - 1, &buf, 1) == -1); 16 | } 17 | -------------------------------------------------------------------------------- /user/vm/mmap-overlap.c: -------------------------------------------------------------------------------- 1 | /* Verifies that overlapping memory mappings are disallowed. */ 2 | 3 | #include "sample.inc" 4 | #include "user.h" 5 | 6 | void main() { 7 | char* start = (char*)0x10000000; 8 | int fd[2]; 9 | 10 | assert((fd[0] = open("zeros", 0)) > 2, "open \"zeros\" once"); 11 | assert(mmap(fd[0], start) != MAP_FAILED, "mmap \"zeros\""); 12 | assert((fd[1] = open("zeros", 0)) > 2 && fd[0] != fd[1], "open \"zeros\" again"); 13 | assert(mmap(fd[1], start + 4096) == MAP_FAILED, "try to mmap \"zeros\" again"); 14 | } 15 | -------------------------------------------------------------------------------- /test/unit/thread/spin_yield.rs: -------------------------------------------------------------------------------- 1 | use crate::thread; 2 | 3 | pub fn main() { 4 | thread::spawn("t1", spin); 5 | thread::spawn("t2", spin); 6 | 7 | for iter in 0..20 { 8 | kprintln!("Yield {} from test thread", iter); 9 | thread::schedule(); 10 | kprintln!("Yield {} again from test thread", iter); 11 | } 12 | } 13 | 14 | fn spin() { 15 | let name = thread::current().name(); 16 | for iter in 0..15 { 17 | kprintln!("Yield {} from thread {}", iter, name); 18 | thread::schedule(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /user/userprogs/read-badfd.c: -------------------------------------------------------------------------------- 1 | /** Tries to read from an invalid fd, which must fail with return value -1. */ 2 | 3 | #include 4 | 5 | #include "user.h" 6 | 7 | void main() { 8 | char buf = 123; 9 | assert(read(0xdeadbeaf, &buf, 1) == -1); 10 | assert(read(5, &buf, 1) == -1); 11 | assert(read(1234, &buf, 1) == -1); 12 | assert(read(-1, &buf, 1) == -1); 13 | assert(read(-1024, &buf, 1) == -1); 14 | assert(read(INT_MIN, &buf, 1) == -1); 15 | assert(read(INT_MAX, &buf, 1) == -1); 16 | assert(buf == 123); 17 | } 18 | -------------------------------------------------------------------------------- /user/vm/child-mm-wrt.c: -------------------------------------------------------------------------------- 1 | /* Child process of mmap-exit. 2 | Mmaps a file and writes to it via the mmap'ing, then exits 3 | without calling munmap. The data in the mapped region must be 4 | written out at program termination. */ 5 | 6 | #include "sample.inc" 7 | #include "user.h" 8 | 9 | #define ACTUAL ((void*)0x10000000) 10 | 11 | void main() { 12 | int fd; 13 | 14 | assert((fd = open("sample.txt", O_WRONLY)) > 1); 15 | assert(mmap(fd, ACTUAL) != MAP_FAILED, "mmap \"sample.txt\""); 16 | memcpy(ACTUAL, sample, sizeof sample); 17 | } 18 | -------------------------------------------------------------------------------- /user/userprogs/recurse.c: -------------------------------------------------------------------------------- 1 | /** Executes itself recursively to the depth indicated by the 2 | first command-line argument. */ 3 | 4 | #include "user.h" 5 | 6 | void main(int argc, char* argv[]) { 7 | assert(argc > 1); 8 | int n = atoi(argv[1]); 9 | 10 | if (n > 0) { 11 | char buf[10]; 12 | itoa(buf, n - 1); 13 | 14 | int child; 15 | const char* argv[] = {"recurse", buf, 0}; 16 | 17 | assert((child = exec(argv[0], argv)) != -1); 18 | assert(wait(child) == n - 1); 19 | } 20 | 21 | exit(n); 22 | } 23 | -------------------------------------------------------------------------------- /user/vm/mmap-exit.c: -------------------------------------------------------------------------------- 1 | /* Executes child-mm-wrt and verifies that the writes that should 2 | have occurred really did. */ 3 | 4 | #include "sample.inc" 5 | #include "user.h" 6 | 7 | void main() { 8 | pid_t child; 9 | 10 | /* Make child write file. */ 11 | const char* args[] = {"child-mm-wrt", 0}; 12 | assert((child = exec(args[0], args)) != -1, "exec \"child-mm-wrt\""); 13 | assert(wait(child) == 0, "wait for child (should return 0)"); 14 | 15 | /* Check file contents. */ 16 | check_file("sample.txt", sample, sizeof sample - 1); 17 | } 18 | -------------------------------------------------------------------------------- /user/userprogs/rox-simple.c: -------------------------------------------------------------------------------- 1 | /** Ensure that the executable of a running process cannot be 2 | modified. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | int fd, r; 8 | char buffer[16]; 9 | 10 | fd = open("rox-simple", O_RDWR); 11 | if (fd <= 1) { 12 | panic("Failed to open \"rox-simple\""); 13 | } 14 | 15 | r = read(fd, buffer, sizeof buffer); 16 | assert(r == (int)sizeof buffer); 17 | assert(write(fd, buffer, sizeof buffer) == -1, 18 | "executable files should not be writable when running"); 19 | 20 | close(fd); 21 | } 22 | -------------------------------------------------------------------------------- /user/vm/mmap-close.c: -------------------------------------------------------------------------------- 1 | /* Verifies that memory mappings persist after file close. */ 2 | 3 | #include "arc4.h" 4 | #include "sample.inc" 5 | #include "user.h" 6 | 7 | #define ACTUAL ((void*)0x10000000) 8 | 9 | void main() { 10 | int fd; 11 | mapid_t map; 12 | 13 | assert((fd = open("sample.txt", 0)) > 1, "open \"sample.txt\""); 14 | assert((map = mmap(fd, ACTUAL)) != MAP_FAILED, "mmap \"sample.txt\""); 15 | 16 | close(fd); 17 | 18 | if (memcmp(ACTUAL, sample, strlen(sample))) panic("read of mmap'd file reported bad data"); 19 | 20 | munmap(map); 21 | } 22 | -------------------------------------------------------------------------------- /src/fs/disk/path.rs: -------------------------------------------------------------------------------- 1 | /// Path. 2 | /// 3 | /// We uses [`alloc::string::String`] methods for path 4 | /// manipulation. 5 | pub struct Path(alloc::string::String); 6 | 7 | impl Path { 8 | pub fn exists(path: Self) -> bool { 9 | super::DISKFS.get().root_dir.lock().exists(&path) 10 | } 11 | } 12 | 13 | impl From<&str> for Path { 14 | fn from(value: &str) -> Self { 15 | Path(value.into()) 16 | } 17 | } 18 | 19 | impl core::ops::Deref for Path { 20 | type Target = alloc::string::String; 21 | fn deref(&self) -> &Self::Target { 22 | &self.0 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/sync/spin.rs: -------------------------------------------------------------------------------- 1 | use core::sync::atomic::{AtomicBool, Ordering::SeqCst}; 2 | 3 | use crate::sbi::interrupt; 4 | use crate::sync::Lock; 5 | 6 | /// Spin lock. 7 | #[derive(Debug, Default)] 8 | pub struct Spin(AtomicBool); 9 | 10 | impl Spin { 11 | pub const fn new() -> Self { 12 | Self(AtomicBool::new(false)) 13 | } 14 | } 15 | 16 | impl Lock for Spin { 17 | fn acquire(&self) { 18 | while self.0.fetch_or(true, SeqCst) { 19 | assert!(interrupt::get(), "may block"); 20 | } 21 | } 22 | 23 | fn release(&self) { 24 | self.0.store(false, SeqCst); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /user/userprogs/open-trunc.c: -------------------------------------------------------------------------------- 1 | /** Truncates a file, and then checks if it can be accessed properly. */ 2 | 3 | #include "user.h" 4 | 5 | void main() { 6 | char buf[32]; 7 | 8 | int fd1 = open("truncfile", O_CREATE | O_TRUNC | O_RDWR); 9 | assert(write(fd1, "abcd", 4) == 4); 10 | 11 | int fd2 = open("truncfile", O_TRUNC | O_RDWR); 12 | 13 | assert(write(fd2, "abcdef", 6) == 6); 14 | seek(fd2, 0); 15 | assert(read(fd2, buf, sizeof buf) == 6); // fd2 was at position 0 16 | assert(read(fd1, buf, sizeof buf) == 2); // fd1 was at position 4 17 | 18 | close(fd1); 19 | close(fd2); 20 | } 21 | -------------------------------------------------------------------------------- /user/vm/child-qsort.c: -------------------------------------------------------------------------------- 1 | /* Reads a 128 kB file onto the stack and "sorts" the bytes in 2 | it, using quick sort, a multi-pass divide and conquer 3 | algorithm. The sorted data is written back to the same file 4 | in-place. */ 5 | 6 | #include "user.h" 7 | #include "qsort.h" 8 | 9 | void main(int argc, char* argv[]) { 10 | int fd; 11 | uint8 buf[128 * 1024]; 12 | size_t size; 13 | 14 | assert((fd = open(argv[1], O_RDWR)) > 2); 15 | 16 | size = read(fd, buf, sizeof buf); 17 | qsort_bytes(buf, sizeof buf); 18 | seek(fd, 0); 19 | write(fd, buf, size); 20 | close(fd); 21 | 22 | exit(72); 23 | } 24 | -------------------------------------------------------------------------------- /tool/bookmarks/lab1.toml: -------------------------------------------------------------------------------- 1 | # case_name = ["args", option] 2 | # Alarms, 18 3 | alarm-zero = ["", 2] 4 | alarm-negative = ["", 2] 5 | alarm-simultaneous = ["", 5] 6 | alarm-single = ["", 4] 7 | alarm-multiple = ["", 5] 8 | # Priority Basic, 36 9 | priority-alarm = ["", 6] 10 | priority-change = ["", 6] 11 | priority-condvar = ["", 6] 12 | priority-fifo = ["", 6] 13 | priority-preempt = ["", 6] 14 | priority-sema = ["", 6] 15 | # Priority Donation, 46 16 | donation-chain = ["", 10] 17 | donation-lower = ["", 6] 18 | donation-nest = ["", 6] 19 | donation-one = ["", 6] 20 | donation-sema = ["", 6] 21 | donation-two = ["", 6] 22 | donation-three = ["", 6] 23 | -------------------------------------------------------------------------------- /test/unit/virtio/repeat.rs: -------------------------------------------------------------------------------- 1 | use crate::device::virtio::{self, Virtio}; 2 | 3 | pub fn main() { 4 | let buf1 = [1; virtio::SECTOR_SIZE]; 5 | let buf2 = [0; virtio::SECTOR_SIZE]; 6 | let mut buf3 = [0; virtio::SECTOR_SIZE]; 7 | 8 | for s in 0..10 { 9 | Virtio::write_sector(s, &buf1); 10 | Virtio::read_sector(s, &mut buf3); 11 | for i in buf3 { 12 | assert_eq!(i, 1); 13 | } 14 | 15 | Virtio::write_sector(s, &buf2); 16 | Virtio::read_sector(s, &mut buf3); 17 | for i in buf3 { 18 | assert_eq!(i, 0); 19 | } 20 | } 21 | 22 | kprintln!("Virtio repeat test done."); 23 | } 24 | -------------------------------------------------------------------------------- /src/linker.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH(riscv) 2 | ENTRY(_entry) 3 | 4 | SECTIONS 5 | { 6 | . = 0xFFFFFFC080200000; 7 | 8 | /* AT(...) gives the load address of this section, which tells 9 | the boot loader where to load the kernel in physical memory */ 10 | .text : AT(0x80200000) { 11 | *(.text.entry) 12 | *(.text .text.*) 13 | } 14 | 15 | . = ALIGN(4K); 16 | etext = .; 17 | 18 | .data : { *(.*data*) } 19 | 20 | . = ALIGN(16); 21 | .bss : { 22 | sbss = .; 23 | *(.*bss*) 24 | ebss = .; 25 | } 26 | 27 | . = ALIGN(4K); 28 | ekernel = .; 29 | 30 | /DISCARD/ : { 31 | *(.eh_frame) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /user/userprogs/close-by-child.c: -------------------------------------------------------------------------------- 1 | /** Opens a file and then runs a subprocess that tries to close 2 | the file. (Pintos does not have inheritance of file handles, 3 | so this must fail.) The parent process then attempts to use 4 | the file handle, which must succeed. */ 5 | 6 | #include "sample.inc" 7 | #include "user.h" 8 | 9 | void main() { 10 | int fd; 11 | 12 | assert((fd = open("sample.txt", O_RDWR)) > 1); 13 | 14 | char fd_str[5]; 15 | itoa(fd_str, fd); 16 | const char* args[] = {"child-close", fd_str, NULL}; 17 | 18 | assert(wait(exec("child-close", args)) == 64); 19 | 20 | check_file_handle(fd, "sample.txt", sample, sizeof sample - 1); 21 | } 22 | -------------------------------------------------------------------------------- /test/unit/fs/disk/readimg.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::disk::{Swap, DISKFS}; 2 | use crate::fs::FileSys; 3 | use crate::io::prelude::*; 4 | use crate::Result; 5 | 6 | pub fn main() -> Result<()> { 7 | let file = DISKFS.open("exit".into())?; 8 | kprintln!("[DISKFS.READIMG] Length of exit: {}", file.len()?); 9 | 10 | kprintln!( 11 | "[DISKFS.READIMG] Swap file length: {}, pages: {}", 12 | Swap::len(), 13 | Swap::page_num() 14 | ); 15 | let mut swap = Swap::lock(); 16 | swap.write_from(0xfabcdeusize)?; 17 | swap.rewind()?; 18 | assert_eq!(swap.read_into::()?, 0xfabcde); 19 | kprintln!("[DISKFS.READIMG] Swap read/write works."); 20 | 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /user/lib/usys.pl: -------------------------------------------------------------------------------- 1 | # Generate usys.S, the stubs for syscalls. 2 | 3 | print "# generated by usys.pl - do not edit\n"; 4 | 5 | print "#include \"syscall.h\"\n"; 6 | 7 | sub entry { 8 | my $name = shift; 9 | my $uname = uc($name); 10 | print ".global $name\n"; 11 | print "$name:\n"; 12 | print " li a7, SYS_$uname\n"; 13 | print " ecall\n"; 14 | print " ret\n"; 15 | } 16 | 17 | entry("halt"); 18 | entry("exit"); 19 | entry("exec"); 20 | entry("wait"); 21 | entry("remove"); 22 | entry("open"); 23 | entry("read"); 24 | entry("write"); 25 | entry("seek"); 26 | entry("tell"); 27 | entry("close"); 28 | entry("fstat"); 29 | entry("mmap"); 30 | entry("munmap"); 31 | entry("chdir"); 32 | entry("mkdir"); 33 | -------------------------------------------------------------------------------- /user/vm/mmap-twice.c: -------------------------------------------------------------------------------- 1 | /* Maps the same file into memory twice and verifies that the 2 | same data is readable in both. */ 3 | 4 | #include "sample.inc" 5 | #include "user.h" 6 | 7 | void main() { 8 | char* actual[2] = {(char*)0x10000000, (char*)0x20000000}; 9 | size_t i; 10 | int fd[2]; 11 | 12 | for (i = 0; i < 2; i++) { 13 | assert((fd[i] = open("sample.txt", 0)) > 2, "open \"sample.txt\" #%d", i); 14 | assert(mmap(fd[i], actual[i]) != MAP_FAILED, "mmap \"sample.txt\" #%d at %p", i, 15 | (void*)actual[i]); 16 | } 17 | 18 | for (i = 0; i < 2; i++) 19 | assert(!memcmp(actual[i], sample, strlen(sample)), "compare mmap'd file %d against data", 20 | i); 21 | } 22 | -------------------------------------------------------------------------------- /user/userprogs/boundary-bad.c: -------------------------------------------------------------------------------- 1 | /** Invokes an open system call with the path string straddling a 2 | page boundary such that the first byte of the string is valid 3 | but the remainder of the string is in invalid memory. Similar 4 | to passing an invalid pointer to `open`, this call should return 5 | with -1. */ 6 | 7 | #include "user.h" 8 | 9 | /* data will be at the end of .bss section */ 10 | static char data[4096] __attribute__ ((section (".testEndmem,\"aw\",@nobits#"))); 11 | 12 | void main() { 13 | char* p = (char*)ROUND_UP(data + sizeof data - 1, 4096) - 1; 14 | *p = 'a'; 15 | 16 | /* only the first byte of string p is valid */ 17 | assert(open(p, O_CREATE) == -1, "part of the path is in a invalid memory space"); 18 | } 19 | -------------------------------------------------------------------------------- /user/vm/mmap-zero.c: -------------------------------------------------------------------------------- 1 | /* Tries to map a zero-length file, which may or may not work but 2 | should not terminate the process or crash. 3 | Then dereferences the address that we tried to map, 4 | and the process must be terminated with -1 exit code. */ 5 | 6 | #include "user.h" 7 | 8 | void main() { 9 | char* data = (char*)0x7f000000; 10 | int fd; 11 | 12 | assert((fd = open("empty", O_CREATE | O_TRUNC | O_RDWR)) > 2, "open \"empty\""); 13 | 14 | /* Calling mmap() might succeed or fail. We don't care. */ 15 | printf("mmap \"empty\""); 16 | mmap(fd, data); 17 | 18 | /* Regardless of whether the call worked, *data should cause 19 | the process to be terminated. */ 20 | panic("unmapped memory is readable (%d)", *data); 21 | } 22 | -------------------------------------------------------------------------------- /user/vm/child-linear.c: -------------------------------------------------------------------------------- 1 | /* Child process of page-parallel. 2 | Encrypts 1 MB of zeros, then decrypts it, and ensures that 3 | the zeros are back. */ 4 | 5 | #include "arc4.h" 6 | #include "user.h" 7 | 8 | #define SIZE (1024 * 1024) 9 | static char buf[SIZE]; 10 | 11 | void main(int argc, char* argv[]) { 12 | const char* key = argv[argc - 1]; 13 | struct arc4 arc4; 14 | size_t i; 15 | 16 | /* Encrypt zeros. */ 17 | arc4_init(&arc4, key, strlen(key)); 18 | arc4_crypt(&arc4, buf, SIZE); 19 | 20 | /* Decrypt back to zeros. */ 21 | arc4_init(&arc4, key, strlen(key)); 22 | arc4_crypt(&arc4, buf, SIZE); 23 | 24 | /* Check that it's all zeros. */ 25 | for (i = 0; i < SIZE; i++) 26 | if (buf[i] != '\0') panic("byte %d != 0", i); 27 | } 28 | -------------------------------------------------------------------------------- /user/vm/mmap-read.c: -------------------------------------------------------------------------------- 1 | /* Uses a memory mapping to read a file. */ 2 | 3 | #include "sample.inc" 4 | #include "user.h" 5 | 6 | void main() { 7 | char* actual = (char*)0x10000000; 8 | int fd; 9 | mapid_t map; 10 | size_t i; 11 | 12 | assert((fd = open("sample.txt", 0)) > 2); 13 | assert((map = mmap(fd, actual)) != MAP_FAILED); 14 | 15 | /* Check that data is correct. */ 16 | if (memcmp(actual, sample, strlen(sample))) panic("read of mmap'd file reported bad data"); 17 | 18 | /* Verify that data is followed by zeros. */ 19 | for (i = strlen(sample); i < 4096; i++) 20 | if (actual[i] != 0) 21 | panic("byte %d of mmap'd region has value %02hhx (should be 0)", i, actual[i]); 22 | 23 | munmap(map); 24 | close(fd); 25 | } 26 | -------------------------------------------------------------------------------- /user/lib/user.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | ENTRY( _main ) 3 | 4 | SECTIONS 5 | { 6 | . = 0x1000; 7 | 8 | .text : { 9 | *(.text .text.*) 10 | } 11 | 12 | .rodata : { 13 | . = ALIGN(16); 14 | *(.srodata .srodata.*) /* do not need to distinguish this from .rodata */ 15 | . = ALIGN(16); 16 | *(.rodata .rodata.*) 17 | . = ALIGN(0x1000); 18 | } 19 | 20 | .data : { 21 | . = ALIGN(16); 22 | *(.sdata .sdata.*) /* do not need to distinguish this from .data */ 23 | . = ALIGN(16); 24 | *(.data .data.*) 25 | } 26 | 27 | .bss : { 28 | . = ALIGN(16); 29 | *(.sbss .sbss.*) /* do not need to distinguish this from .bss */ 30 | . = ALIGN(16); 31 | *(.bss .bss.*) 32 | *(.testEndmem) 33 | } 34 | 35 | PROVIDE(end = .); 36 | } 37 | -------------------------------------------------------------------------------- /user/vm/pt-grow-stk-sc.c: -------------------------------------------------------------------------------- 1 | /* This test checks that the stack is properly extended even if 2 | the first access to a stack location occurs inside a system 3 | call. */ 4 | 5 | #include "sample.inc" 6 | #include "user.h" 7 | 8 | void main(void) { 9 | int fd; 10 | int slen = strlen(sample); 11 | char buf2[65536]; 12 | 13 | /* Write file via write(). */ 14 | assert(((fd = open("sample.txt", O_CREATE | O_WRONLY | O_TRUNC)) > 2)); 15 | assert(write(fd, sample, slen) == slen); 16 | close(fd); 17 | 18 | /* Read back via read(). */ 19 | assert((fd = open("sample.txt", 0)) > 2); 20 | assert(read(fd, buf2 + 32768, slen) == slen); 21 | 22 | assert(!memcmp(sample, buf2 + 32768, slen), "compare written data against read data"); 23 | close(fd); 24 | } 25 | -------------------------------------------------------------------------------- /user/vm/page-shuffle.c: -------------------------------------------------------------------------------- 1 | /* Shuffles a 128 kB data buffer 10 times, printing the checksum 2 | after each time. */ 3 | 4 | #include "cksum.h" 5 | #include "random.h" 6 | #include "user.h" 7 | 8 | #define SIZE (128 * 1024) 9 | 10 | static char buf[SIZE]; 11 | 12 | uint32 _init = 3115322833; 13 | uint32 _shuffle[] = {1691062564, 1973575879, 1647619479, 96566261, 3885786467, 14 | 3022003332, 3614934266, 2704001777, 735775156, 1864109763}; 15 | 16 | void main() { 17 | size_t i; 18 | 19 | /* Initialize. */ 20 | for (i = 0; i < sizeof buf; i++) buf[i] = i * 257; 21 | assert(cksum(buf, sizeof buf) == _init); 22 | 23 | /* Shuffle repeatedly. */ 24 | for (i = 0; i < 10; i++) { 25 | shuffle(buf, sizeof buf, 1); 26 | assert(cksum(buf, sizeof buf) == _shuffle[i]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/trap/syscall.rs: -------------------------------------------------------------------------------- 1 | //! Syscall handlers 2 | //! 3 | 4 | #![allow(dead_code)] 5 | 6 | /* -------------------------------------------------------------------------- */ 7 | /* SYSCALL NUMBER */ 8 | /* -------------------------------------------------------------------------- */ 9 | 10 | const SYS_HALT: usize = 1; 11 | const SYS_EXIT: usize = 2; 12 | const SYS_EXEC: usize = 3; 13 | const SYS_WAIT: usize = 4; 14 | const SYS_REMOVE: usize = 5; 15 | const SYS_OPEN: usize = 6; 16 | const SYS_READ: usize = 7; 17 | const SYS_WRITE: usize = 8; 18 | const SYS_SEEK: usize = 9; 19 | const SYS_TELL: usize = 10; 20 | const SYS_CLOSE: usize = 11; 21 | const SYS_FSTAT: usize = 12; 22 | 23 | pub fn syscall_handler(_id: usize, _args: [usize; 3]) -> isize { 24 | // TODO: LAB2 impl 25 | -1 26 | } 27 | -------------------------------------------------------------------------------- /user/vm/child-sort.c: -------------------------------------------------------------------------------- 1 | /* Reads a 128 kB file into static data and "sorts" the bytes in 2 | it, using counting sort, a single-pass algorithm. The sorted 3 | data is written back to the same file in-place. */ 4 | 5 | #include "user.h" 6 | 7 | uint8 buf[128 * 1024]; 8 | size_t histogram[256]; 9 | 10 | void main(int argc, char* argv[]) { 11 | int fd; 12 | uint8* p; 13 | size_t size; 14 | size_t i; 15 | 16 | assert((fd = open(argv[1], O_RDWR)) > 2); 17 | 18 | size = read(fd, buf, sizeof buf); 19 | for (i = 0; i < size; i++) histogram[buf[i]]++; 20 | p = buf; 21 | for (i = 0; i < sizeof histogram / sizeof *histogram; i++) { 22 | size_t j = histogram[i]; 23 | while (j-- > 0) *p++ = i; 24 | } 25 | seek(fd, 0); 26 | write(fd, buf, size); 27 | close(fd); 28 | exit(123); 29 | } 30 | -------------------------------------------------------------------------------- /user/userprogs/boundary-normal.c: -------------------------------------------------------------------------------- 1 | #include "sample.inc" 2 | #include "user.h" 3 | 4 | /* data will be at the end of .bss section */ 5 | static char data[4096 * 3]; 6 | 7 | void main() { 8 | int fd; 9 | 10 | /* open a path that's across the page boundary */ 11 | char* path = "sample.txt"; 12 | char* p = (char*)ROUND_UP(data, 4096); 13 | char* npath = p - strlen(path) / 2; 14 | strcpy(npath, path); 15 | assert((fd = open(npath, O_RDWR)) > 2); 16 | 17 | /* read across the page boundary */ 18 | char* read_buf = (char*)(ROUND_UP(data + 4096, 4096) - sizeof sample / 2); 19 | assert(read(fd, read_buf, sizeof sample) == sizeof sample - 1); 20 | assert(strcmp(read_buf, sample) == 0); 21 | 22 | /* write across the page boundary */ 23 | seek(fd, 0); 24 | assert(write(fd, read_buf, sizeof sample - 1) == sizeof sample - 1); 25 | } 26 | -------------------------------------------------------------------------------- /tool/src/build.rs: -------------------------------------------------------------------------------- 1 | use std::process; 2 | 3 | const MAKE_DIR: &str = ".."; 4 | 5 | pub fn main(args: crate::cli::BuildArgs) -> std::io::Result<()> { 6 | if args.rebuild { 7 | make(args.verbose, &vec!["clean-tacos"])?; 8 | make(args.verbose, &vec![])?; 9 | } else if args.clean { 10 | make(args.verbose, &vec!["clean-tacos"])?; 11 | } else { 12 | make(args.verbose, &vec![])?; 13 | } 14 | Ok(()) 15 | } 16 | 17 | fn make(verbose: bool, args: &Vec<&str>) -> std::io::Result<()> { 18 | let mut child = process::Command::new("make"); 19 | child.current_dir(MAKE_DIR).stdin(process::Stdio::piped()); 20 | if !verbose { 21 | child 22 | .stdout(process::Stdio::null()) 23 | .stderr(process::Stdio::null()); 24 | } 25 | let mut child = child.args(args).spawn()?; 26 | child.wait()?; 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /user/vm/mmap-write.c: -------------------------------------------------------------------------------- 1 | /* Writes to a file through a mapping, and unmaps the file, 2 | then reads the data in the file back using the read system 3 | call to verify. */ 4 | 5 | #include "sample.inc" 6 | #include "user.h" 7 | 8 | #define ACTUAL ((void*)0x10000000) 9 | 10 | void main() { 11 | int fd, i; 12 | mapid_t map; 13 | char buf[1024]; 14 | 15 | /* Write file via mmap. */ 16 | assert((fd = open("sample.txt", O_RDWR)) > 2); 17 | assert((map = mmap(fd, ACTUAL)) != MAP_FAILED); 18 | 19 | memset(ACTUAL, 42, strlen(sample)); 20 | munmap(map); 21 | 22 | /* Read back via read(). */ 23 | read(fd, buf, strlen(sample)); 24 | for (i = 0; i < strlen(sample); i++) 25 | assert(buf[i] == 42, "check that mmap write was successful"); 26 | 27 | /* Write origin content back */ 28 | write(fd, sample, strlen(sample)); 29 | close(fd); 30 | } 31 | -------------------------------------------------------------------------------- /user/vm/page-linear.c: -------------------------------------------------------------------------------- 1 | /* Encrypts, then decrypts, 2 MB of memory and verifies that the 2 | values are as they should be. */ 3 | 4 | #include "arc4.h" 5 | #include "user.h" 6 | 7 | #define SIZE (2 * 1024 * 1024) 8 | 9 | static char buf[SIZE]; 10 | 11 | void main() { 12 | arc4 arc4; 13 | size_t i; 14 | 15 | /* Initialize to 0x5a. */ 16 | memset(buf, 0x5a, sizeof buf); 17 | 18 | /* Check that it's all 0x5a. */ 19 | for (i = 0; i < SIZE; i++) 20 | if (buf[i] != 0x5a) panic("byte %d != 0x5a", i); 21 | 22 | /* Encrypt zeros. */ 23 | arc4_init(&arc4, "foobar", 6); 24 | arc4_crypt(&arc4, buf, SIZE); 25 | 26 | /* Decrypt back to zeros. */ 27 | arc4_init(&arc4, "foobar", 6); 28 | arc4_crypt(&arc4, buf, SIZE); 29 | 30 | /* Check that it's all 0x5a. */ 31 | for (i = 0; i < SIZE; i++) 32 | if (buf[i] != 0x5a) panic("byte %d != 0x5a", i); 33 | } 34 | -------------------------------------------------------------------------------- /src/fs/disk/swap.rs: -------------------------------------------------------------------------------- 1 | //! Swap file. 2 | //! 3 | // Swap may not be used. 4 | #![allow(dead_code)] 5 | use super::DISKFS; 6 | use crate::fs::{File, FileSys}; 7 | use crate::io::Seek; 8 | use crate::mem::PG_SIZE; 9 | use crate::sync::{Lazy, Mutex, MutexGuard, Primitive}; 10 | 11 | pub struct Swap; 12 | 13 | static SWAPFILE: Lazy> = Lazy::new(|| { 14 | Mutex::new( 15 | DISKFS 16 | .open(".glbswap".into()) 17 | .expect("swap file \".glbswap\" should exist"), 18 | ) 19 | }); 20 | 21 | impl Swap { 22 | pub fn len() -> usize { 23 | SWAPFILE.lock().len().unwrap() 24 | } 25 | 26 | pub fn page_num() -> usize { 27 | // Round down. 28 | Self::len() / PG_SIZE 29 | } 30 | 31 | /// TODO: Design high-level interfaces, or do in lab3? 32 | pub fn lock() -> MutexGuard<'static, File, Primitive> { 33 | SWAPFILE.lock() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /user/vm/mmap-shuffle.c: -------------------------------------------------------------------------------- 1 | /* Creates a 128 kB file and repeatedly shuffles data in it 2 | through a memory mapping. */ 3 | 4 | #include "cksum.h" 5 | #include "random.h" 6 | #include "user.h" 7 | 8 | #define SIZE (128 * 1024) 9 | 10 | static char* buf = (char*)0x10000000; 11 | static char zeros[SIZE]; 12 | 13 | void main() { 14 | size_t i; 15 | int fd; 16 | 17 | /* Create file, mmap. */ 18 | assert((fd = open("buffer", O_CREATE | O_RDWR)) > 2); 19 | assert(write(fd, zeros, SIZE) == SIZE, "write zeros"); 20 | assert(mmap(fd, buf) != MAP_FAILED, "mmap \"buffer\""); 21 | 22 | /* Initialize. */ 23 | for (i = 0; i < SIZE; i++) buf[i] = i * 257; 24 | printf("init: cksum=%d", cksum(buf, SIZE)); 25 | 26 | /* Shuffle repeatedly. */ 27 | for (i = 0; i < 10; i++) { 28 | shuffle(buf, SIZE, 1); 29 | printf("shuffle %d: cksum=%d", i, cksum(buf, SIZE)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /user/userprogs/rox-child.c: -------------------------------------------------------------------------------- 1 | /** Ensure that the executable of a running process cannot be 2 | modified, even by a child process. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | const char* args[] = {"child-rox", "1"}; 8 | int fd, child, r; 9 | char buffer[16]; 10 | 11 | /* Open child-rox, read from it, write back same data. */ 12 | assert((fd = open("child-rox", O_RDWR)) > 2); 13 | 14 | r = read(fd, buffer, sizeof buffer); 15 | assert(r == (int)sizeof buffer); 16 | 17 | seek(fd, 0); 18 | 19 | r = write(fd, buffer, sizeof buffer); 20 | assert(r == (int)sizeof buffer); 21 | 22 | /* Execute child-rox and wait for it. */ 23 | assert((child = exec(args[0], args)) >= 0); 24 | assert(wait(child) == 12); 25 | 26 | /* Write to child-rox again. */ 27 | seek(fd, 0); 28 | r = write(fd, buffer, sizeof buffer); 29 | assert(r == (int)sizeof buffer); 30 | close(fd); 31 | } 32 | -------------------------------------------------------------------------------- /user/userprogs/rox-multichild.c: -------------------------------------------------------------------------------- 1 | /** Ensure that the executable of a running process cannot be 2 | modified, even in the presence of multiple children. */ 3 | 4 | #include "user.h" 5 | 6 | void main() { 7 | const char* args[] = {"child-rox", "5"}; 8 | int fd, child, r; 9 | char buffer[16]; 10 | 11 | /* Open child-rox, read from it, write back same data. */ 12 | assert((fd = open("child-rox", O_RDWR)) > 2); 13 | 14 | r = read(fd, buffer, sizeof buffer); 15 | assert(r == (int)sizeof buffer); 16 | 17 | seek(fd, 0); 18 | 19 | r = write(fd, buffer, sizeof buffer); 20 | assert(r == (int)sizeof buffer); 21 | 22 | /* Execute child-rox and wait for it. */ 23 | assert((child = exec(args[0], args)) >= 0); 24 | assert(wait(child) == 12); 25 | 26 | /* Write to child-rox again. */ 27 | seek(fd, 0); 28 | r = write(fd, buffer, sizeof buffer); 29 | assert(r == (int)sizeof buffer); 30 | close(fd); 31 | } 32 | -------------------------------------------------------------------------------- /user/vm/mmap-inherit.c: -------------------------------------------------------------------------------- 1 | /* Maps a file into memory and runs child-inherit to verify that 2 | mappings are not inherited. */ 3 | 4 | #include "sample.inc" 5 | #include "user.h" 6 | 7 | void main() { 8 | char* actual = (char*)0x54321000; 9 | int fd; 10 | pid_t child; 11 | 12 | /* Open file, map, verify data. */ 13 | assert((fd = open("sample.txt", O_RDONLY)) > 2, "open \"sample.txt\""); 14 | assert(mmap(fd, actual) != MAP_FAILED, "mmap \"sample.txt\""); 15 | if (memcmp(actual, sample, strlen(sample))) panic("read of mmap'd file reported bad data"); 16 | 17 | /* Spawn child and wait. */ 18 | const char* args[] = {"child-inherit", 0}; 19 | assert((child = exec(args[0], args)) != -1, "exec \"child-inherit\""); 20 | assert(wait(child) == -1, "wait for child (should return -1)"); 21 | 22 | /* Verify data again. */ 23 | assert(!memcmp(actual, sample, strlen(sample)), 24 | "checking that mmap'd file still has same data"); 25 | } 26 | -------------------------------------------------------------------------------- /tacos: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if at least one argument is provided 4 | if [ "$#" -lt 1 ]; then 5 | echo "Usage: $0 [additional arguments for qemu]" 6 | exit 1 7 | fi 8 | 9 | make 10 | 11 | # The kernel to be executed with QEMU 12 | KERNEL=$1 13 | 14 | # Copy the disk.img to /tmp directory 15 | rm -f /tmp/disk.img 16 | cp "build/disk.img" /tmp/ 17 | 18 | if [ $? -ne 0 ]; then 19 | echo "Failed to copy file to /tmp. Ensure the file exists and you have permission to copy." 20 | exit 1 21 | fi 22 | 23 | # Shift the first argument so only additional arguments remain 24 | shift 25 | 26 | # Run the binary with qemu, passing any additional arguments 27 | qemu-system-riscv64 \ 28 | -machine virt \ 29 | -display none \ 30 | -bios fw_jump.bin \ 31 | -global virtio-mmio.force-legacy=false \ 32 | --blockdev driver=file,node-name=disk,filename=/tmp/disk.img \ 33 | -device virtio-blk-device,drive=disk,bus=virtio-mmio-bus.0 \ 34 | -kernel "$KERNEL" "$@" 35 | -------------------------------------------------------------------------------- /tool/bookmarks/lab3.toml: -------------------------------------------------------------------------------- 1 | # case_name = ["args", option] 2 | # MMAP: 24 3 | mmap-clean = ["", 3] 4 | mmap-close = ["", 3] 5 | mmap-read = ["", 3] 6 | mmap-remove = ["", 3] 7 | mmap-twice = ["", 3] 8 | mmap-unmap = ["", 3] 9 | mmap-write = ["", 3] 10 | mmap-shuffle = ["", 3] 11 | # Paging: 30 12 | page-linear = ["", 9, 600] 13 | page-parallel = ["", 3, 600] 14 | page-merge-mm = ["", 3, 600] 15 | page-merge-par = ["", 3, 600] 16 | page-merge-seq = ["", 9, 600] 17 | page-merge-stk = ["", 3, 600] 18 | # Robustness: 46 19 | pt-bad-addr = ["", 2] 20 | pt-bad-read = ["", 2] 21 | pt-write-code = ["", 2] 22 | pt-write-code2 = ["", 2] 23 | pt-big-stk-obj = ["", 3] 24 | pt-grow-bad = ["", 3] 25 | pt-grow-stack = ["", 3] 26 | pt-grow-stk-sc = ["", 3] 27 | mmap-bad-fd = ["", 2] 28 | mmap-inherit = ["", 3] 29 | mmap-null = ["", 3] 30 | mmap-zero = ["", 3] 31 | mmap-misalign = ["", 3] 32 | mmap-over-code = ["", 3] 33 | mmap-over-data = ["", 3] 34 | mmap-over-stk = ["", 3] 35 | mmap-overlap = ["", 3] 36 | -------------------------------------------------------------------------------- /user/lib/syscall.h: -------------------------------------------------------------------------------- 1 | #define SYS_HALT 1 /**< Halt the operating system. */ 2 | #define SYS_EXIT 2 /**< Terminate this process. */ 3 | #define SYS_EXEC 3 /**< Start another process. */ 4 | #define SYS_WAIT 4 /**< Wait for a child process to die. */ 5 | #define SYS_REMOVE 5 /**< Delete a file. */ 6 | #define SYS_OPEN 6 /**< Open a file. */ 7 | #define SYS_READ 7 /**< Read from a file. */ 8 | #define SYS_WRITE 8 /**< Write to a file. */ 9 | #define SYS_SEEK 9 /**< Change position in a file. */ 10 | #define SYS_TELL 10 /**< Report current position in a file. */ 11 | #define SYS_CLOSE 11 /**< Close a file. */ 12 | #define SYS_FSTAT 12 /**< Get metadata about a file */ 13 | 14 | /* Project 3 and optionally project 4. */ 15 | #define SYS_MMAP 13 /**< Map a file into memory. */ 16 | #define SYS_MUNMAP 14 /**< Remove a memory mapping. */ 17 | 18 | /* Project 4 only. */ 19 | #define SYS_CHDIR 15 /**< Change the current directory. */ 20 | #define SYS_MKDIR 16 /**< Create a directory. */ 21 | -------------------------------------------------------------------------------- /src/sync.rs: -------------------------------------------------------------------------------- 1 | //! Synchronization and Interior Mutability 2 | //! 3 | 4 | pub mod condvar; 5 | pub mod intr; 6 | pub mod lazy; 7 | pub mod mutex; 8 | pub mod once; 9 | pub mod sema; 10 | pub mod sleep; 11 | pub mod spin; 12 | 13 | pub use self::condvar::Condvar; 14 | pub use self::intr::Intr; 15 | pub use self::lazy::Lazy; 16 | pub use self::mutex::{Mutex, MutexGuard}; 17 | pub use self::once::{Once, OnceCell}; 18 | pub use self::sema::Semaphore; 19 | pub use self::sleep::Sleep; 20 | pub use self::spin::Spin; 21 | pub type Primitive = sleep::Sleep; 22 | 23 | /// Lock trait is used to synchronize between different threads, the implementation 24 | /// of Mutex relies on this trait. Of cource we want every struct that implements 25 | /// to be [Sync], thus we can send its reference through different threads and 26 | /// use the reference to synchronize. 27 | /// 28 | /// Check out comments in [`Mutex`] for more details. 29 | pub trait Lock: Default + Sync + 'static { 30 | fn acquire(&self); 31 | fn release(&self); 32 | } 33 | -------------------------------------------------------------------------------- /test/unit/sync/condvar.rs: -------------------------------------------------------------------------------- 1 | use alloc::sync::Arc; 2 | 3 | use crate::sync::{Condvar, Mutex}; 4 | use crate::thread::{self, Status}; 5 | 6 | pub fn main() { 7 | let s = Arc::new((Condvar::new(), Mutex::new(0))); 8 | let s1 = s.clone(); 9 | let p = thread::spawn("producer_six", move || producer_six(s1)); 10 | 11 | let (cvar, lock) = &*s; 12 | let mut guard = lock.lock(); 13 | while *guard < 6 { 14 | // Waiting for producer_one, producer_all and consumer. 15 | cvar.wait(&mut guard); 16 | } 17 | thread::schedule(); 18 | 19 | assert_eq!(p.status(), Status::Dying); 20 | kprintln!("Main continue."); 21 | } 22 | 23 | fn producer_six(s: Arc<(Condvar, Mutex)>) { 24 | let (cvar, lock) = &*s; 25 | kprintln!("Producer_six begins to work."); 26 | for i in 0..6 { 27 | let mut guard = lock.lock(); 28 | *guard += 1; 29 | cvar.notify_all(); 30 | kprintln!("Producer_six iteration {}.", i); 31 | thread::schedule(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/sync/sleep.rs: -------------------------------------------------------------------------------- 1 | use alloc::sync::Arc; 2 | use core::cell::RefCell; 3 | 4 | use crate::sync::{Lock, Semaphore}; 5 | use crate::thread::{self, Thread}; 6 | 7 | /// Sleep lock. Uses [`Semaphore`] under the hood. 8 | #[derive(Clone)] 9 | pub struct Sleep { 10 | inner: Semaphore, 11 | holder: RefCell>>, 12 | } 13 | 14 | impl Default for Sleep { 15 | fn default() -> Self { 16 | Self { 17 | inner: Semaphore::new(1), 18 | holder: Default::default(), 19 | } 20 | } 21 | } 22 | 23 | impl Lock for Sleep { 24 | fn acquire(&self) { 25 | self.inner.down(); 26 | self.holder.borrow_mut().replace(thread::current()); 27 | } 28 | 29 | fn release(&self) { 30 | assert!(Arc::ptr_eq( 31 | self.holder.borrow().as_ref().unwrap(), 32 | &thread::current() 33 | )); 34 | 35 | self.holder.borrow_mut().take().unwrap(); 36 | self.inner.up(); 37 | } 38 | } 39 | 40 | unsafe impl Sync for Sleep {} 41 | -------------------------------------------------------------------------------- /test/schedule/priority/preempt.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::thread; 3 | 4 | const YIELD_TIMES: usize = 5; 5 | 6 | fn yielding_thread(main_thread: Arc) { 7 | assert_eq!(main_thread.status(), Status::Ready); 8 | 9 | for i in 0..YIELD_TIMES { 10 | kprintln!("Child thread yield {} times.", i); 11 | schedule(); 12 | } 13 | 14 | assert_eq!( 15 | main_thread.status(), 16 | Status::Ready, 17 | "Main thread should exit after child thread!" 18 | ); 19 | kprintln!("Thread 2 exiting..."); 20 | } 21 | 22 | pub fn main() { 23 | kprintln!("Creating a high-priority thread 2"); 24 | 25 | let main_thread = thread::current(); 26 | 27 | let child = Builder::new(move || yielding_thread(main_thread)) 28 | .name("thread 2") 29 | .priority(PRI_DEFAULT + 1) 30 | .spawn(); 31 | 32 | assert_eq!( 33 | child.status(), 34 | Status::Dying, 35 | "Thread 2 should have just exited." 36 | ); 37 | 38 | pass(); 39 | } 40 | -------------------------------------------------------------------------------- /user/userprogs/child-rox.c: -------------------------------------------------------------------------------- 1 | /* Child process run by rox-child and rox-multichild tests. 2 | Opens and tries to write to its own executable, verifying that 3 | that is disallowed. 4 | Then recursively executes itself to the depth indicated by the 5 | first command-line argument. */ 6 | 7 | #include "user.h" 8 | 9 | static void try_write(void) { 10 | int fd; 11 | char buffer[19]; 12 | 13 | assert((fd = open("child-rox", O_WRONLY)) > 2); 14 | assert(write(fd, buffer, sizeof buffer) == -1, "\"child-rox\" is not writable"); 15 | 16 | close(fd); 17 | } 18 | 19 | void main(int argc, char* argv[]) { 20 | try_write(); 21 | 22 | assert(argc > 1); 23 | 24 | int n = atoi(argv[1]); 25 | 26 | if (n > 1) { 27 | int child; 28 | char buf[10]; 29 | itoa(buf, n - 1); 30 | 31 | const char* args[] = {"child-rox", buf, 0}; 32 | assert((child = exec(args[0], args)) >= 0); 33 | assert(wait(child) == 12); 34 | } 35 | 36 | try_write(); 37 | 38 | exit(12); 39 | } 40 | -------------------------------------------------------------------------------- /test/schedule/priority/change.rs: -------------------------------------------------------------------------------- 1 | use crate::thread; 2 | 3 | use super::*; 4 | 5 | fn changing_thread(main_thread: Arc) { 6 | assert_eq!(main_thread.status(), Status::Ready); 7 | 8 | kprintln!("Thread 2 lowering priority"); 9 | set_priority(PRI_DEFAULT - 1); 10 | 11 | assert_eq!(main_thread.status(), Status::Ready); 12 | kprintln!("Thread 2 exiting..."); 13 | } 14 | 15 | pub fn main() { 16 | // TODO: assert(!mlfqs) 17 | 18 | let main_thread = thread::current(); 19 | 20 | kprintln!("Creating a high-priority thread 2"); 21 | let child = Builder::new(move || changing_thread(main_thread)) 22 | .name("thread 2") 23 | .spawn(); 24 | 25 | assert_eq!( 26 | child.status(), 27 | Status::Ready, 28 | "Thread 2 should have just lowered its priority." 29 | ); 30 | 31 | set_priority(PRI_DEFAULT - 2); 32 | 33 | assert_eq!( 34 | child.status(), 35 | Status::Dying, 36 | "Thread 2 shoud have just exited" 37 | ); 38 | 39 | pass(); 40 | } 41 | -------------------------------------------------------------------------------- /user/lib/arc4.c: -------------------------------------------------------------------------------- 1 | #include "arc4.h" 2 | 3 | /* Swap bytes. */ 4 | static inline void swap_byte(uint8* a, uint8* b) { 5 | uint8 t = *a; 6 | *a = *b; 7 | *b = t; 8 | } 9 | 10 | void arc4_init(arc4* arc4, const void* key_, size_t size) { 11 | const uint8* key = key_; 12 | size_t key_idx; 13 | uint8* s; 14 | int i, j; 15 | 16 | s = arc4->s; 17 | arc4->i = arc4->j = 0; 18 | for (i = 0; i < 256; i++) s[i] = i; 19 | for (key_idx = 0, i = j = 0; i < 256; i++) { 20 | j = (j + s[i] + key[key_idx]) & 255; 21 | swap_byte(s + i, s + j); 22 | if (++key_idx >= size) key_idx = 0; 23 | } 24 | } 25 | 26 | void arc4_crypt(arc4* arc4, void* buf_, size_t size) { 27 | uint8* buf = buf_; 28 | uint8* s; 29 | uint8 i, j; 30 | 31 | s = arc4->s; 32 | i = arc4->i; 33 | j = arc4->j; 34 | while (size-- > 0) { 35 | i += 1; 36 | j += s[i]; 37 | swap_byte(s + i, s + j); 38 | *buf++ ^= s[(s[i] + s[j]) & 255]; 39 | } 40 | arc4->i = i; 41 | arc4->j = j; 42 | } 43 | -------------------------------------------------------------------------------- /user/vm/README.md: -------------------------------------------------------------------------------- 1 | Functionality of virtual memory subsystem: 2 | - Test stack growth. 3 | 3 pt-grow-stack 4 | 3 pt-grow-stk-sc 5 | 3 pt-big-stk-obj 6 | [x] pt-grow-pusha (no `pusha` in risc-v) 7 | 8 | - Test paging behavior. 9 | 3 page-linear 10 | 3 page-parallel 11 | 3 page-shuffle 12 | 4 page-merge-seq 13 | 4 page-merge-par 14 | 4 page-merge-mm 15 | 4 page-merge-stk 16 | 17 | - Test "mmap" system call. 18 | 2 mmap-read 19 | 2 mmap-write 20 | 2 mmap-shuffle 21 | 22 | 2 mmap-twice 23 | 24 | 2 mmap-unmap 25 | 1 mmap-exit 26 | 27 | 3 mmap-clean 28 | 29 | 2 mmap-close 30 | 2 mmap-remove 31 | 32 | Robustness of virtual memory subsystem: 33 | - Test robustness of page table support. 34 | 2 pt-bad-addr 35 | 3 pt-bad-read 36 | 2 pt-write-code 37 | 3 pt-write-code2 38 | 4 pt-grow-bad 39 | 40 | - Test robustness of "mmap" system call. 41 | 1 mmap-bad-fd 42 | 1 mmap-inherit 43 | 1 mmap-null 44 | 1 mmap-zero 45 | 46 | 2 mmap-misalign 47 | 48 | 2 mmap-over-code 49 | 2 mmap-over-data 50 | 2 mmap-over-stk 51 | 2 mmap-overlap 52 | 53 | [ ] Choose an exit code for different errors 54 | -------------------------------------------------------------------------------- /src/thread/scheduler.rs: -------------------------------------------------------------------------------- 1 | //! Scheduler 2 | //! 3 | //! [`Manager`](crate::thread::Manager) relies on scheduler to support Kernel Thread Scheduling. 4 | //! FCFS is an example implementation of a scheduler, you can add new schedulers by implementing 5 | //! [`Schedule`] trait. 6 | //! 7 | 8 | pub mod fcfs; 9 | 10 | use alloc::sync::Arc; 11 | 12 | use crate::thread::Thread; 13 | 14 | #[cfg(feature = "thread-scheduler-priority")] 15 | // (Lab1) Your task: priority scheduling 16 | pub type Scheduler = self::fcfs::Fcfs; 17 | #[cfg(not(feature = "thread-scheduler-priority"))] 18 | pub type Scheduler = self::fcfs::Fcfs; 19 | 20 | /// Basic functionalities of thread schedulers 21 | pub trait Schedule: Default { 22 | /// Notify the scheduler that a thread is able to run. Then, this thread 23 | /// becomes a candidate of [`schedule`](Schedule::schedule). 24 | fn register(&mut self, thread: Arc); 25 | 26 | /// Choose the next thread to run. `None` if scheduler decides to keep running 27 | /// the current thread. 28 | fn schedule(&mut self) -> Option>; 29 | } 30 | -------------------------------------------------------------------------------- /test/unit/thread/block.rs: -------------------------------------------------------------------------------- 1 | use alloc::sync::Arc; 2 | 3 | use crate::sync::{Mutex, Semaphore, Sleep}; 4 | use crate::thread::{self, Status}; 5 | 6 | static mut MUTEX: Option> = None; 7 | 8 | const SCHEDULE_NUM: i32 = 10; 9 | 10 | struct S { 11 | sema: Semaphore, 12 | mutex: Mutex, 13 | } 14 | 15 | pub fn main() { 16 | let s = Arc::new(S { 17 | sema: Semaphore::new(0), 18 | mutex: Mutex::new(0), 19 | }); 20 | let s1 = s.clone(); 21 | 22 | let waiter = thread::spawn("waiter", move || waiter_mutex(s1)); 23 | let guard = s.mutex.lock(); 24 | 25 | s.sema.up(); 26 | for _ in 0..SCHEDULE_NUM { 27 | thread::schedule(); 28 | } 29 | 30 | assert_eq!(waiter.status(), Status::Blocked); 31 | kprintln!("Dropping mutex guard"); 32 | drop(guard); 33 | 34 | for _ in 0..SCHEDULE_NUM { 35 | thread::schedule(); 36 | } 37 | 38 | kprintln!("{:?}", waiter.status()); 39 | assert_eq!(waiter.status(), Status::Ready); 40 | } 41 | 42 | fn waiter_mutex(s: Arc) { 43 | s.sema.down(); 44 | s.mutex.lock(); 45 | loop {} 46 | } 47 | -------------------------------------------------------------------------------- /user/userprogs/open-rdwr.c: -------------------------------------------------------------------------------- 1 | /** Opens a file to read and/or write. */ 2 | 3 | #include "sample.inc" 4 | #include "user.h" 5 | 6 | void main() { 7 | int fd; 8 | char buf[256]; 9 | memset(buf, 0, sizeof buf); 10 | 11 | /* Read Only */ 12 | assert((fd = open("sample.txt", O_RDONLY)) > 2); 13 | assert(read(fd, buf, sizeof buf) == (int)(sizeof sample - 1)); 14 | assert(write(fd, buf, 1) == -1, "sample.txt was opened in read-only mode"); 15 | assert(strcmp(buf, sample) == 0); 16 | close(fd); 17 | 18 | /* Write Only */ 19 | assert((fd = open("tmp.txt", O_CREATE | O_WRONLY)) > 2); 20 | assert(write(fd, "abcdefg", 7) == 7); 21 | assert(read(fd, buf, sizeof buf) == -1); 22 | close(fd); 23 | 24 | /* Read and Write */ 25 | assert((fd = open("sample.txt", O_RDWR)) > 2); 26 | assert(read(fd, buf, sizeof buf) == (int)(sizeof sample - 1)); 27 | seek(fd, 0); 28 | assert(write(fd, buf, sizeof sample - 1) == (int)(sizeof sample - 1)); 29 | seek(fd, 0); 30 | assert(read(fd, buf, sizeof buf) == (int)(sizeof sample - 1)); 31 | assert(strcmp(buf, sample) == 0); 32 | close(fd); 33 | } 34 | -------------------------------------------------------------------------------- /test/user.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::ToString; 2 | use alloc::vec::Vec; 3 | 4 | use crate::fs::disk::DISKFS; 5 | use crate::fs::FileSys; 6 | use crate::thread; 7 | use crate::userproc; 8 | 9 | const LEN: usize = 11; 10 | const KILLED_USERPROC: [&str; LEN] = [ 11 | // lab2 tests 12 | "bad-load", 13 | "bad-load2", 14 | "bad-jump", 15 | "bad-jump2", 16 | "bad-store", 17 | "bad-store2", 18 | // lab3 tests 19 | "mmap-unmap", 20 | "mmap-zero", 21 | "pt-bad-addr", 22 | "pt-grow-bad", 23 | "pt-write-code", 24 | ]; 25 | const KILLED_EXIT: isize = -1; 26 | const NORMAL_EXIT: isize = 0; 27 | 28 | pub fn main(cmd: &str) { 29 | kprintln!("Executing command {}", cmd); 30 | 31 | let argv: Vec<_> = cmd.split(" ").map(|s| s.to_string()).collect(); 32 | let name = argv[0].clone(); 33 | let file = DISKFS.open(name.as_str().into()).unwrap(); 34 | 35 | let r = userproc::wait(userproc::execute(file, argv)).unwrap(); 36 | if KILLED_USERPROC.iter().find(|n| name.eq(**n)).is_some() { 37 | assert_eq!(r, KILLED_EXIT); 38 | } else { 39 | assert_eq!(r, NORMAL_EXIT); 40 | } 41 | 42 | thread::schedule(); 43 | } 44 | -------------------------------------------------------------------------------- /src/sbi/interrupt.rs: -------------------------------------------------------------------------------- 1 | //! RISC-V timer & external interrupt 2 | 3 | use riscv::register; 4 | 5 | #[inline] 6 | fn on() { 7 | unsafe { 8 | register::sie::set_stimer(); 9 | register::sie::set_sext(); 10 | }; 11 | } 12 | 13 | #[inline] 14 | fn off() { 15 | unsafe { 16 | register::sie::clear_sext(); 17 | register::sie::clear_stimer(); 18 | }; 19 | } 20 | 21 | /// Get timer & external interrupt level. `true` means interruptible. 22 | #[inline] 23 | pub fn get() -> bool { 24 | register::sie::read().stimer() 25 | } 26 | 27 | /// Set timer & external interrupt level. 28 | pub fn set(level: bool) -> bool { 29 | let old = get(); 30 | 31 | // To avoid unnecessary overhead, we only (un)set timer when its status need 32 | // to be changed. Synchronization is not required here as any changes to 33 | // timer status will be restored by other threads. 34 | if old != level { 35 | if level { 36 | on(); 37 | } else { 38 | off(); 39 | } 40 | } 41 | 42 | old 43 | } 44 | 45 | pub fn init() { 46 | crate::sbi::timer::next(); 47 | 48 | set(true); 49 | 50 | unsafe { register::sstatus::set_sie() }; 51 | } 52 | -------------------------------------------------------------------------------- /src/sync/intr.rs: -------------------------------------------------------------------------------- 1 | use core::cell::Cell; 2 | 3 | use crate::{sbi, sync::Lock}; 4 | 5 | /// A lock based on disabling timer interrupt. 6 | /// 7 | /// The most basic way to synchornize between threads is to turn off/on timer 8 | /// interrpt. However, it has relatively larger overhead sometimes, so please 9 | /// use it only when necessary (especially when handling threads). 10 | /// 11 | /// On acquisition, it turns off the timer and record the old timer status. On release, 12 | /// it simply restores the old status. 13 | #[derive(Debug, Default)] 14 | pub struct Intr(Cell>); 15 | 16 | impl Intr { 17 | pub const fn new() -> Self { 18 | Self(Cell::new(None)) 19 | } 20 | } 21 | 22 | unsafe impl Sync for Intr {} 23 | unsafe impl Send for Intr {} 24 | 25 | impl Lock for Intr { 26 | fn acquire(&self) { 27 | assert!(self.0.get().is_none()); 28 | 29 | // Record the old timer status. Here setting the immutable `self` is safe 30 | // because the interrupt is already turned off. 31 | self.0.set(Some(sbi::interrupt::set(false))); 32 | } 33 | 34 | fn release(&self) { 35 | sbi::interrupt::set(self.0.take().expect("release before acquire")); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/schedule/priority/fifo.rs: -------------------------------------------------------------------------------- 1 | use alloc::sync::Arc; 2 | use alloc::vec::Vec; 3 | 4 | use super::pass; 5 | use crate::sbi::interrupt; 6 | use crate::thread::*; 7 | 8 | const THREAD_CNT: usize = 16; 9 | const ITERS: usize = 16; 10 | 11 | fn thread_func(tid: usize, v: Arc>>) { 12 | interrupt::set(false); 13 | 14 | // TODO: Sync all threads here, is it necessary? 15 | 16 | for _ in 0..ITERS { 17 | v.lock().push(tid); 18 | schedule(); 19 | } 20 | } 21 | 22 | pub fn main() { 23 | interrupt::set(false); 24 | 25 | let log = Arc::new(Mutex::new(Vec::new())); 26 | set_priority(PRI_DEFAULT + 2); 27 | 28 | for tid in 0..THREAD_CNT { 29 | let v = Arc::clone(&log); 30 | Builder::new(move || thread_func(tid, v)) 31 | .name("simple thread") 32 | .priority(PRI_DEFAULT + 1) 33 | .spawn(); 34 | } 35 | 36 | set_priority(PRI_DEFAULT); 37 | 38 | let v = log.lock(); 39 | 40 | assert_eq!(THREAD_CNT * ITERS, v.len(), "Missing logs!"); 41 | 42 | let slice = &v[0..THREAD_CNT]; 43 | for s in v.chunks(THREAD_CNT) { 44 | assert_eq!(s, slice, "Not fifo schedule!"); 45 | } 46 | 47 | pass(); 48 | } 49 | -------------------------------------------------------------------------------- /tool/bookmarks/lab2.toml: -------------------------------------------------------------------------------- 1 | # case_name = ["args", option] 2 | # Functionality: 55 3 | args-none = ["", 2] 4 | args-many = ["a b c d e f g h i j k l m n o p q r s t u v", 2] 5 | open-create = ["", 2] 6 | open-rdwr = ["", 2] 7 | open-trunc = ["", 2] 8 | open-many = ["", 2] 9 | read-zero = ["", 2] 10 | read-normal = ["", 2] 11 | write-zero = ["", 2] 12 | write-normal = ["", 2] 13 | close-normal = ["", 2] 14 | exec-once = ["", 2] 15 | exec-multiple = ["", 2] 16 | exec-arg = ["", 2] 17 | wait-simple = ["", 2] 18 | wait-twice = ["", 2] 19 | exit = ["", 2] 20 | halt = ["", 2] 21 | rox-simple = ["", 2] 22 | rox-child = ["", 2] 23 | rox-multichild = ["", 2] 24 | multi-recurse = ["15", 13] 25 | # Robustness: 45 26 | close-stdio = ["", 2] 27 | close-badfd = ["", 2] 28 | close-twice = ["", 2] 29 | read-badfd = ["", 2] 30 | read-stdout = ["", 2] 31 | write-badfd = ["", 2] 32 | write-stdin = ["", 2] 33 | boundary-normal = ["", 2] 34 | boundary-bad = ["", 2] 35 | open-invalid = ["", 2] 36 | sc-bad-sp = ["", 2] 37 | exec-invalid = ["", 2] 38 | wait-badpid = ["", 2] 39 | wait-killed = ["", 2] 40 | bad-load = ["", 2] 41 | bad-store = ["", 2] 42 | bad-jump = ["", 2] 43 | bad-load2 = ["", 2] 44 | bad-store2 = ["", 2] 45 | bad-jump2 = ["", 2] 46 | sc-bad-args = ["", 5] 47 | -------------------------------------------------------------------------------- /src/mem/layout.rs: -------------------------------------------------------------------------------- 1 | // +------------------+ 2 | // | | 3 | // /\/\/\/\/\/\/\/\/\/\ 4 | // /\/\/\/\/\/\/\/\/\/\ 5 | // | | 6 | // | Unused | 7 | // | | 8 | // +------------------+ <- End of kernel 9 | // | kernel | 10 | // +------------------+ <- 0x80200000 11 | // | | 12 | // | Unused | 13 | // | | 14 | // +------------------+ 15 | // | MMIO | 16 | // +------------------+ <- 0x10001000 17 | // | | 18 | // | Unused | 19 | // | | 20 | // +------------------+ <- 0x0c400000 21 | // | PLIC | 22 | // +------------------+ <- 0x0c000000 23 | // | | 24 | // | Low Memory | 25 | // | | 26 | // +------------------+ <- 0x00000000 27 | 28 | pub const VM_BASE: usize = 0xFFFFFFC080000000; 29 | pub const PM_BASE: usize = 0x0000000080000000; 30 | pub const KERN_BASE: usize = 0x0000000080200000; 31 | pub const VM_OFFSET: usize = VM_BASE - PM_BASE; 32 | pub const PLIC_BASE: usize = 0xC000000 + VM_OFFSET; 33 | pub const MMIO_BASE: usize = 0x10001000 + VM_OFFSET; 34 | -------------------------------------------------------------------------------- /test/schedule/alarm/simultaneous.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | const THREAD_CNT: usize = 3; 4 | const ITERS: usize = 10; 5 | const TEST_START: i64 = 10; 6 | 7 | static mut WAKE_TICKS: [[i64; ITERS]; THREAD_CNT] = [[0; ITERS]; THREAD_CNT]; 8 | 9 | pub fn main() { 10 | for tid in 0..THREAD_CNT { 11 | thread::spawn("Sleeper", move || sleeper(tid)); 12 | } 13 | 14 | // Wait long enough for all the threads to finish. 15 | thread::sleep((20 + ITERS) as i64); 16 | 17 | for i in 0..ITERS { 18 | let wake_time = TEST_START + (i + 1) as i64; 19 | for tid in 0..THREAD_CNT { 20 | let real_time = unsafe { WAKE_TICKS[tid][i] }; 21 | assert_eq!( 22 | real_time, wake_time, 23 | "Sleeper {} is supposed to wake up at tick {}, but wakes up at tick {}", 24 | tid, wake_time, real_time 25 | ); 26 | } 27 | } 28 | 29 | pass(); 30 | } 31 | 32 | fn sleeper(tid: usize) { 33 | // Make sure we're at the beginning of a tiemr tick. 34 | thread::sleep(1); 35 | 36 | for i in 0..ITERS { 37 | let until: i64 = TEST_START + (i + 1) as i64; 38 | thread::sleep(until - timer::timer_ticks()); 39 | unsafe { WAKE_TICKS[tid][i] = timer::timer_ticks() }; 40 | thread::schedule(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | MAKEFLAGS := --jobs=$(shell nproc) 2 | 3 | CARGO := cargo run -q 4 | FILTER := grep -E "[0-9]+ ms" --color=never 5 | BUILD_DIR := build 6 | 7 | all: $(BUILD_DIR)/disk.img 8 | 9 | include user/*.mk 10 | 11 | $(BUILD_DIR)/mkfs: mkfs.c 12 | gcc -o $@ mkfs.c 13 | 14 | $(BUILD_DIR)/disk.img: $(TARGETS) $(BUILD_DIR)/mkfs $(TEST_DIR)/sample.txt $(TEST_DIR)/zeros 15 | cd $(BUILD_DIR)/ && ./mkfs 16 | 17 | run: all 18 | $(CARGO) --release -F test | $(FILTER) 19 | 20 | run-gdb: all 21 | $(CARGO) -- -s -S 22 | 23 | test-%: all 24 | $(CARGO) --features $@ | $(FILTER) 25 | 26 | gdb-%: all 27 | $(CARGO) --features test-$* -- -s -S 28 | 29 | clean: 30 | rm -rf $(BUILD_DIR) 31 | rm -f mkfs 32 | cargo clean 33 | cd tool && cargo clean && cd .. 34 | 35 | clean-tacos: 36 | rm -rf $(BUILD_DIR) 37 | rm -f mkfs 38 | cargo clean 39 | 40 | format: 41 | cargo fmt 42 | find . -type f -name "*.c" -o -name "*.h" -exec clang-format -i {} + 43 | 44 | test-user-%: $(TARGETS) $(BUILD_DIR)/disk.img 45 | $(CARGO) --features test-user -- -append "$*" 46 | 47 | gdb-user-%: $(TARGETS) $(BUILD_DIR)/disk.img 48 | $(CARGO) --features test-user -- -append "$*" -s -S 49 | 50 | test-user-debug-%: $(TARGETS) $(BUILD_DIR)/disk.img 51 | $(CARGO) --features test-user,debug -- -append "$*" 52 | 53 | submission:: clean 54 | tar cjvf submission.tar.bz2 * 55 | -------------------------------------------------------------------------------- /src/sbi/timer.rs: -------------------------------------------------------------------------------- 1 | //! RISC-V Timer Interface 2 | 3 | use core::sync::atomic::{AtomicI64, Ordering::SeqCst}; 4 | 5 | use crate::sbi::set_timer; 6 | 7 | pub const TICKS_PER_SEC: usize = 10; 8 | pub const CLOCK_PRE_SEC: usize = 12500000; 9 | 10 | /// Get the clock's raw reading 11 | pub fn clock() -> usize { 12 | riscv::register::time::read() 13 | } 14 | 15 | /// Get the current clock reading in milliseconds 16 | #[inline] 17 | pub fn time_ms() -> usize { 18 | clock() * 1_000 / CLOCK_PRE_SEC 19 | } 20 | 21 | /// Get the current clock reading in microseconds 22 | #[inline] 23 | pub fn time_us() -> usize { 24 | clock() * 1_000_000 / CLOCK_PRE_SEC 25 | } 26 | 27 | /// Set the next moment when timer interrupt should happen 28 | #[inline] 29 | pub fn next() { 30 | set_timer(clock() + CLOCK_PRE_SEC / TICKS_PER_SEC); 31 | } 32 | 33 | static TICKS: AtomicI64 = AtomicI64::new(0); 34 | 35 | /// Returns the number of timer ticks since booted. 36 | pub fn timer_ticks() -> i64 { 37 | TICKS.load(SeqCst) 38 | } 39 | 40 | /// Increments timer ticks by 1 and sets the next timer interrupt. 41 | pub fn tick() { 42 | TICKS.fetch_add(1, SeqCst); 43 | next(); 44 | } 45 | 46 | /// Returns how many timer ticks elapsed since "then", which should be a 47 | /// value returned by [`timer_ticks()`]. 48 | pub fn timer_elapsed(then: i64) -> i64 { 49 | timer_ticks() - then 50 | } 51 | -------------------------------------------------------------------------------- /test/schedule/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | mod alarm; 4 | mod donation; 5 | mod priority; 6 | 7 | fn pass() { 8 | kprintln!("[PASS]"); 9 | } 10 | 11 | static NAME2CASE: [(&str, fn()); 18] = [ 12 | ("alarm-zero", alarm::boundary::zero::main), 13 | ("alarm-negative", alarm::boundary::negative::main), 14 | ("alarm-simultaneous", alarm::simultaneous::main), 15 | ("alarm-single", alarm::multiple::single::main), 16 | ("alarm-multiple", alarm::multiple::main), 17 | ("priority-alarm", priority::alarm::main), 18 | ("priority-condvar", priority::condvar::main), 19 | ("priority-sema", priority::sema::main), 20 | ("priority-change", priority::change::main), 21 | ("priority-preempt", priority::preempt::main), 22 | ("priority-fifo", priority::fifo::main), 23 | ("donation-chain", donation::chain::main), 24 | ("donation-lower", donation::lower::main), 25 | ("donation-nest", donation::nest::main), 26 | ("donation-one", donation::one::main), 27 | ("donation-sema", donation::sema::main), 28 | ("donation-two", donation::two::main), 29 | ("donation-three", donation::three::main), 30 | ]; 31 | 32 | pub fn main(case: &str) { 33 | for (name, f) in NAME2CASE.iter() { 34 | if case.eq(*name) { 35 | f(); 36 | return; 37 | } 38 | } 39 | 40 | kprintln!("Invalid test case: {}", case); 41 | } 42 | -------------------------------------------------------------------------------- /test/unit/fs/disk/simple.rs: -------------------------------------------------------------------------------- 1 | use crate::device::virtio::SECTOR_SIZE; 2 | use crate::fs::disk::{Path, DISKFS}; 3 | use crate::fs::FileSys; 4 | use crate::io::prelude::*; 5 | 6 | pub fn main() { 7 | let buf = [1; 3 * SECTOR_SIZE]; 8 | let mut buf2 = [0; 4 * SECTOR_SIZE]; 9 | let random_off = SECTOR_SIZE - 100; 10 | { 11 | let mut file = DISKFS.create("/disk-simple".into()).unwrap(); 12 | file.set_len(4 * SECTOR_SIZE).unwrap(); 13 | 14 | // Go to middle of sector. 15 | file.seek(SeekFrom::Start(random_off)).unwrap(); 16 | // However the file should still long enough to contain 3 sectors. 17 | assert!(file.write_all(&buf).is_ok()); 18 | // Go to very beginning; 19 | file.rewind().unwrap(); 20 | // Can read to end. 21 | assert!(file.read_exact(&mut buf2).is_ok()); 22 | // Assert value. 23 | for i in 0..SECTOR_SIZE * 3 { 24 | assert_eq!(buf2[random_off + i], 1); 25 | } 26 | // Drop file and inode. 27 | } 28 | { 29 | // Reopen? 30 | let _file = DISKFS.open("/disk-simple".into()).unwrap(); 31 | // Remove. 32 | DISKFS.remove("/disk-simple".into()).unwrap(); 33 | } 34 | { 35 | // There isn't such file. 36 | assert_eq!(Path::exists("/disk-simple".into()), false); 37 | } 38 | kprintln!("[DISKFS.SIMPLE] Done.") 39 | } 40 | -------------------------------------------------------------------------------- /user/userprogs/sc-bad-args.c: -------------------------------------------------------------------------------- 1 | /** Passes invalid pointers to the exec, open, and write system call. 2 | These syscalls should return with value -1. */ 3 | 4 | #include "sample.inc" 5 | #include "user.h" 6 | 7 | void main() { 8 | int fd; 9 | char buf[256]; 10 | 11 | assert((fd = open("sample.txt", O_RDWR)) > 2, "a normal system call"); 12 | 13 | /* Passes NULL */ 14 | assert(exec((void*)0xbad, (void*)0x478) == -1); 15 | assert(open(NULL, 0) == -1); 16 | assert(read(fd, NULL, sizeof buf)); 17 | assert(write(1, NULL, 10) == -1); 18 | 19 | /* Passes arbitrary invalid pointers */ 20 | assert(exec("child-simple", (void*)0xbad) == -1); 21 | assert(open((void*)0xdeadbeaf, O_CREATE) == -1); 22 | assert(read(fd, (void*)0xdeadbeaf, sizeof buf)); 23 | assert(write(1, (void*)0xbad, 123) == -1); 24 | 25 | /* Passes pointers in the kernel space */ 26 | assert(exec((void*)0xffffffc030000000, (void*)0xffffffc040000000) == -1); 27 | assert(open((void*)0xffffffc030000000, O_CREATE | O_TRUNC) == -1); 28 | assert(read(fd, (void*)0xffffffc000000000, sizeof buf)); 29 | assert(write(1, (void*)0xffffffc030000000, 1234) == -1); 30 | 31 | /* After all the malicious system calls, process can still behave normally. */ 32 | assert(read(fd, buf, sizeof buf) == sizeof sample - 1); 33 | assert(strcmp(buf, sample) == 0); 34 | assert(close(fd) == 0); 35 | } 36 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile 3 | { 4 | "name": "Tacos", 5 | "image": "chromi2eugen/tacos", 6 | "customizations": { 7 | "vscode": { 8 | "settings": { 9 | "editor.formatOnSave": true, 10 | "rust-analyzer.check.allTargets": false 11 | }, 12 | "extensions": [ 13 | "webfreak.debug", 14 | "rust-lang.rust-analyzer", 15 | "tamasfe.even-better-toml", 16 | "aaron-bond.better-comments", 17 | "stackbreak.comment-divider", 18 | "zixuanwang.linkerscript", 19 | "eamodio.gitlens" 20 | ] 21 | } 22 | } 23 | // Features to add to the dev container. More info: https://containers.dev/features. 24 | // "features": {}, 25 | // 26 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 27 | // "forwardPorts": [], 28 | // 29 | // Uncomment the next line to run commands after the container is created. 30 | // "postCreateCommand": "cat /etc/os-release", 31 | // 32 | // Configure tool-specific properties. 33 | // "customizations": {}, 34 | // 35 | // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. 36 | // "remoteUser": "devcontainer" 37 | } 38 | -------------------------------------------------------------------------------- /user/vm/mmap-remove.c: -------------------------------------------------------------------------------- 1 | /* Deletes and closes file that is mapped into memory 2 | and verifies that it can still be read through the mapping. */ 3 | 4 | #include "sample.inc" 5 | #include "user.h" 6 | 7 | void main() { 8 | char* actual = (char*)0x10000000; 9 | int fd; 10 | mapid_t map; 11 | size_t i; 12 | 13 | /* Map file. */ 14 | assert((fd = open("sample.txt", O_RDWR)) > 2); 15 | assert((map = mmap(fd, actual)) != MAP_FAILED, "mmap \"sample.txt\""); 16 | 17 | /* Close file and delete it. */ 18 | close(fd); 19 | assert(remove("sample.txt") == 0, "remove \"sample.txt\""); 20 | assert(open("sample.txt", 0) == -1, "try to open \"sample.txt\""); 21 | 22 | /* Create a new file in hopes of overwriting data from the old 23 | one, in case the file system has incorrectly freed the 24 | file's data. */ 25 | int fd2; 26 | char buf[4096 * 8]; 27 | assert((fd2 = open("another", O_CREATE | O_TRUNC | O_WRONLY))); 28 | assert(write(fd2, buf, 4096 * 8) == 4096 * 8); 29 | 30 | /* Check that mapped data is correct. */ 31 | if (memcmp(actual, sample, strlen(sample))) panic("read of mmap'd file reported bad data"); 32 | 33 | /* Verify that data is followed by zeros. */ 34 | for (i = strlen(sample); i < 4096; i++) 35 | if (actual[i] != 0) 36 | panic("byte %d of mmap'd region has value %02hhx (should be 0)", i, actual[i]); 37 | 38 | munmap(map); 39 | } 40 | -------------------------------------------------------------------------------- /test/schedule/donation/one.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | static mut EXIT_STATUS: [i8; 3] = [0; 3]; 4 | 5 | fn medium(lock: Arc) { 6 | lock.acquire(); 7 | 8 | unsafe { 9 | assert_eq!(EXIT_STATUS, [0, 0, 1], "Medium should exit after high."); 10 | EXIT_STATUS[1] = 1; 11 | }; 12 | 13 | lock.release(); 14 | } 15 | 16 | fn high(lock: Arc) { 17 | lock.acquire(); 18 | 19 | unsafe { 20 | assert_eq!(EXIT_STATUS, [0, 0, 0], "Hish should exit first."); 21 | EXIT_STATUS[2] = 1; 22 | }; 23 | 24 | lock.release(); 25 | } 26 | 27 | pub fn main() { 28 | let lock = Arc::new(Sleep::default()); 29 | 30 | lock.acquire(); 31 | 32 | let create_and_check = |f: fn(Arc), tid| { 33 | let l = Arc::clone(&lock); 34 | let expected = PRI_DEFAULT + tid; 35 | Builder::new(move || f(l)) 36 | .name("child") 37 | .priority(expected) 38 | .spawn(); 39 | let priority = get_priority(); 40 | assert_eq!( 41 | expected, priority, 42 | "This thread should have priority {}. Actual priority {}.", 43 | expected, priority 44 | ); 45 | }; 46 | create_and_check(medium, 1); 47 | create_and_check(high, 2); 48 | 49 | lock.release(); 50 | 51 | unsafe { 52 | assert_eq!(EXIT_STATUS, [0, 1, 1], "Medium & high should have exited."); 53 | }; 54 | 55 | pass(); 56 | } 57 | -------------------------------------------------------------------------------- /src/boot.rs: -------------------------------------------------------------------------------- 1 | // Entry point of the kernel. The kernel starts off at a low address (0x80200000), 2 | // setting up the entry page table and then jumps to a high address space (0xFFFFFFC080000000). 3 | // Then, after setting the boot stack, it enters the "main" function. 4 | core::arch::global_asm! {r#" 5 | .section .text.entry 6 | .global _entry 7 | _entry: 8 | # load the physical address of the entry page table 9 | la t0, entry_pgtable 10 | 11 | # prepare satp's content 12 | srli t0, t0, 12 13 | li t1, 0x8 << 60 14 | or t0, t0, t1 15 | 16 | # enable paging 17 | sfence.vma zero, zero 18 | csrw satp, t0 19 | sfence.vma zero, zero 20 | 21 | # set up the stack 22 | ld t0, _relocated 23 | jr t0 24 | 25 | relocated: 26 | la sp, bootstack_top 27 | j main 28 | 29 | .globl _relocated 30 | _relocated: 31 | .8byte relocated 32 | 33 | .section .data 34 | .align 12 35 | .globl bootstack 36 | bootstack: 37 | .space 8 * 4096 38 | .globl bootstack_top 39 | bootstack_top: 40 | 41 | .align 12 42 | .globl entry_pgtable 43 | entry_pgtable: 44 | .zero 16 45 | .8byte 0x2000002F # [PM_BASE, PM_BASE + 1GB) => [PM_BASE, PM_BASE + 1GB) 46 | .zero 2040 47 | .8byte 0x2000002F # [VM_BASE, VM_BASE + 1GB) => [PM_BASE, PM_BASE + 1GB) 48 | .zero 2048 49 | "#} 50 | -------------------------------------------------------------------------------- /test/unit.rs: -------------------------------------------------------------------------------- 1 | mod fs; 2 | mod malloc; 3 | mod sync; 4 | mod thread; 5 | mod virtio; 6 | 7 | pub fn main() { 8 | #[cfg(any(feature = "test-sync", feature = "test-sync-condvar"))] 9 | sync::condvar::main(); 10 | #[cfg(any(feature = "test-sync", feature = "test-sync-sema_fifo"))] 11 | sync::sema_fifo::main(); 12 | 13 | #[cfg(any(feature = "test-thread", feature = "test-thread-adder"))] 14 | thread::adder::main(); 15 | #[cfg(any(feature = "test-thread", feature = "test-thread-block"))] 16 | thread::block::main(); 17 | 18 | // ! This should fail. 19 | #[cfg(any(feature = "test-thread", feature = "test-thread-bomb"))] 20 | thread::bomb::main(); 21 | 22 | #[cfg(any(feature = "test-thread", feature = "test-thread-spin_yield"))] 23 | thread::spin_yield::main(); 24 | #[cfg(any(feature = "test-thread", feature = "test-thread-spin_interrupt"))] 25 | thread::spin_interrupt::main(); 26 | 27 | /* ------------------------------- VIRTIO TEST ------------------------------ */ 28 | 29 | #[cfg(any(feature = "test-virtio", feature = "test-virtio-simple"))] 30 | virtio::simple::main(); 31 | 32 | #[cfg(any(feature = "test-virtio", feature = "test-virtio-repeat"))] 33 | virtio::repeat::main(); 34 | 35 | #[cfg(feature = "test-mem-malloc")] 36 | malloc::main(); 37 | 38 | #[cfg(feature = "test-fs-inmem")] 39 | fs::inmem::main(); 40 | 41 | #[cfg(feature = "test-fs-disk")] 42 | fs::disk::main(); 43 | } 44 | -------------------------------------------------------------------------------- /test/schedule/donation/lower.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | fn child(lock: Arc) { 4 | lock.acquire(); 5 | 6 | kprintln!("Child got lock."); 7 | 8 | lock.release(); 9 | 10 | kprintln!("Child done."); 11 | } 12 | 13 | pub fn main() { 14 | let lock = Arc::new(Sleep::default()); 15 | 16 | lock.acquire(); 17 | 18 | let l = Arc::clone(&lock); 19 | let child_priority = PRI_DEFAULT + 10; 20 | let child = Builder::new(move || child(l)) 21 | .name("child") 22 | .priority(child_priority) 23 | .spawn(); 24 | 25 | let self_priority = get_priority(); 26 | assert_eq!( 27 | child_priority, self_priority, 28 | "Main thread should have priority {}. Actual priority {}.", 29 | child_priority, self_priority 30 | ); 31 | 32 | kprintln!("Lowering priority..."); 33 | let lower_priority = PRI_DEFAULT - 10; 34 | set_priority(lower_priority); 35 | 36 | assert_eq!( 37 | child_priority, self_priority, 38 | "Main thread should have priority {}. Actual priority {}.", 39 | child_priority, self_priority 40 | ); 41 | 42 | lock.release(); 43 | 44 | assert_eq!( 45 | child.status(), 46 | Status::Dying, 47 | "Child thread must have finished." 48 | ); 49 | 50 | let self_priority = get_priority(); 51 | assert_eq!( 52 | lower_priority, self_priority, 53 | "Main thread should have priority {}. Actual priority {}.", 54 | lower_priority, self_priority 55 | ); 56 | 57 | pass(); 58 | } 59 | -------------------------------------------------------------------------------- /user/utests.mk: -------------------------------------------------------------------------------- 1 | INC_DIR := user/lib 2 | BUILD_DIR := build 3 | SRC_DIRS := user/userprogs user/vm 4 | 5 | TOOLPREFIX := riscv64-unknown-elf- 6 | CC := $(TOOLPREFIX)gcc 7 | CFLAGS := -Wall -Werror -O 8 | CFLAGS += -ffreestanding -fno-common -nostdlib -mno-relax 9 | CFLAGS += -I $(INC_DIR) 10 | LD := $(TOOLPREFIX)ld 11 | LDFLAGS := -T $(INC_DIR)/user.ld 12 | OBJDUMP := $(TOOLPREFIX)objdump 13 | 14 | INCS := $(shell find $(INC_DIR) -name '*.c' -or -name '*.pl') 15 | INCS := $(INCS:%.c=$(BUILD_DIR)/%.o) 16 | INCS := $(INCS:%.pl=$(BUILD_DIR)/%.o) 17 | SRCS := $(shell find $(SRC_DIRS) -name '*.c') 18 | OBJS := $(SRCS:%.c=$(BUILD_DIR)/%.o) 19 | TARGETS := $(OBJS:%.o=%) 20 | 21 | TEST_DIR := $(BUILD_DIR)/user 22 | 23 | $(BUILD_DIR)/%.S: %.pl 24 | perl $^ > $@ 25 | 26 | $(BUILD_DIR)/%.o: $(BUILD_DIR)/%.S 27 | $(CC) $(CFLAGS) -c $< -o $@ 28 | 29 | $(filter-out %/usys.o,$(INCS)):$(BUILD_DIR)/%.o:%.c 30 | @ mkdir -p $(dir $@) 31 | $(CC) $(CFLAGS) -c $< -o $@ 32 | 33 | $(OBJS):$(BUILD_DIR)/%.o:%.c 34 | @ mkdir -p $(dir $@) 35 | $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ 36 | 37 | $(TARGETS):%:%.o $(INCS) 38 | $(LD) $(LDFLAGS) -o $@ $^ 39 | $(OBJDUMP) -S $@ > $*.asm 40 | $(OBJDUMP) -t $@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $*.sym 41 | 42 | .PHONY: $(BUILD_DIR) 43 | $(BUILD_DIR): 44 | @ mkdir -p $(BUILD_DIR) 45 | 46 | $(TEST_DIR)/sample.txt: 47 | #! Ensure sample.txt is stored with LF in disk.img 48 | tr -d '\r' < user/userprogs/sample.txt > $(TEST_DIR)/sample.txt 49 | 50 | $(TEST_DIR)/zeros: 51 | dd if=/dev/zero of=$(TEST_DIR)/zeros bs=8192 count=1 52 | 53 | -------------------------------------------------------------------------------- /doc/lab0.md: -------------------------------------------------------------------------------- 1 | # Lab 1: Appetizer 2 | 3 | --- 4 | 5 | ## Information 6 | 7 | Name: 8 | 9 | Email: 10 | 11 | > Please cite any forms of information source that you have consulted during finishing your assignment, except the TacOS documentation, course slides, and course staff. 12 | 13 | > With any comments that may help TAs to evaluate your work better, please leave them here 14 | 15 | ## Booting Tacos 16 | 17 | > A1: Put the screenshot of Tacos running example here. 18 | 19 | ## Debugging 20 | 21 | ### First instruction 22 | 23 | > B1: What is the first instruction that gets executed? 24 | 25 | > B2: At which physical address is this instruction located? 26 | 27 | ### From ZSBL to SBI 28 | 29 | > B3: Which address will the ZSBL jump to? 30 | 31 | ### SBI, kernel and argument passing 32 | 33 | > B4: What's the value of the argument `hard_id` and `dtb`? 34 | 35 | > B5: What's the value of `Domain0 Next Address`, `Domain0 Next Arg1`, `Domain0 Next Mode` and `Boot HART ID` in OpenSBI's output? 36 | 37 | > B6: What's the relationship between the four output values and the two arguments? 38 | 39 | ### SBI interfaces 40 | 41 | > B7: Inside `console_putchar`, Tacos uses `ecall` instruction to transfer control to SBI. What's the value of register `a6` and `a7` when executing that `ecall`? 42 | 43 | ## Kernel Monitor 44 | 45 | > C1: Put the screenshot of your kernel monitor running example here. (It should show how your kernel shell respond to `whoami`, `exit`, and `other input`.) 46 | 47 | > C2: Explain how you read and write to the console for the kernel monitor. 48 | -------------------------------------------------------------------------------- /test/unit/sync/sema_fifo.rs: -------------------------------------------------------------------------------- 1 | use crate::thread; 2 | use crate::sync::Semaphore; 3 | 4 | use alloc::sync::Arc; 5 | 6 | fn multi_thread_sema(sema: Arc) { 7 | let cnt = sema.value(); 8 | kprintln!("multi_thread_sema(): Thread {} downing sema.", cnt); 9 | sema.down(); 10 | kprintln!("multi_thread_sema(): Thread {} down sema success.", cnt); 11 | for i in 0..2 { 12 | kprintln!("Thread {} yield {} times: ", cnt, i + 1); 13 | thread::schedule(); 14 | } 15 | kprintln!("multi_thread_sema(): Thread {} up sema.", cnt); 16 | sema.up(); 17 | thread::schedule(); 18 | } 19 | 20 | pub fn main() { 21 | kprintln!("############################## MUTEX SEMA TEST ##############################"); 22 | kprintln!("test::mutex::main(): Test semaphore."); 23 | kprintln!("test::mutex::main(): Initialize global semaphore size to 5."); 24 | let s1 = Arc::new(Semaphore::new(5)); 25 | let s2 = s1.clone(); 26 | let s3 = s1.clone(); 27 | let s4 = s1.clone(); 28 | let s5 = s1.clone(); 29 | let s6 = s1.clone(); 30 | thread::spawn("sema5-0", move || multi_thread_sema(s1)); 31 | thread::spawn("sema5-1", move || multi_thread_sema(s2)); 32 | thread::spawn("sema5-2", move || multi_thread_sema(s3)); 33 | thread::spawn("sema5-3", move || multi_thread_sema(s4)); 34 | thread::spawn("sema5-4", move || multi_thread_sema(s5)); 35 | thread::spawn("sema5-5", move || multi_thread_sema(s6)); 36 | for i in 0..10 { 37 | kprintln!("Test thread yield {} times: ", i + 1); 38 | thread::schedule(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/sync/lazy.rs: -------------------------------------------------------------------------------- 1 | use core::{cell::Cell, ops::Deref}; 2 | 3 | use crate::sync::OnceCell; 4 | 5 | /// A value initialized on the first access. It is thread-safe, and can be used in statics. 6 | /// 7 | /// ## Examples 8 | /// ```rust 9 | /// // Suppose it's in the std environment 10 | /// static HASHMAP: Lazy> = Lazy::new(|| { 11 | /// println!("initializing"); 12 | /// let mut m = HashMap::new(); 13 | /// m.insert(13, "Spica".to_string()); 14 | /// m.insert(74, "Hoyten".to_string()); 15 | /// m 16 | /// }); 17 | /// 18 | /// fn main() { 19 | /// println!("ready"); 20 | /// std::thread::spawn(|| { 21 | /// println!("{:?}", HASHMAP.get(&13)); 22 | /// }).join().unwrap(); 23 | /// println!("{:?}", HASHMAP.get(&74)); 24 | /// 25 | /// // Prints: 26 | /// // ready 27 | /// // initializing 28 | /// // Some("Spica") 29 | /// // Some("Hoyten") 30 | /// } 31 | /// ``` 32 | pub struct Lazy T> { 33 | cell: OnceCell, 34 | init: Cell>, 35 | } 36 | 37 | impl T> Lazy { 38 | pub const fn new(f: F) -> Self { 39 | Self { 40 | cell: OnceCell::new(), 41 | init: Cell::new(Some(f)), 42 | } 43 | } 44 | 45 | pub fn get(&self) -> &T { 46 | self.cell.get_or_init(|| { 47 | let f = self.init.take().unwrap(); 48 | f() 49 | }) 50 | } 51 | } 52 | 53 | impl T> Deref for Lazy { 54 | type Target = T; 55 | 56 | #[inline] 57 | fn deref(&self) -> &T { 58 | self.get() 59 | } 60 | } 61 | 62 | unsafe impl Sync for Lazy {} 63 | -------------------------------------------------------------------------------- /test/unit/thread/adder.rs: -------------------------------------------------------------------------------- 1 | use alloc::sync::Arc; 2 | 3 | use crate::sync::{self, Mutex, Semaphore}; 4 | use crate::thread; 5 | 6 | static mut X: Option> = None; 7 | static mut Y: usize = 0; 8 | 9 | static NUM: usize = 5000; 10 | 11 | #[allow(unused)] 12 | pub fn main() { 13 | let finish = Arc::new(Semaphore::new(0)); 14 | let f1 = finish.clone(); 15 | let f2 = finish.clone(); 16 | let f3 = finish.clone(); 17 | let f4 = finish.clone(); 18 | unsafe { 19 | X.replace(Mutex::new(0)); 20 | } 21 | 22 | thread::spawn("good_adder1", move || good_adder(f1)); 23 | thread::spawn("good_adder2", move || good_adder(f2)); 24 | finish.down(); 25 | finish.down(); 26 | kprintln!("Good adder done."); 27 | 28 | thread::spawn("bad_adder1", move || bad_adder(f3)); 29 | thread::spawn("bad_adder2", move || bad_adder(f4)); 30 | finish.down(); 31 | finish.down(); 32 | kprintln!("Bad adder done."); 33 | 34 | assert_eq!(unsafe { *(X.as_ref().unwrap().lock()) }, 2 * NUM); 35 | kprintln!("Bad adder results: {}:{}", unsafe { Y }, 2 * NUM); 36 | } 37 | 38 | pub fn good_adder(finish: Arc) { 39 | let mut i = 0; 40 | while i < NUM { 41 | let mut x = unsafe { X.as_ref().unwrap().lock() }; 42 | for _ in 0..(i % 100) {} 43 | *x += 1; 44 | i += 1; 45 | } 46 | 47 | finish.up(); 48 | } 49 | 50 | pub fn bad_adder(finish: Arc) { 51 | let mut i = 0; 52 | while i < NUM { 53 | let mut y = unsafe { Y }; 54 | y += 1; 55 | for _ in 0..(i % 100) {} 56 | unsafe { Y = y }; 57 | 58 | i += 1; 59 | } 60 | 61 | finish.up(); 62 | } 63 | -------------------------------------------------------------------------------- /user/vm/mmap-clean.c: -------------------------------------------------------------------------------- 1 | /* Verifies that mmap'd regions are only written back on munmap 2 | if the data was actually modified in memory. */ 3 | 4 | #include "sample.inc" 5 | #include "user.h" 6 | 7 | void main() { 8 | static const char overwrite[] = "Now is the time for all good..."; 9 | static char buffer[sizeof sample - 1]; 10 | char* actual = (char*)0x54321000; 11 | int handle; 12 | mapid_t map; 13 | 14 | /* Open file, map, verify data. */ 15 | assert((handle = open("sample.txt", O_RDWR)) > 2, "open \"sample.txt\""); 16 | assert((map = mmap(handle, actual)) != MAP_FAILED, "mmap \"sample.txt\""); 17 | if (memcmp(actual, sample, strlen(sample))) panic("read of mmap'd file reported bad data"); 18 | 19 | /* Modify file. */ 20 | assert(write(handle, overwrite, strlen(overwrite)) == (int)strlen(overwrite), 21 | "write \"sample.txt\""); 22 | 23 | /* Close mapping. Data should not be written back, because we 24 | didn't modify it via the mapping. */ 25 | printf("munmap \"sample.txt\""); 26 | munmap(map); 27 | 28 | /* Read file back. */ 29 | printf("seek \"sample.txt\""); 30 | seek(handle, 0); 31 | assert(read(handle, buffer, sizeof buffer) == sizeof buffer, "read \"sample.txt\""); 32 | 33 | /* Verify that file overwrite worked. */ 34 | if (memcmp(buffer, overwrite, strlen(overwrite)) || 35 | memcmp(buffer + strlen(overwrite), sample + strlen(overwrite), 36 | strlen(sample) - strlen(overwrite))) { 37 | if (!memcmp(buffer, sample, strlen(sample))) { 38 | panic("munmap wrote back clean page"); 39 | } else { 40 | panic("read surprising data from file"); 41 | } 42 | } else 43 | printf("file change was retained after munmap"); 44 | } 45 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | WORKDIR /root 5 | 6 | # Install General toolchain 7 | RUN apt-get update && apt-get install -y curl git python3 perl build-essential wget 8 | RUN apt-get install -y \ 9 | autoconf automake autotools-dev libmpc-dev libmpfr-dev libgmp-dev \ 10 | gawk build-essential bison flex texinfo gperf libtool patchutils bc \ 11 | zlib1g-dev libexpat-dev ninja-build pkg-config libglib2.0-dev \ 12 | libpixman-1-dev libsdl2-dev gcc-riscv64-unknown-elf \ 13 | gdb-multiarch binutils-riscv64-unknown-elf 14 | 15 | # Install qemu 16 | ARG QEMU_VERSION=7.0.0 17 | 18 | RUN wget https://download.qemu.org/qemu-${QEMU_VERSION}.tar.xz && \ 19 | tar xvJf qemu-${QEMU_VERSION}.tar.xz && \ 20 | cd /root/qemu-${QEMU_VERSION} && \ 21 | ./configure --target-list=riscv64-softmmu,riscv64-linux-user && \ 22 | make -j$(nproc) && \ 23 | make install 24 | RUN rm -rf qemu-${QEMU_VERSION} qemu-${QEMU_VERSION}.tar.xz 25 | 26 | # Install Rust 27 | # - https://www.rust-lang.org/tools/install 28 | 29 | ARG RUST_VERSION=1.70 30 | ENV RUSTUP_HOME=/usr/local/rustup \ 31 | CARGO_HOME=/usr/local/cargo \ 32 | PATH=/usr/local/cargo/bin:$PATH 33 | 34 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ 35 | | sh -s -- -y --no-modify-path --profile minimal --default-toolchain ${RUST_VERSION} && \ 36 | chmod -R a+w $RUSTUP_HOME $CARGO_HOME && \ 37 | rustup --version && \ 38 | cargo --version 39 | 40 | RUN cargo install cargo-binutils --vers ~0.2 && \ 41 | rustup target add riscv64gc-unknown-none-elf 42 | 43 | RUN rustup component add clippy rustfmt 44 | 45 | # Make GDB easier 46 | RUN mkdir -p ~/.config/gdb 47 | RUN echo "add-auto-load-safe-path /" > ~/.config/gdb/gdbinit 48 | 49 | # Use tacos as the runner 50 | COPY tacos /usr/bin 51 | RUN chmod +x /usr/bin/tacos -------------------------------------------------------------------------------- /user/userprogs/README.md: -------------------------------------------------------------------------------- 1 | # LAB2 Syscall Tests 2 | 3 | ## Functionality of system calls 4 | 5 | - Test argument passing on command line. 6 | - args-none 7 | - args-many 8 | 9 | - Test "open" system call. 10 | - open-create 11 | - open-rdwr 12 | - open-trunc 13 | - open-many 14 | 15 | - Test "read" system call. 16 | - read-normal 17 | - read-zero 18 | 19 | - Test "write" system call. 20 | - write-normal 21 | - write-zero 22 | 23 | - Test "close" system call. 24 | - close-normal 25 | 26 | - Test "exec" system call. 27 | - exec-once 28 | - exec-multiple 29 | - exec-arg 30 | 31 | - Test "wait" system call. 32 | - wait-simple 33 | - wait-twice 34 | 35 | - Test "exit" system call. 36 | - exit 37 | 38 | - Test "halt" system call. 39 | - halt 40 | 41 | - Test recursive execution of user programs. 42 | - multi-recurse 43 | 44 | - Test read-only executable feature. 45 | - rox-simple 46 | - rox-child 47 | - rox-multichild 48 | 49 | ## Robustness of system calls 50 | 51 | - Test robustness of file descriptor handling. 52 | - close-stdio 53 | - close-badfd 54 | - close-twice 55 | - close-by-child 56 | - read-badfd 57 | - read-stdout 58 | - write-badfd 59 | - write-stdin 60 | 61 | - Test robustness of buffer copying across page boundaries. 62 | - boundary-normal 63 | - boundary-bad 64 | 65 | - Test handling of null pointer and empty strings. 66 | - open-invalid 67 | 68 | - Test robustness of system call implementation and pointer handling. 69 | - sc-bad-sp 70 | - sc-bad-args 71 | 72 | - Test robustness of "exec" and "wait" system calls. 73 | - exec-invalid 74 | - wait-bad-pid 75 | - wait-killed 76 | 77 | - Test robustness of exception handling. 78 | - bad-load 79 | - bad-store 80 | - bad-jump 81 | - bad-load2 82 | - bad-store2 83 | - bad-jump2 84 | -------------------------------------------------------------------------------- /src/sync/sema.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::VecDeque; 2 | use alloc::sync::Arc; 3 | use core::cell::{Cell, RefCell}; 4 | 5 | use crate::sbi; 6 | use crate::thread::{self, Thread}; 7 | 8 | /// Atomic counting semaphore 9 | /// 10 | /// # Examples 11 | /// ``` 12 | /// let sema = Semaphore::new(0); 13 | /// sema.down(); 14 | /// sema.up(); 15 | /// ``` 16 | #[derive(Clone)] 17 | pub struct Semaphore { 18 | value: Cell, 19 | waiters: RefCell>>, 20 | } 21 | 22 | unsafe impl Sync for Semaphore {} 23 | unsafe impl Send for Semaphore {} 24 | 25 | impl Semaphore { 26 | /// Creates a new semaphore of initial value n. 27 | pub const fn new(n: usize) -> Self { 28 | Semaphore { 29 | value: Cell::new(n), 30 | waiters: RefCell::new(VecDeque::new()), 31 | } 32 | } 33 | 34 | /// P operation 35 | pub fn down(&self) { 36 | let old = sbi::interrupt::set(false); 37 | 38 | // Is semaphore available? 39 | while self.value() == 0 { 40 | // `push_front` ensures to wake up threads in a fifo manner 41 | self.waiters.borrow_mut().push_front(thread::current()); 42 | 43 | // Block the current thread until it's awakened by an `up` operation 44 | thread::block(); 45 | } 46 | self.value.set(self.value() - 1); 47 | 48 | sbi::interrupt::set(old); 49 | } 50 | 51 | /// V operation 52 | pub fn up(&self) { 53 | let old = sbi::interrupt::set(false); 54 | let count = self.value.replace(self.value() + 1); 55 | 56 | // Check if we need to wake up a sleeping waiter 57 | if let Some(thread) = self.waiters.borrow_mut().pop_back() { 58 | assert_eq!(count, 0); 59 | 60 | thread::wake_up(thread.clone()); 61 | } 62 | 63 | sbi::interrupt::set(old); 64 | } 65 | 66 | /// Get the current value of a semaphore 67 | pub fn value(&self) -> usize { 68 | self.value.get() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/schedule/donation/two.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | static mut EXIT_STATUS: [i8; 3] = [0; 3]; 4 | 5 | fn medium(m_lock: Arc) { 6 | m_lock.acquire(); 7 | 8 | unsafe { 9 | assert_eq!(EXIT_STATUS, [0, 0, 1], "Medium should exit after high."); 10 | EXIT_STATUS[1] = 1; 11 | } 12 | 13 | m_lock.release(); 14 | } 15 | 16 | fn high(h_lock: Arc) { 17 | h_lock.acquire(); 18 | 19 | unsafe { 20 | assert_eq!(EXIT_STATUS, [0, 0, 0], "Hish should exit first."); 21 | EXIT_STATUS[2] = 1; 22 | } 23 | 24 | h_lock.release(); 25 | } 26 | 27 | pub fn main() { 28 | let m_lock = Arc::new(Sleep::default()); 29 | let h_lock = Arc::new(Sleep::default()); 30 | m_lock.acquire(); 31 | h_lock.acquire(); 32 | 33 | let create_and_check = |f: fn(Arc), expected, lock| { 34 | let l = Arc::clone(lock); 35 | Builder::new(move || f(l)) 36 | .name("child") 37 | .priority(expected) 38 | .spawn(); 39 | let priority = get_priority(); 40 | assert_eq!( 41 | expected, priority, 42 | "This thread should have priority {}. Actual priority {}.", 43 | expected, priority 44 | ); 45 | }; 46 | 47 | // Create two child thread with higher priority. They are donating priority to main. 48 | let m_priority = PRI_DEFAULT + 1; 49 | let h_priority = PRI_DEFAULT + 2; 50 | create_and_check(medium, m_priority, &m_lock); 51 | create_and_check(high, h_priority, &h_lock); 52 | 53 | let release_and_check = |expected, lock: &Arc| { 54 | lock.release(); 55 | 56 | let priority = get_priority(); 57 | assert_eq!( 58 | expected, priority, 59 | "This thread should have priority {}. Actual priority {}.", 60 | expected, priority 61 | ); 62 | }; 63 | 64 | release_and_check(m_priority, &h_lock); 65 | release_and_check(PRI_DEFAULT, &m_lock); 66 | 67 | unsafe { 68 | assert_eq!(EXIT_STATUS, [0, 1, 1], "Medium & high should have exited."); 69 | }; 70 | 71 | pass(); 72 | } 73 | -------------------------------------------------------------------------------- /doc/lab1.md: -------------------------------------------------------------------------------- 1 | # Lab 1: Scheduling 2 | 3 | --- 4 | 5 | ## Information 6 | 7 | Name: 8 | 9 | Email: 10 | 11 | > Please cite any forms of information source that you have consulted during finishing your assignment, except the TacOS documentation, course slides, and course staff. 12 | 13 | > With any comments that may help TAs to evaluate your work better, please leave them here 14 | 15 | ## Alarm Clock 16 | 17 | ### Data Structures 18 | 19 | > A1: Copy here the **declaration** of every new or modified struct, enum type, and global variable. State the purpose of each within 30 words. 20 | 21 | ### Algorithms 22 | 23 | > A2: Briefly describe what happens in `sleep()` and the timer interrupt handler. 24 | 25 | > A3: What are your efforts to minimize the amount of time spent in the timer interrupt handler? 26 | 27 | ### Synchronization 28 | 29 | > A4: How are race conditions avoided when `sleep()` is being called concurrently? 30 | 31 | > A5: How are race conditions avoided when a timer interrupt occurs during a call to `sleep()`? 32 | 33 | ## Priority Scheduling 34 | 35 | ### Data Structures 36 | 37 | > B1: Copy here the **declaration** of every new or modified struct, enum type, and global variable. State the purpose of each within 30 words. 38 | 39 | > B2: Explain the data structure that tracks priority donation. Clarify your answer with any forms of diagram (e.g., the ASCII art). 40 | 41 | ### Algorithms 42 | 43 | > B3: How do you ensure that the highest priority thread waiting for a lock, semaphore, or condition variable wakes up first? 44 | 45 | > B4: Describe the sequence of events when a thread tries to acquire a lock. How is nested donation handled? 46 | 47 | > B5: Describe the sequence of events when a lock, which a higher-priority thread is waiting for, is released. 48 | 49 | ### Synchronization 50 | 51 | > B6: Describe a potential race in `thread::set_priority()` and explain how your implementation avoids it. Can you use a lock to avoid this race? 52 | 53 | ## Rationale 54 | 55 | > C1: Have you considered other design possibilities? You can talk about anything in your solution that you once thought about doing them another way. And for what reasons that you made your choice? 56 | -------------------------------------------------------------------------------- /src/thread/switch.rs: -------------------------------------------------------------------------------- 1 | //! Context Switch 2 | //! 3 | //! [`switch`] stores ra(return address), sp(stack pointer) and all 4 | //! callee-saved general purpose registers on the current stack. 5 | //! 6 | //! According to the RISC-V calling convention, all caller-saved registers 7 | //! should've already been preserved in the stack frame of the caller 8 | //! (i.e. [`Manager::schedule`]). 9 | 10 | use alloc::sync::Arc; 11 | use core::arch::global_asm; 12 | 13 | use crate::thread::{Context, Manager, Thread}; 14 | 15 | #[allow(improper_ctypes)] 16 | extern "C" { 17 | /// Save current registers in the "old" context, and load from the "new" context. 18 | /// 19 | /// The first argument is not used in this function, but it 20 | /// will be forwarded to [`schedule_tail_wrapper`]. 21 | pub fn switch(previous: *const Thread, old: *mut Context, new: *mut Context); 22 | } 23 | 24 | global_asm! {r#" 25 | .section .text 26 | .globl switch 27 | switch: 28 | sd ra, 0x0(a1) 29 | ld ra, 0x0(a2) 30 | sd sp, 0x8(a1) 31 | ld sp, 0x8(a2) 32 | sd s0, 0x10(a1) 33 | ld s0, 0x10(a2) 34 | sd s1, 0x18(a1) 35 | ld s1, 0x18(a2) 36 | sd s2, 0x20(a1) 37 | ld s2, 0x20(a2) 38 | sd s3, 0x28(a1) 39 | ld s3, 0x28(a2) 40 | sd s4, 0x30(a1) 41 | ld s4, 0x30(a2) 42 | sd s5, 0x38(a1) 43 | ld s5, 0x38(a2) 44 | sd s6, 0x40(a1) 45 | ld s6, 0x40(a2) 46 | sd s7, 0x48(a1) 47 | ld s7, 0x48(a2) 48 | sd s8, 0x50(a1) 49 | ld s8, 0x50(a2) 50 | sd s9, 0x58(a1) 51 | ld s9, 0x58(a2) 52 | sd s10, 0x60(a1) 53 | ld s10, 0x60(a2) 54 | sd s11, 0x68(a1) 55 | ld s11, 0x68(a2) 56 | 57 | j schedule_tail_wrapper 58 | "#} 59 | 60 | /// A thin wrapper over [`Manager::schedule_tail`] 61 | /// 62 | /// Note: Stack is not properly built in [`switch`]. Therefore, 63 | /// this function should never be inlined. 64 | #[no_mangle] 65 | #[inline(never)] 66 | extern "C" fn schedule_tail_wrapper(previous: *const Thread) { 67 | Manager::get().schedule_tail(unsafe { Arc::from_raw(previous) }); 68 | } 69 | -------------------------------------------------------------------------------- /user/lib/user.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_USER_H 2 | #define __LIB_USER_H 3 | 4 | #include 5 | 6 | #include "fcntl.h" 7 | #include "fstat.h" 8 | #include "types.h" 9 | 10 | #define NULL ((void*)0) 11 | #define ROUND_UP(p, align) (((uint64)p + (align)-1) / (align) * (align)) 12 | #define ROUND_DOWN(p, align) ((uint64)p / (align) * (align)) 13 | #define PANIC_EXIT 12345 14 | #define NORMAL_EXIT 0 15 | 16 | #define panic(fmt, args...) \ 17 | do { \ 18 | fprintf(2, "panicked at '" fmt "', %s:%d\n", ##args, __FILE__, __LINE__); \ 19 | exit(PANIC_EXIT); \ 20 | } while (0) 21 | 22 | #define assert(condition, args...) \ 23 | if (condition) { \ 24 | } else { \ 25 | panic("assertion failed: `" #condition "`" args); \ 26 | } 27 | 28 | // system calls 29 | void halt(void); 30 | void exit(int status); 31 | int exec(const char* pathname, const char* argv[]); 32 | int wait(int pid); 33 | int remove(const char* pathname); 34 | int open(const char* pathname, int flags); 35 | int read(int fd, void* buffer, uint size); 36 | int write(int fd, const void* buffer, uint size); 37 | void seek(int fd, uint position); 38 | int tell(int fd); 39 | int close(int fd); 40 | int fstat(int fd, stat* buf); 41 | int mmap(int fd, void* addr); 42 | void munmap(int mapid); 43 | int chdir(const char* dir); 44 | int mkdir(const char* dir); 45 | 46 | // ulib.c 47 | void fprintf(int fd, const char* fmt, ...); 48 | void printf(const char* fmt, ...); 49 | int strcmp(const char*, const char*); 50 | char* strcpy(char*, const char*); 51 | int strlen(const char*); 52 | void itoa(char*, int); 53 | int atoi(const char*); 54 | void* memset(void*, int, uint); 55 | int memcmp(const void*, const void*, uint64); 56 | void* memcpy(void*, const void*, size_t); 57 | void check_file(const char*, const void* buf, size_t); 58 | void check_file_handle(int fd, const char* file_name, const void* buf_, size_t size); 59 | uint64 r_sp(); 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /test/schedule/priority/sema.rs: -------------------------------------------------------------------------------- 1 | use crate::sync::Semaphore; 2 | 3 | use super::*; 4 | 5 | const THREAD_CNT: usize = 10; 6 | const WAKE_TIME: i64 = 5 * TICKS_PER_SEC as i64; 7 | static mut EXIT_STATUS: [i8; THREAD_CNT + 1] = [0; THREAD_CNT + 1]; 8 | 9 | const EXPECTED_STATUS: [[i8; THREAD_CNT + 1]; THREAD_CNT + 1] = [ 10 | // Similar with ./alarm.rs 11 | [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 12 | [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1], 13 | [0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1], 14 | [0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1], 15 | [0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1], 16 | [0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1], 17 | [0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1], 18 | [0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1], 19 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 20 | [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], 21 | [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], 22 | ]; 23 | 24 | fn sema_priority_thread(tid: usize, sema: Arc) { 25 | // Busy-wait until the current time changes. 26 | sema.down(); 27 | 28 | unsafe { 29 | // Check other thread's status before exit. 30 | assert_eq!( 31 | EXIT_STATUS, EXPECTED_STATUS[tid], 32 | "When thread {} exit, expected status is {:?}, but real status is {:?}.", 33 | tid, EXPECTED_STATUS[tid], EXIT_STATUS 34 | ); 35 | 36 | // Mark self as exited. 37 | EXIT_STATUS[tid] = 1; 38 | } 39 | } 40 | 41 | pub fn main() { 42 | // Main thread has tid 0. 43 | let sema = Arc::new(Semaphore::new(0)); 44 | 45 | set_priority(PRI_MIN); 46 | 47 | for tid in 1..=THREAD_CNT { 48 | let priority = PRI_DEFAULT - ((tid as u32 + 2) % 10) - 1; 49 | let s = Arc::clone(&sema); 50 | Builder::new(move || sema_priority_thread(tid, s)) 51 | .name("child") 52 | .priority(priority) 53 | .spawn(); 54 | } 55 | 56 | for _ in 1..=THREAD_CNT { 57 | sema.up(); 58 | } 59 | 60 | unsafe { 61 | assert_eq!( 62 | EXIT_STATUS, EXPECTED_STATUS[0], 63 | "When main thread {} exit, expected status is {:?}, but real status is {:?}.", 64 | 0, EXPECTED_STATUS[0], EXIT_STATUS 65 | ); 66 | } 67 | 68 | pass(); 69 | } 70 | -------------------------------------------------------------------------------- /test/schedule/alarm/multiple.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Creates `THREAD_CNT` threads, each of which sleeps a different, fixed 3 | //! duration, `iters` times. Records the wake-up order and verifies 4 | //! that it is valid. 5 | //! 6 | 7 | use super::*; 8 | 9 | const THREAD_CNT: usize = 5; 10 | const ITERS_MAX: usize = 7; 11 | const TEST_START: i64 = 10; 12 | static mut WAKE_TICKS: [[i64; ITERS_MAX]; THREAD_CNT] = [[0; ITERS_MAX]; THREAD_CNT]; 13 | 14 | fn sleeper(tid: usize, iters: usize) { 15 | // Make sure we're at the beginning of a tiemr tick. 16 | thread::sleep(1); 17 | 18 | // Sleeper with different id has different sleep durations. 19 | let duration = tid + 1; 20 | 21 | // Sleep & record wake up time. 22 | for i in 0..iters { 23 | let until: i64 = TEST_START + (duration * (i + 1)) as i64; 24 | thread::sleep(until - timer::timer_ticks()); 25 | unsafe { WAKE_TICKS[tid][i] = timer::timer_ticks() }; 26 | thread::schedule(); 27 | } 28 | } 29 | 30 | fn test_sleeper(iters: usize) { 31 | for tid in 0..THREAD_CNT { 32 | // TODO: If thread.name use String, the name could be more precise. 33 | thread::spawn("Sleeper", move || sleeper(tid, iters)); 34 | } 35 | 36 | // Wait long enough for all the threads to finish. 37 | thread::sleep((20 + THREAD_CNT * iters) as i64); 38 | 39 | for i in 0..iters { 40 | for tid in 0..THREAD_CNT { 41 | let duration = tid + 1; 42 | let wake_time = TEST_START + (duration * (i + 1)) as i64; 43 | let real_time = unsafe { WAKE_TICKS[tid][i] }; 44 | 45 | assert!( 46 | wake_time > 0, 47 | "Sleeper {} is supposed to wake up at tick {}, but it doesn't.", 48 | tid, 49 | wake_time, 50 | ); 51 | 52 | assert_eq!( 53 | real_time, wake_time, 54 | "Sleeper {} is supposed to wake up at tick {}, but wakes up at tick {}.", 55 | tid, wake_time, real_time 56 | ); 57 | } 58 | } 59 | 60 | pass(); 61 | } 62 | 63 | pub fn main() { 64 | test_sleeper(7); 65 | } 66 | 67 | pub mod single { 68 | use super::test_sleeper; 69 | 70 | pub fn main() { 71 | test_sleeper(1); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/trap/pagefault.rs: -------------------------------------------------------------------------------- 1 | use crate::mem::userbuf::{ 2 | __knrl_read_usr_byte_pc, __knrl_read_usr_exit, __knrl_write_usr_byte_pc, __knrl_write_usr_exit, 3 | }; 4 | use crate::mem::PageTable; 5 | use crate::thread::{self}; 6 | use crate::trap::Frame; 7 | use crate::userproc; 8 | 9 | use riscv::register::scause::Exception::{self, *}; 10 | use riscv::register::sstatus::{self, SPP}; 11 | 12 | pub fn handler(frame: &mut Frame, fault: Exception, addr: usize) { 13 | let privilege = frame.sstatus.spp(); 14 | 15 | let present = { 16 | let table = unsafe { PageTable::effective_pagetable() }; 17 | match table.get_pte(addr) { 18 | Some(entry) => entry.is_valid(), 19 | None => false, 20 | } 21 | }; 22 | 23 | unsafe { sstatus::set_sie() }; 24 | 25 | kprintln!( 26 | "Page fault at {:#x}: {} error {} page in {} context.", 27 | addr, 28 | if present { "rights" } else { "not present" }, 29 | match fault { 30 | StorePageFault => "writing", 31 | LoadPageFault => "reading", 32 | InstructionPageFault => "fetching instruction", 33 | _ => panic!("Unknown Page Fault"), 34 | }, 35 | match privilege { 36 | SPP::Supervisor => "kernel", 37 | SPP::User => "user", 38 | } 39 | ); 40 | 41 | match privilege { 42 | SPP::Supervisor => { 43 | if frame.sepc == __knrl_read_usr_byte_pc as _ { 44 | // Failed to read user byte from kernel space when trap in pagefault 45 | frame.x[11] = 1; // set a1 to non-zero 46 | frame.sepc = __knrl_read_usr_exit as _; 47 | } else if frame.sepc == __knrl_write_usr_byte_pc as _ { 48 | // Failed to write user byte from kernel space when trap in pagefault 49 | frame.x[11] = 1; // set a1 to non-zero 50 | frame.sepc = __knrl_write_usr_exit as _; 51 | } else { 52 | panic!("Kernel page fault"); 53 | } 54 | } 55 | SPP::User => { 56 | kprintln!( 57 | "User thread {} dying due to page fault.", 58 | thread::current().name() 59 | ); 60 | userproc::exit(-1); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /user/lib/random.c: -------------------------------------------------------------------------------- 1 | /* RC4-based pseudo-random number generator (PRNG). 2 | 3 | RC4 is a stream cipher. We're not using it here for its 4 | cryptographic properties, but because it is easy to implement 5 | and its output is plenty random for non-cryptographic 6 | purposes. 7 | 8 | See http://en.wikipedia.org/wiki/RC4_(cipher) for information 9 | on RC4.*/ 10 | 11 | #include "random.h" 12 | 13 | /* RC4 state. */ 14 | static uint8 s[256]; /* S[]. */ 15 | static uint8 s_i, s_j; /* i, j. */ 16 | 17 | /* Already initialized? */ 18 | static int inited; 19 | 20 | /* Swaps some bytes pointed to by A and B. */ 21 | static void swap_bytes(void* a_, void* b_, size_t size) { 22 | uint8* a = a_; 23 | uint8* b = b_; 24 | size_t i; 25 | 26 | for (i = 0; i < size; i++) { 27 | uint8 t = a[i]; 28 | a[i] = b[i]; 29 | b[i] = t; 30 | } 31 | } 32 | 33 | /* Initializes or reinitializes the PRNG with the given SEED. */ 34 | void random_init(unsigned seed) { 35 | uint8* seedp = (uint8*)&seed; 36 | int i; 37 | uint8 j; 38 | 39 | if (inited) return; 40 | 41 | for (i = 0; i < 256; i++) s[i] = i; 42 | for (i = j = 0; i < 256; i++) { 43 | j += s[i] + seedp[i % sizeof seed]; 44 | swap_bytes(s + i, s + j, 1); 45 | } 46 | 47 | s_i = s_j = 0; 48 | inited = 1; 49 | } 50 | 51 | /* Writes SIZE random bytes into BUF. */ 52 | void random_bytes(void* buf_, size_t size) { 53 | uint8* buf; 54 | 55 | if (!inited) random_init(0); 56 | 57 | for (buf = buf_; size-- > 0; buf++) { 58 | uint8 s_k; 59 | 60 | s_i++; 61 | s_j += s[s_i]; 62 | swap_bytes(s + s_i, s + s_j, 1); 63 | 64 | s_k = s[s_i] + s[s_j]; 65 | *buf = s[s_k]; 66 | } 67 | } 68 | 69 | /* Returns a pseudo-random unsigned long. 70 | Use random_ulong() % n to obtain a random number in the range 71 | 0...n (exclusive). */ 72 | uint64 random_ulong(void) { 73 | uint64 ul; 74 | random_bytes(&ul, sizeof ul); 75 | return ul; 76 | } 77 | 78 | void shuffle(void* buf_, size_t cnt, size_t size) { 79 | uint8* buf = buf_; 80 | size_t i; 81 | 82 | for (i = 0; i < cnt; i++) { 83 | size_t j = i + random_ulong() % (cnt - i); 84 | swap_bytes(buf + i * size, buf + j * size, size); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tacos" 3 | rust-version = "1.68.0" 4 | version = "0.1.0" 5 | readme = "README.md" 6 | 7 | [dependencies] 8 | riscv = "0.8" 9 | bitflags = "1.3.2" 10 | elf_rs = "0.3.0" 11 | fdt = "0.1.5" 12 | 13 | [profile.dev] 14 | panic = "abort" 15 | 16 | [profile.release] 17 | panic = "abort" 18 | 19 | [features] 20 | debug = [] 21 | 22 | shell = [] 23 | 24 | thread-scheduler-priority = [] 25 | 26 | # ----------------------------------- TEST ----------------------------------- # 27 | 28 | test = [] 29 | 30 | # --------------------------------- UNIT TEST -------------------------------- # 31 | 32 | test-unit = ["test"] 33 | 34 | test-sync = ["test-unit"] 35 | test-sync-condvar = ["test-unit"] 36 | test-sync-sema_fifo = ["test-unit"] 37 | 38 | test-thread = ["test-unit"] 39 | test-thread-adder = ["test-unit"] 40 | test-thread-block = ["test-unit"] 41 | test-thread-bomb = ["test-unit"] 42 | test-thread-spin_yield = ["test-unit"] 43 | test-thread-spin_interrupt = ["test-unit"] 44 | 45 | test-mem-malloc = ["test-unit"] 46 | 47 | test-fs-inmem = ["test-unit"] 48 | test-fs-disk = ["test-unit"] 49 | test-fs-disk-simple = ["test-unit", "test-fs-disk"] 50 | 51 | test-virtio = ["test-unit"] 52 | test-virtio-simple = ["test-unit"] 53 | 54 | # ------------------------------- SCHEDULE TEST ------------------------------ # 55 | 56 | test-schedule = ["thread-scheduler-priority", "test"] 57 | 58 | test-alarm-zero = ["test-schedule"] 59 | test-alarm-negative = ["test-schedule"] 60 | test-alarm-simultaneous = ["test-schedule"] 61 | test-alarm-single = ["test-schedule"] 62 | test-alarm-multiple = ["test-schedule"] 63 | 64 | test-priority-alarm = ["test-schedule"] 65 | test-priority-change = ["test-schedule"] 66 | test-priority-condvar = ["test-schedule"] 67 | test-priority-fifo = ["test-schedule"] 68 | test-priority-preempt = ["test-schedule"] 69 | test-priority-sema = ["test-schedule"] 70 | 71 | test-donation-chain = ["test-schedule"] 72 | test-donation-lower = ["test-schedule"] 73 | test-donation-nest = ["test-schedule"] 74 | test-donation-one = ["test-schedule"] 75 | test-donation-sema = ["test-schedule"] 76 | test-donation-two = ["test-schedule"] 77 | test-donation-three = ["test-schedule"] 78 | 79 | # --------------------------------- USER TEST -------------------------------- # 80 | 81 | test-user = ["test"] 82 | -------------------------------------------------------------------------------- /test/schedule/donation/nest.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | static mut EXIT_STATUS: [i8; 3] = [0; 3]; 4 | 5 | fn medium(pair: Arc<(Sleep, Sleep)>) { 6 | let (m_lock, h_lock) = &*pair; 7 | 8 | h_lock.acquire(); 9 | m_lock.acquire(); 10 | 11 | let expected = PRI_DEFAULT + 2; 12 | let priority = get_priority(); 13 | assert_eq!( 14 | expected, priority, 15 | "Medium thread should have priority {}. Actual priority {}.", 16 | expected, priority 17 | ); 18 | 19 | m_lock.release(); 20 | schedule(); 21 | 22 | h_lock.release(); 23 | schedule(); 24 | 25 | unsafe { 26 | assert_eq!(EXIT_STATUS, [0, 0, 1], "Medium should exit after high."); 27 | EXIT_STATUS[1] = 1; 28 | } 29 | } 30 | 31 | fn high(pair: Arc<(Sleep, Sleep)>) { 32 | let (_, h_lock) = &*pair; 33 | 34 | h_lock.acquire(); 35 | 36 | h_lock.release(); 37 | 38 | unsafe { 39 | assert_eq!(EXIT_STATUS, [0, 0, 0], "Hish should exit first."); 40 | EXIT_STATUS[2] = 1; 41 | } 42 | } 43 | 44 | pub fn main() { 45 | let pair = Arc::new((Sleep::default(), Sleep::default())); 46 | 47 | let (m_lock, _) = &*pair; 48 | 49 | m_lock.acquire(); 50 | 51 | let create_and_check = |f: fn(Arc<(Sleep, Sleep)>), expected| { 52 | let p = Arc::clone(&pair); 53 | Builder::new(move || f(p)) 54 | .name("child") 55 | .priority(expected) 56 | .spawn(); 57 | 58 | schedule(); 59 | 60 | let priority = get_priority(); 61 | assert_eq!( 62 | expected, priority, 63 | "Low thread should have priority {}. Actual priority {}.", 64 | expected, priority 65 | ); 66 | }; 67 | 68 | // Create two child thread with higher priority. They are donating priority to main. 69 | let m_priority = PRI_DEFAULT + 1; 70 | let h_priority = PRI_DEFAULT + 2; 71 | create_and_check(medium, m_priority); 72 | create_and_check(high, h_priority); 73 | 74 | m_lock.release(); 75 | 76 | let priority = get_priority(); 77 | assert_eq!( 78 | PRI_DEFAULT, priority, 79 | "Low thread should have priority {}. Actual priority {}.", 80 | PRI_DEFAULT, priority 81 | ); 82 | 83 | unsafe { 84 | assert_eq!(EXIT_STATUS, [0, 1, 1], "Medium & high should have exited."); 85 | }; 86 | 87 | pass(); 88 | } 89 | -------------------------------------------------------------------------------- /test/unit/fs/disk/chlen.rs: -------------------------------------------------------------------------------- 1 | use crate::device::virtio::SECTOR_SIZE; 2 | use crate::fs::disk::DISKFS; 3 | use crate::fs::{File, FileSys}; 4 | use crate::io::prelude::*; 5 | use crate::{OsError, Result}; 6 | 7 | const INPLACE: u32 = 4; 8 | const OUTOF: u32 = 8; 9 | 10 | pub fn main() -> Result<()> { 11 | // Create a file with initially 1 sector. 12 | let mut f = DISKFS.create("txt".into())?; 13 | // Tag as remove for more than once test. 14 | DISKFS.remove("txt".into())?; 15 | 16 | in_place_extend(&mut f)?; 17 | out_of_place_extend(&mut f)?; 18 | shrink(&mut f)?; 19 | 20 | kprintln!("[DISKFS.CHLEN] Done."); 21 | Ok(()) 22 | } 23 | 24 | fn shrink(f: &mut File) -> Result<()> { 25 | f.set_len(SECTOR_SIZE)?; 26 | let pos = f.seek(SeekFrom::End(0))?; 27 | // TODO: Error type. 28 | Some(pos - SECTOR_SIZE) 29 | .filter(|num| *num == 0) 30 | .ok_or(OsError::UserError)?; 31 | kprintln!("[DISKFS.CHLEN] Shrinking succeeds!"); 32 | Ok(()) 33 | } 34 | 35 | fn out_of_place_extend(f: &mut File) -> Result<()> { 36 | // Use a blocker to prevent in-place extend. 37 | let _b = DISKFS.create("blker".into())?; 38 | DISKFS.remove("blker".into())?; 39 | 40 | // Write to extend the len after in-place test. 41 | linear_write_usize(f, OUTOF)?; 42 | linear_check_usize(f, OUTOF)?; 43 | kprintln!("[DISKFS.CHLEN] Out-of-place extending succeeds!"); 44 | Ok(()) 45 | } 46 | 47 | fn in_place_extend(f: &mut File) -> Result<()> { 48 | f.set_len(SECTOR_SIZE)?; 49 | // Write to extend the initialize len. 50 | linear_write_usize(f, INPLACE)?; 51 | linear_check_usize(f, INPLACE)?; 52 | kprintln!("[DISKFS.CHLEN] In-place extending succeeds!"); 53 | Ok(()) 54 | } 55 | 56 | fn linear_check_usize(f: &mut File, sectors: u32) -> Result<()> { 57 | let cnt = s2u(sectors); 58 | f.rewind()?; 59 | for value in 0..cnt { 60 | let read: usize = f.read_into()?; 61 | if read != value { 62 | return Err(OsError::UserError); 63 | } 64 | } 65 | Ok(()) 66 | } 67 | 68 | fn linear_write_usize(f: &mut File, sectors: u32) -> Result<()> { 69 | let cnt = s2u(sectors); 70 | f.rewind()?; 71 | for value in 0..cnt { 72 | f.write_from(value)?; 73 | } 74 | Ok(()) 75 | } 76 | 77 | fn s2u(sectors: u32) -> usize { 78 | sectors as usize * SECTOR_SIZE / core::mem::size_of::() 79 | } 80 | -------------------------------------------------------------------------------- /src/mem/userbuf.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use core::arch::global_asm; 4 | 5 | use crate::error::OsError; 6 | use crate::mem::in_kernel_space; 7 | use crate::Result; 8 | 9 | /// Read a single byte from user space. 10 | /// 11 | /// ## Return 12 | /// - `Ok(byte)` 13 | /// - `Err`: A page fault happened. 14 | fn read_user_byte(user_src: *const u8) -> Result { 15 | if in_kernel_space(user_src as usize) { 16 | return Err(OsError::BadPtr); 17 | } 18 | 19 | let byte: u8 = 0; 20 | let ret_status: u8 = unsafe { __knrl_read_usr_byte(user_src, &byte as *const u8) }; 21 | 22 | if ret_status == 0 { 23 | Ok(byte) 24 | } else { 25 | Err(OsError::BadPtr) 26 | } 27 | } 28 | 29 | /// Write a single byte to user space. 30 | /// 31 | /// ## Return 32 | /// - `Ok(())` 33 | /// - `Err`: A page fault happened. 34 | fn write_user_byte(user_src: *const u8, value: u8) -> Result<()> { 35 | if in_kernel_space(user_src as usize) { 36 | return Err(OsError::BadPtr); 37 | } 38 | 39 | let ret_status: u8 = unsafe { __knrl_write_usr_byte(user_src, value) }; 40 | 41 | if ret_status == 0 { 42 | Ok(()) 43 | } else { 44 | Err(OsError::BadPtr) 45 | } 46 | } 47 | 48 | extern "C" { 49 | pub fn __knrl_read_usr_byte(user_src: *const u8, byte_ptr: *const u8) -> u8; 50 | pub fn __knrl_read_usr_byte_pc(); 51 | pub fn __knrl_read_usr_exit(); 52 | pub fn __knrl_write_usr_byte(user_src: *const u8, value: u8) -> u8; 53 | pub fn __knrl_write_usr_byte_pc(); 54 | pub fn __knrl_write_usr_exit(); 55 | } 56 | 57 | global_asm! {r#" 58 | .section .text 59 | .globl __knrl_read_usr_byte 60 | .globl __knrl_read_usr_exit 61 | .globl __knrl_read_usr_byte_pc 62 | 63 | __knrl_read_usr_byte: 64 | mv t1, a1 65 | li a1, 0 66 | __knrl_read_usr_byte_pc: 67 | lb t0, (a0) 68 | __knrl_read_usr_exit: 69 | # pagefault handler will set a1 if any error occurs 70 | sb t0, (t1) 71 | mv a0, a1 72 | ret 73 | 74 | .globl __knrl_write_usr_byte 75 | .globl __knrl_write_usr_exit 76 | .globl __knrl_write_usr_byte_pc 77 | 78 | __knrl_write_usr_byte: 79 | mv t1, a1 80 | li a1, 0 81 | __knrl_write_usr_byte_pc: 82 | sb t1, (a0) 83 | __knrl_write_usr_exit: 84 | # pagefault handler will set a1 if any error occurs 85 | mv a0, a1 86 | ret 87 | "#} 88 | -------------------------------------------------------------------------------- /src/sbi/console.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{Result, Write}; 2 | 3 | use crate::sbi::{console_putchar, interrupt}; 4 | 5 | pub struct Stdout; 6 | 7 | /// A locked standard output 8 | /// 9 | /// `StdoutLock` simply shuts down interrupt when acquired, and restores 10 | /// the previous interrupt setting when dropped. 11 | /// 12 | /// ## Examples 13 | /// ``` 14 | /// use crate::sbi::console::stdout; 15 | /// let mut output: StdoutLock = stdout().lock(); 16 | /// // The two lines below won't be interrupted by any other threads. 17 | /// writeln!(output, "hi, there"); 18 | /// writeln!(output, "hi, again"); 19 | /// ``` 20 | pub struct StdoutLock<'a> { 21 | /// A reference to the one and only standard output instance 22 | inner: &'a mut Stdout, 23 | /// interrupt status before stdout being locked 24 | intr: bool, 25 | } 26 | 27 | /// The one and only Stdout instance 28 | pub fn stdout() -> &'static mut Stdout { 29 | static mut INSTANCE: Stdout = Stdout; 30 | unsafe { &mut INSTANCE } 31 | } 32 | 33 | impl Stdout { 34 | /// Lock the Stdout to print message exclusively 35 | /// 36 | /// This is a re-entrant lock, allowing called in a nested manner. 37 | pub fn lock(&self) -> StdoutLock { 38 | StdoutLock { 39 | inner: stdout(), 40 | intr: interrupt::set(false), 41 | } 42 | } 43 | } 44 | 45 | impl Write for Stdout { 46 | fn write_str(&mut self, s: &str) -> Result { 47 | for ch in s.chars() { 48 | console_putchar(ch as usize); 49 | } 50 | Ok(()) 51 | } 52 | } 53 | 54 | impl Write for StdoutLock<'_> { 55 | fn write_str(&mut self, s: &str) -> Result { 56 | self.inner.write_str(s) 57 | } 58 | } 59 | 60 | impl Drop for StdoutLock<'_> { 61 | fn drop(&mut self) { 62 | interrupt::set(self.intr); 63 | } 64 | } 65 | 66 | #[macro_export] 67 | macro_rules! kprint { 68 | ($($arg:tt)*) => {{ 69 | use core::fmt::Write; 70 | let _ = write!(crate::sbi::console::stdout().lock(), $($arg)*); 71 | }} 72 | } 73 | 74 | #[macro_export] 75 | macro_rules! kprintln { 76 | () => { 77 | kprint!("\n"); 78 | }; 79 | ($($arg:tt)*) => {{ 80 | let _x = crate::sbi::console::stdout().lock(); 81 | kprint!( 82 | "[\x1B[38;2;129;165;113;1m{} ms\x1B[0m] ", 83 | crate::sbi::timer::time_ms() 84 | ); 85 | kprint!($($arg)*); 86 | kprint!("\n"); 87 | }}; 88 | } 89 | -------------------------------------------------------------------------------- /test/schedule/priority/condvar.rs: -------------------------------------------------------------------------------- 1 | use crate::sync::{Condvar, Mutex}; 2 | 3 | use super::*; 4 | 5 | const THREAD_CNT: usize = 10; 6 | const WAKE_TIME: i64 = 5 * TICKS_PER_SEC as i64; 7 | static mut EXIT_STATUS: [i8; THREAD_CNT + 1] = [0; THREAD_CNT + 1]; 8 | 9 | const EXPECTED_STATUS: [[i8; THREAD_CNT + 1]; THREAD_CNT + 1] = [ 10 | // Similar with ./alarm.rs 11 | [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 12 | [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], 13 | [0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1], 14 | [0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], 15 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 16 | [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], 17 | [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0], 18 | [0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0], 19 | [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0], 20 | [0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0], 21 | [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0], 22 | ]; 23 | 24 | fn cvar_priority_thread(tid: usize, pair: Arc<(Mutex, Condvar)>) { 25 | // Wait for signal. 26 | let (lock, cvar) = &*pair; 27 | let mut guard = lock.lock(); 28 | cvar.wait(&mut guard); 29 | 30 | // We're notified by the Main thread and are going to exit. 31 | unsafe { 32 | // Check other thread's status before exit. 33 | assert_eq!( 34 | EXIT_STATUS, EXPECTED_STATUS[tid], 35 | "When thread {} exit, expected status is {:?}, but real status is {:?}.", 36 | tid, EXPECTED_STATUS[tid], EXIT_STATUS 37 | ); 38 | 39 | // Mark self as exited. 40 | EXIT_STATUS[tid] = 1; 41 | } 42 | } 43 | 44 | pub fn main() { 45 | // Main thread has tid 0. 46 | let pair = Arc::new((Mutex::new(false), Condvar::new())); 47 | 48 | set_priority(PRI_MIN); 49 | 50 | for tid in 1..=THREAD_CNT { 51 | let priority = PRI_DEFAULT - ((tid as u32 + 6) % 10) - 1; 52 | let p = Arc::clone(&pair); 53 | Builder::new(move || cvar_priority_thread(tid, p)) 54 | .name("child") 55 | .priority(priority) 56 | .spawn(); 57 | } 58 | 59 | let (lock, cvar) = &*pair; 60 | 61 | for _ in 1..=THREAD_CNT { 62 | let _g = lock.lock(); 63 | cvar.notify_one(); 64 | } 65 | 66 | unsafe { 67 | assert_eq!( 68 | EXIT_STATUS, EXPECTED_STATUS[0], 69 | "When main thread {} exit, expected status is {:?}, but real status is {:?}.", 70 | 0, EXPECTED_STATUS[0], EXIT_STATUS 71 | ); 72 | } 73 | 74 | pass(); 75 | } 76 | -------------------------------------------------------------------------------- /src/thread.rs: -------------------------------------------------------------------------------- 1 | //! Kernel Threads 2 | 3 | mod imp; 4 | pub mod manager; 5 | pub mod scheduler; 6 | pub mod switch; 7 | 8 | pub use self::imp::*; 9 | pub use self::manager::Manager; 10 | pub(self) use self::scheduler::{Schedule, Scheduler}; 11 | 12 | use alloc::sync::Arc; 13 | 14 | /// Create a new thread 15 | pub fn spawn(name: &'static str, f: F) -> Arc 16 | where 17 | F: FnOnce() + Send + 'static, 18 | { 19 | Builder::new(f).name(name).spawn() 20 | } 21 | 22 | /// Get the current running thread 23 | pub fn current() -> Arc { 24 | Manager::get().current.lock().clone() 25 | } 26 | 27 | /// Yield the control to another thread (if there's another one ready to run). 28 | pub fn schedule() { 29 | Manager::get().schedule() 30 | } 31 | 32 | /// Gracefully shut down the current thread, and schedule another one. 33 | pub fn exit() -> ! { 34 | { 35 | let current = Manager::get().current.lock(); 36 | 37 | #[cfg(feature = "debug")] 38 | kprintln!("Exit: {:?}", *current); 39 | 40 | current.set_status(Status::Dying); 41 | } 42 | 43 | schedule(); 44 | 45 | unreachable!("An exited thread shouldn't be scheduled again"); 46 | } 47 | 48 | /// Mark the current thread as [`Blocked`](Status::Blocked) and 49 | /// yield the control to another thread 50 | pub fn block() { 51 | let current = current(); 52 | current.set_status(Status::Blocked); 53 | 54 | #[cfg(feature = "debug")] 55 | kprintln!("[THREAD] Block {:?}", current); 56 | 57 | schedule(); 58 | } 59 | 60 | /// Wake up a previously blocked thread, mark it as [`Ready`](Status::Ready), 61 | /// and register it into the scheduler. 62 | pub fn wake_up(thread: Arc) { 63 | assert_eq!(thread.status(), Status::Blocked); 64 | thread.set_status(Status::Ready); 65 | 66 | #[cfg(feature = "debug")] 67 | kprintln!("[THREAD] Wake up {:?}", thread); 68 | 69 | Manager::get().scheduler.lock().register(thread); 70 | } 71 | 72 | /// (Lab1) Sets the current thread's priority to a given value 73 | pub fn set_priority(_priority: u32) {} 74 | 75 | /// (Lab1) Returns the current thread's effective priority. 76 | pub fn get_priority() -> u32 { 77 | 0 78 | } 79 | 80 | /// (Lab1) Make the current thread sleep for the given ticks. 81 | pub fn sleep(ticks: i64) { 82 | use crate::sbi::timer::{timer_elapsed, timer_ticks}; 83 | 84 | let start = timer_ticks(); 85 | 86 | while timer_elapsed(start) < ticks { 87 | schedule(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/sync/mutex.rs: -------------------------------------------------------------------------------- 1 | use core::cell::UnsafeCell; 2 | use core::ops::{Deref, DerefMut}; 3 | 4 | use crate::sync::{self, Lock}; 5 | 6 | /// A mutual exclusion primitive useful for protecting shared data 7 | /// 8 | /// # Examples 9 | /// ``` 10 | /// let foo = Mutex::new(7); 11 | /// { 12 | /// let mut foo = foo.lock(); 13 | /// foo += 3; 14 | /// } 15 | /// assert_eq!(foo.lock(), 10); 16 | /// ``` 17 | #[derive(Debug, Default)] 18 | pub struct Mutex { 19 | value: UnsafeCell, 20 | lock: L, 21 | } 22 | 23 | // The only access to a Mutex's value is MutexGuard, so safety is guaranteed here. 24 | // Requiring T to be Send-able prohibits implementing Sync for types like Mutex<*mut T>. 25 | unsafe impl Sync for Mutex {} 26 | unsafe impl Send for Mutex {} 27 | 28 | impl Mutex { 29 | /// Creates a mutex in an unlocked state ready for use. 30 | pub fn new(value: T) -> Self { 31 | Self { 32 | value: UnsafeCell::new(value), 33 | lock: L::default(), 34 | } 35 | } 36 | 37 | /// Acquires a mutex, blocking the current thread until it is able to do so. 38 | pub fn lock(&self) -> MutexGuard<'_, T, L> { 39 | self.lock.acquire(); 40 | MutexGuard(self) 41 | } 42 | } 43 | 44 | /// An RAII implementation of a “scoped lock” of a mutex. 45 | /// When this structure is dropped (falls out of scope), the lock will be unlocked. 46 | /// 47 | /// The data protected by the mutex can be accessed through 48 | /// this guard via its Deref and DerefMut implementations. 49 | pub struct MutexGuard<'a, T, L: Lock>(&'a Mutex); 50 | 51 | unsafe impl Sync for MutexGuard<'_, T, L> {} 52 | 53 | impl Deref for MutexGuard<'_, T, L> { 54 | type Target = T; 55 | 56 | fn deref(&self) -> &T { 57 | unsafe { &*self.0.value.get() } 58 | } 59 | } 60 | 61 | impl DerefMut for MutexGuard<'_, T, L> { 62 | fn deref_mut(&mut self) -> &mut T { 63 | unsafe { &mut *self.0.value.get() } 64 | } 65 | } 66 | 67 | impl Drop for MutexGuard<'_, T, L> { 68 | fn drop(&mut self) { 69 | self.0.lock.release(); 70 | } 71 | } 72 | 73 | // Useful in Condvar 74 | impl MutexGuard<'_, T, L> { 75 | pub(super) fn release(&self) { 76 | self.0.lock.release(); 77 | } 78 | 79 | pub(super) fn acquire(&self) { 80 | self.0.lock.acquire(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/sync/once.rs: -------------------------------------------------------------------------------- 1 | use core::cell::Cell; 2 | 3 | use super::{Intr, Lock}; 4 | 5 | #[derive(Clone, Copy)] 6 | pub enum OnceState { 7 | InComplete, 8 | Complete, 9 | } 10 | 11 | /// A synchronization primitive which can be 12 | /// used to run a one-time global initialization. 13 | pub struct Once { 14 | inner: Cell, 15 | lock: Intr, 16 | } 17 | 18 | unsafe impl Sync for Once {} 19 | 20 | impl Once { 21 | pub const fn new() -> Self { 22 | Self { 23 | inner: Cell::new(OnceState::InComplete), 24 | lock: Intr::new(), 25 | } 26 | } 27 | 28 | pub fn call_once(&self, f: F) 29 | where 30 | F: FnOnce(), 31 | { 32 | if self.is_completed() { 33 | return; 34 | } 35 | 36 | self.lock.acquire(); 37 | if matches!(self.inner.get(), OnceState::InComplete) { 38 | f(); 39 | self.inner.set(OnceState::Complete); 40 | } 41 | self.lock.release(); 42 | } 43 | 44 | pub fn is_completed(&self) -> bool { 45 | matches!(self.inner.get(), OnceState::Complete) 46 | } 47 | } 48 | 49 | /// A cell which can only be initialized once 50 | pub struct OnceCell { 51 | inner: Cell>, 52 | once: Once, 53 | } 54 | 55 | unsafe impl Sync for OnceCell {} 56 | 57 | impl OnceCell { 58 | /// Creates a lazy-initialized cell. 59 | pub const fn new() -> Self { 60 | Self { 61 | once: Once::new(), 62 | inner: Cell::new(None), 63 | } 64 | } 65 | 66 | pub fn init(&self, f: F) 67 | where 68 | F: FnOnce() -> T, 69 | { 70 | self.once.call_once(|| self.inner.set(Some(f()))); 71 | } 72 | 73 | /// Initialize or get the value from a cell. A cell will only 74 | /// be initialized **once**. 75 | pub fn get_or_init(&self, f: F) -> &T 76 | where 77 | F: FnOnce() -> T, 78 | { 79 | if self.once.is_completed() { 80 | return self.get(); 81 | } 82 | 83 | self.init(f); 84 | self.get() 85 | } 86 | 87 | /// Gets the reference to the underlying value. 88 | /// Returns None if the cell is empty, or being initialized. 89 | pub fn get(&self) -> &T { 90 | unsafe { 91 | match *self.inner.as_ptr() { 92 | Some(ref x) => x, 93 | None => unreachable!("attempted to derefence an uninitialized once_cell"), 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /test/schedule/priority/alarm.rs: -------------------------------------------------------------------------------- 1 | use crate::sbi::timer::{timer_elapsed, timer_ticks}; 2 | use crate::sync::Semaphore; 3 | 4 | use super::*; 5 | 6 | const THREAD_CNT: usize = 10; 7 | const WAKE_TIME: i64 = 5 * TICKS_PER_SEC as i64; 8 | static mut EXIT_STATUS: [i8; THREAD_CNT + 1] = [0; THREAD_CNT + 1]; 9 | 10 | const EXPECTED_STATUS: [[i8; THREAD_CNT + 1]; THREAD_CNT + 1] = [ 11 | // Thread 0 is the main thread. Every other thread should have exited. 12 | [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 13 | // When thread 1 exit, thread 0 is running. 14 | [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1], 15 | // When thread 2 exit, thread 1 should have exited. 16 | [0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1], 17 | [0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1], 18 | [0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1], 19 | [0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], 20 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 21 | [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], 22 | [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0], 23 | [0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0], 24 | [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0], 25 | ]; 26 | 27 | fn alarm_priority_thread(tid: usize, sema: Arc) { 28 | // Busy-wait until the current time changes. 29 | let start = timer_ticks(); 30 | while timer_elapsed(start) == 0 {} 31 | 32 | sleep(WAKE_TIME - start); 33 | 34 | unsafe { 35 | // Check other thread's status before exit. 36 | assert_eq!( 37 | EXIT_STATUS, EXPECTED_STATUS[tid], 38 | "When thread {} exit, expected status is {:?}, but real status is {:?}.", 39 | tid, EXPECTED_STATUS[tid], EXIT_STATUS 40 | ); 41 | 42 | // Mark self as exited. 43 | EXIT_STATUS[tid] = 1; 44 | } 45 | 46 | sema.up(); 47 | } 48 | 49 | pub fn main() { 50 | let s = Arc::new(Semaphore::new(0)); 51 | 52 | // Main thread has tid 0. 53 | for tid in 1..=THREAD_CNT { 54 | let priority = PRI_DEFAULT - ((tid as u32 + 4) % 10) - 1; 55 | let s = s.clone(); 56 | Builder::new(move || alarm_priority_thread(tid, s)) 57 | .name("child") 58 | .priority(priority) 59 | .spawn(); 60 | } 61 | 62 | set_priority(PRI_MIN); 63 | 64 | for _ in 1..=THREAD_CNT { 65 | s.down(); 66 | } 67 | 68 | unsafe { 69 | assert_eq!( 70 | EXIT_STATUS, EXPECTED_STATUS[0], 71 | "When main thread {} exit, expected status is {:?}, but real status is {:?}.", 72 | 0, EXPECTED_STATUS[0], EXIT_STATUS 73 | ); 74 | } 75 | 76 | pass(); 77 | } 78 | -------------------------------------------------------------------------------- /test/schedule/donation/three.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | static mut EXIT_STATUS: [i8; 4] = [0; 4]; 4 | 5 | fn low() { 6 | unsafe { 7 | assert_eq!( 8 | EXIT_STATUS, 9 | [0, 0, 1, 1], 10 | "Low should exit after high and medium." 11 | ); 12 | EXIT_STATUS[1] = 1; 13 | } 14 | } 15 | 16 | fn medium(m_lock: Arc) { 17 | m_lock.acquire(); 18 | 19 | unsafe { 20 | assert_eq!(EXIT_STATUS, [0, 0, 0, 1], "Medium should exit after high."); 21 | EXIT_STATUS[2] = 1; 22 | } 23 | 24 | m_lock.release(); 25 | } 26 | 27 | fn high(h_lock: Arc) { 28 | h_lock.acquire(); 29 | 30 | unsafe { 31 | assert_eq!(EXIT_STATUS, [0, 0, 0, 0], "Hish should exit first."); 32 | EXIT_STATUS[3] = 1; 33 | } 34 | 35 | h_lock.release(); 36 | } 37 | 38 | pub fn main() { 39 | let m_lock = Arc::new(Sleep::default()); 40 | let h_lock = Arc::new(Sleep::default()); 41 | m_lock.acquire(); 42 | h_lock.acquire(); 43 | 44 | let create_and_check = |f: fn(Arc), expected, lock| { 45 | let l = Arc::clone(lock); 46 | Builder::new(move || f(l)) 47 | .name("child") 48 | .priority(expected) 49 | .spawn(); 50 | let priority = get_priority(); 51 | assert_eq!( 52 | expected, priority, 53 | "This thread should have priority {}. Actual priority {}.", 54 | expected, priority 55 | ); 56 | }; 57 | 58 | // Create three child thread with higher priority. They are donating priority to main. 59 | let l_priority = PRI_DEFAULT + 1; 60 | let m_priority = PRI_DEFAULT + 3; 61 | let h_priority = PRI_DEFAULT + 5; 62 | create_and_check(medium, m_priority, &m_lock); 63 | Builder::new(low).name("child").priority(l_priority).spawn(); 64 | create_and_check(high, h_priority, &h_lock); 65 | 66 | let release_and_check = |expected, lock: &Arc| { 67 | lock.release(); 68 | 69 | let priority = get_priority(); 70 | assert_eq!( 71 | expected, priority, 72 | "This thread should have priority {}. Actual priority {}.", 73 | expected, priority 74 | ); 75 | }; 76 | 77 | release_and_check(h_priority, &m_lock); 78 | release_and_check(PRI_DEFAULT, &h_lock); 79 | 80 | unsafe { 81 | assert_eq!( 82 | EXIT_STATUS, 83 | [0, 1, 1, 1], 84 | "Low, medium & high should have exited." 85 | ); 86 | }; 87 | 88 | pass(); 89 | } 90 | -------------------------------------------------------------------------------- /src/mem/utils/list.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | /// A single linked list designed for memory allocators 4 | #[derive(Debug, Clone, Copy)] 5 | pub struct InMemList { 6 | head: *mut usize, 7 | } 8 | 9 | impl InMemList { 10 | /// Creates a new linked list 11 | pub const fn new() -> Self { 12 | Self { 13 | head: core::ptr::null_mut(), 14 | } 15 | } 16 | 17 | /// Is this list empty or not 18 | pub fn is_empty(&self) -> bool { 19 | self.head.is_null() 20 | } 21 | 22 | /// Pushes an item to the front of a list 23 | pub unsafe fn push(&mut self, item: *mut usize) { 24 | *item = self.head as usize; 25 | self.head = item; 26 | } 27 | 28 | /// Removes an item from the front of a list 29 | pub fn pop(&mut self) -> Option<*mut usize> { 30 | match self.is_empty() { 31 | true => None, 32 | false => { 33 | // Advance head pointer 34 | let popped_item = self.head; 35 | self.head = unsafe { *popped_item as *mut usize }; 36 | Some(popped_item) 37 | } 38 | } 39 | } 40 | 41 | /// Return an mutable iterator over the items in the list 42 | pub fn iter_mut(&mut self) -> IterMut { 43 | IterMut { 44 | prev: &mut self.head as *mut *mut usize as *mut usize, 45 | curr: self.head, 46 | phantom: Default::default(), 47 | } 48 | } 49 | } 50 | 51 | pub struct IterMut<'a> { 52 | prev: *mut usize, 53 | curr: *mut usize, 54 | phantom: PhantomData<&'a mut InMemList>, 55 | } 56 | 57 | /// Represent a mutable node in `LinkedList` 58 | pub struct ListNode { 59 | prev: *mut usize, 60 | curr: *mut usize, 61 | } 62 | 63 | impl ListNode { 64 | /// Remove the node from the list 65 | pub fn pop(self) -> *mut usize { 66 | // Skip the current one 67 | unsafe { 68 | *(self.prev) = *(self.curr); 69 | } 70 | self.curr 71 | } 72 | 73 | /// Returns the pointed address 74 | pub fn value(&self) -> *mut usize { 75 | self.curr 76 | } 77 | } 78 | 79 | impl<'a> Iterator for IterMut<'a> { 80 | type Item = ListNode; 81 | 82 | fn next(&mut self) -> Option { 83 | if self.curr.is_null() { 84 | None 85 | } else { 86 | let res = ListNode { 87 | prev: self.prev, 88 | curr: self.curr, 89 | }; 90 | self.prev = self.curr; 91 | self.curr = unsafe { *self.curr as *mut usize }; 92 | Some(res) 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /user/lib/printf.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "user.h" 4 | 5 | static char digits[] = "0123456789ABCDEF"; 6 | 7 | static void putc(int fd, char c) { write(fd, &c, 1); } 8 | 9 | static void printint(int fd, int xx, int base, int sgn) { 10 | char buf[16]; 11 | int i, neg; 12 | uint x; 13 | 14 | neg = 0; 15 | if (sgn && xx < 0) { 16 | neg = 1; 17 | x = -xx; 18 | } else { 19 | x = xx; 20 | } 21 | 22 | i = 0; 23 | do { 24 | buf[i++] = digits[x % base]; 25 | } while ((x /= base) != 0); 26 | if (neg) buf[i++] = '-'; 27 | 28 | while (--i >= 0) putc(fd, buf[i]); 29 | } 30 | 31 | static void printptr(int fd, uint64 x) { 32 | int i; 33 | putc(fd, '0'); 34 | putc(fd, 'x'); 35 | for (i = 0; i < (sizeof(uint64) * 2); i++, x <<= 4) 36 | putc(fd, digits[x >> (sizeof(uint64) * 8 - 4)]); 37 | } 38 | 39 | // Print to the given fd. Only understands %d, %x, %p, %s. 40 | void vprintf(int fd, const char* fmt, va_list ap) { 41 | char* s; 42 | int c, i, state; 43 | 44 | state = 0; 45 | for (i = 0; fmt[i]; i++) { 46 | c = fmt[i] & 0xff; 47 | if (state == 0) { 48 | if (c == '%') { 49 | state = '%'; 50 | } else { 51 | putc(fd, c); 52 | } 53 | } else if (state == '%') { 54 | if (c == 'd') { 55 | printint(fd, va_arg(ap, int), 10, 1); 56 | } else if (c == 'l') { 57 | printint(fd, va_arg(ap, uint64), 10, 0); 58 | } else if (c == 'x') { 59 | printint(fd, va_arg(ap, int), 16, 0); 60 | } else if (c == 'p') { 61 | printptr(fd, va_arg(ap, uint64)); 62 | } else if (c == 's') { 63 | s = va_arg(ap, char*); 64 | if (s == 0) s = "(null)"; 65 | while (*s != 0) { 66 | putc(fd, *s); 67 | s++; 68 | } 69 | } else if (c == 'c') { 70 | putc(fd, va_arg(ap, uint)); 71 | } else if (c == '%') { 72 | putc(fd, c); 73 | } else { 74 | // Unknown % sequence. Print it to draw attention. 75 | putc(fd, '%'); 76 | putc(fd, c); 77 | } 78 | state = 0; 79 | } 80 | } 81 | } 82 | 83 | void fprintf(int fd, const char* fmt, ...) { 84 | va_list ap; 85 | 86 | va_start(ap, fmt); 87 | vprintf(fd, fmt, ap); 88 | } 89 | 90 | void printf(const char* fmt, ...) { 91 | va_list ap; 92 | 93 | va_start(ap, fmt); 94 | vprintf(1, fmt, ap); 95 | } 96 | -------------------------------------------------------------------------------- /src/sbi.rs: -------------------------------------------------------------------------------- 1 | //! Machine Mode Interface 2 | 3 | #[macro_use] 4 | pub mod console; 5 | pub mod interrupt; 6 | pub mod timer; 7 | 8 | pub use self::legacy::*; 9 | pub use self::system_reset::*; 10 | 11 | /* -------------------------------------------------------------------------- */ 12 | /* SBI */ 13 | /* -------------------------------------------------------------------------- */ 14 | 15 | macro_rules! call { 16 | // v0.1 17 | ( $eid: expr; $($args: expr),* ) => { call!($eid, 0; $($args),*).0 }; 18 | 19 | // v0.2 20 | ( $eid: expr, $fid: expr; $($arg0: expr $(, $arg1: expr $(, $arg2: expr $(, $arg3: expr $(, $arg4: expr $(, $arg5: expr)?)?)?)?)?)? ) => { 21 | { 22 | let (err, ret): (usize, usize); 23 | unsafe { 24 | core::arch::asm!("ecall", 25 | in("a7") $eid, lateout("a0") err, 26 | in("a6") $fid, lateout("a1") ret, 27 | 28 | $(in("a0") $arg0, $(in("a1") $arg1, $(in("a2") $arg2, $(in("a3") $arg3, $(in("a4") $arg4, $(in("a5") $arg5)?)?)?)?)?)? 29 | ); 30 | } 31 | 32 | (err, ret) 33 | } 34 | }; 35 | } 36 | 37 | pub mod legacy { 38 | #![allow(dead_code)] 39 | 40 | const SET_TIMER: usize = 0x00; 41 | const CONSOLE_PUTCHAR: usize = 0x01; 42 | const CONSOLE_GETCHAR: usize = 0x02; 43 | const CLEAR_IPI: usize = 0x03; 44 | const SEND_IPI: usize = 0x04; 45 | const REMOTE_FENCE_I: usize = 0x05; 46 | const REMOTE_SFENCE_VMA: usize = 0x06; 47 | const REMOTE_SFENCE_VMA_ASID: usize = 0x07; 48 | const SHUTDOWN: usize = 0x08; 49 | 50 | pub fn set_timer(timer: usize) { 51 | call!(SET_TIMER; timer); 52 | } 53 | 54 | pub fn console_putchar(char: usize) { 55 | call!(CONSOLE_PUTCHAR; char); 56 | } 57 | 58 | pub fn console_getchar() -> usize { 59 | call!(CONSOLE_GETCHAR;) 60 | } 61 | 62 | pub fn shutdown() -> ! { 63 | call!(SHUTDOWN;); 64 | 65 | unreachable!("should have been shutdown") 66 | } 67 | } 68 | 69 | pub mod system_reset { 70 | const SYSTEM_RESET: usize = 0; 71 | 72 | pub enum Type { 73 | Shutdown = 0x00000000, 74 | ColdReboot = 0x00000001, 75 | WarmReboot = 0x00000002, 76 | } 77 | 78 | pub enum Reason { 79 | NoReason = 0x00000000, 80 | SystemFailure = 0x00000001, 81 | } 82 | 83 | pub fn reset(r#type: Type, reason: Reason) -> ! { 84 | call!(0x53525354, SYSTEM_RESET; r#type as usize, reason as usize); 85 | 86 | unreachable!("system reset failed") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/sync/condvar.rs: -------------------------------------------------------------------------------- 1 | //! # Condition Variable 2 | //! 3 | //! [`Condvar`] are able to block a thread so that it consumes no CPU time 4 | //! while waiting for an event to occur. It is typically associated with a 5 | //! boolean predicate (a condition) and a mutex. The predicate is always verified 6 | //! inside of the mutex before determining that a thread must block. 7 | //! 8 | //! ## Usage 9 | //! 10 | //! Suppose there are two threads A and B, and thread A is waiting for some events 11 | //! in thread B to happen. 12 | //! 13 | //! Here is the common practice of thread A: 14 | //! ```rust 15 | //! let pair = Arc::new(Mutex::new(false), Condvar::new()); 16 | //! 17 | //! let (lock, cvar) = &*pair; 18 | //! let condition = lock.lock(); 19 | //! while !condition { 20 | //! cvar.wait(&condition); 21 | //! } 22 | //! ``` 23 | //! 24 | //! Here is a good practice of thread B: 25 | //! ```rust 26 | //! let (lock, cvar) = &*pair; 27 | //! 28 | //! // Lock must be held during a call to `Condvar.notify_one()`. Therefore, `guard` has to bind 29 | //! // to a local variable so that it won't be dropped too soon. 30 | //! 31 | //! let guard = lock.lock(); // Bind `guard` to a local variable 32 | //! *guard = true; // Condition change 33 | //! cvar.notify_one(); // Notify (`guard` will overlive this line) 34 | //! ``` 35 | //! 36 | //! Here is a bad practice of thread B: 37 | //! ```rust 38 | //! let (lock, cvar) = &*pair; 39 | //! 40 | //! *lock.lock() = true; // Lock won't be held after this line. 41 | //! cvar.notify_one(); // Buggy: notify another thread without holding the Lock 42 | //! ``` 43 | //! 44 | 45 | use alloc::collections::VecDeque; 46 | use alloc::sync::Arc; 47 | use core::cell::RefCell; 48 | 49 | use crate::sync::{Lock, MutexGuard, Semaphore}; 50 | 51 | pub struct Condvar(RefCell>>); 52 | 53 | unsafe impl Sync for Condvar {} 54 | unsafe impl Send for Condvar {} 55 | 56 | impl Condvar { 57 | pub fn new() -> Self { 58 | Condvar(Default::default()) 59 | } 60 | 61 | pub fn wait(&self, guard: &mut MutexGuard<'_, T, L>) { 62 | let sema = Arc::new(Semaphore::new(0)); 63 | self.0.borrow_mut().push_front(sema.clone()); 64 | 65 | guard.release(); 66 | sema.down(); 67 | guard.acquire(); 68 | } 69 | 70 | /// Wake up one thread from the waiting list 71 | pub fn notify_one(&self) { 72 | if let Some(sema) = self.0.borrow_mut().pop_back() { 73 | sema.up(); 74 | } 75 | } 76 | 77 | /// Wake up all waiting threads 78 | pub fn notify_all(&self) { 79 | self.0.borrow().iter().for_each(|s| s.up()); 80 | self.0.borrow_mut().clear(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test/unit/fs/inmem.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | 3 | use crate::fs::File; 4 | use crate::fs::{inmem::MemFs, FileSys}; 5 | use crate::io::prelude::*; 6 | use crate::thread; 7 | use crate::Result; 8 | 9 | pub fn main() { 10 | let fs = &MemFs::mount(()).unwrap(); 11 | base::test(fs); 12 | // TODO: actually should use wait(). 13 | sync::test(fs); 14 | } 15 | 16 | mod sync { 17 | use super::*; 18 | pub(super) fn test(fs: &MemFs) { 19 | const NUM: usize = 10; 20 | let sync = [0u8; NUM * core::mem::size_of::()]; 21 | let f = fs.open(Box::from(sync)).unwrap(); 22 | let fw = f.clone(); 23 | 24 | thread::spawn("writer", || writer(fw, NUM)); 25 | thread::schedule(); // firstly write sth. 26 | thread::spawn("reader", || reader(f, NUM)); 27 | for _ in 0..30 { 28 | thread::schedule(); 29 | } 30 | } 31 | 32 | fn writer(file: File, num: usize) { 33 | let ret = usize_writer(file, num); 34 | kprintln!("[Writer] {:?}", ret); 35 | assert_eq!(Ok(()), ret); 36 | } 37 | 38 | fn usize_writer(mut file: File, num: usize) -> Result<()> { 39 | file.seek(SeekFrom::Start(0))?; 40 | for i in 0..num { 41 | file.write_from(i)?; 42 | kprintln!("[Writer] {:>2} ->", i); 43 | thread::schedule(); 44 | } 45 | Ok(()) 46 | } 47 | 48 | fn reader(file: File, num: usize) { 49 | let ret = usize_reader(file, num); 50 | kprintln!("[Reader] {:?}", ret); 51 | assert_eq!(Ok(()), ret); 52 | } 53 | 54 | fn usize_reader(mut file: File, num: usize) -> Result<()> { 55 | file.seek(SeekFrom::Start(0))?; 56 | for i in 0..num { 57 | let v: usize = file.read_into()?; 58 | kprintln!("[Reader] -> {:<2}", v); 59 | assert_eq!(i, v); 60 | thread::schedule(); 61 | } 62 | Ok(()) 63 | } 64 | } 65 | 66 | mod base { 67 | use super::*; 68 | pub(super) fn test(fs: &MemFs) { 69 | let raw: [u8; 8] = [0x1a, 0x2b, 0x3c, 0x4d, 0x5e, 0x6f, 0x70, 0x89]; 70 | 71 | let mut f = fs.open(Box::from(raw)).unwrap(); 72 | 73 | let a: usize = f.read_into().expect("fail to call read_into()"); 74 | assert_eq!(a, 0x_89_70_6f_5e_4d_3c_2b_1a_usize); 75 | 76 | assert_eq!(f.stream_position(), Ok(8)); 77 | assert_eq!(f.seek(SeekFrom::Current(1)), Ok(9)); 78 | assert_eq!(f.seek(SeekFrom::End(-10)), Ok(0)); 79 | assert_eq!(f.seek(SeekFrom::Start(4)), Ok(4)); 80 | 81 | f.write_from(0x_12_34_u16) 82 | .expect("fail to call write_from()"); 83 | f.seek(SeekFrom::Start(0)).unwrap(); 84 | let a: usize = f.read_into().unwrap(); 85 | assert_eq!(a, 0x_89_70_12_34_4d_3c_2b_1a_usize); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /test/schedule/donation/chain.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | const DEPTH: usize = 8; 4 | static mut EXIT_STATUS: [i8; 2 * DEPTH + 1] = [0; 2 * DEPTH + 1]; 5 | 6 | fn interloop(tid: usize) { 7 | unsafe { 8 | assert_eq!( 9 | EXIT_STATUS[tid + 1], 10 | 1, 11 | "Thread {} should exit later than thread {}", 12 | tid, 13 | tid + 1 14 | ); 15 | EXIT_STATUS[tid] = 1; 16 | } 17 | } 18 | 19 | fn donor(tid: usize, first: Arc, second: Arc) { 20 | if tid < 2 * (DEPTH - 1) { 21 | first.acquire(); 22 | } 23 | 24 | second.acquire(); 25 | second.release(); 26 | 27 | let expected = ((DEPTH - 1) * 2) as u32; 28 | let priority = get_priority(); 29 | assert_eq!( 30 | expected, priority, 31 | "Thread {} should have priority {}. Actual priority {}.", 32 | tid, expected, priority 33 | ); 34 | 35 | if tid < 2 * (DEPTH - 1) { 36 | first.release(); 37 | 38 | unsafe { 39 | assert_eq!( 40 | EXIT_STATUS[tid + 1], 41 | 1, 42 | "Thread {} should exit later than thread {}", 43 | tid, 44 | tid + 1 45 | ); 46 | } 47 | } 48 | 49 | unsafe { 50 | EXIT_STATUS[tid] = 1; 51 | } 52 | 53 | let expected = tid as u32; 54 | let priority = get_priority(); 55 | assert_eq!( 56 | expected, priority, 57 | "Thread {} should finish with priority {}. Actual priority {}.", 58 | tid, expected, priority 59 | ); 60 | } 61 | 62 | pub fn main() { 63 | let locks: [_; DEPTH] = core::array::from_fn(|_| Arc::new(Sleep::default())); 64 | 65 | set_priority(PRI_MIN); 66 | 67 | locks[0].acquire(); 68 | 69 | for p in 1..DEPTH { 70 | let expected = 2 * p as u32; 71 | let first = Arc::clone(&locks[p]); 72 | let second = Arc::clone(&locks[p - 1]); 73 | 74 | Builder::new(move || donor(expected as usize, first, second)) 75 | .name("Donor") 76 | .priority(expected) 77 | .spawn(); 78 | 79 | let priority = get_priority(); 80 | assert_eq!( 81 | expected, priority, 82 | "Main should have priority {}. Actual priority {}.", 83 | expected, priority 84 | ); 85 | 86 | Builder::new(move || interloop(expected as usize - 1)) 87 | .name("interloop") 88 | .priority(expected - 1) 89 | .spawn(); 90 | } 91 | 92 | locks[0].release(); 93 | 94 | let expected = PRI_MIN; 95 | let priority = get_priority(); 96 | assert_eq!( 97 | expected, priority, 98 | "Main thread should finish with priority {}. Actual priority {}.", 99 | expected, priority 100 | ); 101 | 102 | pass(); 103 | } 104 | -------------------------------------------------------------------------------- /src/mem/pagetable/entry.rs: -------------------------------------------------------------------------------- 1 | //! Page Table Entry 2 | 3 | use crate::mem::pagetable::PPN_MASK; 4 | use crate::mem::utils::{PhysAddr, PG_SHIFT}; 5 | 6 | /// The format of Sv39 page table entry: 7 | /// | 63-54 | 53-28 | 27-19 | 18-10 | 9-8 |7|6|5|4|3|2|1|0| 8 | /// | Unused | PPN[2] | PPN[1] | PPN[0] | RSW |D|A|G|U|X|W|R|V| 9 | #[repr(transparent)] 10 | #[derive(Clone, Copy, Debug)] 11 | pub struct Entry(usize); 12 | 13 | bitflags::bitflags! { 14 | pub struct PTEFlags: usize { 15 | /// Valid 16 | const V = 0b0000_0001; 17 | /// Readable 18 | const R = 0b0000_0010; 19 | /// Writable 20 | const W = 0b0000_0100; 21 | /// Executable 22 | const X = 0b0000_1000; 23 | /// It this page accessible to user mode? 24 | const U = 0b0001_0000; 25 | /// Global mappings (are those that exist in all address spaces) 26 | const G = 0b0010_0000; 27 | /// Accessed 28 | const A = 0b0100_0000; 29 | /// Dirty 30 | const D = 0b1000_0000; 31 | } 32 | } 33 | 34 | impl Entry { 35 | const FLAG_SHIFT: usize = 10; 36 | 37 | pub fn new(pa: PhysAddr, flags: PTEFlags) -> Entry { 38 | Entry((((pa.value() >> PG_SHIFT) & PPN_MASK) << Self::FLAG_SHIFT) | flags.bits()) 39 | } 40 | 41 | fn flag(&self) -> PTEFlags { 42 | PTEFlags::from_bits_truncate(self.0) 43 | } 44 | 45 | fn ppn(&self) -> usize { 46 | self.0 >> Self::FLAG_SHIFT & PPN_MASK 47 | } 48 | 49 | /// Physical address where the entry maps to 50 | pub fn pa(&self) -> PhysAddr { 51 | PhysAddr::from_pa(self.ppn() << PG_SHIFT) 52 | } 53 | 54 | pub fn is_valid(&self) -> bool { 55 | self.flag().contains(PTEFlags::V) 56 | } 57 | 58 | pub fn is_global(&self) -> bool { 59 | self.flag().contains(PTEFlags::G) 60 | } 61 | 62 | pub fn is_rwable(&self) -> bool { 63 | self.flag().contains(PTEFlags::R | PTEFlags::W) 64 | } 65 | 66 | pub fn is_user(&self) -> bool { 67 | self.flag().contains(PTEFlags::U) 68 | } 69 | 70 | pub fn is_dirty(&self) -> bool { 71 | self.flag().contains(PTEFlags::D) 72 | } 73 | 74 | pub fn is_executable(&self) -> bool { 75 | self.flag().contains(PTEFlags::X) 76 | } 77 | 78 | pub fn is_accessed(&self) -> bool { 79 | self.flag().contains(PTEFlags::A) 80 | } 81 | 82 | // TODO: should implement in pagetable, and re-activate 83 | pub fn set_invalid(&mut self) { 84 | self.0 &= !PTEFlags::V.bits; 85 | } 86 | 87 | pub fn set_unaccessed(&mut self) { 88 | self.0 &= !PTEFlags::A.bits; 89 | } 90 | 91 | /// A PTE is a leaf PTE when at least one bit in R, W and X 92 | /// is set; otherwise, it is a pointer to the next level of 93 | /// the page table. 94 | pub fn is_leaf(&self) -> bool { 95 | self.flag() 96 | .intersects(PTEFlags::R | PTEFlags::W | PTEFlags::X) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/mem.rs: -------------------------------------------------------------------------------- 1 | //! Memory Management 2 | //! 3 | //! Kernel is running on virtual memory, which begins from [mem::VM_BASE]. 4 | //! Physical memory begins from [mem::PM_BASE]. 5 | //! 6 | //! There exist an one-to-one map from Kernel virtual memory(kvm) to physical 7 | //! memory(pm): kvm = pm + [mem::OFFSET]. 8 | //! 9 | 10 | pub mod layout; 11 | pub mod malloc; 12 | pub mod pagetable; 13 | pub mod palloc; 14 | pub mod userbuf; 15 | mod utils; 16 | 17 | use core::mem::size_of; 18 | 19 | pub use self::layout::*; 20 | pub use self::malloc::{kalloc, kfree}; 21 | pub use self::pagetable::*; 22 | pub use self::palloc::Palloc; 23 | pub use self::utils::*; 24 | 25 | use self::palloc::USER_POOL_LIMIT; 26 | 27 | pub fn get_pte(va: usize) -> Option { 28 | match crate::thread::Manager::get().current.lock().pagetable { 29 | Some(ref pt) => pt.lock().get_pte(va).copied(), 30 | None => KernelPgTable::get().get_pte(va).copied(), 31 | } 32 | } 33 | 34 | pub fn init(ram_base: usize, ram_tail: usize, pm_len: usize) { 35 | let palloc_tail = ram_tail - USER_POOL_LIMIT * PG_SIZE; 36 | 37 | unsafe { 38 | palloc::Palloc::init(ram_base, palloc_tail); 39 | palloc::UserPool::init(palloc_tail, ram_tail); 40 | KernelPgTable::init(pm_len); 41 | } 42 | } 43 | 44 | /// Translate a virtual address (pointer, slice) to a kernel virtual address 45 | /// if it's in user space. The translated user object is supposed to be in a page. 46 | pub trait Translate: Sized { 47 | fn translate(self) -> Option; 48 | } 49 | 50 | fn in_same_page(va1: usize, va2: usize) -> bool { 51 | va1 / PG_SIZE == va2 / PG_SIZE 52 | } 53 | 54 | fn translate(va: usize, len: usize) -> Option { 55 | if in_kernel_space(va) { 56 | return Some(va); 57 | } 58 | 59 | if !in_same_page(va, va + len - 1) { 60 | return None; 61 | } 62 | 63 | let pageoff = va & 0xFFF; 64 | let pte = get_pte(va)?; 65 | Some(pte.pa().into_va() | pageoff) 66 | } 67 | 68 | impl Translate for *const T { 69 | fn translate(self) -> Option { 70 | translate(self as usize, size_of::()).map(|va| va as *const T) 71 | } 72 | } 73 | 74 | impl Translate for *mut T { 75 | fn translate(self) -> Option { 76 | translate(self as usize, size_of::()).map(|va| va as *mut T) 77 | } 78 | } 79 | 80 | impl Translate for &[T] { 81 | fn translate(self) -> Option { 82 | let ptr = self.as_ptr(); 83 | let len = self.len(); 84 | translate(ptr as usize, len * size_of::()) 85 | .map(|va| va as *const T) 86 | .map(|ptr| unsafe { core::slice::from_raw_parts(ptr, len) }) 87 | } 88 | } 89 | 90 | impl Translate for &mut [T] { 91 | fn translate(self) -> Option { 92 | let ptr = self.as_mut_ptr(); 93 | let len = self.len(); 94 | translate(ptr as usize, len * size_of::()) 95 | .map(|va| va as *mut T) 96 | .map(|ptr| unsafe { core::slice::from_raw_parts_mut(ptr, len) }) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/mem/utils.rs: -------------------------------------------------------------------------------- 1 | mod list; 2 | 3 | pub use self::list::{InMemList, IterMut}; 4 | 5 | use crate::mem::layout::VM_OFFSET; 6 | 7 | pub const PG_SHIFT: usize = 12; 8 | pub const PG_MASK: usize = (1 << PG_SHIFT) - 1; 9 | pub const PG_SIZE: usize = 1 << PG_SHIFT; 10 | 11 | /// Physical Address 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 13 | pub struct PhysAddr(usize); 14 | 15 | impl PhysAddr { 16 | pub fn value(&self) -> usize { 17 | self.0 18 | } 19 | 20 | /// Physical page number 21 | pub fn ppn(&self) -> usize { 22 | self.0 >> PG_SHIFT 23 | } 24 | 25 | /// Translates physical address to virtual address. 26 | pub fn into_va(&self) -> usize { 27 | self.0 + VM_OFFSET 28 | } 29 | 30 | pub fn from_pa(pa: usize) -> Self { 31 | Self(pa) 32 | } 33 | } 34 | 35 | // Convert a virtual address(stored in usize) to a physical address. 36 | impl From for PhysAddr { 37 | fn from(va: usize) -> Self { 38 | assert!(in_kernel_space(va)); 39 | Self(va - VM_OFFSET) 40 | } 41 | } 42 | 43 | // Convert a pointer(in virtual address) to a physical address. 44 | impl From<*const T> for PhysAddr { 45 | fn from(pa: *const T) -> Self { 46 | PhysAddr::from(pa as usize) 47 | } 48 | } 49 | 50 | // Convert a pointer(in virtual address) to a physical address. 51 | impl From<*mut T> for PhysAddr { 52 | fn from(pa: *mut T) -> Self { 53 | PhysAddr::from(pa as usize) 54 | } 55 | } 56 | 57 | /// Checks if a virtual memory address is valid (lies in the kernel space) 58 | /// Contains kernel, sbi, mmio and plic memory. 59 | pub fn in_kernel_space(va: usize) -> bool { 60 | va & VM_OFFSET == VM_OFFSET 61 | } 62 | 63 | pub const fn div_round_up(n: usize, align: usize) -> usize { 64 | assert!(align.is_power_of_two()); 65 | round_up(n, align) / align 66 | } 67 | 68 | pub const fn round_up(n: usize, align: usize) -> usize { 69 | assert!(align.is_power_of_two()); 70 | (n + align - 1) & !(align - 1) 71 | } 72 | 73 | pub const fn round_down(n: usize, align: usize) -> usize { 74 | assert!(align.is_power_of_two()); 75 | n & !(align - 1) 76 | } 77 | 78 | pub const fn prev_power_of_two(num: usize) -> usize { 79 | 1 << (64 - num.leading_zeros() as usize - 1) 80 | } 81 | 82 | /// Aligned to page boundary. 83 | pub trait PageAlign: Copy + Eq + Sized { 84 | fn floor(self) -> Self; 85 | 86 | fn ceil(self) -> Self; 87 | 88 | fn is_aligned(self) -> bool { 89 | self.floor() == self 90 | } 91 | } 92 | 93 | impl PageAlign for usize { 94 | fn floor(self) -> Self { 95 | (self >> PG_SHIFT) << PG_SHIFT 96 | } 97 | 98 | fn ceil(self) -> Self { 99 | ((self + PG_SIZE - 1) >> PG_SHIFT) << PG_SHIFT 100 | } 101 | } 102 | 103 | impl PageAlign for PhysAddr { 104 | fn floor(self) -> Self { 105 | PhysAddr(self.0.floor()) 106 | } 107 | 108 | fn ceil(self) -> Self { 109 | PhysAddr(self.0.ceil()) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/userproc.rs: -------------------------------------------------------------------------------- 1 | //! User process. 2 | //! 3 | 4 | mod load; 5 | 6 | use alloc::string::String; 7 | use alloc::vec::Vec; 8 | use core::arch::asm; 9 | use core::mem::MaybeUninit; 10 | use riscv::register::sstatus; 11 | 12 | use crate::fs::File; 13 | use crate::mem::pagetable::KernelPgTable; 14 | use crate::thread; 15 | use crate::trap::{trap_exit_u, Frame}; 16 | 17 | pub struct UserProc { 18 | #[allow(dead_code)] 19 | bin: File, 20 | } 21 | 22 | impl UserProc { 23 | pub fn new(file: File) -> Self { 24 | Self { bin: file } 25 | } 26 | } 27 | 28 | /// Execute an object file with arguments. 29 | /// 30 | /// ## Return 31 | /// - `-1`: On error. 32 | /// - `tid`: Tid of the newly spawned thread. 33 | #[allow(unused_variables)] 34 | pub fn execute(mut file: File, argv: Vec) -> isize { 35 | #[cfg(feature = "debug")] 36 | kprintln!( 37 | "[PROCESS] Kernel thread {} prepare to execute a process with args {:?}", 38 | thread::current().name(), 39 | argv 40 | ); 41 | 42 | // It only copies L2 pagetable. This approach allows the new thread 43 | // to access kernel code and data during syscall without the need to 44 | // switch pagetables. 45 | let mut pt = KernelPgTable::clone(); 46 | 47 | let exec_info = match load::load_executable(&mut file, &mut pt) { 48 | Ok(x) => x, 49 | Err(_) => unsafe { 50 | pt.destroy(); 51 | return -1; 52 | }, 53 | }; 54 | 55 | // Initialize frame, pass argument to user. 56 | let mut frame = unsafe { MaybeUninit::::zeroed().assume_init() }; 57 | frame.sepc = exec_info.entry_point; 58 | frame.x[2] = exec_info.init_sp; 59 | 60 | // Here the new process will be created. 61 | let userproc = UserProc::new(file); 62 | 63 | // TODO: (Lab2) Pass arguments to user program 64 | 65 | thread::Builder::new(move || start(frame)) 66 | .pagetable(pt) 67 | .userproc(userproc) 68 | .spawn() 69 | .id() 70 | } 71 | 72 | /// Exits a process. 73 | /// 74 | /// Panic if the current thread doesn't own a user process. 75 | pub fn exit(_value: isize) -> ! { 76 | // TODO: Lab2. 77 | thread::exit(); 78 | } 79 | 80 | /// Waits for a child thread, which must own a user process. 81 | /// 82 | /// ## Return 83 | /// - `Some(exit_value)` 84 | /// - `None`: if tid was not created by the current thread. 85 | pub fn wait(_tid: isize) -> Option { 86 | // TODO: Lab2. 87 | Some(-1) 88 | } 89 | 90 | /// Initializes a user process in current thread. 91 | /// 92 | /// This function won't return. 93 | pub fn start(mut frame: Frame) -> ! { 94 | unsafe { sstatus::set_spp(sstatus::SPP::User) }; 95 | frame.sstatus = sstatus::read(); 96 | 97 | // Set kernel stack pointer to intr frame and then jump to `trap_exit_u()`. 98 | let kernal_sp = (&frame as *const Frame) as usize; 99 | 100 | unsafe { 101 | asm!( 102 | "mv sp, t0", 103 | "jr t1", 104 | in("t0") kernal_sp, 105 | in("t1") trap_exit_u as *const u8 106 | ); 107 | } 108 | 109 | unreachable!(); 110 | } 111 | -------------------------------------------------------------------------------- /tool/src/cli.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | 3 | use clap::{Args, Parser, Subcommand}; 4 | 5 | #[derive(Parser, Debug)] 6 | #[command( 7 | name = "tool", 8 | about = "A tool for building, testing and debugging Tacos." 9 | )] 10 | pub struct Cli { 11 | #[command(subcommand)] 12 | pub command: Commands, 13 | } 14 | 15 | impl Cli { 16 | pub fn parse() -> Self { 17 | ::parse() 18 | } 19 | } 20 | 21 | #[derive(Subcommand, Debug)] 22 | pub enum Commands { 23 | /// Build the project. 24 | Build(BuildArgs), 25 | /// Specify and run test cases. 26 | Test(TestArgs), 27 | /// Remember specific test cases. 28 | Book(BookArgs), 29 | } 30 | 31 | /* ---------------------------------- BUILD --------------------------------- */ 32 | 33 | #[derive(Args, Debug)] 34 | pub struct BuildArgs { 35 | /// Clean. 36 | #[arg(short, long)] 37 | pub clean: bool, 38 | 39 | /// Clean and rebuild. 40 | /// `--clean` will be ignored. 41 | #[arg(short, long)] 42 | pub rebuild: bool, 43 | 44 | /// Show the build process. 45 | #[arg(short, long)] 46 | pub verbose: bool, 47 | } 48 | 49 | /* ---------------------------------- TEST ---------------------------------- */ 50 | 51 | #[derive(Args, Debug)] 52 | pub struct TestArgs { 53 | /// The test cases to run. Only the name of test case is required. 54 | /// 55 | /// Example: 56 | /// `tool test -c args-none` 57 | /// , `tool test -c args-none,args-many` 58 | #[arg(short, long, value_delimiter = ',')] 59 | pub cases: Vec, 60 | 61 | /// Load specified bookmarks and add to the test suite. 62 | #[arg(short, long, value_delimiter = ',')] 63 | pub books: Vec, 64 | 65 | /// Add test cases that failed in previous run to the test suite. 66 | #[arg(short, long)] 67 | pub previous_failed: bool, 68 | 69 | /// Only show the command line to run, without starting it. 70 | #[arg(long)] 71 | pub dry: bool, 72 | 73 | /// Run in gdb mode. Only receive single test case. 74 | #[arg(long)] 75 | pub gdb: bool, 76 | 77 | /// Grading after test. 78 | #[arg(short, long)] 79 | pub grade: bool, 80 | 81 | /// Verbose mode without testing. Suppress `--gdb` and `--grade`. 82 | #[arg(short, long)] 83 | pub verbose: bool, 84 | } 85 | 86 | /* -------------------------------- BOOKMARK -------------------------------- */ 87 | 88 | #[derive(Args, Debug)] 89 | pub struct BookArgs { 90 | /// The name of the bookmark. 91 | /// 92 | /// Bookmark will be saved in `/bookmarks/.json` 93 | #[arg(short, long)] 94 | pub name: String, 95 | 96 | /// Delete mode. Turn 'add' into 'delete' in other options. 97 | /// 98 | /// If no test cases are specified, delete the whole bookmark (with its file!). 99 | #[arg(short, long)] 100 | pub del: bool, 101 | 102 | /// Test cases to add in this bookmark. 103 | #[arg(short, long, value_delimiter = ',')] 104 | pub cases: Vec, 105 | 106 | /// Bookmarks to load and add to this bookmark. 107 | #[arg(short, long, value_delimiter = ',')] 108 | pub books: Vec, 109 | 110 | /// Add test cases that failed in previous run to the bookmark. 111 | #[arg(short, long)] 112 | pub previous_failed: bool, 113 | } 114 | -------------------------------------------------------------------------------- /user/vm/page-merge-seq.c: -------------------------------------------------------------------------------- 1 | /* Generates about 1 MB of random data that is then divided into 2 | 16 chunks. A separate subprocess sorts each chunk in sequence. 3 | Then we merge the chunks and verify that the result is what it 4 | should be. */ 5 | 6 | #include "arc4.h" 7 | #include "user.h" 8 | 9 | /* This is the max file size for an older version of the Pintos 10 | file system that had 126 direct blocks each pointing to a 11 | single disk sector. We could raise it now. */ 12 | #define CHUNK_SIZE (126 * 512) 13 | #define CHUNK_CNT 16 /* Number of chunks. */ 14 | #define DATA_SIZE (CHUNK_CNT * CHUNK_SIZE) /* Buffer size. */ 15 | 16 | static uint8 buf1[DATA_SIZE], buf2[DATA_SIZE]; 17 | static size_t histogram[256]; 18 | 19 | /* Initialize buf1 with random data, 20 | then count the number of instances of each value within it. */ 21 | static void init(void) { 22 | struct arc4 arc4; 23 | size_t i; 24 | 25 | arc4_init(&arc4, "foobar", 6); 26 | arc4_crypt(&arc4, buf1, sizeof buf1); 27 | for (i = 0; i < sizeof buf1; i++) histogram[buf1[i]]++; 28 | } 29 | 30 | /* Sort each chunk of buf1 using a subprocess. */ 31 | static void sort_chunks(void) { 32 | size_t i; 33 | 34 | for (i = 0; i < CHUNK_CNT; i++) { 35 | pid_t child; 36 | int fd; 37 | 38 | /* Write this chunk to a file. */ 39 | assert((fd = open("buffer", O_CREATE | O_WRONLY)) > 2); 40 | write(fd, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); 41 | close(fd); 42 | 43 | /* Sort with subprocess. */ 44 | const char* args[] = {"child-sort", "buffer", 0}; 45 | assert((child = exec(args[0], args)) != -1); 46 | assert(wait(child) == 123, "wait for child-sort"); 47 | 48 | /* Read chunk back from file. */ 49 | assert((fd = open("buffer", 0)) > 2); 50 | read(fd, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); 51 | close(fd); 52 | } 53 | } 54 | 55 | /* Merge the sorted chunks in buf1 into a fully sorted buf2. */ 56 | static void merge(void) { 57 | uint8* mp[CHUNK_CNT]; 58 | size_t mp_left; 59 | uint8* op; 60 | size_t i; 61 | 62 | /* Initialize merge pointers. */ 63 | mp_left = CHUNK_CNT; 64 | for (i = 0; i < CHUNK_CNT; i++) mp[i] = buf1 + CHUNK_SIZE * i; 65 | 66 | /* Merge. */ 67 | op = buf2; 68 | while (mp_left > 0) { 69 | /* Find smallest value. */ 70 | size_t min = 0; 71 | for (i = 1; i < mp_left; i++) 72 | if (*mp[i] < *mp[min]) min = i; 73 | 74 | /* Append value to buf2. */ 75 | *op++ = *mp[min]; 76 | 77 | /* Advance merge pointer. 78 | Delete this chunk from the set if it's emptied. */ 79 | if ((++mp[min] - buf1) % CHUNK_SIZE == 0) mp[min] = mp[--mp_left]; 80 | } 81 | } 82 | 83 | static void verify(void) { 84 | size_t buf_idx; 85 | size_t hist_idx; 86 | 87 | buf_idx = 0; 88 | for (hist_idx = 0; hist_idx < sizeof histogram / sizeof *histogram; hist_idx++) { 89 | while (histogram[hist_idx]-- > 0) { 90 | if (buf2[buf_idx] != hist_idx) 91 | panic("bad value %d in byte %d", buf2[buf_idx], buf_idx); 92 | buf_idx++; 93 | } 94 | } 95 | } 96 | 97 | void main() { 98 | init(); 99 | sort_chunks(); 100 | merge(); 101 | verify(); 102 | } 103 | -------------------------------------------------------------------------------- /src/fs/disk/dir.rs: -------------------------------------------------------------------------------- 1 | //! Root dir. 2 | //! 3 | use super::{Inum, Path}; 4 | use crate::fs::File; 5 | use crate::io::prelude::*; 6 | use crate::{OsError, Result}; 7 | 8 | const FILE_NAME_LEN_MAX: usize = 28; 9 | 10 | /// 32-byte entry. 11 | #[repr(C)] 12 | pub struct DirEntry { 13 | name: [u8; FILE_NAME_LEN_MAX], 14 | inum: Inum, 15 | } 16 | 17 | impl DirEntry { 18 | pub fn is_valid(&self) -> bool { 19 | self.name[0] != '#' as u8 && self.name[0] != 0 20 | } 21 | 22 | pub fn invalidate(&mut self) { 23 | self.name[0] = '#' as u8 24 | } 25 | } 26 | 27 | /// Currently only support root dir. Other files should not be dir. 28 | pub struct RootDir(pub(super) File); 29 | 30 | impl RootDir { 31 | /// Convert a path to inumber. This will iteratively search through the 32 | /// root dir entries, return the first entry that with the same name of given one. 33 | pub fn path2inum(&mut self, path: &Path) -> Result { 34 | self.0.rewind()?; 35 | while let Ok(entry) = self.0.read_into::() { 36 | if !entry.is_valid() { 37 | continue; 38 | } 39 | let name = unsafe { 40 | core::ffi::CStr::from_ptr(&entry.name as *const u8 as *const i8) 41 | .to_str() 42 | .or(Err(OsError::CstrFormatErr))? 43 | }; 44 | // Deref `Path` to `String`. 45 | if path.eq(name) { 46 | return Ok(entry.inum); 47 | } 48 | } 49 | Err(OsError::NoSuchFile) 50 | } 51 | 52 | /// Check if there is a file with the given name. 53 | /// 54 | /// # See 55 | /// [`path2inum()`]. 56 | pub fn exists(&mut self, path: &Path) -> bool { 57 | self.path2inum(path).is_ok() 58 | } 59 | 60 | /// Insert an entry with given name and inumber. 61 | pub fn insert(&mut self, path: &Path, inum: Inum) -> Result<()> { 62 | if !path.is_ascii() { 63 | return Err(OsError::CstrFormatErr); 64 | } 65 | let pos = self.first_invalid()?; 66 | let mut entry = DirEntry { 67 | name: [0; FILE_NAME_LEN_MAX], 68 | inum, 69 | }; 70 | entry.name[0..core::cmp::min(path.len(), FILE_NAME_LEN_MAX - 1)] 71 | .copy_from_slice(path.as_bytes()); 72 | self.0.seek(SeekFrom::Start(pos))?; 73 | self.0.write_from(entry)?; 74 | Ok(()) 75 | } 76 | 77 | /// Remove an entry from root dir by given inumber. 78 | pub fn remove(&mut self, inum: Inum) -> Result<()> { 79 | self.0.rewind()?; 80 | while let Ok(mut entry) = self.0.read_into::() { 81 | if entry.inum == inum { 82 | entry.invalidate(); 83 | self.0.seek(SeekFrom::Current(-32))?; 84 | self.0.write_from(entry)?; 85 | } 86 | } 87 | // Ignore unexisting file. 88 | Ok(()) 89 | } 90 | 91 | /// Find the first invalid place of entry. We may use it to insert a new one later. 92 | fn first_invalid(&mut self) -> Result { 93 | self.0.rewind()?; 94 | while let Ok(entry) = self.0.read_into::() { 95 | if !entry.is_valid() { 96 | return Ok(self.0.seek(SeekFrom::Current(-32)).unwrap()); 97 | } 98 | } 99 | Err(OsError::RootDirFull) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/fs/inmem.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | use alloc::sync::Weak; 3 | use alloc::vec::Vec; 4 | use core::cmp::min; 5 | 6 | use crate::{OsError, Result}; 7 | 8 | use super::*; 9 | 10 | /* -------------------------------------------------------------------------- */ 11 | /* FileSys */ 12 | /* -------------------------------------------------------------------------- */ 13 | 14 | /// An in-memory pseudo file system, wrapping memory buffers 15 | /// with file-like interfaces. 16 | /// 17 | /// # Panic 18 | /// This struct only support [`FileSys::open()`], which 19 | /// copy and wrap a byte buffer into a [`File`]. Calls to 20 | /// [`FileSys::close()`], [`FileSys::create()`] and 21 | /// [`FileSys::remove()`] will panic. 22 | pub struct MemFs { 23 | oft: Mutex>>, 24 | } 25 | 26 | impl FileSys for MemFs { 27 | type Device = (); 28 | type Path = Box<[u8]>; 29 | 30 | fn mount(_device: Self::Device) -> Result { 31 | Ok(Self { 32 | oft: Mutex::new(Vec::new()), 33 | }) 34 | } 35 | 36 | fn unmount(&self) { 37 | unimplemented!(); 38 | } 39 | 40 | fn open(&self, id: Self::Path) -> Result { 41 | let buf = Mutex::new(id); 42 | let vnode = Arc::new(Inode { buf }); 43 | let weak = Arc::downgrade(&vnode); 44 | self.oft.lock().push(weak); 45 | 46 | Ok(File::new(vnode)) 47 | } 48 | 49 | fn close(&self, _file: File) { 50 | unimplemented!(); 51 | } 52 | 53 | fn create(&self, _id: Self::Path) -> Result { 54 | unimplemented!(); 55 | } 56 | 57 | fn remove(&self, _id: Self::Path) -> Result<()> { 58 | unimplemented!(); 59 | } 60 | } 61 | 62 | /* -------------------------------------------------------------------------- */ 63 | /* Inode */ 64 | /* -------------------------------------------------------------------------- */ 65 | 66 | // TODO: should it be pub or not 67 | struct Inode { 68 | buf: Mutex>, 69 | } 70 | 71 | impl Vnode for Inode { 72 | fn inum(&self) -> usize { 73 | self.buf.lock().as_ptr() as usize 74 | } 75 | 76 | fn len(&self) -> usize { 77 | self.buf.lock().len() 78 | } 79 | 80 | fn read_at(&self, buf: &mut [u8], off: usize) -> Result { 81 | // Protect during the whole process. 82 | let lock = self.buf.lock(); 83 | if off >= lock.len() { 84 | return Err(OsError::UnexpectedEOF); 85 | } 86 | 87 | let len = min(lock.len() - off, buf.len()); 88 | 89 | buf[..len].copy_from_slice(&lock[off..off + len]); 90 | Ok(len) 91 | } 92 | 93 | fn write_at(&self, buf: &[u8], off: usize) -> Result { 94 | // Protect during the whole process. 95 | let mut lock = self.buf.lock(); 96 | if off >= lock.len() { 97 | return Err(OsError::UnexpectedEOF); 98 | } 99 | 100 | let len = min(lock.len() - off, buf.len()); 101 | 102 | let nb = &mut lock[off..off + len]; 103 | nb.copy_from_slice(&buf[..len]); 104 | Ok(len) 105 | } 106 | 107 | fn resize(&self, _size: usize) -> Result<()> { 108 | unimplemented!(); 109 | } 110 | 111 | fn close(&self) { 112 | unimplemented!(); 113 | } 114 | 115 | // TODO: Impl deny write for mem file. 116 | fn deny_write(&self) {} 117 | fn allow_write(&self) {} 118 | } 119 | -------------------------------------------------------------------------------- /test/unit/fs/disk/sync.rs: -------------------------------------------------------------------------------- 1 | use crate::device::virtio::SECTOR_SIZE; 2 | use crate::fs::disk::DISKFS; 3 | use crate::fs::FileSys; 4 | use crate::io::prelude::*; 5 | use crate::sync::{Mutex, Semaphore}; 6 | use crate::thread::*; 7 | use crate::{OsError, Result}; 8 | 9 | const FNAME: &str = "/disk-sync"; 10 | 11 | pub fn main() { 12 | let barrier = alloc::sync::Arc::from(Mutex::<()>::new(())); 13 | let teller = alloc::sync::Arc::from(Semaphore::new(0)); 14 | 15 | let b1 = barrier.clone(); 16 | let t1 = teller.clone(); 17 | let b2 = barrier.clone(); 18 | let t2 = teller.clone(); 19 | 20 | let child1 = move || -> Result<()> { 21 | // (1) Open the file. 22 | let mut f = DISKFS.open(FNAME.into())?; 23 | kprintln!("[DISKFS.SYNC] Child1 got file."); 24 | // (2) Write the thing asynchronously. 25 | f.rewind()?; 26 | for value in 0..SECTOR_SIZE / core::mem::size_of::() { 27 | f.write_from(value)?; 28 | } 29 | kprintln!("[DISKFS.SYNC] Child1 wrote things."); 30 | // (3) Barrier. 31 | t1.up(); 32 | b1.lock(); 33 | kprintln!("[DISKFS.SYNC] Child1 passed barrier."); 34 | // (5) Check. 35 | f.rewind()?; 36 | for value in 0..SECTOR_SIZE / core::mem::size_of::() { 37 | if f.read_into::()? != value { 38 | return Err(OsError::UserError); 39 | } 40 | } 41 | kprintln!("[DISKFS.SYNC] Child1 passed."); 42 | t1.up(); 43 | Ok(()) 44 | }; 45 | let child2 = move || -> Result<()> { 46 | // (1) Open the file. 47 | let mut f = DISKFS.open(FNAME.into())?; 48 | kprintln!("[DISKFS.SYNC] Child2 got file."); 49 | // (2) Use a blocker to prevent file1 can be in-place extended. 50 | let _b = DISKFS.create("blker".into())?; 51 | // And asynchronously extend it, the file may be moved to other 52 | // part of the disk, but the write of child1 should be remain. 53 | f.set_len(2 * SECTOR_SIZE)?; 54 | // (3) Write sth. 55 | f.seek(SeekFrom::Start(SECTOR_SIZE))?; 56 | for value in 0..SECTOR_SIZE / core::mem::size_of::() { 57 | f.write_from(value)?; 58 | } 59 | kprintln!("[DISKFS.SYNC] Child2 wrote things."); 60 | // (4) Barrier. 61 | t2.up(); 62 | b2.lock(); 63 | kprintln!("[DISKFS.SYNC] Child2 passed barrier."); 64 | // (5) Check. 65 | f.seek(SeekFrom::Start(SECTOR_SIZE))?; 66 | for value in 0..SECTOR_SIZE / core::mem::size_of::() { 67 | if f.read_into::()? != value { 68 | return Err(OsError::UserError); 69 | } 70 | } 71 | kprintln!("[DISKFS.SYNC] Child2 passed."); 72 | t2.up(); 73 | Ok(()) 74 | }; 75 | let mut f = DISKFS.create(FNAME.into()).unwrap(); 76 | f.set_len(SECTOR_SIZE).unwrap(); 77 | // Tag as removed for multi-time tests. 78 | DISKFS.remove(FNAME.into()).unwrap(); 79 | kprintln!("[DISKFS.SYNC] Created file."); 80 | { 81 | spawn("child1", move || child1().unwrap()); 82 | kprintln!("[DISKFS.SYNC] Spawned child1."); 83 | spawn("child2", move || child2().unwrap()); 84 | kprintln!("[DISKFS.SYNC] Spawned child2."); 85 | } 86 | { 87 | // Barrier. 88 | let _guard = barrier.lock(); 89 | teller.down(); 90 | teller.down(); 91 | kprintln!("[DISKFS.SYNC] Main passed barrier."); 92 | } 93 | teller.down(); 94 | teller.down(); 95 | kprintln!("[DISKFS.SYNC] Done."); 96 | } 97 | --------------------------------------------------------------------------------