add page fault handler

This commit is contained in:
Jianfeng Jiang 2022-12-07 14:08:01 +08:00
parent 429341caa6
commit 52531c6cb6
11 changed files with 181 additions and 41 deletions

View File

@ -20,8 +20,9 @@ pub fn handle_exception(context: &mut CpuContext) {
fn handle_page_fault(trap_info: &TrapInformation) {
const PAGE_NOT_PRESENT_ERROR_MASK: u64 = 0x1 << 0;
const WRITE_ACCESS_MASK: u64 = 0x1 << 1;
if trap_info.err & PAGE_NOT_PRESENT_ERROR_MASK == 0 {
// If page is not present, we should ask the vmar try to commit this page
// TODO: If page is not present, we should ask the vmar try to commit this page
generate_fault_signal(trap_info)
} else {
// Otherwise, the page fault is caused by page protection error.

View File

@ -14,6 +14,7 @@
//! In Jinux, VMARs and VMOs, as well as other capabilities, are implemented
//! as zero-cost capabilities.
mod page_fault_handler;
mod perms;
mod vmar;
mod vmo;

View File

@ -0,0 +1,12 @@
use jinux_frame::vm::Vaddr;
use jinux_frame::Result;
/// This trait is implemented by structs which can handle a user space page fault.
/// In current implementation, they are vmars and vmos.
pub trait PageFaultHandler {
/// Handle a page fault at a specific addr. if write is true, means the page fault is caused by a write access,
/// otherwise, the page fault is caused by a read access.
/// If the page fault can be handled successfully, this function will return Ok(()).
/// Otherwise, this function will return Err.
fn handle_page_fault(&self, page_fault_addr: Vaddr, write: bool) -> Result<()>;
}

View File

@ -1,8 +1,14 @@
use alloc::sync::Arc;
use core::ops::Range;
use jinux_frame::{vm::VmIo, Error, Result};
use jinux_frame::{
vm::{Vaddr, VmIo},
Error, Result,
};
use crate::{rights::Rights, vm::vmo::Vmo};
use crate::{
rights::Rights,
vm::{page_fault_handler::PageFaultHandler, vmo::Vmo},
};
use super::{options::VmarChildOptions, vm_mapping::VmarMapOptions, VmPerms, Vmar, Vmar_};
@ -158,3 +164,14 @@ impl VmIo for Vmar<Rights> {
self.0.write(offset, buf)
}
}
impl PageFaultHandler for Vmar<Rights> {
fn handle_page_fault(&self, page_fault_addr: Vaddr, write: bool) -> Result<()> {
if write {
self.check_rights(Rights::WRITE)?;
} else {
self.check_rights(Rights::READ)?;
}
self.0.handle_page_fault(page_fault_addr, write)
}
}

View File

@ -21,6 +21,8 @@ use spin::Mutex;
use self::vm_mapping::VmMapping;
use super::page_fault_handler::PageFaultHandler;
/// Virtual Memory Address Regions (VMARs) are a type of capability that manages
/// user address spaces.
///
@ -51,6 +53,11 @@ use self::vm_mapping::VmMapping;
pub struct Vmar<R = Rights>(Arc<Vmar_>, R);
// TODO: how page faults can be delivered to and handled by the current VMAR.
impl<R> PageFaultHandler for Vmar<R> {
default fn handle_page_fault(&self, page_fault_addr: Vaddr, write: bool) -> Result<()> {
unimplemented!()
}
}
pub(super) struct Vmar_ {
/// vmar inner
@ -77,12 +84,16 @@ struct VmarInner {
free_regions: BTreeMap<Vaddr, FreeRegion>,
}
// FIXME: How to set the correct root vmar range?
// We should not include addr 0 here(is this right?), since the 0 addr means the null pointer.
// We should include addr 0x0040_0000, since non-pie executables typically are put on 0x0040_0000.
pub const ROOT_VMAR_LOWEST_ADDR: Vaddr = 0x0010_0000;
pub const ROOT_VMAR_HIGHEST_ADDR: Vaddr = 0x1000_0000_0000;
impl Vmar_ {
pub fn new_root() -> Result<Self> {
let mut free_regions = BTreeMap::new();
let root_region = FreeRegion::new(0..ROOT_VMAR_HIGHEST_ADDR);
let root_region = FreeRegion::new(ROOT_VMAR_LOWEST_ADDR..ROOT_VMAR_HIGHEST_ADDR);
free_regions.insert(root_region.start(), root_region);
let vmar_inner = VmarInner {
is_destroyed: false,
@ -156,6 +167,34 @@ impl Vmar_ {
Ok(())
}
/// Handle user space page fault, if the page fault is successfully handled ,return Ok(()).
pub fn handle_page_fault(&self, page_fault_addr: Vaddr, write: bool) -> Result<()> {
if page_fault_addr < self.base || page_fault_addr >= self.base + self.size {
return Err(Error::AccessDenied);
}
let inner = self.inner.lock();
for (child_vmar_base, child_vmar) in &inner.child_vmar_s {
if *child_vmar_base <= page_fault_addr
&& page_fault_addr < *child_vmar_base + child_vmar.size
{
return child_vmar.handle_page_fault(page_fault_addr, write);
}
}
// FIXME: If multiple vmos are mapped to the addr, should we allow all vmos to handle page fault?
for (vm_mapping_base, vm_mapping) in &inner.mapped_vmos {
if *vm_mapping_base <= page_fault_addr
&& page_fault_addr <= *vm_mapping_base + vm_mapping.size()
{
return vm_mapping.handle_page_fault(page_fault_addr, write);
}
}
return Err(Error::AccessDenied);
}
pub fn destroy_all(&self) -> Result<()> {
todo!()
}

View File

@ -1,12 +1,15 @@
use core::ops::Range;
use alloc::sync::Arc;
use jinux_frame::{vm::VmIo, Error, Result};
use jinux_frame::{
vm::{Vaddr, VmIo},
Error, Result,
};
use jinux_rights_proc::require;
use crate::{
rights::{Dup, Rights, TRights},
vm::vmo::Vmo,
vm::{page_fault_handler::PageFaultHandler, vmo::Vmo},
};
use super::{options::VmarChildOptions, vm_mapping::VmarMapOptions, VmPerms, Vmar, Vmar_};
@ -171,3 +174,14 @@ impl<R: TRights> VmIo for Vmar<R> {
self.0.write(offset, buf)
}
}
impl<R: TRights> PageFaultHandler for Vmar<R> {
fn handle_page_fault(&self, page_fault_addr: Vaddr, write: bool) -> Result<()> {
if write {
self.check_rights(Rights::WRITE)?;
} else {
self.check_rights(Rights::READ)?;
}
self.0.handle_page_fault(page_fault_addr, write)
}
}

View File

@ -4,18 +4,18 @@ use alloc::{
};
use jinux_frame::{
config::PAGE_SIZE,
vm::{Vaddr, VmFrameVec, VmPerm},
vm::{Vaddr, VmFrameVec, VmIo, VmPerm},
Error,
};
use jinux_frame::{vm::VmMapOptions, Result};
use spin::Mutex;
use crate::vm::vmo::{Pager, Vmo, Vmo_};
use crate::vm::{page_fault_handler::PageFaultHandler, vmo::Vmo};
use super::{Vmar, Vmar_};
use crate::vm::perms::VmPerms;
use crate::vm::vmar::Rights;
use crate::vm::vmo::VmoRights;
use crate::vm::vmo::VmoRightsOp;
/// A VmMapping represents mapping a vmo into a vmar.
/// A vmar can has multiple VmMappings, which means multiple vmos are mapped to a vmar.
@ -24,8 +24,8 @@ use crate::vm::vmo::VmoRights;
pub struct VmMapping {
/// The parent vmar. The parent should always point to a valid vmar.
parent: Weak<Vmar_>,
/// The mapped vmo.
vmo: Arc<Vmo_>,
/// The mapped vmo. The mapped vmo is with dynamic capability.
vmo: Vmo<Rights>,
/// The mao offset of the vmo, in bytes.
vmo_offset: usize,
/// The size of mapping, in bytes. The map size can even be larger than the size of backup vmo.
@ -54,8 +54,8 @@ impl VmMapping {
can_overwrite,
} = option;
let Vmar(parent_vmar, _) = parent;
let vmo_ = vmo.0;
let vmo_size = vmo_.size();
let vmo = vmo.to_dyn();
let vmo_size = vmo.size();
let map_to_addr = parent_vmar.allocate_free_region_for_vmo(
vmo_size,
size,
@ -79,8 +79,8 @@ impl VmMapping {
vm_map_options.perm(perm);
vm_map_options.can_overwrite(can_overwrite);
vm_map_options.align(align);
if vmo_.page_commited(page_idx) {
vmo_.map_page(page_idx, &vm_space, vm_map_options)?;
if vmo.page_commited(page_idx) {
vmo.map_page(page_idx, &vm_space, vm_map_options)?;
mapped_pages.insert(page_idx);
} else {
// The page is not committed. We simple record the map options for further mapping.
@ -89,7 +89,7 @@ impl VmMapping {
}
Ok(Self {
parent: Arc::downgrade(&parent_vmar),
vmo: vmo_,
vmo,
vmo_offset,
map_size: size,
map_to_addr,
@ -137,6 +137,11 @@ impl VmMapping {
let vmo_write_offset = self.vmo_offset + offset;
self.vmo.write_bytes(vmo_write_offset, buf)
}
pub fn handle_page_fault(&self, page_fault_addr: Vaddr, write: bool) -> Result<()> {
let vmo_offset = self.vmo_offset + page_fault_addr - self.map_to_addr;
self.vmo.handle_page_fault(vmo_offset, write)
}
}
/// Options for creating a new mapping. The mapping is not allowed to overlap

View File

@ -5,7 +5,7 @@ use jinux_frame::{vm::VmIo, Error};
use crate::rights::{Rights, TRights};
use super::VmoRights;
use super::VmoRightsOp;
use super::{
options::{VmoCowChild, VmoSliceChild},
Vmo, VmoChildOptions,
@ -142,12 +142,6 @@ impl Vmo<Rights> {
}
}
impl VmoRights for Vmo<Rights> {
fn rights(&self) -> Rights {
self.1
}
}
impl VmIo for Vmo<Rights> {
fn read_bytes(&self, offset: usize, buf: &mut [u8]) -> Result<()> {
self.check_rights(Rights::READ)?;
@ -159,3 +153,15 @@ impl VmIo for Vmo<Rights> {
self.0.write_bytes(offset, buf)
}
}
impl VmoRightsOp for Vmo<Rights> {
fn rights(&self) -> Rights {
self.1
}
/// Converts to a dynamic capability.
fn to_dyn(self) -> Vmo<Rights> {
let rights = self.rights();
Vmo(self.0, rights)
}
}

View File

@ -22,7 +22,8 @@ pub use options::{VmoChildOptions, VmoOptions};
pub use pager::Pager;
use spin::Mutex;
use super::vmar::vm_mapping::{self, VmMapping};
use super::page_fault_handler::PageFaultHandler;
use super::vmar::vm_mapping::VmMapping;
/// Virtual Memory Objects (VMOs) are a type of capability that represents a
/// range of memory pages.
@ -82,7 +83,7 @@ use super::vmar::vm_mapping::{self, VmMapping};
pub struct Vmo<R = Rights>(pub(super) Arc<Vmo_>, R);
/// Functions exist both for static capbility and dynamic capibility
pub trait VmoRights {
pub trait VmoRightsOp {
/// Returns the access rights.
fn rights(&self) -> Rights;
@ -94,14 +95,37 @@ pub trait VmoRights {
Err(Error::AccessDenied)
}
}
/// Converts to a dynamic capability.
fn to_dyn(self) -> Vmo<Rights>
where
Self: Sized;
}
// We implement this trait for Vmo, so we can use functions on type like Vmo<R> without trait bounds.
// FIXME: This requires the imcomplete feature specialization, which should be fixed further.
impl<R> VmoRights for Vmo<R> {
impl<R> VmoRightsOp for Vmo<R> {
default fn rights(&self) -> Rights {
unimplemented!()
}
default fn to_dyn(self) -> Vmo<Rights>
where
Self: Sized,
{
unimplemented!()
}
}
impl<R> PageFaultHandler for Vmo<R> {
default fn handle_page_fault(&self, page_fault_addr: Vaddr, write: bool) -> Result<()> {
if write {
self.check_rights(Rights::WRITE)?;
} else {
self.check_rights(Rights::READ)?;
}
self.0.handle_page_fault(page_fault_addr, write)
}
}
bitflags! {
@ -155,10 +179,6 @@ struct VmoInner {
inherited_pages: InheritedPages,
/// The current mapping on this vmo. The vmo can be mapped to multiple vmars.
mappings: Vec<Weak<VmMapping>>,
// Pages should be filled with zeros when committed. When create COW child, the pages exceed the range of parent vmo
// should be in this set. According to the on demand requirement, when read or write these pages for the first time,
// we should commit these pages and zeroed these pages.
// pages_should_fill_zeros: BTreeSet<usize>,
}
/// Pages inherited from parent
@ -273,6 +293,16 @@ impl Vmo_ {
Ok(())
}
/// Handle page fault.
pub fn handle_page_fault(&self, offset: usize, write: bool) -> Result<()> {
if offset >= self.size() {
return Err(Error::AccessDenied);
}
let page_idx = offset / PAGE_SIZE;
self.ensure_page_exists(page_idx, write)?;
Ok(())
}
/// determine whether a page is commited
pub fn page_commited(&self, page_idx: usize) -> bool {
self.inner.lock().committed_pages.contains_key(&page_idx)
@ -283,7 +313,7 @@ impl Vmo_ {
&self,
page_idx: usize,
vm_space: &VmSpace,
options: VmMapOptions,
map_options: VmMapOptions,
) -> Result<Vaddr> {
debug_assert!(self.page_commited(page_idx));
if !self.page_commited(page_idx) {
@ -296,7 +326,7 @@ impl Vmo_ {
.get(&page_idx)
.unwrap()
.clone();
vm_space.map(frames, &options)
vm_space.map(frames, &map_options)
}
pub fn add_mapping(&self, mapping: Weak<VmMapping>) {
@ -440,4 +470,19 @@ impl<R> Vmo<R> {
pub fn flags(&self) -> VmoFlags {
self.0.flags()
}
/// return whether a page is already committed
pub fn page_commited(&self, page_idx: usize) -> bool {
self.0.page_commited(page_idx)
}
/// map a committed page, returns the map address
pub fn map_page(
&self,
page_idx: usize,
vm_space: &VmSpace,
map_options: VmMapOptions,
) -> Result<Vaddr> {
self.0.map_page(page_idx, vm_space, map_options)
}
}

View File

@ -19,7 +19,7 @@ use crate::vm::vmo::InheritedPages;
use crate::vm::vmo::VmoType;
use crate::vm::vmo::{VmoInner, Vmo_};
use super::VmoRights;
use super::VmoRightsOp;
use super::{Pager, Vmo, VmoFlags};
/// Options for allocating a root VMO.

View File

@ -6,7 +6,7 @@ use jinux_rights_proc::require;
use crate::rights::*;
use super::VmoRights;
use super::VmoRightsOp;
use super::{
options::{VmoCowChild, VmoSliceChild},
Vmo, VmoChildOptions,
@ -134,12 +134,6 @@ impl<R: TRights> Vmo<R> {
pub fn restrict<R1: TRights>(self) -> Vmo<R1> {
Vmo(self.0, R1::new())
}
/// Converts to a dynamic capability.
pub fn to_dyn(self) -> Vmo<Rights> {
let rights = self.rights();
Vmo(self.0, rights)
}
}
impl<R: TRights> VmIo for Vmo<R> {
@ -154,8 +148,14 @@ impl<R: TRights> VmIo for Vmo<R> {
}
}
impl<R: TRights> VmoRights for Vmo<R> {
impl<R: TRights> VmoRightsOp for Vmo<R> {
fn rights(&self) -> Rights {
Rights::from_bits(R::BITS).unwrap()
}
/// Converts to a dynamic capability.
fn to_dyn(self) -> Vmo<Rights> {
let rights = self.rights();
Vmo(self.0, rights)
}
}