MemorySet Core

Relevant source files

This document covers the MemorySet struct, which serves as the central container for managing collections of memory areas in the memory_set crate. The MemorySet provides high-level memory mapping operations similar to Unix mmap/munmap, including sophisticated area management with automatic splitting, merging, and overlap detection.

For details about individual memory areas, see MemoryArea. For information about the backend abstraction layer, see MappingBackend. For practical usage examples, see Usage Examples and Testing.

Core Data Structure

The MemorySet<B: MappingBackend> struct maintains an ordered collection of non-overlapping memory areas using a BTreeMap for efficient range-based operations.

Internal Organization

classDiagram
class MemorySet {
    -BTreeMap~B_Addr_MemoryArea~ areas
    +new() MemorySet
    +len() usize
    +is_empty() bool
    +iter() Iterator
    +overlaps(range) bool
    +find(addr) Option_MemoryArea_
    +find_free_area(hint, size, limit) Option_B_Addr_
    +map(area, page_table, unmap_overlap) MappingResult
    +unmap(start, size, page_table) MappingResult
    +protect(start, size, update_flags, page_table) MappingResult
    +clear(page_table) MappingResult
}

class BTreeMap~B::Addr, MemoryArea~B~~ {
    
    +range(range) Iterator
    +insert(key, value) Option_MemoryArea_
    +remove(key) Option_MemoryArea_
    +retain(predicate) void
}

class MemoryArea {
    
    +start() B_Addr
    +end() B_Addr
    +size() usize
    +va_range() AddrRange
    +map_area(page_table) MappingResult
    +unmap_area(page_table) MappingResult
    +split(at) Option_MemoryArea_
    +shrink_left(new_size, page_table) MappingResult
    +shrink_right(new_size, page_table) MappingResult
}

MemorySet  *--  BTreeMap : contains
BTreeMap  -->  MemoryArea : stores

The BTreeMap key is the start address of each memory area, enabling efficient range queries and maintaining areas in sorted order. This design allows O(log n) lookup operations and efficient iteration over address ranges.

Sources: memory_set/src/set.rs(L1 - L36) 

Memory Mapping Operations

Map Operation

The map method adds new memory areas to the set, with sophisticated overlap handling:

flowchart TD
start["map(area, page_table, unmap_overlap)"]
validate["Validate area range not empty"]
check_overlap["Check overlaps(area.va_range())"]
has_overlap["Overlaps exist?"]
check_unmap_flag["unmap_overlap = true?"]
do_unmap["Call unmap() on overlap range"]
return_error["Return MappingError::AlreadyExists"]
map_area["Call area.map_area(page_table)"]
insert_area["Insert area into BTreeMap"]
success["Return Ok(())"]
end_error["End with error"]
end_success["End successfully"]

check_overlap --> has_overlap
check_unmap_flag --> do_unmap
check_unmap_flag --> return_error
do_unmap --> map_area
has_overlap --> check_unmap_flag
has_overlap --> map_area
insert_area --> success
map_area --> insert_area
return_error --> end_error
start --> validate
success --> end_success
validate --> check_overlap

The overlap detection uses the BTreeMap's range capabilities to efficiently check both preceding and following areas without scanning the entire collection.

Sources: memory_set/src/set.rs(L93 - L122)  memory_set/src/set.rs(L38 - L51) 

Unmap Operation

The unmap operation is the most complex, handling area removal, shrinking, and splitting:

flowchart TD
start["unmap(start, size, page_table)"]
create_range["Create AddrRange from start/size"]
validate_range["Validate range not empty"]
remove_contained["Remove areas fully contained in range"]
check_left["Check area intersecting left boundary"]
left_intersect["Area extends past start?"]
left_middle["Unmapped range in middle?"]
split_left["Split area, shrink left part"]
shrink_left["Shrink area from right"]
check_right["Check area intersecting right boundary"]
right_intersect["Area starts before end?"]
shrink_right["Remove and shrink area from left"]
complete["Operation complete"]
success["Return Ok(())"]

check_left --> left_intersect
check_right --> right_intersect
complete --> success
create_range --> validate_range
left_intersect --> check_right
left_intersect --> left_middle
left_middle --> shrink_left
left_middle --> split_left
remove_contained --> check_left
right_intersect --> complete
right_intersect --> shrink_right
shrink_left --> check_right
shrink_right --> complete
split_left --> check_right
start --> create_range
validate_range --> remove_contained

The unmap operation maintains three key invariants:

  1. All areas remain non-overlapping
  2. Area boundaries align with page boundaries
  3. The BTreeMap remains sorted by start address

Sources: memory_set/src/set.rs(L124 - L184) 

Search and Query Operations

Address Lookup

The find method locates the memory area containing a specific address:

flowchart TD
addr["Target Address"]
range_query["BTreeMap.range(..=addr).last()"]
candidate["Get last area with start ≤ addr"]
contains_check["Check if area.va_range().contains(addr)"]
result["Return Option<&MemoryArea>"]

addr --> range_query
candidate --> contains_check
contains_check --> result
range_query --> candidate

This leverages the BTreeMap's ordered structure to find the candidate area in O(log n) time, then performs a simple range check.

Sources: memory_set/src/set.rs(L53 - L57) 

Free Space Detection

The find_free_area method implements a gap-finding algorithm:

flowchart TD
start["find_free_area(hint, size, limit)"]
init_last_end["last_end = max(hint, limit.start)"]
check_before["Check area before last_end"]
adjust_last_end["Adjust last_end to area.end() if needed"]
iterate["Iterate areas from last_end"]
check_gap["Gap size ≥ requested size?"]
return_address["Return last_end"]
next_area["last_end = area.end(), continue"]
no_more["No more areas?"]
check_final_gap["Final gap ≥ size?"]
return_final["Return last_end"]
return_none["Return None"]
end_success["End with address"]
end_failure["End with None"]

adjust_last_end --> iterate
check_before --> adjust_last_end
check_final_gap --> return_final
check_final_gap --> return_none
check_gap --> next_area
check_gap --> return_address
init_last_end --> check_before
iterate --> check_gap
iterate --> no_more
next_area --> iterate
no_more --> check_final_gap
return_address --> end_success
return_final --> end_success
return_none --> end_failure
start --> init_last_end

The algorithm performs a single linear scan through areas in address order, making it efficient for typical usage patterns.

Sources: memory_set/src/set.rs(L59 - L91) 

Permission Management

Protect Operation

The protect method changes memory permissions within a specified range, involving complex area manipulation:

flowchart TD
start["protect(start, size, update_flags, page_table)"]
iterate["Iterate through all areas"]
check_flags["Call update_flags(area.flags())"]
has_new_flags["New flags returned?"]
skip["Skip this area"]
check_position["Determine area position vs range"]
fully_contained["Area fully in range"]
straddles_both["Area straddles both boundaries"]
left_boundary["Area intersects left boundary"]
right_boundary["Area intersects right boundary"]
simple_protect["area.protect_area(), set_flags()"]
triple_split["Split into left|middle|right parts"]
right_split["Split area, protect right part"]
left_split["Split area, protect left part"]
continue_loop["Continue to next area"]
insert_new_areas["Queue new areas for insertion"]
all_done["All areas processed?"]
extend_areas["Insert all queued areas"]
success["Return Ok(())"]

all_done --> extend_areas
all_done --> iterate
check_flags --> has_new_flags
check_position --> fully_contained
check_position --> left_boundary
check_position --> right_boundary
check_position --> straddles_both
continue_loop --> all_done
extend_areas --> success
fully_contained --> simple_protect
has_new_flags --> check_position
has_new_flags --> skip
insert_new_areas --> continue_loop
iterate --> check_flags
left_boundary --> right_split
left_split --> insert_new_areas
right_boundary --> left_split
right_split --> insert_new_areas
simple_protect --> continue_loop
skip --> continue_loop
start --> iterate
straddles_both --> triple_split
triple_split --> insert_new_areas

The protect operation must handle five distinct cases based on how the protection range aligns with existing area boundaries, potentially creating new areas through splitting operations.

Sources: memory_set/src/set.rs(L195 - L264) 

Area Management Properties

The MemorySet maintains several critical invariants:

PropertyImplementationVerification
Non-overlapping AreasOverlap detection inmap(), careful splitting inunmap()/protect()overlaps()method, assertions in insertion
Sorted OrderBTreeMap automatically maintains order by start addressBTreeMap invariants
Consistent MappingsAll operations call correspondingMemoryAreamethodsBackend abstraction layer
Boundary AlignmentSize validation and address arithmeticAddrRangetype safety

The design ensures that operations can be composed safely - for example, mapping over an existing area with unmap_overlap=true will cleanly remove conflicts before establishing the new mapping.

Sources: memory_set/src/set.rs(L101 - L121)  memory_set/src/set.rs(L145 - L152)