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