mirror of
https://github.com/rcore-os/rCore-Tutorial-v3.git
synced 2024-11-22 17:36:25 +04:00
commit
40b5370f6b
6
.github/workflows/build-doc.yml
vendored
6
.github/workflows/build-doc.yml
vendored
@ -16,10 +16,10 @@ jobs:
|
|||||||
rustup component add llvm-tools-preview
|
rustup component add llvm-tools-preview
|
||||||
rustup component add rust-src
|
rustup component add rust-src
|
||||||
cd os
|
cd os
|
||||||
cargo doc --no-deps --verbose
|
cargo doc --document-private-items --verbose
|
||||||
- name: Deploy to Github Pages
|
- name: Push to gh-pages
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
publish_dir: ./os/target/riscv64gc-unknown-none-elf/doc
|
publish_dir: ./os/target/riscv64gc-unknown-none-elf/doc
|
||||||
destination_dir: ${{ github.ref_name }}
|
destination_dir: ${{ github.ref_name }}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use super::{get_block_cache, BlockDevice, BLOCK_SZ};
|
use super::{get_block_cache, BlockDevice, BLOCK_SZ};
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
|
/// A bitmap block
|
||||||
type BitmapBlock = [u64; 64];
|
type BitmapBlock = [u64; 64];
|
||||||
|
/// Number of bits in a block
|
||||||
const BLOCK_BITS: usize = BLOCK_SZ * 8;
|
const BLOCK_BITS: usize = BLOCK_SZ * 8;
|
||||||
|
/// A bitmap
|
||||||
pub struct Bitmap {
|
pub struct Bitmap {
|
||||||
start_block_id: usize,
|
start_block_id: usize,
|
||||||
blocks: usize,
|
blocks: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return (block_pos, bits64_pos, inner_pos)
|
/// Decompose bits into (block_pos, bits64_pos, inner_pos)
|
||||||
fn decomposition(mut bit: usize) -> (usize, usize, usize) {
|
fn decomposition(mut bit: usize) -> (usize, usize, usize) {
|
||||||
let block_pos = bit / BLOCK_BITS;
|
let block_pos = bit / BLOCK_BITS;
|
||||||
bit %= BLOCK_BITS;
|
bit %= BLOCK_BITS;
|
||||||
@ -18,13 +18,14 @@ fn decomposition(mut bit: usize) -> (usize, usize, usize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Bitmap {
|
impl Bitmap {
|
||||||
|
/// A new bitmap from start block id and number of blocks
|
||||||
pub fn new(start_block_id: usize, blocks: usize) -> Self {
|
pub fn new(start_block_id: usize, blocks: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
start_block_id,
|
start_block_id,
|
||||||
blocks,
|
blocks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Allocate a new block from a block device
|
||||||
pub fn alloc(&self, block_device: &Arc<dyn BlockDevice>) -> Option<usize> {
|
pub fn alloc(&self, block_device: &Arc<dyn BlockDevice>) -> Option<usize> {
|
||||||
for block_id in 0..self.blocks {
|
for block_id in 0..self.blocks {
|
||||||
let pos = get_block_cache(
|
let pos = get_block_cache(
|
||||||
@ -52,7 +53,7 @@ impl Bitmap {
|
|||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
/// Deallocate a block
|
||||||
pub fn dealloc(&self, block_device: &Arc<dyn BlockDevice>, bit: usize) {
|
pub fn dealloc(&self, block_device: &Arc<dyn BlockDevice>, bit: usize) {
|
||||||
let (block_pos, bits64_pos, inner_pos) = decomposition(bit);
|
let (block_pos, bits64_pos, inner_pos) = decomposition(bit);
|
||||||
get_block_cache(block_pos + self.start_block_id, Arc::clone(block_device))
|
get_block_cache(block_pos + self.start_block_id, Arc::clone(block_device))
|
||||||
@ -62,7 +63,7 @@ impl Bitmap {
|
|||||||
bitmap_block[bits64_pos] -= 1u64 << inner_pos;
|
bitmap_block[bits64_pos] -= 1u64 << inner_pos;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/// Get the max number of allocatable blocks
|
||||||
pub fn maximum(&self) -> usize {
|
pub fn maximum(&self) -> usize {
|
||||||
self.blocks * BLOCK_BITS
|
self.blocks * BLOCK_BITS
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,15 @@ use alloc::collections::VecDeque;
|
|||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use lazy_static::*;
|
use lazy_static::*;
|
||||||
use spin::Mutex;
|
use spin::Mutex;
|
||||||
|
/// Cached block inside memory
|
||||||
pub struct BlockCache {
|
pub struct BlockCache {
|
||||||
|
/// cached block data
|
||||||
cache: [u8; BLOCK_SZ],
|
cache: [u8; BLOCK_SZ],
|
||||||
|
/// underlying block id
|
||||||
block_id: usize,
|
block_id: usize,
|
||||||
|
/// underlying block device
|
||||||
block_device: Arc<dyn BlockDevice>,
|
block_device: Arc<dyn BlockDevice>,
|
||||||
|
/// whether the block is dirty
|
||||||
modified: bool,
|
modified: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,7 +27,7 @@ impl BlockCache {
|
|||||||
modified: false,
|
modified: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Get the address of an offset inside the cached block data
|
||||||
fn addr_of_offset(&self, offset: usize) -> usize {
|
fn addr_of_offset(&self, offset: usize) -> usize {
|
||||||
&self.cache[offset] as *const _ as usize
|
&self.cache[offset] as *const _ as usize
|
||||||
}
|
}
|
||||||
@ -70,7 +74,7 @@ impl Drop for BlockCache {
|
|||||||
self.sync()
|
self.sync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Use a block cache of 16 blocks
|
||||||
const BLOCK_CACHE_SIZE: usize = 16;
|
const BLOCK_CACHE_SIZE: usize = 16;
|
||||||
|
|
||||||
pub struct BlockCacheManager {
|
pub struct BlockCacheManager {
|
||||||
@ -118,10 +122,11 @@ impl BlockCacheManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
/// The global block cache manager
|
||||||
pub static ref BLOCK_CACHE_MANAGER: Mutex<BlockCacheManager> =
|
pub static ref BLOCK_CACHE_MANAGER: Mutex<BlockCacheManager> =
|
||||||
Mutex::new(BlockCacheManager::new());
|
Mutex::new(BlockCacheManager::new());
|
||||||
}
|
}
|
||||||
|
/// Get the block cache corresponding to the given block id and block device
|
||||||
pub fn get_block_cache(
|
pub fn get_block_cache(
|
||||||
block_id: usize,
|
block_id: usize,
|
||||||
block_device: Arc<dyn BlockDevice>,
|
block_device: Arc<dyn BlockDevice>,
|
||||||
@ -130,7 +135,7 @@ pub fn get_block_cache(
|
|||||||
.lock()
|
.lock()
|
||||||
.get_block_cache(block_id, block_device)
|
.get_block_cache(block_id, block_device)
|
||||||
}
|
}
|
||||||
|
/// Sync all block cache to block device
|
||||||
pub fn block_cache_sync_all() {
|
pub fn block_cache_sync_all() {
|
||||||
let manager = BLOCK_CACHE_MANAGER.lock();
|
let manager = BLOCK_CACHE_MANAGER.lock();
|
||||||
for (_, cache) in manager.queue.iter() {
|
for (_, cache) in manager.queue.iter() {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use core::any::Any;
|
use core::any::Any;
|
||||||
|
/// Trait for block devices
|
||||||
|
/// which reads and writes data in the unit of blocks
|
||||||
pub trait BlockDevice: Send + Sync + Any {
|
pub trait BlockDevice: Send + Sync + Any {
|
||||||
|
///Read data form block to buffer
|
||||||
fn read_block(&self, block_id: usize, buf: &mut [u8]);
|
fn read_block(&self, block_id: usize, buf: &mut [u8]);
|
||||||
|
///Write data from buffer to block
|
||||||
fn write_block(&self, block_id: usize, buf: &[u8]);
|
fn write_block(&self, block_id: usize, buf: &[u8]);
|
||||||
}
|
}
|
||||||
|
@ -5,18 +5,22 @@ use super::{
|
|||||||
use crate::BLOCK_SZ;
|
use crate::BLOCK_SZ;
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use spin::Mutex;
|
use spin::Mutex;
|
||||||
|
///An easy file system on block
|
||||||
pub struct EasyFileSystem {
|
pub struct EasyFileSystem {
|
||||||
|
///Real device
|
||||||
pub block_device: Arc<dyn BlockDevice>,
|
pub block_device: Arc<dyn BlockDevice>,
|
||||||
|
///Inode bitmap
|
||||||
pub inode_bitmap: Bitmap,
|
pub inode_bitmap: Bitmap,
|
||||||
|
///Data bitmap
|
||||||
pub data_bitmap: Bitmap,
|
pub data_bitmap: Bitmap,
|
||||||
inode_area_start_block: u32,
|
inode_area_start_block: u32,
|
||||||
data_area_start_block: u32,
|
data_area_start_block: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
type DataBlock = [u8; BLOCK_SZ];
|
type DataBlock = [u8; BLOCK_SZ];
|
||||||
|
/// An easy fs over a block device
|
||||||
impl EasyFileSystem {
|
impl EasyFileSystem {
|
||||||
|
/// A data block of block size
|
||||||
pub fn create(
|
pub fn create(
|
||||||
block_device: Arc<dyn BlockDevice>,
|
block_device: Arc<dyn BlockDevice>,
|
||||||
total_blocks: u32,
|
total_blocks: u32,
|
||||||
@ -77,7 +81,7 @@ impl EasyFileSystem {
|
|||||||
block_cache_sync_all();
|
block_cache_sync_all();
|
||||||
Arc::new(Mutex::new(efs))
|
Arc::new(Mutex::new(efs))
|
||||||
}
|
}
|
||||||
|
/// Open a block device as a filesystem
|
||||||
pub fn open(block_device: Arc<dyn BlockDevice>) -> Arc<Mutex<Self>> {
|
pub fn open(block_device: Arc<dyn BlockDevice>) -> Arc<Mutex<Self>> {
|
||||||
// read SuperBlock
|
// read SuperBlock
|
||||||
get_block_cache(0, Arc::clone(&block_device))
|
get_block_cache(0, Arc::clone(&block_device))
|
||||||
@ -99,7 +103,7 @@ impl EasyFileSystem {
|
|||||||
Arc::new(Mutex::new(efs))
|
Arc::new(Mutex::new(efs))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Get the root inode of the filesystem
|
||||||
pub fn root_inode(efs: &Arc<Mutex<Self>>) -> Inode {
|
pub fn root_inode(efs: &Arc<Mutex<Self>>) -> Inode {
|
||||||
let block_device = Arc::clone(&efs.lock().block_device);
|
let block_device = Arc::clone(&efs.lock().block_device);
|
||||||
// acquire efs lock temporarily
|
// acquire efs lock temporarily
|
||||||
@ -107,7 +111,7 @@ impl EasyFileSystem {
|
|||||||
// release efs lock
|
// release efs lock
|
||||||
Inode::new(block_id, block_offset, Arc::clone(efs), block_device)
|
Inode::new(block_id, block_offset, Arc::clone(efs), block_device)
|
||||||
}
|
}
|
||||||
|
/// Get inode by id
|
||||||
pub fn get_disk_inode_pos(&self, inode_id: u32) -> (u32, usize) {
|
pub fn get_disk_inode_pos(&self, inode_id: u32) -> (u32, usize) {
|
||||||
let inode_size = core::mem::size_of::<DiskInode>();
|
let inode_size = core::mem::size_of::<DiskInode>();
|
||||||
let inodes_per_block = (BLOCK_SZ / inode_size) as u32;
|
let inodes_per_block = (BLOCK_SZ / inode_size) as u32;
|
||||||
@ -117,20 +121,20 @@ impl EasyFileSystem {
|
|||||||
(inode_id % inodes_per_block) as usize * inode_size,
|
(inode_id % inodes_per_block) as usize * inode_size,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
/// Get data block by id
|
||||||
pub fn get_data_block_id(&self, data_block_id: u32) -> u32 {
|
pub fn get_data_block_id(&self, data_block_id: u32) -> u32 {
|
||||||
self.data_area_start_block + data_block_id
|
self.data_area_start_block + data_block_id
|
||||||
}
|
}
|
||||||
|
/// Allocate a new inode
|
||||||
pub fn alloc_inode(&mut self) -> u32 {
|
pub fn alloc_inode(&mut self) -> u32 {
|
||||||
self.inode_bitmap.alloc(&self.block_device).unwrap() as u32
|
self.inode_bitmap.alloc(&self.block_device).unwrap() as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a block ID not ID in the data area.
|
/// Allocate a data block
|
||||||
pub fn alloc_data(&mut self) -> u32 {
|
pub fn alloc_data(&mut self) -> u32 {
|
||||||
self.data_bitmap.alloc(&self.block_device).unwrap() as u32 + self.data_area_start_block
|
self.data_bitmap.alloc(&self.block_device).unwrap() as u32 + self.data_area_start_block
|
||||||
}
|
}
|
||||||
|
/// Deallocate a data block
|
||||||
pub fn dealloc_data(&mut self, block_id: u32) {
|
pub fn dealloc_data(&mut self, block_id: u32) {
|
||||||
get_block_cache(block_id as usize, Arc::clone(&self.block_device))
|
get_block_cache(block_id as usize, Arc::clone(&self.block_device))
|
||||||
.lock()
|
.lock()
|
||||||
|
@ -3,16 +3,24 @@ use alloc::sync::Arc;
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::fmt::{Debug, Formatter, Result};
|
use core::fmt::{Debug, Formatter, Result};
|
||||||
|
|
||||||
|
/// Magic number for sanity check
|
||||||
const EFS_MAGIC: u32 = 0x3b800001;
|
const EFS_MAGIC: u32 = 0x3b800001;
|
||||||
|
/// The max number of direct inodes
|
||||||
const INODE_DIRECT_COUNT: usize = 28;
|
const INODE_DIRECT_COUNT: usize = 28;
|
||||||
|
/// The max length of inode name
|
||||||
const NAME_LENGTH_LIMIT: usize = 27;
|
const NAME_LENGTH_LIMIT: usize = 27;
|
||||||
|
/// The max number of indirect1 inodes
|
||||||
const INODE_INDIRECT1_COUNT: usize = BLOCK_SZ / 4;
|
const INODE_INDIRECT1_COUNT: usize = BLOCK_SZ / 4;
|
||||||
|
/// The max number of indirect2 inodes
|
||||||
const INODE_INDIRECT2_COUNT: usize = INODE_INDIRECT1_COUNT * INODE_INDIRECT1_COUNT;
|
const INODE_INDIRECT2_COUNT: usize = INODE_INDIRECT1_COUNT * INODE_INDIRECT1_COUNT;
|
||||||
|
/// The upper bound of direct inode index
|
||||||
const DIRECT_BOUND: usize = INODE_DIRECT_COUNT;
|
const DIRECT_BOUND: usize = INODE_DIRECT_COUNT;
|
||||||
|
/// The upper bound of indirect1 inode index
|
||||||
const INDIRECT1_BOUND: usize = DIRECT_BOUND + INODE_INDIRECT1_COUNT;
|
const INDIRECT1_BOUND: usize = DIRECT_BOUND + INODE_INDIRECT1_COUNT;
|
||||||
|
/// The upper bound of indirect2 inode indexs
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
const INDIRECT2_BOUND: usize = INDIRECT1_BOUND + INODE_INDIRECT2_COUNT;
|
const INDIRECT2_BOUND: usize = INDIRECT1_BOUND + INODE_INDIRECT2_COUNT;
|
||||||
|
/// Super block of a filesystem
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct SuperBlock {
|
pub struct SuperBlock {
|
||||||
magic: u32,
|
magic: u32,
|
||||||
@ -36,6 +44,7 @@ impl Debug for SuperBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SuperBlock {
|
impl SuperBlock {
|
||||||
|
/// Initialize a super block
|
||||||
pub fn initialize(
|
pub fn initialize(
|
||||||
&mut self,
|
&mut self,
|
||||||
total_blocks: u32,
|
total_blocks: u32,
|
||||||
@ -53,20 +62,23 @@ impl SuperBlock {
|
|||||||
data_area_blocks,
|
data_area_blocks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Check if a super block is valid using efs magic
|
||||||
pub fn is_valid(&self) -> bool {
|
pub fn is_valid(&self) -> bool {
|
||||||
self.magic == EFS_MAGIC
|
self.magic == EFS_MAGIC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Type of a disk inode
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub enum DiskInodeType {
|
pub enum DiskInodeType {
|
||||||
File,
|
File,
|
||||||
Directory,
|
Directory,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A indirect block
|
||||||
type IndirectBlock = [u32; BLOCK_SZ / 4];
|
type IndirectBlock = [u32; BLOCK_SZ / 4];
|
||||||
|
/// A data block
|
||||||
type DataBlock = [u8; BLOCK_SZ];
|
type DataBlock = [u8; BLOCK_SZ];
|
||||||
|
/// A disk inode
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct DiskInode {
|
pub struct DiskInode {
|
||||||
pub size: u32,
|
pub size: u32,
|
||||||
@ -77,7 +89,8 @@ pub struct DiskInode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DiskInode {
|
impl DiskInode {
|
||||||
/// indirect1 and indirect2 block are allocated only when they are needed.
|
/// Initialize a disk inode, as well as all direct inodes under it
|
||||||
|
/// indirect1 and indirect2 block are allocated only when they are needed
|
||||||
pub fn initialize(&mut self, type_: DiskInodeType) {
|
pub fn initialize(&mut self, type_: DiskInodeType) {
|
||||||
self.size = 0;
|
self.size = 0;
|
||||||
self.direct.iter_mut().for_each(|v| *v = 0);
|
self.direct.iter_mut().for_each(|v| *v = 0);
|
||||||
@ -85,9 +98,11 @@ impl DiskInode {
|
|||||||
self.indirect2 = 0;
|
self.indirect2 = 0;
|
||||||
self.type_ = type_;
|
self.type_ = type_;
|
||||||
}
|
}
|
||||||
|
/// Whether this inode is a directory
|
||||||
pub fn is_dir(&self) -> bool {
|
pub fn is_dir(&self) -> bool {
|
||||||
self.type_ == DiskInodeType::Directory
|
self.type_ == DiskInodeType::Directory
|
||||||
}
|
}
|
||||||
|
/// Whether this inode is a file
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn is_file(&self) -> bool {
|
pub fn is_file(&self) -> bool {
|
||||||
self.type_ == DiskInodeType::File
|
self.type_ == DiskInodeType::File
|
||||||
@ -116,10 +131,12 @@ impl DiskInode {
|
|||||||
}
|
}
|
||||||
total as u32
|
total as u32
|
||||||
}
|
}
|
||||||
|
/// Get the number of data blocks that have to be allocated given the new size of data
|
||||||
pub fn blocks_num_needed(&self, new_size: u32) -> u32 {
|
pub fn blocks_num_needed(&self, new_size: u32) -> u32 {
|
||||||
assert!(new_size >= self.size);
|
assert!(new_size >= self.size);
|
||||||
Self::total_blocks(new_size) - Self::total_blocks(self.size)
|
Self::total_blocks(new_size) - Self::total_blocks(self.size)
|
||||||
}
|
}
|
||||||
|
/// Get id of block given inner id
|
||||||
pub fn get_block_id(&self, inner_id: u32, block_device: &Arc<dyn BlockDevice>) -> u32 {
|
pub fn get_block_id(&self, inner_id: u32, block_device: &Arc<dyn BlockDevice>) -> u32 {
|
||||||
let inner_id = inner_id as usize;
|
let inner_id = inner_id as usize;
|
||||||
if inner_id < INODE_DIRECT_COUNT {
|
if inner_id < INODE_DIRECT_COUNT {
|
||||||
@ -144,6 +161,7 @@ impl DiskInode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Inncrease the size of current disk inode
|
||||||
pub fn increase_size(
|
pub fn increase_size(
|
||||||
&mut self,
|
&mut self,
|
||||||
new_size: u32,
|
new_size: u32,
|
||||||
@ -218,7 +236,6 @@ impl DiskInode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Clear size to zero and return blocks that should be deallocated.
|
/// Clear size to zero and return blocks that should be deallocated.
|
||||||
///
|
|
||||||
/// We will clear the block contents to zero later.
|
/// We will clear the block contents to zero later.
|
||||||
pub fn clear_size(&mut self, block_device: &Arc<dyn BlockDevice>) -> Vec<u32> {
|
pub fn clear_size(&mut self, block_device: &Arc<dyn BlockDevice>) -> Vec<u32> {
|
||||||
let mut v: Vec<u32> = Vec::new();
|
let mut v: Vec<u32> = Vec::new();
|
||||||
@ -291,6 +308,7 @@ impl DiskInode {
|
|||||||
self.indirect2 = 0;
|
self.indirect2 = 0;
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
/// Read data from current disk inode
|
||||||
pub fn read_at(
|
pub fn read_at(
|
||||||
&self,
|
&self,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
@ -330,7 +348,8 @@ impl DiskInode {
|
|||||||
}
|
}
|
||||||
read_size
|
read_size
|
||||||
}
|
}
|
||||||
/// File size must be adjusted before.
|
/// Write data into current disk inode
|
||||||
|
/// size must be adjusted properly beforehand
|
||||||
pub fn write_at(
|
pub fn write_at(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
@ -369,22 +388,24 @@ impl DiskInode {
|
|||||||
write_size
|
write_size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// A directory entry
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct DirEntry {
|
pub struct DirEntry {
|
||||||
name: [u8; NAME_LENGTH_LIMIT + 1],
|
name: [u8; NAME_LENGTH_LIMIT + 1],
|
||||||
inode_number: u32,
|
inode_number: u32,
|
||||||
}
|
}
|
||||||
|
/// Size of a directory entry
|
||||||
pub const DIRENT_SZ: usize = 32;
|
pub const DIRENT_SZ: usize = 32;
|
||||||
|
|
||||||
impl DirEntry {
|
impl DirEntry {
|
||||||
|
/// Create an empty directory entry
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: [0u8; NAME_LENGTH_LIMIT + 1],
|
name: [0u8; NAME_LENGTH_LIMIT + 1],
|
||||||
inode_number: 0,
|
inode_number: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Crate a directory entry from name and inode number
|
||||||
pub fn new(name: &str, inode_number: u32) -> Self {
|
pub fn new(name: &str, inode_number: u32) -> Self {
|
||||||
let mut bytes = [0u8; NAME_LENGTH_LIMIT + 1];
|
let mut bytes = [0u8; NAME_LENGTH_LIMIT + 1];
|
||||||
bytes[..name.len()].copy_from_slice(name.as_bytes());
|
bytes[..name.len()].copy_from_slice(name.as_bytes());
|
||||||
@ -393,16 +414,20 @@ impl DirEntry {
|
|||||||
inode_number,
|
inode_number,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Serialize into bytes
|
||||||
pub fn as_bytes(&self) -> &[u8] {
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
unsafe { core::slice::from_raw_parts(self as *const _ as usize as *const u8, DIRENT_SZ) }
|
unsafe { core::slice::from_raw_parts(self as *const _ as usize as *const u8, DIRENT_SZ) }
|
||||||
}
|
}
|
||||||
|
/// Serialize into mutable bytes
|
||||||
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
|
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
|
||||||
unsafe { core::slice::from_raw_parts_mut(self as *mut _ as usize as *mut u8, DIRENT_SZ) }
|
unsafe { core::slice::from_raw_parts_mut(self as *mut _ as usize as *mut u8, DIRENT_SZ) }
|
||||||
}
|
}
|
||||||
|
/// Get name of the entry
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
let len = (0usize..).find(|i| self.name[*i] == 0).unwrap();
|
let len = (0usize..).find(|i| self.name[*i] == 0).unwrap();
|
||||||
core::str::from_utf8(&self.name[..len]).unwrap()
|
core::str::from_utf8(&self.name[..len]).unwrap()
|
||||||
}
|
}
|
||||||
|
/// Get inode number of the entry
|
||||||
pub fn inode_number(&self) -> u32 {
|
pub fn inode_number(&self) -> u32 {
|
||||||
self.inode_number
|
self.inode_number
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
|
//!An easy file system isolated from the kernel
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![deny(missing_docs)]
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
mod bitmap;
|
mod bitmap;
|
||||||
mod block_cache;
|
mod block_cache;
|
||||||
mod block_dev;
|
mod block_dev;
|
||||||
mod efs;
|
mod efs;
|
||||||
mod layout;
|
mod layout;
|
||||||
mod vfs;
|
mod vfs;
|
||||||
|
/// Use a block size of 512 bytes
|
||||||
pub const BLOCK_SZ: usize = 512;
|
pub const BLOCK_SZ: usize = 512;
|
||||||
use bitmap::Bitmap;
|
use bitmap::Bitmap;
|
||||||
use block_cache::{block_cache_sync_all, get_block_cache};
|
use block_cache::{block_cache_sync_all, get_block_cache};
|
||||||
|
@ -6,7 +6,7 @@ use alloc::string::String;
|
|||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use spin::{Mutex, MutexGuard};
|
use spin::{Mutex, MutexGuard};
|
||||||
|
/// Virtual filesystem layer over easy-fs
|
||||||
pub struct Inode {
|
pub struct Inode {
|
||||||
block_id: usize,
|
block_id: usize,
|
||||||
block_offset: usize,
|
block_offset: usize,
|
||||||
@ -15,7 +15,7 @@ pub struct Inode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Inode {
|
impl Inode {
|
||||||
/// We should not acquire efs lock here.
|
/// Create a vfs inode
|
||||||
pub fn new(
|
pub fn new(
|
||||||
block_id: u32,
|
block_id: u32,
|
||||||
block_offset: usize,
|
block_offset: usize,
|
||||||
@ -29,19 +29,19 @@ impl Inode {
|
|||||||
block_device,
|
block_device,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Call a function over a disk inode to read it
|
||||||
fn read_disk_inode<V>(&self, f: impl FnOnce(&DiskInode) -> V) -> V {
|
fn read_disk_inode<V>(&self, f: impl FnOnce(&DiskInode) -> V) -> V {
|
||||||
get_block_cache(self.block_id, Arc::clone(&self.block_device))
|
get_block_cache(self.block_id, Arc::clone(&self.block_device))
|
||||||
.lock()
|
.lock()
|
||||||
.read(self.block_offset, f)
|
.read(self.block_offset, f)
|
||||||
}
|
}
|
||||||
|
/// Call a function over a disk inode to modify it
|
||||||
fn modify_disk_inode<V>(&self, f: impl FnOnce(&mut DiskInode) -> V) -> V {
|
fn modify_disk_inode<V>(&self, f: impl FnOnce(&mut DiskInode) -> V) -> V {
|
||||||
get_block_cache(self.block_id, Arc::clone(&self.block_device))
|
get_block_cache(self.block_id, Arc::clone(&self.block_device))
|
||||||
.lock()
|
.lock()
|
||||||
.modify(self.block_offset, f)
|
.modify(self.block_offset, f)
|
||||||
}
|
}
|
||||||
|
/// Find inode under a disk inode by name
|
||||||
fn find_inode_id(&self, name: &str, disk_inode: &DiskInode) -> Option<u32> {
|
fn find_inode_id(&self, name: &str, disk_inode: &DiskInode) -> Option<u32> {
|
||||||
// assert it is a directory
|
// assert it is a directory
|
||||||
assert!(disk_inode.is_dir());
|
assert!(disk_inode.is_dir());
|
||||||
@ -58,7 +58,7 @@ impl Inode {
|
|||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
/// Find inode under current inode by name
|
||||||
pub fn find(&self, name: &str) -> Option<Arc<Inode>> {
|
pub fn find(&self, name: &str) -> Option<Arc<Inode>> {
|
||||||
let fs = self.fs.lock();
|
let fs = self.fs.lock();
|
||||||
self.read_disk_inode(|disk_inode| {
|
self.read_disk_inode(|disk_inode| {
|
||||||
@ -73,7 +73,7 @@ impl Inode {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Increase the size of a disk inode
|
||||||
fn increase_size(
|
fn increase_size(
|
||||||
&self,
|
&self,
|
||||||
new_size: u32,
|
new_size: u32,
|
||||||
@ -90,7 +90,7 @@ impl Inode {
|
|||||||
}
|
}
|
||||||
disk_inode.increase_size(new_size, v, &self.block_device);
|
disk_inode.increase_size(new_size, v, &self.block_device);
|
||||||
}
|
}
|
||||||
|
/// Create inode under current inode by name
|
||||||
pub fn create(&self, name: &str) -> Option<Arc<Inode>> {
|
pub fn create(&self, name: &str) -> Option<Arc<Inode>> {
|
||||||
let mut fs = self.fs.lock();
|
let mut fs = self.fs.lock();
|
||||||
let op = |root_inode: &DiskInode| {
|
let op = |root_inode: &DiskInode| {
|
||||||
@ -138,7 +138,7 @@ impl Inode {
|
|||||||
)))
|
)))
|
||||||
// release efs lock automatically by compiler
|
// release efs lock automatically by compiler
|
||||||
}
|
}
|
||||||
|
/// List inodes under current inode
|
||||||
pub fn ls(&self) -> Vec<String> {
|
pub fn ls(&self) -> Vec<String> {
|
||||||
let _fs = self.fs.lock();
|
let _fs = self.fs.lock();
|
||||||
self.read_disk_inode(|disk_inode| {
|
self.read_disk_inode(|disk_inode| {
|
||||||
@ -155,12 +155,12 @@ impl Inode {
|
|||||||
v
|
v
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Read data from current inode
|
||||||
pub fn read_at(&self, offset: usize, buf: &mut [u8]) -> usize {
|
pub fn read_at(&self, offset: usize, buf: &mut [u8]) -> usize {
|
||||||
let _fs = self.fs.lock();
|
let _fs = self.fs.lock();
|
||||||
self.read_disk_inode(|disk_inode| disk_inode.read_at(offset, buf, &self.block_device))
|
self.read_disk_inode(|disk_inode| disk_inode.read_at(offset, buf, &self.block_device))
|
||||||
}
|
}
|
||||||
|
/// Write data to current inode
|
||||||
pub fn write_at(&self, offset: usize, buf: &[u8]) -> usize {
|
pub fn write_at(&self, offset: usize, buf: &[u8]) -> usize {
|
||||||
let mut fs = self.fs.lock();
|
let mut fs = self.fs.lock();
|
||||||
let size = self.modify_disk_inode(|disk_inode| {
|
let size = self.modify_disk_inode(|disk_inode| {
|
||||||
@ -170,7 +170,7 @@ impl Inode {
|
|||||||
block_cache_sync_all();
|
block_cache_sync_all();
|
||||||
size
|
size
|
||||||
}
|
}
|
||||||
|
/// Clear the data in current inode
|
||||||
pub fn clear(&self) {
|
pub fn clear(&self) {
|
||||||
let mut fs = self.fs.lock();
|
let mut fs = self.fs.lock();
|
||||||
self.modify_disk_inode(|disk_inode| {
|
self.modify_disk_inode(|disk_inode| {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//! Constants used in rCore
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
|
||||||
pub const USER_STACK_SIZE: usize = 4096 * 2;
|
pub const USER_STACK_SIZE: usize = 4096 * 2;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//! SBI console driver, for text output
|
||||||
use crate::sbi::console_putchar;
|
use crate::sbi::console_putchar;
|
||||||
use core::fmt::{self, Write};
|
use core::fmt::{self, Write};
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ pub fn print(args: fmt::Arguments) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
/// print string macro
|
||||||
macro_rules! print {
|
macro_rules! print {
|
||||||
($fmt: literal $(, $($arg: tt)+)?) => {
|
($fmt: literal $(, $($arg: tt)+)?) => {
|
||||||
$crate::console::print(format_args!($fmt $(, $($arg)+)?));
|
$crate::console::print(format_args!($fmt $(, $($arg)+)?));
|
||||||
@ -24,6 +26,7 @@ macro_rules! print {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
/// println string macro
|
||||||
macro_rules! println {
|
macro_rules! println {
|
||||||
($fmt: literal $(, $($arg: tt)+)?) => {
|
($fmt: literal $(, $($arg: tt)+)?) => {
|
||||||
$crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
|
$crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
//! `Arc<Inode>` -> `OSInodeInner`: In order to open files concurrently
|
||||||
|
//! we need to wrap `Inode` into `Arc`,but `Mutex` in `Inode` prevents
|
||||||
|
//! file systems from being accessed simultaneously
|
||||||
|
//!
|
||||||
|
//! `UPSafeCell<OSInodeInner>` -> `OSInode`: for static `ROOT_INODE`,we
|
||||||
|
//! need to wrap `OSInodeInner` into `UPSafeCell`
|
||||||
use super::File;
|
use super::File;
|
||||||
use crate::drivers::BLOCK_DEVICE;
|
use crate::drivers::BLOCK_DEVICE;
|
||||||
use crate::mm::UserBuffer;
|
use crate::mm::UserBuffer;
|
||||||
@ -7,19 +13,21 @@ use alloc::vec::Vec;
|
|||||||
use bitflags::*;
|
use bitflags::*;
|
||||||
use easy_fs::{EasyFileSystem, Inode};
|
use easy_fs::{EasyFileSystem, Inode};
|
||||||
use lazy_static::*;
|
use lazy_static::*;
|
||||||
|
/// A wrapper around a filesystem inode
|
||||||
|
/// to implement File trait atop
|
||||||
pub struct OSInode {
|
pub struct OSInode {
|
||||||
readable: bool,
|
readable: bool,
|
||||||
writable: bool,
|
writable: bool,
|
||||||
inner: UPSafeCell<OSInodeInner>,
|
inner: UPSafeCell<OSInodeInner>,
|
||||||
}
|
}
|
||||||
|
/// The OS inode inner in 'UPSafeCell'
|
||||||
pub struct OSInodeInner {
|
pub struct OSInodeInner {
|
||||||
offset: usize,
|
offset: usize,
|
||||||
inode: Arc<Inode>,
|
inode: Arc<Inode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OSInode {
|
impl OSInode {
|
||||||
|
/// Construct an OS inode from a inode
|
||||||
pub fn new(readable: bool, writable: bool, inode: Arc<Inode>) -> Self {
|
pub fn new(readable: bool, writable: bool, inode: Arc<Inode>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
readable,
|
readable,
|
||||||
@ -27,6 +35,7 @@ impl OSInode {
|
|||||||
inner: unsafe { UPSafeCell::new(OSInodeInner { offset: 0, inode }) },
|
inner: unsafe { UPSafeCell::new(OSInodeInner { offset: 0, inode }) },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Read all data inside a inode into vector
|
||||||
pub fn read_all(&self) -> Vec<u8> {
|
pub fn read_all(&self) -> Vec<u8> {
|
||||||
let mut inner = self.inner.exclusive_access();
|
let mut inner = self.inner.exclusive_access();
|
||||||
let mut buffer = [0u8; 512];
|
let mut buffer = [0u8; 512];
|
||||||
@ -49,7 +58,7 @@ lazy_static! {
|
|||||||
Arc::new(EasyFileSystem::root_inode(&efs))
|
Arc::new(EasyFileSystem::root_inode(&efs))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/// List all files in the filesystems
|
||||||
pub fn list_apps() {
|
pub fn list_apps() {
|
||||||
println!("/**** APPS ****");
|
println!("/**** APPS ****");
|
||||||
for app in ROOT_INODE.ls() {
|
for app in ROOT_INODE.ls() {
|
||||||
@ -58,12 +67,18 @@ pub fn list_apps() {
|
|||||||
println!("**************/");
|
println!("**************/");
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
///Open file flags
|
||||||
pub struct OpenFlags: u32 {
|
pub struct OpenFlags: u32 {
|
||||||
|
///Read only
|
||||||
const RDONLY = 0;
|
const RDONLY = 0;
|
||||||
|
///Write only
|
||||||
const WRONLY = 1 << 0;
|
const WRONLY = 1 << 0;
|
||||||
|
///Read & Write
|
||||||
const RDWR = 1 << 1;
|
const RDWR = 1 << 1;
|
||||||
|
///Allow create
|
||||||
const CREATE = 1 << 9;
|
const CREATE = 1 << 9;
|
||||||
|
///Clear file and return an empty one
|
||||||
const TRUNC = 1 << 10;
|
const TRUNC = 1 << 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,7 +96,7 @@ impl OpenFlags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
///Open file with flags
|
||||||
pub fn open_file(name: &str, flags: OpenFlags) -> Option<Arc<OSInode>> {
|
pub fn open_file(name: &str, flags: OpenFlags) -> Option<Arc<OSInode>> {
|
||||||
let (readable, writable) = flags.read_write();
|
let (readable, writable) = flags.read_write();
|
||||||
if flags.contains(OpenFlags::CREATE) {
|
if flags.contains(OpenFlags::CREATE) {
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
|
//! File system in os
|
||||||
mod inode;
|
mod inode;
|
||||||
mod stdio;
|
mod stdio;
|
||||||
|
|
||||||
use crate::mm::UserBuffer;
|
use crate::mm::UserBuffer;
|
||||||
|
/// File trait
|
||||||
pub trait File: Send + Sync {
|
pub trait File: Send + Sync {
|
||||||
|
/// If readable
|
||||||
fn readable(&self) -> bool;
|
fn readable(&self) -> bool;
|
||||||
|
/// If writable
|
||||||
fn writable(&self) -> bool;
|
fn writable(&self) -> bool;
|
||||||
|
/// Read file to `UserBuffer`
|
||||||
fn read(&self, buf: UserBuffer) -> usize;
|
fn read(&self, buf: UserBuffer) -> usize;
|
||||||
|
/// Write `UserBuffer` to file
|
||||||
fn write(&self, buf: UserBuffer) -> usize;
|
fn write(&self, buf: UserBuffer) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
//!Stdin & Stdout
|
||||||
use super::File;
|
use super::File;
|
||||||
use crate::mm::UserBuffer;
|
use crate::mm::UserBuffer;
|
||||||
use crate::sbi::console_getchar;
|
use crate::sbi::console_getchar;
|
||||||
use crate::task::suspend_current_and_run_next;
|
use crate::task::suspend_current_and_run_next;
|
||||||
|
///Standard input
|
||||||
pub struct Stdin;
|
pub struct Stdin;
|
||||||
|
///Standard output
|
||||||
pub struct Stdout;
|
pub struct Stdout;
|
||||||
|
|
||||||
impl File for Stdin {
|
impl File for Stdin {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//! The panic handler
|
||||||
use crate::sbi::shutdown;
|
use crate::sbi::shutdown;
|
||||||
use core::panic::PanicInfo;
|
use core::panic::PanicInfo;
|
||||||
|
|
||||||
|
@ -1,3 +1,26 @@
|
|||||||
|
//! 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
|
||||||
|
//! - [`mm`]: Address map using SV39
|
||||||
|
//! - [`sync`]: Wrap a static data structure inside it so that we are able to access it without any `unsafe`.
|
||||||
|
//! - [`fs`]: Separate user from file system with some structures
|
||||||
|
//!
|
||||||
|
//! 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_tasks()`] and for the first time go to
|
||||||
|
//! userspace.
|
||||||
|
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
#![deny(warnings)]
|
||||||
|
#![allow(unused_imports)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![feature(panic_info_message)]
|
#![feature(panic_info_message)]
|
||||||
@ -19,20 +42,20 @@ mod board;
|
|||||||
mod console;
|
mod console;
|
||||||
mod config;
|
mod config;
|
||||||
mod drivers;
|
mod drivers;
|
||||||
mod fs;
|
pub mod fs;
|
||||||
mod lang_items;
|
pub mod lang_items;
|
||||||
mod mm;
|
pub mod mm;
|
||||||
mod sbi;
|
pub mod sbi;
|
||||||
mod sync;
|
pub mod sync;
|
||||||
mod syscall;
|
pub mod syscall;
|
||||||
mod task;
|
pub mod task;
|
||||||
mod timer;
|
pub mod timer;
|
||||||
mod trap;
|
pub mod trap;
|
||||||
|
|
||||||
use core::arch::global_asm;
|
use core::arch::global_asm;
|
||||||
|
|
||||||
global_asm!(include_str!("entry.asm"));
|
global_asm!(include_str!("entry.asm"));
|
||||||
|
/// clear BSS segment
|
||||||
fn clear_bss() {
|
fn clear_bss() {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn sbss();
|
fn sbss();
|
||||||
@ -45,6 +68,7 @@ fn clear_bss() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
/// the rust entry-point of os
|
||||||
pub fn rust_main() -> ! {
|
pub fn rust_main() -> ! {
|
||||||
clear_bss();
|
clear_bss();
|
||||||
println!("[kernel] Hello, world!");
|
println!("[kernel] Hello, world!");
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//! Implementation of physical and virtual address and page number.
|
||||||
use super::PageTableEntry;
|
use super::PageTableEntry;
|
||||||
use crate::config::{PAGE_SIZE, PAGE_SIZE_BITS};
|
use crate::config::{PAGE_SIZE, PAGE_SIZE_BITS};
|
||||||
use core::fmt::{self, Debug, Formatter};
|
use core::fmt::{self, Debug, Formatter};
|
||||||
@ -14,14 +15,17 @@ pub struct PhysAddr(pub usize);
|
|||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
|
///virtual address
|
||||||
pub struct VirtAddr(pub usize);
|
pub struct VirtAddr(pub usize);
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
|
///phiscal page number
|
||||||
pub struct PhysPageNum(pub usize);
|
pub struct PhysPageNum(pub usize);
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
|
///virtual page number
|
||||||
pub struct VirtPageNum(pub usize);
|
pub struct VirtPageNum(pub usize);
|
||||||
|
|
||||||
/// Debugging
|
/// Debugging
|
||||||
@ -91,17 +95,21 @@ impl From<VirtPageNum> for usize {
|
|||||||
v.0
|
v.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
///
|
||||||
impl VirtAddr {
|
impl VirtAddr {
|
||||||
|
///`VirtAddr`->`VirtPageNum`
|
||||||
pub fn floor(&self) -> VirtPageNum {
|
pub fn floor(&self) -> VirtPageNum {
|
||||||
VirtPageNum(self.0 / PAGE_SIZE)
|
VirtPageNum(self.0 / PAGE_SIZE)
|
||||||
}
|
}
|
||||||
|
///`VirtAddr`->`VirtPageNum`
|
||||||
pub fn ceil(&self) -> VirtPageNum {
|
pub fn ceil(&self) -> VirtPageNum {
|
||||||
VirtPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE)
|
VirtPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE)
|
||||||
}
|
}
|
||||||
|
///Get page offset
|
||||||
pub fn page_offset(&self) -> usize {
|
pub fn page_offset(&self) -> usize {
|
||||||
self.0 & (PAGE_SIZE - 1)
|
self.0 & (PAGE_SIZE - 1)
|
||||||
}
|
}
|
||||||
|
///Check page aligned
|
||||||
pub fn aligned(&self) -> bool {
|
pub fn aligned(&self) -> bool {
|
||||||
self.page_offset() == 0
|
self.page_offset() == 0
|
||||||
}
|
}
|
||||||
@ -118,15 +126,19 @@ impl From<VirtPageNum> for VirtAddr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl PhysAddr {
|
impl PhysAddr {
|
||||||
|
///`PhysAddr`->`PhysPageNum`
|
||||||
pub fn floor(&self) -> PhysPageNum {
|
pub fn floor(&self) -> PhysPageNum {
|
||||||
PhysPageNum(self.0 / PAGE_SIZE)
|
PhysPageNum(self.0 / PAGE_SIZE)
|
||||||
}
|
}
|
||||||
|
///`PhysAddr`->`PhysPageNum`
|
||||||
pub fn ceil(&self) -> PhysPageNum {
|
pub fn ceil(&self) -> PhysPageNum {
|
||||||
PhysPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE)
|
PhysPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE)
|
||||||
}
|
}
|
||||||
|
///Get page offset
|
||||||
pub fn page_offset(&self) -> usize {
|
pub fn page_offset(&self) -> usize {
|
||||||
self.0 & (PAGE_SIZE - 1)
|
self.0 & (PAGE_SIZE - 1)
|
||||||
}
|
}
|
||||||
|
///Check page aligned
|
||||||
pub fn aligned(&self) -> bool {
|
pub fn aligned(&self) -> bool {
|
||||||
self.page_offset() == 0
|
self.page_offset() == 0
|
||||||
}
|
}
|
||||||
@ -144,6 +156,7 @@ impl From<PhysPageNum> for PhysAddr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VirtPageNum {
|
impl VirtPageNum {
|
||||||
|
///Return VPN 3 level index
|
||||||
pub fn indexes(&self) -> [usize; 3] {
|
pub fn indexes(&self) -> [usize; 3] {
|
||||||
let mut vpn = self.0;
|
let mut vpn = self.0;
|
||||||
let mut idx = [0usize; 3];
|
let mut idx = [0usize; 3];
|
||||||
@ -156,29 +169,35 @@ impl VirtPageNum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PhysAddr {
|
impl PhysAddr {
|
||||||
|
///Get reference to `PhysAddr` value
|
||||||
pub fn get_ref<T>(&self) -> &'static T {
|
pub fn get_ref<T>(&self) -> &'static T {
|
||||||
unsafe { (self.0 as *const T).as_ref().unwrap() }
|
unsafe { (self.0 as *const T).as_ref().unwrap() }
|
||||||
}
|
}
|
||||||
|
///Get mutable reference to `PhysAddr` value
|
||||||
pub fn get_mut<T>(&self) -> &'static mut T {
|
pub fn get_mut<T>(&self) -> &'static mut T {
|
||||||
unsafe { (self.0 as *mut T).as_mut().unwrap() }
|
unsafe { (self.0 as *mut T).as_mut().unwrap() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl PhysPageNum {
|
impl PhysPageNum {
|
||||||
|
///Get `PageTableEntry` on `PhysPageNum`
|
||||||
pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] {
|
pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] {
|
||||||
let pa: PhysAddr = (*self).into();
|
let pa: PhysAddr = (*self).into();
|
||||||
unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512) }
|
unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512) }
|
||||||
}
|
}
|
||||||
|
///Get u8 array on `PhysPageNum`
|
||||||
pub fn get_bytes_array(&self) -> &'static mut [u8] {
|
pub fn get_bytes_array(&self) -> &'static mut [u8] {
|
||||||
let pa: PhysAddr = (*self).into();
|
let pa: PhysAddr = (*self).into();
|
||||||
unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096) }
|
unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096) }
|
||||||
}
|
}
|
||||||
|
///Get Get mutable reference to `PhysAddr` value on `PhysPageNum`
|
||||||
pub fn get_mut<T>(&self) -> &'static mut T {
|
pub fn get_mut<T>(&self) -> &'static mut T {
|
||||||
let pa: PhysAddr = (*self).into();
|
let pa: PhysAddr = (*self).into();
|
||||||
pa.get_mut()
|
pa.get_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
///Add value by one
|
||||||
pub trait StepByOne {
|
pub trait StepByOne {
|
||||||
|
///Add value by one
|
||||||
fn step(&mut self);
|
fn step(&mut self);
|
||||||
}
|
}
|
||||||
impl StepByOne for VirtPageNum {
|
impl StepByOne for VirtPageNum {
|
||||||
@ -193,6 +212,7 @@ impl StepByOne for PhysPageNum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
|
/// a simple range structure for type T
|
||||||
pub struct SimpleRange<T>
|
pub struct SimpleRange<T>
|
||||||
where
|
where
|
||||||
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
|
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
|
||||||
@ -225,6 +245,7 @@ where
|
|||||||
SimpleRangeIterator::new(self.l, self.r)
|
SimpleRangeIterator::new(self.l, self.r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// iterator for the simple range structure
|
||||||
pub struct SimpleRangeIterator<T>
|
pub struct SimpleRangeIterator<T>
|
||||||
where
|
where
|
||||||
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
|
T: StepByOne + Copy + PartialEq + PartialOrd + Debug,
|
||||||
@ -255,4 +276,5 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// a simple range structure for virtual page number
|
||||||
pub type VPNRange = SimpleRange<VirtPageNum>;
|
pub type VPNRange = SimpleRange<VirtPageNum>;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Implementation of [`FrameAllocator`] which
|
||||||
|
//! controls all the frames in the operating system.
|
||||||
use super::{PhysAddr, PhysPageNum};
|
use super::{PhysAddr, PhysPageNum};
|
||||||
use crate::config::MEMORY_END;
|
use crate::config::MEMORY_END;
|
||||||
use crate::sync::UPSafeCell;
|
use crate::sync::UPSafeCell;
|
||||||
@ -5,11 +7,14 @@ use alloc::vec::Vec;
|
|||||||
use core::fmt::{self, Debug, Formatter};
|
use core::fmt::{self, Debug, Formatter};
|
||||||
use lazy_static::*;
|
use lazy_static::*;
|
||||||
|
|
||||||
|
/// manage a frame which has the same lifecycle as the tracker
|
||||||
pub struct FrameTracker {
|
pub struct FrameTracker {
|
||||||
|
///
|
||||||
pub ppn: PhysPageNum,
|
pub ppn: PhysPageNum,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrameTracker {
|
impl FrameTracker {
|
||||||
|
///Create an empty `FrameTracker`
|
||||||
pub fn new(ppn: PhysPageNum) -> Self {
|
pub fn new(ppn: PhysPageNum) -> Self {
|
||||||
// page cleaning
|
// page cleaning
|
||||||
let bytes_array = ppn.get_bytes_array();
|
let bytes_array = ppn.get_bytes_array();
|
||||||
@ -37,7 +42,7 @@ trait FrameAllocator {
|
|||||||
fn alloc(&mut self) -> Option<PhysPageNum>;
|
fn alloc(&mut self) -> Option<PhysPageNum>;
|
||||||
fn dealloc(&mut self, ppn: PhysPageNum);
|
fn dealloc(&mut self, ppn: PhysPageNum);
|
||||||
}
|
}
|
||||||
|
/// an implementation for frame allocator
|
||||||
pub struct StackFrameAllocator {
|
pub struct StackFrameAllocator {
|
||||||
current: usize,
|
current: usize,
|
||||||
end: usize,
|
end: usize,
|
||||||
@ -83,10 +88,11 @@ impl FrameAllocator for StackFrameAllocator {
|
|||||||
type FrameAllocatorImpl = StackFrameAllocator;
|
type FrameAllocatorImpl = StackFrameAllocator;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
/// frame allocator instance through lazy_static!
|
||||||
pub static ref FRAME_ALLOCATOR: UPSafeCell<FrameAllocatorImpl> =
|
pub static ref FRAME_ALLOCATOR: UPSafeCell<FrameAllocatorImpl> =
|
||||||
unsafe { UPSafeCell::new(FrameAllocatorImpl::new()) };
|
unsafe { UPSafeCell::new(FrameAllocatorImpl::new()) };
|
||||||
}
|
}
|
||||||
|
/// initiate the frame allocator using `ekernel` and `MEMORY_END`
|
||||||
pub fn init_frame_allocator() {
|
pub fn init_frame_allocator() {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn ekernel();
|
fn ekernel();
|
||||||
@ -96,19 +102,20 @@ pub fn init_frame_allocator() {
|
|||||||
PhysAddr::from(MEMORY_END).floor(),
|
PhysAddr::from(MEMORY_END).floor(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/// allocate a frame
|
||||||
pub fn frame_alloc() -> Option<FrameTracker> {
|
pub fn frame_alloc() -> Option<FrameTracker> {
|
||||||
FRAME_ALLOCATOR
|
FRAME_ALLOCATOR
|
||||||
.exclusive_access()
|
.exclusive_access()
|
||||||
.alloc()
|
.alloc()
|
||||||
.map(FrameTracker::new)
|
.map(FrameTracker::new)
|
||||||
}
|
}
|
||||||
|
/// deallocate a frame
|
||||||
pub fn frame_dealloc(ppn: PhysPageNum) {
|
pub fn frame_dealloc(ppn: PhysPageNum) {
|
||||||
FRAME_ALLOCATOR.exclusive_access().dealloc(ppn);
|
FRAME_ALLOCATOR.exclusive_access().dealloc(ppn);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
/// a simple test for frame allocator
|
||||||
pub fn frame_allocator_test() {
|
pub fn frame_allocator_test() {
|
||||||
let mut v: Vec<FrameTracker> = Vec::new();
|
let mut v: Vec<FrameTracker> = Vec::new();
|
||||||
for i in 0..5 {
|
for i in 0..5 {
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
|
//! The global allocator
|
||||||
use crate::config::KERNEL_HEAP_SIZE;
|
use crate::config::KERNEL_HEAP_SIZE;
|
||||||
use buddy_system_allocator::LockedHeap;
|
use buddy_system_allocator::LockedHeap;
|
||||||
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
|
/// heap allocator instance
|
||||||
static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty();
|
static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty();
|
||||||
|
|
||||||
#[alloc_error_handler]
|
#[alloc_error_handler]
|
||||||
|
/// panic when heap allocation error occurs
|
||||||
pub fn handle_alloc_error(layout: core::alloc::Layout) -> ! {
|
pub fn handle_alloc_error(layout: core::alloc::Layout) -> ! {
|
||||||
panic!("Heap allocation error, layout = {:?}", layout);
|
panic!("Heap allocation error, layout = {:?}", layout);
|
||||||
}
|
}
|
||||||
|
/// heap space ([u8; KERNEL_HEAP_SIZE])
|
||||||
static mut HEAP_SPACE: [u8; KERNEL_HEAP_SIZE] = [0; KERNEL_HEAP_SIZE];
|
static mut HEAP_SPACE: [u8; KERNEL_HEAP_SIZE] = [0; KERNEL_HEAP_SIZE];
|
||||||
|
/// initiate heap allocator
|
||||||
pub fn init_heap() {
|
pub fn init_heap() {
|
||||||
unsafe {
|
unsafe {
|
||||||
HEAP_ALLOCATOR
|
HEAP_ALLOCATOR
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//! Implementation of [`MapArea`] and [`MemorySet`].
|
||||||
use super::{frame_alloc, FrameTracker};
|
use super::{frame_alloc, FrameTracker};
|
||||||
use super::{PTEFlags, PageTable, PageTableEntry};
|
use super::{PTEFlags, PageTable, PageTableEntry};
|
||||||
use super::{PhysAddr, PhysPageNum, VirtAddr, VirtPageNum};
|
use super::{PhysAddr, PhysPageNum, VirtAddr, VirtPageNum};
|
||||||
@ -25,26 +26,29 @@ extern "C" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
/// a memory set instance through lazy_static! managing kernel space
|
||||||
pub static ref KERNEL_SPACE: Arc<UPSafeCell<MemorySet>> =
|
pub static ref KERNEL_SPACE: Arc<UPSafeCell<MemorySet>> =
|
||||||
Arc::new(unsafe { UPSafeCell::new(MemorySet::new_kernel()) });
|
Arc::new(unsafe { UPSafeCell::new(MemorySet::new_kernel()) });
|
||||||
}
|
}
|
||||||
|
///Get kernelspace root ppn
|
||||||
pub fn kernel_token() -> usize {
|
pub fn kernel_token() -> usize {
|
||||||
KERNEL_SPACE.exclusive_access().token()
|
KERNEL_SPACE.exclusive_access().token()
|
||||||
}
|
}
|
||||||
|
/// memory set structure, controls virtual-memory space
|
||||||
pub struct MemorySet {
|
pub struct MemorySet {
|
||||||
page_table: PageTable,
|
page_table: PageTable,
|
||||||
areas: Vec<MapArea>,
|
areas: Vec<MapArea>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemorySet {
|
impl MemorySet {
|
||||||
|
///Create an empty `MemorySet`
|
||||||
pub fn new_bare() -> Self {
|
pub fn new_bare() -> Self {
|
||||||
Self {
|
Self {
|
||||||
page_table: PageTable::new(),
|
page_table: PageTable::new(),
|
||||||
areas: Vec::new(),
|
areas: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
///Get pagetable `root_ppn`
|
||||||
pub fn token(&self) -> usize {
|
pub fn token(&self) -> usize {
|
||||||
self.page_table.token()
|
self.page_table.token()
|
||||||
}
|
}
|
||||||
@ -60,6 +64,7 @@ impl MemorySet {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
///Remove `MapArea` that starts with `start_vpn`
|
||||||
pub fn remove_area_with_start_vpn(&mut self, start_vpn: VirtPageNum) {
|
pub fn remove_area_with_start_vpn(&mut self, start_vpn: VirtPageNum) {
|
||||||
if let Some((idx, area)) = self
|
if let Some((idx, area)) = self
|
||||||
.areas
|
.areas
|
||||||
@ -231,6 +236,7 @@ impl MemorySet {
|
|||||||
elf.header.pt2.entry_point() as usize,
|
elf.header.pt2.entry_point() as usize,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
///Clone a same `MemorySet`
|
||||||
pub fn from_existed_user(user_space: &MemorySet) -> MemorySet {
|
pub fn from_existed_user(user_space: &MemorySet) -> MemorySet {
|
||||||
let mut memory_set = Self::new_bare();
|
let mut memory_set = Self::new_bare();
|
||||||
// map trampoline
|
// map trampoline
|
||||||
@ -250,6 +256,7 @@ impl MemorySet {
|
|||||||
}
|
}
|
||||||
memory_set
|
memory_set
|
||||||
}
|
}
|
||||||
|
///Refresh TLB with `sfence.vma`
|
||||||
pub fn activate(&self) {
|
pub fn activate(&self) {
|
||||||
let satp = self.page_table.token();
|
let satp = self.page_table.token();
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -257,15 +264,17 @@ impl MemorySet {
|
|||||||
asm!("sfence.vma");
|
asm!("sfence.vma");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
///Translate throuth pagetable
|
||||||
pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> {
|
pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> {
|
||||||
self.page_table.translate(vpn)
|
self.page_table.translate(vpn)
|
||||||
}
|
}
|
||||||
|
///Remove all `MapArea`
|
||||||
pub fn recycle_data_pages(&mut self) {
|
pub fn recycle_data_pages(&mut self) {
|
||||||
//*self = Self::new_bare();
|
//*self = Self::new_bare();
|
||||||
self.areas.clear();
|
self.areas.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// map area structure, controls a contiguous piece of virtual memory
|
||||||
pub struct MapArea {
|
pub struct MapArea {
|
||||||
vpn_range: VPNRange,
|
vpn_range: VPNRange,
|
||||||
data_frames: BTreeMap<VirtPageNum, FrameTracker>,
|
data_frames: BTreeMap<VirtPageNum, FrameTracker>,
|
||||||
@ -353,21 +362,28 @@ impl MapArea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
/// map type for memory set: identical or framed
|
||||||
pub enum MapType {
|
pub enum MapType {
|
||||||
Identical,
|
Identical,
|
||||||
Framed,
|
Framed,
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
/// map permission corresponding to that in pte: `R W X U`
|
||||||
pub struct MapPermission: u8 {
|
pub struct MapPermission: u8 {
|
||||||
|
///Readable
|
||||||
const R = 1 << 1;
|
const R = 1 << 1;
|
||||||
|
///Writable
|
||||||
const W = 1 << 2;
|
const W = 1 << 2;
|
||||||
|
///Excutable
|
||||||
const X = 1 << 3;
|
const X = 1 << 3;
|
||||||
|
///Accessible in U mode
|
||||||
const U = 1 << 4;
|
const U = 1 << 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
///Check PageTable running correctly
|
||||||
pub fn remap_test() {
|
pub fn remap_test() {
|
||||||
let mut kernel_space = KERNEL_SPACE.exclusive_access();
|
let mut kernel_space = KERNEL_SPACE.exclusive_access();
|
||||||
let mid_text: VirtAddr = ((stext as usize + etext as usize) / 2).into();
|
let mid_text: VirtAddr = ((stext as usize + etext as usize) / 2).into();
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
//! Memory management implementation
|
||||||
|
//!
|
||||||
|
//! SV39 page-based virtual-memory architecture for RV64 systems, and
|
||||||
|
//! everything about memory management, like frame allocator, page table,
|
||||||
|
//! map area and memory set, is implemented here.
|
||||||
|
//!
|
||||||
|
//! Every task or process has a memory_set to control its virtual memory.
|
||||||
mod address;
|
mod address;
|
||||||
mod frame_allocator;
|
mod frame_allocator;
|
||||||
mod heap_allocator;
|
mod heap_allocator;
|
||||||
@ -14,7 +21,7 @@ pub use page_table::{
|
|||||||
translated_byte_buffer, translated_ref, translated_refmut, translated_str, PageTable,
|
translated_byte_buffer, translated_ref, translated_refmut, translated_str, PageTable,
|
||||||
PageTableEntry, UserBuffer, UserBufferIterator,
|
PageTableEntry, UserBuffer, UserBufferIterator,
|
||||||
};
|
};
|
||||||
|
/// initiate heap allocator, frame allocator and kernel space
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
heap_allocator::init_heap();
|
heap_allocator::init_heap();
|
||||||
frame_allocator::init_frame_allocator();
|
frame_allocator::init_frame_allocator();
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//! Implementation of [`PageTableEntry`] and [`PageTable`].
|
||||||
use super::{frame_alloc, FrameTracker, PhysAddr, PhysPageNum, StepByOne, VirtAddr, VirtPageNum};
|
use super::{frame_alloc, FrameTracker, PhysAddr, PhysPageNum, StepByOne, VirtAddr, VirtPageNum};
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
@ -19,39 +20,49 @@ bitflags! {
|
|||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
/// page table entry structure
|
||||||
pub struct PageTableEntry {
|
pub struct PageTableEntry {
|
||||||
|
///PTE
|
||||||
pub bits: usize,
|
pub bits: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PageTableEntry {
|
impl PageTableEntry {
|
||||||
|
///Create a PTE from ppn
|
||||||
pub fn new(ppn: PhysPageNum, flags: PTEFlags) -> Self {
|
pub fn new(ppn: PhysPageNum, flags: PTEFlags) -> Self {
|
||||||
PageTableEntry {
|
PageTableEntry {
|
||||||
bits: ppn.0 << 10 | flags.bits as usize,
|
bits: ppn.0 << 10 | flags.bits as usize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
///Return an empty PTE
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
PageTableEntry { bits: 0 }
|
PageTableEntry { bits: 0 }
|
||||||
}
|
}
|
||||||
|
///Return 44bit ppn
|
||||||
pub fn ppn(&self) -> PhysPageNum {
|
pub fn ppn(&self) -> PhysPageNum {
|
||||||
(self.bits >> 10 & ((1usize << 44) - 1)).into()
|
(self.bits >> 10 & ((1usize << 44) - 1)).into()
|
||||||
}
|
}
|
||||||
|
///Return 10bit flag
|
||||||
pub fn flags(&self) -> PTEFlags {
|
pub fn flags(&self) -> PTEFlags {
|
||||||
PTEFlags::from_bits(self.bits as u8).unwrap()
|
PTEFlags::from_bits(self.bits as u8).unwrap()
|
||||||
}
|
}
|
||||||
|
///Check PTE valid
|
||||||
pub fn is_valid(&self) -> bool {
|
pub fn is_valid(&self) -> bool {
|
||||||
(self.flags() & PTEFlags::V) != PTEFlags::empty()
|
(self.flags() & PTEFlags::V) != PTEFlags::empty()
|
||||||
}
|
}
|
||||||
|
///Check PTE readable
|
||||||
pub fn readable(&self) -> bool {
|
pub fn readable(&self) -> bool {
|
||||||
(self.flags() & PTEFlags::R) != PTEFlags::empty()
|
(self.flags() & PTEFlags::R) != PTEFlags::empty()
|
||||||
}
|
}
|
||||||
|
///Check PTE writable
|
||||||
pub fn writable(&self) -> bool {
|
pub fn writable(&self) -> bool {
|
||||||
(self.flags() & PTEFlags::W) != PTEFlags::empty()
|
(self.flags() & PTEFlags::W) != PTEFlags::empty()
|
||||||
}
|
}
|
||||||
|
///Check PTE executable
|
||||||
pub fn executable(&self) -> bool {
|
pub fn executable(&self) -> bool {
|
||||||
(self.flags() & PTEFlags::X) != PTEFlags::empty()
|
(self.flags() & PTEFlags::X) != PTEFlags::empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
///Record root ppn and has the same lifetime as 1 and 2 level `PageTableEntry`
|
||||||
pub struct PageTable {
|
pub struct PageTable {
|
||||||
root_ppn: PhysPageNum,
|
root_ppn: PhysPageNum,
|
||||||
frames: Vec<FrameTracker>,
|
frames: Vec<FrameTracker>,
|
||||||
@ -59,6 +70,7 @@ pub struct PageTable {
|
|||||||
|
|
||||||
/// Assume that it won't oom when creating/mapping.
|
/// Assume that it won't oom when creating/mapping.
|
||||||
impl PageTable {
|
impl PageTable {
|
||||||
|
/// Create an empty `PageTable`
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let frame = frame_alloc().unwrap();
|
let frame = frame_alloc().unwrap();
|
||||||
PageTable {
|
PageTable {
|
||||||
@ -73,6 +85,7 @@ impl PageTable {
|
|||||||
frames: Vec::new(),
|
frames: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Find phsical address by virtual address, create a frame if not exist
|
||||||
fn find_pte_create(&mut self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> {
|
fn find_pte_create(&mut self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> {
|
||||||
let idxs = vpn.indexes();
|
let idxs = vpn.indexes();
|
||||||
let mut ppn = self.root_ppn;
|
let mut ppn = self.root_ppn;
|
||||||
@ -92,6 +105,7 @@ impl PageTable {
|
|||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
/// Find phsical address by virtual address
|
||||||
fn find_pte(&self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> {
|
fn find_pte(&self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> {
|
||||||
let idxs = vpn.indexes();
|
let idxs = vpn.indexes();
|
||||||
let mut ppn = self.root_ppn;
|
let mut ppn = self.root_ppn;
|
||||||
@ -110,20 +124,24 @@ impl PageTable {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
/// Create a mapping form `vpn` to `ppn`
|
||||||
pub fn map(&mut self, vpn: VirtPageNum, ppn: PhysPageNum, flags: PTEFlags) {
|
pub fn map(&mut self, vpn: VirtPageNum, ppn: PhysPageNum, flags: PTEFlags) {
|
||||||
let pte = self.find_pte_create(vpn).unwrap();
|
let pte = self.find_pte_create(vpn).unwrap();
|
||||||
assert!(!pte.is_valid(), "vpn {:?} is mapped before mapping", vpn);
|
assert!(!pte.is_valid(), "vpn {:?} is mapped before mapping", vpn);
|
||||||
*pte = PageTableEntry::new(ppn, flags | PTEFlags::V);
|
*pte = PageTableEntry::new(ppn, flags | PTEFlags::V);
|
||||||
}
|
}
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
/// Delete a mapping form `vpn`
|
||||||
pub fn unmap(&mut self, vpn: VirtPageNum) {
|
pub fn unmap(&mut self, vpn: VirtPageNum) {
|
||||||
let pte = self.find_pte(vpn).unwrap();
|
let pte = self.find_pte(vpn).unwrap();
|
||||||
assert!(pte.is_valid(), "vpn {:?} is invalid before unmapping", vpn);
|
assert!(pte.is_valid(), "vpn {:?} is invalid before unmapping", vpn);
|
||||||
*pte = PageTableEntry::empty();
|
*pte = PageTableEntry::empty();
|
||||||
}
|
}
|
||||||
|
/// Translate `VirtPageNum` to `PageTableEntry`
|
||||||
pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> {
|
pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> {
|
||||||
self.find_pte(vpn).map(|pte| *pte)
|
self.find_pte(vpn).map(|pte| *pte)
|
||||||
}
|
}
|
||||||
|
/// Translate `VirtAddr` to `PhysAddr`
|
||||||
pub fn translate_va(&self, va: VirtAddr) -> Option<PhysAddr> {
|
pub fn translate_va(&self, va: VirtAddr) -> Option<PhysAddr> {
|
||||||
self.find_pte(va.clone().floor()).map(|pte| {
|
self.find_pte(va.clone().floor()).map(|pte| {
|
||||||
let aligned_pa: PhysAddr = pte.ppn().into();
|
let aligned_pa: PhysAddr = pte.ppn().into();
|
||||||
@ -132,12 +150,13 @@ impl PageTable {
|
|||||||
(aligned_pa_usize + offset).into()
|
(aligned_pa_usize + offset).into()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Get root ppn
|
||||||
pub fn token(&self) -> usize {
|
pub fn token(&self) -> usize {
|
||||||
8usize << 60 | self.root_ppn.0
|
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]> {
|
pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Vec<&'static mut [u8]> {
|
||||||
let page_table = PageTable::from_token(token);
|
let page_table = PageTable::from_token(token);
|
||||||
let mut start = ptr as usize;
|
let mut start = ptr as usize;
|
||||||
let end = start + len;
|
let end = start + len;
|
||||||
@ -159,7 +178,7 @@ pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Vec<&
|
|||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a string from other address spaces into kernel space without an end `\0`.
|
/// Translate a pointer to a mutable u8 Vec end with `\0` through page table to a `String`
|
||||||
pub fn translated_str(token: usize, ptr: *const u8) -> String {
|
pub fn translated_str(token: usize, ptr: *const u8) -> String {
|
||||||
let page_table = PageTable::from_token(token);
|
let page_table = PageTable::from_token(token);
|
||||||
let mut string = String::new();
|
let mut string = String::new();
|
||||||
@ -179,6 +198,7 @@ pub fn translated_str(token: usize, ptr: *const u8) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
///Translate a generic through page table and return a reference
|
||||||
pub fn translated_ref<T>(token: usize, ptr: *const T) -> &'static T {
|
pub fn translated_ref<T>(token: usize, ptr: *const T) -> &'static T {
|
||||||
let page_table = PageTable::from_token(token);
|
let page_table = PageTable::from_token(token);
|
||||||
page_table
|
page_table
|
||||||
@ -186,7 +206,7 @@ pub fn translated_ref<T>(token: usize, ptr: *const T) -> &'static T {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.get_ref()
|
.get_ref()
|
||||||
}
|
}
|
||||||
|
///Translate a generic through page table and return a mutable reference
|
||||||
pub fn translated_refmut<T>(token: usize, ptr: *mut T) -> &'static mut T {
|
pub fn translated_refmut<T>(token: usize, ptr: *mut T) -> &'static mut T {
|
||||||
let page_table = PageTable::from_token(token);
|
let page_table = PageTable::from_token(token);
|
||||||
let va = ptr as usize;
|
let va = ptr as usize;
|
||||||
@ -195,15 +215,18 @@ pub fn translated_refmut<T>(token: usize, ptr: *mut T) -> &'static mut T {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.get_mut()
|
.get_mut()
|
||||||
}
|
}
|
||||||
|
///Array of u8 slice that user communicate with os
|
||||||
pub struct UserBuffer {
|
pub struct UserBuffer {
|
||||||
|
///U8 vec
|
||||||
pub buffers: Vec<&'static mut [u8]>,
|
pub buffers: Vec<&'static mut [u8]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserBuffer {
|
impl UserBuffer {
|
||||||
|
///Create a `UserBuffer` by parameter
|
||||||
pub fn new(buffers: Vec<&'static mut [u8]>) -> Self {
|
pub fn new(buffers: Vec<&'static mut [u8]>) -> Self {
|
||||||
Self { buffers }
|
Self { buffers }
|
||||||
}
|
}
|
||||||
|
///Length of `UserBuffer`
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
let mut total: usize = 0;
|
let mut total: usize = 0;
|
||||||
for b in self.buffers.iter() {
|
for b in self.buffers.iter() {
|
||||||
@ -224,7 +247,7 @@ impl IntoIterator for UserBuffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Iterator of `UserBuffer`
|
||||||
pub struct UserBufferIterator {
|
pub struct UserBufferIterator {
|
||||||
buffers: Vec<&'static mut [u8]>,
|
buffers: Vec<&'static mut [u8]>,
|
||||||
current_buffer: usize,
|
current_buffer: usize,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//! SBI call wrappers
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
|
|
||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
@ -11,7 +12,7 @@ const SBI_REMOTE_FENCE_I: usize = 5;
|
|||||||
const SBI_REMOTE_SFENCE_VMA: usize = 6;
|
const SBI_REMOTE_SFENCE_VMA: usize = 6;
|
||||||
const SBI_REMOTE_SFENCE_VMA_ASID: usize = 7;
|
const SBI_REMOTE_SFENCE_VMA_ASID: usize = 7;
|
||||||
const SBI_SHUTDOWN: usize = 8;
|
const SBI_SHUTDOWN: usize = 8;
|
||||||
|
/// general sbi call
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize {
|
fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize {
|
||||||
let mut ret;
|
let mut ret;
|
||||||
@ -26,19 +27,19 @@ fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize {
|
|||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
/// use sbi call to set timer
|
||||||
pub fn set_timer(timer: usize) {
|
pub fn set_timer(timer: usize) {
|
||||||
sbi_call(SBI_SET_TIMER, timer, 0, 0);
|
sbi_call(SBI_SET_TIMER, timer, 0, 0);
|
||||||
}
|
}
|
||||||
|
/// use sbi call to putchar in console (qemu uart handler)
|
||||||
pub fn console_putchar(c: usize) {
|
pub fn console_putchar(c: usize) {
|
||||||
sbi_call(SBI_CONSOLE_PUTCHAR, c, 0, 0);
|
sbi_call(SBI_CONSOLE_PUTCHAR, c, 0, 0);
|
||||||
}
|
}
|
||||||
|
/// use sbi call to getchar from console (qemu uart handler)
|
||||||
pub fn console_getchar() -> usize {
|
pub fn console_getchar() -> usize {
|
||||||
sbi_call(SBI_CONSOLE_GETCHAR, 0, 0, 0)
|
sbi_call(SBI_CONSOLE_GETCHAR, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
/// use sbi call to shutdown the kernel
|
||||||
pub fn shutdown() -> ! {
|
pub fn shutdown() -> ! {
|
||||||
sbi_call(SBI_SHUTDOWN, 0, 0, 0);
|
sbi_call(SBI_SHUTDOWN, 0, 0, 0);
|
||||||
panic!("It should shutdown!");
|
panic!("It should shutdown!");
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//! Synchronization and interior mutability primitives
|
||||||
mod up;
|
mod up;
|
||||||
|
|
||||||
pub use up::UPSafeCell;
|
pub use up::UPSafeCell;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//! Uniprocessor interior mutability primitives
|
||||||
use core::cell::{RefCell, RefMut};
|
use core::cell::{RefCell, RefMut};
|
||||||
|
|
||||||
/// Wrap a static data structure inside it so that we are
|
/// Wrap a static data structure inside it so that we are
|
||||||
@ -22,7 +23,7 @@ impl<T> UPSafeCell<T> {
|
|||||||
inner: RefCell::new(value),
|
inner: RefCell::new(value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Panic if the data has been borrowed.
|
/// Exclusive access inner data in UPSafeCell. Panic if the data has been borrowed.
|
||||||
pub fn exclusive_access(&self) -> RefMut<'_, T> {
|
pub fn exclusive_access(&self) -> RefMut<'_, T> {
|
||||||
self.inner.borrow_mut()
|
self.inner.borrow_mut()
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//! File and filesystem-related syscalls
|
||||||
use crate::fs::{open_file, OpenFlags};
|
use crate::fs::{open_file, OpenFlags};
|
||||||
use crate::mm::{translated_byte_buffer, translated_str, UserBuffer};
|
use crate::mm::{translated_byte_buffer, translated_str, UserBuffer};
|
||||||
use crate::task::{current_task, current_user_token};
|
use crate::task::{current_task, current_user_token};
|
||||||
|
@ -1,3 +1,14 @@
|
|||||||
|
//! 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_OPEN: usize = 56;
|
const SYSCALL_OPEN: usize = 56;
|
||||||
const SYSCALL_CLOSE: usize = 57;
|
const SYSCALL_CLOSE: usize = 57;
|
||||||
const SYSCALL_READ: usize = 63;
|
const SYSCALL_READ: usize = 63;
|
||||||
@ -15,7 +26,7 @@ mod process;
|
|||||||
|
|
||||||
use fs::*;
|
use fs::*;
|
||||||
use process::*;
|
use process::*;
|
||||||
|
/// handle syscall exception with `syscall_id` and other arguments
|
||||||
pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
|
pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
|
||||||
match syscall_id {
|
match syscall_id {
|
||||||
SYSCALL_OPEN => sys_open(args[0] as *const u8, args[1] as u32),
|
SYSCALL_OPEN => sys_open(args[0] as *const u8, args[1] as u32),
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
|
//! Implementation of [`TaskContext`]
|
||||||
use crate::trap::trap_return;
|
use crate::trap::trap_return;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
/// task context structure containing some registers
|
||||||
pub struct TaskContext {
|
pub struct TaskContext {
|
||||||
|
/// return address ( e.g. __restore ) of __switch ASM function
|
||||||
ra: usize,
|
ra: usize,
|
||||||
|
/// kernel stack pointer of app
|
||||||
sp: usize,
|
sp: usize,
|
||||||
|
/// s0-11 register, callee saved
|
||||||
s: [usize; 12],
|
s: [usize; 12],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskContext {
|
impl TaskContext {
|
||||||
|
/// init task context
|
||||||
pub fn zero_init() -> Self {
|
pub fn zero_init() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ra: 0,
|
ra: 0,
|
||||||
@ -15,6 +21,7 @@ impl TaskContext {
|
|||||||
s: [0; 12],
|
s: [0; 12],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// set Task Context{__restore ASM funciton: trap_return, sp: kstack_ptr, s: s_0..12}
|
||||||
pub fn goto_trap_return(kstack_ptr: usize) -> Self {
|
pub fn goto_trap_return(kstack_ptr: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ra: trap_return as usize,
|
ra: trap_return as usize,
|
||||||
|
@ -1,23 +1,27 @@
|
|||||||
|
//!Implementation of [`TaskManager`]
|
||||||
use super::TaskControlBlock;
|
use super::TaskControlBlock;
|
||||||
use crate::sync::UPSafeCell;
|
use crate::sync::UPSafeCell;
|
||||||
use alloc::collections::VecDeque;
|
use alloc::collections::VecDeque;
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use lazy_static::*;
|
use lazy_static::*;
|
||||||
|
///A array of `TaskControlBlock` that is thread-safe
|
||||||
pub struct TaskManager {
|
pub struct TaskManager {
|
||||||
ready_queue: VecDeque<Arc<TaskControlBlock>>,
|
ready_queue: VecDeque<Arc<TaskControlBlock>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple FIFO scheduler.
|
/// A simple FIFO scheduler.
|
||||||
impl TaskManager {
|
impl TaskManager {
|
||||||
|
///Creat an empty TaskManager
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ready_queue: VecDeque::new(),
|
ready_queue: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
///Add a task to `TaskManager`
|
||||||
pub fn add(&mut self, task: Arc<TaskControlBlock>) {
|
pub fn add(&mut self, task: Arc<TaskControlBlock>) {
|
||||||
self.ready_queue.push_back(task);
|
self.ready_queue.push_back(task);
|
||||||
}
|
}
|
||||||
|
///Remove the first task and return it,or `None` if `TaskManager` is empty
|
||||||
pub fn fetch(&mut self) -> Option<Arc<TaskControlBlock>> {
|
pub fn fetch(&mut self) -> Option<Arc<TaskControlBlock>> {
|
||||||
self.ready_queue.pop_front()
|
self.ready_queue.pop_front()
|
||||||
}
|
}
|
||||||
@ -27,11 +31,11 @@ lazy_static! {
|
|||||||
pub static ref TASK_MANAGER: UPSafeCell<TaskManager> =
|
pub static ref TASK_MANAGER: UPSafeCell<TaskManager> =
|
||||||
unsafe { UPSafeCell::new(TaskManager::new()) };
|
unsafe { UPSafeCell::new(TaskManager::new()) };
|
||||||
}
|
}
|
||||||
|
///Interface offered to add task
|
||||||
pub fn add_task(task: Arc<TaskControlBlock>) {
|
pub fn add_task(task: Arc<TaskControlBlock>) {
|
||||||
TASK_MANAGER.exclusive_access().add(task);
|
TASK_MANAGER.exclusive_access().add(task);
|
||||||
}
|
}
|
||||||
|
///Interface offered to pop the first task
|
||||||
pub fn fetch_task() -> Option<Arc<TaskControlBlock>> {
|
pub fn fetch_task() -> Option<Arc<TaskControlBlock>> {
|
||||||
TASK_MANAGER.exclusive_access().fetch()
|
TASK_MANAGER.exclusive_access().fetch()
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,42 @@
|
|||||||
|
//! 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 whole operating system.
|
||||||
|
//!
|
||||||
|
//! A single global instance of [`Processor`] called `PROCESSOR` monitors running
|
||||||
|
//! task(s) for each core.
|
||||||
|
//!
|
||||||
|
//! A single global instance of [`PidAllocator`] called `PID_ALLOCATOR` allocates
|
||||||
|
//! pid for user apps.
|
||||||
|
//!
|
||||||
|
//! Be careful when you see `__switch` ASM function in `switch.S`. Control flow around this function
|
||||||
|
//! might not be what you expect.
|
||||||
mod context;
|
mod context;
|
||||||
mod manager;
|
mod manager;
|
||||||
mod pid;
|
mod pid;
|
||||||
mod processor;
|
mod processor;
|
||||||
mod switch;
|
mod switch;
|
||||||
#[allow(clippy::module_inception)]
|
#[allow(clippy::module_inception)]
|
||||||
|
#[allow(rustdoc::private_intra_doc_links)]
|
||||||
mod task;
|
mod task;
|
||||||
|
|
||||||
use crate::fs::{open_file, OpenFlags};
|
use crate::fs::{open_file, OpenFlags};
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
pub use context::TaskContext;
|
pub use context::TaskContext;
|
||||||
use lazy_static::*;
|
use lazy_static::*;
|
||||||
use manager::fetch_task;
|
pub use manager::{fetch_task,TaskManager};
|
||||||
use switch::__switch;
|
use switch::__switch;
|
||||||
use task::{TaskControlBlock, TaskStatus};
|
use task::{TaskControlBlock, TaskStatus};
|
||||||
|
|
||||||
pub use manager::add_task;
|
pub use manager::add_task;
|
||||||
pub use pid::{pid_alloc, KernelStack, PidHandle};
|
pub use pid::{pid_alloc, KernelStack, PidHandle,PidAllocator};
|
||||||
pub use processor::{
|
pub use processor::{
|
||||||
current_task, current_trap_cx, current_user_token, run_tasks, schedule, take_current_task,
|
current_task, current_trap_cx, current_user_token, run_tasks, schedule, take_current_task,Processor
|
||||||
};
|
};
|
||||||
|
/// Suspend the current 'Running' task and run the next task in task list.
|
||||||
pub fn suspend_current_and_run_next() {
|
pub fn suspend_current_and_run_next() {
|
||||||
// There must be an application running.
|
// There must be an application running.
|
||||||
let task = take_current_task().unwrap();
|
let task = take_current_task().unwrap();
|
||||||
@ -37,7 +54,7 @@ pub fn suspend_current_and_run_next() {
|
|||||||
// jump to scheduling cycle
|
// jump to scheduling cycle
|
||||||
schedule(task_cx_ptr);
|
schedule(task_cx_ptr);
|
||||||
}
|
}
|
||||||
|
/// Exit the current 'Running' task and run the next task in task list.
|
||||||
pub fn exit_current_and_run_next(exit_code: i32) {
|
pub fn exit_current_and_run_next(exit_code: i32) {
|
||||||
// take from Processor
|
// take from Processor
|
||||||
let task = take_current_task().unwrap();
|
let task = take_current_task().unwrap();
|
||||||
@ -72,13 +89,14 @@ pub fn exit_current_and_run_next(exit_code: i32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
///Globle process that init user shell
|
||||||
pub static ref INITPROC: Arc<TaskControlBlock> = Arc::new({
|
pub static ref INITPROC: Arc<TaskControlBlock> = Arc::new({
|
||||||
let inode = open_file("initproc", OpenFlags::RDONLY).unwrap();
|
let inode = open_file("initproc", OpenFlags::RDONLY).unwrap();
|
||||||
let v = inode.read_all();
|
let v = inode.read_all();
|
||||||
TaskControlBlock::new(v.as_slice())
|
TaskControlBlock::new(v.as_slice())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
///Add init process to the manager
|
||||||
pub fn add_initproc() {
|
pub fn add_initproc() {
|
||||||
add_task(INITPROC.clone());
|
add_task(INITPROC.clone());
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,24 @@
|
|||||||
|
//!Implementation of [`PidAllocator`]
|
||||||
use crate::config::{KERNEL_STACK_SIZE, PAGE_SIZE, TRAMPOLINE};
|
use crate::config::{KERNEL_STACK_SIZE, PAGE_SIZE, TRAMPOLINE};
|
||||||
use crate::mm::{MapPermission, VirtAddr, KERNEL_SPACE};
|
use crate::mm::{MapPermission, VirtAddr, KERNEL_SPACE};
|
||||||
use crate::sync::UPSafeCell;
|
use crate::sync::UPSafeCell;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use lazy_static::*;
|
use lazy_static::*;
|
||||||
|
///Pid Allocator struct
|
||||||
struct PidAllocator {
|
pub struct PidAllocator {
|
||||||
current: usize,
|
current: usize,
|
||||||
recycled: Vec<usize>,
|
recycled: Vec<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PidAllocator {
|
impl PidAllocator {
|
||||||
|
///Create an empty `PidAllocator`
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
PidAllocator {
|
PidAllocator {
|
||||||
current: 0,
|
current: 0,
|
||||||
recycled: Vec::new(),
|
recycled: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
///Allocate a pid
|
||||||
pub fn alloc(&mut self) -> PidHandle {
|
pub fn alloc(&mut self) -> PidHandle {
|
||||||
if let Some(pid) = self.recycled.pop() {
|
if let Some(pid) = self.recycled.pop() {
|
||||||
PidHandle(pid)
|
PidHandle(pid)
|
||||||
@ -24,6 +27,7 @@ impl PidAllocator {
|
|||||||
PidHandle(self.current - 1)
|
PidHandle(self.current - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
///Recycle a pid
|
||||||
pub fn dealloc(&mut self, pid: usize) {
|
pub fn dealloc(&mut self, pid: usize) {
|
||||||
assert!(pid < self.current);
|
assert!(pid < self.current);
|
||||||
assert!(
|
assert!(
|
||||||
@ -36,10 +40,10 @@ impl PidAllocator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref PID_ALLOCATOR: UPSafeCell<PidAllocator> =
|
pub static ref PID_ALLOCATOR: UPSafeCell<PidAllocator> =
|
||||||
unsafe { UPSafeCell::new(PidAllocator::new()) };
|
unsafe { UPSafeCell::new(PidAllocator::new()) };
|
||||||
}
|
}
|
||||||
|
///Bind pid lifetime to `PidHandle`
|
||||||
pub struct PidHandle(pub usize);
|
pub struct PidHandle(pub usize);
|
||||||
|
|
||||||
impl Drop for PidHandle {
|
impl Drop for PidHandle {
|
||||||
@ -48,7 +52,7 @@ impl Drop for PidHandle {
|
|||||||
PID_ALLOCATOR.exclusive_access().dealloc(self.0);
|
PID_ALLOCATOR.exclusive_access().dealloc(self.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
///Allocate a pid from PID_ALLOCATOR
|
||||||
pub fn pid_alloc() -> PidHandle {
|
pub fn pid_alloc() -> PidHandle {
|
||||||
PID_ALLOCATOR.exclusive_access().alloc()
|
PID_ALLOCATOR.exclusive_access().alloc()
|
||||||
}
|
}
|
||||||
@ -59,12 +63,13 @@ pub fn kernel_stack_position(app_id: usize) -> (usize, usize) {
|
|||||||
let bottom = top - KERNEL_STACK_SIZE;
|
let bottom = top - KERNEL_STACK_SIZE;
|
||||||
(bottom, top)
|
(bottom, top)
|
||||||
}
|
}
|
||||||
|
///Kernelstack for app
|
||||||
pub struct KernelStack {
|
pub struct KernelStack {
|
||||||
pid: usize,
|
pid: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KernelStack {
|
impl KernelStack {
|
||||||
|
///Create a kernelstack from pid
|
||||||
pub fn new(pid_handle: &PidHandle) -> Self {
|
pub fn new(pid_handle: &PidHandle) -> Self {
|
||||||
let pid = pid_handle.0;
|
let pid = pid_handle.0;
|
||||||
let (kernel_stack_bottom, kernel_stack_top) = kernel_stack_position(pid);
|
let (kernel_stack_bottom, kernel_stack_top) = kernel_stack_position(pid);
|
||||||
@ -76,6 +81,7 @@ impl KernelStack {
|
|||||||
KernelStack { pid: pid_handle.0 }
|
KernelStack { pid: pid_handle.0 }
|
||||||
}
|
}
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
///Push a value on top of kernelstack
|
||||||
pub fn push_on_top<T>(&self, value: T) -> *mut T
|
pub fn push_on_top<T>(&self, value: T) -> *mut T
|
||||||
where
|
where
|
||||||
T: Sized,
|
T: Sized,
|
||||||
@ -87,6 +93,7 @@ impl KernelStack {
|
|||||||
}
|
}
|
||||||
ptr_mut
|
ptr_mut
|
||||||
}
|
}
|
||||||
|
///Get the value on the top of kernelstack
|
||||||
pub fn get_top(&self) -> usize {
|
pub fn get_top(&self) -> usize {
|
||||||
let (_, kernel_stack_top) = kernel_stack_position(self.pid);
|
let (_, kernel_stack_top) = kernel_stack_position(self.pid);
|
||||||
kernel_stack_top
|
kernel_stack_top
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//!Implementation of [`Processor`] and Intersection of control flow
|
||||||
use super::__switch;
|
use super::__switch;
|
||||||
use super::{fetch_task, TaskStatus};
|
use super::{fetch_task, TaskStatus};
|
||||||
use super::{TaskContext, TaskControlBlock};
|
use super::{TaskContext, TaskControlBlock};
|
||||||
@ -5,25 +6,31 @@ use crate::sync::UPSafeCell;
|
|||||||
use crate::trap::TrapContext;
|
use crate::trap::TrapContext;
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use lazy_static::*;
|
use lazy_static::*;
|
||||||
|
///Processor management structure
|
||||||
pub struct Processor {
|
pub struct Processor {
|
||||||
|
///The task currently executing on the current processor
|
||||||
current: Option<Arc<TaskControlBlock>>,
|
current: Option<Arc<TaskControlBlock>>,
|
||||||
|
///The basic control flow of each core, helping to select and switch process
|
||||||
idle_task_cx: TaskContext,
|
idle_task_cx: TaskContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Processor {
|
impl Processor {
|
||||||
|
///Create an empty Processor
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
current: None,
|
current: None,
|
||||||
idle_task_cx: TaskContext::zero_init(),
|
idle_task_cx: TaskContext::zero_init(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
///Get mutable reference to `idle_task_cx`
|
||||||
fn get_idle_task_cx_ptr(&mut self) -> *mut TaskContext {
|
fn get_idle_task_cx_ptr(&mut self) -> *mut TaskContext {
|
||||||
&mut self.idle_task_cx as *mut _
|
&mut self.idle_task_cx as *mut _
|
||||||
}
|
}
|
||||||
|
///Get current task in moving semanteme
|
||||||
pub fn take_current(&mut self) -> Option<Arc<TaskControlBlock>> {
|
pub fn take_current(&mut self) -> Option<Arc<TaskControlBlock>> {
|
||||||
self.current.take()
|
self.current.take()
|
||||||
}
|
}
|
||||||
|
///Get current task in cloning semanteme
|
||||||
pub fn current(&self) -> Option<Arc<TaskControlBlock>> {
|
pub fn current(&self) -> Option<Arc<TaskControlBlock>> {
|
||||||
self.current.as_ref().map(Arc::clone)
|
self.current.as_ref().map(Arc::clone)
|
||||||
}
|
}
|
||||||
@ -32,7 +39,8 @@ impl Processor {
|
|||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref PROCESSOR: UPSafeCell<Processor> = unsafe { UPSafeCell::new(Processor::new()) };
|
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() {
|
pub fn run_tasks() {
|
||||||
loop {
|
loop {
|
||||||
let mut processor = PROCESSOR.exclusive_access();
|
let mut processor = PROCESSOR.exclusive_access();
|
||||||
@ -53,28 +61,28 @@ pub fn run_tasks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
///Take the current task,leaving a None in its place
|
||||||
pub fn take_current_task() -> Option<Arc<TaskControlBlock>> {
|
pub fn take_current_task() -> Option<Arc<TaskControlBlock>> {
|
||||||
PROCESSOR.exclusive_access().take_current()
|
PROCESSOR.exclusive_access().take_current()
|
||||||
}
|
}
|
||||||
|
///Get running task
|
||||||
pub fn current_task() -> Option<Arc<TaskControlBlock>> {
|
pub fn current_task() -> Option<Arc<TaskControlBlock>> {
|
||||||
PROCESSOR.exclusive_access().current()
|
PROCESSOR.exclusive_access().current()
|
||||||
}
|
}
|
||||||
|
///Get token of the address space of current task
|
||||||
pub fn current_user_token() -> usize {
|
pub fn current_user_token() -> usize {
|
||||||
let task = current_task().unwrap();
|
let task = current_task().unwrap();
|
||||||
let token = task.inner_exclusive_access().get_user_token();
|
let token = task.inner_exclusive_access().get_user_token();
|
||||||
token
|
token
|
||||||
}
|
}
|
||||||
|
///Get the mutable reference to trap context of current task
|
||||||
pub fn current_trap_cx() -> &'static mut TrapContext {
|
pub fn current_trap_cx() -> &'static mut TrapContext {
|
||||||
current_task()
|
current_task()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.inner_exclusive_access()
|
.inner_exclusive_access()
|
||||||
.get_trap_cx()
|
.get_trap_cx()
|
||||||
}
|
}
|
||||||
|
///Return to idle control flow for new scheduling
|
||||||
pub fn schedule(switched_task_cx_ptr: *mut TaskContext) {
|
pub fn schedule(switched_task_cx_ptr: *mut TaskContext) {
|
||||||
let mut processor = PROCESSOR.exclusive_access();
|
let mut processor = PROCESSOR.exclusive_access();
|
||||||
let idle_task_cx_ptr = processor.get_idle_task_cx_ptr();
|
let idle_task_cx_ptr = processor.get_idle_task_cx_ptr();
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//!Wrap `switch.S` as a function
|
||||||
use super::TaskContext;
|
use super::TaskContext;
|
||||||
use core::arch::global_asm;
|
use core::arch::global_asm;
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//!Implementation of [`TaskControlBlock`]
|
||||||
use super::TaskContext;
|
use super::TaskContext;
|
||||||
use super::{pid_alloc, KernelStack, PidHandle};
|
use super::{pid_alloc, KernelStack, PidHandle};
|
||||||
use crate::config::TRAP_CONTEXT;
|
use crate::config::TRAP_CONTEXT;
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
|
|
||||||
|
//! RISC-V timer-related functionality
|
||||||
|
|
||||||
use crate::config::CLOCK_FREQ;
|
use crate::config::CLOCK_FREQ;
|
||||||
use crate::sbi::set_timer;
|
use crate::sbi::set_timer;
|
||||||
use riscv::register::time;
|
use riscv::register::time;
|
||||||
|
|
||||||
const TICKS_PER_SEC: usize = 100;
|
const TICKS_PER_SEC: usize = 100;
|
||||||
const MSEC_PER_SEC: usize = 1000;
|
const MSEC_PER_SEC: usize = 1000;
|
||||||
|
///get current time
|
||||||
pub fn get_time() -> usize {
|
pub fn get_time() -> usize {
|
||||||
time::read()
|
time::read()
|
||||||
}
|
}
|
||||||
|
/// get current time in microseconds
|
||||||
pub fn get_time_ms() -> usize {
|
pub fn get_time_ms() -> usize {
|
||||||
time::read() / (CLOCK_FREQ / MSEC_PER_SEC)
|
time::read() / (CLOCK_FREQ / MSEC_PER_SEC)
|
||||||
}
|
}
|
||||||
|
/// set the next timer interrupt
|
||||||
pub fn set_next_trigger() {
|
pub fn set_next_trigger() {
|
||||||
set_timer(get_time() + CLOCK_FREQ / TICKS_PER_SEC);
|
set_timer(get_time() + CLOCK_FREQ / TICKS_PER_SEC);
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,30 @@
|
|||||||
|
//! Implementation of [`TrapContext`]
|
||||||
use riscv::register::sstatus::{self, Sstatus, SPP};
|
use riscv::register::sstatus::{self, Sstatus, SPP};
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
///trap context structure containing sstatus, sepc and registers
|
||||||
pub struct TrapContext {
|
pub struct TrapContext {
|
||||||
|
/// general regs[0..31]
|
||||||
pub x: [usize; 32],
|
pub x: [usize; 32],
|
||||||
|
/// CSR sstatus
|
||||||
pub sstatus: Sstatus,
|
pub sstatus: Sstatus,
|
||||||
|
/// CSR sepc
|
||||||
pub sepc: usize,
|
pub sepc: usize,
|
||||||
|
/// Addr of Page Table
|
||||||
pub kernel_satp: usize,
|
pub kernel_satp: usize,
|
||||||
|
/// kernel stack
|
||||||
pub kernel_sp: usize,
|
pub kernel_sp: usize,
|
||||||
|
/// Addr of trap_handler function
|
||||||
pub trap_handler: usize,
|
pub trap_handler: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrapContext {
|
impl TrapContext {
|
||||||
|
///set stack pointer to x_2 reg (sp)
|
||||||
pub fn set_sp(&mut self, sp: usize) {
|
pub fn set_sp(&mut self, sp: usize) {
|
||||||
self.x[2] = sp;
|
self.x[2] = sp;
|
||||||
}
|
}
|
||||||
|
///init app context
|
||||||
pub fn app_init_context(
|
pub fn app_init_context(
|
||||||
entry: usize,
|
entry: usize,
|
||||||
sp: usize,
|
sp: usize,
|
||||||
|
@ -1,3 +1,16 @@
|
|||||||
|
//! Trap handling functionality
|
||||||
|
//!
|
||||||
|
//! For rCore, we have a single trap entry point, namely `__alltraps`. At
|
||||||
|
//! initialization in [`init()`], we set the `stvec` CSR to point to it.
|
||||||
|
//!
|
||||||
|
//! All traps go through `__alltraps`, which is defined in `trap.S`. The
|
||||||
|
//! assembly language code does just enough work restore the kernel space
|
||||||
|
//! context, ensuring that Rust code safely runs, and transfers control to
|
||||||
|
//! [`trap_handler()`].
|
||||||
|
//!
|
||||||
|
//! It then calls different functionality based on what exactly the exception
|
||||||
|
//! was. For example, timer interrupts trigger task preemption, and syscalls go
|
||||||
|
//! to [`syscall()`].
|
||||||
mod context;
|
mod context;
|
||||||
|
|
||||||
use crate::config::{TRAMPOLINE, TRAP_CONTEXT};
|
use crate::config::{TRAMPOLINE, TRAP_CONTEXT};
|
||||||
@ -14,7 +27,7 @@ use riscv::register::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
global_asm!(include_str!("trap.S"));
|
global_asm!(include_str!("trap.S"));
|
||||||
|
/// initialize CSR `stvec` as the entry of `__alltraps`
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
set_kernel_trap_entry();
|
set_kernel_trap_entry();
|
||||||
}
|
}
|
||||||
@ -30,7 +43,7 @@ fn set_user_trap_entry() {
|
|||||||
stvec::write(TRAMPOLINE as usize, TrapMode::Direct);
|
stvec::write(TRAMPOLINE as usize, TrapMode::Direct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// enable timer interrupt in sie CSR
|
||||||
pub fn enable_timer_interrupt() {
|
pub fn enable_timer_interrupt() {
|
||||||
unsafe {
|
unsafe {
|
||||||
sie::set_stimer();
|
sie::set_stimer();
|
||||||
@ -38,6 +51,7 @@ pub fn enable_timer_interrupt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
/// handle an interrupt, exception, or system call from user space
|
||||||
pub fn trap_handler() -> ! {
|
pub fn trap_handler() -> ! {
|
||||||
set_kernel_trap_entry();
|
set_kernel_trap_entry();
|
||||||
let scause = scause::read();
|
let scause = scause::read();
|
||||||
@ -90,6 +104,9 @@ pub fn trap_handler() -> ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
/// set the new addr of __restore asm function in TRAMPOLINE page,
|
||||||
|
/// set the reg a0 = trap_cx_ptr, reg a1 = phy addr of usr page table,
|
||||||
|
/// finally, jump to new addr of __restore asm function
|
||||||
pub fn trap_return() -> ! {
|
pub fn trap_return() -> ! {
|
||||||
set_user_trap_entry();
|
set_user_trap_entry();
|
||||||
let trap_cx_ptr = TRAP_CONTEXT;
|
let trap_cx_ptr = TRAP_CONTEXT;
|
||||||
@ -112,6 +129,8 @@ pub fn trap_return() -> ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
/// Unimplement: traps/interrupts/exceptions from kernel mode
|
||||||
|
/// Todo: Chapter 9: I/O device
|
||||||
pub fn trap_from_kernel() -> ! {
|
pub fn trap_from_kernel() -> ! {
|
||||||
use riscv::register::sepc;
|
use riscv::register::sepc;
|
||||||
println!("stval = {:#x}, sepc = {:#x}", stval::read(), sepc::read());
|
println!("stval = {:#x}, sepc = {:#x}", stval::read(), sepc::read());
|
||||||
|
Loading…
Reference in New Issue
Block a user