Compare commits

...
Sign in to create a new pull request.

88 commits

Author SHA1 Message Date
Tateisi
f830ce95d6 lab04: sys_get_time
Some checks failed
Build Rust Doc And Run tests / build-doc (push) Has been cancelled
Build Rust Doc And Run tests / run-tests (push) Has been cancelled
2025-08-03 17:36:14 +08:00
Tateisi
58fa67414e time unmodified 2025-08-03 15:42:14 +08:00
Tateisi
cc1a9a4751 lab-04: mmap, munmap 2025-08-03 13:48:14 +08:00
Yifan Wu
eaacb4fa25 buildtools: feat #135: Check the version of QEMU ahead 2024-07-14 20:57:48 +08:00
Yifan Wu
5c2fb93267 mm: bugfix #133: MapArea::copy_data does not need &mut PageTable 2024-06-30 20:37:32 +08:00
Yifan Wu
decd33075f toolchain: Bump Rust to 1.80.0-nightly 2024-05-02 12:30:36 +08:00
Yifan Wu
a274521e80 Merge pull request #139 from jklincn/main 2024-01-26 22:14:30 +08:00
Yifan Wu
28c8a9e9fc config: use MEMORY_END from mod boards #136 2024-01-21 22:06:20 +08:00
Yifan Wu
f2c7dcc72b Merge pull request #134 from cndoit18/add-devcontainer
feat: support devcontainer
2024-01-21 01:24:43 +08:00
Yifan Wu
89f8a52636 Bump rust to version 1.77.0-nightly 2024-01-20 22:24:24 +08:00
Yifan Wu
821784080c Update doc-and-test.yml 2023-10-15 15:26:51 +08:00
Yifan Wu
7147cf95f7 Bump rust to version 1.75.0-nightly 2023-10-10 21:51:25 +08:00
Yifan Wu
90e969c0ae Fix issue #123 2023-05-31 21:17:53 +08:00
Yifan Wu
45477b345f Update os/Makefile: Added QEMU_ARGS 2023-04-30 01:07:40 +08:00
Yifan Wu
9c227174af cargo fmt 2023-03-30 22:56:11 +08:00
Yifan Wu
5a439b8689 fix ch4 2023-03-29 20:40:34 +08:00
Yifan Wu
22e52514a5 Use error! instead of println! when panicking 2023-03-29 20:39:19 +08:00
Yifan Wu
0ae57e1779 Using sbi-rt instead of asm && update rustsbi-qemu to latest
rustsbi-qemu version: a4f0bbe44d9f2f1069a9e5becd09f291e542852c
2023-03-29 20:39:15 +08:00
chyyuu
90b9070800 Merge pull request #99 from LearningOS/ch4
Ch4
2023-02-05 18:04:37 +08:00
闭浩扬
76ac3b9886 fmt & fixed 2023-02-04 22:43:58 +08:00
闭浩扬
f45e14bfeb add sbrk and a usertest 2023-02-04 22:15:17 +08:00
Yifan Wu
aaa42cfeb2 Remove unnecessary output. 2023-02-01 21:11:09 +08:00
Yu Chen
b3ea15a161 fix for rust-analyzer warning 2022-12-31 11:09:23 +08:00
Yifan Wu
b6f4c1aa56 user add fp support 2022-12-21 21:21:57 +08:00
Yifan Wu
0692fe93d8 Workflow: Remove k210 support. 2022-12-14 00:33:33 +08:00
Yifan Wu
f0da2f412b Remove K210 support. 2022-12-14 00:06:13 +08:00
Yifan Wu
6915bd2e63 Remove K210 support. 2022-12-13 22:48:37 +08:00
Yifan Wu
87a014df80 in entry.asm: boot_stack->boot_stack_lower_bound 2022-11-29 09:41:56 +08:00
YdrMaster
5f68e25700 build: update toolchain
Signed-off-by: YdrMaster <ydrml@hotmail.com>
2022-10-20 11:51:02 +08:00
Yifan Wu
628f05a2c3 Bump rustsbi-qemu to 701e891 2022-10-09 05:09:36 +08:00
Yifan Wu
24dd283737 Update Docker 2022-10-01 20:23:53 +08:00
Yu Chen
0ad6203b69 udpate rust-toolchain: nightly-2022-07-20, cargo-utils 0.36 2022-07-25 11:50:11 +08:00
Yu Chen
36ec7312b0 update Dockerfile: ubuntu 18.04-->20.04, QEMU-5.0-->7.0 2022-07-18 11:25:37 +08:00
Yu Chen
426d4bead8 support rust-analyzer for board_qemu features 2022-07-14 10:09:01 +08:00
Yu Chen
31cbc6d290 add CI autotest, update README 2022-06-24 00:00:37 +08:00
Yu Chen
d7815c6297 update rustsbi commit-id 74c103bc0f0c1931074c6edcd65a83fdff3cec33 in git@github.com:YdrMaster/rustsbi-qemu.git 2022-06-22 00:45:20 +08:00
Yu Chen
0fcea5a05b sbi spec uses a6, a7 as sbicall id, so we need to set a6 to 0 2022-06-22 00:45:17 +08:00
Yu Chen
ab99bc8c28 add cargo fmt in Makefile, and exec make fmt 2022-05-20 08:55:07 +08:00
Yifan Wu
e87bb122a8 Fix #69. 2022-05-13 00:58:11 -07:00
Yifan Wu
c8d138a7e3 Rollback rustsbi && debugging in release mode 2022-04-16 15:39:10 -07:00
Yifan Wu
9cfba2a415 Bump to Rust nightly-2022-04-11 rustsbi-qemu=5992db7 rustsbi-k210=1937341 default-mode=debug 2022-04-15 03:45:32 -07:00
Yu Chen
139fbc637a add #![deny(missing_docs)] AND #![deny(warnings)] in main.rs, and add more comments 2022-03-26 23:00:04 +08:00
Yu Chen
5946903948 update ch4 with more comments 2022-03-26 21:34:42 +08:00
Yu Chen
76056c8626 update README 2022-03-20 23:52:32 +08:00
Yu Chen
483ede54e4 add CI for build-doc 2022-03-20 21:37:32 +08:00
Yu Chen
90cf5659aa update ch4:add some exception support in trap_handler function, add two *_fault.rs apps 2022-03-11 19:11:40 +08:00
Yifan Wu
3731e84460 Add boards/ && cargo clippy 2022-01-24 16:12:56 -08:00
Yifan Wu
3a120122ba cargo clippy & fmt 2022-01-21 14:23:53 -08:00
Yifan Wu
e3601918ba Bump Rust to nightly-2022-01-19 2022-01-20 16:13:05 -08:00
Yifan Wu
96ea0506b0 Update .gitignore 2022-01-19 05:07:16 -08:00
Yifan Wu
3503a177ac Now PageTable::unmap calls PageTable::find_pte instead of PageTable::find_pte_create. 2022-01-13 17:04:23 -08:00
Yifan Wu
880656d6a0 Kernel cannot dump now. 2022-01-03 19:37:32 -08:00
Yifan Wu
55cda2a855 User base 0x0->0x10000 2022-01-02 14:12:31 -08:00
Yifan Wu
76b6ba65ac Bump to rust nightly-2022-01-01, feature global_asm,asm->stable 2022-01-01 01:48:25 -08:00
Yifan Wu
ac136366fb Bump to Rust nightly 2021-12-15 2021-12-22 03:26:36 -08:00
Yifan Wu
378e61f0fd Update os/Makefile, rm ... -f -> rm -f ... 2021-11-27 01:54:31 -08:00
Yu Chen
7eec642398 update .gitignore, README.md, dev-env-info.md 2021-11-20 16:26:06 +08:00
Yifan Wu
a8ccba1aed Now construction of PA/VA only uses 56/39 bits. 2021-11-13 02:55:14 -08:00
Yifan Wu
71797b294c rust->nightly-2021-10-15,cargo-binutils->0.3.3 2021-10-20 13:28:55 -07:00
Yifan Wu
37346c8d0f Remove spin::Mutex in frame_allocator and memory_set. 2021-09-08 00:26:22 +08:00
Yifan Wu
32b85b1d0c Update README 2021-08-26 20:00:35 +08:00
Yifan Wu
ffa94e149c Update rustc && rustsbi 2021-08-26 19:57:28 +08:00
Yu Chen
315e834762 update to rustc 1.56.0-nightly (08095fc1f 2021-07-26) 2021-07-29 16:10:10 +08:00
Yifan Wu
cb7249f0e7 Exclusive UPSafeCell: A RefCell wrapper 2021-07-20 00:07:56 +08:00
Yifan Wu
258516a3ca Replace llvm_asm! with asm 2021-07-18 19:21:16 +08:00
Yifan Wu
4388e6ddec rustc 1.55.0-nightly (2f391da2e 2021-07-14) 2021-07-18 00:13:53 +08:00
Yifan Wu
c076e41952 Bump rustsbi to qemu[d4968dd2] k210[b689314e]. 2021-07-17 10:44:36 +08:00
Yifan Wu
cf7c2abaa6 RefCell->UPSafeCell && TaskCx->TCB 2021-07-12 22:09:34 +08:00
Yifan Wu
bf53e6d211 Downgrade cargo-binutils to 0.2.0 2021-04-05 16:27:32 +08:00
Yifan Wu
8ca3b5826d Fix end_va bug in translated_byte_buffer 2021-03-23 19:18:12 +08:00
Yifan Wu
171accfadd Bump rustsbi to 0.2.0-alpha.1[81d53d8] 2021-03-09 16:08:56 +08:00
Yifan Wu
1c6c8757ee Link small sections in linker 2021-03-07 19:56:36 +08:00
Yifan Wu
5b0ceeb66a Do not fetch tools when running on qemu. 2021-03-06 03:21:55 +08:00
Yifan Wu
2486abe7ff Add Ubuntu18.04 docker 2021-02-28 06:32:26 +08:00
Yifan Wu
87e30fdaa8 Fix overflow bug when ceiling va 2021-02-24 03:48:55 +08:00
Yifan Wu
b520ed2597 Fix alignment in os/build.rs 2021-02-14 01:01:20 +08:00
Yifan Wu
ab062588aa Move kflash.py out of proj. 2021-02-08 11:26:57 +08:00
Yifan Wu
04af18cb47 Bump rustsbi to 0.1.1 && make config of qemu/k210 different 2021-02-07 18:17:00 +08:00
Yifan Wu
ca9bc70781 Update os/Makefile && Update rust to 2021-01-30 2021-02-02 17:44:45 +08:00
Yifan Wu
05fd1c8f85 Do not clone KERNEL_SPACE in mm::init 2021-01-27 07:29:16 +08:00
Yifan Wu
251758316f Fix k210 CLOCK_FREQ 2021-01-26 15:32:51 +08:00
Yifan Wu
ae751d62f2 Fix os/Makefile: Support macOS 2021-01-21 00:16:33 +08:00
Yu Chen
3ce58c3aa8 rust-toochain --> nightly 2021-01-16 19:27:41 +08:00
Yifan Wu
c33c0f8aea Update from previous chapters. 2021-01-06 00:19:10 +08:00
Yifan Wu
3851c2d561 Move some variable name to task_cx to task_cx_ptr2 2021-01-04 16:05:15 +08:00
Yifan Wu
6011a6a943 Flush icache before returning to user. 2021-01-03 17:02:34 +08:00
Yifan Wu
8522c2f4cd Remove meaningless sstatus::set_sie() when initializing. 2021-01-03 16:05:04 +08:00
Yifan Wu
f36a507c93 Update env. 2021-01-02 10:03:24 +08:00
70 changed files with 2025 additions and 1958 deletions

View file

@ -0,0 +1,21 @@
{
"name": "rcore-tutorial-v3",
"build": {
"dockerfile": "../Dockerfile",
"args": {
"QEMU_VERSION": "7.0.0",
"DEBIAN_FRONTEND": "noninteractive",
"GDB_VERSION": "14.1"
}
},
"postCreateCommand": "rustup show",
"customizations": {
"vscode": {
"extensions": [
"rust-lang.rust-analyzer",
"ms-vscode.cpptools",
"tamasfe.even-better-toml"
]
}
}
}

2
.dockerignore Normal file
View file

@ -0,0 +1,2 @@
*/*
!rust-toolchain.toml

68
.github/workflows/doc-and-test.yml vendored Normal file
View file

@ -0,0 +1,68 @@
name: Build Rust Doc And Run tests
on: [push]
env:
CARGO_TERM_COLOR: always
rust_toolchain: nightly-2024-01-18
jobs:
build-doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ env.rust_toolchain }}
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: ${{ env.rust_toolchain }}
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

23
.gitignore vendored
View file

@ -1,8 +1,25 @@
.idea/* .*/*
os/target/* !.github/*
os/.idea/* !.vscode/settings.json
!.devcontainer/devcontainer.json
.idea
**/Cargo.lock
**/target/
os/src/link_app.S os/src/link_app.S
os/src/linker.ld
os/last-*
os/Cargo.lock os/Cargo.lock
os/.gdb_history
user/build
user/target/* user/target/*
user/.idea/* user/.idea/*
user/Cargo.lock user/Cargo.lock
easy-fs/Cargo.lock
easy-fs/target/*
easy-fs-fuse/Cargo.lock
easy-fs-fuse/target/*
tools/
pushall.sh
*.bak

13
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,13 @@
{
// 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,
// "rust-analyzer.cargo.features": [
// "board_qemu"
// ]
}

72
Dockerfile Normal file
View file

@ -0,0 +1,72 @@
# syntax=docker/dockerfile:1
# Stage 1 Build QEMU
# - https://www.qemu.org/download/
# - https://wiki.qemu.org/Hosts/Linux#Building_QEMU_for_Linux
# - https://wiki.qemu.org/Documentation/Platforms/RISCV
FROM ubuntu:20.04 as build_qemu
ARG QEMU_VERSION=7.0.0
RUN sed -i 's/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list && \
sed -i 's/security.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y wget build-essential libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build
RUN wget https://download.qemu.org/qemu-${QEMU_VERSION}.tar.xz && \
tar xf qemu-${QEMU_VERSION}.tar.xz && \
cd qemu-${QEMU_VERSION} && \
./configure --target-list=riscv64-softmmu,riscv64-linux-user && \
make -j$(nproc) && \
make install
# Stage 2 Set Lab Environment
FROM ubuntu:20.04 as build
WORKDIR /tmp
# 2.0. Install general tools
RUN sed -i 's/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y jq curl git python3 wget build-essential \
# qemu dependency
libglib2.0-0 libfdt1 libpixman-1-0 zlib1g \
# gdb
gdb-multiarch
# 2.1. Copy qemu
COPY --from=build_qemu /usr/local/bin/* /usr/local/bin
# 2.2. Install Rust
# - https://www.rust-lang.org/tools/install
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH \
RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static \
RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
sh -s -- -y --no-modify-path --profile minimal --default-toolchain nightly
# 2.3. Build env for labs
# See os/Makefile `env:` for example.
# This avoids having to wait for these steps each time using a new container.
COPY rust-toolchain.toml rust-toolchain.toml
RUN rustup target add riscv64gc-unknown-none-elf && \
cargo install toml-cli cargo-binutils && \
RUST_VERSION=$(toml get -r rust-toolchain.toml toolchain.channel) && \
Components=$(toml get -r rust-toolchain.toml toolchain.components | jq -r 'join(" ")') && \
rustup install $RUST_VERSION && \
rustup component add --toolchain $RUST_VERSION $Components
# 2.4. Set GDB
RUN ln -s /usr/bin/gdb-multiarch /usr/bin/riscv64-unknown-elf-gdb
# Stage 3 Sanity checking
FROM build as test
RUN qemu-system-riscv64 --version && \
qemu-riscv64 --version && \
rustup --version && \
cargo --version && \
rustc --version && \
riscv64-unknown-elf-gdb --version

12
Makefile Normal file
View file

@ -0,0 +1,12 @@
DOCKER_TAG ?= rcore-tutorial-v3:latest
.PHONY: docker build_docker
docker:
docker run --rm -it -v ${PWD}:/mnt -w /mnt --name rcore-tutorial-v3 ${DOCKER_TAG} bash
build_docker:
docker build -t ${DOCKER_TAG} --target build .
fmt:
cd os ; cargo fmt; cd ..

296
README.md
View file

@ -1,2 +1,296 @@
# rCore-Tutorial-v3 # rCore-Tutorial-v3
rCore-Tutorial version 3. rCore-Tutorial version 3.6. 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
- 23/06/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 7.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-7.0.0.tar.xz
# extract to qemu-7.0.0/
$ tar xvJf qemu-7.0.0.tar.xz
$ cd qemu-7.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-7.0.0
export PATH=$PATH:/home/shinbokuow/Downloads/built/qemu-7.0.0/riscv64-softmmu
export PATH=$PATH:/home/shinbokuow/Downloads/built/qemu-7.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 7.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.6.0 (chapter 1-9) has been published, and we are still working on it.
* chapter 9: need more descripts about different I/O devices
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
* [x] code of chapter 9: device drivers based on interrupts, including UART, block, keyboard, mouse, gpu devices
* [x] add CI autotest and doc in github
### Todo(High priority)
* [ ] review documentation, current progress: 8/9
* [ ] use old fs image optionally, do not always rebuild the image
* [ ] 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
* [ ] add more os functions for understanding os conecpts and principles
### 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.

Binary file not shown.

Binary file not shown.

18
dev-env-info.md Normal file
View file

@ -0,0 +1,18 @@
# rCore-Tutorial-v3
rCore-Tutorial version 3.x
## Dependency
### Binaries
* rustc: 1.57.0-nightly (e1e9319d9 2021-10-14)
* cargo-binutils: 0.3.3
* qemu: 5.0.0
* rustsbi-lib: 0.2.0-alpha.4
rustsbi-qemu: d4968dd2
rustsbi-k210: b689314e

View file

@ -2,7 +2,7 @@
name = "os" name = "os"
version = "0.1.0" version = "0.1.0"
authors = ["Yifan Wu <shinbokuow@163.com>"] authors = ["Yifan Wu <shinbokuow@163.com>"]
edition = "2018" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -10,10 +10,10 @@ edition = "2018"
riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] } riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] }
lazy_static = { version = "1.4.0", features = ["spin_no_std"] } lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
buddy_system_allocator = "0.6" buddy_system_allocator = "0.6"
spin = "0.7.0"
bitflags = "1.2.1" bitflags = "1.2.1"
xmas-elf = "0.7.0" xmas-elf = "0.7.0"
log = "0.4"
sbi-rt = { version = "0.0.2", features = ["legacy"] }
[features] [profile.release]
board_qemu = [] debug = true
board_k210 = []

View file

@ -3,17 +3,20 @@ TARGET := riscv64gc-unknown-none-elf
MODE := release MODE := release
KERNEL_ELF := target/$(TARGET)/$(MODE)/os KERNEL_ELF := target/$(TARGET)/$(MODE)/os
KERNEL_BIN := $(KERNEL_ELF).bin KERNEL_BIN := $(KERNEL_ELF).bin
KERNEL_ENTRY_PA := 0x80020000
DISASM_TMP := target/$(TARGET)/$(MODE)/asm DISASM_TMP := target/$(TARGET)/$(MODE)/asm
# BOARD # BOARD
BOARD ?= qemu BOARD := qemu
SBI ?= rustsbi SBI ?= rustsbi
BOOTLOADER := ../bootloader/$(SBI)-$(BOARD).bin BOOTLOADER := ../bootloader/$(SBI)-$(BOARD).bin
# Run K210 # Building mode argument
K210-SERIALPORT = /dev/ttyUSB0 ifeq ($(MODE), release)
K210-BURNER = ../tools/kflash.py MODE_ARG := --release
endif
# KERNEL ENTRY
KERNEL_ENTRY_PA := 0x80200000
# Binutils # Binutils
OBJDUMP := rust-objdump --arch-name=riscv64 OBJDUMP := rust-objdump --arch-name=riscv64
@ -22,14 +25,23 @@ OBJCOPY := rust-objcopy --binary-architecture=riscv64
# Disassembly # Disassembly
DISASM ?= -x DISASM ?= -x
build: $(KERNEL_BIN) build: env $(KERNEL_BIN)
env:
(rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add $(TARGET)
cargo install cargo-binutils
rustup component add rust-src
rustup component add llvm-tools-preview
$(KERNEL_BIN): kernel $(KERNEL_BIN): kernel
@$(OBJCOPY) $(KERNEL_ELF) --strip-all -O binary $@ @$(OBJCOPY) $(KERNEL_ELF) --strip-all -O binary $@
kernel: kernel:
@cd ../user && make build @cd ../user && make build
@cargo build --release --features "board_$(BOARD)" @echo Platform: $(BOARD)
@cp src/linker-$(BOARD).ld src/linker.ld
@cargo build $(MODE_ARG)
@rm src/linker.ld
clean: clean:
@cargo clean @cargo clean
@ -44,26 +56,28 @@ disasm-vim: kernel
run: run-inner run: run-inner
run-inner: build QEMU_ARGS := -machine virt \
ifeq ($(BOARD),qemu) -nographic \
@qemu-system-riscv64 \ -bios $(BOOTLOADER) \
-machine virt \ -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA)
-nographic \
-bios $(BOOTLOADER) \
-device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA)
else
@cp $(BOOTLOADER) $(BOOTLOADER).copy
@dd if=$(KERNEL_BIN) of=$(BOOTLOADER).copy bs=128K seek=1
@mv $(BOOTLOADER).copy $(KERNEL_BIN)
@sudo chmod 777 $(K210-SERIALPORT)
python3 $(K210-BURNER) -p $(K210-SERIALPORT) -b 1500000 $(KERNEL_BIN)
miniterm --eol LF --dtr 0 --rts 0 --filter direct $(K210-SERIALPORT) 115200
endif
debug: build QEMU_NAME := qemu-system-riscv64
qemu-version-check:
@sh scripts/qemu-ver-check.sh $(QEMU_NAME)
run-inner: qemu-version-check build
@qemu-system-riscv64 $(QEMU_ARGS)
debug: qemu-version-check build
@tmux new-session -d \ @tmux new-session -d \
"qemu-system-riscv64 -machine virt -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) -s -S" && \ "qemu-system-riscv64 $(QEMU_ARGS) -s -S" && \
tmux split-window -h "riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234'" && \ tmux split-window -h "riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234'" && \
tmux -2 attach-session -d tmux -2 attach-session -d
.PHONY: build kernel clean disasm disasm-vim run-inner gdbserver: qemu-version-check build
@qemu-system-riscv64 $(QEMU_ARGS) -s -S
gdbclient:
@riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234'
.PHONY: build env kernel clean disasm disasm-vim run-inner gdbserver gdbclient qemu-version-check

View file

@ -1,5 +1,5 @@
use std::fs::{read_dir, File};
use std::io::{Result, Write}; use std::io::{Result, Write};
use std::fs::{File, read_dir};
fn main() { fn main() {
println!("cargo:rerun-if-changed=../user/src/"); println!("cargo:rerun-if-changed=../user/src/");
@ -22,12 +22,16 @@ fn insert_app_data() -> Result<()> {
.collect(); .collect();
apps.sort(); apps.sort();
writeln!(f, r#" writeln!(
.align 4 f,
r#"
.align 3
.section .data .section .data
.global _num_app .global _num_app
_num_app: _num_app:
.quad {}"#, apps.len())?; .quad {}"#,
apps.len()
)?;
for i in 0..apps.len() { for i in 0..apps.len() {
writeln!(f, r#" .quad app_{}_start"#, i)?; writeln!(f, r#" .quad app_{}_start"#, i)?;
@ -36,13 +40,18 @@ _num_app:
for (idx, app) in apps.iter().enumerate() { for (idx, app) in apps.iter().enumerate() {
println!("app_{}: {}", idx, app); println!("app_{}: {}", idx, app);
writeln!(f, r#" writeln!(
f,
r#"
.section .data .section .data
.global app_{0}_start .global app_{0}_start
.global app_{0}_end .global app_{0}_end
.align 3
app_{0}_start: app_{0}_start:
.incbin "{2}{1}" .incbin "{2}{1}"
app_{0}_end:"#, idx, app, TARGET_PATH)?; app_{0}_end:"#,
idx, app, TARGET_PATH
)?;
} }
Ok(()) Ok(())
} }

View file

@ -0,0 +1,26 @@
#!/bin/sh
# Argument1: The filename of qemu executable, e.g. qemu-system-riscv64
QEMU_PATH=$(which $1)
RET=$?
MINIMUM_MAJOR_VERSION=7
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
if [ $RET != 0 ]
then
echo "$1 not found"
exit 1
else
QEMU_VERSION=$($1 --version|head -n 1|awk '{print $4}')
MAJOR_VERSION=$(echo $QEMU_VERSION|cut -c1-1)
if [ $MAJOR_VERSION -lt $MINIMUM_MAJOR_VERSION ]
then
echo "${RED}Error: Required major version of QEMU is ${MINIMUM_MAJOR_VERSION}, " \
"but current is ${QEMU_VERSION}.${NC}"
exit 1
else
echo "${GREEN}QEMU version is ${QEMU_VERSION}(>=${MINIMUM_MAJOR_VERSION}), OK!${NC}"
exit 0
fi
fi

8
os/src/boards/qemu.rs Normal file
View file

@ -0,0 +1,8 @@
//! Constants used in rCore for qemu
pub const CLOCK_FREQ: usize = 12500000;
pub const MEMORY_END: usize = 0x8800_0000;
pub const MMIO: &[(usize, usize)] = &[
(0x0010_0000, 0x00_2000), // VIRT_TEST/RTC in virt machine
];

View file

@ -1,7 +1,8 @@
//! Constants used in rCore
pub const USER_STACK_SIZE: usize = 4096 * 2; pub const USER_STACK_SIZE: usize = 4096 * 2;
pub const KERNEL_STACK_SIZE: usize = 4096 * 2; pub const KERNEL_STACK_SIZE: usize = 4096 * 2;
pub const KERNEL_HEAP_SIZE: usize = 0x30_0000; pub const KERNEL_HEAP_SIZE: usize = 0x30_0000;
pub const MEMORY_END: usize = 0x80800000;
pub const PAGE_SIZE: usize = 0x1000; pub const PAGE_SIZE: usize = 0x1000;
pub const PAGE_SIZE_BITS: usize = 0xc; pub const PAGE_SIZE_BITS: usize = 0xc;
@ -14,8 +15,4 @@ pub fn kernel_stack_position(app_id: usize) -> (usize, usize) {
(bottom, top) (bottom, top)
} }
#[cfg(feature = "board_k210")] pub use crate::board::{CLOCK_FREQ, MEMORY_END, MMIO};
pub const CPU_FREQ: usize = 10000000;
#[cfg(feature = "board_qemu")]
pub const CPU_FREQ: usize = 12500000;

View file

@ -1,5 +1,7 @@
use core::fmt::{self, Write}; //! SBI console driver, for text output
use crate::sbi::console_putchar; use crate::sbi::console_putchar;
use core::fmt::{self, Write};
struct Stdout; struct Stdout;
@ -17,6 +19,7 @@ pub fn print(args: fmt::Arguments) {
} }
#[macro_export] #[macro_export]
/// print string macro
macro_rules! print { macro_rules! print {
($fmt: literal $(, $($arg: tt)+)?) => { ($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!($fmt $(, $($arg)+)?)); $crate::console::print(format_args!($fmt $(, $($arg)+)?));
@ -24,10 +27,9 @@ macro_rules! print {
} }
#[macro_export] #[macro_export]
/// println string macro
macro_rules! println { macro_rules! println {
($fmt: literal $(, $($arg: tt)+)?) => { ($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?)); $crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
} }
} }

View file

@ -5,8 +5,8 @@ _start:
call rust_main call rust_main
.section .bss.stack .section .bss.stack
.globl boot_stack .globl boot_stack_lower_bound
boot_stack: boot_stack_lower_bound:
.space 4096 * 16 .space 4096 * 16
.globl boot_stack_top .globl boot_stack_top
boot_stack_top: boot_stack_top:

View file

@ -1,12 +1,21 @@
use core::panic::PanicInfo; //! The panic handler
use crate::sbi::shutdown; use crate::sbi::shutdown;
use core::panic::PanicInfo;
use log::*;
#[panic_handler] #[panic_handler]
/// panic handler
fn panic(info: &PanicInfo) -> ! { fn panic(info: &PanicInfo) -> ! {
if let Some(location) = info.location() { if let Some(location) = info.location() {
println!("[kernel] Panicked at {}:{} {}", location.file(), location.line(), info.message().unwrap()); error!(
"[kernel] Panicked at {}:{} {}",
location.file(),
location.line(),
info.message().unwrap()
);
} else { } else {
println!("[kernel] Panicked: {}", info.message().unwrap()); error!("[kernel] Panicked: {}", info.message().unwrap());
} }
shutdown() shutdown(true)
} }

View file

@ -1,6 +1,6 @@
OUTPUT_ARCH(riscv) OUTPUT_ARCH(riscv)
ENTRY(_start) ENTRY(_start)
BASE_ADDRESS = 0x80020000; BASE_ADDRESS = 0x80200000;
SECTIONS SECTIONS
{ {
@ -22,6 +22,7 @@ SECTIONS
srodata = .; srodata = .;
.rodata : { .rodata : {
*(.rodata .rodata.*) *(.rodata .rodata.*)
*(.srodata .srodata.*)
} }
. = ALIGN(4K); . = ALIGN(4K);
@ -29,6 +30,7 @@ SECTIONS
sdata = .; sdata = .;
.data : { .data : {
*(.data .data.*) *(.data .data.*)
*(.sdata .sdata.*)
} }
. = ALIGN(4K); . = ALIGN(4K);
@ -38,6 +40,7 @@ SECTIONS
*(.bss.stack) *(.bss.stack)
sbss = .; sbss = .;
*(.bss .bss.*) *(.bss .bss.*)
*(.sbss .sbss.*)
} }
. = ALIGN(4K); . = ALIGN(4K);

View file

@ -1,20 +1,26 @@
//! Loading user applications into memory
/// Get the total number of applications.
pub fn get_num_app() -> usize { pub fn get_num_app() -> usize {
extern "C" { fn _num_app(); } extern "C" {
fn _num_app();
}
unsafe { (_num_app as usize as *const usize).read_volatile() } unsafe { (_num_app as usize as *const usize).read_volatile() }
} }
/// get applications data
pub fn get_app_data(app_id: usize) -> &'static [u8] { pub fn get_app_data(app_id: usize) -> &'static [u8] {
extern "C" { fn _num_app(); } extern "C" {
fn _num_app();
}
let num_app_ptr = _num_app as usize as *const usize; let num_app_ptr = _num_app as usize as *const usize;
let num_app = get_num_app(); let num_app = get_num_app();
let app_start = unsafe { let app_start = unsafe { core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1) };
core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1)
};
assert!(app_id < num_app); assert!(app_id < num_app);
unsafe { unsafe {
core::slice::from_raw_parts( core::slice::from_raw_parts(
app_start[app_id] as *const u8, app_start[app_id] as *const u8,
app_start[app_id + 1] - app_start[app_id] app_start[app_id + 1] - app_start[app_id],
) )
} }
} }

View file

@ -1,9 +1,25 @@
//! The main module and entrypoint
//!
//! Various facilities of the kernels are implemented as submodules. The most
//! important ones are:
//!
//! - [`trap`]: Handles all cases of switching from userspace to the kernel
//! - [`task`]: Task management
//! - [`syscall`]: System call handling and implementation
//!
//! The operating system also starts in this module. Kernel code starts
//! executing from `entry.asm`, after which [`rust_main()`] is called to
//! initialize various pieces of functionality. (See its source code for
//! details.)
//!
//! We then call [`task::run_first_task()`] and for the first time go to
//! userspace.
#![deny(missing_docs)]
#![deny(warnings)]
#![no_std] #![no_std]
#![no_main] #![no_main]
#![feature(global_asm)]
#![feature(llvm_asm)]
#![feature(panic_info_message)] #![feature(panic_info_message)]
#![feature(const_in_array_repeat_expressions)]
#![feature(alloc_error_handler)] #![feature(alloc_error_handler)]
extern crate alloc; extern crate alloc;
@ -11,32 +27,39 @@ extern crate alloc;
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
#[path = "boards/qemu.rs"]
mod board;
#[macro_use] #[macro_use]
mod console; mod console;
mod lang_items;
mod sbi;
mod syscall;
mod trap;
mod loader;
mod config; mod config;
mod task; mod lang_items;
mod timer; mod loader;
mod mm; mod mm;
mod sbi;
mod sync;
pub mod syscall;
pub mod task;
mod timer;
pub mod trap;
global_asm!(include_str!("entry.asm")); core::arch::global_asm!(include_str!("entry.asm"));
global_asm!(include_str!("link_app.S")); core::arch::global_asm!(include_str!("link_app.S"));
/// clear BSS segment
fn clear_bss() { fn clear_bss() {
extern "C" { extern "C" {
fn sbss(); fn sbss();
fn ebss(); fn ebss();
} }
(sbss as usize..ebss as usize).for_each(|a| { unsafe {
unsafe { (a as *mut u8).write_volatile(0) } core::slice::from_raw_parts_mut(sbss as usize as *mut u8, ebss as usize - sbss as usize)
}); .fill(0);
}
} }
#[no_mangle] #[no_mangle]
/// the rust entry-point of os
pub fn rust_main() -> ! { pub fn rust_main() -> ! {
clear_bss(); clear_bss();
println!("[kernel] Hello, world!"); println!("[kernel] Hello, world!");
@ -49,4 +72,4 @@ pub fn rust_main() -> ! {
timer::set_next_trigger(); timer::set_next_trigger();
task::run_first_task(); task::run_first_task();
panic!("Unreachable in rust_main!"); panic!("Unreachable in rust_main!");
} }

View file

@ -1,17 +1,28 @@
use crate::config::{PAGE_SIZE, PAGE_SIZE_BITS}; //! Implementation of physical and virtual address and page number.
use super::PageTableEntry; use super::PageTableEntry;
use crate::config::{PAGE_SIZE, PAGE_SIZE_BITS};
use core::fmt::{self, Debug, Formatter}; use core::fmt::{self, Debug, Formatter};
/// physical address
const PA_WIDTH_SV39: usize = 56;
const VA_WIDTH_SV39: usize = 39;
const PPN_WIDTH_SV39: usize = PA_WIDTH_SV39 - PAGE_SIZE_BITS;
const VPN_WIDTH_SV39: usize = VA_WIDTH_SV39 - PAGE_SIZE_BITS;
/// Definitions /// Definitions
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct PhysAddr(pub usize); pub struct PhysAddr(pub usize);
/// virtual address
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct VirtAddr(pub usize); pub struct VirtAddr(pub usize);
/// physical page number
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct PhysPageNum(pub usize); pub struct PhysPageNum(pub usize);
/// virtual page number
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct VirtPageNum(pub usize); pub struct VirtPageNum(pub usize);
@ -43,35 +54,67 @@ impl Debug for PhysPageNum {
/// usize -> T: usize.into() /// usize -> T: usize.into()
impl From<usize> for PhysAddr { impl From<usize> for PhysAddr {
fn from(v: usize) -> Self { Self(v) } fn from(v: usize) -> Self {
Self(v & ((1 << PA_WIDTH_SV39) - 1))
}
} }
impl From<usize> for PhysPageNum { impl From<usize> for PhysPageNum {
fn from(v: usize) -> Self { Self(v) } fn from(v: usize) -> Self {
Self(v & ((1 << PPN_WIDTH_SV39) - 1))
}
} }
impl From<usize> for VirtAddr { impl From<usize> for VirtAddr {
fn from(v: usize) -> Self { Self(v) } fn from(v: usize) -> Self {
Self(v & ((1 << VA_WIDTH_SV39) - 1))
}
} }
impl From<usize> for VirtPageNum { impl From<usize> for VirtPageNum {
fn from(v: usize) -> Self { Self(v) } fn from(v: usize) -> Self {
Self(v & ((1 << VPN_WIDTH_SV39) - 1))
}
} }
impl From<PhysAddr> for usize { impl From<PhysAddr> for usize {
fn from(v: PhysAddr) -> Self { v.0 } fn from(v: PhysAddr) -> Self {
v.0
}
} }
impl From<PhysPageNum> for usize { impl From<PhysPageNum> for usize {
fn from(v: PhysPageNum) -> Self { v.0 } fn from(v: PhysPageNum) -> Self {
v.0
}
} }
impl From<VirtAddr> for usize { impl From<VirtAddr> for usize {
fn from(v: VirtAddr) -> Self { v.0 } fn from(v: VirtAddr) -> Self {
if v.0 >= (1 << (VA_WIDTH_SV39 - 1)) {
v.0 | (!((1 << VA_WIDTH_SV39) - 1))
} else {
v.0
}
}
} }
impl From<VirtPageNum> for usize { impl From<VirtPageNum> for usize {
fn from(v: VirtPageNum) -> Self { v.0 } fn from(v: VirtPageNum) -> Self {
v.0
}
} }
impl VirtAddr { impl VirtAddr {
pub fn floor(&self) -> VirtPageNum { VirtPageNum(self.0 / PAGE_SIZE) } pub fn floor(&self) -> VirtPageNum {
pub fn ceil(&self) -> VirtPageNum { VirtPageNum((self.0 + PAGE_SIZE - 1) / PAGE_SIZE) } VirtPageNum(self.0 / PAGE_SIZE)
pub fn page_offset(&self) -> usize { self.0 & (PAGE_SIZE - 1) } }
pub fn aligned(&self) -> bool { self.page_offset() == 0 } pub fn ceil(&self) -> VirtPageNum {
if self.0 == 0 {
VirtPageNum(0)
} else {
VirtPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE)
}
}
pub fn page_offset(&self) -> usize {
self.0 & (PAGE_SIZE - 1)
}
pub fn aligned(&self) -> bool {
self.page_offset() == 0
}
} }
impl From<VirtAddr> for VirtPageNum { impl From<VirtAddr> for VirtPageNum {
fn from(v: VirtAddr) -> Self { fn from(v: VirtAddr) -> Self {
@ -80,13 +123,27 @@ impl From<VirtAddr> for VirtPageNum {
} }
} }
impl From<VirtPageNum> for VirtAddr { impl From<VirtPageNum> for VirtAddr {
fn from(v: VirtPageNum) -> Self { Self(v.0 << PAGE_SIZE_BITS) } fn from(v: VirtPageNum) -> Self {
Self(v.0 << PAGE_SIZE_BITS)
}
} }
impl PhysAddr { impl PhysAddr {
pub fn floor(&self) -> PhysPageNum { PhysPageNum(self.0 / PAGE_SIZE) } pub fn floor(&self) -> PhysPageNum {
pub fn ceil(&self) -> PhysPageNum { PhysPageNum((self.0 + PAGE_SIZE - 1) / PAGE_SIZE) } PhysPageNum(self.0 / PAGE_SIZE)
pub fn page_offset(&self) -> usize { self.0 & (PAGE_SIZE - 1) } }
pub fn aligned(&self) -> bool { self.page_offset() == 0 } pub fn ceil(&self) -> PhysPageNum {
if self.0 == 0 {
PhysPageNum(0)
} else {
PhysPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE)
}
}
pub fn page_offset(&self) -> usize {
self.0 & (PAGE_SIZE - 1)
}
pub fn aligned(&self) -> bool {
self.page_offset() == 0
}
} }
impl From<PhysAddr> for PhysPageNum { impl From<PhysAddr> for PhysPageNum {
fn from(v: PhysAddr) -> Self { fn from(v: PhysAddr) -> Self {
@ -95,7 +152,9 @@ impl From<PhysAddr> for PhysPageNum {
} }
} }
impl From<PhysPageNum> for PhysAddr { impl From<PhysPageNum> for PhysAddr {
fn from(v: PhysPageNum) -> Self { Self(v.0 << PAGE_SIZE_BITS) } fn from(v: PhysPageNum) -> Self {
Self(v.0 << PAGE_SIZE_BITS)
}
} }
impl VirtPageNum { impl VirtPageNum {
@ -112,22 +171,16 @@ impl VirtPageNum {
impl PhysPageNum { impl PhysPageNum {
pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] { pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] {
let pa: PhysAddr = self.clone().into(); let pa: PhysAddr = (*self).into();
unsafe { unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512) }
core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512)
}
} }
pub fn get_bytes_array(&self) -> &'static mut [u8] { pub fn get_bytes_array(&self) -> &'static mut [u8] {
let pa: PhysAddr = self.clone().into(); let pa: PhysAddr = (*self).into();
unsafe { unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096) }
core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096)
}
} }
pub fn get_mut<T>(&self) -> &'static mut T { pub fn get_mut<T>(&self) -> &'static mut T {
let pa: PhysAddr = self.clone().into(); let pa: PhysAddr = (*self).into();
unsafe { unsafe { (pa.0 as *mut T).as_mut().unwrap() }
(pa.0 as *mut T).as_mut().unwrap()
}
} }
} }
@ -141,41 +194,59 @@ impl StepByOne for VirtPageNum {
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct SimpleRange<T> where /// a simple range structure for type T
T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { pub struct SimpleRange<T>
where
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
{
l: T, l: T,
r: T, r: T,
} }
impl<T> SimpleRange<T> where impl<T> SimpleRange<T>
T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { where
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
{
pub fn new(start: T, end: T) -> Self { pub fn new(start: T, end: T) -> Self {
assert!(start <= end, "start {:?} > end {:?}!", start, end); assert!(start <= end, "start {:?} > end {:?}!", start, end);
Self { l: start, r: end } Self { l: start, r: end }
} }
pub fn get_start(&self) -> T { self.l } pub fn get_start(&self) -> T {
pub fn get_end(&self) -> T { self.r } self.l
}
pub fn get_end(&self) -> T {
self.r
}
} }
impl<T> IntoIterator for SimpleRange<T> where impl<T> IntoIterator for SimpleRange<T>
T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { where
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
{
type Item = T; type Item = T;
type IntoIter = SimpleRangeIterator<T>; type IntoIter = SimpleRangeIterator<T>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
SimpleRangeIterator::new(self.l, self.r) SimpleRangeIterator::new(self.l, self.r)
} }
} }
pub struct SimpleRangeIterator<T> where /// iterator for the simple range structure
T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { pub struct SimpleRangeIterator<T>
where
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
{
current: T, current: T,
end: T, end: T,
} }
impl<T> SimpleRangeIterator<T> where impl<T> SimpleRangeIterator<T>
T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { where
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
{
pub fn new(l: T, r: T) -> Self { pub fn new(l: T, r: T) -> Self {
Self { current: l, end: r, } Self { current: l, end: r }
} }
} }
impl<T> Iterator for SimpleRangeIterator<T> where impl<T> Iterator for SimpleRangeIterator<T>
T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { where
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
{
type Item = T; type Item = T;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.current == self.end { if self.current == self.end {
@ -187,4 +258,6 @@ impl<T> Iterator for SimpleRangeIterator<T> where
} }
} }
} }
pub type VPNRange = SimpleRange<VirtPageNum>;
/// a simple range structure for virtual page number
pub type VPNRange = SimpleRange<VirtPageNum>;

View file

@ -1,10 +1,14 @@
use super::{PhysAddr, PhysPageNum}; //! Implementation of [`FrameAllocator`] which
use alloc::vec::Vec; //! controls all the frames in the operating system.
use spin::Mutex;
use crate::config::MEMORY_END;
use lazy_static::*;
use core::fmt::{self, Debug, Formatter};
use super::{PhysAddr, PhysPageNum};
use crate::config::MEMORY_END;
use crate::sync::UPSafeCell;
use alloc::vec::Vec;
use core::fmt::{self, Debug, Formatter};
use lazy_static::*;
/// manage a frame which has the same lifecycle as the tracker
pub struct FrameTracker { pub struct FrameTracker {
pub ppn: PhysPageNum, pub ppn: PhysPageNum,
} }
@ -38,6 +42,7 @@ trait FrameAllocator {
fn dealloc(&mut self, ppn: PhysPageNum); fn dealloc(&mut self, ppn: PhysPageNum);
} }
/// an implementation for frame allocator
pub struct StackFrameAllocator { pub struct StackFrameAllocator {
current: usize, current: usize,
end: usize, end: usize,
@ -61,22 +66,17 @@ impl FrameAllocator for StackFrameAllocator {
fn alloc(&mut self) -> Option<PhysPageNum> { fn alloc(&mut self) -> Option<PhysPageNum> {
if let Some(ppn) = self.recycled.pop() { if let Some(ppn) = self.recycled.pop() {
Some(ppn.into()) Some(ppn.into())
} else if self.current == self.end {
None
} else { } else {
if self.current == self.end { self.current += 1;
None Some((self.current - 1).into())
} else {
self.current += 1;
Some((self.current - 1).into())
}
} }
} }
fn dealloc(&mut self, ppn: PhysPageNum) { fn dealloc(&mut self, ppn: PhysPageNum) {
let ppn = ppn.0; let ppn = ppn.0;
// validity check // validity check
if ppn >= self.current || self.recycled if ppn >= self.current || self.recycled.iter().any(|&v| v == ppn) {
.iter()
.find(|&v| {*v == ppn})
.is_some() {
panic!("Frame ppn={:#x} has not been allocated!", ppn); panic!("Frame ppn={:#x} has not been allocated!", ppn);
} }
// recycle // recycle
@ -87,33 +87,37 @@ impl FrameAllocator for StackFrameAllocator {
type FrameAllocatorImpl = StackFrameAllocator; type FrameAllocatorImpl = StackFrameAllocator;
lazy_static! { lazy_static! {
pub static ref FRAME_ALLOCATOR: Mutex<FrameAllocatorImpl> = /// frame allocator instance through lazy_static!
Mutex::new(FrameAllocatorImpl::new()); pub static ref FRAME_ALLOCATOR: UPSafeCell<FrameAllocatorImpl> =
unsafe { UPSafeCell::new(FrameAllocatorImpl::new()) };
} }
/// initiate the frame allocator using `ekernel` and `MEMORY_END`
pub fn init_frame_allocator() { pub fn init_frame_allocator() {
extern "C" { extern "C" {
fn ekernel(); fn ekernel();
} }
FRAME_ALLOCATOR FRAME_ALLOCATOR.exclusive_access().init(
.lock() PhysAddr::from(ekernel as usize).ceil(),
.init(PhysAddr::from(ekernel as usize).ceil(), PhysAddr::from(MEMORY_END).floor()); PhysAddr::from(MEMORY_END).floor(),
);
} }
/// allocate a frame
pub fn frame_alloc() -> Option<FrameTracker> { pub fn frame_alloc() -> Option<FrameTracker> {
FRAME_ALLOCATOR FRAME_ALLOCATOR
.lock() .exclusive_access()
.alloc() .alloc()
.map(|ppn| FrameTracker::new(ppn)) .map(FrameTracker::new)
} }
/// deallocate a frame
fn frame_dealloc(ppn: PhysPageNum) { fn frame_dealloc(ppn: PhysPageNum) {
FRAME_ALLOCATOR FRAME_ALLOCATOR.exclusive_access().dealloc(ppn);
.lock()
.dealloc(ppn);
} }
#[allow(unused)] #[allow(unused)]
/// a simple test for frame allocator
pub fn frame_allocator_test() { pub fn frame_allocator_test() {
let mut v: Vec<FrameTracker> = Vec::new(); let mut v: Vec<FrameTracker> = Vec::new();
for i in 0..5 { for i in 0..5 {
@ -129,4 +133,4 @@ pub fn frame_allocator_test() {
} }
drop(v); drop(v);
println!("frame_allocator_test passed!"); println!("frame_allocator_test passed!");
} }

View file

@ -1,16 +1,22 @@
use buddy_system_allocator::LockedHeap; //! The global allocator
use crate::config::KERNEL_HEAP_SIZE; use crate::config::KERNEL_HEAP_SIZE;
use buddy_system_allocator::LockedHeap;
#[global_allocator] #[global_allocator]
/// heap allocator instance
static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty(); static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty();
#[alloc_error_handler] #[alloc_error_handler]
/// panic when heap allocation error occurs
pub fn handle_alloc_error(layout: core::alloc::Layout) -> ! { pub fn handle_alloc_error(layout: core::alloc::Layout) -> ! {
panic!("Heap allocation error, layout = {:?}", layout); panic!("Heap allocation error, layout = {:?}", layout);
} }
/// heap space ([u8; KERNEL_HEAP_SIZE])
static mut HEAP_SPACE: [u8; KERNEL_HEAP_SIZE] = [0; KERNEL_HEAP_SIZE]; static mut HEAP_SPACE: [u8; KERNEL_HEAP_SIZE] = [0; KERNEL_HEAP_SIZE];
/// initiate heap allocator
pub fn init_heap() { pub fn init_heap() {
unsafe { unsafe {
HEAP_ALLOCATOR HEAP_ALLOCATOR
@ -36,8 +42,8 @@ pub fn heap_test() {
for i in 0..500 { for i in 0..500 {
v.push(i); v.push(i);
} }
for i in 0..500 { for (i, val) in v.iter().take(500).enumerate() {
assert_eq!(v[i], i); assert_eq!(*val, i);
} }
assert!(bss_range.contains(&(v.as_ptr() as usize))); assert!(bss_range.contains(&(v.as_ptr() as usize)));
drop(v); drop(v);

View file

@ -1,20 +1,17 @@
use super::{PageTable, PageTableEntry, PTEFlags}; //! Implementation of [`MapArea`] and [`MemorySet`].
use super::{VirtPageNum, VirtAddr, PhysPageNum, PhysAddr};
use super::{FrameTracker, frame_alloc}; use super::{frame_alloc, FrameTracker};
use super::{VPNRange, StepByOne}; use super::{PTEFlags, PageTable, PageTableEntry};
use super::{PhysAddr, PhysPageNum, VirtAddr, VirtPageNum};
use super::{StepByOne, VPNRange};
use crate::config::{MEMORY_END, MMIO, PAGE_SIZE, TRAMPOLINE, TRAP_CONTEXT, USER_STACK_SIZE};
use crate::sync::UPSafeCell;
use alloc::collections::BTreeMap; use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use riscv::register::satp;
use alloc::sync::Arc; use alloc::sync::Arc;
use alloc::vec::Vec;
use core::arch::asm;
use lazy_static::*; use lazy_static::*;
use spin::Mutex; use riscv::register::satp;
use crate::config::{
MEMORY_END,
PAGE_SIZE,
TRAMPOLINE,
TRAP_CONTEXT,
USER_STACK_SIZE
};
extern "C" { extern "C" {
fn stext(); fn stext();
@ -30,11 +27,12 @@ extern "C" {
} }
lazy_static! { lazy_static! {
pub static ref KERNEL_SPACE: Arc<Mutex<MemorySet>> = Arc::new(Mutex::new( /// a memory set instance through lazy_static! managing kernel space
MemorySet::new_kernel() pub static ref KERNEL_SPACE: Arc<UPSafeCell<MemorySet>> =
)); Arc::new(unsafe { UPSafeCell::new(MemorySet::new_kernel()) });
} }
/// memory set structure, controls virtual-memory space
pub struct MemorySet { pub struct MemorySet {
page_table: PageTable, page_table: PageTable,
areas: Vec<MapArea>, areas: Vec<MapArea>,
@ -51,18 +49,21 @@ impl MemorySet {
self.page_table.token() self.page_table.token()
} }
/// Assume that no conflicts. /// Assume that no conflicts.
pub fn insert_framed_area(&mut self, start_va: VirtAddr, end_va: VirtAddr, permission: MapPermission) { pub fn insert_framed_area(
self.push(MapArea::new( &mut self,
start_va, start_va: VirtAddr,
end_va, end_va: VirtAddr,
MapType::Framed, permission: MapPermission,
permission, ) {
), None); self.push(
MapArea::new(start_va, end_va, MapType::Framed, permission),
None,
);
} }
fn push(&mut self, mut map_area: MapArea, data: Option<&[u8]>) { fn push(&mut self, mut map_area: MapArea, data: Option<&[u8]>) {
map_area.map(&mut self.page_table); map_area.map(&mut self.page_table);
if let Some(data) = data { if let Some(data) = data {
map_area.copy_data(&mut self.page_table, data); map_area.copy_data(&self.page_table, data);
} }
self.areas.push(map_area); self.areas.push(map_area);
} }
@ -83,42 +84,72 @@ impl MemorySet {
println!(".text [{:#x}, {:#x})", stext as usize, etext as usize); println!(".text [{:#x}, {:#x})", stext as usize, etext as usize);
println!(".rodata [{:#x}, {:#x})", srodata as usize, erodata as usize); println!(".rodata [{:#x}, {:#x})", srodata as usize, erodata as usize);
println!(".data [{:#x}, {:#x})", sdata as usize, edata as usize); println!(".data [{:#x}, {:#x})", sdata as usize, edata as usize);
println!(".bss [{:#x}, {:#x})", sbss_with_stack as usize, ebss as usize); println!(
".bss [{:#x}, {:#x})",
sbss_with_stack as usize, ebss as usize
);
println!("mapping .text section"); println!("mapping .text section");
memory_set.push(MapArea::new( memory_set.push(
(stext as usize).into(), MapArea::new(
(etext as usize).into(), (stext as usize).into(),
MapType::Identical, (etext as usize).into(),
MapPermission::R | MapPermission::X, MapType::Identical,
), None); MapPermission::R | MapPermission::X,
),
None,
);
println!("mapping .rodata section"); println!("mapping .rodata section");
memory_set.push(MapArea::new( memory_set.push(
(srodata as usize).into(), MapArea::new(
(erodata as usize).into(), (srodata as usize).into(),
MapType::Identical, (erodata as usize).into(),
MapPermission::R, MapType::Identical,
), None); MapPermission::R,
),
None,
);
println!("mapping .data section"); println!("mapping .data section");
memory_set.push(MapArea::new( memory_set.push(
(sdata as usize).into(), MapArea::new(
(edata as usize).into(), (sdata as usize).into(),
MapType::Identical, (edata as usize).into(),
MapPermission::R | MapPermission::W, MapType::Identical,
), None); MapPermission::R | MapPermission::W,
),
None,
);
println!("mapping .bss section"); println!("mapping .bss section");
memory_set.push(MapArea::new( memory_set.push(
(sbss_with_stack as usize).into(), MapArea::new(
(ebss as usize).into(), (sbss_with_stack as usize).into(),
MapType::Identical, (ebss as usize).into(),
MapPermission::R | MapPermission::W, MapType::Identical,
), None); MapPermission::R | MapPermission::W,
),
None,
);
println!("mapping physical memory"); println!("mapping physical memory");
memory_set.push(MapArea::new( memory_set.push(
(ekernel as usize).into(), MapArea::new(
MEMORY_END.into(), (ekernel as usize).into(),
MapType::Identical, MEMORY_END.into(),
MapPermission::R | MapPermission::W, MapType::Identical,
), None); MapPermission::R | MapPermission::W,
),
None,
);
println!("mapping memory-mapped registers");
for pair in MMIO {
memory_set.push(
MapArea::new(
(*pair).0.into(),
((*pair).0 + (*pair).1).into(),
MapType::Identical,
MapPermission::R | MapPermission::W,
),
None,
);
}
memory_set memory_set
} }
/// Include sections in elf and trampoline and TrapContext and user stack, /// Include sections in elf and trampoline and TrapContext and user stack,
@ -141,19 +172,20 @@ impl MemorySet {
let end_va: VirtAddr = ((ph.virtual_addr() + ph.mem_size()) as usize).into(); let end_va: VirtAddr = ((ph.virtual_addr() + ph.mem_size()) as usize).into();
let mut map_perm = MapPermission::U; let mut map_perm = MapPermission::U;
let ph_flags = ph.flags(); let ph_flags = ph.flags();
if ph_flags.is_read() { map_perm |= MapPermission::R; } if ph_flags.is_read() {
if ph_flags.is_write() { map_perm |= MapPermission::W; } map_perm |= MapPermission::R;
if ph_flags.is_execute() { map_perm |= MapPermission::X; } }
let map_area = MapArea::new( if ph_flags.is_write() {
start_va, map_perm |= MapPermission::W;
end_va, }
MapType::Framed, if ph_flags.is_execute() {
map_perm, map_perm |= MapPermission::X;
); }
let map_area = MapArea::new(start_va, end_va, MapType::Framed, map_perm);
max_end_vpn = map_area.vpn_range.get_end(); max_end_vpn = map_area.vpn_range.get_end();
memory_set.push( memory_set.push(
map_area, map_area,
Some(&elf.input[ph.offset() as usize..(ph.offset() + ph.file_size()) as usize]) Some(&elf.input[ph.offset() as usize..(ph.offset() + ph.file_size()) as usize]),
); );
} }
} }
@ -163,33 +195,83 @@ impl MemorySet {
// guard page // guard page
user_stack_bottom += PAGE_SIZE; user_stack_bottom += PAGE_SIZE;
let user_stack_top = user_stack_bottom + USER_STACK_SIZE; let user_stack_top = user_stack_bottom + USER_STACK_SIZE;
memory_set.push(MapArea::new( memory_set.push(
user_stack_bottom.into(), MapArea::new(
user_stack_top.into(), user_stack_bottom.into(),
MapType::Framed, user_stack_top.into(),
MapPermission::R | MapPermission::W | MapPermission::U, MapType::Framed,
), None); MapPermission::R | MapPermission::W | MapPermission::U,
),
None,
);
// used in sbrk
memory_set.push(
MapArea::new(
user_stack_top.into(),
user_stack_top.into(),
MapType::Framed,
MapPermission::R | MapPermission::W | MapPermission::U,
),
None,
);
// map TrapContext // map TrapContext
memory_set.push(MapArea::new( memory_set.push(
TRAP_CONTEXT.into(), MapArea::new(
TRAMPOLINE.into(), TRAP_CONTEXT.into(),
MapType::Framed, TRAMPOLINE.into(),
MapPermission::R | MapPermission::W, MapType::Framed,
), None); MapPermission::R | MapPermission::W,
(memory_set, user_stack_top, elf.header.pt2.entry_point() as usize) ),
None,
);
(
memory_set,
user_stack_top,
elf.header.pt2.entry_point() as usize,
)
} }
pub fn activate(&self) { pub fn activate(&self) {
let satp = self.page_table.token(); let satp = self.page_table.token();
unsafe { unsafe {
satp::write(satp); satp::write(satp);
llvm_asm!("sfence.vma" :::: "volatile"); asm!("sfence.vma");
} }
} }
pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> { pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> {
self.page_table.translate(vpn) self.page_table.translate(vpn)
} }
#[allow(unused)]
pub fn shrink_to(&mut self, start: VirtAddr, new_end: VirtAddr) -> bool {
if let Some(area) = self
.areas
.iter_mut()
.find(|area| area.vpn_range.get_start() == start.floor())
{
area.shrink_to(&mut self.page_table, new_end.ceil());
true
} else {
false
}
}
#[allow(unused)]
pub fn append_to(&mut self, start: VirtAddr, new_end: VirtAddr) -> bool {
if let Some(area) = self
.areas
.iter_mut()
.find(|area| area.vpn_range.get_start() == start.floor())
{
area.append_to(&mut self.page_table, new_end.ceil());
true
} else {
false
}
}
pub fn unmap(&mut self, vpn: VirtPageNum) {
self.page_table.unmap(vpn)
}
} }
/// map area structure, controls a contiguous piece of virtual memory
pub struct MapArea { pub struct MapArea {
vpn_range: VPNRange, vpn_range: VPNRange,
data_frames: BTreeMap<VirtPageNum, FrameTracker>, data_frames: BTreeMap<VirtPageNum, FrameTracker>,
@ -202,7 +284,7 @@ impl MapArea {
start_va: VirtAddr, start_va: VirtAddr,
end_va: VirtAddr, end_va: VirtAddr,
map_type: MapType, map_type: MapType,
map_perm: MapPermission map_perm: MapPermission,
) -> Self { ) -> Self {
let start_vpn: VirtPageNum = start_va.floor(); let start_vpn: VirtPageNum = start_va.floor();
let end_vpn: VirtPageNum = end_va.ceil(); let end_vpn: VirtPageNum = end_va.ceil();
@ -230,11 +312,8 @@ impl MapArea {
} }
#[allow(unused)] #[allow(unused)]
pub fn unmap_one(&mut self, page_table: &mut PageTable, vpn: VirtPageNum) { pub fn unmap_one(&mut self, page_table: &mut PageTable, vpn: VirtPageNum) {
match self.map_type { if self.map_type == MapType::Framed {
MapType::Framed => { self.data_frames.remove(&vpn);
self.data_frames.remove(&vpn);
}
_ => {}
} }
page_table.unmap(vpn); page_table.unmap(vpn);
} }
@ -249,9 +328,23 @@ impl MapArea {
self.unmap_one(page_table, vpn); self.unmap_one(page_table, vpn);
} }
} }
#[allow(unused)]
pub fn shrink_to(&mut self, page_table: &mut PageTable, new_end: VirtPageNum) {
for vpn in VPNRange::new(new_end, self.vpn_range.get_end()) {
self.unmap_one(page_table, vpn)
}
self.vpn_range = VPNRange::new(self.vpn_range.get_start(), new_end);
}
#[allow(unused)]
pub fn append_to(&mut self, page_table: &mut PageTable, new_end: VirtPageNum) {
for vpn in VPNRange::new(self.vpn_range.get_end(), new_end) {
self.map_one(page_table, vpn)
}
self.vpn_range = VPNRange::new(self.vpn_range.get_start(), new_end);
}
/// data: start-aligned but maybe with shorter length /// data: start-aligned but maybe with shorter length
/// assume that all frames were cleared before /// assume that all frames were cleared before
pub fn copy_data(&mut self, page_table: &mut PageTable, data: &[u8]) { pub fn copy_data(&mut self, page_table: &PageTable, data: &[u8]) {
assert_eq!(self.map_type, MapType::Framed); assert_eq!(self.map_type, MapType::Framed);
let mut start: usize = 0; let mut start: usize = 0;
let mut current_vpn = self.vpn_range.get_start(); let mut current_vpn = self.vpn_range.get_start();
@ -274,12 +367,14 @@ impl MapArea {
} }
#[derive(Copy, Clone, PartialEq, Debug)] #[derive(Copy, Clone, PartialEq, Debug)]
/// map type for memory set: identical or framed
pub enum MapType { pub enum MapType {
Identical, Identical,
Framed, Framed,
} }
bitflags! { bitflags! {
/// map permission corresponding to that in pte: `R W X U`
pub struct MapPermission: u8 { pub struct MapPermission: u8 {
const R = 1 << 1; const R = 1 << 1;
const W = 1 << 2; const W = 1 << 2;
@ -290,21 +385,24 @@ bitflags! {
#[allow(unused)] #[allow(unused)]
pub fn remap_test() { pub fn remap_test() {
let mut kernel_space = KERNEL_SPACE.lock(); let mut kernel_space = KERNEL_SPACE.exclusive_access();
let mid_text: VirtAddr = ((stext as usize + etext as usize) / 2).into(); let mid_text: VirtAddr = ((stext as usize + etext as usize) / 2).into();
let mid_rodata: VirtAddr = ((srodata as usize + erodata as usize) / 2).into(); let mid_rodata: VirtAddr = ((srodata as usize + erodata as usize) / 2).into();
let mid_data: VirtAddr = ((sdata as usize + edata as usize) / 2).into(); let mid_data: VirtAddr = ((sdata as usize + edata as usize) / 2).into();
assert_eq!( assert!(!kernel_space
kernel_space.page_table.translate(mid_text.floor()).unwrap().writable(), .page_table
false .translate(mid_text.floor())
); .unwrap()
assert_eq!( .writable(),);
kernel_space.page_table.translate(mid_rodata.floor()).unwrap().writable(), assert!(!kernel_space
false, .page_table
); .translate(mid_rodata.floor())
assert_eq!( .unwrap()
kernel_space.page_table.translate(mid_data.floor()).unwrap().executable(), .writable(),);
false, assert!(!kernel_space
); .page_table
.translate(mid_data.floor())
.unwrap()
.executable(),);
println!("remap_test passed!"); println!("remap_test passed!");
} }

View file

@ -1,19 +1,29 @@
mod heap_allocator; //! Memory management implementation
//!
//! SV39 page-based virtual-memory architecture for RV64 systems, and
//! everything about memory management, like frame allocator, page table,
//! map area and memory set, is implemented here.
//!
//! Every task or process has a memory_set to control its virtual memory.
mod address; mod address;
mod frame_allocator; mod frame_allocator;
mod page_table; mod heap_allocator;
mod memory_set; mod memory_set;
mod page_table;
use page_table::{PageTable, PTEFlags}; pub use address::{PhysAddr, PhysPageNum, VirtAddr, VirtPageNum};
use address::{VPNRange, StepByOne}; use address::StepByOne;
pub use address::{PhysAddr, VirtAddr, PhysPageNum, VirtPageNum}; pub use address::VPNRange;
pub use frame_allocator::{FrameTracker, frame_alloc}; pub use frame_allocator::{frame_alloc, FrameTracker};
pub use page_table::{PageTableEntry, translated_byte_buffer};
pub use memory_set::{MemorySet, KERNEL_SPACE, MapPermission};
pub use memory_set::remap_test; pub use memory_set::remap_test;
pub use memory_set::{MapPermission, MemorySet, KERNEL_SPACE};
pub use page_table::{translated_byte_buffer, PageTableEntry};
use page_table::{PTEFlags, PageTable};
/// initiate heap allocator, frame allocator and kernel space
pub fn init() { pub fn init() {
heap_allocator::init_heap(); heap_allocator::init_heap();
frame_allocator::init_frame_allocator(); frame_allocator::init_frame_allocator();
KERNEL_SPACE.clone().lock().activate(); KERNEL_SPACE.exclusive_access().activate();
} }

View file

@ -1,9 +1,12 @@
use super::{frame_alloc, PhysPageNum, FrameTracker, VirtPageNum, VirtAddr, StepByOne}; //! Implementation of [`PageTableEntry`] and [`PageTable`].
use alloc::vec::Vec;
use super::{frame_alloc, FrameTracker, PhysPageNum, StepByOne, VirtAddr, VirtPageNum};
use alloc::vec; use alloc::vec;
use alloc::vec::Vec;
use bitflags::*; use bitflags::*;
bitflags! { bitflags! {
/// page table entry flags
pub struct PTEFlags: u8 { pub struct PTEFlags: u8 {
const V = 1 << 0; const V = 1 << 0;
const R = 1 << 1; const R = 1 << 1;
@ -18,6 +21,7 @@ bitflags! {
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
#[repr(C)] #[repr(C)]
/// page table entry structure
pub struct PageTableEntry { pub struct PageTableEntry {
pub bits: usize, pub bits: usize,
} }
@ -29,9 +33,7 @@ impl PageTableEntry {
} }
} }
pub fn empty() -> Self { pub fn empty() -> Self {
PageTableEntry { PageTableEntry { bits: 0 }
bits: 0,
}
} }
pub fn ppn(&self) -> PhysPageNum { pub fn ppn(&self) -> PhysPageNum {
(self.bits >> 10 & ((1usize << 44) - 1)).into() (self.bits >> 10 & ((1usize << 44) - 1)).into()
@ -53,6 +55,7 @@ impl PageTableEntry {
} }
} }
/// page table structure
pub struct PageTable { pub struct PageTable {
root_ppn: PhysPageNum, root_ppn: PhysPageNum,
frames: Vec<FrameTracker>, frames: Vec<FrameTracker>,
@ -78,8 +81,8 @@ impl PageTable {
let idxs = vpn.indexes(); let idxs = vpn.indexes();
let mut ppn = self.root_ppn; let mut ppn = self.root_ppn;
let mut result: Option<&mut PageTableEntry> = None; let mut result: Option<&mut PageTableEntry> = None;
for i in 0..3 { for (i, idx) in idxs.iter().enumerate() {
let pte = &mut ppn.get_pte_array()[idxs[i]]; let pte = &mut ppn.get_pte_array()[*idx];
if i == 2 { if i == 2 {
result = Some(pte); result = Some(pte);
break; break;
@ -93,12 +96,12 @@ impl PageTable {
} }
result result
} }
fn find_pte(&self, vpn: VirtPageNum) -> Option<&PageTableEntry> { fn find_pte(&self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> {
let idxs = vpn.indexes(); let idxs = vpn.indexes();
let mut ppn = self.root_ppn; let mut ppn = self.root_ppn;
let mut result: Option<&PageTableEntry> = None; let mut result: Option<&mut PageTableEntry> = None;
for i in 0..3 { for (i, idx) in idxs.iter().enumerate() {
let pte = &ppn.get_pte_array()[idxs[i]]; let pte = &mut ppn.get_pte_array()[*idx];
if i == 2 { if i == 2 {
result = Some(pte); result = Some(pte);
break; break;
@ -118,20 +121,20 @@ impl PageTable {
} }
#[allow(unused)] #[allow(unused)]
pub fn unmap(&mut self, vpn: VirtPageNum) { pub fn unmap(&mut self, vpn: VirtPageNum) {
let pte = self.find_pte_create(vpn).unwrap(); let pte = self.find_pte(vpn).unwrap();
assert!(pte.is_valid(), "vpn {:?} is invalid before unmapping", vpn); assert!(pte.is_valid(), "vpn {:?} is invalid before unmapping", vpn);
*pte = PageTableEntry::empty(); *pte = PageTableEntry::empty();
} }
pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> { pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> {
self.find_pte(vpn) self.find_pte(vpn).map(|pte| *pte)
.map(|pte| {pte.clone()})
} }
pub fn token(&self) -> usize { pub fn token(&self) -> usize {
8usize << 60 | self.root_ppn.0 8usize << 60 | self.root_ppn.0
} }
} }
pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Vec<&'static [u8]> { /// translate a pointer to a mutable u8 Vec through page table
pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Vec<&'static mut [u8]> {
let page_table = PageTable::from_token(token); let page_table = PageTable::from_token(token);
let mut start = ptr as usize; let mut start = ptr as usize;
let end = start + len; let end = start + len;
@ -139,15 +142,16 @@ pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Vec<&
while start < end { while start < end {
let start_va = VirtAddr::from(start); let start_va = VirtAddr::from(start);
let mut vpn = start_va.floor(); let mut vpn = start_va.floor();
let ppn = page_table let ppn = page_table.translate(vpn).unwrap().ppn();
.translate(vpn)
.unwrap()
.ppn();
vpn.step(); vpn.step();
let mut end_va: VirtAddr = vpn.into(); let mut end_va: VirtAddr = vpn.into();
end_va = end_va.min(VirtAddr::from(end)); end_va = end_va.min(VirtAddr::from(end));
v.push(&ppn.get_bytes_array()[start_va.page_offset()..end_va.page_offset()]); if end_va.page_offset() == 0 {
v.push(&mut ppn.get_bytes_array()[start_va.page_offset()..]);
} else {
v.push(&mut ppn.get_bytes_array()[start_va.page_offset()..end_va.page_offset()]);
}
start = end_va.into(); start = end_va.into();
} }
v v
} }

View file

@ -1,43 +1,23 @@
#![allow(unused)] //! SBI call wrappers
const SBI_SET_TIMER: usize = 0;
const SBI_CONSOLE_PUTCHAR: usize = 1;
const SBI_CONSOLE_GETCHAR: usize = 2;
const SBI_CLEAR_IPI: usize = 3;
const SBI_SEND_IPI: usize = 4;
const SBI_REMOTE_FENCE_I: usize = 5;
const SBI_REMOTE_SFENCE_VMA: usize = 6;
const SBI_REMOTE_SFENCE_VMA_ASID: usize = 7;
const SBI_SHUTDOWN: usize = 8;
#[inline(always)]
fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize {
let mut ret;
unsafe {
llvm_asm!("ecall"
: "={x10}" (ret)
: "{x10}" (arg0), "{x11}" (arg1), "{x12}" (arg2), "{x17}" (which)
: "memory"
: "volatile"
);
}
ret
}
pub fn set_timer(timer: usize) {
sbi_call(SBI_SET_TIMER, timer, 0, 0);
}
/// use sbi call to putchar in console (qemu uart handler)
pub fn console_putchar(c: usize) { pub fn console_putchar(c: usize) {
sbi_call(SBI_CONSOLE_PUTCHAR, c, 0, 0); #[allow(deprecated)]
sbi_rt::legacy::console_putchar(c);
} }
pub fn console_getchar() -> usize { /// use sbi call to set timer
sbi_call(SBI_CONSOLE_GETCHAR, 0, 0, 0) pub fn set_timer(timer: usize) {
sbi_rt::set_timer(timer as _);
} }
pub fn shutdown() -> ! { /// use sbi call to shutdown the kernel
sbi_call(SBI_SHUTDOWN, 0, 0, 0); pub fn shutdown(failure: bool) -> ! {
panic!("It should shutdown!"); use sbi_rt::{system_reset, NoReason, Shutdown, SystemFailure};
if !failure {
system_reset(Shutdown, NoReason);
} else {
system_reset(Shutdown, SystemFailure);
}
unreachable!()
} }

5
os/src/sync/mod.rs Normal file
View file

@ -0,0 +1,5 @@
//! Synchronization and interior mutability primitives
mod up;
pub use up::UPSafeCell;

31
os/src/sync/up.rs Normal file
View file

@ -0,0 +1,31 @@
//! Uniprocessor interior mutability primitives
use core::cell::{RefCell, RefMut};
/// Wrap a static data structure inside it so that we are
/// able to access it without any `unsafe`.
///
/// We should only use it in uniprocessor.
///
/// In order to get mutable reference of inner data, call
/// `exclusive_access`.
pub struct UPSafeCell<T> {
/// inner data
inner: RefCell<T>,
}
unsafe impl<T> Sync for UPSafeCell<T> {}
impl<T> UPSafeCell<T> {
/// User is responsible to guarantee that inner struct is only used in
/// uniprocessor.
pub unsafe fn new(value: T) -> Self {
Self {
inner: RefCell::new(value),
}
}
/// Exclusive access inner data in UPSafeCell. Panic if the data has been borrowed.
pub fn exclusive_access(&self) -> RefMut<'_, T> {
self.inner.borrow_mut()
}
}

View file

@ -1,8 +1,11 @@
//! File and filesystem-related syscalls
use crate::mm::translated_byte_buffer; use crate::mm::translated_byte_buffer;
use crate::task::current_user_token; use crate::task::current_user_token;
const FD_STDOUT: usize = 1; const FD_STDOUT: usize = 1;
/// write buf of length `len` to a file with `fd`
pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize { pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
match fd { match fd {
FD_STDOUT => { FD_STDOUT => {
@ -11,9 +14,9 @@ pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
print!("{}", core::str::from_utf8(buffer).unwrap()); print!("{}", core::str::from_utf8(buffer).unwrap());
} }
len as isize len as isize
}, }
_ => { _ => {
panic!("Unsupported fd in sys_write!"); panic!("Unsupported fd in sys_write!");
} }
} }
} }

View file

@ -1,7 +1,22 @@
//! Implementation of syscalls
//!
//! The single entry point to all system calls, [`syscall()`], is called
//! whenever userspace wishes to perform a system call using the `ecall`
//! instruction. In this case, the processor raises an 'Environment call from
//! U-mode' exception, which is handled as one of the cases in
//! [`crate::trap::trap_handler`].
//!
//! For clarity, each single syscall is implemented as its own function, named
//! `sys_` then the name of the syscall. You can find functions like this in
//! submodules, and you should also implement syscalls this way.
const SYSCALL_WRITE: usize = 64; const SYSCALL_WRITE: usize = 64;
const SYSCALL_EXIT: usize = 93; const SYSCALL_EXIT: usize = 93;
const SYSCALL_YIELD: usize = 124; const SYSCALL_YIELD: usize = 124;
const SYSCALL_GET_TIME: usize = 169; const SYSCALL_GET_TIME: usize = 169;
const SYSCALL_SBRK: usize = 214;
const SYSCALL_MMAP: usize = 222;
const SYSCALL_MUNMAP: usize = 215;
mod fs; mod fs;
mod process; mod process;
@ -9,13 +24,16 @@ mod process;
use fs::*; use fs::*;
use process::*; use process::*;
/// handle syscall exception with `syscall_id` and other arguments
pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize { pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
match syscall_id { match syscall_id {
SYSCALL_WRITE => sys_write(args[0], args[1] as *const u8, args[2]), SYSCALL_WRITE => sys_write(args[0], args[1] as *const u8, args[2]),
SYSCALL_EXIT => sys_exit(args[0] as i32), SYSCALL_EXIT => sys_exit(args[0] as i32),
SYSCALL_YIELD => sys_yield(), SYSCALL_YIELD => sys_yield(),
SYSCALL_GET_TIME => sys_get_time(), SYSCALL_GET_TIME => sys_get_time(args[0] as *mut TimeVal, args[1]),
SYSCALL_SBRK => sys_sbrk(args[0] as i32),
SYSCALL_MMAP => sys_mmap(args[0], args[1], args[2]),
SYSCALL_MUNMAP => sys_munmap(args[0], args[1]),
_ => panic!("Unsupported syscall_id: {}", syscall_id), _ => panic!("Unsupported syscall_id: {}", syscall_id),
} }
} }

View file

@ -1,20 +1,105 @@
use crate::task::{ //! Process management syscalls
suspend_current_and_run_next,
exit_current_and_run_next,
};
use crate::timer::get_time;
pub fn sys_exit(xstate: i32) -> ! { use crate::config::PAGE_SIZE;
println!("[kernel] Application exited with code {}", xstate); use crate::mm::{translated_byte_buffer, MapPermission, VPNRange, VirtAddr};
use crate::task::{change_program_brk, create_new_map_area, current_user_token, exit_current_and_run_next, get_current_task_page_table_entry, suspend_current_and_run_next, unmap_virtual_page};
use crate::timer::get_time_us;
/// task exits and submit an exit code
pub fn sys_exit(exit_code: i32) -> ! {
println!("[kernel] Application exited with code {}", exit_code);
exit_current_and_run_next(); exit_current_and_run_next();
panic!("Unreachable in sys_exit!"); panic!("Unreachable in sys_exit!");
} }
/// current task gives up resources for other tasks
pub fn sys_yield() -> isize { pub fn sys_yield() -> isize {
suspend_current_and_run_next(); suspend_current_and_run_next();
0 0
} }
pub fn sys_get_time() -> isize { #[repr(C)]
get_time() as isize #[derive(Debug)]
} pub struct TimeVal {
pub sec: usize,
pub usec: usize,
}
/// get current time
pub fn sys_get_time(ts: *mut TimeVal, _tz: usize) -> isize {
let us = get_time_us();
let dst_vec = translated_byte_buffer(
current_user_token(),
ts as *const u8, core::mem::size_of::<TimeVal>()
);
let ref time_val = TimeVal {
sec: us / 1_000_000,
usec: us % 1_000_000,
};
let src_ptr = time_val as *const TimeVal;
for (idx, dst) in dst_vec.into_iter().enumerate() {
let unit_len = dst.len();
unsafe {
dst.copy_from_slice(
core::slice::from_raw_parts(
src_ptr.wrapping_byte_add(idx * unit_len) as *const u8,
unit_len
)
);
}
}
0
}
/// change data segment size
pub fn sys_sbrk(size: i32) -> isize {
if let Some(old_brk) = change_program_brk(size) {
old_brk as isize
} else {
-1
}
}
/// map files or devices into memory
pub fn sys_mmap(start: usize, len: usize, prot: usize) -> isize {
if start % PAGE_SIZE != 0 || prot & !0x7 != 0 || prot & 0x7 == 0 {
return -1;
}
let vpn_start = VirtAddr::from(start).floor();
let vpn_end = VirtAddr::from(start + len).ceil();
let vpn_range = VPNRange::new(vpn_start, vpn_end);
for vpn in vpn_range {
if let Some(pte) = get_current_task_page_table_entry(vpn) {
if pte.is_valid() {
return -1;
}
}
}
create_new_map_area(
vpn_start.into(),
vpn_end.into(),
MapPermission::from_bits_truncate((prot << 1) as u8) | MapPermission::U
);
0
}
/// unmap files or devices into memory
pub fn sys_munmap(start: usize, len: usize) -> isize {
if start % PAGE_SIZE != 0 {
return -1;
}
let vpn_start = VirtAddr::from(start).floor();
let vpn_end = VirtAddr::from(start + len).ceil();
let vpn_range = VPNRange::new(vpn_start, vpn_end);
for vpn in vpn_range {
if let Some(pte) = get_current_task_page_table_entry(vpn) {
if !pte.is_valid() {
return -1;
}
unmap_virtual_page(vpn)
} else {
return -1;
}
}
0
}

View file

@ -1,17 +1,32 @@
//! Implementation of [`TaskContext`]
use crate::trap::trap_return; use crate::trap::trap_return;
#[repr(C)] #[repr(C)]
/// task context structure containing some registers
pub struct TaskContext { pub struct TaskContext {
/// return address ( e.g. __restore ) of __switch ASM function
ra: usize, ra: usize,
/// kernel stack pointer of app
sp: usize,
/// callee saved registers: s 0..11
s: [usize; 12], s: [usize; 12],
} }
impl TaskContext { impl TaskContext {
pub fn goto_trap_return() -> Self { /// init task context
pub fn zero_init() -> Self {
Self {
ra: 0,
sp: 0,
s: [0; 12],
}
}
/// set Task Context{__restore ASM funciton: trap_return, sp: kstack_ptr, s: s_0..12}
pub fn goto_trap_return(kstack_ptr: usize) -> Self {
Self { Self {
ra: trap_return as usize, ra: trap_return as usize,
sp: kstack_ptr,
s: [0; 12], s: [0; 12],
} }
} }
} }

View file

@ -1,149 +1,228 @@
//! Task management implementation
//!
//! Everything about task management, like starting and switching tasks is
//! implemented here.
//!
//! A single global instance of [`TaskManager`] called `TASK_MANAGER` controls
//! all the tasks in the operating system.
//!
//! Be careful when you see `__switch` ASM function in `switch.S`. Control flow around this function
//! might not be what you expect.
mod context; mod context;
mod switch; mod switch;
#[allow(clippy::module_inception)]
mod task; mod task;
use crate::loader::{get_num_app, get_app_data}; use crate::loader::{get_app_data, get_num_app};
use crate::mm::{MapPermission, PageTableEntry, VirtAddr, VirtPageNum};
use crate::sbi::shutdown;
use crate::sync::UPSafeCell;
use crate::trap::TrapContext; use crate::trap::TrapContext;
use core::cell::RefCell; use alloc::vec::Vec;
use lazy_static::*; use lazy_static::*;
use switch::__switch; use switch::__switch;
use task::{TaskControlBlock, TaskStatus}; use task::{TaskControlBlock, TaskStatus};
use alloc::vec::Vec;
pub use context::TaskContext; pub use context::TaskContext;
/// The task manager, where all the tasks are managed.
///
/// Functions implemented on `TaskManager` deals with all task state transitions
/// and task context switching. For convenience, you can find wrappers around it
/// in the module level.
///
/// Most of `TaskManager` are hidden behind the field `inner`, to defer
/// borrowing checks to runtime. You can see examples on how to use `inner` in
/// existing functions on `TaskManager`.
pub struct TaskManager { pub struct TaskManager {
/// total number of tasks
num_app: usize, num_app: usize,
inner: RefCell<TaskManagerInner>, /// use inner value to get mutable access
inner: UPSafeCell<TaskManagerInner>,
} }
/// The task manager inner in 'UPSafeCell'
struct TaskManagerInner { struct TaskManagerInner {
/// task list
tasks: Vec<TaskControlBlock>, tasks: Vec<TaskControlBlock>,
/// id of current `Running` task
current_task: usize, current_task: usize,
} }
unsafe impl Sync for TaskManager {}
lazy_static! { lazy_static! {
/// a `TaskManager` global instance through lazy_static!
pub static ref TASK_MANAGER: TaskManager = { pub static ref TASK_MANAGER: TaskManager = {
println!("init TASK_MANAGER"); println!("init TASK_MANAGER");
let num_app = get_num_app(); let num_app = get_num_app();
println!("num_app = {}", num_app); println!("num_app = {}", num_app);
let mut tasks: Vec<TaskControlBlock> = Vec::new(); let mut tasks: Vec<TaskControlBlock> = Vec::new();
for i in 0..num_app { for i in 0..num_app {
tasks.push(TaskControlBlock::new( tasks.push(TaskControlBlock::new(get_app_data(i), i));
get_app_data(i),
i,
));
} }
TaskManager { TaskManager {
num_app, num_app,
inner: RefCell::new(TaskManagerInner { inner: unsafe {
tasks, UPSafeCell::new(TaskManagerInner {
current_task: 0, tasks,
}), current_task: 0,
})
},
} }
}; };
} }
impl TaskManager { impl TaskManager {
fn run_first_task(&self) { /// Run the first task in task list.
self.inner.borrow_mut().tasks[0].task_status = TaskStatus::Running; ///
let next_task_cx = self.inner.borrow().tasks[0].get_task_cx_ptr2(); /// Generally, the first task in task list is an idle task (we call it zero process later).
let _unused: usize = 0; /// But in ch4, we load apps statically, so the first task is a real app.
fn run_first_task(&self) -> ! {
let mut inner = self.inner.exclusive_access();
let next_task = &mut inner.tasks[0];
next_task.task_status = TaskStatus::Running;
let next_task_cx_ptr = &next_task.task_cx as *const TaskContext;
drop(inner);
let mut _unused = TaskContext::zero_init();
// before this, we should drop local variables that must be dropped manually
unsafe { unsafe {
__switch( __switch(&mut _unused as *mut _, next_task_cx_ptr);
&_unused as *const _,
next_task_cx,
);
} }
panic!("unreachable in run_first_task!");
} }
/// Change the status of current `Running` task into `Ready`.
fn mark_current_suspended(&self) { fn mark_current_suspended(&self) {
let mut inner = self.inner.borrow_mut(); let mut inner = self.inner.exclusive_access();
let current = inner.current_task; let cur = inner.current_task;
inner.tasks[current].task_status = TaskStatus::Ready; inner.tasks[cur].task_status = TaskStatus::Ready;
} }
/// Change the status of current `Running` task into `Exited`.
fn mark_current_exited(&self) { fn mark_current_exited(&self) {
let mut inner = self.inner.borrow_mut(); let mut inner = self.inner.exclusive_access();
let current = inner.current_task; let cur = inner.current_task;
inner.tasks[current].task_status = TaskStatus::Exited; inner.tasks[cur].task_status = TaskStatus::Exited;
} }
/// Find next task to run and return task id.
///
/// In this case, we only return the first `Ready` task in task list.
fn find_next_task(&self) -> Option<usize> { fn find_next_task(&self) -> Option<usize> {
let inner = self.inner.borrow(); let inner = self.inner.exclusive_access();
let current = inner.current_task; let current = inner.current_task;
(current + 1..current + self.num_app + 1) (current + 1..current + self.num_app + 1)
.map(|id| id % self.num_app) .map(|id| id % self.num_app)
.find(|id| { .find(|id| inner.tasks[*id].task_status == TaskStatus::Ready)
inner.tasks[*id].task_status == TaskStatus::Ready
})
} }
/// Get the current 'Running' task's token.
fn get_current_token(&self) -> usize { fn get_current_token(&self) -> usize {
let inner = self.inner.borrow(); let inner = self.inner.exclusive_access();
let current = inner.current_task; inner.tasks[inner.current_task].get_user_token()
inner.tasks[current].get_user_token()
} }
fn get_current_trap_cx(&self) -> &mut TrapContext { /// Get the current 'Running' task's trap contexts.
let inner = self.inner.borrow(); fn get_current_trap_cx(&self) -> &'static mut TrapContext {
let current = inner.current_task; let inner = self.inner.exclusive_access();
inner.tasks[current].get_trap_cx() inner.tasks[inner.current_task].get_trap_cx()
} }
/// Change the current 'Running' task's program break
pub fn change_current_program_brk(&self, size: i32) -> Option<usize> {
let mut inner = self.inner.exclusive_access();
let cur = inner.current_task;
inner.tasks[cur].change_program_brk(size)
}
/// Switch current `Running` task to the task we have found,
/// or there is no `Ready` task and we can exit with all applications completed
fn run_next_task(&self) { fn run_next_task(&self) {
if let Some(next) = self.find_next_task() { if let Some(next) = self.find_next_task() {
let mut inner = self.inner.borrow_mut(); let mut inner = self.inner.exclusive_access();
let current = inner.current_task; let current = inner.current_task;
inner.tasks[next].task_status = TaskStatus::Running; inner.tasks[next].task_status = TaskStatus::Running;
inner.current_task = next; inner.current_task = next;
let current_task_cx = inner.tasks[current].get_task_cx_ptr2(); let current_task_cx_ptr = &mut inner.tasks[current].task_cx as *mut TaskContext;
let next_task_cx = inner.tasks[next].get_task_cx_ptr2(); let next_task_cx_ptr = &inner.tasks[next].task_cx as *const TaskContext;
core::mem::drop(inner); drop(inner);
// before this, we should drop local variables that must be dropped manually
unsafe { unsafe {
__switch( __switch(current_task_cx_ptr, next_task_cx_ptr);
current_task_cx,
next_task_cx,
);
} }
// go back to user mode
} else { } else {
panic!("All applications completed!"); println!("All applications completed!");
shutdown(false);
} }
} }
} }
/// Run the first task in task list.
pub fn run_first_task() { pub fn run_first_task() {
TASK_MANAGER.run_first_task(); TASK_MANAGER.run_first_task();
} }
/// Switch current `Running` task to the task we have found,
/// or there is no `Ready` task and we can exit with all applications completed
fn run_next_task() { fn run_next_task() {
TASK_MANAGER.run_next_task(); TASK_MANAGER.run_next_task();
} }
/// Change the status of current `Running` task into `Ready`.
fn mark_current_suspended() { fn mark_current_suspended() {
TASK_MANAGER.mark_current_suspended(); TASK_MANAGER.mark_current_suspended();
} }
/// Change the status of current `Running` task into `Exited`.
fn mark_current_exited() { fn mark_current_exited() {
TASK_MANAGER.mark_current_exited(); TASK_MANAGER.mark_current_exited();
} }
/// Suspend the current 'Running' task and run the next task in task list.
pub fn suspend_current_and_run_next() { pub fn suspend_current_and_run_next() {
mark_current_suspended(); mark_current_suspended();
run_next_task(); run_next_task();
} }
/// Exit the current 'Running' task and run the next task in task list.
pub fn exit_current_and_run_next() { pub fn exit_current_and_run_next() {
mark_current_exited(); mark_current_exited();
run_next_task(); run_next_task();
} }
/// Get the current 'Running' task's token.
pub fn current_user_token() -> usize { pub fn current_user_token() -> usize {
TASK_MANAGER.get_current_token() TASK_MANAGER.get_current_token()
} }
/// Get the current 'Running' task's trap contexts.
pub fn current_trap_cx() -> &'static mut TrapContext { pub fn current_trap_cx() -> &'static mut TrapContext {
TASK_MANAGER.get_current_trap_cx() TASK_MANAGER.get_current_trap_cx()
} }
/// Change the current 'Running' task's program break
pub fn change_program_brk(size: i32) -> Option<usize> {
TASK_MANAGER.change_current_program_brk(size)
}
/// Get the curent 'Running' task's PTE
pub fn get_current_task_page_table_entry(vpn: VirtPageNum) -> Option<PageTableEntry> {
let inner = TASK_MANAGER.inner.exclusive_access();
let current = inner.current_task;
inner.tasks[current].memory_set.translate(vpn)
}
/// create new mapping area
pub fn create_new_map_area(start_va: VirtAddr, end_va: VirtAddr, perm: MapPermission) {
let mut inner = TASK_MANAGER.inner.exclusive_access();
let current = inner.current_task;
inner.tasks[current].memory_set.insert_framed_area(start_va, end_va, perm);
}
/// unmap virtual page
pub fn unmap_virtual_page(vpn: VirtPageNum) {
let mut inner = TASK_MANAGER.inner.exclusive_access();
let current = inner.current_task;
inner.tasks[current].memory_set.unmap(vpn);
}

View file

@ -1,34 +1,34 @@
.altmacro .altmacro
.macro SAVE_SN n .macro SAVE_SN n
sd s\n, (\n+1)*8(sp) sd s\n, (\n+2)*8(a0)
.endm .endm
.macro LOAD_SN n .macro LOAD_SN n
ld s\n, (\n+1)*8(sp) ld s\n, (\n+2)*8(a1)
.endm .endm
.section .text .section .text
.globl __switch .globl __switch
__switch: __switch:
# __switch(current_task_cx: &*const TaskContext, next_task_cx: &*const TaskContext) # __switch(
# push TaskContext to current sp and save its address to where a0 points to # current_task_cx_ptr: *mut TaskContext,
addi sp, sp, -13*8 # next_task_cx_ptr: *const TaskContext
sd sp, 0(a0) # )
# fill TaskContext with ra & s0-s11 # save kernel stack of current task
sd ra, 0(sp) sd sp, 8(a0)
# save ra & s0~s11 of current execution
sd ra, 0(a0)
.set n, 0 .set n, 0
.rept 12 .rept 12
SAVE_SN %n SAVE_SN %n
.set n, n + 1 .set n, n + 1
.endr .endr
# ready for loading TaskContext a1 points to # restore ra & s0~s11 of next execution
ld sp, 0(a1) ld ra, 0(a1)
# load registers in the TaskContext
ld ra, 0(sp)
.set n, 0 .set n, 0
.rept 12 .rept 12
LOAD_SN %n LOAD_SN %n
.set n, n + 1 .set n, n + 1
.endr .endr
# pop TaskContext # restore kernel stack of next task
addi sp, sp, 13*8 ld sp, 8(a1)
ret ret

View file

@ -1,5 +1,15 @@
global_asm!(include_str!("switch.S")); //! Rust wrapper around `__switch`.
//!
//! Switching to a different task's context happens here. The actual
//! implementation must not be in Rust and (essentially) has to be in assembly
//! language (Do you know why?), so this module really is just a wrapper around
//! `switch.S`.
core::arch::global_asm!(include_str!("switch.S"));
use super::TaskContext;
extern "C" { extern "C" {
pub fn __switch(current_task_cx: *const usize, next_task_cx: *const usize); /// Switch to the context of `next_task_cx_ptr`, saving the current context
/// in `current_task_cx_ptr`.
pub fn __switch(current_task_cx_ptr: *mut TaskContext, next_task_cx_ptr: *const TaskContext);
} }

View file

@ -1,20 +1,22 @@
use crate::mm::{MemorySet, MapPermission, PhysPageNum, KERNEL_SPACE, VirtAddr}; //! Types related to task management
use crate::trap::{TrapContext, trap_handler};
use crate::config::{TRAP_CONTEXT, kernel_stack_position};
use super::TaskContext; use super::TaskContext;
use crate::config::{kernel_stack_position, TRAP_CONTEXT};
use crate::mm::{MapPermission, MemorySet, PhysPageNum, VirtAddr, KERNEL_SPACE};
use crate::trap::{trap_handler, TrapContext};
/// task control block structure
pub struct TaskControlBlock { pub struct TaskControlBlock {
pub task_cx_ptr: usize,
pub task_status: TaskStatus, pub task_status: TaskStatus,
pub task_cx: TaskContext,
pub memory_set: MemorySet, pub memory_set: MemorySet,
pub trap_cx_ppn: PhysPageNum, pub trap_cx_ppn: PhysPageNum,
#[allow(unused)]
pub base_size: usize, pub base_size: usize,
pub heap_bottom: usize,
pub program_brk: usize,
} }
impl TaskControlBlock { impl TaskControlBlock {
pub fn get_task_cx_ptr2(&self) -> *const usize {
&self.task_cx_ptr as *const usize
}
pub fn get_trap_cx(&self) -> &'static mut TrapContext { pub fn get_trap_cx(&self) -> &'static mut TrapContext {
self.trap_cx_ppn.get_mut() self.trap_cx_ppn.get_mut()
} }
@ -31,38 +33,58 @@ impl TaskControlBlock {
let task_status = TaskStatus::Ready; let task_status = TaskStatus::Ready;
// map a kernel-stack in kernel space // map a kernel-stack in kernel space
let (kernel_stack_bottom, kernel_stack_top) = kernel_stack_position(app_id); let (kernel_stack_bottom, kernel_stack_top) = kernel_stack_position(app_id);
KERNEL_SPACE KERNEL_SPACE.exclusive_access().insert_framed_area(
.lock() kernel_stack_bottom.into(),
.insert_framed_area( kernel_stack_top.into(),
kernel_stack_bottom.into(), MapPermission::R | MapPermission::W,
kernel_stack_top.into(), );
MapPermission::R | MapPermission::W,
);
let task_cx_ptr = (kernel_stack_top - core::mem::size_of::<TaskContext>()) as *mut TaskContext;
unsafe { *task_cx_ptr = TaskContext::goto_trap_return(); }
let task_control_block = Self { let task_control_block = Self {
task_cx_ptr: task_cx_ptr as usize,
task_status, task_status,
task_cx: TaskContext::goto_trap_return(kernel_stack_top),
memory_set, memory_set,
trap_cx_ppn, trap_cx_ppn,
base_size: user_sp, base_size: user_sp,
heap_bottom: user_sp,
program_brk: user_sp,
}; };
// prepare TrapContext in user space // prepare TrapContext in user space
let trap_cx = task_control_block.get_trap_cx(); let trap_cx = task_control_block.get_trap_cx();
*trap_cx = TrapContext::app_init_context( *trap_cx = TrapContext::app_init_context(
entry_point, entry_point,
user_sp, user_sp,
KERNEL_SPACE.lock().token(), KERNEL_SPACE.exclusive_access().token(),
kernel_stack_top, kernel_stack_top,
trap_handler as usize, trap_handler as usize,
); );
task_control_block task_control_block
} }
/// change the location of the program break. return None if failed.
pub fn change_program_brk(&mut self, size: i32) -> Option<usize> {
let old_break = self.program_brk;
let new_brk = self.program_brk as isize + size as isize;
if new_brk < self.heap_bottom as isize {
return None;
}
let result = if size < 0 {
self.memory_set
.shrink_to(VirtAddr(self.heap_bottom), VirtAddr(new_brk as usize))
} else {
self.memory_set
.append_to(VirtAddr(self.heap_bottom), VirtAddr(new_brk as usize))
};
if result {
self.program_brk = new_brk as usize;
Some(old_break)
} else {
None
}
}
} }
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq)]
/// task status: UnInit, Ready, Running, Exited
pub enum TaskStatus { pub enum TaskStatus {
Ready, Ready,
Running, Running,
Exited, Exited,
} }

View file

@ -1,13 +1,29 @@
use riscv::register::time; //! RISC-V timer-related functionality
use crate::config::CLOCK_FREQ;
use crate::sbi::set_timer; use crate::sbi::set_timer;
use crate::config::CPU_FREQ; use riscv::register::time;
const TICKS_PER_SEC: usize = 100; const TICKS_PER_SEC: usize = 100;
const MSEC_PER_SEC: usize = 1000;
const MICRO_PER_SEC: usize = 1_000_000;
pub fn get_time() -> usize { pub fn get_time() -> usize {
time::read() time::read()
} }
/// get current time in microseconds
#[allow(unused)]
pub fn get_time_ms() -> usize {
time::read() / (CLOCK_FREQ / MSEC_PER_SEC)
}
/// get current time in us
pub fn get_time_us() -> usize {
time::read() / (CLOCK_FREQ / MICRO_PER_SEC)
}
/// set the next timer interrupt
pub fn set_next_trigger() { pub fn set_next_trigger() {
set_timer(get_time() + CPU_FREQ / TICKS_PER_SEC); set_timer(get_time() + CLOCK_FREQ / TICKS_PER_SEC);
} }

View file

@ -1,17 +1,30 @@
use riscv::register::sstatus::{Sstatus, self, SPP}; //! Implementation of [`TrapContext`]
use riscv::register::sstatus::{self, Sstatus, SPP};
#[repr(C)] #[repr(C)]
/// trap context structure containing sstatus, sepc and registers
pub struct TrapContext { pub struct TrapContext {
/// general regs[0..31]
pub x: [usize; 32], pub x: [usize; 32],
/// CSR sstatus
pub sstatus: Sstatus, pub sstatus: Sstatus,
/// CSR sepc
pub sepc: usize, pub sepc: usize,
/// Addr of Page Table
pub kernel_satp: usize, pub kernel_satp: usize,
/// kernel stack
pub kernel_sp: usize, pub kernel_sp: usize,
/// Addr of trap_handler function
pub trap_handler: usize, pub trap_handler: usize,
} }
impl TrapContext { impl TrapContext {
pub fn set_sp(&mut self, sp: usize) { self.x[2] = sp; } /// set stack pointer to x_2 reg (sp)
pub fn set_sp(&mut self, sp: usize) {
self.x[2] = sp;
}
/// init app context
pub fn app_init_context( pub fn app_init_context(
entry: usize, entry: usize,
sp: usize, sp: usize,
@ -19,18 +32,17 @@ impl TrapContext {
kernel_sp: usize, kernel_sp: usize,
trap_handler: usize, trap_handler: usize,
) -> Self { ) -> Self {
let mut sstatus = sstatus::read(); let mut sstatus = sstatus::read(); // CSR sstatus
sstatus.set_spp(SPP::User); sstatus.set_spp(SPP::User); //previous privilege mode: user mode
sstatus.set_spie(true);
let mut cx = Self { let mut cx = Self {
x: [0; 32], x: [0; 32],
sstatus, sstatus,
sepc: entry, sepc: entry, // entry point of app
kernel_satp, kernel_satp, // addr of page table
kernel_sp, kernel_sp, // kernel stack
trap_handler, trap_handler, // addr of trap_handler function
}; };
cx.set_sp(sp); cx.set_sp(sp); // app's user stack pointer
cx cx // return initial Trap Context of app
} }
} }

View file

@ -1,30 +1,34 @@
//! Trap handling functionality
//!
//! For rCore, we have a single trap entry point, namely `__alltraps`. At
//! initialization in [`init()`], we set the `stvec` CSR to point to it.
//!
//! All traps go through `__alltraps`, which is defined in `trap.S`. The
//! assembly language code does just enough work restore the kernel space
//! context, ensuring that Rust code safely runs, and transfers control to
//! [`trap_handler()`].
//!
//! It then calls different functionality based on what exactly the exception
//! was. For example, timer interrupts trigger task preemption, and syscalls go
//! to [`syscall()`].
mod context; mod context;
use riscv::register::{ use crate::config::{TRAMPOLINE, TRAP_CONTEXT};
mtvec::TrapMode,
stvec,
scause::{
self,
Trap,
Exception,
Interrupt,
},
stval,
sstatus,
sie,
};
use crate::syscall::syscall; use crate::syscall::syscall;
use crate::task::{ use crate::task::{
exit_current_and_run_next, current_trap_cx, current_user_token, exit_current_and_run_next, suspend_current_and_run_next,
suspend_current_and_run_next,
current_user_token,
current_trap_cx,
}; };
use crate::timer::set_next_trigger; use crate::timer::set_next_trigger;
use crate::config::{TRAP_CONTEXT, TRAMPOLINE}; use core::arch::{asm, global_asm};
use riscv::register::{
mtvec::TrapMode,
scause::{self, Exception, Interrupt, Trap},
sie, stval, stvec,
};
global_asm!(include_str!("trap.S")); global_asm!(include_str!("trap.S"));
/// initialize CSR `stvec` as the entry of `__alltraps`
pub fn init() { pub fn init() {
set_kernel_trap_entry(); set_kernel_trap_entry();
} }
@ -41,16 +45,15 @@ fn set_user_trap_entry() {
} }
} }
#[allow(unused)] /// enable timer interrupt in sie CSR
pub fn enable_interrupt() {
unsafe { sstatus::set_sie(); }
}
pub fn enable_timer_interrupt() { pub fn enable_timer_interrupt() {
unsafe { sie::set_stimer(); } unsafe {
sie::set_stimer();
}
} }
#[no_mangle] #[no_mangle]
/// handle an interrupt, exception, or system call from user space
pub fn trap_handler() -> ! { pub fn trap_handler() -> ! {
set_kernel_trap_entry(); set_kernel_trap_entry();
let cx = current_trap_cx(); let cx = current_trap_cx();
@ -61,13 +64,15 @@ pub fn trap_handler() -> ! {
cx.sepc += 4; cx.sepc += 4;
cx.x[10] = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]) as usize; cx.x[10] = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]) as usize;
} }
Trap::Exception(Exception::StoreFault) | Trap::Exception(Exception::StoreFault)
Trap::Exception(Exception::StorePageFault) => { | Trap::Exception(Exception::StorePageFault)
println!("[kernel] PageFault in application, bad addr = {:#x}, bad instruction = {:#x}, core dumped.", stval, cx.sepc); | Trap::Exception(Exception::LoadFault)
| Trap::Exception(Exception::LoadPageFault) => {
println!("[kernel] PageFault in application, bad addr = {:#x}, bad instruction = {:#x}, kernel killed it.", stval, cx.sepc);
exit_current_and_run_next(); exit_current_and_run_next();
} }
Trap::Exception(Exception::IllegalInstruction) => { Trap::Exception(Exception::IllegalInstruction) => {
println!("[kernel] IllegalInstruction in application, core dumped."); println!("[kernel] IllegalInstruction in application, kernel killed it.");
exit_current_and_run_next(); exit_current_and_run_next();
} }
Trap::Interrupt(Interrupt::SupervisorTimer) => { Trap::Interrupt(Interrupt::SupervisorTimer) => {
@ -75,13 +80,20 @@ pub fn trap_handler() -> ! {
suspend_current_and_run_next(); suspend_current_and_run_next();
} }
_ => { _ => {
panic!("Unsupported trap {:?}, stval = {:#x}!", scause.cause(), stval); panic!(
"Unsupported trap {:?}, stval = {:#x}!",
scause.cause(),
stval
);
} }
} }
trap_return(); trap_return();
} }
#[no_mangle] #[no_mangle]
/// set the new addr of __restore asm function in TRAMPOLINE page,
/// set the reg a0 = trap_cx_ptr, reg a1 = phy addr of usr page table,
/// finally, jump to new addr of __restore asm function
pub fn trap_return() -> ! { pub fn trap_return() -> ! {
set_user_trap_entry(); set_user_trap_entry();
let trap_cx_ptr = TRAP_CONTEXT; let trap_cx_ptr = TRAP_CONTEXT;
@ -92,14 +104,22 @@ pub fn trap_return() -> ! {
} }
let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE; let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE;
unsafe { unsafe {
llvm_asm!("jr $0" :: "r"(restore_va), "{a0}"(trap_cx_ptr), "{a1}"(user_satp) :: "volatile"); asm!(
"fence.i",
"jr {restore_va}", // jump to new addr of __restore asm function
restore_va = in(reg) restore_va,
in("a0") trap_cx_ptr, // a0 = virt addr of Trap Context
in("a1") user_satp, // a1 = phy addr of usr page table
options(noreturn)
);
} }
panic!("Unreachable in back_to_user!");
} }
#[no_mangle] #[no_mangle]
/// Unimplement: traps/interrupts/exceptions from kernel mode
/// Todo: Chapter 9: I/O device
pub fn trap_from_kernel() -> ! { pub fn trap_from_kernel() -> ! {
panic!("a trap from kernel!"); panic!("a trap from kernel!");
} }
pub use context::{TrapContext}; pub use context::TrapContext;

View file

@ -1 +0,0 @@
nightly-2020-11-01

6
rust-toolchain.toml Normal file
View file

@ -0,0 +1,6 @@
[toolchain]
profile = "minimal"
# use the nightly version of the last stable toolchain, see <https://forge.rust-lang.org/>
channel = "nightly-2024-05-01"
components = ["rust-src", "llvm-tools", "rustfmt", "clippy"]
targets = ["riscv64gc-unknown-none-elf"]

File diff suppressed because one or more lines are too long

View file

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

View file

@ -7,3 +7,10 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
[profile.release]
debug = true
# [features]
# board_qemu = []
# board_k210 = []

View file

@ -13,7 +13,7 @@ elf: $(APPS)
@cargo build --release @cargo build --release
binary: elf binary: elf
$(foreach elf, $(ELFS), $(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf));) @$(foreach elf, $(ELFS), $(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf));)
build: binary build: binary

View file

@ -23,7 +23,7 @@ unsafe fn main() -> i32 {
println!("power_3 [{}/{}]", i, iter); println!("power_3 [{}/{}]", i, iter);
} }
} }
println!("{}^{} = {}(mod {})", p, iter, S[cur], m); println!("{}^{} = {}(MOD {})", p, iter, S[cur], m);
println!("Test power_3 OK!"); println!("Test power_3 OK!");
0 0
} }

View file

@ -23,7 +23,7 @@ unsafe fn main() -> i32 {
println!("power_5 [{}/{}]", i, iter); println!("power_5 [{}/{}]", i, iter);
} }
} }
println!("{}^{} = {}(mod {})", p, iter, S[cur], m); println!("{}^{} = {}(MOD {})", p, iter, S[cur], m);
println!("Test power_5 OK!"); println!("Test power_5 OK!");
0 0
} }

View file

@ -23,7 +23,7 @@ unsafe fn main() -> i32 {
println!("power_7 [{}/{}]", i, iter); println!("power_7 [{}/{}]", i, iter);
} }
} }
println!("{}^{} = {}(mod {})", p, iter, S[cur], m); println!("{}^{} = {}(MOD {})", p, iter, S[cur], m);
println!("Test power_7 OK!"); println!("Test power_7 OK!");
0 0
} }

View file

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

22
user/src/bin/03sleep1.rs Normal file
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,18 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use core::ptr::{null_mut, read_volatile};
#[no_mangle]
fn main() -> i32 {
println!("\nload_fault APP running...\n");
println!("Into Test load_fault, we will insert an invalid load operation...");
println!("Kernel should kill this application!");
unsafe {
let _i = read_volatile(null_mut::<u8>());
}
0
}

View file

@ -0,0 +1,18 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use core::ptr::null_mut;
#[no_mangle]
fn main() -> i32 {
println!("\nstore_fault APP running...\n");
println!("Into Test store_fault, we will insert an invalid store operation...");
println!("Kernel should kill this application!");
unsafe {
null_mut::<u8>().write_volatile(1);
}
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
}

47
user/src/bin/ch4b_sbrk.rs Normal file
View file

@ -0,0 +1,47 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::sbrk;
use core::ptr::slice_from_raw_parts_mut;
#[no_mangle]
fn main() -> i32 {
println!("Test sbrk start.");
const PAGE_SIZE: usize = 0x1000;
let origin_brk = sbrk(0);
println!("origin break point = {:x}", origin_brk);
let brk = sbrk(PAGE_SIZE as i32);
if brk != origin_brk {
return -1
}
let brk = sbrk(0);
println!("one page allocated, break point = {:x}", brk);
println!("try write to allocated page");
let new_page = unsafe { &mut *slice_from_raw_parts_mut(origin_brk as usize as *const u8 as *mut u8, PAGE_SIZE) };
for pos in 0..PAGE_SIZE {
new_page[pos] = 1;
}
println!("write ok");
sbrk(PAGE_SIZE as i32 * 10);
let brk = sbrk(0);
println!("10 page allocated, break point = {:x}", brk);
sbrk(PAGE_SIZE as i32 * -11);
let brk = sbrk(0);
println!("11 page DEALLOCATED, break point = {:x}", brk);
println!("try DEALLOCATED more one page, should be failed.");
let ret = sbrk(PAGE_SIZE as i32 * -1);
if ret != -1 {
println!("Test sbrk failed!");
return -1
}
println!("Test sbrk almost OK!");
println!("now write to deallocated page, should cause page fault.");
for pos in 0..PAGE_SIZE {
new_page[pos] = 2;
}
println!("Test sbrk failed!");
0
}

48
user/src/bin/sbrk_test.rs Normal file
View file

@ -0,0 +1,48 @@
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use core::ptr::slice_from_raw_parts_mut;
use user_lib::sbrk;
#[no_mangle]
fn main() -> i32 {
println!("Test sbrk start.");
const PAGE_SIZE: usize = 0x1000;
let origin_brk = sbrk(0);
println!("origin break point = {:x}", origin_brk);
let brk = sbrk(PAGE_SIZE as i32);
if brk != origin_brk {
return -1;
}
let brk = sbrk(0);
println!("one page allocated, break point = {:x}", brk);
println!("try write to allocated page");
let new_page = unsafe {
&mut *slice_from_raw_parts_mut(origin_brk as usize as *const u8 as *mut u8, PAGE_SIZE)
};
for pos in 0..PAGE_SIZE {
new_page[pos] = 1;
}
println!("write ok");
sbrk(PAGE_SIZE as i32 * 10);
let brk = sbrk(0);
println!("10 page allocated, break point = {:x}", brk);
sbrk(PAGE_SIZE as i32 * -11);
let brk = sbrk(0);
println!("11 page DEALLOCATED, break point = {:x}", brk);
println!("try DEALLOCATED more one page, should be failed.");
let ret = sbrk(PAGE_SIZE as i32 * -1);
if ret != -1 {
println!("Test sbrk failed!");
return -1;
}
println!("Test sbrk almost OK!");
println!("now write to deallocated page, should cause page fault.");
for pos in 0..PAGE_SIZE {
new_page[pos] = 2;
}
0
}

View file

@ -1,11 +1,13 @@
use super::write;
use core::fmt::{self, Write}; use core::fmt::{self, Write};
use crate::syscall::{STDOUT, sys_write};
struct Stdout; struct Stdout;
const STDOUT: usize = 1;
impl Write for Stdout { impl Write for Stdout {
fn write_str(&mut self, s: &str) -> fmt::Result { fn write_str(&mut self, s: &str) -> fmt::Result {
sys_write(STDOUT, s.as_bytes()); write(STDOUT, s.as_bytes());
Ok(()) Ok(())
} }
} }
@ -26,4 +28,4 @@ macro_rules! println {
($fmt: literal $(, $($arg: tt)+)?) => { ($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?)); $crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
} }
} }

View file

@ -2,9 +2,14 @@
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! { fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
let err = panic_info.message().unwrap(); let err = panic_info.message().unwrap();
if let Some(location) = panic_info.location() { if let Some(location) = panic_info.location() {
println!("Panicked at {}:{}, {}", location.file(), location.line(), err); println!(
"Panicked at {}:{}, {}",
location.file(),
location.line(),
err
);
} else { } else {
println!("Panicked: {}", err); println!("Panicked: {}", err);
} }
loop {} loop {}
} }

View file

@ -1,17 +1,16 @@
#![no_std] #![no_std]
#![feature(llvm_asm)]
#![feature(linkage)] #![feature(linkage)]
#![feature(panic_info_message)] #![feature(panic_info_message)]
#[macro_use] #[macro_use]
pub mod console; pub mod console;
mod syscall;
mod lang_items; mod lang_items;
mod syscall;
#[no_mangle] #[no_mangle]
#[link_section = ".text.entry"] #[link_section = ".text.entry"]
pub extern "C" fn _start() -> ! { pub extern "C" fn _start() -> ! {
syscall::sys_exit(main()); exit(main());
panic!("unreachable after sys_exit!"); panic!("unreachable after sys_exit!");
} }
@ -21,5 +20,53 @@ fn main() -> i32 {
panic!("Cannot find main!"); panic!("Cannot find main!");
} }
#[repr(C)]
#[derive(Debug, Default)]
pub struct TimeVal {
pub sec: usize,
pub usec: usize,
}
pub use syscall::*; impl TimeVal {
pub fn new() -> Self {
Self::default()
}
}
use syscall::*;
pub fn write(fd: usize, buf: &[u8]) -> isize {
sys_write(fd, buf)
}
pub fn exit(exit_code: i32) -> isize {
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 sbrk(size: i32) -> isize {
sys_sbrk(size)
}
pub fn munmap(start: usize, len: usize) -> isize {
sys_munmap(start, len)
}
pub fn mmap(start: usize, len: usize, prot: usize) -> isize {
sys_mmap(start, len, prot)
}
pub fn sleep(period_ms: usize) {
let start = get_time();
while get_time() < start + period_ms as isize {
sys_yield();
}
}

View file

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

View file

@ -1,18 +1,24 @@
pub const STDOUT: usize = 1; use core::arch::asm;
use crate::TimeVal;
const SYSCALL_WRITE: usize = 64; const SYSCALL_WRITE: usize = 64;
const SYSCALL_EXIT: usize = 93; const SYSCALL_EXIT: usize = 93;
const SYSCALL_YIELD: usize = 124; const SYSCALL_YIELD: usize = 124;
const SYSCALL_GET_TIME: usize = 169; const SYSCALL_GET_TIME: usize = 169;
const SYSCALL_SBRK: usize = 214;
const SYSCALL_MMAP: usize = 222;
const SYSCALL_MUNMAP: usize = 215;
fn syscall(id: usize, args: [usize; 3]) -> isize { fn syscall(id: usize, args: [usize; 3]) -> isize {
let mut ret: isize; let mut ret: isize;
unsafe { unsafe {
llvm_asm!("ecall" asm!(
: "={x10}" (ret) "ecall",
: "{x10}" (args[0]), "{x11}" (args[1]), "{x12}" (args[2]), "{x17}" (id) inlateout("x10") args[0] => ret,
: "memory" in("x11") args[1],
: "volatile" in("x12") args[2],
in("x17") id
); );
} }
ret ret
@ -22,14 +28,26 @@ pub fn sys_write(fd: usize, buffer: &[u8]) -> isize {
syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()]) syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()])
} }
pub fn sys_exit(xstate: i32) -> isize { pub fn sys_exit(exit_code: i32) -> isize {
syscall(SYSCALL_EXIT, [xstate as usize, 0, 0]) syscall(SYSCALL_EXIT, [exit_code as usize, 0, 0])
} }
pub fn sys_yield() -> isize { pub fn sys_yield() -> isize {
syscall(SYSCALL_YIELD, [0, 0, 0]) syscall(SYSCALL_YIELD, [0, 0, 0])
} }
pub fn sys_get_time() -> isize { pub fn sys_get_time(time: &TimeVal, tz: usize) -> isize {
syscall(SYSCALL_GET_TIME, [0, 0, 0]) syscall(SYSCALL_GET_TIME, [time as *const _ as usize, tz, 0])
} }
pub fn sys_sbrk(size: i32) -> isize {
syscall(SYSCALL_SBRK, [size 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])
}