From 52531c6cb6e979bb5abcf07a2a133bbfaf643a0e Mon Sep 17 00:00:00 2001 From: Jianfeng Jiang Date: Wed, 7 Dec 2022 14:08:01 +0800 Subject: [PATCH] add page fault handler --- .../libs/jinux-std/src/process/exception.rs | 3 +- src/services/libs/jinux-std/src/vm/mod.rs | 1 + .../jinux-std/src/vm/page_fault_handler.rs | 12 ++++ .../libs/jinux-std/src/vm/vmar/dyn_cap.rs | 21 ++++++- .../libs/jinux-std/src/vm/vmar/mod.rs | 41 +++++++++++- .../libs/jinux-std/src/vm/vmar/static_cap.rs | 18 +++++- .../libs/jinux-std/src/vm/vmar/vm_mapping.rs | 25 +++++--- .../libs/jinux-std/src/vm/vmo/dyn_cap.rs | 20 +++--- src/services/libs/jinux-std/src/vm/vmo/mod.rs | 63 ++++++++++++++++--- .../libs/jinux-std/src/vm/vmo/options.rs | 2 +- .../libs/jinux-std/src/vm/vmo/static_cap.rs | 16 ++--- 11 files changed, 181 insertions(+), 41 deletions(-) create mode 100644 src/services/libs/jinux-std/src/vm/page_fault_handler.rs diff --git a/src/services/libs/jinux-std/src/process/exception.rs b/src/services/libs/jinux-std/src/process/exception.rs index b280ebbe..7182d1e6 100644 --- a/src/services/libs/jinux-std/src/process/exception.rs +++ b/src/services/libs/jinux-std/src/process/exception.rs @@ -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. diff --git a/src/services/libs/jinux-std/src/vm/mod.rs b/src/services/libs/jinux-std/src/vm/mod.rs index 6538c5b9..7230a842 100644 --- a/src/services/libs/jinux-std/src/vm/mod.rs +++ b/src/services/libs/jinux-std/src/vm/mod.rs @@ -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; diff --git a/src/services/libs/jinux-std/src/vm/page_fault_handler.rs b/src/services/libs/jinux-std/src/vm/page_fault_handler.rs new file mode 100644 index 00000000..e7a63f04 --- /dev/null +++ b/src/services/libs/jinux-std/src/vm/page_fault_handler.rs @@ -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<()>; +} diff --git a/src/services/libs/jinux-std/src/vm/vmar/dyn_cap.rs b/src/services/libs/jinux-std/src/vm/vmar/dyn_cap.rs index 5c657ef2..8a92d719 100644 --- a/src/services/libs/jinux-std/src/vm/vmar/dyn_cap.rs +++ b/src/services/libs/jinux-std/src/vm/vmar/dyn_cap.rs @@ -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 { self.0.write(offset, buf) } } + +impl PageFaultHandler for Vmar { + 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) + } +} diff --git a/src/services/libs/jinux-std/src/vm/vmar/mod.rs b/src/services/libs/jinux-std/src/vm/vmar/mod.rs index c246d2c5..215cf2cf 100644 --- a/src/services/libs/jinux-std/src/vm/vmar/mod.rs +++ b/src/services/libs/jinux-std/src/vm/vmar/mod.rs @@ -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(Arc, R); // TODO: how page faults can be delivered to and handled by the current VMAR. +impl PageFaultHandler for Vmar { + 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, } +// 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 { 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!() } diff --git a/src/services/libs/jinux-std/src/vm/vmar/static_cap.rs b/src/services/libs/jinux-std/src/vm/vmar/static_cap.rs index 2fbe2ba4..d50187ef 100644 --- a/src/services/libs/jinux-std/src/vm/vmar/static_cap.rs +++ b/src/services/libs/jinux-std/src/vm/vmar/static_cap.rs @@ -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 VmIo for Vmar { self.0.write(offset, buf) } } + +impl PageFaultHandler for Vmar { + 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) + } +} diff --git a/src/services/libs/jinux-std/src/vm/vmar/vm_mapping.rs b/src/services/libs/jinux-std/src/vm/vmar/vm_mapping.rs index 0ad96f03..42e5d74b 100644 --- a/src/services/libs/jinux-std/src/vm/vmar/vm_mapping.rs +++ b/src/services/libs/jinux-std/src/vm/vmar/vm_mapping.rs @@ -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, - /// The mapped vmo. - vmo: Arc, + /// The mapped vmo. The mapped vmo is with dynamic capability. + vmo: Vmo, /// 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 diff --git a/src/services/libs/jinux-std/src/vm/vmo/dyn_cap.rs b/src/services/libs/jinux-std/src/vm/vmo/dyn_cap.rs index 3ee8413d..b8ad4ef2 100644 --- a/src/services/libs/jinux-std/src/vm/vmo/dyn_cap.rs +++ b/src/services/libs/jinux-std/src/vm/vmo/dyn_cap.rs @@ -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 { } } -impl VmoRights for Vmo { - fn rights(&self) -> Rights { - self.1 - } -} - impl VmIo for Vmo { fn read_bytes(&self, offset: usize, buf: &mut [u8]) -> Result<()> { self.check_rights(Rights::READ)?; @@ -159,3 +153,15 @@ impl VmIo for Vmo { self.0.write_bytes(offset, buf) } } + +impl VmoRightsOp for Vmo { + fn rights(&self) -> Rights { + self.1 + } + + /// Converts to a dynamic capability. + fn to_dyn(self) -> Vmo { + let rights = self.rights(); + Vmo(self.0, rights) + } +} diff --git a/src/services/libs/jinux-std/src/vm/vmo/mod.rs b/src/services/libs/jinux-std/src/vm/vmo/mod.rs index 43b43259..e8ebc3c1 100644 --- a/src/services/libs/jinux-std/src/vm/vmo/mod.rs +++ b/src/services/libs/jinux-std/src/vm/vmo/mod.rs @@ -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(pub(super) Arc, 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 + where + Self: Sized; } // We implement this trait for Vmo, so we can use functions on type like Vmo without trait bounds. // FIXME: This requires the imcomplete feature specialization, which should be fixed further. -impl VmoRights for Vmo { +impl VmoRightsOp for Vmo { default fn rights(&self) -> Rights { unimplemented!() } + + default fn to_dyn(self) -> Vmo + where + Self: Sized, + { + unimplemented!() + } +} + +impl PageFaultHandler for Vmo { + 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>, - // 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, } /// 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 { 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) { @@ -440,4 +470,19 @@ impl Vmo { 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 { + self.0.map_page(page_idx, vm_space, map_options) + } } diff --git a/src/services/libs/jinux-std/src/vm/vmo/options.rs b/src/services/libs/jinux-std/src/vm/vmo/options.rs index 7eb54cc2..e21e2903 100644 --- a/src/services/libs/jinux-std/src/vm/vmo/options.rs +++ b/src/services/libs/jinux-std/src/vm/vmo/options.rs @@ -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. diff --git a/src/services/libs/jinux-std/src/vm/vmo/static_cap.rs b/src/services/libs/jinux-std/src/vm/vmo/static_cap.rs index 28c1210b..8dacd526 100644 --- a/src/services/libs/jinux-std/src/vm/vmo/static_cap.rs +++ b/src/services/libs/jinux-std/src/vm/vmo/static_cap.rs @@ -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 Vmo { pub fn restrict(self) -> Vmo { Vmo(self.0, R1::new()) } - - /// Converts to a dynamic capability. - pub fn to_dyn(self) -> Vmo { - let rights = self.rights(); - Vmo(self.0, rights) - } } impl VmIo for Vmo { @@ -154,8 +148,14 @@ impl VmIo for Vmo { } } -impl VmoRights for Vmo { +impl VmoRightsOp for Vmo { fn rights(&self) -> Rights { Rights::from_bits(R::BITS).unwrap() } + + /// Converts to a dynamic capability. + fn to_dyn(self) -> Vmo { + let rights = self.rights(); + Vmo(self.0, rights) + } }