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 uninterp fn new_spec() -> Self

A spec function to create a new VmSpace instance.

The reason why this function is marked as uninterp is that the implementation details of the VmSpace struct are not important for the verification of its clients.

Source

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

{
    &&& vm_owner.inv()
    &&& vm_owner.active
    &&& vm_owner.can_create_reader(vaddr, len)
    &&& vaddr != 0 && len > 0 && vaddr + len <= MAX_USERSPACE_VADDR
    &&& current_page_table_paddr_spec() == self.pt.root_paddr_spec()

}

Checks the preconditions for creating a reader or writer for the given virtual address range.

Essentially, this requires that

Source

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

{
    &&& vm_owner.inv()
    &&& vm_owner.active
    &&& vm_owner.can_create_writer(vaddr, len)
    &&& vaddr != 0 && len > 0 && vaddr + len <= MAX_USERSPACE_VADDR
    &&& current_page_table_paddr_spec() == self.pt.root_paddr_spec()

}

Checks the preconditions for creating a writer for the given virtual address range.

Most of the pre-conditions are the same as those for creating a reader (see Self::reader_requires), except that the caller must also have permission to create a writer for the given virtual address range.

Source

pub open spec fn reader_ensures( &self, vm_owner_old: VmSpaceOwner<'_>, vm_owner_new: VmSpaceOwner<'_>, vaddr: Vaddr, len: usize, r: Result<VmReader<'_>, Error>, r_owner: Option<VmIoOwner<'_>>, ) -> bool

{
    &&& vm_owner_new.inv()
    &&& vm_owner_new.readers == vm_owner_old.readers
    &&& vm_owner_new.writers == vm_owner_old.writers
    &&& r matches Ok(
        reader,
    ) && r_owner matches Some(
        owner,
    ) ==> {
        &&& reader.inv()
        &&& owner.inv_with_reader(reader)
        &&& owner.mem_view is None

    }

}

The guarantees of the created reader or writer, assuming the preconditions are satisfied.

Essentially, this ensures that

  • the invariants of the new VmSpace and the reader or writer hold;
  • the reader or writer is associated with a VmIoOwner that is well-formed with respect to the reader or writer;
  • the reader or writer has no memory view, as the memory view will be taken from the VmSpaceOwner when the reader or writer is activated.
§Special Note

The newly created instance of VmReader and its associated VmIoOwner are not yet activated, so the guarantees about the memory view only require that the memory view is None. The guarantees about the memory view will be provided by the activation function (see VmSpaceOwner::activate_reader and VmSpaceOwner::activate_writer).

We avoid mixing the creation, usage and deletion into one giant function as this would create some unnecessary life-cycle management complexities and not really help with the verification itself.

Source

pub open spec fn writer_ensures( &self, vm_owner_old: VmSpaceOwner<'a>, vm_owner_new: VmSpaceOwner<'a>, vaddr: Vaddr, len: usize, r: Result<VmWriter<'a>, Error>, r_owner: Option<VmIoOwner<'a>>, ) -> bool

{
    &&& vm_owner_new.inv()
    &&& vm_owner_new.readers == vm_owner_old.readers
    &&& vm_owner_new.writers == vm_owner_old.writers
    &&& r matches Ok(
        writer,
    ) && r_owner matches Some(
        owner,
    ) ==> {
        &&& writer.inv()
        &&& owner.inv_with_writer(writer)
        &&& owner.mem_view is None

    }

}

The guarantees of the created writer, assuming the preconditions are satisfied.

Most of the guarantees are the same as those for creating a reader (see Self::reader_ensures), except that the writer is associated with a VmIoOwner that is well-formed with respect to the writer.

Source

pub exec fn new() -> r : Self

ensures
r == Self::new_spec(),

Creates a new VM address space.

§Verification Design

This function is marked as external_body for now as the current design does not entail the concrete implementation details of the underlying data structure of the VmSpace.

§Preconditions

None

§Postconditions
  • The returned VmSpace instance satisfies the invariants of VmSpace
  • The returned VmSpace instance is equal to the one created by the Self::new_spec function, which is an uninterp function that can be used in specifications.
Source

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

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.

Source

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

Gets an 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 the mapping of another cursor.

Source

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

with
Tracked(owner): Tracked<&'a mut VmSpaceOwner<'a>>,
->
vm_reader_owner: Tracked<Option<VmIoOwner<'a>>>,
requires
self.reader_requires(*old(owner), vaddr, len),
ensures
self.reader_ensures(*old(owner), *owner, vaddr, len, r, vm_reader_owner@),

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

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

Users must ensure that no other page table is activated in the current task during the lifetime of the created VmReader. This guarantees that the VmReader can operate correctly.

§Verified Properties
§Preconditions
  • The VmSpace invariants must hold with respect to the VmSpaceOwner, which must be active.
  • The range vaddr..vaddr + len must represent a user space memory range.
  • The VmSpaceOwner must not have any active writers overlapping with the range vaddr..vaddr + len.
§Postconditions
  • An inactive VmReader will be created with the range vaddr..vaddr + len.
§Safety
  • The function preserves all memory invariants.
  • By requiring that the VmSpaceOwner must not have any active writers overlapping with the target range, it prevents data races between the reader and any writers.
Source

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

with
Tracked(owner): Tracked<&mut VmSpaceOwner<'a>>,
->
new_owner: Tracked<Option<VmIoOwner<'a>>>,
requires
self.writer_requires(*old(owner), vaddr, len),
ensures
self.writer_ensures(*old(owner), *owner, vaddr, len, r, new_owner@),

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

Users must ensure that no other page table is activated in the current task during the lifetime of the created VmWriter. This guarantees that the VmWriter can operate correctly.

§Verified Properties
§Preconditions
  • The VmSpace invariants must hold with respect to the VmSpaceOwner, which must be active.
  • The range vaddr..vaddr + len must represent a user space memory range.
  • The VmSpaceOwner must not have any active readers or writers overlapping with the range vaddr..vaddr + len.
§Postconditions
  • An inactive VmWriter will be created with the range vaddr..vaddr + len.
§Safety
  • The function preserves all memory invariants.
  • By requiring that the VmSpaceOwner must not have any active readers or writers overlapping with the target range, it prevents data races.

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> 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>