add test apps

This commit is contained in:
Yu Chen
2022-06-28 09:25:52 +08:00
parent 9e725d3e30
commit 2811808a73
85 changed files with 4367 additions and 0 deletions

7
user/.cargo/config Normal file
View File

@@ -0,0 +1,7 @@
[build]
target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf]
rustflags = [
"-Clink-args=-Tsrc/linker.ld",
]

4
user/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
target/*
.vscode/
build/
.idea/

19
user/Cargo.toml Normal file
View File

@@ -0,0 +1,19 @@
[package]
name = "user_lib"
version = "0.1.0"
authors = ["Yifan Wu <shinbokuow@163.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
buddy_system_allocator = "0.6"
bitflags = "1.2.1"
spin = "0.9"
lock_api = "=0.4.6"
lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
[profile.release]
opt-level = "z" # Optimize for size.
strip = true # Automatically strip symbols from the binary.
lto = true

64
user/Makefile Normal file
View File

@@ -0,0 +1,64 @@
TARGET := riscv64gc-unknown-none-elf
MODE := release
APP_DIR := src/bin
TARGET_DIR := target/$(TARGET)/$(MODE)
BUILD_DIR := build
OBJDUMP := rust-objdump --arch-name=riscv64
OBJCOPY := rust-objcopy --binary-architecture=riscv64
PY := python3
BASE ?= 0
CHAPTER ?= 0
TEST ?= $(CHAPTER)
ifeq ($(TEST), 0) # No test, deprecated, previously used in v3
APPS := $(filter-out $(wildcard $(APP_DIR)/ch*.rs), $(wildcard $(APP_DIR)/*.rs))
else ifeq ($(TEST), 1) # All test
APPS := $(wildcard $(APP_DIR)/ch*.rs)
else
TESTS := $(shell seq $(BASE) $(TEST))
ifeq ($(BASE), 0) # Normal tests only
APPS := $(foreach T, $(TESTS), $(wildcard $(APP_DIR)/ch$(T)_*.rs))
else ifeq ($(BASE), 1) # Basic tests only
APPS := $(foreach T, $(TESTS), $(wildcard $(APP_DIR)/ch$(T)b_*.rs))
else # Basic and normal
APPS := $(foreach T, $(TESTS), $(wildcard $(APP_DIR)/ch$(T)*.rs))
endif
endif
ELFS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%, $(APPS))
binary:
@echo $(ELFS)
@if [ ${CHAPTER} -gt 3 ]; then \
cargo build --release ;\
else \
CHAPTER=$(CHAPTER) python3 build.py ;\
fi
@$(foreach elf, $(ELFS), \
$(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf)); \
cp $(elf) $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.elf, $(elf));)
disasm:
@$(foreach elf, $(ELFS), \
$(OBJDUMP) $(elf) -S > $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.asm, $(elf));)
@$(foreach t, $(ELFS), cp $(t).asm $(BUILD_DIR)/asm/;)
pre:
@mkdir -p $(BUILD_DIR)/bin/
@mkdir -p $(BUILD_DIR)/elf/
@mkdir -p $(BUILD_DIR)/app/
@mkdir -p $(BUILD_DIR)/asm/
@$(foreach t, $(APPS), cp $(t) $(BUILD_DIR)/app/;)
build: clean pre binary
@$(foreach t, $(ELFS), cp $(t).bin $(BUILD_DIR)/bin/;)
@$(foreach t, $(ELFS), cp $(t).elf $(BUILD_DIR)/elf/;)
clean:
@cargo clean
@rm -rf $(BUILD_DIR)
all: build
.PHONY: elf binary build clean all

23
user/build.py Normal file
View File

@@ -0,0 +1,23 @@
import os
base_address = 0x80400000
step = 0x20000
linker = "src/linker.ld"
app_id = 0
apps = os.listdir("build/app")
apps.sort()
chapter = os.getenv("CHAPTER")
for app in apps:
app = app[: app.find(".")]
os.system(
"cargo rustc --bin %s --release -- -Clink-args=-Ttext=%x"
% (app, base_address + step * app_id)
)
print(
"[build.py] application %s start with address %s"
% (app, hex(base_address + step * app_id))
)
if chapter == '3':
app_id = app_id + 1

View File

@@ -0,0 +1,16 @@
#![no_std]
#![no_main]
extern crate user_lib;
/// 由于 rustsbi 的问题,该程序无法正确退出
/// > rustsbi 0.2.0-alpha.1 已经修复,可以正常退出
#[no_mangle]
pub fn main() -> isize {
unsafe {
#[allow(clippy::zero_ptr)]
(0x0 as *mut u8).write_volatile(0);
}
panic!("FAIL: T.T\n");
}

View File

@@ -0,0 +1,15 @@
#![no_std]
#![no_main]
extern crate user_lib;
/// 由于 rustsbi 的问题,该程序无法正确退出
/// > rustsbi 0.2.0-alpha.1 已经修复,可以正常退出
#[no_mangle]
pub fn main() -> ! {
unsafe {
core::arch::asm!("sret");
}
panic!("FAIL: T.T\n");
}

View File

@@ -0,0 +1,16 @@
#![no_std]
#![no_main]
extern crate user_lib;
/// 由于 rustsbi 的问题,该程序无法正确退出
/// > rustsbi 0.2.0-alpha.1 已经修复,可以正常退出
#[no_mangle]
pub fn main() -> ! {
let mut sstatus: usize;
unsafe {
core::arch::asm!("csrr {}, sstatus", out(reg) sstatus);
}
panic!("(-_-) I get sstatus:{:x}\nFAIL: T.T\n", sstatus);
}

View File

@@ -0,0 +1,14 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
/// 正确输出:
/// Hello world from user mode program!
#[no_mangle]
fn main() -> i32 {
println!("Hello, world from user mode program!");
0
}

View File

@@ -0,0 +1,28 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
const LEN: usize = 100;
#[no_mangle]
fn main() -> i32 {
let p = 3u64;
let m = 998244353u64;
let iter: usize = 200000;
let mut s = [0u64; LEN];
let mut cur = 0usize;
s[cur] = 1;
for i in 1..=iter {
let next = if cur + 1 == LEN { 0 } else { cur + 1 };
s[next] = s[cur] * p % m;
cur = next;
if i % 10000 == 0 {
println!("power_3 [{}/{}]", i, iter);
}
}
println!("{}^{} = {}(MOD {})", p, iter, s[cur], m);
println!("Test power_3 OK!");
0
}

View File

@@ -0,0 +1,28 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
const LEN: usize = 100;
#[no_mangle]
fn main() -> i32 {
let p = 5u64;
let m = 998244353u64;
let iter: usize = 140000;
let mut s = [0u64; LEN];
let mut cur = 0usize;
s[cur] = 1;
for i in 1..=iter {
let next = if cur + 1 == LEN { 0 } else { cur + 1 };
s[next] = s[cur] * p % m;
cur = next;
if i % 10000 == 0 {
println!("power_5 [{}/{}]", i, iter);
}
}
println!("{}^{} = {}(MOD {})", p, iter, s[cur], m);
println!("Test power_5 OK!");
0
}

View File

@@ -0,0 +1,28 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
const LEN: usize = 100;
#[no_mangle]
fn main() -> i32 {
let p = 7u64;
let m = 998244353u64;
let iter: usize = 160000;
let mut s = [0u64; LEN];
let mut cur = 0usize;
s[cur] = 1;
for i in 1..=iter {
let next = if cur + 1 == LEN { 0 } else { cur + 1 };
s[next] = s[cur] * p % m;
cur = next;
if i % 10000 == 0 {
println!("power_7 [{}/{}]", i, iter);
}
}
println!("{}^{} = {}(MOD {})", p, iter, s[cur], m);
println!("Test power_7 OK!");
0
}

View File

@@ -0,0 +1,46 @@
#![no_std]
#![no_main]
extern crate user_lib;
use user_lib::{
get_time, println, sleep, task_info, TaskInfo, TaskStatus, SYSCALL_EXIT, SYSCALL_GETTIMEOFDAY,
SYSCALL_TASK_INFO, SYSCALL_WRITE, SYSCALL_YIELD,
};
#[no_mangle]
pub fn main() -> usize {
let t1 = get_time() as usize;
let info = TaskInfo::new();
get_time();
sleep(500);
let t2 = get_time() as usize;
// 注意本次 task info 调用也计入
assert_eq!(0, task_info(&info));
let t3 = get_time() as usize;
assert!(3 <= info.syscall_times[SYSCALL_GETTIMEOFDAY]);
assert_eq!(1, info.syscall_times[SYSCALL_TASK_INFO]);
assert_eq!(0, info.syscall_times[SYSCALL_WRITE]);
assert!(0 < info.syscall_times[SYSCALL_YIELD]);
assert_eq!(0, info.syscall_times[SYSCALL_EXIT]);
assert!(t2 - t1 <= info.time + 1);
assert!(info.time < t3 - t1 + 100);
assert!(info.status == TaskStatus::Running);
// 想想为什么 write 调用是两次
println!("string from task info test\n");
let t4 = get_time() as usize;
assert_eq!(0, task_info(&info));
let t5 = get_time() as usize;
assert!(5 <= info.syscall_times[SYSCALL_GETTIMEOFDAY]);
assert_eq!(2, info.syscall_times[SYSCALL_TASK_INFO]);
assert_eq!(2, info.syscall_times[SYSCALL_WRITE]);
assert!(0 < info.syscall_times[SYSCALL_YIELD]);
assert_eq!(0, info.syscall_times[SYSCALL_EXIT]);
assert!(t4 - t1 <= info.time + 1);
assert!(info.time < t5 - t1 + 100);
assert!(info.status == TaskStatus::Running);
println!("Test task info OK!");
0
}

View File

@@ -0,0 +1,24 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{get_time, yield_};
/// 正确输出:(无报错信息)
/// get_time OK! {...}
/// Test sleep OK!
#[no_mangle]
fn main() -> i32 {
let current_time = get_time();
assert!(current_time > 0);
println!("get_time OK! {}", current_time);
let wait_for = current_time + 3000;
while get_time() < wait_for {
yield_();
}
println!("Test sleep OK!");
0
}

View File

@@ -0,0 +1,22 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{get_time, sleep};
#[no_mangle]
pub fn main() -> i32 {
let start = get_time();
println!("current time_msec = {}", start);
sleep(100);
let end = get_time();
println!(
"time_msec = {} after sleeping 100 ticks, delta = {}ms!",
end,
end - start
);
println!("Test sleep1 passed!");
0
}

View File

@@ -0,0 +1,30 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::yield_;
const WIDTH: usize = 10;
const HEIGHT: usize = 5;
/*
理想结果:三个程序交替输出 ABC
*/
#[no_mangle]
fn main() -> i32 {
for i in 0..HEIGHT {
let buf = ['A' as u8; WIDTH];
println!(
"{} [{}/{}]",
core::str::from_utf8(&buf).unwrap(),
i + 1,
HEIGHT
);
yield_();
}
println!("Test write A OK!");
0
}

View File

@@ -0,0 +1,30 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::yield_;
const WIDTH: usize = 10;
const HEIGHT: usize = 5;
/*
理想结果:三个程序交替输出 ABC
*/
#[no_mangle]
fn main() -> i32 {
for i in 0..HEIGHT {
let buf = ['B' as u8; WIDTH];
println!(
"{} [{}/{}]",
core::str::from_utf8(&buf).unwrap(),
i + 1,
HEIGHT
);
yield_();
}
println!("Test write B OK!");
0
}

View File

@@ -0,0 +1,30 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::yield_;
/*
理想结果:三个程序交替输出 ABC
*/
const WIDTH: usize = 10;
const HEIGHT: usize = 5;
#[no_mangle]
fn main() -> i32 {
for i in 0..HEIGHT {
let buf = ['C' as u8; WIDTH];
println!(
"{} [{}/{}]",
core::str::from_utf8(&buf).unwrap(),
i + 1,
HEIGHT
);
yield_();
}
println!("Test write C OK!");
0
}

33
user/src/bin/ch4_mmap0.rs Normal file
View File

@@ -0,0 +1,33 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::mmap;
/*
理想结果:输出 Test 04_1 OK!
*/
#[no_mangle]
fn main() -> i32 {
let start: usize = 0x10000000;
let len: usize = 4096;
let prot: usize = 3;
assert_eq!(0, mmap(start, len, prot));
for i in start..(start + len) {
let addr: *mut u8 = i as *mut u8;
unsafe {
*addr = i as u8;
}
}
for i in start..(start + len) {
let addr: *mut u8 = i as *mut u8;
unsafe {
assert_eq!(*addr, i as u8);
}
}
println!("Test 04_1 OK!");
0
}

25
user/src/bin/ch4_mmap1.rs Normal file
View File

@@ -0,0 +1,25 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::mmap;
/*
理想结果:程序触发访存异常,被杀死。不输出 error 就算过。
*/
#[no_mangle]
fn main() -> i32 {
let start: usize = 0x10000000;
let len: usize = 4096;
let prot: usize = 1;
assert_eq!(0, mmap(start, len, prot));
let addr: *mut u8 = start as *mut u8;
unsafe {
*addr = start as u8;
}
println!("Should cause error, Test 04_2 fail!");
0
}

26
user/src/bin/ch4_mmap2.rs Normal file
View File

@@ -0,0 +1,26 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::mmap;
/*
理想结果:程序触发访存异常,被杀死。不输出 error 就算过。
*/
#[no_mangle]
fn main() -> i32 {
let start: usize = 0x10000000;
let len: usize = 4096;
let prot: usize = 2;
assert_eq!(0, mmap(start, len, prot));
let addr: *mut u8 = start as *mut u8;
unsafe {
// *addr = start as u8; // can't write, R == 0 && W == 1 is illegal in riscv
assert!(*addr != 0);
}
println!("Should cause error, Test 04_2 fail!");
0
}

25
user/src/bin/ch4_mmap3.rs Normal file
View File

@@ -0,0 +1,25 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::mmap;
/*
理想结果:对于错误的 mmap 返回 -1最终输出 Test 04_4 test OK!
*/
#[no_mangle]
fn main() -> i32 {
let start: usize = 0x10000000;
let len: usize = 4096;
let prot: usize = 3;
assert_eq!(0, mmap(start, len, prot));
assert_eq!(mmap(start - len, len + 1, prot), -1);
assert_eq!(mmap(start + len + 1, len, prot), -1);
assert_eq!(mmap(start + len, len, 0), -1);
assert_eq!(mmap(start + len, len, prot | 8), -1);
println!("Test 04_4 test OK!");
0
}

36
user/src/bin/ch4_unmap.rs Normal file
View File

@@ -0,0 +1,36 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{mmap, munmap};
/*
理想结果:输出 Test 04_5 ummap OK!
*/
#[no_mangle]
fn main() -> i32 {
let start: usize = 0x10000000;
let len: usize = 4096;
let prot: usize = 3;
assert_eq!(0, mmap(start, len, prot));
assert_eq!(mmap(start + len, len * 2, prot), 0);
assert_eq!(munmap(start, len), 0);
assert_eq!(mmap(start - len, len + 1, prot), 0);
for i in (start - len)..(start + len * 3) {
let addr: *mut u8 = i as *mut u8;
unsafe {
*addr = i as u8;
}
}
for i in (start - len)..(start + len * 3) {
let addr: *mut u8 = i as *mut u8;
unsafe {
assert_eq!(*addr, i as u8);
}
}
println!("Test 04_5 ummap OK!");
0
}

View File

@@ -0,0 +1,23 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{mmap, munmap};
/*
理想结果:输出 Test 04_6 ummap2 OK!
*/
#[no_mangle]
fn main() -> i32 {
let start: usize = 0x10000000;
let len: usize = 4096;
let prot: usize = 3;
assert_eq!(0, mmap(start, len, prot));
assert_eq!(munmap(start, len + 1), -1);
assert_eq!(munmap(start + 1, len - 1), -1);
println!("Test 04_6 ummap2 OK!");
0
}

17
user/src/bin/ch5_exit0.rs Normal file
View File

@@ -0,0 +1,17 @@
#![no_std]
#![no_main]
extern crate user_lib;
use user_lib::exit;
/*
辅助测例,正常退出,不输出 FAIL 即可。
*/
#[allow(unreachable_code)]
#[no_mangle]
pub fn main() -> i32 {
exit(66778);
panic!("FAIL: T.T\n");
0
}

17
user/src/bin/ch5_exit1.rs Normal file
View File

@@ -0,0 +1,17 @@
#![no_std]
#![no_main]
extern crate user_lib;
use user_lib::exit;
/*
辅助测例,正常退出,不输出 FAIL 即可。
*/
#[allow(unreachable_code)]
#[no_mangle]
pub fn main() -> i32 {
exit(-233);
panic!("FAIL: T.T\n");
0
}

View File

@@ -0,0 +1,18 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::getpid;
/*
辅助测例 打印子进程 pid
*/
#[no_mangle]
pub fn main() -> i32 {
let pid = getpid();
println!("Test getpid OK! pid = {}", pid);
0
}

View File

@@ -0,0 +1,20 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::set_priority;
/// 正确输出:(无报错信息)
/// Test set_priority OK!
#[no_mangle]
pub fn main() -> i32 {
assert_eq!(set_priority(10), 10);
assert_eq!(set_priority(isize::MAX), isize::MAX);
assert_eq!(set_priority(0), -1);
assert_eq!(set_priority(1), -1);
assert_eq!(set_priority(-10), -1);
println!("Test set_priority OK!");
0
}

View File

@@ -0,0 +1,29 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{spawn, wait};
const MAX_CHILD: usize = 40;
/*
理想结果:生成 MAX_CHILD 个 getpid 的子进程,全部结束后,输出 Test spawn0 OK!
*/
#[no_mangle]
pub fn main() -> i32 {
for _ in 0..MAX_CHILD {
let cpid = spawn("ch5_getpid\0");
assert!(cpid >= 0, "child pid invalid");
println!("new child {}", cpid);
}
let mut exit_code: i32 = 0;
for _ in 0..MAX_CHILD {
assert!(wait(&mut exit_code) > 0, "wait stopped early");
assert_eq!(exit_code, 0, "error exit ocde {}", exit_code);
}
assert!(wait(&mut exit_code) <= 0, "wait got too many");
println!("Test spawn0 OK!");
0
}

View File

@@ -0,0 +1,35 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{spawn, wait, waitpid};
/// 程序行为:先后产生 3 个有特定返回值的程序,检查 waitpid 能够获取正确返回值。
/// 理想输出:
/// new child i
/// Test wait OK!
/// Test waitpid OK!
#[no_mangle]
pub fn main() -> i32 {
let cpid = spawn("ch5_exit0\0");
assert!(cpid >= 0, "child pid invalid");
println!("new child {}", cpid);
let mut exit_code: i32 = 0;
let exit_pid = wait(&mut exit_code);
assert_eq!(exit_pid, cpid, "error exit pid");
assert_eq!(exit_code, 66778, "error exit code");
println!("Test wait OK!");
let (cpid0, cpid1) = (spawn("ch5_exit0\0"), spawn("ch5_exit1\0"));
let exit_pid = waitpid(cpid1 as usize, &mut exit_code);
assert_eq!(exit_pid, cpid1, "error exit pid");
assert_eq!(exit_code, -233, "error exit code");
let exit_pid = wait(&mut exit_code);
assert_eq!(exit_pid, cpid0, "error exit pid");
assert_eq!(exit_code, 66778, "error exit code");
println!("Test waitpid OK!");
0
}

View File

@@ -0,0 +1,33 @@
#![no_std]
#![no_main]
extern crate user_lib;
static TESTS: &[&str] = &[
"ch5_stride0\0",
"ch5_stride1\0",
"ch5_stride2\0",
"ch5_stride3\0",
"ch5_stride4\0",
"ch5_stride5\0",
];
use user_lib::{spawn, waitpid, set_priority};
#[no_mangle]
pub fn main() -> i32 {
let mut pid = [0; 6];
let mut i = 0;
for test in TESTS {
pid[i] = spawn(*test);
i += 1;
}
set_priority(4);
for i in 0..6{
let mut xstate: i32 = Default::default();
let wait_pid = waitpid(pid[i] as usize, &mut xstate);
assert_eq!(pid[i], wait_pid);
}
0
}

View File

@@ -0,0 +1,43 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{get_time, set_priority};
/*
理想结果6个进程退出时输出 count 基本正比于 priority
*/
fn spin_delay() {
let mut j = true;
for _ in 0..10 {
j = !j;
}
}
// to get enough accuracy, MAX_TIME (the running time of each process) should > 1000 mseconds.
const MAX_TIME: isize = 4000;
pub fn count_during(prio: isize) -> isize {
let start_time = get_time();
let mut acc = 0;
set_priority(prio);
loop {
spin_delay();
acc += 1;
if acc % 400 == 0 {
let time = get_time() - start_time;
if time > MAX_TIME {
return acc;
}
}
}
}
#[no_mangle]
pub fn main() -> usize {
let prio = 5;
let count = count_during(prio);
println!("priority = {}, exitcode = {}, ratio = {}", prio, count, count/prio);
0
}

View File

@@ -0,0 +1,39 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{get_time, set_priority};
fn spin_delay() {
let mut j = true;
for _ in 0..10 {
j = !j;
}
}
// to get enough accuracy, MAX_TIME (the running time of each process) should > 1000 mseconds.
const MAX_TIME: isize = 4000;
fn count_during(prio: isize) -> isize {
let start_time = get_time();
let mut acc = 0;
set_priority(prio);
loop {
spin_delay();
acc += 1;
if acc % 400 == 0 {
let time = get_time() - start_time;
if time > MAX_TIME {
return acc;
}
}
}
}
#[no_mangle]
pub fn main() -> usize {
let prio = 6;
let count = count_during(prio);
println!("priority = {}, exitcode = {}, ratio = {}", prio, count, count/prio);
0
}

View File

@@ -0,0 +1,39 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{get_time, set_priority};
fn spin_delay() {
let mut j = true;
for _ in 0..10 {
j = !j;
}
}
// to get enough accuracy, MAX_TIME (the running time of each process) should > 1000 mseconds.
const MAX_TIME: isize = 4000;
fn count_during(prio: isize) -> isize {
let start_time = get_time();
let mut acc = 0;
set_priority(prio);
loop {
spin_delay();
acc += 1;
if acc % 400 == 0 {
let time = get_time() - start_time;
if time > MAX_TIME {
return acc;
}
}
}
}
#[no_mangle]
pub fn main() -> usize {
let prio = 7;
let count = count_during(prio);
println!("priority = {}, exitcode = {}, ratio = {}", prio, count, count/prio);
0
}

View File

@@ -0,0 +1,39 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{get_time, set_priority};
fn spin_delay() {
let mut j = true;
for _ in 0..10 {
j = !j;
}
}
// to get enough accuracy, MAX_TIME (the running time of each process) should > 1000 mseconds.
const MAX_TIME: isize = 4000;
fn count_during(prio: isize) -> isize {
let start_time = get_time();
let mut acc = 0;
set_priority(prio);
loop {
spin_delay();
acc += 1;
if acc % 400 == 0 {
let time = get_time() - start_time;
if time > MAX_TIME {
return acc;
}
}
}
}
#[no_mangle]
pub fn main() -> usize {
let prio = 8;
let count = count_during(prio);
println!("priority = {}, exitcode = {}, ratio = {}", prio, count, count/prio);
0
}

View File

@@ -0,0 +1,39 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{get_time, set_priority};
fn spin_delay() {
let mut j = true;
for _ in 0..10 {
j = !j;
}
}
// to get enough accuracy, MAX_TIME (the running time of each process) should > 1000 mseconds.
const MAX_TIME: isize = 4000;
fn count_during(prio: isize) -> isize {
let start_time = get_time();
let mut acc = 0;
set_priority(prio);
loop {
spin_delay();
acc += 1;
if acc % 400 == 0 {
let time = get_time() - start_time;
if time > MAX_TIME {
return acc;
}
}
}
}
#[no_mangle]
pub fn main() -> usize {
let prio = 9;
let count = count_during(prio);
println!("priority = {}, exitcode = {}, ratio = {}", prio, count, count/prio);
0
}

View File

@@ -0,0 +1,39 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{get_time, set_priority};
fn spin_delay() {
let mut j = true;
for _ in 0..10 {
j = !j;
}
}
// to get enough accuracy, MAX_TIME (the running time of each process) should > 1000 mseconds.
const MAX_TIME: isize = 4000;
fn count_during(prio: isize) -> isize {
let start_time = get_time();
let mut acc = 0;
set_priority(prio);
loop {
spin_delay();
acc += 1;
if acc % 400 == 0 {
let time = get_time() - start_time;
if time > MAX_TIME {
return acc;
}
}
}
}
#[no_mangle]
pub fn main() -> usize {
let prio = 10;
let count = count_during(prio);
println!("priority = {}, exitcode = {}, ratio = {}", prio, count, count/prio);
0
}

View File

@@ -0,0 +1,61 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
static TESTS: &[&str] = &[
"ch2b_hello_world\0",
"ch2b_power_3\0",
"ch2b_power_5\0",
"ch2b_power_7\0",
"ch3b_yield0\0",
"ch3b_yield1\0",
"ch3b_yield2\0",
"ch3b_sleep\0",
"ch3b_sleep1\0",
"ch4_mmap0\0",
"ch4_mmap1\0",
"ch4_mmap2\0",
"ch4_mmap3\0",
"ch4_unmap\0",
"ch4_unmap2\0",
"ch5_spawn0\0",
"ch5_spawn1\0",
"ch5_setprio\0",
// "ch5_stride\0",
];
static STEST: &str = "ch5_stride\0";
use user_lib::{spawn, waitpid};
/// 辅助测例,运行所有其他测例。
#[no_mangle]
pub fn main() -> i32 {
let mut pid = [0; 20];
for (i, &test) in TESTS.iter().enumerate() {
println!("Usertests: Running {}", test);
pid[i] = spawn(test);
}
let mut xstate: i32 = Default::default();
for (i, &test) in TESTS.iter().enumerate() {
let wait_pid = waitpid(pid[i] as usize, &mut xstate);
assert_eq!(pid[i], wait_pid);
println!(
"\x1b[32mUsertests: Test {} in Process {} exited with code {}\x1b[0m",
test, pid[i], xstate
);
}
println!("Usertests: Running {}", STEST);
let spid = spawn(STEST);
xstate = Default::default();
let wait_pid = waitpid(spid as usize, &mut xstate);
assert_eq!(spid, wait_pid);
println!(
"\x1b[32mUsertests: Test {} in Process {} exited with code {}\x1b[0m",
STEST, spid, xstate
);
println!("ch5 Usertests passed!");
0
}

30
user/src/bin/ch5b_exit.rs Normal file
View File

@@ -0,0 +1,30 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{exit, fork, wait, waitpid, yield_};
const MAGIC: i32 = -0x10384;
#[no_mangle]
pub fn main() -> i32 {
println!("I am the parent. Forking the child...");
let pid = fork();
if pid == 0 {
println!("I am the child.");
for _ in 0..7 {
yield_();
}
exit(MAGIC);
} else {
println!("I am parent, fork a child pid {}", pid);
}
println!("I am the parent, waiting now..");
let mut xstate: i32 = 0;
assert!(waitpid(pid as usize, &mut xstate) == pid && xstate == MAGIC);
assert!(waitpid(pid as usize, &mut xstate) < 0 && wait(&mut xstate) <= 0);
println!("waitpid {} ok.", pid);
println!("exit pass.");
0
}

View File

@@ -0,0 +1,34 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{exit, fork, wait};
const MAX_CHILD: usize = 40;
#[no_mangle]
pub fn main() -> i32 {
for i in 0..MAX_CHILD {
let pid = fork();
if pid == 0 {
println!("I am child {}", i);
exit(0);
} else {
println!("forked child pid = {}", pid);
}
assert!(pid > 0);
}
let mut exit_code: i32 = 0;
for _ in 0..MAX_CHILD {
if wait(&mut exit_code) <= 0 {
panic!("wait stopped early");
}
}
if wait(&mut exit_code) > 0 {
panic!("wait got too many");
}
println!("forktest pass.");
0
}

View File

@@ -0,0 +1,34 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{exit, fork, get_time, getpid, sleep, wait};
static NUM: usize = 30;
#[no_mangle]
pub fn main() -> i32 {
for _ in 0..NUM {
let pid = fork();
if pid == 0 {
let current_time = get_time();
let sleep_length =
(current_time as i32 as isize) * (current_time as i32 as isize) % 1000 + 1000;
println!("pid {} sleep for {} ms", getpid(), sleep_length);
sleep(sleep_length as usize);
println!("pid {} OK!", getpid());
exit(0);
}
}
let mut exit_code: i32 = 0;
for _ in 0..NUM {
assert!(wait(&mut exit_code) > 0);
assert_eq!(exit_code, 0);
}
assert!(wait(&mut exit_code) < 0);
println!("forktest2 test passed!");
0
}

View File

@@ -0,0 +1,28 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{fork, getpid, wait};
#[no_mangle]
pub fn main() -> i32 {
assert_eq!(wait(&mut 0i32), -1);
println!("sys_wait without child process test passed!");
println!("parent start, pid = {}!", getpid());
let pid = fork();
if pid == 0 {
// child process
println!("hello child process!");
100
} else {
// parent process
let mut exit_code: i32 = 0;
println!("ready waiting on parent process!");
assert_eq!(pid, wait(&mut exit_code));
assert_eq!(exit_code, 100);
println!("child process pid = {}, exit code = {}", pid, exit_code);
0
}
}

View File

@@ -0,0 +1,37 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{exit, fork, getpid, sleep, yield_};
const DEPTH: usize = 4;
fn fork_child(cur: &str, branch: char) {
let mut next = [0u8; DEPTH + 1];
let l = cur.len();
if l >= DEPTH {
return;
}
next[..l].copy_from_slice(cur.as_bytes());
next[l] = branch as u8;
if fork() == 0 {
fork_tree(core::str::from_utf8(&next[..l + 1]).unwrap());
yield_();
exit(0);
}
}
fn fork_tree(cur: &str) {
println!("pid{}: {}", getpid(), cur);
fork_child(cur, '0');
fork_child(cur, '1');
}
#[no_mangle]
pub fn main() -> i32 {
fork_tree("");
sleep(3000);
0
}

View File

@@ -0,0 +1,28 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{exec, fork, wait, yield_};
#[no_mangle]
fn main() -> i32 {
if fork() == 0 {
exec("ch5b_user_shell\0", &[0 as *const u8]);
} else {
loop {
let mut exit_code: i32 = 0;
let pid = wait(&mut exit_code);
if pid == -1 {
yield_();
continue;
}
println!(
"[initproc] Released a zombie process, pid={}, exit_code={}",
pid, exit_code,
);
}
}
0
}

View File

@@ -0,0 +1,66 @@
#![no_std]
#![no_main]
extern crate alloc;
#[macro_use]
extern crate user_lib;
const LF: u8 = 0x0au8;
const CR: u8 = 0x0du8;
const DL: u8 = 0x7fu8;
const BS: u8 = 0x08u8;
use alloc::string::String;
use user_lib::console::getchar;
use user_lib::{exec, flush, fork, waitpid};
#[no_mangle]
pub fn main() -> i32 {
println!("Rust user shell");
let mut line: String = String::new();
print!(">> ");
flush();
loop {
let c = getchar();
match c {
LF | CR => {
print!("\n");
if !line.is_empty() {
line.push('\0');
let pid = fork();
if pid == 0 {
// child process
if exec(line.as_str(), &[0 as *const u8]) == -1 {
println!("Error when executing!");
return -4;
}
unreachable!();
} else {
let mut exit_code: i32 = 0;
let exit_pid = waitpid(pid as usize, &mut exit_code);
assert_eq!(pid, exit_pid);
println!("Shell: Process {} exited with code {}", pid, exit_code);
}
line.clear();
}
print!(">> ");
flush();
}
BS | DL => {
if !line.is_empty() {
print!("{}", BS as char);
print!(" ");
print!("{}", BS as char);
flush();
line.pop();
}
}
_ => {
print!("{}", c as char);
flush();
line.push(c as char);
}
}
}
}

31
user/src/bin/ch6_file0.rs Normal file
View File

@@ -0,0 +1,31 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{close, open, read, write, OpenFlags};
/// 测试文件基本读写输出 Test file0 OK! 就算正确。
#[no_mangle]
pub fn main() -> i32 {
let test_str = "Hello, world!";
let fname = "fname\0";
let fd = open(fname, OpenFlags::CREATE | OpenFlags::WRONLY);
assert!(fd > 0);
let fd = fd as usize;
write(fd, test_str.as_bytes());
close(fd);
let fd = open(fname, OpenFlags::RDONLY);
assert!(fd > 0);
let fd = fd as usize;
let mut buffer = [0u8; 100];
let read_len = read(fd, &mut buffer) as usize;
close(fd);
assert_eq!(test_str, core::str::from_utf8(&buffer[..read_len]).unwrap(),);
println!("Test file0 OK!");
0
}

26
user/src/bin/ch6_file1.rs Normal file
View File

@@ -0,0 +1,26 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{close, fstat, open, OpenFlags, Stat, StatMode};
/// 测试 fstat输出 Test fstat OK! 就算正确。
#[no_mangle]
pub fn main() -> i32 {
let fname = "fname1\0";
let fd = open(fname, OpenFlags::CREATE | OpenFlags::WRONLY);
assert!(fd > 0);
let fd = fd as usize;
let stat: Stat = Stat::new();
let ret = fstat(fd, &stat);
assert_eq!(ret, 0);
assert_eq!(stat.mode, StatMode::FILE);
assert_eq!(stat.nlink, 1);
close(fd);
// unlink(fname);
// It's recommended to rebuild the disk image. This program will not clean the file "fname1".
println!("Test fstat OK!");
0
}

46
user/src/bin/ch6_file2.rs Normal file
View File

@@ -0,0 +1,46 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{close, fstat, link, open, read, unlink, write, OpenFlags, Stat};
/// 测试 link/unlink输出 Test link OK! 就算正确。
#[no_mangle]
pub fn main() -> i32 {
let test_str = "Hello, world!";
let fname = "fname2\0";
let (lname0, lname1, lname2) = ("linkname0\0", "linkname1\0", "linkname2\0");
let fd = open(fname, OpenFlags::CREATE | OpenFlags::WRONLY) as usize;
link(fname, lname0);
let stat = Stat::new();
fstat(fd, &stat);
assert_eq!(stat.nlink, 2);
link(fname, lname1);
link(fname, lname2);
fstat(fd, &stat);
assert_eq!(stat.nlink, 4);
write(fd, test_str.as_bytes());
close(fd);
unlink(fname);
let fd = open(lname0, OpenFlags::RDONLY) as usize;
let stat2 = Stat::new();
let mut buf = [0u8; 100];
let read_len = read(fd, &mut buf) as usize;
assert_eq!(test_str, core::str::from_utf8(&buf[..read_len]).unwrap(),);
fstat(fd, &stat2);
assert_eq!(stat2.dev, stat.dev);
assert_eq!(stat2.ino, stat.ino);
assert_eq!(stat2.nlink, 3);
unlink(lname1);
unlink(lname2);
fstat(fd, &stat2);
assert_eq!(stat2.nlink, 1);
close(fd);
unlink(lname0);
// It's Ok if you don't delete the inode and data blocks.
println!("Test link OK!");
0
}

31
user/src/bin/ch6_file3.rs Normal file
View File

@@ -0,0 +1,31 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{close, open, unlink, write, OpenFlags};
/// 测试大量 open/unlink输出 Test mass open/unlink OK! 就算正确。
#[no_mangle]
pub fn main() -> i32 {
let test_str = "some random long long long long long long long long string".repeat(50);
let fname = "fname3\0";
for i in 0..10 {
let fd = open(fname, OpenFlags::CREATE | OpenFlags::WRONLY);
if fd == -1 {
panic!("failed to crate file");
}
let fd = fd as usize;
for _ in 0..50 {
write(fd, test_str.as_bytes());
}
close(fd);
assert_eq!(unlink(fname), 0);
let fd = open(fname, OpenFlags::RDONLY);
assert!(fd < 0);
println!("test iteration {}", i)
}
println!("Test mass open/unlink OK!");
0
}

View File

@@ -0,0 +1,51 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
static TESTS: &[&str] = &[
"ch2b_hello_world\0",
"ch2b_power_3\0",
"ch2b_power_5\0",
"ch2b_power_7\0",
"ch3b_yield0\0",
"ch3b_yield1\0",
"ch3b_yield2\0",
"ch3b_sleep\0",
"ch3b_sleep1\0",
"ch4_mmap0\0",
"ch4_mmap1\0",
"ch4_mmap2\0",
"ch4_mmap3\0",
"ch4_unmap\0",
"ch4_unmap2\0",
"ch5b_forktest2\0",
"ch5_spawn0\0",
"ch5_spawn1\0",
"ch6_file0\0",
"ch6_file1\0",
"ch6_file2\0",
"ch6_file3\0",
];
use user_lib::{spawn, waitpid};
/// 辅助测例,运行所有其他测例。
#[no_mangle]
pub fn main() -> i32 {
for test in TESTS {
println!("Usertests: Running {}", test);
let pid = spawn(*test);
let mut xstate: i32 = Default::default();
let wait_pid = waitpid(pid as usize, &mut xstate);
assert_eq!(pid, wait_pid);
println!(
"\x1b[32mUsertests: Test {} in Process {} exited with code {}\x1b[0m",
test, pid, xstate
);
}
println!("ch6 Usertests passed!");
0
}

33
user/src/bin/ch6b_cat.rs Normal file
View File

@@ -0,0 +1,33 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use user_lib::{
open,
OpenFlags,
close,
read,
};
use alloc::string::String;
#[no_mangle]
pub fn main() -> i32 {
let fd = open("filea\0", OpenFlags::RDONLY);
if fd == -1 {
panic!("Error occured when opening file");
}
let fd = fd as usize;
let mut buf = [0u8; 16];
let mut s = String::new();
loop {
let size = read(fd, &mut buf) as usize;
if size == 0 { break; }
s.push_str(core::str::from_utf8(&buf[..size]).unwrap());
}
println!("{}", s);
close(fd);
0
}

View File

@@ -0,0 +1,38 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{
open,
close,
read,
write,
OpenFlags,
};
#[no_mangle]
pub fn main() -> i32 {
let test_str = "Hello, world!";
let filea = "filea\0";
let fd = open(filea, OpenFlags::CREATE | OpenFlags::WRONLY);
assert!(fd > 0);
let fd = fd as usize;
write(fd, test_str.as_bytes());
close(fd);
let fd = open(filea, OpenFlags::RDONLY);
assert!(fd > 0);
let fd = fd as usize;
let mut buffer = [0u8; 100];
let read_len = read(fd, &mut buffer) as usize;
close(fd);
assert_eq!(
test_str,
core::str::from_utf8(&buffer[..read_len]).unwrap(),
);
println!("file_test passed!");
0
}

View File

@@ -0,0 +1,28 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{exec, fork, wait, yield_};
#[no_mangle]
fn main() -> i32 {
if fork() == 0 {
exec("ch6b_user_shell\0", &[0 as *const u8]);
} else {
loop {
let mut exit_code: i32 = 0;
let pid = wait(&mut exit_code);
if pid == -1 {
yield_();
continue;
}
println!(
"[initproc] Released a zombie process, pid={}, exit_code={}",
pid, exit_code,
);
}
}
0
}

View File

@@ -0,0 +1,66 @@
#![no_std]
#![no_main]
extern crate alloc;
#[macro_use]
extern crate user_lib;
const LF: u8 = 0x0au8;
const CR: u8 = 0x0du8;
const DL: u8 = 0x7fu8;
const BS: u8 = 0x08u8;
use alloc::string::String;
use user_lib::console::getchar;
use user_lib::{exec, flush, fork, waitpid};
#[no_mangle]
pub fn main() -> i32 {
println!("Rust user shell");
let mut line: String = String::new();
print!(">> ");
flush();
loop {
let c = getchar();
match c {
LF | CR => {
print!("\n");
if !line.is_empty() {
line.push('\0');
let pid = fork();
if pid == 0 {
// child process
if exec(line.as_str(), &[0 as *const u8]) == -1 {
println!("Error when executing!");
return -4;
}
unreachable!();
} else {
let mut exit_code: i32 = 0;
let exit_pid = waitpid(pid as usize, &mut exit_code);
assert_eq!(pid, exit_pid);
println!("Shell: Process {} exited with code {}", pid, exit_code);
}
line.clear();
}
print!(">> ");
flush();
}
BS | DL => {
if !line.is_empty() {
print!("{}", BS as char);
print!(" ");
print!("{}", BS as char);
flush();
line.pop();
}
}
_ => {
print!("{}", c as char);
flush();
line.push(c as char);
}
}
}
}

View File

@@ -0,0 +1,29 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
static TESTS: &[&str] = &[
];
use user_lib::{spawn, waitpid};
/// 辅助测例,运行所有其他测例。
#[no_mangle]
pub fn main() -> i32 {
for test in TESTS {
println!("Usertests: Running {}", test);
let pid = spawn(*test);
let mut xstate: i32 = Default::default();
let wait_pid = waitpid(pid as usize, &mut xstate);
assert_eq!(pid, wait_pid);
println!(
"\x1b[32mUsertests: Test {} in Process {} exited with code {}\x1b[0m",
test, pid, xstate
);
}
println!("ch7 Usertests passed!");
0
}

34
user/src/bin/ch7b_cat.rs Normal file
View File

@@ -0,0 +1,34 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use user_lib::{
open,
OpenFlags,
close,
read,
};
use alloc::string::String;
#[no_mangle]
pub fn main(argc: usize, argv: &[&str]) -> i32 {
assert!(argc == 2);
let fd = open(argv[1], OpenFlags::RDONLY);
if fd == -1 {
panic!("Error occured when opening file");
}
let fd = fd as usize;
let mut buf = [0u8; 16];
let mut s = String::new();
loop {
let size = read(fd, &mut buf) as usize;
if size == 0 { break; }
s.push_str(core::str::from_utf8(&buf[..size]).unwrap());
}
println!("{}", s);
close(fd);
0
}

View File

@@ -0,0 +1,28 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{exec, fork, wait, yield_};
#[no_mangle]
fn main() -> i32 {
if fork() == 0 {
exec("ch7b_user_shell\0", &[0 as *const u8]);
} else {
loop {
let mut exit_code: i32 = 0;
let pid = wait(&mut exit_code);
if pid == -1 {
yield_();
continue;
}
println!(
"[initproc] Released a zombie process, pid={}, exit_code={}",
pid, exit_code,
);
}
}
0
}

View File

@@ -0,0 +1,71 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use alloc::format;
use user_lib::{close, fork, get_time, pipe, read, wait, write};
const LENGTH: usize = 3000;
#[no_mangle]
pub fn main() -> i32 {
// create pipes
// parent write to child
let mut down_pipe_fd = [0usize; 2];
// child write to parent
let mut up_pipe_fd = [0usize; 2];
pipe(&mut down_pipe_fd);
pipe(&mut up_pipe_fd);
let mut random_str = [0u8; LENGTH];
if fork() == 0 {
// close write end of down pipe
close(down_pipe_fd[1]);
// close read end of up pipe
close(up_pipe_fd[0]);
assert_eq!(read(down_pipe_fd[0], &mut random_str) as usize, LENGTH);
close(down_pipe_fd[0]);
let sum: usize = random_str.iter().map(|v| *v as usize).sum::<usize>();
println!("sum = {}(child)", sum);
let sum_str = format!("{}", sum);
write(up_pipe_fd[1], sum_str.as_bytes());
close(up_pipe_fd[1]);
println!("Child process exited!");
0
} else {
// close read end of down pipe
close(down_pipe_fd[0]);
// close write end of up pipe
close(up_pipe_fd[1]);
// generate a long random string
for ch in random_str.iter_mut().take(LENGTH) {
*ch = get_time() as u8;
}
// send it
assert_eq!(
write(down_pipe_fd[1], &random_str) as usize,
random_str.len()
);
// close write end of down pipe
close(down_pipe_fd[1]);
// calculate sum(parent)
let sum: usize = random_str.iter().map(|v| *v as usize).sum::<usize>();
println!("sum = {}(parent)", sum);
// recv sum(child)
let mut child_result = [0u8; 32];
let result_len = read(up_pipe_fd[0], &mut child_result) as usize;
close(up_pipe_fd[0]);
// check
assert_eq!(
sum,
str::parse::<usize>(core::str::from_utf8(&child_result[..result_len]).unwrap())
.unwrap()
);
let mut _unused: i32 = 0;
wait(&mut _unused);
println!("pipe_large_test passed!");
0
}
}

View File

@@ -0,0 +1,44 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{close, fork, pipe, read, wait, write};
static STR: &str = "Hello, world!";
#[no_mangle]
pub fn main() -> i32 {
// create pipe
let mut pipe_fd = [0usize; 2];
pipe(&mut pipe_fd);
// read end
assert_eq!(pipe_fd[0], 3);
// write end
assert_eq!(pipe_fd[1], 4);
if fork() == 0 {
// child process, read from parent
// close write_end
close(pipe_fd[1]);
let mut buffer = [0u8; 32];
let len_read = read(pipe_fd[0], &mut buffer) as usize;
// close read_end
close(pipe_fd[0]);
assert_eq!(core::str::from_utf8(&buffer[..len_read]).unwrap(), STR);
println!("Read OK, child process exited!");
0
} else {
// parent process, write to child
// close read end
close(pipe_fd[0]);
assert_eq!(write(pipe_fd[1], STR.as_bytes()), STR.len() as isize);
// close write end
close(pipe_fd[1]);
let mut child_exit_code: i32 = 0;
wait(&mut child_exit_code);
assert_eq!(child_exit_code, 0);
println!("pipetest passed!");
0
}
}

View File

@@ -0,0 +1,21 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{exec, fork, wait};
#[no_mangle]
pub fn main() -> i32 {
for i in 0..1000 {
if fork() == 0 {
exec("ch7b_pipe_large_test\0", &[0 as *const u8]);
} else {
let mut _unused: i32 = 0;
wait(&mut _unused);
println!("Iter {} OK.", i);
}
}
0
}

View File

@@ -0,0 +1,130 @@
#![no_std]
#![no_main]
extern crate alloc;
#[macro_use]
extern crate user_lib;
const LF: u8 = 0x0au8;
const CR: u8 = 0x0du8;
const DL: u8 = 0x7fu8;
const BS: u8 = 0x08u8;
use alloc::string::String;
use alloc::vec::Vec;
use user_lib::console::getchar;
use user_lib::{close, dup, exec, flush, fork, open, waitpid, OpenFlags};
#[no_mangle]
pub fn main() -> i32 {
println!("Rust user shell");
let mut line: String = String::new();
print!(">> ");
flush();
loop {
let c = getchar();
match c {
LF | CR => {
println!("");
if !line.is_empty() {
let args: Vec<_> = line.as_str().split(' ').collect();
let mut args_copy: Vec<String> = args
.iter()
.map(|&arg| {
let mut string = String::new();
string.push_str(arg);
string
})
.collect();
args_copy.iter_mut().for_each(|string| {
string.push('\0');
});
// redirect input
let mut input = String::new();
if let Some((idx, _)) = args_copy
.iter()
.enumerate()
.find(|(_, arg)| arg.as_str() == "<\0")
{
input = args_copy[idx + 1].clone();
args_copy.drain(idx..=idx + 1);
}
// redirect output
let mut output = String::new();
if let Some((idx, _)) = args_copy
.iter()
.enumerate()
.find(|(_, arg)| arg.as_str() == ">\0")
{
output = args_copy[idx + 1].clone();
args_copy.drain(idx..=idx + 1);
}
let mut args_addr: Vec<*const u8> =
args_copy.iter().map(|arg| arg.as_ptr()).collect();
args_addr.push(0 as *const u8);
let pid = fork();
if pid == 0 {
// input redirection
if !input.is_empty() {
let input_fd = open(input.as_str(), OpenFlags::RDONLY);
if input_fd == -1 {
println!("Error when opening file {}", input);
return -4;
}
let input_fd = input_fd as usize;
close(0);
assert_eq!(dup(input_fd), 0);
close(input_fd);
}
// output redirection
if !output.is_empty() {
let output_fd =
open(output.as_str(), OpenFlags::CREATE | OpenFlags::WRONLY);
if output_fd == -1 {
println!("Error when opening file {}", output);
return -4;
}
let output_fd = output_fd as usize;
close(1);
assert_eq!(dup(output_fd), 1);
close(output_fd);
}
// child process
if exec(args_copy[0].as_str(), args_addr.as_slice()) == -1 {
println!("Error when executing!");
return -4;
}
unreachable!();
} else {
let mut exit_code: i32 = 0;
let exit_pid = waitpid(pid as usize, &mut exit_code);
assert_eq!(pid, exit_pid);
println!("Shell: Process {} exited with code {}", pid, exit_code);
}
line.clear();
}
print!(">> ");
flush();
}
BS | DL => {
if !line.is_empty() {
print!("{}", BS as char);
print!(" ");
print!("{}", BS as char);
flush();
line.pop();
}
}
_ => {
print!("{}", c as char);
flush();
line.push(c as char);
}
}
}
}

View File

@@ -0,0 +1,17 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{getpid, yield_};
#[no_mangle]
pub fn main() -> i32 {
println!("Hello, I am process {}.", getpid());
for i in 0..5 {
yield_();
println!("Back in process {}, iteration {}.", getpid(), i);
}
println!("yield pass.");
0
}

View File

@@ -0,0 +1,22 @@
#![no_std]
#![no_main]
#![allow(clippy::println_empty_string)]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use user_lib::{enable_deadlock_detect, mutex_blocking_create, mutex_lock, mutex_unlock};
// 理想结果:检测到死锁
#[no_mangle]
pub fn main() -> i32 {
enable_deadlock_detect(true);
let mid = mutex_blocking_create() as usize;
assert_eq!(mutex_lock(mid), 0);
assert_eq!(mutex_lock(mid), -0xdead);
mutex_unlock(mid);
println!("deadlock test mutex 1 OK!");
0
}

View File

@@ -0,0 +1,102 @@
#![no_std]
#![no_main]
#![allow(clippy::println_empty_string)]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use user_lib::{
enable_deadlock_detect, exit, semaphore_create, semaphore_down, semaphore_up, sleep,
};
use user_lib::{gettid, thread_create, waittid};
// sem 0: used to sync child thread with main
// sem 1-3: representing some kind of resource
// 理想结果:检测到死锁,至少有一个子线程返回值不为 0
const SEM_BARRIER: usize = 0;
const THREAD_N: usize = 3;
const RES_TYPE: usize = 3;
const RES_NUM: [usize; RES_TYPE] = [1, 2, 1];
const REQUEST: [Option<usize>; THREAD_N] = [Some(1), Some(3), Some(2)];
fn try_sem_down(sem_id: usize) {
if semaphore_down(sem_id) == -0xdead {
sem_dealloc(gettid() as usize);
println!("Deadlock detected. Test 08_sem1 failed!");
exit(-1);
}
}
fn sem_alloc(tid: usize) {
match tid {
1 => assert_eq!(semaphore_down(2), 0),
2 => {
assert_eq!(semaphore_down(1), 0);
assert_eq!(semaphore_down(2), 0);
}
3 => assert_eq!(semaphore_down(3), 0),
_ => exit(1),
}
semaphore_down(SEM_BARRIER);
}
fn sem_dealloc(tid: usize) {
semaphore_up(SEM_BARRIER);
match tid {
1 => semaphore_up(2),
2 => {
semaphore_up(1);
semaphore_up(2);
}
3 => semaphore_up(3),
_ => exit(1),
}
}
fn deadlock_test() {
let tid = gettid() as usize;
println!("thread {} running", tid);
sem_alloc(tid);
if let Some(sem_id) = REQUEST[tid - 1] {
try_sem_down(sem_id);
semaphore_up(sem_id);
}
sem_dealloc(tid);
println!("thread {} exited", tid);
exit(0);
}
#[no_mangle]
pub fn main() -> i32 {
enable_deadlock_detect(true);
assert_eq!(semaphore_create(THREAD_N) as usize, SEM_BARRIER);
for _ in 0..THREAD_N {
semaphore_down(SEM_BARRIER);
}
for n in RES_NUM {
semaphore_create(n);
}
let mut tids = [0; THREAD_N];
for i in 0..THREAD_N {
tids[i] = thread_create(deadlock_test as usize, 0) as usize;
}
sleep(500);
for _ in 0..THREAD_N {
semaphore_up(SEM_BARRIER);
}
let mut failed = 0;
for tid in tids {
if waittid(tid) != 0 {
failed += 1;
}
}
assert!(failed > 0);
println!("deadlock test semaphore 1 OK!");
0
}

View File

@@ -0,0 +1,76 @@
#![no_std]
#![no_main]
#![allow(clippy::println_empty_string)]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use user_lib::{
enable_deadlock_detect, exit, semaphore_create, semaphore_down, semaphore_up, sleep,
};
use user_lib::{gettid, thread_create, waittid};
// sem 0: used to sync child thread with main
// sem 1-3: representing some kind of resource
// 理想结果:未检测到死锁,子线程返回值均为 0
const THREAD_N: usize = 4;
const RES_TYPE: usize = 2;
const RES_NUM: [usize; RES_TYPE] = [2, 2];
const ALLOC: [usize; THREAD_N] = [2, 1, 1, 2];
const REQUEST: [Option<usize>; THREAD_N] = [Some(1), None, Some(2), None];
fn try_sem_down(sem_id: usize) {
if semaphore_down(sem_id) == -0xdead {
semaphore_up(ALLOC[(gettid() - 1) as usize]);
exit(-1);
}
}
fn deadlock_test() {
let id = (gettid() - 1) as usize;
assert_eq!(semaphore_down(ALLOC[id]), 0);
semaphore_down(0);
if let Some(sem_id) = REQUEST[id] {
try_sem_down(sem_id);
semaphore_up(sem_id);
}
semaphore_up(ALLOC[id]);
exit(0);
}
#[no_mangle]
pub fn main() -> i32 {
enable_deadlock_detect(true);
semaphore_create(THREAD_N);
for _ in 0..THREAD_N {
semaphore_down(0);
}
for n in RES_NUM {
semaphore_create(n);
}
let mut tids = [0; THREAD_N];
for i in 0..THREAD_N {
tids[i] = thread_create(deadlock_test as usize, 0) as usize;
}
sleep(1000);
for _ in 0..THREAD_N {
semaphore_up(0);
}
let mut failed = 0;
for tid in tids {
if waittid(tid) != 0 {
failed += 1;
}
}
assert_eq!(failed, 0);
println!("deadlock test semaphore 2 OK!");
0
}

View File

@@ -0,0 +1,60 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
const TESTS: &[&str] = &[
"ch2b_hello_world\0",
"ch2b_power_3\0",
"ch2b_power_5\0",
"ch2b_power_7\0",
"ch3b_sleep\0",
"ch3b_sleep1\0",
"ch3b_yield0\0",
"ch3b_yield1\0",
"ch3b_yield2\0",
"ch5b_forktest2\0",
"ch6b_filetest_simple\0",
"ch7b_pipetest\0",
"ch8_deadlock_mutex1\0",
"ch8_deadlock_sem1\0",
"ch8_deadlock_sem2\0",
"ch8b_mpsc_sem\0",
"ch8b_phil_din_mutex\0",
"ch8b_race_adder_mutex_spin\0",
"ch8b_sync_sem\0",
"ch8b_test_condvar\0",
"ch8b_threads\0",
"ch8b_threads_arg\0",
];
const TEST_NUM: usize = TESTS.len();
use user_lib::{exec, fork, waitpid};
#[no_mangle]
pub fn main() -> i32 {
let mut pids = [0; TEST_NUM];
for (i, &test) in TESTS.iter().enumerate() {
println!("Usertests: Running {}", test);
let pid = fork();
if pid == 0 {
exec(&*test, &[core::ptr::null::<u8>()]);
panic!("unreachable!");
} else {
pids[i] = pid;
}
}
let mut xstate: i32 = Default::default();
for (i, &test) in TESTS.iter().enumerate() {
let wait_pid = waitpid(pids[i] as usize, &mut xstate);
assert_eq!(pids[i], wait_pid);
println!(
"\x1b[32mUsertests: Test {} in Process {} exited with code {}\x1b[0m",
test, pids[i], xstate
);
}
println!("ch8 Usertests passed!");
0
}

View File

@@ -0,0 +1,28 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{exec, fork, wait, yield_};
#[no_mangle]
fn main() -> i32 {
if fork() == 0 {
exec("ch7b_user_shell\0", &[core::ptr::null::<u8>()]);
} else {
loop {
let mut exit_code: i32 = 0;
let pid = wait(&mut exit_code);
if pid == -1 {
yield_();
continue;
}
println!(
"[initproc] Released a zombie process, pid={}, exit_code={}",
pid, exit_code,
);
}
}
0
}

View File

@@ -0,0 +1,73 @@
#![no_std]
#![no_main]
#![allow(clippy::println_empty_string)]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use alloc::vec::Vec;
use user_lib::exit;
use user_lib::{semaphore_create, semaphore_down, semaphore_up};
use user_lib::{thread_create, waittid};
const SEM_MUTEX: usize = 0;
const SEM_EMPTY: usize = 1;
const SEM_EXISTED: usize = 2;
const BUFFER_SIZE: usize = 8;
static mut BUFFER: [usize; BUFFER_SIZE] = [0; BUFFER_SIZE];
static mut FRONT: usize = 0;
static mut TAIL: usize = 0;
const PRODUCER_COUNT: usize = 4;
const NUMBER_PER_PRODUCER: usize = 100;
unsafe fn producer(id: *const usize) -> ! {
let id = *id;
for _ in 0..NUMBER_PER_PRODUCER {
semaphore_down(SEM_EMPTY);
semaphore_down(SEM_MUTEX);
BUFFER[FRONT] = id;
FRONT = (FRONT + 1) % BUFFER_SIZE;
semaphore_up(SEM_MUTEX);
semaphore_up(SEM_EXISTED);
}
exit(0)
}
unsafe fn consumer() -> ! {
for _ in 0..PRODUCER_COUNT * NUMBER_PER_PRODUCER {
semaphore_down(SEM_EXISTED);
semaphore_down(SEM_MUTEX);
print!("{} ", BUFFER[TAIL]);
TAIL = (TAIL + 1) % BUFFER_SIZE;
semaphore_up(SEM_MUTEX);
semaphore_up(SEM_EMPTY);
}
println!("");
exit(0)
}
#[no_mangle]
pub fn main() -> i32 {
// create semaphores
assert_eq!(semaphore_create(1) as usize, SEM_MUTEX);
assert_eq!(semaphore_create(BUFFER_SIZE) as usize, SEM_EMPTY);
assert_eq!(semaphore_create(0) as usize, SEM_EXISTED);
// create threads
let ids: Vec<_> = (0..PRODUCER_COUNT).collect();
let mut threads = Vec::new();
for i in 0..PRODUCER_COUNT {
threads.push(thread_create(
producer as usize,
&ids.as_slice()[i] as *const _ as usize,
));
}
threads.push(thread_create(consumer as usize, 0));
// wait for all threads to complete
for thread in threads.iter() {
waittid(*thread as usize);
}
println!("mpsc_sem passed!");
0
}

View File

@@ -0,0 +1,109 @@
#![no_std]
#![no_main]
#![allow(clippy::println_empty_string)]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use alloc::vec::Vec;
use user_lib::{exit, get_time, sleep_blocking};
use user_lib::{mutex_blocking_create, mutex_lock, mutex_unlock};
use user_lib::{thread_create, waittid};
const N: usize = 5;
const ROUND: usize = 4;
// A round: think -> wait for forks -> eat
const GRAPH_SCALE: usize = 10;
fn get_time_u() -> usize {
get_time() as usize
}
// Time unit: ms
const ARR: [[usize; ROUND * 2]; N] = [
[70, 80, 100, 40, 50, 60, 20, 40],
[30, 60, 20, 70, 100, 10, 30, 60],
[50, 20, 90, 20, 40, 60, 120, 40],
[50, 100, 60, 50, 80, 60, 20, 90],
[60, 10, 60, 60, 20, 50, 60, 20],
];
static mut THINK: [[usize; ROUND * 2]; N] = [[0; ROUND * 2]; N];
static mut EAT: [[usize; ROUND * 2]; N] = [[0; ROUND * 2]; N];
fn philosopher_dining_problem(id: *const usize) {
let id = unsafe { *id };
let left = id;
let right = if id == N - 1 { 0 } else { id + 1 };
let min = if left < right { left } else { right };
let max = left + right - min;
for round in 0..ROUND {
// thinking
unsafe {
THINK[id][2 * round] = get_time_u();
}
sleep_blocking(ARR[id][2 * round]);
unsafe {
THINK[id][2 * round + 1] = get_time_u();
}
// wait for forks
mutex_lock(min);
mutex_lock(max);
// eating
unsafe {
EAT[id][2 * round] = get_time_u();
}
sleep_blocking(ARR[id][2 * round + 1]);
unsafe {
EAT[id][2 * round + 1] = get_time_u();
}
mutex_unlock(max);
mutex_unlock(min);
}
exit(0)
}
#[no_mangle]
pub fn main() -> i32 {
println!("Here comes {} philosophers!", N);
let mut v = Vec::new();
let ids: Vec<_> = (0..N).collect();
let start = get_time_u();
for i in 0..N {
assert_eq!(mutex_blocking_create(), i as isize);
v.push(thread_create(
philosopher_dining_problem as usize,
&ids.as_slice()[i] as *const _ as usize,
));
}
for tid in v.iter() {
waittid(*tid as usize);
}
let time_cost = get_time_u() - start;
println!("time cost = {}", time_cost);
println!("'-' -> THINKING; 'x' -> EATING; ' ' -> WAITING ");
for id in (0..N).into_iter().chain(0..=0) {
print!("#{}:", id);
for j in 0..time_cost / GRAPH_SCALE {
let current_time = j * GRAPH_SCALE + start;
if (0..ROUND).any(|round| unsafe {
let start_thinking = THINK[id][2 * round];
let end_thinking = THINK[id][2 * round + 1];
start_thinking <= current_time && current_time <= end_thinking
}) {
print!("-");
} else if (0..ROUND).any(|round| unsafe {
let start_eating = EAT[id][2 * round];
let end_eating = EAT[id][2 * round + 1];
start_eating <= current_time && current_time <= end_eating
}) {
print!("x");
} else {
print!(" ");
};
}
println!("");
}
println!("philosopher dining problem with mutex test passed!");
0
}

View File

@@ -0,0 +1,43 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use alloc::vec::Vec;
use user_lib::{exit, get_time, thread_create, waittid};
static mut A: usize = 0;
const PER_THREAD: usize = 1000;
const THREAD_COUNT: usize = 16;
unsafe fn f() -> ! {
let mut t = 2usize;
for _ in 0..PER_THREAD {
let a = &mut A as *mut usize;
let cur = a.read_volatile();
for _ in 0..500 {
t = t * t % 10007;
}
a.write_volatile(cur + 1);
}
exit(t as i32)
}
#[no_mangle]
pub fn main() -> i32 {
let start = get_time();
let mut v = Vec::new();
for _ in 0..THREAD_COUNT {
v.push(thread_create(f as usize, 0) as usize);
}
let mut time_cost = Vec::new();
for tid in v.iter() {
time_cost.push(waittid(*tid));
}
println!("time cost is {}ms", get_time() - start);
assert_eq!(unsafe { A }, PER_THREAD * THREAD_COUNT);
println!("race adder test passed!");
0
}

View File

@@ -0,0 +1,52 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicBool, Ordering};
use user_lib::{exit, get_time, thread_create, waittid, yield_};
static mut A: usize = 0;
static OCCUPIED: AtomicBool = AtomicBool::new(false);
const PER_THREAD: usize = 1000;
const THREAD_COUNT: usize = 16;
unsafe fn f() -> ! {
let mut t = 2usize;
for _ in 0..PER_THREAD {
while OCCUPIED
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_err()
{
yield_();
}
let a = &mut A as *mut usize;
let cur = a.read_volatile();
for _ in 0..500 {
t = t * t % 10007;
}
a.write_volatile(cur + 1);
OCCUPIED.store(false, Ordering::Relaxed);
}
exit(t as i32)
}
#[no_mangle]
pub fn main() -> i32 {
let start = get_time();
let mut v = Vec::new();
for _ in 0..THREAD_COUNT {
v.push(thread_create(f as usize, 0) as usize);
}
let mut time_cost = Vec::new();
for tid in v.iter() {
time_cost.push(waittid(*tid));
}
println!("time cost is {}ms", get_time() - start);
assert_eq!(unsafe { A }, PER_THREAD * THREAD_COUNT);
println!("race adder using atomic test passed!");
0
}

View File

@@ -0,0 +1,52 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use alloc::vec::Vec;
use user_lib::{exit, get_time, thread_create, waittid, yield_};
static mut A: usize = 0;
static mut OCCUPIED: bool = false;
const PER_THREAD: usize = 1000;
const THREAD_COUNT: usize = 16;
unsafe fn f() -> ! {
let mut t = 2usize;
for _ in 0..PER_THREAD {
while OCCUPIED {
yield_();
}
OCCUPIED = true;
// enter critical section
let a = &mut A as *mut usize;
let cur = a.read_volatile();
for _ in 0..500 {
t = t * t % 10007;
}
a.write_volatile(cur + 1);
// exit critical section
OCCUPIED = false;
}
exit(t as i32)
}
#[no_mangle]
pub fn main() -> i32 {
let start = get_time();
let mut v = Vec::new();
for _ in 0..THREAD_COUNT {
v.push(thread_create(f as usize, 0) as usize);
}
let mut time_cost = Vec::new();
for tid in v.iter() {
time_cost.push(waittid(*tid));
}
println!("time cost is {}ms", get_time() - start);
assert_eq!(unsafe { A }, PER_THREAD * THREAD_COUNT);
println!("race adder using loop test passed!");
0
}

View File

@@ -0,0 +1,47 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use alloc::vec::Vec;
use user_lib::{exit, get_time, thread_create, waittid};
use user_lib::{mutex_create, mutex_lock, mutex_unlock};
static mut A: usize = 0;
const PER_THREAD: usize = 1000;
const THREAD_COUNT: usize = 16;
unsafe fn f() -> ! {
let mut t = 2usize;
for _ in 0..PER_THREAD {
mutex_lock(0);
let a = &mut A as *mut usize;
let cur = a.read_volatile();
for _ in 0..500 {
t = t * t % 10007;
}
a.write_volatile(cur + 1);
mutex_unlock(0);
}
exit(t as i32)
}
#[no_mangle]
pub fn main() -> i32 {
let start = get_time();
assert_eq!(mutex_create(), 0);
let mut v = Vec::new();
for _ in 0..THREAD_COUNT {
v.push(thread_create(f as usize, 0) as usize);
}
let mut time_cost = Vec::new();
for tid in v.iter() {
time_cost.push(waittid(*tid));
}
println!("time cost is {}ms", get_time() - start);
assert_eq!(unsafe { A }, PER_THREAD * THREAD_COUNT);
println!("race adder using spin mutex test passed!");
0
}

View File

@@ -0,0 +1,346 @@
// we porting below codes to Rcore Tutorial v3
// https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/
// https://github.com/cfsamson/example-greenthreads
#![no_std]
#![no_main]
#![feature(naked_functions)]
extern crate alloc;
#[macro_use]
extern crate user_lib;
use alloc::vec;
use alloc::vec::Vec;
use core::arch::asm;
use user_lib::exit;
// In our simple example we set most constraints here.
const DEFAULT_STACK_SIZE: usize = 4096; //128 got SEGFAULT, 256(1024, 4096) got right results.
const MAX_TASKS: usize = 5;
static mut RUNTIME: usize = 0;
pub struct Runtime {
tasks: Vec<Task>,
current: usize,
}
#[derive(PartialEq, Eq, Debug)]
enum State {
Available,
Running,
Ready,
}
struct Task {
id: usize,
stack: Vec<u8>,
ctx: TaskContext,
state: State,
}
#[derive(Debug, Default)]
#[repr(C)] // not strictly needed but Rust ABI is not guaranteed to be stable
pub struct TaskContext {
// 15 u64
x1: u64, //ra: return addres
x2: u64, //sp
x8: u64, //s0,fp
x9: u64, //s1
x18: u64, //x18-27: s2-11
x19: u64,
x20: u64,
x21: u64,
x22: u64,
x23: u64,
x24: u64,
x25: u64,
x26: u64,
x27: u64,
nx1: u64, //new return addres
}
impl Task {
fn new(id: usize) -> Self {
// We initialize each task here and allocate the stack. This is not neccesary,
// we can allocate memory for it later, but it keeps complexity down and lets us focus on more interesting parts
// to do it here. The important part is that once allocated it MUST NOT move in memory.
Task {
id,
stack: vec![0_u8; DEFAULT_STACK_SIZE],
ctx: TaskContext::default(),
state: State::Available,
}
}
}
impl Runtime {
pub fn new() -> Self {
// This will be our base task, which will be initialized in the `running` state
let base_task = Task {
id: 0,
stack: vec![0_u8; DEFAULT_STACK_SIZE],
ctx: TaskContext::default(),
state: State::Running,
};
// We initialize the rest of our tasks.
let mut tasks = vec![base_task];
let mut available_tasks: Vec<Task> = (1..MAX_TASKS).map(|i| Task::new(i)).collect();
tasks.append(&mut available_tasks);
Runtime { tasks, current: 0 }
}
/// This is cheating a bit, but we need a pointer to our Runtime stored so we can call yield on it even if
/// we don't have a reference to it.
pub fn init(&self) {
unsafe {
let r_ptr: *const Runtime = self;
RUNTIME = r_ptr as usize;
}
}
/// This is where we start running our runtime. If it is our base task, we call yield until
/// it returns false (which means that there are no tasks scheduled) and we are done.
pub fn run(&mut self) {
while self.t_yield() {}
println!("All tasks finished!");
}
/// This is our return function. The only place we use this is in our `guard` function.
/// If the current task is not our base task we set its state to Available. It means
/// we're finished with it. Then we yield which will schedule a new task to be run.
fn t_return(&mut self) {
if self.current != 0 {
self.tasks[self.current].state = State::Available;
self.t_yield();
}
}
/// This is the heart of our runtime. Here we go through all tasks and see if anyone is in the `Ready` state.
/// If no task is `Ready` we're all done. This is an extremely simple scheduler using only a round-robin algorithm.
///
/// If we find a task that's ready to be run we change the state of the current task from `Running` to `Ready`.
/// Then we call switch which will save the current context (the old context) and load the new context
/// into the CPU which then resumes based on the context it was just passed.
///
/// NOITCE: if we comment below `#[inline(never)]`, we can not get the corrent running result
#[inline(never)]
fn t_yield(&mut self) -> bool {
let mut pos = self.current;
while self.tasks[pos].state != State::Ready {
pos += 1;
if pos == self.tasks.len() {
pos = 0;
}
if pos == self.current {
return false;
}
}
if self.tasks[self.current].state != State::Available {
self.tasks[self.current].state = State::Ready;
}
self.tasks[pos].state = State::Running;
let old_pos = self.current;
self.current = pos;
unsafe {
switch(&mut self.tasks[old_pos].ctx, &self.tasks[pos].ctx);
}
// NOTE: this might look strange and it is. Normally we would just mark this as `unreachable!()` but our compiler
// is too smart for it's own good so it optimized our code away on release builds. Curiously this happens on windows
// and not on linux. This is a common problem in tests so Rust has a `black_box` function in the `test` crate that
// will "pretend" to use a value we give it to prevent the compiler from eliminating code. I'll just do this instead,
// this code will never be run anyways and if it did it would always be `true`.
self.tasks.len() > 0
}
/// While `yield` is the logically interesting function I think this the technically most interesting.
///
/// When we spawn a new task we first check if there are any available tasks (tasks in `Parked` state).
/// If we run out of tasks we panic in this scenario but there are several (better) ways to handle that.
/// We keep things simple for now.
///
/// When we find an available task we get the stack length and a pointer to our u8 bytearray.
///
/// The next part we have to use some unsafe functions. First we write an address to our `guard` function
/// that will be called if the function we provide returns. Then we set the address to the function we
/// pass inn.
///
/// Third, we set the value of `sp` which is the stack pointer to the address of our provided function so we start
/// executing that first when we are scheuled to run.
///
/// Lastly we set the state as `Ready` which means we have work to do and is ready to do it.
pub fn spawn(&mut self, f: fn()) {
let available = self
.tasks
.iter_mut()
.find(|t| t.state == State::Available)
.expect("no available task.");
let size = available.stack.len();
unsafe {
let s_ptr = available.stack.as_mut_ptr().offset(size as isize);
// make sure our stack itself is 8 byte aligned - it will always
// offset to a lower memory address. Since we know we're at the "high"
// memory address of our allocated space, we know that offsetting to
// a lower one will be a valid address (given that we actually allocated)
// enough space to actually get an aligned pointer in the first place).
let s_ptr = (s_ptr as usize & !7) as *mut u8;
available.ctx.x1 = guard as u64; //ctx.x1 is old return address
available.ctx.nx1 = f as u64; //ctx.nx2 is new return address
available.ctx.x2 = s_ptr.offset(-32) as u64; //cxt.x2 is sp
}
available.state = State::Ready;
}
}
/// This is our guard function that we place on top of the stack. All this function does is set the
/// state of our current task and then `yield` which will then schedule a new task to be run.
fn guard() {
unsafe {
let rt_ptr = RUNTIME as *mut Runtime;
(*rt_ptr).t_return();
};
}
/// We know that Runtime is alive the length of the program and that we only access from one core
/// (so no datarace). We yield execution of the current task by dereferencing a pointer to our
/// Runtime and then calling `t_yield`
pub fn yield_task() {
unsafe {
let rt_ptr = RUNTIME as *mut Runtime;
(*rt_ptr).t_yield();
};
}
/// So here is our inline Assembly. As you remember from our first example this is just a bit more elaborate where we first
/// read out the values of all the registers we need and then sets all the register values to the register values we
/// saved when we suspended exceution on the "new" task.
///
/// This is essentially all we need to do to save and resume execution.
///
/// Some details about inline assembly.
///
/// The assembly commands in the string literal is called the assemblt template. It is preceeded by
/// zero or up to four segments indicated by ":":
///
/// - First ":" we have our output parameters, this parameters that this function will return.
/// - Second ":" we have the input parameters which is our contexts. We only read from the "new" context
/// but we modify the "old" context saving our registers there (see volatile option below)
/// - Third ":" This our clobber list, this is information to the compiler that these registers can't be used freely
/// - Fourth ":" This is options we can pass inn, Rust has 3: "alignstack", "volatile" and "intel"
///
/// For this to work on windows we need to use "alignstack" where the compiler adds the neccesary padding to
/// make sure our stack is aligned. Since we modify one of our inputs, our assembly has "side effects"
/// therefore we should use the `volatile` option. I **think** this is actually set for us by default
/// when there are no output parameters given (my own assumption after going through the source code)
/// for the `asm` macro, but we should make it explicit anyway.
///
/// One last important part (it will not work without this) is the #[naked] attribute. Basically this lets us have full
/// control over the stack layout since normal functions has a prologue-and epilogue added by the
/// compiler that will cause trouble for us. We avoid this by marking the funtion as "Naked".
/// For this to work on `release` builds we also need to use the `#[inline(never)] attribute or else
/// the compiler decides to inline this function (curiously this currently only happens on Windows).
/// If the function is inlined we get a curious runtime error where it fails when switching back
/// to as saved context and in general our assembly will not work as expected.
///
/// see: https://github.com/rust-lang/rfcs/blob/master/text/1201-naked-fns.md
/// see: https://doc.rust-lang.org/nightly/reference/inline-assembly.html
/// see: https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html
#[naked]
#[no_mangle]
unsafe fn switch(old: *mut TaskContext, new: *const TaskContext) {
// a0: _old, a1: _new
asm!(
"
sd x1, 0x00(a0)
sd x2, 0x08(a0)
sd x8, 0x10(a0)
sd x9, 0x18(a0)
sd x18, 0x20(a0)
sd x19, 0x28(a0)
sd x20, 0x30(a0)
sd x21, 0x38(a0)
sd x22, 0x40(a0)
sd x23, 0x48(a0)
sd x24, 0x50(a0)
sd x25, 0x58(a0)
sd x26, 0x60(a0)
sd x27, 0x68(a0)
sd x1, 0x70(a0)
ld x1, 0x00(a1)
ld x2, 0x08(a1)
ld x8, 0x10(a1)
ld x9, 0x18(a1)
ld x18, 0x20(a1)
ld x19, 0x28(a1)
ld x20, 0x30(a1)
ld x21, 0x38(a1)
ld x22, 0x40(a1)
ld x23, 0x48(a1)
ld x24, 0x50(a1)
ld x25, 0x58(a1)
ld x26, 0x60(a1)
ld x27, 0x68(a1)
ld t0, 0x70(a1)
jr t0
",
options(noreturn)
);
}
#[no_mangle]
pub fn main() {
println!("stackful_coroutine begin...");
println!("TASK 0(Runtime) STARTING");
let mut runtime = Runtime::new();
runtime.init();
runtime.spawn(|| {
println!("TASK 1 STARTING");
let id = 1;
for i in 0..4 {
println!("task: {} counter: {}", id, i);
yield_task();
}
println!("TASK 1 FINISHED");
});
runtime.spawn(|| {
println!("TASK 2 STARTING");
let id = 2;
for i in 0..8 {
println!("task: {} counter: {}", id, i);
yield_task();
}
println!("TASK 2 FINISHED");
});
runtime.spawn(|| {
println!("TASK 3 STARTING");
let id = 3;
for i in 0..12 {
println!("task: {} counter: {}", id, i);
yield_task();
}
println!("TASK 3 FINISHED");
});
runtime.spawn(|| {
println!("TASK 4 STARTING");
let id = 4;
for i in 0..16 {
println!("task: {} counter: {}", id, i);
yield_task();
}
println!("TASK 4 FINISHED");
});
runtime.run();
println!("stackful_coroutine PASSED");
exit(0);
}

View File

@@ -0,0 +1,129 @@
// https://blog.aloni.org/posts/a-stack-less-rust-coroutine-100-loc/
// https://github.com/chyyuu/example-coroutine-and-thread/tree/stackless-coroutine-x86
#![no_std]
#![no_main]
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
use core::task::{RawWaker, RawWakerVTable, Waker};
extern crate alloc;
use alloc::collections::VecDeque;
use alloc::boxed::Box;
#[macro_use]
extern crate user_lib;
enum State {
Halted,
Running,
}
struct Task {
state: State,
}
impl Task {
fn waiter<'a>(&'a mut self) -> Waiter<'a> {
Waiter { task: self }
}
}
struct Waiter<'a> {
task: &'a mut Task,
}
impl<'a> Future for Waiter<'a> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
match self.task.state {
State::Halted => {
self.task.state = State::Running;
Poll::Ready(())
}
State::Running => {
self.task.state = State::Halted;
Poll::Pending
}
}
}
}
struct Executor {
tasks: VecDeque<Pin<Box<dyn Future<Output = ()>>>>,
}
impl Executor {
fn new() -> Self {
Executor {
tasks: VecDeque::new(),
}
}
fn push<C, F>(&mut self, closure: C)
where
F: Future<Output = ()> + 'static,
C: FnOnce(Task) -> F,
{
let task = Task {
state: State::Running,
};
self.tasks.push_back(Box::pin(closure(task)));
}
fn run(&mut self) {
let waker = create_waker();
let mut context = Context::from_waker(&waker);
while let Some(mut task) = self.tasks.pop_front() {
match task.as_mut().poll(&mut context) {
Poll::Pending => {
self.tasks.push_back(task);
}
Poll::Ready(()) => {}
}
}
}
}
pub fn create_waker() -> Waker {
// Safety: The waker points to a vtable with functions that do nothing. Doing
// nothing is memory-safe.
unsafe { Waker::from_raw(RAW_WAKER) }
}
const RAW_WAKER: RawWaker = RawWaker::new(core::ptr::null(), &VTABLE);
const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
unsafe fn clone(_: *const ()) -> RawWaker {
RAW_WAKER
}
unsafe fn wake(_: *const ()) {}
unsafe fn wake_by_ref(_: *const ()) {}
unsafe fn drop(_: *const ()) {}
#[no_mangle]
pub fn main() -> i32 {
println!("stackless coroutine Begin..");
let mut exec = Executor::new();
println!(" Create futures");
for instance in 1..=3 {
exec.push(move |mut task| async move {
println!(" Task {}: begin state", instance);
task.waiter().await;
println!(" Task {}: next state", instance);
task.waiter().await;
println!(" Task {}: end state", instance);
});
}
println!(" Running");
exec.run();
println!(" Done");
println!("stackless coroutine PASSED");
0
}

View File

@@ -0,0 +1,45 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use alloc::vec;
use user_lib::exit;
use user_lib::{semaphore_create, semaphore_down, semaphore_up};
use user_lib::{sleep_blocking, thread_create, waittid};
const SEM_SYNC: usize = 0;
unsafe fn first() -> ! {
sleep_blocking(10);
println!("First work and wakeup Second");
semaphore_up(SEM_SYNC);
exit(0)
}
unsafe fn second() -> ! {
println!("Second want to continue,but need to wait first");
semaphore_down(SEM_SYNC);
println!("Second can work now");
exit(0)
}
#[no_mangle]
pub fn main() -> i32 {
// create semaphores
assert_eq!(semaphore_create(0) as usize, SEM_SYNC);
// create threads
let threads = vec![
thread_create(first as usize, 0),
thread_create(second as usize, 0),
];
// wait for all threads to complete
for thread in threads.iter() {
waittid(*thread as usize);
}
println!("sync_sem passed!");
0
}

View File

@@ -0,0 +1,59 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use alloc::vec;
use user_lib::exit;
use user_lib::{
condvar_create, condvar_signal, condvar_wait, mutex_blocking_create, mutex_lock, mutex_unlock,
};
use user_lib::{sleep_blocking, thread_create, waittid};
static mut A: usize = 0;
const CONDVAR_ID: usize = 0;
const MUTEX_ID: usize = 0;
unsafe fn first() -> ! {
sleep_blocking(10);
println!("First work, Change A --> 1 and wakeup Second");
mutex_lock(MUTEX_ID);
A = 1;
condvar_signal(CONDVAR_ID);
mutex_unlock(MUTEX_ID);
exit(0)
}
unsafe fn second() -> ! {
println!("Second want to continue,but need to wait A=1");
mutex_lock(MUTEX_ID);
while A == 0 {
println!("Second: A is {}", A);
condvar_wait(CONDVAR_ID, MUTEX_ID);
}
mutex_unlock(MUTEX_ID);
println!("A is {}, Second can work now", A);
exit(0)
}
#[no_mangle]
pub fn main() -> i32 {
// create condvar & mutex
assert_eq!(condvar_create() as usize, CONDVAR_ID);
assert_eq!(mutex_blocking_create() as usize, MUTEX_ID);
// create threads
let threads = vec![
thread_create(first as usize, 0),
thread_create(second as usize, 0),
];
// wait for all threads to complete
for thread in threads.iter() {
waittid(*thread as usize);
}
println!("test_condvar passed!");
0
}

View File

@@ -0,0 +1,62 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use alloc::vec;
use user_lib::{exit, thread_create, waittid};
pub fn thread_a() -> ! {
let mut t = 2i32;
for _ in 0..1000 {
print!("a");
for __ in 0..5000 {
t = t * t % 10007;
}
}
println!("{}", t);
exit(1)
}
pub fn thread_b() -> ! {
let mut t = 2i32;
for _ in 0..1000 {
print!("b");
for __ in 0..5000 {
t = t * t % 10007;
}
}
println!("{}", t);
exit(2)
}
pub fn thread_c() -> ! {
let mut t = 2i32;
for _ in 0..1000 {
print!("c");
for __ in 0..5000 {
t = t * t % 10007;
}
}
println!("{}", t);
exit(3)
}
#[no_mangle]
pub fn main() -> i32 {
let v = vec![
thread_create(thread_a as usize, 0),
thread_create(thread_b as usize, 0),
thread_create(thread_c as usize, 0),
];
for tid in v.iter() {
let exit_code = waittid(*tid as usize);
println!("thread#{} exited with code {}", tid, exit_code);
assert_eq!(*tid, exit_code);
}
println!("main thread exited.");
println!("threads test passed!");
0
}

View File

@@ -0,0 +1,45 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
extern crate alloc;
use alloc::vec::Vec;
use user_lib::{exit, thread_create, waittid};
struct Argument {
pub ch: char,
pub rc: i32,
}
fn thread_print(arg: *const Argument) -> ! {
let arg = unsafe { &*arg };
for _ in 0..1000 {
print!("{}", arg.ch);
}
exit(arg.rc)
}
#[no_mangle]
pub fn main() -> i32 {
let mut v = Vec::new();
let args = [
Argument { ch: 'a', rc: 1 },
Argument { ch: 'b', rc: 2 },
Argument { ch: 'c', rc: 3 },
];
for arg in args.iter() {
v.push(thread_create(
thread_print as usize,
arg as *const _ as usize,
));
}
for tid in v.iter() {
let exit_code = waittid(*tid as usize);
println!("thread#{} exited with code {}", tid, exit_code);
}
println!("main thread exited.");
println!("threads with arg test passed!");
0
}

View File

@@ -0,0 +1,214 @@
#![no_std]
#![no_main]
#![allow(clippy::println_empty_string)]
extern crate alloc;
#[macro_use]
extern crate user_lib;
const LF: u8 = 0x0au8;
const CR: u8 = 0x0du8;
const DL: u8 = 0x7fu8;
const BS: u8 = 0x08u8;
const LINE_START: &str = ">> ";
use alloc::string::String;
use alloc::vec::Vec;
use user_lib::console::getchar;
use user_lib::{close, dup, exec, fork, open, pipe, waitpid, OpenFlags};
#[derive(Debug)]
struct ProcessArguments {
input: String,
output: String,
args_copy: Vec<String>,
args_addr: Vec<*const u8>,
}
impl ProcessArguments {
pub fn new(command: &str) -> Self {
let args: Vec<_> = command.split(' ').collect();
let mut args_copy: Vec<String> = args
.iter()
.filter(|&arg| !arg.is_empty())
.map(|&arg| {
let mut string = String::new();
string.push_str(arg);
string.push('\0');
string
})
.collect();
// redirect input
let mut input = String::new();
if let Some((idx, _)) = args_copy
.iter()
.enumerate()
.find(|(_, arg)| arg.as_str() == "<\0")
{
input = args_copy[idx + 1].clone();
args_copy.drain(idx..=idx + 1);
}
// redirect output
let mut output = String::new();
if let Some((idx, _)) = args_copy
.iter()
.enumerate()
.find(|(_, arg)| arg.as_str() == ">\0")
{
output = args_copy[idx + 1].clone();
args_copy.drain(idx..=idx + 1);
}
let mut args_addr: Vec<*const u8> = args_copy.iter().map(|arg| arg.as_ptr()).collect();
args_addr.push(core::ptr::null::<u8>());
Self {
input,
output,
args_copy,
args_addr,
}
}
}
#[no_mangle]
pub fn main() -> i32 {
println!("Rust user shell");
let mut line: String = String::new();
print!("{}", LINE_START);
loop {
let c = getchar();
match c {
LF | CR => {
println!("");
if !line.is_empty() {
let splited: Vec<_> = line.as_str().split('|').collect();
let process_arguments_list: Vec<_> = splited
.iter()
.map(|&cmd| ProcessArguments::new(cmd))
.collect();
let mut valid = true;
for (i, process_args) in process_arguments_list.iter().enumerate() {
if i == 0 {
if !process_args.output.is_empty() {
valid = false;
}
} else if i == process_arguments_list.len() - 1 {
if !process_args.input.is_empty() {
valid = false;
}
} else if !process_args.output.is_empty() || !process_args.input.is_empty()
{
valid = false;
}
}
if process_arguments_list.len() == 1 {
valid = true;
}
if !valid {
println!("Invalid command: Inputs/Outputs cannot be correctly binded!");
} else {
// create pipes
let mut pipes_fd: Vec<[usize; 2]> = Vec::new();
if !process_arguments_list.is_empty() {
for _ in 0..process_arguments_list.len() - 1 {
let mut pipe_fd = [0usize; 2];
pipe(&mut pipe_fd);
pipes_fd.push(pipe_fd);
}
}
let mut children: Vec<_> = Vec::new();
for (i, process_argument) in process_arguments_list.iter().enumerate() {
let pid = fork();
if pid == 0 {
let input = &process_argument.input;
let output = &process_argument.output;
let args_copy = &process_argument.args_copy;
let args_addr = &process_argument.args_addr;
// redirect input
if !input.is_empty() {
let input_fd = open(input.as_str(), OpenFlags::RDONLY);
if input_fd == -1 {
println!("Error when opening file {}", input);
return -4;
}
let input_fd = input_fd as usize;
close(0);
assert_eq!(dup(input_fd), 0);
close(input_fd);
}
// redirect output
if !output.is_empty() {
let output_fd = open(
output.as_str(),
OpenFlags::CREATE | OpenFlags::WRONLY,
);
if output_fd == -1 {
println!("Error when opening file {}", output);
return -4;
}
let output_fd = output_fd as usize;
close(1);
assert_eq!(dup(output_fd), 1);
close(output_fd);
}
// receive input from the previous process
if i > 0 {
close(0);
let read_end = pipes_fd.get(i - 1).unwrap()[0];
assert_eq!(dup(read_end), 0);
}
// send output to the next process
if i < process_arguments_list.len() - 1 {
close(1);
let write_end = pipes_fd.get(i).unwrap()[1];
assert_eq!(dup(write_end), 1);
}
// close all pipe ends inherited from the parent process
for pipe_fd in pipes_fd.iter() {
close(pipe_fd[0]);
close(pipe_fd[1]);
}
// execute new application
if exec(args_copy[0].as_str(), args_addr.as_slice()) == -1 {
println!("Error when executing!");
return -4;
}
unreachable!();
} else {
children.push(pid);
}
}
for pipe_fd in pipes_fd.iter() {
close(pipe_fd[0]);
close(pipe_fd[1]);
}
let mut exit_code: i32 = 0;
for pid in children.into_iter() {
let exit_pid = waitpid(pid as usize, &mut exit_code);
assert_eq!(pid, exit_pid);
//println!("Shell: Process {} exited with code {}", pid, exit_code);
}
}
line.clear();
}
print!("{}", LINE_START);
}
BS | DL => {
if !line.is_empty() {
print!("{}", BS as char);
print!(" ");
print!("{}", BS as char);
line.pop();
}
}
_ => {
print!("{}", c as char);
line.push(c as char);
}
}
}
}

75
user/src/console.rs Normal file
View File

@@ -0,0 +1,75 @@
use alloc::collections::vec_deque::VecDeque;
use alloc::sync::Arc;
use core::fmt::{self, Write};
use spin::mutex::Mutex;
pub const STDIN: usize = 0;
pub const STDOUT: usize = 1;
const CONSOLE_BUFFER_SIZE: usize = 256 * 10;
use super::{read, write};
use lazy_static::*;
struct ConsoleBuffer(VecDeque<u8>);
lazy_static! {
static ref CONSOLE_BUFFER: Arc<Mutex<ConsoleBuffer>> = {
let buffer = VecDeque::<u8>::with_capacity(CONSOLE_BUFFER_SIZE);
Arc::new(Mutex::new(ConsoleBuffer(buffer)))
};
}
impl ConsoleBuffer {
fn flush(&mut self) -> isize {
let s: &[u8] = self.0.make_contiguous();
let ret = write(STDOUT, s);
self.0.clear();
ret
}
}
impl Write for ConsoleBuffer {
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.as_bytes().iter() {
self.0.push_back(*c);
if (*c == b'\n' || self.0.len() == CONSOLE_BUFFER_SIZE) && -1 == self.flush() {
return Err(fmt::Error);
}
}
Ok(())
}
}
#[allow(unused)]
pub fn print(args: fmt::Arguments) {
let mut buf = CONSOLE_BUFFER.lock();
// buf.write_fmt(args).unwrap();
// BUG FIX: 关闭 stdout 后,本函数不能触发 panic否则会造成死锁
buf.write_fmt(args);
}
#[macro_export]
macro_rules! print {
($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!($fmt $(, $($arg)+)?));
}
}
#[macro_export]
macro_rules! println {
($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
}
}
pub fn getchar() -> u8 {
let mut c = [0u8; 1];
read(STDIN, &mut c);
c[0]
}
pub fn flush() {
let mut buf = CONSOLE_BUFFER.lock();
buf.flush();
}

17
user/src/lang_items.rs Normal file
View File

@@ -0,0 +1,17 @@
use crate::exit;
#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
let err = panic_info.message().unwrap();
if let Some(location) = panic_info.location() {
println!(
"Panicked at {}:{}, {}",
location.file(),
location.line(),
err
);
} else {
println!("Panicked: {}", err);
}
exit(-1);
}

359
user/src/lib.rs Normal file
View File

@@ -0,0 +1,359 @@
#![no_std]
#![feature(linkage)]
#![feature(panic_info_message)]
#![feature(alloc_error_handler)]
#[macro_use]
pub mod console;
mod lang_items;
mod syscall;
extern crate alloc;
extern crate core;
#[macro_use]
extern crate bitflags;
use alloc::vec::Vec;
use buddy_system_allocator::LockedHeap;
pub use console::{flush, STDIN, STDOUT};
pub use syscall::*;
const USER_HEAP_SIZE: usize = 16384;
static mut HEAP_SPACE: [u8; USER_HEAP_SIZE] = [0; USER_HEAP_SIZE];
#[global_allocator]
static HEAP: LockedHeap = LockedHeap::empty();
#[alloc_error_handler]
pub fn handle_alloc_error(layout: core::alloc::Layout) -> ! {
panic!("Heap allocation error, layout = {:?}", layout);
}
fn clear_bss() {
extern "C" {
fn start_bss();
fn end_bss();
}
unsafe {
core::slice::from_raw_parts_mut(
start_bss as usize as *mut u8,
end_bss as usize - start_bss as usize,
)
.fill(0);
}
}
#[no_mangle]
#[link_section = ".text.entry"]
pub extern "C" fn _start(argc: usize, argv: usize) -> ! {
clear_bss();
unsafe {
HEAP.lock()
.init(HEAP_SPACE.as_ptr() as usize, USER_HEAP_SIZE);
}
let mut v: Vec<&'static str> = Vec::new();
for i in 0..argc {
let str_start =
unsafe { ((argv + i * core::mem::size_of::<usize>()) as *const usize).read_volatile() };
let len = (0usize..)
.find(|i| unsafe { ((str_start + *i) as *const u8).read_volatile() == 0 })
.unwrap();
v.push(
core::str::from_utf8(unsafe {
core::slice::from_raw_parts(str_start as *const u8, len)
})
.unwrap(),
);
}
exit(main(argc, v.as_slice()));
}
#[linkage = "weak"]
#[no_mangle]
fn main(_argc: usize, _argv: &[&str]) -> i32 {
panic!("Cannot find main!");
}
bitflags! {
pub struct OpenFlags: u32 {
const RDONLY = 0;
const WRONLY = 1 << 0;
const RDWR = 1 << 1;
const CREATE = 1 << 9;
const TRUNC = 1 << 10;
}
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct TimeVal {
pub sec: usize,
pub usec: usize,
}
impl TimeVal {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum TaskStatus {
UnInit,
Ready,
Running,
Exited,
}
#[derive(Copy, Clone, Debug)]
pub struct SyscallInfo {
pub id: usize,
pub times: usize,
}
const MAX_SYSCALL_NUM: usize = 500;
#[derive(Debug)]
pub struct TaskInfo {
pub status: TaskStatus,
pub syscall_times: [u32; MAX_SYSCALL_NUM],
pub time: usize,
}
impl TaskInfo {
pub fn new() -> Self {
TaskInfo {
status: TaskStatus::UnInit,
syscall_times: [0; MAX_SYSCALL_NUM],
time: 0,
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct Stat {
/// ID of device containing file
pub dev: u64,
/// inode number
pub ino: u64,
/// file type and mode
pub mode: StatMode,
/// number of hard links
pub nlink: u32,
/// unused pad
pad: [u64; 7],
}
impl Stat {
pub fn new() -> Self {
Stat {
dev: 0,
ino: 0,
mode: StatMode::NULL,
nlink: 0,
pad: [0; 7],
}
}
}
impl Default for Stat {
fn default() -> Self {
Self::new()
}
}
bitflags! {
pub struct StatMode: u32 {
const NULL = 0;
/// directory
const DIR = 0o040000;
/// ordinary regular file
const FILE = 0o100000;
}
}
const AT_FDCWD: isize = -100;
pub fn open(path: &str, flags: OpenFlags) -> isize {
sys_openat(AT_FDCWD as usize, path, flags.bits, OpenFlags::RDWR.bits)
}
pub fn close(fd: usize) -> isize {
if fd == STDOUT {
console::flush();
}
sys_close(fd)
}
pub fn read(fd: usize, buf: &mut [u8]) -> isize {
sys_read(fd, buf)
}
pub fn write(fd: usize, buf: &[u8]) -> isize {
sys_write(fd, buf)
}
pub fn link(old_path: &str, new_path: &str) -> isize {
sys_linkat(AT_FDCWD as usize, old_path, AT_FDCWD as usize, new_path, 0)
}
pub fn unlink(path: &str) -> isize {
sys_unlinkat(AT_FDCWD as usize, path, 0)
}
pub fn fstat(fd: usize, st: &Stat) -> isize {
sys_fstat(fd, st)
}
pub fn mail_read(buf: &mut [u8]) -> isize {
sys_mail_read(buf)
}
pub fn mail_write(pid: usize, buf: &[u8]) -> isize {
sys_mail_write(pid, buf)
}
pub fn exit(exit_code: i32) -> ! {
console::flush();
sys_exit(exit_code);
}
pub fn yield_() -> isize {
sys_yield()
}
pub fn get_time() -> isize {
let time = TimeVal::new();
match sys_get_time(&time, 0) {
0 => ((time.sec & 0xffff) * 1000 + time.usec / 1000) as isize,
_ => -1,
}
}
pub fn getpid() -> isize {
sys_getpid()
}
pub fn fork() -> isize {
sys_fork()
}
pub fn exec(path: &str, args: &[*const u8]) -> isize {
sys_exec(path, args)
}
pub fn set_priority(prio: isize) -> isize {
sys_set_priority(prio)
}
pub fn wait(exit_code: &mut i32) -> isize {
loop {
match sys_waitpid(-1, exit_code as *mut _) {
-2 => {
sys_yield();
}
n => {
return n;
}
}
}
}
pub fn waitpid(pid: usize, exit_code: &mut i32) -> isize {
loop {
match sys_waitpid(pid as isize, exit_code as *mut _) {
-2 => {
sys_yield();
}
n => {
return n;
}
}
}
}
pub fn sleep_blocking(sleep_ms: usize) {
sys_sleep(sleep_ms);
}
pub fn sleep(period_ms: usize) {
let start = get_time();
while get_time() < start + period_ms as isize {
sys_yield();
}
}
pub fn mmap(start: usize, len: usize, prot: usize) -> isize {
sys_mmap(start, len, prot)
}
pub fn munmap(start: usize, len: usize) -> isize {
sys_munmap(start, len)
}
pub fn spawn(path: &str) -> isize {
sys_spawn(path)
}
pub fn dup(fd: usize) -> isize {
sys_dup(fd)
}
pub fn pipe(pipe_fd: &mut [usize]) -> isize {
sys_pipe(pipe_fd)
}
pub fn task_info(info: &TaskInfo) -> isize {
sys_task_info(info)
}
pub fn thread_create(entry: usize, arg: usize) -> isize {
sys_thread_create(entry, arg)
}
pub fn gettid() -> isize {
sys_gettid()
}
pub fn waittid(tid: usize) -> isize {
loop {
match sys_waittid(tid) {
-2 => {
yield_();
}
exit_code => return exit_code,
}
}
}
pub fn mutex_create() -> isize {
sys_mutex_create(false)
}
pub fn mutex_blocking_create() -> isize {
sys_mutex_create(true)
}
pub fn mutex_lock(mutex_id: usize) -> isize {
sys_mutex_lock(mutex_id)
}
pub fn mutex_unlock(mutex_id: usize) {
sys_mutex_unlock(mutex_id);
}
pub fn semaphore_create(res_count: usize) -> isize {
sys_semaphore_create(res_count)
}
pub fn semaphore_up(sem_id: usize) {
sys_semaphore_up(sem_id);
}
pub fn enable_deadlock_detect(enabled: bool) -> isize {
sys_enable_deadlock_detect(enabled as usize)
}
pub fn semaphore_down(sem_id: usize) -> isize {
sys_semaphore_down(sem_id)
}
pub fn condvar_create() -> isize {
sys_condvar_create(0)
}
pub fn condvar_signal(condvar_id: usize) {
sys_condvar_signal(condvar_id);
}
pub fn condvar_wait(condvar_id: usize, mutex_id: usize) {
sys_condvar_wait(condvar_id, mutex_id);
}

33
user/src/linker.ld Normal file
View File

@@ -0,0 +1,33 @@
OUTPUT_ARCH(riscv)
ENTRY(_start)
BASE_ADDRESS = 0x0;
SECTIONS
{
. = BASE_ADDRESS;
.text : {
*(.text.entry)
*(.text .text.*)
}
. = ALIGN(4K);
.rodata : {
*(.rodata .rodata.*)
*(.srodata .srodata.*)
}
. = ALIGN(4K);
.data : {
*(.data .data.*)
*(.sdata .sdata.*)
}
.bss : {
start_bss = .;
*(.bss .bss.*)
*(.sbss .sbss.*)
end_bss = .;
}
/DISCARD/ : {
*(.eh_frame)
*(.debug*)
}
}

258
user/src/syscall.rs Normal file
View File

@@ -0,0 +1,258 @@
use crate::TaskInfo;
use super::{Stat, TimeVal};
pub const SYSCALL_OPENAT: usize = 56;
pub const SYSCALL_CLOSE: usize = 57;
pub const SYSCALL_READ: usize = 63;
pub const SYSCALL_WRITE: usize = 64;
pub const SYSCALL_UNLINKAT: usize = 35;
pub const SYSCALL_LINKAT: usize = 37;
pub const SYSCALL_FSTAT: usize = 80;
pub const SYSCALL_EXIT: usize = 93;
pub const SYSCALL_SLEEP: usize = 101;
pub const SYSCALL_YIELD: usize = 124;
pub const SYSCALL_GETTIMEOFDAY: usize = 169;
pub const SYSCALL_GETPID: usize = 172;
pub const SYSCALL_GETTID: usize = 178;
pub const SYSCALL_FORK: usize = 220;
pub const SYSCALL_EXEC: usize = 221;
pub const SYSCALL_WAITPID: usize = 260;
pub const SYSCALL_SET_PRIORITY: usize = 140;
pub const SYSCALL_MUNMAP: usize = 215;
pub const SYSCALL_MMAP: usize = 222;
pub const SYSCALL_SPAWN: usize = 400;
pub const SYSCALL_MAIL_READ: usize = 401;
pub const SYSCALL_MAIL_WRITE: usize = 402;
pub const SYSCALL_DUP: usize = 24;
pub const SYSCALL_PIPE: usize = 59;
pub const SYSCALL_TASK_INFO: usize = 410;
pub const SYSCALL_THREAD_CREATE: usize = 460;
pub const SYSCALL_WAITTID: usize = 462;
pub const SYSCALL_MUTEX_CREATE: usize = 463;
pub const SYSCALL_MUTEX_LOCK: usize = 464;
pub const SYSCALL_MUTEX_UNLOCK: usize = 466;
pub const SYSCALL_SEMAPHORE_CREATE: usize = 467;
pub const SYSCALL_SEMAPHORE_UP: usize = 468;
pub const SYSCALL_ENABLE_DEADLOCK_DETECT: usize = 469;
pub const SYSCALL_SEMAPHORE_DOWN: usize = 470;
pub const SYSCALL_CONDVAR_CREATE: usize = 471;
pub const SYSCALL_CONDVAR_SIGNAL: usize = 472;
pub const SYSCALL_CONDVAR_WAIT: usize = 473;
pub fn syscall(id: usize, args: [usize; 3]) -> isize {
let mut ret: isize;
unsafe {
core::arch::asm!(
"ecall",
inlateout("x10") args[0] => ret,
in("x11") args[1],
in("x12") args[2],
in("x17") id
);
}
ret
}
pub fn syscall6(id: usize, args: [usize; 6]) -> isize {
let mut ret: isize;
unsafe {
core::arch::asm!("ecall",
inlateout("x10") args[0] => ret,
in("x11") args[1],
in("x12") args[2],
in("x13") args[3],
in("x14") args[4],
in("x15") args[5],
in("x17") id
);
}
ret
}
pub fn sys_openat(dirfd: usize, path: &str, flags: u32, mode: u32) -> isize {
syscall6(
SYSCALL_OPENAT,
[
dirfd,
path.as_ptr() as usize,
flags as usize,
mode as usize,
0,
0,
],
)
}
pub fn sys_close(fd: usize) -> isize {
syscall(SYSCALL_CLOSE, [fd, 0, 0])
}
pub fn sys_read(fd: usize, buffer: &mut [u8]) -> isize {
syscall(
SYSCALL_READ,
[fd, buffer.as_mut_ptr() as usize, buffer.len()],
)
}
pub fn sys_write(fd: usize, buffer: &[u8]) -> isize {
syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()])
}
pub fn sys_linkat(
old_dirfd: usize,
old_path: &str,
new_dirfd: usize,
new_path: &str,
flags: usize,
) -> isize {
syscall6(
SYSCALL_LINKAT,
[
old_dirfd,
old_path.as_ptr() as usize,
new_dirfd,
new_path.as_ptr() as usize,
flags,
0,
],
)
}
pub fn sys_unlinkat(dirfd: usize, path: &str, flags: usize) -> isize {
syscall(SYSCALL_UNLINKAT, [dirfd, path.as_ptr() as usize, flags])
}
pub fn sys_fstat(fd: usize, st: &Stat) -> isize {
syscall(SYSCALL_FSTAT, [fd, st as *const _ as usize, 0])
}
pub fn sys_mail_read(buffer: &mut [u8]) -> isize {
syscall(
SYSCALL_MAIL_READ,
[buffer.as_ptr() as usize, buffer.len(), 0],
)
}
pub fn sys_mail_write(pid: usize, buffer: &[u8]) -> isize {
syscall(
SYSCALL_MAIL_WRITE,
[pid, buffer.as_ptr() as usize, buffer.len()],
)
}
pub fn sys_exit(exit_code: i32) -> ! {
syscall(SYSCALL_EXIT, [exit_code as usize, 0, 0]);
panic!("sys_exit never returns!");
}
pub fn sys_sleep(sleep_ms: usize) -> isize {
syscall(SYSCALL_SLEEP, [sleep_ms, 0, 0])
}
pub fn sys_yield() -> isize {
syscall(SYSCALL_YIELD, [0, 0, 0])
}
pub fn sys_get_time(time: &TimeVal, tz: usize) -> isize {
syscall(SYSCALL_GETTIMEOFDAY, [time as *const _ as usize, tz, 0])
}
pub fn sys_getpid() -> isize {
syscall(SYSCALL_GETPID, [0, 0, 0])
}
pub fn sys_fork() -> isize {
syscall(SYSCALL_FORK, [0, 0, 0])
}
pub fn sys_exec(path: &str, args: &[*const u8]) -> isize {
syscall(
SYSCALL_EXEC,
[path.as_ptr() as usize, args.as_ptr() as usize, 0],
)
}
pub fn sys_waitpid(pid: isize, xstatus: *mut i32) -> isize {
syscall(SYSCALL_WAITPID, [pid as usize, xstatus as usize, 0])
}
pub fn sys_set_priority(prio: isize) -> isize {
syscall(SYSCALL_SET_PRIORITY, [prio as usize, 0, 0])
}
pub fn sys_mmap(start: usize, len: usize, prot: usize) -> isize {
syscall(SYSCALL_MMAP, [start, len, prot])
}
pub fn sys_munmap(start: usize, len: usize) -> isize {
syscall(SYSCALL_MUNMAP, [start, len, 0])
}
pub fn sys_spawn(path: &str) -> isize {
syscall(SYSCALL_SPAWN, [path.as_ptr() as usize, 0, 0])
}
pub fn sys_dup(fd: usize) -> isize {
syscall(SYSCALL_DUP, [fd, 0, 0])
}
pub fn sys_pipe(pipe: &mut [usize]) -> isize {
syscall(SYSCALL_PIPE, [pipe.as_mut_ptr() as usize, 0, 0])
}
pub fn sys_task_info(info: &TaskInfo) -> isize {
syscall(SYSCALL_TASK_INFO, [info as *const _ as usize, 0, 0])
}
pub fn sys_thread_create(entry: usize, arg: usize) -> isize {
syscall(SYSCALL_THREAD_CREATE, [entry, arg, 0])
}
pub fn sys_gettid() -> isize {
syscall(SYSCALL_GETTID, [0; 3])
}
pub fn sys_waittid(tid: usize) -> isize {
syscall(SYSCALL_WAITTID, [tid, 0, 0])
}
pub fn sys_mutex_create(blocking: bool) -> isize {
syscall(SYSCALL_MUTEX_CREATE, [blocking as usize, 0, 0])
}
pub fn sys_mutex_lock(id: usize) -> isize {
syscall(SYSCALL_MUTEX_LOCK, [id, 0, 0])
}
pub fn sys_mutex_unlock(id: usize) -> isize {
syscall(SYSCALL_MUTEX_UNLOCK, [id, 0, 0])
}
pub fn sys_semaphore_create(res_count: usize) -> isize {
syscall(SYSCALL_SEMAPHORE_CREATE, [res_count, 0, 0])
}
pub fn sys_semaphore_up(sem_id: usize) -> isize {
syscall(SYSCALL_SEMAPHORE_UP, [sem_id, 0, 0])
}
pub fn sys_enable_deadlock_detect(enabled: usize) -> isize {
syscall(SYSCALL_ENABLE_DEADLOCK_DETECT, [enabled, 0, 0])
}
pub fn sys_semaphore_down(sem_id: usize) -> isize {
syscall(SYSCALL_SEMAPHORE_DOWN, [sem_id, 0, 0])
}
pub fn sys_condvar_create(_arg: usize) -> isize {
syscall(SYSCALL_CONDVAR_CREATE, [_arg, 0, 0])
}
pub fn sys_condvar_signal(condvar_id: usize) -> isize {
syscall(SYSCALL_CONDVAR_SIGNAL, [condvar_id, 0, 0])
}
pub fn sys_condvar_wait(condvar_id: usize, mutex_id: usize) -> isize {
syscall(SYSCALL_CONDVAR_WAIT, [condvar_id, mutex_id, 0])
}