System Architecture

Relevant source files

This document details the architectural design of the memory_set crate, focusing on how the core components interact, the generic type system design, and the data flow through memory management operations. For an introduction to the fundamental concepts of MemorySet, MemoryArea, and MappingBackend, see Core Concepts. For detailed implementation specifics of individual components, see Implementation Details.

Component Architecture and Interactions

The memory_set crate follows a layered architecture with three primary abstraction levels: the collection layer (MemorySet), the area management layer (MemoryArea), and the backend interface layer (MappingBackend).

Component Interaction Overview

flowchart TD
subgraph subGraph3["External Dependencies"]
    PageTable["Page Table (P)"]
    MemoryAddr["memory_addr crate"]
end
subgraph subGraph2["Backend Interface Layer"]
    MappingBackend["MappingBackend<F,P> trait"]
    ConcreteBackend["Concrete Backend Implementation"]
end
subgraph subGraph1["Area Management Layer"]
    MemoryArea1["MemoryArea<F,P,B>"]
    MemoryArea2["MemoryArea<F,P,B>"]
    VirtAddrRange["VirtAddrRange"]
end
subgraph subGraph0["Collection Layer"]
    MemorySet["MemorySet<F,P,B>"]
    BTreeStorage["BTreeMap<VirtAddr, MemoryArea>"]
end

BTreeStorage --> MemoryArea1
BTreeStorage --> MemoryArea2
ConcreteBackend --> MappingBackend
MappingBackend --> PageTable
MemoryArea1 --> MappingBackend
MemoryArea1 --> VirtAddrRange
MemoryArea2 --> MappingBackend
MemorySet --> BTreeStorage
MemorySet --> PageTable
VirtAddrRange --> MemoryAddr

The MemorySet acts as the orchestrator, managing collections of MemoryArea objects through a BTreeMap indexed by virtual addresses. Each MemoryArea delegates actual page table manipulation to its associated MappingBackend implementation.

Sources: src/set.rs(L9 - L11)  README.md(L18 - L41) 

Generic Type System Design

The crate achieves flexibility through a carefully designed generic type system that allows different flag types, page table implementations, and backend strategies while maintaining type safety.

Generic Type Parameter Relationships

flowchart TD
subgraph subGraph2["Concrete Mock Example"]
    MockFlags["MockFlags = u8"]
    MockPageTable["MockPageTable = [u8; MAX_ADDR]"]
    MockBackend["MockBackend"]
    ConcreteMemorySet["MemorySet<u8, [u8; MAX_ADDR], MockBackend>"]
end
subgraph subGraph1["Core Generic Types"]
    MemorySet["MemorySet<F,P,B>"]
    MemoryArea["MemoryArea<F,P,B>"]
    MappingBackendTrait["MappingBackend<F,P>"]
end
subgraph subGraph0["Generic Parameters"]
    F["F: Copy (Flags Type)"]
    P["P (Page Table Type)"]
    B["B: MappingBackend<F,P> (Backend Type)"]
end

B --> MappingBackendTrait
B --> MemoryArea
B --> MemorySet
F --> MappingBackendTrait
F --> MemoryArea
F --> MemorySet
MockBackend --> ConcreteMemorySet
MockBackend --> MappingBackendTrait
MockFlags --> ConcreteMemorySet
MockPageTable --> ConcreteMemorySet
P --> MappingBackendTrait
P --> MemoryArea
P --> MemorySet

The type parameter F represents memory flags and must implement Copy. The page table type P is completely generic, allowing integration with different page table implementations. The backend type B must implement MappingBackend<F,P>, creating a three-way constraint that ensures type compatibility across the entire system.

Sources: src/set.rs(L9)  README.md(L24 - L31) 

Memory Management Data Flow

Memory management operations follow predictable patterns that involve coordination between all architectural layers. The most complex operations, such as unmapping that splits existing areas, demonstrate the sophisticated interaction patterns.

Map Operation Data Flow

sequenceDiagram
    participant Client as Client
    participant MemorySet as MemorySet
    participant BTreeMap as BTreeMap
    participant MemoryArea as MemoryArea
    participant MappingBackend as MappingBackend
    participant PageTable as PageTable

    Client ->> MemorySet: "map(area, page_table, unmap_overlap)"
    MemorySet ->> MemorySet: "overlaps() check"
    alt overlaps && unmap_overlap
        MemorySet ->> MemorySet: "unmap() existing regions"
        MemorySet ->> MappingBackend: "unmap() calls"
        MappingBackend ->> PageTable: "clear entries"
    else overlaps && !unmap_overlap
        MemorySet -->> Client: "MappingError::AlreadyExists"
    end
    MemorySet ->> MemoryArea: "map_area(page_table)"
    MemoryArea ->> MappingBackend: "map() call"
    MappingBackend ->> PageTable: "set entries"
    MappingBackend -->> MemoryArea: "success/failure"
    MemoryArea -->> MemorySet: "MappingResult"
    MemorySet ->> BTreeMap: "insert(area.start(), area)"
    MemorySet -->> Client: "MappingResult"

Unmap Operation with Area Splitting

sequenceDiagram
    participant Client as Client
    participant MemorySet as MemorySet
    participant BTreeMap as BTreeMap
    participant MemoryArea as MemoryArea
    participant MappingBackend as MappingBackend

    Client ->> MemorySet: "unmap(start, size, page_table)"
    MemorySet ->> BTreeMap: "find affected areas"
    loop for each affected area
    alt area fully contained
        MemorySet ->> BTreeMap: "remove area"
        MemorySet ->> MemoryArea: "unmap_area()"
    else area intersects left boundary
        MemorySet ->> MemoryArea: "shrink_right()"
    else area intersects right boundary
        MemorySet ->> MemoryArea: "shrink_left()"
    else unmap range in middle
        MemorySet ->> MemoryArea: "split(end_addr)"
        MemoryArea -->> MemorySet: "right_part created"
        MemorySet ->> MemoryArea: "shrink_right() on left part"
        MemorySet ->> BTreeMap: "insert(end, right_part)"
    end
    MemorySet ->> MappingBackend: "unmap() call"
    end
    MemorySet -->> Client: "MappingResult"

The unmap operation demonstrates the most complex data flow, involving area splitting, shrinking, and careful coordination with the page table backend to maintain consistency.

Sources: src/set.rs(L93 - L114)  src/set.rs(L122 - L169) 

Storage Organization and Efficiency

The MemorySet uses a BTreeMap<VirtAddr, MemoryArea> as its core storage mechanism, providing efficient operations for the common memory management use cases.

BTreeMap Storage Structure

flowchart TD
subgraph subGraph3["Key Operations"]
    OverlapDetection["overlaps() - O(log n)"]
    FindOperation["find() - O(log n)"]
    FreeAreaSearch["find_free_area() - O(n)"]
end
subgraph subGraph2["Efficiency Operations"]
    SortedOrder["Sorted by start address"]
    LogarithmicLookup["O(log n) lookups"]
    RangeQueries["Efficient range queries"]
end
subgraph subGraph1["BTreeMap Key-Value Organization"]
    Entry1["va!(0x1000) → MemoryArea[0x1000..0x2000]"]
    Entry2["va!(0x3000) → MemoryArea[0x3000..0x4000]"]
    Entry3["va!(0x5000) → MemoryArea[0x5000..0x6000]"]
end
subgraph subGraph0["MemorySet Internal Storage"]
    MemorySet["MemorySet"]
    Areas["areas: BTreeMap<VirtAddr, MemoryArea>"]
end

Areas --> Entry1
Areas --> Entry2
Areas --> Entry3
Entry1 --> SortedOrder
Entry2 --> LogarithmicLookup
Entry3 --> RangeQueries
LogarithmicLookup --> FindOperation
MemorySet --> Areas
RangeQueries --> FreeAreaSearch
SortedOrder --> OverlapDetection

The BTreeMap provides several efficiency advantages:

OperationComplexityImplementation
overlaps()O(log n)Range queries before/after target range
find()O(log n)Range query up to target address
find_free_area()O(n)Linear scan between existing areas
map()O(log n)Insert operation after overlap check
unmap()O(log n + k)Where k is the number of affected areas

Key Algorithm: Overlap Detection

The overlap detection algorithm uses BTreeMap's range query capabilities to efficiently check for conflicts:

flowchart TD
subgraph subGraph0["Overlap Detection Strategy"]
    RangeBefore["range(..range.start).last()"]
    RangeAfter["range(range.start..).next()"]
    CheckBefore["Check if before.va_range().overlaps(range)"]
    CheckAfter["Check if after.va_range().overlaps(range)"]
end
Result["Boolean result"]

CheckAfter --> Result
CheckBefore --> Result
RangeAfter --> CheckAfter
RangeBefore --> CheckBefore

This approach requires at most two BTreeMap lookups regardless of the total number of areas, making it highly efficient even for large memory sets.

Sources: src/set.rs(L10)  src/set.rs(L37 - L49)  src/set.rs(L52 - L55)  src/set.rs(L64 - L83)