Address Space Management
Relevant source files
Purpose and Scope
This document covers the address space management components within the axptr library. The address space management layer provides an abstraction for safely interacting with user-space memory through virtual address spaces and page tables. For information about the user space pointers that utilize this abstraction, see User Space Pointers.
Overview
Address space management in axptr is built around the AddrSpaceProvider
trait, which serves as a bridge between user pointers (UserPtr
/UserConstPtr
) and the underlying memory management system. This abstraction allows for flexible implementation of address space operations while maintaining memory safety guarantees.
flowchart TD subgraph subGraph2["Memory Management Layer"] check["Memory Region Checking"] populate["Page Table Population"] end subgraph subGraph1["Address Space Layer"] asp["AddrSpaceProvider trait"] aspimp["AddrSpace implementation"] end subgraph subGraph0["User Pointer Layer"] userptr["UserPtr"] userconstptr["UserConstPtr"] end asp --> aspimp aspimp --> check aspimp --> populate userconstptr --> asp userptr --> asp
Sources: src/lib.rs(L119 - L126) src/lib.rs(L31 - L54)
AddrSpaceProvider Trait
The AddrSpaceProvider
trait defines a contract for accessing an address space. It contains a single method that allows temporary access to an AddrSpace
object through a closure.
classDiagram class AddrSpaceProvider { <<trait>> +with_addr_space(f: impl FnOnce(&mut AddrSpace) -~ R) -~ R } class AddrSpace { +check_region_access(range: VirtAddrRange, flags: MappingFlags) -~ bool +populate_area(start: VirtAddr, size: usize) -~ LinuxResult~() ~ } AddrSpace --> AddrSpaceProvider : provides access to
Sources: src/lib.rs(L119 - L121) src/lib.rs(L122 - L126)
Implementation and Usage
The library provides a default implementation of AddrSpaceProvider
for &mut AddrSpace
:
#![allow(unused)] fn main() { impl AddrSpaceProvider for &mut AddrSpace { fn with_addr_space<R>(&mut self, f: impl FnOnce(&mut AddrSpace) -> R) -> R { f(self) } } }
This simple implementation allows a mutable reference to an AddrSpace
to be used as an AddrSpaceProvider
. The implementation pattern ensures that the AddrSpace
is only accessible within the provided closure, enforcing proper resource management.
Sources: src/lib.rs(L122 - L126)
Memory Region Management
The address space management layer is responsible for two primary operations:
- Checking if a memory region is accessible with specific permissions
- Populating page tables to ensure memory is mapped when accessed
These operations are encapsulated in the check_region
function:
flowchart TD A["check_region(aspace, start, layout, access_flags)"] B["Check address alignment"] C["Return EFAULT"] D["Check region access permissions"] E["Calculate page boundaries"] F["Populate page tables"] G["Return error"] H["Return Ok(())"] A --> B B --> C B --> D D --> C D --> E E --> F F --> G F --> H
Sources: src/lib.rs(L31 - L54)
Region Checking Process
The check_region
function performs several validation steps:
- Alignment Check: Verifies that the memory address is properly aligned for the requested data type
- Permission Check: Ensures the memory region has the appropriate access flags (read/write)
- Page Table Population: Maps the necessary pages in virtual memory
This function returns a LinuxResult<()>
which is Ok(())
if the region is valid and accessible, or Err(LinuxError::EFAULT)
if the region cannot be accessed.
Sources: src/lib.rs(L31 - L54)
Integration with User Pointers
The address space management layer is primarily used by the user pointer types (UserPtr
and UserConstPtr
) to safely access user-space memory. These types call into the address space abstraction whenever they need to validate memory accesses.
sequenceDiagram participant UserPtrUserConstPtr as "UserPtr/UserConstPtr" participant AddrSpaceProvider as "AddrSpaceProvider" participant AddrSpace as "AddrSpace" participant check_region as "check_region" UserPtrUserConstPtr ->> AddrSpaceProvider: with_addr_space(closure) AddrSpaceProvider ->> AddrSpace: invoke closure with &mut AddrSpace AddrSpace ->> check_region: check_region(start, layout, flags) check_region -->> AddrSpace: Ok(()) or Err(LinuxError) AddrSpace -->> AddrSpaceProvider: return result AddrSpaceProvider -->> UserPtrUserConstPtr: return result UserPtrUserConstPtr ->> UserPtrUserConstPtr: access memory if Ok
Sources: src/lib.rs(L175 - L182) src/lib.rs(L258 - L266)
Helper Function: check_region_with
To simplify the interaction between user pointers and address space providers, the library includes a check_region_with
helper function:
#![allow(unused)] fn main() { fn check_region_with( mut aspace: impl AddrSpaceProvider, start: VirtAddr, layout: Layout, access_flags: MappingFlags, ) -> LinuxResult<()> { aspace.with_addr_space(|aspace| check_region(aspace, start, layout, access_flags)) } }
This function takes an AddrSpaceProvider
and delegates to the check_region
function, simplifying the code in the user pointer methods.
Sources: src/lib.rs(L110 - L117)
Null-Terminated Data Handling
Special handling is provided for null-terminated data (like C strings) through the check_null_terminated
function. This function safely traverses memory until it finds a null terminator, validating each page as needed.
flowchart TD A["check_null_terminated(aspace, start, access_flags)"] B["Check address alignment"] C["Return EFAULT"] D["Initialize variables"] E["Begin memory traversal loop"] F["Check if current position crosses page boundary"] G["Validate next page access permissions"] H["Return EFAULT"] I["Move to next page"] J["Read memory at current position"] K["Check if value is null terminator"] L["Return pointer and length"] M["Increment length"] A --> B B --> C B --> D D --> E E --> F F --> G F --> J G --> H G --> I I --> J J --> K K --> L K --> M M --> E
Sources: src/lib.rs(L56 - L107)
The check_null_terminated
function uses the access_user_memory
helper to set a thread-local flag that indicates user memory is being accessed, allowing the kernel to handle page faults correctly.
Memory Access Context Management
To safely handle page faults during user memory access, the address space management system uses a thread-local flag:
flowchart TD A["access_user_memory(f)"] B["Set ACCESSING_USER_MEM = true"] C["Execute closure f"] D["Set ACCESSING_USER_MEM = false"] E["Return result of f"] A --> B B --> C C --> D D --> E
Sources: src/lib.rs(L22 - L29) src/lib.rs(L11 - L20)
The is_accessing_user_memory()
function provides a way for the OS to check if a page fault occurred during a legitimate user memory access, allowing it to handle these faults differently from other kernel faults.
Sources: src/lib.rs(L14 - L20)
Implementation Notes
- The address space management layer is designed to be minimal yet flexible, providing only the necessary abstractions for safe user memory access
- The
AddrSpaceProvider
trait follows the resource acquisition is initialization (RAII) pattern, ensuring proper resource management - All memory checks are performed before memory is accessed, preventing undefined behavior
- Page table population is done lazily, only when memory is actually accessed