Skip to main content

VmSpace

Struct VmSpace 

Source
pub struct VmSpace<'a> {
    pub pt: PageTable<UserPtConfig>,
    pub cpus: AtomicCpuSet,
    pub _marker: PhantomData<&'a ()>,
}
Expand description

A virtual address space for user-mode tasks, enabling safe manipulation of user-space memory.

The VmSpace type provides memory isolation guarantees between user-space and kernel-space. For example, given an arbitrary user-space pointer, one can read and write the memory location referred to by the user-space pointer without the risk of breaking the memory safety of the kernel space.

§Task Association Semantics

As far as OSTD is concerned, a VmSpace is not necessarily associated with a task. Once a VmSpace is activated (see [VmSpace::activate]), it remains activated until another VmSpace is activated possibly by another task running on the same CPU.

This means that it’s up to the kernel to ensure that a task’s VmSpace is always activated while the task is running. This can be done by using the injected post schedule handler (see inject_post_schedule_handler) to always activate the correct VmSpace after each context switch.

If the kernel otherwise decides not to ensure that the running task’s VmSpace is always activated, the kernel must deal with race conditions when calling methods that require the VmSpace to be activated, e.g., UserMode::execute, VmReader and VmWriter. Otherwise, the behavior is unspecified, though it’s guaranteed not to compromise the kernel’s memory safety.

§Memory Backing

A newly-created VmSpace is not backed by any physical memory pages. To provide memory pages for a VmSpace, one can allocate and map physical memory (UFrames) to the VmSpace using the cursor.

A VmSpace can also attach a page fault handler, which will be invoked to handle page faults generated from user space.

§Verification Design

A VmSpace has a corresponding VmSpaceOwner object that is used to track its state, and against which its invariants are stated. The VmSpaceOwner catalogues the readers and writers that are associated with the VmSpace, and the MemView which encodes the active page table and the subset of the TLB that covers the same virtual address space. All proofs about the correctness of the readers and writers are founded on the well-formedness of the MemView:

open spec fn mem_view_wf(self) -> bool {
   &&& self.mem_view is Some <==> self.mv_range@ is Some
   // This requires that TotalMapping (mvv) = mv ∪ writer mappings ∪ reader mappings
   &&& self.mem_view matches Some(remaining_view)
       ==> self.mv_range@ matches Some(total_view)
       ==> {
       &&& remaining_view.mappings_are_disjoint()
       &&& remaining_view.mappings.finite()
       &&& total_view.mappings_are_disjoint()
       &&& total_view.mappings.finite()
       // ======================
       // Remaining Consistency
       // ======================
       &&& remaining_view.mappings.subset_of(total_view.mappings)
       &&& remaining_view.memory.dom().subset_of(
           total_view.memory.dom(),
       )
       // =====================
       // Total View Consistency
       // =====================
       &&& forall|va: usize|
           remaining_view.addr_transl(va) == total_view.addr_transl(
               va,
           )
       // =====================
       // Writer correctness
       // =====================
       &&& forall|i: int|
           0 <= i < self.writers.len() as int ==> {
               &&& self.writers[i].inv()
           }
       }
   }
}

Fields§

§pt: PageTable<UserPtConfig>§cpus: AtomicCpuSet§_marker: PhantomData<&'a ()>

Implementations§

Source§

impl<'a> VmSpace<'a>

Source

pub open spec fn reader_success_cond(self, vaddr: Vaddr, len: usize) -> bool

{
    &&& vaddr != 0 && len > 0 && vaddr + len <= MAX_USERSPACE_VADDR
    &&& current_page_table_paddr_spec() == self.pt.root_paddr_spec()

}
Source

pub open spec fn writer_requires( &self, vm_owner: VmSpaceOwner<'a>, vaddr: Vaddr, len: usize, ) -> bool

{
    &&& vm_owner.inv()

}
Source

pub open spec fn writer_success_cond(self, vaddr: Vaddr, len: usize) -> bool

{
    &&& vaddr != 0 && len > 0 && vaddr + len <= MAX_USERSPACE_VADDR
    &&& current_page_table_paddr_spec() == self.pt.root_paddr_spec()

}
Source§

impl<'a> VmSpace<'a>

Source

pub exec fn new() -> r : Self

with
Tracked(regions): Tracked<&mut MetaRegionOwners>,
Tracked(guards_k): Tracked<&mut Guards<'static, KernelPtConfig>>,
Tracked(guards_u): Tracked<&mut Guards<'static, UserPtConfig>>,
requires
old(regions).inv(),

Creates a new VM address space.

This allocates a new user page table by duplicating the kernel page table’s top-level entries, and returns a VmSpace that wraps it.

§Verified Properties
§Preconditions
  • Safety Invariants: The meta-region invariants must hold.
§Postconditions
Source

pub exec fn cursor<G: InAtomicMode>( &'a self, guard: &'a G, va: &Range<Vaddr>, ) -> r : Result<Cursor<'a, G>, Error>

with
Tracked(owner): Tracked<PageTableOwner<UserPtConfig>>,
Tracked(guard_perm): Tracked<GuardPerm<'a, UserPtConfig>>,
Tracked(regions): Tracked<&mut MetaRegionOwners>,
Tracked(guards): Tracked<&mut Guards<'a, UserPtConfig>>,
->
cursor_owner: Tracked<Option<CursorOwner<'a, UserPtConfig>>>,
requires
owner.inv(),
ensures
crate::mm::page_table::Cursor::<UserPtConfig, G>::cursor_new_success_conditions(va)
    ==> (r matches Ok(_) && cursor_owner@ matches Some(_)),

Gets an immutable cursor in the virtual address range.

The cursor behaves like a lock guard, exclusively owning a sub-tree of the page table, preventing others from creating a cursor in it. So be sure to drop the cursor as soon as possible.

The creation of the cursor may block if another cursor having an overlapping range is alive.

§Verified Properties
§Preconditions
  • Safety Invariants: The page table owner must be valid.
§Postconditions
Source

pub exec fn cursor_mut<G: InAtomicMode>( &'a self, guard: &'a G, va: &Range<Vaddr>, ) -> r : Result<CursorMut<'a, G>, Error>

with
Tracked(owner): Tracked<PageTableOwner<UserPtConfig>>,
Tracked(guard_perm): Tracked<GuardPerm<'a, UserPtConfig>>,
Tracked(regions): Tracked<&mut MetaRegionOwners>,
Tracked(guards): Tracked<&mut Guards<'a, UserPtConfig>>,
->
cursor_owner: Tracked<Option<CursorOwner<'a, UserPtConfig>>>,
ensures
crate::mm::page_table::Cursor::<UserPtConfig, G>::cursor_new_success_conditions(va)
    ==> (r matches Ok(_) && cursor_owner@ matches Some(_)),

Gets a mutable cursor in the virtual address range.

The same as Self::cursor, the cursor behaves like a lock guard, exclusively owning a sub-tree of the page table, preventing others from creating a cursor in it. So be sure to drop the cursor as soon as possible.

The creation of the cursor may block if another cursor having an overlapping range is alive. The modification to the mapping by the cursor may also block or be overridden by the mapping of another cursor.

§Verified Properties
§Preconditions
  • Safety Invariants: The page table owner must be valid.
§Postconditions
Source

pub exec fn reader(&self, vaddr: Vaddr, len: usize) -> r : Result<VmReader<'a>, Error>

with
Tracked(owner): Tracked<&'a mut VmSpaceOwner<'a>>,
->
reader_owner: Tracked<Option<VmIoOwner<'a>>>,
requires
old(owner).inv(),
ensures
final(owner).inv(),
self.reader_success_cond(vaddr, len) ==> r is Ok && reader_owner@ is Some,
r is Ok && reader_owner@ is Some
    ==> {
        &&& r.unwrap().wf(reader_owner@.unwrap())
        &&& reader_owner@.unwrap().mem_view is None

    },

Creates a reader to read data from the user space of the current task.

Returns Err if this VmSpace is not the user space of the current task or the vaddr and len do not represent a valid user space memory range.

§Verified Properties
§Preconditions
§Postconditions
§Safety
  • The function does not interact with the lower-level memory system directly. By checking that the target (user) page table is not the active (kernel) one, we ensure that the resulting reader cannot interact with kernel memory.
Source

pub exec fn writer(self, vaddr: Vaddr, len: usize) -> r : Result<VmWriter<'a>, Error>

with
Tracked(owner): Tracked<&mut VmSpaceOwner<'a>>,
->
writer_owner: Tracked<Option<VmIoOwner<'a>>>,
requires
old(owner).inv(),
ensures
final(owner).inv(),
self.writer_success_cond(vaddr, len) ==> r is Ok && writer_owner@ is Some,
r is Ok && writer_owner@ is Some
    ==> {
        &&& r.unwrap().wf(writer_owner@.unwrap())
        &&& writer_owner@.unwrap().mem_view is None

    },

Creates a writer to write data to the user space of the current task.

Returns Err if this VmSpace is not the user space of the current task or the vaddr and len do not represent a valid user space memory range.

§Verified Properties
§Preconditions
§Postconditions
§Safety
  • The function does not interact with the lower-level memory system directly. By checking that the target (user) page table is not the active (kernel) one, we ensure that the resulting writer cannot interact with kernel memory.

Auto Trait Implementations§

§

impl<'a> Freeze for VmSpace<'a>

§

impl<'a> !RefUnwindSafe for VmSpace<'a>

§

impl<'a> Send for VmSpace<'a>

§

impl<'a> Sync for VmSpace<'a>

§

impl<'a> Unpin for VmSpace<'a>

§

impl<'a> UnsafeUnpin for VmSpace<'a>

§

impl<'a> UnwindSafe for VmSpace<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T, VERUS_SPEC__A> FromSpec<T> for VERUS_SPEC__A
where VERUS_SPEC__A: From<T>,

§

fn obeys_from_spec() -> bool

§

fn from_spec(v: T) -> VERUS_SPEC__A

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T, VERUS_SPEC__A> IntoSpec<T> for VERUS_SPEC__A
where VERUS_SPEC__A: Into<T>,

§

fn obeys_into_spec() -> bool

§

fn into_spec(self) -> T

§

impl<T, U> IntoSpecImpl<U> for T
where U: From<T>,

§

fn obeys_into_spec() -> bool

§

fn into_spec(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
§

impl<T, VERUS_SPEC__A> TryFromSpec<T> for VERUS_SPEC__A
where VERUS_SPEC__A: TryFrom<T>,

§

fn obeys_try_from_spec() -> bool

§

fn try_from_spec( v: T, ) -> Result<VERUS_SPEC__A, <VERUS_SPEC__A as TryFrom<T>>::Error>

Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T, VERUS_SPEC__A> TryIntoSpec<T> for VERUS_SPEC__A
where VERUS_SPEC__A: TryInto<T>,

§

fn obeys_try_into_spec() -> bool

§

fn try_into_spec(self) -> Result<T, <VERUS_SPEC__A as TryInto<T>>::Error>

§

impl<T, U> TryIntoSpecImpl<U> for T
where U: TryFrom<T>,

§

fn obeys_try_into_spec() -> bool

§

fn try_into_spec(self) -> Result<U, <U as TryFrom<T>>::Error>