Basic Usage Patterns
Relevant source files
This document demonstrates the fundamental usage patterns for the memory_set crate, showing how to create and configure MemorySet
, MemoryArea
, and implement MappingBackend
for typical memory management scenarios. The examples here focus on the core API usage and basic operations like mapping, unmapping, and memory protection.
For detailed implementation internals, see Implementation Details. For advanced usage scenarios and testing patterns, see Advanced Examples and Testing.
Setting Up Types and Backend
The memory_set crate uses a generic type system that requires three type parameters to be specified. The typical pattern involves creating type aliases and implementing a backend that handles the actual memory operations.
Type Configuration Pattern
Component | Purpose | Example Type |
---|---|---|
F(Flags) | Memory protection flags | u8, custom bitflags |
PT(Page Table) | Page table representation | Array, actual page table struct |
B(Backend) | Memory mapping implementation | Custom struct implementingMappingBackend |
The basic setup pattern follows this structure:
type MockFlags = u8;
type MockPageTable = [MockFlags; MAX_ADDR];
struct MockBackend;
Core Type Relationships
flowchart TD subgraph subGraph2["Instantiated Types"] MSI["MemorySet"] MAI["MemoryArea"] end subgraph subGraph1["Generic Types"] MS["MemorySet"] MA["MemoryArea"] MB["MappingBackend trait"] end subgraph subGraph0["User Configuration"] UF["MockFlags = u8"] UPT["MockPageTable = [u8; MAX_ADDR]"] UB["MockBackend struct"] end MA --> MAI MS --> MSI MSI --> MAI UB --> MA UB --> MB UB --> MS UF --> MA UF --> MS UPT --> MS
Sources: README.md(L22 - L31)
Creating and Initializing MemorySet
The basic pattern for creating a MemorySet
involves calling the new()
constructor with appropriate type parameters:
let mut memory_set = MemorySet::<MockFlags, MockPageTable, MockBackend>::new();
The MemorySet
maintains an internal BTreeMap
that organizes memory areas by their starting virtual addresses, enabling efficient overlap detection and range queries.
MemorySet Initialization Flow
flowchart TD subgraph subGraph0["Required Context"] PT["&mut MockPageTable"] AREAS["MemoryArea instances"] end NEW["MemorySet::new()"] BT["BTreeMap"] EMPTY["Empty memory set ready for mapping"] BT --> EMPTY EMPTY --> AREAS EMPTY --> PT NEW --> BT
Sources: README.md(L34)
Mapping Memory Areas
The fundamental mapping operation involves creating a MemoryArea
and adding it to the MemorySet
. The pattern requires specifying the virtual address range, flags, and backend implementation.
Basic Mapping Pattern
memory_set.map(
MemoryArea::new(va!(0x1000), 0x4000, flags, MockBackend),
&mut page_table,
unmap_overlap_flag,
)
MemoryArea Construction Parameters
Parameter | Type | Purpose |
---|---|---|
start | VirtAddr | Starting virtual address |
size | usize | Size in bytes |
flags | F | Memory protection flags |
backend | B | Backend implementation |
Memory Mapping Operation Flow
sequenceDiagram participant User as User participant MemorySet as MemorySet participant BTreeMap as BTreeMap participant MemoryArea as MemoryArea participant MappingBackend as MappingBackend User ->> MemorySet: "map(area, page_table, false)" MemorySet ->> BTreeMap: "Check for overlaps" alt No overlaps MemorySet ->> MemoryArea: "Get backend reference" MemoryArea ->> MappingBackend: "map(start, size, flags, pt)" MappingBackend -->> MemoryArea: "Success/failure" MemorySet ->> BTreeMap: "Insert area" MemorySet -->> User: "Ok(())" else Overlaps found and MemorySet -->> User: "Err(AlreadyExists)" end
Sources: README.md(L36 - L41)
Unmapping and Area Management
Unmapping operations can result in area splitting when the unmapped region falls within an existing area's boundaries. This demonstrates the sophisticated area management capabilities.
Unmapping Pattern
memory_set.unmap(start_addr, size, &mut page_table)
The unmap operation handles three scenarios:
- Complete removal: Area fully contained within unmap range
- Area splitting: Unmap range falls within area boundaries
- Partial removal: Unmap range overlaps area boundaries
Area Splitting During Unmap
flowchart TD subgraph subGraph3["BTreeMap State"] MAP1["Key: 0x1000 → MemoryArea(0x1000-0x2000)"] MAP2["Key: 0x4000 → MemoryArea(0x4000-0x5000)"] end subgraph subGraph2["After Unmap"] LEFT["MemoryArea: 0x1000-0x2000"] RIGHT["MemoryArea: 0x4000-0x5000"] end subgraph subGraph1["Unmap Operation"] UNMAP["unmap(0x2000, 0x2000)"] end subgraph subGraph0["Before Unmap"] ORIG["MemoryArea: 0x1000-0x5000"] end LEFT --> MAP1 ORIG --> UNMAP RIGHT --> MAP2 UNMAP --> LEFT UNMAP --> RIGHT
Sources: README.md(L42 - L48)
Implementing MappingBackend
The MappingBackend
trait defines the interface for actual memory operations. Implementations must provide three core methods that manipulate the underlying page table or memory management structure.
Required Methods
Method | Purpose | Return Type |
---|---|---|
map | Establish new mappings | bool(success/failure) |
unmap | Remove existing mappings | bool(success/failure) |
protect | Change mapping permissions | bool(success/failure) |
Implementation Pattern
The typical pattern involves checking existing state and modifying page table entries:
#![allow(unused)] fn main() { impl MappingBackend<MockFlags, MockPageTable> for MockBackend { fn map(&self, start: VirtAddr, size: usize, flags: MockFlags, pt: &mut MockPageTable) -> bool { // Check for conflicts, then set entries } fn unmap(&self, start: VirtAddr, size: usize, pt: &mut MockPageTable) -> bool { // Verify mappings exist, then clear entries } fn protect(&self, start: VirtAddr, size: usize, new_flags: MockFlags, pt: &mut MockPageTable) -> bool { // Verify mappings exist, then update flags } } }
MappingBackend Method Responsibilities
flowchart TD subgraph subGraph2["Implementation Details"] ITER["pt.iter_mut().skip(start).take(size)"] SET["*entry = flags"] CLEAR["*entry = 0"] end subgraph subGraph1["Page Table Operations"] CHECK["Validate existing state"] MODIFY["Modify page table entries"] VERIFY["Return success/failure"] end subgraph subGraph0["MappingBackend Interface"] MAP["map(start, size, flags, pt)"] UNMAP["unmap(start, size, pt)"] PROTECT["protect(start, size, flags, pt)"] end CHECK --> MODIFY ITER --> CLEAR ITER --> SET MAP --> CHECK MODIFY --> ITER MODIFY --> VERIFY PROTECT --> CHECK UNMAP --> CHECK
Sources: README.md(L51 - L87)
Complete Usage Flow
The following demonstrates a complete usage pattern that combines all the basic operations:
Initialization and Setup
- Define type aliases for flags, page table, and backend
- Create page table instance
- Initialize empty
MemorySet
Memory Operations
- Create
MemoryArea
with desired parameters - Map area using
MemorySet::map()
- Perform unmapping operations that may split areas
- Iterate over resulting areas to verify state
Error Handling
All operations return MappingResult<T>
which wraps either success values or MappingError
variants for proper error handling.
Complete Operation Sequence
flowchart TD subgraph subGraph0["Backend Operations"] BACKEND_MAP["backend.map()"] BACKEND_UNMAP["backend.unmap()"] PT_UPDATE["Page table updates"] end INIT["Initialize types and MemorySet"] CREATE["Create MemoryArea"] MAP["memory_set.map()"] CHECK["Check result"] UNMAP["memory_set.unmap()"] HANDLE["Handle MappingError"] VERIFY["Verify area splitting"] ITER["memory_set.iter()"] END["Operation complete"] BACKEND_MAP --> PT_UPDATE BACKEND_UNMAP --> PT_UPDATE CHECK --> HANDLE CHECK --> UNMAP CREATE --> MAP HANDLE --> END INIT --> CREATE ITER --> END MAP --> BACKEND_MAP MAP --> CHECK UNMAP --> BACKEND_UNMAP UNMAP --> VERIFY VERIFY --> ITER
Sources: README.md(L18 - L88)