mirror of
https://github.com/LearningOS/rust-based-os-comp2022.git
synced 2026-05-05 11:20:06 +08:00
add os[1-8]-ref for os refereces, add guide, add README
This commit is contained in:
7
os4-ref/.cargo/config
Normal file
7
os4-ref/.cargo/config
Normal file
@@ -0,0 +1,7 @@
|
||||
[build]
|
||||
target = "riscv64gc-unknown-none-elf"
|
||||
|
||||
[target.riscv64gc-unknown-none-elf]
|
||||
rustflags = [
|
||||
"-Clink-arg=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes"
|
||||
]
|
||||
17
os4-ref/Cargo.toml
Normal file
17
os4-ref/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "os"
|
||||
version = "0.1.0"
|
||||
authors = ["Yifan Wu <shinbokuow@163.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.2.1"
|
||||
buddy_system_allocator = "0.6"
|
||||
lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
|
||||
log = "0.4"
|
||||
riscv = { git = "https://gitee.com/rcore-os/riscv", features = ["inline-asm"] }
|
||||
spin = "0.9"
|
||||
lock_api = "=0.4.6"
|
||||
xmas-elf = "0.7.0"
|
||||
59
os4-ref/Makefile
Normal file
59
os4-ref/Makefile
Normal file
@@ -0,0 +1,59 @@
|
||||
# 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 ?= $(shell git rev-parse --abbrev-ref HEAD | grep -o 'ch[0-9]' | grep -o '[0-9]')
|
||||
TEST ?= $(CHAPTER)
|
||||
BASE ?= 1
|
||||
|
||||
build: env $(KERNEL_BIN)
|
||||
|
||||
$(KERNEL_BIN): kernel
|
||||
@$(OBJCOPY) $(KERNEL_ELF) --strip-all -O binary $@
|
||||
@$(OBJDUMP) $(KERNEL_ELF) -S > $(KERNEL_ELF).asm
|
||||
|
||||
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
|
||||
|
||||
.PHONY: build env kernel clean run-inner
|
||||
60
os4-ref/build.rs
Normal file
60
os4-ref/build.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
//! Building applications linker
|
||||
|
||||
use std::fs::{read_dir, File};
|
||||
use std::io::{Result, Write};
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=../user/src/");
|
||||
println!("cargo:rerun-if-changed={}", TARGET_PATH);
|
||||
insert_app_data().unwrap();
|
||||
}
|
||||
|
||||
static TARGET_PATH: &str = "../user/build/elf/";
|
||||
|
||||
/// get app data and build linker
|
||||
fn insert_app_data() -> Result<()> {
|
||||
let mut f = File::create("src/link_app.S").unwrap();
|
||||
let mut apps: Vec<_> = read_dir("../user/build/elf/")
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|dir_entry| {
|
||||
let mut name_with_ext = dir_entry.unwrap().file_name().into_string().unwrap();
|
||||
name_with_ext.drain(name_with_ext.find('.').unwrap()..name_with_ext.len());
|
||||
name_with_ext
|
||||
})
|
||||
.collect();
|
||||
apps.sort();
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
r#"
|
||||
.align 3
|
||||
.section .data
|
||||
.global _num_app
|
||||
_num_app:
|
||||
.quad {}"#,
|
||||
apps.len()
|
||||
)?;
|
||||
|
||||
for i in 0..apps.len() {
|
||||
writeln!(f, r#" .quad app_{}_start"#, i)?;
|
||||
}
|
||||
writeln!(f, r#" .quad app_{}_end"#, apps.len() - 1)?;
|
||||
|
||||
for (idx, app) in apps.iter().enumerate() {
|
||||
println!("app_{}: {}", idx, app);
|
||||
writeln!(
|
||||
f,
|
||||
r#"
|
||||
.section .data
|
||||
.global app_{0}_start
|
||||
.global app_{0}_end
|
||||
.align 3
|
||||
app_{0}_start:
|
||||
.incbin "{2}{1}.elf"
|
||||
app_{0}_end:"#,
|
||||
idx, app, TARGET_PATH
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
20
os4-ref/src/config.rs
Normal file
20
os4-ref/src/config.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
//! Constants used in rCore
|
||||
|
||||
pub const USER_STACK_SIZE: usize = 4096 * 2;
|
||||
pub const KERNEL_STACK_SIZE: usize = 4096 * 2;
|
||||
pub const KERNEL_HEAP_SIZE: usize = 0x30_0000;
|
||||
pub const MEMORY_END: usize = 0x80800000;
|
||||
pub const PAGE_SIZE: usize = 0x1000;
|
||||
pub const PAGE_SIZE_BITS: usize = 0xc;
|
||||
pub const MAX_SYSCALL_NUM: usize = 500;
|
||||
|
||||
pub const TRAMPOLINE: usize = usize::MAX - PAGE_SIZE + 1;
|
||||
pub const TRAP_CONTEXT: usize = TRAMPOLINE - PAGE_SIZE;
|
||||
/// 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)
|
||||
}
|
||||
|
||||
pub const CLOCK_FREQ: usize = 12500000;
|
||||
35
os4-ref/src/console.rs
Normal file
35
os4-ref/src/console.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
//! SBI console driver, for text output
|
||||
|
||||
use crate::sbi::console_putchar;
|
||||
use core::fmt::{self, Write};
|
||||
|
||||
struct Stdout;
|
||||
|
||||
impl Write for Stdout {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
for c in s.chars() {
|
||||
console_putchar(c as usize);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print(args: fmt::Arguments) {
|
||||
Stdout.write_fmt(args).unwrap();
|
||||
}
|
||||
|
||||
#[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)+)?));
|
||||
}
|
||||
}
|
||||
12
os4-ref/src/entry.asm
Normal file
12
os4-ref/src/entry.asm
Normal file
@@ -0,0 +1,12 @@
|
||||
.section .text.entry
|
||||
.globl _start
|
||||
_start:
|
||||
la sp, boot_stack_top
|
||||
call rust_main
|
||||
|
||||
.section .bss.stack
|
||||
.globl boot_stack
|
||||
boot_stack:
|
||||
.space 4096 * 16
|
||||
.globl boot_stack_top
|
||||
boot_stack_top:
|
||||
26
os4-ref/src/heap_alloc.rs
Normal file
26
os4-ref/src/heap_alloc.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
//! 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();
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
||||
#[alloc_error_handler]
|
||||
/// panic when heap allocation error occurs
|
||||
pub fn handle_alloc_error(layout: core::alloc::Layout) -> ! {
|
||||
panic!("Heap allocation error, layout = {:?}", layout);
|
||||
}
|
||||
20
os4-ref/src/lang_items.rs
Normal file
20
os4-ref/src/lang_items.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
//! The panic handler
|
||||
|
||||
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 {}:{} {}",
|
||||
location.file(),
|
||||
location.line(),
|
||||
info.message().unwrap()
|
||||
);
|
||||
} else {
|
||||
println!("[kernel] Panicked: {}", info.message().unwrap());
|
||||
}
|
||||
shutdown()
|
||||
}
|
||||
53
os4-ref/src/linker.ld
Normal file
53
os4-ref/src/linker.ld
Normal file
@@ -0,0 +1,53 @@
|
||||
OUTPUT_ARCH(riscv)
|
||||
ENTRY(_start)
|
||||
BASE_ADDRESS = 0x80200000;
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = BASE_ADDRESS;
|
||||
skernel = .;
|
||||
|
||||
stext = .;
|
||||
.text : {
|
||||
*(.text.entry)
|
||||
. = ALIGN(4K);
|
||||
strampoline = .;
|
||||
*(.text.trampoline);
|
||||
. = ALIGN(4K);
|
||||
*(.text .text.*)
|
||||
}
|
||||
|
||||
. = ALIGN(4K);
|
||||
etext = .;
|
||||
srodata = .;
|
||||
.rodata : {
|
||||
*(.rodata .rodata.*)
|
||||
*(.srodata .srodata.*)
|
||||
}
|
||||
|
||||
. = ALIGN(4K);
|
||||
erodata = .;
|
||||
sdata = .;
|
||||
.data : {
|
||||
*(.data .data.*)
|
||||
*(.sdata .sdata.*)
|
||||
}
|
||||
|
||||
. = ALIGN(4K);
|
||||
edata = .;
|
||||
sbss_with_stack = .;
|
||||
.bss : {
|
||||
*(.bss.stack)
|
||||
sbss = .;
|
||||
*(.bss .bss.*)
|
||||
*(.sbss .sbss.*)
|
||||
}
|
||||
|
||||
. = ALIGN(4K);
|
||||
ebss = .;
|
||||
ekernel = .;
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.eh_frame)
|
||||
}
|
||||
}
|
||||
26
os4-ref/src/loader.rs
Normal file
26
os4-ref/src/loader.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
//! Loading user applications into memory
|
||||
|
||||
/// 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();
|
||||
}
|
||||
let num_app_ptr = _num_app as usize as *const usize;
|
||||
let num_app = get_num_app();
|
||||
let app_start = unsafe { core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1) };
|
||||
assert!(app_id < num_app);
|
||||
unsafe {
|
||||
core::slice::from_raw_parts(
|
||||
app_start[app_id] as *const u8,
|
||||
app_start[app_id + 1] - app_start[app_id],
|
||||
)
|
||||
}
|
||||
}
|
||||
45
os4-ref/src/logging.rs
Normal file
45
os4-ref/src/logging.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
//! Global logger
|
||||
|
||||
use log::{self, Level, LevelFilter, Log, Metadata, Record};
|
||||
|
||||
/// a simple logger
|
||||
struct SimpleLogger;
|
||||
|
||||
impl Log for SimpleLogger {
|
||||
fn enabled(&self, _metadata: &Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
fn log(&self, record: &Record) {
|
||||
if !self.enabled(record.metadata()) {
|
||||
return;
|
||||
}
|
||||
let color = match record.level() {
|
||||
Level::Error => 31, // Red
|
||||
Level::Warn => 93, // BrightYellow
|
||||
Level::Info => 34, // Blue
|
||||
Level::Debug => 32, // Green
|
||||
Level::Trace => 90, // BrightBlack
|
||||
};
|
||||
println!(
|
||||
"\u{1B}[{}m[{:>5}] {}\u{1B}[0m",
|
||||
color,
|
||||
record.level(),
|
||||
record.args(),
|
||||
);
|
||||
}
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
/// initiate logger
|
||||
pub fn init() {
|
||||
static LOGGER: SimpleLogger = SimpleLogger;
|
||||
log::set_logger(&LOGGER).unwrap();
|
||||
log::set_max_level(match option_env!("LOG") {
|
||||
Some("ERROR") => LevelFilter::Error,
|
||||
Some("WARN") => LevelFilter::Warn,
|
||||
Some("INFO") => LevelFilter::Info,
|
||||
Some("DEBUG") => LevelFilter::Debug,
|
||||
Some("TRACE") => LevelFilter::Trace,
|
||||
_ => LevelFilter::Off,
|
||||
});
|
||||
}
|
||||
74
os4-ref/src/main.rs
Normal file
74
os4-ref/src/main.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
//! 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)]
|
||||
#![feature(alloc_error_handler)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
#[macro_use]
|
||||
mod console;
|
||||
mod config;
|
||||
mod lang_items;
|
||||
mod loader;
|
||||
mod logging;
|
||||
mod mm;
|
||||
mod sbi;
|
||||
mod sync;
|
||||
pub mod syscall;
|
||||
pub mod task;
|
||||
mod timer;
|
||||
pub 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();
|
||||
fn ebss();
|
||||
}
|
||||
unsafe {
|
||||
core::slice::from_raw_parts_mut(sbss as usize as *mut u8, ebss as usize - sbss as usize)
|
||||
.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
/// the rust entry-point of os
|
||||
pub fn rust_main() -> ! {
|
||||
clear_bss();
|
||||
logging::init();
|
||||
println!("[kernel] Hello, world!");
|
||||
mm::init();
|
||||
println!("[kernel] back to world!");
|
||||
mm::remap_test();
|
||||
trap::init();
|
||||
//trap::enable_interrupt();
|
||||
trap::enable_timer_interrupt();
|
||||
timer::set_next_trigger();
|
||||
task::run_first_task();
|
||||
panic!("Unreachable in rust_main!");
|
||||
}
|
||||
245
os4-ref/src/mm/address.rs
Normal file
245
os4-ref/src/mm/address.rs
Normal file
@@ -0,0 +1,245 @@
|
||||
//! 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};
|
||||
|
||||
/// physical address
|
||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct PhysAddr(pub usize);
|
||||
|
||||
/// virtual address
|
||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct VirtAddr(pub usize);
|
||||
|
||||
/// physical page number
|
||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct PhysPageNum(pub usize);
|
||||
|
||||
/// virtual page number
|
||||
#[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 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();
|
||||
unsafe { (pa.0 as *mut T).as_mut().unwrap() }
|
||||
}
|
||||
}
|
||||
|
||||
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
os4-ref/src/mm/frame_allocator.rs
Normal file
136
os4-ref/src/mm/frame_allocator.rs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
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()) };
|
||||
}
|
||||
|
||||
/// initiate the frame allocator using `ekernel` and `MEMORY_END`
|
||||
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(),
|
||||
);
|
||||
}
|
||||
|
||||
/// allocate a frame
|
||||
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
os4-ref/src/mm/heap_allocator.rs
Normal file
51
os4-ref/src/mm/heap_allocator.rs
Normal file
@@ -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!");
|
||||
}
|
||||
346
os4-ref/src/mm/memory_set.rs
Normal file
346
os4-ref/src/mm/memory_set.rs
Normal file
@@ -0,0 +1,346 @@
|
||||
//! Implementation of [`MapArea`] and [`MemorySet`].
|
||||
|
||||
use super::{frame_alloc, FrameTracker};
|
||||
use super::{PTEFlags, PageTable, PageTableEntry};
|
||||
use super::{PhysAddr, PhysPageNum, VirtAddr, VirtPageNum};
|
||||
use super::{StepByOne, VPNRange};
|
||||
use crate::config::{MEMORY_END, PAGE_SIZE, TRAMPOLINE, TRAP_CONTEXT, USER_STACK_SIZE};
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use lazy_static::*;
|
||||
use riscv::register::satp;
|
||||
use spin::Mutex;
|
||||
|
||||
extern "C" {
|
||||
fn stext();
|
||||
fn etext();
|
||||
fn srodata();
|
||||
fn erodata();
|
||||
fn sdata();
|
||||
fn edata();
|
||||
fn sbss_with_stack();
|
||||
fn ebss();
|
||||
fn ekernel();
|
||||
fn strampoline();
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// a memory set instance through lazy_static! managing kernel space
|
||||
pub static ref KERNEL_SPACE: Arc<Mutex<MemorySet>> =
|
||||
Arc::new(Mutex::new(MemorySet::new_kernel()));
|
||||
}
|
||||
|
||||
/// memory set structure, controls virtual-memory space
|
||||
pub struct MemorySet {
|
||||
page_table: PageTable,
|
||||
areas: Vec<MapArea>,
|
||||
}
|
||||
|
||||
impl MemorySet {
|
||||
pub fn new_bare() -> Self {
|
||||
Self {
|
||||
page_table: PageTable::new(),
|
||||
areas: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn token(&self) -> usize {
|
||||
self.page_table.token()
|
||||
}
|
||||
/// Assume that no conflicts.
|
||||
pub fn insert_framed_area(
|
||||
&mut self,
|
||||
start_va: VirtAddr,
|
||||
end_va: VirtAddr,
|
||||
permission: MapPermission,
|
||||
) {
|
||||
self.push(
|
||||
MapArea::new(start_va, end_va, MapType::Framed, permission),
|
||||
None,
|
||||
);
|
||||
}
|
||||
fn push(&mut self, mut map_area: MapArea, data: Option<&[u8]>) {
|
||||
map_area.map(&mut self.page_table);
|
||||
if let Some(data) = data {
|
||||
map_area.copy_data(&mut self.page_table, data);
|
||||
}
|
||||
self.areas.push(map_area);
|
||||
}
|
||||
/// Mention that trampoline is not collected by areas.
|
||||
fn map_trampoline(&mut self) {
|
||||
self.page_table.map(
|
||||
VirtAddr::from(TRAMPOLINE).into(),
|
||||
PhysAddr::from(strampoline as usize).into(),
|
||||
PTEFlags::R | PTEFlags::X,
|
||||
);
|
||||
}
|
||||
/// Without kernel stacks.
|
||||
pub fn new_kernel() -> Self {
|
||||
let mut memory_set = Self::new_bare();
|
||||
// map trampoline
|
||||
memory_set.map_trampoline();
|
||||
// map kernel sections
|
||||
info!(".text [{:#x}, {:#x})", stext as usize, etext as usize);
|
||||
info!(".rodata [{:#x}, {:#x})", srodata as usize, erodata as usize);
|
||||
info!(".data [{:#x}, {:#x})", sdata as usize, edata as usize);
|
||||
info!(
|
||||
".bss [{:#x}, {:#x})",
|
||||
sbss_with_stack as usize, ebss as usize
|
||||
);
|
||||
info!("mapping .text section");
|
||||
memory_set.push(
|
||||
MapArea::new(
|
||||
(stext as usize).into(),
|
||||
(etext as usize).into(),
|
||||
MapType::Identical,
|
||||
MapPermission::R | MapPermission::X,
|
||||
),
|
||||
None,
|
||||
);
|
||||
info!("mapping .rodata section");
|
||||
memory_set.push(
|
||||
MapArea::new(
|
||||
(srodata as usize).into(),
|
||||
(erodata as usize).into(),
|
||||
MapType::Identical,
|
||||
MapPermission::R,
|
||||
),
|
||||
None,
|
||||
);
|
||||
info!("mapping .data section");
|
||||
memory_set.push(
|
||||
MapArea::new(
|
||||
(sdata as usize).into(),
|
||||
(edata as usize).into(),
|
||||
MapType::Identical,
|
||||
MapPermission::R | MapPermission::W,
|
||||
),
|
||||
None,
|
||||
);
|
||||
info!("mapping .bss section");
|
||||
memory_set.push(
|
||||
MapArea::new(
|
||||
(sbss_with_stack as usize).into(),
|
||||
(ebss as usize).into(),
|
||||
MapType::Identical,
|
||||
MapPermission::R | MapPermission::W,
|
||||
),
|
||||
None,
|
||||
);
|
||||
info!("mapping physical memory");
|
||||
memory_set.push(
|
||||
MapArea::new(
|
||||
(ekernel as usize).into(),
|
||||
MEMORY_END.into(),
|
||||
MapType::Identical,
|
||||
MapPermission::R | MapPermission::W,
|
||||
),
|
||||
None,
|
||||
);
|
||||
memory_set
|
||||
}
|
||||
/// Include sections in elf and trampoline and TrapContext and user stack,
|
||||
/// also returns user_sp and entry point.
|
||||
pub fn from_elf(elf_data: &[u8]) -> (Self, usize, usize) {
|
||||
let mut memory_set = Self::new_bare();
|
||||
// map trampoline
|
||||
memory_set.map_trampoline();
|
||||
// map program headers of elf, with U flag
|
||||
let elf = xmas_elf::ElfFile::new(elf_data).unwrap();
|
||||
let elf_header = elf.header;
|
||||
let magic = elf_header.pt1.magic;
|
||||
assert_eq!(magic, [0x7f, 0x45, 0x4c, 0x46], "invalid elf!");
|
||||
let ph_count = elf_header.pt2.ph_count();
|
||||
let mut max_end_vpn = VirtPageNum(0);
|
||||
for i in 0..ph_count {
|
||||
let ph = elf.program_header(i).unwrap();
|
||||
if ph.get_type().unwrap() == xmas_elf::program::Type::Load {
|
||||
let start_va: VirtAddr = (ph.virtual_addr() as usize).into();
|
||||
let end_va: VirtAddr = ((ph.virtual_addr() + ph.mem_size()) as usize).into();
|
||||
let mut map_perm = MapPermission::U;
|
||||
let ph_flags = ph.flags();
|
||||
if ph_flags.is_read() {
|
||||
map_perm |= MapPermission::R;
|
||||
}
|
||||
if ph_flags.is_write() {
|
||||
map_perm |= MapPermission::W;
|
||||
}
|
||||
if ph_flags.is_execute() {
|
||||
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();
|
||||
memory_set.push(
|
||||
map_area,
|
||||
Some(&elf.input[ph.offset() as usize..(ph.offset() + ph.file_size()) as usize]),
|
||||
);
|
||||
}
|
||||
}
|
||||
// map user stack with U flags
|
||||
let max_end_va: VirtAddr = max_end_vpn.into();
|
||||
let mut user_stack_bottom: usize = max_end_va.into();
|
||||
// guard page
|
||||
user_stack_bottom += PAGE_SIZE;
|
||||
let user_stack_top = user_stack_bottom + USER_STACK_SIZE;
|
||||
memory_set.push(
|
||||
MapArea::new(
|
||||
user_stack_bottom.into(),
|
||||
user_stack_top.into(),
|
||||
MapType::Framed,
|
||||
MapPermission::R | MapPermission::W | MapPermission::U,
|
||||
),
|
||||
None,
|
||||
);
|
||||
// map TrapContext
|
||||
memory_set.push(
|
||||
MapArea::new(
|
||||
TRAP_CONTEXT.into(),
|
||||
TRAMPOLINE.into(),
|
||||
MapType::Framed,
|
||||
MapPermission::R | MapPermission::W,
|
||||
),
|
||||
None,
|
||||
);
|
||||
(
|
||||
memory_set,
|
||||
user_stack_top,
|
||||
elf.header.pt2.entry_point() as usize,
|
||||
)
|
||||
}
|
||||
pub fn activate(&self) {
|
||||
let satp = self.page_table.token();
|
||||
unsafe {
|
||||
satp::write(satp);
|
||||
core::arch::asm!("sfence.vma");
|
||||
}
|
||||
}
|
||||
pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> {
|
||||
self.page_table.translate(vpn)
|
||||
}
|
||||
}
|
||||
|
||||
/// map area structure, controls a contiguous piece of virtual memory
|
||||
pub struct MapArea {
|
||||
vpn_range: VPNRange,
|
||||
data_frames: BTreeMap<VirtPageNum, FrameTracker>,
|
||||
map_type: MapType,
|
||||
map_perm: MapPermission,
|
||||
}
|
||||
|
||||
impl MapArea {
|
||||
pub fn new(
|
||||
start_va: VirtAddr,
|
||||
end_va: VirtAddr,
|
||||
map_type: MapType,
|
||||
map_perm: MapPermission,
|
||||
) -> Self {
|
||||
let start_vpn: VirtPageNum = start_va.floor();
|
||||
let end_vpn: VirtPageNum = end_va.ceil();
|
||||
Self {
|
||||
vpn_range: VPNRange::new(start_vpn, end_vpn),
|
||||
data_frames: BTreeMap::new(),
|
||||
map_type,
|
||||
map_perm,
|
||||
}
|
||||
}
|
||||
pub fn map_one(&mut self, page_table: &mut PageTable, vpn: VirtPageNum) {
|
||||
let ppn: PhysPageNum;
|
||||
match self.map_type {
|
||||
MapType::Identical => {
|
||||
ppn = PhysPageNum(vpn.0);
|
||||
}
|
||||
MapType::Framed => {
|
||||
let frame = frame_alloc().unwrap();
|
||||
ppn = frame.ppn;
|
||||
self.data_frames.insert(vpn, frame);
|
||||
}
|
||||
}
|
||||
let pte_flags = PTEFlags::from_bits(self.map_perm.bits).unwrap();
|
||||
page_table.map(vpn, ppn, pte_flags);
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn unmap_one(&mut self, page_table: &mut PageTable, vpn: VirtPageNum) {
|
||||
#[allow(clippy::single_match)]
|
||||
match self.map_type {
|
||||
MapType::Framed => {
|
||||
self.data_frames.remove(&vpn);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
page_table.unmap(vpn);
|
||||
}
|
||||
pub fn map(&mut self, page_table: &mut PageTable) {
|
||||
for vpn in self.vpn_range {
|
||||
self.map_one(page_table, vpn);
|
||||
}
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn unmap(&mut self, page_table: &mut PageTable) {
|
||||
for vpn in self.vpn_range {
|
||||
self.unmap_one(page_table, vpn);
|
||||
}
|
||||
}
|
||||
/// data: start-aligned but maybe with shorter length
|
||||
/// assume that all frames were cleared before
|
||||
pub fn copy_data(&mut self, page_table: &mut PageTable, data: &[u8]) {
|
||||
assert_eq!(self.map_type, MapType::Framed);
|
||||
let mut start: usize = 0;
|
||||
let mut current_vpn = self.vpn_range.get_start();
|
||||
let len = data.len();
|
||||
loop {
|
||||
let src = &data[start..len.min(start + PAGE_SIZE)];
|
||||
let dst = &mut page_table
|
||||
.translate(current_vpn)
|
||||
.unwrap()
|
||||
.ppn()
|
||||
.get_bytes_array()[..src.len()];
|
||||
dst.copy_from_slice(src);
|
||||
start += PAGE_SIZE;
|
||||
if start >= len {
|
||||
break;
|
||||
}
|
||||
current_vpn.step();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
/// map type for memory set: identical or framed
|
||||
pub enum MapType {
|
||||
Identical,
|
||||
Framed,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// map permission corresponding to that in pte: `R W X U`
|
||||
pub struct MapPermission: u8 {
|
||||
const R = 1 << 1;
|
||||
const W = 1 << 2;
|
||||
const X = 1 << 3;
|
||||
const U = 1 << 4;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn remap_test() {
|
||||
let mut kernel_space = KERNEL_SPACE.lock();
|
||||
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_data: VirtAddr = ((sdata as usize + edata as usize) / 2).into();
|
||||
assert!(!kernel_space
|
||||
.page_table
|
||||
.translate(mid_text.floor())
|
||||
.unwrap()
|
||||
.writable());
|
||||
assert!(!kernel_space
|
||||
.page_table
|
||||
.translate(mid_rodata.floor())
|
||||
.unwrap()
|
||||
.writable());
|
||||
assert!(!kernel_space
|
||||
.page_table
|
||||
.translate(mid_data.floor())
|
||||
.unwrap()
|
||||
.executable());
|
||||
info!("remap_test passed!");
|
||||
}
|
||||
29
os4-ref/src/mm/mod.rs
Normal file
29
os4-ref/src/mm/mod.rs
Normal file
@@ -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};
|
||||
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::{translated_byte_buffer, PageTableEntry};
|
||||
use page_table::{PTEFlags, PageTable};
|
||||
|
||||
/// initiate heap allocator, frame allocator and kernel space
|
||||
pub fn init() {
|
||||
heap_allocator::init_heap();
|
||||
frame_allocator::init_frame_allocator();
|
||||
KERNEL_SPACE.lock().activate();
|
||||
}
|
||||
157
os4-ref/src/mm/page_table.rs
Normal file
157
os4-ref/src/mm/page_table.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
//! Implementation of [`PageTableEntry`] and [`PageTable`].
|
||||
|
||||
use super::{frame_alloc, FrameTracker, PhysPageNum, StepByOne, VirtAddr, VirtPageNum};
|
||||
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 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
|
||||
}
|
||||
45
os4-ref/src/sbi.rs
Normal file
45
os4-ref/src/sbi.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
//! SBI call wrappers
|
||||
|
||||
#![allow(unused)]
|
||||
|
||||
const SBI_SET_TIMER: usize = 0;
|
||||
const SBI_CONSOLE_PUTCHAR: usize = 1;
|
||||
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!(
|
||||
"ecall",
|
||||
inlateout("x10") arg0 => ret,
|
||||
in("x11") arg1,
|
||||
in("x12") arg2,
|
||||
in("x17") which,
|
||||
);
|
||||
}
|
||||
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!");
|
||||
}
|
||||
5
os4-ref/src/sync/mod.rs
Normal file
5
os4-ref/src/sync/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
//! Synchronization and interior mutability primitives
|
||||
|
||||
mod up;
|
||||
|
||||
pub use up::UPSafeCell;
|
||||
31
os4-ref/src/sync/up.rs
Normal file
31
os4-ref/src/sync/up.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
/// Panic if the data has been borrowed.
|
||||
pub fn exclusive_access(&self) -> RefMut<'_, T> {
|
||||
self.inner.borrow_mut()
|
||||
}
|
||||
}
|
||||
21
os4-ref/src/syscall/fs.rs
Normal file
21
os4-ref/src/syscall/fs.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
//! File and filesystem-related syscalls
|
||||
|
||||
use crate::mm::translated_byte_buffer;
|
||||
use crate::task::current_user_token;
|
||||
|
||||
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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
42
os4-ref/src/syscall/mod.rs
Normal file
42
os4-ref/src/syscall/mod.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
//! 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_EXIT: usize = 93;
|
||||
const SYSCALL_YIELD: usize = 124;
|
||||
const SYSCALL_GET_TIME: usize = 169;
|
||||
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::*;
|
||||
|
||||
/// handle syscall exception with `syscall_id` and other arguments
|
||||
pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
|
||||
// LAB1: You may need to update syscall info here.
|
||||
match syscall_id {
|
||||
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_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),
|
||||
_ => panic!("Unsupported syscall_id: {}", syscall_id),
|
||||
}
|
||||
}
|
||||
62
os4-ref/src/syscall/process.rs
Normal file
62
os4-ref/src/syscall/process.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
//! Process management syscalls
|
||||
|
||||
use crate::config::MAX_SYSCALL_NUM;
|
||||
use crate::task::{exit_current_and_run_next, suspend_current_and_run_next, TaskStatus};
|
||||
use crate::timer::get_time_us;
|
||||
|
||||
#[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) -> ! {
|
||||
info!("[kernel] Application exited with code {}", exit_code);
|
||||
exit_current_and_run_next();
|
||||
panic!("Unreachable in sys_exit!");
|
||||
}
|
||||
|
||||
/// current task gives up resources for other tasks
|
||||
pub fn sys_yield() -> isize {
|
||||
suspend_current_and_run_next();
|
||||
0
|
||||
}
|
||||
|
||||
// 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,
|
||||
// };
|
||||
// }
|
||||
0
|
||||
}
|
||||
|
||||
// CLUE: 从 ch4 开始不再对调度算法进行测试~
|
||||
pub fn sys_set_priority(_prio: isize) -> isize {
|
||||
-1
|
||||
}
|
||||
|
||||
// YOUR JOB: 扩展内核以实现 sys_mmap 和 sys_munmap
|
||||
pub fn sys_mmap(_start: usize, _len: usize, _port: usize) -> isize {
|
||||
-1
|
||||
}
|
||||
|
||||
pub fn sys_munmap(_start: usize, _len: usize) -> isize {
|
||||
-1
|
||||
}
|
||||
|
||||
// YOUR JOB: 引入虚地址后重写 sys_task_info
|
||||
pub fn sys_task_info(ti: *mut TaskInfo) -> isize {
|
||||
-1
|
||||
}
|
||||
28
os4-ref/src/task/context.rs
Normal file
28
os4-ref/src/task/context.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
//! Implementation of [`TaskContext`]
|
||||
use crate::trap::trap_return;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
/// task context structure containing some registers
|
||||
pub struct TaskContext {
|
||||
ra: usize,
|
||||
sp: usize,
|
||||
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],
|
||||
}
|
||||
}
|
||||
}
|
||||
193
os4-ref/src/task/mod.rs
Normal file
193
os4-ref/src/task/mod.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
//! 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`]. Control flow around this function
|
||||
//! might not be what you expect.
|
||||
|
||||
mod context;
|
||||
mod switch;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod task;
|
||||
|
||||
use crate::loader::{get_app_data, get_num_app};
|
||||
use crate::sync::UPSafeCell;
|
||||
use crate::trap::TrapContext;
|
||||
use alloc::vec::Vec;
|
||||
use lazy_static::*;
|
||||
pub use switch::__switch;
|
||||
pub use task::{TaskControlBlock, TaskStatus};
|
||||
|
||||
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 {
|
||||
/// total number of tasks
|
||||
num_app: usize,
|
||||
/// use inner value to get mutable access
|
||||
inner: UPSafeCell<TaskManagerInner>,
|
||||
}
|
||||
|
||||
/// The task manager inner in 'UPSafeCell'
|
||||
struct TaskManagerInner {
|
||||
/// task list
|
||||
tasks: Vec<TaskControlBlock>,
|
||||
/// id of current `Running` task
|
||||
current_task: usize,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// a `TaskManager` instance through lazy_static!
|
||||
pub static ref TASK_MANAGER: TaskManager = {
|
||||
info!("init TASK_MANAGER");
|
||||
let num_app = get_num_app();
|
||||
info!("num_app = {}", num_app);
|
||||
let mut tasks: Vec<TaskControlBlock> = Vec::new();
|
||||
for i in 0..num_app {
|
||||
tasks.push(TaskControlBlock::new(get_app_data(i), i));
|
||||
}
|
||||
TaskManager {
|
||||
num_app,
|
||||
inner: unsafe {
|
||||
UPSafeCell::new(TaskManagerInner {
|
||||
tasks,
|
||||
current_task: 0,
|
||||
})
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl TaskManager {
|
||||
/// Run the first task in task list.
|
||||
///
|
||||
/// Generally, the first task in task list is an idle task (we call it zero process later).
|
||||
/// 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 {
|
||||
__switch(&mut _unused as *mut _, next_task_cx_ptr);
|
||||
}
|
||||
panic!("unreachable in run_first_task!");
|
||||
}
|
||||
|
||||
/// Change the status of current `Running` task into `Ready`.
|
||||
fn mark_current_suspended(&self) {
|
||||
let mut inner = self.inner.exclusive_access();
|
||||
let current = inner.current_task;
|
||||
inner.tasks[current].task_status = TaskStatus::Ready;
|
||||
}
|
||||
|
||||
/// Change the status of current `Running` task into `Exited`.
|
||||
fn mark_current_exited(&self) {
|
||||
let mut inner = self.inner.exclusive_access();
|
||||
let current = inner.current_task;
|
||||
inner.tasks[current].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> {
|
||||
let inner = self.inner.exclusive_access();
|
||||
let current = inner.current_task;
|
||||
(current + 1..current + self.num_app + 1)
|
||||
.map(|id| id % self.num_app)
|
||||
.find(|id| inner.tasks[*id].task_status == TaskStatus::Ready)
|
||||
}
|
||||
|
||||
/// Get the current 'Running' task's token.
|
||||
fn get_current_token(&self) -> usize {
|
||||
let inner = self.inner.exclusive_access();
|
||||
inner.tasks[inner.current_task].get_user_token()
|
||||
}
|
||||
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
/// Get the current 'Running' task's trap contexts.
|
||||
fn get_current_trap_cx(&self) -> &mut TrapContext {
|
||||
let inner = self.inner.exclusive_access();
|
||||
inner.tasks[inner.current_task].get_trap_cx()
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
if let Some(next) = self.find_next_task() {
|
||||
let mut inner = self.inner.exclusive_access();
|
||||
let current = inner.current_task;
|
||||
inner.tasks[next].task_status = TaskStatus::Running;
|
||||
inner.current_task = next;
|
||||
let current_task_cx_ptr = &mut inner.tasks[current].task_cx as *mut TaskContext;
|
||||
let next_task_cx_ptr = &inner.tasks[next].task_cx as *const TaskContext;
|
||||
drop(inner);
|
||||
// before this, we should drop local variables that must be dropped manually
|
||||
unsafe {
|
||||
__switch(current_task_cx_ptr, next_task_cx_ptr);
|
||||
}
|
||||
// go back to user mode
|
||||
} else {
|
||||
panic!("All applications completed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the first task in task list.
|
||||
pub fn 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() {
|
||||
TASK_MANAGER.run_next_task();
|
||||
}
|
||||
|
||||
/// Change the status of current `Running` task into `Ready`.
|
||||
fn mark_current_suspended() {
|
||||
TASK_MANAGER.mark_current_suspended();
|
||||
}
|
||||
|
||||
/// Change the status of current `Running` task into `Exited`.
|
||||
fn 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() {
|
||||
mark_current_suspended();
|
||||
run_next_task();
|
||||
}
|
||||
|
||||
/// Exit the current 'Running' task and run the next task in task list.
|
||||
pub fn exit_current_and_run_next() {
|
||||
mark_current_exited();
|
||||
run_next_task();
|
||||
}
|
||||
|
||||
/// Get the current 'Running' task's token.
|
||||
pub fn current_user_token() -> usize {
|
||||
TASK_MANAGER.get_current_token()
|
||||
}
|
||||
|
||||
/// Get the current 'Running' task's trap contexts.
|
||||
pub fn current_trap_cx() -> &'static mut TrapContext {
|
||||
TASK_MANAGER.get_current_trap_cx()
|
||||
}
|
||||
34
os4-ref/src/task/switch.S
Normal file
34
os4-ref/src/task/switch.S
Normal file
@@ -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
os4-ref/src/task/switch.rs
Normal file
16
os4-ref/src/task/switch.rs
Normal file
@@ -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);
|
||||
}
|
||||
64
os4-ref/src/task/task.rs
Normal file
64
os4-ref/src/task/task.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
//! Types related to task management
|
||||
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 task_status: TaskStatus,
|
||||
pub task_cx: TaskContext,
|
||||
pub memory_set: MemorySet,
|
||||
pub trap_cx_ppn: PhysPageNum,
|
||||
pub base_size: usize,
|
||||
}
|
||||
|
||||
impl TaskControlBlock {
|
||||
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 new(elf_data: &[u8], app_id: usize) -> 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();
|
||||
let task_status = TaskStatus::Ready;
|
||||
// map a kernel-stack in kernel space
|
||||
let (kernel_stack_bottom, kernel_stack_top) = kernel_stack_position(app_id);
|
||||
KERNEL_SPACE.lock().insert_framed_area(
|
||||
kernel_stack_bottom.into(),
|
||||
kernel_stack_top.into(),
|
||||
MapPermission::R | MapPermission::W,
|
||||
);
|
||||
let task_control_block = Self {
|
||||
task_status,
|
||||
task_cx: TaskContext::goto_trap_return(kernel_stack_top),
|
||||
memory_set,
|
||||
trap_cx_ppn,
|
||||
base_size: user_sp,
|
||||
};
|
||||
// prepare TrapContext in user space
|
||||
let trap_cx = task_control_block.get_trap_cx();
|
||||
*trap_cx = TrapContext::app_init_context(
|
||||
entry_point,
|
||||
user_sp,
|
||||
KERNEL_SPACE.lock().token(),
|
||||
kernel_stack_top,
|
||||
trap_handler as usize,
|
||||
);
|
||||
task_control_block
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
/// task status: UnInit, Ready, Running, Exited
|
||||
pub enum TaskStatus {
|
||||
Ready,
|
||||
Running,
|
||||
Exited,
|
||||
}
|
||||
23
os4-ref/src/timer.rs
Normal file
23
os4-ref/src/timer.rs
Normal file
@@ -0,0 +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);
|
||||
}
|
||||
40
os4-ref/src/trap/context.rs
Normal file
40
os4-ref/src/trap/context.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
//! Implementation of [`TrapContext`]
|
||||
|
||||
use riscv::register::sstatus::{self, Sstatus, SPP};
|
||||
|
||||
#[repr(C)]
|
||||
/// trap context structure containing sstatus, sepc and registers
|
||||
pub struct TrapContext {
|
||||
pub x: [usize; 32],
|
||||
pub sstatus: Sstatus,
|
||||
pub sepc: usize,
|
||||
pub kernel_satp: usize,
|
||||
pub kernel_sp: usize,
|
||||
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();
|
||||
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
|
||||
}
|
||||
}
|
||||
115
os4-ref/src/trap/mod.rs
Normal file
115
os4-ref/src/trap/mod.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
//! 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 cx = current_trap_cx();
|
||||
let scause = scause::read();
|
||||
let stval = stval::read();
|
||||
match scause.cause() {
|
||||
Trap::Exception(Exception::UserEnvCall) => {
|
||||
cx.sepc += 4;
|
||||
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::StorePageFault)
|
||||
| Trap::Exception(Exception::LoadPageFault) => {
|
||||
error!("[kernel] PageFault in application, bad addr = {:#x}, bad instruction = {:#x}, core dumped.", stval, cx.sepc);
|
||||
exit_current_and_run_next();
|
||||
}
|
||||
Trap::Exception(Exception::IllegalInstruction) => {
|
||||
error!("[kernel] IllegalInstruction in application, core dumped.");
|
||||
exit_current_and_run_next();
|
||||
}
|
||||
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!");
|
||||
}
|
||||
|
||||
pub use context::TrapContext;
|
||||
69
os4-ref/src/trap/trap.S
Normal file
69
os4-ref/src/trap/trap.S
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user