Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feedback #1

Open
wants to merge 7 commits into
base: feedback
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/classroom/autograding.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"tests": [
{
"name": "run_os5",
"setup": "",
"run": "make test5",
"input": "",
"output": "",
"comparison": "included",
"timeout": 15,
"points": 100
}
]
}
47 changes: 47 additions & 0 deletions .github/workflows/classroom.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: GitHub Classroom Workflow

on: [push]

permissions:
checks: write
actions: read
contents: read

jobs:
build:
name: Autograding
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-04-11
components: rust-src, llvm-tools-preview
target: riscv64gc-unknown-none-elf
- uses: actions-rs/[email protected]
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
- uses: education/autograding@v1
58 changes: 58 additions & 0 deletions os5-ref/src/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Building
TARGET := riscv64gc-unknown-none-elf
MODE := release
KERNEL_ELF := target/$(TARGET)/$(MODE)/os
KERNEL_BIN := $(KERNEL_ELF).bin
KERNEL_ASM := $(KERNEL_ELF).asm

# BOARD
BOARD ?= qemu
SBI ?= rustsbi
BOOTLOADER := ../bootloader/$(SBI)-$(BOARD).bin

# KERNEL ENTRY
KERNEL_ENTRY_PA := 0x80200000

# Binutils
OBJDUMP := rust-objdump --arch-name=riscv64
OBJCOPY := rust-objcopy --binary-architecture=riscv64

CHAPTER ?= 5
TEST ?= $(CHAPTER)
BASE ?= 1

build: env $(KERNEL_BIN)

env:
(rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add $(TARGET)
cargo install cargo-binutils --vers ~0.3
rustup component add rust-src
rustup component add llvm-tools-preview

$(KERNEL_BIN): kernel
@$(OBJCOPY) $(KERNEL_ELF) --strip-all -O binary $@

kernel:
@make -C ../user build TEST=$(TEST) CHAPTER=$(CHAPTER) BASE=$(BASE)
@cargo build --release

clean:
@cargo clean

run: build
@qemu-system-riscv64 \
-machine virt \
-nographic \
-bios $(BOOTLOADER) \
-device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA)

debug: build
@tmux new-session -d \
"qemu-system-riscv64 -machine virt -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) -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 -2 attach-session -d

dbg: build
qemu-system-riscv64 -machine virt -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) -s -S

.PHONY: build env kernel clean run-inner
4 changes: 0 additions & 4 deletions os5/Cargo.toml
Original file line number Diff line number Diff line change
@@ -15,7 +15,3 @@ riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] }
spin = "0.9"
xmas-elf = "0.7.0"
lock_api = "=0.4.6"

[profile.release]
debug = true
opt-level = 0
3 changes: 0 additions & 3 deletions os5/Makefile
Original file line number Diff line number Diff line change
@@ -52,7 +52,4 @@ debug: build
tmux split-window -h "riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234'" && \
tmux -2 attach-session -d

dbg: build
qemu-system-riscv64 -machine virt -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) -s -S

.PHONY: build env kernel clean run-inner
9 changes: 6 additions & 3 deletions os5/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
//! Constants used in rCore
pub const USER_STACK_SIZE: usize = 4096 * 2;
pub const KERNEL_STACK_SIZE: usize = 4096 * 20;
pub const KERNEL_HEAP_SIZE: usize = 0x30_0000;
pub const KERNEL_STACK_SIZE: usize = 4096 * 2;
pub const KERNEL_HEAP_SIZE: usize = 0x20_0000;
pub const MEMORY_END: usize = 0x88000000;
pub const PAGE_SIZE: usize = 0x1000;
pub const PAGE_SIZE_BITS: usize = 0xc;
pub const MAX_SYSCALL_NUM: usize = 500;
pub const BIG_STRIDE: usize = usize::MAX;

pub const TRAMPOLINE: usize = usize::MAX - PAGE_SIZE + 1;
pub const TRAP_CONTEXT: usize = TRAMPOLINE - PAGE_SIZE;
pub const CLOCK_FREQ: usize = 12500000;

pub const BIG_STRIDE: u8 = u8::MAX;
101 changes: 98 additions & 3 deletions os5/src/console.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/*!
本模块实现了 print 和 println 宏
*/
//! SBI console driver, for text output
use crate::sbi::console_putchar;
use core::fmt::{self, Write};
@@ -21,15 +19,112 @@ pub fn print(args: fmt::Arguments) {
}

#[macro_export]
/// print string macro
macro_rules! print {
($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!($fmt $(, $($arg)+)?));
}
}

#[macro_export]
/// println string macro
macro_rules! println {
($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
}
}

/*
以下代码提供了与颜色相关的 ANSI 转义字符,以及彩色输出可以使用的函数与宏。
可以使用它们,甚至扩展它们,来提升开发体验和显示效果。
*/

// 使用 ANSI 转义字符来加上颜色
#[macro_export]
macro_rules! colorize {
($content: ident, $foreground_color: ident) => {
format_args!("\u{1B}[{}m{}\u{1B}[0m", $foreground_color as u8, $content)
};
($content: ident, $foreground_color: ident, $background_color: ident) => {
format_args!(
"\u{1B}[{}m\u{1B}[{}m{}\u{1B}[0m",
$foreground_color.into(),
$background_color.into(),
$content
)
};
}

pub fn print_colorized(
args: fmt::Arguments,
foreground_color: impl Into<u8>,
background_color: impl Into<u8>,
) {
Stdout
.write_fmt(colorize!(args, foreground_color, background_color))
.unwrap();
}

#[macro_export]
macro_rules! print_colorized {
($fmt: literal, $foreground_color: expr, $background_color: expr $(, $($arg: tt)+)?) => {
$crate::console::print_colorized(format_args!($fmt $(, $($arg)+)?), $foreground_color as u8, $background_color as u8);
};
}

#[macro_export]
macro_rules! println_colorized {
($fmt: literal, $foreground_color: expr, $background_color: expr $(, $($arg: tt)+)?) => {
$crate::console::print_colorized(format_args!(concat!($fmt, "\n") $(, $($arg)+)?), $foreground_color as u8, $background_color as u8);
}
}

#[allow(dead_code)]
pub enum ANSICON {
Reset = 0,
Bold = 1,
Underline = 4,
Blink = 5,
Reverse = 7,
FgBlack = 30,
FgRed = 31,
FgGreen = 32,
FgYellow = 33,
FgBlue = 34,
FgMagenta = 35,
FgCyan = 36,
FgWhite = 37,
FgDefault = 39,
FgLightGray = 90,
FgLightRed = 91,
FgLightGreen = 92,
FgLightYellow = 93,
FgLightBlue = 94,
FgLightMagenta = 95,
FgLightCyan = 96,
FgLightWhite = 97,
BgBlack = 40,
BgRed = 41,
BgGreen = 42,
BgYellow = 43,
BgBlue = 44,
BgMagenta = 45,
BgCyan = 46,
BgWhite = 47,
BgDefault = 49,
BgLightGray = 100,
BgLightRed = 101,
BgLightGreen = 102,
BgLightYellow = 103,
BgLightBlue = 104,
BgLightMagenta = 105,
BgLightCyan = 106,
BgLightWhite = 107,
}

impl From<ANSICON> for u8 {
fn from(con: ANSICON) -> Self {
con as Self
}
}
18 changes: 15 additions & 3 deletions os5/src/lang_items.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
//! The panic handler
use crate::console::ANSICON;
use crate::sbi::shutdown;

use core::panic::PanicInfo;

#[panic_handler]
/// panic handler
fn panic(info: &PanicInfo) -> ! {
if let Some(location) = info.location() {
println!(
"Panicked at {}:{} {}",
println_colorized!(
"[kernel] Panicked at {}:{} {}",
ANSICON::FgRed,
ANSICON::BgDefault,
location.file(),
location.line(),
info.message().unwrap()
);
} else {
println!("[kernel] Panicked: {}", info.message().unwrap());
println_colorized!(
"[kernel] Panicked: {}",
ANSICON::FgRed,
ANSICON::BgDefault,
info.message().unwrap()
);
}
shutdown()
}
200 changes: 200 additions & 0 deletions os5/src/link_app.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@

.align 3
.section .data
.global _num_app
_num_app:
.quad 19
.quad app_0_start
.quad app_1_start
.quad app_2_start
.quad app_3_start
.quad app_4_start
.quad app_5_start
.quad app_6_start
.quad app_7_start
.quad app_8_start
.quad app_9_start
.quad app_10_start
.quad app_11_start
.quad app_12_start
.quad app_13_start
.quad app_14_start
.quad app_15_start
.quad app_16_start
.quad app_17_start
.quad app_18_start
.quad app_18_end

.global _app_names
_app_names:
.string "ch2b_bad_address"
.string "ch2b_bad_instructions"
.string "ch2b_bad_register"
.string "ch2b_hello_world"
.string "ch2b_power_3"
.string "ch2b_power_5"
.string "ch2b_power_7"
.string "ch3b_sleep"
.string "ch3b_sleep1"
.string "ch3b_yield0"
.string "ch3b_yield1"
.string "ch3b_yield2"
.string "ch5b_exit"
.string "ch5b_forktest"
.string "ch5b_forktest2"
.string "ch5b_forktest_simple"
.string "ch5b_forktree"
.string "ch5b_initproc"
.string "ch5b_user_shell"

.section .data
.global app_0_start
.global app_0_end
.align 3
app_0_start:
.incbin "../user/build/elf/ch2b_bad_address.elf"
app_0_end:

.section .data
.global app_1_start
.global app_1_end
.align 3
app_1_start:
.incbin "../user/build/elf/ch2b_bad_instructions.elf"
app_1_end:

.section .data
.global app_2_start
.global app_2_end
.align 3
app_2_start:
.incbin "../user/build/elf/ch2b_bad_register.elf"
app_2_end:

.section .data
.global app_3_start
.global app_3_end
.align 3
app_3_start:
.incbin "../user/build/elf/ch2b_hello_world.elf"
app_3_end:

.section .data
.global app_4_start
.global app_4_end
.align 3
app_4_start:
.incbin "../user/build/elf/ch2b_power_3.elf"
app_4_end:

.section .data
.global app_5_start
.global app_5_end
.align 3
app_5_start:
.incbin "../user/build/elf/ch2b_power_5.elf"
app_5_end:

.section .data
.global app_6_start
.global app_6_end
.align 3
app_6_start:
.incbin "../user/build/elf/ch2b_power_7.elf"
app_6_end:

.section .data
.global app_7_start
.global app_7_end
.align 3
app_7_start:
.incbin "../user/build/elf/ch3b_sleep.elf"
app_7_end:

.section .data
.global app_8_start
.global app_8_end
.align 3
app_8_start:
.incbin "../user/build/elf/ch3b_sleep1.elf"
app_8_end:

.section .data
.global app_9_start
.global app_9_end
.align 3
app_9_start:
.incbin "../user/build/elf/ch3b_yield0.elf"
app_9_end:

.section .data
.global app_10_start
.global app_10_end
.align 3
app_10_start:
.incbin "../user/build/elf/ch3b_yield1.elf"
app_10_end:

.section .data
.global app_11_start
.global app_11_end
.align 3
app_11_start:
.incbin "../user/build/elf/ch3b_yield2.elf"
app_11_end:

.section .data
.global app_12_start
.global app_12_end
.align 3
app_12_start:
.incbin "../user/build/elf/ch5b_exit.elf"
app_12_end:

.section .data
.global app_13_start
.global app_13_end
.align 3
app_13_start:
.incbin "../user/build/elf/ch5b_forktest.elf"
app_13_end:

.section .data
.global app_14_start
.global app_14_end
.align 3
app_14_start:
.incbin "../user/build/elf/ch5b_forktest2.elf"
app_14_end:

.section .data
.global app_15_start
.global app_15_end
.align 3
app_15_start:
.incbin "../user/build/elf/ch5b_forktest_simple.elf"
app_15_end:

.section .data
.global app_16_start
.global app_16_end
.align 3
app_16_start:
.incbin "../user/build/elf/ch5b_forktree.elf"
app_16_end:

.section .data
.global app_17_start
.global app_17_end
.align 3
app_17_start:
.incbin "../user/build/elf/ch5b_initproc.elf"
app_17_end:

.section .data
.global app_18_start
.global app_18_end
.align 3
app_18_start:
.incbin "../user/build/elf/ch5b_user_shell.elf"
app_18_end:
7 changes: 7 additions & 0 deletions os5/src/loader.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
//! Loading user applications into memory
use alloc::vec::Vec;
use lazy_static::*;

/// Get the total number of applications.
pub fn get_num_app() -> usize {
extern "C" {
fn _num_app();
}
unsafe { (_num_app as usize as *const usize).read_volatile() }
}

/// get applications data
pub fn get_app_data(app_id: usize) -> &'static [u8] {
extern "C" {
fn _num_app();
@@ -25,6 +29,7 @@ pub fn get_app_data(app_id: usize) -> &'static [u8] {
}

lazy_static! {
/// A global read-only vector for saving app names
static ref APP_NAMES: Vec<&'static str> = {
let num_app = get_num_app();
extern "C" {
@@ -48,13 +53,15 @@ lazy_static! {
};
}

/// Get elf data by app name
pub fn get_app_data_by_name(name: &str) -> Option<&'static [u8]> {
let num_app = get_num_app();
(0..num_app)
.find(|&i| APP_NAMES[i] == name)
.map(get_app_data)
}

/// Print all of app names during kernel initialization
pub fn list_apps() {
println!("/**** APPS ****");
for app in APP_NAMES.iter() {
4 changes: 4 additions & 0 deletions os5/src/logging.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! Global logger
use log::{self, Level, LevelFilter, Log, Metadata, Record};

/// a simple logger
struct SimpleLogger;

impl Log for SimpleLogger {
@@ -27,6 +30,7 @@ impl Log for SimpleLogger {
fn flush(&self) {}
}

/// initiate logger
pub fn init() {
static LOGGER: SimpleLogger = SimpleLogger;
log::set_logger(&LOGGER).unwrap();
19 changes: 19 additions & 0 deletions os5/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
//! 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.
#![no_std]
#![no_main]
#![feature(panic_info_message)]
@@ -27,6 +44,7 @@ mod trap;
core::arch::global_asm!(include_str!("entry.asm"));
core::arch::global_asm!(include_str!("link_app.S"));

/// clear BSS segment
fn clear_bss() {
extern "C" {
fn sbss();
@@ -39,6 +57,7 @@ fn clear_bss() {
}

#[no_mangle]
/// the rust entry-point of os
pub fn rust_main() -> ! {
clear_bss();
logging::init();
Empty file removed os5/src/mm/README.md
Empty file.
246 changes: 246 additions & 0 deletions os5/src/mm/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
//! Implementation of physical and virtual address and page number.
use super::PageTableEntry;
use crate::config::{PAGE_SIZE, PAGE_SIZE_BITS};
use core::fmt::{self, Debug, Formatter};

/// Definitions
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct PhysAddr(pub usize);

#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct VirtAddr(pub usize);

#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct PhysPageNum(pub usize);

#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct VirtPageNum(pub usize);

/// Debugging
impl Debug for VirtAddr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("VA:{:#x}", self.0))
}
}
impl Debug for VirtPageNum {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("VPN:{:#x}", self.0))
}
}
impl Debug for PhysAddr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("PA:{:#x}", self.0))
}
}
impl Debug for PhysPageNum {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("PPN:{:#x}", self.0))
}
}

/// T: {PhysAddr, VirtAddr, PhysPageNum, VirtPageNum}
/// T -> usize: T.0
/// usize -> T: usize.into()
impl From<usize> for PhysAddr {
fn from(v: usize) -> Self {
Self(v)
}
}
impl From<usize> for PhysPageNum {
fn from(v: usize) -> Self {
Self(v)
}
}
impl From<usize> for VirtAddr {
fn from(v: usize) -> Self {
Self(v)
}
}
impl From<usize> for VirtPageNum {
fn from(v: usize) -> Self {
Self(v)
}
}
impl From<PhysAddr> for usize {
fn from(v: PhysAddr) -> Self {
v.0
}
}
impl From<PhysPageNum> for usize {
fn from(v: PhysPageNum) -> Self {
v.0
}
}
impl From<VirtAddr> for usize {
fn from(v: VirtAddr) -> Self {
v.0
}
}
impl From<VirtPageNum> for usize {
fn from(v: VirtPageNum) -> Self {
v.0
}
}

impl VirtAddr {
pub fn floor(&self) -> VirtPageNum {
VirtPageNum(self.0 / PAGE_SIZE)
}
pub fn ceil(&self) -> VirtPageNum {
VirtPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE)
}
pub fn page_offset(&self) -> usize {
self.0 & (PAGE_SIZE - 1)
}
pub fn aligned(&self) -> bool {
self.page_offset() == 0
}
}
impl From<VirtAddr> for VirtPageNum {
fn from(v: VirtAddr) -> Self {
assert_eq!(v.page_offset(), 0);
v.floor()
}
}
impl From<VirtPageNum> for VirtAddr {
fn from(v: VirtPageNum) -> Self {
Self(v.0 << PAGE_SIZE_BITS)
}
}
impl PhysAddr {
pub fn floor(&self) -> PhysPageNum {
PhysPageNum(self.0 / PAGE_SIZE)
}
pub fn ceil(&self) -> PhysPageNum {
PhysPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE)
}
pub fn page_offset(&self) -> usize {
self.0 & (PAGE_SIZE - 1)
}
pub fn aligned(&self) -> bool {
self.page_offset() == 0
}
}
impl From<PhysAddr> for PhysPageNum {
fn from(v: PhysAddr) -> Self {
assert_eq!(v.page_offset(), 0);
v.floor()
}
}
impl From<PhysPageNum> for PhysAddr {
fn from(v: PhysPageNum) -> Self {
Self(v.0 << PAGE_SIZE_BITS)
}
}

impl VirtPageNum {
pub fn indexes(&self) -> [usize; 3] {
let mut vpn = self.0;
let mut idx = [0usize; 3];
for i in (0..3).rev() {
idx[i] = vpn & 511;
vpn >>= 9;
}
idx
}
}

impl PhysAddr {
pub fn get_mut<T>(&self) -> &'static mut T {
unsafe { (self.0 as *mut T).as_mut().unwrap() }
}
}
impl PhysPageNum {
pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] {
let pa: PhysAddr = (*self).into();
unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512) }
}
pub fn get_bytes_array(&self) -> &'static mut [u8] {
let pa: PhysAddr = (*self).into();
unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096) }
}
pub fn get_mut<T>(&self) -> &'static mut T {
let pa: PhysAddr = (*self).into();
pa.get_mut()
}
}

pub trait StepByOne {
fn step(&mut self);
}
impl StepByOne for VirtPageNum {
fn step(&mut self) {
self.0 += 1;
}
}

#[derive(Copy, Clone)]
/// a simple range structure for type T
pub struct SimpleRange<T>
where
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
{
l: T,
r: T,
}
impl<T> SimpleRange<T>
where
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
{
pub fn new(start: T, end: T) -> Self {
assert!(start <= end, "start {:?} > end {:?}!", start, end);
Self { l: start, r: end }
}
pub fn get_start(&self) -> T {
self.l
}
pub fn get_end(&self) -> T {
self.r
}
}
impl<T> IntoIterator for SimpleRange<T>
where
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
{
type Item = T;
type IntoIter = SimpleRangeIterator<T>;
fn into_iter(self) -> Self::IntoIter {
SimpleRangeIterator::new(self.l, self.r)
}
}
/// iterator for the simple range structure
pub struct SimpleRangeIterator<T>
where
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
{
current: T,
end: T,
}
impl<T> SimpleRangeIterator<T>
where
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
{
pub fn new(l: T, r: T) -> Self {
Self { current: l, end: r }
}
}
impl<T> Iterator for SimpleRangeIterator<T>
where
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if self.current == self.end {
None
} else {
let t = self.current;
self.current.step();
Some(t)
}
}
}

/// a simple range structure for virtual page number
pub type VPNRange = SimpleRange<VirtPageNum>;
136 changes: 136 additions & 0 deletions os5/src/mm/frame_allocator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//! Implementation of [`FrameAllocator`] which
//! controls all the frames in the operating system.
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 ppn: PhysPageNum,
}

impl FrameTracker {
pub fn new(ppn: PhysPageNum) -> Self {
// page cleaning
let bytes_array = ppn.get_bytes_array();
for i in bytes_array {
*i = 0;
}
Self { ppn }
}
}

impl Debug for FrameTracker {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("FrameTracker:PPN={:#x}", self.ppn.0))
}
}

impl Drop for FrameTracker {
fn drop(&mut self) {
frame_dealloc(self.ppn);
}
}

trait FrameAllocator {
fn new() -> Self;
fn alloc(&mut self) -> Option<PhysPageNum>;
fn dealloc(&mut self, ppn: PhysPageNum);
}

/// an implementation for frame allocator
pub struct StackFrameAllocator {
current: usize,
end: usize,
recycled: Vec<usize>,
}

impl StackFrameAllocator {
pub fn init(&mut self, l: PhysPageNum, r: PhysPageNum) {
self.current = l.0;
self.end = r.0;
info!("last {} Physical Frames.", self.end - self.current);
}
}
impl FrameAllocator for StackFrameAllocator {
fn new() -> Self {
Self {
current: 0,
end: 0,
recycled: Vec::new(),
}
}
fn alloc(&mut self) -> Option<PhysPageNum> {
if let Some(ppn) = self.recycled.pop() {
Some(ppn.into())
} else if self.current == self.end {
None
} else {
self.current += 1;
Some((self.current - 1).into())
}
}
fn dealloc(&mut self, ppn: PhysPageNum) {
let ppn = ppn.0;
// validity check
if ppn >= self.current || self.recycled.iter().any(|v| *v == ppn) {
panic!("Frame ppn={:#x} has not been allocated!", ppn);
}
// recycle
self.recycled.push(ppn);
}
}

type FrameAllocatorImpl = StackFrameAllocator;

lazy_static! {
/// frame allocator instance through lazy_static!
pub static ref FRAME_ALLOCATOR: UPSafeCell<FrameAllocatorImpl> =
unsafe { UPSafeCell::new(FrameAllocatorImpl::new()) };
}

pub fn init_frame_allocator() {
extern "C" {
fn ekernel();
}
FRAME_ALLOCATOR.exclusive_access().init(
PhysAddr::from(ekernel as usize).ceil(),
PhysAddr::from(MEMORY_END).floor(),
);
}

/// initiate the frame allocator using `ekernel` and `MEMORY_END`
pub fn frame_alloc() -> Option<FrameTracker> {
FRAME_ALLOCATOR
.exclusive_access()
.alloc()
.map(FrameTracker::new)
}

/// deallocate a frame
fn frame_dealloc(ppn: PhysPageNum) {
FRAME_ALLOCATOR.exclusive_access().dealloc(ppn);
}

#[allow(unused)]
/// a simple test for frame allocator
pub fn frame_allocator_test() {
let mut v: Vec<FrameTracker> = Vec::new();
for i in 0..5 {
let frame = frame_alloc().unwrap();
info!("{:?}", frame);
v.push(frame);
}
v.clear();
for i in 0..5 {
let frame = frame_alloc().unwrap();
info!("{:?}", frame);
v.push(frame);
}
drop(v);
info!("frame_allocator_test passed!");
}
51 changes: 51 additions & 0 deletions os5/src/mm/heap_allocator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! The global allocator
use crate::config::KERNEL_HEAP_SIZE;
use buddy_system_allocator::LockedHeap;

#[global_allocator]
/// heap allocator instance
static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty();

#[alloc_error_handler]
/// panic when heap allocation error occurs
pub fn handle_alloc_error(layout: core::alloc::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];

/// initiate heap allocator
pub fn init_heap() {
unsafe {
HEAP_ALLOCATOR
.lock()
.init(HEAP_SPACE.as_ptr() as usize, KERNEL_HEAP_SIZE);
}
}

#[allow(unused)]
pub fn heap_test() {
use alloc::boxed::Box;
use alloc::vec::Vec;
extern "C" {
fn sbss();
fn ebss();
}
let bss_range = sbss as usize..ebss as usize;
let a = Box::new(5);
assert_eq!(*a, 5);
assert!(bss_range.contains(&(a.as_ref() as *const _ as usize)));
drop(a);
let mut v: Vec<usize> = Vec::new();
for i in 0..500 {
v.push(i);
}
for (i, vi) in v.iter().enumerate().take(500) {
assert_eq!(*vi, i);
}
assert!(bss_range.contains(&(v.as_ptr() as usize)));
drop(v);
info!("heap_test passed!");
}
484 changes: 484 additions & 0 deletions os5/src/mm/memory_set.rs

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions os5/src/mm/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//! 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 frame_allocator;
mod heap_allocator;
mod memory_set;
mod page_table;

pub use address::{PhysAddr, PhysPageNum, VirtAddr, VirtPageNum};
pub use address::{StepByOne, VPNRange};
pub use frame_allocator::{frame_alloc, FrameTracker};
pub use memory_set::remap_test;
pub use memory_set::{MapPermission, MemorySet, KERNEL_SPACE};
pub use page_table::*;


/// initiate heap allocator, frame allocator and kernel space
pub fn init() {
heap_allocator::init_heap();
frame_allocator::init_frame_allocator();
KERNEL_SPACE.exclusive_access().activate();
}
206 changes: 206 additions & 0 deletions os5/src/mm/page_table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
//! Implementation of [`PageTableEntry`] and [`PageTable`].
use super::{frame_alloc, FrameTracker, PhysAddr, PhysPageNum, StepByOne, VirtAddr, VirtPageNum};
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use bitflags::*;

bitflags! {
/// page table entry flags
pub struct PTEFlags: u8 {
const V = 1 << 0;
const R = 1 << 1;
const W = 1 << 2;
const X = 1 << 3;
const U = 1 << 4;
const G = 1 << 5;
const A = 1 << 6;
const D = 1 << 7;
}
}

#[derive(Copy, Clone)]
#[repr(C)]
/// page table entry structure
pub struct PageTableEntry {
pub bits: usize,
}

impl PageTableEntry {
pub fn new(ppn: PhysPageNum, flags: PTEFlags) -> Self {
PageTableEntry {
bits: ppn.0 << 10 | flags.bits as usize,
}
}
pub fn empty() -> Self {
PageTableEntry { bits: 0 }
}
pub fn ppn(&self) -> PhysPageNum {
(self.bits >> 10 & ((1usize << 44) - 1)).into()
}
pub fn flags(&self) -> PTEFlags {
PTEFlags::from_bits(self.bits as u8).unwrap()
}
pub fn is_valid(&self) -> bool {
(self.flags() & PTEFlags::V) != PTEFlags::empty()
}
pub fn readable(&self) -> bool {
(self.flags() & PTEFlags::R) != PTEFlags::empty()
}
pub fn writable(&self) -> bool {
(self.flags() & PTEFlags::W) != PTEFlags::empty()
}
pub fn executable(&self) -> bool {
(self.flags() & PTEFlags::X) != PTEFlags::empty()
}
}

/// page table structure
pub struct PageTable {
root_ppn: PhysPageNum,
frames: Vec<FrameTracker>,
}

/// Assume that it won't oom when creating/mapping.
impl PageTable {
pub fn new() -> Self {
let frame = frame_alloc().unwrap();
PageTable {
root_ppn: frame.ppn,
frames: vec![frame],
}
}
/// Temporarily used to get arguments from user space.
pub fn from_token(satp: usize) -> Self {
Self {
root_ppn: PhysPageNum::from(satp & ((1usize << 44) - 1)),
frames: Vec::new(),
}
}
fn find_pte_create(&mut self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> {
let mut idxs = vpn.indexes();
let mut ppn = self.root_ppn;
let mut result: Option<&mut PageTableEntry> = None;
for (i, idx) in idxs.iter_mut().enumerate() {
let pte = &mut ppn.get_pte_array()[*idx];
if i == 2 {
result = Some(pte);
break;
}
if !pte.is_valid() {
let frame = frame_alloc().unwrap();
*pte = PageTableEntry::new(frame.ppn, PTEFlags::V);
self.frames.push(frame);
}
ppn = pte.ppn();
}
result
}
fn find_pte(&self, vpn: VirtPageNum) -> Option<&PageTableEntry> {
let idxs = vpn.indexes();
let mut ppn = self.root_ppn;
let mut result: Option<&PageTableEntry> = None;
for (i, idx) in idxs.iter().enumerate() {
let pte = &ppn.get_pte_array()[*idx];
if i == 2 {
result = Some(pte);
break;
}
if !pte.is_valid() {
return None;
}
ppn = pte.ppn();
}
result
}
#[allow(unused)]
pub fn map(&mut self, vpn: VirtPageNum, ppn: PhysPageNum, flags: PTEFlags) {
let pte = self.find_pte_create(vpn).unwrap();
assert!(!pte.is_valid(), "vpn {:?} is mapped before mapping", vpn);
*pte = PageTableEntry::new(ppn, flags | PTEFlags::V);
}
#[allow(unused)]
pub fn unmap(&mut self, vpn: VirtPageNum) {
let pte = self.find_pte_create(vpn).unwrap();
assert!(pte.is_valid(), "vpn {:?} is invalid before unmapping", vpn);
*pte = PageTableEntry::empty();
}
pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> {
self.find_pte(vpn).copied()
}
pub fn translate_va(&self, va: VirtAddr) -> Option<PhysAddr> {
self.find_pte(va.clone().floor()).map(|pte| {
//println!("translate_va:va = {:?}", va);
let aligned_pa: PhysAddr = pte.ppn().into();
//println!("translate_va:pa_align = {:?}", aligned_pa);
let offset = va.page_offset();
let aligned_pa_usize: usize = aligned_pa.into();
(aligned_pa_usize + offset).into()
})
}
pub fn token(&self) -> usize {
8usize << 60 | self.root_ppn.0
}
}

/// 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 mut start = ptr as usize;
let end = start + len;
let mut v = Vec::new();
while start < end {
let start_va = VirtAddr::from(start);
let mut vpn = start_va.floor();
let ppn = page_table.translate(vpn).unwrap().ppn();
vpn.step();
let mut end_va: VirtAddr = vpn.into();
end_va = end_va.min(VirtAddr::from(end));
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();
}
v
}

pub fn translated_str(token: usize, ptr: *const u8) -> String {
let page_table = PageTable::from_token(token);
let mut string = String::new();
let mut va = ptr as usize;
loop {
let ch: u8 = *(page_table
.translate_va(VirtAddr::from(va))
.unwrap()
.get_mut());
if ch == 0 {
break;
} else {
string.push(ch as char);
va += 1;
}
}
string
}

pub fn translated_refmut<T>(token: usize, ptr: *mut T) -> &'static mut T {
//println!("into translated_refmut!");
let page_table = PageTable::from_token(token);
let va = ptr as usize;
//println!("translated_refmut: before translate_va");
page_table
.translate_va(VirtAddr::from(va))
.unwrap()
.get_mut()
}


//
pub fn translated_va2pa(token: usize, va: VirtAddr) -> usize {
let page_table = PageTable::from_token(token);
let pa = page_table.translate_va(va).unwrap();
pa.0
}
8 changes: 7 additions & 1 deletion os5/src/sbi.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! SBI call wrappers
#![allow(unused)]

const SBI_SET_TIMER: usize = 0;
@@ -6,11 +8,11 @@ const SBI_CONSOLE_GETCHAR: usize = 2;
const SBI_SHUTDOWN: usize = 8;

#[inline(always)]
/// general sbi call
fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize {
let mut ret;
unsafe {
core::arch::asm!(
"li x16, 0",
"ecall",
inlateout("x10") arg0 => ret,
in("x11") arg1,
@@ -21,18 +23,22 @@ fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize {
ret
}

/// use sbi call to set timer
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) {
sbi_call(SBI_CONSOLE_PUTCHAR, c, 0, 0);
}

/// use sbi call to getchar from console (qemu uart handler)
pub fn console_getchar() -> usize {
sbi_call(SBI_CONSOLE_GETCHAR, 0, 0, 0)
}

/// use sbi call to shutdown the kernel
pub fn shutdown() -> ! {
sbi_call(SBI_SHUTDOWN, 0, 0, 0);
panic!("It should shutdown!");
Empty file removed os5/src/sync/README.md
Empty file.
5 changes: 5 additions & 0 deletions os5/src/sync/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//! Synchronization and interior mutability primitives
mod up;

pub use up::UPSafeCell;
31 changes: 31 additions & 0 deletions os5/src/sync/up.rs
Original file line number Diff line number Diff line change
@@ -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),
}
}
/// Panic if the data has been borrowed.
pub fn exclusive_access(&self) -> RefMut<'_, T> {
self.inner.borrow_mut()
}
}
Empty file removed os5/src/syscall/README.md
Empty file.
50 changes: 50 additions & 0 deletions os5/src/syscall/fs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//! File and filesystem-related syscalls
use crate::mm::translated_byte_buffer;
use crate::sbi::console_getchar;
use crate::task::{current_user_token, suspend_current_and_run_next};

const FD_STDIN: usize = 0;
const FD_STDOUT: usize = 1;

pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
match fd {
FD_STDOUT => {
let buffers = translated_byte_buffer(current_user_token(), buf, len);
for buffer in buffers {
print!("{}", core::str::from_utf8(buffer).unwrap());
}
len as isize
}
_ => {
panic!("Unsupported fd in sys_write!");
}
}
}

pub fn sys_read(fd: usize, buf: *const u8, len: usize) -> isize {
match fd {
FD_STDIN => {
assert_eq!(len, 1, "Only support len = 1 in sys_read!");
let mut c: usize;
loop {
c = console_getchar();
if c == 0 {
suspend_current_and_run_next();
continue;
} else {
break;
}
}
let ch = c as u8;
let mut buffers = translated_byte_buffer(current_user_token(), buf, len);
unsafe {
buffers[0].as_mut_ptr().write_volatile(ch);
}
1
}
_ => {
panic!("Unsupported fd in sys_read!");
}
}
}
56 changes: 56 additions & 0 deletions os5/src/syscall/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! 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_READ: usize = 63;
const SYSCALL_WRITE: usize = 64;
const SYSCALL_EXIT: usize = 93;
const SYSCALL_YIELD: usize = 124;
const SYSCALL_GET_TIME: usize = 169;
const SYSCALL_GETPID: usize = 172;
const SYSCALL_FORK: usize = 220;
const SYSCALL_EXEC: usize = 221;
const SYSCALL_WAITPID: usize = 260;
const SYSCALL_SPAWN: usize = 400;
const SYSCALL_MUNMAP: usize = 215;
const SYSCALL_MMAP: usize = 222;
const SYSCALL_SET_PRIORITY: usize = 140;
const SYSCALL_TASK_INFO: usize = 410;

mod fs;
mod process;

use fs::*;
use process::*;

use crate::task::incr_syscall_times;

/// handle syscall exception with `syscall_id` and other arguments
pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
incr_syscall_times(syscall_id);
match syscall_id {
SYSCALL_READ => sys_read(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_YIELD => sys_yield(),
SYSCALL_GETPID => sys_getpid(),
SYSCALL_FORK => sys_fork(),
SYSCALL_EXEC => sys_exec(args[0] as *const u8),
SYSCALL_WAITPID => sys_waitpid(args[0] as isize, args[1] as *mut i32),
SYSCALL_GET_TIME => sys_get_time(args[0] as *mut TimeVal, args[1]),
SYSCALL_MMAP => sys_mmap(args[0], args[1], args[2]),
SYSCALL_MUNMAP => sys_munmap(args[0], args[1]),
SYSCALL_SET_PRIORITY => sys_set_priority(args[0] as isize),
SYSCALL_TASK_INFO => sys_task_info(args[0] as *mut TaskInfo),
SYSCALL_SPAWN => sys_spawn(args[0] as *const u8),
_ => panic!("Unsupported syscall_id: {}", syscall_id),
}
}
222 changes: 222 additions & 0 deletions os5/src/syscall/process.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
//! Process management syscalls
use crate::loader::get_app_data_by_name;
use crate::mm::*;
use crate::task::*;
use crate::timer::get_time_us;
use alloc::sync::Arc;
use crate::config::{PAGE_SIZE, MAX_SYSCALL_NUM};

#[repr(C)]
#[derive(Debug)]
pub struct TimeVal {
pub sec: usize,
pub usec: usize,
}

#[derive(Clone, Copy)]
pub struct TaskInfo {
pub status: TaskStatus,
pub syscall_times: [u32; MAX_SYSCALL_NUM],
pub time: usize,
}

pub fn sys_exit(exit_code: i32) -> ! {
debug!("[kernel] Application exited with code {}", exit_code);
exit_current_and_run_next(exit_code);
panic!("Unreachable in sys_exit!");
}

/// current task gives up resources for other tasks
pub fn sys_yield() -> isize {
suspend_current_and_run_next();
0
}

pub fn sys_getpid() -> isize {
current_task().unwrap().pid.0 as isize
}

/// Syscall Fork which returns 0 for child process and child_pid for parent process
pub fn sys_fork() -> isize {
let current_task = current_task().unwrap();
let new_task = current_task.fork();
let new_pid = new_task.pid.0;
// modify trap context of new_task, because it returns immediately after switching
let trap_cx = new_task.inner_exclusive_access().get_trap_cx();
// we do not have to move to next instruction since we have done it before
// for child process, fork returns 0
trap_cx.x[10] = 0;
// add new task to scheduler
add_task(new_task);
new_pid as isize
}

/// Syscall Exec which accepts the elf path
pub fn sys_exec(path: *const u8) -> isize {
let token = current_user_token();
let path = translated_str(token, path);
if let Some(data) = get_app_data_by_name(path.as_str()) {
let task = current_task().unwrap();
task.exec(data);
0
} else {
-1
}
}

/// If there is not a child process whose pid is same as given, return -1.
/// Else if there is a child process but it is still running, return -2.
pub fn sys_waitpid(pid: isize, exit_code_ptr: *mut i32) -> isize {
let task = current_task().unwrap();
// find a child process

// ---- access current TCB exclusively
let mut inner = task.inner_exclusive_access();
if !inner
.children
.iter()
.any(|p| pid == -1 || pid as usize == p.getpid())
{
return -1;
// ---- release current PCB
}
let pair = inner.children.iter().enumerate().find(|(_, p)| {
// ++++ temporarily access child PCB lock exclusively
p.inner_exclusive_access().is_zombie() && (pid == -1 || pid as usize == p.getpid())
// ++++ release child PCB
});
if let Some((idx, _)) = pair {
let child = inner.children.remove(idx);
// confirm that child will be deallocated after removing from children list
assert_eq!(Arc::strong_count(&child), 1);
let found_pid = child.getpid();
// ++++ temporarily access child TCB exclusively
let exit_code = child.inner_exclusive_access().exit_code;
// ++++ release child PCB
*translated_refmut(inner.memory_set.token(), exit_code_ptr) = exit_code;
found_pid as isize
} else {
-2
}
// ---- release current PCB lock automatically
}

// YOUR JOB: 引入虚地址后重写 sys_get_time
pub fn sys_get_time(_ts: *mut TimeVal, _tz: usize) -> isize {
let _us = get_time_us();
// unsafe {
// *ts = TimeVal {
// sec: us / 1_000_000,
// usec: us % 1_000_000,
// };
// }
let va = VirtAddr::from(_ts as usize);
let pa = translated_va2pa(current_user_token(), va);

let ptr_ts = pa as *mut TimeVal;
unsafe {
*ptr_ts = TimeVal {
sec: _us / 1_000_000,
usec: _us % 1_000_000,
};
}
0
}

// YOUR JOB: 引入虚地址后重写 sys_task_info
pub fn sys_task_info(ti: *mut TaskInfo) -> isize {
let va = VirtAddr::from(ti as usize);
let pa = translated_va2pa(current_user_token(), va);

let time = cal_time(get_time_us());

let ptr_ti = pa as *mut TaskInfo;
unsafe {
*ptr_ti = TaskInfo {
status: get_current_task_status(),
syscall_times: get_current_syscall_times(),
time,
};
}
0
}

pub fn cal_time(us: usize) -> usize {
let sec = us / 1_000_000;
let usec = us % 1_000_000;

((sec & 0xffff) * 1000 + usec / 1000) as usize
}

// YOUR JOB: 实现sys_set_priority,为任务添加优先级
// 合法返回proi, 否则返回-1
pub fn sys_set_priority(_prio: isize) -> isize {
// stride 调度要求进程优先级 >= 2
if _prio < 2 {
return -1;
}
set_priority(_prio);
_prio
}

// YOUR JOB: 扩展内核以实现 sys_mmap 和 sys_munmap
// YOUR JOB: 扩展内核以实现 sys_mmap 和 sys_munmap
pub fn sys_mmap(_start: usize, _len: usize, _port: usize) -> isize {
if _start % PAGE_SIZE != 0 { // 未对齐
return -1;
}

if _port & !0x07 != 0 || _port & 0x07 == 0 || _len <= 0 {
return -1;
}

let p = _port as u8;
let mut perm = MapPermission::from_bits(p << 1).unwrap();
perm |= MapPermission::U;

let start_va: VirtAddr = VirtAddr::from(_start).floor().into();
let end_va: VirtAddr = VirtAddr::from(_start + _len).ceil().into();

let ok = set_mmap(start_va, end_va, perm);
if !ok {
return -1;
}
0
}

pub fn sys_munmap(_start: usize, _len: usize) -> isize {
if _start % PAGE_SIZE != 0 { // 未对齐
return -1;
}

if _len <= 0 {
return -1;
}

let start_va: VirtAddr = VirtAddr::from(_start).floor().into();
let end_va: VirtAddr = VirtAddr::from(_start + _len).ceil().into();

let ok = set_munmap(start_va, end_va);
if !ok {
return -1;
}
0
}

//
// YOUR JOB: 实现 sys_spawn 系统调用
// ALERT: 注意在实现 SPAWN 时不需要复制父进程地址空间,SPAWN != FORK + EXEC
pub fn sys_spawn(_path: *const u8) -> isize {
let path = translated_str(current_user_token(), _path);
// assume the name is valid
let elf_data = get_app_data_by_name(path.as_str()).unwrap();

let current_task = current_task().unwrap();
let new_task = current_task.spawn(elf_data);
let new_pid = new_task.pid.0;

//add new task to scheduler
add_task(new_task);
new_pid as isize
}
Empty file removed os5/src/task/README.md
Empty file.
32 changes: 32 additions & 0 deletions os5/src/task/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//! Implementation of [`TaskContext`]
use crate::trap::trap_return;

#[derive(Copy, Clone)]
#[repr(C)]
/// task context structure containing some registers
pub struct TaskContext {
/// Ret position after task switching
ra: usize,
/// Stack pointer
sp: usize,
/// s0-11 register, callee saved
s: [usize; 12],
}

impl TaskContext {
pub fn zero_init() -> Self {
Self {
ra: 0,
sp: 0,
s: [0; 12],
}
}
pub fn goto_trap_return(kstack_ptr: usize) -> Self {
Self {
ra: trap_return as usize,
sp: kstack_ptr,
s: [0; 12],
}
}
}
68 changes: 68 additions & 0 deletions os5/src/task/manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//! Implementation of [`TaskManager`]
//!
//! It is only used to manage processes and schedule process based on ready queue.
//! Other CPU process monitoring functions are in Processor.

use super::TaskControlBlock;
use crate::sync::UPSafeCell;
use alloc::collections::VecDeque;
// use alloc::collections::BinaryHeap;
use alloc::sync::Arc;
use lazy_static::*;

pub struct TaskManager {
ready_queue: VecDeque<Arc<TaskControlBlock>>,
}

// pub struct TaskManager {
// ready_queue: BinaryHeap<Arc<TaskControlBlock>>,
// }

// YOUR JOB: FIFO->Stride
/// A simple FIFO scheduler.
impl TaskManager {
pub fn new() -> Self {
Self {
ready_queue: VecDeque::new(),
}
}
/// Add process back to ready queue
pub fn add(&mut self, task: Arc<TaskControlBlock>) {
self.ready_queue.push_back(task);
}
/// Take a process out of the ready queue
pub fn fetch(&mut self) -> Option<Arc<TaskControlBlock>> {
// self.ready_queue.pop_front()

// for stride schedule
let mut index = 0;
let mut minimun_stride = self.ready_queue[index].inner_exclusive_access().stride;
for (i, task) in self.ready_queue.iter().enumerate() {
let curr_stride = task.inner_exclusive_access().stride;
let cmp = (curr_stride - minimun_stride) as i8;
if cmp <= 0 {
minimun_stride = curr_stride;
index = i;
}
}
self.ready_queue.remove(index)
}
}




lazy_static! {
/// TASK_MANAGER instance through lazy_static!
pub static ref TASK_MANAGER: UPSafeCell<TaskManager> =
unsafe { UPSafeCell::new(TaskManager::new()) };
}

pub fn add_task(task: Arc<TaskControlBlock>) {
TASK_MANAGER.exclusive_access().add(task);
}

pub fn fetch_task() -> Option<Arc<TaskControlBlock>> {
TASK_MANAGER.exclusive_access().fetch()
}
97 changes: 97 additions & 0 deletions os5/src/task/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//! Implementation of process management mechanism
//!
//! Here is the entry for process scheduling required by other modules
//! (such as syscall or clock interrupt).
//! By suspending or exiting the current process, you can
//! modify the process state, manage the process queue through TASK_MANAGER,
//! and switch the control flow through PROCESSOR.
//!
//! Be careful when you see [`__switch`]. Control flow around this function
//! might not be what you expect.
mod context;
mod manager;
mod pid;
mod processor;
mod switch;
#[allow(clippy::module_inception)]
mod task;

use crate::loader::get_app_data_by_name;
use alloc::sync::Arc;
use lazy_static::*;
use manager::fetch_task;
use switch::__switch;
pub use task::{TaskControlBlock, TaskStatus};

pub use context::TaskContext;
pub use manager::add_task;
pub use pid::{pid_alloc, KernelStack, PidHandle};
pub use processor::*;

/// Make current task suspended and switch to the next task
pub fn suspend_current_and_run_next() {
// There must be an application running.
let task = take_current_task().unwrap();

// ---- access current TCB exclusively
let mut task_inner = task.inner_exclusive_access();
let task_cx_ptr = &mut task_inner.task_cx as *mut TaskContext;
// Change status to Ready
task_inner.task_status = TaskStatus::Ready;
drop(task_inner);
// ---- release current PCB

// push back to ready queue.
add_task(task);
// jump to scheduling cycle
schedule(task_cx_ptr);
}

/// Exit current task, recycle process resources and switch to the next task
pub fn exit_current_and_run_next(exit_code: i32) {
// take from Processor
let task = take_current_task().unwrap();
// **** access current TCB exclusively
let mut inner = task.inner_exclusive_access();
// Change status to Zombie
inner.task_status = TaskStatus::Zombie;
// Record exit code
inner.exit_code = exit_code;
// do not move to its parent but under initproc

// ++++++ access initproc TCB exclusively
{
let mut initproc_inner = INITPROC.inner_exclusive_access();
for child in inner.children.iter() {
child.inner_exclusive_access().parent = Some(Arc::downgrade(&INITPROC));
initproc_inner.children.push(child.clone());
}
}
// ++++++ release parent PCB

inner.children.clear();
// deallocate user space
inner.memory_set.recycle_data_pages();
drop(inner);
// **** release current PCB
// drop task manually to maintain rc correctly
drop(task);
// we do not have to save task context
let mut _unused = TaskContext::zero_init();
schedule(&mut _unused as *mut _);
}

lazy_static! {
/// Creation of initial process
///
/// the name "initproc" may be changed to any other app name like "usertests",
/// but we have user_shell, so we don't need to change it.
pub static ref INITPROC: Arc<TaskControlBlock> = Arc::new(TaskControlBlock::new(
get_app_data_by_name("ch5b_initproc").unwrap()
));
}

pub fn add_initproc() {
add_task(INITPROC.clone());
}
116 changes: 116 additions & 0 deletions os5/src/task/pid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//! Task pid implementation.
//!
//! Assign PID to the process here. At the same time, the position of the application KernelStack
//! is determined according to the PID.
use crate::config::{KERNEL_STACK_SIZE, PAGE_SIZE, TRAMPOLINE};
use crate::mm::{MapPermission, VirtAddr, KERNEL_SPACE};
use crate::sync::UPSafeCell;
use alloc::vec::Vec;
use lazy_static::*;

/// Process identifier allocator using stack allocation
struct PidAllocator {
/// A new PID to be assigned
current: usize,
/// Recycled PID sequence
recycled: Vec<usize>,
}

impl PidAllocator {
pub fn new() -> Self {
PidAllocator {
current: 0,
recycled: Vec::new(),
}
}
pub fn alloc(&mut self) -> PidHandle {
if let Some(pid) = self.recycled.pop() {
PidHandle(pid)
} else {
self.current += 1;
PidHandle(self.current - 1)
}
}
pub fn dealloc(&mut self, pid: usize) {
assert!(pid < self.current);
assert!(
!self.recycled.iter().any(|ppid| *ppid == pid),
"pid {} has been deallocated!",
pid
);
self.recycled.push(pid);
}
}

lazy_static! {
/// Pid allocator instance through lazy_static!
static ref PID_ALLOCATOR: UPSafeCell<PidAllocator> =
unsafe { UPSafeCell::new(PidAllocator::new()) };
}

/// Abstract structure of PID
pub struct PidHandle(pub usize);

impl Drop for PidHandle {
fn drop(&mut self) {
//println!("drop pid {}", self.0);
PID_ALLOCATOR.exclusive_access().dealloc(self.0);
}
}

pub fn pid_alloc() -> PidHandle {
PID_ALLOCATOR.exclusive_access().alloc()
}

/// Return (bottom, top) of a kernel stack in kernel space.
pub fn kernel_stack_position(app_id: usize) -> (usize, usize) {
let top = TRAMPOLINE - app_id * (KERNEL_STACK_SIZE + PAGE_SIZE);
let bottom = top - KERNEL_STACK_SIZE;
(bottom, top)
}

/// KernelStack corresponding to PID
pub struct KernelStack {
pid: usize,
}

impl KernelStack {
pub fn new(pid_handle: &PidHandle) -> Self {
let pid = pid_handle.0;
let (kernel_stack_bottom, kernel_stack_top) = kernel_stack_position(pid);
KERNEL_SPACE.exclusive_access().insert_framed_area(
kernel_stack_bottom.into(),
kernel_stack_top.into(),
MapPermission::R | MapPermission::W,
);
KernelStack { pid: pid_handle.0 }
}
#[allow(unused)]
/// Push a variable of type T into the top of the KernelStack and return its raw pointer
pub fn push_on_top<T>(&self, value: T) -> *mut T
where
T: Sized,
{
let kernel_stack_top = self.get_top();
let ptr_mut = (kernel_stack_top - core::mem::size_of::<T>()) as *mut T;
unsafe {
*ptr_mut = value;
}
ptr_mut
}
pub fn get_top(&self) -> usize {
let (_, kernel_stack_top) = kernel_stack_position(self.pid);
kernel_stack_top
}
}

impl Drop for KernelStack {
fn drop(&mut self) {
let (kernel_stack_bottom, _) = kernel_stack_position(self.pid);
let kernel_stack_bottom_va: VirtAddr = kernel_stack_bottom.into();
KERNEL_SPACE
.exclusive_access()
.remove_area_with_start_vpn(kernel_stack_bottom_va.into());
}
}
144 changes: 144 additions & 0 deletions os5/src/task/processor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//! Implementation of [`Processor`] and Intersection of control flow
//!
//! Here, the continuous operation of user apps in CPU is maintained,
//! the current running state of CPU is recorded,
//! and the replacement and transfer of control flow of different applications are executed.

use super::__switch;
use super::{fetch_task, TaskStatus};
use super::{TaskContext, TaskControlBlock};
use crate::config::{MAX_SYSCALL_NUM, BIG_STRIDE};
use crate::sync::UPSafeCell;
use crate::trap::TrapContext;
use alloc::sync::Arc;
use lazy_static::*;
use crate::mm::*;

/// Processor management structure
pub struct Processor {
/// The task currently executing on the current processor
current: Option<Arc<TaskControlBlock>>,
/// The basic control flow of each core, helping to select and switch process
idle_task_cx: TaskContext,
}

impl Processor {
pub fn new() -> Self {
Self {
current: None,
idle_task_cx: TaskContext::zero_init(),
}
}
fn get_idle_task_cx_ptr(&mut self) -> *mut TaskContext {
&mut self.idle_task_cx as *mut _
}
pub fn take_current(&mut self) -> Option<Arc<TaskControlBlock>> {
self.current.take()
}
pub fn current(&self) -> Option<Arc<TaskControlBlock>> {
self.current.as_ref().map(|task| Arc::clone(task))
}
}

lazy_static! {
/// PROCESSOR instance through lazy_static!
pub static ref PROCESSOR: UPSafeCell<Processor> = unsafe { UPSafeCell::new(Processor::new()) };
}

/// The main part of process execution and scheduling
///
/// Loop fetch_task to get the process that needs to run,
/// and switch the process through __switch
pub fn run_tasks() {
loop {
let mut processor = PROCESSOR.exclusive_access();
if let Some(task) = fetch_task() {
let idle_task_cx_ptr = processor.get_idle_task_cx_ptr();
// access coming task TCB exclusively
let mut task_inner = task.inner_exclusive_access();
let next_task_cx_ptr = &task_inner.task_cx as *const TaskContext;
task_inner.task_status = TaskStatus::Running;
// accumulate stride
task_inner.stride += BIG_STRIDE / task_inner.priority;
drop(task_inner);
// release coming task TCB manually
processor.current = Some(task);
// release processor manually
drop(processor);
unsafe {
__switch(idle_task_cx_ptr, next_task_cx_ptr);
}
}
}
}

// get status of the current task
pub fn get_current_task_status() -> TaskStatus {
let task = current_task().unwrap();
let status = task.inner_exclusive_access().get_status();
status
}

pub fn get_current_syscall_times() -> [u32; MAX_SYSCALL_NUM] {
let task = current_task().unwrap();
let syscall_times = task.inner_exclusive_access().get_syscall_times();
syscall_times
}

pub fn incr_syscall_times(syscall_id: usize) {
let task = current_task().unwrap();
task.inner_exclusive_access().set_syscall_times(syscall_id);
}

pub fn set_mmap(start_va: VirtAddr, end_va: VirtAddr, perm: MapPermission) -> bool {
let task = current_task().unwrap();
let memory_set = &mut task.inner_exclusive_access().memory_set;
memory_set.set_mmap(start_va, end_va, perm)
}

pub fn set_munmap(start_va: VirtAddr, end_va: VirtAddr) -> bool {
let task = current_task().unwrap();
let memory_set = &mut task.inner_exclusive_access().memory_set;
memory_set.set_munmap(start_va, end_va)
}

pub fn set_priority(prio: isize) {
let task = current_task().unwrap();
task.inner_exclusive_access().priority = prio as u8;
}

/// Get current task through take, leaving a None in its place
pub fn take_current_task() -> Option<Arc<TaskControlBlock>> {
PROCESSOR.exclusive_access().take_current()
}

/// Get a copy of the current task
pub fn current_task() -> Option<Arc<TaskControlBlock>> {
PROCESSOR.exclusive_access().current()
}

/// Get token of the address space of current task
pub fn current_user_token() -> usize {
let task = current_task().unwrap();
let token = task.inner_exclusive_access().get_user_token();
token
}

/// Get the mutable reference to trap context of current task
pub fn current_trap_cx() -> &'static mut TrapContext {
current_task()
.unwrap()
.inner_exclusive_access()
.get_trap_cx()
}

/// Return to idle control flow for new scheduling
pub fn schedule(switched_task_cx_ptr: *mut TaskContext) {
let mut processor = PROCESSOR.exclusive_access();
let idle_task_cx_ptr = processor.get_idle_task_cx_ptr();
drop(processor);
unsafe {
__switch(switched_task_cx_ptr, idle_task_cx_ptr);
}
}
34 changes: 34 additions & 0 deletions os5/src/task/switch.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.altmacro
.macro SAVE_SN n
sd s\n, (\n+2)*8(a0)
.endm
.macro LOAD_SN n
ld s\n, (\n+2)*8(a1)
.endm
.section .text
.globl __switch
__switch:
# __switch(
# current_task_cx_ptr: *mut TaskContext,
# next_task_cx_ptr: *const TaskContext
# )
# save kernel stack of current task
sd sp, 8(a0)
# save ra & s0~s11 of current execution
sd ra, 0(a0)
.set n, 0
.rept 12
SAVE_SN %n
.set n, n + 1
.endr
# restore ra & s0~s11 of next execution
ld ra, 0(a1)
.set n, 0
.rept 12
LOAD_SN %n
.set n, n + 1
.endr
# restore kernel stack of next task
ld sp, 8(a1)
ret

16 changes: 16 additions & 0 deletions os5/src/task/switch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! 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" {
/// 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);
}
288 changes: 288 additions & 0 deletions os5/src/task/task.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
//! Types related to task management & Functions for completely changing TCB
use super::TaskContext;
use super::{pid_alloc, KernelStack, PidHandle};
use crate::config::{TRAP_CONTEXT, MAX_SYSCALL_NUM};
use crate::mm::{MemorySet, PhysPageNum, VirtAddr, KERNEL_SPACE};
use crate::sync::UPSafeCell;
use crate::trap::{trap_handler, TrapContext};
use alloc::sync::{Arc, Weak};
use alloc::vec::Vec;
use core::cell::RefMut;

/// Task control block structure
///
/// Directly save the contents that will not change during running
pub struct TaskControlBlock {
// immutable
/// Process identifier
pub pid: PidHandle,
/// Kernel stack corresponding to PID
pub kernel_stack: KernelStack,
// mutable
inner: UPSafeCell<TaskControlBlockInner>,
}

/// Structure containing more process content
///
/// Store the contents that will change during operation
/// and are wrapped by UPSafeCell to provide mutual exclusion
pub struct TaskControlBlockInner {
/// The physical page number of the frame where the trap context is placed
pub trap_cx_ppn: PhysPageNum,
/// Application data can only appear in areas
/// where the application address space is lower than base_size
pub base_size: usize,
/// Save task context
pub task_cx: TaskContext,
/// Maintain the execution status of the current process
pub task_status: TaskStatus,
/// Application address space
pub memory_set: MemorySet,
/// Parent process of the current process.
/// Weak will not affect the reference count of the parent
pub parent: Option<Weak<TaskControlBlock>>,
/// A vector containing TCBs of all child processes of the current process
pub children: Vec<Arc<TaskControlBlock>>,
/// It is set when active exit or execution error occurs
pub exit_code: i32,

/// for task info
pub task_syscall_times: [u32; MAX_SYSCALL_NUM],

// for stride scheduling
pub stride: u8,
// pub pass: u32,
pub priority: u8,
}

/// Simple access to its internal fields
impl TaskControlBlockInner {
/*
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 {
self.trap_cx_ppn.get_mut()
}
pub fn get_user_token(&self) -> usize {
self.memory_set.token()
}
pub fn get_status(&self) -> TaskStatus {
self.task_status
}
pub fn is_zombie(&self) -> bool {
self.get_status() == TaskStatus::Zombie
}

pub fn get_syscall_times(&self) -> [u32; MAX_SYSCALL_NUM] {
self.task_syscall_times
}

pub fn set_syscall_times(&mut self, id: usize) {
self.task_syscall_times[id] += 1;
}
}

impl TaskControlBlock {
/// Get the mutex to get the RefMut TaskControlBlockInner
pub fn inner_exclusive_access(&self) -> RefMut<'_, TaskControlBlockInner> {
self.inner.exclusive_access()
}

/// Create a new process
///
/// At present, it is only used for the creation of initproc
pub fn new(elf_data: &[u8]) -> Self {
// memory_set with elf program headers/trampoline/trap context/user stack
let (memory_set, user_sp, entry_point) = MemorySet::from_elf(elf_data);
let trap_cx_ppn = memory_set
.translate(VirtAddr::from(TRAP_CONTEXT).into())
.unwrap()
.ppn();
// alloc a pid and a kernel stack in kernel space
let pid_handle = pid_alloc();
let kernel_stack = KernelStack::new(&pid_handle);
let kernel_stack_top = kernel_stack.get_top();
// push a task context which goes to trap_return to the top of kernel stack
let task_control_block = Self {
pid: pid_handle,
kernel_stack,
inner: unsafe {
UPSafeCell::new(TaskControlBlockInner {
trap_cx_ppn,
base_size: user_sp,
task_cx: TaskContext::goto_trap_return(kernel_stack_top),
task_status: TaskStatus::Ready,
memory_set,
parent: None,
children: Vec::new(),
exit_code: 0,
task_syscall_times: [0; MAX_SYSCALL_NUM],
stride: 0,
priority: 16,
})
},
};
// prepare TrapContext in user space
let trap_cx = task_control_block.inner_exclusive_access().get_trap_cx();
*trap_cx = TrapContext::app_init_context(
entry_point,
user_sp,
KERNEL_SPACE.exclusive_access().token(),
kernel_stack_top,
trap_handler as usize,
);
task_control_block
}
/// Load a new elf to replace the original application address space and start execution
pub fn exec(&self, elf_data: &[u8]) {
// memory_set with elf program headers/trampoline/trap context/user stack
let (memory_set, user_sp, entry_point) = MemorySet::from_elf(elf_data);
let trap_cx_ppn = memory_set
.translate(VirtAddr::from(TRAP_CONTEXT).into())
.unwrap()
.ppn();

// **** access inner exclusively
let mut inner = self.inner_exclusive_access();
// substitute memory_set
inner.memory_set = memory_set;
// update trap_cx ppn
inner.trap_cx_ppn = trap_cx_ppn;
// initialize trap_cx
let trap_cx = inner.get_trap_cx();
*trap_cx = TrapContext::app_init_context(
entry_point,
user_sp,
KERNEL_SPACE.exclusive_access().token(),
self.kernel_stack.get_top(),
trap_handler as usize,
);
// **** release inner automatically
}
/// Fork from parent to child
pub fn fork(self: &Arc<TaskControlBlock>) -> Arc<TaskControlBlock> {
// ---- access parent PCB exclusively
let mut parent_inner = self.inner_exclusive_access();
// copy user space(include trap context)
let memory_set = MemorySet::from_existed_user(&parent_inner.memory_set);
let trap_cx_ppn = memory_set
.translate(VirtAddr::from(TRAP_CONTEXT).into())
.unwrap()
.ppn();
// alloc a pid and a kernel stack in kernel space
let pid_handle = pid_alloc();
let kernel_stack = KernelStack::new(&pid_handle);
let kernel_stack_top = kernel_stack.get_top();
let task_control_block = Arc::new(TaskControlBlock {
pid: pid_handle,
kernel_stack,
inner: unsafe {
UPSafeCell::new(TaskControlBlockInner {
trap_cx_ppn,
base_size: parent_inner.base_size,
task_cx: TaskContext::goto_trap_return(kernel_stack_top),
task_status: TaskStatus::Ready,
memory_set,
parent: Some(Arc::downgrade(self)),
children: Vec::new(),
exit_code: 0,
task_syscall_times: [0; MAX_SYSCALL_NUM],
stride: 0,
priority: 16,
})
},
});
// add child
parent_inner.children.push(task_control_block.clone());
// modify kernel_sp in trap_cx
// **** access children PCB exclusively
let trap_cx = task_control_block.inner_exclusive_access().get_trap_cx();
trap_cx.kernel_sp = kernel_stack_top;
// return
task_control_block
// ---- release parent PCB automatically
// **** release children PCB automatically
}

pub fn spawn(&self, elf_data: &[u8]) -> Arc<TaskControlBlock> {
// this code piece is modified from new functions
// memory_set with elf program headers/trampoline/trap context/user stack
let (memory_set, user_sp, entry_point) = MemorySet::from_elf(elf_data);
let trap_cx_ppn = memory_set
.translate(VirtAddr::from(TRAP_CONTEXT).into())
.unwrap()
.ppn();
// alloc a pid and a kernel stack in kernel space
let pid_handle = pid_alloc();
let kernel_stack = KernelStack::new(&pid_handle);
let kernel_stack_top = kernel_stack.get_top();
// push a task context which goes to trap_return to the top of kernel stack
let task_control_block = Arc::new(TaskControlBlock {
pid: pid_handle,
kernel_stack,
inner: unsafe {
UPSafeCell::new(TaskControlBlockInner {
trap_cx_ppn,
base_size: user_sp,
task_cx: TaskContext::goto_trap_return(kernel_stack_top),
task_status: TaskStatus::Ready,
memory_set,
parent: None,
children: Vec::new(),
exit_code: 0,
task_syscall_times: [0; MAX_SYSCALL_NUM],
stride: 0,
priority: 16,
})
},
});

// add child
self.inner_exclusive_access().children.push(task_control_block.clone());

// prepare TrapContext in user space
let trap_cx = task_control_block.inner_exclusive_access().get_trap_cx();
*trap_cx = TrapContext::app_init_context(
entry_point,
user_sp,
KERNEL_SPACE.exclusive_access().token(),
kernel_stack_top,
trap_handler as usize,
);
task_control_block
}

pub fn getpid(&self) -> usize {
self.pid.0
}
}

#[derive(Copy, Clone, PartialEq)]
/// task status: UnInit, Ready, Running, Exited
pub enum TaskStatus {
UnInit,
Ready,
Running,
Zombie,
}

// #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
// pub struct Stride(u64);

// // Debuging
// impl Debug for Stride {
// fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// f.write_fmt(format_args!("Stride: {}", self.0))
// }
// }

// impl From<u64> for Stride {
// fn from(v: u64) -> Self {
// Self(v)
// }
// }


5 changes: 5 additions & 0 deletions os5/src/timer.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
//! RISC-V timer-related functionality
use crate::config::CLOCK_FREQ;
use crate::sbi::set_timer;
use riscv::register::time;

const TICKS_PER_SEC: usize = 100;
const MICRO_PER_SEC: usize = 1_000_000;

/// read the `mtime` register
pub fn get_time() -> usize {
time::read()
}

/// get current time in microseconds
pub fn get_time_us() -> usize {
time::read() / (CLOCK_FREQ / MICRO_PER_SEC)
}

/// set the next timer interrupt
pub fn set_next_trigger() {
set_timer(get_time() + CLOCK_FREQ / TICKS_PER_SEC);
}
Empty file removed os5/src/trap/README.md
Empty file.
47 changes: 47 additions & 0 deletions os5/src/trap/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Implementation of [`TrapContext`]
use riscv::register::sstatus::{self, Sstatus, SPP};

#[repr(C)]
/// trap context structure containing sstatus, sepc and registers
pub struct TrapContext {
/// General-Purpose Register x0-31
pub x: [usize; 32],
/// sstatus
pub sstatus: Sstatus,
/// sepc
pub sepc: usize,
/// Token of kernel address space
pub kernel_satp: usize,
/// Kernel stack pointer of the current application
pub kernel_sp: usize,
/// Virtual address of trap handler entry point in kernel
pub trap_handler: usize,
}

impl TrapContext {
pub fn set_sp(&mut self, sp: usize) {
self.x[2] = sp;
}
pub fn app_init_context(
entry: usize,
sp: usize,
kernel_satp: usize,
kernel_sp: usize,
trap_handler: usize,
) -> Self {
let mut sstatus = sstatus::read();
// set CPU privilege to User after trapping back
sstatus.set_spp(SPP::User);
let mut cx = Self {
x: [0; 32],
sstatus,
sepc: entry,
kernel_satp,
kernel_sp,
trap_handler,
};
cx.set_sp(sp);
cx
}
}
131 changes: 131 additions & 0 deletions os5/src/trap/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//! 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;

use crate::config::{TRAMPOLINE, TRAP_CONTEXT};
use crate::syscall::syscall;
use crate::task::{
current_trap_cx, current_user_token, exit_current_and_run_next, suspend_current_and_run_next,
};
use crate::timer::set_next_trigger;
use riscv::register::{
mtvec::TrapMode,
scause::{self, Exception, Interrupt, Trap},
sie, stval, stvec,
};

core::arch::global_asm!(include_str!("trap.S"));

pub fn init() {
set_kernel_trap_entry();
}

fn set_kernel_trap_entry() {
unsafe {
stvec::write(trap_from_kernel as usize, TrapMode::Direct);
}
}

fn set_user_trap_entry() {
unsafe {
stvec::write(TRAMPOLINE as usize, TrapMode::Direct);
}
}

pub fn enable_timer_interrupt() {
unsafe {
sie::set_stimer();
}
}

#[no_mangle]
pub fn trap_handler() -> ! {
set_kernel_trap_entry();
let scause = scause::read();
let stval = stval::read();
match scause.cause() {
Trap::Exception(Exception::UserEnvCall) => {
// jump to next instruction anyway
let mut cx = current_trap_cx();
cx.sepc += 4;
// get system call return value
let result = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]);
// cx is changed during sys_exec, so we have to call it again
cx = current_trap_cx();
cx.x[10] = result as usize;
}
Trap::Exception(Exception::StoreFault)
| Trap::Exception(Exception::StorePageFault)
| Trap::Exception(Exception::InstructionFault)
| Trap::Exception(Exception::InstructionPageFault)
| Trap::Exception(Exception::LoadFault)
| Trap::Exception(Exception::LoadPageFault) => {
println!(
"[kernel] {:?} in application, bad addr = {:#x}, bad instruction = {:#x}, core dumped.",
scause.cause(),
stval,
current_trap_cx().sepc,
);
// page fault exit code
exit_current_and_run_next(-2);
}
Trap::Exception(Exception::IllegalInstruction) => {
println!("[kernel] IllegalInstruction in application, core dumped.");
// illegal instruction exit code
exit_current_and_run_next(-3);
}
Trap::Interrupt(Interrupt::SupervisorTimer) => {
set_next_trigger();
suspend_current_and_run_next();
}
_ => {
panic!(
"Unsupported trap {:?}, stval = {:#x}!",
scause.cause(),
stval
);
}
}
trap_return();
}

#[no_mangle]
pub fn trap_return() -> ! {
set_user_trap_entry();
let trap_cx_ptr = TRAP_CONTEXT;
let user_satp = current_user_token();
extern "C" {
fn __alltraps();
fn __restore();
}
let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE;
unsafe {
core::arch::asm!(
"fence.i",
"jr {restore_va}",
restore_va = in(reg) restore_va,
in("a0") trap_cx_ptr,
in("a1") user_satp,
options(noreturn)
);
}
}

#[no_mangle]
pub fn trap_from_kernel() -> ! {
panic!("a trap {:?} from kernel!", scause::read().cause());
}

pub use context::TrapContext;
69 changes: 69 additions & 0 deletions os5/src/trap/trap.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
.altmacro
.macro SAVE_GP n
sd x\n, \n*8(sp)
.endm
.macro LOAD_GP n
ld x\n, \n*8(sp)
.endm
.section .text.trampoline
.globl __alltraps
.globl __restore
.align 2
__alltraps:
csrrw sp, sscratch, sp
# now sp->*TrapContext in user space, sscratch->user stack
# save other general purpose registers
sd x1, 1*8(sp)
# skip sp(x2), we will save it later
sd x3, 3*8(sp)
# skip tp(x4), application does not use it
# save x5~x31
.set n, 5
.rept 27
SAVE_GP %n
.set n, n+1
.endr
# we can use t0/t1/t2 freely, because they have been saved in TrapContext
csrr t0, sstatus
csrr t1, sepc
sd t0, 32*8(sp)
sd t1, 33*8(sp)
# read user stack from sscratch and save it in TrapContext
csrr t2, sscratch
sd t2, 2*8(sp)
# load kernel_satp into t0
ld t0, 34*8(sp)
# load trap_handler into t1
ld t1, 36*8(sp)
# move to kernel_sp
ld sp, 35*8(sp)
# switch to kernel space
csrw satp, t0
sfence.vma
# jump to trap_handler
jr t1

__restore:
# a0: *TrapContext in user space(Constant); a1: user space token
# switch to user space
csrw satp, a1
sfence.vma
csrw sscratch, a0
mv sp, a0
# now sp points to TrapContext in user space, start restoring based on it
# restore sstatus/sepc
ld t0, 32*8(sp)
ld t1, 33*8(sp)
csrw sstatus, t0
csrw sepc, t1
# restore general purpose registers except x0/sp/tp
ld x1, 1*8(sp)
ld x3, 3*8(sp)
.set n, 5
.rept 27
LOAD_GP %n
.set n, n+1
.endr
# back to user stack
ld sp, 2*8(sp)
sret