Compare commits

..

4 Commits

Author SHA1 Message Date
Yifan Wu 2801dcf540 Update ch3/ch4 Description.
5 years ago
Yifan Wu 632ae01ab9 Update ch3-coop Description.
5 years ago
Yifan Wu 698b4553f5 Update testcases.
5 years ago
Yifan Wu e5a62b4330 Add tests for chapter1-3.
5 years ago

@ -1,66 +0,0 @@
name: Build Rust Doc And Run tests
on: [push]
env:
CARGO_TERM_COLOR: always
jobs:
build-doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-04-11
components: rust-src, llvm-tools-preview
target: riscv64gc-unknown-none-elf
- name: Build doc
run: cd os && cargo doc --no-deps --verbose
- name: Deploy to Github Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./os/target/riscv64gc-unknown-none-elf/doc
destination_dir: ${{ github.ref_name }}
run-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-04-11
components: rust-src, llvm-tools-preview
target: riscv64gc-unknown-none-elf
- uses: actions-rs/install@v0.1
with:
crate: cargo-binutils
version: latest
use-tool-cache: true
- name: Cache QEMU
uses: actions/cache@v3
with:
path: qemu-7.0.0
key: qemu-7.0.0-x86_64-riscv64
- name: Install QEMU
run: |
sudo apt-get update
sudo apt-get install ninja-build -y
if [ ! -d qemu-7.0.0 ]; then
wget https://download.qemu.org/qemu-7.0.0.tar.xz
tar -xf qemu-7.0.0.tar.xz
cd qemu-7.0.0
./configure --target-list=riscv64-softmmu
make -j
else
cd qemu-7.0.0
fi
sudo make install
qemu-system-riscv64 --version
- name: Run usertests
run: cd os && make run TEST=1
timeout-minutes: 10

14
.gitignore vendored

@ -1,13 +1 @@
.*/*
!.github/*
!.vscode/settings.json
**/target/
**/Cargo.lock
os/src/link_app.S
os/src/linker.ld
os/last-*
os/.gdb_history
tools/
pushall.sh
.idea/*

@ -1,10 +0,0 @@
{
// Prevent "can't find crate for `test`" error on no_std
// Ref: https://github.com/rust-lang/vscode-rust/issues/729
// For vscode-rust plugin users:
"rust.target": "riscv64gc-unknown-none-elf",
"rust.all_targets": false,
// For Rust Analyzer plugin users:
"rust-analyzer.cargo.target": "riscv64gc-unknown-none-elf",
"rust-analyzer.checkOnSave.allTargets": false
}

@ -1,40 +0,0 @@
FROM ubuntu:18.04
LABEL maintainer="dinghao188" \
version="1.1" \
description="ubuntu 18.04 with tools for tsinghua's rCore-Tutorial-V3"
#install some deps
RUN set -x \
&& apt-get update \
&& apt-get install -y curl wget autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev \
gawk build-essential bison flex texinfo gperf libtool patchutils bc xz-utils \
zlib1g-dev libexpat-dev pkg-config libglib2.0-dev libpixman-1-dev git tmux python3
#install rust and qemu
RUN set -x; \
RUSTUP='/root/rustup.sh' \
&& cd $HOME \
#install rust
&& curl https://sh.rustup.rs -sSf > $RUSTUP && chmod +x $RUSTUP \
&& $RUSTUP -y --default-toolchain nightly --profile minimal \
#compile qemu
&& wget https://ftp.osuosl.org/pub/blfs/conglomeration/qemu/qemu-5.0.0.tar.xz \
&& tar xvJf qemu-5.0.0.tar.xz \
&& cd qemu-5.0.0 \
&& ./configure --target-list=riscv64-softmmu,riscv64-linux-user \
&& make -j$(nproc) install \
&& cd $HOME && rm -rf qemu-5.0.0 qemu-5.0.0.tar.xz
#for chinese network
RUN set -x; \
APT_CONF='/etc/apt/sources.list'; \
CARGO_CONF='/root/.cargo/config'; \
BASHRC='/root/.bashrc' \
&& echo 'export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static' >> $BASHRC \
&& echo 'export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup' >> $BASHRC \
&& touch $CARGO_CONF \
&& echo '[source.crates-io]' > $CARGO_CONF \
&& echo "replace-with = 'ustc'" >> $CARGO_CONF \
&& echo '[source.ustc]' >> $CARGO_CONF \
&& echo 'registry = "git://mirrors.ustc.edu.cn/crates.io-index"' >> $CARGO_CONF

@ -1,10 +0,0 @@
DOCKER_NAME ?= dinghao188/rcore-tutorial
.PHONY: docker build_docker
docker:
docker run --rm -it --mount type=bind,source=$(shell pwd),destination=/mnt ${DOCKER_NAME}
build_docker:
docker build -t ${DOCKER_NAME} .
fmt:
cd easy-fs; cargo fmt; cd ../easy-fs-fuse cargo fmt; cd ../os ; cargo fmt; cd ../user; cargo fmt; cd ..

@ -1,300 +1,2 @@
# rCore-Tutorial-v3
rCore-Tutorial version 3.5. See the [Documentation in Chinese](https://rcore-os.github.io/rCore-Tutorial-Book-v3/).
rCore-Tutorial API Docs. See the [API Docs of Ten OSes ](#OS-API-DOCS)
If you don't know Rust Language and try to learn it, please visit [Rust Learning Resources](https://github.com/rcore-os/rCore/wiki/study-resource-of-system-programming-in-RUST)
Official QQ group number: 735045051
## news
- 25/01/2022: Version 3.6.0 is on the way! Now we directly update the code on chX branches, please periodically check if there are any updates.
## Overview
This project aims to show how to write an **Unix-like OS** running on **RISC-V** platforms **from scratch** in **[Rust](https://www.rust-lang.org/)** for **beginners** without any background knowledge about **computer architectures, assembly languages or operating systems**.
## Features
* Platform supported: `qemu-system-riscv64` simulator or dev boards based on [Kendryte K210 SoC](https://canaan.io/product/kendryteai) such as [Maix Dock](https://www.seeedstudio.com/Sipeed-MAIX-Dock-p-4815.html)
* OS
* concurrency of multiple processes each of which contains mutiple native threads
* preemptive scheduling(Round-Robin algorithm)
* dynamic memory management in kernel
* virtual memory
* a simple file system with a block cache
* an interactive shell in the userspace
* **only 4K+ LoC**
* [A detailed documentation in Chinese](https://rcore-os.github.io/rCore-Tutorial-Book-v3/) in spite of the lack of comments in the code(English version is not available at present)
## Prerequisites
### Install Rust
See [official guide](https://www.rust-lang.org/tools/install).
Install some tools:
```sh
$ rustup target add riscv64gc-unknown-none-elf
$ cargo install cargo-binutils --vers =0.3.3
$ rustup component add llvm-tools-preview
$ rustup component add rust-src
```
### Install Qemu
Here we manually compile and install Qemu 5.0.0. For example, on Ubuntu 18.04:
```sh
# install dependency packages
$ sudo apt install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev \
gawk build-essential bison flex texinfo gperf libtool patchutils bc \
zlib1g-dev libexpat-dev pkg-config libglib2.0-dev libpixman-1-dev git tmux python3 python3-pip
# download Qemu source code
$ wget https://download.qemu.org/qemu-5.0.0.tar.xz
# extract to qemu-5.0.0/
$ tar xvJf qemu-5.0.0.tar.xz
$ cd qemu-5.0.0
# build
$ ./configure --target-list=riscv64-softmmu,riscv64-linux-user
$ make -j$(nproc)
```
Then, add following contents to `~/.bashrc`(please adjust these paths according to your environment):
```
export PATH=$PATH:/home/shinbokuow/Downloads/built/qemu-5.0.0
export PATH=$PATH:/home/shinbokuow/Downloads/built/qemu-5.0.0/riscv64-softmmu
export PATH=$PATH:/home/shinbokuow/Downloads/built/qemu-5.0.0/riscv64-linux-user
```
Finally, update the current shell:
```sh
$ source ~/.bashrc
```
Now we can check the version of Qemu:
```sh
$ qemu-system-riscv64 --version
QEMU emulator version 5.0.0
Copyright (c) 2003-2020 Fabrice Bellard and the QEMU Project developers
```
### Install RISC-V GNU Embedded Toolchain(including GDB)
Download the compressed file according to your platform From [Sifive website](https://www.sifive.com/software)(Ctrl+F 'toolchain').
Extract it and append the location of the 'bin' directory under its root directory to `$PATH`.
For example, we can check the version of GDB:
```sh
$ riscv64-unknown-elf-gdb --version
GNU gdb (SiFive GDB-Metal 10.1.0-2020.12.7) 10.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
```
### Install serial tools(Optional, if you want to run on K210)
```sh
$ pip3 install pyserial
$ sudo apt install python3-serial
```
## Run our project
### Qemu
```sh
$ git clone https://github.com/rcore-os/rCore-Tutorial-v3.git
$ cd rCore-Tutorial-v3/os
$ make run
```
After outputing some debug messages, the kernel lists all the applications available and enter the user shell:
```
/**** APPS ****
mpsc_sem
usertests
pipetest
forktest2
cat
initproc
race_adder_loop
threads_arg
race_adder_mutex_spin
race_adder_mutex_blocking
forktree
user_shell
huge_write
race_adder
race_adder_atomic
threads
stack_overflow
filetest_simple
forktest_simple
cmdline_args
run_pipe_test
forktest
matrix
exit
fantastic_text
sleep_simple
yield
hello_world
pipe_large_test
sleep
phil_din_mutex
**************/
Rust user shell
>>
```
You can run any application except for `initproc` and `user_shell` itself. To run an application, just input its filename and hit enter. `usertests` can run a bunch of applications, thus it is recommended.
Type `Ctrl+a` then `x` to exit Qemu.
### K210
Before chapter 6, you do not need a SD card:
```sh
$ git clone https://github.com/rcore-os/rCore-Tutorial-v3.git
$ cd rCore-Tutorial-v3/os
$ make run BOARD=k210
```
From chapter 6, before running the kernel, we should insert a SD card into PC and manually write the filesystem image to it:
```sh
$ cd rCore-Tutorial-v3/os
$ make sdcard
```
By default it will overwrite the device `/dev/sdb` which is the SD card, but you can provide another location. For example, `make sdcard SDCARD=/dev/sdc`.
After that, remove the SD card from PC and insert it to the slot of K210. Connect the K210 to PC and then:
```sh
$ git clone https://github.com/rcore-os/rCore-Tutorial-v3.git
$ cd rCore-Tutorial-v3/os
$ make run BOARD=k210
```
Type `Ctrl+]` to disconnect from K210.
## Show runtime debug info of OS kernel version
The branch of ch9-log contains a lot of debug info. You could try to run rcore tutorial
for understand the internal behavior of os kernel.
```sh
$ git clone https://github.com/rcore-os/rCore-Tutorial-v3.git
$ cd rCore-Tutorial-v3/os
$ git checkout ch9-log
$ make run
......
[rustsbi] RustSBI version 0.2.0-alpha.10, adapting to RISC-V SBI v0.3
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[rustsbi] Implementation: RustSBI-QEMU Version 0.0.2
[rustsbi-dtb] Hart count: cluster0 with 1 cores
[rustsbi] misa: RV64ACDFIMSU
[rustsbi] mideleg: ssoft, stimer, sext (0x222)
[rustsbi] medeleg: ima, ia, bkpt, la, sa, uecall, ipage, lpage, spage (0xb1ab)
[rustsbi] pmp0: 0x10000000 ..= 0x10001fff (rw-)
[rustsbi] pmp1: 0x2000000 ..= 0x200ffff (rw-)
[rustsbi] pmp2: 0xc000000 ..= 0xc3fffff (rw-)
[rustsbi] pmp3: 0x80000000 ..= 0x8fffffff (rwx)
[rustsbi] enter supervisor 0x80200000
[KERN] rust_main() begin
[KERN] clear_bss() begin
[KERN] clear_bss() end
[KERN] mm::init() begin
[KERN] mm::init_heap() begin
[KERN] mm::init_heap() end
[KERN] mm::init_frame_allocator() begin
[KERN] mm::frame_allocator::lazy_static!FRAME_ALLOCATOR begin
......
```
## Rustdoc
Currently it can only help you view the code since only a tiny part of the code has been documented.
You can open a doc html of `os` using `cargo doc --no-deps --open` under `os` directory.
### OS-API-DOCS
The API Docs for Ten OS
1. [Lib-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch1/os/index.html)
1. [Batch-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch2/os/index.html)
1. [MultiProg-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch3-coop/os/index.html)
1. [TimeSharing-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch3/os/index.html)
1. [AddrSpace-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch4/os/index.html)
1. [Process-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch5/os/index.html)
1. [FileSystem-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch6/os/index.html)
1. [IPC-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch7/os/index.html)
1. [SyncMutex-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch8/os/index.html)
1. [IODevice-OS API doc](https://learningos.github.io/rCore-Tutorial-v3/ch9/os/index.html)
## Working in progress
Our first release 3.5.0 (chapter 1-7) has been published.
There will be 9 chapters in our next release 3.6.0, where 2 new chapters will be added:
* chapter 8: synchronization on a uniprocessor
* chapter 9: I/O devices
Current version is 3.6.0-alpha.1 and we are still working on it.
Here are the updates since 3.5.0:
### Completed
* [x] automatically clean up and rebuild before running our project on a different platform
* [x] fix `power` series application in early chapters, now you can find modulus in the output
* [x] use `UPSafeCell` instead of `RefCell` or `spin::Mutex` in order to access static data structures and adjust its API so that it cannot be borrowed twice at a time(mention `& .exclusive_access().task[0]` in `run_first_task`)
* [x] move `TaskContext` into `TaskControlBlock` instead of restoring it in place on kernel stack(since ch3), eliminating annoying `task_cx_ptr2`
* [x] replace `llvm_asm!` with `asm!`
* [x] expand the fs image size generated by `rcore-fs-fuse` to 128MiB
* [x] add a new test named `huge_write` which evaluates the fs performance(qemu\~500KiB/s k210\~50KiB/s)
* [x] flush all block cache to disk after a fs transaction which involves write operation
* [x] replace `spin::Mutex` with `UPSafeCell` before SMP chapter
* [x] add codes for a new chapter about synchronization & mutual exclusion(uniprocessor only)
* [x] bug fix: we should call `find_pte` rather than `find_pte_create` in `PageTable::unmap`
* [x] clarify: "check validity of level-3 pte in `find_pte` instead of checking it outside this function" should not be a bug
* [x] code of chapter 8: synchronization on a uniprocessor
* [x] switch the code of chapter 6 and chapter 7
* [x] support signal mechanism in chapter 7/8(only works for apps with a single thread)
* [x] Add boards/ directory and support rustdoc, for example you can use `cargo doc --no-deps --open` to view the documentation of a crate
### Todo(High priority)
* [ ] review documentation, current progress: 5/9
* [ ] support user-level sync primitives in chapter 8
* [ ] code of chapter 9: device drivers based on interrupts, including UART and block devices
* [ ] use old fs image optionally, do not always rebuild the image
* [ ] add new system calls: getdents64/fstat
* [ ] shell functionality improvement(to be continued...)
* [ ] give every non-zero process exit code an unique and clear error type
* [ ] effective error handling of mm module
### Todo(Low priority)
* [ ] rewrite practice doc and remove some inproper questions
* [ ] provide smooth debug experience at a Rust source code level
* [ ] format the code using official tools
### Crates
We will add them later.
rCore-Tutorial version 3.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,3 @@
# Tutorial 第一章测试用例
第一章只需要在 RV64 裸机平台运行一个嵌入式应用,因此只要能够在 SBI 的帮助下在屏幕上输出一行字符串就算成功。

2
ch2/.gitignore vendored

@ -0,0 +1,2 @@
target/*
Cargo.lock

@ -1,5 +1,5 @@
[package]
name = "easy-fs-fuse"
name = "user_lib"
version = "0.1.0"
authors = ["Yifan Wu <shinbokuow@163.com>"]
edition = "2018"
@ -7,6 +7,3 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = "2.33.3"
easy-fs = { path = "../easy-fs" }
rand = "0.8.0"

@ -0,0 +1,21 @@
TARGET := riscv64gc-unknown-none-elf
MODE := release
APP_DIR := src/bin
TARGET_DIR := target/$(TARGET)/$(MODE)
APPS := $(wildcard $(APP_DIR)/*.rs)
ELFS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%, $(APPS))
BINS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%.bin, $(APPS))
OBJDUMP := rust-objdump --arch-name=riscv64
OBJCOPY := rust-objcopy --binary-architecture=riscv64
elf:
@cargo build --release
@echo $(APPS)
@echo $(ELFS)
@echo $(BINS)
binary: elf
$(foreach elf, $(ELFS), $(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf));)
build: binary

@ -0,0 +1,20 @@
# Tutorial 第二章测试用例
第二章我们需要实现一个批处理系统。在 `src/bin` 目录中,我们可以找到三个应用程序:
* `00hello_world`
* `01store_fault`
* `02power`
我们需要按照编号从小到大的顺序去加载并运行它们。
应用被设计为运行在用户模式,批处理系统应运行在监督模式,它们都直接访问物理内存。
三个应用被需要被加载到同一个物理地址。
本章需要实现的系统调用:
* `sys_write` 用于向屏幕输出字符串;
* `sys_exit` 用于告知批处理系统当前应用退出,应切换到下一个应用。
注意:应用 `01store_fault` 会访问非法的物理地址,批处理系统需要杀死它并能够正常运行序列中的下一个应用 `02power`

@ -0,0 +1,11 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
#[no_mangle]
fn main() -> i32 {
println!("Hello, world!");
0
}

@ -1,5 +1,6 @@
#![no_std]
#![no_main]
#![feature(llvm_asm)]
#[macro_use]
extern crate user_lib;
@ -8,8 +9,6 @@ extern crate user_lib;
fn main() -> i32 {
println!("Into Test store_fault, we will insert an invalid store operation...");
println!("Kernel should kill this application!");
unsafe {
core::ptr::null_mut::<u8>().write_volatile(0);
}
unsafe { (0x0 as *mut u8).write_volatile(0); }
0
}
}

@ -0,0 +1,27 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
const SIZE: usize = 10;
const P: u32 = 3;
const STEP: usize = 100000;
const MOD: u32 = 10007;
#[no_mangle]
fn main() -> i32 {
let mut pow = [0u32; SIZE];
let mut index: usize = 0;
pow[index] = 1;
for i in 1..=STEP {
let last = pow[index];
index = (index + 1) % SIZE;
pow[index] = last * P % MOD;
if i % 10000 == 0 {
println!("{}^{}={}", P, i, pow[index]);
}
}
println!("Test power OK!");
0
}

@ -1,13 +1,11 @@
use crate::drivers::chardev::{CharDevice, UART};
use core::fmt::{self, Write};
use crate::syscall::{STDOUT, sys_write};
struct Stdout;
impl Write for Stdout {
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.chars() {
UART.write(c as u8);
}
sys_write(STDOUT, s.as_bytes());
Ok(())
}
}
@ -19,13 +17,13 @@ pub fn print(args: fmt::Arguments) {
#[macro_export]
macro_rules! print {
($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!($fmt $(, $($arg)+)?))
$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)+)?))
$crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
}
}
}

@ -0,0 +1,10 @@
#[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);
}
loop {}
}

@ -0,0 +1,33 @@
#![no_std]
#![feature(llvm_asm)]
#![feature(linkage)]
#![feature(panic_info_message)]
#[macro_use]
pub mod console;
mod syscall;
mod lang_items;
#[no_mangle]
#[link_section = ".text.entry"]
pub extern "C" fn _start() -> ! {
clear_bss();
syscall::sys_exit(main());
panic!("unreachable after sys_exit!");
}
#[linkage = "weak"]
#[no_mangle]
fn main() -> i32 {
panic!("Cannot find main!");
}
fn clear_bss() {
extern "C" {
fn start_bss();
fn end_bss();
}
(start_bss as usize..end_bss as usize).for_each(|addr| {
unsafe { (addr as *mut u8).write_volatile(0); }
});
}

@ -0,0 +1,28 @@
OUTPUT_ARCH(riscv)
ENTRY(_start)
BASE_ADDRESS = 0x80040000;
SECTIONS
{
. = BASE_ADDRESS;
.text : {
*(.text.entry)
*(.text .text.*)
}
.rodata : {
*(.rodata .rodata.*)
}
.data : {
*(.data .data.*)
}
.bss : {
start_bss = .;
*(.bss .bss.*)
end_bss = .;
}
/DISCARD/ : {
*(.eh_frame)
*(.debug*)
}
}

@ -0,0 +1,25 @@
pub const STDOUT: usize = 1;
const SYSCALL_WRITE: usize = 64;
const SYSCALL_EXIT: usize = 93;
fn syscall(id: usize, args: [usize; 3]) -> isize {
let mut ret: isize;
unsafe {
llvm_asm!("ecall"
: "={x10}" (ret)
: "{x10}" (args[0]), "{x11}" (args[1]), "{x12}" (args[2]), "{x17}" (id)
: "memory"
: "volatile"
);
}
ret
}
pub fn sys_write(fd: usize, buffer: &[u8]) -> isize {
syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()])
}
pub fn sys_exit(xstate: i32) -> isize {
syscall(SYSCALL_EXIT, [xstate as usize, 0, 0])
}

@ -3,5 +3,5 @@ target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf]
rustflags = [
"-Clink-arg=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes"
"-Clink-args=-Tsrc/linker.ld",
]

@ -1,5 +1,5 @@
[package]
name = "easy-fs"
name = "user_lib"
version = "0.1.0"
authors = ["Yifan Wu <shinbokuow@163.com>"]
edition = "2018"
@ -7,8 +7,3 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
spin = "0.7.0"
lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
[profile.release]
debug = true

@ -0,0 +1,23 @@
TARGET := riscv64gc-unknown-none-elf
MODE := release
APP_DIR := src/bin
TARGET_DIR := target/$(TARGET)/$(MODE)
APPS := $(wildcard $(APP_DIR)/*.rs)
ELFS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%, $(APPS))
BINS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%.bin, $(APPS))
OBJDUMP := rust-objdump --arch-name=riscv64
OBJCOPY := rust-objcopy --binary-architecture=riscv64
elf: $(APPS)
@python3 build.py
binary: elf
$(foreach elf, $(ELFS), $(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf));)
build: binary
clean:
@cargo clean
.PHONY: elf binary build clean

@ -0,0 +1,19 @@
# Tutorial 第三章测试用例 part1
在 Tutorial 第三章第一阶段中,只需实现一个非抢占式调度的分时多任务系统。在 `src/bin` 目录中,我们可以找到三个应用程序:
* `00write_a`
* `01write_b`
* `02write_c`
我们需要在系统初始化的时候将它们分别加载到一个应用与内核预先约定的物理地址。对于编号 0 <= i <= 2应该将其加载到物理地址 0x80100000+0x20000\*i 处运行。这需要在构建应用程序的时候动态调整 `linker.ld` 中的应用起始地址,参见 `build.py`
应用被设计为运行在用户模式,批处理系统应运行在监督模式,它们都直接访问物理内存。
这一阶段需要实现的系统调用:
* `sys_yield`:用来交出当前应用的 CPU 使用权并切换到下一个应用,等到再次获得 CPU 使用权才能继续向下执行。
阅读应用程序代码可知,每个应用都输出一个字母方阵,但是每输出一行之后就会 `sys_yield`,于是最终应该看到一个字母交错的方阵。
注意:当所有应用通过 `sys_exit` 退出之后,系统也应该退出。

@ -0,0 +1,25 @@
import os
base_address = 0x80100000
step = 0x20000
linker = 'src/linker.ld'
app_id = 0
apps = os.listdir('src/bin')
apps.sort()
for app in apps:
app = app[:app.find('.')]
lines = []
lines_before = []
with open(linker, 'r') as f:
for line in f.readlines():
lines_before.append(line)
line = line.replace(hex(base_address), hex(base_address+step*app_id))
lines.append(line)
with open(linker, 'w+') as f:
f.writelines(lines)
os.system('cargo build --bin %s --release' % app)
print('[build.py] application %s start with address %s' %(app, hex(base_address+step*app_id)))
with open(linker, 'w+') as f:
f.writelines(lines_before)
app_id = app_id + 1

@ -0,0 +1,21 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::sys_yield;
const WIDTH: usize = 10;
const HEIGHT: usize = 5;
#[no_mangle]
fn main() -> i32 {
for i in 0..HEIGHT {
for _ in 0..WIDTH { print!("A"); }
println!(" [{}/{}]", i + 1, HEIGHT);
sys_yield();
}
println!("Test write_a OK!");
0
}

@ -0,0 +1,21 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::sys_yield;
const WIDTH: usize = 10;
const HEIGHT: usize = 2;
#[no_mangle]
fn main() -> i32 {
for i in 0..HEIGHT {
for _ in 0..WIDTH { print!("B"); }
println!(" [{}/{}]", i + 1, HEIGHT);
sys_yield();
}
println!("Test write_b OK!");
0
}

@ -0,0 +1,21 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::sys_yield;
const WIDTH: usize = 10;
const HEIGHT: usize = 3;
#[no_mangle]
fn main() -> i32 {
for i in 0..HEIGHT {
for _ in 0..WIDTH { print!("C"); }
println!(" [{}/{}]", i + 1, HEIGHT);
sys_yield();
}
println!("Test write_c OK!");
0
}

@ -0,0 +1,29 @@
use core::fmt::{self, Write};
use crate::syscall::{STDOUT, sys_write};
struct Stdout;
impl Write for Stdout {
fn write_str(&mut self, s: &str) -> fmt::Result {
sys_write(STDOUT, s.as_bytes());
Ok(())
}
}
pub fn print(args: fmt::Arguments) {
Stdout.write_fmt(args).unwrap();
}
#[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)+)?));
}
}

@ -0,0 +1,10 @@
#[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);
}
loop {}
}

@ -0,0 +1,35 @@
#![no_std]
#![feature(llvm_asm)]
#![feature(linkage)]
#![feature(panic_info_message)]
#[macro_use]
pub mod console;
mod syscall;
mod lang_items;
#[no_mangle]
#[link_section = ".text.entry"]
pub extern "C" fn _start() -> ! {
clear_bss();
syscall::sys_exit(main());
panic!("unreachable after sys_exit!");
}
#[linkage = "weak"]
#[no_mangle]
fn main() -> i32 {
panic!("Cannot find main!");
}
fn clear_bss() {
extern "C" {
fn start_bss();
fn end_bss();
}
(start_bss as usize..end_bss as usize).for_each(|addr| {
unsafe { (addr as *mut u8).write_volatile(0); }
});
}
pub use syscall::*;

@ -0,0 +1,29 @@
OUTPUT_ARCH(riscv)
ENTRY(_start)
BASE_ADDRESS = 0x80100000;
SECTIONS
{
. = BASE_ADDRESS;
.text : {
*(.text.entry)
*(.text .text.*)
}
.rodata : {
*(.rodata .rodata.*)
}
.data : {
*(.data .data.*)
}
.bss : {
start_bss = .;
*(.bss .bss.*)
end_bss = .;
}
/DISCARD/ : {
*(.eh_frame)
*(.debug*)
}
}

@ -0,0 +1,35 @@
pub const STDOUT: usize = 1;
const SYSCALL_WRITE: usize = 64;
const SYSCALL_EXIT: usize = 93;
const SYSCALL_YIELD: usize = 124;
const SYSCALL_GET_TIME: usize = 169;
fn syscall(id: usize, args: [usize; 3]) -> isize {
let mut ret: isize;
unsafe {
llvm_asm!("ecall"
: "={x10}" (ret)
: "{x10}" (args[0]), "{x11}" (args[1]), "{x12}" (args[2]), "{x17}" (id)
: "memory"
: "volatile"
);
}
ret
}
pub fn sys_write(fd: usize, buffer: &[u8]) -> isize {
syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()])
}
pub fn sys_exit(xstate: i32) -> isize {
syscall(SYSCALL_EXIT, [xstate as usize, 0, 0])
}
pub fn sys_yield() -> isize {
syscall(SYSCALL_YIELD, [0, 0, 0])
}
pub fn sys_get_time() -> isize {
syscall(SYSCALL_GET_TIME, [0, 0, 0])
}

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

1
ch3/.gitignore vendored

@ -0,0 +1 @@
target/*

@ -7,9 +7,3 @@ 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"
riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] }
[profile.release]
debug = true

@ -0,0 +1,23 @@
TARGET := riscv64gc-unknown-none-elf
MODE := release
APP_DIR := src/bin
TARGET_DIR := target/$(TARGET)/$(MODE)
APPS := $(wildcard $(APP_DIR)/*.rs)
ELFS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%, $(APPS))
BINS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%.bin, $(APPS))
OBJDUMP := rust-objdump --arch-name=riscv64
OBJCOPY := rust-objcopy --binary-architecture=riscv64
elf: $(APPS)
@python3 build.py
binary: elf
$(foreach elf, $(ELFS), $(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf));)
build: binary
clean:
@cargo clean
.PHONY: elf binary build clean

@ -0,0 +1,19 @@
# Tutorial 第三章测试用例 part2
在第三章第二阶段中,需要在第一阶段的基础上实现一个抢占式调度的分时多任务系统。在 `src/bin` 目录下,我们可以找到四个应用程序:
* ``00power_3``
* ``01power_5``
* ``02power_7``
* ``03sleep``
像第一阶段一样,它们也需要被加载到不同的物理地址处运行。
应用被设计为运行在用户模式,批处理系统应运行在监督模式,它们都直接访问物理内存。
这一阶段需要实现的系统调用:
* `sys_get_time`:获取系统当前的毫秒数,在应用 `03sleep` 中使用,可以简单估计所有应用运行的总时间。
阅读应用程序代码可知,三个 `power` 应用分别计算一个质数的幂次对另一个大质数取模的余数,由于次数很高在一个时间片之内无法完成,而它们又不会使用 `sys_yield` 主动交出 CPU 使用权,因此只能由内核进行强制切换。

@ -0,0 +1,25 @@
import os
base_address = 0x80100000
step = 0x20000
linker = 'src/linker.ld'
app_id = 0
apps = os.listdir('src/bin')
apps.sort()
for app in apps:
app = app[:app.find('.')]
lines = []
lines_before = []
with open(linker, 'r') as f:
for line in f.readlines():
lines_before.append(line)
line = line.replace(hex(base_address), hex(base_address+step*app_id))
lines.append(line)
with open(linker, 'w+') as f:
f.writelines(lines)
os.system('cargo build --bin %s --release' % app)
print('[build.py] application %s start with address %s' %(app, hex(base_address+step*app_id)))
with open(linker, 'w+') as f:
f.writelines(lines_before)
app_id = app_id + 1

@ -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!("{}^{} = {}", p, iter, s[cur]);
println!("Test power_3 OK!");
0
}

@ -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!("{}^{} = {}", p, iter, s[cur]);
println!("Test power_5 OK!");
0
}

@ -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!("{}^{} = {}", p, iter, s[cur]);
println!("Test power_7 OK!");
0
}

@ -0,0 +1,18 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{sys_get_time, sys_yield};
#[no_mangle]
fn main() -> i32 {
let current_timer = sys_get_time();
let wait_for = current_timer + 10000000;
while sys_get_time() < wait_for {
sys_yield();
}
println!("Test sleep OK!");
0
}

@ -0,0 +1,29 @@
use core::fmt::{self, Write};
use crate::syscall::{STDOUT, sys_write};
struct Stdout;
impl Write for Stdout {
fn write_str(&mut self, s: &str) -> fmt::Result {
sys_write(STDOUT, s.as_bytes());
Ok(())
}
}
pub fn print(args: fmt::Arguments) {
Stdout.write_fmt(args).unwrap();
}
#[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)+)?));
}
}

@ -0,0 +1,10 @@
#[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);
}
loop {}
}

@ -0,0 +1,35 @@
#![no_std]
#![feature(llvm_asm)]
#![feature(linkage)]
#![feature(panic_info_message)]
#[macro_use]
pub mod console;
mod syscall;
mod lang_items;
#[no_mangle]
#[link_section = ".text.entry"]
pub extern "C" fn _start() -> ! {
clear_bss();
syscall::sys_exit(main());
panic!("unreachable after sys_exit!");
}
#[linkage = "weak"]
#[no_mangle]
fn main() -> i32 {
panic!("Cannot find main!");
}
fn clear_bss() {
extern "C" {
fn start_bss();
fn end_bss();
}
(start_bss as usize..end_bss as usize).for_each(|addr| {
unsafe { (addr as *mut u8).write_volatile(0); }
});
}
pub use syscall::*;

@ -0,0 +1,29 @@
OUTPUT_ARCH(riscv)
ENTRY(_start)
BASE_ADDRESS = 0x80100000;
SECTIONS
{
. = BASE_ADDRESS;
.text : {
*(.text.entry)
*(.text .text.*)
}
.rodata : {
*(.rodata .rodata.*)
}
.data : {
*(.data .data.*)
}
.bss : {
start_bss = .;
*(.bss .bss.*)
end_bss = .;
}
/DISCARD/ : {
*(.eh_frame)
*(.debug*)
}
}

@ -0,0 +1,35 @@
pub const STDOUT: usize = 1;
const SYSCALL_WRITE: usize = 64;
const SYSCALL_EXIT: usize = 93;
const SYSCALL_YIELD: usize = 124;
const SYSCALL_GET_TIME: usize = 169;
fn syscall(id: usize, args: [usize; 3]) -> isize {
let mut ret: isize;
unsafe {
llvm_asm!("ecall"
: "={x10}" (ret)
: "{x10}" (args[0]), "{x11}" (args[1]), "{x12}" (args[2]), "{x17}" (id)
: "memory"
: "volatile"
);
}
ret
}
pub fn sys_write(fd: usize, buffer: &[u8]) -> isize {
syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()])
}
pub fn sys_exit(xstate: i32) -> isize {
syscall(SYSCALL_EXIT, [xstate as usize, 0, 0])
}
pub fn sys_yield() -> isize {
syscall(SYSCALL_YIELD, [0, 0, 0])
}
pub fn sys_get_time() -> isize {
syscall(SYSCALL_GET_TIME, [0, 0, 0])
}

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

1
ch4/.gitignore vendored

@ -0,0 +1 @@
target/*

@ -0,0 +1,9 @@
[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]

@ -8,15 +8,9 @@ BINS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%.bin, $(APPS))
OBJDUMP := rust-objdump --arch-name=riscv64
OBJCOPY := rust-objcopy --binary-architecture=riscv64
CP := cp
TEST ?=
elf: $(APPS)
@cargo build --release
ifeq ($(TEST), 1)
@$(CP) $(TARGET_DIR)/usertests $(TARGET_DIR)/initproc
endif
binary: elf
$(foreach elf, $(ELFS), $(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf));)
@ -26,4 +20,4 @@ build: binary
clean:
@cargo clean
.PHONY: elf binary build clean
.PHONY: elf binary build clean

@ -0,0 +1,3 @@
# Tutorial 第四章测试用例
第四章的应用与第三章第二阶段的应用一样,只是它们均被链接到 0x0 开头的地址空间上。这就需要我们利用 CPU 提供的页表机制,为每个应用创造一个虚拟地址空间。

@ -0,0 +1,29 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
const LEN: usize = 100;
static mut S: [u64; LEN] = [0u64; LEN];
#[no_mangle]
unsafe fn main() -> i32 {
let p = 3u64;
let m = 998244353u64;
let iter: usize = 300000;
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
}

@ -0,0 +1,29 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
const LEN: usize = 100;
static mut S: [u64; LEN] = [0u64; LEN];
#[no_mangle]
unsafe fn main() -> i32 {
let p = 5u64;
let m = 998244353u64;
let iter: usize = 210000;
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
}

@ -0,0 +1,29 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
const LEN: usize = 100;
static mut S: [u64; LEN] = [0u64; LEN];
#[no_mangle]
unsafe fn main() -> i32 {
let p = 7u64;
let m = 998244353u64;
let iter: usize = 240000;
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
}

@ -0,0 +1,18 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{sys_get_time, sys_yield};
#[no_mangle]
fn main() -> i32 {
let current_timer = sys_get_time();
let wait_for = current_timer + 10000000;
while sys_get_time() < wait_for {
sys_yield();
}
println!("Test sleep OK!");
0
}

@ -0,0 +1,29 @@
use core::fmt::{self, Write};
use crate::syscall::{STDOUT, sys_write};
struct Stdout;
impl Write for Stdout {
fn write_str(&mut self, s: &str) -> fmt::Result {
sys_write(STDOUT, s.as_bytes());
Ok(())
}
}
pub fn print(args: fmt::Arguments) {
Stdout.write_fmt(args).unwrap();
}
#[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)+)?));
}
}

@ -0,0 +1,10 @@
#[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);
}
loop {}
}

@ -0,0 +1,25 @@
#![no_std]
#![feature(llvm_asm)]
#![feature(linkage)]
#![feature(panic_info_message)]
#[macro_use]
pub mod console;
mod syscall;
mod lang_items;
#[no_mangle]
#[link_section = ".text.entry"]
pub extern "C" fn _start() -> ! {
syscall::sys_exit(main());
panic!("unreachable after sys_exit!");
}
#[linkage = "weak"]
#[no_mangle]
fn main() -> i32 {
panic!("Cannot find main!");
}
pub use syscall::*;

@ -2,7 +2,7 @@
OUTPUT_ARCH(riscv)
ENTRY(_start)
BASE_ADDRESS = 0x10000;
BASE_ADDRESS = 0x0;
SECTIONS
{
@ -14,19 +14,16 @@ SECTIONS
. = ALIGN(4K);
.rodata : {
*(.rodata .rodata.*)
*(.srodata .srodata.*)
}
. = ALIGN(4K);
.data : {
*(.data .data.*)
*(.sdata .sdata.*)
}
.bss : {
*(.bss .bss.*)
*(.sbss .sbss.*)
}
/DISCARD/ : {
*(.eh_frame)
*(.debug*)
}
}
}

@ -0,0 +1,35 @@
pub const STDOUT: usize = 1;
const SYSCALL_WRITE: usize = 64;
const SYSCALL_EXIT: usize = 93;
const SYSCALL_YIELD: usize = 124;
const SYSCALL_GET_TIME: usize = 169;
fn syscall(id: usize, args: [usize; 3]) -> isize {
let mut ret: isize;
unsafe {
llvm_asm!("ecall"
: "={x10}" (ret)
: "{x10}" (args[0]), "{x11}" (args[1]), "{x12}" (args[2]), "{x17}" (id)
: "memory"
: "volatile"
);
}
ret
}
pub fn sys_write(fd: usize, buffer: &[u8]) -> isize {
syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()])
}
pub fn sys_exit(xstate: i32) -> isize {
syscall(SYSCALL_EXIT, [xstate as usize, 0, 0])
}
pub fn sys_yield() -> isize {
syscall(SYSCALL_YIELD, [0, 0, 0])
}
pub fn sys_get_time() -> isize {
syscall(SYSCALL_GET_TIME, [0, 0, 0])
}

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

1
ch5/.gitignore vendored

@ -0,0 +1 @@
target/*

@ -0,0 +1,10 @@
[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"

@ -0,0 +1,23 @@
TARGET := riscv64gc-unknown-none-elf
MODE := release
APP_DIR := src/bin
TARGET_DIR := target/$(TARGET)/$(MODE)
APPS := $(wildcard $(APP_DIR)/*.rs)
ELFS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%, $(APPS))
BINS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%.bin, $(APPS))
OBJDUMP := rust-objdump --arch-name=riscv64
OBJCOPY := rust-objcopy --binary-architecture=riscv64
elf: $(APPS)
@cargo build --release
binary: elf
$(foreach elf, $(ELFS), $(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf));)
build: binary
clean:
@cargo clean
.PHONY: elf binary build clean

@ -3,7 +3,7 @@
#[macro_use]
extern crate user_lib;
use user_lib::{exit, fork, wait, waitpid, yield_};
use user_lib::{fork, yield_, waitpid, exit, wait};
const MAGIC: i32 = -0x10384;
@ -13,9 +13,7 @@ pub fn main() -> i32 {
let pid = fork();
if pid == 0 {
println!("I am the child.");
for _ in 0..7 {
yield_();
}
for _ in 0..7 { yield_(); }
exit(MAGIC);
} else {
println!("I am parent, fork a child pid {}", pid);
@ -28,3 +26,4 @@ pub fn main() -> i32 {
println!("exit pass.");
0
}

@ -41,4 +41,4 @@ pub fn main() -> i32 {
println!("{}", color_text!(text, i));
}
0
}
}

@ -4,9 +4,9 @@
#[macro_use]
extern crate user_lib;
use user_lib::{exit, fork, wait};
use user_lib::{fork, wait, exit};
const MAX_CHILD: usize = 30;
const MAX_CHILD: usize = 40;
#[no_mangle]
pub fn main() -> i32 {
@ -31,4 +31,4 @@ pub fn main() -> i32 {
}
println!("forktest pass.");
0
}
}

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

@ -18,11 +18,11 @@ pub fn main() -> i32 {
100
} else {
// parent process
let mut exit_code: i32 = 0;
let mut xstate: 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);
assert_eq!(pid, wait(&mut xstate));
assert_eq!(xstate, 100);
println!("child process pid = {}, exit code = {}", pid, xstate);
0
}
}
}

@ -4,7 +4,7 @@
#[macro_use]
extern crate user_lib;
use user_lib::{exit, fork, getpid, sleep, wait, yield_};
use user_lib::{sleep, getpid, fork, exit, yield_};
const DEPTH: usize = 4;
@ -14,7 +14,7 @@ fn fork_child(cur: &str, branch: char) {
if l >= DEPTH {
return;
}
next[..l].copy_from_slice(cur.as_bytes());
&mut 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());
@ -27,19 +27,11 @@ fn fork_tree(cur: &str) {
println!("pid{}: {}", getpid(), cur);
fork_child(cur, '0');
fork_child(cur, '1');
let mut exit_code: i32 = 0;
for _ in 0..2 {
wait(&mut exit_code);
}
}
#[no_mangle]
pub fn main() -> i32 {
fork_tree("");
let mut exit_code: i32 = 0;
for _ in 0..2 {
wait(&mut exit_code);
}
sleep(3000);
0
}

@ -8,4 +8,4 @@ extern crate user_lib;
pub fn main() -> i32 {
println!("Hello world from user mode program!");
0
}
}

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

@ -1,13 +1,12 @@
#![no_std]
#![no_main]
#![allow(clippy::needless_range_loop)]
#[macro_use]
extern crate user_lib;
use user_lib::{exit, fork, get_time, getpid, wait, yield_};
use user_lib::{fork, wait, yield_, exit, getpid, get_time};
static NUM: usize = 30;
static NUM: usize = 35;
const N: usize = 10;
static P: i32 = 10007;
type Arr = [[i32; N]; N];
@ -57,13 +56,13 @@ pub fn main() -> i32 {
println!("fork ok.");
let mut exit_code: i32 = 0;
let mut xstate: i32 = 0;
for _ in 0..NUM {
if wait(&mut exit_code) < 0 {
if wait(&mut xstate) < 0 {
panic!("wait failed.");
}
}
assert!(wait(&mut exit_code) < 0);
assert!(wait(&mut xstate) < 0);
println!("matrix passed.");
0
}
}

@ -4,7 +4,7 @@
#[macro_use]
extern crate user_lib;
use user_lib::{exit, fork, get_time, sleep, waitpid};
use user_lib::{sleep, exit, get_time, fork, waitpid};
fn sleepy() {
let time: usize = 100;
@ -19,12 +19,12 @@ fn sleepy() {
pub fn main() -> i32 {
let current_time = get_time();
let pid = fork();
let mut exit_code: i32 = 0;
let mut xstate: i32 = 0;
if pid == 0 {
sleepy();
}
assert!(waitpid(pid as usize, &mut exit_code) == pid && exit_code == 0);
assert!(waitpid(pid as usize, &mut xstate) == pid && xstate == 0);
println!("use {} msecs.", get_time() - current_time);
println!("sleep pass.");
0
}
}

@ -13,11 +13,7 @@ pub fn main() -> i32 {
println!("current time_msec = {}", start);
sleep(100);
let end = get_time();
println!(
"time_msec = {} after sleeping 100 ticks, delta = {}ms!",
end,
end - start
);
println!("time_msec = {} after sleeping 100 ticks, delta = {}ms!", end, end - start);
println!("r_sleep passed!");
0
}
}

@ -5,7 +5,7 @@
extern crate user_lib;
fn f(d: usize) {
println!("d = {}", d);
println!("d = {}",d);
f(d + 1);
}
@ -14,4 +14,4 @@ pub fn main() -> i32 {
println!("It should trigger segmentation fault!");
f(0);
0
}
}

@ -0,0 +1,70 @@
#![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::{fork, exec, waitpid, yield_};
use user_lib::console::getchar;
#[no_mangle]
pub fn main() -> i32 {
println!("Rust user shell");
let mut line: String = String::new();
print!(">> ");
loop {
let c = getchar();
match c {
LF | CR => {
println!("");
if !line.is_empty() {
line.push('\0');
let pid = fork();
if pid == 0 {
// child process
if exec(line.as_str()) == -1 {
println!("Error when executing!");
return -4;
}
unreachable!();
} else {
let mut xstate: i32 = 0;
let mut exit_pid: isize;
loop {
exit_pid = waitpid(pid as usize, &mut xstate);
if exit_pid == -1 {
yield_();
} else {
assert_eq!(pid, exit_pid);
println!("Shell: Process {} exited with code {}", pid, xstate);
break;
}
}
}
line.clear();
}
print!(">> ");
}
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);
}
}
}
}

@ -0,0 +1,40 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
static TESTS: &[&str] = &[
"exit\0",
"fantastic_text\0",
"forktest\0",
"forktest2\0",
"forktest_simple\0",
"hello_world\0",
"matrix\0",
"sleep\0",
"sleep_simple\0",
"stack_overflow\0",
"yield\0",
];
use user_lib::{exec, fork, waitpid};
#[no_mangle]
pub fn main() -> i32 {
for test in TESTS {
println!("Usertests: Running {}", test);
let pid = fork();
if pid == 0 {
exec(*test);
panic!("unreachable!");
} else {
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!("Usertests passed!");
0
}

@ -14,4 +14,4 @@ pub fn main() -> i32 {
}
println!("yield pass.");
0
}
}

@ -0,0 +1,10 @@
#[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);
}
loop {}
}

@ -0,0 +1,75 @@
#![no_std]
#![feature(llvm_asm)]
#![feature(linkage)]
#![feature(panic_info_message)]
#![feature(alloc_error_handler)]
#[macro_use]
pub mod console;
mod syscall;
mod lang_items;
use syscall::*;
use buddy_system_allocator::LockedHeap;
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);
}
#[no_mangle]
#[link_section = ".text.entry"]
pub extern "C" fn _start() -> ! {
unsafe {
HEAP.lock()
.init(HEAP_SPACE.as_ptr() as usize, USER_HEAP_SIZE);
}
exit(main());
}
#[linkage = "weak"]
#[no_mangle]
fn main() -> i32 {
panic!("Cannot find main!");
}
pub fn read(fd: usize, buf: &mut [u8]) -> isize { sys_read(fd, buf) }
pub fn write(fd: usize, buf: &[u8]) -> isize { sys_write(fd, buf) }
pub fn exit(exit_code: i32) -> ! { sys_exit(exit_code); }
pub fn yield_() -> isize { sys_yield() }
pub fn get_time() -> isize { sys_get_time() }
pub fn getpid() -> isize { sys_getpid() }
pub fn fork() -> isize { sys_fork() }
pub fn exec(path: &str) -> isize { sys_exec(path) }
pub fn wait(exit_code: &mut i32) -> isize {
loop {
match sys_waitpid(-1, exit_code as *mut _) {
-2 => { yield_(); }
// -1 or a real pid
exit_pid => return exit_pid,
}
}
}
pub fn waitpid(pid: usize, exit_code: &mut i32) -> isize {
loop {
match sys_waitpid(pid as isize, exit_code as *mut _) {
-2 => { yield_(); }
// -1 or a real pid
exit_pid => return exit_pid,
}
}
}
pub fn sleep(period_ms: usize) {
let start = sys_get_time();
while sys_get_time() < start + period_ms as isize {
sys_yield();
}
}

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

@ -0,0 +1,59 @@
const SYSCALL_READ: usize = 63;
const SYSCALL_WRITE: usize = 64;
const SYSCALL_EXIT: usize = 93;
const SYSCALL_YIELD: usize = 124;
const SYSCALL_GET_TIME: usize = 169;
const SYSCALL_GETPID: usize = 172;
const SYSCALL_FORK: usize = 220;
const SYSCALL_EXEC: usize = 221;
const SYSCALL_WAITPID: usize = 260;
fn syscall(id: usize, args: [usize; 3]) -> isize {
let mut ret: isize;
unsafe {
llvm_asm!("ecall"
: "={x10}" (ret)
: "{x10}" (args[0]), "{x11}" (args[1]), "{x12}" (args[2]), "{x17}" (id)
: "memory"
: "volatile"
);
}
ret
}
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_exit(exit_code: i32) -> ! {
syscall(SYSCALL_EXIT, [exit_code as usize, 0, 0]);
panic!("sys_exit never returns!");
}
pub fn sys_yield() -> isize {
syscall(SYSCALL_YIELD, [0, 0, 0])
}
pub fn sys_get_time() -> isize {
syscall(SYSCALL_GET_TIME, [0, 0, 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) -> isize {
syscall(SYSCALL_EXEC, [path.as_ptr() as usize, 0, 0])
}
pub fn sys_waitpid(pid: isize, xstatus: *mut i32) -> isize {
syscall(SYSCALL_WAITPID, [pid as usize, xstatus as usize, 0])
}

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

1
ch6/.gitignore vendored

@ -0,0 +1 @@
target/*

@ -0,0 +1,10 @@
[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"

@ -0,0 +1,23 @@
TARGET := riscv64gc-unknown-none-elf
MODE := release
APP_DIR := src/bin
TARGET_DIR := target/$(TARGET)/$(MODE)
APPS := $(wildcard $(APP_DIR)/*.rs)
ELFS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%, $(APPS))
BINS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%.bin, $(APPS))
OBJDUMP := rust-objdump --arch-name=riscv64
OBJCOPY := rust-objcopy --binary-architecture=riscv64
elf: $(APPS)
@cargo build --release
binary: elf
$(foreach elf, $(ELFS), $(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf));)
build: binary
clean:
@cargo clean
.PHONY: elf binary build clean

@ -0,0 +1,29 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{fork, yield_, waitpid, exit, wait};
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
}

@ -0,0 +1,44 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
macro_rules! color_text {
($text:expr, $color:expr) => {{
format_args!("\x1b[{}m{}\x1b[0m", $color, $text)
}};
}
#[no_mangle]
pub fn main() -> i32 {
println!(
"{}{}{}{}{} {}{}{}{} {}{}{}{}{}{}",
color_text!("H", 31),
color_text!("e", 32),
color_text!("l", 33),
color_text!("l", 34),
color_text!("o", 35),
color_text!("R", 36),
color_text!("u", 37),
color_text!("s", 90),
color_text!("t", 91),
color_text!("u", 92),
color_text!("C", 93),
color_text!("o", 94),
color_text!("r", 95),
color_text!("e", 96),
color_text!("!", 97),
);
let text =
"reguler \x1b[4munderline\x1b[24m \x1b[7mreverse\x1b[27m \x1b[9mstrikethrough\x1b[29m";
println!("\x1b[47m{}\x1b[0m", color_text!(text, 30));
for i in 31..38 {
println!("{}", color_text!(text, i));
}
for i in 90..98 {
println!("{}", color_text!(text, i));
}
0
}

@ -0,0 +1,34 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{fork, wait, exit};
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
}

@ -0,0 +1,33 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{fork, wait, getpid, exit, sleep, get_time};
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 xstate: i32 = 0;
for _ in 0..NUM {
assert!(wait(&mut xstate) > 0);
assert_eq!(xstate, 0);
}
assert!(wait(&mut xstate) < 0);
println!("forktest2 test passed!");
0
}

@ -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 xstate: i32 = 0;
println!("ready waiting on parent process!");
assert_eq!(pid, wait(&mut xstate));
assert_eq!(xstate, 100);
println!("child process pid = {}, exit code = {}", pid, xstate);
0
}
}

@ -0,0 +1,37 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::{sleep, getpid, fork, exit, 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;
}
&mut 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
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save