Overview
Relevant source files
Purpose and Scope
This document provides an introduction to the cap_access
library, a capability-based access control system designed for ArceOS and embedded environments. This overview covers the library's fundamental purpose, core components, and architectural design. For detailed implementation specifics of the capability system, see Capability System. For practical usage examples and integration patterns, see Usage Guide. For information about ArceOS-specific integration details, see ArceOS Integration.
What is cap_access
The cap_access
library implements a capability-based access control mechanism that provides unforgeable access tokens for protected objects. It serves as a foundational security primitive within the ArceOS operating system ecosystem, enabling fine-grained permission management in resource-constrained environments.
The library centers around two primary components defined in src/lib.rs(L4 - L15) :
Cap
bitflags representing access permissions (READ
,WRITE
,EXECUTE
)WithCap<T>
wrapper struct that associates objects with their required capabilities
Unlike traditional access control lists or ownership models, capability-based security provides direct, unforgeable references to objects along with the permissions needed to access them. This approach eliminates ambient authority and reduces the attack surface in system security.
Sources: Cargo.toml(L1 - L16) src/lib.rs(L1 - L21) README.md(L7 - L12)
Core Components Overview
The cap_access
library consists of three fundamental layers that work together to provide secure object access:
Component | Location | Purpose |
---|---|---|
Capbitflags | src/lib.rs4-15 | Define access permissions using efficient bit operations |
WithCap | src/lib.rs17-21 | Associate any object type with capability requirements |
Access control methods | src/lib.rs46-99 | Provide safe and unsafe access patterns with capability validation |
The system leverages the bitflags
crate v2.6 for efficient permission operations, enabling combinations like Cap::READ | Cap::WRITE
while maintaining no_std
compatibility for embedded environments.
Sources: src/lib.rs(L4 - L15) src/lib.rs(L17 - L21) Cargo.toml(L14 - L15)
System Architecture
flowchart TD subgraph ExternalDeps["External Dependencies"] BitflagsLib["bitflags crate v2.6Efficient flag operations"] end subgraph CreationAPI["Object Creation"] NewMethod["WithCap::new(inner: T, cap: Cap)→ WithCap<T>"] CapGetter["cap() → Cap"] end subgraph AccessMethods["Access Control Methods"] CanAccess["can_access(cap: Cap)→ bool"] Access["access(cap: Cap)→ Option<&T>"] AccessOrErr["access_or_err(cap: Cap, err: E)→ Result<&T, E>"] AccessUnchecked["access_unchecked()→ &T (unsafe)"] end subgraph CoreTypes["Core Type System"] CapBitflags["Cap bitflagsREAD | WRITE | EXECUTE"] WithCapStruct["WithCap<T>inner: T, cap: Cap"] end CanAccess --> Access CanAccess --> AccessOrErr CapBitflags --> BitflagsLib CapBitflags --> WithCapStruct CapGetter --> CapBitflags NewMethod --> WithCapStruct WithCapStruct --> CanAccess
This architecture diagram shows the relationship between the core types and the methods that operate on them. The Cap
bitflags provide the foundation for permission representation, while WithCap<T>
wraps arbitrary objects with capability requirements. Access control methods then enforce these requirements through various safe and unsafe interfaces.
Sources: src/lib.rs(L4 - L15) src/lib.rs(L17 - L27) src/lib.rs(L46 - L99) Cargo.toml(L14 - L15)
Access Control Flow
This sequence diagram illustrates the different access patterns available in the cap_access
system. The can_access()
method serves as the core validation function used by all safe access methods, while access_unchecked()
bypasses capability validation entirely for performance-critical scenarios.
Sources: src/lib.rs(L46 - L48) src/lib.rs(L72 - L78) src/lib.rs(L93 - L99) src/lib.rs(L55 - L57)
Target Environments and Use Cases
The cap_access
library is specifically designed for system-level programming environments where security and resource constraints are primary concerns:
no_std Compatibility
The library operates in no_std
environments as specified in src/lib.rs(L1) making it suitable for:
- Embedded systems with limited memory
- Kernel modules without standard library access
- Bare-metal applications on microcontrollers
ArceOS Integration
As indicated by the package metadata in Cargo.toml(L8 - L11) cap_access
serves as a foundational component within the ArceOS operating system, providing:
- Memory region access control
- File system permission enforcement
- Device driver capability management
- Process isolation mechanisms
Multi-Architecture Support
The library supports multiple target architectures through its build system, including x86_64, RISC-V, and ARM64 platforms in both hosted and bare-metal configurations.
Sources: src/lib.rs(L1) Cargo.toml(L8 - L12) README.md(L7 - L8)
Key Design Principles
The cap_access
library embodies several important design principles that distinguish it from traditional access control mechanisms:
- Unforgeable Capabilities: The
Cap
bitflags cannot be arbitrarily created or modified, ensuring that only authorized code can grant permissions - Zero-Cost Abstractions: Capability checks compile to efficient bitwise operations with minimal runtime overhead
- Type Safety: The
WithCap<T>
wrapper preserves the original type while adding capability enforcement - Flexible Access Patterns: Multiple access methods (
access()
,access_or_err()
,access_unchecked()
) accommodate different error handling strategies and performance requirements
This capability-based approach provides stronger security guarantees than discretionary access control while maintaining the performance characteristics required for systems programming.
Sources: src/lib.rs(L4 - L15) src/lib.rs(L46 - L99) README.md(L9 - L12)
Core Architecture
Relevant source files
Purpose and Scope
This document provides a comprehensive overview of the fundamental architecture underlying the cap_access capability-based access control system. It covers the core components that enable secure object protection through unforgeable capability tokens and controlled access patterns.
For specific implementation details of the capability flags system, see Capability System. For in-depth coverage of the object wrapper mechanics, see Object Protection with WithCap. For detailed access method implementations, see Access Control Methods.
System Overview
The cap_access library implements a capability-based access control model built around two primary components: capability tokens (Cap
) and protected object wrappers (WithCap<T>
). The architecture enforces access control at compile time and runtime through a combination of type safety and capability checking.
Core Architecture Diagram
flowchart TD subgraph subGraph3["Access Methods"] CanAccess["can_access()"] Access["access()"] AccessOrErr["access_or_err()"] AccessUnchecked["access_unchecked()"] end subgraph subGraph2["WithCap Fields"] Inner["inner: T"] CapField["cap: Cap"] end subgraph subGraph1["Cap Implementation Details"] READ["Cap::READ (1 << 0)"] WRITE["Cap::WRITE (1 << 1)"] EXECUTE["Cap::EXECUTE (1 << 2)"] Contains["Cap::contains()"] end subgraph subGraph0["cap_access Core Components"] Cap["Cap bitflags struct"] WithCap["WithCap<T> wrapper struct"] Methods["Access control methods"] end CanAccess --> Contains Cap --> Contains Cap --> EXECUTE Cap --> READ Cap --> WRITE CapField --> Contains Methods --> Access Methods --> AccessOrErr Methods --> AccessUnchecked Methods --> CanAccess WithCap --> CapField WithCap --> Inner WithCap --> Methods
Sources: src/lib.rs(L4 - L15) src/lib.rs(L17 - L21) src/lib.rs(L23 - L100)
Core Components
Capability Token System
The Cap
struct serves as the foundational security primitive, implemented using the bitflags
crate to provide efficient bitwise operations on capability flags. The three basic capabilities can be combined using bitwise OR operations to create compound permissions.
Capability | Value | Purpose |
---|---|---|
Cap::READ | 1 << 0 | Allows reading access to protected data |
Cap::WRITE | 1 << 1 | Enables modification of protected data |
Cap::EXECUTE | 1 << 2 | Permits execution of protected code |
Protected Object Wrapper
The WithCap<T>
generic struct encapsulates any type T
along with its associated capabilities. This wrapper ensures that access to the inner object is mediated through capability checks, preventing unauthorized operations.
flowchart TD subgraph subGraph2["Capability Query"] GetCap["cap() -> Cap"] CanAccess["can_access(cap: Cap) -> bool"] end subgraph Constructor["Constructor"] New["WithCap::new(inner: T, cap: Cap)"] end subgraph subGraph0["WithCap<T> Structure"] InnerField["inner: T"] CapacityField["cap: Cap"] end CapacityField --> CanAccess CapacityField --> GetCap New --> CapacityField New --> InnerField
Sources: src/lib.rs(L17 - L21) src/lib.rs(L24 - L27) src/lib.rs(L29 - L32) src/lib.rs(L34 - L48)
Access Control Implementation
Capability Checking Logic
The core access control mechanism centers on the can_access
method, which uses the Cap::contains
operation to verify that the wrapper's capability set includes all bits required by the requested capability.
flowchart TD Request["Access Request with Cap"] Check["can_access(requested_cap)"] Contains["self.cap.contains(requested_cap)"] Allow["Grant Access"] Deny["Deny Access"] Check --> Contains Contains --> Allow Contains --> Deny Request --> Check
Sources: src/lib.rs(L46 - L48)
Access Pattern Variants
The architecture provides three distinct access patterns, each with different safety and error handling characteristics:
Method | Return Type | Safety | Use Case |
---|---|---|---|
access(cap) | Option<&T> | Safe | Optional access with None on failure |
access_or_err(cap, err) | Result<&T, E> | Safe | Error propagation with custom error types |
access_unchecked() | &T | Unsafe | Performance-critical code with guaranteed valid capabilities |
Access Method Flow
flowchart TD AccessCall["Access Method Called"] SafeCheck["Safe or Unsafe?"] CapCheck["Capability Check"] OptionReturn["Return Option<&T>"] ResultReturn["Return Result<&T, E>"] UnsafeReturn["Return &T directly"] AccessCall --> SafeCheck CapCheck --> OptionReturn CapCheck --> ResultReturn SafeCheck --> CapCheck SafeCheck --> UnsafeReturn
Sources: src/lib.rs(L50 - L57) src/lib.rs(L59 - L78) src/lib.rs(L80 - L99)
Type Safety and Memory Safety
The architecture leverages Rust's type system to enforce capability-based access control at compile time while providing runtime capability validation. The WithCap<T>
wrapper prevents direct access to the inner object, forcing all access through the controlled interface methods.
The unsafe access_unchecked
method provides an escape hatch for performance-critical scenarios where the caller can guarantee capability validity, while maintaining clear documentation of the safety requirements.
Sources: src/lib.rs(L1 - L101)
Capability System
Relevant source files
Purpose and Scope
The Capability System forms the foundation of access control in the cap_access library. This document covers the Cap
bitflags structure, the three fundamental capability types (READ, WRITE, EXECUTE), and the mechanisms for combining and checking capabilities. For information about how capabilities are used with protected objects, see Object Protection with WithCap. For details on access control methods that utilize these capabilities, see Access Control Methods.
Cap Bitflags Structure
The capability system is implemented using the bitflags
crate to provide efficient bit-level operations for permission management. The Cap
structure represents unforgeable access tokens that can be combined using bitwise operations.
flowchart TD subgraph subGraph2["Bitwise Operations"] UNION["Union (|)"] INTERSECTION["Intersection (&)"] CONTAINS["contains()"] end subgraph subGraph1["Bitflags Traits"] DEFAULT["Default"] DEBUG["Debug"] CLONE["Clone"] COPY["Copy"] end subgraph subGraph0["Cap Structure [lines 4-15]"] CAP["Cap: u32"] READ["READ = 1 << 0"] WRITE["WRITE = 1 << 1"] EXECUTE["EXECUTE = 1 << 2"] end CAP --> CLONE CAP --> COPY CAP --> DEBUG CAP --> DEFAULT CAP --> EXECUTE CAP --> READ CAP --> WRITE EXECUTE --> UNION INTERSECTION --> CONTAINS READ --> UNION UNION --> CONTAINS WRITE --> UNION
Cap Bitflags Architecture
The Cap
struct is defined as a bitflags structure that wraps a u32
value, providing type-safe capability manipulation with automatic trait derivations for common operations.
Sources: src/lib.rs(L4 - L15)
Basic Capability Types
The capability system defines three fundamental access rights that correspond to common operating system permissions:
Capability | Bit Position | Value | Purpose |
---|---|---|---|
READ | 0 | 1 << 0(1) | Grants readable access to protected data |
WRITE | 1 | 1 << 1(2) | Grants writable access to protected data |
EXECUTE | 2 | 1 << 2(4) | Grants executable access to protected data |
flowchart TD subgraph subGraph1["Capability Constants"] READ_CONST["Cap::READ = 0x01"] WRITE_CONST["Cap::WRITE = 0x02"] EXECUTE_CONST["Cap::EXECUTE = 0x04"] end subgraph subGraph0["Bit Layout [u32]"] BIT2["Bit 2: EXECUTE"] BIT1["Bit 1: WRITE"] BIT0["Bit 0: READ"] UNUSED["Bits 3-31: Reserved"] end BIT0 --> READ_CONST BIT1 --> WRITE_CONST BIT2 --> EXECUTE_CONST
Capability Bit Layout
Each capability type occupies a specific bit position, allowing for efficient combination and checking operations using bitwise arithmetic.
Sources: src/lib.rs(L8 - L14)
Capability Combinations
Capabilities can be combined using bitwise OR operations to create composite permissions. The bitflags implementation provides natural syntax for capability composition:
flowchart TD subgraph subGraph2["Use Cases"] READONLY["Read-only files"] READWRITE["Mutable data"] EXECUTABLE["Program files"] FULLACCESS["Administrative access"] end subgraph subGraph1["Common Combinations"] RW["Cap::READ | Cap::WRITE"] RX["Cap::READ | Cap::EXECUTE"] WX["Cap::WRITE | Cap::EXECUTE"] RWX["Cap::READ | Cap::WRITE | Cap::EXECUTE"] end subgraph subGraph0["Single Capabilities"] R["Cap::READ"] W["Cap::WRITE"] X["Cap::EXECUTE"] end R --> READONLY R --> RW R --> RWX R --> RX RW --> READWRITE RWX --> FULLACCESS RX --> EXECUTABLE W --> RW W --> RWX W --> WX X --> RWX X --> RX X --> WX
Capability Combination Patterns
The bitflags design enables natural combination of capabilities to express complex permission requirements.
Sources: src/lib.rs(L4 - L15)
Capability Checking Logic
The capability system provides the contains
method for checking whether a set of capabilities includes required permissions. This forms the foundation for all access control decisions in the system.
flowchart TD subgraph subGraph0["contains() Logic"] BITWISE["(self.bits & other.bits) == other.bits"] SUBSET["Check if 'other' is subset of 'self'"] end subgraph subGraph1["Example Scenarios"] SCENARIO1["Object has READ|WRITERequest: READ → true"] SCENARIO2["Object has READRequest: WRITE → false"] SCENARIO3["Object has READ|WRITERequest: READ|WRITE → true"] end START["can_access(cap: Cap)"] CHECK["self.cap.contains(cap)"] TRUE["Return true"] FALSE["Return false"] BITWISE --> SUBSET CHECK --> BITWISE START --> CHECK SUBSET --> FALSE SUBSET --> TRUE
Capability Checking Flow
The can_access
method uses bitwise operations to determine if the requested capabilities are a subset of the available capabilities.
Sources: src/lib.rs(L46 - L48)
Implementation Details
The Cap
structure leverages several key design patterns for efficient and safe capability management:
Bitflags Integration
The implementation uses the bitflags!
macro to generate a complete capability management API, including:
- Automatic implementation of bitwise operations (
|
,&
,^
,!
) - Type-safe flag manipulation methods
- Built-in
contains()
method for subset checking - Standard trait implementations (
Debug
,Clone
,Copy
,Default
)
Memory Efficiency
The Cap
structure occupies only 4 bytes (u32
) regardless of capability combinations, making it suitable for embedded environments where memory usage is critical.
Constant Evaluation
The can_access
method is marked as const fn
, enabling compile-time capability checking when capability values are known at compile time.
Sources: src/lib.rs(L4 - L15) src/lib.rs(L46 - L48)
Object Protection with WithCap
Relevant source files
Purpose and Scope
This document covers the WithCap<T>
wrapper struct, which serves as the core object protection mechanism in the cap_access library. The WithCap<T>
struct associates any object of type T
with a specific capability (Cap
), providing controlled access to the wrapped object through capability checking.
For details about the underlying capability system and Cap
bitflags, see Capability System. For comprehensive coverage of the access control methods provided by WithCap<T>
, see Access Control Methods.
WithCap Structure Overview
The WithCap<T>
struct provides a simple but powerful abstraction for capability-based object protection. It wraps any object with an associated capability, ensuring that access to the object is mediated through capability checks.
Core Architecture
flowchart TD subgraph AccessMethods["Access Methods"] new["new(inner, cap)"] can_access["can_access(cap)"] access["access(cap)"] access_or_err["access_or_err(cap, err)"] access_unchecked["access_unchecked()"] cap_getter["cap()"] end subgraph CapSystem["Capability System"] READ["Cap::READ"] WRITE["Cap::WRITE"] EXECUTE["Cap::EXECUTE"] COMBINED["Combined Capabilities"] end subgraph WithCap["WithCap<T> Structure"] inner["inner: T"] cap["cap: Cap"] end COMBINED --> cap EXECUTE --> cap READ --> cap WRITE --> cap
Sources: src/lib.rs(L18 - L21) src/lib.rs(L23 - L100)
Struct Definition
The WithCap<T>
struct contains exactly two fields that work together to provide controlled access:
Field | Type | Purpose |
---|---|---|
inner | T | The protected object of arbitrary type |
cap | Cap | The capability bitflags defining allowed access |
The struct is generic over type T
, allowing any object to be protected with capabilities.
Sources: src/lib.rs(L18 - L21)
Object Wrapping Process
Creating Protected Objects
The WithCap::new()
constructor associates an object with specific capabilities, creating an immutable binding between the object and its access permissions.
flowchart TD subgraph Examples["Example Objects"] FileData["File Data"] MemoryRegion["Memory Region"] DeviceHandle["Device Handle"] end subgraph Creation["Object Protection Process"] UnprotectedObj["Unprotected Object"] Capability["Cap Permissions"] Constructor["WithCap::new()"] ProtectedObj["WithCap<T>"] end Capability --> Constructor Constructor --> ProtectedObj DeviceHandle --> UnprotectedObj FileData --> UnprotectedObj MemoryRegion --> UnprotectedObj UnprotectedObj --> Constructor
Sources: src/lib.rs(L24 - L27)
Capability Association
The new()
method permanently associates the provided capability with the object. Once created, the capability cannot be modified, ensuring the integrity of the access control policy.
The constructor signature demonstrates this immutable binding:
pub fn new(inner: T, cap: Cap) -> Self
Sources: src/lib.rs(L24 - L27)
Access Control Implementation
Capability Checking Logic
The core access control mechanism relies on the can_access()
method, which performs bitwise capability checking using the underlying Cap::contains()
operation.
flowchart TD subgraph AccessMethods["Access Method Types"] SafeAccess["access() → Option<&T>"] ErrorAccess["access_or_err() → Result<&T, E>"] UnsafeAccess["access_unchecked() → &T"] end subgraph AccessFlow["Access Control Flow"] Request["Access Request with Cap"] Check["can_access(cap)"] Contains["cap.contains(requested_cap)"] Allow["Grant Access"] Deny["Deny Access"] end Allow --> ErrorAccess Allow --> SafeAccess Check --> Contains Contains --> Allow Contains --> Deny Deny --> ErrorAccess Deny --> SafeAccess Request --> Check UnsafeAccess --> Check
Sources: src/lib.rs(L46 - L48) src/lib.rs(L72 - L78) src/lib.rs(L93 - L99) src/lib.rs(L55 - L57)
Method Categories
The WithCap<T>
struct provides three categories of access methods:
Method Category | Safety | Return Type | Use Case |
---|---|---|---|
Checked Access | Safe | Option<&T> | General-purpose access |
Error-based Access | Safe | Result<&T, E> | When custom error handling needed |
Unchecked Access | Unsafe | &T | Performance-critical code paths |
Sources: src/lib.rs(L72 - L78) src/lib.rs(L93 - L99) src/lib.rs(L55 - L57)
Internal Architecture Details
Capability Storage
The WithCap<T>
struct stores capabilities as Cap
bitflags, enabling efficient bitwise operations for permission checking. The cap()
getter method provides read-only access to the stored capability.
Memory Layout Considerations
The struct maintains a simple memory layout with the protected object and capability stored adjacently. This design minimizes overhead while maintaining clear separation between the object and its access permissions.
flowchart TD subgraph CapabilityBits["Cap Bitfield Structure"] Bit0["Bit 0: READ"] Bit1["Bit 1: WRITE"] Bit2["Bit 2: EXECUTE"] Reserved["Bits 3-31: Reserved"] end subgraph MemoryLayout["WithCap<T> Memory Layout"] InnerData["inner: T(Protected Object)"] CapData["cap: Cap(u32 bitflags)"] end WithCap["WithCap"] CapData --> Bit0 CapData --> Bit1 CapData --> Bit2 CapData --> Reserved CapData --> WithCap InnerData --> WithCap
Sources: src/lib.rs(L18 - L21) src/lib.rs(L29 - L32)
Const Methods
Several methods are declared as const fn
, enabling compile-time evaluation when possible:
cap()
- capability gettercan_access()
- capability checkingaccess()
- checked access
This design supports zero-cost abstractions in performance-critical embedded environments.
Sources: src/lib.rs(L30) src/lib.rs(L46) src/lib.rs(L72)
Access Control Methods
Relevant source files
Purpose and Scope
This document covers the access control methods provided by the WithCap<T>
wrapper for accessing protected objects. It explains the different access patterns available, their safety guarantees, and appropriate usage scenarios. For information about the capability system and permission flags, see Capability System. For details about the WithCap<T>
wrapper structure itself, see Object Protection with WithCap.
The access control methods implement a capability-based security model where objects can only be accessed when the caller presents the required capabilities. The system provides multiple access patterns to accommodate different error handling strategies and performance requirements.
Access Control Flow
The access control system follows a consistent pattern where capability validation determines whether access is granted to the protected object.
flowchart TD CALLER["Caller Code"] METHOD["Access Method Call"] CHECK["can_access(cap)"] VALIDATE["CapabilityValidation"] GRANT["Access Granted"] DENY["Access Denied"] RETURN_REF["Return &T"] ERROR_HANDLING["Error Handling"] OPTION["Option::None"] RESULT["Result::Err(E)"] UNSAFE["Undefined Behavior"] SAFE_ACCESS["access(cap)"] RESULT_ACCESS["access_or_err(cap, err)"] UNSAFE_ACCESS["access_unchecked()"] CALLER --> METHOD CHECK --> VALIDATE DENY --> ERROR_HANDLING ERROR_HANDLING --> OPTION ERROR_HANDLING --> RESULT ERROR_HANDLING --> UNSAFE GRANT --> RETURN_REF METHOD --> CHECK METHOD --> RESULT_ACCESS METHOD --> SAFE_ACCESS METHOD --> UNSAFE_ACCESS RESULT_ACCESS --> CHECK SAFE_ACCESS --> CHECK UNSAFE_ACCESS --> RETURN_REF VALIDATE --> DENY VALIDATE --> GRANT
Sources: src/lib.rs(L46 - L99)
Capability Validation
All safe access methods rely on the can_access
method for capability validation. This method performs a bitwise containment check to determine if the required capabilities are present.
can_accessMethod
The can_access
method provides the fundamental capability checking logic used by all safe access patterns.
Method Signature | Return Type | Purpose |
---|---|---|
can_access(&self, cap: Cap) | bool | Check if capabilities allow access |
Implementation Details:
- Uses
self.cap.contains(cap)
for bitwise capability checking - Declared as
const fn
for compile-time evaluation - Returns
true
if all requested capability bits are present - Returns
false
if any requested capability bits are missing
Sources: src/lib.rs(L46 - L48)
Safe Access Methods
The library provides two safe access methods that perform capability validation before granting access to the protected object.
Option-Based Access
The access
method provides safe access with Option<&T>
return semantics for cases where capability failure should be handled gracefully.
flowchart TD INPUT["access(cap: Cap)"] CHECK["can_access(cap)"] SOME["Some(&self.inner)"] NONE["None"] CLIENT_OK["Client handles success"] CLIENT_ERR["Client handles None"] CHECK --> NONE CHECK --> SOME INPUT --> CHECK NONE --> CLIENT_ERR SOME --> CLIENT_OK
Method Characteristics:
- Signature:
access(&self, cap: Cap) -> Option<&T>
- Safety: Memory safe with compile-time guarantees
- Performance: Minimal overhead with
const fn
optimization - Error Handling: Returns
None
on capability mismatch
Usage Pattern:
if let Some(data) = protected_obj.access(Cap::READ) {
// Use data safely
} else {
// Handle access denial
}
Sources: src/lib.rs(L72 - L78)
Result-Based Access
The access_or_err
method provides safe access with Result<&T, E>
return semantics, allowing custom error types and error propagation patterns.
flowchart TD INPUT["access_or_err(cap: Cap, err: E)"] CHECK["can_access(cap)"] OK["Ok(&self.inner)"] ERR["Err(err)"] CLIENT_OK["Client handles success"] CLIENT_ERR["Client handles custom error"] PROPAGATE["Error propagation with ?"] CHECK --> ERR CHECK --> OK ERR --> CLIENT_ERR ERR --> PROPAGATE INPUT --> CHECK OK --> CLIENT_OK
Method Characteristics:
- Signature:
access_or_err<E>(&self, cap: Cap, err: E) -> Result<&T, E>
- Safety: Memory safe with compile-time guarantees
- Error Handling: Returns custom error type on capability mismatch
- Integration: Compatible with
?
operator for error propagation
Usage Pattern:
let data = protected_obj.access_or_err(Cap::WRITE, "Write access denied")?;
// Use data safely, or propagate error
Sources: src/lib.rs(L93 - L99)
Unsafe Access Methods
The library provides an unsafe access method for performance-critical scenarios where capability checking overhead must be eliminated.
Unchecked Access
The access_unchecked
method bypasses all capability validation and directly returns a reference to the protected object.
flowchart TD INPUT["access_unchecked()"] DIRECT["&self.inner"] WARNING["⚠️ UNSAFE:No capability validation"] CALLER["Caller Responsibility"] BYPASS["Bypasses can_access()"] PERFORMANCE["Zero-overhead access"] BYPASS --> PERFORMANCE DIRECT --> WARNING INPUT --> BYPASS INPUT --> DIRECT WARNING --> CALLER
Method Characteristics:
- Signature:
unsafe fn access_unchecked(&self) -> &T
- Safety: UNSAFE - No capability validation performed
- Performance: Zero overhead - direct memory access
- Responsibility: Caller must ensure capability compliance
Safety Requirements: The caller must guarantee that they have the appropriate capabilities for the intended use of the object. Violating this contract may lead to:
- Security vulnerabilities
- Privilege escalation
- Undefined behavior in security-critical contexts
Sources: src/lib.rs(L55 - L57)
Access Method Comparison
Method | Safety | Return Type | Overhead | Use Case |
---|---|---|---|---|
access | Safe | Option<&T> | Minimal | General purpose with option handling |
access_or_err | Safe | Result<&T, E> | Minimal | Error propagation and custom errors |
access_unchecked | Unsafe | &T | Zero | Performance-critical trusted code |
Implementation Architecture
The access control methods are implemented as part of the WithCap<T>
struct and follow a layered architecture:
flowchart TD subgraph subGraph2["Memory Access"] INNER_REF["&self.inner"] end subgraph subGraph1["Core Validation"] CONTAINS["self.cap.contains(cap)"] BITFLAGS["bitflags operations"] end subgraph subGraph0["WithCap Access Methods"] CAN_ACCESS["can_access(cap: Cap)→ bool"] ACCESS["access(cap: Cap)→ Option<&T>"] ACCESS_ERR["access_or_err(cap: Cap, err: E)→ Result<&T, E>"] ACCESS_UNSAFE["access_unchecked()→ &T"] end ACCESS --> CAN_ACCESS ACCESS --> INNER_REF ACCESS_ERR --> CAN_ACCESS ACCESS_ERR --> INNER_REF ACCESS_UNSAFE --> INNER_REF CAN_ACCESS --> CONTAINS CONTAINS --> BITFLAGS
Architecture Characteristics:
- Layered Design: Safe methods build upon capability validation
- Shared Validation: All safe methods use
can_access
for consistency - Zero-Cost Abstractions:
const fn
declarations enable compile-time optimization - Type Safety: Generic design works with any wrapped type
T
Sources: src/lib.rs(L23 - L100)
Usage Guide
Relevant source files
This guide provides practical examples and patterns for using the cap_access
library in real applications. It covers the essential usage patterns, access methods, and best practices for implementing capability-based access control in your code.
For architectural details about the underlying capability system, see Core Architecture. For information about integrating with ArceOS, see ArceOS Integration.
Basic Usage Patterns
The fundamental workflow with cap_access
involves creating protected objects with WithCap::new()
and accessing them through capability-checked methods.
Creating Protected Objects
flowchart TD subgraph subGraph0["Cap Constants"] READ["Cap::READ"] WRITE["Cap::WRITE"] EXECUTE["Cap::EXECUTE"] end Object["Raw Object(T)"] WithCap_new["WithCap::new()"] Capability["Cap Permission(READ | WRITE | EXECUTE)"] Protected["WithCap<T>Protected Object"] Capability --> WithCap_new EXECUTE --> Capability Object --> WithCap_new READ --> Capability WRITE --> Capability WithCap_new --> Protected
The most common pattern is to wrap objects at creation time with appropriate capabilities:
Object Type | Typical Capabilities | Use Case |
---|---|---|
Configuration Data | Cap::READ | Read-only system settings |
User Files | Cap::READ | Cap::WRITE | Editable user content |
Executable Code | Cap::READ | Cap::EXECUTE | Program binaries |
System Resources | Cap::READ | Cap::WRITE | Cap::EXECUTE | Full access resources |
Sources: src/lib.rs(L4 - L15) src/lib.rs(L23 - L27)
Capability Composition
Capabilities can be combined using bitwise operations to create complex permission sets:
flowchart TD subgraph subGraph1["Combined Capabilities"] RW["Cap::READ | Cap::WRITERead-Write Access"] RX["Cap::READ | Cap::EXECUTERead-Execute Access"] RWX["Cap::READ | Cap::WRITE | Cap::EXECUTEFull Access"] end subgraph subGraph0["Individual Capabilities"] READ_CAP["Cap::READ(1 << 0)"] WRITE_CAP["Cap::WRITE(1 << 1)"] EXEC_CAP["Cap::EXECUTE(1 << 2)"] end EXEC_CAP --> RWX EXEC_CAP --> RX READ_CAP --> RW READ_CAP --> RWX READ_CAP --> RX WRITE_CAP --> RW WRITE_CAP --> RWX
Sources: src/lib.rs(L4 - L15) README.md(L16 - L29)
Access Method Comparison
The WithCap<T>
struct provides three distinct access patterns, each with different safety and error handling characteristics:
flowchart TD subgraph subGraph1["Return Types"] Bool["Boolean CheckValidation only"] Option["Option PatternNone on failure"] Result["Result PatternCustom error on failure"] Direct["Direct ReferenceNo safety checks"] end subgraph subGraph0["Access Methods"] can_access["can_access(cap)→ bool"] access["access(cap)→ Option<&T>"] access_or_err["access_or_err(cap, err)→ Result<&T, E>"] access_unchecked["access_unchecked()→ &T (UNSAFE)"] end WithCap["WithCap<T>Protected Object"] WithCap --> access WithCap --> access_or_err WithCap --> access_unchecked WithCap --> can_access access --> Option access_or_err --> Result access_unchecked --> Direct can_access --> Bool
Method Selection Guidelines
Method | When to Use | Safety Level | Performance |
---|---|---|---|
can_access() | Pre-flight validation checks | Safe | Fastest |
access() | Optional access patterns | Safe | Fast |
access_or_err() | Error handling with context | Safe | Fast |
access_unchecked() | Performance-critical paths | Unsafe | Fastest |
Sources: src/lib.rs(L34 - L48) src/lib.rs(L59 - L78) src/lib.rs(L80 - L99) src/lib.rs(L50 - L57)
Practical Access Patterns
Safe Access with Option Handling
The access()
method returns Option<&T>
and is ideal for scenarios where access failure is expected and should be handled gracefully:
sequenceDiagram participant Client as Client participant WithCapaccess as "WithCap::access()" participant WithCapcan_access as "WithCap::can_access()" participant innerT as "inner: T" Client ->> WithCapaccess: access(requested_cap) WithCapaccess ->> WithCapcan_access: self.cap.contains(requested_cap) alt Capability Match WithCapcan_access -->> WithCapaccess: true WithCapaccess ->> innerT: &self.inner innerT -->> WithCapaccess: &T WithCapaccess -->> Client: Some(&T) else Capability Mismatch WithCapcan_access -->> WithCapaccess: false WithCapaccess -->> Client: None end
This pattern is demonstrated in the README examples where checking for Cap::EXECUTE
on a read-write object returns None
.
Sources: src/lib.rs(L72 - L78) README.md(L22 - L28)
Error-Based Access with Context
The access_or_err()
method provides custom error messages for better debugging and user feedback:
flowchart TD subgraph Output["Output"] Result_Ok["Result::Ok(&T)"] Result_Err["Result::Err(E)"] end subgraph subGraph1["access_or_err Process"] Check["can_access(cap)"] Success["Ok(&inner)"] Failure["Err(custom_error)"] end subgraph Input["Input"] Cap_Request["Requested Capability"] Error_Value["Custom Error Value"] end Cap_Request --> Check Check --> Failure Check --> Success Error_Value --> Check Failure --> Result_Err Success --> Result_Ok
This method is particularly useful in system programming contexts where specific error codes or messages are required for debugging.
Sources: src/lib.rs(L93 - L99)
Unsafe High-Performance Access
The access_unchecked()
method bypasses capability validation for performance-critical code paths:
Safety Requirements:
- Caller must manually verify capability compliance
- Should only be used in trusted, performance-critical contexts
- Requires
unsafe
block, making the safety contract explicit
Sources: src/lib.rs(L55 - L57)
Best Practices
Capability Design Patterns
- Principle of Least Privilege: Grant minimal necessary capabilities
- Capability Composition: Use bitwise OR to combine permissions
- Validation at Boundaries: Check capabilities at system boundaries
- Consistent Error Handling: Choose one access method per subsystem
Integration Patterns
flowchart TD subgraph subGraph2["Protected Resources"] FileSystem["File System Objects"] Memory["Memory Regions"] DeviceDrivers["Device Drivers"] end subgraph subGraph1["cap_access Integration"] WithCap_Factory["WithCap CreationWithCap::new(obj, cap)"] Access_Layer["Access Controlaccess() / access_or_err()"] Validation["Capability Validationcan_access()"] end subgraph subGraph0["Application Layer"] UserCode["User Application Code"] ServiceLayer["Service Layer"] end Access_Layer --> Validation ServiceLayer --> Access_Layer ServiceLayer --> WithCap_Factory UserCode --> Access_Layer UserCode --> WithCap_Factory WithCap_Factory --> DeviceDrivers WithCap_Factory --> FileSystem WithCap_Factory --> Memory
Common Usage Scenarios
Scenario | Recommended Pattern | Example |
---|---|---|
File Access Control | WithCap::new(file, Cap::READ | Cap::WRITE) | User file permissions |
Memory Protection | WithCap::new(region, Cap::READ | Cap::EXECUTE) | Code segment protection |
Device Driver Access | WithCap::new(device, Cap::WRITE) | Hardware write-only access |
Configuration Data | WithCap::new(config, Cap::READ) | Read-only system settings |
Sources: src/lib.rs(L23 - L27) README.md(L19 - L24)
Advanced Usage Patterns
Dynamic Capability Checking
The can_access()
method enables sophisticated access control logic:
This pattern is essential for security-sensitive applications that need comprehensive access logging and audit trails.
Sources: src/lib.rs(L46 - L48)
Multi-Level Access Control
Complex systems often require nested capability checks across multiple abstraction layers:
flowchart TD subgraph subGraph1["WithCap Instances"] AppData["WithCap<UserData>"] KernelData["WithCap<SystemCall>"] HWData["WithCap<Device>"] end subgraph subGraph0["System Architecture"] App["Application LayerCap::READ | Cap::WRITE"] Kernel["Kernel LayerCap::EXECUTE"] Hardware["Hardware LayerCap::READ | Cap::WRITE | Cap::EXECUTE"] end UserAccess["User Read Access"] SyscallAccess["System Call Execution"] DirectHW["Direct Hardware Access"] App --> AppData AppData --> UserAccess HWData --> DirectHW Hardware --> HWData Kernel --> KernelData KernelData --> SyscallAccess
This layered approach ensures that capability validation occurs at appropriate system boundaries while maintaining performance where needed.
Sources: src/lib.rs(L1 - L101)
ArceOS Integration
Relevant source files
Purpose and Scope
This document explains how the cap_access
library integrates into the ArceOS operating system ecosystem, serving as a foundational security primitive for capability-based access control. It covers the library's role in kernel security, no_std compatibility requirements, and integration patterns across ArceOS components.
For detailed information about the core capability system and access control methods, see Core Architecture. For practical usage examples, see Usage Guide.
ArceOS Ecosystem Integration
The cap_access
library serves as a core security infrastructure component within the ArceOS operating system, providing capability-based access control primitives that are used throughout the kernel and user-space applications.
Integration Architecture
flowchart TD subgraph subGraph3["User Applications"] SystemServices["System Services"] UserApps["User Applications"] RuntimeLibs["Runtime Libraries"] end subgraph subGraph2["Protected Resources"] MemoryRegions["Memory Regions"] FileObjects["File Objects"] DeviceResources["Device Resources"] ProcessData["Process Data"] end subgraph subGraph1["cap_access Security Layer"] CapStruct["Cap bitflags"] WithCapStruct["WithCap"] AccessMethods["Access Methods"] end subgraph subGraph0["ArceOS Kernel Layer"] KernelCore["Kernel Core"] MemoryMgr["Memory Manager"] FileSystem["File System"] DeviceDrivers["Device Drivers"] end AccessMethods --> CapStruct AccessMethods --> WithCapStruct DeviceDrivers --> WithCapStruct FileSystem --> WithCapStruct KernelCore --> CapStruct MemoryMgr --> WithCapStruct RuntimeLibs --> AccessMethods SystemServices --> AccessMethods UserApps --> AccessMethods WithCapStruct --> DeviceResources WithCapStruct --> FileObjects WithCapStruct --> MemoryRegions WithCapStruct --> ProcessData
Sources: Cargo.toml(L8 - L12) src/lib.rs(L17 - L21)
Package Configuration for ArceOS
The library is specifically configured for integration with ArceOS through its package metadata:
Configuration | Value | Purpose |
---|---|---|
Homepage | https://github.com/arceos-org/arceos | Links to ArceOS project |
Keywords | ["arceos", "capabilities", "permission", "access-control"] | ArceOS-specific tagging |
Categories | ["os", "no-std"] | Operating system and embedded focus |
Sources: Cargo.toml(L8 - L12)
Kernel Security Layer
The cap_access
library provides unforgeable security tokens through its capability-based access control system, enabling fine-grained permission management within ArceOS kernel components.
Security Primitive Integration
flowchart TD subgraph subGraph3["Kernel Usage"] KernelCheck["Kernel Permission Check"] ResourceAccess["Resource Access"] ErrorHandling["Error Handling"] end subgraph subGraph2["Access Control"] CanAccess["can_access()"] Access["access()"] AccessOrErr["access_or_err()"] AccessUnchecked["access_unchecked()"] end subgraph subGraph1["Object Protection"] WithCapNew["WithCap::new()"] WithCapStruct["WithCap"] InnerData["inner: T"] CapField["cap: Cap"] end subgraph subGraph0["Capability Definition"] CapRead["Cap::READ"] CapWrite["Cap::WRITE"] CapExecute["Cap::EXECUTE"] end Access --> CanAccess Access --> KernelCheck AccessOrErr --> CanAccess AccessOrErr --> ErrorHandling AccessUnchecked --> ResourceAccess CanAccess --> CapField CapExecute --> WithCapNew CapRead --> WithCapNew CapWrite --> WithCapNew KernelCheck --> ResourceAccess WithCapNew --> WithCapStruct WithCapStruct --> CapField WithCapStruct --> InnerData
Sources: src/lib.rs(L4 - L15) src/lib.rs(L46 - L48) src/lib.rs(L72 - L78)
Security Guarantees
The capability system provides several security guarantees essential for kernel operation:
- Unforgeable Tokens:
Cap
bitflags cannot be arbitrarily created, only through controlled initialization - Compile-time Safety: The type system prevents capability escalation through
WithCap<T>
wrapper - Runtime Validation: Methods like
can_access()
andaccess()
provide runtime permission checking - Controlled Unsafe Access:
access_unchecked()
provides escape hatch for performance-critical kernel code
Sources: src/lib.rs(L46 - L48) src/lib.rs(L72 - L78) src/lib.rs(L55 - L57)
no_std Compatibility
The cap_access
library is designed for no_std environments, making it suitable for embedded systems and kernel contexts where the standard library is not available.
no_std Configuration
The library uses conditional compilation to support both std and no_std environments:
#![cfg_attr(not(test), no_std)]
This configuration enables:
- Kernel Integration: Direct usage within ArceOS kernel without standard library dependencies
- Embedded Support: Deployment on resource-constrained embedded systems
- Bare Metal: Operation on bare metal platforms without operating system support
Sources: src/lib.rs(L1)
Dependencies
The library maintains minimal dependencies to support no_std compatibility:
Dependency | Version | Purpose | no_std Compatible |
---|---|---|---|
bitflags | 2.6 | Efficient bitflag operations forCap | Yes |
Sources: Cargo.toml(L14 - L15)
Multi-Platform Support
ArceOS targets multiple hardware architectures, and cap_access
provides consistent capability-based access control across all supported platforms.
Supported Architectures
Based on the CI configuration and ArceOS requirements, cap_access
supports:
- x86_64: Standard desktop and server platforms (
x86_64-unknown-linux-gnu
,x86_64-unknown-none
) - RISC-V: Embedded and specialized systems (
riscv64gc-unknown-none-elf
) - ARM64: Mobile and embedded platforms (
aarch64-unknown-none-softfloat
)
Platform-Agnostic Design
The capability system is designed to be platform-agnostic through:
- Pure Rust Implementation: No platform-specific assembly or system calls
- Bitflag Operations: Efficient bitwise operations supported on all target architectures
- Const Functions: Compile-time evaluation where possible for performance
Sources: src/lib.rs(L30 - L32) src/lib.rs(L46 - L48) src/lib.rs(L72 - L78)
Integration Patterns
Typical ArceOS Usage Patterns
Within ArceOS, cap_access
follows several common integration patterns:
- Kernel Resource Protection: Wrapping kernel data structures with
WithCap<T>
to control access - System Call Interface: Using capability checking in system call implementations
- Driver Security: Protecting device driver resources with capability-based access
- Memory Management: Controlling access to memory regions through capability tokens
Example Integration Flow
sequenceDiagram participant Application as "Application" participant ArceOSKernel as "ArceOS Kernel" participant cap_access as "cap_access" participant ProtectedResource as "Protected Resource" Application ->> ArceOSKernel: "System call with capability" ArceOSKernel ->> cap_access: "WithCap::new(resource, cap)" cap_access ->> ProtectedResource: "Wrap with capability" ProtectedResource -->> cap_access: "WithCap<Resource>" cap_access -->> ArceOSKernel: "Protected object" Application ->> ArceOSKernel: "Access request" ArceOSKernel ->> cap_access: "access(required_cap)" cap_access ->> cap_access: "can_access() check" alt "Capability sufficient" cap_access -->> ArceOSKernel: "Some(&resource)" ArceOSKernel -->> Application: "Access granted" else "Capability insufficient" cap_access -->> ArceOSKernel: "None" ArceOSKernel -->> Application: "Access denied" end
Sources: src/lib.rs(L24 - L27) src/lib.rs(L72 - L78) src/lib.rs(L46 - L48)
This integration pattern ensures that ArceOS maintains strong security guarantees while providing the performance characteristics required for operating system operations across diverse hardware platforms.
Development Guide
Relevant source files
This document provides guidance for developers contributing to the cap_access
library. It covers the project structure, development environment setup, testing procedures, and contribution workflow. For detailed information about the build system and CI pipeline, see Build System and CI. For platform-specific development considerations, see Multi-Platform Support.
Project Structure
The cap_access
project follows a standard Rust library structure with emphasis on embedded and no_std
compatibility. The codebase is designed as a foundational security primitive for the ArceOS operating system ecosystem.
Repository Organization
flowchart TD subgraph subGraph2["Generated Artifacts"] TARGET["target/Build outputs"] DOCS["target/doc/Generated documentation"] LOCK["Cargo.lockDependency lock file"] end subgraph subGraph1["GitHub Workflows"] CI[".github/workflows/ci.ymlContinuous integration"] end subgraph subGraph0["Repository Root"] CARGO["Cargo.tomlPackage metadata"] GITIGNORE[".gitignoreVCS exclusions"] SRC["src/Source code"] end CARGO --> TARGET CI --> DOCS CI --> TARGET GITIGNORE --> LOCK GITIGNORE --> TARGET SRC --> TARGET
Sources: Cargo.toml(L1 - L16) .gitignore(L1 - L5) .github/workflows/ci.yml(L1 - L56)
Package Configuration
The project is configured as a multi-licensed Rust library with specific focus on operating systems and embedded development:
Property | Value |
---|---|
Name | cap_access |
Version | 0.1.0 |
Edition | 2021 |
License | GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0 |
Categories | os,no-std |
Keywords | arceos,capabilities,permission,access-control |
Sources: Cargo.toml(L1 - L12)
Development Environment
Required Tools
The development environment requires the Rust nightly toolchain with specific components for cross-compilation and code quality:
flowchart TD subgraph subGraph1["Target Platforms"] LINUX["x86_64-unknown-linux-gnuStandard Linux"] BAREMETAL["x86_64-unknown-noneBare metal x86_64"] RISCV["riscv64gc-unknown-none-elfRISC-V embedded"] ARM["aarch64-unknown-none-softfloatARM64 embedded"] end subgraph subGraph0["Rust Toolchain"] NIGHTLY["rustc nightlyCompiler toolchain"] RUSTFMT["rustfmtCode formatter"] CLIPPY["clippyLinter"] RUSTSRC["rust-srcSource code component"] end CLIPPY --> NIGHTLY NIGHTLY --> ARM NIGHTLY --> BAREMETAL NIGHTLY --> LINUX NIGHTLY --> RISCV RUSTFMT --> NIGHTLY RUSTSRC --> NIGHTLY
Sources: .github/workflows/ci.yml(L15 - L19) .github/workflows/ci.yml(L11 - L12)
Dependencies
The project maintains minimal dependencies to ensure compatibility with embedded environments:
- bitflags 2.6: Provides efficient bit flag operations for the
Cap
permission system
Sources: Cargo.toml(L14 - L15)
Quality Assurance Pipeline
Automated Checks
The CI pipeline enforces code quality through multiple automated checks:
flowchart TD subgraph subGraph1["Target Matrix"] T1["x86_64-unknown-linux-gnu"] T2["x86_64-unknown-none"] T3["riscv64gc-unknown-none-elf"] T4["aarch64-unknown-none-softfloat"] end subgraph subGraph0["Code Quality Gates"] FORMAT["cargo fmt --all --checkCode formatting validation"] LINT["cargo clippy --target TARGET --all-featuresLinting analysis"] BUILD["cargo build --target TARGET --all-featuresCompilation verification"] TEST["cargo test --target x86_64-unknown-linux-gnuUnit test execution"] end BUILD --> T1 BUILD --> T2 BUILD --> T3 BUILD --> T4 BUILD --> TEST FORMAT --> LINT LINT --> BUILD LINT --> T1 LINT --> T2 LINT --> T3 LINT --> T4 TEST --> T1
Sources: .github/workflows/ci.yml(L22 - L30)
Clippy Configuration
The linting process uses Clippy with specific allowances for the project's design patterns:
- Allows
clippy::new_without_default
due to theWithCap::new
design requiring explicit capability parameters
Sources: .github/workflows/ci.yml(L25)
Documentation System
Documentation Generation
The project uses a dual documentation system for both development and public consumption:
flowchart TD subgraph subGraph2["Quality Controls"] BROKEN["-D rustdoc::broken_intra_doc_linksLink validation"] MISSING["-D missing-docsCoverage enforcement"] end subgraph subGraph1["Documentation Outputs"] LOCALDIR["target/doc/Local documentation"] GHPAGES["gh-pages branchPublished documentation"] DOCSRS["docs.rsPublic registry docs"] end subgraph subGraph0["Documentation Pipeline"] RUSTDOC["cargo doc --no-deps --all-featuresAPI documentation generation"] INDEX["printf redirect to index.htmlEntry point creation"] DEPLOY["JamesIves/github-pages-deploy-actionGitHub Pages deployment"] end BROKEN --> RUSTDOC DEPLOY --> GHPAGES GHPAGES --> DOCSRS INDEX --> LOCALDIR LOCALDIR --> DEPLOY MISSING --> RUSTDOC RUSTDOC --> LOCALDIR
Sources: .github/workflows/ci.yml(L32 - L55) .github/workflows/ci.yml(L40)
Documentation Standards
The documentation system enforces strict quality standards:
- Broken Link Detection: All intra-documentation links must be valid
- Coverage Requirement: All public APIs must be documented
- Automatic Deployment: Documentation updates are automatically published on the default branch
Sources: .github/workflows/ci.yml(L40)
Contributing Workflow
Pre-Commit Requirements
Before submitting changes, ensure all quality gates pass:
- Format Check:
cargo fmt --all -- --check
- Lint Check:
cargo clippy --all-features
- Build Verification: Test on all supported targets
- Unit Tests:
cargo test
onx86_64-unknown-linux-gnu
Supported Platforms
Development must consider compatibility across multiple target architectures:
Target | Environment | Purpose |
---|---|---|
x86_64-unknown-linux-gnu | Standard Linux | Development and testing |
x86_64-unknown-none | Bare metal x86_64 | Embedded systems |
riscv64gc-unknown-none-elf | RISC-V embedded | IoT and embedded |
aarch64-unknown-none-softfloat | ARM64 embedded | Mobile and embedded |
Sources: .github/workflows/ci.yml(L12)
The development process is designed to maintain the library's core principles of security, performance, and broad platform compatibility while ensuring code quality through automated verification.
Build System and CI
Relevant source files
This document covers the build system configuration, continuous integration pipeline, and multi-target support for the cap_access library. It explains how the project maintains code quality across multiple embedded and systems programming targets, automates testing and documentation generation, and ensures compatibility with no_std environments.
For information about the specific target architectures and their embedded considerations, see Multi-Platform Support.
Package Configuration
The cap_access library is configured as a Rust crate with specific metadata optimized for embedded and systems programming environments. The package configuration emphasizes no_std compatibility and integration with the ArceOS ecosystem.
Core Package Metadata
The project is defined in Cargo.toml(L1 - L12) with the following key characteristics:
Property | Value | Purpose |
---|---|---|
name | cap_access | Primary crate identifier |
version | 0.1.0 | Initial release version |
edition | 2021 | Rust 2021 edition features |
categories | ["os", "no-std"] | Operating systems and no_std environments |
keywords | ["arceos", "capabilities", "permission", "access-control"] | Discovery and classification |
The triple licensing scheme specified in Cargo.toml(L7) (GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0
) provides maximum compatibility with different project requirements, particularly important for kernel and embedded development where licensing constraints vary.
Dependencies
The crate maintains minimal dependencies to support no_std environments:
[dependencies]
bitflags = "2.6"
The single dependency on bitflags
version 2.6 Cargo.toml(L14 - L15) provides efficient flag operations for the capability system while maintaining no_std compatibility.
Sources: Cargo.toml(L1 - L16)
CI Pipeline Architecture
The continuous integration system implements a comprehensive multi-target testing strategy designed for embedded and systems programming environments. The pipeline validates code quality, builds across multiple architectures, and automates documentation deployment.
Pipeline Structure
flowchart TD subgraph subGraph2["Quality Checks"] FMT["cargo fmt --all -- --check"] CLIPPY["cargo clippy --target TARGET --all-features"] BUILD["cargo build --target TARGET --all-features"] TEST["cargo test --target TARGET"] end subgraph subGraph1["CI Job Matrix"] RUST_VERSION["nightly toolchain"] TARGET_X86_LINUX["x86_64-unknown-linux-gnu"] TARGET_X86_BARE["x86_64-unknown-none"] TARGET_RISCV["riscv64gc-unknown-none-elf"] TARGET_ARM["aarch64-unknown-none-softfloat"] end subgraph subGraph0["CI Triggers"] PUSH["push events"] PR["pull_request events"] end subgraph subGraph3["Documentation Job"] DOC_BUILD["cargo doc --no-deps --all-features"] INDEX_GEN["printf redirect to index.html"] PAGES_DEPLOY["JamesIves/github-pages-deploy-action@v4"] end CI_JOB["CI_JOB"] CI_JOB --> RUST_VERSION DOC_BUILD --> INDEX_GEN INDEX_GEN --> PAGES_DEPLOY PR --> CI_JOB PUSH --> CI_JOB RUST_VERSION --> TARGET_ARM RUST_VERSION --> TARGET_RISCV RUST_VERSION --> TARGET_X86_BARE RUST_VERSION --> TARGET_X86_LINUX TARGET_ARM --> BUILD TARGET_ARM --> CLIPPY TARGET_RISCV --> BUILD TARGET_RISCV --> CLIPPY TARGET_X86_BARE --> BUILD TARGET_X86_BARE --> CLIPPY TARGET_X86_LINUX --> BUILD TARGET_X86_LINUX --> CLIPPY TARGET_X86_LINUX --> FMT TARGET_X86_LINUX --> TEST
Sources: .github/workflows/ci.yml(L1 - L56)
Job Configuration Matrix
The CI pipeline uses a matrix strategy to test across multiple targets simultaneously. The configuration in .github/workflows/ci.yml(L8 - L12) defines:
strategy:
fail-fast: false
matrix:
rust-toolchain: [nightly]
targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat]
The fail-fast: false
setting ensures that failures on one target don't prevent testing of other targets, crucial for multi-architecture development.
Sources: .github/workflows/ci.yml(L8 - L12)
Quality Assurance Pipeline
The quality assurance process implements multiple validation stages to ensure code correctness and consistency across all supported targets.
Toolchain Setup
Each CI run establishes a complete Rust development environment using .github/workflows/ci.yml(L15 - L19) :
- uses: dtolnay/rust-toolchain@nightly
with:
toolchain: ${{ matrix.rust-toolchain }}
components: rust-src, clippy, rustfmt
targets: ${{ matrix.targets }}
The rust-src
component enables building for bare-metal targets that require custom standard libraries.
Code Quality Checks
flowchart TD subgraph subGraph1["Quality Enforcement"] CLIPPY_CONFIG["-A clippy::new_without_default"] FORMAT_STRICT["--check flag enforces formatting"] TEST_OUTPUT["--nocapture for debugging"] ALL_FEATURES["--all-features enables complete validation"] end subgraph subGraph0["Quality Gates"] VERSION_CHECK["rustc --version --verbose"] FORMAT_CHECK["cargo fmt --all -- --check"] LINT_CHECK["cargo clippy --target TARGET"] BUILD_CHECK["cargo build --target TARGET"] UNIT_TEST["cargo test --target TARGET"] end BUILD_CHECK --> ALL_FEATURES BUILD_CHECK --> UNIT_TEST FORMAT_CHECK --> FORMAT_STRICT FORMAT_CHECK --> LINT_CHECK LINT_CHECK --> BUILD_CHECK LINT_CHECK --> CLIPPY_CONFIG UNIT_TEST --> TEST_OUTPUT VERSION_CHECK --> FORMAT_CHECK
The pipeline enforces strict code formatting through .github/workflows/ci.yml(L22 - L23) and comprehensive linting via .github/workflows/ci.yml(L24 - L25) The clippy configuration includes -A clippy::new_without_default
to suppress warnings about missing Default
implementations, which may not be appropriate for capability-wrapped types.
Testing Strategy
Unit tests execute only on the x86_64-unknown-linux-gnu
target .github/workflows/ci.yml(L28 - L30) as bare-metal targets cannot run standard test frameworks. The test command includes --nocapture
to display all output for debugging purposes.
Sources: .github/workflows/ci.yml(L20 - L30)
Multi-Target Build Matrix
The build system supports diverse execution environments ranging from standard Linux development to bare-metal embedded systems.
Target Architecture Mapping
Target Triple | Architecture | Environment | Use Case |
---|---|---|---|
x86_64-unknown-linux-gnu | x86_64 | Standard Linux | Development and testing |
x86_64-unknown-none | x86_64 | Bare metal | Kernel development |
riscv64gc-unknown-none-elf | RISC-V 64-bit | Embedded ELF | IoT and embedded systems |
aarch64-unknown-none-softfloat | ARM64 | Embedded soft-float | Resource-constrained ARM systems |
Build Process Flow
flowchart TD subgraph subGraph2["Validation Per Target"] CLIPPY_LINUX["clippy x86_64-unknown-linux-gnu"] CLIPPY_X86_BARE["clippy x86_64-unknown-none"] CLIPPY_RISCV["clippy riscv64gc-unknown-none-elf"] CLIPPY_ARM["clippy aarch64-unknown-none-softfloat"] end subgraph subGraph1["Target-Specific Builds"] BUILD_LINUX["cargo build --target x86_64-unknown-linux-gnu"] BUILD_X86_BARE["cargo build --target x86_64-unknown-none"] BUILD_RISCV["cargo build --target riscv64gc-unknown-none-elf"] BUILD_ARM["cargo build --target aarch64-unknown-none-softfloat"] end subgraph subGraph0["Source Validation"] SOURCE_CODE["src/lib.rs"] CARGO_CONFIG["Cargo.toml"] end BUILD_ARM --> CLIPPY_ARM BUILD_LINUX --> CLIPPY_LINUX BUILD_RISCV --> CLIPPY_RISCV BUILD_X86_BARE --> CLIPPY_X86_BARE CARGO_CONFIG --> BUILD_ARM CARGO_CONFIG --> BUILD_LINUX CARGO_CONFIG --> BUILD_RISCV CARGO_CONFIG --> BUILD_X86_BARE SOURCE_CODE --> BUILD_ARM SOURCE_CODE --> BUILD_LINUX SOURCE_CODE --> BUILD_RISCV SOURCE_CODE --> BUILD_X86_BARE
Each target undergoes identical validation through .github/workflows/ci.yml(L24 - L27) ensuring consistent behavior across all supported platforms.
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L24 - L27)
Documentation Generation and Deployment
The documentation system automatically generates and deploys API documentation to GitHub Pages, providing accessible reference material for library users.
Documentation Build Process
The documentation job runs independently of the main CI pipeline .github/workflows/ci.yml(L32 - L56) and includes sophisticated error handling for different branch contexts:
- name: Build docs
continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }}
run: |
cargo doc --no-deps --all-features
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
The continue-on-error
condition in .github/workflows/ci.yml(L45) allows documentation failures on non-default branches while ensuring they block merges to the main branch.
Automated Index Generation
The documentation build process includes automatic index page creation .github/workflows/ci.yml(L46 - L48) that redirects to the main crate documentation, improving user experience when accessing the documentation site.
GitHub Pages Deployment
Documentation deployment uses the JamesIves/github-pages-deploy-action@v4
action .github/workflows/ci.yml(L49 - L55) with single-commit deployment to the gh-pages
branch, ensuring clean deployment history.
The deployment only triggers on pushes to the default branch .github/workflows/ci.yml(L50) preventing unnecessary deployments from feature branches.
Sources: .github/workflows/ci.yml(L32 - L56)
Build Environment Configuration
Environment Variables and Flags
The documentation build process configures strict linting through the RUSTDOCFLAGS
environment variable .github/workflows/ci.yml(L40) :
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
This configuration treats broken documentation links and missing documentation as errors, ensuring comprehensive and correct documentation.
Permission Configuration
The documentation job requires special permissions .github/workflows/ci.yml(L36 - L37) to write to the repository for GitHub Pages deployment:
permissions:
contents: write
Sources: .github/workflows/ci.yml(L36 - L40)
Multi-Platform Support
Relevant source files
This document covers the multi-platform architecture support implemented in cap_access
, including the supported target architectures, build matrix configuration, and platform-specific considerations for embedded and bare-metal environments.
For information about the overall build system and continuous integration setup, see Build System and CI. For details about how cap_access
integrates with ArceOS across different platforms, see ArceOS Integration.
Supported Target Architectures
The cap_access
library is designed to run across multiple hardware architectures and execution environments, with a particular focus on embedded and systems programming contexts. The library supports both hosted and bare-metal execution environments.
Target Architecture Matrix
flowchart TD subgraph subGraph2["cap_access Library"] core_lib["Core Libraryno_std compatible"] bitflags_dep["bitflags dependencyVersion 2.6"] end subgraph subGraph1["Bare Metal Environments"] x86_64_bare["x86_64-unknown-noneBare metal x86_64"] riscv64["riscv64gc-unknown-none-elfRISC-V 64-bit with extensions"] aarch64["aarch64-unknown-none-softfloatARM64 without hardware floating point"] end subgraph subGraph0["Linux Hosted Environment"] x86_64_linux["x86_64-unknown-linux-gnuStandard Linux target"] end bitflags_dep --> core_lib core_lib --> aarch64 core_lib --> riscv64 core_lib --> x86_64_bare core_lib --> x86_64_linux
Target Architecture Details
Target | Environment | Use Case | Floating Point |
---|---|---|---|
x86_64-unknown-linux-gnu | Hosted Linux | Development, testing | Hardware |
x86_64-unknown-none | Bare metal | Embedded x86_64 systems | Software |
riscv64gc-unknown-none-elf | Bare metal | RISC-V embedded boards | Software |
aarch64-unknown-none-softfloat | Bare metal | ARM64 embedded devices | Software |
Sources: .github/workflows/ci.yml(L12) Cargo.toml(L12)
Continuous Integration Build Matrix
The multi-platform support is validated through a comprehensive CI pipeline that builds and tests the library across all supported architectures.
CI Pipeline Architecture
flowchart TD subgraph subGraph3["Quality Checks"] fmt_check["cargo fmt --all --check"] clippy_check["cargo clippy --target TARGET"] build_check["cargo build --target TARGET"] unit_test["cargo test --target TARGET"] end subgraph subGraph2["Target Matrix Execution"] x86_linux_job["x86_64-unknown-linux-gnuBuild + Test + Clippy"] x86_bare_job["x86_64-unknown-noneBuild + Clippy"] riscv_job["riscv64gc-unknown-none-elfBuild + Clippy"] arm_job["aarch64-unknown-none-softfloatBuild + Clippy"] end subgraph subGraph1["Build Matrix Strategy"] rust_nightly["Rust Nightly Toolchain"] fail_fast_false["fail-fast: false"] end subgraph subGraph0["GitHub Actions Workflow"] trigger["Push/Pull Request Triggers"] ubuntu_runner["ubuntu-latest runner"] end arm_job --> build_check arm_job --> clippy_check fail_fast_false --> arm_job fail_fast_false --> riscv_job fail_fast_false --> x86_bare_job fail_fast_false --> x86_linux_job riscv_job --> build_check riscv_job --> clippy_check rust_nightly --> fail_fast_false trigger --> ubuntu_runner ubuntu_runner --> rust_nightly x86_bare_job --> build_check x86_bare_job --> clippy_check x86_linux_job --> build_check x86_linux_job --> clippy_check x86_linux_job --> fmt_check x86_linux_job --> unit_test
CI Pipeline Configuration Details
The CI pipeline implements a matrix strategy with the following characteristics:
- Rust Toolchain: Nightly with
rust-src
,clippy
, andrustfmt
components - Failure Strategy:
fail-fast: false
allows all targets to complete even if one fails - Testing Scope: Unit tests only run on
x86_64-unknown-linux-gnu
due to hosted environment requirements - Quality Checks: All targets undergo formatting, linting, and build verification
Sources: .github/workflows/ci.yml(L8 - L12) .github/workflows/ci.yml(L15 - L19) .github/workflows/ci.yml(L28 - L30)
Platform-Specific Considerations
No Standard Library Compatibility
The cap_access
library is designed with no_std
compatibility as a primary requirement, enabling deployment in resource-constrained embedded environments.
Dependency Management
The library maintains a minimal dependency footprint with only the bitflags
crate as an external dependency, ensuring compatibility with embedded environments that cannot support the full Rust standard library.
Sources: Cargo.toml(L14 - L15) Cargo.toml(L12)
Architecture-Specific Build Considerations
Each target architecture has specific build requirements and constraints:
x86_64 Targets
x86_64-unknown-linux-gnu
: Full hosted environment with standard library support for development and testingx86_64-unknown-none
: Bare metal target requiring#![no_std]
and custom runtime support
RISC-V Target
riscv64gc-unknown-none-elf
: Supports RISC-V 64-bit with general extensions (G) and compressed instructions (C)- Requires software floating-point operations
- Common target for embedded RISC-V development boards
ARM64 Target
aarch64-unknown-none-softfloat
: ARM64 architecture without hardware floating-point unit- Optimized for embedded ARM64 devices with software floating-point implementation
- Suitable for resource-constrained ARM-based systems
Sources: .github/workflows/ci.yml(L12)
Testing and Validation Strategy
The multi-platform support validation employs a targeted testing strategy that accounts for the constraints of different execution environments.
Platform Testing Matrix
flowchart TD subgraph subGraph1["Target Platform Coverage"] x86_linux["x86_64-unknown-linux-gnu✓ Format ✓ Lint ✓ Build ✓ Test"] x86_bare["x86_64-unknown-none✓ Format ✓ Lint ✓ Build ✗ Test"] riscv["riscv64gc-unknown-none-elf✓ Format ✓ Lint ✓ Build ✗ Test"] arm["aarch64-unknown-none-softfloat✓ Format ✓ Lint ✓ Build ✗ Test"] end subgraph subGraph0["Test Categories"] format_test["Code Formattingcargo fmt --all --check"] lint_test["Static Analysiscargo clippy"] build_test["Compilationcargo build"] unit_test["Runtime Testingcargo test"] end build_test --> arm build_test --> riscv build_test --> x86_bare build_test --> x86_linux format_test --> arm format_test --> riscv format_test --> x86_bare format_test --> x86_linux lint_test --> arm lint_test --> riscv lint_test --> x86_bare lint_test --> x86_linux unit_test --> x86_linux
Testing Constraints by Platform
- Hosted Platforms: Full test suite including unit tests can execute in the hosted Linux environment
- Bare Metal Platforms: Limited to static analysis and compilation verification due to lack of test runtime
- Cross-Compilation: All bare metal targets are cross-compiled from the x86_64 Linux host environment
The testing strategy ensures that while runtime testing is limited to hosted environments, all platforms receive comprehensive static analysis and successful compilation verification.
Sources: .github/workflows/ci.yml(L22 - L30) .github/workflows/ci.yml(L25) .github/workflows/ci.yml(L29)
Overview
Relevant source files
The kspin
crate provides kernel-space spinlock implementations with configurable protection levels for the ArceOS operating system ecosystem. This library offers three distinct spinlock types that control interrupt and preemption states during critical sections, enabling safe concurrent access to shared data in kernel environments.
This document covers the fundamental architecture, protection mechanisms, and usage patterns of the kspin
crate. For detailed implementation specifics of the core spinlock logic, see Core Implementation Architecture. For usage guidelines and safety considerations, see Usage Guidelines and Safety.
System Architecture
The kspin
crate implements a layered architecture where specialized spinlock types are built upon a generic BaseSpinLock
foundation. The system integrates with the kernel_guard
crate to provide different levels of protection through compile-time type safety.
Component Architecture
flowchart TD subgraph subGraph3["Feature Flags"] SMPFeature["smp feature"] end subgraph subGraph2["External Dependencies"] NoOp["kernel_guard::NoOp"] NoPreempt["kernel_guard::NoPreempt"] NoPreemptIrqSave["kernel_guard::NoPreemptIrqSave"] CfgIf["cfg-if"] end subgraph subGraph1["Core Implementation"] BaseSpinLock["BaseSpinLock"] BaseSpinLockGuard["BaseSpinLockGuard"] end subgraph subGraph0["Public API Layer"] SpinRaw["SpinRaw"] SpinNoPreempt["SpinNoPreempt"] SpinNoIrq["SpinNoIrq"] SpinRawGuard["SpinRawGuard"] SpinNoPreemptGuard["SpinNoPreemptGuard"] SpinNoIrqGuard["SpinNoIrqGuard"] end BaseSpinLock --> CfgIf BaseSpinLock --> NoOp BaseSpinLock --> NoPreempt BaseSpinLock --> NoPreemptIrqSave BaseSpinLock --> SMPFeature SpinNoIrq --> BaseSpinLock SpinNoIrqGuard --> BaseSpinLockGuard SpinNoPreempt --> BaseSpinLock SpinNoPreemptGuard --> BaseSpinLockGuard SpinRaw --> BaseSpinLock SpinRawGuard --> BaseSpinLockGuard
Sources: src/lib.rs(L1 - L37) Cargo.toml(L19 - L21) Cargo.toml(L14 - L17)
Protection Level Hierarchy
The kspin
crate provides three protection levels, each corresponding to different kernel execution contexts and safety requirements. The protection levels are implemented through type aliases that parameterize the generic BaseSpinLock
with specific guard types.
Protection Levels and Guard Mapping
flowchart TD subgraph subGraph3["Usage Context"] IRQDisabledContext["IRQ-disabled context"] GeneralKernelContext["General kernel context"] InterruptHandler["Interrupt handler"] end subgraph subGraph2["Protection Behavior"] NoBehavior["No protectionManual control required"] PreemptionDisabled["Disables preemptionIRQ context required"] FullProtection["Disables preemption + IRQsAny context safe"] end subgraph subGraph1["Guard Types"] NoOp["NoOp"] NoPreempt["NoPreempt"] NoPreemptIrqSave["NoPreemptIrqSave"] end subgraph subGraph0["Spinlock Types"] SpinRaw["SpinRaw"] SpinNoPreempt["SpinNoPreempt"] SpinNoIrq["SpinNoIrq"] end FullProtection --> GeneralKernelContext FullProtection --> InterruptHandler NoBehavior --> IRQDisabledContext NoOp --> NoBehavior NoPreempt --> PreemptionDisabled NoPreemptIrqSave --> FullProtection PreemptionDisabled --> IRQDisabledContext SpinNoIrq --> NoPreemptIrqSave SpinNoPreempt --> NoPreempt SpinRaw --> NoOp
Sources: src/lib.rs(L10 - L36) README.md(L16 - L33)
Key Design Principles
Principle | Implementation | Benefit |
---|---|---|
Type Safety | Guard types encode protection requirements | Compile-time prevention of misuse |
RAII Pattern | BaseSpinLockGuardensures cleanup | Automatic lock release on scope exit |
Zero-Cost Abstraction | smpfeature eliminates overhead | Single-core optimization |
Flexible Protection | Three distinct protection levels | Context-appropriate safety |
The crate follows a compile-time optimization strategy where the smp
feature flag controls whether actual atomic operations are generated. In single-core environments, the lock state is optimized away entirely, reducing the spinlock to a simple guard management system.
Sources: Cargo.toml(L14 - L17) README.md(L12)
Integration with ArceOS Ecosystem
The kspin
crate serves as a foundational synchronization primitive within the ArceOS operating system project. It provides the kernel-level locking mechanisms required for:
- Memory management subsystems requiring atomic access to page tables and allocation structures
- Device driver frameworks needing interrupt-safe critical sections
- Scheduler components protecting task queues and scheduling state
- File system implementations ensuring metadata consistency
The library's design emphasizes kernel-space usage through its dependency on kernel_guard
, which provides the underlying preemption and interrupt control mechanisms. The three-tier protection model allows different kernel subsystems to choose the appropriate level of protection based on their execution context requirements.
Sources: Cargo.toml(L6) Cargo.toml(L8) src/lib.rs(L6)
Compilation Model
The kspin
crate uses conditional compilation to adapt its behavior based on target environment characteristics:
flowchart TD subgraph subGraph2["Multi-Core Build"] AtomicState["AtomicBool lock state"] CompareExchange["compare_exchange operations"] SpinBehavior["Actual spinning behavior"] end subgraph subGraph1["Single-Core Build"] NoLockState["No lock state field"] NoAtomics["No atomic operations"] AlwaysSucceed["Lock always succeeds"] end subgraph subGraph0["Feature Detection"] SMPCheck["smp feature enabled?"] end SMPCheck --> AlwaysSucceed SMPCheck --> AtomicState SMPCheck --> CompareExchange SMPCheck --> NoAtomics SMPCheck --> NoLockState SMPCheck --> SpinBehavior
This compilation strategy ensures that embedded and single-core kernel environments receive fully optimized code with no synchronization overhead, while multi-core systems get the full atomic operation implementation required for correct concurrent behavior.
Sources: README.md(L12) Cargo.toml(L15 - L16)
Project Structure and Dependencies
Relevant source files
This document describes the organizational structure of the kspin crate, including its file layout, external dependencies, and build configuration. It covers the project's dependency management, feature flag system, and how the crate is organized to support different compilation targets.
For detailed information about the specific spinlock types and their usage, see Spinlock Types and Public API. For implementation details of the core architecture, see Core Implementation Architecture.
File Structure Overview
The kspin crate follows a minimal but well-organized structure typical of focused Rust libraries. The project consists of core source files, build configuration, and development tooling.
Project File Organization
flowchart TD subgraph subGraph2["Development Tools"] VSCodeDir[".vscode/IDE configuration"] TargetDir["target/Build artifacts (ignored)"] end subgraph subGraph1["Source Code (src/)"] LibRs["lib.rsPublic API & type aliases"] BaseRs["base.rsCore BaseSpinLock implementation"] end subgraph subGraph0["Root Directory"] CargoToml["Cargo.tomlProject metadata & dependencies"] CargoLock["Cargo.lockDependency lock file"] GitIgnore[".gitignoreVersion control exclusions"] end subgraph CI/CD["CI/CD"] GithubActions[".github/workflows/Automated testing & docs"] end CargoLock --> CargoToml CargoToml --> BaseRs CargoToml --> LibRs GitIgnore --> TargetDir GitIgnore --> VSCodeDir LibRs --> BaseRs
Sources: Cargo.toml(L1 - L22) .gitignore(L1 - L4) Cargo.lock(L1 - L74)
Source File Responsibilities
File | Purpose | Key Components |
---|---|---|
src/lib.rs | Public API surface | SpinRaw,SpinNoPreempt,SpinNoIrqtype aliases |
src/base.rs | Core implementation | BaseSpinLock,BaseSpinLockGuard,BaseGuardtrait |
The crate deliberately maintains a minimal file structure with only two source files, emphasizing simplicity and focused functionality.
Sources: Cargo.toml(L1 - L22)
External Dependencies
The kspin crate has a carefully curated set of external dependencies that provide essential functionality for kernel-space synchronization and conditional compilation.
Direct Dependencies
flowchart TD subgraph subGraph2["Transitive Dependencies"] CrateInterface["crate_interfacev0.1.4Interface definitions"] ProcMacro2["proc-macro2v1.0.93Procedural macros"] Quote["quotev1.0.38Code generation"] Syn["synv2.0.96Syntax parsing"] UnicodeIdent["unicode-identv1.0.14Unicode support"] end subgraph subGraph1["Direct Dependencies"] CfgIf["cfg-ifv1.0.0Conditional compilation"] KernelGuard["kernel_guardv0.1.2Protection mechanisms"] end subgraph subGraph0["kspin Crate"] KSpin["kspinv0.1.0"] end CrateInterface --> ProcMacro2 CrateInterface --> Quote CrateInterface --> Syn KSpin --> CfgIf KSpin --> KernelGuard KernelGuard --> CfgIf KernelGuard --> CrateInterface ProcMacro2 --> UnicodeIdent Quote --> ProcMacro2 Syn --> ProcMacro2 Syn --> Quote Syn --> UnicodeIdent
Sources: Cargo.toml(L19 - L21) Cargo.lock(L5 - L74)
Dependency Roles
Crate | Version | Purpose | Usage in kspin |
---|---|---|---|
cfg-if | 1.0.0 | Conditional compilation utilities | Enables SMP vs single-core optimizations |
kernel_guard | 0.1.2 | Kernel protection mechanisms | ProvidesNoOp,NoPreempt,NoPreemptIrqSaveguards |
The kernel_guard
crate is the primary external dependency, providing the guard types that implement different protection levels. The cfg-if
crate enables clean conditional compilation based on feature flags.
Sources: Cargo.toml(L20 - L21) Cargo.lock(L23 - L30)
Feature Configuration
The kspin crate uses Cargo feature flags to enable compile-time optimization for different target environments, particularly distinguishing between single-core and multi-core systems.
Feature Flag System
flowchart TD subgraph subGraph2["Generated Code Characteristics"] SMPCode["SMP Implementation• AtomicBool for lock state• compare_exchange operations• Actual spinning behavior"] SingleCoreCode["Single-core Implementation• No lock state field• Lock always succeeds• No atomic operations"] end subgraph subGraph1["Compilation Modes"] SMPMode["SMP ModeMulti-core systems"] SingleCoreMode["Single-core ModeEmbedded/simple systems"] end subgraph subGraph0["Feature Flags"] SMPFeature["smpMulti-core support"] DefaultFeature["defaultEmpty set"] end DefaultFeature --> SingleCoreMode SMPFeature --> SMPMode SMPMode --> SMPCode SingleCoreMode --> SingleCoreCode
Sources: Cargo.toml(L14 - L17)
Feature Flag Details
Feature | Default | Description | Impact |
---|---|---|---|
smp | No | Enable multi-core support | Adds atomic operations and actual lock state |
default | Yes | Default feature set (empty) | Optimized for single-core environments |
The feature system allows the same codebase to generate dramatically different implementations:
- Without
smp
: Lock operations are compile-time no-ops, eliminating all atomic overhead - With
smp
: Full atomic spinlock implementation with proper memory ordering
Sources: Cargo.toml(L14 - L17)
Build System Organization
The project follows standard Rust crate conventions with specific configurations for kernel-space development and multi-platform support.
Package Metadata Configuration
flowchart TD subgraph Licensing["Licensing"] License["licenseGPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"] end subgraph subGraph2["Repository Links"] Homepage["homepageArceOS GitHub"] Repository["repositorykspin GitHub"] Documentation["documentationdocs.rs"] end subgraph subGraph1["Package Classification"] Keywords["keywords['arceos', 'synchronization', 'spinlock', 'no-irq']"] Categories["categories['os', 'no-std']"] Description["description'Spinlocks for kernel space...'"] end subgraph subGraph0["Package Identity"] Name["name = 'kspin'"] Version["version = '0.1.0'"] Edition["edition = '2021'"] Authors["authors = ['Yuekai Jia']"] end Description --> Repository Homepage --> License Name --> Keywords Repository --> Documentation Version --> Categories
Sources: Cargo.toml(L1 - L12)
Development Environment Setup
The project includes configuration for common development tools:
Tool | Configuration | Purpose |
---|---|---|
Git | .gitignoreexcludes/target,/.vscode,.DS_Store | Clean repository state |
VS Code | .vscode/directory (ignored) | IDE-specific settings |
Cargo | Cargo.lockcommitted | Reproducible builds |
The .gitignore
configuration ensures that build artifacts and IDE-specific files don't pollute the repository, while the committed Cargo.lock
file ensures reproducible builds across different environments.
Sources: .gitignore(L1 - L4) Cargo.lock(L1 - L4)
Spinlock Types and Public API
Relevant source files
This document covers the three main spinlock types exposed by the kspin crate and their public interface. These types provide different levels of protection suitable for various kernel contexts. For detailed implementation internals, see Core Implementation Architecture. For comprehensive usage guidelines and safety requirements, see Usage Guidelines and Safety.
Spinlock Type Overview
The kspin crate provides three specialized spinlock types, each offering a different balance between performance and protection. All types are implemented as type aliases of the generic BaseSpinLock
with different guard types from the kernel_guard
crate.
Spinlock Type | Guard Type | Protection Level | Use Context |
---|---|---|---|
SpinRaw | NoOp | No protection | Preemption and IRQ-disabled contexts |
SpinNoPreempt | NoPreempt | Disables preemption | IRQ-disabled contexts |
SpinNoIrq | NoPreemptIrqSave | Disables preemption + IRQs | Any context |
Sources: src/lib.rs(L10 - L36)
Type Hierarchy and Guard Relationships
flowchart TD subgraph subGraph2["Core Implementation (src/base.rs)"] BaseSpinLock["BaseSpinLock<G, T>Generic spinlock"] BaseSpinLockGuard["BaseSpinLockGuard<'a, G, T>RAII guard"] end subgraph subGraph1["kernel_guard Crate Guards"] NoOp["NoOpDoes nothing"] NoPreempt["NoPreemptDisables preemption"] NoPreemptIrqSave["NoPreemptIrqSaveDisables preemption + IRQs"] end subgraph subGraph0["Public API Types (src/lib.rs)"] SpinRaw["SpinRaw<T>BaseSpinLock<NoOp, T>"] SpinNoPreempt["SpinNoPreempt<T>BaseSpinLock<NoPreempt, T>"] SpinNoIrq["SpinNoIrq<T>BaseSpinLock<NoPreemptIrqSave, T>"] SpinRawGuard["SpinRawGuard<'a, T>BaseSpinLockGuard<'a, NoOp, T>"] SpinNoPreemptGuard["SpinNoPreemptGuard<'a, T>BaseSpinLockGuard<'a, NoPreempt, T>"] SpinNoIrqGuard["SpinNoIrqGuard<'a, T>BaseSpinLockGuard<'a, NoPreemptIrqSave, T>"] end SpinNoIrq --> BaseSpinLock SpinNoIrq --> NoPreemptIrqSave SpinNoIrq --> SpinNoIrqGuard SpinNoIrqGuard --> BaseSpinLockGuard SpinNoPreempt --> BaseSpinLock SpinNoPreempt --> NoPreempt SpinNoPreempt --> SpinNoPreemptGuard SpinNoPreemptGuard --> BaseSpinLockGuard SpinRaw --> BaseSpinLock SpinRaw --> NoOp SpinRaw --> SpinRawGuard SpinRawGuard --> BaseSpinLockGuard
Sources: src/lib.rs(L6 - L36)
Public API Structure
flowchart TD subgraph subGraph2["RAII Lifecycle"] acquire["Guard AcquisitionProtection enabled"] critical["Critical SectionData access"] release["Guard DropProtection disabled"] end subgraph subGraph1["Guard Operations"] deref["Deref::deref()→ &T"] deref_mut["DerefMut::deref_mut()→ &mut T"] drop_guard["Drop::drop()→ ()"] end subgraph subGraph0["Spinlock Operations"] new["new(data: T)→ SpinLock<T>"] lock["lock()→ Guard<'_, T>"] try_lock["try_lock()→ Option<Guard<'_, T>>"] is_locked["is_locked()→ bool"] end acquire --> deref acquire --> deref_mut critical --> drop_guard deref --> critical deref_mut --> critical drop_guard --> release lock --> acquire new --> lock new --> try_lock try_lock --> acquire
Sources: src/lib.rs(L8) README.md(L19 - L32)
Type Definitions and Documentation
SpinRaw
SpinRaw<T>
provides the fastest spinlock implementation with no built-in protection mechanisms. It is defined as BaseSpinLock<NoOp, T>
where the NoOp
guard performs no protection operations.
Key Characteristics:
- Zero protection overhead
- Requires manual IRQ and preemption management
- Must be used in preemption-disabled and IRQ-disabled contexts
- Cannot be used in interrupt handlers
Sources: src/lib.rs(L29 - L33)
SpinNoPreempt
SpinNoPreempt<T>
disables kernel preemption during lock acquisition and critical sections. It is defined as BaseSpinLock<NoPreempt, T>
.
Key Characteristics:
- Automatically disables preemption when acquiring the lock
- Safe from task switching but not from interrupts
- Must be used in IRQ-disabled contexts
- Cannot be used in interrupt handlers
Sources: src/lib.rs(L10 - L15)
SpinNoIrq
SpinNoIrq<T>
provides the highest level of protection by disabling both kernel preemption and local IRQs. It is defined as BaseSpinLock<NoPreemptIrqSave, T>
.
Key Characteristics:
- Disables both preemption and IRQs
- Safe to use in any context including IRQ-enabled environments
- Highest protection overhead but maximum safety
- Can be used in interrupt handlers
Sources: src/lib.rs(L20 - L24)
Associated Guard Types
Each spinlock type has a corresponding guard type that implements the RAII pattern:
SpinRawGuard<'a, T>
forSpinRaw<T>
SpinNoPreemptGuard<'a, T>
forSpinNoPreempt<T>
SpinNoIrqGuard<'a, T>
forSpinNoIrq<T>
These guards provide mutable access to the protected data through Deref
and DerefMut
implementations, and automatically release the lock when dropped.
Sources: src/lib.rs(L17 - L18) src/lib.rs(L26 - L27) src/lib.rs(L35 - L36)
Usage Example from Public Documentation
The crate provides standard usage patterns as demonstrated in the README:
// Raw spinlock - fastest, requires manual protection
let data = SpinRaw::new(());
let mut guard = data.lock();
/* critical section, does nothing while trying to lock. */
drop(guard);
// Preemption-disabled spinlock
let data = SpinNoPreempt::new(());
let mut guard = data.lock();
/* critical section, preemption are disabled. */
drop(guard);
// Full protection spinlock
let data = SpinNoIrq::new(());
let mut guard = data.lock();
/* critical section, both preemption and IRQs are disabled. */
drop(guard);
Sources: README.md(L16 - L33)
SpinRaw
Relevant source files
Purpose and Scope
This page documents SpinRaw<T>
, the raw spinlock implementation in the kspin crate that provides no built-in protection mechanisms. SpinRaw
offers the fastest performance among the three spinlock types but requires manual management of preemption and interrupt state by the caller.
For information about spinlocks with built-in preemption protection, see SpinNoPreempt. For full protection including IRQ disabling, see SpinNoIrq. For detailed implementation internals, see BaseSpinLock and BaseSpinLockGuard. For comprehensive usage guidelines across all spinlock types, see Usage Guidelines and Safety.
Type Definition and Basic Interface
SpinRaw<T>
is implemented as a type alias that specializes the generic BaseSpinLock
with the NoOp
guard type from the kernel_guard
crate.
Core Type Definitions
flowchart TD subgraph subGraph0["SpinRaw Type System"] SpinRaw["SpinRaw<T>Type alias"] BaseSpinLock["BaseSpinLock<NoOp, T>Generic implementation"] SpinRawGuard["SpinRawGuard<'a, T>RAII guard"] BaseSpinLockGuard["BaseSpinLockGuard<'a, NoOp, T>Generic guard"] NoOp["NoOpkernel_guard guard type"] end BaseSpinLock --> BaseSpinLockGuard BaseSpinLock --> NoOp BaseSpinLockGuard --> NoOp SpinRaw --> BaseSpinLock SpinRawGuard --> BaseSpinLockGuard
Sources: src/lib.rs(L33 - L36)
The SpinRaw<T>
type provides the standard spinlock interface methods inherited from BaseSpinLock
:
Method | Return Type | Description |
---|---|---|
new(data: T) | SpinRaw | Creates a new spinlock containing the given data |
lock() | SpinRawGuard<'_, T> | Acquires the lock, spinning until successful |
try_lock() | Option<SpinRawGuard<'_, T>> | Attempts to acquire the lock without spinning |
Protection Behavior and NoOp Guard
The defining characteristic of SpinRaw
is its use of the NoOp
guard type, which performs no protection actions during lock acquisition or release.
Protection Flow Diagram
flowchart TD subgraph subGraph1["NoOp Guard Behavior"] NoOpAcquire["NoOp::acquire()Does nothing"] NoOpRelease["NoOp::release()Does nothing"] end subgraph subGraph0["SpinRaw Lock Acquisition Flow"] Start["lock() called"] TryAcquire["Attempt atomic acquisition"] IsLocked["Lock alreadyheld?"] Spin["Spin wait(busy loop)"] Acquired["Lock acquired"] GuardCreated["SpinRawGuard createdwith NoOp::acquire()"] end note1["Note: No preemption disablingNo IRQ disablingNo protection barriers"] Acquired --> GuardCreated GuardCreated --> NoOpAcquire GuardCreated --> NoOpRelease IsLocked --> Acquired IsLocked --> Spin NoOpAcquire --> note1 NoOpRelease --> note1 Spin --> TryAcquire Start --> TryAcquire TryAcquire --> IsLocked
Sources: src/lib.rs(L29 - L36) src/base.rs
Unlike SpinNoPreempt
and SpinNoIrq
, the NoOp
guard performs no system-level protection operations:
- No preemption disabling/enabling
- No interrupt disabling/enabling
- No memory barriers beyond those required for the atomic lock operations
Safety Requirements and Usage Contexts
SpinRaw
places the burden of ensuring safe usage entirely on the caller. The lock itself provides only the basic mutual exclusion mechanism without any system-level protections.
Required Caller Responsibilities
flowchart TD subgraph subGraph1["Consequences of Violation"] Deadlock["Potential deadlockif preempted while holding lock"] RaceCondition["Race conditionswith interrupt handlers"] DataCorruption["Data corruptionfrom concurrent access"] end subgraph subGraph0["Caller Must Ensure"] PreemptDisabled["Preemption already disabledBefore calling lock()"] IRQDisabled["Local IRQs already disabledBefore calling lock()"] NoInterruptUse["Never used ininterrupt handlers"] ProperContext["Operating in appropriatekernel context"] end subgraph subGraph2["Safe Usage Pattern"] DisablePreempt["disable_preemption()"] DisableIRQ["disable_local_irq()"] UseLock["SpinRaw::lock()"] CriticalSection["/* critical section */"] ReleaseLock["drop(guard)"] EnableIRQ["enable_local_irq()"] EnablePreempt["enable_preemption()"] end CriticalSection --> ReleaseLock DisableIRQ --> UseLock DisablePreempt --> DisableIRQ EnableIRQ --> EnablePreempt IRQDisabled --> RaceCondition NoInterruptUse --> RaceCondition PreemptDisabled --> Deadlock ProperContext --> DataCorruption ReleaseLock --> EnableIRQ UseLock --> CriticalSection
Sources: src/lib.rs(L31 - L32) README.md(L19 - L22)
Performance Characteristics
SpinRaw
offers the highest performance among the three spinlock types due to its minimal overhead approach.
Performance Comparison
Spinlock Type | Acquisition Overhead | Release Overhead | Context Switches |
---|---|---|---|
SpinRaw | Atomic operation only | Atomic operation only | Manual control required |
SpinNoPreempt | Atomic + preemption disable | Atomic + preemption enable | Prevented during lock |
SpinNoIrq | Atomic + preemption + IRQ disable | Atomic + preemption + IRQ enable | Fully prevented |
SMP vs Single-Core Behavior
flowchart TD subgraph subGraph2["Feature Flag Impact on SpinRaw"] SMPEnabled["smp featureenabled?"] subgraph subGraph1["Single-Core Path (no smp)"] NoLockField["No lock fieldoptimized out"] NoAtomics["No atomic operationslock always succeeds"] NoSpinning["No spinning behaviorimmediate success"] NoOverhead["Zero overheadcompile-time optimization"] end subgraph subGraph0["Multi-Core Path (smp)"] AtomicField["AtomicBool lock fieldin BaseSpinLock"] CompareExchange["compare_exchange operationsfor acquisition"] RealSpin["Actual spinning behavioron contention"] MemoryOrder["Memory orderingconstraints"] end end AtomicField --> CompareExchange CompareExchange --> RealSpin NoAtomics --> NoSpinning NoLockField --> NoAtomics NoSpinning --> NoOverhead RealSpin --> MemoryOrder SMPEnabled --> AtomicField SMPEnabled --> NoLockField
Sources: README.md(L12) src/base.rs
In single-core environments (without the smp
feature), SpinRaw
becomes a zero-cost abstraction since no actual locking is necessary when preemption and interrupts are properly controlled.
Integration with BaseSpinLock Architecture
SpinRaw
leverages the generic BaseSpinLock
implementation while providing no additional protection through its NoOp
guard parameter.
Architectural Mapping
classDiagram class SpinRaw { <<type alias>> +new(data: T) SpinRaw~T~ +lock() SpinRawGuard~T~ +try_lock() Option~SpinRawGuard~T~~ } class BaseSpinLock~NoOp_T~ { -lock_state: AtomicBool +new(data: T) Self +lock() BaseSpinLockGuard~NoOp_T~ +try_lock() Option~BaseSpinLockGuard~NoOp_T~~ -try_lock_inner() bool } class SpinRawGuard { <<type alias>> +deref() &T +deref_mut() &mut T } class BaseSpinLockGuard~NoOp_T~ { -lock: BaseSpinLock~NoOp_T~ -guard_state: NoOp +new() Self +deref() &T +deref_mut() &mut T } class NoOp { +acquire() Self +release() } SpinRaw --|> BaseSpinLock : type alias SpinRawGuard --|> BaseSpinLockGuard : type alias BaseSpinLock --> BaseSpinLockGuard : creates BaseSpinLockGuard --> NoOp : uses
Sources: src/lib.rs(L33 - L36) src/base.rs
The SpinRaw
type inherits all functionality from BaseSpinLock
while the NoOp
guard ensures minimal overhead by performing no protection operations during the guard's lifetime.
SpinNoPreempt
Relevant source files
This document describes the SpinNoPreempt
spinlock type, which provides kernel-space synchronization by disabling preemption during critical sections. This spinlock offers a balanced approach between performance and safety, suitable for IRQ-disabled contexts where preemption control is necessary but interrupt masking is already handled externally.
For information about the raw spinlock without protection mechanisms, see SpinRaw. For the full-protection spinlock that also disables IRQs, see SpinNoIrq. For comprehensive usage guidelines across all spinlock types, see Usage Guidelines and Safety.
Type Definition and Core Components
SpinNoPreempt<T>
is implemented as a type alias that combines the generic BaseSpinLock
with the NoPreempt
guard type from the kernel_guard
crate. This composition provides preemption-disabled synchronization while maintaining type safety and RAII semantics.
Type Composition Diagram
flowchart TD SpinNoPreempt["SpinNoPreempt<T>"] BaseSpinLock["BaseSpinLock<NoPreempt, T>"] NoPreempt["NoPreempt"] SpinNoPreemptGuard["SpinNoPreemptGuard<'a, T>"] BaseSpinLockGuard["BaseSpinLockGuard<'a, NoPreempt, T>"] BaseSpinLock --> NoPreempt BaseSpinLock --> SpinNoPreemptGuard BaseSpinLockGuard --> NoPreempt SpinNoPreempt --> BaseSpinLock SpinNoPreemptGuard --> BaseSpinLockGuard
Sources: src/lib.rs(L15 - L18)
The key components are:
Component | Definition | Purpose |
---|---|---|
SpinNoPreempt | BaseSpinLock<NoPreempt, T> | Main spinlock type that holds data of typeT |
SpinNoPreemptGuard<'a, T> | BaseSpinLockGuard<'a, NoPreempt, T> | RAII guard providing mutable access to protected data |
NoPreempt | Guard type fromkernel_guardcrate | Implements preemption disable/enable behavior |
Sources: src/lib.rs(L15 - L18) src/lib.rs(L6)
Safety Requirements and Usage Context
SpinNoPreempt
has specific safety requirements that must be understood before use. The spinlock is designed for contexts where IRQ handling is already managed externally, but preemption control is needed for the critical section.
Safety Context Requirements
flowchart TD subgraph subGraph2["SpinNoPreempt Behavior"] AcquireLock["lock() called"] DisablePreemption["NoPreempt::acquire()"] CriticalExecution["Protected code execution"] EnablePreemption["NoPreempt::release()"] ReleaseLock["Guard dropped"] end subgraph subGraph1["Unsafe Usage Contexts"] IRQEnabled["IRQ-Enabled Context"] InterruptHandler["Interrupt Handler"] UserSpace["User Space"] end subgraph subGraph0["Safe Usage Contexts"] IRQDisabled["IRQ-Disabled Context"] KernelCode["Kernel Code with IRQ Control"] CriticalSection["Existing Critical Section"] end AcquireLock --> DisablePreemption CriticalExecution --> EnablePreemption CriticalSection --> AcquireLock DisablePreemption --> CriticalExecution EnablePreemption --> ReleaseLock IRQDisabled --> AcquireLock IRQEnabled --> AcquireLock InterruptHandler --> AcquireLock KernelCode --> AcquireLock
Sources: src/lib.rs(L10 - L14)
Key Safety Rules
- IRQ Context Requirement: Must be used in local IRQ-disabled context
- Interrupt Handler Prohibition: Never use in interrupt handlers
- Preemption Assumption: Assumes external IRQ management is already in place
The documentation explicitly states: "It must be used in the local IRQ-disabled context, or never be used in interrupt handlers."
Sources: src/lib.rs(L13 - L14)
Implementation Architecture
SpinNoPreempt
leverages the modular architecture of the kspin crate, where the BaseSpinLock
provides the core spinning logic and the NoPreempt
guard type defines the protection behavior.
Lock Acquisition Flow
Sources: src/lib.rs(L15) src/base.rs (referenced via BaseSpinLock usage)
Comparison with Other Spinlock Types
SpinNoPreempt
occupies the middle ground in the protection spectrum provided by the kspin crate, offering more protection than SpinRaw
but less overhead than SpinNoIrq
.
Protection Level Comparison
Spinlock Type | Preemption Control | IRQ Control | Usage Context | Performance |
---|---|---|---|---|
SpinRaw | None | None | IRQ-disabled + preemption-disabled | Fastest |
SpinNoPreempt | Disabled during lock | Must be externally disabled | IRQ-disabled contexts | Balanced |
SpinNoIrq | Disabled during lock | Disabled during lock | Any context | Safest but slower |
Sources: src/lib.rs(L15) src/lib.rs(L24) src/lib.rs(L33)
Guard Type Mapping
flowchart TD subgraph subGraph2["Protection Behavior"] NoneProtection["No protection"] PreemptProtection["Preemption disabled"] FullProtection["Preemption + IRQ disabled"] end subgraph subGraph1["Guard Types (kernel_guard)"] NoOp["NoOp"] NoPreempt["NoPreempt"] NoPreemptIrqSave["NoPreemptIrqSave"] end subgraph subGraph0["Spinlock Types"] SpinRaw["SpinRaw<T>"] SpinNoPreempt["SpinNoPreempt<T>"] SpinNoIrq["SpinNoIrq<T>"] end NoOp --> NoneProtection NoPreempt --> PreemptProtection NoPreemptIrqSave --> FullProtection SpinNoIrq --> NoPreemptIrqSave SpinNoPreempt --> NoPreempt SpinRaw --> NoOp
Sources: src/lib.rs(L6) src/lib.rs(L15) src/lib.rs(L24) src/lib.rs(L33)
Usage Patterns and Examples
The typical usage pattern for SpinNoPreempt
follows the RAII principle, where the guard automatically manages preemption state during its lifetime.
Basic Usage Pattern
From the repository examples, the standard pattern is:
let data = SpinNoPreempt::new(());
let mut guard = data.lock();
/* critical section, preemption are disabled. */
drop(guard);
Sources: README.md(L24 - L27)
Method Interface
SpinNoPreempt<T>
inherits all methods from BaseSpinLock<NoPreempt, T>
, providing the standard spinlock interface:
Method | Return Type | Purpose |
---|---|---|
new(data: T) | Self | Create new spinlock with initial data |
lock() | SpinNoPreemptGuard<'_, T> | Acquire lock, blocking until available |
try_lock() | Option<SpinNoPreemptGuard<'_, T>> | Attempt to acquire lock without blocking |
The guard provides:
- Automatic preemption re-enabling on drop
- Mutable access to protected data via
Deref
andDerefMut
- Lock release when guard goes out of scope
Sources: src/lib.rs(L15 - L18) src/base.rs (inherited interface)
SpinNoIrq
Relevant source files
SpinNoIrq
is the full-protection spinlock implementation in the kspin crate that provides maximum safety by disabling both kernel preemption and local interrupts during lock acquisition and critical sections. This is the safest but most performance-intensive spinlock variant, designed for use in IRQ-enabled contexts where complete isolation is required.
For information about lighter-weight spinlock variants, see SpinRaw and SpinNoPreempt. For comprehensive usage guidelines across all spinlock types, see Usage Guidelines and Safety.
Type Definition and Core Components
SpinNoIrq
is implemented as a type alias that specializes the generic BaseSpinLock
with the NoPreemptIrqSave
guard type from the kernel_guard
crate.
flowchart TD subgraph subGraph0["SpinNoIrq Type System"] SpinNoIrq["SpinNoIrq<T>Type alias"] BaseSpinLock["BaseSpinLock<NoPreemptIrqSave, T>Generic implementation"] SpinNoIrqGuard["SpinNoIrqGuard<'a, T>RAII guard"] BaseSpinLockGuard["BaseSpinLockGuard<'a, NoPreemptIrqSave, T>Generic guard"] NoPreemptIrqSave["NoPreemptIrqSavekernel_guard protection"] end BaseSpinLock --> NoPreemptIrqSave BaseSpinLockGuard --> NoPreemptIrqSave SpinNoIrq --> BaseSpinLock SpinNoIrqGuard --> BaseSpinLockGuard
The core type definitions establish the relationship between the public API and the underlying implementation:
Component | Definition | Purpose |
---|---|---|
SpinNoIrq | BaseSpinLock<NoPreemptIrqSave, T> | Main spinlock type for data of typeT |
SpinNoIrqGuard<'a, T> | BaseSpinLockGuard<'a, NoPreemptIrqSave, T> | RAII guard providing mutable access to protected data |
Sources: src/lib.rs(L24 - L27)
Protection Mechanism
SpinNoIrq
implements a dual-protection mechanism that addresses both concurrency sources in kernel environments: task preemption and interrupt handling.
sequenceDiagram participant CallingThread as "Calling Thread" participant SpinNoIrq as "SpinNoIrq" participant NoPreemptIrqSave as "NoPreemptIrqSave" participant KernelScheduler as "Kernel Scheduler" participant InterruptController as "Interrupt Controller" CallingThread ->> SpinNoIrq: lock() SpinNoIrq ->> NoPreemptIrqSave: acquire() NoPreemptIrqSave ->> KernelScheduler: disable_preemption() NoPreemptIrqSave ->> InterruptController: save_and_disable_irqs() Note over CallingThread,InterruptController: Critical Section<br>- No task switches possible<br>- No interrupts delivered<br>- Complete isolation SpinNoIrq ->> CallingThread: SpinNoIrqGuard CallingThread ->> CallingThread: /* access protected data */ CallingThread ->> SpinNoIrq: drop(guard) SpinNoIrq ->> NoPreemptIrqSave: release() NoPreemptIrqSave ->> InterruptController: restore_irqs() NoPreemptIrqSave ->> KernelScheduler: enable_preemption()
The protection mechanism operates through the NoPreemptIrqSave
guard which:
- Disables Preemption: Prevents the kernel scheduler from switching to other tasks
- Saves and Disables IRQs: Stores current interrupt state and disables local interrupts
- Provides Restoration: Automatically restores the previous state when the guard is dropped
Sources: src/lib.rs(L20 - L23)
Usage Patterns
SpinNoIrq
is designed for scenarios requiring complete protection and can be safely used in any kernel context.
Safe Contexts
flowchart TD subgraph subGraph1["SpinNoIrq Usage"] SpinNoIrqLock["SpinNoIrq::lock()"] CriticalSection["Protected Critical Section"] AutoRestore["Automatic State Restoration"] end subgraph subGraph0["Kernel Contexts Where SpinNoIrq is Safe"] IRQEnabled["IRQ-EnabledKernel Code"] IRQDisabled["IRQ-DisabledKernel Code"] SyscallHandler["System CallHandlers"] TimerContext["Timer/SoftirqContext"] InitCode["InitializationCode"] end CriticalSection --> AutoRestore IRQDisabled --> SpinNoIrqLock IRQEnabled --> SpinNoIrqLock InitCode --> SpinNoIrqLock SpinNoIrqLock --> CriticalSection SyscallHandler --> SpinNoIrqLock TimerContext --> SpinNoIrqLock
Typical Usage Pattern
The standard usage follows the RAII pattern where the guard automatically manages protection state:
Operation | Effect | Automatic Behavior |
---|---|---|
SpinNoIrq::new(data) | Creates protected spinlock | None |
lock.lock() | Acquires lock | Disables preemption + IRQs |
Guard in scope | Access to protected data | Maintains protection |
drop(guard) | Releases lock | Restores preemption + IRQ state |
Sources: README.md(L29 - L33)
Safety Guarantees
SpinNoIrq
provides the strongest safety guarantees among all spinlock variants in the kspin crate.
Protection Matrix
Threat Vector | SpinNoIrq Protection | Mechanism |
---|---|---|
Task Preemption | ✅ Complete Protection | NoPreemptcomponent disables scheduler |
Hardware Interrupts | ✅ Complete Protection | IrqSavecomponent disables IRQs |
Nested Lock Acquisition | ✅ Deadlock Prevention | IRQ disabling prevents interrupt-based nesting |
Data Race Conditions | ✅ Mutual Exclusion | Atomic lock state + protection barriers |
Context Safety Analysis
flowchart TD subgraph Reasoning["Reasoning"] IRQProtection["IRQ disabling providescomplete isolation"] PreemptProtection["Preemption disablingprevents task switches"] IRQHandlerIssue["Using in IRQ handleris redundant sinceIRQs already disabled"] end subgraph subGraph0["Context Safety for SpinNoIrq"] AnyContext["Any Kernel Context"] IRQHandler["Interrupt Handler"] TaskContext["Task Context"] SystemCall["System Call"] SafeResult["✅ Safe to Use"] UnsafeResult["❌ Not Recommended"] end AnyContext --> SafeResult IRQHandler --> UnsafeResult SafeResult --> IRQProtection SafeResult --> PreemptProtection SystemCall --> SafeResult TaskContext --> SafeResult UnsafeResult --> IRQHandlerIssue
Sources: src/lib.rs(L20 - L23)
Performance Considerations
SpinNoIrq
trades performance for safety, making it the most expensive but safest option.
Performance Impact Analysis
Aspect | Impact Level | Details |
---|---|---|
Lock Acquisition Overhead | High | IRQ save/restore + preemption control |
Critical Section Latency | Highest | No interrupts can be serviced |
System Responsiveness | Significant | Delayed interrupt handling |
Throughput Impact | Moderate | Depends on critical section duration |
Performance Comparison
flowchart TD subgraph subGraph1["Trade-off Characteristics"] Performance["Performance"] Safety["Safety"] end subgraph subGraph0["Relative Performance (Lower is Faster)"] SpinRaw["SpinRawOverhead: Minimal"] SpinNoPreempt["SpinNoPreemptOverhead: Low"] SpinNoIrq["SpinNoIrqOverhead: High"] end Performance --> SpinNoIrq Safety --> SpinNoIrq SpinNoPreempt --> SpinNoIrq SpinRaw --> SpinNoPreempt
Sources: src/lib.rs(L20 - L27)
Relationship to Other Spinlock Types
SpinNoIrq
sits at the top of the protection hierarchy, providing comprehensive safety at the cost of performance.
Spinlock Hierarchy
flowchart TD subgraph subGraph1["Usage Context Requirements"] ManualControl["Manual IRQ/preemption control required"] IRQDisabledContext["Must be in IRQ-disabled context"] AnyKernelContext["Can be used in any kernel context"] end subgraph subGraph0["kspin Protection Hierarchy"] SpinRaw["SpinRaw<T>BaseSpinLock<NoOp, T>No protection"] SpinNoPreempt["SpinNoPreempt<T>BaseSpinLock<NoPreempt, T>Preemption disabled"] SpinNoIrq["SpinNoIrq<T>BaseSpinLock<NoPreemptIrqSave, T>Preemption + IRQs disabled"] end SpinNoIrq --> AnyKernelContext SpinNoPreempt --> IRQDisabledContext SpinNoPreempt --> SpinNoIrq SpinRaw --> ManualControl SpinRaw --> SpinNoPreempt
Selection Criteria
Scenario | Recommended Type | Rationale |
---|---|---|
General kernel code | SpinNoIrq | Maximum safety, acceptable overhead |
Performance-critical paths with manual control | SpinRaw | Minimal overhead when protection already handled |
IRQ-disabled contexts | SpinNoPreempt | Balanced protection without redundant IRQ control |
Uncertain context or shared between contexts | SpinNoIrq | Safe default choice |
Sources: src/lib.rs(L10 - L36) README.md(L16 - L33)
Usage Guidelines and Safety
Relevant source files
This document provides comprehensive guidelines for safely selecting and using the appropriate spinlock type from the kspin crate. It covers the safety requirements, context restrictions, and performance considerations for each spinlock variant.
For detailed information about the individual spinlock types and their APIs, see SpinRaw, SpinNoPreempt, and SpinNoIrq. For implementation details, see Core Implementation Architecture.
Safety Model Overview
The kspin crate implements a safety model based on execution context and protection levels. Each spinlock type provides different guarantees and has specific usage requirements that must be followed to ensure system correctness.
Protection Level Hierarchy
flowchart TD A["SpinRaw"] B["NoOp guard"] C["SpinNoPreempt"] D["NoPreempt guard"] E["SpinNoIrq"] F["NoPreemptIrqSave guard"] G["No automatic protection"] H["Disables preemption"] I["Disables preemption + IRQs"] J["Fastest performance"] K["Balanced performance"] L["Maximum safety"] M["Manual context control required"] N["IRQ-disabled context required"] O["Any context safe"] A --> B B --> G C --> D D --> H E --> F F --> I G --> J G --> M H --> K H --> N I --> L I --> O
Sources: src/lib.rs(L6) src/lib.rs(L10 - L36)
Context Classification
The kernel execution environment is divided into distinct contexts, each with different safety requirements:
Context Type | IRQ State | Preemption State | Allowed Spinlocks |
---|---|---|---|
IRQ-enabled context | Enabled | Enabled | SpinNoIrq |
IRQ-disabled context | Disabled | May be enabled | SpinNoIrq |
Preemption-disabled context | Disabled | Disabled | SpinNoIrq |
Interrupt handler | Disabled | Disabled | SpinNoIrq |
Sources: src/lib.rs(L10 - L36)
Spinlock Selection Decision Tree
flowchart TD Start["Need spinlock protection?"] Context["What execution context?"] IRQEnabled["IRQ-enabled context(general kernel code)"] IRQDisabled["IRQ-disabled context(critical sections)"] IntHandler["Interrupt handler"] PreemptDisabled["Preemption-disabled context"] UseNoIrq["Use SpinNoIrq"] UseNoIrq2["Use SpinNoIrq(only safe option)"] Performance["Performance critical?"] UseRaw["Use SpinRaw(if preemption also disabled)"] UseNoPreempt["Use SpinNoPreempt"] UseNoIrq3["Use SpinNoIrq"] PerfCritical["Need maximum performance?"] UseRaw2["Use SpinRaw"] UseNoIrq4["Use SpinNoIrq"] Context --> IRQDisabled Context --> IRQEnabled Context --> IntHandler Context --> PreemptDisabled IRQDisabled --> Performance IRQEnabled --> UseNoIrq IntHandler --> UseNoIrq2 PerfCritical --> UseNoIrq4 PerfCritical --> UseRaw2 Performance --> UseNoIrq3 Performance --> UseNoPreempt Performance --> UseRaw PreemptDisabled --> PerfCritical Start --> Context
Sources: src/lib.rs(L10 - L36) README.md(L16 - L33)
Context-Specific Usage Rules
IRQ-Enabled Context Usage
In IRQ-enabled contexts (typical kernel code), only SpinNoIrq<T>
should be used:
// Safe: SpinNoIrq automatically handles IRQ and preemption state
let data = SpinNoIrq::new(shared_resource);
let guard = data.lock(); // IRQs and preemption disabled automatically
// ... critical section ...
drop(guard); // IRQs and preemption restored
Prohibited patterns:
- Using
SpinNoPreempt<T>
orSpinRaw<T>
in IRQ-enabled contexts - Assuming manual IRQ control is sufficient
Sources: src/lib.rs(L20 - L24) README.md(L29 - L32)
IRQ-Disabled Context Usage
When IRQs are already disabled, you have multiple options based on performance requirements:
// Option 1: Maximum safety (recommended)
let data = SpinNoIrq::new(resource);
// Option 2: Performance optimization when preemption control is needed
let data = SpinNoPreempt::new(resource);
// Option 3: Maximum performance when preemption is already disabled
let data = SpinRaw::new(resource);
Sources: src/lib.rs(L10 - L18) src/lib.rs(L29 - L36)
Interrupt Handler Restrictions
Interrupt handlers have strict requirements due to their execution context:
flowchart TD IntHandler["Interrupt Handler"] Requirement1["IRQs already disabled"] Requirement2["Preemption already disabled"] Requirement3["Cannot use SpinNoPreempt"] Requirement4["Cannot use SpinRaw"] Reason1["Would attempt to disablealready-disabled preemption"] Reason2["No protection againstnested interrupts"] Solution["Use SpinNoIrq only"] IntHandler --> Requirement1 IntHandler --> Requirement2 IntHandler --> Requirement3 IntHandler --> Requirement4 IntHandler --> Solution Requirement3 --> Reason1 Requirement4 --> Reason2
Sources: src/lib.rs(L13 - L15) src/lib.rs(L31 - L33)
Common Safety Violations and Pitfalls
Deadlock Scenarios
Understanding potential deadlock situations is critical for safe spinlock usage:
Scenario | Risk Level | Spinlock Types Affected | Mitigation |
---|---|---|---|
Nested locking (same lock) | High | All types | Usetry_lock()or redesign |
Lock ordering violation | High | All types | Establish consistent lock hierarchy |
IRQ handler accessing same lock | Critical | SpinRaw | UseSpinNoIrq |
Long critical sections | Medium | All types | Minimize critical section duration |
Context Mismatches
Common mistakes when choosing the wrong spinlock type:
// WRONG: SpinRaw in IRQ-enabled context
let data = SpinRaw::new(resource);
let guard = data.lock(); // No protection - race condition possible
// WRONG: SpinNoPreempt in interrupt handler
let data = SpinNoPreempt::new(resource);
let guard = data.lock(); // May not provide sufficient protection
// CORRECT: SpinNoIrq for maximum compatibility
let data = SpinNoIrq::new(resource);
let guard = data.lock(); // Safe in any context
Sources: src/lib.rs(L10 - L36)
Performance Considerations
Overhead Comparison
The protection mechanisms impose different performance costs:
flowchart TD SpinRaw["SpinRaw"] Cost1["Minimal overhead• Atomic operations only• No guard state changes"] SpinNoPreempt["SpinNoPreempt"] Cost2["Moderate overhead• Atomic operations• Preemption control"] SpinNoIrq["SpinNoIrq"] Cost3["Higher overhead• Atomic operations• Preemption + IRQ control• State save/restore"] Perf1["Fastest"] Perf2["Balanced"] Perf3["Safest"] Cost1 --> Perf1 Cost2 --> Perf2 Cost3 --> Perf3 SpinNoIrq --> Cost3 SpinNoPreempt --> Cost2 SpinRaw --> Cost1
SMP vs Single-Core Optimization
The smp
feature flag dramatically affects performance characteristics:
Configuration | Lock State | Atomic Operations | Spinning Behavior |
---|---|---|---|
Single-core (smpdisabled) | Optimized out | None | Always succeeds |
Multi-core (smpenabled) | AtomicBool | compare_exchange | Actual spinning |
Sources: README.md(L12)
Best Practices Summary
Selection Guidelines
- Default choice: Use
SpinNoIrq<T>
unless performance profiling indicates a bottleneck - Performance-critical paths: Consider
SpinNoPreempt<T>
in IRQ-disabled contexts - Maximum performance: Use
SpinRaw<T>
only in preemption-disabled, IRQ-disabled contexts - Interrupt handlers: Always use
SpinNoIrq<T>
Implementation Patterns
// Pattern 1: Safe default for shared kernel data
static SHARED_DATA: SpinNoIrq<SharedResource> = SpinNoIrq::new(SharedResource::new());
// Pattern 2: Performance-optimized for known IRQ-disabled context
fn irq_disabled_function() {
static LOCAL_DATA: SpinNoPreempt<LocalResource> = SpinNoPreempt::new(LocalResource::new());
let guard = LOCAL_DATA.lock();
// ... critical section ...
}
// Pattern 3: Maximum performance in controlled environment
fn preempt_and_irq_disabled_function() {
static FAST_DATA: SpinRaw<FastResource> = SpinRaw::new(FastResource::new());
let guard = FAST_DATA.lock();
// ... minimal critical section ...
}
Verification Checklist
- Spinlock type matches execution context requirements
- No nested acquisition of the same lock
- Consistent lock ordering across all code paths
- Critical sections are minimal in duration
- Interrupt handlers only use
SpinNoIrq<T>
- Performance requirements justify any use of
SpinRaw<T>
orSpinNoPreempt<T>
Sources: src/lib.rs(L10 - L36) README.md(L16 - L33)
Core Implementation Architecture
Relevant source files
This page provides a detailed analysis of the core implementation that underlies all spinlock types in the kspin crate. It covers the generic BaseSpinLock
architecture, the RAII guard system, and the feature-conditional compilation strategy that enables optimization for different target environments.
For information about the specific spinlock types that users interact with, see Spinlock Types and Public API. For detailed implementation analysis of individual components, see BaseSpinLock and BaseSpinLockGuard, BaseGuard Trait System, SMP vs Single-Core Implementation, and Memory Ordering and Atomic Operations.
Architectural Overview
The kspin crate implements a layered architecture where all public spinlock types (SpinRaw
, SpinNoPreempt
, SpinNoIrq
) are type aliases that wrap a single generic implementation: BaseSpinLock<G, T>
. This design provides compile-time specialization through the type system while maintaining a unified codebase.
Core Components Diagram
flowchart TD subgraph subGraph4["External Dependencies"] KernelGuard["kernel_guard crate"] CoreHint["core::hint::spin_loop()"] CoreSync["core::sync::atomic"] end subgraph subGraph3["Data Storage"] UnsafeCell["UnsafeCell<T>"] AtomicBool["AtomicBool (SMP only)"] end subgraph subGraph2["Type System Integration"] BaseGuard["BaseGuard trait"] PhantomData["PhantomData<G>"] end subgraph subGraph1["Generic Implementation Layer"] BaseSpinLock["BaseSpinLock<G, T>"] BaseSpinLockGuard["BaseSpinLockGuard<'a, G, T>"] end subgraph subGraph0["Public API Layer"] SpinRaw["SpinRaw<T>"] SpinNoPreempt["SpinNoPreempt<T>"] SpinNoIrq["SpinNoIrq<T>"] end AtomicBool --> CoreSync BaseGuard --> KernelGuard BaseSpinLock --> AtomicBool BaseSpinLock --> BaseSpinLockGuard BaseSpinLock --> CoreHint BaseSpinLock --> PhantomData BaseSpinLock --> UnsafeCell PhantomData --> BaseGuard SpinNoIrq --> BaseSpinLock SpinNoPreempt --> BaseSpinLock SpinRaw --> BaseSpinLock
Sources: src/base.rs(L27 - L32) src/base.rs(L37 - L43)
Generic Type Parameterization
The BaseSpinLock<G, T>
struct uses two generic parameters that enable compile-time specialization:
Parameter | Purpose | Constraints |
---|---|---|
G | Guard behavior type | Must implementBaseGuardtrait |
T | Protected data type | Supports?Sizedfor unsized types |
The guard type G
determines the protection behavior through the BaseGuard
trait, which provides acquire()
and release()
methods that are called when entering and exiting the critical section.
Type Parameterization Flow
flowchart TD subgraph subGraph2["Public Type Aliases"] SpinRawAlias["SpinRaw<T>"] SpinNoPreemptAlias["SpinNoPreempt<T>"] SpinNoIrqAlias["SpinNoIrq<T>"] end subgraph subGraph1["BaseSpinLock Instantiations"] RawLock["BaseSpinLock<NoOp, T>"] PreemptLock["BaseSpinLock<NoPreempt, T>"] IrqLock["BaseSpinLock<NoPreemptIrqSave, T>"] end subgraph subGraph0["Guard Types (kernel_guard)"] NoOp["NoOp"] NoPreempt["NoPreempt"] NoPreemptIrqSave["NoPreemptIrqSave"] end IrqLock --> SpinNoIrqAlias NoOp --> RawLock NoPreempt --> PreemptLock NoPreemptIrqSave --> IrqLock PreemptLock --> SpinNoPreemptAlias RawLock --> SpinRawAlias
Sources: src/base.rs(L27 - L32) src/lib.rs
Feature-Conditional Architecture
The implementation uses the smp
feature flag to provide dramatically different code paths for single-core versus multi-core environments:
SMP Feature Compilation Strategy
Sources: src/base.rs(L13 - L14) src/base.rs(L29 - L30) src/base.rs(L41 - L42) src/base.rs(L79 - L93) src/base.rs(L111 - L118) src/base.rs(L125 - L136)
Lock Acquisition and Guard Lifecycle
The core operation flow demonstrates how the RAII pattern ensures correct lock management:
Lock Lifecycle Flow
sequenceDiagram participant Client as Client participant BaseSpinLock as BaseSpinLock participant BaseGuard as BaseGuard participant AtomicBool as AtomicBool participant BaseSpinLockGuard as BaseSpinLockGuard Client ->> BaseSpinLock: lock() BaseSpinLock ->> BaseGuard: G::acquire() Note over BaseGuard: Disable IRQs/preemption BaseGuard -->> BaseSpinLock: irq_state alt SMP enabled BaseSpinLock ->> AtomicBool: compare_exchange_weak(false, true) loop While locked AtomicBool -->> BaseSpinLock: Err (already locked) BaseSpinLock ->> AtomicBool: load(Ordering::Relaxed) BaseSpinLock ->> BaseSpinLock: core::hint::spin_loop() end AtomicBool -->> BaseSpinLock: Ok (acquired) else SMP disabled Note over BaseSpinLock: Lock always succeeds end BaseSpinLock -->> Client: BaseSpinLockGuard Note over Client: Use protected data via Deref/DerefMut Client ->> BaseSpinLockGuard: drop() alt SMP enabled BaseSpinLockGuard ->> AtomicBool: store(false, Ordering::Release) end BaseSpinLockGuard ->> BaseGuard: G::release(irq_state) Note over BaseGuard: Restore IRQs/preemption
Sources: src/base.rs(L77 - L101) src/base.rs(L218 - L227)
Memory Safety and Synchronization
The implementation provides memory safety through several mechanisms:
Mechanism | Implementation | Purpose |
---|---|---|
UnsafeCell | src/base.rs31 | Interior mutability for protected data |
RAII Guard | src/base.rs37-43 | Automatic lock release on scope exit |
Raw pointer in guard | src/base.rs40 | Direct data access while lock is held |
Atomic operations | src/base.rs83-85 | Multi-core synchronization |
Memory ordering | Ordering::Acquire/Release | Proper memory barriers |
Data Access Safety Model
flowchart TD subgraph subGraph2["Safety Guarantees"] ExclusiveAccess["Exclusive data access"] AutoRelease["Automatic lock release"] MemoryOrdering["Proper memory ordering"] end subgraph subGraph1["BaseSpinLockGuard Structure"] DataPtr["data: *mut T"] LockRef["lock: &'a AtomicBool"] IrqState["irq_state: G::State"] PhantomRef["_phantom: &'a PhantomData<G>"] end subgraph subGraph0["BaseSpinLock Structure"] UnsafeCellField["data: UnsafeCell<T>"] AtomicLock["lock: AtomicBool"] PhantomG["_phantom: PhantomData<G>"] end AtomicLock --> LockRef AtomicLock --> MemoryOrdering DataPtr --> ExclusiveAccess LockRef --> AtomicLock LockRef --> AutoRelease PhantomG --> PhantomRef PhantomRef --> UnsafeCellField UnsafeCellField --> DataPtr
Sources: src/base.rs(L27 - L32) src/base.rs(L37 - L43) src/base.rs(L195 - L210) src/base.rs(L218 - L227)
BaseSpinLock and BaseSpinLockGuard
Relevant source files
Purpose and Scope
This document provides detailed technical analysis of the core spinlock implementation in the kspin crate. The BaseSpinLock<G, T>
struct and its companion BaseSpinLockGuard<G, T>
form the foundational implementation that underlies all public spinlock types in the crate. This generic implementation enables different protection behaviors through type parameterization while providing efficient RAII-based lock management.
For information about the public spinlock APIs that users interact with, see Spinlock Types and Public API. For details about the trait system that enables different protection behaviors, see BaseGuard Trait System.
Architecture Overview
The core implementation consists of two primary components that work together to provide safe, efficient spinlock functionality:
Core Implementation Structure
flowchart TD subgraph subGraph3["BaseSpinLock Architecture"] BaseSpinLock["BaseSpinLock<G, T>Generic spinlock container"] BaseSpinLockGuard["BaseSpinLockGuard<G, T>RAII access guard"] subgraph subGraph2["Core Operations"] AcquireLock["G::acquire() + atomic CAS"] ReleaseLock["atomic store + G::release()"] DataAccess["Deref/DerefMut traits"] end subgraph subGraph1["BaseSpinLockGuard Fields"] PhantomRef["_phantom: &PhantomData<G>Lifetime-bound guard marker"] IrqState["irq_state: G::StateSaved protection state"] DataPtr["data: *mut TDirect data access pointer"] LockRef["lock: &AtomicBool(SMP only)"] end subgraph subGraph0["BaseSpinLock Fields"] PhantomG["_phantom: PhantomData<G>Zero-cost guard type marker"] LockState["lock: AtomicBool(SMP only)"] Data["data: UnsafeCell<T>Protected data storage"] end end AcquireLock --> BaseSpinLockGuard BaseSpinLock --> AcquireLock BaseSpinLock --> Data BaseSpinLock --> LockState BaseSpinLock --> PhantomG BaseSpinLockGuard --> DataAccess BaseSpinLockGuard --> DataPtr BaseSpinLockGuard --> IrqState BaseSpinLockGuard --> LockRef BaseSpinLockGuard --> PhantomRef BaseSpinLockGuard --> ReleaseLock
Sources: src/base.rs(L27 - L43) src/base.rs(L49 - L101) src/base.rs(L218 - L227)
Method Implementation Map
flowchart TD subgraph subGraph2["SMP-Conditional Logic"] CompareExchange["compare_exchange_weakline 85"] SpinLoop["spin_loop()line 90"] AtomicStore["store(false)line 224"] end subgraph subGraph1["BaseSpinLockGuard Traits"] Deref["Deref::deref()line 198"] DerefMut["DerefMut::deref_mut()line 206"] Drop["Drop::drop()line 222"] Debug["Debug::fmt()line 213"] end subgraph subGraph0["BaseSpinLock Methods"] New["new(data: T)line 52"] Lock["lock()line 77"] TryLock["try_lock()line 122"] IsLocked["is_locked()line 110"] ForceUnlock["force_unlock()line 159"] GetMut["get_mut()line 170"] IntoInner["into_inner()line 63"] end BaseSpinLockGuard["BaseSpinLockGuard"] BaseSpinLockGuard --> Deref BaseSpinLockGuard --> DerefMut BaseSpinLockGuard --> Drop Drop --> AtomicStore Lock --> CompareExchange Lock --> SpinLoop TryLock --> CompareExchange
Sources: src/base.rs(L49 - L175) src/base.rs(L195 - L227)
BaseSpinLock Structure Analysis
Field Organization and Purpose
The BaseSpinLock<G, T>
struct is carefully designed to minimize memory overhead while supporting both SMP and single-core environments:
Field | Type | Purpose | Conditional |
---|---|---|---|
_phantom | PhantomData | Zero-cost guard type marker | Always present |
lock | AtomicBool | Lock state for atomic operations | SMP feature only |
data | UnsafeCell | Protected data storage | Always present |
The conditional compilation using #[cfg(feature = "smp")]
allows the lock state to be completely eliminated in single-core builds, reducing memory overhead and eliminating unnecessary atomic operations.
Sources: src/base.rs(L27 - L32)
Constructor and Data Access Methods
The new()
constructor provides compile-time initialization with zero runtime cost:
// Simplified view of the new() method
pub const fn new(data: T) -> Self {
Self {
_phantom: PhantomData,
data: UnsafeCell::new(data),
#[cfg(feature = "smp")]
lock: AtomicBool::new(false),
}
}
The into_inner()
and get_mut()
methods provide safe data extraction when exclusive access is statically guaranteed, eliminating the need for runtime locking.
Sources: src/base.rs(L52 - L59) src/base.rs(L63 - L68) src/base.rs(L170 - L175)
BaseSpinLockGuard RAII Implementation
Guard Structure and Lifetime Management
The BaseSpinLockGuard<'a, G, T>
implements the RAII pattern to ensure automatic lock release:
flowchart TD subgraph subGraph2["Guard Lifecycle"] Create["Guard Creationlock() or try_lock()"] Access["Data AccessDeref/DerefMut"] Drop["Automatic CleanupDrop trait"] subgraph subGraph1["Drop Steps"] AtomicRelease["lock.store(false)(SMP only)"] GuardRelease["G::release(irq_state)Restore protection"] end subgraph subGraph0["Creation Steps"] Acquire["G::acquire()Get protection state"] CAS["compare_exchange(SMP only)"] ConstructGuard["Construct guard withdata pointer & state"] end end Access --> Drop Acquire --> CAS AtomicRelease --> GuardRelease CAS --> ConstructGuard ConstructGuard --> Access Create --> Acquire Drop --> AtomicRelease
Sources: src/base.rs(L77 - L101) src/base.rs(L218 - L227)
Memory Safety Through Type System
The guard uses several mechanisms to ensure memory safety:
- Lifetime binding: The
'a
lifetime ensures the guard cannot outlive the lock - Raw pointer storage: Direct
*mut T
access eliminates borrowing conflicts - Phantom reference:
&'a PhantomData<G>
ties the guard lifetime to the lock - Exclusive data access: The guard provides exclusive access through
Deref
/DerefMut
Sources: src/base.rs(L37 - L43) src/base.rs(L195 - L210)
Core Locking Operations
Lock Acquisition Algorithm
The lock()
method implements a two-phase spinning algorithm optimized for different contention scenarios:
The algorithm uses compare_exchange_weak
for potential efficiency gains on some architectures, falling back to spinning until the lock appears available before retrying.
Sources: src/base.rs(L77 - L101)
Try-Lock Implementation
The try_lock()
method provides non-blocking acquisition using a strong compare-exchange operation:
flowchart TD subgraph subGraph1["Single-Core Path"] AlwaysSuccess["Always succeedsis_unlocked = true"] CreateGuard["Create guard"] end subgraph subGraph0["SMP Path"] StrongCAS["compare_exchange(strong)"] Success["Create guard"] Failure["Return None"] end TryLock["try_lock()"] Acquire["G::acquire()"] Acquire --> AlwaysSuccess Acquire --> StrongCAS AlwaysSuccess --> CreateGuard StrongCAS --> Failure StrongCAS --> Success TryLock --> Acquire
The use of strong compare-exchange is specifically chosen over weak to avoid unnecessary retry loops in the try-lock scenario.
Sources: src/base.rs(L122 - L149)
SMP vs Single-Core Optimization
Conditional Compilation Strategy
The crate uses cfg_if!
macros to provide dramatically different implementations based on the smp
feature:
Operation | SMP Implementation | Single-Core Implementation |
---|---|---|
Lock state | AtomicBoolfield | No field (zero bytes) |
lock() | Atomic CAS + spinning | Immediate success |
try_lock() | Atomic CAS | Always succeeds |
is_locked() | load(Relaxed) | Always returnsfalse |
Guard drop | store(false, Release) | No atomic operation |
This approach allows single-core builds to have zero overhead for lock operations while maintaining identical APIs.
Sources: src/base.rs(L13 - L14) src/base.rs(L29 - L30) src/base.rs(L111 - L118) src/base.rs(L125 - L136)
Memory Ordering Semantics
The SMP implementation uses carefully chosen memory orderings:
- Acquire ordering on successful CAS: Ensures all subsequent reads see writes from the previous critical section
- Relaxed ordering on failed CAS: No synchronization needed for failed attempts
- Release ordering on unlock: Ensures all writes in critical section are visible before lock release
- Relaxed ordering for
is_locked()
: Only a hint, no synchronization guarantees
Sources: src/base.rs(L85) src/base.rs(L131) src/base.rs(L224) src/base.rs(L113)
Thread Safety and Sync Implementation
Safety Trait Implementations
The crate provides the same safety guarantees as std::sync::Mutex
:
unsafe impl<G: BaseGuard, T: ?Sized + Send> Sync for BaseSpinLock<G, T> {}
unsafe impl<G: BaseGuard, T: ?Sized + Send> Send for BaseSpinLock<G, T> {}
These implementations are safe because:
- The lock ensures exclusive access to the data
- The guard system prevents concurrent access
- The
BaseGuard
trait handles interrupt/preemption safety
Sources: src/base.rs(L46 - L47)
Debug and Display Support
Both BaseSpinLock
and BaseSpinLockGuard
implement Debug
to aid in development:
- The lock's debug implementation attempts
try_lock()
to safely display data - The guard's debug implementation directly displays the protected data
- Locked spinlocks display
"SpinLock { <locked> }"
to avoid blocking
Sources: src/base.rs(L184 - L193) src/base.rs(L212 - L216)
BaseGuard Trait System
Relevant source files
The BaseGuard trait system is the core abstraction that enables different protection behaviors in kspin spinlocks through type parameterization. This system allows BaseSpinLock<G, T>
to provide varying levels of interrupt and preemption protection by substituting different guard implementations at compile time.
For information about the specific spinlock types that use these guards, see Spinlock Types and Public API. For details about the underlying BaseSpinLock
implementation, see BaseSpinLock and BaseSpinLockGuard.
BaseGuard Trait Interface
The BaseGuard
trait is defined in the external kernel_guard
crate and provides a standardized interface for managing system-level protection states during critical sections.
classDiagram class BaseGuard { <<trait>> +type State +acquire() State +release(state: State) } class NoOp { +type State =() +acquire()() +release(state:()) } class NoPreempt { +type State = PreemptState +acquire() PreemptState +release(state: PreemptState) } class NoPreemptIrqSave { +type State = IrqState +acquire() IrqState +release(state: IrqState) } BaseGuard --|> NoOp BaseGuard --|> NoPreempt NoPreemptIrqSave --|> NoPreempt
BaseGuard Trait Interface
Sources: src/base.rs(L16) src/lib.rs(L6)
Integration with BaseSpinLock
The BaseSpinLock<G, T>
struct uses the BaseGuard
trait as a type parameter to customize protection behavior without runtime overhead. The guard type G
determines what system-level protections are applied during lock acquisition and release.
flowchart TD subgraph subGraph1["Lock Operations"] Lock["lock()"] TryLock["try_lock()"] Drop["Drop::drop()"] end subgraph subGraph0["Guard Integration Points"] Acquire["G::acquire()"] Release["G::release(state)"] StateStorage["irq_state: G::State"] end BaseSpinLock["BaseSpinLock<G: BaseGuard, T>"] BaseSpinLockGuard["BaseSpinLockGuard<G, T>"] Acquire --> StateStorage BaseSpinLock --> Lock BaseSpinLock --> TryLock BaseSpinLockGuard --> Drop Drop --> Release Lock --> Acquire StateStorage --> BaseSpinLockGuard TryLock --> Acquire
BaseGuard Integration Flow
The integration occurs at three key points in src/base.rs:
- State acquisition during lock operations src/base.rs(L78) src/base.rs(L123)
- State storage in the guard struct src/base.rs(L39)
- State release during guard destruction src/base.rs(L225)
Sources: src/base.rs(L27) src/base.rs(L37 - L43) src/base.rs(L77 - L101) src/base.rs(L218 - L227)
Concrete Guard Implementations
The kspin crate uses three specific implementations of BaseGuard
from the kernel_guard
crate, each providing different levels of system protection.
Guard Type | Protection Level | State Type | Use Context |
---|---|---|---|
NoOp | None | () | IRQ-disabled, preemption-disabled |
NoPreempt | Disable preemption | PreemptState | IRQ-disabled contexts |
NoPreemptIrqSave | Disable preemption + IRQs | IrqState | Any context |
flowchart TD subgraph subGraph1["Spinlock Types"] SpinRaw["SpinRaw<T>"] SpinNoPreempt["SpinNoPreempt<T>"] SpinNoIrq["SpinNoIrq<T>"] end subgraph subGraph0["Protection Levels"] NoOp["NoOpRaw Protection"] NoPreempt["NoPreemptPreemption Protection"] NoPreemptIrqSave["NoPreemptIrqSaveFull Protection"] end NoOp --> SpinRaw NoPreempt --> SpinNoPreempt NoPreemptIrqSave --> SpinNoIrq
Guard Type to Spinlock Type Mapping
Sources: src/lib.rs(L6) src/lib.rs(L15) src/lib.rs(L24) src/lib.rs(L33)
Type-Driven Protection Behavior
The BaseGuard trait system enables compile-time selection of protection behavior through Rust's type system. Each guard implementation provides different acquire()
and release()
semantics without requiring runtime branching.
sequenceDiagram participant Client as Client participant BaseSpinLock as BaseSpinLock participant GBaseGuard as "G: BaseGuard" participant KernelSubsystems as "Kernel Subsystems" Client ->> BaseSpinLock: lock() BaseSpinLock ->> GBaseGuard: G::acquire() GBaseGuard ->> KernelSubsystems: Disable preemption/IRQs KernelSubsystems -->> GBaseGuard: Return saved state GBaseGuard -->> BaseSpinLock: G::State BaseSpinLock ->> BaseSpinLock: Acquire atomic lock BaseSpinLock -->> Client: BaseSpinLockGuard Note over Client: Use protected data Client ->> BaseSpinLock: Drop guard BaseSpinLock ->> BaseSpinLock: Release atomic lock BaseSpinLock ->> GBaseGuard: G::release(state) GBaseGuard ->> KernelSubsystems: Restore preemption/IRQs KernelSubsystems -->> GBaseGuard: Complete
Protection State Management Sequence
The type-driven approach ensures that:
NoOp
:acquire()
andrelease()
are no-ops, providing zero overheadNoPreempt
: Manages preemption state to prevent task switchingNoPreemptIrqSave
: Manages both preemption and interrupt state for maximum protection
Sources: src/base.rs(L77 - L79) src/base.rs(L122 - L124) src/base.rs(L222 - L225)
State Management in Guard Lifecycle
The BaseSpinLockGuard
stores the protection state returned by G::acquire()
and ensures proper cleanup through Rust's RAII pattern. This guarantees that system protection state is always restored, even in the presence of panics.
Guard State Lifecycle
The state management implementation in BaseSpinLockGuard
:
- State storage:
irq_state: G::State
field holds the protection state src/base.rs(L39) - State acquisition: Captured during guard creation src/base.rs(L96) src/base.rs(L141)
- State restoration: Automatically handled in
Drop::drop()
src/base.rs(L225)
This design ensures that protection state is never leaked and that the system always returns to its original state when the critical section ends.
Sources: src/base.rs(L37 - L43) src/base.rs(L94 - L100) src/base.rs(L138 - L145) src/base.rs(L218 - L227)
SMP vs Single-Core Implementation
Relevant source files
Purpose and Scope
This document explains how the kspin crate uses feature flags to provide dramatically different implementations for multi-core (SMP) and single-core environments. The smp
feature flag enables compile-time optimization that completely eliminates atomic operations and lock state when they are unnecessary in single-core systems.
For details about the underlying BaseSpinLock
structure, see BaseSpinLock and BaseSpinLockGuard. For information about how different guard types interact with both implementations, see BaseGuard Trait System. For technical details about atomic operations used in SMP mode, see Memory Ordering and Atomic Operations.
Feature Flag Architecture
The kspin crate uses conditional compilation through the smp
feature flag to generate entirely different code paths for multi-core and single-core environments. This approach enables zero-cost abstractions where single-core systems pay no performance penalty for multi-core synchronization primitives.
Conditional Compilation Flow
flowchart TD subgraph subGraph1["Single-Core Implementation"] SingleStruct["BaseSpinLock {_phantom: PhantomData"] SingleGuard["BaseSpinLockGuard {_phantom: &PhantomData"] SingleOps["No Atomic Operations:• lock() always succeeds• try_lock() always succeeds• is_locked() always false"] end subgraph subGraph0["SMP Implementation"] SMPStruct["BaseSpinLock {_phantom: PhantomData"] SMPGuard["BaseSpinLockGuard {_phantom: &PhantomData"] SMPOps["Atomic Operations:• compare_exchange_weak• load/store with ordering• spin_loop hints"] end FeatureFlag["smp feature flag"] SMPBuild["SMP Build Target"] SingleBuild["Single-Core Build Target"] FeatureFlag --> SMPBuild FeatureFlag --> SingleBuild SMPBuild --> SMPGuard SMPBuild --> SMPOps SMPBuild --> SMPStruct SingleBuild --> SingleGuard SingleBuild --> SingleOps SingleBuild --> SingleStruct
Sources: Cargo.toml(L14 - L16) src/base.rs(L13 - L14) src/base.rs(L29 - L31) src/base.rs(L41 - L43)
SMP Implementation Details
In SMP environments, the BaseSpinLock
maintains actual lock state using atomic operations to coordinate between multiple CPU cores. The implementation provides true mutual exclusion through hardware-level atomic instructions.
SMP Lock Structure and Operations
flowchart TD subgraph subGraph2["Memory Ordering"] AcquireOrder["Ordering::Acquire on success"] RelaxedOrder["Ordering::Relaxed on failure"] ReleaseOrder["Ordering::Release on unlock"] end subgraph subGraph1["Lock Acquisition Process"] AcquireGuard["G::acquire()"] CompareExchange["compare_exchange_weak(false, true)"] SpinLoop["spin_loop() while locked"] CreateGuard["BaseSpinLockGuard creation"] end subgraph subGraph0["BaseSpinLock SMP Structure"] SMPLock["BaseSpinLock"] Phantom["_phantom: PhantomData"] AtomicLock["lock: AtomicBool"] Data["data: UnsafeCell"] end AcquireGuard --> CompareExchange AtomicLock --> CompareExchange CompareExchange --> AcquireOrder CompareExchange --> CreateGuard CompareExchange --> RelaxedOrder CompareExchange --> SpinLoop CreateGuard --> ReleaseOrder SMPLock --> AtomicLock SMPLock --> Data SMPLock --> Phantom SpinLoop --> CompareExchange
The SMP implementation uses a two-phase locking strategy:
- Guard Acquisition: First acquires the protection guard (disabling preemption/IRQs)
- Atomic Lock: Then attempts to acquire the atomic lock using compare-and-swap operations
Key SMP Code Paths:
- Lock acquisition with spinning: src/base.rs(L79 - L93)
- Try-lock with strong compare-exchange: src/base.rs(L126 - L132)
- Lock status checking: src/base.rs(L112 - L113)
- Force unlock operation: src/base.rs(L160 - L161)
Sources: src/base.rs(L27 - L32) src/base.rs(L77 - L101) src/base.rs(L122 - L149) src/base.rs(L159 - L162)
Single-Core Implementation Details
In single-core environments, the BaseSpinLock
completely eliminates the atomic lock state. Since only one CPU core exists, proper guard acquisition (disabling preemption/IRQs) provides sufficient mutual exclusion without any atomic operations.
Single-Core Optimization Strategy
flowchart TD subgraph subGraph2["Eliminated Operations"] NoAtomic["❌ No AtomicBool field"] NoCompareExchange["❌ No compare_exchange"] NoSpinning["❌ No spinning loops"] NoMemoryOrdering["❌ No memory ordering"] AcquireGuard2["G::acquire()"] SingleLock["BaseSpinLock"] end subgraph subGraph0["BaseSpinLock Single-Core Structure"] NoCompareExchange["❌ No compare_exchange"] SingleLock["BaseSpinLock"] Data2["data: UnsafeCell"] subgraph subGraph1["Simplified Lock Process"] NoAtomic["❌ No AtomicBool field"] AcquireGuard2["G::acquire()"] DirectAccess["Direct data access"] Phantom2["_phantom: PhantomData"] end end AcquireGuard2 --> DirectAccess SingleLock --> Data2 SingleLock --> Phantom2
Single-Core Behavior:
lock()
always succeeds immediately after guard acquisitiontry_lock()
always returnsSome(guard)
is_locked()
always returnsfalse
force_unlock()
performs no atomic operations
Key Single-Core Code Paths:
- Simplified lock acquisition: src/base.rs(L94 - L101)
- Always-successful try-lock: src/base.rs(L134 - L135)
- Always-false lock status: src/base.rs(L114 - L115)
- No-op force unlock: src/base.rs(L159 - L162)
Sources: src/base.rs(L25 - L26) src/base.rs(L133 - L135) src/base.rs(L114 - L116)
Compile-Time Optimization Benefits
The feature flag approach provides significant performance and size benefits for single-core targets by eliminating unnecessary code at compile time.
Performance Comparison
Operation | SMP Implementation | Single-Core Implementation |
---|---|---|
Lock Acquisition | Guard + Atomic CAS loop | Guard only |
Try Lock | Guard + Atomic CAS | Guard only |
Lock Check | Atomic load | Constantfalse |
Unlock | Atomic store + Guard release | Guard release only |
Memory Usage | +1AtomicBoolper lock | No additional fields |
Code Size | Full atomic operation codegen | Optimized away |
Optimization Mechanisms
Sources: src/base.rs(L111 - L117) src/base.rs(L125 - L136) Cargo.toml(L14 - L16)
Code Generation Differences
The conditional compilation results in fundamentally different assembly code generation for the two target environments.
Structure Layout Differences
flowchart TD subgraph subGraph0["SMP Memory Layout"] SMPData["data: UnsafeCell(sizeof T bytes)"] subgraph subGraph2["Guard Layout Differences"] SMPGuardLayout["SMP Guard:• phantom reference• irq_state• data pointer• lock reference"] SingleGuardLayout["Single-Core Guard:• phantom reference• irq_state• data pointer"] SingleStruct2["BaseSpinLock"] SinglePhantom["_phantom: PhantomData"] SingleData["data: UnsafeCell(sizeof T bytes)"] SMPStruct2["BaseSpinLock"] SMPPhantom["_phantom: PhantomData"] SMPAtomic["lock: AtomicBool(1 byte + padding)"] end subgraph subGraph1["Single-Core Memory Layout"] SMPGuardLayout["SMP Guard:• phantom reference• irq_state• data pointer• lock reference"] SingleGuardLayout["Single-Core Guard:• phantom reference• irq_state• data pointer"] SingleStruct2["BaseSpinLock"] SinglePhantom["_phantom: PhantomData"] SingleData["data: UnsafeCell(sizeof T bytes)"] SMPStruct2["BaseSpinLock"] SMPPhantom["_phantom: PhantomData"] SMPAtomic["lock: AtomicBool(1 byte + padding)"] end end SMPStruct2 --> SMPAtomic SMPStruct2 --> SMPData SMPStruct2 --> SMPPhantom SingleStruct2 --> SingleData SingleStruct2 --> SinglePhantom
Function Implementation Differences
The same function signatures produce completely different implementations:
lock()
method: SMP version includes spinning loop src/base.rs(L83 - L92) single-core version skips directly to guard creation src/base.rs(L94 - L101)try_lock()
method: SMP version uses atomic compare-exchange src/base.rs(L129 - L132) single-core version setsis_unlocked = true
src/base.rs(L134)is_locked()
method: SMP version loads atomic state src/base.rs(L113) single-core version returns constantfalse
src/base.rs(L115)
This design ensures that single-core embedded systems receive highly optimized code while SMP systems get full multi-core safety guarantees.
Sources: src/base.rs(L27 - L32) src/base.rs(L37 - L43) src/base.rs(L52 - L59) src/base.rs(L77 - L149) src/base.rs(L218 - L226)
Memory Ordering and Atomic Operations
Relevant source files
This document covers the atomic operations and memory ordering semantics used in the kspin crate's spinlock implementation. It details how the BaseSpinLock
uses platform-specific atomic primitives to ensure thread safety in multi-core environments, and how these operations are optimized away for single-core systems.
For information about the overall spinlock architecture and guard types, see Core Implementation Architecture. For details about the SMP feature flag system, see SMP vs Single-Core Implementation.
Conditional Atomic Operations
The kspin crate uses conditional compilation to include atomic operations only when targeting multi-core systems. The smp
feature flag controls whether atomic synchronization primitives are compiled into the final binary.
flowchart TD subgraph subGraph2["Lock Operations"] AtomicOps["compare_exchange_weakcompare_exchangeload/store operations"] NoOps["Always succeedsNo atomic operationsOptimized away"] end subgraph subGraph1["Data Structures"] AtomicLock["lock: AtomicBool"] NoLockField["No lock field"] end subgraph subGraph0["Compilation Paths"] SMPEnabled["SMP Feature Enabled"] SMPDisabled["SMP Feature Disabled"] end AtomicLock --> AtomicOps NoLockField --> NoOps SMPDisabled --> NoLockField SMPEnabled --> AtomicLock
The BaseSpinLock
struct conditionally includes an AtomicBool
field based on the smp
feature:
Compilation Mode | Lock Field | Behavior |
---|---|---|
SMP enabled | lock: AtomicBool | Full atomic synchronization |
SMP disabled | No lock field | All lock operations succeed immediately |
Sources: src/base.rs(L13 - L14) src/base.rs(L29 - L30) src/base.rs(L111 - L117)
Memory Ordering Semantics
The spinlock implementation uses three specific memory orderings to ensure correct synchronization semantics while minimizing performance overhead:
flowchart TD subgraph Operations["Operations"] CompareExchange["compare_exchange(_weak)Acquire + Relaxed"] Store["store(false)Release"] Load["load()Relaxed"] end subgraph subGraph0["Memory Ordering Types"] Acquire["Ordering::AcquireLock acquisition"] Release["Ordering::ReleaseLock release"] Relaxed["Ordering::RelaxedStatus checks"] end Acquire --> CompareExchange Relaxed --> CompareExchange Relaxed --> Load Release --> Store
Memory Ordering Usage Patterns
Operation | Success Ordering | Failure Ordering | Purpose |
---|---|---|---|
compare_exchange_weak | Acquire | Relaxed | Lock acquisition with retry |
compare_exchange | Acquire | Relaxed | Single attempt lock acquisition |
store(false) | Release | N/A | Lock release |
load() | Relaxed | N/A | Non-blocking status check |
The Acquire
ordering on successful lock acquisition ensures that all subsequent reads and writes cannot be reordered before the lock acquisition. The Release
ordering on lock release ensures that all previous reads and writes complete before the lock is released.
Sources: src/base.rs(L85) src/base.rs(L131) src/base.rs(L161) src/base.rs(L224) src/base.rs(L113)
Atomic Operation Flow
The spinlock uses a two-phase approach for lock acquisition: an optimistic compare-and-swap followed by a passive wait loop.
flowchart TD Start["lock() called"] AcquireGuard["G::acquire()"] TryLock["compare_exchange_weak(false, true)"] CheckResult["Success?"] CreateGuard["Create BaseSpinLockGuard"] CheckLocked["is_locked()"] SpinLoop["core::hint::spin_loop()"] StillLocked["Still locked?"] AcquireGuard --> TryLock CheckLocked --> StillLocked CheckResult --> CheckLocked CheckResult --> CreateGuard SpinLoop --> CheckLocked Start --> AcquireGuard StillLocked --> SpinLoop StillLocked --> TryLock TryLock --> CheckResult
Lock Acquisition Pattern
The lock()
method implements an efficient two-stage spinning strategy:
- Active spinning: Attempts to acquire the lock using
compare_exchange_weak
- Passive waiting: When acquisition fails, enters a read-only spin loop checking
is_locked()
- CPU optimization: Uses
core::hint::spin_loop()
to signal the processor during busy waiting
Sources: src/base.rs(L77 - L101) src/base.rs(L83 - L92) src/base.rs(L89 - L91)
Try-Lock Pattern
The try_lock()
method uses a single-shot approach with compare_exchange
(strong semantics) rather than the weak variant used in the spinning loop:
flowchart TD TryLock["try_lock()"] AcquireGuard["G::acquire()"] CompareExchange["compare_exchange(false, true)"] CheckResult["Success?"] ReturnSome["Some(guard)"] ReturnNone["None"] AcquireGuard --> CompareExchange CheckResult --> ReturnNone CheckResult --> ReturnSome CompareExchange --> CheckResult TryLock --> AcquireGuard
The strong compare-exchange is used because there's no retry loop, making the single operation more likely to succeed on architectures where weak operations can fail spuriously.
Sources: src/base.rs(L122 - L149) src/base.rs(L129 - L132)
Lock Release Mechanism
Lock release occurs automatically through the RAII Drop
implementation of BaseSpinLockGuard
. The release process ensures proper memory ordering and guard state cleanup:
sequenceDiagram participant BaseSpinLockGuard as "BaseSpinLockGuard" participant AtomicBoollock as "AtomicBool lock" participant GBaseGuard as "G: BaseGuard" Note over BaseSpinLockGuard: Guard goes out of scope BaseSpinLockGuard ->> AtomicBoollock: store(false, Ordering::Release) BaseSpinLockGuard ->> GBaseGuard: G::release(irq_state) Note over BaseSpinLockGuard: Guard destroyed
The release ordering ensures that all memory operations performed while holding the lock are visible to other threads before the lock becomes available.
Sources: src/base.rs(L218 - L227) src/base.rs(L224) src/base.rs(L225)
Performance Optimizations
CPU Spin Loop Hints
The implementation uses core::hint::spin_loop()
to provide architecture-specific optimizations during busy waiting:
- x86/x86_64: Translates to the
PAUSE
instruction, reducing power consumption - ARM: May use
YIELD
or similar instructions - RISC-V: Architecture-specific power management hints
Weak vs Strong Compare-Exchange
The spinlock strategically chooses between weak and strong compare-exchange operations:
Context | Operation | Rationale |
---|---|---|
Spinning loop | compare_exchange_weak | Acceptable spurious failures in retry context |
Single attempt | compare_exchange | Must succeed if possible, no retry mechanism |
Sources: src/base.rs(L85) src/base.rs(L131) src/base.rs(L127 - L128)
Single-Core Optimization
When compiled without the smp
feature, all atomic operations are eliminated:
flowchart TD subgraph Benefits["Benefits"] ZeroCost["Zero-cost abstraction"] NoMemoryBarriers["No memory barriers"] OptimizedBinary["Smaller binary size"] end subgraph subGraph0["SMP Disabled Path"] LockCall["lock()"] GuardAcquire["G::acquire()"] CreateGuard["Create guard immediately"] NoAtomics["No atomic operations"] end CreateGuard --> NoAtomics GuardAcquire --> CreateGuard LockCall --> GuardAcquire NoAtomics --> NoMemoryBarriers NoAtomics --> OptimizedBinary NoAtomics --> ZeroCost
This optimization is particularly important for embedded systems where multi-core synchronization overhead would be unnecessary.
Sources: src/base.rs(L79 - L93) src/base.rs(L126 - L136) src/base.rs(L160 - L161) src/base.rs(L223 - L224)
Development and Building
Relevant source files
This document provides comprehensive information for developers who want to build, test, or contribute to the kspin crate. It covers the build system configuration, feature flag usage, continuous integration pipeline, and development environment setup. For information about the actual spinlock APIs and usage patterns, see Spinlock Types and Public API. For details about the internal implementation architecture, see Core Implementation Architecture.
Build System Overview
The kspin crate uses Cargo as its primary build system with support for multiple target platforms and optional feature flags. The build configuration is centralized in the project's Cargo.toml
file, which defines dependencies, metadata, and feature gates that control compilation behavior.
Package Configuration
The crate is configured as a library package targeting kernel-space environments with no-std compatibility. The package metadata includes licensing under multiple schemes (GPL-3.0-or-later, Apache-2.0, MulanPSL-2.0) and categorization for operating system and no-std use cases Cargo.toml(L1 - L13)
Target Platform Support
The build system supports multiple target architectures commonly used in kernel and embedded development:
Target | Architecture | Use Case |
---|---|---|
x86_64-unknown-linux-gnu | x86-64 | Hosted testing environment |
x86_64-unknown-none | x86-64 | Bare metal/kernel |
riscv64gc-unknown-none-elf | RISC-V 64-bit | Embedded/kernel |
aarch64-unknown-none-softfloat | ARM64 | Embedded/kernel |
Build System Architecture
flowchart TD subgraph subGraph0["Build Targets"] x86Linux["x86_64-unknown-linux-gnu"] x86None["x86_64-unknown-none"] RiscV["riscv64gc-unknown-none-elf"] ARM["aarch64-unknown-none-softfloat"] end CargoToml["Cargo.toml"] Dependencies["Dependencies"] Features["Feature Flags"] Metadata["Package Metadata"] KernelGuard["kernel_guard: 0.1"] CfgIf["cfg-if: 1.0"] SMP["smp feature"] Default["default = []"] Name["name: kspin"] Version["version: 0.1.0"] License["Triple license"] MultiCore["Multi-core compilation"] SingleCore["Single-core compilation"] CargoToml --> ARM CargoToml --> Dependencies CargoToml --> Features CargoToml --> Metadata CargoToml --> RiscV CargoToml --> x86Linux CargoToml --> x86None Default --> SingleCore Dependencies --> CfgIf Dependencies --> KernelGuard Features --> Default Features --> SMP Metadata --> License Metadata --> Name Metadata --> Version SMP --> MultiCore
Sources: Cargo.toml(L1 - L22) .github/workflows/ci.yml(L12)
Feature Flag System
The kspin crate uses Cargo feature flags to enable compile-time optimization and conditional compilation based on the target environment. The primary feature flag is smp
, which controls whether the spinlock implementation includes multi-core synchronization primitives.
Feature Configuration
The feature system is defined in the [features]
section of Cargo.toml
:
smp
: Enables multi-core environment support with atomic operationsdefault
: Empty default feature set for maximum compatibility
When the smp
feature is disabled, the implementation optimizes away atomic operations and lock state for single-core environments Cargo.toml(L14 - L17)
Feature Flag Compilation Flow
flowchart TD subgraph subGraph2["cfg-if Usage"] SMPCheck["smp feature enabled?"] CfgIf["cfg-if crate"] end subgraph subGraph1["Single-core Build Path"] SingleBuild["Single-core Build"] NoAtomic["No atomic operations"] NoLockState["No lock state field"] OptimizedAway["Lock always succeeds"] end subgraph subGraph0["SMP Build Path"] SMPBuild["SMP Build"] AtomicOps["Include AtomicBool"] CompareExchange["compare_exchange operations"] MemoryOrdering["Memory ordering constraints"] end FeatureInput["Feature Selection"] FinalBinary["Final Binary"] AtomicOps --> CompareExchange CfgIf --> SMPCheck CompareExchange --> MemoryOrdering FeatureInput --> SMPCheck MemoryOrdering --> FinalBinary NoAtomic --> NoLockState NoLockState --> OptimizedAway OptimizedAway --> FinalBinary SMPBuild --> AtomicOps SMPCheck --> SMPBuild SMPCheck --> SingleBuild SingleBuild --> NoAtomic
Sources: Cargo.toml(L14 - L17) Cargo.toml(L20 - L21)
Continuous Integration Pipeline
The project uses GitHub Actions for automated testing, building, and documentation deployment. The CI pipeline is defined in .github/workflows/ci.yml
and consists of two main jobs: ci
for code validation and doc
for documentation generation.
CI Job Matrix
The ci
job uses a matrix strategy to test across multiple dimensions:
Matrix Dimension | Values |
---|---|
Rust Toolchain | nightly |
Target Platforms | 4 platforms (see table above) |
Feature Combinations | Each feature individually viacargo-hack |
CI Pipeline Stages
CI/CD Pipeline Architecture
flowchart TD subgraph subGraph2["Documentation Job"] DocJob["doc job"] DocCheckout["actions/checkout@v4"] DocRust["dtolnay/rust-toolchain@nightly"] CargoDoc["cargo doc --no-deps --all-features"] IndexHTML["Generate index.html redirect"] GitHubPages["Deploy to gh-pages"] end subgraph subGraph1["CI Steps"] Checkout["actions/checkout@v4"] InstallHack["install cargo-hack"] InstallRust["dtolnay/rust-toolchain@nightly"] Components["rust-src, clippy, rustfmt"] RustVersion["Check rust version"] Format["cargo fmt --check"] Clippy["cargo hack clippy --each-feature"] Build["cargo hack build --each-feature"] Test["cargo hack test --each-feature"] end subgraph subGraph0["CI Job Matrix"] CIJob["ci job"] Matrix["Matrix Strategy"] Nightly["rust-toolchain: nightly"] Targets["4 target platforms"] end Trigger["Push/PR Event"] Success["CI Success"] DocSuccess["Documentation Published"] Build --> Test CIJob --> Matrix CargoDoc --> IndexHTML Checkout --> InstallHack Clippy --> Build Components --> RustVersion DocCheckout --> DocRust DocJob --> DocCheckout DocRust --> CargoDoc Format --> Clippy GitHubPages --> DocSuccess IndexHTML --> GitHubPages InstallHack --> InstallRust InstallRust --> Components Matrix --> Checkout Matrix --> Nightly Matrix --> Targets RustVersion --> Format Test --> Success Trigger --> CIJob Trigger --> DocJob
Sources: .github/workflows/ci.yml(L1 - L57)
Code Quality Checks
The CI pipeline enforces code quality through multiple automated checks:
- Code Formatting:
cargo fmt --all -- --check
ensures consistent code style .github/workflows/ci.yml(L24) - Linting:
cargo hack clippy --target ${{ matrix.targets }} --each-feature -- -D warnings
catches potential issues .github/workflows/ci.yml(L26) - Building:
cargo hack build --target ${{ matrix.targets }} --each-feature
validates compilation .github/workflows/ci.yml(L28) - Testing:
cargo hack test --target ${{ matrix.targets }} --each-feature
runs unit tests (Linux only) .github/workflows/ci.yml(L30 - L31)
Documentation Deployment
The doc
job automatically builds and deploys documentation to GitHub Pages when changes are pushed to the default branch. The documentation is built with strict settings that treat broken links and missing documentation as errors .github/workflows/ci.yml(L41)
Development Environment Setup
To contribute to the kspin crate, developers need to set up a Rust development environment with specific toolchain components and target support.
Required Toolchain Components
The development environment requires the Rust nightly toolchain with the following components:
rust-src
: Source code for cross-compilationclippy
: Linting tool for code analysisrustfmt
: Code formatting tool
Target Installation
Install the required compilation targets:
rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
Development Tools
Install cargo-hack
for feature flag testing:
cargo install cargo-hack
This tool enables testing with each feature flag combination individually, which is essential for ensuring the crate works correctly across different configurations .github/workflows/ci.yml(L15)
Local Development Workflow
Development Workflow
flowchart TD subgraph subGraph1["CI Validation"] CITrigger["CI Pipeline Triggered"] CIFormat["CI: Format check"] CIClippy["CI: Clippy check"] CIBuild["CI: Build all targets"] CITest["CI: Run tests"] CIDoc["CI: Build docs"] end subgraph subGraph0["Development Loop"] DevLoop["Development Loop"] CodeChange["Make code changes"] LocalFormat["cargo fmt"] LocalClippy["cargo clippy"] LocalBuild["cargo build"] LocalTest["cargo test"] Validation["Changes valid?"] Commit["git commit"] end DevStart["Start Development"] Clone["git clone repository"] Setup["Install toolchain components"] Targets["Install target platforms"] Push["git push"] Success["Ready for merge"] CIBuild --> CITest CIClippy --> CIBuild CIDoc --> Success CIFormat --> CIClippy CITest --> CIDoc CITrigger --> CIFormat Clone --> Setup CodeChange --> LocalFormat Commit --> Push DevLoop --> CodeChange DevStart --> Clone LocalBuild --> LocalTest LocalClippy --> LocalBuild LocalFormat --> LocalClippy LocalTest --> Validation Push --> CITrigger Setup --> Targets Targets --> DevLoop Validation --> CodeChange Validation --> Commit
Sources: .github/workflows/ci.yml(L14 - L31) Cargo.toml(L1 - L22)
Testing Procedures
The kspin crate employs comprehensive testing through both local development tools and automated CI validation. Testing is performed across multiple feature combinations and target platforms to ensure broad compatibility.
Local Testing Commands
Command | Purpose |
---|---|
cargo test | Run tests with default features |
cargo hack test --each-feature | Test each feature individually |
cargo hack test --feature smp | Test with SMP feature enabled |
cargo test --target x86_64-unknown-linux-gnu | Test on specific target |
CI Testing Matrix
The CI system automatically tests every feature combination across all supported target platforms. Unit tests are executed only on the x86_64-unknown-linux-gnu
target, as this is the only hosted environment that supports test execution .github/workflows/ci.yml(L30 - L31)
The use of cargo-hack
ensures that feature flag interactions are properly validated and that the crate maintains compatibility across different compilation configurations .github/workflows/ci.yml(L26 - L31)
Sources: .github/workflows/ci.yml(L26 - L31) Cargo.toml(L14 - L22)
Build System and Feature Flags
Relevant source files
This document covers the build system configuration, feature flags, and compilation targets for the kspin crate. It explains how to build the crate with different feature combinations and target platforms, and describes the automated build and testing infrastructure.
For information about the development environment setup and tooling, see Development Environment Setup. For details about the testing infrastructure, see Testing and CI Pipeline.
Feature Flags
The kspin crate uses Cargo feature flags to enable compile-time optimization for different deployment scenarios. The primary feature flag is smp
, which controls whether the spinlock implementation includes multi-core synchronization logic.
SMP Feature Flag
The smp
feature flag is defined in Cargo.toml(L14 - L16) and controls fundamental compilation behavior:
Build Matrix: Feature Flag Compilation
When the smp
feature is enabled:
BaseSpinLock
includes anAtomicBool
state field- Lock operations use
compare_exchange
atomic operations - Full memory ordering constraints are enforced
- Spinning behavior is implemented for contention scenarios
When the smp
feature is disabled:
BaseSpinLock
has no state field (zero-cost abstraction)- Lock operations become no-ops or compile-time optimizations
- No atomic operations are generated
- Suitable for single-core embedded environments
Sources: Cargo.toml(L14 - L16)
Target Platform Support
The build system supports multiple target architectures through the CI matrix configuration. Each target represents a different deployment environment with specific requirements.
Supported Targets
Target Platform | Architecture | Use Case | SMP Support |
---|---|---|---|
x86_64-unknown-linux-gnu | x86-64 | Development/Testing | Full |
x86_64-unknown-none | x86-64 | Bare metal kernel | Full |
riscv64gc-unknown-none-elf | RISC-V 64-bit | Embedded kernel | Full |
aarch64-unknown-none-softfloat | ARM64 | Embedded systems | Full |
flowchart TD subgraph subGraph2["Feature Testing"] EachFeature[".github/workflows/ci.yml:26,28--each-feature flag"] DefaultFeatures["Default buildNo features enabled"] SMPFeatures["SMP buildsmp feature enabled"] end subgraph subGraph1["Build Tools"] RustToolchain[".github/workflows/ci.yml:11rust-toolchain: [nightly]"] CargoHack[".github/workflows/ci.yml:15cargo-hack tool"] Components[".github/workflows/ci.yml:19rust-src, clippy, rustfmt"] end subgraph subGraph0["CI Build Matrix"] Matrix[".github/workflows/ci.yml:10-12matrix.targets"] x86Linux["x86_64-unknown-linux-gnuStandard Linux targetFull testing capabilities"] x86Bare["x86_64-unknown-noneBare metal x86No std library"] RISCV["riscv64gc-unknown-none-elfRISC-V embeddedArceOS target"] ARM["aarch64-unknown-none-softfloatARM64 embeddedSoftware floating point"] end CargoHack --> EachFeature Components --> Matrix EachFeature --> DefaultFeatures EachFeature --> SMPFeatures Matrix --> ARM Matrix --> RISCV Matrix --> x86Bare Matrix --> x86Linux RustToolchain --> Matrix
Sources: .github/workflows/ci.yml(L10 - L12) .github/workflows/ci.yml(L19)
Build Commands and Configuration
Basic Build Commands
The standard build commands work with cargo's feature flag system:
# Default build (no features)
cargo build
# Build with SMP support
cargo build --features smp
# Build for specific target
cargo build --target x86_64-unknown-none --features smp
# Build all feature combinations
cargo hack build --each-feature
CI Build Pipeline
The automated build pipeline uses cargo-hack
to test all feature combinations across all target platforms:
sequenceDiagram participant GitHubActions as "GitHub Actions" participant BuildMatrix as "Build Matrix" participant cargohack as "cargo-hack" participant TargetPlatform as "Target Platform" GitHubActions ->> BuildMatrix: Trigger CI workflow GitHubActions ->> BuildMatrix: .github/workflows/ci.yml:3 BuildMatrix ->> cargohack: Install cargo-hack tool BuildMatrix ->> cargohack: .github/workflows/ci.yml:15 loop Each Target Platform BuildMatrix ->> TargetPlatform: Setup toolchain BuildMatrix ->> TargetPlatform: .github/workflows/ci.yml:16-20 TargetPlatform ->> cargohack: Check format TargetPlatform ->> cargohack: .github/workflows/ci.yml:24 TargetPlatform ->> cargohack: Run clippy TargetPlatform ->> cargohack: .github/workflows/ci.yml:26 cargohack ->> TargetPlatform: --each-feature flag TargetPlatform ->> cargohack: Build all features TargetPlatform ->> cargohack: .github/workflows/ci.yml:28 alt x86_64-unknown-linux-gnu alt only TargetPlatform ->> cargohack: Run tests TargetPlatform ->> cargohack: .github/workflows/ci.yml:30-31 end end end
Build Steps Detail:
- Format Check:
cargo fmt --all -- --check
.github/workflows/ci.yml(L24) - Linting:
cargo hack clippy --target $target --each-feature
.github/workflows/ci.yml(L26) - Compilation:
cargo hack build --target $target --each-feature
.github/workflows/ci.yml(L28) - Testing:
cargo hack test --target $target --each-feature
(Linux only) .github/workflows/ci.yml(L31)
Sources: .github/workflows/ci.yml(L24 - L31)
Dependency Management
The crate's dependencies are managed through Cargo.toml and support the feature flag system:
Dependency Roles:
- cfg-if: Enables conditional compilation based on feature flags and target platform
- kernel_guard: Provides
NoOp
,NoPreempt
, andNoPreemptIrqSave
guard types - smp feature: Controls atomic operation inclusion without additional dependencies
Sources: Cargo.toml(L19 - L21) Cargo.toml(L14 - L17)
Documentation Generation
The build system includes automated documentation generation and deployment:
flowchart TD subgraph Output["Output"] TargetDoc["target/doc/Generated documentation"] DocsRS["docs.rs/kspinCargo.toml:10"] end subgraph subGraph1["Documentation Flags"] RustDocFlags["RUSTDOCFLAGS.github/workflows/ci.yml:41-D rustdoc::broken_intra_doc_links-D missing-docs"] end subgraph subGraph0["Documentation Pipeline"] DocJob[".github/workflows/ci.yml:33doc job"] BuildDocs[".github/workflows/ci.yml:48cargo doc --no-deps --all-features"] IndexGeneration[".github/workflows/ci.yml:49printf redirect to index.html"] GitHubPages[".github/workflows/ci.yml:50-56Deploy to gh-pages branch"] end BuildDocs --> IndexGeneration DocJob --> BuildDocs GitHubPages --> DocsRS IndexGeneration --> TargetDoc RustDocFlags --> BuildDocs TargetDoc --> GitHubPages
The documentation build enforces strict documentation requirements through RUSTDOCFLAGS
and deploys automatically to GitHub Pages for the default branch.
Sources: .github/workflows/ci.yml(L33 - L56) .github/workflows/ci.yml(L41) Cargo.toml(L10)
Testing and CI Pipeline
Relevant source files
This document covers the automated testing infrastructure and continuous integration setup for the kspin crate. The testing and CI pipeline ensures code quality, compatibility across multiple target platforms, and automated documentation deployment.
For information about manually building the crate and configuring development environments, see Build System and Feature Flags and Development Environment Setup.
CI Pipeline Architecture
The kspin crate uses GitHub Actions for continuous integration, configured through a single workflow file that handles both testing and documentation deployment. The pipeline is designed to validate the crate across multiple embedded and general-purpose target platforms.
CI Pipeline Overview
flowchart TD subgraph subGraph3["Doc Job Steps"] DocCheckout["actions/checkout@v4"] DocRust["dtolnay/rust-toolchain@nightly"] BuildDocs["cargo doc --no-deps --all-features"] Deploy["JamesIves/github-pages-deploy-action@v4"] end subgraph subGraph2["CI Job Steps"] Checkout["actions/checkout@v4"] InstallHack["taiki-e/install-action@cargo-hack"] SetupRust["dtolnay/rust-toolchain@nightly"] CheckVersion["rustc --version --verbose"] Format["cargo fmt --all -- --check"] Clippy["cargo hack clippy"] Build["cargo hack build"] Test["cargo hack test"] end subgraph subGraph1["GitHub Actions Workflow"] CIJob["ci jobTesting & Quality"] DocJob["doc jobDocumentation"] end subgraph subGraph0["Trigger Events"] Push["push events"] PR["pull_request events"] end Build --> Test BuildDocs --> Deploy CIJob --> Checkout CheckVersion --> Format Checkout --> InstallHack Clippy --> Build DocCheckout --> DocRust DocJob --> DocCheckout DocRust --> BuildDocs Format --> Clippy InstallHack --> SetupRust PR --> CIJob PR --> DocJob Push --> CIJob Push --> DocJob SetupRust --> CheckVersion
Sources: .github/workflows/ci.yml(L1 - L57)
Build Matrix Strategy
The CI pipeline uses a matrix build strategy to validate the codebase across multiple target platforms and feature combinations. This ensures compatibility with the diverse environments where kernel spinlocks are typically deployed.
Component | Configuration |
---|---|
Runner | ubuntu-latest |
Rust Toolchain | nightly |
Required Components | rust-src,clippy,rustfmt |
Target Platforms | 4 targets (see below) |
Failure Strategy | fail-fast: false |
Target Platform Matrix
flowchart TD subgraph subGraph1["Target Platforms"] LinuxGNU["x86_64-unknown-linux-gnuStandard Linux userspace"] x86None["x86_64-unknown-noneBare metal x86_64"] RISCV["riscv64gc-unknown-none-elfRISC-V bare metal"] ARM["aarch64-unknown-none-softfloatARM64 bare metal"] end subgraph subGraph0["Matrix Strategy"] Matrix["matrix.targets"] end subgraph subGraph2["Feature Testing"] EachFeature["--each-feature flagTests all feature combinations"] SMPFeature["smp featureMulti-core vs single-core"] end EachFeature --> SMPFeature Matrix --> ARM Matrix --> LinuxGNU Matrix --> RISCV Matrix --> x86None
The cargo-hack
tool is used with the --each-feature
flag to test all possible feature combinations across all target platforms, ensuring that feature-conditional compilation works correctly.
Sources: .github/workflows/ci.yml(L8 - L20) .github/workflows/ci.yml(L26 - L31)
Quality Assurance Pipeline
The CI pipeline enforces code quality through multiple automated checks that must pass before code can be merged.
Quality Check Sequence
sequenceDiagram participant GitHubActions as "GitHub Actions" participant RustToolchain as "Rust Toolchain" participant cargohack as "cargo-hack" participant TargetPlatform as "Target Platform" GitHubActions ->> RustToolchain: Setup nightly toolchain RustToolchain ->> GitHubActions: Install rust-src, clippy, rustfmt Note over GitHubActions,TargetPlatform: For each target in matrix GitHubActions ->> RustToolchain: cargo fmt --all -- --check RustToolchain ->> GitHubActions: ✓ Code formatting validated GitHubActions ->> cargohack: cargo hack clippy --target <target> --each-feature cargohack ->> TargetPlatform: Run clippy for each feature combination TargetPlatform ->> cargohack: ✓ No warnings (-D warnings) cargohack ->> GitHubActions: ✓ Linting complete GitHubActions ->> cargohack: cargo hack build --target <target> --each-feature cargohack ->> TargetPlatform: Build each feature combination TargetPlatform ->> cargohack: ✓ Successful build cargohack ->> GitHubActions: ✓ Build complete
Code Formatting
- Tool:
cargo fmt
- Enforcement:
--check
flag ensures CI fails if code is not properly formatted - Scope: All files (
--all
flag)
Linting
- Tool:
cargo clippy
- Configuration: Treats all warnings as errors (
-D warnings
) - Coverage: All target platforms and feature combinations
- Command:
cargo hack clippy --target ${{ matrix.targets }} --each-feature -- -D warnings
Build Validation
- Tool:
cargo build
viacargo-hack
- Coverage: All target platforms and feature combinations
- Command:
cargo hack build --target ${{ matrix.targets }} --each-feature
Sources: .github/workflows/ci.yml(L24 - L28)
Testing Strategy
The testing approach recognizes the constraints of kernel-space code while maximizing coverage where possible.
Test Execution Matrix
flowchart TD subgraph subGraph2["Test Execution"] TestRun["if: matrix.targets == 'x86_64-unknown-linux-gnu'cargo hack test --target --each-feature -- --nocapture"] BuildOnly["Build-only validationfor bare metal targets"] end subgraph subGraph1["Test Types"] UnitTests["Unit Testscargo hack test"] BuildTests["Build Testscargo hack build"] FeatureTests["Feature Tests--each-feature"] end subgraph subGraph0["Test Targets"] LinuxTarget["x86_64-unknown-linux-gnu"] BareMetalTargets["Bare metal targetsx86_64-unknown-noneriscv64gc-unknown-none-elfaarch64-unknown-none-softfloat"] end BareMetalTargets --> BuildOnly BareMetalTargets --> BuildTests BareMetalTargets --> FeatureTests FeatureTests --> TestRun LinuxTarget --> BuildTests LinuxTarget --> FeatureTests LinuxTarget --> TestRun LinuxTarget --> UnitTests UnitTests --> TestRun
Unit Test Execution
- Platform Restriction: Tests only run on
x86_64-unknown-linux-gnu
- Rationale: Bare metal targets cannot execute standard Rust test framework
- Configuration: Tests run with
--nocapture
for full output visibility - Feature Coverage: All feature combinations tested via
--each-feature
Build-Only Testing
For bare metal targets (x86_64-unknown-none
, riscv64gc-unknown-none-elf
, aarch64-unknown-none-softfloat
), the pipeline performs build-only validation to ensure:
- Code compiles successfully for target architecture
- Feature flags work correctly in no-std environments
- Cross-compilation succeeds without runtime dependencies
Sources: .github/workflows/ci.yml(L30 - L31)
Documentation Pipeline
The documentation system automatically builds and deploys API documentation to GitHub Pages, ensuring developers always have access to current documentation.
Documentation Workflow
flowchart TD subgraph Deployment["Deployment"] BranchCheck["if: github.ref == env.default-branch"] GHPages["JamesIves/github-pages-deploy-action@v4branch: gh-pagesfolder: target/docsingle-commit: true"] end subgraph subGraph1["Build Process"] DocBuild["cargo doc --no-deps --all-features"] IndexGen["Generate redirect index.html$(cargo tree | head -1 | cut -d' ' -f1)"] ContinueOnError["continue-on-error: conditionalBased on branch and event type"] end subgraph subGraph0["Documentation Job"] DocTrigger["Push to default branchOR pull request"] DocPerms["permissions:contents: write"] DocEnv["RUSTDOCFLAGS:-D rustdoc::broken_intra_doc_links-D missing-docs"] end BranchCheck --> GHPages ContinueOnError --> BranchCheck DocBuild --> IndexGen DocEnv --> DocBuild DocPerms --> DocEnv DocTrigger --> DocPerms IndexGen --> ContinueOnError
Documentation Quality Enforcement
The documentation build process enforces strict quality standards:
Setting | Purpose |
---|---|
-D rustdoc::broken_intra_doc_links | Fails build on broken internal documentation links |
-D missing-docs | Requires documentation for all public items |
--no-deps | Builds only crate documentation, not dependencies |
--all-features | Documents all feature-gated functionality |
Deployment Strategy
- Target Branch:
gh-pages
- Deployment Condition: Only on pushes to the default branch
- Commit Strategy:
single-commit: true
keeps deployment history clean - Index Generation: Automatically creates redirect to main crate documentation
Sources: .github/workflows/ci.yml(L33 - L57)
Pipeline Robustness Features
The CI configuration includes several features designed to maintain pipeline reliability and provide useful feedback:
Error Handling
- Fail-fast disabled:
fail-fast: false
allows all matrix jobs to complete even if some fail - Conditional error handling: Documentation builds continue on error for non-default branches
- Warning promotion: Clippy warnings treated as errors to maintain code quality
Toolchain Management
- Nightly Rust: Uses bleeding-edge features required for no-std development
- Component installation: Automatically installs required components (
rust-src
,clippy
,rustfmt
) - Version verification: Explicit Rust version checking for debugging
Resource Optimization
- Single commit deployment: Minimizes repository size growth from documentation updates
- Targeted testing: Unit tests only run where feasible (Linux target)
- Efficient caching: Standard GitHub Actions caching for Rust toolchain and dependencies
Sources: .github/workflows/ci.yml(L9) .github/workflows/ci.yml(L41) .github/workflows/ci.yml(L46)
Development Environment Setup
Relevant source files
This document provides guidance for setting up a development environment to build, test, and contribute to the kspin crate. It covers the required toolchain, build processes, and development practices used by the project.
For information about the build system's feature flags and compilation targets, see Build System and Feature Flags. For details about the automated testing infrastructure, see Testing and CI Pipeline.
Prerequisites
The kspin crate requires specific Rust toolchain components and supports multiple target platforms. The development environment must be configured to handle both hosted and no-std embedded targets.
Required Rust Toolchain Components
The project uses the Rust nightly toolchain with specific components required for cross-platform development and code quality checks:
Component | Purpose |
---|---|
rust-src | Source code for cross-compilation to no-std targets |
clippy | Linting and static analysis |
rustfmt | Code formatting |
Supported Target Platforms
The CI system validates builds across multiple target architectures:
Target | Platform Type | Usage Context |
---|---|---|
x86_64-unknown-linux-gnu | Hosted Linux | Development and testing |
x86_64-unknown-none | Bare metal x86-64 | Kernel environments |
riscv64gc-unknown-none-elf | Bare metal RISC-V | Embedded kernel systems |
aarch64-unknown-none-softfloat | Bare metal ARM64 | Embedded kernel systems |
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L18 - L20)
Development Toolchain Setup
Rust Toolchain Installation
Install the nightly Rust toolchain with required components and target platforms:
# Install nightly toolchain
rustup toolchain install nightly
# Add required components
rustup component add --toolchain nightly rust-src clippy rustfmt
# Add target platforms
rustup target add --toolchain nightly x86_64-unknown-none
rustup target add --toolchain nightly riscv64gc-unknown-none-elf
rustup target add --toolchain nightly aarch64-unknown-none-softfloat
Development Tools
Install cargo-hack
for comprehensive feature combination testing:
cargo install cargo-hack
The cargo-hack
tool enables testing all feature combinations across different targets, matching the CI environment exactly.
Sources: .github/workflows/ci.yml(L15) .github/workflows/ci.yml(L16 - L20)
Development Workflow
Development Environment Flow
flowchart TD subgraph Documentation["Documentation"] DocBuild["cargo doc --no-deps --all-featuresLocal documentation"] DocView["Open target/doc/kspin/index.htmlView generated docs"] end subgraph subGraph2["Local Development Commands"] Format["cargo fmt --all --checkCode formatting"] Clippy["cargo hack clippy--target TARGET --each-feature-- -D warnings"] Build["cargo hack build--target TARGET --each-feature"] Test["cargo hack test--target x86_64-unknown-linux-gnu--each-feature -- --nocapture"] end subgraph subGraph1["Development Tools"] VSCode[".vscode/VS Code configuration"] Git[".gitignoreGit ignore rules"] CargoToml["Cargo.tomlDependencies & metadata"] end subgraph subGraph0["Local Development Setup"] RustInstall["rustup install nightly"] ComponentAdd["rustup component addrust-src clippy rustfmt"] TargetAdd["rustup target addx86_64-unknown-noneriscv64gc-unknown-none-elfaarch64-unknown-none-softfloat"] CargoHack["cargo install cargo-hack"] end Build --> Test CargoHack --> Format CargoToml --> Build Clippy --> Build ComponentAdd --> TargetAdd DocBuild --> DocView Format --> Clippy Git --> VSCode RustInstall --> ComponentAdd TargetAdd --> CargoHack Test --> DocBuild VSCode --> Format
Sources: .github/workflows/ci.yml(L16 - L31) .github/workflows/ci.yml(L45 - L49) .gitignore(L2)
Local Build and Test Process
Code Quality Checks
Replicate the CI environment locally by running the same commands used in automated testing:
Code Formatting
cargo fmt --all -- --check
Linting with Feature Combinations
# Lint for specific target with all feature combinations
cargo hack clippy --target x86_64-unknown-none --each-feature -- -D warnings
cargo hack clippy --target riscv64gc-unknown-none-elf --each-feature -- -D warnings
Building Across Targets
# Build for all feature combinations on each target
cargo hack build --target x86_64-unknown-none --each-feature
cargo hack build --target riscv64gc-unknown-none-elf --each-feature
cargo hack build --target aarch64-unknown-none-softfloat --each-feature
Testing
# Run tests (only on hosted target)
cargo hack test --target x86_64-unknown-linux-gnu --each-feature -- --nocapture
Sources: .github/workflows/ci.yml(L24) .github/workflows/ci.yml(L26) .github/workflows/ci.yml(L28) .github/workflows/ci.yml(L31)
Feature Flag Testing
The --each-feature
flag tests the following combinations:
- No features (default)
smp
feature enabled- All features enabled
This ensures the crate works correctly in both single-core and multi-core environments.
Local Development Command Mapping
flowchart TD subgraph subGraph3["Target Platforms"] LinuxGnu["x86_64-unknown-linux-gnu(testing only)"] X86None["x86_64-unknown-none(kernel target)"] RiscV["riscv64gc-unknown-none-elf(embedded kernel)"] ARM["aarch64-unknown-none-softfloat(embedded kernel)"] end subgraph subGraph2["Local Development"] LocalFormat["cargo fmt"] LocalClippy["cargo clippy"] LocalBuild["cargo build"] LocalTest["cargo test"] LocalDoc["cargo doc --no-deps --all-features"] end subgraph subGraph1["CI Commands"] CIFormat["cargo fmt --all -- --check"] CIClippy["cargo hack clippy --target TARGET--each-feature -- -D warnings"] CIBuild["cargo hack build --target TARGET--each-feature"] CITest["cargo hack test --target TARGET--each-feature -- --nocapture"] end subgraph subGraph0["Cargo.toml Configuration"] Features["[features]smp = []default = []"] Dependencies["[dependencies]cfg-if = '1.0'kernel_guard = '0.1'"] end CIBuild --> ARM CIBuild --> LocalBuild CIBuild --> RiscV CIBuild --> X86None CIClippy --> LocalClippy CIFormat --> LocalFormat CITest --> LinuxGnu CITest --> LocalTest Dependencies --> LocalBuild Features --> CIBuild LocalDoc --> CIBuild
Sources: Cargo.toml(L14 - L17) Cargo.toml(L19 - L21) .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L24 - L31)
Development Environment Configuration
IDE Setup
The project includes VS Code ignore rules, indicating support for Visual Studio Code development:
/.vscode
Developers can create local .vscode/settings.json
configurations for:
- Rust-analyzer settings
- Target-specific build configurations
- Code formatting preferences
Git Configuration
The .gitignore
file excludes:
- Build artifacts (
/target
) - IDE configurations (
/.vscode
) - System files (
.DS_Store
)
Sources: .gitignore(L1 - L3)
Documentation Development
Local Documentation Building
Generate and view documentation locally:
# Build documentation with all features
cargo doc --no-deps --all-features
# View in browser
open target/doc/kspin/index.html
The documentation build process matches the CI environment, ensuring consistency with the published GitHub Pages documentation.
Documentation Quality Checks
The CI system enforces documentation quality with specific RUSTDOCFLAGS
:
RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs" cargo doc --no-deps --all-features
Sources: .github/workflows/ci.yml(L41) .github/workflows/ci.yml(L45 - L49)
Continuous Integration Alignment
The local development environment should replicate the CI matrix strategy to ensure compatibility:
Matrix Testing Strategy
Toolchain | All Targets | Feature Testing |
---|---|---|
nightly | ✓ | --each-feature |
CI Job Replication
- Format Check:
cargo fmt --all -- --check
- Lint Check:
cargo hack clippy --target TARGET --each-feature -- -D warnings
- Build Check:
cargo hack build --target TARGET --each-feature
- Unit Tests:
cargo hack test --target x86_64-unknown-linux-gnu --each-feature -- --nocapture
- Documentation:
cargo doc --no-deps --all-features
Running these commands locally before committing ensures smooth CI pipeline execution.
Sources: .github/workflows/ci.yml(L8 - L12) .github/workflows/ci.yml(L24 - L31)
Overview
Relevant source files
Purpose and Scope
The kernel_guard
crate provides RAII (Resource Acquisition Is Initialization) wrappers for creating critical sections in kernel-level code where local interrupts (IRQs) and/or preemption must be temporarily disabled. This crate is specifically designed for operating system kernels and bare-metal systems that require fine-grained control over interrupt handling and task scheduling.
The primary use case is implementing synchronization primitives like spinlocks in kernel code, where critical sections must be protected from both interrupt handlers and preemptive task switching. For detailed information about the core architecture and guard implementations, see Core Architecture. For multi-architecture support details, see Multi-Architecture Support. For integration guidance, see Integration Guide.
Sources: Cargo.toml(L6 - L12) src/lib.rs(L1 - L11) README.md(L7 - L11)
Core Guard Types and RAII Pattern
The crate implements four distinct guard types that follow the RAII pattern, automatically managing critical section entry and exit through constructor and destructor semantics:
Guard Type Architecture
flowchart TD subgraph subGraph2["Target Dependencies"] ARCH["arch::local_irq_save_and_disable()arch::local_irq_restore()"] INTERFACE["crate_interface::call_interface!KernelGuardIf::disable_preemptKernelGuardIf::enable_preempt"] end subgraph subGraph1["Concrete Guards"] NOOP["NoOptype State = ()No operation"] IRQ["IrqSavetype State = usizeSaves/restores IRQ state"] PREEMPT["NoPreempttype State = ()Disables preemption"] BOTH["NoPreemptIrqSavetype State = usizeDisables both"] end subgraph subGraph0["BaseGuard Trait"] BG["BaseGuardacquire() -> Staterelease(State)"] end BG --> BOTH BG --> IRQ BG --> NOOP BG --> PREEMPT BOTH --> ARCH BOTH --> INTERFACE IRQ --> ARCH PREEMPT --> INTERFACE
Sources: src/lib.rs(L68 - L78) src/lib.rs(L81 - L111) src/lib.rs(L134 - L179)
Guard Type | State Type | IRQ Control | Preemption Control | Target Compatibility |
---|---|---|---|---|
NoOp | () | None | None | All targets |
IrqSave | usize | Save/restore | None | target_os = "none"only |
NoPreempt | () | None | Disable/enable | Requirespreemptfeature |
NoPreemptIrqSave | usize | Save/restore | Disable/enable | target_os = "none"+preemptfeature |
Sources: src/lib.rs(L113 - L128) src/lib.rs(L134 - L179) src/lib.rs(L200 - L237)
Conditional Compilation Strategy
The crate uses sophisticated conditional compilation to provide different implementations based on the target environment and enabled features:
Compilation Flow and Code Paths
Sources: src/lib.rs(L83 - L111) src/lib.rs(L130 - L238) Cargo.toml(L14 - L16)
Multi-Architecture Support Integration
The crate abstracts platform-specific interrupt handling through a unified architecture interface:
Architecture Module Structure
Sources: src/lib.rs(L56) src/lib.rs(L139 - L145) src/lib.rs(L170 - L174)
User Integration Pattern
The crate uses the crate_interface
mechanism to allow users to provide preemption control implementations:
Integration Interface Flow
flowchart TD subgraph subGraph2["Runtime Behavior"] ACQUIRE["acquire() calls disable_preempt"] DROP["drop() calls enable_preempt"] end subgraph subGraph1["kernel_guard Crate"] TRAIT_DEF["#[crate_interface::def_interface]trait KernelGuardIfsrc/lib.rs:59-66"] CALL_SITE["crate_interface::call_interface!KernelGuardIf::disable_preemptsrc/lib.rs:154"] GUARD_IMPL["NoPreempt BaseGuard implsrc/lib.rs:149-161"] end subgraph subGraph0["User Code"] USER_IMPL["struct KernelGuardIfImpl"] TRAIT_IMPL["#[crate_interface::impl_interface]impl KernelGuardIf for KernelGuardIfImpl"] USAGE["let guard = NoPreempt::new()"] end CALL_SITE --> TRAIT_IMPL GUARD_IMPL --> ACQUIRE GUARD_IMPL --> CALL_SITE GUARD_IMPL --> DROP TRAIT_IMPL --> TRAIT_DEF USAGE --> GUARD_IMPL USER_IMPL --> TRAIT_IMPL
Sources: src/lib.rs(L58 - L66) src/lib.rs(L149 - L161) src/lib.rs(L31 - L52) README.md(L36 - L58)
Feature Configuration Summary
The crate provides two primary configuration axes:
Configuration | Effect | Code Path |
---|---|---|
target_os = "none" | Enables real IRQ control implementations | src/lib.rs86-100 |
target_os != "none" | UsesNoOpaliases for all guards | src/lib.rs102-110 |
feature = "preempt" | Enables preemption control calls | src/lib.rs153-159 |
Nopreemptfeature | Preemption calls become no-ops | Conditional compilation removes calls |
Sources: src/lib.rs(L83 - L111) Cargo.toml(L14 - L16) src/lib.rs(L153 - L159)
The crate is specifically designed for the ArceOS ecosystem but provides a generic interface suitable for any kernel or bare-metal system requiring interrupt and preemption management. The modular architecture ensures that only the necessary functionality is compiled for each target platform while maintaining a consistent API across all supported architectures.
Core Architecture
Relevant source files
This document describes the core architecture of the kernel_guard
crate, focusing on the trait system, RAII guard implementations, and conditional compilation strategy. The architecture provides a unified interface for creating critical sections while abstracting platform-specific interrupt handling and kernel preemption control.
For information about platform-specific implementations, see Multi-Architecture Support. For integration details and feature configuration, see Integration Guide.
Trait System Foundation
The kernel_guard
crate is built around two core traits that define the contract for critical section management and kernel integration.
BaseGuard Trait
The BaseGuard
trait serves as the fundamental interface that all guard implementations must satisfy. It defines a generic pattern for acquiring and releasing critical section protection.
classDiagram class BaseGuard { <<trait>> +type State: Clone + Copy +acquire() State +release(state: State) } class NoOp { +type State =() +acquire()() +release(()) } class IrqSave { +type State = usize +acquire() usize +release(usize) } class NoPreempt { +type State =() +acquire()() +release(()) } class NoPreemptIrqSave { +type State = usize +acquire() usize +release(usize) } BaseGuard ..|> NoOp BaseGuard ..|> IrqSave BaseGuard ..|> NoPreempt NoPreemptIrqSave ..|> IrqSave
BaseGuard Trait Architecture
The trait uses an associated State
type to capture platform-specific information needed to restore the system state when the critical section ends. For example, interrupt guards store the previous interrupt flag state, while preemption-only guards use unit type since no state needs preservation.
Sources: src/lib.rs(L68 - L78)
KernelGuardIf Interface
The KernelGuardIf
trait provides the integration point between kernel_guard
and the user's kernel implementation. This trait must be implemented by crate users when the preempt
feature is enabled.
flowchart TD subgraph subGraph1["kernel_guard Crate"] KernelGuardIf["KernelGuardIf Traitenable_preempt()disable_preempt()"] CallInterface["crate_interface::call_interface!Runtime dispatch"] NoPreempt["NoPreempt Guard"] NoPreemptIrqSave["NoPreemptIrqSave Guard"] end subgraph subGraph0["User Crate"] UserImpl["User ImplementationKernelGuardIfImpl"] end CallInterface --> KernelGuardIf NoPreempt --> CallInterface NoPreemptIrqSave --> CallInterface UserImpl --> KernelGuardIf
KernelGuardIf Integration Pattern
The interface uses the crate_interface
crate to provide stable ABI boundaries, allowing the kernel implementation to be provided at runtime rather than compile time.
Sources: src/lib.rs(L58 - L66) src/lib.rs(L154) src/lib.rs(L159) src/lib.rs(L168) src/lib.rs(L177)
Guard Implementation Hierarchy
The crate provides four distinct guard types, each designed for specific synchronization requirements in kernel contexts.
Guard Type Matrix
Guard Type | IRQ Control | Preemption Control | State Type | Primary Use Case |
---|---|---|---|---|
NoOp | None | None | () | Testing, userspace |
IrqSave | Disable/Restore | None | usize | IRQ-sensitive operations |
NoPreempt | None | Disable/Enable | () | Scheduler coordination |
NoPreemptIrqSave | Disable/Restore | Disable/Enable | usize | Complete critical sections |
Implementation Strategy
flowchart TD subgraph subGraph2["Platform Integration"] ArchIRQ["arch::local_irq_save_and_disable()"] ArchRestore["arch::local_irq_restore(state)"] KernelIF["KernelGuardIf::disable_preempt()"] KernelRestore["KernelGuardIf::enable_preempt()"] end subgraph subGraph1["State Management"] IRQState["IRQ State (usize)Platform flags"] NoState["No State (())Simple enable/disable"] end subgraph subGraph0["Guard Lifecycle"] Create["Guard::new()"] Acquire["BaseGuard::acquire()"] Critical["Critical SectionUser Code"] Drop["impl Drop"] Release["BaseGuard::release(state)"] end Acquire --> ArchIRQ Acquire --> Critical Acquire --> KernelIF ArchIRQ --> IRQState Create --> Acquire Critical --> Drop Drop --> Release IRQState --> ArchRestore KernelIF --> NoState NoState --> KernelRestore Release --> ArchRestore Release --> KernelRestore
RAII Guard Lifecycle and Platform Integration
Sources: src/lib.rs(L134 - L179) src/lib.rs(L181 - L237)
Conditional Compilation Strategy
The crate uses sophisticated conditional compilation to provide different implementations based on the target environment and available features.
Target Environment Detection
flowchart TD subgraph subGraph3["Feature Gates"] PreemptEnabled["#[cfg(feature = 'preempt')]crate_interface calls"] PreemptDisabled["No preemption control"] end subgraph subGraph2["Userspace Path"] AliasIrqSave["type IrqSave = NoOp"] AliasNoPreempt["type NoPreempt = NoOp"] AliasNoPreemptIrqSave["type NoPreemptIrqSave = NoOp"] end subgraph subGraph1["Bare Metal Path"] RealIrqSave["struct IrqSave(usize)"] RealNoPreempt["struct NoPreempt"] RealNoPreemptIrqSave["struct NoPreemptIrqSave(usize)"] ArchIntegration["arch::local_irq_* calls"] end subgraph subGraph0["Compilation Decision"] CfgIf["cfg_if! macro"] TargetCheck["target_os = 'none' or doc?"] FeatureCheck["preempt feature enabled?"] end CfgIf --> TargetCheck FeatureCheck --> PreemptDisabled FeatureCheck --> PreemptEnabled RealIrqSave --> ArchIntegration RealNoPreempt --> PreemptEnabled RealNoPreemptIrqSave --> ArchIntegration RealNoPreemptIrqSave --> PreemptEnabled TargetCheck --> AliasIrqSave TargetCheck --> AliasNoPreempt TargetCheck --> AliasNoPreemptIrqSave TargetCheck --> RealIrqSave TargetCheck --> RealNoPreempt TargetCheck --> RealNoPreemptIrqSave
Conditional Compilation Flow
The compilation strategy ensures that:
- Bare metal targets (
target_os = "none"
) receive full guard implementations with platform-specific interrupt control - Userspace targets receive no-op aliases to prevent compilation errors while maintaining API compatibility
- Feature-gated preemption only compiles preemption control when the
preempt
feature is enabled
Sources: src/lib.rs(L83 - L111) src/lib.rs(L130 - L238) src/lib.rs(L153 - L154) src/lib.rs(L158 - L159) src/lib.rs(L167 - L168) src/lib.rs(L176 - L177)
Architecture Module Integration
The core library integrates with platform-specific implementations through the arch
module, which provides a uniform interface for interrupt control across different CPU architectures.
Architecture Abstraction Interface
flowchart TD subgraph subGraph2["Platform Implementations"] X86["x86::local_irq_*"] RISCV["riscv::local_irq_*"] AArch64["aarch64::local_irq_*"] LoongArch["loongarch64::local_irq_*"] end subgraph subGraph1["Architecture Module"] ArchMod["mod arch"] LocalIrqSave["local_irq_save_and_disable()"] LocalIrqRestore["local_irq_restore(state)"] end subgraph subGraph0["Core Library"] IrqSave["IrqSave::acquire()"] IrqRestore["IrqSave::release()"] NoPreemptIrqSave["NoPreemptIrqSave"] end ArchMod --> LocalIrqRestore ArchMod --> LocalIrqSave IrqRestore --> LocalIrqRestore IrqSave --> LocalIrqSave LocalIrqRestore --> AArch64 LocalIrqRestore --> LoongArch LocalIrqRestore --> RISCV LocalIrqRestore --> X86 LocalIrqSave --> AArch64 LocalIrqSave --> LoongArch LocalIrqSave --> RISCV LocalIrqSave --> X86 NoPreemptIrqSave --> LocalIrqRestore NoPreemptIrqSave --> LocalIrqSave
Architecture Module Integration Pattern
The architecture module provides two critical functions that abstract platform-specific interrupt handling:
local_irq_save_and_disable()
: Returns current interrupt state and disables interruptslocal_irq_restore(state)
: Restores interrupt state from saved value
Sources: src/lib.rs(L56) src/lib.rs(L139) src/lib.rs(L145) src/lib.rs(L170) src/lib.rs(L174)
RAII Lifecycle Management
The crate implements the RAII (Resource Acquisition Is Initialization) pattern to ensure critical sections are properly managed without requiring explicit cleanup calls.
Drop Implementation Pattern
sequenceDiagram participant UserCode as "User Code" participant GuardInstance as "Guard Instance" participant BaseGuardacquirerelease as "BaseGuard::acquire/release" participant PlatformLayer as "Platform Layer" UserCode ->> GuardInstance: "Guard::new()" GuardInstance ->> BaseGuardacquirerelease: "acquire()" BaseGuardacquirerelease ->> PlatformLayer: "disable IRQ/preemption" PlatformLayer -->> BaseGuardacquirerelease: "return state" BaseGuardacquirerelease -->> GuardInstance: "return state" GuardInstance -->> UserCode: "return guard instance" Note over UserCode: Critical section executes UserCode ->> GuardInstance: "drop(guard) or scope end" GuardInstance ->> BaseGuardacquirerelease: "release(state)" BaseGuardacquirerelease ->> PlatformLayer: "restore IRQ/preemption" PlatformLayer -->> BaseGuardacquirerelease: "complete" BaseGuardacquirerelease -->> GuardInstance: "complete" GuardInstance -->> UserCode: "destruction complete"
RAII Lifecycle Sequence
Each guard type implements the Drop
trait to ensure that critical section protection is automatically released when the guard goes out of scope, preventing resource leaks and ensuring system stability.
Sources: src/lib.rs(L126 - L128) src/lib.rs(L188 - L192) src/lib.rs(L208 - L212) src/lib.rs(L227 - L231)
RAII Guards
Relevant source files
This document describes the four RAII guard types provided by the kernel_guard
crate: NoOp
, IrqSave
, NoPreempt
, and NoPreemptIrqSave
. These guards implement the Resource Acquisition Is Initialization (RAII) pattern to automatically manage critical sections by disabling interrupts and/or preemption when created and restoring the previous state when dropped.
For information about the underlying trait system that enables these guards, see Trait System. For architecture-specific implementations of interrupt control, see Multi-Architecture Support.
Overview
The RAII guards in kernel_guard
provide automatic critical section management through Rust's ownership system. Each guard type disables specific kernel features upon creation and restores them when the guard goes out of scope, ensuring that critical sections are properly bounded even in the presence of early returns or panics.
Guard Type Hierarchy
Sources: src/lib.rs(L68 - L78) src/lib.rs(L80 - L111) src/lib.rs(L113 - L128)
RAII Lifecycle Pattern
sequenceDiagram participant UserCode as "User Code" participant GuardStruct as "Guard Struct" participant BaseGuardacquire as "BaseGuard::acquire()" participant archlocal_irq_ as "arch::local_irq_*" participant KernelGuardIf as "KernelGuardIf" participant Dropdrop as "Drop::drop()" UserCode ->> GuardStruct: "new()" GuardStruct ->> BaseGuardacquire: "acquire()" alt IrqSave or NoPreemptIrqSave BaseGuardacquire ->> archlocal_irq_: "local_irq_save_and_disable()" archlocal_irq_ -->> BaseGuardacquire: "saved_flags: usize" end alt NoPreempt or NoPreemptIrqSave BaseGuardacquire ->> KernelGuardIf: "disable_preempt()" end BaseGuardacquire -->> GuardStruct: "state" GuardStruct -->> UserCode: "guard_instance" Note over UserCode: Critical section code runs UserCode ->> Dropdrop: "guard goes out of scope" Dropdrop ->> BaseGuardacquire: "release(state)" alt IrqSave or NoPreemptIrqSave BaseGuardacquire ->> archlocal_irq_: "local_irq_restore(state)" end alt NoPreempt or NoPreemptIrqSave BaseGuardacquire ->> KernelGuardIf: "enable_preempt()" end
Sources: src/lib.rs(L134 - L179) src/lib.rs(L181 - L237)
Guard Types
NoOp Guard
The NoOp
guard provides a no-operation implementation that does nothing around critical sections. It serves as a placeholder when guard functionality is not needed or not available.
Property | Value |
---|---|
State Type | () |
Target Availability | All targets |
IRQ Control | None |
Preemption Control | None |
The NoOp
guard is always available and implements BaseGuard
with empty operations:
// Implementation reference from src/lib.rs:113-117
impl BaseGuard for NoOp {
type State = ();
fn acquire() -> Self::State {}
fn release(_state: Self::State) {}
}
Sources: src/lib.rs(L80 - L81) src/lib.rs(L113 - L128)
IrqSave Guard
The IrqSave
guard disables local interrupts and saves the previous interrupt state, restoring it when dropped. This guard is only available on bare-metal targets (target_os = "none"
).
Property | Value |
---|---|
State Type | usize(saved IRQ flags) |
Target Availability | target_os = "none"only |
IRQ Control | Saves and disables, then restores |
Preemption Control | None |
On non-bare-metal targets, IrqSave
becomes a type alias to NoOp
:
// Conditional compilation from src/lib.rs:83-111
cfg_if::cfg_if! {
if #[cfg(any(target_os = "none", doc))] {
pub struct IrqSave(usize);
} else {
pub type IrqSave = NoOp;
}
}
The IrqSave
implementation calls architecture-specific interrupt control functions:
Sources: src/lib.rs(L88) src/lib.rs(L102 - L103) src/lib.rs(L134 - L147) src/lib.rs(L181 - L198)
NoPreempt Guard
The NoPreempt
guard disables kernel preemption for the duration of the critical section. It requires the preempt
feature to be enabled and a user implementation of KernelGuardIf
.
Property | Value |
---|---|
State Type | () |
Target Availability | target_os = "none"only |
IRQ Control | None |
Preemption Control | Disables, then re-enables |
The preemption control is conditional on the preempt
feature:
// Feature-gated implementation from src/lib.rs:149-161
impl BaseGuard for NoPreempt {
type State = ();
fn acquire() -> Self::State {
#[cfg(feature = "preempt")]
crate_interface::call_interface!(KernelGuardIf::disable_preempt);
}
fn release(_state: Self::State) {
#[cfg(feature = "preempt")]
crate_interface::call_interface!(KernelGuardIf::enable_preempt);
}
}
Sources: src/lib.rs(L92) src/lib.rs(L105 - L106) src/lib.rs(L149 - L161) src/lib.rs(L200 - L218)
NoPreemptIrqSave Guard
The NoPreemptIrqSave
guard combines both preemption and interrupt control, providing the strongest level of critical section protection. It disables preemption first, then interrupts, and restores them in reverse order.
Property | Value |
---|---|
State Type | usize(saved IRQ flags) |
Target Availability | target_os = "none"only |
IRQ Control | Saves and disables, then restores |
Preemption Control | Disables, then re-enables |
The ordering ensures proper nesting: preemption is disabled before IRQs and re-enabled after IRQs are restored:
// Ordered disable/enable from src/lib.rs:163-179
impl BaseGuard for NoPreemptIrqSave {
type State = usize;
fn acquire() -> Self::State {
// disable preempt first
#[cfg(feature = "preempt")]
crate_interface::call_interface!(KernelGuardIf::disable_preempt);
// then disable IRQs
super::arch::local_irq_save_and_disable()
}
fn release(state: Self::State) {
// restore IRQs first
super::arch::local_irq_restore(state);
// then enable preempt
#[cfg(feature = "preempt")]
crate_interface::call_interface!(KernelGuardIf::enable_preempt);
}
}
Sources: src/lib.rs(L100) src/lib.rs(L108 - L109) src/lib.rs(L163 - L179) src/lib.rs(L220 - L237)
Conditional Compilation Strategy
The guards use cfg_if
macros to provide different implementations based on the target platform. This ensures that the crate can be used in both kernel and userspace contexts.
Target-Based Implementation Selection
Sources: src/lib.rs(L83 - L111) src/lib.rs(L130 - L238)
Usage Patterns
All guards follow the same basic usage pattern, differing only in their protection scope:
Guard Type | Use Case | Dependencies |
---|---|---|
NoOp | Testing, userspace | None |
IrqSave | Interrupt-safe critical sections | Architecture support |
NoPreempt | Preemption-safe critical sections | preemptfeature +KernelGuardIfimpl |
NoPreemptIrqSave | Maximum protection critical sections | Both above dependencies |
The RAII pattern ensures automatic cleanup:
// Pattern from src/lib.rs:45-51
let guard = GuardType::new();
// Critical section automatically begins here
// ... critical section code ...
// Critical section automatically ends when guard is dropped
Sources: src/lib.rs(L12 - L19) src/lib.rs(L45 - L51) src/lib.rs(L181 - L237)
Trait System
Relevant source files
This document explains the trait-based architecture that forms the foundation of the kernel_guard crate. The trait system defines the interfaces for implementing critical section guards and provides extension points for user code to integrate preemption control. For information about specific guard implementations, see RAII Guards. For details on architecture-specific implementations, see Multi-Architecture Support.
Core Trait Architecture
The kernel_guard crate is built around two primary traits that define the interface contracts for guard behavior and user integration.
BaseGuard Trait
The BaseGuard
trait serves as the foundational interface that all guard types must implement. It defines the basic lifecycle operations for entering and exiting critical sections using the RAII pattern.
flowchart TD BG["BaseGuard trait"] ST["Associated Type: State"] ACQ["acquire() -> State"] REL["release(state: State)"] CLONE["Clone + Copy bounds"] BEFORE["Execute before critical section"] AFTER["Execute after critical section"] IMPL1["NoOp"] IMPL2["IrqSave"] IMPL3["NoPreempt"] IMPL4["NoPreemptIrqSave"] ACQ --> BEFORE BG --> ACQ BG --> REL BG --> ST IMPL1 --> BG IMPL2 --> BG IMPL3 --> BG IMPL4 --> BG REL --> AFTER ST --> CLONE
BaseGuard Trait Definition
The trait defines three key components at src/lib.rs(L68 - L78) :
Component | Purpose | Requirements |
---|---|---|
Stateassociated type | Stores state information during critical section | Must implementClone + Copy |
acquire()method | Performs setup operations before critical section | ReturnsStatevalue |
release(state)method | Performs cleanup operations after critical section | Consumes savedState |
Sources: src/lib.rs(L68 - L78)
KernelGuardIf Trait
The KernelGuardIf
trait provides an extension point for user code to implement preemption control functionality. This trait uses the crate_interface
mechanism to enable runtime dispatch to user-provided implementations.
flowchart TD DEF["KernelGuardIf trait definition"] ENABLE["enable_preempt()"] DISABLE["disable_preempt()"] IMPL["User Implementation"] CRATE_IMPL["#[crate_interface::impl_interface]"] RUNTIME["Runtime dispatch"] CALL1["NoPreempt::acquire()"] DISPATCH1["crate_interface::call_interface!"] CALL2["NoPreempt::release()"] DISPATCH2["crate_interface::call_interface!"] CALL1 --> DISPATCH1 CALL2 --> DISPATCH2 CRATE_IMPL --> RUNTIME DEF --> DISABLE DEF --> ENABLE DISPATCH1 --> RUNTIME DISPATCH2 --> RUNTIME IMPL --> CRATE_IMPL RUNTIME --> IMPL
Interface Definition
The trait is defined at src/lib.rs(L59 - L66) with two required methods:
enable_preempt()
: Called when preemption should be re-enableddisable_preempt()
: Called when preemption should be disabled
Sources: src/lib.rs(L59 - L66)
Runtime Dispatch Mechanism
The crate uses the crate_interface
library to implement a stable interface between the kernel_guard crate and user code. This mechanism allows the library to call user-provided functions at runtime without compile-time dependencies.
crate_interface Integration
Interface Declaration
The trait is declared as an interface using #[crate_interface::def_interface]
at src/lib.rs(L59) which generates the necessary infrastructure for runtime dispatch.
User Implementation Pattern
Users must implement the trait using the #[crate_interface::impl_interface]
attribute, as shown in the example at src/lib.rs(L35 - L43) :
#![allow(unused)] fn main() { #[crate_interface::impl_interface] impl KernelGuardIf for KernelGuardIfImpl { fn enable_preempt() { /* implementation */ } fn disable_preempt() { /* implementation */ } } }
Runtime Calls
The library invokes user implementations using crate_interface::call_interface!
macro at several locations:
- src/lib.rs(L154) - Disable preemption in
NoPreempt::acquire()
- src/lib.rs(L159) - Enable preemption in
NoPreempt::release()
- src/lib.rs(L168) - Disable preemption in
NoPreemptIrqSave::acquire()
- src/lib.rs(L177) - Enable preemption in
NoPreemptIrqSave::release()
Sources: src/lib.rs(L59) src/lib.rs(L35 - L43) src/lib.rs(L154) src/lib.rs(L159) src/lib.rs(L168) src/lib.rs(L177)
Trait Implementation Patterns
Feature-Conditional Implementation
The KernelGuardIf
calls are conditionally compiled based on the preempt
feature flag. When the feature is disabled, the calls become no-ops:
Conditional Compilation Points:
- src/lib.rs(L153 - L154) -
disable_preempt
inNoPreempt::acquire()
- src/lib.rs(L158 - L159) -
enable_preempt
inNoPreempt::release()
- src/lib.rs(L167 - L168) -
disable_preempt
inNoPreemptIrqSave::acquire()
- src/lib.rs(L176 - L177) -
enable_preempt
inNoPreemptIrqSave::release()
Sources: src/lib.rs(L153 - L154) src/lib.rs(L158 - L159) src/lib.rs(L167 - L168) src/lib.rs(L176 - L177)
State Management Patterns
Different guard types use different State
type strategies:
Guard Type | State Type | Purpose |
---|---|---|
NoOp | () | No state needed |
IrqSave | usize | Stores IRQ flags for restoration |
NoPreempt | () | Preemption state managed externally |
NoPreemptIrqSave | usize | Stores IRQ flags, preemption managed externally |
Sources: src/lib.rs(L114) src/lib.rs(L135) src/lib.rs(L150) src/lib.rs(L164)
Complete Trait Interaction Flow
The following diagram shows how the traits interact during a complete guard lifecycle:
sequenceDiagram participant UserCode as "User Code" participant GuardInstance as "Guard Instance" participant BaseGuardacquire as "BaseGuard::acquire()" participant archlocal_irq_ as "arch::local_irq_*" participant KernelGuardIf as "KernelGuardIf" participant UserImplementation as "User Implementation" UserCode ->> GuardInstance: new() GuardInstance ->> BaseGuardacquire: acquire() alt NoPreempt or NoPreemptIrqSave BaseGuardacquire ->> KernelGuardIf: crate_interface::call_interface! KernelGuardIf ->> UserImplementation: disable_preempt() UserImplementation -->> KernelGuardIf: KernelGuardIf -->> BaseGuardacquire: end alt IrqSave or NoPreemptIrqSave BaseGuardacquire ->> archlocal_irq_: local_irq_save_and_disable() archlocal_irq_ -->> BaseGuardacquire: flags: usize end BaseGuardacquire -->> GuardInstance: State GuardInstance -->> UserCode: Guard instance Note over UserCode: Critical Section Execution UserCode ->> GuardInstance: drop() GuardInstance ->> BaseGuardacquire: release(state) alt IrqSave or NoPreemptIrqSave BaseGuardacquire ->> archlocal_irq_: local_irq_restore(flags) archlocal_irq_ -->> BaseGuardacquire: end alt NoPreempt or NoPreemptIrqSave BaseGuardacquire ->> KernelGuardIf: crate_interface::call_interface! KernelGuardIf ->> UserImplementation: enable_preempt() UserImplementation -->> KernelGuardIf: KernelGuardIf -->> BaseGuardacquire: end BaseGuardacquire -->> GuardInstance: GuardInstance -->> UserCode:
Sources: src/lib.rs(L183 - L185) src/lib.rs(L202 - L205) src/lib.rs(L222 - L224) src/lib.rs(L134 - L147) src/lib.rs(L149 - L161) src/lib.rs(L163 - L179)
Multi-Architecture Support
Relevant source files
This document covers how the kernel_guard
crate provides cross-platform support for multiple CPU architectures through conditional compilation and architecture-specific implementations. It explains the architecture selection mechanism, supported platforms, and how platform-specific interrupt control is abstracted behind a unified interface.
For detailed implementation specifics of each architecture, see Architecture Abstraction Layer, x86/x86_64 Implementation, RISC-V Implementation, AArch64 Implementation, and LoongArch64 Implementation. For information about how these implementations integrate with the core guard system, see Core Architecture.
Architecture Selection Mechanism
The kernel_guard
crate uses the cfg_if
macro to conditionally compile architecture-specific code based on the target platform. The selection logic is centralized in the architecture module, which determines which platform-specific implementation to include at compile time.
flowchart TD CfgIf["cfg_if::cfg_if! macro"] X86Check["target_arch = 'x86' or 'x86_64'"] RiscvCheck["target_arch = 'riscv32' or 'riscv64'"] ArmCheck["target_arch = 'aarch64'"] LoongCheck["target_arch = 'loongarch64'"] X86Mod["mod x86"] RiscvMod["mod riscv"] ArmMod["mod aarch64"] LoongMod["mod loongarch64"] X86Use["pub use self::x86::*"] RiscvUse["pub use self::riscv::*"] ArmUse["pub use self::aarch64::*"] LoongUse["pub use self::loongarch64::*"] UnifiedApi["Unified arch:: interface"] ArmCheck --> ArmMod ArmMod --> ArmUse ArmUse --> UnifiedApi CfgIf --> ArmCheck CfgIf --> LoongCheck CfgIf --> RiscvCheck CfgIf --> X86Check LoongCheck --> LoongMod LoongMod --> LoongUse LoongUse --> UnifiedApi RiscvCheck --> RiscvMod RiscvMod --> RiscvUse RiscvUse --> UnifiedApi X86Check --> X86Mod X86Mod --> X86Use X86Use --> UnifiedApi
The conditional compilation ensures that only the relevant architecture-specific code is included in the final binary, reducing both compile time and binary size. Each architecture module provides the same interface functions but with platform-specific implementations.
Sources: src/arch/mod.rs(L3 - L17)
Supported Architectures
The crate currently supports four major CPU architecture families, each with specific register and instruction handling for interrupt control:
Architecture | Target Identifiers | Key Registers | Instruction Set |
---|---|---|---|
x86/x86_64 | x86,x86_64 | EFLAGS | cli,sti |
RISC-V | riscv32,riscv64 | sstatusCSR | csrrc,csrrs |
AArch64 | aarch64 | DAIF | mrs,msr |
LoongArch64 | loongarch64 | CSR | csrxchg |
Each architecture implementation provides the same core functions: local_irq_save
, local_irq_restore
, local_irq_enable
, and local_irq_disable
. The specific mechanism for manipulating interrupt state varies by platform but the interface remains consistent.
Sources: src/arch/mod.rs(L4 - L16)
Conditional Compilation Strategy
The architecture abstraction uses a hierarchical conditional compilation strategy that first selects the appropriate architecture module, then re-exports its symbols to create a unified interface:
flowchart TD CompileTime["Compile Time"] TargetArch["target_arch evaluation"] ModSelection["Module Selection"] X86Path["x86.rs module"] RiscvPath["riscv.rs module"] Aarch64Path["aarch64.rs module"] LoongPath["loongarch64.rs module"] ReExport["pub use self::arch::*"] GuardImpl["Guard implementations access arch functions"] IrqSave["IrqSave::acquire() calls arch::local_irq_save()"] NoPreemptIrqSave["NoPreemptIrqSave::acquire() calls arch functions"] Aarch64Path --> ReExport CompileTime --> TargetArch GuardImpl --> IrqSave GuardImpl --> NoPreemptIrqSave LoongPath --> ReExport ModSelection --> Aarch64Path ModSelection --> LoongPath ModSelection --> RiscvPath ModSelection --> X86Path ReExport --> GuardImpl RiscvPath --> ReExport TargetArch --> ModSelection X86Path --> ReExport
This design allows the core guard implementations to remain architecture-agnostic while delegating platform-specific operations to the appropriate architecture module. The cfg_if
macro ensures clean compilation without unused code warnings on platforms where certain modules are not applicable.
Sources: src/arch/mod.rs(L1 - L17) Cargo.toml(L19)
Dead Code Handling
The architecture module includes a conditional compilation attribute to handle scenarios where architecture-specific code might not be used on certain targets:
#![cfg_attr(not(target_os = "none"), allow(dead_code, unused_imports))]
This attribute prevents compiler warnings when building for non-bare-metal targets (where target_os != "none"
), as some architecture-specific functionality may not be utilized in hosted environments where the operating system handles interrupt management.
Sources: src/arch/mod.rs(L1)
Integration Points
The multi-architecture support integrates with the broader kernel_guard
system through several key mechanisms:
- Guard Implementations: The
IrqSave
andNoPreemptIrqSave
guards call architecture-specific functions through the unifiedarch::
interface - Feature Gates: The
cfg_if
dependency enables the conditional compilation logic - Target OS Detection: The crate can detect bare-metal vs. hosted environments and adjust behavior accordingly
- Interface Consistency: All architecture modules provide the same function signatures, ensuring guard implementations work across platforms
This architecture enables the same guard code to work across multiple CPU architectures while maintaining platform-specific optimizations for interrupt control.
Sources: src/arch/mod.rs(L3 - L17) Cargo.toml(L19)
Architecture Abstraction Layer
Relevant source files
Purpose and Scope
The Architecture Abstraction Layer provides a unified interface for interrupt control across multiple CPU architectures through conditional compilation. This module serves as the entry point that selects and re-exports the appropriate architecture-specific implementation based on the target compilation architecture.
For details on specific architecture implementations, see x86/x86_64 Implementation, RISC-V Implementation, AArch64 Implementation, and LoongArch64 Implementation. For information about how these implementations are used by the core guard system, see RAII Guards.
Conditional Compilation Strategy
The architecture abstraction layer uses the cfg_if
macro to perform compile-time architecture selection. This approach ensures that only the relevant code for the target architecture is included in the final binary, reducing both code size and complexity.
Architecture Selection Flow
flowchart TD START["Compilation Start"] CFGIF["cfg_if::cfg_if! macro"] X86_CHECK["target_arch = x86 or x86_64?"] RISCV_CHECK["target_arch = riscv32 or riscv64?"] ARM_CHECK["target_arch = aarch64?"] LOONG_CHECK["target_arch = loongarch64?"] X86_MOD["mod x86"] X86_USE["pub use self::x86::*"] RISCV_MOD["mod riscv"] RISCV_USE["pub use self::riscv::*"] ARM_MOD["mod aarch64"] ARM_USE["pub use self::aarch64::*"] LOONG_MOD["mod loongarch64"] LOONG_USE["pub use self::loongarch64::*"] UNIFIED["Unified arch:: namespace"] GUARDS["Used by guard implementations"] ARM_CHECK --> ARM_MOD ARM_MOD --> ARM_USE ARM_USE --> UNIFIED CFGIF --> ARM_CHECK CFGIF --> LOONG_CHECK CFGIF --> RISCV_CHECK CFGIF --> X86_CHECK LOONG_CHECK --> LOONG_MOD LOONG_MOD --> LOONG_USE LOONG_USE --> UNIFIED RISCV_CHECK --> RISCV_MOD RISCV_MOD --> RISCV_USE RISCV_USE --> UNIFIED START --> CFGIF UNIFIED --> GUARDS X86_CHECK --> X86_MOD X86_MOD --> X86_USE X86_USE --> UNIFIED
Sources: src/arch/mod.rs(L3 - L17)
Module Structure and Re-exports
The abstraction layer follows a simple but effective pattern where each supported architecture has its own module that implements the same interface. The main module conditionally includes and re-exports the appropriate implementation:
Target Architecture | Condition | Module | Re-export |
---|---|---|---|
x86 32-bit | target_arch = "x86" | mod x86 | pub use self::x86::* |
x86 64-bit | target_arch = "x86_64" | mod x86 | pub use self::x86::* |
RISC-V 32-bit | target_arch = "riscv32" | mod riscv | pub use self::riscv::* |
RISC-V 64-bit | target_arch = "riscv64" | mod riscv | pub use self::riscv::* |
AArch64 | target_arch = "aarch64" | mod aarch64 | pub use self::aarch64::* |
LoongArch64 | target_arch = "loongarch64" | mod loongarch64 | pub use self::loongarch64::* |
Sources: src/arch/mod.rs(L4 - L16)
Code Entity Mapping
flowchart TD subgraph FUNCTIONS["Exported functions"] LOCAL_IRQ_SAVE["local_irq_save()"] LOCAL_IRQ_RESTORE["local_irq_restore()"] end subgraph ARCH_MODULES["Architecture-specific modules"] X86_RS["src/arch/x86.rs"] RISCV_RS["src/arch/riscv.rs"] ARM_RS["src/arch/aarch64.rs"] LOONG_RS["src/arch/loongarch64.rs"] end subgraph MOD_RS["src/arch/mod.rs"] CFGIF_MACRO["cfg_if::cfg_if!"] X86_BRANCH["x86/x86_64 branch"] RISCV_BRANCH["riscv32/riscv64 branch"] ARM_BRANCH["aarch64 branch"] LOONG_BRANCH["loongarch64 branch"] end ARM_BRANCH --> ARM_RS ARM_RS --> LOCAL_IRQ_RESTORE ARM_RS --> LOCAL_IRQ_SAVE CFGIF_MACRO --> ARM_BRANCH CFGIF_MACRO --> LOONG_BRANCH CFGIF_MACRO --> RISCV_BRANCH CFGIF_MACRO --> X86_BRANCH LOONG_BRANCH --> LOONG_RS LOONG_RS --> LOCAL_IRQ_RESTORE LOONG_RS --> LOCAL_IRQ_SAVE RISCV_BRANCH --> RISCV_RS RISCV_RS --> LOCAL_IRQ_RESTORE RISCV_RS --> LOCAL_IRQ_SAVE X86_BRANCH --> X86_RS X86_RS --> LOCAL_IRQ_RESTORE X86_RS --> LOCAL_IRQ_SAVE
Sources: src/arch/mod.rs(L1 - L18)
Compiler Attributes and Target Handling
The module includes a conditional compiler attribute that suppresses warnings for unused code when targeting non-bare-metal environments:
#![cfg_attr(not(target_os = "none"), allow(dead_code, unused_imports))]
This attribute serves an important purpose:
- When
target_os = "none"
(bare metal/no_std environments), the architecture-specific code is actively used - When targeting other operating systems (like Linux), the interrupt control functions may not be used by the guard implementations, leading to dead code warnings
- The attribute prevents these warnings without removing the code, maintaining consistency across build targets
Sources: src/arch/mod.rs(L1)
Integration with Core Library
The architecture abstraction layer provides the low-level interrupt control functions that are consumed by the guard implementations in the core library. The local_irq_save()
and local_irq_restore()
functions exposed through this layer are called by:
IrqSave
guard for interrupt-only protectionNoPreemptIrqSave
guard for combined interrupt and preemption protection
This design ensures that the same high-level guard interface can work across all supported architectures while utilizing the most efficient interrupt control mechanism available on each platform.
Sources: src/arch/mod.rs(L3 - L17)
x86/x86_64 Implementation
Relevant source files
Purpose and Scope
This document covers the x86 and x86_64 architecture-specific implementation of interrupt control within the kernel_guard crate. The x86 implementation provides low-level primitives for disabling and restoring local interrupts by manipulating the EFLAGS register using inline assembly.
This implementation is automatically selected when compiling for target_arch = "x86"
or target_arch = "x86_64"
through the conditional compilation system. For information about other architecture implementations, see RISC-V Implementation, AArch64 Implementation, and LoongArch64 Implementation. For details about the architecture selection mechanism, see Architecture Abstraction Layer.
x86 Interrupt Control Mechanism
The x86 architecture provides interrupt control through the Interrupt Flag (IF) bit in the EFLAGS register. When this bit is set, the processor responds to maskable hardware interrupts; when cleared, such interrupts are ignored.
x86 Architecture Integration
flowchart TD CFGIF["cfg_if! macro evaluation"] X86_CHECK["target_arch == x86 or x86_64?"] X86_MOD["mod x86"] OTHER_ARCH["Other architecture modules"] REEXPORT["pub use self::arch::*"] LOCAL_IRQ_SAVE["local_irq_save_and_disable()"] LOCAL_IRQ_RESTORE["local_irq_restore()"] IRQSAVE_GUARD["IrqSave guard"] NOPREEMPT_IRQ_GUARD["NoPreemptIrqSave guard"] CFGIF --> X86_CHECK LOCAL_IRQ_RESTORE --> IRQSAVE_GUARD LOCAL_IRQ_RESTORE --> NOPREEMPT_IRQ_GUARD LOCAL_IRQ_SAVE --> IRQSAVE_GUARD LOCAL_IRQ_SAVE --> NOPREEMPT_IRQ_GUARD OTHER_ARCH --> REEXPORT REEXPORT --> LOCAL_IRQ_RESTORE REEXPORT --> LOCAL_IRQ_SAVE X86_CHECK --> OTHER_ARCH X86_CHECK --> X86_MOD X86_MOD --> REEXPORT
Sources: src/arch/x86.rs(L1 - L21)
Implementation Details
The x86 implementation consists of two core functions that manipulate the EFLAGS register through inline assembly.
Interrupt Flag Constants
The implementation defines the Interrupt Flag bit position as a constant:
Constant | Value | Purpose |
---|---|---|
IF_BIT | 1 << 9 | Bit mask for the Interrupt Flag in EFLAGS register |
Core Functions
local_irq_save_and_disable()
This function atomically saves the current interrupt state and disables interrupts. It returns the previous state of the Interrupt Flag for later restoration.
Implementation Strategy:
- Uses
pushf
instruction to push EFLAGS onto the stack - Uses
pop
instruction to retrieve EFLAGS value into a register - Uses
cli
instruction to clear the Interrupt Flag - Returns only the IF bit status (masked with
IF_BIT
)
local_irq_restore(flags)
This function restores the interrupt state based on the previously saved flags value.
Implementation Strategy:
- Checks if the saved flags indicate interrupts were enabled
- Uses
sti
instruction to set the Interrupt Flag if interrupts should be enabled - Uses
cli
instruction to clear the Interrupt Flag if interrupts should remain disabled
Interrupt Control Flow
flowchart TD ACQUIRE["Guard Acquire"] SAVE_DISABLE["local_irq_save_and_disable()"] PUSHF["pushf (push EFLAGS)"] POP["pop reg (get EFLAGS value)"] CLI["cli (disable interrupts)"] MASK["flags & IF_BIT"] RETURN_FLAGS["return saved_flags"] CRITICAL["Critical Section Execution"] RELEASE["Guard Release"] RESTORE["local_irq_restore(saved_flags)"] CHECK_FLAGS["saved_flags != 0?"] STI["sti (enable interrupts)"] CLI_RESTORE["cli (keep disabled)"] COMPLETE["Interrupts Restored"] ACQUIRE --> SAVE_DISABLE CHECK_FLAGS --> CLI_RESTORE CHECK_FLAGS --> STI CLI --> MASK CLI_RESTORE --> COMPLETE CRITICAL --> RELEASE MASK --> RETURN_FLAGS POP --> CLI PUSHF --> POP RELEASE --> RESTORE RESTORE --> CHECK_FLAGS RETURN_FLAGS --> CRITICAL SAVE_DISABLE --> PUSHF STI --> COMPLETE
Sources: src/arch/x86.rs(L7 - L20)
Assembly Instructions Reference
The x86 implementation uses specific assembly instructions for interrupt control:
Instruction | Purpose | Usage in Implementation |
---|---|---|
pushf | Push EFLAGS register onto stack | Save current interrupt state |
pop | Pop value from stack into register | Retrieve EFLAGS value |
cli | Clear Interrupt Flag | Disable maskable interrupts |
sti | Set Interrupt Flag | Enable maskable interrupts |
EFLAGS Register Structure
The implementation specifically targets bit 9 of the EFLAGS register:
flowchart TD EFLAGS["EFLAGS Register (32/64-bit)"] BIT9["Bit 9: Interrupt Flag (IF)"] ENABLED["1 = Interrupts Enabled"] DISABLED["0 = Interrupts Disabled"] IF_BIT_CONST["IF_BIT = 1 << 9"] MASK_OP["flags & IF_BIT"] IF_STATE["Extract IF state"] BIT9 --> DISABLED BIT9 --> ENABLED EFLAGS --> BIT9 IF_BIT_CONST --> BIT9 MASK_OP --> IF_STATE
Sources: src/arch/x86.rs(L3 - L4) src/arch/x86.rs(L10)
Integration with Guard System
The x86 interrupt control functions integrate directly with the RAII guard implementations. When guards are created in bare-metal environments (target_os = "none"
), they call these architecture-specific functions to provide actual interrupt control functionality.
Function Usage by Guards:
IrqSave
guard callslocal_irq_save_and_disable()
on creation andlocal_irq_restore()
on dropNoPreemptIrqSave
guard uses the same interrupt control functions in addition to preemption control
The inline assembly ensures minimal overhead and atomic operation for critical section protection in kernel environments.
Sources: src/arch/x86.rs(L1 - L21)
RISC-V Implementation
Relevant source files
This document covers the RISC-V-specific implementation of interrupt control functionality within the kernel_guard crate. The RISC-V implementation provides low-level interrupt disable/restore operations by manipulating the sstatus
Control and Status Register (CSR), specifically the Supervisor Interrupt Enable (SIE) bit.
For the overall architecture abstraction mechanism, see Architecture Abstraction Layer. For other architecture implementations, see x86/x86_64 Implementation, AArch64 Implementation, and LoongArch64 Implementation.
RISC-V CSR Architecture Context
The RISC-V implementation operates within the Supervisor mode privilege level, using the sstatus
CSR to control interrupt delivery. The implementation focuses on atomic manipulation of the SIE bit to provide safe critical section entry and exit.
RISC-V Architecture Selection Flow
flowchart TD cfg_if["cfg_if! macro evaluation"] riscv_check["target_arch = riscv32 or riscv64?"] riscv_mod["mod riscv;"] other_arch["Other architecture modules"] pub_use["pub use self::riscv::*;"] local_irq_save["local_irq_save_and_disable()"] local_irq_restore["local_irq_restore()"] guard_acquire["BaseGuard::acquire()"] guard_release["BaseGuard::release()"] irq_save_guard["IrqSave guard"] no_preempt_irq_save["NoPreemptIrqSave guard"] cfg_if --> riscv_check guard_acquire --> irq_save_guard guard_acquire --> no_preempt_irq_save guard_release --> irq_save_guard guard_release --> no_preempt_irq_save local_irq_restore --> guard_release local_irq_save --> guard_acquire pub_use --> local_irq_restore pub_use --> local_irq_save riscv_check --> other_arch riscv_check --> riscv_mod riscv_mod --> pub_use
Sources: src/arch/mod.rs(L1 - L50) src/arch/riscv.rs(L1 - L19)
CSR Manipulation Implementation
The RISC-V implementation consists of two core functions that provide atomic interrupt state management through direct CSR manipulation.
Core Function Mapping
flowchart TD subgraph subGraph2["Hardware Registers"] sstatus_reg["sstatus CSR"] sie_bit["SIE bit (bit 1)"] end subgraph subGraph1["RISC-V Assembly"] csrrc_instr["csrrc instruction"] csrrs_instr["csrrs instruction"] end subgraph subGraph0["Code Functions"] save_disable["local_irq_save_and_disable()"] restore["local_irq_restore(flags)"] end csrrc_instr --> sstatus_reg csrrs_instr --> sstatus_reg restore --> csrrs_instr save_disable --> csrrc_instr sstatus_reg --> sie_bit
Sources: src/arch/riscv.rs(L6 - L18)
Assembly Instruction Analysis
The implementation uses RISC-V CSR atomic read-modify-write instructions to ensure interrupt state changes are atomic and cannot be interrupted.
CSR Instruction Details
Function | Assembly Instruction | Operation | Purpose |
---|---|---|---|
local_irq_save_and_disable() | csrrc {}, sstatus, {} | Clear bits and read old value | Atomically disable interrupts and save state |
local_irq_restore() | csrrs x0, sstatus, {} | Set bits, discard result | Atomically restore interrupt state |
The SIE_BIT
constant is defined as 1 << 1
, targeting bit 1 of the sstatus
register which controls Supervisor Interrupt Enable.
CSR Operation Flow
sequenceDiagram participant IrqSaveGuard as "IrqSave Guard" participant local_irq_save_and_disable as "local_irq_save_and_disable()" participant sstatusCSR as "sstatus CSR" participant local_irq_restore as "local_irq_restore()" IrqSaveGuard ->> local_irq_save_and_disable: acquire() local_irq_save_and_disable ->> sstatusCSR: csrrc flags, sstatus, SIE_BIT sstatusCSR -->> local_irq_save_and_disable: return old sstatus value local_irq_save_and_disable -->> IrqSaveGuard: return (flags & SIE_BIT) Note over IrqSaveGuard: Critical section execution IrqSaveGuard ->> local_irq_restore: release(flags) local_irq_restore ->> sstatusCSR: csrrs x0, sstatus, flags sstatusCSR -->> local_irq_restore: (discard result)
Sources: src/arch/riscv.rs(L7 - L11) src/arch/riscv.rs(L15 - L17)
Implementation Details
Interrupt Save and Disable
The local_irq_save_and_disable()
function src/arch/riscv.rs(L7 - L12) performs an atomic clear-and-read operation:
- Uses inline assembly with
csrrc
(CSR Read and Clear bits) - Clears the
SIE_BIT
in thesstatus
register - Returns only the relevant bit state (
flags & SIE_BIT
) rather than the entire register - Ensures the returned value is either 0 (interrupts were disabled) or
SIE_BIT
(interrupts were enabled)
Interrupt Restore
The local_irq_restore()
function src/arch/riscv.rs(L15 - L18) performs an atomic set operation:
- Uses inline assembly with
csrrs
(CSR Read and Set bits) - Sets bits specified by the
flags
parameter in thesstatus
register - Uses
x0
as the destination register to discard the read result - Only modifies the interrupt state without returning any value
Register Usage and Safety
The implementation uses specific register constraints:
out(reg)
for capturing the old CSR valuein(reg)
for providing the restore flagsconst SIE_BIT
for compile-time constant bit mask- All operations are marked
unsafe
due to direct hardware manipulation
Sources: src/arch/riscv.rs(L1 - L19)
AArch64 Implementation
Relevant source files
This document details the AArch64-specific implementation of interrupt control mechanisms within the kernel_guard crate. It covers the ARM64 architecture's DAIF register manipulation and the assembly instructions used to save and restore interrupt states for RAII guard implementations.
For information about the overall architecture abstraction system, see Architecture Abstraction Layer. For comparisons with other CPU architectures, see x86/x86_64 Implementation, RISC-V Implementation, and LoongArch64 Implementation.
DAIF Register and Interrupt Control
The AArch64 implementation centers around the DAIF (Debug, SError, IRQ, FIQ) system register, which controls various exception types. The kernel_guard crate specifically manipulates the IRQ (Interrupt Request) bit to implement critical sections.
DAIF Register Structure
flowchart TD subgraph subGraph2["Assembly Instructions"] MRS["mrs (Move from System Register)Read DAIF value"] MSR_SET["msr daifset, #2Set I bit (disable IRQs)"] MSR_RESTORE["msr daif, registerRestore full DAIF"] end subgraph subGraph1["kernel_guard Operations"] SAVE["local_irq_save_and_disable()Read DAIF + Set I bit"] RESTORE["local_irq_restore(flags)Write saved DAIF"] end subgraph subGraph0["DAIF Register (64-bit)"] D["D (bit 9)Debug exceptions"] A["A (bit 8)SError interrupts"] I["I (bit 7)IRQ interrupts"] F["F (bit 6)FIQ interrupts"] end MRS --> A MRS --> D MRS --> F MRS --> I MSR_RESTORE --> A MSR_RESTORE --> D MSR_RESTORE --> F MSR_RESTORE --> I MSR_SET --> I RESTORE --> MSR_RESTORE SAVE --> MRS SAVE --> MSR_SET
Sources: src/arch/aarch64.rs(L1 - L15)
Core Implementation Functions
The AArch64 implementation provides two primary functions that form the foundation for all interrupt-disabling guards on this architecture.
Interrupt Save and Disable
The local_irq_save_and_disable
function captures the current interrupt state and disables IRQs atomically:
Function | Return Type | Operation | Assembly Instructions |
---|---|---|---|
local_irq_save_and_disable | usize | Save DAIF, disable IRQs | mrs {}, daif; msr daifset, #2 |
The function uses two ARM64 instructions in sequence:
mrs {}, daif
- Move the DAIF register value to a general-purpose registermsr daifset, #2
- Set bit 1 (which corresponds to the IRQ mask bit) in DAIF
Interrupt Restore
The local_irq_restore
function restores the previously saved interrupt state:
Function | Parameters | Operation | Assembly Instructions |
---|---|---|---|
local_irq_restore | flags: usize | Restore DAIF | msr daif, {} |
This function directly writes the saved flags back to the DAIF register, restoring the complete interrupt state.
Sources: src/arch/aarch64.rs(L3 - L14)
Integration with Guard System
The AArch64 implementation integrates with the broader kernel_guard architecture through the conditional compilation system and provides the low-level primitives for RAII guards.
flowchart TD subgraph subGraph3["RAII Pattern"] ACQUIRE["BaseGuard::acquire()"] DROP["Drop implementation"] end subgraph subGraph2["Guard Implementations"] IRQ_GUARD["IrqSave guard"] COMBO_GUARD["NoPreemptIrqSave guard"] end subgraph subGraph1["AArch64 Module (src/arch/aarch64.rs)"] SAVE_FN["local_irq_save_and_disable()"] RESTORE_FN["local_irq_restore(flags)"] end subgraph subGraph0["Architecture Selection"] CFG_IF["cfg_if! macro"] AARCH64_CHECK["target_arch = 'aarch64'"] end AARCH64_CHECK --> RESTORE_FN AARCH64_CHECK --> SAVE_FN CFG_IF --> AARCH64_CHECK COMBO_GUARD --> ACQUIRE COMBO_GUARD --> DROP IRQ_GUARD --> ACQUIRE IRQ_GUARD --> DROP RESTORE_FN --> COMBO_GUARD RESTORE_FN --> IRQ_GUARD SAVE_FN --> COMBO_GUARD SAVE_FN --> IRQ_GUARD
Sources: src/arch/aarch64.rs(L1 - L15)
Assembly Instruction Details
The AArch64 implementation relies on specific ARM64 assembly instructions for system register manipulation:
System Register Access Instructions
Instruction | Purpose | Syntax | Usage in kernel_guard |
---|---|---|---|
mrs | Move from System Register | mrs Xt, system_reg | Read current DAIF state |
msr | Move to System Register | msr system_reg, Xt | Write to DAIF register |
msr(immediate) | Move immediate to System Register | msr daifset, #imm | Set specific DAIF bits |
Bit Manipulation Strategy
flowchart TD subgraph subGraph2["Critical Section State"] BEFORE["IRQs enabled (I=0)"] DURING["IRQs disabled (I=1)"] AFTER["IRQs restored"] end subgraph subGraph1["daifset #2 Operation"] IMM2["Immediate value #2"] SHIFT["Left shift by 6 positions"] RESULT["Sets bit 7 (IRQ mask)"] end subgraph subGraph0["DAIF Bit Positions"] BIT9["Bit 9: Debug"] BIT8["Bit 8: SError"] BIT7["Bit 7: IRQ"] BIT6["Bit 6: FIQ"] end BEFORE --> DURING BIT7 --> DURING DURING --> AFTER IMM2 --> SHIFT RESULT --> BIT7 SHIFT --> RESULT
The daifset #2
instruction specifically targets the IRQ bit by using immediate value 2, which when processed by the instruction becomes a mask for bit 7 of the DAIF register.
Sources: src/arch/aarch64.rs(L7 - L13)
Safety and Inline Optimization
Both functions are marked with #[inline]
for performance optimization and use unsafe
blocks for assembly code execution:
- Inline annotation: Ensures the assembly instructions are inlined at call sites for minimal overhead
- Unsafe blocks: Required for all inline assembly operations in Rust
- Register constraints: Uses
out(reg)
andin(reg)
to specify register allocation for the assembler
The implementation assumes that the DAIF register is accessible in the current execution context, which is typically true for kernel-level code running at EL1 (Exception Level 1) or higher privilege levels.
Sources: src/arch/aarch64.rs(L3 - L14)
LoongArch64 Implementation
Relevant source files
Purpose and Scope
This document covers the LoongArch64-specific implementation of interrupt control mechanisms within the kernel_guard crate. The LoongArch64 implementation provides platform-specific functions for saving, disabling, and restoring interrupt states using Control Status Register (CSR) manipulation.
For general architecture abstraction concepts, see Architecture Abstraction Layer. For other architecture implementations, see x86/x86_64 Implementation, RISC-V Implementation, and AArch64 Implementation.
Architecture Integration
The LoongArch64 implementation is conditionally compiled as part of the multi-architecture support system. It provides the same interface as other architectures but uses LoongArch64-specific Control Status Registers and the csrxchg
instruction for atomic CSR exchange operations.
Architecture Selection Flow
flowchart TD CFGIF["cfg_if! macro evaluation"] LOONG_CHECK["target_arch = loongarch64?"] LOONG_MOD["mod loongarch64"] OTHER_ARCH["Other architecture modules"] PUB_USE["pub use self::loongarch64::*"] LOCAL_IRQ_SAVE["local_irq_save_and_disable()"] LOCAL_IRQ_RESTORE["local_irq_restore()"] IRQ_SAVE_GUARD["IrqSave guard"] NO_PREEMPT_IRQ_SAVE["NoPreemptIrqSave guard"] CFGIF --> LOONG_CHECK IRQ_SAVE_GUARD --> NO_PREEMPT_IRQ_SAVE LOCAL_IRQ_RESTORE --> IRQ_SAVE_GUARD LOCAL_IRQ_SAVE --> IRQ_SAVE_GUARD LOONG_CHECK --> LOONG_MOD LOONG_CHECK --> OTHER_ARCH LOONG_MOD --> PUB_USE PUB_USE --> LOCAL_IRQ_RESTORE PUB_USE --> LOCAL_IRQ_SAVE
Sources: src/arch/loongarch64.rs(L1 - L18)
Interrupt Control Implementation
The LoongArch64 implementation centers around two core functions that manipulate the interrupt enable bit in Control Status Registers using the atomic csrxchg
instruction.
Core Functions
Function | Purpose | Return Value |
---|---|---|
local_irq_save_and_disable() | Save current interrupt state and disable interrupts | Previous interrupt enable state (masked) |
local_irq_restore(flags) | Restore interrupt state from saved flags | None |
CSR Manipulation Details
flowchart TD subgraph local_irq_restore["local_irq_restore"] RESTORE_START["Function entryflags parameter"] CSRXCHG_RESTORE["csrxchg {}, {}, 0x0in(reg) flagsin(reg) IE_MASK"] RESTORE_END["Function return"] end subgraph local_irq_save_and_disable["local_irq_save_and_disable"] SAVE_START["Function entry"] FLAGS_INIT["flags: usize = 0"] CSRXCHG_SAVE["csrxchg {}, {}, 0x0inout(reg) flagsin(reg) IE_MASK"] MASK_RETURN["Return flags & IE_MASK"] end subgraph subGraph0["LoongArch64 CSR Operations"] IE_MASK["IE_MASK constant1 << 2 (bit 2)"] CSRXCHG["csrxchg instructionAtomic CSR exchange"] end CSRXCHG_RESTORE --> RESTORE_END CSRXCHG_SAVE --> MASK_RETURN FLAGS_INIT --> CSRXCHG_SAVE IE_MASK --> CSRXCHG_RESTORE IE_MASK --> CSRXCHG_SAVE RESTORE_START --> CSRXCHG_RESTORE SAVE_START --> FLAGS_INIT
Sources: src/arch/loongarch64.rs(L3 - L17)
Control Status Register (CSR) Operations
The implementation uses CSR address 0x0
(likely CRMD - Current Mode) with the csrxchg
instruction for atomic read-modify-write operations on the interrupt enable bit.
Interrupt Enable Bit Manipulation
The IE_MASK
constant defines bit 2 as the interrupt enable bit:
flowchart TD subgraph subGraph1["IE_MASK Constant"] MASK_VAL["1 << 2 = 0x4"] MASK_PURPOSE["Isolates IE bitfor CSR operations"] end subgraph subGraph0["CSR Bit Layout"] BIT0["Bit 0"] BIT1["Bit 1"] BIT2["Bit 2IE (Interrupt Enable)"] BIT3["Bit 3"] BITN["Bit N"] end BIT2 --> MASK_VAL MASK_VAL --> MASK_PURPOSE
Sources: src/arch/loongarch64.rs(L3)
Assembly Instruction Details
The csrxchg
instruction performs atomic exchange operations:
- Save Operation:
csrxchg {flags}, {IE_MASK}, 0x0
clears the IE bit and returns the old CSR value - Restore Operation:
csrxchg {flags}, {IE_MASK}, 0x0
sets the IE bit based on the flags parameter
The instruction syntax uses:
inout(reg) flags
for read-modify-write of the flags variablein(reg) IE_MASK
for the bit mask input0x0
as the CSR address
Sources: src/arch/loongarch64.rs(L9 - L16)
Integration with Guard System
The LoongArch64 functions integrate seamlessly with the kernel_guard's RAII guard system through the architecture abstraction layer.
Guard Usage Flow
sequenceDiagram participant IrqSaveGuard as "IrqSave Guard" participant ArchitectureLayer as "Architecture Layer" participant LoongArch64Implementation as "LoongArch64 Implementation" participant LoongArch64CSR as "LoongArch64 CSR" Note over IrqSaveGuard: Guard creation IrqSaveGuard ->> ArchitectureLayer: BaseGuard::acquire() ArchitectureLayer ->> LoongArch64Implementation: local_irq_save_and_disable() LoongArch64Implementation ->> LoongArch64CSR: csrxchg (clear IE) LoongArch64CSR -->> LoongArch64Implementation: Previous CSR value LoongArch64Implementation -->> ArchitectureLayer: Masked IE state ArchitectureLayer -->> IrqSaveGuard: Saved flags Note over IrqSaveGuard: Critical section execution Note over IrqSaveGuard: Guard destruction IrqSaveGuard ->> ArchitectureLayer: BaseGuard::release(flags) ArchitectureLayer ->> LoongArch64Implementation: local_irq_restore(flags) LoongArch64Implementation ->> LoongArch64CSR: csrxchg (restore IE) LoongArch64CSR -->> LoongArch64Implementation: Updated CSR LoongArch64Implementation -->> ArchitectureLayer: Return ArchitectureLayer -->> IrqSaveGuard: Return
Sources: src/arch/loongarch64.rs(L6 - L17)
Integration Guide
Relevant source files
This page provides a practical guide for integrating the kernel_guard
crate into your kernel or OS project. It covers dependency setup, feature configuration, and implementing the required interfaces to enable preemption control.
For detailed information about the available guard types and their behavior, see RAII Guards. For architecture-specific implementation details, see Multi-Architecture Support.
Integration Overview
The kernel_guard
crate integration follows a two-phase approach: dependency configuration and interface implementation. The crate uses conditional compilation to provide different functionality levels based on target platform and enabled features.
Integration Flow Diagram
flowchart TD START["Start Integration"] CARGO["Add kernel_guard to Cargo.toml"] FEATURE_CHOICE["Enable preempt feature?"] IMPL_TRAIT["Implement KernelGuardIf trait"] USE_GUARDS["Use IRQ-only guards"] CRATE_INTERFACE["Use crate_interface::impl_interface"] DEFINE_IMPL["Define enable_preempt() and disable_preempt()"] FULL_GUARDS["Access all guard types"] LIMITED_GUARDS["Access IrqSave and NoOp guards"] USAGE["Use guards in critical sections"] RUNTIME["Runtime protection active"] CARGO --> FEATURE_CHOICE CRATE_INTERFACE --> DEFINE_IMPL DEFINE_IMPL --> FULL_GUARDS FEATURE_CHOICE --> IMPL_TRAIT FEATURE_CHOICE --> USE_GUARDS FULL_GUARDS --> USAGE IMPL_TRAIT --> CRATE_INTERFACE LIMITED_GUARDS --> USAGE START --> CARGO USAGE --> RUNTIME USE_GUARDS --> LIMITED_GUARDS
Sources: Cargo.toml(L14 - L16) src/lib.rs(L58 - L66) src/lib.rs(L83 - L111)
Dependency Configuration
Add kernel_guard
to your Cargo.toml
dependencies section:
[dependencies]
kernel_guard = "0.1.2"
For preemptive systems that need both IRQ and preemption control:
[dependencies]
kernel_guard = { version = "0.1.2", features = ["preempt"] }
Supported Dependencies
Dependency | Version | Purpose |
---|---|---|
cfg-if | 1.0 | Conditional compilation for architecture selection |
crate_interface | 0.1 | Runtime interface dispatch forKernelGuardIf |
Sources: Cargo.toml(L18 - L20)
Interface Implementation Requirements
When the preempt
feature is enabled, you must implement the KernelGuardIf
trait to provide preemption control functionality.
KernelGuardIf Implementation Pattern
flowchart TD subgraph subGraph2["Runtime Dispatch"] NOPREEMPT_ACQUIRE["NoPreempt::acquire()"] NOPREEMPT_RELEASE["NoPreempt::release()"] COMBINED_ACQUIRE["NoPreemptIrqSave::acquire()"] COMBINED_RELEASE["NoPreemptIrqSave::release()"] end subgraph subGraph1["kernel_guard Crate"] TRAIT_DEF["#[crate_interface::def_interface] trait KernelGuardIf"] CALL_SITES["crate_interface::call_interface! call sites"] end subgraph subGraph0["User Crate"] USER_STRUCT["struct KernelGuardIfImpl"] IMPL_MACRO["#[crate_interface::impl_interface]"] ENABLE_FN["fn enable_preempt()"] DISABLE_FN["fn disable_preempt()"] end CALL_SITES --> COMBINED_ACQUIRE CALL_SITES --> COMBINED_RELEASE CALL_SITES --> NOPREEMPT_ACQUIRE CALL_SITES --> NOPREEMPT_RELEASE DISABLE_FN --> CALL_SITES ENABLE_FN --> CALL_SITES IMPL_MACRO --> DISABLE_FN IMPL_MACRO --> ENABLE_FN TRAIT_DEF --> CALL_SITES USER_STRUCT --> IMPL_MACRO
Sources: src/lib.rs(L59 - L66) src/lib.rs(L153 - L159) src/lib.rs(L167 - L177)
Implementation Template
The following template shows the required implementation structure:
#![allow(unused)] fn main() { use kernel_guard::KernelGuardIf; struct KernelGuardIfImpl; #[crate_interface::impl_interface] impl KernelGuardIf for KernelGuardIfImpl { fn enable_preempt() { // Platform-specific preemption enable code } fn disable_preempt() { // Platform-specific preemption disable code } } }
Sources: src/lib.rs(L30 - L43) README.md(L36 - L49)
Target Platform Considerations
The crate behavior varies based on the target platform:
Platform-Specific Guard Availability
Target | IrqSave | NoPreempt | NoPreemptIrqSave | Notes |
---|---|---|---|---|
target_os = "none" | ✅ Full | ✅ Full* | ✅ Full* | Real implementations |
Other targets | ❌ NoOp alias | ❌ NoOp alias | ❌ NoOp alias | User-mode safety |
*Requires preempt
feature and KernelGuardIf
implementation
Sources: src/lib.rs(L83 - L111)
Conditional Compilation Logic
flowchart TD COMPILE_START["Compilation begins"] TARGET_CHECK["target_os == 'none' || doc?"] REAL_IMPL["Real guard implementations"] ALIAS_IMPL["NoOp type aliases"] PREEMPT_CHECK["preempt feature enabled?"] FULL_PREEMPT["Full preemption control"] IRQ_ONLY["IRQ control only"] NOOP_GUARDS["All guards = NoOp"] CALL_INTERFACE["crate_interface::call_interface!"] ARCH_IRQ["arch::local_irq_* functions"] USER_IMPL["User KernelGuardIf implementation"] PLATFORM_CODE["Platform-specific IRQ code"] ALIAS_IMPL --> NOOP_GUARDS ARCH_IRQ --> PLATFORM_CODE CALL_INTERFACE --> USER_IMPL COMPILE_START --> TARGET_CHECK FULL_PREEMPT --> CALL_INTERFACE IRQ_ONLY --> ARCH_IRQ PREEMPT_CHECK --> FULL_PREEMPT PREEMPT_CHECK --> IRQ_ONLY REAL_IMPL --> PREEMPT_CHECK TARGET_CHECK --> ALIAS_IMPL TARGET_CHECK --> REAL_IMPL
Sources: src/lib.rs(L83 - L111) src/lib.rs(L130 - L238)
Usage Patterns
Basic Guard Usage
All guards follow the RAII pattern where the critical section begins at guard creation and ends when the guard is dropped:
// IRQ protection only
let _guard = IrqSave::new();
// Critical section protected from interrupts
// Preemption protection (requires preempt feature + KernelGuardIf)
let _guard = NoPreempt::new();
// Critical section protected from preemption
// Combined protection
let _guard = NoPreemptIrqSave::new();
// Critical section protected from both interrupts and preemption
Integration Verification
To verify your integration is working correctly:
- Compilation Test: Ensure your project compiles with the desired feature set
- Runtime Test: Verify that critical sections actually disable the expected mechanisms
- Documentation Test: Use
cargo doc
to check that all guard types are available
Sources: src/lib.rs(L181 - L237) README.md(L50 - L58)
Feature Configuration
Relevant source files
This document explains the feature-based configuration system in the kernel_guard
crate, specifically focusing on the preempt
feature and its impact on guard availability and functionality. For information about implementing the required traits when using features, see Implementing KernelGuardIf.
Overview
The kernel_guard
crate uses Cargo features to provide conditional functionality that adapts to different system requirements. The primary feature is preempt
, which enables kernel preemption control capabilities in guard implementations. This feature system allows the crate to be used in both simple interrupt-only scenarios and more complex preemptive kernel environments.
Feature Definitions
The crate defines its features in the Cargo manifest with a minimal configuration approach:
Feature | Default | Purpose |
---|---|---|
preempt | Disabled | Enables kernel preemption control in applicable guards |
default | Empty | No features enabled by default |
Sources: Cargo.toml(L14 - L16)
Preempt Feature Behavior
Feature Impact on Guard Implementations
The preempt
feature primarily affects two guard types: NoPreempt
and NoPreemptIrqSave
. When this feature is enabled, these guards will attempt to disable and re-enable kernel preemption around critical sections.
Compilation-Time Feature Resolution
flowchart TD COMPILE["Compilation Start"] FEATURE_CHECK["preempt featureenabled?"] PREEMPT_ENABLED["Preemption ControlActive"] PREEMPT_DISABLED["Preemption ControlNo-Op"] NOPRE_IMPL["NoPreempt::acquire()calls KernelGuardIf::disable_preempt"] NOPRE_REL["NoPreempt::release()calls KernelGuardIf::enable_preempt"] NOPRE_NOOP["NoPreempt::acquire()no operation"] NOPRE_NOOP_REL["NoPreempt::release()no operation"] COMBO_IMPL["NoPreemptIrqSave::acquire()calls disable_preempt + IRQ disable"] COMBO_REL["NoPreemptIrqSave::release()IRQ restore + enable_preempt"] COMBO_IRQ["NoPreemptIrqSave::acquire()IRQ disable only"] COMBO_IRQ_REL["NoPreemptIrqSave::release()IRQ restore only"] COMPILE --> FEATURE_CHECK FEATURE_CHECK --> PREEMPT_DISABLED FEATURE_CHECK --> PREEMPT_ENABLED PREEMPT_DISABLED --> COMBO_IRQ PREEMPT_DISABLED --> COMBO_IRQ_REL PREEMPT_DISABLED --> NOPRE_NOOP PREEMPT_DISABLED --> NOPRE_NOOP_REL PREEMPT_ENABLED --> COMBO_IMPL PREEMPT_ENABLED --> COMBO_REL PREEMPT_ENABLED --> NOPRE_IMPL PREEMPT_ENABLED --> NOPRE_REL
Sources: src/lib.rs(L149 - L161) src/lib.rs(L163 - L179)
Guard Behavior Matrix
The following table shows how each guard type behaves based on feature configuration:
Guard Type | preempt Feature Disabled | preempt Feature Enabled |
---|---|---|
NoOp | No operation | No operation |
IrqSave | IRQ disable/restore only | IRQ disable/restore only |
NoPreempt | No operation | Preemption disable/enable viaKernelGuardIf |
NoPreemptIrqSave | IRQ disable/restore only | Preemption disable + IRQ disable/restore |
Implementation Details
NoPreempt Guard Feature Integration
Sources: src/lib.rs(L149 - L161) src/lib.rs(L200 - L217)
NoPreemptIrqSave Combined Behavior
The NoPreemptIrqSave
guard demonstrates the most complex feature-dependent behavior, combining both IRQ control and optional preemption control:
sequenceDiagram participant UserCode as "User Code" participant NoPreemptIrqSave as "NoPreemptIrqSave" participant KernelGuardIf as "KernelGuardIf" participant archlocal_irq_ as "arch::local_irq_*" UserCode ->> NoPreemptIrqSave: new() NoPreemptIrqSave ->> NoPreemptIrqSave: acquire() alt preempt feature enabled NoPreemptIrqSave ->> KernelGuardIf: disable_preempt() Note over KernelGuardIf: Kernel preemption disabled else preempt feature disabled Note over NoPreemptIrqSave: Skip preemption control end NoPreemptIrqSave ->> archlocal_irq_: local_irq_save_and_disable() archlocal_irq_ -->> NoPreemptIrqSave: irq_state Note over NoPreemptIrqSave,archlocal_irq_: Critical section active UserCode ->> NoPreemptIrqSave: drop() NoPreemptIrqSave ->> NoPreemptIrqSave: release(irq_state) NoPreemptIrqSave ->> archlocal_irq_: local_irq_restore(irq_state) alt preempt feature enabled NoPreemptIrqSave ->> KernelGuardIf: enable_preempt() Note over KernelGuardIf: Kernel preemption restored else preempt feature disabled Note over NoPreemptIrqSave: Skip preemption control end
Sources: src/lib.rs(L163 - L179) src/lib.rs(L220 - L237)
Integration Requirements
Feature Dependencies
When the preempt
feature is enabled, user code must provide an implementation of the KernelGuardIf
trait using the crate_interface
mechanism. The relationship between feature activation and implementation requirements is:
Configuration | User Implementation Required |
---|---|
preemptdisabled | None |
preemptenabled | Must implementKernelGuardIftrait |
Runtime Interface Resolution
The crate uses crate_interface::call_interface!
macros to dynamically dispatch to user-provided implementations at runtime. This occurs only when the preempt
feature is active:
flowchart TD FEATURE_ENABLED["preempt featureenabled"] MACRO_CALL["crate_interface::call_interface!(KernelGuardIf::disable_preempt)"] RUNTIME_DISPATCH["Runtime InterfaceResolution"] USER_IMPL["User Implementationof KernelGuardIf"] ACTUAL_DISABLE["Actual PreemptionDisable Operation"] FEATURE_DISABLED["preempt featuredisabled"] COMPILE_SKIP["Conditional CompilationSkips Call"] NO_OPERATION["No PreemptionControl"] COMPILE_SKIP --> NO_OPERATION FEATURE_DISABLED --> COMPILE_SKIP FEATURE_ENABLED --> MACRO_CALL MACRO_CALL --> RUNTIME_DISPATCH RUNTIME_DISPATCH --> USER_IMPL USER_IMPL --> ACTUAL_DISABLE
Sources: src/lib.rs(L153 - L154) src/lib.rs(L158 - L159) src/lib.rs(L167 - L168) src/lib.rs(L176 - L177)
Configuration Examples
Basic Usage Without Preemption
[dependencies]
kernel_guard = "0.1"
In this configuration, NoPreempt
and NoPreemptIrqSave
guards will only control IRQs, making them functionally equivalent to IrqSave
for the IRQ portion.
Full Preemptive Configuration
[dependencies]
kernel_guard = { version = "0.1", features = ["preempt"] }
This configuration enables full preemption control capabilities, requiring the user to implement KernelGuardIf
.
Sources: Cargo.toml(L14 - L16) src/lib.rs(L21 - L26)
Implementing KernelGuardIf
Relevant source files
This page provides a comprehensive guide for implementing the KernelGuardIf
trait to enable preemption control in the kernel_guard crate. This implementation is required when using guards that need to disable kernel preemption, such as NoPreempt
and NoPreemptIrqSave
. For information about feature configuration, see Feature Configuration. For details about the core guard types, see RAII Guards.
When KernelGuardIf Implementation is Required
The KernelGuardIf
trait must be implemented by users when the preempt
feature is enabled and they want to use preemption-aware guards. Without this implementation, preemption control operations become no-ops.
Guard Type | Requires KernelGuardIf | IRQ Control | Preemption Control |
---|---|---|---|
NoOp | No | No | No |
IrqSave | No | Yes | No |
NoPreempt | Yes (with preempt feature) | No | Yes |
NoPreemptIrqSave | Yes (with preempt feature) | Yes | Yes |
Sources: src/lib.rs(L80 - L111) src/lib.rs(L149 - L179)
Trait Definition and Interface
The KernelGuardIf
trait defines the interface for kernel preemption control:
flowchart TD subgraph subGraph2["Guard Usage"] ACQUIRE["BaseGuard::acquire()"] RELEASE["BaseGuard::release()"] CALL["crate_interface::call_interface!"] end subgraph subGraph1["User Implementation"] IMPL["User struct implementing KernelGuardIf#[crate_interface::impl_interface]"] ENABLE_IMPL["enable_preempt() implementation"] DISABLE_IMPL["disable_preempt() implementation"] end subgraph subGraph0["KernelGuardIf Trait Interface"] TRAIT["KernelGuardIf trait#[crate_interface::def_interface]"] ENABLE["fn enable_preempt()"] DISABLE["fn disable_preempt()"] end ACQUIRE --> CALL CALL --> DISABLE_IMPL CALL --> ENABLE_IMPL IMPL --> DISABLE_IMPL IMPL --> ENABLE_IMPL RELEASE --> CALL TRAIT --> DISABLE TRAIT --> ENABLE
The trait is defined using the crate_interface::def_interface
attribute, which creates a stable interface that can be implemented by external crates. The methods are called through the crate_interface::call_interface!
macro.
Sources: src/lib.rs(L58 - L66)
Implementation Steps
Step 1: Define Implementation Struct
Create a struct that will implement the KernelGuardIf
trait:
struct KernelGuardIfImpl;
The struct typically doesn't need any fields, as preemption control is usually a global system operation.
Step 2: Implement the Trait
Use the #[crate_interface::impl_interface]
attribute to implement the trait:
flowchart TD subgraph subGraph0["Implementation Pattern"] ATTR["#[crate_interface::impl_interface]"] IMPL_BLOCK["impl KernelGuardIf for UserStruct"] ENABLE_FN["fn enable_preempt()"] DISABLE_FN["fn disable_preempt()"] end ATTR --> IMPL_BLOCK IMPL_BLOCK --> DISABLE_FN IMPL_BLOCK --> ENABLE_FN
The #[crate_interface::impl_interface]
attribute registers the implementation with the crate_interface system, making it available for dynamic dispatch.
Sources: src/lib.rs(L35 - L43) README.md(L41 - L49)
Step 3: Implement Method Bodies
Provide platform-specific implementations for enabling and disabling preemption:
enable_preempt()
: Should re-enable kernel preemptiondisable_preempt()
: Should disable kernel preemption
These implementations are highly platform-specific and depend on the kernel or operating system being used.
Interface Mechanism and Call Flow
The crate_interface mechanism provides runtime dispatch to user implementations:
sequenceDiagram participant Guard as Guard participant call_interface as call_interface! participant UserImplementation as User Implementation Note over Guard: Guard creation (e.g., NoPreempt::new()) Guard ->> call_interface: BaseGuard::acquire() call_interface ->> UserImplementation: KernelGuardIf::disable_preempt() UserImplementation -->> call_interface: Platform-specific disable call_interface -->> Guard: Return Note over Guard: Critical section execution Note over Guard: Guard drop Guard ->> call_interface: BaseGuard::release() call_interface ->> UserImplementation: KernelGuardIf::enable_preempt() UserImplementation -->> call_interface: Platform-specific enable call_interface -->> Guard: Return
The guards call the interface methods conditionally based on feature flags:
NoPreempt
calls the interface in bothacquire()
andrelease()
methodsNoPreemptIrqSave
calls the interface alongside IRQ control operations- Calls are wrapped in
#[cfg(feature = "preempt")]
conditionals
Sources: src/lib.rs(L153 - L154) src/lib.rs(L158 - L159) src/lib.rs(L167 - L168) src/lib.rs(L176 - L177)
Complete Implementation Example
Here's a complete example showing the implementation pattern:
use kernel_guard::{KernelGuardIf, NoPreempt};
struct KernelGuardIfImpl;
#[crate_interface::impl_interface]
impl KernelGuardIf for KernelGuardIfImpl {
fn enable_preempt() {
// Platform-specific implementation
// Example: atomic decrement of preemption counter
// or direct scheduler manipulation
}
fn disable_preempt() {
// Platform-specific implementation
// Example: atomic increment of preemption counter
// or direct scheduler manipulation
}
}
// Usage after implementation
let guard = NoPreempt::new();
// Critical section with preemption disabled
drop(guard); // Preemption re-enabled
Sources: src/lib.rs(L30 - L52) README.md(L36 - L58)
Guard Behavior with KernelGuardIf
The following diagram shows how different guards utilize the KernelGuardIf implementation:
flowchart TD subgraph subGraph1["NoPreemptIrqSave Guard"] NPIS_IRQ_DISABLE["arch::local_irq_save_and_disable()"] NPIS_ENABLE["call_interface!(enable_preempt)"] subgraph subGraph0["NoPreempt Guard"] NPIS_NEW["NoPreemptIrqSave::new()"] NPIS_ACQUIRE["BaseGuard::acquire()"] NPIS_DISABLE["call_interface!(disable_preempt)"] NPIS_DROP["Drop::drop()"] NPIS_RELEASE["BaseGuard::release()"] NPIS_IRQ_RESTORE["arch::local_irq_restore()"] NP_NEW["NoPreempt::new()"] NP_ACQUIRE["BaseGuard::acquire()"] NP_DISABLE["call_interface!(disable_preempt)"] NP_DROP["Drop::drop()"] NP_RELEASE["BaseGuard::release()"] NP_ENABLE["call_interface!(enable_preempt)"] end end NPIS_ACQUIRE --> NPIS_DISABLE NPIS_DISABLE --> NPIS_IRQ_DISABLE NPIS_DROP --> NPIS_RELEASE NPIS_IRQ_RESTORE --> NPIS_ENABLE NPIS_NEW --> NPIS_ACQUIRE NPIS_RELEASE --> NPIS_IRQ_RESTORE NP_ACQUIRE --> NP_DISABLE NP_DROP --> NP_RELEASE NP_NEW --> NP_ACQUIRE NP_RELEASE --> NP_ENABLE
Note the ordering in NoPreemptIrqSave
: preemption is disabled first, then IRQs are disabled. On release, IRQs are restored first, then preemption is re-enabled.
Sources: src/lib.rs(L149 - L179)
Best Practices and Considerations
Implementation Guidelines
- Thread Safety: Ensure implementations are thread-safe, as they may be called from multiple contexts
- Performance: Keep implementations lightweight, as they're called frequently in critical sections
- Error Handling: Implementations should not panic, as this would break RAII guarantees
- Platform Specific: Implementations must match the target platform's preemption semantics
Common Implementation Patterns
Pattern | Use Case | Example |
---|---|---|
Reference Counting | Nested preemption disable | Atomic counter increment/decrement |
Direct Control | Simple scheduler toggle | Direct register manipulation |
System Call | User-space kernels | Syscall to kernel preemption control |
No-op Stub | Testing/debugging | Empty implementations for testing |
Feature Flag Considerations
When the preempt
feature is disabled, all calls to KernelGuardIf
methods are compiled out. This means:
- No runtime overhead when preemption control is not needed
- Guards still provide IRQ control functionality
- Implementation is not required when feature is disabled
Sources: src/lib.rs(L23 - L26) src/lib.rs(L153) src/lib.rs(L158) src/lib.rs(L167) src/lib.rs(L176)
Development
Relevant source files
This document provides comprehensive information for contributors and maintainers about the development workflow, testing procedures, and CI/CD pipeline for the kernel_guard
crate. It covers the automated build system, multi-architecture testing strategy, documentation generation process, and local development environment setup.
For information about integrating kernel_guard
into other projects, see Integration Guide. For details about the core architecture and implementation, see Core Architecture.
Development Workflow Overview
The kernel_guard
project follows a streamlined development workflow centered around GitHub Actions for continuous integration and automated documentation deployment.
Development Process Flow
flowchart TD DEV["Developer"] CLONE["git clone"] LOCAL["Local Development"] FORMAT["cargo fmt"] CLIPPY["cargo clippy"] BUILD["cargo build"] TEST["cargo test"] COMMIT["git commit"] PUSH["git push / PR"] TRIGGER["GitHub Actions Trigger"] CI_JOB["ci job"] DOC_JOB["doc job"] MATRIX["Multi-target Matrix"] FMT_CHECK["cargo fmt --check"] CLIPPY_CHECK["cargo clippy"] BUILD_CHECK["cargo build"] TEST_CHECK["cargo test"] DOC_BUILD["cargo doc"] PAGES["GitHub Pages Deploy"] PASS["All Checks Pass?"] MERGE["Merge/Deploy"] FAIL["Build Failure"] BUILD --> TEST BUILD_CHECK --> PASS CI_JOB --> MATRIX CLIPPY --> BUILD CLIPPY_CHECK --> PASS CLONE --> LOCAL COMMIT --> PUSH DEV --> CLONE DOC_BUILD --> PAGES DOC_JOB --> DOC_BUILD FAIL --> LOCAL FMT_CHECK --> PASS FORMAT --> CLIPPY LOCAL --> FORMAT MATRIX --> BUILD_CHECK MATRIX --> CLIPPY_CHECK MATRIX --> FMT_CHECK MATRIX --> TEST_CHECK PASS --> FAIL PASS --> MERGE PUSH --> TRIGGER TEST --> COMMIT TEST_CHECK --> PASS TRIGGER --> CI_JOB TRIGGER --> DOC_JOB
Sources: .github/workflows/ci.yml(L1 - L56) Cargo.toml(L1 - L21)
CI/CD Pipeline Architecture
The continuous integration system is defined in .github/workflows/ci.yml and consists of two primary jobs: ci
and doc
, each serving distinct purposes in the development pipeline.
CI Job Matrix Configuration
flowchart TD subgraph subGraph3["Quality Gates"] FORMAT["cargo fmt --all --check"] CLIPPY["cargo clippy --target TARGET --all-features"] BUILD["cargo build --target TARGET --all-features"] UNITTEST["cargo test (Linux only)"] end subgraph subGraph2["Target Matrix"] T1["x86_64-unknown-linux-gnu"] T2["x86_64-unknown-none"] T3["riscv64gc-unknown-none-elf"] T4["aarch64-unknown-none-softfloat"] T5["loongarch64-unknown-none-softfloat"] end subgraph subGraph1["Matrix Configuration"] TOOLCHAIN["rust-toolchain: nightly"] TARGETS["Target Architectures"] end subgraph subGraph0["CI Pipeline"] TRIGGER["Push/Pull Request"] CI["ci job"] STRATEGY["fail-fast: false"] MATRIX["Matrix Strategy"] end BUILD --> UNITTEST CI --> STRATEGY CLIPPY --> BUILD FORMAT --> CLIPPY MATRIX --> TARGETS MATRIX --> TOOLCHAIN STRATEGY --> MATRIX T1 --> FORMAT T2 --> FORMAT T3 --> FORMAT T4 --> FORMAT T5 --> FORMAT TARGETS --> T1 TARGETS --> T2 TARGETS --> T3 TARGETS --> T4 TARGETS --> T5 TRIGGER --> CI
Sources: .github/workflows/ci.yml(L6 - L30)
Build and Test Matrix
The CI system uses a matrix strategy to test across multiple target architectures simultaneously:
Target Architecture | Operating System | Test Execution |
---|---|---|
x86_64-unknown-linux-gnu | Linux (hosted) | Full tests |
x86_64-unknown-none | Bare metal | Build only |
riscv64gc-unknown-none-elf | Bare metal | Build only |
aarch64-unknown-none-softfloat | Bare metal | Build only |
loongarch64-unknown-none-softfloat | Bare metal | Build only |
The conditional test execution is controlled by the matrix condition at .github/workflows/ci.yml(L29) : if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }}
.
Sources: .github/workflows/ci.yml(L11 - L12) .github/workflows/ci.yml(L28 - L30)
Toolchain and Dependencies
Rust Toolchain Requirements
The project requires Rust nightly toolchain with specific components:
- Toolchain:
nightly
.github/workflows/ci.yml(L17) - Components:
rust-src
,clippy
,rustfmt
.github/workflows/ci.yml(L18) - Targets: All supported architectures .github/workflows/ci.yml(L19)
Crate Dependencies
The project maintains minimal dependencies as defined in Cargo.toml(L18 - L20) :
Dependency | Version | Purpose |
---|---|---|
cfg-if | 1.0 | Conditional compilation |
crate_interface | 0.1 | Stable API abstraction |
Sources: Cargo.toml(L18 - L20) .github/workflows/ci.yml(L15 - L19)
Quality Assurance Process
Code Quality Checks
The CI pipeline enforces code quality through multiple automated checks:
flowchart TD subgraph subGraph3["Build Verification"] MULTI_TARGET["All target architectures"] ALL_FEAT["All feature combinations"] end subgraph subGraph2["Format Enforcement"] FAIL_FMT["Formatting violations fail CI"] end subgraph subGraph1["Clippy Configuration"] FEATURES["--all-features"] ALLOW["-A clippy::new_without_default"] end subgraph subGraph0["Quality Gates"] FMT["cargo fmt --all --check"] CLIPPY["cargo clippy"] BUILD["cargo build"] TEST["cargo test"] end BUILD --> ALL_FEAT BUILD --> MULTI_TARGET BUILD --> TEST CLIPPY --> ALLOW CLIPPY --> BUILD CLIPPY --> FEATURES FMT --> CLIPPY FMT --> FAIL_FMT
Sources: .github/workflows/ci.yml(L22 - L27)
Testing Strategy
Unit testing is architecture-specific and only executed on the Linux target:
- Test Execution: .github/workflows/ci.yml(L28 - L30)
- Test Command:
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
- Rationale: Bare metal targets cannot execute standard unit tests
Sources: .github/workflows/ci.yml(L28 - L30)
Documentation Pipeline
Automated Documentation Generation
The doc
job handles documentation building and deployment:
flowchart TD subgraph subGraph0["Documentation Flags"] RUSTDOC_FLAGS["RUSTDOCFLAGS environment"] BROKEN_LINKS["-D rustdoc::broken_intra_doc_links"] MISSING_DOCS["-D missing-docs"] end DOC_TRIGGER["Push to main branch"] DOC_JOB["doc job"] DOC_BUILD["cargo doc --no-deps --all-features"] INDEX_GEN["Generate index.html redirect"] DEPLOY_CHECK["Is main branch?"] PAGES_DEPLOY["GitHub Pages Deploy"] CONTINUE_ERROR["continue-on-error"] BRANCH["gh-pages branch"] FOLDER["target/doc folder"] DEPLOY_CHECK --> CONTINUE_ERROR DEPLOY_CHECK --> PAGES_DEPLOY DOC_BUILD --> INDEX_GEN DOC_JOB --> RUSTDOC_FLAGS DOC_TRIGGER --> DOC_JOB INDEX_GEN --> DEPLOY_CHECK PAGES_DEPLOY --> BRANCH PAGES_DEPLOY --> FOLDER RUSTDOC_FLAGS --> BROKEN_LINKS RUSTDOC_FLAGS --> DOC_BUILD RUSTDOC_FLAGS --> MISSING_DOCS
Sources: .github/workflows/ci.yml(L32 - L56) .github/workflows/ci.yml(L40)
Documentation Configuration
The documentation system includes several important configurations:
- Strict Documentation:
-D missing-docs
flag requires all public items to be documented - Link Validation:
-D rustdoc::broken_intra_doc_links
prevents broken documentation links - Automatic Indexing: Dynamic index.html generation using
cargo tree
output .github/workflows/ci.yml(L48)
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L47 - L48)
Development Environment Setup
Local Development Requirements
For local development, ensure your environment excludes build artifacts and temporary files as specified in .gitignore(L1 - L4) :
/target
/.vscode
.DS_Store
Cargo.lock
Feature Development
The crate supports feature-based development through Cargo.toml(L14 - L16) :
- Default Features: None (
default = []
) - Optional Features:
preempt
for preemption control functionality
Local Testing Commands
For effective local development, use these commands that mirror the CI pipeline:
# Format check
cargo fmt --all -- --check
# Linting
cargo clippy --all-features -- -A clippy::new_without_default
# Multi-target builds
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
# Unit testing (Linux only)
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
# Documentation generation
cargo doc --no-deps --all-features
Sources: .github/workflows/ci.yml(L22 - L30) .gitignore(L1 - L4) Cargo.toml(L14 - L16)
Build System and CI
Relevant source files
This document covers the automated build system and continuous integration (CI) pipeline for the kernel_guard
crate. The CI system is implemented using GitHub Actions and provides multi-architecture testing, code quality enforcement, and automated documentation deployment.
For information about setting up a local development environment, see Development Environment. For details about the specific architectures supported, see Multi-Architecture Support.
CI Pipeline Overview
The kernel_guard project uses GitHub Actions for continuous integration, defined in a single workflow file that handles both build/test operations and documentation deployment. The pipeline is triggered on all push and pull request events.
CI Pipeline Structure
flowchart TD TRIGGER["on: [push, pull_request]"] JOBS["GitHub Actions Jobs"] CI_JOB["ci job"] DOC_JOB["doc job"] MATRIX["strategy.matrix"] CI_STEPS["CI Steps"] DOC_PERMISSIONS["permissions: contents: write"] DOC_STEPS["Documentation Steps"] TOOLCHAIN["rust-toolchain: [nightly]"] TARGETS["targets: [5 architectures]"] CHECKOUT["actions/checkout@v4"] TOOLCHAIN_SETUP["dtolnay/rust-toolchain@nightly"] FORMAT_CHECK["cargo fmt --all -- --check"] CLIPPY_CHECK["cargo clippy"] BUILD_STEP["cargo build"] TEST_STEP["cargo test"] DOC_BUILD["cargo doc --no-deps --all-features"] PAGES_DEPLOY["JamesIves/github-pages-deploy-action@v4"] CI_JOB --> CI_STEPS CI_JOB --> MATRIX CI_STEPS --> BUILD_STEP CI_STEPS --> CHECKOUT CI_STEPS --> CLIPPY_CHECK CI_STEPS --> FORMAT_CHECK CI_STEPS --> TEST_STEP CI_STEPS --> TOOLCHAIN_SETUP DOC_JOB --> DOC_PERMISSIONS DOC_JOB --> DOC_STEPS DOC_STEPS --> DOC_BUILD DOC_STEPS --> PAGES_DEPLOY JOBS --> CI_JOB JOBS --> DOC_JOB MATRIX --> TARGETS MATRIX --> TOOLCHAIN TRIGGER --> JOBS
Sources: .github/workflows/ci.yml(L1 - L56)
CI Job Configuration
The primary ci
job runs on ubuntu-latest
and uses a matrix strategy to test multiple configurations simultaneously. The job is configured with fail-fast: false
to ensure all matrix combinations are tested even if some fail.
Matrix Strategy
The build matrix tests against multiple target architectures using the nightly Rust toolchain:
Target Architecture | Purpose |
---|---|
x86_64-unknown-linux-gnu | Standard Linux testing and unit tests |
x86_64-unknown-none | Bare metal x86_64 |
riscv64gc-unknown-none-elf | RISC-V 64-bit bare metal |
aarch64-unknown-none-softfloat | ARM64 bare metal |
loongarch64-unknown-none-softfloat | LoongArch64 bare metal |
Matrix Configuration Flow
flowchart TD MATRIX_START["strategy.matrix"] TOOLCHAIN_CONFIG["rust-toolchain: nightly"] TARGET_CONFIG["targets: 5 architectures"] NIGHTLY["nightly toolchain"] X86_LINUX["x86_64-unknown-linux-gnu"] X86_NONE["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] AARCH64["aarch64-unknown-none-softfloat"] LOONG["loongarch64-unknown-none-softfloat"] COMPONENTS["components: rust-src, clippy, rustfmt"] TEST_ENABLED["Unit tests enabled"] BUILD_ONLY["Build only"] AARCH64 --> BUILD_ONLY LOONG --> BUILD_ONLY MATRIX_START --> TARGET_CONFIG MATRIX_START --> TOOLCHAIN_CONFIG NIGHTLY --> COMPONENTS RISCV --> BUILD_ONLY TARGET_CONFIG --> AARCH64 TARGET_CONFIG --> LOONG TARGET_CONFIG --> RISCV TARGET_CONFIG --> X86_LINUX TARGET_CONFIG --> X86_NONE TOOLCHAIN_CONFIG --> NIGHTLY X86_LINUX --> TEST_ENABLED X86_NONE --> BUILD_ONLY
Sources: .github/workflows/ci.yml(L8 - L12) .github/workflows/ci.yml(L16 - L19)
Quality Assurance Steps
The CI job enforces code quality through a series of automated checks:
Quality Assurance Workflow
flowchart TD SETUP["Rust Toolchain Setup"] VERSION_CHECK["rustc --version --verbose"] FORMAT_CHECK["cargo fmt --all -- --check"] CLIPPY_CHECK["cargo clippy --target TARGET --all-features"] BUILD_STEP["cargo build --target TARGET --all-features"] TEST_DECISION["TARGET == x86_64-unknown-linux-gnu?"] UNIT_TEST["cargo test --target TARGET -- --nocapture"] COMPLETE["Job Complete"] CLIPPY_ALLOW["-A clippy::new_without_default"] BUILD_STEP --> TEST_DECISION CLIPPY_CHECK --> BUILD_STEP CLIPPY_CHECK --> CLIPPY_ALLOW FORMAT_CHECK --> CLIPPY_CHECK SETUP --> VERSION_CHECK TEST_DECISION --> COMPLETE TEST_DECISION --> UNIT_TEST UNIT_TEST --> COMPLETE VERSION_CHECK --> FORMAT_CHECK
The clippy step includes a specific allowance for the new_without_default
lint, configured via the -A clippy::new_without_default
flag.
Sources: .github/workflows/ci.yml(L20 - L30)
Documentation Job and GitHub Pages
The doc
job handles automated documentation generation and deployment to GitHub Pages. This job requires write permissions to the repository contents for deployment.
Documentation Build Process
The documentation job uses specific environment variables and build flags to ensure high-quality documentation:
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
Documentation Deployment Flow
flowchart TD DOC_TRIGGER["doc job trigger"] PERMISSIONS_CHECK["permissions.contents: write"] ENV_SETUP["RUSTDOCFLAGS environment"] BRANCH_CHECK["default-branch variable"] DOC_BUILD["cargo doc --no-deps --all-features"] INDEX_GEN["Generate index.html redirect"] BRANCH_CONDITION["github.ref == default-branch?"] DEPLOY_PAGES["JamesIves/github-pages-deploy-action@v4"] SKIP_DEPLOY["Skip deployment"] PAGES_CONFIG["single-commit: truebranch: gh-pagesfolder: target/doc"] STRICT_DOCS["Broken links fail buildMissing docs fail build"] BRANCH_CHECK --> DOC_BUILD BRANCH_CONDITION --> DEPLOY_PAGES BRANCH_CONDITION --> SKIP_DEPLOY DEPLOY_PAGES --> PAGES_CONFIG DOC_BUILD --> INDEX_GEN DOC_TRIGGER --> PERMISSIONS_CHECK ENV_SETUP --> BRANCH_CHECK ENV_SETUP --> STRICT_DOCS INDEX_GEN --> BRANCH_CONDITION PERMISSIONS_CHECK --> ENV_SETUP
The index.html generation uses a shell command to extract the crate name and create a redirect:
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
Sources: .github/workflows/ci.yml(L32 - L56) .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L46 - L48)
Build and Test Execution
The CI system executes different build and test strategies based on the target architecture:
Target-Specific Behavior
flowchart TD BUILD_START["cargo build --target TARGET --all-features"] TARGET_CHECK["Check target architecture"] LINUX_TARGET["x86_64-unknown-linux-gnu"] BAREMETAL_X86["x86_64-unknown-none"] BAREMETAL_RISCV["riscv64gc-unknown-none-elf"] BAREMETAL_ARM["aarch64-unknown-none-softfloat"] BAREMETAL_LOONG["loongarch64-unknown-none-softfloat"] BUILD_SUCCESS["Build Success"] TEST_CHECK["if: matrix.targets == 'x86_64-unknown-linux-gnu'"] UNIT_TESTS["cargo test --target TARGET -- --nocapture"] NO_TESTS["Skip unit tests"] TEST_COMPLETE["All checks complete"] BAREMETAL_ARM --> BUILD_SUCCESS BAREMETAL_LOONG --> BUILD_SUCCESS BAREMETAL_RISCV --> BUILD_SUCCESS BAREMETAL_X86 --> BUILD_SUCCESS BUILD_START --> TARGET_CHECK BUILD_SUCCESS --> TEST_CHECK LINUX_TARGET --> BUILD_SUCCESS NO_TESTS --> TEST_COMPLETE TARGET_CHECK --> BAREMETAL_ARM TARGET_CHECK --> BAREMETAL_LOONG TARGET_CHECK --> BAREMETAL_RISCV TARGET_CHECK --> BAREMETAL_X86 TARGET_CHECK --> LINUX_TARGET TEST_CHECK --> NO_TESTS TEST_CHECK --> UNIT_TESTS UNIT_TESTS --> TEST_COMPLETE
Unit tests are only executed on the x86_64-unknown-linux-gnu
target because the bare metal targets cannot run standard Rust test frameworks.
Sources: .github/workflows/ci.yml(L26 - L30)
The CI pipeline ensures that the kernel_guard crate builds correctly across all supported architectures while maintaining code quality standards through automated formatting, linting, and testing.
Development Environment
Relevant source files
This section provides setup instructions for local development of the kernel_guard
crate, including toolchain requirements, dependency management, and project configuration. For information about the CI/CD pipeline and automated testing, see Build System and CI.
Prerequisites and Toolchain Setup
The kernel_guard
crate is designed for kernel-level development and supports multiple target architectures. Development requires a properly configured Rust toolchain with cross-compilation capabilities.
Rust Toolchain Requirements
The project uses Rust 2021 edition and requires a recent stable Rust compiler. The crate is designed for no-std
environments, making it suitable for bare-metal kernel development.
flowchart TD RUST["rustc (stable)"] EDITION["2021 edition"] NOSTD["no-std compatibility"] TARGETS["Multi-target support"] X86["x86_64-unknown-linux-gnux86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] AARCH64["aarch64-unknown-none-softfloat"] LOONG["loongarch64-unknown-none-softfloat"] CARGO["Cargo build system"] KERNEL["Kernel development"] EDITION --> CARGO NOSTD --> KERNEL RUST --> EDITION RUST --> NOSTD RUST --> TARGETS TARGETS --> AARCH64 TARGETS --> LOONG TARGETS --> RISCV TARGETS --> X86
Rust Toolchain Setup
# Install required targets for cross-compilation
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
rustup target add loongarch64-unknown-none-softfloat
Sources: Cargo.toml(L4) Cargo.toml(L12)
Project Dependencies and Configuration
The project maintains minimal dependencies to ensure compatibility with kernel environments. All dependencies are carefully selected for no-std
compatibility.
Core Dependencies
The project relies on two primary dependencies that enable its architecture-agnostic design and conditional compilation features.
Dependency | Version | Purpose |
---|---|---|
cfg-if | 1.0 | Conditional compilation macros |
crate_interface | 0.1 | Stable API abstraction layer |
flowchart TD CARGO["Cargo.toml"] DEPS["Dependencies"] CFGIF["cfg-if = \1.0\"] CRATEIF["crate_interface = \0.1\"] ARCH["Architecture selection"] TRAIT["KernelGuardIf trait"] COMPILE["Conditional compilation"] INTERFACE["User implementation"] ARCH --> COMPILE CARGO --> DEPS CFGIF --> ARCH CRATEIF --> TRAIT DEPS --> CFGIF DEPS --> CRATEIF TRAIT --> INTERFACE
Sources: Cargo.toml(L18 - L20)
Feature Configuration
The crate provides optional features that control guard functionality and compilation behavior.
Feature | Default | Description |
---|---|---|
default | ✓ | Empty default feature set |
preempt | ✗ | Enables preemption control guards |
flowchart TD FEATURES["Crate Features"] DEFAULT["default = []"] PREEMPT["preempt"] BASIC["Basic IRQ guards only"] ADVANCED["NoPreempt + NoPreemptIrqSave"] IRQSAVE["IrqSave guard"] NOPREEMPT["NoPreempt guard"] COMBINED["NoPreemptIrqSave guard"] ADVANCED --> COMBINED ADVANCED --> NOPREEMPT BASIC --> IRQSAVE DEFAULT --> BASIC FEATURES --> DEFAULT FEATURES --> PREEMPT PREEMPT --> ADVANCED
Sources: Cargo.toml(L14 - L16)
Local Development Workflow
Project Structure
The development environment excludes common build artifacts and editor-specific files to maintain a clean repository state.
Excluded Files and Directories
/target
- Cargo build artifacts and dependencies/.vscode
- Visual Studio Code workspace settings.DS_Store
- macOS file system metadataCargo.lock
- Dependency lock file (excluded for libraries)
flowchart TD REPO["Repository Root"] SRC["src/"] CARGO["Cargo.toml"] GITIGNORE[".gitignore"] EXCLUDED["Excluded Files"] TARGET["/target"] VSCODE["/.vscode"] DSSTORE[".DS_Store"] LOCK["Cargo.lock"] BUILD["Build artifacts"] DEPS["Downloaded dependencies"] EDITOR["Editor configuration"] VERSIONS["Dependency versions"] EXCLUDED --> DSSTORE EXCLUDED --> LOCK EXCLUDED --> TARGET EXCLUDED --> VSCODE GITIGNORE --> EXCLUDED LOCK --> VERSIONS REPO --> CARGO REPO --> GITIGNORE REPO --> SRC TARGET --> BUILD TARGET --> DEPS VSCODE --> EDITOR
Sources: .gitignore(L1 - L4)
Build Commands
Basic Development Commands
# Build for host target
cargo build
# Build for specific target
cargo build --target x86_64-unknown-none
# Enable preempt feature
cargo build --features preempt
# Check formatting
cargo fmt --check
# Run linting
cargo clippy
Cross-Compilation Workflow
# Build for all supported architectures
cargo build --target x86_64-unknown-none
cargo build --target riscv64gc-unknown-none-elf
cargo build --target aarch64-unknown-none-softfloat
cargo build --target loongarch64-unknown-none-softfloat
Testing Limitations
Due to the kernel-specific nature of the crate, comprehensive testing is only available on hosted environments. The guards provide no-op implementations when not running on bare metal, allowing basic compilation and unit testing on development machines.
flowchart TD ENV["Development Environment"] HOSTED["Hosted (Linux/macOS)"] BAREMETAL["Bare Metal Target"] NOOP["NoOp guard implementations"] TESTS["Unit tests available"] REAL["Real guard implementations"] LIMITED["Limited testing"] COMPILE["Compilation verification"] CI["CI pipeline validation"] BAREMETAL --> LIMITED BAREMETAL --> REAL ENV --> BAREMETAL ENV --> HOSTED HOSTED --> NOOP HOSTED --> TESTS NOOP --> COMPILE TESTS --> CI
Sources: Cargo.toml(L12)
Documentation Development
The project uses standard Rust documentation tools with GitHub Pages integration for hosting.
Documentation Commands
# Generate local documentation
cargo doc --open
# Generate documentation with all features
cargo doc --all-features --open
# Check documentation examples
cargo test --doc
The documentation is automatically deployed to GitHub Pages through the CI pipeline, providing up-to-date API reference at the configured documentation URL.
Sources: Cargo.toml(L10)
Overview
Relevant source files
This document covers the timer_list
crate, a Rust library that provides efficient timer event management for the ArceOS operating system. The crate implements a priority queue-based system for scheduling and triggering time-based events in no-std
environments. For detailed API documentation, see Core API Reference. For practical implementation examples, see Usage Guide and Examples.
Purpose and Core Design
The timer_list
crate serves as a foundational component for time-based event scheduling in operating system kernels and embedded systems. It provides a TimerList
data structure that manages TimerEvent
objects, ensuring they are triggered sequentially when their deadlines expire.
Core Design Principles
The following diagram illustrates the core system architecture and maps natural language concepts to specific code entities:
flowchart TD subgraph subGraph2["Internal Implementation"] HEAP["BinaryHeap"] WRAP["TimerEventWrapper"] ORD["Ord implementation"] end subgraph subGraph1["Core Operations"] SET["set(deadline, event)"] EXP["expire_one(now)"] EMPTY["is_empty()"] NEXT["next_deadline()"] end subgraph subGraph0["Public Interface"] TL["TimerList"] TE["TimerEvent trait"] TEF["TimerEventFn struct"] TV["TimeValue type alias"] end EXP --> HEAP HEAP --> WRAP SET --> HEAP TE --> TEF TEF --> TL TL --> EMPTY TL --> EXP TL --> NEXT TL --> SET WRAP --> ORD
Sources: Cargo.toml(L1 - L15) README.md(L12 - L34)
System Architecture
The timer_list
crate implements a min-heap based priority queue system for efficient timer management. The following diagram shows the complete system architecture:
flowchart TD subgraph subGraph3["ArceOS Integration"] KERNEL["Kernel Timer System"] NOSTD["no-std Environment"] TARGETS["Multi-architecture Support"] end subgraph subGraph2["Data Management"] PQUEUE["Priority Queue (BinaryHeap)"] EVENTS["TimerEventWrapper objects"] ORDERING["Min-heap ordering by deadline"] end subgraph subGraph1["timer_list Crate"] API["TimerList API"] TRAIT["TimerEvent trait"] WRAPPER["TimerEventFn wrapper"] end subgraph subGraph0["Application Layer"] APP["User Code"] CB["Event Callbacks"] end API --> KERNEL API --> NOSTD API --> PQUEUE APP --> API APP --> TRAIT CB --> WRAPPER EVENTS --> ORDERING NOSTD --> TARGETS PQUEUE --> ORDERING TRAIT --> EVENTS WRAPPER --> EVENTS
Sources: Cargo.toml(L6 - L7) Cargo.toml(L12) README.md(L7 - L8)
Key Capabilities
The timer_list
crate provides the following core capabilities:
Capability | Implementation | Code Entity |
---|---|---|
Event Scheduling | Priority queue with O(log n) insertion | TimerList::set() |
Event Expiration | O(1) access to next expired event | TimerList::expire_one() |
Custom Events | Trait-based event system | TimerEventtrait |
Function Callbacks | Wrapper for closure-based events | TimerEventFn |
Queue Status | Check if events are pending | TimerList::is_empty() |
Deadline Access | Get next event deadline | TimerList::next_deadline() |
Sequential Processing Model
The crate enforces sequential processing of timer events, where events are triggered one at a time in deadline order. This design ensures deterministic behavior suitable for real-time systems and kernel environments.
sequenceDiagram participant UserCode as User Code participant TimerList as TimerList participant BinaryHeap as BinaryHeap participant TimerEvent as TimerEvent UserCode ->> TimerList: "set(deadline, event)" TimerList ->> BinaryHeap: "push(TimerEventWrapper)" Note over BinaryHeap: "Auto-sorts by deadline" loop "Event Processing" UserCode ->> TimerList: "expire_one(current_time)" TimerList ->> BinaryHeap: "peek() - check earliest" alt "Event expired" BinaryHeap -->> TimerList: "Return wrapper" TimerList ->> BinaryHeap: "pop() - remove" TimerList -->> UserCode: "Some((deadline, event))" UserCode ->> TimerEvent: "event.callback(now)" else "No expired events" TimerList -->> UserCode: "None" end end
Sources: README.md(L24 - L33)
ArceOS Ecosystem Integration
The timer_list
crate is designed specifically for integration with the ArceOS operating system ecosystem. Key integration aspects include:
- No-std Compatibility: Designed for kernel and embedded environments without standard library dependencies
- Multi-architecture Support: Compatible with x86_64, RISC-V, and ARM architectures
- License Flexibility: Triple-licensed (GPL-3.0, Apache-2.0, MulanPSL-2.0) for broad ecosystem compatibility
- Crates.io Publication: Available as a standalone library for reuse in other projects
The crate serves as a foundational component that other ArceOS modules can depend on for timer-based functionality, from interrupt handling to task scheduling.
Sources: Cargo.toml(L7 - L12) README.md(L3 - L5)
Core API Reference
Relevant source files
This document provides comprehensive reference documentation for all public types, traits, and methods exposed by the timer_list
crate. The API consists of four main components: the TimerList
data structure for managing timed events, the TimerEvent
trait for defining event callbacks, the TimerEventFn
wrapper for closure-based events, and the TimeValue
type alias for time representation.
For practical usage examples and integration patterns, see Usage Guide and Examples. For detailed explanations of the internal architecture, see TimerList Data Structure and TimerEvent System.
Type System Overview
flowchart TD TimeValue["TimeValue(type alias)"] Duration["core::time::Duration"] TimerEvent["TimerEvent(trait)"] callback["callback(self, now: TimeValue)"] TimerEventFn["TimerEventFn(struct)"] BoxedClosure["Box<dyn FnOnce(TimeValue)>"] TimerList["TimerList<E: TimerEvent>(struct)"] BinaryHeap["BinaryHeap<TimerEventWrapper<E>>"] TimerEventWrapper["TimerEventWrapper<E>(internal)"] deadline["deadline: TimeValue"] event["event: E"] BinaryHeap --> TimerEventWrapper TimeValue --> Duration TimerEvent --> callback TimerEventFn --> BoxedClosure TimerEventFn --> TimerEvent TimerEventWrapper --> deadline TimerEventWrapper --> event TimerList --> BinaryHeap TimerList --> TimerEvent
Type Relationships in timer_list Crate
This diagram shows the complete type hierarchy and relationships within the crate. The TimerList
is parameterized over types implementing TimerEvent
, while TimerEventFn
provides a concrete implementation for closure-based events.
Sources: src/lib.rs(L10 - L32) src/lib.rs(L108 - L127)
Core Types
TimeValue
pub type TimeValue = Duration;
The TimeValue
type is an alias for core::time::Duration
and represents all time values used throughout the timer system. This includes event deadlines, current time measurements, and time deltas.
Usage Context | Description |
---|---|
Event deadlines | Absolute time when events should expire |
Current time | Time parameter passed toexpire_one() |
Callback parameter | Time value passed toTimerEvent::callback() |
Sources: src/lib.rs(L10 - L13)
TimerEvent Trait
The TimerEvent
trait defines the interface that all timed events must implement. It consists of a single required method:
#![allow(unused)] fn main() { pub trait TimerEvent { fn callback(self, now: TimeValue); } }
Method | Parameters | Description |
---|---|---|
callback | self, now: TimeValue | Called when the timer expires; consumes the event |
The callback
method takes ownership of the event (self
) and receives the current time when the event expires. This design ensures that each event can only be triggered once.
Sources: src/lib.rs(L15 - L19)
TimerEventFn Wrapper
The TimerEventFn
struct provides a convenient way to create timer events from closures without implementing the TimerEvent
trait manually.
pub struct TimerEventFn(Box<dyn FnOnce(TimeValue) + 'static>);
Method | Signature | Description |
---|---|---|
new | pub fn new | Creates a newTimerEventFnfrom a closure |
The wrapper automatically implements TimerEvent
by calling the stored closure when the event expires.
Sources: src/lib.rs(L108 - L127)
TimerList API Methods
Constructor and State
Method | Signature | Description |
---|---|---|
new | pub fn new() -> Self | Creates a new empty timer list |
is_empty | pub fn is_empty(&self) -> bool | Returnstrueif no events are scheduled |
The TimerList
also implements Default
, which delegates to new()
.
Sources: src/lib.rs(L55 - L66) src/lib.rs(L102 - L106)
Event Management
Method | Signature | Description |
---|---|---|
set | pub fn set(&mut self, deadline: TimeValue, event: E) | Schedules an event to expire at the specified deadline |
cancel | pub fn cancel | Removes all events matching the condition function |
next_deadline | pub fn next_deadline(&self) -> Option | Returns the deadline of the earliest scheduled event |
expire_one | pub fn expire_one(&mut self, now: TimeValue) -> Option<(TimeValue, E)> | Expires the earliest event if its deadline has passed |
The set
method has O(log n) complexity due to the underlying min-heap structure. The cancel
method currently has O(n) complexity and includes a TODO comment for performance optimization.
Sources: src/lib.rs(L68 - L99)
Event Processing Flow
sequenceDiagram participant ClientCode as "Client Code" participant TimerList as "TimerList" participant BinaryHeap as "BinaryHeap" participant TimerEventWrapper as "TimerEventWrapper" Note over ClientCode,TimerEventWrapper: Event Scheduling ClientCode ->> TimerList: "set(deadline, event)" TimerList ->> TimerEventWrapper: "TimerEventWrapper::new" TimerList ->> BinaryHeap: "push(wrapper)" Note over BinaryHeap: "Min-heap reorders by deadline" Note over ClientCode,TimerEventWrapper: Event Expiration ClientCode ->> TimerList: "expire_one(now)" TimerList ->> BinaryHeap: "peek()" BinaryHeap -->> TimerList: "earliest event" alt "deadline <= now" TimerList ->> BinaryHeap: "pop()" BinaryHeap -->> TimerList: "TimerEventWrapper" TimerList -->> ClientCode: "Some((deadline, event))" ClientCode ->> ClientCode: "event.callback(now)" else "deadline > now" TimerList -->> ClientCode: "None" end
Event Lifecycle from Scheduling to Expiration
This sequence diagram illustrates the complete flow from event scheduling through expiration processing, showing how the TimerList
coordinates with the internal BinaryHeap
and TimerEventWrapper
structures.
Sources: src/lib.rs(L21 - L24) src/lib.rs(L68 - L99)
Internal Implementation Details
TimerEventWrapper
The internal TimerEventWrapper<E>
struct combines an event with its deadline and implements the ordering traits required for the min-heap:
struct TimerEventWrapper<E> {
deadline: TimeValue,
event: E,
}
The wrapper implements Ord
, PartialOrd
, Eq
, and PartialEq
with reversed ordering to create a min-heap from Rust's max-heap BinaryHeap
. The comparison is based solely on the deadline
field.
Trait Implementation | Behavior |
---|---|
Ord::cmp | Comparesother.deadlinewithself.deadline(reversed) |
PartialOrd::partial_cmp | Delegates toOrd::cmp |
Eq,PartialEq | Compares deadlines for equality |
Sources: src/lib.rs(L21 - L24) src/lib.rs(L34 - L52)
Min-Heap Architecture
flowchart TD subgraph subGraph0["Ordering Property"] parent["Parent deadline"] child1["Child1 deadline"] child2["Child2 deadline"] end TimerList["TimerList<E>"] events["events: BinaryHeap"] root["Root: earliest deadline"] left["Left child"] right["Right child"] leftleft["..."] leftright["..."] rightleft["..."] rightright["..."] TimerList --> events events --> root left --> leftleft left --> leftright parent --> child1 parent --> child2 right --> rightleft right --> rightright root --> left root --> right
Internal Min-Heap Structure for Event Ordering
The BinaryHeap
maintains the min-heap property where each parent node has a deadline less than or equal to its children, ensuring O(1) access to the earliest event and O(log n) insertion.
Sources: src/lib.rs(L26 - L32) src/lib.rs(L40 - L43)
TimerList Data Structure
Relevant source files
This document provides detailed technical documentation of the TimerList
struct, which serves as the core data structure for managing timed events in the timer_list crate. It covers the internal min-heap architecture, public methods, performance characteristics, and implementation details.
For information about the TimerEvent
trait and event callback system, see TimerEvent System. For practical usage examples, see Usage Guide and Examples.
Core Structure Overview
The TimerList<E: TimerEvent>
struct provides an efficient priority queue implementation for managing timed events. It internally uses a binary heap data structure to maintain events in deadline order, enabling O(log n) insertions and O(1) access to the next expiring event.
Primary Components
flowchart TD subgraph subGraph0["Type Constraints"] TC["E: TimerEvent"] end TL["TimerList<E>"] BH["BinaryHeap<TimerEventWrapper<E>>"] TEW1["TimerEventWrapper<E>"] TEW2["TimerEventWrapper<E>"] TEW3["TimerEventWrapper<E>"] DL1["deadline: TimeValue"] EV1["event: E"] DL2["deadline: TimeValue"] EV2["event: E"] DL3["deadline: TimeValue"] EV3["event: E"] BH --> TEW1 BH --> TEW2 BH --> TEW3 TEW1 --> DL1 TEW1 --> EV1 TEW2 --> DL2 TEW2 --> EV2 TEW3 --> DL3 TEW3 --> EV3 TL --> BH TL --> TC
Sources: src/lib.rs(L30 - L32) src/lib.rs(L21 - L24)
The structure consists of three main components:
Component | Type | Purpose |
---|---|---|
TimerList | Public struct | Main interface for timer management |
BinaryHeap<TimerEventWrapper | Internal field | Heap storage for efficient ordering |
TimerEventWrapper | Internal struct | Wrapper containing deadline and event data |
Min-Heap Architecture
The TimerList
implements a min-heap through custom ordering logic on TimerEventWrapper<E>
. This ensures that events with earlier deadlines are always at the top of the heap for efficient retrieval.
Heap Ordering Implementation
flowchart TD subgraph subGraph2["Trait Implementations"] ORD["Ord"] PORD["PartialOrd"] EQT["Eq"] PEQ["PartialEq"] end subgraph subGraph1["Min-Heap Property"] ROOT["Earliest Deadline"] L1["Later Deadline"] R1["Later Deadline"] L2["Even Later"] R2["Even Later"] end subgraph subGraph0["TimerEventWrapper Ordering"] CMP["cmp()"] REV["other.deadline.cmp(&self.deadline)"] PCMP["partial_cmp()"] EQ["eq()"] COMP["self.deadline == other.deadline"] end CMP --> REV CMP --> ROOT EQ --> COMP EQT --> EQ L1 --> L2 L1 --> R2 ORD --> CMP PCMP --> CMP PEQ --> EQ PORD --> PCMP ROOT --> L1 ROOT --> R1
Sources: src/lib.rs(L34 - L52)
The ordering implementation uses reversed comparison logic:
Trait | Implementation | Purpose |
---|---|---|
Ord::cmp() | other.deadline.cmp(&self.deadline) | Reverses natural ordering for min-heap |
PartialOrd::partial_cmp() | Delegates tocmp() | Required for heap operations |
Eq/PartialEq::eq() | self.deadline == other.deadline | Deadline-based equality |
Public Methods
The TimerList
provides a clean API for timer management operations:
Core Operations
Method | Signature | Complexity | Purpose |
---|---|---|---|
new() | fn new() -> Self | O(1) | Creates empty timer list |
set() | fn set(&mut self, deadline: TimeValue, event: E) | O(log n) | Schedules new event |
expire_one() | fn expire_one(&mut self, now: TimeValue) -> Option<(TimeValue, E)> | O(log n) | Processes earliest expired event |
cancel() | fn cancel | O(n) | Removes events matching condition |
Query Operations
Method | Signature | Complexity | Purpose |
---|---|---|---|
is_empty() | fn is_empty(&self) -> bool | O(1) | Checks if any events exist |
next_deadline() | fn next_deadline(&self) -> Option | O(1) | Returns earliest deadline |
Sources: src/lib.rs(L54 - L100)
Internal Implementation Details
Event Scheduling Flow
sequenceDiagram participant Client as Client participant TimerList as TimerList participant BinaryHeap as BinaryHeap participant TimerEventWrapper as TimerEventWrapper Client ->> TimerList: "set(deadline, event)" TimerList ->> TimerEventWrapper: "TimerEventWrapper { deadline, event }" TimerList ->> BinaryHeap: "push(wrapper)" Note over BinaryHeap: "Auto-reorders via Ord trait" BinaryHeap -->> TimerList: "Heap maintains min property" TimerList -->> Client: "Event scheduled"
Sources: src/lib.rs(L69 - L71)
Event Expiration Flow
sequenceDiagram participant Client as Client participant TimerList as TimerList participant BinaryHeap as BinaryHeap Client ->> TimerList: "expire_one(now)" TimerList ->> BinaryHeap: "peek()" BinaryHeap -->> TimerList: "Some(earliest_wrapper)" alt "deadline <= now" TimerList ->> BinaryHeap: "pop()" BinaryHeap -->> TimerList: "TimerEventWrapper" TimerList -->> Client: "Some((deadline, event))" else "deadline > now" TimerList -->> Client: "None" end
Sources: src/lib.rs(L92 - L99)
Data Structure Memory Layout
The TimerList
maintains minimal memory overhead with efficient heap allocation:
flowchart TD subgraph subGraph1["Heap Properties"] HP1["Min-heap ordering"] HP2["O(log n) rebalancing"] HP3["Contiguous memory"] end subgraph subGraph0["Memory Layout"] TLS["TimerList Struct"] BHS["BinaryHeap Storage"] VEC["Vec<TimerEventWrapper>"] TEW1["TimerEventWrapper { deadline, event }"] TEW2["TimerEventWrapper { deadline, event }"] TEWN["..."] end BHS --> HP1 BHS --> HP2 BHS --> VEC TLS --> BHS VEC --> HP3 VEC --> TEW1 VEC --> TEW2 VEC --> TEWN
Sources: src/lib.rs(L30 - L32) src/lib.rs(L21 - L24)
Performance Characteristics
Operation | Time Complexity | Space Complexity | Notes |
---|---|---|---|
Insert (set) | O(log n) | O(1) additional | Binary heap insertion |
Peek next (next_deadline) | O(1) | O(1) | Heap root access |
Pop next (expire_one) | O(log n) | O(1) | Heap rebalancing required |
Cancel events | O(n) | O(n) | Linear scan with filtering |
Check empty | O(1) | O(1) | Heap size check |
The min-heap implementation provides optimal performance for the primary use case of sequential event processing by deadline order.
Sources: src/lib.rs(L54 - L100)
TimerEvent System
Relevant source files
This document covers the timer event system that defines how callbacks are executed when timers expire in the timer_list crate. It focuses on the TimerEvent
trait and its implementations, particularly the TimerEventFn
wrapper for closure-based events.
For information about the underlying timer storage and scheduling mechanisms, see TimerList Data Structure.
Purpose and Scope
The TimerEvent system provides the interface and implementations for executable timer callbacks in the timer_list crate. It defines how events are structured and executed when their deadlines are reached, supporting both custom event types and simple closure-based events.
TimerEvent Trait
The TimerEvent
trait serves as the core interface that all timer events must implement. It defines a single callback method that consumes the event when executed.
Trait Definition
The trait is defined with a simple interface at src/lib.rs(L15 - L19) :
#![allow(unused)] fn main() { pub trait TimerEvent { fn callback(self, now: TimeValue); } }
Key Characteristics
Aspect | Description |
---|---|
Consumption | Thecallbackmethod takesselfby value, consuming the event |
Time Parameter | Receives the current time asTimeValue(alias forDuration) |
Return Type | Returns()- events perform side effects rather than return values |
Execution Model | Called exactly once when the timer expires |
The trait design ensures that events cannot be accidentally reused after execution, providing memory safety and preventing common timer-related bugs.
TimerEvent Trait Architecture
classDiagram class TimerEvent { <<trait>> +callback(self, now: TimeValue) } class TimerEventFn { -Box~dyn FnOnce(TimeValue) ~ +new(f: F) TimerEventFn +callback(self, now: TimeValue) } class CustomEvent { <<user-defined>> +callback(self, now: TimeValue) } TimerEvent ..|> TimerEventFn : implements TimerEvent ..|> CustomEvent : implements
Sources: src/lib.rs(L15 - L19) src/lib.rs(L108 - L127)
TimerEventFn Implementation
The TimerEventFn
struct provides a convenient wrapper that allows closures to be used as timer events without requiring custom trait implementations.
Structure and Construction
The TimerEventFn
wraps a boxed closure at src/lib.rs(L111) :
pub struct TimerEventFn(Box<dyn FnOnce(TimeValue) + 'static>);
Construction is handled by the new
method at src/lib.rs(L114 - L121) :
Parameter | Type | Description |
---|---|---|
f | F: FnOnce(TimeValue) + 'static | Closure to execute when timer expires |
Return | TimerEventFn | Wrapped timer event ready for scheduling |
TimerEvent Implementation
The trait implementation at src/lib.rs(L123 - L127) simply extracts and calls the wrapped closure:
#![allow(unused)] fn main() { impl TimerEvent for TimerEventFn { fn callback(self, now: TimeValue) { (self.0)(now) } } }
This design enables functional programming patterns while maintaining the trait's consumption semantics.
TimerEventFn Data Flow
flowchart TD A["closure: FnOnce(TimeValue)"] B["TimerEventFn::new(closure)"] C["TimerEventFn(Box::new(closure))"] D["TimerList::set(deadline, event)"] E["TimerEventWrapper { deadline, event }"] F["BinaryHeap::push(wrapper)"] G["TimerList::expire_one(now)"] H["BinaryHeap::pop()"] I["event.callback(now)"] J["(closure)(now)"] A --> B B --> C C --> D D --> E E --> F G --> H H --> I I --> J
Sources: src/lib.rs(L108 - L127) src/lib.rs(L69 - L71)
Event Lifecycle Integration
Timer events integrate with the TimerList
through a well-defined lifecycle that ensures proper execution timing and resource management.
Event Scheduling Process
Events are scheduled through the TimerList::set
method, which wraps them in TimerEventWrapper
structures for heap management:
Step | Component | Action |
---|---|---|
1 | Client Code | Creates event implementingTimerEvent |
2 | TimerList::set | Wraps event inTimerEventWrapperwith deadline |
3 | BinaryHeap | Stores wrapper using min-heap ordering |
4 | TimerList::expire_one | Checks earliest deadline against current time |
5 | Event Callback | Executesevent.callback(now)if expired |
Execution Semantics
When events expire, they follow a strict execution pattern defined at src/lib.rs(L92 - L99) :
- Events are processed one at a time in deadline order
- Only events with
deadline <= now
are eligible for execution - Events are removed from the heap before callback execution
- Callbacks receive the actual current time, not the original deadline
Event Execution Flow
sequenceDiagram participant ClientCode as "Client Code" participant TimerList as "TimerList" participant BinaryHeapTimerEventWrapper as "BinaryHeap<TimerEventWrapper>" participant TimerEventcallback as "TimerEvent::callback" Note over ClientCode,TimerEventcallback: Event Scheduling ClientCode ->> TimerList: set(deadline, event) TimerList ->> BinaryHeapTimerEventWrapper: push(TimerEventWrapper) Note over ClientCode,TimerEventcallback: Event Processing loop Processing Loop ClientCode ->> TimerList: expire_one(now) TimerList ->> BinaryHeapTimerEventWrapper: peek() alt deadline <= now BinaryHeapTimerEventWrapper -->> TimerList: earliest event TimerList ->> BinaryHeapTimerEventWrapper: pop() TimerList ->> TimerEventcallback: callback(now) TimerEventcallback -->> ClientCode: side effects TimerList -->> ClientCode: Some((deadline, event)) else deadline > now TimerList -->> ClientCode: None end end
Sources: src/lib.rs(L69 - L71) src/lib.rs(L92 - L99) src/lib.rs(L21 - L24)
Custom Event Implementation
While TimerEventFn
handles simple closure cases, custom event types can implement TimerEvent
directly for more complex scenarios. The test suite demonstrates this pattern at src/lib.rs(L140 - L153) :
Example Pattern
#![allow(unused)] fn main() { struct TestTimerEvent(usize, TimeValue); impl TimerEvent for TestTimerEvent { fn callback(self, now: TimeValue) { // Custom logic with access to event data println!("Event {} executed at {:?}", self.0, now); } } }
This approach allows events to carry additional data and implement complex callback logic while maintaining the same execution guarantees as TimerEventFn
.
Code Entity Mapping
flowchart TD subgraph subGraph2["Test Examples"] H["TestTimerEvent[src/lib.rs:140-153]"] I["test_timer_list_fn[src/lib.rs:183-206]"] end subgraph subGraph1["Integration Points"] E["TimerList::set()[src/lib.rs:69-71]"] F["TimerList::expire_one()[src/lib.rs:92-99]"] G["TimerEventWrapper[src/lib.rs:21-24]"] end subgraph subGraph0["TimerEvent System API"] A["TimerEvent trait[src/lib.rs:15-19]"] B["TimerEventFn struct[src/lib.rs:111]"] C["TimerEventFn::new()[src/lib.rs:114-121]"] D["TimeValue type[src/lib.rs:10-13]"] end A --> B A --> D A --> H B --> C B --> I E --> G F --> A
Sources: src/lib.rs(L15 - L19) src/lib.rs(L108 - L127) src/lib.rs(L69 - L71) src/lib.rs(L92 - L99) src/lib.rs(L140 - L153) src/lib.rs(L183 - L206)
Usage Guide and Examples
Relevant source files
This document provides practical examples and usage patterns for the timer_list
crate, demonstrating how to integrate timer management functionality into applications. The examples range from basic single-timer usage to advanced patterns for managing multiple concurrent timers with custom events.
For detailed API documentation of the core types and methods, see Core API Reference. For information about the internal implementation architecture, see TimerList Data Structure and TimerEvent System.
Basic Timer Usage with Closures
The simplest way to use timer_list
is with the TimerEventFn
wrapper, which allows you to schedule closures to execute at specific deadlines.
Simple Timer Example
sequenceDiagram participant ApplicationCode as "Application Code" participant TimerListTimerEventFn as "TimerList<TimerEventFn>" participant TimerEventFn as "TimerEventFn" ApplicationCode ->> TimerListTimerEventFn: "new()" ApplicationCode ->> TimerEventFn: "new(closure)" ApplicationCode ->> TimerListTimerEventFn: "set(deadline, event)" loop "Event Processing Loop" ApplicationCode ->> TimerListTimerEventFn: "expire_one(now)" alt "Event Ready" TimerListTimerEventFn -->> ApplicationCode: "Some((deadline, event))" ApplicationCode ->> TimerEventFn: "callback(now)" else "No Events Ready" TimerListTimerEventFn -->> ApplicationCode: "None" end end
Basic Timer Setup and Processing
The core pattern involves creating a TimerList
, setting timer events with deadlines, and periodically checking for expired events in a processing loop.
Sources: README.md(L12 - L34) src/lib.rs(L111 - L127)
Implementation Pattern
The basic usage follows this pattern:
- Create a
TimerList<TimerEventFn>
instance - Use
TimerEventFn::new()
to wrap closures as timer events - Call
set()
method with aTimeValue
deadline and the event - Periodically call
expire_one()
with current time to process expired events - Execute the callback when events are returned
flowchart TD subgraph subGraph1["Event Processing"] D["Current Time"] E["timer_list.expire_one(now)"] F["Event Expired?"] G["event.callback(now)"] H["None returned"] end subgraph subGraph0["Timer Creation"] A["TimerList::new()"] B["TimerEventFn::new(closure)"] C["timer_list.set(deadline, event)"] end A --> B B --> C C --> E D --> E E --> F F --> G F --> H G --> E H --> E
Timer Event Lifecycle from Creation to Execution
Sources: README.md(L16 - L33) src/lib.rs(L69 - L98)
Custom Timer Events
For more complex use cases, implement the TimerEvent
trait directly to create custom timer events with specific behavior and data.
Custom Event Implementation
The TimerEvent
trait requires implementing a single callback
method that consumes the event and receives the current time.
classDiagram class TimerEvent { <<trait>> +callback(self, now: TimeValue) } class TimerEventFn { -closure: Box~dyn FnOnce(TimeValue) ~ +new(f: F) TimerEventFn +callback(self, now: TimeValue) } class CustomEvent { -id: usize -expected_deadline: TimeValue +new(id: usize, deadline: TimeValue) CustomEvent +callback(self, now: TimeValue) } TimerEvent ..|> TimerEventFn TimerEvent ..|> CustomEvent
TimerEvent Trait Implementation Hierarchy
Sources: src/lib.rs(L15 - L19) src/lib.rs(L123 - L127) src/lib.rs(L140 - L153)
Custom Event Example
The test case in the source code demonstrates a custom TestTimerEvent
that tracks execution order and timing accuracy:
Component | Purpose | Key Methods |
---|---|---|
TestTimerEvent | Custom event with ID and expected deadline | callback(self, now: TimeValue) |
Event validation | Verify execution order and timing | Comparenowwith stored deadline |
Atomic counter | Track execution sequence | AtomicUsizefor thread-safe counting |
The custom event pattern allows embedding application-specific data and logic within timer events, enabling complex scheduling scenarios.
Sources: src/lib.rs(L140 - L153)
Advanced Usage Patterns
Multiple Timer Management
When managing multiple concurrent timers, the TimerList
maintains them in deadline order using an internal min-heap structure.
flowchart TD subgraph subGraph2["Sequential Processing"] I["expire_one() - Timer 1"] J["expire_one() - Timer 2"] K["expire_one() - Timer 3"] L["expire_one() - Timer 4"] M["Timer 5 (canceled)"] end subgraph subGraph1["Internal Organization"] G["BinaryHeap"] H["Min-heap by deadline"] end subgraph subGraph0["Multiple Timer Setup"] A["Timer 1 (0.5s)"] D["TimerList::set()"] B["Timer 2 (1.0s)"] C["Timer 3 (2.99s)"] E["Timer 4 (3.0s)"] F["Timer 5 (4.0s)"] end A --> D B --> D C --> D D --> G E --> D F --> D G --> H H --> I I --> J J --> K K --> L L --> M
Multiple Timer Processing Order
The timers are processed in deadline order regardless of insertion sequence. The min-heap ensures O(log n)
insertion and O(1)
access to the earliest deadline.
Sources: src/lib.rs(L157 - L167) src/lib.rs(L30 - L32) src/lib.rs(L40 - L43)
Timer Cancellation
The cancel()
method allows removing events that match a specified condition before they expire.
Operation | Method Signature | Use Case |
---|---|---|
Cancel by condition | cancel | Remove events matching predicate |
Check empty | is_empty(&self) -> bool | Verify if any timers remain |
Next deadline | next_deadline(&self) -> Option | Get earliest scheduled time |
The cancellation mechanism uses a closure to test each event, providing flexible filtering capabilities. The implementation uses BinaryHeap::retain()
to remove matching events.
Sources: src/lib.rs(L73 - L80) src/lib.rs(L171 - L173)
Integration Patterns
Event Loop Integration
Timer management typically integrates with application event loops or system schedulers using a polling pattern.
flowchart TD subgraph subGraph1["Timer State"] I["TimerList"] J["BinaryHeap ordering"] K["next_deadline()"] L["Optimal sleep duration"] end subgraph subGraph0["Application Event Loop"] A["Loop Start"] B["Get Current Time"] C["timer_list.expire_one(now)"] D["Event Expired?"] E["Execute Callback"] F["Other Processing"] G["Continue Loop"] H["Sleep/Yield"] end A --> B B --> C C --> D C --> I D --> E D --> F E --> G F --> G G --> H H --> B I --> J J --> K K --> L L --> H
Event Loop Integration Pattern
Applications can use next_deadline()
to calculate optimal sleep durations, reducing unnecessary CPU usage while maintaining timer accuracy.
Sources: src/lib.rs(L169 - L178) src/lib.rs(L84 - L86)
No-std Environment Usage
The crate is designed for no-std
environments, making it suitable for embedded systems and kernel-level code.
Requirement | Implementation | Benefit |
---|---|---|
no-stdcompatibility | #![cfg_attr(not(test), no_std)] | Embedded/kernel usage |
Heap allocation | extern crate alloc | Dynamic timer storage |
Core types only | core::time::Duration | Minimal dependencies |
No threading | Sequential processing | Deterministic behavior |
The design ensures predictable performance characteristics essential for real-time and embedded applications.
Sources: src/lib.rs(L1) src/lib.rs(L4) src/lib.rs(L10 - L13)
Common Patterns and Best Practices
Error Handling and Edge Cases
The TimerList
API is designed to be infallible for core operations, with Option
types indicating state rather than errors.
flowchart TD subgraph subGraph1["State Handling"] I["Some: Event ready"] J["None: No events ready"] K["Some: Next deadline"] L["None: No events scheduled"] end subgraph subGraph0["Safe Operations"] A["set(deadline, event)"] B["Always succeeds"] C["expire_one(now)"] D["Option<(TimeValue, E)>"] E["next_deadline()"] F["Option"] G["is_empty()"] H["bool"] end A --> B C --> D D --> I D --> J E --> F F --> K F --> L G --> H
API Safety and State Management
All operations are memory-safe and return clear indicators of timer list state, eliminating common timer management error scenarios.
Sources: src/lib.rs(L54 - L100)
Performance Considerations
Operation | Complexity | Notes |
---|---|---|
set() | O(log n) | Heap insertion |
expire_one() | O(log n) | Heap removal when expired |
next_deadline() | O(1) | Heap peek operation |
cancel() | O(n) | Linear scan with rebuild |
The min-heap structure provides efficient access to the earliest deadline, but cancellation operations require full heap traversal. For frequent cancellation scenarios, consider alternative approaches like event flags.
Sources: src/lib.rs(L69 - L71) src/lib.rs(L92 - L98) src/lib.rs(L74 - L80)
Development Workflow
Relevant source files
This document provides guidance for contributors working on the timer_list
crate. It covers local development setup, build processes, testing procedures, and the automated CI/CD pipeline. For detailed API documentation, see Core API Reference. For usage examples and integration patterns, see Usage Guide and Examples.
Local Development Environment
The timer_list
crate requires the Rust nightly toolchain with specific components and target architectures. The development environment must support cross-compilation for embedded and bare-metal targets.
Required Toolchain Components
flowchart TD A["rust-toolchain: nightly"] B["rust-src component"] C["clippy component"] D["rustfmt component"] E["Target Architectures"] F["x86_64-unknown-linux-gnu"] G["x86_64-unknown-none"] H["riscv64gc-unknown-none-elf"] I["aarch64-unknown-none-softfloat"] A --> B A --> C A --> D A --> E E --> F E --> G E --> H E --> I
Toolchain Setup
The project requires rustc nightly
with cross-compilation support for multiple architectures. Install the required components:
rustup toolchain install nightly
rustup component add rust-src clippy rustfmt --toolchain nightly
rustup target add x86_64-unknown-none riscv64gc-unknown-none-elf aarch64-unknown-none-softfloat --toolchain nightly
Sources: .github/workflows/ci.yml(L15 - L19)
Build and Development Commands
Core Development Workflow
flowchart TD A["cargo fmt --all -- --check"] B["cargo clippy --target TARGET --all-features"] C["cargo build --target TARGET --all-features"] D["cargo test --target x86_64-unknown-linux-gnu"] E["cargo doc --no-deps --all-features"] F["Format Check"] G["Lint Analysis"] H["Cross-Platform Build"] I["Unit Testing"] J["Documentation Generation"] A --> B B --> C C --> D D --> E F --> A G --> B H --> C I --> D J --> E
Format Checking
cargo fmt --all -- --check
Lint Analysis
cargo clippy --target <TARGET> --all-features -- -A clippy::new_without_default
Building for Specific Targets
cargo build --target <TARGET> --all-features
Running Tests
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
Sources: .github/workflows/ci.yml(L22 - L30)
Multi-Target Architecture Support
The crate supports four distinct target architectures, each serving different deployment scenarios:
Target Architecture | Use Case | Testing Support |
---|---|---|
x86_64-unknown-linux-gnu | Standard Linux development | Full unit testing |
x86_64-unknown-none | Bare-metal x86_64 systems | Build-only |
riscv64gc-unknown-none-elf | RISC-V embedded systems | Build-only |
aarch64-unknown-none-softfloat | ARM64 embedded systems | Build-only |
flowchart TD A["timer_list crate"] B["x86_64-unknown-linux-gnu"] C["x86_64-unknown-none"] D["riscv64gc-unknown-none-elf"] E["aarch64-unknown-none-softfloat"] F["Linux DevelopmentUnit Testing"] G["Bare-metal x86_64Build Verification"] H["RISC-V EmbeddedBuild Verification"] I["ARM64 EmbeddedBuild Verification"] A --> B A --> C A --> D A --> E B --> F C --> G D --> H E --> I
Unit tests execute only on x86_64-unknown-linux-gnu
due to the no-std
nature of other targets and the lack of standard library support for test execution.
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L29 - L30)
Continuous Integration Pipeline
CI Job Matrix Strategy
flowchart TD subgraph subGraph1["Documentation Pipeline"] C["Documentation Job"] H["cargo doc build"] I["GitHub Pages Deploy"] end subgraph subGraph0["CI Job Matrix"] B["CI Job"] D["Format Check"] E["Clippy Analysis"] F["Multi-Target Build"] G["Unit Tests"] end A["GitHub Push/PR Trigger"] J["x86_64-unknown-linux-gnu build"] K["x86_64-unknown-none build"] L["riscv64gc-unknown-none-elf build"] M["aarch64-unknown-none-softfloat build"] N["x86_64-unknown-linux-gnu tests only"] A --> B A --> C B --> D B --> E B --> F B --> G C --> H C --> I F --> J F --> K F --> L F --> M G --> N
CI Job Execution Steps:
- Environment Setup: Ubuntu latest with nightly Rust toolchain
- Format Verification:
cargo fmt --all -- --check
- Lint Analysis:
cargo clippy
with custom configuration - Cross-Compilation: Build for all four target architectures
- Test Execution: Unit tests on Linux target only
Documentation Job Features:
- Builds documentation with strict link checking
- Enforces complete documentation coverage
- Deploys to GitHub Pages on default branch pushes
- Generates redirect index page automatically
Sources: .github/workflows/ci.yml(L6 - L31) .github/workflows/ci.yml(L32 - L55)
Documentation Standards
Documentation Pipeline Configuration
flowchart TD A["RUSTDOCFLAGS Environment"] B["-D rustdoc::broken_intra_doc_links"] C["-D missing-docs"] D["cargo doc --no-deps --all-features"] E["Documentation Build"] F["Index Page Generation"] G["GitHub Pages Deployment"] H["Default Branch Push"] I["Pull Request"] J["Build Only"] A --> B A --> C D --> E E --> F F --> G H --> G I --> J
The documentation system enforces strict standards:
- Broken Link Detection: All intra-doc links must resolve correctly
- Complete Coverage: Missing documentation generates build failures
- Dependency Exclusion: Only project documentation is generated (
--no-deps
) - Feature Complete: All features are documented (
--all-features
)
The system automatically generates an index redirect page using the crate name extracted from cargo tree
output.
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L47 - L48)
Repository Structure and Ignored Files
Git Configuration
flowchart TD A["Repository Root"] B["Source Files"] C["Ignored Artifacts"] D["src/"] E["Cargo.toml"] F[".github/workflows/"] G["/target"] H["/.vscode"] I[".DS_Store"] J["Cargo.lock"] A --> B A --> C B --> D B --> E B --> F C --> G C --> H C --> I C --> J
Ignored Files and Directories:
/target
: Rust build artifacts and compiled binaries/.vscode
: Visual Studio Code workspace settings.DS_Store
: macOS filesystem metadata filesCargo.lock
: Dependency lock file (excluded for library crates)
The Cargo.lock
exclusion indicates this crate follows Rust library conventions, allowing downstream projects to resolve their own dependency versions.
Sources: .gitignore(L1 - L4)
Contributing Guidelines
Code Quality Requirements
All contributions must pass the complete CI pipeline:
- Format Compliance: Code must conform to
rustfmt
standards - Lint Compliance: Must pass
clippy
analysis with project-specific exceptions - Cross-Platform Compatibility: Must build successfully on all four target architectures
- Test Coverage: New functionality requires corresponding unit tests
- Documentation: All public APIs must include comprehensive documentation
Development Branch Strategy
The project uses a straightforward branching model:
- Default Branch: Primary development and release branch
- Feature Branches: Individual contributions via pull requests
- GitHub Pages: Automatic deployment from default branch
Documentation deployment occurs automatically on default branch pushes, ensuring the published documentation remains current with the latest code changes.
Sources: .github/workflows/ci.yml(L3) .github/workflows/ci.yml(L50)
Building and Testing
Relevant source files
This page documents the automated build and testing infrastructure for the timer_list
crate, including the CI/CD pipeline configuration, supported target architectures, and local development commands. The information covers both automated workflows triggered by Git events and manual testing procedures for local development.
For information about the project file structure and development environment setup, see Project Structure.
CI/CD Pipeline Overview
The timer_list
crate uses GitHub Actions for continuous integration and deployment. The pipeline consists of two main jobs that ensure code quality, cross-platform compatibility, and automated documentation deployment.
Pipeline Architecture
flowchart TD A["push/pull_request"] B["GitHub Actions Trigger"] C["ci job"] D["doc job"] E["ubuntu-latest runner"] F["ubuntu-latest runner"] G["Matrix Strategy"] H["rust-toolchain: nightly"] I["4 target architectures"] J["Format Check"] K["Clippy Linting"] L["Multi-target Build"] M["Unit Tests"] N["Documentation Build"] O["GitHub Pages Deploy"] P["x86_64-unknown-linux-gnu only"] Q["gh-pages branch"] A --> B B --> C B --> D C --> E D --> F E --> G E --> J E --> K E --> L E --> M F --> N F --> O G --> H G --> I M --> P O --> Q
The pipeline implements a fail-fast strategy set to false
, allowing all target builds to complete even if one fails, providing comprehensive feedback across all supported architectures.
Sources: .github/workflows/ci.yml(L1 - L56)
Build Matrix Configuration
The CI system uses a matrix strategy to test across multiple configurations simultaneously:
Component | Value |
---|---|
Rust Toolchain | nightly |
Runner OS | ubuntu-latest |
Fail Fast | false |
Target Architectures | 4 cross-compilation targets |
flowchart TD A["Matrix Strategy"] B["nightly toolchain"] C["Target Matrix"] D["x86_64-unknown-linux-gnu"] E["x86_64-unknown-none"] F["riscv64gc-unknown-none-elf"] G["aarch64-unknown-none-softfloat"] H["rust-src component"] I["clippy component"] J["rustfmt component"] A --> B A --> C B --> H B --> I B --> J C --> D C --> E C --> F C --> G
The pipeline installs additional Rust components (rust-src
, clippy
, rustfmt
) and configures targets for each architecture in the matrix.
Sources: .github/workflows/ci.yml(L8 - L19)
Target Architecture Support
The crate supports four target architectures, reflecting its use in embedded and operating system environments:
Architecture Details
Target | Purpose | Testing |
---|---|---|
x86_64-unknown-linux-gnu | Standard Linux development | Full testing enabled |
x86_64-unknown-none | Bare metal x86_64 | Build verification only |
riscv64gc-unknown-none-elf | RISC-V embedded systems | Build verification only |
aarch64-unknown-none-softfloat | ARM64 embedded systems | Build verification only |
flowchart TD A["timer_list Crate"] B["Cross-Platform Support"] C["x86_64-unknown-linux-gnu"] D["x86_64-unknown-none"] E["riscv64gc-unknown-none-elf"] F["aarch64-unknown-none-softfloat"] G["cargo test enabled"] H["cargo build only"] I["cargo build only"] J["cargo build only"] K["Unit test execution"] L["no-std compatibility"] M["RISC-V validation"] N["ARM64 validation"] A --> B B --> C B --> D B --> E B --> F C --> G D --> H E --> I F --> J G --> K H --> L I --> M J --> N
Unit tests execute only on x86_64-unknown-linux-gnu
due to the testing framework requirements, while other targets verify no-std
compatibility and cross-compilation success.
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L29 - L30)
Build and Test Commands
The CI pipeline executes a specific sequence of commands for each target architecture. These commands can also be run locally for development purposes.
Core CI Commands
sequenceDiagram participant DeveloperCI as "Developer/CI" participant RustToolchain as "Rust Toolchain" participant Cargo as "Cargo" participant TargetBinary as "Target Binary" DeveloperCI ->> RustToolchain: "rustc --version --verbose" RustToolchain -->> DeveloperCI: "Version information" DeveloperCI ->> Cargo: "cargo fmt --all -- --check" Cargo -->> DeveloperCI: "Format validation" DeveloperCI ->> Cargo: "cargo clippy --target TARGET --all-features" Cargo -->> DeveloperCI: "Lint results" DeveloperCI ->> Cargo: "cargo build --target TARGET --all-features" Cargo ->> TargetBinary: "Compile for target" TargetBinary -->> DeveloperCI: "Build artifacts" alt "x86_64-unknown-linux-gnu only" DeveloperCI ->> Cargo: "cargo test --target TARGET -- --nocapture" Cargo -->> DeveloperCI: "Test results" end
Local Development Commands
For local development, developers can run the same commands used in CI:
Command | Purpose |
---|---|
cargo fmt --all -- --check | Verify code formatting |
cargo clippy --all-features | Run linter with all features |
cargo build --all-features | Build with all features enabled |
cargo test -- --nocapture | Run unit tests with output |
The --all-features
flag ensures that all conditional compilation features are enabled during builds and linting.
Sources: .github/workflows/ci.yml(L22 - L30)
Documentation Generation
The doc
job handles automated documentation generation and deployment to GitHub Pages.
Documentation Workflow
flowchart TD A["doc job trigger"] B["ubuntu-latest"] C["Rust nightly setup"] D["cargo doc build"] E["RUSTDOCFLAGS validation"] F["broken_intra_doc_links check"] G["missing-docs check"] H["target/doc generation"] I["index.html redirect"] J["default branch?"] K["Deploy to gh-pages"] L["Skip deployment"] M["GitHub Pages"] A --> B B --> C C --> D D --> E D --> H E --> F E --> G H --> I I --> J J --> K J --> L K --> M
The documentation job includes strict validation using RUSTDOCFLAGS
to ensure documentation quality by failing on broken internal links and missing documentation.
Documentation Commands
The documentation generation process uses these key commands:
cargo doc --no-deps --all-features
- Generate documentation without dependenciescargo tree | head -1 | cut -d' ' -f1
- Extract crate name for redirect- Auto-generated
index.html
redirect to the main crate documentation
Sources: .github/workflows/ci.yml(L32 - L55)
Error Handling and Permissions
The CI configuration includes specific error handling strategies:
- Continue on Error: Documentation builds continue on error for non-default branches and non-PR events
- Permissions: The
doc
job requirescontents: write
permission for GitHub Pages deployment - Conditional Deployment: Pages deployment only occurs on pushes to the default branch
The clippy
command includes the flag -A clippy::new_without_default
to suppress specific linting warnings that are not relevant to this crate's design patterns.
Sources: .github/workflows/ci.yml(L25) .github/workflows/ci.yml(L36 - L37) .github/workflows/ci.yml(L45) .github/workflows/ci.yml(L50)
Project Structure
Relevant source files
This document provides an overview of the timer_list
repository organization, key configuration files, and development environment setup. It is intended for contributors who need to understand how the codebase is structured and how to set up a local development environment.
For information about building and testing the crate, see Building and Testing. For details about the core API implementation, see Core API Reference.
Repository Layout
The timer_list
repository follows a standard Rust crate structure with additional configuration for CI/CD and documentation.
Overall Repository Structure
flowchart TD subgraph workflows_directory["workflows/ Directory"] ci_yml["ci.yml"] end subgraph github_directory[".github/ Directory"] workflows_dir["workflows/"] end subgraph src_directory["src/ Directory"] lib_rs["lib.rs"] end subgraph timer_list_repository["timer_list Repository Root"] cargo_toml["Cargo.toml"] src_dir["src/"] readme["README.md"] gitignore[".gitignore"] github_dir[".github/"] end cargo_toml --> src_dir github_dir --> workflows_dir src_dir --> lib_rs workflows_dir --> ci_yml
Sources: Cargo.toml(L1 - L15) .gitignore(L1 - L5)
File Organization Principles
The repository follows these organizational principles:
Directory/File | Purpose | Key Contents |
---|---|---|
Cargo.toml | Package configuration | Dependencies, metadata, build settings |
src/lib.rs | Core implementation | TimerList,TimerEventtrait, main API |
.gitignore | Version control exclusions | Build artifacts, IDE files, OS files |
.github/workflows/ | CI/CD automation | Format checking, linting, multi-arch builds |
README.md | Project documentation | Usage examples, feature overview |
Sources: Cargo.toml(L1 - L15) .gitignore(L1 - L5)
Core Configuration Files
Package Configuration
The Cargo.toml
file defines the crate's identity, dependencies, and metadata:
flowchart TD subgraph cargo_toml_structure["Cargo.toml Structure"] name_field["name = timer_list"] package_section["[package]"] dependencies_section["[dependencies]"] end subgraph package_metadata["Package Metadata"] name_field["name = timer_list"] version_field["version = 0.1.0"] edition_field["edition = 2021"] author_field["authors"] description_field["description"] license_field["license"] homepage_field["homepage"] repository_field["repository"] documentation_field["documentation"] keywords_field["keywords"] categories_field["categories"] package_section["[package]"] end empty_deps["(empty)"] dependencies_section --> empty_deps
Sources: Cargo.toml(L1 - L15)
Key Package Metadata
Field | Value | Purpose |
---|---|---|
name | "timer_list" | Crate identifier for cargo and crates.io |
version | "0.1.0" | Semantic version following semver |
edition | "2021" | Rust edition for language features |
description | Timer events description | Brief summary for crate discovery |
license | Triple license | GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0 |
categories | ["no-std", "data-structures", "date-and-time"] | Categorization for crates.io |
keywords | ["arceos", "timer", "events"] | Search tags for discoverability |
Sources: Cargo.toml(L2 - L12)
External References
The package configuration includes several external links:
- Homepage: Cargo.toml(L8) points to the ArceOS organization
- Repository: Cargo.toml(L9) points to the specific timer_list repository
- Documentation: Cargo.toml(L10) points to docs.rs for API documentation
Sources: Cargo.toml(L8 - L10)
Dependencies
The crate currently has no external dependencies, as shown by the empty [dependencies]
section at Cargo.toml(L14 - L15) This supports the crate's no-std
design and minimal footprint for embedded environments.
Sources: Cargo.toml(L14 - L15)
Git Configuration
The .gitignore
file excludes development artifacts and environment-specific files:
flowchart TD subgraph ignored_patterns["Ignored Patterns"] target_dir["/target"] vscode_dir["/.vscode"] ds_store[".DS_Store"] cargo_lock["Cargo.lock"] end subgraph git_ignore_categories["Git Ignore Categories"] build_artifacts["Build Artifacts"] ide_files["IDE Files"] os_files["OS Files"] security_files["Security Files"] end build_artifacts --> cargo_lock build_artifacts --> target_dir ide_files --> vscode_dir os_files --> ds_store
Sources: .gitignore(L1 - L5)
Ignored File Types
Pattern | Category | Reason for Exclusion |
---|---|---|
/target | Build artifacts | Cargo build output directory |
/.vscode | IDE configuration | Visual Studio Code workspace settings |
.DS_Store | OS metadata | macOS directory metadata files |
Cargo.lock | Dependency lockfile | Generated file, not needed for libraries |
Sources: .gitignore(L1 - L4)
Development Environment Setup
Prerequisites
To contribute to the timer_list
crate, developers need:
- Rust Toolchain: Edition 2021 or later
- Target Support: Multiple architectures supported by CI
- Development Tools:
cargo fmt
,cargo clippy
for code quality
Local Development Workflow
sequenceDiagram participant Developer as "Developer" participant LocalEnvironment as "Local Environment" participant GitRepository as "Git Repository" participant CIPipeline as "CI Pipeline" Developer ->> LocalEnvironment: "cargo check" Developer ->> LocalEnvironment: "cargo fmt" Developer ->> LocalEnvironment: "cargo clippy" Developer ->> LocalEnvironment: "cargo test" Developer ->> GitRepository: "git commit & push" GitRepository ->> CIPipeline: "trigger workflows" CIPipeline ->> CIPipeline: "multi-arch builds" CIPipeline ->> CIPipeline: "documentation generation"
Sources: Cargo.toml(L1 - L15) .gitignore(L1 - L5)
File Modification Guidelines
When modifying the repository structure:
- Core Implementation: Changes to
src/lib.rs
affect the main API - Package Configuration: Changes to
Cargo.toml
affect crate metadata and dependencies - CI Configuration: Changes to
.github/workflows/
affect automated testing - Documentation: Changes to
README.md
affect project overview and examples
The minimal file structure supports the crate's focused purpose as a lightweight timer event management system for no-std
environments.
Sources: Cargo.toml(L12) .gitignore(L1 - L5)
Overview
Relevant source files
This document provides a comprehensive overview of the slab_allocator
crate, a hybrid memory allocation system designed for no_std
environments. The allocator combines the performance benefits of slab allocation for small, fixed-size blocks with the flexibility of buddy allocation for larger, variable-size requests.
For detailed implementation specifics of individual components, see Core Architecture. For practical usage examples and setup instructions, see Getting Started.
Purpose and Target Environment
The slab_allocator
crate addresses the critical need for efficient memory management in resource-constrained environments where the standard library is unavailable. It targets embedded systems, kernel development, and real-time applications that require deterministic allocation performance.
The allocator implements a hybrid strategy that provides O(1) allocation for blocks ≤ 4096 bytes through slab allocation, while falling back to a buddy system allocator for larger requests. This design balances performance predictability with memory utilization efficiency.
Sources: Cargo.toml(L8 - L9) src/lib.rs(L1 - L3)
System Architecture
The allocator's architecture centers around the Heap
struct, which orchestrates allocation requests between multiple specialized components based on request size and alignment requirements.
Core Components Mapping
flowchart TD subgraph subGraph4["Internal Slab Components"] FreeBlockList["FreeBlockList<BLK_SIZE>(src/slab.rs:65)"] FreeBlock["FreeBlock(src/slab.rs:112)"] end subgraph subGraph3["Large Block Allocation"] BuddyAllocator["buddy_allocatorbuddy_system_allocator::Heap<32>(src/lib.rs:48)"] end subgraph subGraph2["Slab Allocation System"] Slab64["slab_64_bytes: Slab<64>(src/lib.rs:41)"] Slab128["slab_128_bytes: Slab<128>(src/lib.rs:42)"] Slab256["slab_256_bytes: Slab<256>(src/lib.rs:43)"] Slab512["slab_512_bytes: Slab<512>(src/lib.rs:44)"] Slab1024["slab_1024_bytes: Slab<1024>(src/lib.rs:45)"] Slab2048["slab_2048_bytes: Slab<2048>(src/lib.rs:46)"] Slab4096["slab_4096_bytes: Slab<4096>(src/lib.rs:47)"] end subgraph subGraph1["Allocation Routing"] HeapAllocator["HeapAllocator enum(src/lib.rs:27)"] layout_to_allocator["layout_to_allocator()(src/lib.rs:208)"] end subgraph subGraph0["Primary Interface"] Heap["Heap(src/lib.rs:40)"] end FreeBlockList --> FreeBlock Heap --> HeapAllocator Heap --> layout_to_allocator Slab1024 --> FreeBlockList Slab128 --> FreeBlockList Slab2048 --> FreeBlockList Slab256 --> FreeBlockList Slab4096 --> FreeBlockList Slab512 --> FreeBlockList Slab64 --> FreeBlockList layout_to_allocator --> BuddyAllocator layout_to_allocator --> Slab1024 layout_to_allocator --> Slab128 layout_to_allocator --> Slab2048 layout_to_allocator --> Slab256 layout_to_allocator --> Slab4096 layout_to_allocator --> Slab512 layout_to_allocator --> Slab64
Sources: src/lib.rs(L40 - L49) src/lib.rs(L27 - L36) src/slab.rs(L4 - L7) src/slab.rs(L65 - L68)
Allocation Decision Flow
The system routes allocation requests through a deterministic decision tree based on the Layout
parameter's size and alignment requirements.
flowchart TD Request["allocate(layout: Layout)(src/lib.rs:135)"] Router["layout_to_allocator(&layout)(src/lib.rs:208-226)"] SizeCheck["Size > 4096?(src/lib.rs:209)"] Buddy["HeapAllocator::BuddyAllocator(src/lib.rs:210)"] AlignCheck["Check size and alignmentconstraints (src/lib.rs:211-225)"] Slab64Check["≤64 && align≤64?(src/lib.rs:211-212)"] Slab128Check["≤128 && align≤128?(src/lib.rs:213-214)"] Slab256Check["≤256 && align≤256?(src/lib.rs:215-216)"] Slab512Check["≤512 && align≤512?(src/lib.rs:217-218)"] Slab1024Check["≤1024 && align≤1024?(src/lib.rs:219-220)"] Slab2048Check["≤2048 && align≤2048?(src/lib.rs:221-222)"] SlabDefault["HeapAllocator::Slab4096Bytes(src/lib.rs:224)"] Slab64Enum["HeapAllocator::Slab64Bytes"] Slab128Enum["HeapAllocator::Slab128Bytes"] Slab256Enum["HeapAllocator::Slab256Bytes"] Slab512Enum["HeapAllocator::Slab512Bytes"] Slab1024Enum["HeapAllocator::Slab1024Bytes"] Slab2048Enum["HeapAllocator::Slab2048Bytes"] BuddyImpl["buddy_allocator.alloc()(src/lib.rs:158-162)"] SlabImpl["slab_X_bytes.allocate()(src/lib.rs:137-157)"] AlignCheck --> Slab1024Check AlignCheck --> Slab128Check AlignCheck --> Slab2048Check AlignCheck --> Slab256Check AlignCheck --> Slab512Check AlignCheck --> Slab64Check AlignCheck --> SlabDefault Buddy --> BuddyImpl Request --> Router Router --> SizeCheck SizeCheck --> AlignCheck SizeCheck --> Buddy Slab1024Check --> Slab1024Enum Slab1024Enum --> SlabImpl Slab128Check --> Slab128Enum Slab128Enum --> SlabImpl Slab2048Check --> Slab2048Enum Slab2048Enum --> SlabImpl Slab256Check --> Slab256Enum Slab256Enum --> SlabImpl Slab512Check --> Slab512Enum Slab512Enum --> SlabImpl Slab64Check --> Slab64Enum Slab64Enum --> SlabImpl SlabDefault --> SlabImpl
Sources: src/lib.rs(L135 - L164) src/lib.rs(L208 - L226)
Memory Management Strategy
The allocator employs a two-tier memory management approach that optimizes for different allocation patterns:
Allocation Size | Strategy | Allocator | Time Complexity | Use Case |
---|---|---|---|---|
≤ 64 bytes | Fixed-size slab | Slab<64> | O(1) | Small objects, metadata |
65-128 bytes | Fixed-size slab | Slab<128> | O(1) | Small structures |
129-256 bytes | Fixed-size slab | Slab<256> | O(1) | Medium structures |
257-512 bytes | Fixed-size slab | Slab<512> | O(1) | Small buffers |
513-1024 bytes | Fixed-size slab | Slab<1024> | O(1) | Medium buffers |
1025-2048 bytes | Fixed-size slab | Slab<2048> | O(1) | Large structures |
2049-4096 bytes | Fixed-size slab | Slab<4096> | O(1) | Page-sized allocations |
> 4096 bytes | Buddy system | buddy_system_allocator | O(log n) | Large buffers, dynamic data |
Sources: src/lib.rs(L208 - L226) src/lib.rs(L41 - L48)
Dynamic Growth Mechanism
When a slab exhausts its available blocks, the allocator automatically requests additional memory from the buddy allocator in fixed-size chunks defined by SET_SIZE
(64 blocks per growth operation).
flowchart TD SlabEmpty["Slab free_block_list empty(src/slab.rs:42)"] RequestGrowth["Request SET_SIZE * BLK_SIZEfrom buddy allocator(src/slab.rs:43-47)"] GrowSlab["grow() creates new FreeBlockList(src/slab.rs:26-33)"] MergeBlocks["Merge new blocks intoexisting free_block_list(src/slab.rs:30-32)"] ReturnBlock["Return first available block(src/slab.rs:49)"] GrowSlab --> MergeBlocks MergeBlocks --> ReturnBlock RequestGrowth --> GrowSlab SlabEmpty --> RequestGrowth
Sources: src/slab.rs(L35 - L55) src/slab.rs(L26 - L33) src/lib.rs(L24)
Platform Support and Testing
The allocator supports multiple embedded architectures and maintains compatibility through extensive CI/CD testing:
Target Platform | Purpose | Test Coverage |
---|---|---|
x86_64-unknown-linux-gnu | Development/testing | Full unit tests |
x86_64-unknown-none | Bare metal x86_64 | Build verification |
riscv64gc-unknown-none-elf | RISC-V embedded | Build verification |
aarch64-unknown-none-softfloat | ARM64 embedded | Build verification |
The test suite includes allocation patterns from simple double-usize allocations to complex multi-size scenarios with varying alignment requirements, ensuring robust behavior across different usage patterns.
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L28 - L30) src/tests.rs(L39 - L163)
Key Features and Constraints
Design Features
no_std
compatibility: No dependency on the standard library- Deterministic performance: O(1) allocation for blocks ≤ 4096 bytes
- Automatic growth: Dynamic expansion when slabs become exhausted
- Memory statistics: Runtime visibility into allocation patterns
- Multiple alignment support: Handles various alignment requirements efficiently
System Constraints
- Minimum heap size: 32KB (
MIN_HEAP_SIZE = 0x8000
) - Page alignment requirement: Heap start address must be 4096-byte aligned
- Growth granularity: Memory added in 4096-byte increments
- Slab threshold: Fixed 4096-byte boundary between slab and buddy allocation
Sources: src/lib.rs(L25) src/lib.rs(L59 - L71) src/lib.rs(L95 - L106)
Getting Started
Relevant source files
This page provides a practical introduction to using the slab_allocator
crate, covering dependency setup, basic initialization, and simple usage patterns. The examples shown here demonstrate core allocation patterns using code from the test suite. For detailed architectural information, see Core Architecture. For complete API documentation, see API Reference.
Adding the Dependency
Add the slab_allocator
crate to your Cargo.toml
file. The crate is designed for no_std
environments and requires the allocator_api
feature.
[dependencies]
slab_allocator = "0.3.1"
The crate automatically includes buddy_system_allocator
version 0.10 as a dependency for handling large allocations over 4096 bytes.
Sources: Cargo.toml(L1 - L13)
Basic Setup and Initialization
Memory Requirements
The heap allocator requires page-aligned memory with specific size constraints defined in the core library:
Requirement | Value | Purpose |
---|---|---|
Minimum heap size | 32KB (MIN_HEAP_SIZE = 0x8000) | Ensures sufficient space for slab initialization |
Address alignment | 4096 bytes | Page boundary alignment for memory management |
Size alignment | Multiple ofMIN_HEAP_SIZE | Simplifies buddy allocator integration |
Heap Initialization Flow
flowchart TD A["Memory Region"] B["Heap::new()"] C["Address Validation"] D["Size Validation"] E["Initialize Slabs"] F["Initialize buddy_system_allocator::Heap"] G["Ready for Allocation"] H["heap_start_addr % 4096 == 0"] I["heap_size >= MIN_HEAP_SIZE"] J["heap_size % MIN_HEAP_SIZE == 0"] K["Slab<64>::new()"] L["Slab<128>::new()"] M["...up to Slab<4096>"] N["buddy.init(heap_start_addr, heap_size)"] A --> B B --> C C --> D C --> H D --> E D --> I D --> J E --> F E --> K E --> L E --> M F --> G F --> N
Sources: src/lib.rs(L52 - L86) src/lib.rs(L24 - L26)
Basic Initialization Example
The test suite demonstrates standard heap initialization patterns:
// From test suite - basic heap setup
const HEAP_SIZE: usize = 16 * 4096; // 64KB
#[repr(align(4096))]
struct TestHeap {
heap_space: [u8; HEAP_SIZE],
}
fn new_heap() -> Heap {
let test_heap = TestHeap {
heap_space: [0u8; HEAP_SIZE],
};
unsafe {
Heap::new(&test_heap.heap_space[0] as *const u8 as usize, HEAP_SIZE)
}
}
Sources: src/tests.rs(L5) src/tests.rs(L8 - L24)
Simple Usage Examples
Basic Allocation and Deallocation
The fundamental allocation pattern uses Layout
to specify size and alignment requirements:
use alloc::alloc::Layout;
use core::mem::{size_of, align_of};
let mut heap = new_heap();
let layout = Layout::from_size_align(size_of::<usize>() * 2, align_of::<usize>()).unwrap();
// Allocate memory
let addr = heap.allocate(layout.clone()).unwrap();
// Use the memory
unsafe {
*(addr as *mut (usize, usize)) = (0xdeafdeadbeafbabe, 0xdeafdeadbeafbabe);
// Deallocate when done
heap.deallocate(addr, layout);
}
Sources: src/tests.rs(L47 - L68)
Multi-Size Allocation Example
The allocator efficiently handles multiple allocation sizes simultaneously:
let mut heap = new_heap();
let base_size = size_of::<usize>();
let base_align = align_of::<usize>();
let layout_1 = Layout::from_size_align(base_size * 2, base_align).unwrap();
let layout_2 = Layout::from_size_align(base_size * 3, base_align).unwrap();
let layout_3 = Layout::from_size_align(base_size * 3, base_align * 8).unwrap();
let layout_4 = Layout::from_size_align(base_size * 10, base_align).unwrap();
let x = heap.allocate(layout_1.clone()).unwrap();
let y = heap.allocate(layout_2.clone()).unwrap();
let z = heap.allocate(layout_3.clone()).unwrap();
Sources: src/tests.rs(L90 - L117)
Allocation Strategy Overview
Layout-to-Allocator Routing
The heap automatically routes allocation requests to the appropriate allocator based on size and alignment requirements:
flowchart TD A["Layout::from_size_align()"] B["Heap::allocate()"] C["Heap::layout_to_allocator()"] D["layout.size() > 4096?"] E["HeapAllocator::BuddyAllocator"] F["Select Slab Allocator"] G["size <= 64 && align <= 64?"] H["HeapAllocator::Slab64Bytes"] I["size <= 128 && align <= 128?"] J["HeapAllocator::Slab128Bytes"] K["... up to Slab4096Bytes"] L["slab_64_bytes.allocate()"] M["slab_128_bytes.allocate()"] N["buddy_allocator.alloc()"] O["O(1) allocation"] P["O(n) allocation"] A --> B B --> C C --> D D --> E D --> F E --> N F --> G G --> H G --> I H --> L I --> J I --> K J --> M L --> O M --> O N --> P
Sources: src/lib.rs(L208 - L226) src/lib.rs(L135 - L164)
Slab Size Categories
The allocator maintains seven fixed-size slab categories, each optimized for specific allocation patterns:
Slab Type | Block Size | Target Use Cases |
---|---|---|
Slab<64> | 64 bytes | Small structures, basic types |
Slab<128> | 128 bytes | Medium structures |
Slab<256> | 256 bytes | Larger data structures |
Slab<512> | 512 bytes | Small buffers |
Slab<1024> | 1024 bytes | Medium buffers |
Slab<2048> | 2048 bytes | Large buffers |
Slab<4096> | 4096 bytes | Page-sized allocations |
Buddy Allocator | >4096 bytes | Variable large allocations |
Sources: src/lib.rs(L27 - L36) src/lib.rs(L40 - L49)
Memory Statistics
The heap provides real-time memory usage statistics:
let total = heap.total_bytes(); // Total heap capacity
let used = heap.used_bytes(); // Currently allocated bytes
let available = heap.available_bytes(); // Free bytes remaining
let (min_size, max_size) = heap.usable_size(layout); // Allocation bounds
Sources: src/lib.rs(L228 - L255) src/lib.rs(L192 - L205)
Error Handling
The allocator returns AllocError
for failed allocations, typically due to insufficient memory:
let layout = Layout::from_size_align(HEAP_SIZE + 1, align_of::<usize>()).unwrap();
let result = heap.allocate(layout);
assert!(result.is_err()); // Out of memory
Sources: src/tests.rs(L40 - L45)
Next Steps
- For detailed architecture information including slab implementation details, see Core Architecture
- For complete API documentation and advanced usage patterns, see API Reference
- For comprehensive testing examples and validation approaches, see Testing and Validation
- For development workflow and contribution guidelines, see Development Workflow
Sources: src/lib.rs(L1 - L257) src/tests.rs(L1 - L164)
Core Architecture
Relevant source files
This document provides a comprehensive overview of the slab allocator's hybrid memory management architecture. It explains the fundamental design decisions, component interactions, and allocation strategies that enable efficient memory management in no_std
environments. For detailed implementation specifics of individual components, see Heap Allocator Design and Slab Implementation.
Hybrid Allocation Strategy
The slab allocator implements a two-tier memory allocation system that optimizes for both small fixed-size allocations and large variable-size allocations. The core design principle is to route allocation requests to the most appropriate allocator based on size and alignment requirements.
Allocation Decision Tree
flowchart TD A["Layout::size() > 4096"] B["Size Check"] C["buddy_system_allocator::Heap<32>"] D["Size and Alignment Analysis"] E["size <= 64 && align <= 64"] F["Slab<64>"] G["size <= 128 && align <= 128"] H["Slab<128>"] I["size <= 256 && align <= 256"] J["Slab<256>"] K["size <= 512 && align <= 512"] L["Slab<512>"] M["size <= 1024 && align <= 1024"] N["Slab<1024>"] O["size <= 2048 && align <= 2048"] P["Slab<2048>"] Q["Slab<4096>"] A --> B B --> C B --> D D --> E E --> F E --> G G --> H G --> I I --> J I --> K K --> L K --> M M --> N M --> O O --> P O --> Q
Sources: src/lib.rs(L207 - L226)
Component Architecture
The Heap
struct orchestrates multiple specialized allocators to provide a unified memory management interface:
flowchart TD subgraph subGraph1["HeapAllocator Enum"] J["Slab64Bytes"] K["Slab128Bytes"] L["Slab256Bytes"] M["Slab512Bytes"] N["Slab1024Bytes"] O["Slab2048Bytes"] P["Slab4096Bytes"] Q["BuddyAllocator"] end subgraph subGraph0["Heap Struct"] A["pub struct Heap"] B["slab_64_bytes: Slab<64>"] C["slab_128_bytes: Slab<128>"] D["slab_256_bytes: Slab<256>"] E["slab_512_bytes: Slab<512>"] F["slab_1024_bytes: Slab<1024>"] G["slab_2048_bytes: Slab<2048>"] H["slab_4096_bytes: Slab<4096>"] I["buddy_allocator: buddy_system_allocator::Heap<32>"] end R["layout_to_allocator()"] A --> B A --> C A --> D A --> E A --> F A --> G A --> H A --> I J --> B K --> C L --> D M --> E N --> F O --> G P --> H Q --> I R --> J R --> K R --> L R --> M R --> N R --> O R --> P R --> Q
Sources: src/lib.rs(L40 - L49) src/lib.rs(L27 - L36)
Memory Organization and Block Management
Each slab allocator manages fixed-size blocks through a linked list of free blocks. The system uses a hybrid approach where slabs can dynamically grow by requesting memory from the buddy allocator.
Slab Internal Structure
flowchart TD subgraph subGraph1["FreeBlockList Operations"] E["pop()"] F["Returns available block"] G["push()"] H["Adds freed block to list"] I["new()"] J["Initializes from memory range"] end subgraph Slab<BLK_SIZE>["Slab"] A["free_block_list: FreeBlockList"] B["head: Option<&'static mut FreeBlock>"] C["len: usize"] D["total_blocks: usize"] end subgraph subGraph2["Dynamic Growth"] K["allocate() fails"] L["Request SET_SIZE * BLK_SIZE from buddy"] M["grow()"] N["Create new FreeBlockList"] O["Transfer blocks to main list"] end A --> B A --> C A --> E A --> G B --> F B --> H E --> F G --> H I --> J K --> L L --> M M --> N N --> O
Sources: src/slab.rs(L4 - L7) src/slab.rs(L65 - L68)
Allocation Flow and Integration Points
The allocation process demonstrates the tight integration between slab allocators and the buddy allocator, with automatic fallback and growth mechanisms.
Allocation Process Flow
sequenceDiagram participant Client as Client participant Heap as Heap participant Slab as Slab participant FreeBlockList as FreeBlockList participant BuddyAllocator as BuddyAllocator Client ->> Heap: allocate(layout) Heap ->> Heap: layout_to_allocator(layout) alt Size <= 4096 Heap ->> Slab: allocate(layout, buddy) Slab ->> FreeBlockList: pop() alt Free block available FreeBlockList -->> Slab: Some(block) Slab -->> Heap: Ok(block.addr()) else No free blocks Slab ->> BuddyAllocator: alloc(SET_SIZE * BLK_SIZE) BuddyAllocator -->> Slab: Ok(ptr) Slab ->> Slab: grow(ptr, size) Slab ->> FreeBlockList: pop() FreeBlockList -->> Slab: Some(block) Slab -->> Heap: Ok(block.addr()) end Heap -->> Client: Ok(address) else Size > 4096 Heap ->> BuddyAllocator: alloc(layout) BuddyAllocator -->> Heap: Ok(ptr) Heap -->> Client: Ok(address) end
Sources: src/lib.rs(L135 - L164) src/slab.rs(L35 - L55)
Performance Characteristics and Design Rationale
Allocation Complexity
Allocation Type | Time Complexity | Space Overhead | Use Case |
---|---|---|---|
Slab (≤ 4096 bytes) | O(1) | Fixed per slab | Frequent small allocations |
Buddy (> 4096 bytes) | O(log n) | Variable | Large or variable-size allocations |
Slab growth | O(SET_SIZE) | Batch allocation | Slab expansion |
Key Design Constants
const SET_SIZE: usize = 64; // Blocks per growth operation
const MIN_HEAP_SIZE: usize = 0x8000; // Minimum heap size (32KB)
The SET_SIZE
constant controls the granularity of slab growth operations. When a slab runs out of free blocks, it requests SET_SIZE * BLK_SIZE
bytes from the buddy allocator, ensuring efficient batch allocation while minimizing buddy allocator overhead.
Sources: src/lib.rs(L24 - L25) src/slab.rs(L44 - L48)
Memory Statistics and Monitoring
The architecture provides comprehensive memory usage tracking across both allocation subsystems:
Statistics Calculation
flowchart TD subgraph subGraph1["Per-Slab Calculation"] I["slab.total_blocks() * BLOCK_SIZE"] J["slab.used_blocks() * BLOCK_SIZE"] end subgraph subGraph0["Memory Metrics"] A["total_bytes()"] B["Sum of all slab capacities"] C["buddy_allocator.stats_total_bytes()"] D["used_bytes()"] E["Sum of allocated slab blocks"] F["buddy_allocator.stats_alloc_actual()"] G["available_bytes()"] H["total_bytes() - used_bytes()"] end A --> B A --> C B --> I D --> E D --> F E --> J G --> H
Sources: src/lib.rs(L228 - L255)
Integration with Buddy Allocator
The system maintains a symbiotic relationship with the buddy_system_allocator
crate, using it both as a fallback for large allocations and as a memory provider for slab growth operations.
Buddy Allocator Usage Patterns
- Direct allocation: Requests over 4096 bytes bypass slabs entirely
- Slab growth: Slabs request memory chunks for expansion
- Memory initialization: Initial heap setup uses buddy allocator
- Memory extension: Additional memory regions added through buddy allocator
The buddy allocator is configured with a 32-level tree (Heap<32>
), providing efficient allocation for a wide range of block sizes while maintaining reasonable memory overhead.
Sources: src/lib.rs(L48) src/lib.rs(L80 - L85) src/lib.rs(L158 - L163)
Heap Allocator Design
Relevant source files
This document covers the design and implementation of the main Heap
struct, which serves as the primary interface for memory allocation in the slab_allocator crate. It explains the hybrid allocation strategy, routing logic, and integration with the buddy system allocator. For details about individual slab implementations and their internal mechanics, see Slab Implementation. For API usage examples and method documentation, see API Reference.
Core Components Overview
The heap allocator is built around the Heap
struct, which coordinates between multiple fixed-size slab allocators and a buddy system allocator for larger requests.
Heap Structure Components
flowchart TD subgraph subGraph0["Heap Struct Fields"] C1["SET_SIZE: usize = 64"] C2["MIN_HEAP_SIZE: usize = 0x8000"] B["buddy_allocator: buddy_system_allocator::Heap<32>"] subgraph subGraph1["HeapAllocator Enum Variants"] E["HeapAllocator"] E64["Slab64Bytes"] E128["Slab128Bytes"] E256["Slab256Bytes"] E512["Slab512Bytes"] E1024["Slab1024Bytes"] E2048["Slab2048Bytes"] E4096["Slab4096Bytes"] EB["BuddyAllocator"] H["Heap"] S64["slab_64_bytes: Slab<64>"] S128["slab_128_bytes: Slab<128>"] S256["slab_256_bytes: Slab<256>"] S512["slab_512_bytes: Slab<512>"] S1024["slab_1024_bytes: Slab<1024>"] S2048["slab_2048_bytes: Slab<2048>"] S4096["slab_4096_bytes: Slab<4096>"] subgraph Constants["Constants"] C1["SET_SIZE: usize = 64"] C2["MIN_HEAP_SIZE: usize = 0x8000"] end end end E --> E1024 E --> E128 E --> E2048 E --> E256 E --> E4096 E --> E512 E --> E64 E --> EB H --> B H --> S1024 H --> S128 H --> S2048 H --> S256 H --> S4096 H --> S512 H --> S64
Sources: src/lib.rs(L40 - L49) src/lib.rs(L27 - L36) src/lib.rs(L24 - L25)
The Heap
struct contains seven slab allocators, each handling fixed-size blocks, plus a buddy_system_allocator::Heap
for variable-size allocations. The HeapAllocator
enum provides a type-safe way to route allocation requests to the appropriate allocator.
Allocation Routing Strategy
The heart of the heap allocator's design is the layout_to_allocator
function, which determines which allocator to use based on the requested layout's size and alignment requirements.
Layout Routing Logic
flowchart TD L["Layout { size, align }"] Check1["size > 4096?"] BA["HeapAllocator::BuddyAllocator"] Check64["size ≤ 64 && align ≤ 64?"] S64["HeapAllocator::Slab64Bytes"] Check128["size ≤ 128 && align ≤ 128?"] S128["HeapAllocator::Slab128Bytes"] Check256["size ≤ 256 && align ≤ 256?"] S256["HeapAllocator::Slab256Bytes"] Check512["size ≤ 512 && align ≤ 512?"] S512["HeapAllocator::Slab512Bytes"] Check1024["size ≤ 1024 && align ≤ 1024?"] S1024["HeapAllocator::Slab1024Bytes"] Check2048["size ≤ 2048 && align ≤ 2048?"] S2048["HeapAllocator::Slab2048Bytes"] S4096["HeapAllocator::Slab4096Bytes"] Check1 --> BA Check1 --> Check64 Check1024 --> Check2048 Check1024 --> S1024 Check128 --> Check256 Check128 --> S128 Check2048 --> S2048 Check2048 --> S4096 Check256 --> Check512 Check256 --> S256 Check512 --> Check1024 Check512 --> S512 Check64 --> Check128 Check64 --> S64 L --> Check1
Sources: src/lib.rs(L208 - L226)
The routing logic follows a cascading pattern where both size and alignment constraints must be satisfied. This ensures that allocations are placed in the smallest slab that can accommodate both the requested size and alignment requirements.
Memory Management Workflow
The allocation and deallocation processes follow a consistent pattern of routing through the HeapAllocator
enum to the appropriate underlying allocator.
Allocation Process Flow
sequenceDiagram participant ClientCode as "Client Code" participant Heapallocate as "Heap::allocate" participant layout_to_allocator as "layout_to_allocator" participant Slaballocate as "Slab::allocate" participant buddy_allocator as "buddy_allocator" ClientCode ->> Heapallocate: "allocate(layout)" Heapallocate ->> layout_to_allocator: "layout_to_allocator(&layout)" layout_to_allocator -->> Heapallocate: "HeapAllocator variant" alt "Slab allocation" Heapallocate ->> Slaballocate: "slab.allocate(layout, &mut buddy_allocator)" Slaballocate ->> Slaballocate: "Check free blocks" alt "No free blocks" Slaballocate ->> buddy_allocator: "Request memory for growth" buddy_allocator -->> Slaballocate: "Memory region" Slaballocate ->> Slaballocate: "Initialize new blocks" end Slaballocate -->> Heapallocate: "Result<usize, AllocError>" else "Buddy allocation" Heapallocate ->> buddy_allocator: "buddy_allocator.alloc(layout)" buddy_allocator -->> Heapallocate: "Result<NonNull<u8>, AllocError>" end Heapallocate -->> ClientCode: "Result<usize, AllocError>"
Sources: src/lib.rs(L135 - L164) src/lib.rs(L177 - L190)
Deallocation Process Flow
sequenceDiagram participant ClientCode as "Client Code" participant Heapdeallocate as "Heap::deallocate" participant layout_to_allocator as "layout_to_allocator" participant Slabdeallocate as "Slab::deallocate" participant buddy_allocator as "buddy_allocator" ClientCode ->> Heapdeallocate: "deallocate(ptr, layout)" Heapdeallocate ->> layout_to_allocator: "layout_to_allocator(&layout)" layout_to_allocator -->> Heapdeallocate: "HeapAllocator variant" alt "Slab deallocation" Heapdeallocate ->> Slabdeallocate: "slab.deallocate(ptr)" Slabdeallocate ->> Slabdeallocate: "Add block to free list" Slabdeallocate -->> Heapdeallocate: "void" else "Buddy deallocation" Heapdeallocate ->> buddy_allocator: "buddy_allocator.dealloc(ptr, layout)" buddy_allocator -->> Heapdeallocate: "void" end
Sources: src/lib.rs(L177 - L190)
Performance Characteristics
The hybrid design provides different performance guarantees depending on allocation size:
Allocation Size | Allocator Used | Time Complexity | Characteristics |
---|---|---|---|
≤ 4096 bytes | Slab allocator | O(1) | Fixed-size blocks, predictable performance |
> 4096 bytes | Buddy allocator | O(n) | Variable-size blocks, flexible but slower |
Sources: src/lib.rs(L134 - L135) src/lib.rs(L172 - L173)
The O(1) performance for small allocations is achieved because slab allocators maintain free lists of pre-allocated blocks. The O(n) complexity for large allocations reflects the buddy allocator's tree-based search for appropriately sized free blocks.
Memory Statistics and Monitoring
The Heap
struct provides comprehensive memory usage statistics by aggregating data from all component allocators.
Statistics Methods
flowchart TD subgraph subGraph0["Memory Statistics API"] TB["total_bytes()"] Calc1["Sum of slab total_blocks() * block_size"] Calc2["Unsupported markdown: list"] UB["used_bytes()"] Calc3["Sum of slab used_blocks() * block_size"] Calc4["Unsupported markdown: list"] AB["available_bytes()"] Calc5["total_bytes() - used_bytes()"] US["usable_size(layout)"] Route["Route by HeapAllocator"] SlabSize["Slab: (layout.size(), slab_size)"] BuddySize["Buddy: (layout.size(), layout.size())"] end AB --> Calc5 Route --> BuddySize Route --> SlabSize TB --> Calc1 TB --> Calc2 UB --> Calc3 UB --> Calc4 US --> Route
Sources: src/lib.rs(L229 - L255) src/lib.rs(L194 - L205)
The statistics methods provide real-time visibility into memory usage across both slab and buddy allocators, enabling monitoring and debugging of allocation patterns.
Integration Points
The heap allocator serves as a coordinator between the slab allocators and the buddy system allocator, handling several key integration scenarios:
Slab Growth Mechanism
When a slab runs out of free blocks, it requests additional memory from the buddy allocator through the Slab::allocate
method. This allows slabs to grow dynamically based on demand while maintaining their fixed-block-size efficiency.
Memory Addition
The add_memory
and _grow
methods allow runtime expansion of the heap, with new memory being routed to the appropriate allocator:
Sources: src/lib.rs(L95 - L106) src/lib.rs(L116 - L129)
- Public
add_memory
: Adds memory directly to the buddy allocator - Private
_grow
: Routes memory to specific slabs based on theHeapAllocator
variant
This design enables flexible memory management where the heap can expand as needed while maintaining the performance characteristics of the hybrid allocation strategy.
Slab Implementation
Relevant source files
This document covers the internal implementation of the Slab
struct and its associated components, including the free block management system, allocation/deallocation mechanisms, and dynamic growth strategies. This is the core component that provides O(1) fixed-size block allocation within the hybrid memory allocator.
For information about how slabs integrate into the overall heap allocation strategy, see Heap Allocator Design. For complete API documentation, see API Reference.
Slab Structure Overview
The slab allocator is implemented as a generic struct that manages fixed-size memory blocks. Each slab is parameterized by its block size and maintains a linked list of free blocks for rapid allocation and deallocation.
Slab Core Components
flowchart TD subgraph subGraph0["Key Methods"] I["new()"] J["allocate()"] K["deallocate()"] L["grow()"] M["total_blocks()"] N["used_blocks()"] end A["Slab<const BLK_SIZE: usize>"] B["free_block_list: FreeBlockList<BLK_SIZE>"] C["total_blocks: usize"] D["len: usize"] E["head: Option<&'static mut FreeBlock>"] F["FreeBlock"] G["next: Option<&'static mut FreeBlock>"] H["addr() -> usize"] A --> B A --> C A --> I A --> J A --> K A --> L A --> M A --> N B --> D B --> E E --> F F --> G F --> H
Sources: src/slab.rs(L4 - L7) src/slab.rs(L65 - L68) src/slab.rs(L112 - L114)
FreeBlockList Management
The FreeBlockList
implements a stack-based linked list for managing free memory blocks. Each free block contains a pointer to the next free block, creating an intrusive linked list that uses the free memory itself for storage.
FreeBlockList Structure and Operations
flowchart TD subgraph subGraph1["Linked List Chain"] C["FreeBlock@addr1"] D["FreeBlock@addr2"] E["FreeBlock@addr3"] F["next: None"] end subgraph subGraph0["FreeBlockList State"] A["head: Some(block_ptr)"] B["len: 3"] end subgraph Operations["Operations"] G["push(free_block)"] H["Sets block.next = current head"] I["Updates head = new block"] J["Increments len"] K["pop()"] L["Takes current head"] M["Updates head = head.next"] N["Decrements len"] O["Returns old head"] end A --> B A --> C C --> D D --> E E --> F G --> H H --> I I --> J K --> L L --> M M --> N N --> O
The FreeBlockList
provides O(1) operations for both insertion and removal:
Operation | Method | Complexity | Description |
---|---|---|---|
Insert | push() | O(1) | Adds block to front of list |
Remove | pop() | O(1) | Removes block from front of list |
Check Length | len() | O(1) | Returns current free block count |
Check Empty | is_empty() | O(1) | Tests if any blocks available |
Sources: src/slab.rs(L92 - L98) src/slab.rs(L100 - L104) src/slab.rs(L88 - L90)
Block Allocation Process
The allocation process follows a straightforward strategy: attempt to pop a free block from the list, and if none are available, grow the slab by requesting memory from the buddy allocator.
Allocation Flow Diagram
flowchart TD subgraph subGraph0["grow() Details"] J["Calculate num_of_blocks"] K["Create new FreeBlockList"] L["Transfer all blocks to main list"] M["Update total_blocks"] end A["allocate(layout, buddy)"] B["free_block_list.pop()"] C["Return block.addr()"] D["Create Layout(SET_SIZE * BLK_SIZE, 4096)"] E["buddy.alloc(layout)"] F["grow(ptr, SET_SIZE * BLK_SIZE)"] G["Return AllocError"] H["free_block_list.pop().unwrap()"] I["Return block.addr()"] A --> B B --> C B --> D D --> E E --> F E --> G F --> H F --> J H --> I J --> K K --> L L --> M
The allocation mechanism includes automatic growth when the free list is exhausted. The growth size is determined by SET_SIZE * BLK_SIZE
with 4096-byte alignment, where SET_SIZE
is imported from the parent module.
Sources: src/slab.rs(L35 - L55) src/slab.rs(L26 - L33)
Block Deallocation Process
Deallocation is simpler than allocation, requiring only the conversion of the memory address back to a FreeBlock
and pushing it onto the free list.
Deallocation Mechanism
flowchart TD subgraph subGraph0["push() Implementation"] E["block.next = current head"] F["head = Some(block)"] G["len += 1"] end A["deallocate(ptr: usize)"] B["Cast ptr as *mut FreeBlock"] C["Dereference to &mut FreeBlock"] D["free_block_list.push(block)"] A --> B B --> C C --> D D --> E E --> F F --> G
The deallocation process assumes the pointer was originally allocated by this slab and is properly aligned for the block size. No validation is performed on the input pointer.
Sources: src/slab.rs(L57 - L62)
Dynamic Growth Mechanism
When a slab runs out of free blocks, it can dynamically grow by requesting additional memory from the buddy allocator. This growth is performed in chunks to amortize the allocation overhead.
Growth Process Details
flowchart TD subgraph subGraph0["Memory Layout"] L["start_addr"] M["Block 0"] N["Block 1"] O["Block 2"] P["... Block n-1"] end A["grow(start_addr, slab_size)"] B["Calculate num_of_blocks = slab_size / BLK_SIZE"] C["total_blocks += num_of_blocks"] D["Create temporary FreeBlockList::new()"] E["Initialize blocks in reverse order"] F["for i in (0..num_of_blocks).rev()"] G["block_addr = start_addr + i * BLK_SIZE"] H["temp_list.push(block_addr as FreeBlock)"] I["Transfer all blocks to main list"] J["while let Some(block) = temp_list.pop()"] K["main_list.push(block)"] A --> B B --> C C --> D D --> E E --> F F --> G G --> H H --> I I --> J J --> K L --> M M --> N N --> O O --> P
The growth mechanism uses reverse iteration to maintain proper block ordering in the free list. Blocks are added to a temporary list first, then transferred to the main free list to ensure correct linkage.
Sources: src/slab.rs(L26 - L33) src/slab.rs(L71 - L82)
Memory Statistics and Tracking
The Slab
provides methods to track memory usage statistics, essential for monitoring and debugging allocation patterns.
Statistic | Method | Calculation | Purpose |
---|---|---|---|
Total Blocks | total_blocks() | Direct field access | Maximum capacity |
Used Blocks | used_blocks() | total_blocks - free_block_list.len() | Current utilization |
Free Blocks | N/A (internal) | free_block_list.len() | Available capacity |
Statistics Calculation Flow
flowchart TD A["total_blocks: usize"] B["total_blocks()"] C["free_block_list.len()"] D["used_blocks() calculation"] E["total_blocks - free_list.len()"] A --> B A --> D C --> D D --> E
These statistics enable the higher-level heap allocator to make informed decisions about memory management and provide usage information to applications.
Sources: src/slab.rs(L18 - L24) src/slab.rs(L88 - L90)
API Reference
Relevant source files
This page provides comprehensive documentation for all public APIs in the slab_allocator
crate. It covers the Heap
struct and its methods, including constructor functions, allocation operations, and memory statistics. For architectural details about how these APIs work internally, see Core Architecture. For practical usage examples and integration patterns, see Getting Started.
Core Types
Heap Struct
The Heap
struct is the primary interface for memory allocation in this crate, implementing a hybrid allocation strategy with multiple slab allocators and a buddy system allocator.
pub struct Heap {
slab_64_bytes: Slab<64>,
slab_128_bytes: Slab<128>,
slab_256_bytes: Slab<256>,
slab_512_bytes: Slab<512>,
slab_1024_bytes: Slab<1024>,
slab_2048_bytes: Slab<2048>,
slab_4096_bytes: Slab<4096>,
buddy_allocator: buddy_system_allocator::Heap<32>,
}
API Structure
flowchart TD subgraph subGraph2["Internal Routing"] M["layout_to_allocator()"] N["HeapAllocator::Slab64Bytes"] O["HeapAllocator::Slab128Bytes"] P["HeapAllocator::Slab256Bytes"] Q["HeapAllocator::Slab512Bytes"] R["HeapAllocator::Slab1024Bytes"] S["HeapAllocator::Slab2048Bytes"] T["HeapAllocator::Slab4096Bytes"] U["HeapAllocator::BuddyAllocator"] end subgraph subGraph1["Method Categories"] J["Constructor Methods"] K["Allocation Methods"] L["Statistics Methods"] end subgraph subGraph0["Public API"] A["Heap"] B["new()"] C["add_memory()"] D["allocate()"] E["deallocate()"] F["usable_size()"] G["total_bytes()"] H["used_bytes()"] I["available_bytes()"] end A --> B A --> C A --> D A --> E A --> F A --> G A --> H A --> I D --> M E --> M F --> M J --> B J --> C K --> D K --> E K --> F L --> G L --> H L --> I M --> N M --> O M --> P M --> Q M --> R M --> S M --> T M --> U
Sources: src/lib.rs(L40 - L49) src/lib.rs(L27 - L36)
Constructor Methods
new()
Creates a new heap instance with the specified memory region.
#![allow(unused)] fn main() { pub unsafe fn new(heap_start_addr: usize, heap_size: usize) -> Heap }
Parameters:
heap_start_addr: usize
- Starting address of the heap memory region (must be page-aligned)heap_size: usize
- Size of the heap in bytes (must be ≥ MIN_HEAP_SIZE and multiple of MIN_HEAP_SIZE)
Returns: Heap
- A new heap instance
Safety Requirements:
- The start address must be valid and accessible
- The memory range
<FileRef file-url="https://github.com/arceos-org/slab_allocator/blob/3c13499d/heap_start_addr, heap_start_addr + heap_size)
must not be used elsewhere\n-heap_start_addr
must be page-aligned (4096-byte boundary)\n-heap_size
must be at least0x8000
bytes (32KB)\n-heap_size
must be a multiple of0x8000
bytes\n\nSources#LNaN-LNaN" NaN file-path="heap_start_addr, heap_start_addr + heap_size)must not be used elsewhere\n-
heap_start_addrmust be page-aligned (4096-byte boundary)\n-
heap_sizemust be at least
0x8000bytes (32KB)\n-
heap_sizemust be a multiple of
0x8000` bytes\n\nSources">Hii src/lib.rs(L25)
add_memory()
Extends the heap with additional memory region.
#![allow(unused)] fn main() { pub unsafe fn add_memory(&mut self, heap_start_addr: usize, heap_size: usize) }
Parameters:
heap_start_addr: usize
- Starting address of the additional memory region (must be page-aligned)heap_size: usize
- Size of the additional memory in bytes (must be multiple of page size)
Safety Requirements:
- The start address must be valid and accessible
- The memory range must not overlap with existing allocations
heap_start_addr
must be page-aligned (4096-byte boundary)heap_size
must be a multiple of page size (4096 bytes)
Sources: src/lib.rs(L88 - L106)
Allocation Methods
allocate()
Allocates memory according to the specified layout requirements.
#![allow(unused)] fn main() { pub fn allocate(&mut self, layout: Layout) -> Result<usize, AllocError> }
Parameters:
layout: Layout
- Memory layout specification including size and alignment requirements
Returns:
Ok(usize)
- Address of the allocated memory blockErr(AllocError)
- Allocation failed due to insufficient memory
Performance:
- O(1) for allocations ≤ 4096 bytes (slab allocators)
- O(n) for allocations > 4096 bytes (buddy allocator)
Allocation Routing Logic
flowchart TD A["allocate(layout)"] B["layout_to_allocator(layout)"] C["Size > 4096?"] D["HeapAllocator::BuddyAllocator"] E["Size ≤ 64 && Align ≤ 64?"] F["HeapAllocator::Slab64Bytes"] G["Size ≤ 128 && Align ≤ 128?"] H["HeapAllocator::Slab128Bytes"] I["Size ≤ 256 && Align ≤ 256?"] J["HeapAllocator::Slab256Bytes"] K["Size ≤ 512 && Align ≤ 512?"] L["HeapAllocator::Slab512Bytes"] M["Size ≤ 1024 && Align ≤ 1024?"] N["HeapAllocator::Slab1024Bytes"] O["Size ≤ 2048 && Align ≤ 2048?"] P["HeapAllocator::Slab2048Bytes"] Q["HeapAllocator::Slab4096Bytes"] R["slab_64_bytes.allocate()"] S["slab_128_bytes.allocate()"] T["slab_256_bytes.allocate()"] U["slab_512_bytes.allocate()"] V["slab_1024_bytes.allocate()"] W["slab_2048_bytes.allocate()"] X["slab_4096_bytes.allocate()"] Y["buddy_allocator.alloc()"] A --> B B --> C C --> D C --> E D --> Y E --> F E --> G F --> R G --> H G --> I H --> S I --> J I --> K J --> T K --> L K --> M L --> U M --> N M --> O N --> V O --> P O --> Q P --> W Q --> X
Sources: src/lib.rs(L131 - L164) src/lib.rs(L207 - L226)
deallocate()
Frees a previously allocated memory block.
#![allow(unused)] fn main() { pub unsafe fn deallocate(&mut self, ptr: usize, layout: Layout) }
Parameters:
ptr: usize
- Address of the memory block to free (must be returned from previousallocate()
call)layout: Layout
- Original layout used for allocation (must match exactly)
Safety Requirements:
ptr
must be a valid pointer returned by a previous call toallocate()
with identical layout- The memory block must not have been previously freed
- The layout must match the original allocation layout exactly
Performance:
- O(1) for blocks ≤ 4096 bytes (slab allocators)
- O(n) for blocks > 4096 bytes (buddy allocator)
Sources: src/lib.rs(L166 - L190)
usable_size()
Returns the bounds on the usable size of an allocation with the given layout.
#![allow(unused)] fn main() { pub fn usable_size(&self, layout: Layout) -> (usize, usize) }
Parameters:
layout: Layout
- Memory layout specification
Returns: (usize, usize)
- Tuple of (minimum_usable_size, maximum_usable_size)
Behavior:
- For slab allocations: returns
(layout.size(), slab_block_size)
- For buddy allocations: returns
(layout.size(), layout.size())
Slab Size | Usable Size Range |
---|---|
64 bytes | (requested_size, 64) |
128 bytes | (requested_size, 128) |
256 bytes | (requested_size, 256) |
512 bytes | (requested_size, 512) |
1024 bytes | (requested_size, 1024) |
2048 bytes | (requested_size, 2048) |
4096 bytes | (requested_size, 4096) |
Buddy allocator | (requested_size, requested_size) |
Sources: src/lib.rs(L192 - L205)
Statistics Methods
total_bytes()
Returns the total memory size managed by the heap.
#![allow(unused)] fn main() { pub fn total_bytes(&self) -> usize }
Returns: usize
- Total memory in bytes across all slab allocators and buddy allocator
Calculation: Sum of (total_blocks × block_size) for each slab plus buddy allocator total bytes.
Sources: src/lib.rs(L228 - L238)
used_bytes()
Returns the currently allocated memory size.
#![allow(unused)] fn main() { pub fn used_bytes(&self) -> usize }
Returns: usize
- Currently allocated memory in bytes
Calculation: Sum of (used_blocks × block_size) for each slab plus buddy allocator allocated bytes.
Sources: src/lib.rs(L240 - L250)
available_bytes()
Returns the available memory size for new allocations.
#![allow(unused)] fn main() { pub fn available_bytes(&self) -> usize }
Returns: usize
- Available memory in bytes
Calculation: total_bytes() - used_bytes()
Sources: src/lib.rs(L252 - L255)
Constants and Configuration
Memory Size Constants
Constant | Value | Description |
---|---|---|
SET_SIZE | 64 | Number of blocks allocated when a slab needs to grow |
MIN_HEAP_SIZE | 0x8000 (32KB) | Minimum heap size fornew()constructor |
Sources: src/lib.rs(L24 - L25)
Slab Size Configuration
The allocator uses seven fixed-size slab allocators with the following block sizes:
Slab Type | Block Size | Use Case |
---|---|---|
Slab<64> | 64 bytes | Small objects, metadata |
Slab<128> | 128 bytes | Small structures |
Slab<256> | 256 bytes | Medium structures |
Slab<512> | 512 bytes | Large structures |
Slab<1024> | 1024 bytes | Very large structures |
Slab<2048> | 2048 bytes | Page-like allocations |
Slab<4096> | 4096 bytes | Page allocations |
Buddy Allocator | Variable | Allocations > 4096 bytes |
Sources: src/lib.rs(L41 - L48)
Usage Patterns
Memory Statistics Flow
flowchart TD A["Heap Instance"] B["total_bytes()"] C["used_bytes()"] D["available_bytes()"] E["Sum all slab total_blocks * block_size"] F["Add buddy_allocator.stats_total_bytes()"] G["Sum all slab used_blocks * block_size"] H["Add buddy_allocator.stats_alloc_actual()"] I["total_bytes - used_bytes"] J["Final Total"] K["Final Used"] L["Final Available"] A --> B A --> C A --> D B --> E B --> F C --> G C --> H D --> I E --> J F --> J G --> K H --> K I --> L
Sources: src/lib.rs(L228 - L255)
Typical API Usage Sequence:
- Create heap with
unsafe { Heap::new(start_addr, size) }
- Optionally extend with
unsafe { heap.add_memory(addr, size) }
- Allocate memory with
heap.allocate(layout)
- Use allocated memory
- Free memory with
unsafe { heap.deallocate(ptr, layout) }
- Monitor statistics with
heap.total_bytes()
,heap.used_bytes()
,heap.available_bytes()
Sources: src/lib.rs(L52 - L255)
Testing and Validation
Relevant source files
This page provides a comprehensive guide to the test suite for the slab_allocator crate. It covers the test infrastructure, validation scenarios, test execution procedures, and analysis of different allocation patterns used to ensure the reliability and correctness of the hybrid memory allocation system.
For information about the core allocation architecture being tested, see Core Architecture. For API usage patterns demonstrated in tests, see API Reference.
Test Infrastructure
The test suite is built around two primary test heap configurations that provide controlled environments for validation.
Test Heap Configurations
The testing infrastructure uses statically allocated, page-aligned memory regions to simulate real-world heap conditions:
Configuration | Size | Purpose |
---|---|---|
TestHeap | 64KB (16 × 4096 bytes) | Small-scale allocation testing |
TestBigHeap | 640KB (64KB × 10) | Large allocation and growth testing |
flowchart TD subgraph subGraph1["Test Infrastructure Architecture"] A["TestHeap"] B["64KB aligned memory"] C["TestBigHeap"] D["640KB aligned memory"] E["new_heap()"] F["new_big_heap()"] G["Heap::new()"] H["Test Execution"] subgraph subGraph0["Memory Layout"] I["#[repr(align(4096))]"] J["heap_space: [u8; SIZE]"] end end A --> B A --> I B --> E B --> J C --> D C --> I D --> F D --> J E --> G F --> G G --> H
The test structures use #[repr(align(4096))]
to ensure proper page alignment, which is critical for the underlying buddy allocator's operation.
Sources: src/tests.rs(L8 - L16) src/tests.rs(L18 - L37)
Test Heap Factory Functions
Two factory functions provide standardized heap initialization:
new_heap()
- Creates a 64KB heap instance for basic allocation testingnew_big_heap()
- Creates a 640KB heap instance for large allocation scenarios
These functions handle the unsafe memory region setup and return initialized Heap
instances ready for testing.
Sources: src/tests.rs(L18 - L37)
Test Categories and Validation Scenarios
The test suite validates the allocator through several distinct categories of allocation patterns and edge cases.
Memory Exhaustion Testing
flowchart TD subgraph subGraph0["OOM Test Flow"] A["oom() test"] B["Request HEAP_SIZE + 1 bytes"] C["Layout::from_size_align()"] D["heap.allocate()"] E["Verify Error Result"] F["Heap: 64KB"] G["Request: 64KB + 1"] H["Expected: Allocation Failure"] end A --> B B --> C C --> D D --> E F --> G G --> H
The oom()
test validates out-of-memory handling by requesting more memory than the total heap capacity. This ensures the allocator gracefully handles resource exhaustion rather than causing undefined behavior.
Sources: src/tests.rs(L39 - L45)
Basic Allocation Patterns
The test suite validates fundamental allocation operations through progressively complex scenarios:
Test Function | Allocation Size | Validation Focus |
---|---|---|
allocate_double_usize() | 2 ×usize | Basic allocation success |
allocate_and_free_double_usize() | 2 ×usize | Allocation + deallocation cycle |
reallocate_double_usize() | 2 ×usize | Memory reuse verification |
flowchart TD subgraph subGraph0["Basic Allocation Test Flow"] A["Layout Creation"] B["size_of::() * 2"] C["align_of::()"] D["Layout::from_size_align()"] E["heap.allocate()"] F["Success Verification"] G["Memory Write Test"] H["0xdeafdeadbeafbabe pattern"] I["heap.deallocate()"] J["Memory Reuse Check"] end A --> B A --> C B --> D C --> D D --> E E --> F F --> G G --> H H --> I I --> J
Sources: src/tests.rs(L47 - L87)
Multi-Size Allocation Testing
The allocate_multiple_sizes()
test validates the allocator's ability to handle concurrent allocations of different sizes and alignments:
- Size Variations: 2×, 3×, 10× base
usize
- Alignment Variations: Standard alignment and 8× alignment
- Deallocation Patterns: Non-sequential deallocation order
This test ensures the slab allocator correctly routes different sizes to appropriate slabs and handles fragmentation scenarios.
Sources: src/tests.rs(L89 - L117)
Large Block Allocation Testing
Two specialized tests validate large allocation handling that exceeds the slab threshold:
flowchart TD subgraph subGraph0["Large Block Test Strategy"] A["allocate_one_4096_block()"] B["Single 4KB allocation"] C["allocate_multiple_4096_blocks()"] D["Multiple 4KB+ allocations"] E["512 * usize allocation"] F["Mixed size allocations"] G["Complex deallocation patterns"] H["Buddy allocator routing"] I["Memory pattern verification"] J["0xdeafdeadbeafbabe write test"] end A --> B B --> E C --> D D --> F D --> G E --> H F --> H G --> H H --> I I --> J
These tests specifically target the boundary between slab allocation (≤4096 bytes) and buddy allocation (>4096 bytes), ensuring proper routing and memory management across the threshold.
Sources: src/tests.rs(L119 - L163)
Test Execution and Validation Patterns
Layout-Based Testing
All tests use Rust's Layout
type to specify allocation requirements, ensuring compatibility with the standard allocator interface:
// Example pattern from tests
let layout = Layout::from_size_align(size, alignment).unwrap();
let addr = heap.allocate(layout.clone());
This approach validates that the allocator correctly handles:
- Size requirements
- Alignment constraints
- Error propagation
- Memory safety
Memory Pattern Verification
Critical tests write specific bit patterns to allocated memory to verify:
- Successful memory access
- Proper alignment
- Data integrity across allocation/deallocation cycles
The pattern 0xdeafdeadbeafbabe
is used as a recognizable marker for debugging and validation.
Sources: src/tests.rs(L64) src/tests.rs(L161)
Allocation Lifecycle Testing
flowchart TD subgraph subGraph0["Complete Allocation Lifecycle"] A["Allocate"] B["Write Pattern"] C["Verify Access"] D["Deallocate"] E["Re-allocate"] F["Verify Reuse"] G["Layout Creation"] H["Error Handling"] I["Memory Safety"] J["Resource Management"] end A --> B B --> C C --> D D --> E E --> F G --> A H --> A I --> C J --> D
Tests validate the complete allocation lifecycle to ensure:
- Proper initialization
- Safe memory access
- Correct deallocation
- Memory reuse efficiency
- No memory leaks
Sources: src/tests.rs(L71 - L87)
Running the Test Suite
The test suite runs using Rust's standard testing framework:
cargo test
Target Platform Testing
The CI/CD pipeline executes tests across multiple target platforms:
x86_64-unknown-linux-gnu
(with full test execution)x86_64-unknown-none
(build verification)riscv64gc-unknown-none-elf
(build verification)aarch64-unknown-none-softfloat
(build verification)
This ensures the allocator works correctly across different embedded and no_std
environments.
Sources: Based on repository CI/CD configuration referenced in overview diagrams
Test Coverage Analysis
The test suite provides comprehensive coverage of the allocator's functionality:
Component | Test Coverage |
---|---|
Heap initialization | ✓ Factory functions |
Small allocations | ✓ Slab routing tests |
Large allocations | ✓ Buddy allocator tests |
Memory exhaustion | ✓ OOM handling |
Fragmentation | ✓ Multi-size patterns |
Alignment | ✓ Various alignment requirements |
Memory reuse | ✓ Allocation/deallocation cycles |
The tests validate both the high-level Heap
interface and the underlying allocation strategy switching between slab and buddy allocation based on size thresholds.
Sources: src/tests.rs(L1 - L164)
Development Workflow
Relevant source files
This document provides a comprehensive guide for contributors to the slab_allocator project, covering development environment setup, build system usage, CI/CD pipeline configuration, and project maintenance practices. It focuses on the technical infrastructure that supports development and ensures code quality across multiple target platforms.
For information about running and understanding the test suite, see Testing and Validation. For API usage examples and integration patterns, see Getting Started.
Development Environment Setup
The slab_allocator project is designed for no_std
environments and requires specific toolchain configurations to support cross-compilation to embedded targets. The development environment must support Rust nightly toolchain with multiple target architectures.
Required Toolchain Components
The project requires the following Rust toolchain components as defined in the CI configuration:
Component | Purpose |
---|---|
rust-src | Source code for cross-compilation |
clippy | Linting and code analysis |
rustfmt | Code formatting |
nightlytoolchain | Access to unstable features required byno_std |
Supported Target Architectures
The project supports four primary target architectures:
Target | Environment | Testing |
---|---|---|
x86_64-unknown-linux-gnu | Linux development | Full test suite |
x86_64-unknown-none | Bare metal x86_64 | Build only |
riscv64gc-unknown-none-elf | RISC-V embedded | Build only |
aarch64-unknown-none-softfloat | ARM64 embedded | Build only |
Sources: .github/workflows/ci.yml(L11 - L12) .github/workflows/ci.yml(L17 - L19)
Local Development Workflow
flowchart TD A["Developer Environment"] B["Local Changes"] C["cargo fmt --all -- --check"] D["cargo clippy --all-features"] E["cargo build --all-features"] F["cargo test -- --nocapture"] G["Format Check"] H["Clippy Analysis"] I["Build Success"] J["Test Results"] K["Ready for Commit"] L["Fix Formatting"] M["Fix Clippy Issues"] N["Fix Build Errors"] O["Fix Test Failures"] P["git commit / push"] Q["CI/CD Pipeline Triggered"] A --> B B --> C B --> D B --> E B --> F C --> G D --> H E --> I F --> J G --> K G --> L H --> K H --> M I --> K I --> N J --> K J --> O K --> P L --> B M --> B N --> B O --> B P --> Q
The local development process mirrors the CI pipeline checks to catch issues early. Developers should run the same commands locally that will be executed in the CI environment.
Sources: .github/workflows/ci.yml(L23 - L30)
CI/CD Pipeline Architecture
Pipeline Overview
flowchart TD A["GitHub Events"] B["push/pull_request"] C["ci job"] D["doc job"] E["Matrix Strategy"] F["nightly toolchain"] G["4 target platforms"] H["Format Check"] I["Clippy Analysis"] J["Multi-target Build"] K["Unit Tests"] L["Documentation Build"] M["GitHub Pages Deploy"] A --> B B --> C B --> D C --> E D --> L D --> M E --> F E --> G F --> H F --> I F --> J F --> K
The CI/CD system uses GitHub Actions with two parallel job streams: code validation (ci
) and documentation generation (doc
).
Sources: .github/workflows/ci.yml(L1 - L6) .github/workflows/ci.yml(L32 - L33)
CI Job Matrix Configuration
flowchart TD A["ci job"] B["Matrix Strategy"] C["fail-fast: false"] D["rust-toolchain: [nightly]"] E["targets array"] F["x86_64-unknown-linux-gnu"] G["x86_64-unknown-none"] H["riscv64gc-unknown-none-elf"] I["aarch64-unknown-none-softfloat"] J["Full Testing"] K["Build Only"] L["cargo test"] M["cargo build"] A --> B B --> C B --> D B --> E E --> F E --> G E --> H E --> I F --> J G --> K H --> K I --> K J --> L K --> M
The matrix strategy ensures the codebase builds successfully across all supported embedded architectures while running comprehensive tests only on the Linux target where a full runtime environment is available.
Sources: .github/workflows/ci.yml(L8 - L12) .github/workflows/ci.yml(L28 - L30)
Code Quality Pipeline
Quality Check Sequence
The CI pipeline enforces code quality through a structured sequence of checks:
Step | Command | Purpose | Failure Impact |
---|---|---|---|
Format | cargo fmt --all -- --check | Consistent code style | CI failure |
Lint | cargo clippy --target $TARGET --all-features | Code analysis | CI failure |
Build | cargo build --target $TARGET --all-features | Compilation validation | CI failure |
Test | cargo test --target x86_64-unknown-linux-gnu | Functional validation | CI failure |
Clippy Configuration
The project uses clippy with a specific configuration to suppress certain warnings:
cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default
The -A clippy::new_without_default
flag allows new()
methods without corresponding default()
implementations, which is appropriate for allocator constructors that require explicit memory region parameters.
Sources: .github/workflows/ci.yml(L22 - L30) .github/workflows/ci.yml(L25)
Documentation System
Documentation Build Process
flowchart TD A["doc job"] B["Checkout Repository"] C["Setup Rust Nightly"] D["Build Documentation"] E["cargo doc --no-deps --all-features"] F["RUSTDOCFLAGS Validation"] G["Generate index.html"] H["Branch Check"] I["Deploy to gh-pages"] J["Skip Deployment"] K["Broken Links Check"] L["Missing Docs Check"] A --> B B --> C C --> D D --> E E --> F F --> G F --> K F --> L G --> H H --> I H --> J
Documentation Quality Enforcement
The documentation build enforces strict quality standards through RUSTDOCFLAGS
:
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
This configuration treats broken internal documentation links and missing documentation as compilation errors, ensuring comprehensive and accurate documentation.
GitHub Pages Deployment
Documentation deployment follows a conditional strategy:
- Default branch pushes: Automatic deployment to GitHub Pages
- Pull requests and other branches: Documentation builds but does not deploy
- Deployment target:
gh-pages
branch with single-commit history
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L44 - L55)
Project Configuration
Dependency Management
The project maintains minimal dependencies to support no_std
environments:
[dependencies]
buddy_system_allocator = { version = "0.10", default-features = false }
The default-features = false
configuration ensures the buddy allocator dependency remains no_std
compatible.
Git Configuration
The .gitignore
configuration excludes development artifacts:
Pattern | Purpose |
---|---|
/target | Rust build artifacts |
/.vscode | IDE configuration |
.DS_Store | macOS system files |
Cargo.lock | Dependency lock file (library project) |
Sources: Cargo.toml(L11 - L12) .gitignore(L1 - L4)
Development Best Practices
Pre-commit Validation
Contributors should run the complete CI validation suite locally before committing:
# Format check
cargo fmt --all -- --check
# Lint analysis
cargo clippy --all-features -- -A clippy::new_without_default
# Multi-target build validation
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features
# Full test suite
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
Release Management
Version management follows semantic versioning as indicated by the current version 0.3.1
in Cargo.toml
. The project maintains backwards compatibility within major versions while supporting iterative improvements in minor releases.
Sources: Cargo.toml(L3) .github/workflows/ci.yml(L22 - L30)
Overview
Relevant source files
Purpose and Scope
The cpumask
library provides a type-safe, memory-efficient implementation of CPU affinity masks for the ArceOS operating system. It represents sets of physical CPUs as compact bitmaps, where each bit position corresponds to a CPU number in the system. This library serves as the foundational component for CPU scheduling, process affinity management, and NUMA-aware operations within ArceOS.
The library is designed as a direct Rust equivalent to Linux's cpumask_t
data structure, providing similar functionality while leveraging Rust's type system for compile-time size optimization and memory safety. For detailed API documentation, see API Reference. For implementation details and performance characteristics, see Architecture and Design.
Sources: src/lib.rs(L9 - L16) README.md(L7 - L16) Cargo.toml(L6 - L12)
Core Architecture
The cpumask
library is built around the CpuMask<const SIZE: usize>
generic struct, which provides a strongly-typed wrapper over the bitmaps
crate's Bitmap
implementation. This architecture enables automatic storage optimization and type-safe CPU mask operations.
flowchart TD A["CpuMask"] B["Bitmap<{ SIZE }>"] C["BitsImpl: Bits"] D["BitOps trait methods"] E["Automatic storage selection"] F["bool (SIZE = 1)"] G["u8 (SIZE ≤ 8)"] H["u16 (SIZE ≤ 16)"] I["u32 (SIZE ≤ 32)"] J["u64 (SIZE ≤ 64)"] K["u128 (SIZE ≤ 128)"] L["[u128; N] (SIZE > 128)"] M["CPU-specific operations"] N["Iterator implementation"] O["Bitwise operators"] A --> B A --> M A --> N A --> O B --> C C --> D C --> E E --> F E --> G E --> H E --> I E --> J E --> K E --> L
Sources: src/lib.rs(L17 - L23) src/lib.rs(L7)
Storage Optimization Strategy
The CpuMask
type automatically selects the most memory-efficient storage representation based on the compile-time SIZE
parameter. This optimization reduces memory overhead and improves cache performance for systems with different CPU counts.
CPU Count Range | Storage Type | Memory Usage |
---|---|---|
1 | bool | 1 bit |
2-8 | u8 | 1 byte |
9-16 | u16 | 2 bytes |
17-32 | u32 | 4 bytes |
33-64 | u64 | 8 bytes |
65-128 | u128 | 16 bytes |
129-1024 | [u128; N] | 16×N bytes |
flowchart TD A["Compile-time SIZE"] B["Storage Selection"] C["bool storage"] D["u8 storage"] E["u16 storage"] F["u32 storage"] G["u64 storage"] H["u128 storage"] I["[u128; N] array"] J["Optimal memory usage"] A --> B B --> C B --> D B --> E B --> F B --> G B --> H B --> I C --> J D --> J E --> J F --> J G --> J H --> J I --> J
Sources: src/lib.rs(L12 - L16) src/lib.rs(L328 - L410)
API Surface Categories
The CpuMask
API is organized into five main functional categories, providing comprehensive CPU mask manipulation capabilities:
flowchart TD A["CpuMask API"] B["Construction Methods"] C["Query Operations"] D["Modification Operations"] E["Iteration Interface"] F["Bitwise Operations"] B1["new() - empty mask"] B2["full() - all CPUs set"] B3["mask(bits) - range mask"] B4["one_shot(index) - single CPU"] B5["from_raw_bits(value)"] B6["from_value(data)"] C1["get(index) - test bit"] C2["len() - count set bits"] C3["is_empty() / is_full()"] C4["first_index() / last_index()"] C5["next_index() / prev_index()"] C6["*_false_index variants"] D1["set(index, value)"] D2["invert() - flip all bits"] E1["IntoIterator trait"] E2["Iter<'a, SIZE> struct"] E3["DoubleEndedIterator"] F1["BitAnd (&) - intersection"] F2["BitOr (|) - union"] F3["BitXor (^) - difference"] F4["Not (!) - complement"] F5["*Assign variants"] A --> B A --> C A --> D A --> E A --> F B --> B1 B --> B2 B --> B3 B --> B4 B --> B5 B --> B6 C --> C1 C --> C2 C --> C3 C --> C4 C --> C5 C --> C6 D --> D1 D --> D2 E --> E1 E --> E2 E --> E3 F --> F1 F --> F2 F --> F3 F --> F4 F --> F5
Sources: src/lib.rs(L68 - L235) src/lib.rs(L237 - L251) src/lib.rs(L253 - L326)
Integration with ArceOS Ecosystem
The cpumask
library serves as a foundational component within the ArceOS operating system, providing essential CPU affinity management capabilities for kernel subsystems:
flowchart TD subgraph subGraph3["External Dependencies"] N["bitmaps v3.2.1"] O["no-std environment"] end subgraph subGraph2["Hardware Abstraction"] J["Multi-core systems"] K["NUMA topologies"] L["CPU hotplug events"] M["Embedded constraints"] end subgraph subGraph1["cpumask Library Core"] F["CPU set operations"] G["Efficient bit manipulation"] H["Iterator support"] I["Memory optimization"] end subgraph subGraph0["ArceOS Operating System"] A["Process Scheduler"] E["cpumask::CpuMask"] B["Thread Management"] C["CPU Affinity Control"] D["Load Balancing"] end A --> E B --> E C --> E D --> E E --> F E --> G E --> H E --> I F --> J G --> K H --> L I --> M N --> E O --> E
The library's no_std
compatibility makes it suitable for both kernel-space and embedded environments, while its dependency on the bitmaps
crate provides battle-tested bitmap operations with optimal performance characteristics.
Sources: Cargo.toml(L12) Cargo.toml(L14 - L15) src/lib.rs(L1)
Usage Example
The following example demonstrates typical CPU mask operations in an operating system context:
use cpumask::CpuMask;
// Create a CPU mask for a 32-CPU system
const SMP: usize = 32;
let mut available_cpus = CpuMask::<SMP>::new();
// Set CPUs 0, 2, 4 as available
available_cpus.set(0, true);
available_cpus.set(2, true);
available_cpus.set(4, true);
// Check system state
assert_eq!(available_cpus.len(), 3);
assert_eq!(available_cpus.first_index(), Some(0));
// Create process affinity mask (restrict to even CPUs)
let even_cpus = CpuMask::<SMP>::mask(16); // CPUs 0-15
let process_affinity = available_cpus & even_cpus;
// Iterate over assigned CPUs
for cpu_id in &process_affinity {
println!("Process can run on CPU {}", cpu_id);
}
Sources: README.md(L20 - L57) src/lib.rs(L417 - L427)
API Reference
Relevant source files
This document provides complete reference documentation for the CpuMask<SIZE>
struct and its associated types, methods, and trait implementations. The API enables efficient CPU affinity management through bitset operations optimized for different CPU count scenarios.
For architectural details and storage optimization strategies, see Architecture and Design. For practical usage examples and common patterns, see Usage Guide and Examples.
Core Types and Structure
The cpumask library centers around the CpuMask<const SIZE: usize>
generic struct, which provides a compile-time sized bitset for representing CPU sets.
flowchart TD subgraph subGraph3["Iterator Interface"] G1["IntoIterator trait"] G2["Iter<'a, SIZE>"] G3["Iterator + DoubleEndedIterator"] end subgraph subGraph2["Query Operations"] D1["get(index)"] D2["len()"] D3["is_empty()"] D4["is_full()"] D5["first_index()"] D6["last_index()"] D7["next_index(index)"] D8["prev_index(index)"] D9["*_false_index variants"] end subgraph subGraph1["Construction Methods"] C1["new()"] C2["full()"] C3["mask(bits)"] C4["from_value(data)"] C5["from_raw_bits(value)"] C6["one_shot(index)"] end subgraph subGraph0["Core API Structure"] A["CpuMask<SIZE>"] B["Bitmap<SIZE> value"] C["Construction Methods"] D["Query Operations"] E["Modification Methods"] F["Bitwise Operations"] G["Iterator Interface"] end A --> B A --> C A --> D A --> E A --> F A --> G C --> C1 C --> C2 C --> C3 C --> C4 C --> C5 C --> C6 D --> D1 D --> D2 D --> D3 D --> D4 D --> D5 D --> D6 D --> D7 D --> D8 D --> D9 G --> G1 G --> G2 G --> G3
Sources: src/lib.rs(L18 - L23) src/lib.rs(L68 - L235)
Type Definitions and Constraints
Type | Definition | Constraints |
---|---|---|
CpuMask | Main bitset struct | BitsImpl |
Iter<'a, const SIZE: usize> | Iterator over set bit indices | BitsImpl |
Storage Type | <BitsImpl | Automatically selected based on SIZE |
The storage type is automatically optimized based on the SIZE
parameter:
SIZE Range | Storage Type | Memory Usage |
---|---|---|
1 | bool | 1 bit |
2-8 | u8 | 8 bits |
9-16 | u16 | 16 bits |
17-32 | u32 | 32 bits |
33-64 | u64 | 64 bits |
65-128 | u128 | 128 bits |
129-1024 | [u128; N] | N × 128 bits |
Sources: src/lib.rs(L12 - L16) src/lib.rs(L18 - L23)
Construction and Conversion Methods
The CpuMask
type provides multiple ways to create and convert between different representations.
Basic Construction
Method | Signature | Description | Time Complexity |
---|---|---|---|
new() | fn new() -> Self | Creates empty mask (all bits false) | O(1) |
full() | fn full() -> Self | Creates full mask (all bits true) | O(1) |
mask(bits) | fn mask(bits: usize) -> Self | Creates mask with firstbitsset to true | O(1) |
one_shot(index) | fn one_shot(index: usize) -> Self | Creates mask with single bit set atindex | O(1) |
Advanced Construction
Method | Signature | Description | Panics |
---|---|---|---|
from_value(data) | fn from_value(data: Store) -> Self | Creates from backing store value | Never |
from_raw_bits(value) | fn from_raw_bits(value: usize) -> Self | Creates from raw usize bits | Ifvalue >= 2^SIZE |
Conversion Methods
Method | Signature | Description |
---|---|---|
into_value(self) | fn into_value(self) -> Store | Converts to backing store value |
as_value(&self) | fn as_value(&self) -> &Store | Gets reference to backing store |
as_bytes(&self) | fn as_bytes(&self) -> &[u8] | Gets byte slice representation |
Sources: src/lib.rs(L72 - L146)
Query and Inspection Operations
flowchart TD subgraph subGraph2["Index Finding (False Bits)"] I["first_false_index()"] I1["Option<usize>"] J["last_false_index()"] J1["Option<usize>"] K["next_false_index(idx)"] K1["Option<usize>"] E["first_index()"] E1["Option<usize>"] F["last_index()"] F1["Option<usize>"] G["next_index(idx)"] G1["Option<usize>"] H["prev_index(idx)"] H1["Option<usize>"] A["get(index)"] A1["Returns bool"] B["len()"] B1["Count of set bits"] C["is_empty()"] C1["No bits set"] D["is_full()"] D1["All bits set"] subgraph subGraph1["Index Finding (True Bits)"] L["prev_false_index(idx)"] L1["Option<usize>"] subgraph subGraph0["Bit Query Operations"] I["first_false_index()"] I1["Option<usize>"] J["last_false_index()"] J1["Option<usize>"] K["next_false_index(idx)"] K1["Option<usize>"] E["first_index()"] E1["Option<usize>"] F["last_index()"] F1["Option<usize>"] G["next_index(idx)"] G1["Option<usize>"] H["prev_index(idx)"] H1["Option<usize>"] A["get(index)"] A1["Returns bool"] B["len()"] B1["Count of set bits"] C["is_empty()"] C1["No bits set"] D["is_full()"] D1["All bits set"] end end end A --> A1 B --> B1 C --> C1 D --> D1 E --> E1 F --> F1 G --> G1 H --> H1 I --> I1 J --> J1 K --> K1 L --> L1
Bit Testing Operations
Method | Return Type | Description | Time Complexity |
---|---|---|---|
get(index) | bool | Tests if bit at index is set | O(1) |
len() | usize | Counts number of set bits | O(n) for arrays, O(1) for primitives |
is_empty() | bool | Tests if no bits are set | O(log n) |
is_full() | bool | Tests if all bits are set | O(log n) |
Index Finding Operations
Method | Return Type | Description | Time Complexity |
---|---|---|---|
first_index() | Option | Finds first set bit | O(log n) |
last_index() | Option | Finds last set bit | O(log n) |
next_index(index) | Option | Finds next set bit after index | O(log n) |
prev_index(index) | Option | Finds previous set bit before index | O(log n) |
first_false_index() | Option | Finds first unset bit | O(log n) |
last_false_index() | Option | Finds last unset bit | O(log n) |
next_false_index(index) | Option | Finds next unset bit after index | O(log n) |
prev_false_index(index) | Option | Finds previous unset bit before index | O(log n) |
Sources: src/lib.rs(L148 - L228)
Modification and Iteration
Modification Operations
Method | Signature | Description | Returns |
---|---|---|---|
set(&mut self, index, value) | fn set(&mut self, index: usize, value: bool) -> bool | Sets bit at index | Previous value |
invert(&mut self) | fn invert(&mut self) | Inverts all bits | () |
Iterator Interface
The CpuMask
implements IntoIterator
for &CpuMask<SIZE>
, yielding indices of set bits.
flowchart TD subgraph subGraph1["Iter Struct Fields"] J["head: Option<usize>"] K["tail: Option<usize>"] L["data: &'a CpuMask<SIZE>"] end subgraph subGraph0["Iterator Implementation"] A["&CpuMask<SIZE>"] B["IntoIterator"] C["Iter<'a, SIZE>"] D["Iterator"] E["DoubleEndedIterator"] F["next()"] G["Option<usize>"] H["next_back()"] I["Option<usize>"] end A --> B B --> C C --> D C --> E C --> J C --> K C --> L D --> F E --> H F --> G H --> I
Iterator Type | Item Type | Description |
---|---|---|
Iter<'a, SIZE> | usize | Iterates over indices of set bits |
Trait Implementation | Methods | Description |
---|---|---|
Iterator | next() | Forward iteration through set bit indices |
DoubleEndedIterator | next_back() | Reverse iteration through set bit indices |
Sources: src/lib.rs(L172 - L180) src/lib.rs(L230 - L234) src/lib.rs(L237 - L251) src/lib.rs(L412 - L518)
Bitwise Operations and Trait Implementations
Binary Bitwise Operations
flowchart TD subgraph subGraph0["Binary Operations"] E["&="] E1["BitAndAssign"] D["!CpuMask"] D1["Not - Complement"] A["CpuMask & CpuMask"] A1["BitAnd - Intersection"] C["CpuMask ^ CpuMask"] C1["BitXor - Symmetric Difference"] subgraph subGraph2["Assignment Operations"] F["|="] F1["BitOrAssign"] G["^="] G1["BitXorAssign"] B["CpuMask | CpuMask"] B1["BitOr - Union"] subgraph subGraph1["Unary Operations"] E["&="] E1["BitAndAssign"] D["!CpuMask"] D1["Not - Complement"] A["CpuMask & CpuMask"] A1["BitAnd - Intersection"] end end end A --> A1 B --> B1 C --> C1 D --> D1 E --> E1 F --> F1 G --> G1
Operator | Trait | Description | Result |
---|---|---|---|
& | BitAnd | Intersection of two masks | Set bits present in both |
| | BitOr | Union of two masks | Set bits present in either |
^ | BitXor | Symmetric difference | Set bits present in exactly one |
! | Not | Complement of mask | Inverts all bits |
&= | BitAndAssign | In-place intersection | Modifies left operand |
|= | BitOrAssign | In-place union | Modifies left operand |
^= | BitXorAssign | In-place symmetric difference | Modifies left operand |
Standard Trait Implementations
Trait | Implementation | Notes |
---|---|---|
Clone | Derived | Bitwise copy |
Copy | Derived | Trivial copy semantics |
Default | Derived | Creates empty mask |
Eq | Derived | Bitwise equality |
PartialEq | Derived | Bitwise equality |
Debug | Custom | Displays as "cpumask: [idx1, idx2, ...]" |
Hash | Custom | Hashes underlying storage value |
PartialOrd | Custom | Compares underlying storage value |
Ord | Custom | Compares underlying storage value |
Sources: src/lib.rs(L17) src/lib.rs(L25 - L66) src/lib.rs(L253 - L326)
Array Conversion Implementations
For large CPU counts, the library provides From
trait implementations for array conversions:
Array Size | CpuMask Size | Conversion |
---|---|---|
[u128; 2] | CpuMask<256> | BidirectionalFrom |
[u128; 3] | CpuMask<384> | BidirectionalFrom |
[u128; 4] | CpuMask<512> | BidirectionalFrom |
[u128; 5] | CpuMask<640> | BidirectionalFrom |
[u128; 6] | CpuMask<768> | BidirectionalFrom |
[u128; 7] | CpuMask<896> | BidirectionalFrom |
[u128; 8] | CpuMask<1024> | BidirectionalFrom |
These implementations enable direct conversion between CpuMask
instances and their underlying array storage for sizes exceeding 128 bits.
Sources: src/lib.rs(L328 - L410)
Construction and Conversion Methods
Relevant source files
This page documents all methods for creating CpuMask
instances and converting between different representations. It covers the complete set of constructors, factory methods, and type conversion utilities provided by the CpuMask<SIZE>
struct.
For information about querying and inspecting existing CpuMask
instances, see Query and Inspection Operations. For modifying CpuMask
state after construction, see Modification and Iteration.
Construction Methods
The CpuMask<SIZE>
struct provides multiple construction patterns to accommodate different use cases and data sources.
Basic Construction
The most fundamental construction methods create CpuMask
instances with predefined bit patterns:
Method | Purpose | Bit Pattern |
---|---|---|
new() | Empty cpumask | All bits set tofalse |
full() | Complete cpumask | All bits set totrue |
mask(bits: usize) | Range-based mask | Firstbitsindices set totrue |
The new()
method src/lib.rs(L74 - L76) creates an empty cpumask by delegating to the Default
trait implementation. The full()
method src/lib.rs(L79 - L84) creates a cpumask with all valid bits set using the underlying Bitmap::mask(SIZE)
operation. The mask(bits: usize)
method src/lib.rs(L89 - L94) creates a mask where indices 0
through bits-1
are set to true
.
Data-Driven Construction
For scenarios where CpuMask
instances must be created from existing data, several factory methods accept different input formats:
The from_value(data)
method src/lib.rs(L97 - L102) accepts data of the backing store type, which varies based on the SIZE
parameter due to storage optimization. The from_raw_bits(value: usize)
method src/lib.rs(L106 - L119) constructs a cpumask from a raw usize
value, with assertion checking to ensure the value fits within the specified size. The one_shot(index: usize)
method src/lib.rs(L123 - L128) creates a cpumask with exactly one bit set at the specified index.
Construction Method Flow
flowchart TD A["CpuMask Construction"] B["new()"] C["full()"] D["mask(bits)"] E["from_value(data)"] F["from_raw_bits(value)"] G["one_shot(index)"] H["From trait implementations"] I["Bitmap::new()"] J["Bitmap::mask(SIZE)"] K["Bitmap::mask(bits)"] L["Bitmap::from_value(data)"] M["Manual bit setting loop"] N["Bitmap::new() + set(index, true)"] O["Array to Bitmap conversion"] P["CpuMask { value: bitmap }"] A --> B A --> C A --> D A --> E A --> F A --> G A --> H B --> I C --> J D --> K E --> L F --> M G --> N H --> O I --> P J --> P K --> P L --> P M --> P N --> P O --> P
Sources: src/lib.rs(L74 - L128)
Array-Based Construction
For larger cpumasks that use array backing storage, the library provides From
trait implementations for u128
arrays of various sizes. These implementations support cpumasks with sizes 256, 384, 512, 640, 768, 896, and 1024 bits:
flowchart TD A["[u128; 2]"] B["CpuMask<256>"] C["[u128; 3]"] D["CpuMask<384>"] E["[u128; 4]"] F["CpuMask<512>"] G["[u128; 5]"] H["CpuMask<640>"] I["[u128; 6]"] J["CpuMask<768>"] K["[u128; 7]"] L["CpuMask<896>"] M["[u128; 8]"] N["CpuMask<1024>"] O["cpumask.into()"] A --> B B --> O C --> D D --> O E --> F F --> O G --> H H --> O I --> J J --> O K --> L L --> O M --> N N --> O
Sources: src/lib.rs(L328 - L368)
Conversion Methods
The CpuMask
struct provides multiple methods for extracting data in different formats, enabling interoperability with various system interfaces and storage requirements.
Value Extraction
The primary conversion methods expose the underlying storage in different forms:
The into_value(self)
method src/lib.rs(L131 - L134) consumes the cpumask and returns the backing store value. The as_value(&self)
method src/lib.rs(L137 - L140) provides a reference to the backing store without transferring ownership. The as_bytes(&self)
method src/lib.rs(L143 - L146) returns a byte slice representation of the cpumask data.
Bidirectional Array Conversion
The library implements bidirectional conversion between CpuMask
instances and u128
arrays, enabling seamless integration with external APIs that expect array representations:
flowchart TD A["CpuMask<256>"] B["[u128; 2]"] C["CpuMask<384>"] D["[u128; 3]"] E["CpuMask<512>"] F["[u128; 4]"] G["CpuMask<640>"] H["[u128; 5]"] I["CpuMask<768>"] J["[u128; 6]"] K["CpuMask<896>"] L["[u128; 7]"] M["CpuMask<1024>"] N["[u128; 8]"] A --> B B --> A C --> D D --> C E --> F F --> E G --> H H --> G I --> J J --> I K --> L L --> K M --> N N --> M
Sources: src/lib.rs(L370 - L410)
Storage Type Relationships
The conversion methods interact with the automatic storage optimization system, where the backing store type depends on the SIZE
parameter:
flowchart TD A["SIZE Parameter"] B["Storage Selection"] C["bool"] D["u8"] E["u16"] F["u32"] G["u64"] H["u128"] I["[u128; N]"] J["from_value(bool)"] K["from_value(u8)"] L["from_value(u16)"] M["from_value(u32)"] N["from_value(u64)"] O["from_value(u128)"] P["from_value([u128; N])"] Q["CpuMask<1>"] R["CpuMask"] S["CpuMask"] A --> B B --> C B --> D B --> E B --> F B --> G B --> H B --> I C --> J D --> K E --> L F --> M G --> N H --> O I --> P J --> Q K --> R L --> R M --> R N --> R O --> R P --> S
Sources: src/lib.rs(L97 - L102) src/lib.rs(L12 - L16)
Type Safety and Validation
Construction methods incorporate compile-time and runtime validation to ensure cpumask integrity:
- The
mask(bits)
method includes a debug assertion src/lib.rs(L90) thatbits <= SIZE
- The
from_raw_bits(value)
method validates src/lib.rs(L107) thatvalue >> SIZE == 0
- The
one_shot(index)
method asserts src/lib.rs(L124) thatindex < SIZE
- Array-based
From
implementations use const generics to enforce size compatibility at compile time
These validation mechanisms prevent buffer overflows and ensure that cpumask operations remain within the defined size boundaries.
Sources: src/lib.rs(L90) src/lib.rs(L107) src/lib.rs(L124) src/lib.rs(L328 - L410)
Query and Inspection Operations
Relevant source files
This section documents the read-only operations available on CpuMask<SIZE>
instances for examining their state, testing individual bits, and finding set or unset CPU positions. These operations do not modify the cpumask and provide various ways to inspect its contents.
For information about creating new cpumask instances, see Construction and Conversion Methods. For operations that modify cpumask state, see Modification and Iteration.
Overview of Query Operations
The CpuMask
struct provides comprehensive query capabilities organized into several functional categories. These operations leverage the underlying bitmaps::Bitmap
functionality while providing a CPU-specific interface.
flowchart TD subgraph subGraph1["Return Types"] U["bool"] V["bool / usize"] W["Option"] X["Option"] Y["Store / &Store / &[u8]"] end subgraph subGraph0["CpuMask Query API"] A["get(index)"] B["Basic Bit Testing"] C["len()"] D["Size and State Queries"] E["is_empty()"] F["is_full()"] G["first_index()"] H["True Bit Location"] I["last_index()"] J["next_index(index)"] K["prev_index(index)"] L["first_false_index()"] M["False Bit Location"] N["last_false_index()"] O["next_false_index(index)"] P["prev_false_index(index)"] Q["as_value()"] R["Value Access"] S["into_value()"] T["as_bytes()"] end A --> B B --> U C --> D D --> V E --> D F --> D G --> H H --> W I --> H J --> H K --> H L --> M M --> X N --> M O --> M P --> M Q --> R R --> Y S --> R T --> R
Sources: src/lib.rs(L148 - L234)
Basic Bit Testing
get Method
The get
method tests whether a specific CPU bit is set in the mask.
Method | Signature | Description | Performance |
---|---|---|---|
get | get(self, index: usize) -> bool | Returnstrueif the bit atindexis set | O(1) |
The method performs bounds checking in debug builds and delegates to the underlying BitsImpl<SIZE>::Store::get
implementation.
flowchart TD A["CpuMask::get(index)"] B["debug_assert!(index < SIZE)"] C["self.into_value()"] D["BitsImpl::Store::get(&value, index)"] E["bool result"] A --> B B --> C C --> D D --> E
Sources: src/lib.rs(L167 - L171)
Size and State Queries
These methods provide high-level information about the cpumask's state without requiring iteration through individual bits.
Method | Signature | Description | Performance |
---|---|---|---|
len | len(self) -> usize | Count oftruebits in the mask | O(n) for most storage types |
is_empty | is_empty(self) -> bool | Tests if no bits are set | O(log n) |
is_full | is_full(self) -> bool | Tests if all valid bits are set | O(log n) |
Implementation Details
The is_empty
and is_full
methods are implemented in terms of the index-finding operations:
flowchart TD A["is_empty()"] B["first_index().is_none()"] C["is_full()"] D["first_false_index().is_none()"] E["Check if any bit is true"] F["Check if any bit is false"] G["len()"] H["self.value.len()"] I["Bitmap::len()"] A --> B B --> E C --> D D --> F G --> H H --> I
Sources: src/lib.rs(L149 - L164)
Index Finding Operations
The cpumask provides efficient methods for locating set and unset bits, supporting both forward and backward traversal patterns commonly used in CPU scheduling algorithms.
True Bit Location Methods
Method | Signature | Description |
---|---|---|
first_index | first_index(self) -> Option | Find first set bit |
last_index | last_index(self) -> Option | Find last set bit |
next_index | next_index(self, index: usize) -> Option | Find next set bit afterindex |
prev_index | prev_index(self, index: usize) -> Option | Find previous set bit beforeindex |
False Bit Location Methods
Method | Signature | Description |
---|---|---|
first_false_index | first_false_index(self) -> Option | Find first unset bit |
last_false_index | last_false_index(self) -> Option | Find last unset bit |
next_false_index | next_false_index(self, index: usize) -> Option | Find next unset bit afterindex |
prev_false_index | prev_false_index(self, index: usize) -> Option | Find previous unset bit beforeindex |
Delegation to BitsImpl
All index-finding operations delegate to the corresponding methods on the underlying storage type, with false-bit methods using corrected implementations that respect the SIZE
boundary:
flowchart TD subgraph subGraph2["Storage Access"] Q["self.into_value()"] R["as Bits>::Store"] end subgraph subGraph1["False Bit Methods"] I["first_false_index()"] M["BitsImpl::corrected_first_false_index()"] J["last_false_index()"] N["BitsImpl::corrected_last_false_index()"] K["next_false_index(index)"] O["BitsImpl::corrected_next_false_index()"] L["prev_false_index(index)"] P["BitsImpl::Store::prev_false_index()"] end subgraph subGraph0["True Bit Methods"] A["first_index()"] E["BitsImpl::Store::first_index()"] B["last_index()"] F["BitsImpl::Store::last_index()"] C["next_index(index)"] G["BitsImpl::Store::next_index()"] D["prev_index(index)"] H["BitsImpl::Store::prev_index()"] end A --> E B --> F C --> G D --> H E --> Q F --> Q G --> Q H --> Q I --> M J --> N K --> O L --> P M --> Q N --> Q O --> Q P --> Q Q --> R
Sources: src/lib.rs(L183 - L228)
Value Access Operations
These methods provide direct access to the underlying storage representation of the cpumask, enabling interoperability with external systems and efficient bulk operations.
Method | Signature | Description | Usage |
---|---|---|---|
into_value | into_value(self) -> <BitsImpl | Consume and return backing store | Move semantics |
as_value | as_value(&self) -> &<BitsImpl | Reference to backing store | Borrowed access |
as_bytes | as_bytes(&self) -> &[u8] | View as byte slice | Serialization |
Storage Type Mapping
The actual storage type returned by into_value
and referenced by as_value
depends on the SIZE
parameter:
flowchart TD A["SIZE Parameter"] B["Storage Selection"] C["bool"] D["u8"] E["u16"] F["u32"] G["u64"] H["u128"] I["[u128; N]"] J["into_value() -> bool"] K["into_value() -> u8"] L["into_value() -> u16"] M["into_value() -> u32"] N["into_value() -> u64"] O["into_value() -> u128"] P["into_value() -> [u128; N]"] A --> B B --> C B --> D B --> E B --> F B --> G B --> H B --> I C --> J D --> K E --> L F --> M G --> N H --> O I --> P
Sources: src/lib.rs(L131 - L146)
Performance Characteristics
The query operations exhibit different performance characteristics based on the underlying storage type and operation complexity:
Operation Category | Time Complexity | Notes |
---|---|---|
get(index) | O(1) | Direct bit access |
len() | O(n) | Requires bit counting |
is_empty(),is_full() | O(log n) | Uses optimized bit scanning |
Index finding | O(log n) to O(n) | Depends on bit density and hardware support |
Value access | O(1) | Direct storage access |
The actual performance depends heavily on the target architecture's bit manipulation instructions and the density of set bits in the mask.
Sources: src/lib.rs(L148 - L234)
Modification and Iteration
Relevant source files
This page documents the CpuMask
methods and traits for modifying mask state after construction and for iterating over set CPU indices. For mask construction methods, see Construction and Conversion Methods. For read-only query operations, see Query and Inspection Operations. For bitwise operations and operator overloads, see Bitwise Operations and Traits.
Modification Operations
The CpuMask
provides two primary methods for modifying the state of a mask after construction: individual bit manipulation via set
and bulk inversion via invert
.
Bit Manipulation
The set
method allows modification of individual CPU bits within the mask. It takes an index and a boolean value, sets the bit at that position, and returns the previous value of that bit.
flowchart TD A["CpuMask::set(index, value)"] B["debug_assert!(index < SIZE)"] C["self.value.set(index, value)"] D["Returns previous bit value"] E["Input: index: usize"] F["Input: value: bool"] A --> B B --> C C --> D E --> A F --> A
Sources: src/lib.rs(L177 - L180)
The method includes a debug assertion to ensure the index is within the valid range (less than SIZE
). The actual bit manipulation is delegated to the underlying Bitmap::set
method from the bitmaps crate.
Bulk Inversion
The invert
method provides a way to flip all bits in the mask simultaneously, converting true
bits to false
and vice versa.
flowchart TD A["CpuMask::invert()"] B["self.value.invert()"] C["All bits flipped"] D["Before: 101010"] E["After: 010101"] A --> B B --> C D --> A E --> C
Sources: src/lib.rs(L232 - L234)
This operation is useful for computing the complement of a CPU set, such as finding all CPUs that are not currently assigned to a particular task.
Iteration Interface
The CpuMask
implements comprehensive iteration support through the IntoIterator
trait and a custom Iter
struct that provides both forward and backward iteration over set CPU indices.
Iterator Implementation Architecture
flowchart TD A["&CpuMask"] B["IntoIterator trait"] C["Iter<'a, SIZE>"] D["Iterator trait"] E["DoubleEndedIterator trait"] F["next() -> Option"] G["next_back() -> Option"] H["head: Option"] I["tail: Option"] J["data: &'a CpuMask"] A --> B B --> C C --> D C --> E D --> F E --> G H --> C I --> C J --> C
Sources: src/lib.rs(L237 - L251) src/lib.rs(L429 - L436) src/lib.rs(L438 - L480) src/lib.rs(L482 - L518)
Iterator State Management
The Iter
struct maintains iteration state using two optional indices:
Field | Type | Purpose |
---|---|---|
head | Option | Tracks forward iteration position |
tail | Option | Tracks backward iteration position |
data | &'a CpuMask | Reference to the mask being iterated |
The iterator is initialized with head
set to None
and tail
set to Some(SIZE + 1)
, indicating that iteration can proceed in both directions from the extremes.
Forward Iteration Logic
flowchart TD A["next() called"] B["head == None?"] C["result = data.first_index()"] D["index >= SIZE?"] E["result = None"] F["result = data.next_index(index)"] G["result found?"] H["Check tail boundary"] I["head = SIZE + 1"] J["tail < index?"] K["Stop iteration"] L["head = index"] M["Return None"] N["Return Some(index)"] A --> B B --> C B --> D C --> G D --> E D --> F E --> G F --> G G --> H G --> I H --> J I --> M J --> K J --> L K --> M L --> N
Sources: src/lib.rs(L444 - L479)
Backward Iteration Logic
The DoubleEndedIterator
implementation provides next_back
functionality with similar boundary checking but in reverse:
flowchart TD A["next_back() called"] B["tail == None?"] C["result = None"] D["index >= SIZE?"] E["result = data.last_index()"] F["result = data.prev_index(index)"] G["result found?"] H["Check head boundary"] I["tail = None"] J["head > index?"] K["Stop iteration"] L["tail = index"] M["Return None"] N["Return Some(index)"] A --> B B --> C B --> D C --> G D --> E D --> F E --> G F --> G G --> H G --> I H --> J I --> M J --> K J --> L K --> M L --> N
Sources: src/lib.rs(L486 - L517)
Usage Patterns
Modification Examples
The modification operations enable dynamic CPU mask management:
Operation | Purpose | Method Call |
---|---|---|
Enable CPU | Add CPU to active set | mask.set(cpu_id, true) |
Disable CPU | Remove CPU from active set | mask.set(cpu_id, false) |
Toggle all CPUs | Invert entire mask | mask.invert() |
Iteration Examples
The iterator yields usize
indices representing CPU numbers that are set to true
in the mask:
// Iterate through all active CPUs
for cpu_id in &mask {
// cpu_id is usize, represents an active CPU
}
// Collect to vector for further processing
let active_cpus: Vec<usize> = mask.into_iter().collect();
// Reverse iteration for priority-based processing
for cpu_id in mask.into_iter().rev() {
// Process from highest to lowest CPU index
}
The implementation ensures that only indices with true
bits are yielded, making it efficient for sparse CPU masks where only a few CPUs are active.
Sources: src/lib.rs(L419 - L427) src/lib.rs(L31 - L32)
Bitwise Operations and Traits
Relevant source files
This page documents the operator overloads, trait implementations, and set operations available on CpuMask
instances. These operations enable intuitive mathematical set operations, comparisons, and conversions between CPU mask representations.
For information about basic construction and conversion methods, see Construction and Conversion Methods. For details about query operations and bit manipulation, see Query and Inspection Operations.
Bitwise Set Operations
The CpuMask
type implements standard bitwise operators that correspond to mathematical set operations on CPU collections.
Binary Operators
The CpuMask
struct implements four core binary bitwise operations:
Operator | Trait | Set Operation | Description |
---|---|---|---|
& | BitAnd | Intersection | CPUs present in both masks |
| | BitOr | Union | CPUs present in either mask |
^ | BitXor | Symmetric Difference | CPUs present in exactly one mask |
! | Not | Complement | Invert all CPU bits |
Each operator returns a new CpuMask
instance without modifying the operands.
flowchart TD subgraph subGraph1["Implementation Details"] J["self.value.bitand(rhs.value)"] K["self.value.bitor(rhs.value)"] L["self.value.bitxor(rhs.value)"] M["self.value.not()"] end subgraph subGraph0["Bitwise Operations"] A["CpuMask"] B["& (BitAnd)"] C["| (BitOr)"] D["^ (BitXor)"] E["! (Not)"] F["Intersection"] G["Union"] H["Symmetric Difference"] I["Complement"] end A --> B A --> C A --> D A --> E B --> F C --> G D --> H E --> I F --> J G --> K H --> L I --> M
Sources: src/lib.rs(L253 - L299)
Assignment Operators
The CpuMask
type also implements in-place assignment variants for efficiency:
Operator | Trait | Description |
---|---|---|
&= | BitAndAssign | In-place intersection |
|= | BitOrAssign | In-place union |
^= | BitXorAssign | In-place symmetric difference |
These operators modify the left operand directly, avoiding temporary allocations.
flowchart TD subgraph subGraph0["Assignment Operations"] A["cpumask1 &= cpumask2"] B["BitAndAssign::bitand_assign"] C["cpumask1 |= cpumask2"] D["BitOrAssign::bitor_assign"] E["cpumask1 ^= cpumask2"] F["BitXorAssign::bitxor_assign"] G["self.value.bitand_assign(rhs.value)"] H["self.value.bitor_assign(rhs.value)"] I["self.value.bitxor_assign(rhs.value)"] end A --> B B --> G C --> D D --> H E --> F F --> I
Sources: src/lib.rs(L301 - L326)
Comparison and Ordering Traits
The CpuMask
type implements a complete hierarchy of comparison traits, enabling sorting and ordering operations on CPU mask collections.
Trait Implementation Hierarchy
flowchart TD subgraph subGraph1["Implementation Details"] F["self.value.as_value().partial_cmp(other.value.as_value())"] G["self.value.as_value().cmp(other.value.as_value())"] end subgraph subGraph0["Comparison Traits"] A["PartialEq"] B["Eq"] C["PartialOrd"] D["Ord"] E["CpuMask implements"] end A --> B B --> C C --> D C --> F D --> G E --> A E --> B E --> C E --> D
The comparison operations delegate to the underlying storage type's comparison methods, ensuring consistent ordering across different storage implementations.
Sources: src/lib.rs(L17) src/lib.rs(L48 - L66)
Utility Traits
Debug Formatting
The Debug
trait provides a human-readable representation showing the indices of set CPU bits:
Sources: src/lib.rs(L25 - L36)
Hash Implementation
The Hash
trait enables use of CpuMask
instances as keys in hash-based collections:
flowchart TD A["Hash::hash"] B["self.value.as_value().hash(state)"] C["Delegates to underlying storage hash"] A --> B B --> C
The hash implementation requires that the underlying storage type implement Hash
, which is satisfied by all primitive integer types used for storage.
Sources: src/lib.rs(L38 - L46)
Derived Traits
The CpuMask
struct automatically derives several fundamental traits:
Trait | Purpose |
---|---|
Clone | Creates deep copies of CPU masks |
Copy | Enables pass-by-value for smaller masks |
Default | Creates empty CPU masks |
Eq | Enables exact equality comparisons |
PartialEq | Enables equality comparisons |
Sources: src/lib.rs(L17)
Conversion Traits
The library provides specialized From
trait implementations for converting between CpuMask
instances and u128
arrays for larger mask sizes.
Array Conversion Support
flowchart TD subgraph subGraph1["From CpuMask to Array"] I["[u128; 2]"] J["[u128; 3]"] K["[u128; 4]"] L["[u128; 8]"] end subgraph subGraph0["From Array to CpuMask"] A["[u128; 2]"] B["CpuMask<256>"] C["[u128; 3]"] D["CpuMask<384>"] E["[u128; 4]"] F["CpuMask<512>"] G["[u128; 8]"] H["CpuMask<1024>"] end A --> B B --> I C --> D D --> J E --> F F --> K G --> H H --> L
These implementations enable seamless conversion between raw array representations and type-safe CPU mask instances for masks requiring more than 128 bits of storage.
Sources: src/lib.rs(L328 - L410)
Iterator Trait Implementation
The IntoIterator
trait enables CpuMask
instances to be used directly in for
loops and with iterator methods.
Iterator Implementation Structure
flowchart TD subgraph subGraph1["Internal State"] H["head: Option"] I["tail: Option"] J["data: &'a CpuMask"] end subgraph subGraph0["Iterator Pattern"] A["&CpuMask"] B["IntoIterator::into_iter"] C["Iter<'a, SIZE>"] D["Iterator::next"] E["DoubleEndedIterator::next_back"] F["Returns Option"] G["Returns Option"] end A --> B B --> C C --> D C --> E C --> H C --> I C --> J D --> F E --> G
The iterator yields the indices of set bits as usize
values, enabling direct access to CPU indices without needing to manually scan the mask.
Sources: src/lib.rs(L237 - L251) src/lib.rs(L412 - L518)
Architecture and Design
Relevant source files
This document covers the internal implementation details, storage optimization strategy, performance characteristics, and architectural decisions of the cpumask library. It explains how the CpuMask
struct integrates with the bitmaps
crate to provide efficient CPU affinity management. For API usage patterns and practical examples, see Usage Guide and Examples. For complete API documentation, see API Reference.
Core Architecture
The cpumask library is built around a single primary type CpuMask<const SIZE: usize>
that provides a type-safe, compile-time sized bitmap for CPU affinity management. The architecture leverages Rust's const generics and the bitmaps
crate's automatic storage optimization to provide efficient memory usage across different CPU count scenarios.
Primary Components
flowchart TD subgraph subGraph2["Concrete Storage Types"] E["bool (SIZE=1)"] F["u8 (SIZE≤8)"] G["u16 (SIZE≤16)"] H["u32 (SIZE≤32)"] I["u64 (SIZE≤64)"] J["u128 (SIZE≤128)"] K["[u128; N] (SIZE>128)"] end subgraph subGraph1["Storage Abstraction"] B["Bitmap"] C["BitsImpl"] D["Bits trait"] end subgraph subGraph0["User API Layer"] A["CpuMask"] end L["IntoIterator"] M["BitOps traits"] N["Hash, PartialOrd, Ord"] A --> B A --> L A --> M A --> N B --> C C --> D D --> E D --> F D --> G D --> H D --> I D --> J D --> K
Core Architecture Design
The CpuMask
struct wraps a Bitmap<SIZE>
from the bitmaps crate, which automatically selects the most efficient storage type based on the compile-time SIZE
parameter. The BitsImpl<SIZE>
type alias resolves to the appropriate concrete storage type, and the Bits
trait provides uniform operations across all storage variants.
Sources: src/lib.rs(L18 - L23) src/lib.rs(L7) src/lib.rs(L98 - L102)
Type System Integration
flowchart TD subgraph subGraph2["Operational Traits"] H["BitAnd, BitOr, BitXor"] I["Not"] J["BitAndAssign, BitOrAssign, BitXorAssign"] K["IntoIterator"] end subgraph subGraph1["Trait Bounds"] C["Default"] D["Clone, Copy"] E["Eq, PartialEq"] F["Hash"] G["PartialOrd, Ord"] end subgraph subGraph0["Const Generic System"] A["const SIZE: usize"] B["BitsImpl: Bits"] end A --> B B --> C B --> D B --> E B --> F B --> G B --> H B --> I B --> J B --> K
Type System Design
The library extensively uses Rust's trait system to provide consistent behavior across different storage types. All traits are conditionally implemented based on the capabilities of the underlying storage type, ensuring type safety while maintaining performance.
Sources: src/lib.rs(L17) src/lib.rs(L25 - L66) src/lib.rs(L253 - L326)
Storage Optimization Strategy
The most significant architectural feature is the automatic storage optimization that occurs at compile time based on the SIZE
parameter. This optimization is handled entirely by the bitmaps
crate's BitsImpl
type system.
Storage Selection Logic
flowchart TD A["Compile-time SIZE parameter"] B["SIZE == 1?"] C["bool storage1 bit"] D["SIZE <= 8?"] E["u8 storage8 bits"] F["SIZE <= 16?"] G["u16 storage16 bits"] H["SIZE <= 32?"] I["u32 storage32 bits"] J["SIZE <= 64?"] K["u64 storage64 bits"] L["SIZE <= 128?"] M["u128 storage128 bits"] N["[u128; N] storageN = (SIZE + 127) / 128"] O["Memory: 1 byte"] P["Memory: 1 byte"] Q["Memory: 2 bytes"] R["Memory: 4 bytes"] S["Memory: 8 bytes"] T["Memory: 16 bytes"] U["Memory: 16 * N bytes"] A --> B B --> C B --> D C --> O D --> E D --> F E --> P F --> G F --> H G --> Q H --> I H --> J I --> R J --> K J --> L K --> S L --> M L --> N M --> T N --> U
Storage Selection Algorithm
The storage selection is determined by the BitsImpl<SIZE>
type alias from the bitmaps crate. This provides optimal memory usage by selecting the smallest storage type that can accommodate the required number of bits, with special handling for the single-bit case using bool
.
Sources: src/lib.rs(L12 - L16) src/lib.rs(L20)
Array Storage for Large Sizes
For CPU masks exceeding 128 bits, the library uses arrays of u128
values. The implementation provides explicit conversion traits for common large sizes used in high-core-count systems.
Size | Storage Type | Conversion Traits |
---|---|---|
256 | [u128; 2] | From<[u128; 2]>,Into<[u128; 2]> |
384 | [u128; 3] | From<[u128; 3]>,Into<[u128; 3]> |
512 | [u128; 4] | From<[u128; 4]>,Into<[u128; 4]> |
640 | [u128; 5] | From<[u128; 5]>,Into<[u128; 5]> |
768 | [u128; 6] | From<[u128; 6]>,Into<[u128; 6]> |
896 | [u128; 7] | From<[u128; 7]>,Into<[u128; 7]> |
1024 | [u128; 8] | From<[u128; 8]>,Into<[u128; 8]> |
Sources: src/lib.rs(L328 - L410)
Integration with Bitmaps Crate
The cpumask library is designed as a domain-specific wrapper around the bitmaps
crate, leveraging its efficient bitmap implementation while providing CPU-focused semantics and additional functionality.
Delegation Pattern
flowchart TD subgraph subGraph1["Bitmap Delegation"] I["Bitmap::new()"] J["Bitmap::mask(SIZE)"] K["Bitmap::mask(bits)"] L["Store::get()"] M["Bitmap::set()"] N["Bitmap::len()"] O["Store::first_index()"] P["Bitmap::invert()"] end subgraph subGraph0["CpuMask Methods"] A["new()"] B["full()"] C["mask(bits)"] D["get(index)"] E["set(index, value)"] F["len()"] G["first_index()"] H["invert()"] end A --> I B --> J C --> K D --> L E --> M F --> N G --> O H --> P
Method Delegation Architecture
Most CpuMask
methods directly delegate to the underlying Bitmap<SIZE>
implementation, providing a thin wrapper that maintains the CPU-specific semantics while leveraging the optimized bitmap operations.
Sources: src/lib.rs(L74 - L84) src/lib.rs(L167 - L234)
Storage Access Methods
The library provides multiple ways to access the underlying storage, enabling efficient integration with system APIs that expect raw bit representations:
flowchart TD A["CpuMask"] B["into_value()"] C["as_value()"] D["as_bytes()"] E["from_value(data)"] F["as Bits>::Store"] G["& as Bits>::Store"] H["&[u8]"] I["CpuMask"] J["Direct storage access"] K["Borrowed storage access"] L["Byte array representation"] M["Construction from storage"] A --> B A --> C A --> D A --> E B --> F C --> G D --> H E --> I F --> J G --> K H --> L I --> M
Storage Access Patterns
These methods enable zero-copy conversions between CpuMask
and its underlying storage representation, facilitating efficient interaction with system calls and external APIs that work with raw bit data.
Sources: src/lib.rs(L131 - L146)
Performance Characteristics
The architectural design prioritizes performance through several key strategies:
Operation Complexity
Operation Category | Time Complexity | Space Complexity | Implementation |
---|---|---|---|
Construction | O(1) | O(1) | Direct storage initialization |
Single bit access (get,set) | O(1) | O(1) | Direct bit manipulation |
Bit counting (len) | O(log n) | O(1) | Hardware population count |
Index finding (first_index,last_index) | O(log n) | O(1) | Hardware bit scan |
Iteration | O(k) | O(1) | Where k = number of set bits |
Bitwise operations | O(1) | O(1) | Single instruction for small sizes |
Memory Layout Optimization
flowchart TD subgraph subGraph1["Performance Impact"] E["Cache-friendly access"] F["Minimal memory overhead"] G["SIMD-compatible alignment"] H["Efficient copying"] end subgraph subGraph0["Memory Layout Examples"] A["CpuMask<4>Storage: u8Memory: 1 byteAlignment: 1"] B["CpuMask<16>Storage: u16Memory: 2 bytesAlignment: 2"] C["CpuMask<64>Storage: u64Memory: 8 bytesAlignment: 8"] D["CpuMask<256>Storage: [u128; 2]Memory: 32 bytesAlignment: 16"] end A --> E B --> F C --> G D --> H
Memory Efficiency Design
The automatic storage selection ensures that CPU masks consume the minimum possible memory while maintaining optimal alignment for the target architecture. This reduces cache pressure and enables efficient copying and comparison operations.
Sources: src/lib.rs(L12 - L16)
Iterator Implementation
The iterator design provides efficient traversal of set bits without allocating additional memory, supporting both forward and backward iteration.
Iterator State Management
stateDiagram-v2 [*] --> Initial Initial --> Forward : "next()" Initial --> Backward : "next_back()" Forward --> ForwardActive : "found index" Forward --> Exhausted : "no more indices" Backward --> BackwardActive : "found index" Backward --> Exhausted : "no more indices" ForwardActive --> Forward : "next()" ForwardActive --> Collision : "head >= tail" BackwardActive --> Backward : "next_back()" BackwardActive --> Collision : "head >= tail" Collision --> Exhausted Exhausted --> [*]
Iterator State Machine
The Iter<'a, SIZE>
struct maintains head and tail pointers to support bidirectional iteration while detecting when the iterators meet to avoid double-yielding indices.
Sources: src/lib.rs(L428 - L518)
Iterator Performance
The iterator implementation leverages the underlying bitmap's efficient index-finding operations:
first_index()
: Hardware-accelerated bit scanningnext_index(index)
: Continues from previous positionprev_index(index)
: Reverse bit scanning- Memory usage: Fixed 24 bytes (two
Option<usize>
+ reference)
Sources: src/lib.rs(L444 - L479) src/lib.rs(L486 - L517)
API Design Patterns
The library follows consistent design patterns that enhance usability and performance:
Constructor Patterns
flowchart TD subgraph subGraph1["Use Cases"] G["Empty initialization"] H["All CPUs available"] I["Range-based masks"] J["Single CPU selection"] K["Integer conversion"] L["Type-safe conversion"] end subgraph subGraph0["Construction Methods"] A["new()"] B["full()"] C["mask(bits)"] D["one_shot(index)"] E["from_raw_bits(value)"] F["from_value(data)"] end A --> G B --> H C --> I D --> J E --> K F --> L
Constructor Design Philosophy
Each constructor serves a specific use case common in CPU affinity management, from empty masks for incremental building to full masks for restriction-based workflows.
Sources: src/lib.rs(L72 - L128)
Error Handling Strategy
The library uses a combination of compile-time guarantees and runtime assertions to ensure correctness:
- Compile-time:
SIZE
parameter ensures storage adequacy - Debug assertions: Index bounds checking in debug builds
- Runtime panics: Explicit validation for user-provided values
Sources: src/lib.rs(L90) src/lib.rs(L107) src/lib.rs(L124) src/lib.rs(L169) src/lib.rs(L178)
This architectural design provides a balance between performance, safety, and usability, making the cpumask library suitable for high-performance operating system kernels while maintaining Rust's safety guarantees.
Usage Guide and Examples
Relevant source files
This page provides practical examples and common usage patterns for the cpumask
library in operating system contexts. It demonstrates how to effectively use the CpuMask<SIZE>
struct for CPU affinity management, process scheduling, and system resource allocation.
For detailed API documentation, see API Reference. For implementation details and performance characteristics, see Architecture and Design.
Basic Usage Patterns
Creating CPU Masks
The most common way to start working with CPU masks is through the various construction methods provided by CpuMask<SIZE>
:
flowchart TD A["CpuMask::new()"] B["Empty mask - all false"] C["CpuMask::full()"] D["Full mask - all true"] E["CpuMask::mask(bits)"] F["Range mask - first bits true"] G["CpuMask::one_shot(index)"] H["Single CPU mask"] I["CpuMask::from_raw_bits(value)"] J["From usize value"] K["CpuMask::from_value(data)"] L["From backing store type"] M["Basic CPU set operations"] A --> B B --> M C --> D D --> M E --> F F --> M G --> H H --> M I --> J J --> M K --> L L --> M
Sources: src/lib.rs(L72 - L128)
Basic Manipulation Operations
The fundamental operations for working with individual CPU bits follow a consistent pattern:
Operation | Method | Description | Return Value |
---|---|---|---|
Test bit | get(index) | Check if CPU is in mask | bool |
Set bit | set(index, value) | Add/remove CPU from mask | Previousboolvalue |
Count bits | len() | Number of CPUs in mask | usize |
Test empty | is_empty() | Check if no CPUs set | bool |
Test full | is_full() | Check if all CPUs set | bool |
Sources: src/lib.rs(L148 - L179)
Finding CPUs in Masks
The library provides efficient methods for locating specific CPUs within masks:
flowchart TD A["CpuMask"] B["first_index()"] C["last_index()"] D["next_index(current)"] E["prev_index(current)"] F["Inverse operations"] G["first_false_index()"] H["last_false_index()"] I["next_false_index(current)"] J["prev_false_index(current)"] K["Option<usize>"] A --> B A --> C A --> D A --> E B --> K C --> K D --> K E --> K F --> G F --> H F --> I F --> J G --> K H --> K I --> K J --> K
Sources: src/lib.rs(L182 - L228)
Set Operations and Logic
Bitwise Operations
CPU masks support standard bitwise operations for combining and manipulating sets of CPUs:
flowchart TD A["mask1: CpuMask<N>"] E["mask1 & mask2"] B["mask2: CpuMask<N>"] F["Intersection - CPUs in both"] G["mask1 | mask2"] H["Union - CPUs in either"] I["mask1 ^ mask2"] J["Symmetric difference - CPUs in one but not both"] K["!mask1"] L["Complement - inverse of mask"] M["Assignment variants"] N["&=, |=, ^="] O["In-place operations"] A --> E A --> G A --> I A --> K B --> E B --> G B --> I E --> F G --> H I --> J K --> L M --> N N --> O
Sources: src/lib.rs(L253 - L326)
Practical Set Operations Example
Consider a scenario where you need to manage CPU allocation for different process types:
- System processes - CPUs 0-3 (reserved for kernel)
- User processes - CPUs 4-15 (general workload)
- Real-time processes - CPUs 12-15 (high-performance subset)
This demonstrates how set operations enable sophisticated CPU management policies.
Sources: src/lib.rs(L253 - L287)
Iteration and Traversal
Forward and Backward Iteration
The CpuMask
implements both Iterator
and DoubleEndedIterator
, enabling flexible traversal patterns:
Sources: src/lib.rs(L237 - L518)
Iteration Use Cases
The iterator yields CPU indices where the bit is set to true
, making it ideal for:
- Process scheduling: Iterate through available CPUs for task assignment
- Load balancing: Traverse CPU sets to find optimal placement
- Resource monitoring: Check specific CPU states in sequence
- Affinity enforcement: Validate process can run on assigned CPUs
Sources: src/lib.rs(L412 - L427)
Operating System Scenarios
Process Scheduling Workflow
This diagram shows how CpuMask
integrates into a typical process scheduler:
flowchart TD A["Process creation"] B["Determine CPU affinity policy"] C["CpuMask::new() or policy-specific constructor"] D["Store affinity in process control block"] E["Scheduler activation"] F["Load process affinity mask"] G["available_cpus & process_affinity"] H["Any CPUs available?"] I["Select CPU from mask"] J["Wait or migrate"] K["Schedule process on selected CPU"] L["Update available_cpus"] M["CPU hotplug event"] N["Update system CPU mask"] O["Revalidate all process affinities"] P["Migrate processes if needed"] A --> B B --> C C --> D E --> F F --> G G --> H H --> I H --> J I --> K J --> L L --> G M --> N N --> O O --> P
Sources: src/lib.rs(L9 - L23)
NUMA-Aware CPU Management
In NUMA systems, CPU masks help maintain memory locality:
flowchart TD subgraph subGraph1["NUMA Node 1"] C["CPUs 8-15"] D["Local Memory"] end subgraph subGraph0["NUMA Node 0"] A["CPUs 0-7"] B["Local Memory"] end E["Process A"] F["CpuMask::mask(8) - Node 0 only"] G["Process B"] H["CpuMask::from_raw_bits(0xFF00) - Node 1 only"] I["System Process"] J["CpuMask::full() - Any CPU"] K["Load balancer decides"] E --> F F --> A G --> H H --> C I --> J J --> K K --> A K --> C
Sources: src/lib.rs(L86 - L94) src/lib.rs(L106 - L119)
Thread Affinity Management
For multi-threaded applications, CPU masks enable fine-grained control:
Thread Type | Affinity Strategy | Implementation |
---|---|---|
Main thread | Single CPU | CpuMask::one_shot(0) |
Worker threads | Subset of CPUs | CpuMask::mask(worker_count) |
I/O threads | Non-main CPUs | !CpuMask::one_shot(0) |
RT threads | Dedicated CPUs | CpuMask::from_raw_bits(rt_mask) |
Sources: src/lib.rs(L121 - L128) src/lib.rs(L289 - L299)
Advanced Usage Patterns
Dynamic CPU Set Management
Real systems require dynamic adjustment of CPU availability:
Sources: src/lib.rs(L177 - L180) src/lib.rs(L301 - L326)
Performance Optimization Strategies
The choice of SIZE
parameter significantly impacts performance:
System Size | Recommended SIZE | Storage Type | Use Case |
---|---|---|---|
Single core | SIZE = 1 | bool | Embedded systems |
Small SMP (≤8) | SIZE = 8 | u8 | IoT devices |
Desktop/Server (≤32) | SIZE = 32 | u32 | General purpose |
Large server (≤128) | SIZE = 128 | u128 | HPC systems |
Massive systems | SIZE = 1024 | [u128; 8] | Cloud infrastructure |
The automatic storage selection in src/lib.rs(L12 - L16) ensures optimal memory usage and performance characteristics for each size category.
Error Handling Patterns
While CpuMask
operations are generally infallible, certain usage patterns require validation:
- Bounds checking: Methods use
debug_assert!
for index validation - Size validation:
from_raw_bits()
asserts value fits in SIZE bits - Range validation:
one_shot()
panics if index exceeds SIZE
In production systems, wrap these operations with appropriate error handling based on your safety requirements.
Sources: src/lib.rs(L106 - L108) src/lib.rs(L123 - L124) src/lib.rs(L168 - L170)
Development and Contribution
Relevant source files
This document provides comprehensive guidance for developing and contributing to the cpumask library. It covers the development environment setup, build processes, testing procedures, code quality standards, and the continuous integration pipeline that ensures the library maintains compatibility across multiple target platforms.
For information about the library's API and usage patterns, see API Reference and Usage Guide and Examples. For architectural details about the implementation, see Architecture and Design.
Development Environment Setup
The cpumask library requires a Rust nightly toolchain due to its no-std compatibility and advanced const generic features. The development environment must support cross-compilation for multiple target architectures.
Required Tools and Dependencies
flowchart TD subgraph subGraph2["External Dependencies"] I["bitmaps v3.2.1"] J["no default features"] end subgraph subGraph1["Target Platforms"] E["x86_64-unknown-linux-gnu"] F["x86_64-unknown-none"] G["riscv64gc-unknown-none-elf"] H["aarch64-unknown-none-softfloat"] end subgraph subGraph0["Development Environment"] A["rust-toolchain nightly"] B["rust-src component"] C["clippy component"] D["rustfmt component"] end A --> B A --> C A --> D A --> E A --> F A --> G A --> H B --> I I --> J
The toolchain configuration requires nightly Rust with specific components and target support as defined in .github/workflows/ci.yml(L15 - L19)
Sources: .github/workflows/ci.yml(L11 - L19) Cargo.toml(L14 - L15)
Package Configuration
The crate is configured for multi-environment compatibility with the following key characteristics:
Configuration | Value | Purpose |
---|---|---|
Edition | 2021 | Modern Rust features |
License | GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0 | Triple licensing for flexibility |
Categories | os, no-std | Operating system and embedded use |
Dependencies | bitmaps 3.2.1 (no default features) | Minimal dependency footprint |
The no-std
compatibility and minimal dependencies ensure the library can be used in kernel-space and embedded environments as specified in Cargo.toml(L12) and Cargo.toml(L15)
Sources: Cargo.toml(L1 - L15)
Build System and Compilation
Multi-Target Build Process
flowchart TD subgraph subGraph2["Validation Steps"] G["cargo build"] H["cargo clippy"] I["cargo test (linux only)"] end subgraph subGraph1["Target Validation"] C["x86_64-unknown-linux-gnu"] D["x86_64-unknown-none"] E["riscv64gc-unknown-none-elf"] F["aarch64-unknown-none-softfloat"] end subgraph subGraph0["Build Matrix"] A["Source Code"] B["Cargo Build"] end A --> B B --> C B --> D B --> E B --> F C --> G C --> H C --> I D --> G D --> H E --> G E --> H F --> G F --> H
The build system validates compilation across four distinct target architectures to ensure compatibility with hosted Linux environments, bare-metal x86_64 systems, RISC-V embedded systems, and ARM64 embedded systems.
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L26 - L27)
Local Development Commands
Standard development workflow commands:
# Build for default target
cargo build --all-features
# Build for specific target
cargo build --target x86_64-unknown-none --all-features
# Run tests (only supported on hosted targets)
cargo test -- --nocapture
# Format check
cargo fmt --all -- --check
# Lint check
cargo clippy --all-features -- -A clippy::new_without_default
Sources: .github/workflows/ci.yml(L23 - L30)
Quality Assurance Pipeline
Code Quality Checks
The CI pipeline enforces multiple code quality standards through automated checks:
flowchart TD subgraph Documentation["Documentation"] D1["cargo doc --no-deps --all-features"] D2["Enforces missing-docs"] D3["Checks broken intra-doc links"] end subgraph subGraph3["Test Execution"] C1["cargo test --target x86_64-unknown-linux-gnu"] C2["Runs with --nocapture flag"] C3["Linux target only"] end subgraph subGraph2["Clippy Analysis"] B1["cargo clippy --target TARGET"] B2["Allows clippy::new_without_default"] B3["Fails on other warnings"] end subgraph subGraph1["Format Check"] A1["cargo fmt --all -- --check"] A2["Enforces consistent style"] end subgraph subGraph0["Quality Gates"] A["Code Format Check"] B["Clippy Linting"] C["Unit Testing"] D["Documentation Build"] end A --> A1 A --> A2 B --> B1 B --> B2 B --> B3 C --> C1 C --> C2 C --> C3 D --> D1 D --> D2 D --> D3
Sources: .github/workflows/ci.yml(L22 - L30) .github/workflows/ci.yml(L40)
Testing Strategy
Testing is limited to the x86_64-unknown-linux-gnu
target due to the hosted environment requirements for test execution. The test suite runs with the --nocapture
flag to provide detailed output for debugging as configured in .github/workflows/ci.yml(L29 - L30)
Sources: .github/workflows/ci.yml(L28 - L30)
Continuous Integration Workflow
CI Pipeline Architecture
flowchart TD subgraph subGraph3["Execution Steps"] G["actions/checkout@v4"] H["dtolnay/rust-toolchain@nightly"] I["rustc --version --verbose"] J["cargo fmt --all -- --check"] K["cargo clippy per target"] L["cargo build per target"] M["cargo test (linux only)"] end subgraph subGraph2["Build Matrix"] E["rust-toolchain: nightly"] F["targets: 4 platforms"] end subgraph subGraph1["CI Job"] C["Matrix Strategy"] D["fail-fast: false"] end subgraph subGraph0["Trigger Events"] A["Push Events"] B["Pull Request Events"] end A --> C B --> C C --> D C --> E C --> F E --> G F --> G G --> H H --> I I --> J J --> K K --> L L --> M
The CI system uses a fail-fast disabled strategy to ensure all target platforms are validated even if one fails, providing comprehensive feedback on compatibility issues.
Sources: .github/workflows/ci.yml(L3) .github/workflows/ci.yml(L8 - L12)
Documentation Deployment
flowchart TD subgraph subGraph2["Deployment Process"] G["JamesIves/github-pages-deploy-action@v4"] H["single-commit: true"] I["branch: gh-pages"] J["folder: target/doc"] end subgraph subGraph1["Quality Enforcement"] D["broken intra-doc links check"] E["missing-docs requirement"] F["default branch restriction"] end subgraph subGraph0["Documentation Pipeline"] A["cargo doc build"] B["RUSTDOCFLAGS validation"] C["GitHub Pages deployment"] end A --> B B --> D B --> E C --> G D --> F E --> F F --> C G --> H G --> I G --> J
Documentation is automatically built and deployed to GitHub Pages, but only from the default branch to prevent documentation pollution from development branches.
Sources: .github/workflows/ci.yml(L32 - L55) .github/workflows/ci.yml(L40)
Contribution Guidelines
Repository Structure
The repository follows a minimal structure optimized for a single-crate library:
Path | Purpose | Managed By |
---|---|---|
src/lib.rs | Core implementation | Developers |
Cargo.toml | Package configuration | Maintainers |
.github/workflows/ci.yml | CI pipeline | Maintainers |
.gitignore | Git exclusions | Maintainers |
target/ | Build artifacts | Git ignored |
Sources: .gitignore(L1 - L4)
Development Workflow
- Environment Setup: Install nightly Rust with required components
- Local Testing: Verify code compiles for all targets
- Quality Checks: Run
cargo fmt
andcargo clippy
- Documentation: Ensure all public APIs are documented
- Submission: Create pull request with clear description
The CI system will automatically validate all changes against the full target matrix and quality standards.
Sources: .github/workflows/ci.yml(L13 - L30)
License Compliance
The library uses triple licensing (GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0) as specified in Cargo.toml(L7) Contributors must ensure their submissions are compatible with all three license options.
Sources: Cargo.toml(L7)
Overview
Relevant source files
The axcpu
repository provides a unified, multi-architecture CPU abstraction library that enables privileged instruction execution and low-level CPU state management across different processor architectures. This library serves as a hardware abstraction layer (HAL) for the ArceOS operating system project, offering consistent interfaces for CPU context switching, trap handling, memory management, and system initialization.
This document covers the overall architecture and capabilities of the axcpu
library. For detailed information about specific architectures, see x86_64 Architecture, AArch64 Architecture, RISC-V Architecture, and LoongArch64 Architecture. For cross-architecture features and abstractions, see Cross-Architecture Features.
Library Architecture
The axcpu
library implements a modular architecture that provides consistent abstractions across four supported CPU architectures through conditional compilation and feature flags.
Architecture Selection and Module Structure
Sources: src/lib.rs(L14 - L28) Cargo.toml(L37 - L52)
Core Abstractions and Data Structures
The library provides several key abstractions that are implemented consistently across all supported architectures:
flowchart TD subgraph subGraph2["Architecture-Specific Implementations"] ARCH_IMPL["Per-architecture modulesx86_64, aarch64, riscv, loongarch64"] end subgraph subGraph1["Key Operations"] SWITCH["context_switch()Assembly routine for task switching"] SAVE["save_context()Save CPU state to memory"] RESTORE["restore_context()Restore CPU state from memory"] INIT_TRAP["init_trap()Initialize trap handling"] INIT_MMU["init_mmu()Initialize memory management"] end subgraph subGraph0["Core Data Structures"] TC["TaskContextMinimal CPU state for task switching"] TF["TrapFrameComplete CPU state for exception handling"] FP["FpState/ExtendedStateFloating-point and SIMD state"] end FP --> RESTORE FP --> SAVE INIT_MMU --> ARCH_IMPL INIT_TRAP --> ARCH_IMPL SWITCH --> ARCH_IMPL TC --> SWITCH TF --> RESTORE TF --> SAVE
Sources: Architecture-specific context.rs files, init.rs files across all architectures
Feature Flags and Conditional Compilation
The library uses Cargo features to enable optional functionality and reduce code size when certain capabilities are not needed:
Feature | Description | Affected Components |
---|---|---|
fp-simd | Floating-point and SIMD state management | FpState,ExtendedStatestructures |
tls | Thread-local storage support | Thread pointer management inTaskContext |
uspace | User space support | System call handling, user/kernel context switching |
Sources: Cargo.toml(L23 - L27)
Architecture-Specific Dependencies
Each architecture requires specific external crates for low-level operations:
Sources: Cargo.toml(L29 - L52)
Trap Handling Framework
The library implements a unified trap handling system using distributed slices that allows external code to register handlers for different types of CPU exceptions and interrupts.
Trap Handler Registration
flowchart TD subgraph subGraph2["Runtime Dispatch"] HANDLE_TRAP["handle_trap! macroGeneric trap dispatcher"] HANDLE_SYSCALL["handle_syscall()System call dispatcher"] end subgraph subGraph1["Registration Mechanism"] LINKME["linkme::distributed_sliceCompile-time handler collection"] DEF_MACRO["def_trap_handler macroHandler slice definition"] REG_MACRO["register_trap_handler macroHandler registration"] end subgraph subGraph0["Trap Handler Types"] IRQ_SLICE["IRQ: [fn(usize) -> bool]Hardware interrupt handlers"] PF_SLICE["PAGE_FAULT: [fn(VirtAddr, PageFaultFlags, bool) -> bool]Page fault handlers"] SYSCALL_SLICE["SYSCALL: [fn(&TrapFrame, usize) -> isize]System call handlers"] end DEF_MACRO --> LINKME IRQ_SLICE --> HANDLE_TRAP LINKME --> IRQ_SLICE LINKME --> PF_SLICE LINKME --> SYSCALL_SLICE PF_SLICE --> HANDLE_TRAP REG_MACRO --> LINKME SYSCALL_SLICE --> HANDLE_SYSCALL
Sources: src/trap.rs(L10 - L22) src/trap.rs(L25 - L44)
Integration with ArceOS Ecosystem
The axcpu
library is designed as a foundational component of the ArceOS operating system, providing the low-level CPU abstractions needed by higher-level OS components:
- Memory Management: Integrates with
memory_addr
andpage_table_entry
crates for address translation and page table management - Architecture Support: Provides consistent interfaces that higher-level ArceOS components can use without architecture-specific code
- Modular Design: Uses feature flags to enable only needed functionality, supporting both full OS and embedded use cases
- No-std Compatible: Designed for bare-metal and kernel environments without standard library dependencies
Sources: Cargo.toml(L14) Cargo.toml(L19 - L20) src/lib.rs(L1)
Documentation and Target Platforms
The library supports multiple target platforms and provides comprehensive documentation:
- Documented Targets:
x86_64-unknown-none
,aarch64-unknown-none-softfloat
,riscv64gc-unknown-none-elf
,loongarch64-unknown-none-softfloat
- Documentation: Available at docs.rs with all features enabled for complete API coverage
- License: Triple-licensed under GPL-3.0-or-later, Apache-2.0, and MulanPSL-2.0
Sources: Cargo.toml(L57 - L59) Cargo.toml(L15) Cargo.toml(L18)
x86_64 Architecture
Relevant source files
Purpose and Scope
This document provides an overview of the x86_64 architecture support in axcpu, which represents the most comprehensive and mature architecture implementation in the codebase. The x86_64 module provides CPU context management, trap handling, system call support, and hardware initialization for Intel/AMD 64-bit processors.
This page covers the overall module organization and key data structures. For detailed information about specific aspects, see:
- Context switching and CPU state management: Context Management
- Exception and interrupt handling: Trap and Exception Handling
- System call implementation: System Calls
- Hardware initialization procedures: System Initialization
For user space support across all architectures, see User Space Support.
Module Organization
The x86_64 architecture support is organized into several specialized modules, each handling different aspects of CPU management:
x86_64 Module Structure
Sources: src/x86_64/mod.rs(L1 - L21)
Core Data Structures
The x86_64 implementation exports several key data structures that represent different aspects of CPU state:
Exported Types and Their Purposes
flowchart TD subgraph subGraph2["External Dependencies"] X86_64_CRATE["x86_64::structures::tss"] end subgraph subGraph1["Hardware Tables"] TASK_CONTEXT["TaskContextMinimal context for task switching"] EXTENDED_STATE["ExtendedStateFPU and SIMD state"] TRAP_FRAME["TrapFrameComplete CPU state during exceptions"] FXSAVE_AREA["FxsaveAreax86 FXSAVE format"] GDT_STRUCT["GdtStructGlobal descriptor table"] IDT_STRUCT["IdtStructInterrupt descriptor table"] TSS["TaskStateSegmentTask state segment"] end subgraph subGraph0["Context Management"] TASK_CONTEXT["TaskContextMinimal context for task switching"] EXTENDED_STATE["ExtendedStateFPU and SIMD state"] TRAP_FRAME["TrapFrameComplete CPU state during exceptions"] FXSAVE_AREA["FxsaveAreax86 FXSAVE format"] GDT_STRUCT["GdtStructGlobal descriptor table"] end GDT_STRUCT --> TSS TASK_CONTEXT --> EXTENDED_STATE TRAP_FRAME --> FXSAVE_AREA TSS --> X86_64_CRATE
Sources: src/x86_64/mod.rs(L17 - L20)
Data Structure | Purpose | Module Source |
---|---|---|
TaskContext | Stores callee-saved registers and stack pointer for task switching | context |
TrapFrame | Captures complete CPU state during exceptions and interrupts | context |
ExtendedState | Manages FPU and SIMD register state | context |
FxsaveArea | x86-specific FXSAVE/FXRSTOR data format | context |
GdtStruct | Global descriptor table management | gdt |
IdtStruct | Interrupt descriptor table management | idt |
TaskStateSegment | Hardware task state segment (from x86_64 crate) | External |
Feature-Based Compilation
The x86_64 module uses conditional compilation to enable different capabilities based on build configuration:
Conditional Module Loading
flowchart TD subgraph subGraph2["Compilation Conditions"] TARGET_NONE["target_os = \none\"] USPACE_FEAT["feature = \uspace\"] end subgraph subGraph1["Conditional Modules"] TRAP_MOD["trap.rs"] SYSCALL_MOD["syscall.rs"] USPACE_MOD["uspace.rs"] end subgraph subGraph0["Base Modules"] BASE_MODS["context, gdt, idt, asm, init"] end BASE_MODS --> SYSCALL_MOD BASE_MODS --> TRAP_MOD BASE_MODS --> USPACE_MOD TARGET_NONE --> TRAP_MOD USPACE_FEAT --> SYSCALL_MOD USPACE_FEAT --> USPACE_MOD
Sources: src/x86_64/mod.rs(L8 - L15)
The conditional compilation enables:
- Trap handling (
target_os = "none"
): Exception and interrupt processing for bare-metal environments - User space support (
feature = "uspace"
): System call handling and user-kernel transitions - System call interface (
feature = "uspace"
): Dedicated syscall entry points and parameter handling
Key Components Overview
The x86_64 architecture support provides several essential capabilities:
Hardware Abstraction Layer
- Context Management: Efficient task switching using minimal register sets in
TaskContext
- Exception Handling: Complete trap frame capture and restoration for all exception types
- Memory Management: Integration with x86_64 paging hardware and TLB management
- FPU/SIMD Support: Extended state preservation for floating-point and vector operations
System Integration
- Descriptor Tables: GDT and IDT management for privilege level transitions
- Task State Segment: Hardware task switching support (though typically unused in modern kernels)
- System Calls: Fast system call entry points using
SYSCALL
/SYSRET
instructions - User Space: Complete user-kernel isolation with separate address spaces
Assembly Interface
The asm
module provides low-level operations that cannot be expressed in safe Rust, including:
- Context switching routines
- Trap entry/exit sequences
- CPU state manipulation
- Hardware feature detection and configuration
This architecture implementation serves as the foundation for kernel development on x86_64 platforms, providing all necessary abstractions for task management, memory protection, and hardware interaction.
Sources: src/x86_64/mod.rs(L1 - L21)
x86_64 Context Management
Relevant source files
This document covers the x86_64 CPU context management implementation in axcpu, focusing on the core data structures and mechanisms used for task context switching, exception handling, and state preservation. The implementation provides both kernel-level task switching and user space context management capabilities.
For x86_64 trap and exception handling mechanisms, see x86_64 Trap and Exception Handling. For system call implementation details, see x86_64 System Calls.
Core Context Data Structures
The x86_64 context management system uses several key data structures to manage CPU state across different scenarios:
flowchart TD subgraph subGraph2["Hardware Integration"] CPU["x86_64 CPU"] Stack["Kernel Stack"] Registers["CPU Registers"] end subgraph subGraph0["Core Context Types"] TaskContext["TaskContextMain task switching context"] TrapFrame["TrapFrameException/interrupt context"] ContextSwitchFrame["ContextSwitchFrameInternal context switch frame"] ExtendedState["ExtendedStateFP/SIMD state container"] FxsaveArea["FxsaveArea512-byte FXSAVE region"] end ContextSwitchFrame --> Registers ExtendedState --> CPU ExtendedState --> FxsaveArea TaskContext --> ContextSwitchFrame TaskContext --> ExtendedState TaskContext --> Stack TrapFrame --> Registers
Sources: src/x86_64/context.rs(L1 - L291)
TaskContext Structure
The TaskContext
struct represents the complete saved hardware state of a task, containing all necessary information for context switching:
Field | Type | Purpose | Feature Gate |
---|---|---|---|
kstack_top | VirtAddr | Top of kernel stack | Always |
rsp | u64 | Stack pointer after register saves | Always |
fs_base | usize | Thread Local Storage base | Always |
gs_base | usize | User space GS base register | uspace |
ext_state | ExtendedState | FP/SIMD state | fp-simd |
cr3 | PhysAddr | Page table root | uspace |
Sources: src/x86_64/context.rs(L166 - L183)
TrapFrame Structure
The TrapFrame
captures the complete CPU register state when a trap (interrupt or exception) occurs, containing all general-purpose registers plus trap-specific information:
flowchart TD subgraph subGraph1["Syscall Interface"] ARG0["arg0() -> rdi"] ARG1["arg1() -> rsi"] ARG2["arg2() -> rdx"] ARG3["arg3() -> r10"] ARG4["arg4() -> r8"] ARG5["arg5() -> r9"] USER_CHECK["is_user() -> cs & 3 == 3"] end subgraph subGraph0["TrapFrame Layout"] GPR["General Purpose Registersrax, rbx, rcx, rdx, rbp, rsi, rdir8, r9, r10, r11, r12, r13, r14, r15"] TRAP_INFO["Trap Informationvector, error_code"] CPU_PUSHED["CPU-Pushed Fieldsrip, cs, rflags, rsp, ss"] end CPU_PUSHED --> USER_CHECK GPR --> ARG0 GPR --> ARG1 GPR --> ARG2 GPR --> ARG3 GPR --> ARG4 GPR --> ARG5
Sources: src/x86_64/context.rs(L4 - L72)
Context Switching Mechanism
The context switching process involves saving the current task's state and restoring the next task's state through a coordinated sequence of operations:
flowchart TD subgraph subGraph1["Feature Gates"] FP_GATE["fp-simd feature"] TLS_GATE["tls feature"] USPACE_GATE["uspace feature"] end subgraph subGraph0["Context Switch Flow"] START["switch_to() called"] SAVE_FP["Save FP/SIMD stateext_state.save()"] RESTORE_FP["Restore FP/SIMD statenext_ctx.ext_state.restore()"] SAVE_TLS["Save current TLSread_thread_pointer()"] RESTORE_TLS["Restore next TLSwrite_thread_pointer()"] SAVE_GS["Save GS baserdmsr(IA32_KERNEL_GSBASE)"] RESTORE_GS["Restore GS basewrmsr(IA32_KERNEL_GSBASE)"] UPDATE_TSS["Update TSS RSP0write_tss_rsp0()"] SWITCH_PT["Switch page tablewrite_user_page_table()"] ASM_SWITCH["Assembly context switchcontext_switch()"] END["Context switch complete"] end ASM_SWITCH --> END RESTORE_FP --> FP_GATE RESTORE_FP --> SAVE_TLS RESTORE_GS --> UPDATE_TSS RESTORE_GS --> USPACE_GATE RESTORE_TLS --> SAVE_GS RESTORE_TLS --> TLS_GATE SAVE_FP --> FP_GATE SAVE_FP --> RESTORE_FP SAVE_GS --> RESTORE_GS SAVE_GS --> USPACE_GATE SAVE_TLS --> RESTORE_TLS SAVE_TLS --> TLS_GATE START --> SAVE_FP SWITCH_PT --> ASM_SWITCH SWITCH_PT --> USPACE_GATE UPDATE_TSS --> SWITCH_PT UPDATE_TSS --> USPACE_GATE
Sources: src/x86_64/context.rs(L242 - L265)
Assembly Context Switch Implementation
The low-level context switching is implemented in assembly using a naked function that saves and restores callee-saved registers:
flowchart TD subgraph subGraph0["context_switch Assembly"] PUSH["Push callee-saved registersrbp, rbx, r12-r15"] SAVE_RSP["Save current RSPmov [rdi], rsp"] LOAD_RSP["Load next RSPmov rsp, [rsi]"] POP["Pop callee-saved registersr15-r12, rbx, rbp"] RET["Return to new taskret"] end LOAD_RSP --> POP POP --> RET PUSH --> SAVE_RSP SAVE_RSP --> LOAD_RSP
Sources: src/x86_64/context.rs(L268 - L290)
Extended State Management
The x86_64 architecture provides extensive floating-point and SIMD capabilities that require specialized state management:
FxsaveArea Structure
The FxsaveArea
represents the 512-byte memory region used by the FXSAVE/FXRSTOR instructions:
Field | Type | Purpose |
---|---|---|
fcw | u16 | FPU Control Word |
fsw | u16 | FPU Status Word |
ftw | u16 | FPU Tag Word |
fop | u16 | FPU Opcode |
fip | u64 | FPU Instruction Pointer |
fdp | u64 | FPU Data Pointer |
mxcsr | u32 | MXCSR Register |
mxcsr_mask | u32 | MXCSR Mask |
st | [u64; 16] | ST0-ST7 FPU registers |
xmm | [u64; 32] | XMM0-XMM15 registers |
Sources: src/x86_64/context.rs(L86 - L107)
ExtendedState Operations
The ExtendedState
provides methods for saving and restoring FP/SIMD state:
flowchart TD subgraph subGraph2["Hardware Interface"] CPU_FPU["CPU FPU/SIMD Units"] FXSAVE_AREA["512-byte aligned memory"] end subgraph subGraph1["Default Values"] FCW["fcw = 0x37fStandard FPU control"] FTW["ftw = 0xffffAll tags empty"] MXCSR["mxcsr = 0x1f80Standard SIMD control"] end subgraph subGraph0["ExtendedState Operations"] SAVE["save()_fxsave64()"] RESTORE["restore()_fxrstor64()"] DEFAULT["default()Initialize with standard values"] end DEFAULT --> FCW DEFAULT --> FTW DEFAULT --> MXCSR RESTORE --> CPU_FPU RESTORE --> FXSAVE_AREA SAVE --> CPU_FPU SAVE --> FXSAVE_AREA
Sources: src/x86_64/context.rs(L115 - L137)
Task Context Initialization
The task context initialization process sets up a new task for execution:
flowchart TD subgraph subGraph2["Stack Layout"] STACK_TOP["Stack top (16-byte aligned)"] PADDING["8-byte padding"] SWITCH_FRAME["ContextSwitchFramerip = entry point"] end subgraph subGraph1["Default Values"] KSTACK["kstack_top = 0"] RSP["rsp = 0"] FS_BASE["fs_base = 0"] CR3["cr3 = kernel page table"] EXT_STATE["ext_state = default"] GS_BASE["gs_base = 0"] end subgraph subGraph0["Task Initialization"] NEW["new()Create empty context"] INIT["init()Setup for execution"] SETUP_STACK["Setup kernel stack16-byte alignment"] CREATE_FRAME["Create ContextSwitchFrameSet entry point"] SET_TLS["Set TLS basefs_base = tls_area"] end CREATE_FRAME --> SET_TLS INIT --> SETUP_STACK NEW --> CR3 NEW --> EXT_STATE NEW --> FS_BASE NEW --> GS_BASE NEW --> KSTACK NEW --> RSP PADDING --> SWITCH_FRAME SETUP_STACK --> CREATE_FRAME SETUP_STACK --> STACK_TOP STACK_TOP --> PADDING
Sources: src/x86_64/context.rs(L185 - L227)
User Space Context Management
When the uspace
feature is enabled, the context management system provides additional support for user space processes:
User Space Fields
Field | Purpose | Usage |
---|---|---|
gs_base | User space GS base register | Saved/restored via MSR operations |
cr3 | Page table root | Updated during context switch |
Page Table Management
The context switching process includes page table switching when transitioning between user space tasks:
flowchart TD subgraph subGraph1["TSS Update"] UPDATE_TSS["write_tss_rsp0(next_ctx.kstack_top)"] KERNEL_STACK["Update kernel stack for syscalls"] end subgraph subGraph0["Page Table Switch Logic"] CHECK["Compare next_ctx.cr3 with current cr3"] SWITCH["write_user_page_table(next_ctx.cr3)"] FLUSH["TLB automatically flushed"] SKIP["Skip page table switch"] end CHECK --> SKIP CHECK --> SWITCH SWITCH --> FLUSH SWITCH --> UPDATE_TSS UPDATE_TSS --> KERNEL_STACK
Sources: src/x86_64/context.rs(L253 - L263)
System Call Argument Extraction
The TrapFrame
provides convenient methods for extracting system call arguments following the x86_64 calling convention:
Method | Register | Purpose |
---|---|---|
arg0() | rdi | First argument |
arg1() | rsi | Second argument |
arg2() | rdx | Third argument |
arg3() | r10 | Fourth argument (note: r10, not rcx) |
arg4() | r8 | Fifth argument |
arg5() | r9 | Sixth argument |
is_user() | cs & 3 | Check if trap originated from user space |
Sources: src/x86_64/context.rs(L37 - L71)
x86_64 Trap and Exception Handling
Relevant source files
This document covers the x86_64-specific trap and exception handling implementation in the axcpu library. It focuses on how hardware exceptions, interrupts, and system calls are processed and dispatched to appropriate handlers on the x86_64 architecture.
For broader context management including TrapFrame
structure details, see x86_64 Context Management. For system call implementation specifics, see x86_64 System Calls. For cross-architecture trap handling abstractions, see Core Trap Handling Framework.
Exception Dispatch Architecture
The x86_64 trap handling system centers around the x86_trap_handler
function, which serves as the main dispatcher for all hardware exceptions and interrupts. This handler examines the trap vector number to determine the appropriate response.
flowchart TD subgraph subGraph2["Exception Handlers"] PAGE_FAULT["handle_page_fault()"] BREAKPOINT["Breakpoint Debug"] GPF["General Protection Fault Panic"] SYSCALL["x86_syscall_handler()"] IRQ["IRQ Handler via handle_trap!"] UNKNOWN["Unhandled Exception Panic"] end subgraph subGraph1["Main Dispatch Logic"] TRAP_HANDLER["x86_trap_handler()"] VECTOR_MATCH["Match tf.vector"] end subgraph subGraph0["Hardware Exception Entry"] HW_TRAP["Hardware Exception/Interrupt"] ASM_ENTRY["trap.S Assembly Entry Point"] SAVE_CONTEXT["Save CPU State to TrapFrame"] end ASM_ENTRY --> SAVE_CONTEXT HW_TRAP --> ASM_ENTRY SAVE_CONTEXT --> TRAP_HANDLER TRAP_HANDLER --> VECTOR_MATCH VECTOR_MATCH --> BREAKPOINT VECTOR_MATCH --> GPF VECTOR_MATCH --> IRQ VECTOR_MATCH --> PAGE_FAULT VECTOR_MATCH --> SYSCALL VECTOR_MATCH --> UNKNOWN
The dispatch mechanism uses a simple match statement on the trap vector stored in the TrapFrame
. Each vector corresponds to a specific x86_64 exception or interrupt type defined by the hardware architecture.
Sources: src/x86_64/trap.rs(L33 - L59)
Vector Classification and Constants
The trap handler categorizes exceptions and interrupts using predefined vector constants that correspond to x86_64 architectural definitions:
Vector Type | Range/Value | Purpose |
---|---|---|
PAGE_FAULT_VECTOR | 14 | Memory access violations |
BREAKPOINT_VECTOR | 3 | Debug breakpoints |
GENERAL_PROTECTION_FAULT_VECTOR | 13 | Protection violations |
LEGACY_SYSCALL_VECTOR | 0x80 | System calls (int 0x80) |
IRQ_VECTOR_STARTtoIRQ_VECTOR_END | 0x20-0xFF | Hardware interrupts |
Sources: src/x86_64/trap.rs(L10 - L13) src/x86_64/trap.rs(L34 - L47)
Page Fault Handling
Page fault processing involves extracting the fault address from the CR2 control register and converting the hardware error code into portable flags for the broader trap handling framework.
flowchart TD subgraph subGraph3["Dispatch Decision"] HANDLE_TRAP["handle_trap!(PAGE_FAULT, ...)"] SUCCESS["Handler processes fault"] PANIC["Unhandled page fault panic"] end subgraph subGraph2["Flag Mapping"] WRITE_FLAG["PageFaultFlags::WRITE"] READ_FLAG["PageFaultFlags::READ"] USER_FLAG["PageFaultFlags::USER"] EXEC_FLAG["PageFaultFlags::EXECUTE"] end subgraph subGraph1["Error Code Analysis"] WRITE_BIT["CAUSED_BY_WRITE bit"] USER_BIT["USER_MODE bit"] EXEC_BIT["INSTRUCTION_FETCH bit"] RESERVED_CHECK["Check reserved bits"] end subgraph subGraph0["Page Fault Processing"] PF_ENTRY["handle_page_fault(tf)"] ERR_CONVERT["err_code_to_flags(tf.error_code)"] CR2_READ["Read fault address from CR2"] FLAG_CHECK["Convert to PageFaultFlags"] end read_FLAG["read_FLAG"] CR2_READ --> HANDLE_TRAP ERR_CONVERT --> FLAG_CHECK EXEC_BIT --> EXEC_FLAG EXEC_FLAG --> HANDLE_TRAP FLAG_CHECK --> EXEC_BIT FLAG_CHECK --> RESERVED_CHECK FLAG_CHECK --> USER_BIT FLAG_CHECK --> WRITE_BIT HANDLE_TRAP --> PANIC HANDLE_TRAP --> SUCCESS PF_ENTRY --> CR2_READ PF_ENTRY --> ERR_CONVERT USER_BIT --> USER_FLAG USER_FLAG --> HANDLE_TRAP WRITE_BIT --> READ_FLAG WRITE_BIT --> WRITE_FLAG WRITE_FLAG --> HANDLE_TRAP read_FLAG --> HANDLE_TRAP
The err_code_to_flags
function translates x86_64 page fault error codes into architecture-neutral PageFaultFlags
. This abstraction allows the same page fault logic to work across different architectures.
Sources: src/x86_64/trap.rs(L15 - L30) src/x86_64/trap.rs(L69 - L92)
Interrupt Request (IRQ) Handling
Hardware interrupts occupy vector numbers 0x20 through 0xFF and are handled through the unified trap framework. The handler identifies IRQ vectors and delegates processing to registered handlers.
flowchart TD subgraph subGraph1["IRQ Vector Space"] VEC_20["0x20: Timer"] VEC_21["0x21: Keyboard"] VEC_22["0x22: Cascade"] VEC_DOTS["..."] VEC_FF["0xFF: Spurious"] end subgraph subGraph0["IRQ Processing Flow"] IRQ_DETECT["Vector in IRQ_VECTOR_START..=IRQ_VECTOR_END"] IRQ_EXTRACT["Extract IRQ number from tf.vector"] HANDLE_TRAP_IRQ["handle_trap!(IRQ, tf.vector as _)"] end IRQ_DETECT --> IRQ_EXTRACT IRQ_EXTRACT --> HANDLE_TRAP_IRQ VEC_DOTS --> IRQ_DETECT VEC_FF --> IRQ_DETECT
The IRQ handling delegates to the cross-architecture trap framework using the handle_trap!
macro, which allows different system components to register interrupt handlers.
Sources: src/x86_64/trap.rs(L45 - L47)
Assembly Integration
The trap handling system integrates with assembly code through the trap.S
file, which contains the low-level exception entry points and state saving routines.
The assembly code is included at compile time and provides the hardware-level interface between x86_64 exception mechanisms and the Rust handler functions.
Sources: src/x86_64/trap.rs(L7)
System Call Integration
When the uspace
feature is enabled, the trap handler supports legacy system calls via the int 0x80
instruction. This provides compatibility with traditional Unix system call interfaces.
System call handling is feature-gated and delegates to the dedicated system call module for processing.
Sources: src/x86_64/trap.rs(L9 - L10) src/x86_64/trap.rs(L44)
Error Handling and Diagnostics
The trap handler implements comprehensive error reporting for unhandled exceptions and invalid page faults. Critical errors result in detailed panic messages that include register state and fault information.
Unhandled Exception Reporting
For unknown or unsupported exception vectors, the handler provides detailed diagnostic information including vector number, mnemonic name, error code, and complete register state dump.
Page Fault Error Reporting
Page fault panics include comprehensive fault analysis showing whether the fault occurred in user or kernel mode, the faulting instruction address, the virtual address that caused the fault, and the decoded access flags.
Sources: src/x86_64/trap.rs(L20 - L29) src/x86_64/trap.rs(L48 - L57) src/x86_64/trap.rs(L61 - L67)
Integration with Cross-Architecture Framework
The x86_64 trap handler integrates with the broader axcpu trap handling framework through the handle_trap!
macro and standardized flag types like PageFaultFlags
. This design allows architecture-specific trap handling while maintaining consistent interfaces for higher-level system components.
Sources: src/x86_64/trap.rs(L5) src/x86_64/trap.rs(L19) src/x86_64/trap.rs(L46)
x86_64 System Calls
Relevant source files
This document covers the x86_64 system call implementation in the axcpu library, including the low-level assembly entry point, MSR configuration, and user-kernel transition mechanisms. The system call interface enables user space programs to request kernel services through the syscall
instruction.
For general trap and exception handling beyond system calls, see x86_64 Trap and Exception Handling. For user space context management across architectures, see User Space Support.
System Call Architecture Overview
The x86_64 system call implementation consists of three main components: MSR configuration during initialization, the assembly entry point that handles the low-level transition, and the Rust handler that processes the actual system call.
flowchart TD subgraph subGraph2["Context Management"] SWAPGS["swapgs"] STACK_SWITCH["Stack Switch"] TRAPFRAME["TrapFrame Construction"] RESTORE["Context Restore"] end subgraph subGraph0["System Call Flow"] USER["User Space Process"] SYSCALL_INSTR["syscall instruction"] ENTRY["syscall_entry (Assembly)"] HANDLER["x86_syscall_handler (Rust)"] GENERIC["handle_syscall (Generic)"] SYSRET["sysretq instruction"] end subgraph Configuration["Configuration"] INIT["init_syscall()"] LSTAR["LSTAR MSR"] STAR["STAR MSR"] SFMASK["SFMASK MSR"] EFER["EFER MSR"] end ENTRY --> HANDLER ENTRY --> STACK_SWITCH ENTRY --> SWAPGS ENTRY --> TRAPFRAME GENERIC --> HANDLER HANDLER --> GENERIC HANDLER --> SYSRET INIT --> EFER INIT --> LSTAR INIT --> SFMASK INIT --> STAR SYSCALL_INSTR --> ENTRY SYSRET --> RESTORE SYSRET --> USER USER --> SYSCALL_INSTR
Sources: src/x86_64/syscall.rs(L1 - L49) src/x86_64/syscall.S(L1 - L56)
System Call Initialization
The init_syscall
function configures the x86_64 Fast System Call mechanism by programming several Model Specific Registers (MSRs).
MSR | Purpose | Configuration |
---|---|---|
LSTAR | Long Syscall Target Address | Points tosyscall_entryassembly function |
STAR | Syscall Target Address | Contains segment selectors for user/kernel code/data |
SFMASK | Syscall Flag Mask | Masks specific RFLAGS during syscall execution |
EFER | Extended Feature Enable | Enables System Call Extensions (SCE bit) |
flowchart TD subgraph subGraph3["MSR Configuration in init_syscall()"] INIT_FUNC["init_syscall()"] subgraph subGraph2["Flag Masking"] TF["TRAP_FLAG"] IF["INTERRUPT_FLAG"] DF["DIRECTION_FLAG"] IOPL["IOPL_LOW | IOPL_HIGH"] AC["ALIGNMENT_CHECK"] NT["NESTED_TASK"] end subgraph subGraph1["GDT Selectors"] UCODE["UCODE64_SELECTOR"] UDATA["UDATA_SELECTOR"] KCODE["KCODE64_SELECTOR"] KDATA["KDATA_SELECTOR"] end subgraph subGraph0["MSR Setup"] LSTAR_SETUP["LStar::write(syscall_entry)"] STAR_SETUP["Star::write(selectors)"] SFMASK_SETUP["SFMask::write(flags)"] EFER_SETUP["Efer::update(SCE)"] KERNELGS_SETUP["KernelGsBase::write(0)"] end end INIT_FUNC --> EFER_SETUP INIT_FUNC --> KERNELGS_SETUP INIT_FUNC --> LSTAR_SETUP INIT_FUNC --> SFMASK_SETUP INIT_FUNC --> STAR_SETUP SFMASK_SETUP --> AC SFMASK_SETUP --> DF SFMASK_SETUP --> IF SFMASK_SETUP --> IOPL SFMASK_SETUP --> NT SFMASK_SETUP --> TF STAR_SETUP --> KCODE STAR_SETUP --> KDATA STAR_SETUP --> UCODE STAR_SETUP --> UDATA
The SFMASK
register masks flags with value 0x47700
, ensuring that potentially dangerous flags like interrupts and trap flags are cleared during system call execution.
Sources: src/x86_64/syscall.rs(L22 - L48)
Assembly Entry Point
The syscall_entry
function in assembly handles the low-level transition from user space to kernel space. It performs critical operations including GS base swapping, stack switching, and trap frame construction.
sequenceDiagram participant UserSpace as "User Space" participant CPUHardware as "CPU Hardware" participant KernelSpace as "Kernel Space" UserSpace ->> CPUHardware: syscall instruction CPUHardware ->> KernelSpace: Jump to syscall_entry Note over KernelSpace: swapgs (switch to kernel GS) KernelSpace ->> KernelSpace: Save user RSP to percpu area KernelSpace ->> KernelSpace: Load kernel stack from TSS Note over KernelSpace: Build TrapFrame on stack KernelSpace ->> KernelSpace: Push user RSP, RFLAGS, RIP KernelSpace ->> KernelSpace: Push all general purpose registers KernelSpace ->> KernelSpace: Call x86_syscall_handler(trapframe) KernelSpace ->> KernelSpace: Pop all general purpose registers KernelSpace ->> KernelSpace: Restore user RSP, RFLAGS, RIP Note over KernelSpace: swapgs (switch to user GS) KernelSpace ->> CPUHardware: sysretq instruction CPUHardware ->> UserSpace: Return to user space
Key Assembly Operations
The entry point performs these critical steps:
- GS Base Switching:
swapgs
switches between user and kernel GS base registers - Stack Management: Saves user RSP and switches to kernel stack from TSS
- Register Preservation: Pushes all general-purpose registers to build a
TrapFrame
- Handler Invocation: Calls
x86_syscall_handler
with the trap frame pointer - Context Restoration: Restores all registers and user context
- Return: Uses
sysretq
to return to user space
Sources: src/x86_64/syscall.S(L3 - L55)
System Call Handler
The Rust-based system call handler processes the actual system call request after the assembly entry point has set up the execution context.
flowchart TD subgraph subGraph2["Per-CPU Data"] USER_RSP["USER_RSP_OFFSET"] TSS_STACK["TSS privilege_stack_table"] end subgraph subGraph1["Data Flow"] TRAPFRAME_IN["TrapFrame (Input)"] SYSCALL_NUM["tf.rax (System Call Number)"] RESULT["Return Value"] TRAPFRAME_OUT["tf.rax (Updated)"] end subgraph subGraph0["Handler Flow"] ASM_ENTRY["syscall_entry (Assembly)"] RUST_HANDLER["x86_syscall_handler"] GENERIC_HANDLER["crate::trap::handle_syscall"] RETURN["Return to Assembly"] end ASM_ENTRY --> RUST_HANDLER ASM_ENTRY --> TSS_STACK ASM_ENTRY --> USER_RSP GENERIC_HANDLER --> RESULT GENERIC_HANDLER --> RUST_HANDLER RESULT --> TRAPFRAME_OUT RUST_HANDLER --> GENERIC_HANDLER RUST_HANDLER --> RETURN SYSCALL_NUM --> GENERIC_HANDLER TRAPFRAME_IN --> RUST_HANDLER
The handler function is minimal but critical:
#![allow(unused)] fn main() { #[unsafe(no_mangle)] pub(super) fn x86_syscall_handler(tf: &mut TrapFrame) { tf.rax = crate::trap::handle_syscall(tf, tf.rax as usize) as u64; } }
It extracts the system call number from rax
, delegates to the generic system call handler, and stores the return value back in rax
for return to user space.
Sources: src/x86_64/syscall.rs(L17 - L20)
Per-CPU State Management
The system call implementation uses per-CPU variables to manage state during the user-kernel transition:
Variable | Purpose | Usage |
---|---|---|
USER_RSP_OFFSET | Stores user stack pointer | Saved during entry, restored during exit |
TSS.privilege_stack_table | Kernel stack pointer | Loaded from TSS during stack switch |
flowchart TD subgraph subGraph1["Assembly Operations"] SAVE_USER_RSP["mov gs:[USER_RSP_OFFSET], rsp"] LOAD_KERNEL_RSP["mov rsp, gs:[TSS + rsp0_offset]"] RESTORE_USER_RSP["mov rsp, [rsp - 2 * 8]"] end subgraph subGraph0["Per-CPU Memory Layout"] PERCPU_BASE["Per-CPU Base Address"] USER_RSP_VAR["USER_RSP_OFFSET"] TSS_VAR["TSS Structure"] TSS_RSP0["privilege_stack_table[0]"] end PERCPU_BASE --> TSS_VAR PERCPU_BASE --> USER_RSP_VAR SAVE_USER_RSP --> RESTORE_USER_RSP TSS_RSP0 --> LOAD_KERNEL_RSP TSS_VAR --> TSS_RSP0 USER_RSP_VAR --> SAVE_USER_RSP
The per-CPU storage ensures that system calls work correctly in multi-CPU environments where each CPU needs its own temporary storage for user state.
Sources: src/x86_64/syscall.rs(L9 - L10) src/x86_64/syscall.S(L5 - L6) src/x86_64/syscall.S(L52)
Integration with Trap Framework
The x86_64 system call implementation integrates with the broader trap handling framework through the handle_syscall
function, which provides architecture-independent system call processing.
flowchart TD subgraph subGraph2["Return Path"] RESULT_VAL["Return Value"] RAX_UPDATE["tf.rax = result"] SYSRET["sysretq"] end subgraph subGraph1["Generic Trap Framework"] HANDLE_SYSCALL["crate::trap::handle_syscall"] SYSCALL_DISPATCH["System Call Dispatch"] SYSCALL_IMPL["Actual System Call Implementation"] end subgraph subGraph0["Architecture-Specific Layer"] SYSCALL_ENTRY["syscall_entry.S"] X86_HANDLER["x86_syscall_handler"] TRAPFRAME["TrapFrame"] end HANDLE_SYSCALL --> SYSCALL_DISPATCH RAX_UPDATE --> SYSRET RESULT_VAL --> RAX_UPDATE SYSCALL_DISPATCH --> SYSCALL_IMPL SYSCALL_ENTRY --> X86_HANDLER SYSCALL_IMPL --> RESULT_VAL TRAPFRAME --> HANDLE_SYSCALL X86_HANDLER --> TRAPFRAME
This layered approach allows the generic trap framework to handle system call logic while the x86_64-specific code manages the low-level hardware interface and calling conventions.
Sources: src/x86_64/syscall.rs(L18 - L19)
x86_64 System Initialization
Relevant source files
This document covers the x86_64 CPU initialization procedures during system bootstrap, including the setup of critical system tables and per-CPU data structures. The initialization process establishes the Global Descriptor Table (GDT), Interrupt Descriptor Table (IDT), and prepares the CPU for trap handling and task switching.
For x86_64 trap and exception handling mechanisms, see x86_64 Trap and Exception Handling. For x86_64 system call implementation details, see x86_64 System Calls.
Initialization Overview
The x86_64 system initialization follows a structured sequence that prepares the CPU for operation in both kernel and user modes. The process is coordinated through the init
module and involves setting up fundamental x86_64 system tables.
flowchart TD start["System Boot"] init_percpu["init_percpu()"] init_trap["init_trap()"] init_gdt["init_gdt()"] init_idt["init_idt()"] init_syscall["init_syscall()"] gdt_setup["GDT Table Setup"] load_gdt["lgdt instruction"] load_tss["load_tss()"] idt_setup["IDT Table Setup"] load_idt["lidt instruction"] msr_setup["MSR Configuration"] ready["CPU Ready"] gdt_setup --> load_gdt idt_setup --> load_idt init_gdt --> gdt_setup init_idt --> idt_setup init_percpu --> init_trap init_syscall --> msr_setup init_trap --> init_gdt init_trap --> init_idt init_trap --> init_syscall load_gdt --> load_tss load_idt --> ready load_tss --> ready msr_setup --> ready start --> init_percpu
Initialization Sequence Flow
Sources: src/x86_64/init.rs(L15 - L38)
Per-CPU Data Structure Initialization
The init_percpu()
function establishes per-CPU data structures required for multi-core operation. This initialization must occur before trap handling setup.
Function | Purpose | Dependencies |
---|---|---|
percpu::init() | Initialize percpu framework | None |
percpu::init_percpu_reg() | Set CPU-specific register | CPU ID |
The function takes a cpu_id
parameter to configure CPU-specific storage and must be called on each CPU core before init_trap()
.
flowchart TD percpu_init["percpu::init()"] percpu_reg["percpu::init_percpu_reg(cpu_id)"] tss_ready["TSS per-CPU ready"] gdt_ready["GDT per-CPU ready"] percpu_init --> percpu_reg percpu_reg --> gdt_ready percpu_reg --> tss_ready
Per-CPU Initialization Dependencies
Sources: src/x86_64/init.rs(L15 - L18)
Global Descriptor Table (GDT) Initialization
The GDT setup establishes memory segmentation for x86_64 operation, including kernel and user code/data segments plus the Task State Segment (TSS).
GDT Structure
The GdtStruct
contains predefined segment selectors for different privilege levels and operating modes:
Selector | Index | Purpose | Privilege |
---|---|---|---|
KCODE32_SELECTOR | 1 | Kernel 32-bit code | Ring 0 |
KCODE64_SELECTOR | 2 | Kernel 64-bit code | Ring 0 |
KDATA_SELECTOR | 3 | Kernel data | Ring 0 |
UCODE32_SELECTOR | 4 | User 32-bit code | Ring 3 |
UDATA_SELECTOR | 5 | User data | Ring 3 |
UCODE64_SELECTOR | 6 | User 64-bit code | Ring 3 |
TSS_SELECTOR | 7 | Task State Segment | Ring 0 |
GDT Initialization Process
flowchart TD tss_percpu["TSS per-CPU instance"] gdt_new["GdtStruct::new(tss)"] gdt_table["table[1-8] segment descriptors"] gdt_load["gdt.load()"] lgdt["lgdt instruction"] cs_set["CS::set_reg(KCODE64_SELECTOR)"] load_tss_call["gdt.load_tss()"] ltr["load_tss(TSS_SELECTOR)"] cs_set --> load_tss_call gdt_load --> lgdt gdt_new --> gdt_table gdt_table --> gdt_load lgdt --> cs_set load_tss_call --> ltr tss_percpu --> gdt_new
GDT Setup and Loading Process
The TSS is configured as a per-CPU structure using the percpu
crate, allowing each CPU core to maintain its own stack pointers and state information.
Sources: src/x86_64/gdt.rs(L104 - L111) src/x86_64/gdt.rs(L41 - L55) src/x86_64/gdt.rs(L73 - L90)
Interrupt Descriptor Table (IDT) Initialization
The IDT setup configures interrupt and exception handlers for the x86_64 architecture, mapping 256 possible interrupt vectors to their respective handler functions.
IDT Structure and Handler Mapping
The IdtStruct
populates all 256 interrupt vectors from an external trap_handler_table
:
flowchart TD trap_handler_table["trap_handler_table[256]"] idt_new["IdtStruct::new()"] entry_loop["for i in 0..256"] set_handler["entries[i].set_handler_fn()"] privilege_check["i == 0x3 || i == 0x80?"] ring3["set_privilege_level(Ring3)"] ring0["Default Ring0"] idt_load["idt.load()"] lidt["lidt instruction"] entry_loop --> set_handler idt_load --> lidt idt_new --> entry_loop privilege_check --> ring0 privilege_check --> ring3 ring0 --> idt_load ring3 --> idt_load set_handler --> privilege_check trap_handler_table --> idt_new
IDT Initialization and Handler Assignment
Special Privilege Level Handling
Two interrupt vectors receive special Ring 3 (user mode) access privileges:
- Vector
0x3
: Breakpoint exception for user-space debugging - Vector
0x80
: Legacy system call interface
Sources: src/x86_64/idt.rs(L22 - L46) src/x86_64/idt.rs(L77 - L81)
System Call Initialization (Optional)
When the uspace
feature is enabled, init_syscall()
configures Model-Specific Registers (MSRs) for fast system call handling using the syscall
/sysret
instructions.
Conditional System Call Initialization
The system call initialization is conditionally compiled and only executed when user space support is required.
Sources: src/x86_64/init.rs(L6 - L7) src/x86_64/init.rs(L36 - L37)
Task State Segment (TSS) Management
The TSS provides crucial stack switching capabilities for privilege level transitions, particularly important for user space support.
TSS Per-CPU Organization
flowchart TD percpu_tss["#[percpu::def_percpu] TSS"] cpu0_tss["CPU 0 TSS instance"] cpu1_tss["CPU 1 TSS instance"] cpun_tss["CPU N TSS instance"] rsp0_0["privilege_stack_table[0]"] rsp0_1["privilege_stack_table[0]"] rsp0_n["privilege_stack_table[0]"] cpu0_tss --> rsp0_0 cpu1_tss --> rsp0_1 cpun_tss --> rsp0_n percpu_tss --> cpu0_tss percpu_tss --> cpu1_tss percpu_tss --> cpun_tss
Per-CPU TSS Instance Management
The TSS includes utility functions for managing the Ring 0 stack pointer (RSP0
) used during privilege level transitions:
read_tss_rsp0()
: Retrieves current RSP0 valuewrite_tss_rsp0()
: Updates RSP0 for kernel stack switching
Sources: src/x86_64/gdt.rs(L10 - L12) src/x86_64/gdt.rs(L114 - L129)
Complete Initialization Function
The init_trap()
function orchestrates the complete x86_64 system initialization sequence:
#![allow(unused)] fn main() { pub fn init_trap() { init_gdt(); init_idt(); #[cfg(feature = "uspace")] init_syscall(); } }
This function must be called after init_percpu()
to ensure proper per-CPU data structure availability. The initialization establishes a fully functional x86_64 CPU state capable of handling interrupts, exceptions, and system calls.
Sources: src/x86_64/init.rs(L33 - L38)
AArch64 Architecture
Relevant source files
Purpose and Scope
This document provides a comprehensive overview of AArch64 (ARM64) architecture support within the axcpu library. The AArch64 implementation provides CPU abstraction capabilities including context management, trap handling, and system initialization for ARM 64-bit processors.
For detailed information about specific AArch64 subsystems, see AArch64 Context Management, AArch64 Trap and Exception Handling, and AArch64 System Initialization. For information about cross-architecture features that work with AArch64, see Cross-Architecture Features.
Module Organization
The AArch64 architecture support is organized into several focused modules that handle different aspects of CPU management and abstraction.
AArch64 Module Structure
The module structure follows a consistent pattern with other architectures in axcpu, providing specialized implementations for AArch64's unique characteristics while maintaining a unified interface.
Sources: src/aarch64/mod.rs(L1 - L13)
Core Data Structures
The AArch64 implementation centers around three primary data structures that encapsulate different aspects of CPU state management.
Context Management Abstractions
flowchart TD subgraph subGraph3["FpState Contents"] FP_VREGS["V0-V31Vector registers"] FP_FPCR["FPCRFloating-point control"] FP_FPSR["FPSRFloating-point status"] end subgraph subGraph2["TrapFrame Contents"] TF_REGS["R0-R30General purpose registers"] TF_SP_USER["SP_EL0User stack pointer"] TF_ELR["ELR_EL1Exception link register"] TF_SPSR["SPSR_EL1Saved program status"] end subgraph subGraph1["TaskContext Contents"] TC_REGS["R19-R29Callee-saved registers"] TC_SP["SPStack pointer"] TC_TPIDR["TPIDR_EL0Thread pointer"] TC_FP_STATE["FpStateFloating-point context"] end subgraph subGraph0["AArch64 Context Types"] TC["TaskContext"] TF["TrapFrame"] FP["FpState"] end FP --> FP_FPCR FP --> FP_FPSR FP --> FP_VREGS TC --> TC_FP_STATE TC --> TC_REGS TC --> TC_SP TC --> TC_TPIDR TC_FP_STATE --> FP TF --> TF_ELR TF --> TF_REGS TF --> TF_SPSR TF --> TF_SP_USER
These structures provide the foundation for AArch64's context switching, exception handling, and floating-point state management capabilities.
Sources: src/aarch64/mod.rs(L12)
Feature-Based Compilation
The AArch64 implementation uses conditional compilation to provide different capabilities based on the target environment and enabled features.
Condition | Module | Purpose |
---|---|---|
target_os = "none" | trap | Bare-metal trap handling |
feature = "uspace" | uspace | User space support and system calls |
Default | context,asm,init | Core functionality always available |
Conditional Module Integration
This modular design allows the AArch64 implementation to be tailored for different deployment scenarios, from bare-metal embedded systems to full operating system kernels with user space support.
Sources: src/aarch64/mod.rs(L6 - L10)
Architecture Integration
The AArch64 implementation integrates with the broader axcpu framework through standardized interfaces and common abstractions.
Cross-Architecture Compatibility
flowchart TD subgraph subGraph2["External Dependencies"] AARCH64_CPU["aarch64-cpu crateArchitecture-specific types"] MEMORY_ADDR["memory_addrAddress abstractions"] PAGE_TABLE["page_table_entryPage table support"] end subgraph subGraph1["AArch64 Implementation"] AARCH64_CONTEXT["AArch64 ContextTaskContext, TrapFrame, FpState"] AARCH64_ASM["AArch64 AssemblyLow-level operations"] AARCH64_INIT["AArch64 InitSystem startup"] AARCH64_TRAP["AArch64 TrapException handlers"] end subgraph subGraph0["axcpu Framework"] CORE_TRAITS["Core TraitsCommon interfaces"] MEMORY_MGMT["Memory ManagementPage tables, MMU"] TRAP_FRAMEWORK["Trap FrameworkUnified exception handling"] end AARCH64_ASM --> AARCH64_CPU AARCH64_CONTEXT --> AARCH64_CPU AARCH64_INIT --> MEMORY_ADDR AARCH64_INIT --> PAGE_TABLE CORE_TRAITS --> AARCH64_CONTEXT MEMORY_MGMT --> AARCH64_INIT TRAP_FRAMEWORK --> AARCH64_TRAP
The AArch64 implementation follows the established patterns used by other architectures in axcpu, ensuring consistent behavior and interfaces across different processor architectures while leveraging AArch64-specific features and optimizations.
Sources: src/aarch64/mod.rs(L1 - L13)
AArch64 Context Management
Relevant source files
This document covers CPU context management for the AArch64 architecture within the axcpu library. It details the data structures, mechanisms, and assembly implementations used for task switching, exception handling, and state preservation on ARM64 systems.
The scope includes the TaskContext
structure for task switching, TrapFrame
for exception handling, and FpState
for floating-point operations. For information about how these contexts are used during exception processing, see AArch64 Trap and Exception Handling. For system initialization and setup procedures, see AArch64 System Initialization.
Context Data Structures
The AArch64 context management revolves around three primary data structures, each serving different aspects of CPU state management.
Context Structure Overview
flowchart TD subgraph subGraph3["FpState Fields"] FP_REGS["regs[32]: u128V0-V31 SIMD Registers"] FP_CTRL["fpcr/fpsr: u32Control/Status Registers"] end subgraph subGraph2["TrapFrame Fields"] TF_REGS["r[31]: u64General Purpose Registers"] TF_USP["usp: u64User Stack Pointer"] TF_ELR["elr: u64Exception Link Register"] TF_SPSR["spsr: u64Saved Process Status"] end subgraph subGraph1["TaskContext Fields"] TC_SP["sp: u64Stack Pointer"] TC_TPIDR["tpidr_el0: u64Thread Local Storage"] TC_REGS["r19-r29, lr: u64Callee-saved Registers"] TC_TTBR["ttbr0_el1: PhysAddrPage Table Root"] TC_FP["fp_state: FpStateFP/SIMD State"] end subgraph subGraph0["AArch64 Context Management"] TaskContext["TaskContextTask Switching Context"] TrapFrame["TrapFrameException Context"] FpState["FpStateFP/SIMD Context"] end FpState --> FP_CTRL FpState --> FP_REGS TC_FP --> FpState TaskContext --> TC_FP TaskContext --> TC_REGS TaskContext --> TC_SP TaskContext --> TC_TPIDR TaskContext --> TC_TTBR TrapFrame --> TF_ELR TrapFrame --> TF_REGS TrapFrame --> TF_SPSR TrapFrame --> TF_USP
Sources: src/aarch64/context.rs(L8 - L124)
TaskContext Structure
The TaskContext
structure maintains the minimal set of registers needed for task switching, focusing on callee-saved registers and system state.
Field | Type | Purpose |
---|---|---|
sp | u64 | Stack pointer register |
tpidr_el0 | u64 | Thread pointer for TLS |
r19-r29 | u64 | Callee-saved general registers |
lr | u64 | Link register (r30) |
ttbr0_el1 | PhysAddr | User page table root (uspace feature) |
fp_state | FpState | Floating-point state (fp-simd feature) |
The structure is conditionally compiled based on enabled features, with ttbr0_el1
only included with the uspace
feature and fp_state
only with the fp-simd
feature.
Sources: src/aarch64/context.rs(L104 - L124)
TrapFrame Structure
The TrapFrame
captures the complete CPU state during exceptions and system calls, preserving all general-purpose registers and processor state.
flowchart TD subgraph subGraph0["TrapFrame Memory Layout"] R0_30["r[0-30]General Registers248 bytes"] USP["uspUser Stack Pointer8 bytes"] ELR["elrException Link Register8 bytes"] SPSR["spsrSaved Process Status8 bytes"] end ELR --> SPSR USP --> ELR
The TrapFrame
provides accessor methods for system call arguments through arg0()
through arg5()
methods, which map to registers r[0]
through r[5]
respectively.
Sources: src/aarch64/context.rs(L8 - L63)
FpState Structure
The FpState
structure manages floating-point and SIMD register state, with 16-byte alignment required for proper SIMD operations.
flowchart TD subgraph Operations["Operations"] SAVE["save()CPU → Memory"] RESTORE["restore()Memory → CPU"] end subgraph subGraph0["FpState Structure"] REGS["regs[32]: u128V0-V31 SIMD/FP Registers512 bytes total"] FPCR["fpcr: u32Floating-Point Control Register"] FPSR["fpsr: u32Floating-Point Status Register"] end FPCR --> SAVE FPSR --> SAVE REGS --> SAVE RESTORE --> FPCR RESTORE --> FPSR RESTORE --> REGS SAVE --> RESTORE
Sources: src/aarch64/context.rs(L66 - L88)
Context Switching Mechanism
Task switching on AArch64 involves saving the current task's callee-saved registers and restoring the next task's context, with optional handling of floating-point state and page tables.
Context Switch Flow
sequenceDiagram participant CurrentTaskContext as "Current TaskContext" participant NextTaskContext as "Next TaskContext" participant AArch64CPU as "AArch64 CPU" participant MemoryManagementUnit as "Memory Management Unit" CurrentTaskContext ->> CurrentTaskContext: Check fp-simd feature alt fp-simd enabled CurrentTaskContext ->> AArch64CPU: Save FP/SIMD state NextTaskContext ->> AArch64CPU: Restore FP/SIMD state end CurrentTaskContext ->> CurrentTaskContext: Check uspace feature alt uspace enabled and ttbr0_el1 differs NextTaskContext ->> MemoryManagementUnit: Update page table root MemoryManagementUnit ->> MemoryManagementUnit: Flush TLB end CurrentTaskContext ->> AArch64CPU: context_switch(current, next) Note over AArch64CPU: Assembly context switch AArch64CPU ->> CurrentTaskContext: Save callee-saved registers AArch64CPU ->> AArch64CPU: Restore next task registers AArch64CPU ->> NextTaskContext: Load context to CPU
The switch_to
method orchestrates the complete context switch:
- FP/SIMD State: If
fp-simd
feature is enabled, saves current FP state and restores next task's FP state - Page Table Switching: If
uspace
feature is enabled and page tables differ, updatesttbr0_el1
and flushes TLB - Register Context: Calls assembly
context_switch
function to handle callee-saved registers
Sources: src/aarch64/context.rs(L160 - L172)
Assembly Context Switch Implementation
The low-level context switching is implemented in naked assembly within the context_switch
function:
flowchart TD subgraph subGraph0["context_switch Assembly Flow"] SAVE_START["Save Current Contextstp x29,x30 [x0,12*8]"] SAVE_REGS["Save Callee-Savedr19-r28 to memory"] SAVE_SP_TLS["Save SP and TLSmov x19,sp; mrs x20,tpidr_el0"] RESTORE_SP_TLS["Restore SP and TLSmov sp,x19; msr tpidr_el0,x20"] RESTORE_REGS["Restore Callee-Savedr19-r28 from memory"] RESTORE_END["Restore Frameldp x29,x30 [x1,12*8]"] RET["Returnret"] end RESTORE_END --> RET RESTORE_REGS --> RESTORE_END RESTORE_SP_TLS --> RESTORE_REGS SAVE_REGS --> SAVE_SP_TLS SAVE_SP_TLS --> RESTORE_SP_TLS SAVE_START --> SAVE_REGS
The assembly implementation uses paired load/store instructions (stp
/ldp
) for efficiency, handling registers in pairs and preserving the AArch64 calling convention.
Sources: src/aarch64/context.rs(L175 - L203)
Feature-Conditional Context Management
The AArch64 context management includes several optional features that extend the base functionality.
Feature Integration Overview
flowchart TD subgraph subGraph2["Feature Operations"] SET_PT["set_page_table_root()Update ttbr0_el1"] SWITCH_PT["Page table switchingin switch_to()"] FP_SAVE["fp_state.save()CPU → Memory"] FP_RESTORE["fp_state.restore()Memory → CPU"] TLS_INIT["TLS initializationin init()"] end subgraph subGraph1["Optional Features"] USPACE["uspace Featurettbr0_el1: PhysAddr"] FPSIMD["fp-simd Featurefp_state: FpState"] TLS["tls Supporttpidr_el0 management"] end subgraph subGraph0["Base Context"] BASE["TaskContext Basesp, tpidr_el0, r19-r29, lr"] end BASE --> FPSIMD BASE --> TLS BASE --> USPACE FPSIMD --> FP_RESTORE FPSIMD --> FP_SAVE TLS --> TLS_INIT USPACE --> SET_PT USPACE --> SWITCH_PT
User Space Support
When the uspace
feature is enabled, TaskContext
includes the ttbr0_el1
field for managing user-space page tables. The set_page_table_root
method allows updating the page table root, and context switching automatically handles page table updates and TLB flushes when switching between tasks with different address spaces.
Floating-Point State Management
The fp-simd
feature enables comprehensive floating-point and SIMD state management through the FpState
structure. The assembly implementations fpstate_save
and fpstate_restore
handle all 32 vector registers (V0-V31) plus control registers using quad-word load/store instructions.
Sources: src/aarch64/context.rs(L77 - L88) src/aarch64/context.rs(L120 - L123) src/aarch64/context.rs(L147 - L154) src/aarch64/context.rs(L206 - L267)
Task Initialization
New tasks are initialized through the TaskContext::init
method, which sets up the minimal execution environment:
flowchart TD subgraph subGraph1["TaskContext Setup"] SET_SP["self.sp = kstack_top"] SET_LR["self.lr = entry"] SET_TLS["self.tpidr_el0 = tls_area"] end subgraph subGraph0["Task Initialization Parameters"] ENTRY["entry: usizeTask entry point"] KSTACK["kstack_top: VirtAddrKernel stack top"] TLS_AREA["tls_area: VirtAddrThread-local storage"] end ENTRY --> SET_LR KSTACK --> SET_SP TLS_AREA --> SET_TLS
The initialization sets the stack pointer to the kernel stack top, the link register to the task entry point, and the thread pointer for thread-local storage support.
Sources: src/aarch64/context.rs(L140 - L145)
AArch64 Trap and Exception Handling
Relevant source files
This document covers the AArch64 (ARM64) trap and exception handling implementation in the axcpu library. It details the exception classification system, handler dispatch mechanism, and integration with the cross-architecture trap handling framework. For AArch64 context management including TrapFrame
structure, see AArch64 Context Management. For system initialization and exception vector setup, see AArch64 System Initialization.
Exception Classification System
The AArch64 trap handler uses a two-dimensional classification system to categorize exceptions based on their type and source context.
Exception Types
The system defines four primary exception types in the TrapKind
enumeration:
Exception Type | Value | Description |
---|---|---|
Synchronous | 0 | Synchronous exceptions requiring immediate handling |
Irq | 1 | Standard interrupt requests |
Fiq | 2 | Fast interrupt requests (higher priority) |
SError | 3 | System error exceptions |
Exception Sources
The TrapSource
enumeration categorizes the execution context from which exceptions originate:
Source Type | Value | Description |
---|---|---|
CurrentSpEl0 | 0 | Current stack pointer, Exception Level 0 |
CurrentSpElx | 1 | Current stack pointer, Exception Level 1+ |
LowerAArch64 | 2 | Lower exception level, AArch64 state |
LowerAArch32 | 3 | Lower exception level, AArch32 state |
Sources: src/aarch64/trap.rs(L9 - L27)
Exception Handler Architecture
Exception Handler Dispatch Flow
flowchart TD ASM_ENTRY["trap.S Assembly Entry Points"] DISPATCH["Exception Dispatch Logic"] SYNC_CHECK["Exception Type?"] SYNC_HANDLER["handle_sync_exception()"] IRQ_HANDLER["handle_irq_exception()"] INVALID_HANDLER["invalid_exception()"] ESR_READ["Read ESR_EL1 Register"] EC_DECODE["Decode Exception Class"] SYSCALL["handle_syscall()"] INSTR_ABORT["handle_instruction_abort()"] DATA_ABORT["handle_data_abort()"] BRK_HANDLER["Debug Break Handler"] PANIC["Panic with Details"] TRAP_MACRO["handle_trap!(IRQ, 0)"] PAGE_FAULT_CHECK["Translation/Permission Fault?"] PAGE_FAULT_CHECK2["Translation/Permission Fault?"] PAGE_FAULT_HANDLER["handle_trap!(PAGE_FAULT, ...)"] ABORT_PANIC["Panic with Fault Details"] ASM_ENTRY --> DISPATCH DATA_ABORT --> PAGE_FAULT_CHECK2 DISPATCH --> SYNC_CHECK EC_DECODE --> BRK_HANDLER EC_DECODE --> DATA_ABORT EC_DECODE --> INSTR_ABORT EC_DECODE --> PANIC EC_DECODE --> SYSCALL ESR_READ --> EC_DECODE INSTR_ABORT --> PAGE_FAULT_CHECK IRQ_HANDLER --> TRAP_MACRO PAGE_FAULT_CHECK --> ABORT_PANIC PAGE_FAULT_CHECK --> PAGE_FAULT_HANDLER PAGE_FAULT_CHECK2 --> ABORT_PANIC PAGE_FAULT_CHECK2 --> PAGE_FAULT_HANDLER SYNC_CHECK --> INVALID_HANDLER SYNC_CHECK --> IRQ_HANDLER SYNC_CHECK --> SYNC_HANDLER SYNC_HANDLER --> ESR_READ
Sources: src/aarch64/trap.rs(L29 - L121)
Core Handler Functions
The trap handling system implements several specialized handler functions:
Synchronous Exception Handler
The handle_sync_exception
function serves as the primary dispatcher for synchronous exceptions, using the ESR_EL1 register to determine the specific exception class:
flowchart TD ESR_READ["ESR_EL1.extract()"] EC_MATCH["Match Exception Class"] SYSCALL_IMPL["tf.r[0] = handle_syscall(tf, tf.r[8])"] INSTR_LOWER["handle_instruction_abort(tf, iss, true)"] INSTR_CURRENT["handle_instruction_abort(tf, iss, false)"] DATA_LOWER["handle_data_abort(tf, iss, true)"] DATA_CURRENT["handle_data_abort(tf, iss, false)"] BRK_ADVANCE["tf.elr += 4"] UNHANDLED_PANIC["panic!(unhandled exception)"] EC_MATCH --> BRK_ADVANCE EC_MATCH --> DATA_CURRENT EC_MATCH --> DATA_LOWER EC_MATCH --> INSTR_CURRENT EC_MATCH --> INSTR_LOWER EC_MATCH --> SYSCALL_IMPL EC_MATCH --> UNHANDLED_PANIC ESR_READ --> EC_MATCH
Sources: src/aarch64/trap.rs(L94 - L121)
IRQ Exception Handler
The handle_irq_exception
function provides a simple interface to the cross-architecture interrupt handling system through the handle_trap!
macro:
Sources: src/aarch64/trap.rs(L37 - L40)
Invalid Exception Handler
The invalid_exception
function handles unexpected exception types or sources by panicking with detailed context information including the TrapFrame
, exception kind, and source classification:
Sources: src/aarch64/trap.rs(L29 - L35)
Memory Fault Handling
The AArch64 implementation provides specialized handling for instruction and data aborts, which correspond to page faults in other architectures.
Page Fault Classification
Both instruction and data aborts use a common pattern for classifying page faults:
Fault Type | ISS Bits | Handled | Description |
---|---|---|---|
Translation Fault | 0b0100 | Yes | Missing page table entry |
Permission Fault | 0b1100 | Yes | Access permission violation |
Other Faults | Various | No | Unhandled fault types |
Access Flag Generation
The system generates PageFaultFlags
based on the fault characteristics:
Instruction Abort Flags
- Always includes
PageFaultFlags::EXECUTE
- Adds
PageFaultFlags::USER
for EL0 (user) faults
Data Abort Flags
- Uses ESR_EL1 ISS WnR bit (Write not Read) to determine access type
- Considers CM bit (Cache Maintenance) to exclude cache operations from write classification
- Adds
PageFaultFlags::USER
for EL0 (user) faults
flowchart TD DATA_ABORT["Data Abort Exception"] ISS_DECODE["Decode ISS Bits from ESR_EL1"] WNR_CHECK["WnR Bit Set?"] CM_CHECK["CM Bit Set?"] EL_CHECK["Exception from EL0?"] WRITE_FLAG["PageFaultFlags::WRITE"] READ_FLAG["PageFaultFlags::read"] OVERRIDE_READ["Override to READ (cache maintenance)"] USER_FLAG["Add PageFaultFlags::USER"] ACCESS_FLAGS["Combined Access Flags"] read_FLAG["PageFaultFlags::read"] DFSC_CHECK["DFSC bits = 0100 or 1100?"] PAGE_FAULT_TRAP["handle_trap!(PAGE_FAULT, vaddr, flags, is_user)"] UNHANDLED_PANIC["panic!(unhandled data abort)"] ACCESS_FLAGS --> DFSC_CHECK CM_CHECK --> OVERRIDE_READ DATA_ABORT --> ISS_DECODE DFSC_CHECK --> PAGE_FAULT_TRAP DFSC_CHECK --> UNHANDLED_PANIC EL_CHECK --> USER_FLAG ISS_DECODE --> CM_CHECK ISS_DECODE --> EL_CHECK ISS_DECODE --> WNR_CHECK OVERRIDE_READ --> ACCESS_FLAGS USER_FLAG --> ACCESS_FLAGS WNR_CHECK --> READ_FLAG WNR_CHECK --> WRITE_FLAG WRITE_FLAG --> ACCESS_FLAGS read_FLAG --> ACCESS_FLAGS
Sources: src/aarch64/trap.rs(L42 - L92)
System Call Integration
When the uspace
feature is enabled, the trap handler supports AArch64 system calls through the SVC (Supervisor Call) instruction.
System Call Convention
- System call number: Passed in register
x8
(tf.r[8]
) - Return value: Stored in register
x0
(tf.r[0]
) - Handler: Delegates to cross-architecture
crate::trap::handle_syscall
Sources: src/aarch64/trap.rs(L99 - L102)
Integration with Cross-Architecture Framework
The AArch64 trap handler integrates with the broader axcpu trap handling framework through several mechanisms:
Trap Handler Macros
The implementation uses the handle_trap!
macro for consistent integration:
handle_trap!(IRQ, 0)
for interrupt processinghandle_trap!(PAGE_FAULT, vaddr, access_flags, is_user)
for memory faults
Assembly Integration
The Rust handlers are called from assembly code included via:
core::arch::global_asm!(include_str!("trap.S"));
Register Access
The system uses the aarch64_cpu
crate to access AArch64 system registers:
ESR_EL1
: Exception Syndrome Register for fault classificationFAR_EL1
: Fault Address Register for memory fault addresses
Sources: src/aarch64/trap.rs(L1 - L7)
AArch64 System Initialization
Relevant source files
This document covers the AArch64 system initialization procedures provided by the axcpu library. The initialization process includes exception level transitions, Memory Management Unit (MMU) configuration, and trap handler setup. These functions are primarily used during system bootstrap to establish a proper execution environment at EL1.
For detailed information about AArch64 trap and exception handling mechanisms, see AArch64 Trap and Exception Handling. For AArch64 context management including TaskContext and TrapFrame structures, see AArch64 Context Management.
Exception Level Transition
AArch64 systems support multiple exception levels (EL0-EL3), with EL3 being the highest privilege level and EL0 being user space. The switch_to_el1()
function handles the transition from higher exception levels (EL2 or EL3) down to EL1, which is the typical kernel execution level.
Exception Level Transition Flow
flowchart TD START["switch_to_el1()"] CHECK_EL["CurrentEL.read(CurrentEL::EL)"] EL3_CHECK["current_el == 3?"] EL2_PLUS["current_el >= 2?"] EL3_CONFIG["Configure EL3→EL2 transitionSCR_EL3.write()SPSR_EL3.write()ELR_EL3.set()"] EL2_CONFIG["Configure EL2→EL1 transitionCNTHCTL_EL2.modify()CNTVOFF_EL2.set()HCR_EL2.write()SPSR_EL2.write()SP_EL1.set()ELR_EL2.set()"] ERET["aarch64_cpu::asm::eret()"] DONE["Return (already in EL1)"] CHECK_EL --> EL2_PLUS EL2_CONFIG --> ERET EL2_PLUS --> DONE EL2_PLUS --> EL3_CHECK EL3_CHECK --> EL2_CONFIG EL3_CHECK --> EL3_CONFIG EL3_CONFIG --> EL2_CONFIG ERET --> DONE START --> CHECK_EL
Sources: src/aarch64/init.rs(L15 - L52)
The transition process configures multiple system registers:
Register | Purpose | Configuration |
---|---|---|
SCR_EL3 | Secure Configuration Register | Non-secure world, HVC enabled, AArch64 |
SPSR_EL3/EL2 | Saved Program Status Register | EL1h mode, interrupts masked |
ELR_EL3/EL2 | Exception Link Register | Return address from LR |
HCR_EL2 | Hypervisor Configuration Register | EL1 in AArch64 mode |
CNTHCTL_EL2 | Counter-timer Hypervisor Control | Enable EL1 timer access |
MMU Initialization
The Memory Management Unit initialization is handled by the init_mmu()
function, which configures address translation and enables caching. This function sets up a dual page table configuration using both TTBR0_EL1 and TTBR1_EL1.
MMU Configuration Sequence
flowchart TD INIT_MMU["init_mmu(root_paddr)"] SET_MAIR["MAIR_EL1.set(MemAttr::MAIR_VALUE)"] CONFIG_TCR["Configure TCR_EL1- Page size: 4KB- VA size: 48 bits- PA size: 48 bits"] SET_TTBR["Set page table baseTTBR0_EL1.set(root_paddr)TTBR1_EL1.set(root_paddr)"] FLUSH_TLB["crate::asm::flush_tlb(None)"] ENABLE_MMU["SCTLR_EL1.modify()Enable MMU + I-cache + D-cache"] ISB["barrier::isb(barrier::SY)"] CONFIG_TCR --> ISB ENABLE_MMU --> ISB FLUSH_TLB --> ENABLE_MMU INIT_MMU --> SET_MAIR ISB --> SET_TTBR SET_MAIR --> CONFIG_TCR SET_TTBR --> FLUSH_TLB
Sources: src/aarch64/init.rs(L54 - L95)
Memory Attribute Configuration
The MMU initialization configures memory attributes through several system registers:
Register | Configuration | Purpose |
---|---|---|
MAIR_EL1 | Memory attribute encoding | Defines memory types (Normal, Device, etc.) |
TCR_EL1 | Translation control | Page size, address ranges, cacheability |
TTBR0_EL1 | Translation table base 0 | User space page table root |
TTBR1_EL1 | Translation table base 1 | Kernel space page table root |
SCTLR_EL1 | System control | MMU enable, cache enable |
The function configures both translation table base registers to use the same physical address, enabling a unified address space configuration at initialization time.
Trap Handler Initialization
The init_trap()
function sets up the exception handling infrastructure by configuring the exception vector table base address and initializing user space memory protection.
Trap Initialization Components
flowchart TD subgraph subGraph1["User Space Protection"] TTBR0_ZERO["TTBR0_EL1 = 0Blocks low address access"] USER_ISOLATION["User space isolationPrevents unauthorized access"] end subgraph subGraph0["Exception Vector Setup"] VBAR_EL1["VBAR_EL1 registerPoints to exception_vector_base"] VECTOR_TABLE["Exception vector tableSynchronous, IRQ, FIQ, SError"] end INIT_TRAP["init_trap()"] GET_VECTOR["extern exception_vector_base()"] SET_VBAR["crate::asm::write_exception_vector_base()"] CLEAR_TTBR0["crate::asm::write_user_page_table(0)"] CLEAR_TTBR0 --> TTBR0_ZERO GET_VECTOR --> SET_VBAR INIT_TRAP --> GET_VECTOR SET_VBAR --> CLEAR_TTBR0 SET_VBAR --> VBAR_EL1 TTBR0_ZERO --> USER_ISOLATION VBAR_EL1 --> VECTOR_TABLE
Sources: src/aarch64/init.rs(L97 - L109)
The function performs two critical operations:
- Vector Base Setup: Points
VBAR_EL1
to theexception_vector_base
symbol, which contains the exception handlers - User Space Protection: Sets
TTBR0_EL1
to 0, effectively disabling user space address translation until a proper user page table is loaded
System Initialization Sequence
The complete AArch64 system initialization follows a specific sequence to ensure proper system state transitions and hardware configuration.
Complete Initialization Flow
flowchart TD subgraph subGraph2["Exception Handling Setup"] SET_VBAR_OP["Set VBAR_EL1 to exception_vector_base"] PROTECT_USER["Zero TTBR0_EL1 for protection"] end subgraph subGraph1["Memory Management Setup"] MMU_REGS["Configure MMU registersMAIR_EL1, TCR_EL1, TTBR0/1_EL1"] ENABLE_MMU_OP["Enable SCTLR_EL1.M + C + I"] end subgraph subGraph0["Exception Level Transition"] EL_REGS["Configure EL transition registersSCR_EL3, SPSR_EL3, HCR_EL2, etc."] ERET_OP["Execute ERET instruction"] end BOOT["System Boot(EL2/EL3)"] SWITCH["switch_to_el1()"] EL1_MODE["Running in EL1"] INIT_MMU_STEP["init_mmu(root_paddr)"] MMU_ENABLED["MMU + Caches Enabled"] INIT_TRAP_STEP["init_trap()"] SYSTEM_READY["System Ready"] BOOT --> SWITCH EL1_MODE --> INIT_MMU_STEP EL_REGS --> ERET_OP ENABLE_MMU_OP --> MMU_ENABLED ERET_OP --> EL1_MODE INIT_MMU_STEP --> MMU_REGS INIT_TRAP_STEP --> SET_VBAR_OP MMU_ENABLED --> INIT_TRAP_STEP MMU_REGS --> ENABLE_MMU_OP PROTECT_USER --> SYSTEM_READY SET_VBAR_OP --> PROTECT_USER SWITCH --> EL_REGS
Sources: src/aarch64/init.rs(L1 - L110)
Safety Considerations
All initialization functions are marked as unsafe
due to their privileged operations:
switch_to_el1()
: Changes CPU execution mode and privilege levelinit_mmu()
: Modifies address translation configuration, which can affect memory access behavior- Assembly operations: Direct register manipulation and barrier instructions require careful sequencing
The initialization sequence must be performed in the correct order, as MMU configuration depends on being in the proper exception level, and trap handling setup assumes MMU functionality is available.
RISC-V Architecture
Relevant source files
Purpose and Scope
This document covers the RISC-V architecture support within the axcpu library, focusing on the module organization, key data structures, and integration patterns specific to RISC-V processors. The RISC-V implementation follows the common multi-architecture abstraction pattern established by axcpu, providing CPU context management, trap handling, and system initialization capabilities.
For detailed coverage of RISC-V context management operations, see RISC-V Context Management. For RISC-V trap and exception handling mechanisms, see RISC-V Trap and Exception Handling. For RISC-V system initialization procedures, see RISC-V System Initialization.
Module Organization
The RISC-V architecture support is organized under the src/riscv/
directory, following the standard architecture module pattern used throughout axcpu. The main module file orchestrates the various components and provides the public API.
RISC-V Module Structure
The mod.rs
file serves as the central orchestrator, defining the module hierarchy and exporting the primary data structures. The macro system provides low-level assembly abstractions shared across the RISC-V modules.
Sources: src/riscv/mod.rs(L1 - L14)
Core Data Structures
The RISC-V architecture implementation defines three fundamental data structures that encapsulate different aspects of CPU state management: GeneralRegisters
, TaskContext
, and TrapFrame
.
RISC-V Context Management Hierarchy
flowchart TD subgraph FEATURES["Optional Features"] FP["fp-simdFloating point state"] TLS["tlsThread-local storage"] USPACE["uspaceUser space transitions"] end subgraph OPERATIONS["Context Operations"] SWITCH["context_switch()Assembly routine"] SAVE["Save context state"] RESTORE["Restore context state"] TRAP_ENTRY["Trap entry/exit"] end subgraph CONTEXT_MOD["context.rs Module"] GR["GeneralRegistersBasic register set"] TC["TaskContextTask switching state"] TF["TrapFrameComplete trap state"] end GR --> TC GR --> TF SWITCH --> RESTORE SWITCH --> SAVE TC --> SWITCH TC --> TLS TC --> USPACE TF --> FP TF --> TRAP_ENTRY TF --> USPACE
The GeneralRegisters
structure provides the foundation for both task switching and trap handling contexts. The TaskContext
builds upon this for efficient task switching, while TrapFrame
extends it with additional state needed for complete exception handling.
Sources: src/riscv/mod.rs(L13)
Architecture Integration
The RISC-V implementation integrates with the broader axcpu framework through standardized interfaces and conditional compilation features. This allows RISC-V-specific optimizations while maintaining compatibility with the cross-architecture abstractions.
RISC-V Feature Integration
flowchart TD subgraph MODULES["Module Implementation"] USPACE_M["uspace.rsUser transitionsKernel-user boundary"] INIT_M["init.rsSystem setupTrap vectorsMMU configuration"] ASM_M["asm.rsAssembly operationsRegister accessControl flow"] end subgraph OPTIONAL["Optional Features"] USPACE_F["uspace FeatureUser space supportSystem callsSATP management"] FP_F["fp-simd FeatureFuture FP supportVector extensions"] TLS_F["tls FeatureTP register managementThread-local storage"] end subgraph CORE["Core RISC-V Support"] BASE["Base ImplementationTaskContextTrapFrameBasic Operations"] end BASE --> ASM_M BASE --> FP_F BASE --> INIT_M BASE --> TLS_F BASE --> USPACE_F USPACE_F --> USPACE_M
The modular design allows RISC-V support to be configured with only the necessary features for a given use case, while providing extension points for future capabilities like floating-point and vector processing.
Sources: src/riscv/mod.rs(L10 - L11)
System Initialization and Runtime
The RISC-V architecture support provides comprehensive system initialization capabilities through the init
module, complemented by low-level assembly operations in the asm
module. These components work together to establish the runtime environment and provide the foundation for higher-level abstractions.
The trap handling system integrates with the RISC-V privilege architecture, managing transitions between different privilege levels and handling various exception types. This forms the basis for kernel-user space transitions and system call implementations when the uspace
feature is enabled.
Sources: src/riscv/mod.rs(L7 - L8)
RISC-V Context Management
Relevant source files
Purpose and Scope
This document covers the RISC-V architecture's CPU context management implementation within the axcpu library. It focuses on the data structures and mechanisms used to manage CPU state during task switching and exception handling, including register preservation, memory management integration, and thread-local storage support.
For RISC-V trap and exception handling mechanisms, see RISC-V Trap and Exception Handling. For system initialization procedures, see RISC-V System Initialization.
Core Context Structures
The RISC-V context management is built around three primary data structures that represent different levels of CPU state preservation.
Context Structure Hierarchy
flowchart TD subgraph subGraph2["RISC-V Hardware Registers"] SEPC["SEPCSupervisor Exception PC"] SSTATUS["SSTATUSSupervisor Status"] SATP["SATPAddress Translation"] TP_REG["TPThread Pointer"] end subgraph subGraph1["Usage Contexts"] EXCEPTION["Exception/Trap HandlingFull state preservation"] SWITCHING["Task SwitchingMinimal state preservation"] SYSCALL["System Call InterfaceArgument extraction"] end subgraph subGraph0["RISC-V Context Management"] GR["GeneralRegistersAll 31 GP registersra, sp, gp, tp, t0-t6, s0-s11, a0-a7"] TF["TrapFrameComplete exception stateregs + sepc + sstatus"] TC["TaskContextMinimal switching stateCallee-saved + sp + tp + satp"] end GR --> TF TC --> SATP TC --> SWITCHING TC --> TP_REG TF --> EXCEPTION TF --> SEPC TF --> SSTATUS TF --> SYSCALL
Sources: src/riscv/context.rs(L8 - L123)
GeneralRegisters Structure
The GeneralRegisters
structure represents the complete RISC-V general-purpose register set, containing all 31 registers as defined by the RISC-V specification.
Register Group | Registers | Purpose |
---|---|---|
Return Address | ra | Function return address |
Stack Pointer | sp | Current stack pointer |
Global Pointer | gp | Global data pointer (user traps only) |
Thread Pointer | tp | Thread-local storage (user traps only) |
Temporaries | t0-t6 | Temporary registers |
Saved Registers | s0-s11 | Callee-saved registers |
Arguments | a0-a7 | Function arguments and return values |
Sources: src/riscv/context.rs(L8 - L40)
TrapFrame Structure
The TrapFrame
extends GeneralRegisters
with supervisor-mode control and status registers required for complete exception context preservation.
flowchart TD subgraph subGraph1["System Call Interface"] ARG0["arg0() -> a0"] ARG1["arg1() -> a1"] ARG2["arg2() -> a2"] ARG3["arg3() -> a3"] ARG4["arg4() -> a4"] ARG5["arg5() -> a5"] end subgraph subGraph0["TrapFrame Layout"] REGS["regs: GeneralRegistersComplete register file"] SEPC["sepc: usizeException return address"] SSTATUS["sstatus: usizeProcessor status"] end REGS --> ARG0 REGS --> ARG1 REGS --> ARG2 REGS --> ARG3 REGS --> ARG4 REGS --> ARG5
The TrapFrame
provides accessor methods for system call arguments through the arg0()
through arg5()
methods, which extract values from the appropriate argument registers (a0
-a5
).
Sources: src/riscv/context.rs(L44 - L84)
TaskContext Structure
The TaskContext
represents the minimal CPU state required for task switching, containing only callee-saved registers and system-specific state.
Field | Purpose | Availability |
---|---|---|
ra | Return address for context switch | Always |
sp | Stack pointer | Always |
s0-s11 | Callee-saved registers | Always |
tp | Thread pointer for TLS | Always |
satp | Page table root | uspacefeature only |
Sources: src/riscv/context.rs(L100 - L123)
Context Switching Mechanism
The RISC-V context switching implementation uses a combination of Rust methods and naked assembly functions to efficiently preserve and restore task state.
Context Switch Flow
flowchart TD subgraph subGraph1["context_switch() Assembly"] SAVE_REGS["Save callee-saved registersSTR ra,sp,s0-s11 -> current"] RESTORE_REGS["Restore callee-saved registersLDR s11-s0,sp,ra <- next"] RETURN["ret"] end subgraph TaskContext::switch_to()["TaskContext::switch_to()"] START["switch_to(&self, next_ctx: &Self)"] TLS_CHECK["TLS featureenabled?"] TLS_SAVE["Save current TPLoad next TP"] USPACE_CHECK["USPACE featureenabled?"] SATP_CMP["Current SATP !=Next SATP?"] SATP_SWITCH["Switch page tableFlush TLB"] ASM_SWITCH["context_switch(self, next_ctx)"] end ASM_SWITCH --> SAVE_REGS RESTORE_REGS --> RETURN SATP_CMP --> ASM_SWITCH SATP_CMP --> SATP_SWITCH SATP_SWITCH --> ASM_SWITCH SAVE_REGS --> RESTORE_REGS START --> TLS_CHECK TLS_CHECK --> TLS_SAVE TLS_CHECK --> USPACE_CHECK TLS_SAVE --> USPACE_CHECK USPACE_CHECK --> ASM_SWITCH USPACE_CHECK --> SATP_CMP
Sources: src/riscv/context.rs(L162 - L177) src/riscv/context.rs(L181 - L219)
Assembly Context Switch Implementation
The context_switch
function is implemented as a naked assembly function that directly manipulates the task context structures:
- Save Phase: Uses
STR
macro to store callee-saved registers from CPU to current task's context - Restore Phase: Uses
LDR
macro to load callee-saved registers from next task's context to CPU - Register Order: Processes registers in reverse order during restore to maintain stack discipline
The assembly implementation assumes that the STR
and LDR
macros are defined to handle register storage and loading operations specific to the RISC-V architecture.
Sources: src/riscv/context.rs(L181 - L219)
Memory Management Integration
The RISC-V context management integrates closely with the memory management system through the satp
(Supervisor Address Translation and Protection) register.
Page Table Root Management
flowchart TD subgraph subGraph1["SATP Management"] CURRENT_SATP["Current Task SATP"] NEXT_SATP["Next Task SATP"] COMPARE["SATP valuesdifferent?"] WRITE_SATP["write_user_page_table()"] FLUSH_TLB["flush_tlb(None)"] end subgraph subGraph0["User Space Context Management"] NEW_TASK["TaskContext::new()"] KERNEL_SATP["read_kernel_page_table()"] SET_ROOT["set_page_table_root(satp)"] SWITCH["Context Switch"] end COMPARE --> SWITCH COMPARE --> WRITE_SATP CURRENT_SATP --> COMPARE NEW_TASK --> KERNEL_SATP NEXT_SATP --> COMPARE SET_ROOT --> CURRENT_SATP SWITCH --> COMPARE WRITE_SATP --> FLUSH_TLB
Key Operations:
- Initialization: New tasks start with the kernel page table root
- Page Table Switching: Only occurs when SATP values differ between tasks
- TLB Management: Complete TLB flush after page table switches
- Conditional Compilation: Available only with
uspace
feature
Sources: src/riscv/context.rs(L120 - L121) src/riscv/context.rs(L154 - L156) src/riscv/context.rs(L168 - L172)
Thread Local Storage Support
The RISC-V implementation provides thread-local storage support through the tp
(thread pointer) register, available when the tls
feature is enabled.
TLS Context Management
Operation | Method | Register | Purpose |
---|---|---|---|
Initialization | init() | Settpfield | Configure TLS area for new tasks |
Context Switch | switch_to() | Save/restoretp | Maintain per-task TLS state |
Register Access | ASM functions | Read/write TP | Low-level TLS manipulation |
The TLS implementation ensures that each task maintains its own thread-local storage area by:
- Saving Current State: Reading the current
tp
register value before switching - Restoring Next State: Writing the next task's
tp
value to the register - Initialization Support: Setting the TLS area address during task creation
Sources: src/riscv/context.rs(L118) src/riscv/context.rs(L146) src/riscv/context.rs(L164 - L167)
TaskContext Lifecycle
The TaskContext
follows a specific lifecycle pattern for task creation and management:
flowchart TD subgraph subGraph1["Runtime Operations"] SWITCH["switch_to(next_ctx)"] SET_PT["set_page_table_root()"] ACTIVE["Task Execution"] end subgraph subGraph0["Task Creation Flow"] NEW["TaskContext::new()"] INIT["init(entry, kstack_top, tls_area)"] READY["Ready for Switching"] end ACTIVE --> SWITCH INIT --> READY NEW --> INIT READY --> SET_PT READY --> SWITCH SET_PT --> SWITCH SWITCH --> ACTIVE
Initialization Parameters:
entry
: Task entry point address (stored inra
)kstack_top
: Kernel stack top (stored insp
)tls_area
: Thread-local storage area (stored intp
)
Sources: src/riscv/context.rs(L133 - L147)
RISC-V Trap and Exception Handling
Relevant source files
Purpose and Scope
This page covers the RISC-V trap and exception handling implementation in axcpu, including both the assembly-level trap entry/exit mechanisms and the Rust-based trap dispatch logic. The system handles supervisor-mode and user-mode traps, including page faults, system calls, interrupts, and debugging exceptions.
For information about RISC-V context management and register state, see 4.1. For system initialization and trap vector setup, see 4.3. For cross-architecture trap handling abstractions, see 6.2.
Trap Handling Architecture
The RISC-V trap handling system operates in two phases: assembly-level trap entry/exit for performance-critical register save/restore operations, and Rust-based trap dispatch for high-level exception handling logic.
Trap Flow Diagram
flowchart TD HW["Hardware Exception/Interrupt"] TV["trap_vector_base"] SS["sscratch == 0?"] STE[".Ltrap_entry_s(Supervisor trap)"] UTE[".Ltrap_entry_u(User trap)"] SRS["SAVE_REGS 0"] URS["SAVE_REGS 1"] RTH["riscv_trap_handler(tf, false)"] RTH2["riscv_trap_handler(tf, true)"] SC["scause analysis"] PF["Page Faulthandle_page_fault()"] BP["Breakpointhandle_breakpoint()"] SYS["System Callhandle_syscall()"] IRQ["Interrupthandle_trap!(IRQ)"] SRR["RESTORE_REGS 0"] URR["RESTORE_REGS 1"] SRET["sret"] SRET2["sret"] BP --> SRR HW --> TV IRQ --> SRR PF --> SRR RTH --> SC RTH2 --> SC SC --> BP SC --> IRQ SC --> PF SC --> SYS SRR --> SRET SRS --> RTH SS --> STE SS --> UTE STE --> SRS SYS --> URR TV --> SS URR --> SRET2 URS --> RTH2 UTE --> URS
Sources: src/riscv/trap.S(L45 - L69) src/riscv/trap.rs(L36 - L71)
Assembly Trap Entry Mechanism
The trap entry mechanism uses the sscratch
register to distinguish between supervisor-mode and user-mode traps, implementing different register save/restore strategies for each privilege level.
Register Save/Restore Strategy
Trap Source | sscratch Value | Entry Point | Register Handling |
---|---|---|---|
Supervisor Mode | 0 | .Ltrap_entry_s | Basic register save, no privilege switch |
User Mode | Non-zero (supervisor SP) | .Ltrap_entry_u | Full context switch including GP/TP registers |
Trap Vector Implementation
flowchart TD TV["trap_vector_base"] SSW["csrrw sp, sscratch, sp"] BZ["bnez sp, .Ltrap_entry_u"] CSR["csrr sp, sscratch"] UE[".Ltrap_entry_u"] SE[".Ltrap_entry_s"] SR0["SAVE_REGS 0Basic save"] SR1["SAVE_REGS 1Context switch"] CH["call riscv_trap_handler(tf, false)"] CH2["call riscv_trap_handler(tf, true)"] BZ --> CSR BZ --> UE CSR --> SE SE --> SR0 SR0 --> CH SR1 --> CH2 SSW --> BZ TV --> SSW UE --> SR1
Sources: src/riscv/trap.S(L45 - L69)
SAVE_REGS Macro Behavior
The SAVE_REGS
macro implements different register handling strategies based on the trap source:
Supervisor Mode (from_user=0):
- Saves all general-purpose registers to trap frame
- Preserves
sepc
,sstatus
, andsscratch
- No privilege-level register switching
User Mode (from_user=1):
- Saves user GP and TP registers to trap frame
- Loads supervisor GP and TP from trap frame offsets 2 and 3
- Switches to supervisor register context
Sources: src/riscv/trap.S(L1 - L20)
Rust Trap Handler Dispatch
The riscv_trap_handler
function serves as the main dispatch point for all RISC-V traps, analyzing the scause
register to determine trap type and invoking appropriate handlers.
Trap Classification and Dispatch
flowchart TD RTH["riscv_trap_handler(tf, from_user)"] SCR["scause::read()"] TTC["scause.cause().try_into::()"] EXC["Exception Branch"] INT["Interrupt Branch"] UNK["Unknown trap"] UEC["UserEnvCall(System Call)"] LPF["LoadPageFault"] SPF["StorePageFault"] IPF["InstructionPageFault"] BKP["Breakpoint"] HIRQ["handle_trap!(IRQ)"] HSC["handle_syscall(tf, tf.regs.a7)"] HPF1["handle_page_fault(tf, READ, from_user)"] HPF2["handle_page_fault(tf, WRITE, from_user)"] HPF3["handle_page_fault(tf, EXECUTE, from_user)"] HBK["handle_breakpoint(&mut tf.sepc)"] PAN["panic!(Unknown trap)"] BKP --> HBK EXC --> BKP EXC --> IPF EXC --> LPF EXC --> SPF EXC --> UEC INT --> HIRQ IPF --> HPF3 LPF --> HPF1 RTH --> SCR SCR --> TTC SPF --> HPF2 TTC --> EXC TTC --> INT TTC --> UNK UEC --> HSC UNK --> PAN
Sources: src/riscv/trap.rs(L36 - L71)
Specific Trap Type Handlers
Page Fault Handling
The handle_page_fault
function processes memory access violations by extracting the fault address from stval
and determining access permissions:
flowchart TD HPF["handle_page_fault(tf, access_flags, is_user)"] USR["is_user?"] UFLG["access_flags |= USER"] VADDR["vaddr = va!(stval::read())"] HTM["handle_trap!(PAGE_FAULT, vaddr, access_flags, is_user)"] SUC["Success?"] RET["Return"] PAN["panic!(Unhandled Page Fault)"] HPF --> USR HTM --> SUC SUC --> PAN SUC --> RET UFLG --> VADDR USR --> UFLG USR --> VADDR VADDR --> HTM
Page Fault Types:
LoadPageFault
: Read access violation (PageFaultFlags::READ
)StorePageFault
: Write access violation (PageFaultFlags::WRITE
)InstructionPageFault
: Execute access violation (PageFaultFlags::EXECUTE
)
Sources: src/riscv/trap.rs(L19 - L34) src/riscv/trap.rs(L46 - L54)
System Call Handling
System calls are handled through the UserEnvCall
exception when the uspace
feature is enabled:
tf.regs.a0 = crate::trap::handle_syscall(tf, tf.regs.a7) as usize;
tf.sepc += 4;
The system call number is passed in register a7
, and the return value is stored in a0
. The program counter (sepc
) is incremented by 4 to skip the ecall
instruction.
Sources: src/riscv/trap.rs(L42 - L45)
Breakpoint Handling
Breakpoint exceptions increment the program counter by 2 bytes to skip the compressed ebreak
instruction:
#![allow(unused)] fn main() { fn handle_breakpoint(sepc: &mut usize) { debug!("Exception(Breakpoint) @ {sepc:#x} "); *sepc += 2 } }
Sources: src/riscv/trap.rs(L14 - L17) src/riscv/trap.rs(L55)
Interrupt Handling
Hardware interrupts are dispatched to the common trap handling framework using the handle_trap!
macro with the IRQ
trap type and scause.bits()
as the interrupt number.
Sources: src/riscv/trap.rs(L56 - L58)
Integration with Common Framework
The RISC-V trap handler integrates with axcpu's cross-architecture trap handling framework through several mechanisms:
Trap Framework Integration
flowchart TD subgraph subGraph1["Common Framework"] HTM["handle_trap! macro"] HSC["crate::trap::handle_syscall"] PFF["PageFaultFlags"] end subgraph subGraph0["RISC-V Specific"] RTH["riscv_trap_handler"] HPF["handle_page_fault"] HBP["handle_breakpoint"] end HPF --> HTM HPF --> PFF RTH --> HSC RTH --> HTM
Framework Components Used:
PageFaultFlags
: Common page fault flag definitionshandle_trap!
macro: Architecture-agnostic trap dispatchcrate::trap::handle_syscall
: Common system call interface
Sources: src/riscv/trap.rs(L1 - L6) src/riscv/trap.rs(L24) src/riscv/trap.rs(L43) src/riscv/trap.rs(L57)
Assembly Integration
The trap handling system integrates assembly code through the global_asm!
macro, including architecture-specific macros and the trap frame size constant:
core::arch::global_asm!(
include_asm_macros!(),
include_str!("trap.S"),
trapframe_size = const core::mem::size_of::<TrapFrame>(),
);
This approach ensures type safety by using the Rust TrapFrame
size directly in assembly code, preventing layout mismatches between Rust and assembly implementations.
Sources: src/riscv/trap.rs(L8 - L12)
RISC-V System Initialization
Relevant source files
Purpose and Scope
This document covers the RISC-V-specific system initialization procedures implemented in the axcpu library. It focuses on the bootstrap sequence for setting up CPU state and trap handling on RISC-V platforms during system startup.
For RISC-V context management structures and switching mechanisms, see RISC-V Context Management. For detailed trap handling implementation after initialization, see RISC-V Trap and Exception Handling. For initialization procedures on other architectures, see x86_64 System Initialization and AArch64 System Initialization.
System Initialization Overview
The RISC-V system initialization is implemented as a minimal bootstrap procedure that configures essential CPU state for trap handling. Unlike other architectures that may require complex descriptor table setup, RISC-V initialization focuses primarily on trap vector configuration.
flowchart TD subgraph subGraph1["External Dependencies"] ASM_MODULE["asm module"] TRAP_ASSEMBLY["trap.S"] end subgraph subGraph0["RISC-V System Bootstrap"] BOOT["System Boot"] INIT_TRAP["init_trap()"] VECTOR_BASE["trap_vector_base()"] WRITE_VECTOR["write_trap_vector_base()"] READY["CPU Ready for Operation"] end BOOT --> INIT_TRAP INIT_TRAP --> ASM_MODULE INIT_TRAP --> VECTOR_BASE INIT_TRAP --> WRITE_VECTOR VECTOR_BASE --> TRAP_ASSEMBLY VECTOR_BASE --> WRITE_VECTOR WRITE_VECTOR --> ASM_MODULE WRITE_VECTOR --> READY
RISC-V Initialization Flow
Sources: src/riscv/init.rs(L1 - L14)
Trap Vector Initialization
The core initialization function init_trap()
establishes the trap vector base address that the RISC-V CPU uses to locate exception handlers. This is a critical step that must be completed before the system can handle any interrupts or exceptions.
flowchart TD subgraph subGraph1["External Symbols"] TRAP_VECTOR["trap_vector_base()"] ASM_WRITE["asm::write_trap_vector_base()"] end subgraph subGraph0["init_trap() Implementation"] FUNC_START["init_trap()"] GET_ADDR["Get trap_vector_base address"] CAST_ADDR["Cast to usize"] WRITE_CSR["write_trap_vector_base()"] COMPLETE["Initialization Complete"] end CAST_ADDR --> WRITE_CSR FUNC_START --> GET_ADDR GET_ADDR --> CAST_ADDR GET_ADDR --> TRAP_VECTOR WRITE_CSR --> ASM_WRITE WRITE_CSR --> COMPLETE
Trap Vector Setup Process
The implementation follows this sequence:
- Vector Address Resolution: The
trap_vector_base
symbol is resolved as an external C function pointer, representing the base address of the trap vector table - Address Conversion: The function pointer is cast to
usize
for use as a memory address - CSR Configuration: The address is written to the appropriate RISC-V Control and Status Register (CSR) via
write_trap_vector_base()
Sources: src/riscv/init.rs(L6 - L13)
Integration with Assembly Layer
The initialization depends on two key external components that bridge to the assembly layer:
Component | Type | Purpose |
---|---|---|
trap_vector_base | External C function | Provides base address of trap vector table |
write_trap_vector_base() | Assembly function | Writes address to RISC-V trap vector CSR |
flowchart TD subgraph subGraph2["Hardware Layer"] TVEC_CSR["RISC-V tvec CSR"] TRAP_TABLE["Trap Vector Table"] end subgraph subGraph1["Assembly Interface"] TRAP_BASE["trap_vector_base symbol"] WRITE_CSR["write_trap_vector_base()"] end subgraph subGraph0["Rust Layer"] INIT_TRAP["init_trap()"] end INIT_TRAP --> TRAP_BASE INIT_TRAP --> WRITE_CSR TRAP_BASE --> TRAP_TABLE TVEC_CSR --> TRAP_TABLE WRITE_CSR --> TVEC_CSR
Assembly Interface Integration
The unsafe
blocks in the implementation reflect the direct hardware manipulation required for system initialization, where incorrect trap vector configuration could compromise system stability.
Sources: src/riscv/init.rs(L7 - L12)
Relationship to Trap Handling Architecture
The initialization establishes the foundation for the RISC-V trap handling system. The configured trap vector base points to assembly routines that will save CPU state into TrapFrame
structures and dispatch to Rust handlers.
flowchart TD subgraph subGraph1["Data Structures"] TRAP_FRAME["TrapFrame"] TASK_CONTEXT["TaskContext"] end subgraph subGraph0["System Lifecycle"] INIT["init_trap()"] VECTOR_SET["Trap Vector Configured"] TRAP_OCCUR["Trap/Exception Occurs"] VECTOR_JUMP["Hardware jumps to trap_vector_base"] SAVE_CONTEXT["Save to TrapFrame"] RUST_HANDLER["Rust trap handler"] end INIT --> VECTOR_SET RUST_HANDLER --> TASK_CONTEXT SAVE_CONTEXT --> RUST_HANDLER SAVE_CONTEXT --> TRAP_FRAME TRAP_OCCUR --> VECTOR_JUMP VECTOR_JUMP --> SAVE_CONTEXT VECTOR_SET --> TRAP_OCCUR
Initialization to Runtime Flow
The initialization creates the linkage between hardware trap events and the software trap handling framework implemented in the broader RISC-V module.
Sources: src/riscv/init.rs(L1 - L14)
Architectural Simplicity
The RISC-V initialization implementation demonstrates the architectural simplicity compared to other platforms. The entire initialization consists of a single function with minimal setup requirements, reflecting RISC-V's streamlined approach to system configuration.
This contrasts with x86_64 initialization which requires GDT and IDT setup, or AArch64 initialization which involves exception level transitions and MMU configuration. The RISC-V approach focuses on the essential trap vector configuration needed for basic system operation.
Sources: src/riscv/init.rs(L1 - L14)
LoongArch64 Architecture
Relevant source files
Purpose and Scope
This document covers the LoongArch64 architecture support within the axcpu multi-architecture CPU abstraction library. LoongArch64 is a RISC instruction set architecture developed by Loongson Technology, and this module provides comprehensive CPU context management, trap handling, and system initialization capabilities for LoongArch64-based systems.
For detailed information about specific aspects of LoongArch64 support, see LoongArch64 Context Management, LoongArch64 Assembly Operations, and LoongArch64 System Initialization. For comparative information about other supported architectures, see x86_64 Architecture, AArch64 Architecture, and RISC-V Architecture.
LoongArch64 Module Organization
The LoongArch64 architecture support follows the standard axcpu architecture pattern, with a well-organized module structure that separates concerns between low-level assembly operations, context management, trap handling, and system initialization.
Module Structure Overview
Sources: src/loongarch64/mod.rs(L1 - L14)
Core Data Structures
The LoongArch64 module exports four key data structures that form the foundation of CPU state management:
Structure | Purpose | Module |
---|---|---|
FpuState | Floating-point unit register state | context |
GeneralRegisters | General-purpose register state | context |
TaskContext | Minimal context for task switching | context |
TrapFrame | Complete CPU state for exception handling | context |
These structures provide the necessary abstractions for context switching, exception handling, and floating-point state management specific to the LoongArch64 architecture.
Sources: src/loongarch64/mod.rs(L13)
LoongArch64 Feature Support
The LoongArch64 implementation supports both core functionality and optional features that enhance system capabilities.
Feature Matrix
Sources: src/loongarch64/mod.rs(L10 - L11)
Architecture Integration
The LoongArch64 module integrates seamlessly with the broader axcpu framework, following established patterns while accommodating LoongArch64-specific requirements.
Integration with axcpu Framework
flowchart TD subgraph subGraph2["External Dependencies"] MEMORY_ADDR["memory_addr crateAddress Abstractions"] PAGE_TABLE["page_table_entry cratePage Table Support"] LOONGARCH_CRATES["LoongArch64 Support CratesArchitecture-Specific Types"] end subgraph subGraph1["LoongArch64 Implementation"] LA_MOD["loongarch64::modArchitecture Module"] LA_CONTEXT["loongarch64::contextTaskContext, TrapFrame"] LA_TRAP["loongarch64::trapException Handlers"] LA_ASM["loongarch64::asmAssembly Operations"] LA_INIT["loongarch64::initSystem Bootstrap"] end subgraph subGraph0["axcpu Core Framework"] CORE_LIB["src/lib.rsCore Library"] CORE_TRAP["src/trap.rsUnified Trap Framework"] end CORE_LIB --> LA_MOD CORE_TRAP --> LA_TRAP LA_ASM --> LOONGARCH_CRATES LA_CONTEXT --> MEMORY_ADDR LA_INIT --> PAGE_TABLE LA_MOD --> LA_ASM LA_MOD --> LA_CONTEXT LA_MOD --> LA_INIT LA_MOD --> LA_TRAP
Sources: src/loongarch64/mod.rs(L1 - L14)
LoongArch64-Specific Characteristics
LoongArch64 brings several unique characteristics to the axcpu framework that distinguish it from other supported architectures:
Register Architecture
- General Purpose Registers: 32 general-purpose registers (R0-R31)
- Floating Point Registers: 32 floating-point registers (F0-F31)
- Control Registers: Including ERA (Exception Return Address), PRMD (Privilege Mode), and others
- Thread Pointer: TP register for thread-local storage support
Memory Management
- Page Table Structure: Multi-level page tables with LoongArch64-specific layout
- TLB Management: Translation Lookaside Buffer with architecture-specific invalidation
- MMU Configuration: Memory Management Unit setup with LoongArch64 control registers
Exception Handling
- Exception Types: Synchronous exceptions, interrupts, and system calls
- Exception Levels: Multiple privilege levels with controlled transitions
- Vector Table: LoongArch64-specific exception vector organization
The LoongArch64 implementation maintains compatibility with the unified axcpu interface while providing full access to architecture-specific features through specialized modules and assembly operations.
Sources: src/loongarch64/mod.rs(L1 - L14)
LoongArch64 Context Management
Relevant source files
This document covers the CPU context management system for the LoongArch64 architecture within the axcpu library. It details the data structures and mechanisms used to save, restore, and switch between different execution contexts including general-purpose registers, floating-point state, and task switching contexts.
For LoongArch64 assembly operations and low-level register manipulation, see LoongArch64 Assembly Operations. For system initialization including MMU and TLB setup, see LoongArch64 System Initialization.
Context Management Overview
The LoongArch64 context management system provides three primary context types, each serving different purposes in the execution lifecycle:
flowchart TD subgraph subGraph2["Register Categories"] GPR["General Purpose Registers$r0-$r31"] FPR["Floating Point Registers$f0-$f31"] CSR["Control/Status RegistersPRMD, ERA, FCSR"] end subgraph subGraph1["Usage Scenarios"] TRAP["Exception/Interrupt Handling"] SWITCH["Task Switching"] SYSCALL["System Call Processing"] end subgraph subGraph0["LoongArch64 Context Types"] GR["GeneralRegisters"] TF["TrapFrame"] TC["TaskContext"] FPU["FpuState"] end FPU --> FPR FPU --> TC GR --> GPR TC --> GR TC --> SWITCH TF --> CSR TF --> GR TF --> SYSCALL TF --> TRAP
Sources: src/loongarch64/context.rs(L6 - L43) src/loongarch64/context.rs(L72 - L82) src/loongarch64/context.rs(L116 - L145)
General Register Management
The GeneralRegisters
structure captures all LoongArch64 general-purpose registers following the architecture's register naming convention:
Register Category | Registers | Purpose |
---|---|---|
Zero Register | zero($r0) | Always contains zero |
Return Address | ra($r1) | Function return address |
Stack/Thread | sp($r3),tp($r2) | Stack pointer, thread pointer |
Arguments | a0-a7($r4-$r11) | Function arguments and return values |
Temporaries | t0-t8($r12-$r20) | Temporary registers |
Saved | s0-s8($r23-$r31) | Callee-saved registers |
Frame Pointer | fp($r22) | Frame pointer |
Reserved | u0($r21) | Reserved for user applications |
The register layout in GeneralRegisters
matches the LoongArch64 ABI specification, ensuring compatibility with compiler-generated code and system call conventions.
Sources: src/loongarch64/context.rs(L6 - L43)
Floating-Point State Management
FpuState Structure
The FpuState
structure manages LoongArch64 floating-point context:
flowchart TD subgraph subGraph2["Assembly Macros"] SAVE_FP["SAVE_FP"] SAVE_FCC_M["SAVE_FCC"] SAVE_FCSR["SAVE_FCSR"] RESTORE_FP["RESTORE_FP"] RESTORE_FCC_M["RESTORE_FCC"] RESTORE_FCSR["RESTORE_FCSR"] end subgraph subGraph1["FPU Operations"] SAVE_OP["save()"] RESTORE_OP["restore()"] SAVE_ASM["save_fp_registers()"] RESTORE_ASM["restore_fp_registers()"] end subgraph subGraph0["FpuState Components"] FP_REGS["fp: [u64; 32]Floating-point registers f0-f31"] FCC["fcc: [u8; 8]Floating-point condition codes"] FCSR["fcsr: u32Control and status register"] end FCC --> SAVE_OP FCSR --> SAVE_OP FP_REGS --> SAVE_OP RESTORE_ASM --> RESTORE_FCC_M RESTORE_ASM --> RESTORE_FCSR RESTORE_ASM --> RESTORE_FP RESTORE_OP --> RESTORE_ASM SAVE_ASM --> SAVE_FCC_M SAVE_ASM --> SAVE_FCSR SAVE_ASM --> SAVE_FP SAVE_OP --> SAVE_ASM
FPU State Operations
The floating-point state management is conditional on the fp-simd
feature flag. When enabled, the FpuState
structure provides save()
and restore()
methods that delegate to assembly routines for efficient register transfers.
The assembly implementations use offset calculations to access structure fields directly, ensuring optimal performance during context switches.
Sources: src/loongarch64/context.rs(L45 - L70) src/loongarch64/context.rs(L197 - L229)
Trap Frame Context
TrapFrame Structure
The TrapFrame
captures the complete CPU state when exceptions, interrupts, or system calls occur:
flowchart TD subgraph subGraph2["Exception Context"] EXCEPTION["Hardware Exception"] INTERRUPT["Interrupt"] SYSCALL["System Call"] end subgraph subGraph1["System Call Interface"] ARG0["arg0() -> a0"] ARG1["arg1() -> a1"] ARG2["arg2() -> a2"] ARG3["arg3() -> a3"] ARG4["arg4() -> a4"] ARG5["arg5() -> a5"] end subgraph subGraph0["TrapFrame Layout"] REGS["regs: GeneralRegistersAll general-purpose registers"] PRMD["prmd: usizePre-exception Mode Information"] ERA["era: usizeException Return Address"] end EXCEPTION --> ERA EXCEPTION --> PRMD INTERRUPT --> ERA INTERRUPT --> PRMD REGS --> ARG0 REGS --> ARG1 REGS --> ARG2 REGS --> ARG3 REGS --> ARG4 REGS --> ARG5 SYSCALL --> ARG0
System Call Argument Access
The TrapFrame
provides convenience methods for accessing system call arguments through registers a0
through a5
. These methods cast the register values appropriately for syscall parameter passing conventions.
Sources: src/loongarch64/context.rs(L72 - L114)
Task Context and Switching
TaskContext Structure
The TaskContext
represents the minimal state required for task switching:
Field | Type | Purpose | Feature Flag |
---|---|---|---|
ra | usize | Return address | Always |
sp | usize | Stack pointer | Always |
s | [usize; 10] | Saved registers $r22-$r31 | Always |
tp | usize | Thread pointer | Always |
pgdl | usize | Page table root | uspace |
fpu | FpuState | FPU state | fp-simd |
Context Switch Implementation
The task switching process involves multiple phases coordinated between Rust and assembly code:
Context Switch Assembly Implementation
The low-level context switch uses the STD
and LDD
assembly macros to efficiently save and restore the minimal register set required for task switching. The assembly routine saves callee-saved registers from the current context and restores them for the next context.
Sources: src/loongarch64/context.rs(L116 - L195) src/loongarch64/context.rs(L231 - L266)
Feature-Conditional Context Management
The LoongArch64 context management system supports several optional features that extend the basic context switching capabilities:
Thread-Local Storage Support
When the tls
feature is enabled, the context switch saves and restores the thread pointer (tp
) register, enabling proper thread-local storage functionality across task switches.
User Space Support
The uspace
feature adds user page table management to task contexts. During context switches, the system checks if the page table root (pgdl
) has changed and updates the hardware page table register accordingly, followed by a TLB flush.
Floating-Point SIMD Support
With the fp-simd
feature, the task context includes complete FPU state management. During context switches, the current task's FPU state is saved and the next task's FPU state is restored, ensuring floating-point computations remain isolated between tasks.
Sources: src/loongarch64/context.rs(L175 - L194)
LoongArch64 Assembly Operations
Relevant source files
This document covers the low-level assembly operations and macros provided by the LoongArch64 architecture implementation in axcpu. It focuses on the assembly-level primitives used for CPU state management, register manipulation, and hardware control operations.
For information about LoongArch64 context structures and high-level context switching, see LoongArch64 Context Management. For system initialization procedures, see LoongArch64 System Initialization.
Control and Status Register (CSR) Operations
The LoongArch64 implementation defines a comprehensive set of Control and Status Register (CSR) constants and operations. These registers control fundamental CPU behaviors including exception handling, memory management, and system configuration.
CSR Definitions
The core CSR registers are defined as assembly constants for direct hardware access:
Register | Value | Purpose |
---|---|---|
LA_CSR_PRMD | 0x1 | Previous Mode Data - saves processor state |
LA_CSR_EUEN | 0x2 | Extended Unit Enable - controls extensions |
LA_CSR_ERA | 0x6 | Exception Return Address |
LA_CSR_PGDL | 0x19 | Page table base when VA[47] = 0 |
LA_CSR_PGDH | 0x1a | Page table base when VA[47] = 1 |
LA_CSR_PGD | 0x1b | General page table base |
LA_CSR_TLBRENTRY | 0x88 | TLB refill exception entry |
LA_CSR_DMW0/DMW1 | 0x180/0x181 | Direct Mapped Windows |
flowchart TD subgraph CSR_SYSTEM["LoongArch64 CSR System"] subgraph KSAVE_REGS["Kernel Save Registers"] KSAVE_KSP["KSAVE_KSPKernel Stack Pointer"] KSAVE_TEMP["KSAVE_TEMPTemporary Storage"] KSAVE_R21["KSAVE_R21Register 21 Save"] KSAVE_TP["KSAVE_TPThread Pointer Save"] EUEN["LA_CSR_EUENExtended Unit Enable"] TLBRENTRY["LA_CSR_TLBRENTRYTLB Refill Entry"] PGDL["LA_CSR_PGDLUser Page Table"] PRMD["LA_CSR_PRMDPrevious Mode Data"] end subgraph EXTENSION_CSRS["Extensions"] subgraph TLB_CSRS["TLB Management"] KSAVE_KSP["KSAVE_KSPKernel Stack Pointer"] EUEN["LA_CSR_EUENExtended Unit Enable"] DMW0["LA_CSR_DMW0Direct Map Window 0"] DMW1["LA_CSR_DMW1Direct Map Window 1"] TLBRENTRY["LA_CSR_TLBRENTRYTLB Refill Entry"] TLBRBADV["LA_CSR_TLBRBADVTLB Bad Address"] TLBRERA["LA_CSR_TLBRERATLB Refill ERA"] PGDL["LA_CSR_PGDLUser Page Table"] PGDH["LA_CSR_PGDHKernel Page Table"] PWCL["LA_CSR_PWCLPage Walk Lower"] PRMD["LA_CSR_PRMDPrevious Mode Data"] ERA["LA_CSR_ERAException Return Address"] EENTRY["Exception Entry Base"] end end subgraph MEMORY_CSRS["Memory Management"] KSAVE_KSP["KSAVE_KSPKernel Stack Pointer"] EUEN["LA_CSR_EUENExtended Unit Enable"] DMW0["LA_CSR_DMW0Direct Map Window 0"] DMW1["LA_CSR_DMW1Direct Map Window 1"] TLBRENTRY["LA_CSR_TLBRENTRYTLB Refill Entry"] TLBRBADV["LA_CSR_TLBRBADVTLB Bad Address"] TLBRERA["LA_CSR_TLBRERATLB Refill ERA"] PGDL["LA_CSR_PGDLUser Page Table"] PGDH["LA_CSR_PGDHKernel Page Table"] PWCL["LA_CSR_PWCLPage Walk Lower"] PWCH["LA_CSR_PWCHPage Walk Higher"] PRMD["LA_CSR_PRMDPrevious Mode Data"] ERA["LA_CSR_ERAException Return Address"] EENTRY["Exception Entry Base"] end subgraph EXCEPTION_CSRS["Exception Control"] KSAVE_KSP["KSAVE_KSPKernel Stack Pointer"] EUEN["LA_CSR_EUENExtended Unit Enable"] DMW0["LA_CSR_DMW0Direct Map Window 0"] DMW1["LA_CSR_DMW1Direct Map Window 1"] TLBRENTRY["LA_CSR_TLBRENTRYTLB Refill Entry"] TLBRBADV["LA_CSR_TLBRBADVTLB Bad Address"] TLBRERA["LA_CSR_TLBRERATLB Refill ERA"] PGDL["LA_CSR_PGDLUser Page Table"] PGDH["LA_CSR_PGDHKernel Page Table"] PWCL["LA_CSR_PWCLPage Walk Lower"] PRMD["LA_CSR_PRMDPrevious Mode Data"] ERA["LA_CSR_ERAException Return Address"] EENTRY["Exception Entry Base"] end end
Sources: src/loongarch64/macros.rs(L7 - L29)
Register Access Macros
The implementation provides optimized macros for common register operations:
STD rd, rj, off
- Store doubleword with scaled offsetLDD rd, rj, off
- Load doubleword with scaled offset
These macros automatically scale the offset by 8 bytes for 64-bit operations, simplifying stack and structure access patterns.
Sources: src/loongarch64/macros.rs(L31 - L36)
General Purpose Register Operations
Register Save/Restore Framework
The LoongArch64 implementation provides a systematic approach to saving and restoring general purpose registers using parameterized macros:
flowchart TD subgraph GPR_OPERATIONS["General Purpose Register Operations"] subgraph CONCRETE_MACROS["Concrete Operations"] PUSH_GENERAL["PUSH_GENERAL_REGSSave All GPRs"] POP_GENERAL["POP_GENERAL_REGSRestore All GPRs"] end subgraph REGISTER_GROUPS["Register Groups"] ARGS["Argument Registersa0-a7"] TEMPS["Temporary Registerst0-t8"] SAVED["Saved Registerss0-s8"] SPECIAL["Special Registersra, fp, sp"] end subgraph MACRO_FRAMEWORK["Macro Framework"] PUSH_POP["PUSH_POP_GENERAL_REGSParameterized Operation"] STD_OP["STD OperationStore Doubleword"] LDD_OP["LDD OperationLoad Doubleword"] end end ARGS --> PUSH_GENERAL POP_GENERAL --> PUSH_POP PUSH_GENERAL --> PUSH_POP PUSH_POP --> LDD_OP PUSH_POP --> STD_OP SAVED --> PUSH_GENERAL SPECIAL --> PUSH_GENERAL TEMPS --> PUSH_GENERAL
The PUSH_POP_GENERAL_REGS
macro takes an operation parameter (STD
for save, LDD
for restore) and systematically processes all general purpose registers in a standardized order. This ensures consistent register handling across all context switching operations.
Sources: src/loongarch64/macros.rs(L38 - L74)
Register Layout and Ordering
The register save/restore operations follow the LoongArch64 ABI conventions:
Offset | Register | Purpose |
---|---|---|
1 | $ra | Return address |
4-11 | $a0-$a7 | Function arguments |
12-20 | $t0-$t8 | Temporary registers |
22 | $fp | Frame pointer |
23-31 | $s0-$s8 | Saved registers |
Sources: src/loongarch64/macros.rs(L39 - L66)
Floating Point Operations
When the fp-simd
feature is enabled, the LoongArch64 implementation provides comprehensive floating point state management through specialized assembly macros.
Floating Point Condition Code (FCC) Management
The FCC registers (fcc0
-fcc7
) store comparison results and require special handling due to their packed storage format:
flowchart TD subgraph FCC_OPERATIONS["Floating Point Condition Code Operations"] FCC_UNPACK["Unpack bit fieldsbstrpick.d instructions"] FCC_WRITE["Write fcc0-fcc7movgr2cf instructions"] FCC_PACK["Pack into single registerbstrins.d instructions"] FCC_STORE["Store to memoryst.d instruction"] subgraph BIT_LAYOUT["64-bit FCC Layout"] FCC0_BITS["fcc0: bits 7:0"] FCC1_BITS["fcc1: bits 15:8"] FCC2_BITS["fcc2: bits 23:16"] FCC3_BITS["fcc3: bits 31:24"] FCC4_BITS["fcc4: bits 39:32"] FCC5_BITS["fcc5: bits 47:40"] FCC6_BITS["fcc6: bits 55:48"] FCC7_BITS["fcc7: bits 63:56"] FCC_LOAD["Load from memoryld.d instruction"] FCC_READ["Read fcc0-fcc7movcf2gr instructions"] end subgraph FCC_RESTORE["RESTORE_FCC Macro Flow"] subgraph FCC_SAVE["SAVE_FCC Macro Flow"] FCC0_BITS["fcc0: bits 7:0"] FCC_LOAD["Load from memoryld.d instruction"] FCC_UNPACK["Unpack bit fieldsbstrpick.d instructions"] FCC_WRITE["Write fcc0-fcc7movgr2cf instructions"] FCC_READ["Read fcc0-fcc7movcf2gr instructions"] FCC_PACK["Pack into single registerbstrins.d instructions"] FCC_STORE["Store to memoryst.d instruction"] end end end FCC_LOAD --> FCC_UNPACK FCC_PACK --> FCC_STORE FCC_READ --> FCC_PACK FCC_UNPACK --> FCC_WRITE
The implementation uses bit manipulation instructions (bstrins.d
, bstrpick.d
) to efficiently pack and unpack the 8 condition code registers into a single 64-bit value for storage.
Sources: src/loongarch64/macros.rs(L87 - L125)
Floating Point Control and Status Register (FCSR)
The FCSR management is simpler than FCC handling since it's a single 32-bit register:
SAVE_FCSR
- Usesmovfcsr2gr
to read FCSR andst.w
to storeRESTORE_FCSR
- Usesld.w
to load andmovgr2fcsr
to write back
Sources: src/loongarch64/macros.rs(L127 - L135)
Floating Point Register Operations
The floating point registers ($f0
-$f31
) are handled through a parameterized macro system similar to general purpose registers:
flowchart TD subgraph FP_REG_OPS["Floating Point Register Operations"] subgraph FP_REGISTERS["32 Floating Point Registers"] F0_F7["f0-f7Temporary registers"] F8_F15["f8-f15Temporary registers"] F16_F23["f16-f23Saved registers"] F24_F31["f24-f31Saved registers"] end subgraph FP_CONCRETE["Concrete Operations"] SAVE_FP["SAVE_FP MacroUses fst.d operation"] RESTORE_FP["RESTORE_FP MacroUses fld.d operation"] end subgraph FP_MACRO["PUSH_POP_FLOAT_REGS Macro"] FP_PARAM["Takes operation and base registerfst.d or fld.d"] FP_LOOP["Iterates f0 through f31Sequential 8-byte offsets"] end end F0_F7 --> FP_LOOP F16_F23 --> FP_LOOP F24_F31 --> FP_LOOP F8_F15 --> FP_LOOP FP_PARAM --> RESTORE_FP FP_PARAM --> SAVE_FP
Sources: src/loongarch64/macros.rs(L138 - L179)
Memory Management Operations
The LoongArch64 architecture provides sophisticated memory management capabilities through dedicated wrapper functions that interact with hardware registers and TLB operations.
Page Table Management
LoongArch64 uses separate page table base registers for user and kernel address spaces:
Function | Register | Address Space |
---|---|---|
read_user_page_table() | PGDL | VA[47] = 0 (user space) |
read_kernel_page_table() | PGDH | VA[47] = 1 (kernel space) |
write_user_page_table() | PGDL | User space page table root |
write_kernel_page_table() | PGDH | Kernel space page table root |
flowchart TD subgraph PAGE_TABLE_OPS["Page Table Operations"] subgraph ADDRESS_SPACES["Virtual Address Spaces"] USER_SPACE["User SpaceVA[47] = 0"] KERNEL_SPACE["Kernel SpaceVA[47] = 1"] end subgraph HARDWARE_REGS["Hardware Registers"] PGDL_REG["PGDL RegisterUser Space Page Table"] PGDH_REG["PGDH RegisterKernel Space Page Table"] end subgraph WRITE_OPS["Write Operations"] WRITE_USER["write_user_page_table()pgdl::set_base()"] WRITE_KERNEL["write_kernel_page_table()pgdh::set_base()"] end subgraph READ_OPS["Read Operations"] READ_USER["read_user_page_table()pgdl::read().base()"] READ_KERNEL["read_kernel_page_table()pgdh::read().base()"] end end PGDH_REG --> KERNEL_SPACE PGDL_REG --> USER_SPACE READ_KERNEL --> PGDH_REG READ_USER --> PGDL_REG WRITE_KERNEL --> PGDH_REG WRITE_USER --> PGDL_REG
Sources: src/loongarch64/asm.rs(L41 - L79)
TLB Management
The Translation Lookaside Buffer (TLB) management uses the invtlb
instruction with specific operation codes:
invtlb 0x00
- Clear all TLB entries (full flush)invtlb 0x05
- Clear specific VA with ASID=0 (single page flush)
The implementation includes proper memory barriers (dbar 0
) to ensure ordering of memory operations around TLB invalidation.
Sources: src/loongarch64/asm.rs(L81 - L111)
Page Walk Controller Configuration
The write_pwc()
function configures the hardware page walker through the PWCL and PWCH registers, which control page table traversal parameters for lower and upper address spaces respectively.
Sources: src/loongarch64/asm.rs(L130 - L150)
Exception and Interrupt Handling
Interrupt Control
The LoongArch64 interrupt system is controlled through the Current Mode register (CRMD):
Function | Operation | Hardware Effect |
---|---|---|
enable_irqs() | crmd::set_ie(true) | Sets IE bit in CRMD |
disable_irqs() | crmd::set_ie(false) | Clears IE bit in CRMD |
irqs_enabled() | crmd::read().ie() | Reads IE bit status |
flowchart TD subgraph IRQ_CONTROL["Interrupt Control System"] subgraph HARDWARE_STATE["Hardware State"] CRMD_REG["CRMD RegisterCurrent Mode Data"] IE_BIT["IE BitInterrupt Enable"] CPU_STATE["CPU Execution StateRunning/Idle"] end subgraph POWER_MANAGEMENT["Power Management"] WAIT_IRQS["wait_for_irqs()loongArch64::asm::idle()"] HALT_CPU["halt()disable_irqs() + idle()"] end subgraph IRQ_FUNCTIONS["IRQ Control Functions"] ENABLE_IRQS["enable_irqs()crmd::set_ie(true)"] DISABLE_IRQS["disable_irqs()crmd::set_ie(false)"] CHECK_IRQS["irqs_enabled()crmd::read().ie()"] end end CHECK_IRQS --> IE_BIT DISABLE_IRQS --> IE_BIT ENABLE_IRQS --> IE_BIT HALT_CPU --> CPU_STATE HALT_CPU --> DISABLE_IRQS IE_BIT --> CRMD_REG WAIT_IRQS --> CPU_STATE
Sources: src/loongarch64/asm.rs(L8 - L39)
Exception Entry Configuration
The write_exception_entry_base()
function configures the exception handling entry point by setting both the Exception Entry Base Address register (EENTRY) and the Exception Configuration register (ECFG):
- Sets
ECFG.VS = 0
for unified exception entry - Sets
EENTRY
to the provided exception handler address
Sources: src/loongarch64/asm.rs(L113 - L128)
Thread Local Storage Support
LoongArch64 implements Thread Local Storage (TLS) through the thread pointer register ($tp
):
read_thread_pointer()
- Reads current$tp
value usingmove
instructionwrite_thread_pointer()
- Updates$tp
register for TLS base address
The implementation also provides kernel stack pointer management through a custom CSR (KSAVE_KSP
) when the uspace
feature is enabled, supporting user-space context switching.
Sources: src/loongarch64/asm.rs(L152 - L199)
Extension Support
Floating Point Extensions
The LoongArch64 implementation supports multiple floating point and SIMD extensions:
enable_fp()
- Enables floating-point instructions by settingEUEN.FPE
enable_lsx()
- Enables LSX (LoongArch SIMD eXtension) by settingEUEN.SXE
These functions control the Extended Unit Enable register (EUEN) to selectively enable hardware extensions based on system requirements.
Sources: src/loongarch64/asm.rs(L174 - L187)
LoongArch64 System Initialization
Relevant source files
This document covers the LoongArch64 system initialization routines in the axcpu library, focusing on the bootstrapping procedures for CPU state, memory management unit (MMU), and trap handling. The initialization process establishes the fundamental hardware configurations required for the CPU to operate in the kernel environment.
This page specifically covers system-level hardware initialization. For LoongArch64 CPU context management and task switching, see LoongArch64 Context Management. For low-level assembly operations and register manipulation, see LoongArch64 Assembly Operations.
Initialization Overview
The LoongArch64 system initialization process consists of two primary components: MMU/TLB configuration and trap handling setup. These initialization routines are implemented in the init_mmu
and init_trap
functions, which must be called during system bootstrap to establish a properly configured execution environment.
flowchart TD subgraph subGraph2["Trap Initialization"] EXCEPTION_VECTOR["Exception Vector Setup"] HANDLER_BASE["Handler Base Address"] end subgraph subGraph1["MMU Initialization"] TLB_CONFIG["TLB Configuration"] PAGE_TABLE["Page Table Setup"] TRANSLATION["Enable Translation"] end subgraph subGraph0["System Bootstrap Sequence"] START["System Boot"] INIT_MMU["init_mmu()"] INIT_TRAP["init_trap()"] READY["System Ready"] end INIT_MMU --> PAGE_TABLE INIT_MMU --> READY INIT_MMU --> TLB_CONFIG INIT_MMU --> TRANSLATION INIT_TRAP --> EXCEPTION_VECTOR INIT_TRAP --> HANDLER_BASE INIT_TRAP --> READY START --> INIT_MMU START --> INIT_TRAP
Sources: src/loongarch64/init.rs(L1 - L49)
MMU and TLB Initialization
The init_mmu
function performs comprehensive memory management unit initialization, configuring the Translation Lookaside Buffer (TLB), page table walking parameters, and enabling virtual memory translation.
TLB Configuration Process
The MMU initialization begins with TLB configuration, setting consistent 4KB page sizes across multiple TLB-related registers and establishing the TLB refill exception handler address.
flowchart TD subgraph subGraph1["Handler Setup"] HANDLE_TLB["handle_tlb_refill(extern assembly function)"] PHYS_ADDR["Convert to physical addressusing phys_virt_offset"] end subgraph subGraph0["TLB Register Configuration"] PS_4K["PS_4K = 0x0c(4KB Page Size)"] TLBIDX["tlbidx::set_ps()"] STLBPS["stlbps::set_ps()"] TLBREHI["tlbrehi::set_ps()"] TLBRENTRY["tlbrentry::set_tlbrentry()"] end HANDLE_TLB --> PHYS_ADDR PHYS_ADDR --> TLBRENTRY PS_4K --> STLBPS PS_4K --> TLBIDX PS_4K --> TLBREHI
Sources: src/loongarch64/init.rs(L19 - L25)
Page Table Walking Configuration
Following TLB setup, the function configures page table walking parameters and establishes both kernel and user page table root addresses.
Configuration Step | Function Call | Purpose |
---|---|---|
PWC Registers | write_pwc(PWCL_VALUE, PWCH_VALUE) | Set page walking control values |
Kernel Page Table | write_kernel_page_table(root_paddr) | Configure kernel space translation |
User Page Table | write_user_page_table(pa!(0)) | Initialize user space (initially zero) |
TLB Flush | flush_tlb(None) | Clear existing TLB entries |
Sources: src/loongarch64/init.rs(L28 - L33)
Translation Mode Activation
The final step enables mapped address translation mode through the Current Mode (CRMD
) register, activating virtual memory management.
flowchart TD subgraph subGraph0["Translation Activation"] CRMD_REG["CRMD Register"] PG_BIT["PG Bit = true"] TRANSLATION_ON["Virtual Memory Active"] end CRMD_REG --> PG_BIT PG_BIT --> TRANSLATION_ON
Sources: src/loongarch64/init.rs(L35 - L36)
Trap and Exception Initialization
The init_trap
function establishes the exception handling infrastructure by configuring the exception vector base address, which serves as the entry point for all hardware exceptions and interrupts.
Exception Vector Configuration
The trap initialization process involves setting up the base address for exception handling routines, linking the hardware exception mechanism to the software handlers.
flowchart TD subgraph subGraph0["Exception Vector Setup"] EXCEPTION_ENTRY["exception_entry_base(extern assembly function)"] ADDR_CALC["Get function address(exception_entry_base as usize)"] WRITE_BASE["write_exception_entry_base()(assembly call)"] VECTOR_SET["Exception Vector Configured"] end ADDR_CALC --> WRITE_BASE EXCEPTION_ENTRY --> ADDR_CALC WRITE_BASE --> VECTOR_SET
Sources: src/loongarch64/init.rs(L42 - L48)
Assembly Integration Points
Both initialization functions rely heavily on external assembly routines and low-level register operations that interface directly with LoongArch64 hardware.
External Assembly Functions
Function | Purpose | Used By |
---|---|---|
handle_tlb_refill | TLB refill exception handler | init_mmu |
exception_entry_base | Exception vector entry point | init_trap |
Assembly Module Integration
The initialization routines call several functions from the crate::asm
module:
write_pwc()
- Configure page walking control registerswrite_kernel_page_table()
- Set kernel page table rootwrite_user_page_table()
- Set user page table rootflush_tlb()
- Invalidate TLB entrieswrite_exception_entry_base()
- Set exception vector base
Sources: src/loongarch64/init.rs(L29 - L47)
Register and Hardware References
The initialization code directly manipulates several LoongArch64-specific control registers through the loongArch64::register
module:
- CRMD - Current Mode register for translation control
- STLBPS - Shared TLB Page Size register
- TLBIDX - TLB Index register
- TLBREHI - TLB Refill Entry High register
- TLBRENTRY - TLB Refill Exception Entry Base Address register
These registers are documented in the LoongArch Architecture Reference Manual, with specific links provided in the source code comments.
Sources: src/loongarch64/init.rs(L3 - L13)
Cross-Architecture Features
Relevant source files
Purpose and Scope
This document covers features and abstractions in the axcpu library that work uniformly across all supported architectures (x86_64, AArch64, RISC-V, and LoongArch64). These cross-architecture features provide unified interfaces and common functionality that abstract away architecture-specific implementation details.
For architecture-specific implementations, see the dedicated architecture pages: x86_64 Architecture, AArch64 Architecture, RISC-V Architecture, and LoongArch64 Architecture. For user space specific functionality, see User Space Support.
Unified Trap Handling Framework
The axcpu library provides a distributed trap handling system that allows external code to register handlers for various types of traps and exceptions. This system works consistently across all supported architectures through the linkme
crate's distributed slice mechanism.
Trap Handler Registration
The core trap handling framework defines three types of distributed handler slices:
flowchart TD subgraph subGraph2["Handler Invocation"] HANDLE_TRAP["handle_trap! macroDispatches to first handler"] HANDLE_SYSCALL["handle_syscall functionDirect syscall dispatch"] end subgraph subGraph1["Registration Mechanism"] LINKME["linkme::distributed_sliceCompile-time collection"] DEF_TRAP["def_trap_handler macroCreates handler slices"] REG_TRAP["register_trap_handler macroRegisters individual handlers"] end subgraph subGraph0["Trap Handler Types"] IRQ["IRQ[fn(usize) -> bool]Hardware interrupts"] PAGE_FAULT["PAGE_FAULT[fn(VirtAddr, PageFaultFlags, bool) -> bool]Memory access violations"] SYSCALL["SYSCALL[fn(&TrapFrame, usize) -> isize]System calls (uspace feature)"] end DEF_TRAP --> IRQ DEF_TRAP --> PAGE_FAULT DEF_TRAP --> SYSCALL IRQ --> HANDLE_TRAP LINKME --> DEF_TRAP PAGE_FAULT --> HANDLE_TRAP REG_TRAP --> IRQ REG_TRAP --> PAGE_FAULT REG_TRAP --> SYSCALL SYSCALL --> HANDLE_SYSCALL
Sources: src/trap.rs(L10 - L22) src/trap.rs(L6 - L7)
Handler Dispatch Mechanism
The trap handling system uses a macro-based dispatch mechanism that supports single-handler registration. When multiple handlers are registered for the same trap type, the system issues a warning and uses only the first handler.
flowchart TD subgraph subGraph1["Handler Selection Logic"] ITERATOR["Handler slice iterator"] FIRST["Take first handler"] WARN["Warn if multiple handlers"] DEFAULT["Default behavior if none"] end subgraph subGraph0["Dispatch Flow"] TRAP_EVENT["Trap EventHardware generated"] ARCH_HANDLER["Architecture HandlerCaptures context"] HANDLE_MACRO["handle_trap! macroFinds registered handler"] USER_HANDLER["User HandlerApplication logic"] RETURN["Return ControlResume or terminate"] end ARCH_HANDLER --> HANDLE_MACRO HANDLE_MACRO --> ITERATOR HANDLE_MACRO --> USER_HANDLER ITERATOR --> DEFAULT ITERATOR --> FIRST ITERATOR --> WARN TRAP_EVENT --> ARCH_HANDLER USER_HANDLER --> RETURN
Sources: src/trap.rs(L25 - L38) src/trap.rs(L42 - L44)
Architecture-Agnostic Abstractions
The library provides common abstractions that are implemented differently by each architecture but present a unified interface to higher-level code.
Core Data Structure Patterns
Each architecture implements the same core abstractions with architecture-specific layouts:
flowchart TD subgraph subGraph1["Architecture Implementations"] X86_IMPL["x86_64 ImplementationCallee-saved regs + stack + TLS"] ARM_IMPL["aarch64 ImplementationR19-R29 + SP + TPIDR + FpState"] RISC_IMPL["riscv Implementationra, sp, s0-s11, tp + SATP"] LOONG_IMPL["loongarch64 ImplementationCallee-saved + SP + TP + FPU"] end subgraph subGraph0["Common Abstractions"] TASK_CONTEXT["TaskContextMinimal context for task switching"] TRAP_FRAME["TrapFrameComplete CPU state for exceptions"] EXTENDED_STATE["Extended StateFPU, SIMD, special registers"] end EXTENDED_STATE --> ARM_IMPL EXTENDED_STATE --> LOONG_IMPL EXTENDED_STATE --> RISC_IMPL EXTENDED_STATE --> X86_IMPL TASK_CONTEXT --> ARM_IMPL TASK_CONTEXT --> LOONG_IMPL TASK_CONTEXT --> RISC_IMPL TASK_CONTEXT --> X86_IMPL TRAP_FRAME --> ARM_IMPL TRAP_FRAME --> LOONG_IMPL TRAP_FRAME --> RISC_IMPL TRAP_FRAME --> X86_IMPL
Sources: src/lib.rs(L14 - L28) src/trap.rs(L5)
Common Interface Design
The library uses Rust's module system and conditional compilation to provide a unified interface while allowing architecture-specific implementations:
flowchart TD subgraph subGraph2["Architecture Modules"] X86_MOD["x86_64 moduleIntel/AMD implementation"] ARM_MOD["aarch64 moduleARM implementation"] RISC_MOD["riscv moduleRISC-V implementation"] LOONG_MOD["loongarch64 moduleLoongArch implementation"] end subgraph subGraph1["Architecture Selection"] CFG_IF["cfg_if! macroCompile-time arch selection"] TARGET_ARCH["target_arch attributesRust compiler directives"] end subgraph subGraph0["Interface Layer"] LIB_RS["src/lib.rsMain entry point"] TRAP_RS["src/trap.rsCross-arch trap handling"] PUBLIC_API["Public APIRe-exported types and functions"] end ARM_MOD --> PUBLIC_API CFG_IF --> TARGET_ARCH LIB_RS --> CFG_IF LOONG_MOD --> PUBLIC_API RISC_MOD --> PUBLIC_API TARGET_ARCH --> ARM_MOD TARGET_ARCH --> LOONG_MOD TARGET_ARCH --> RISC_MOD TARGET_ARCH --> X86_MOD TRAP_RS --> PUBLIC_API X86_MOD --> PUBLIC_API
Sources: src/lib.rs(L14 - L28)
Feature-Based Conditional Compilation
The axcpu library uses Cargo features to enable optional functionality across all architectures. These features control the compilation of additional capabilities that may not be needed in all use cases.
Feature Flag System
Feature | Purpose | Availability |
---|---|---|
uspace | User space support including system calls | All architectures |
fp-simd | Floating point and SIMD register management | All architectures |
tls | Thread-local storage support | All architectures |
User Space Feature Integration
The uspace
feature enables system call handling across all architectures:
flowchart TD subgraph subGraph1["Architecture Integration"] X86_SYSCALL["x86_64: SYSCALL instructionGS_BASE, CR3 management"] ARM_SYSCALL["aarch64: SVC instructionTTBR0 user page tables"] RISC_SYSCALL["riscv: ECALL instructionSATP register switching"] LOONG_SYSCALL["loongarch64: SYSCALLPGDL page directory"] end subgraph subGraph0["uspace Feature"] FEATURE_FLAG["#[cfg(feature = uspace)]Conditional compilation"] SYSCALL_SLICE["SYSCALL handler sliceSystem call dispatch"] HANDLE_SYSCALL_FN["handle_syscall functionDirect invocation"] end FEATURE_FLAG --> SYSCALL_SLICE HANDLE_SYSCALL_FN --> ARM_SYSCALL HANDLE_SYSCALL_FN --> LOONG_SYSCALL HANDLE_SYSCALL_FN --> RISC_SYSCALL HANDLE_SYSCALL_FN --> X86_SYSCALL SYSCALL_SLICE --> HANDLE_SYSCALL_FN
Sources: src/trap.rs(L19 - L22) src/trap.rs(L41 - L44)
Common Memory Management Abstractions
The library integrates with external crates to provide consistent memory management abstractions across architectures.
External Dependencies Integration
flowchart TD subgraph subGraph2["Architecture Implementation"] MMU_INIT["init_mmu functionsArchitecture-specific setup"] PAGE_TABLES["Page table managementArchitecture-specific formats"] TLB_MGMT["TLB managementTranslation cache control"] end subgraph subGraph1["Cross-Architecture Usage"] VIRT_ADDR["VirtAddrVirtual address type"] PAGE_FAULT_FLAGS["PageFaultFlagsAlias for MappingFlags"] TRAP_HANDLERS["Distributed trap handlersCompile-time registration"] end subgraph subGraph0["External Crate Integration"] MEMORY_ADDR["memory_addr crateVirtAddr, PhysAddr types"] PAGE_TABLE_ENTRY["page_table_entry crateMappingFlags abstraction"] LINKME["linkme crateDistributed slice collection"] end LINKME --> TRAP_HANDLERS MEMORY_ADDR --> VIRT_ADDR PAGE_FAULT_FLAGS --> PAGE_TABLES PAGE_TABLE_ENTRY --> PAGE_FAULT_FLAGS TRAP_HANDLERS --> TLB_MGMT VIRT_ADDR --> MMU_INIT
Sources: src/lib.rs(L9) src/trap.rs(L3) src/trap.rs(L8) src/trap.rs(L6)
Error Handling and Warnings
The cross-architecture framework includes built-in error handling and diagnostic capabilities that work consistently across all supported architectures.
Handler Registration Validation
The trap handling system validates handler registration at runtime and provides warnings for common configuration issues:
flowchart TD subgraph subGraph0["Handler Validation Flow"] CHECK_ITER["Check handler iterator"] FIRST_HANDLER["Get first handler"] CHECK_MORE["Check for additional handlers"] WARN_MULTIPLE["Warn about multiple handlers"] INVOKE["Invoke selected handler"] NO_HANDLER["Warn about missing handlers"] end CHECK_ITER --> FIRST_HANDLER CHECK_ITER --> NO_HANDLER CHECK_MORE --> INVOKE CHECK_MORE --> WARN_MULTIPLE FIRST_HANDLER --> CHECK_MORE NO_HANDLER --> INVOKE
The system issues specific warning messages for:
- Multiple handlers registered for the same trap type
- No handlers registered for a trap that occurs
- Feature mismatches between compile-time and runtime expectations
Sources: src/trap.rs(L25 - L38)
User Space Support
Relevant source files
This document covers the user space support functionality provided by the axcpu library, which enables transitions from kernel mode to user mode across supported architectures. This feature allows operating systems built on axcpu to execute user programs in unprivileged mode while maintaining proper isolation and control.
For architecture-specific trap handling that occurs when transitioning back from user space, see the trap handling sections for each architecture (2.2, 3.2, 4.2). For general context management during task switching, see the context management sections (2.1, 3.1, 4.1, 5.1).
Architecture Support Overview
The user space support is implemented through the uspace
feature and provides a consistent interface across multiple architectures. Each supported architecture implements the functionality through a dedicated uspace.rs
module that provides the UspaceContext
structure and associated methods.
flowchart TD subgraph subGraph3["User Space Support Architecture"] USPACE_FEAT["uspace feature flag"] subgraph subGraph2["Common Interface"] EMPTY["empty()"] NEW["new()"] FROM["from()"] GET_IP["get_ip()"] GET_SP["get_sp()"] SET_IP["set_ip()"] SET_SP["set_sp()"] SET_RETVAL["set_retval()"] end subgraph subGraph1["Core Components"] USPACE_CTX["UspaceContext struct"] TRAP_FRAME["TrapFrame wrapper"] ENTER_USPACE["enter_uspace() method"] end subgraph subGraph0["Architecture Implementations"] RISCV_USPACE["src/riscv/uspace.rsUspaceContext"] AARCH64_USPACE["src/aarch64/uspace.rsUspaceContext"] LOONGARCH64_USPACE["src/loongarch64/uspace.rsUspaceContext"] end end AARCH64_USPACE --> USPACE_CTX LOONGARCH64_USPACE --> USPACE_CTX RISCV_USPACE --> USPACE_CTX USPACE_CTX --> EMPTY USPACE_CTX --> ENTER_USPACE USPACE_CTX --> FROM USPACE_CTX --> GET_IP USPACE_CTX --> GET_SP USPACE_CTX --> NEW USPACE_CTX --> SET_IP USPACE_CTX --> SET_RETVAL USPACE_CTX --> SET_SP USPACE_CTX --> TRAP_FRAME USPACE_FEAT --> AARCH64_USPACE USPACE_FEAT --> LOONGARCH64_USPACE USPACE_FEAT --> RISCV_USPACE
Sources: src/riscv/uspace.rs(L1 - L104) src/aarch64/uspace.rs(L1 - L113) src/loongarch64/uspace.rs(L1 - L98)
UspaceContext Structure
The UspaceContext
is implemented as a wrapper around the architecture-specific TrapFrame
structure. This design provides a unified interface while leveraging the existing trap frame infrastructure for context management.
Architecture | UspaceContext Definition | Underlying TrapFrame |
---|---|---|
RISC-V | pub struct UspaceContext(TrapFrame) | ContainsGeneralRegisters,sepc,sstatus |
AArch64 | pub struct UspaceContext(TrapFrame) | Contains register arrayr[31],usp,elr,spsr |
LoongArch64 | pub struct UspaceContext(TrapFrame) | Contains registers,era,prmd |
Sources: src/riscv/uspace.rs(L8) src/aarch64/uspace.rs(L8) src/loongarch64/uspace.rs(L8)
Context Creation and Management
Each architecture provides consistent methods for creating and manipulating user space contexts:
Context Creation Methods
flowchart TD subgraph subGraph1["Implementation Details"] EMPTY_ZERO["All registers zeroed"] NEW_SETUP["Entry point + stack + argument configured"] FROM_COPY["Copy from existing TrapFrame"] end subgraph subGraph0["UspaceContext Creation"] EMPTY["UspaceContext::empty()"] NEW["UspaceContext::new(entry, ustack_top, arg0)"] FROM["UspaceContext::from(trap_frame)"] end EMPTY --> EMPTY_ZERO FROM --> FROM_COPY NEW --> NEW_SETUP
Sources: src/riscv/uspace.rs(L12 - L35) src/aarch64/uspace.rs(L12 - L38) src/loongarch64/uspace.rs(L12 - L32)
The new()
method performs architecture-specific initialization:
- RISC-V: Sets
SPIE
(enable interrupts) andSUM
(supervisor user memory access) flags insstatus
src/riscv/uspace.rs(L19 - L29) - AArch64: Configures
SPSR_EL1
withEL0t
mode and appropriate interrupt masking src/aarch64/uspace.rs(L26 - L32) - LoongArch64: Sets
PPLV_UMODE
(user privilege level) andPIE
(previous interrupt enable) inprmd
src/loongarch64/uspace.rs(L20 - L26)
Context Access Methods
All architectures provide consistent getter and setter methods:
Method | Purpose | RISC-V | AArch64 | LoongArch64 |
---|---|---|---|---|
get_ip() | Get instruction pointer | self.0.sepc | self.0.elr | self.0.era |
get_sp() | Get stack pointer | self.0.regs.sp | self.0.usp | self.0.regs.sp |
set_ip() | Set instruction pointer | self.0.sepc = pc | self.0.elr = pc | self.0.era = pc |
set_sp() | Set stack pointer | self.0.regs.sp = sp | self.0.usp = sp | self.0.regs.sp = sp |
set_retval() | Set return value | self.0.regs.a0 = a0 | self.0.r[0] = r0 | self.0.regs.a0 = a0 |
Sources: src/riscv/uspace.rs(L37 - L61) src/aarch64/uspace.rs(L40 - L63) src/loongarch64/uspace.rs(L34 - L57)
User Space Entry Process
The enter_uspace()
method performs the critical transition from kernel mode to user mode. This is an unsafe operation that fundamentally changes the processor's execution context.
flowchart TD subgraph subGraph1["Architecture-Specific Details"] RISCV_IMPL["RISC-V: sscratch, sepc, sret"] AARCH64_IMPL["AArch64: sp_el0, elr_el1, spsr_el1, eret"] LOONGARCH64_IMPL["LoongArch64: era, PRMD, ertn"] end subgraph subGraph0["enter_uspace() Flow"] START["enter_uspace(kstack_top)"] DISABLE_IRQ["Disable interrupts"] SETUP_KERNEL_STACK["Setup kernel stack for trap handling"] CONFIGURE_ARCH["Architecture-specific register setup"] RESTORE_CONTEXT["Restore user context from TrapFrame"] SWITCH_MODE["Switch to user mode"] USER_EXEC["Execute user code"] TRAP_RETURN["[On trap/syscall] Return to kernel"] end CONFIGURE_ARCH --> AARCH64_IMPL CONFIGURE_ARCH --> LOONGARCH64_IMPL CONFIGURE_ARCH --> RESTORE_CONTEXT CONFIGURE_ARCH --> RISCV_IMPL DISABLE_IRQ --> SETUP_KERNEL_STACK RESTORE_CONTEXT --> SWITCH_MODE SETUP_KERNEL_STACK --> CONFIGURE_ARCH START --> DISABLE_IRQ SWITCH_MODE --> USER_EXEC USER_EXEC --> TRAP_RETURN
Sources: src/riscv/uspace.rs(L72 - L102) src/aarch64/uspace.rs(L75 - L111) src/loongarch64/uspace.rs(L69 - L96)
Architecture-Specific Entry Implementation
Each architecture implements the final transition using inline assembly:
RISC-V Implementation:
- Uses
sscratch
to store kernel stack pointer for trap handling src/riscv/uspace.rs(L79) - Sets
sepc
with user entry point src/riscv/uspace.rs(L80) - Executes
sret
instruction to return to user mode src/riscv/uspace.rs(L96)
AArch64 Implementation:
- Sets
sp_el0
for user stack,elr_el1
for entry point,spsr_el1
for processor state src/aarch64/uspace.rs(L86 - L88) - Uses
eret
instruction to return to user mode src/aarch64/uspace.rs(L105)
LoongArch64 Implementation:
- Configures
era
register with user entry point src/loongarch64/uspace.rs(L74) - Uses
ertn
instruction to return to user mode src/loongarch64/uspace.rs(L91)
Cross-Architecture Design Patterns
The user space support demonstrates several consistent design patterns across architectures:
Trap Frame Reuse
All implementations leverage the existing TrapFrame
structure rather than defining separate user context formats. This provides consistency with the trap handling infrastructure and ensures that user contexts contain all necessary state for exception handling.
Kernel Stack Management
Each architecture properly configures the kernel stack pointer to handle subsequent traps or system calls from user mode:
- RISC-V: Uses
sscratch
CSR src/riscv/uspace.rs(L79) - AArch64: Uses
sp_el1
(kernel stack remains in place) src/aarch64/uspace.rs(L77 - L79) - LoongArch64: Uses
write_kernel_sp()
helper src/loongarch64/uspace.rs(L73)
Register State Initialization
The new()
method in each architecture sets appropriate processor state flags for user mode execution, ensuring proper privilege levels and interrupt handling configuration.
Sources: src/riscv/uspace.rs(L1 - L104) src/aarch64/uspace.rs(L1 - L113) src/loongarch64/uspace.rs(L1 - L98)
Core Trap Handling Framework
Relevant source files
Purpose and Scope
The Core Trap Handling Framework provides a unified, cross-architecture mechanism for registering and dispatching trap handlers in axcpu. This framework enables external code to register handlers for interrupts, page faults, and system calls without needing to know architecture-specific details. The framework uses Rust's linkme
crate to create distributed slices that collect handlers at link time.
For architecture-specific trap handling implementations, see x86_64 Trap and Exception Handling, AArch64 Trap and Exception Handling, RISC-V Trap and Exception Handling, and related sections. For user space system call support, see User Space Support.
Handler Registration Mechanism
The framework uses linkme::distributed_slice
to create static collections of handler functions that are populated at link time. This allows modules throughout the codebase to register handlers without requiring explicit registration calls.
Handler Registration Architecture
flowchart TD subgraph subGraph2["External Modules"] EXT_IRQ["External IRQ Handler"] EXT_PF["External Page Fault Handler"] EXT_SYS["External Syscall Handler"] end subgraph subGraph1["Static Handler Collections"] IRQ_SLICE["IRQ: [fn(usize) -> bool]"] PF_SLICE["PAGE_FAULT: [fn(VirtAddr, PageFaultFlags, bool) -> bool]"] SYSCALL_SLICE["SYSCALL: [fn(&TrapFrame, usize) -> isize]"] end subgraph subGraph0["Handler Registration"] LINKME["linkme::distributed_slice"] DEF_MACRO["def_trap_handler macro"] REG_MACRO["register_trap_handler macro"] end DEF_MACRO --> IRQ_SLICE DEF_MACRO --> PF_SLICE DEF_MACRO --> SYSCALL_SLICE EXT_IRQ --> IRQ_SLICE EXT_PF --> PF_SLICE EXT_SYS --> SYSCALL_SLICE LINKME --> DEF_MACRO LINKME --> REG_MACRO
Sources: src/trap.rs(L6 - L7) src/trap.rs(L11 - L22)
The framework exports two key macros:
def_trap_handler
- Used internally to define handler collectionsregister_trap_handler
- Used by external code to register handlers
Trap Types and Handler Signatures
The framework defines three primary trap types, each with specific handler signatures optimized for their use cases.
Trap Type | Handler Signature | Purpose | Feature Gate |
---|---|---|---|
IRQ | fn(usize) -> bool | Hardware interrupt handling | Always available |
PAGE_FAULT | fn(VirtAddr, PageFaultFlags, bool) -> bool | Memory access violations | Always available |
SYSCALL | fn(&TrapFrame, usize) -> isize | System call handling | uspacefeature |
Trap Handler Details
flowchart TD subgraph subGraph1["Page Fault Handler"] SYS_FRAME["TrapFrame"] SYS_RESULT["Return Value (isize)"] PF_ADDR["Virtual Address"] PF_USER["User Space (bool)"] IRQ_NUM["IRQ Number (usize)"] IRQ_RESULT["Handled (bool)"] subgraph subGraph2["Syscall Handler"] SYS_NUM["Syscall Number"] PF_RESULT["Handled (bool)"] PF_FLAGS["PageFaultFlags"] subgraph subGraph0["IRQ Handler"] SYS_FRAME["TrapFrame"] SYS_RESULT["Return Value (isize)"] PF_ADDR["Virtual Address"] IRQ_NUM["IRQ Number (usize)"] IRQ_RESULT["Handled (bool)"] end end end IRQ_NUM --> IRQ_RESULT PF_ADDR --> PF_RESULT PF_FLAGS --> PF_RESULT PF_USER --> PF_RESULT SYS_FRAME --> SYS_RESULT SYS_NUM --> SYS_RESULT
Sources: src/trap.rs(L12) src/trap.rs(L16) src/trap.rs(L22)
Trap Dispatching Framework
The framework provides a unified dispatching mechanism through the handle_trap
macro and specialized functions for different trap types.
Dispatch Mechanism
flowchart TD subgraph subGraph1["Error Handling"] MULTI_WARN["Multiple Handler Warning"] NO_HANDLER["No Handler Warning"] end subgraph subGraph0["Trap Dispatch Flow"] ARCH_TRAP["Architecture-Specific Trap Entry"] HANDLE_MACRO["handle_trap! macro"] HANDLER_LOOKUP["Handler Lookup"] SINGLE_CHECK["Single Handler Check"] HANDLER_CALL["Handler Function Call"] end subgraph subGraph2["Specialized Functions"] SYSCALL_FUNC["handle_syscall()"] DIRECT_CALL["Direct SYSCALL[0] call"] end ARCH_TRAP --> HANDLE_MACRO HANDLER_LOOKUP --> NO_HANDLER HANDLER_LOOKUP --> SINGLE_CHECK HANDLE_MACRO --> HANDLER_LOOKUP SINGLE_CHECK --> HANDLER_CALL SINGLE_CHECK --> MULTI_WARN SYSCALL_FUNC --> DIRECT_CALL
Sources: src/trap.rs(L25 - L38) src/trap.rs(L42 - L44)
Macro Implementation
The handle_trap
macro provides a standardized way to dispatch to registered handlers:
- Retrieves the handler slice for the specified trap type
- Checks for exactly one registered handler
- Issues warnings for multiple or missing handlers
- Calls the handler with the provided arguments
System Call Specialization
System calls receive special handling through the handle_syscall
function, which directly calls the first (and expected only) syscall handler without the overhead of the generic dispatch mechanism.
Sources: src/trap.rs(L41 - L44)
Integration with Architecture-Specific Code
The Core Trap Handling Framework serves as the interface between architecture-specific trap entry points and higher-level system components.
Architecture Integration Flow
flowchart TD subgraph subGraph2["System Components"] KERNEL["Kernel Services"] DRIVER["Device Drivers"] SYSCALL_SVC["System Call Service"] end subgraph subGraph1["Core Framework"] TRAP_MACRO["handle_trap! macro"] IRQ_HANDLERS["IRQ handler slice"] PF_HANDLERS["PAGE_FAULT handler slice"] SYS_HANDLERS["SYSCALL handler slice"] end subgraph subGraph0["Architecture Layer"] X86_TRAP["x86_64 trap handlers"] ARM_TRAP["aarch64 trap handlers"] RISCV_TRAP["riscv trap handlers"] LOONG_TRAP["loongarch64 trap handlers"] end ARM_TRAP --> TRAP_MACRO IRQ_HANDLERS --> DRIVER LOONG_TRAP --> TRAP_MACRO PF_HANDLERS --> KERNEL RISCV_TRAP --> TRAP_MACRO SYS_HANDLERS --> SYSCALL_SVC TRAP_MACRO --> IRQ_HANDLERS TRAP_MACRO --> PF_HANDLERS TRAP_MACRO --> SYS_HANDLERS X86_TRAP --> TRAP_MACRO
Sources: src/lib.rs(L12) src/lib.rs(L15 - L27)
Type System Integration
The framework integrates with core axcpu types:
TrapFrame
- Architecture-specific CPU state during trapsVirtAddr
- Virtual memory addresses from thememory_addr
cratePageFaultFlags
- Memory access flags frompage_table_entry
crate
This type integration ensures that handlers receive properly structured data regardless of the underlying architecture.
Sources: src/trap.rs(L3) src/trap.rs(L5) src/trap.rs(L8)
Development and Build Configuration
Relevant source files
This document covers the build system, dependency management, and development environment configuration for the axcpu library. It explains how to set up the development environment, configure build targets, and understand the feature-based compilation system.
For information about architecture-specific implementations, see the respective architecture pages (x86_64, AArch64, RISC-V, LoongArch64). For details about cross-architecture features and their runtime behavior, see Cross-Architecture Features.
Build System Overview
The axcpu library uses Cargo as its build system with a sophisticated feature-based configuration that enables conditional compilation for different architectures and capabilities. The build system is designed to support both bare-metal (no_std) environments and multiple target architectures simultaneously.
Package Configuration
flowchart TD subgraph Categories["Categories"] EMBEDDED["embedded"] NOSTD_CAT["no-std"] HWSUPPORT["hardware-support"] OS["os"] end subgraph subGraph1["Build Requirements"] RUST["Rust 1.88.0+rust-version requirement"] NOSTD["no_std EnvironmentBare metal support"] TARGETS["Multiple TargetsCross-compilation ready"] end subgraph subGraph0["Package Structure"] PKG["axcpu Packageversion: 0.1.1edition: 2021"] DESC["Multi-arch CPU abstractionGPL-3.0 | Apache-2.0 | MulanPSL-2.0"] REPO["Repositorygithub.com/arceos-org/axcpu"] end PKG --> DESC PKG --> EMBEDDED PKG --> HWSUPPORT PKG --> NOSTD PKG --> NOSTD_CAT PKG --> OS PKG --> REPO PKG --> RUST PKG --> TARGETS
The package configuration establishes axcpu as a foundational library in the embedded and OS development ecosystem, with specific version requirements and licensing terms.
Sources: Cargo.toml(L1 - L21)
Feature Flag System
The axcpu library uses a feature-based compilation system that allows selective inclusion of functionality based on target requirements and capabilities.
Feature Flag Architecture
flowchart TD subgraph subGraph2["Architecture Integration"] X86_FP["x86_64: FXSAVE/FXRSTORSSE/AVX support"] ARM_FP["aarch64: V-registersSIMD operations"] RISC_FP["riscv: Future FP supportExtension hooks"] LOONG_FP["loongarch64: F-registersFPU state management"] X86_TLS["x86_64: FS_BASESegment-based TLS"] ARM_TLS["aarch64: TPIDR_EL0Thread pointer register"] RISC_TLS["riscv: TP registerThread pointer"] LOONG_TLS["loongarch64: TP registerThread pointer"] X86_US["x86_64: CR3 + GS_BASESyscall interface"] ARM_US["aarch64: TTBR0EL0 support"] RISC_US["riscv: SATPSystem calls"] LOONG_US["loongarch64: PGDLSystem calls"] end subgraph subGraph1["Core Features"] DEFAULT["default = []Minimal feature set"] subgraph subGraph0["Optional Features"] FPSIMD["fp-simdFloating-point & SIMD"] TLS["tlsThread Local Storage"] USPACE["uspaceUser space support"] end end FPSIMD --> ARM_FP FPSIMD --> LOONG_FP FPSIMD --> RISC_FP FPSIMD --> X86_FP TLS --> ARM_TLS TLS --> LOONG_TLS TLS --> RISC_TLS TLS --> X86_TLS USPACE --> ARM_US USPACE --> LOONG_US USPACE --> RISC_US USPACE --> X86_US
Each feature flag enables specific functionality across all supported architectures, with architecture-specific implementations handling the low-level details.
Sources: Cargo.toml(L23 - L27)
Dependency Management
The dependency structure is organized into common dependencies and architecture-specific dependencies that are conditionally included based on the target architecture.
Common Dependencies
flowchart TD subgraph subGraph1["Core Functionality"] AXCPU["axcpu Library"] CONTEXT["Context Management"] TRAP["Trap Handling"] INIT["System Initialization"] end subgraph subGraph0["Universal Dependencies"] LINKME["linkme = 0.3Static registration"] LOG["log = 0.4Logging framework"] CFGIF["cfg-if = 1.0Conditional compilation"] MEMADDR["memory_addr = 0.3Address types"] PTE["page_table_entry = 0.5Page table abstractions"] STATIC["static_assertions = 1.1.0Compile-time checks"] end CFGIF --> CONTEXT LINKME --> TRAP LOG --> AXCPU MEMADDR --> CONTEXT MEMADDR --> TRAP PTE --> INIT STATIC --> AXCPU
These dependencies provide foundational functionality used across all architectures, including memory management abstractions, logging, and compile-time utilities.
Sources: Cargo.toml(L29 - L35)
Architecture-Specific Dependencies
flowchart TD subgraph subGraph4["Target Conditions"] X86_TARGET["cfg(target_arch = x86_64)"] ARM_TARGET["cfg(target_arch = aarch64)"] RISC_TARGET["cfg(any(riscv32, riscv64))"] LOONG_TARGET["cfg(target_arch = loongarch64)"] end subgraph subGraph3["LoongArch64 Dependencies"] LOONG["loongArch64 = 0.2.4LoongArch ISA"] PTMULTI["page_table_multiarch = 0.5Multi-arch page tables"] end subgraph subGraph2["RISC-V Dependencies"] RISCV["riscv = 0.14RISC-V ISA support"] end subgraph subGraph1["AArch64 Dependencies"] AARCH64["aarch64-cpu = 10.0ARM64 CPU features"] TOCK["tock-registers = 0.9Register abstractions"] end subgraph subGraph0["x86_64 Dependencies"] X86["x86 = 0.52x86 instruction wrappers"] X86_64["x86_64 = 0.15.2x86_64 structures"] PERCPU["percpu = 0.2Per-CPU variables"] LAZY["lazyinit = 0.2Lazy initialization"] end ARM_TARGET --> AARCH64 ARM_TARGET --> TOCK LOONG_TARGET --> LOONG LOONG_TARGET --> PTMULTI RISC_TARGET --> RISCV X86_TARGET --> LAZY X86_TARGET --> PERCPU X86_TARGET --> X86 X86_TARGET --> X86_64
Each architecture includes specific dependencies that provide low-level access to architecture-specific features, instruction sets, and register management.
Sources: Cargo.toml(L37 - L52)
Target Architecture Configuration
The build system supports multiple target architectures with specific configurations for documentation and cross-compilation.
Documentation Targets
The library is configured to build documentation for all supported target architectures:
Target | Purpose | ABI |
---|---|---|
x86_64-unknown-none | x86_64 bare metal | Hard float |
aarch64-unknown-none-softfloat | ARM64 bare metal | Soft float |
riscv64gc-unknown-none-elf | RISC-V 64-bit | General+Compressed |
loongarch64-unknown-none-softfloat | LoongArch64 bare metal | Soft float |
flowchart TD subgraph subGraph2["Cross-Compilation Support"] RUSTC["rustc target support"] CARGO["cargo build system"] TOOLCHAIN["Architecture toolchains"] end subgraph subGraph1["Documentation Build"] ALL_FEATURES["all-features = trueComplete API coverage"] DOCS_RS["docs.rs integrationOnline documentation"] end subgraph subGraph0["Build Targets"] X86_TARGET["x86_64-unknown-noneIntel/AMD 64-bit"] ARM_TARGET["aarch64-unknown-none-softfloatARM 64-bit softfloat"] RISC_TARGET["riscv64gc-unknown-none-elfRISC-V 64-bit G+C"] LOONG_TARGET["loongarch64-unknown-none-softfloatLoongArch 64-bit"] end ALL_FEATURES --> DOCS_RS ARM_TARGET --> ALL_FEATURES ARM_TARGET --> RUSTC CARGO --> TOOLCHAIN LOONG_TARGET --> ALL_FEATURES LOONG_TARGET --> RUSTC RISC_TARGET --> ALL_FEATURES RISC_TARGET --> RUSTC RUSTC --> CARGO X86_TARGET --> ALL_FEATURES X86_TARGET --> RUSTC
Sources: Cargo.toml(L57 - L59)
Development Workflow
Setting Up the Development Environment
- Rust Toolchain: Ensure Rust 1.88.0 or later is installed
- Target Installation: Install required target architectures using
rustup target add
- Cross-Compilation Tools: Install architecture-specific toolchains if needed
- Feature Testing: Use
cargo build --features
to test specific feature combinations
Build Commands
# Build with all features
cargo build --all-features
# Build for specific architecture
cargo build --target x86_64-unknown-none
# Build with specific features
cargo build --features "fp-simd,tls,uspace"
# Generate documentation
cargo doc --all-features --open
Linting Configuration
The project includes specific linting rules to maintain code quality:
flowchart TD subgraph subGraph1["Code Quality"] STATIC_ASSERT["static_assertionsCompile-time verification"] RUSTFMT["rustfmtCode formatting"] CHECKS["Automated checksCI/CD pipeline"] end subgraph subGraph0["Clippy Configuration"] CLIPPY["clippy lints"] NEWDEFAULT["new_without_default = allowConstructor pattern flexibility"] end CLIPPY --> NEWDEFAULT CLIPPY --> STATIC_ASSERT RUSTFMT --> CHECKS STATIC_ASSERT --> RUSTFMT
Sources: Cargo.toml(L54 - L55)
The development and build configuration ensures consistent behavior across all supported architectures while providing flexibility for different deployment scenarios and feature requirements.
Dependencies and Package Structure
Relevant source files
This document explains the dependency structure and package organization of the axcpu library. It covers the external crates that provide architecture-specific functionality, common utilities, and abstractions used across all supported architectures. For detailed information about specific architecture implementations, see the individual architecture sections (x86_64, AArch64, RISC-V, LoongArch64).
Core Package Structure
The axcpu crate is organized as a single package that provides multi-architecture CPU abstraction through conditional compilation and architecture-specific dependencies. The library selectively includes dependencies based on the target architecture being compiled for.
Main Dependency Categories
flowchart TD subgraph subGraph4["axcpu Package Structure"] AXCPU["axcpu v0.1.1Main Package"] subgraph subGraph3["Build Support"] CFG_IF["cfg-if v1.0.1Conditional compilation"] STATIC_ASSERTIONS["static_assertions v1.1.0Compile-time checks"] LOG["log v0.4.27Logging facade"] end subgraph subGraph2["System Utilities"] PERCPU["percpu v0.2.0Per-CPU data structures"] LAZYINIT["lazyinit v0.2.2Lazy initialization"] LINKME["linkme v0.3.33Linker-based collections"] TOCK_REGISTERS["tock-registers v0.9.0Register field access"] end subgraph subGraph1["Memory Management"] MEMORY_ADDR["memory_addr v0.3.2Address type abstractions"] PAGE_TABLE_ENTRY["page_table_entry v0.5.3Page table entry types"] PAGE_TABLE_MULTI["page_table_multiarch v0.5.3Multi-arch page tables"] end subgraph subGraph0["Architecture Support Crates"] X86["x86 v0.52.0x86 CPU abstractions"] X86_64["x86_64 v0.15.2x86_64 specific features"] AARCH64["aarch64-cpu v10.0.0ARM64 register access"] RISCV["riscv v0.14.0RISC-V CSR and instructions"] LOONGARCH["loongArch64 v0.2.5LoongArch64 support"] end end AXCPU --> AARCH64 AXCPU --> CFG_IF AXCPU --> LAZYINIT AXCPU --> LINKME AXCPU --> LOG AXCPU --> LOONGARCH AXCPU --> MEMORY_ADDR AXCPU --> PAGE_TABLE_ENTRY AXCPU --> PAGE_TABLE_MULTI AXCPU --> PERCPU AXCPU --> RISCV AXCPU --> STATIC_ASSERTIONS AXCPU --> TOCK_REGISTERS AXCPU --> X86 AXCPU --> X86_64
Sources: Cargo.lock(L21 - L39)
Architecture-Specific Dependencies
Each supported architecture relies on specialized crates that provide low-level access to CPU features, registers, and instructions.
x86/x86_64 Dependencies
flowchart TD subgraph subGraph2["x86_64 Architecture Support"] X86_CRATE["x86 v0.52.0"] X86_64_CRATE["x86_64 v0.15.2"] subgraph subGraph1["x86_64 Dependencies"] BITFLAGS_2["bitflags v2.9.1"] VOLATILE["volatile v0.4.6"] RUSTVERSION["rustversion v1.0.21"] end subgraph subGraph0["x86 Dependencies"] BIT_FIELD["bit_field v0.10.2"] BITFLAGS_1["bitflags v1.3.2"] RAW_CPUID["raw-cpuid v10.7.0"] end end X86_64_CRATE --> BITFLAGS_2 X86_64_CRATE --> BIT_FIELD X86_64_CRATE --> RUSTVERSION X86_64_CRATE --> VOLATILE X86_CRATE --> BITFLAGS_1 X86_CRATE --> BIT_FIELD X86_CRATE --> RAW_CPUID
Crate | Purpose | Key Features |
---|---|---|
x86 | 32-bit x86 support | CPU ID, MSR access, I/O port operations |
x86_64 | 64-bit extensions | Page table management, 64-bit registers, VirtAddr/PhysAddr |
raw-cpuid | CPU feature detection | CPUID instruction wrapper |
volatile | Memory access control | Prevents compiler optimizations on memory operations |
Sources: Cargo.lock(L315 - L335)
AArch64 Dependencies
flowchart TD subgraph subGraph0["AArch64 Architecture Support"] AARCH64_CRATE["aarch64-cpu v10.0.0"] TOCK_REG["tock-registers v0.9.0"] end AARCH64_CRATE --> TOCK_REG
The aarch64-cpu
crate provides register access patterns and system control for ARM64 processors, built on top of the tock-registers
framework for type-safe register field manipulation.
Sources: Cargo.lock(L6 - L12)
RISC-V Dependencies
flowchart TD subgraph subGraph1["RISC-V Architecture Support"] RISCV_CRATE["riscv v0.14.0"] subgraph subGraph0["RISC-V Dependencies"] CRITICAL_SECTION["critical-section v1.2.0"] EMBEDDED_HAL["embedded-hal v1.0.0"] PASTE["paste v1.0.15"] RISCV_MACROS["riscv-macros v0.2.0"] RISCV_PAC["riscv-pac v0.2.0"] end end RISCV_CRATE --> CRITICAL_SECTION RISCV_CRATE --> EMBEDDED_HAL RISCV_CRATE --> PASTE RISCV_CRATE --> RISCV_MACROS RISCV_CRATE --> RISCV_PAC
Component | Purpose |
---|---|
riscv-macros | Procedural macros for CSR access |
riscv-pac | Peripheral Access Crate for RISC-V |
critical-section | Interrupt-safe critical sections |
paste | Token pasting for macro generation |
Sources: Cargo.lock(L229 - L239)
LoongArch64 Dependencies
flowchart TD subgraph subGraph1["LoongArch64 Architecture Support"] LOONGARCH_CRATE["loongArch64 v0.2.5"] subgraph subGraph0["LoongArch Dependencies"] BIT_FIELD_LA["bit_field v0.10.2"] BITFLAGS_LA["bitflags v2.9.1"] end end LOONGARCH_CRATE --> BITFLAGS_LA LOONGARCH_CRATE --> BIT_FIELD_LA
The LoongArch64 support uses bit manipulation utilities for register and instruction field access.
Sources: Cargo.lock(L120 - L127)
Memory Management Dependencies
The memory management subsystem relies on a set of interconnected crates that provide address abstractions and page table management across architectures.
flowchart TD subgraph subGraph2["Memory Management Dependency Chain"] PAGE_TABLE_ENTRY_CRATE["page_table_entry v0.5.3PTE abstractions"] PAGE_TABLE_MULTI_CRATE["page_table_multiarch v0.5.3Multi-arch page tables"] MEMORY_ADDR_CRATE["memory_addr v0.3.2VirtAddr, PhysAddr types"] subgraph subGraph1["Multi-arch Page Table Dependencies"] PTM_LOG["log"] PTM_MEMORY_ADDR["memory_addr"] PTM_PAGE_TABLE_ENTRY["page_table_entry"] PTM_RISCV["riscv v0.12.1"] PTM_X86["x86"] end subgraph subGraph0["Page Table Entry Dependencies"] PTE_AARCH64["aarch64-cpu"] PTE_X86_64["x86_64"] PTE_BITFLAGS["bitflags v2.9.1"] PTE_MEMORY_ADDR["memory_addr"] end end MEMORY_ADDR_CRATE --> PAGE_TABLE_ENTRY_CRATE PAGE_TABLE_ENTRY_CRATE --> PAGE_TABLE_MULTI_CRATE PAGE_TABLE_ENTRY_CRATE --> PTE_AARCH64 PAGE_TABLE_ENTRY_CRATE --> PTE_BITFLAGS PAGE_TABLE_ENTRY_CRATE --> PTE_MEMORY_ADDR PAGE_TABLE_ENTRY_CRATE --> PTE_X86_64 PAGE_TABLE_MULTI_CRATE --> PTM_LOG PAGE_TABLE_MULTI_CRATE --> PTM_MEMORY_ADDR PAGE_TABLE_MULTI_CRATE --> PTM_PAGE_TABLE_ENTRY PAGE_TABLE_MULTI_CRATE --> PTM_RISCV PAGE_TABLE_MULTI_CRATE --> PTM_X86
Sources: Cargo.lock(L130 - L158)
System Utility Dependencies
Per-CPU Data Management
flowchart TD subgraph subGraph2["percpu Crate Structure"] PERCPU_CRATE["percpu v0.2.0Per-CPU data structures"] subgraph subGraph1["Macro Dependencies"] PROC_MACRO2["proc-macro2 v1.0.95"] QUOTE["quote v1.0.40"] SYN["syn v2.0.104"] end subgraph subGraph0["percpu Dependencies"] PERCPU_CFG_IF["cfg-if v1.0.1"] PERCPU_MACROS["percpu_macros v0.2.0"] PERCPU_SPIN["spin v0.9.8"] PERCPU_X86["x86 v0.52.0"] end end PERCPU_CRATE --> PERCPU_CFG_IF PERCPU_CRATE --> PERCPU_MACROS PERCPU_CRATE --> PERCPU_SPIN PERCPU_CRATE --> PERCPU_X86 PERCPU_MACROS --> PROC_MACRO2 PERCPU_MACROS --> QUOTE PERCPU_MACROS --> SYN
The percpu
crate provides architecture-aware per-CPU data structures with x86-specific optimizations for CPU-local storage access.
Sources: Cargo.lock(L167 - L187)
Linker-Based Collections
The linkme
crate enables compile-time collection of distributed static data, used for registering trap handlers and other system components across architecture modules.
Crate | Purpose | Dependencies |
---|---|---|
linkme | Distributed static collections | linkme-impl |
linkme-impl | Implementation macros | proc-macro2,quote,syn |
Sources: Cargo.lock(L84 - L101)
Conditional Compilation Structure
The axcpu library uses feature-based compilation to include only the dependencies and code paths relevant to the target architecture.
Sources: Cargo.lock(L60 - L63)
Build-Time Dependencies
Static Assertions and Verification
The static_assertions
crate provides compile-time verification of type sizes, alignment requirements, and other invariants critical for low-level CPU context management.
Procedural Macro Infrastructure
Macro Crate | Purpose | Used By |
---|---|---|
proc-macro2 | Procedural macro toolkit | linkme-impl,percpu_macros,riscv-macros |
quote | Code generation helper | All macro implementations |
syn | Rust syntax parsing | All macro implementations |
Sources: Cargo.lock(L190 - L294)
Version Constraints and Compatibility
The dependency graph maintains compatibility across architecture-specific crates through careful version selection:
- Bitflags: Uses both v1.3.2 (for x86) and v2.9.1 (for newer crates)
- RISC-V: Uses v0.14.0 for axcpu, v0.12.1 for page table compatibility
- Register Access: Standardized on
tock-registers
v0.9.0 for AArch64
This structure ensures that each architecture module can use the most appropriate version of its dependencies while maintaining overall system compatibility.
Sources: Cargo.lock(L1 - L336)
Toolchain Configuration
Relevant source files
This document covers the Rust toolchain configuration required for building and developing the axcpu multi-architecture CPU abstraction library. It details the specific compiler targets, development tools, and build requirements needed to support all four target architectures.
For information about the dependency structure and external crates, see Dependencies and Package Structure.
Rust Toolchain Requirements
The axcpu project requires the Rust nightly toolchain to access advanced features needed for low-level systems programming. The toolchain configuration is centrally managed through the rust-toolchain.toml
file.
Toolchain Specification
The project uses a minimal nightly toolchain profile with essential development components:
flowchart TD subgraph subGraph3["Rust Toolchain Configuration"] TOOLCHAIN["rust-toolchain.toml"] subgraph subGraph2["Target Architectures"] X86_TARGET["x86_64-unknown-none"] RISC_TARGET["riscv64gc-unknown-none-elf"] ARM_TARGET["aarch64-unknown-none-softfloat"] LOONG_TARGET["loongarch64-unknown-none-softfloat"] end subgraph subGraph1["Development Components"] RUSTFMT["rustfmtCode formatting"] CLIPPY["clippyLinting and analysis"] end subgraph subGraph0["Core Settings"] PROFILE["profile: minimal"] CHANNEL["channel: nightly"] end end TOOLCHAIN --> ARM_TARGET TOOLCHAIN --> CHANNEL TOOLCHAIN --> CLIPPY TOOLCHAIN --> LOONG_TARGET TOOLCHAIN --> PROFILE TOOLCHAIN --> RISC_TARGET TOOLCHAIN --> RUSTFMT TOOLCHAIN --> X86_TARGET
Toolchain Configuration Mapping
Sources: rust-toolchain.toml(L1 - L10)
Target Architecture Configuration
Each supported architecture requires a specific Rust target triple that defines the compilation environment and ABI requirements for bare-metal development.
Architecture Target Mapping
Architecture | Target Triple | Purpose |
---|---|---|
x86_64 | x86_64-unknown-none | 64-bit x86 bare-metal |
RISC-V | riscv64gc-unknown-none-elf | 64-bit RISC-V with compressed instructions |
AArch64 | aarch64-unknown-none-softfloat | 64-bit ARM with software floating-point |
LoongArch64 | loongarch64-unknown-none-softfloat | 64-bit LoongArch with software floating-point |
flowchart TD subgraph subGraph5["Architecture to Target Mapping"] subgraph subGraph4["Build System"] CARGO["cargo build --target"] CROSS_COMPILE["Cross-compilation"] end subgraph subGraph3["LoongArch64 Architecture"] LOONG_ARCH["loongarch64 CPU Support"] LOONG_TARGET["loongarch64-unknown-none-softfloat"] end subgraph subGraph2["AArch64 Architecture"] ARM_ARCH["aarch64 CPU Support"] ARM_TARGET["aarch64-unknown-none-softfloat"] end subgraph subGraph1["RISC-V Architecture"] RISC_ARCH["riscv64 CPU Support"] RISC_TARGET["riscv64gc-unknown-none-elf"] end subgraph subGraph0["x86_64 Architecture"] X86_ARCH["x86_64 CPU Support"] X86_TARGET["x86_64-unknown-none"] end end ARM_ARCH --> ARM_TARGET ARM_TARGET --> CARGO CARGO --> CROSS_COMPILE LOONG_ARCH --> LOONG_TARGET LOONG_TARGET --> CARGO RISC_ARCH --> RISC_TARGET RISC_TARGET --> CARGO X86_ARCH --> X86_TARGET X86_TARGET --> CARGO
Target Architecture Configuration
Sources: rust-toolchain.toml(L5 - L10)
Target Characteristics
x86_64-unknown-none
- Bare-metal x86_64 target without operating system
- Supports full x86_64 instruction set including SSE/AVX
- Used for kernel-level CPU abstraction implementations
riscv64gc-unknown-none-elf
- 64-bit RISC-V with general-purpose and compressed instruction extensions
- ELF format for bare-metal environments
- Supports the standard RISC-V calling convention
aarch64-unknown-none-softfloat
- 64-bit ARM architecture without hardware floating-point
- Suitable for systems programming where FPU may not be available
- Uses software emulation for floating-point operations
loongarch64-unknown-none-softfloat
- 64-bit LoongArch architecture with software floating-point
- Bare-metal target for the Chinese LoongArch instruction set
- Supports the LoongArch ABI for systems programming
Sources: rust-toolchain.toml(L6 - L9)
Development Tools Configuration
The toolchain includes essential development tools for code quality and maintenance across all target architectures.
Included Components
rustfmt
- Automatic code formatting according to Rust style guidelines
- Ensures consistent code style across all architecture implementations
- Configured through
rustfmt.toml
(if present)
clippy
- Advanced linting and static analysis tool
- Catches common programming errors and suggests improvements
- Particularly important for unsafe code used in low-level CPU operations
flowchart TD subgraph subGraph2["Development Workflow"] SOURCE["Source Code"] RUSTFMT["rustfmt"] CLIPPY["clippy"] COMPILER["rustc"] subgraph subGraph1["Target Outputs"] X86_LIB["x86_64 Library"] RISC_LIB["riscv64 Library"] ARM_LIB["aarch64 Library"] LOONG_LIB["loongarch64 Library"] end subgraph subGraph0["Quality Gates"] FORMAT_CHECK["Format Check"] LINT_CHECK["Lint Analysis"] COMPILE_CHECK["Compilation"] end end CLIPPY --> LINT_CHECK COMPILER --> COMPILE_CHECK COMPILE_CHECK --> ARM_LIB COMPILE_CHECK --> LOONG_LIB COMPILE_CHECK --> RISC_LIB COMPILE_CHECK --> X86_LIB FORMAT_CHECK --> CLIPPY LINT_CHECK --> COMPILER RUSTFMT --> FORMAT_CHECK SOURCE --> RUSTFMT
Development Tools Integration
Sources: rust-toolchain.toml(L4)
Cross-Compilation Setup
The toolchain configuration enables seamless cross-compilation to all supported architectures from any development host.
Build Process
The rust-toolchain.toml
file automatically configures the development environment when developers run rustup
in the project directory. This ensures all contributors use the same compiler version and have access to the required target architectures.
Automatic Target Installation
- Running
rustup target add
for each specified target - Ensures consistent build environment across development machines
- Eliminates manual toolchain setup requirements
Development Commands
cargo build
- Build for the host architecturecargo build --target x86_64-unknown-none
- Cross-compile for x86_64cargo build --target riscv64gc-unknown-none-elf
- Cross-compile for RISC-Vcargo build --target aarch64-unknown-none-softfloat
- Cross-compile for AArch64cargo build --target loongarch64-unknown-none-softfloat
- Cross-compile for LoongArch64
Sources: rust-toolchain.toml(L1 - L10)
Nightly Features Usage
The nightly toolchain requirement enables access to unstable Rust features essential for systems programming:
- Inline Assembly: Required for architecture-specific assembly code
- Custom Target Specifications: Enables bare-metal target definitions
- Advanced Const Generics: Used for compile-time architecture dispatch
- Unstable Library Features: Access to experimental standard library APIs
These features are critical for implementing low-level CPU abstractions and context switching routines across different architectures.
Sources: rust-toolchain.toml(L3)
Overview
Relevant source files
This document introduces the axfs_crates repository, a collection of Rust crates designed to provide filesystem abstractions for embedded and OS development environments. The repository focuses on providing lightweight, modular filesystem components that can be used in both standard and no_std environments.
For detailed information about specific components, see File System Architecture, Virtual File System Interface, Device File System, or RAM File System.
Purpose and Scope
The axfs_crates repository implements a virtual filesystem framework with concrete implementations designed for operating systems and embedded environments. Its key goals are:
- Provide a clean, trait-based filesystem abstraction layer
- Offer concrete filesystem implementations for common use cases
- Support no_std environments for embedded systems development
- Maintain a modular design that allows picking only needed components
The framework follows Unix-like filesystem semantics while leveraging Rust's type system and memory safety features.
Sources: README.md(L1 - L10) Cargo.toml(L1 - L21)
Repository Structure
The repository consists of three primary crates:
Crate | Description |
---|---|
axfs_vfs | Defines the virtual filesystem interfaces and traits that other filesystem implementations must implement |
axfs_devfs | Implements a device filesystem for managing device files (similar to /dev in Unix systems) |
axfs_ramfs | Implements a RAM-based filesystem that stores all data in memory |
These crates can be used independently or together depending on the needs of the application.
Sources: README.md(L5 - L9) Cargo.toml(L4 - L8)
High-Level Architecture
Component Relationships
flowchart TD A["Applications"] B["axfs_vfs: Virtual File System Interface"] C["axfs_devfs: Device File System"] D["axfs_ramfs: RAM-based File System"] E["Hardware Devices"] F["System Memory"] A --> B B --> C B --> D C --> E D --> F
The diagram above illustrates the layered architecture of axfs_crates. Applications interact with filesystems through the VFS interface provided by axfs_vfs
. The concrete implementations (axfs_devfs
and axfs_ramfs
) implement this interface and interact with their respective resources.
Sources: README.md(L5 - L9)
Core Interface Implementation
classDiagram class VfsNodeOps { <<trait>> +get_attr() -~ VfsResult~VfsNodeAttr~ +parent() -~ Option~VfsNodeRef~ +lookup(path: &str) -~ VfsResult~VfsNodeRef~ +read_dir(start_idx: usize, dirents: &mut [VfsDirEntry]) -~ VfsResult~usize~ +read_at(offset: u64, buf: &mut [u8]) -~ VfsResult~usize~ +write_at(offset: u64, buf: &[u8]) -~ VfsResult~usize~ +create(path: &str, ty: VfsNodeType) -~ VfsResult +remove(path: &str) -~ VfsResult +truncate(size: u64) -~ VfsResult } class VfsOps { <<trait>> +mount(path: &str, mount_point: VfsNodeRef) -~ VfsResult +root_dir() -~ VfsNodeRef } class DeviceFileSystem { +new() -~ DeviceFileSystem +mkdir(name: &str) -~ Arc +add(name: &str, node: VfsNodeRef) } class RamFileSystem { +new() -~ RamFileSystem } class DeviceNodes { } class RamNodes { } VfsOps ..|> DeviceFileSystem VfsOps ..|> RamFileSystem VfsNodeOps ..|> DeviceNodes VfsNodeOps ..|> RamNodes
This diagram shows the key traits defined in axfs_vfs
and how they are implemented by the filesystem implementations. The VfsNodeOps
trait defines operations that can be performed on filesystem nodes (files, directories, devices), while the VfsOps
trait defines filesystem-level operations.
Sources: README.md(L7 - L9)
Key Design Characteristics
The axfs_crates architecture embodies several key design characteristics:
- Trait-Based Design: Uses Rust traits (
VfsNodeOps
,VfsOps
) to define interfaces, allowing polymorphic behavior and clean separation of concerns. - Reference-Counted Memory Management: Employs
Arc
(Atomic Reference Counting) andWeak
references to manage object lifetimes and prevent circular references. - Hierarchical Structure: Filesystems are organized in a tree-like structure similar to traditional Unix filesystems, with directories containing other directories and files.
- Concurrent Access Support: Utilizes synchronization primitives to ensure thread safety in multithreaded environments.
- No-std Compatibility: Designed to work in environments without the standard library, making it suitable for embedded systems and operating system development.
Example Filesystem Structure
The axfs_crates framework allows for creating filesystem hierarchies like the following:
flowchart TD A["Root Directory"] B["/dev/null"] C["/dev/zero"] D["/mnt Directory"] E["/mnt/data Directory"] F["/mnt/data/file.txt"] A --> B A --> C A --> D D --> E E --> F
This structure shows a typical filesystem arrangement with special device files (/dev/null
, /dev/zero
) and a mounted filesystem with directories and regular files.
Common Usage Pattern
The typical usage flow for using axfs_crates components looks like:
- Create a filesystem instance (DeviceFileSystem, RamFileSystem, etc.)
- Obtain the root directory node
- Create the desired hierarchy (directories, files)
- Perform operations on nodes (read, write, etc.)
- Mount other filesystems at specific mount points if needed
For specific implementation details and examples, see the dedicated pages for each component: Virtual File System Interface, Device File System, or RAM File System.
Repository Structure
Relevant source files
This page provides an overview of the organization of the axfs_crates repository, including its workspace setup, available crates, and the relationships between these components. For information about the overall filesystem architecture, see File System Architecture.
Workspace Organization
The axfs_crates repository is structured as a Rust workspace containing three primary crates, each implementing a distinct aspect of the filesystem infrastructure:
flowchart TD A["axfs_crates Workspace"] B["axfs_vfs: Virtual File System Interface"] C["Implements VFS interface"] D["Implements VFS interface"] A --> B A --> C A --> D B --> C B --> D
Title: Repository Workspace Structure
Sources: Cargo.toml(L1 - L8) README.md(L5 - L9)
The workspace configuration is defined in the root Cargo.toml file, which specifies the member crates and shared metadata, including version information, author details, and licensing.
Crate Overview
The repository consists of the following crates:
Crate | Description | Role in the System |
---|---|---|
axfs_vfs | Virtual Filesystem Interface | Defines traits and interfaces that filesystem implementations must implement |
axfs_devfs | Device Filesystem | Provides access to system devices through the VFS interface |
axfs_ramfs | RAM Filesystem | Implements an in-memory filesystem using the VFS interface |
Sources: README.md(L5 - L9) Cargo.toml(L4 - L8)
Dependency Relationships
The crates follow a clear hierarchical relationship, with axfs_vfs
serving as the foundation that other filesystems build upon:
flowchart TD subgraph subGraph1["External Usage"] app["Application Code"] end subgraph subGraph0["Crate Dependencies"] vfs["axfs_vfs: VfsNodeOps, VfsOps traits"] devfs["axfs_devfs: DeviceFileSystem"] ramfs["axfs_ramfs: RAMFileSystem"] end app --> devfs app --> ramfs app --> vfs devfs --> vfs ramfs --> vfs
Title: Dependency Structure Between Crates
Sources: Cargo.toml(L19 - L20)
Key aspects of this dependency structure:
axfs_vfs
has no dependencies on other filesystem crates in the workspace- Both
axfs_devfs
andaxfs_ramfs
depend onaxfs_vfs
to implement its interfaces - Application code can use any or all of these crates, typically interacting through the
axfs_vfs
abstractions
Internal Structure of the Crates
axfs_vfs
The Virtual File System (VFS) crate defines the core interfaces that filesystem implementations must adhere to:
classDiagram class VfsNodeOps { <<trait>> +get_attr() VfsResult~VfsNodeAttr~ +parent() Option~VfsNodeRef~ +lookup(path: str) VfsResult~VfsNodeRef~ +read_dir(start_idx: usize, dirents: [VfsDirEntry]) VfsResult~usize~ +read_at(offset: u64, buf: &mut [u8]) VfsResult~usize~ +write_at(offset: u64, buf: &[u8]) VfsResult~usize~ +create(path: str, ty: VfsNodeType) VfsResult +remove(path: str) VfsResult +truncate(size: u64) VfsResult } class VfsOps { <<trait>> +mount(path: str, mount_point: VfsNodeRef) VfsResult +root_dir() VfsNodeRef } class VfsNodeType { <<enum>> File Dir SymLink CharDevice BlockDevice Socket NamedPipe }
Title: Core Traits in axfs_vfs
Sources: Based on the system architecture diagrams
axfs_devfs
The Device Filesystem provides a hierarchical structure for accessing system devices:
classDiagram class DeviceFileSystem { -parent: Once~VfsNodeRef~ -root: Arc~DirNode~ +new() DeviceFileSystem +mkdir(name: str) Arc~DirNode~ +add(name: str, node: VfsNodeRef) void } class DirNode { -parent: RwLock~Weak~dyn VfsNodeOps~~ -children: RwLock~BTreeMap~str, VfsNodeRef~~ +new(parent: Option~&VfsNodeRef~) Arc +mkdir(name: str) Arc +add(name: str, node: VfsNodeRef) } class NullDev { implements VfsNodeOps } class ZeroDev { implements VfsNodeOps } DeviceFileSystem --> DirNode : "contains root" DirNode --> NullDev : "can contain" DirNode --> ZeroDev : "can contain"
Title: DeviceFileSystem Implementation Structure
Sources: Based on the system architecture diagrams
axfs_ramfs
The RAM Filesystem provides an in-memory implementation of the filesystem interface:
classDiagram class RAMFileSystem { implements VfsOps -root: Arc~DirNode~ +new() RAMFileSystem +root_dir() VfsNodeRef } class DirNode { implements VfsNodeOps } class FileNode { implements VfsNodeOps -content: RwLock~Vec~u8~~ } RAMFileSystem --> DirNode : "contains root" DirNode --> DirNode DirNode --> DirNode : "can contain subdirectories" DirNode --> DirNode DirNode --> FileNode : "can contain files"
Title: RAMFileSystem Implementation Structure
Sources: Based on the system architecture diagrams
Repository Configuration
The repository follows standard Rust project conventions and includes the following key configuration files:
File | Purpose |
---|---|
Cargo.toml | Defines the workspace and its members, shared dependencies, and package metadata |
.gitignore | Specifies files to be excluded from version control (build artifacts, IDE files, etc.) |
README.md | Provides a brief overview of the repository and its components |
Sources: Cargo.toml(L1 - L21) .gitignore(L1 - L4) README.md(L1 - L10)
Publishing Information
The crates in this repository are published on crates.io and have the following shared metadata:
Metadata | Value |
---|---|
Version | 0.1.1 |
License | GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0 |
Documentation | https://arceos-org.github.io/axfs_crates |
Repository | https://github.com/arceos-org/axfs_crates |
Categories | os, no-std, filesystem |
Sources: Cargo.toml(L10 - L17)
This repository structure enables the development of modular filesystem components that can be used independently or together to provide filesystem functionality for the ArceOS operating system or other embedded systems projects.
Development and Contribution
Relevant source files
This document outlines the development workflow, testing processes, and continuous integration setup for the AxFS crates project. It provides guidance for contributors on how to set up their development environment, run tests, and ensure their contributions meet the project's quality standards. For information about the repository structure, see Repository Structure.
Development Environment Setup
To begin contributing to the AxFS crates project, you'll need to set up your development environment with the following prerequisites:
- Rust toolchain (nightly version required)
- Rust source components, Clippy, and rustfmt
- Git for version control
Installing Prerequisites
- Install Rust (nightly) - The project requires the nightly toolchain:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default nightly
rustup component add rust-src clippy rustfmt
- Add target platforms - The project supports multiple targets:
rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
- Clone the repository:
git clone https://github.com/arceos-org/axfs_crates.git
cd axfs_crates
Sources: .github/workflows/ci.yml(L14 - L19)
Development Workflow
The typical development workflow for contributing to AxFS crates follows these steps:
flowchart TD A["Fork Repository"] B["Clone Repository"] C["Create Feature Branch"] D["Make Changes"] E["Run Local Tests"] F["Format Code"] G["Run Clippy"] H["Commit Changes"] I["Push to Fork"] J["Create Pull Request"] K["CI Checks"] L["Code Review"] M["Address Feedback"] N["Merge"] A --> B B --> C C --> D D --> E E --> F F --> G G --> H H --> I I --> J J --> K K --> L L --> M L --> N M --> D
Diagram: AxFS Crates Development Workflow
Sources: .github/workflows/ci.yml(L3 - L30)
Coding Standards
When contributing to the project, ensure you follow these coding standards:
- All code must be formatted with
rustfmt
- Code must pass Clippy linting with no warnings (with the exception of
clippy::new_without_default
) - All code must compile for all supported targets
- New functionality should include appropriate tests
You can check your code against these standards with:
cargo fmt --all
cargo clippy --all-features -- -A clippy::new_without_default
Sources: .github/workflows/ci.yml(L22 - L25)
Testing
The project uses Rust's built-in testing framework. Testing processes include:
- Unit Tests: Tests for individual components
- Integration Tests: Tests that verify the interaction between different parts
Running Tests
To run all tests locally:
cargo test --all-features -- --nocapture
Note that comprehensive tests only run on the x86_64-unknown-linux-gnu
target in the CI pipeline, but you should verify functionality on other targets as appropriate for your changes.
flowchart TD A["cargo test"] B["Unit Tests"] C["Integration Tests"] D["Pass?"] E["Commit Changes"] F["Fix Issues"] A --> B A --> C B --> D C --> D D --> E D --> F F --> A
Diagram: AxFS Testing Process
Sources: .github/workflows/ci.yml(L28 - L30)
Continuous Integration
The project uses GitHub Actions for continuous integration. The CI pipeline is triggered on both push and pull request events and consists of two main jobs: code validation and documentation building.
CI Pipeline Architecture
flowchart TD subgraph subGraph1["Documentation Job"] F["Setup Rust"] G["Build Docs"] H["Deploy to GitHub Pages"] A["Setup Rust Toolchain"] B["Check Formatting"] C["Run Clippy"] end subgraph subGraph0["CI Job"] F["Setup Rust"] G["Build Docs"] H["Deploy to GitHub Pages"] A["Setup Rust Toolchain"] B["Check Formatting"] C["Run Clippy"] D["Build"] E["Run Tests"] end Push["Push/PR Event"] CI["CI Job"] Doc["Documentation Job"] A --> B B --> C C --> D D --> E F --> G G --> H Push --> CI Push --> Doc
Diagram: AxFS Continuous Integration Architecture
Sources: .github/workflows/ci.yml(L5 - L53)
CI Environments and Targets
The CI pipeline tests across multiple Rust targets to ensure cross-platform compatibility:
Target | Description |
---|---|
x86_64-unknown-linux-gnu | Standard Linux target |
x86_64-unknown-none | Bare-metal x86_64 |
riscv64gc-unknown-none-elf | RISC-V 64-bit |
aarch64-unknown-none-softfloat | ARM 64-bit without floating point |
Sources: .github/workflows/ci.yml(L12)
Documentation
Documentation Standards
The project enforces strict documentation standards:
- All public items must be documented
- Documentation must not contain broken intra-doc links
- The documentation is generated with a unified index page
These standards are enforced by the RUSTDOCFLAGS
environment variable set in the CI workflow:
-Zunstable-options --enable-index-page -D rustdoc::broken_intra_doc_links -D missing-docs
Sources: .github/workflows/ci.yml(L40)
Generating Documentation
To generate documentation locally:
RUSTDOCFLAGS="-Zunstable-options --enable-index-page -D rustdoc::broken_intra_doc_links -D missing-docs" cargo doc --no-deps --all-features
The documentation is automatically built and deployed to GitHub Pages when changes are pushed to the default branch.
Sources: .github/workflows/ci.yml(L42 - L53)
Contribution Guidelines
Pull Request Process
- Fork the repository and create your branch from the default branch
- Ensure your code follows the project's coding standards
- Make sure all tests pass locally
- Update documentation as needed
- Submit a pull request
- Address any feedback from reviewers
Code Review
All contributions undergo code review before being merged. The review process checks:
- Code quality and adherence to project standards
- Test coverage
- Documentation completeness
- Design consistency
Once a pull request passes CI checks and receives approval from maintainers, it can be merged into the main codebase.
Sources: .github/workflows/ci.yml(L3 - L30)
Project Structure Reference
To understand how your contributions fit into the overall project structure, refer to the repository structure and codebase architecture information in the Overview and Repository Structure pages.
File System Architecture
Relevant source files
This document provides an overview of the architectural design of the file system implementation in axfs_crates. It covers the foundational components, their relationships, and the core design patterns that enable the flexible and extensible file system abstraction.
For detailed information about the Virtual File System Interface implementation, see Virtual File System Interface (axfs_vfs). For specific implementations, see Device File System (axfs_devfs) and RAM File System (axfs_ramfs).
Core Components
The axfs_crates file system architecture consists of three main crates that work together to provide a complete file system abstraction:
flowchart TD Apps["Applications"] VfsInterface["axfs_vfs (VfsOps/VfsNodeOps traits)"] DevFS["axfs_devfs::DeviceFileSystem"] RamFS["axfs_ramfs::RamFileSystem"] DevNodes["Device Nodes (NullDev, ZeroDev)"] MemNodes["Memory-backed Nodes"] Apps --> VfsInterface DevFS --> DevNodes RamFS --> MemNodes VfsInterface --> DevFS VfsInterface --> RamFS
Core Components:
- axfs_vfs: Defines the virtual file system interface through traits that other file systems implement
- axfs_devfs: Implements a device file system following the VFS interface
- axfs_ramfs: Provides a RAM-based file system implementation
Sources: README.md(L5 - L9) axfs_devfs/src/lib.rs(L1 - L4)
Layered Architecture Design
The file system architecture follows a layered design pattern, separating abstract interfaces from concrete implementations:
classDiagram class VfsNodeOps { <<trait>> +get_attr() VfsResult~VfsNodeAttr~ +parent() Option~VfsNodeRef~ +lookup(path) VfsResult~VfsNodeRef~ +read_dir(start_idx, dirents) VfsResult~usize~ +read_at(offset, buf) VfsResult~usize~ +write_at(offset, buf) VfsResult~usize~ +create(path, type) VfsResult +remove(path) VfsResult +truncate(size) VfsResult } class VfsOps { <<trait>> +mount(path, mount_point) VfsResult +root_dir() VfsNodeRef } class DeviceFileSystem { -parent: Once~VfsNodeRef~ -root: Arc~DirNode~ +new() DeviceFileSystem +mkdir(name) Arc~DirNode~ +add(name, node) void } class DirNode { -parent: RwLock~Weak~VfsNodeOps~~ -children: RwLock~BTreeMap~String,VfsNodeRef~~ +new() Arc~DirNode~ +mkdir(name) Arc~DirNode~ +add(name, node) void } VfsOps ..|> DeviceFileSystem VfsNodeOps ..|> DirNode
The diagram shows the core traits and their implementations:
VfsNodeOps
: Defines operations on individual file system nodesVfsOps
: Defines operations on the file system as a wholeDeviceFileSystem
: ImplementsVfsOps
for device filesDirNode
: ImplementsVfsNodeOps
for directory operations
Sources: axfs_devfs/src/lib.rs(L25 - L71)
File System Implementation Structure
The actual file system implementation forms a hierarchical tree structure:
flowchart TD DevFS["DeviceFileSystem"] Root["root: Arc"] Child1["Child DirNode (e.g., 'dev')"] Dev1["NullDev (/null)"] Dev2["ZeroDev (/zero)"] GrandChild["Subdirectory DirNode"] Dev3["Device Node"] Child1 --> Dev3 Child1 --> GrandChild DevFS --> Root Root --> Child1 Root --> Dev1 Root --> Dev2
The DeviceFileSystem
structure contains:
- A root directory node (
root: Arc<DirNode>
) - An optional parent node for mounting (
parent: Once<VfsNodeRef>
)
The tree structure supports:
- Creating subdirectories using
mkdir(name)
method - Adding device nodes with
add(name, node)
method - Standard file system path traversal
Sources: axfs_devfs/src/lib.rs(L25 - L49)
Memory Management and Reference Counting
The architecture uses Rust's reference counting mechanisms for memory safety and object lifetime management:
flowchart TD DevFS["DeviceFileSystem"] Root["Root DirNode"] Dir["Subdirectory DirNode"] Dev["Device Node"] DevFS --> Root Dir --> Root Root --> Dev Root --> Dir
Key memory management features:
Arc<T>
(Atomic Reference Counting) for shared ownership of nodesWeak<T>
references for parent pointers to avoid reference cyclesRwLock<T>
for thread-safe concurrent access to shared dataOnce<T>
for one-time initialization of parent references during mounting
This approach guarantees memory safety while allowing flexible node relationships:
- Strong references (Arc) from parent to children
- Weak references from children to parents
- Thread-safe access through RwLock
Sources: axfs_devfs/src/lib.rs(L20 - L21)
Path Resolution Process
When accessing a file or directory, the file system employs a recursive path resolution process:
sequenceDiagram participant Application as "Application" participant DeviceFileSystem as "DeviceFileSystem" participant RootDirNode as "Root DirNode" participant SubdirectoryDirNode as "Subdirectory DirNode" participant FileNode as "FileNode" Application ->> DeviceFileSystem: lookup("/dir/file") DeviceFileSystem ->> RootDirNode: root_dir().lookup("/dir/file") RootDirNode ->> RootDirNode: Parse "dir" component RootDirNode ->> SubdirectoryDirNode: children["dir"].lookup("file") SubdirectoryDirNode ->> SubdirectoryDirNode: Parse "file" component SubdirectoryDirNode ->> FileNode: children["file"] FileNode ->> SubdirectoryDirNode: Return node reference SubdirectoryDirNode ->> RootDirNode: Return node reference RootDirNode ->> DeviceFileSystem: Return node reference DeviceFileSystem ->> Application: Return node reference
This multi-step resolution process navigates the file system hierarchy to locate requested resources. Path resolution is managed through:
- The
lookup
method inVfsNodeOps
trait - Path component parsing (directory names)
- Recursively traversing the file system tree until reaching the target node
Sources: axfs_devfs/src/lib.rs(L52 - L65)
Mount Operation Flow
The following sequence diagram illustrates the mounting process:
sequenceDiagram participant Application as "Application" participant VFSLayer as "VFS Layer" participant DeviceFileSystem as "DeviceFileSystem" participant MountPointNode as "MountPoint Node" participant RootDirNode as "Root DirNode" Application ->> VFSLayer: Mount file system at "/mnt" VFSLayer ->> MountPointNode: Get mount point node VFSLayer ->> DeviceFileSystem: mount("/mnt", mountPointNode) DeviceFileSystem ->> RootDirNode: set_parent(mountPointNode.parent()) RootDirNode ->> DeviceFileSystem: Success DeviceFileSystem ->> VFSLayer: VfsResult::Ok(()) VFSLayer ->> Application: Mount complete
The mount operation connects a file system's root directory to a specified mount point in another file system, enabling seamless path traversal across multiple file systems.
The key steps in the mounting process are:
- Setting the parent of the file system's root node
- Establishing the connection between mount point and file system
Sources: axfs_devfs/src/lib.rs(L53 - L60)
Implementation Inheritance Hierarchy
The complete inheritance hierarchy shows how concrete implementations relate to the core traits:
classDiagram class VfsNodeOps { <<trait>> +Core node operations } class VfsOps { <<trait>> +File system operations } class DeviceFileSystem { -parent: Once -root: Arc +new() +mkdir(name) +add(name, node) } class DirNode { -parent: RwLock~ -children: RwLock~ +new() +mkdir(name) +add(name, node) } class NullDev { +read_at() +write_at() } class ZeroDev { +read_at() +write_at() } VfsOps ..|> DeviceFileSystem VfsNodeOps ..|> DirNode VfsNodeOps ..|> NullDev VfsNodeOps ..|> ZeroDev
This diagram shows the complete implementation hierarchy, illustrating how different components inherit from and implement the core VFS traits.
Sources: axfs_devfs/src/lib.rs(L16 - L18) axfs_devfs/src/lib.rs(L25 - L71)
Summary
The axfs_crates file system architecture employs:
- Trait-Based Design: Defines behavior through traits like
VfsNodeOps
andVfsOps
- Layered Architecture: Separates interface (axfs_vfs) from implementations (axfs_devfs, axfs_ramfs)
- Memory Safety: Uses reference counting and thread-safe primitives
- Hierarchical Structure: Organizes nodes in a tree-like structure
- Polymorphism: Allows different implementations to be used interchangeably
This design provides a flexible, extensible foundation for building various file system implementations while maintaining a consistent interface for applications.
Sources: README.md(L5 - L9) axfs_devfs/src/lib.rs(L1 - L71)
Virtual File System Interface (axfs_vfs)
Relevant source files
Purpose and Scope
The axfs_vfs
crate provides a virtual file system (VFS) abstraction layer for the axfs_crates ecosystem. It defines the fundamental interfaces and data structures that concrete file system implementations must adhere to. This abstraction allows applications to interact with different file system types through a unified interface without needing to understand their specific implementations.
This document covers the core traits, data structures, and interfaces defined in the axfs_vfs
crate. For specific implementations, see Device File System (axfs_devfs) or RAM File System (axfs_ramfs).
Architecture Overview
The Virtual File System Interface serves as the foundational abstraction layer in the axfs_crates architecture. It defines contracts that concrete file systems implement to provide specific functionality.
flowchart TD subgraph subGraph0["axfs_vfs Core Components"] I1["VfsNodeOps Trait"] I2["VfsOps Trait"] I3["Path Resolution"] I4["Data Structures"] I5["Error Types"] end A["Applications"] B["axfs_vfs Interface"] C["axfs_devfs Implementation"] D["axfs_ramfs Implementation"] A --> B B --> C B --> D
Sources: README.md
Core Traits
The VFS interface is built around two primary traits that define the contract for file system implementations:
VfsNodeOps Trait
The VfsNodeOps
trait represents file system nodes (files, directories, devices) and defines operations that can be performed on these nodes.
classDiagram class VfsNodeOps { <<trait>> +get_attr() -~ VfsResult~VfsNodeAttr~ +parent() -~ Option~VfsNodeRef~ +lookup(path: &str) -~ VfsResult~VfsNodeRef~ +read_dir(start_idx: usize, dirents: &mut [VfsDirEntry]) -~ VfsResult~usize~ +read_at(offset: u64, buf: &mut [u8]) -~ VfsResult~usize~ +write_at(offset: u64, buf: &[u8]) -~ VfsResult~usize~ +create(path: &str, ty: VfsNodeType) -~ VfsResult +remove(path: &str) -~ VfsResult +truncate(size: u64) -~ VfsResult }
get_attr()
: Retrieves attributes of the node (size, permissions, type, etc.)parent()
: Returns a reference to the parent node if it existslookup()
: Resolves a path relative to this noderead_dir()
: Lists directory entries, returning the number of entries readread_at()/write_at()
: Read/write operations at specified offsetscreate()
: Creates a new file/directory under this noderemove()
: Removes a file/directorytruncate()
: Changes file size
VfsOps Trait
The VfsOps
trait represents operations at the file system level rather than individual nodes.
classDiagram class VfsOps { <<trait>> +mount(path: &str, mount_point: VfsNodeRef) -~ VfsResult +root_dir() -~ VfsNodeRef }
mount()
: Mounts a file system at the specified mount pointroot_dir()
: Returns a reference to the root directory of the file system
Sources: High-Level System Architecture diagrams
Data Structures
The axfs_vfs
crate defines several key data structures used throughout the file system interface:
Node Types and References
classDiagram class VfsNodeRef { Arc~dyn VfsNodeOps~ } class VfsNodeType { <<enum>> File Dir SymLink CharDevice BlockDevice Socket NamedPipe } class VfsNodeAttr { mode: VfsMode type: VfsNodeType size: u64 blocks: u64 atime: Timespec mtime: Timespec ctime: Timespec }
VfsNodeRef
: A reference-counted pointer to a trait object implementingVfsNodeOps
VfsNodeType
: Enumerates the possible types of file system nodesVfsNodeAttr
: Contains metadata about a file system node
Directory Entries
classDiagram class VfsDirEntry { name: String type: VfsNodeType }
The VfsDirEntry
structure represents entries in a directory, containing the name and type of each entry.
Sources: High-Level System Architecture diagrams
Error Handling
The VFS interface uses the VfsResult
type alias for error handling:
classDiagram class VfsResult~T~ { Result~T, VfsError~ } class VfsError { <<enum>> NotFound AlreadyExists InvalidInput NotADirectory IsADirectory NotEmpty ReadOnly PermissionDenied IoError // Other error variants }
This approach provides a consistent error handling mechanism across all file system implementations.
Sources: High-Level System Architecture diagrams
Path Resolution
Path resolution is a key functionality of the VFS interface, allowing for traversal of the file system hierarchy:
sequenceDiagram participant Client as Client participant RootNode as "Root Node" participant DirectoryNode as "Directory Node" participant TargetNode as "Target Node" Client ->> RootNode: lookup("/path/to/file") RootNode ->> RootNode: Parse "path" component RootNode ->> DirectoryNode: lookup("to/file") DirectoryNode ->> DirectoryNode: Parse "to" component DirectoryNode ->> TargetNode: lookup("file") TargetNode ->> DirectoryNode: Return node reference DirectoryNode ->> RootNode: Return node reference RootNode ->> Client: VfsNodeRef to target
The path resolution algorithm:
- Splits the path into components
- Traverses the file system hierarchy recursively
- At each step, calls the
lookup()
method on the current node - Returns a reference to the target node if found, or an error
Sources: High-Level System Architecture diagrams
Memory Management
The VFS interface uses Rust's memory management features to ensure safety and prevent memory leaks:
flowchart TD A["VfsNodeRef (Arc)"] B["VfsNodeOps implementation"] C["Weak reference"] D["Parent Node"] A --> B B --> D C --> B
- Uses
Arc
(Atomic Reference Counting) for shared ownership of nodes - Employs weak references to prevent reference cycles (e.g., between parent and child nodes)
- Ensures thread safety for concurrent access through synchronization primitives
Sources: High-Level System Architecture diagrams
Integration with File System Implementations
The axfs_vfs
interfaces are implemented by concrete file systems to provide specific functionality:
classDiagram class VfsNodeOps { <<trait>> // Other methods +lookup() +read_at() +write_at() +read_dir() } class VfsOps { <<trait>> +mount() +root_dir() } class DeviceFileSystem { -parent: Once -root: Arc } class RamFileSystem { -root: Arc } class DirNode { -children: RwLock } class RamDirNode { -entries: RwLock } VfsOps ..|> DeviceFileSystem VfsOps ..|> RamFileSystem VfsNodeOps ..|> DirNode RamDirNode ..|> DirNode
axfs_devfs
implements the VFS interface to provide device files (/dev/null, /dev/zero, etc.)axfs_ramfs
implements the VFS interface to provide a RAM-based file system- Both implementations create their own node types that implement the
VfsNodeOps
trait
Sources: High-Level System Architecture diagrams
Summary
The axfs_vfs
crate provides a flexible, trait-based abstraction layer for file systems in the axfs_crates ecosystem. By defining common interfaces and data structures, it enables the development of different file system implementations that can be used interchangeably. The architecture follows Rust's ownership model and makes extensive use of trait objects and reference counting for safe and efficient memory management.
Device File System (axfs_devfs)
Relevant source files
Purpose and Scope
The Device File System (axfs_devfs) is a specialized file system implementation for managing access to device files within the ArceOS operating system. It provides a hierarchical structure for organizing and accessing device files, similar to the /dev
directory in Unix-like systems. This document covers the architecture, components, and usage of axfs_devfs.
For information about the underlying virtual file system interface that axfs_devfs implements, see Virtual File System Interface (axfs_vfs).
Sources: axfs_devfs/Cargo.toml(L1 - L18) axfs_devfs/README.md(L1 - L10) axfs_devfs/src/lib.rs(L1 - L4)
System Overview
The Device File System is built on the Virtual File System (VFS) abstraction layer provided by axfs_vfs. It implements the core VFS interfaces and provides a directory structure for organizing device nodes. The primary purpose is to expose hardware and virtual devices through a file-like interface.
flowchart TD subgraph subGraph1["axfs_devfs Components"] F["DeviceFileSystem"] G["DirNode"] H["Device Nodes"] I["NullDev"] J["ZeroDev"] end subgraph subGraph0["AxFS Ecosystem"] A["Applications"] B["axfs_vfs: VFS Interface"] C["axfs_devfs: Device File System"] D["Hardware/Virtual Devices"] E["Other File Systems"] end A --> B B --> C B --> E C --> D C --> F F --> G F --> H H --> I H --> J
Sources: axfs_devfs/src/lib.rs(L1 - L71)
Core Components
DeviceFileSystem
The DeviceFileSystem
is the main entry point for the device file system. It implements the VfsOps
trait from axfs_vfs, which provides the core file system operations.
classDiagram class DeviceFileSystem { -parent: Once~VfsNodeRef~ -root: Arc~DirNode~ +new() DeviceFileSystem +mkdir(name: str) Arc~DirNode~ +add(name: str, node: VfsNodeRef) void +mount(path: str, mount_point: VfsNodeRef) VfsResult +root_dir() VfsNodeRef } class VfsOps { <<trait>> +mount(path: str, mount_point: VfsNodeRef) VfsResult +root_dir() VfsNodeRef } VfsOps ..|> DeviceFileSystem
Key features of the DeviceFileSystem
:
- Maintains a root directory node
- Provides methods for creating directories and adding device nodes
- Implements the
VfsOps
trait for mounting and root directory access
Sources: axfs_devfs/src/lib.rs(L24 - L71)
Directory Structure
The device file system organizes its contents in a hierarchical structure, with directories containing other directories or device nodes.
flowchart TD subgraph subGraph0["Example DevFS Structure"] A["Root Directory"] B["/dev/null"] C["/dev/zero"] D["/custom Directory"] E["/custom/device1"] F["/custom/subdir"] G["/custom/subdir/device2"] end A --> B A --> C A --> D D --> E D --> F F --> G
The root directory is created when initializing the DeviceFileSystem
, and subdirectories and device nodes can be added using the provided methods:
// Create a new device file system
let devfs = DeviceFileSystem::new();
// Create a subdirectory
let custom_dir = devfs.mkdir("custom");
// Add a device node to the root
devfs.add("null", Arc::new(NullDev::new()));
// Add a device node to a subdirectory
custom_dir.add("device1", Arc::new(ZeroDev::new()));
Sources: axfs_devfs/src/lib.rs(L31 - L49)
Device Types
Null Device
The NullDev
implementation represents a /dev/null
-like device that:
- Discards all data written to it
- Returns EOF (end-of-file) when read from
- Takes no storage space
flowchart TD A["Application"] B["NullDev"] C["(Void)"] D["Application"] A --> B B --> C B --> D D --> B
Sources: axfs_devfs/src/lib.rs(L10 - L17)
Zero Device
The ZeroDev
implementation represents a /dev/zero
-like device that:
- Discards all data written to it
- Returns an infinite stream of zero bytes when read from
- Takes no storage space
flowchart TD A["Application"] B["ZeroDev"] C["(Void)"] D["Application"] A --> B B --> C B --> D D --> B
Sources: axfs_devfs/src/lib.rs(L11 - L17)
Path Resolution
When accessing files in the device file system, paths are resolved by traversing the directory structure:
sequenceDiagram participant Application as "Application" participant DeviceFileSystem as "DeviceFileSystem" participant RootDirNode as "Root DirNode" participant Subdirectory as "Subdirectory" participant DeviceNode as "Device Node" Application ->> DeviceFileSystem: lookup("/path/to/device") DeviceFileSystem ->> RootDirNode: root_dir() DeviceFileSystem ->> RootDirNode: lookup("path/to/device") RootDirNode ->> RootDirNode: Find "path" component RootDirNode ->> Subdirectory: lookup("to/device") Subdirectory ->> Subdirectory: Find "to" component Subdirectory ->> DeviceNode: lookup("device") Subdirectory ->> RootDirNode: Return device node RootDirNode ->> DeviceFileSystem: Return device node DeviceFileSystem ->> Application: Return device node
The path resolution process:
- Starts at the root directory
- Splits the path into components
- Processes each component sequentially
- Returns the target node or an error if not found
Sources: axfs_devfs/src/lib.rs(L52 - L65)
Integration with VFS
The DeviceFileSystem
implements the VfsOps
trait from axfs_vfs, which allows it to be mounted as part of a larger file system hierarchy:
flowchart TD subgraph subGraph0["VFS Integration"] A["Root File System"] B["Mount Points"] C["/dev Mount Point"] D["DeviceFileSystem"] E["Device Nodes"] end A --> B B --> C C --> D D --> E
When mounted, the device file system:
- Sets the parent of its root directory to the mount point
- Exposes its root directory and all children through the mount point
- Handles file operations through the VFS interfaces
Sources: axfs_devfs/src/lib.rs(L52 - L65)
Usage Example
Below is a typical usage example for creating and using a device file system:
// Create a new device file system
let devfs = DeviceFileSystem::new();
// Add standard device nodes
devfs.add("null", Arc::new(NullDev::new()));
devfs.add("zero", Arc::new(ZeroDev::new()));
// Create custom subdirectories and devices
let custom_dir = devfs.mkdir("custom");
custom_dir.add("special_device", Arc::new(MyCustomDevice::new()));
// Mount the device file system (assuming a root file system exists)
root_fs.mount("/dev", devfs.root_dir()).expect("Failed to mount devfs");
// Now accessible via paths like "/dev/null", "/dev/custom/special_device"
Sources: axfs_devfs/src/lib.rs(L31 - L49) axfs_devfs/src/lib.rs(L52 - L65)
Summary
The Device File System (axfs_devfs) provides a hierarchical structure for organizing and accessing device files within the ArceOS operating system. It implements the VFS interfaces defined by axfs_vfs and includes implementations for common device types such as null and zero devices. Its modular design allows for easy extension with custom device implementations.
Sources: axfs_devfs/src/lib.rs(L1 - L71)
Directory Structure
Relevant source files
This document details the directory structure implementation in the axfs_devfs filesystem. It focuses on how directories are represented, managed, and traversed within the device filesystem. For information about the overall device filesystem architecture, see Device File System (axfs_devfs).
Directory Node Implementation
The directory structure in axfs_devfs is built around the DirNode
struct, which represents directory nodes in the filesystem hierarchy. Each directory can contain other directories or device nodes, forming a tree-like structure similar to traditional filesystems.
classDiagram class DirNode { -parent: RwLock~Weak~dyn VfsNodeOps~~ -children: RwLock~BTreeMap~&'static str, VfsNodeRef~~ +new(parent: Option~&VfsNodeRef~) Arc +set_parent(parent: Option~&VfsNodeRef~) void +mkdir(name: &'static str) Arc +add(name: &'static str, node: VfsNodeRef) void } class VfsNodeOps { <<trait>> +get_attr() VfsResult~VfsNodeAttr~ +parent() Option~VfsNodeRef~ +lookup(path: &str) VfsResult~VfsNodeRef~ +read_dir(start_idx: usize, dirents: &mut [VfsDirEntry]) VfsResult~usize~ +create(path: &str, ty: VfsNodeType) VfsResult +remove(path: &str) VfsResult } VfsNodeOps ..|> DirNode
Sources: axfs_devfs/src/dir.rs(L7 - L40) axfs_devfs/src/dir.rs(L42 - L131)
Key Components
The DirNode
struct consists of two primary fields:
- parent: A read-write lock containing a weak reference to the parent directory
- children: A read-write lock containing a BTree map of child nodes indexed by name
This structure allows for efficient directory traversal and lookup operations while preventing strong reference cycles between parent and child nodes.
Sources: axfs_devfs/src/dir.rs(L10 - L13)
Directory Operations
The DirNode
implementation provides several key operations for directory management:
Directory Creation
sequenceDiagram participant DeviceFileSystem as "DeviceFileSystem" participant ParentDirNode as "Parent DirNode" participant NewDirNode as "New DirNode" DeviceFileSystem ->> ParentDirNode: mkdir("subdir") ParentDirNode ->> NewDirNode: new(Some(&parent)) NewDirNode -->> ParentDirNode: Arc<DirNode> ParentDirNode ->> ParentDirNode: children.write().insert("subdir", node) ParentDirNode -->> DeviceFileSystem: Arc<DirNode>
Sources: axfs_devfs/src/dir.rs(L29 - L34) axfs_devfs/src/lib.rs(L39 - L42)
Node Addition
Directories can contain various types of nodes including other directories and device nodes. The add
method allows adding any node that implements the VfsNodeOps
trait to a directory.
Sources: axfs_devfs/src/dir.rs(L36 - L39) axfs_devfs/src/lib.rs(L44 - L49)
Path Lookup
The lookup
method is a crucial operation that navigates the directory structure to find nodes by path. It implements path traversal using a recursive approach:
- Split the path into the current component and the rest
- Look up the current component in the children map
- If there's a remaining path and the found node exists, recursively call
lookup
on that node - Otherwise, return the found node
Sources: axfs_devfs/src/dir.rs(L51 - L69) axfs_devfs/src/dir.rs(L133 - L138)
Directory Listing
The read_dir
method returns directory entries for the current directory, including the special "." and ".." entries.
Sources: axfs_devfs/src/dir.rs(L71 - L88)
Path Resolution Process
Path resolution is a fundamental operation in the directory structure. The split_path
helper function separates a path into its first component and the remainder, enabling recursive traversal.
flowchart TD A["lookup(path)"] B["split_path(path)"] C["name is?"] D["Return self"] E["Return parent"] F["Look up name in children"] G["Return NotFound error"] H["rest path?"] I["node.lookup(rest)"] J["Return node"] A --> B B --> C C --> D C --> E C --> F F --> G F --> H H --> I H --> J
Sources: axfs_devfs/src/dir.rs(L51 - L69) axfs_devfs/src/dir.rs(L133 - L138)
Special Path Components
The directory implementation handles special path components:
"."
- Refers to the current directory".."
- Refers to the parent directory""
(empty component) - Treated as the current directory
Sources: axfs_devfs/src/dir.rs(L53 - L55)
Directory Hierarchy in DeviceFileSystem
The DeviceFileSystem
struct maintains the root directory of the filesystem. It provides methods to manipulate the root directory and implements the VfsOps
trait required by the VFS layer.
flowchart TD A["DeviceFileSystem"] B["Root DirNode"] C["Child Node 1"] D["Child Node 2"] E["Subdirectory"] F["Child Node 3"] A --> B B --> C B --> D B --> E E --> F
Sources: axfs_devfs/src/lib.rs(L25 - L28) axfs_devfs/src/lib.rs(L30 - L49)
Mount Behavior
When the DeviceFileSystem is mounted, it sets the parent of its root directory to the specified mount point. This establishes the connection between the device filesystem and the broader filesystem hierarchy.
Sources: axfs_devfs/src/lib.rs(L53 - L60)
Concurrency and Memory Management
The directory implementation includes several key design aspects to handle concurrency and memory management:
- Reader-Writer Locks: Both the parent reference and children map are protected by
RwLock
to allow concurrent read access while ensuring exclusive write access. - Weak References: Parent references are stored as weak references (
Weak<dyn VfsNodeOps>
) to prevent reference cycles and memory leaks. - Arc for Shared Ownership: Nodes are wrapped in
Arc
(Atomic Reference Counting) to allow shared ownership across the filesystem.
Sources: axfs_devfs/src/dir.rs(L10 - L13) axfs_devfs/src/dir.rs(L16 - L22)
Limitations
The device filesystem has some intentional limitations in its directory structure:
- Static Names: Node names must be
&'static str
, implying they must be string literals or have a 'static lifetime. - No Dynamic Node Creation/Removal: The
create
andremove
methods are implemented to reject runtime addition or removal of nodes through the VFS interface.
Sources: axfs_devfs/src/dir.rs(L90 - L109) axfs_devfs/src/dir.rs(L111 - L128)
Usage Example
The following example shows how to build a directory structure in the device filesystem:
flowchart TD subgraph subGraph0["Device Filesystem Example"] A["DeviceFileSystem"] B["root (DirNode)"] C["dev (DirNode)"] D["null (NullDev)"] E["zero (ZeroDev)"] F["tmp (DirNode)"] end A --> B B --> C B --> F C --> D C --> E
This creates a structure with paths:
/dev/null
/dev/zero
/tmp
Sources: axfs_devfs/src/lib.rs(L30 - L49)
Null Device
Relevant source files
Purpose and Overview
The Null Device in axfs_devfs implements a virtual device that behaves like the traditional /dev/null
found in Unix-like operating systems. It serves as a "bit bucket" or "black hole" for data - all writes to this device are discarded, and reads yield no data. This component is useful for discarding unwanted output, testing I/O operations, or as a sink for data that doesn't need to be stored.
For information about other special devices, see Zero Device and for a broader context of the device filesystem architecture, see Device File System (axfs_devfs).
Implementation Details
The Null Device is implemented as the NullDev
struct in the axfs_devfs crate. It implements the VfsNodeOps
trait from axfs_vfs, which defines the interface for all file system nodes.
classDiagram class VfsNodeOps { <<trait>> ... other methods ... +get_attr() VfsResult~VfsNodeAttr~ +read_at(offset: u64, buf: &mut [u8]) VfsResult~usize~ +write_at(offset: u64, buf: &[u8]) VfsResult~usize~ +truncate(size: u64) VfsResult } class NullDev { +get_attr() VfsResult~VfsNodeAttr~ +read_at(offset: u64, buf: &mut [u8]) VfsResult~usize~ +write_at(offset: u64, buf: &[u8]) VfsResult~usize~ +truncate(size: u64) VfsResult } VfsNodeOps ..|> NullDev : implements
The NullDev
struct is notably minimal - it contains no data members and implements only the necessary trait methods to provide null device behavior.
Sources: axfs_devfs/src/null.rs(L6 - L7)
Key Method Implementations
The NullDev
struct implements the following key methods from the VfsNodeOps
trait:
- get_attr(): Reports the node as a character device with default file permissions and zero size.
- read_at(): Always returns 0 bytes read, regardless of the requested buffer or offset.
- write_at(): Returns the full buffer length as if all bytes were successfully written, but actually discards the data.
- truncate(): Does nothing and reports success.
Other methods required by the VfsNodeOps
trait are provided through the impl_vfs_non_dir_default!
macro.
Sources: axfs_devfs/src/null.rs(L8 - L30)
Behavioral Characteristics
The Null Device exhibits the following behaviors when interacted with:
sequenceDiagram participant Client as Client participant NullDev as NullDev Client ->> NullDev: read_at(offset, buf) Note over ,NullDev: Ignores offset and buffer NullDev -->> Client: 0 (no bytes read) Client ->> NullDev: write_at(offset, buf) Note over ,NullDev: Discards all data<br>Reports success NullDev -->> Client: buf.len() (all bytes "written") Client ->> NullDev: get_attr() NullDev -->> Client: CharDevice type, 0 size Client ->> NullDev: truncate(size) NullDev -->> Client: Success (no action)
Sources: axfs_devfs/src/null.rs(L8 - L30)
Read Operation
When reading from the null device, it always returns 0, indicating no data was read. The provided buffer remains unchanged.
#![allow(unused)] fn main() { fn read_at(&self, _offset: u64, _buf: &mut [u8]) -> VfsResult<usize> { Ok(0) } }
This behavior can be verified in the tests, where reading from a null device doesn't alter the buffer's content:
let mut buf = [1; N];
assert_eq!(node.read_at(0, &mut buf)?, 0); // Returns 0 bytes read
assert_eq!(buf, [1; N]); // Buffer still contains all 1s
Sources: axfs_devfs/src/null.rs(L18 - L20) axfs_devfs/src/tests.rs(L27 - L28)
Write Operation
When writing to the null device, all data is discarded, but the operation reports success by returning the full length of the input buffer.
#![allow(unused)] fn main() { fn write_at(&self, _offset: u64, buf: &[u8]) -> VfsResult<usize> { Ok(buf.len()) } }
In the test code, we can see this behavior verified:
assert_eq!(node.write_at(N as _, &buf)?, N); // Reports all bytes as written
Sources: axfs_devfs/src/null.rs(L22 - L24) axfs_devfs/src/tests.rs(L29)
Node Attributes
The null device reports itself as a character device with default file permissions and zero size:
#![allow(unused)] fn main() { fn get_attr(&self) -> VfsResult<VfsNodeAttr> { Ok(VfsNodeAttr::new( VfsNodePerm::default_file(), VfsNodeType::CharDevice, 0, 0, )) } }
Sources: axfs_devfs/src/null.rs(L9 - L16)
Integration with Device File System
The Null Device can be added to the Device File System at any location, typically at /dev/null
. Here's how it fits into the overall device file system architecture:
flowchart TD subgraph subGraph0["DeviceFileSystem Structure"] root["Root Directory"] null["/null (NullDev)"] zero["/zero (ZeroDev)"] foo["/foo Directory"] bar["/foo/bar Directory"] f1["/foo/bar/f1 (NullDev)"] end bar --> f1 foo --> bar root --> foo root --> null root --> zero
Sources: axfs_devfs/src/tests.rs(L96 - L102)
A NullDev
instance can be added to the DeviceFileSystem using the add
method:
let devfs = DeviceFileSystem::new();
devfs.add("null", Arc::new(NullDev));
This makes the null device accessible through path resolution from the root of the device file system.
Sources: axfs_devfs/src/tests.rs(L104 - L106)
Usage Examples
The tests in the axfs_devfs crate provide practical examples of how to use the null device:
Creating and Accessing a Null Device
// Create device file system with a null device
let devfs = DeviceFileSystem::new();
devfs.add("null", Arc::new(NullDev));
// Access the null device (supports various path formats)
let node = root.lookup("////null")?;
// Verify it's a character device
assert_eq!(node.get_attr()?.file_type(), VfsNodeType::CharDevice);
Sources: axfs_devfs/src/tests.rs(L104 - L106) axfs_devfs/src/tests.rs(L23 - L25)
Adding a Null Device to Subdirectories
The null device can also be added to subdirectories:
let devfs = DeviceFileSystem::new();
// Create directory structure
let dir_foo = devfs.mkdir("foo");
let dir_bar = dir_foo.mkdir("bar");
// Add null device in a subdirectory
dir_bar.add("f1", Arc::new(NullDev)); // Creates /foo/bar/f1 as a null device
Sources: axfs_devfs/src/tests.rs(L108 - L111)
Technical Specifications
Property | Value |
---|---|
Node Type | Character Device |
Size | 0 bytes |
Permissions | Default file permissions |
Read Behavior | Always returns 0 bytes, buffer unchanged |
Write Behavior | Discards data, reports full buffer length as written |
Truncate Behavior | No-op, reports success |
Directory Operations | Not supported (reportsNotADirectoryerror) |
Sources: axfs_devfs/src/null.rs(L8 - L30) axfs_devfs/src/tests.rs(L23 - L30)
Summary
The Null Device (NullDev
) is a simple yet essential virtual device that adheres to the standard semantics of /dev/null
in Unix-like systems. It discards all written data and provides no data when read, making it useful for scenarios where data needs to be ignored or when a non-functional sink or source is required. It can be integrated anywhere in the device file system hierarchy and behaves consistently with its traditional counterpart.
See also:
Zero Device
Relevant source files
The Zero Device (ZeroDev
) is a special character device in the AxFS Device File System that mimics the behavior of the /dev/zero
device in Unix-like operating systems. It provides an infinite stream of null bytes (\0
) when read and silently discards any data written to it. This page documents the implementation, behavior, and usage of the Zero Device in the axfs_crates repository.
For information about the Null Device, which behaves differently, see Null Device.
Implementation
The Zero Device is implemented as a simple Rust struct that implements the VfsNodeOps
trait defined in the Virtual File System interface.
classDiagram class VfsNodeOps { <<trait>> +many more methods... +get_attr() VfsResult~VfsNodeAttr~ +read_at(offset: u64, buf: &mut [u8]) VfsResult~usize~ +write_at(offset: u64, buf: &[u8]) VfsResult~usize~ +truncate(size: u64) VfsResult } class ZeroDev { <<struct>> } VfsNodeOps --|> ZeroDev : implements
The ZeroDev
struct itself is extremely simple, containing no fields:
pub struct ZeroDev;
It implements the VfsNodeOps
trait to provide the behavior expected of a zero device.
Sources: axfs_devfs/src/zero.rs(L1 - L32)
Behavior
Read Operations
When any read operation is performed on the Zero Device, it fills the provided buffer completely with null bytes (\0
) and returns the length of the buffer as the number of bytes read. This gives the appearance of an infinite stream of zeros.
sequenceDiagram participant Client as Client participant ZeroDev as ZeroDev Client ->> ZeroDev: read_at(offset, buffer) Note over ,ZeroDev: Fill buffer with zeros<br>(buffer.fill(0)) ZeroDev ->> Client: Return buffer.len()
The implementation is straightforward:
#![allow(unused)] fn main() { fn read_at(&self, _offset: u64, buf: &mut [u8]) -> VfsResult<usize> { buf.fill(0); Ok(buf.len()) } }
Sources: axfs_devfs/src/zero.rs(L18 - L21)
Write Operations
The Zero Device silently discards any data written to it, but it reports success as if the write operation completed successfully. The device always reports that all bytes were written, returning the length of the buffer.
sequenceDiagram participant Client as Client participant ZeroDev as ZeroDev Client ->> ZeroDev: write_at(offset, buffer) Note over ,ZeroDev: Discard all data<br>(do nothing) ZeroDev ->> Client: Return buffer.len()
The implementation is extremely minimal:
#![allow(unused)] fn main() { fn write_at(&self, _offset: u64, buf: &[u8]) -> VfsResult<usize> { Ok(buf.len()) } }
Sources: axfs_devfs/src/zero.rs(L23 - L25)
Truncate Operation
The truncate
method is implemented but effectively does nothing, as the Zero Device has no actual storage:
#![allow(unused)] fn main() { fn truncate(&self, _size: u64) -> VfsResult { Ok(()) } }
Sources: axfs_devfs/src/zero.rs(L27 - L29)
Attributes
The Zero Device reports itself as a character device with size 0 and default file permissions:
#![allow(unused)] fn main() { fn get_attr(&self) -> VfsResult<VfsNodeAttr> { Ok(VfsNodeAttr::new( VfsNodePerm::default_file(), VfsNodeType::CharDevice, 0, 0, )) } }
Sources: axfs_devfs/src/zero.rs(L9 - L16)
Integration with Device File System
The Zero Device is typically mounted in a Device File System (DevFS) structure. The creation and mounting process is simple:
flowchart TD subgraph subGraph0["File System Structure"] E["/"] F["/zero"] G["/null"] H["/foo"] I["/foo/f2 (zero)"] J["/foo/bar"] K["/foo/bar/f1 (null)"] end A["DeviceFileSystem::new()"] B["devfs"] C["devfs.add('zero', Arc::new(ZeroDev))"] D["'/zero' accessible in file system"] A --> B B --> C C --> D E --> F E --> G E --> H H --> I H --> J J --> K
Sources: axfs_devfs/src/tests.rs(L96 - L106)
Usage Examples
Creating a Zero Device
To create a Zero Device and add it to a Device File System, you need to:
- Create a new
ZeroDev
instance - Wrap it in an
Arc
(Atomic Reference Count) - Add it to the Device File System with a name
let devfs = DeviceFileSystem::new();
devfs.add("zero", Arc::new(ZeroDev));
You can also create Zero Devices in subdirectories:
let dir_foo = devfs.mkdir("foo");
dir_foo.add("f2", Arc::new(ZeroDev));
Sources: axfs_devfs/src/tests.rs(L104 - L109)
Reading from a Zero Device
When reading from a Zero Device, the buffer will be filled with zeros regardless of the read position:
let node = devfs.root_dir().lookup("zero")?;
let mut buf = [1; 32]; // Buffer filled with 1s
assert_eq!(node.read_at(10, &mut buf)?, 32);
assert_eq!(buf, [0; 32]); // Buffer now filled with 0s
This demonstrates that regardless of the initial buffer content and the offset parameter, the Zero Device will always fill the buffer with zeros and return the full buffer length.
Sources: axfs_devfs/src/tests.rs(L32 - L37)
Writing to a Zero Device
Writing to a Zero Device always succeeds, even though the data is discarded:
let node = devfs.root_dir().lookup("zero")?;
let buf = [1; 32];
assert_eq!(node.write_at(0, &buf)?, 32); // Returns success with the buffer length
Sources: axfs_devfs/src/tests.rs(L38)
Summary
The Zero Device (ZeroDev
) is a simple but useful virtual device in the AxFS Device File System that:
- Provides an infinite stream of zero bytes (
\0
) when read - Silently discards all data written to it
- Reports itself as a character device
- Can be mounted at any location in the device file system
This implementation matches the behavior of the /dev/zero
device in Unix-like systems, providing a consistent interface for applications expecting such a device.
Sources: axfs_devfs/src/zero.rs(L1 - L32) axfs_devfs/src/tests.rs(L32 - L38)
Usage Examples
Relevant source files
This page provides practical examples of how to use the Device File System (axfs_devfs) in your applications. It demonstrates the creation, configuration, and interaction with a device filesystem, focusing on real-world usage patterns. For details about the core DevFS implementation, see Device File System (axfs_devfs) and for specific device implementations, refer to Null Device and Zero Device.
Creating and Configuring a Device File System
The most basic usage of the Device File System involves creating an instance and adding various devices and directories to it. The following example demonstrates how to create a complete filesystem structure:
flowchart TD subgraph subGraph0["Device File System Example Structure"] R["/"] N["/null"] Z["/zero"] F["/foo"] B["/foo/bar"] F2["/foo/f2"] F1["/foo/bar/f1"] end B --> F1 F --> B F --> F2 R --> F R --> N R --> Z
Sources: axfs_devfs/src/tests.rs(L94 - L112)
The code to create this structure:
- First, create a new
DeviceFileSystem
instance - Add device nodes (null and zero) to the root
- Create directories and add more devices to them
let devfs = DeviceFileSystem::new();
devfs.add("null", Arc::new(NullDev));
devfs.add("zero", Arc::new(ZeroDev));
let dir_foo = devfs.mkdir("foo");
dir_foo.add("f2", Arc::new(ZeroDev));
let dir_bar = dir_foo.mkdir("bar");
dir_bar.add("f1", Arc::new(NullDev));
Sources: axfs_devfs/src/tests.rs(L104 - L111)
Path Resolution Examples
The Device File System supports flexible path resolution with various path formats, including absolute paths, relative paths, and paths with special components like .
and ..
. The following diagram illustrates how path resolution works:
flowchart TD subgraph subGraph0["Path Resolution Process"] P1["Root lookup: '/foo/bar/f1'"] S1["Split into components: 'foo', 'bar', 'f1'"] L1["Find 'foo' in root"] L2["Find 'bar' in foo"] L3["Find 'f1' in bar"] R1["Return f1 node"] P2["Path with dot components: './foo/../foo/bar'"] S2["Process dot components"] N2["Normalize to 'foo/bar'"] L4["Resolve 'foo/bar'"] end L1 --> L2 L2 --> L3 L3 --> R1 N2 --> L4 P1 --> S1 P2 --> S2 S1 --> L1 S2 --> N2
Sources: axfs_devfs/src/tests.rs(L22 - L41) axfs_devfs/src/tests.rs(L75 - L89)
Examples of Valid Path Formats
The following table shows examples of path formats that resolve to the same nodes:
Path Format | Resolves To | Notes |
---|---|---|
"null" | Null device at root | Simple node name |
"////null" | Null device at root | Multiple slashes are ignored |
".///.//././/.////zero" | Zero device at root | Dot directories are processed |
"/foo/.." | Root directory | Parent directory reference |
"foo/.//f2" | f2 node in foo | Mixed formats |
"./foo/././bar/../.." | Root directory | Complex path with various components |
"/.//foo/" | foo directory | Mixed absolute path |
Sources: axfs_devfs/src/tests.rs(L23 - L41) axfs_devfs/src/tests.rs(L76 - L89)
Interacting with Devices and Directories
Once you have a reference to a file system node, you can interact with it according to its type:
sequenceDiagram participant Application as Application participant DeviceFileSystem as DeviceFileSystem participant RootDir as RootDir participant FileNode as FileNode Application ->> DeviceFileSystem: root_dir() DeviceFileSystem -->> Application: root node Application ->> RootDir: lookup("zero") RootDir -->> Application: zero device node Application ->> FileNode: get_attr() FileNode -->> Application: attributes (type=CharDevice) Application ->> FileNode: read_at(10, buf) FileNode -->> Application: N bytes of zeros Note over Application,FileNode: Different interaction with directories Application ->> RootDir: lookup("foo") RootDir -->> Application: foo directory node Application ->> FileNode: get_attr() FileNode -->> Application: attributes (type=Dir) Application ->> FileNode: read_at(10, buf) FileNode -->> Application: Error: IsADirectory
Sources: axfs_devfs/src/tests.rs(L7 - L60)
Reading and Writing to Device Nodes
The example below shows how to read from and write to device nodes:
- Null Device:
- Reading returns 0 bytes and doesn't modify the buffer
- Writing accepts any number of bytes (discards them) and returns the number of bytes written
- Zero Device:
- Reading fills the buffer with zeros and returns the buffer size
- Writing accepts any number of bytes (discards them) and returns the number of bytes written
// For null device:
let node = root.lookup("null")?;
assert_eq!(node.read_at(0, &mut buf)?, 0); // Returns 0 bytes
assert_eq!(buf, [1; N]); // Buffer unchanged
assert_eq!(node.write_at(N as _, &buf)?, N); // Returns N bytes written
// For zero device:
let node = root.lookup("zero")?;
assert_eq!(node.read_at(10, &mut buf)?, N); // Returns N bytes
assert_eq!(buf, [0; N]); // Buffer filled with zeros
assert_eq!(node.write_at(0, &buf)?, N); // Returns N bytes written
Sources: axfs_devfs/src/tests.rs(L22 - L39)
Directory Navigation and Parent References
The Device File System allows navigation through the directory structure using parent references. This is particularly useful for implementing commands like cd ..
in a shell or navigating relative paths.
flowchart TD subgraph subGraph0["Parent Navigation Example"] R["Root"] F["foo"] B["bar"] F1["f1"] F2["f2"] L1["lookup('foo/bar')"] L2["lookup('foo/bar/..')"] L3["lookup('foo/bar/../..')"] end B --> F1 F --> B F --> F2 L1 --> B L2 --> F L3 --> R R --> F
Sources: axfs_devfs/src/tests.rs(L69 - L73) axfs_devfs/src/tests.rs(L75 - L89)
You can navigate the file system using parent references:
// Get a reference to the 'bar' directory
let node = root.lookup(".//foo/bar")?;
// Get its parent (which should be 'foo')
let parent = node.parent().unwrap();
// Verify it's the same as directly looking up 'foo'
assert!(Arc::ptr_eq(&parent, &root.lookup("foo")?));
// Various ways to navigate up the tree
assert!(Arc::ptr_eq(&root.lookup("/foo/..")?, &root.lookup(".")?));
Sources: axfs_devfs/src/tests.rs(L69 - L77)
Common Use Patterns
Here are some typical usage patterns for the Device File System:
Pattern 1: Creating a Standard /dev Directory
A common pattern is to create a /dev
directory with standard Unix-like device nodes:
let devfs = DeviceFileSystem::new();
devfs.add("null", Arc::new(NullDev));
devfs.add("zero", Arc::new(ZeroDev));
// Add other standard devices as needed
Sources: axfs_devfs/src/tests.rs(L104 - L106)
Pattern 2: Testing File System Structure
When implementing file system-related functionality, you can use this pattern to verify correct path resolution:
// Test complex path resolution
assert!(Arc::ptr_eq(
&root.lookup("///foo//bar///..//././")?,
&devfs.root_dir().lookup(".//./foo/")?,
));
Sources: axfs_devfs/src/tests.rs(L82 - L85)
Pattern 3: Testing Device Characteristics
This pattern is useful for verifying that device nodes behave as expected:
let node = root.lookup("zero")?;
assert_eq!(node.get_attr()?.file_type(), VfsNodeType::CharDevice);
assert!(!node.get_attr()?.is_dir());
assert_eq!(node.get_attr()?.size(), 0);
// Test zero device behavior
let mut buf = [1; 32];
assert_eq!(node.read_at(10, &mut buf)?, 32);
assert_eq!(buf, [0; 32]);
Sources: axfs_devfs/src/tests.rs(L32 - L38)
Error Handling
When using the Device File System, various errors can occur. Here's how to handle common error cases:
// Handle "not found" errors
match root.lookup("urandom") {
Ok(node) => { /* use node */ },
Err(VfsError::NotFound) => { /* handle not found */ },
Err(e) => { /* handle other errors */ },
}
// Handle "not a directory" errors
match root.lookup("zero/") {
Ok(node) => { /* use node */ },
Err(VfsError::NotADirectory) => { /* handle not a directory */ },
Err(e) => { /* handle other errors */ },
}
// Handle "is a directory" errors when trying to read/write
match foo.read_at(10, &mut buf) {
Ok(bytes_read) => { /* use bytes_read */ },
Err(VfsError::IsADirectory) => { /* handle is a directory */ },
Err(e) => { /* handle other errors */ },
}
Sources: axfs_devfs/src/tests.rs(L14 - L21) axfs_devfs/src/tests.rs(L42 - L45)
Integration with VFS
The Device File System is designed to integrate with the Virtual File System interface. This lets you use it alongside other file systems in a unified hierarchy.
flowchart TD subgraph subGraph0["DFS Implementation"] DFS["DeviceFileSystem"] D1["root_dir()"] D2["mount()"] end A["Application"] VFS["VfsOps Interface"] RFS["RamFileSystem"] DR["Returns root DirNode"] DM["Sets parent relationship"] A --> VFS D1 --> DR D2 --> DM DFS --> D1 DFS --> D2 VFS --> DFS VFS --> RFS
Sources: axfs_devfs/src/lib.rs(L52 - L65)
For more information on VFS integration, see Virtual File System Interface (axfs_vfs).
RAM File System (axfs_ramfs)
Relevant source files
Purpose and Scope
axfs_ramfs
implements a RAM-based file system for the axfs_crates ecosystem. It provides an in-memory storage solution that conforms to the Virtual File System (VFS) interfaces defined in axfs_vfs
. This document covers the architecture, components, and usage patterns of the RAM File System.
For information about the VFS interface that axfs_ramfs
implements, see Virtual File System Interface (axfs_vfs).
Sources: Cargo.toml(L4 - L8) README.md(L7 - L9)
System Architecture
The RAM File System is one of the concrete implementations in the axfs_crates ecosystem, alongside the Device File System (axfs_devfs
).
Integration with axfs_crates
flowchart TD A["Applications"] B["axfs_vfs: VfsOps & VfsNodeOps Traits"] C["axfs_ramfs: RamFileSystem"] D["axfs_devfs: DeviceFileSystem"] E["System Memory (RAM)"] F["Hardware Devices"] A --> B B --> C B --> D C --> E D --> F
This diagram illustrates how axfs_ramfs
fits within the overall architecture. Applications interact with file systems through the VFS interface, which delegates operations to the appropriate implementation based on mount points.
Sources: Cargo.toml(L4 - L8) README.md(L7 - L9)
Core Components
Based on the VFS interface requirements, the RAM File System likely consists of these core components:
Component Relationships
classDiagram class VfsOps { <<trait>> +mount(path, mount_point) +root_dir() } class VfsNodeOps { <<trait>> +get_attr() +lookup(path) +read_dir(start_idx, dirents) +read_at(offset, buf) +write_at(offset, buf) +create(path, type) +remove(path) +truncate(size) } class RamFileSystem { -root +new() +root_dir() +mount(path, mount_point) } class RamDirNode { -parent -children +new() +lookup(path) +create(name, type) } class RamFileNode { -parent -content +new() +read_at(offset, buf) +write_at(offset, buf) +truncate(size) } VfsOps ..|> RamFileSystem : implements VfsNodeOps ..|> RamDirNode : implements VfsNodeOps ..|> RamFileNode : implements RamFileSystem --> RamDirNode : contains RamDirNode --> RamDirNode RamDirNode --> RamDirNode : contains RamDirNode --> RamDirNode RamDirNode --> RamFileNode : contains
The key components likely include:
Component | Purpose | Key Responsibilities |
---|---|---|
RamFileSystem | Main file system implementation | ImplementsVfsOpstrait, manages mount operations, provides access to root directory |
RamDirNode | Directory node implementation | Stores hierarchical structure, manages children (files and subdirectories) |
RamFileNode | File node implementation | Stores file content in memory, handles read/write operations |
These components work together to provide a complete in-memory file system hierarchy with the same interface as other file systems.
Sources: Based on common Rust patterns and the VFS architecture shown in the high-level diagrams
In-Memory Hierarchy
A typical RAM file system hierarchy structure:
flowchart TD A["RamDirNode: Root '/'"] B["RamDirNode: '/tmp'"] C["RamDirNode: '/home'"] D["RamFileNode: 'temp.txt'"] E["RamDirNode: 'cache'"] F["RamFileNode: 'data.bin'"] G["RamDirNode: 'user1'"] H["RamFileNode: 'document.txt'"] I["RamFileNode: 'image.png'"] A --> B A --> C B --> D B --> E C --> G E --> F G --> H G --> I
This structure mirrors traditional file systems but exists entirely in memory. Each node (RamDirNode
or RamFileNode
) implements the VfsNodeOps
trait, providing a consistent interface regardless of node type.
Sources: Based on common file system hierarchy patterns
Operation Patterns
The RAM File System implements file system operations defined by the VFS traits:
File System Operations (VfsOpsTrait)
mount
: Attaches the RAM file system to a mount pointroot_dir
: Provides access to the root directory
Node Operations (VfsNodeOpsTrait)
Operation | Description | Implementation Considerations |
---|---|---|
get_attr | Get node attributes (size, type, etc.) | Return cached attributes or calculate on demand |
lookup | Find a node by path | Traverse directory hierarchy to locate node |
read_dir | List directory contents | Return entries from directory's children map |
read_at | Read file data from specified offset | Access in-memory buffer at offset |
write_at | Write file data at specified offset | Modify in-memory buffer, potentially resize |
create | Create new file or directory | Allocate new node, add to parent's children |
remove | Delete file or directory | Remove from parent's children, free memory |
truncate | Change file size | Resize in-memory buffer |
Sources: Based on VFS interface requirements
Usage Lifecycle
sequenceDiagram participant Application as "Application" participant VFSInterface as "VFS Interface" participant RamFileSystem as "RamFileSystem" participant RamDirNode as "RamDirNode" participant RamFileNode as "RamFileNode" Application ->> RamFileSystem: RamFileSystem::new() RamFileSystem ->> RamDirNode: RamDirNode::new() RamDirNode ->> RamFileSystem: Arc<RamDirNode> RamFileSystem ->> Application: RamFileSystem instance Application ->> VFSInterface: mount("/tmp", ramfs.root_dir()) VFSInterface ->> RamFileSystem: mount operation Application ->> VFSInterface: create("/tmp/file.txt", VfsNodeType::File) VFSInterface ->> RamFileSystem: create request RamFileSystem ->> RamDirNode: lookup("/tmp") RamDirNode ->> RamFileSystem: tmp directory node RamFileSystem ->> RamDirNode: create("file.txt", VfsNodeType::File) RamDirNode ->> RamFileNode: RamFileNode::new() RamFileNode ->> RamDirNode: Arc<RamFileNode> RamDirNode ->> RamFileSystem: Success RamFileSystem ->> VFSInterface: Success VFSInterface ->> Application: Success Application ->> VFSInterface: lookup("/tmp/file.txt") VFSInterface ->> RamFileSystem: lookup request RamFileSystem ->> RamDirNode: lookup("/tmp") RamDirNode ->> RamFileSystem: tmp directory node RamFileSystem ->> RamDirNode: lookup("file.txt") RamDirNode ->> RamFileNode: Get file node RamFileNode ->> RamDirNode: file node RamDirNode ->> RamFileSystem: file node RamFileSystem ->> VFSInterface: file node VFSInterface ->> Application: file node Application ->> VFSInterface: write_at(file, 0, data) VFSInterface ->> RamFileNode: write_at(0, data) RamFileNode ->> RamFileNode: Store data in memory buffer RamFileNode ->> VFSInterface: bytes_written VFSInterface ->> Application: bytes_written
This sequence diagram illustrates the typical lifecycle of RAM file system usage, from creation to file operations.
Sources: Based on common file system operation patterns
Implementation Considerations
The RAM File System implementation likely uses these Rust features:
Arc<T>
for shared ownership of nodesWeak<T>
references to prevent reference cycles (particularly for parent pointers)RwLock<T>
orMutex<T>
for concurrent access to mutable dataVec<u8>
or similar for storing file contentsBTreeMap<String, VfsNodeRef>
for directory entries
Advantages and Limitations
Advantages
- Performance: Memory operations are faster than disk I/O
- Simplicity: No need for persistent storage management
- Portability: Works in environments without storage devices
Limitations
- Volatility: All data is lost on system shutdown or restart
- Capacity: Limited by available RAM
- Concurrent Access: Requires careful synchronization for thread safety
Use Cases
- Temporary file storage
- Cache for frequently accessed data
- Test environments
- Embedded systems with limited storage
- Performance-critical applications
Sources: README.md(L9)
Overview
Relevant source files
Purpose and Scope
The scheduler crate provides a unified interface for multiple scheduling algorithms within the ArceOS operating system project. This document covers the overall architecture, core components, and design principles of the scheduler system. The crate implements three distinct scheduling algorithms—FIFO, Round Robin, and Completely Fair Scheduler (CFS)—through a common BaseScheduler
trait interface.
For detailed information about individual scheduler implementations, see Scheduler Implementations. For guidance on extending or modifying the scheduler system, see Development Guide.
System Architecture
The scheduler crate follows a trait-based design pattern that enables polymorphic usage of different scheduling algorithms within the ArceOS kernel. The system consists of a core trait definition, multiple algorithm implementations, and associated task wrapper types.
Core Components Overview
Sources: src/lib.rs(L24 - L68) Cargo.toml(L1 - L14)
Trait Implementation Mapping
classDiagram class BaseScheduler { <<trait>> +SchedItem type +init() +add_task(task) +remove_task(task) Option~SchedItem~ +pick_next_task() Option~SchedItem~ +put_prev_task(prev, preempt) +task_tick(current) bool +set_priority(task, prio) bool } class FifoScheduler { +SchedItem Arc~FifoTask~T~~ +scheduler_name() "FIFO" } class RRScheduler { +SchedItem Arc~RRTask~T~~ +scheduler_name() "Round-robin" } class CFScheduler { +SchedItem Arc~CFSTask~T~~ +scheduler_name() "CFS" } BaseScheduler ..|> FifoScheduler BaseScheduler ..|> RRScheduler BaseScheduler ..|> CFScheduler
Sources: src/lib.rs(L28 - L68) src/lib.rs(L20 - L22)
Algorithm Characteristics
The crate provides three scheduling algorithms with distinct characteristics and use cases:
Algorithm | Type | Preemption | Priority Support | Data Structure |
---|---|---|---|---|
FIFO | Cooperative | No | No | linked_list_r4l::List |
Round Robin | Preemptive | Timer-based | No | VecDeque |
CFS | Preemptive | Virtual runtime | Nice values | BTreeMap |
Core Interface Design
The BaseScheduler
trait defines the fundamental operations required by all scheduling algorithms. The trait uses an associated type SchedItem
to represent scheduled entities, enabling type-safe implementation across different task wrapper types.
Key operations include:
- Task Management:
add_task()
,remove_task()
for inserting and removing tasks from the scheduler - Scheduling Decisions:
pick_next_task()
for selecting the next task to execute - Context Switching:
put_prev_task()
for handling previously running tasks - Timer Integration:
task_tick()
for time-based scheduling decisions - Priority Control:
set_priority()
for dynamic priority adjustment
Sources: src/lib.rs(L28 - L68)
Integration with ArceOS
The scheduler crate is designed as a foundational component for the ArceOS operating system, providing the scheduling subsystem that manages task execution. The unified interface allows the kernel to interact with different scheduling policies through the same API, enabling runtime scheduler selection or algorithm-specific optimizations.
The crate uses #![cfg_attr(not(test), no_std)]
to support both hosted testing environments and bare-metal execution contexts required by ArceOS.
Sources: src/lib.rs(L9) Cargo.toml(L8 - L10)
Testing and Validation
The crate includes a comprehensive testing framework that validates the behavior of all scheduler implementations. The testing system uses macro-based test generation to ensure consistent validation across different algorithms.
For detailed information about the testing framework and development workflow, see Testing Framework and Development Guide.
Sources: src/lib.rs(L15 - L16)
Next Steps
This overview provides the foundation for understanding the scheduler crate architecture. For more detailed information:
- Core Architecture - Deep dive into the
BaseScheduler
trait design and implementation patterns - Scheduler Implementations - Detailed documentation of each scheduling algorithm
- Testing Framework - Testing infrastructure and validation methodology
- Development Guide - Guidelines for contributing to and extending the scheduler system
Core Architecture
Relevant source files
This document explains the foundational design of the scheduler crate, focusing on the BaseScheduler
trait and the architectural principles that enable a unified interface across different scheduling algorithms. For specific implementation details of individual schedulers, see Scheduler Implementations. For testing methodology, see Testing Framework.
Unified Scheduler Interface
The scheduler crate's core architecture is built around the BaseScheduler
trait, which provides a consistent interface for all scheduling algorithm implementations. This trait defines eight essential methods that every scheduler must implement, ensuring predictable behavior across different scheduling policies.
BaseScheduler Trait Structure
classDiagram class BaseScheduler { <<trait>> +SchedItem: type +init() +add_task(task: SchedItem) +remove_task(task: &SchedItem) Option~SchedItem~ +pick_next_task() Option~SchedItem~ +put_prev_task(prev: SchedItem, preempt: bool) +task_tick(current: &SchedItem) bool +set_priority(task: &SchedItem, prio: isize) bool } class FifoScheduler { +SchedItem: Arc~FifoTask~T~~ +scheduler_name() : "FIFO" } class RRScheduler { +SchedItem: Arc~RRTask~T,S~~ +scheduler_name() : "Round-robin" } class CFScheduler { +SchedItem: Arc~CFSTask~T~~ +scheduler_name() : "CFS" } BaseScheduler ..|> FifoScheduler BaseScheduler ..|> RRScheduler BaseScheduler ..|> CFScheduler
Sources: src/lib.rs(L24 - L68)
The trait uses an associated type SchedItem
to allow each scheduler implementation to define its own task wrapper type, providing type safety while maintaining interface consistency.
Task Lifecycle Management
The architecture defines a clear task lifecycle through the trait methods, ensuring that all schedulers handle task state transitions uniformly.
Task State Flow
stateDiagram-v2 [*] --> Ready : "add_task()" Ready --> Running : "pick_next_task()" Running --> Ready : "put_prev_task(preempt=false)" Running --> Preempted : "put_prev_task(preempt=true)" Preempted --> Ready : "scheduler decision" Running --> TimerTick : "task_tick()" TimerTick --> Running : "return false" TimerTick --> NeedResched : "return true" NeedResched --> Ready : "put_prev_task()" Ready --> Removed : "remove_task()" Running --> Removed : "remove_task()" Removed --> [*]
Sources: src/lib.rs(L35 - L67)
Core Methods and Responsibilities
The BaseScheduler
trait defines specific responsibilities for each method:
Method | Purpose | Return Type | Safety Requirements |
---|---|---|---|
init | Initialize scheduler state | () | None |
add_task | Add runnable task to scheduler | () | Task must be runnable |
remove_task | Remove task by reference | Option | Task must exist in scheduler |
pick_next_task | Select and remove next task | Option | None |
put_prev_task | Return task to scheduler | () | None |
task_tick | Process timer tick | bool | Current task must be valid |
set_priority | Modify task priority | bool | Task must exist in scheduler |
Sources: src/lib.rs(L32 - L67)
Scheduler Implementation Pattern
The architecture follows a consistent pattern where each scheduler implementation manages its own specialized task wrapper type and internal data structures.
Implementation Hierarchy
flowchart TD BaseScheduler["BaseScheduler<trait>"] FifoScheduler["FifoScheduler"] RRScheduler["RRScheduler"] CFScheduler["CFScheduler"] FifoTask["FifoTask<T>"] RRTask["RRTask<T,S>"] CFSTask["CFSTask<T>"] inner_fifo["inner: T"] inner_rr["inner: T"] time_slice["time_slice: AtomicIsize"] inner_cfs["inner: T"] vruntime["init_vruntime: AtomicIsize"] nice["nice: AtomicIsize"] delta["delta: AtomicIsize"] BaseScheduler --> CFScheduler BaseScheduler --> FifoScheduler BaseScheduler --> RRScheduler CFSTask --> delta CFSTask --> inner_cfs CFSTask --> nice CFSTask --> vruntime CFScheduler --> CFSTask FifoScheduler --> FifoTask FifoTask --> inner_fifo RRScheduler --> RRTask RRTask --> inner_rr RRTask --> time_slice
Sources: src/lib.rs(L20 - L22)
Design Principles
Type Safety through Associated Types
The architecture uses Rust's associated type system to ensure compile-time type safety while allowing each scheduler to define its own task wrapper:
type SchedItem; // Defined per implementation
This approach prevents mixing task types between different scheduler implementations while maintaining a unified interface.
Memory Safety Patterns
The trait design incorporates several memory safety patterns:
- Ownership Transfer:
pick_next_task()
transfers ownership of tasks out of the scheduler - Reference-based Operations:
remove_task()
andtask_tick()
use references to avoid unnecessary ownership transfers - Optional Returns: Methods return
Option<SchedItem>
to handle empty scheduler states safely
Preemption Awareness
The architecture explicitly supports both cooperative and preemptive scheduling through the preempt
parameter in put_prev_task()
, allowing schedulers to implement different policies for preempted versus yielding tasks.
Sources: src/lib.rs(L55 - L58)
Module Organization
The crate follows a modular architecture with clear separation of concerns:
flowchart TD lib_rs["lib.rs"] trait_def["BaseScheduler trait definition"] exports["Public exports"] fifo_mod["mod fifo"] rr_mod["mod round_robin"] cfs_mod["mod cfs"] tests_mod["mod tests"] FifoScheduler["FifoScheduler"] FifoTask["FifoTask"] RRScheduler["RRScheduler"] RRTask["RRTask"] CFScheduler["CFScheduler"] CFSTask["CFSTask"] cfs_mod --> CFSTask cfs_mod --> CFScheduler exports --> CFSTask exports --> CFScheduler exports --> FifoScheduler exports --> FifoTask exports --> RRScheduler exports --> RRTask fifo_mod --> FifoScheduler fifo_mod --> FifoTask lib_rs --> cfs_mod lib_rs --> exports lib_rs --> fifo_mod lib_rs --> rr_mod lib_rs --> tests_mod lib_rs --> trait_def rr_mod --> RRScheduler rr_mod --> RRTask
Sources: src/lib.rs(L11 - L22)
This modular design ensures that each scheduling algorithm is self-contained while contributing to the unified interface through the central trait definition.
Scheduler Implementations
Relevant source files
This document provides an overview of the three scheduler algorithm implementations provided by the crate: FIFO, Round Robin, and Completely Fair Scheduler (CFS). Each implementation adheres to the unified BaseScheduler
interface while providing distinct scheduling behaviors and performance characteristics.
For details about the BaseScheduler
trait and core architecture, see Core Architecture. For detailed documentation of each individual scheduler, see FIFO Scheduler, Completely Fair Scheduler (CFS), and Round Robin Scheduler.
Scheduler Overview
The crate provides three distinct scheduling algorithms, each designed for different use cases and performance requirements:
Scheduler | Type | Priority Support | Preemption | Data Structure | Use Case |
---|---|---|---|---|---|
FifoScheduler | Cooperative | No | No | linked_list_r4l::List | Simple sequential execution |
RRScheduler | Preemptive | No | Time-based | VecDeque | Fair time sharing |
CFScheduler | Preemptive | Yes (nice values) | Virtual runtime | BTreeMap | Advanced fair scheduling |
Each scheduler manages tasks wrapped in scheduler-specific container types that add necessary scheduling metadata.
Sources: src/lib.rs(L1 - L8) src/fifo.rs(L14 - L22) src/round_robin.rs(L46 - L56) src/cfs.rs(L100 - L102)
Implementation Architecture
Trait Implementation Hierarchy
flowchart TD subgraph subGraph3["CFS Implementation"] CFS["CFScheduler<T>"] CFT["Arc<CFSTask<T>>"] CFS_queue["ready_queue: BTreeMap"] CFS_vtime["vruntime tracking"] CFS_nice["nice values"] end subgraph subGraph2["Round Robin Implementation"] RRS["RRScheduler<T, MAX_TIME_SLICE>"] RRT["Arc<RRTask<T, S>>"] RRS_queue["ready_queue: VecDeque"] RRS_slice["time_slice: AtomicIsize"] end subgraph subGraph1["FIFO Implementation"] FS["FifoScheduler<T>"] FT["Arc<FifoTask<T>>"] FS_queue["ready_queue: List"] end subgraph subGraph0["BaseScheduler Trait"] BS["BaseScheduler<SchedItem>"] BS_init["init()"] BS_add["add_task(task)"] BS_remove["remove_task(task)"] BS_pick["pick_next_task()"] BS_put["put_prev_task(prev, preempt)"] BS_tick["task_tick(current)"] BS_prio["set_priority(task, prio)"] end BS --> CFS BS --> FS BS --> RRS CFS --> CFS_nice CFS --> CFS_queue CFS --> CFS_vtime CFS --> CFT FS --> FS_queue FS --> FT RRS --> RRS_queue RRS --> RRS_slice RRS --> RRT
Sources: src/lib.rs(L24 - L68) src/fifo.rs(L23 - L38) src/round_robin.rs(L58 - L73) src/cfs.rs(L103 - L122)
Task Wrapper Architecture
flowchart TD subgraph subGraph2["Scheduling Metadata"] LinkedList["linked_list_r4l node"] TimeSlice["time_slice: AtomicIsize"] VRuntime["init_vruntime: AtomicIsize"] Delta["delta: AtomicIsize"] Nice["nice: AtomicIsize"] TaskId["id: AtomicIsize"] end subgraph subGraph1["Inner Task"] T["T (user task)"] end subgraph subGraph0["Task Wrappers"] FifoTask["FifoTask<T>"] RRTask["RRTask<T, MAX_TIME_SLICE>"] CFSTask["CFSTask<T>"] end CFSTask --> Delta CFSTask --> Nice CFSTask --> T CFSTask --> TaskId CFSTask --> VRuntime FifoTask --> LinkedList FifoTask --> T RRTask --> T RRTask --> TimeSlice
Sources: src/fifo.rs(L7 - L12) src/round_robin.rs(L7 - L13) src/cfs.rs(L7 - L14)
Scheduling Behavior Comparison
Core Scheduling Methods
Each scheduler implements the BaseScheduler
methods differently:
Task Selection (pick_next_task
):
FifoScheduler
: Removes and returns the head of the linked list src/fifo.rs(L53 - L55)RRScheduler
: Removes and returns the front of theVecDeque
src/round_robin.rs(L92 - L94)CFScheduler
: Removes and returns the task with minimum virtual runtime fromBTreeMap
src/cfs.rs(L161 - L167)
Task Insertion (put_prev_task
):
FifoScheduler
: Always appends to the back of the queue src/fifo.rs(L57 - L59)RRScheduler
: Inserts at front if preempted with remaining time slice, otherwise resets time slice and appends to back src/round_robin.rs(L96 - L103)CFScheduler
: Assigns new task ID and inserts based on updated virtual runtime src/cfs.rs(L169 - L174)
Preemption Logic (task_tick
):
FifoScheduler
: Never requests reschedule (cooperative) src/fifo.rs(L61 - L63)RRScheduler
: Decrements time slice and requests reschedule when it reaches zero src/round_robin.rs(L105 - L108)CFScheduler
: Increments virtual runtime delta and requests reschedule when current task's virtual runtime exceeds minimum src/cfs.rs(L176 - L183)
Sources: src/fifo.rs(L40 - L68) src/round_robin.rs(L75 - L113) src/cfs.rs(L124 - L193)
Priority Support
Only the CFScheduler
supports dynamic priority adjustment through nice values:
flowchart TD subgraph subGraph0["CFS Priority Implementation"] CFSWeight["get_weight() from nice value"] CFSVruntime["adjust virtual runtime calculation"] CFSNICES["NICE2WEIGHT_POS/NEG arrays"] end SetPrio["set_priority(task, prio)"] FifoCheck["FifoScheduler?"] RRCheck["RRScheduler?"] CFSCheck["CFScheduler?"] FifoReturn["return false"] RRReturn["return false"] CFSRange["prio in [-20, 19]?"] CFSSet["task.set_priority(prio)"] CFSFalse["return false"] CFSTrue["return true"] CFSCheck --> CFSRange CFSRange --> CFSFalse CFSRange --> CFSSet CFSSet --> CFSTrue CFSSet --> CFSWeight CFSWeight --> CFSNICES CFSWeight --> CFSVruntime FifoCheck --> FifoReturn RRCheck --> RRReturn SetPrio --> CFSCheck SetPrio --> FifoCheck SetPrio --> RRCheck
Sources: src/fifo.rs(L65 - L67) src/round_robin.rs(L110 - L112) src/cfs.rs(L185 - L192) src/cfs.rs(L23 - L29) src/cfs.rs(L43 - L50)
Data Structure Performance Characteristics
Operation | FifoScheduler | RRScheduler | CFScheduler |
---|---|---|---|
add_task | O(1) | O(1) | O(log n) |
pick_next_task | O(1) | O(1) | O(log n) |
remove_task | O(1) | O(n) | O(log n) |
put_prev_task | O(1) | O(1) | O(log n) |
Memory overhead | Minimal | Low | Moderate |
The choice of data structure reflects each scheduler's priorities:
FifoScheduler
useslinked_list_r4l::List
for optimal insertion/removal performanceRRScheduler
usesVecDeque
for simple front/back operations but suffers from O(n) arbitrary removalCFScheduler
usesBTreeMap
keyed by(vruntime, task_id)
to maintain virtual runtime ordering
Sources: src/fifo.rs(L23 - L25) src/round_robin.rs(L58 - L60) src/cfs.rs(L103 - L107)
Thread Safety and Atomic Operations
All task metadata requiring atomic access is implemented using AtomicIsize
:
RRTask
:time_slice
for preemption-safe time tracking src/round_robin.rs(L10 - L13)CFSTask
:init_vruntime
,delta
,nice
, andid
for lock-free virtual runtime calculations src/cfs.rs(L8 - L14)FifoTask
: No atomic state needed due to cooperative nature src/fifo.rs(L7 - L12)
This design enables safe concurrent access to task state during scheduling operations without requiring additional synchronization primitives.
Sources: src/round_robin.rs(L10 - L13) src/cfs.rs(L8 - L14) src/fifo.rs(L7 - L12)
FIFO Scheduler
Relevant source files
Purpose and Scope
This document covers the FIFO (First-In-First-Out) scheduler implementation within the ArceOS scheduler crate. The FIFO scheduler provides cooperative task scheduling with simple queue-based ordering. For information about the unified scheduler interface, see Core Architecture. For comparisons with other scheduling algorithms, see Scheduler Comparison.
Overview
The FIFO scheduler is the simplest scheduling algorithm provided by the scheduler crate. It implements a cooperative, non-preemptive scheduling policy where tasks are executed in the order they were added to the ready queue.
Key Characteristics
Characteristic | Value |
---|---|
Scheduling Policy | Cooperative, Non-preemptive |
Data Structure | Linked List (linked_list_r4l::List) |
Task Ordering | First-In-First-Out |
Priority Support | No |
Timer Preemption | No |
Scheduler Name | "FIFO" |
Sources: src/fifo.rs(L14 - L22) src/fifo.rs(L35 - L37)
Implementation Architecture
FIFO Scheduler Structure
The FifoScheduler<T>
maintains a single ready queue implemented as a linked list:
flowchart TD subgraph subGraph1["Task Flow"] NewTask["New Task"] QueueTail["Queue Tail"] QueueHead["Queue Head"] RunningTask["Running Task"] end subgraph subGraph0["Ready Queue Operations"] ready_queue["ready_queue: List<Arc<FifoTask<T>>>"] push_back["push_back()"] pop_front["pop_front()"] remove["remove()"] end FifoScheduler["FifoScheduler<T>"] FifoScheduler --> ready_queue NewTask --> push_back QueueHead --> pop_front pop_front --> RunningTask push_back --> QueueTail ready_queue --> pop_front ready_queue --> push_back ready_queue --> remove
Sources: src/fifo.rs(L23 - L25) src/fifo.rs(L46) src/fifo.rs(L54) src/fifo.rs(L50)
Task Wrapper Implementation
FifoTask Structure
The FifoTask<T>
wraps user tasks to work with the linked list data structure:
flowchart TD subgraph subGraph2["Memory Management"] Arc["Arc<FifoTask<T>>"] SharedOwnership["Shared ownership across scheduler"] end subgraph subGraph1["Macro Generated"] def_node["def_node! macro"] NodeMethods["Node manipulation methods"] end subgraph subGraph0["Task Wrapper"] UserTask["User Task T"] FifoTask["FifoTask<T>"] LinkedListNode["Linked List Node"] end Arc --> SharedOwnership FifoTask --> Arc FifoTask --> LinkedListNode UserTask --> FifoTask def_node --> LinkedListNode def_node --> NodeMethods
The FifoTask
is defined using the def_node!
macro from linked_list_r4l
, which automatically generates the necessary linked list node methods.
Sources: src/fifo.rs(L7 - L12)
BaseScheduler Implementation
Core Scheduling Methods
The FifoScheduler
implements all required BaseScheduler
trait methods:
flowchart TD subgraph subGraph1["FIFO Behavior"] push_back_queue["push_back to ready_queue"] pop_front_queue["pop_front from ready_queue"] push_back_queue2["push_back to ready_queue"] no_reschedule["return false (no reschedule)"] not_supported["return false (not supported)"] end subgraph subGraph0["Required Methods"] FifoScheduler["FifoScheduler<T>"] init["init()"] add_task["add_task()"] remove_task["remove_task()"] pick_next_task["pick_next_task()"] put_prev_task["put_prev_task()"] task_tick["task_tick()"] set_priority["set_priority()"] end BaseScheduler["BaseScheduler trait"] BaseScheduler --> FifoScheduler FifoScheduler --> add_task FifoScheduler --> init FifoScheduler --> pick_next_task FifoScheduler --> put_prev_task FifoScheduler --> remove_task FifoScheduler --> set_priority FifoScheduler --> task_tick add_task --> push_back_queue pick_next_task --> pop_front_queue put_prev_task --> push_back_queue2 set_priority --> not_supported task_tick --> no_reschedule
Sources: src/fifo.rs(L40 - L68)
Method Implementations
Method | Implementation | Return Value |
---|---|---|
init() | No initialization required | () |
add_task() | Adds task to rear of queue viapush_back() | () |
remove_task() | Removes task using unsaferemove() | OptionSelf::SchedItem |
pick_next_task() | Gets task from front viapop_front() | OptionSelf::SchedItem |
put_prev_task() | Adds task back to rear of queue | () |
task_tick() | No action (cooperative scheduling) | false |
set_priority() | No priority support | false |
Sources: src/fifo.rs(L43 - L67)
Scheduling Behavior
Task Lifecycle in FIFO Scheduler
sequenceDiagram participant ArceOSKernel as "ArceOS Kernel" participant FifoScheduler as "FifoScheduler" participant ready_queue as "ready_queue" participant FifoTask as "FifoTask" ArceOSKernel ->> FifoScheduler: add_task(task) FifoScheduler ->> ready_queue: push_back(task) Note over ready_queue: Task added to rear ArceOSKernel ->> FifoScheduler: pick_next_task() ready_queue ->> FifoScheduler: pop_front() FifoScheduler ->> ArceOSKernel: return task Note over ArceOSKernel: Task runs cooperatively ArceOSKernel ->> FifoScheduler: task_tick(current_task) FifoScheduler ->> ArceOSKernel: return false Note over ArceOSKernel: No preemption occurs ArceOSKernel ->> FifoScheduler: put_prev_task(task, preempt) FifoScheduler ->> ready_queue: push_back(task) Note over ready_queue: Task goes to rear again
Sources: src/fifo.rs(L45 - L46) src/fifo.rs(L53 - L54) src/fifo.rs(L57 - L58) src/fifo.rs(L61 - L62)
Cooperative Scheduling Model
The FIFO scheduler operates under a cooperative model where:
- No Preemption: Tasks run until they voluntarily yield control
- Timer Tick Ignored: The
task_tick()
method always returnsfalse
, indicating no need for rescheduling - Simple Ordering: Tasks are scheduled strictly in FIFO order
- No Priorities: All tasks have equal scheduling priority
Sources: src/fifo.rs(L20) src/fifo.rs(L61 - L62) src/fifo.rs(L65 - L66)
Constructor and Utility Methods
The FifoScheduler
provides simple construction and identification:
// Constructor
pub const fn new() -> Self
// Scheduler identification
pub fn scheduler_name() -> &'static str
The new()
method creates an empty scheduler with an initialized but empty ready_queue
. The scheduler_name()
method returns the string "FIFO"
for identification purposes.
Sources: src/fifo.rs(L28 - L33) src/fifo.rs(L34 - L37)
Completely Fair Scheduler (CFS)
Relevant source files
This document covers the Completely Fair Scheduler (CFS) implementation provided by the scheduler crate. The CFS implementation aims to provide fair CPU time allocation among tasks based on virtual runtime calculations and supports priority adjustment through nice values.
For information about the overall scheduler architecture and BaseScheduler trait, see Core Architecture. For comparisons with other scheduler implementations, see Scheduler Comparison.
Purpose and Fairness Model
The CFScheduler
implements a simplified version of the Linux Completely Fair Scheduler algorithm. It maintains fairness by tracking virtual runtime for each task and always selecting the task with the lowest virtual runtime for execution. Tasks with higher priority (lower nice values) accumulate virtual runtime more slowly, allowing them to run more frequently.
Key Fairness Principles:
- Tasks are ordered by virtual runtime in a red-black tree (implemented as
BTreeMap
) - Virtual runtime accumulates based on actual execution time and task weight
- Higher priority tasks (negative nice values) get lower weights, accumulating vruntime slower
- The scheduler always picks the leftmost task (lowest virtual runtime) from the tree
Sources: src/cfs.rs(L100 - L122)
Core Data Structures
CFSTask Wrapper
The CFSTask<T>
struct wraps user tasks with CFS-specific metadata:
Field | Type | Purpose |
---|---|---|
inner | T | The wrapped user task |
init_vruntime | AtomicIsize | Initial virtual runtime baseline |
delta | AtomicIsize | Accumulated execution time delta |
nice | AtomicIsize | Priority value (-20 to 19 range) |
id | AtomicIsize | Unique task identifier |
flowchart TD CFSTask["CFSTask<T>"] inner["inner: T"] init_vruntime["init_vruntime: AtomicIsize"] delta["delta: AtomicIsize"] nice["nice: AtomicIsize"] id["id: AtomicIsize"] vruntime_calc["Virtual Runtime Calculation"] weight_lookup["Nice-to-Weight Lookup"] scheduling_decision["Scheduling Decision"] CFSTask --> delta CFSTask --> id CFSTask --> init_vruntime CFSTask --> inner CFSTask --> nice delta --> vruntime_calc init_vruntime --> vruntime_calc nice --> weight_lookup vruntime_calc --> scheduling_decision weight_lookup --> vruntime_calc
Sources: src/cfs.rs(L7 - L14) src/cfs.rs(L31 - L41)
CFScheduler Structure
The main scheduler maintains a BTreeMap
as the ready queue and tracks global minimum virtual runtime:
flowchart TD CFScheduler["CFScheduler<T>"] ready_queue["ready_queue: BTreeMap<(isize, isize), Arc<CFSTask<T>>>"] min_vruntime["min_vruntime: Option<AtomicIsize>"] id_pool["id_pool: AtomicIsize"] key_structure["Key: (vruntime, taskid)"] value_structure["Value: Arc<CFSTask<T>>"] ordering["BTreeMap Natural Ordering"] leftmost["Leftmost = Lowest vruntime"] pick_next["pick_next_task()"] CFScheduler --> id_pool CFScheduler --> min_vruntime CFScheduler --> ready_queue key_structure --> ordering leftmost --> pick_next ordering --> leftmost ready_queue --> key_structure ready_queue --> value_structure
Sources: src/cfs.rs(L103 - L107) src/cfs.rs(L109 - L122)
Virtual Runtime Calculation
The virtual runtime algorithm ensures fairness by adjusting how quickly tasks accumulate runtime based on their priority:
Nice-to-Weight Conversion
CFS uses Linux-compatible lookup tables to convert nice values to weights:
flowchart TD nice_value["Nice Value (-20 to 19)"] weight_check["nice >= 0?"] pos_table["NICE2WEIGHT_POS[nice]"] neg_table["NICE2WEIGHT_NEG[-nice]"] weight_result["Task Weight"] vruntime_formula["vruntime = init + delta * 1024 / weight"] neg_table --> weight_result nice_value --> weight_check pos_table --> weight_result weight_check --> neg_table weight_check --> pos_table weight_result --> vruntime_formula
The weight tables are defined as constants with nice value 0 corresponding to weight 1024:
- Positive nice values (0-19): Lower priority, higher weights
- Negative nice values (-20 to -1): Higher priority, lower weights
Sources: src/cfs.rs(L16 - L29) src/cfs.rs(L43 - L50)
Runtime Accumulation
flowchart TD task_tick["task_tick()"] delta_increment["delta.fetch_add(1)"] get_vruntime["get_vruntime()"] nice_check["nice == 0?"] simple_calc["init_vruntime + delta"] weighted_calc["init_vruntime + delta * 1024 / weight"] final_vruntime["Virtual Runtime"] btreemap_key["BTreeMap Key: (vruntime, id)"] final_vruntime --> btreemap_key get_vruntime --> nice_check nice_check --> simple_calc nice_check --> weighted_calc simple_calc --> final_vruntime task_tick --> delta_increment weighted_calc --> final_vruntime
Sources: src/cfs.rs(L56 - L63) src/cfs.rs(L83 - L85)
Scheduling Algorithm Flow
Task Addition and Removal
sequenceDiagram participant Kernel as Kernel participant CFScheduler as CFScheduler participant BTreeMap as BTreeMap participant CFSTask as CFSTask Kernel ->> CFScheduler: add_task(task) CFScheduler ->> CFScheduler: assign_id() CFScheduler ->> CFSTask: set_vruntime(min_vruntime) CFScheduler ->> CFSTask: set_id(taskid) CFScheduler ->> BTreeMap: insert((vruntime, taskid), task) CFScheduler ->> CFScheduler: update_min_vruntime() Note over CFScheduler,BTreeMap: Task ordered by virtual runtime Kernel ->> CFScheduler: pick_next_task() CFScheduler ->> BTreeMap: pop_first() BTreeMap -->> CFScheduler: leftmost task CFScheduler -->> Kernel: task with lowest vruntime
Sources: src/cfs.rs(L129 - L143) src/cfs.rs(L161 - L167)
Preemption Decision
The CFS scheduler makes preemption decisions in task_tick()
by comparing the current task's virtual runtime with the minimum virtual runtime in the ready queue:
flowchart TD task_tick_call["task_tick(current)"] increment_delta["current.task_tick()"] check_empty["ready_queue.is_empty()?"] no_preempt["return false"] compare_vruntime["current.vruntime > min_vruntime?"] should_preempt["return true"] continue_running["return false"] check_empty --> compare_vruntime check_empty --> no_preempt compare_vruntime --> continue_running compare_vruntime --> should_preempt increment_delta --> check_empty task_tick_call --> increment_delta
Sources: src/cfs.rs(L176 - L183)
Priority Management
CFS supports dynamic priority changes through the nice value interface:
flowchart TD set_priority["set_priority(task, nice)"] validate_range["nice in [-20, 19]?"] return_false["return false"] save_current["current_vruntime = task.get_vruntime()"] update_init["init_vruntime = current_vruntime"] reset_delta["delta = 0"] set_nice["nice = new_nice"] return_true["return true"] reset_delta --> set_nice save_current --> update_init set_nice --> return_true set_priority --> validate_range update_init --> reset_delta validate_range --> return_false validate_range --> save_current
This implementation preserves the task's current virtual runtime position while applying the new priority for future runtime accumulation.
Sources: src/cfs.rs(L69 - L77) src/cfs.rs(L185 - L192)
BaseScheduler Trait Integration
The CFScheduler
implements the BaseScheduler
trait with the following type and method mappings:
BaseScheduler Method | CFS Implementation | Behavior |
---|---|---|
SchedItem | Arc<CFSTask | Reference-counted task wrapper |
add_task() | Insert into BTreeMap | Assigns ID and initial vruntime |
remove_task() | Remove from BTreeMap | Updates min_vruntime tracking |
pick_next_task() | pop_first() | Selects leftmost (lowest vruntime) |
put_prev_task() | Re-insert with new ID | Maintains vruntime ordering |
task_tick() | Compare with min_vruntime | Preemption based on fairness |
set_priority() | Update nice value | Range validation and vruntime preservation |
Sources: src/cfs.rs(L124 - L193)
Thread Safety and Atomics
All CFS task metadata uses atomic operations for thread-safe access:
flowchart TD CFSTask_atomics["CFSTask Atomic Fields"] init_vruntime_atomic["init_vruntime: AtomicIsize"] delta_atomic["delta: AtomicIsize"] nice_atomic["nice: AtomicIsize"] id_atomic["id: AtomicIsize"] ordering_acquire["Ordering::Acquire/Release"] memory_safety["Memory-safe concurrent access"] CFSTask_atomics --> delta_atomic CFSTask_atomics --> id_atomic CFSTask_atomics --> init_vruntime_atomic CFSTask_atomics --> nice_atomic delta_atomic --> ordering_acquire id_atomic --> ordering_acquire init_vruntime_atomic --> ordering_acquire nice_atomic --> ordering_acquire ordering_acquire --> memory_safety
The scheduler itself is not thread-safe and requires external synchronization, but individual task metadata can be safely accessed concurrently.
Sources: src/cfs.rs(L10 - L13) src/cfs.rs(L44) src/cfs.rs(L57) src/cfs.rs(L66) src/cfs.rs(L76) src/cfs.rs(L84)
Round Robin Scheduler
Relevant source files
Purpose and Scope
This document covers the Round Robin scheduler implementation (RRScheduler
) in the scheduler crate, which provides preemptive scheduling based on time quantum allocation. The Round Robin scheduler ensures fair CPU time distribution among tasks by rotating execution using fixed time slices.
For information about the unified scheduler interface, see Core Architecture. For comparisons with other scheduling algorithms, see Scheduler Comparison.
Architecture Overview
The Round Robin scheduler consists of two main components: the RRTask
wrapper that adds time slice tracking to tasks, and the RRScheduler
that implements the preemptive scheduling logic using a FIFO queue with time quantum management.
Round Robin Scheduler Components
flowchart TD subgraph subGraph1["Round Robin Scheduler System"] RRScheduler["RRScheduler<T, MAX_TIME_SLICE>"] VecDeque["VecDeque<Arc<RRTask>>"] RRTask["RRTask<T, MAX_TIME_SLICE>"] AtomicIsize["AtomicIsize time_slice"] subgraph subGraph0["BaseScheduler Interface"] AddTask["add_task()"] RemoveTask["remove_task()"] PickNext["pick_next_task()"] PutPrev["put_prev_task()"] TaskTick["task_tick()"] SetPriority["set_priority()"] end end RRScheduler --> AddTask RRScheduler --> PickNext RRScheduler --> PutPrev RRScheduler --> RemoveTask RRScheduler --> SetPriority RRScheduler --> TaskTick RRScheduler --> VecDeque RRTask --> AtomicIsize VecDeque --> RRTask
Sources: src/round_robin.rs(L1 - L114)
RRTask Wrapper
The RRTask
wrapper extends any task type T
with time slice management capabilities. It maintains an atomic counter that tracks the remaining time slice for preemption decisions.
RRTask Structure and Methods
Method | Purpose | Atomicity |
---|---|---|
new(inner: T) | Creates wrapper with initial time slice set toMAX_TIME_SLICE | - |
time_slice() | Returns current time slice value | Acquireordering |
reset_time_slice() | Resets time slice toMAX_TIME_SLICE | Releaseordering |
inner() | Provides access to wrapped task | - |
The time slice counter is implemented as an AtomicIsize
to ensure thread-safe access during concurrent scheduler operations.
Sources: src/round_robin.rs(L7 - L44)
RRScheduler Implementation
The RRScheduler
implements the BaseScheduler
trait using a VecDeque
as the ready queue. This provides O(1) insertion and removal at both ends, but O(n) removal from arbitrary positions.
Scheduler Data Structure and Core Methods
flowchart TD subgraph subGraph2["RRScheduler Operations"] ReadyQueue["ready_queue: VecDeque"] subgraph subGraph1["Time Management"] TaskTick["task_tick()"] TimeSlice["time_slice counter"] end subgraph subGraph0["Task Management"] AddTask["add_task()"] PickNext["pick_next_task()"] PutPrev["put_prev_task()"] RemoveTask["remove_task()"] end end AddTask --> ReadyQueue PutPrev --> ReadyQueue ReadyQueue --> PickNext RemoveTask --> ReadyQueue TaskTick --> TimeSlice TimeSlice --> PutPrev
Key Implementation Details
- Ready Queue: Uses
VecDeque<Arc<RRTask<T, MAX_TIME_SLICE>>>
for task storage - Task Addition: New tasks are added to the back of the queue via
push_back()
- Task Selection: Next task is selected from the front via
pop_front()
- Task Removal: Requires O(n) search using
Arc::ptr_eq()
for pointer comparison - Priority Support: Not supported -
set_priority()
always returnsfalse
Sources: src/round_robin.rs(L58 - L113)
Time Slice Management
The Round Robin scheduler's core feature is its time quantum management system. Each task receives a fixed time slice that determines how long it can execute before being preempted.
Time Slice Lifecycle
flowchart TD subgraph subGraph0["put_prev_task() Logic"] PutPrev["put_prev_task(prev, preempt)"] CheckPreempt["preempt && time_slice > 0?"] QueueFront["push_front()"] ResetAndBack["reset_time_slice() + push_back()"] end TaskCreated["Task Created"] InitTimeSlice["time_slice = MAX_TIME_SLICE"] Running["Task Running"] TimerTick["Timer Tick Occurs"] DecrementSlice["time_slice--"] CheckSlice["time_slice <= 0?"] Preempted["Task Preempted"] ResetSlice["reset_time_slice()"] BackToQueue["Added to Queue Back"] BackToQueue --> QueueFront BackToQueue --> ResetAndBack CheckPreempt --> QueueFront CheckPreempt --> ResetAndBack CheckSlice --> Preempted CheckSlice --> Running DecrementSlice --> CheckSlice InitTimeSlice --> Running Preempted --> ResetSlice PutPrev --> CheckPreempt ResetSlice --> BackToQueue Running --> TimerTick TaskCreated --> InitTimeSlice TimerTick --> DecrementSlice
Timer Tick Implementation
The task_tick()
method implements the time slice decrement logic:
// From src/round_robin.rs:105-108
fn task_tick(&mut self, current: &Self::SchedItem) -> bool {
let old_slice = current.time_slice.fetch_sub(1, Ordering::Release);
old_slice <= 1
}
This atomic operation decrements the time slice and returns true
when the task should be preempted (when the slice reaches zero).
Sources: src/round_robin.rs(L105 - L108)
Scheduling Behavior
The Round Robin scheduler exhibits specific behavior patterns that distinguish it from other scheduling algorithms in the crate.
Task State Transitions
Scheduler Method | Action | Queue Position | Time Slice Action |
---|---|---|---|
add_task() | Add new task | Back of queue | Set toMAX_TIME_SLICE |
pick_next_task() | Select task | Remove from front | No change |
put_prev_task()(preempted, slice > 0) | Voluntary yield | Front of queue | No change |
put_prev_task()(preempted, slice = 0) | Time expired | Back of queue | Reset toMAX_TIME_SLICE |
put_prev_task()(not preempted) | Cooperative yield | Back of queue | Reset toMAX_TIME_SLICE |
Preemption Logic
The scheduler distinguishes between voluntary and involuntary preemption in its put_prev_task()
implementation:
- Voluntary Preemption: Task still has time slice remaining and yielded voluntarily - placed at front of queue
- Time Expiration: Task's time slice reached zero - time slice reset and task placed at back of queue
- Cooperative Yield: Task not preempted (blocking I/O, etc.) - time slice reset and task placed at back of queue
Sources: src/round_robin.rs(L96 - L103)
Performance Characteristics
Computational Complexity
Operation | Time Complexity | Space Complexity | Notes |
---|---|---|---|
add_task() | O(1) | O(1) | VecDeque::push_back() |
pick_next_task() | O(1) | O(1) | VecDeque::pop_front() |
remove_task() | O(n) | O(1) | Linear search withArc::ptr_eq() |
put_prev_task() | O(1) | O(1) | VecDeque::push_front()orpush_back() |
task_tick() | O(1) | O(1) | Atomic decrement operation |
Design Tradeoffs
- Fairness: Provides time-based fairness through fixed time slices
- Preemption: Supports timer-based preemption unlike FIFO scheduler
- Simplicity: Simpler than CFS but more complex than FIFO
- Performance: O(n) task removal limits scalability for workloads with frequent task removal
- Priority: No priority support - all tasks receive equal time allocation
Sources: src/round_robin.rs(L84 - L90) src/round_robin.rs(L110 - L112)
Scheduler Comparison
Relevant source files
This document compares the three scheduler implementations provided by the scheduler crate: FIFO, Round Robin, and Completely Fair Scheduler (CFS). The comparison covers their scheduling algorithms, data structures, performance characteristics, and appropriate use cases.
For detailed implementation specifics of each scheduler, see FIFO Scheduler, Round Robin Scheduler, and Completely Fair Scheduler.
Scheduler Overview
The scheduler crate provides three distinct scheduling algorithms, each implementing the BaseScheduler
trait but with fundamentally different approaches to task management and execution ordering.
Scheduler | Type | Data Structure | Preemption | Priority Support | Complexity |
---|---|---|---|---|---|
FifoScheduler | Cooperative | List<Arc<FifoTask | None | None | O(1) add/pick, O(n) remove |
RRScheduler | Preemptive | VecDeque<Arc<RRTask<T, S>>> | Time-based | None | O(1) add/pick, O(n) remove |
CFScheduler | Preemptive | BTreeMap<(isize, isize), Arc<CFSTask | Virtual runtime | Nice values (-20 to 19) | O(log n) all operations |
Sources: src/fifo.rs(L23 - L25) src/round_robin.rs(L58 - L60) src/cfs.rs(L103 - L107)
Data Structure Comparison
Ready Queue Implementation Comparison
flowchart TD subgraph subGraph2["CFScheduler Data Flow"] C1["BTreeMap<(isize, isize), Arc>>"] C2["insert((vruntime, taskid))"] C3["pop_first()"] C5["Virtual Runtime Ordering"] B1["VecDeque>>"] B2["push_back()"] B3["pop_front()"] B5["Time Slice Management"] A1["List>>"] A2["push_back()"] A3["pop_front()"] A4["FIFO Order"] subgraph subGraph1["RRScheduler Data Flow"] C4["remove_entry()"] B4["remove(idx)"] subgraph subGraph0["FifoScheduler Data Flow"] C1["BTreeMap<(isize, isize), Arc>>"] C2["insert((vruntime, taskid))"] C3["pop_first()"] C5["Virtual Runtime Ordering"] B1["VecDeque>>"] B2["push_back()"] B3["pop_front()"] B5["Time Slice Management"] A1["List>>"] A2["push_back()"] A3["pop_front()"] A4["FIFO Order"] end end end A1 --> A2 A1 --> A3 A2 --> A4 A3 --> A4 B1 --> B2 B1 --> B3 B1 --> B4 B2 --> B5 B3 --> B5 B4 --> B5 C1 --> C2 C1 --> C3 C1 --> C4 C2 --> C5 C3 --> C5 C4 --> C5
Sources: src/fifo.rs(L46 - L47) src/round_robin.rs(L81 - L82) src/cfs.rs(L137)
Task Wrapper Feature Comparison
flowchart TD subgraph CFSTask<T>["CFSTask"] C1["inner: T"] C2["init_vruntime: AtomicIsize"] C4["nice: AtomicIsize"] C5["id: AtomicIsize"] R1["inner: T"] R2["time_slice: AtomicIsize"] F1["inner: T"] F2["No additional state"] subgraph subGraph1["RRTask"] C3["delta: AtomicIsize"] R3["MAX_TIME_SLICE: const"] subgraph FifoTask<T>["FifoTask"] C1["inner: T"] C2["init_vruntime: AtomicIsize"] R1["inner: T"] R2["time_slice: AtomicIsize"] F1["inner: T"] F2["No additional state"] end end end
Sources: src/fifo.rs(L7 - L12) src/round_robin.rs(L10 - L13) src/cfs.rs(L8 - L14)
Scheduling Behavior Analysis
Preemption and Time Management
Method | FifoScheduler | RRScheduler | CFScheduler |
---|---|---|---|
task_tick() | Always returnsfalse | Decrements time slice, returnstruewhen<= 1 | Incrementsdelta, compares withmin_vruntime |
put_prev_task() | Always re-queues at back | Conditional placement based on preemption and time slice | Re-inserts with updatedvruntime |
Preemption Logic | None (cooperative) | old_slice <= 1 | current.get_vruntime() > min_vruntime |
Sources: src/fifo.rs(L61 - L63) src/round_robin.rs(L105 - L108) src/cfs.rs(L176 - L183)
Priority and Weight System
flowchart TD subgraph subGraph2["CFS Priority Handling"] C1["set_priority(nice)"] C2["Range check: -20 <= nice <= 19"] C3["NICE2WEIGHT_POS/NEG lookup"] C4["Virtual runtime adjustment"] C5["Weight-based scheduling"] R1["set_priority()"] R2["return false"] F1["set_priority()"] F2["return false"] end C1 --> C2 C2 --> C3 C3 --> C4 C4 --> C5 F1 --> F2 R1 --> R2
Sources: src/fifo.rs(L65 - L67) src/round_robin.rs(L110 - L112) src/cfs.rs(L185 - L192)
Performance Characteristics
Algorithmic Complexity
Operation | FifoScheduler | RRScheduler | CFScheduler |
---|---|---|---|
add_task() | O(1) -push_back() | O(1) -push_back() | O(log n) -BTreeMap::insert() |
remove_task() | O(n) - linked list traversal | O(n) -VecDequesearch | O(log n) -BTreeMap::remove_entry() |
pick_next_task() | O(1) -pop_front() | O(1) -pop_front() | O(log n) -BTreeMap::pop_first() |
put_prev_task() | O(1) -push_back() | O(1) - conditional placement | O(log n) -BTreeMap::insert() |
Sources: src/fifo.rs(L45 - L58) src/round_robin.rs(L80 - L102) src/cfs.rs(L129 - L174)
Memory Overhead
flowchart TD subgraph subGraph1["Scheduler State"] subgraph subGraph0["Memory per Task"] D["FifoScheduler: List head/tail pointers"] E["RRScheduler: VecDeque capacity + length"] F["CFScheduler: BTreeMap tree + AtomicIsize + AtomicIsize"] A["FifoTask: sizeof(T) + linked list pointers"] B["RRTask: sizeof(T) + AtomicIsize"] C["CFSTask: sizeof(T) + 4 × AtomicIsize"] end end
Sources: src/fifo.rs(L7 - L12) src/round_robin.rs(L10 - L13) src/cfs.rs(L8 - L14) src/cfs.rs(L103 - L107)
Virtual Runtime Calculation in CFS
The CFS implementation uses a sophisticated virtual runtime system based on Linux CFS:
flowchart TD A["task_tick()"] B["delta.fetch_add(1)"] C["get_vruntime()"] D["nice == 0?"] E["vruntime = init_vruntime + delta"] F["vruntime = init_vruntime + delta * 1024 / weight"] G["weight = NICE2WEIGHT_POS/NEG[nice]"] H["Compare with min_vruntime"] I["Preemption decision"] A --> B B --> C C --> D D --> E D --> F E --> H F --> G F --> H H --> I
Sources: src/cfs.rs(L56 - L63) src/cfs.rs(L83 - L85) src/cfs.rs(L23 - L29)
Use Case Recommendations
FifoScheduler
Best for:
- Embedded systems with predictable workloads
- Cooperative multitasking environments
- Systems where task completion order matters
- Low-overhead requirements
Limitations:
- No fairness guarantees
- No preemption support
- Potential for task starvation
RRScheduler
Best for:
- Interactive systems requiring responsiveness
- Equal priority tasks
- Time-sharing systems
- Simple preemptive scheduling needs
Limitations:
- No priority differentiation
- Fixed time quantum may not suit all workloads
- O(n) task removal cost
CFScheduler
Best for:
- Multi-user systems
- Priority-aware workloads
- Fair resource allocation requirements
- Linux-compatible scheduling behavior
Limitations:
- Higher computational overhead
- More complex implementation
- Memory overhead from priority tracking
Sources: src/fifo.rs(L14 - L22) src/round_robin.rs(L46 - L56) src/cfs.rs(L100 - L102)
Testing Framework
Relevant source files
The testing framework provides a unified, macro-based testing infrastructure that ensures consistent validation and performance measurement across all scheduler implementations in the crate. The framework automatically generates comprehensive test suites for each scheduler type, including basic functionality verification and performance benchmarks.
For information about the specific scheduler implementations being tested, see Scheduler Implementations. For details about the development workflow and CI pipeline that executes these tests, see Development Guide.
Macro-Based Test Generation
The testing framework is built around the def_test_sched
macro, which generates a complete test module for any given scheduler and task type combination. This approach ensures that all scheduler implementations are tested consistently with identical test logic.
Test Generation Architecture
flowchart TD MacroDefinition["def_test_sched macro"] MacroParams["Parameters: name, scheduler, task"] ModuleGeneration["Generated Test Module"] BasicTest["test_sched()"] YieldBench["bench_yield()"] RemoveBench["bench_remove()"] MacroInvocations["Macro Invocations"] FifoTests["fifo module"] RRTests["rr module"] CFSTests["cfs module"] FifoScheduler["FifoScheduler"] FifoTask["FifoTask"] RRScheduler["RRScheduler"] RRTask["RRTask"] CFScheduler["CFScheduler"] CFSTask["CFSTask"] CFSTests --> CFSTask CFSTests --> CFScheduler FifoTests --> FifoScheduler FifoTests --> FifoTask MacroDefinition --> MacroParams MacroInvocations --> CFSTests MacroInvocations --> FifoTests MacroInvocations --> RRTests MacroParams --> ModuleGeneration ModuleGeneration --> BasicTest ModuleGeneration --> RemoveBench ModuleGeneration --> YieldBench RRTests --> RRScheduler RRTests --> RRTask
Sources: src/tests.rs(L1 - L84)
The macro accepts three parameters: a module name identifier, a scheduler type, and a task wrapper type. It then generates a complete test module containing three distinct test functions that exercise different aspects of scheduler behavior.
Test Types
The framework provides three categories of tests, each targeting specific scheduler capabilities and performance characteristics.
Basic Functionality Tests
The test_sched
function verifies core scheduler operations using a controlled set of 11 tasks. This test validates the fundamental scheduler contract defined by the BaseScheduler
trait.
Test Execution Flow
flowchart TD TestStart["test_sched() begins"] CreateScheduler["scheduler = Scheduler::new()"] AddTasks["Add 11 tasks (i = 0..10)"] SchedulingLoop["Execute 109 scheduling cycles"] PickNext["pick_next_task()"] ValidateTask["assert_eq!(*next.inner(), i % 11)"] TaskTick["task_tick(&next)"] PutPrev["put_prev_task(next, false)"] NextIteration["i < 109?"] DrainScheduler["Drain remaining tasks"] CountTasks["Count remaining tasks"] ValidateCount["assert_eq!(n, 11)"] TestComplete["Test Complete"] AddTasks --> SchedulingLoop CountTasks --> ValidateCount CreateScheduler --> AddTasks DrainScheduler --> CountTasks NextIteration --> DrainScheduler NextIteration --> PickNext PickNext --> ValidateTask PutPrev --> NextIteration SchedulingLoop --> PickNext TaskTick --> PutPrev TestStart --> CreateScheduler ValidateCount --> TestComplete ValidateTask --> TaskTick
Sources: src/tests.rs(L8 - L29)
The test performs multiple scheduling rounds to verify that tasks are selected in the expected order based on each scheduler's algorithm. The task_tick
call ensures proper time accounting for time-based schedulers like Round Robin and CFS.
Performance Benchmarks
The framework includes two performance benchmarks that measure different scheduler operations under load.
Yield Benchmark
The bench_yield
function measures task switching performance by repeatedly picking and yielding tasks in a large task set.
Parameter | Value | Purpose |
---|---|---|
NUM_TASKS | 1,000,000 | Large task set for realistic load testing |
COUNT | 3,000,000 | Number of yield operations (3x tasks) |
Measurement | Time per yield operation | Scheduler switching overhead |
flowchart TD BenchSetup["Setup 1M tasks"] BenchLoop["3M yield cycles"] PickTask["pick_next_task()"] PutTask["put_prev_task(task, false)"] Measure["Measure elapsed time"] Result["Time per yield operation"] BenchLoop --> PickTask BenchSetup --> BenchLoop Measure --> Result PickTask --> PutTask PutTask --> Measure
Sources: src/tests.rs(L32 - L52)
Remove Benchmark
The bench_remove
function measures task removal performance by removing tasks from the scheduler in reverse order.
Parameter | Value | Purpose |
---|---|---|
NUM_TASKS | 10,000 | Moderate task set for removal testing |
Removal Order | Reverse (9999→0) | Worst-case removal pattern |
Measurement | Time per removal | Task lookup and removal overhead |
Sources: src/tests.rs(L54 - L77)
Test Application Across Schedulers
The framework applies identical test logic to all three scheduler implementations through macro invocations, ensuring consistent validation while accommodating scheduler-specific type parameters.
Scheduler Test Instantiation
flowchart TD MacroExpansion["def_test_sched macro expansion"] TestModules["Generated Test Modules"] FifoMod["mod fifo"] RRMod["mod rr"] CFSMod["mod cfs"] FifoTypes["FifoScheduler, FifoTask"] RRTypes["RRScheduler, RRTask"] CFSTypes["CFScheduler, CFSTask"] FifoTests["fifo::test_sched(), fifo::bench_yield(), fifo::bench_remove()"] RRTests["rr::test_sched(), rr::bench_yield(), rr::bench_remove()"] CFSTests["cfs::test_sched(), cfs::bench_yield(), cfs::bench_remove()"] CFSMod --> CFSTypes CFSTypes --> CFSTests FifoMod --> FifoTypes FifoTypes --> FifoTests MacroExpansion --> TestModules RRMod --> RRTypes RRTypes --> RRTests TestModules --> CFSMod TestModules --> FifoMod TestModules --> RRMod
Sources: src/tests.rs(L82 - L84)
Each scheduler implementation receives type-appropriate parameters:
- FIFO: Simple
usize
task payload withFifoScheduler
andFifoTask
- Round Robin:
usize
payload with time slice constant5
forRRScheduler<usize, 5>
andRRTask<usize, 5>
- CFS:
usize
payload withCFScheduler
andCFSTask
for virtual runtime scheduling
Performance Measurement
The benchmarks provide quantitative performance data for comparing scheduler implementations. The framework measures operation latency using std::time::Instant
and reports results in time per operation.
Benchmark Output Format
The benchmarks output performance metrics in a standardized format:
- Yield Benchmark:
"{scheduler_name}: task yield speed: {duration}/task"
- Remove Benchmark:
"{scheduler_name}: task remove speed: {duration}/task"
This consistent reporting enables direct performance comparison between scheduler algorithms and helps identify performance characteristics specific to each implementation.
Sources: src/tests.rs(L47 - L51) src/tests.rs(L72 - L76)
The testing framework ensures that all scheduler implementations meet the same functional requirements while providing objective performance data to guide scheduler selection for specific use cases.
Development Guide
Relevant source files
Purpose and Scope
This guide covers the development workflow, build system, and continuous integration pipeline for the scheduler crate. It provides instructions for contributors on setting up the development environment, running tests, and understanding the automated quality assurance processes.
For information about the testing framework and test structure, see Testing Framework. For details about the scheduler implementations themselves, see Scheduler Implementations.
Development Workflow
The scheduler crate follows a standard Rust development workflow with automated quality checks enforced through GitHub Actions.
Development Workflow Overview
flowchart TD Developer["Developer"] LocalDev["Local Development"] CodeChanges["Code Changes"] FormatCheck["cargo fmt --check"] ClippyCheck["cargo clippy"] LocalTest["cargo test"] Commit["Git Commit"] Push["Git Push"] GitHub["GitHub Repository"] CITrigger["CI Pipeline Triggered"] MultiTarget["Multi-Target Build Matrix"] FormatJob["Format Check Job"] ClippyJob["Clippy Job"] BuildJob["Build Job"] TestJob["Unit Test Job"] DocJob["Documentation Job"] CIResult["CI Results"] Success["Success: Deploy Docs"] Failure["Failure: Fix Issues"] Merge["Merge to Main"] BuildJob --> CIResult CIResult --> Failure CIResult --> Success CITrigger --> MultiTarget ClippyCheck --> LocalTest ClippyJob --> CIResult CodeChanges --> FormatCheck Commit --> Push Developer --> LocalDev DocJob --> CIResult Failure --> CodeChanges FormatCheck --> ClippyCheck FormatJob --> CIResult GitHub --> CITrigger LocalDev --> CodeChanges LocalTest --> Commit MultiTarget --> BuildJob MultiTarget --> ClippyJob MultiTarget --> DocJob MultiTarget --> FormatJob MultiTarget --> TestJob Push --> GitHub Success --> Merge TestJob --> CIResult
Sources: .github/workflows/ci.yml(L1 - L56)
Local Development Setup
The project requires Rust nightly toolchain with specific components and target support:
Component | Purpose |
---|---|
rust-src | Source code for cross-compilation |
clippy | Linting and code analysis |
rustfmt | Code formatting |
Target architectures that must be installed locally:
x86_64-unknown-linux-gnu
- Linux development and testingx86_64-unknown-none
- Bare metal x86_64riscv64gc-unknown-none-elf
- RISC-V bare metalaarch64-unknown-none-softfloat
- ARM64 bare metal
Sources: .github/workflows/ci.yml(L15 - L19)
Continuous Integration Pipeline
The CI system uses GitHub Actions with a comprehensive test matrix covering multiple target architectures and quality checks.
CI Pipeline Architecture
flowchart TD subgraph subGraph4["Documentation Pipeline"] DocBuild["cargo doc --no-deps --all-features"] IndexGeneration["Generate index.html redirect"] GitHubPages["Deploy to gh-pages branch"] end subgraph subGraph3["Quality Checks"] VersionCheck["rustc --version --verbose"] FormatCheck["cargo fmt --all -- --check"] ClippyLint["cargo clippy --target TARGET --all-features"] BuildStep["cargo build --target TARGET --all-features"] UnitTest["cargo test --target x86_64-unknown-linux-gnu"] end subgraph subGraph2["CI Job Matrix"] JobMatrix["CI Job Matrix"] RustNightly["rust-toolchain: nightly"] subgraph subGraph1["Target Architectures"] LinuxTarget["x86_64-unknown-linux-gnu"] BareMetalTarget["x86_64-unknown-none"] RiscvTarget["riscv64gc-unknown-none-elf"] ArmTarget["aarch64-unknown-none-softfloat"] end end subgraph subGraph0["GitHub Events"] PushEvent["push"] PREvent["pull_request"] end ArmTarget --> VersionCheck BareMetalTarget --> VersionCheck BuildStep --> UnitTest ClippyLint --> BuildStep DocBuild --> IndexGeneration FormatCheck --> ClippyLint IndexGeneration --> GitHubPages JobMatrix --> DocBuild JobMatrix --> RustNightly LinuxTarget --> VersionCheck PREvent --> JobMatrix PushEvent --> JobMatrix RiscvTarget --> VersionCheck RustNightly --> ArmTarget RustNightly --> BareMetalTarget RustNightly --> LinuxTarget RustNightly --> RiscvTarget VersionCheck --> FormatCheck
Sources: .github/workflows/ci.yml(L1 - L56)
CI Configuration Details
The CI pipeline consists of two main jobs defined in .github/workflows/ci.yml(L6 - L56) :
Main CI Job (ci)
- Trigger: Push and pull request events .github/workflows/ci.yml(L3)
- Matrix Strategy: Tests against all target architectures .github/workflows/ci.yml(L10 - L12)
- Rust Toolchain: Nightly with required components .github/workflows/ci.yml(L17 - L18)
- Quality Gates:
- Code formatting verification .github/workflows/ci.yml(L23)
- Clippy linting with custom allow rules .github/workflows/ci.yml(L25)
- Cross-compilation builds .github/workflows/ci.yml(L27)
- Unit tests (Linux target only) .github/workflows/ci.yml(L29 - L30)
Documentation Job (doc)
- Environment: Dedicated permissions for GitHub Pages deployment .github/workflows/ci.yml(L36 - L37)
- Rustdoc Flags: Strict documentation requirements .github/workflows/ci.yml(L40)
- Deployment: Automatic deployment to
gh-pages
branch on main branch pushes .github/workflows/ci.yml(L49 - L55)
Build System
Target Architecture Support
The scheduler crate supports four distinct target architectures, each serving different deployment scenarios:
Target | Environment | Use Case |
---|---|---|
x86_64-unknown-linux-gnu | Hosted Linux | Development and testing |
x86_64-unknown-none | Bare metal x86_64 | ArceOS deployment |
riscv64gc-unknown-none-elf | Bare metal RISC-V | Embedded ArceOS |
aarch64-unknown-none-softfloat | Bare metal ARM64 | ARM-based systems |
Sources: .github/workflows/ci.yml(L12)
Build Process Flow
flowchart TD subgraph subGraph2["Target Outputs"] LinuxBinary["Linux binary"] BareMetalLib["Bare metal library"] RiscvLib["RISC-V library"] ArmLib["ARM64 library"] end subgraph subGraph1["Build Process"] CargoCheck["cargo check"] CargoClippy["cargo clippy"] CargoBuild["cargo build"] CargoTest["cargo test"] end subgraph subGraph0["Source Code"] SourceFiles["src/ directory"] CargoToml["Cargo.toml"] end subgraph subGraph3["Excluded Files"] GitIgnore[".gitignore"] TargetDir["/target"] VSCodeDir["/.vscode"] CargoLock["Cargo.lock"] end CargoBuild --> ArmLib CargoBuild --> BareMetalLib CargoBuild --> CargoTest CargoBuild --> LinuxBinary CargoBuild --> RiscvLib CargoCheck --> CargoClippy CargoClippy --> CargoBuild CargoToml --> CargoCheck GitIgnore --> CargoLock GitIgnore --> TargetDir GitIgnore --> VSCodeDir SourceFiles --> CargoCheck
Sources: .github/workflows/ci.yml(L25 - L27) .gitignore(L1 - L4)
Testing Procedures
Local Testing Commands
For comprehensive local testing, developers should run the following commands:
# Format check
cargo fmt --all -- --check
# Linting
cargo clippy --all-features -- -A clippy::new_without_default
# Build for all targets
cargo build --target x86_64-unknown-linux-gnu --all-features
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features
# Unit tests (Linux only)
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
Sources: .github/workflows/ci.yml(L23 - L30)
Test Execution Strategy
The testing framework uses a conditional execution model:
- Format and Linting: Applied to all targets
- Build Verification: Cross-compiled for all supported architectures
- Unit Test Execution: Limited to
x86_64-unknown-linux-gnu
target for practical execution
This approach ensures code quality across all platforms while maintaining efficient CI execution times.
Sources: .github/workflows/ci.yml(L29 - L30)
Documentation System
Documentation Generation Process
flowchart TD subgraph subGraph3["Quality Controls"] BrokenLinks["RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links"] MissingDocs["RUSTDOCFLAGS: -D missing-docs"] end subgraph Deployment["Deployment"] GitHubPages["GitHub Pages"] GhPagesBranch["gh-pages branch"] MainBranchCheck["main branch check"] end subgraph subGraph1["Build Process"] CargoDoc["cargo doc --no-deps --all-features"] IndexGen["Generate index.html redirect"] TargetDoc["target/doc/ output"] end subgraph subGraph0["Documentation Sources"] RustSource["Rust source files"] DocComments["/// doc comments"] CargoToml["Cargo.toml metadata"] end BrokenLinks --> CargoDoc CargoDoc --> IndexGen CargoToml --> CargoDoc DocComments --> CargoDoc GhPagesBranch --> GitHubPages IndexGen --> TargetDoc MainBranchCheck --> GhPagesBranch MissingDocs --> CargoDoc RustSource --> CargoDoc TargetDoc --> MainBranchCheck
Sources: .github/workflows/ci.yml(L40 - L55)
Documentation Deployment
The documentation system enforces strict quality requirements through rustdoc flags:
- Broken intra-documentation links are treated as errors .github/workflows/ci.yml(L40)
- Missing documentation comments trigger build failures .github/workflows/ci.yml(L40)
Deployment occurs automatically when changes are pushed to the main branch, using the JamesIves/github-pages-deploy-action
for single-commit deployment to the gh-pages
branch .github/workflows/ci.yml(L51 - L55)
Sources: .github/workflows/ci.yml(L32 - L55)
Overview
Relevant source files
This document provides an introduction to the tuple_for_each
crate, a Rust procedural macro library that generates iteration utilities for tuple structs. The crate enables developers to iterate over tuple struct fields in a type-safe manner through automatically generated macros and methods.
For implementation details, see Implementation Guide. For usage examples and getting started, see Getting Started. For complete API documentation, see API Reference.
Purpose and Core Functionality
The tuple_for_each
crate addresses the limitation that Rust tuple structs cannot be iterated over directly since their fields may have different types. It provides a TupleForEach
derive macro that automatically generates iteration utilities at compile time.
When applied to a tuple struct, the derive macro generates:
*_for_each!
macro for field iteration*_enumerate!
macro for indexed field iterationlen()
method returning the number of fieldsis_empty()
method checking if the tuple has fields
Core Architecture Overview
flowchart TD subgraph Dependencies["Proc Macro Dependencies"] SynCrate["syn crate - AST parsing"] QuoteCrate["quote crate - code generation"] ProcMacro2["proc-macro2 - token streams"] end subgraph Generated["Generated Code"] ForEachMacro["*_for_each! macro"] EnumerateMacro["*_enumerate! macro"] LenMethod["len() method"] IsEmptyMethod["is_empty() method"] end UserTuple["User Tuple Struct with #[derive(TupleForEach)]"] DeriveMacro["tuple_for_each() entry point"] ImplForEach["impl_for_each() core logic"] DeriveMacro --> ImplForEach ImplForEach --> EnumerateMacro ImplForEach --> ForEachMacro ImplForEach --> IsEmptyMethod ImplForEach --> LenMethod ImplForEach --> ProcMacro2 ImplForEach --> QuoteCrate ImplForEach --> SynCrate UserTuple --> DeriveMacro
Sources: Cargo.toml(L1 - L21) README.md(L1 - L40)
Macro Processing Pipeline
The crate follows a standard procedural macro architecture where compile-time code generation produces runtime utilities. The process transforms user-defined tuple structs into enhanced types with iteration capabilities.
Compilation Flow
flowchart TD subgraph ErrorHandling["Error Cases"] InvalidStruct["Invalid tuple struct"] CompileError["Compile-time error"] end Input["TokenStream input from #[derive(TupleForEach)]"] ParseInput["syn::parse_macro_input!()"] Validation["Validate tuple struct format"] NameConversion["pascal_to_snake() conversion"] FieldAccess["Generate field access patterns"] MacroGeneration["Create macro_rules! definitions"] QuoteExpansion["quote! macro expansion"] Output["Generated TokenStream output"] FieldAccess --> MacroGeneration Input --> ParseInput InvalidStruct --> CompileError MacroGeneration --> QuoteExpansion NameConversion --> FieldAccess ParseInput --> Validation QuoteExpansion --> Output Validation --> InvalidStruct Validation --> NameConversion
Sources: Cargo.toml(L14 - L17) README.md(L20 - L39)
Generated Code Structure
For each tuple struct, the macro generates a consistent set of utilities following a naming convention based on the struct name converted from PascalCase to snake_case.
Generated Item | Purpose | Example forFooBarstruct |
---|---|---|
Iteration macro | Field-by-field iteration | foo_bar_for_each!(x in tuple { ... }) |
Enumeration macro | Indexed field iteration | foo_bar_enumerate!((i, x) in tuple { ... }) |
Length method | Field count | tuple.len() |
Empty check method | Zero-field detection | tuple.is_empty() |
The generated macros support both immutable and mutable access patterns, enabling flexible usage across different contexts.
Sources: README.md(L9 - L16) README.md(L30 - L38)
Target Use Cases
The crate is designed for systems programming contexts where tuple structs represent heterogeneous data collections that need iteration capabilities. The multi-target build configuration supports embedded and cross-platform development.
Supported Target Architectures:
x86_64-unknown-linux-gnu
(full testing)x86_64-unknown-none
(build verification)riscv64gc-unknown-none-elf
(embedded RISC-V)aarch64-unknown-none-softfloat
(embedded ARM)
The no_std
compatibility and embedded target support indicate the crate is suitable for resource-constrained environments where compile-time code generation provides runtime efficiency benefits.
Sources: Cargo.toml(L1 - L21) README.md(L1 - L7)
Dependencies and Ecosystem Integration
The crate follows standard Rust ecosystem patterns and integrates with the procedural macro infrastructure:
- proc-macro2: Token stream manipulation and procedural macro utilities
- quote: Template-based code generation with compile-time expansion
- syn: Abstract syntax tree parsing and manipulation
The crate is configured as a procedural macro library through proc-macro = true
in the [lib]
section, making it available for use with #[derive()]
attributes.
Sources: Cargo.toml(L14 - L21)
Project Structure
Relevant source files
This document describes the architectural organization of the tuple_for_each
crate, including its dependencies, build configuration, and development infrastructure. The project is a procedural macro library designed to generate iteration utilities for tuple structs, with particular emphasis on cross-platform compatibility for embedded systems development.
For details about the core macro implementation, see 3.1. For information about the CI/CD pipeline specifics, see 4.2.
Crate Architecture
The tuple_for_each
crate follows a standard Rust procedural macro library structure, with the core implementation residing in src/lib.rs
and supporting infrastructure for multi-platform builds and documentation.
Crate Configuration
flowchart TD subgraph subGraph3["ArceOS Ecosystem"] HOMEPAGE["Homepage: arceos-org/arceos"] REPO["Repository: tuple_for_each"] DOCS["Documentation: docs.rs"] end subgraph Dependencies["Dependencies"] SYN["syn = '2.0'"] QUOTE["quote = '1.0'"] PM2["proc-macro2 = '1.0'"] end subgraph subGraph1["Crate Type"] PROC["proc-macro = true"] LIB["[lib] Configuration"] end subgraph subGraph0["Package Metadata"] PM["tuple_for_eachv0.1.0Edition 2021"] AUTH["Author: Yuekai Jia"] LIC["Triple License:GPL-3.0 | Apache-2.0 | MulanPSL-2.0"] end HOMEPAGE --> REPO LIB --> PM2 LIB --> QUOTE LIB --> SYN PM --> HOMEPAGE PM --> PROC PROC --> LIB REPO --> DOCS
Crate Configuration Details
The crate is configured as a procedural macro library through the proc-macro = true
setting in Cargo.toml(L19 - L20) This enables the crate to export procedural macros that operate at compile time. The package metadata indicates this is part of the ArceOS project ecosystem, focusing on systems programming and embedded development.
Sources: Cargo.toml(L1 - L21)
Dependency Architecture
flowchart TD subgraph subGraph2["Generated Artifacts"] MACROS["_for_each!_enumerate!"] METHODS["len()is_empty()"] end subgraph subGraph1["Procedural Macro Stack"] SYN_DEP["syn 2.0AST Parsing"] QUOTE_DEP["quote 1.0Code Generation"] PM2_DEP["proc-macro2 1.0Token Management"] end subgraph subGraph0["tuple_for_each Crate"] DERIVE["TupleForEachDerive Macro"] IMPL["impl_for_each()Core Logic"] end DERIVE --> PM2_DEP DERIVE --> QUOTE_DEP DERIVE --> SYN_DEP IMPL --> QUOTE_DEP IMPL --> SYN_DEP PM2_DEP --> QUOTE_DEP QUOTE_DEP --> MACROS QUOTE_DEP --> METHODS SYN_DEP --> IMPL
Dependency Roles
Dependency | Version | Purpose |
---|---|---|
syn | 2.0 | Parsing Rust syntax trees from macro input tokens |
quote | 1.0 | Generating Rust code from templates and interpolation |
proc-macro2 | 1.0 | Low-level token stream manipulation and span handling |
The dependency selection follows Rust procedural macro best practices, using the latest stable versions of the core macro development libraries.
Sources: Cargo.toml(L14 - L17)
Build Matrix and Target Support
Multi-Platform Build Configuration
flowchart TD subgraph subGraph2["Build Strategy"] FULL_TEST["Full CI PipelineFormat + Lint + Build + Test"] BUILD_ONLY["Build VerificationFormat + Lint + Build"] end subgraph subGraph1["Target Platforms"] X86_LINUX["x86_64-unknown-linux-gnuDevelopment & Testing"] X86_NONE["x86_64-unknown-noneBare Metal x86"] RISCV["riscv64gc-unknown-none-elfRISC-V Embedded"] ARM["aarch64-unknown-none-softfloatARM64 Embedded"] end subgraph subGraph0["Rust Toolchain"] NIGHTLY["nightlyRequired Toolchain"] COMPONENTS["Components:rust-src, clippy, rustfmt"] end ARM --> BUILD_ONLY COMPONENTS --> ARM COMPONENTS --> RISCV COMPONENTS --> X86_LINUX COMPONENTS --> X86_NONE NIGHTLY --> COMPONENTS RISCV --> BUILD_ONLY X86_LINUX --> FULL_TEST X86_NONE --> BUILD_ONLY
Target Platform Strategy
The build matrix demonstrates the crate's focus on embedded and systems programming:
- Primary Development:
x86_64-unknown-linux-gnu
with full testing support - Bare Metal x86:
x86_64-unknown-none
for bootloader and kernel development - RISC-V Embedded:
riscv64gc-unknown-none-elf
for RISC-V microcontrollers - ARM64 Embedded:
aarch64-unknown-none-softfloat
for ARM embedded systems
Testing is restricted to the Linux target due to the embedded nature of other platforms, which typically lack standard library support required for test execution.
Sources: .github/workflows/ci.yml(L10 - L12) .github/workflows/ci.yml(L28 - L30)
Development Infrastructure
CI/CD Pipeline Architecture
flowchart TD subgraph subGraph3["Documentation Pipeline"] DOC_BUILD["cargo doc --no-deps"] REDIRECT["Generate index.html redirect"] DEPLOY["GitHub Pages deployment"] end subgraph subGraph2["CI Pipeline Steps"] CHECKOUT["actions/checkout@v4"] TOOLCHAIN["dtolnay/rust-toolchain@nightly"] FORMAT["cargo fmt --check"] CLIPPY["cargo clippy"] BUILD["cargo build"] TEST["cargo test(x86_64-linux only)"] end subgraph subGraph1["CI Job Matrix"] CI_JOB["ci jobMulti-target builds"] DOC_JOB["doc jobDocumentation"] end subgraph subGraph0["Trigger Events"] PUSH["git push"] PR["pull_request"] end BUILD --> TEST CHECKOUT --> TOOLCHAIN CI_JOB --> CHECKOUT CLIPPY --> BUILD DOC_BUILD --> REDIRECT DOC_JOB --> DOC_BUILD FORMAT --> CLIPPY PR --> CI_JOB PUSH --> CI_JOB PUSH --> DOC_JOB REDIRECT --> DEPLOY TOOLCHAIN --> FORMAT
Quality Assurance Steps
The CI pipeline enforces code quality through multiple verification stages:
- Format Checking:
cargo fmt --all -- --check
ensures consistent code style - Linting:
cargo clippy
with custom configuration excludingnew_without_default
warnings - Multi-target Building: Verification across all supported platforms
- Testing: Unit tests executed only on
x86_64-unknown-linux-gnu
Sources: .github/workflows/ci.yml(L22 - L30)
Documentation Deployment
The documentation system automatically builds and deploys to GitHub Pages on pushes to the default branch. The deployment includes:
- API Documentation: Generated via
cargo doc --no-deps --all-features
- Redirect Setup: Automatic index.html generation for seamless navigation
- Single Commit Deployment: Clean deployment strategy to the
gh-pages
branch
Sources: .github/workflows/ci.yml(L44 - L55)
Development Environment
Required Toolchain Components
flowchart TD subgraph subGraph2["Target Installation"] TARGETS["rustup target addAll matrix targets"] end subgraph subGraph1["Required Components"] RUST_SRC["rust-srcFor no_std targets"] CLIPPY["clippyLinting"] RUSTFMT["rustfmtCode formatting"] end subgraph subGraph0["Rust Nightly Toolchain"] RUSTC["rustc nightly"] CARGO["cargo"] end CARGO --> CLIPPY CARGO --> RUSTFMT RUSTC --> RUST_SRC RUST_SRC --> TARGETS
Development Setup Requirements
- Rust Nightly: Required for advanced procedural macro features
- rust-src Component: Essential for building
no_std
embedded targets - Cross-compilation Support: All target platforms must be installed via
rustup
Sources: .github/workflows/ci.yml(L15 - L19)
Project File Structure
File/Directory | Purpose |
---|---|
Cargo.toml | Package configuration and dependencies |
src/lib.rs | Core procedural macro implementation |
tests/ | Integration tests for macro functionality |
.github/workflows/ | CI/CD pipeline definitions |
.gitignore | Version control exclusions |
The project follows standard Rust library conventions with emphasis on procedural macro development patterns.
Sources: .gitignore(L1 - L5)
Getting Started
Relevant source files
This page provides a practical introduction to using the tuple_for_each
crate in your Rust projects. It covers the essential steps needed to apply the TupleForEach
derive macro to tuple structs and use the generated iteration functionality.
For detailed API documentation, see API Reference. For implementation details and advanced topics, see Implementation Guide.
Installation and Setup
Add tuple_for_each
to your Cargo.toml
dependencies:
[dependencies]
tuple_for_each = "0.1"
Import the derive macro in your Rust code:
use tuple_for_each::TupleForEach;
The crate requires no additional setup or configuration. All functionality is generated at compile time through the procedural macro system.
Sources: README.md(L3 - L4) tests/test_tuple_for_each.rs(L1)
Quick Start Workflow
Workflow: From Struct Definition to Iteration
flowchart TD DEFINE["Define Tuple Struct"] DERIVE["Add #[derive(TupleForEach)]"] INSTANTIATE["Create Instance"] METHODS["Use len() and is_empty()"] FOR_EACH["Use *_for_each! macro"] ENUMERATE["Use *_enumerate! macro"] GENERATE["Generates: macros and methods"] DEFINE --> DERIVE DERIVE --> GENERATE DERIVE --> INSTANTIATE GENERATE --> ENUMERATE GENERATE --> FOR_EACH GENERATE --> METHODS INSTANTIATE --> ENUMERATE INSTANTIATE --> FOR_EACH INSTANTIATE --> METHODS
Step 1: Define and Annotate Tuple Struct
Apply the #[derive(TupleForEach)]
attribute to any tuple struct:
#[derive(TupleForEach)]
struct FooBar(u32, &'static str, bool);
Step 2: Create Instance and Access Metadata
let tup = FooBar(23, "hello", true);
assert_eq!(tup.len(), 3);
assert!(!tup.is_empty());
Step 3: Iterate Over Fields
Use the generated *_for_each!
macro for field iteration:
foo_bar_for_each!(x in tup {
println!("{}", x);
});
Use the generated *_enumerate!
macro for indexed iteration:
foo_bar_enumerate!((i, x) in tup {
println!("{}: {}", i, x);
});
Sources: README.md(L18 - L39) tests/test_tuple_for_each.rs(L44 - L48)
Generated Code Entities
Code Generation Mapping
flowchart TD subgraph subGraph1["Generated Macros"] FOR_EACH["foo_bar_for_each!(x in tup { ... })"] ENUMERATE["foo_bar_enumerate!((i, x) in tup { ... })"] end subgraph subGraph0["Generated Methods"] LEN["len() -> usize"] EMPTY["is_empty() -> bool"] end STRUCT["Tuple Struct: FooBar(u32, &str, bool)"] VARIANTS["Supports: immutable and mutable variants"] ENUMERATE --> VARIANTS FOR_EACH --> VARIANTS STRUCT --> EMPTY STRUCT --> ENUMERATE STRUCT --> FOR_EACH STRUCT --> LEN
The derive macro generates the following code entities for each annotated struct:
Generated Entity | Purpose | Example Usage |
---|---|---|
len()method | Returns field count | tup.len() |
is_empty()method | Checks if tuple has zero fields | tup.is_empty() |
*_for_each!macro | Iterates over fields | foo_bar_for_each!(x in tup { ... }) |
*_enumerate!macro | Iterates with field index | foo_bar_enumerate!((i, x) in tup { ... }) |
Naming Convention: Macro names are derived by converting the struct name from PascalCase to snake_case. FooBar
becomes foo_bar_for_each!
and foo_bar_enumerate!
.
Sources: README.md(L9 - L16) tests/test_tuple_for_each.rs(L52 - L61)
Mutable and Immutable Variants
Both generated macros support mutable and immutable field access:
Immutable Access:
pair_for_each!(x in t {
x.bar(); // Call immutable methods
});
Mutable Access:
tuple_for_each!(x in mut t {
x.bar_mut(); // Call mutable methods
});
The same pattern applies to the enumerate macro:
pair_enumerate!((i, x) in mut t {
x.bar_mut();
});
Sources: tests/test_tuple_for_each.rs(L65 - L76) tests/test_tuple_for_each.rs(L94 - L106)
Common Usage Patterns
Field Type Uniformity: The macros work with heterogeneous tuple fields. Each field can be a different type, as demonstrated in the test patterns where A
, B
, and C
are distinct types implementing a common trait.
Trait-Based Operations: A common pattern is to define a trait that all tuple field types implement, then call trait methods within the iteration macros:
#![allow(unused)] fn main() { trait Base { fn foo(&self) -> Self::Item; fn bar(&self); } }
Index Tracking: The enumerate macro provides automatic index tracking, eliminating the need for manual counter variables.
Sources: tests/test_tuple_for_each.rs(L3 - L42) tests/test_tuple_for_each.rs(L83 - L90)
Next Steps
- For complete syntax and advanced usage examples, see Basic Usage
- For detailed explanation of all generated functionality, see Generated Functionality
- For understanding the internal implementation, see Implementation Guide
- For comprehensive API documentation, see API Reference
Sources: README.md(L1 - L40) tests/test_tuple_for_each.rs(L1 - L107)
Basic Usage
Relevant source files
This document covers the fundamental usage patterns for the tuple_for_each
crate, demonstrating how to apply the TupleForEach
derive macro to tuple structs and use the generated iteration functionality. This page focuses on simple, straightforward examples that illustrate the core workflow from struct definition to runtime usage.
For detailed information about the generated API surface and all available methods, see Generated Functionality. For comprehensive API reference documentation, see API Reference.
Applying the Derive Macro
The TupleForEach
derive macro is applied to tuple structs to automatically generate iteration utilities. The basic pattern involves importing the trait and applying the derive attribute:
use tuple_for_each::TupleForEach;
#[derive(TupleForEach)]
struct FooBar(u32, &'static str, bool);
The derive macro analyzes the tuple struct at compile time and generates several pieces of functionality based on the struct's name and field structure.
Basic Derive Workflow
flowchart TD A["User defines tuple struct"] B["#[derive(TupleForEach)]"] C["Compile-time processing"] D["Generated macros"] E["Generated methods"] F["foo_bar_for_each!"] G["foo_bar_enumerate!"] H["len()"] I["is_empty()"] J["Runtime iteration"] K["Runtime metadata"] A --> B B --> C C --> D C --> E D --> F D --> G E --> H E --> I F --> J G --> J H --> K I --> K
Sources: README.md(L20 - L28) tests/test_tuple_for_each.rs(L44 - L48)
Generated Methods
The derive macro automatically implements two utility methods on the tuple struct:
Method | Return Type | Purpose |
---|---|---|
len() | usize | Returns the number of fields in the tuple |
is_empty() | bool | Returnstrueif the tuple has no fields |
These methods provide metadata about the tuple structure:
let tup = FooBar(23, "hello", true);
assert_eq!(tup.len(), 3);
assert!(!tup.is_empty());
Sources: README.md(L9 - L11) README.md(L26 - L28) tests/test_tuple_for_each.rs(L53) tests/test_tuple_for_each.rs(L67)
Generated For-Each Macros
The derive macro generates iteration macros that follow a naming convention based on the struct name converted from PascalCase to snake_case. For a struct named FooBar
, the generated macro is foo_bar_for_each!
.
For-Each Macro Usage
flowchart TD A["FooBar struct"] B["foo_bar_for_each! macro"] C["Iterates over each field"] D["Executes block for each field"] E["Pair struct"] F["pair_for_each! macro"] G["Iterates over each field"] H["Executes block for each field"] A --> B B --> C C --> D E --> F F --> G G --> H
The macro syntax uses the pattern <struct_name>_for_each!(variable in tuple { block })
:
// Immutable iteration
foo_bar_for_each!(x in tup {
println!("{}", x);
});
// Mutable iteration
foo_bar_for_each!(x in mut tup {
// Can modify x here
});
Sources: README.md(L30 - L33) tests/test_tuple_for_each.rs(L56 - L61) tests/test_tuple_for_each.rs(L70 - L75)
Generated Enumerate Macros
The enumerate macros provide indexed iteration, giving access to both the field index and the field value. The macro follows the pattern <struct_name>_enumerate!((index, variable) in tuple { block })
:
foo_bar_enumerate!((i, x) in tup {
println!("{}: {}", i, x);
});
The index starts at 0 and increments for each field in the tuple. Like the for-each macros, enumerate macros support both immutable and mutable access patterns.
Runtime Iteration Patterns
flowchart TD A["tuple_for_each!(x in tup { ... })"] B["Immutable field access"] C["tuple_for_each!(x in mut tup { ... })"] D["Mutable field access"] E["tuple_enumerate!((i, x) in tup { ... })"] F["Indexed immutable access"] G["tuple_enumerate!((i, x) in mut tup { ... })"] H["Indexed mutable access"] I["Read-only operations"] J["Modify field values"] K["Index-aware read operations"] L["Index-aware modify operations"] A --> B B --> I C --> D D --> J E --> F F --> K G --> H H --> L
Sources: README.md(L35 - L38) tests/test_tuple_for_each.rs(L84 - L90) tests/test_tuple_for_each.rs(L99 - L105)
Mutable Access Patterns
Both for-each and enumerate macros support mutable access by adding the mut
keyword before the tuple variable. This allows the iteration block to modify the fields during iteration:
let mut t = Tuple(A, B, C);
// Mutable for-each
tuple_for_each!(x in mut t {
x.bar_mut(); // Can call mutable methods
});
// Mutable enumerate
tuple_enumerate!((i, x) in mut t {
x.bar_mut(); // Can call mutable methods with index
});
The mutable access pattern is particularly useful when the tuple fields implement traits that require mutable methods or when you need to modify the state of the contained values during iteration.
Sources: tests/test_tuple_for_each.rs(L65 - L76) tests/test_tuple_for_each.rs(L94 - L106)
Generated Functionality
Relevant source files
This page details the specific functionality generated when applying the #[derive(TupleForEach)]
attribute to a tuple struct. It covers the generated methods, macros, and their syntax variants.
For basic usage examples, see Basic Usage. For implementation details of how this code is generated, see Code Generation Pipeline.
Overview
When the TupleForEach
derive macro is applied to a tuple struct, it generates several pieces of functionality:
Generated Item | Purpose | Variants |
---|---|---|
len()method | Returns the number of fields in the tuple | Const method |
is_empty()method | Returns true if tuple has zero fields | Const method |
Iterates over tuple fields | Immutable, Mutable | |
Iterates over fields with indices | Immutable, Mutable |
Generated Functionality Mapping
flowchart TD Input["TupleStruct(A, B, C)"] Derive["#[derive(TupleForEach)]"] Methods["impl TupleStruct"] ForEach["tuple_struct_for_each!"] Enumerate["tuple_struct_enumerate!"] Len["len() -> usize"] Empty["is_empty() -> bool"] ForEachImm["field in tuple { ... }"] ForEachMut["field in mut tuple { ... }"] EnumImm["(i, field) in tuple { ... }"] EnumMut["(i, field) in mut tuple { ... }"] Derive --> Enumerate Derive --> ForEach Derive --> Methods Enumerate --> EnumImm Enumerate --> EnumMut ForEach --> ForEachImm ForEach --> ForEachMut Input --> Derive Methods --> Empty Methods --> Len
Sources: src/lib.rs(L58 - L122)
Generated Methods
The derive macro adds two constant methods to the tuple struct through an impl
block:
len()Method
Returns the number of fields in the tuple as a compile-time constant.
pub const fn len(&self) -> usize
The implementation directly returns the field count determined at compile time.
is_empty()Method
Returns true
if the tuple has zero fields, false
otherwise.
pub const fn is_empty(&self) -> bool
The implementation compares self.len() == 0
.
Sources: src/lib.rs(L88 - L98)
Generated Macros
Two primary macros are generated, each with immutable and mutable variants.
Macro Naming Convention
The macro names are derived from the struct name using pascal_to_snake
conversion:
Struct Name | Macro Prefix |
---|---|
FooBar | foo_bar |
MyTuple | my_tuple |
HTTPResponse | h_t_t_p_response |
Naming Conversion Process
flowchart TD Pascal["PascalCase"] Snake["snake_case"] Insert["Insert '_' before uppercase"] Lower["Convert to lowercase"] Example1["FooBar"] Example1Out["foo_bar"] Example2["MyTuple"] Example2Out["my_tuple"] Example1 --> Example1Out Example2 --> Example2Out Insert --> Lower Lower --> Snake Pascal --> Insert Pascal --> Snake
Sources: src/lib.rs(L124 - L133) src/lib.rs(L60)
_for_each!Macro
Iterates over each field in the tuple, binding each field to a variable.
Syntax Variants:
- Immutable:
macro_name_for_each!(item in tuple { code })
- Mutable:
macro_name_for_each!(item in mut tuple { code })
Generated Code Structure:
flowchart TD ForEach["_for_each! macro"] ImmVariant["Immutable Variant"] MutVariant["Mutable Variant"] ImmGen["{ let $item = &$tuple.0; $code }{ let $item = &$tuple.1; $code }..."] MutGen["{ let $item = &mut $tuple.0; $code }{ let $item = &mut $tuple.1; $code }..."] ForEach --> ImmVariant ForEach --> MutVariant ImmVariant --> ImmGen MutVariant --> MutGen
Sources: src/lib.rs(L100 - L109) src/lib.rs(L71 - L72)
_enumerate!Macro
Iterates over each field with its index, binding both the index and field to variables.
Syntax Variants:
- Immutable:
macro_name_enumerate!((idx, item) in tuple { code })
- Mutable:
macro_name_enumerate!((idx, item) in mut tuple { code })
The generated code provides both the field index and field reference for each iteration.
Sources: src/lib.rs(L111 - L120) src/lib.rs(L73 - L82)
Macro Implementation Details
Each macro variant generates a sequence of code blocks, one for each tuple field:
Field Access Pattern
flowchart TD Tuple["Tuple(field0, field1, field2)"] Gen["Code Generation"] Block0["{ let item = &tuple.0; code }"] Block1["{ let item = &tuple.1; code }"] Block2["{ let item = &tuple.2; code }"] Exec0["Execute user code with field0"] Exec1["Execute user code with field1"] Exec2["Execute user code with field2"] Block0 --> Exec0 Block1 --> Exec1 Block2 --> Exec2 Gen --> Block0 Gen --> Block1 Gen --> Block2 Tuple --> Gen
The mutable variants follow the same pattern but use &mut
instead of &
for field references.
For enumerate macros, an additional index binding is generated: let $idx = #idx;
where #idx
is the literal field index (0, 1, 2, etc.).
Sources: src/lib.rs(L69 - L83)
Documentation Generation
The generated macros include comprehensive documentation with usage examples. The documentation is generated dynamically based on the struct name and macro type using the gen_doc
function.
Each generated macro receives structured documentation that includes:
- Purpose description
- Link back to the derive macro
- Code examples with proper syntax
Sources: src/lib.rs(L26 - L56) src/lib.rs(L85 - L86)
Implementation Guide
Relevant source files
This document provides a detailed technical guide to the internal implementation of the tuple_for_each
derive macro. It covers the macro processing pipeline, code generation strategies, and the architectural decisions behind the crate's design.
This guide focuses on how the derive macro transforms user code at compile time. For basic usage examples, see Getting Started. For API reference documentation, see API Reference.
Macro Processing Architecture
The tuple_for_each
derive macro follows a standard procedural macro architecture with clear separation between parsing, validation, and code generation phases.
Processing Pipeline
flowchart TD Input["TokenStream Input"] Entry["tuple_for_each()"] Parse["syn::parse_macro_input!()"] Validate["Validation Logic"] Valid["Valid Tuple Struct?"] Error["Error::new_spanned()"] Generate["impl_for_each()"] NameConv["pascal_to_snake()"] FieldIter["Field Iteration Logic"] DocGen["gen_doc()"] CodeGen["quote! Macro Generation"] MacroNames["Macro Name Generation"] ForEachGen["for_each Variants"] EnumerateGen["enumerate Variants"] Documentation["Inline Documentation"] Methods["len() & is_empty() Methods"] Macros["Generated macro_rules!"] Output["proc_macro2::TokenStream"] ErrorOutput["Compile Error"] CodeGen --> Macros CodeGen --> Methods DocGen --> Documentation Documentation --> CodeGen Entry --> Parse EnumerateGen --> CodeGen Error --> ErrorOutput FieldIter --> EnumerateGen FieldIter --> ForEachGen ForEachGen --> CodeGen Generate --> CodeGen Generate --> DocGen Generate --> FieldIter Generate --> NameConv Input --> Entry MacroNames --> CodeGen Macros --> Output Methods --> Output NameConv --> MacroNames Parse --> Validate Valid --> Error Valid --> Generate Validate --> Valid
The pipeline starts with the tuple_for_each
function as the entry point and flows through validation, name conversion, and code generation phases before producing the final token stream.
Sources: src/lib.rs(L10 - L24) src/lib.rs(L58 - L122)
Entry Point and Validation
The derive macro entry point performs essential validation to ensure the macro is only applied to tuple structs:
flowchart TD TokenStream["TokenStream"] ParseInput["syn::parse_macro_input!"] AST["DeriveInput AST"] CheckData["Check ast.data"] IsStruct["Data::Struct?"] CompileError["Compile Error"] CheckFields["Check strct.fields"] IsUnnamed["Fields::Unnamed?"] CallImpl["impl_for_each()"] ErrorToken["Error TokenStream"] GeneratedCode["Generated TokenStream"] AST --> CheckData CallImpl --> GeneratedCode CheckData --> IsStruct CheckFields --> IsUnnamed CompileError --> ErrorToken IsStruct --> CheckFields IsStruct --> CompileError IsUnnamed --> CallImpl IsUnnamed --> CompileError ParseInput --> AST TokenStream --> ParseInput
The validation logic specifically checks for Data::Struct
with Fields::Unnamed
, ensuring the derive macro only works on tuple structs like struct MyTuple(A, B, C)
.
Sources: src/lib.rs(L11 - L24)
Code Generation Pipeline
The core code generation happens in the impl_for_each
function, which orchestrates the creation of all generated functionality.
Generation Strategy
flowchart TD subgraph subGraph4["Final Assembly"] ImplBlock["impl block"] MacroRules["macro_rules! definitions"] QuoteOutput["quote! expansion"] end subgraph Documentation["Documentation"] DocGeneration["gen_doc()"] ForEachDoc["macro_for_each_doc"] EnumerateDoc["macro_enumerate_doc"] end subgraph subGraph2["Code Vectors"] ForEachVec["for_each Vec"] ForEachMutVec["for_each_mut Vec"] EnumerateVec["enumerate Vec"] EnumerateMutVec["enumerate_mut Vec"] end subgraph subGraph1["Name Generation"] PascalToSnake["pascal_to_snake()"] MacroPrefix["macro_name"] ForEachMacro["macro_for_each"] EnumerateMacro["macro_enumerate"] end subgraph subGraph0["Input Processing"] ASTInput["DeriveInput & DataStruct"] StructName["ast.ident"] FieldCount["strct.fields.len()"] end ASTInput --> FieldCount ASTInput --> StructName DocGeneration --> EnumerateDoc DocGeneration --> ForEachDoc EnumerateDoc --> MacroRules EnumerateMutVec --> MacroRules EnumerateVec --> MacroRules FieldCount --> EnumerateMutVec FieldCount --> EnumerateVec FieldCount --> ForEachMutVec FieldCount --> ForEachVec ForEachDoc --> MacroRules ForEachMutVec --> ImplBlock ForEachVec --> ImplBlock ImplBlock --> QuoteOutput MacroPrefix --> DocGeneration MacroPrefix --> EnumerateMacro MacroPrefix --> ForEachMacro MacroRules --> QuoteOutput PascalToSnake --> MacroPrefix StructName --> DocGeneration StructName --> PascalToSnake
The generation process creates separate code vectors for each variant (immutable/mutable × for_each/enumerate) and assembles them into the final token stream.
Sources: src/lib.rs(L58 - L122)
Field Iteration Logic
The macro generates field access code by iterating over field indices and creating appropriate access patterns:
Variant | Access Pattern | Generated Code Template |
---|---|---|
for_each | &$tuple.#idx | { let $item = &$tuple.#idx; $code } |
for_each_mut | &mut $tuple.#idx | { let $item = &mut $tuple.#idx; $code } |
enumerate | &$tuple.#idxwith index | { let $idx = #idx; let $item = &$tuple.#idx; $code } |
enumerate_mut | &mut $tuple.#idxwith index | { let $idx = #idx; let $item = &mut $tuple.#idx; $code } |
The field iteration uses syn::Index
to generate numeric field accessors for tuple struct fields.
Sources: src/lib.rs(L64 - L83)
Generated Code Structure
The macro produces a comprehensive set of functionality for each tuple struct, including both methods and macros.
Output Components
flowchart TD subgraph Documentation["Documentation"] MacroDoc["Generated macro documentation"] Examples["Usage examples"] end subgraph subGraph2["Macro Variants"] ForEachImmut["($item:ident in $tuple:ident $code:block)"] ForEachMut["($item:ident in mut $tuple:ident $code:block)"] EnumImmut["(($idx:ident, $item:ident) in $tuple:ident $code:block)"] EnumMut["(($idx:ident, $item:ident) in mut $tuple:ident $code:block)"] end subgraph subGraph1["Generated Macros"] ImplBlock["impl StructName"] LenMethod["len() method"] IsEmptyMethod["is_empty() method"] ForEachMacro["struct_name_for_each!"] EnumerateMacro["struct_name_enumerate!"] end subgraph subGraph0["Generated Implementation"] ImplBlock["impl StructName"] LenMethod["len() method"] IsEmptyMethod["is_empty() method"] end EnumerateMacro --> EnumImmut EnumerateMacro --> EnumMut EnumerateMacro --> MacroDoc ForEachMacro --> ForEachImmut ForEachMacro --> ForEachMut ForEachMacro --> MacroDoc ImplBlock --> IsEmptyMethod ImplBlock --> LenMethod MacroDoc --> Examples
Each component serves a specific purpose in providing iteration capabilities for tuple structs.
Sources: src/lib.rs(L87 - L121)
Method Generation
The implementation block includes utility methods that provide metadata about the tuple:
Method | Return Type | Implementation | Purpose |
---|---|---|---|
len() | usize | #field_num(const) | Returns field count |
is_empty() | bool | self.len() == 0 | Checks if tuple has fields |
Both methods are const fn
, allowing compile-time evaluation when possible.
Sources: src/lib.rs(L88 - L98)
Helper Utilities
The implementation includes several utility functions that support the code generation process.
Name Conversion
The pascal_to_snake
function converts Pascal case struct names to snake case for macro naming:
flowchart TD Input["PascalCase String"] Iterator["Character Iterator"] Check["Check Character"] IsUpper["is_ascii_uppercase()?"] AddUnderscore["Add '_' if not empty"] AddChar["Add character"] ToLower["to_ascii_lowercase()"] Result["snake_case String"] AddChar --> ToLower AddUnderscore --> ToLower Check --> IsUpper Input --> Iterator IsUpper --> AddChar IsUpper --> AddUnderscore Iterator --> Check ToLower --> Result
This conversion ensures that MyTupleStruct
becomes my_tuple_struct_for_each!
for the generated macro names.
Sources: src/lib.rs(L124 - L133)
Documentation Generation
The gen_doc
function creates inline documentation for generated macros:
Documentation Type | Template | Generated Content |
---|---|---|
"for_each" | Field iteration | Usage examples and description for*_for_each! |
"enumerate" | Indexed iteration | Usage examples and description for*_enumerate! |
The documentation includes proper cross-references to the derive macro and usage examples in ignore blocks.
Sources: src/lib.rs(L26 - L56)
Token Stream Assembly
The final assembly uses the quote!
macro to combine all generated components into a single proc_macro2::TokenStream
. The macro leverages token interpolation to insert:
- Field counts as literal numbers
- Generated code vectors using
#(#vector)*
expansion - Dynamically created identifiers via
format_ident!
- Documentation strings through
#doc
attributes
Sources: src/lib.rs(L87 - L122)
Derive Macro Processing
Relevant source files
This document explains the entry point processing for the TupleForEach
derive macro, covering input parsing, validation, and error handling. This covers the initial phase where user-provided tuple struct definitions are parsed and validated before code generation begins. For information about the actual code generation logic, see Code Generation Pipeline.
Entry Point Function
The derive macro processing begins with the tuple_for_each()
function, which serves as the procedural macro entry point. This function is marked with the #[proc_macro_derive(TupleForEach)]
attribute and handles the initial processing of user code.
flowchart TD UserCode["User Code: #[derive(TupleForEach)] struct MyTuple(A, B);"] EntryPoint["tuple_for_each()"] ParseInput["syn::parse_macro_input!()"] Validation["Tuple Struct Validation"] Success["impl_for_each()"] Error["Compile Error"] EntryPoint --> ParseInput ParseInput --> Validation UserCode --> EntryPoint Validation --> Error Validation --> Success
Entry Point Processing Flow
The tuple_for_each()
function receives a TokenStream
containing the user's struct definition and immediately delegates parsing to the syn
crate. The function signature and initial parsing occur at src/lib.rs(L11 - L12)
Sources: src/lib.rs(L10 - L24)
Input Parsing with syn
The macro uses the syn
crate to parse the incoming TokenStream
into a structured Abstract Syntax Tree (AST). This parsing transforms the raw token stream into typed Rust syntax structures that can be programmatically analyzed.
flowchart TD subgraph subGraph1["Validation Targets"] StructData["Data::Struct"] UnnamedFields["Fields::Unnamed"] end subgraph subGraph0["syn Parsing Components"] TokenStream["proc_macro::TokenStream"] MacroInput["syn::parse_macro_input!"] DeriveInput["syn::DeriveInput"] Data["syn::Data"] DataStruct["syn::DataStruct"] Fields["syn::Fields"] end Data --> DataStruct Data --> StructData DataStruct --> Fields DeriveInput --> Data Fields --> UnnamedFields MacroInput --> DeriveInput TokenStream --> MacroInput
syn AST Structure and Validation Points
The parsing process extracts key structural information:
Component | Type | Purpose | Validation |
---|---|---|---|
ast | DeriveInput | Root AST node containing struct metadata | Contains struct name and data |
ast.data | Data | Discriminates between struct, enum, union | Must beData::Struct |
strct | DataStruct | Struct-specific data | Extracted fromData::Struct |
strct.fields | Fields | Field information | Must beFields::Unnamedfor tuple structs |
The parsing occurs with explicit type annotation at src/lib.rs(L12) ensuring the input conforms to the expected DeriveInput
structure.
Sources: src/lib.rs(L11 - L14)
Tuple Struct Validation
The validation logic implements a two-stage check to ensure the derive macro is only applied to valid tuple structs. This prevents compilation errors and provides clear error messages for invalid usage.
flowchart TD AST["DeriveInput AST"] CheckData["Check ast.data"] IsStruct["Data::Struct?"] CheckFields["Check strct.fields"] IsUnnamed["Fields::Unnamed?"] ValidTuple["Valid Tuple Struct"] StructError["Not a struct"] FieldError["Not a tuple struct"] CompileError["Compile Error"] AST --> CheckData CheckData --> IsStruct CheckFields --> IsUnnamed FieldError --> CompileError IsStruct --> CheckFields IsStruct --> StructError IsUnnamed --> FieldError IsUnnamed --> ValidTuple StructError --> CompileError
Validation Decision Tree
The validation process implements nested pattern matching:
- Struct Type Check: Verifies
ast.data
matchesData::Struct(strct)
pattern at src/lib.rs(L13) - Field Type Check: Verifies
strct.fields
matchesFields::Unnamed(_)
pattern at src/lib.rs(L14) - Success Path: If both checks pass, control flows to
impl_for_each(&ast, strct)
at src/lib.rs(L15)
The validation logic excludes:
- Named field structs (
struct Foo { a: i32 }
) - Unit structs (
struct Foo;
) - Enums and unions
- Any non-struct types
Sources: src/lib.rs(L13 - L17)
Error Handling
When validation fails, the macro generates compile-time errors using syn::Error
to provide meaningful feedback to users. The error handling system ensures that invalid usage is caught early with descriptive messages.
flowchart TD subgraph subGraph1["Error Context"] AST["ast (full input)"] Span["Source span information"] end subgraph subGraph0["Error Generation"] ErrorNew["syn::Error::new_spanned()"] ErrorMsg["\attribute Unsupported markdown: codespan can only be attached to tuple structs\"] CompileError["to_compile_error()"] TokenStream["TokenStream"] end AST --> ErrorNew CompileError --> TokenStream ErrorMsg --> ErrorNew ErrorNew --> CompileError Span --> ErrorNew
Error Generation Process
The error handling mechanism provides:
- Precise Source Location: Uses
new_spanned(ast, ...)
to attach error to the original input location at src/lib.rs(L18 - L19) - Clear Error Message: Explains the constraint that only tuple structs are supported at src/lib.rs(L20)
- Compile-Time Failure: Converts error to token stream that causes compilation to fail at src/lib.rs(L22 - L23)
This approach ensures users receive actionable feedback when they attempt to apply the derive macro to incompatible types.
Sources: src/lib.rs(L18 - L24)
Integration with Code Generation
Upon successful validation, the entry point function delegates to the core code generation system. This handoff represents the transition from input processing to actual macro expansion.
sequenceDiagram participant UserCode as "User Code" participant tuple_for_each as "tuple_for_each()" participant synparse_macro_input as "syn::parse_macro_input" participant ValidationLogic as "Validation Logic" participant impl_for_each as "impl_for_each()" participant GeneratedTokenStream as "Generated TokenStream" UserCode ->> tuple_for_each: " tuple_for_each ->> synparse_macro_input: Parse TokenStream synparse_macro_input -->> tuple_for_each: DeriveInput AST tuple_for_each ->> ValidationLogic: Check struct type & fields ValidationLogic -->> tuple_for_each: Valid tuple struct tuple_for_each ->> impl_for_each: Call impl_for_each(&ast, &strct) impl_for_each -->> tuple_for_each: proc_macro2::TokenStream tuple_for_each -->> UserCode: Generated code TokenStream
Processing Handoff Sequence
The successful path through validation culminates in the call to impl_for_each(&ast, strct)
at src/lib.rs(L15) This function receives:
&ast
: Reference to the completeDeriveInput
containing struct metadatastrct
: Reference to theDataStruct
containing field information
The return value is converted from proc_macro2::TokenStream
to proc_macro::TokenStream
using .into()
for compatibility with the procedural macro interface.
Sources: src/lib.rs(L15) src/lib.rs(L58)
Code Generation Pipeline
Relevant source files
This document details the core code generation logic within the tuple_for_each
crate, specifically focusing on the impl_for_each
function and its supporting components. This covers the transformation process from parsed AST input to generated Rust code output, including field iteration patterns, macro template creation, and documentation generation.
For information about the initial parsing and validation phase, see Derive Macro Processing. For details about the generated API surface, see Generated Functionality.
Pipeline Overview
The code generation pipeline transforms a validated tuple struct AST into executable Rust code through a series of well-defined steps. The process centers around the impl_for_each
function, which orchestrates the entire generation workflow.
Code Generation Flow
flowchart TD A["tuple_for_each()"] B["impl_for_each()"] C["pascal_to_snake()"] D["Field Access Generation"] E["Macro Template Creation"] F["Documentation Generation"] G["macro_name: String"] H["for_each: Vec"] I["for_each_mut: Vec"] J["enumerate: Vec"] K["enumerate_mut: Vec"] L["macro_for_each!"] M["macro_enumerate!"] N["len() method"] O["is_empty() method"] P["macro_for_each_doc"] Q["macro_enumerate_doc"] R["Final TokenStream"] A --> B B --> C B --> D B --> E B --> F C --> G D --> H D --> I D --> J D --> K E --> L E --> M E --> N E --> O F --> P F --> Q G --> L G --> M H --> L I --> L J --> M K --> M L --> R M --> R N --> R O --> R P --> L Q --> M
Sources: src/lib.rs(L58 - L122)
Name Conversion Process
The pipeline begins by converting the tuple struct's PascalCase name to snake_case for macro naming. This conversion is handled by the pascal_to_snake
utility function.
Conversion Algorithm
Input (PascalCase) | Output (snake_case) | Generated Macro Names |
---|---|---|
MyTuple | my_tuple | my_tuple_for_each!,my_tuple_enumerate! |
HTTPResponse | h_t_t_p_response | h_t_t_p_response_for_each!,h_t_t_p_response_enumerate! |
SimpleStruct | simple_struct | simple_struct_for_each!,simple_struct_enumerate! |
The conversion process inserts underscores before uppercase characters (except the first character) and converts all characters to lowercase:
flowchart TD A["tuple_name.to_string()"] B["pascal_to_snake()"] C["macro_name: String"] D["format_ident!('{}_for_each', macro_name)"] E["format_ident!('{}_enumerate', macro_name)"] F["macro_for_each: Ident"] G["macro_enumerate: Ident"] A --> B B --> C C --> D C --> E D --> F E --> G
Sources: src/lib.rs(L60 - L62) src/lib.rs(L124 - L133)
Field Access Code Generation
The core of the pipeline generates field access patterns for each tuple field. This process creates four distinct code patterns to handle different iteration scenarios.
Field Iteration Logic
flowchart TD A["field_num = strct.fields.len()"] B["for i in 0..field_num"] C["idx = Index::from(i)"] D["for_each.push()"] E["for_each_mut.push()"] F["enumerate.push()"] G["enumerate_mut.push()"] H["&$tuple.#idx"] I["&mut $tuple.#idx"] J["($idx = #idx, &$tuple.#idx)"] K["($idx = #idx, &mut $tuple.#idx)"] A --> B B --> C C --> D C --> E C --> F C --> G D --> H E --> I F --> J G --> K
Each field access pattern is generated using the quote!
macro to create TokenStream
fragments:
Pattern Type | Code Template | Variable Binding |
---|---|---|
for_each | let $item = &$tuple.#idx; $code | Immutable reference |
for_each_mut | let $item = &mut $tuple.#idx; $code | Mutable reference |
enumerate | let $idx = #idx; let $item = &$tuple.#idx; $code | Index + immutable reference |
enumerate_mut | let $idx = #idx; let $item = &mut $tuple.#idx; $code | Index + mutable reference |
Sources: src/lib.rs(L64 - L83)
Macro Template Creation
The generated field access patterns are assembled into complete macro definitions using Rust's macro_rules!
system. Each macro supports both immutable and mutable variants through pattern matching.
Macro Structure Assembly
flowchart TD A["for_each patterns"] B["macro_for_each definition"] C["enumerate patterns"] D["macro_enumerate definition"] E["($item:ident in $tuple:ident $code:block)"] F["($item:ident in mut $tuple:ident $code:block)"] G["(($idx:ident, $item:ident) in $tuple:ident $code:block)"] H["(($idx:ident, $item:ident) in mut $tuple:ident $code:block)"] I["#(#for_each)*"] J["#(#for_each_mut)*"] K["#(#enumerate)*"] L["#(#enumerate_mut)*"] A --> B B --> E B --> F C --> D D --> G D --> H E --> I F --> J G --> K H --> L
The macro definitions use repetition syntax (#()*
) to expand the vector of field access patterns into sequential code blocks. This creates the illusion of iteration over heterogeneous tuple fields at compile time.
Sources: src/lib.rs(L102 - L120)
Documentation Generation
The pipeline includes automated documentation generation for the created macros. The gen_doc
function creates formatted documentation strings with usage examples.
Documentation Template System
Documentation Type | Template Function | Generated Content |
---|---|---|
for_each | gen_doc("for_each", tuple_name, macro_name) | Usage examples with field iteration |
enumerate | gen_doc("enumerate", tuple_name, macro_name) | Usage examples with index + field iteration |
The documentation templates include:
- Description of macro purpose
- Reference to the derive macro that generated it
- Code examples showing proper usage syntax
- Placeholder substitution for tuple and macro names
flowchart TD A["tuple_name.to_string()"] B["gen_doc()"] C["macro_name"] D["macro_for_each_doc"] E["macro_enumerate_doc"] F["#[doc = #macro_for_each_doc]"] G["#[doc = #macro_enumerate_doc]"] A --> B B --> D B --> E C --> B D --> F E --> G
Sources: src/lib.rs(L26 - L56) src/lib.rs(L85 - L86)
Final Assembly
The pipeline concludes by assembling all generated components into a single TokenStream
using the quote!
macro. This creates the complete implementation that will be inserted into the user's code.
Component Integration
flowchart TD subgraph subGraph2["Supporting Elements"] E["Documentation strings"] F["#[macro_export] attributes"] end subgraph subGraph1["Generated Macros"] C["macro_for_each!"] D["macro_enumerate!"] end subgraph subGraph0["Generated Methods"] A["len() method"] B["is_empty() method"] end G["impl #tuple_name block"] H["Final TokenStream"] A --> G B --> G C --> H D --> H E --> H F --> H G --> H
The final assembly includes:
- An
impl
block for the original tuple struct containinglen()
andis_empty()
methods - Two exported macros with complete documentation
- All necessary attributes for proper macro visibility
The entire generated code block is wrapped in a single quote!
invocation that produces the final proc_macro2::TokenStream
returned to the Rust compiler.
Sources: src/lib.rs(L87 - L122)
Development
Relevant source files
This page provides comprehensive guidance for contributors to the tuple_for_each
crate, covering development workflow, testing strategies, and quality assurance processes. It focuses on the practical aspects of building, testing, and maintaining the codebase across multiple target architectures.
For detailed information about testing patterns and integration test structure, see Testing. For CI/CD pipeline configuration and deployment processes, see CI/CD Pipeline.
Development Environment Setup
The tuple_for_each
crate requires a Rust nightly toolchain with specific components for cross-platform development and quality checks.
Required Toolchain Components
Component | Purpose |
---|---|
rust-src | Source code for cross-compilation |
clippy | Linting and static analysis |
rustfmt | Code formatting |
Supported Target Architectures
The project supports multiple target architectures to ensure compatibility across embedded and systems programming environments:
flowchart TD subgraph subGraph2["Testing Strategy"] FULL_TEST["Full test suite"] BUILD_ONLY["Build verification only"] end subgraph subGraph1["Target Architectures"] X86_LINUX["x86_64-unknown-linux-gnu"] X86_NONE["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] ARM["aarch64-unknown-none-softfloat"] end subgraph Toolchain["Toolchain"] NIGHTLY["nightly toolchain"] end ARM --> BUILD_ONLY NIGHTLY --> ARM NIGHTLY --> RISCV NIGHTLY --> X86_LINUX NIGHTLY --> X86_NONE RISCV --> BUILD_ONLY X86_LINUX --> FULL_TEST X86_NONE --> BUILD_ONLY
Target Architecture Support Matrix
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L29 - L30)
Development Workflow
Quality Gates Pipeline
The development process enforces multiple quality gates before code integration:
flowchart TD subgraph subGraph0["Parallel Execution"] TARGET1["x86_64-unknown-linux-gnu"] TARGET2["x86_64-unknown-none"] TARGET3["riscv64gc-unknown-none-elf"] TARGET4["aarch64-unknown-none-softfloat"] end COMMIT["Code Commit"] FMT_CHECK["cargo fmt --all -- --check"] CLIPPY_CHECK["cargo clippy --target TARGET --all-features"] BUILD["cargo build --target TARGET --all-features"] UNIT_TEST["cargo test --target x86_64-unknown-linux-gnu"] SUCCESS["Integration Complete"] BUILD --> TARGET1 BUILD --> TARGET2 BUILD --> TARGET3 BUILD --> TARGET4 BUILD --> UNIT_TEST CLIPPY_CHECK --> BUILD COMMIT --> FMT_CHECK FMT_CHECK --> CLIPPY_CHECK TARGET1 --> SUCCESS TARGET2 --> SUCCESS TARGET3 --> SUCCESS TARGET4 --> SUCCESS UNIT_TEST --> SUCCESS
Development Quality Pipeline
Sources: .github/workflows/ci.yml(L22 - L30)
Local Development Commands
Command | Purpose |
---|---|
cargo fmt --all -- --check | Verify code formatting |
cargo clippy --all-features | Run linting checks |
cargo build --all-features | Build for host target |
cargo test -- --nocapture | Run integration tests |
Testing Architecture
The testing strategy focuses on integration tests that verify the generated macro functionality across different usage patterns.
Test Structure Overview
flowchart TD subgraph subGraph3["Test Module Structure"] TEST_FILE["tests/test_tuple_for_each.rs"] subgraph subGraph2["Test Functions"] TEST_FOR_EACH["test_for_each()"] TEST_FOR_EACH_MUT["test_for_each_mut()"] TEST_ENUMERATE["test_enumerate()"] TEST_ENUMERATE_MUT["test_enumerate_mut()"] end subgraph subGraph1["Test Targets"] PAIR_STRUCT["Pair(A, B)"] TUPLE_STRUCT["Tuple(A, B, C)"] end subgraph subGraph0["Test Infrastructure"] BASE_TRAIT["Base trait"] IMPL_A["A impl Base"] IMPL_B["B impl Base"] IMPL_C["C impl Base"] end end BASE_TRAIT --> IMPL_A BASE_TRAIT --> IMPL_B BASE_TRAIT --> IMPL_C IMPL_A --> PAIR_STRUCT IMPL_B --> PAIR_STRUCT IMPL_B --> TUPLE_STRUCT IMPL_C --> TUPLE_STRUCT PAIR_STRUCT --> TEST_ENUMERATE_MUT PAIR_STRUCT --> TEST_FOR_EACH TEST_FILE --> BASE_TRAIT TUPLE_STRUCT --> TEST_ENUMERATE TUPLE_STRUCT --> TEST_FOR_EACH_MUT
Integration Test Architecture
Test Function Coverage
Test Function | Target Struct | Generated Macro | Validation |
---|---|---|---|
test_for_each | Pair | pair_for_each! | Iteration count, method calls |
test_for_each_mut | Tuple | tuple_for_each! | Mutable iteration |
test_enumerate | Tuple | tuple_enumerate! | Index validation |
test_enumerate_mut | Pair | pair_enumerate! | Mutable enumeration |
Sources: tests/test_tuple_for_each.rs(L50 - L106)
CI/CD Infrastructure
Workflow Jobs Architecture
flowchart TD subgraph subGraph3["Documentation Job"] DOC_JOB["doc job"] BUILD_DOCS["cargo doc --no-deps --all-features"] DEPLOY_PAGES["Deploy to GitHub Pages"] end subgraph subGraph2["CI Job Matrix"] CI_JOB["ci job"] subgraph subGraph1["Matrix Strategy"] RUST_NIGHTLY["rust-toolchain: nightly"] TARGETS_MATRIX["targets: [4 architectures]"] end end subgraph subGraph0["GitHub Actions Triggers"] PUSH["push event"] PR["pull_request event"] end BUILD_DOCS --> DEPLOY_PAGES CI_JOB --> RUST_NIGHTLY CI_JOB --> TARGETS_MATRIX DOC_JOB --> BUILD_DOCS PR --> CI_JOB PUSH --> CI_JOB PUSH --> DOC_JOB
CI/CD Job Architecture
Documentation Deployment Process
The documentation pipeline includes automated deployment to GitHub Pages with strict quality requirements:
Environment Variable | Purpose |
---|---|
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs | Enforce complete documentation |
default-branch | Control deployment target |
Sources: .github/workflows/ci.yml(L32 - L55) .github/workflows/ci.yml(L40)
Multi-Target Build Strategy
Cross-Compilation Support
The crate supports embedded and systems programming environments through comprehensive cross-compilation testing:
flowchart TD subgraph subGraph2["Build Verification"] FORMAT_CHECK["Format verification"] LINT_CHECK["Clippy linting"] BUILD_CHECK["Compilation check"] UNIT_TESTING["Unit test execution"] end subgraph subGraph1["Target Platforms"] LINUX_GNU["x86_64-unknown-linux-gnuStandard Linux"] BARE_METAL_X86["x86_64-unknown-noneBare metal x86"] RISCV_EMBEDDED["riscv64gc-unknown-none-elfRISC-V embedded"] ARM_EMBEDDED["aarch64-unknown-none-softfloatARM embedded"] end subgraph subGraph0["Host Environment"] UBUNTU["ubuntu-latest runner"] NIGHTLY_TOOLCHAIN["Rust nightly toolchain"] end ARM_EMBEDDED --> BUILD_CHECK ARM_EMBEDDED --> FORMAT_CHECK ARM_EMBEDDED --> LINT_CHECK BARE_METAL_X86 --> BUILD_CHECK BARE_METAL_X86 --> FORMAT_CHECK BARE_METAL_X86 --> LINT_CHECK LINUX_GNU --> BUILD_CHECK LINUX_GNU --> FORMAT_CHECK LINUX_GNU --> LINT_CHECK LINUX_GNU --> UNIT_TESTING NIGHTLY_TOOLCHAIN --> ARM_EMBEDDED NIGHTLY_TOOLCHAIN --> BARE_METAL_X86 NIGHTLY_TOOLCHAIN --> LINUX_GNU NIGHTLY_TOOLCHAIN --> RISCV_EMBEDDED RISCV_EMBEDDED --> BUILD_CHECK RISCV_EMBEDDED --> FORMAT_CHECK RISCV_EMBEDDED --> LINT_CHECK UBUNTU --> NIGHTLY_TOOLCHAIN
Multi-Target Build Matrix
Testing Limitations by Target
Only the x86_64-unknown-linux-gnu
target supports full test execution due to standard library dependencies in the test environment. Embedded targets (*-none-*
) undergo build verification to ensure compilation compatibility without runtime testing.
Sources: .github/workflows/ci.yml(L8 - L30)
Testing
Relevant source files
This document covers the testing strategy and test patterns for the tuple_for_each
crate. It explains how to verify that the derive macro correctly generates iteration utilities and provides guidance for testing procedural macro functionality. For information about the CI/CD pipeline and automated testing infrastructure, see CI/CD Pipeline.
Test Structure Overview
The tuple_for_each
crate uses integration tests to validate macro functionality. All tests are located in tests/test_tuple_for_each.rs(L1 - L107) and follow a pattern-based approach to verify that the TupleForEach
derive macro generates correct code for different tuple struct configurations.
Test Foundation Components
The tests establish a common foundation using a trait-based approach:
Component | Purpose | Lines |
---|---|---|
Basetrait | Provides common interface for test types | tests/test_tuple_for_each.rs3-8 |
Test types (A,B,C) | Concrete implementations with different associated types | tests/test_tuple_for_each.rs10-42 |
Test tuples (Pair,Tuple) | Tuple structs with varying field counts | tests/test_tuple_for_each.rs44-48 |
The Base
trait defines methods that return different types (u32
, f32
, &'static str
) to verify that the generated macros handle heterogeneous tuple fields correctly.
Sources: tests/test_tuple_for_each.rs(L1 - L48)
Integration Test Patterns
Test Function Structure
Each test function follows a consistent pattern that validates both the generated macros and utility methods:
flowchart TD subgraph subGraph0["Generated Macro Types"] G["*_for_each! (immutable)"] H["*_for_each! (mutable)"] I["*_enumerate! (immutable)"] J["*_enumerate! (mutable)"] end A["Create tuple instance"] B["Verify len() method"] C["Initialize counter/validator"] D["Execute generated macro"] E["Perform operations on each field"] F["Verify counter/index correctness"] A --> B B --> C C --> D D --> E D --> G D --> H D --> I D --> J E --> F
Test Pattern Validation Flow
Sources: tests/test_tuple_for_each.rs(L50 - L106)
Macro Functionality Coverage
The test suite covers four distinct macro generation scenarios:
Test Function | Macro Tested | Mutability | Enumeration | Tuple Type |
---|---|---|---|---|
test_for_each | pair_for_each! | Immutable | No | Pair(A, B) |
test_for_each_mut | tuple_for_each! | Mutable | No | Tuple(A, B, C) |
test_enumerate | tuple_enumerate! | Immutable | Yes | Tuple(A, B, C) |
test_enumerate_mut | pair_enumerate! | Mutable | Yes | Pair(A, B) |
Each test validates:
- Generated method functionality:
len()
returns correct field count - Macro syntax: Proper expansion of macro rules
- Field access: Ability to call methods on tuple fields
- Iteration count: Verification that all fields are processed
- Index accuracy: For enumerate variants, index values match expected sequence
Sources: tests/test_tuple_for_each.rs(L50 - L106)
Test Execution Validation
Immutable Iteration Testing
The test_for_each
function tests/test_tuple_for_each.rs(L50 - L62) validates immutable field access:
- Creates
Pair(A, B)
instance - Verifies
len()
returns2
- Uses
pair_for_each!
macro to iterate over fields - Calls
foo()
andbar()
methods on each field - Confirms iteration count matches field count
Mutable Iteration Testing
The test_for_each_mut
function tests/test_tuple_for_each.rs(L64 - L76) validates mutable field access:
- Creates
Tuple(A, B, C)
instance - Uses
tuple_for_each!
withmut
keyword - Calls
bar_mut()
method requiring mutable access - Verifies all three fields are processed
Enumeration Testing
The enumeration tests tests/test_tuple_for_each.rs(L78 - L106) verify index generation:
test_enumerate
: Teststuple_enumerate!
with immutable accesstest_enumerate_mut
: Testspair_enumerate!
with mutable access- Both validate that indices start at
0
and increment sequentially - Confirm index values match expected position in tuple
Sources: tests/test_tuple_for_each.rs(L50 - L106)
Code Generation Verification
flowchart TD subgraph subGraph2["Test Validation Points"] V1["Macro syntax correctness"] V2["Field access patterns"] V3["Mutability handling"] V4["Index generation"] V5["Method implementation"] end subgraph subGraph1["Generated Artifacts"] PFE["pair_for_each! macro"] PE["pair_enumerate! macro"] TFE["tuple_for_each! macro"] TE["tuple_enumerate! macro"] LEN["len() method"] EMPTY["is_empty() method"] end subgraph subGraph0["Source Code Input"] DS["#[derive(TupleForEach)]struct Pair(A, B)"] DT["#[derive(TupleForEach)]struct Tuple(A, B, C)"] end DS --> EMPTY DS --> LEN DS --> PE DS --> PFE DT --> EMPTY DT --> LEN DT --> TE DT --> TFE LEN --> V5 PE --> V4 PFE --> V1 PFE --> V2 TE --> V4 TFE --> V3
Generated Code Validation Matrix
Sources: tests/test_tuple_for_each.rs(L44 - L48) tests/test_tuple_for_each.rs(L50 - L106)
Running Tests
Tests are executed using standard Cargo commands:
cargo test # Run all tests
cargo test test_for_each # Run specific test function
cargo test --verbose # Run with detailed output
The tests validate macro functionality at compile time (macro expansion correctness) and runtime (iteration behavior). Since this is a procedural macro crate, successful compilation indicates that the macro generates syntactically correct Rust code, while test execution verifies semantic correctness.
Sources: tests/test_tuple_for_each.rs(L1 - L107)
CI/CD Pipeline
Relevant source files
This document covers the continuous integration and continuous deployment (CI/CD) infrastructure for the tuple_for_each
crate. The pipeline is implemented using GitHub Actions and provides automated quality assurance, multi-target compilation, and documentation deployment.
The CI/CD system ensures code quality across multiple target architectures and automatically publishes documentation. For information about local development testing, see Testing.
Pipeline Overview
The CI/CD pipeline consists of two primary jobs defined in the GitHub Actions workflow: the ci
job for quality assurance and multi-target builds, and the doc
job for documentation generation and deployment.
CI/CD Architecture
flowchart TD Trigger["GitHub Event Triggerpush | pull_request"] Pipeline["GitHub Actions Workflow.github/workflows/ci.yml"] CIJob["ci JobQuality Assurance"] DocJob["doc JobDocumentation"] Matrix["Matrix Strategyrust-toolchain: nightlytargets: 4 platforms"] Target1["x86_64-unknown-linux-gnuFull Testing"] Target2["x86_64-unknown-noneBuild Only"] Target3["riscv64gc-unknown-none-elfBuild Only"] Target4["aarch64-unknown-none-softfloatBuild Only"] DocBuild["cargo doc --no-deps --all-features"] Deploy["GitHub Pages Deploymentgh-pages branch"] QualityGates["Quality GatesFormat | Clippy | Build | Test"] BuildOnly["Build GateFormat | Clippy | Build"] CIJob --> Matrix DocJob --> Deploy DocJob --> DocBuild Matrix --> Target1 Matrix --> Target2 Matrix --> Target3 Matrix --> Target4 Pipeline --> CIJob Pipeline --> DocJob Target1 --> QualityGates Target2 --> BuildOnly Target3 --> BuildOnly Target4 --> BuildOnly Trigger --> Pipeline
Sources: .github/workflows/ci.yml(L1 - L56)
CI Job Workflow
The ci
job implements a comprehensive quality assurance pipeline that runs across multiple target architectures using a matrix strategy.
Matrix Build Strategy
The CI job uses a fail-fast strategy disabled to ensure all target builds are attempted even if one fails:
Configuration | Value |
---|---|
Runner | ubuntu-latest |
Rust Toolchain | nightly |
Target Architectures | 4 platforms |
Fail Fast | false |
The target matrix includes both hosted and embedded platforms:
flowchart TD Nightly["rust-toolchain: nightlywith components:rust-src, clippy, rustfmt"] Targets["Target Matrix"] Linux["x86_64-unknown-linux-gnuStandard Linux TargetFull Testing Enabled"] BareMetal["x86_64-unknown-noneBare Metal x86_64Build Only"] RISCV["riscv64gc-unknown-none-elfRISC-V 64-bitBuild Only"] ARM["aarch64-unknown-none-softfloatARM64 Bare MetalBuild Only"] FullPipeline["Format Check → Clippy → Build → Test"] BuildPipeline["Format Check → Clippy → Build"] ARM --> BuildPipeline BareMetal --> BuildPipeline Linux --> FullPipeline Nightly --> Targets RISCV --> BuildPipeline Targets --> ARM Targets --> BareMetal Targets --> Linux Targets --> RISCV
Sources: .github/workflows/ci.yml(L8 - L12) .github/workflows/ci.yml(L15 - L19)
Quality Gates
The CI pipeline implements several sequential quality gates:
1. Code Format Verification
cargo fmt --all -- --check
Ensures all code follows consistent formatting standards using rustfmt
.
2. Linting with Clippy
cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default
Performs static analysis with the new_without_default
warning specifically allowed.
3. Compilation
cargo build --target ${{ matrix.targets }} --all-features
Validates successful compilation for each target architecture.
4. Unit Testing
cargo test --target ${{ matrix.targets }} -- --nocapture
Executes the test suite, but only for the x86_64-unknown-linux-gnu
target due to testing infrastructure limitations on bare-metal targets.
CI Job Steps Flow
sequenceDiagram participant GitHubActions as "GitHub Actions" participant ubuntulatestRunner as "ubuntu-latest Runner" participant RustToolchain as "Rust Toolchain" participant SourceCode as "Source Code" GitHubActions ->> ubuntulatestRunner: "actions/checkout@v4" ubuntulatestRunner ->> SourceCode: Clone repository GitHubActions ->> ubuntulatestRunner: "dtolnay/rust-toolchain@nightly" ubuntulatestRunner ->> RustToolchain: Install nightly + components + targets ubuntulatestRunner ->> RustToolchain: "rustc --version --verbose" RustToolchain -->> ubuntulatestRunner: Version info ubuntulatestRunner ->> SourceCode: "cargo fmt --all -- --check" SourceCode -->> ubuntulatestRunner: Format validation result ubuntulatestRunner ->> SourceCode: "cargo clippy --target TARGET" SourceCode -->> ubuntulatestRunner: Lint analysis result ubuntulatestRunner ->> SourceCode: "cargo build --target TARGET" SourceCode -->> ubuntulatestRunner: Build result alt TARGET == x86_64-unknown-linux-gnu ubuntulatestRunner ->> SourceCode: "cargo test --target TARGET" SourceCode -->> ubuntulatestRunner: Test results end
Sources: .github/workflows/ci.yml(L13 - L30)
Documentation Job
The doc
job handles automated documentation generation and deployment to GitHub Pages.
Documentation Build Process
The documentation job runs independently of the CI matrix and focuses on generating comprehensive API documentation:
flowchart TD DocTrigger["GitHub Event Trigger"] DocJob["doc Jobubuntu-latest"] Checkout["actions/checkout@v4"] Toolchain["dtolnay/rust-toolchain@nightly"] DocBuild["cargo doc --no-deps --all-featuresRUSTDOCFLAGS:-D rustdoc::broken_intra_doc_links-D missing-docs"] IndexGen["Generate redirect index.htmlcargo tree | head -1 | cut -d' ' -f1"] Condition["github.ref == default-branchAND not continue-on-error"] Deploy["JamesIves/github-pages-deploy-action@v4Branch: gh-pagesFolder: target/docsingle-commit: true"] Skip["Skip deployment"] Pages["GitHub PagesDocumentation Site"] Checkout --> DocBuild Condition --> Deploy Condition --> Skip Deploy --> Pages DocBuild --> IndexGen DocJob --> Checkout DocJob --> Toolchain DocTrigger --> DocJob IndexGen --> Condition
Sources: .github/workflows/ci.yml(L32 - L56)
Documentation Configuration
The documentation build enforces strict standards through RUSTDOCFLAGS
:
Flag | Purpose |
---|---|
-D rustdoc::broken_intra_doc_links | Treat broken documentation links as errors |
-D missing-docs | Require documentation for all public items |
The build generates a redirect index page using project metadata:
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
Deployment Strategy
Documentation deployment occurs only on the default branch using the JamesIves/github-pages-deploy-action@v4
action with single-commit mode to maintain a clean gh-pages
branch history.
Sources: .github/workflows/ci.yml(L36 - L55)
Multi-Target Architecture Support
The pipeline supports diverse target architectures to ensure compatibility across different deployment environments:
Target Architecture Matrix
Target | Architecture | Environment | Testing Level |
---|---|---|---|
x86_64-unknown-linux-gnu | x86_64 | Linux with libc | Full (build + test) |
x86_64-unknown-none | x86_64 | Bare metal | Build only |
riscv64gc-unknown-none-elf | RISC-V 64-bit | Bare metal ELF | Build only |
aarch64-unknown-none-softfloat | ARM64 | Bare metal soft-float | Build only |
The restricted testing on embedded targets reflects the procedural macro nature of the crate - the generated code needs to compile for embedded targets, but the macro itself only executes during compilation on the host.
flowchart TD Source["Source CodeProcedural Macro"] CompileTime["Compile TimeHost: x86_64-linux"] HostTest["Host Testingx86_64-unknown-linux-gnuFull test suite"] EmbeddedValidation["Embedded Validation"] BareMetal["x86_64-unknown-noneBuild validation"] RISCV["riscv64gc-unknown-none-elfBuild validation"] ARM["aarch64-unknown-none-softfloatBuild validation"] Runtime["RuntimeGenerated macros executeon target platforms"] ARM --> Runtime BareMetal --> Runtime CompileTime --> EmbeddedValidation CompileTime --> HostTest EmbeddedValidation --> ARM EmbeddedValidation --> BareMetal EmbeddedValidation --> RISCV HostTest --> Runtime RISCV --> Runtime Source --> CompileTime
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L29 - L30)
API Reference
Relevant source files
This document provides comprehensive reference documentation for all public APIs and generated functionality provided by the tuple_for_each
crate. It covers the TupleForEach
derive macro and all code it generates, including macros and methods added to tuple structs.
For implementation details about how the derive macro works internally, see Implementation Guide. For basic usage examples and getting started information, see Getting Started.
API Surface Overview
The tuple_for_each
crate provides a single derive macro that generates multiple APIs for tuple structs. The following diagram shows the complete API surface generated by the derive macro:
flowchart TD subgraph subGraph2["Macro Variants"] ForEachImmutable["for_each!(x in tuple { ... })"] ForEachMutable["for_each!(x in mut tuple { ... })"] EnumerateImmutable["enumerate!((i, x) in tuple { ... })"] EnumerateMutable["enumerate!((i, x) in mut tuple { ... })"] end subgraph subGraph1["Generated APIs"] LenMethod["len() -> usize method"] IsEmptyMethod["is_empty() -> bool method"] ForEachMacro["*_for_each! macro"] EnumerateMacro["*_enumerate! macro"] end subgraph subGraph0["User Input"] TupleStruct["Tuple Struct with #[derive(TupleForEach)]"] end EnumerateMacro --> EnumerateImmutable EnumerateMacro --> EnumerateMutable ForEachMacro --> ForEachImmutable ForEachMacro --> ForEachMutable TupleStruct --> EnumerateMacro TupleStruct --> ForEachMacro TupleStruct --> IsEmptyMethod TupleStruct --> LenMethod
Sources: src/lib.rs(L10 - L24) src/lib.rs(L88 - L121)
TupleForEach Derive Macro
The TupleForEach
derive macro is the primary entry point of the crate, implemented as a procedural macro that transforms tuple struct definitions at compile time.
Attribute | Value |
---|---|
Macro Type | #[proc_macro_derive] |
Target | Tuple structs with unnamed fields only |
Entry Point | tuple_for_each()function |
Location | src/lib.rs10-24 |
Validation Rules
The derive macro validates input and only accepts tuple structs:
- Accepted:
struct MyTuple(Type1, Type2, Type3);
- Rejected: Named struct fields, unit structs, enums
- Error Message:
"attribute 'tuple_for_each' can only be attached to tuple structs"
The validation logic checks for Data::Struct
with Fields::Unnamed
at src/lib.rs(L13 - L14)
Sources: src/lib.rs(L10 - L24)
Generated API Components
When applied to a tuple struct, the derive macro generates four distinct API components. The following diagram shows the code generation pipeline and relationships:
flowchart TD subgraph subGraph0["Field Iteration Logic"] ForEachGen["*_for_each! macro generation"] EnumGen["*_enumerate! macro generation"] ForEachLoop["for i in 0..field_num"] EnumLoop["for i in 0..field_num"] FieldAccess["$tuple.#idx access"] IndexedAccess["#idx, $tuple.#idx access"] end Input["TupleStruct(Field1, Field2, ...)"] EntryPoint["tuple_for_each()"] Parser["syn::parse_macro_input!"] CoreLogic["impl_for_each()"] NameGen["pascal_to_snake()"] MacroNames["macro_for_each, macro_enumerate"] MethodGen["len() & is_empty() methods"] ConstMethods["const fn len(), const fn is_empty()"] MacroRules["macro_rules! expansion"] Output["Generated TokenStream"] CoreLogic --> EnumGen CoreLogic --> ForEachGen CoreLogic --> MethodGen CoreLogic --> NameGen EntryPoint --> Parser EnumGen --> EnumLoop EnumLoop --> IndexedAccess FieldAccess --> MacroRules ForEachGen --> ForEachLoop ForEachLoop --> FieldAccess IndexedAccess --> MacroRules Input --> EntryPoint MacroRules --> Output MethodGen --> ConstMethods NameGen --> MacroNames Parser --> CoreLogic
Sources: src/lib.rs(L58 - L122) src/lib.rs(L124 - L133)
Methods Added to Tuple Structs
Two constant methods are added to every tuple struct via an impl
block:
Method | Signature | Description | Implementation |
---|---|---|---|
len() | pub const fn len(&self) -> usize | Returns the number of fields | src/lib.rs90-92 |
is_empty() | pub const fn is_empty(&self) -> bool | Returnstrueif no fields | src/lib.rs95-97 |
Both methods are const fn
, allowing compile-time evaluation. The len()
method returns the literal field count (#field_num
), while is_empty()
compares against zero.
Generated Iteration Macros
Two macro families are generated, each with immutable and mutable variants:
*_for_each! Macro
<snake_case_name>_for_each!(item in tuple { code_block })
<snake_case_name>_for_each!(item in mut tuple { code_block })
- Pattern:
$item:ident in $tuple:ident $code:block
- Mutable Pattern:
$item:ident in mut $tuple:ident $code:block
- Implementation: src/lib.rs(L102 - L109)
*_enumerate! Macro
<snake_case_name>_enumerate!((index, item) in tuple { code_block })
<snake_case_name>_enumerate!((index, item) in mut tuple { code_block })
- Pattern:
($idx:ident, $item:ident) in $tuple:ident $code:block
- Mutable Pattern:
($idx:ident, $item:ident) in mut $tuple:ident $code:block
- Implementation: src/lib.rs(L113 - L120)
Sources: src/lib.rs(L100 - L121)
Name Generation Strategy
The macro names are derived from the tuple struct name using a pascal_to_snake
conversion:
flowchart TD PascalCase["MyTupleStruct"] Conversion["pascal_to_snake()"] SnakeCase["my_tuple_struct"] ForEachName["my_tuple_struct_for_each!"] EnumName["my_tuple_struct_enumerate!"] Conversion --> SnakeCase PascalCase --> Conversion SnakeCase --> EnumName SnakeCase --> ForEachName
The conversion algorithm at src/lib.rs(L124 - L133) processes each character, inserting underscores before uppercase letters (except the first) and converting to lowercase.
Sources: src/lib.rs(L60 - L62) src/lib.rs(L124 - L133)
Documentation Generation
The generated macros include automatically generated documentation using the gen_doc()
function at src/lib.rs(L26 - L56) Documentation templates provide usage examples and link back to the derive macro.
Documentation Template Variables
Variable | Purpose | Example |
---|---|---|
tuple_name | Original struct name | "MyTuple" |
macro_name | Snake case name | "my_tuple" |
kind | Macro type | "for_each"or"enumerate" |
Sources: src/lib.rs(L26 - L56) src/lib.rs(L85 - L86)
Complete API Reference
For detailed information about specific components:
- TupleForEach Derive Macro: See TupleForEach Derive Macro
- Generated Macros: See Generated Macros
- Generated Methods: See Generated Methods
Each subsection provides comprehensive documentation, usage patterns, and implementation details for the respective API components.
Sources: src/lib.rs(L1 - L134) README.md(L1 - L40)
TupleForEach Derive Macro
Relevant source files
This document provides comprehensive reference documentation for the TupleForEach
derive macro, which is the core procedural macro that generates iteration utilities for tuple structs. For information about the generated macros themselves, see Generated Macros. For information about the generated methods, see Generated Methods.
Purpose and Scope
The TupleForEach
derive macro is a procedural macro that automatically generates iteration utilities for tuple structs at compile time. When applied to a tuple struct, it creates field iteration macros and utility methods that enable ergonomic access to tuple fields without manual indexing.
Macro Declaration
The derive macro is declared as a procedural macro using the #[proc_macro_derive]
attribute:
#![allow(unused)] fn main() { #[proc_macro_derive(TupleForEach)] pub fn tuple_for_each(item: TokenStream) -> TokenStream }
Entry Point Flow
flowchart TD Input["TokenStream input"] Parse["syn::parse_macro_input!(item as DeriveInput)"] Check["ast.data matches Data::Struct?"] Error1["Compile Error"] Fields["strct.fields matches Fields::Unnamed?"] Error2["Compile Error: 'can only be attached to tuple structs'"] Generate["impl_for_each(&ast, strct)"] Output["Generated TokenStream"] CompileError["Error::new_spanned().to_compile_error()"] Check --> Error1 Check --> Fields Error1 --> CompileError Error2 --> CompileError Fields --> Error2 Fields --> Generate Generate --> Output Input --> Parse Parse --> Check
Sources: src/lib.rs(L11 - L24)
Requirements and Constraints
Valid Input Types
The derive macro can only be applied to tuple structs - structs with unnamed fields. The validation logic enforces two conditions:
Condition | Requirement | Error if Failed |
---|---|---|
Struct Type | Must beData::Structvariant | Compile error with span |
Field Type | Must haveFields::Unnamed | "attributetuple_for_eachcan only be attached to tuple structs" |
Supported Tuple Struct Examples
// ✅ Valid - basic tuple struct
#[derive(TupleForEach)]
struct Point(i32, i32);
// ✅ Valid - mixed types
#[derive(TupleForEach)]
struct Mixed(String, i32, bool);
// ✅ Valid - generic tuple struct
#[derive(TupleForEach)]
struct Generic<T, U>(T, U);
// ❌ Invalid - named struct
#[derive(TupleForEach)]
struct Named { x: i32, y: i32 }
// ❌ Invalid - unit struct
#[derive(TupleForEach)]
struct Unit;
Validation Logic Flow
flowchart TD ast["DeriveInput AST"] data_check["ast.data == Data::Struct?"] error_span["Error::new_spanned(ast, message)"] strct["Extract DataStruct"] fields_check["strct.fields == Fields::Unnamed?"] error_msg["'can only be attached to tuple structs'"] impl_call["impl_for_each(&ast, strct)"] compile_error["to_compile_error().into()"] success["Generated Code TokenStream"] ast --> data_check data_check --> error_span data_check --> strct error_msg --> compile_error error_span --> compile_error fields_check --> error_msg fields_check --> impl_call impl_call --> success strct --> fields_check
Sources: src/lib.rs(L13 - L23)
Code Generation Process
Name Resolution
The macro converts tuple struct names from PascalCase to snake_case using the pascal_to_snake
function to create macro names:
Tuple Struct Name | Generated Macro Prefix | For Each Macro | Enumerate Macro |
---|---|---|---|
Point | point | point_for_each! | point_enumerate! |
MyTuple | my_tuple | my_tuple_for_each! | my_tuple_enumerate! |
HttpResponse | http_response | http_response_for_each! | http_response_enumerate! |
Generated Code Structure
For each tuple struct, the macro generates:
- Utility Methods:
len()
andis_empty()
implementations - For Each Macro: Field iteration with immutable and mutable variants
- Enumerate Macro: Indexed field iteration with immutable and mutable variants
Code Generation Pipeline
flowchart TD ast_input["DeriveInput & DataStruct"] name_conv["pascal_to_snake(tuple_name)"] macro_names["format_ident! for macro names"] field_count["strct.fields.len()"] iter_gen["Generate field iteration code"] for_each_imm["for_each: &$tuple.#idx"] for_each_mut["for_each_mut: &mut $tuple.#idx"] enum_imm["enumerate: (#idx, &$tuple.#idx)"] enum_mut["enumerate_mut: (#idx, &mut $tuple.#idx)"] macro_def["macro_rules! definitions"] methods["impl block with len() & is_empty()"] quote_expand["quote! macro expansion"] token_stream["Final TokenStream"] ast_input --> name_conv enum_imm --> macro_def enum_mut --> macro_def field_count --> iter_gen field_count --> methods for_each_imm --> macro_def for_each_mut --> macro_def iter_gen --> enum_imm iter_gen --> enum_mut iter_gen --> for_each_imm iter_gen --> for_each_mut macro_def --> quote_expand macro_names --> field_count methods --> quote_expand name_conv --> macro_names quote_expand --> token_stream
Sources: src/lib.rs(L58 - L122)
Field Access Pattern Generation
The macro generates field access code for each tuple field using zero-based indexing:
// For a 3-field tuple struct, generates:
// Field 0: &$tuple.0, &mut $tuple.0
// Field 1: &$tuple.1, &mut $tuple.1
// Field 2: &$tuple.2, &mut $tuple.2
The field iteration logic creates four variants of access patterns:
Pattern Type | Mutability | Generated Code Template |
---|---|---|
for_each | Immutable | { let $item = &$tuple.#idx; $code } |
for_each_mut | Mutable | { let $item = &mut $tuple.#idx; $code } |
enumerate | Immutable | { let $idx = #idx; let $item = &$tuple.#idx; $code } |
enumerate_mut | Mutable | { let $idx = #idx; let $item = &mut $tuple.#idx; $code } |
Sources: src/lib.rs(L64 - L83)
Error Handling
The derive macro implements compile-time validation with descriptive error messages:
Error Cases
- Non-Struct Types: Applied to enums, unions, or other non-struct types
- Named Structs: Applied to structs with named fields instead of tuple structs
- Unit Structs: Applied to unit structs (no fields)
Error Message Generation
Error::new_spanned(
ast,
"attribute `tuple_for_each` can only be attached to tuple structs",
)
.to_compile_error()
.into()
The error uses syn::Error::new_spanned
to provide precise source location information and converts to a TokenStream
containing the compile error.
Error Handling Flow
flowchart TD invalid_input["Invalid Input Structure"] new_spanned["Error::new_spanned(ast, message)"] error_message["'attribute tuple_for_each can only be attached to tuple structs'"] to_compile_error["to_compile_error()"] into_token_stream["into()"] compiler_error["Compiler displays error at source location"] error_message --> to_compile_error into_token_stream --> compiler_error invalid_input --> new_spanned new_spanned --> error_message to_compile_error --> into_token_stream
Sources: src/lib.rs(L18 - L23)
Implementation Dependencies
The derive macro relies on several key crates for parsing and code generation:
Dependency | Purpose | Usage in Code |
---|---|---|
syn | AST parsing | syn::parse_macro_input!,DeriveInput,DataStruct |
quote | Code generation | quote!macro,format_ident! |
proc_macro2 | Token stream handling | Return type forimpl_for_each |
proc_macro | Procedural macro interface | TokenStreaminput/output |
Sources: src/lib.rs(L3 - L5)
Generated Macros
Relevant source files
This page documents the iteration macros automatically generated by the TupleForEach
derive macro. These macros provide a convenient way to iterate over tuple struct fields at compile time with type safety.
For information about the derive macro itself, see TupleForEach Derive Macro. For the generated methods (len()
and is_empty()
), see Generated Methods.
Overview
When you apply #[derive(TupleForEach)]
to a tuple struct, the macro generates two types of iteration macros:
Macro Type | Purpose | Naming Pattern |
---|---|---|
*_for_each! | Iterate over fields | {snake_case_name}_for_each! |
*_enumerate! | Iterate with field indices | {snake_case_name}_enumerate! |
Each macro type supports both immutable and mutable variants, allowing you to read from or modify tuple fields during iteration.
Macro Name Generation
flowchart TD PascalCase["PascalCase Struct Namee.g., 'MyTuple'"] pascal_to_snake["pascal_to_snake()Function"] SnakeCase["snake_case Namee.g., 'my_tuple'"] ForEach["my_tuple_for_each!Macro"] Enumerate["my_tuple_enumerate!Macro"] PascalCase --> pascal_to_snake SnakeCase --> Enumerate SnakeCase --> ForEach pascal_to_snake --> SnakeCase
Sources: src/lib.rs(L124 - L133) src/lib.rs(L60 - L62)
For Each Macros
The *_for_each!
macros iterate over tuple fields sequentially, providing access to each field value.
Syntax
{name}_for_each!(field_var in tuple_instance {
// code block using field_var
});
{name}_for_each!(field_var in mut tuple_instance {
// code block with mutable access to field_var
});
Generated Implementation
Macro Structure and Field Access Pattern
flowchart TD MacroCall["macro_for_each!(item in tuple { code })"] ImmutablePath["Immutable Variant"] MutablePath["macro_for_each!(item in mut tuple { code })"] ImmutableExpansion["{ let $item = &$tuple.0; $code }{ let $item = &$tuple.1; $code }{ let $item = &$tuple.2; $code }"] MutableExpansion["{ let $item = &mut $tuple.0; $code }{ let $item = &mut $tuple.1; $code }{ let $item = &mut $tuple.2; $code }"] ImmutablePath --> ImmutableExpansion MacroCall --> ImmutablePath MacroCall --> MutablePath MutablePath --> MutableExpansion
The macro generates sequential code blocks for each tuple field, where:
$item
becomes the field variable name you specify$tuple
becomes the tuple instance name you provide$code
becomes your code block- Field access uses numeric indices (
.0
,.1
,.2
, etc.)
Sources: src/lib.rs(L102 - L109) src/lib.rs(L71 - L72)
Usage Examples
From the test suite, here are practical examples:
// Immutable iteration over Pair(A, B)
pair_for_each!(x in t {
println!("for_each {}: {}", i, x.foo());
x.bar();
i += 1;
});
// Mutable iteration over Tuple(A, B, C)
tuple_for_each!(x in mut t {
println!("for_each_mut {}: {}", i, x.foo());
x.bar_mut();
i += 1;
});
Sources: tests/test_tuple_for_each.rs(L56 - L61) tests/test_tuple_for_each.rs(L70 - L75)
Enumerate Macros
The *_enumerate!
macros provide both field index and field value during iteration, similar to iterator enumerate functionality.
Syntax
{name}_enumerate!((index_var, field_var) in tuple_instance {
// code block using index_var and field_var
});
{name}_enumerate!((index_var, field_var) in mut tuple_instance {
// code block with mutable access to field_var
});
Generated Implementation
Enumerate Macro Expansion Pattern
flowchart TD EnumCall["macro_enumerate!((idx, item) in tuple { code })"] ImmEnum["Immutable Enumerate"] MutEnum["Mutable Enumerate"] ImmEnumExp["{ let $idx = 0; let $item = &$tuple.0; $code }{ let $idx = 1; let $item = &$tuple.1; $code }{ let $idx = 2; let $item = &$tuple.2; $code }"] MutEnumExp["{ let $idx = 0; let $item = &mut $tuple.0; $code }{ let $idx = 1; let $item = &mut $tuple.1; $code }{ let $idx = 2; let $item = &mut $tuple.2; $code }"] EnumCall --> ImmEnum EnumCall --> MutEnum ImmEnum --> ImmEnumExp MutEnum --> MutEnumExp
The enumerate macros generate code blocks that provide:
$idx
- the field index as a literal value (0, 1, 2, etc.)$item
- reference to the field value (immutable or mutable)- Sequential execution for each tuple field
Sources: src/lib.rs(L113 - L120) src/lib.rs(L73 - L82)
Usage Examples
// Immutable enumeration over Tuple(A, B, C)
tuple_enumerate!((i, x) in t {
println!("enumerate {}: {}", i, x.foo());
x.bar();
assert_eq!(i, real_idx);
real_idx += 1;
});
// Mutable enumeration over Pair(A, B)
pair_enumerate!((i, x) in mut t {
println!("enumerate_mut {}: {}", i, x.foo());
x.bar_mut();
assert_eq!(i, real_idx);
real_idx += 1;
});
Sources: tests/test_tuple_for_each.rs(L84 - L90) tests/test_tuple_for_each.rs(L99 - L105)
Implementation Details
Macro Export and Documentation
Both macro types are exported using #[macro_export]
and include generated documentation with usage examples specific to the tuple struct name.
Code Generation Pipeline for Macros
flowchart TD StructName["Tuple Struct Name"] pascal_to_snake["pascal_to_snake()"] MacroNames["Macro Identifiers"] FieldCount["Field Count"] FieldLoops["Field Generation Loops"] ForEachVecs["for_each & for_each_mut Vectors"] EnumVecs["enumerate & enumerate_mut Vectors"] Documentation["gen_doc() Function"] DocStrings["Generated Doc Strings"] QuoteExpansion["quote! Macro"] FinalTokens["Generated macro_rules! Definitions"] DocStrings --> QuoteExpansion Documentation --> DocStrings EnumVecs --> QuoteExpansion FieldCount --> FieldLoops FieldLoops --> EnumVecs FieldLoops --> ForEachVecs ForEachVecs --> QuoteExpansion MacroNames --> Documentation MacroNames --> QuoteExpansion QuoteExpansion --> FinalTokens StructName --> pascal_to_snake pascal_to_snake --> MacroNames
The generation process involves:
- Converting struct name from PascalCase to snake_case
- Creating macro identifiers with
_for_each
and_enumerate
suffixes - Generating field access code for each tuple position
- Creating documentation strings with examples
- Assembling everything into
macro_rules!
definitions usingquote!
Sources: src/lib.rs(L58 - L122) src/lib.rs(L26 - L56)
Field Access Pattern
All generated macros use numeric field access (.0
, .1
, .2
) since tuple structs have unnamed fields. The field index is determined by the position in the tuple struct definition.
Field Position | Access Pattern | Generated Index |
---|---|---|
First field | $tuple.0 | 0(for enumerate) |
Second field | $tuple.1 | 1(for enumerate) |
Third field | $tuple.2 | 2(for enumerate) |
Sources: src/lib.rs(L69 - L83)
Generated Methods
Relevant source files
This page documents the instance methods that are automatically generated when applying the #[derive(TupleForEach)]
attribute to a tuple struct. These methods provide metadata and utility functions for working with tuple instances.
For information about the generated iteration macros, see Generated Macros. For details about the derive macro itself, see TupleForEach Derive Macro.
Overview
The TupleForEach
derive macro generates exactly two methods on the target tuple struct, both providing metadata about the tuple's structure:
Method | Return Type | Purpose |
---|---|---|
len() | usize | Returns the number of fields in the tuple |
is_empty() | bool | Returns whether the tuple contains zero fields |
Both methods are implemented as const fn
, making them available for compile-time evaluation.
Generated Method Implementation Pattern
flowchart TD subgraph subGraph0["Generated Methods"] LM["len() method generation"] EM["is_empty() method generation"] end DS["DeriveInput (tuple struct)"] IF["impl_for_each()"] FN["field_num calculation"] IG["impl block for tuple struct"] TS["Generated TokenStream"] CC["Rust Compiler"] UM["User methods available"] CC --> UM DS --> IF EM --> IG FN --> EM FN --> LM IF --> FN IG --> TS LM --> IG TS --> CC
Sources: src/lib.rs(L58 - L122)
Thelen()Method
Signature
pub const fn len(&self) -> usize
Behavior
Returns the compile-time constant representing the number of fields in the tuple struct. This value is determined during macro expansion by counting the fields in the DataStruct
.
Implementation Details
The method is generated with the field count as a literal value, making it a true compile-time constant:
pub const fn len(&self) -> usize {
#field_num // Literal field count
}
Usage Characteristics
- Compile-time evaluation: Can be used in const contexts
- Zero-cost: Resolves to a literal integer at compile time
- No runtime computation: Field count is baked into the binary
Sources: src/lib.rs(L89 - L92)
Theis_empty()Method
Signature
pub const fn is_empty(&self) -> bool
Behavior
Returns true
if the tuple has zero fields, false
otherwise. The implementation delegates to the len()
method for consistency.
Implementation Details
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
Edge Cases
For tuple structs with zero fields (unit-like structs), this method returns true
. However, such structs are uncommon in practice since they're typically represented as unit structs rather than tuple structs.
Sources: src/lib.rs(L94 - L97)
Method Generation Process
Code Generation Pipeline
flowchart TD subgraph subGraph0["Template Generation"] QLA["quote! len() method"] QIE["quote! is_empty() method"] end AST["syn::DeriveInput"] VF["validate Fields::Unnamed"] IE["impl_for_each() entry"] FC["strct.fields.len()"] FN["field_num variable"] IB["impl block assembly"] TS["proc_macro2::TokenStream"] CO["Compiler output"] AST --> VF FC --> FN FN --> QIE FN --> QLA IB --> TS IE --> FC QIE --> IB QLA --> IB TS --> CO VF --> IE
Sources: src/lib.rs(L58 - L98)
Usage Examples
Basic Metadata Access
use tuple_for_each::TupleForEach;
#[derive(TupleForEach)]
struct Triple(i32, String, bool);
let data = Triple(42, "hello".to_string(), true);
// Compile-time accessible
const TRIPLE_SIZE: usize = Triple(0, String::new(), false).len();
// Runtime usage
assert_eq!(data.len(), 3);
assert!(!data.is_empty());
Integration with Generic Code
#![allow(unused)] fn main() { fn process_tuple<T>(tuple: T) where T: /* has len() and is_empty() methods */ { if !tuple.is_empty() { println!("Processing tuple with {} fields", tuple.len()); // Process non-empty tuple } } }
Conditional Compilation
#[derive(TupleForEach)]
struct Config(u32, String);
const CONFIG_FIELDS: usize = Config(0, String::new()).len();
#[cfg(const_eval)]
const _: () = {
assert!(CONFIG_FIELDS > 0);
};
Sources: README.md(L20 - L28)
Implementation Architecture
Method Generation Context
flowchart TD subgraph subGraph3["Output Assembly"] QT["quote! impl block"] TS["TokenStream output"] end subgraph subGraph2["Method Generation"] LG["len() generation"] IG["is_empty() generation"] end subgraph subGraph1["Field Analysis"] FL["strct.fields.len()"] FN["field_num: usize"] end subgraph subGraph0["Input Processing"] TN["tuple_name: &Ident"] ST["strct: &DataStruct"] end FL --> FN FN --> IG FN --> LG IG --> QT LG --> QT QT --> TS ST --> FL TN --> QT
The methods are generated within the same impl
block that contains the iteration macros, ensuring they're part of a cohesive API surface for tuple manipulation.
Sources: src/lib.rs(L64 - L98)
Performance Characteristics
Compile-Time Properties
- Zero runtime cost: Both methods resolve to compile-time constants
- No memory overhead: No additional storage required for metadata
- Inlining friendly: Simple constant returns are trivially inlined
Runtime Behavior
len()
: Single instruction returning immediate valueis_empty()
: Single comparison against zero, often optimized away
Const Evaluation
Both methods are const fn
, enabling their use in:
- Const contexts and const generics
- Array size specifications
- Compile-time assertions
- Static initialization
Sources: src/lib.rs(L89 - L97)
Overview
Relevant source files
Purpose and Scope
The allocator
crate provides a unified interface for various memory allocation algorithms in Rust. It abstracts different allocation strategies behind common traits, allowing users to select the most appropriate allocator for their use case through feature flags. The crate serves as a wrapper around specialized external allocator implementations, providing consistent APIs and error handling across all variants.
This document covers the core architecture, trait system, and high-level design of the allocator crate. For detailed information about specific allocator implementations, see Allocator Implementations. For usage examples and configuration guidance, see Usage and Configuration.
Core Architecture
The allocator crate is built around a trait-based architecture that defines three primary allocator categories, each serving different memory management needs:
Primary Allocator Traits
flowchart TD BaseAllocator["BaseAllocator• init(start, size)• add_memory(start, size)"] ByteAllocator["ByteAllocator• alloc(layout)• dealloc(pos, layout)• total_bytes()• used_bytes()"] PageAllocator["PageAllocator• PAGE_SIZE: usize• alloc_pages(num_pages, align_pow2)• dealloc_pages(pos, num_pages)• total_pages()"] IdAllocator["IdAllocator• alloc_id(count, align_pow2)• dealloc_id(start_id, count)• is_allocated(id)• alloc_fixed_id(id)"] BaseAllocator --> ByteAllocator BaseAllocator --> IdAllocator BaseAllocator --> PageAllocator
Sources: src/lib.rs(L54 - L131)
Concrete Implementation Mapping
flowchart TD subgraph subGraph2["External Dependencies"] buddy_system_allocator["buddy_system_allocator"] slab_allocator["slab_allocator"] rlsf["rlsf"] bitmap_allocator["bitmap-allocator"] end subgraph subGraph1["Implementation Layer"] BuddyByteAllocator["BuddyByteAllocatorfeature: buddy"] SlabByteAllocator["SlabByteAllocatorfeature: slab"] TlsfByteAllocator["TlsfByteAllocatorfeature: tlsf"] BitmapPageAllocator["BitmapPageAllocatorfeature: bitmap"] end subgraph subGraph0["Trait Layer"] ByteAllocator_trait["ByteAllocator"] PageAllocator_trait["PageAllocator"] end BitmapPageAllocator --> bitmap_allocator BuddyByteAllocator --> buddy_system_allocator ByteAllocator_trait --> BuddyByteAllocator ByteAllocator_trait --> SlabByteAllocator ByteAllocator_trait --> TlsfByteAllocator PageAllocator_trait --> BitmapPageAllocator SlabByteAllocator --> slab_allocator TlsfByteAllocator --> rlsf
Sources: src/lib.rs(L14 - L32) Cargo.toml(L16 - L34)
Feature-Gated Compilation System
The crate uses Cargo features to enable selective compilation of allocator implementations, minimizing binary size when only specific allocators are needed:
Feature Configuration
Feature | Purpose | External Dependency |
---|---|---|
bitmap | EnableBitmapPageAllocator | bitmap-allocator v0.2 |
buddy | EnableBuddyByteAllocator | buddy_system_allocator v0.10 |
slab | EnableSlabByteAllocator | slab_allocator v0.3.1 |
tlsf | EnableTlsfByteAllocator | rlsf v0.2 |
allocator_api | EnableAllocatorRcwrapper | None (stdlib integration) |
page-alloc-* | Configure page allocator size limits | None |
Sources: Cargo.toml(L12 - L27)
Conditional Compilation Flow
Sources: src/lib.rs(L14 - L32) src/lib.rs(L151 - L196)
Error Handling System
The crate defines a unified error handling approach through the AllocError
enum and AllocResult
type alias:
// Core error types from the codebase
pub enum AllocError {
InvalidParam, // Invalid size or align_pow2
MemoryOverlap, // Memory regions overlap
NoMemory, // Insufficient memory
NotAllocated, // Deallocation of unallocated region
}
pub type AllocResult<T = ()> = Result<T, AllocError>;
Sources: src/lib.rs(L37 - L51)
Standard Library Integration
When the allocator_api
feature is enabled, the crate provides AllocatorRc<A>
, which wraps any ByteAllocator
implementation in Rc<RefCell<A>>
and implements the standard library's core::alloc::Allocator
trait:
flowchart TD ByteAllocator_impl["ByteAllocator Implementation(BuddyByteAllocator, etc.)"] AllocatorRc["AllocatorRc<A>Rc<RefCell<A>>"] StdAllocator["core::alloc::AllocatorStandard Library Trait"] AllocatorRc --> StdAllocator ByteAllocator_impl --> AllocatorRc
Sources: src/lib.rs(L151 - L196)
Key Components Summary
Component | File | Purpose |
---|---|---|
Core traits | src/lib.rs54-131 | Define allocator interfaces |
Error types | src/lib.rs37-51 | Unified error handling |
Alignment utilities | src/lib.rs133-149 | Memory alignment helpers |
Standard library bridge | src/lib.rs151-196 | Integration withcore::alloc::Allocator |
Feature configuration | Cargo.toml12-34 | Conditional compilation setup |
The crate architecture emphasizes modularity through feature gates, consistency through shared traits, and flexibility through multiple allocation strategies while maintaining a minimal footprint when only specific allocators are needed.
Sources: Cargo.toml(L1 - L44) src/lib.rs(L1 - L197)
Architecture and Design
Relevant source files
This page documents the core architectural design of the allocator crate, including its trait-based interface system, error handling mechanisms, and feature-gated compilation model. The design provides a unified API for different memory allocation algorithms while maintaining modularity and minimal dependencies.
For implementation details of specific allocators, see Allocator Implementations. For practical usage guidance, see Usage and Configuration.
Core Trait Hierarchy
The allocator crate employs a trait-based architecture that separates allocation concerns by granularity and use case. All allocators inherit from a common BaseAllocator
trait and specialize into three distinct categories.
Trait System Overview
flowchart TD subgraph Methods["Methods"] BaseAllocator["BaseAllocator"] ByteAllocator["ByteAllocator"] PageAllocator["PageAllocator"] IdAllocator["IdAllocator"] init["init(start, size)"] add_memory["add_memory(start, size)"] alloc["alloc(layout)"] dealloc["dealloc(pos, layout)"] total_bytes["total_bytes()"] used_bytes["used_bytes()"] available_bytes["available_bytes()"] alloc_pages["alloc_pages(num_pages, align_pow2)"] dealloc_pages["dealloc_pages(pos, num_pages)"] total_pages["total_pages()"] alloc_id["alloc_id(count, align_pow2)"] dealloc_id["dealloc_id(start_id, count)"] is_allocated["is_allocated(id)"] end BuddyByteAllocator["BuddyByteAllocator"] SlabByteAllocator["SlabByteAllocator"] TlsfByteAllocator["TlsfByteAllocator"] BitmapPageAllocator["BitmapPageAllocator"] BaseAllocator --> ByteAllocator BaseAllocator --> IdAllocator BaseAllocator --> PageAllocator BaseAllocator --> add_memory BaseAllocator --> init ByteAllocator --> BuddyByteAllocator ByteAllocator --> SlabByteAllocator ByteAllocator --> TlsfByteAllocator ByteAllocator --> alloc ByteAllocator --> available_bytes ByteAllocator --> dealloc ByteAllocator --> total_bytes ByteAllocator --> used_bytes IdAllocator --> alloc_id IdAllocator --> dealloc_id IdAllocator --> is_allocated PageAllocator --> BitmapPageAllocator PageAllocator --> alloc_pages PageAllocator --> dealloc_pages PageAllocator --> total_pages
Sources: src/lib.rs(L54 - L131)
BaseAllocator Trait
The BaseAllocator
trait provides fundamental memory management operations shared by all allocator types:
Method | Purpose | Parameters |
---|---|---|
init() | Initialize allocator with initial memory region | start: usize, size: usize |
add_memory() | Add additional memory regions | start: usize, size: usize→AllocResult |
Sources: src/lib.rs(L54 - L60)
ByteAllocator Trait
The ByteAllocator
trait handles byte-granularity allocation using Rust's Layout
type for size and alignment specification:
Method | Purpose | Return Type |
---|---|---|
alloc() | Allocate memory with layout constraints | AllocResult<NonNull |
dealloc() | Deallocate previously allocated memory | () |
total_bytes() | Query total memory pool size | usize |
used_bytes() | Query currently allocated bytes | usize |
available_bytes() | Query remaining available bytes | usize |
Sources: src/lib.rs(L62 - L78)
PageAllocator Trait
The PageAllocator
trait manages memory at page granularity with configurable page sizes:
Method | Purpose | Parameters |
---|---|---|
alloc_pages() | Allocate contiguous pages | num_pages: usize, align_pow2: usize |
alloc_pages_at() | Allocate pages at specific address | base: usize, num_pages: usize, align_pow2: usize |
dealloc_pages() | Deallocate page range | pos: usize, num_pages: usize |
Sources: src/lib.rs(L80 - L107)
IdAllocator Trait
The IdAllocator
trait provides unique identifier allocation with support for fixed ID assignment:
Method | Purpose | Return Type |
---|---|---|
alloc_id() | Allocate contiguous ID range | AllocResult |
alloc_fixed_id() | Reserve specific ID | AllocResult |
is_allocated() | Check ID allocation status | bool |
Sources: src/lib.rs(L109 - L131)
Error Handling System
The crate implements a comprehensive error handling system using custom error types that map to common allocation failure scenarios.
AllocError Enumeration
flowchart TD AllocResult["AllocResult<T>"] Ok["Ok(T)"] Err["Err(AllocError)"] InvalidParam["InvalidParam"] MemoryOverlap["MemoryOverlap"] NoMemory["NoMemory"] NotAllocated["NotAllocated"] InvalidDesc["Invalid size or align_pow2"] OverlapDesc["Memory regions overlap"] NoMemDesc["Insufficient free memory"] NotAllocDesc["Deallocating unallocated region"] AllocResult --> Err AllocResult --> Ok Err --> InvalidParam Err --> MemoryOverlap Err --> NoMemory Err --> NotAllocated InvalidParam --> InvalidDesc MemoryOverlap --> OverlapDesc NoMemory --> NoMemDesc NotAllocated --> NotAllocDesc
Sources: src/lib.rs(L37 - L51)
Error Categories
Error Variant | Trigger Condition | Usage Context |
---|---|---|
InvalidParam | Invalid size or alignment parameters | Input validation |
MemoryOverlap | Overlapping memory regions inadd_memory() | Memory pool expansion |
NoMemory | Insufficient free memory for allocation | Runtime allocation |
NotAllocated | Deallocation of unallocated memory | Memory safety |
The AllocResult<T>
type alias simplifies error handling throughout the API by defaulting to Result<T, AllocError>
.
Sources: src/lib.rs(L37 - L51)
Feature-Gated Architecture
The crate uses Cargo features to enable conditional compilation of allocator implementations, minimizing binary size and dependencies for applications that only need specific allocation algorithms.
Feature-Based Compilation
Sources: src/lib.rs(L14 - L32) src/lib.rs(L151 - L196)
Conditional Compilation Strategy
The feature system works through conditional compilation attributes:
- Each allocator implementation resides in a separate module
- Module inclusion controlled by
#[cfg(feature = "name")]
attributes - Public re-exports controlled by the same feature flags
- External dependencies only included when corresponding features are enabled
This design allows applications to include only required allocators, reducing binary size and compilation time.
Sources: src/lib.rs(L14 - L32)
Standard Library Integration
The optional allocator_api
feature provides integration with Rust's standard library allocator infrastructure through the AllocatorRc
wrapper.
AllocatorRc Implementation
flowchart TD subgraph subGraph1["Error Mapping"] AllocError_internal["super::AllocError"] AllocError_std["core::alloc::AllocError"] end subgraph subGraph0["Trait Implementations"] AllocatorRc["AllocatorRc<A: ByteAllocator>"] allocate_impl["allocate(layout: Layout)"] deallocate_impl["deallocate(ptr: NonNull<u8>, layout: Layout)"] Allocator["core::alloc::Allocator"] Clone["Clone"] end RcRefCell["Rc<RefCell<A>>"] new["new(inner: A, pool: &mut [u8])"] AllocError_internal --> AllocError_std AllocatorRc --> Clone AllocatorRc --> RcRefCell AllocatorRc --> allocate_impl AllocatorRc --> deallocate_impl AllocatorRc --> new allocate_impl --> AllocError_internal allocate_impl --> Allocator deallocate_impl --> Allocator
Sources: src/lib.rs(L151 - L196)
Integration Mechanics
The AllocatorRc
wrapper provides:
Component | Purpose | Implementation |
---|---|---|
Rc<RefCell> | Shared ownership and interior mutability | Enables cloning while maintaining mutable access |
Allocatortrait | Standard library compatibility | Mapsallocate()/deallocate()to trait methods |
Error conversion | Error type compatibility | ConvertsAllocErrortocore::alloc::AllocError |
Zero-size handling | Standard compliance | Returns dangling pointer for zero-size allocations |
Sources: src/lib.rs(L162 - L186)
Utility Functions
The crate provides alignment utility functions that support the core allocation operations:
Alignment Operations
flowchart TD subgraph subGraph0["Bit Operations"] down_result["pos & !(align - 1)"] up_result["(pos + align - 1) & !(align - 1)"] aligned_result["base_addr & (align - 1) == 0"] mask_clear["Clear low bits"] round_up["Round up then clear"] check_remainder["Check remainder is zero"] end input_addr["Input Address"] align_down["align_down(pos, align)"] align_up["align_up(pos, align)"] is_aligned["is_aligned(base_addr, align)"] align_down --> down_result align_up --> up_result aligned_result --> check_remainder down_result --> mask_clear input_addr --> align_down input_addr --> align_up input_addr --> is_aligned is_aligned --> aligned_result up_result --> round_up
Sources: src/lib.rs(L133 - L149)
Function Specifications
Function | Purpose | Algorithm | Constraints |
---|---|---|---|
align_down() | Round address down to alignment boundary | pos & !(align - 1) | alignmust be power of two |
align_up() | Round address up to alignment boundary | (pos + align - 1) & !(align - 1) | alignmust be power of two |
is_aligned() | Check if address meets alignment requirement | base_addr & (align - 1) == 0 | alignmust be power of two |
These functions are marked const fn
to enable compile-time evaluation and are used throughout the allocator implementations for address calculations.
Sources: src/lib.rs(L133 - L149)
Allocator Implementations
Relevant source files
This document provides an overview of the different memory allocator implementations available in the allocator crate. It explains how each implementation relates to the trait system and their intended use cases within the unified allocator interface.
For detailed information about the core trait architecture that these implementations use, see Architecture and Design. For usage guidance and feature configuration, see Usage and Configuration.
Implementation Overview
The allocator crate provides four distinct allocator implementations, each optimized for different allocation patterns and granularities. These implementations are feature-gated, allowing users to compile only the allocators they need.
Implementation Categories
The implementations are organized according to the three main allocator trait categories:
Implementation | Trait Category | Granularity | Feature Gate | Use Case |
---|---|---|---|---|
BitmapPageAllocator | PageAllocator | Page-level | bitmap | Large contiguous allocations |
BuddyByteAllocator | ByteAllocator | Byte-level | buddy | General-purpose with low fragmentation |
SlabByteAllocator | ByteAllocator | Byte-level | slab | Fixed-size object allocation |
TlsfByteAllocator | ByteAllocator | Byte-level | tlsf | Real-time systems requiring deterministic allocation |
Implementation Architecture
flowchart TD subgraph subGraph3["Standard Library Integration"] AllocatorRc["AllocatorRc[lib.rs:162]feature: allocator_api"] StdAllocator["core::alloc::AllocatorStandard Library Trait"] end subgraph subGraph2["Page-Level Implementations"] BitmapPageAllocator["BitmapPageAllocator[bitmap.rs]feature: bitmap"] end subgraph subGraph1["Byte-Level Implementations"] BuddyByteAllocator["BuddyByteAllocator[buddy.rs]feature: buddy"] SlabByteAllocator["SlabByteAllocator[slab.rs]feature: slab"] TlsfByteAllocator["TlsfByteAllocator[tlsf.rs]feature: tlsf"] end subgraph subGraph0["Core Traits [lib.rs:54-131]"] BaseAllocator["BaseAllocator• init(start, size)• add_memory(start, size)"] ByteAllocator["ByteAllocator• alloc(layout)• dealloc(pos, layout)• memory statistics"] PageAllocator["PageAllocator• alloc_pages(num, align)• dealloc_pages(pos, num)• page statistics"] IdAllocator["IdAllocator• alloc_id(count, align)• dealloc_id(start, count)• id management"] end AllocatorRc --> StdAllocator BaseAllocator --> ByteAllocator BaseAllocator --> IdAllocator BaseAllocator --> PageAllocator ByteAllocator --> AllocatorRc ByteAllocator --> BuddyByteAllocator ByteAllocator --> SlabByteAllocator ByteAllocator --> TlsfByteAllocator PageAllocator --> BitmapPageAllocator
Sources: src/lib.rs(L1 - L197)
Feature-Gated Compilation
Sources: src/lib.rs(L14 - L32) src/lib.rs(L151 - L196)
Allocator Characteristics
Memory Granularity Comparison
The implementations operate at different levels of granularity, affecting their suitability for different use cases:
Byte-Level Allocators
BuddyByteAllocator
: Uses buddy system algorithm for efficient memory management with power-of-two sized blocksSlabByteAllocator
: Optimized for allocating many objects of the same size with minimal overheadTlsfByteAllocator
: Two-Level Segregated Fit algorithm providing constant-time allocation suitable for real-time systems
Page-Level Allocators
BitmapPageAllocator
: Manages memory at page granularity using bitmap tracking for large contiguous allocations
Error Handling
All implementations use the unified error handling system defined in the core traits:
flowchart TD AllocResult["AllocResult[lib.rs:51]"] AllocError["AllocError[lib.rs:38-48]"] InvalidParam["InvalidParamInvalid size/alignment"] MemoryOverlap["MemoryOverlapOverlapped memory regions"] NoMemory["NoMemoryInsufficient memory"] NotAllocated["NotAllocatedInvalid deallocation"] AllocError --> InvalidParam AllocError --> MemoryOverlap AllocError --> NoMemory AllocError --> NotAllocated AllocResult --> AllocError
Sources: src/lib.rs(L37 - L51)
Implementation Selection Guide
Performance Characteristics
Allocator | Allocation Speed | Deallocation Speed | Memory Overhead | Fragmentation Resistance |
---|---|---|---|---|
BuddyByteAllocator | O(log n) | O(log n) | Moderate | Good |
SlabByteAllocator | O(1) | O(1) | Low | Excellent (same-size) |
TlsfByteAllocator | O(1) | O(1) | Low | Good |
BitmapPageAllocator | O(n) | O(1) | Very low | Excellent |
Use Case Recommendations
- General-purpose applications:
BuddyByteAllocator
provides good balance of performance and memory efficiency - Object-oriented systems with many same-sized allocations:
SlabByteAllocator
excels at allocating uniform objects - Real-time systems:
TlsfByteAllocator
offers deterministic allocation times - Systems requiring large contiguous memory blocks:
BitmapPageAllocator
efficiently manages page-sized allocations
Standard Library Integration
The AllocatorRc
wrapper enables integration with Rust's standard library allocator API when the allocator_api
feature is enabled. This allows byte allocators to be used with standard collections and memory management primitives.
The wrapper implements the core::alloc::Allocator
trait by wrapping any ByteAllocator
implementation in Rc<RefCell<_>>
for shared access patterns typical in standard library usage.
Sources: src/lib.rs(L151 - L196)
Implementation Details
For detailed documentation of each allocator implementation, including algorithm specifics, configuration options, and usage examples:
- Bitmap Page Allocator - Page-granularity allocation using bitmap tracking
- Buddy System Allocator - Binary tree-based allocation with buddy merging
- Slab Allocator - Cache-friendly allocation for uniform object sizes
- TLSF Allocator - Two-level segregated fit for real-time constraints
Bitmap Page Allocator
Relevant source files
This document covers the BitmapPageAllocator
implementation, which provides page-granularity memory allocation using a bitmap-based approach. The allocator manages memory in fixed-size pages and uses an external bitmap data structure to track allocation status.
For byte-granularity allocation algorithms, see Buddy System Allocator, Slab Allocator, and TLSF Allocator. For broader architectural concepts and trait definitions, see Architecture and Design.
Overview and Purpose
The BitmapPageAllocator
is a page-granularity allocator that internally uses a bitmap where each bit indicates whether a page has been allocated. It wraps the external bitmap_allocator
crate and implements the PageAllocator
and BaseAllocator
traits defined in the core library.
Core Allocator Structure
classDiagram class BitmapPageAllocator { +PAGE_SIZE: usize -base: usize -total_pages: usize -used_pages: usize -inner: BitAllocUsed +new() BitmapPageAllocator +alloc_pages(num_pages, align_pow2) AllocResult~usize~ +alloc_pages_at(base, num_pages, align_pow2) AllocResult~usize~ +dealloc_pages(pos, num_pages) +total_pages() usize +used_pages() usize +available_pages() usize } class BaseAllocator { <<interface>> +init(start, size) +add_memory(start, size) AllocResult } class PageAllocator { <<interface>> +PAGE_SIZE: usize +alloc_pages(num_pages, align_pow2) AllocResult~usize~ +dealloc_pages(pos, num_pages) +total_pages() usize +used_pages() usize +available_pages() usize } class BitAllocUsed { <<external>> +alloc() Option~usize~ +alloc_contiguous(start, num, align_log2) Option~usize~ +dealloc(idx) bool +dealloc_contiguous(start, num) bool +insert(range) } PageAllocator --|> BaseAllocator BitmapPageAllocator --|> PageAllocator BitmapPageAllocator *-- BitAllocUsed
Sources: src/bitmap.rs(L34 - L50) src/bitmap.rs(L53 - L75) src/bitmap.rs(L77 - L160)
Memory Size Configuration
The allocator uses feature flags to configure the maximum memory size it can manage. Different BitAlloc
implementations from the external bitmap_allocator
crate are selected based on compilation features.
Feature-Based BitAlloc Selection
flowchart TD subgraph subGraph2["Memory Capacity"] CAP_4G["4GB Memory(Testing)"] CAP_1T["1TB Memory"] CAP_64G["64GB Memory"] CAP_256M["256MB Memory(Default)"] end subgraph subGraph1["BitAlloc Types"] BITALLOC_1M["BitAlloc1M1M pages = 4GB max"] BITALLOC_256M["BitAlloc256M256M pages = 1TB max"] BITALLOC_16M["BitAlloc16M16M pages = 64GB max"] BITALLOC_64K["BitAlloc64K64K pages = 256MB max"] end subgraph subGraph0["Feature Flags"] TEST["cfg(test)"] FEAT_1T["page-alloc-1t"] FEAT_64G["page-alloc-64g"] FEAT_4G["page-alloc-4g"] DEFAULT["default/page-alloc-256m"] end BITALLOC_16M --> CAP_64G BITALLOC_1M --> CAP_4G BITALLOC_256M --> CAP_1T BITALLOC_64K --> CAP_256M DEFAULT --> BITALLOC_64K FEAT_1T --> BITALLOC_256M FEAT_4G --> BITALLOC_1M FEAT_64G --> BITALLOC_16M TEST --> BITALLOC_1M
Sources: src/bitmap.rs(L9 - L26)
The configuration table shows the relationship between features and memory limits:
Feature Flag | BitAlloc Type | Max Pages | Max Memory (4KB pages) |
---|---|---|---|
test | BitAlloc1M | 1,048,576 | 4GB |
page-alloc-1t | BitAlloc256M | 268,435,456 | 1TB |
page-alloc-64g | BitAlloc16M | 16,777,216 | 64GB |
page-alloc-4g | BitAlloc1M | 1,048,576 | 4GB |
page-alloc-256m(default) | BitAlloc64K | 65,536 | 256MB |
Core Implementation Details
Memory Layout and Base Address Calculation
The allocator manages memory regions by calculating a base address aligned to 1GB boundaries and tracking page indices relative to this base.
flowchart TD subgraph subGraph2["Bitmap Initialization"] INSERT_RANGE["inner.insert(start_idx..start_idx + total_pages)"] end subgraph subGraph1["Base Address Calculation"] CALC_BASE["base = align_down(start, MAX_ALIGN_1GB)"] REL_START["relative_start = start - base"] START_IDX["start_idx = relative_start / PAGE_SIZE"] end subgraph subGraph0["Memory Region Setup"] INPUT_START["Input: start, size"] ALIGN_START["align_up(start, PAGE_SIZE)"] ALIGN_END["align_down(start + size, PAGE_SIZE)"] CALC_TOTAL["total_pages = (end - start) / PAGE_SIZE"] end ALIGN_END --> CALC_TOTAL ALIGN_START --> CALC_BASE ALIGN_START --> CALC_TOTAL CALC_BASE --> REL_START CALC_TOTAL --> INSERT_RANGE INPUT_START --> ALIGN_END INPUT_START --> ALIGN_START REL_START --> START_IDX START_IDX --> INSERT_RANGE
Sources: src/bitmap.rs(L54 - L70) src/bitmap.rs(L7)
Page Allocation Process
The allocator provides two allocation methods: general allocation with alignment requirements and allocation at specific addresses.
sequenceDiagram participant Client as Client participant BitmapPageAllocator as BitmapPageAllocator participant BitAllocUsed as BitAllocUsed Note over Client,BitAllocUsed: General Page Allocation Client ->> BitmapPageAllocator: alloc_pages(num_pages, align_pow2) BitmapPageAllocator ->> BitmapPageAllocator: Validate alignment parameters BitmapPageAllocator ->> BitmapPageAllocator: Convert align_pow2 to page units alt num_pages == 1 BitmapPageAllocator ->> BitAllocUsed: alloc() else num_pages > 1 BitmapPageAllocator ->> BitAllocUsed: alloc_contiguous(None, num_pages, align_log2) end BitAllocUsed -->> BitmapPageAllocator: Option<page_idx> BitmapPageAllocator ->> BitmapPageAllocator: Convert to address: idx * PAGE_SIZE + base BitmapPageAllocator ->> BitmapPageAllocator: Update used_pages count BitmapPageAllocator -->> Client: AllocResult<usize> Note over Client,BitAllocUsed: Specific Address Allocation Client ->> BitmapPageAllocator: alloc_pages_at(base, num_pages, align_pow2) BitmapPageAllocator ->> BitmapPageAllocator: Validate alignment and base address BitmapPageAllocator ->> BitmapPageAllocator: Calculate target page index BitmapPageAllocator ->> BitAllocUsed: alloc_contiguous(Some(idx), num_pages, align_log2) BitAllocUsed -->> BitmapPageAllocator: Option<page_idx> BitmapPageAllocator -->> Client: AllocResult<usize>
Sources: src/bitmap.rs(L80 - L100) src/bitmap.rs(L103 - L131)
Allocation Constraints and Validation
The allocator enforces several constraints to ensure correct operation:
Alignment Validation Process
flowchart TD subgraph Results["Results"] VALID["Valid Parameters"] INVALID["AllocError::InvalidParam"] end subgraph subGraph2["Parameter Validation"] CHECK_NUM_PAGES["num_pages > 0"] CHECK_PAGE_SIZE["PAGE_SIZE.is_power_of_two()"] end subgraph subGraph1["Address Validation (alloc_pages_at)"] CHECK_BASE_ALIGN["is_aligned(base, align_pow2)"] end subgraph subGraph0["Alignment Checks"] CHECK_MAX["align_pow2 <= MAX_ALIGN_1GB"] CHECK_PAGE_ALIGN["is_aligned(align_pow2, PAGE_SIZE)"] CHECK_POW2["(align_pow2 / PAGE_SIZE).is_power_of_two()"] end CHECK_BASE_ALIGN --> INVALID CHECK_BASE_ALIGN --> VALID CHECK_MAX --> INVALID CHECK_MAX --> VALID CHECK_NUM_PAGES --> INVALID CHECK_NUM_PAGES --> VALID CHECK_PAGE_ALIGN --> INVALID CHECK_PAGE_ALIGN --> VALID CHECK_PAGE_SIZE --> VALID CHECK_POW2 --> INVALID CHECK_POW2 --> VALID
Sources: src/bitmap.rs(L82 - L88) src/bitmap.rs(L111 - L121) src/bitmap.rs(L55)
Key constraints include:
PAGE_SIZE
must be a power of two- Maximum alignment is 1GB (
MAX_ALIGN_1GB = 0x4000_0000
) - Alignment must be a multiple of
PAGE_SIZE
- Alignment (in page units) must be a power of two
- For
alloc_pages_at
, the base address must be aligned to the requested alignment
Memory Management Operations
Deallocation Process
flowchart TD subgraph subGraph1["Deallocation Flow"] INPUT["dealloc_pages(pos, num_pages)"] ASSERT_ALIGN["assert: pos aligned to PAGE_SIZE"] CALC_IDX["idx = (pos - base) / PAGE_SIZE"] UPDATE_COUNT["used_pages -= num_pages"] subgraph subGraph0["Bitmap Update"] SINGLE["num_pages == 1:inner.dealloc(idx)"] MULTI["num_pages > 1:inner.dealloc_contiguous(idx, num_pages)"] end end ASSERT_ALIGN --> CALC_IDX CALC_IDX --> MULTI CALC_IDX --> SINGLE INPUT --> ASSERT_ALIGN MULTI --> UPDATE_COUNT SINGLE --> UPDATE_COUNT
Sources: src/bitmap.rs(L133 - L147)
Statistics Tracking
The allocator maintains three key statistics accessible through the PageAllocator
interface:
Method | Description | Implementation |
---|---|---|
total_pages() | Total pages managed | Returnsself.total_pages |
used_pages() | Currently allocated pages | Returnsself.used_pages |
available_pages() | Free pages remaining | Returnstotal_pages - used_pages |
Sources: src/bitmap.rs(L149 - L159)
Usage Patterns and Testing
The test suite demonstrates typical usage patterns and validates the allocator's behavior across different scenarios.
Single Page Allocation Example
sequenceDiagram participant TestCode as Test Code participant BitmapPageAllocator as BitmapPageAllocator TestCode ->> BitmapPageAllocator: new() TestCode ->> BitmapPageAllocator: init(PAGE_SIZE, PAGE_SIZE) Note over BitmapPageAllocator: total_pages=1, used_pages=0 TestCode ->> BitmapPageAllocator: alloc_pages(1, PAGE_SIZE) BitmapPageAllocator -->> TestCode: addr = 0x1000 Note over BitmapPageAllocator: used_pages=1, available_pages=0 TestCode ->> BitmapPageAllocator: dealloc_pages(0x1000, 1) Note over BitmapPageAllocator: used_pages=0, available_pages=1 TestCode ->> BitmapPageAllocator: alloc_pages(1, PAGE_SIZE) BitmapPageAllocator -->> TestCode: addr = 0x1000 (reused)
Sources: src/bitmap.rs(L168 - L190)
Large Memory Region Testing
The test suite validates operation with large memory regions (2GB) and various allocation patterns:
- Sequential allocation/deallocation of 1, 10, 100, 1000 pages
- Alignment testing with powers of 2 from
PAGE_SIZE
toMAX_ALIGN_1GB
- Multiple concurrent allocations with different alignments
- Specific address allocation using
alloc_pages_at
Sources: src/bitmap.rs(L193 - L327)
The allocator demonstrates robust behavior across different memory sizes and allocation patterns while maintaining efficient bitmap-based tracking of page allocation status.
Buddy System Allocator
Relevant source files
This document covers the BuddyByteAllocator
implementation, which provides byte-granularity memory allocation using the buddy system algorithm. This allocator is one of several byte-level allocators available in the crate and is enabled through the buddy
feature flag.
For information about the overall trait architecture and how allocators integrate with the system, see Architecture and Design. For other byte-level allocator implementations, see Slab Allocator and TLSF Allocator.
Implementation Overview
The BuddyByteAllocator
serves as a wrapper around the external buddy_system_allocator::Heap
crate, adapting it to the crate's trait-based interface. The implementation focuses on providing a consistent API while leveraging the performance characteristics of the buddy system algorithm.
Core Structure
flowchart TD subgraph subGraph3["Key Methods"] INIT["init(start, size)"] ADD["add_memory(start, size)"] ALLOC["alloc(layout)"] DEALLOC["dealloc(pos, layout)"] STATS["total_bytes(), used_bytes(), available_bytes()"] end subgraph subGraph2["External Dependency"] EXT["buddy_system_allocator::Heap<32>"] end subgraph subGraph1["Trait Implementations"] BASE["BaseAllocator"] BYTE["ByteAllocator"] end subgraph subGraph0["BuddyByteAllocator Structure"] BUDDY["BuddyByteAllocator"] INNER["inner: Heap<32>"] end BASE --> ADD BASE --> INIT BUDDY --> BASE BUDDY --> BYTE BUDDY --> INNER BYTE --> ALLOC BYTE --> DEALLOC BYTE --> STATS INNER --> EXT
Sources: src/buddy.rs(L14 - L25)
The allocator uses a fixed const generic parameter of 32, which determines the maximum order of buddy blocks that can be allocated from the underlying Heap<32>
implementation.
Component | Type | Purpose |
---|---|---|
BuddyByteAllocator | Struct | Main allocator wrapper |
inner | Heap<32> | Underlying buddy system implementation |
Generic Parameter | 32 | Maximum buddy block order |
Trait Implementation Details
BaseAllocator Implementation
The BaseAllocator
trait provides fundamental memory management operations for initializing and expanding the allocator's memory pool.
flowchart TD subgraph subGraph2["Safety Considerations"] UNSAFE_INIT["unsafe { inner.init() }"] UNSAFE_ADD["unsafe { inner.add_to_heap() }"] end subgraph subGraph1["Internal Operations"] HEAP_INIT["inner.init(start, size)"] HEAP_ADD["inner.add_to_heap(start, start + size)"] end subgraph subGraph0["BaseAllocator Methods"] INIT_CALL["init(start: usize, size: usize)"] ADD_CALL["add_memory(start: usize, size: usize)"] end ADD_CALL --> HEAP_ADD HEAP_ADD --> UNSAFE_ADD HEAP_INIT --> UNSAFE_INIT INIT_CALL --> HEAP_INIT
Sources: src/buddy.rs(L27 - L36)
The init
method performs initial setup of the memory region, while add_memory
allows dynamic expansion of the allocator's available memory pool. Both operations delegate to the underlying Heap
implementation using unsafe blocks.
ByteAllocator Implementation
The ByteAllocator
trait provides the core allocation and deallocation interface along with memory usage statistics.
flowchart TD subgraph Statistics["Statistics"] TOTAL["total_bytes()"] STATS_TOTAL["inner.stats_total_bytes()"] USED["used_bytes()"] STATS_ACTUAL["inner.stats_alloc_actual()"] AVAILABLE["available_bytes()"] CALC_AVAIL["total - actual"] DEALLOC_REQ["dealloc(pos: NonNull, layout: Layout)"] HEAP_DEALLOC["inner.dealloc(pos, layout)"] ALLOC_REQ["alloc(layout: Layout)"] HEAP_ALLOC["inner.alloc(layout)"] end subgraph subGraph0["Allocation Flow"] ERROR_MAP["map_err(|_| AllocError::NoMemory)"] RESULT["AllocResult>"] subgraph subGraph1["Deallocation Flow"] TOTAL["total_bytes()"] STATS_TOTAL["inner.stats_total_bytes()"] DEALLOC_REQ["dealloc(pos: NonNull, layout: Layout)"] HEAP_DEALLOC["inner.dealloc(pos, layout)"] ALLOC_REQ["alloc(layout: Layout)"] HEAP_ALLOC["inner.alloc(layout)"] end end ALLOC_REQ --> HEAP_ALLOC AVAILABLE --> CALC_AVAIL DEALLOC_REQ --> HEAP_DEALLOC ERROR_MAP --> RESULT HEAP_ALLOC --> ERROR_MAP TOTAL --> STATS_TOTAL USED --> STATS_ACTUAL
Sources: src/buddy.rs(L38 - L58)
Method | Return Type | Description |
---|---|---|
alloc | AllocResult<NonNull | Allocates memory according to layout requirements |
dealloc | () | Deallocates previously allocated memory |
total_bytes | usize | Total memory managed by allocator |
used_bytes | usize | Currently allocated memory |
available_bytes | usize | Calculated as total minus used bytes |
Memory Management Characteristics
Buddy System Algorithm
The buddy system algorithm manages memory by maintaining blocks in powers of two sizes. When a block is split, it creates two "buddy" blocks that can be efficiently merged when both become free.
Error Handling
The allocator implements a simple error mapping strategy where allocation failures from the underlying Heap
are converted to AllocError::NoMemory
. This provides a consistent error interface across all allocator implementations in the crate.
Memory Statistics
The implementation provides three key statistics through delegation to the underlying heap:
- Total bytes: Complete memory pool size managed by the allocator
- Used bytes: Currently allocated memory tracked by the heap
- Available bytes: Computed difference representing allocatable memory
Sources: src/buddy.rs(L47 - L57)
Integration with Feature System
The BuddyByteAllocator
is conditionally compiled based on the buddy
feature flag defined in the crate's feature system. When enabled, it provides byte-granularity allocation as an alternative to page-based or other allocation strategies.
The allocator integrates with the crate's unified interface through the trait implementations, allowing it to be used interchangeably with other ByteAllocator
implementations depending on the application's requirements.
Sources: src/buddy.rs(L1 - L59)
Slab Allocator
Relevant source files
This document covers the SlabByteAllocator
implementation, which provides byte-granularity memory allocation using the slab allocation algorithm. The slab allocator is designed for efficient allocation and deallocation of fixed-size objects by pre-allocating chunks of memory organized into "slabs."
For information about other byte-level allocators, see Buddy System Allocator and TLSF Allocator. For page-granularity allocation, see Bitmap Page Allocator. For the overall trait architecture that this allocator implements, see Architecture and Design.
Architecture and Design
Trait Implementation Hierarchy
The SlabByteAllocator
implements both core allocator traits defined in the crate's trait system:
flowchart TD BaseAlloc["BaseAllocator"] ByteAlloc["ByteAllocator"] SlabImpl["SlabByteAllocator"] HeapWrapper["slab_allocator::Heap"] BaseInit["init(start, size)"] BaseAdd["add_memory(start, size)"] ByteAllocMethod["alloc(layout)"] ByteDealloc["dealloc(pos, layout)"] ByteStats["total_bytes(), used_bytes(), available_bytes()"] SlabNew["new()"] SlabInner["inner_mut(), inner()"] BaseAlloc --> BaseAdd BaseAlloc --> BaseInit BaseAlloc --> SlabImpl ByteAlloc --> ByteAllocMethod ByteAlloc --> ByteDealloc ByteAlloc --> ByteStats ByteAlloc --> SlabImpl SlabImpl --> HeapWrapper SlabImpl --> SlabInner SlabImpl --> SlabNew
Sources: src/slab.rs(L5 - L68)
External Dependency Integration
The SlabByteAllocator
serves as a wrapper around the external slab_allocator
crate, specifically the Heap
type:
flowchart TD UserCode["User Code"] SlabWrapper["SlabByteAllocator"] InternalHeap["Option"] ExternalCrate["slab_allocator v0.3.1"] TraitMethods["BaseAllocator + ByteAllocator methods"] HeapMethods["Heap::new(), allocate(), deallocate()"] InternalHeap --> ExternalCrate InternalHeap --> HeapMethods SlabWrapper --> InternalHeap SlabWrapper --> TraitMethods UserCode --> SlabWrapper
Sources: src/slab.rs(L8) src/slab.rs(L14)
Implementation Details
Struct Definition and State Management
The SlabByteAllocator
maintains minimal internal state, delegating the actual allocation logic to the wrapped Heap
:
Component | Type | Purpose |
---|---|---|
inner | Option | Wrapped slab allocator instance |
The Option
wrapper allows for lazy initialization through the BaseAllocator::init
method.
Sources: src/slab.rs(L13 - L15)
Construction and Initialization
sequenceDiagram participant User as User participant SlabByteAllocator as SlabByteAllocator participant Heap as Heap User ->> SlabByteAllocator: new() Note over Heap,SlabByteAllocator: inner = None User ->> SlabByteAllocator: init(start, size) SlabByteAllocator ->> Heap: unsafe Heap::new(start, size) Heap -->> SlabByteAllocator: heap instance Note over Heap,SlabByteAllocator: inner = Some(heap)
The allocator follows a two-phase initialization pattern:
new()
creates an uninitialized allocator withinner = None
init(start, size)
creates the underlyingHeap
instance
Sources: src/slab.rs(L18 - L21) src/slab.rs(L33 - L35)
Memory Management Operations
The allocator provides internal accessor methods for safe access to the initialized heap:
flowchart TD AccessMethods["Accessor Methods"] InnerMut["inner_mut() -> &mut Heap"] Inner["inner() -> &Heap"] Unwrap["Option::as_mut().unwrap()"] MutOperations["alloc(), dealloc(), add_memory()"] ReadOperations["total_bytes(), used_bytes(), available_bytes()"] AccessMethods --> Inner AccessMethods --> InnerMut Inner --> ReadOperations Inner --> Unwrap InnerMut --> MutOperations InnerMut --> Unwrap
Sources: src/slab.rs(L23 - L29)
Memory Allocation API
BaseAllocator Implementation
The BaseAllocator
trait provides fundamental memory region management:
Method | Parameters | Behavior |
---|---|---|
init | start: usize, size: usize | Creates newHeapinstance for memory region |
add_memory | start: usize, size: usize | Adds additional memory region to existing heap |
Both methods work with raw memory addresses and sizes in bytes.
Sources: src/slab.rs(L32 - L43)
ByteAllocator Implementation
The ByteAllocator
trait provides fine-grained allocation operations:
flowchart TD ByteAllocator["ByteAllocator Trait"] AllocMethod["alloc(layout: Layout)"] DeallocMethod["dealloc(pos: NonNull, layout: Layout)"] StatsGroup["Memory Statistics"] AllocResult["AllocResult>"] ErrorMap["map_err(|_| AllocError::NoMemory)"] UnsafeDealloc["unsafe deallocate(ptr, layout)"] TotalBytes["total_bytes()"] UsedBytes["used_bytes()"] AvailableBytes["available_bytes()"] AllocMethod --> AllocResult AllocMethod --> ErrorMap ByteAllocator --> AllocMethod ByteAllocator --> DeallocMethod ByteAllocator --> StatsGroup DeallocMethod --> UnsafeDealloc StatsGroup --> AvailableBytes StatsGroup --> TotalBytes StatsGroup --> UsedBytes
Error Handling
The allocation method converts internal allocation failures to the standardized AllocError::NoMemory
variant, providing consistent error semantics across all allocator implementations in the crate.
Sources: src/slab.rs(L45 - L68)
Memory Statistics
The allocator exposes three memory statistics through delegation to the underlying Heap
:
Statistic | Description |
---|---|
total_bytes() | Total memory managed by allocator |
used_bytes() | Currently allocated memory |
available_bytes() | Free memory available for allocation |
Sources: src/slab.rs(L57 - L67)
Slab Allocation Characteristics
The slab allocator is particularly efficient for workloads involving:
- Frequent allocation and deallocation of similar-sized objects
- Scenarios where memory fragmentation needs to be minimized
- Applications requiring predictable allocation performance
The underlying slab_allocator::Heap
implementation handles the complexity of slab management, chunk organization, and free list maintenance, while the wrapper provides integration with the crate's trait system and error handling conventions.
Sources: src/slab.rs(L1 - L3) src/slab.rs(L10 - L12)
TLSF Allocator
Relevant source files
This document covers the TLSF (Two-Level Segregated Fit) byte allocator implementation in the allocator crate. The TlsfByteAllocator
provides real-time memory allocation with O(1) time complexity for both allocation and deallocation operations.
For information about other byte-level allocators, see Buddy System Allocator and Slab Allocator. For page-level allocation, see Bitmap Page Allocator.
TLSF Algorithm Overview
The Two-Level Segregated Fit (TLSF) algorithm is a real-time memory allocator that maintains free memory blocks in a two-level segregated list structure. It achieves deterministic O(1) allocation and deallocation performance by organizing free blocks into size classes using a first-level index (FLI) and second-level index (SLI).
Algorithm Structure
flowchart TD subgraph subGraph0["TLSF Algorithm Organization"] SLI1["Second Level Index 0"] SLI2["Second Level Index 1"] SLI3["Second Level Index N"] BLOCK1["Free Block 1"] BLOCK2["Free Block 2"] BLOCK3["Free Block 3"] BLOCKN["Free Block N"] subgraph subGraph1["Size Classification"] SMALL["Small blocks: 2^i to 2^(i+1)-1"] MEDIUM["Medium blocks: subdivided by SLI"] LARGE["Large blocks: power-of-2 ranges"] FLI["First Level Index (FLI)"] end end FLI --> SLI1 FLI --> SLI2 FLI --> SLI3 SLI1 --> BLOCK1 SLI1 --> BLOCK2 SLI2 --> BLOCK3 SLI3 --> BLOCKN
Sources: src/tlsf.rs(L1 - L18)
Implementation Structure
The TlsfByteAllocator
is implemented as a wrapper around the rlsf::Tlsf
external crate with specific configuration parameters optimized for the allocator crate's use cases.
Core Components
flowchart TD subgraph subGraph2["Trait Implementation"] BASE["BaseAllocator"] BYTE["ByteAllocator"] INIT["init()"] ADD["add_memory()"] ALLOC["alloc()"] DEALLOC["dealloc()"] end subgraph subGraph1["External Dependency"] RLSF["rlsf::Tlsf"] PARAMS["Generic Parameters"] FLLEN["FLLEN = 28"] SLLEN["SLLEN = 32"] MAXSIZE["Max Pool Size = 8GB"] end subgraph subGraph0["TlsfByteAllocator Structure"] WRAPPER["TlsfByteAllocator"] INNER["inner: Tlsf<'static, u32, u32, 28, 32>"] TOTAL["total_bytes: usize"] USED["used_bytes: usize"] end BASE --> ADD BASE --> INIT BYTE --> ALLOC BYTE --> DEALLOC INNER --> RLSF PARAMS --> FLLEN PARAMS --> MAXSIZE PARAMS --> SLLEN RLSF --> PARAMS WRAPPER --> BASE WRAPPER --> BYTE WRAPPER --> INNER WRAPPER --> TOTAL WRAPPER --> USED
Sources: src/tlsf.rs(L10 - L18) src/tlsf.rs(L31 - L77)
Configuration Parameters
The implementation uses fixed configuration parameters that define the allocator's capabilities:
Parameter | Value | Purpose |
---|---|---|
FLLEN | 28 | First-level index length, supporting up to 2^28 byte blocks |
SLLEN | 32 | Second-level index length for fine-grained size classification |
Max Pool Size | 8GB | Theoretical maximum: 32 * 2^28 bytes |
Generic Types | u32, u32 | Index types for FLI and SLI bitmaps |
Sources: src/tlsf.rs(L15)
BaseAllocator Implementation
The TlsfByteAllocator
implements the BaseAllocator
trait to provide memory pool initialization and management capabilities.
Memory Pool Management
flowchart TD subgraph subGraph0["Memory Pool Operations"] INIT["init(start: usize, size: usize)"] RAW_PTR["Raw Memory Pointer"] ADD["add_memory(start: usize, size: usize)"] SLICE["core::slice::from_raw_parts_mut()"] NONNULL["NonNull<[u8]>"] INSERT["inner.insert_free_block_ptr()"] TOTAL_UPDATE["total_bytes += size"] ERROR_HANDLE["AllocError::InvalidParam"] end ADD --> RAW_PTR INIT --> RAW_PTR INSERT --> ERROR_HANDLE INSERT --> TOTAL_UPDATE NONNULL --> INSERT RAW_PTR --> SLICE SLICE --> NONNULL
The implementation converts raw memory addresses into safe slice references before inserting them into the TLSF data structure. Both init()
and add_memory()
follow the same pattern but with different error handling strategies.
Sources: src/tlsf.rs(L31 - L51)
ByteAllocator Implementation
The ByteAllocator
trait implementation provides the core allocation and deallocation functionality with automatic memory usage tracking.
Allocation Flow
flowchart TD subgraph subGraph2["Memory Statistics"] TOTAL_BYTES["total_bytes()"] USED_BYTES["used_bytes()"] AVAIL_BYTES["available_bytes() = total - used"] DEALLOC_REQ["dealloc(pos: NonNull, layout: Layout)"] ALLOC_REQ["alloc(layout: Layout)"] end subgraph subGraph0["Allocation Process"] USED_BYTES["used_bytes()"] NO_MEMORY["Err(AllocError::NoMemory)"] RETURN_PTR["Ok(NonNull)"] subgraph subGraph1["Deallocation Process"] TOTAL_BYTES["total_bytes()"] DEALLOC_REQ["dealloc(pos: NonNull, layout: Layout)"] TLSF_DEALLOC["inner.deallocate(pos, layout.align())"] DECREASE_USED["used_bytes -= layout.size()"] ALLOC_REQ["alloc(layout: Layout)"] TLSF_ALLOC["inner.allocate(layout)"] PTR_CHECK["Result, ()>"] UPDATE_USED["used_bytes += layout.size()"] end end ALLOC_REQ --> TLSF_ALLOC DEALLOC_REQ --> TLSF_DEALLOC PTR_CHECK --> NO_MEMORY PTR_CHECK --> UPDATE_USED TLSF_ALLOC --> PTR_CHECK TLSF_DEALLOC --> DECREASE_USED UPDATE_USED --> RETURN_PTR
Sources: src/tlsf.rs(L54 - L77)
Memory Usage Tracking
The allocator maintains accurate memory usage statistics by tracking:
total_bytes
: Total memory pool size across all added memory regionsused_bytes
: Currently allocated memory, updated on each alloc/dealloc operationavailable_bytes
: Computed astotal_bytes - used_bytes
This tracking is performed at the wrapper level rather than delegating to the underlying rlsf
implementation.
Sources: src/tlsf.rs(L66 - L77)
Performance Characteristics
The TLSF allocator provides several performance advantages:
Time Complexity
Operation | Complexity | Description |
---|---|---|
Allocation | O(1) | Constant time regardless of pool size |
Deallocation | O(1) | Constant time with immediate coalescing |
Memory Addition | O(1) | Adding new memory pools |
Statistics | O(1) | Memory usage queries |
Memory Efficiency
The two-level segregated fit approach minimizes fragmentation by:
- Maintaining precise size classes for small allocations
- Using power-of-2 ranges for larger allocations
- Immediate coalescing of adjacent free blocks
- Good-fit allocation strategy to reduce waste
Sources: src/tlsf.rs(L1 - L3)
Configuration and Limitations
Size Limitations
The current configuration imposes several limits:
- Maximum single allocation: Limited by
FLLEN = 28
, supporting up to 2^28 bytes (256MB) - Maximum total pool size: 8GB theoretical limit (32 * 2^28)
- Minimum allocation: Determined by underlying
rlsf
implementation constraints
Integration Requirements
The allocator requires:
- Feature gate
tlsf
to be enabled inCargo.toml
- External dependency on
rlsf
crate version 0.2 - Unsafe operations for memory pool management
'static
lifetime for the internal TLSF structure
Sources: src/tlsf.rs(L15) src/tlsf.rs(L32 - L51)
Usage and Configuration
Relevant source files
This document provides a comprehensive guide for integrating and configuring the allocator crate in your projects. It covers dependency management, feature selection, basic usage patterns, and integration with Rust's standard library allocator API.
For detailed information about specific allocator implementations and their algorithms, see Allocator Implementations. For testing and performance evaluation, see Testing and Benchmarks.
Adding the Dependency
The allocator crate is designed as a feature-gated library that allows you to compile only the allocator implementations you need. Add it to your Cargo.toml
dependencies section:
[dependencies]
allocator = "0.1.1"
By default, this enables only the bitmap page allocator with 256MB memory space support through the page-alloc-256m
feature as defined in Cargo.toml(L13)
Feature Configuration
The crate provides a flexible feature system that allows selective compilation of allocator types and memory space configurations.
Feature Selection Overview
flowchart TD subgraph subGraph4["API Integration"] ALLOC_API["allocator_api"] end subgraph subGraph3["Page Size Features"] SIZE_1T["page-alloc-1t"] SIZE_64G["page-alloc-64g"] SIZE_4G["page-alloc-4g"] SIZE_256M["page-alloc-256m"] end subgraph subGraph2["Allocator Features"] BITMAP["bitmap"] TLSF["tlsf"] SLAB["slab"] BUDDY["buddy"] end subgraph subGraph1["Preset Features"] DEFAULT["default"] FULL["full"] end subgraph subGraph0["Feature Categories"] PRESET["Preset Configurations"] ALLOCATORS["Allocator Types"] PAGE_SIZES["Page Allocator Sizes"] INTEGRATION["Standard Library Integration"] end ALLOCATORS --> BITMAP ALLOCATORS --> BUDDY ALLOCATORS --> SLAB ALLOCATORS --> TLSF DEFAULT --> SIZE_256M FULL --> ALLOC_API FULL --> BITMAP FULL --> BUDDY FULL --> SIZE_256M FULL --> SLAB FULL --> TLSF INTEGRATION --> ALLOC_API PAGE_SIZES --> SIZE_1T PAGE_SIZES --> SIZE_256M PAGE_SIZES --> SIZE_4G PAGE_SIZES --> SIZE_64G PRESET --> DEFAULT PRESET --> FULL
Sources: Cargo.toml(L12 - L27)
Available Features
Feature | Purpose | Dependencies |
---|---|---|
default | Enables bitmap page allocator with 256MB support | page-alloc-256m |
full | Enables all allocator types and API integration | All features |
bitmap | Bitmap-based page allocator | bitmap-allocator v0.2 |
tlsf | Two-Level Segregated Fit byte allocator | rlsf v0.2 |
slab | Slab-based byte allocator | slab_allocator v0.3.1 |
buddy | Buddy system byte allocator | buddy_system_allocator v0.10 |
allocator_api | Standard library allocator trait integration | None |
page-alloc-1t | 1TB memory space support | None |
page-alloc-64g | 64GB memory space support | None |
page-alloc-4g | 4GB memory space support | None |
page-alloc-256m | 256MB memory space support | None |
Common Configuration Patterns
Minimal Configuration (Default):
[dependencies]
allocator = "0.1.1"
All Allocators Enabled:
[dependencies]
allocator = { version = "0.1.1", features = ["full"] }
Specific Allocator Selection:
[dependencies]
allocator = { version = "0.1.1", features = ["buddy", "tlsf", "allocator_api"] }
Large Memory Space Configuration:
[dependencies]
allocator = { version = "0.1.1", features = ["bitmap", "page-alloc-1t"] }
Sources: Cargo.toml(L12 - L34)
Basic Usage Patterns
The crate provides a trait-based interface that allows uniform interaction with different allocator implementations. The core traits are defined in src/lib.rs and enable polymorphic usage of allocators.
Allocator Type Selection
flowchart TD subgraph subGraph3["Traits Used"] BYTE_TRAIT["ByteAllocator trait"] PAGE_TRAIT["PageAllocator trait"] BASE_TRAIT["BaseAllocator trait"] end subgraph subGraph2["Page Allocators"] BITMAP_PAGE["BitmapPageAllocator"] end subgraph subGraph1["Byte Allocators"] BUDDY_BYTE["BuddyByteAllocator"] SLAB_BYTE["SlabByteAllocator"] TLSF_BYTE["TlsfByteAllocator"] end subgraph subGraph0["Allocation Granularity"] BYTE_LEVEL["Byte-level Allocation"] PAGE_LEVEL["Page-level Allocation"] ID_LEVEL["ID-based Allocation"] end USE_CASE["Use Case Requirements"] BITMAP_PAGE --> PAGE_TRAIT BUDDY_BYTE --> BYTE_TRAIT BYTE_LEVEL --> BUDDY_BYTE BYTE_LEVEL --> SLAB_BYTE BYTE_LEVEL --> TLSF_BYTE BYTE_TRAIT --> BASE_TRAIT PAGE_LEVEL --> BITMAP_PAGE PAGE_TRAIT --> BASE_TRAIT SLAB_BYTE --> BYTE_TRAIT TLSF_BYTE --> BYTE_TRAIT USE_CASE --> BYTE_LEVEL USE_CASE --> ID_LEVEL USE_CASE --> PAGE_LEVEL
Sources: src/lib.rs src/bitmap.rs src/buddy.rs src/slab.rs src/tlsf.rs
Integration Patterns
The allocators follow a consistent initialization and usage pattern:
- Initialization: Call
init()
with memory region parameters - Memory Addition: Use
add_memory()
to register additional memory regions - Allocation: Use type-specific allocation methods (
alloc
,alloc_pages
, etc.) - Deallocation: Use corresponding deallocation methods
- Statistics: Query memory usage through built-in statistics methods
Error Handling
All allocator operations return AllocResult<T>
, which is a type alias for Result<T, AllocError>
. The error types include:
InvalidParam
: Invalid parameters passed to allocator functionsMemoryOverlap
: Attempt to add overlapping memory regionsNoMemory
: Insufficient memory for allocation requestNotAllocated
: Attempt to deallocate memory that wasn't allocated
Sources: src/lib.rs
Standard Library Integration
When the allocator_api
feature is enabled, the crate provides integration with Rust's standard library allocator interface through the AllocatorRc
wrapper.
AllocatorRc Usage
flowchart TD subgraph subGraph3["Standard Library"] CORE_ALLOC["core::alloc::Allocator"] end subgraph subGraph2["Core Allocators"] BYTE_ALLOC["ByteAllocator Implementation"] end subgraph subGraph1["Allocator Integration"] ALLOC_RC["AllocatorRc"] RC_REFCELL["Rc>"] end subgraph subGraph0["Application Code"] APP["User Application"] STD_COLLECTIONS["std::collections"] end ALLOC_RC --> CORE_ALLOC ALLOC_RC --> RC_REFCELL APP --> STD_COLLECTIONS RC_REFCELL --> BYTE_ALLOC STD_COLLECTIONS --> ALLOC_RC
The AllocatorRc
provides:
- Reference-counted access to byte allocators
- Implementation of
core::alloc::Allocator
trait - Thread-safe usage through
RefCell
interior mutability - Compatibility with standard library collections
Sources: src/lib.rs Cargo.toml(L22)
Memory Space Configuration
The page allocator supports different maximum memory space configurations through mutually exclusive features:
Feature | Maximum Memory | Use Case |
---|---|---|
page-alloc-256m | 256 MB | Small embedded systems, testing |
page-alloc-4g | 4 GB | Desktop applications, moderate memory |
page-alloc-64g | 64 GB | Server applications, large memory |
page-alloc-1t | 1 TB | High-performance computing, very large memory |
These features control the underlying bitmap allocator size and should be selected based on your application's maximum memory requirements. Using a smaller configuration reduces memory overhead of the allocator itself.
Sources: Cargo.toml(L24 - L27)
Development Dependencies
When developing with or testing the allocator crate, additional dependencies are available:
- Testing: The
full
feature is automatically enabled for development builds - Benchmarking:
criterion
with HTML reports for performance testing - Randomization:
rand
withsmall_rng
feature for test data generation
These are configured as dev-dependencies and don't affect production builds.
Sources: Cargo.toml(L36 - L43)
Testing and Benchmarks
Relevant source files
This document covers the testing infrastructure and performance evaluation framework for the allocator crate. The testing system validates correctness and measures performance across all supported allocator implementations through comprehensive integration tests and benchmarks.
For information about specific allocator implementations being tested, see Allocator Implementations. For details on individual test scenarios, see Integration Tests and Performance Benchmarks.
Testing Infrastructure Overview
The allocator crate provides a comprehensive testing framework that validates both correctness and performance across all allocator implementations. The testing infrastructure consists of integration tests and performance benchmarks that exercise the allocators through realistic usage patterns.
Testing Architecture
flowchart TD subgraph subGraph3["Test Execution"] RUN_TEST["run_test()"] MEMORY_POOL["MemoryPool"] CRITERION["criterion_benchmark()"] end subgraph subGraph2["Allocator Implementations Under Test"] SYS_ALLOC["std::alloc::System"] BUDDY_RC["AllocatorRc"] SLAB_RC["AllocatorRc"] TLSF_RC["AllocatorRc"] end subgraph subGraph1["Test Functions"] TEST_VEC["test_vec()"] TEST_VEC2["test_vec2()"] TEST_BTREE["test_btree_map()"] TEST_ALIGN["test_alignment()"] BENCH_VEC_PUSH["vec_push()"] BENCH_VEC_RAND["vec_rand_free()"] BENCH_BTREE["btree_map()"] end subgraph subGraph0["Test Infrastructure"] INTEGRATION["tests/allocator.rs"] BENCHMARKS["benches/collections.rs"] UTILS["benches/utils/mod.rs"] end BENCHMARKS --> BENCH_BTREE BENCHMARKS --> BENCH_VEC_PUSH BENCHMARKS --> BENCH_VEC_RAND BENCHMARKS --> CRITERION BENCHMARKS --> MEMORY_POOL INTEGRATION --> TEST_ALIGN INTEGRATION --> TEST_BTREE INTEGRATION --> TEST_VEC INTEGRATION --> TEST_VEC2 RUN_TEST --> MEMORY_POOL TEST_VEC --> BUDDY_RC TEST_VEC --> SLAB_RC TEST_VEC --> SYS_ALLOC TEST_VEC --> TLSF_RC TEST_VEC2 --> BUDDY_RC TEST_VEC2 --> SLAB_RC TEST_VEC2 --> SYS_ALLOC TEST_VEC2 --> TLSF_RC UTILS --> MEMORY_POOL
The testing architecture separates integration testing from performance benchmarking while using consistent test scenarios across both. Each allocator implementation is tested through the same set of operations to ensure behavioral consistency and enable performance comparison.
Sources: tests/allocator.rs(L1 - L144) benches/collections.rs(L1 - L102)
Test Environment Configuration
The testing framework uses a standardized memory pool configuration to ensure consistent and reproducible results across all test runs.
Configuration | Value | Purpose |
---|---|---|
POOL_SIZE | 128 MB | Standard memory pool size for custom allocators |
Memory Layout | 4096-byte aligned | Ensures proper page alignment for testing |
Random Seed | 0xdead_beef | Deterministic randomization for benchmarks |
Sample Size | 10 iterations | Benchmark measurement accuracy |
flowchart TD subgraph subGraph1["Allocator Initialization"] BUDDY_INIT["BuddyByteAllocator::new()"] SLAB_INIT["SlabByteAllocator::new()"] TLSF_INIT["TlsfByteAllocator::new()"] RC_WRAPPER["AllocatorRc::new(allocator, pool)"] end subgraph subGraph0["Memory Pool Setup"] ALLOC_LAYOUT["Layout::from_size_align(POOL_SIZE, 4096)"] ALLOC_PTR["std::alloc::alloc_zeroed()"] SLICE_POOL["core::slice::from_raw_parts_mut()"] end ALLOC_LAYOUT --> ALLOC_PTR ALLOC_PTR --> SLICE_POOL BUDDY_INIT --> RC_WRAPPER SLAB_INIT --> RC_WRAPPER SLICE_POOL --> RC_WRAPPER TLSF_INIT --> RC_WRAPPER
Sources: tests/allocator.rs(L11) tests/allocator.rs(L87 - L95) benches/collections.rs(L16)
Test Scenarios and Methodology
The testing framework implements several key scenarios that exercise different allocation patterns and stress test the allocators under various conditions.
Core Test Scenarios
Test Function | Purpose | Parameters | Stress Pattern |
---|---|---|---|
test_vec | Vector growth and sorting | 3M elements | Sequential allocation |
test_vec2 | Fragmentation testing | 30K/7.5K blocks | Random deallocation |
test_btree_map | Complex data structures | 50K operations | Mixed insert/remove |
test_alignment | Alignment validation | 50 iterations | Variable size/alignment |
Vector Operation Testing
The test_vec
function validates basic allocation behavior through vector operations that require continuous memory growth and reallocation.
sequenceDiagram participant test_vec as "test_vec()" participant Vecu32 as "Vec<u32>" participant Allocator as "Allocator" test_vec ->> Vecu32: "Vec::with_capacity_in(n, alloc)" loop "n iterations" test_vec ->> Vecu32: "push(random_u32)" Vecu32 ->> Allocator: "allocate/reallocate" end test_vec ->> Vecu32: "sort()" test_vec ->> test_vec: "verify sorted order" test_vec ->> Vecu32: "drop()" Vecu32 ->> Allocator: "deallocate"
Sources: tests/allocator.rs(L13 - L22) benches/collections.rs(L18 - L24)
Fragmentation Testing
The test_vec2
and vec_rand_free
functions specifically target memory fragmentation by creating many small allocations and then randomly deallocating them.
flowchart TD subgraph subGraph1["Test Parameters"] PARAMS1["25K blocks × 64 bytes"] PARAMS2["7.5K blocks × 520 bytes"] RNG["SmallRng::seed_from_u64(0xdead_beef)"] end subgraph subGraph0["Fragmentation Test Flow"] ALLOCATE["Allocate n blocks of blk_size"] SHUFFLE["Shuffle deallocation order"] DEALLOCATE["Randomly deallocate blocks"] VERIFY["Verify allocator state"] end ALLOCATE --> SHUFFLE DEALLOCATE --> VERIFY PARAMS1 --> ALLOCATE PARAMS2 --> ALLOCATE RNG --> SHUFFLE SHUFFLE --> DEALLOCATE
Sources: tests/allocator.rs(L24 - L40) benches/collections.rs(L26 - L44)
Alignment and Layout Testing
The test_alignment
function validates that allocators properly handle various size and alignment requirements, which is critical for compatibility with different data types.
Sources: tests/allocator.rs(L63 - L85)
Performance Measurement Framework
The benchmark suite uses the criterion
crate to provide statistically rigorous performance measurements across all allocator implementations.
Benchmark Execution Flow
flowchart TD subgraph subGraph2["Benchmark Functions"] VEC_PUSH["vec_push_3M"] VEC_RAND1["vec_rand_free_25K_64"] VEC_RAND2["vec_rand_free_7500_520"] BTREE["btree_map_50K"] end subgraph subGraph1["Allocator Testing Matrix"] SYSTEM["bench(c, 'system', std::alloc::System)"] TLSF["bench(c, 'tlsf', AllocatorRc)"] SLAB["bench(c, 'slab', AllocatorRc)"] BUDDY["bench(c, 'buddy', AllocatorRc)"] end subgraph subGraph0["Benchmark Setup"] POOL_INIT["MemoryPool::new(POOL_SIZE)"] CRITERION["Criterion::default()"] BENCH_GROUP["benchmark_group(alloc_name)"] end BENCH_GROUP --> VEC_PUSH CRITERION --> BENCH_GROUP POOL_INIT --> BUDDY POOL_INIT --> SLAB POOL_INIT --> SYSTEM POOL_INIT --> TLSF SYSTEM --> BTREE SYSTEM --> VEC_PUSH SYSTEM --> VEC_RAND1 SYSTEM --> VEC_RAND2
Sources: benches/collections.rs(L63 - L98)
Benchmark Scenarios
The benchmark suite measures performance across scenarios that represent common real-world allocation patterns:
Benchmark | Operation Count | Block Size | Pattern Type |
---|---|---|---|
vec_push_3M | 3,000,000 pushes | Variable | Sequential growth |
vec_rand_free_25K_64 | 25,000 blocks | 64 bytes | Random fragmentation |
vec_rand_free_7500_520 | 7,500 blocks | 520 bytes | Large block fragmentation |
btree_map_50K | 50,000 operations | Variable | Complex data structure |
Sources: benches/collections.rs(L65 - L77)
Running Tests and Interpreting Results
Integration Test Execution
Integration tests are executed using standard Rust testing tools and validate allocator correctness across all implementations:
# Run all integration tests
cargo test
# Run tests for specific allocator
cargo test buddy_alloc
cargo test slab_alloc
cargo test tlsf_alloc
cargo test system_alloc
Each test function follows the pattern {allocator}_alloc()
and executes the complete test suite against that allocator implementation.
Sources: tests/allocator.rs(L97 - L143)
Benchmark Execution
Performance benchmarks are run using the criterion
framework, which provides detailed statistical analysis:
# Run all benchmarks
cargo bench
# Generate HTML report
cargo bench -- --output-format html
The benchmark results compare allocator performance against the system allocator baseline, providing insights into:
- Allocation speed for different patterns
- Memory fragmentation behavior
- Overhead of custom allocator implementations
Sources: benches/collections.rs(L100 - L101)
Integration Tests
Relevant source files
Purpose and Scope
The integration tests validate that all allocator implementations in the crate correctly implement the standard library's Allocator
trait and can handle real-world allocation patterns. These tests ensure that each allocator (BuddyByteAllocator
, SlabByteAllocator
, TlsfByteAllocator
) functions correctly when used with standard Rust collections and various allocation scenarios.
For information about the specific allocator implementations being tested, see the allocator-specific documentation in section 3. For performance benchmarking information, see Performance Benchmarks.
Test Architecture
The integration test suite follows a systematic approach where each allocator is tested against the same set of scenarios to ensure consistent behavior across implementations.
Test Execution Flow
flowchart TD subgraph subGraph1["Allocator Types"] SYS["std::alloc::System"] BUDDY["AllocatorRc"] SLAB["AllocatorRc"] TLSF["AllocatorRc"] end subgraph subGraph0["Test Scenarios"] VEC["test_vec() - Vector Operations"] VEC2["test_vec2() - Fragmentation Testing"] BTREE["test_btree_map() - BTreeMap Operations"] ALIGN["test_alignment() - Alignment Testing"] end START["Test Execution Start"] POOL["run_test() - Memory Pool Setup"] ALLOC_INIT["Allocator Initialization"] SCENARIOS["Test Scenario Execution"] CLEANUP["Memory Pool Cleanup"] ALIGN --> CLEANUP ALLOC_INIT --> BUDDY ALLOC_INIT --> SCENARIOS ALLOC_INIT --> SLAB ALLOC_INIT --> SYS ALLOC_INIT --> TLSF BTREE --> CLEANUP POOL --> ALLOC_INIT SCENARIOS --> ALIGN SCENARIOS --> BTREE SCENARIOS --> VEC SCENARIOS --> VEC2 START --> POOL VEC --> CLEANUP VEC2 --> CLEANUP
Sources: tests/allocator.rs(L87 - L95) tests/allocator.rs(L97 - L143)
Test Scenarios
Each test scenario validates different aspects of allocator behavior and memory management patterns.
Vector Operations Test (test_vec)
This test validates basic allocation and collection growth by creating a vector with a specified capacity, filling it with random data, and performing operations that require memory reallocation.
Test Parameter | Value |
---|---|
Vector Size | 3,000,000 elements |
Element Type | u32 |
Operations | Push, sort, comparison |
The test exercises the allocator's ability to handle large contiguous allocations and validates that the allocated memory remains accessible and correctly manages data integrity.
Sources: tests/allocator.rs(L13 - L22)
Fragmentation Testing (test_vec2)
This test creates intentional memory fragmentation by allocating numerous small vectors and then deallocating them in random order, simulating real-world fragmentation scenarios.
Test Configuration | Small Blocks | Large Blocks |
---|---|---|
Block Count | 30,000 | 7,500 |
Block Size | 64 elements | 520 elements |
Element Type | u64 | u64 |
The test validates the allocator's ability to handle fragmented memory and efficiently reuse deallocated blocks.
Sources: tests/allocator.rs(L24 - L40)
BTreeMap Operations Test (test_btree_map)
This test exercises complex allocation patterns through BTreeMap operations, including node allocation, key-value storage, and tree rebalancing operations.
Test Parameter | Value |
---|---|
Operations | 50,000 |
Deletion Probability | 20% (1 in 5) |
Key Type | Vec |
Value Type | u32 |
The test validates that allocators can handle the complex allocation patterns required by balanced tree data structures.
Sources: tests/allocator.rs(L42 - L61)
Alignment Testing (test_alignment)
This test validates that allocators correctly handle various alignment requirements and allocation sizes, which is critical for systems programming.
flowchart TD RANDOM["Random Number Generator"] SIZE_GEN["Size Generation2^0 to 2^15 * 1.0-2.0"] ALIGN_GEN["Alignment Generation2^0 to 2^7"] LAYOUT["Layout::from_size_align()"] ALLOC_OP["Allocate/Deallocate2:1 ratio"] ALIGN_GEN --> LAYOUT LAYOUT --> ALLOC_OP RANDOM --> ALIGN_GEN RANDOM --> ALLOC_OP RANDOM --> SIZE_GEN SIZE_GEN --> LAYOUT
Test Parameter | Range |
---|---|
Allocation Size | 1 to 131,072 bytes |
Alignment | 1 to 128 bytes |
Operations | 50 |
Allocation Probability | 66% |
Sources: tests/allocator.rs(L63 - L85)
Allocator Test Matrix
The test suite validates four different allocator implementations using identical test scenarios to ensure behavioral consistency.
Allocator Implementation Matrix
flowchart TD subgraph subGraph1["Allocator Implementations"] SYS["system_alloc()std::alloc::System"] BUDDY["buddy_alloc()AllocatorRc + BuddyByteAllocator"] SLAB["slab_alloc()AllocatorRc + SlabByteAllocator"] TLSF["tlsf_alloc()AllocatorRc + TlsfByteAllocator"] end subgraph subGraph0["Test Functions"] TV["test_vec(3_000_000)"] TV2A["test_vec2(30_000, 64)"] TV2B["test_vec2(7_500, 520)"] TBM["test_btree_map(50_000)"] TA["test_alignment(50)"] end TA --> BUDDY TA --> SLAB TA --> SYS TA --> TLSF TBM --> BUDDY TBM --> SLAB TBM --> SYS TBM --> TLSF TV --> BUDDY TV --> SLAB TV --> SYS TV --> TLSF TV2A --> BUDDY TV2A --> SLAB TV2A --> SYS TV2A --> TLSF TV2B --> BUDDY TV2B --> SLAB TV2B --> SYS TV2B --> TLSF
System Allocator Test (system_alloc)
Tests the standard library allocator as a baseline reference implementation. This test validates that the test scenarios themselves are correct and provides performance/behavior comparison data.
Custom Allocator Tests
Each custom allocator test follows the same pattern:
- Initialize the allocator using
AllocatorRc::new()
- Execute all test scenarios with the wrapped allocator
- Validate correct behavior across all scenarios
The AllocatorRc
wrapper enables the custom byte allocators to implement the standard Allocator
trait required by Rust collections.
Sources: tests/allocator.rs(L97 - L143)
Memory Pool Management
The test infrastructure uses a dedicated memory pool to isolate allocator testing from system memory management.
Memory Pool Setup Process
sequenceDiagram participant TestFunction as "Test Function" participant run_test as "run_test()" participant stdalloc as "std::alloc" participant MemoryPool as "Memory Pool" participant CustomAllocator as "Custom Allocator" TestFunction ->> run_test: Call run_test(closure) run_test ->> stdalloc: alloc_zeroed(128MB, 4KB align) stdalloc -->> run_test: Raw memory pointer run_test ->> MemoryPool: Create slice from raw memory run_test ->> CustomAllocator: Initialize with pool slice run_test ->> TestFunction: Execute test closure(pool) TestFunction -->> run_test: Test completion run_test ->> stdalloc: dealloc(ptr, layout)
Pool Parameter | Value |
---|---|
Size | 128 MB (POOL_SIZE) |
Alignment | 4096 bytes |
Initialization | Zero-filled |
Layout | Contiguous memory block |
The run_test
helper function manages the complete lifecycle of the memory pool, ensuring proper cleanup regardless of test outcomes.
Sources: tests/allocator.rs(L11) tests/allocator.rs(L87 - L95)
Running and Interpreting Tests
Execution Commands
# Run all integration tests
cargo test
# Run tests for a specific allocator
cargo test buddy_alloc
cargo test slab_alloc
cargo test tlsf_alloc
cargo test system_alloc
# Run with specific features enabled
cargo test --features "buddy,slab,tlsf,allocator_api"
Test Requirements
The integration tests require several unstable Rust features:
Feature | Purpose |
---|---|
btreemap_alloc | Enable BTreeMap with custom allocators |
allocator_api | Enable Allocator trait implementation |
These features are enabled via the feature gates at the top of the test file.
Expected Behavior
All tests should pass for all allocator implementations, demonstrating that:
- Each allocator correctly implements the
Allocator
trait - Memory allocation and deallocation work correctly
- Collections can be used with custom allocators
- Alignment requirements are respected
- Memory fragmentation is handled appropriately
- Large allocations are supported
Test failures indicate potential issues with allocator implementation, memory corruption, or alignment violations.
Sources: tests/allocator.rs(L1 - L2) tests/allocator.rs(L8)
Performance Benchmarks
Relevant source files
This document covers the performance benchmark suite for the allocator crate, which provides standardized testing methodology to compare the performance characteristics of different memory allocation implementations. The benchmarks evaluate allocation patterns commonly found in real-world applications and measure relative performance against the system allocator baseline.
For information about the individual allocator implementations being tested, see Allocator Implementations. For details about the integration test suite, see Integration Tests.
Benchmark Architecture
The benchmark infrastructure is built using the Criterion benchmarking framework and provides a controlled testing environment for evaluating allocator performance across different workload patterns.
Benchmark Framework Overview
flowchart TD subgraph subGraph3["Allocator Instances"] SYS_ALLOC["std::alloc::System"] TLSF_ALLOC["AllocatorRc"] SLAB_ALLOC["AllocatorRc"] BUDDY_ALLOC["AllocatorRc"] end subgraph subGraph2["Memory Pool Management"] POOL_NEW["MemoryPool::new()"] POOL_SLICE["MemoryPool::as_slice()"] ALLOC_ZEROED["std::alloc::alloc_zeroed()"] LAYOUT["Layout::from_size_align(size, 4096)"] end subgraph subGraph1["Test Infrastructure"] POOL["MemoryPool::new(POOL_SIZE)"] BENCH_FN["bench(c, alloc_name, alloc)"] CRITERION["Criterion::benchmark_group()"] end subgraph subGraph0["Benchmark Entry Point"] MAIN["criterion_main!(benches)"] GROUP["criterion_group!(benches, criterion_benchmark)"] end BENCH_FN --> BUDDY_ALLOC BENCH_FN --> CRITERION BENCH_FN --> POOL BENCH_FN --> SLAB_ALLOC BENCH_FN --> SYS_ALLOC BENCH_FN --> TLSF_ALLOC GROUP --> BENCH_FN MAIN --> GROUP POOL --> POOL_NEW POOL --> POOL_SLICE POOL_NEW --> ALLOC_ZEROED POOL_NEW --> LAYOUT POOL_SLICE --> BUDDY_ALLOC POOL_SLICE --> SLAB_ALLOC POOL_SLICE --> TLSF_ALLOC
The benchmark system creates a 128MB memory pool (POOL_SIZE = 1024 * 1024 * 128
) for testing custom allocators, while the system allocator operates directly on system memory. Each allocator is wrapped in AllocatorRc
to provide the standard library Allocator
trait interface.
Sources: benches/collections.rs(L16) benches/collections.rs(L80 - L98) benches/utils/mod.rs(L9 - L18)
Test Scenario Implementation
flowchart TD subgraph subGraph3["btree_map Implementation"] BT_NEW["BTreeMap::new_in(alloc.clone())"] BT_RNG["SmallRng::seed_from_u64(0xdead_beef)"] BT_INSERT["m.insert(key, value)"] BT_REMOVE["m.pop_first()"] BT_CLEAR["m.clear()"] end subgraph subGraph2["vec_rand_free Implementation"] VR_ALLOC["Vec::with_capacity_in(blk_size, alloc)"] VR_RNG["SmallRng::seed_from_u64(0xdead_beef)"] VR_SHUFFLE["index.shuffle(&mut rng)"] VR_FREE["v[i] = Vec::new_in(alloc)"] end subgraph subGraph1["vec_push Implementation"] VP_NEW["Vec::new_in(alloc.clone())"] VP_LOOP["for _ in 0..n"] VP_PUSH["v.push(0xdead_beef)"] VP_DROP["drop(v)"] end subgraph subGraph0["Benchmark Functions"] VEC_PUSH["vec_push(n, alloc)"] VEC_RAND["vec_rand_free(n, blk_size, alloc)"] BTREE_MAP["btree_map(n, alloc)"] end BTREE_MAP --> BT_NEW BTREE_MAP --> BT_RNG BT_INSERT --> BT_CLEAR BT_REMOVE --> BT_CLEAR BT_RNG --> BT_INSERT BT_RNG --> BT_REMOVE VEC_PUSH --> VP_NEW VEC_RAND --> VR_ALLOC VEC_RAND --> VR_RNG VP_LOOP --> VP_PUSH VP_NEW --> VP_LOOP VP_PUSH --> VP_DROP VR_RNG --> VR_SHUFFLE VR_SHUFFLE --> VR_FREE
Each benchmark function implements a specific allocation pattern designed to stress different aspects of allocator performance. The functions use deterministic random seeds to ensure reproducible results across benchmark runs.
Sources: benches/collections.rs(L18 - L24) benches/collections.rs(L26 - L44) benches/collections.rs(L46 - L61)
Test Scenarios
The benchmark suite includes four distinct test scenarios that evaluate different aspects of allocator performance:
Benchmark | Function | Parameters | Purpose |
---|---|---|---|
vec_push_3M | vec_push | n=3,000,000 | Sequential allocation stress test |
vec_rand_free_25K_64 | vec_rand_free | n=25,000, blk_size=64 | Small block fragmentation test |
vec_rand_free_7500_520 | vec_rand_free | n=7,500, blk_size=520 | Large block fragmentation test |
btree_map_50K | btree_map | n=50,000 | Mixed allocation/deallocation pattern |
Sequential Allocation Test
The vec_push_3M
benchmark evaluates pure allocation performance by pushing 3 million u32
values into a vector. This test measures:
- Allocation throughput for growing data structures
- Memory reallocation efficiency during vector growth
- Allocator overhead for sequential memory requests
Fragmentation Tests
The vec_rand_free
tests evaluate allocator behavior under fragmentation stress:
Small Block Test (vec_rand_free_25K_64
):
- Allocates 25,000 blocks of 64 bytes each
- Randomly deallocates blocks using shuffled indices
- Tests small allocation efficiency and fragmentation handling
Large Block Test (vec_rand_free_7500_520
):
- Allocates 7,500 blocks of 520 bytes each
- Randomly deallocates blocks using shuffled indices
- Tests allocator behavior with larger allocation sizes
Mixed Workload Test
The btree_map_50K
benchmark simulates realistic application behavior:
- Performs 50,000 operations on a
BTreeMap
- 20% probability of removal operations (
rng.next_u32() % 5 == 0
) - 80% probability of insertion operations
- Uses string keys with dynamic allocation
- Tests allocator performance under mixed allocation patterns
Sources: benches/collections.rs(L65 - L77) benches/collections.rs(L26 - L44) benches/collections.rs(L46 - L61)
Allocator Testing Matrix
The benchmark system tests four different allocators to provide comprehensive performance comparison:
Allocator Configuration
flowchart TD subgraph subGraph3["Allocator Initialization"] TLSF_NEW["TlsfByteAllocator::new()"] SLAB_NEW["SlabByteAllocator::new()"] BUDDY_NEW["BuddyByteAllocator::new()"] end subgraph subGraph2["Memory Pool"] SYS["std::alloc::System"] POOL_MEM["MemoryPool (128MB)"] POOL_SLICE["&mut [u8] slice"] end subgraph subGraph1["Custom Allocators"] TLSF_RC["AllocatorRc"] SLAB_RC["AllocatorRc"] BUDDY_RC["AllocatorRc"] end subgraph subGraph0["System Allocator"] SYS["std::alloc::System"] SYS_DESC["Direct system memoryBaseline reference"] POOL_MEM["MemoryPool (128MB)"] end BUDDY_NEW --> BUDDY_RC POOL_MEM --> POOL_SLICE POOL_SLICE --> BUDDY_RC POOL_SLICE --> SLAB_RC POOL_SLICE --> TLSF_RC SLAB_NEW --> SLAB_RC SYS --> SYS_DESC TLSF_NEW --> TLSF_RC
Each custom allocator is initialized with the same 128MB memory pool to ensure fair comparison. The AllocatorRc
wrapper provides reference counting and the standard library Allocator
trait implementation.
Sources: benches/collections.rs(L82 - L97) benches/utils/mod.rs(L10 - L14)
Memory Pool Utility
The MemoryPool
utility provides controlled memory management for benchmark testing:
MemoryPool Implementation
The MemoryPool
struct manages a fixed-size memory region for allocator testing:
// Memory pool allocation with 4KB alignment
let layout = Layout::from_size_align(size, 4096).unwrap();
let ptr = NonNull::new(unsafe { std::alloc::alloc_zeroed(layout) }).unwrap();
Key characteristics:
- Size: 128MB (
POOL_SIZE = 1024 * 1024 * 128
) - Alignment: 4KB page alignment for optimal performance
- Initialization: Zero-filled memory using
alloc_zeroed
- Lifetime: Automatic cleanup through
Drop
implementation
The pool provides a mutable slice interface (as_slice()
) that allocators use for their internal memory management, ensuring all custom allocators operate within the same controlled memory environment.
Sources: benches/utils/mod.rs(L9 - L18) benches/utils/mod.rs(L21 - L25) benches/collections.rs(L16)
Running Benchmarks
The benchmark suite uses the Criterion framework for statistical analysis and result reporting. To execute the benchmarks:
cargo bench --features full
The --features full
flag enables all allocator implementations for comprehensive testing. The benchmark configuration includes:
- Sample size: 10 iterations per test for statistical significance
- Measurement: Wall clock time for complete test scenarios
- Output: Statistical analysis including mean, standard deviation, and confidence intervals
Individual allocator benchmarks can be run using Criterion's filtering capability:
cargo bench -- tlsf # Run only TLSF allocator benchmarks
cargo bench -- vec_push # Run only vector push benchmarks
Sources: benches/collections.rs(L68) benches/collections.rs(L100 - L101)
Benchmark Results Interpretation
The benchmark results provide comparative performance metrics across allocators:
Performance Metrics
Throughput Comparison: Results show relative performance against the system allocator baseline, helping identify which allocators perform best for specific workload patterns.
Fragmentation Behavior: The random free tests reveal how effectively each allocator handles memory fragmentation and reuses freed blocks.
Mixed Workload Performance: The BTreeMap benchmark demonstrates allocator behavior under realistic application usage patterns with mixed allocation and deallocation operations.
Expected Performance Characteristics
- TLSF: Consistent O(1) allocation/deallocation with good fragmentation resistance
- Slab: Optimal for fixed-size allocations, may show overhead for variable sizes
- Buddy: Good general-purpose performance with power-of-two block sizes
- System: Reference baseline representing operating system allocator performance
Sources: benches/collections.rs(L63 - L78)
Development and Contributing
Relevant source files
This document provides comprehensive guidance for developers who want to contribute to the allocator crate, including development environment setup, code quality standards, testing procedures, and the CI/CD pipeline. It covers the technical requirements and workflows necessary for maintaining the project's high standards of quality and cross-platform compatibility.
For information about using the allocators in your projects, see Usage and Configuration. For details about the testing infrastructure from a user perspective, see Testing and Benchmarks.
Development Environment Setup
The allocator crate requires a specific development environment to ensure consistency across all contributors and maintain compatibility with the supported target platforms.
Required Toolchain
The project exclusively uses the Rust nightly toolchain with specific components and target platforms. The nightly toolchain is required for no_std
compatibility and advanced allocation features.
Required Components:
rust-src
- Source code for cross-compilationclippy
- Linting tool for code qualityrustfmt
- Code formatting tool
Supported Target Platforms:
x86_64-unknown-linux-gnu
- Standard Linux developmentx86_64-unknown-none
- Bare metal x86_64riscv64gc-unknown-none-elf
- RISC-V bare metalaarch64-unknown-none-softfloat
- ARM64 bare metal
Development Workflow
flowchart TD SETUP["Development Setup"] TOOLCHAIN["Install Nightly Toolchainrustup toolchain install nightly"] COMPONENTS["Add Componentsrust-src, clippy, rustfmt"] TARGETS["Add Target Platformsx86_64, RISC-V, ARM64"] DEVELOP["Development Phase"] FORMAT["Code Formattingcargo fmt --all -- --check"] LINT["Lintingcargo clippy --all-features"] BUILD["Cross-Platform Buildcargo build --target TARGET"] TEST["Unit Testingcargo test --nocapture"] SUBMIT["Contribution Submission"] PR["Create Pull Request"] CI["Automated CI Pipeline"] REVIEW["Code Review Process"] BUILD --> SUBMIT COMPONENTS --> DEVELOP DEVELOP --> BUILD DEVELOP --> FORMAT DEVELOP --> LINT DEVELOP --> TEST FORMAT --> SUBMIT LINT --> SUBMIT SETUP --> COMPONENTS SETUP --> TARGETS SETUP --> TOOLCHAIN SUBMIT --> CI SUBMIT --> PR SUBMIT --> REVIEW TARGETS --> DEVELOP TEST --> SUBMIT TOOLCHAIN --> DEVELOP
Sources: .github/workflows/ci.yml(L11 - L19)
CI/CD Pipeline Overview
The project uses GitHub Actions for continuous integration and deployment, ensuring code quality and cross-platform compatibility for every contribution.
CI Pipeline Architecture
flowchart TD subgraph subGraph3["Documentation Job"] DOC_BUILD["Documentation Buildcargo doc --no-deps --all-features"] DOC_DEPLOY["Deploy to GitHub Pagesgh-pages branch"] end subgraph subGraph2["Quality Checks"] FORMAT_CHECK["Format Checkcargo fmt --all -- --check"] CLIPPY_CHECK["Clippy Lintingcargo clippy --all-features"] BUILD_CHECK["Build Verificationcargo build --all-features"] UNIT_TEST["Unit Testsx86_64-unknown-linux-gnu only"] end subgraph subGraph1["CI Job Matrix"] CI_JOB["CI JobUbuntu Latest"] RUST_NIGHTLY["Rust Toolchainnightly"] subgraph subGraph0["Target Matrix"] TARGET_LINUX["x86_64-unknown-linux-gnu"] TARGET_BARE["x86_64-unknown-none"] TARGET_RISCV["riscv64gc-unknown-none-elf"] TARGET_ARM["aarch64-unknown-none-softfloat"] end end TRIGGER["GitHub Eventspush, pull_request"] CI_JOB --> RUST_NIGHTLY DOC_BUILD --> DOC_DEPLOY RUST_NIGHTLY --> TARGET_ARM RUST_NIGHTLY --> TARGET_BARE RUST_NIGHTLY --> TARGET_LINUX RUST_NIGHTLY --> TARGET_RISCV TARGET_LINUX --> BUILD_CHECK TARGET_LINUX --> CLIPPY_CHECK TARGET_LINUX --> FORMAT_CHECK TARGET_LINUX --> UNIT_TEST TRIGGER --> CI_JOB TRIGGER --> DOC_BUILD
Pipeline Configuration Details
The CI pipeline is configured with fail-fast disabled to ensure all target platforms are tested even if one fails. This comprehensive testing approach ensures the allocator implementations work correctly across all supported embedded and hosted environments.
Quality Gate Requirements:
- All code must pass
rustfmt
formatting checks - All code must pass
clippy
linting withall-features
enabled - All target platforms must build successfully
- Unit tests must pass on the Linux target
- Documentation must build without broken links or missing docs
Sources: .github/workflows/ci.yml(L5 - L31)
Code Quality Standards
The project maintains strict code quality standards enforced through automated tooling and CI pipeline checks.
Formatting Standards
All code must adhere to the standard Rust formatting rules enforced by rustfmt
. The CI pipeline automatically rejects any code that doesn't meet these formatting requirements.
Formatting Command:
cargo fmt --all -- --check
Linting Requirements
The project uses clippy
with all features enabled to catch potential issues and enforce best practices. One specific clippy warning is allowed: clippy::new_without_default
, which is common in allocator implementations where Default
trait implementation may not be appropriate.
Linting Configuration:
- Target: All supported platforms
- Features:
--all-features
enabled - Allowed warnings:
clippy::new_without_default
Sources: .github/workflows/ci.yml(L23 - L25)
Cross-Platform Compatibility
All contributions must build successfully on all supported target platforms. This ensures the allocator implementations work correctly in both hosted and bare-metal environments.
Build Verification Process:
flowchart TD subgraph subGraph0["Build Targets"] LINUX["Linux GNUx86_64-unknown-linux-gnu"] BARE_X86["Bare Metal x86x86_64-unknown-none"] RISCV["RISC-Vriscv64gc-unknown-none-elf"] ARM["ARM64aarch64-unknown-none-softfloat"] end SOURCE["Source Code"] SUCCESS["All Builds Must Pass"] ARM --> SUCCESS BARE_X86 --> SUCCESS LINUX --> SUCCESS RISCV --> SUCCESS SOURCE --> ARM SOURCE --> BARE_X86 SOURCE --> LINUX SOURCE --> RISCV
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L27)
Testing Procedures
The project includes comprehensive testing procedures that run automatically in the CI pipeline and can be executed locally during development.
Unit Test Execution
Unit tests are executed only on the x86_64-unknown-linux-gnu
target platform, as this provides the most complete testing environment while still ensuring the code works correctly.
Test Command:
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
The --nocapture
flag ensures that all test output is visible, which is essential for debugging allocator behavior and performance characteristics.
Local Testing Workflow
For local development, contributors should run the complete test suite that mirrors the CI pipeline:
- Format Check:
cargo fmt --all -- --check
- Lint Check:
cargo clippy --all-features
- Build All Targets:
cargo build --target <TARGET> --all-features
- Run Tests:
cargo test -- --nocapture
Sources: .github/workflows/ci.yml(L28 - L30)
Documentation Requirements
The project maintains high documentation standards with automated enforcement through the CI pipeline.
Documentation Standards
All public APIs must include comprehensive documentation. The CI pipeline enforces these standards through specific rustdoc
flags that treat documentation issues as errors.
Documentation Requirements:
- No broken intra-doc links (
-D rustdoc::broken_intra_doc_links
) - No missing documentation (
-D missing-docs
) - All features must be documented (
--all-features
)
Automated Documentation Deployment
Documentation is automatically built and deployed to GitHub Pages for the default branch. This ensures that the latest documentation is always available to users and contributors.
Documentation Build Process:
flowchart TD DOC_TRIGGER["Documentation Job Trigger"] BUILD["Build Documentationcargo doc --no-deps --all-features"] INDEX["Generate Index PageRedirect to crate docs"] DEPLOY["Deploy to GitHub Pagesgh-pages branch"] PAGES["GitHub Pages SitePublic Documentation"] NOTE["Only deploys fromdefault branch"] BUILD --> INDEX DEPLOY --> NOTE DEPLOY --> PAGES DOC_TRIGGER --> BUILD INDEX --> DEPLOY
The documentation build includes a custom index page that automatically redirects to the main crate documentation, providing a seamless user experience.
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L44 - L55)
Contributing Guidelines
Contribution Workflow
- Fork and Clone: Fork the repository and clone your fork locally
- Setup Environment: Install nightly toolchain with required components and targets
- Create Branch: Create a feature branch for your changes
- Develop: Make changes following the code quality standards
- Test Locally: Run the complete test suite locally
- Submit PR: Create a pull request with a clear description of changes
- CI Validation: Ensure all CI checks pass
- Code Review: Participate in the code review process
Submission Requirements
All contributions must:
- Pass automated formatting and linting checks
- Build successfully on all supported platforms
- Include appropriate unit tests where applicable
- Maintain or improve documentation coverage
- Follow the existing code style and architecture patterns
Project Maintenance
The project uses specific ignore patterns to maintain a clean repository structure:
Ignored Files and Directories:
/target
- Build artifacts/.vscode
- IDE-specific configuration.DS_Store
- macOS system filesCargo.lock
- Lock file (library project)
Sources: .gitignore(L1 - L4)
Overview
Relevant source files
Purpose and Scope
The axmm_crates
repository provides foundational memory management primitives designed for operating system kernels, hypervisors, and embedded systems. This workspace contains two complementary crates that implement type-safe address handling and high-level memory mapping operations for no-std
environments.
This document covers the overall architecture and purpose of the axmm_crates
workspace. For detailed information about the layered design principles, see System Architecture. For implementation details of the individual crates, see memory_addr Crate and memory_set Crate.
Repository Structure
The axmm_crates
workspace is organized as a Rust workspace containing two primary crates that work together to provide comprehensive memory management capabilities:
Crate | Purpose | Key Components |
---|---|---|
memory_addr | Address type foundations | PhysAddr,VirtAddr,MemoryAddrtrait, alignment utilities |
memory_set | Memory mapping management | MemorySet,MemoryArea,MappingBackendtrait |
flowchart TD subgraph subGraph2["axmm_crates Workspace"] WORKSPACE["Cargo.tomlWorkspace Root"] subgraph subGraph1["Management Layer"] MS["memory_set/"] MS_CARGO["memory_set/Cargo.toml"] MS_LIB["memory_set/src/lib.rs"] end subgraph subGraph0["Foundation Layer"] MA["memory_addr/"] MA_CARGO["memory_addr/Cargo.toml"] MA_LIB["memory_addr/src/lib.rs"] end end MA --> MA_CARGO MA --> MA_LIB MS --> MS_CARGO MS --> MS_LIB MS_LIB --> MA_LIB WORKSPACE --> MA WORKSPACE --> MS
Sources: Cargo.toml(L1 - L20) README.md(L1 - L8)
Core Crates Overview
memory_addr: Address Type Foundations
The memory_addr
crate provides type-safe abstractions for physical and virtual memory addresses. It implements the MemoryAddr
trait that serves as the foundation for all address operations throughout the system.
Key Features:
- Type-safe
PhysAddr
andVirtAddr
wrappers preventing address type confusion - Alignment operations for page boundaries and custom alignments
- Address range types with containment and overlap detection
- Page iteration utilities for memory traversal
memory_set: Memory Mapping Management
The memory_set
crate builds upon memory_addr
to provide high-level memory mapping operations similar to mmap()
functionality. It manages collections of memory areas and provides abstraction over different mapping backends.
Key Features:
MemorySet
collections for managing multiple memory areasMemoryArea
structures representing individual mapped regionsMappingBackend
trait for hardware abstraction- Area splitting, merging, and overlap resolution
flowchart TD subgraph subGraph0["Code Entity Relationships"] MEMORY_ADDR_TRAIT["MemoryAddr trait"] PHYS_ADDR["PhysAddr struct"] VIRT_ADDR["VirtAddr struct"] ADDR_RANGE["AddrRange types"] MEMORY_AREA["MemoryArea struct"] MAPPING_BACKEND["MappingBackend trait"] MEMORY_SET["MemorySet struct"] MAPPING_ERROR["MappingError enum"] end ADDR_RANGE --> MEMORY_AREA MAPPING_BACKEND --> MAPPING_ERROR MAPPING_BACKEND --> MEMORY_AREA MEMORY_ADDR_TRAIT --> ADDR_RANGE MEMORY_ADDR_TRAIT --> PHYS_ADDR MEMORY_ADDR_TRAIT --> VIRT_ADDR MEMORY_AREA --> MEMORY_SET VIRT_ADDR --> MEMORY_AREA
Sources: README.md(L5 - L6)
Target Environment and Use Cases
The axmm_crates
workspace is specifically designed for systems programming contexts where memory management is critical:
Primary Target: ArceOS Integration
This workspace serves as a foundational component for the ArceOS operating system project, providing reusable memory management primitives that can be integrated into kernel subsystems.
Supported Environments
Environment | Compatibility | Notes |
---|---|---|
no-std | Full support | Core design requirement |
Rust 1.70.0+ | Required | Minimum supported version |
OS Kernels | Primary use case | Direct hardware memory management |
Hypervisors | Supported | Guest memory management |
Embedded Systems | Supported | Resource-constrained environments |
License and Availability
The workspace is released under a triple license scheme allowing flexibility for different use cases:
- GPL-3.0-or-later for open source projects
- Apache-2.0 for permissive licensing needs
- MulanPSL-2.0 for compliance with Chinese software standards
flowchart TD subgraph subGraph1["axmm_crates Components"] MEMORY_ADDR_LIB["memory_addr crate"] MEMORY_SET_LIB["memory_set crate"] end subgraph subGraph0["Integration Context"] ARCEOS["ArceOS Operating System"] KERNEL_SUBSYS["Kernel Subsystems"] MM_LAYER["Memory Management Layer"] CONSUMERS["Consumer Applications"] HYPERVISOR["Hypervisor Systems"] EMBEDDED["Embedded Systems"] end ARCEOS --> MM_LAYER CONSUMERS --> MEMORY_SET_LIB EMBEDDED --> MEMORY_ADDR_LIB HYPERVISOR --> MEMORY_SET_LIB KERNEL_SUBSYS --> MEMORY_SET_LIB MEMORY_SET_LIB --> MEMORY_ADDR_LIB MM_LAYER --> MEMORY_SET_LIB
Sources: Cargo.toml(L9 - L19) README.md(L1 - L3)
The modular design allows consumers to use only the components they need - systems requiring only basic address handling can depend solely on memory_addr
, while those needing full memory mapping capabilities can use both crates together.
System Architecture
Relevant source files
Purpose and Scope
This document explains the layered architecture design of the axmm_crates workspace, which provides foundational memory management capabilities for operating system development. The architecture consists of two primary crates: memory_addr
serving as the foundational address handling layer, and memory_set
providing higher-level memory mapping management built upon those foundations.
For detailed documentation of individual components, see memory_addr Crate and memory_set Crate. For practical implementation guidance, see Development Guide.
Workspace Structure and Dependencies
The axmm_crates workspace implements a clean two-layer architecture where each layer provides specific abstractions for memory management operations.
Workspace Architecture
flowchart TD subgraph external["External Context"] arceos["ArceOS Operating System"] consumers["Consumer Systems"] end subgraph workspace["axmm_crates Workspace"] subgraph management["Management Layer"] memory_set["memory_set crate"] end subgraph foundation["Foundation Layer"] memory_addr["memory_addr crate"] end end arceos --> memory_addr arceos --> memory_set memory_addr --> consumers memory_addr --> memory_set memory_set --> consumers
Sources: Cargo.toml(L1 - L20) README.md(L1 - L8)
Core Component Dependencies
flowchart TD subgraph set_crate["memory_set Components"] MappingBackend["MappingBackend trait"] MemoryArea["MemoryArea struct"] MemorySet["MemorySet struct"] MappingError["MappingError enum"] end subgraph addr_crate["memory_addr Components"] MemoryAddr["MemoryAddr trait"] PhysAddr["PhysAddr struct"] VirtAddr["VirtAddr struct"] AddrRange["AddrRange types"] PageIter["PageIter types"] end AddrRange --> MemoryArea AddrRange --> PageIter MappingBackend --> MappingError MappingBackend --> MemoryArea MemoryAddr --> PhysAddr MemoryAddr --> VirtAddr MemoryArea --> MemorySet PhysAddr --> AddrRange VirtAddr --> AddrRange VirtAddr --> MappingBackend VirtAddr --> MemoryArea
Sources: memory_addr/README.md(L12 - L29) memory_set/README.md(L17 - L18) memory_set/README.md(L49 - L89)
Design Principles
Type Safety and Address Separation
The architecture enforces strict type safety by separating physical and virtual addresses through distinct types. The MemoryAddr
trait provides common operations while PhysAddr
and VirtAddr
prevent mixing address types inappropriately.
Principle | Implementation | Benefit |
---|---|---|
Type Safety | SeparatePhysAddrandVirtAddrtypes | Prevents address type confusion |
Trait Abstraction | MemoryAddrtrait for common operations | Consistent interface across address types |
Range Operations | AddrRangetypes for memory regions | Safe range manipulation and validation |
Backend Abstraction Layer
The MappingBackend
trait provides hardware-agnostic memory mapping operations, enabling the same high-level logic to work across different memory management units and page table implementations.
flowchart TD subgraph implementations["Backend Implementations"] MockBackend["MockBackend"] HardwareBackend["Hardware-specific backends"] TestBackend["Testing backends"] end subgraph abstraction["Abstraction Layer"] MappingBackend_trait["MappingBackend trait"] end subgraph highlevel["High-Level Operations"] MemorySet_map["MemorySet::map()"] MemorySet_unmap["MemorySet::unmap()"] MemorySet_protect["MemorySet::protect()"] end MappingBackend_trait --> HardwareBackend MappingBackend_trait --> MockBackend MappingBackend_trait --> TestBackend MemorySet_map --> MappingBackend_trait MemorySet_protect --> MappingBackend_trait MemorySet_unmap --> MappingBackend_trait
Sources: memory_set/README.md(L49 - L89)
No-std Compatibility
The entire architecture is designed for no-std
environments, making it suitable for kernel development and embedded systems where standard library features are unavailable.
Integration Patterns
ArceOS Integration
The crates are specifically designed as foundational components for the ArceOS operating system project, providing reusable memory management primitives that can be composed into larger memory management systems.
Consumer Usage Patterns
sequenceDiagram participant OSKernelHypervisor as "OS Kernel/Hypervisor" participant MemorySet as "MemorySet" participant MemoryArea as "MemoryArea" participant MappingBackendimpl as "MappingBackend impl" participant VirtAddrPhysAddr as "VirtAddr/PhysAddr" OSKernelHypervisor ->> VirtAddrPhysAddr: "Create typed addresses" OSKernelHypervisor ->> MemoryArea: "new(start, size, flags, backend)" MemoryArea ->> VirtAddrPhysAddr: "Validate address ranges" OSKernelHypervisor ->> MemorySet: "map(area, page_table, overwrite)" MemorySet ->> MemoryArea: "map_area(page_table)" MemoryArea ->> MappingBackendimpl: "map(start, size, flags, pt)" MappingBackendimpl -->> MemoryArea: "Success/Error" MemoryArea -->> MemorySet: "Result" MemorySet -->> OSKernelHypervisor: "Final result"
Sources: memory_set/README.md(L34 - L46)
Key Architecture Benefits
Benefit | Implementation Detail | Code Location |
---|---|---|
Modularity | Separate crates for different abstraction levels | Cargo.toml4-7 |
Reusability | Trait-based interfaces enable multiple implementations | memory_set/README.md49-89 |
Safety | Type-safe address handling prevents common errors | memory_addr/README.md14-21 |
Testability | Mock backends enable comprehensive testing | memory_set/README.md22-30 |
Hardware Independence | Backend abstraction supports different MMU architectures | memory_set/README.md49-89 |
The layered architecture ensures that low-level address manipulation primitives remain independent and reusable, while higher-level memory mapping operations build upon these foundations to provide comprehensive memory management capabilities.
Sources: Cargo.toml(L1 - L20) README.md(L1 - L8) memory_addr/README.md(L1 - L30) memory_set/README.md(L1 - L91)
memory_addr Crate
Relevant source files
Purpose and Scope
The memory_addr
crate provides foundational abstractions for physical and virtual memory addresses in the ArceOS ecosystem. It serves as the lowest-level component in the memory management stack, offering type-safe address representations, alignment utilities, and iteration capabilities over memory regions.
This crate focuses specifically on address manipulation primitives and does not handle memory mapping or higher-level memory management operations. For memory mapping functionality and management of memory regions, see memory_set Crate.
The crate is designed for no-std
environments and provides essential building blocks for operating system kernels, hypervisors, and embedded systems that need precise control over memory addresses.
Sources: memory_addr/Cargo.toml(L1 - L17) memory_addr/README.md(L1 - L30) memory_addr/src/lib.rs(L1 - L11)
Core Address Type System
The crate implements a type-safe address system built around a common trait with specialized implementations for different address spaces.
flowchart TD subgraph Constants["Constants"] PAGE_SIZE_4K["PAGE_SIZE_4Kconst = 0x1000"] end subgraph subGraph2["Iteration Support"] PageIter["PageIter<SIZE, A>generic iterator"] PageIter4K["PageIter4K<A>type alias"] end subgraph subGraph1["Range Types"] AddrRange["AddrRange<A>generic range"] PhysAddrRange["PhysAddrRangetype alias"] VirtAddrRange["VirtAddrRangetype alias"] end subgraph subGraph0["Core Types"] MemoryAddr["MemoryAddrtrait"] PhysAddr["PhysAddrstruct"] VirtAddr["VirtAddrstruct"] end AddrRange --> PhysAddrRange AddrRange --> VirtAddrRange MemoryAddr --> PageIter MemoryAddr --> PhysAddr MemoryAddr --> VirtAddr PAGE_SIZE_4K --> PageIter4K PageIter --> PageIter4K PhysAddr --> PhysAddrRange VirtAddr --> VirtAddrRange
Sources: memory_addr/src/lib.rs(L8 - L16)
Address Operations and Utilities
The crate provides a comprehensive set of alignment and arithmetic operations that work consistently across all address types.
flowchart TD subgraph subGraph2["Address Methods"] addr_align_down["addr.align_down(align)"] addr_align_up_4k["addr.align_up_4k()"] addr_align_offset_4k["addr.align_offset_4k()"] addr_is_aligned_4k["addr.is_aligned_4k()"] end subgraph subGraph1["4K-Specific Functions"] align_down_4k["align_down_4k(addr)"] align_up_4k["align_up_4k(addr)"] align_offset_4k["align_offset_4k(addr)"] is_aligned_4k["is_aligned_4k(addr)"] end subgraph subGraph0["Generic Functions"] align_down["align_down(addr, align)"] align_up["align_up(addr, align)"] align_offset["align_offset(addr, align)"] is_aligned["is_aligned(addr, align)"] end align_down --> addr_align_down align_down --> align_down_4k align_offset --> align_offset_4k align_offset_4k --> addr_align_offset_4k align_up --> align_up_4k align_up_4k --> addr_align_up_4k is_aligned --> is_aligned_4k is_aligned_4k --> addr_is_aligned_4k
Sources: memory_addr/src/lib.rs(L18 - L76)
Function Categories and Implementation
The crate organizes its functionality into several key categories that build upon each other to provide a complete address manipulation toolkit.
Category | Functions | Purpose |
---|---|---|
Generic Alignment | align_down,align_up,align_offset,is_aligned | Power-of-two alignment operations for any alignment value |
4K Page Helpers | align_down_4k,align_up_4k,align_offset_4k,is_aligned_4k | Specialized functions for common 4K page operations |
Type Wrappers | PhysAddr,VirtAddr | Type-safe address representations with built-in methods |
Range Operations | AddrRange,PhysAddrRange,VirtAddrRange | Address range abstractions for memory region management |
Iteration Support | PageIter,PageIter4K | Iterator types for traversing memory in page-sized chunks |
Sources: memory_addr/src/lib.rs(L18 - L76)
Core Constants and Type Aliases
The crate defines essential constants and type aliases that standardize common memory management patterns:
// From memory_addr/src/lib.rs:12-16
pub const PAGE_SIZE_4K: usize = 0x1000;
pub type PageIter4K<A> = PageIter<PAGE_SIZE_4K, A>;
The PAGE_SIZE_4K
constant (4096 bytes) is fundamental to the system's page-oriented design, while PageIter4K
provides a convenient type alias for the most common page iteration scenario.
Sources: memory_addr/src/lib.rs(L12 - L16)
Module Structure and Exports
The crate follows a clean modular design with focused responsibilities:
flowchart TD subgraph subGraph1["Public API"] MemoryAddr_export["MemoryAddr"] PhysAddr_export["PhysAddr"] VirtAddr_export["VirtAddr"] PageIter_export["PageIter"] AddrRange_export["AddrRange"] PhysAddrRange_export["PhysAddrRange"] VirtAddrRange_export["VirtAddrRange"] end subgraph memory_addr/src/["memory_addr/src/"] lib["lib.rsMain module & re-exports"] addr_mod["addr.rsMemoryAddr trait & types"] iter_mod["iter.rsPageIter implementation"] range_mod["range.rsAddrRange types"] end addr_mod --> MemoryAddr_export addr_mod --> PhysAddr_export addr_mod --> VirtAddr_export iter_mod --> PageIter_export lib --> AddrRange_export lib --> MemoryAddr_export lib --> PageIter_export lib --> PhysAddrRange_export lib --> PhysAddr_export lib --> VirtAddrRange_export lib --> VirtAddr_export range_mod --> AddrRange_export range_mod --> PhysAddrRange_export range_mod --> VirtAddrRange_export
Sources: memory_addr/src/lib.rs(L4 - L10)
Alignment Implementation Details
The alignment functions implement efficient bit manipulation operations that require power-of-two alignments:
align_down(addr, align)
: Usesaddr & !(align - 1)
to mask off lower bitsalign_up(addr, align)
: Uses(addr + align - 1) & !(align - 1)
to round upalign_offset(addr, align)
: Usesaddr & (align - 1)
to get remainderis_aligned(addr, align)
: Checks ifalign_offset(addr, align) == 0
These operations are marked const fn
, enabling compile-time computation when used with constant values.
Sources: memory_addr/src/lib.rs(L24 - L51)
Integration with ArceOS Ecosystem
The memory_addr
crate serves as the foundation layer in the ArceOS memory management stack. It provides the type-safe primitives that higher-level components build upon, particularly the memory_set
crate which uses these address types for memory region management.
The crate's no_std
compatibility and const function implementations make it suitable for both kernel and embedded environments where compile-time optimization and resource constraints are critical.
Sources: memory_addr/Cargo.toml(L5) memory_addr/src/lib.rs(L1)
Address Types and Operations
Relevant source files
This document covers the foundational address handling types and operations provided by the memory_addr
crate. These types provide type-safe abstractions for physical and virtual memory addresses, along with comprehensive alignment and arithmetic operations. For information about address ranges and iteration, see Address Ranges and Page Iteration.
Core Architecture
The address system is built around the MemoryAddr
trait, which provides a common interface for all memory address types. The crate includes two concrete address types (PhysAddr
and VirtAddr
) and utilities for creating custom address types.
Address Type Hierarchy
classDiagram note for MemoryAddr "Auto-implemented for types that are:Copy + From + Into + Ord" class MemoryAddr { <<trait>> +align_down(align) Self +align_up(align) Self +align_offset(align) usize +is_aligned(align) bool +align_down_4k() Self +align_up_4k() Self +offset(offset) Self +add(rhs) Self +sub(rhs) Self +checked_add(rhs) Option~Self~ +checked_sub(rhs) Option~Self~ } class PhysAddr { -usize: usize +from_usize(addr) PhysAddr +as_usize() usize } class VirtAddr { -usize: usize +from_usize(addr) VirtAddr +as_usize() usize +from_ptr_of~T~(*const T) VirtAddr +as_ptr() *const u8 +as_ptr_of~T~() *const T +as_mut_ptr() *mut u8 } MemoryAddr ..|> PhysAddr : implements MemoryAddr ..|> VirtAddr : implements
Sources: memory_addr/src/addr.rs(L12 - L263) memory_addr/src/addr.rs(L450 - L461)
Address Types
PhysAddr
PhysAddr
represents a physical memory address. It is a transparent wrapper around usize
that provides type safety to prevent mixing physical and virtual addresses.
Method | Description | Return Type |
---|---|---|
from_usize(addr) | Creates from raw address value | PhysAddr |
as_usize() | Converts to raw address value | usize |
pa!(addr) | Convenience macro for creation | PhysAddr |
VirtAddr
VirtAddr
represents a virtual memory address with additional pointer conversion capabilities beyond the base MemoryAddr
trait.
Method | Description | Return Type |
---|---|---|
from_ptr_of | Creates from typed pointer | VirtAddr |
from_mut_ptr_of | Creates from mutable pointer | VirtAddr |
as_ptr() | Converts to byte pointer | *const u8 |
as_ptr_of | Converts to typed pointer | *const T |
as_mut_ptr() | Converts to mutable byte pointer | *mut u8 |
as_mut_ptr_of | Converts to mutable typed pointer | *mut T |
va!(addr) | Convenience macro for creation | VirtAddr |
Sources: memory_addr/src/addr.rs(L450 - L461) memory_addr/src/addr.rs(L463 - L500) memory_addr/src/addr.rs(L502 - L516)
Address Type Operations Flow
flowchart TD subgraph Results["Results"] new_addr["New Address"] offset_val["usize offset"] ptr_result["*const/*mut T"] bool_result["bool"] end subgraph Operations["Operations"] align["Alignment Operations"] arith["Arithmetic Operations"] convert["Conversion Operations"] end subgraph subGraph1["Address Types"] PhysAddr["PhysAddr"] VirtAddr["VirtAddr"] end subgraph Creation["Creation"] usize["usize value"] ptr["*const/*mut T"] macro_pa["pa!(0x1000)"] macro_va["va!(0x2000)"] end PhysAddr --> align PhysAddr --> arith VirtAddr --> align VirtAddr --> arith VirtAddr --> convert align --> bool_result align --> new_addr align --> offset_val arith --> new_addr arith --> offset_val convert --> ptr_result macro_pa --> PhysAddr macro_va --> VirtAddr ptr --> VirtAddr usize --> PhysAddr usize --> VirtAddr
Sources: memory_addr/src/addr.rs(L450 - L500) memory_addr/src/addr.rs(L502 - L516)
Alignment Operations
The MemoryAddr
trait provides comprehensive alignment functionality for working with page boundaries and custom alignments.
Core Alignment Methods
Method | Description | Example Usage |
---|---|---|
align_down(align) | Rounds down to alignment boundary | addr.align_down(0x1000) |
align_up(align) | Rounds up to alignment boundary | addr.align_up(0x1000) |
align_offset(align) | Returns offset within alignment | addr.align_offset(0x1000) |
is_aligned(align) | Checks if address is aligned | addr.is_aligned(0x1000) |
4K Page Alignment Shortcuts
Method | Description | Equivalent To |
---|---|---|
align_down_4k() | Aligns down to 4K boundary | align_down(4096) |
align_up_4k() | Aligns up to 4K boundary | align_up(4096) |
align_offset_4k() | Offset within 4K page | align_offset(4096) |
is_aligned_4k() | Checks 4K alignment | is_aligned(4096) |
Alignment Operation Examples
flowchart TD subgraph align_up_4k()["align_up_4k()"] result4["0x3000"] result5["0x3000"] result6["0x4000"] end subgraph align_down_4k()["align_down_4k()"] result1["0x2000"] result2["0x3000"] result3["0x3000"] end subgraph subGraph0["Input Addresses"] addr1["0x2fff"] addr2["0x3000"] addr3["0x3001"] end addr1 --> result1 addr1 --> result4 addr2 --> result2 addr2 --> result5 addr3 --> result3 addr3 --> result6
Sources: memory_addr/src/addr.rs(L27 - L94)
Arithmetic Operations
The trait provides extensive arithmetic operations with multiple overflow handling strategies.
Basic Arithmetic
Method | Description | Overflow Behavior |
---|---|---|
offset(isize) | Signed offset addition | Panics |
add(usize) | Unsigned addition | Panics |
sub(usize) | Unsigned subtraction | Panics |
sub_addr(Self) | Address difference | Panics |
Wrapping Arithmetic
Method | Description | Overflow Behavior |
---|---|---|
wrapping_offset(isize) | Signed offset addition | Wraps around |
wrapping_add(usize) | Unsigned addition | Wraps around |
wrapping_sub(usize) | Unsigned subtraction | Wraps around |
wrapping_sub_addr(Self) | Address difference | Wraps around |
Checked Arithmetic
Method | Description | Return Type |
---|---|---|
checked_add(usize) | Safe addition | Option |
checked_sub(usize) | Safe subtraction | Option |
checked_sub_addr(Self) | Safe address difference | Option |
Overflowing Arithmetic
Method | Description | Return Type |
---|---|---|
overflowing_add(usize) | Addition with overflow flag | (Self, bool) |
overflowing_sub(usize) | Subtraction with overflow flag | (Self, bool) |
overflowing_sub_addr(Self) | Address difference with overflow flag | (usize, bool) |
Arithmetic Operations Decision Tree
flowchart TD start["Need Address Arithmetic?"] signed["Signed Offset?"] overflow["Overflow Handling?"] offset_op["offset(isize)wrapping_offset(isize)"] panic_ops["add(usize)sub(usize)sub_addr(Self)"] wrap_ops["wrapping_add(usize)wrapping_sub(usize)wrapping_sub_addr(Self)"] check_ops["checked_add(usize)checked_sub(usize)checked_sub_addr(Self)"] over_ops["overflowing_add(usize)overflowing_sub(usize)overflowing_sub_addr(Self)"] overflow --> check_ops overflow --> over_ops overflow --> panic_ops overflow --> wrap_ops signed --> offset_op signed --> overflow start --> signed
Sources: memory_addr/src/addr.rs(L99 - L258)
Address Type Creation
The crate provides macros for creating custom address types with the same capabilities as PhysAddr
and VirtAddr
.
def_usize_addr! Macro
The def_usize_addr!
macro generates new address types with automatic implementations:
- Basic traits:
Copy
,Clone
,Default
,Ord
,PartialOrd
,Eq
,PartialEq
- Conversion traits:
From<usize>
,Into<usize>
- Arithmetic operators:
Add<usize>
,Sub<usize>
,Sub<Self>
- Utility methods:
from_usize()
,as_usize()
def_usize_addr_formatter! Macro
The def_usize_addr_formatter!
macro generates formatting implementations:
Debug
trait with custom format stringLowerHex
andUpperHex
traits
Custom Address Type Creation Flow
sequenceDiagram participant Developer as "Developer" participant def_usize_addr as "def_usize_addr!" participant def_usize_addr_formatter as "def_usize_addr_formatter!" participant CustomAddressType as "Custom Address Type" Developer ->> def_usize_addr: "Define type CustomAddr" def_usize_addr ->> CustomAddressType: "Generate struct with usize field" def_usize_addr ->> CustomAddressType: "Implement required traits" def_usize_addr ->> CustomAddressType: "Add conversion methods" def_usize_addr ->> CustomAddressType: "Add arithmetic operators" Developer ->> def_usize_addr_formatter: "Set format string" def_usize_addr_formatter ->> CustomAddressType: "Implement Debug trait" def_usize_addr_formatter ->> CustomAddressType: "Implement Hex traits" CustomAddressType ->> Developer: "Ready for use with MemoryAddr"
Sources: memory_addr/src/addr.rs(L302 - L448)
MemoryAddr Trait Implementation
The MemoryAddr
trait is automatically implemented for any type meeting the required bounds, providing a blanket implementation that enables consistent behavior across all address types.
Automatic Implementation Requirements
flowchart TD subgraph Result["Result"] unified["Unified Interface"] type_safety["Type Safety"] rich_api["Rich API"] end subgraph subGraph1["Automatic Implementation"] memory_addr["MemoryAddr trait"] alignment["Alignment Methods"] arithmetic["Arithmetic Methods"] end subgraph subGraph0["Required Bounds"] copy["Copy"] from_usize["From"] into_usize["Into"] ord["Ord"] end alignment --> unified arithmetic --> type_safety copy --> memory_addr from_usize --> memory_addr into_usize --> memory_addr memory_addr --> alignment memory_addr --> arithmetic ord --> memory_addr unified --> rich_api
Sources: memory_addr/src/addr.rs(L261 - L263)
Address Ranges
Relevant source files
This document covers the address range functionality provided by the memory_addr
crate, specifically the AddrRange
generic type and its associated operations. Address ranges represent contiguous spans of memory addresses with type safety and comprehensive range manipulation operations.
For information about individual address types and arithmetic operations, see Address Types and Operations. For page iteration over ranges, see Page Iteration.
Address Range Structure
The core of the address range system is the AddrRange<A>
generic struct, where A
implements the MemoryAddr
trait. This provides type-safe representation of memory address ranges with inclusive start bounds and exclusive end bounds.
AddrRange Type Hierarchy
flowchart TD subgraph subGraph2["Address Types"] VA["VirtAddr"] PA["PhysAddr"] UA["usize"] end subgraph subGraph1["Concrete Type Aliases"] VAR["VirtAddrRangeAddrRange<VirtAddr>"] PAR["PhysAddrRangeAddrRange<PhysAddr>"] end subgraph subGraph0["Generic Types"] AR["AddrRange<A>Generic range type"] MA["MemoryAddr traitConstraint for A"] end AR --> MA AR --> UA PA --> MA PAR --> AR PAR --> PA UA --> MA VA --> MA VAR --> AR VAR --> VA
Sources: memory_addr/src/range.rs(L1 - L28) memory_addr/src/range.rs(L365 - L368)
The AddrRange<A>
struct contains two public fields:
start: A
- The lower bound of the range (inclusive)end: A
- The upper bound of the range (exclusive)
Type aliases provide convenient names for common address range types:
VirtAddrRange
=AddrRange<VirtAddr>
PhysAddrRange
=AddrRange<PhysAddr>
Range Construction Methods
Address ranges can be constructed through several methods, each with different safety and error-handling characteristics.
Construction Method Categories
Method | Safety | Overflow Handling | Use Case |
---|---|---|---|
new() | Panics on invalid range | Checked | Safe construction with panic |
try_new() | ReturnsOption | Checked | Fallible construction |
new_unchecked() | Unsafe | Unchecked | Performance-critical paths |
from_start_size() | Panics on overflow | Checked | Size-based construction |
try_from_start_size() | ReturnsOption | Checked | Fallible size-based construction |
from_start_size_unchecked() | Unsafe | Unchecked | Performance-critical size-based |
Sources: memory_addr/src/range.rs(L34 - L193)
Range Construction Flow
flowchart TD subgraph Results["Results"] SUCCESS["AddrRange<A>"] PANIC["Panic"] NONE["None"] ERROR["Error"] end subgraph Validation["Validation"] CHECK["start <= end?"] OVERFLOW["Addition overflow?"] end subgraph subGraph1["Construction Methods"] NEW["new()"] TRYNEW["try_new()"] UNSAFE["new_unchecked()"] FSS["from_start_size()"] TRYFSS["try_from_start_size()"] UNSAFEFSS["from_start_size_unchecked()"] TRYFROM["TryFrom<Range<T>>"] end subgraph subGraph0["Input Types"] SE["start, end addresses"] SS["start address, size"] RNG["Rust Range<T>"] end START["Range Construction Request"] CHECK --> NONE CHECK --> PANIC CHECK --> SUCCESS FSS --> OVERFLOW NEW --> CHECK OVERFLOW --> NONE OVERFLOW --> PANIC OVERFLOW --> SUCCESS RNG --> TRYFROM SE --> NEW SE --> TRYNEW SE --> UNSAFE SS --> FSS SS --> TRYFSS SS --> UNSAFEFSS START --> RNG START --> SE START --> SS TRYFROM --> CHECK TRYFSS --> OVERFLOW TRYNEW --> CHECK UNSAFE --> SUCCESS UNSAFEFSS --> SUCCESS
Sources: memory_addr/src/range.rs(L57 - L193) memory_addr/src/range.rs(L307 - L317)
Range Operations and Relationships
Address ranges support comprehensive operations for checking containment, overlap, and spatial relationships between ranges.
Range Relationship Operations
flowchart TD subgraph Results["Results"] BOOL["bool"] USIZE["usize"] end subgraph subGraph1["Query Operations"] CONTAINS["contains(addr: A)Point containment"] CONTAINSRANGE["contains_range(other)Range containment"] CONTAINEDIN["contained_in(other)Reverse containment"] OVERLAPS["overlaps(other)Range intersection"] ISEMPTY["is_empty()Zero-size check"] SIZE["size()Range size"] end subgraph subGraph0["Range A"] RA["AddrRange<A>"] end CONTAINEDIN --> BOOL CONTAINS --> BOOL CONTAINSRANGE --> BOOL ISEMPTY --> BOOL OVERLAPS --> BOOL RA --> CONTAINEDIN RA --> CONTAINS RA --> CONTAINSRANGE RA --> ISEMPTY RA --> OVERLAPS RA --> SIZE SIZE --> USIZE
Sources: memory_addr/src/range.rs(L228 - L302)
Key Range Operations
The following operations are available on all AddrRange<A>
instances:
- Point Containment:
contains(addr)
checks if a single address falls within the range - Range Containment:
contains_range(other)
checks if another range is entirely within this range - Containment Check:
contained_in(other)
checks if this range is entirely within another range - Overlap Detection:
overlaps(other)
checks if two ranges have any intersection - Empty Range Check:
is_empty()
returns true if start equals end - Size Calculation:
size()
returns the number of bytes in the range
Convenience Macros
The crate provides three macros for convenient address range creation with compile-time type inference and runtime validation.
Macro Overview
Macro | Target Type | Purpose |
---|---|---|
addr_range! | AddrRange(inferred) | Generic range creation |
va_range! | VirtAddrRange | Virtual address range creation |
pa_range! | PhysAddrRange | Physical address range creation |
Sources: memory_addr/src/range.rs(L391 - L448)
Macro Usage Pattern
flowchart TD subgraph subGraph3["Output Types"] GENERICRANGE["AddrRange<A> (inferred)"] VIRTRANGE["VirtAddrRange"] PHYSRANGE["PhysAddrRange"] end subgraph subGraph2["Internal Processing"] TRYFROM["TryFrom<Range<T>>::try_from()"] EXPECT["expect() on Result"] end subgraph subGraph1["Macro Processing"] ADDRRANGE["addr_range!(0x1000..0x2000)"] VARANGE["va_range!(0x1000..0x2000)"] PARANGE["pa_range!(0x1000..0x2000)"] end subgraph subGraph0["Input Syntax"] RANGE["Rust range syntaxstart..end"] end ADDRRANGE --> TRYFROM EXPECT --> GENERICRANGE EXPECT --> PHYSRANGE EXPECT --> VIRTRANGE PARANGE --> TRYFROM RANGE --> ADDRRANGE RANGE --> PARANGE RANGE --> VARANGE TRYFROM --> EXPECT VARANGE --> TRYFROM
Sources: memory_addr/src/range.rs(L391 - L448)
Usage Examples and Patterns
The test suite demonstrates common usage patterns for address ranges, showing both basic operations and complex relationship checking.
Basic Range Operations
// Creating ranges with different methods
let range = VirtAddrRange::new(0x1000.into(), 0x2000.into());
let size_range = VirtAddrRange::from_start_size(0x1000.into(), 0x1000);
// Using macros for convenience
let macro_range = va_range!(0x1000..0x2000);
// Checking basic properties
assert_eq!(range.size(), 0x1000);
assert!(!range.is_empty());
assert!(range.contains(0x1500.into()));
Sources: memory_addr/src/range.rs(L464 - L511)
Range Relationship Testing
The codebase extensively tests range relationships using various scenarios:
// Containment checking
assert!(range.contains_range(va_range!(0x1001..0x1fff)));
assert!(!range.contains_range(va_range!(0xfff..0x2001)));
// Overlap detection
assert!(range.overlaps(va_range!(0x1800..0x2001)));
assert!(!range.overlaps(va_range!(0x2000..0x2800)));
// Spatial relationships
assert!(range.contained_in(va_range!(0xfff..0x2001)));
Sources: memory_addr/src/range.rs(L483 - L505)
Error Handling Patterns
The range system provides both panicking and fallible construction methods:
// Panicking construction (for known-valid ranges)
let valid_range = VirtAddrRange::new(start, end);
// Fallible construction (for potentially invalid input)
if let Some(range) = VirtAddrRange::try_new(start, end) {
// Use the valid range
}
// Size-based construction with overflow handling
let safe_range = VirtAddrRange::try_from_start_size(start, size)?;
Sources: memory_addr/src/range.rs(L58 - L167)
Page Iteration
Relevant source files
This document covers the page iteration functionality provided by the memory_addr
crate, specifically the PageIter
struct and its associated methods. Page iteration allows traversing memory addresses in fixed-size page increments, which is essential for memory management operations like mapping, unmapping, and allocating memory in page-aligned chunks.
For information about address types and basic operations, see Address Types and Operations. For address range operations, see Address Ranges.
Overview
The PageIter
struct provides a safe, type-safe iterator for traversing memory addresses in page-sized steps. It enforces alignment requirements and validates page sizes at construction time, ensuring that iteration only occurs over properly aligned memory regions.
PageIter Structure Design
Sources: memory_addr/src/iter.rs(L22 - L28) memory_addr/src/iter.rs(L50 - L65)
PageIter Structure and Parameters
The PageIter
struct is defined with two generic parameters that control its behavior:
Parameter | Type | Purpose |
---|---|---|
PAGE_SIZE | const usize | Compile-time constant specifying page size in bytes |
A | Type bound byMemoryAddr | Address type (e.g.,PhysAddr,VirtAddr,usize) |
The struct contains two fields:
start
: Current position in the iterationend
: End boundary (exclusive) for the iteration
The PAGE_SIZE
parameter must be a power of 2, and both start
and end
addresses must be aligned to PAGE_SIZE
boundaries.
Sources: memory_addr/src/iter.rs(L22 - L28)
Creating PageIter Instances
PageIter Construction Flow
flowchart TD Start["PageIter::new(start, end)"] CheckPowerOf2["PAGE_SIZE.is_power_of_two()"] ReturnNone["Return None"] CheckStartAlign["start.is_aligned(PAGE_SIZE)"] CheckEndAlign["end.is_aligned(PAGE_SIZE)"] CreateIter["Create PageIter instance"] ReturnSome["Return Some(PageIter)"] CheckEndAlign --> CreateIter CheckEndAlign --> ReturnNone CheckPowerOf2 --> CheckStartAlign CheckPowerOf2 --> ReturnNone CheckStartAlign --> CheckEndAlign CheckStartAlign --> ReturnNone CreateIter --> ReturnSome Start --> CheckPowerOf2
Sources: memory_addr/src/iter.rs(L34 - L47)
The new
method performs several validation checks:
- Page Size Validation: Ensures
PAGE_SIZE
is a power of 2 - Start Address Alignment: Verifies
start
is aligned toPAGE_SIZE
- End Address Alignment: Verifies
end
is aligned toPAGE_SIZE
If any validation fails, the method returns None
. Otherwise, it returns Some(PageIter)
with the validated parameters.
Iterator Implementation
The PageIter
implements the Iterator
trait, yielding addresses of type A
on each iteration:
Iterator Mechanics
sequenceDiagram participant Client as Client participant PageIterinstance as "PageIter instance" participant MemoryAddrtrait as "MemoryAddr trait" Client ->> PageIterinstance: next() PageIterinstance ->> PageIterinstance: Check if start < end alt start < end PageIterinstance ->> PageIterinstance: Store current start as ret PageIterinstance ->> MemoryAddrtrait: start.add(PAGE_SIZE) MemoryAddrtrait -->> PageIterinstance: New start address PageIterinstance ->> PageIterinstance: Update self.start PageIterinstance -->> Client: Some(ret) else start >= end PageIterinstance -->> Client: None end
Sources: memory_addr/src/iter.rs(L50 - L65)
The iteration process:
- Boundary Check: Compare current
start
withend
- Value Return: If within bounds, return current
start
value - Advance: Increment
start
byPAGE_SIZE
using theadd
method - Termination: Return
None
whenstart
reaches or exceedsend
Usage Patterns
Basic Page Iteration
The most common usage pattern involves iterating over a memory range with 4KB pages:
// Example from documentation
let mut iter = PageIter::<0x1000, usize>::new(0x1000, 0x3000).unwrap();
assert_eq!(iter.next(), Some(0x1000));
assert_eq!(iter.next(), Some(0x2000));
assert_eq!(iter.next(), None);
Error Handling
PageIter construction can fail if alignment requirements are not met:
// This will return None due to misaligned end address
assert!(PageIter::<0x1000, usize>::new(0x1000, 0x3001).is_none());
Common PageIter Usage Scenarios
flowchart TD subgraph subGraph0["Common Page Sizes"] Page4K["0x1000 (4KB)"] Page2M["0x200000 (2MB)"] Page1G["0x40000000 (1GB)"] end PageIter["PageIter<PAGE_SIZE, A>"] Mapping["Memory Mapping Operations"] Allocation["Page Allocation"] Scanning["Memory Scanning"] Cleanup["Memory Cleanup"] MapPages["Map individual pages"] FindFree["Find free page sequences"] CheckPerms["Check page permissions"] UnmapPages["Unmap page ranges"] Allocation --> FindFree Cleanup --> UnmapPages Mapping --> MapPages PageIter --> Allocation PageIter --> Cleanup PageIter --> Mapping PageIter --> Page1G PageIter --> Page2M PageIter --> Page4K PageIter --> Scanning Scanning --> CheckPerms
Sources: memory_addr/src/iter.rs(L10 - L21)
Integration with Address Types
PageIter works with any type implementing MemoryAddr
, enabling type-safe iteration over different address spaces:
- Physical addresses:
PageIter<0x1000, PhysAddr>
- Virtual addresses:
PageIter<0x1000, VirtAddr>
- Raw addresses:
PageIter<0x1000, usize>
The iterator leverages the MemoryAddr
trait's is_aligned
and add
methods to ensure correct alignment validation and address arithmetic.
Sources: memory_addr/src/iter.rs(L1) memory_addr/src/iter.rs(L24) memory_addr/src/iter.rs(L32)
memory_set Crate
Relevant source files
Purpose and Scope
The memory_set
crate provides data structures and operations for managing memory mappings in operating system kernels and hypervisors. It implements a high-level abstraction layer for memory area management that supports operations similar to Unix mmap
, munmap
, and mprotect
system calls. This crate builds upon the foundational address types from the memory_addr
crate to provide a complete memory mapping management solution.
For information about the underlying address types and operations, see memory_addr Crate. For detailed documentation of specific components within this crate, see MemorySet Core, MemoryArea, and MappingBackend.
Core Components Overview
The memory_set
crate provides three primary components that work together to manage memory mappings:
Core Types Architecture
flowchart TD subgraph subGraph2["Generic Parameters"] PT["PageTable type"] FL["Flags type"] AD["Addr type"] end subgraph subGraph1["memory_addr Dependencies"] VA["VirtAddr"] AR["AddrRange<VirtAddr>"] end subgraph subGraph0["memory_set Crate"] MS["MemorySet<B>"] MA["MemoryArea<B>"] MB["MappingBackend trait"] ME["MappingError enum"] MR["MappingResult<T> type"] end MA --> AR MA --> MB MA --> MR MA --> VA MB --> AD MB --> FL MB --> PT MR --> ME MS --> MA MS --> MR
Sources: memory_set/src/lib.rs(L13 - L15) memory_set/src/lib.rs(L17 - L29)
Component Responsibilities
Component | Purpose | Key Methods |
---|---|---|
MemorySet | Collection manager for memory areas | map(),unmap(),protect(),find_free_area() |
MemoryArea | Individual memory region representation | new(),va_range(),size(),flags() |
MappingBackend | Hardware abstraction trait | map(),unmap(),protect() |
MappingError | Error type for mapping operations | InvalidParam,AlreadyExists,BadState |
Sources: memory_set/src/lib.rs(L13 - L15) memory_set/src/lib.rs(L17 - L26)
Memory Mapping Workflow
The following diagram illustrates how the components interact during typical memory mapping operations:
Memory Mapping Operation Flow
Sources: memory_set/README.md(L34 - L46) memory_set/README.md(L49 - L89)
Error Handling and Types
The crate defines a comprehensive error handling system for memory mapping operations:
Error Types
flowchart TD subgraph subGraph1["Error Variants"] IP["InvalidParam"] AE["AlreadyExists"] BS["BadState"] end subgraph subGraph0["Result Types"] MR["MappingResult<T>"] RES["Result<T, MappingError>"] end DESC1["Parameter Validation"] DESC2["Overlap Detection"] DESC3["Backend State"] AE --> DESC2 BS --> DESC3 IP --> DESC1 MR --> RES RES --> AE RES --> BS RES --> IP
Sources: memory_set/src/lib.rs(L17 - L29)
Error Handling Usage
The error types provide specific information about mapping operation failures:
InvalidParam
: Used when input parameters like addresses, sizes, or flags are invalidAlreadyExists
: Returned when attempting to map a range that overlaps with existing mappingsBadState
: Indicates the underlying page table or backend is in an inconsistent state
The MappingResult<T>
type alias simplifies function signatures throughout the crate by defaulting the success type to unit ()
for operations that don't return values.
Sources: memory_set/src/lib.rs(L20 - L26) memory_set/src/lib.rs(L28 - L29)
Integration with memory_addr
The memory_set
crate builds directly on the memory_addr
crate's foundational types:
Address Type Integration
flowchart TD subgraph subGraph2["MappingBackend Generic"] MB_ADDR["MappingBackend::Addr"] MB_MAP["map(start: Self::Addr, ...)"] MB_UNMAP["unmap(start: Self::Addr, ...)"] end subgraph subGraph1["memory_set Usage"] MA_START["MemoryArea::start: VirtAddr"] MA_RANGE["MemoryArea::va_range() → VirtAddrRange"] MS_FIND["MemorySet::find_free_area(base: VirtAddr)"] MS_UNMAP["MemorySet::unmap(start: VirtAddr, size: usize)"] end subgraph subGraph0["memory_addr Types"] VA["VirtAddr"] PA["PhysAddr"] AR["AddrRange<A>"] VAR["VirtAddrRange"] end MB_ADDR --> MB_MAP MB_ADDR --> MB_UNMAP VA --> MA_START VA --> MB_ADDR VA --> MS_FIND VA --> MS_UNMAP VAR --> MA_RANGE
Sources: memory_set/Cargo.toml(L16 - L17) memory_set/README.md(L17) memory_set/README.md(L50)
The memory_set
crate leverages the type safety and address manipulation capabilities provided by memory_addr
to ensure that memory mapping operations are performed on properly validated and typed addresses. This integration prevents common errors like mixing physical and virtual addresses or operating on misaligned memory ranges.
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:
- All areas remain non-overlapping
- Area boundaries align with page boundaries
- 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:
Property | Implementation | Verification |
---|---|---|
Non-overlapping Areas | Overlap detection inmap(), careful splitting inunmap()/protect() | overlaps()method, assertions in insertion |
Sorted Order | BTreeMap automatically maintains order by start address | BTreeMap invariants |
Consistent Mappings | All operations call correspondingMemoryAreamethods | Backend abstraction layer |
Boundary Alignment | Size validation and address arithmetic | AddrRangetype 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)
MemoryArea
Relevant source files
This document covers the MemoryArea
struct, which represents individual memory regions within the memory_set crate. MemoryArea encapsulates a continuous range of virtual memory addresses along with their associated flags and mapping backend. For information about managing collections of memory areas, see MemorySet Core. For details about the abstraction layer for memory mapping implementations, see MappingBackend.
Core Structure and Purpose
The MemoryArea<B: MappingBackend>
struct represents a single contiguous virtual memory region with uniform properties. It serves as the fundamental building block for memory management operations, combining address ranges from the memory_addr crate with backend-specific mapping functionality.
MemoryArea Components Diagram
flowchart TD subgraph subGraph2["MappingBackend Dependencies"] BACKEND_TRAIT["MappingBackend trait"] PAGE_TABLE["B::PageTable"] ADDR_TYPE["B::Addr"] FLAGS_TYPE["B::Flags"] end subgraph subGraph1["memory_addr Dependencies"] ADDR_RANGE["AddrRange"] MEMORY_ADDR["MemoryAddr trait"] end subgraph subGraph0["MemoryArea Structure"] MA["MemoryArea<B>"] VAR["va_range: AddrRange<B::Addr>"] FLAGS["flags: B::Flags"] BACKEND["backend: B"] end ADDR_RANGE --> MEMORY_ADDR BACKEND --> BACKEND_TRAIT BACKEND --> PAGE_TABLE FLAGS --> FLAGS_TYPE MA --> BACKEND MA --> FLAGS MA --> VAR VAR --> ADDR_RANGE VAR --> ADDR_TYPE
Sources: memory_set/src/area.rs(L12 - L16)
Construction and Access Methods
MemoryArea provides a straightforward constructor and comprehensive access methods for its properties.
Constructor and Basic Properties
Method | Return Type | Purpose |
---|---|---|
new(start, size, flags, backend) | Self | Creates new memory area from start address and size |
va_range() | AddrRange<B::Addr> | Returns the virtual address range |
flags() | B::Flags | Returns memory flags (permissions, etc.) |
start() | B::Addr | Returns start address |
end() | B::Addr | Returns end address |
size() | usize | Returns size in bytes |
backend() | &B | Returns reference to mapping backend |
The constructor uses AddrRange::from_start_size()
to create the virtual address range and will panic if start + size
overflows.
Sources: memory_set/src/area.rs(L18 - L61)
Memory Mapping Operations
MemoryArea provides three core mapping operations that interface with the page table through the MappingBackend.
Mapping Operations Flow
sequenceDiagram participant MemorySet as MemorySet participant MemoryArea as MemoryArea participant MappingBackend as MappingBackend participant PageTable as PageTable Note over MemorySet,PageTable: Core Mapping Operations MemorySet ->> MemoryArea: map_area(page_table) MemoryArea ->> MemoryArea: self.start(), self.size(), self.flags MemoryArea ->> MappingBackend: backend.map(start, size, flags, page_table) MappingBackend ->> PageTable: Hardware-specific mapping PageTable -->> MappingBackend: Success/failure MappingBackend -->> MemoryArea: bool result MemoryArea -->> MemorySet: MappingResult MemorySet ->> MemoryArea: unmap_area(page_table) MemoryArea ->> MappingBackend: backend.unmap(start, size, page_table) MappingBackend ->> PageTable: Hardware-specific unmapping PageTable -->> MappingBackend: Success/failure MappingBackend -->> MemoryArea: bool result MemoryArea -->> MemorySet: MappingResult MemorySet ->> MemoryArea: protect_area(new_flags, page_table) MemoryArea ->> MappingBackend: backend.protect(start, size, new_flags, page_table) MemoryArea ->> MemoryArea: self.flags = new_flags MemoryArea -->> MemorySet: MappingResult
Core Mapping Methods
map_area(page_table)
- Maps the entire memory area using the backend's map functionunmap_area(page_table)
- Unmaps the entire memory areaprotect_area(new_flags, page_table)
- Changes memory protection flags
All methods return MappingResult
, which is Result<(), MappingError>
. Operations that fail return MappingError::BadState
.
Sources: memory_set/src/area.rs(L74 - L99)
Area Manipulation Operations
MemoryArea supports sophisticated manipulation operations for dynamic memory management scenarios, including shrinking from either end and splitting into multiple areas.
Area Manipulation Capabilities
flowchart TD subgraph subGraph3["split Operations"] SP1["Area1: start |-- size1 --| pos"] SP2["Area2: pos |-- size2 --| end"] end subgraph subGraph2["shrink_right Operations"] SR1["start |-- new_size --| new_end"] SR2["unmapped: new_end |--| end"] end subgraph subGraph1["shrink_left Operations"] SL1["new_start |-- new_size --| end"] SL2["unmapped: start |--| new_start"] end subgraph subGraph0["Original MemoryArea"] ORIG["start |-------- size --------| end"] end ORIG --> SL1 ORIG --> SL2 ORIG --> SP1 ORIG --> SP2 ORIG --> SR1 ORIG --> SR2
Manipulation Method Details
Method | Parameters | Behavior | Constraints |
---|---|---|---|
shrink_left(new_size, page_table) | new_size, page_table | Increases start address, unmaps left portion | new_size > 0 && < current_size |
shrink_right(new_size, page_table) | new_size, page_table | Decreases end address, unmaps right portion | new_size > 0 && < current_size |
split(pos) | pos (split position) | Creates two areas, returns right portion | start < pos < end |
shrink_left Implementation Details
- Calculates
unmap_size = old_size - new_size
- Unmaps from current start for
unmap_size
bytes - Updates
va_range.start
usingwrapping_add
for overflow safety - Returns
MappingError::BadState
if unmapping fails
split Implementation Details
- Validates that
pos
is within the area boundaries (exclusive) - Creates new MemoryArea for the right portion using
backend.clone()
- Updates original area's end to
pos
- Returns
None
if split position is invalid or would create empty areas
Sources: memory_set/src/area.rs(L101 - L178)
Internal Modification Methods
MemoryArea provides several private methods used by MemorySet for internal state management.
Internal State Management
flowchart TD subgraph subGraph1["Usage Context"] MS["MemorySet"] PROTECT["protect operations"] MERGE["area merging"] UNMAP["partial unmapping"] end subgraph subGraph0["Internal Modification Methods"] SF["set_flags(new_flags)"] SE["set_end(new_end)"] end MERGE --> SE MS --> SE MS --> SF PROTECT --> SF UNMAP --> SE
set_flags(new_flags)
- Updates the flags field directlyset_end(new_end)
- Modifies the end address of the virtual address range
These methods are marked pub(crate)
and are used internally by MemorySet for complex operations like area merging and partial unmapping.
Sources: memory_set/src/area.rs(L63 - L73)
Integration with Type System
MemoryArea implements Debug
when the backend's address and flags types support debugging, providing formatted output for development and debugging purposes.
The struct uses extensive generic constraints through the MappingBackend
trait, ensuring type safety across different address types and flag representations while maintaining backend independence.
Sources: memory_set/src/area.rs(L180 - L191)
MappingBackend
Relevant source files
The MappingBackend
trait provides an abstraction layer for different memory mapping implementations within the memory_set crate. This trait defines the underlying operations that occur when manipulating memory mappings within specific memory areas, allowing the system to support different mapping strategies such as linear mappings, lazy mappings, and custom hardware-specific implementations.
For information about how MappingBackend integrates with memory area management, see MemoryArea. For details on how backends are used within the broader memory set system, see MemorySet Core.
Trait Definition and Architecture
The MappingBackend
trait serves as the foundation for all memory mapping operations in the memory_set system. It defines a generic interface that can be implemented by different backend strategies while maintaining type safety through associated types.
Associated Types Architecture
The trait defines three associated types that provide flexibility while maintaining type safety:
Associated Type | Constraint | Purpose |
---|---|---|
Addr | MemoryAddr | Address type used for all memory operations |
Flags | Copy | Permission and attribute flags for memory regions |
PageTable | None | Page table implementation specific to the backend |
Sources: memory_set/src/backend.rs(L10 - L16)
Core Operations
The MappingBackend
trait defines three fundamental operations that all memory mapping implementations must provide:
Memory Mapping Operations
sequenceDiagram participant Client as Client participant MemoryArea as MemoryArea participant MappingBackend as MappingBackend participant PageTable as PageTable Note over Client,PageTable: "Map Operation Flow" Client ->> MemoryArea: "map_area(page_table)" MemoryArea ->> MappingBackend: "map(start, size, flags, page_table)" MappingBackend ->> PageTable: "Modify page table entries" PageTable -->> MappingBackend: "Success/failure" MappingBackend -->> MemoryArea: "bool result" MemoryArea -->> Client: "Mapping result" Note over Client,PageTable: "Unmap Operation Flow" Client ->> MemoryArea: "unmap_area(page_table)" MemoryArea ->> MappingBackend: "unmap(start, size, page_table)" MappingBackend ->> PageTable: "Remove page table entries" PageTable -->> MappingBackend: "Success/failure" MappingBackend -->> MemoryArea: "bool result" MemoryArea -->> Client: "Unmapping result" Note over Client,PageTable: "Protect Operation Flow" Client ->> MemoryArea: "protect(new_flags, page_table)" MemoryArea ->> MappingBackend: "protect(start, size, new_flags, page_table)" MappingBackend ->> PageTable: "Update entry permissions" PageTable -->> MappingBackend: "Success/failure" MappingBackend -->> MemoryArea: "bool result" MemoryArea -->> Client: "Protection result"
Method Specifications
Map Operation
The map
method establishes new memory mappings within a region:
- Parameters: start address, size, access flags, mutable page table reference
- Returns:
bool
indicating success or failure - Purpose: Create page table entries according to the backend's mapping strategy
Unmap Operation
The unmap
method removes existing memory mappings:
- Parameters: start address, size, mutable page table reference
- Returns:
bool
indicating success or failure - Purpose: Remove page table entries and invalidate translations
Protect Operation
The protect
method modifies access permissions for existing mappings:
- Parameters: start address, size, new flags, mutable page table reference
- Returns:
bool
indicating success or failure - Purpose: Update page table entry permissions without changing mappings
Sources: memory_set/src/backend.rs(L18 - L38)
Backend Implementation Patterns
Different backend implementations support various memory management strategies within the same unified interface:
Backend Strategy Types
flowchart TD subgraph subGraph1["Mapping Strategies"] DirectMap["Direct physical-to-virtualmapping at map() time"] PageFault["Empty mappings thattrigger page faults"] CustomHW["Hardware-specificoptimizations"] Testing["Controlled behaviorfor unit tests"] end subgraph subGraph0["Implementation Types"] LinearBackend["Linear Mapping Backend"] LazyBackend["Lazy Mapping Backend"] HardwareBackend["Hardware-Specific Backend"] MockBackend["Mock/Test Backend"] end MappingBackend["MappingBackend Trait"] HardwareBackend --> CustomHW LazyBackend --> PageFault LinearBackend --> DirectMap MappingBackend --> HardwareBackend MappingBackend --> LazyBackend MappingBackend --> LinearBackend MappingBackend --> MockBackend MockBackend --> Testing
Linear vs Lazy Mapping Strategies
The trait documentation specifically mentions two primary mapping approaches:
Linear Mappings: The target physical address is known when added to the page table. These backends implement direct address translation setup during the map
operation.
Lazy Mappings: Empty mappings are added to the page table to trigger page faults. These backends defer actual memory allocation until first access, implementing demand paging strategies.
Sources: memory_set/src/backend.rs(L6 - L9)
Integration with Memory Management System
The MappingBackend
trait integrates closely with other components in the memory_set system:
System Integration Architecture
Backend Selection and Usage
Different memory areas can use different backends based on their specific requirements:
- Device memory areas might use linear backends for direct hardware access
- Application memory areas might use lazy backends for demand paging
- Kernel memory areas might use hardware-optimized backends
The Clone
requirement on the trait ensures that backends can be easily shared and copied when creating new memory areas or splitting existing ones.
Sources: memory_set/src/backend.rs(L1 - L38)
Type Safety and Constraints
The trait design emphasizes type safety through associated type constraints:
Type Constraint System
The associated type constraints ensure that:
- Address operations are type-safe and support all required memory address manipulations
- Flag operations are efficient through the
Copy
trait requirement - Backend sharing is possible through the
Clone
requirement
Sources: memory_set/src/backend.rs(L10 - L16)
Usage Examples and Testing
Relevant source files
This page demonstrates practical usage patterns for the memory_set
crate components through comprehensive examples and testing scenarios. It covers both basic operations and advanced memory management patterns that showcase the full capabilities of the system.
For information about the core MemorySet data structures, see MemorySet Core. For details about the MappingBackend trait implementation requirements, see MappingBackend.
Mock Testing Infrastructure
The memory_set
crate uses a comprehensive mock backend system for testing that serves as an excellent example of how to implement the MappingBackend
trait for real-world usage.
MockBackend Implementation
The testing infrastructure centers around a MockBackend
that simulates memory mapping operations using a simple array-based page table:
flowchart TD subgraph subGraph2["Test Operations"] TMAP["Test Mappingset.map(area, pt, overlap)"] TUNMAP["Test Unmappingset.unmap(start, size, pt)"] TPROTECT["Test Protectionset.protect(start, size, update_fn, pt)"] TFIND["Test Free Area Findingset.find_free_area(base, size, range)"] end subgraph subGraph1["MappingBackend Implementation"] MAP["map(start, size, flags, pt)→ bool"] UNMAP["unmap(start, size, pt)→ bool"] PROTECT["protect(start, size, new_flags, pt)→ bool"] end subgraph subGraph0["Mock Testing Setup"] MT["MockPageTable[MockFlags; MAX_ADDR]"] MB["MockBackendstruct"] MF["MockFlagstype = u8"] MS["MockMemorySetMemorySet<MockBackend>"] end MB --> MAP MB --> PROTECT MB --> UNMAP MF --> MB MS --> MB MT --> MB TFIND --> MS TMAP --> MS TPROTECT --> MS TUNMAP --> MS
Sources: memory_set/src/tests.rs(L10 - L55) memory_set/README.md(L22 - L89)
Key Testing Constants and Types
Component | Definition | Purpose |
---|---|---|
MAX_ADDR | 0x10000 | Maximum address space for testing |
MockFlags | u8 | Simple flags representation |
MockPageTable | [MockFlags; MAX_ADDR] | Array-based page table simulation |
MockBackend | struct | Implementation ofMappingBackendtrait |
The mock backend implements three core operations that demonstrate the expected behavior patterns for real memory management systems.
Sources: memory_set/src/tests.rs(L5 - L13)
Basic Usage Patterns
Simple Map and Unmap Operations
The most fundamental usage pattern involves creating a MemorySet
, mapping memory areas, and performing basic operations:
Sources: memory_set/src/tests.rs(L110 - L116) memory_set/src/tests.rs(L330 - L364)
Advanced Testing Scenarios
Area Splitting During Unmap Operations
One of the most complex scenarios tested is how the system handles unmapping portions of existing areas, which can result in area splitting:
The test demonstrates that protection changes that don't actually modify flags (like the third protect operation) are optimized to avoid unnecessary area splitting.
Sources: memory_set/src/tests.rs(L232 - L328)
Free Area Discovery
The system includes sophisticated logic for finding available memory regions that can accommodate new allocations:
Test Scenario | Base Address | Size | Expected Result | Reasoning |
---|---|---|---|---|
Exact fit | 0x0 | 0x1000 | 0x1000 | First available gap |
Partial fit | 0x800 | 0x800 | 0x1000 | Rounds up to available space |
Within gap | 0x1800 | 0x800 | 0x1800 | Fits in existing gap |
Requires larger gap | 0x1800 | 0x1000 | 0x3000 | Moves to next suitable gap |
At boundary | 0xf000 | 0x1000 | 0xf000 | Fits at end of address space |
Impossible | 0xf001 | 0x1000 | None | No space available |
This testing validates the free area detection logic that would be crucial for implementing memory allocators on top of the system.
Sources: memory_set/src/tests.rs(L330 - L364)
Test Infrastructure Utilities
Assertion Macros
The test suite defines custom assertion macros that simplify error checking:
assert_ok!(expr)
- Verifies operation succeedsassert_err!(expr)
- Verifies operation failsassert_err!(expr, ErrorType)
- Verifies specific error type
Memory Set Inspection
The dump_memory_set
function provides detailed debugging output showing the current state of all memory areas, which is invaluable during development and testing.
Sources: memory_set/src/tests.rs(L57 - L81)
Integration Testing Patterns
Comprehensive State Validation
The tests systematically verify both the high-level MemorySet state and the low-level MockPageTable state after each operation:
flowchart TD subgraph subGraph1["Test Operations"] OP["Memory Operation(map/unmap/protect)"] end subgraph subGraph0["Validation Layers"] HL["High-Level Validation• Area count (set.len())• Area boundaries• Area flags• Iterator consistency"] LL["Low-Level Validation• Page table entries• Flag consistency• Unmapped region verification"] CROSS["Cross-Layer Validation• MemorySet areas match page table• No gaps in coverage• Flag coherence"] end HL --> CROSS LL --> CROSS OP --> HL OP --> LL
This multi-layered validation approach ensures that the abstract memory area management remains consistent with the underlying page table state.
Sources: memory_set/src/tests.rs(L106 - L108) memory_set/src/tests.rs(L140 - L142) memory_set/src/tests.rs(L186 - L221)
Development Guide
Relevant source files
This document provides comprehensive information for developers contributing to the axmm_crates repository. It covers development environment setup, CI/CD pipeline configuration, code quality standards, testing procedures, and contribution workflow. For information about the system architecture and crate functionality, see Overview and the individual crate documentation pages memory_addr Crate and memory_set Crate.
Development Environment Setup
The axmm_crates project requires specific toolchain configuration to support multiple target architectures and maintain compatibility with no-std
environments.
Required Rust Toolchain
The project exclusively uses Rust nightly toolchain with the following required components:
Component | Purpose |
---|---|
rust-src | Source code for cross-compilation to bare-metal targets |
clippy | Linting and static analysis |
rustfmt | Code formatting enforcement |
Supported Target Architectures
flowchart TD nightly["Rust Nightly Toolchain"] x86_64_linux["x86_64-unknown-linux-gnu"] x86_64_none["x86_64-unknown-none"] riscv64["riscv64gc-unknown-none-elf"] aarch64["aarch64-unknown-none-softfloat"] hosted["Hosted EnvironmentUnit Testing"] bare_metal_x86["Bare Metal x86_64Kernel Development"] bare_metal_riscv["Bare Metal RISC-VEmbedded Systems"] bare_metal_arm["Bare Metal ARM64Embedded/Mobile"] aarch64 --> bare_metal_arm nightly --> aarch64 nightly --> riscv64 nightly --> x86_64_linux nightly --> x86_64_none riscv64 --> bare_metal_riscv x86_64_linux --> hosted x86_64_none --> bare_metal_x86
Sources: .github/workflows/ci.yml(L11 - L12) .github/workflows/ci.yml(L16 - L19)
CI/CD Pipeline Architecture
The continuous integration system is implemented using GitHub Actions and follows a comprehensive validation strategy.
CI Workflow Structure
flowchart TD trigger["Push/Pull Request Trigger"] ci_job["CI Job Matrix"] doc_job["Documentation Job"] setup["Environment Setup- Checkout code- Install Rust nightly- Add components & targets"] version_check["rustc --version --verbose"] format_check["cargo fmt --all -- --check"] clippy_check["cargo clippy --target TARGET --all-features"] build_step["cargo build --target TARGET --all-features"] unit_tests["cargo test (x86_64-linux only)"] doc_setup["Documentation Setup- Checkout code- Install Rust nightly"] doc_build["cargo doc --no-deps --all-features"] pages_deploy["Deploy to GitHub Pages(main branch only)"] build_step --> unit_tests ci_job --> setup clippy_check --> build_step doc_build --> pages_deploy doc_job --> doc_setup doc_setup --> doc_build format_check --> clippy_check setup --> version_check trigger --> ci_job trigger --> doc_job version_check --> format_check
Sources: .github/workflows/ci.yml(L5 - L31) .github/workflows/ci.yml(L32 - L55)
Quality Gates and Validation
Stage | Command | Target Scope | Failure Behavior |
---|---|---|---|
Format Check | cargo fmt --all -- --check | All code | Fail fast |
Linting | cargo clippy --target TARGET --all-features | Per target | Allow new_without_default warnings |
Build Validation | cargo build --target TARGET --all-features | Per target | Fail on error |
Unit Testing | cargo test --target x86_64-unknown-linux-gnu | Hosted only | Output captured |
Sources: .github/workflows/ci.yml(L22 - L30)
Code Quality Standards
Formatting Configuration
The project enforces consistent code formatting through rustfmt
with specific configuration:
wrap_comments = true
This setting ensures that long comments are automatically wrapped to maintain readability.
Sources: rustfmt.toml(L1 - L2)
Linting Rules
The CI pipeline runs clippy
with specific allowances configured:
- Suppressed Warning:
clippy::new_without_default
- Allowsnew()
methods without implementingDefault
trait - Target-Specific Analysis: Linting runs against all supported target architectures to catch platform-specific issues
Sources: .github/workflows/ci.yml(L25)
Testing Strategy
Test Execution Matrix
flowchart TD subgraph subGraph1["Test Types"] unit_tests["Unit Tests--nocapture flag"] build_tests["Build ValidationCross-compilation"] format_tests["Format ValidationCode Style"] lint_tests["Static AnalysisClippy Checks"] end subgraph subGraph0["Test Targets"] x86_64_linux["x86_64-unknown-linux-gnuFull Test Suite"] other_targets["Other TargetsBuild-Only Validation"] end build_tests --> format_tests format_tests --> lint_tests other_targets --> build_tests unit_tests --> format_tests x86_64_linux --> build_tests x86_64_linux --> unit_tests
Sources: .github/workflows/ci.yml(L28 - L30)
Test Environment Configuration
- Output Capture: Tests run with
--nocapture
flag to display all output during CI execution - Target Restriction: Unit tests only execute on
x86_64-unknown-linux-gnu
due to hosted environment requirements - Cross-Compilation Validation: All targets must successfully build to ensure broad compatibility
Documentation Generation
Automated Documentation Pipeline
The documentation system generates comprehensive API documentation with enhanced features:
Documentation Build Configuration
RUSTDOCFLAGS="-Zunstable-options --enable-index-page -D rustdoc::broken_intra_doc_links -D missing-docs"
cargo doc --no-deps --all-features
Flag | Purpose |
---|---|
--enable-index-page | Creates unified documentation index |
-D rustdoc::broken_intra_doc_links | Treats broken internal links as errors |
-D missing-docs | Requires documentation for all public items |
--no-deps | Excludes dependency documentation |
--all-features | Documents all feature combinations |
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L47)
Documentation Deployment
sequenceDiagram participant Developer as "Developer" participant GitHubActions as "GitHub Actions" participant GitHubPages as "GitHub Pages" Developer ->> GitHubActions: "Push to main branch" GitHubActions ->> GitHubActions: "cargo doc --no-deps --all-features" GitHubActions ->> GitHubActions: "Validate documentation build" alt Documentation build succeeds GitHubActions ->> GitHubPages: "Deploy target/doc to gh-pages branch" GitHubPages ->> GitHubPages: "Update live documentation site" else Documentation build fails GitHubActions ->> Developer: "CI failure notification" end
Sources: .github/workflows/ci.yml(L48 - L54)
Development Workflow
Repository Structure
The project follows standard Rust workspace conventions with specific exclusions:
axmm_crates/
├── target/ # Build artifacts (ignored)
├── .vscode/ # Editor configuration (ignored)
├── Cargo.lock # Dependency lock file (ignored)
├── memory_addr/ # Foundation address crate
├── memory_set/ # Memory mapping management crate
└── .github/workflows/ # CI/CD configuration
Sources: .gitignore(L1 - L5)
Contribution Process
flowchart TD fork["Fork Repository"] clone["Clone Fork Locally"] branch["Create Feature Branch"] develop["Local Development"] format["Run cargo fmt"] test["Run cargo test"] clippy["Run cargo clippy"] commit["Commit Changes"] push["Push to Fork"] pr["Create Pull Request"] ci_check["CI Pipeline Execution"] review["Code Review Process"] merge["Merge to Main"] branch --> develop ci_check --> review clippy --> commit clone --> branch commit --> push develop --> format fork --> clone format --> test pr --> ci_check push --> pr review --> merge test --> clippy
Local Development Commands
Task | Command | Purpose |
---|---|---|
Format Code | cargo fmt --all | Apply consistent formatting |
Run Tests | cargo test -- --nocapture | Execute test suite with output |
Lint Code | cargo clippy --all-features | Static analysis and suggestions |
Build All Targets | cargo build --target | Validate cross-compilation |
Generate Docs | cargo doc --no-deps --all-features | Build documentation locally |
Sources: .github/workflows/ci.yml(L22 - L30) .github/workflows/ci.yml(L47)
Overview
Relevant source files
Purpose and Scope
The arm_pl031
crate provides a safe Rust driver for the ARM PL031 Real-Time Clock (RTC) hardware on aarch64 systems. This driver enables embedded and systems software to read and set time values, configure interrupts, and manage RTC functionality through a memory-mapped I/O interface.
The crate is designed for no_std
environments and supports both basic Unix timestamp operations and optional high-level DateTime handling through the chrono
feature. It targets embedded systems, hypervisors, and operating system kernels that need direct hardware access to PL031 RTC devices.
For detailed implementation specifics, see Core Driver Implementation. For optional features and extensions, see Features and Extensions. For practical usage guidance, see Getting Started.
Sources: Cargo.toml(L1 - L20) src/lib.rs(L1 - L8) README.md(L1 - L6)
System Architecture
The arm_pl031
driver implements a layered architecture that provides multiple abstraction levels over the PL031 hardware, from raw register access to high-level DateTime operations.
Core Driver Architecture
flowchart TD subgraph subGraph3["Safe API Layer"] GetTime["get_unix_timestamp()"] SetTime["set_unix_timestamp()"] SetMatch["set_match_timestamp()"] IntOps["Interrupt Operations"] end subgraph subGraph2["Register Interface"] DR["dr: Data Register"] MR["mr: Match Register"] LR["lr: Load Register"] CR["cr: Control Register"] IMSC["imsc: Interrupt Mask"] RIS["ris: Raw Interrupt Status"] MIS["mis: Masked Interrupt Status"] ICR["icr: Interrupt Clear"] end subgraph subGraph1["Driver Core (src/lib.rs)"] Rtc["Rtc struct"] Registers["Registers struct"] SafetyBoundary["unsafe fn new()"] end subgraph subGraph0["Hardware Layer"] PL031["PL031 RTC Hardware"] MMIO["Memory-Mapped Registers"] DT["Device Tree Configuration"] end DT --> PL031 MMIO --> SafetyBoundary PL031 --> MMIO Registers --> CR Registers --> DR Registers --> ICR Registers --> IMSC Registers --> LR Registers --> MIS Registers --> MR Registers --> RIS Rtc --> GetTime Rtc --> IntOps Rtc --> Registers Rtc --> SetMatch Rtc --> SetTime SafetyBoundary --> Rtc
The driver centers around the Rtc
struct which encapsulates a raw pointer to the hardware registers. The Registers
struct defines the memory layout matching the PL031 hardware specification, ensuring correct MMIO access patterns.
Sources: src/lib.rs(L15 - L44) src/lib.rs(L46 - L61)
Feature-Based System Components
The system uses Cargo features to provide modular functionality. The core driver in lib.rs
provides essential RTC operations, while the optional chrono
module adds convenient DateTime handling for applications that need it.
Sources: Cargo.toml(L14 - L19) src/lib.rs(L10 - L11) src/lib.rs(L123 - L128)
Hardware Interface and Safety Model
Memory-Mapped I/O Operations
flowchart TD subgraph subGraph4["Hardware Registers"] HardwareRegs["PL031 MMIO Registers"] PhysicalRTC["Physical RTC Hardware"] end subgraph subGraph3["Volatile MMIO"] ReadVol["addr_of!(*registers.dr).read_volatile()"] WriteVol["addr_of_mut!(*registers.lr).write_volatile()"] RegAccess["Register field access"] end subgraph subGraph2["Safe Operations"] GetUnix["get_unix_timestamp() -> u32"] SetUnix["set_unix_timestamp(u32)"] SetMatch["set_match_timestamp(u32)"] EnableInt["enable_interrupt(bool)"] CheckInt["interrupt_pending() -> bool"] ClearInt["clear_interrupt()"] end subgraph subGraph1["Safety Boundary"] UnsafeNew["unsafe fn new(base_address)"] SafetyDoc["Safety Documentation"] end subgraph subGraph0["Application Code"] UserApp["User Application"] BaseAddr["base_address: *mut u32"] end BaseAddr --> UnsafeNew CheckInt --> ReadVol ClearInt --> WriteVol EnableInt --> WriteVol GetUnix --> ReadVol HardwareRegs --> PhysicalRTC ReadVol --> RegAccess RegAccess --> HardwareRegs SetMatch --> WriteVol SetUnix --> WriteVol UnsafeNew --> CheckInt UnsafeNew --> ClearInt UnsafeNew --> EnableInt UnsafeNew --> GetUnix UnsafeNew --> SafetyDoc UnsafeNew --> SetMatch UnsafeNew --> SetUnix UserApp --> BaseAddr WriteVol --> RegAccess
The driver implements a clear safety boundary where unsafe operations are isolated to the constructor new()
function, while all subsequent operations use safe Rust APIs. All hardware access uses volatile operations to ensure proper MMIO semantics.
Sources: src/lib.rs(L46 - L60) src/lib.rs(L62 - L74) src/lib.rs(L76 - L120)
Key Code Entities and Relationships
The following table maps the primary system components to their code representations:
System Component | Code Entity | File Location | Purpose |
---|---|---|---|
RTC Driver | Rtcstruct | src/lib.rs42-44 | Main driver interface |
Hardware Layout | Registersstruct | src/lib.rs15-39 | Memory-mapped register layout |
Initialization | unsafe fn new() | src/lib.rs47-60 | Driver constructor with safety contract |
Time Reading | get_unix_timestamp() | src/lib.rs62-67 | Read current time from hardware |
Time Setting | set_unix_timestamp() | src/lib.rs69-74 | Set hardware time |
Interrupt Control | enable_interrupt() | src/lib.rs108-113 | Manage interrupt masking |
Interrupt Status | interrupt_pending() | src/lib.rs97-102 | Check interrupt state |
Optional DateTime | chrono module | src/lib.rs10-11 | High-level time operations |
Register Field Mapping
The Registers
struct directly corresponds to the PL031 hardware specification:
Register Field | Hardware Purpose | Access Pattern |
---|---|---|
dr | Data Register - current time | Read-only viaread_volatile() |
mr | Match Register - interrupt trigger | Write viawrite_volatile() |
lr | Load Register - set time | Write viawrite_volatile() |
cr | Control Register - device control | Reserved for future use |
imsc | Interrupt Mask - enable/disable | Write viawrite_volatile() |
ris | Raw Interrupt Status - match state | Read viaread_volatile() |
mis | Masked Interrupt Status - pending | Read viaread_volatile() |
icr | Interrupt Clear - acknowledge | Write viawrite_volatile() |
Sources: src/lib.rs(L17 - L39) src/lib.rs(L63 - L66) src/lib.rs(L70 - L73) src/lib.rs(L78 - L81)
Project Purpose and Scope
Relevant source files
This document defines the purpose and scope of the arm_pl031
crate, a Real Time Clock (RTC) driver for ARM PL031 hardware on aarch64 platforms. For information about the internal driver implementation, see Core Driver Implementation. For build and deployment details, see Building and Testing.
Overview
The arm_pl031
crate provides a safe Rust interface to ARM PL031 Real Time Clock hardware, specifically designed for aarch64-based embedded systems and operating system kernels. The driver abstracts the low-level memory-mapped I/O operations required to interact with PL031 RTC hardware while maintaining memory safety and providing both basic timestamp operations and high-level DateTime functionality.
Sources: Cargo.toml(L1 - L20) README.md(L1 - L38)
Target Platforms and Compatibility
Supported Architectures
The driver primarily targets aarch64 platforms but maintains compatibility with multiple architectures through its no_std
design:
Architecture | Support Level | Use Case |
---|---|---|
aarch64-unknown-none-* | Primary | Embedded/bare-metal systems |
x86_64-unknown-linux-gnu | Testing | Development and CI |
riscv64gc-unknown-none-elf | Secondary | Cross-platform embedded |
Environment Compatibility
The crate is designed for no_std
environments, making it suitable for:
- Embedded operating systems (such as ArceOS)
- Bare-metal applications
- Kernel-level drivers
- Real-time systems requiring precise timekeeping
Sources: Cargo.toml(L12) README.md(L5)
Core Capabilities and Feature Set
Hardware Interface Diagram
flowchart TD subgraph subGraph2["Hardware Layer"] pl031_mmio["PL031 MMIO Registers"] device_tree["Device Tree Configuration"] base_addr["Base Address (0x9010000)"] end subgraph subGraph1["Optional Chrono Feature"] get_time["get_time() -> DateTime"] set_time["set_time(DateTime)"] chrono_dep["chrono crate v0.4.38"] end subgraph subGraph0["arm_pl031 Crate Interface"] Rtc["Rtc struct"] new["unsafe new(base_addr)"] get_unix_timestamp["get_unix_timestamp()"] set_unix_timestamp["set_unix_timestamp()"] enable_interrupt["enable_interrupt()"] clear_interrupt["clear_interrupt()"] end Rtc --> clear_interrupt Rtc --> enable_interrupt Rtc --> get_unix_timestamp Rtc --> pl031_mmio Rtc --> set_unix_timestamp base_addr --> new device_tree --> base_addr get_time --> chrono_dep get_time --> get_unix_timestamp new --> Rtc set_time --> chrono_dep set_time --> set_unix_timestamp
Feature Configuration
The crate provides a modular feature system:
flowchart TD subgraph subGraph1["Optional Features"] chrono_feat["chrono feature (default)"] datetime_api["DateTime API"] chrono_crate["chrono dependency"] end subgraph subGraph0["Core Features (Always Available)"] core_rtc["Core RTC Operations"] unix_timestamp["Unix Timestamp API"] interrupt_mgmt["Interrupt Management"] mmio_safety["Memory-Safe MMIO"] end chrono_feat --> datetime_api core_rtc --> interrupt_mgmt core_rtc --> mmio_safety core_rtc --> unix_timestamp datetime_api --> chrono_crate datetime_api --> unix_timestamp
Sources: Cargo.toml(L14 - L19) README.md(L9 - L14)
Usage Scenarios
Primary Use Cases
- Embedded Operating Systems: Integration into kernel-level time management systems
- Bare-Metal Applications: Direct hardware timekeeping without OS overhead
- Real-Time Systems: Precise timestamp generation and interrupt-driven time events
- Device Drivers: Foundation for higher-level system time services
Integration Pattern
The typical integration follows this initialization pattern:
sequenceDiagram participant ApplicationKernel as "Application/Kernel" participant DeviceTree as "Device Tree" participant arm_pl031Rtc as "arm_pl031::Rtc" participant PL031Hardware as "PL031 Hardware" ApplicationKernel ->> DeviceTree: "Parse device tree" DeviceTree -->> ApplicationKernel: "Base address (0x9010000)" ApplicationKernel ->> arm_pl031Rtc: "unsafe Rtc::new(base_addr)" arm_pl031Rtc ->> PL031Hardware: "Initialize MMIO access" ApplicationKernel ->> arm_pl031Rtc: "get_unix_timestamp()" arm_pl031Rtc ->> PL031Hardware: "Read DR register" PL031Hardware -->> arm_pl031Rtc: "Raw timestamp" arm_pl031Rtc -->> ApplicationKernel: "u32 timestamp"
Sources: README.md(L16 - L37)
Scope Boundaries
What the Crate Provides
- Hardware Abstraction: Safe wrapper around PL031 MMIO operations
- Memory Safety: Encapsulation of unsafe hardware access
- Time Operations: Unix timestamp reading and writing
- Interrupt Support: Match register configuration and interrupt handling
- Optional DateTime: High-level time manipulation via chrono integration
What the Crate Does NOT Provide
- Device Discovery: Users must obtain base addresses from device tree or firmware
- Clock Configuration: Hardware clock setup is assumed to be done by firmware/bootloader
- Time Zone Support: Only UTC timestamps are supported natively
- Persistence: No handling of RTC battery backup or power management
- Multiple Instance Management: Single RTC instance per hardware unit
Licensing and Compliance
The crate uses a triple-license approach supporting multiple compliance requirements:
- GPL-3.0-or-later: For GPL-compatible projects
- Apache-2.0: For Apache ecosystem integration
- MulanPSL-2.0: For Chinese regulatory compliance
Sources: Cargo.toml(L7) Cargo.toml(L6) Cargo.toml(L11 - L12)
System Architecture
Relevant source files
Purpose and Scope
This document explains the high-level architecture of the arm_pl031 crate, focusing on how the driver abstracts ARM PL031 RTC hardware through layered components, safety boundaries, and feature integration. It covers the structural relationships between the core Rtc
driver, hardware register interface, and optional feature extensions.
For implementation details of specific components, see Core Driver Implementation. For practical usage guidance, see Getting Started.
Overall Architecture Overview
The arm_pl031 crate implements a layered architecture that provides safe, high-level access to ARM PL031 RTC hardware while maintaining clear separation between abstraction levels.
flowchart TD subgraph subGraph3["Safety Boundary"] UNSAFE_OPS["unsafe operations"] end subgraph subGraph2["Hardware Layer"] MMIO["Memory-Mapped I/O"] PL031_HW["ARM PL031 Hardware"] end subgraph subGraph1["arm_pl031 Crate"] RTC["Rtc struct"] REGISTERS["Registers struct"] CHRONO_MOD["chrono module"] end subgraph subGraph0["Application Layer"] APP["Application Code"] CHRONO_API["chrono::DateTime API"] UNIX_API["Unix Timestamp API"] end APP --> CHRONO_API APP --> UNIX_API CHRONO_API --> CHRONO_MOD CHRONO_MOD --> RTC MMIO --> PL031_HW REGISTERS --> MMIO REGISTERS --> UNSAFE_OPS RTC --> REGISTERS UNIX_API --> RTC UNSAFE_OPS --> MMIO
Architecture Diagram: Overall System Layers
This architecture provides multiple abstraction levels, allowing developers to choose between high-level DateTime
operations or low-level timestamp manipulation while maintaining memory safety through controlled unsafe boundaries.
Sources: src/lib.rs(L1 - L159)
Hardware Abstraction Layers
The driver implements a three-tier abstraction model that isolates hardware specifics while providing flexible access patterns.
flowchart TD subgraph subGraph2["Hardware Interface"] READ_VOLATILE["read_volatile()"] WRITE_VOLATILE["write_volatile()"] ADDR_OF["addr_of!()"] ADDR_OF_MUT["addr_of_mut!()"] end subgraph subGraph1["Register Abstraction"] DR["dr: u32"] LR["lr: u32"] MR["mr: u32"] IMSC["imsc: u8"] MIS["mis: u8"] ICR["icr: u8"] end subgraph subGraph0["Safe Rust Interface"] GET_TIME["get_unix_timestamp()"] SET_TIME["set_unix_timestamp()"] INTERRUPTS["enable_interrupt()"] MATCH_OPS["set_match_timestamp()"] end DR --> READ_VOLATILE GET_TIME --> DR IMSC --> WRITE_VOLATILE INTERRUPTS --> IMSC LR --> WRITE_VOLATILE MATCH_OPS --> MR MR --> WRITE_VOLATILE READ_VOLATILE --> ADDR_OF SET_TIME --> LR WRITE_VOLATILE --> ADDR_OF_MUT
Architecture Diagram: Hardware Abstraction Tiers
Each tier serves a specific purpose:
- Safe Interface: Provides memory-safe methods with clear semantics
- Register Abstraction: Maps PL031 registers to Rust types with proper layout
- Hardware Interface: Handles volatile memory operations with explicit safety documentation
Sources: src/lib.rs(L15 - L39) src/lib.rs(L62 - L121)
Component Architecture and Relationships
The core architecture centers around two primary structures that encapsulate hardware interaction and provide the driver interface.
Component | Purpose | Safety Level | Key Responsibilities |
---|---|---|---|
Rtc | Driver interface | Safe wrapper | Time operations, interrupt management |
Registers | Hardware layout | Memory representation | Register field mapping, memory alignment |
chronomodule | Feature extension | Safe wrapper | DateTime conversion, high-level API |
flowchart TD subgraph subGraph2["Memory Operations"] PTR_DEREF["(*self.registers)"] VOLATILE_READ["addr_of!(field).read_volatile()"] VOLATILE_WRITE["addr_of_mut!(field).write_volatile(value)"] end subgraph subGraph1["Register Layout Structure"] REG_STRUCT["#[repr(C, align(4))] Registers"] DR_FIELD["dr: u32"] MR_FIELD["mr: u32"] LR_FIELD["lr: u32"] CONTROL_FIELDS["cr, imsc, ris, mis, icr: u8"] RESERVED["_reserved0..4: [u8; 3]"] end subgraph subGraph0["Rtc Driver Structure"] RTC_STRUCT["Rtc { registers: *mut Registers }"] NEW_METHOD["unsafe fn new(base_address: *mut u32)"] GET_TIMESTAMP["fn get_unix_timestamp() -> u32"] SET_TIMESTAMP["fn set_unix_timestamp(unix_time: u32)"] INTERRUPT_METHODS["interrupt management methods"] end GET_TIMESTAMP --> PTR_DEREF INTERRUPT_METHODS --> PTR_DEREF NEW_METHOD --> RTC_STRUCT PTR_DEREF --> VOLATILE_READ PTR_DEREF --> VOLATILE_WRITE RTC_STRUCT --> REG_STRUCT SET_TIMESTAMP --> PTR_DEREF VOLATILE_READ --> DR_FIELD VOLATILE_WRITE --> LR_FIELD VOLATILE_WRITE --> MR_FIELD
Architecture Diagram: Core Component Relationships
The Rtc
struct maintains a single pointer to a Registers
structure, which provides C-compatible memory layout matching the PL031 hardware specification. All hardware access flows through volatile pointer operations to ensure proper memory semantics.
Sources: src/lib.rs(L15 - L44) src/lib.rs(L46 - L61)
Safety Model and Boundaries
The driver implements a clear safety model that contains all unsafe operations at the hardware interface boundary while providing safe abstractions for application code.
flowchart TD subgraph subGraph2["Hardware Zone"] MMIO_ACCESS["Direct MMIO register access"] DEVICE_MEMORY["PL031 device memory"] end subgraph subGraph1["Unsafe Boundary"] UNSAFE_NEW["unsafe fn new()"] SAFETY_INVARIANTS["Safety invariants and documentation"] VOLATILE_OPS["Volatile memory operations"] end subgraph subGraph0["Safe Zone"] SAFE_METHODS["Public safe methods"] TIMESTAMP_OPS["get_unix_timestamp, set_unix_timestamp"] INTERRUPT_OPS["enable_interrupt, clear_interrupt"] MATCH_OPS["set_match_timestamp, matched"] end subgraph subGraph3["Concurrency Safety"] SEND_IMPL["unsafe impl Send for Rtc"] THREAD_SAFETY["Multi-thread access guarantees"] SYNC_IMPL["unsafe impl Sync for Rtc"] end UNSAFE_BOUNDARY["UNSAFE_BOUNDARY"] INTERRUPT_OPS --> VOLATILE_OPS MATCH_OPS --> VOLATILE_OPS MMIO_ACCESS --> DEVICE_MEMORY SAFE_METHODS --> UNSAFE_BOUNDARY SEND_IMPL --> THREAD_SAFETY SYNC_IMPL --> THREAD_SAFETY TIMESTAMP_OPS --> VOLATILE_OPS UNSAFE_NEW --> SAFETY_INVARIANTS VOLATILE_OPS --> MMIO_ACCESS
Architecture Diagram: Safety Boundaries and Concurrency Model
Safety Requirements
The driver enforces specific safety requirements at the boundary:
- Memory Safety: The
base_address
parameter inRtc::new()
must point to valid PL031 MMIO registers - Alignment: Hardware addresses must be 4-byte aligned as enforced by
#[repr(C, align(4))]
- Exclusive Access: No other aliases to the device memory region are permitted
- Device Mapping: Memory must be mapped as device memory, not normal cacheable memory
Concurrency Safety
The driver provides thread-safety through carefully designed Send
and Sync
implementations:
Send
: Safe because device memory access works from any thread contextSync
: Safe because shared read access to device registers is inherently safe, and mutation requires&mut self
Sources: src/lib.rs(L51 - L60) src/lib.rs(L123 - L128)
Feature Integration Architecture
The crate supports optional feature integration through Cargo feature flags, with the chrono integration serving as the primary example of the extensibility model.
Architecture Diagram: Feature Integration and Build Configuration
Feature Design Principles
- Minimal Core: The base driver remains lightweight with only essential functionality
- Optional Extensions: Advanced features like DateTime support are opt-in through feature flags
- No-std Compatibility: All features maintain compatibility with embedded environments
- Layered Integration: Feature modules build upon the core API without modifying its interface
The chrono integration demonstrates this pattern by providing higher-level DateTime<Utc>
operations while delegating to the core get_unix_timestamp()
and set_unix_timestamp()
methods.
Sources: src/lib.rs(L10 - L11) src/lib.rs(L3)
Getting Started
Relevant source files
This page provides practical guidance for integrating and using the arm_pl031
driver in your projects. It covers the essential steps to add the crate as a dependency, initialize the driver, and perform basic time operations. For detailed installation instructions and dependency management, see Installation and Dependencies. For comprehensive usage examples and device tree configuration details, see Basic Usage and Examples.
Integration Workflow
The following diagram shows the typical workflow for integrating the arm_pl031
driver into an embedded project:
flowchart TD START["Project Setup"] CARGO["Add arm_pl031 to Cargo.toml"] DT["Configure Device Tree"] ADDR["Obtain Base Address"] INIT["unsafe Rtc::new()"] USAGE["Use RTC APIs"] CHRONO["chrono support enabled"] CORE["core APIs only"] READ["get_unix_timestamp()"] WRITE["set_unix_timestamp()"] INT["interrupt operations"] DATETIME["DateTime APIs"] TIMESTAMP["u32 timestamp APIs"] ADDR --> INIT CARGO --> CHRONO CARGO --> CORE CARGO --> DT CHRONO --> DATETIME CORE --> TIMESTAMP DT --> ADDR INIT --> USAGE START --> CARGO USAGE --> INT USAGE --> READ USAGE --> WRITE
Sources: Cargo.toml(L1 - L20) src/lib.rs(L46 - L60) README.md(L9 - L14)
Core Driver Components
This diagram maps the key components you'll interact with when using the driver:
flowchart TD subgraph subGraph3["Optional Features"] CHRONO_MOD["chrono module"] DATETIME_API["DateTime conversion"] end subgraph subGraph2["Hardware Integration"] DT_CONFIG["Device Tree pl031@address"] MMIO_ADDR["MMIO Base Address"] PL031_HW["PL031 Hardware"] end subgraph subGraph1["arm_pl031 Crate"] RTC_STRUCT["Rtc struct"] NEW_FN["unsafe fn new(base_address)"] GET_TS["get_unix_timestamp()"] SET_TS["set_unix_timestamp()"] MATCH_FN["set_match_timestamp()"] INT_FNS["interrupt methods"] end subgraph subGraph0["User Code"] APP["Application"] CONFIG["Cargo.toml dependencies"] end APP --> GET_TS APP --> INT_FNS APP --> MATCH_FN APP --> NEW_FN APP --> SET_TS CHRONO_MOD --> DATETIME_API CONFIG --> CHRONO_MOD CONFIG --> RTC_STRUCT DATETIME_API --> GET_TS DT_CONFIG --> MMIO_ADDR MMIO_ADDR --> PL031_HW NEW_FN --> MMIO_ADDR
Sources: src/lib.rs(L42 - L44) src/lib.rs(L56 - L60) Cargo.toml(L14 - L19) README.md(L27 - L33)
Quick Start Example
Here's the minimal code needed to read the current time from a PL031 RTC:
Add the dependency to your Cargo.toml
:
[dependencies]
arm_pl031 = "0.2.1"
Basic usage in your code:
use arm_pl031::Rtc;
// Initialize with hardware base address from device tree
let rtc = unsafe { Rtc::new(0x901_0000 as *mut u32) };
// Read current time as Unix timestamp
let current_time = rtc.get_unix_timestamp();
Sources: Cargo.toml(L2 - L3) README.md(L9 - L14) src/lib.rs(L56 - L67)
Essential Requirements
Requirement | Details | Code Reference |
---|---|---|
Base Address | Must point to PL031 MMIO registers | src/lib.rs53-55 |
Address Alignment | Must be 4-byte aligned | src/lib.rs55 |
Memory Mapping | Must be mapped as device memory | src/lib.rs53-54 |
Safety | Constructor requiresunsafedue to raw pointer | src/lib.rs51-56 |
Device Tree | Hardware address typically from device tree | README.md19-37 |
Device Tree Configuration
The PL031 device must be properly configured in your device tree. Here's a typical configuration:
pl031@9010000 {
clock-names = "apb_pclk";
clocks = <0x8000>;
interrupts = <0x00 0x02 0x04>;
reg = <0x00 0x9010000 0x00 0x1000>;
compatible = "arm,pl031", "arm,primecell";
};
The reg
property provides the base address (0x9010000
) and size (0x1000
) that you'll use when calling Rtc::new()
.
Sources: README.md(L27 - L33)
Feature Configuration
The crate supports optional features that can be controlled in your Cargo.toml
:
Feature | Default | Purpose | Dependencies |
---|---|---|---|
chrono | Enabled | DateTime conversion support | chrono = "0.4.38" |
(none) | Core only | Unix timestamp operations only | None |
To use only core functionality without chrono
:
[dependencies]
arm_pl031 = { version = "0.2.1", default-features = false }
To explicitly enable chrono support:
[dependencies]
arm_pl031 = { version = "0.2.1", features = ["chrono"] }
Sources: Cargo.toml(L14 - L19) src/lib.rs(L10 - L11)
Next Steps
- For complete installation instructions and dependency management, see Installation and Dependencies
- For comprehensive usage examples and advanced configuration, see Basic Usage and Examples
- For understanding the driver architecture, see Core Driver Implementation
- For chrono DateTime integration details, see Chrono Integration
Installation and Dependencies
Relevant source files
This section covers how to integrate the arm_pl031
crate into your Rust project, configure optional features, and manage dependencies. It focuses on the practical aspects of adding the crate to your Cargo.toml
and understanding the available configuration options.
For information about using the driver after installation, see Basic Usage and Examples. For detailed feature configuration and no_std
compatibility, see Feature Configuration.
Adding the Crate to Your Project
The arm_pl031
crate is designed for embedded systems requiring ARM PL031 RTC functionality. Add it to your project's Cargo.toml
dependencies section:
[dependencies]
arm_pl031 = "0.2.1"
The crate is published on crates.io with the identifier arm_pl031
and follows semantic versioning. The current stable version provides the core RTC driver functionality with optional chrono integration.
Dependency Graph
flowchart TD PROJECT["Your ProjectCargo.toml"] ARM_PL031["arm_pl031 = '0.2.1'"] CHRONO_DEP["chrono = '0.4.38'(optional)"] CORE_FEATURES["Core RTC Functionality- Register access- Interrupt handling- Unix timestamps"] DATETIME_FEATURES["DateTime Support- DateTime- Time conversion- Chrono types"] ARM_PL031 --> CHRONO_DEP ARM_PL031 --> CORE_FEATURES CHRONO_DEP --> DATETIME_FEATURES PROJECT --> ARM_PL031
Sources: Cargo.toml(L1 - L20)
Feature Configuration
The crate provides feature-based configuration to balance functionality with dependency minimization. The available features are controlled through Cargo feature flags.
Feature | Default | Purpose | Dependencies |
---|---|---|---|
chrono | ✓ | EnablesDateTime | chrono = "0.4.38" |
Default Configuration
By default, the chrono
feature is enabled, providing high-level time handling capabilities:
[dependencies]
arm_pl031 = "0.2.1" # chrono feature included by default
Minimal Configuration
To minimize dependencies and use only core RTC functionality, disable default features:
[dependencies]
arm_pl031 = { version = "0.2.1", default-features = false }
Custom Feature Selection
Enable specific features explicitly:
[dependencies]
arm_pl031 = { version = "0.2.1", default-features = false, features = ["chrono"] }
Feature Configuration Flow
flowchart TD START["Add arm_pl031 dependency"] DEFAULT_CHECK["Use default features?"] CHRONO_NEEDED["Need DateTime support?"] DEFAULT_YES["arm_pl031 = '0.2.1'(includes chrono)"] DEFAULT_NO["arm_pl031 = { version = '0.2.1',default-features = false }"] ADD_CHRONO["features = ['chrono']"] CORE_ONLY["Core RTC only- Unix timestamps- Register access- Interrupts"] FULL_FEATURES["Full functionality- DateTime- Time conversions- Core RTC"] ADD_CHRONO --> FULL_FEATURES CHRONO_NEEDED --> ADD_CHRONO CHRONO_NEEDED --> CORE_ONLY DEFAULT_CHECK --> DEFAULT_NO DEFAULT_CHECK --> DEFAULT_YES DEFAULT_NO --> CHRONO_NEEDED DEFAULT_YES --> FULL_FEATURES START --> DEFAULT_CHECK
Sources: Cargo.toml(L14 - L19)
Dependency Management
The crate maintains minimal external dependencies to support embedded environments and no_std
targets.
Direct Dependencies
The only optional dependency is the chrono
crate, configured with minimal features:
chrono = { version = "0.4.38", default-features = false, optional = true }
This configuration ensures:
- No standard library dependencies from chrono
- Compatible with
no_std
environments - Minimal binary size impact when enabled
- Optional inclusion based on feature flags
Platform Compatibility
The crate is designed for multiple target architectures:
Target | Support | Use Case |
---|---|---|
aarch64-unknown-none-softfloat | ✓ | Bare metal ARM64 |
x86_64-unknown-linux-gnu | ✓ | Development/testing |
x86_64-unknown-none | ✓ | Bare metal x86_64 |
riscv64gc-unknown-none-elf | ✓ | RISC-V embedded |
No Standard Library Support
The crate is no_std
compatible by default, making it suitable for embedded environments:
#![no_std] // Your embedded project
use arm_pl031::Rtc;
Dependency Resolution Matrix
flowchart TD subgraph subGraph2["Runtime Requirements"] NO_STD["no_std compatible"] MMIO["MMIO access capability"] UNSAFE["unsafe operations allowed"] end subgraph subGraph1["Feature Configurations"] MINIMAL["default-features = falseNo external deps"] CHRONO_ENABLED["chrono feature+ chrono 0.4.38"] end subgraph subGraph0["Target Categories"] EMBEDDED["Embedded Targetsaarch64-unknown-none-*riscv64gc-unknown-none-elf"] HOSTED["Hosted Targetsx86_64-unknown-linux-gnu"] end CHRONO_ENABLED --> NO_STD EMBEDDED --> CHRONO_ENABLED EMBEDDED --> MINIMAL HOSTED --> CHRONO_ENABLED HOSTED --> MINIMAL MINIMAL --> NO_STD MMIO --> UNSAFE NO_STD --> MMIO
Sources: Cargo.toml(L12) Cargo.toml(L15)
License and Legal Requirements
The crate is available under multiple license options to accommodate different project requirements:
GPL-3.0-or-later
- GNU General Public License v3.0 or laterApache-2.0
- Apache License 2.0MulanPSL-2.0
- Mulan Permissive Software License v2
Projects can choose the most appropriate license for their specific use case and compliance requirements.
Sources: Cargo.toml(L7)
Basic Usage and Examples
Relevant source files
This page provides practical examples for integrating and using the arm_pl031 RTC driver in your projects. It covers device initialization, basic time operations, and simple interrupt handling. For detailed information about optional features like chrono integration, see Chrono Integration. For comprehensive driver internals and advanced usage, see Core Driver Implementation.
Device Tree Configuration and Address Discovery
The ARM PL031 RTC driver requires a memory-mapped I/O (MMIO) base address to access the hardware registers. This address is typically obtained from the device tree configuration in your system.
Device Tree Example
The device tree entry for a PL031 RTC device follows this structure:
pl031@9010000 {
clock-names = "apb_pclk";
clocks = <0x8000>;
interrupts = <0x00 0x02 0x04>;
reg = <0x00 0x9010000 0x00 0x1000>;
compatible = "arm,pl031", "arm,primecell";
};
The reg
property specifies the base address (0x9010000
) and size (0x1000
) of the MMIO region. Your system's device tree parser should map this physical address to a virtual address accessible to your driver.
Address Discovery Flow
flowchart TD DT["Device Tree Entry"] PARSER["Device Tree Parser"] PHYS["Physical Address0x9010000"] MMU["Memory Management Unit"] VIRT["Virtual AddressMapped for MMIO"] RTC_NEW["Rtc::new(base_address)"] RTC_INSTANCE["Rtc Instance"] REG_PROP["reg = <0x00 0x9010000 0x00 0x1000>"] COMPAT["compatible = arm,pl031"] DT --> COMPAT DT --> PARSER DT --> REG_PROP MMU --> VIRT PARSER --> PHYS PHYS --> MMU RTC_NEW --> RTC_INSTANCE VIRT --> RTC_NEW
Sources: README.md(L19 - L37) src/lib.rs(L47 - L60)
Basic Initialization
The Rtc
struct provides the primary interface to the PL031 hardware. Initialization requires an unsafe constructor call since it involves raw memory access.
Creating an RTC Instance
use arm_pl031::Rtc;
// Obtain base_address from device tree mapping
let base_address = 0x901_0000 as *mut u32;
// SAFETY: base_address must point to valid PL031 MMIO registers
let rtc = unsafe { Rtc::new(base_address) };
Safety Requirements
The new
constructor is marked unsafe because it requires several guarantees from the caller:
- The base address must point to valid PL031 MMIO control registers
- The memory region must be mapped as device memory (not cached)
- The address must be aligned to a 4-byte boundary
- No other aliases to this memory region should exist
flowchart TD UNSAFE_NEW["unsafe Rtc::new(base_address)"] SAFETY_CHECK["Safety Requirements Check"] VALID_ADDR["Valid MMIO Address"] DEVICE_MEM["Device Memory Mapping"] ALIGNMENT["4-byte Alignment"] NO_ALIAS["No Memory Aliases"] RTC_STRUCT["Rtc { registers: *mut Registers }"] API_CALLS["Safe API Methods"] ALIGNMENT --> RTC_STRUCT DEVICE_MEM --> RTC_STRUCT NO_ALIAS --> RTC_STRUCT RTC_STRUCT --> API_CALLS SAFETY_CHECK --> ALIGNMENT SAFETY_CHECK --> DEVICE_MEM SAFETY_CHECK --> NO_ALIAS SAFETY_CHECK --> VALID_ADDR UNSAFE_NEW --> SAFETY_CHECK VALID_ADDR --> RTC_STRUCT
Sources: src/lib.rs(L47 - L60) README.md(L9 - L14)
Reading and Setting Time
The driver provides straightforward methods for time operations using Unix timestamps (seconds since epoch).
Reading Current Time
The get_unix_timestamp
method reads the current time from the Data Register (dr
):
let current_time = rtc.get_unix_timestamp();
println!("Current Unix timestamp: {}", current_time);
Setting Current Time
The set_unix_timestamp
method writes to the Load Register (lr
) to update the RTC:
let new_time = 1609459200; // January 1, 2021 00:00:00 UTC
rtc.set_unix_timestamp(new_time);
Time Operations Flow
flowchart TD subgraph subGraph2["Hardware Registers"] DR_REG["DR (Data Register)Read Current Time"] LR_REG["LR (Load Register)Set New Time"] end subgraph subGraph1["Write Operations"] SET_CALL["set_unix_timestamp(time)"] WRITE_LR["write_volatile(lr)"] UPDATE["RTC Updated"] end subgraph subGraph0["Read Operations"] GET_CALL["get_unix_timestamp()"] READ_DR["read_volatile(dr)"] TIMESTAMP["u32 timestamp"] end GET_CALL --> READ_DR READ_DR --> DR_REG READ_DR --> TIMESTAMP SET_CALL --> WRITE_LR WRITE_LR --> LR_REG WRITE_LR --> UPDATE
Sources: src/lib.rs(L63 - L74) src/lib.rs(L135 - L157)
Basic Interrupt Operations
The PL031 supports interrupt generation when the current time matches a preset value. This enables alarm functionality and periodic notifications.
Setting Up Interrupts
// Set a match timestamp (e.g., 1 hour from now)
let alarm_time = rtc.get_unix_timestamp() + 3600;
rtc.set_match_timestamp(alarm_time);
// Enable the interrupt
rtc.enable_interrupt(true);
Checking Interrupt Status
// Check if the match condition occurred
if rtc.matched() {
println!("Time match occurred");
}
// Check if an interrupt is pending (matched + enabled)
if rtc.interrupt_pending() {
println!("Interrupt is pending");
// Clear the interrupt
rtc.clear_interrupt();
}
Interrupt State Machine
stateDiagram-v2 [*] --> Idle : "enable_interrupt(false)" Idle --> Armed : "set_match_timestamp() +enable_interrupt(true)" Armed --> Matched : "RTC time == Match Register" Matched --> Interrupted : "matched() == trueinterrupt_pending() == true" Interrupted --> Idle : "clear_interrupt()" Armed --> Idle : "enable_interrupt(false)" Matched --> Idle : "enable_interrupt(false)"
Register-Level Interrupt Flow
flowchart TD subgraph subGraph1["Hardware Registers"] MR["MR (Match Register)"] IMSC["IMSC (Interrupt Mask)"] RIS["RIS (Raw Interrupt Status)"] MIS["MIS (Masked Interrupt Status)"] ICR["ICR (Interrupt Clear)"] end subgraph subGraph0["Application API"] SET_MATCH["set_match_timestamp(time)"] ENABLE_INT["enable_interrupt(true)"] CHECK_PENDING["interrupt_pending()"] CLEAR_INT["clear_interrupt()"] end CHECK_PENDING --> MIS CLEAR_INT --> ICR ENABLE_INT --> IMSC IMSC --> MIS MR --> RIS RIS --> MIS SET_MATCH --> MR
Sources: src/lib.rs(L76 - L120) src/lib.rs(L17 - L39)
Error Handling and Best Practices
Memory Safety
The driver maintains memory safety through careful use of volatile operations and proper Send/Sync implementations:
// The driver is Send + Sync safe
fn use_rtc_across_threads(rtc: Rtc) {
std::thread::spawn(move || {
let time = rtc.get_unix_timestamp(); // Safe from any thread
});
}
Typical Usage Pattern
// 1. Initialize from device tree
let base_addr = get_pl031_base_from_device_tree();
let mut rtc = unsafe { Rtc::new(base_addr) };
// 2. Read current time
let current_time = rtc.get_unix_timestamp();
// 3. Set up periodic alarm (optional)
rtc.set_match_timestamp(current_time + 60); // 1 minute alarm
rtc.enable_interrupt(true);
// 4. In interrupt handler or polling loop
if rtc.interrupt_pending() {
handle_rtc_alarm();
rtc.clear_interrupt();
}
Sources: src/lib.rs(L123 - L128) README.md(L9 - L14)
Core Driver Implementation
Relevant source files
This document provides a detailed technical analysis of the core ARM PL031 RTC driver implementation. It covers the internal structure of the Rtc
driver, register layout, hardware interface patterns, and safety mechanisms. This focuses specifically on the implementation details found in the main driver code.
For basic usage examples and integration guidance, see Basic Usage and Examples. For information about optional features like chrono integration, see Chrono Integration.
Driver Structure and Core Components
The core driver implementation centers around two primary structures that provide the foundation for all RTC operations.
Core Driver Architecture
The Rtc
struct src/lib.rs(L42 - L44) serves as the primary driver interface, containing a single field that points to the memory-mapped register layout. The Registers
struct src/lib.rs(L15 - L39) defines the exact memory layout of the PL031 hardware registers with proper alignment and padding.
Sources: src/lib.rs(L15 - L44)
Register Layout and Hardware Interface
The driver implements a direct mapping to the ARM PL031 hardware registers through a carefully structured memory layout.
PL031 Register Mapping
Register | Type | Offset | Purpose |
---|---|---|---|
dr | u32 | 0x00 | Data Register - current RTC value |
mr | u32 | 0x04 | Match Register - alarm comparison value |
lr | u32 | 0x08 | Load Register - sets RTC value |
cr | u8 | 0x0C | Control Register - enables RTC |
imsc | u8 | 0x10 | Interrupt Mask Set/Clear |
ris | u8 | 0x14 | Raw Interrupt Status |
mis | u8 | 0x18 | Masked Interrupt Status |
icr | u8 | 0x1C | Interrupt Clear Register |
The Registers
struct uses #[repr(C, align(4))]
src/lib.rs(L16) to ensure proper memory alignment and layout compatibility with the hardware. Reserved padding fields like _reserved0: [u8; 3]
src/lib.rs(L26) maintain correct register spacing.
MMIO Access Patterns
All hardware access follows a consistent volatile read/write pattern:
The driver uses addr_of!
and addr_of_mut!
macros src/lib.rs(L66) src/lib.rs(L73) to create pointers to specific register fields, followed by volatile operations to ensure proper hardware communication without compiler optimizations.
Sources: src/lib.rs(L15 - L39) src/lib.rs(L63 - L74)
Core Time Operations
The driver provides fundamental time management through Unix timestamp operations, implementing both read and write access to the RTC hardware.
Time Reading and Writing
The get_unix_timestamp()
method src/lib.rs(L63 - L67) reads the current RTC value from the Data Register (dr
), while set_unix_timestamp()
src/lib.rs(L70 - L74) writes to the Load Register (lr
) to update the RTC time.
flowchart TD subgraph subGraph1["Time Write Flow"] HW_UPDATE["Hardware updated"] subgraph subGraph0["Time Read Flow"] SET_CALL["set_unix_timestamp(unix_time)"] LR_WRITE["(*self.registers).lr"] VOLATILE_WRITE["write_volatile(unix_time)"] GET_CALL["get_unix_timestamp()"] DR_READ["(*self.registers).dr"] VOLATILE_READ["read_volatile()"] UNIX_TIME["u32 timestamp"] end end DR_READ --> VOLATILE_READ GET_CALL --> DR_READ LR_WRITE --> VOLATILE_WRITE SET_CALL --> LR_WRITE VOLATILE_READ --> UNIX_TIME VOLATILE_WRITE --> HW_UPDATE
Both operations use 32-bit unsigned integers, matching the hardware register width. The choice of u32
over u64
was made in version 0.2.0 CHANGELOG.md(L14 - L15) to align with the actual hardware capabilities.
Sources: src/lib.rs(L63 - L74) CHANGELOG.md(L14 - L15)
Interrupt Management System
The driver implements a comprehensive interrupt management system using the PL031's match register functionality and interrupt control registers.
Interrupt Control Flow
Interrupt Methods
Method | Register | Purpose |
---|---|---|
set_match_timestamp()src/lib.rs78-82 | mr | Configure alarm time |
enable_interrupt()src/lib.rs108-113 | imsc | Enable/disable interrupts |
matched()src/lib.rs86-91 | ris | Check if match occurred |
interrupt_pending()src/lib.rs97-102 | mis | Check if interrupt is pending |
clear_interrupt()src/lib.rs116-120 | icr | Clear pending interrupt |
The interrupt system operates by comparing the RTC value against the match register. When they match, the Raw Interrupt Status (ris
) bit is set src/lib.rs(L89 - L90) If interrupts are enabled via the Interrupt Mask (imsc
), the Masked Interrupt Status (mis
) will also be set src/lib.rs(L100 - L101)
Sources: src/lib.rs(L78 - L120)
Memory Safety and Concurrency Model
The driver implements a carefully designed safety model that provides safe abstractions over unsafe hardware operations while supporting concurrent access.
Safety Boundaries
Constructor Safety Requirements
The new()
constructor src/lib.rs(L56 - L60) is marked unsafe
and requires several safety guarantees:
- Base address must point to valid PL031 MMIO registers
- Memory must be mapped as device memory without aliases
- Address must be aligned to 4-byte boundary
- Caller must ensure exclusive access during construction
Thread Safety Implementation
The driver implements Send
src/lib.rs(L124) and Sync
src/lib.rs(L128) traits with careful safety reasoning:
- Send: The
Rtc
struct contains only a pointer to device memory, which can be safely transferred between threads - Sync: Shared references (
&Rtc
) only allow reading device registers, which is safe for concurrent access
The set_unix_timestamp()
method requires &mut self
src/lib.rs(L70) because it performs write operations, ensuring exclusive access for modifications while allowing concurrent reads.
Sources: src/lib.rs(L47 - L60) src/lib.rs(L124 - L128) CHANGELOG.md(L16 - L17) CHANGELOG.md(L21)
Driver Architecture and Design
Relevant source files
This page documents the internal architecture and design principles of the arm_pl031
RTC driver, focusing on the core Rtc
struct, register layout, and hardware abstraction patterns. The material covers how the driver translates hardware register operations into safe Rust APIs while maintaining minimal overhead.
For hardware interface details and MMIO operations, see Hardware Interface and MMIO. For register-specific operations and meanings, see Register Operations.
Core Driver Structure
The driver architecture centers around two primary components: the Registers
struct that defines the hardware memory layout, and the Rtc
struct that provides the safe API interface.
Driver Components and Relationships
flowchart TD subgraph subGraph3["Hardware Layer"] MMIO["Memory-Mapped I/O"] PL031["PL031 RTC Hardware"] end subgraph subGraph2["Unsafe Operations Layer"] VolatileOps["Volatile Operationsread_volatile()write_volatile()"] PtrOps["Pointer Operationsaddr_of!()addr_of_mut!()"] end subgraph subGraph1["Hardware Abstraction Layer"] Registers["Registers struct"] RegisterFields["Register Fieldsdr: u32mr: u32lr: u32cr: u8imsc: u8ris: u8mis: u8icr: u8"] end subgraph subGraph0["Driver API Layer"] Rtc["Rtc struct"] PublicMethods["Public Methodsget_unix_timestamp()set_unix_timestamp()set_match_timestamp()matched()interrupt_pending()enable_interrupt()clear_interrupt()"] end MMIO --> PL031 PtrOps --> MMIO PublicMethods --> Registers RegisterFields --> VolatileOps Registers --> RegisterFields Rtc --> PublicMethods Rtc --> Registers VolatileOps --> PtrOps
Sources: src/lib.rs(L42 - L44) src/lib.rs(L15 - L39) src/lib.rs(L46 - L121)
Register Memory Layout
The Registers
struct provides a direct mapping to the PL031 hardware register layout, ensuring proper alignment and padding for hardware compatibility.
flowchart TD subgraph subGraph0["Memory Layout (repr C, align 4)"] DR["dr: u32Offset: 0x00Data Register"] MR["mr: u32Offset: 0x04Match Register"] LR["lr: u32Offset: 0x08Load Register"] CR["cr: u8Offset: 0x0CControl Register"] PAD1["_reserved0: [u8; 3]Offset: 0x0D-0x0F"] IMSC["imsc: u8Offset: 0x10Interrupt Mask"] PAD2["_reserved1: [u8; 3]Offset: 0x11-0x13"] RIS["ris: u8Offset: 0x14Raw Interrupt Status"] PAD3["_reserved2: [u8; 3]Offset: 0x15-0x17"] MIS["mis: u8Offset: 0x18Masked Interrupt Status"] PAD4["_reserved3: [u8; 3]Offset: 0x19-0x1B"] ICR["icr: u8Offset: 0x1CInterrupt Clear"] PAD5["_reserved4: [u8; 3]Offset: 0x1D-0x1F"] end
Sources: src/lib.rs(L15 - L39)
Driver Design Principles
Safety Boundary Architecture
The driver implements a clear safety boundary where all unsafe operations are contained within method implementations, exposing only safe APIs to users.
Component | Safety Level | Responsibility |
---|---|---|
Rtc::new() | Unsafe | Initial pointer validation and construction |
Public methods | Safe | High-level operations with safety guarantees |
Volatile operations | Unsafe (internal) | Direct hardware register access |
Pointer arithmetic | Unsafe (internal) | Register address calculation |
State Management Design
The Rtc
struct follows a stateless design pattern, storing only the base pointer to hardware registers without caching any hardware state.
flowchart TD subgraph subGraph2["Operation Pattern"] Read["Read OperationsDirect volatile readsNo local caching"] Write["Write OperationsDirect volatile writesImmediate effect"] end subgraph subGraph1["Hardware State"] HardwareRegs["PL031 Hardware RegistersAll state lives in hardware"] end subgraph subGraph0["Rtc State"] BasePtr["registers: *mut RegistersBase pointer to MMIO region"] end BasePtr --> HardwareRegs Read --> HardwareRegs Write --> HardwareRegs
Sources: src/lib.rs(L42 - L44) src/lib.rs(L63 - L66) src/lib.rs(L70 - L73)
Method Organization and Patterns
Functional Categories
The driver methods are organized into distinct functional categories, each following consistent patterns for hardware interaction.
Category | Methods | Pattern | Register Access |
---|---|---|---|
Time Management | get_unix_timestamp(),set_unix_timestamp() | Read/Write DR/LR | Direct timestamp operations |
Alarm Management | set_match_timestamp(),matched() | Write MR, Read RIS | Match register operations |
Interrupt Management | enable_interrupt(),interrupt_pending(),clear_interrupt() | Read/Write IMSC/MIS/ICR | Interrupt control flow |
Memory Access Pattern
All hardware access follows a consistent volatile operation pattern using addr_of!
and addr_of_mut!
macros for safe pointer arithmetic.
flowchart TD ReadAddr --> ReadVolatile ReadStart --> ReadAddr ReadVolatile --> ReadReturn WriteAddr --> WriteVolatile WriteStart --> WriteAddr WriteVolatile --> WriteComplete
Sources: src/lib.rs(L63 - L66) src/lib.rs(L70 - L73) src/lib.rs(L108 - L112)
Concurrency and Thread Safety
Send and Sync Implementation
The driver implements Send
and Sync
traits with explicit safety documentation, enabling safe usage across thread boundaries.
flowchart TD subgraph subGraph2["Usage Patterns"] SharedRead["Shared Read AccessMultiple &Rtc referencesConcurrent timestamp reading"] ExclusiveWrite["Exclusive Write AccessSingle &mut Rtc referenceSerialized configuration changes"] end subgraph subGraph1["Safety Guarantees"] DeviceMemory["Device Memory PropertiesHardware provides atomicityNo local state to corrupt"] ReadSafety["Concurrent Read SafetyMultiple readers allowedHardware state remains consistent"] end subgraph subGraph0["Thread Safety Design"] SendImpl["unsafe impl Send for RtcDevice memory accessible from any context"] SyncImpl["unsafe impl Sync for RtcRead operations safe from multiple threads"] end DeviceMemory --> ExclusiveWrite DeviceMemory --> SharedRead ReadSafety --> SharedRead SendImpl --> DeviceMemory SyncImpl --> ReadSafety
Sources: src/lib.rs(L123 - L128)
Construction and Initialization
The driver construction follows an unsafe initialization pattern that requires explicit safety contracts from the caller.
Initialization Requirements
The new()
method establishes the safety contract for proper driver operation:
- Base address must point to valid PL031 MMIO registers
- Memory must be mapped as device memory
- No other aliases to the memory region
- 4-byte alignment requirement
Sources: src/lib.rs(L46 - L60)
Hardware Interface and MMIO
Relevant source files
This document details how the ARM PL031 RTC driver interfaces with the physical hardware through Memory-Mapped I/O (MMIO) operations. It covers the register layout, volatile access patterns, memory safety boundaries, and the hardware abstraction layer implementation.
For information about specific register operations and their meanings, see Register Operations. For details about the overall driver architecture, see Driver Architecture and Design.
MMIO Register Layout
The PL031 hardware interface is accessed through a well-defined memory-mapped register layout represented by the Registers
struct. This struct provides a direct mapping to the hardware register space with proper alignment and padding.
PL031 Register Memory Layout
flowchart TD subgraph subGraph0["MMIO Address Space"] BASE["Base Address(4-byte aligned)"] DR["DR (0x00)Data Register32-bit"] MR["MR (0x04)Match Register32-bit"] LR["LR (0x08)Load Register32-bit"] CR["CR (0x0C)Control Register8-bit + 3 reserved"] IMSC["IMSC (0x10)Interrupt Mask8-bit + 3 reserved"] RIS["RIS (0x14)Raw Interrupt Status8-bit + 3 reserved"] MIS["MIS (0x18)Masked Interrupt Status8-bit + 3 reserved"] ICR["ICR (0x1C)Interrupt Clear8-bit + 3 reserved"] end BASE --> DR CR --> IMSC DR --> MR IMSC --> RIS LR --> CR MIS --> ICR MR --> LR RIS --> MIS
The Registers
struct enforces proper memory layout through careful use of padding and alignment directives. Each register is positioned according to the PL031 specification, with reserved bytes maintaining proper spacing between hardware registers.
Sources: src/lib.rs(L15 - L39)
Hardware Access Abstraction
The driver implements a three-layer abstraction for hardware access, ensuring both safety and performance while maintaining direct hardware control.
Hardware Access Layers
flowchart TD subgraph subGraph1["Hardware Layer"] MMIO["Memory-Mapped I/ODevice Memory Region"] REGS["PL031 Hardware RegistersPhysical Hardware"] end subgraph subGraph0["Software Layers"] API["Public API Methodsget_unix_timestamp()set_unix_timestamp()enable_interrupt()"] UNSAFE["Unsafe Hardware Accessaddr_of!()read_volatile()write_volatile()"] PTR["Raw Pointer Operations(*self.registers).dr(*self.registers).mr"] end API --> UNSAFE MMIO --> REGS PTR --> MMIO UNSAFE --> PTR
The Rtc
struct maintains a single *mut Registers
pointer that serves as the bridge between safe Rust code and raw hardware access. All hardware operations go through volatile memory operations to ensure proper interaction with device memory.
Sources: src/lib.rs(L42 - L44) src/lib.rs(L56 - L60)
Volatile Memory Operations
All hardware register access uses volatile operations to prevent compiler optimizations that could interfere with hardware behavior. The driver employs a consistent pattern for both read and write operations.
MMIO Operation Patterns
sequenceDiagram participant PublicAPI as "Public API" participant UnsafeBlock as "Unsafe Block" participant addr_ofaddr_of_mut as "addr_of!/addr_of_mut!" participant read_volatilewrite_volatile as "read_volatile/write_volatile" participant PL031Hardware as "PL031 Hardware" Note over PublicAPI,PL031Hardware: Read Operation Pattern PublicAPI ->> UnsafeBlock: Call hardware access UnsafeBlock ->> addr_ofaddr_of_mut: addr_of!((*registers).field) addr_ofaddr_of_mut ->> read_volatilewrite_volatile: .read_volatile() read_volatilewrite_volatile ->> PL031Hardware: Load from device memory PL031Hardware -->> read_volatilewrite_volatile: Register value read_volatilewrite_volatile -->> addr_ofaddr_of_mut: Raw value addr_ofaddr_of_mut -->> UnsafeBlock: Typed value UnsafeBlock -->> PublicAPI: Safe result Note over PublicAPI,PL031Hardware: Write Operation Pattern PublicAPI ->> UnsafeBlock: Call hardware access UnsafeBlock ->> addr_ofaddr_of_mut: addr_of_mut!((*registers).field) addr_ofaddr_of_mut ->> read_volatilewrite_volatile: .write_volatile(value) read_volatilewrite_volatile ->> PL031Hardware: Store to device memory PL031Hardware -->> read_volatilewrite_volatile: Write acknowledgment read_volatilewrite_volatile -->> addr_ofaddr_of_mut: Write complete addr_ofaddr_of_mut -->> UnsafeBlock: Operation complete UnsafeBlock -->> PublicAPI: Safe completion
The driver uses addr_of!
and addr_of_mut!
macros to safely obtain field addresses from the raw pointer without creating intermediate references, avoiding undefined behavior while maintaining direct hardware access.
Sources: src/lib.rs(L63 - L67) src/lib.rs(L70 - L74) src/lib.rs(L78 - L82)
Memory Safety Model
The hardware interface implements a carefully designed safety model that isolates unsafe operations while providing safe public APIs. The safety boundaries are clearly defined and documented.
Operation Type | Safety Level | Access Pattern | Usage |
---|---|---|---|
Construction | Unsafe | Rtc::new() | Requires valid MMIO base address |
Register Read | Safe | get_unix_timestamp() | Volatile read through safe wrapper |
Register Write | Safe | set_unix_timestamp() | Volatile write through safe wrapper |
Interrupt Control | Safe | enable_interrupt() | Masked volatile operations |
The Rtc
struct implements Send
and Sync
traits with careful safety justification, allowing the driver to be used in multi-threaded contexts while maintaining memory safety guarantees.
Safety Boundary Implementation
flowchart TD subgraph subGraph2["Hardware Zone"] DEVICE_MEM["Device Memory"] PL031_HW["PL031 Hardware"] end subgraph subGraph1["Unsafe Zone"] UNSAFE_BLOCKS["unsafe blocks"] RAW_PTR["*mut Registers"] VOLATILE_OPS["volatile operations"] end subgraph subGraph0["Safe Zone"] SAFE_API["Safe Public Methods"] SAFE_REFS["&self / &mut self"] end RAW_PTR --> DEVICE_MEM SAFE_API --> UNSAFE_BLOCKS SAFE_REFS --> RAW_PTR UNSAFE_BLOCKS --> VOLATILE_OPS VOLATILE_OPS --> PL031_HW
Sources: src/lib.rs(L51 - L60) src/lib.rs(L123 - L128)
Address Space Configuration
The hardware interface requires proper address space configuration to function correctly. The base address must point to a valid PL031 device memory region with appropriate mapping characteristics.
Address Space Requirements
flowchart TD subgraph subGraph2["Safety Requirements"] EXCLUSIVE["Exclusive AccessNo other aliases"] VALID["Valid MappingProcess address space"] DEVICE["Device Memory TypeProper memory attributes"] end subgraph subGraph1["Physical Hardware"] PL031_DEV["PL031 DeviceHardware Registers"] MMIO_REGION["MMIO Region32-byte space"] end subgraph subGraph0["Virtual Address Space"] BASE_ADDR["Base Address(from device tree)"] ALIGN["4-byte AlignmentRequired"] MAPPING["Device Memory MappingNon-cacheable"] end ALIGN --> MAPPING BASE_ADDR --> ALIGN BASE_ADDR --> EXCLUSIVE MAPPING --> PL031_DEV MAPPING --> VALID MMIO_REGION --> DEVICE PL031_DEV --> MMIO_REGION
The driver expects the base address to be obtained from platform-specific sources such as device tree configuration, and requires the caller to ensure proper memory mapping with device memory characteristics.
Sources: src/lib.rs(L47 - L60)
Register Operations
Relevant source files
This page provides comprehensive documentation of the PL031 register operations implemented in the ARM PL031 RTC driver. It covers the register memory layout, volatile access patterns, and the meaning of each hardware register. For information about the broader hardware interface and MMIO concepts, see Hardware Interface and MMIO. For details about interrupt-specific register usage, see Interrupt Handling.
Register Layout and Memory Mapping
The PL031 device exposes its functionality through a set of memory-mapped registers. The driver defines these registers using a C-compatible structure that matches the hardware layout exactly.
Memory Layout Structure
The register layout is defined in the Registers
struct which uses precise memory alignment and padding to match the PL031 hardware specification:
flowchart TD subgraph subGraph0["Registers Struct Layout"] DR["dr: u32Data RegisterOffset: 0x00"] MR["mr: u32Match RegisterOffset: 0x04"] LR["lr: u32Load RegisterOffset: 0x08"] CR["cr: u8 + paddingControl RegisterOffset: 0x0C"] IMSC["imsc: u8 + paddingInterrupt MaskOffset: 0x10"] RIS["ris: u8 + paddingRaw Interrupt StatusOffset: 0x14"] MIS["mis: u8 + paddingMasked Interrupt StatusOffset: 0x18"] ICR["icr: u8 + paddingInterrupt ClearOffset: 0x1C"] end CR --> IMSC DR --> MR IMSC --> RIS LR --> CR MIS --> ICR MR --> LR RIS --> MIS
The structure uses #[repr(C, align(4))]
to ensure proper memory layout and alignment with the hardware expectations. Reserved fields with specific padding ensure that each register appears at the correct memory offset.
Sources: src/lib.rs(L15 - L39)
Register Access Patterns
All register operations use volatile memory access to prevent compiler optimizations that could interfere with hardware communication:
flowchart TD subgraph subGraph1["Write Operations"] subgraph subGraph0["Read Operations"] WRITE_START["Function Call"] WRITE_PTR["addr_of_mut!((*registers).field)"] WRITE_VOLATILE["write_volatile(value)"] WRITE_COMPLETE["Hardware Updated"] READ_START["Function Call"] READ_PTR["addr_of!((*registers).field)"] READ_VOLATILE["read_volatile()"] READ_RESULT["Return Value"] end end READ_PTR --> READ_VOLATILE READ_START --> READ_PTR READ_VOLATILE --> READ_RESULT WRITE_PTR --> WRITE_VOLATILE WRITE_START --> WRITE_PTR WRITE_VOLATILE --> WRITE_COMPLETE
Sources: src/lib.rs(L64 - L66) src/lib.rs(L70 - L74) src/lib.rs(L78 - L82)
Individual Register Operations
Data Register (DR)
The Data Register provides read-only access to the current RTC value as a 32-bit Unix timestamp.
Field | Type | Access | Purpose |
---|---|---|---|
dr | u32 | Read-only | Current time in seconds since Unix epoch |
The get_unix_timestamp()
method reads this register:
sequenceDiagram participant Application as "Application" participant Rtcget_unix_timestamp as "Rtc::get_unix_timestamp()" participant PL031Hardware as "PL031 Hardware" Application ->> Rtcget_unix_timestamp: "get_unix_timestamp()" Rtcget_unix_timestamp ->> PL031Hardware: "addr_of!((*registers).dr).read_volatile()" PL031Hardware -->> Rtcget_unix_timestamp: "u32 timestamp" Rtcget_unix_timestamp -->> Application: "u32 timestamp"
Sources: src/lib.rs(L18) src/lib.rs(L62 - L67)
Load Register (LR)
The Load Register allows setting the current RTC time by writing a 32-bit Unix timestamp.
Field | Type | Access | Purpose |
---|---|---|---|
lr | u32 | Write-only | Sets current time in seconds since Unix epoch |
The set_unix_timestamp()
method writes to this register:
Sources: src/lib.rs(L22) src/lib.rs(L69 - L74)
Match Register (MR)
The Match Register stores a timestamp value that triggers an interrupt when the RTC time matches it.
Field | Type | Access | Purpose |
---|---|---|---|
mr | u32 | Write-only | Alarm time for interrupt generation |
The set_match_timestamp()
method configures this register:
Sources: src/lib.rs(L20) src/lib.rs(L76 - L82)
Interrupt Control Registers
The PL031 provides several registers for interrupt management:
Register | Field | Type | Purpose |
---|---|---|---|
IMSC | imsc | u8 | Interrupt mask control (enable/disable) |
RIS | ris | u8 | Raw interrupt status (always shows match state) |
MIS | mis | u8 | Masked interrupt status (shows pending interrupts) |
ICR | icr | u8 | Interrupt clear (write to acknowledge) |
flowchart TD subgraph subGraph0["Interrupt Register Flow"] MATCH["Hardware Match Event"] RIS_SET["RIS[0] = 1"] IMSC_CHECK["Check IMSC[0]"] MIS_SET["MIS[0] = RIS[0] & IMSC[0]"] IRQ["Interrupt Signal"] ICR_WRITE["Write ICR[0] = 1"] CLEAR["Clear RIS[0] and MIS[0]"] end ICR_WRITE --> CLEAR IMSC_CHECK --> MIS_SET IRQ --> ICR_WRITE MATCH --> RIS_SET MIS_SET --> IRQ RIS_SET --> IMSC_CHECK
Sources: src/lib.rs(L27 - L38) src/lib.rs(L104 - L113) src/lib.rs(L115 - L120)
Volatile Access Safety
All register operations are performed within unsafe
blocks due to the raw pointer dereference required for MMIO access. The driver ensures safety through several mechanisms:
Memory Safety Guarantees
- The
Rtc::new()
constructor requires the caller to provide safety guarantees about the base address - All register access uses
addr_of!
andaddr_of_mut!
macros to create properly aligned pointers - Volatile operations prevent compiler optimizations that could reorder or eliminate hardware accesses
flowchart TD subgraph subGraph1["Safety Documentation"] DOC_NEW["Safety doc for new()"] DOC_BLOCKS["Safety comments in blocks"] end subgraph subGraph0["Safety Boundaries"] UNSAFE_NEW["unsafe fn new(base_address)"] SAFE_METHODS["Safe public methods"] UNSAFE_BLOCKS["Unsafe register access blocks"] HARDWARE["Hardware registers"] end DOC_BLOCKS --> UNSAFE_BLOCKS DOC_NEW --> UNSAFE_NEW SAFE_METHODS --> UNSAFE_BLOCKS UNSAFE_BLOCKS --> HARDWARE UNSAFE_NEW --> SAFE_METHODS
Sources: src/lib.rs(L51 - L60) src/lib.rs(L64 - L66) src/lib.rs(L72 - L74)
Register Field Access Patterns
The driver uses consistent patterns for accessing register fields:
Operation | Pattern | Example |
---|---|---|
Read u32 | addr_of!((*registers).field).read_volatile() | DR, RIS, MIS |
Write u32 | addr_of_mut!((*registers).field).write_volatile(value) | LR, MR |
Read u8 | addr_of!((*registers).field).read_volatile() | Status registers |
Write u8 | addr_of_mut!((*registers).field).write_volatile(value) | Control registers |
All register operations include safety comments explaining why the unsafe operation is valid within the context of the driver's safety guarantees.
Sources: src/lib.rs(L64 - L74) src/lib.rs(L88 - L90) src/lib.rs(L99 - L101) src/lib.rs(L111 - L112) src/lib.rs(L118 - L119)
Interrupt Handling
Relevant source files
This page covers the interrupt handling capabilities of the ARM PL031 RTC driver, including the interrupt registers, match-based interrupt generation, status checking, and interrupt management functions. The interrupt system allows applications to receive notifications when the RTC reaches a specific timestamp.
For general driver architecture and register operations, see 3.1 and 3.3. For memory safety considerations related to interrupt handling, see 3.5.
Interrupt System Overview
The PL031 RTC interrupt system is based on a timestamp matching mechanism. When the current RTC time matches a pre-configured match value, an interrupt is generated if enabled. The driver provides safe abstractions over the hardware interrupt registers to manage this functionality.
flowchart TD subgraph subGraph3["Hardware Logic"] COMPARE["Time Comparison Logic"] INT_GEN["Interrupt Generation"] end subgraph subGraph2["Hardware Registers"] MR["Match Register (mr)"] IMSC["Interrupt Mask (imsc)"] RIS["Raw Interrupt Status (ris)"] MIS["Masked Interrupt Status (mis)"] ICR["Interrupt Clear (icr)"] DR["Data Register (dr)"] end subgraph subGraph1["Driver API"] SET_MATCH["set_match_timestamp()"] ENABLE_INT["enable_interrupt()"] CHECK_PENDING["interrupt_pending()"] CHECK_MATCHED["matched()"] CLEAR_INT["clear_interrupt()"] end subgraph subGraph0["Application Layer"] APP["Application Code"] HANDLER["Interrupt Handler"] end APP --> ENABLE_INT APP --> SET_MATCH CHECK_MATCHED --> RIS CHECK_PENDING --> MIS CLEAR_INT --> ICR COMPARE --> RIS DR --> COMPARE ENABLE_INT --> IMSC HANDLER --> CHECK_PENDING HANDLER --> CLEAR_INT IMSC --> INT_GEN INT_GEN --> MIS MR --> COMPARE RIS --> INT_GEN SET_MATCH --> MR
Sources: src/lib.rs(L17 - L39) src/lib.rs(L78 - L120)
Interrupt Registers Layout
The PL031 interrupt system uses five dedicated registers within the Registers
structure to manage interrupt functionality:
Register | Offset | Size | Purpose | Access |
---|---|---|---|---|
mr | 0x04 | 32-bit | Match Register - stores target timestamp | Read/Write |
imsc | 0x10 | 8-bit | Interrupt Mask Set/Clear - enables/disables interrupts | Read/Write |
ris | 0x14 | 8-bit | Raw Interrupt Status - shows match status regardless of mask | Read |
mis | 0x18 | 8-bit | Masked Interrupt Status - shows actual interrupt state | Read |
icr | 0x1C | 8-bit | Interrupt Clear Register - clears pending interrupts | Write |
flowchart TD subgraph subGraph4["Driver Methods"] SET_MATCH_FUNC["set_match_timestamp()"] ENABLE_FUNC["enable_interrupt()"] MATCHED_FUNC["matched()"] PENDING_FUNC["interrupt_pending()"] CLEAR_FUNC["clear_interrupt()"] end subgraph subGraph3["Register Operations"] subgraph subGraph2["Interrupt Clearing"] ICR_OP["icr: write_volatile(0x01)"] end subgraph subGraph1["Status Reading"] RIS_OP["ris: read_volatile() & 0x01"] MIS_OP["mis: read_volatile() & 0x01"] end subgraph Configuration["Configuration"] MR_OP["mr: write_volatile(timestamp)"] IMSC_OP["imsc: write_volatile(0x01/0x00)"] end end CLEAR_FUNC --> ICR_OP ENABLE_FUNC --> IMSC_OP MATCHED_FUNC --> RIS_OP PENDING_FUNC --> MIS_OP SET_MATCH_FUNC --> MR_OP
Sources: src/lib.rs(L17 - L39) src/lib.rs(L78 - L120)
Interrupt Lifecycle and State Management
The interrupt system follows a well-defined lifecycle from configuration through interrupt generation to clearing:
stateDiagram-v2 [*] --> Idle : "RTC initialized" Idle --> Configured : "set_match_timestamp()" Configured --> Armed : "enable_interrupt(true)" Armed --> Matched : "RTC time == match time" Matched --> InterruptPending : "Hardware generates interrupt" InterruptPending --> Cleared : "clear_interrupt()" Cleared --> Armed : "Interrupt cleared, still armed" Armed --> Disabled : "enable_interrupt(false)" Disabled --> Idle : "Interrupt disabled" note left of Matched : ['"matched() returns true"'] note left of InterruptPending : ['"interrupt_pending() returns true"'] note left of Cleared : ['"Hardware interrupt cleared"']
Interrupt Configuration
The set_match_timestamp()
method configures when an interrupt should be generated:
#![allow(unused)] fn main() { pub fn set_match_timestamp(&mut self, match_timestamp: u32) }
This function writes the target timestamp to the match register (mr
). When the RTC's data register (dr
) equals this value, the hardware sets the raw interrupt status bit.
Interrupt Enabling and Masking
The enable_interrupt()
method controls whether interrupts are actually generated:
#![allow(unused)] fn main() { pub fn enable_interrupt(&mut self, mask: bool) }
When mask
is true
, the function writes 0x01
to the interrupt mask register (imsc
), enabling interrupts. When false
, it writes 0x00
, disabling them.
Sources: src/lib.rs(L78 - L82) src/lib.rs(L108 - L113)
Status Checking and Interrupt Detection
The driver provides two distinct methods for checking interrupt status, each serving different purposes:
Raw Match Status (matched())
The matched()
method checks the raw interrupt status register (ris
) to determine if the match condition has occurred, regardless of whether interrupts are enabled:
#![allow(unused)] fn main() { pub fn matched(&self) -> bool }
This method reads the ris
register and returns true
if bit 0 is set, indicating that the RTC time matches the match register value.
Masked Interrupt Status (interrupt_pending())
The interrupt_pending()
method checks the masked interrupt status register (mis
) to determine if there is an actual pending interrupt:
#![allow(unused)] fn main() { pub fn interrupt_pending(&self) -> bool }
This method returns true
only when both the match condition is met AND interrupts are enabled. This is the method typically used in interrupt handlers.
flowchart TD subgraph subGraph0["Status Check Flow"] TIME_CHECK["RTC Time == Match Time?"] RIS_SET["Set ris bit 0"] MASK_CHECK["Interrupt enabled (imsc)?"] MIS_SET["Set mis bit 0"] MATCHED_CALL["matched() called"] READ_RIS["Read ris register"] MATCHED_RESULT["Return (ris & 0x01) != 0"] PENDING_CALL["interrupt_pending() called"] READ_MIS["Read mis register"] PENDING_RESULT["Return (mis & 0x01) != 0"] end MASK_CHECK --> MIS_SET MATCHED_CALL --> READ_RIS PENDING_CALL --> READ_MIS READ_MIS --> PENDING_RESULT READ_RIS --> MATCHED_RESULT RIS_SET --> MASK_CHECK TIME_CHECK --> RIS_SET
Sources: src/lib.rs(L86 - L91) src/lib.rs(L97 - L102)
Interrupt Clearing
The clear_interrupt()
method resets the interrupt state by writing to the interrupt clear register (icr
):
#![allow(unused)] fn main() { pub fn clear_interrupt(&mut self) }
This function writes 0x01
to the icr
register, which clears both the raw interrupt status (ris
) and masked interrupt status (mis
) bits. After clearing, the interrupt system can generate new interrupts when the next match condition occurs.
Usage Patterns and Best Practices
Basic Interrupt Setup
// Configure interrupt for specific timestamp
rtc.set_match_timestamp(target_time);
rtc.enable_interrupt(true);
Interrupt Handler Pattern
// In interrupt handler
if rtc.interrupt_pending() {
// Handle the interrupt
handle_rtc_interrupt();
// Clear the interrupt
rtc.clear_interrupt();
// Optionally set new match time
rtc.set_match_timestamp(next_target_time);
}
Polling Pattern (Alternative to Interrupts)
// Poll for match without using interrupts
rtc.enable_interrupt(false);
loop {
if rtc.matched() {
handle_rtc_event();
// Set new match time or break
rtc.set_match_timestamp(next_target_time);
}
// Other work...
}
Sources: src/lib.rs(L78 - L120)
Thread Safety and Concurrency
The interrupt-related methods maintain the same thread safety guarantees as the rest of the driver. Reading methods like matched()
and interrupt_pending()
are safe to call concurrently, while writing methods like set_match_timestamp()
, enable_interrupt()
, and clear_interrupt()
require mutable access to ensure atomicity.
The driver implements Send
and Sync
traits, allowing the RTC instance to be shared across threads with appropriate synchronization mechanisms.
Sources: src/lib.rs(L123 - L128)
Memory Safety and Concurrency
Relevant source files
This page covers the memory safety model and concurrency characteristics of the ARM PL031 RTC driver. It explains how the driver maintains safety while performing low-level hardware operations, the boundaries between safe and unsafe code, and the thread safety guarantees provided by the implementation.
For information about the core driver architecture, see Driver Architecture and Design. For details about hardware register operations, see Register Operations.
Safety Model and Unsafe Boundaries
The arm_pl031 driver implements a layered safety model that encapsulates all unsafe hardware operations within well-defined boundaries while providing safe APIs to consumers.
Unsafe Code Isolation
The driver concentrates all unsafe operations within the core Rtc
implementation, creating a clear safety boundary:
flowchart TD subgraph subGraph2["Hardware Layer"] REGISTERS["PL031 Registers*mut Registers"] DEVICE_MEMORY["Device MemoryMMIO Region"] end subgraph subGraph1["Unsafe Boundary"] CONSTRUCTOR["unsafe fn new()Raw pointer validation"] MMIO_OPS["MMIO Operationsread_volatile()write_volatile()"] end subgraph subGraph0["Safe API Layer"] PUBLIC_API["Public Methodsget_unix_timestamp()set_unix_timestamp()enable_interrupt()"] end CONSTRUCTOR --> REGISTERS MMIO_OPS --> REGISTERS PUBLIC_API --> CONSTRUCTOR PUBLIC_API --> MMIO_OPS REGISTERS --> DEVICE_MEMORY
Sources: src/lib.rs(L46 - L60) src/lib.rs(L63 - L120)
Constructor Safety Requirements
The unsafe fn new()
constructor establishes the fundamental safety contract for the entire driver. The caller must guarantee several critical invariants:
Safety Requirement | Description | Validation |
---|---|---|
Valid MMIO Mapping | Base address points to mapped PL031 registers | Caller responsibility |
Exclusive Access | No other aliases to the memory region | Caller responsibility |
Device Memory Type | Memory mapped as device memory, not normal memory | Caller responsibility |
Alignment | Address aligned to 4-byte boundary | Enforced by#[repr(C, align(4))] |
Sources: src/lib.rs(L51 - L60)
Hardware Register Access Safety
The driver uses specific patterns to ensure safe access to hardware registers while maintaining proper volatile semantics.
Volatile Access Pattern
All register operations use volatile access through addr_of!
and addr_of_mut!
macros to prevent undefined behavior:
flowchart TD subgraph Hardware["Hardware"] DR_REGISTER["DR RegisterData Register"] end subgraph subGraph1["Unsafe Block"] ADDR_OF["addr_of!((*self.registers).dr)"] READ_VOLATILE["read_volatile()"] end subgraph subGraph0["Safe Method Call"] METHOD["get_unix_timestamp()"] end ADDR_OF --> READ_VOLATILE METHOD --> ADDR_OF READ_VOLATILE --> DR_REGISTER
Sources: src/lib.rs(L63 - L67) src/lib.rs(L70 - L74)
Register Layout Safety
The Registers
struct uses precise memory layout control to match hardware expectations:
flowchart TD subgraph subGraph1["Layout Attributes"] REPR_C["#[repr(C)]C-compatible layout"] ALIGN_4["#[repr(align(4))]4-byte alignment"] end subgraph subGraph0["Memory Layout"] DR["dr: u32Offset 0x00"] MR["mr: u32Offset 0x04"] LR["lr: u32Offset 0x08"] CR["cr: u8 + paddingOffset 0x0C"] IMSC["imsc: u8 + paddingOffset 0x10"] RIS["ris: u8 + paddingOffset 0x14"] MIS["mis: u8 + paddingOffset 0x18"] ICR["icr: u8 + paddingOffset 0x1C"] end CR --> IMSC DR --> MR IMSC --> RIS LR --> CR MIS --> ICR MR --> LR REPR_C --> DR RIS --> MIS
Sources: src/lib.rs(L15 - L39)
Thread Safety and Concurrency Model
The driver implements explicit thread safety through manual Send
and Sync
trait implementations with documented safety reasoning.
Send Implementation
The Send
trait implementation allows the Rtc
instance to be transferred between threads:
flowchart TD subgraph Hardware["Hardware"] DEVICE["PL031 DeviceDevice Memory"] end subgraph subGraph1["Thread B"] RTC_B["Rtc instance"] ACCESS_B["Hardware access"] end subgraph subGraph0["Thread A"] RTC_A["Rtc instance"] MOVE["move rtc"] end ACCESS_B --> DEVICE MOVE --> RTC_B RTC_A --> MOVE RTC_B --> ACCESS_B
The implementation is safe because device memory can be accessed from any thread context.
Sources: src/lib.rs(L123 - L124)
Sync Implementation
The Sync
trait implementation allows shared references across threads:
flowchart TD subgraph Hardware["Hardware"] REGISTERS["PL031 RegistersRead-only operations"] end subgraph subGraph2["Shared Instance"] RTC_SHARED["Rtc instance"] end subgraph subGraph1["Thread 2"] REF2["&Rtc"] READ2["matched()"] end subgraph subGraph0["Thread 1"] REF1["&Rtc"] READ1["get_unix_timestamp()"] end READ1 --> REGISTERS READ2 --> REGISTERS REF1 --> RTC_SHARED REF2 --> RTC_SHARED
The implementation is safe because shared references only allow read operations on device registers, which can be performed concurrently.
Sources: src/lib.rs(L126 - L128)
Mutation Safety
Methods requiring &mut self
ensure exclusive access for write operations:
Method Category | Reference Type | Thread Safety | Hardware Impact |
---|---|---|---|
Read Operations | &self | Concurrent safe | Read-only register access |
Write Operations | &mut self | Exclusive access | Register modification |
Interrupt Management | &mut self | Exclusive access | Control register writes |
Sources: src/lib.rs(L70 - L120)
Memory Safety Guarantees
The driver provides several layers of memory safety guarantees through its design:
Pointer Validity
Once constructed with a valid base address, all register accesses are guaranteed to be within the mapped MMIO region:
flowchart TD subgraph subGraph1["Runtime Guarantees"] NO_UB["No undefined behaviorAll accesses valid"] ATOMIC_OPS["Atomic register operationsSingle instruction access"] CONSISTENT_STATE["Consistent hardware stateProper ordering"] end subgraph subGraph0["Safety Invariants"] VALID_PTR["Valid base pointerEstablished in new()"] BOUNDED_ACCESS["Bounded register accessWithin Registers struct"] VOLATILE_OPS["Volatile operationsProper device memory handling"] end BOUNDED_ACCESS --> ATOMIC_OPS VALID_PTR --> NO_UB VOLATILE_OPS --> CONSISTENT_STATE
Sources: src/lib.rs(L56 - L60) src/lib.rs(L15 - L39)
Strict Safety Lints
The crate enforces strict safety requirements through compiler lints:
clippy::missing_safety_doc
: All unsafe functions must have safety documentationclippy::undocumented_unsafe_blocks
: All unsafe blocks must have safety commentsunsafe_op_in_unsafe_fn
: Unsafe operations in unsafe functions must be explicit
Sources: src/lib.rs(L4 - L8)
This comprehensive safety model ensures that despite the low-level hardware interaction, the driver maintains Rust's memory safety guarantees while providing efficient concurrent access to the RTC hardware.
Features and Extensions
Relevant source files
This document covers the optional features and extensions available in the arm_pl031 crate. These features provide additional functionality beyond the core RTC driver implementation, allowing developers to choose the appropriate level of abstraction and functionality for their specific use cases.
For information about the core driver implementation and basic functionality, see Core Driver Implementation. For details about configuration and build options, see Development and Contributing.
Feature Overview
The arm_pl031 crate provides a modular architecture where additional functionality can be enabled through feature flags. This design keeps the core driver lightweight while allowing optional enhancements for improved developer experience.
Available Features
Feature | Description | Default | Dependencies |
---|---|---|---|
chrono | High-level date/time operations using chrono crate | Enabled | chrono v0.4.38 |
The crate follows a minimal core approach where the base functionality requires no external dependencies, while optional features add convenience layers on top of the core API.
Feature Architecture
flowchart TD subgraph subGraph3["Hardware Layer"] PL031_HW["PL031 Hardware"] end subgraph subGraph2["Core Driver"] CORE_API["Core RTC API"] UNIX_TS["Unix Timestamp Operations"] REGISTERS["Register Access"] end subgraph subGraph1["Feature Extensions"] CHRONO_API["chrono Feature API"] DATETIME["DateTime Operations"] end subgraph subGraph0["Application Layer"] APP["Application Code"] end APP --> CHRONO_API APP --> CORE_API CHRONO_API --> DATETIME CORE_API --> UNIX_TS DATETIME --> UNIX_TS REGISTERS --> PL031_HW UNIX_TS --> REGISTERS
Sources: Cargo.toml(L14 - L19) src/chrono.rs(L1 - L26)
Feature Integration Architecture
The feature system in arm_pl031 uses Rust's conditional compilation to provide optional functionality without affecting the core driver when features are disabled.
Chrono Feature Integration
flowchart TD subgraph subGraph3["Core Dependencies"] CHRONO_CRATE["chrono = '0.4.38'"] DATETIME_UTC["DateTime"] TRY_FROM_INT["TryFromIntError"] end subgraph subGraph2["API Methods"] GET_TIME["get_time()"] SET_TIME["set_time()"] SET_MATCH["set_match()"] end subgraph subGraph1["Implementation Modules"] LIB_RS["src/lib.rs"] CHRONO_RS["src/chrono.rs"] CHRONO_IMPL["Rtc impl block"] end subgraph subGraph0["Feature Flag System"] CARGO_FEATURES["Cargo.toml features"] CHRONO_FLAG["chrono = ['dep:chrono']"] DEFAULT_FLAG["default = ['chrono']"] end CARGO_FEATURES --> CHRONO_FLAG CARGO_FEATURES --> DEFAULT_FLAG CHRONO_CRATE --> DATETIME_UTC CHRONO_CRATE --> TRY_FROM_INT CHRONO_FLAG --> CHRONO_CRATE CHRONO_FLAG --> CHRONO_RS CHRONO_IMPL --> GET_TIME CHRONO_IMPL --> SET_MATCH CHRONO_IMPL --> SET_TIME CHRONO_RS --> CHRONO_IMPL GET_TIME --> DATETIME_UTC SET_MATCH --> TRY_FROM_INT SET_TIME --> TRY_FROM_INT
Sources: Cargo.toml(L15 - L19) src/chrono.rs(L1 - L4)
API Layer Mapping
The feature system creates distinct API layers that build upon each other, allowing developers to choose the appropriate abstraction level.
Method Resolution and Type Conversion
flowchart TD subgraph subGraph2["Type Conversions"] UTC_TIMESTAMP["Utc.timestamp_opt()"] TIMESTAMP_TRY_INTO["timestamp().try_into()"] INTO_CONVERSION["into()"] end subgraph subGraph1["Core API (always available)"] GET_UNIX["get_unix_timestamp() -> u32"] SET_UNIX["set_unix_timestamp(u32)"] SET_MATCH_TS["set_match_timestamp(u32)"] end subgraph subGraph0["High-Level API (chrono feature)"] GET_TIME_CHRONO["get_time() -> DateTime"] SET_TIME_CHRONO["set_time(DateTime) -> Result<(), TryFromIntError>"] SET_MATCH_CHRONO["set_match(DateTime) -> Result<(), TryFromIntError>"] end GET_TIME_CHRONO --> GET_UNIX GET_TIME_CHRONO --> UTC_TIMESTAMP GET_UNIX --> INTO_CONVERSION SET_MATCH_CHRONO --> TIMESTAMP_TRY_INTO SET_TIME_CHRONO --> TIMESTAMP_TRY_INTO TIMESTAMP_TRY_INTO --> SET_MATCH_TS TIMESTAMP_TRY_INTO --> SET_UNIX
Sources: src/chrono.rs(L6 - L25)
Configuration and Build Integration
The feature system integrates with Cargo's dependency resolution and compilation process to provide clean separation between core and optional functionality.
Dependency Management
The chrono feature demonstrates careful dependency management:
- Optional Dependency: The chrono crate is marked as
optional = true
in Cargo.toml(L15) - No Default Features: chrono is imported with
default-features = false
to maintain minimal footprint - Feature Gate: The
dep:chrono
syntax ensures the dependency is only included when the feature is enabled - Default Enabled: The feature is enabled by default for convenience but can be disabled for minimal builds
Build Configuration Matrix
Feature State | Dependencies | API Available | Use Case |
---|---|---|---|
--no-default-features | None | Core timestamp API only | Minimal embedded systems |
--features chrono | chrono v0.4.38 | Core + DateTime API | Standard embedded applications |
Default | chrono v0.4.38 | Core + DateTime API | General purpose usage |
Sources: Cargo.toml(L14 - L19)
Error Handling Strategy
The feature extensions implement consistent error handling patterns that integrate with the core driver's safety model.
Error Type Integration
The chrono feature introduces TryFromIntError
for handling timestamp conversion failures that can occur when converting between DateTime<Utc>
(using i64
timestamps) and the PL031's u32
register format.
Error Flow Pattern:
DateTime<Utc>
providestimestamp()
returningi64
- Conversion to
u32
viatry_into()
may fail for out-of-range values - Methods return
Result<(), TryFromIntError>
to propagate conversion errors - Core driver methods remain infallible for valid
u32
inputs
This design ensures that type safety is maintained across the feature boundary while preserving the core driver's simple error model.
Sources: src/chrono.rs(L3) src/chrono.rs(L15 - L17) src/chrono.rs(L22 - L24)
Chrono Integration
Relevant source files
This document covers the optional chrono
feature integration that provides high-level DateTime operations for the ARM PL031 RTC driver. This feature extends the core Unix timestamp API with convenient date and time handling using the chrono
crate.
For information about the core driver functionality and Unix timestamp operations, see Core Driver Implementation. For details about enabling and configuring the chrono feature, see Feature Configuration.
Purpose and Scope
The chrono integration provides a type-safe, ergonomic API layer over the core RTC driver by converting between Unix timestamps and DateTime<Utc>
objects. This feature is enabled by default but can be disabled for minimal deployments that only need raw timestamp operations.
The integration handles timezone considerations by standardizing on UTC, performs bounds checking for timestamp conversions, and maintains the same interrupt and match functionality as the core API while operating on DateTime objects.
API Overview
The chrono integration extends the Rtc
struct with three additional methods that mirror the core timestamp operations but operate on DateTime<Utc>
types.
DateTime Operations
Method | Core Equivalent | Purpose |
---|---|---|
get_time() | get_unix_timestamp() | Read current time as DateTime |
set_time() | set_unix_timestamp() | Set current time from DateTime |
set_match() | set_match_timestamp() | Set match time for interrupts |
Method Implementation Details
Time Reading
The get_time()
method provides a safe conversion from the hardware's 32-bit Unix timestamp to a proper DateTime object:
flowchart TD RTC["Rtc::get_time()"] CORE["get_unix_timestamp()"] CONVERT["Utc.timestamp_opt()"] UNWRAP["unwrap()"] DATETIME["DateTime"] CONVERT --> UNWRAP CORE --> CONVERT RTC --> CORE UNWRAP --> DATETIME
Chrono DateTime Conversion Flow
The conversion uses chrono::Utc::timestamp_opt()
with nanoseconds set to 0, since the PL031 hardware only provides second-level precision. The unwrap()
is safe because valid PL031 timestamps always fall within chrono's supported range.
Sources: src/chrono.rs(L6 - L10)
Time Setting
The set_time()
method handles the reverse conversion with proper error handling for out-of-bounds timestamps:
flowchart TD INPUT["DateTime"] TIMESTAMP["time.timestamp()"] CONVERT["try_into()?"] SUCCESS["set_unix_timestamp()"] ERROR["TryFromIntError"] CONVERT --> ERROR CONVERT --> SUCCESS INPUT --> TIMESTAMP TIMESTAMP --> CONVERT
DateTime to Timestamp Conversion with Error Handling
The method returns Result<(), TryFromIntError>
to handle cases where the DateTime represents a time outside the PL031's supported range (Unix timestamps that don't fit in 32 bits).
Sources: src/chrono.rs(L12 - L18)
Match Register Integration
The set_match()
method extends the interrupt functionality to work with DateTime objects:
flowchart TD subgraph subGraph2["Hardware Layer"] PL031_MR["PL031 Match Register"] INTERRUPT_GEN["Interrupt Generation"] end subgraph subGraph1["Core Driver Layer"] SET_MATCH_TS["set_match_timestamp(u32)"] MATCH_REG["write_volatile(MR)"] end subgraph subGraph0["Chrono API Layer"] SET_MATCH["set_match(DateTime)"] CONVERT_MATCH["match_time.timestamp().try_into()?"] end CONVERT_MATCH --> SET_MATCH_TS MATCH_REG --> PL031_MR PL031_MR --> INTERRUPT_GEN SET_MATCH --> CONVERT_MATCH SET_MATCH_TS --> MATCH_REG
Match Register DateTime Integration
This allows applications to schedule interrupts using natural DateTime objects rather than calculating Unix timestamps manually.
Sources: src/chrono.rs(L20 - L26)
Error Handling and Type Safety
Conversion Error Types
The chrono integration uses TryFromIntError
to handle timestamp conversion failures. This occurs when attempting to convert a DateTime
that represents a time outside the range of a 32-bit Unix timestamp (before 1970 or after 2106).
flowchart TD DATETIME["DateTime"] TIMESTAMP["i64 timestamp"] CHECK["fits in u32?"] SUCCESS["u32 timestamp"] ERROR["TryFromIntError"] CORE_API["Core RTC API"] CHECK --> ERROR CHECK --> SUCCESS DATETIME --> TIMESTAMP SUCCESS --> CORE_API TIMESTAMP --> CHECK
Timestamp Range Validation
Applications should handle these errors appropriately, either by validating DateTime inputs or providing fallback behavior for out-of-range times.
UTC Timezone Handling
The integration standardizes on UTC timezone to avoid complex timezone conversion logic in embedded systems. This ensures consistent behavior across different deployment environments and simplifies the API surface.
Sources: src/chrono.rs(L1 - L3)
Integration with Core Functionality
The chrono methods are thin wrappers around the core driver functionality, maintaining the same safety properties and hardware access patterns while providing a more convenient interface.
flowchart TD subgraph Hardware["Hardware"] PL031["ARM PL031 RTC"] end subgraph subGraph2["Core Driver (lib.rs)"] GET_UNIX["get_unix_timestamp()"] SET_UNIX["set_unix_timestamp()"] SET_MATCH_UNIX["set_match_timestamp()"] REGISTERS["Register Operations"] end subgraph subGraph1["Chrono Feature (chrono.rs)"] GET_TIME["get_time()"] SET_TIME["set_time()"] SET_MATCH_CHRONO["set_match()"] end subgraph subGraph0["Application Code"] APP_CHRONO["DateTime Operations"] APP_CORE["Unix Timestamp Operations"] end APP_CHRONO --> GET_TIME APP_CHRONO --> SET_MATCH_CHRONO APP_CHRONO --> SET_TIME APP_CORE --> GET_UNIX APP_CORE --> SET_MATCH_UNIX APP_CORE --> SET_UNIX GET_TIME --> GET_UNIX GET_UNIX --> REGISTERS REGISTERS --> PL031 SET_MATCH_CHRONO --> SET_MATCH_UNIX SET_MATCH_UNIX --> REGISTERS SET_TIME --> SET_UNIX SET_UNIX --> REGISTERS
Chrono Integration Architecture
This design allows applications to choose the appropriate abstraction level: high-level DateTime operations for convenience, or low-level timestamp operations for performance-critical code.
Sources: src/chrono.rs(L1 - L27)
Feature Configuration
Relevant source files
This document covers the available feature flags, no_std
compatibility settings, and build configuration options for the arm_pl031
crate. It explains how to customize the driver's functionality through feature selection and target-specific compilation settings.
For information about the chrono-specific functionality enabled by features, see Chrono Integration. For build and testing procedures across different targets, see Building and Testing.
Feature Flags Overview
The arm_pl031
crate provides a minimal set of feature flags to control optional functionality while maintaining a lightweight core driver suitable for embedded environments.
Available Features
Feature | Description | Default | Dependencies |
---|---|---|---|
chrono | Enables DateTime integration with the chrono crate | ✓ Enabled | chrono = "0.4.38" |
Feature Dependencies Diagram
flowchart TD subgraph subGraph1["Optional Enhancement"] CHRONO_IMPL["DateTime<Utc> support"] end subgraph subGraph0["Always Available"] BASIC_API["Unix timestamp API"] INTERRUPT_API["Interrupt management"] REGISTER_API["Register operations"] end DEFAULT["default features"] CHRONO_FEAT["chrono feature"] CHRONO_DEP["chrono crate v0.4.38"] CORE["Core RTC Driver"] NO_CHRONO["without chrono feature"] CHRONO_DEP --> CHRONO_IMPL CHRONO_FEAT --> CHRONO_DEP CHRONO_IMPL --> CORE CORE --> BASIC_API CORE --> INTERRUPT_API CORE --> REGISTER_API DEFAULT --> CHRONO_FEAT NO_CHRONO --> CORE
Sources: Cargo.toml(L17 - L19)
Core Driver (Always Available)
The core functionality remains available regardless of feature selection:
- Unix timestamp operations via
get_unix_timestamp()
andset_unix_timestamp()
- Interrupt management through match registers
- Direct register access for all PL031 hardware features
- Memory-mapped I/O operations
Chrono Feature
When the chrono
feature is enabled (default), the driver provides additional convenience methods:
get_time()
returnsResult<DateTime<Utc>, chrono::ParseError>
set_time()
acceptsDateTime<Utc>
parameters- Automatic conversion between Unix timestamps and RFC 3339 formatted strings
Sources: Cargo.toml(L15) Cargo.toml(L18)
Default Configuration
The crate ships with the chrono
feature enabled by default to provide the most user-friendly experience for common use cases.
Default Feature Resolution
flowchart TD USER_PROJECT["User's Cargo.toml"] DEPENDENCY_DECL["arm_pl031 dependency"] DEFAULT_RESOLUTION["Resolve default features"] CHRONO_ENABLED["chrono = true"] ENHANCED_API["Enhanced DateTime API"] OPT_OUT["default-features = false"] MINIMAL_CONFIG["Core-only build"] UNIX_TIMESTAMP_ONLY["Unix timestamp API only"] CHRONO_ENABLED --> ENHANCED_API DEFAULT_RESOLUTION --> CHRONO_ENABLED DEPENDENCY_DECL --> DEFAULT_RESOLUTION DEPENDENCY_DECL --> OPT_OUT MINIMAL_CONFIG --> UNIX_TIMESTAMP_ONLY OPT_OUT --> MINIMAL_CONFIG USER_PROJECT --> DEPENDENCY_DECL
To use the default configuration:
[dependencies]
arm_pl031 = "0.2.1"
To disable default features:
[dependencies]
arm_pl031 = { version = "0.2.1", default-features = false }
Sources: Cargo.toml(L19)
No-std Compatibility
The arm_pl031
crate is designed for no_std
environments and embedded systems. All features maintain no_std
compatibility.
No-std Design Principles
Component | No-std Status | Implementation Notes |
---|---|---|
Core driver | ✓ Full support | Usesvolatileoperations, no heap allocation |
Chrono integration | ✓ Compatible | Useschronowithdefault-features = false |
Error handling | ✓ Compatible | UsesResulttypes, no panic in normal operation |
Memory management | ✓ Compatible | Zero dynamic allocation, stack-based operations |
No-std Compatibility Architecture
flowchart TD subgraph subGraph2["Hardware Layer"] MMIO_REGISTERS["Memory-Mapped Registers"] PL031_HW["ARM PL031 Hardware"] end subgraph subGraph1["arm_pl031 Components"] CORE_DRIVER["Core RTC Driver"] CHRONO_NOSTD["chrono (no default features)"] VOLATILE_OPS["Volatile MMIO Operations"] RESULT_TYPES["Result-based Error Handling"] end subgraph subGraph0["no_std Environment"] EMBEDDED_TARGET["Embedded Target"] NO_HEAP["No Heap Allocation"] NO_STD_LIB["No Standard Library"] end CORE_DRIVER --> CHRONO_NOSTD CORE_DRIVER --> VOLATILE_OPS EMBEDDED_TARGET --> CORE_DRIVER MMIO_REGISTERS --> PL031_HW NO_HEAP --> VOLATILE_OPS NO_STD_LIB --> RESULT_TYPES VOLATILE_OPS --> MMIO_REGISTERS
Sources: Cargo.toml(L12) Cargo.toml(L15)
Build Configuration Options
Target Architecture Support
The driver supports multiple target architectures while maintaining consistent functionality:
Target Triple | Purpose | Feature Support |
---|---|---|
aarch64-unknown-none-softfloat | Bare metal ARM64 | All features |
x86_64-unknown-none | Bare metal x86_64 (testing) | All features |
riscv64gc-unknown-none-elf | RISC-V embedded | All features |
x86_64-unknown-linux-gnu | Linux development/testing | All features |
Feature Selection Examples
Minimal embedded build:
[dependencies]
arm_pl031 = { version = "0.2.1", default-features = false }
Full-featured embedded build:
[dependencies]
arm_pl031 = "0.2.1" # Includes chrono by default
Selective feature enabling:
[dependencies]
arm_pl031 = { version = "0.2.1", default-features = false, features = ["chrono"] }
Dependency Management
The chrono dependency is configured for embedded compatibility:
- Version constraint:
0.4.38
default-features = false
to avoid std dependencies- Optional inclusion based on feature flags
Dependency Resolution Flow
flowchart TD CARGO_TOML["Cargo.toml feature selection"] FEATURE_CHECK["chrono feature enabled?"] CHRONO_DEP["Include chrono 0.4.38"] MINIMAL_BUILD["Core driver only"] NO_DEFAULT_FEATURES["chrono with default-features = false"] COMPATIBLE_BUILD["no_std compatible build"] TARGET_COMPILATION["Target-specific compilation"] CARGO_TOML --> FEATURE_CHECK CHRONO_DEP --> NO_DEFAULT_FEATURES COMPATIBLE_BUILD --> TARGET_COMPILATION FEATURE_CHECK --> CHRONO_DEP FEATURE_CHECK --> MINIMAL_BUILD MINIMAL_BUILD --> COMPATIBLE_BUILD NO_DEFAULT_FEATURES --> COMPATIBLE_BUILD
Sources: Cargo.toml(L14 - L15) Cargo.toml(L17 - L19)
Configuration Best Practices
For Embedded Systems
- Use
default-features = false
for smallest binary size - Enable only required features to minimize dependencies
- Consider target-specific optimizations in your project's Cargo.toml
For Development and Testing
- Use default features for convenient DateTime handling
- Enable all features during development for full API access
- Use hosted targets like
x86_64-unknown-linux-gnu
for rapid development
For Production Deployment
- Carefully evaluate which features are needed for your use case
- Test with the exact feature configuration used in production
- Document feature selections in your project's dependency specifications
Sources: Cargo.toml(L1 - L20)
Development and Contributing
Relevant source files
This page provides comprehensive information for developers who want to build, test, modify, or contribute to the arm_pl031 crate. It covers the development workflow, build system, testing procedures, and contribution guidelines.
For information about using the driver in your applications, see Getting Started. For details about the crate's API and functionality, see Core Driver Implementation and Features and Extensions.
Development Workflow Overview
The arm_pl031 project follows a standard Rust development workflow with automated quality assurance through GitHub Actions. The development process emphasizes cross-platform compatibility, code quality, and comprehensive testing.
Development Pipeline Architecture
flowchart TD subgraph Deployment["Deployment"] MERGE["Merge to Main"] PAGES["GitHub Pages"] RELEASE["Crates.io Release"] end subgraph subGraph2["Quality Gates"] FORMAT_CHECK["Format Validation"] LINT_CHECK["Clippy Linting"] BUILD_CHECK["Cross-compilation"] TEST_CHECK["Test Execution"] DOC_CHECK["Documentation Generation"] end subgraph subGraph1["Continuous Integration"] PR["Pull Request"] MATRIX["CI Matrix Build"] TARGETS["Multi-target Validation"] TESTS["Unit Tests"] DOCS["Documentation Build"] end subgraph subGraph0["Development Phase"] DEV["Developer Changes"] LOCAL["Local Development"] FMT["cargo fmt"] CLIPPY["cargo clippy"] end BUILD_CHECK --> TARGETS CLIPPY --> PR DEV --> LOCAL DOCS --> MERGE DOC_CHECK --> DOCS FMT --> CLIPPY FORMAT_CHECK --> TARGETS LINT_CHECK --> TARGETS LOCAL --> FMT MATRIX --> BUILD_CHECK MATRIX --> DOC_CHECK MATRIX --> FORMAT_CHECK MATRIX --> LINT_CHECK MATRIX --> TEST_CHECK MERGE --> PAGES MERGE --> RELEASE PR --> MATRIX TARGETS --> MERGE TESTS --> MERGE TEST_CHECK --> TESTS
Sources: .github/workflows/ci.yml(L1 - L58)
Build System and Target Support
The project supports multiple architectures and build configurations to ensure compatibility across different embedded and hosted environments.
Supported Build Targets
Target | Architecture | Environment | Primary Use Case |
---|---|---|---|
x86_64-unknown-linux-gnu | x86_64 | Linux hosted | Development, testing |
x86_64-unknown-none | x86_64 | Bare metal | Testing no_std compatibility |
riscv64gc-unknown-none-elf | RISC-V 64-bit | Bare metal | RISC-V embedded systems |
aarch64-unknown-none-softfloat | ARM64 | Bare metal | ARM64 embedded systems |
The build matrix validates all targets with both minimal and full feature sets:
# Build with no features (core functionality only)
cargo build --target <target> --no-default-features
# Build with all features (including chrono integration)
cargo build --target <target> --all-features
Feature Configuration Matrix
flowchart TD subgraph subGraph2["API Surface"] BASIC_API["Basic RTC APIUnix timestamps"] CHRONO_API["DateTime APIchrono integration"] end subgraph Dependencies["Dependencies"] NO_DEPS["No Dependencies"] CHRONO_DEP["chrono = 0.4.38no default features"] end subgraph subGraph0["Feature Combinations"] CORE["Core Only--no-default-features"] DEFAULT["Default Buildchrono enabled"] ALL["All Features--all-features"] end ALL --> CHRONO_DEP CHRONO_DEP --> BASIC_API CHRONO_DEP --> CHRONO_API CORE --> NO_DEPS DEFAULT --> CHRONO_DEP NO_DEPS --> BASIC_API
Sources: Cargo.toml(L14 - L19) .github/workflows/ci.yml(L26 - L29)
Code Quality and Validation
Automated Quality Checks
The CI pipeline enforces several quality standards that must pass before code can be merged:
- Code Formatting: All code must be formatted with
rustfmt
using default settings
cargo fmt --all -- --check
- Linting: Code must pass
clippy
analysis with minimal warnings
cargo clippy --target <target> --all-features -- -A clippy::new_without_default
- Cross-compilation: Code must compile successfully on all supported targets
- Unit Testing: Tests must pass on the primary development target (
x86_64-unknown-linux-gnu
)
Testing Strategy
flowchart TD subgraph subGraph2["Validation Scope"] CORE_FUNC["Core Functionality"] CHRONO_FEAT["Chrono Feature"] NO_STD["no_std Compatibility"] SAFETY["Memory Safety"] end subgraph subGraph1["Test Execution Environment"] LINUX["x86_64-unknown-linux-gnuFull test execution"] EMBEDDED["Embedded TargetsCompilation only"] end subgraph subGraph0["Test Categories"] UNIT["Unit Testscargo test"] COMPILE["Compilation TestsCross-platform builds"] DOC["Documentation Testsrustdoc validation"] end COMPILE --> EMBEDDED DOC --> LINUX EMBEDDED --> NO_STD EMBEDDED --> SAFETY LINUX --> CHRONO_FEAT LINUX --> CORE_FUNC UNIT --> LINUX
Sources: .github/workflows/ci.yml(L30 - L32) .github/workflows/ci.yml(L26 - L29)
Documentation and API Standards
Documentation Requirements
The project maintains strict documentation standards enforced through the CI pipeline:
- Missing Documentation: All public APIs must be documented (
-D missing-docs
) - Broken Links: Intra-doc links must be valid (
-D rustdoc::broken_intra_doc_links
) - API Examples: Public functions should include usage examples
- Safety Documentation: All
unsafe
functions must document their safety requirements
Documentation Deployment
Documentation is automatically built and deployed to GitHub Pages on every push to the main branch:
flowchart TD subgraph subGraph1["Access Points"] DOCS_RS["docs.rsOfficial documentation"] GH_PAGES["GitHub PagesDevelopment docs"] end subgraph subGraph0["Documentation Pipeline"] BUILD["cargo doc--no-deps --all-features"] INDEX["Generate index.htmlAuto-redirect"] DEPLOY["GitHub Pagesgh-pages branch"] end BUILD --> DOCS_RS BUILD --> INDEX DEPLOY --> GH_PAGES INDEX --> DEPLOY
Sources: .github/workflows/ci.yml(L34 - L57) .github/workflows/ci.yml(L42)
Version Management and API Evolution
Semantic Versioning Policy
The project follows semantic versioning with careful attention to API stability:
- Patch versions (0.2.0 → 0.2.1): Bug fixes, new features that don't break existing APIs
- Minor versions (0.1.x → 0.2.0): New features, may include breaking changes in pre-1.0 releases
- Major versions (future 1.x → 2.x): Significant breaking changes
API Evolution History
Breaking Changes Documentation
Major API changes are carefully documented in the changelog with migration guidance:
Version | Breaking Change | Rationale | Migration Path |
---|---|---|---|
v0.2.0 | get_unix_timestamp()returnsu32 | Match hardware register size | Castu64tou32if needed |
v0.2.0 | Rtc::new()isunsafe | Requires valid MMIO pointer | Addunsafeblock with safety comment |
v0.2.0 | set_unix_timestamp()takes&mut self | Reflects write operation semantics | Use mutable reference |
Sources: CHANGELOG.md(L1 - L26)
Contributing Guidelines
Development Environment Setup
- Rust Toolchain: Install nightly Rust with required components
rustup toolchain install nightly
rustup component add rust-src clippy rustfmt
- Target Support: Add required compilation targets
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
- Development Workflow: Follow the standard validation steps
cargo fmt --all # Format code
cargo clippy --all-features # Lint code
cargo test # Run tests
cargo build --no-default-features # Test minimal build
Pull Request Process
flowchart TD subgraph Integration["Integration"] APPROVAL["Review Approval"] MERGE["Merge to Main"] CLEANUP["Branch Cleanup"] end subgraph Submission["Submission"] PR_CREATE["Create Pull Request"] CI_RUN["CI Pipeline Execution"] REVIEW["Code Review"] end subgraph Validation["Validation"] LOCAL_TEST["Local Testing"] FORMAT["Code Formatting"] LINT["Clippy Validation"] end subgraph Preparation["Preparation"] FORK["Fork Repository"] BRANCH["Create Feature Branch"] CHANGES["Implement Changes"] end APPROVAL --> MERGE BRANCH --> CHANGES CHANGES --> LOCAL_TEST CI_RUN --> REVIEW FORK --> BRANCH FORMAT --> LINT LINT --> PR_CREATE LOCAL_TEST --> FORMAT MERGE --> CLEANUP PR_CREATE --> CI_RUN REVIEW --> APPROVAL
Code Standards
- Safety: All
unsafe
code must be justified and documented - Documentation: Public APIs require comprehensive documentation
- Testing: New functionality should include appropriate tests
- Compatibility: Changes must not break existing supported targets
- Feature Flags: Optional functionality should be feature-gated appropriately
Sources: .github/workflows/ci.yml(L1 - L58) Cargo.toml(L1 - L20)
Building and Testing
Relevant source files
This section covers the build process, multi-target support, continuous integration pipeline, and testing procedures for the arm_pl031
crate. It provides developers with comprehensive information about how to build the crate for different target architectures, understand the automated testing infrastructure, and contribute to the project with confidence.
For information about setting up the development environment and contribution guidelines, see Development Environment. For details about the crate's API evolution and versioning, see API Evolution and Changelog.
Build System Overview
The arm_pl031
crate uses Cargo as its primary build system and is configured for cross-platform compilation with no_std
compatibility. The crate supports both embedded and hosted environments through careful dependency management and feature flags.
Build Configuration
The build configuration is defined in [Cargo.toml(L1 - L20) ](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/
Cargo.toml#L1-L20) and emphasizes embedded-first design:
Configuration | Value | Purpose |
---|---|---|
Edition | 2021 | Modern Rust features and patterns |
Categories | os,hardware-support,no-std | Embedded systems focus |
Default Features | chrono | DateTime support enabled by default |
Optional Dependencies | chrono = "0.4.38" | DateTime integration without std |
flowchart TD subgraph subGraph3["Build Outputs"] LIB_NOSTD["libarm_pl031.rlib (no_std)"] LIB_CHRONO["libarm_pl031.rlib (with chrono)"] DOCS["target/doc/"] end subgraph subGraph2["Target Compilation"] X86_LINUX["x86_64-unknown-linux-gnu"] X86_NONE["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] AARCH64["aarch64-unknown-none-softfloat"] end subgraph subGraph1["Feature Resolution"] NO_FEATURES["--no-default-features"] DEFAULT["default = [chrono]"] ALL_FEATURES["--all-features"] end subgraph subGraph0["Build Inputs"] CARGO["Cargo.toml"] SRC["src/lib.rs"] CHRONO_SRC["src/chrono.rs"] end AARCH64 --> LIB_NOSTD CARGO --> ALL_FEATURES CARGO --> DEFAULT CARGO --> NO_FEATURES CHRONO_SRC --> ALL_FEATURES CHRONO_SRC --> DEFAULT DEFAULT --> AARCH64 DEFAULT --> RISCV DEFAULT --> X86_LINUX DEFAULT --> X86_NONE NO_FEATURES --> AARCH64 NO_FEATURES --> RISCV NO_FEATURES --> X86_LINUX NO_FEATURES --> X86_NONE RISCV --> LIB_NOSTD SRC --> DEFAULT SRC --> NO_FEATURES X86_LINUX --> DOCS X86_LINUX --> LIB_CHRONO X86_LINUX --> LIB_NOSTD X86_NONE --> LIB_NOSTD
Build System Architecture
Sources: [Cargo.toml(L1 - L20) ](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/
Cargo.toml#L1-L20)
Multi-Target Support
The crate is designed to work across multiple target architectures, with particular emphasis on embedded no_std
environments. The CI system validates compatibility across four primary target configurations.
Supported Target Architectures
flowchart TD subgraph subGraph5["Validation Steps"] FMT_CHECK["cargo fmt --all -- --check"] CLIPPY["cargo clippy --all-features"] UNIT_TEST["cargo test"] end subgraph subGraph4["Build Commands"] NO_FEAT["cargo build --no-default-features"] ALL_FEAT["cargo build --all-features"] end subgraph subGraph3["ARM64 Embedded"] AARCH64["aarch64-unknown-none-softfloat"] end subgraph subGraph2["RISC-V Embedded"] RISCV["riscv64gc-unknown-none-elf"] end subgraph subGraph1["Bare Metal x86"] X86_NONE["x86_64-unknown-none"] end subgraph subGraph0["Hosted Targets"] LINUX["x86_64-unknown-linux-gnu"] end ALL_FEAT --> AARCH64 ALL_FEAT --> LINUX ALL_FEAT --> RISCV ALL_FEAT --> X86_NONE CLIPPY --> LINUX FMT_CHECK --> LINUX NO_FEAT --> AARCH64 NO_FEAT --> LINUX NO_FEAT --> RISCV NO_FEAT --> X86_NONE UNIT_TEST --> LINUX
Multi-Target Build Matrix
Target | Purpose | Features Tested | Test Execution |
---|---|---|---|
x86_64-unknown-linux-gnu | Hosted development | All features, unit tests | Full test suite |
x86_64-unknown-none | Bare metal x86 | Core functionality | Build verification |
riscv64gc-unknown-none-elf | RISC-V embedded | Core functionality | Build verification |
aarch64-unknown-none-softfloat | ARM64 embedded | Core functionality | Build verification |
Sources: [.github/workflows/ci.yml(L12) ](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/
.github/workflows/ci.yml#L12-L12) [.github/workflows/ci.yml(L25 - L29) ](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/
.github/workflows/ci.yml#L25-L29)
Continuous Integration Pipeline
The CI pipeline is implemented using GitHub Actions and provides comprehensive validation across multiple dimensions: code quality, build compatibility, and documentation generation.
CI Workflow Structure
flowchart TD subgraph Documentation["Documentation"] DOC_BUILD["cargo doc --no-deps --all-features"] DEPLOY["JamesIves/github-pages-deploy-action@v4"] end subgraph Testing["Testing"] UNIT_TEST["cargo test (x86_64-linux only)"] end subgraph subGraph3["Build Validation"] BUILD_NO["cargo build --no-default-features"] BUILD_ALL["cargo build --all-features"] end subgraph subGraph2["Quality Checks"] VERSION["rustc --version --verbose"] FORMAT["cargo fmt --all -- --check"] LINT["cargo clippy --all-features"] end subgraph subGraph1["CI Job Matrix"] RUST_VER["rust-toolchain: nightly"] TARGET_MATRIX["4 target architectures"] end subgraph subGraph0["Trigger Events"] PUSH["git push"] PR["pull_request"] end BUILD_ALL --> UNIT_TEST BUILD_NO --> BUILD_ALL DOC_BUILD --> DEPLOY FORMAT --> LINT LINT --> BUILD_NO PR --> RUST_VER PUSH --> RUST_VER RUST_VER --> DOC_BUILD RUST_VER --> TARGET_MATRIX TARGET_MATRIX --> VERSION VERSION --> FORMAT
GitHub Actions CI Pipeline
Toolchain and Components
The CI system uses Rust nightly toolchain with specific components required for comprehensive validation:
Component | Purpose | Usage |
---|---|---|
rust-src | Source code for cross-compilation | Required forno_stdtargets |
clippy | Lint analysis | Code quality enforcement |
rustfmt | Code formatting | Style consistency |
nightlytoolchain | Latest Rust features | Embedded development support |
Sources: [.github/workflows/ci.yml(L17 - L19) ](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/
.github/workflows/ci.yml#L17-L19)
Testing Strategy
The testing approach balances comprehensive validation with practical constraints of embedded development, where some targets cannot execute tests directly.
Test Execution Matrix
flowchart TD subgraph subGraph2["Test Commands"] FMT_CMD["cargo fmt --all -- --check"] CLIPPY_CMD["cargo clippy --target TARGET --all-features"] BUILD_CMD["cargo build --target TARGET"] TEST_CMD["cargo test --target TARGET -- --nocapture"] end subgraph subGraph1["Target Scope"] ALL_TARGETS["All 4 targets"] LINUX_ONLY["x86_64-unknown-linux-gnu only"] end subgraph subGraph0["Test Categories"] FORMAT_TEST["Format Validation"] LINT_TEST["Clippy Analysis"] BUILD_TEST["Compilation Test"] UNIT_TEST["Unit Test Execution"] end ALL_TARGETS --> BUILD_CMD ALL_TARGETS --> CLIPPY_CMD ALL_TARGETS --> FMT_CMD BUILD_TEST --> ALL_TARGETS FORMAT_TEST --> ALL_TARGETS LINT_TEST --> ALL_TARGETS LINUX_ONLY --> TEST_CMD UNIT_TEST --> LINUX_ONLY
Testing Architecture by Target
Quality Assurance Steps
The CI pipeline enforces multiple quality gates to ensure code reliability and maintainability:
- Code Formatting: Enforced via [
cargo fmt --all -- --check
](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/cargo fmt --all -- --check
) - Lint Analysis: Comprehensive clippy checks with [
-A clippy](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/
-A clippy#LNaN-LNaN) exception - Build Verification: Both minimal and full feature builds for each target
- Unit Testing: Executed only on
x86_64-unknown-linux-gnu
with--nocapture
for detailed output
Sources: [.github/workflows/ci.yml(L22 - L32) ](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/
.github/workflows/ci.yml#L22-L32)
Documentation Building and Deployment
The documentation system automatically generates and deploys API documentation to GitHub Pages, ensuring developers always have access to current documentation.
Documentation Pipeline
sequenceDiagram participant Developer as "Developer" participant GitHubActions as "GitHub Actions" participant cargodoc as "cargo doc" participant GitHubPages as "GitHub Pages" Developer ->> GitHubActions: "Push to main branch" GitHubActions ->> cargodoc: "cargo doc --no-deps --all-features" Note over cargodoc: "RUSTDOCFLAGS=-D broken_intra_doc_links" cargodoc ->> cargodoc: "Generate target/doc/" cargodoc ->> cargodoc: "Create redirect index.html" GitHubActions ->> GitHubPages: "Deploy via JamesIves/github-pages-deploy-action@v4" Note over GitHubPages: "Single commit deployment to gh-pages branch"
Documentation Deployment Workflow
Documentation Configuration
The documentation build process includes several quality controls:
Setting | Value | Purpose |
---|---|---|
RUSTDOCFLAGS | -D rustdoc::broken_intra_doc_links -D missing-docs | Enforce documentation completeness |
--no-deps | Skip dependency docs | Focus on crate-specific documentation |
--all-features | Include all features | Document complete API surface |
single-commit | True | Clean deployment history |
The system automatically generates a redirect index page using the crate name extracted from [cargo tree
](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/cargo tree
) ensuring seamless navigation to the main documentation.
Sources: [.github/workflows/ci.yml(L34 - L58) ](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/
.github/workflows/ci.yml#L34-L58) [.github/workflows/ci.yml(L42) ](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/
.github/workflows/ci.yml#L42-L42) [.github/workflows/ci.yml(L48 - L50) ](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/
.github/workflows/ci.yml#L48-L50)
Build Commands Reference
For local development and testing, the following commands replicate the CI environment:
Basic Build Commands
# Minimal build (no chrono feature)
cargo build --no-default-features
# Full build (all features)
cargo build --all-features
# Target-specific build
cargo build --target aarch64-unknown-none-softfloat
Quality Assurance Commands
# Format check
cargo fmt --all -- --check
# Lint analysis
cargo clippy --all-features -- -A clippy::new_without_default
# Unit tests
cargo test -- --nocapture
Documentation Commands
# Generate documentation
cargo doc --no-deps --all-features
# Open documentation locally
cargo doc --no-deps --all-features --open
Sources: [.github/workflows/ci.yml(L23 - L32) ](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/
.github/workflows/ci.yml#L23-L32) [.github/workflows/ci.yml(L49) ](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/
.github/workflows/ci.yml#L49-L49)
API Evolution and Changelog
Relevant source files
This document tracks the evolution of the arm_pl031
crate's public API across versions, documenting breaking changes, feature additions, and migration guidance. It serves as a reference for understanding how the crate has evolved and helps developers plan upgrades and maintain backward compatibility.
For information about building and testing the current version, see Building and Testing. For development environment setup, see Development Environment.
Version History Overview
The arm_pl031
crate has evolved through three major releases, each introducing significant improvements to the API design and functionality. The evolution demonstrates a progression from a basic RTC interface to a more robust, safe, and feature-rich driver.
API Evolution Timeline
Sources: CHANGELOG.md(L1 - L26)
Breaking Changes Analysis
Version 0.2.0 Breaking Changes
Version 0.2.0 introduced several breaking changes that improved the API's safety and correctness:
flowchart TD subgraph subGraph1["v0.2.0 API"] NEW_NEW["unsafe Rtc::new()"] NEW_GET["get_unix_timestamp() -> u32"] NEW_SET["set_unix_timestamp(&mut self, u32)"] SEND_SYNC["impl Send + Sync for Rtc"] end subgraph subGraph0["v0.1.0 API"] OLD_NEW["Rtc::new()"] OLD_GET["get_unix_timestamp() -> u64"] OLD_SET["set_unix_timestamp(&self, u64)"] end NEW_NEW --> SEND_SYNC OLD_GET --> NEW_GET OLD_NEW --> NEW_NEW OLD_SET --> NEW_SET
Sources: CHANGELOG.md(L10 - L22)
Timestamp Type Change (u64 → u32)
The most significant breaking change was the modification of timestamp handling functions:
Function | v0.1.0 Signature | v0.2.0 Signature | Rationale |
---|---|---|---|
get_unix_timestamp | fn() -> u64 | fn() -> u32 | Match PL031 register width |
set_unix_timestamp | fn(&self, u64) | fn(&mut self, u32) | Type consistency + mutability |
This change aligned the API with the underlying hardware's 32-bit registers, preventing potential data truncation issues and improving type safety.
Safety Boundary Introduction
The Rtc::new()
constructor was marked as unsafe
to properly reflect that it requires a valid memory-mapped I/O pointer:
#![allow(unused)] fn main() { // v0.1.0 impl Rtc { pub fn new(base: *mut u8) -> Self // v0.2.0 impl Rtc { pub unsafe fn new(base: *mut u8) -> Self }
Mutability Requirements
The set_unix_timestamp
function was changed to require a mutable reference, correctly reflecting that it modifies device state:
#![allow(unused)] fn main() { // v0.1.0 fn set_unix_timestamp(&self, timestamp: u64) // v0.2.0 fn set_unix_timestamp(&mut self, timestamp: u32) }
Sources: CHANGELOG.md(L14 - L17)
Feature Evolution
Version 0.2.1 Feature Additions
Version 0.2.1 introduced new capabilities without breaking existing APIs:
flowchart TD subgraph subGraph1["Match & Interrupt API (v0.2.1)"] MATCH["Match Register Methods"] INT_ENABLE["enable_interrupt()"] INT_PENDING["interrupt_pending()"] INT_CLEAR["clear_interrupt()"] SET_MATCH["set_match_timestamp()"] end subgraph subGraph0["Core API (v0.2.0)"] BASIC["Basic RTC Operations"] TIMESTAMP["Unix Timestamp Methods"] end subgraph subGraph2["Chrono Integration (v0.2.1)"] CHRONO_DEP["Optional chrono dependency"] DATETIME["DateTime support"] GET_TIME["get_time()"] SET_TIME["set_time()"] end BASIC --> MATCH CHRONO_DEP --> DATETIME DATETIME --> GET_TIME DATETIME --> SET_TIME TIMESTAMP --> SET_MATCH
Sources: CHANGELOG.md(L3 - L9)
Interrupt Management
The addition of interrupt-related methods provided comprehensive control over PL031 interrupt functionality:
enable_interrupt()
- Enable/disable alarm interruptsinterrupt_pending()
- Check interrupt statusclear_interrupt()
- Clear pending interruptsset_match_timestamp()
- Configure alarm time
Chrono Integration
The optional chrono
feature added high-level time handling:
- Conversion between Unix timestamps and
DateTime<Utc>
- Timezone-aware time operations
- Integration with the broader Rust ecosystem
Migration Guidance
Migrating from v0.1.0 to v0.2.0
Type Changes
// v0.1.0 code
let timestamp: u64 = rtc.get_unix_timestamp();
rtc.set_unix_timestamp(timestamp);
// v0.2.0 migration
let timestamp: u32 = rtc.get_unix_timestamp();
rtc.set_unix_timestamp(timestamp); // Also requires &mut rtc
Safety Annotations
// v0.1.0 code
let rtc = Rtc::new(base_addr);
// v0.2.0 migration
let rtc = unsafe { Rtc::new(base_addr) };
Mutability Requirements
#![allow(unused)] fn main() { // v0.1.0 code fn update_time(rtc: &Rtc, new_time: u64) { rtc.set_unix_timestamp(new_time); } // v0.2.0 migration fn update_time(rtc: &mut Rtc, new_time: u32) { rtc.set_unix_timestamp(new_time); } }
Migrating from v0.2.0 to v0.2.1
Version 0.2.1 is fully backward compatible. New features can be adopted incrementally:
// Existing v0.2.0 code continues to work
let timestamp = rtc.get_unix_timestamp();
// New v0.2.1 features (optional)
rtc.set_match_timestamp(timestamp + 3600); // Alarm in 1 hour
rtc.enable_interrupt(true);
// With chrono feature enabled
#[cfg(feature = "chrono")]
{
let datetime = rtc.get_time().unwrap();
println!("Current time: {}", datetime);
}
Sources: CHANGELOG.md(L7 - L8)
Thread Safety Evolution
Send + Sync Implementation
Version 0.2.0 introduced thread safety guarantees:
flowchart TD subgraph subGraph0["Thread Safety Model"] RTC["Rtc struct"] SEND["impl Send"] SYNC["impl Sync"] VOLATILE["Volatile MMIO operations"] ATOMIC["Atomic hardware access"] end RTC --> SEND RTC --> SYNC SEND --> VOLATILE SYNC --> ATOMIC
The Send
and Sync
implementations enable safe use in multi-threaded environments, with the underlying volatile memory operations providing the necessary synchronization primitives.
Sources: CHANGELOG.md(L21)
Compatibility Matrix
Version | Breaking Changes | New Features | Rust Edition | no_std Support |
---|---|---|---|---|
0.1.0 | N/A | Basic RTC operations | 2021 | Yes |
0.2.0 | Timestamp types, Safety | Send/Sync traits | 2021 | Yes |
0.2.1 | None | Interrupts, chrono | 2021 | Yes |
Future Compatibility Considerations
The API design demonstrates several patterns that suggest future stability:
- Hardware Alignment: The v0.2.0 type changes align with hardware constraints, reducing likelihood of future type changes
- Safety First: The unsafe boundary is clearly defined and unlikely to change
- Feature Flags: Optional features like
chrono
allow expansion without breaking core functionality - Thread Safety: The Send/Sync implementation provides a stable foundation for concurrent use
The crate follows semantic versioning, ensuring that future minor versions (0.2.x) will maintain backward compatibility while major versions (0.x.0) may introduce breaking changes.
Sources: CHANGELOG.md(L1 - L26)
Development Environment
Relevant source files
This page provides comprehensive setup instructions for developing and contributing to the arm_pl031
crate. It covers toolchain requirements, development tools configuration, build procedures, and the continuous integration pipeline that ensures code quality across multiple target architectures.
For information about the project's build system and testing procedures in production, see Building and Testing. For details about project structure and API design, see Core Driver Implementation.
Prerequisites and Toolchain
The arm_pl031
crate requires specific Rust toolchain configurations to support its embedded and cross-platform nature. The project is built exclusively with Rust nightly to access unstable features required for embedded development.
Required Rust Toolchain
The development environment requires Rust nightly with specific components:
Component | Purpose |
---|---|
rust-src | Required for building core library and no_std targets |
clippy | Linting and code analysis |
rustfmt | Code formatting enforcement |
Supported Target Architectures
The crate supports multiple target architectures for cross-compilation:
Target | Architecture | Environment |
---|---|---|
x86_64-unknown-linux-gnu | x86_64 | Hosted Linux environment |
x86_64-unknown-none | x86_64 | Bare metal no_std |
riscv64gc-unknown-none-elf | RISC-V 64-bit | Embedded no_std |
aarch64-unknown-none-softfloat | ARM64 | Embedded no_std with soft float |
Development Environment Setup
flowchart TD subgraph subGraph0["Required Targets"] T1["x86_64-unknown-linux-gnu"] T2["x86_64-unknown-none"] T3["riscv64gc-unknown-none-elf"] T4["aarch64-unknown-none-softfloat"] end RUSTUP["rustup toolchain install nightly"] COMPONENTS["rustup component add rust-src clippy rustfmt"] TARGETS["rustup target add"] VERIFY["rustc --version --verbose"] COMPONENTS --> TARGETS RUSTUP --> COMPONENTS TARGETS --> T1 TARGETS --> T2 TARGETS --> T3 TARGETS --> T4 TARGETS --> VERIFY
Sources: .github/workflows/ci.yml(L11 - L19)
Development Tooling and Code Quality
The project enforces strict code quality standards through automated tooling integrated into the development workflow.
Code Formatting
Code formatting is enforced using rustfmt
with project-specific configuration. All code must pass formatting checks before merge.
Formatting Workflow:
- Local development:
cargo fmt --all
- CI enforcement:
cargo fmt --all -- --check
Linting and Static Analysis
The project uses clippy
for comprehensive linting with custom rule configuration:
Clippy Configuration:
- Runs on all target architectures
- Uses
--all-features
flag for complete analysis - Suppresses
clippy::new_without_default
warnings - Command:
cargo clippy --target <target> --all-features -- -A clippy::new_without_default
CI Quality Gates
flowchart TD subgraph subGraph1["Build Verification"] BUILD_NO_FEAT["cargo build --target TARGET --no-default-features"] BUILD_ALL_FEAT["cargo build --target TARGET --all-features"] end subgraph subGraph0["Quality Checks"] FORMAT["cargo fmt --all -- --check"] CLIPPY["cargo clippy --target TARGET --all-features"] end CHECKOUT["actions/checkout@v4"] TOOLCHAIN["dtolnay/rust-toolchain@nightly"] TEST["cargo test --target x86_64-unknown-linux-gnu"] BUILD_ALL_FEAT --> TEST BUILD_NO_FEAT --> BUILD_ALL_FEAT CHECKOUT --> TOOLCHAIN CLIPPY --> BUILD_NO_FEAT FORMAT --> CLIPPY TOOLCHAIN --> FORMAT
Sources: .github/workflows/ci.yml(L22 - L32)
Build System and Testing
The build system supports multiple feature configurations and target architectures with comprehensive testing on hosted platforms.
Build Configurations
The project supports two primary build configurations:
Configuration | Command | Purpose |
---|---|---|
Minimal | cargo build --no-default-features | Core functionality only |
Full | cargo build --all-features | All features including chrono integration |
Testing Framework
Unit tests run exclusively on the x86_64-unknown-linux-gnu
target due to hosted environment requirements:
Test Execution:
- Target:
x86_64-unknown-linux-gnu
only - Command:
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
- Output: Verbose test output with
--nocapture
flag
Cross-Compilation Verification
flowchart TD subgraph subGraph1["Build Steps per Target"] B1["build --no-default-features"] B2["build --all-features"] T1["test (linux-gnu only)"] end subgraph subGraph0["Build Matrix"] M1["nightly + x86_64-unknown-linux-gnu"] M2["nightly + x86_64-unknown-none"] M3["nightly + riscv64gc-unknown-none-elf"] M4["nightly + aarch64-unknown-none-softfloat"] end B1 --> B2 B2 --> T1 M1 --> B1 M2 --> B1 M3 --> B1 M4 --> B1 T1 --> M1
Sources: .github/workflows/ci.yml(L8 - L12) .github/workflows/ci.yml(L26 - L32)
Documentation Generation
The project maintains comprehensive documentation with automated generation and deployment to GitHub Pages.
Documentation Build Process
Documentation generation includes:
- API documentation:
cargo doc --no-deps --all-features
- Intra-doc link validation:
-D rustdoc::broken_intra_doc_links
- Missing documentation detection:
-D missing-docs
- Automatic index page generation for navigation
Documentation Deployment
GitHub Pages Integration:
- Automatic deployment from default branch
- Single-commit deployment strategy
- Target branch:
gh-pages
- Deploy action:
JamesIves/github-pages-deploy-action@v4
Documentation Pipeline
flowchart TD subgraph subGraph0["Documentation Flags"] F1["-D rustdoc::broken_intra_doc_links"] F2["-D missing-docs"] end DOC_BUILD["cargo doc --no-deps --all-features"] INDEX_GEN["Generate index.html redirect"] DEPLOY_CHECK["Default branch?"] DEPLOY["Deploy to gh-pages"] SKIP["Skip deployment"] DEPLOY_CHECK --> DEPLOY DEPLOY_CHECK --> SKIP DOC_BUILD --> INDEX_GEN F1 --> DOC_BUILD F2 --> DOC_BUILD INDEX_GEN --> DEPLOY_CHECK
Sources: .github/workflows/ci.yml(L34 - L57) .github/workflows/ci.yml(L42)
Continuous Integration Pipeline
The CI system ensures code quality and compatibility across all supported platforms through comprehensive automated testing.
Pipeline Architecture
The CI pipeline consists of two primary jobs:
Job | Purpose | Trigger |
---|---|---|
ci | Code quality, building, testing | Push, Pull Request |
doc | Documentation generation and deployment | Push, Pull Request |
Matrix Strategy
The CI uses a matrix build strategy for comprehensive testing:
Matrix Configuration:
- Rust toolchain:
nightly
(only) - Fail-fast:
false
(continue testing other targets on failure) - Parallel execution across all target architectures
CI Architecture Flow
flowchart TD subgraph subGraph2["Matrix Targets"] T1["x86_64-unknown-linux-gnu"] T2["x86_64-unknown-none"] T3["riscv64gc-unknown-none-elf"] T4["aarch64-unknown-none-softfloat"] end subgraph subGraph1["Doc Job"] DOC_START["Documentation Job"] DOC_BUILD["Build Documentation"] DOC_DEPLOY["Deploy to GitHub Pages"] end subgraph subGraph0["CI Job (Matrix Build)"] M_START["Matrix Start"] TOOLS["Setup Rust Nightly + Components"] QUALITY["Code Quality Checks"] BUILD["Multi-target Builds"] TEST["Unit Tests (linux-gnu only)"] end TRIGGER["Push / Pull Request"] BUILD --> T1 BUILD --> T2 BUILD --> T3 BUILD --> T4 BUILD --> TEST DOC_BUILD --> DOC_DEPLOY DOC_START --> DOC_BUILD M_START --> TOOLS QUALITY --> BUILD TOOLS --> QUALITY TRIGGER --> DOC_START TRIGGER --> M_START
Sources: .github/workflows/ci.yml(L1 - L33) .github/workflows/ci.yml(L34 - L57)
Contribution Workflow
Local Development Setup
- Install Rust Nightly:
rustup toolchain install nightly
rustup default nightly
- Add Required Components:
rustup component add rust-src clippy rustfmt
- Install Target Architectures:
rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
Pre-commit Checklist
Before submitting contributions, ensure all quality gates pass:
Check | Command | Requirement |
---|---|---|
Format | cargo fmt --all -- --check | Must pass |
Lint | cargo clippy --all-features | Must pass |
Build (minimal) | cargo build --no-default-features | All targets |
Build (full) | cargo build --all-features | All targets |
Test | cargo test | Must pass on linux-gnu |
Git Configuration
The project excludes standard development artifacts:
Ignored Files:
/target
- Rust build artifacts/.vscode
- VS Code configuration.DS_Store
- macOS system filesCargo.lock
- Dependency lock file (excluded for libraries)
Sources: .gitignore(L1 - L4)
Overview
Relevant source files
Purpose and Scope
The crate_interface
crate is a procedural macro library that enables cross-crate trait interfaces in Rust. It provides a solution for defining trait interfaces in one crate while allowing implementations and usage in separate crates, effectively solving circular dependency problems between crates. This document covers the high-level architecture, core concepts, and integration patterns within the ArceOS ecosystem.
For detailed usage instructions, see Getting Started. For comprehensive macro documentation, see Macro Reference. For implementation details, see Architecture and Internals.
Sources: Cargo.toml(L1 - L22) README.md(L1 - L85)
Core Problem and Solution
The crate addresses the fundamental challenge of circular dependencies in Rust crate ecosystems. Traditional trait definitions create tight coupling between interface definitions and their implementations, preventing modular plugin architectures.
The crate_interface
solution employs a three-phase approach using procedural macros that generate extern "Rust"
function declarations and implementations with specific symbol naming conventions. This allows the Rust linker to resolve cross-crate trait method calls without requiring direct crate dependencies.
flowchart TD subgraph subGraph1["crate_interface Solution"] G["Generated #[export_name] functions"] H["Crate C: call_interface!"] I["Safe extern function calls"] subgraph subGraph0["Traditional Approach Problem"] D["Crate A: #[def_interface]"] E["Generated extern declarations"] F["Crate B: #[impl_interface]"] A["Crate A: Interface"] B["Crate B: Implementation"] C["Crate C: Consumer"] end end A --> B A --> C B --> C C --> A D --> E E --> G F --> G H --> I I --> G
Sources: README.md(L7 - L10) Cargo.toml(L6)
Three-Macro System Architecture
The crate implements a coordinated system of three procedural macros that work together to enable cross-crate trait interfaces:
flowchart TD subgraph subGraph3["Runtime Linking"] M["Rust Linker"] end subgraph subGraph2["call_interface Phase"] I["call_interface!"] J["HelloIf::hello macro call"] K["unsafe extern function call"] L["__HelloIf_mod::__HelloIf_hello"] end subgraph subGraph1["impl_interface Phase"] E["#[impl_interface]"] F["impl HelloIf for HelloIfImpl"] G["#[export_name] extern fn"] H["__HelloIf_hello symbol"] end subgraph subGraph0["def_interface Phase"] A["#[def_interface]"] B["trait HelloIf"] C["__HelloIf_mod module"] D["extern fn __HelloIf_hello"] end A --> B B --> C C --> D D --> M E --> F F --> G G --> H H --> M I --> J J --> K K --> L L --> M
Sources: README.md(L13 - L40) README.md(L44 - L85)
Generated Code Flow
The macro system transforms high-level trait definitions into low-level extern function interfaces that can be linked across crate boundaries:
Sources: README.md(L46 - L85)
Integration with ArceOS Ecosystem
The crate_interface
crate is designed as a foundational component within the ArceOS modular operating system architecture. It enables plugin-style extensibility and cross-crate interfaces essential for kernel modularity:
Feature | Purpose | ArceOS Use Case |
---|---|---|
No-std compatibility | Embedded/kernel environments | Bare-metal kernel modules |
Cross-crate interfaces | Plugin architecture | Device drivers, file systems |
Symbol-based linking | Runtime module loading | Dynamic kernel extensions |
Zero-overhead abstractions | Performance-critical code | Kernel syscall interfaces |
flowchart TD subgraph subGraph1["Build Targets"] J["x86_64-unknown-none"] K["riscv64gc-unknown-none-elf"] L["aarch64-unknown-none-softfloat"] end subgraph subGraph0["ArceOS Architecture"] A["Kernel Core"] B["crate_interface"] C["Driver Interface Traits"] D["FS Interface Traits"] E["Network Interface Traits"] F["Device Drivers"] G["File Systems"] H["Network Stack"] I["Applications"] end A --> B B --> C B --> D B --> E B --> J B --> K B --> L F --> C G --> D H --> E I --> C I --> D I --> E
Sources: Cargo.toml(L8) Cargo.toml(L11 - L12)
Key Design Principles
The crate follows several core design principles that make it suitable for system-level programming:
- Symbol-based Decoupling: Uses extern function symbols instead of direct trait object vtables
- Compile-time Safety: Procedural macros ensure type safety while generating unsafe extern calls
- Zero Runtime Overhead: Direct function calls with no dynamic dispatch or allocations
- Cross-platform Compatibility: Works across multiple target architectures and toolchains
- No-std First: Designed for embedded and kernel environments without standard library dependencies
Sources: Cargo.toml(L12) Cargo.toml(L15 - L18)
Getting Started
Relevant source files
This document provides a quick start guide for using the crate_interface
crate to define trait interfaces that can be implemented and called across crate boundaries. It covers the basic three-step workflow of defining interfaces, implementing them, and calling interface methods.
For comprehensive macro syntax and options, see Macro Reference. For understanding the underlying implementation details, see Architecture and Internals.
Purpose and Scope
The crate_interface
crate solves circular dependency problems by enabling trait definitions in one crate while allowing implementations and usage in separate crates. This guide demonstrates the essential usage patterns through practical examples using the three core macros: def_interface
, impl_interface
, and call_interface!
.
Installation
Add crate_interface
to your Cargo.toml
dependencies:
[dependencies]
crate_interface = "0.1.4"
The crate supports no-std
environments and requires Rust 1.57 or later.
Sources: Cargo.toml(L1 - L22)
Three-Step Workflow
The crate_interface
system follows a consistent three-step pattern that maps directly to the three procedural macros:
Basic Workflow Diagram
flowchart TD A["Step 1: Define"] B["Step 2: Implement"] C["Step 3: Call"] D["#[def_interface]trait HelloIf"] E["#[impl_interface]impl HelloIf"] F["call_interface!HelloIf::hello"] G["Generates __HelloIf_modextern fn declarations"] H["Generates #[export_name]extern fn definitions"] I["Generates unsafe__HelloIf_mod calls"] A --> B A --> D B --> C B --> E C --> F D --> G E --> H F --> I
Sources: README.md(L13 - L40) tests/test_crate_interface.rs(L3 - L11)
Step 1: Define Interface
Use the #[def_interface]
attribute macro to define a trait interface:
#![allow(unused)] fn main() { #[crate_interface::def_interface] pub trait HelloIf { fn hello(&self, name: &str, id: usize) -> String; } }
This generates the trait plus a hidden module containing extern "Rust"
function declarations.
Step 2: Implement Interface
Use the #[impl_interface]
attribute macro to implement the interface:
#![allow(unused)] fn main() { struct HelloIfImpl; #[crate_interface::impl_interface] impl HelloIf for HelloIfImpl { fn hello(&self, name: &str, id: usize) -> String { format!("Hello, {} {}!", name, id) } } }
This generates #[export_name]
extern functions that can be linked across crates.
Step 3: Call Interface Methods
Use the call_interface!
function-like macro to safely call interface methods:
use crate_interface::call_interface;
// Method-style calling
let result = call_interface!(HelloIf::hello("world", 123));
// Function-style calling
let result = call_interface!(HelloIf::hello, "rust", 456);
Sources: README.md(L13 - L40) tests/test_crate_interface.rs(L36 - L41)
Generated Code Structure
Understanding what code gets generated helps debug issues and optimize usage:
Code Generation Mapping
flowchart TD subgraph subGraph1["Generated Symbols"] D["trait HelloIf(unchanged)"] E["__HelloIf_mod module"] F["__HelloIf_helloextern declaration"] G["__HelloIf_helloextern definition"] H["#[export_name]attribute"] I["unsafe __HelloIf_mod::__HelloIf_hello call"] end subgraph subGraph0["Input Code"] A["#[def_interface]trait HelloIf"] B["#[impl_interface]impl HelloIf for HelloIfImpl"] C["call_interface!HelloIf::hello"] end A --> D A --> E A --> F B --> G B --> H C --> I F --> G I --> F
The def_interface
macro generates a module named __HelloIf_mod
containing extern function declarations like __HelloIf_hello
. The impl_interface
macro creates matching extern function definitions with #[export_name]
attributes. The call_interface!
macro generates safe wrappers around unsafe extern calls.
Sources: README.md(L44 - L85)
Advanced Usage Patterns
Default Method Implementations
Traits can include default implementations that will be preserved:
#![allow(unused)] fn main() { #[def_interface] trait SimpleIf { fn foo() -> u32 { 123 // Default implementation } fn bar(&self, a: u16, b: &[u8], c: &str); } }
Cross-Module Calls
Interface calls work from any module scope by using appropriate path syntax:
mod private {
pub fn test_call_in_mod() {
crate::call_interface!(super::SimpleIf::bar(123, &[2, 3, 5, 7, 11], "test"));
crate::call_interface!(crate::SimpleIf::foo,);
}
}
Method Documentation
Documentation comments are preserved in the generated code:
#![allow(unused)] fn main() { #[def_interface] trait SimpleIf { /// Test comments for method fn bar(&self, a: u16, b: &[u8], c: &str); } #[impl_interface] impl SimpleIf for SimpleIfImpl { /// Implementation-specific documentation fn bar(&self, a: u16, b: &[u8], c: &str) { // Implementation here } } }
Sources: tests/test_crate_interface.rs(L3 - L42)
Cross-Crate Usage
Crate | Role | Required Items |
---|---|---|
Interface Crate | Defines traits | #[def_interface]trait definition |
Implementation Crate | Provides implementations | #[impl_interface]impl block + struct |
Consumer Crate | Calls methods | call_interface!macro calls |
The implementation crate and consumer crate can be the same, different, or have multiple implementations across multiple crates.
Next Steps
- For detailed macro syntax and all available options, see Macro Reference
- For understanding how the extern function linking works, see Architecture and Internals
- For contributing to the project, see Development Guide
- For running and writing tests, see Testing
Sources: README.md(L1 - L85) tests/test_crate_interface.rs(L1 - L42) Cargo.toml(L1 - L22)
Macro Reference
Relevant source files
This page provides comprehensive documentation for the three core procedural macros that form the crate_interface
system: def_interface
, impl_interface
, and call_interface
. These macros work together to enable cross-crate trait interfaces by generating extern function declarations, exported implementations, and safe call wrappers.
For detailed documentation of individual macros, see def_interface Macro, impl_interface Macro, and call_interface Macro. For understanding the underlying architecture and symbol linking mechanisms, see Architecture and Internals.
Three-Macro System Overview
The crate_interface
system consists of three interdependent procedural macros that implement a complete cross-crate interface solution:
Macro Interaction Flow
flowchart TD A["#[def_interface] trait MyTrait"] B["def_interface macro"] C["Original trait + __MyTrait_mod"] D["extern fn __MyTrait_method declarations"] E["#[impl_interface] impl MyTrait"] F["impl_interface macro"] G["Modified impl block"] H["#[export_name] extern fns"] I["call_interface!(MyTrait::method)"] J["call_interface macro"] K["unsafe __MyTrait_mod::__MyTrait_method call"] L["Rust Linker Symbol Table"] M["Runtime Symbol Resolution"] A --> B B --> C C --> D D --> L E --> F F --> G G --> H H --> L I --> J J --> K K --> L L --> M
Sources: src/lib.rs(L28 - L75) src/lib.rs(L87 - L162) src/lib.rs(L192 - L210)
Core Components
Macro | Purpose | Input | Output |
---|---|---|---|
def_interface | Interface definition | Trait declaration | Trait + hidden module with extern declarations |
impl_interface | Interface implementation | Trait impl block | Modified impl with exported extern functions |
call_interface | Interface invocation | Method call syntax | Unsafe extern function call |
Generated Code Architecture
The macro system transforms high-level trait definitions into low-level extern function interfaces that can be linked across crate boundaries:
Code Generation Pipeline
flowchart TD subgraph Linking["Linking"] K["Symbol: __HelloIf_hello"] end subgraph subGraph2["Generated Code"] G["__HelloIf_mod module"] H["extern fn __HelloIf_hello"] I["#[export_name] extern fn"] J["unsafe extern call"] end subgraph subGraph1["Macro Processing"] D["def_interface"] E["impl_interface"] F["call_interface"] end subgraph subGraph0["Input Code"] A["trait HelloIf"] B["impl HelloIf for HelloIfImpl"] C["HelloIf::hello call"] end A --> D B --> E C --> F D --> G D --> H E --> I F --> J H --> K I --> K J --> K
Sources: src/lib.rs(L40 - L73) src/lib.rs(L108 - L161) src/lib.rs(L194 - L210) README.md(L44 - L85)
Naming Conventions
The macro system uses consistent naming patterns to ensure symbol uniqueness and avoid conflicts:
Symbol Generation Pattern
Element | Pattern | Example |
---|---|---|
Hidden module | __{TraitName}_mod | __HelloIf_mod |
Extern function | __{TraitName}_{method} | __HelloIf_hello |
Export symbol | __{TraitName}_{method} | __HelloIf_hello |
Implementation Details
The naming convention implementation is found in the macro code:
- Module name generation: src/lib.rs(L61)
- Function name generation: src/lib.rs(L45) src/lib.rs(L113)
- Symbol export naming: src/lib.rs(L113) src/lib.rs(L203)
Syntax Patterns
Basic Usage Syntax
#![allow(unused)] fn main() { // 1. Interface Definition #[crate_interface::def_interface] pub trait TraitName { fn method_name(&self, param: Type) -> ReturnType; } // 2. Interface Implementation #[crate_interface::impl_interface] impl TraitName for StructName { fn method_name(&self, param: Type) -> ReturnType { // implementation } } // 3. Interface Invocation call_interface!(TraitName::method_name(args)) call_interface!(TraitName::method_name, arg1, arg2) }
Calling Syntax Variants
The call_interface
macro supports two calling styles as demonstrated in the test suite:
Style | Syntax | Example |
---|---|---|
Parentheses | call_interface!(Trait::method(args)) | call_interface!(SimpleIf::bar(123, &[2, 3], "test")) |
Comma-separated | call_interface!(Trait::method, args) | call_interface!(SimpleIf::bar, 123, &[2, 3], "test") |
Sources: tests/test_crate_interface.rs(L31 - L32) tests/test_crate_interface.rs(L38 - L39)
Cross-Crate Symbol Resolution
Symbol Export and Import Mechanism
flowchart TD subgraph subGraph3["Linker Resolution"] D1["Symbol Table"] D2["__MyIf_method symbol"] end subgraph subGraph2["Crate C: Usage"] C1["call_interface!(MyIf::method)"] C2["unsafe __MyIf_mod::__MyIf_method call"] end subgraph subGraph1["Crate B: Implementation"] B1["#[impl_interface] impl MyIf"] B2["#[export_name = __MyIf_method]"] B3["extern fn __MyIf_method definition"] end subgraph subGraph0["Crate A: Interface Definition"] A1["#[def_interface] trait MyIf"] A2["__MyIf_mod module"] A3["extern fn __MyIf_method declaration"] end A1 --> A2 A2 --> A3 A3 --> D1 B1 --> B2 B2 --> B3 B3 --> D1 C1 --> C2 C2 --> D1 D1 --> D2
Sources: src/lib.rs(L54 - L58) src/lib.rs(L147 - L151) src/lib.rs(L209)
Macro Attributes and Visibility
Visibility Inheritance
The macro system preserves and propagates visibility modifiers:
def_interface
preserves trait visibility: src/lib.rs(L38) src/lib.rs(L67)impl_interface
preserves method attributes: src/lib.rs(L111) src/lib.rs(L142 - L143)- Generated modules use
#[doc(hidden)]
: src/lib.rs(L65)
Attribute Preservation
The implementation preserves existing attributes on trait methods and impl functions, as seen in the test examples with #[cfg(test)]
and doc comments: tests/test_crate_interface.rs(L9) tests/test_crate_interface.rs(L17) tests/test_crate_interface.rs(L22)
Error Handling
The macro system includes comprehensive error checking for common usage mistakes:
- Empty attribute validation: src/lib.rs(L29 - L34) src/lib.rs(L89 - L94)
- Trait implementation validation: src/lib.rs(L97 - L106)
- Path parsing validation: src/lib.rs(L198 - L200)
Sources: src/lib.rs(L14 - L16) src/lib.rs(L29 - L34) src/lib.rs(L89 - L94) src/lib.rs(L97 - L106) src/lib.rs(L198 - L200)
def_interface Macro
Relevant source files
The def_interface
macro is a procedural attribute macro that transforms trait definitions to enable cross-crate implementations without circular dependencies. It generates extern function declarations that serve as the linking contract between trait definitions and their implementations across crate boundaries.
For information about implementing these interfaces, see impl_interface Macro. For calling interface methods, see call_interface Macro.
Purpose and Functionality
The def_interface
macro solves the circular dependency problem in Rust by creating a trait interface that can be implemented in any crate. When applied to a trait definition, it generates both the original trait and a hidden module containing extern function declarations that correspond to each trait method.
flowchart TD A["Original Trait Definition"] B["def_interface macro"] C["Generated Trait + Hidden Module"] D["Original Trait"] E["__TraitName_mod module"] F["extern fn declarations"] G["Symbol linking contract"] A --> B B --> C C --> D C --> E E --> F F --> G
def_interface Macro Processing Flow
Sources: src/lib.rs(L28 - L75) README.md(L14 - L18)
Syntax and Usage
The macro is applied as an attribute above trait definitions with no additional parameters:
#![allow(unused)] fn main() { #[crate_interface::def_interface] pub trait TraitName { fn method_name(&self, param: Type) -> ReturnType; } }
The macro accepts only empty attributes and will generate a compile error if any parameters are provided.
Sources: src/lib.rs(L29 - L34) tests/test_crate_interface.rs(L3 - L11)
Generated Code Structure
When def_interface
processes a trait, it generates two main components:
1. Original Trait Preservation
The original trait definition is preserved unchanged, maintaining all method signatures, documentation, and attributes.
2. Hidden Module Generation
A hidden module is created with the naming pattern __TraitName_mod
containing extern function declarations.
flowchart TD subgraph subGraph2["Extern Function Signature"] E["name: &str"] F["id: usize"] G["-> String"] end subgraph subGraph1["Generated Module"] A["trait HelloIf"] B["fn hello(&self, name: &str, id: usize)"] C["__HelloIf_mod"] D["extern fn __HelloIf_hello"] end subgraph subGraph0["Input Trait"] A["trait HelloIf"] B["fn hello(&self, name: &str, id: usize)"] C["__HelloIf_mod"] end A --> B C --> D D --> E D --> F D --> G
Code Generation Structure for def_interface
Sources: src/lib.rs(L36 - L74) README.md(L47 - L58)
Function Signature Transformation
The macro transforms trait method signatures into extern function signatures by:
- Name Transformation: Method names become
__TraitName_methodName
- Self Parameter Removal: The
&self
parameter is stripped from extern signatures - Parameter Preservation: All typed parameters are preserved in their original order
flowchart TD subgraph subGraph1["Generated Components"] G["extern fn __TraitName_method"] H["pub visibility"] I["Rust ABI"] end subgraph subGraph0["Method Signature Processing"] A["sig.ident = format_ident!"] B["_{}{} pattern"] C["sig.inputs = Punctuated::new()"] D["Clear input parameters"] E["Filter FnArg::Typed"] F["Preserve non-self params"] end A --> B B --> G C --> D D --> G E --> F F --> G H --> G I --> G
Function Signature Transformation Process
Sources: src/lib.rs(L40 - L58)
Module Structure and Visibility
The generated module follows a specific structure:
Component | Pattern | Purpose |
---|---|---|
Module Name | __TraitName_mod | Hidden module containing extern declarations |
Module Visibility | Inherits from trait | Matches original trait visibility |
Module Attributes | #[doc(hidden)],#[allow(non_snake_case)] | Hide from documentation, allow naming convention |
Extern Block | extern "Rust" | Rust ABI for cross-crate linking |
The module includes a use super::*;
statement to inherit the parent scope, ensuring type definitions remain accessible.
Sources: src/lib.rs(L61 - L73)
Integration with Other Macros
The def_interface
macro creates the foundation for the other two macros in the system:
flowchart TD subgraph subGraph2["call_interface Usage"] G["References __TraitName_mod"] H["Calls extern functions"] I["Provides safe wrapper"] end subgraph subGraph1["impl_interface Usage"] D["Reads trait signature"] E["Generates #[export_name]"] F["Creates exported functions"] end subgraph subGraph0["def_interface Output"] A["Original Trait"] B["__TraitName_mod"] C["extern fn __TraitName_method"] end A --> D B --> G C --> E C --> H F --> H
Macro System Integration
Sources: src/lib.rs(L88 - L162) src/lib.rs(L193 - L210)
Error Handling and Validation
The macro performs several validation checks:
- Attribute Validation: Ensures no parameters are provided
- Trait Item Processing: Only processes
TraitItem::Fn
items - Method Signature Extraction: Safely extracts method signatures and parameters
Invalid usage results in compile-time errors with descriptive messages.
Sources: src/lib.rs(L29 - L34) src/lib.rs(L14 - L16)
Examples and Test Cases
The test suite demonstrates various usage patterns:
#![allow(unused)] fn main() { #[def_interface] trait SimpleIf { fn foo() -> u32 { 123 } fn bar(&self, a: u16, b: &[u8], c: &str); } }
This generates:
- The original
SimpleIf
trait - Module
__SimpleIf_mod
with extern declarations - Functions
__SimpleIf_foo
and__SimpleIf_bar
Sources: tests/test_crate_interface.rs(L3 - L11) README.md(L14 - L18)
Technical Implementation Details
The macro implementation uses several key Rust procedural macro components:
Component | Purpose | Usage |
---|---|---|
syn::ItemTrait | Parse trait definitions | Input parsing |
format_ident! | Generate identifiers | Name transformation |
quote! | Code generation | Output synthesis |
proc_macro2::Span | Error reporting | Compile-time diagnostics |
The generated code uses extern "Rust"
blocks to leverage Rust's symbol mangling and linking behavior, enabling cross-crate function resolution without runtime overhead.
Sources: src/lib.rs(L3 - L12) src/lib.rs(L36 - L74)
impl_interface Macro
Relevant source files
The impl_interface
macro enables trait implementations to be used across crate boundaries by generating exported extern functions that can be linked at runtime. This macro transforms a standard trait implementation into a cross-crate compatible implementation by creating symbol exports that follow the interface contract established by def_interface
.
For information about defining interfaces, see def_interface Macro. For information about calling cross-crate interfaces, see call_interface Macro.
Purpose and Functionality
The impl_interface
macro serves as the implementation phase of the three-macro system. It takes a trait implementation and automatically generates the necessary extern function exports that can be linked to by other crates, eliminating the need for direct crate dependencies while maintaining type safety.
Core Functions:
- Parses trait implementations and extracts method signatures
- Generates
#[export_name]
extern functions for each trait method - Maintains original implementation logic while adding cross-crate compatibility
- Handles both instance methods (
&self
) and static methods
Sources: src/lib.rs(L77 - L86) README.md(L20 - L28)
Syntax and Usage
The macro is applied as an attribute to trait implementations:
#[impl_interface]
impl TraitName for StructName {
// method implementations
}
The macro requires:
- A trait implementation (not a standalone impl block)
- The trait must be previously defined with
#[def_interface]
- The implementation struct must be a named type (not anonymous)
Usage Example from Tests:
#![allow(unused)] fn main() { #[impl_interface] impl SimpleIf for SimpleIfImpl { fn foo() -> u32 { 456 } fn bar(&self, a: u16, b: &[u8], c: &str) { /* implementation */ } } }
Sources: tests/test_crate_interface.rs(L15 - L27) src/lib.rs(L87 - L94)
Code Generation Process
Implementation Analysis Flow
flowchart TD A["Parse ItemImpl AST"] B["Extract trait_name"] C["Extract impl_name (struct)"] D["Iterate over impl methods"] E["For each ImplItem::Fn"] F["Generate extern function signature"] G["Create export_name attribute"] H["Generate method wrapper"] I["Insert into original method"] J["Return modified implementation"] A --> B B --> C C --> D D --> E D --> J E --> F F --> G G --> H H --> I I --> D
The macro performs the following transformations:
- Parse Implementation: Extracts trait name and implementing struct name from the AST
- Method Processing: For each method in the implementation, generates corresponding extern functions
- Signature Transformation: Removes
self
parameter from extern function signatures while preserving other parameters - Export Generation: Creates
#[export_name]
attributes following the__TraitName_methodName
convention - Wrapper Injection: Injects the extern function into the original method implementation
Sources: src/lib.rs(L96 - L107) src/lib.rs(L108 - L161)
Generated Code Structure
flowchart TD subgraph subGraph0["Generated Components"] D["Original method body"] E["Inline extern function"] F["#[export_name] attribute"] G["Symbol export"] end A["Original Implementation"] B["impl_interface macro"] C["Modified Implementation"] A --> B B --> C C --> D C --> E E --> F F --> G
Sources: src/lib.rs(L140 - L157) README.md(L62 - L78)
Method Signature Processing
The macro handles two types of method signatures differently:
Instance Methods (with&self)
For methods that take &self
as the first parameter:
- Extern Function: Removes
&self
from the signature - Call Generation: Creates an instance of the implementing struct and calls the method on it
- Parameter Forwarding: Passes all non-self parameters to the method call
Generated Pattern:
#[export_name = "__TraitName_methodName"]
extern "Rust" fn __TraitName_methodName(/* params without self */) -> ReturnType {
let _impl: StructName = StructName;
_impl.methodName(/* params */)
}
Static Methods (no&self)
For static methods that don't take &self
:
- Direct Call: Calls the static method directly on the implementing type
- Parameter Preservation: Maintains all original parameters in the extern function
Generated Pattern:
#![allow(unused)] fn main() { #[export_name = "__TraitName_methodName"] extern "Rust" fn __TraitName_methodName(/* all params */) -> ReturnType { StructName::methodName(/* params */) } }
Sources: src/lib.rs(L119 - L138) tests/test_crate_interface.rs(L18 - L26)
Symbol Export Mechanism
Export Name Generation
flowchart TD A["trait_name: SimpleIf"] C["Combine with separator"] B["method_name: bar"] D["__SimpleIf_bar"] E["#[export_name] attribute"] F["Linker symbol export"] G["External crate"] H["Symbol resolution"] I["Runtime function call"] A --> C B --> C C --> D D --> E E --> F F --> H G --> H H --> I
The naming convention __TraitName_methodName
ensures:
- Uniqueness: Prevents symbol collisions across different traits
- Consistency: Matches the naming pattern expected by
def_interface
- Linkability: Creates symbols that the Rust linker can resolve across crates
Sources: src/lib.rs(L113 - L116) src/lib.rs(L148)
Integration with Macro System
Cross-Macro Coordination
flowchart TD subgraph subGraph2["call_interface Usage"] D["unsafe call to extern fn"] end subgraph subGraph1["impl_interface Output"] B["#[export_name = '__TraitName_method']"] C["extern fn implementation"] end subgraph subGraph0["def_interface Output"] A["extern fn __TraitName_method"] end E["Linker resolution"] A --> B C --> E D --> E
The impl_interface
macro must coordinate with the other macros:
- Symbol Contract: Must export functions with names matching
def_interface
declarations - Type Compatibility: Generated extern functions must match the signatures expected by
call_interface
- Linking Requirements: Exported symbols must be available at link time for consuming crates
Sources: src/lib.rs(L203 - L209) README.md(L52 - L58)
Error Handling and Validation
The macro performs several validation checks:
Input Validation:
- Ensures the attribute has no parameters:
#[impl_interface]
only - Verifies the target is a trait implementation, not a standalone impl block
- Requires the implementing type to be a named struct (not anonymous)
Error Cases:
// Invalid: attribute parameters
#[impl_interface(param)] // Compile error
// Invalid: not a trait implementation
#[impl_interface]
impl SomeStruct { } // Compile error
// Invalid: anonymous type
#[impl_interface]
impl SomeTrait for (u32, String) { } // Compile error
Sources: src/lib.rs(L88 - L94) src/lib.rs(L99 - L106)
Complete Transformation Example
Input Code:
#![allow(unused)] fn main() { #[impl_interface] impl SimpleIf for SimpleIfImpl { fn bar(&self, a: u16, b: &[u8], c: &str) { println!("{} {:?} {}", a, b, c); } } }
Generated Output (Conceptual):
impl SimpleIf for SimpleIfImpl {
#[inline]
fn bar(&self, a: u16, b: &[u8], c: &str) {
{
#[inline]
#[export_name = "__SimpleIf_bar"]
extern "Rust" fn __SimpleIf_bar(a: u16, b: &[u8], c: &str) {
let _impl: SimpleIfImpl = SimpleIfImpl;
_impl.bar(a, b, c)
}
}
{
println!("{} {:?} {}", a, b, c);
}
}
}
This transformation enables the implementation to be called from any crate that links to this one, without requiring direct dependency relationships.
Sources: README.md(L62 - L78) tests/test_crate_interface.rs(L23 - L26)
call_interface Macro
Relevant source files
The call_interface
macro provides a safe wrapper for invoking methods defined by traits marked with #[def_interface]
and implemented with #[impl_interface]
. This procedural macro encapsulates unsafe extern function calls, enabling cross-crate trait method invocation without requiring direct dependencies between interface definitions and implementations.
For information about defining interfaces, see def_interface Macro. For implementing interfaces, see impl_interface Macro.
Purpose and Functionality
The call_interface!
macro serves as the consumer-facing component of the crate_interface system. It transforms trait method calls into unsafe extern function invocations that link to implementations across crate boundaries at runtime.
Basic Syntax Patterns
The macro supports two primary calling conventions:
Syntax Pattern | Description | Example |
---|---|---|
Parenthesized Arguments | Traditional function call syntax | call_interface!(HelloIf::hello("world", 123)) |
Comma-Separated Arguments | Alternative syntax with comma separator | call_interface!(HelloIf::hello, "rust", 456) |
No Arguments | Function calls without parameters | call_interface!(SimpleIf::foo) |
Sources: README.md(L30 - L40) tests/test_crate_interface.rs(L37 - L41)
Macro Implementation Architecture
Call Interface Parsing Structure
flowchart TD Input["call_interface!(Trait::method, arg1, arg2)"] Parser["CallInterface::parse()"] PathStruct["Path: Trait::method"] ArgsStruct["Punctuated"] TraitName["trait_name: Ident"] MethodName["fn_name: Ident"] ExternFnName["__TraitName_methodName"] ModPath["__TraitName_mod"] UnsafeCall["unsafe { __TraitName_mod::__TraitName_methodName(args) }"] ArgsStruct --> UnsafeCall ExternFnName --> UnsafeCall Input --> Parser MethodName --> ExternFnName ModPath --> UnsafeCall Parser --> ArgsStruct Parser --> PathStruct PathStruct --> MethodName PathStruct --> TraitName TraitName --> ExternFnName
Sources: src/lib.rs(L164 - L184) src/lib.rs(L192 - L210)
Code Transformation Process
The macro performs a systematic transformation from trait method calls to extern function invocations:
Sources: src/lib.rs(L194 - L210)
Implementation Details
CallInterface Structure
The macro uses a custom CallInterface
struct to parse input tokens:
Field | Type | Purpose |
---|---|---|
path | Path | Contains the trait and method names (e.g.,HelloIf::hello) |
args | Punctuated<Expr, Token![,]> | Function arguments parsed as expressions |
The parsing logic handles both parenthesized and comma-separated argument formats by checking for different token patterns.
Sources: src/lib.rs(L164 - L184)
Symbol Name Generation
The macro generates extern function names using a consistent naming convention:
__<TraitName>_<MethodName>
For example, HelloIf::hello
becomes __HelloIf_hello
. This naming scheme ensures unique symbol names across the entire linked binary while maintaining predictable resolution patterns.
Sources: src/lib.rs(L203)
Module Path Resolution
Generated extern functions are accessed through hidden modules created by #[def_interface]
:
__<TraitName>_mod::<ExternFunctionName>
This module structure provides namespace isolation and enables the linker to resolve symbols correctly across crate boundaries.
Sources: src/lib.rs(L205 - L209)
Usage Patterns and Examples
Standard Method Calls
The most common usage pattern involves calling trait methods with arguments:
// Interface definition (in any crate)
#[def_interface]
trait HelloIf {
fn hello(&self, name: &str, id: usize) -> String;
}
// Usage (in any other crate)
let result = call_interface!(HelloIf::hello("world", 123));
Sources: README.md(L13 - L18) README.md(L30 - L35)
Alternative Calling Syntax
The macro supports comma-separated arguments as an alternative to parenthesized calls:
// Both forms are equivalent
call_interface!(HelloIf::hello("rust", 456))
call_interface!(HelloIf::hello, "rust", 456)
Sources: README.md(L36 - L39)
Static Method Calls
Methods without &self
parameters (static methods) are supported:
let value = call_interface!(SimpleIf::foo);
Sources: tests/test_crate_interface.rs(L39)
Cross-Module Usage
The macro works correctly when called from within modules or different crate contexts:
mod private {
pub fn test_call_in_mod() {
crate::call_interface!(super::SimpleIf::bar(123, &[2, 3, 5, 7, 11], "test"));
crate::call_interface!(crate::SimpleIf::foo,);
}
}
Sources: tests/test_crate_interface.rs(L29 - L34)
Safety and Runtime Behavior
Unsafe Encapsulation
The call_interface!
macro encapsulates unsafe extern function calls, providing a safer interface while maintaining the underlying performance characteristics. The generated code always uses unsafe
blocks because extern function calls are inherently unsafe in Rust.
Link-Time Symbol Resolution
The macro relies on the Rust linker to resolve extern function symbols at link time. If an implementation is not linked into the final binary, the program will fail to link rather than producing runtime errors.
Runtime Assumptions
The macro assumes that:
- The target trait was defined with
#[def_interface]
- An implementation exists and was compiled with
#[impl_interface]
- All required crates are linked together in the final binary
Sources: src/lib.rs(L209) README.md(L80 - L85)
Integration with Interface System
Relationship to Other Macros
flowchart TD subgraph subGraph3["Runtime Linking"] Linker["Rust Linker"] end subgraph subGraph2["Usage Phase"] CallMacro["call_interface!"] UserCall["HelloIf::hello(args)"] UnsafeCall["unsafe { __HelloIf_mod::__HelloIf_hello(args) }"] end subgraph subGraph1["Implementation Phase"] ImplMacro["#[impl_interface]"] ImplBlock["impl HelloIf for HelloIfImpl"] ExportedFn["#[export_name] extern fn"] end subgraph subGraph0["Interface Definition Phase"] DefMacro["#[def_interface]"] TraitDef["trait HelloIf"] HiddenMod["__HelloIf_mod"] ExternDecl["extern fn __HelloIf_hello"] end CallMacro --> UnsafeCall CallMacro --> UserCall DefMacro --> HiddenMod DefMacro --> TraitDef ExportedFn --> Linker ExternDecl --> Linker HiddenMod --> ExternDecl ImplMacro --> ExportedFn ImplMacro --> ImplBlock UnsafeCall --> Linker
Sources: src/lib.rs(L186 - L210) README.md(L42 - L85)
The call_interface!
macro completes the interface system by providing the consumer interface that connects to implementations through the linker's symbol resolution mechanism.
Architecture and Internals
Relevant source files
This document provides a deep technical dive into the internal architecture of the crate_interface
procedural macro system. It explains how the three-phase macro transformation works, the extern symbol generation and linking mechanisms, and the cross-crate communication patterns that enable trait interfaces without circular dependencies.
For basic usage examples, see Getting Started. For detailed macro syntax and options, see Macro Reference.
Overall System Architecture
The crate_interface
system implements a three-phase transformation pipeline that converts trait definitions and implementations into extern function-based cross-crate interfaces.
Three-Phase Transformation Pipeline
flowchart TD subgraph subGraph3["Runtime[Rust Linker Resolution]"] L["Symbol table matching"] end subgraph subGraph2["Phase3[Phase 3: Interface Invocation]"] I["call_interface! macro"] J["Unsafe extern call generated"] K["__TraitName_mod::__TraitName_methodName invoked"] end subgraph subGraph1["Phase2[Phase 2: Implementation Binding]"] E["impl_interface macro"] F["Implementation preserved"] G["Nested extern fn with export_name"] H["Symbol __TraitName_methodName exported"] end subgraph subGraph0["Phase1[Phase 1: Interface Definition]"] A["def_interface macro"] B["Original trait preserved"] C["Hidden module __TraitName_mod created"] D["extern fn __TraitName_methodName declarations"] end A --> B A --> C C --> D D --> L E --> F E --> G G --> H H --> L I --> J J --> K K --> L
Sources: src/lib.rs(L28 - L75) src/lib.rs(L88 - L162) src/lib.rs(L193 - L210)
Code Entity Mapping
Sources: README.md(L47 - L85) src/lib.rs(L45 - L46) src/lib.rs(L113 - L116)
Symbol Generation and Naming Conventions
The macro system uses a consistent naming convention to generate unique extern function symbols that can be linked across crate boundaries.
Symbol Generation Process
Input | Generated Symbol | Purpose |
---|---|---|
trait MyTrait+fn my_method | __MyTrait_my_method | Extern function name |
trait MyTrait | __MyTrait_mod | Hidden module name |
Implementation | #[export_name = "__MyTrait_my_method"] | Linker symbol |
The symbol generation follows this pattern implemented in the macros:
Sources: src/lib.rs(L45 - L46) src/lib.rs(L61) src/lib.rs(L113 - L116) src/lib.rs(L203 - L206)
Function Signature Transformation
The macros perform signature transformations to convert trait methods into extern functions:
The self
parameter is removed from extern function signatures since the implementation struct is instantiated within the exported function body.
Sources: src/lib.rs(L46 - L52) src/lib.rs(L120 - L129)
Cross-Crate Linking Mechanism
The system leverages Rust's extern function linking to enable cross-crate trait implementations without circular dependencies.
Linking Architecture
Sources: src/lib.rs(L69 - L71) src/lib.rs(L148 - L149) src/lib.rs(L209)
Hidden Module System
The def_interface
macro generates hidden modules to contain extern function declarations:
The hidden module serves several purposes:
- Namespace isolation for extern function declarations
- Visibility control with
#[doc(hidden)]
- Snake case warnings suppression with
#[allow(non_snake_case)]
- Access to parent scope types via
use super::*
Sources: src/lib.rs(L65 - L72)
Procedural Macro Implementation Details
Each macro in the system performs specific AST transformations using the syn
, quote
, and proc_macro2
crates.
def_interface Implementation
Sources: src/lib.rs(L36 - L38) src/lib.rs(L41 - L58) src/lib.rs(L62 - L74)
impl_interface Implementation
flowchart TD subgraph subGraph2["Generate[Nested Function Generation]"] C1["Create inline extern fn with export_name"] C2["Embed call_impl in extern fn body"] C3["Preserve original method implementation"] C4["Wrap in method structure"] end subgraph subGraph1["Transform[Method Transformation]"] B1["Clone original method signature"] B2["Generate extern_fn_name string"] B3["Create new_sig without self parameter"] B4["Extract args from original signature"] B5["Generate call_impl based on has_self"] end subgraph subGraph0["Extract[Extraction Phase]"] A1["syn::parse_macro_input!(item as ItemImpl)"] A2["Extract trait_name from ast.trait_"] A3["Extract impl_name from ast.self_ty"] A4["Iterate ast.items for ImplItem::Fn"] end A1 --> A2 A2 --> A3 A3 --> A4 A4 --> B1 B1 --> B2 B2 --> B3 B3 --> B4 B4 --> B5 B5 --> C1 C1 --> C2 C2 --> C3 C3 --> C4
Sources: src/lib.rs(L96 - L106) src/lib.rs(L108 - L138) src/lib.rs(L140 - L161)
call_interface Implementation
The call_interface
macro uses a custom parser to handle multiple calling syntaxes:
flowchart TD subgraph subGraph2["Generate[Call Generation]"] C1["quote! { unsafe { #path :: #extern_fn_name( #args ) } }"] end subgraph subGraph1["PathProcess[Path Processing]"] B1["Extract fn_name = path.pop()"] B2["Extract trait_name = path.pop()"] B3["Generate extern_fn_name"] B4["Reconstruct path with __TraitName_mod"] end subgraph subGraph0["Parse[Parsing Strategy]"] A1["Parse path: Path"] A2["Check for comma or parentheses"] A3["Parse args as Punctuated"] end A1 --> A2 A2 --> A3 A3 --> B1 B1 --> B2 B2 --> B3 B3 --> B4 B4 --> C1
Sources: src/lib.rs(L164 - L184) src/lib.rs(L194 - L210)
Memory Layout and Safety Considerations
The system maintains memory safety by carefully managing the transition between safe trait calls and unsafe extern function calls.
Safety Boundary Management
The safety guarantees rely on:
- ABI Compatibility: All functions use
extern "Rust"
calling convention - Type Preservation: Function signatures are preserved exactly during transformation
- Symbol Uniqueness: Naming convention prevents symbol collisions
- Linker Validation: Missing implementations cause link-time errors, not runtime failures
Sources: src/lib.rs(L54 - L56) src/lib.rs(L148 - L151) src/lib.rs(L209)
Object Instantiation Pattern
The impl_interface
macro generates a specific pattern for handling self
parameters:
flowchart TD subgraph subGraph1["StaticHandling[Static Method Handling]"] B1["Original: fn static_method(args...)"] B2["Extern: fn __Trait_static_method(args...)"] B3["ImplStruct::static_method(args)"] A1["Original: fn method(&self, args...)"] A2["Extern: fn __Trait_method(args...)"] A3["let _impl: ImplStruct = ImplStruct;"] end subgraph subGraph0["SelfHandling[Self Parameter Handling]"] B1["Original: fn static_method(args...)"] B2["Extern: fn __Trait_static_method(args...)"] B3["ImplStruct::static_method(args)"] A1["Original: fn method(&self, args...)"] A2["Extern: fn __Trait_method(args...)"] A3["let _impl: ImplStruct = ImplStruct;"] A4["_impl.method(args)"] end A1 --> A2 A2 --> A3 A3 --> A4 B1 --> B2 B2 --> B3
This pattern ensures that:
- Instance methods get a fresh instance of the implementing struct
- Static methods are called directly on the implementing type
- Memory layout is predictable and doesn't depend on external state
Sources: src/lib.rs(L131 - L138)
Development Guide
Relevant source files
This document provides comprehensive guidance for developers contributing to the crate_interface
project. It covers the development environment setup, project structure, contribution workflows, and development practices specific to this procedural macro crate.
For detailed information about testing practices, see Testing. For CI/CD pipeline specifics, see CI/CD Pipeline. For in-depth project structure analysis, see Project Structure.
Development Environment Setup
The crate_interface
project is a procedural macro crate that requires specific Rust toolchain capabilities and dependencies for development.
Development Workflow
flowchart TD subgraph subGraph0["Local Development Loop"] E["Implement changes"] L["cargo fmt"] M["cargo clippy"] N["cargo test"] O["cargo doc"] end A["Fork repository"] B["Clone locally"] C["Setup development environment"] D["Create feature branch"] F["Run local tests"] G["Update documentation"] H["Submit pull request"] I["CI validation"] J["Code review"] K["Merge to main"] A --> B B --> C C --> D D --> E E --> F E --> L F --> G G --> H H --> I I --> J J --> K L --> M M --> N N --> O O --> E
Sources: Cargo.toml(L1 - L22)
Required Tools and Dependencies
The project uses a minimal but specific set of dependencies for procedural macro development:
Component | Version | Purpose |
---|---|---|
proc-macro2 | 1.0 | Low-level token manipulation |
quote | 1.0 | Code generation from templates |
syn | 2.0 | Rust syntax tree parsing |
The syn
dependency is configured with the full
feature to enable complete Rust syntax parsing capabilities Cargo.toml(L18)
Minimum Rust Version
The project requires Rust 1.57 or later Cargo.toml(L13) which ensures compatibility with:
- Modern
syn
v2.0 APIs - Stable procedural macro features
- Target architectures used in ArceOS ecosystem
Project Structure and Development Architecture
flowchart TD subgraph subGraph4["Development Artifacts"] L["target/"] M[".vscode/"] N["Cargo.lock"] end subgraph subGraph3["CI Configuration"] J[".github/workflows/"] K["CI pipeline definitions"] end subgraph subGraph2["Test Structure"] H["tests/"] I["Integration tests"] end subgraph subGraph1["Source Structure"] G["src/lib.rs"] end subgraph subGraph0["Repository Root"] A["Cargo.toml"] B["src/"] C["tests/"] D[".github/"] E["README.md"] F[".gitignore"] end A --> G B --> G C --> I D --> K F --> L F --> M F --> N
Sources: Cargo.toml(L1 - L22) .gitignore(L1 - L5)
Contribution Workflow
Code Organization Principles
The crate_interface
project follows a single-file library structure with all core functionality contained in src/lib.rs
. This architectural decision supports:
- Simplicity: Single point of entry for all macro definitions
- Maintainability: Reduced complexity in navigation and understanding
- Performance: Minimized compilation overhead for consumers
Development Practices
Procedural Macro Development
When working with the three core macros (def_interface
, impl_interface
, call_interface
), follow these practices:
- Token Stream Handling: Use
proc-macro2::TokenStream
for all internal processing - Error Reporting: Leverage
syn::Error
for compilation-time error messages - Code Generation: Use
quote!
macros for generating Rust code templates
Testing Strategy
The project employs comprehensive integration testing to validate macro functionality across different usage patterns. Test cases should cover:
- Basic interface definition and implementation scenarios
- Cross-crate compilation patterns
- Error conditions and edge cases
- Target architecture compatibility
For complete testing documentation, see Testing.
Dependencies and Build Configuration
flowchart TD subgraph subGraph2["Generated Artifacts"] G["TokenStream processing"] H["Code generation"] I["AST manipulation"] end subgraph subGraph1["Macro System"] D["def_interface"] E["impl_interface"] F["call_interface"] end subgraph subGraph0["Core Dependencies"] A["proc-macro2"] B["quote"] C["syn"] end A --> G B --> H C --> I G --> D G --> E G --> F H --> D H --> E H --> F I --> D I --> E I --> F
Sources: Cargo.toml(L15 - L18)
The build system is configured as a procedural macro crate Cargo.toml(L20 - L21) which enables the proc_macro
attribute and allows the macros to be used by consuming crates.
Version Control and Exclusions
The project maintains a minimal .gitignore
configuration .gitignore(L1 - L5) that excludes:
- Build Artifacts:
/target
directory containing compilation outputs - Editor Configuration:
.vscode
directory for development environment settings - System Files:
.DS_Store
for macOS filesystem metadata - Lock Files:
Cargo.lock
which is typically excluded for library crates
This configuration ensures that only source code and essential project files are tracked in version control.
Package Metadata and Distribution
The crate is configured for distribution through multiple channels:
- Primary Repository: GitHub at
arceos-org/crate_interface
- Documentation: Hosted on docs.rs
- Package Registry: Available through crates.io
- License: Triple-licensed under GPL-3.0-or-later, Apache-2.0, and MulanPSL-2.0
The package is categorized for procedural macro development tools and no-std compatibility Cargo.toml(L12) making it discoverable for embedded and systems programming use cases.
Sources: Cargo.toml(L7 - L12)
Testing
Relevant source files
This document covers the test suite for the crate_interface
crate, including test structure, patterns used to validate macro functionality, and how to run the tests. The tests demonstrate practical usage of the three core macros (def_interface
, impl_interface
, and call_interface!
) and verify that cross-crate trait interfaces work correctly at runtime.
For information about the CI/CD pipeline and automated testing infrastructure, see CI/CD Pipeline. For details about project structure and development setup, see Project Structure.
Test Suite Overview
The crate_interface
crate uses integration tests to validate macro functionality. The test suite is designed to verify that the generated code correctly implements cross-crate trait interfaces through extern function linking.
flowchart TD A["test_crate_interface.rs"] B["SimpleIf trait definition"] C["SimpleIfImpl struct"] D["test_crate_interface_call function"] E["#[def_interface] macro"] F["#[impl_interface] macro"] G["call_interface! macro"] H["Generated extern fn declarations"] I["Generated #[export_name] functions"] J["Runtime function calls"] K["Symbol linking"] L["Test execution and validation"] A --> B A --> C A --> D B --> E C --> F D --> G E --> H F --> I G --> J H --> K I --> K J --> K K --> L
Test Architecture Flow
Sources: tests/test_crate_interface.rs(L1 - L42)
Test Structure and Patterns
The test file follows a specific pattern that mirrors real-world usage of the crate_interface
system across multiple crates, but compressed into a single test file for validation.
Interface Definition Pattern
The test defines a trait using the #[def_interface]
attribute with both default implementations and abstract methods:
flowchart TD A["SimpleIf trait"] B["foo() -> u32"] C["bar(&self, a: u16, b: &[u8], c: &str)"] D["Default implementation: 123"] E["Abstract method"] F["#[def_interface]"] G["Can be overridden"] H["Must be implemented"] A --> B A --> C B --> D C --> E D --> G E --> H F --> A
Interface Definition Components
The trait includes documentation comments and various parameter types to test the macro's handling of different Rust syntax elements.
Sources: tests/test_crate_interface.rs(L3 - L11)
Implementation Pattern
The test implements the interface using the #[impl_interface]
attribute, demonstrating both method override and concrete implementation:
Method | Type | Test Purpose |
---|---|---|
foo() | Static method with override | Tests default method replacement with#[cfg(test)] |
bar() | Instance method | Tests parameter passing and assertions |
The implementation includes conditional compilation (#[cfg(test)]
) to test macro handling of Rust attributes.
Sources: tests/test_crate_interface.rs(L13 - L27)
Module Visibility Testing
The test suite includes a private module to verify that interface calls work correctly across module boundaries:
flowchart TD A["test_crate_interface_call"] B["Direct interface calls"] C["private::test_call_in_mod"] D["super::SimpleIf::bar call"] E["crate::SimpleIf::foo call"] F["Module boundary"] G["Path resolution testing"] A --> B A --> C C --> D C --> E F --> D F --> E G --> D G --> E
Module Testing Pattern
Sources: tests/test_crate_interface.rs(L29 - L34)
Test Execution Patterns
The main test function demonstrates different calling syntaxes supported by the call_interface!
macro:
Interface Call Testing Matrix
Call Pattern | Syntax | Purpose |
---|---|---|
Method with args | call_interface!(SimpleIf::bar, 123, &[2, 3, 5, 7, 11], "test") | Tests parameter passing |
Method return value | assert_eq!(call_interface!(SimpleIf::foo), 456) | Tests return value handling |
Trailing comma | call_interface!(SimpleIf::foo,) | Tests syntax flexibility |
Sources: tests/test_crate_interface.rs(L36 - L41)
Running Tests
The test suite can be executed using standard Rust testing commands:
cargo test # Run all tests
cargo test test_crate_interface_call # Run specific test
Test Validation Points
The tests validate several critical aspects of the macro system:
flowchart TD A["Test Execution"] B["Macro Expansion"] C["Symbol Linking"] D["Runtime Behavior"] E["def_interface generates extern declarations"] F["impl_interface generates export functions"] G["call_interface generates safe wrappers"] H["Exported symbols are found"] I["Function signatures match"] J["Parameters pass correctly"] K["Return values work"] L["Assertions pass"] M["Compilation success"] N["Linking success"] O["Runtime success"] A --> B A --> C A --> D B --> E B --> F B --> G C --> H C --> I D --> J D --> K D --> L E --> M F --> M G --> M H --> N I --> N J --> O K --> O L --> O
Test Validation Flow
The test verifies that:
- The byte array parameter
&[2, 3, 5, 7, 11]
correctly passes through the interface - The assertion
assert_eq!(b[1], 3)
validates parameter integrity - The overridden
foo()
method returns456
instead of the default123
- Interface calls work from different module contexts
Sources: tests/test_crate_interface.rs(L25) tests/test_crate_interface.rs(L39)
Test Coverage Areas
The integration test covers the complete macro workflow in a single compilation unit, which simulates the cross-crate scenario that the macros are designed to support:
Coverage Area | Test Elements | Validation |
---|---|---|
Trait definition | #[def_interface], default methods, abstract methods | Macro expansion correctness |
Implementation | #[impl_interface], method override, concrete implementation | Symbol export generation |
Interface calls | call_interface!variations, parameter types, return values | Runtime linking and execution |
Module boundaries | Private module calls, path resolution | Cross-module functionality |
Attribute handling | #[cfg(test)], documentation comments | Rust syntax compatibility |
Sources: tests/test_crate_interface.rs(L1 - L42)
CI/CD Pipeline
Relevant source files
This document covers the continuous integration and deployment pipeline for the crate_interface
crate, implemented using GitHub Actions. The pipeline ensures code quality, cross-platform compatibility, and automated documentation deployment.
For information about the testing strategies and test suite organization, see Testing. For details about the overall project organization, see Project Structure.
Pipeline Overview
The CI/CD pipeline consists of three main jobs that work together to validate, test, and deploy the crate across multiple Rust toolchains and target architectures.
CI Workflow Structure
flowchart TD subgraph Outputs["Outputs"] F["Quality Validation"] G["Cross-Platform Testing"] H["GitHub Pages Deployment"] end subgraph Jobs["Jobs"] C["metadata"] D["ci"] E["doc"] end subgraph Triggers["Triggers"] A["push"] B["pull_request"] end A --> C B --> C C --> D C --> E D --> F D --> G E --> H
Sources: .github/workflows/ci.yml(L1 - L5) .github/workflows/ci.yml(L6 - L27) .github/workflows/ci.yml(L28 - L58) .github/workflows/ci.yml(L59 - L82)
Metadata Extraction Job
The metadata
job extracts essential crate information that other jobs depend on, particularly the minimum supported Rust version (MSRV).
Metadata Extraction Process
flowchart TD subgraph Extracted_Data["Extracted_Data"] F["name"] G["version"] H["rust_version"] end subgraph metadata_job["metadata_job"] A["actions/checkout@v4"] B["dtolnay/rust-toolchain@stable"] C["cargo metadata --no-deps"] D["jq processing"] E["GITHUB_OUTPUT"] end A --> B B --> C C --> D D --> E E --> F E --> G E --> H
The job uses cargo metadata --no-deps --format-version=1
to extract crate metadata and processes it with jq
to output the crate name, version, and minimum Rust version. These outputs are made available to downstream jobs via GitHub Actions output variables.
Sources: .github/workflows/ci.yml(L6 - L26)
Matrix Testing Job
The ci
job implements a comprehensive matrix testing strategy across multiple Rust toolchains and target architectures, with specific focus on embedded and no-std
environments.
Testing Matrix Configuration
Rust Toolchain | x86_64-unknown-linux-gnu | x86_64-unknown-none | riscv64gc-unknown-none-elf | aarch64-unknown-none-softfloat |
---|---|---|---|---|
nightly | ✅ | ✅ | ✅ | ✅ |
MSRV | ✅ | ❌* | ✅ | ✅ |
*Excluded due to rust-std
component unavailability for older toolchains on x86_64-unknown-none
.
Quality Gates Pipeline
flowchart TD subgraph Conditions["Conditions"] H["Test only on x86_64-unknown-linux-gnu"] I["Test only on non-MSRV toolchain"] end subgraph Quality_Gates["Quality_Gates"] D["cargo fmt --all --check"] E["cargo clippy --target TARGET --all-features"] F["cargo build --target TARGET --all-features"] G["cargo test --target TARGET"] end subgraph Setup["Setup"] A["actions/checkout@v4"] B["dtolnay/rust-toolchain"] C["Install components: rust-src, clippy, rustfmt"] end A --> B B --> C C --> D D --> E E --> F F --> G G --> H G --> I
The testing process enforces several quality gates in sequence:
- Code formatting: Ensures consistent style using
cargo fmt --all -- --check
- Linting: Validates code quality with
cargo clippy
across all target architectures - Compilation: Verifies successful builds for all target platforms
- Unit testing: Runs tests only on
x86_64-unknown-linux-gnu
with non-MSRV toolchains
Sources: .github/workflows/ci.yml(L28 - L58) .github/workflows/ci.yml(L31 - L39) .github/workflows/ci.yml(L49 - L57)
Documentation Job
The doc
job builds and deploys comprehensive documentation to GitHub Pages using a nightly Rust toolchain.
Documentation Build and Deployment
flowchart TD subgraph Conditions["Conditions"] K["Deploy only on default branch"] end subgraph Deployment["Deployment"] H["JamesIves/github-pages-deploy-action@v4"] I["Deploy to gh-pages branch"] J["GitHub Pages hosting"] end subgraph Quality_Controls["Quality_Controls"] E["RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links"] F["RUSTDOCFLAGS: -D missing-docs"] G["continue-on-error for non-main branches"] end subgraph Build_Process["Build_Process"] A["actions/checkout@v4"] B["dtolnay/rust-toolchain@nightly"] C["cargo doc --no-deps --all-features"] D["Generate index.html redirect"] end A --> B B --> C C --> D D --> H E --> C F --> C G --> C H --> I H --> K I --> J
The documentation pipeline includes:
- Strict documentation validation: Uses
RUSTDOCFLAGS
to enforce complete documentation coverage and valid internal links - Automatic index generation: Creates a redirect from the root documentation page to the main crate documentation
- Conditional deployment: Only deploys to GitHub Pages when building the default branch
- Single-commit deployment: Uses
single-commit: true
to maintain a clean deployment history
Sources: .github/workflows/ci.yml(L59 - L82) .github/workflows/ci.yml(L67 - L68) .github/workflows/ci.yml(L76 - L82)
Target Architecture Support
The pipeline specifically tests against target architectures relevant to the ArceOS ecosystem and embedded systems development:
x86_64-unknown-linux-gnu
: Standard Linux development and testing environmentx86_64-unknown-none
: Bare-metal x86_64 systems without OSriscv64gc-unknown-none-elf
: RISC-V 64-bit bare-metal systems with compressed instructionsaarch64-unknown-none-softfloat
: ARM64 bare-metal systems with software floating-point
These targets ensure the crate functions correctly in both hosted and no-std
environments, which is essential for the cross-crate interface functionality in kernel and embedded contexts.
Sources: .github/workflows/ci.yml(L35) .github/workflows/ci.yml(L37 - L39)
Pipeline Triggers and Permissions
The workflow triggers on all push
and pull_request
events, ensuring comprehensive validation for both direct commits and proposed changes. The doc
job includes contents: write
permissions specifically for GitHub Pages deployment.
The pipeline uses modern GitHub Actions including actions/checkout@v4
and dtolnay/rust-toolchain
for reliable and up-to-date build environments.
Sources: .github/workflows/ci.yml(L1 - L3) .github/workflows/ci.yml(L63 - L64)
Project Structure
Relevant source files
This document covers the organization and configuration of the crate_interface
repository, including its file structure, dependencies, build setup, and development environment. For information about the actual macro implementations and testing, see Testing and Macro Reference.
Repository Organization
The crate_interface
project follows a standard Rust crate structure optimized for procedural macro development. The repository is intentionally minimal, focusing on a single library crate that exports three core procedural macros.
flowchart TD subgraph Documentation["Documentation"] ReadmeMd["README.md"] end subgraph CI/CD["CI/CD"] GithubDir[".github/"] WorkflowsDir[".github/workflows/"] CiYml["ci.yml"] end subgraph Testing["Testing"] TestsDir["tests/"] TestFiles["Integration test files"] end subgraph subGraph1["Source Code"] SrcDir["src/"] LibRs["src/lib.rs"] end subgraph subGraph0["Configuration Files"] Root["crate_interface/"] CargoToml["Cargo.toml"] GitIgnore[".gitignore"] end GithubDir --> WorkflowsDir Root --> CargoToml Root --> GitIgnore Root --> GithubDir Root --> ReadmeMd Root --> SrcDir Root --> TestsDir SrcDir --> LibRs TestsDir --> TestFiles WorkflowsDir --> CiYml
Repository Structure Overview
Sources: Cargo.toml(L1 - L22) .gitignore(L1 - L5)
Package Configuration
The project is configured as a procedural macro crate through its Cargo.toml
manifest. The package metadata defines the crate's identity within the ArceOS ecosystem and its distribution characteristics.
Package Configuration Details
Field | Value | Purpose |
---|---|---|
name | "crate_interface" | Crate identifier for Cargo and crates.io |
version | "0.1.4" | Semantic versioning for API compatibility |
edition | "2021" | Rust edition for language features |
rust-version | "1.57" | Minimum supported Rust version |
proc-macro | true | Enables procedural macro compilation |
Sources: Cargo.toml(L1 - L14) Cargo.toml(L20 - L22)
Dependencies Architecture
The crate relies on three essential procedural macro development dependencies, each serving a specific role in the macro compilation pipeline.
flowchart TD subgraph subGraph2["Processing Pipeline"] TokenStream["TokenStream manipulation"] AstParsing["AST parsing and analysis"] CodeGeneration["Code generation"] ExternFunctions["extern function synthesis"] end subgraph subGraph1["Core Macro Functions"] DefInterface["def_interface macro"] ImplInterface["impl_interface macro"] CallInterface["call_interface! macro"] end subgraph subGraph0["External Dependencies"] ProcMacro2["proc-macro2 v1.0"] Quote["quote v1.0"] Syn["syn v2.0 with full features"] end AstParsing --> CallInterface AstParsing --> DefInterface AstParsing --> ImplInterface CallInterface --> ExternFunctions CodeGeneration --> CallInterface CodeGeneration --> DefInterface CodeGeneration --> ImplInterface DefInterface --> ExternFunctions ImplInterface --> ExternFunctions ProcMacro2 --> TokenStream Quote --> CodeGeneration Syn --> AstParsing TokenStream --> CallInterface TokenStream --> DefInterface TokenStream --> ImplInterface
Dependency Functions
Dependency | Version | Purpose |
---|---|---|
proc-macro2 | 1.0 | TokenStream manipulation and span preservation |
quote | 1.0 | Rust code generation with interpolation |
syn | 2.0 | Rust syntax tree parsing with full feature set |
The syn
dependency includes the "full"
feature set to enable parsing of complete Rust syntax, including trait definitions, implementations, and method signatures required by the macro system.
Sources: Cargo.toml(L15 - L18)
Build System Configuration
The crate is configured as a procedural macro library, which affects compilation behavior and usage patterns.
flowchart TD subgraph subGraph2["Usage Context"] MacroExpansion["Compile-time macro expansion"] CodeGeneration["Generated extern functions"] CrossCrateLink["Cross-crate symbol linking"] end subgraph subGraph1["Compilation Output"] DylibFormat[".so/.dll/.dylib"] CompilerPlugin["Compiler plugin format"] end subgraph subGraph0["Build Target"] ProcMacroLib["proc-macro = true"] LibCrate["Library Crate Type"] end CodeGeneration --> CrossCrateLink CompilerPlugin --> MacroExpansion DylibFormat --> CompilerPlugin LibCrate --> DylibFormat MacroExpansion --> CodeGeneration ProcMacroLib --> LibCrate
Build Configuration Implications
The proc-macro = true
setting in [lib]
configures the crate to:
- Compile as a dynamic library for use by the Rust compiler
- Load at compile-time during macro expansion phases
- Generate code that becomes part of dependent crates
- Enable cross-crate trait interface functionality
Sources: Cargo.toml(L20 - L22)
Development Environment
The project supports multiple development workflows and target environments, as configured through the CI pipeline and package metadata.
flowchart TD subgraph subGraph3["Environment Support"] StdEnv["std environments"] NoStdEnv["no-std environments"] EmbeddedTargets["Embedded targets"] end subgraph subGraph2["Development Commands"] CargoFmt["cargo fmt --check"] CargoClippy["cargo clippy"] CargoBuild["cargo build"] CargoTest["cargo test"] CargoDoc["cargo doc"] end subgraph subGraph1["Target Architectures"] X86Linux["x86_64-unknown-linux-gnu"] X86None["x86_64-unknown-none"] RiscV["riscv64gc-unknown-none-elf"] Aarch64["aarch64-unknown-none-softfloat"] end subgraph subGraph0["Supported Toolchains"] Stable["stable"] Beta["beta"] Nightly["nightly"] Msrv["1.57.0 (MSRV)"] end Aarch64 --> EmbeddedTargets Beta --> X86None CargoBuild --> EmbeddedTargets CargoClippy --> NoStdEnv CargoFmt --> StdEnv Msrv --> Aarch64 Nightly --> RiscV RiscV --> EmbeddedTargets Stable --> X86Linux X86Linux --> StdEnv X86None --> NoStdEnv
Environment Requirements
The development environment is designed to support:
- Standard library environments for general development
no-std
environments for embedded and kernel development- Multiple architectures including x86_64, RISC-V, and ARM64
- Cross-compilation for bare-metal targets
Sources: Cargo.toml(L12 - L13) .gitignore(L1 - L5)
Version Control Configuration
The .gitignore
configuration excludes standard Rust development artifacts and common editor files.
Excluded Files and Directories
Pattern | Purpose |
---|---|
/target | Cargo build artifacts and dependencies |
/.vscode | Visual Studio Code workspace configuration |
.DS_Store | macOS file system metadata |
Cargo.lock | Dependency version lockfile (appropriate for libraries) |
The exclusion of Cargo.lock
follows Rust library conventions, allowing dependent crates to resolve their own dependency versions while maintaining compatibility with the specified version ranges.
Sources: .gitignore(L1 - L5)
Overview
Relevant source files
Purpose and Scope
This document provides an overview of the lazyinit
crate, a Rust library that enables thread-safe lazy initialization of static values. The material covers the crate's core purpose, key features, architecture, and usage patterns. For detailed implementation specifics of the LazyInit<T>
struct, see LazyInit Implementation. For API documentation and method details, see API Reference. For project configuration and dependencies, see Project Configuration.
What is LazyInit
The lazyinit
crate provides the LazyInit<T>
type for initializing static values lazily in a thread-safe manner. Unlike compile-time initialization or macro-based solutions like lazy_static
, LazyInit<T>
allows runtime initialization with arbitrary logic while guaranteeing that initialization occurs exactly once across all threads.
The core abstraction is the LazyInit<T>
struct, which wraps a value of type T
and manages its initialization state through atomic operations. The value remains uninitialized until the first call to init_once
or call_once
, at which point it becomes permanently initialized and accessible.
Sources: README.md(L7 - L11) Cargo.toml(L6)
Key Features and Capabilities
Feature | Description | Methods |
---|---|---|
Thread-Safe Initialization | Guarantees exactly one initialization across multiple threads | init_once,call_once |
Flexible Initialization | Supports both direct value initialization and closure-based initialization | init_once(value), `call_once( |
Safe Access Patterns | Provides both safe and unsafe access methods | get(),get_mut(),get_unchecked() |
State Inspection | Allows checking initialization status without accessing the value | is_inited() |
Direct Access | ImplementsDerefandDerefMutfor transparent access after initialization | *VALUE,&mut *VALUE |
No-std Compatibility | Works in embedded and kernel environments without standard library | No external dependencies |
Sources: README.md(L15 - L57) Cargo.toml(L12)
Core Architecture
The following diagram illustrates the relationship between the main components and their roles in the codebase:
LazyInit Core Components and Relationships
flowchart TD subgraph subGraph2["Memory Safety Guarantees"] single_init["Single Initialization Guarantee"] thread_safety["Thread Safety"] memory_ordering["Memory Ordering (Acquire/Relaxed)"] end subgraph subGraph1["Internal State Management"] AtomicBool["AtomicBool (initialization flag)"] UnsafeCell["UnsafeCell<MaybeUninit<T>> (data storage)"] compare_exchange["compare_exchange_weak operations"] end subgraph subGraph0["Public API Layer"] LazyInit["LazyInit<T>"] new["LazyInit::new()"] init_once["init_once(value: T)"] call_once["call_once(f: FnOnce() -> T)"] get["get() -> Option<&T>"] get_mut["get_mut() -> Option<&mut T>"] is_inited["is_inited() -> bool"] get_unchecked["get_unchecked() -> &T"] end AtomicBool --> compare_exchange LazyInit --> call_once LazyInit --> get LazyInit --> get_mut LazyInit --> get_unchecked LazyInit --> init_once LazyInit --> is_inited LazyInit --> new call_once --> AtomicBool call_once --> UnsafeCell compare_exchange --> memory_ordering compare_exchange --> single_init compare_exchange --> thread_safety get --> AtomicBool get --> UnsafeCell get_mut --> UnsafeCell get_unchecked --> UnsafeCell init_once --> AtomicBool init_once --> UnsafeCell is_inited --> AtomicBool
Sources: README.md(L16 - L28) README.md(L32 - L57)
API Surface Overview
The following diagram shows the complete API surface and usage patterns supported by LazyInit<T>
:
LazyInit API Methods and Usage Patterns
flowchart TD subgraph subGraph4["Return Types"] option_some["Some(&T) / Some(&mut T)"] option_none["None"] panic_behavior["Panic on double init"] direct_ref["&T / &mut T"] end subgraph subGraph3["Status Methods"] is_inited_method["is_inited() -> bool"] end subgraph subGraph2["Access Methods"] get_method["get() -> Option<&T>"] get_mut_method["get_mut() -> Option<&mut T>"] deref_impl["Deref/DerefMut traits"] get_unchecked_method["get_unchecked() -> &T (unsafe)"] end subgraph subGraph1["Initialization Methods"] init_once_method["init_once(value: T)"] call_once_method["call_once(f: FnOnce() -> T)"] end subgraph Creation["Creation"] LazyInit_new["LazyInit::new()"] end true_false["true / false"] LazyInit_new --> call_once_method LazyInit_new --> init_once_method call_once_method --> option_none call_once_method --> option_some deref_impl --> direct_ref deref_impl --> panic_behavior get_method --> option_none get_method --> option_some get_mut_method --> option_none get_mut_method --> option_some get_unchecked_method --> direct_ref init_once_method --> option_some init_once_method --> panic_behavior is_inited_method --> true_false
Sources: README.md(L18 - L27) README.md(L39 - L56)
Use Cases and When to Use LazyInit
LazyInit<T>
is designed for scenarios where you need lazy initialization with these characteristics:
Primary Use Cases
- Static Variables with Runtime Initialization: When you need a static variable that requires runtime computation or I/O to initialize
- Expensive Computations: When initialization is costly and should only occur if the value is actually needed
- Resource Initialization: When initializing system resources, file handles, or network connections that should be shared globally
- Multi-threaded Environments: When multiple threads might attempt to initialize the same static value concurrently
Advantages Over Alternatives
- vs.
lazy_static!
: Provides more flexible initialization patterns and doesn't require macro-based declaration - vs.
std::sync::Once
: Combines synchronization with storage, providing a more ergonomic API - vs.
std::cell::OnceCell
: Offers thread-safe initialization for static contexts
Sources: README.md(L9 - L11) Cargo.toml(L11)
Project Characteristics
No-std Compatibility
The crate is designed for no-std
environments, making it suitable for:
- Embedded systems
- Kernel-level code
- WebAssembly targets
- Bare metal programming
Licensing and Distribution
Aspect | Details |
---|---|
Licenses | Triple-licensed: GPL-3.0-or-later, Apache-2.0, MulanPSL-2.0 |
Categories | no-std,rust-patterns |
Keywords | lazy,initialization,static |
Dependencies | None (pure Rust implementation) |
Supported Targets
The crate supports multiple target architectures through CI validation, including both hosted and bare-metal targets.
Sources: Cargo.toml(L1 - L15)
LazyInit Implementation
Relevant source files
This document provides a comprehensive analysis of the LazyInit<T>
struct implementation, covering its internal architecture, state management, and the mechanisms that enable thread-safe lazy initialization. This page focuses on the core implementation details and design decisions.
For specific API documentation and method signatures, see API Reference. For detailed thread safety mechanisms and memory ordering, see Thread Safety & Memory Model. For practical usage examples and patterns, see Usage Patterns & Examples.
Core Architecture
The LazyInit<T>
struct implements thread-safe lazy initialization through a two-field design that separates state tracking from data storage. The implementation leverages atomic operations and unsafe memory management to achieve both safety and performance.
Struct Layout
flowchart TD subgraph subGraph2["Data Storage"] E["MaybeUninit<T>::uninit()"] F["MaybeUninit<T>::init(value)"] end subgraph subGraph1["State Tracking"] C["false = Uninitialized"] D["true = Initialized"] end subgraph LazyInit<T>["LazyInit<T>"] A["inited: AtomicBool"] B["data: UnsafeCell<MaybeUninit<T>>"] end A --> C A --> D B --> E B --> F C --> D E --> F
The inited
field serves as the synchronization point for all threads, while data
provides the actual storage location. This separation allows atomic state transitions independent of the data type T
.
Sources: src/lib.rs(L14 - L17)
Memory Safety Guarantees
The implementation achieves memory safety through careful coordination of atomic operations and unsafe memory access:
flowchart TD subgraph subGraph2["Atomic Coordination"] I["compare_exchange_weak"] J["load(Acquire)"] end subgraph subGraph1["Unsafe Internal Layer"] E["force_get()"] F["force_get_mut()"] G["assume_init_ref()"] H["write()"] end subgraph subGraph0["Safe API Layer"] A["init_once()"] B["call_once()"] C["get()"] D["is_inited()"] end A --> I B --> I C --> J D --> J E --> G F --> G I --> H J --> E
All unsafe operations are gated behind atomic checks that ensure the data has been properly initialized before access.
Sources: src/lib.rs(L36 - L47) src/lib.rs(L53 - L67) src/lib.rs(L77 - L83) src/lib.rs(L118 - L126)
State Machine
The LazyInit<T>
implementation follows a simple but robust state machine with three logical states:
stateDiagram-v2 [*] --> Uninitialized : "new()" Uninitialized --> InitInProgress : "init_once() / call_once()" InitInProgress --> Initialized : "compare_exchange success" InitInProgress --> Uninitialized : "compare_exchange failure" Initialized --> [*] : "drop()" note left of Uninitialized : ['inited = false<br>data = MaybeUninit::uninit()'] note left of InitInProgress : ['Transient state during<br>compare_exchange_weak operation'] note left of Initialized : ['inited = true<br>data = valid T instance']
The state machine ensures that once initialization succeeds, the instance remains in the Initialized
state for its entire lifetime. The transient InitInProgress
state handles race conditions between multiple initializing threads.
Sources: src/lib.rs(L24 - L29) src/lib.rs(L37 - L39) src/lib.rs(L57 - L59) src/lib.rs(L177 - L181)
Method Categories
The LazyInit<T>
API is organized into distinct categories based on functionality and safety guarantees:
Category | Methods | Safety | Purpose |
---|---|---|---|
Construction | new(),default() | Safe | Create uninitialized instances |
Initialization | init_once(),call_once() | Safe | Perform one-time initialization |
State Inspection | is_inited() | Safe | Check initialization status |
Safe Access | get(),get_mut() | Safe | Access with runtime checks |
Direct Access | deref(),deref_mut() | Safe but panics | Transparent access toT |
Unsafe Access | get_unchecked(),get_mut_unchecked() | Unsafe | Performance-critical access |
Internal | force_get(),force_get_mut() | Private unsafe | Implementation details |
Initialization Methods
flowchart TD subgraph Actions["Actions"] F["write(data) / write(f())"] G["panic! / return None"] end subgraph Outcomes["Outcomes"] D["Ok(_): First to initialize"] E["Err(_): Lost race"] end subgraph subGraph1["Atomic Competition"] C["compare_exchange_weak(false, true)"] end subgraph subGraph0["Initialization Entry Points"] A["init_once(data: T)"] B["call_once<F>(f: F)"] end A --> C B --> C C --> D C --> E D --> F E --> G
Both initialization methods use the same atomic compare-exchange operation but handle initialization failure differently - init_once()
panics while call_once()
returns None
.
Sources: src/lib.rs(L36 - L47) src/lib.rs(L53 - L67)
Thread Safety Implementation
The thread safety of LazyInit<T>
relies on the atomic inited
flag and proper memory ordering:
flowchart TD subgraph Implementation["Implementation"] H["AtomicBool::compare_exchange_weak"] I["Ordering::Acquire / Relaxed"] J["UnsafeCell<MaybeUninit<T>>"] end subgraph Guarantees["Guarantees"] E["Single initialization"] F["Memory ordering"] G["Data race prevention"] end subgraph Requirements["Requirements"] C["T: Send"] D["T: Send + Sync"] end subgraph subGraph0["Thread Safety Traits"] A["Send for LazyInit<T>"] B["Sync for LazyInit<T>"] end A --> C B --> D D --> E D --> F D --> G E --> H F --> I G --> J
The unsafe impl
blocks for Send
and Sync
establish the thread safety contract, requiring appropriate bounds on the contained type T
.
Sources: src/lib.rs(L19 - L20) src/lib.rs(L37 - L39) src/lib.rs(L70 - L72)
Trait Implementations
The LazyInit<T>
struct implements several standard traits to provide ergonomic usage:
Deref and DerefMut Implementation
flowchart TD subgraph Outcomes["Outcomes"] D["force_get() / force_get_mut()"] E["panic_message()"] end subgraph subGraph2["Safety Check"] C["is_inited()"] end subgraph subGraph1["DerefMut Trait"] B["deref_mut(&mut self) -> &mut T"] end subgraph subGraph0["Deref Trait"] A["deref(&self) -> &T"] end A --> C B --> C C --> D C --> E
The Deref
implementations enable transparent access to the wrapped value using the *
operator, with runtime panics for uninitialized access.
Sources: src/lib.rs(L153 - L174) src/lib.rs(L128 - L133)
Debug and Drop Implementation
The Debug
implementation provides meaningful output for both initialized and uninitialized states, while Drop
ensures proper cleanup of initialized values:
flowchart TD subgraph Debug::fmt["Debug::fmt"] C["None -> <uninitialized>"] subgraph Drop::drop["Drop::drop"] D["if self.is_inited()"] E["drop_in_place(data.as_mut_ptr())"] A["match self.get()"] B["Some(s) -> format with data"] end end A --> B A --> C D --> E
Sources: src/lib.rs(L136 - L144) src/lib.rs(L176 - L182)
Performance Considerations
The implementation includes several performance optimizations:
compare_exchange_weak
- Uses the weak variant for better performance on architectures where it matters- Inline annotations - Critical path methods are marked
#[inline]
for optimization - Minimal overhead - Only two words of storage overhead regardless of
T
- Fast path optimization - Post-initialization access is a simple atomic load followed by unsafe dereference
The get_unchecked()
methods provide a performance escape hatch for scenarios where the initialization state is guaranteed by external logic.
Sources: src/lib.rs(L37 - L39) src/lib.rs(L101 - L105) src/lib.rs(L112 - L116) src/lib.rs(L118 - L126)
API Reference
Relevant source files
This document provides comprehensive documentation of all public methods, traits, and behavior of the LazyInit<T>
struct. It covers method signatures, safety guarantees, panic conditions, and usage semantics for thread-safe lazy initialization.
For implementation details about thread safety mechanisms and memory ordering, see Thread Safety & Memory Model. For practical usage examples and patterns, see Usage Patterns & Examples.
Method Categories and State Flow
The LazyInit<T>
API is organized into distinct categories based on functionality and safety guarantees:
flowchart TD subgraph subGraph4["Automatic Traits"] deref["Deref/DerefMut"] debug["Debug"] default["Default"] drop["Drop"] end subgraph subGraph2["Safe Access"] get["get() -> Option<&T>"] get_mut["get_mut() -> Option<&mut T>"] is_inited["is_inited() -> bool"] end subgraph subGraph1["Initialization Methods"] init_once["init_once(data: T)"] call_once["call_once(f: F)"] end subgraph Constructor["Constructor"] new["LazyInit::new()"] end subgraph subGraph3["Unsafe Access"] get_unchecked["get_unchecked() -> &T"] get_mut_unchecked["get_mut_unchecked() -> &mut T"] end call_once --> get call_once --> get_mut get --> deref get_mut --> deref init_once --> get init_once --> get_mut new --> call_once new --> init_once
Sources: src/lib.rs(L22 - L182)
Constructor Methods
LazyInit::new()
pub const fn new() -> Self
Creates a new uninitialized LazyInit<T>
instance. This is a const
function, making it suitable for static variable initialization.
Property | Value |
---|---|
Thread Safety | Yes |
Const | Yes |
Panics | Never |
Return | New uninitialized instance |
The internal state is initialized with inited: false
and uninitialized memory for the data field.
Sources: src/lib.rs(L23 - L29)
Initialization Methods
init_once()
#![allow(unused)] fn main() { pub fn init_once(&self, data: T) -> &T }
Initializes the value exactly once with the provided data. Uses atomic compare-and-swap to ensure only one thread can succeed.
Property | Value |
---|---|
Thread Safety | Yes |
Atomicity | compare_exchange_weak |
Panics | If already initialized |
Return | Reference to initialized value |
Panic Conditions:
- Called when value is already initialized
Memory Ordering:
- Success:
Ordering::Acquire
- Failure:
Ordering::Relaxed
Sources: src/lib.rs(L31 - L47)
call_once()
#![allow(unused)] fn main() { pub fn call_once<F>(&self, f: F) -> Option<&T> where F: FnOnce() -> T }
Performs initialization using a closure exactly once. Returns None
if already initialized, avoiding panics.
Property | Value |
---|---|
Thread Safety | Yes |
Atomicity | compare_exchange_weak |
Panics | Never (returnsNoneinstead) |
Return | Some(&T)on success,Noneif already initialized |
Behavior:
- Only one thread succeeds in initialization
- Failed threads receive
None
rather than panicking - Closure is called exactly once across all threads
Sources: src/lib.rs(L49 - L67)
Safe Access Methods
get()
#![allow(unused)] fn main() { pub fn get(&self) -> Option<&T> }
Returns a reference to the initialized value, or None
if uninitialized.
Property | Value |
---|---|
Thread Safety | Yes |
Panics | Never |
Return | Some(&T)if initialized,Noneotherwise |
Sources: src/lib.rs(L74 - L83)
get_mut()
#![allow(unused)] fn main() { pub fn get_mut(&mut self) -> Option<&mut T> }
Returns a mutable reference to the initialized value, or None
if uninitialized. Requires exclusive access to the LazyInit
instance.
Property | Value |
---|---|
Thread Safety | Yes (requires&mut self) |
Panics | Never |
Return | Some(&mut T)if initialized,Noneotherwise |
Sources: src/lib.rs(L85 - L94)
is_inited()
#![allow(unused)] fn main() { pub fn is_inited(&self) -> bool }
Checks initialization state without accessing the value.
Property | Value |
---|---|
Thread Safety | Yes |
Memory Ordering | Ordering::Acquire |
Panics | Never |
Return | trueif initialized,falseotherwise |
Sources: src/lib.rs(L69 - L72)
Unsafe Access Methods
get_unchecked()
#![allow(unused)] fn main() { pub unsafe fn get_unchecked(&self) -> &T }
Returns a reference to the value without checking initialization state. Provides maximum performance for hot paths.
Property | Value |
---|---|
Safety | UNSAFE- Must be called after initialization |
Thread Safety | Yes (if preconditions met) |
Debug Assertion | Checksis_inited()in debug builds |
Panics | Undefined behavior if uninitialized |
Safety Requirements:
- Must only be called after successful initialization
- Caller must ensure initialization has occurred
Sources: src/lib.rs(L96 - L105)
get_mut_unchecked()
#![allow(unused)] fn main() { pub unsafe fn get_mut_unchecked(&mut self) -> &mut T }
Returns a mutable reference without checking initialization state.
Property | Value |
---|---|
Safety | UNSAFE- Must be called after initialization |
Thread Safety | Yes (requires&mut self) |
Debug Assertion | Checksis_inited()in debug builds |
Panics | Undefined behavior if uninitialized |
Safety Requirements:
- Must only be called after successful initialization
- Requires exclusive access to the
LazyInit
instance
Sources: src/lib.rs(L107 - L116)
State Transition Diagram
stateDiagram-v2 [*] --> Uninitialized : "new()" Uninitialized --> Initialized : "init_once(data)" Uninitialized --> Initialized : "call_once(f) -> Some" Uninitialized --> Initialized note left of Initialized : ['Deref panics if called<br>on uninitialized value'] note left of Initialized----note : ['Uninitialized'] Initialized --> Initialized Initialized --> Initialized : "Deref/DerefMut" Initialized --> Initialized Initialized --> [*] : "Drop" Uninitialized --> [*] : "Drop" note left of Uninitialized : ['init_once() panics if called<br>on already initialized value'] note left of Initialized : ['Deref panics if called<br>on uninitialized value']
Sources: src/lib.rs(L22 - L182)
Trait Implementations
DerefandDerefMut
#![allow(unused)] fn main() { impl<T> Deref for LazyInit<T> { type Target = T; fn deref(&self) -> &T } impl<T> DerefMut for LazyInit<T> { fn deref_mut(&mut self) -> &mut T } }
Enables direct access to the contained value using the dereference operator (*
).
Property | Value |
---|---|
Panics | If value is uninitialized |
Thread Safety | Yes |
Usage | *lazy_initorlazy_init.method() |
Panic Message: "Use uninitialized value: LazyInit<T>"
Sources: src/lib.rs(L153 - L174)
Debug
impl<T: fmt::Debug> fmt::Debug for LazyInit<T>
Provides debug formatting that safely handles both initialized and uninitialized states.
Output Format:
- Initialized:
"LazyInit { data: <debug_value> }"
- Uninitialized:
"LazyInit { <uninitialized> }"
Sources: src/lib.rs(L136 - L145)
Default
#![allow(unused)] fn main() { impl<T> Default for LazyInit<T> { fn default() -> Self { Self::new() } } }
Creates a new uninitialized instance, equivalent to LazyInit::new()
.
Sources: src/lib.rs(L147 - L151)
Drop
impl<T> Drop for LazyInit<T>
Properly destroys the contained value if initialized, ensuring no memory leaks.
Behavior:
- Checks initialization state before dropping
- Only drops the contained value if initialized
- Safe for uninitialized instances
Sources: src/lib.rs(L176 - L182)
Thread Safety Traits
unsafe impl<T: Send + Sync> Sync for LazyInit<T> {}
unsafe impl<T: Send> Send for LazyInit<T> {}
Manual trait implementations that enable thread safety when the contained type supports it.
Trait | Requirement | Purpose |
---|---|---|
Send | T: Send | Can be transferred between threads |
Sync | T: Send + Sync | Can be shared between threads |
Sources: src/lib.rs(L19 - L20)
Method Safety Summary
Method | Safety Level | Panics | Thread Safe |
---|---|---|---|
new() | Safe | Never | Yes |
init_once() | Safe | If already initialized | Yes |
call_once() | Safe | Never | Yes |
get() | Safe | Never | Yes |
get_mut() | Safe | Never | Yes |
is_inited() | Safe | Never | Yes |
get_unchecked() | Unsafe | UB if uninitialized | Yes* |
get_mut_unchecked() | Unsafe | UB if uninitialized | Yes* |
Deref/DerefMut | Safe | If uninitialized | Yes |
*Thread safe only if safety preconditions are met.
Sources: src/lib.rs(L22 - L182)
Thread Safety & Memory Model
Relevant source files
This document explains the synchronization mechanisms, atomic operations, and memory ordering guarantees that make LazyInit<T>
thread-safe. It covers the low-level implementation details of how concurrent access is coordinated and memory safety is maintained across multiple threads.
For information about the high-level API and usage patterns, see API Reference and Usage Patterns & Examples.
Synchronization Primitives
The LazyInit<T>
implementation relies on two core synchronization primitives to achieve thread safety:
Component | Type | Purpose |
---|---|---|
inited | AtomicBool | Tracks initialization state atomically |
data | UnsafeCell<MaybeUninit | Provides interior mutability for the stored value |
Atomic State Management
The initialization state is tracked using an AtomicBool
that serves as the primary coordination mechanism between threads. This atomic variable ensures that only one thread can successfully transition from the uninitialized to initialized state.
flowchart TD A["AtomicBool_inited"] B["compare_exchange_weak(false, true)"] C["CAS_Success"] D["Write_data_initialize"] E["Already_initialized"] F["Return_reference"] G["Handle_according_to_method"] H["Thread_1"] I["Thread_2"] J["Thread_N"] A --> B B --> C C --> D C --> E D --> F E --> G H --> B I --> B J --> B
Atomic State Coordination Diagram Sources: src/lib.rs(L15 - L16) src/lib.rs(L37 - L46) src/lib.rs(L57 - L66)
Interior Mutability Pattern
The UnsafeCell<MaybeUninit<T>>
provides the necessary interior mutability to allow initialization through shared references while maintaining memory safety through careful synchronization.
flowchart TD A["UnsafeCell<MaybeUninit<T>>"] B["get()"] C["*mut_MaybeUninit<T>"] D["as_mut_ptr()"] E["write(data)"] F["assume_init_ref()"] G["&T"] H["Memory_Safety"] I["Protected_by_AtomicBool"] A --> B B --> C C --> D C --> F D --> E F --> G H --> I I --> A
Interior Mutability and Memory Safety Sources: src/lib.rs(L16) src/lib.rs(L42) src/lib.rs(L119 - L120)
Memory Ordering Semantics
The implementation uses specific memory ordering guarantees to ensure correct synchronization across threads while maintaining performance.
Ordering Strategy
Operation | Success Ordering | Failure Ordering | Purpose |
---|---|---|---|
compare_exchange_weak | Ordering::Acquire | Ordering::Relaxed | Synchronize initialization |
loadoperations | Ordering::Acquire | N/A | Ensure visibility of writes |
Acquire-Release Semantics
sequenceDiagram participant Thread_1 as Thread_1 participant inited_AtomicBool as inited_AtomicBool participant data_UnsafeCell as data_UnsafeCell participant Thread_2 as Thread_2 Thread_1 ->> inited_AtomicBool: compare_exchange_weak(false, true, Acquire, Relaxed) Note over inited_AtomicBool: CAS succeeds inited_AtomicBool -->> Thread_1: Ok(false) Thread_1 ->> data_UnsafeCell: write(data) Note over Thread_1,data_UnsafeCell: Write becomes visible Thread_2 ->> inited_AtomicBool: load(Acquire) inited_AtomicBool -->> Thread_2: true Note over Thread_2: Acquire guarantees visibility of T1's write Thread_2 ->> data_UnsafeCell: assume_init_ref() data_UnsafeCell -->> Thread_2: &T (safe)
Memory Ordering Synchronization Sources: src/lib.rs(L38 - L39) src/lib.rs(L58 - L59) src/lib.rs(L71)
Thread Safety Implementation
Send and Sync Bounds
The trait implementations provide precise thread safety guarantees:
flowchart TD A["LazyInit<T>"] B["Send_for_LazyInit<T>"] C["Sync_for_LazyInit<T>"] D["T:_Send"] E["T:Send+_Sync"] F["Rationale"] G["Can_transfer_ownership_(Send)"] H["Can_share_references_(Sync)"] A --> B A --> C B --> D C --> E D --> G E --> H F --> G F --> H
Thread Safety Trait Bounds Sources: src/lib.rs(L19 - L20)
The bounds ensure that:
Send
is implemented whenT: Send
, allowing transfer of ownership across threadsSync
is implemented whenT: Send + Sync
, allowing shared access from multiple threads
Race Condition Prevention
The implementation prevents common race conditions through atomic compare-and-swap operations:
flowchart TD A["Multiple_threads_call_init_once"] B["compare_exchange_weak"] C["First_thread"] D["Performs_initialization"] E["Receives_Err"] F["Writes_data_safely"] G["Panics_Already_initialized"] H["call_once_behavior"] I["Returns_None_for_losers"] J["No_double_initialization"] K["Guaranteed_by_CAS"] A --> B B --> C C --> D C --> E D --> F D --> K E --> G E --> I G --> K H --> I J --> K
Race Condition Prevention Mechanisms Sources: src/lib.rs(L36 - L46) src/lib.rs(L53 - L66)
Memory Safety Guarantees
Initialization State Tracking
The atomic boolean serves as a guard that prevents access to uninitialized memory:
Method | Check Mechanism | Safety Level |
---|---|---|
get() | is_inited()check | Safe - returnsOption<&T> |
deref() | is_inited()check | Panic on uninitialized |
get_unchecked() | Debug assertion only | Unsafe - caller responsibility |
Memory Layout and Access Patterns
flowchart TD subgraph Unsafe_Access_Path["Unsafe_Access_Path"] H["get_unchecked()"] I["debug_assert"] J["force_get()"] end subgraph Safe_Access_Path["Safe_Access_Path"] C["is_inited()"] D["load(Acquire)"] E["true"] F["force_get()"] G["Return_None_or_Panic"] end subgraph LazyInit_Memory_Layout["LazyInit_Memory_Layout"] A["inited:_AtomicBool"] B["data:_UnsafeCell<MaybeUninit<T>>"] end K["assume_init_ref()"] L["&T"] A --> C B --> F B --> J C --> D D --> E E --> F E --> G F --> K H --> I I --> J J --> K K --> L
Memory Access Safety Mechanisms Sources: src/lib.rs(L77 - L83) src/lib.rs(L102 - L105) src/lib.rs(L119 - L120)
Performance Characteristics
Fast Path Optimization
Once initialized, access to the value follows a fast path with minimal overhead:
- Single atomic load -
is_inited()
performs oneOrdering::Acquire
load - Direct memory access -
force_get()
usesassume_init_ref()
without additional checks - No contention - Post-initialization reads don't modify atomic state
Compare-Exchange Optimization
The use of compare_exchange_weak
instead of compare_exchange
provides better performance on architectures where weak CAS can fail spuriously but retry efficiently.
flowchart TD A["compare_exchange_weak"] B["May_fail_spuriously"] C["Better_performance_on_some_architectures"] D["Initialization_path"] E["One-time_cost"] F["Amortized_over_lifetime"] G["Read_path"] H["Single_atomic_load"] I["Minimal_overhead"] A --> B B --> C D --> E E --> F G --> H H --> I
Performance Optimization Strategy Sources: src/lib.rs(L38 - L39) src/lib.rs(L58 - L59) src/lib.rs(L71)
The memory model ensures that the expensive synchronization cost is paid only once during initialization, while subsequent accesses benefit from the acquire-release semantics without additional synchronization overhead.
Sources: src/lib.rs(L1 - L183)
Usage Patterns & Examples
Relevant source files
This document covers common usage patterns, practical examples, and best practices for using the LazyInit<T>
type. It demonstrates how to effectively leverage thread-safe lazy initialization in various scenarios, from simple static variables to complex multi-threaded applications.
For detailed API documentation, see API Reference. For information about the underlying thread safety mechanisms, see Thread Safety & Memory Model.
Core Usage Patterns
The LazyInit<T>
type supports several distinct usage patterns, each optimized for different scenarios and requirements.
Static Variable Initialization Pattern
The most common pattern involves declaring static variables that are initialized lazily at runtime:
flowchart TD A["Static Declaration"] B["LazyInit::new()"] C["Runtime Check"] D["is_inited()"] E["Initialize Once"] F["Direct Access"] G["init_once() or call_once()"] H["*VALUE or VALUE.get()"] A --> B B --> C C --> D D --> E D --> F E --> G F --> H G --> F
Static Variable Initialization Flow
This pattern leverages the const fn new()
method to create compile-time initialized but runtime-lazy static variables.
Sources: src/lib.rs(L24 - L29) README.md(L15 - L28)
Thread-Safe Initialization Pattern
For multi-threaded scenarios, LazyInit<T>
provides race-condition-free initialization:
sequenceDiagram participant Thread1 as "Thread 1" participant Thread2 as "Thread 2" participant Thread3 as "Thread 3" participant LazyInitT as "LazyInit<T>" par Multiple Threads Thread1 ->> LazyInitT: call_once(|| value1) Thread2 ->> LazyInitT: call_once(|| value2) Thread3 ->> LazyInitT: call_once(|| value3) end Note over LazyInitT: compare_exchange_weak ensures<br>only one succeeds LazyInitT -->> Thread1: Some(&value1) LazyInitT -->> Thread2: None LazyInitT -->> Thread3: None par All Threads Access Thread1 ->> LazyInitT: get() or *deref Thread2 ->> LazyInitT: get() or *deref Thread3 ->> LazyInitT: get() or *deref end LazyInitT -->> Thread1: Same initialized value LazyInitT -->> Thread2: Same initialized value LazyInitT -->> Thread3: Same initialized value
Multi-threaded Initialization Sequence
The atomic compare_exchange_weak
operation ensures exactly one thread succeeds in initialization, while others receive None
from call_once
.
Sources: src/lib.rs(L53 - L67) src/lib.rs(L37 - L47) README.md(L30 - L57)
Initialization Method Patterns
Direct Value Initialization withinit_once
The init_once
method is used when you have a concrete value ready for initialization:
Characteristic | Behavior |
---|---|
Input | Direct value of typeT |
Return | Reference to initialized value |
Error Handling | Panics if already initialized |
Use Case | Static initialization with known values |
This pattern is ideal for scenarios where initialization failure should be treated as a programming error.
Sources: src/lib.rs(L31 - L47)
Closure-Based Initialization withcall_once
The call_once
method enables lazy evaluation and graceful handling of race conditions:
Characteristic | Behavior |
---|---|
Input | ClosureFnOnce() -> T |
Return | Option<&T> |
Error Handling | ReturnsNoneif already initialized |
Use Case | Thread-safe lazy evaluation |
This pattern provides more flexibility for complex initialization logic and better composability in concurrent contexts.
Sources: src/lib.rs(L49 - L67)
Access Pattern Categories
Safe Access Patterns
flowchart TD subgraph subGraph2["Safety Guarantees"] I["No UB"] J["Runtime Checks"] K["Clear Error Modes"] end subgraph subGraph1["Return Types"] E["bool"] F["Option<&T>"] G["Option<&mut T>"] H["&T or panic"] end subgraph subGraph0["Safe Access Methods"] A["is_inited()"] B["get()"] C["get_mut()"] D["*value (Deref)"] end A --> E B --> F C --> G D --> H E --> I F --> I G --> I H --> J I --> K J --> K
Safe Access Method Categories
Safe access methods provide runtime checks and clear error semantics at the cost of slight performance overhead.
Sources: src/lib.rs(L69 - L83) src/lib.rs(L85 - L94) src/lib.rs(L153 - L174)
Performance-Critical Access Patterns
For performance-critical code paths, unsafe methods bypass runtime checks:
Method | Safety Requirement | Performance Benefit |
---|---|---|
get_unchecked() | Must be called after initialization | Eliminates bounds checking |
get_mut_unchecked() | Must be called after initialization | Eliminates bounds checking |
Directforce_get() | Internal use only | Maximum performance |
These methods include debug_assert!
checks in debug builds while providing zero-cost access in release builds.
Sources: src/lib.rs(L96 - L116) src/lib.rs(L118 - L126)
Error Handling Strategies
Panic-Based Error Handling
flowchart TD A["Method Call"] B["Error Condition?"] C["init_once() panic"] D["Deref panic"] E["Normal Operation"] F["Program Termination"] G["Continue Execution"] A --> B B --> C B --> D B --> E C --> F D --> F E --> G
Panic-Based Error Flow
The init_once
method and Deref
implementations use panics to indicate programming errors that should not occur in correct code.
Sources: src/lib.rs(L35) src/lib.rs(L45) src/lib.rs(L160) src/lib.rs(L128 - L133)
Option-Based Error Handling
The call_once
and get
methods use Option
types for graceful error handling in scenarios where races or uninitialized access might be expected.
Scenario | Method | Return Value |
---|---|---|
Successful initialization | call_once(f) | Some(&T) |
Lost initialization race | call_once(f) | None |
Access to initialized value | get() | Some(&T) |
Access to uninitialized value | get() | None |
Sources: src/lib.rs(L53) src/lib.rs(L77)
Common Implementation Patterns
Singleton Pattern Implementation
Static LazyInit
variables can implement thread-safe singletons:
flowchart TD subgraph subGraph0["Singleton Pattern with LazyInit"] A["static INSTANCE: LazyInit"] B["get_instance() function"] C["call_once(|| ExpensiveResource::new())"] D["Singleton access"] end E["Thread-safe singleton access"] A --> B B --> C C --> D D --> E
Singleton Pattern Flow
This pattern ensures expensive resources are created only once and shared safely across threads.
Sources: src/lib.rs(L24 - L29) src/lib.rs(L53 - L67)
Configuration Loading Pattern
Lazy initialization of configuration data that may be loaded from files or environment variables:
flowchart TD subgraph subGraph0["Config Loading Pattern"] A["static CONFIG: LazyInit"] B["call_once(load_config)"] C["File/Env Loading"] D["Cached Access"] end E["Fast subsequent access"] A --> B B --> C C --> D D --> E
Configuration Loading Flow
This pattern delays expensive I/O operations until the configuration is actually needed.
Sources: src/lib.rs(L53 - L67) README.md(L39 - L46)
Performance Considerations
Initialization Cost vs Access Cost
Phase | Cost | Frequency | Optimization Strategy |
---|---|---|---|
Initialization | High (one-time) | Once | Usecall_oncefor lazy evaluation |
Access (Safe) | Low | Frequent | Useget()for most cases |
Access (Unsafe) | Minimal | Critical path | Useget_unchecked()when proven safe |
The compare_exchange_weak
operation provides optimal performance for the initialization phase by reducing spurious failures compared to compare_exchange
.
Sources: src/lib.rs(L38 - L39) src/lib.rs(L58 - L59) src/lib.rs(L102 - L105)
Memory Overhead Analysis
The LazyInit<T>
wrapper adds minimal overhead:
AtomicBool inited
: 1 byte + paddingUnsafeCell<MaybeUninit<T>>
: Same size asT
- Total overhead: Typically 8 bytes on 64-bit systems due to alignment
Sources: src/lib.rs(L14 - L17)
Project Configuration
Relevant source files
This document covers the project configuration of the lazyinit
crate, focusing on package metadata, licensing strategy, dependency management, and no-std compatibility. The configuration is primarily defined in Cargo.toml
and establishes the crate's minimal design philosophy and broad compatibility requirements.
For information about the core implementation details, see LazyInit Implementation. For development tooling and CI configuration, see Development & Contributing.
Configuration Structure
The project configuration follows a minimal approach with clear separation of concerns between package metadata, dependencies, and build targets.
Package Configuration Hierarchy
flowchart TD subgraph Dependencies["Dependencies"] N["Empty - no dependencies"] end subgraph subGraph1["Package Metadata"] C["name = lazyinit"] D["version = 0.2.1"] E["edition = 2021"] F["authors"] G["description"] H["license"] I["homepage"] J["repository"] K["documentation"] L["keywords"] M["categories"] end subgraph Cargo.toml["Cargo.toml"] A["[package]"] B["[dependencies]"] end A --> C A --> D A --> E A --> F A --> G A --> H A --> I A --> J A --> K A --> L A --> M B --> N
Sources: Cargo.toml(L1 - L15)
Package Metadata
The package metadata defines the crate's identity, versioning, and discoverability characteristics.
Field | Value | Purpose |
---|---|---|
name | lazyinit | Crate identifier on crates.io |
version | 0.2.1 | Semantic version following SemVer |
edition | 2021 | Rust edition compatibility |
authors | Yuekai Jia equation618@gmail.com | Primary maintainer |
description | Initialize a static value lazily. | Brief functionality summary |
homepage | https://github.com/arceos-org/arceos | Parent project reference |
repository | https://github.com/arceos-org/lazyinit | Source code location |
documentation | https://docs.rs/lazyinit | Generated API documentation |
Sources: Cargo.toml(L1 - L12)
Keywords and Categories
The crate is tagged with specific keywords and categories that indicate its purpose and compatibility:
- Keywords:
lazy
,initialization
,static
- Categories:
no-std
,rust-patterns
These classifications signal the crate's focus on lazy initialization patterns and its compatibility with no-std
environments.
Sources: Cargo.toml(L11 - L12)
Licensing Strategy
The project employs a tri-license approach providing flexibility for different use cases and legal requirements.
Multi-License Configuration
flowchart TD subgraph subGraph2["Use Cases"] E["Open Source Projects"] F["Commercial Software"] G["Chinese Legal Framework"] end subgraph subGraph1["License Options"] B["GPL-3.0-or-later"] C["Apache-2.0"] D["MulanPSL-2.0"] end subgraph subGraph0["lazyinit Crate"] A["license field"] end A --> B A --> C A --> D B --> E C --> F D --> G
The license string GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0
allows users to choose the most appropriate license for their specific requirements:
- GPL-3.0-or-later: Copyleft license for open source projects
- Apache-2.0: Permissive license suitable for commercial use
- MulanPSL-2.0: License compatible with Chinese legal frameworks
Sources: Cargo.toml(L7)
No-std Compatibility
The configuration explicitly supports no-std
environments through strategic design choices.
Zero-Dependency Architecture
flowchart TD subgraph subGraph2["Enabled Environments"] G["std environments"] H["no-std environments"] I["embedded systems"] J["kernel modules"] end subgraph subGraph1["Core Rust"] C["core library"] D["AtomicBool"] E["UnsafeCell"] F["MaybeUninit"] end subgraph subGraph0["lazyinit Crate"] A["[dependencies]"] B["Empty section"] end A --> B B --> C C --> D C --> E C --> F D --> G D --> H D --> I D --> J
The empty [dependencies]
section indicates that the crate relies exclusively on Rust's core
library, enabling usage in:
- Embedded systems without heap allocation
- Kernel modules and OS development
- WebAssembly environments
- Any
no-std
compatible Rust project
Sources: Cargo.toml(L14 - L15)
Build Target Compatibility
The configuration supports the crate's multi-target compatibility strategy, as evidenced by the CI pipeline testing multiple architectures.
Target Architecture Support
Target | Environment | Purpose |
---|---|---|
x86_64-unknown-linux-gnu | Standard Linux | General development and testing |
x86_64-unknown-none | Bare metal x86_64 | OS development and embedded |
riscv64gc-unknown-none-elf | RISC-V bare metal | RISC-V embedded systems |
aarch64-unknown-none-softfloat | ARM64 bare metal | ARM embedded systems |
The configuration's minimal dependencies and no-std
categorization enable this broad target support without requiring target-specific conditional compilation.
Sources: Cargo.toml(L12)
Configuration Philosophy
The project configuration reflects several key design principles:
- Minimalism: Zero external dependencies reduce attack surface and compatibility issues
- Flexibility: Multi-licensing accommodates diverse legal requirements
- Universality: No-std compatibility enables use across all Rust environments
- Clarity: Explicit categorization and keywords aid discoverability
This configuration strategy aligns with the crate's role as a fundamental building block for lazy initialization patterns across the Rust ecosystem.
Sources: Cargo.toml(L1 - L15)
Development & Contributing
Relevant source files
This document provides information for developers who want to contribute to the lazyinit crate or understand its development processes. It covers the contribution workflow, development environment requirements, quality assurance mechanisms, and deployment procedures.
For detailed information about the CI/CD pipeline implementation, see CI/CD Pipeline. For guidance on setting up a local development environment, see Development Environment Setup.
Development Workflow Overview
The lazyinit crate follows a standard GitHub-based development workflow with automated quality gates and multi-target validation. All contributions are validated through a comprehensive CI/CD pipeline that ensures code quality, functionality, and compatibility across multiple target architectures.
Contribution Process Flow
flowchart TD A["Developer Fork/Clone"] B["Local Development"] C["cargo fmt --all -- --check"] D["cargo clippy --all-features"] E["cargo build --all-features"] F["cargo test -- --nocapture"] G["Push to Branch"] H["Create Pull Request"] I["GitHub Actions CI"] J["CI Passes?"] K["Code Review"] L["Fix Issues"] M["Merge to Main"] N["Deploy Documentation"] A --> B B --> C C --> D D --> E E --> F F --> G G --> H H --> I I --> J J --> K J --> L K --> M L --> B M --> N
Sources: .github/workflows/ci.yml(L1 - L56)
Quality Gates and Validation
The project enforces several quality gates that all contributions must pass. These are implemented as sequential steps in the CI pipeline to ensure code quality and consistency.
Quality Gate Pipeline
flowchart TD subgraph subGraph1["CI Validation"] F["actions/checkout@v4"] G["dtolnay/rust-toolchain@nightly"] H["cargo fmt --all -- --check"] I["cargo clippy --target TARGET --all-features"] J["cargo build --target TARGET --all-features"] K["cargo test --target x86_64-unknown-linux-gnu"] end subgraph subGraph0["Local Development"] A["rustc"] B["rustfmt"] C["clippy"] D["build"] E["test"] end A --> B B --> C C --> D D --> E E --> F F --> G G --> H H --> I I --> J J --> K
The quality gates include:
Gate | Command | Purpose | Scope |
---|---|---|---|
Format Check | cargo fmt --all -- --check | Code formatting consistency | All code |
Linting | cargo clippy --all-features | Code quality and best practices | All targets |
Build | cargo build --all-features | Compilation validation | All targets |
Testing | cargo test -- --nocapture | Functional validation | Linux only |
Sources: .github/workflows/ci.yml(L22 - L30)
Multi-Target Architecture Support
The crate supports multiple target architectures to ensure compatibility with diverse embedded and systems programming environments. This is particularly important for a no-std
compatible crate.
Target Architecture Matrix
flowchart TD subgraph subGraph1["Validation Scope"] E["Format Check"] F["Clippy Linting"] G["Build Validation"] H["Unit Testing"] end subgraph subGraph0["Supported Targets"] A["x86_64-unknown-linux-gnu"] B["x86_64-unknown-none"] C["riscv64gc-unknown-none-elf"] D["aarch64-unknown-none-softfloat"] end E --> A E --> B E --> C E --> D F --> A F --> B F --> C F --> D G --> A G --> B G --> C G --> D H --> A
The target matrix includes:
x86_64-unknown-linux-gnu
: Standard Linux development target with full testingx86_64-unknown-none
: Bare metal x86_64 target for embedded systemsriscv64gc-unknown-none-elf
: RISC-V 64-bit embedded targetaarch64-unknown-none-softfloat
: ARM64 embedded target with software floating point
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L25 - L30)
Toolchain and Component Requirements
Development requires the Rust nightly toolchain with specific components for comprehensive validation and documentation generation.
Required Toolchain Components
Component | Purpose | Required For |
---|---|---|
rust-src | Source code for cross-compilation | Multi-target builds |
clippy | Linting and code analysis | Quality gates |
rustfmt | Code formatting | Style consistency |
nightly toolchain | Latest language features | Core functionality |
Sources: .github/workflows/ci.yml(L15 - L19)
Documentation Generation and Deployment
The project maintains automated documentation deployment to GitHub Pages with strict documentation quality enforcement.
Documentation Pipeline
sequenceDiagram participant Developer as "Developer" participant GitHubActions as "GitHub Actions" participant GitHubPages as "GitHub Pages" Developer ->> GitHubActions: "Push to main branch" GitHubActions ->> GitHubActions: "cargo doc --no-deps --all-features" Note over GitHubActions: RUSTDOCFLAGS:<br>-D rustdoc::broken_intra_doc_links<br>-D missing-docs GitHubActions ->> GitHubActions: "Generate index.html redirect" GitHubActions ->> GitHubPages: "Deploy to gh-pages branch" GitHubPages -->> Developer: "Documentation available"
Documentation requirements:
- All public APIs must have documentation comments
- No broken intra-doc links allowed (
-D rustdoc::broken_intra_doc_links
) - Missing documentation is treated as an error (
-D missing-docs
) - Automatic index page generation for navigation
Sources: .github/workflows/ci.yml(L32 - L56) .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L47 - L48)
Development Environment Configuration
The project includes standard development environment configuration files to ensure consistent development experiences across contributors.
Excluded Artifacts
The .gitignore
configuration excludes common development artifacts:
Pattern | Purpose |
---|---|
/target | Rust build output directory |
/.vscode | Visual Studio Code configuration |
.DS_Store | macOS filesystem metadata |
Cargo.lock | Dependency lock file (for libraries) |
Sources: .gitignore(L1 - L5)
Contribution Guidelines
- Fork and Clone: Create a fork of the repository and clone it locally
- Branch: Create a feature branch for your changes
- Develop: Make changes ensuring they pass all local quality gates
- Test: Verify functionality on the primary target (
x86_64-unknown-linux-gnu
) - Submit: Create a pull request with clear description of changes
- Review: Address feedback from maintainers and automated checks
All contributions are automatically validated against the multi-target matrix to ensure compatibility across supported architectures.
Sources: .github/workflows/ci.yml(L3) .github/workflows/ci.yml(L6 - L30)
CI/CD Pipeline
Relevant source files
This document details the continuous integration and continuous deployment (CI/CD) pipeline for the lazyinit crate. The pipeline ensures code quality through automated testing, linting, formatting checks, and multi-target compilation, while also automating documentation deployment to GitHub Pages.
For information about setting up the development environment locally, see Development Environment Setup. For details about the core LazyInit implementation being tested by this pipeline, see LazyInit Implementation.
Pipeline Overview
The CI/CD pipeline consists of two parallel workflows defined in a single GitHub Actions configuration file. The pipeline triggers on both push events and pull requests to ensure all changes are validated.
CI/CD Workflow Architecture
flowchart TD subgraph subGraph4["Documentation Pipeline"] M["cargo doc"] N["rustdoc validation"] O["GitHub Pages publish"] end subgraph subGraph3["Quality Gates"] I["cargo fmt --check"] J["cargo clippy"] K["cargo build"] L["cargo test"] end subgraph subGraph2["doc Job"] F["ubuntu-latest"] G["nightly toolchain"] H["GitHub Pages deployment"] end subgraph subGraph1["ci Job"] C["ubuntu-latest"] D["nightly toolchain"] E["Multi-target matrix"] end subgraph subGraph0["Trigger Events"] A["push"] B["pull_request"] end A --> C A --> F B --> C B --> F C --> E C --> I C --> J C --> K C --> L F --> M F --> N F --> O
Sources: .github/workflows/ci.yml(L1 - L56)
Target Platform Matrix
The pipeline validates the crate across multiple target platforms to ensure broad compatibility, particularly for no-std
environments:
Target | Purpose | Test Execution |
---|---|---|
x86_64-unknown-linux-gnu | Standard Linux development | Full test suite |
x86_64-unknown-none | Bare metal x86_64 | Build and lint only |
riscv64gc-unknown-none-elf | RISC-V bare metal | Build and lint only |
aarch64-unknown-none-softfloat | ARM64 bare metal | Build and lint only |
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L29 - L30)
CI Job Workflow
The ci
job implements a comprehensive quality assurance workflow that runs across all target platforms using a matrix strategy.
Quality Assurance Pipeline
flowchart TD subgraph subGraph2["Target Matrix"] I["x86_64-unknown-linux-gnu"] J["x86_64-unknown-none"] K["riscv64gc-unknown-none-elf"] L["aarch64-unknown-none-softfloat"] end subgraph subGraph1["Validation Steps"] D["rustc --version --verbose"] E["cargo fmt --all --check"] F["cargo clippy --target TARGET"] G["cargo build --target TARGET"] H["cargo test --target TARGET"] end subgraph subGraph0["Setup Phase"] A["actions/checkout@v4"] B["dtolnay/rust-toolchain@nightly"] C["rust-src, clippy, rustfmt components"] end A --> B B --> C C --> D D --> E E --> F F --> G F --> I F --> J F --> K F --> L G --> H G --> I G --> J G --> K G --> L H --> I
Sources: .github/workflows/ci.yml(L14 - L30)
Quality Gates Implementation
The pipeline enforces multiple quality gates in sequence:
- Code Formatting:
cargo fmt --all -- --check
ensures consistent code style - Linting:
cargo clippy --target ${{ matrix.targets }} --all-features
with specific allowances forclippy::new_without_default
- Compilation:
cargo build --target ${{ matrix.targets }} --all-features
validates cross-platform compatibility - Testing:
cargo test --target ${{ matrix.targets }}
runs only onx86_64-unknown-linux-gnu
target
The conditional test execution pattern reflects the reality that unit tests require a hosted environment, while build validation can occur for bare metal targets.
Sources: .github/workflows/ci.yml(L22 - L30)
Documentation Job Workflow
The doc
job handles automated documentation generation and deployment with sophisticated branching logic.
Documentation Pipeline Architecture
flowchart TD subgraph subGraph2["Deployment Conditions"] G["JamesIves/github-pages-deploy-action@v4"] H["single-commit: true"] I["branch: gh-pages"] J["folder: target/doc"] end subgraph subGraph1["Branch Logic"] D["github.ref == default-branch"] E["github.event_name == pull_request"] F["continue-on-error for non-default branches"] end subgraph subGraph0["Documentation Build"] A["cargo doc --no-deps --all-features"] B["RUSTDOCFLAGS validation"] C["Index page generation"] end A --> B B --> C C --> D D --> G E --> F F --> A G --> H G --> I G --> J
Sources: .github/workflows/ci.yml(L32 - L56)
Documentation Quality Controls
The documentation job implements strict quality controls through environment variables:
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
treats documentation warnings as errors- Automated index page generation using
cargo tree
output for navigation - Conditional error handling that allows documentation build failures on non-default branches
The index page generation command creates a redirect: printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L46 - L48)
Build Matrix Configuration
The pipeline uses a sophisticated matrix strategy to validate across multiple dimensions while maintaining efficiency.
Matrix Strategy Implementation
flowchart TD subgraph subGraph1["Build Combinations"] C["nightly + x86_64-unknown-linux-gnu"] D["nightly + x86_64-unknown-none"] E["nightly + riscv64gc-unknown-none-elf"] F["nightly + aarch64-unknown-none-softfloat"] end subgraph subGraph0["Matrix Dimensions"] A["rust-toolchain: [nightly]"] B["targets: [4 platforms]"] end subgraph subGraph2["Execution Strategy"] G["fail-fast: false"] H["Parallel execution"] I["Independent failure isolation"] end A --> C A --> D A --> E A --> F B --> C B --> D B --> E B --> F G --> H H --> I
The fail-fast: false
configuration ensures that a failure on one target platform doesn't prevent validation of other platforms, providing comprehensive feedback on cross-platform compatibility issues.
Sources: .github/workflows/ci.yml(L8 - L12)
Deployment and Permissions
The documentation deployment process uses specific GitHub Actions permissions and deployment strategies optimized for documentation sites.
GitHub Pages Deployment Configuration
The doc
job requires contents: write
permissions for GitHub Pages deployment. The deployment uses JamesIves/github-pages-deploy-action@v4
with:
single-commit: true
for clean deployment historybranch: gh-pages
as the deployment targetfolder: target/doc
containing the generated documentation
Deployment only occurs on the default branch (github.ref == env.default-branch
), preventing documentation pollution from feature branches.
Sources: .github/workflows/ci.yml(L36 - L37) .github/workflows/ci.yml(L49 - L55)
Development Environment Setup
Relevant source files
This document provides a comprehensive guide for setting up a development environment for the lazyinit crate. It covers toolchain requirements, target platform configuration, development workflows, and build artifact management. The information is derived from the project's CI/CD configuration and development infrastructure files.
For information about the CI/CD pipeline and automated quality gates, see CI/CD Pipeline. For details about the core implementation and API, see LazyInit Implementation.
Toolchain Requirements
The lazyinit crate requires specific Rust toolchain components and configuration to support its no-std compatibility and multi-target architecture.
Required Rust Components
The development environment must include the nightly Rust toolchain with the following components:
Component | Purpose |
---|---|
rust-src | Source code for cross-compilation targets |
clippy | Linting and static analysis |
rustfmt | Code formatting |
Target Platform Support
The crate supports multiple target platforms to ensure broad compatibility across different architectures and environments:
flowchart TD A["nightly-toolchain"] B["x86_64-unknown-linux-gnu"] C["x86_64-unknown-none"] D["riscv64gc-unknown-none-elf"] E["aarch64-unknown-none-softfloat"] F["Standard Linux Environment"] G["Bare Metal x86_64"] H["RISC-V Bare Metal"] I["ARM64 Bare Metal"] J["Unit Testing"] K["No-std Validation"] A --> B A --> C A --> D A --> E B --> F C --> G D --> H E --> I F --> J G --> K H --> K I --> K
Development Toolchain Setup Process
Sources: .github/workflows/ci.yml(L11 - L19)
Development Workflow
The development workflow follows a structured approach with multiple quality gates to ensure code quality and compatibility across all supported targets.
Core Development Commands
flowchart TD A["Source Code Changes"] B["cargo fmt --all --check"] C["cargo clippy --target TARGET --all-features"] D["cargo build --target TARGET --all-features"] E["cargo test --target TARGET"] F["Code Ready"] G["Format Violation"] H["Lint Warnings"] I["Build Errors"] J["Test Failures"] A --> B B --> C B --> G C --> D C --> H D --> E D --> I E --> F E --> J G --> A H --> A I --> A J --> A
Development Quality Gate Workflow
Command Reference
The following commands form the core development workflow:
Command | Purpose | Target Scope |
---|---|---|
cargo fmt --all -- --check | Code formatting validation | All code |
cargo clippy --target | Linting with custom rules | Per target |
cargo build --target | Compilation verification | Per target |
cargo test --target | Unit test execution | Linux only |
Clippy Configuration
The project uses a specific clippy configuration that allows the new_without_default
lint, which is appropriate for the LazyInit::new()
constructor pattern.
Sources: .github/workflows/ci.yml(L22 - L30)
Build Artifacts and Git Configuration
The development environment is configured to exclude specific files and directories that are generated during development or are platform-specific.
Excluded Files and Directories
flowchart TD A[".gitignore"] B["/target"] C["/.vscode"] D[".DS_Store"] E["Cargo.lock"] F["Build Artifacts"] G["Documentation Output"] H["Test Binaries"] I["VS Code Configuration"] J["Editor Settings"] K["macOS System Files"] L["Dependency Lock File"] M["Version Pinning"] A --> B A --> C A --> D A --> E B --> F B --> G B --> H C --> I C --> J D --> K E --> L E --> M
Git Ignore Structure and Build Artifacts
Build Artifact Management
Path | Content | Reason for Exclusion |
---|---|---|
/target | Compiled binaries, documentation, test artifacts | Generated content, large files |
/.vscode | VS Code editor configuration | Editor-specific, not universal |
.DS_Store | macOS filesystem metadata | Platform-specific system files |
Cargo.lock | Dependency version locks | Library crate convention |
Sources: .gitignore(L1 - L5)
Documentation Generation
The project includes sophisticated documentation generation with specific configuration for quality and deployment.
Documentation Build Configuration
The documentation build process uses specific RUSTDOCFLAGS
to enforce documentation quality:
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
These flags ensure:
- All intra-documentation links are valid
- All public items have documentation
Documentation Workflow
flowchart TD A["cargo doc --no-deps --all-features"] B["Generate Documentation"] C["Create index.html redirect"] D["target/doc directory"] E["GitHub Pages Deployment"] F["RUSTDOCFLAGS"] G["-D rustdoc::broken_intra_doc_links"] H["-D missing-docs"] I["Default Branch Only"] A --> B B --> C C --> D D --> E F --> A G --> F H --> F I --> E
Documentation Generation and Deployment Process
The documentation generation creates a redirect index file using the crate name extracted from cargo tree
output:
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
Sources: .github/workflows/ci.yml(L40 - L55)
Local Development Setup
To establish a complete development environment for the lazyinit crate:
- Install Rust Nightly:
rustup toolchain install nightly
rustup default nightly
- Add Required Components:
rustup component add rust-src clippy rustfmt
- Add Target Platforms:
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
- Verify Setup:
rustc --version --verbose
cargo fmt --version
cargo clippy --version
This configuration ensures compatibility with the project's CI/CD pipeline and enables local development that matches the automated testing environment.
Sources: .github/workflows/ci.yml(L15 - L21)
Overview
Relevant source files
This document provides an overview of the linked_list_r4l
crate, a Rust library that implements intrusive linked lists with constant-time arbitrary removal capabilities. It covers the crate's purpose, key features, architecture, and position within the ArceOS ecosystem.
For detailed usage examples, see Quick Start Guide. For comprehensive API documentation, see API Reference. For advanced concepts like memory management and thread safety, see Core Concepts.
Purpose and Scope
The linked_list_r4l
crate provides linked lists that support arbitrary removal of nodes in constant time O(1), regardless of the node's position in the list. This capability is achieved through an intrusive design where list metadata is embedded directly within the nodes themselves, eliminating the need to traverse the list to find and remove specific elements.
The library is designed for systems programming contexts where performance predictability is critical, particularly in kernel and embedded environments. It originated from the Rust-for-Linux project and has been adapted for use in the ArceOS operating system kernel.
Sources: Cargo.toml(L6) README.md(L7 - L11)
Key Features
Feature | Description | Benefit |
---|---|---|
Constant-time removal | Remove any node in O(1) time | Predictable performance for real-time systems |
Thread safety | Atomic operations with insertion tracking | Safe concurrent access patterns |
Multiple ownership models | Support forBox | Flexible memory management strategies |
Zero-cost abstractions | Compile-time polymorphism via traits | No runtime overhead |
no-stdcompatible | Works in embedded and kernel contexts | Suitable for constrained environments |
Sources: Cargo.toml(L6 - L12) README.md(L7)
Architecture Overview
The crate implements a three-layer architecture that balances safety, performance, and usability:
flowchart TD subgraph Raw_Layer["Raw Unsafe Layer"] RawList["RawList<G: GetLinks>"] GetLinks["GetLinks trait"] Links["Links<T> struct"] ListEntry["ListEntry<T>"] end subgraph Wrapper_Layer["Safe Wrapper Layer"] List_G["List<G: GetLinksWrapped>"] GetLinksWrapped["GetLinksWrapped trait"] Wrapper["Wrapper trait"] end subgraph User_API_Layer["User API Layer"] def_node["def_node! macro"] List_T["List<T>"] Examples["Usage Examples"] end Examples --> def_node GetLinksWrapped --> GetLinks Links --> ListEntry List_G --> GetLinksWrapped List_G --> RawList List_G --> Wrapper List_T --> List_G RawList --> Links def_node --> List_T
Three-Layer Architecture
- User API Layer: Provides convenient macros and simple interfaces for common use cases
- Safe Wrapper Layer: Manages memory ownership and provides safe abstractions over raw operations
- Raw Unsafe Layer: Implements core linked list algorithms using unsafe pointer operations
Sources: Architecture diagrams, README.md(L15 - L62)
Core Components and Code Entities
The following diagram maps the main system concepts to their corresponding code entities:
flowchart TD subgraph Operations["Core Operations"] push_back["push_back()"] push_front["push_front()"] remove["remove()"] pop_front["pop_front()"] iter["iter()"] end subgraph Node_Structure["Node Memory Layout"] UserData["User Data(inner: T)"] LinkData["Link Metadata(links: Links<Self>)"] end subgraph Code_Entities["Primary Code Entities"] Links_struct["Links<T>src/raw_list.rs"] GetLinks_trait["GetLinks traitsrc/raw_list.rs"] RawList_struct["RawList<G>src/raw_list.rs"] List_linked_list["List<G>src/linked_list.rs"] List_lib["List<T>src/lib.rs"] def_node_macro["def_node! macrosrc/lib.rs"] end GetLinks_trait --> Links_struct Links_struct --> LinkData List_lib --> List_linked_list List_lib --> iter List_lib --> pop_front List_lib --> push_back List_lib --> push_front List_lib --> remove List_linked_list --> RawList_struct RawList_struct --> GetLinks_trait def_node_macro --> LinkData def_node_macro --> UserData
Key Code Entities:
Links<T>
: Contains the intrusive link metadata embedded in each nodeGetLinks
trait: Provides access to a type's embeddedLinks<T>
instanceRawList<G>
: Implements core unsafe linked list operationsList<G>
(linked_list.rs): Safe wrapper managing ownership viaWrapper
traitList<T>
(lib.rs): User-friendly API for common casesdef_node!
macro: Generates node structs with embedded links
Sources: README.md(L16 - L31) Architecture diagrams
Position in ArceOS Ecosystem
The linked_list_r4l
crate serves as a foundational data structure within the ArceOS operating system kernel. Its constant-time removal capability makes it suitable for:
- Task scheduling: Efficient manipulation of process/thread queues
- Memory management: Managing free block lists with predictable performance
- Device drivers: Maintaining I/O request queues with guaranteed latency bounds
- Interrupt handling: Time-critical data structure operations
The crate's no-std
compatibility and zero-cost abstractions align with ArceOS's goals of providing a high-performance, safe kernel implementation.
Sources: Cargo.toml(L8 - L12) README.md(L9 - L11)
Getting Started
To begin using this crate:
- Basic usage: See Quick Start Guide for immediate examples
- API details: Consult API Reference for comprehensive interface documentation
- Advanced topics: Review Core Concepts for memory management and thread safety patterns
- Development: See Development Guide for contribution guidelines
The typical usage pattern involves defining nodes with the def_node!
macro, creating a List<T>
instance, and performing standard list operations with the guarantee of constant-time arbitrary removal.
Sources: README.md(L13 - L62)
Quick Start Guide
Relevant source files
This guide covers the essential steps to start using the linked_list_r4l
crate for basic linked list operations. It demonstrates node creation using the def_node!
macro and common list operations like insertion, removal, and iteration. For detailed architecture information, see Architecture Overview. For comprehensive API documentation, see API Reference.
Prerequisites and Setup
Add the crate to your Cargo.toml
:
[dependencies]
linked_list_r4l = "0.1"
The library provides two main approaches for creating list nodes:
- Recommended: Using the
def_node!
macro for automatic code generation - Manual: Implementing the
GetLinks
trait manually
Creating Nodes with def_node! Macro
The def_node!
macro is the primary way to create node types. It automatically generates the required structure and trait implementations.
Basic Node Definition
use linked_list_r4l::{def_node, List};
def_node! {
/// A simple node containing a usize value
struct NumberNode(usize);
/// A public node with generic type
pub struct GenericNode<T>(T);
}
The macro generates a struct with two fields: inner
containing your data and links
for list management. It also implements the GetLinks
trait and provides helper methods.
Sources: src/lib.rs(L109 - L178)
Node Creation and Basic Operations
// Create nodes
let node1 = Box::new(NumberNode::new(42));
let node2 = Box::new(NumberNode::new(100));
// Create list
let mut list = List::<Box<NumberNode>>::new();
// Add nodes
list.push_back(node1);
list.push_back(node2);
// Access data
for node in list.iter() {
println!("Value: {}", *node.inner());
}
// Remove nodes
let first = list.pop_front().unwrap();
assert_eq!(first.into_inner(), 42);
Sources: src/lib.rs(L136 - L165)
Generated Node Structure
When you use def_node!
, the following structure is generated:
flowchart TD UserMacro["def_node! { struct NumberNode(usize); }"] GeneratedStruct["Generated Struct NumberNode• inner: usize• links: Links<Self>"] GeneratedImpl["Generated Implementations• GetLinks trait• new() constructor• inner() accessor• into_inner() converter• Deref to inner type"] UserCode["User Codelet node = NumberNode::new(42)"] ListAPI["List APIlist.push_back(Box::new(node))"] GeneratedImpl --> UserCode GeneratedStruct --> GeneratedImpl UserCode --> ListAPI UserMacro --> GeneratedStruct
Sources: src/lib.rs(L12 - L58) src/lib.rs(L60 - L107)
List Operations Flow
The following diagram shows how common list operations map to code entities:
flowchart TD subgraph subGraph2["Core Types"] ListStruct["List<G>"] LinksStruct["Links<T>"] GetLinksTrait["GetLinks trait"] end subgraph subGraph1["Node Management"] BoxWrapper["Box<NumberNode>"] NodeData["NumberNode.inner"] NodeLinks["NumberNode.links"] end subgraph subGraph0["User Operations"] CreateList["List::new()"] AddNode["list.push_back(node)"] RemoveNode["list.pop_front()"] IterateList["list.iter()"] end AddNode --> BoxWrapper BoxWrapper --> NodeData BoxWrapper --> NodeLinks CreateList --> ListStruct IterateList --> GetLinksTrait LinksStruct --> GetLinksTrait ListStruct --> LinksStruct NodeLinks --> LinksStruct RemoveNode --> BoxWrapper
Sources: src/lib.rs(L6) src/lib.rs(L7) src/lib.rs(L19 - L26)
Complete Example
Here's a comprehensive example showing typical usage patterns:
use linked_list_r4l::{def_node, List};
def_node! {
/// Task node for a simple scheduler
pub struct TaskNode(String);
/// Priority task with generic data
pub struct PriorityTask<T>(T);
}
fn main() {
// Create task list
let mut task_queue = List::<Box<TaskNode>>::new();
// Add tasks
task_queue.push_back(Box::new(TaskNode::new("initialize".to_string())));
task_queue.push_back(Box::new(TaskNode::new("process".to_string())));
task_queue.push_back(Box::new(TaskNode::new("cleanup".to_string())));
// Process tasks
while let Some(task) = task_queue.pop_front() {
println!("Processing: {}", task.inner());
// Task is automatically dropped here
}
// Generic example
let mut numbers = List::<Box<PriorityTask<i32>>>::new();
numbers.push_back(Box::new(PriorityTask::new(1)));
numbers.push_back(Box::new(PriorityTask::new(2)));
// Iterate without removing
for (index, task) in numbers.iter().enumerate() {
println!("Task {}: {}", index, task.inner());
}
}
Sources: src/lib.rs(L125 - L165)
Memory Management
The library uses Box<T>
for heap allocation by default, but supports other ownership models:
Wrapper Type | Use Case | Example |
---|---|---|
Box | Single ownership, heap allocated | List::<Box |
Arc | Shared ownership, reference counted | List::<Arc |
&Node | Borrowed references | List::<&MyNode>::new() |
Sources: src/lib.rs(L6)
Manual Node Implementation (Alternative)
For advanced use cases, you can implement the GetLinks
trait manually instead of using the macro:
#![allow(unused)] fn main() { use linked_list_r4l::{GetLinks, Links, List}; pub struct CustomNode { pub data: String, links: Links<Self>, } impl GetLinks for CustomNode { type EntryType = Self; fn get_links(t: &Self) -> &Links<Self> { &t.links } } impl CustomNode { fn new(data: String) -> Self { Self { data, links: Links::new(), } } } }
Sources: README.md(L15 - L44)
Next Steps
- For understanding the library's architecture, see Architecture Overview
- For advanced ownership patterns, see Advanced API
- For thread safety considerations, see Thread Safety
- For contributing to the project, see Development Guide
Architecture Overview
Relevant source files
This document explains the three-layer architecture of the linked_list_r4l
crate, detailing how the unsafe raw list implementation, safe wrapper layer, and user-friendly API work together to provide constant-time arbitrary removal with memory safety guarantees.
For specific API usage examples, see Quick Start Guide. For detailed trait and struct documentation, see API Reference. For memory safety and thread safety concepts, see Core Concepts.
Three-Layer Architecture
The linked_list_r4l
crate implements a sophisticated three-layer architecture that separates concerns between performance, safety, and usability:
flowchart TD subgraph subGraph2["Layer 1: Raw Unsafe Layer"] RawListStruct["RawList<G: GetLinks>"] GetLinksTrait["GetLinks trait"] LinksStruct["Links<T> struct"] ListEntryStruct["ListEntry<T> struct"] AtomicOps["AtomicBool operations"] end subgraph subGraph1["Layer 2: Safe Wrapper Layer"] WrappedList["List<G: GetLinksWrapped>"] WrapperTrait["Wrapper<T> trait"] GetLinksWrappedTrait["GetLinksWrapped trait"] BoxImpl["Box<T> implementation"] ArcImpl["Arc<T> implementation"] RefImpl["&T implementation"] end subgraph subGraph0["Layer 3: User-Friendly API"] DefNodeMacro["def_node! macro"] SimpleList["List<T> type alias"] GeneratedNodes["Generated Node Types"] end DefNodeMacro --> GeneratedNodes GeneratedNodes --> GetLinksTrait GetLinksWrappedTrait --> GetLinksTrait LinksStruct --> AtomicOps LinksStruct --> ListEntryStruct RawListStruct --> GetLinksTrait RawListStruct --> LinksStruct SimpleList --> WrappedList WrappedList --> GetLinksWrappedTrait WrappedList --> RawListStruct WrappedList --> WrapperTrait WrapperTrait --> ArcImpl WrapperTrait --> BoxImpl WrapperTrait --> RefImpl
Layer 1 (Raw Unsafe): Provides maximum performance through direct pointer manipulation and atomic operations. The RawList<G: GetLinks>
struct handles all unsafe linked list operations.
Layer 2 (Safe Wrapper): Manages memory ownership and provides safe abstractions over the raw layer. The List<G: GetLinksWrapped>
struct ensures memory safety through the Wrapper<T>
trait.
Layer 3 (User-Friendly): Offers convenient APIs and code generation. The def_node!
macro automatically generates node types that implement required traits.
Sources: src/lib.rs(L1 - L179) src/linked_list.rs(L1 - L355) src/raw_list.rs(L1 - L596)
Type System and Trait Architecture
The crate's type system uses traits to enable flexibility while maintaining type safety:
flowchart TD subgraph subGraph3["Wrapper Implementations"] BoxWrapper["impl Wrapper<T> for Box<T>"] ArcWrapper["impl Wrapper<T> for Arc<T>"] RefWrapper["impl Wrapper<T> for &T"] end subgraph subGraph2["Generated Types"] MacroGenerated["def_node! generated• inner: T• links: Links<Self>• implements GetLinks"] end subgraph subGraph1["Data Structures"] Links["Links<T> struct• inserted: AtomicBool• entry: UnsafeCell"] ListEntry["ListEntry<T> struct• next: Option<NonNull<T>>• prev: Option<NonNull<T>>"] RawList["RawList<G> struct• head: Option<NonNull>"] List["List<G> struct• list: RawList<G>"] end subgraph subGraph0["Core Traits"] GetLinks["GetLinks trait• type EntryType• fn get_links()"] GetLinksWrapped["GetLinksWrapped trait• type Wrapped• extends GetLinks"] Wrapper["Wrapper<T> trait• fn into_pointer()• fn from_pointer()• fn as_ref()"] end ArcWrapper --> Wrapper BoxWrapper --> Wrapper GetLinksWrapped --> GetLinks GetLinksWrapped --> Wrapper Links --> ListEntry List --> GetLinksWrapped List --> RawList MacroGenerated --> GetLinks RawList --> GetLinks RawList --> Links RefWrapper --> Wrapper
The GetLinks
trait (src/raw_list.rs(L23 - L29) ) allows any type to participate in linked lists by providing access to embedded Links<T>
. The Wrapper<T>
trait (src/linked_list.rs(L18 - L31) ) abstracts over different ownership models, enabling the same list implementation to work with Box<T>
, Arc<T>
, and &T
.
Sources: src/raw_list.rs(L23 - L29) src/linked_list.rs(L18 - L31) src/linked_list.rs(L85 - L121) src/lib.rs(L11 - L107)
Memory Management Architecture
The crate implements a controlled boundary between safe and unsafe code to manage memory ownership:
flowchart TD subgraph subGraph2["Unsafe Pointer Zone"] NonNullPtrs["NonNull<T>Raw pointers"] RawListOps["RawList<G>Unsafe operations"] AtomicInsertion["Links<T>.insertedAtomicBool tracking"] PointerArithmetic["ListEntry<T>next/prev pointers"] end subgraph subGraph1["Safety Boundary"] WrapperLayer["Wrapper<T> traitinto_pointer() / from_pointer()"] GetLinksWrappedLayer["GetLinksWrapped traittype Wrapped"] ListLayer["List<G> structSafe operations"] end subgraph subGraph0["Safe Memory Zone"] UserCode["User Code"] OwnedBox["Box<Node>Heap ownership"] SharedArc["Arc<Node>Reference counting"] BorrowedRef["&NodeBorrowed reference"] end BorrowedRef --> WrapperLayer GetLinksWrappedLayer --> ListLayer ListLayer --> NonNullPtrs NonNullPtrs --> RawListOps OwnedBox --> WrapperLayer RawListOps --> AtomicInsertion RawListOps --> PointerArithmetic SharedArc --> WrapperLayer UserCode --> BorrowedRef UserCode --> OwnedBox UserCode --> SharedArc WrapperLayer --> GetLinksWrappedLayer
The safety boundary ensures that:
- Owned objects are converted to raw pointers only when inserted
- Raw pointers are converted back to owned objects when removed
- Atomic tracking prevents double-insertion and use-after-free
- Memory lifetime is managed by the wrapper type
Sources: src/linked_list.rs(L33 - L83) src/raw_list.rs(L35 - L66) src/raw_list.rs(L74 - L86)
Data Flow and Operation Architecture
Operations flow through the abstraction layers with ownership transformations at each boundary:
The data flow demonstrates how:
- The macro generates boilerplate
GetLinks
implementations - Ownership transfers happen at wrapper boundaries
- Atomic operations ensure thread-safe insertion tracking
- Constant-time removal works through direct pointer manipulation
Sources: src/lib.rs(L11 - L107) src/linked_list.rs(L153 - L162) src/linked_list.rs(L201 - L208) src/raw_list.rs(L57 - L65) src/raw_list.rs(L199 - L235)
Cursor and Iterator Architecture
The crate provides both immutable iteration and mutable cursor-based traversal:
flowchart TD subgraph subGraph2["Raw Layer Implementation"] RawIterator["raw_list::Iterator<G>• cursor_front: Cursor• cursor_back: Cursor"] RawCursor["raw_list::Cursor<G>• current() -> &T• move_next/prev()"] RawCursorMut["raw_list::CursorMut<G>• current() -> &mut T• remove_current()• peek operations"] CommonCursor["CommonCursor<G>• cur: Option<NonNull>• move_next/prev logic"] end subgraph subGraph1["Cursor-Based Mutation"] CursorMut["List<G>::cursor_front_mut()Returns CursorMut<G>"] CursorMutOps["CursorMut operations• current() -> &mut T• remove_current()• peek_next/prev()• move_next()"] end subgraph subGraph0["High-Level Iteration"] ListIter["List<G>::iter()Returns Iterator<G>"] ListIterImpl["impl Iterator for Iterator<G>Delegates to raw_list::Iterator"] end CursorMut --> CursorMutOps CursorMutOps --> RawCursorMut ListIter --> ListIterImpl ListIterImpl --> RawIterator RawCursor --> CommonCursor RawCursorMut --> CommonCursor RawIterator --> RawCursor
Cursors enable safe mutable access to list elements while maintaining the linked list invariants. The CursorMut<G>
(src/linked_list.rs(L238 - L276) ) provides ownership-aware removal that properly converts raw pointers back to owned objects.
Sources: src/linked_list.rs(L139 - L142) src/linked_list.rs(L220 - L276) src/raw_list.rs(L104 - L106) src/raw_list.rs(L270 - L423) src/raw_list.rs(L433 - L464)
API Reference
Relevant source files
This document provides comprehensive documentation of all public APIs in the linked_list_r4l
crate, organized by abstraction level. The crate provides three distinct API layers: a user-friendly macro-based interface for typical usage, an advanced ownership management interface for complex scenarios, and a low-level unsafe interface for maximum performance.
For conceptual information about memory management and thread safety, see Core Concepts. For practical usage examples, see Quick Start Guide.
API Architecture Overview
The following diagram illustrates the complete API structure and relationships between the three abstraction layers:
flowchart TD subgraph subGraph2["Low-Level API Layer (raw_list.rs)"] GetLinksTrait["GetLinks trait"] LinksStruct["Links<T> struct"] ListEntryStruct["ListEntry<T> struct"] RawListStruct["RawList<G: GetLinks>"] RawCursor["Cursor<G>"] RawCursorMut["CursorMut<G>"] RawIterator["Iterator<G>"] end subgraph subGraph1["Advanced API Layer (linked_list.rs)"] WrapperTrait["Wrapper<T> trait"] GetLinksWrappedTrait["GetLinksWrapped trait"] AdvancedList["List<G: GetLinksWrapped>"] AdvancedCursor["CursorMut<G>"] AdvancedIterator["Iterator<G>"] BoxImpl["Box<T> impl"] ArcImpl["Arc<T> impl"] RefImpl["&T impl"] end subgraph subGraph0["User-Friendly API Layer (lib.rs)"] DefNodeMacro["def_node! macro"] GeneratedStruct["Generated Node Struct"] SimpleList["List<Box<Node>>"] end AdvancedCursor --> RawCursorMut AdvancedIterator --> RawIterator AdvancedList --> RawListStruct DefNodeMacro --> GeneratedStruct GeneratedStruct --> SimpleList GetLinksTrait --> LinksStruct GetLinksWrappedTrait --> GetLinksTrait GetLinksWrappedTrait --> WrapperTrait LinksStruct --> ListEntryStruct RawListStruct --> GetLinksTrait RawListStruct --> RawCursor RawListStruct --> RawCursorMut RawListStruct --> RawIterator SimpleList --> AdvancedList WrapperTrait --> ArcImpl WrapperTrait --> BoxImpl WrapperTrait --> RefImpl
Sources: src/lib.rs(L1 - L179) src/linked_list.rs(L1 - L355) src/raw_list.rs(L1 - L596)
Core Type Hierarchy and Trait System
This diagram shows the relationships between the main traits and types, mapping directly to code entities:
flowchart TD subgraph subGraph3["Wrapper Implementations"] BoxWrapper["impl Wrapper<T> for Box<T>"] ArcWrapper["impl Wrapper<T> for Arc<T>"] RefWrapper["impl Wrapper<T> for &T"] BoxGetLinksWrapped["impl GetLinksWrapped for Box<T>"] ArcGetLinksWrapped["impl GetLinksWrapped for Arc<T>"] end subgraph subGraph2["Generated Types"] NodeStruct["Generated Node Struct"] NodeGetLinks["impl GetLinks for Node"] end subgraph subGraph1["Core Data Structures"] LinksT["Links<T>"] ListEntryT["ListEntry<T>"] RawListT["RawList<G>"] ListT["List<G>"] end subgraph subGraph0["Trait Definitions"] GetLinksT["GetLinks"] GetLinksWrappedT["GetLinksWrapped"] WrapperT["Wrapper<T>"] end ArcGetLinksWrapped --> GetLinksWrappedT ArcWrapper --> WrapperT BoxGetLinksWrapped --> GetLinksWrappedT BoxWrapper --> WrapperT GetLinksWrappedT --> GetLinksT GetLinksWrappedT --> WrapperT LinksT --> ListEntryT ListT --> GetLinksWrappedT ListT --> RawListT NodeGetLinks --> GetLinksT NodeStruct --> NodeGetLinks RawListT --> GetLinksT RefWrapper --> WrapperT
Sources: src/raw_list.rs(L23 - L29) src/linked_list.rs(L18 - L89) src/lib.rs(L11 - L107)
User-Friendly API
def_node!Macro
The def_node!
macro generates complete node types suitable for use in linked lists.
Syntax:
def_node! {
/// Optional documentation
[visibility] struct NodeName(inner_type);
/// Generic version
[visibility] struct GenericNode<T>(T);
}
Generated Interface: Each generated node type includes:
Method | Description | Return Type |
---|---|---|
new(inner: T) | Constructs a new node | Self |
inner(&self) | Returns reference to wrapped value | &T |
into_inner(self) | Consumes node, returns wrapped value | T |
The macro also implements GetLinks
, Deref
, and provides embedded Links<Self>
for list operations.
Sources: src/lib.rs(L109 - L178) src/lib.rs(L11 - L107)
Simple List Type
For basic usage with generated nodes, the crate re-exports List
from the advanced API:
pub use linked_list::List;
This allows simple usage patterns like List<Box<MyNode>>
without requiring knowledge of the underlying trait system.
Sources: src/lib.rs(L6)
Advanced API
WrapperTrait
The Wrapper<T>
trait abstracts over different ownership models, enabling the same list implementation to work with Box<T>
, Arc<T>
, and &T
.
#![allow(unused)] fn main() { pub trait Wrapper<T: ?Sized> { fn into_pointer(self) -> NonNull<T>; unsafe fn from_pointer(ptr: NonNull<T>) -> Self; fn as_ref(&self) -> &T; } }
Implementations:
Type | into_pointer | from_pointer | as_ref |
---|---|---|---|
Box | Box::into_raw | Box::from_raw | AsRef::as_ref |
Arc | Arc::into_raw | Arc::from_raw | AsRef::as_ref |
&T | NonNull::from | Dereference pointer | Identity |
Sources: src/linked_list.rs(L18 - L83)
GetLinksWrappedTrait
The GetLinksWrapped
trait combines GetLinks
functionality with wrapper ownership management:
pub trait GetLinksWrapped: GetLinks {
type Wrapped: Wrapper<Self::EntryType>;
}
This trait is automatically implemented for Box<T>
and Arc<T>
when T
implements GetLinks
.
Sources: src/linked_list.rs(L86 - L121)
List<G: GetLinksWrapped>
The main list type that manages owned elements through the wrapper system.
Core Methods:
Method | Description | Safety Requirements |
---|---|---|
new() | Creates empty list | None |
is_empty() | Checks if list is empty | None |
push_back(data) | Adds element to end | None |
push_front(data) | Adds element to beginning | None |
pop_front() | Removes and returns first element | None |
insert_after(existing, data) | Inserts after existing element | existingmust be on this list |
remove(data) | Removes specific element | datamust be on this list or no list |
iter() | Returns iterator | None |
cursor_front_mut() | Returns mutable cursor | None |
Sources: src/linked_list.rs(L127 - L223)
CursorMut<'a, G>
Provides mutable cursor functionality for advanced list manipulation:
Method | Description |
---|---|
current() | Returns mutable reference to current element |
remove_current() | Removes current element and advances cursor |
peek_next() | Returns reference to next element |
peek_prev() | Returns reference to previous element |
move_next() | Advances cursor to next element |
Sources: src/linked_list.rs(L238 - L276)
Iterator<'a, G>
Standard iterator implementation supporting both forward and backward iteration:
- Implements
Iterator
withItem = &'a G::EntryType
- Implements
DoubleEndedIterator
for reverse iteration
Sources: src/linked_list.rs(L279 - L303)
Low-Level API
GetLinksTrait
The fundamental trait that allows any type to be stored in a linked list:
#![allow(unused)] fn main() { pub trait GetLinks { type EntryType: ?Sized; fn get_links(data: &Self::EntryType) -> &Links<Self::EntryType>; } }
Types implementing this trait must provide access to embedded Links<T>
for list management.
Sources: src/raw_list.rs(L23 - L29)
LinksStructure
The core structure embedded in list elements, managing insertion state and list pointers:
pub struct Links<T: ?Sized> {
inserted: AtomicBool,
entry: UnsafeCell<ListEntry<T>>,
}
Key Methods:
Method | Visibility | Purpose |
---|---|---|
new() | Public | Creates new uninserted Links |
acquire_for_insertion() | Private | Atomically marks as inserted |
release_after_removal() | Private | Atomically marks as not inserted |
The AtomicBool
ensures thread-safe insertion tracking, while UnsafeCell<ListEntry<T>>
holds the actual list pointers.
Sources: src/raw_list.rs(L35 - L72)
ListEntryStructure
Internal structure containing the actual linked list pointers:
struct ListEntry<T: ?Sized> {
next: Option<NonNull<T>>,
prev: Option<NonNull<T>>,
}
This structure is private and managed entirely by the RawList
implementation.
Sources: src/raw_list.rs(L74 - L86)
RawList<G: GetLinks>
The low-level unsafe list implementation providing maximum performance:
Core Operations:
Method | Safety Requirements | Returns |
---|---|---|
new() | None | Empty list |
is_empty() | None | bool |
push_back(entry) | Entry must be valid and live longer than list | bool(insertion success) |
push_front(entry) | Entry must be valid and live longer than list | bool(insertion success) |
insert_after(existing, new) | Both entries must be valid, existing must be on list | bool(insertion success) |
remove(entry) | Entry must be on this list or no list | bool(removal success) |
pop_front() | None | Option<NonNull<G::EntryType>> |
Sources: src/raw_list.rs(L93 - L284)
Low-Level Cursors and Iterators
The raw list provides cursor and iterator types for traversal:
Cursor<'a, G>
(read-only):
current()
- Returns current element referencemove_next()
- Advances to next elementmove_prev()
- Moves to previous element
CursorMut<'a, G>
(mutable):
current()
- Returns mutable reference to current elementremove_current()
- Removes current element and advancespeek_next()
/peek_prev()
- Look at adjacent elementsmove_next()
- Advances cursor
Iterator<'a, G>
:
- Implements standard
Iterator
andDoubleEndedIterator
traits - Used by higher-level list types
Sources: src/raw_list.rs(L340 - L464)
Thread Safety Characteristics
All types implement Send
and Sync
conditionally based on their entry types:
RawList<G>
isSend + Sync
whenG::EntryType: Send + Sync
Links<T>
isSend + Sync
whenT: Send + Sync
- Atomic operations in
Links<T>
ensure safe concurrent access
Sources: src/raw_list.rs(L40 - L46) src/raw_list.rs(L331 - L337)
User-Friendly API
Relevant source files
This page documents the user-friendly API layer of the linked_list_r4l crate, which provides the simplest interface for typical usage scenarios. This layer consists of the def_node!
macro for automatic node generation and the simple List<T>
interface for basic list operations.
For advanced ownership management and custom wrapper types, see Advanced API. For low-level unsafe operations and performance-critical usage, see Low-Level API.
The def_node! Macro
The def_node!
macro is the primary entry point for users to create list-compatible node types. It automatically generates the necessary boilerplate code to make any type work with the linked list system.
Macro Syntax
The macro supports two syntax forms:
Form | Example | Use Case |
---|---|---|
Simple struct | struct NodeName(inner_type); | Non-generic wrapped types |
Generic struct | struct NodeName | Generic wrapped types |
Generated Node Structure
When you invoke def_node!
, it generates a complete node structure with the following components:
flowchart TD subgraph subGraph3["List Integration"] ListUsage["List>"] end subgraph subGraph2["Generated Implementations"] GetLinksImpl["GetLinks implementation"] NodeMethods["Node methods:• new()• inner()• into_inner()"] DerefImpl["Deref implementation"] end subgraph subGraph1["Generated Node Structure"] NodeStruct["MyNode struct"] InnerField["inner: i32"] LinksField["links: Links"] end subgraph subGraph0["def_node! Macro Input"] UserStruct["struct MyNode(i32)"] end NodeStruct --> DerefImpl NodeStruct --> GetLinksImpl NodeStruct --> InnerField NodeStruct --> LinksField NodeStruct --> ListUsage NodeStruct --> NodeMethods UserStruct --> NodeStruct
Sources: src/lib.rs(L11 - L107) src/lib.rs(L168 - L178)
Node Methods
Each generated node provides these essential methods:
Method | Signature | Purpose |
---|---|---|
new() | pub const fn new(inner: T) -> Self | Creates a new node wrapping the inner value |
inner() | pub const fn inner(&self) -> &T | Returns a reference to the wrapped value |
into_inner() | pub fn into_inner(self) -> T | Consumes the node and returns the wrapped value |
The generated nodes also implement Deref
, allowing direct access to the inner type's methods through the node.
Sources: src/lib.rs(L28 - L48) src/lib.rs(L76 - L96) src/lib.rs(L50 - L57) src/lib.rs(L98 - L105)
Code Generation Flow
The macro expansion process transforms user declarations into fully functional list nodes:
flowchart TD subgraph subGraph3["Usage Ready"] BoxedUsage["Box"] ListUsage["List>"] end subgraph subGraph2["Generated Code Components"] StructDef["struct MyNode {inner: i32,links: Links}"] GetLinksImpl["impl GetLinks for MyNode"] Methods["impl MyNode {new(), inner(), into_inner()}"] DerefImpl["impl Deref for MyNode"] end subgraph subGraph1["Macro Processing"] DefNodeMacro["def_node! macro"] InternalMacro["__def_node_internal! macro"] end subgraph subGraph0["User Code"] UserDecl["def_node! {struct MyNode(i32);}"] end BoxedUsage --> ListUsage DefNodeMacro --> InternalMacro InternalMacro --> DerefImpl InternalMacro --> GetLinksImpl InternalMacro --> Methods InternalMacro --> StructDef StructDef --> BoxedUsage UserDecl --> DefNodeMacro
Sources: src/lib.rs(L168 - L178) src/lib.rs(L11 - L107)
Simple List Interface
The user-friendly API provides a straightforward List<T>
interface that works with any boxed node type generated by def_node!
.
Basic Operations
The simple list interface supports these fundamental operations:
Operation | Method | Description |
---|---|---|
Creation | List::new() | Creates an empty list |
Insertion | push_back(node),push_front(node) | Adds nodes to the list |
Removal | pop_back(),pop_front() | Removes and returns nodes |
Iteration | iter() | Provides iterator over list elements |
Typical Usage Pattern
Here's the standard workflow for using the user-friendly API:
Sources: src/lib.rs(L124 - L165) README.md(L15 - L62)
Complete Usage Example
The macro documentation includes a comprehensive example showing both simple and generic node usage:
- Node Definition: Using
def_node!
to create various node types with different visibility modifiers - List Creation: Instantiating typed lists for specific node types
- List Operations: Adding, iterating, and removing nodes
- Value Access: Using
inner()
to access wrapped values andinto_inner()
to extract them
The example demonstrates the seamless integration between the macro-generated nodes and the list operations, showing how the user-friendly API hides the complexity of the underlying link management.
Sources: src/lib.rs(L124 - L165) README.md(L46 - L61)
Integration with Type System
The user-friendly API integrates cleanly with Rust's type system:
- Ownership: Nodes are typically owned through
Box<T>
for heap allocation - Type Safety: The list type
List<Box<NodeType>>
ensures only compatible nodes can be inserted - Generic Support: Both the nodes and lists can be generic over inner types
- Deref Coercion: Nodes automatically dereference to their inner type for convenient method access
This design allows users to work with linked lists using familiar Rust patterns while benefiting from the high-performance constant-time removal capabilities of the underlying implementation.
Sources: src/lib.rs(L6) src/lib.rs(L50 - L57) src/lib.rs(L98 - L105)
Advanced API
Relevant source files
This document covers the advanced API layer of the linked_list_r4l crate, which provides safe ownership management and flexible pointer wrapping capabilities. This API sits between the user-friendly interface (see User-Friendly API) and the low-level unsafe operations (see Low-Level API).
The advanced API is designed for users who need control over memory ownership models while maintaining memory safety guarantees. It centers around the List<G: GetLinksWrapped>
type and the Wrapper
trait system that abstracts over different pointer types like Box<T>
, Arc<T>
, and &T
.
Core Trait System
The advanced API is built on two key traits that work together to provide type-safe ownership management:
Wrapper Trait
The Wrapper<T>
trait abstracts over different pointer types and ownership models, providing a uniform interface for converting between owned objects and raw pointers.
flowchart TD WrapperTrait["Wrapper<T> trait"] IntoPointer["into_pointer() -> NonNull<T>"] FromPointer["from_pointer(NonNull<T>) -> Self"] AsRef["as_ref() -> &T"] BoxImpl["Box<T> impl"] ArcImpl["Arc<T> impl"] RefImpl["&T impl"] BoxIntoRaw["Box::into_raw()"] BoxFromRaw["Box::from_raw()"] ArcIntoRaw["Arc::into_raw()"] ArcFromRaw["Arc::from_raw()"] NonNullFrom["NonNull::from()"] PtrDeref["ptr.as_ptr() deref"] ArcImpl --> ArcFromRaw ArcImpl --> ArcIntoRaw ArcImpl --> WrapperTrait BoxImpl --> BoxFromRaw BoxImpl --> BoxIntoRaw BoxImpl --> WrapperTrait RefImpl --> NonNullFrom RefImpl --> PtrDeref RefImpl --> WrapperTrait WrapperTrait --> AsRef WrapperTrait --> FromPointer WrapperTrait --> IntoPointer
Wrapper Trait Implementations
The trait is implemented for three fundamental pointer types:
Type | Ownership Model | Use Case |
---|---|---|
Box | Unique ownership | Single-threaded owned data |
Arc | Shared ownership | Multi-threaded reference counting |
&T | Borrowed reference | Temporary list membership |
Sources: src/linked_list.rs(L18 - L83)
GetLinksWrapped Trait
The GetLinksWrapped
trait extends GetLinks
to specify which wrapper type should be used for list elements:
flowchart TD GetLinksWrapped["GetLinksWrapped trait"] GetLinks["extends GetLinks"] WrappedType["type Wrapped: Wrapper<Self::EntryType>"] BoxGetLinksWrapped["Box<T> impl GetLinksWrapped"] ArcGetLinksWrapped["Arc<T> impl GetLinksWrapped"] BoxWrapped["type Wrapped = Box<T::EntryType>"] ArcWrapped["type Wrapped = Arc<T::EntryType>"] ArcGetLinksWrapped --> ArcWrapped ArcGetLinksWrapped --> GetLinksWrapped BoxGetLinksWrapped --> BoxWrapped BoxGetLinksWrapped --> GetLinksWrapped GetLinksWrapped --> GetLinks GetLinksWrapped --> WrappedType
Sources: src/linked_list.rs(L85 - L121)
List<G: GetLinksWrapped> Interface
The List<G: GetLinksWrapped>
struct provides the main interface for ownership-managed linked lists. It wraps the lower-level RawList<G>
and handles ownership transfer automatically.
Core Operations
List Creation and Basic Operations
Method | Purpose | Safety |
---|---|---|
new() | Create empty list | Safe |
push_back(data) | Add to end | Safe, auto-cleanup on failure |
push_front(data) | Add to beginning | Safe, auto-cleanup on failure |
pop_front() | Remove from beginning | Safe, returns ownership |
is_empty() | Check if empty | Safe |
Sources: src/linked_list.rs(L131 - L147) src/linked_list.rs(L149 - L177) src/linked_list.rs(L210 - L217)
Advanced Operations
The interface also provides more sophisticated operations for precise list manipulation:
flowchart TD AdvancedOps["Advanced Operations"] InsertAfter["insert_after(existing, data)"] Remove["remove(data)"] CursorMut["cursor_front_mut()"] UnsafeBlock["unsafe block required"] AsRefCall["Wrapper::as_ref(existing)"] OwnershipReturn["Option<G::Wrapped>"] CursorMutStruct["CursorMut<'a, G>"] CurrentAccess["current()"] RemoveCurrent["remove_current()"] Navigation["move_next(), peek_next(), peek_prev()"] AdvancedOps --> CursorMut AdvancedOps --> InsertAfter AdvancedOps --> Remove CursorMut --> CursorMutStruct CursorMutStruct --> CurrentAccess CursorMutStruct --> Navigation CursorMutStruct --> RemoveCurrent InsertAfter --> AsRefCall InsertAfter --> UnsafeBlock Remove --> OwnershipReturn Remove --> UnsafeBlock
Unsafe Operations Requirements
Method | Safety Requirement |
---|---|
insert_after(existing, data) | existingmust be valid entry on this list |
remove(data) | datamust be on this list or no list |
Sources: src/linked_list.rs(L178 - L208)
Cursors and Iterators
The advanced API provides two mechanisms for list traversal with different capabilities:
CursorMut
The CursorMut<'a, G>
provides mutable access and modification capabilities during traversal:
flowchart TD CursorMutStruct["CursorMut<'a, G>"] RawCursor["raw_list::CursorMut<'a, G>"] CurrentMethod["current() -> Option<&mut G::EntryType>"] RemoveCurrentMethod["remove_current() -> Option<G::Wrapped>"] PeekNext["peek_next() -> Option<&mut G::EntryType>"] PeekPrev["peek_prev() -> Option<&mut G::EntryType>"] MoveNext["move_next()"] OwnershipReturn["Returns owned object"] MutableAccess["Allows mutation"] CurrentMethod --> MutableAccess CursorMutStruct --> CurrentMethod CursorMutStruct --> MoveNext CursorMutStruct --> PeekNext CursorMutStruct --> PeekPrev CursorMutStruct --> RawCursor CursorMutStruct --> RemoveCurrentMethod RemoveCurrentMethod --> OwnershipReturn
Sources: src/linked_list.rs(L237 - L276)
Iterator
The Iterator<'a, G>
provides immutable forward and backward traversal:
flowchart TD IteratorStruct["Iterator<'a, G>"] RawIterator["raw_list::Iterator<'a, G>"] StdIterator["impl Iterator"] DoubleEnded["impl DoubleEndedIterator"] NextMethod["next() -> Option<&G::EntryType>"] NextBackMethod["next_back() -> Option<&G::EntryType>"] ImmutableRef["Immutable reference"] DoubleEnded --> NextBackMethod IteratorStruct --> DoubleEnded IteratorStruct --> RawIterator IteratorStruct --> StdIterator NextBackMethod --> ImmutableRef NextMethod --> ImmutableRef StdIterator --> NextMethod
Sources: src/linked_list.rs(L278 - L303)
Ownership Management
The advanced API's key strength is its automatic ownership management across different pointer types:
Ownership Transfer Flow
flowchart TD subgraph subGraph0["Memory Safety"] ListStorage["List internal storage"] RawPointers["NonNull<T> storage"] AtomicOps["Atomic operations"] end UserCode["User Code"] WrappedObject["G::Wrapped object"] IntoPointer["into_pointer()"] RemovalOp["Removal operation"] FromPointer["from_pointer()"] ReturnedObject["Owned object returned"] InsertionFail["Insertion failure"] AutoCleanup["Automatic cleanup"] AutoCleanup --> FromPointer FromPointer --> ReturnedObject InsertionFail --> AutoCleanup IntoPointer --> ListStorage ListStorage --> RawPointers ListStorage --> RemovalOp RawPointers --> AtomicOps RemovalOp --> FromPointer UserCode --> WrappedObject WrappedObject --> InsertionFail WrappedObject --> IntoPointer
Ownership Transfer Guarantees
Scenario | Behavior | Memory Safety |
---|---|---|
Successful insertion | Ownership transferred to list | Guaranteed byinto_pointer() |
Failed insertion | Object automatically reconstructed | Ensured byfrom_pointer()call |
Successful removal | Ownership returned to caller | Guaranteed byfrom_pointer() |
List drop | All elements properly dropped | Handled byDropimplementation |
Sources: src/linked_list.rs(L153 - L162) src/linked_list.rs(L168 - L176) src/linked_list.rs(L231 - L235)
Drop Behavior
The List<G>
implements Drop
to ensure all remaining elements are properly cleaned up:
flowchart TD DropImpl["List<G> Drop"] PopLoop["while pop_front().is_some()"] FromPointer["G::Wrapped::from_pointer()"] WrapperDrop["Wrapper's Drop impl"] BoxDrop["Box: heap deallocation"] ArcDrop["Arc: reference count decrement"] RefDrop["&T: no action needed"] DropImpl --> PopLoop FromPointer --> WrapperDrop PopLoop --> FromPointer WrapperDrop --> ArcDrop WrapperDrop --> BoxDrop WrapperDrop --> RefDrop
Sources: src/linked_list.rs(L231 - L235)
Thread Safety Considerations
While the advanced API provides memory safety guarantees, thread safety depends on the underlying RawList
implementation and the specific wrapper type used:
Wrapper Type | Thread Safety Notes |
---|---|
Box | Single-threaded ownership, no inherent thread safety |
Arc | Reference counting is thread-safe, but list operations are not |
&T | Depends on the referenced data's thread safety |
For multi-threaded usage, external synchronization (e.g., Mutex
, RwLock
) around the List<G>
is required.
Sources: src/linked_list.rs(L50 - L66)
Low-Level API
Relevant source files
This page documents the unsafe, low-level API provided by the RawList<G: GetLinks>
interface for advanced users who need direct control over memory management and performance-critical operations. This layer operates with raw pointers and requires careful attention to memory safety invariants.
For safe ownership management and wrapper abstractions, see Advanced API. For the user-friendly macro-generated interface, see User-Friendly API.
Core Types and Traits
The low-level API is built around three primary types that work together to provide intrusive linked list functionality.
GetLinks Trait
The GetLinks
trait serves as the fundamental abstraction that allows any type to be stored in a linked list by providing access to embedded link fields.
flowchart TD subgraph subGraph1["Implementation Requirements"] UserStruct["User Struct(contains Links field)"] LinksField["links: Links"] ImplBlock["impl GetLinksfor UserStruct"] end subgraph subGraph0["GetLinks Trait System"] GetLinksTrait["GetLinkstrait"] EntryType["type EntryType: ?Sized"] GetLinksMethod["get_links(&EntryType)-> &Links"] end GetLinksMethod --> LinksField GetLinksTrait --> EntryType GetLinksTrait --> GetLinksMethod ImplBlock --> GetLinksTrait UserStruct --> ImplBlock UserStruct --> LinksField
Core GetLinks Implementation Pattern
The trait defines two key components: an associated type EntryType
that represents the type of objects stored in the list, and a method get_links()
that returns a reference to the embedded Links<T>
structure.
Sources: src/raw_list.rs(L23 - L29)
Links Structure
The Links<T>
structure contains the actual linked list pointers and provides atomic insertion tracking to prevent double-insertion and enable constant-time removal.
flowchart TD subgraph subGraph2["Atomic Operations"] AcquireInsert["acquire_for_insertion()compare_exchange(false, true)"] ReleaseRemove["release_after_removal()store(false, Release)"] end subgraph subGraph1["ListEntry Contents"] NextPtr["next: Option>"] PrevPtr["prev: Option>"] end subgraph subGraph0["Links Memory Layout"] LinksStruct["Links"] InsertedFlag["inserted: AtomicBool(tracks insertion state)"] EntryCell["entry: UnsafeCell>(contains pointers)"] end EntryCell --> NextPtr EntryCell --> PrevPtr InsertedFlag --> AcquireInsert InsertedFlag --> ReleaseRemove LinksStruct --> EntryCell LinksStruct --> InsertedFlag
Atomic Insertion Tracking
The inserted
field uses atomic compare-and-swap operations to ensure thread-safe insertion tracking. The acquire_for_insertion()
method attempts to atomically change the state from false
to true
, preventing double-insertion.
Sources: src/raw_list.rs(L35 - L66) src/raw_list.rs(L74 - L86)
RawList Structure
The RawList<G: GetLinks>
provides the core linked list operations using raw pointer manipulation.
flowchart TD subgraph subGraph3["Iteration Support"] Iter["iter() -> Iterator"] CursorFront["cursor_front_mut()-> CursorMut"] end subgraph subGraph2["Query Operations"] IsEmpty["is_empty() -> bool"] Front["front() -> Option>"] Back["back() -> Option>"] end subgraph subGraph1["Primary Operations"] PushBack["push_back(&EntryType)-> bool"] PushFront["push_front(&EntryType)-> bool"] Remove["remove(&EntryType)-> bool"] PopFront["pop_front()-> Option>"] InsertAfter["insert_after(&existing, &new)-> bool"] end subgraph subGraph0["RawList"] RawListStruct["RawList"] HeadPtr["head: Option>"] end RawListStruct --> Back RawListStruct --> CursorFront RawListStruct --> Front RawListStruct --> HeadPtr RawListStruct --> InsertAfter RawListStruct --> IsEmpty RawListStruct --> Iter RawListStruct --> PopFront RawListStruct --> PushBack RawListStruct --> PushFront RawListStruct --> Remove
Sources: src/raw_list.rs(L88 - L284)
List Operations
Insertion Operations
The RawList provides several insertion methods that work with raw references and return boolean values indicating success or failure.
Operation | Method | Safety Requirements | Return Value |
---|---|---|---|
Back Insertion | push_back(&EntryType) | Reference must remain valid while on list | bool- true if inserted, false if already on list |
Front Insertion | push_front(&EntryType) | Reference must remain valid while on list | bool- true if inserted, false if already on list |
Positional Insertion | insert_after(&existing, &new) | Both references valid, existing must be on list | bool- true if inserted, false if new already on list |
Insertion Safety Model
All insertion operations use the atomic acquire_for_insertion()
mechanism to prevent double-insertion. The operations are marked unsafe
because the caller must ensure reference validity for the lifetime of list membership.
Sources: src/raw_list.rs(L153 - L197) src/raw_list.rs(L135 - L151)
Removal Operations
Removal operations provide constant-time complexity by updating surrounding pointers directly without traversal.
flowchart TD subgraph subGraph0["Removal Process"] CheckInsertion["Check insertion state(entry.next != None)"] UpdatePointers["Update prev.nextand next.prev"] UpdateHead["Update head ifremoving head element"] ResetLinks["Reset entry.nextand entry.prev to None"] ReleaseState["release_after_removal()store(false, Release)"] end CheckInsertion --> UpdatePointers ResetLinks --> ReleaseState UpdateHead --> ResetLinks UpdatePointers --> UpdateHead
Constant-Time Removal Implementation
The remove_internal()
method achieves O(1) complexity by directly accessing the target element's links rather than traversing the list. The atomic release operation ensures thread-safe state management.
Sources: src/raw_list.rs(L199 - L245) src/raw_list.rs(L247 - L257)
Cursor System
The cursor system provides controlled traversal and modification capabilities for the linked list.
Cursor Types
Cursor Type | Mutability | Capabilities |
---|---|---|
Cursor<'a, G> | Immutable | Read-only traversal, element inspection |
CursorMut<'a, G> | Mutable | Traversal, element modification, removal |
Cursor Operations
flowchart TD subgraph subGraph2["Mutable Cursor"] CursorMut["CursorMut<'a, G>"] CurrentMut["current() -> Option<&mut EntryType>"] RemoveCurrent["remove_current()-> Option>"] PeekNext["peek_next() -> Option<&mut EntryType>"] PeekPrev["peek_prev() -> Option<&mut EntryType>"] end subgraph subGraph1["Read-Only Cursor"] Cursor["Cursor<'a, G>"] Current["current() -> Option<&EntryType>"] end subgraph subGraph0["Cursor Navigation"] CommonCursor["CommonCursor"] MoveNext["move_next(&RawList)"] MovePrev["move_prev(&RawList)"] CurrentPos["cur: Option>"] end CommonCursor --> CurrentPos CommonCursor --> MoveNext CommonCursor --> MovePrev Cursor --> CommonCursor Cursor --> Current CursorMut --> CommonCursor CursorMut --> CurrentMut CursorMut --> PeekNext CursorMut --> PeekPrev CursorMut --> RemoveCurrent
Cursor Safety Model
Cursors maintain lifetime relationships with the underlying list to prevent use-after-free scenarios. The mutable cursor provides safe removal operations that automatically advance the cursor position.
Sources: src/raw_list.rs(L339 - L423) src/raw_list.rs(L286 - L329)
Iterator Implementation
The iterator system provides both forward and backward traversal capabilities.
Iterator Structure
flowchart TD subgraph subGraph2["IntoIterator Support"] IntoIter["impl IntoIterator for &RawListinto_iter() -> Iterator"] end subgraph subGraph1["Iterator Traits"] StdIterator["impl Iteratornext() -> Option<&EntryType>"] DoubleEnded["impl DoubleEndedIteratornext_back() -> Option<&EntryType>"] end subgraph subGraph0["Iterator Implementation"] IteratorStruct["Iterator<'a, G>"] CursorFront["cursor_front: Cursor<'a, G>"] CursorBack["cursor_back: Cursor<'a, G>"] end IntoIter --> IteratorStruct IteratorStruct --> CursorBack IteratorStruct --> CursorFront IteratorStruct --> DoubleEnded IteratorStruct --> StdIterator
Bidirectional Iteration
The iterator uses two cursors to enable efficient bidirectional traversal, supporting both Iterator
and DoubleEndedIterator
traits for compatibility with standard Rust iteration patterns.
Sources: src/raw_list.rs(L434 - L464) src/raw_list.rs(L425 - L431)
Memory Layout and Safety Invariants
Thread Safety Model
The RawList implements thread safety through atomic operations and careful ordering constraints.
Component | Thread Safety Mechanism | Ordering |
---|---|---|
Insertion Tracking | AtomicBoolwith compare-exchange | Acquireon insert,Relaxedon failure |
Removal State | AtomicBoolstore operation | Releaseordering |
Pointer Access | UnsafeCellwith controlled access | Synchronized through insertion state |
Safety Invariants
- Link Ownership: Once successfully inserted, the list owns the links until removal
- Reference Validity: Callers must ensure references remain valid while on the list
- Single List Membership: An element can only be on one list at a time (enforced by atomic insertion tracking)
- Removal Safety: Only elements currently on the list may be removed
Sources: src/raw_list.rs(L40 - L46) src/raw_list.rs(L331 - L337)
Memory Layout Diagram
flowchart TD subgraph subGraph3["List Head"] RawListHead["RawList.headOption>"] end subgraph subGraph2["ListEntry Pointers"] NextPointer["next: Option>(8 bytes on 64-bit)"] PrevPointer["prev: Option>(8 bytes on 64-bit)"] end subgraph subGraph1["Links Internal Layout"] AtomicBool["inserted: AtomicBool(1 byte + padding)"] UnsafeCell["entry: UnsafeCell(contains raw pointers)"] end subgraph subGraph0["Node Memory Structure"] UserData["User Data Fields(application-specific)"] LinksField["links: Links"] end LinksField --> AtomicBool LinksField --> UnsafeCell NextPointer --> UserData PrevPointer --> UserData RawListHead --> UserData UnsafeCell --> NextPointer UnsafeCell --> PrevPointer UserData --> LinksField
Sources: src/raw_list.rs(L35 - L38) src/raw_list.rs(L74 - L77) src/raw_list.rs(L93 - L95)
Core Concepts
Relevant source files
This page provides a deep dive into the fundamental concepts that enable the linked_list_r4l crate to provide intrusive linked lists with constant-time arbitrary removal and thread safety. It covers the trait system, memory layout, atomic operations, and safety architecture that form the foundation of the library.
For basic usage patterns, see Quick Start Guide. For detailed API documentation, see API Reference. For memory management specifics, see Memory Management. For thread safety implementation details, see Thread Safety.
The GetLinks Trait System
The core of the linked list architecture is built around two key traits that enable intrusive linking while maintaining type safety and flexible ownership models.
GetLinks Trait
The GetLinks
trait is the fundamental abstraction that allows any type to participate in a linked list by providing access to embedded linking metadata.
flowchart TD GetLinks["GetLinks trait"] EntryType["type EntryType: ?Sized"] GetLinksMethod["fn get_links(&EntryType) -> &Links"] UserStruct["User-defined struct"] LinksField["links: Links"] UserImpl["impl GetLinks for UserStruct"] GetLinks --> EntryType GetLinks --> GetLinksMethod GetLinksMethod --> LinksField UserImpl --> GetLinks UserStruct --> LinksField UserStruct --> UserImpl
GetLinks Trait Definition and Usage
The trait defines two essential components:
EntryType
: The type of objects that will be stored in the listget_links()
: A function that returns a reference to theLinks
struct embedded in the entry
This design enables a type to be in multiple different lists simultaneously by implementing GetLinks
multiple times with different associated types.
GetLinksWrapped Trait
The GetLinksWrapped
trait extends GetLinks
to add ownership management through the Wrapper
trait, enabling safe memory management across different allocation strategies.
flowchart TD GetLinksWrapped["GetLinksWrapped trait"] GetLinksBase["extends GetLinks"] WrappedType["type Wrapped: Wrapper"] BoxImpl["impl GetLinksWrapped for Box"] ArcImpl["impl GetLinksWrapped for Arc"] BoxWrapper["Box: Wrapper"] ArcWrapper["Arc: Wrapper"] RefWrapper["&T: Wrapper"] Wrapper["Wrapper trait"] IntoPtr["fn into_pointer(self) -> NonNull"] FromPtr["unsafe fn from_pointer(NonNull) -> Self"] AsRef["fn as_ref(&self) -> &T"] ArcImpl --> ArcWrapper ArcImpl --> GetLinksWrapped ArcWrapper --> Wrapper BoxImpl --> BoxWrapper BoxImpl --> GetLinksWrapped BoxWrapper --> Wrapper GetLinksWrapped --> GetLinksBase GetLinksWrapped --> WrappedType RefWrapper --> Wrapper Wrapper --> AsRef Wrapper --> FromPtr Wrapper --> IntoPtr
Trait Relationship and Implementation
Sources: src/raw_list.rs(L23 - L29) src/linked_list.rs(L86 - L96) src/linked_list.rs(L18 - L31)
Links and ListEntry Structure
The Links
struct contains the core linking infrastructure that enables intrusive list membership with atomic insertion tracking.
Memory Layout and Components
flowchart TD subgraph subGraph2["ListEntry Structure"] ListEntry["ListEntry"] Next["next: Option>"] Prev["prev: Option>"] end subgraph subGraph1["Links Structure"] Links["Links"] Inserted["inserted: AtomicBool"] Entry["entry: UnsafeCell>"] end subgraph subGraph0["Node Memory Layout"] Node["User Node Struct"] UserData["user data fields"] LinksField["links: Links"] end Entry --> ListEntry Links --> Entry Links --> Inserted LinksField --> Links ListEntry --> Next ListEntry --> Prev Node --> LinksField Node --> UserData
Links Structure Components
The Links<T>
struct contains two critical fields:
inserted: AtomicBool
- Atomic flag tracking whether the node is currently inserted in any listentry: UnsafeCell<ListEntry<T>>
- Contains the actual next/prev pointers wrapped inUnsafeCell
for interior mutability
The ListEntry<T>
struct contains the doubly-linked list pointers:
next: Option<NonNull<T>>
- Pointer to the next node in the listprev: Option<NonNull<T>>
- Pointer to the previous node in the list
Atomic Insertion Tracking
The atomic insertion flag provides thread-safe tracking of list membership and prevents double-insertion:
Operation | Atomic Operation | Memory Ordering |
---|---|---|
Insertion | compare_exchange(false, true, Acquire, Relaxed) | Acquire on success |
Removal | store(false, Release) | Release |
Check | load(Relaxed) | Relaxed |
Sources: src/raw_list.rs(L35 - L66) src/raw_list.rs(L74 - L86)
Constant-Time Arbitrary Removal
The intrusive design enables O(1) removal of any node from the list without requiring traversal to find the node's neighbors.
Removal Algorithm
Key Algorithmic Properties
- No Traversal Required: Because each node contains its own links, removal requires no list traversal
- Atomic Safety: The
inserted
flag prevents concurrent modifications during removal - Neighbor Updates: Only the immediate neighbors need pointer updates
- Head Management: Special handling when removing the head node
The algorithm handles three cases:
- Single node: Set
head = None
- Head node: Update
head
to point to next node - Interior node: Update neighbor pointers only
Insertion Tracking and Race Prevention
Race Condition Prevention
The atomic inserted
flag prevents several race conditions:
- Double insertion: Only one thread can successfully set
inserted
fromfalse
totrue
- Insertion during removal: Removal always succeeds once a node is inserted
- Memory ordering:
Acquire
on insertion synchronizes withRelease
on removal
Sources: src/raw_list.rs(L57 - L65) src/raw_list.rs(L199 - L235) src/raw_list.rs(L140 - L151)
Memory Safety Architecture
The library implements a layered safety model that transitions from safe ownership management to unsafe pointer operations.
Safety Boundary and Wrapper Abstraction
flowchart TD subgraph subGraph2["Unsafe Zone"] NonNullPtrs["NonNull"] RawList["RawList operations"] PtrArithmetic["Pointer manipulation"] end subgraph subGraph1["Wrapper Boundary"] WrapperTrait["Wrapper trait"] IntoPtr["into_pointer() -> NonNull"] FromPtr["from_pointer(NonNull) -> Self"] AsRef["as_ref() -> &T"] end subgraph subGraph0["Safe Zone"] UserCode["User Code"] BoxOwned["Box"] ArcShared["Arc"] RefBorrowed["&Node"] end ArcShared --> WrapperTrait BoxOwned --> WrapperTrait FromPtr --> NonNullPtrs IntoPtr --> NonNullPtrs NonNullPtrs --> RawList RawList --> PtrArithmetic RefBorrowed --> WrapperTrait UserCode --> ArcShared UserCode --> BoxOwned UserCode --> RefBorrowed WrapperTrait --> AsRef WrapperTrait --> FromPtr WrapperTrait --> IntoPtr
Ownership Model Abstraction
The Wrapper
trait abstracts over different ownership models:
Wrapper Type | Ownership Model | Use Case |
---|---|---|
Box | Unique ownership | Single-threaded exclusive access |
Arc | Shared ownership | Multi-threaded shared access |
&T | Borrowed reference | Stack-allocated or external lifetime management |
Safe-to-Unsafe Transition
The transition from safe to unsafe code follows a specific protocol:
- Entry: Safe wrapper objects are converted to raw pointers via
into_pointer()
- Operation: Unsafe
RawList
operations manipulate the raw pointers - Exit: Raw pointers are converted back to safe wrappers via
from_pointer()
This ensures that:
- Memory safety is maintained at the boundaries
- Ownership transfer is explicit and controlled
- Unsafe operations are isolated to the core implementation
Sources: src/linked_list.rs(L18 - L83) src/linked_list.rs(L86 - L89) src/linked_list.rs(L127 - L162)
Cursors and Iteration
The library provides cursor-based iteration that enables both inspection and mutation of list elements during traversal.
Cursor Implementation Architecture
flowchart TD subgraph subGraph2["List Integration"] RawList["**RawList"] ListHead["head: Option>"] CircularLinks["Circular next/prev pointers"] end subgraph subGraph1["Common Operations"] CommonCursor["**CommonCursor"] MoveNext["move_next()"] MovePrev["move_prev()"] Current["cur: Option>"] end subgraph subGraph0["Cursor Types"] Cursor["Cursor<'a, G>"] CursorMut["CursorMut<'a, G>"] Iterator["Iterator<'a, G>"] end CommonCursor --> Current CommonCursor --> MoveNext CommonCursor --> MovePrev Cursor --> CommonCursor CursorMut --> CommonCursor Iterator --> Cursor MoveNext --> RawList MovePrev --> RawList RawList --> CircularLinks RawList --> ListHead
Cursor Navigation Logic
Cursors navigate the circular list structure by:
- Forward movement: Follow
next
pointers until reaching the head again - Backward movement: Follow
prev
pointers until reaching the head again - Boundary detection: Stop iteration when returning to the starting position
The circular structure simplifies navigation logic and eliminates special cases for list boundaries.
Mutable Cursor Operations
CursorMut
provides additional operations for list modification during iteration:
Operation | Description | Safety Requirements |
---|---|---|
current() | Get mutable reference to current element | Element must remain valid |
remove_current() | Remove current element and advance cursor | Returns ownership of removed element |
peek_next() | Get mutable reference to next element | No cursor advancement |
peek_prev() | Get mutable reference to previous element | No cursor advancement |
Sources: src/raw_list.rs(L286 - L329) src/raw_list.rs(L339 - L423) src/raw_list.rs(L433 - L464)
Memory Management
Relevant source files
This document explains how the linked_list_r4l
crate manages memory safety, ownership models, and the boundary between safe and unsafe code. It covers the ownership abstraction through the Wrapper
trait, atomic memory operations, and the embedded links structure that enables constant-time removal. For information about thread safety and atomic operations, see Thread Safety.
Ownership Models and the Wrapper Trait
The library supports multiple ownership models through the Wrapper<T>
trait, which abstracts over different ways of managing object lifetime. This allows the same linked list implementation to work with heap-allocated objects, reference-counted objects, and borrowed references.
Wrapper Trait Implementation
classDiagram class Wrapper~T~ { <<trait>> +into_pointer() NonNull~T~ +from_pointer(NonNull~T~) Self +as_ref() &T } class BoxUnsupported markdown: del { +into_pointer() NonNull~T~ +from_pointer(NonNull~T~) Box~T~ +as_ref() &T } class ArcUnsupported markdown: del { +into_pointer() NonNull~T~ +from_pointer(NonNull~T~) Arc~T~ +as_ref() &T } class &T { +into_pointer() NonNull~T~ +from_pointer(NonNull~T~) &T +as_ref() &T } Wrapper ..|> Wrapper Wrapper ..|> Wrapper Wrapper ..|> Wrapper
The Wrapper<T>
trait provides three key operations:
into_pointer()
converts owned objects to raw pointers for list storagefrom_pointer()
reconstructs ownership when removing from the listas_ref()
provides safe access without transferring ownership
Sources: src/linked_list.rs(L18 - L31) src/linked_list.rs(L33 - L83)
Ownership Transfer Flow
sequenceDiagram participant UserCode as "User Code" participant ListG as "List~G~" participant WrapperT as "Wrapper~T~" participant RawListG as "RawList~G~" Note over UserCode,RawListG: Insertion Flow - Ownership Transfer UserCode ->> ListG: "push_back(owned_object)" ListG ->> WrapperT: "into_pointer()" WrapperT -->> ListG: "NonNull~T~" ListG ->> RawListG: "push_back(&T)" Note over RawListG: "Stores raw pointer,<br>ownership transferred" Note over UserCode,RawListG: Removal Flow - Ownership Recovery UserCode ->> ListG: "pop_front()" ListG ->> RawListG: "pop_front()" RawListG -->> ListG: "Some(NonNull~T~)" ListG ->> WrapperT: "from_pointer(ptr)" WrapperT -->> ListG: "Owned Object" ListG -->> UserCode: "Some(owned_object)"
This flow ensures that ownership is properly transferred to the list during insertion and recovered during removal, preventing memory leaks and use-after-free errors.
Sources: src/linked_list.rs(L153 - L162) src/linked_list.rs(L213 - L217)
Memory Layout and Links Structure
Each node in the linked list contains an embedded Links<T>
structure that manages the actual list pointers and insertion state. This design enables constant-time arbitrary removal without requiring traversal.
Node Memory Layout
flowchart TD subgraph NodeMemory["Node Memory Layout"] subgraph LinksStruct["LinksUnsupported markdown: del Structure"] NotInserted["false: Not on any list"] Inserted["true: Currently on a list"] NextPtr["next: OptionUnsupported markdown: delT~~"] InnerField["inner: T"] InsertedFlag["inserted: AtomicBool"] EntryCell["entry: UnsafeCellUnsupported markdown: delT~~"] end end subgraph ListEntryMemory["ListEntryUnsupported markdown: del Memory"] NotInserted["false: Not on any list"] NextPtr["next: OptionUnsupported markdown: delT~~"] PrevPtr["prev: OptionUnsupported markdown: delT~~"] InnerField["inner: T"] subgraph AtomicStates["Atomic States"] Inserted["true: Currently on a list"] subgraph UserData["User Data"] NotInserted["false: Not on any list"] NextPtr["next: OptionUnsupported markdown: delT~~"] InnerField["inner: T"] end end end
The Links<T>
structure contains:
inserted
: AnAtomicBool
tracking whether the node is currently on a listentry
: AnUnsafeCell<ListEntry<T>>
containing the actual forward/backward pointers
Sources: src/raw_list.rs(L35 - L38) src/raw_list.rs(L74 - L86)
GetLinks Trait Integration
The GetLinks
trait provides access to the embedded Links<T>
structure within user-defined types:
classDiagram class GetLinks~T~ { <<trait>> +type EntryType +get_links(data: &EntryType) &Links~EntryType~ } class UserNode { +inner: SomeType +links: Links~Self~ } class Links~T~ { +inserted: AtomicBool +entry: UnsafeCell~ListEntry~T~~ } class ListEntry~T~ { +next: Option~NonNull~T~~ +prev: Option~NonNull~T~~ } Links ..|> UserNode : implements UserNode *-- Links : contains Links *-- ListEntry : contains
Sources: src/raw_list.rs(L23 - L29) src/raw_list.rs(L35 - L55)
Safety Boundaries and Abstraction Layers
The library maintains memory safety through a carefully designed abstraction hierarchy that isolates unsafe operations to the lowest layer.
Safety Layer Architecture
flowchart TD subgraph UnsafeZone["Unsafe Zone - Raw Pointer Operations"] RawListOps["RawListUnsupported markdown: delRaw pointer operations"] AtomicOps["Atomic Links Management"] PtrArithmetic["Pointer arithmetic & linking"] end subgraph SafeZone["Safe Zone - Memory Ownership"] UserAPI["User CodeListUnsupported markdown: del API"] OwnershipMgmt["Ownership ManagementListUnsupported markdown: del"] WrapperBoundary["Wrapper Trait Boundary"] end OwnershipMgmt --> WrapperBoundary RawListOps --> AtomicOps RawListOps --> PtrArithmetic UserAPI --> OwnershipMgmt WrapperBoundary --> RawListOps
Memory Safety Guarantees
The safety boundaries provide these guarantees:
Layer | Safety Mechanism | Guarantees |
---|---|---|
User API | Rust ownership system | No use-after-free, no double-free |
Wrapper Boundary | Controlled pointer conversion | Ownership tracking across boundaries |
Raw Operations | Unsafe blocks with invariants | Pointer validity maintained |
Atomic Operations | Memory ordering constraints | Thread-safe state transitions |
Sources: src/linked_list.rs(L127 - L235) src/raw_list.rs(L97 - L284)
Atomic Memory Management
The Links<T>
structure uses atomic operations to prevent double-insertion and ensure thread-safe state transitions.
Atomic Insertion Protocol
The atomic protocol ensures:
- Only one thread can successfully insert a node at a time
- Nodes cannot be inserted twice without being removed first
- Memory ordering prevents reordering of pointer updates
Sources: src/raw_list.rs(L57 - L65)
Atomic Operations Implementation
The key atomic methods in Links<T>
:
#![allow(unused)] fn main() { // From src/raw_list.rs:57-65 fn acquire_for_insertion(&self) -> bool { self.inserted .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) .is_ok() } fn release_after_removal(&self) { self.inserted.store(false, Ordering::Release); } }
acquire_for_insertion()
uses compare-and-swap withAcquire
orderingrelease_after_removal()
usesRelease
ordering for proper synchronization- Failed insertion attempts indicate the node is already on a list
Sources: src/raw_list.rs(L57 - L65)
Raw Pointer Operations and Memory Invariants
The RawList<G>
layer performs all raw pointer manipulation while maintaining critical memory safety invariants.
RawList Memory Invariants
Critical Memory Operations
The most complex memory operations involve updating multiple pointers atomically:
Operation | Memory Updates | Safety Requirements |
---|---|---|
push_back() | Update head, link new node | Node not already inserted |
insert_after() | Update 3 nodes' pointers | Existing node on this list |
remove() | Update 2 neighbors, reset node | Node on this list |
pop_front() | Update head, unlink node | List not empty |
Each operation maintains the circular doubly-linked structure while ensuring that partially-updated states are never visible to other threads.
Sources: src/raw_list.rs(L113 - L284)
Thread Safety
Relevant source files
This document covers the thread safety mechanisms, atomic operations, and concurrent access patterns implemented by the linked_list_r4l library. The focus is on how the library uses atomic operations to ensure safe concurrent insertion and removal operations while maintaining performance.
For information about memory management and ownership models, see Memory Management. For details about the overall architecture and abstraction layers, see Architecture Overview.
Atomic Insertion Tracking
The core thread safety mechanism in linked_list_r4l is the atomic insertion tracking system implemented in the Links<T>
struct. This system prevents data races when multiple threads attempt to insert or remove the same node concurrently.
Insertion State Management
The Links<T>
struct contains an AtomicBool inserted
field that tracks whether a node is currently inserted in any list:
Atomic State Transitions in Links
The insertion tracking prevents double-insertion and ensures that only one thread can successfully insert a node at a time. The acquire_for_insertion()
method uses compare-and-swap semantics to atomically transition from false
to true
, while release_after_removal()
unconditionally sets the state back to false
.
Sources: src/raw_list.rs(L35 - L66)
Compare-and-Swap Operations
The atomic operations use specific memory ordering to ensure correctness:
Operation | Memory Ordering | Purpose |
---|---|---|
compare_exchange(false, true, Acquire, Relaxed) | Acquire | Synchronizes with release operations from other threads |
store(false, Release) | Release | Ensures all preceding writes are visible to other threads |
The Acquire
ordering on successful insertion ensures that any subsequent operations on the node happen-after the insertion completes. The Release
ordering on removal ensures that all modifications to the node's links are visible before the node becomes available for re-insertion.
Sources: src/raw_list.rs(L57 - L65)
Thread Safety Boundaries
The library implements thread safety through a layered approach with explicit unsafe boundaries:
flowchart TD subgraph subGraph2["Boundary Layer"] Links["Links<T>"] SendSync["Send + Sync Implementations"] end subgraph subGraph1["Unsafe Zone"] UnsafeCell["UnsafeCell<ListEntry>"] RawPointers["NonNull<T> Raw Pointers"] UnsafeMethods["Unsafe Method Implementations"] end subgraph subGraph0["Safe Zone"] SafeAPI["Safe API Methods"] AtomicOps["Atomic Operations"] end AtomicOps --> Links Links --> SendSync Links --> UnsafeCell RawPointers --> UnsafeMethods SafeAPI --> Links SendSync --> UnsafeCell UnsafeCell --> RawPointers
Thread Safety Architecture
The Links<T>
struct serves as the boundary between safe atomic operations and unsafe raw pointer manipulation. The UnsafeCell<ListEntry<T>>
provides interior mutability for the actual linked list pointers while the atomic boolean provides synchronization.
Sources: src/raw_list.rs(L35 - L46) src/raw_list.rs(L331 - L337)
Send and Sync Implementations
The library provides conditional Send
and Sync
implementations that propagate thread safety requirements:
Links Thread Safety
// SAFETY: Links can be safely sent to other threads but we restrict it to being Send
// only when the list entries it points to are also Send.
unsafe impl<T: ?Sized> Send for Links<T> {}
// SAFETY: Links is usable from other threads via references but we restrict it to being Sync
// only when the list entries it points to are also Sync.
unsafe impl<T: ?Sized> Sync for Links<T> {}
RawList Thread Safety
// SAFETY: The list is itself can be safely sent to other threads but we restrict it to being Send
// only when its entries are also Send.
unsafe impl<G: GetLinks> Send for RawList<G> where G::EntryType: Send {}
// SAFETY: The list is itself usable from other threads via references but we restrict it to being Sync
// only when its entries are also Sync.
unsafe impl<G: GetLinks> Sync for RawList<G> where G::EntryType: Sync {}
These implementations ensure that thread safety is only available when the contained data types are themselves thread-safe, following Rust's standard library patterns.
Sources: src/raw_list.rs(L40 - L46) src/raw_list.rs(L331 - L337)
Concurrent Access Patterns
Safe Concurrent Operations
Concurrent Insertion Protocol
The atomic insertion tracking ensures that even if multiple threads attempt to insert the same node simultaneously, only one will succeed. The losing threads receive a false
return value and can handle the case appropriately.
Sources: src/raw_list.rs(L140 - L151) src/raw_list.rs(L153 - L179)
Unsafe Raw Pointer Operations
While the insertion tracking is thread-safe, the actual pointer manipulation within RawList
operations is not thread-safe. The library relies on the caller to ensure exclusive access to the list structure during modifications:
Method | Thread Safety | Requirements |
---|---|---|
push_back() | Not thread-safe | Requires exclusive access to&mut RawList |
remove() | Not thread-safe | Requires exclusive access to&mut RawList |
iter() | Thread-safe | Can be called with shared reference&RawList |
The mutable reference requirement (&mut RawList<G>
) for modification operations provides compile-time guarantees that no other thread can access the list structure during modifications.
Sources: src/raw_list.rs(L186 - L197) src/raw_list.rs(L243 - L245)
Memory Ordering Guarantees
The library uses acquire-release semantics to establish happens-before relationships:
flowchart TD subgraph subGraph1["Thread B"] B1["release_after_removal()"] B2["Release ordering"] B3["observe node data changes"] end subgraph subGraph0["Thread A"] A1["modify node data"] A2["acquire_for_insertion()"] A3["Acquire ordering"] end A1 --> A2 A2 --> A3 A3 --> B2 B1 --> B3 B2 --> B1
Memory Ordering Relationships
The acquire ordering on insertion ensures that any writes to the node data that occurred before the insertion become visible to threads that subsequently observe the insertion. The release ordering on removal ensures that all modifications to the node are visible before the node becomes available for re-use.
Sources: src/raw_list.rs(L57 - L65)
Safety Invariants
Insertion Tracking Invariants
- Single Insertion: A node with
inserted == true
is guaranteed to be in exactly one list - Atomic Transitions: State changes between inserted/not-inserted are atomic and visible to all threads
- Exclusive Modification: Only the thread that successfully acquires for insertion can modify the node's list links
Raw Pointer Safety
- Validity Duration: Raw pointers in
ListEntry<T>
are valid only while the corresponding nodes remain in the list - Exclusive List Access: Modification operations require exclusive access to prevent data races on the list structure
- Node Lifetime: Nodes must outlive their presence in the list to prevent use-after-free
Sources: src/raw_list.rs(L88 - L95) src/raw_list.rs(L199 - L235)
The thread safety model of linked_list_r4l provides atomic insertion tracking while requiring higher-level synchronization for list structure modifications. This design enables efficient constant-time removal while maintaining memory safety in concurrent environments.
Development Guide
Relevant source files
Purpose and Scope
This guide provides essential information for developers contributing to the linked_list_r4l
crate. It covers build setup, testing procedures, CI/CD pipeline configuration, and project structure. For information about the library's APIs and usage patterns, see API Reference. For architectural concepts and design principles, see Architecture Overview and Core Concepts.
Prerequisites and Environment Setup
The linked_list_r4l
crate requires specific toolchain components and supports multiple target architectures for embedded and systems programming use cases.
Required Toolchain
Component | Version | Purpose |
---|---|---|
Rust Toolchain | nightly | Required for advanced features |
rust-src | Latest | Source code for cross-compilation |
clippy | Latest | Linting and code analysis |
rustfmt | Latest | Code formatting |
Supported Target Architectures
The project supports multiple target architectures as defined in the CI pipeline:
flowchart TD subgraph subGraph1["Development Activities"] UnitTests["Unit Testingcargo test"] CrossBuild["Cross Compilationcargo build"] Clippy["Static Analysiscargo clippy"] Format["Code Formattingcargo fmt"] end subgraph subGraph0["Supported Targets"] HostTarget["x86_64-unknown-linux-gnu\Host Development\"] BareMetal["x86_64-unknown-none\Bare Metal x86_64\"] RiscV["riscv64gc-unknown-none-elf\RISC-V Embedded\"] ARM["aarch64-unknown-none-softfloat\ARM64 Embedded\"] end ARM --> CrossBuild BareMetal --> CrossBuild Clippy --> Format CrossBuild --> Clippy HostTarget --> CrossBuild HostTarget --> UnitTests RiscV --> CrossBuild UnitTests --> Clippy
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L15 - L19)
Project Structure
The linked_list_r4l
crate follows a standard Rust library structure with specialized CI/CD configuration for embedded systems development.
Repository Layout
flowchart TD subgraph subGraph2["Build Outputs"] DocDir["target/doc/\Generated Documentation\"] BuildDir["target/[target]/\Compiled Artifacts\"] end subgraph subGraph1["GitHub Integration"] WorkflowsDir[".github/workflows/\CI/CD Configuration\"] CIYml["ci.yml\Build Pipeline\"] GHPages["gh-pages\Documentation Deployment\"] end subgraph subGraph0["Root Directory"] CargoToml["Cargo.toml\Package Configuration\"] GitIgnore[".gitignore\Version Control\"] SrcDir["src/\Source Code\"] TargetDir["target/\Build Artifacts\"] end CIYml --> DocDir CIYml --> TargetDir CargoToml --> SrcDir DocDir --> GHPages TargetDir --> BuildDir WorkflowsDir --> CIYml
Sources: Cargo.toml(L1 - L15) .gitignore(L1 - L5) .github/workflows/ci.yml(L1)
Build System Configuration
The crate is configured as a no-std
library with no external dependencies, making it suitable for embedded environments.
Package Metadata
The project configuration emphasizes systems programming and embedded compatibility:
Property | Value | Significance |
---|---|---|
name | linked_list_r4l | Crate identifier |
version | 0.2.1 | Current release version |
edition | 2021 | Rust edition with latest features |
categories | ["no-std", "rust-patterns"] | Embedded and pattern library |
license | GPL-2.0-or-later | Open source license |
Dependency Management
flowchart TD subgraph subGraph2["Internal Modules"] LibRS["lib.rs\Public API\"] LinkedListRS["linked_list.rs\High-Level Interface\"] RawListRS["raw_list.rs\Low-Level Operations\"] end subgraph subGraph1["Standard Library"] NoStd["#![no_std]\Core Library Only\"] CoreCrate["core::\Essential Types\"] end subgraph subGraph0["External Dependencies"] NoDeps["[dependencies]\No External Crates\"] end CoreCrate --> LibRS LibRS --> LinkedListRS LibRS --> RawListRS NoDeps --> NoStd NoStd --> CoreCrate
Sources: Cargo.toml(L14 - L15) Cargo.toml(L12)
Testing Framework
The testing infrastructure supports both unit testing and cross-compilation verification across multiple target architectures.
Test Execution Strategy
sequenceDiagram participant Developer as "Developer" participant CIPipeline as "CI Pipeline" participant BuildMatrix as "Build Matrix" participant x86_64unknownlinuxgnu as "x86_64-unknown-linux-gnu" participant EmbeddedTargets as "Embedded Targets" Developer ->> CIPipeline: "Push/PR Trigger" CIPipeline ->> BuildMatrix: "Initialize Build Matrix" Note over BuildMatrix,EmbeddedTargets: Parallel Execution BuildMatrix ->> x86_64unknownlinuxgnu: "cargo test --target x86_64-unknown-linux-gnu" BuildMatrix ->> EmbeddedTargets: "cargo build --target [embedded-target]" x86_64unknownlinuxgnu ->> x86_64unknownlinuxgnu: "Run Unit Tests with --nocapture" EmbeddedTargets ->> EmbeddedTargets: "Verify Compilation Only" x86_64unknownlinuxgnu -->> CIPipeline: "Test Results" EmbeddedTargets -->> CIPipeline: "Build Verification" CIPipeline -->> Developer: "Pipeline Status"
Test Configuration
Unit tests are executed only on the host target (x86_64-unknown-linux-gnu
) while other targets verify compilation compatibility:
# Host target with unit tests
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
# Cross-compilation verification
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features
Sources: .github/workflows/ci.yml(L28 - L30) .github/workflows/ci.yml(L26 - L27)
CI/CD Pipeline Architecture
The continuous integration system implements a comprehensive quality assurance workflow with parallel job execution and automated documentation deployment.
Pipeline Workflow
flowchart TD subgraph subGraph3["Documentation Pipeline"] DocBuild["cargo doc --no-deps --all-features"] DocDeploy["JamesIves/github-pages-deploy-action@v4"] GHPages["GitHub Pages Deployment"] end subgraph subGraph2["Quality Gates"] Checkout["actions/checkout@v4"] Setup["dtolnay/rust-toolchain@nightly"] Format["cargo fmt --all -- --check"] Clippy["cargo clippy --target TARGET --all-features"] Build["cargo build --target TARGET --all-features"] Test["cargo test --target TARGET -- --nocapture"] end subgraph subGraph1["CI Job Matrix"] Toolchain["Rust Toolchain: nightly"] Target1["x86_64-unknown-linux-gnu"] Target2["x86_64-unknown-none"] Target3["riscv64gc-unknown-none-elf"] Target4["aarch64-unknown-none-softfloat"] end subgraph subGraph0["Trigger Events"] PushEvent["git push"] PREvent["Pull Request"] end Build --> Test Checkout --> Setup Clippy --> Build DocBuild --> DocDeploy DocDeploy --> GHPages Format --> Clippy PREvent --> Toolchain PushEvent --> Toolchain Setup --> Format Target1 --> Checkout Target1 --> DocBuild Target2 --> Checkout Target3 --> Checkout Target4 --> Checkout Toolchain --> Target1 Toolchain --> Target2 Toolchain --> Target3 Toolchain --> Target4
Pipeline Configuration Details
Stage | Command | Target Filter | Purpose |
---|---|---|---|
Format Check | cargo fmt --all -- --check | All targets | Code style consistency |
Linting | cargo clippy --target TARGET --all-features | All targets | Static analysis |
Build | cargo build --target TARGET --all-features | All targets | Compilation verification |
Testing | cargo test --target TARGET -- --nocapture | Host only | Unit test execution |
Documentation | cargo doc --no-deps --all-features | Default | API documentation |
Sources: .github/workflows/ci.yml(L5 - L31) .github/workflows/ci.yml(L32 - L55)
Code Quality Standards
The project enforces strict code quality standards through automated tooling and custom configuration.
Linting Configuration
The clippy configuration includes specific allowances for library design patterns:
cargo clippy --target TARGET --all-features -- -A clippy::new_without_default
This configuration allows constructors that don't implement Default
, which is appropriate for specialized data structures like linked lists.
Documentation Standards
Documentation generation includes strict validation:
RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs"
This configuration:
- Treats broken documentation links as compilation errors (
-D rustdoc::broken_intra_doc_links
) - Requires documentation for all public APIs (
-D missing-docs
)
Documentation Deployment
flowchart TD subgraph subGraph1["Deployment Process"] BranchCheck["Check: refs/heads/default_branch"] SingleCommit["single-commit: true"] GHPagesBranch["branch: gh-pages"] TargetDoc["folder: target/doc"] end subgraph subGraph0["Documentation Build"] CargoDoc["cargo doc --no-deps --all-features"] IndexGen["Generate index.html redirect"] TreeCmd["cargo tree | head -1 | cut -d' ' -f1"] end BranchCheck --> SingleCommit CargoDoc --> IndexGen GHPagesBranch --> TargetDoc IndexGen --> BranchCheck SingleCommit --> GHPagesBranch TreeCmd --> IndexGen
Sources: .github/workflows/ci.yml(L25) .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L44 - L55)
Contributing Workflow
Development Environment Setup
- Install Rust Nightly Toolchain
rustup toolchain install nightly
rustup component add rust-src clippy rustfmt
- Add Target Architectures
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
- Verify Installation
rustc --version --verbose
Pre-commit Validation
Before submitting changes, run the complete validation suite locally:
# Format check
cargo fmt --all -- --check
# Linting
cargo clippy --all-features -- -A clippy::new_without_default
# Cross-compilation verification
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features
# Unit tests
cargo test -- --nocapture
# Documentation build
cargo doc --no-deps --all-features
This local validation mirrors the CI pipeline and helps catch issues before pushing to the repository.
Sources: .github/workflows/ci.yml(L20 - L30) .github/workflows/ci.yml(L44 - L47)
Overview
Relevant source files
The arm_pl011
crate provides type-safe register definitions and basic operations for the ARM PL011 UART controller. This library serves as a hardware abstraction layer for embedded systems and operating system kernels that need to interface with PL011 UART hardware found in ARM-based systems.
The crate is designed for no_std
environments and integrates with the ArceOS operating system ecosystem, though it can be used independently in any Rust embedded project requiring PL011 UART support. For detailed hardware specifications and register layouts, see Hardware Reference. For specific implementation details, see Core Implementation.
System Context
The following diagram illustrates how arm_pl011
fits within the broader embedded systems software stack:
System Integration Context
flowchart TD subgraph subGraph2["Hardware Layer"] MMIO["Memory-mapped I/O"] PL011_HW["PL011 UART Controller"] SerialPort["Physical Serial Port"] end subgraph subGraph1["Hardware Abstraction"] arm_pl011["arm_pl011 crate"] Pl011Uart["Pl011Uart struct"] tock_registers["tock-registers"] end subgraph subGraph0["Application Layer"] UserApp["User Applications"] Kernel["ArceOS Kernel"] Bootloader["Bootloader"] end Bootloader --> arm_pl011 Kernel --> arm_pl011 MMIO --> PL011_HW PL011_HW --> SerialPort Pl011Uart --> tock_registers UserApp --> arm_pl011 arm_pl011 --> Pl011Uart tock_registers --> MMIO
Sources: Cargo.toml(L8 - L12) README.md(L7 - L9)
Crate Structure and Code Entities
The crate follows a minimal, focused design with clear separation between public API and internal implementation:
Code Organization and Key Entities
flowchart TD subgraph subGraph3["Build Targets"] linux_gnu["x86_64-unknown-linux-gnu"] x86_bare["x86_64-unknown-none"] riscv64["riscv64gc-unknown-none-elf"] aarch64["aarch64-unknown-none-softfloat"] end subgraph Dependencies["Dependencies"] tock_registers_dep["tock-registers = 0.8"] no_std["#![no_std]"] const_features["const_option, const_nonnull_new"] end subgraph subGraph1["Core Implementation"] pl011_mod["src/pl011.rs"] Pl011Uart_struct["Pl011Uart struct"] Pl011UartRegs["Pl011UartRegs struct"] methods["UART methods"] end subgraph subGraph0["Public API"] lib_rs["src/lib.rs"] Pl011Uart_export["pub use Pl011Uart"] end Pl011Uart_struct --> Pl011UartRegs Pl011Uart_struct --> methods lib_rs --> Pl011Uart_export lib_rs --> aarch64 lib_rs --> const_features lib_rs --> linux_gnu lib_rs --> no_std lib_rs --> pl011_mod lib_rs --> riscv64 lib_rs --> x86_bare pl011_mod --> Pl011Uart_struct pl011_mod --> tock_registers_dep
Sources: src/lib.rs(L1 - L8) Cargo.toml(L14 - L15)
Key Features
The arm_pl011
crate provides the following core capabilities:
Feature | Description | Code Entity |
---|---|---|
Type-safe register access | Memory-mapped register operations with compile-time safety | tock-registersintegration |
UART initialization | Hardware setup and configuration | Pl011Uart::init() |
Character I/O | Blocking send/receive operations | putchar(),getchar() |
Interrupt management | Interrupt status checking and acknowledgment | is_receive_interrupt(),ack_interrupts() |
Multi-target support | Cross-compilation for embedded and hosted environments | no_stdcompatibility |
Const operations | Compile-time initialization support | const_option,const_nonnull_new |
Sources: Cargo.toml(L1 - L12) src/lib.rs(L2 - L3)
Target Use Cases
The crate is designed for several specific embedded development scenarios:
- Operating System Kernels: Provides console and debug output for kernel development, particularly within the ArceOS ecosystem
- Bootloaders: Early-stage system initialization requiring minimal dependencies and
no_std
compatibility - Embedded Applications: Direct hardware control in bare-metal ARM systems with PL011 UART controllers
- Cross-platform Development: Testing and development on hosted systems before deployment to embedded targets
The multi-target build support enables development workflows that span from Linux-based development environments to bare-metal embedded deployment, making it suitable for both prototyping and production use.
Sources: Cargo.toml(L8 - L12) README.md(L1 - L9)
Architecture
Relevant source files
This page describes the architectural design of the arm_pl011
crate, covering how it abstracts the PL011 UART hardware controller into a safe, type-checked Rust interface. The architecture demonstrates a layered approach that bridges low-level hardware registers to high-level embedded system interfaces.
For specific implementation details of UART operations, see 2.2. For hardware register specifications, see 5.
Architectural Overview
The arm_pl011
crate implements a three-layer architecture that provides safe access to PL011 UART hardware through memory-mapped I/O registers.
System Architecture
flowchart TD subgraph subGraph3["Hardware Layer"] MMIO["Memory-Mapped I/O"] PL011Hardware["PL011 UART Controller"] end subgraph subGraph2["Register Abstraction"] TockRegs["tock-registers"] RegisterStructs["register_structs! macro"] TypeSafety["ReadWrite, ReadOnly"] end subgraph subGraph1["arm_pl011 Crate"] LibEntry["lib.rs"] Pl011Uart["Pl011Uart struct"] UartMethods["UART Methodsinit(), putchar(), getchar()"] Pl011UartRegs["Pl011UartRegs struct"] end subgraph subGraph0["Application Layer"] App["Application Code"] OS["Operating System / ArceOS"] end App --> LibEntry LibEntry --> Pl011Uart MMIO --> PL011Hardware OS --> LibEntry Pl011Uart --> Pl011UartRegs Pl011Uart --> UartMethods Pl011UartRegs --> RegisterStructs RegisterStructs --> TockRegs TockRegs --> TypeSafety TypeSafety --> MMIO
Sources: src/lib.rs(L1 - L9) src/pl011.rs(L9 - L32) src/pl011.rs(L42 - L44) Cargo.toml(L14 - L15)
Core Components
The architecture centers around two primary structures that encapsulate hardware access and provide safe interfaces.
Component Relationships
flowchart TD subgraph subGraph2["Register Definition Layer"] Pl011UartRegsStruct["Pl011UartRegs struct"] DrReg["dr: ReadWrite"] FrReg["fr: ReadOnly"] CrReg["cr: ReadWrite"] ImscReg["imsc: ReadWrite"] IcrReg["icr: WriteOnly"] RegsMethod["regs() -> &Pl011UartRegs"] end subgraph subGraph1["Implementation Layer"] Pl011Mod["pl011.rs module"] Pl011UartStruct["Pl011Uart struct"] BasePointer["base: NonNull"] UartNew["new(base: *mut u8)"] UartInit["init()"] UartPutchar["putchar(c: u8)"] UartGetchar["getchar() -> Option"] UartIsReceiveInterrupt["is_receive_interrupt() -> bool"] UartAckInterrupts["ack_interrupts()"] end subgraph subGraph0["Public API Layer"] LibRs["lib.rs"] PublicApi["pub use pl011::Pl011Uart"] end BasePointer --> Pl011UartRegsStruct LibRs --> PublicApi Pl011UartRegsStruct --> CrReg Pl011UartRegsStruct --> DrReg Pl011UartRegsStruct --> FrReg Pl011UartRegsStruct --> IcrReg Pl011UartRegsStruct --> ImscReg Pl011UartStruct --> BasePointer Pl011UartStruct --> UartAckInterrupts Pl011UartStruct --> UartGetchar Pl011UartStruct --> UartInit Pl011UartStruct --> UartIsReceiveInterrupt Pl011UartStruct --> UartNew Pl011UartStruct --> UartPutchar PublicApi --> Pl011UartStruct RegsMethod --> Pl011UartRegsStruct UartAckInterrupts --> RegsMethod UartGetchar --> RegsMethod UartInit --> RegsMethod UartIsReceiveInterrupt --> RegsMethod UartPutchar --> RegsMethod
Sources: src/lib.rs(L6 - L8) src/pl011.rs(L42 - L44) src/pl011.rs(L49 - L103) src/pl011.rs(L9 - L32)
Register Abstraction Model
The crate uses the tock-registers
library to provide type-safe, zero-cost abstractions over hardware registers. This approach ensures compile-time verification of register access patterns.
Register Memory Layout
flowchart TD subgraph subGraph2["Type Safety Layer"] ReadWriteType["ReadWrite"] ReadOnlyType["ReadOnly"] WriteOnlyType["WriteOnly"] end subgraph subGraph1["Memory Space"] BaseAddr["Base Address(base: NonNull)"] subgraph subGraph0["Register Offsets"] DR["0x00: drReadWrite"] Reserved0["0x04-0x14: _reserved0"] FR["0x18: frReadOnly"] Reserved1["0x1c-0x2c: _reserved1"] CR["0x30: crReadWrite"] IFLS["0x34: iflsReadWrite"] IMSC["0x38: imscReadWrite"] RIS["0x3c: risReadOnly"] MIS["0x40: misReadOnly"] ICR["0x44: icrWriteOnly"] End["0x48: @END"] end end BaseAddr --> CR BaseAddr --> DR BaseAddr --> FR BaseAddr --> ICR BaseAddr --> IMSC CR --> ReadWriteType DR --> ReadWriteType FR --> ReadOnlyType ICR --> WriteOnlyType IMSC --> ReadWriteType
Sources: src/pl011.rs(L9 - L32) src/pl011.rs(L51 - L54) src/pl011.rs(L57 - L59)
Memory Safety Architecture
The architecture implements several safety mechanisms to ensure correct hardware access in embedded environments.
Safety Mechanism | Implementation | Purpose |
---|---|---|
Type Safety | tock-registerstypes | Prevents invalid register access patterns |
Pointer Safety | NonNull | Guarantees non-null base address |
Const Construction | const fn new() | Enables compile-time initialization |
Thread Safety | Send + Synctraits | Allows multi-threaded access |
Memory Layout | register_structs!macro | Enforces correct register offsets |
Sources: src/pl011.rs(L46 - L47) src/pl011.rs(L51 - L55) src/pl011.rs(L9 - L32)
Data Flow Architecture
flowchart TD subgraph subGraph3["Interrupt Flow"] InterruptCheck["is_receive_interrupt()"] MisRead["Read mis register"] AckCall["ack_interrupts()"] IcrWrite["Write to icr register"] end subgraph subGraph2["Read Operation Flow"] GetcharCall["uart.getchar()"] RxFifoCheck["Check fr register bit 4"] DrRead["Read from dr register"] ReturnOption["Return Option"] end subgraph subGraph1["Write Operation Flow"] PutcharCall["uart.putchar(byte)"] TxFifoCheck["Check fr register bit 5"] DrWrite["Write to dr register"] end subgraph subGraph0["Initialization Flow"] UserCode["User Code"] NewCall["Pl011Uart::new(base_ptr)"] InitCall["uart.init()"] RegisterSetup["Register Configuration"] end AckCall --> IcrWrite DrRead --> ReturnOption GetcharCall --> RxFifoCheck InitCall --> RegisterSetup InterruptCheck --> MisRead NewCall --> InitCall PutcharCall --> TxFifoCheck RxFifoCheck --> DrRead TxFifoCheck --> DrWrite UserCode --> AckCall UserCode --> GetcharCall UserCode --> InterruptCheck UserCode --> NewCall UserCode --> PutcharCall
Sources: src/pl011.rs(L64 - L76) src/pl011.rs(L79 - L82) src/pl011.rs(L85 - L91) src/pl011.rs(L94 - L102)
Integration Points
The architecture provides clean integration points for embedded systems and operating system kernels:
- Const Construction: The
new()
function isconst
, enabling static initialization in bootloaders and kernels - No-std Compatibility: All code operates without standard library dependencies
- Zero-cost Abstractions: Register access compiles to direct memory operations
- Multi-target Support: Architecture works across x86, ARM64, and RISC-V platforms
The design allows the crate to function as a foundational component in embedded systems, providing reliable UART functionality while maintaining the performance characteristics required for system-level programming.
Sources: src/lib.rs(L1 - L3) src/pl011.rs(L51) Cargo.toml(L12 - L13)
Getting Started
Relevant source files
This page provides a practical guide for integrating and using the arm_pl011
crate in your embedded systems project. It covers adding the crate as a dependency, basic initialization, and fundamental UART operations.
For detailed hardware register specifications, see Hardware Reference. For comprehensive API documentation, see API Reference.
Prerequisites
The arm_pl011
crate is designed for embedded systems development with the following requirements:
Requirement | Details |
---|---|
Rust Edition | 2021 or later |
Target Environment | no_stdcompatible |
Hardware | ARM-based system with PL011 UART controller |
Memory Management | Access to memory-mapped I/O addresses |
Dependencies | tock-registersfor type-safe register access |
Target Architecture Support:
aarch64-unknown-none-softfloat
(primary target)x86_64-unknown-linux-gnu
(development/testing)riscv64gc-unknown-none-elf
(cross-platform compatibility)
Sources: Cargo.toml(L1 - L16)
Adding the Crate to Your Project
Add arm_pl011
to your Cargo.toml
dependencies:
[dependencies]
arm_pl011 = "0.1.0"
The crate automatically includes the tock-registers
dependency for register manipulation safety.
Sources: Cargo.toml(L14 - L15)
Basic Usage Flow
Initialization and Operation Sequence
flowchart TD Start["Start"] GetBase["Obtain UART Base Address"] NewUart["Pl011Uart::new(base_ptr)"] InitUart["uart.init()"] Ready["UART Ready for Operations"] TxPath["Transmit Path"] RxPath["Receive Path"] IntPath["Interrupt Path"] Putchar["uart.putchar(byte)"] TxDone["Character Transmitted"] Getchar["uart.getchar()"] CheckRx["Data Available?"] RxSuccess["Character Received"] RxEmpty["No Data"] CheckInt["uart.is_receive_interrupt()"] AckInt["uart.ack_interrupts()"] IntHandled["Interrupt Processed"] AckInt --> IntHandled CheckInt --> AckInt CheckRx --> RxEmpty CheckRx --> RxSuccess GetBase --> NewUart Getchar --> CheckRx InitUart --> Ready IntPath --> CheckInt NewUart --> InitUart Putchar --> TxDone Ready --> IntPath Ready --> RxPath Ready --> TxPath RxPath --> Getchar Start --> GetBase TxPath --> Putchar
Sources: src/pl011.rs(L51 - L103)
Code Entity Mapping
flowchart TD Hardware["PL011 Hardware Controller"] BaseAddr["Memory-mapped Base Address"] NewMethod["Pl011Uart::new(base: *mut u8)"] UartStruct["Pl011Uart struct"] RegStruct["Pl011UartRegs"] InitMethod["init()"] PutcharMethod["putchar(c: u8)"] GetcharMethod["getchar() -> Option"] IntCheckMethod["is_receive_interrupt()"] AckMethod["ack_interrupts()"] DataReg["dr: ReadWrite"] FlagReg["fr: ReadOnly"] ControlReg["cr: ReadWrite"] IntMaskReg["imsc: ReadWrite"] IntClearReg["icr: WriteOnly"] BaseAddr --> NewMethod Hardware --> BaseAddr NewMethod --> UartStruct RegStruct --> ControlReg RegStruct --> DataReg RegStruct --> FlagReg RegStruct --> IntClearReg RegStruct --> IntMaskReg UartStruct --> AckMethod UartStruct --> GetcharMethod UartStruct --> InitMethod UartStruct --> IntCheckMethod UartStruct --> PutcharMethod UartStruct --> RegStruct
Sources: src/pl011.rs(L42 - L44) src/pl011.rs(L11 - L32) src/pl011.rs(L49 - L103)
Simple Usage Examples
Basic UART Setup
use arm_pl011::Pl011Uart;
// Obtain the base address for your PL011 UART
// This is platform-specific and typically provided by your BSP
let uart_base = 0x0900_0000 as *mut u8;
// Create UART instance
let mut uart = Pl011Uart::new(uart_base);
// Initialize the UART with default settings
uart.init();
Character Transmission
The putchar
method blocks until the transmit FIFO has space:
// Send a single character
uart.putchar(b'H');
// Send a string
for byte in b"Hello, World!\n" {
uart.putchar(*byte);
}
Character Reception
The getchar
method returns immediately with an Option
:
// Check for received data
match uart.getchar() {
Some(byte) => {
// Process received byte
uart.putchar(byte); // Echo back
}
None => {
// No data available
}
}
Interrupt Handling
// Check if receive interrupt occurred
if uart.is_receive_interrupt() {
// Handle the interrupt
if let Some(byte) = uart.getchar() {
// Process received data
}
// Clear interrupts
uart.ack_interrupts();
}
Sources: src/pl011.rs(L51 - L55) src/pl011.rs(L64 - L76) src/pl011.rs(L79 - L82) src/pl011.rs(L85 - L91) src/pl011.rs(L94 - L102)
Memory Safety Considerations
The Pl011Uart
struct implements Send
and Sync
traits, enabling safe usage across thread boundaries:
Safety Feature | Implementation |
---|---|
Memory Safety | UsesNonNull |
Thread Safety | ManualSend + Syncimplementation for multi-threaded environments |
Register Safety | tock-registersprovides compile-time type safety for register access |
Sources: src/pl011.rs(L46 - L47) src/pl011.rs(L43)
Configuration Details
The init()
method configures the UART with these default settings:
Setting | Value | Register | Purpose |
---|---|---|---|
FIFO Trigger | 1/8 level | ifls | Interrupt timing |
RX Interrupt | Enabled | imsc | Receive notifications |
UART Enable | Yes | cr | Overall operation |
TX Enable | Yes | cr | Transmission capability |
RX Enable | Yes | cr | Reception capability |
Sources: src/pl011.rs(L64 - L76)
Next Steps
After completing basic setup:
- Advanced Configuration: See UART Operations for FIFO management and custom interrupt handling
- Register Details: See Register Definitions for low-level register manipulation
- Thread Safety: See Thread Safety and Memory Safety for multi-threaded usage patterns
- Hardware Integration: See Hardware Reference for platform-specific considerations
For comprehensive method documentation, proceed to Pl011Uart Methods.
Sources: src/pl011.rs(L1 - L104) Cargo.toml(L1 - L16)
Core Implementation
Relevant source files
This document covers the core implementation details of the Pl011Uart
driver, including the main driver struct, register abstraction layer, memory safety mechanisms, and fundamental design patterns. This page focuses on the internal architecture and implementation strategies rather than usage examples.
For detailed register specifications and hardware mappings, see Register Definitions. For comprehensive method documentation and API usage, see API Reference.
Pl011Uart Structure and Architecture
The core driver implementation centers around the Pl011Uart
struct, which serves as the primary interface for all UART operations. The driver follows a layered architecture that separates hardware register access from high-level UART functionality.
Core Driver Architecture
flowchart TD App["Application Code"] Pl011Uart["Pl011Uart struct"] base["base: NonNull"] regs_method["regs() method"] Pl011UartRegs["Pl011UartRegs struct"] dr["dr: ReadWrite"] fr["fr: ReadOnly"] cr["cr: ReadWrite"] imsc["imsc: ReadWrite"] icr["icr: WriteOnly"] others["ifls, ris, mis registers"] Hardware["Memory-mapped Hardware Registers"] App --> Pl011Uart Pl011Uart --> base Pl011Uart --> regs_method Pl011UartRegs --> cr Pl011UartRegs --> dr Pl011UartRegs --> fr Pl011UartRegs --> icr Pl011UartRegs --> imsc Pl011UartRegs --> others cr --> Hardware dr --> Hardware fr --> Hardware icr --> Hardware imsc --> Hardware others --> Hardware regs_method --> Pl011UartRegs
Sources: src/pl011.rs(L34 - L44) src/pl011.rs(L9 - L32)
The Pl011Uart
struct maintains a single field base
of type NonNull<Pl011UartRegs>
that points to the memory-mapped register structure. This design provides type-safe access to hardware registers while maintaining zero-cost abstractions.
Register Access Pattern
flowchart TD Method["UART Method"] regs_call["self.regs()"] unsafe_deref["unsafe { self.base.as_ref() }"] RegisterStruct["&Pl011UartRegs"] register_access["register.get() / register.set()"] MMIO["Memory-mapped I/O"] Method --> regs_call RegisterStruct --> register_access register_access --> MMIO regs_call --> unsafe_deref unsafe_deref --> RegisterStruct
Sources: src/pl011.rs(L57 - L59)
The regs()
method provides controlled access to the register structure through a const function that dereferences the NonNull
pointer. This pattern encapsulates the unsafe memory access within a single, well-defined boundary.
Memory Safety and Thread Safety
The driver implements explicit safety markers to enable usage in multi-threaded embedded environments while maintaining Rust's safety guarantees.
Safety Trait | Implementation | Purpose |
---|---|---|
Send | unsafe impl Send for Pl011Uart {} | Allows transfer between threads |
Sync | unsafe impl Sync for Pl011Uart {} | Allows shared references across threads |
Sources: src/pl011.rs(L46 - L47)
These unsafe implementations are justified because:
- The PL011 UART controller is designed for single-threaded access per instance
- Memory-mapped I/O operations are atomic at the hardware level
- The
NonNull
pointer provides guaranteed non-null access - Register operations through
tock_registers
are inherently safe
Constructor and Initialization Pattern
The driver follows a two-phase initialization pattern that separates object construction from hardware configuration.
Construction Phase
flowchart TD base_ptr["*mut u8"] new_method["Pl011Uart::new()"] nonnull_new["NonNull::new(base).unwrap()"] cast_operation[".cast()"] Pl011Uart_instance["Pl011Uart { base }"] base_ptr --> new_method cast_operation --> Pl011Uart_instance new_method --> nonnull_new nonnull_new --> cast_operation
Sources: src/pl011.rs(L51 - L55)
The new()
constructor is marked as const fn
, enabling compile-time initialization in embedded contexts. The constructor performs type casting from a raw byte pointer to the structured register layout.
Hardware Initialization Sequence
The init()
method configures the hardware through a specific sequence of register operations:
Step | Register | Operation | Purpose |
---|---|---|---|
1 | icr | set(0x7ff) | Clear all pending interrupts |
2 | ifls | set(0) | Set FIFO trigger levels (1/8 depth) |
3 | imsc | set(1 << 4) | Enable receive interrupts |
4 | cr | set((1 << 0) | (1 << 8) | (1 << 9)) | Enable TX, RX, and UART |
Sources: src/pl011.rs(L64 - L76)
Data Flow and Operation Methods
The driver implements fundamental UART operations through direct register manipulation with appropriate status checking.
Character Transmission Flow
flowchart TD putchar_call["putchar(c: u8)"] tx_ready_check["while fr.get() & (1 << 5) != 0"] dr_write["dr.set(c as u32)"] hardware_tx["Hardware transmission"] dr_write --> hardware_tx putchar_call --> tx_ready_check tx_ready_check --> dr_write
Sources: src/pl011.rs(L79 - L82)
The transmission method implements busy-waiting on the Transmit FIFO Full flag (bit 5) in the Flag Register before writing data.
Character Reception Flow
flowchart TD getchar_call["getchar()"] rx_empty_check["fr.get() & (1 << 4) == 0"] read_data["Some(dr.get() as u8)"] no_data["None"] return_char["Return Option"] getchar_call --> rx_empty_check no_data --> return_char read_data --> return_char rx_empty_check --> no_data rx_empty_check --> read_data
Sources: src/pl011.rs(L85 - L91)
The reception method checks the Receive FIFO Empty flag (bit 4) and returns Option<u8>
to handle the absence of available data without blocking.
Interrupt Handling Architecture
The driver provides interrupt status checking and acknowledgment methods that integrate with higher-level interrupt management systems.
Interrupt Status Detection
flowchart TD is_receive_interrupt["is_receive_interrupt()"] mis_read["mis.get()"] mask_check["pending & (1 << 4) != 0"] bool_result["bool"] is_receive_interrupt --> mis_read mask_check --> bool_result mis_read --> mask_check
Sources: src/pl011.rs(L94 - L97)
The interrupt detection reads the Masked Interrupt Status register and specifically checks for receive interrupts (bit 4).
Interrupt Acknowledgment
The ack_interrupts()
method clears all interrupt conditions by writing to the Interrupt Clear Register with a comprehensive mask value 0x7ff
, ensuring no stale interrupt states remain.
Sources: src/pl011.rs(L100 - L102)
This implementation pattern provides a clean separation between interrupt detection, handling, and acknowledgment, enabling integration with various interrupt management strategies in embedded operating systems.
Register Definitions
Relevant source files
This document details the PL011 UART register definitions implemented in the arm_pl011
crate, including the memory-mapped register structure, individual register specifications, and the type-safe abstractions provided by the tock-registers
library. This covers the hardware abstraction layer that maps PL011 UART controller registers to Rust data structures.
For information about how these registers are used in UART operations, see UART Operations. For the complete API methods that interact with these registers, see Pl011Uart Methods.
Register Structure Overview
The PL011 UART registers are defined using the register_structs!
macro from the tock-registers
crate, which provides compile-time memory layout verification and type-safe register access. The Pl011UartRegs
structure maps the complete PL011 register set to their hardware-defined memory offsets.
Register Memory Layout
flowchart TD subgraph subGraph0["Pl011UartRegs Structure"] DR["dr: ReadWrite0x00 - Data Register"] RES0["_reserved00x04-0x17"] FR["fr: ReadOnly0x18 - Flag Register"] RES1["_reserved10x1C-0x2F"] CR["cr: ReadWrite0x30 - Control Register"] IFLS["ifls: ReadWrite0x34 - Interrupt FIFO Level"] IMSC["imsc: ReadWrite0x38 - Interrupt Mask Set Clear"] RIS["ris: ReadOnly0x3C - Raw Interrupt Status"] MIS["mis: ReadOnly0x40 - Masked Interrupt Status"] ICR["icr: WriteOnly0x44 - Interrupt Clear"] END["@END0x48"] end CR --> IFLS DR --> RES0 FR --> RES1 ICR --> END IFLS --> IMSC IMSC --> RIS MIS --> ICR RES0 --> FR RES1 --> CR RIS --> MIS
Sources: src/pl011.rs(L9 - L32)
Individual Register Definitions
The register structure defines eight functional registers with specific access patterns and purposes:
Register | Offset | Type | Description |
---|---|---|---|
dr | 0x00 | ReadWrite | Data Register - transmit/receive data |
fr | 0x18 | ReadOnly | Flag Register - UART status flags |
cr | 0x30 | ReadWrite | Control Register - UART configuration |
ifls | 0x34 | ReadWrite | Interrupt FIFO Level Select Register |
imsc | 0x38 | ReadWrite | Interrupt Mask Set Clear Register |
ris | 0x3C | ReadOnly | Raw Interrupt Status Register |
mis | 0x40 | ReadOnly | Masked Interrupt Status Register |
icr | 0x44 | WriteOnly | Interrupt Clear Register |
Data Register (dr)
The Data Register at offset 0x00
provides bidirectional data access for character transmission and reception. It supports both read and write operations for handling UART data flow.
Flag Register (fr)
The Flag Register at offset 0x18
provides read-only status information about the UART state, including transmit/receive FIFO status and busy indicators.
Control Register (cr)
The Control Register at offset 0x30
configures UART operational parameters including enable/disable states for transmission, reception, and the overall UART controller.
Sources: src/pl011.rs(L9 - L32)
Type Safety and Memory Mapping
The register definitions leverage the tock-registers
library to provide compile-time guarantees about register access patterns and memory safety:
Register Access Type Safety
flowchart TD subgraph subGraph2["Compile-time Safety"] TYPE_CHECK["Type Checking"] ACCESS_CONTROL["Access Control"] MEMORY_LAYOUT["Memory Layout Verification"] end subgraph subGraph1["Tock-Registers Traits"] READABLE["Readable Interface"] WRITEABLE["Writeable Interface"] end subgraph subGraph0["Access Patterns"] RO["ReadOnlyfr, ris, mis"] RW["ReadWritedr, cr, ifls, imsc"] WO["WriteOnlyicr"] end ACCESS_CONTROL --> MEMORY_LAYOUT READABLE --> TYPE_CHECK RO --> READABLE RW --> READABLE RW --> WRITEABLE TYPE_CHECK --> MEMORY_LAYOUT WO --> WRITEABLE WRITEABLE --> ACCESS_CONTROL
The register_structs!
macro ensures that:
- Register offsets match PL011 hardware specifications
- Reserved memory regions are properly handled
- Access patterns prevent invalid operations (e.g., reading write-only registers)
- Memory layout is validated at compile time
Sources: src/pl011.rs(L3 - L7) src/pl011.rs(L9 - L32)
Register Access Implementation
The Pl011Uart
structure contains a NonNull<Pl011UartRegs>
pointer that provides safe access to the memory-mapped registers. The regs()
method returns a reference to the register structure for performing hardware operations.
Register Access Flow
flowchart TD subgraph subGraph2["Hardware Interface"] MMIO["Memory-Mapped I/O"] PL011_HW["PL011 Hardware"] end subgraph subGraph1["Register Operations"] READ["Register Read Operations"] WRITE["Register Write Operations"] SET["Register Set Operations"] GET["Register Get Operations"] end subgraph subGraph0["Pl011Uart Structure"] BASE["base: NonNull"] REGS_METHOD["regs() -> &Pl011UartRegs"] end BASE --> REGS_METHOD GET --> MMIO MMIO --> PL011_HW READ --> SET REGS_METHOD --> READ REGS_METHOD --> WRITE SET --> MMIO WRITE --> GET
The implementation ensures memory safety through:
- Const construction via
NonNull::new().unwrap().cast()
- Unsafe register access contained within safe method boundaries
- Type-safe register operations through tock-registers interfaces
Sources: src/pl011.rs(L42 - L59)
UART Operations
Relevant source files
This document covers the core UART operations provided by the Pl011Uart
implementation, including initialization, character transmission and reception, status monitoring, and interrupt handling. These operations form the fundamental interface for communicating with PL011 UART hardware controllers.
For details about the underlying register structure and memory layout, see Register Definitions. For complete API documentation and method signatures, see Pl011Uart Methods.
Operation Categories
The Pl011Uart
struct provides five main categories of operations implemented through distinct methods that directly manipulate hardware registers:
Operation Category | Methods | Purpose |
---|---|---|
Initialization | init() | Configure UART for operation |
Character Output | putchar() | Transmit single characters |
Character Input | getchar() | Receive single characters |
Interrupt Detection | is_receive_interrupt() | Check for receive interrupts |
Interrupt Handling | ack_interrupts() | Clear pending interrupts |
Initialization Process
init() Method Operation Flow
flowchart TD subgraph subGraph0["Register Operations"] ICR["icr: Interrupt Clear Register"] IFLS["ifls: FIFO Level Select"] IMSC["imsc: Interrupt Mask"] CR["cr: Control Register"] end Start["init() called"] ClearIRQ["Clear all interruptsicr.set(0x7ff)"] SetFIFO["Set FIFO trigger levelsifls.set(0)"] EnableRxInt["Enable RX interruptimsc.set(1 << 4)"] EnableUART["Enable UART componentscr.set(tx|rx|uart enable)"] Complete["UART ready for operation"] ClearIRQ --> SetFIFO EnableRxInt --> EnableUART EnableUART --> Complete SetFIFO --> EnableRxInt Start --> ClearIRQ
The init()
method src/pl011.rs(L64 - L76) performs a four-step hardware configuration sequence:
- Interrupt Clearing: Sets
icr
register to0x7ff
to clear all pending interrupts - FIFO Configuration: Sets
ifls
register to0
for 1/8 RX FIFO and 1/8 TX FIFO trigger levels - Interrupt Enabling: Sets
imsc
register bit 4 to enable receive interrupts (rxim
) - UART Activation: Sets
cr
register bits 0, 8, and 9 to enable transmission, reception, and overall UART operation
Sources: src/pl011.rs(L61 - L76)
Character Transmission
putchar() Operation Flow
flowchart TD subgraph subGraph0["Register Access"] FR["fr: Flag Register (Read-Only)"] DR["dr: Data Register (Read-Write)"] end PutChar["putchar(c: u8) called"] CheckTXFull["Poll fr registerCheck bit 5 (TXFF)"] IsTXFull["TX FIFO Full?"] Wait["Wait - continue polling"] WriteDR["Write character to dr registerdr.set(c as u32)"] Complete["Character transmitted"] CheckTXFull --> IsTXFull IsTXFull --> Wait IsTXFull --> WriteDR PutChar --> CheckTXFull Wait --> CheckTXFull WriteDR --> Complete
The putchar()
method src/pl011.rs(L79 - L82) implements blocking character transmission:
- Status Polling: Continuously reads the
fr
(Flag Register) and checks bit 5 (TXFF
- Transmit FIFO Full) - Busy Waiting: If bit 5 is set, the transmit FIFO is full and the method continues polling
- Character Writing: When bit 5 is clear, writes the character to the
dr
(Data Register) as a 32-bit value
Sources: src/pl011.rs(L78 - L82)
Character Reception
getchar() Operation Flow
flowchart TD subgraph subGraph0["Register Access"] FR["fr: Flag Register (Read-Only)"] DR["dr: Data Register (Read-Write)"] end GetChar["getchar() called"] CheckRXEmpty["Read fr registerCheck bit 4 (RXFE)"] IsRXEmpty["RX FIFO Empty?"] ReturnNone["Return None"] ReadDR["Read character from dr registerdr.get() as u8"] ReturnSome["Return Some(character)"] CheckRXEmpty --> IsRXEmpty GetChar --> CheckRXEmpty IsRXEmpty --> ReadDR IsRXEmpty --> ReturnNone ReadDR --> ReturnSome
The getchar()
method src/pl011.rs(L85 - L91) provides non-blocking character reception:
- Status Check: Reads the
fr
(Flag Register) and examines bit 4 (RXFE
- Receive FIFO Empty) - Conditional Read: If bit 4 is clear (FIFO not empty), reads from the
dr
(Data Register) - Return Logic: Returns
Some(u8)
if data is available,None
if FIFO is empty
Sources: src/pl011.rs(L84 - L91)
Interrupt Handling
Interrupt Detection and Acknowledgment
flowchart TD subgraph subGraph2["Register Types"] MIS["mis: Masked Interrupt Status (Read-Only)"] ICR["icr: Interrupt Clear Register (Write-Only)"] AckInt["ack_interrupts() called"] CheckInt["is_receive_interrupt() called"] end subgraph subGraph0["Interrupt Detection"] ReturnBool["Return boolean result"] subgraph subGraph1["Interrupt Acknowledgment"] MIS["mis: Masked Interrupt Status (Read-Only)"] AckInt["ack_interrupts() called"] ClearICR["Write 0x7ff to icr register(clears all interrupt types)"] IntCleared["All interrupts cleared"] CheckInt["is_receive_interrupt() called"] ReadMIS["Read mis register(Masked Interrupt Status)"] CheckBit4["Check bit 4 (receive interrupt)"] end end AckInt --> ClearICR CheckBit4 --> ReturnBool CheckInt --> ReadMIS ClearICR --> IntCleared ReadMIS --> CheckBit4
The interrupt handling operations src/pl011.rs(L94 - L102) provide status checking and clearing functionality:
Interrupt Detection (is_receive_interrupt()
):
- Reads the
mis
(Masked Interrupt Status) register - Checks bit 4 to determine if a receive interrupt is pending
- Returns boolean indicating interrupt status
Interrupt Acknowledgment (ack_interrupts()
):
- Writes
0x7ff
to theicr
(Interrupt Clear Register) - Clears all interrupt types simultaneously
- Used after handling interrupts to reset hardware state
Sources: src/pl011.rs(L93 - L102)
FIFO Management
The PL011 UART controller includes internal FIFOs for both transmission and reception. The implementation configures and interacts with these FIFOs through specific register operations:
FIFO Aspect | Register | Configuration | Purpose |
---|---|---|---|
Trigger Levels | ifls | Set to0 | 1/8 depth triggers for both TX/RX |
TX Status | frbit 5 | Read-only | Indicates TX FIFO full condition |
RX Status | frbit 4 | Read-only | Indicates RX FIFO empty condition |
Data Transfer | dr | Read/Write | Single entry point for FIFO access |
The FIFO configuration ensures efficient interrupt-driven operation while preventing data loss during high-throughput scenarios.
Sources: src/pl011.rs(L68 - L69) src/pl011.rs(L80) src/pl011.rs(L86)
Register Access Patterns
All UART operations follow consistent patterns for hardware register access through the regs()
method src/pl011.rs(L57 - L59) :
flowchart TD subgraph subGraph0["Access Types"] ReadOnly["ReadOnly (fr, ris, mis)"] WriteOnly["WriteOnly (icr)"] ReadWrite["ReadWrite (dr, cr, ifls, imsc)"] end Method["UART Method"] RegsCall["self.regs()"] UnsafeDeref["unsafe { self.base.as_ref() }"] RegisterStruct["&Pl011UartRegs"] SpecificReg["Specific Register Access"] Method --> RegsCall RegisterStruct --> SpecificReg RegsCall --> UnsafeDeref UnsafeDeref --> RegisterStruct
The regs()
method provides type-safe access to memory-mapped registers through the tock-registers
crate, ensuring that register access constraints are enforced at compile time.
Sources: src/pl011.rs(L57 - L59) src/pl011.rs(L9 - L32)
API Reference
Relevant source files
This document provides comprehensive API documentation for the arm_pl011
crate, covering all public interfaces, method signatures, and usage patterns. The API is designed for direct hardware control of PL011 UART controllers in embedded systems and bare-metal environments.
For implementation details and internal architecture, see Core Implementation. For hardware-specific register specifications, see Hardware Reference.
API Overview
The arm_pl011
crate exposes a minimal but complete interface centered around the Pl011Uart
struct. The API follows a pattern of explicit initialization followed by character-level I/O operations with optional interrupt handling.
Public Interface Structure
flowchart TD subgraph subGraph5["Safety Traits"] SEND["unsafe impl Send"] SYNC["unsafe impl Sync"] end subgraph subGraph4["Interrupt Methods"] IS_IRQ["is_receive_interrupt(&self) -> bool"] ACK_IRQ["ack_interrupts(&mut self)"] end subgraph subGraph3["I/O Methods"] PUTCHAR["putchar(&mut self, c: u8)"] GETCHAR["getchar(&mut self) -> Option"] end subgraph subGraph2["Initialization Methods"] INIT["init(&mut self)"] end subgraph subGraph1["Constructor Methods"] NEW["new(base: *mut u8)"] end subgraph subGraph0["Public API Surface"] CRATE["arm_pl011 crate"] UART["Pl011Uart struct"] REGS["Pl011UartRegs (internal)"] end CRATE --> UART UART --> ACK_IRQ UART --> GETCHAR UART --> INIT UART --> IS_IRQ UART --> NEW UART --> PUTCHAR UART --> REGS UART --> SEND UART --> SYNC
Sources: src/lib.rs(L8) src/pl011.rs(L34 - L103)
Method Call Flow
sequenceDiagram participant Application as "Application" participant Pl011Uart as "Pl011Uart" participant PL011Hardware as "PL011 Hardware" Application ->> Pl011Uart: new(base_addr) Note over Pl011Uart: Construct with hardware base address Application ->> Pl011Uart: init() Pl011Uart ->> PL011Hardware: Configure registers (icr, ifls, imsc, cr) Note over PL011Hardware: Clear interrupts, set FIFO levels, enable RX/TX loop "Character Output" Application ->> Pl011Uart: putchar(c) Pl011Uart ->> PL011Hardware: Check fr register (TX FIFO full?) Pl011Uart ->> PL011Hardware: Write to dr register end loop "Character Input" Application ->> Pl011Uart: getchar() Pl011Uart ->> PL011Hardware: Check fr register (RX FIFO empty?) Pl011Uart -->> Application: Some(char) or None end opt "Interrupt Handling" PL011Hardware ->> Pl011Uart: Hardware interrupt Application ->> Pl011Uart: is_receive_interrupt() Pl011Uart ->> PL011Hardware: Read mis register Pl011Uart -->> Application: true/false Application ->> Pl011Uart: ack_interrupts() Pl011Uart ->> PL011Hardware: Write to icr register end
Sources: src/pl011.rs(L51 - L103)
Core Types
Pl011Uart
The main driver struct that provides safe access to PL011 UART hardware registers.
Field | Type | Description |
---|---|---|
base | NonNull | Typed pointer to memory-mapped register base address |
Safety Characteristics:
- Implements
Send
andSync
for multi-threaded environments - Uses
NonNull
for memory safety guarantees - Requires
unsafe
implementations due to hardware register access
Sources: src/pl011.rs(L42 - L47)
Pl011UartRegs
Internal register structure defining the memory layout of PL011 UART registers.
Offset | Register | Type | Description |
---|---|---|---|
0x00 | dr | ReadWrite | Data Register |
0x18 | fr | ReadOnly | Flag Register |
0x30 | cr | ReadWrite | Control Register |
0x34 | ifls | ReadWrite | Interrupt FIFO Level Select |
0x38 | imsc | ReadWrite | Interrupt Mask Set Clear |
0x3c | ris | ReadOnly | Raw Interrupt Status |
0x40 | mis | ReadOnly | Masked Interrupt Status |
0x44 | icr | WriteOnly | Interrupt Clear |
Sources: src/pl011.rs(L9 - L32)
Method Documentation
Constructor Methods
new(base: *mut u8) -> Self
Creates a new Pl011Uart
instance from a hardware base address.
Parameters:
base
: Raw pointer to the memory-mapped UART register base address
Returns:
Pl011Uart
instance ready for initialization
Safety:
- Marked as
const fn
for compile-time construction - Uses
NonNull::new().unwrap()
- will panic if base address is null - Caller must ensure base address points to valid PL011 hardware
Example Usage:
// Base address for QEMU virt machine PL011
const UART_BASE: *mut u8 = 0x09000000 as *mut u8;
let mut uart = Pl011Uart::new(UART_BASE);
Sources: src/pl011.rs(L50 - L55)
Initialization Methods
init(&mut self)
Initializes the PL011 UART with default configuration for basic operation.
Configuration Applied:
- Clears all pending interrupts (
icr = 0x7ff
) - Sets FIFO trigger levels to 1/8 for both RX and TX (
ifls = 0
) - Enables receive interrupts (
imsc = 1 << 4
) - Enables UART, transmitter, and receiver (
cr = (1 << 0) | (1 << 8) | (1 << 9)
)
Prerequisites:
- Hardware clock and reset must be properly configured
- Base address must point to accessible PL011 registers
Sources: src/pl011.rs(L61 - L76)
I/O Methods
putchar(&mut self, c: u8)
Transmits a single byte through the UART.
Parameters:
c
: Byte value to transmit
Behavior:
- Blocks until TX FIFO has space (polls
fr
register bit 5) - Writes character to data register (
dr
) - No return value - always succeeds once FIFO space available
Register Operations:
// Wait for TX FIFO not full
while self.regs().fr.get() & (1 << 5) != 0 {}
// Write character
self.regs().dr.set(c as u32);
Sources: src/pl011.rs(L78 - L82)
getchar(&mut self) -> Option
Attempts to read a single byte from the UART receive buffer.
Returns:
Some(byte)
: If data is available in RX FIFONone
: If RX FIFO is empty
Behavior:
- Non-blocking operation
- Checks RX FIFO empty flag (
fr
register bit 4) - Reads from data register (
dr
) if data available
Register Operations:
if self.regs().fr.get() & (1 << 4) == 0 {
Some(self.regs().dr.get() as u8)
} else {
None
}
Sources: src/pl011.rs(L84 - L91)
Interrupt Methods
is_receive_interrupt(&self) -> bool
Checks if a receive interrupt is currently pending.
Returns:
true
: Receive interrupt is pendingfalse
: No receive interrupt pending
Register Operations:
- Reads masked interrupt status (
mis
register) - Checks receive interrupt bit (bit 4)
Sources: src/pl011.rs(L93 - L97)
ack_interrupts(&mut self)
Clears all pending UART interrupts.
Behavior:
- Writes
0x7ff
to interrupt clear register (icr
) - Clears all interrupt types simultaneously
- Should be called from interrupt handler after processing
Sources: src/pl011.rs(L99 - L102)
Thread Safety Implementation
The Pl011Uart
struct implements both Send
and Sync
traits to enable safe usage across thread boundaries in multi-threaded embedded systems.
flowchart TD subgraph subGraph1["Safety Rationale"] HW_ATOMIC["Hardware register access is inherently atomic"] NO_STATE["No shared mutable state between operations"] MMIO_SAFE["Memory-mapped I/O operations are thread-safe"] end subgraph subGraph0["Thread Safety Guarantees"] UART["Pl011Uart"] SEND["unsafe impl Send"] SYNC["unsafe impl Sync"] end SEND --> HW_ATOMIC SYNC --> MMIO_SAFE SYNC --> NO_STATE UART --> SEND UART --> SYNC
Implementation Details:
Send
: Safe to transfer ownership between threadsSync
: Safe to share references between threads- Hardware registers provide atomic access guarantees
- No internal synchronization primitives required
Usage Considerations:
- Multiple threads can safely call read-only methods (
is_receive_interrupt
) - Mutable methods require external synchronization for concurrent access
- Interrupt handlers can safely access the same instance
Sources: src/pl011.rs(L46 - L47)
Pl011Uart Methods
Relevant source files
This page provides detailed documentation for all public methods of the Pl011Uart
struct, which implements the primary interface for controlling PL011 UART hardware. These methods handle UART initialization, character transmission and reception, and interrupt management.
For information about the underlying register definitions and hardware abstraction, see Register Definitions. For thread safety and memory safety considerations when using these methods, see Thread Safety and Memory Safety.
Constructor Methods
new()
The new()
method constructs a new Pl011Uart
instance from a hardware base address.
Method | Signature | Purpose |
---|---|---|
new | pub const fn new(base: *mut u8) -> Self | Create UART instance from base address |
// Usage example - base address typically from device tree or platform code
let uart = Pl011Uart::new(0x0900_0000 as *mut u8);
This constructor uses const fn
to enable compile-time initialization and leverages NonNull::new().unwrap().cast()
to safely convert the raw pointer to a typed register pointer. The method is marked as const
to support static initialization patterns common in embedded systems.
Sources: src/pl011.rs(L50 - L55)
Initialization Methods
init()
The init()
method performs complete UART hardware initialization, configuring interrupts, FIFO levels, and enabling transmission/reception.
Method | Signature | Purpose |
---|---|---|
init | pub fn init(&mut self) | Initialize UART hardware for operation |
Initialization Sequence:
flowchart TD START["init() called"] CLEAR["Clear all interrupts"] FIFO["Set FIFO trigger levels"] RX_INT["Enable RX interrupts"] ENABLE["Enable TX/RX/UART"] READY["UART ready for operation"] CLEAR --> FIFO ENABLE --> READY FIFO --> RX_INT RX_INT --> ENABLE START --> CLEAR
The initialization process involves:
- Interrupt Clearing: Sets ICR register to
0x7ff
to clear all pending interrupts - FIFO Configuration: Sets IFLS register to
0
for 1/8 RX and TX FIFO trigger levels - Interrupt Enablement: Sets IMSC register bit 4 to enable RX interrupts
- UART Enablement: Sets CR register bits 0, 8, and 9 to enable TX, RX, and overall UART operation
Sources: src/pl011.rs(L61 - L76)
Character I/O Methods
putchar()
The putchar()
method transmits a single character through the UART, implementing blocking transmission with FIFO status checking.
Method | Signature | Purpose |
---|---|---|
putchar | pub fn putchar(&mut self, c: u8) | Transmit single character (blocking) |
Transmission Process:
flowchart TD CALL["putchar(c)"] CHECK["Check FR[5] TXFF flag"] WAIT["TX FIFO full?"] WRITE["Write char to DR register"] DONE["Character transmitted"] CALL --> CHECK CHECK --> WAIT WAIT --> CHECK WAIT --> WRITE WRITE --> DONE
The method blocks until the transmit FIFO has space (FR register bit 5 clear), then writes the character to the data register for transmission.
Sources: src/pl011.rs(L78 - L82)
getchar()
The getchar()
method attempts to receive a character from the UART, returning Some(u8)
if data is available or None
if the receive FIFO is empty.
Method | Signature | Purpose |
---|---|---|
getchar | pub fn getchar(&mut self) -> Option | Receive single character (non-blocking) |
Reception Logic:
flowchart TD CALL["getchar()"] CHECK["Check FR[4] RXFE flag"] EMPTY["RX FIFO empty?"] NONE["Return None"] READ["Read from DR register"] SOME["Return Some(char)"] CALL --> CHECK CHECK --> EMPTY EMPTY --> NONE EMPTY --> READ READ --> SOME
The method checks the FR register bit 4 (RXFE - receive FIFO empty). If clear, data is available and the method reads from the data register; otherwise it returns None
.
Sources: src/pl011.rs(L84 - L91)
Interrupt Handling Methods
is_receive_interrupt()
The is_receive_interrupt()
method checks if a receive interrupt is currently pending, enabling interrupt-driven I/O patterns.
Method | Signature | Purpose |
---|---|---|
is_receive_interrupt | pub fn is_receive_interrupt(&self) -> bool | Check for pending RX interrupt |
This method reads the MIS (Masked Interrupt Status) register and tests bit 4 to determine if a receive interrupt is active. It only detects interrupts that are both asserted and enabled through the interrupt mask.
Sources: src/pl011.rs(L93 - L97)
ack_interrupts()
The ack_interrupts()
method clears all pending UART interrupts by writing to the interrupt clear register.
Method | Signature | Purpose |
---|---|---|
ack_interrupts | pub fn ack_interrupts(&mut self) | Clear all pending interrupts |
This method writes 0x7ff
to the ICR register, clearing all possible interrupt conditions. This is typically called in interrupt service routines to acknowledge interrupt processing.
Sources: src/pl011.rs(L99 - L102)
Method Access Patterns
The following diagram shows how each method interacts with the PL011 hardware registers:
flowchart TD subgraph subGraph1["PL011 Registers"] DR["DR - Data Register"] FR["FR - Flag Register"] CR["CR - Control Register"] IFLS["IFLS - FIFO Level Select"] IMSC["IMSC - Interrupt Mask"] MIS["MIS - Masked Interrupt Status"] ICR["ICR - Interrupt Clear"] end subgraph subGraph0["Pl011Uart Methods"] NEW["new()"] INIT["init()"] PUTCHAR["putchar()"] GETCHAR["getchar()"] IS_INT["is_receive_interrupt()"] ACK_INT["ack_interrupts()"] end BASE["Base Address Setup"] ACK_INT --> ICR GETCHAR --> DR GETCHAR --> FR INIT --> CR INIT --> ICR INIT --> IFLS INIT --> IMSC IS_INT --> MIS NEW --> BASE PUTCHAR --> DR PUTCHAR --> FR
Typical Usage Flow
Most applications follow this pattern when using Pl011Uart
methods:
flowchart TD CREATE["Create with new()"] INITIALIZE["Call init()"] READY["UART Ready"] TX_PATH["Transmission Path"] RX_PATH["Reception Path"] INT_PATH["Interrupt Path"] PUTCHAR_CALL["putchar(c)"] GETCHAR_CALL["getchar()"] RX_CHECK["Data available?"] PROCESS["Process character"] INT_CHECK["is_receive_interrupt()"] INT_HANDLE["Interrupt pending?"] HANDLE_INT["Handle interrupt"] ACK_CALL["ack_interrupts()"] ACK_CALL --> INT_PATH CREATE --> INITIALIZE GETCHAR_CALL --> RX_CHECK HANDLE_INT --> ACK_CALL INITIALIZE --> READY INT_CHECK --> INT_HANDLE INT_HANDLE --> HANDLE_INT INT_HANDLE --> INT_PATH INT_PATH --> INT_CHECK PROCESS --> RX_PATH PUTCHAR_CALL --> TX_PATH READY --> INT_PATH READY --> RX_PATH READY --> TX_PATH RX_CHECK --> PROCESS RX_CHECK --> RX_PATH RX_PATH --> GETCHAR_CALL TX_PATH --> PUTCHAR_CALL
Private Helper Methods
regs()
The regs()
method provides internal access to the register structure, converting the base pointer to a register reference.
Method | Signature | Purpose |
---|---|---|
regs | const fn regs(&self) -> &Pl011UartRegs | Internal register access |
This private method uses unsafe
code to dereference the base pointer and return a reference to the register structure. All public methods use this helper to access hardware registers safely.
Sources: src/pl011.rs(L57 - L59)
Thread Safety and Memory Safety
Relevant source files
This document covers the thread safety and memory safety guarantees provided by the arm_pl011
crate, focusing on the Pl011Uart
implementation and its safe abstractions over hardware register access. For general API usage patterns, see 3.1. For hardware register specifications, see 5.
Purpose and Scope
The arm_pl011
crate provides memory-safe and thread-safe abstractions for PL011 UART hardware access in no_std
embedded environments. This page examines the explicit Send
and Sync
implementations, memory safety guarantees through NonNull
and tock-registers
, and safe usage patterns for concurrent access scenarios.
Send and Sync Implementations
The Pl011Uart
struct explicitly implements both Send
and Sync
traits through unsafe implementations, enabling safe transfer and sharing between threads.
Safety Guarantees
flowchart TD Pl011Uart["Pl011Uart struct"] Send["unsafe impl Send"] Sync["unsafe impl Sync"] NonNull["NonNull<Pl011UartRegs> base"] SendSafety["Transfer between threads"] SyncSafety["Shared references across threads"] MemSafety["Non-null pointer guarantee"] TockRegs["tock-registers abstraction"] TypeSafe["Type-safe register access"] VolatileOps["Volatile read/write operations"] HardwareExclusive["Hardware access exclusive to instance"] RegisterAtomic["Register operations are atomic"] NonNull --> MemSafety NonNull --> TockRegs Pl011Uart --> NonNull Pl011Uart --> Send Pl011Uart --> Sync Send --> SendSafety SendSafety --> HardwareExclusive Sync --> SyncSafety SyncSafety --> RegisterAtomic TockRegs --> TypeSafe TockRegs --> VolatileOps
Send Safety Justification: Each Pl011Uart
instance exclusively owns access to its memory-mapped register region. The hardware controller exists at a unique physical address, making transfer between threads safe as long as no aliasing occurs.
Sync Safety Justification: PL011 register operations are atomic at the hardware level. The tock-registers
abstraction ensures volatile access patterns that are safe for concurrent readers.
Sources: src/pl011.rs(L46 - L47)
Implementation Details
Trait | Safety Requirement | Implementation Rationale |
---|---|---|
Send | Safe to transfer ownership between threads | Hardware register access is location-bound, not thread-bound |
Sync | Safe to share references across threads | Register operations are atomic; concurrent reads are safe |
The implementations rely on hardware-level atomicity guarantees and the absence of internal mutability that could cause data races.
Sources: src/pl011.rs(L42 - L47)
Memory Safety Guarantees
NonNull Pointer Management
flowchart TD Construction["Pl011Uart::new(base: *mut u8)"] NonNullWrap["NonNull::new(base).unwrap()"] Cast["cast<Pl011UartRegs>()"] Storage["NonNull<Pl011UartRegs> base"] RegAccess["regs() -> &Pl011UartRegs"] UnsafeDeref["unsafe { self.base.as_ref() }"] SafeWrapper["Safe register interface"] PanicOnNull["Panic on null pointer"] ValidityAssumption["Assumes valid memory mapping"] Cast --> Storage Construction --> NonNullWrap NonNullWrap --> Cast NonNullWrap --> PanicOnNull RegAccess --> UnsafeDeref Storage --> RegAccess UnsafeDeref --> SafeWrapper UnsafeDeref --> ValidityAssumption
The Pl011Uart
constructor uses NonNull::new().unwrap()
to ensure non-null pointer storage, panicking immediately on null input rather than deferring undefined behavior.
Memory Safety Properties:
- Non-null guarantee:
NonNull<T>
type prevents null pointer dereference - Const construction: Construction in const context validates pointer at compile time when possible
- Type safety: Cast to
Pl011UartRegs
provides typed access to register layout
Sources: src/pl011.rs(L51 - L54) src/pl011.rs(L57 - L59)
Register Access Safety
The crate uses tock-registers
to provide memory-safe register access patterns:
flowchart TD UnsafeDeref["unsafe { self.base.as_ref() }"] TockInterface["tock-registers interface"] ReadOnly["ReadOnly<u32> registers"] ReadWrite["ReadWrite<u32> registers"] WriteOnly["WriteOnly<u32> registers"] VolatileRead["Volatile read operations"] VolatileReadWrite["Volatile read/write operations"] VolatileWrite["Volatile write operations"] CompilerBarrier["Prevents compiler reordering"] HardwareSync["Synchronized with hardware state"] CompilerBarrier --> HardwareSync ReadOnly --> VolatileRead ReadWrite --> VolatileReadWrite TockInterface --> ReadOnly TockInterface --> ReadWrite TockInterface --> WriteOnly UnsafeDeref --> TockInterface VolatileRead --> CompilerBarrier VolatileReadWrite --> CompilerBarrier VolatileWrite --> CompilerBarrier WriteOnly --> VolatileWrite
Safety Mechanisms:
- Volatile access: All register operations use volatile semantics to prevent compiler optimizations
- Type safety: Register types prevent incorrect access patterns (e.g., writing to read-only registers)
- Memory barriers: Volatile operations provide necessary compiler barriers for hardware synchronization
Sources: src/pl011.rs(L9 - L32) src/pl011.rs(L57 - L59)
Thread Safety Considerations
Concurrent Access Patterns
flowchart TD subgraph subGraph2["Method Safety"] ConstMethods["&self methods (read-only)"] MutMethods["&mut self methods (exclusive)"] end subgraph subGraph1["Unsafe Patterns"] AliasedMutable["Multiple mutable references"] UnprotectedShared["Unprotected shared mutable access"] end subgraph subGraph0["Safe Patterns"] ExclusiveOwnership["Exclusive ownership per thread"] ReadOnlySharing["Shared read-only access"] MutexWrapped["Mutex<Pl011Uart> for shared writes"] end SafeTransfer["Safe: move between threads"] ReadRegisters["Read: fr, mis, ris registers"] WriteRegisters["Write: dr, cr, imsc, icr registers"] DataRace["Data race on register access"] UndefinedBehavior["Undefined behavior"] AliasedMutable --> DataRace ConstMethods --> ReadRegisters ExclusiveOwnership --> SafeTransfer MutMethods --> WriteRegisters MutexWrapped --> MutMethods ReadOnlySharing --> ConstMethods UnprotectedShared --> UndefinedBehavior
Thread Safety Model:
- Shared immutable access: Multiple threads can safely call
&self
methods for reading status - Exclusive mutable access: Only one thread should have
&mut
access for configuration and I/O operations - Hardware atomicity: Individual register operations are atomic at the hardware level
Sources: src/pl011.rs(L78 - L103)
Method Safety Classification
Method | Signature | Thread Safety | Hardware Impact |
---|---|---|---|
is_receive_interrupt() | &self | Safe for concurrent access | Read-only status check |
putchar() | &mut self | Requires exclusive access | Modifies transmit state |
getchar() | &mut self | Requires exclusive access | Modifies receive state |
init() | &mut self | Requires exclusive access | Modifies controller configuration |
ack_interrupts() | &mut self | Requires exclusive access | Clears interrupt state |
The distinction between &self
and &mut self
methods reflects the underlying hardware behavior and safety requirements.
Sources: src/pl011.rs(L64 - L103)
Safe Usage Patterns
Recommended Concurrent Access
flowchart TD SingleOwner["Single-threaded ownership"] DirectAccess["Direct method calls"] MultipleReaders["Multiple reader threads"] SharedRef["Arc<Pl011Uart>"] ReadOnlyOps["Only &self methods"] SharedWriter["Shared writer access"] MutexWrap["Arc<Mutex<Pl011Uart>>"] ExclusiveLock["lock().unwrap()"] FullAccess["All methods available"] InterruptHandler["Interrupt context"] AtomicCheck["Atomic status check"] DeferredWork["Defer work to thread context"] AtomicCheck --> DeferredWork ExclusiveLock --> FullAccess InterruptHandler --> AtomicCheck MultipleReaders --> SharedRef MutexWrap --> ExclusiveLock SharedRef --> ReadOnlyOps SharedWriter --> MutexWrap SingleOwner --> DirectAccess
Best Practices:
- Single ownership: Preferred pattern for most embedded applications
- Arc sharing: Use for status monitoring across multiple threads
- Mutex protection: Required for shared mutable access
- Interrupt safety: Minimize work in interrupt context, use atomic operations only
Sources: src/pl011.rs(L94 - L96)
Initialization Safety
The const fn new()
constructor enables compile-time safety verification when used with known memory addresses:
// Safe: Address known at compile time
const UART0: Pl011Uart = Pl011Uart::new(0x9000_0000 as *mut u8);
// Runtime construction - requires valid address
let uart = Pl011Uart::new(base_addr);
Safety Requirements:
- Base address must point to valid PL011 hardware registers
- Memory region must remain mapped for lifetime of
Pl011Uart
instance - No other code should directly access the same register region
Sources: src/pl011.rs(L50 - L55)
Development
Relevant source files
This section provides a comprehensive guide for contributors to the arm_pl011
crate, covering the development workflow, multi-target building, code quality standards, and continuous integration processes. The material focuses on the practical aspects of contributing to this embedded systems driver library.
For detailed API documentation and usage patterns, see API Reference. For hardware-specific implementation details, see Core Implementation.
Development Environment Setup
The arm_pl011
crate is designed as a no_std
embedded library that supports multiple target architectures. Development requires the Rust nightly toolchain with specific components and target platforms.
Required Toolchain Components
The project uses Rust nightly with the following components as defined in the CI configuration:
Component | Purpose |
---|---|
rust-src | Source code for cross-compilation |
clippy | Linting and code quality checks |
rustfmt | Code formatting enforcement |
Supported Target Platforms
The crate maintains compatibility across four distinct target architectures:
Target | Use Case |
---|---|
x86_64-unknown-linux-gnu | Development and testing on Linux |
x86_64-unknown-none | Bare metal x86_64 systems |
riscv64gc-unknown-none-elf | RISC-V embedded systems |
aarch64-unknown-none-softfloat | ARM64 embedded systems without FPU |
Sources: .github/workflows/ci.yml(L11 - L12) .github/workflows/ci.yml(L19)
Multi-Target Development Workflow
The development process accommodates the diverse embedded systems ecosystem by supporting multiple target architectures through a unified workflow.
Target Architecture Matrix
flowchart TD subgraph subGraph2["Build Verification"] FMT["cargo fmt --check"] CLIPPY["cargo clippy"] BUILD["cargo build"] TEST["cargo test(Linux only)"] end subgraph subGraph1["Target Matrix"] LINUX["x86_64-unknown-linux-gnuDevelopment & Testing"] BARE_X86["x86_64-unknown-noneBare Metal x86"] RISCV["riscv64gc-unknown-none-elfRISC-V Systems"] ARM64["aarch64-unknown-none-softfloatARM64 Embedded"] end subgraph subGraph0["Development Workflow"] SRC["Source Codesrc/lib.rssrc/pl011.rs"] TOOLCHAIN["Rust Nightlyrust-src, clippy, rustfmt"] end ARM64 --> FMT BARE_X86 --> FMT BUILD --> TEST CLIPPY --> BUILD FMT --> CLIPPY LINUX --> FMT RISCV --> FMT SRC --> TOOLCHAIN TOOLCHAIN --> ARM64 TOOLCHAIN --> BARE_X86 TOOLCHAIN --> LINUX TOOLCHAIN --> RISCV
Sources: .github/workflows/ci.yml(L11 - L12) .github/workflows/ci.yml(L23 - L30)
Local Development Commands
For local development, contributors should verify their changes against all supported targets:
# Format check
cargo fmt --all -- --check
# Linting for each target
cargo clippy --target <TARGET> --all-features
# Build verification
cargo build --target <TARGET> --all-features
# Unit tests (Linux only)
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
The --all-features
flag ensures compatibility with the complete feature set defined in [Cargo.toml(L15) ](https://github.com/arceos-org/arm_pl011/blob/a5a02f1f/
Cargo.toml#L15-L15)
Sources: .github/workflows/ci.yml(L23 - L30)
Continuous Integration Pipeline
The project maintains code quality through an automated CI/CD pipeline that validates all changes across the supported target matrix.
CI/CD Architecture
flowchart TD subgraph subGraph4["Documentation Pipeline"] DOC_BUILD["cargo doc --no-deps"] RUSTDOC_FLAGS["-D rustdoc::broken_intra_doc_links-D missing-docs"] GH_PAGES["Deploy to gh-pages(main branch only)"] end subgraph subGraph3["Quality Gates"] FORMAT["cargo fmt --check"] LINT["cargo clippy"] BUILD_ALL["cargo build(all targets)"] UNIT_TEST["cargo test(Linux only)"] end subgraph subGraph2["CI Matrix Strategy"] TOOLCHAIN["rust-toolchain: nightly"] TARGET_MATRIX["targets matrix:x86_64-unknown-linux-gnux86_64-unknown-noneriscv64gc-unknown-none-elfaarch64-unknown-none-softfloat"] end subgraph subGraph1["CI Jobs"] CI_JOB["ci jobubuntu-latest"] DOC_JOB["doc jobubuntu-latest"] end subgraph subGraph0["Trigger Events"] PUSH["git push"] PR["pull_request"] end BUILD_ALL --> UNIT_TEST CI_JOB --> TARGET_MATRIX CI_JOB --> TOOLCHAIN DOC_BUILD --> RUSTDOC_FLAGS DOC_JOB --> DOC_BUILD FORMAT --> LINT LINT --> BUILD_ALL PR --> CI_JOB PR --> DOC_JOB PUSH --> CI_JOB PUSH --> DOC_JOB RUSTDOC_FLAGS --> GH_PAGES TARGET_MATRIX --> FORMAT
Sources: .github/workflows/ci.yml(L1 - L56)
Quality Enforcement Standards
The CI pipeline enforces strict quality standards through multiple validation stages:
Code Formatting
All code must pass rustfmt
validation with the project's formatting rules. The CI fails if formatting inconsistencies are detected.
Linting Rules
The project uses clippy
with custom configuration that allows the clippy::new_without_default
warning while maintaining strict standards for other potential issues.
Documentation Requirements
Documentation builds enforce strict standards through RUSTDOCFLAGS
:
-D rustdoc::broken_intra_doc_links
: Fails on broken documentation links-D missing-docs
: Requires documentation for all public interfaces
Sources: .github/workflows/ci.yml(L23 - L25) .github/workflows/ci.yml(L40)
Testing Strategy
The project implements a targeted testing approach:
- Unit Tests: Execute only on
x86_64-unknown-linux-gnu
for practical development iteration - Build Verification: All targets must compile successfully to ensure cross-platform compatibility
- Documentation Tests: Included in the documentation build process
This strategy balances comprehensive validation with CI resource efficiency, since the core functionality is hardware-agnostic while the compilation targets verify platform compatibility.
Sources: .github/workflows/ci.yml(L28 - L30)
Documentation Deployment
The documentation pipeline automatically generates and deploys API documentation to GitHub Pages for the main branch. The process includes:
- Building documentation with
cargo doc --no-deps --all-features
- Generating an index redirect page based on the crate name from
cargo tree
- Deploying to the
gh-pages
branch using single-commit strategy
This ensures that the latest documentation is always available at the project's GitHub Pages URL as specified in [Cargo.toml(L10) ](https://github.com/arceos-org/arm_pl011/blob/a5a02f1f/
Cargo.toml#L10-L10)
Sources: .github/workflows/ci.yml(L44 - L55)
Building and Testing
Relevant source files
This document provides comprehensive instructions for building the arm_pl011
crate locally, running tests, and working with the multi-target build matrix. It covers the development workflow from initial setup through quality assurance checks and documentation generation.
For information about the automated CI/CD pipeline and deployment processes, see CI/CD Pipeline.
Prerequisites and Setup
The arm_pl011
crate requires Rust nightly toolchain with specific components and target architectures. The crate uses no_std
and requires unstable features for cross-platform embedded development.
Required Toolchain Components
Component | Purpose |
---|---|
rust-src | Source code for cross-compilation |
clippy | Linting and code analysis |
rustfmt | Code formatting |
Supported Target Architectures
The crate supports multiple target architectures as defined in the CI configuration:
Target | Use Case |
---|---|
x86_64-unknown-linux-gnu | Development and testing on Linux |
x86_64-unknown-none | Bare metal x86_64 systems |
riscv64gc-unknown-none-elf | RISC-V embedded systems |
aarch64-unknown-none-softfloat | ARM64 embedded systems |
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L18 - L19)
Local Build Workflow
Development Build Process
flowchart TD Start["Start Development"] Setup["Setup Toolchain"] Install["Install Components"] AddTargets["Add Target Architectures"] Format["cargo fmt --check"] Lint["cargo clippy"] Build["cargo build --target"] Test["cargo test"] Doc["cargo doc"] Complete["Development Complete"] FormatFail["Format Check Failed"] LintFail["Clippy Warnings/Errors"] BuildFail["Build Failed"] TestFail["Test Failed"] FixFormat["Fix Formatting Issues"] FixLint["Fix Lint Issues"] FixBuild["Fix Build Errors"] FixTest["Fix Test Failures"] AddTargets --> Format Build --> BuildFail Build --> Test BuildFail --> FixBuild Doc --> Complete FixBuild --> Build FixFormat --> Format FixLint --> Lint FixTest --> Test Format --> FormatFail Format --> Lint FormatFail --> FixFormat Install --> AddTargets Lint --> Build Lint --> LintFail LintFail --> FixLint Setup --> Install Start --> Setup Test --> Doc Test --> TestFail TestFail --> FixTest
Sources: .github/workflows/ci.yml(L22 - L30)
Multi-Target Building
Target Architecture Matrix
The crate implements a comprehensive multi-target build strategy to ensure compatibility across different embedded platforms:
flowchart TD subgraph subGraph3["Quality Checks"] rustfmt["cargo fmt --check"] clippy["cargo clippy"] build["cargo build"] test["cargo test"] end subgraph subGraph2["Build Outputs"] LinuxLib["Linux Development Library"] BareMetalLib["Bare Metal Library"] RiscvLib["RISC-V Embedded Library"] ArmLib["ARM64 Embedded Library"] end subgraph subGraph1["Build Matrix"] x86Linux["x86_64-unknown-linux-gnu"] x86Bare["x86_64-unknown-none"] riscv["riscv64gc-unknown-none-elf"] arm64["aarch64-unknown-none-softfloat"] end subgraph subGraph0["Source Code"] CargoToml["Cargo.toml"] SrcLib["src/lib.rs"] SrcPl011["src/pl011.rs"] end CargoToml --> arm64 CargoToml --> riscv CargoToml --> x86Bare CargoToml --> x86Linux SrcLib --> arm64 SrcLib --> riscv SrcLib --> x86Bare SrcLib --> x86Linux SrcPl011 --> arm64 SrcPl011 --> riscv SrcPl011 --> x86Bare SrcPl011 --> x86Linux arm64 --> ArmLib riscv --> RiscvLib x86Bare --> BareMetalLib x86Linux --> LinuxLib x86Linux --> build x86Linux --> clippy x86Linux --> rustfmt x86Linux --> test
Building for Specific Targets
To build for a specific target architecture:
# Build for Linux development
cargo build --target x86_64-unknown-linux-gnu --all-features
# Build for bare metal x86_64
cargo build --target x86_64-unknown-none --all-features
# Build for RISC-V embedded
cargo build --target riscv64gc-unknown-none-elf --all-features
# Build for ARM64 embedded
cargo build --target aarch64-unknown-none-softfloat --all-features
Sources: .github/workflows/ci.yml(L27) Cargo.toml(L1 - L16)
Testing Strategy
Test Execution Matrix
Testing is platform-specific due to the embedded nature of the crate:
flowchart TD subgraph subGraph2["Test Execution"] CanTest["Tests Run"] CannotTest["Tests Skipped"] end subgraph subGraph1["Target Platforms"] LinuxTarget["x86_64-unknown-linux-gnu"] BareTarget["x86_64-unknown-none"] RiscvTarget["riscv64gc-unknown-none-elf"] ArmTarget["aarch64-unknown-none-softfloat"] end subgraph subGraph0["Test Types"] UnitTests["Unit Tests"] IntegrationTests["Integration Tests"] DocTests["Documentation Tests"] end TestResults["Test Results Output"] BuildOnly["Build Verification Only"] ArmTarget --> CannotTest BareTarget --> CannotTest CanTest --> TestResults CannotTest --> BuildOnly DocTests --> LinuxTarget IntegrationTests --> LinuxTarget LinuxTarget --> CanTest RiscvTarget --> CannotTest UnitTests --> LinuxTarget
Running Tests Locally
Unit tests are only executed on the x86_64-unknown-linux-gnu
target due to the embedded nature of other targets:
# Run unit tests with output
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
# Run specific test
cargo test --target x86_64-unknown-linux-gnu test_name -- --nocapture
# Run tests with all features
cargo test --target x86_64-unknown-linux-gnu --all-features -- --nocapture
Sources: .github/workflows/ci.yml(L28 - L30)
Code Quality Assurance
Quality Check Pipeline
The development workflow includes mandatory quality checks that mirror the CI pipeline:
flowchart TD subgraph subGraph3["Build Verification"] BuildCmd["cargo build --target TARGET"] AllFeatures["--all-features"] BuildResult["Build Success"] end subgraph subGraph2["Clippy Analysis"] ClippyCmd["cargo clippy --all-features"] ClippyFilter["-A clippy::new_without_default"] ClippyResult["Lint Compliance"] end subgraph subGraph1["Format Check"] FormatCmd["cargo fmt --all --check"] FormatResult["Format Compliance"] end subgraph subGraph0["Code Quality Checks"] Format["rustfmt Check"] Lint["clippy Analysis"] Build["Multi-target Build"] Test["Unit Testing"] end AllFeatures --> BuildResult Build --> BuildCmd BuildCmd --> AllFeatures ClippyCmd --> ClippyFilter ClippyFilter --> ClippyResult Format --> FormatCmd FormatCmd --> FormatResult Lint --> ClippyCmd
Local Quality Checks
Run the same quality checks locally as the CI pipeline:
# Format check
cargo fmt --all -- --check
# Clippy with target-specific analysis
cargo clippy --target x86_64-unknown-linux-gnu --all-features -- -A clippy::new_without_default
cargo clippy --target aarch64-unknown-none-softfloat --all-features -- -A clippy::new_without_default
# Build all targets
cargo build --target x86_64-unknown-linux-gnu --all-features
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features
Sources: .github/workflows/ci.yml(L22 - L27)
Documentation Building
Documentation Generation Process
flowchart TD subgraph Output["Output"] TargetDoc["target/doc/"] IndexHtml["index.html Redirect"] DocsResult["Generated Documentation"] end subgraph subGraph2["Documentation Checks"] BrokenLinks["-D rustdoc::broken_intra_doc_links"] MissingDocs["-D missing-docs"] end subgraph subGraph1["Doc Generation"] CargoDoc["cargo doc --no-deps"] AllFeatures["--all-features"] RustDocFlags["RUSTDOCFLAGS Environment"] end subgraph subGraph0["Documentation Sources"] SrcDocs["Source Code Documentation"] ReadmeDoc["README.md"] CargoMeta["Cargo.toml Metadata"] end AllFeatures --> RustDocFlags BrokenLinks --> TargetDoc CargoDoc --> AllFeatures CargoMeta --> CargoDoc IndexHtml --> DocsResult MissingDocs --> TargetDoc ReadmeDoc --> CargoDoc RustDocFlags --> BrokenLinks RustDocFlags --> MissingDocs SrcDocs --> CargoDoc TargetDoc --> IndexHtml
Building Documentation Locally
Generate documentation with the same strict requirements as CI:
# Set documentation flags for strict checking
export RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs"
# Generate documentation
cargo doc --no-deps --all-features
# Create index redirect (mimics CI behavior)
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' \
$(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
# Open documentation in browser
open target/doc/index.html # macOS
xdg-open target/doc/index.html # Linux
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L44 - L48) Cargo.toml(L10)
Development Environment Setup
Complete Setup Commands
# Install nightly toolchain
rustup toolchain install nightly
# Add required components
rustup component add rust-src clippy rustfmt
# Add target architectures
rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
# Set nightly as default for this project
rustup override set nightly
# Verify setup
rustc --version --verbose
cargo --version
Sources: .github/workflows/ci.yml(L15 - L21)
CI/CD Pipeline
Relevant source files
This document describes the automated continuous integration and deployment pipeline for the arm_pl011 crate. The pipeline handles code quality checks, multi-target compilation, testing, documentation generation, and automated deployment to GitHub Pages.
For information about local development and testing procedures, see Building and Testing. For API documentation specifics, see API Reference.
Pipeline Overview
The CI/CD pipeline is implemented using GitHub Actions and consists of two primary workflows that execute on every push and pull request to ensure code quality and maintain up-to-date documentation.
Workflow Architecture
flowchart TD Trigger["GitHub Events"] Event1["push"] Event2["pull_request"] Pipeline["CI Workflow"] Job1["ci Job"] Job2["doc Job"] Matrix["Matrix Strategy"] Target1["x86_64-unknown-linux-gnu"] Target2["x86_64-unknown-none"] Target3["riscv64gc-unknown-none-elf"] Target4["aarch64-unknown-none-softfloat"] DocBuild["cargo doc"] Deploy["GitHub Pages Deploy"] QualityChecks["Quality Checks"] Format["cargo fmt"] Lint["cargo clippy"] Build["cargo build"] Test["cargo test"] Event1 --> Pipeline Event2 --> Pipeline Job1 --> Matrix Job2 --> Deploy Job2 --> DocBuild Matrix --> Target1 Matrix --> Target2 Matrix --> Target3 Matrix --> Target4 Pipeline --> Job1 Pipeline --> Job2 QualityChecks --> Build QualityChecks --> Format QualityChecks --> Lint QualityChecks --> Test Target1 --> QualityChecks Target2 --> QualityChecks Target3 --> QualityChecks Target4 --> QualityChecks Trigger --> Event1 Trigger --> Event2
Sources: .github/workflows/ci.yml(L1 - L56)
CI Job Implementation
The ci
job implements comprehensive quality assurance through a matrix build strategy that validates the crate across multiple target architectures.
Matrix Configuration
Component | Value |
---|---|
Runner | ubuntu-latest |
Rust Toolchain | nightly |
Fail Fast | false |
Target Count | 4 architectures |
The matrix strategy ensures the crate builds correctly across all supported embedded platforms:
flowchart TD subgraph subGraph2["Toolchain Components"] RustSrc["rust-src"] Clippy["clippy"] Rustfmt["rustfmt"] end subgraph subGraph1["Target Matrix"] T1["x86_64-unknown-linux-gnu"] T2["x86_64-unknown-none"] T3["riscv64gc-unknown-none-elf"] T4["aarch64-unknown-none-softfloat"] end subgraph subGraph0["Matrix Configuration"] Toolchain["rust-toolchain: nightly"] Strategy["fail-fast: false"] end Strategy --> T1 Strategy --> T2 Strategy --> T3 Strategy --> T4 Toolchain --> Clippy Toolchain --> RustSrc Toolchain --> Rustfmt
Sources: .github/workflows/ci.yml(L8 - L19)
Quality Check Steps
The pipeline implements a four-stage quality verification process:
flowchart TD Setup["Checkout & Rust Setup"] Version["rustc --version --verbose"] Format["cargo fmt --all -- --check"] Clippy["cargo clippy --target TARGET --all-features"] Build["cargo build --target TARGET --all-features"] Test["cargo test --target TARGET"] Condition["TARGET == x86_64-unknown-linux-gnu?"] Execute["Execute Tests"] Skip["Skip Tests"] Build --> Test Clippy --> Build Condition --> Execute Condition --> Skip Format --> Clippy Setup --> Version Test --> Condition Version --> Format
Code Formatting
The cargo fmt --all -- --check
command validates that all code adheres to standard Rust formatting conventions without making modifications.
Linting Analysis
Clippy performs static analysis with the configuration cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default
, specifically allowing the new_without_default
lint for the crate's design patterns.
Multi-Target Compilation
Each target architecture undergoes full compilation with cargo build --target ${{ matrix.targets }} --all-features
to ensure cross-platform compatibility.
Unit Testing
Unit tests execute only on the x86_64-unknown-linux-gnu
target using cargo test --target ${{ matrix.targets }} -- --nocapture
for comprehensive output visibility.
Sources: .github/workflows/ci.yml(L20 - L30)
Documentation Job
The doc
job generates and deploys API documentation with strict quality enforcement.
Documentation Build Process
flowchart TD DocJob["doc Job"] Permissions["contents: write"] EnvCheck["Branch Check"] DocFlags["RUSTDOCFLAGS Setup"] Flag1["-D rustdoc::broken_intra_doc_links"] Flag2["-D missing-docs"] Build["cargo doc --no-deps --all-features"] IndexGen["Generate index.html redirect"] BranchCheck["github.ref == default-branch?"] Deploy["JamesIves/github-pages-deploy-action@v4"] Skip["Skip Deployment"] Target["gh-pages branch"] Folder["target/doc"] BranchCheck --> Deploy BranchCheck --> Skip Build --> IndexGen Deploy --> Folder Deploy --> Target DocFlags --> Flag1 DocFlags --> Flag2 DocJob --> Permissions EnvCheck --> DocFlags Flag1 --> Build Flag2 --> Build IndexGen --> BranchCheck Permissions --> EnvCheck
Documentation Configuration
The documentation build enforces strict quality standards through RUSTDOCFLAGS
:
Flag | Purpose |
---|---|
-D rustdoc::broken_intra_doc_links | Fail on broken internal documentation links |
-D missing-docs | Require documentation for all public items |
The build process includes automatic index generation:
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
Sources: .github/workflows/ci.yml(L32 - L48)
Deployment Strategy
GitHub Pages Integration
The deployment uses the JamesIves/github-pages-deploy-action@v4
action with specific configuration:
flowchart TD DefaultBranch["Default Branch Push"] DeployAction["github-pages-deploy-action@v4"] Config1["single-commit: true"] Config2["branch: gh-pages"] Config3["folder: target/doc"] Result["Documentation Site"] Config1 --> Result Config2 --> Result Config3 --> Result DefaultBranch --> DeployAction DeployAction --> Config1 DeployAction --> Config2 DeployAction --> Config3
Conditional Deployment Logic
Documentation deployment occurs only when:
- The push targets the repository's default branch (
github.ref == env.default-branch
) - The documentation build succeeds without errors
For non-default branches and pull requests, the documentation build continues with continue-on-error: true
to provide feedback without blocking the pipeline.
Sources: .github/workflows/ci.yml(L38 - L55)
Target Architecture Matrix
The pipeline validates compilation across four distinct target architectures, ensuring broad embedded systems compatibility:
Architecture Support Matrix
Target | Environment | Use Case |
---|---|---|
x86_64-unknown-linux-gnu | Linux userspace | Development and testing |
x86_64-unknown-none | Bare metal x86 | OS kernels and bootloaders |
riscv64gc-unknown-none-elf | RISC-V embedded | RISC-V based systems |
aarch64-unknown-none-softfloat | ARM64 embedded | ARM-based embedded systems |
Build Validation Flow
flowchart TD Source["Source Code"] Validation["Multi-Target Validation"] Linux["x86_64-unknown-linux-gnu"] BareX86["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] ARM64["aarch64-unknown-none-softfloat"] TestSuite["Unit Test Suite"] CompileOnly["Compile Only"] Success["Pipeline Success"] ARM64 --> CompileOnly BareX86 --> CompileOnly CompileOnly --> Success Linux --> TestSuite RISCV --> CompileOnly Source --> Validation TestSuite --> Success Validation --> ARM64 Validation --> BareX86 Validation --> Linux Validation --> RISCV
Sources: .github/workflows/ci.yml(L12 - L30)
Hardware Reference
Relevant source files
This document provides technical specifications and hardware details for the ARM PL011 UART controller. It covers register definitions, memory mapping, signal interfaces, and hardware configuration requirements necessary for embedded systems development with the PL011.
For software implementation details and API usage, see Core Implementation. For register abstraction and driver implementation, see Register Definitions.
PL011 UART Controller Overview
The ARM PL011 is a programmable serial interface controller that implements the Universal Asynchronous Receiver/Transmitter (UART) functionality. It provides full-duplex serial communication with configurable baud rates, data formats, and hardware flow control capabilities.
Hardware Block Architecture
flowchart TD subgraph System_Interface["System_Interface"] CLK["UARTCLK"] PCLK["PCLK"] RESET["PRESETn"] IRQ["UARTINTR"] end subgraph External_Signals["External_Signals"] TXD["TXD (Transmit Data)"] RXD["RXD (Receive Data)"] RTS["nRTS (Request to Send)"] CTS["nCTS (Clear to Send)"] DSR["nDSR (Data Set Ready)"] DTR["nDTR (Data Terminal Ready)"] DCD["nDCD (Data Carrier Detect)"] RI["nRI (Ring Indicator)"] end subgraph PL011_Hardware_Block["PL011_Hardware_Block"] APB["APB Interface"] CTRL["Control Logic"] TXFIFO["TX FIFO(16 entries)"] RXFIFO["RX FIFO(16 entries)"] BAUD["Baud Rate Generator"] SER["Serializer/Deserializer"] INT["Interrupt Controller"] end APB --> CTRL BAUD --> SER CLK --> BAUD CTRL --> BAUD CTRL --> INT CTRL --> RXFIFO CTRL --> TXFIFO CTS --> SER DCD --> SER DSR --> SER INT --> IRQ PCLK --> APB RESET --> CTRL RI --> SER RXD --> SER SER --> DTR SER --> RTS SER --> RXFIFO SER --> TXD TXFIFO --> SER
Sources: src/pl011.rs(L9 - L32)
Register Map and Memory Layout
The PL011 uses a memory-mapped register interface accessed through the APB bus. The register map spans 72 bytes (0x48) with specific registers at defined offsets.
Complete Register Map
Offset | Register | Access | Description |
---|---|---|---|
0x00 | DR | R/W | Data Register |
0x04-0x14 | Reserved | - | Reserved space |
0x18 | FR | RO | Flag Register |
0x1C-0x2C | Reserved | - | Reserved space |
0x30 | CR | R/W | Control Register |
0x34 | IFLS | R/W | Interrupt FIFO Level Select |
0x38 | IMSC | R/W | Interrupt Mask Set/Clear |
0x3C | RIS | RO | Raw Interrupt Status |
0x40 | MIS | RO | Masked Interrupt Status |
0x44 | ICR | WO | Interrupt Clear |
0x48 | END | - | End of register space |
flowchart TD subgraph Memory_Layout_0x48_bytes["Memory_Layout_0x48_bytes"] DR["0x00: DRData Register(R/W)"] RES1["0x04-0x14Reserved"] FR["0x18: FRFlag Register(RO)"] RES2["0x1C-0x2CReserved"] CR["0x30: CRControl Register(R/W)"] IFLS["0x34: IFLSInterrupt FIFO Level(R/W)"] IMSC["0x38: IMSCInterrupt Mask(R/W)"] RIS["0x3C: RISRaw Interrupt Status(RO)"] MIS["0x40: MISMasked Interrupt Status(RO)"] ICR["0x44: ICRInterrupt Clear(WO)"] end CR --> IFLS DR --> RES1 FR --> RES2 IFLS --> IMSC IMSC --> RIS MIS --> ICR RES1 --> FR RES2 --> CR RIS --> MIS
Sources: src/pl011.rs(L9 - L32)
Register Specifications
Data Register (DR) - Offset 0x00
The Data Register provides the interface for transmitting and receiving data. It handles both read and write operations with automatic FIFO management.
Bits | Field | Access | Description |
---|---|---|---|
31:12 | Reserved | - | Reserved, reads as zero |
11 | OE | RO | Overrun Error |
10 | BE | RO | Break Error |
9 | PE | RO | Parity Error |
8 | FE | RO | Framing Error |
7:0 | DATA | R/W | Transmit/Receive Data |
Flag Register (FR) - Offset 0x18
The Flag Register provides status information about the UART's operational state and FIFO conditions.
Bits | Field | Access | Description |
---|---|---|---|
31:8 | Reserved | - | Reserved |
7 | TXFE | RO | Transmit FIFO Empty |
6 | RXFF | RO | Receive FIFO Full |
5 | TXFF | RO | Transmit FIFO Full |
4 | RXFE | RO | Receive FIFO Empty |
3 | BUSY | RO | UART Busy |
2 | DCD | RO | Data Carrier Detect |
1 | DSR | RO | Data Set Ready |
0 | CTS | RO | Clear to Send |
Control Register (CR) - Offset 0x30
The Control Register configures the operational parameters of the UART including enables, loopback, and flow control.
Bits | Field | Access | Description |
---|---|---|---|
31:16 | Reserved | - | Reserved |
15 | CTSEN | R/W | CTS Hardware Flow Control Enable |
14 | RTSEN | R/W | RTS Hardware Flow Control Enable |
13:12 | Reserved | - | Reserved |
11 | RTS | R/W | Request to Send |
10 | DTR | R/W | Data Terminal Ready |
9 | RXE | R/W | Receive Enable |
8 | TXE | R/W | Transmit Enable |
7 | LBE | R/W | Loopback Enable |
6:3 | Reserved | - | Reserved |
2:1 | SIRLP | R/W | SIR Low Power Mode |
0 | UARTEN | R/W | UART Enable |
Sources: src/pl011.rs(L12 - L31)
Hardware Features
FIFO Configuration
The PL011 contains separate 16-entry FIFOs for transmit and receive operations, providing buffering to reduce interrupt overhead and improve system performance.
flowchart TD subgraph Trigger_Levels["Trigger_Levels"] TX_1_8["TX: 1/8 (2 entries)"] TX_1_4["TX: 1/4 (4 entries)"] TX_1_2["TX: 1/2 (8 entries)"] TX_3_4["TX: 3/4 (12 entries)"] TX_7_8["TX: 7/8 (14 entries)"] RX_1_8["RX: 1/8 (2 entries)"] RX_1_4["RX: 1/4 (4 entries)"] RX_1_2["RX: 1/2 (8 entries)"] RX_3_4["RX: 3/4 (12 entries)"] RX_7_8["RX: 7/8 (14 entries)"] end subgraph FIFO_Architecture["FIFO_Architecture"] TXFIFO["TX FIFO16 entries8-bit width"] RXFIFO["RX FIFO16 entries8-bit width + 4-bit error"] TXLOGIC["TX Logic"] RXLOGIC["RX Logic"] IFLS_REG["IFLS RegisterTrigger Levels"] end IFLS_REG --> RXFIFO IFLS_REG --> RX_1_8 IFLS_REG --> TXFIFO IFLS_REG --> TX_1_8 RXLOGIC --> RXFIFO TXFIFO --> TXLOGIC
Sources: src/pl011.rs(L68 - L69)
Interrupt System
The PL011 provides comprehensive interrupt support with multiple interrupt sources and configurable masking.
Interrupt | Bit | Description |
---|---|---|
OEIM | 10 | Overrun Error Interrupt |
BEIM | 9 | Break Error Interrupt |
PEIM | 8 | Parity Error Interrupt |
FEIM | 7 | Framing Error Interrupt |
RTIM | 6 | Receive Timeout Interrupt |
TXIM | 5 | Transmit Interrupt |
RXIM | 4 | Receive Interrupt |
DSRMIM | 3 | nUARTDSR Modem Interrupt |
DCDMIM | 2 | nUARTDCD Modem Interrupt |
CTSMIM | 1 | nUARTCTS Modem Interrupt |
RIMIM | 0 | nUARTRI Modem Interrupt |
Sources: src/pl011.rs(L71 - L72) src/pl011.rs(L94 - L96) src/pl011.rs(L100 - L101)
Signal Interface and Pin Configuration
Primary UART Signals
The PL011 requires the following primary signals for basic UART operation:
Signal | Direction | Description |
---|---|---|
TXD | Output | Transmit Data |
RXD | Input | Receive Data |
UARTCLK | Input | UART Reference Clock |
PCLK | Input | APB Bus Clock |
PRESETn | Input | APB Reset (active low) |
Modem Control Signals (Optional)
For full modem control functionality, additional signals are available:
Signal | Direction | Description |
---|---|---|
nRTS | Output | Request to Send (active low) |
nCTS | Input | Clear to Send (active low) |
nDTR | Output | Data Terminal Ready (active low) |
nDSR | Input | Data Set Ready (active low) |
nDCD | Input | Data Carrier Detect (active low) |
nRI | Input | Ring Indicator (active low) |
flowchart TD subgraph External_Device["External_Device"] EXT["External UART Deviceor Terminal"] end subgraph PL011_Pin_Interface["PL011_Pin_Interface"] UART["PL011 Controller"] TXD_PIN["TXD"] RXD_PIN["RXD"] RTS_PIN["nRTS"] CTS_PIN["nCTS"] DTR_PIN["nDTR"] DSR_PIN["nDSR"] DCD_PIN["nDCD"] RI_PIN["nRI"] CLK_PIN["UARTCLK"] PCLK_PIN["PCLK"] RST_PIN["PRESETn"] end CLK_PIN --> UART CTS_PIN --> UART DCD_PIN --> UART DSR_PIN --> UART EXT --> CTS_PIN EXT --> RXD_PIN PCLK_PIN --> UART RI_PIN --> UART RST_PIN --> UART RTS_PIN --> EXT RXD_PIN --> UART TXD_PIN --> EXT UART --> DTR_PIN UART --> RTS_PIN UART --> TXD_PIN
Sources: README.md references ARM documentation
Hardware Initialization Requirements
Power-On Reset Sequence
The PL011 requires a specific initialization sequence to ensure proper operation:
- Assert PRESETn: Hold the reset signal low during power-up
- Clock Stabilization: Ensure UARTCLK and PCLK are stable
- Release Reset: Deassert PRESETn to begin initialization
- Register Configuration: Configure control and interrupt registers
Essential Configuration Steps
Based on the driver implementation, the hardware requires this initialization sequence:
flowchart TD RESET["Hardware ResetPRESETn asserted"] CLK_STABLE["Clock StabilizationUARTCLK, PCLK stable"] REL_RESET["Release ResetPRESETn deasserted"] CLEAR_INT["Clear All InterruptsICR = 0x7FF"] SET_FIFO["Set FIFO LevelsIFLS = 0x00"] EN_RX_INT["Enable RX InterruptIMSC = 0x10"] EN_UART["Enable UART OperationCR = 0x301"] CLEAR_INT --> SET_FIFO CLK_STABLE --> REL_RESET EN_RX_INT --> EN_UART REL_RESET --> CLEAR_INT RESET --> CLK_STABLE SET_FIFO --> EN_RX_INT
The specific register values used in initialization:
- ICR:
0x7FF
- Clears all interrupt sources - IFLS:
0x00
- Sets 1/8 trigger level for both TX and RX FIFOs - IMSC:
0x10
- Enables receive interrupt (RXIM bit) - CR:
0x301
- Enables UART, transmit, and receive (UARTEN | TXE | RXE
)
Sources: src/pl011.rs(L64 - L76)
Performance and Timing Considerations
Clock Requirements
The PL011 operates with two clock domains:
- PCLK: APB bus clock for register access (typically system clock frequency)
- UARTCLK: Reference clock for baud rate generation (can be independent)
Baud Rate Generation
The internal baud rate generator uses UARTCLK to create the desired communication speed. The relationship is:
Baud Rate = UARTCLK / (16 × (IBRD + FBRD/64))
Where IBRD and FBRD are integer and fractional baud rate divisors configured through additional registers not exposed in this implementation.
FIFO Timing Characteristics
- FIFO Depth: 16 entries for both TX and RX
- Access Time: Single PCLK cycle for register access
- Interrupt Latency: Configurable based on FIFO trigger levels
- Overrun Protection: Hardware prevents data loss when FIFOs are properly managed
Sources: src/pl011.rs(L68 - L69) for FIFO configuration, ARM PL011 Technical Reference Manual for timing specifications
Introduction
Relevant source files
Purpose and Scope
This document covers the handler_table
crate, a lock-free event handling system designed for the ArceOS operating system. The crate provides a thread-safe, fixed-size table that maps event identifiers to handler functions without requiring locks or dynamic memory allocation.
For detailed API usage and examples, see User Guide. For implementation details and atomic operations, see Implementation Details. For ArceOS-specific integration patterns, see ArceOS Integration.
Overview
The handler_table
crate implements a HandlerTable<N>
structure that stores function pointers in a compile-time sized array using atomic operations for thread safety. It serves as a central dispatch mechanism for event handling in kernel and embedded environments where lock-free operations are critical for performance and real-time guarantees.
System Architecture
The following diagram illustrates the core components and their relationships in the codebase:
HandlerTable Core Components
flowchart TD subgraph subGraph2["Handler Storage"] FunctionPointer["fn() as *const ()"] ZeroValue["0 (empty slot)"] end subgraph subGraph1["Atomic Operations"] compare_exchange["compare_exchange()"] load["load()"] swap["swap()"] end subgraph subGraph0["Public API Methods"] new["new()"] register_handler["register_handler(idx, handler)"] handle["handle(idx)"] unregister_handler["unregister_handler(idx)"] end HandlerTable["HandlerTable<N>"] AtomicArray["[AtomicUsize; N]"] AtomicArray --> FunctionPointer AtomicArray --> ZeroValue HandlerTable --> AtomicArray compare_exchange --> AtomicArray handle --> load load --> AtomicArray new --> HandlerTable register_handler --> compare_exchange swap --> AtomicArray unregister_handler --> swap
Sources: Cargo.toml(L1 - L15) README.md(L11 - L31)
Event Handling Flow
The following diagram shows how events flow through the system using specific code entities:
Event Processing Pipeline
Sources: README.md(L16 - L28)
Key Characteristics
The handler_table
crate provides several critical features for system-level event handling:
Feature | Implementation | Benefit |
---|---|---|
Lock-free Operation | Atomic operations only (AtomicUsize) | No blocking, suitable for interrupt contexts |
Fixed Size | Compile-time array[AtomicUsize; N] | No dynamic allocation,no_stdcompatible |
Thread Safety | Memory ordering guarantees | Safe concurrent access across threads |
Zero Dependencies | Pure core library usage | Minimal footprint for embedded systems |
Target Environments
The crate is specifically designed for:
- ArceOS kernel components - Central event dispatch system
- Embedded systems - Real-time event handling without heap allocation
- Interrupt handlers - Lock-free operation in critical sections
- Multi-architecture support - Works across x86_64, RISC-V, and ARM platforms
Sources: Cargo.toml(L6 - L12)
Integration Context
Within the ArceOS ecosystem, handler_table
serves as a foundational component for event-driven programming patterns. The lock-free design enables high-performance event dispatch suitable for kernel-level operations where traditional locking mechanisms would introduce unacceptable latency or deadlock risks.
For specific integration patterns with ArceOS components, see ArceOS Integration. For understanding the lock-free design principles and their benefits, see Lock-free Design Benefits.
Sources: Cargo.toml(L8 - L11) README.md(L1 - L7)
ArceOS Integration
Relevant source files
This document explains how the handler_table
crate integrates into the ArceOS operating system ecosystem and serves as a foundational component for lock-free event handling in kernel and embedded environments.
The scope covers the architectural role of handler_table
within ArceOS, its no_std
design constraints, and how it fits into the broader operating system infrastructure. For details about the lock-free programming concepts and performance benefits, see Lock-free Design Benefits. For implementation specifics, see Implementation Details.
Purpose and Role in ArceOS
The handler_table
crate provides a core event handling mechanism designed specifically for the ArceOS operating system. ArceOS is a modular, component-based operating system written in Rust that targets both traditional and embedded computing environments.
ArceOS Integration Architecture
flowchart TD subgraph Event_Sources["Event Sources"] Hardware_Interrupts["Hardware Interrupts"] Timer_Events["Timer Events"] System_Calls["System Calls"] IO_Completion["I/O Completion"] end subgraph handler_table_Crate["handler_table Crate"] HandlerTable_Struct["HandlerTable"] register_handler["register_handler()"] handle_method["handle()"] unregister_handler["unregister_handler()"] end subgraph ArceOS_Ecosystem["ArceOS Operating System Ecosystem"] ArceOS_Kernel["ArceOS Kernel Core"] Task_Scheduler["Task Scheduler"] Memory_Manager["Memory Manager"] Device_Drivers["Device Drivers"] Interrupt_Controller["Interrupt Controller"] end ArceOS_Kernel --> HandlerTable_Struct Device_Drivers --> HandlerTable_Struct HandlerTable_Struct --> handle_method Hardware_Interrupts --> register_handler IO_Completion --> register_handler Interrupt_Controller --> HandlerTable_Struct System_Calls --> register_handler Task_Scheduler --> HandlerTable_Struct Timer_Events --> register_handler handle_method --> unregister_handler
The crate serves as a central dispatch mechanism that allows different ArceOS subsystems to register event handlers without requiring traditional locking mechanisms that would introduce latency and complexity in kernel code.
Sources: Cargo.toml(L1 - L15)
No-std Environment Design
The handler_table
crate is specifically designed for no_std
environments, making it suitable for embedded systems and kernel-level code where the standard library is not available.
Design Constraint | Implementation | ArceOS Benefit |
---|---|---|
No heap allocation | Fixed-size arrays with compile-time bounds | Predictable memory usage in kernel |
No standard library | Core atomics and primitive types only | Minimal dependencies for embedded targets |
Zero-cost abstractions | Direct atomic operations without runtime overhead | High-performance event handling |
Compile-time sizing | GenericHandlerTable | Memory layout known at build time |
No-std Integration Flow
flowchart TD subgraph ArceOS_Targets["ArceOS Target Architectures"] x86_64_none["x86_64-unknown-none"] riscv64_none["riscv64gc-unknown-none-elf"] aarch64_none["aarch64-unknown-none-softfloat"] end subgraph handler_table_Integration["handler_table Integration"] Core_Only["core:: imports only"] AtomicUsize_Array["AtomicUsize array storage"] Const_Generic["HandlerTable sizing"] end subgraph Build_Process["ArceOS Build Process"] Cargo_Config["Cargo Configuration"] Target_Spec["Target Specification"] no_std_Flag["#![no_std] Attribute"] end AtomicUsize_Array --> Const_Generic Cargo_Config --> Core_Only Const_Generic --> aarch64_none Const_Generic --> riscv64_none Const_Generic --> x86_64_none Core_Only --> AtomicUsize_Array Target_Spec --> Core_Only no_std_Flag --> Core_Only
This design enables ArceOS to use handler_table
across different target architectures without modification, supporting both traditional x86_64 systems and embedded RISC-V and ARM platforms.
Sources: Cargo.toml(L12)
Integration Patterns
ArceOS components integrate with handler_table
through several common patterns that leverage the crate's lock-free design.
Event Registration Pattern
sequenceDiagram participant ArceOSModule as "ArceOS Module" participant HandlerTableN as "HandlerTable<N>" participant AtomicUsizeidx as "AtomicUsize[idx]" Note over ArceOSModule,AtomicUsizeidx: Module Initialization Phase ArceOSModule ->> HandlerTableN: "register_handler(event_id, handler_fn)" HandlerTableN ->> AtomicUsizeidx: "compare_exchange(0, handler_ptr)" alt Registration Success AtomicUsizeidx -->> HandlerTableN: "Ok(0)" HandlerTableN -->> ArceOSModule: "true" Note over ArceOSModule: "Handler registered successfully" else Slot Already Occupied AtomicUsizeidx -->> HandlerTableN: "Err(existing_ptr)" HandlerTableN -->> ArceOSModule: "false" Note over ArceOSModule: "Handle registration conflict" end Note over ArceOSModule,AtomicUsizeidx: Runtime Event Handling ArceOSModule ->> HandlerTableN: "handle(event_id)" HandlerTableN ->> AtomicUsizeidx: "load(Ordering::Acquire)" AtomicUsizeidx -->> HandlerTableN: "handler_ptr" alt Handler Exists HandlerTableN ->> HandlerTableN: "transmute & call handler" HandlerTableN -->> ArceOSModule: "true (handled)" else No Handler HandlerTableN -->> ArceOSModule: "false (not handled)" end
This pattern allows ArceOS modules to register event handlers during system initialization and efficiently dispatch events during runtime without locks.
Sources: Cargo.toml(L6)
Repository and Documentation Structure
The crate is maintained as part of the broader ArceOS organization, with integration points designed to support the operating system's modular architecture.
ArceOS Organization Structure
flowchart TD subgraph Integration_Points["Integration Points"] homepage_link["Homepage -> arceos repo"] keywords_arceos["Keywords: 'arceos'"] license_compat["Compatible licensing"] end subgraph Documentation["Documentation Infrastructure"] docs_rs["docs.rs documentation"] github_pages["GitHub Pages"] readme_docs["README.md examples"] end subgraph arceos_org["ArceOS Organization (GitHub)"] arceos_main["arceos (main repository)"] handler_table_repo["handler_table (this crate)"] other_crates["Other ArceOS crates"] end arceos_main --> handler_table_repo handler_table_repo --> docs_rs handler_table_repo --> github_pages handler_table_repo --> other_crates handler_table_repo --> readme_docs homepage_link --> arceos_main keywords_arceos --> arceos_main license_compat --> arceos_main
The repository structure ensures that handler_table
can be discovered and integrated by ArceOS developers while maintaining independent versioning and documentation.
Sources: Cargo.toml(L8 - L11)
Version and Compatibility
The current version 0.1.2
indicates this is an early-stage crate that is being actively developed alongside the ArceOS ecosystem. The version strategy supports incremental improvements while maintaining API stability for core ArceOS components.
Aspect | Current State | ArceOS Integration Impact |
---|---|---|
Version | 0.1.2 | Early development, compatible with ArceOS components |
License | GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0 | Multiple license options for different ArceOS use cases |
Edition | 2021 | Modern Rust features available for ArceOS development |
Dependencies | None | Zero-dependency design reduces ArceOS build complexity |
The tri-license approach provides flexibility for different ArceOS deployment scenarios, supporting both open-source and commercial use cases.
Sources: Cargo.toml(L3 - L7) Cargo.toml(L14 - L15)
Lock-free Design Benefits
Relevant source files
Purpose and Scope
This document explains the lock-free design principles used in the handler_table
crate and the specific benefits this approach provides for event handling in operating systems. We focus on how lock-free programming concepts enable high-performance, concurrent event dispatch without traditional synchronization primitives.
For information about how this crate integrates with ArceOS specifically, see ArceOS Integration. For detailed implementation specifics of the atomic operations, see Atomic Operations.
Lock-free Programming Fundamentals
Lock-free programming is a concurrency paradigm that eliminates the use of traditional locks (mutexes, semaphores) by relying on atomic operations to ensure thread safety. In the handler_table
crate, this is achieved through the use of AtomicUsize
arrays and carefully chosen memory ordering constraints.
Core Lock-free Principles in HandlerTable
flowchart TD subgraph subGraph2["HandlerTable Implementation"] HANDLERS["handlers: [AtomicUsize; N]"] REG["register_handler()"] HANDLE["handle()"] UNREG["unregister_handler()"] end subgraph subGraph1["Lock-free HandlerTable Approach"] A2["Thread 2"] L2["Wait for Lock"] A3["Thread N"] L3["Wait for Lock"] R1["Release Lock"] O2["Modify Data"] B1["Thread 1"] CAS1["compare_exchange on AtomicUsize"] B2["Thread 2"] LOAD1["load from AtomicUsize"] B3["Thread N"] SWAP1["swap on AtomicUsize"] SUCCESS1["Immediate Success/Failure"] SUCCESS2["Immediate Read"] SUCCESS3["Immediate Exchange"] end subgraph subGraph0["Traditional Locking Approach"] A1["Thread 1"] L1["Acquire Lock"] A2["Thread 2"] L2["Wait for Lock"] A3["Thread N"] L3["Wait for Lock"] O1["Modify Data"] R1["Release Lock"] O2["Modify Data"] B1["Thread 1"] B2["Thread 2"] end A1 --> L1 A2 --> L2 A3 --> L3 B1 --> CAS1 B2 --> LOAD1 B3 --> SWAP1 CAS1 --> REG CAS1 --> SUCCESS1 HANDLE --> HANDLERS L1 --> O1 L2 --> O2 LOAD1 --> HANDLE LOAD1 --> SUCCESS2 O1 --> R1 R1 --> L2 REG --> HANDLERS SWAP1 --> SUCCESS3 SWAP1 --> UNREG UNREG --> HANDLERS
Sources: src/lib.rs(L14 - L16) src/lib.rs(L30 - L37) src/lib.rs(L58 - L70) src/lib.rs(L42 - L52)
Atomic Operations Mapping
The HandlerTable
maps each operation to specific atomic primitives:
Operation | Atomic Method | Memory Ordering | Purpose |
---|---|---|---|
register_handler | compare_exchange | Acquire/Relaxed | Atomically register if slot empty |
handle | load | Acquire | Read handler pointer safely |
unregister_handler | swap | Acquire | Atomically remove and return handler |
Sources: src/lib.rs(L34 - L36) src/lib.rs(L62) src/lib.rs(L46)
Benefits in Event Handling Systems
Non-blocking Event Processing
Lock-free design enables event handlers to be registered, executed, and unregistered without blocking other threads. This is critical in operating system kernels where interrupt handlers and system calls must execute with minimal latency.
sequenceDiagram participant InterruptContext as Interrupt Context participant HandlerTable as HandlerTable participant AtomicUsizeidx as AtomicUsize[idx] participant EventHandler as Event Handler Note over InterruptContext,EventHandler: Concurrent Event Processing par Registration in Kernel Thread InterruptContext ->> HandlerTable: "register_handler(IRQ_TIMER, timer_handler)" HandlerTable ->> AtomicUsizeidx: "compare_exchange(0, handler_ptr)" AtomicUsizeidx -->> HandlerTable: "Ok() - Registration Success" else Event Handling in Interrupt InterruptContext ->> HandlerTable: "handle(IRQ_TIMER)" HandlerTable ->> AtomicUsizeidx: "load(Ordering::Acquire)" AtomicUsizeidx -->> HandlerTable: "handler_ptr" HandlerTable ->> EventHandler: "Call timer_handler()" EventHandler -->> HandlerTable: "Handler Complete" HandlerTable -->> InterruptContext: "true - Event Handled" else Cleanup in User Thread InterruptContext ->> HandlerTable: "unregister_handler(IRQ_TIMER)" HandlerTable ->> AtomicUsizeidx: "swap(0, Ordering::Acquire)" AtomicUsizeidx -->> HandlerTable: "old_handler_ptr" HandlerTable -->> InterruptContext: "Some(timer_handler)" end
Sources: src/lib.rs(L30 - L37) src/lib.rs(L58 - L70) src/lib.rs(L42 - L52)
Deterministic Performance Characteristics
Unlike mutex-based approaches, lock-free operations have bounded execution time regardless of concurrent access patterns:
Characteristic | Lock-based Approach | Lock-free HandlerTable |
---|---|---|
Worst-case latency | Unbounded (lock contention) | Bounded (atomic operation time) |
Priority inversion | Possible | Impossible |
Deadlock risk | Present | Absent |
Context switching | Required on contention | Never required |
Sources: src/lib.rs(L4) src/lib.rs(L34 - L36)
Implementation in HandlerTable
Memory Layout and Atomic Storage
The HandlerTable<N>
stores function pointers as usize
values in an AtomicUsize
array, leveraging the fact that function pointers have the same size as usize
on supported architectures.
flowchart TD subgraph subGraph2["Concurrent Operations"] CAS["compare_exchange(0, fn_ptr)"] LOAD["load() -> usize"] SWAP["swap(0) -> old_fn_ptr"] end subgraph subGraph1["Atomic Value Semantics"] ZERO["0 = Empty Slot"] FNPTR["non-zero = Handler Function Pointer"] TRANSMUTE["unsafe transmute<usize, fn()>"] end subgraph subGraph0["HandlerTable Memory Layout"] STRUCT["HandlerTable<N>"] ARRAY["handlers: [AtomicUsize; N]"] SLOT0["AtomicUsize[0]"] SLOT1["AtomicUsize[1]"] SLOTN["AtomicUsize[N-1]"] end ARRAY --> SLOT0 ARRAY --> SLOT1 ARRAY --> SLOTN CAS --> SLOT0 FNPTR --> TRANSMUTE LOAD --> SLOT1 SLOT0 --> ZERO SLOT1 --> FNPTR SLOTN --> FNPTR STRUCT --> ARRAY SWAP --> SLOTN
Sources: src/lib.rs(L14 - L16) src/lib.rs(L22 - L23) src/lib.rs(L48) src/lib.rs(L64)
Memory Ordering Guarantees
The crate uses Ordering::Acquire
for reads and exchanges, ensuring that:
- Handler registration becomes visible to other threads before any subsequent operations
- Handler execution observes all memory writes that happened before registration
- Handler unregistration synchronizes with any concurrent access attempts
// From register_handler - ensures visibility of registration
compare_exchange(0, handler as usize, Ordering::Acquire, Ordering::Relaxed)
// From handle - ensures handler sees all prior writes
load(Ordering::Acquire)
// From unregister_handler - ensures cleanup synchronization
swap(0, Ordering::Acquire)
Sources: src/lib.rs(L35) src/lib.rs(L62) src/lib.rs(L46)
Performance and Scalability Benefits
Scalability Analysis
Lock-free design provides several scalability advantages for event-driven systems:
Metric | Impact | Benefit |
---|---|---|
Thread contention | Eliminated | Linear scalability with core count |
Cache coherency | Minimized | Reduced memory bus traffic |
Context switches | Avoided | Lower CPU overhead |
Real-time guarantees | Preserved | Predictable worst-case execution time |
No-std Environment Optimization
The #![no_std]
design requirement makes lock-free programming essential, as traditional locking primitives are unavailable or too heavyweight for embedded/kernel environments.
flowchart TD subgraph subGraph2["ArceOS Integration Benefits"] KERNEL["Kernel Space Usage"] INTERRUPT["Interrupt Context Safe"] EMBEDDED["Embedded Target Support"] REALTIME["Real-time Guarantees"] end subgraph subGraph1["no_std HandlerTable Design"] NOSTD["#![no_std]"] ATOMIC["core::sync::atomic"] CONST["const fn new()"] FIXED["Fixed-size Array"] end subgraph subGraph0["Traditional OS Constraints"] HEAP["Heap Allocation"] STDLIB["Standard Library"] THREADS["Thread Primitives"] MUTEX["Mutex/Condvar"] end ATOMIC --> KERNEL ATOMIC --> REALTIME CONST --> INTERRUPT FIXED --> EMBEDDED HEAP --> NOSTD MUTEX --> NOSTD NOSTD --> ATOMIC NOSTD --> CONST NOSTD --> FIXED STDLIB --> NOSTD THREADS --> NOSTD
Sources: src/lib.rs(L1) src/lib.rs(L4) src/lib.rs(L20 - L24)
Memory Safety and Correctness
Safe Concurrent Access Patterns
The lock-free design maintains memory safety through several mechanisms:
- Atomic pointer operations: Function pointers are stored and accessed atomically
- ABA problem avoidance: Using
compare_exchange
prevents race conditions during registration - Memory ordering:
Acquire
ordering ensures proper synchronization - Bounded unsafe code:
unsafe
transmutation is isolated to well-defined conversion points
flowchart TD subgraph subGraph2["Mitigation Strategies"] ZERO_CHECK["Zero Value = Empty Check"] CAS_ATOMICITY["compare_exchange Atomicity"] BOUNDS_CHECK["Array Bounds Validation"] end subgraph subGraph1["Potential Hazards"] TRANSMUTE["transmute<usize, fn()>"] NULL_DEREF["Null Function Pointer"] RACE_CONDITION["Registration Races"] end subgraph subGraph0["Safety Guarantees"] ATOMIC_OPS["Atomic Operations Only"] MEMORY_ORDER["Acquire Memory Ordering"] CAS_PROTECTION["CAS Prevents ABA"] ISOLATED_UNSAFE["Bounded unsafe Regions"] end ATOMIC_OPS --> CAS_ATOMICITY CAS_PROTECTION --> CAS_ATOMICITY ISOLATED_UNSAFE --> BOUNDS_CHECK MEMORY_ORDER --> CAS_ATOMICITY NULL_DEREF --> ZERO_CHECK RACE_CONDITION --> CAS_ATOMICITY TRANSMUTE --> ZERO_CHECK
Sources: src/lib.rs(L48) src/lib.rs(L64) src/lib.rs(L31 - L32) src/lib.rs(L43 - L44) src/lib.rs(L59 - L60)
The lock-free design of HandlerTable
provides essential performance and safety characteristics required for kernel-level event handling while maintaining the simplicity needed for embedded and real-time systems.
User Guide
Relevant source files
This guide provides practical instructions for using the handler_table
crate in your applications. The guide covers the complete API usage workflow, from initialization through event handling and cleanup. For detailed API documentation, see API Reference, and for comprehensive examples, see Usage Examples. For implementation details and internals, see Implementation Details.
Overview
The HandlerTable<N>
provides a lock-free, fixed-size table for registering and invoking event handlers. It is designed for no_std
environments where traditional synchronization primitives are unavailable or undesirable. The table uses atomic operations to ensure thread safety without blocking.
Core Usage Pattern:
flowchart TD Create["HandlerTable::new()"] Register["register_handler(idx, fn)"] Handle["handle(idx)"] Unregister["unregister_handler(idx)"] Handle2["handle(other_idx)"] Create --> Register Handle --> Unregister Register --> Handle Register --> Handle2
Sources: README.md(L1 - L32)
Key Concepts
The HandlerTable<N>
operates on several fundamental concepts:
Concept | Description | Code Entity |
---|---|---|
Fixed Size | Table size determined at compile time | HandlerTable |
Event Index | Numeric identifier for each event type | idxparameter in methods |
Handler Function | Zero-argument closure or function pointer | FnOnce()trait bound |
Atomic Storage | Lock-free concurrent access | InternalAtomicUsizearray |
Handler Table Lifecycle
Sources: README.md(L11 - L31)
Quick Start Example
The basic usage follows a simple pattern of registration, handling, and optional cleanup:
use handler_table::HandlerTable;
// Create a table with 8 slots
static TABLE: HandlerTable<8> = HandlerTable::new();
// Register handlers for specific event indices
TABLE.register_handler(0, || {
println!("Hello, event 0!");
});
TABLE.register_handler(1, || {
println!("Hello, event 1!");
});
// Handle events by index
assert!(TABLE.handle(0)); // Returns true, prints message
assert!(!TABLE.handle(2)); // Returns false, no handler registered
// Unregister and retrieve handlers
let handler = TABLE.unregister_handler(1).unwrap();
handler(); // Can still call the retrieved handler
Sources: README.md(L11 - L31)
API Workflow
The following diagram shows how the main API methods interact with the internal atomic storage:
sequenceDiagram participant UserCode as "User Code" participant HandlerTableN as "HandlerTable<N>" participant AtomicUsizeidx as "AtomicUsize[idx]" Note over UserCode,AtomicUsizeidx: "Registration Phase" UserCode ->> HandlerTableN: "register_handler(idx, fn)" HandlerTableN ->> AtomicUsizeidx: "compare_exchange(0, handler_ptr)" alt "Slot Empty" AtomicUsizeidx -->> HandlerTableN: "Ok(0)" HandlerTableN -->> UserCode: "true (success)" else "Slot Occupied" AtomicUsizeidx -->> HandlerTableN: "Err(current_ptr)" HandlerTableN -->> UserCode: "false (already registered)" end Note over UserCode,AtomicUsizeidx: "Execution Phase" UserCode ->> HandlerTableN: "handle(idx)" HandlerTableN ->> AtomicUsizeidx: "load(Ordering::Acquire)" AtomicUsizeidx -->> HandlerTableN: "handler_ptr or 0" alt "Handler Present" HandlerTableN ->> HandlerTableN: "transmute and call handler" HandlerTableN -->> UserCode: "true (handled)" else "No Handler" HandlerTableN -->> UserCode: "false (not handled)" end Note over UserCode,AtomicUsizeidx: "Cleanup Phase" UserCode ->> HandlerTableN: "unregister_handler(idx)" HandlerTableN ->> AtomicUsizeidx: "swap(0, Ordering::Acquire)" AtomicUsizeidx -->> HandlerTableN: "old_handler_ptr" HandlerTableN -->> UserCode: "Some(handler_fn) or None"
Sources: README.md(L16 - L30)
Core API Methods
The HandlerTable<N>
provides four essential methods for handler management:
Initialization
HandlerTable::new()
- Creates a new empty handler table with all slots initialized to zero
Registration
register_handler(idx: usize, handler: impl FnOnce())
- Atomically registers a handler function at the specified index- Returns
true
if registration succeeds - Returns
false
if a handler is already registered at that index
Execution
handle(idx: usize)
- Invokes the handler registered at the specified index- Returns
true
if a handler was found and executed - Returns
false
if no handler is registered at that index
Cleanup
unregister_handler(idx: usize)
- Atomically removes and returns the handler at the specified index- Returns
Some(handler)
if a handler was registered - Returns
None
if no handler was registered
flowchart TD subgraph subGraph1["Internal AtomicUsize Array"] slot0["slots[0]: AtomicUsize"] slot1["slots[1]: AtomicUsize"] slotN["slots[N-1]: AtomicUsize"] end subgraph subGraph0["HandlerTable Public API"] new["new() -> HandlerTable"] register["register_handler(idx, fn) -> bool"] handle["handle(idx) -> bool"] unregister["unregister_handler(idx) -> Option"] end handle --> slot0 handle --> slot1 handle --> slotN new --> slot0 new --> slot1 new --> slotN register --> slot0 register --> slot1 register --> slotN unregister --> slot0 unregister --> slot1 unregister --> slotN
Sources: README.md(L16 - L30)
Best Practices
Size Selection
Choose the table size N
based on your maximum expected number of concurrent event types. The size is fixed at compile time and cannot be changed dynamically.
Index Management
- Use consistent index values across your application
- Consider defining constants for event indices to avoid magic numbers
- Ensure indices are within bounds (0 to N-1)
Handler Design
- Keep handlers lightweight and fast-executing
- Avoid blocking operations within handlers
- Consider handler re-entrancy if the same index might be triggered during execution
Concurrent Usage
- Multiple threads can safely call any method simultaneously
- Registration conflicts return
false
rather than blocking - Handler execution is atomic but the handler itself may not be re-entrant
Error Handling
- Check return values from
register_handler()
to handle registration conflicts - Check return values from
handle()
to distinguish between handled and unhandled events - Use the return value from
unregister_handler()
to verify successful removal
Sources: README.md(L1 - L32)
API Reference
Relevant source files
This page provides comprehensive documentation for all public APIs in the handler_table
crate. It covers the core HandlerTable<N>
data structure, its methods, and the Handler
type alias.
For practical usage examples and integration patterns, see Usage Examples. For detailed information about the underlying atomic operations and memory safety guarantees, see Implementation Details.
Core Types
Handler Type Alias
The Handler
type represents a function pointer to an event handler with no parameters and no return value.
pub type Handler = fn();
Key Characteristics:
- Zero-argument function pointer
- No return value
- Suitable for simple event callbacks
- Compatible with closures that capture no variables
Sources: src/lib.rs(L6 - L9)
HandlerTable Struct
The HandlerTable<N>
is a generic data structure that stores up to N
event handlers in a lock-free manner using atomic operations.
pub struct HandlerTable<const N: usize> {
handlers: [AtomicUsize; N],
}
Key Characteristics:
- Compile-time fixed size determined by const generic
N
- Uses
AtomicUsize
array for lock-free operation - Stores function pointers as
usize
values via transmutation - Zero-initialized (empty) slots contain value
0
Sources: src/lib.rs(L11 - L16)
API Structure Overview
flowchart TD subgraph subGraph2["Atomic Operations"] compare_exchange["compare_exchange(0, handler_ptr)"] swap["swap(0)"] load["load(Ordering::Acquire)"] end subgraph subGraph1["Core Types"] Handler["Handler = fn()"] AtomicArray["[AtomicUsize; N]"] end subgraph subGraph0["HandlerTable Public API"] new["new() -> Self"] register["register_handler(idx, handler) -> bool"] unregister["unregister_handler(idx) -> Option"] handle["handle(idx) -> bool"] default["Default::default() -> Self"] end AtomicArray --> compare_exchange AtomicArray --> load AtomicArray --> swap Handler --> register default --> new handle --> load new --> AtomicArray register --> compare_exchange unregister --> swap
Sources: src/lib.rs(L18 - L77)
Constructor Methods
new()
Creates a new HandlerTable<N>
with all handler slots initialized to empty.
Signature:
pub const fn new() -> Self
Behavior:
- Initializes all
N
slots to zero usingAtomicUsize::new(0)
- Can be used in
const
contexts for static allocation - All slots are initially unregistered
Example Usage:
static TABLE: HandlerTable<8> = HandlerTable::new();
Sources: src/lib.rs(L19 - L24)
Default Implementation
The Default
trait is implemented to delegate to new()
.
Signature:
impl<const N: usize> Default for HandlerTable<N>
Behavior:
- Calls
Self::new()
internally - Provides standard Rust default initialization pattern
Sources: src/lib.rs(L73 - L77)
Handler Registration
register_handler()
Registers an event handler for a specific index slot.
Signature:
#![allow(unused)] fn main() { pub fn register_handler(&self, idx: usize, handler: Handler) -> bool }
Parameters:
Parameter | Type | Description |
---|---|---|
idx | usize | Index of the handler slot (must be < N) |
handler | Handler | Function pointer to register |
Return Value:
true
- Registration successfulfalse
- Registration failed (index out of bounds or slot already occupied)
Behavior:
- Validates
idx < N
- Attempts atomic compare-and-exchange from
0
tohandler as usize
- Uses
Ordering::Acquire
for success andOrdering::Relaxed
for failure - Fails if slot is already occupied (non-zero value)
Atomic Operation Details:
sequenceDiagram participant Client as Client participant HandlerTable as HandlerTable participant AtomicSlotAtomicUsizeidx as AtomicSlot["AtomicUsize[idx]"] participant AtomicSlot as AtomicSlot Client ->> HandlerTable: "register_handler(idx, handler)" HandlerTable ->> HandlerTable: "Validate idx < N" HandlerTable ->> AtomicSlot: "compare_exchange(0, handler_ptr, Acquire, Relaxed)" alt "Slot is empty (0)" AtomicSlot -->> HandlerTable: "Ok(0)" HandlerTable -->> Client: "true" else "Slot occupied" AtomicSlot -->> HandlerTable: "Err(current_value)" HandlerTable -->> Client: "false" end
Sources: src/lib.rs(L26 - L37)
Handler Deregistration
unregister_handler()
Removes and returns the handler for a specific index slot.
Signature:
#![allow(unused)] fn main() { pub fn unregister_handler(&self, idx: usize) -> Option<Handler> }
Parameters:
Parameter | Type | Description |
---|---|---|
idx | usize | Index of the handler slot to unregister |
Return Value:
Some(Handler)
- Previously registered handlerNone
- No handler was registered or index out of bounds
Behavior:
- Validates
idx < N
- Performs atomic swap with
0
usingOrdering::Acquire
- If previous value was non-zero, transmutes back to
Handler
- Returns
None
if slot was already empty
Safety Considerations:
- Uses
unsafe { core::mem::transmute::<usize, fn()>(handler) }
for pointer conversion - Safe because only valid function pointers are stored via
register_handler
Sources: src/lib.rs(L39 - L52)
Event Handling
handle()
Executes the handler registered for a specific index.
Signature:
#![allow(unused)] fn main() { pub fn handle(&self, idx: usize) -> bool }
Parameters:
Parameter | Type | Description |
---|---|---|
idx | usize | Index of the event to handle |
Return Value:
true
- Event was handled (handler existed and was called)false
- No handler registered or index out of bounds
Behavior:
- Validates
idx < N
- Loads handler value atomically with
Ordering::Acquire
- If non-zero, transmutes to
Handler
and calls it - Handler execution is synchronous
Event Handling Flow:
flowchart TD Start["handle(idx)"] ValidateIdx["idx < N ?"] ReturnFalse1["return false"] LoadHandler["load(Ordering::Acquire)"] CheckHandler["handler != 0 ?"] ReturnFalse2["return false"] Transmute["unsafe transmute to Handler"] CallHandler["handler()"] ReturnTrue["return true"] CallHandler --> ReturnTrue CheckHandler --> ReturnFalse2 CheckHandler --> Transmute LoadHandler --> CheckHandler Start --> ValidateIdx Transmute --> CallHandler ValidateIdx --> LoadHandler ValidateIdx --> ReturnFalse1
Safety Considerations:
- Uses
unsafe { core::mem::transmute(handler) }
for pointer conversion - Safe because only valid function pointers can be stored in slots
Sources: src/lib.rs(L54 - L70)
Memory Ordering and Thread Safety
All atomic operations use Ordering::Acquire
for loads and successful compare-exchange operations, ensuring proper synchronization between threads.
Operation | Memory Ordering | Purpose |
---|---|---|
compare_exchangesuccess | Ordering::Acquire | Synchronize handler registration |
compare_exchangefailure | Ordering::Relaxed | No synchronization needed on failure |
swap | Ordering::Acquire | Synchronize handler removal |
load | Ordering::Acquire | Synchronize handler access |
Thread Safety Guarantees:
- Multiple threads can safely call any method concurrently
- Registration is atomic and will never corrupt existing handlers
- Handler execution is exclusive per slot but concurrent across slots
- No locks or blocking operations are used
Sources: src/lib.rs(L34 - L62)
Index Bounds Validation
All methods that accept an idx
parameter perform bounds checking against the compile-time constant N
.
Validation Pattern:
if idx >= N {
return false; // or None for unregister_handler
}
Characteristics:
- Bounds check occurs before any atomic operations
- Out-of-bounds access returns failure indicators rather than panicking
- Provides safe API even with invalid indices
Sources: src/lib.rs(L31 - L61)
Usage Examples
Relevant source files
This document provides practical examples demonstrating how to use the HandlerTable
API in real-world scenarios. It covers common usage patterns, integration strategies, and best practices for implementing lock-free event handling in no_std environments.
For detailed API documentation, see API Reference. For implementation details about the underlying atomic operations, see Implementation Details.
Basic Event Handler Registration
The fundamental usage pattern involves creating a static HandlerTable
, registering event handlers, and dispatching events as they occur.
Static Table Creation and Handler Registration
The basic pattern demonstrated in README.md(L11 - L31) shows:
Operation | Method | Purpose |
---|---|---|
Creation | HandlerTable::new() | Initialize empty handler table |
Registration | register_handler(idx, fn) | Associate handler function with event ID |
Dispatch | handle(idx) | Execute registered handler for event |
Cleanup | unregister_handler(idx) | Remove and retrieve handler function |
Sources: README.md(L11 - L31)
Event Registration Patterns
Single Event Handler Registration
The most common pattern involves registering individual handlers for specific event types. Each handler is a closure or function pointer that gets executed when the corresponding event occurs.
sequenceDiagram participant EventSource as "Event Source" participant HandlerTableN as "HandlerTable<N>" participant AtomicUsizeidx as "AtomicUsize[idx]" participant EventHandlerFn as "Event Handler Fn" EventSource ->> HandlerTableN: "register_handler(0, closure)" HandlerTableN ->> AtomicUsizeidx: "compare_exchange(0, fn_ptr)" alt "Registration Success" AtomicUsizeidx -->> HandlerTableN: "Ok(0)" HandlerTableN -->> EventSource: "true" else "Slot Occupied" AtomicUsizeidx -->> HandlerTableN: "Err(existing_ptr)" HandlerTableN -->> EventSource: "false" end Note over EventSource,EventHandlerFn: "Later: Event Dispatch" EventSource ->> HandlerTableN: "handle(0)" HandlerTableN ->> AtomicUsizeidx: "load(Ordering::Acquire)" AtomicUsizeidx -->> HandlerTableN: "fn_ptr" HandlerTableN ->> EventHandlerFn: "transmute + call()" EventHandlerFn -->> HandlerTableN: "execution complete" HandlerTableN -->> EventSource: "true"
Sources: README.md(L16 - L21)
Multiple Handler Registration
For systems handling multiple event types, handlers are typically registered during initialization:
flowchart TD subgraph subGraph2["Handler Functions"] TimerFn["timer_interrupt_handler"] IOFn["io_completion_handler"] UserFn["user_signal_handler"] end subgraph subGraph1["HandlerTable Operations"] Reg0["register_handler(0, timer_fn)"] Reg1["register_handler(1, io_fn)"] Reg2["register_handler(2, user_fn)"] end subgraph subGraph0["System Initialization Phase"] Init["System Init"] Timer["Timer Handler Setup"] IO["I/O Handler Setup"] User["User Event Setup"] end IO --> Reg1 Init --> IO Init --> Timer Init --> User Reg0 --> TimerFn Reg1 --> IOFn Reg2 --> UserFn Timer --> Reg0 User --> Reg2
Sources: README.md(L16 - L21)
Event Handling Scenarios
Successful Event Dispatch
When an event occurs and a handler is registered, the handle
method executes the associated function and returns true
:
flowchart TD subgraph subGraph0["Example from README"] Ex1["handle(0) -> true"] Ex2["handle(2) -> false"] end Event["Event Occurs"] Handle["handle(event_id)"] Check["Check AtomicUsize[event_id]"] Found["Handler Found?"] Execute["Execute Handler Function"] ReturnFalse["Return false"] ReturnTrue["Return true"] Check --> Found Event --> Handle Execute --> ReturnTrue Found --> Execute Found --> ReturnFalse Handle --> Check
The example in README.md(L23 - L24) demonstrates both successful dispatch (handle(0)
returns true
) and missing handler scenarios (handle(2)
returns false
).
Sources: README.md(L23 - L24)
Handler Retrieval and Cleanup
The unregister_handler
method provides atomic removal and retrieval of handler functions:
flowchart TD subgraph subGraph2["Example Calls"] Unreg1["unregister_handler(2) -> None"] Unreg2["unregister_handler(1) -> Some(fn)"] end subgraph subGraph1["Return Values"] Some["Some(handler_fn)"] None["None"] end subgraph subGraph0["Unregistration Process"] Call["unregister_handler(idx)"] Swap["atomic_swap(0)"] Check["Check Previous Value"] Result["Option"] end Call --> Swap Check --> Result None --> Unreg1 Result --> None Result --> Some Some --> Unreg2 Swap --> Check
The pattern shown in README.md(L26 - L28) demonstrates retrieving a previously registered handler function, which can then be called directly.
Sources: README.md(L26 - L30)
Advanced Usage Patterns
Concurrent Handler Management
In multi-threaded environments, multiple threads can safely register, handle, and unregister events without synchronization primitives:
flowchart TD subgraph subGraph2["Memory Ordering"] Acquire["Acquire Ordering"] Release["Release Ordering"] Consistency["Memory Consistency"] end subgraph subGraph1["Atomic Operations Layer"] CAS["compare_exchange"] Load["load(Acquire)"] Swap["swap(Acquire)"] end subgraph subGraph0["Thread Safety Guarantees"] T1["Thread 1: register_handler"] T2["Thread 2: handle"] T3["Thread 3: unregister_handler"] end Acquire --> Consistency CAS --> Release Load --> Acquire Release --> Consistency Swap --> Acquire T1 --> CAS T2 --> Load T3 --> Swap
Sources: README.md(L14)
Static vs Dynamic Handler Tables
The compile-time sized HandlerTable<N>
enables static allocation suitable for no_std environments:
Approach | Declaration | Use Case |
---|---|---|
Static Global | static TABLE: HandlerTable<8> | System-wide event handling |
Local Instance | let table = HandlerTable::<16>::new() | Component-specific events |
Const Generic | HandlerTable<MAX_EVENTS> | Configurable event capacity |
The static approach shown in README.md(L14) is preferred for kernel-level integration where heap allocation is unavailable.
Sources: README.md(L14)
Integration Examples
ArceOS Kernel Integration
In the ArceOS operating system context, HandlerTable
serves as the central event dispatch mechanism:
flowchart TD subgraph subGraph2["Event Dispatch Flow"] Dispatch["event_dispatch(id)"] Handle["TABLE.handle(id)"] Execute["handler_function()"] end subgraph subGraph1["HandlerTable Integration"] TABLE["static KERNEL_EVENTS: HandlerTable<32>"] IRQ["IRQ_HANDLERS[0..15]"] TIMER["TIMER_EVENTS[16..23]"] SCHED["SCHED_EVENTS[24..31]"] end subgraph subGraph0["ArceOS Kernel Components"] Interrupt["Interrupt Controller"] Timer["Timer Subsystem"] Scheduler["Task Scheduler"] Syscall["System Call Handler"] end Dispatch --> Handle Handle --> Execute IRQ --> TABLE Interrupt --> IRQ SCHED --> TABLE Scheduler --> SCHED Syscall --> TABLE TABLE --> Dispatch TIMER --> TABLE Timer --> TIMER
Sources: README.md(L7)
Device Driver Event Handling
Device drivers can use dedicated handler tables for managing I/O completion events:
flowchart TD subgraph subGraph2["Event Types"] Complete["DMA_COMPLETE"] Error["DMA_ERROR"] Timeout["DMA_TIMEOUT"] end subgraph subGraph1["Event Management"] DriverTable["HandlerTable"] CompletionFn["io_completion_handler"] ErrorFn["io_error_handler"] end subgraph subGraph0["Driver Architecture"] Driver["Device Driver"] DMA["DMA Controller"] Buffer["I/O Buffers"] end Complete --> CompletionFn CompletionFn --> Buffer DMA --> Complete DMA --> Error DMA --> Timeout Driver --> DriverTable Error --> ErrorFn ErrorFn --> Buffer Timeout --> ErrorFn
This pattern allows drivers to maintain separate event handling contexts while leveraging the same lock-free infrastructure.
Sources: README.md(L7)
Implementation Details
Relevant source files
This document provides a deep technical analysis of the HandlerTable<N>
implementation, covering the internal architecture, atomic operations strategy, and lock-free design patterns. It explains how function pointers are stored atomically, the memory ordering guarantees, and the safety mechanisms that enable concurrent access without locks.
For high-level usage information, see User Guide. For atomic operations theory, see Atomic Operations.
Core Data Structure
The HandlerTable<N>
struct implements a fixed-size, lock-free event handler registry using a compile-time constant generic parameter N
to define the maximum number of event handlers.
HandlerTable Structure
flowchart TD subgraph subGraph2["Value Interpretation"] Zero["0 = Empty Slot"] NonZero["Non-zero = Function Pointer"] Transmute["unsafe transmute"] end subgraph subGraph1["Handler Storage"] H0["handlers[0]: AtomicUsize"] H1["handlers[1]: AtomicUsize"] HN["handlers[N-1]: AtomicUsize"] end subgraph HandlerTable<N>["HandlerTable"] HT["HandlerTable"] Array["handlers: [AtomicUsize; N]"] end Array --> H0 Array --> H1 Array --> HN H0 --> Zero H1 --> NonZero HN --> Zero HT --> Array NonZero --> Transmute
The fundamental design stores function pointers as usize
values within AtomicUsize
containers. An empty slot is represented by the value 0
, while registered handlers store the function pointer cast to usize
. This design enables atomic operations on what are essentially function pointer references.
Sources: src/lib.rs(L14 - L16) src/lib.rs(L21 - L23)
Initialization Pattern
The constructor uses a const-evaluated array initialization pattern that ensures all atomic slots begin in the empty state:
flowchart TD subgraph subGraph1["Memory Layout"] Slot0["AtomicUsize(0)"] Slot1["AtomicUsize(0)"] SlotN["AtomicUsize(0)"] end subgraph subGraph0["new() Construction"] New["HandlerTable::new()"] ConstExpr["const { AtomicUsize::new(0) }"] ArrayInit["[...; N]"] end ArrayInit --> Slot0 ArrayInit --> Slot1 ArrayInit --> SlotN ConstExpr --> ArrayInit New --> ConstExpr
The const { AtomicUsize::new(0) }
expression leverages Rust's const evaluation to initialize each array element at compile time, ensuring zero-cost initialization and immediate readiness for concurrent access.
Sources: src/lib.rs(L19 - L24)
Atomic Operations Strategy
The implementation employs three distinct atomic operations, each optimized for its specific use case within the lock-free protocol.
Operation Mapping
Method | Atomic Operation | Success Condition | Failure Behavior |
---|---|---|---|
register_handler | compare_exchange | Slot was empty (0) | Returns false |
unregister_handler | swap | Always succeeds | Returns None if empty |
handle | load | Always succeeds | Returns false if empty |
Registration Protocol
sequenceDiagram participant Client as Client participant HandlerTable as HandlerTable participant AtomicSlothandlersidx as AtomicSlot["handlers[idx]"] participant AtomicSlot as AtomicSlot Client ->> HandlerTable: "register_handler(idx, handler)" HandlerTable ->> HandlerTable: "Check idx < N" HandlerTable ->> AtomicSlot: "compare_exchange(0, handler as usize, Acquire, Relaxed)" alt "Slot was empty" AtomicSlot -->> HandlerTable: "Ok(0)" HandlerTable -->> Client: "true" else "Slot occupied" AtomicSlot -->> HandlerTable: "Err(current_value)" HandlerTable -->> Client: "false" end
The compare_exchange
operation provides atomic test-and-set functionality, ensuring that only one thread can successfully register a handler for any given index. The operation uses Ordering::Acquire
for success to establish happens-before relationships with subsequent operations.
Sources: src/lib.rs(L30 - L37)
Deregistration Protocol
The swap
operation unconditionally exchanges the current value with 0
, returning the previous value. This ensures atomic removal regardless of the current state:
flowchart TD subgraph subGraph0["unregister_handler Flow"] Input["unregister_handler(idx)"] BoundsCheck["idx < N ?"] SwapOp["swap(0, Ordering::Acquire)"] ReturnNone["None"] CheckResult["handler != 0 ?"] Transmute["unsafe transmute"] ReturnSome["Some(handler)"] end BoundsCheck --> ReturnNone BoundsCheck --> SwapOp CheckResult --> ReturnNone CheckResult --> Transmute Input --> BoundsCheck SwapOp --> CheckResult Transmute --> ReturnSome
Sources: src/lib.rs(L42 - L52)
Function Pointer Storage and Safety
The core challenge in this implementation is safely storing and retrieving function pointers through atomic operations. Since AtomicPtr<fn()>
doesn't exist in stable Rust, the implementation uses AtomicUsize
with unsafe transmutation.
Pointer-to-Integer Conversion
flowchart TD subgraph subGraph1["Safety Invariants"] NonNull["Non-null Function Pointers"] ValidCode["Valid Code Addresses"] SameLifetime["Pointer Lifetime ≥ Table Lifetime"] end subgraph subGraph0["Storage Mechanism"] FnPtr["fn() - Function Pointer"] AsUsize["handler as usize"] AtomicStore["AtomicUsize::store/load"] TransmuteBack["transmute"] CallHandler["handler()"] end AsUsize --> AtomicStore AtomicStore --> TransmuteBack FnPtr --> AsUsize FnPtr --> NonNull FnPtr --> SameLifetime FnPtr --> ValidCode TransmuteBack --> CallHandler
The implementation relies on several critical safety assumptions:
- Function pointers are never null (guaranteed by Rust's type system)
- Function pointers have the same size as
usize
on the target platform - Function addresses remain valid for the lifetime of the
HandlerTable
- The transmutation between
usize
andfn()
preserves the pointer value
Sources: src/lib.rs(L48) src/lib.rs(L64)
Event Handling Execution
The handle
method demonstrates the complete pointer recovery and execution sequence:
flowchart TD subgraph subGraph0["handle Method Flow"] LoadPtr["load(Ordering::Acquire)"] CheckZero["handler == 0 ?"] Transmute["unsafe transmute(handler)"] ReturnFalse["return false"] Execute["handler()"] ReturnTrue["return true"] end CheckZero --> ReturnFalse CheckZero --> Transmute Execute --> ReturnTrue LoadPtr --> CheckZero Transmute --> Execute
The atomic load with Ordering::Acquire
ensures that any writes to memory performed by the registering thread before the compare_exchange
are visible to the handling thread.
Sources: src/lib.rs(L58 - L70)
Memory Ordering Guarantees
The implementation uses a carefully chosen set of memory orderings to balance performance with correctness guarantees.
Ordering Strategy
Operation | Success Ordering | Failure Ordering | Rationale |
---|---|---|---|
compare_exchange | Acquire | Relaxed | Synchronize with prior writes |
swap | Acquire | N/A | Ensure visibility of handler execution |
load | Acquire | N/A | See registration writes |
Happens-Before Relationships
flowchart TD subgraph subGraph1["Thread B - Execution"] HandleCall["handle(idx)"] LoadHandler["load(Ordering::Acquire)"] ExecuteHandler["handler()"] end subgraph subGraph0["Thread A - Registration"] SetupHandler["Setup handler environment"] RegisterCall["register_handler(idx, fn)"] CompareExchange["compare_exchange(..., Acquire, Relaxed)"] end subgraph subGraph2["Memory Ordering Chain"] HappensBefore["Happens-Before Edge"] end CompareExchange --> LoadHandler LoadHandler --> ExecuteHandler RegisterCall --> CompareExchange SetupHandler --> RegisterCall
The Acquire
ordering on successful registration creates a happens-before relationship with the Acquire
load during event handling, ensuring that any memory operations completed before registration are visible during handler execution.
Sources: src/lib.rs(L35) src/lib.rs(L46) src/lib.rs(L62)
Lock-free Design Patterns
The HandlerTable
implementation demonstrates several fundamental lock-free programming patterns adapted for event handling scenarios.
Compare-and-Swap Pattern
The registration mechanism implements the classic compare-and-swap pattern for atomic updates:
flowchart TD subgraph subGraph0["CAS Loop Pattern"] ReadCurrent["Read Current Value"] PrepareNew["Prepare New Value"] CASAttempt["Compare-and-Swap"] Success["Success"] Retry["Retry"] end CASAttempt --> Retry CASAttempt --> Success PrepareNew --> CASAttempt ReadCurrent --> PrepareNew Retry --> ReadCurrent
However, the handler table simplifies this pattern by only allowing transitions from empty (0) to occupied (non-zero), eliminating the need for retry loops.
ABA Problem Avoidance
The design inherently avoids the ABA problem through its state transition model:
From State | To State | Operation | ABA Risk |
---|---|---|---|
Empty (0) | Handler (ptr) | register_handler | None - single transition |
Handler (ptr) | Empty (0) | unregister_handler | None - swap is atomic |
Any | Any | handle | None - read-only |
The absence of complex state transitions and the use of swap for deregistration eliminate scenarios where the ABA problem could manifest.
Sources: src/lib.rs(L30 - L37) src/lib.rs(L42 - L52) src/lib.rs(L58 - L70)
Atomic Operations
Relevant source files
This document explains the atomic operations that form the foundation of the HandlerTable
's lock-free design. It covers the specific atomic primitives used, memory ordering guarantees, and how these operations ensure thread safety without traditional locking mechanisms.
For information about the overall implementation architecture and memory safety aspects, see Memory Layout and Safety. For practical usage of the lock-free API, see API Reference.
Atomic Data Structure Foundation
The HandlerTable
uses an array of AtomicUsize
values as its core storage mechanism. Each slot in the array can atomically hold either a null value (0) or a function pointer cast to usize
.
Core Atomic Array Structure
flowchart TD subgraph States["Slot States"] Empty["Empty: 0"] Occupied["Occupied: handler as usize"] end subgraph AtomicSlots["Atomic Storage Slots"] Slot0["AtomicUsize[0]Value: 0 or fn_ptr"] Slot1["AtomicUsize[1]Value: 0 or fn_ptr"] SlotN["AtomicUsize[N-1]Value: 0 or fn_ptr"] end subgraph HandlerTable["HandlerTable<N>"] Array["handlers: [AtomicUsize; N]"] end Array --> Slot0 Array --> Slot1 Array --> SlotN Slot0 --> Empty Slot0 --> Occupied Slot1 --> Empty Slot1 --> Occupied SlotN --> Empty SlotN --> Occupied
Sources: src/lib.rs(L14 - L16) src/lib.rs(L21 - L23)
The atomic array is initialized with zero values using const generics, ensuring compile-time allocation without heap usage. Each AtomicUsize
slot represents either an empty handler position (value 0) or contains a function pointer cast to usize
.
Core Atomic Operations
The HandlerTable
implements three primary atomic operations that provide lock-free access to the handler storage.
Compare-Exchange Operation
The compare_exchange
operation is used in register_handler
to atomically install a new handler only if the slot is currently empty.
sequenceDiagram participant ClientThread as "Client Thread" participant AtomicUsizeidx as "AtomicUsize[idx]" participant MemorySystem as "Memory System" ClientThread ->> AtomicUsizeidx: "compare_exchange(0, handler_ptr, Acquire, Relaxed)" AtomicUsizeidx ->> MemorySystem: "Check current value == 0" alt Current value is 0 MemorySystem ->> AtomicUsizeidx: "Replace with handler_ptr" AtomicUsizeidx ->> ClientThread: "Ok(0)" Note over ClientThread: "Registration successful" else Current value != 0 MemorySystem ->> AtomicUsizeidx: "Keep current value" AtomicUsizeidx ->> ClientThread: "Err(current_value)" Note over ClientThread: "Registration failed - slot occupied" end
Sources: src/lib.rs(L34 - L36)
The operation uses Ordering::Acquire
for success and Ordering::Relaxed
for failure, ensuring proper synchronization when a handler is successfully installed.
Atomic Swap Operation
The swap
operation in unregister_handler
atomically replaces the current handler with zero and returns the previous value.
flowchart TD Start["swap(0, Ordering::Acquire)"] Read["Atomically read current value"] Write["Atomically write 0"] Check["Current value != 0?"] Convert["transmute<usize, fn()>(value)"] ReturnSome["Return Some(handler)"] ReturnNone["Return None"] Check --> Convert Check --> ReturnNone Convert --> ReturnSome Read --> Write Start --> Read Write --> Check
Sources: src/lib.rs(L46) src/lib.rs(L47 - L51)
Atomic Load Operation
The load
operation in handle
reads the current handler value without modification, using acquire ordering to ensure proper synchronization.
flowchart TD subgraph Results["Possible Results"] Success["Return true"] NoHandler["Return false"] end subgraph LoadOperation["Atomic Load Process"] Load["load(Ordering::Acquire)"] Check["Check value != 0"] Transmute["unsafe transmute<usize, Handler>"] Call["handler()"] end Call --> Success Check --> NoHandler Check --> Transmute Load --> Check Transmute --> Call
Sources: src/lib.rs(L62) src/lib.rs(L63 - L66)
Memory Ordering Guarantees
The atomic operations use specific memory ordering to ensure correct synchronization across threads while minimizing performance overhead.
Ordering Usage Patterns
Operation | Success Ordering | Failure Ordering | Purpose |
---|---|---|---|
compare_exchange | Acquire | Relaxed | Synchronize on successful registration |
swap | Acquire | N/A | Synchronize when removing handler |
load | Acquire | N/A | Synchronize when reading handler |
Sources: src/lib.rs(L35) src/lib.rs(L46) src/lib.rs(L62)
Acquire Ordering Semantics
flowchart TD subgraph Ordering["Memory Ordering Guarantee"] Sync["Acquire ensures visibilityof prior writes"] end subgraph Thread2["Thread 2 - Handle Event"] T2_Load["load(Acquire)"] T2_Read["Read handler data"] T2_Call["Call handler"] end subgraph Thread1["Thread 1 - Register Handler"] T1_Write["Write handler data"] T1_CAS["compare_exchange(..., Acquire, ...)"] end T1_CAS --> Sync T1_CAS --> T2_Load T1_Write --> T1_CAS T2_Load --> Sync T2_Load --> T2_Read T2_Read --> T2_Call
Sources: src/lib.rs(L35) src/lib.rs(L62)
The Acquire
ordering ensures that when a thread successfully reads a non-zero handler value, it observes all memory writes that happened-before the handler was stored.
Lock-Free Properties
The atomic operations provide several lock-free guarantees essential for kernel-level event handling.
Wait-Free Registration
stateDiagram-v2 [*] --> Attempting : "register_handler(idx, fn)" Attempting --> Success : "CAS succeeds (slot was 0)" Attempting --> Failure : "CAS fails (slot occupied)" Success --> [*] : "Return true" Failure --> [*] : "Return false" note left of Success : ['"Single atomic operation"'] note left of Failure : ['"No retry loops"']
Sources: src/lib.rs(L30 - L37)
The registration operation is wait-free - it completes in a bounded number of steps regardless of other thread activity. Either the compare-exchange succeeds immediately, or it fails immediately if another handler is already registered.
Non-Blocking Event Handling
The handle
operation never blocks and provides consistent performance characteristics:
- Constant Time: Single atomic load operation
- No Contention: Multiple threads can simultaneously handle different events
- Real-Time Safe: No unbounded waiting or priority inversion
Sources: src/lib.rs(L58 - L70)
Function Pointer Storage Mechanism
The atomic operations work on usize
values that represent function pointers, requiring careful handling to maintain type safety.
Pointer-to-Integer Conversion
flowchart TD subgraph Output["Output Types"] Recovered["Handler (fn())"] end subgraph Retrieval["Type Recovery"] Load["AtomicUsize::load"] Transmute["unsafe transmute<usize, fn()>"] end subgraph Storage["Atomic Storage"] Atomic["AtomicUsize value"] end subgraph Conversion["Type Conversion"] Cast["handler as usize"] Store["AtomicUsize::store"] end subgraph Input["Input Types"] Handler["Handler (fn())"] end Atomic --> Load Cast --> Store Handler --> Cast Load --> Transmute Store --> Atomic Transmute --> Recovered
Sources: src/lib.rs(L35) src/lib.rs(L48) src/lib.rs(L64)
The conversion relies on the platform guarantee that function pointers can be safely cast to usize
and back. The unsafe
transmute operations are necessary because the atomic types only work with integer values, but the conversions preserve the original function pointer values exactly.
Memory Layout and Safety
Relevant source files
This document explains the memory layout of the HandlerTable
structure and the safety mechanisms that ensure correct lock-free operation. It covers how function pointers are stored atomically, memory ordering guarantees, and the safety implications of pointer transmutation operations.
For information about the specific atomic operations used, see Atomic Operations. For general API usage patterns, see API Reference.
Memory Layout Overview
The HandlerTable<N>
structure has a simple but carefully designed memory layout that enables lock-free concurrent access to event handlers.
HandlerTable Structure Layout
flowchart TD subgraph MemoryValues["Stored Values"] Zero["0 = Empty Slot"] FnPtr["fn() as usizeFunction Pointer"] end subgraph HandlerTableStruct["HandlerTable<N> Memory Layout"] ArrayField["handlers: [AtomicUsize; N]"] subgraph AtomicArray["AtomicUsize Array"] Slot0["AtomicUsize[0]8 bytes"] Slot1["AtomicUsize[1]8 bytes"] SlotDots["..."] SlotN["AtomicUsize[N-1]8 bytes"] end end Slot0 --> Zero Slot1 --> FnPtr SlotN --> Zero
HandlerTable Memory Characteristics
Property | Value | Rationale |
---|---|---|
Size | N * size_of:: | Fixed compile-time size |
Alignment | align_of:: | Platform atomic alignment |
Initialization | All slots = 0 | Zero represents empty slot |
Mutability | Atomic operations only | Lock-free concurrency |
Sources: src/lib.rs(L14 - L16) src/lib.rs(L20 - L24)
Function Pointer Storage Mechanism
The core safety challenge is storing function pointers (fn()
) in atomic integers (AtomicUsize
) while maintaining type safety and memory safety.
Pointer Transmutation Process
sequenceDiagram participant ApplicationCode as "Application Code" participant HandlerTable as "HandlerTable" participant AtomicUsizeidx as "AtomicUsize[idx]" participant RawMemory as "Raw Memory" Note over ApplicationCode,RawMemory: Registration: fn() → usize ApplicationCode ->> HandlerTable: "register_handler(idx, handler_fn)" HandlerTable ->> HandlerTable: "handler as usize" Note over AtomicUsizeidx,HandlerTable: Transmute fn() to usize HandlerTable ->> AtomicUsizeidx: "compare_exchange(0, usize_value)" AtomicUsizeidx ->> RawMemory: "Atomic store" Note over ApplicationCode,RawMemory: Execution: usize → fn() ApplicationCode ->> HandlerTable: "handle(idx)" HandlerTable ->> AtomicUsizeidx: "load(Ordering::Acquire)" AtomicUsizeidx ->> RawMemory: "Atomic load" RawMemory -->> HandlerTable: "usize_value" HandlerTable ->> HandlerTable: "transmute::<usize, fn()>(usize_value)" Note over AtomicUsizeidx,HandlerTable: Transmute usize to fn() HandlerTable ->> HandlerTable: "handler()" Note over AtomicUsizeidx,HandlerTable: Call function
Transmutation Safety Requirements
The unsafe transmute operations in the codebase rely on several critical safety invariants:
- Size Compatibility:
size_of::<fn()>() == size_of::<usize>()
on the target platform - Representation Compatibility: Function pointers can be safely cast to/from usize
- Lifetime Management: Function pointers remain valid for the lifetime of storage
- Non-zero Constraint: Valid function pointers are never zero (allowing zero as "empty")
Sources: src/lib.rs(L35) src/lib.rs(L48) src/lib.rs(L64)
Memory Ordering Guarantees
The HandlerTable
uses specific memory ordering constraints to ensure correct synchronization without locks.
Ordering Operations Mapping
flowchart TD subgraph Ordering["Memory Ordering"] Acquire["Ordering::Acquire"] Relaxed["Ordering::Relaxed"] end subgraph AtomicOps["Atomic Operations"] CAS["compare_exchange()"] Load["load()"] Swap["swap()"] end subgraph Operations["HandlerTable Operations"] Register["register_handler()"] Handle["handle()"] Unregister["unregister_handler()"] end CAS --> Acquire CAS --> Relaxed Handle --> Load Load --> Acquire Register --> CAS Swap --> Acquire Unregister --> Swap
Memory Ordering Analysis
Operation | Success Ordering | Failure Ordering | Purpose |
---|---|---|---|
compare_exchange | Acquire | Relaxed | Synchronize handler installation |
load | Acquire | N/A | Ensure handler visibility |
swap | Acquire | N/A | Synchronize handler removal |
The Acquire
ordering ensures that:
- Handler installations are visible to all threads before the operation completes
- Handler loads see the most recent installed handler
- Handler removals synchronize with ongoing executions
Sources: src/lib.rs(L35) src/lib.rs(L46) src/lib.rs(L62)
Zero Value Semantics
The choice of zero as the "empty slot" marker has important memory safety implications.
Zero Representation Safety
flowchart TD subgraph SafetyChecks["Runtime Safety Checks"] CheckNonZero["if handler != 0"] ReturnSome["Some(handler)"] ReturnNone["None"] end subgraph ZeroSemantics["Zero Value Semantics"] EmptySlot["0 = Empty Slot"] ValidPtr["Non-zero = Valid fn()"] NullPtr["Null Function Pointer = 0"] end subgraph Initialization["Array Initialization"] ConstNew["const fn new()"] AtomicNew["AtomicUsize::new(0)"] ArrayInit["[const { ... }; N]"] end ArrayInit --> EmptySlot AtomicNew --> ArrayInit CheckNonZero --> ReturnNone CheckNonZero --> ReturnSome ConstNew --> AtomicNew EmptySlot --> CheckNonZero EmptySlot --> ValidPtr ValidPtr --> NullPtr
Zero Value Safety Properties
- Platform Guarantee: On all supported platforms, null function pointers have value 0
- Initialization Safety:
AtomicUsize::new(0)
is const and safe - Detection Safety: Zero checks reliably distinguish empty from occupied slots
- Transmute Safety: Zero values are never transmuted to function pointers
Sources: src/lib.rs(L22) src/lib.rs(L47 - L51) src/lib.rs(L63 - L68)
Compile-time Safety Constraints
The generic constant N
parameter provides compile-time memory safety guarantees.
Fixed-Size Array Benefits
flowchart TD subgraph MemoryLayout["Memory Layout Properties"] Contiguous["Contiguous Memory"] CacheEfficient["Cache-Friendly Access"] NoFragmentation["No Heap Fragmentation"] FixedSize["Fixed Memory Footprint"] end subgraph RuntimeSafety["Runtime Bounds Safety"] BoundsCheck["if idx >= N"] EarlyReturn["return false/None"] SafeAccess["handlers[idx]"] end subgraph CompileTime["Compile-time Guarantees"] ConstN["const N: usize"] FixedArray["[AtomicUsize; N]"] StackAlloc["Stack Allocation"] NoHeap["No Heap Required"] end BoundsCheck --> EarlyReturn BoundsCheck --> SafeAccess ConstN --> FixedArray Contiguous --> CacheEfficient FixedArray --> BoundsCheck FixedArray --> FixedSize FixedArray --> StackAlloc NoHeap --> NoFragmentation StackAlloc --> Contiguous StackAlloc --> NoHeap
Compile-time Safety Benefits
- No Dynamic Allocation: Array size known at compile time, enabling
no_std
compatibility - Bounds Safety: Array access bounds are checked explicitly before atomic operations
- Memory Predictability: Fixed memory footprint enables real-time system usage
- Cache Locality: Contiguous array layout optimizes memory access patterns
Sources: src/lib.rs(L14 - L16) src/lib.rs(L31 - L33) src/lib.rs(L43 - L45) src/lib.rs(L59 - L61)
Unsafe Code Safety Analysis
The HandlerTable
contains two unsafe
blocks that require careful safety analysis.
Transmute Safety Invariants
flowchart TD subgraph Verification["Safety Verification"] CompileTime["Compile-time size check"] RuntimeCheck["Runtime zero check"] SourceGuarantee["Only valid fn() stored"] StaticLifetime["Static function lifetime"] end subgraph SafetyConditions["Required Safety Conditions"] SizeMatch["sizeof(fn()) == sizeof(usize)"] ValidPtr["Stored value was valid fn()"] NonZero["Value != 0 before transmute"] Lifetime["Function still valid"] end subgraph UnsafeBlocks["Unsafe Code Locations"] Unregister["unregister_handler() line 48"] Handle["handle() line 64"] end Lifetime --> StaticLifetime NonZero --> RuntimeCheck SizeMatch --> CompileTime ValidPtr --> SourceGuarantee
Safety Argument Summary
- Size Safety: Function pointers and
usize
have identical size on all target platforms - Value Safety: Only valid function pointers (converted via
as usize
) are stored - Zero Safety: Runtime checks prevent transmuting zero values
- Lifetime Safety: Event handlers typically have static lifetime in kernel contexts
The unsafe transmute operations are sound because they only reverse a previous safe cast operation under controlled conditions.
Sources: src/lib.rs(L48) src/lib.rs(L64)
Development Guide
Relevant source files
This document provides comprehensive information for developers who want to build, test, or contribute to the handler_table
crate. It covers local development setup, testing procedures, and the automated CI/CD pipeline that ensures code quality across multiple target architectures.
For information about using the HandlerTable API, see User Guide. For implementation details and internal architecture, see Implementation Details.
Development Environment Setup
The handler_table
crate requires a Rust nightly toolchain with specific components and target platforms for comprehensive development and testing. The development workflow supports both local development on x86_64-unknown-linux-gnu
and cross-compilation for embedded no_std
targets.
Required Toolchain Configuration
flowchart TD Toolchain["nightly toolchain"] Components["Required Components"] Targets["Target Platforms"] RustSrc["rust-src"] Clippy["clippy"] Rustfmt["rustfmt"] LinuxGnu["x86_64-unknown-linux-gnu"] BareX86["x86_64-unknown-none"] RiscV["riscv64gc-unknown-none-elf"] ARM64["aarch64-unknown-none-softfloat"] TestExecution["Unit Test Execution"] BuildOnly["Build Verification"] ARM64 --> BuildOnly BareX86 --> BuildOnly Components --> Clippy Components --> RustSrc Components --> Rustfmt LinuxGnu --> TestExecution RiscV --> BuildOnly Targets --> ARM64 Targets --> BareX86 Targets --> LinuxGnu Targets --> RiscV Toolchain --> Components Toolchain --> Targets
Development Environment Commands
The following commands establish the required development environment:
- Install nightly toolchain:
rustup toolchain install nightly
- Add required components:
rustup component add rust-src clippy rustfmt
- Add target platforms:
rustup target add x86_64-unknown-none riscv64gc-unknown-none-elf aarch64-unknown-none-softfloat
Sources: .github/workflows/ci.yml(L15 - L19)
Build and Test Workflow
The development workflow follows a structured approach ensuring code quality through formatting checks, linting, building, and testing across all supported target architectures.
Local Development Commands
flowchart TD FormatCheck["cargo fmt --all -- --check"] ClippyLint["cargo clippy --target TARGET --all-features"] Build["cargo build --target TARGET --all-features"] Test["cargo test --target x86_64-unknown-linux-gnu -- --nocapture"] Success["Development Ready"] CrossCompile["Cross-compilation Verified"] Build --> CrossCompile Build --> Test ClippyLint --> Build FormatCheck --> ClippyLint Test --> Success
Command Details
Command Purpose | Command | Target Scope |
---|---|---|
Format verification | cargo fmt --all -- --check | All source files |
Static analysis | cargo clippy --target $TARGET --all-features | Per-target analysis |
Build verification | cargo build --target $TARGET --all-features | All target platforms |
Unit test execution | cargo test --target x86_64-unknown-linux-gnu -- --nocapture | Linux target only |
The build process verifies compilation across all target architectures, but unit tests execute only on x86_64-unknown-linux-gnu
due to the embedded nature of other targets.
Sources: .github/workflows/ci.yml(L22 - L30)
CI/CD Pipeline Architecture
The automated pipeline implements a comprehensive verification strategy using GitHub Actions with matrix builds across multiple Rust toolchains and target architectures.
Pipeline Jobs and Dependencies
flowchart TD subgraph subGraph1["Documentation Job"] DocJob["doc"] DocBuild["cargo doc --no-deps --all-features"] GHPages["GitHub Pages Deployment"] end subgraph subGraph0["CI Job Matrix"] Job1["ci-x86_64-unknown-linux-gnu"] Job2["ci-x86_64-unknown-none"] Job3["ci-riscv64gc-unknown-none-elf"] Job4["ci-aarch64-unknown-none-softfloat"] end Trigger["Push/PR Events"] UnitTests["Unit Test Execution"] BuildVerify["Build Verification"] DocBuild --> GHPages DocJob --> DocBuild Job1 --> UnitTests Job2 --> BuildVerify Job3 --> BuildVerify Job4 --> BuildVerify Trigger --> DocJob Trigger --> Job1 Trigger --> Job2 Trigger --> Job3 Trigger --> Job4
Matrix Build Configuration
The CI pipeline uses a fail-fast strategy set to false
, ensuring all target architectures complete their builds even if one fails. Each matrix job executes the complete verification workflow for its assigned target.
Matrix Dimension | Values |
---|---|
rust-toolchain | nightly |
targets | x86_64-unknown-linux-gnu,x86_64-unknown-none,riscv64gc-unknown-none-elf,aarch64-unknown-none-softfloat |
Sources: .github/workflows/ci.yml(L8 - L12)
Documentation Generation and Deployment
The documentation pipeline generates API documentation and deploys it to GitHub Pages with automatic index page generation and dependency exclusion.
flowchart TD DocTrigger["Default Branch Push"] DocBuild["cargo doc --no-deps --all-features"] IndexGen["Generate index.html redirect"] Deploy["JamesIves/github-pages-deploy-action@v4"] GHPages["GitHub Pages Site"] RustDocFlags["RUSTDOCFLAGS validation"] BrokenLinks["-D rustdoc::broken_intra_doc_links"] MissingDocs["-D missing-docs"] Deploy --> GHPages DocBuild --> IndexGen DocBuild --> RustDocFlags DocTrigger --> DocBuild IndexGen --> Deploy RustDocFlags --> BrokenLinks RustDocFlags --> MissingDocs
Documentation Environment Variables
RUSTDOCFLAGS
:-D rustdoc::broken_intra_doc_links -D missing-docs
default-branch
: Dynamic branch reference for deployment conditions
The documentation deployment occurs only on pushes to the default branch, with automatic index page generation using the crate name extracted from cargo tree
output.
Sources: .github/workflows/ci.yml(L32 - L55)
Contributing Workflow
Code Quality Requirements
All contributions must pass the complete CI pipeline verification, including:
- Format Compliance: Code must conform to
rustfmt
standards - Lint Compliance: No
clippy
warnings across all target architectures - Build Success: Successful compilation on all supported targets
- Test Passage: Unit tests must pass on
x86_64-unknown-linux-gnu
- Documentation Standards: No broken intra-doc links or missing documentation
Target Architecture Support
The crate maintains compatibility across diverse target architectures representing different use cases:
Target | Purpose | Test Coverage |
---|---|---|
x86_64-unknown-linux-gnu | Development and testing | Full unit tests |
x86_64-unknown-none | Bare metal x86_64 | Build verification |
riscv64gc-unknown-none-elf | RISC-V embedded | Build verification |
aarch64-unknown-none-softfloat | ARM64 embedded | Build verification |
This architecture matrix ensures the crate functions correctly across the diverse hardware platforms supported by ArceOS.
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L25 - L30)
Building and Testing
Relevant source files
This page covers how to set up a local development environment for the handler_table
crate, including building, testing, and code quality verification. The build process is designed to support multiple target architectures, including bare metal environments required for ArceOS integration.
For information about the automated CI/CD pipeline and deployment process, see CI/CD Pipeline.
Prerequisites
The handler_table
crate requires specific toolchain components and target platforms to build successfully across all supported architectures.
Required Rust Toolchain
The crate requires the nightly Rust toolchain with specific components:
Component | Purpose |
---|---|
rust-src | Source code for cross-compilation to bare metal targets |
clippy | Lint checking for code quality |
rustfmt | Code formatting verification |
Supported Target Architectures
The crate builds for multiple target platforms as defined in the CI matrix:
Target | Environment | Test Support |
---|---|---|
x86_64-unknown-linux-gnu | Linux development | ✅ Full testing |
x86_64-unknown-none | Bare metal x86_64 | ✅ Build only |
riscv64gc-unknown-none-elf | Bare metal RISC-V | ✅ Build only |
aarch64-unknown-none-softfloat | Bare metal ARM64 | ✅ Build only |
Build and Test Workflow
flowchart TD subgraph subGraph0["Target Matrix"] Linux["x86_64-unknown-linux-gnu"] BareX86["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] ARM64["aarch64-unknown-none-softfloat"] end Setup["Rust Toolchain Setup"] Fmt["cargo fmt --check"] Clippy["cargo clippy"] Build["cargo build"] Test["cargo test"] Doc["cargo doc"] Build --> ARM64 Build --> BareX86 Build --> Linux Build --> RISCV Build --> Test Clippy --> Build Fmt --> Clippy Setup --> Fmt Test --> Doc Test --> Linux
Sources: .github/workflows/ci.yml(L11 - L19) .github/workflows/ci.yml(L22 - L30)
Local Development Setup
Installing Required Components
Install the nightly toolchain and required components:
# Install nightly toolchain
rustup toolchain install nightly
# Add required components
rustup component add --toolchain nightly rust-src clippy rustfmt
# Add target platforms
rustup target add --toolchain nightly x86_64-unknown-none
rustup target add --toolchain nightly riscv64gc-unknown-none-elf
rustup target add --toolchain nightly aarch64-unknown-none-softfloat
Setting Default Toolchain
Set nightly as the default for the project:
rustup override set nightly
Verify the installation:
rustc --version --verbose
Sources: .github/workflows/ci.yml(L15 - L21)
Building the Crate
Standard Build
Build for the default target (host platform):
cargo build --all-features
Multi-Target Building
Build for specific target architectures:
# Linux target (default)
cargo build --target x86_64-unknown-linux-gnu --all-features
# Bare metal x86_64
cargo build --target x86_64-unknown-none --all-features
# Bare metal RISC-V
cargo build --target riscv64gc-unknown-none-elf --all-features
# Bare metal ARM64
cargo build --target aarch64-unknown-none-softfloat --all-features
Multi-Target Build Process
flowchart TD subgraph subGraph1["Build Artifacts"] LinuxLib["libhandler_table-*.rlib"] BareX86Lib["libhandler_table-*.rlib"] RISCVLib["libhandler_table-*.rlib"] ARM64Lib["libhandler_table-*.rlib"] end subgraph subGraph0["Build Targets"] Linux["x86_64-unknown-linux-gnu"] BareX86["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] ARM64["aarch64-unknown-none-softfloat"] end Source["src/lib.rs"] Cargo["Cargo.toml"] ARM64 --> ARM64Lib BareX86 --> BareX86Lib Cargo --> ARM64 Cargo --> BareX86 Cargo --> Linux Cargo --> RISCV Linux --> LinuxLib RISCV --> RISCVLib Source --> ARM64 Source --> BareX86 Source --> Linux Source --> RISCV
Sources: .github/workflows/ci.yml(L26 - L27)
Running Tests
Unit Tests
Unit tests can only be executed on the x86_64-unknown-linux-gnu
target due to the no_std
nature of the crate and bare metal target limitations:
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
The --nocapture
flag allows println!
statements in tests to be visible during test execution.
Test Limitations
Target | Test Support | Reason |
---|---|---|
x86_64-unknown-linux-gnu | ✅ Full | Standard library available |
x86_64-unknown-none | ❌ None | No test framework in bare metal |
riscv64gc-unknown-none-elf | ❌ None | No test framework in bare metal |
aarch64-unknown-none-softfloat | ❌ None | No test framework in bare metal |
Sources: .github/workflows/ci.yml(L28 - L30)
Code Quality Checks
Format Checking
Verify code formatting compliance:
cargo fmt --all -- --check
This command checks that all code follows the standard Rust formatting rules without modifying files.
Lint Checking
Run Clippy for all targets:
# Check specific target
cargo clippy --target x86_64-unknown-linux-gnu --all-features
cargo clippy --target x86_64-unknown-none --all-features
cargo clippy --target riscv64gc-unknown-none-elf --all-features
cargo clippy --target aarch64-unknown-none-softfloat --all-features
Code Quality Pipeline
flowchart TD subgraph Results["Results"] FmtOK["Format ✅"] LintOK["Lint ✅"] QualityOK["Quality Approved"] end subgraph subGraph0["Quality Checks"] Fmt["cargo fmt --check"] ClippyLinux["clippy x86_64-linux"] ClippyBare["clippy x86_64-none"] ClippyRISCV["clippy riscv64gc"] ClippyARM["clippy aarch64"] end Code["Source Code"] ClippyARM --> LintOK ClippyBare --> LintOK ClippyLinux --> LintOK ClippyRISCV --> LintOK Code --> ClippyARM Code --> ClippyBare Code --> ClippyLinux Code --> ClippyRISCV Code --> Fmt Fmt --> FmtOK FmtOK --> QualityOK LintOK --> QualityOK
Sources: .github/workflows/ci.yml(L22 - L25)
Documentation Generation
Building Documentation
Generate crate documentation:
cargo doc --no-deps --all-features
The --no-deps
flag excludes dependency documentation, focusing only on the handler_table
crate.
Documentation Configuration
The documentation build process uses specific RUSTDOCFLAGS
for strict documentation requirements:
export RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs"
cargo doc --no-deps --all-features
Flag | Purpose |
---|---|
-D rustdoc::broken_intra_doc_links | Fail on broken internal documentation links |
-D missing-docs | Fail on missing documentation comments |
Viewing Documentation
After building, documentation is available at:
target/doc/handler_table/index.html
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L44 - L48)
Development Workflow
Complete Development Cycle
A typical development workflow combines all verification steps:
# 1. Format check
cargo fmt --all -- --check
# 2. Lint check for primary target
cargo clippy --target x86_64-unknown-linux-gnu --all-features
# 3. Build all targets
cargo build --target x86_64-unknown-linux-gnu --all-features
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features
# 4. Run tests
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
# 5. Generate documentation
cargo doc --no-deps --all-features
Ignored Files
The development environment excludes certain files and directories from version control:
Path | Purpose |
---|---|
/target | Build artifacts and compiled output |
/.vscode | Visual Studio Code configuration |
.DS_Store | macOS system files |
Cargo.lock | Dependency lock file (library crate) |
Sources: .gitignore(L1 - L4)
CI/CD Pipeline
Relevant source files
This page documents the Continuous Integration and Continuous Deployment (CI/CD) pipeline for the handler_table
crate. The pipeline ensures code quality, compatibility across multiple architectures, and automated documentation deployment through GitHub Actions workflows.
For information about building and testing locally during development, see Building and Testing.
Pipeline Overview
The CI/CD system consists of two primary GitHub Actions workflows that execute on push and pull request events. The pipeline validates code across multiple target architectures and automatically deploys documentation to GitHub Pages.
CI/CD Workflow Architecture
flowchart TD subgraph subGraph3["Documentation Pipeline"] DocWorkflow["doc job"] DocCheckout["actions/checkout@v4"] DocBuild["cargo doc --no-deps"] IndexGen["generate index.html redirect"] Deploy["JamesIves/github-pages-deploy-action@v4"] Pages["GitHub Pages (gh-pages branch)"] end subgraph subGraph2["CI Steps"] Checkout["actions/checkout@v4"] Toolchain["dtolnay/rust-toolchain@nightly"] Format["cargo fmt --check"] Clippy["cargo clippy"] Build["cargo build"] Test["cargo test (linux only)"] end subgraph subGraph1["Target Architectures"] LinuxGNU["x86_64-unknown-linux-gnu"] BareX86["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] ARM["aarch64-unknown-none-softfloat"] end subgraph subGraph0["CI Job Matrix"] CIWorkflow["ci job"] Matrix["strategy.matrix"] RustNightly["rust-toolchain: nightly"] Targets["4 target architectures"] end Triggers["push/pull_request events"] Build --> Test CIWorkflow --> Matrix Checkout --> Toolchain Clippy --> Build Deploy --> Pages DocBuild --> IndexGen DocCheckout --> DocBuild DocWorkflow --> DocCheckout Format --> Clippy IndexGen --> Deploy Matrix --> RustNightly Matrix --> Targets RustNightly --> Checkout Targets --> ARM Targets --> BareX86 Targets --> LinuxGNU Targets --> RISCV Toolchain --> Format Triggers --> CIWorkflow Triggers --> DocWorkflow
Sources: .github/workflows/ci.yml(L1 - L56)
Multi-Architecture Testing Strategy
The CI pipeline validates compatibility across four distinct target architectures to ensure the handler_table
crate functions correctly in diverse embedded and system environments.
Architecture Testing Matrix
Target | Environment | Test Coverage |
---|---|---|
x86_64-unknown-linux-gnu | Standard Linux | Full (fmt, clippy, build, test) |
x86_64-unknown-none | Bare metal x86_64 | Build and lint only |
riscv64gc-unknown-none-elf | RISC-V bare metal | Build and lint only |
aarch64-unknown-none-softfloat | ARM64 bare metal | Build and lint only |
The pipeline uses a fail-fast strategy set to false
, ensuring all target combinations are tested even if one fails.
Target-Specific Execution Flow
flowchart TD subgraph subGraph2["Toolchain Components"] RustSrc["rust-src"] ClippyComp["clippy"] RustfmtComp["rustfmt"] TargetSpec["target-specific toolchain"] end subgraph subGraph1["Per-Target Steps"] Setup["Setup: checkout + toolchain + components"] VersionCheck["rustc --version --verbose"] FormatCheck["cargo fmt --all -- --check"] ClippyLint["cargo clippy --target TARGET --all-features"] BuildStep["cargo build --target TARGET --all-features"] TestStep["cargo test (if x86_64-unknown-linux-gnu)"] end subgraph subGraph0["Matrix Strategy"] FailFast["fail-fast: false"] NightlyToolchain["rust-toolchain: [nightly]"] FourTargets["targets: [4 architectures]"] end BuildStep --> TestStep ClippyLint --> BuildStep FailFast --> Setup FormatCheck --> ClippyLint FourTargets --> Setup NightlyToolchain --> Setup Setup --> ClippyComp Setup --> RustSrc Setup --> RustfmtComp Setup --> TargetSpec Setup --> VersionCheck VersionCheck --> FormatCheck
Sources: .github/workflows/ci.yml(L8 - L12) .github/workflows/ci.yml(L25 - L30)
Documentation Deployment
The documentation pipeline builds API documentation using cargo doc
and deploys it to GitHub Pages. The deployment only occurs on pushes to the default branch, while documentation building is attempted on all branches and pull requests.
Documentation Build Process
The documentation job includes specific configurations for documentation quality:
- Environment Variables:
RUSTDOCFLAGS
set to-D rustdoc::broken_intra_doc_links -D missing-docs
to enforce documentation standards - Build Command:
cargo doc --no-deps --all-features
to generate comprehensive API documentation - Index Generation: Automatic creation of a redirect
index.html
pointing to the crate's documentation root
Documentation Deployment Flow
flowchart TD subgraph subGraph2["Error Handling"] ContinueOnError["continue-on-error for non-default branches"] end subgraph subGraph1["Conditional Deployment"] BranchCheck["github.ref == default-branch?"] DeployAction["JamesIves/github-pages-deploy-action@v4"] SkipDeploy["Skip deployment"] SingleCommit["single-commit: true"] GHPages["branch: gh-pages"] TargetDoc["folder: target/doc"] PagesDeployment["GitHub Pages Site"] end subgraph subGraph0["Documentation Build"] DocJob["doc job"] DocSetup["checkout + rust nightly"] DocFlags["RUSTDOCFLAGS environment"] CargoDocs["cargo doc --no-deps --all-features"] IndexHTML["generate target/doc/index.html redirect"] end DocTrigger["Push/PR to any branch"] BranchCheck --> DeployAction BranchCheck --> SkipDeploy CargoDocs --> ContinueOnError CargoDocs --> IndexHTML DeployAction --> GHPages DeployAction --> SingleCommit DeployAction --> TargetDoc DocFlags --> CargoDocs DocJob --> DocSetup DocSetup --> DocFlags DocTrigger --> DocJob GHPages --> PagesDeployment IndexHTML --> BranchCheck SingleCommit --> PagesDeployment TargetDoc --> PagesDeployment
Sources: .github/workflows/ci.yml(L32 - L56) .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L47 - L48)
Workflow Job Structure
The CI/CD pipeline is organized into two distinct GitHub Actions jobs with different responsibilities and execution contexts.
Job Configuration Details
CI Job (ci
):
- Runner:
ubuntu-latest
- Strategy: Matrix execution across rust toolchain and target combinations
- Purpose: Code quality validation and multi-architecture compatibility testing
Documentation Job (doc
):
- Runner:
ubuntu-latest
- Strategy: Single execution context
- Permissions:
contents: write
for GitHub Pages deployment - Purpose: API documentation generation and deployment
Complete Workflow Structure
flowchart TD subgraph subGraph4["Doc Job Steps"] DocStep1["actions/checkout@v4"] DocStep2["dtolnay/rust-toolchain@nightly"] DocStep3["cargo doc --no-deps --all-features"] DocStep4["generate index.html redirect"] DocStep5["JamesIves/github-pages-deploy-action@v4"] end subgraph subGraph3["Doc Job Configuration"] DocRunner["runs-on: ubuntu-latest"] DocPerms["permissions: contents: write"] DocEnv["RUSTDOCFLAGS environment"] end subgraph subGraph2["CI Job Steps"] CIStep1["actions/checkout@v4"] CIStep2["dtolnay/rust-toolchain@nightly"] CIStep3["rustc --version --verbose"] CIStep4["cargo fmt --all -- --check"] CIStep5["cargo clippy --target TARGET"] CIStep6["cargo build --target TARGET"] CIStep7["cargo test (conditional)"] end subgraph subGraph1["CI Job Configuration"] CIRunner["runs-on: ubuntu-latest"] CIStrategy["strategy.fail-fast: false"] CIMatrix["matrix: nightly × 4 targets"] end subgraph subGraph0["Workflow Triggers"] PushEvent["on: push"] PREvent["on: pull_request"] end CIMatrix --> CIStep1 CIRunner --> CIStrategy CIStep1 --> CIStep2 CIStep2 --> CIStep3 CIStep3 --> CIStep4 CIStep4 --> CIStep5 CIStep5 --> CIStep6 CIStep6 --> CIStep7 CIStrategy --> CIMatrix DocEnv --> DocStep1 DocPerms --> DocEnv DocRunner --> DocPerms DocStep1 --> DocStep2 DocStep2 --> DocStep3 DocStep3 --> DocStep4 DocStep4 --> DocStep5 PREvent --> CIRunner PREvent --> DocRunner PushEvent --> CIRunner PushEvent --> DocRunner
Sources: .github/workflows/ci.yml(L1 - L31) .github/workflows/ci.yml(L32 - L56) .github/workflows/ci.yml(L36 - L37)
Overview
Relevant source files
The memory_set
crate provides building blocks for memory mapping management in Rust, offering data structures and operations similar to Unix system calls like mmap
, munmap
, and mprotect
. This document covers the core architecture, components, and design patterns of the memory management system.
Note: This repository has been archived and the code has been moved to the axmm_crates repository as indicated in the README.
For detailed implementation specifics of individual components, see Implementation Details. For practical usage examples and patterns, see Usage and Examples.
Core Components and Structure
The memory_set
crate is built around three primary components that work together to provide flexible memory mapping management:
Core Components Overview
flowchart TD subgraph Storage["Storage"] BT["BTreeMap<VirtAddr, MemoryArea>"] end subgraph subGraph1["Supporting Types"] MR["MappingResult<T>"] ME["MappingError"] VA["VirtAddr"] VAR["VirtAddrRange"] end subgraph subGraph0["Primary Types"] MS["MemorySet<F,PT,B>"] MA["MemoryArea<F,B>"] MB["MappingBackend<F,PT> trait"] end MA --> MB MA --> VAR MR --> ME MS --> BT MS --> MA MS --> MR VAR --> VA
Sources: src/lib.rs(L12 - L13) src/lib.rs(L15 - L27) README.md(L18 - L41)
The system uses a generic type parameter approach where:
F
represents memory flags (must implementCopy
)PT
represents the page table typeB
represents the mapping backend implementation
System Architecture and Data Flow
The memory management system follows a layered architecture where high-level operations are decomposed into area management and low-level page table manipulation:
Memory Management Operation Flow
sequenceDiagram participant Client as Client participant MemorySet as MemorySet participant BTreeMap as BTreeMap participant MemoryArea as MemoryArea participant MappingBackend as MappingBackend Note over Client,MappingBackend: map() operation Client ->> MemorySet: "map(area, page_table, unmap_overlap)" MemorySet ->> BTreeMap: "Check for overlaps" BTreeMap -->> MemorySet: "Overlapping areas found" alt unmap_overlap = true MemorySet ->> MemorySet: "Split/remove overlapping areas" MemorySet ->> MappingBackend: "unmap existing regions" else unmap_overlap = false MemorySet -->> Client: "MappingError::AlreadyExists" end MemorySet ->> MemoryArea: "Get backend reference" MemoryArea ->> MappingBackend: "map(start, size, flags, page_table)" MappingBackend -->> MemorySet: "Success/failure" MemorySet ->> BTreeMap: "Insert new area" MemorySet -->> Client: "MappingResult" Note over Client,MappingBackend: unmap() operation Client ->> MemorySet: "unmap(start, size, page_table)" MemorySet ->> BTreeMap: "Find affected areas" loop For each affected area alt Fully contained MemorySet ->> BTreeMap: "Remove area" else Partially overlapping MemorySet ->> MemoryArea: "split(boundary_addr)" MemoryArea -->> MemorySet: "New area created" MemorySet ->> BTreeMap: "Update area collection" end MemorySet ->> MappingBackend: "unmap(start, size, page_table)" end MemorySet -->> Client: "MappingResult"
Sources: README.md(L36 - L48) src/lib.rs(L15 - L27)
Generic Type System Design
The crate achieves flexibility through careful use of Rust generics, allowing different implementations for flags, page tables, and backends while maintaining type safety:
Generic Type Relationships
flowchart TD subgraph subGraph2["Mock Implementation Example"] MF["MockFlags = u8"] MPT["MockPageTable = [u8; MAX_ADDR]"] MOCK["MockBackend"] MS_CONCRETE["MemorySet<MockFlags, MockPageTable, MockBackend>"] end subgraph subGraph1["Core Generic Types"] MS["MemorySet<F,PT,B>"] MA["MemoryArea<F,B>"] MBT["MappingBackend<F,PT>"] end subgraph subGraph0["Generic Parameters"] F["F: Copy"] PT["PT"] B["B: MappingBackend<F,PT>"] end B --> MA B --> MBT B --> MS F --> MA F --> MBT F --> MS MF --> MS_CONCRETE MOCK --> MBT MOCK --> MS_CONCRETE MPT --> MS_CONCRETE PT --> MBT PT --> MS
Sources: README.md(L24 - L34) README.md(L51 - L87) src/lib.rs(L12 - L13)
Error Handling Strategy
The crate uses a dedicated error type system to handle various failure scenarios in memory operations:
Error Type | Description | Usage Context |
---|---|---|
MappingError::InvalidParam | Invalid parameters likeaddr,size,flags | Input validation |
MappingError::AlreadyExists | Range overlaps with existing mapping | Overlap detection |
MappingError::BadState | Backend page table in bad state | Backend operation failures |
The MappingResult<T>
type alias provides a convenient Result
type for all memory operations, defaulting to MappingResult<()>
for operations that return no meaningful data on success.
Sources: src/lib.rs(L15 - L27)
Key Design Principles
- Generic Flexibility: The system uses three generic type parameters (
F
,PT
,B
) to allow customization of flags, page tables, and backend implementations - Area-Based Management: Memory is managed in discrete areas that can be split, merged, and manipulated independently
- Backend Abstraction: The
MappingBackend
trait abstracts actual page table manipulation, enabling different implementation strategies - Overlap Handling: Sophisticated overlap detection and resolution with options for automatic unmapping of conflicting regions
- Type Safety: Rust's type system ensures memory safety and prevents common memory management errors
This overview establishes the foundation for understanding the detailed implementation covered in Implementation Details and the practical usage patterns demonstrated in Usage and Examples.
Sources: README.md(L9 - L14) src/lib.rs(L1 - L13)
Core Concepts
Relevant source files
This document explains the fundamental building blocks of the memory_set crate: MemorySet
, MemoryArea
, and MappingBackend
. These three types work together to provide a flexible abstraction for memory mapping operations similar to mmap
, munmap
, and mprotect
system calls.
For detailed implementation specifics, see Implementation Details. For practical usage examples, see Basic Usage Patterns.
The Three Core Types
The memory_set crate is built around three primary abstractions that work together to manage memory mappings:
flowchart TD subgraph subGraph0["Generic Parameters"] F["F: Memory Flags"] PT["PT: Page Table Type"] B["B: Backend Implementation"] end MemorySet["MemorySet"] MemoryArea["MemoryArea"] MappingBackend["MappingBackend"] BTreeMap["BTreeMap"] VirtAddrRange["VirtAddrRange"] PageTable["Page Table (PT)"] B --> MemorySet F --> MemorySet MappingBackend --> PageTable MemoryArea --> MappingBackend MemoryArea --> VirtAddrRange MemorySet --> BTreeMap MemorySet --> MappingBackend MemorySet --> MemoryArea PT --> MemorySet
Sources: README.md(L18 - L41)
MemorySet: Collection Management
MemorySet<F, PT, B>
serves as the primary interface for managing collections of memory areas. It maintains a sorted collection of non-overlapping memory regions and provides high-level operations for mapping, unmapping, and protecting memory.
Key Responsibilities
Operation | Description | Overlap Handling |
---|---|---|
map() | Add new memory area | Can split/remove existing areas |
unmap() | Remove memory regions | Automatically splits affected areas |
protect() | Change permissions | Updates flags for matching areas |
iter() | Enumerate areas | Provides ordered traversal |
The MemorySet
uses a BTreeMap<VirtAddr, MemoryArea>
internally to maintain areas sorted by their starting virtual address, enabling efficient overlap detection and range queries.
flowchart TD subgraph Operations["Operations"] VR1["VirtAddrRange: 0x1000-0x2000"] VR2["VirtAddrRange: 0x3000-0x4000"] VR3["VirtAddrRange: 0x5000-0x6000"] end subgraph subGraph1["Area Organization"] A1["va!(0x1000) -> MemoryArea"] A2["va!(0x3000) -> MemoryArea"] A3["va!(0x5000) -> MemoryArea"] end subgraph subGraph0["MemorySet Internal Structure"] MS["MemorySet"] BT["BTreeMap"] end A1 --> VR1 A2 --> VR2 A3 --> VR3 BT --> A1 BT --> A2 BT --> A3 MS --> BT
Sources: README.md(L34 - L48)
MemoryArea: Individual Memory Regions
MemoryArea<F, B>
represents a contiguous region of virtual memory with specific properties including address range, permissions flags, and an associated backend for page table operations.
Core Properties
Each MemoryArea
encapsulates:
- Virtual Address Range: Start address and size defining the memory region
- Flags: Memory permissions (read, write, execute, etc.)
- Backend: Implementation for actual page table manipulation
Area Lifecycle Operations
Sources: README.md(L37 - L38)
MappingBackend: Page Table Interface
The MappingBackend<F, PT>
trait defines the interface between memory areas and the underlying page table implementation. This abstraction allows the memory_set crate to work with different page table formats and memory management systems.
Required Operations
All backends must implement three core operations:
Method | Parameters | Purpose |
---|---|---|
map() | start: VirtAddr, size: usize, flags: F, pt: &mut PT | Establish new memory mappings |
unmap() | start: VirtAddr, size: usize, pt: &mut PT | Remove existing mappings |
protect() | start: VirtAddr, size: usize, new_flags: F, pt: &mut PT | Change mapping permissions |
Example Implementation Pattern
The mock backend demonstrates the interface pattern:
flowchart TD subgraph subGraph1["MappingBackend Trait"] map_method["map()"] unmap_method["unmap()"] protect_method["protect()"] end subgraph subGraph0["MockBackend Implementation"] MockBackend["MockBackend struct"] MockFlags["MockFlags = u8"] MockPageTable["MockPageTable = [u8; MAX_ADDR]"] end MockBackend --> map_method MockBackend --> protect_method MockBackend --> unmap_method MockFlags --> MockPageTable map_method --> MockPageTable protect_method --> MockPageTable unmap_method --> MockPageTable
Sources: README.md(L51 - L87)
Generic Type System
The memory_set crate uses a sophisticated generic type system to provide flexibility while maintaining type safety:
Type Parameters
- F: Memory flags type (must implement
Copy
) - PT: Page table type (can be any structure)
- B: Backend implementation (must implement
MappingBackend<F, PT>
)
This design allows the crate to work with different:
- Flag representations (bitfields, enums, integers)
- Page table formats (arrays, trees, hardware tables)
- Backend strategies (direct manipulation, system calls, simulation)
flowchart TD subgraph Usage["Usage"] MemorySet_Concrete["MemorySet"] end subgraph subGraph1["Concrete Example"] MockFlags_u8["MockFlags = u8"] MockPT_Array["MockPageTable = [u8; MAX_ADDR]"] MockBackend_Impl["MockBackend: MappingBackend"] end subgraph subGraph0["Generic Constraints"] F_Copy["F: Copy"] B_Backend["B: MappingBackend"] PT_Any["PT: Any type"] end B_Backend --> MockBackend_Impl F_Copy --> MockFlags_u8 MockBackend_Impl --> MemorySet_Concrete MockFlags_u8 --> MemorySet_Concrete MockPT_Array --> MemorySet_Concrete PT_Any --> MockPT_Array
Sources: README.md(L24 - L31)
Coordinated Operation Flow
The three core types work together in a coordinated fashion to handle memory management operations:
sequenceDiagram participant Client as Client participant MemorySet as MemorySet participant MemoryArea as MemoryArea participant MappingBackend as MappingBackend participant PageTable as PageTable Note over Client,PageTable: Memory Mapping Operation Client ->> MemorySet: "map(area, page_table, unmap_overlap)" MemorySet ->> MemorySet: "check for overlapping areas" alt overlaps exist and unmap_overlap=true MemorySet ->> MemoryArea: "split/shrink existing areas" MemorySet ->> MappingBackend: "unmap() overlapping regions" MappingBackend ->> PageTable: "clear page table entries" end MemorySet ->> MemoryArea: "get backend reference" MemoryArea ->> MappingBackend: "map(start, size, flags, pt)" MappingBackend ->> PageTable: "set page table entries" MappingBackend -->> MemorySet: "return success/failure" MemorySet ->> MemorySet: "insert area into BTreeMap" MemorySet -->> Client: "return MappingResult" Note over Client,PageTable: Memory Unmapping Operation Client ->> MemorySet: "unmap(start, size, page_table)" MemorySet ->> MemorySet: "find affected areas" loop for each affected area MemorySet ->> MemoryArea: "determine overlap relationship" alt area fully contained MemorySet ->> MemorySet: "remove area from BTreeMap" else area partially overlapping MemorySet ->> MemoryArea: "split area at boundaries" MemorySet ->> MemorySet: "update BTreeMap with new areas" end MemorySet ->> MappingBackend: "unmap(start, size, pt)" MappingBackend ->> PageTable: "clear page table entries" end MemorySet -->> Client: "return MappingResult"
Sources: README.md(L42 - L43)
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:
Operation | Complexity | Implementation |
---|---|---|
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)
Implementation Details
Relevant source files
This document provides detailed technical information about the internal implementation of the memory_set crate's core components. It covers the internal data structures, algorithms, and patterns used to implement memory mapping management functionality. For high-level concepts and architecture overview, see System Architecture. For usage examples and public API documentation, see Public API and Error Handling.
Core Data Structures and Types
The memory_set crate is built around three primary data structures that work together through a carefully designed generic type system.
MemoryArea Internal Structure
The MemoryArea
struct serves as the fundamental building block representing a contiguous virtual memory region with uniform properties.
flowchart TD subgraph subGraph3["Key Methods"] MAPAREA["map_area()"] UNMAPAREA["unmap_area()"] PROTECTAREA["protect_area()"] SPLIT["split()"] SHRINKLEFT["shrink_left()"] SHRINKRIGHT["shrink_right()"] end subgraph subGraph2["Type Constraints"] FCOPY["F: Copy"] BMAPPING["B: MappingBackend"] PGENERIC["P: Page Table Type"] end subgraph subGraph1["VirtAddrRange Components"] START["start: VirtAddr"] END["end: VirtAddr"] end subgraph subGraph0["MemoryArea Fields"] MA["MemoryArea"] VAR["va_range: VirtAddrRange"] FLAGS["flags: F"] BACKEND["backend: B"] PHANTOM["_phantom: PhantomData<(F,P)>"] end BACKEND --> BMAPPING FLAGS --> FCOPY MA --> BACKEND MA --> FLAGS MA --> MAPAREA MA --> PHANTOM MA --> PROTECTAREA MA --> SHRINKLEFT MA --> SHRINKRIGHT MA --> SPLIT MA --> UNMAPAREA MA --> VAR PHANTOM --> PGENERIC VAR --> END VAR --> START
The MemoryArea
struct maintains both metadata about the virtual address range and a reference to the backend that handles the actual page table manipulation. The PhantomData
field ensures proper generic type relationships without runtime overhead.
Sources: src/area.rs(L29 - L34) src/area.rs(L36 - L76)
MappingBackend Trait Implementation
The MappingBackend
trait defines the interface for different memory mapping strategies, allowing the system to support various page table implementations and mapping policies.
flowchart TD subgraph subGraph3["Implementation Requirements"] CLONE["Clone trait bound"] BOOLRESULT["Boolean success indicator"] end subgraph subGraph2["Generic Parameters"] F["F: Copy (Flags Type)"] P["P: Page Table Type"] end subgraph subGraph1["Backend Responsibilities"] MAPENTRY["Set page table entries"] CLEARENTRY["Clear page table entries"] UPDATEENTRY["Update entry permissions"] end subgraph subGraph0["MappingBackend Trait"] MB["MappingBackend"] MAP["map(start, size, flags, page_table) -> bool"] UNMAP["unmap(start, size, page_table) -> bool"] PROTECT["protect(start, size, new_flags, page_table) -> bool"] end F --> MB MAP --> BOOLRESULT MAP --> MAPENTRY MB --> CLONE MB --> MAP MB --> PROTECT MB --> UNMAP P --> MB PROTECT --> BOOLRESULT PROTECT --> UPDATEENTRY UNMAP --> BOOLRESULT UNMAP --> CLEARENTRY
The trait's boolean return values allow backends to signal success or failure, which the higher-level operations convert into proper MappingResult
types for error handling.
Sources: src/area.rs(L15 - L22) src/area.rs(L90 - L110)
MemorySet Collection Structure
The MemorySet
uses a BTreeMap
to maintain an ordered collection of memory areas, enabling efficient range queries and overlap detection.
flowchart TD subgraph subGraph3["Core Methods"] MAPMETHOD["map()"] UNMAPMETHOD["unmap()"] PROTECTMETHOD["protect()"] OVERLAPS["overlaps()"] FIND["find()"] FINDFREE["find_free_area()"] end subgraph subGraph2["Efficiency Operations"] RANGEQUERIES["O(log n) range queries"] OVERLAP["Efficient overlap detection"] SEARCH["Binary search for areas"] end subgraph subGraph1["BTreeMap Key-Value Organization"] KEY1["Key: area.start()"] VAL1["Value: MemoryArea"] ORDERING["Sorted by start address"] end subgraph subGraph0["MemorySet Structure"] MS["MemorySet"] BTREE["areas: BTreeMap>"] end BTREE --> KEY1 BTREE --> VAL1 KEY1 --> ORDERING MS --> BTREE MS --> FIND MS --> FINDFREE MS --> MAPMETHOD MS --> OVERLAPS MS --> PROTECTMETHOD MS --> UNMAPMETHOD ORDERING --> OVERLAP ORDERING --> RANGEQUERIES ORDERING --> SEARCH
The choice of VirtAddr
as the key ensures that areas are naturally sorted by their start addresses, which is crucial for the overlap detection and range manipulation algorithms.
Sources: src/set.rs(L9 - L11) src/set.rs(L36 - L49)
Memory Area Lifecycle Operations
Memory areas support sophisticated lifecycle operations that enable complex memory management patterns while maintaining consistency with the underlying page table.
Area Splitting Algorithm
The split()
method implements a critical operation for handling partial unmapping and protection changes. It divides a single memory area into two independent areas at a specified boundary.
sequenceDiagram participant Client as Client participant MemoryArea as "MemoryArea" participant MappingBackend as "MappingBackend" Note over Client,MappingBackend: split(pos: VirtAddr) Operation Client ->> MemoryArea: split(pos) MemoryArea ->> MemoryArea: Validate pos in range (start < pos < end) alt Valid position MemoryArea ->> MemoryArea: Create new_area from pos to end Note over MemoryArea: new_area = MemoryArea::new(pos, end-pos, flags, backend.clone()) MemoryArea ->> MemoryArea: Shrink original to start..pos Note over MemoryArea: self.va_range.end = pos MemoryArea ->> Client: Return Some(new_area) else Invalid position MemoryArea ->> Client: Return None end Note over MemoryArea,MappingBackend: No page table operations during split Note over MemoryArea: Backend cloned, both areas share same mapping behavior
The splitting operation is purely metadata manipulation - it doesn't modify the page table entries. The actual page table changes happen when subsequent operations like unmap()
or protect()
are called on the split areas.
Sources: src/area.rs(L148 - L163)
Area Shrinking Operations
Memory areas support shrinking from either end, which is essential for handling partial unmapping operations efficiently.
flowchart TD subgraph subGraph2["Error Handling"] CHECKRESULT["Check backend success"] SUCCESS["Update metadata"] FAILURE["Return MappingError::BadState"] end subgraph subGraph1["shrink_right() Process"] SR["shrink_right(new_size)"] CALCUNMAP2["unmap_size = old_size - new_size"] UNMAPRIGHT["backend.unmap(start + new_size, unmap_size)"] UPDATEEND["va_range.end -= unmap_size"] end subgraph subGraph0["shrink_left() Process"] SL["shrink_left(new_size)"] CALCUNMAP["unmap_size = old_size - new_size"] UNMAPLEFT["backend.unmap(start, unmap_size)"] UPDATESTART["va_range.start += unmap_size"] end CALCUNMAP --> UNMAPLEFT CALCUNMAP2 --> UNMAPRIGHT CHECKRESULT --> FAILURE CHECKRESULT --> SUCCESS SL --> CALCUNMAP SR --> CALCUNMAP2 UNMAPLEFT --> CHECKRESULT UNMAPLEFT --> UPDATESTART UNMAPRIGHT --> CHECKRESULT UNMAPRIGHT --> UPDATEEND
Both shrinking operations immediately update the page table through the backend before modifying the area's metadata, ensuring consistency between the virtual memory layout and page table state.
Sources: src/area.rs(L116 - L139)
Collection Management Algorithms
The MemorySet
implements sophisticated algorithms for managing collections of memory areas, particularly for handling overlapping operations and maintaining area integrity.
Overlap Detection Strategy
The overlap detection algorithm leverages the BTreeMap
's ordered structure to efficiently check for conflicts with minimal tree traversal.
flowchart TD subgraph subGraph0["BTreeMap Range Queries"] BEFORECHECK["Find last area before range.start"] AFTERCHECK["Find first area >= range.start"] RANGEBEFORE["areas.range(..range.start).last()"] RANGEAFTER["areas.range(range.start..).next()"] end START["overlaps(range: VirtAddrRange)"] BEFOREOVERLAP["Does before area overlap with range?"] RETURNTRUE["return true"] AFTEROVERLAP["Does after area overlap with range?"] RETURNFALSE["return false"] AFTERCHECK --> AFTEROVERLAP AFTERCHECK --> RANGEAFTER AFTEROVERLAP --> RETURNFALSE AFTEROVERLAP --> RETURNTRUE BEFORECHECK --> BEFOREOVERLAP BEFORECHECK --> RANGEBEFORE BEFOREOVERLAP --> AFTERCHECK BEFOREOVERLAP --> RETURNTRUE START --> BEFORECHECK
This algorithm achieves O(log n) complexity by examining at most two areas, regardless of the total number of areas in the set.
Sources: src/set.rs(L36 - L49)
Complex Unmapping Algorithm
The unmap()
operation handles the most complex scenario in memory management: removing an arbitrary address range that may affect multiple existing areas in different ways.
flowchart TD subgraph subGraph3["Phase 3 Details"] RIGHTRANGE["areas.range_mut(start..).next()"] RIGHTINTERSECT["Check if after.start < end"] RIGHTCASE["Shrink left of area"] end subgraph subGraph2["Phase 2 Details"] LEFTRANGE["areas.range_mut(..start).last()"] LEFTINTERSECT["Check if before.end > start"] LEFTCASE1["Case 1: Unmap at end of area"] LEFTCASE2["Case 2: Unmap in middle (split required)"] end subgraph subGraph1["Phase 1 Details"] RETAIN["areas.retain() with condition"] CONTAINED["area.va_range().contained_in(range)"] REMOVE["Remove and unmap area"] end subgraph subGraph0["unmap() Three-Phase Algorithm"] PHASE1["Phase 1: Remove Fully Contained Areas"] PHASE2["Phase 2: Handle Left Boundary Intersection"] PHASE3["Phase 3: Handle Right Boundary Intersection"] end CONTAINED --> REMOVE LEFTINTERSECT --> LEFTCASE1 LEFTINTERSECT --> LEFTCASE2 LEFTRANGE --> LEFTINTERSECT PHASE1 --> RETAIN PHASE2 --> LEFTRANGE PHASE3 --> RIGHTRANGE RETAIN --> CONTAINED RIGHTINTERSECT --> RIGHTCASE RIGHTRANGE --> RIGHTINTERSECT
This three-phase approach ensures that all possible area-range relationships are handled correctly, from simple removal to complex splitting scenarios.
Sources: src/set.rs(L122 - L168)
Protection Changes with Area Management
The protect()
operation demonstrates the most sophisticated area manipulation, potentially creating new areas while modifying existing ones.
sequenceDiagram participant MemorySet as "MemorySet" participant BTreeMap as "BTreeMap" participant MemoryArea as "MemoryArea" participant MappingBackend as "MappingBackend" participant to_insertVec as "to_insert: Vec" Note over MemorySet,to_insertVec: protect(start, size, update_flags, page_table) MemorySet ->> BTreeMap: Iterate through all areas loop For each area in range BTreeMap ->> MemorySet: area reference MemorySet ->> MemorySet: Call update_flags(area.flags()) alt Flags should be updated Note over MemorySet,to_insertVec: Determine area-range relationship alt Area fully contained in range MemorySet ->> MemoryArea: protect_area(new_flags) MemoryArea ->> MappingBackend: protect(start, size, new_flags) MemorySet ->> MemoryArea: set_flags(new_flags) else Range fully contained in area (split into 3) MemorySet ->> MemoryArea: split(end) -> right_part MemorySet ->> MemoryArea: set_end(start) (becomes left_part) MemorySet ->> MemorySet: Create middle_part with new_flags MemorySet ->> to_insertVec: Queue right_part and middle_part for insertion else Partial overlaps MemorySet ->> MemoryArea: split() and protect as needed MemorySet ->> to_insertVec: Queue new parts for insertion end end end MemorySet ->> BTreeMap: areas.extend(to_insert)
The algorithm defers insertions to avoid iterator invalidation, collecting new areas in a vector and inserting them after the main iteration completes.
Sources: src/set.rs(L189 - L247)
Generic Type System Implementation
The crate's generic type system enables flexible memory management while maintaining type safety and zero-cost abstractions.
Type Parameter Relationships
flowchart TD subgraph subGraph1["Type Flow Through System"] MAFLAG["MemoryArea.flags: F"] MBFLAG["MappingBackend.map(flags: F)"] MBPT["MappingBackend methods(page_table: &mut P)"] MABACKEND["MemoryArea.backend: B"] CLONE["Cloned for area.split()"] end subgraph subGraph0["Generic Type Constraints"] F["F: Copy + Debug"] P["P: Any"] B["B: MappingBackend + Clone"] end subgraph subGraph3["PhantomData Usage"] PHANTOM["PhantomData<(F,P)>"] TYPEREL["Maintains F,P relationship in MemoryArea"] ZEROSIZE["Zero runtime cost"] FCOPY["F: Copy enables efficient flag passing"] BCLONE["B: Clone enables area splitting"] end subgraph subGraph2["Trait Bounds Enforcement"] PHANTOM["PhantomData<(F,P)>"] FCOPY["F: Copy enables efficient flag passing"] BCLONE["B: Clone enables area splitting"] BMAPPING["B: MappingBackend ensures consistent interface"] end B --> CLONE B --> MABACKEND F --> MAFLAG F --> MBFLAG P --> MBPT PHANTOM --> TYPEREL PHANTOM --> ZEROSIZE
The PhantomData<(F,P)>
field in MemoryArea
ensures that the compiler tracks the relationship between flag type F
and page table type P
even though P
is not directly stored in the struct.
Sources: src/area.rs(L29 - L34) src/area.rs(L15 - L22)
Error Handling Strategy
The crate implements a consistent error handling strategy that propagates failures through the operation chain while maintaining transactional semantics.
Error Propagation Pattern
flowchart TD subgraph subGraph3["Error Handling Locations"] AREAOPS["MemoryArea operations"] SETOPS["MemorySet operations"] PUBLICAPI["Public API boundary"] end subgraph subGraph2["Error Propagation"] PROPAGATE["? operator propagation"] end subgraph subGraph1["Error Sources"] BACKEND["Backend operation failure"] VALIDATION["Parameter validation"] OVERLAP["Overlap detection"] end subgraph subGraph0["Error Type Hierarchy"] MR["MappingResult = Result"] ME["MappingError"] BE["BadState"] IE["InvalidParam"] AE["AlreadyExists"] end AE --> PROPAGATE AREAOPS --> SETOPS BACKEND --> BE BE --> PROPAGATE IE --> PROPAGATE ME --> AE ME --> BE ME --> IE OVERLAP --> AE PROPAGATE --> AREAOPS SETOPS --> PUBLICAPI VALIDATION --> IE
The error handling design ensures that failures at any level (backend, area, or set) are properly propagated to the caller with meaningful error information.
Sources: src/area.rs(L6) src/area.rs(L90 - L110) src/set.rs(L98 - L114)
Backend Error Translation
The system translates boolean failure indicators from backends into structured error types:
sequenceDiagram participant MemoryArea as "MemoryArea" participant MappingBackend as "MappingBackend" participant MappingResult as "MappingResult" Note over MemoryArea,MappingResult: Backend Error Translation Pattern MemoryArea ->> MappingBackend: map(start, size, flags, page_table) MappingBackend ->> MemoryArea: return bool (success/failure) alt Backend returns true MemoryArea ->> MappingResult: Ok(()) else Backend returns false MemoryArea ->> MappingResult: Err(MappingError::BadState) end Note over MemoryArea,MappingResult: Pattern used in map_area(), unmap_area(), protect_area()
This translation happens in the MemoryArea
methods that interface with the backend, converting the simple boolean results into the richer MappingResult
type for higher-level error handling.
Sources: src/area.rs(L90 - L103) src/area.rs(L116 - L139)
MemoryArea and MappingBackend
Relevant source files
This page provides detailed technical documentation of the MemoryArea
struct and MappingBackend
trait, which form the core building blocks for individual memory region management within the memory_set crate. The MappingBackend
trait defines the interface for different memory mapping strategies, while MemoryArea
represents a contiguous virtual memory region with associated flags and backend implementation.
For information about how multiple memory areas are organized and managed as collections, see 2.2. For the public API that users interact with, see 2.3.
MappingBackend Trait
The MappingBackend
trait defines the fundamental operations required for memory mapping implementations. It serves as an abstraction layer that allows different mapping strategies to be plugged into the memory management system.
Trait Definition and Contract
classDiagram note for MappingBackend "Clone trait requiredfor area splitting operations" class MappingBackend { <<trait>> +map(start: VirtAddr, size: usize, flags: F, page_table: &mut P) bool +unmap(start: VirtAddr, size: usize, page_table: &mut P) bool +protect(start: VirtAddr, size: usize, new_flags: F, page_table: &mut P) bool } class TypeParameters { F: Copy "Memory flags type" P "Page table type" } MappingBackend --> TypeParameters : "Generic over"
The trait requires implementors to handle three core operations:
Method | Purpose | Return Value |
---|---|---|
map | Establish virtual-to-physical mappings | trueon success,falseon failure |
unmap | Remove existing mappings | trueon success,falseon failure |
protect | Modify access permissions | trueon success,falseon failure |
Sources: src/area.rs(L8 - L22)
Implementation Requirements
The trait imposes several constraints on implementors:
- Clone Requirement: Backends must implement
Clone
to support memory area splitting operations - Generic Flexibility: The
F
type parameter allows different flag representations (e.g., bitfields, enums) - Page Table Agnostic: The
P
type parameter enables integration with various page table implementations - Boolean Return Convention: All methods return boolean success indicators
Sources: src/area.rs(L15)
MemoryArea Struct
The MemoryArea
struct represents a contiguous virtual memory region with uniform properties. It combines address range information, access flags, and a backend implementation to provide a complete memory mapping abstraction.
Structure and Fields
classDiagram note for MemoryArea "Generic over F: Copy, P, B: MappingBackend" class MemoryArea { -va_range: VirtAddrRange -flags: F -backend: B -_phantom: PhantomData~F,P~ +new(start: VirtAddr, size: usize, flags: F, backend: B) Self +va_range() VirtAddrRange +flags() F +start() VirtAddr +end() VirtAddr +size() usize +backend() &B } class VirtAddrRange { +start: VirtAddr +end: VirtAddr +size() usize } class MappingBackend { <<trait>> } MemoryArea --> VirtAddrRange : "contains" MemoryArea --> MappingBackend : "implements B"
Core Fields:
va_range
: Defines the virtual address boundaries usingVirtAddrRange
flags
: Stores memory access permissions of typeF
backend
: Contains the mapping implementation of typeB
_phantom
: Enables generic type parameters without runtime overhead
Sources: src/area.rs(L24 - L34)
Public Accessor Methods
The struct provides const accessor methods for retrieving area properties:
Method | Return Type | Description |
---|---|---|
va_range() | VirtAddrRange | Complete address range information |
flags() | F | Current access flags |
start() | VirtAddr | Starting virtual address |
end() | VirtAddr | Ending virtual address (exclusive) |
size() | usize | Size in bytes |
backend() | &B | Reference to mapping backend |
Sources: src/area.rs(L47 - L75)
Core Operations and Lifecycle
Memory areas support a complete lifecycle from creation through mapping, modification, and unmapping operations.
Area Lifecycle State Machine
stateDiagram-v2 state Mapped { [*] --> FullMapped FullMapped --> LeftShrunk : "shrink_left()" FullMapped --> RightShrunk : "shrink_right()" LeftShrunk --> FullMapped : "conceptual expansion" RightShrunk --> FullMapped : "conceptual expansion" } state SplitState { Original --> LeftPart : "split()" Original --> RightPart : "split()" } [*] --> Created : "new()" Protected --> Unmapped : "unmap_area()" Created --> Unmapped : "never mapped" Unmapped --> [*] Created --> Mapped : "map_area()" Mapped --> Protected : "protect_area()" Protected --> Mapped : "protect_area() with different flags" Mapped --> SplitState : "split() called" Protected --> SplitState : "split() called" SplitState --> Mapped : "Both parts continue" Mapped --> Unmapped : "unmap_area()"
Sources: src/area.rs(L89 - L163)
Mapping Operations
The area provides three fundamental mapping operations that delegate to the backend:
map_area
- Purpose: Establishes page table entries for the entire area
- Implementation: Calls
backend.map()
with area's start, size, and flags - Error Handling: Returns
MappingError::BadState
on backend failure
unmap_area
- Purpose: Removes all page table entries for the area
- Implementation: Calls
backend.unmap()
with area's start and size - Error Handling: Returns
MappingError::BadState
on backend failure
protect_area
- Purpose: Modifies access permissions without unmapping
- Implementation: Calls
backend.protect()
and updates internal flags - Error Handling: Currently assumes success (returns
Ok(())
)
Sources: src/area.rs(L89 - L110)
Area Manipulation Methods
Memory areas support sophisticated manipulation operations that enable complex memory management scenarios.
Shrinking Operations
flowchart TD subgraph subGraph2["Implementation Details"] C1["Calculate unmap_size = size - new_size"] C2["Call backend.unmap()"] C3["Update va_range boundaries"] C4["Return MappingResult"] B1["Original Area: [start, end)"] A1["Original Area: [start, end)"] end A1 --> A2 A2 --> A3 B1 --> B2 B2 --> B3
shrink_left Implementation:
- Calculates unmap size as
current_size - new_size
- Calls
backend.unmap(start, unmap_size, page_table)
- Updates
va_range.start
by addingunmap_size
- Returns error if backend operation fails
shrink_right Implementation:
- Calculates unmap size as
current_size - new_size
- Calls
backend.unmap(start + new_size, unmap_size, page_table)
- Updates
va_range.end
by subtractingunmap_size
- Returns error if backend operation fails
Sources: src/area.rs(L112 - L139)
Area Splitting
The split
method enables dividing a single memory area into two independent areas:
flowchart TD subgraph subGraph1["After Split at pos"] B1["Left Area [start, pos)"] B2["Right Area [pos, end)"] end subgraph subGraph0["Before Split"] A1["MemoryArea [start, end)"] end subgraph Requirements["Requirements"] C1["start < pos < end"] C2["Both parts non-empty"] C3["Backend implements Clone"] end A1 --> B1 A1 --> B2
Split Implementation Details:
- Input Validation: Ensures
start < pos < end
to prevent empty areas - Left Area: Original area with
end
updated topos
- Right Area: New area created with
backend.clone()
- Return Value:
Some(new_area)
on success,None
on invalid position
Sources: src/area.rs(L141 - L163)
Type Relationships and Generic Design
The memory area system uses a sophisticated generic design to provide flexibility while maintaining type safety.
Generic Type Parameter Flow
flowchart TD subgraph subGraph2["External Dependencies"] VA["VirtAddr (memory_addr crate)"] PD["PhantomData<(F,P)>"] end subgraph subGraph1["Core Types"] MA["MemoryArea"] MB["MappingBackend"] VR["VirtAddrRange"] end subgraph subGraph0["Type Parameters"] F["F: Copy (Flags)"] P["P (Page Table)"] B["B: MappingBackend"] end note1["F must implement Copy for flag operations"] note2["B must implement Clone for area splitting"] note3["PhantomData enables P parameter without runtime cost"] B --> MA B --> MB F --> MA F --> MB P --> MA P --> MB PD --> MA VA --> VR VR --> MA
Type Parameter Constraints:
- F: Copy: Enables efficient flag copying during operations
- P: Unconstrained page table type for maximum flexibility
- B: MappingBackend<F,P>: Ensures backend compatibility with flag and page table types
Key Design Benefits:
- Zero-cost abstraction: PhantomData eliminates runtime overhead
- Type safety: Generic constraints prevent mismatched components
- Flexibility: Different flag encodings and page table implementations supported
Sources: src/area.rs(L29 - L34) src/area.rs(L15)
MemorySet Collection Management
Relevant source files
This document covers the internal collection management mechanisms of MemorySet
, focusing on how it organizes, tracks, and manipulates collections of memory areas using a BTreeMap
data structure. It examines overlap detection algorithms, area lifecycle operations, and complex manipulation procedures like splitting and shrinking.
For information about individual MemoryArea
objects and the MappingBackend
trait, see MemoryArea and MappingBackend. For the public API interface, see Public API and Error Handling.
Core Data Structure Organization
The MemorySet
struct uses a BTreeMap<VirtAddr, MemoryArea<F, P, B>>
as its primary storage mechanism, where the key is the starting virtual address of each memory area. This design provides efficient logarithmic-time operations for address-based queries and maintains areas in sorted order by their start addresses.
MemorySet BTreeMap Organization
flowchart TD subgraph subGraph2["Key Properties"] Sorted["Keys sorted by address"] Unique["Each key is area.start()"] NoOverlap["No overlapping ranges"] end subgraph subGraph1["BTreeMap Structure"] Key1["va!(0x1000)"] Key2["va!(0x3000)"] Key3["va!(0x5000)"] Area1["MemoryArea[0x1000-0x2000]"] Area2["MemoryArea[0x3000-0x4000]"] Area3["MemoryArea[0x5000-0x6000]"] end subgraph MemorySet["MemorySet<F,P,B>"] BTree["BTreeMap<VirtAddr, MemoryArea>"] end BTree --> Key1 BTree --> Key2 BTree --> Key3 BTree --> NoOverlap BTree --> Sorted BTree --> Unique Key1 --> Area1 Key2 --> Area2 Key3 --> Area3
Sources: src/set.rs(L9 - L11)
Basic Collection Operations
The MemorySet
provides fundamental collection operations that leverage the BTreeMap
's sorted structure:
Operation | Method | Time Complexity | Purpose |
---|---|---|---|
Count areas | len() | O(1) | Returns number of areas |
Check emptiness | is_empty() | O(1) | Tests if collection is empty |
Iterate areas | iter() | O(n) | Provides iterator over all areas |
Find by address | find(addr) | O(log n) | Locates area containing address |
The find()
method demonstrates efficient address lookup using range queries on the BTreeMap
:
Address Lookup Algorithm
flowchart TD Start["find(addr)"] RangeQuery["areas.range(..=addr).last()"] GetCandidate["Get last area with start ≤ addr"] CheckContains["Check if area.va_range().contains(addr)"] ReturnArea["Return Some(area)"] ReturnNone["Return None"] CheckContains --> ReturnArea CheckContains --> ReturnNone GetCandidate --> CheckContains RangeQuery --> GetCandidate Start --> RangeQuery
Sources: src/set.rs(L21 - L55)
Overlap Detection and Management
The overlaps()
method implements efficient overlap detection by checking at most two adjacent areas in the sorted collection, rather than scanning all areas:
Overlap Detection Strategy
flowchart TD CheckOverlap["overlaps(range)"] CheckBefore["Check area before range.start"] CheckAfter["Check area at/after range.start"] BeforeRange["areas.range(..range.start).last()"] AfterRange["areas.range(range.start..).next()"] TestBeforeOverlap["before.va_range().overlaps(range)"] TestAfterOverlap["after.va_range().overlaps(range)"] ReturnTrue1["Return true"] ReturnTrue2["Return true"] ContinueAfter["Continue to after check"] ReturnFalse["Return false"] AfterRange --> TestAfterOverlap BeforeRange --> TestBeforeOverlap CheckAfter --> AfterRange CheckBefore --> BeforeRange CheckOverlap --> CheckAfter CheckOverlap --> CheckBefore TestAfterOverlap --> ReturnFalse TestAfterOverlap --> ReturnTrue2 TestBeforeOverlap --> ContinueAfter TestBeforeOverlap --> ReturnTrue1
This algorithm achieves O(log n) complexity by exploiting the sorted nature of the BTreeMap
and the non-overlapping invariant of stored areas.
Sources: src/set.rs(L36 - L49)
Memory Area Addition and Conflict Resolution
The map()
operation handles the complex process of adding new areas while managing potential conflicts:
Map Operation Flow
sequenceDiagram participant Client as Client participant MemorySet as MemorySet participant BTreeMap as BTreeMap participant MemoryArea as MemoryArea Client ->> MemorySet: "map(area, page_table, unmap_overlap)" MemorySet ->> MemorySet: "Validate area.va_range().is_empty()" MemorySet ->> MemorySet: "overlaps(area.va_range())" alt "Overlap detected" alt "unmap_overlap = true" MemorySet ->> MemorySet: "unmap(area.start(), area.size(), page_table)" Note over MemorySet: "Remove conflicting areas" else "unmap_overlap = false" MemorySet -->> Client: "MappingError::AlreadyExists" end end MemorySet ->> MemoryArea: "area.map_area(page_table)" MemoryArea -->> MemorySet: "MappingResult" MemorySet ->> BTreeMap: "insert(area.start(), area)" MemorySet -->> Client: "Success"
Sources: src/set.rs(L85 - L114)
Complex Area Manipulation Operations
Unmap Operation with Area Splitting
The unmap()
operation implements sophisticated area manipulation, including splitting areas when the unmapped region falls in the middle of an existing area:
Unmap Cases and Transformations
flowchart TD subgraph subGraph3["Case 4: Middle Split"] subgraph subGraph1["Case 2: Right Boundary Intersection"] C4Before["[████████████████][unmap]"] C4After1["[██] [██]split into two"] C3Before["[████████████][unmap]"] C3After1["[████]shrink_left"] C2Before["[██████████████][unmap]"] C2After1["[████]shrink_right"] C1Before["[████████████]Existing Area"] C1After1["(removed)"] subgraph subGraph2["Case 3: Left Boundary Intersection"] subgraph subGraph0["Case 1: Full Containment"] C4Before["[████████████████][unmap]"] C4After1["[██] [██]split into two"] C3Before["[████████████][unmap]"] C3After1["[████]shrink_left"] C2Before["[██████████████][unmap]"] C2After1["[████]shrink_right"] C1Before["[████████████]Existing Area"] C1After1["(removed)"] end end end end C1Before --> C1After1 C2Before --> C2After1 C3Before --> C3After1 C4Before --> C4After1
The implementation handles these cases through a three-phase process:
- Full Removal: Remove areas completely contained in the unmap range using
retain()
- Left Boundary Processing: Shrink or split areas that intersect the left boundary
- Right Boundary Processing: Shrink areas that intersect the right boundary
Sources: src/set.rs(L116 - L169)
Protect Operation with Multi-way Splitting
The protect()
operation changes memory flags within a range and may require splitting existing areas into multiple parts:
Protect Operation Area Handling
Sources: src/set.rs(L180 - L247)
Free Space Management
The find_free_area()
method locates unallocated address ranges by examining gaps between consecutive areas:
Free Area Search Algorithm
flowchart TD FindFree["find_free_area(hint, size, limit)"] InitLastEnd["last_end = max(hint, limit.start)"] IterateAreas["For each (addr, area) in areas"] CheckGap["if last_end + size <= addr"] ReturnGap["Return Some(last_end)"] UpdateEnd["last_end = area.end()"] NextArea["Continue to next area"] NoMoreAreas["End of areas reached"] CheckFinalGap["if last_end + size <= limit.end"] ReturnFinal["Return Some(last_end)"] ReturnNone["Return None"] CheckFinalGap --> ReturnFinal CheckFinalGap --> ReturnNone CheckGap --> ReturnGap CheckGap --> UpdateEnd FindFree --> InitLastEnd InitLastEnd --> IterateAreas IterateAreas --> CheckGap IterateAreas --> NoMoreAreas NextArea --> IterateAreas NoMoreAreas --> CheckFinalGap UpdateEnd --> NextArea
Sources: src/set.rs(L57 - L83)
Collection Cleanup Operations
The clear()
method provides atomic cleanup of all memory areas and their underlying mappings:
// Unmaps all areas from the page table, then clears the collection
pub fn clear(&mut self, page_table: &mut P) -> MappingResult
This operation iterates through all areas, calls unmap_area()
on each, and then clears the BTreeMap
if all unmapping operations succeed.
Sources: src/set.rs(L171 - L178)
Public API and Error Handling
Relevant source files
This document covers the public interface exposed by the memory_set
crate and its error handling mechanisms. It focuses on what the library makes available to users and how errors are represented and propagated throughout the system.
For implementation details of the individual components, see MemoryArea and MappingBackend and MemorySet Collection Management. For practical usage examples, see Basic Usage Patterns.
Public API Structure
The memory_set
crate exposes a clean public interface consisting of three main components and a structured error handling system. The public API is defined through selective re-exports in the library root.
Public Components
The library exposes exactly three primary types to users:
Component | Type | Purpose |
---|---|---|
MemorySet<F,PT,B> | Generic struct | Top-level memory management container |
MemoryArea<F,B> | Generic struct | Individual memory region representation |
MappingBackend<F,PT> | Trait | Backend interface for page table operations |
Public API Export Structure
flowchart TD subgraph subGraph3["Internal Modules"] SET_MOD["set module"] AREA_MOD["area module"] MS_IMPL["MemorySet implementation"] MA_IMPL["MemoryArea implementation"] MB_TRAIT["MappingBackend trait"] end subgraph subGraph2["src/lib.rs - Public Interface"] LIB["lib.rs"] subgraph subGraph1["Error Types"] ME["pub enum MappingError"] MR["pub type MappingResult"] end subgraph Re-exports["Re-exports"] MS_EXPORT["pub use self::set::MemorySet"] MA_EXPORT["pub use self::area::MemoryArea"] MB_EXPORT["pub use self::area::MappingBackend"] end end AREA_MOD --> MA_IMPL AREA_MOD --> MB_TRAIT LIB --> MA_EXPORT LIB --> MB_EXPORT LIB --> ME LIB --> MR LIB --> MS_EXPORT MA_EXPORT --> AREA_MOD MB_EXPORT --> AREA_MOD MS_EXPORT --> SET_MOD SET_MOD --> MS_IMPL
Sources: src/lib.rs(L12 - L13)
Generic Type Parameters
All public components use consistent generic type parameters that provide flexibility while maintaining type safety:
F: Copy
- Memory flags type (e.g., read/write/execute permissions)PT
- Page table implementation typeB: MappingBackend<F,PT>
- Concrete backend implementation
This design allows users to plug in their own flag representations, page table structures, and backend implementations while ensuring type compatibility across the system.
Sources: src/lib.rs(L1 - L28)
Error Handling System
The library implements a structured error handling approach using a custom error type and result wrapper that provides clear feedback about operation failures.
MappingError Variants
Error Type Definition
flowchart TD subgraph subGraph2["Usage in Results"] MR["MappingResult<T>"] RES_OK["Ok(T)"] RES_ERR["Err(MappingError)"] end subgraph subGraph1["Error Contexts"] IP_DESC["Invalid addr, size, flags, etc."] AE_DESC["Range overlaps existing mapping"] BS_DESC["Backend page table corrupted"] end subgraph subGraph0["MappingError Enum"] ME["MappingError"] IP["InvalidParam"] AE["AlreadyExists"] BS["BadState"] end AE --> AE_DESC BS --> BS_DESC IP --> IP_DESC ME --> AE ME --> BS ME --> IP MR --> RES_ERR MR --> RES_OK RES_ERR --> ME
The error system defines three specific failure modes:
Error Variant | Description | Common Causes |
---|---|---|
InvalidParam | Invalid operation parameters | Misaligned addresses, zero sizes, invalid flags |
AlreadyExists | Range conflicts with existing mapping | Overlapping memory regions without unmap permission |
BadState | Backend page table corruption | Hardware errors, concurrent modification |
Sources: src/lib.rs(L15 - L24)
Result Type Pattern
The library uses a type alias MappingResult<T>
that standardizes error handling across all operations:
pub type MappingResult<T = ()> = Result<T, MappingError>;
This pattern allows operations to return either successful results with typed values or structured error information. The default unit type ()
accommodates operations that succeed without returning data.
Sources: src/lib.rs(L26 - L27)
API Design Patterns
Error Propagation Flow
Error Handling Throughout the System
sequenceDiagram participant UserCode as User Code participant MemorySet as MemorySet participant MappingBackend as MappingBackend participant PageTable as Page Table participant MA as MA UserCode ->> MemorySet: "map(area, page_table, flags)" alt "Parameter Validation" MemorySet ->> MemorySet: "Validate inputs" MemorySet -->> UserCode: "MappingResult::Err(InvalidParam)" end alt "Overlap Detection" MemorySet ->> MemorySet: "Check for overlaps" MemorySet -->> UserCode: "MappingResult::Err(AlreadyExists)" end MemorySet ->> MA: "Get backend reference" MA ->> MappingBackend: "map(start, size, flags, page_table)" MappingBackend ->> PageTable: "Set page table entries" alt "Backend Failure" PageTable -->> MappingBackend: "Hardware error" MappingBackend -->> MA: "MappingResult::Err(BadState)" MA -->> MemorySet: "Propagate error" MemorySet -->> UserCode: "MappingResult::Err(BadState)" else "Success Path" PageTable -->> MappingBackend: "Success" MappingBackend -->> MA: "MappingResult::Ok(())" MA -->> MemorySet: "Success" MemorySet -->> UserCode: "MappingResult::Ok(())" end
Sources: src/lib.rs(L15 - L27)
Trait-Based Extension
The MappingBackend
trait allows users to implement custom page table management while maintaining compatibility with the error handling system. All backend implementations must return MappingResult<T>
values, ensuring consistent error propagation.
Backend Implementation Requirements
flowchart TD subgraph subGraph2["Error Contract"] ALL_METHODS["All methods return MappingResult<T>"] ERROR_TYPES["Can return any MappingError variant"] CONSISTENT["Consistent error handling across implementations"] end subgraph subGraph1["User Implementation"] CUSTOM_BACKEND["CustomBackend"] IMPL_BLOCK["impl MappingBackend<F,PT> for CustomBackend"] end subgraph subGraph0["MappingBackend Trait"] MB_TRAIT["MappingBackend<F,PT>"] MAP_METHOD["fn map(...) -> MappingResult<()>"] UNMAP_METHOD["fn unmap(...) -> MappingResult<()>"] PROTECT_METHOD["fn protect(...) -> MappingResult<()>"] end ALL_METHODS --> ERROR_TYPES CUSTOM_BACKEND --> IMPL_BLOCK ERROR_TYPES --> CONSISTENT IMPL_BLOCK --> MB_TRAIT MAP_METHOD --> ALL_METHODS MB_TRAIT --> MAP_METHOD MB_TRAIT --> PROTECT_METHOD MB_TRAIT --> UNMAP_METHOD
Sources: src/lib.rs(L12 - L13)
Usage Interface Characteristics
Type Safety Guarantees
The public API enforces several compile-time guarantees:
- Backend Compatibility:
B: MappingBackend<F,PT>
ensures backends match flag and page table types - Flag Consistency:
F: Copy
requirement enables efficient flag operations - Generic Flexibility: Users can substitute their own types while maintaining safety
Memory Management Operations
The public interface supports standard memory management operations through the MemorySet
type:
- Mapping: Create new memory regions with specified permissions
- Unmapping: Remove existing memory regions
- Protection: Modify permissions on existing regions
- Querying: Inspect current memory layout and permissions
All operations return MappingResult<T>
values that must be handled by user code, preventing silent failures and ensuring error visibility.
Sources: src/lib.rs(L1 - L28)
Usage and Examples
Relevant source files
This document provides practical guidance for using the memory_set
crate, including common usage patterns, complete working examples, and best practices. The examples demonstrate how to create memory mappings, manage overlapping areas, and implement custom backends for different page table systems.
For detailed implementation specifics of the core components, see Implementation Details. For information about project setup and dependencies, see Development and Project Setup.
Quick Start Example
The most basic usage involves creating a MemorySet
with a custom backend implementation and using it to manage memory mappings. The following example from the README demonstrates the fundamental operations:
flowchart TD subgraph subGraph1["Required Components"] Backend["MockBackend: MappingBackend"] PT["MockPageTable: [u8; MAX_ADDR]"] Flags["MockFlags: u8"] Area["MemoryArea::new(addr, size, flags, backend)"] end subgraph subGraph0["Basic Usage Flow"] Create["MemorySet::new()"] Map["map(MemoryArea, PageTable, unmap_overlap)"] Unmap["unmap(start, size, PageTable)"] Query["find(addr) / iter()"] end Area --> Map Backend --> Map Create --> Map Flags --> Area Map --> Unmap PT --> Map Unmap --> Query
Basic Usage Pattern Flow
Sources: README.md(L18 - L88)
The core workflow requires implementing the MappingBackend
trait for your specific page table system, then using MemorySet
to manage collections of MemoryArea
objects. Here's the essential pattern from README.md(L33 - L48) :
Operation | Purpose | Key Parameters |
---|---|---|
MemorySet::new() | Initialize empty memory set | None |
map() | Add new memory mapping | MemoryArea, page table, overlap handling |
unmap() | Remove memory mapping | Start address, size, page table |
find() | Locate area containing address | Virtual address |
iter() | Enumerate all areas | None |
Backend Implementation Pattern
Every usage of memory_set
requires implementing the MappingBackend
trait. The implementation defines how memory operations interact with your specific page table system:
flowchart TD subgraph subGraph2["Return Values"] Success["true: Operation succeeded"] Failure["false: Operation failed"] end subgraph subGraph1["Page Table Operations"] SetEntries["Set page table entries"] ClearEntries["Clear page table entries"] UpdateEntries["Update entry flags"] end subgraph subGraph0["MappingBackend Implementation"] Map["map(start, size, flags, pt)"] Unmap["unmap(start, size, pt)"] Protect["protect(start, size, new_flags, pt)"] end ClearEntries --> Failure ClearEntries --> Success Map --> SetEntries Protect --> UpdateEntries SetEntries --> Failure SetEntries --> Success Unmap --> ClearEntries UpdateEntries --> Failure UpdateEntries --> Success
Backend Implementation Requirements
Sources: README.md(L51 - L87) src/tests.rs(L15 - L51)
The backend implementation handles the actual page table manipulation. Each method returns bool
indicating success or failure:
map()
: Sets page table entries for the specified range if all entries are currently unmappedunmap()
: Clears page table entries for the specified range if all entries are currently mappedprotect()
: Updates flags for page table entries in the specified range if all entries are currently mapped
Memory Area Management Operations
The MemorySet
provides sophisticated area management that handles overlaps, splits areas when needed, and maintains sorted order for efficient operations:
sequenceDiagram participant Client as Client participant MemorySet as MemorySet participant BTreeMap as BTreeMap participant MemoryArea as MemoryArea participant Backend as Backend Note over Client,Backend: Mapping with Overlap Resolution Client ->> MemorySet: "map(area, pt, unmap_overlap=true)" MemorySet ->> BTreeMap: "Check for overlapping areas" BTreeMap -->> MemorySet: "Found overlaps" MemorySet ->> MemorySet: "Remove/split overlapping areas" MemorySet ->> Backend: "unmap() existing regions" MemorySet ->> Backend: "map() new region" Backend -->> MemorySet: "Success" MemorySet ->> BTreeMap: "Insert new area" Note over Client,Backend: Unmapping that Splits Areas Client ->> MemorySet: "unmap(start, size, pt)" MemorySet ->> BTreeMap: "Find affected areas" loop "For each affected area" MemorySet ->> MemoryArea: "Check overlap relationship" alt "Partially overlapping" MemorySet ->> MemoryArea: "split() at boundaries" MemoryArea -->> MemorySet: "New area created" end MemorySet ->> Backend: "unmap() region" end MemorySet ->> BTreeMap: "Update area collection"
Complex Memory Operations Sequence
Sources: src/tests.rs(L152 - L226) README.md(L42 - L48)
Common Usage Patterns
Based on the test examples, several common patterns emerge for different memory management scenarios:
Pattern 1: Basic Mapping and Unmapping
src/tests.rs(L84 - L104) demonstrates mapping non-overlapping areas in a regular pattern, followed by querying operations.
Pattern 2: Overlap Resolution
src/tests.rs(L114 - L127) shows how to handle mapping conflicts using the unmap_overlap
parameter to automatically resolve overlaps.
Pattern 3: Area Splitting
src/tests.rs(L166 - L192) illustrates how unmapping operations can split existing areas into multiple smaller areas when the unmapped region falls within an existing area.
Pattern 4: Protection Changes
src/tests.rs(L252 - L285) demonstrates using protect()
operations to change memory permissions, which can also result in area splitting when applied to partial ranges.
Error Handling Patterns
The crate uses MappingResult<T>
for error handling, with MappingError
variants indicating different failure modes:
Error Type | Cause | Common Resolution |
---|---|---|
AlreadyExists | Mapping overlaps existing area | Useunmap_overlap=trueor unmap manually |
Backend failures | Page table operation failed | Check page table state and permissions |
Sources: src/tests.rs(L114 - L121) src/tests.rs(L53 - L66)
Type System Usage
The generic type system allows customization for different environments:
flowchart TD subgraph subGraph2["Instantiated Types"] MS["MemorySet"] MA["MemoryArea"] end subgraph subGraph1["Concrete Example Types"] MockFlags["MockFlags = u8"] MockPT["MockPageTable = [u8; MAX_ADDR]"] MockBackend["MockBackend struct"] end subgraph subGraph0["Generic Parameters"] F["F: Copy (Flags Type)"] PT["PT (Page Table Type)"] B["B: MappingBackend"] end B --> MockBackend F --> MockFlags MockBackend --> MA MockBackend --> MS MockFlags --> MA MockFlags --> MS MockPT --> MS PT --> MockPT
Type System Instantiation Pattern
Sources: README.md(L22 - L34) src/tests.rs(L5 - L13)
The examples use simple types (u8
for flags, byte array for page table) but the system supports any types that meet the trait bounds. This flexibility allows integration with different operating system kernels, hypervisors, or memory management systems that have varying page table structures and flag representations.
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)
Advanced Examples and Testing
Relevant source files
This page covers advanced usage patterns, comprehensive testing strategies, and the MockBackend implementation that demonstrates the full capabilities of the memory_set crate. It focuses on complex memory management scenarios, sophisticated testing patterns, and how to implement custom backends for specialized use cases.
For basic usage patterns and simple examples, see Basic Usage Patterns. For implementation details of the core types, see Implementation Details.
MockBackend Implementation Pattern
The memory_set crate includes a comprehensive mock implementation that serves both as a testing framework and as an example of how to implement the MappingBackend
trait for custom use cases.
Mock Type Definitions
The testing framework defines simplified types that demonstrate the generic nature of the memory management system:
flowchart TD subgraph subGraph2["Array-Based Page Table"] array_entry["pt[addr] = flags"] zero_check["pt[addr] == 0 (unmapped)"] flag_update["pt[addr] = new_flags"] end subgraph subGraph1["MappingBackend Implementation"] map_method["map(start, size, flags, pt)"] unmap_method["unmap(start, size, pt)"] protect_method["protect(start, size, new_flags, pt)"] end subgraph subGraph0["Mock Type System"] MF["MockFlags = u8"] MPT["MockPageTable = [MockFlags; MAX_ADDR]"] MB["MockBackend (struct)"] MMS["MockMemorySet = MemorySet<MockFlags, MockPageTable, MockBackend>"] end MB --> MMS MB --> map_method MB --> protect_method MB --> unmap_method MF --> MB MPT --> MB MPT --> array_entry MPT --> flag_update MPT --> zero_check map_method --> array_entry protect_method --> flag_update unmap_method --> zero_check
MockBackend Implementation Strategy
The MockBackend
uses a simple array where each index represents a virtual address and the value represents the mapping flags. This approach enables:
- Direct address-to-flag mapping for O(1) lookups
- Simple validation of mapping state
- Easy verification of operations in tests
Sources: src/tests.rs(L5 - L13) src/tests.rs(L15 - L51)
Backend Operation Implementation
The mock implementation demonstrates the three core operations required by the MappingBackend
trait:
sequenceDiagram participant Test as Test participant MockBackend as MockBackend participant MockPageTable as MockPageTable Note over Test,MockPageTable: Mapping Operation Test ->> MockBackend: map(start, size, flags, pt) MockBackend ->> MockPageTable: Check pt[start..start+size] == 0 alt All entries unmapped MockBackend ->> MockPageTable: Set pt[addr] = flags for range MockBackend -->> Test: true (success) else Some entries already mapped MockBackend -->> Test: false (failure) end Note over Test,MockPageTable: Unmapping Operation Test ->> MockBackend: unmap(start, size, pt) MockBackend ->> MockPageTable: Check pt[start..start+size] != 0 alt All entries mapped MockBackend ->> MockPageTable: Set pt[addr] = 0 for range MockBackend -->> Test: true (success) else Some entries already unmapped MockBackend -->> Test: false (failure) end Note over Test,MockPageTable: Protection Operation Test ->> MockBackend: protect(start, size, new_flags, pt) MockBackend ->> MockPageTable: Check pt[start..start+size] != 0 alt All entries mapped MockBackend ->> MockPageTable: Set pt[addr] = new_flags for range MockBackend -->> Test: true (success) else Some entries unmapped MockBackend -->> Test: false (failure) end
Key Implementation Details:
- Validation First: Each operation validates the current state before making changes
- Atomic Failure: Operations fail completely if any part of the range is in an invalid state
- Simple State Model: Uses 0 for unmapped, non-zero for mapped with specific flags
Sources: src/tests.rs(L16 - L24) src/tests.rs(L26 - L34) src/tests.rs(L36 - L50)
Testing Utilities and Assertion Framework
The test suite includes specialized utilities for testing memory management operations:
Custom Assertion Macros
Macro | Purpose | Usage Pattern |
---|---|---|
assert_ok! | Verify operation success | assert_ok!(set.map(area, &mut pt, false)) |
assert_err! | Verify operation failure | assert_err!(operation, ExpectedError) |
Debug and Inspection Tools
flowchart TD subgraph subGraph1["Verification Methods"] len_check["set.len() validation"] pt_check["Page table state verification"] area_check["Individual area validation"] range_check["Address range verification"] end subgraph subGraph0["Testing Infrastructure"] dump["dump_memory_set()"] lock["DUMP_LOCK (Mutex)"] iter["set.iter()"] find["set.find(addr)"] end verification["Test Assertions"] area_check --> verification dump --> iter dump --> lock find --> area_check iter --> area_check len_check --> verification pt_check --> verification range_check --> verification
Debug Output Pattern:
The dump_memory_set
function provides synchronized debug output showing the current state of all memory areas, which is essential for understanding complex test scenarios involving area splitting and merging.
Sources: src/tests.rs(L53 - L66) src/tests.rs(L68 - L77)
Complex Memory Management Testing
Overlapping Area Management
The test suite demonstrates sophisticated overlap handling scenarios:
flowchart TD subgraph subGraph2["Resolution Strategy"] check_flag["unmap_overlap = false"] error_result["Returns AlreadyExists"] force_flag["unmap_overlap = true"] success_result["Removes overlapping areas, maps new area"] end subgraph subGraph1["Overlap Scenario"] O["New Area[0x4000, 0x8000) flags=3"] overlap_next["Overlaps next area"] end subgraph subGraph0["Initial Mapping Pattern"] A["Area[0, 0x1000) flags=1"] B["Area[0x2000, 0x3000) flags=2"] C["Area[0x4000, 0x5000) flags=1"] D["Area[0x6000, 0x7000) flags=2"] end O --> C O --> check_flag O --> force_flag O --> overlap_next check_flag --> error_result force_flag --> success_result
Test Coverage Includes:
- Overlap Detection: Testing how the system identifies conflicting memory areas
- Forced Unmapping: Using
unmap_overlap=true
to resolve conflicts automatically - Area Consolidation: Verifying that overlapping areas are properly removed and replaced
Sources: src/tests.rs(L113 - L138)
Advanced Unmapping and Area Splitting
The most complex testing scenarios involve partial unmapping operations that split existing areas:
flowchart TD subgraph subGraph3["Split Operation Types"] shrink_left["Shrink from right"] shrink_right["Shrink from left"] split_middle["Split in middle"] end subgraph subGraph2["Resulting Areas"] left["Area[0x0, 0xc00) flags=1"] gap["Unmapped[0xc00, 0x2400)"] right["Area[0x2400, 0x3000) flags=1"] end subgraph subGraph1["Unmap Operation"] unmap_op["unmap(0xc00, 0x1800)"] end subgraph subGraph0["Original Area State"] orig["Area[0x0, 0x1000) flags=1"] end orig --> unmap_op unmap_op --> gap unmap_op --> left unmap_op --> right unmap_op --> shrink_left unmap_op --> shrink_right unmap_op --> split_middle
Complex Splitting Scenarios:
- Boundary Shrinking: Areas shrink when unmapping occurs at boundaries
- Middle Splitting: Areas split into two when unmapping occurs in the middle
- Cross-Boundary Operations: Unmapping spans multiple areas with different behaviors
Sources: src/tests.rs(L152 - L226)
Protection and Flag Management Testing
Dynamic Protection Changes
The protection testing demonstrates how memory areas adapt to changing access permissions:
stateDiagram-v2 [*] --> init : Map areas with flags=0x7 init : Initial Areas init : Areas with flags=0x7 init --> protect1 : protect(addr, size, update_flags(0x1)) init : Initial Areas init : Areas with flags=0x7 protect1 : After First Protect protect1 : Split areas with flags=0x7,0x1 protect1 --> protect2 : protect(addr, size, update_flags(0x13)) protect1 : After First Protect protect1 : Split areas with flags=0x7,0x1 protect2 : After Second Protect protect2 : Further split with flags=0x7,0x1,0x3 protect2 --> [*] : unmap(0, MAX_ADDR) protect2 : After Second Protect protect2 : Further split with flags=0x7,0x1,0x3 note left of protect1 : ['Areas split at protection boundaries<br>Original: 8 areas<br>Result: 23 areas'] protect1 : After First Protect protect1 : Split areas with flags=0x7,0x1 note left of protect2 : ['Further splitting on nested protection<br>Result: 39 areas'] protect2 : After Second Protect protect2 : Further split with flags=0x7,0x1,0x3
Protection Update Function Pattern: The tests use a closure-based approach for flag updates, allowing complex flag transformation logic while preserving other flag bits:
let update_flags = |new_flags: MockFlags| {
move |old_flags: MockFlags| -> Option<MockFlags> {
// Complex flag transformation logic
}
};
Sources: src/tests.rs(L228 - L324)
Protection Optimization Testing
flowchart TD subgraph subGraph1["Protection Application"] subgraph subGraph0["Protection Optimization"] diff_flags["Different flags"] apply_op["Apply protection"] area_split["Area splitting occurs"] same_flags["Same flags check"] skip_op["Skip operation"] no_split["No area splitting"] end end apply_op --> area_split diff_flags --> apply_op same_flags --> skip_op skip_op --> no_split
Optimization Strategy: The protection system includes optimization to avoid unnecessary operations when the new flags would be identical to existing flags, preventing unnecessary area splitting.
Sources: src/tests.rs(L312 - L316)
Test Scenario Architecture
Comprehensive Test Coverage Matrix
Test Category | Operations Tested | Area Behavior | Validation Focus |
---|---|---|---|
test_map_unmap | Map, Find, Forced Map, Unmap | Overlap handling, Area replacement | Page table consistency, Area count |
test_unmap_split | Partial Unmap, Boundary operations | Area shrinking, Middle splitting | Split correctness, Gap verification |
test_protect | Protection changes, Flag updates | Protection-based splitting | Flag transformation, Optimization |
Testing Pattern Integration
flowchart TD subgraph subGraph1["Validation Strategies"] area_count["Area count verification"] pt_state["Page table state check"] area_props["Individual area properties"] consistency["Cross-validation consistency"] end subgraph subGraph0["Test Execution Flow"] setup["Setup MockMemorySet + MockPageTable"] pattern["Execute test pattern"] validate["Validate results"] debug["Debug output (if needed)"] cleanup["Verify cleanup"] end debug --> cleanup pattern --> validate setup --> pattern validate --> area_count validate --> area_props validate --> consistency validate --> debug validate --> pt_state
Testing Integration Points:
- State Verification: Each test verifies both the MemorySet state and the MockPageTable state
- Debug Integration: Debug output is synchronized and can be enabled for complex test debugging
- Cleanup Validation: Tests ensure complete cleanup to verify the unmapping functionality
Sources: src/tests.rs(L79 - L149) src/tests.rs(L151 - L226) src/tests.rs(L228 - L324)
Development and Project Setup
Relevant source files
This document provides essential information for developers working on or with the memory_set
crate, covering project dependencies, build configuration, supported targets, and development workflows. For implementation details and API usage, see Implementation Details and Usage and Examples.
Project Overview and Structure
The memory_set
crate is designed as a foundational component within the ArceOS ecosystem, providing no_std
compatible memory mapping management capabilities. The project follows standard Rust conventions with additional considerations for embedded and OS development environments.
Project Structure and Dependencies
flowchart TD subgraph Licensing["Licensing"] LIC["GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"] end subgraph Categorization["Categorization"] KW["keywords: [arceos, virtual-memory, memory-area, mmap]"] CAT["categories: [os, memory-management, no-std]"] end subgraph subGraph2["External Dependencies"] MA["memory_addr = 0.2"] end subgraph subGraph1["Package Metadata"] PN["name: memory_set"] PV["version: 0.1.0"] PE["edition: 2021"] PA["author: Yuekai Jia"] end subgraph subGraph0["Project Configuration"] CT["Cargo.toml"] CI[".github/workflows/ci.yml"] end CT --> CAT CT --> KW CT --> LIC CT --> MA CT --> PA CT --> PE CT --> PN CT --> PV
Sources: Cargo.toml(L1 - L16)
The crate maintains minimal external dependencies, requiring only the memory_addr
crate for address type definitions. This design choice supports the no_std
environment requirement and reduces compilation complexity.
Configuration | Value | Purpose |
---|---|---|
Edition | 2021 | Modern Rust language features |
Categories | os,memory-management,no-std | Ecosystem positioning |
License | Triple-licensed | Compatibility with various projects |
Repository | github.com/arceos-org/memory_set | Source code location |
Documentation | docs.rs/memory_set | API documentation |
Sources: Cargo.toml(L1 - L16)
Build Configuration and Target Support
The project supports multiple target architectures, reflecting its intended use in diverse embedded and OS development scenarios. The build configuration accommodates both hosted and bare-metal environments.
Supported Target Architectures
flowchart TD subgraph subGraph2["Bare Metal Targets"] X86N["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] ARM64["aarch64-unknown-none-softfloat"] end subgraph subGraph1["Hosted Targets"] X86L["x86_64-unknown-linux-gnu"] end subgraph subGraph0["CI Target Matrix"] RT["nightly toolchain"] end RT --> ARM64 RT --> RISCV RT --> X86L RT --> X86N
Sources: .github/workflows/ci.yml(L11 - L12)
The build process includes several validation steps:
Step | Command | Purpose |
---|---|---|
Format Check | cargo fmt --all -- --check | Code style enforcement |
Linting | cargo clippy --target TARGET --all-features | Static analysis |
Build | cargo build --target TARGET --all-features | Compilation verification |
Testing | cargo test --target TARGET | Unit test execution |
Sources: .github/workflows/ci.yml(L23 - L30)
Toolchain Requirements
The project requires the nightly Rust toolchain with specific components:
rust-src
: Required for no_std target compilationclippy
: Static analysis and lintingrustfmt
: Code formatting enforcement
Toolchain Setup Process
sequenceDiagram participant Developer as Developer participant CISystem as CI System participant RustToolchain as Rust Toolchain participant TargetBuild as Target Build Developer ->> CISystem: "Push/PR trigger" CISystem ->> RustToolchain: "Install nightly toolchain" RustToolchain ->> CISystem: "rust-src, clippy, rustfmt components" CISystem ->> RustToolchain: "rustc --version --verbose" loop "For each target" CISystem ->> TargetBuild: "cargo fmt --check" CISystem ->> TargetBuild: "cargo clippy --target TARGET" CISystem ->> TargetBuild: "cargo build --target TARGET" alt "x86_64-unknown-linux-gnu only" CISystem ->> TargetBuild: "cargo test --target TARGET" end end
Sources: .github/workflows/ci.yml(L14 - L30)
Development Workflow and CI Pipeline
The continuous integration system enforces code quality standards and validates functionality across all supported targets. The workflow is designed to catch issues early and maintain consistency.
CI Job Configuration
The CI pipeline consists of two primary jobs:
- Main CI Job (
ci
): Validates code across all target architectures - Documentation Job (
doc
): Builds and deploys API documentation
CI Pipeline Architecture
flowchart TD subgraph subGraph5["Documentation Job"] DOCBUILD["cargo doc --no-deps"] DEPLOY["Deploy to gh-pages"] end subgraph subGraph4["Validation Steps"] FMT["cargo fmt --check"] CLIP["cargo clippy"] BUILD["cargo build"] TEST["cargo test (linux only)"] end subgraph subGraph3["CI Job Matrix"] subgraph Targets["Targets"] T1["x86_64-unknown-linux-gnu"] T2["x86_64-unknown-none"] T3["riscv64gc-unknown-none-elf"] T4["aarch64-unknown-none-softfloat"] end subgraph Toolchain["Toolchain"] NIGHTLY["nightly"] end end subgraph subGraph0["Trigger Events"] PUSH["push events"] PR["pull_request events"] end CI["CI"] BUILD --> TEST CI --> DOCBUILD CLIP --> BUILD DOCBUILD --> DEPLOY FMT --> CLIP NIGHTLY --> T1 NIGHTLY --> T2 NIGHTLY --> T3 NIGHTLY --> T4 PR --> CI PUSH --> CI T1 --> FMT T2 --> FMT T3 --> FMT T4 --> FMT
Sources: .github/workflows/ci.yml(L1 - L56)
Documentation Generation
The documentation workflow includes specialized configuration for maintaining high-quality API documentation:
- RUSTDOCFLAGS:
-D rustdoc::broken_intra_doc_links -D missing-docs
- Deployment: Automatic GitHub Pages deployment on main branch
- Index Generation: Automatic redirect to crate documentation
Sources: .github/workflows/ci.yml(L40 - L48)
Testing Strategy
Testing is performed only on the x86_64-unknown-linux-gnu
target due to the hosted environment requirements for test execution. The test command includes --nocapture
to display output from test functions.
Test Execution Conditions
flowchart TD START["CI Job Start"] CHECK_TARGET["Target == x86_64-unknown-linux-gnu?"] RUN_TESTS["cargo test --target TARGET -- --nocapture"] SKIP_TESTS["Skip unit tests"] END["Job Complete"] CHECK_TARGET --> RUN_TESTS CHECK_TARGET --> SKIP_TESTS RUN_TESTS --> END SKIP_TESTS --> END START --> CHECK_TARGET
Sources: .github/workflows/ci.yml(L28 - L30)
ArceOS Ecosystem Integration
The memory_set
crate is positioned as a foundational component within the broader ArceOS operating system project. This integration influences several design decisions:
- Homepage: Points to the main ArceOS project (
github.com/arceos-org/arceos
) - Keywords: Include
arceos
for discoverability within the ecosystem - License: Triple-licensing supports integration with various ArceOS components
- No-std Compatibility: Essential for kernel-level memory management
Sources: Cargo.toml(L8 - L12)
The minimal dependency footprint and broad target support make this crate suitable for integration across different ArceOS configurations and deployment scenarios.
Dependencies and Configuration
Relevant source files
This document covers the project dependencies, build configuration, and integration aspects of the memory_set
crate within the broader ArceOS ecosystem. It provides essential information for developers who need to understand how to build, configure, and integrate this memory management library.
For information about the actual usage patterns and API details, see Basic Usage Patterns. For development workflow and testing setup, see Development Workflow.
Package Configuration
The memory_set
crate is configured as a foundational memory management library designed specifically for operating system development. The package configuration reflects its role as a core component in the ArceOS ecosystem.
Crate Metadata
Configuration | Value |
---|---|
Package Name | memory_set |
Version | 0.1.0 |
Rust Edition | 2021 |
License | Triple licensed: GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0 |
Categories | os,memory-management,no-std |
The crate is explicitly designed for no-std
environments, making it suitable for kernel-level and embedded development where the standard library is not available.
flowchart TD subgraph subGraph2["ArceOS Integration"] HOMEPAGE["homepage = 'github.com/arceos-org/arceos'"] KEYWORDS["keywords = ['arceos', 'virtual-memory', 'memory-area', 'mmap']"] REPO["repository = 'github.com/arceos-org/memory_set'"] end subgraph subGraph1["Target Environment"] NOSTD["no-std compatible"] KERNEL["Kernel development"] EMBED["Embedded systems"] OS["Operating systems"] end subgraph subGraph0["Package Metadata"] PKG["memory_set v0.1.0"] ED["edition = '2021'"] LIC["Triple License"] CAT["Categories: os, memory-management, no-std"] end GPL["GPL-3.0-or-later"] APACHE["Apache-2.0"] MULAN["MulanPSL-2.0"] HOMEPAGE --> PKG KEYWORDS --> PKG LIC --> APACHE LIC --> GPL LIC --> MULAN PKG --> EMBED PKG --> KERNEL PKG --> NOSTD PKG --> OS REPO --> PKG
Crate Package Configuration Structure
Sources: Cargo.toml(L1 - L13)
Dependencies
The memory_set
crate maintains a minimal dependency footprint with only one external dependency, reflecting its design philosophy of being a lightweight, foundational library.
External Dependencies
The crate depends on a single external library:
memory_addr = "0.2"
- Provides core address types and utilities for virtual memory management
This dependency provides the fundamental VirtAddr
and VirtAddrRange
types that are used throughout the memory management system.
flowchart TD subgraph subGraph2["Standard Library"] CORE["core (no-std)"] ALLOC["alloc (BTreeMap)"] end subgraph subGraph1["External Dependencies"] MEMADDR["memory_addr v0.2"] VIRTADDR["VirtAddr"] VIRTADDRRANGE["VirtAddrRange"] end subgraph subGraph0["memory_set Crate"] MS["MemorySet"] MA["MemoryArea"] MB["MappingBackend"] VAR["VirtAddrRange usage"] VA["VirtAddr usage"] end MA --> CORE MA --> VA MA --> VAR MB --> CORE MS --> ALLOC MS --> VA MS --> VAR VA --> VIRTADDR VAR --> VIRTADDRRANGE VIRTADDR --> MEMADDR VIRTADDRRANGE --> MEMADDR
Dependency Graph and Type Flow
Internal Standard Library Usage
While the crate is no-std
compatible, it uses specific components from the Rust standard library:
core
- Fundamental types and traits (available inno-std
)alloc
- ForBTreeMap
collection used inMemorySet
(requires allocator)
The use of BTreeMap
indicates that while the crate is no-std
, it requires an allocator to be available in the target environment.
Sources: Cargo.toml(L14 - L15)
Build Configuration
Rust Edition and Compiler Features
The crate uses Rust Edition 2021, which provides access to modern Rust language features while maintaining compatibility with no-std
environments.
Key build characteristics:
- No standard library dependency - Suitable for kernel and embedded development
- Allocator requirement - Uses
BTreeMap
which requires heap allocation - Generic-heavy design - Relies on Rust's zero-cost abstractions
Target Environment Compatibility
flowchart TD subgraph subGraph2["Required Components"] CORE["core library"] ALLOCLIB["alloc library"] BTREE["BTreeMap support"] end subgraph subGraph1["Supported Targets"] KERNEL["Kernel modules"] RTOS["Real-time OS"] EMBED["Embedded systems"] BAREMETAL["Bare metal"] end subgraph subGraph0["Build Requirements"] RUST2021["Rust Edition 2021"] NOSTD["no-std environment"] ALLOC["Allocator available"] end ALLOC --> ALLOCLIB ALLOCLIB --> BTREE BTREE --> CORE NOSTD --> ALLOC NOSTD --> BAREMETAL NOSTD --> EMBED NOSTD --> KERNEL NOSTD --> RTOS RUST2021 --> NOSTD
Build Environment and Target Compatibility
Sources: Cargo.toml(L4) Cargo.toml(L12)
ArceOS Ecosystem Integration
The memory_set
crate is designed as a foundational component within the ArceOS operating system ecosystem. Its configuration reflects this tight integration while maintaining modularity.
Ecosystem Position
The crate serves as a core memory management building block within ArceOS:
- Homepage: Points to the main ArceOS project (
https://github.com/arceos-org/arceos
) - Keywords: Explicitly includes
"arceos"
along with memory management terms - Repository: Maintains separate repository for modular development
- License: Uses triple licensing compatible with various OS licensing requirements
Integration Points
Aspect | Configuration | Purpose |
---|---|---|
Keywords | "arceos", "virtual-memory", "memory-area", "mmap" | Discoverability and categorization |
Categories | "os", "memory-management", "no-std" | Crate ecosystem positioning |
Documentation | docs.rs/memory_set | Public API documentation |
Homepage | ArceOS main project | Ecosystem context |
The triple licensing scheme (GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0) provides flexibility for integration into various operating system projects with different licensing requirements.
Sources: Cargo.toml(L7 - L12)
Development Environment Setup
Prerequisites
To work with the memory_set
crate, developers need:
- Rust toolchain with Edition 2021 support
- Target environment that supports
no-std
andalloc
- Allocator implementation for the target platform
Build Commands
Standard Rust cargo commands apply:
cargo build
- Build the librarycargo test
- Run tests (requires test environment with allocator)cargo doc
- Generate documentation
The crate's minimal dependency structure ensures fast build times and reduced complexity in integration scenarios.
Sources: Cargo.toml(L1 - L16)
Development Workflow
Relevant source files
This document covers the continuous integration setup, testing strategies, and development environment configuration for contributors to the memory_set crate. It explains how code quality is maintained, how testing is automated, and what tools developers need to work effectively with the codebase.
For information about project dependencies and build configuration, see Dependencies and Configuration. For usage examples and testing patterns, see Advanced Examples and Testing.
CI Pipeline Overview
The memory_set crate uses GitHub Actions for continuous integration, with a comprehensive pipeline that ensures code quality across multiple Rust targets and maintains documentation.
CI Workflow Structure
flowchart TD PR["Push / Pull Request"] CIJob["ci job"] DocJob["doc job"] Matrix["Matrix Strategy"] T1["x86_64-unknown-linux-gnu"] T2["x86_64-unknown-none"] T3["riscv64gc-unknown-none-elf"] T4["aarch64-unknown-none-softfloat"] Steps1["Format → Clippy → Build → Test"] Steps2["Format → Clippy → Build"] Steps3["Format → Clippy → Build"] Steps4["Format → Clippy → Build"] DocBuild["cargo doc --no-deps --all-features"] GHPages["Deploy to gh-pages"] Success["CI Success"] CIJob --> Matrix DocBuild --> GHPages DocJob --> DocBuild GHPages --> Success Matrix --> T1 Matrix --> T2 Matrix --> T3 Matrix --> T4 PR --> CIJob PR --> DocJob Steps1 --> Success Steps2 --> Success Steps3 --> Success Steps4 --> Success T1 --> Steps1 T2 --> Steps2 T3 --> Steps3 T4 --> Steps4
Sources: .github/workflows/ci.yml(L1 - L56)
Target Platform Matrix
The CI pipeline tests across multiple Rust targets to ensure compatibility with different architectures and environments:
Target | Purpose | Testing Level |
---|---|---|
x86_64-unknown-linux-gnu | Standard Linux development | Full (includes unit tests) |
x86_64-unknown-none | Bare metal x86_64 | Build and lint only |
riscv64gc-unknown-none-elf | RISC-V bare metal | Build and lint only |
aarch64-unknown-none-softfloat | ARM64 bare metal | Build and lint only |
Sources: .github/workflows/ci.yml(L11 - L12)
Code Quality Pipeline
Quality Check Sequence
sequenceDiagram participant Developer as "Developer" participant GitHubActions as "GitHub Actions" participant RustCompiler as "Rust Compiler" participant ClippyLinter as "Clippy Linter" participant rustfmt as "rustfmt" Developer ->> GitHubActions: "git push / PR" GitHubActions ->> rustfmt: "cargo fmt --all -- --check" rustfmt -->> GitHubActions: "Format check result" GitHubActions ->> ClippyLinter: "cargo clippy --target TARGET --all-features" ClippyLinter ->> RustCompiler: "Run analysis" RustCompiler -->> ClippyLinter: "Lint results" ClippyLinter -->> GitHubActions: "Clippy results (-A clippy::new_without_default)" GitHubActions ->> RustCompiler: "cargo build --target TARGET --all-features" RustCompiler -->> GitHubActions: "Build result" alt Target is x86_64-unknown-linux-gnu GitHubActions ->> RustCompiler: "cargo test --target TARGET -- --nocapture" RustCompiler -->> GitHubActions: "Test results" end GitHubActions -->> Developer: "CI Status"
Sources: .github/workflows/ci.yml(L22 - L30)
Code Quality Tools Configuration
The CI pipeline enforces several code quality standards:
- Format Checking: Uses
cargo fmt --all -- --check
to ensure consistent code formatting - Linting: Runs
cargo clippy
with custom configuration allowingclippy::new_without_default
- Compilation: Verifies code builds successfully across all target platforms
- Testing: Executes unit tests on the primary Linux target with
--nocapture
flag
Sources: .github/workflows/ci.yml(L22 - L30)
Testing Strategy
Test Execution Environment
flowchart TD subgraph subGraph1["Test Execution"] OnlyLinux["Tests run only on x86_64-unknown-linux-gnu"] NoCapture["--nocapture flag enabled"] UnitTests["Unit tests from lib.rs and modules"] end subgraph subGraph0["Test Environment"] Ubuntu["ubuntu-latest runner"] Nightly["Rust nightly toolchain"] Components["rust-src + clippy + rustfmt"] end subgraph subGraph2["Cross-Platform Validation"] BuildOnly["Build-only testing on bare metal targets"] LintOnly["Clippy linting on all targets"] FormatAll["Format checking across all code"] end BuildOnly --> LintOnly Components --> OnlyLinux LintOnly --> FormatAll Nightly --> OnlyLinux OnlyLinux --> NoCapture OnlyLinux --> UnitTests Ubuntu --> OnlyLinux
Sources: .github/workflows/ci.yml(L28 - L30) .github/workflows/ci.yml(L11 - L19)
Testing Limitations and Strategy
The testing strategy reflects the embedded/systems programming nature of the memory_set crate:
- Full Testing: Only performed on
x86_64-unknown-linux-gnu
where standard library features are available - Build Verification: All bare metal targets are compiled to ensure cross-platform compatibility
- Mock Testing: The crate includes
MockBackend
implementation for testing without real page tables
Sources: .github/workflows/ci.yml(L29 - L30)
Documentation Workflow
Documentation Build and Deployment
flowchart TD subgraph Deployment["Deployment"] DefaultBranch["Is default branch?"] GHPages["Deploy to gh-pages"] SkipDeploy["Skip deployment"] end subgraph subGraph1["Quality Checks"] BrokenLinks["-D rustdoc::broken_intra_doc_links"] MissingDocs["-D missing-docs"] end subgraph subGraph0["Doc Generation"] DocBuild["cargo doc --no-deps --all-features"] DocFlags["RUSTDOCFLAGS checks"] IndexGen["Generate redirect index.html"] end BrokenLinks --> IndexGen DefaultBranch --> GHPages DefaultBranch --> SkipDeploy DocBuild --> DocFlags DocFlags --> BrokenLinks DocFlags --> MissingDocs IndexGen --> DefaultBranch MissingDocs --> IndexGen
Sources: .github/workflows/ci.yml(L32 - L56)
Documentation Quality Standards
The documentation workflow enforces strict quality standards:
- Broken Link Detection:
-D rustdoc::broken_intra_doc_links
fails build on broken documentation links - Missing Documentation:
-D missing-docs
requires all public APIs to have documentation - Automatic Deployment: Documentation is automatically deployed to GitHub Pages on default branch updates
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L44 - L55)
Development Environment Setup
Required Toolchain Components
For local development, contributors need:
# Install nightly Rust toolchain
rustup toolchain install nightly
# Add required components
rustup component add rust-src clippy rustfmt
# Add target platforms for cross-compilation testing
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
Sources: .github/workflows/ci.yml(L15 - L19)
Local Development Commands
To replicate CI checks locally:
# Format check
cargo fmt --all -- --check
# Linting
cargo clippy --all-features -- -A clippy::new_without_default
# Build for different targets
cargo build --target x86_64-unknown-linux-gnu --all-features
cargo build --target x86_64-unknown-none --all-features
# Run tests
cargo test -- --nocapture
# Generate documentation
cargo doc --no-deps --all-features
Sources: .github/workflows/ci.yml(L22 - L30) .github/workflows/ci.yml(L47)
Git Configuration
The project maintains a standard .gitignore
configuration:
/target
- Rust build artifacts/.vscode
- IDE configuration files.DS_Store
- macOS system filesCargo.lock
- Dependency lock file (excluded for libraries)
Sources: .gitignore(L1 - L5)
Overview
Relevant source files
This document provides an introduction to the int_ratio
crate, an efficient integer-based rational arithmetic library designed for embedded systems and performance-critical applications within the ArceOS ecosystem. The crate implements rational number operations using optimized integer arithmetic that avoids expensive division operations.
For detailed implementation specifics of the core Ratio
type, see Ratio Type Implementation. For practical usage examples and patterns, see Usage Guide.
Purpose and Scope
The int_ratio
crate serves as a foundational mathematical library that provides precise rational number arithmetic without relying on floating-point operations or expensive integer division. It is specifically designed for use in embedded systems, bare-metal environments, and other performance-critical scenarios where computational efficiency and deterministic behavior are essential.
Sources: Cargo.toml(L1 - L15) README.md(L1 - L22)
Core Functionality
The crate centers around the Ratio
struct, which represents a rational number as the quotient of two integers. Rather than storing the numerator and denominator directly, the implementation uses an optimized internal representation that enables fast multiplication operations through bit shifting.
Ratio Operations
flowchart TD subgraph Output["Output"] I["u64 results"] J["Ratio instances"] end subgraph subGraph1["Internal Representation"] F["mult: u32"] G["shift: u32"] H["ratio = mult / (1 << shift)"] end subgraph subGraph0["Public API"] A["Ratio::new(num, den)"] B["Ratio::zero()"] C["ratio.mul_trunc(value)"] D["ratio.mul_round(value)"] E["ratio.inverse()"] end A --> F A --> G B --> J C --> I D --> I E --> J F --> H G --> H
Ratio API and Internal Architecture
The Ratio::new()
constructor transforms input numerator and denominator values into an optimized mult
/shift
representation, while mul_trunc()
and mul_round()
provide different precision behaviors for arithmetic operations.
Sources: README.md(L14 - L21)
ArceOS Ecosystem Integration
The int_ratio
crate operates as a core mathematical utility within the broader ArceOS operating system ecosystem, providing rational arithmetic capabilities that support system-level operations requiring precise fractional calculations.
flowchart TD subgraph subGraph2["Application Domains"] H["Timing Systems"] I["Resource Management"] J["Scheduling Algorithms"] K["Performance Monitoring"] end subgraph subGraph1["Target Platforms"] D["x86_64 Linux"] E["x86_64 Bare Metal"] F["RISC-V Embedded"] G["ARM64 Embedded"] end subgraph subGraph0["ArceOS Ecosystem"] A["ArceOS Operating System"] B["int_ratio crate"] C["System Components"] end A --> B B --> C B --> D B --> E B --> F B --> G C --> H C --> I C --> J C --> K
ArceOS Integration and Platform Support
The crate provides cross-platform rational arithmetic services that enable ArceOS system components to perform precise fractional calculations across multiple hardware architectures.
Sources: Cargo.toml(L8 - L9)
Key Design Principles
Performance Optimization
The core innovation of int_ratio
lies in its avoidance of expensive division operations during runtime calculations. Instead of performing value * numerator / denominator
, the implementation pre-computes an equivalent mult
and shift
representation that enables the same calculation using fast bit-shift operations: (value * mult) >> shift
.
Precision Control
The crate provides explicit control over rounding behavior through distinct methods:
Method | Behavior | Use Case |
---|---|---|
mul_trunc() | Truncates fractional results | When lower bounds are needed |
mul_round() | Rounds to nearest integer | When best approximation is needed |
Embedded System Compatibility
The library operates in no_std
environments and avoids dependencies that would be unsuitable for embedded or bare-metal development. This makes it particularly valuable for resource-constrained systems where standard library features may not be available.
Sources: Cargo.toml(L11 - L12) README.md(L7 - L10)
Mathematical Foundation
The transformation from traditional rational representation numerator/denominator
to the optimized mult/(1<<shift)
form maintains mathematical precision while enabling performance improvements. This approach ensures that:
- No
u128
division operations are required during multiplication - Bit-shift operations replace expensive division
- Precision is preserved through careful selection of
mult
andshift
values - Results remain deterministic and reproducible across platforms
The example from the README demonstrates this optimization: Ratio(1/3 ~= 1431655765/4294967296)
shows how the fraction 1/3 is represented internally using the optimized format where 4294967296 = 1 << 32
.
Sources: README.md(L18 - L21)
Related Documentation
For implementation details of the core algorithms and internal architecture, see Ratio Type Implementation. For practical examples and common usage patterns, see Usage Guide. Development setup and contribution guidelines are covered in Development Guide.
Ratio Type Implementation
Relevant source files
This document provides a comprehensive overview of the core Ratio
struct implementation, focusing on its innovative multiplication-shift optimization strategy that eliminates expensive division operations. The Ratio
type transforms traditional numerator/denominator
arithmetic into an efficient mult/(1<<shift)
representation optimized for embedded and performance-critical applications.
For detailed internal algorithms and mathematical proofs, see Internal Architecture. For complete method documentation and usage examples, see API Reference. For mathematical foundations and precision analysis, see Mathematical Foundation.
Core Transformation Strategy
The fundamental innovation of the Ratio
type lies in its preprocessing approach that converts rational arithmetic into optimized integer operations.
Ratio Transformation Process
flowchart TD subgraph subGraph0["Performance Benefits"] J["No division in arithmetic"] K["Bit-shift operations only"] L["Maintained precision"] end A["Input: numerator/denominator"] B["new() constructor"] C["Calculate optimal shift value"] D["Calculate mult factor"] E["mult = (numerator << shift) / denominator"] F["Optimize: reduce common factors of 2"] G["Store: mult/(1<"] H["mul_trunc(): (value * mult) >> shift"] I["mul_round(): ((value * mult) + round) >> shift"] A --> B B --> C B --> D C --> E D --> E E --> F F --> G G --> H G --> I G --> L H --> J I --> K
Sources: src/lib.rs(L51 - L84)
Struct Layout and Field Responsibilities
The Ratio
struct maintains both the original rational representation and the optimized computational form:
Field | Type | Purpose | Usage |
---|---|---|---|
numerator | u32 | Original numerator value | Stored forinverse()andDebug |
denominator | u32 | Original denominator value | Stored forinverse()andDebug |
mult | u32 | Optimized multiplier | Used inmul_trunc()andmul_round() |
shift | u32 | Bit-shift amount | Represents power of 2 denominator |
The struct maintains both representations to support mathematical operations like inverse()
while providing optimized arithmetic through the mult
/shift
fields.
Sources: src/lib.rs(L13 - L19)
Constructor Methods and Special Cases
Constructor Method Mapping
flowchart TD subgraph Outcomes["Outcomes"] F["panic!"] G["mult=0, shift=0"] H["Optimization algorithm"] end subgraph subGraph1["Input Validation"] C["denominator == 0 && numerator != 0"] D["numerator == 0"] E["Normal case"] end subgraph subGraph0["Constructor Methods"] A["new(numerator, denominator)"] B["zero()"] end A --> C A --> D A --> E B --> G C --> F D --> G E --> H
Sources: src/lib.rs(L37 - L44) src/lib.rs(L51 - L84)
The new()
constructor implements a sophisticated optimization algorithm:
- Shift Maximization: Starts with
shift = 32
and decreases untilmult
fits inu32
src/lib.rs(L63 - L71) - Precision Optimization: Uses
(numerator << shift + denominator/2) / denominator
for rounding src/lib.rs(L66) - Factor Reduction: Removes common factors of 2 to minimize storage requirements src/lib.rs(L73 - L76)
The special zero()
constructor creates a 0/0
ratio that behaves safely in operations, particularly inverse()
which returns another zero ratio instead of panicking.
Arithmetic Operations Implementation
The core arithmetic methods leverage the mult
/shift
representation for efficient computation:
Operation Implementation Details
flowchart TD subgraph subGraph2["mul_round Implementation"] F["(value as u128 * mult as u128)"] G["Unsupported markdown: list"] H["result >> shift"] I["Rounded u64 result"] end subgraph subGraph1["mul_trunc Implementation"] C["(value as u128 * mult as u128)"] D["result >> shift"] E["Truncated u64 result"] end subgraph Input["Input"] A["value: u64"] B["Ratio{mult, shift}"] end A --> C A --> F B --> C B --> F C --> D D --> E F --> G G --> H H --> I
Sources: src/lib.rs(L114 - L116) src/lib.rs(L130 - L132)
Key implementation characteristics:
- Widening Multiplication: Uses
u128
intermediate to prevent overflow src/lib.rs(L115) src/lib.rs(L131) - Truncation vs Rounding:
mul_trunc()
simply shifts, whilemul_round()
adds(1 << shift >> 1)
for rounding - Bit-Shift Performance: Final division replaced with right-shift operation
- No Division Operations: All arithmetic uses multiplication and bit manipulation
Performance and Precision Characteristics
The Ratio
implementation achieves optimal performance through several design decisions:
Aspect | Traditional Approach | RatioImplementation |
---|---|---|
Division Operations | Required for each calculation | Pre-computed in constructor |
Intermediate Precision | Limited by operand width | Maximized through optimalshift |
Platform Dependencies | Varies by division hardware | Consistent across architectures |
Memory Overhead | Minimal (8 bytes) | Moderate (16 bytes) for optimization |
The Debug
implementation reveals the transformation: Ratio(numerator/denominator ~= mult/(1<<shift))
src/lib.rs(L137 - L145) allowing verification of the optimization quality.
Equality and Comparison Semantics
The PartialEq
implementation compares the optimized representation rather than the original values:
// Equality based on mult and shift, not numerator/denominator
self.mult == other.mult && self.shift == other.shift
This design choice ensures that mathematically equivalent ratios with different original representations are considered equal after optimization.
Sources: src/lib.rs(L148 - L153)
Internal Architecture
Relevant source files
This document explains the internal design and optimization strategies of the Ratio
struct, focusing on how it transforms traditional fractional representation into a high-performance mult/(1<<shift)
format. This page covers the core data structure, field relationships, and the constructor optimization algorithm that enables division-free arithmetic operations.
For information about the public API methods and their usage, see API Reference. For the mathematical principles underlying the optimization strategy, see Mathematical Foundation.
Core Data Structure
The Ratio
struct stores both the original rational number representation and its optimized computational form. This dual representation enables both mathematical correctness and performance optimization.
flowchart TD subgraph subGraph3["Mathematical Equivalence"] G["numerator / denominator ≈ mult / (1 << shift)"] end subgraph subGraph2["Optimized Representation"] F["mult / (1 << shift)"] end subgraph subGraph1["Original Representation"] E["numerator / denominator"] end subgraph subGraph0["Ratio Struct Fields"] A["numerator: u32"] B["denominator: u32"] C["mult: u32"] D["shift: u32"] end A --> E B --> E C --> F D --> F E --> G F --> G
Ratio Struct Field Mapping
The struct maintains four fields that work together to provide both the original rational number and its optimized computational equivalent:
Field | Type | Purpose | Usage |
---|---|---|---|
numerator | u32 | Original fraction numerator | Mathematical operations, inverse calculation |
denominator | u32 | Original fraction denominator | Mathematical operations, inverse calculation |
mult | u32 | Optimized multiplier | Fast arithmetic via(value * mult) >> shift |
shift | u32 | Bit shift amount | Replaces division with bit shifting |
Sources: src/lib.rs(L13 - L19)
Mult/Shift Transformation Algorithm
The core optimization transforms any rational number numerator/denominator
into an equivalent mult/(1<<shift)
representation. This transformation enables replacing expensive division operations with fast bit shifts during arithmetic operations.
flowchart TD subgraph subGraph3["Final Result"] L["Optimized mult, shift values"] end subgraph subGraph2["Factor Reduction"] H["mult % 2 == 0 && shift > 0?"] I["mult /= 2"] J["shift -= 1"] K["Continue reduction"] end subgraph subGraph1["Shift Optimization Loop"] C["shift = 32"] D["mult = (numerator << shift + denominator/2) / denominator"] E["mult <= u32::MAX?"] F["shift -= 1"] G["Continue loop"] end subgraph subGraph0["Input Processing"] A["numerator: u32"] B["denominator: u32"] end A --> C B --> C C --> D D --> E E --> F E --> H F --> G G --> D H --> I H --> L I --> J J --> K K --> H
Transformation Process
The algorithm operates in two phases to find the optimal mult
and shift
values:
- Shift Maximization Phase src/lib.rs(L63 - L71) :
- Starts with
shift = 32
(maximum precision) - Calculates
mult = (numerator << shift + denominator/2) / denominator
- Reduces
shift
untilmult
fits inu32::MAX
- The
+ denominator/2
term provides rounding during the division
- Factor Reduction Phase src/lib.rs(L73 - L76) :
- Removes common factors of 2 from
mult
andshift
- Continues while
mult
is even andshift > 0
- Optimizes for the smallest possible
mult
value
Sources: src/lib.rs(L62 - L84)
Constructor Logic Flow
The new
constructor implements the transformation algorithm with special handling for edge cases and zero values.
flowchart TD subgraph subGraph2["Optimization Algorithm"] F["Execute shift maximization loop"] G["Execute factor reduction loop"] H["Construct final Ratio struct"] end subgraph subGraph1["Zero Handling"] D["numerator == 0?"] E["Return Ratio with mult=0, shift=0"] end subgraph subGraph0["Input Validation"] A["new(numerator, denominator)"] B["denominator == 0 && numerator != 0?"] C["panic!"] end A --> B B --> C B --> D D --> E D --> F F --> G G --> H
Edge Case Handling
The constructor provides specific behavior for mathematical edge cases:
- Invalid Division src/lib.rs(L52) : Panics when
denominator == 0
andnumerator != 0
- Zero Numerator src/lib.rs(L53 - L60) : Returns a ratio with
mult = 0, shift = 0
for any0/x
case - Zero Ratio src/lib.rs(L37 - L44) : Special
zero()
constructor creates0/0
ratio that doesn't panic on inversion
Sources: src/lib.rs(L46 - L84)
Internal Field Relationships
The optimized fields maintain mathematical equivalence with the original fraction while enabling performance optimizations.
flowchart TD subgraph subGraph2["Performance Benefits"] G["Expensive: 64-bit division"] H["Fast: 64-bit multiplication + bit shift"] end subgraph subGraph0["Mathematical Equivalence"] A["numerator / denominator"] B["≈"] C["mult / (1 << shift)"] end subgraph subGraph1["Arithmetic Operations"] D["value * numerator / denominator"] E["≈"] F["(value * mult) >> shift"] end A --> B A --> G B --> C C --> H D --> E E --> F
Precision vs Performance Trade-offs
The transformation balances mathematical precision with computational efficiency:
Aspect | Traditional | Optimized |
---|---|---|
Representation | numerator/denominator | mult/(1<<shift) |
Arithmetic | (value * num) / denom | (value * mult) >> shift |
Operations | Division (expensive) | Multiplication + Bit shift |
Precision | Exact | Approximated withinu32constraints |
Performance | Slower on embedded | Optimized for embedded targets |
The shift
value is maximized to preserve as much precision as possible within the u32
constraint for mult
. The factor reduction phase then optimizes the representation by removing unnecessary powers of 2.
Sources: src/lib.rs(L8 - L11) src/lib.rs(L114 - L132)
Zero Value Optimization
The crate provides optimized handling for zero values to avoid computational overhead and support mathematical edge cases.
flowchart TD subgraph subGraph2["Behavioral Differences"] E["Ratio::new(0,x).inverse() → panic"] F["Ratio::zero().inverse() → Ratio::zero()"] end subgraph subGraph1["Internal Representation"] C["mult = 0, shift = 0"] D["denominator = 0 (zero case only)"] end subgraph subGraph0["Zero Value Types"] A["Ratio::new(0, x) where x != 0"] B["Ratio::zero() // 0/0 special case"] end A --> C A --> E B --> C B --> D B --> F
The zero optimization eliminates unnecessary computation for zero multiplication operations while providing safe inversion behavior for the special 0/0
case.
Sources: src/lib.rs(L22 - L44) src/lib.rs(L99 - L101)
API Reference
Relevant source files
This document provides comprehensive documentation of all public APIs in the int_ratio
crate. It covers the Ratio
struct, its constructors, arithmetic operations, and utility methods. For implementation details of the internal architecture, see Internal Architecture. For mathematical principles behind the optimization strategy, see Mathematical Foundation.
Core Type
Ratio Struct
The Ratio
struct is the central type that represents a rational number optimized for performance through a mult/(1<<shift)
representation.
Field | Type | Purpose |
---|---|---|
numerator | u32 | Original numerator value |
denominator | u32 | Original denominator value |
mult | u32 | Optimized multiplication factor |
shift | u32 | Bit shift amount for division |
The struct implements Clone
, Copy
, Debug
, and PartialEq
traits for convenience.
Sources: src/lib.rs(L13 - L19)
Constructor Methods
Ratio::new
pub const fn new(numerator: u32, denominator: u32) -> Self
Creates a new ratio from numerator/denominator
. The constructor performs optimization to convert the fraction into mult/(1<<shift)
form for efficient arithmetic operations.
Behavior:
- Panics if
denominator
is zero andnumerator
is non-zero - Returns a zero ratio if
numerator
is zero - Calculates optimal
mult
andshift
values to maximize precision - Removes common factors of 2 to minimize
shift
Example from codebase:
let ratio = Ratio::new(625_000, 1_000_000); // Results in mult=5, shift=3
Sources: src/lib.rs(L51 - L84)
Ratio::zero
pub const fn zero() -> Self
Creates a special zero ratio with numerator=0
and denominator=0
. This differs from other zero ratios (0/x
) because it does not panic when inverted, instead returning another zero ratio.
Example from codebase:
let zero = Ratio::zero();
assert_eq!(zero.mul_trunc(123), 0);
assert_eq!(zero.inverse(), Ratio::zero());
Sources: src/lib.rs(L37 - L44)
Arithmetic Operations
mul_trunc
pub const fn mul_trunc(self, value: u64) -> u64
Multiplies the ratio by a value and truncates (rounds down) the result. Uses the optimized mult
and shift
values to avoid expensive division.
Implementation: ((value * mult) >> shift)
Example from codebase:
let ratio = Ratio::new(2, 3);
assert_eq!(ratio.mul_trunc(100), 66); // trunc(100 * 2 / 3) = trunc(66.66...) = 66
Sources: src/lib.rs(L114 - L116)
mul_round
pub const fn mul_round(self, value: u64) -> u64
Multiplies the ratio by a value and rounds to the nearest whole number. Adds a rounding factor before shifting.
Implementation: ((value * mult + (1 << shift >> 1)) >> shift)
Example from codebase:
let ratio = Ratio::new(2, 3);
assert_eq!(ratio.mul_round(100), 67); // round(100 * 2 / 3) = round(66.66...) = 67
Sources: src/lib.rs(L130 - L132)
Utility Methods
inverse
pub const fn inverse(self) -> Self
Returns the mathematical inverse of the ratio by swapping numerator and denominator. Handles the special zero ratio case gracefully.
Example from codebase:
let ratio = Ratio::new(1, 2);
assert_eq!(ratio.inverse(), Ratio::new(2, 1));
Sources: src/lib.rs(L99 - L101)
Trait Implementations
Debug
The Debug
trait provides readable output showing both original and optimized representations:
// Format: "Ratio(numerator/denominator ~= mult/(1<<shift))"
println!("{:?}", Ratio::new(1, 3)); // Ratio(1/3 ~= 1431655765/4294967296)
Sources: src/lib.rs(L135 - L146)
PartialEq
Equality comparison based on the optimized mult
and shift
values rather than original numerator/denominator:
#![allow(unused)] fn main() { fn eq(&self, other: &Ratio) -> bool { self.mult == other.mult && self.shift == other.shift } }
Sources: src/lib.rs(L148 - L153)
API Usage Patterns
Method Call Flow Diagram
flowchart TD A["Ratio::new(num, den)"] B["Ratio instance"] C["Ratio::zero()"] D["mul_trunc(value)"] E["mul_round(value)"] F["inverse()"] G["u64 result (truncated)"] H["u64 result (rounded)"] I["Ratio instance (inverted)"] J["Debug formatting"] K["PartialEq comparison"] L["String representation"] M["bool result"] A --> B B --> D B --> E B --> F B --> J B --> K C --> B D --> G E --> H F --> I J --> L K --> M
Sources: src/lib.rs(L21 - L153)
Method Relationship and Data Flow
Ratio Internal State and Operations
flowchart TD subgraph Output["Output"] J["u64 truncated result"] K["u64 rounded result"] L["Ratio inverted instance"] end subgraph Operations["Operations"] G["mul_trunc: (value * mult) >> shift"] H["mul_round: (value * mult + round) >> shift"] I["inverse: new(denominator, numerator)"] end subgraph Construction["Construction"] A["new(numerator, denominator)"] B["mult calculation"] C["zero()"] D["mult=0, shift=0"] E["shift optimization"] F["Ratio{numerator, denominator, mult, shift}"] end A --> B B --> E C --> D D --> F E --> F F --> G F --> H F --> I G --> J H --> K I --> L
Sources: src/lib.rs(L51 - L132)
Performance Characteristics
Operation | Time Complexity | Notes |
---|---|---|
new() | O(1) | Constant time optimization during construction |
zero() | O(1) | Direct field assignment |
mul_trunc() | O(1) | Single multiplication and bit shift |
mul_round() | O(1) | Single multiplication, addition, and bit shift |
inverse() | O(1) | Callsnew()with swapped parameters |
The key performance benefit is that all arithmetic operations use multiplication and bit shifts instead of division, making them suitable for embedded and real-time systems.
Sources: src/lib.rs(L114 - L116) src/lib.rs(L130 - L132)
Mathematical Foundation
Relevant source files
This document explains the mathematical principles underlying the Ratio
type's optimization strategy and arithmetic operations. It covers the core transformation from traditional rational arithmetic to fixed-point representation, precision analysis, and the algorithms used to maximize computational efficiency while maintaining mathematical accuracy.
For implementation details of the Ratio
API, see API Reference. For architectural specifics of the internal data structures, see Internal Architecture.
Core Mathematical Transformation
The fundamental innovation of the int_ratio
crate lies in transforming traditional rational arithmetic numerator/denominator
into an optimized fixed-point representation mult/(1<<shift)
. This transformation enables fast multiplication operations using bit shifts instead of expensive division operations.
Mathematical Equivalence
The transformation maintains mathematical equivalence through the following relationship:
numerator/denominator = mult/(1<<shift)
Where:
mult = (numerator × (1<<shift) + denominator/2) / denominator
shift
is maximized subject tomult ≤ u32::MAX
Transformation Algorithm Flow
flowchart TD A["Input: numerator, denominator"] B["Initialize shift = 32"] C["Calculate mult = (numerator × 2^shift + denominator/2) / denominator"] D["mult ≤ u32::MAX?"] E["shift -= 1"] F["Optimize: Remove factors of 2"] G["mult % 2 == 0 AND shift > 0?"] H["mult /= 2, shift -= 1"] I["Result: mult, shift"] A --> B B --> C C --> D D --> E D --> F E --> C F --> G G --> H G --> I H --> G
Sources: src/lib.rs(L62 - L83)
Precision Optimization Strategy
The algorithm maximizes precision by selecting the largest possible shift
value while ensuring mult
fits within a u32
. This approach balances two competing requirements:
Requirement | Implementation | Benefit |
---|---|---|
Maximize precision | Start withshift = 32, decrement untilmult ≤ u32::MAX | Uses maximum available bits |
Minimize computation | Remove common factors of 2 frommultandshift | Reduces multiplication overhead |
Maintain accuracy | Adddenominator/2before division | Implements banker's rounding |
Sources: src/lib.rs(L63 - L76)
Arithmetic Operation Mathematics
Multiplication with Truncation
The mul_trunc
operation implements the mathematical formula:
result = ⌊(value × mult) / (1<<shift)⌋
This is efficiently computed as a bit shift operation:
flowchart TD A["value: u64"] B["Cast to u128"] C["Multiply by mult"] D["Right shift by shift bits"] E["Cast to u64"] F["Truncated result"] A --> B B --> C C --> D D --> E E --> F
Sources: src/lib.rs(L114 - L116)
Multiplication with Rounding
The mul_round
operation adds a rounding term before the shift:
result = ⌊(value × mult + 2^(shift-1)) / (1<<shift)⌋
The rounding term 2^(shift-1)
represents half of the divisor 2^shift
, implementing proper mathematical rounding:
Rounding Behavior Analysis
flowchart TD A["Original: value × numerator / denominator"] B["Transformed: (value × mult) >> shift"] C["With rounding: (value × mult + round_term) >> shift"] D["round_term = 1 << (shift - 1)"] E["Fractional part < 0.5"] F["Rounds down"] G["Fractional part ≥ 0.5"] H["Rounds up"] A --> B B --> C C --> E C --> G D --> C E --> F G --> H
Sources: src/lib.rs(L130 - L132)
Precision Analysis and Bounds
Maximum Precision Calculation
The precision of the representation depends on the selected shift
value. The algorithm maximizes precision by:
- Starting with maximum shift: Begin with
shift = 32
to use all available bits - Constraint enforcement: Ensure
mult ≤ u32::MAX
to prevent overflow - Optimization: Remove unnecessary factors of 2 to minimize computational cost
Error Bounds
The maximum relative error introduced by the transformation is bounded by:
|error| ≤ 1 / (2^shift)
This bound is achieved because the worst-case rounding error in computing mult
is at most 0.5, which translates to a relative error of 0.5 / (2^shift)
in the final result.
Error Analysis by Shift Value
Shift Value | Maximum Relative Error | Precision (decimal places) |
---|---|---|
32 | 2.33 × 10⁻¹⁰ | ~9.7 |
16 | 1.53 × 10⁻⁵ | ~4.8 |
8 | 3.91 × 10⁻³ | ~2.4 |
Sources: src/lib.rs(L66 - L77)
Special Cases and Edge Conditions
Zero Handling
The zero ratio requires special mathematical treatment to avoid division by zero in inverse operations:
flowchart TD A["Input: numerator = 0"] B["denominator = 0?"] C["Zero ratio: 0/0"] D["Standard zero: 0/denominator"] E["mult = 0, shift = 0"] F["Inverse of zero ratio"] G["Returns zero ratio"] H["Inverse of standard zero"] I["Panics: division by zero"] A --> B B --> C B --> D C --> E D --> E F --> G H --> I
Sources: src/lib.rs(L37 - L44) src/lib.rs(L53 - L60)
Boundary Conditions
The algorithm handles several mathematical boundary conditions:
- Maximum values: When
numerator = u32::MAX
anddenominator = 1
, the result ismult = u32::MAX, shift = 0
- Minimum precision: When
numerator = 1
anddenominator = u32::MAX
, maximum shift is used - Unity ratio: When
numerator = denominator
, the result ismult = 1, shift = 0
Sources: src/lib.rs(L161 - L185)
Computational Complexity
The mathematical operations achieve the following complexity characteristics:
Operation | Time Complexity | Space Complexity | Notes |
---|---|---|---|
new() | O(log n) | O(1) | Due to shift optimization loop |
mul_trunc() | O(1) | O(1) | Single multiplication + shift |
mul_round() | O(1) | O(1) | Single multiplication + shift |
inverse() | O(log n) | O(1) | Callsnew()with swapped parameters |
The O(log n) complexity of construction comes from the iterative reduction of the shift value and the factor-of-2 elimination loop.
Sources: src/lib.rs(L65 - L83) src/lib.rs(L99 - L101)
Usage Guide
Relevant source files
This guide demonstrates how to use the int_ratio
crate in practical applications. It covers the core usage patterns, arithmetic operations, and performance considerations for working with the Ratio
type.
For detailed information about the internal architecture and optimization strategies, see Internal Architecture. For comprehensive API documentation, see API Reference.
Creating and Using Ratios
The Ratio
type provides efficient rational arithmetic through its optimized mult/(1<<shift)
representation. The basic workflow involves creating a ratio from integers and then performing multiplication operations with either truncation or rounding behavior.
Basic Ratio Creation Workflow
flowchart TD A["Input: numerator, denominator"] B["Ratio::new()"] C["Internal optimization"] D["Ready for calculations"] E["mul_trunc()"] F["mul_round()"] G["inverse()"] H["Truncated result"] I["Rounded result"] J["Inverted ratio"] K["Special case: Ratio::zero()"] A --> B B --> C C --> D D --> E D --> F D --> G E --> H F --> I G --> J K --> D
Creating Ratios from Integers
The primary constructor Ratio::new()
accepts u32
values for both numerator and denominator:
use int_ratio::Ratio;
// Create a ratio representing 2/3
let ratio = Ratio::new(2, 3);
// Create a ratio representing 625/1000 (0.625)
let fraction = Ratio::new(625, 1000);
// Create unit ratio (1/1)
let unit = Ratio::new(1, 1);
Sources: src/lib.rs(L46 - L84)
Special Zero Ratio
The Ratio::zero()
constructor creates a special 0/0
ratio that behaves safely in all operations, including inversion:
let zero = Ratio::zero();
assert_eq!(zero.mul_trunc(123), 0);
assert_eq!(zero.inverse(), Ratio::zero()); // Safe inversion
Sources: src/lib.rs(L22 - L44)
Arithmetic Operations
The crate provides two multiplication methods with different rounding behaviors, plus ratio inversion.
Truncation vs Rounding Comparison
Operation | Method | Behavior | Example (2/3 × 100) |
---|---|---|---|
Truncation | mul_trunc() | Round down | 66(from 66.666...) |
Rounding | mul_round() | Round to nearest | 67(from 66.666...) |
Inversion | inverse() | Swap num/den | Ratio::new(3, 2) |
Truncation Operations
The mul_trunc()
method always rounds down to the nearest integer:
let ratio = Ratio::new(2, 3);
// These demonstrate truncation behavior
assert_eq!(ratio.mul_trunc(99), 66); // 99 × 2/3 = 66.0 exactly
assert_eq!(ratio.mul_trunc(100), 66); // 100 × 2/3 = 66.666... → 66
assert_eq!(ratio.mul_trunc(101), 67); // 101 × 2/3 = 67.333... → 67
Sources: src/lib.rs(L103 - L116)
Rounding Operations
The mul_round()
method rounds to the nearest integer using standard rounding rules:
let ratio = Ratio::new(2, 3);
// These demonstrate rounding behavior
assert_eq!(ratio.mul_round(99), 66); // 99 × 2/3 = 66.0 exactly
assert_eq!(ratio.mul_round(100), 67); // 100 × 2/3 = 66.666... → 67
assert_eq!(ratio.mul_round(101), 67); // 101 × 2/3 = 67.333... → 67
Sources: src/lib.rs(L118 - L132)
Common Usage Patterns
Pattern: Time and Frequency Conversion
flowchart TD A["Clock frequency"] B["Ratio::new(target_freq, source_freq)"] C["mul_round(time_value)"] D["Converted time units"] E["Timer ticks"] F["Ratio::new(nanoseconds, tick_freq)"] G["mul_trunc(tick_count)"] H["Nanosecond duration"] A --> B B --> C C --> D E --> F F --> G G --> H
Example: Converting between time units
// Convert microseconds to nanoseconds (1000:1 ratio)
let us_to_ns = Ratio::new(1000, 1);
let nanoseconds = us_to_ns.mul_trunc(42); // 42000 ns
// Convert CPU cycles to milliseconds (assuming 1GHz CPU)
let cycles_to_ms = Ratio::new(1, 1_000_000); // 1ms per 1M cycles
let duration_ms = cycles_to_ms.mul_round(2_500_000); // ~3ms
Pattern: Resource Scaling
flowchart TD A["Available memory"] B["Ratio::new(allocated_portion, total_memory)"] C["mul_trunc(request_size)"] D["Allocated amount"] E["CPU load"] F["Ratio::new(used_cores, total_cores)"] G["mul_round(workload)"] H["Distributed load"] A --> B B --> C C --> D E --> F F --> G G --> H
Example: Memory allocation ratios
// Allocate 3/4 of available memory to cache
let cache_ratio = Ratio::new(3, 4);
let total_memory = 1024 * 1024 * 1024; // 1GB
let cache_size = cache_ratio.mul_trunc(total_memory); // 768MB
// Scale down resource requests by 2/3
let scale_down = Ratio::new(2, 3);
let reduced_request = scale_down.mul_round(original_request);
Pattern: Rate Limiting and Throttling
// Throttle to 70% of maximum rate
let throttle = Ratio::new(7, 10);
let allowed_requests = throttle.mul_trunc(max_requests_per_second);
// Convert packet rate between different time windows
let per_second_to_per_minute = Ratio::new(60, 1);
let packets_per_minute = per_second_to_per_minute.mul_round(packets_per_second);
Performance Considerations
The Ratio
type is optimized for performance-critical code through its mult/(1<<shift)
representation.
Performance Benefits Workflow
flowchart TD A["Traditional: value * num / den"] B["Expensive division"] C["Variable performance"] D["int_ratio: (value * mult) >> shift"] E["Bit shift operation"] F["Consistent fast performance"] G["Construction time"] H["Pre-compute mult/shift"] I["Amortized cost over many operations"] A --> B B --> C D --> E E --> F G --> H H --> I
When to Use Each Operation:
Use Case | Recommended Method | Rationale |
---|---|---|
Memory allocation | mul_trunc() | Never over-allocate |
Time calculations | mul_round() | Minimize timing errors |
Rate limiting | mul_trunc() | Conservative limiting |
Display/UI values | mul_round() | Better visual appearance |
Construction Cost vs Runtime Benefit
The Ratio::new()
constructor performs optimization work upfront to enable fast runtime operations:
// One-time construction cost
let ratio = Ratio::new(2, 3); // Computes optimal mult/shift values
// Many fast runtime operations
for value in large_dataset {
let result = ratio.mul_trunc(value); // Just multiplication + shift
process(result);
}
Sources: src/lib.rs(L62 - L83)
Edge Cases and Error Handling
Division by Zero Protection
The constructor panics for invalid ratios but allows the special zero case:
// Panics: invalid ratio
// let invalid = Ratio::new(5, 0);
// Safe: zero ratio
let zero_num = Ratio::new(0, 5); // Represents 0/5 = 0
let zero_special = Ratio::zero(); // Special 0/0 case
// Safe inversion of zero ratios
assert_eq!(zero_special.inverse(), Ratio::zero());
Precision Limits
The Ratio
type works with u32
inputs and u64
calculation values:
// Maximum values supported
let max_ratio = Ratio::new(u32::MAX, u32::MAX); // Represents 1.0
let tiny_ratio = Ratio::new(1, u32::MAX); // Very small fraction
// Large value calculations
let large_result = max_ratio.mul_trunc(u64::MAX); // Uses u128 internally
Sources: src/lib.rs(L159 - L185)
Debug and Inspection
The Debug
implementation shows both the original and optimized representations:
let ratio = Ratio::new(625, 1000);
println!("{:?}", ratio);
// Output: Ratio(625/1000 ~= 5/8)
This format displays:
- Original numerator/denominator
- Optimized mult/shift representation as an equivalent fraction
Sources: src/lib.rs(L135 - L146)
Development Guide
Relevant source files
This document provides comprehensive information for developers contributing to the int_ratio crate. It covers development environment setup, continuous integration pipeline, code quality standards, and testing procedures across multiple target architectures. For information about using the int_ratio API in applications, see Usage Guide. For details about the internal implementation, see Ratio Type Implementation.
Development Environment Setup
The int_ratio crate requires a Rust nightly toolchain with specific components and target support for cross-platform compatibility testing.
Required Toolchain Configuration
The development environment must include:
- Rust Toolchain:
nightly
channel - Components:
rust-src
,clippy
,rustfmt
- Target Architectures:
x86_64-unknown-linux-gnu
,x86_64-unknown-none
,riscv64gc-unknown-none-elf
,aarch64-unknown-none-softfloat
flowchart TD A["Rust Nightly Toolchain"] B["rust-src component"] C["clippy component"] D["rustfmt component"] E["Target Architectures"] F["x86_64-unknown-linux-gnu"] G["x86_64-unknown-none"] H["riscv64gc-unknown-none-elf"] I["aarch64-unknown-none-softfloat"] J["Linux Testing Platform"] K["Bare Metal x86_64"] L["RISC-V Embedded"] M["ARM64 Embedded"] A --> B A --> C A --> D E --> F E --> G E --> H E --> I F --> J G --> K H --> L I --> M
Development Environment Architecture
Sources: .github/workflows/ci.yml(L15 - L19)
CI Pipeline Architecture
The continuous integration system employs a matrix strategy to ensure cross-platform compatibility and maintain code quality standards.
flowchart TD subgraph subGraph0["Target Matrix"] J["x86_64-unknown-linux-gnu"] K["x86_64-unknown-none"] L["riscv64gc-unknown-none-elf"] M["aarch64-unknown-none-softfloat"] end A["Push/PR Event"] B["CI Job Matrix"] C["ubuntu-latest Runner"] D["Rust Toolchain Setup"] E["rustc Version Check"] F["Code Format Check"] G["Clippy Linting"] H["Build Process"] I["Unit Testing"] N["Test Results"] O["Documentation Job"] P["cargo doc"] Q["GitHub Pages Deploy"] A --> B B --> C B --> J B --> K B --> L B --> M C --> D D --> E E --> F F --> G G --> H H --> I I --> N O --> P P --> Q
CI Pipeline Flow and Target Matrix
Pipeline Stages
Stage | Command | Purpose | Target Scope |
---|---|---|---|
Format Check | cargo fmt --all -- --check | Enforce code style consistency | All targets |
Linting | cargo clippy --target $TARGET --all-features | Static analysis and best practices | Per target |
Build | cargo build --target $TARGET --all-features | Compilation verification | Per target |
Unit Test | cargo test --target $TARGET -- --nocapture | Functional testing | Linux only |
Sources: .github/workflows/ci.yml(L22 - L30)
Code Quality Standards
The project enforces strict code quality through automated tooling integrated into the CI pipeline.
Formatting Requirements
Code formatting uses rustfmt
with default configuration. All contributions must pass the format check defined in .github/workflows/ci.yml(L22 - L23)
Linting Standards
Clippy linting runs with custom configuration allowing the clippy::new_without_default
warning, as specified in .github/workflows/ci.yml(L24 - L25) This exception acknowledges that the Ratio::new
constructor appropriately requires explicit parameters.
Documentation Standards
Documentation builds enforce strict standards through RUSTDOCFLAGS
configuration:
-D rustdoc::broken_intra_doc_links
: Fails on broken internal links-D missing-docs
: Requires documentation for all public items
Sources: .github/workflows/ci.yml(L40)
Testing Strategy
The testing approach balances comprehensive platform coverage with practical constraints of embedded target environments.
flowchart TD A["Test Execution Strategy"] B["Full Test Suite"] C["Build-Only Verification"] D["x86_64-unknown-linux-gnu"] E["x86_64-unknown-none"] F["riscv64gc-unknown-none-elf"] G["aarch64-unknown-none-softfloat"] H["Standard Library Available"] I["Bare Metal Environment"] J["RISC-V Embedded"] K["ARM64 Embedded"] L["Unit Tests Execute"] M["Compilation Verified"] A --> B A --> C B --> D C --> E C --> F C --> G D --> H E --> I F --> J G --> K H --> L I --> M J --> M K --> M
Multi-Architecture Testing Strategy
Platform-Specific Testing
- Linux Platform (
x86_64-unknown-linux-gnu
): Full unit test execution with--nocapture
flag for detailed output - Embedded Targets: Build verification only due to runtime environment constraints
The conditional test execution logic is implemented in .github/workflows/ci.yml(L28 - L30)
Sources: .github/workflows/ci.yml(L11 - L12) .github/workflows/ci.yml(L28 - L30)
Documentation Deployment
The project maintains automated documentation deployment to GitHub Pages with the following workflow:
flowchart TD A["Documentation Job"] B["Rust Toolchain Setup"] C["cargo doc --no-deps --all-features"] D["Index HTML Generation"] E["Branch Check"] F["Default Branch?"] G["Deploy to gh-pages"] H["Skip Deployment"] I["GitHub Pages"] A --> B B --> C C --> D D --> E E --> F F --> G F --> H G --> I
Documentation Build and Deployment Process
The documentation system automatically generates API documentation and deploys it to GitHub Pages when changes are pushed to the default branch, as configured in .github/workflows/ci.yml(L49 - L55)
Sources: .github/workflows/ci.yml(L32 - L55)
Contributing Workflow
Pre-Contribution Checklist
Before submitting contributions, ensure:
- Local Testing: Run
cargo test
onx86_64-unknown-linux-gnu
target - Format Compliance: Execute
cargo fmt --all -- --check
- Lint Compliance: Run
cargo clippy --all-features
for each target - Build Verification: Confirm
cargo build
succeeds on all targets
Repository Configuration
The project uses standard Git configuration with build artifacts excluded via .gitignore(L1 - L4)
Sources: .gitignore(L1 - L4)
Overview
Relevant source files
Purpose and Scope
This document provides an overview of the flatten_objects
crate, a specialized Rust library that implements a container data structure for managing numbered objects with unique ID assignment. The crate is designed specifically for resource-constrained environments such as operating system kernels and embedded systems where the standard library is unavailable (no_std
).
This overview covers the fundamental concepts, core data structures, and high-level architecture of the library. For detailed API documentation, see FlattenObjects API Documentation. For implementation specifics and memory management details, see Implementation Details. For practical usage examples and integration patterns, see Usage Guide and Examples.
Sources: Cargo.toml(L1 - L17) README.md(L1 - L37)
Core Functionality
The flatten_objects
crate provides the FlattenObjects<T, CAP>
container, which serves as a fixed-capacity array-like structure that automatically assigns unique integer IDs to stored objects. The container supports efficient ID reuse, allowing previously removed object IDs to be reassigned to new objects, making it ideal for implementing object pools, handle tables, and resource management systems in kernel environments.
The primary operations include:
- Object Addition:
add()
for automatic ID assignment,add_at()
for specific ID placement - Object Removal:
remove()
to free objects and make their IDs available for reuse - Object Access:
get()
andget_mut()
for safe reference retrieval - State Inspection:
is_assigned()
,count()
,capacity()
, andids()
for container introspection
Sources: README.md(L7 - L11) README.md(L14 - L36)
System Architecture
Core Components Diagram
flowchart TD subgraph subGraph2["External Dependencies"] BitmapsCrate["bitmaps crate v3.2"] CoreMem["core::mem::MaybeUninit"] end subgraph subGraph1["Public API Methods"] AddMethods["add() / add_at() / add_or_replace_at()"] AccessMethods["get() / get_mut() / is_assigned()"] RemoveMethods["remove()"] QueryMethods["capacity() / count() / ids()"] end subgraph subGraph0["FlattenObjects"] ObjectArray["objects: [MaybeUninit; CAP]"] IDBitmap["id_bitmap: Bitmap"] Count["count: usize"] end AccessMethods --> IDBitmap AccessMethods --> ObjectArray AddMethods --> Count AddMethods --> IDBitmap AddMethods --> ObjectArray IDBitmap --> BitmapsCrate ObjectArray --> CoreMem QueryMethods --> Count QueryMethods --> IDBitmap RemoveMethods --> Count RemoveMethods --> IDBitmap RemoveMethods --> ObjectArray
Sources: Cargo.toml(L15 - L16) README.md(L14 - L36)
Integration Context Diagram
flowchart TD subgraph subGraph3["Target Environments"] NoStdEnvironment["no_std Environment"] EmbeddedSystems["Embedded Systems"] KernelSpace["Kernel Space"] end subgraph subGraph2["flatten_objects Library"] FlattenObjectsContainer["FlattenObjects"] subgraph subGraph1["Common Use Cases"] HandleTables["Handle Tables"] ObjectPools["Object Pools"] ResourceManagers["Resource Managers"] end end subgraph subGraph0["ArceOS Ecosystem"] ArceOSKernel["ArceOS Kernel"] ProcessManagement["Process Management"] MemoryManagement["Memory Management"] FileSystem["File System"] end ArceOSKernel --> FlattenObjectsContainer FileSystem --> ResourceManagers FlattenObjectsContainer --> EmbeddedSystems FlattenObjectsContainer --> KernelSpace FlattenObjectsContainer --> NoStdEnvironment HandleTables --> FlattenObjectsContainer MemoryManagement --> ObjectPools ObjectPools --> FlattenObjectsContainer ProcessManagement --> HandleTables ResourceManagers --> FlattenObjectsContainer
Sources: Cargo.toml(L8 - L12) Cargo.toml(L15 - L16)
Key Characteristics
Characteristic | Description |
---|---|
Fixed Capacity | Compile-time constantCAPparameter defines maximum object count |
ID Reuse Strategy | Automatically reuses IDs from removed objects via bitmap tracking |
Memory Efficiency | UsesMaybeUninit |
no_std Compatible | Designed for environments without standard library support |
Type Safety | Maintains safe abstractions over unsafe memory operations |
Zero Runtime Allocation | All memory allocated at compile-time, no heap usage |
The library is particularly well-suited for scenarios requiring predictable memory usage and deterministic performance characteristics, such as real-time systems, kernel modules, and embedded applications where dynamic allocation is prohibited or undesirable.
Sources: Cargo.toml(L12) Cargo.toml(L15 - L16) README.md(L17 - L36)
Project Structure and Dependencies
Relevant source files
This document details the crate configuration, external dependencies, and organizational structure of the flatten_objects
library. It explains how the project is configured for the ArceOS ecosystem and its role as a no_std
compatible container library.
For implementation details of the core data structures, see Internal Data Structures. For build and testing procedures, see Building and Testing.
Crate Configuration and Metadata
The flatten_objects
crate is configured as a library package targeting resource-constrained environments. The crate metadata defines its purpose as a numbered object container with unique ID assignment capabilities.
Project Metadata
Field | Value | Purpose |
---|---|---|
name | flatten_objects | Crate identifier for the Rust ecosystem |
version | 0.2.3 | Current release version following semantic versioning |
edition | 2024 | Uses Rust 2024 edition features |
description | "A container that stores numbered objects..." | Describes the core functionality |
license | GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0 | Triple-licensed for maximum compatibility |
homepage | https://github.com/arceos-org/arceos | Links to the broader ArceOS project |
repository | https://github.com/arceos-org/flatten_objects | Source code location |
rust-version | 1.85 | Minimum supported Rust version |
The crate is categorized under no-std
and data-structures
, indicating its suitability for embedded and kernel development environments where the standard library is unavailable.
Crate Classification and Targets
flowchart TD subgraph target_environments["Target Environments"] edition_2024["Rust Edition 2024"] triple_license["Triple License Compatibility"] min_rust["Minimum Rust 1.85"] no_std["no_std Environment"] embedded["Embedded Systems"] kernel["Kernel Development"] arceos_os["ArceOS Operating System"] metadata["Crate Metadata"] end subgraph flatten_objects_crate["flatten_objects Crate"] arceos_os["ArceOS Operating System"] categories["categories: [no-std, data-structures]"] keywords["keywords: [arceos, data-structures]"] rust_version["rust-version: 1.85"] subgraph compatibility_matrix["Compatibility Matrix"] edition_2024["Rust Edition 2024"] triple_license["Triple License Compatibility"] min_rust["Minimum Rust 1.85"] no_std["no_std Environment"] embedded["Embedded Systems"] kernel["Kernel Development"] metadata["Crate Metadata"] end end metadata --> categories metadata --> keywords metadata --> rust_version
Sources: Cargo.toml(L1 - L13)
Dependencies Analysis
The crate maintains a minimal dependency footprint with only one external dependency to ensure compatibility with resource-constrained environments.
External Dependencies
The bitmaps
crate version 3.2
serves as the sole external dependency, providing efficient bitmap operations for ID management:
[dependencies]
bitmaps = { version = "3.2", default-features = false }
The default-features = false
configuration ensures no_std
compatibility by disabling standard library features of the bitmaps
crate.
Dependency Relationship Diagram
flowchart TD subgraph internal_usage["Internal Usage in flatten_objects"] id_bitmap["id_bitmap: Bitmap"] id_management["ID Management System"] allocation_tracking["Allocation Tracking"] end subgraph bitmaps_features["bitmaps Crate Features"] default_features_disabled["default-features = false"] no_std_compat["no_std compatibility"] bitmap_ops["Bitmap Operations"] end subgraph flatten_objects_dependencies["flatten_objects Dependencies"] flatten_objects["flatten_objects"] bitmaps_dep["bitmaps v3.2"] core_std["core (no_std)"] end bitmap_ops --> id_bitmap bitmaps_dep --> bitmap_ops bitmaps_dep --> default_features_disabled default_features_disabled --> no_std_compat flatten_objects --> bitmaps_dep flatten_objects --> core_std id_bitmap --> id_management id_management --> allocation_tracking
Dependency Rationale
Dependency | Version | Configuration | Purpose |
---|---|---|---|
bitmaps | 3.2 | default-features = false | ProvidesBitmap |
Standard Library | N/A | Excluded (no_std) | Maintains compatibility with kernel environments |
core | Implicit | Always available | ProvidesMaybeUninitand essential types |
The bitmaps
crate integration enables the FlattenObjects
container to efficiently track which IDs are allocated using bitwise operations, essential for the ID reuse mechanism.
Sources: Cargo.toml(L15 - L16)
ArceOS Ecosystem Integration
The flatten_objects
crate is designed specifically for integration within the ArceOS operating system ecosystem, serving as a foundational data structure for kernel-level resource management.
Ecosystem Positioning
The crate's homepage points to the broader ArceOS project (https://github.com/arceos-org/arceos
), indicating its role as a component within a larger operating system framework.
ArceOS Integration Architecture
flowchart TD subgraph kernel_integration_points["Kernel Integration Points"] process_descriptors["Process Descriptors"] file_handles["File Handles"] memory_regions["Memory Regions"] device_handles["Device Handles"] end subgraph flatten_objects_role["flatten_objects Role"] flatten_objects_lib["flatten_objects Library"] object_pools["Object Pools"] handle_tables["Handle Tables"] resource_mgmt["Resource Management"] id_allocation["ID Allocation"] end subgraph arceos_ecosystem["ArceOS Ecosystem"] arceos_kernel["ArceOS Kernel"] process_mgmt["Process Management"] memory_mgmt["Memory Management"] file_system["File System"] device_drivers["Device Drivers"] end arceos_kernel --> flatten_objects_lib device_drivers --> id_allocation file_system --> resource_mgmt flatten_objects_lib --> handle_tables flatten_objects_lib --> id_allocation flatten_objects_lib --> object_pools flatten_objects_lib --> resource_mgmt handle_tables --> file_handles id_allocation --> device_handles memory_mgmt --> handle_tables object_pools --> process_descriptors process_mgmt --> object_pools resource_mgmt --> memory_regions
Target Use Cases
The crate addresses specific requirements in kernel and embedded development:
- Fixed-capacity containers: Essential for predictable memory usage in kernel environments
- Efficient ID reuse: Critical for long-running systems that create and destroy many objects
- No heap allocation: Required for kernel code and embedded systems with limited memory
- Type-safe object storage: Maintains memory safety while allowing low-level optimizations
Sources: Cargo.toml(L8 - L11)
Project Structure Overview
Based on the crate configuration and typical Rust library conventions, the project follows a standard structure optimized for library distribution and ArceOS integration.
Repository Structure
Project Organization Diagram
flowchart TD subgraph repository_root["Repository Root"] ci_workflow["CI/CD Pipeline"] package_metadata["Package Metadata"] public_api["Public API Methods"] cargo_toml["Cargo.toml"] readme_md["README.md"] gitignore[".gitignore"] src_dir["src/"] end subgraph configuration_files["Configuration Files"] flatten_objects_struct["FlattenObjects"] subgraph development_infrastructure["Development Infrastructure"] ci_workflow["CI/CD Pipeline"] documentation["API Documentation"] multi_target["Multi-target Support"] package_metadata["Package Metadata"] dependency_spec["Dependency Specification"] build_config["Build Configuration"] lib_rs["src/lib.rs"] end end subgraph source_structure["Source Structure"] ci_workflow["CI/CD Pipeline"] documentation["API Documentation"] package_metadata["Package Metadata"] dependency_spec["Dependency Specification"] lib_rs["src/lib.rs"] flatten_objects_struct["FlattenObjects"] public_api["Public API Methods"] internal_impl["Internal Implementation"] cargo_toml["Cargo.toml"] readme_md["README.md"] end flatten_objects_struct --> internal_impl flatten_objects_struct --> public_api lib_rs --> flatten_objects_struct
Build and Distribution Configuration
The crate is configured for distribution through the standard Rust package ecosystem while maintaining specialized requirements for kernel development:
Configuration Aspect | Setting | Impact |
---|---|---|
Edition | 2024 | Access to latest Rust language features |
Categories | ["no-std", "data-structures"] | Discoverable by embedded/kernel developers |
Keywords | ["arceos", "data-structures"] | Associated with ArceOS ecosystem |
Documentation | docs.rsintegration | Automatic API documentation generation |
Multi-target support | Implicit viano_std | Compatible with diverse hardware platforms |
The minimal dependency configuration ensures fast compilation and reduces the attack surface for security-critical kernel components.
Sources: Cargo.toml(L1 - L17)
Key Concepts and Terminology
Relevant source files
This document explains the fundamental concepts and terminology used throughout the flatten_objects
crate. It covers the core principles of object containers, unique ID assignment, ID reuse strategies, and memory management in no_std
environments. For implementation details of the data structures, see Internal Data Structures. For specific API usage patterns, see Basic Operations.
Object Container Fundamentals
The flatten_objects
crate provides a specialized container called FlattenObjects<T, CAP>
that stores objects of type T
with a fixed capacity of CAP
items. Unlike standard collections, this container assigns each stored object a unique numerical identifier (ID) that can be used for subsequent access.
flowchart TD subgraph Operations["Key Operations"] IDAssignment["ID Assignment"] ObjectRetrieval["Object Retrieval"] IDRecycling["ID Recycling"] end subgraph Container["FlattenObjects<T, CAP>"] Bitmap["id_bitmap: Bitmap<CAP>"] Count["count: usize"] Slot0["objects[0]: MaybeUninit<T>"] Slot1["objects[1]: MaybeUninit<T>"] subgraph Interface["Public Interface"] AddMethods["add(), add_at(), add_or_replace_at()"] AccessMethods["get(), get_mut(), is_assigned()"] RemoveMethods["remove()"] QueryMethods["capacity(), count(), ids()"] end subgraph Storage["Object Storage"] SlotN["objects[N]: MaybeUninit<T>"] subgraph Tracking["ID Tracking"] Bitmap["id_bitmap: Bitmap<CAP>"] Count["count: usize"] Slot0["objects[0]: MaybeUninit<T>"] Slot1["objects[1]: MaybeUninit<T>"] end end end AccessMethods --> ObjectRetrieval AddMethods --> IDAssignment IDAssignment --> ObjectRetrieval ObjectRetrieval --> IDRecycling RemoveMethods --> IDRecycling
Container Characteristics
- Fixed capacity: The container can hold at most
CAP
objects, whereCAP
is a compile-time constant - Numbered storage: Each object is stored at a specific index corresponding to its ID
- Sparse allocation: Not all slots need to be filled; IDs can have gaps
- Type safety: All objects in the container must be of the same type
T
Sources: src/lib.rs(L44 - L51) src/lib.rs(L1 - L4)
Unique ID Assignment System
The container assigns unique IDs to objects using a bitmap-based allocation strategy. IDs are non-negative integers ranging from 0 to CAP-1
.
flowchart TD subgraph AllocationStrategy["Allocation Strategy"] FirstFit["first_false_index()"] ExplicitID["add_at(id, value)"] SmallestAvailable["add(value) → smallest ID"] end subgraph BitmapState["id_bitmap State"] Bit0["bit[0]: false (available)"] Bit1["bit[1]: true (assigned)"] Bit2["bit[2]: false (available)"] BitN["bit[N]: true (assigned)"] end subgraph IDSpace["ID Space (0 to CAP-1)"] ID0["ID 0"] ID1["ID 1"] ID2["ID 2"] IDN["ID N"] end FirstFit --> SmallestAvailable ID0 --> Bit0 ID1 --> Bit1 ID2 --> Bit2 IDN --> BitN
Assignment Methods
- Automatic assignment:
add(value)
finds the smallest available ID usingid_bitmap.first_false_index()
- Explicit assignment:
add_at(id, value)
assigns a specific ID if available - Replacement assignment:
add_or_replace_at(id, value)
overwrites existing objects
ID Constraints
- IDs must be in range
<FileRef file-url="https://github.com/arceos-org/flatten_objects/blob/ac0a74b9/0, CAP)
\n- Each ID can be assigned to at most one object at a time\n- The maximum capacityCAP
is limited to 1024 due to bitmap implementation constraints\n\nSources#LNaN-LNaN" NaN file-path="0, CAP)\n- Each ID can be assigned to at most one object at a time\n- The maximum capacity
CAP` is limited to 1024 due to bitmap implementation constraints\n\nSources">Hii src/lib.rs(L249 - L257) src/lib.rs(L42 - L43)
ID Reuse and Lifecycle Management
When objects are removed from the container, their IDs become available for reuse. This recycling mechanism ensures efficient utilization of the limited ID space.
stateDiagram-v2 [*] --> Unassigned Unassigned --> Reserved : "add() or add_at()" note left of Reserved : ['"id_bitmap.set(id, true)<br>count += 1"'] [*] --> Occupied Occupied --> Accessed : "get() or get_mut()" Accessed --> Occupied : "Reference dropped" note left of Occupied : ['"Object stored at objects[id]<br>MaybeUninit contains valid T"'] [*] --> root_start : "Container initialized" [*] --> Assigned_start : "Object added" [*] --> Available_start : "remove(id)" note left of Available----note : [] note left of Occupied : ['"Bitmap bit = true<br>Slot contains valid object"']
Lifecycle States
- Available: ID slot is free and can be assigned (
id_bitmap.get(id) == false
) - Assigned: ID slot contains a valid object (
id_bitmap.get(id) == true
) - Recycled: Previously assigned ID becomes available again after
remove()
Reuse Strategy
- IDs are reused in ascending order when
add()
is called - The
first_false_index()
method finds the lowest available ID - No guarantees about ID ordering when mixing
add()
andadd_at()
calls
Sources: src/lib.rs(L315 - L326) src/lib.rs(L223 - L224)
Memory Safety inno_stdEnvironments
The crate operates in no_std
environments, which imposes specific constraints on memory management and available standard library features.
no_std
Implications
- No heap allocation: All storage uses stack-allocated arrays
- No
std::collections
: Custom container implementation required - Limited error handling: No
std::error
trait or panic handling - Core library only: Restricted to
core::mem
and similar basic utilities
Memory Safety Mechanisms
MaybeUninit<T>
wrapper: Safely handles uninitialized memory slots- Bitmap synchronization: Ensures
id_bitmap
state matches object initialization - Unsafe operations: Contained within safe public interfaces with documented invariants
flowchart TD subgraph SafetyInvariants["Safety Invariants"] Invariant1["If id_bitmap.get(i) == truethen objects[i] contains valid T"] Invariant2["If id_bitmap.get(i) == falsethen objects[i] is uninitialized"] Invariant3["count equals number of true bitsin id_bitmap"] end subgraph MemoryLayout["Memory Layout"] subgraph BitmapArray["id_bitmap: Bitmap<CAP>"] FalseBits["false bits (unassigned)"] TrueBits["true bits (assigned)"] end subgraph ObjectArray["objects: [MaybeUninit<T>; CAP]"] Uninit["MaybeUninit::uninit()"] Valid["MaybeUninit with valid T"] end end FalseBits --> Uninit TrueBits --> Valid Uninit --> Invariant2 Valid --> Invariant1
Safety Guarantees
- Objects are only accessible when their corresponding bitmap bit is set
assume_init_ref()
andassume_init_mut()
are only called on initialized slots- Memory is properly cleaned up when objects are removed via
assume_init_read()
Sources: src/lib.rs(L32) src/lib.rs(L48) src/lib.rs(L79 - L83) src/lib.rs(L165 - L172)
Capacity Constraints and Generic Parameters
The container uses compile-time generic parameters to ensure type safety and memory efficiency.
Generic Parameters
T
: The type of objects stored in the containerCAP
: Compile-time constant defining maximum capacity
Type Constraints
BitsImpl<{ CAP }>: Bits
: Ensures the bitmap implementation supports the specified capacityCAP
must not exceed 1024 due to bitmap library limitations
Capacity Characteristics
- Fixed size: Memory allocation is determined at compile time
- Zero-cost abstraction: No runtime overhead for capacity checks
- Stack allocated: Entire container lives on the stack (no heap allocation)
Sources: src/lib.rs(L44 - L46) src/lib.rs(L61) src/lib.rs(L86 - L101)
Dependency Integration
The crate integrates with the bitmaps
crate for efficient ID tracking and management.
External Dependencies
bitmaps
crate (v3.2): ProvidesBitmap<CAP>
implementationcore::mem
: ProvidesMaybeUninit
for safe uninitialized memory handling
Bitmap Integration
Bitmap<CAP>
tracks which IDs are assignedfirst_false_index()
method enables efficient ID allocationIter<CAP>
provides iteration over assigned IDs viaids()
method
Sources: src/lib.rs(L34) src/lib.rs(L344 - L346)
FlattenObjects API Documentation
Relevant source files
This document provides comprehensive API documentation for the FlattenObjects<T, CAP>
struct, which serves as the core container type in the flatten_objects crate. The documentation covers all public methods, their signatures, behavior, error conditions, and usage patterns.
For conceptual information about the crate's purpose and design principles, see Overview. For implementation details and internal architecture, see Implementation Details. For practical usage examples and patterns, see Usage Guide and Examples.
API Structure Overview
The FlattenObjects
API is organized around three primary operation categories: container management, object lifecycle operations, and query/inspection methods.
flowchart TD subgraph Return_Types["Return_Types"] Result_usize_T["Result"] Result_usize_Option_T["Result>"] Option_ref_T["Option<&T>"] Option_mut_T["Option<&mut T>"] Option_T["Option"] Iter_CAP["Iter"] end subgraph subGraph6["FlattenObjects"] subgraph Internal_State["Internal_State"] objects_field["objects: [MaybeUninit; CAP]"] id_bitmap["id_bitmap: Bitmap"] count_field["count: usize"] end subgraph Iteration["Iteration"] ids["ids()"] end subgraph Access_Operations["Access_Operations"] get["get(id: usize)"] get_mut["get_mut(id: usize)"] is_assigned["is_assigned(id: usize)"] end subgraph Object_Lifecycle["Object_Lifecycle"] capacity["capacity()"] new["new()"] add["add(value: T)"] add_at["add_at(id: usize, value: T)"] add_or_replace["add_or_replace_at(id: usize, value: T)"] remove["remove(id: usize)"] end subgraph Configuration["Configuration"] count["count()"] subgraph Creation["Creation"] capacity["capacity()"] new["new()"] end end end bool["bool"] add --> Result_usize_T add --> count_field add --> id_bitmap add --> objects_field add_at --> Result_usize_T add_at --> count_field add_at --> id_bitmap add_at --> objects_field add_or_replace --> Result_usize_Option_T get --> Option_ref_T get --> id_bitmap get --> objects_field get_mut --> Option_mut_T get_mut --> id_bitmap get_mut --> objects_field ids --> Iter_CAP ids --> id_bitmap is_assigned --> bool is_assigned --> id_bitmap remove --> Option_T remove --> count_field remove --> id_bitmap remove --> objects_field
Sources: src/lib.rs(L37 - L347)
Method Classification and Signatures
The API methods can be classified by their primary function and the type of operations they perform on the container state.
Category | Method | Signature | State Changes |
---|---|---|---|
Creation | new | const fn new() -> Self | Initializes empty container |
Inspection | capacity | const fn capacity(&self) -> usize | None |
Inspection | count | const fn count(&self) -> usize | None |
Query | is_assigned | fn is_assigned(&self, id: usize) -> bool | None |
Access | get | fn get(&self, id: usize) -> Option<&T> | None |
Access | get_mut | fn get_mut(&mut self, id: usize) -> Option<&mut T> | None |
Mutation | add | fn add(&mut self, value: T) -> Result<usize, T> | Modifies objects, id_bitmap, count |
Mutation | add_at | fn add_at(&mut self, id: usize, value: T) -> Result<usize, T> | Modifies objects, id_bitmap, count |
Mutation | add_or_replace_at | fn add_or_replace_at(&mut self, id: usize, value: T) -> Result<usize, Option | Modifies objects, id_bitmap, potentially count |
Mutation | remove | fn remove(&mut self, id: usize) -> Option | Modifies objects, id_bitmap, count |
Iteration | ids | fn ids(&self) -> Iter | None |
Sources: src/lib.rs(L77 - L84) src/lib.rs(L98 - L101) src/lib.rs(L121 - L124) src/lib.rs(L143 - L146) src/lib.rs(L164 - L173) src/lib.rs(L193 - L202) src/lib.rs(L222 - L232) src/lib.rs(L249 - L257) src/lib.rs(L277 - L297) src/lib.rs(L315 - L326) src/lib.rs(L343 - L346)
Object Lifecycle State Machine
The following diagram shows how objects transition through different states within the FlattenObjects
container, mapped to specific API operations.
stateDiagram-v2 [*] --> Unallocated : "new()" [*] --> Available Available --> Available Available --> Available : "Failed add/add_at calls" Available --> Available [*] --> Assigned Assigned --> Modified : "get_mut(id)" Modified --> Assigned : "Access complete" Assigned --> Modified Modified --> Modified : "get(id), is_assigned(id)" Modified --> Assigned note left of Available----note : ['"id_bitmap.get(id) == false<br>objects[id] uninitialized"'] note left of Available : ['"id_bitmap.get(id) == false<br>objects[id] uninitialized"'] note left of Assigned : ['"id_bitmap.get(id) == true<br>objects[id] contains valid T"'] note left of Modified : ['"Mutable reference active<br>Temporary state during get_mut"'] [*] --> alloc_start : "add(), add_at(), add_or_replace_at()" [*] --> unalloc_start : "remove(id)" note left of alloc_start : ['"id_bitmap.get(id) == false<br>objects[id] uninitialized"'] note left of Available----note : ['"id_bitmap.get(id) == false<br>objects[id] uninitialized"']
Sources: src/lib.rs(L222 - L232) src/lib.rs(L249 - L257) src/lib.rs(L277 - L297) src/lib.rs(L315 - L326) src/lib.rs(L164 - L173) src/lib.rs(L193 - L202) src/lib.rs(L143 - L146)
Container Creation and Configuration
Constructor Method
The new()
method creates an empty FlattenObjects
container with all slots unallocated.
// From src/lib.rs:77-84
pub const fn new() -> Self
Constraints:
CAP
must not exceed 1024 (enforced at compile time)- Function is
const
, enabling compile-time initialization - Zero-initializes the
id_bitmap
safely usingMaybeUninit::zeroed()
Example behavior:
let objects = FlattenObjects::<u32, 20>::new();
// All IDs 0..19 are available
// count() returns 0
// No memory is initialized for objects
Sources: src/lib.rs(L77 - L84)
Capacity and Size Methods
The container provides compile-time and runtime information about its configuration and current state.
Method | Return Type | Description |
---|---|---|
capacity() | usize | Maximum objects that can be stored (CAP) |
count() | usize | Current number of assigned objects |
The relationship between these values follows the invariant: 0 <= count() <= capacity()
.
Sources: src/lib.rs(L98 - L101) src/lib.rs(L121 - L124)
Object Management Operations
Addition Methods
The container provides three distinct methods for adding objects, each with different ID assignment strategies:
flowchart TD subgraph Return_Values["Return_Values"] Ok_id["Ok(id)"] Err_value["Err(value)"] Err_Some_old["Err(Some(old_value))"] Err_None["Err(None)"] end subgraph Success_Conditions["Success_Conditions"] available_slot["Available slot exists"] id_not_assigned["ID not assigned && id < CAP"] id_in_range["id < CAP"] end subgraph ID_Assignment["ID_Assignment"] auto_id["Automatic ID(first_false_index)"] explicit_id["Explicit ID(user-specified)"] replace_id["Explicit ID(with replacement)"] end subgraph Addition_Methods["Addition_Methods"] add_method["add(value: T)"] add_at_method["add_at(id: usize, value: T)"] add_or_replace_method["add_or_replace_at(id: usize, value: T)"] end add_at_method --> explicit_id add_method --> auto_id add_or_replace_method --> replace_id auto_id --> available_slot available_slot --> Err_value available_slot --> Ok_id explicit_id --> id_not_assigned id_in_range --> Err_None id_in_range --> Err_Some_old id_in_range --> Ok_id id_not_assigned --> Err_value id_not_assigned --> Ok_id replace_id --> id_in_range
Sources: src/lib.rs(L222 - L232) src/lib.rs(L249 - L257) src/lib.rs(L277 - L297)
Automatic ID Assignment:add()
#![allow(unused)] fn main() { // From src/lib.rs:222-232 pub fn add(&mut self, value: T) -> Result<usize, T> }
Behavior:
- Finds the first available ID using
id_bitmap.first_false_index()
- Assigns the object to that ID if available
- Updates
count
,id_bitmap
, and initializesobjects[id]
Error conditions:
- Returns
Err(value)
if no available slots exist - Returns
Err(value)
iffirst_false_index()
returns ID >=CAP
Explicit ID Assignment:add_at()
#![allow(unused)] fn main() { // From src/lib.rs:249-257 pub fn add_at(&mut self, id: usize, value: T) -> Result<usize, T> }
Behavior:
- Validates
id < CAP
and!is_assigned(id)
- Assigns object to specified ID if validation passes
- Updates
count
,id_bitmap
, and initializesobjects[id]
Error conditions:
- Returns
Err(value)
ifid >= CAP
- Returns
Err(value)
ifis_assigned(id)
returnstrue
Replacement Assignment:add_or_replace_at()
#![allow(unused)] fn main() { // From src/lib.rs:277-297 pub fn add_or_replace_at(&mut self, id: usize, value: T) -> Result<usize, Option<T>> }
Behavior:
- Validates
id < CAP
- If ID assigned: replaces object and returns
Err(Some(old_value))
- If ID unassigned: assigns object and returns
Ok(id)
Error conditions:
- Returns
Err(None)
ifid >= CAP
- Returns
Err(Some(old))
if replacing existing object
Sources: src/lib.rs(L222 - L232) src/lib.rs(L249 - L257) src/lib.rs(L277 - L297)
Removal Method
#![allow(unused)] fn main() { // From src/lib.rs:315-326 pub fn remove(&mut self, id: usize) -> Option<T> }
State changes:
- Sets
id_bitmap.set(id, false)
to mark ID as available - Decrements
count
- Reads and returns object using
assume_init_read()
Safety considerations:
- Object memory is not overwritten, only marked as uninitialized
- ID becomes available for reuse immediately
- Calling
remove()
on unassigned ID returnsNone
without side effects
Sources: src/lib.rs(L315 - L326)
Query and Inspection Methods
Access Methods
The container provides immutable and mutable access to stored objects through bounds-checked operations.
flowchart TD subgraph Return_Types["Return_Types"] Option_ref["Option<&T>"] Option_mut["Option<&mut T>"] bool_result["bool"] end subgraph Memory_Operations["Memory_Operations"] assume_init_ref["assume_init_ref()"] assume_init_mut["assume_init_mut()"] no_memory_access["Return boolean only"] end subgraph Validation_Steps["Validation_Steps"] bounds_check["id < CAP"] bitmap_check["id_bitmap.get(id)"] combined_check["is_assigned(id)"] end subgraph Access_Methods["Access_Methods"] get_method["get(id: usize)"] get_mut_method["get_mut(id: usize)"] is_assigned_method["is_assigned(id: usize)"] end assume_init_mut --> Option_mut assume_init_ref --> Option_ref bitmap_check --> no_memory_access combined_check --> assume_init_mut combined_check --> assume_init_ref get_method --> combined_check get_mut_method --> combined_check is_assigned_method --> bitmap_check is_assigned_method --> bounds_check no_memory_access --> bool_result
Sources: src/lib.rs(L164 - L173) src/lib.rs(L193 - L202) src/lib.rs(L143 - L146)
Immutable Access:get()
#![allow(unused)] fn main() { // From src/lib.rs:164-173 pub fn get(&self, id: usize) -> Option<&T> }
Implementation details:
- Calls
is_assigned(id)
for validation - Uses
assume_init_ref()
to create reference fromMaybeUninit<T>
- Returns
None
for unassigned IDs or out-of-bounds access
Mutable Access:get_mut()
#![allow(unused)] fn main() { // From src/lib.rs:193-202 pub fn get_mut(&mut self, id: usize) -> Option<&mut T> }
Implementation details:
- Identical validation logic to
get()
- Uses
assume_init_mut()
to create mutable reference - Requires exclusive container access (
&mut self
)
Assignment Check:is_assigned()
#![allow(unused)] fn main() { // From src/lib.rs:143-146 pub fn is_assigned(&self, id: usize) -> bool }
Implementation details:
- Returns
false
forid >= CAP
(bounds check) - Returns
id_bitmap.get(id)
for valid IDs - Does not access object memory, only bitmap state
Sources: src/lib.rs(L164 - L173) src/lib.rs(L193 - L202) src/lib.rs(L143 - L146)
Iteration Support
#![allow(unused)] fn main() { // From src/lib.rs:343-346 pub fn ids(&self) -> Iter<CAP> }
The ids()
method provides an iterator over assigned object IDs by delegating to the bitmap's iterator implementation.
Characteristics:
- Returns
Iter<CAP>
from thebitmaps
crate - Iterates only over assigned IDs (where bitmap bit is
true
) - Order follows the bitmap's internal iteration order (typically ascending)
- Iterator is independent of object values, only reads bitmap state
Sources: src/lib.rs(L343 - L346)
Container Creation and Configuration
Relevant source files
This document covers the creation and configuration of FlattenObjects
containers, including struct definition, generic parameters, capacity constraints, and initialization methods. For information about object management operations like adding and removing objects, see Object Management Operations. For internal implementation details, see Internal Data Structures.
FlattenObjects Struct Definition
The FlattenObjects<T, CAP>
struct serves as the core container for managing numbered objects with unique IDs. The structure is designed for no_std
environments and provides compile-time capacity configuration.
flowchart TD subgraph subGraph3["FlattenObjects Structure"] FlattenObjects["FlattenObjects<T, CAP>"] subgraph subGraph2["Trait Bounds"] bits_impl["BitsImpl<{CAP}>: Bits"] end subgraph subGraph1["Generic Parameters"] T_param["T: Object Type"] CAP_param["CAP: Capacity (const usize)"] end subgraph Fields["Fields"] objects_field["objects: [MaybeUninit<T>; CAP]"] id_bitmap_field["id_bitmap: Bitmap<CAP>"] count_field["count: usize"] end end CAP_param --> id_bitmap_field CAP_param --> objects_field FlattenObjects --> CAP_param FlattenObjects --> T_param FlattenObjects --> bits_impl FlattenObjects --> count_field FlattenObjects --> id_bitmap_field FlattenObjects --> objects_field T_param --> objects_field bits_impl --> id_bitmap_field
The struct contains three essential fields that work together to provide efficient object storage and ID management:
Field | Type | Purpose |
---|---|---|
objects | [MaybeUninit | Fixed-size array storing the actual objects |
id_bitmap | Bitmap | Tracks which IDs are currently assigned |
count | usize | Maintains the current number of stored objects |
Sources: src/lib.rs(L44 - L51)
Generic Parameters and Constraints
The FlattenObjects
struct uses two generic parameters that determine its behavior and capabilities:
flowchart TD subgraph subGraph3["Generic Parameter System"] subgraph subGraph2["Trait Bound"] BitsImpl["BitsImpl<{CAP}>: Bits"] bitmap_dep["Required bybitmaps crate"] end subgraph subGraph1["Const Parameter CAP"] CAP["const CAP: usize"] CAP_constraints["Constraints:• Must be ≤ 1024• Compile-time constant• Determines max objects"] end subgraph subGraph0["Type Parameter T"] T["T"] T_examples["Examples:• u32• String• ProcessDescriptor• FileHandle"] end end BitsImpl --> bitmap_dep CAP --> BitsImpl CAP --> CAP_constraints T --> T_examples
Type ParameterT
The T
parameter represents the type of objects stored in the container. It can be any type that implements the required traits for the operations you intend to perform. Common examples include primitive types, structs representing system resources, or handles.
Capacity ParameterCAP
The CAP
parameter is a compile-time constant that defines:
- Maximum number of objects the container can hold
- Maximum ID value (CAP - 1)
- Size of the internal bitmap and object array
The capacity is subject to a hard limit of 1024 objects, enforced by the bitmaps
crate dependency.
Trait Bounds
The constraint BitsImpl<{ CAP }>: Bits
ensures that the bitmaps
crate can handle the specified capacity. This trait bound is automatically satisfied for capacities up to 1024.
Sources: src/lib.rs(L44 - L46) src/lib.rs(L54 - L55)
Container Initialization
Container creation is handled through the new()
constructor method, which provides compile-time initialization with proper memory layout.
flowchart TD subgraph subGraph3["Container Initialization Process"] new_call["FlattenObjects::<T, CAP>::new()"] subgraph Result["Result"] empty_container["Empty FlattenObjects instance"] end subgraph Validation["Validation"] cap_check["CAP ≤ 1024 check"] compile_panic["Compile-time panic if CAP > 1024"] end subgraph subGraph0["Field Initialization"] init_objects["objects: [MaybeUninit::uninit(); CAP]"] init_bitmap["id_bitmap: Bitmap zeroed"] init_count["count: 0"] end end cap_check --> compile_panic cap_check --> init_bitmap cap_check --> init_count cap_check --> init_objects init_bitmap --> empty_container init_count --> empty_container init_objects --> empty_container new_call --> cap_check
Constructor Method
The new()
method is declared as const fn
, enabling compile-time evaluation and static initialization. Key initialization steps include:
- Object Array: Initialized with
MaybeUninit::uninit()
to avoid unnecessary initialization overhead - ID Bitmap: Zero-initialized using unsafe operations, which is valid for the
Bitmap
type - Count Field: Set to 0 to indicate an empty container
Capacity Validation
The constructor enforces the 1024 capacity limit at compile time. Attempts to create containers with CAP > 1024
will result in compilation failure.
// Valid - compiles successfully
let objects = FlattenObjects::<u32, 20>::new();
// Invalid - compilation error
let objects = FlattenObjects::<u32, 1025>::new();
Memory Safety Considerations
The initialization process carefully handles memory safety:
- Uses
MaybeUninit<T>
to avoid constructingT
instances prematurely - Employs safe zero-initialization for the bitmap
- Maintains proper alignment and memory layout
Sources: src/lib.rs(L77 - L84) src/lib.rs(L59 - L61)
Capacity Configuration and Limits
The capacity system provides both flexibility and safety through compile-time configuration with runtime access methods.
Capacity Access
The capacity()
method provides read-only access to the container's maximum capacity:
Method | Return Type | Description |
---|---|---|
capacity() | usize | Returns theCAPgeneric parameter value |
count() | usize | Returns current number of stored objects |
Capacity Relationship with ID Range
The capacity directly determines the valid ID range for objects:
- Valid IDs:
0
toCAP - 1
(inclusive) - Total possible IDs:
CAP
- Maximum objects:
CAP
Implementation Constraints
The 1024 limit stems from the underlying bitmaps
crate implementation, which uses fixed-size bitmap representations optimized for performance in constrained environments.
Sources: src/lib.rs(L98 - L101) src/lib.rs(L41 - L43)
Object Management Operations
Relevant source files
This document covers the core object management operations provided by the FlattenObjects<T, CAP>
container. These operations enable adding, removing, and accessing objects within the fixed-capacity container while maintaining unique ID assignments and memory safety.
The operations documented here handle the fundamental lifecycle of objects within the container. For information about container creation and configuration, see Container Creation and Configuration. For query and inspection methods that don't modify objects, see Query and Inspection Methods.
Operation Categories Overview
The FlattenObjects
container provides three primary categories of object management operations:
Category | Methods | Purpose |
---|---|---|
Adding Objects | add(),add_at(),add_or_replace_at() | Insert objects with automatic or manual ID assignment |
Removing Objects | remove() | Remove objects and free their IDs for reuse |
Accessing Objects | get(),get_mut() | Retrieve immutable or mutable references to stored objects |
Each operation maintains the invariant relationship between the id_bitmap
and objects
array, ensuring that only initialized memory locations are accessible and that ID assignments remain consistent.
Object Management Operation Flow
flowchart TD subgraph subGraph3["Internal State Updates"] BitmapSet["id_bitmap.set(id, true)"] BitmapClear["id_bitmap.set(id, false)"] CountIncr["count += 1"] CountDecr["count -= 1"] WriteObj["objects[id].write(value)"] ReadObj["objects[id].assume_init_ref()"] TakeObj["objects[id].assume_init_read()"] end subgraph subGraph2["Remove Operations"] Remove["remove(id)"] end subgraph subGraph1["Access Operations"] Get["get(id)"] GetMut["get_mut(id)"] end subgraph subGraph0["Add Operations"] Add["add(value)"] AddAt["add_at(id, value)"] AddReplace["add_or_replace_at(id, value)"] end Start["Object Management Request"] Add --> BitmapSet AddAt --> BitmapSet AddReplace --> BitmapSet BitmapClear --> CountDecr BitmapSet --> CountIncr CountDecr --> TakeObj CountIncr --> WriteObj Get --> ReadObj GetMut --> ReadObj Remove --> BitmapClear Start --> Add Start --> AddAt Start --> AddReplace Start --> Get Start --> GetMut Start --> Remove
Sources: src/lib.rs(L222 - L347)
Adding Objects
The container provides three methods for adding objects, each with different ID assignment strategies and error handling behaviors.
Automatic ID Assignment:add()
The add()
method assigns the smallest available ID to a new object using the bitmap's first_false_index()
function.
flowchart TD AddCall["add(value: T)"] FindID["id_bitmap.first_false_index()"] CheckCap["id < CAP?"] UpdateBitmap["id_bitmap.set(id, true)"] IncrCount["count += 1"] WriteValue["objects[id].write(value)"] ReturnOk["Ok(id)"] ReturnErr["Err(value)"] AddCall --> FindID CheckCap --> ReturnErr CheckCap --> UpdateBitmap FindID --> CheckCap IncrCount --> WriteValue UpdateBitmap --> IncrCount WriteValue --> ReturnOk
Method Signature: pub fn add(&mut self, value: T) -> Result<usize, T>
Behavior:
- Searches for the first unassigned ID using
first_false_index()
- Validates the ID is within capacity bounds (
id < CAP
) - Updates
id_bitmap
, incrementscount
, and writes the object - Returns the assigned ID on success, or the original value on failure
Error Conditions:
- Container is at full capacity
- No available IDs found
Sources: src/lib.rs(L222 - L232)
Manual ID Assignment:add_at()
The add_at()
method places an object at a specific ID if that ID is available.
Method Signature: pub fn add_at(&mut self, id: usize, value: T) -> Result<usize, T>
Behavior:
- Validates
id < CAP
and!is_assigned(id)
- If valid, performs the same state updates as
add()
- Returns the specified ID on success
Error Conditions:
- ID is out of range (
id >= CAP
) - ID is already assigned (
is_assigned(id)
returnstrue
)
Sources: src/lib.rs(L249 - L257)
Replace-or-Add:add_or_replace_at()
The add_or_replace_at()
method either adds a new object or replaces an existing one at the specified ID.
Method Signature: pub fn add_or_replace_at(&mut self, id: usize, value: T) -> Result<usize, Option<T>>
Behavior:
- If ID is unassigned: performs standard add operation
- If ID is assigned: replaces existing object without changing
count
- Uses
assume_init_read()
to safely extract the old value
Return Values:
Ok(id)
: Object added successfully at the specified IDErr(Some(old_value))
: ID was assigned, old object returnedErr(None)
: ID is out of range
Sources: src/lib.rs(L277 - L297)
Removing Objects
The remove()
method extracts an object from the container and frees its ID for reuse.
Object Removal State Transition
stateDiagram-v2 state Assigned ID { [*] --> BitmapTrue : "id_bitmap.get(id) == true" [*] --> ObjectInit : "objects[id] contains valid T" [*] --> CountIncluded : "included in count" } state Remove Operation { [*] --> CheckAssigned : "remove(id) called" CheckAssigned --> ClearBitmap : "is_assigned(id) == true" ClearBitmap --> DecrCount : "id_bitmap.set(id, false)" DecrCount --> ReadObject : "count -= 1" ReadObject --> ReturnObject : "assume_init_read()" CheckAssigned --> ReturnNone : "is_assigned(id) == false" } state Available ID { [*] --> BitmapFalse : "id_bitmap.get(id) == false" [*] --> ObjectUninit : "objects[id] uninitialized" [*] --> CountExcluded : "not included in count" } [*] --> Assigned ID : "object added" Assigned ID --> Remove Operation : "remove(id)" Remove Operation --> Available ID : "success" Remove Operation --> Assigned ID : "failure (return None)"
Method Signature: pub fn remove(&mut self, id: usize) -> Option<T>
Behavior:
- Checks
is_assigned(id)
to validate the operation - Clears the bitmap bit:
id_bitmap.set(id, false)
- Decrements the object count:
count -= 1
- Safely reads the object value using
assume_init_read()
- Returns the extracted object
Memory Safety: The method uses assume_init_read()
which is safe because the bitmap ensures only initialized memory locations are accessed.
Sources: src/lib.rs(L315 - L326)
Accessing Objects
The container provides both immutable and mutable access to stored objects through bounds-checked operations.
Immutable Access:get()
Method Signature: pub fn get(&self, id: usize) -> Option<&T>
Behavior:
- Validates ID assignment using
is_assigned(id)
- Returns immutable reference via
assume_init_ref()
- Returns
None
for unassigned or out-of-range IDs
Mutable Access:get_mut()
Method Signature: pub fn get_mut(&mut self, id: usize) -> Option<&mut T>
Behavior:
- Identical validation to
get()
- Returns mutable reference via
assume_init_mut()
- Enables in-place modification of stored objects
Safety Invariant: Both methods rely on the bitmap to ensure that only initialized MaybeUninit<T>
slots are accessed, maintaining memory safety without runtime initialization checks.
Sources: src/lib.rs(L165 - L202)
Error Handling Patterns
The object management operations use consistent error handling patterns that preserve the original values when operations fail:
Operation | Success Type | Error Type | Error Preservation |
---|---|---|---|
add() | Ok(usize) | Err(T) | Returns original value |
add_at() | Ok(usize) | Err(T) | Returns original value |
add_or_replace_at() | Ok(usize) | Err(Option | Returns old value orNone |
remove() | Some(T) | None | N/A |
get() | Some(&T) | None | N/A |
get_mut() | Some(&mut T) | None | N/A |
This design ensures that failed operations don't consume the input values, allowing callers to retry or handle errors appropriately.
ID Assignment and Reuse Logic
The container implements an efficient ID reuse strategy through the bitmap-based allocation system:
- Initial Assignment:
add()
usesfirst_false_index()
to find the lowest available ID - ID Liberation:
remove()
immediately marks IDs as available by clearing bitmap bits - Reuse Priority: Newly available IDs become candidates for the next
add()
operation - Capacity Bounds: All operations respect the compile-time
CAP
limit
This approach ensures optimal ID utilization while maintaining O(1) allocation performance for most operations.
Sources: src/lib.rs(L223 - L231) src/lib.rs(L315 - L325)
Query and Inspection Methods
Relevant source files
This document covers the read-only methods provided by FlattenObjects
for inspecting container state and querying information about stored objects. These methods allow users to check object assignment status, retrieve container metadata, and iterate over assigned IDs without modifying the container's contents.
For methods that modify container contents (adding, removing, and accessing objects), see Object Management Operations. For details about the underlying data structures, see Internal Data Structures.
Core State Inspection Methods
The FlattenObjects
container provides several fundamental methods for querying its current state. These methods form the foundation for understanding what objects are stored and how the container's capacity is being utilized.
flowchart TD subgraph subGraph2["Information Provided"] assignmentStatus["ID Assignment Status"] containerCapacity["Maximum Capacity"] currentUsage["Current Object Count"] assignedIds["List of Active IDs"] end subgraph subGraph1["Internal Data Structures"] idBitmap["id_bitmap: Bitmap"] objectsArray["objects: [MaybeUninit; CAP]"] countField["count: usize"] capConstant["CAP: const usize"] end subgraph subGraph0["Query Methods API"] isAssigned["is_assigned(id: usize) -> bool"] capacity["capacity() -> usize"] count["count() -> usize"] ids["ids() -> Iter"] end capacity --> capConstant capacity --> containerCapacity count --> countField count --> currentUsage ids --> assignedIds ids --> idBitmap isAssigned --> assignmentStatus isAssigned --> idBitmap
Title: Query Methods and Data Structure Relationships
Sources: src/lib.rs(L44 - L51) src/lib.rs(L86 - L146) src/lib.rs(L328 - L346)
Assignment Status Checking
The is_assigned
method determines whether a specific ID currently has an object assigned to it. This method provides bounds checking and bitmap consultation in a single operation.
Method | Parameters | Return Type | Purpose |
---|---|---|---|
is_assigned | id: usize | bool | Check if ID is assigned and valid |
The method returns false
for IDs that are out of range (≥ CAP
) or unassigned. This dual behavior eliminates the need for separate bounds checking in user code.
Sources: src/lib.rs(L126 - L146)
Container Capacity Information
The capacity
method returns the compile-time constant CAP
, representing the maximum number of objects the container can hold. This value also represents the highest valid ID plus one.
Method | Parameters | Return Type | Purpose |
---|---|---|---|
capacity | None | usize | Get maximum container capacity |
The capacity is immutable and determined at compile time through the generic parameter CAP
.
Sources: src/lib.rs(L86 - L101)
Current Usage Tracking
The count
method provides the current number of assigned objects in the container. This count is maintained automatically as objects are added and removed.
Method | Parameters | Return Type | Purpose |
---|---|---|---|
count | None | usize | Get current number of stored objects |
The count reflects only assigned objects and decreases when objects are removed, allowing IDs to be reused.
Sources: src/lib.rs(L103 - L124)
Container Iteration
The ids
method provides an iterator over all currently assigned IDs, enabling enumeration of active objects without accessing their values.
sequenceDiagram participant UserCode as "User Code" participant FlattenObjects as "FlattenObjects" participant id_bitmapBitmapCAP as "id_bitmap: Bitmap<CAP>" participant IterCAP as "Iter<CAP>" UserCode ->> FlattenObjects: ids() FlattenObjects ->> id_bitmapBitmapCAP: into_iter() id_bitmapBitmapCAP ->> IterCAP: create iterator IterCAP -->> FlattenObjects: Iter<CAP> FlattenObjects -->> UserCode: Iter<CAP> loop "For each assigned ID" UserCode ->> IterCAP: next() IterCAP ->> id_bitmapBitmapCAP: find next set bit id_bitmapBitmapCAP -->> IterCAP: Option<usize> IterCAP -->> UserCode: Option<usize> end
Title: ID Iterator Workflow
The iterator is provided by the bitmaps
crate and efficiently traverses only the set bits in the bitmap, skipping unassigned IDs.
Method | Parameters | Return Type | Purpose |
---|---|---|---|
ids | None | Iter | Iterator over assigned IDs |
Sources: src/lib.rs(L328 - L346) src/lib.rs(L34)
State Inspection Patterns
These query methods are commonly used together to implement various inspection patterns for understanding container state.
flowchart TD subgraph subGraph2["Use Cases"] resourceManagement["Resource Management"] debugging["Debugging/Monitoring"] algorithms["Algorithm Logic"] safety["Safety Checks"] end subgraph subGraph1["Method Combinations"] countVsCapacity["count() vs capacity()"] isAssignedCheck["is_assigned(id)"] idsIteration["ids() iteration"] combined["Multiple methods"] end subgraph subGraph0["Common Inspection Patterns"] fullnessCheck["Fullness Check"] availabilityCheck["ID Availability Check"] usageAnalysis["Usage Analysis"] validation["State Validation"] end availabilityCheck --> algorithms availabilityCheck --> isAssignedCheck fullnessCheck --> countVsCapacity fullnessCheck --> resourceManagement usageAnalysis --> debugging usageAnalysis --> idsIteration validation --> combined validation --> safety
Title: Query Method Usage Patterns
Capacity and Usage Analysis
Comparing count()
and capacity()
provides insight into container utilization:
- Full container:
count() == capacity()
- Empty container:
count() == 0
- Utilization ratio:
count() as f32 / capacity() as f32
ID Validation Workflows
The is_assigned
method serves multiple validation purposes:
- Pre-access validation before calling
get
orget_mut
- Availability checking before
add_at
operations - State consistency verification in algorithms
Comprehensive State Enumeration
The ids()
iterator enables complete state inspection:
- Enumerating all active objects without accessing values
- Building secondary data structures based on assigned IDs
- Implementing custom iteration patterns over active objects
Sources: src/lib.rs(L8 - L30) src/lib.rs(L332 - L342)
Implementation Details
The query methods leverage the internal bitmap and counter to provide efficient, constant-time operations for most queries.
flowchart TD subgraph subGraph2["Safety Guarantees"] boundsChecking["Automatic Bounds Checking"] noUnsafeCode["No Unsafe Operations"] stateConsistency["State Consistency"] end subgraph subGraph1["Data Dependencies"] bitmapOps["Bitmap Operations"] constantAccess["Constant Access"] iteratorLogic["Iterator Logic"] end subgraph subGraph0["Performance Characteristics"] isAssignedTime["is_assigned: O(1)"] capacityTime["capacity: O(1)"] countTime["count: O(1)"] idsTime["ids: O(n) iteration"] end bitmapOps --> boundsChecking capacityTime --> constantAccess constantAccess --> noUnsafeCode countTime --> constantAccess idsTime --> iteratorLogic isAssignedTime --> bitmapOps iteratorLogic --> stateConsistency
Title: Query Method Implementation Characteristics
Bounds Checking Integration
The is_assigned
method combines bounds checking with bitmap lookup: id < CAP && self.id_bitmap.get(id)
. This ensures that out-of-range IDs are handled safely without requiring separate validation.
Bitmap Integration
All query operations rely on the bitmaps
crate for efficient bit manipulation. The bitmap provides O(1) bit access and optimized iteration over set bits.
Memory Safety
Unlike the object management methods, query methods require no unsafe operations. They only access the bitmap and counter fields, which are always in a valid state.
Sources: src/lib.rs(L144 - L146) src/lib.rs(L98 - L100) src/lib.rs(L121 - L123) src/lib.rs(L343 - L345)
Implementation Details
Relevant source files
This document provides a comprehensive analysis of the internal architecture, data structures, and implementation strategies used in the flatten_objects
crate. It covers the low-level details of how the FlattenObjects
container manages memory, maintains safety invariants, and implements efficient ID allocation.
For high-level API usage patterns, see Usage Guide and Examples. For specific API method documentation, see FlattenObjects API Documentation.
Core Architecture Overview
The FlattenObjects<T, CAP>
struct implements a fixed-capacity object container through three interconnected components that work together to provide safe, efficient object storage with unique ID assignment.
Internal Data Structure Layout
flowchart TD subgraph subGraph3["External Dependencies"] BitsImpl["BitsImpl<CAP>: Bits"] MaybeUninit_core["core::mem::MaybeUninit"] bitmaps_crate["bitmaps::Bitmap"] end subgraph subGraph2["Bitmap Representation"] bit0["bit 0: assigned/free"] bit1["bit 1: assigned/free"] bitN["bit CAP-1: assigned/free"] end subgraph subGraph1["Memory Organization"] slot0["objects[0]: MaybeUninit<T>"] slot1["objects[1]: MaybeUninit<T>"] slotN["objects[CAP-1]: MaybeUninit<T>"] end subgraph FlattenObjects["FlattenObjects<T, CAP>"] objects["objects: [MaybeUninit<T>; CAP]"] id_bitmap["id_bitmap: Bitmap<CAP>"] count["count: usize"] end bit0 --> slot0 bit1 --> slot1 bitN --> slotN id_bitmap --> bit0 id_bitmap --> bit1 id_bitmap --> bitN id_bitmap --> bitmaps_crate objects --> MaybeUninit_core objects --> slot0 objects --> slot1 objects --> slotN
The structure maintains three critical invariants:
id_bitmap.get(i) == true
if and only ifobjects[i]
contains an initialized valuecount
equals the number of set bits inid_bitmap
- All operations preserve these relationships atomically
Sources: src/lib.rs(L44 - L51)
Generic Parameter Constraints
The FlattenObjects
implementation relies on compile-time constraints that ensure type safety and capacity limits:
Parameter | Constraint | Purpose |
---|---|---|
T | Any type | The stored object type |
CAP | const usize | Maximum container capacity |
BitsImpl<{CAP}> | Bits | Enables bitmap operations for the given capacity |
The capacity constraint CAP <= 1024
is enforced through the bitmaps
crate's type system and validated at construction time in new()
.
Sources: src/lib.rs(L44 - L46) src/lib.rs(L59 - L61)
Memory Management Strategy
The implementation uses MaybeUninit<T>
to avoid unnecessary initialization overhead and enable precise control over object lifecycle:
Initialization States and Transitions
The bitmap serves as the authoritative source of truth for memory initialization state. All access to objects[i]
is guarded by checking id_bitmap.get(i)
first.
Sources: src/lib.rs(L48 - L49) src/lib.rs(L77 - L84)
Unsafe Operation Boundaries
The implementation carefully encapsulates unsafe operations within safe method boundaries:
Unsafe Operation | Location | Safety Guarantee |
---|---|---|
assume_init_ref() | get()method | Protected byis_assigned()check |
assume_init_mut() | get_mut()method | Protected byis_assigned()check |
assume_init_read() | remove()andadd_or_replace_at() | Protected byis_assigned()check |
MaybeUninit::zeroed().assume_init() | new()method | Valid forBitmap |
Sources: src/lib.rs(L169) src/lib.rs(L198) src/lib.rs(L286) src/lib.rs(L322) src/lib.rs(L81)
ID Allocation and Management
The ID management system uses the bitmaps
crate to efficiently track available slots and implement ID reuse:
ID Allocation Flow
flowchart TD add_request["add(value)"] find_id["id_bitmap.first_false_index()"] add_at_request["add_at(id, value)"] check_bounds["id < CAP"] add_or_replace_request["add_or_replace_at(id, value)"] id_available["id.is_some() && id < CAP"] check_assigned["is_assigned(id)"] allocate_id["Allocate ID"] return_error["Err(value)"] replace_object["Replace existing"] update_bitmap["id_bitmap.set(id, true)"] write_value["objects[id].write(value)"] increment_count["count += 1"] return_success["Ok(id)"] end_flow["End"] add_at_request --> check_bounds add_or_replace_request --> check_bounds add_request --> find_id allocate_id --> update_bitmap check_assigned --> allocate_id check_assigned --> replace_object check_assigned --> return_error check_bounds --> check_assigned find_id --> id_available id_available --> allocate_id id_available --> return_error increment_count --> write_value replace_object --> write_value return_error --> end_flow return_success --> end_flow update_bitmap --> increment_count write_value --> return_success
The first_false_index()
method from the bitmaps
crate provides O(1) amortized ID allocation by efficiently finding the first unset bit.
Sources: src/lib.rs(L222 - L232) src/lib.rs(L249 - L257) src/lib.rs(L277 - L297)
Critical Safety Invariants
The implementation maintains several critical invariants that ensure memory safety:
- Bitmap-Memory Synchronization:
id_bitmap.get(i) == true
if and only ifobjects[i]
is initialized - Count Consistency:
count
equalsid_bitmap.into_iter().count()
- Bounds Safety: All array access is bounds-checked against
CAP
- Initialization Ordering:
id_bitmap.set(id, true)
occurs beforeobjects[id].write(value)
- Deinitialization Ordering:
id_bitmap.set(id, false)
occurs afterobjects[id].assume_init_read()
These invariants are maintained through careful ordering in methods like add()
, remove()
, and add_or_replace_at()
.
Sources: src/lib.rs(L225 - L228) src/lib.rs(L253 - L256) src/lib.rs(L317 - L322)
Capacity Constraints and Compilation
The CAP
parameter is constrained by the bitmaps
crate's Bits
trait implementation, which currently supports capacities up to 1024. This constraint is enforced at compile time through the trait bound BitsImpl<{CAP}>: Bits
.
The new()
method includes a runtime panic for capacities exceeding 1024, providing clear error messaging during development while maintaining const fn
compatibility.
Sources: src/lib.rs(L46) src/lib.rs(L59 - L61) src/lib.rs(L77)
Internal Data Structures
Relevant source files
This document details the internal data structures that comprise the FlattenObjects
container, explaining how the three core fields work together to provide efficient object storage with bitmap-based ID management. This covers the memory layout, field purposes, and structural relationships that enable the container's functionality.
For information about the public API methods that operate on these structures, see Object Management Operations. For details about memory safety and MaybeUninit
usage, see Memory Management and Safety.
Core Data Structure Overview
The FlattenObjects<T, CAP>
struct contains exactly three fields that work in coordination to manage object storage and ID assignment. Each field serves a distinct purpose in maintaining the container's invariants.
flowchart TD subgraph subGraph2["External Dependencies"] MaybeUninit["core::mem::MaybeUninit"] Bitmap["bitmaps::Bitmap"] end subgraph Purpose["Purpose"] storage["Object Storage Array"] tracking["ID Assignment Tracking"] metadata["Count Metadata"] end subgraph subGraph0["FlattenObjects"] objects["objects: [MaybeUninit<T>; CAP]"] id_bitmap["id_bitmap: Bitmap<CAP>"] count["count: usize"] end count --> metadata id_bitmap --> Bitmap id_bitmap --> tracking objects --> MaybeUninit objects --> storage
Core Fields Summary
Field | Type | Purpose | Size |
---|---|---|---|
objects | [MaybeUninit | Stores the actual objects | CAP * size_of:: |
id_bitmap | Bitmap | Tracks which IDs are assigned | Implementation-dependent |
count | usize | Number of currently assigned objects | 8 bytes (on 64-bit) |
Sources: src/lib.rs(L44 - L51)
Object Storage Array
The objects
field is a fixed-size array that provides the actual storage space for objects. Each array slot corresponds directly to a potential ID, creating a one-to-one mapping between array indices and object IDs.
MaybeUninit Wrapper
The array uses MaybeUninit<T>
rather than T
directly, which allows the container to:
- Avoid default initialization of unused slots
- Safely represent uninitialized memory states
- Enable manual control over object lifecycle
flowchart TD subgraph subGraph2["ID Mapping"] id0["ID 0"] id1["ID 1"] id2["ID 2"] idN["ID CAP-1"] end subgraph subGraph1["Possible States"] uninit["Uninitialized(ID not assigned)"] init["Initialized(ID assigned)"] end subgraph subGraph0["objects Array Layout"] slot0["objects[0]MaybeUninit<T>"] slot1["objects[1]MaybeUninit<T>"] slot2["objects[2]MaybeUninit<T>"] dotdot["..."] slotN["objects[CAP-1]MaybeUninit<T>"] end slot0 --> id0 slot0 --> init slot1 --> id1 slot1 --> uninit slot2 --> id2 slot2 --> init slotN --> idN slotN --> uninit
Array Operations
The container performs these operations on the objects
array:
- Write:
self.objects[id].write(value)
- Initialize a slot with a new object - Read:
self.objects[id].assume_init_read()
- Move an object out of a slot - Borrow:
self.objects[id].assume_init_ref()
- Get an immutable reference - Borrow Mut:
self.objects[id].assume_init_mut()
- Get a mutable reference
Sources: src/lib.rs(L48) src/lib.rs(L227) src/lib.rs(L255) src/lib.rs(L287) src/lib.rs(L322)
ID Bitmap Management
The id_bitmap
field uses the external bitmaps
crate to efficiently track which IDs are currently assigned. Each bit position corresponds to an array index in the objects
field.
Bitmap State Representation
Bit Value | ID State | Objects Array State |
---|---|---|
false(0) | ID available | MaybeUninituninitialized |
true(1) | ID assigned | MaybeUninitcontains validT |
Key Bitmap Operations
The container uses these bitmap operations:
- Assignment Check:
self.id_bitmap.get(id)
- Test if an ID is assigned - Mark Assigned:
self.id_bitmap.set(id, true)
- Mark an ID as assigned - Mark Available:
self.id_bitmap.set(id, false)
- Free an ID for reuse - Find Available:
self.id_bitmap.first_false_index()
- Locate next available ID - Iterate Assigned:
self.id_bitmap.into_iter()
- Enumerate all assigned IDs
flowchart TD subgraph subGraph1["Corresponding Actions"] add_op["add() operation"] assign_op["Assignment complete"] access_op["get()/get_mut() access"] remove_op["remove() operation"] ids_op["ids() iterator"] end subgraph subGraph0["Bitmap Operations Flow"] find_available["first_false_index()"] set_true["set(id, true)"] get_check["get(id)"] set_false["set(id, false)"] iterate["into_iter()"] end access_op --> get_check add_op --> set_true find_available --> add_op ids_op --> iterate remove_op --> set_false set_true --> assign_op
Sources: src/lib.rs(L49) src/lib.rs(L145) src/lib.rs(L223) src/lib.rs(L226) src/lib.rs(L317) src/lib.rs(L344 - L346)
Count Field
The count
field maintains an accurate count of currently assigned objects, eliminating the need to iterate through the bitmap for this common query.
Count Maintenance
The count
field is updated during these operations:
Operation | Count Change | Condition |
---|---|---|
add() | +1 | When ID assignment succeeds |
add_at() | +1 | When ID is available |
add_or_replace_at() | +1 | When ID was not previously assigned |
remove() | -1 | When ID was assigned |
Invariant Relationship
The container maintains this invariant at all times:
count == number of true bits in id_bitmap == number of initialized slots in objects
This invariant ensures that count()
method calls execute in constant time rather than requiring bitmap iteration.
Sources: src/lib.rs(L50) src/lib.rs(L122 - L124) src/lib.rs(L225) src/lib.rs(L253) src/lib.rs(L291) src/lib.rs(L318)
Data Structure Coordination
The three fields work together to maintain consistency through coordinated updates. Every operation that modifies the container state updates multiple fields atomically.
sequenceDiagram participant APIMethod as "API Method" participant id_bitmap as "id_bitmap" participant objectsarray as "objects array" participant countfield as "count field" Note over APIMethod: add(value) operation APIMethod ->> id_bitmap: first_false_index() id_bitmap -->> APIMethod: Some(id) APIMethod ->> countfield: count += 1 APIMethod ->> id_bitmap: set(id, true) APIMethod ->> objectsarray: objects[id].write(value) Note over APIMethod: remove(id) operation APIMethod ->> id_bitmap: get(id) id_bitmap -->> APIMethod: true APIMethod ->> id_bitmap: set(id, false) APIMethod ->> countfield: count -= 1 APIMethod ->> objectsarray: objects[id].assume_init_read()
Synchronization Requirements
Since all operations are performed through &mut self
, the Rust borrow checker ensures that no concurrent modifications can occur. This eliminates the need for internal synchronization primitives while maintaining data structure integrity.
Memory Layout Considerations
The struct fields are laid out in declaration order, with potential padding for alignment:
objects: [MaybeUninit<T>; CAP]
- Largest field, aligned toT
's alignmentid_bitmap: Bitmap<CAP>
- Size depends onCAP
and bitmap implementationcount: usize
- 8 bytes on 64-bit platforms
The total memory footprint is approximately CAP * size_of::<T>() + bitmap_size + 8
bytes, making it suitable for resource-constrained environments when CAP
is chosen appropriately.
Sources: src/lib.rs(L44 - L51) src/lib.rs(L222 - L232) src/lib.rs(L315 - L326)
Memory Management and Safety
Relevant source files
This document details the memory management strategies and safety mechanisms employed by the FlattenObjects
container. It covers the use of uninitialized memory, unsafe operations, and the critical safety invariants that ensure memory safety despite storing objects in a fixed-capacity array.
For information about the ID management system that works alongside these memory safety mechanisms, see ID Management System. For details about the public API that maintains these safety guarantees, see Object Management Operations.
Memory Layout and Data Structures
The FlattenObjects
container uses a carefully designed memory layout that balances performance with safety in no_std
environments.
Core Memory Components
flowchart TD subgraph subGraph2["Safety Invariants"] invariant1["If bitmap[i] == true, then objects[i] is initialized"] invariant2["If bitmap[i] == false, then objects[i] is uninitialized"] invariant3["count == number of true bits in bitmap"] end subgraph subGraph1["Memory States"] uninit["MaybeUninit::uninit()"] init["MaybeUninit with valid T"] bitmap_false["Bitmap bit = false"] bitmap_true["Bitmap bit = true"] end subgraph subGraph0["FlattenObjects"] objects["objects: [MaybeUninit; CAP]"] id_bitmap["id_bitmap: Bitmap"] count["count: usize"] end bitmap_false --> invariant2 bitmap_true --> invariant1 count --> invariant3 id_bitmap --> bitmap_false id_bitmap --> bitmap_true objects --> init objects --> uninit
Sources: src/lib.rs(L44 - L51)
The container maintains three core fields that work together to provide safe object storage:
Field | Type | Purpose |
---|---|---|
objects | [MaybeUninit | Stores potentially uninitialized objects |
id_bitmap | Bitmap | Tracks which array slots contain valid objects |
count | usize | Number of currently stored objects |
MaybeUninit Usage Pattern
The container uses MaybeUninit<T>
to defer object initialization until actually needed, avoiding the overhead of default initialization for the entire array.
Sources: src/lib.rs(L79) src/lib.rs(L227) src/lib.rs(L255) src/lib.rs(L286 - L287) src/lib.rs(L322)
Safety Mechanisms and Unsafe Operations
The FlattenObjects
implementation contains several unsafe operations that are carefully controlled by safety invariants.
Unsafe Operation Sites
flowchart TD subgraph subGraph0["Constructor Safety"] remove_fn["remove()"] assume_init_read["assume_init_read()"] replace_fn["add_or_replace_at()"] get_fn["get()"] assume_init_ref["assume_init_ref()"] get_mut_fn["get_mut()"] assume_init_mut["assume_init_mut()"] new_fn["new()"] bitmap_init["MaybeUninit::zeroed().assume_init()"] array_init["[MaybeUninit::uninit(); CAP]"] subgraph subGraph1["Access Operations"] subgraph subGraph2["Removal Operations"] remove_fn["remove()"] assume_init_read["assume_init_read()"] replace_fn["add_or_replace_at()"] get_fn["get()"] assume_init_ref["assume_init_ref()"] get_mut_fn["get_mut()"] assume_init_mut["assume_init_mut()"] new_fn["new()"] bitmap_init["MaybeUninit::zeroed().assume_init()"] end end end get_fn --> assume_init_ref get_mut_fn --> assume_init_mut new_fn --> array_init new_fn --> bitmap_init remove_fn --> assume_init_read replace_fn --> assume_init_read
Sources: src/lib.rs(L77 - L84) src/lib.rs(L165 - L173) src/lib.rs(L194 - L202) src/lib.rs(L315 - L326) src/lib.rs(L277 - L297)
Critical Safety Contracts
Each unsafe operation in the codebase follows specific safety contracts:
Operation | Location | Safety Contract |
---|---|---|
MaybeUninit::zeroed().assume_init() | src/lib.rs81 | Safe for bitmap (array of integers) |
assume_init_ref() | src/lib.rs169 | Called only whenis_assigned(id)returns true |
assume_init_mut() | src/lib.rs198 | Called only whenis_assigned(id)returns true |
assume_init_read() | src/lib.rs286src/lib.rs322 | Called only whenis_assigned(id)returns true |
Safety Invariant Enforcement
The container maintains critical safety invariants through careful coordination between the bitmap and object array states.
Invariant Maintenance Operations
sequenceDiagram participant Client as Client participant FlattenObjects as FlattenObjects participant Bitmap as Bitmap participant ObjectArray as ObjectArray Note over FlattenObjects: Add Operation Client ->> FlattenObjects: add(value) FlattenObjects ->> Bitmap: first_false_index() Bitmap -->> FlattenObjects: Some(id) FlattenObjects ->> FlattenObjects: count += 1 FlattenObjects ->> Bitmap: set(id, true) FlattenObjects ->> ObjectArray: objects[id].write(value) FlattenObjects -->> Client: Ok(id) Note over FlattenObjects: Access Operation Client ->> FlattenObjects: get(id) FlattenObjects ->> Bitmap: get(id) Bitmap -->> FlattenObjects: true FlattenObjects ->> ObjectArray: assume_init_ref() ObjectArray -->> FlattenObjects: &T FlattenObjects -->> Client: Some(&T) Note over FlattenObjects: Remove Operation Client ->> FlattenObjects: remove(id) FlattenObjects ->> Bitmap: get(id) Bitmap -->> FlattenObjects: true FlattenObjects ->> Bitmap: set(id, false) FlattenObjects ->> FlattenObjects: count -= 1 FlattenObjects ->> ObjectArray: assume_init_read() ObjectArray -->> FlattenObjects: T FlattenObjects -->> Client: Some(T)
Sources: src/lib.rs(L222 - L232) src/lib.rs(L165 - L173) src/lib.rs(L315 - L326)
Bitmap Synchronization Strategy
The id_bitmap
field serves as the authoritative source of truth for object initialization state. All access to MaybeUninit
storage goes through bitmap checks:
flowchart TD subgraph subGraph1["Safe Failure Pattern"] check_false["is_assigned(id) == false"] none_return["Return None"] end subgraph subGraph0["Safe Access Pattern"] check["is_assigned(id)"] bitmap_get["id_bitmap.get(id)"] range_check["id < CAP"] unsafe_access["assume_init_*()"] safe_return["Return Some(T)"] end bitmap_get --> unsafe_access check --> bitmap_get check --> check_false check --> range_check check_false --> none_return range_check --> unsafe_access unsafe_access --> safe_return
Sources: src/lib.rs(L144 - L146) src/lib.rs(L166) src/lib.rs(L195)
Memory Efficiency Considerations
The design prioritizes memory efficiency through several strategies suitable for resource-constrained environments.
Zero-Initialization Avoidance
flowchart TD subgraph Benefits["Benefits"] no_default["No Default trait requirement"] fast_creation["O(1) container creation"] mem_efficient["Memory efficient for sparse usage"] end subgraph subGraph1["FlattenObjects Approach"] lazy_init["Objects initialized only when added"] compact_storage["Minimal stack footprint"] subgraph subGraph0["Traditional Array[T; CAP]"] trad_init["Default::default() called CAP times"] trad_memory["Full T initialization overhead"] trad_stack["Large stack usage"] maybe_init["MaybeUninit::uninit() - zero cost"] end end compact_storage --> mem_efficient lazy_init --> fast_creation maybe_init --> no_default
Sources: src/lib.rs(L79) src/lib.rs(L227)
Const Construction Support
The container supports const
construction for compile-time initialization in embedded contexts:
flowchart TD subgraph subGraph1["Runtime Benefits"] no_runtime_init["No runtime initialization cost"] static_storage["Can be stored in static memory"] embedded_friendly["Suitable for embedded systems"] end subgraph subGraph0["Const Construction"] const_new["const fn new()"] const_array["const MaybeUninit array"] const_bitmap["zeroed bitmap"] const_count["count = 0"] end const_array --> static_storage const_bitmap --> embedded_friendly const_new --> no_runtime_init
Sources: src/lib.rs(L77 - L84)
The memory management strategy ensures that FlattenObjects
maintains safety guarantees while providing efficient object storage suitable for kernel-level and embedded programming contexts where traditional heap allocation is not available.
ID Management System
Relevant source files
This document covers the bitmap-based ID allocation and management system used by FlattenObjects
to assign unique identifiers to stored objects. The system provides efficient ID allocation, reuse, and tracking using a bitmap data structure from the external bitmaps
crate.
For information about the overall container architecture, see Internal Data Structures. For details about memory safety and object lifecycle, see Memory Management and Safety.
Core ID Management Architecture
The ID management system centers around a bitmap that tracks which IDs are currently assigned. Each bit position corresponds to a potential object ID, with true
indicating an assigned ID and false
indicating an available ID.
flowchart TD subgraph subGraph2["ID Operations"] add_op["add()"] add_at_op["add_at(id, value)"] remove_op["remove(id)"] is_assigned_op["is_assigned(id)"] end subgraph subGraph1["bitmaps Crate"] Bitmap["Bitmap"] BitsImpl["BitsImpl: Bits"] first_false_index["first_false_index()"] get_method["get(id: usize)"] set_method["set(id: usize, value: bool)"] into_iter["into_iter() -> Iter"] end subgraph subGraph0["FlattenObjects"] id_bitmap["id_bitmap: Bitmap"] objects["objects: [MaybeUninit; CAP]"] count["count: usize"] end Bitmap --> BitsImpl add_at_op --> get_method add_op --> first_false_index first_false_index --> objects id_bitmap --> Bitmap is_assigned_op --> get_method objects --> count remove_op --> set_method set_method --> count set_method --> objects
Bitmap-based ID Management
Sources: src/lib.rs(L34) src/lib.rs(L49) Cargo.toml(L16)
ID Allocation Algorithm
The system uses a "first-fit" allocation strategy where new objects receive the smallest available ID. This is implemented through the first_false_index()
method from the bitmaps
crate.
flowchart TD add_call["add(value: T)"] find_id["id_bitmap.first_false_index()"] check_cap["id < CAP?"] check_available["ID available?"] assign_id["id_bitmap.set(id, true)"] write_object["objects[id].write(value)"] increment_count["count += 1"] return_ok["return Ok(id)"] return_err["return Err(value)"] add_call --> find_id assign_id --> write_object check_available --> assign_id check_available --> return_err check_cap --> check_available check_cap --> return_err find_id --> check_cap increment_count --> return_ok write_object --> increment_count
ID Allocation Flow
Sources: src/lib.rs(L222 - L232) src/lib.rs(L223) src/lib.rs(L226)
ID State Transitions
Each ID in the system can be in one of two states, tracked by the corresponding bitmap bit. The state transitions occur during object lifecycle operations.
Operation | Bitmap Bit Change | Count Change | Object Array Effect |
---|---|---|---|
add() | false→true | +1 | MaybeUninit::uninit()→MaybeUninit::new(value) |
add_at(id) | false→true | +1 | MaybeUninit::uninit()→MaybeUninit::new(value) |
remove(id) | true→false | -1 | MaybeUninit::new(value)→MaybeUninit::uninit() |
add_or_replace_at(id) | unchanged | 0or+1 | Replaces existing value |
ID State Transition Table
Sources: src/lib.rs(L226) src/lib.rs(L254) src/lib.rs(L317) src/lib.rs(L292)
Bitmap Integration Details
The system integrates with the bitmaps
crate v3.2, which provides efficient bitmap operations with compile-time size verification through the Bits
trait constraint.
flowchart TD subgraph subGraph2["FlattenObjects Methods"] CAP["const CAP: usize"] BitsImpl_CAP["BitsImpl<{CAP}>"] Bits_trait["Bits trait"] add_method["add()"] is_assigned_method["is_assigned()"] remove_method["remove()"] ids_method["ids()"] end subgraph subGraph1["Bitmap Operations"] first_false["first_false_index() -> Option"] get_bit["get(index: usize) -> bool"] set_bit["set(index: usize, value: bool)"] iter_bits["into_iter() -> Iter"] end subgraph subGraph0["Type Constraints"] CAP["const CAP: usize"] BitsImpl_CAP["BitsImpl<{CAP}>"] Bits_trait["Bits trait"] add_method["add()"] is_assigned_method["is_assigned()"] remove_method["remove()"] end BitsImpl_CAP --> Bits_trait CAP --> BitsImpl_CAP add_method --> first_false ids_method --> iter_bits is_assigned_method --> get_bit remove_method --> set_bit
Bitmap Integration Architecture
Sources: src/lib.rs(L46) src/lib.rs(L34) src/lib.rs(L144 - L146) src/lib.rs(L344 - L346)
ID Reuse Strategy
The system implements immediate ID reuse - when an object is removed, its ID becomes immediately available for the next allocation. This is achieved by setting the corresponding bitmap bit to false
.
sequenceDiagram participant Client as Client participant FlattenObjects as FlattenObjects participant id_bitmap as id_bitmap participant objects_array as objects_array Client ->> FlattenObjects: add(value1) FlattenObjects ->> id_bitmap: first_false_index() id_bitmap -->> FlattenObjects: Some(0) FlattenObjects ->> id_bitmap: set(0, true) FlattenObjects ->> objects_array: objects[0].write(value1) FlattenObjects -->> Client: Ok(0) Client ->> FlattenObjects: add(value2) FlattenObjects ->> id_bitmap: first_false_index() id_bitmap -->> FlattenObjects: Some(1) FlattenObjects ->> id_bitmap: set(1, true) FlattenObjects ->> objects_array: objects[1].write(value2) FlattenObjects -->> Client: Ok(1) Client ->> FlattenObjects: remove(0) FlattenObjects ->> id_bitmap: set(0, false) FlattenObjects ->> objects_array: objects[0].assume_init_read() FlattenObjects -->> Client: Some(value1) Client ->> FlattenObjects: add(value3) FlattenObjects ->> id_bitmap: first_false_index() id_bitmap -->> FlattenObjects: Some(0) Note over FlattenObjects: ID 0 is reused FlattenObjects ->> id_bitmap: set(0, true) FlattenObjects ->> objects_array: objects[0].write(value3) FlattenObjects -->> Client: Ok(0)
ID Reuse Sequence
Sources: src/lib.rs(L315 - L326) src/lib.rs(L317) src/lib.rs(L322)
Capacity Constraints and Limitations
The ID management system has several built-in constraints that ensure safe operation within the bounds of the bitmaps
crate implementation.
Maximum Capacity Constraint
The system enforces a maximum capacity of 1024 objects, which is a limitation of the underlying bitmaps
crate implementation.
flowchart TD CAP_check["CAP <= 1024?"] compile_success["Compilation succeeds"] compile_fail["Compilation fails"] BitsImpl_constraint["BitsImpl<{CAP}>: Bits"] BitsImpl_constraint --> compile_success CAP_check --> BitsImpl_constraint CAP_check --> compile_fail
Capacity Constraint Validation
Sources: src/lib.rs(L42 - L43) src/lib.rs(L61) src/lib.rs(L75 - L76)
ID Range Validation
All ID operations include bounds checking to ensure IDs remain within the valid range <FileRef file-url="https://github.com/arceos-org/flatten_objects/blob/ac0a74b9/0, CAP)
.\n\n| Method | ID Validation | Behavior on Invalid ID |\n|--------|---------------|----------------------|\n| is_assigned(id)
| id < CAP
| Returns false
|\n| add_at(id, value)
| id >= CAP
| Returns Err(value)
|\n| add_or_replace_at(id, value)
| id >= CAP
| Returns Err(None)
|\n| get(id)
/ get_mut(id)
| Via is_assigned(id)
| Returns None
|\n\nID Validation Table\n\nSources#LNaN-LNaN" NaN file-path="0, CAP).\n\n| Method | ID Validation | Behavior on Invalid ID |\n|--------|---------------|----------------------|\n|
is_assigned(id)|
id < CAP| Returns
false|\n|
add_at(id, value)|
id >= CAP| Returns
Err(value)|\n|
add_or_replace_at(id, value)|
id >= CAP| Returns
Err(None)|\n|
get(id)/
get_mut(id)| Via
is_assigned(id)| Returns
None` |\n\nID Validation Table\n\nSources">Hii src/lib.rs(L250) src/lib.rs(L278)
Bitmap Initialization and Safety
The bitmap is initialized with zero values, ensuring all IDs start as available. This initialization uses unsafe
code but is considered safe because zero-initialization is valid for integer arrays.
// From new() method - line 81
id_bitmap: unsafe { MaybeUninit::zeroed().assume_init() }
The zero-initialization strategy ensures:
- All bitmap bits start as
false
(available) - No undefined behavior from uninitialized memory
- Deterministic initial state for ID allocation
Sources: src/lib.rs(L80 - L82)
Iterator Support
The system provides iteration over assigned IDs through the ids()
method, which returns an iterator from the underlying bitmap.
flowchart TD ids_method["ids() -> Iter"] bitmap_iter["id_bitmap.into_iter()"] assigned_ids["Iterator over assigned IDs"] note1["Only returns IDs where bitmap bit = true"] assigned_ids --> note1 bitmap_iter --> assigned_ids ids_method --> bitmap_iter
ID Iterator Implementation
Sources: src/lib.rs(L344 - L346)
Usage Guide and Examples
Relevant source files
This document provides practical guidance for using the FlattenObjects
container in real-world applications. It covers common usage patterns, best practices, and integration strategies for kernel-level and embedded systems development.
For detailed API documentation, see FlattenObjects API Documentation. For implementation details and internal architecture, see Implementation Details.
Container Creation and Basic Operations
The FlattenObjects
container requires compile-time specification of both the object type and maximum capacity. This design ensures memory efficiency and predictable behavior in resource-constrained environments.
Basic Container Workflow
flowchart TD subgraph RemoveOps["Remove Operations"] Remove["remove(id)"] RemoveResult["Returns Option"] MaybeEmpty["Container may become empty"] end subgraph AddOps["Object Addition Operations"] Add["add(object)"] FindID["Find next available ID"] AddAt["add_at(id, object)"] CheckID["Check if ID available"] AddReplace["add_or_replace_at(id, object)"] ForceID["Use specified ID"] Success1["Returns assigned ID"] Success2["Returns () on success"] Success3["Returns previous object if any"] end subgraph QueryOps["Query Operations"] Get["get(id)"] GetResult["Returns Option<&T>"] GetMut["get_mut(id)"] GetMutResult["Returns Option<&mut T>"] IsAssigned["is_assigned(id)"] BoolResult["Returns bool"] Create["FlattenObjects::new()"] Empty["Empty Containercount = 0"] end Active["Active Containercount > 0"] Add --> FindID AddAt --> CheckID AddReplace --> ForceID CheckID --> Success2 Create --> Empty FindID --> Success1 ForceID --> Success3 Get --> GetResult GetMut --> GetMutResult IsAssigned --> BoolResult MaybeEmpty --> Empty Remove --> RemoveResult RemoveResult --> MaybeEmpty Success1 --> Active Success2 --> Active Success3 --> Active
Basic Usage Example from README
The fundamental usage pattern demonstrates object insertion, retrieval, and removal:
This example shows:
- Container creation with
FlattenObjects::<u32, 20>::new()
- Sequential ID assignment using
add_at()
- Object removal with
remove()
- Automatic ID reuse when using
add()
- State checking with
is_assigned()
Sources: README.md(L14 - L36)
Object Lifecycle Management
Sources: README.md(L19 - L35)
ID Management Patterns
The container provides flexible ID assignment strategies for different use cases:
Automatic ID Assignment
// From README example - automatic ID assignment
let id = objects.add(42).unwrap();
// ID 6 was reused from previously removed object
assert_eq!(id, 6);
This pattern uses add()
to let the container find the next available ID, prioritizing ID reuse for memory efficiency.
Explicit ID Assignment
// From README example - explicit ID assignment
for i in 0..=9 {
objects.add_at(i, 23).unwrap();
}
This pattern uses add_at()
when specific ID values are required, such as for handle tables or slot-based allocation systems.
ID Assignment Strategy Comparison
Method | Use Case | ID Selection | Error on Conflict |
---|---|---|---|
add() | General object pooling | Automatic (reuses lowest) | No (finds available) |
add_at() | Handle tables, specific slots | Explicit | Yes (fails if occupied) |
add_or_replace_at() | Overwrite semantics | Explicit | No (replaces existing) |
Sources: README.md(L19 - L35)
Error Handling Strategies
The container uses Result
types for operations that can fail, enabling robust error handling in system code:
Common Error Scenarios
flowchart TD subgraph subGraph2["Return Types"] ResultID["Result"] ResultUnit["Result<(), ()>"] OptionRef["Option<&T>"] OptionOwned["Option"] end subgraph subGraph1["Error Conditions"] CapFull["Container at capacity(count == CAP)"] IDTaken["ID already assigned"] IDInvalid["ID not assigned"] end subgraph subGraph0["Operation Types"] AddOp["add() operation"] AddAtOp["add_at() operation"] GetOp["get() / get_mut() operation"] RemoveOp["remove() operation"] end AddAtOp --> CapFull AddAtOp --> IDTaken AddAtOp --> ResultUnit AddOp --> CapFull AddOp --> ResultID CapFull --> ResultID CapFull --> ResultUnit GetOp --> IDInvalid GetOp --> OptionRef IDInvalid --> OptionOwned IDInvalid --> OptionRef IDTaken --> ResultUnit RemoveOp --> IDInvalid RemoveOp --> OptionOwned
Error Handling Pattern Example
// Based on README pattern - defensive programming
match objects.add(value) {
Ok(id) => {
// Successfully added, use the returned ID
println!("Object assigned ID: {}", id);
}
Err(()) => {
// Container is full, handle gracefully
eprintln!("Container capacity exceeded");
}
}
Sources: README.md(L30 - L31)
Integration with System Components
The FlattenObjects
container is designed for integration into kernel and embedded system components:
Typical Integration Patterns
flowchart TD subgraph subGraph3["Common Operations"] Alloc["Handle Allocationadd() / add_at()"] Lookup["Handle Resolutionget() / get_mut()"] Release["Resource Cleanupremove()"] end subgraph subGraph2["Object Types"] Proc["ProcessDescriptor"] File["FileHandle"] Conn["TcpConnection"] Dev["DeviceHandle"] end subgraph subGraph1["FlattenObjects Usage"] ProcTable["Process Handle TableFlattenObjects"] FileTable["File Descriptor TableFlattenObjects"] ConnTable["Connection PoolFlattenObjects"] DevTable["Device RegistryFlattenObjects"] end subgraph subGraph0["System Layer"] Kernel["Kernel Subsystem"] Driver["Device Driver"] FS["File System"] Net["Network Stack"] end ConnTable --> Conn ConnTable --> Release DevTable --> Alloc DevTable --> Dev Driver --> DevTable FS --> FileTable FileTable --> File FileTable --> Lookup Kernel --> ProcTable Net --> ConnTable ProcTable --> Alloc ProcTable --> Proc
Resource Management Pattern
#![allow(unused)] fn main() { // Typical kernel subsystem integration pattern struct ProcessManager { processes: FlattenObjects<ProcessDescriptor, MAX_PROCESSES>, } impl ProcessManager { pub fn spawn_process(&mut self, descriptor: ProcessDescriptor) -> Result<ProcessId, SpawnError> { match self.processes.add(descriptor) { Ok(pid) => Ok(ProcessId(pid)), Err(()) => Err(SpawnError::TooManyProcesses), } } pub fn get_process(&self, pid: ProcessId) -> Option<&ProcessDescriptor> { self.processes.get(pid.0) } pub fn terminate_process(&mut self, pid: ProcessId) -> Option<ProcessDescriptor> { self.processes.remove(pid.0) } } }
This pattern demonstrates:
- Wrapping
FlattenObjects
in higher-level abstractions - Converting container IDs to domain-specific handle types
- Providing semantic error types for integration
Sources: README.md(L7 - L11)
Container State Inspection
The container provides methods for monitoring and debugging:
State Inspection Methods
Method | Return Type | Purpose |
---|---|---|
capacity() | usize | Maximum number of objects |
count() | usize | Current number of objects |
is_assigned(id) | bool | Check if specific ID is in use |
ids() | Iterator | Iterate over assigned IDs |
Monitoring Pattern
// Based on available methods from API
fn print_container_status<T, const CAP: usize>(container: &FlattenObjects<T, CAP>) {
println!("Capacity: {}/{}", container.count(), container.capacity());
println!("Assigned IDs: {:?}", container.ids().collect::<Vec<_>>());
// Check specific ID ranges
for id in 0..container.capacity() {
if container.is_assigned(id) {
println!("ID {} is assigned", id);
}
}
}
This monitoring approach is essential for:
- Resource usage tracking in kernel subsystems
- Debugging handle leaks
- Capacity planning for system limits
Sources: README.md(L22 - L32)
Basic Operations
Relevant source files
This document provides step-by-step examples of the fundamental operations available in the FlattenObjects
container. It covers container creation, object addition, retrieval, removal, and state querying operations. These examples demonstrate the core functionality needed for most use cases with practical code patterns.
For detailed API documentation, see FlattenObjects API Documentation. For advanced usage patterns and integration strategies, see Advanced Patterns and Best Practices.
Container Creation
The first step in using FlattenObjects
is creating a new container instance. The container requires two generic parameters: the object type T
and the maximum capacity CAP
.
use flatten_objects::FlattenObjects;
// Create a container that can hold up to 20 u32 values
let mut objects = FlattenObjects::<u32, 20>::new();
The new()
method creates an empty container with zero-initialized internal state. The capacity is fixed at compile time and cannot exceed 1024 objects.
Container Creation Workflow
flowchart TD Start["Start"] CreateContainer["FlattenObjects::new()"] InitObjects["Initialize objects: [MaybeUninit; CAP]"] InitBitmap["Initialize id_bitmap: Bitmap"] InitCount["Initialize count: 0"] Ready["Container Ready"] CheckCapacity["capacity() returns CAP"] CheckCount["count() returns 0"] CheckEmpty["All IDs unassigned"] CreateContainer --> InitObjects InitBitmap --> InitCount InitCount --> Ready InitObjects --> InitBitmap Ready --> CheckCapacity Ready --> CheckCount Ready --> CheckEmpty Start --> CreateContainer
Sources: src/lib.rs(L77 - L84) src/lib.rs(L44 - L51)
Adding Objects
The FlattenObjects
container provides three methods for adding objects, each with different ID assignment strategies.
Automatic ID Assignment withadd()
The add()
method automatically assigns the smallest available ID to the object:
let mut objects = FlattenObjects::<u32, 20>::new();
// Add objects with automatic ID assignment
let id1 = objects.add(23).unwrap(); // Returns 0
let id2 = objects.add(42).unwrap(); // Returns 1
let id3 = objects.add(100).unwrap(); // Returns 2
assert_eq!(id1, 0);
assert_eq!(id2, 1);
assert_eq!(id3, 2);
When the container reaches capacity, add()
returns Err(value)
containing the object that couldn't be added.
Specific ID Assignment withadd_at()
The add_at()
method allows placing an object at a specific ID:
let mut objects = FlattenObjects::<u32, 20>::new();
// Add objects at specific IDs
objects.add_at(5, 23).unwrap(); // Assign ID 5
objects.add_at(10, 42).unwrap(); // Assign ID 10
objects.add_at(0, 100).unwrap(); // Assign ID 0
// Attempting to use an already assigned ID fails
assert_eq!(objects.add_at(5, 999), Err(999));
Replacement withadd_or_replace_at()
The add_or_replace_at()
method adds an object at a specific ID, optionally replacing any existing object:
let mut objects = FlattenObjects::<u32, 20>::new();
// Initial placement
objects.add_or_replace_at(5, 23).unwrap(); // Returns Ok(5)
// Replacement - returns the old value
let result = objects.add_or_replace_at(5, 42);
assert_eq!(result, Err(Some(23))); // Old value returned
assert_eq!(objects.get(5), Some(&42)); // New value in place
Object Addition State Transitions
Sources: src/lib.rs(L222 - L232) src/lib.rs(L249 - L257) src/lib.rs(L277 - L297)
Retrieving Objects
Objects can be accessed using their assigned IDs through immutable and mutable reference operations.
Immutable Access withget()
The get()
method returns an immutable reference to the object at the specified ID:
let mut objects = FlattenObjects::<u32, 20>::new();
objects.add_at(5, 42).unwrap();
// Get immutable reference
if let Some(value) = objects.get(5) {
assert_eq!(*value, 42);
}
// Non-existent ID returns None
assert_eq!(objects.get(10), None);
Mutable Access withget_mut()
The get_mut()
method returns a mutable reference for in-place modification:
let mut objects = FlattenObjects::<u32, 20>::new();
objects.add_at(5, 42).unwrap();
// Modify object in place
if let Some(value) = objects.get_mut(5) {
*value = 100;
}
assert_eq!(objects.get(5), Some(&100));
Checking Assignment Status withis_assigned()
The is_assigned()
method checks whether a specific ID has an object assigned:
let mut objects = FlattenObjects::<u32, 20>::new();
objects.add_at(5, 42).unwrap();
assert!(objects.is_assigned(5)); // true - ID 5 has an object
assert!(!objects.is_assigned(10)); // false - ID 10 is unassigned
assert!(!objects.is_assigned(25)); // false - ID 25 is out of range
Object Retrieval Operations
flowchart TD GetRequest["get(id) / get_mut(id) / is_assigned(id)"] CheckRange["id < CAP?"] ReturnNone["Return None/false"] CheckBitmap["id_bitmap.get(id)?"] ValidID["ID is assigned"] GetOp["get(id)"] GetMutOp["get_mut(id)"] IsAssignedOp["is_assigned(id)"] UnsafeRef["unsafe { objects[id].assume_init_ref() }"] UnsafeMut["unsafe { objects[id].assume_init_mut() }"] ReturnTrue["Return true"] ReturnSomeRef["Return Some(&T)"] ReturnSomeMut["Return Some(&mut T)"] CheckBitmap --> ReturnNone CheckBitmap --> ValidID CheckRange --> CheckBitmap CheckRange --> ReturnNone GetMutOp --> UnsafeMut GetOp --> UnsafeRef GetRequest --> CheckRange IsAssignedOp --> ReturnTrue UnsafeMut --> ReturnSomeMut UnsafeRef --> ReturnSomeRef ValidID --> GetMutOp ValidID --> GetOp ValidID --> IsAssignedOp
Sources: src/lib.rs(L165 - L173) src/lib.rs(L194 - L202) src/lib.rs(L144 - L146)
Removing Objects
The remove()
method extracts an object from the container and frees its ID for reuse:
let mut objects = FlattenObjects::<u32, 20>::new();
objects.add_at(5, 42).unwrap();
// Remove and return the object
let removed = objects.remove(5);
assert_eq!(removed, Some(42));
// ID is now available for reuse
assert!(!objects.is_assigned(5));
// Removing again returns None
assert_eq!(objects.remove(5), None);
When an object is removed, the ID becomes available for future add()
operations, following a lowest-ID-first reuse strategy.
ID Reuse After Removal
let mut objects = FlattenObjects::<u32, 20>::new();
// Fill some IDs
objects.add_at(0, 10).unwrap();
objects.add_at(1, 20).unwrap();
objects.add_at(2, 30).unwrap();
// Remove ID 1
objects.remove(1);
// Next add() will reuse ID 1 (lowest available)
let reused_id = objects.add(999).unwrap();
assert_eq!(reused_id, 1);
Sources: src/lib.rs(L315 - L326)
Querying Container State
The container provides several methods to inspect its current state and metadata.
Capacity and Count
let mut objects = FlattenObjects::<u32, 20>::new();
assert_eq!(objects.capacity(), 20); // Maximum capacity
assert_eq!(objects.count(), 0); // Current object count
objects.add(42).unwrap();
assert_eq!(objects.count(), 1); // Count increases
objects.remove(0);
assert_eq!(objects.count(), 0); // Count decreases
Iterating Assigned IDs
The ids()
method returns an iterator over all currently assigned IDs:
let mut objects = FlattenObjects::<u32, 20>::new();
objects.add_at(0, 10).unwrap();
objects.add_at(5, 50).unwrap();
objects.add_at(2, 20).unwrap();
let assigned_ids: Vec<usize> = objects.ids().collect();
assert_eq!(assigned_ids, vec![0, 2, 5]); // Sorted order
Sources: src/lib.rs(L99 - L101) src/lib.rs(L122 - L124) src/lib.rs(L344 - L346)
Complete Workflow Example
The following example demonstrates a complete workflow combining all basic operations:
use flatten_objects::FlattenObjects;
// 1. Create container
let mut objects = FlattenObjects::<String, 10>::new();
// 2. Add objects with different methods
let auto_id = objects.add("First".to_string()).unwrap(); // ID 0
objects.add_at(5, "At Five".to_string()).unwrap(); // ID 5
objects.add_or_replace_at(2, "At Two".to_string()).unwrap(); // ID 2
// 3. Query state
assert_eq!(objects.count(), 3);
assert!(objects.is_assigned(0));
assert!(objects.is_assigned(2));
assert!(objects.is_assigned(5));
// 4. Access objects
if let Some(first) = objects.get(0) {
println!("Object at ID 0: {}", first);
}
// 5. Modify object
if let Some(second) = objects.get_mut(2) {
second.push_str(" (Modified)");
}
// 6. Remove object and reuse ID
objects.remove(0);
let reused_id = objects.add("Reused".to_string()).unwrap();
assert_eq!(reused_id, 0); // Reuses the freed ID
// 7. Iterate over assigned IDs
for id in objects.ids() {
println!("ID {}: {:?}", id, objects.get(id));
}
Complete Operation Flow
sequenceDiagram participant User as User participant FlattenObjects as "FlattenObjects" participant id_bitmap as "id_bitmap" participant objectsCAP as "objects[CAP]" User ->> FlattenObjects: new() FlattenObjects ->> id_bitmap: Initialize (all false) FlattenObjects ->> objectsCAP: Initialize (all uninit) User ->> FlattenObjects: add(value) FlattenObjects ->> id_bitmap: first_false_index() id_bitmap -->> FlattenObjects: Some(id) FlattenObjects ->> id_bitmap: set(id, true) FlattenObjects ->> objectsCAP: objects[id].write(value) FlattenObjects -->> User: Ok(id) User ->> FlattenObjects: get(id) FlattenObjects ->> id_bitmap: get(id) id_bitmap -->> FlattenObjects: true FlattenObjects ->> objectsCAP: objects[id].assume_init_ref() FlattenObjects -->> User: Some(&T) User ->> FlattenObjects: remove(id) FlattenObjects ->> id_bitmap: set(id, false) FlattenObjects ->> objectsCAP: objects[id].assume_init_read() FlattenObjects -->> User: Some(T)
Sources: src/lib.rs(L6 - L30) README.md(L12 - L36)
Advanced Patterns and Best Practices
Relevant source files
This document covers advanced usage patterns, error handling strategies, and integration techniques for the FlattenObjects
container in resource-constrained and kernel environments. It focuses on production-ready patterns beyond basic operations.
For basic container operations and initialization, see Basic Operations. For implementation details of the underlying data structures, see Implementation Details.
Error Handling Strategies
The FlattenObjects
API provides different error handling mechanisms depending on the operation semantics. Understanding these patterns is crucial for robust system integration.
Capacity-Aware Addition Patterns
The container provides three distinct addition methods with different error semantics:
flowchart TD AddRequest["Object Addition Request"] AutoID["add(value)"] SpecificID["add_at(id, value)"] ReplaceID["add_or_replace_at(id, value)"] AutoSuccess["Ok(assigned_id)"] AutoFail["Err(value)"] SpecificSuccess["Ok(id)"] SpecificFail["Err(value)"] ReplaceSuccess["Ok(id)"] ReplaceOccupied["Err(Some(old_value))"] ReplaceOutOfRange["Err(None)"] RecoveryStrategy["Recovery Strategy"] IDConflictHandling["ID Conflict Handling"] OldValueProcessing["Process Replaced Value"] BoundsErrorHandling["Bounds Error Handling"] AddRequest --> AutoID AddRequest --> ReplaceID AddRequest --> SpecificID AutoFail --> RecoveryStrategy AutoID --> AutoFail AutoID --> AutoSuccess ReplaceID --> ReplaceOccupied ReplaceID --> ReplaceOutOfRange ReplaceID --> ReplaceSuccess ReplaceOccupied --> OldValueProcessing ReplaceOutOfRange --> BoundsErrorHandling SpecificFail --> IDConflictHandling SpecificID --> SpecificFail SpecificID --> SpecificSuccess
Pattern 1: Graceful Degradation with Automatic ID Assignment
// Pattern for systems that can fallback to alternative storage
match container.add(expensive_object) {
Ok(id) => {
// Store ID for later retrieval
register_object_id(id);
}
Err(object) => {
// Container full - fallback to alternative storage
fallback_storage.store(object);
}
}
Pattern 2: Pre-validation for Specific ID Assignment
// Pattern for systems with predetermined ID requirements
if container.is_assigned(required_id) {
return Err(IdAlreadyAssigned(required_id));
}
match container.add_at(required_id, object) {
Ok(id) => process_success(id),
Err(object) => {
// This should not happen if pre-validation succeeded
panic!("Unexpected ID assignment failure");
}
}
Sources: src/lib.rs(L222 - L232) src/lib.rs(L249 - L257) src/lib.rs(L277 - L297)
Resource Exhaustion Handling
The 1024 capacity limit requires careful resource management:
flowchart TD subgraph subGraph2["Recovery Mechanisms"] LRUEviction["LRU Eviction"] CompactionGC["Compaction GC"] EmergencyCleanup["Emergency Cleanup"] end subgraph subGraph1["Allocation Strategy"] PreCheck["Pre-allocation Check"] ReservationSystem["ID Reservation"] PriorityQueuing["Priority-based Queuing"] end subgraph subGraph0["Capacity Management"] CurrentCount["count()"] MaxCapacity["capacity()"] AvailableSlots["capacity() - count()"] end AvailableSlots --> LRUEviction CompactionGC --> EmergencyCleanup CurrentCount --> PreCheck LRUEviction --> CompactionGC MaxCapacity --> PreCheck PreCheck --> ReservationSystem ReservationSystem --> PriorityQueuing
Sources: src/lib.rs(L99 - L101) src/lib.rs(L122 - L124) src/lib.rs(L77 - L84)
Memory Management Patterns
Zero-Copy Object Lifecycle
The MaybeUninit<T>
backing storage enables zero-copy patterns critical for kernel environments:
flowchart TD subgraph subGraph2["Safety Invariants"] SafetyCheck["is_assigned(id) check"] UnsafeAccess["Unsafe memory access"] end subgraph subGraph1["Bitmap States"] BitmapFalse["id_bitmap.get(id) == false"] BitmapTrue["id_bitmap.get(id) == true"] end subgraph subGraph0["Object States"] Uninit["MaybeUninit::uninit()"] Written["MaybeUninit::write(value)"] InitRef["assume_init_ref()"] InitMut["assume_init_mut()"] Read["assume_init_read()"] end BitmapFalse --> Uninit BitmapTrue --> Read BitmapTrue --> SafetyCheck Read --> BitmapFalse SafetyCheck --> InitMut SafetyCheck --> InitRef SafetyCheck --> UnsafeAccess Uninit --> Written Written --> BitmapTrue
Pattern: In-Place Modification
For large objects where copying is expensive, use get_mut
for in-place modifications:
// Efficient pattern for large object updates
if let Some(large_object) = container.get_mut(object_id) {
large_object.update_field_efficiently();
large_object.perform_in_place_operation();
}
Sources: src/lib.rs(L48 - L51) src/lib.rs(L194 - L202) src/lib.rs(L79)
Memory Layout Optimization
The fixed-size array layout provides predictable memory access patterns:
flowchart TD subgraph subGraph1["Cache Locality"] SequentialAccess["Sequential ID Access"] LocalityPreservation["Cache Line Preservation"] PrefetchOptimization["Prefetch Optimization"] end subgraph subGraph0["Memory Layout"] ObjectArray["objects: [MaybeUninit<T>; CAP]"] Bitmap["id_bitmap: Bitmap<CAP>"] Counter["count: usize"] end LocalityPreservation --> PrefetchOptimization ObjectArray --> SequentialAccess SequentialAccess --> LocalityPreservation
Sources: src/lib.rs(L44 - L51)
Performance Optimization Techniques
Batch Operations Pattern
For systems that need to add multiple objects efficiently:
flowchart TD subgraph subGraph1["ID Allocation Optimization"] FirstFalse["first_false_index()"] BitmapScan["Bitmap Scanning"] IDPooling["ID Pooling Strategy"] end subgraph subGraph0["Batch Addition Strategy"] BatchRequest["Batch Object Request"] CapacityCheck["Available Capacity Check"] ReserveIDs["Reserve ID Block"] BulkInsert["Bulk Insert Operations"] end BatchRequest --> CapacityCheck BitmapScan --> IDPooling CapacityCheck --> ReserveIDs FirstFalse --> BitmapScan IDPooling --> BulkInsert ReserveIDs --> FirstFalse
Implementation Pattern:
// Efficient batch addition with pre-validation
let required_capacity = objects_to_add.len();
if container.count() + required_capacity > container.capacity() {
return Err(InsufficientCapacity);
}
let mut assigned_ids = Vec::with_capacity(required_capacity);
for object in objects_to_add {
match container.add(object) {
Ok(id) => assigned_ids.push(id),
Err(_) => {
// Should not happen due to pre-validation
// Cleanup already assigned objects
for &cleanup_id in &assigned_ids {
container.remove(cleanup_id);
}
return Err(BatchAdditionFailed);
}
}
}
Sources: src/lib.rs(L223 - L224) src/lib.rs(L222 - L232)
Iterator-Based Access Patterns
The ids()
iterator enables efficient bulk operations:
flowchart TD subgraph subGraph1["Bulk Operations"] BulkRead["Bulk Read Operations"] ConditionalUpdate["Conditional Updates"] StatisticsGathering["Statistics Gathering"] end subgraph subGraph0["Iterator Usage"] IDsIterator["ids()"] BitmapIter["Bitmap::into_iter()"] FilteredAccess["Filtered Object Access"] end BitmapIter --> FilteredAccess FilteredAccess --> BulkRead FilteredAccess --> ConditionalUpdate FilteredAccess --> StatisticsGathering IDsIterator --> BitmapIter
Sources: src/lib.rs(L344 - L346) src/lib.rs(L328 - L347)
Kernel Integration Patterns
Resource Table Implementation
FlattenObjects
is commonly used to implement kernel resource tables:
flowchart TD subgraph subGraph2["Resource Lifecycle"] Allocation["Resource Allocation"] ActiveUse["Active Resource Use"] Cleanup["Resource Cleanup"] end subgraph subGraph1["System Call Interface"] SyscallEntry["System Call Entry"] IDValidation["Resource ID Validation"] ResourceAccess["Resource Access"] PermissionCheck["Permission Checking"] end subgraph subGraph0["Kernel Resource Management"] ProcessTable["FlattenObjects<ProcessDescriptor, 1024>"] FileTable["FlattenObjects<FileHandle, 512>"] SocketTable["FlattenObjects<NetworkSocket, 256>"] end ActiveUse --> ProcessTable Allocation --> ProcessTable Cleanup --> ProcessTable IDValidation --> FileTable IDValidation --> ProcessTable IDValidation --> SocketTable PermissionCheck --> ActiveUse ResourceAccess --> PermissionCheck SyscallEntry --> IDValidation
Kernel Pattern: Process Management
// Kernel process table implementation
pub struct ProcessManager {
processes: FlattenObjects<ProcessDescriptor, 1024>,
}
impl ProcessManager {
pub fn create_process(&mut self, executable: &Path) -> Result<ProcessId, ProcessError> {
let process = ProcessDescriptor::new(executable)?;
match self.processes.add(process) {
Ok(pid) => Ok(ProcessId(pid)),
Err(_) => Err(ProcessError::ProcessLimitExceeded),
}
}
pub fn get_process(&self, pid: ProcessId) -> Option<&ProcessDescriptor> {
self.processes.get(pid.0)
}
}
Sources: src/lib.rs(L44 - L51) src/lib.rs(L32)
Handle Table Pattern
For systems requiring opaque handle management:
flowchart TD subgraph subGraph1["Handle Validation"] HandleLookup["Handle Lookup"] TokenVerification["Token Verification"] ResourceAccess["Safe Resource Access"] end subgraph subGraph0["Handle Generation"] IDAssignment["ID Assignment"] HandleOpaque["Opaque Handle Creation"] SecurityToken["Security Token Embedding"] end HandleLookup --> TokenVerification HandleOpaque --> SecurityToken IDAssignment --> HandleOpaque SecurityToken --> HandleLookup TokenVerification --> ResourceAccess
Sources: src/lib.rs(L164 - L173) src/lib.rs(L144 - L146)
Safety and Correctness Patterns
Unsafe Code Encapsulation
The library encapsulates unsafe operations behind safe interfaces:
flowchart TD subgraph subGraph2["Unsafe Operations"] AssumeInitRef["assume_init_ref()"] AssumeInitMut["assume_init_mut()"] AssumeInitRead["assume_init_read()"] MaybeUninitWrite["MaybeUninit::write()"] end subgraph subGraph1["Memory Safety Invariants"] BitmapSync["Bitmap-Array Synchronization"] InitializationState["Initialization State Tracking"] LifecycleManagement["Object Lifecycle Management"] end subgraph subGraph0["Safety Boundaries"] PublicAPI["Public Safe API"] SafetyChecks["is_assigned() Validation"] UnsafeOps["Unsafe Memory Operations"] end BitmapSync --> InitializationState InitializationState --> UnsafeOps PublicAPI --> SafetyChecks SafetyChecks --> BitmapSync UnsafeOps --> AssumeInitMut UnsafeOps --> AssumeInitRead UnsafeOps --> AssumeInitRef UnsafeOps --> MaybeUninitWrite
Critical Safety Pattern:
The library maintains the invariant that id_bitmap.get(id) == true
if and only if objects[id]
contains an initialized value. This enables safe access through the public API.
Sources: src/lib.rs(L165 - L172) src/lib.rs(L195 - L201) src/lib.rs(L283 - L286) src/lib.rs(L316 - L325)
Const-Correctness in No-Std
The const fn new()
pattern enables compile-time initialization:
#![allow(unused)] fn main() { // Static allocation pattern for kernel use static GLOBAL_PROCESS_TABLE: FlattenObjects<ProcessDescriptor, 1024> = FlattenObjects::new(); // Boot-time initialization static mut INITIALIZED: bool = false; pub fn init_process_system() { unsafe { if !INITIALIZED { // Perform additional initialization if needed INITIALIZED = true; } } } }
Sources: src/lib.rs(L77 - L84) src/lib.rs(L32)
Resource Lifecycle Management
RAII Integration Pattern
For automatic resource cleanup in kernel contexts:
flowchart TD subgraph subGraph2["Automatic Cleanup"] DropImpl["Drop Implementation"] RemoveCall["container.remove(id)"] ResourceCleanup["Resource-specific cleanup"] end subgraph subGraph1["Lifecycle Events"] HandleWrapper["ResourceHandle<T>"] AcquireResource["acquire()"] UseResource["deref() / deref_mut()"] ReleaseResource["drop()"] end subgraph subGraph0["RAII Wrapper"] HandleWrapper["ResourceHandle<T>"] ContainerRef["&FlattenObjects<T, CAP>"] ResourceID["usize"] AcquireResource["acquire()"] end AcquireResource --> UseResource DropImpl --> RemoveCall HandleWrapper --> ContainerRef HandleWrapper --> ResourceID ReleaseResource --> DropImpl RemoveCall --> ResourceCleanup UseResource --> ReleaseResource
RAII Implementation Pattern:
pub struct ResourceHandle<'a, T> {
container: &'a mut FlattenObjects<T, 1024>,
id: usize,
}
impl<'a, T> Drop for ResourceHandle<'a, T> {
fn drop(&mut self) {
// Automatic cleanup when handle goes out of scope
if let Some(resource) = self.container.remove(self.id) {
// Perform resource-specific cleanup
cleanup_resource(resource);
}
}
}
impl<'a, T> Deref for ResourceHandle<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.container.get(self.id)
.expect("Handle invariant violated")
}
}
Sources: src/lib.rs(L315 - L326) src/lib.rs(L164 - L173)
Development and Maintenance
Relevant source files
This document covers the development infrastructure, build processes, and maintenance procedures for the flatten_objects
crate. It provides guidance for contributors working on the codebase and maintainers responsible for releases and quality assurance.
For detailed API usage information, see Overview. For implementation specifics, see Implementation Details.
Development Workflow Overview
The flatten_objects
crate follows a standard Rust development workflow with automated CI/CD pipelines and multi-target support for embedded and kernel environments.
Development Workflow
flowchart TD Dev["Developer"] LocalDev["Local Development"] Format["cargo fmt"] Clippy["cargo clippy"] Build["cargo build"] Test["cargo test"] Push["git push"] CI["GitHub Actions CI"] FormatCheck["Format Check"] ClippyCheck["Clippy Analysis"] MultiTarget["Multi-Target Build"] UnitTest["Unit Tests"] DocBuild["Documentation Build"] Linux["x86_64-unknown-linux-gnu"] BareMetal["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] ARM["aarch64-unknown-none-softfloat"] DocDeploy["GitHub Pages"] Success["✓ All Checks Pass"] Merge["Ready for Merge"] CI --> ClippyCheck CI --> DocBuild CI --> FormatCheck CI --> MultiTarget CI --> UnitTest Dev --> LocalDev DocBuild --> DocDeploy LocalDev --> Build LocalDev --> Clippy LocalDev --> Format LocalDev --> Push LocalDev --> Test MultiTarget --> ARM MultiTarget --> BareMetal MultiTarget --> Linux MultiTarget --> RISCV Push --> CI Success --> Merge UnitTest --> Success
Sources: .github/workflows/ci.yml(L1 - L56)
Continuous Integration Pipeline
The CI system runs on every push and pull request, performing comprehensive validation across multiple target platforms.
CI Job Configuration
Component | Configuration |
---|---|
Runner | ubuntu-latest |
Rust Toolchain | nightly |
Required Components | rust-src,clippy,rustfmt |
Target Platforms | 4 distinct targets |
Failure Strategy | fail-fast: false |
CI Pipeline Stages
flowchart TD subgraph Documentation["Documentation"] DocGen["cargo doc"] PagesDeploy["GitHub Pages Deploy"] end subgraph Testing["Testing"] UnitTests["cargo test"] LinuxOnly["Linux target only"] end subgraph subGraph1["Quality Assurance"] FmtCheck["cargo fmt --check"] ClippyRun["cargo clippy"] BuildStep["cargo build"] end subgraph subGraph0["Setup Phase"] Checkout["actions/checkout@v4"] Toolchain["dtolnay/rust-toolchain@nightly"] Version["rustc --version"] end BuildStep --> DocGen BuildStep --> UnitTests Checkout --> Toolchain ClippyRun --> BuildStep DocGen --> PagesDeploy FmtCheck --> ClippyRun Toolchain --> Version UnitTests --> LinuxOnly Version --> FmtCheck
Sources: .github/workflows/ci.yml(L5 - L31)
Multi-Target Build Support
The crate supports multiple target architectures to accommodate different embedded and kernel environments.
Supported Target Platforms
Target | Purpose | Environment |
---|---|---|
x86_64-unknown-linux-gnu | Development and testing | Standard Linux userspace |
x86_64-unknown-none | Bare metal x86_64 | Kernel/bootloader environments |
riscv64gc-unknown-none-elf | RISC-V bare metal | RISC-V embedded systems |
aarch64-unknown-none-softfloat | ARM64 bare metal | ARM embedded systems |
Build Matrix Execution
flowchart TD subgraph subGraph2["Target-Specific Logic"] LinuxTest["Unit tests: Linux only"] BareMetalBuild["Bare metal: Build only"] end subgraph subGraph1["Per-Target Operations"] ClippyTarget["cargo clippy --target TARGET"] BuildTarget["cargo build --target TARGET"] TestTarget["cargo test --target TARGET"] end subgraph subGraph0["Matrix Strategy"] Toolchain["nightly toolchain"] Targets["4 target platforms"] end BuildTarget --> TestTarget ClippyTarget --> BuildTarget Targets --> ClippyTarget TestTarget --> BareMetalBuild TestTarget --> LinuxTest Toolchain --> ClippyTarget
Sources: .github/workflows/ci.yml(L8 - L30)
Code Quality Assurance
The CI pipeline enforces code quality through multiple automated checks.
Quality Check Commands
Check | Command | Purpose |
---|---|---|
Formatting | cargo fmt --all -- --check | Enforce consistent code style |
Linting | cargo clippy --target TARGET --all-features | Static analysis and best practices |
Build Verification | cargo build --target TARGET --all-features | Compilation validation |
Testing | cargo test --target TARGET -- --nocapture | Functional correctness |
Clippy Configuration
The CI pipeline includes specific clippy configuration to suppress certain warnings:
- Suppressed:
clippy::new_without_default
- Allowsnew()
methods withoutDefault
trait - Features:
--all-features
- Enables all crate features during analysis
Sources: .github/workflows/ci.yml(L22 - L30)
Documentation System
The crate maintains automatically generated and deployed documentation.
Documentation Pipeline
flowchart TD subgraph Configuration["Configuration"] RustDocFlags["RUSTDOCFLAGS environment"] BrokenLinks["-D rustdoc::broken_intra_doc_links"] MissingDocs["-D missing-docs"] end subgraph subGraph1["Deployment Logic"] BranchCheck["Check if default branch"] Deploy["JamesIves/github-pages-deploy-action@v4"] PagesUpdate["Update gh-pages branch"] end subgraph subGraph0["Documentation Generation"] DocBuild["cargo doc --no-deps"] IndexGen["Generate index.html redirect"] TreeParse["cargo tree | head -1"] end BranchCheck --> Deploy BrokenLinks --> DocBuild Deploy --> PagesUpdate DocBuild --> IndexGen IndexGen --> BranchCheck MissingDocs --> DocBuild RustDocFlags --> BrokenLinks RustDocFlags --> MissingDocs TreeParse --> IndexGen
Documentation Configuration
Setting | Value | Purpose |
---|---|---|
RUSTDOCFLAGS | -D rustdoc::broken_intra_doc_links -D missing-docs | Enforce complete documentation |
Deployment Branch | gh-pages | GitHub Pages hosting |
Build Options | --no-deps --all-features | Generate only crate docs with all features |
Deployment Trigger | Default branch pushes only | Automatic documentation updates |
Sources: .github/workflows/ci.yml(L32 - L56)
Development Environment Requirements
Required Rust Components
Component | Purpose |
---|---|
nightly toolchain | Required for no_std development |
rust-src | Source code for cross-compilation |
clippy | Linting and static analysis |
rustfmt | Code formatting |
Target Installation
To set up a complete development environment:
# Install nightly toolchain with required components
rustup toolchain install nightly --component rust-src clippy rustfmt
# Add target platforms
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
Local Development Commands
Operation | Command |
---|---|
Format code | cargo fmt |
Check formatting | cargo fmt --check |
Run clippy | cargo clippy --all-features |
Build for target | cargo build --target |
Run tests | cargo test -- --nocapture |
Generate docs | cargo doc --no-deps --all-features |
Sources: .github/workflows/ci.yml(L15 - L19)
Building and Testing
Relevant source files
This document details the continuous integration pipeline, multi-target build system, testing strategy, and development workflow for the flatten_objects
crate. It covers the automated quality assurance processes, supported target platforms, and local development practices.
For information about project configuration and development environment setup, see Project Configuration.
CI Pipeline Overview
The flatten_objects
crate uses GitHub Actions for continuous integration with two primary workflows: code quality validation and documentation deployment. The pipeline ensures compatibility across multiple target platforms while maintaining code quality standards.
CI Workflow Architecture
flowchart TD subgraph subGraph3["Documentation Job"] doc_build["cargo doc --no-deps --all-features"] pages_deploy["JamesIves/github-pages-deploy-action@v4"] end subgraph subGraph2["Build Steps"] checkout["actions/checkout@v4"] toolchain["dtolnay/rust-toolchain@nightly"] rustc_version["rustc --version"] cargo_fmt["cargo fmt --all --check"] cargo_clippy["cargo clippy --target TARGET"] cargo_build["cargo build --target TARGET"] cargo_test["cargo test --target x86_64-unknown-linux-gnu"] end subgraph subGraph1["CI Job Matrix"] strategy["strategy.matrix"] rust_nightly["rust-toolchain: nightly"] target_linux["x86_64-unknown-linux-gnu"] target_none["x86_64-unknown-none"] target_riscv["riscv64gc-unknown-none-elf"] target_arm["aarch64-unknown-none-softfloat"] end subgraph subGraph0["GitHub Events"] push["push"] pr["pull_request"] end cargo_build --> cargo_test cargo_clippy --> cargo_build cargo_fmt --> cargo_clippy checkout --> toolchain doc_build --> pages_deploy pr --> strategy push --> doc_build push --> strategy rust_nightly --> target_arm rust_nightly --> target_linux rust_nightly --> target_none rust_nightly --> target_riscv rustc_version --> cargo_fmt strategy --> rust_nightly target_arm --> checkout target_linux --> checkout target_none --> checkout target_riscv --> checkout toolchain --> rustc_version
Sources: .github/workflows/ci.yml(L1 - L56)
Job Matrix Configuration
The CI system uses a matrix strategy to test against multiple target platforms simultaneously:
Target Platform | Purpose | Test Execution |
---|---|---|
x86_64-unknown-linux-gnu | Standard Linux development | Full test suite |
x86_64-unknown-none | Bare metal x86_64 | Build only |
riscv64gc-unknown-none-elf | RISC-V bare metal | Build only |
aarch64-unknown-none-softfloat | ARM64 bare metal | Build only |
Sources: .github/workflows/ci.yml(L12)
Multi-Target Build System
The crate supports multiple target architectures to ensure compatibility with various embedded and bare metal environments. The build system validates compilation across all supported targets.
Target Platform Validation Flow
flowchart TD subgraph subGraph3["Test Execution"] test_linux["cargo test --target x86_64-unknown-linux-gnu"] test_condition["if matrix.targets == 'x86_64-unknown-linux-gnu'"] end subgraph subGraph2["Build Validation"] build_linux["cargo build --target x86_64-unknown-linux-gnu"] build_none["cargo build --target x86_64-unknown-none"] build_riscv["cargo build --target riscv64gc-unknown-none-elf"] build_arm["cargo build --target aarch64-unknown-none-softfloat"] end subgraph subGraph1["Quality Checks"] fmt_check["cargo fmt --all --check"] clippy_check["cargo clippy --target TARGET"] end subgraph subGraph0["Rust Toolchain Setup"] nightly["nightly toolchain"] components["rust-src, clippy, rustfmt"] targets["target installation"] end build_linux --> test_condition clippy_check --> build_arm clippy_check --> build_linux clippy_check --> build_none clippy_check --> build_riscv components --> targets fmt_check --> clippy_check nightly --> components targets --> fmt_check test_condition --> test_linux
Sources: .github/workflows/ci.yml(L15 - L30)
Target-Specific Build Commands
Each target platform uses identical build commands but with different target specifications:
- Format Check:
cargo fmt --all -- --check
validates code formatting consistency - Linting:
cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default
- Build:
cargo build --target ${{ matrix.targets }} --all-features
- Testing:
cargo test --target ${{ matrix.targets }} -- --nocapture
(Linux only)
Sources: .github/workflows/ci.yml(L23 - L30)
Testing Strategy
The testing approach focuses on functional validation while accommodating the constraints of bare metal target platforms.
Test Execution Matrix
flowchart TD subgraph subGraph2["Execution Strategy"] linux_only["x86_64-unknown-linux-gnu only"] build_only["Build validation only"] nocapture["--nocapture flag"] end subgraph subGraph1["Test Types"] unit_tests["Unit Tests"] integration_tests["Integration Tests"] doc_tests["Documentation Tests"] end subgraph subGraph0["Test Environments"] hosted["Hosted Environment"] bare_metal["Bare Metal Targets"] end bare_metal --> build_only doc_tests --> linux_only hosted --> doc_tests hosted --> integration_tests hosted --> unit_tests integration_tests --> linux_only linux_only --> nocapture unit_tests --> linux_only
Sources: .github/workflows/ci.yml(L28 - L30)
Test Execution Conditions
Tests are executed only on the x86_64-unknown-linux-gnu
target due to the following constraints:
- Bare metal targets lack test harness support
no_std
environments have limited testing infrastructure- The
--nocapture
flag provides detailed test output for debugging
The conditional test execution is implemented using GitHub Actions matrix conditions:
if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }}
Sources: .github/workflows/ci.yml(L29)
Code Quality Checks
The CI pipeline enforces code quality through automated formatting and linting checks that run before build validation.
Quality Assurance Pipeline
flowchart TD subgraph subGraph2["Quality Gates"] fmt_gate["Format validation"] lint_gate["Lint validation"] allowlist["clippy::new_without_default"] end subgraph subGraph1["Quality Commands"] fmt_cmd["cargo fmt --all --check"] clippy_cmd["cargo clippy --all-features"] version_cmd["rustc --version --verbose"] end subgraph subGraph0["Code Quality Tools"] rustfmt["rustfmt"] clippy["clippy"] rustc["rustc"] end clippy --> clippy_cmd clippy_cmd --> lint_gate fmt_cmd --> fmt_gate lint_gate --> allowlist rustc --> version_cmd rustfmt --> fmt_cmd
Sources: .github/workflows/ci.yml(L20 - L25)
Clippy Configuration
The linting process uses specific allowlist configurations:
- Suppressed Warning:
clippy::new_without_default
is allowed since the crate provides specialized constructors - Target-Specific: Clippy runs against each target platform independently
- Feature Complete:
--all-features
ensures comprehensive linting coverage
Sources: .github/workflows/ci.yml(L25)
Documentation Build and Deployment
The documentation system automatically builds and deploys API documentation to GitHub Pages for the main branch.
Documentation Workflow
flowchart TD subgraph subGraph3["Error Handling"] continue_on_error["continue-on-error"] branch_condition["github.ref == env.default-branch"] end subgraph Deployment["Deployment"] pages_action["JamesIves/github-pages-deploy-action@v4"] gh_pages["gh-pages branch"] single_commit["single-commit: true"] end subgraph subGraph1["Build Process"] cargo_doc["cargo doc --no-deps --all-features"] doc_tree["cargo tree"] index_html["index.html redirect"] end subgraph subGraph0["Documentation Job"] doc_trigger["default-branch push"] doc_permissions["contents: write"] rustdoc_flags["RUSTDOCFLAGS environment"] end branch_condition --> pages_action cargo_doc --> continue_on_error cargo_doc --> doc_tree doc_permissions --> rustdoc_flags doc_tree --> index_html doc_trigger --> doc_permissions index_html --> branch_condition pages_action --> gh_pages pages_action --> single_commit rustdoc_flags --> cargo_doc
Sources: .github/workflows/ci.yml(L32 - L56)
Documentation Configuration
The documentation build process includes several important configurations:
- Error Detection:
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
- Dependency Exclusion:
--no-deps
focuses on crate-specific documentation - Branch Protection: Deployment only occurs on the default branch
- Index Generation: Automatic redirect to main crate documentation
Sources: .github/workflows/ci.yml(L40 - L48)
Local Development Workflow
For local development, developers should follow the same quality checks used in CI to ensure compatibility before pushing changes.
Local Development Commands
Command | Purpose | Target Requirement |
---|---|---|
cargo fmt --all --check | Validate formatting | Any |
cargo clippy --all-features | Run linting | Any |
cargo build --target | Build validation | Specific target |
cargo test | Execute tests | Linux host only |
cargo doc --no-deps | Generate documentation | Any |
Recommended Development Sequence
flowchart TD subgraph subGraph2["Commit Process"] git_commit["git commit"] ci_trigger["CI pipeline trigger"] end subgraph subGraph1["Pre-commit Validation"] fmt_check_local["cargo fmt --all --check"] clippy_check_local["cargo clippy --all-features"] multi_target["Multi-target builds"] end subgraph subGraph0["Local Development"] code_change["Code changes"] fmt_local["cargo fmt --all"] clippy_local["cargo clippy --fix"] build_local["cargo build"] test_local["cargo test"] end build_local --> test_local clippy_check_local --> multi_target clippy_local --> build_local code_change --> fmt_local fmt_check_local --> clippy_check_local fmt_local --> clippy_local git_commit --> ci_trigger multi_target --> git_commit test_local --> fmt_check_local
Sources: .github/workflows/ci.yml(L22 - L27)
Project Configuration
Relevant source files
This document covers the configuration files and settings that control the development environment, build process, and distribution of the flatten_objects
crate. It focuses on the static configuration files that define project metadata, dependencies, and development workflows.
For information about the actual build and testing processes, see Building and Testing. For details about the codebase structure and implementation, see Implementation Details.
Package Configuration
The primary configuration for the flatten_objects
crate is defined in Cargo.toml
, which contains all package metadata, dependency specifications, and publication settings.
Package Metadata
The crate is configured as a library package with the following key attributes:
Field | Value | Purpose |
---|---|---|
name | "flatten_objects" | Crate identifier for Cargo registry |
version | "0.2.3" | Semantic version following SemVer |
edition | "2024" | Rust edition (latest available) |
rust-version | "1.85" | Minimum supported Rust version (MSRV) |
The package description clearly identifies its purpose: "A container that stores numbered objects. Each object can be assigned with a unique ID." This aligns with the crate's role as a specialized data structure for resource-constrained environments.
Project Configuration Structure
flowchart TD subgraph subGraph3["Dependency Specification"] Bitmaps["bitmaps = { version = 3.2, default-features = false }"] end subgraph subGraph2["Package Metadata"] Name["name = flatten_objects"] Version["version = 0.2.3"] Edition["edition = 2024"] Authors["authors"] License["license"] Links["repository, homepage, docs"] end subgraph subGraph1["Cargo.toml Sections"] Package["[package]"] Dependencies["[dependencies]"] end subgraph subGraph0["Project Root"] CargoToml["Cargo.toml"] GitIgnore[".gitignore"] SrcDir["src/"] TargetDir["target/ (ignored)"] end CargoToml --> Dependencies CargoToml --> Package Dependencies --> Bitmaps GitIgnore --> TargetDir Package --> Authors Package --> Edition Package --> License Package --> Links Package --> Name Package --> Version
Sources: Cargo.toml(L1 - L17)
Licensing and Legal Configuration
The crate uses a triple-license configuration to maximize compatibility across different ecosystems:
license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"
This licensing strategy accommodates:
- GPL-3.0-or-later: Copyleft compatibility for GPL projects
- Apache-2.0: Permissive licensing for commercial use
- MulanPSL-2.0: Chinese legal framework compliance
Sources: Cargo.toml(L7)
Repository and Documentation Links
The configuration establishes the project's presence in the Rust ecosystem:
Field | URL | Purpose |
---|---|---|
homepage | https://github.com/arceos-org/arceos | Links to parent ArceOS project |
repository | https://github.com/arceos-org/flatten_objects | Source code location |
documentation | https://docs.rs/flatten_objects | Auto-generated API docs |
Sources: Cargo.toml(L8 - L10)
Dependency Management
The crate maintains a minimal dependency footprint with only one external dependency, reflecting its design for resource-constrained environments.
External Dependencies
Dependency Configuration Analysis
flowchart TD subgraph subGraph2["Standard Library"] CoreOnly["core only"] NoStdLib["no std library"] NoAlloc["no alloc"] end subgraph subGraph1["External Dependencies"] BitmapsCrate["bitmaps crate"] BitmapsVersion["version = 3.2"] BitmapsConfig["default-features = false"] end subgraph subGraph0["flatten_objects Crate"] MainCrate["flatten_objects"] NoStdEnv["no_std environment"] end BitmapsCrate --> BitmapsConfig BitmapsCrate --> BitmapsVersion MainCrate --> BitmapsCrate MainCrate --> NoStdEnv NoStdEnv --> CoreOnly NoStdEnv --> NoAlloc NoStdEnv --> NoStdLib
The single dependency is carefully configured:
bitmaps = { version = "3.2", default-features = false }
Key aspects of this dependency configuration:
- Version constraint:
3.2
provides stable bitmap operations - Feature configuration:
default-features = false
ensuresno_std
compatibility - Minimal footprint: Only includes essential bitmap functionality
Sources: Cargo.toml(L15 - L16)
No Standard Library Dependencies
The crate is designed for no_std
environments, which means:
- No implicit dependency on the Rust standard library
- Only uses
core
library functionality - Compatible with embedded and kernel environments
- Suitable for ArceOS integration
This configuration is reflected in the package categories: ["no-std", "data-structures"]
.
Sources: Cargo.toml(L12)
Development Environment Configuration
Version Control Configuration
The .gitignore
file defines which files and directories are excluded from version control:
Version Control Exclusions
flowchart TD subgraph subGraph3["Tracked Files"] SourceCode["src/"] CargoToml["Cargo.toml"] ReadMe["README.md"] end subgraph subGraph2["Ignored Files"] DSStore[".DS_Store"] CargoLock["Cargo.lock"] end subgraph subGraph1["Ignored Directories"] Target["/target"] VSCode["/.vscode"] end subgraph subGraph0["Version Control"] GitRepo["Git Repository"] GitIgnore[".gitignore"] end GitIgnore --> CargoLock GitIgnore --> DSStore GitIgnore --> Target GitIgnore --> VSCode GitRepo --> CargoToml GitRepo --> ReadMe GitRepo --> SourceCode
Ignored Item | Type | Reason |
---|---|---|
/target | Directory | Build artifacts and compiled output |
/.vscode | Directory | Editor-specific configuration |
.DS_Store | File | macOS system metadata |
Cargo.lock | File | Dependency lock file (library crate) |
The exclusion of Cargo.lock
is significant because this is a library crate, not an application. Library crates typically don't commit their lock files to allow downstream users flexibility in dependency resolution.
Sources: .gitignore(L1 - L4)
Editor Configuration
The .gitignore
configuration shows consideration for multiple development environments:
- VS Code: Excludes
.vscode/
directory for editor settings - macOS: Excludes
.DS_Store
system files - Cross-platform: Standard Rust build artifact exclusions
This configuration supports a diverse development team working across different platforms and editors.
Sources: .gitignore(L2 - L3)
Publication and Distribution Settings
Crates.io Configuration
The package is configured for publication to the official Rust package registry with appropriate metadata for discoverability:
keywords = ["arceos", "data-structures"]
categories = ["no-std", "data-structures"]
Distribution Categories and Keywords
flowchart TD subgraph subGraph4["Target Audience"] EmbeddedDevs["Embedded Developers"] KernelDevs["Kernel Developers"] ArceOSUsers["ArceOS Users"] end subgraph subGraph3["Category Tags"] NoStd["no-std"] DataStructuresCategory["data-structures"] end subgraph subGraph2["Keyword Tags"] ArceOS["arceos"] DataStructures["data-structures"] end subgraph subGraph1["Package Classification"] Keywords["keywords"] Categories["categories"] end subgraph subGraph0["Crates.io Registry"] CratesIo["crates.io"] SearchIndex["Search Index"] end ArceOS --> ArceOSUsers Categories --> DataStructuresCategory Categories --> NoStd CratesIo --> SearchIndex DataStructures --> KernelDevs Keywords --> ArceOS Keywords --> DataStructures NoStd --> EmbeddedDevs SearchIndex --> Categories SearchIndex --> Keywords
These classifications help users discover the crate when searching for:
- ArceOS-related components
- Data structure libraries
- no_std compatible crates
Sources: Cargo.toml(L11 - L12)
Documentation Configuration
The crate leverages automatic documentation generation through docs.rs:
- Documentation URL:
https://docs.rs/flatten_objects
- Automatic builds on crate publication
- API documentation generated from source code comments
This configuration ensures that comprehensive documentation is available to users without requiring separate hosting infrastructure.
Sources: Cargo.toml(L10)
Overview
Relevant source files
This document provides an overview of the arm_gicv2
crate, a hardware abstraction layer for the ARM Generic Interrupt Controller version 2 (GICv2). The crate provides type-safe register definitions and basic operations for managing interrupts on ARM-based systems.
For detailed information about interrupt classification and ranges, see Interrupt System Architecture. For implementation details of the hardware interfaces, see Hardware Interface Implementation. For development guidance and build system information, see Development Guide.
Purpose and Scope
The arm_gicv2
crate serves as a low-level hardware abstraction layer for the ARM Generic Interrupt Controller version 2, enabling operating systems and embedded applications to manage interrupts in a type-safe manner. The crate provides register definitions, interrupt type classifications, and basic operations without imposing higher-level interrupt handling policies.
Sources: Cargo.toml(L1 - L16) README.md(L1 - L10)
Target Platforms and Use Cases
The crate targets ARM systems that implement the GICv2 specification, specifically:
Platform | Architecture | Use Cases |
---|---|---|
ARMv7-A | Application Processors | Operating systems, hypervisors |
ARMv7-R | Real-time Processors | Real-time systems, safety-critical applications |
The crate is designed for no_std
environments and supports multiple target architectures including aarch64-unknown-none-softfloat
and riscv64gc-unknown-none-elf
, making it suitable for bare-metal operating system development and embedded applications.
Sources: Cargo.toml(L11 - L12)
Core Architecture
The following diagram illustrates the main components of the arm_gicv2
crate and their relationships to code entities:
flowchart TD subgraph subGraph4["Hardware Target"] ARM_GICv2["ARM GICv2 ControllerMemory-mapped registers1024 interrupt capacity"] end subgraph subGraph3["External Dependencies"] tock_registers["tock-registers crateType-safe register access"] end subgraph subGraph2["Register Definitions"] GicDistributorRegs["GicDistributorRegsCTLR, ISENABLERICFGR, ITARGETSR"] GicCpuInterfaceRegs["GicCpuInterfaceRegsIAR, EOIRPMR, CTLR"] end subgraph subGraph1["Hardware Abstraction Layer"] gicv2["src/gic_v2.rs"] GicDistributor["GicDistributor struct"] GicCpuInterface["GicCpuInterface struct"] end subgraph subGraph0["Public API Layer"] lib["src/lib.rs"] Constants["SGI_RANGE, PPI_RANGESPI_RANGE, GIC_MAX_IRQ"] InterruptType["InterruptType enumtranslate_irq()"] TriggerMode["TriggerMode enum"] end GicCpuInterface --> GicCpuInterfaceRegs GicCpuInterfaceRegs --> ARM_GICv2 GicCpuInterfaceRegs --> tock_registers GicDistributor --> GicDistributorRegs GicDistributorRegs --> ARM_GICv2 GicDistributorRegs --> tock_registers InterruptType --> GicDistributor TriggerMode --> GicDistributor gicv2 --> GicCpuInterface gicv2 --> GicDistributor lib --> Constants lib --> InterruptType lib --> TriggerMode
Sources: Cargo.toml(L14 - L15) high-level architecture diagrams
Key Features
The arm_gicv2
crate provides the following core features:
Interrupt Type System
- SGI (Software Generated Interrupts): IDs 0-15 for inter-processor communication
- PPI (Private Peripheral Interrupts): IDs 16-31 for CPU-specific peripherals
- SPI (Shared Peripheral Interrupts): IDs 32-1019 for system-wide peripherals
- ID Translation: The
translate_irq()
function maps logical interrupt IDs to physical GIC interrupt IDs
Hardware Interface Components
GicDistributor
: System-wide interrupt configuration, routing, and priority managementGicCpuInterface
: Per-CPU interrupt handling, acknowledgment, and completion signaling
Type-Safe Register Access
The crate leverages the tock-registers
dependency to provide compile-time safety for register operations, preventing common errors in low-level hardware programming.
Sources: Cargo.toml(L6) Cargo.toml(L14 - L15)
Integration Context
The following diagram shows how the arm_gicv2
crate integrates into larger system contexts:
flowchart TD subgraph subGraph2["Common Use Patterns"] IntMgmt["Interrupt ManagementEnable/disablePriority configCPU routing"] TaskSched["Task SchedulingPreemptive multitaskingLoad balancing"] IPC["Inter-Processor CommSGI handlingCore synchronization"] end subgraph subGraph1["arm_gicv2 Crate Interface"] PublicAPI["Public APIInterruptTypetranslate_irq()Constants"] HardwareAbs["Hardware AbstractionsGicDistributorGicCpuInterface"] end subgraph subGraph0["System Software"] OS["Operating SystemsBare-metal OSHypervisors"] RTOS["Real-time SystemsSafety-critical appsIoT devices"] end HardwareAbs --> IPC HardwareAbs --> IntMgmt HardwareAbs --> TaskSched OS --> PublicAPI PublicAPI --> HardwareAbs RTOS --> PublicAPI
The crate is designed to be a foundational component in system software stacks, providing the necessary abstractions for higher-level interrupt management policies while maintaining direct access to hardware capabilities when needed.
Sources: Cargo.toml(L11 - L12) README.md(L7 - L9)
Interrupt System Architecture
Relevant source files
This document explains the interrupt classification and architectural design of the ARM GICv2 interrupt controller as modeled by the arm_gicv2
crate. It covers the three interrupt types (SGI, PPI, SPI), their ID ranges, trigger modes, and the translation mechanisms that map logical interrupt identifiers to physical GIC interrupt IDs.
For details about the hardware interface implementation that manages these interrupts, see Hardware Interface Implementation. For specific build and development information, see Development Guide.
GICv2 Interrupt Hierarchy
The ARM Generic Interrupt Controller version 2 supports up to 1024 interrupt sources, organized into three distinct categories based on their scope and routing capabilities. The arm_gicv2
crate models this architecture through a combination of range constants, enumeration types, and translation functions.
GICv2 Interrupt Address Space
Sources: src/lib.rs(L12 - L30)
Interrupt Classification System
The crate defines three interrupt types through the InterruptType
enum, each corresponding to different use cases and routing behaviors in the GICv2 architecture.
Interrupt Type Definitions
Interrupt Type | ID Range | Constant | Purpose |
---|---|---|---|
SGI (Software Generated) | 0-15 | SGI_RANGE | Inter-processor communication |
PPI (Private Peripheral) | 16-31 | PPI_RANGE | Single-processor specific interrupts |
SPI (Shared Peripheral) | 32-1019 | SPI_RANGE | Multi-processor routable interrupts |
Interrupt Type Architecture
Sources: src/lib.rs(L48 - L63) src/lib.rs(L12 - L27)
Software Generated Interrupts (SGI)
SGIs occupy interrupt IDs 0-15 and are generated through software writes to the GICD_SGIR
register. These interrupts enable communication between processor cores in multi-core systems.
- Range: Defined by
SGI_RANGE
constant as0..16
- Generation: Software write to GIC distributor register
- Routing: Can target specific CPUs or CPU groups
- Use Cases: Inter-processor synchronization, cross-core notifications
Private Peripheral Interrupts (PPI)
PPIs use interrupt IDs 16-31 and are associated with peripherals private to individual processor cores, such as per-core timers and performance monitoring units.
- Range: Defined by
PPI_RANGE
constant as16..32
- Scope: Private to individual CPU cores
- Routing: Cannot be routed between cores
- Use Cases: Local timers, CPU-specific peripherals
Shared Peripheral Interrupts (SPI)
SPIs occupy the largest address space (32-1019) and represent interrupts from shared system peripherals that can be routed to any available processor core.
- Range: Defined by
SPI_RANGE
constant as32..1020
- Routing: Configurable to any CPU or CPU group
- Distribution: Managed by GIC distributor
- Use Cases: External device interrupts, system peripherals
Sources: src/lib.rs(L12 - L27)
ID Translation Architecture
The translate_irq
function provides a mapping mechanism between logical interrupt identifiers and physical GIC interrupt IDs, enabling type-safe interrupt management.
Translation Function Flow
Translation Examples
Input Type | Logical ID | Physical GIC ID | Calculation |
---|---|---|---|
SGI | 5 | 5 | Direct mapping |
PPI | 3 | 19 | 3 + 16 (PPI_RANGE.start) |
SPI | 100 | 132 | 100 + 32 (SPI_RANGE.start) |
The function returns Option<usize>
, with None
indicating an invalid logical ID for the specified interrupt type.
Sources: src/lib.rs(L65 - L90)
Trigger Mode System
The GICv2 controller supports two trigger modes for interrupt signals, modeled through the TriggerMode
enum. These modes determine how the hardware interprets interrupt signal transitions.
Trigger Mode Definitions
Edge-Triggered Mode
Edge-triggered interrupts are asserted on detection of a rising edge and remain asserted until explicitly cleared by software, regardless of the ongoing signal state.
- Enum Value:
TriggerMode::Edge = 0
- Detection: Rising edge of interrupt signal
- Persistence: Remains asserted until software clears
- Use Cases: Event-driven interrupts, completion notifications
Level-Sensitive Mode
Level-sensitive interrupts are asserted whenever the interrupt signal is at an active level and automatically deasserted when the signal becomes inactive.
- Enum Value:
TriggerMode::Level = 1
- Detection: Active signal level
- Persistence: Follows signal state
- Use Cases: Status-driven interrupts, continuous monitoring
Sources: src/lib.rs(L32 - L46)
Interrupt Types and Ranges
Relevant source files
This document details the three interrupt types supported by the ARM GICv2 controller and their corresponding interrupt ID ranges as implemented in the arm_gicv2 crate. It covers the classification system that divides the 1024-interrupt address space into Software-Generated Interrupts (SGI), Private Peripheral Interrupts (PPI), and Shared Peripheral Interrupts (SPI), along with the translation mechanisms between logical and physical interrupt identifiers.
For information about trigger modes and the translate_irq
function implementation details, see Trigger Modes and Translation. For the hardware interface implementations that use these interrupt types, see Hardware Interface Implementation.
Interrupt Classification Overview
The ARM GICv2 specification defines a structured interrupt ID space of 1024 interrupts (0-1023) divided into three distinct types, each serving different purposes in the system architecture.
GICv2 Interrupt ID Space Allocation
flowchart TD subgraph subGraph0["GIC_MAX_IRQ [1024 total interrupts]"] SGI["SGI_RANGEIDs 0-15Software GeneratedInter-processor communication"] PPI["PPI_RANGEIDs 16-31Private PeripheralSingle CPU specific"] SPI["SPI_RANGEIDs 32-1019Shared PeripheralMulti-CPU routing"] Reserved["ReservedIDs 1020-1023Implementation specific"] end InterruptType_SGI["InterruptType::SGI"] InterruptType_PPI["InterruptType::PPI"] InterruptType_SPI["InterruptType::SPI"] translate_irq["translate_irq()"] InterruptType_PPI --> translate_irq InterruptType_SGI --> translate_irq InterruptType_SPI --> translate_irq PPI --> InterruptType_PPI SGI --> InterruptType_SGI SPI --> InterruptType_SPI
Sources: src/lib.rs(L12 - L30)
Software-Generated Interrupts (SGI)
Software-Generated Interrupts occupy interrupt IDs 0-15 and are primarily used for inter-processor communication in multi-core systems. These interrupts are triggered by software writing to the GIC Distributor's GICD_SGIR
register.
Property | Value |
---|---|
Range Constant | SGI_RANGE |
ID Range | 0-15 (16 interrupts) |
Purpose | Inter-processor communication |
Trigger Method | Software write to GICD_SGIR |
Target | Specific CPU cores |
SGI Characteristics and Usage
flowchart TD subgraph subGraph0["Common SGI Use Cases"] IPC["Inter-processorcommunication"] TaskMigration["Task migrationrequests"] CacheOps["Cache maintenanceoperations"] Scheduling["Schedulernotifications"] end CPU0["CPU Core 0"] GICD_SGIR["GICD_SGIR RegisterSGI Generation"] CPU1["CPU Core 1"] CPU2["CPU Core 2"] CPU3["CPU Core 3"] SGI_0_15["SGI IDs 0-15SGI_RANGE"] CPU0_Interface["CPU 0 Interface"] CPU1_Interface["CPU 1 Interface"] CPU2_Interface["CPU 2 Interface"] CPU3_Interface["CPU 3 Interface"] CPU0 --> GICD_SGIR CPU1 --> GICD_SGIR CPU2 --> GICD_SGIR CPU3 --> GICD_SGIR GICD_SGIR --> SGI_0_15
Sources: src/lib.rs(L12 - L16)
Private Peripheral Interrupts (PPI)
Private Peripheral Interrupts use interrupt IDs 16-31 and are generated by peripherals that are private to each individual processor core. Each CPU core has its own set of PPI interrupt sources.
Property | Value |
---|---|
Range Constant | PPI_RANGE |
ID Range | 16-31 (16 interrupts) |
Purpose | Private peripheral interrupts |
Scope | Single CPU core |
Examples | Private timers, PMU, virtual timer |
PPI Architecture and Core Association
flowchart TD subgraph subGraph2["PPI_RANGE [16-31]"] PPI_16["PPI ID 16"] PPI_17["PPI ID 17"] PPI_18["PPI ID 18"] PPI_Dots["..."] PPI_31["PPI ID 31"] end subgraph subGraph1["CPU Core 1"] Core1_Timer["Private Timer"] Core1_PMU["Performance Monitor"] Core1_VTimer["Virtual Timer"] Core1_Interface["CPU Interface 1"] end subgraph subGraph0["CPU Core 0"] Core0_Timer["Private Timer"] Core0_PMU["Performance Monitor"] Core0_VTimer["Virtual Timer"] Core0_Interface["CPU Interface 0"] end Core0_PMU --> PPI_17 Core0_Timer --> PPI_16 Core0_VTimer --> PPI_18 Core1_PMU --> PPI_17 Core1_Timer --> PPI_16 Core1_VTimer --> PPI_18
Sources: src/lib.rs(L18 - L21)
Shared Peripheral Interrupts (SPI)
Shared Peripheral Interrupts occupy the largest portion of the interrupt space, using IDs 32-1019. These interrupts can be routed to any CPU core and are typically generated by system-wide peripherals.
Property | Value |
---|---|
Range Constant | SPI_RANGE |
ID Range | 32-1019 (988 interrupts) |
Purpose | Shared peripheral interrupts |
Routing | Configurable to any CPU core |
Examples | UART, GPIO, DMA, network controllers |
SPI Routing and Distribution
flowchart TD subgraph subGraph3["CPU Cores"] CPU_0["CPU Core 0"] CPU_1["CPU Core 1"] CPU_2["CPU Core 2"] CPU_3["CPU Core 3"] end subgraph subGraph2["GicDistributor Routing"] ITARGETSR["ITARGETSR RegistersCPU target configuration"] ICFGR["ICFGR RegistersTrigger mode config"] end subgraph subGraph1["SPI_RANGE [32-1019]"] SPI_32["SPI ID 32"] SPI_33["SPI ID 33"] SPI_Dots["..."] SPI_1019["SPI ID 1019"] end subgraph subGraph0["Shared Peripherals"] UART["UART Controllers"] GPIO["GPIO Controllers"] DMA["DMA Controllers"] Network["Network Interfaces"] Storage["Storage Controllers"] end DMA --> SPI_Dots GPIO --> SPI_33 ITARGETSR --> CPU_0 ITARGETSR --> CPU_1 ITARGETSR --> CPU_2 ITARGETSR --> CPU_3 Network --> SPI_Dots SPI_Dots --> ITARGETSR Storage --> SPI_1019 UART --> SPI_32
Sources: src/lib.rs(L23 - L27)
Interrupt ID Translation
The translate_irq
function converts logical interrupt IDs (relative to each interrupt type) into physical GIC interrupt IDs (absolute addressing within the 1024-interrupt space).
Translation Logic and Mapping
flowchart TD subgraph subGraph2["Physical GIC IDs [Output]"] GIC_SGI["GIC ID 0-15"] GIC_PPI["GIC ID 16-31"] GIC_SPI["GIC ID 32-1019"] end subgraph subGraph1["translate_irq Function"] InterruptType_SGI_Match["InterruptType::SGIid < SGI_RANGE.end"] InterruptType_PPI_Match["InterruptType::PPIid + PPI_RANGE.start"] InterruptType_SPI_Match["InterruptType::SPIid + SPI_RANGE.start"] end subgraph subGraph0["Logical IDs [Input]"] SGI_Logical["SGI Logical ID0..15"] PPI_Logical["PPI Logical ID0..15"] SPI_Logical["SPI Logical ID0..987"] end InterruptType_PPI_Match --> GIC_PPI InterruptType_SGI_Match --> GIC_SGI InterruptType_SPI_Match --> GIC_SPI PPI_Logical --> InterruptType_PPI_Match SGI_Logical --> InterruptType_SGI_Match SPI_Logical --> InterruptType_SPI_Match
Translation Examples
Input | Interrupt Type | Calculation | Output |
---|---|---|---|
id=5 | InterruptType::SGI | id(direct mapping) | Some(5) |
id=3 | InterruptType::PPI | id + 16 | Some(19) |
id=10 | InterruptType::SPI | id + 32 | Some(42) |
id=20 | InterruptType::SGI | Invalid (≥16) | None |
Range Validation
The translation function includes bounds checking to ensure logical IDs are valid for their respective interrupt types:
- SGI: Logical ID must be
< SGI_RANGE.end
(16) - PPI: Logical ID must be
< PPI_RANGE.end - PPI_RANGE.start
(16) - SPI: Logical ID must be
< SPI_RANGE.end - SPI_RANGE.start
(988)
Sources: src/lib.rs(L65 - L90)
Implementation Constants
The interrupt ranges and limits are defined as compile-time constants in the crate's public API:
Constant | Value | Purpose |
---|---|---|
SGI_RANGE | 0..16 | Software-generated interrupt range |
PPI_RANGE | 16..32 | Private peripheral interrupt range |
SPI_RANGE | 32..1020 | Shared peripheral interrupt range |
GIC_MAX_IRQ | 1024 | Maximum interrupt capacity |
These constants are used throughout the crate for bounds checking, validation, and hardware register configuration.
Sources: src/lib.rs(L12 - L30)
Trigger Modes and Translation
Relevant source files
Purpose and Scope
This document explains the interrupt trigger mode configuration and interrupt ID translation mechanisms provided by the ARM GICv2 crate. It covers how the TriggerMode
enum defines edge and level triggering behavior, and how the translate_irq
function maps logical interrupt IDs to physical GIC interrupt IDs across different interrupt types.
For information about interrupt types and their ranges, see Interrupt Types and Ranges. For hardware implementation details, see Hardware Interface Implementation.
Trigger Modes
The GICv2 supports two fundamental interrupt trigger modes that determine how the interrupt controller responds to interrupt signals from peripheral devices.
Edge vs Level Triggering
flowchart TD subgraph TriggerMode::Edge["TriggerMode::Edge"] subgraph subGraph1["TriggerMode::Level"] LevelSignal["Signal Level Active"] LevelBehavior["Interrupt asserted while activeDeasserted when level inactiveFollows signal state"] EdgeSignal["Rising Edge Detection"] EdgeBehavior["Interrupt asserted on edgeRemains asserted until clearedIndependent of signal state"] end end EdgeSignal --> EdgeBehavior LevelSignal --> LevelBehavior
The TriggerMode
enum defines two triggering behaviors:
Mode | Value | Behavior |
---|---|---|
Edge | 0 | Asserted on rising edge detection, remains asserted until cleared |
Level | 1 | Asserted while signal level is active, deasserted when inactive |
Edge-triggered interrupts are suitable for event-based peripherals where the interrupt indicates that something has happened, such as a timer expiration or a button press. The interrupt remains active until software explicitly clears it.
Level-sensitive interrupts are appropriate for status-based peripherals where the interrupt indicates an ongoing condition, such as a UART receive buffer containing data or a device error state.
Sources: src/lib.rs(L32 - L46)
Interrupt ID Translation
The translate_irq
function provides a mapping layer between logical interrupt IDs and physical GIC interrupt IDs. This abstraction allows software to work with interrupt IDs relative to each interrupt type rather than absolute GIC interrupt IDs.
Translation Mechanism
flowchart TD subgraph subGraph2["Physical GIC Space"] PhysicalSGI["SGI: 0-15"] PhysicalPPI["PPI: 16-31"] PhysicalSPI["SPI: 32-1019"] end subgraph subGraph1["translate_irq Function"] TranslateFunc["translate_irq(id, int_type)"] end subgraph subGraph0["Logical Space"] LogicalSGI["SGI: 0-15"] LogicalPPI["PPI: 0-15"] LogicalSPI["SPI: 0-987"] end LogicalPPI --> TranslateFunc LogicalSGI --> TranslateFunc LogicalSPI --> TranslateFunc TranslateFunc --> PhysicalPPI TranslateFunc --> PhysicalSGI TranslateFunc --> PhysicalSPI
The translation function maps logical IDs to physical GIC interrupt IDs based on interrupt type:
Interrupt Type | Logical ID Range | Physical ID Range | Translation Formula |
---|---|---|---|
SGI | 0-15 | 0-15 | physical_id = logical_id |
PPI | 0-15 | 16-31 | physical_id = logical_id + 16 |
SPI | 0-987 | 32-1019 | physical_id = logical_id + 32 |
Implementation Logic
The translate_irq
function implements the following logic for each interrupt type:
SGI Translation: SGI interrupts maintain a direct 1:1 mapping between logical and physical IDs since they occupy the lowest interrupt ID range (0-15).
PPI Translation: PPI interrupts add an offset of 16 to map logical IDs 0-15 to physical IDs 16-31. The function validates that the logical ID is within the valid PPI range.
SPI Translation: SPI interrupts add an offset of 32 to map logical IDs 0-987 to physical IDs 32-1019. The function ensures the logical ID fits within the available SPI range.
Error Handling
The function returns Option<usize>
to handle invalid interrupt IDs gracefully. It returns None
when:
- SGI logical ID ≥ 16
- PPI logical ID ≥ 16
- SPI logical ID ≥ 988
flowchart TD Input["translate_irq(id, int_type)"] SGICheck["int_type == SGI"] PPICheck["int_type == PPI"] SPICheck["int_type == SPI"] SGIValid["id < 16?"] PPIValid["id < 16?"] SPIValid["id < 988?"] SGIResult["Some(id)"] PPIResult["Some(id + 16)"] SPIResult["Some(id + 32)"] ErrorResult["None"] Input --> SGICheck PPICheck --> PPIValid PPICheck --> SPICheck PPIValid --> ErrorResult PPIValid --> PPIResult SGICheck --> PPICheck SGICheck --> SGIValid SGIValid --> ErrorResult SGIValid --> SGIResult SPICheck --> SPIValid SPIValid --> ErrorResult SPIValid --> SPIResult
This validation ensures that only valid interrupt IDs are translated, preventing out-of-bounds access to GIC registers.
Sources: src/lib.rs(L65 - L90) src/lib.rs(L12 - L30)
Hardware Interface Implementation
Relevant source files
This document provides an overview of the two primary hardware abstraction components in the arm_gicv2 crate: GicDistributor
and GicCpuInterface
. These components provide type-safe, register-level access to ARM Generic Interrupt Controller v2 hardware through memory-mapped register interfaces.
For detailed interrupt classification and routing mechanisms, see Interrupt System Architecture. For implementation details of individual components, see GIC Distributor and GIC CPU Interface.
Core Hardware Abstraction Components
The arm_gicv2 crate implements hardware abstraction through two main structures that directly correspond to the physical GICv2 hardware blocks:
Component | Hardware Block | Primary Responsibility |
---|---|---|
GicDistributor | GIC Distributor (GICD) | System-wide interrupt configuration and routing |
GicCpuInterface | GIC CPU Interface (GICC) | Per-CPU interrupt handling and acknowledgment |
Both components are defined in src/gic_v2.rs(L96 - L116) and exported through the public API in src/lib.rs(L10)
GicDistributor Structure
The GicDistributor
manages system-wide interrupt configuration and provides the programming interface for:
- Globally enabling interrupt forwarding
- Configuring interrupt trigger modes (edge/level)
- Setting interrupt target processors
- Managing interrupt priority and grouping
- Generating software interrupts (SGIs)
GicCpuInterface Structure
The GicCpuInterface
handles per-CPU interrupt processing and provides the programming interface for:
- Acknowledging pending interrupts
- Signaling interrupt completion
- Setting priority masks
- Managing preemption policies
Sources: src/gic_v2.rs(L78 - L116) src/lib.rs(L10)
Hardware Abstraction Architecture
flowchart TD subgraph subGraph3["Physical Hardware"] GICD["GIC Distributor Hardware"] GICC["GIC CPU Interface Hardware"] end subgraph subGraph2["Register Access Layer"] TockRegisters["tock_registers traits"] Readable["Readable"] Writeable["Writeable"] ReadOnly["ReadOnly"] WriteOnly["WriteOnly"] end subgraph subGraph1["Hardware Abstraction Layer"] DistRegs["GicDistributorRegs"] CpuRegs["GicCpuInterfaceRegs"] DistBase["NonNull"] CpuBase["NonNull"] end subgraph subGraph0["arm_gicv2 Public API"] LibAPI["lib.rs exports"] GicDist["GicDistributor"] GicCpu["GicCpuInterface"] end CpuBase --> TockRegisters CpuRegs --> CpuBase DistBase --> TockRegisters DistRegs --> DistBase GicCpu --> CpuRegs GicDist --> DistRegs LibAPI --> GicCpu LibAPI --> GicDist ReadOnly --> GICC Readable --> GICD TockRegisters --> ReadOnly TockRegisters --> Readable TockRegisters --> WriteOnly TockRegisters --> Writeable WriteOnly --> GICC Writeable --> GICD
Sources: src/gic_v2.rs(L1 - L276) src/lib.rs(L8 - L10)
Register Interface Design
flowchart TD subgraph subGraph2["Memory Layout"] DistBase["base: NonNull"] CpuBase["base: NonNull"] end subgraph subGraph1["GicCpuInterfaceRegs Structure"] CCTLR["CTLR: ReadWrite"] CPMR["PMR: ReadWrite"] CBPR["BPR: ReadWrite"] CIAR["IAR: ReadOnly"] CEOIR["EOIR: WriteOnly"] CRPR["RPR: ReadOnly"] CHPPIR["HPPIR: ReadOnly"] CDIR["DIR: WriteOnly"] end subgraph subGraph0["GicDistributorRegs Structure"] DCTLR["CTLR: ReadWrite"] DTYPER["TYPER: ReadOnly"] DISENABLER["ISENABLER: [ReadWrite; 0x20]"] DICENABLER["ICENABLER: [ReadWrite; 0x20]"] DIPRIORITYR["IPRIORITYR: [ReadWrite; 0x100]"] DITARGETSR["ITARGETSR: [ReadWrite; 0x100]"] DICFGR["ICFGR: [ReadWrite; 0x40]"] DSGIR["SGIR: WriteOnly"] end CpuBase --> CCTLR CpuBase --> CEOIR CpuBase --> CIAR CpuBase --> CPMR DistBase --> DCTLR DistBase --> DICFGR DistBase --> DIPRIORITYR DistBase --> DISENABLER
The register structures are defined using the tock_registers::register_structs!
macro, which provides compile-time memory layout validation and type-safe register access patterns.
Sources: src/gic_v2.rs(L12 - L76)
Component Initialization and Lifecycle
Both hardware components follow a consistent initialization pattern:
Phase | GicDistributor | GicCpuInterface |
---|---|---|
Construction | new(base: *mut u8) | new(base: *mut u8) |
Initialization | init()- Disables all interrupts, configures SPIs | init()- Enables interface, unmasks priorities |
Runtime | Configuration and routing operations | Interrupt handling and acknowledgment |
The initialization sequence ensures that:
- All interrupts are initially disabled
- SPI interrupts are configured as edge-triggered by default
- SPI targets are set to CPU 0 in multi-CPU systems
- Hardware interfaces are enabled for operation
Sources: src/gic_v2.rs(L124 - L131) src/gic_v2.rs(L212 - L218) src/gic_v2.rs(L180 - L209) src/gic_v2.rs(L264 - L274)
Thread Safety and Send/Sync Implementation
Both components implement Send
and Sync
traits through explicit unsafe impl
blocks, enabling their use in multi-threaded environments:
unsafe impl Send for GicDistributor {}
unsafe impl Sync for GicDistributor {}
unsafe impl Send for GicCpuInterface {}
unsafe impl Sync for GicCpuInterface {}
This design acknowledges that while the underlying hardware registers require careful coordination, the abstraction layer itself can be safely shared across thread boundaries when properly synchronized at the application level.
Sources: src/gic_v2.rs(L118 - L122)
GIC Distributor
Relevant source files
The GIC Distributor provides system-wide interrupt configuration and routing capabilities for the ARM GICv2 interrupt controller. This component manages interrupt prioritization, distribution to CPU interfaces, and global interrupt settings across all processors in the system.
For per-CPU interrupt handling and acknowledgment, see GIC CPU Interface. For background on interrupt types and ranges, see Interrupt Types and Ranges.
Register Structure and Memory Layout
The GIC Distributor is accessed through a memory-mapped register interface defined by the GicDistributorRegs
structure. This provides type-safe access to all distributor control registers.
Distributor Register Layout
Sources: src/gic_v2.rs(L12 - L48)
Core Implementation Structure
The GicDistributor
struct provides the main interface for distributor operations, encapsulating the register base address and maximum interrupt count.
Distributor Component Architecture
flowchart TD subgraph subGraph5["GicDistributor Implementation"] Struct["GicDistributorbase: NonNull<GicDistributorRegs>max_irqs: usize"] subgraph subGraph4["Register Access"] Regs["regs()→ &GicDistributorRegs"] end subgraph Initialization["Initialization"] Init["init()"] end subgraph subGraph2["Configuration Methods"] ConfigureInterrupt["configure_interrupt(vector: usize,tm: TriggerMode)"] SetEnable["set_enable(vector: usize,enable: bool)"] end subgraph subGraph1["Information Methods"] CpuNum["cpu_num()→ usize"] MaxIrqs["max_irqs()→ usize"] end subgraph subGraph0["Constructor Methods"] New["new(base: *mut u8)→ Self"] end end Regs --> ConfigureInterrupt Regs --> CpuNum Regs --> Init Regs --> MaxIrqs Regs --> SetEnable Struct --> ConfigureInterrupt Struct --> CpuNum Struct --> Init Struct --> MaxIrqs Struct --> New Struct --> Regs Struct --> SetEnable
Sources: src/gic_v2.rs(L96 - L210)
Initialization Process
The distributor initialization follows a specific sequence to establish a known state and configure default interrupt routing.
Initialization Sequence
Step | Operation | Register(s) | Purpose |
---|---|---|---|
1 | Disable all interrupts | ICENABLER[*] | Clear existing enables |
2 | Clear pending interrupts | ICPENDR[*] | Reset pending state |
3 | Set SPI targets to CPU 0 | ITARGETSR[*] | Default routing |
4 | Configure SPIs as edge-triggered | ICFGR[*] | Set trigger mode |
5 | Enable distributor | CTLR | Activate GICD |
flowchart TD Start["init() called"] ReadMaxIrqs["Read max_irqs from TYPER registerAssert ≤ GIC_MAX_IRQ"] DisableAll["Disable all interruptsICENABLER[i] = 0xFFFFFFFFfor i in 0..max_irqs/32"] ClearPending["Clear pending interruptsICPENDR[i] = 0xFFFFFFFFfor i in 0..max_irqs/32"] CheckMultiCpu["cpu_num() > 1?"] SetTargets["Set SPI targets to CPU 0ITARGETSR[i] = 0x01010101for i in SPI_RANGE"] ConfigEdge["Configure SPIs as edge-triggeredconfigure_interrupt(i, TriggerMode::Edge)for i in SPI_RANGE"] EnableGICD["Enable distributorCTLR = 1"] End["Initialization complete"] CheckMultiCpu --> ConfigEdge CheckMultiCpu --> SetTargets ClearPending --> CheckMultiCpu ConfigEdge --> EnableGICD DisableAll --> ClearPending EnableGICD --> End ReadMaxIrqs --> DisableAll SetTargets --> ConfigEdge Start --> ReadMaxIrqs
Sources: src/gic_v2.rs(L186 - L209)
Interrupt Configuration
The distributor provides methods to configure interrupt properties including trigger modes and enable/disable states.
Trigger Mode Configuration
The configure_interrupt
method sets the trigger mode for SPI interrupts using the ICFGR
registers:
flowchart TD subgraph subGraph0["configure_interrupt Method"] Input["vector: usizetm: TriggerMode"] Validate["Validate vector≥ SPI_RANGE.start< max_irqs"] CalcReg["Calculate register indexreg_idx = vector >> 4bit_shift = ((vector & 0xf) << 1) + 1"] ReadReg["Read ICFGR[reg_idx]"] ModifyBit["Modify trigger bitEdge: set bitLevel: clear bit"] WriteReg["Write ICFGR[reg_idx]"] end CalcReg --> ReadReg Input --> Validate ModifyBit --> WriteReg ReadReg --> ModifyBit Validate --> CalcReg
Enable/Disable Control
The set_enable
method controls interrupt enable state using dedicated enable/disable registers:
Operation | Register Used | Bit Effect |
---|---|---|
Enable | ISENABLER[reg] | Set bit atvector % 32 |
Disable | ICENABLER[reg] | Set bit atvector % 32 |
Where reg = vector / 32
.
Sources: src/gic_v2.rs(L147 - L178)
System Information Interface
The distributor provides methods to query system configuration determined by hardware implementation.
Hardware Detection Methods
flowchart TD subgraph subGraph0["System Information"] TYPER_Reg["TYPER Register"] CpuNumCalc["cpu_num()((TYPER >> 5) & 0b111) + 1"] MaxIrqsCalc["max_irqs()((TYPER & 0b11111) + 1) * 32"] CpuCount["Number of CPU interfaces"] IrqCount["Maximum interrupt count"] end CpuNumCalc --> CpuCount MaxIrqsCalc --> IrqCount TYPER_Reg --> CpuNumCalc TYPER_Reg --> MaxIrqsCalc
Sources: src/gic_v2.rs(L138 - L145)
Thread Safety and Memory Access
The GicDistributor
implements Send
and Sync
traits for safe concurrent access across threads, with register access performed through unsafe memory operations wrapped in safe interfaces.
The register access uses NonNull<GicDistributorRegs>
to ensure the base pointer is always valid and uses the tock-registers crate for type-safe memory-mapped I/O operations.
Sources: src/gic_v2.rs(L5 - L135)
GIC CPU Interface
Relevant source files
This document covers the GicCpuInterface
implementation, which provides per-CPU interrupt handling functionality for ARM GICv2 controllers. The CPU interface handles interrupt acknowledgment, completion signaling, priority masking, and preemption control for individual processor cores.
For system-wide interrupt distribution and configuration, see GIC Distributor. For the overall interrupt classification system, see Interrupt System Architecture.
CPU Interface Register Structure
The GicCpuInterfaceRegs
struct defines the memory-mapped register layout for the CPU interface, providing type-safe access to hardware registers that control per-CPU interrupt processing.
flowchart TD subgraph subGraph1["Register Types"] RW["ReadWrite"] RO["ReadOnly"] WO["WriteOnly"] end subgraph subGraph0["GicCpuInterfaceRegs Structure"] CTLR["CTLR @ 0x0000CPU Interface Control"] PMR["PMR @ 0x0004Priority Mask Register"] BPR["BPR @ 0x0008Binary Point Register"] IAR["IAR @ 0x000cInterrupt Acknowledge"] EOIR["EOIR @ 0x0010End of Interrupt"] RPR["RPR @ 0x0014Running Priority"] HPPIR["HPPIR @ 0x0018Highest Priority Pending"] IIDR["IIDR @ 0x00fcInterface Identification"] DIR["DIR @ 0x1000Deactivate Interrupt"] end BPR --> RW CTLR --> RW DIR --> WO EOIR --> WO HPPIR --> RO IAR --> RO IIDR --> RO PMR --> RW RPR --> RO
Sources: src/gic_v2.rs(L50 - L76)
Core Interface Implementation
The GicCpuInterface
struct wraps the register structure and provides safe methods for interrupt handling operations.
Component | Type | Purpose |
---|---|---|
base | NonNull | Pointer to memory-mapped registers |
Thread Safety | Send + Sync | Safe for multi-threaded access |
flowchart TD subgraph subGraph1["Register Operations"] IAR_read["IAR.get()"] EOIR_write["EOIR.set(iar)"] CTLR_enable["CTLR.set(1)"] PMR_unmask["PMR.set(0xff)"] end subgraph subGraph0["GicCpuInterface Methods"] new["new(base: *mut u8)"] regs["regs() -> &GicCpuInterfaceRegs"] iar["iar() -> u32"] eoi["eoi(iar: u32)"] handle_irq["handle_irq(handler: F)"] init["init()"] end eoi --> EOIR_write handle_irq --> eoi handle_irq --> iar iar --> IAR_read init --> CTLR_enable init --> PMR_unmask new --> regs
Sources: src/gic_v2.rs(L114 - L116) src/gic_v2.rs(L214 - L222)
Interrupt Acknowledgment and Completion
The CPU interface provides a two-phase interrupt handling protocol: acknowledgment via iar()
and completion via eoi()
.
Interrupt Acknowledge Register (IAR)
The iar()
method reads the GICC_IAR
register to obtain the interrupt ID of the highest priority pending interrupt. This operation atomically acknowledges the interrupt and changes its state from pending to active.
#![allow(unused)] fn main() { pub fn iar(&self) -> u32 { self.regs().IAR.get() } }
Spurious Interrupt Handling: The method returns interrupt ID 1023
when no valid interrupt is pending, the distributor is disabled, or the CPU interface is disabled.
Sources: src/gic_v2.rs(L230 - L232)
End of Interrupt (EOI)
The eoi()
method writes to the GICC_EOIR
register to signal completion of interrupt processing. The value written must match the value previously read from iar()
.
#![allow(unused)] fn main() { pub fn eoi(&self, iar: u32) { self.regs().EOIR.set(iar); } }
Sources: src/gic_v2.rs(L238 - L240)
High-Level Interrupt Handling
The handle_irq()
method provides a complete interrupt handling flow that combines acknowledgment, handler execution, and completion in a single operation.
flowchart TD start["handle_irq() called"] read_iar["Read IAR registeriar = self.iar()"] extract_vector["Extract vector IDvector = iar & 0x3ff"] check_spurious["vector < 1020?"] call_handler["Call handler(vector)"] write_eoir["Write EOIR registerself.eoi(iar)"] spurious["Ignore spurious interrupt"] end_flow["Return"] call_handler --> write_eoir check_spurious --> call_handler check_spurious --> spurious extract_vector --> check_spurious read_iar --> extract_vector spurious --> end_flow start --> read_iar write_eoir --> end_flow
The method masks the interrupt ID to 10 bits (iar & 0x3ff
) to extract the vector number, as the upper bits of the IAR register contain additional status information.
Sources: src/gic_v2.rs(L250 - L262)
CPU Interface Initialization
The init()
method configures the CPU interface for operation by enabling interrupt delivery and setting priority masking.
flowchart TD subgraph subGraph1["Register Effects"] ctrl_effect["Enables interrupt signalingto the processor"] pmr_effect["Allows interrupts atall priority levels"] end subgraph subGraph0["init() Operations"] enable_ctrl["Enable CPU InterfaceCTLR.set(1)"] unmask_prio["Unmask All PrioritiesPMR.set(0xff)"] end enable_ctrl --> ctrl_effect unmask_prio --> pmr_effect
Initialization Requirements:
- Must be called exactly once per CPU
- Should be called after distributor initialization
- Priority mask value
0xff
represents the lowest priority threshold, allowing all interrupts
Sources: src/gic_v2.rs(L269 - L275)
Thread Safety and Memory Management
The GicCpuInterface
implements Send
and Sync
traits, enabling safe usage across thread boundaries in multi-core systems.
Safety Aspect | Implementation |
---|---|
Memory Safety | NonNull |
Thread Safety | unsafe impl Send + Syncallows cross-thread usage |
Register Access | const fn regs()provides immutable reference to registers |
Hardware Synchronization | Hardware ensures atomic register operations |
The unsafe
implementations of Send
and Sync
are justified because:
- Hardware registers are designed for concurrent access from multiple CPUs
- Each CPU interface instance manages a separate set of memory-mapped registers
- Register operations are atomic at the hardware level
Sources: src/gic_v2.rs(L121 - L122)
Development Guide
Relevant source files
This page provides an overview of the development workflow, tools, and processes for contributing to the arm_gicv2
crate. It covers the essential information developers need to understand the build system, quality assurance processes, and deployment pipeline.
For detailed information about specific aspects of development, see the following sub-sections:
- Build system configuration and dependency management: Build System and Dependencies
- Automated testing and deployment processes: CI/CD Pipeline
- Setting up your local development environment: Development Environment
Development Overview
The arm_gicv2
crate follows modern Rust development practices with automated quality assurance and documentation deployment. The development workflow centers around a no_std
compatible library that provides hardware abstraction for ARM Generic Interrupt Controller v2 across multiple target architectures.
Core Development Tools and Dependencies
The development ecosystem relies on several key components that work together to ensure code quality and compatibility:
Development Tool Chain
flowchart TD subgraph subGraph3["Package Configuration"] PackageName["arm_gicv2"] Version["0.1.0"] License["GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"] Categories["embedded, no-std, hardware-support, os"] end subgraph subGraph2["Target Architectures"] Linux["x86_64-unknown-linux-gnu"] Bare["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] AArch64["aarch64-unknown-none-softfloat"] end subgraph subGraph1["Quality Tools"] Rustfmt["rustfmt"] Clippy["clippy"] Rustdoc["rustdoc"] Tests["cargo test"] end subgraph subGraph0["Core Tools"] Rust["rustc nightly"] Cargo["cargo build system"] TockRegs["tock-registers 0.8"] end Cargo --> AArch64 Cargo --> Bare Cargo --> Clippy Cargo --> Linux Cargo --> RISCV Cargo --> Rustdoc Cargo --> Rustfmt Cargo --> Tests Categories --> Cargo License --> Cargo PackageName --> Cargo Rust --> Cargo TockRegs --> Cargo Version --> Cargo
Sources: Cargo.toml(L1 - L16) .github/workflows/ci.yml(L11 - L19)
Automated Development Lifecycle
The project implements a comprehensive CI/CD pipeline that ensures code quality and deploys documentation automatically:
CI/CD Workflow
flowchart TD subgraph subGraph4["Environment Variables"] DefaultBranch["default-branch"] RustDocFlags["RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs"] end subgraph Documentation["Documentation"] DocBuild["cargo doc --no-deps --all-features"] IndexGen["printf redirect to index.html"] GHPagesDeploy["JamesIves/github-pages-deploy-action@v4"] end subgraph subGraph2["Quality Checks"] VersionCheck["rustc --version --verbose"] FormatCheck["cargo fmt --all -- --check"] ClippyCheck["cargo clippy --target TARGET --all-features"] BuildCheck["cargo build --target TARGET --all-features"] UnitTest["cargo test --target x86_64-unknown-linux-gnu"] end subgraph subGraph1["CI Job Matrix"] Toolchain["rust-toolchain: nightly"] Target1["x86_64-unknown-linux-gnu"] Target2["x86_64-unknown-none"] Target3["riscv64gc-unknown-none-elf"] Target4["aarch64-unknown-none-softfloat"] end subgraph subGraph0["Trigger Events"] Push["git push"] PR["pull_request"] end BuildCheck --> UnitTest ClippyCheck --> BuildCheck DefaultBranch --> GHPagesDeploy DocBuild --> IndexGen FormatCheck --> ClippyCheck IndexGen --> GHPagesDeploy PR --> Toolchain Push --> Toolchain RustDocFlags --> DocBuild Target1 --> DocBuild Target1 --> VersionCheck Toolchain --> Target1 Toolchain --> Target2 Toolchain --> Target3 Toolchain --> Target4 VersionCheck --> FormatCheck
Sources: .github/workflows/ci.yml(L1 - L56)
Package Metadata and Configuration
The crate is configured as a library package with specific metadata that defines its purpose and compatibility:
Configuration | Value | Purpose |
---|---|---|
name | arm_gicv2 | Crate identifier for Cargo registry |
version | 0.1.0 | Semantic versioning for API compatibility |
edition | 2021 | Rust language edition features |
authors | Yuekai Jia equation618@gmail.com | Primary maintainer contact |
description | ARM Generic Interrupt Controller version 2 (GICv2) register definitions and basic operations | Crate purpose summary |
license | GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0 | Multi-license compatibility |
keywords | arceos, arm, aarch64, gic, interrupt-controller | Discovery and categorization |
categories | embedded, no-std, hardware-support, os | Cargo registry classification |
Sources: Cargo.toml(L1 - L12)
Key Development Characteristics
No-std Compatibility
The crate is designed for no_std
environments, making it suitable for bare-metal development, embedded systems, and operating system kernels. This is reflected in the target architecture support and category classifications.
Multi-target Support
Development and testing occur across four distinct target architectures:
x86_64-unknown-linux-gnu
: Standard Linux development and testingx86_64-unknown-none
: Bare-metal x86_64 systemsriscv64gc-unknown-none-elf
: RISC-V 64-bit bare-metal systemsaarch64-unknown-none-softfloat
: ARM64 bare-metal with software floating point
Single Dependency
The crate maintains minimal external dependencies with only tock-registers = "0.8"
providing type-safe register access abstractions. This design choice supports the embedded and bare-metal use cases where dependency minimization is critical.
Sources: Cargo.toml(L14 - L15) .github/workflows/ci.yml(L12)
Build System and Dependencies
Relevant source files
This page covers the build system configuration, dependency management, and target architecture support for the arm_gicv2
crate. It explains the Cargo package configuration, the tock-registers
dependency choice, no_std
compatibility requirements, and multi-target build support that enables the crate to work across embedded systems and operating system development environments.
For information about the CI/CD automation and testing infrastructure, see CI/CD Pipeline. For development environment setup and contribution workflows, see Development Environment.
Package Configuration
The arm_gicv2
crate is configured as a Rust library package that targets embedded systems and operating system development. The package metadata defines its purpose as providing ARM Generic Interrupt Controller version 2 register definitions and basic operations.
Package Metadata Structure
flowchart TD subgraph subGraph2["Legal and Distribution"] license["license: GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"] homepage["homepage: github.com/arceos-org/arceos"] repository["repository: github.com/arceos-org/arm_gicv2"] documentation["documentation: docs.rs/arm_gicv2"] end subgraph subGraph1["Package Description"] description["description: ARM GICv2 register definitions and operations"] keywords["keywords: arceos, arm, aarch64, gic, interrupt-controller"] categories["categories: embedded, no-std, hardware-support, os"] end subgraph subGraph0["Package Identity"] name["name: arm_gicv2"] version["version: 0.1.0"] edition["edition: 2021"] authors["authors: Yuekai Jia"] end categories --> documentation description --> license edition --> categories keywords --> repository name --> description version --> keywords
Sources: Cargo.toml(L1 - L12)
The package uses Rust edition 2021, which provides modern language features while maintaining compatibility with embedded and no_std
environments. The triple licensing approach (GPL-3.0-or-later, Apache-2.0, MulanPSL-2.0) ensures broad compatibility with different project licensing requirements.
Target Categories and Keywords
The crate is categorized for four primary use cases:
Category | Purpose |
---|---|
embedded | IoT devices and microcontroller applications |
no-std | Environments without standard library support |
hardware-support | Low-level hardware abstraction and drivers |
os | Operating system kernel and hypervisor development |
Sources: Cargo.toml(L12)
Dependency Management
The crate has a minimal dependency footprint, using only the tock-registers
crate for type-safe register access. This design choice prioritizes compatibility with resource-constrained environments while providing safe hardware register manipulation.
Core Dependency: tock-registers
flowchart TD subgraph subGraph2["Hardware Registers"] hw_dist["Distributor Registers"] hw_cpu["CPU Interface Registers"] end subgraph subGraph1["tock-registers 0.8"] reg_types["Register Types"] read_write["ReadWrite"] read_only["ReadOnly"] write_only["WriteOnly"] local_reg["LocalRegisterCopy"] end subgraph subGraph0["arm_gicv2 Crate"] gicv2["arm_gicv2"] gic_dist["GicDistributor"] gic_cpu["GicCpuInterface"] dist_regs["GicDistributorRegs"] cpu_regs["GicCpuInterfaceRegs"] end cpu_regs --> local_reg cpu_regs --> read_write cpu_regs --> write_only dist_regs --> read_only dist_regs --> read_write gic_cpu --> cpu_regs gic_dist --> dist_regs gicv2 --> gic_cpu gicv2 --> gic_dist local_reg --> hw_cpu read_only --> hw_dist read_write --> hw_cpu read_write --> hw_dist write_only --> hw_cpu
Sources: Cargo.toml(L15)
The tock-registers
dependency provides:
- Type-safe register field access with compile-time guarantees
- Memory-mapped register abstractions that prevent undefined behavior
- Zero-cost abstractions that compile to direct memory operations
- Support for read-only, write-only, and read-write register semantics
Dependency Version Strategy
The crate pins tock-registers
to version 0.8
, ensuring API stability while allowing patch-level updates. This approach balances security updates with build reproducibility across different development environments.
Sources: Cargo.toml(L14 - L15)
Target Architecture Support
The build system supports multiple target architectures to enable deployment across different ARM-based systems and development environments. The no_std
compatibility ensures the crate works in resource-constrained embedded environments.
Supported Target Architectures
flowchart TD subgraph subGraph2["Use Cases"] development["Development Testing"] embedded["Embedded Systems"] os_dev["OS Development"] hypervisor["Hypervisor Development"] end subgraph subGraph1["Runtime Environments"] hosted["Hosted EnvironmentStandard library available"] bare_metal["Bare-metal EnvironmentNo standard library"] cross_arch["Cross-architecture TestingAlgorithm verification"] end subgraph subGraph0["Build Targets"] x86_linux["x86_64-unknown-linux-gnuDevelopment and testing"] x86_none["x86_64-unknown-noneBare-metal x86 systems"] riscv["riscv64gc-unknown-none-elfRISC-V bare-metal"] aarch64["aarch64-unknown-none-softfloatARM64 bare-metal"] end aarch64 --> bare_metal bare_metal --> embedded bare_metal --> hypervisor bare_metal --> os_dev development --> cross_arch embedded --> cross_arch hosted --> development riscv --> bare_metal x86_linux --> hosted x86_none --> bare_metal
Sources: Referenced from high-level architecture diagrams
The multi-target support enables:
- Development targets: Linux-based development and unit testing
- Bare-metal targets: Direct hardware deployment without operating system
- Cross-architecture validation: Algorithm correctness verification across platforms
no_std Compatibility Requirements
The crate maintains no_std
compatibility through:
Requirement | Implementation |
---|---|
Core library only | No standard library dependencies |
Minimal allocations | Stack-based data structures only |
Hardware-direct access | Memory-mapped register operations |
Deterministic behavior | No dynamic memory allocation |
This design ensures the crate works in interrupt handlers, kernel space, and resource-constrained embedded environments where the standard library is unavailable or inappropriate.
Build Configuration
The build system uses Cargo's default configuration with specific optimizations for embedded and systems programming use cases.
Build Process Flow
flowchart TD subgraph Artifacts["Artifacts"] lib_crate["Library crate (.rlib)"] doc_artifacts["Documentation artifacts"] metadata["Crate metadata"] end subgraph subGraph2["Compilation Process"] target_select["Select target architecture"] no_std_build["Compile with no_std"] register_codegen["Generate register access code"] optimize["Apply target optimizations"] end subgraph subGraph1["Dependency Resolution"] tock_fetch["Fetch tock-registers 0.8"] dep_check["Verify no_std compatibility"] version_lock["Lock dependency versions"] end subgraph subGraph0["Source Organization"] cargo_toml["Cargo.tomlPackage configuration"] src_lib["src/lib.rsPublic API definitions"] src_gic["src/gic_v2.rsHardware implementation"] end cargo_toml --> tock_fetch dep_check --> no_std_build no_std_build --> lib_crate optimize --> metadata register_codegen --> doc_artifacts src_gic --> version_lock src_lib --> dep_check target_select --> optimize tock_fetch --> target_select version_lock --> register_codegen
Sources: Cargo.toml(L1 - L16) .gitignore(L1 - L4)
Development Artifacts Management
The .gitignore
configuration excludes build artifacts and development-specific files:
Ignored Path | Purpose |
---|---|
/target | Cargo build artifacts and intermediate files |
/.vscode | Visual Studio Code workspace configuration |
.DS_Store | macOS file system metadata |
Cargo.lock | Dependency lock file (excluded for library crates) |
Sources: .gitignore(L1 - L4)
The exclusion of Cargo.lock
follows Rust library conventions, allowing downstream projects to resolve their own dependency versions while maintaining compatibility with the specified version ranges.
CI/CD Pipeline
Relevant source files
This document describes the automated continuous integration and continuous deployment (CI/CD) pipeline for the arm_gicv2 crate. The pipeline is implemented using GitHub Actions and handles code quality assurance, multi-target builds, testing, and automated documentation deployment.
For information about the build system configuration and dependencies, see Build System and Dependencies. For development environment setup, see Development Environment.
Pipeline Overview
The CI/CD pipeline consists of two primary workflows that execute on every push and pull request, ensuring code quality and maintaining up-to-date documentation.
flowchart TD subgraph subGraph3["Doc Job Outputs"] DocBuild["API documentation"] Deploy["GitHub Pages deployment"] end subgraph subGraph2["CI Job Outputs"] Build["Multi-target builds"] Tests["Unit tests"] Quality["Code quality checks"] end subgraph subGraph1["GitHub Actions Workflows"] CI["ci job"] DOC["doc job"] end subgraph subGraph0["Trigger Events"] Push["push"] PR["pull_request"] end CI --> Build CI --> Quality CI --> Tests DOC --> Deploy DOC --> DocBuild PR --> CI PR --> DOC Push --> CI Push --> DOC
CI/CD Pipeline Overview
Sources: .github/workflows/ci.yml(L1 - L56)
CI Job Configuration
The main ci
job runs quality assurance checks and builds across multiple target architectures using a matrix strategy.
Matrix Strategy
The pipeline uses a fail-fast matrix configuration to test across multiple target platforms:
Target Architecture | Purpose |
---|---|
x86_64-unknown-linux-gnu | Standard Linux development and testing |
x86_64-unknown-none | Bare-metal x86_64 environments |
riscv64gc-unknown-none-elf | RISC-V embedded systems |
aarch64-unknown-none-softfloat | ARM64 embedded systems |
flowchart TD subgraph subGraph2["CI Steps for Each Target"] Setup["actions/checkout@v4dtolnay/rust-toolchain@nightly"] Format["cargo fmt --all -- --check"] Lint["cargo clippy --target TARGET --all-features"] Build["cargo build --target TARGET --all-features"] Test["cargo test --target TARGET"] end subgraph subGraph1["Matrix Strategy"] Toolchain["nightly toolchain"] subgraph subGraph0["Target Architectures"] X86Linux["x86_64-unknown-linux-gnu"] X86None["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] ARM64["aarch64-unknown-none-softfloat"] end end ARM64 --> Setup Build --> Test Format --> Lint Lint --> Build RISCV --> Setup Setup --> Format Toolchain --> ARM64 Toolchain --> RISCV Toolchain --> X86Linux Toolchain --> X86None X86Linux --> Setup X86None --> Setup
CI Job Matrix and Execution Flow
Sources: .github/workflows/ci.yml(L6 - L30)
Quality Assurance Steps
The CI pipeline enforces code quality through multiple automated checks:
- Code Formatting: Uses
cargo fmt
with the--check
flag to ensure consistent code formatting - Linting: Runs
cargo clippy
with all features enabled, specifically allowing theclippy::new_without_default
lint - Building: Compiles the crate for each target architecture with all features enabled
- Testing: Executes unit tests, but only for the
x86_64-unknown-linux-gnu
target
flowchart TD subgraph subGraph1["Failure Conditions"] FormatFail["Format violations"] ClippyFail["Lint violations"] BuildFail["Compilation errors"] TestFail["Test failures"] end subgraph subGraph0["Quality Gates"] FormatCheck["cargo fmt --all -- --check"] ClippyCheck["cargo clippy --target TARGET --all-features-- -A clippy::new_without_default"] BuildCheck["cargo build --target TARGET --all-features"] TestCheck["cargo test --target TARGET(Linux only)"] end BuildCheck --> BuildFail BuildCheck --> TestCheck ClippyCheck --> BuildCheck ClippyCheck --> ClippyFail FormatCheck --> ClippyCheck FormatCheck --> FormatFail TestCheck --> TestFail
Quality Assurance Gate Sequence
Sources: .github/workflows/ci.yml(L22 - L30)
Documentation Job
The doc
job handles API documentation generation and deployment to GitHub Pages.
Documentation Build Process
The documentation build process includes strict documentation standards and automated deployment:
flowchart TD subgraph subGraph2["Deployment Logic"] BranchCheck["Check if default branch"] Deploy["JamesIves/github-pages-deploy-action@v4"] GHPages["Deploy to gh-pages branch"] end subgraph subGraph1["Build Process"] DocBuild["cargo doc --no-deps --all-features"] IndexGen["Generate index.html redirect"] TreeExtract["Extract crate name from cargo tree"] end subgraph subGraph0["Documentation Environment"] DocEnv["RUSTDOCFLAGS:-D rustdoc::broken_intra_doc_links-D missing-docs"] Checkout["actions/checkout@v4"] Toolchain["dtolnay/rust-toolchain@nightly"] end BranchCheck --> Deploy Checkout --> Toolchain Deploy --> GHPages DocBuild --> IndexGen DocEnv --> Checkout IndexGen --> TreeExtract Toolchain --> DocBuild TreeExtract --> BranchCheck
Documentation Build and Deployment Flow
Sources: .github/workflows/ci.yml(L32 - L56)
Documentation Standards
The pipeline enforces strict documentation requirements through RUSTDOCFLAGS
:
- Broken Links:
-D rustdoc::broken_intra_doc_links
treats broken documentation links as errors - Missing Documentation:
-D missing-docs
requires all public items to have documentation
Deployment Configuration
Documentation deployment occurs automatically under specific conditions:
- Branch Condition: Only deploys when the push is to the default branch
- Target Branch: Deploys to the
gh-pages
branch - Single Commit: Uses
single-commit: true
to maintain a clean deployment history - Source Folder: Deploys the
target/doc
directory containing generated documentation
Sources: .github/workflows/ci.yml(L38 - L55)
Toolchain and Component Configuration
The pipeline uses the Rust nightly toolchain with specific components required for the complete CI/CD process:
Component | Purpose |
---|---|
rust-src | Source code for cross-compilation |
clippy | Linting and code analysis |
rustfmt | Code formatting |
The toolchain setup includes automatic target installation for all matrix targets, ensuring consistent build environments across all supported architectures.
Sources: .github/workflows/ci.yml(L15 - L19)
Development Environment
Relevant source files
This document covers setting up and configuring a local development environment for contributing to the arm_gicv2 crate. It focuses on developer tooling, project organization, and recommended workflows for contributors.
For information about the build system configuration and dependency management, see Build System and Dependencies. For details about the automated CI/CD pipeline, see CI/CD Pipeline.
Local Development Setup
The arm_gicv2 crate requires specific toolchain configurations to support its target embedded systems. The development environment must accommodate multiple target architectures and maintain compatibility with no_std
environments.
Required Toolchain Components
The project uses Rust nightly toolchain with specific components as defined in the CI configuration:
Component | Purpose | Usage Context |
---|---|---|
rust-src | Source code for cross-compilation | Required forno_stdtargets |
clippy | Linting and code analysis | Code quality enforcement |
rustfmt | Code formatting | Style consistency |
Development Workflow Diagram
flowchart TD Dev["Developer"] Setup["Local Setup"] Toolchain["rustup toolchain install nightly"] Components["rustup component add rust-src clippy rustfmt"] Targets["rustup target add"] Format["cargo fmt --check"] Lint["cargo clippy"] Build["cargo build --target"] Test["cargo test (Linux only)"] Commit["git commit"] Push["git push"] CI["GitHub Actions CI"] Build --> Commit Commit --> Push Components --> Build Components --> Format Components --> Lint Components --> Test Dev --> Setup Format --> Commit Lint --> Commit Push --> CI Setup --> Components Setup --> Targets Setup --> Toolchain Test --> Commit
Sources: .github/workflows/ci.yml(L15 - L19)
Target Architecture Support
The development environment must support multiple target architectures for cross-compilation testing:
flowchart TD subgraph subGraph1["Development Commands"] Check["cargo check --target"] Build["cargo build --target"] Clippy["cargo clippy --target"] Test["cargo test --target"] end subgraph subGraph0["Development Targets"] Linux["x86_64-unknown-linux-gnuTesting & Development"] Bare["x86_64-unknown-noneBare Metal x86"] RISC["riscv64gc-unknown-none-elfRISC-V Embedded"] ARM["aarch64-unknown-none-softfloatARM64 Embedded"] end ARM --> Build ARM --> Check ARM --> Clippy Bare --> Build Bare --> Check Bare --> Clippy Linux --> Build Linux --> Check Linux --> Clippy Linux --> Test RISC --> Build RISC --> Check RISC --> Clippy
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L25 - L30)
Development Tools and Editor Configuration
Editor Support
The project includes VS Code in its gitignore patterns, indicating that some developers use VS Code but configuration is not standardized across the team.
File Organization and Ignored Paths
flowchart TD subgraph subGraph2["Development Actions"] Src["src/Source code"] Cargo["Cargo.tomlProject manifest"] CI[".github/CI configuration"] Git[".gitignoreIgnore patterns"] Build["cargo build"] Editor["VS Code setup"] macOS["macOS development"] Install["cargo install"] end subgraph subGraph1["Ignored Files (.gitignore)"] Target["/targetBuild artifacts"] VSCode["/.vscodeEditor configuration"] DS[".DS_StoremacOS file metadata"] Lock["Cargo.lockDependency lockfile"] end subgraph subGraph0["Tracked Files"] Src["src/Source code"] Cargo["Cargo.tomlProject manifest"] CI[".github/CI configuration"] Git[".gitignoreIgnore patterns"] README["README.mdDocumentation"] Build["cargo build"] Editor["VS Code setup"] macOS["macOS development"] Install["cargo install"] end Build --> Target Editor --> VSCode Install --> Lock macOS --> DS
Sources: .gitignore(L1 - L4)
Ignored Development Artifacts
The .gitignore
configuration reveals important aspects of the development environment:
Pattern | Purpose | Rationale |
---|---|---|
/target | Rust build artifacts | Generated bycargo buildcommands |
/.vscode | VS Code workspace settings | Developer-specific editor configuration |
.DS_Store | macOS file system metadata | Platform-specific system files |
Cargo.lock | Dependency version lockfile | Library crate - consumers control versions |
The exclusion of Cargo.lock
is particularly significant as it indicates this is a library crate where downstream consumers should control dependency versions rather than the library itself.
Sources: .gitignore(L1 - L4)
Quality Assurance Integration
Local Development Commands
The development environment should replicate CI checks locally before committing:
# Format check (matches CI)
cargo fmt --all -- --check
# Linting (matches CI with specific allowances)
cargo clippy --target <target> --all-features -- -A clippy::new_without_default
# Build verification
cargo build --target <target> --all-features
# Unit testing (Linux target only)
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
Documentation Development
Local documentation building follows the same process as CI deployment:
# Build documentation with strict link checking
RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs" cargo doc --no-deps --all-features
Sources: .github/workflows/ci.yml(L23 - L30) .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L47)
Contribution Workflow
Pre-commit Verification
Before submitting changes, developers should verify their code passes all CI checks locally:
Local Testing Workflow
flowchart TD Change["Code Changes"] Format["cargo fmt --all"] FormatCheck["cargo fmt --all -- --check"] Clippy["cargo clippy (all targets)"] Build["cargo build (all targets)"] Test["cargo test (Linux only)"] Doc["cargo doc --no-deps"] Commit["Ready for commit"] Fix["Fix lint issues"] Debug["Fix build errors"] TestFix["Fix test failures"] DocFix["Fix documentation"] Build --> Debug Build --> Test Change --> Format Clippy --> Build Clippy --> Fix Debug --> Build Doc --> Commit Doc --> DocFix DocFix --> Doc Fix --> Clippy Format --> FormatCheck FormatCheck --> Clippy FormatCheck --> Format Test --> Doc Test --> TestFix TestFix --> Test
Platform-Specific Considerations
Since unit tests only run on x86_64-unknown-linux-gnu
, developers working on other platforms should ensure access to a Linux environment for complete testing.
The multi-target build verification ensures that changes maintain compatibility across all supported embedded platforms without requiring developers to have access to physical hardware.
Sources: .github/workflows/ci.yml(L28 - L30) .github/workflows/ci.yml(L12)
Overview
Relevant source files
Purpose and Scope
This document provides an overview of the axconfig-gen
repository, a TOML-based configuration generation system designed for ArceOS. The system provides two primary interfaces: a command-line tool (axconfig-gen
) for build-time configuration generation and procedural macros (axconfig-macros
) for compile-time configuration embedding. The system transforms TOML configuration specifications into either TOML output files or Rust constant definitions with proper type annotations.
For detailed CLI usage patterns, see Command Line Interface. For comprehensive macro usage, see Macro Usage Patterns. For configuration format specifications, see TOML Configuration Format.
System Architecture
The repository implements a dual-interface configuration system organized as a Cargo workspace with two main crates that share core processing logic.
Component Architecture
flowchart TD subgraph subGraph3["External Dependencies"] CLAP["clap (CLI parsing)"] TOML_EDIT["toml_edit (TOML manipulation)"] PROC_MACRO["proc-macro2, quote, syn"] end subgraph subGraph2["Cargo Workspace"] subgraph subGraph1["axconfig-macros Crate"] PARSE["parse_configs! macro"] INCLUDE["include_configs! macro"] MACLIB["Macro Implementation (lib.rs)"] end subgraph subGraph0["axconfig-gen Crate"] CLI["CLI Tool (main.rs)"] LIB["Library API (lib.rs)"] CONFIG["Config Structures"] VALUE["ConfigValue Types"] OUTPUT["Output Generation"] end end CLI --> CLAP CLI --> CONFIG CLI --> OUTPUT CONFIG --> TOML_EDIT INCLUDE --> LIB LIB --> CONFIG LIB --> VALUE MACLIB --> LIB MACLIB --> PROC_MACRO PARSE --> LIB
Sources: README.md(L1 - L109) [Cargo.toml workspace structure implied]
Core Processing Pipeline
flowchart TD subgraph subGraph2["Output Generation"] TOML_OUT["TOML Output (.axconfig.toml)"] RUST_OUT["Rust Constants (pub const)"] COMPILE_TIME["Compile-time Embedding"] end subgraph subGraph1["Core Processing (axconfig-gen)"] PARSER["Config::from_toml()"] VALIDATOR["Config validation & merging"] TYPESYS["ConfigType inference"] FORMATTER["OutputFormat selection"] end subgraph subGraph0["Input Sources"] TOML_FILES["TOML Files (.toml)"] TOML_STRINGS["Inline TOML Strings"] TYPE_COMMENTS["Type Annotations (# comments)"] end FORMATTER --> COMPILE_TIME FORMATTER --> RUST_OUT FORMATTER --> TOML_OUT PARSER --> VALIDATOR TOML_FILES --> PARSER TOML_STRINGS --> PARSER TYPESYS --> FORMATTER TYPE_COMMENTS --> TYPESYS VALIDATOR --> TYPESYS
Sources: README.md(L39 - L65) README.md(L69 - L98)
Usage Modes
The system operates in two distinct modes, each targeting different use cases in the ArceOS build pipeline.
Mode | Interface | Input Source | Output Target | Use Case |
---|---|---|---|---|
CLI Mode | axconfig-gencommand | File paths ( | File output (-o,-f) | Build-time generation |
Macro Mode | parse_configs!,include_configs! | Inline strings or file paths | Token stream | Compile-time embedding |
CLI Mode Operation
The CLI tool processes multiple TOML specification files and generates output files in either TOML or Rust format.
flowchart TD subgraph Outputs["Outputs"] TOML_FILE[".axconfig.toml"] RUST_FILE["constants.rs"] end subgraph Processing["Processing"] MERGE["Config merging"] VALIDATE["Type validation"] GENERATE["Output generation"] end subgraph subGraph0["CLI Arguments"] SPEC["... (input files)"] OUTPUT["-o(output path)"] FORMAT["-f (toml|rust)"] OLDCONFIG["-c (merging)"] end FORMAT --> GENERATE GENERATE --> RUST_FILE GENERATE --> TOML_FILE MERGE --> VALIDATE OLDCONFIG --> MERGE OUTPUT --> GENERATE SPEC --> MERGE VALIDATE --> GENERATE
Sources: README.md(L8 - L31)
Macro Mode Operation
Procedural macros embed configuration processing directly into the compilation process, generating constants at compile time.
flowchart TD subgraph subGraph2["Generated Constants"] GLOBAL_CONST["pub const GLOBAL_VAR"] MODULE_CONST["pub mod module_name"] TYPED_CONST["Typed constants"] end subgraph subGraph1["Compile-time Processing"] TOKEN_PARSE["TokenStream parsing"] CONFIG_PROC["Config processing"] CODE_GEN["Rust code generation"] end subgraph subGraph0["Macro Invocations"] PARSE_INLINE["parse_configs!(toml_string)"] INCLUDE_FILE["include_configs!(file_path)"] INCLUDE_ENV["include_configs!(path_env = VAR)"] end CODE_GEN --> GLOBAL_CONST CODE_GEN --> MODULE_CONST CODE_GEN --> TYPED_CONST CONFIG_PROC --> CODE_GEN INCLUDE_ENV --> TOKEN_PARSE INCLUDE_FILE --> TOKEN_PARSE PARSE_INLINE --> TOKEN_PARSE TOKEN_PARSE --> CONFIG_PROC
Sources: README.md(L67 - L108)
Type System and Output Generation
The system implements a sophisticated type inference and annotation system that enables generation of properly typed Rust constants.
Type Annotation System
Configuration values support type annotations through TOML comments, enabling precise Rust type generation:
bool
- Boolean valuesint
- Signed integers (isize
)uint
- Unsigned integers (usize
)str
- String literals (&str
)[type]
- Arrays of specified type(type1, type2, ...)
- Tuples with mixed types
Configuration Structure Mapping
flowchart TD subgraph subGraph1["Rust Output Structure"] PUB_CONST["pub const GLOBAL_ITEMS"] PUB_MOD["pub mod section_name"] NESTED_CONST["pub const SECTION_ITEMS"] TYPED_VALUES["Properly typed values"] end subgraph subGraph0["TOML Structure"] GLOBAL_TABLE["Global Table (root level)"] NAMED_TABLES["Named Tables ([section])"] CONFIG_ITEMS["Key-Value Pairs"] TYPE_ANNOTATIONS["Unsupported markdown: heading"] end CONFIG_ITEMS --> NESTED_CONST GLOBAL_TABLE --> PUB_CONST NAMED_TABLES --> PUB_MOD PUB_MOD --> NESTED_CONST TYPE_ANNOTATIONS --> TYPED_VALUES
Sources: README.md(L35 - L36) README.md(L42 - L64) README.md(L89 - L97)
Integration with ArceOS
The configuration system integrates into the ArceOS build process through multiple pathways, providing flexible configuration management for the operating system's components.
flowchart TD subgraph subGraph3["ArceOS Build"] CARGO_BUILD["Cargo Build Process"] ARCEOS_KERNEL["ArceOS Kernel"] end subgraph subGraph2["Generated Artifacts"] AXCONFIG[".axconfig.toml"] RUST_CONSTS["Rust Constants"] EMBEDDED_CONFIG["Compile-time Config"] end subgraph subGraph1["Configuration Processing"] CLI_GEN["axconfig-gen CLI"] MACRO_PROC["axconfig-macros processing"] end subgraph Development["Development"] DEV_CONFIG["defconfig.toml"] BUILD_SCRIPTS["Build Scripts"] RUST_SOURCES["Rust Source Files"] end AXCONFIG --> CARGO_BUILD BUILD_SCRIPTS --> CLI_GEN CARGO_BUILD --> ARCEOS_KERNEL CLI_GEN --> AXCONFIG CLI_GEN --> RUST_CONSTS DEV_CONFIG --> CLI_GEN DEV_CONFIG --> MACRO_PROC EMBEDDED_CONFIG --> CARGO_BUILD MACRO_PROC --> EMBEDDED_CONFIG RUST_CONSTS --> CARGO_BUILD RUST_SOURCES --> MACRO_PROC
Sources: README.md(L27 - L33) README.md(L102 - L108)
This architecture enables ArceOS to maintain consistent configuration across build-time generation and compile-time embedding, supporting both static configuration files and dynamic configuration processing during compilation.
System Architecture
Relevant source files
This document explains the architectural design of the axconfig-gen configuration system, covering how the axconfig-gen
and axconfig-macros
packages work together within a unified Cargo workspace. It details the component relationships, data flow patterns, and integration points that enable both CLI-based and compile-time configuration processing for ArceOS.
For specific CLI tool usage, see Command Line Interface. For procedural macro implementation details, see Macro Implementation.
Workspace Organization
The axconfig-gen repository implements a dual-package architecture within a Cargo workspace that provides both standalone tooling and compile-time integration capabilities.
Workspace Structure
flowchart TD subgraph subGraph2["External Dependencies"] TOML_EDIT["toml_edit = '0.22'"] CLAP["clap = '4'"] PROC_MACRO["proc-macro2, quote, syn"] end subgraph subGraph1["axconfig-macros Package"] AM_LIB["src/lib.rsparse_configs!, include_configs!"] AM_TESTS["tests/Integration tests"] end subgraph subGraph0["axconfig-gen Package"] AG_MAIN["src/main.rsCLI entry point"] AG_LIB["src/lib.rsPublic API exports"] AG_CONFIG["src/config.rsConfig, ConfigItem"] AG_VALUE["src/value.rsConfigValue"] AG_TYPE["src/ty.rsConfigType"] AG_OUTPUT["src/output.rsOutputFormat"] end WS["Cargo Workspaceresolver = '2'"] AG_CONFIG --> AG_OUTPUT AG_CONFIG --> AG_TYPE AG_CONFIG --> AG_VALUE AG_CONFIG --> TOML_EDIT AG_LIB --> AG_CONFIG AG_MAIN --> AG_CONFIG AG_MAIN --> CLAP AM_LIB --> AG_LIB AM_LIB --> PROC_MACRO AM_TESTS --> AM_LIB WS --> AG_LIB WS --> AG_MAIN WS --> AM_LIB
Sources: Cargo.toml(L1 - L7) axconfig-gen/Cargo.toml(L1 - L18) axconfig-macros/Cargo.toml(L1 - L26)
Component Architecture
The system implements a layered architecture where axconfig-macros
depends on axconfig-gen
for core functionality, enabling code reuse across both CLI and compile-time processing modes.
Core Processing Components
flowchart TD subgraph subGraph3["Macro Integration"] PARSE_CONFIGS["parse_configs!TokenStream processing"] INCLUDE_CONFIGS["include_configs!File path resolution"] PROC_MACRO_API["proc_macro2::TokenStreamquote! macro usage"] end subgraph subGraph2["Processing Layer"] TOML_PARSE["toml_edit integrationDocument parsing"] TYPE_INFERENCE["Type inferencefrom values and comments"] VALIDATION["Value validationagainst types"] CODE_GEN["Rust code generationpub const definitions"] end subgraph subGraph1["Data Model Layer"] CONFIG["Configglobal: ConfigTabletables: BTreeMap"] CONFIG_ITEM["ConfigItemvalue: ConfigValuety: Optioncomment: Option"] CONFIG_VALUE["ConfigValueBool, Int, Str, Array, Table"] CONFIG_TYPE["ConfigTypeBool, Int, UInt, Str, Tuple, Array"] end subgraph subGraph0["Public API Layer"] CONFIG_API["Config::from_toml()Config::merge()Config::update()"] OUTPUT_API["Config::dump(OutputFormat)"] CLI_API["clap::ParserArgs struct"] end CLI_API --> CONFIG_API CODE_GEN --> PROC_MACRO_API CONFIG --> CODE_GEN CONFIG --> CONFIG_ITEM CONFIG --> TOML_PARSE CONFIG_API --> CONFIG CONFIG_ITEM --> CONFIG_TYPE CONFIG_ITEM --> CONFIG_VALUE CONFIG_TYPE --> VALIDATION CONFIG_VALUE --> TYPE_INFERENCE INCLUDE_CONFIGS --> CONFIG_API OUTPUT_API --> CONFIG PARSE_CONFIGS --> CONFIG_API
Sources: README.md(L39 - L65) README.md(L69 - L98)
Integration Patterns
The architecture supports two distinct integration patterns that share common core processing but serve different use cases in the ArceOS build pipeline.
Dual Processing Modes
Processing Mode | Entry Point | Input Source | Output Target | Use Case |
---|---|---|---|---|
CLI Mode | axconfig-genbinary | File arguments | Generated files | Build-time configuration |
Macro Mode | parse_configs!/include_configs! | Inline TOML / File paths | Token streams | Compile-time constants |
Data Flow Architecture
flowchart TD subgraph subGraph3["Integration Points"] CLI_ARGS["clap::Args--spec, --output, --fmt"] MACRO_INVOKE["macro invocationparse_configs!()"] BUILD_SYSTEM["Cargo buildintegration"] end subgraph subGraph2["Output Generation"] TOML_OUT["OutputFormat::Tomlregenerated TOML"] RUST_OUT["OutputFormat::Rustpub const code"] TOKEN_STREAM["proc_macro2::TokenStreamcompile-time expansion"] end subgraph subGraph1["Core Processing"] PARSE["toml_edit::Documentparsing"] CONFIG_BUILD["Config::from_toml()structure building"] TYPE_PROC["ConfigType inferenceand validation"] MERGE["Config::merge()multiple sources"] end subgraph subGraph0["Input Sources"] TOML_FILES["TOML filesdefconfig.toml"] TOML_STRINGS["Inline TOMLstring literals"] ENV_VARS["Environment variablespath resolution"] end CLI_ARGS --> TOML_FILES CONFIG_BUILD --> TYPE_PROC ENV_VARS --> TOML_FILES MACRO_INVOKE --> TOML_STRINGS MERGE --> RUST_OUT MERGE --> TOKEN_STREAM MERGE --> TOML_OUT PARSE --> CONFIG_BUILD RUST_OUT --> BUILD_SYSTEM TOKEN_STREAM --> BUILD_SYSTEM TOML_FILES --> PARSE TOML_OUT --> BUILD_SYSTEM TOML_STRINGS --> PARSE TYPE_PROC --> MERGE
Sources: README.md(L10 - L31) README.md(L100 - L108)
Processing Modes
The system architecture enables flexible configuration processing through two complementary approaches that share the same core data model and validation logic.
CLI Processing Pipeline
The CLI mode implements external file processing for build-time configuration generation:
- Input: Multiple TOML specification files via command-line arguments
- Processing: File-based merging using
Config::merge()
operations - Output: Generated
.axconfig.toml
or Rust constant files - Integration: Build script integration via file I/O
Macro Processing Pipeline
The macro mode implements compile-time configuration embedding:
- Input: Inline TOML strings or file paths in macro invocations
- Processing: Token stream manipulation using
proc-macro2
infrastructure - Output: Rust constant definitions injected into the compilation AST
- Integration: Direct compile-time constant availability
Shared Core Components
Both processing modes utilize the same underlying components:
Config
struct for configuration representationConfigValue
andConfigType
for type-safe value handlingtoml_edit
integration for TOML parsing and manipulation- Rust code generation logic for consistent output formatting
Sources: axconfig-gen/Cargo.toml(L15 - L17) axconfig-macros/Cargo.toml(L18 - L22)
Quick Start Guide
Relevant source files
This guide provides step-by-step instructions for immediately getting started with axconfig-gen. It covers the three primary usage patterns: CLI tool for file generation, library API for programmatic use, and procedural macros for compile-time configuration embedding.
For detailed CLI documentation, see Command Line Interface. For comprehensive macro usage patterns, see Macro Usage Patterns. For complete configuration format specification, see TOML Configuration Format.
Installation
Install axconfig-gen using Cargo:
cargo install axconfig-gen
For procedural macro support, add these dependencies to your Cargo.toml
:
[dependencies]
axconfig-gen = "0.1"
axconfig-macros = "0.1"
CLI Quick Start
Basic File Generation
Create a configuration specification file myconfig.toml
:
# Global configurations
arch = "x86_64" # str
debug = true # bool
version = 123 # uint
[kernel]
stack-size = 0x8000 # uint
task-limit = 64 # uint
Generate a processed configuration file:
axconfig-gen myconfig.toml -o .axconfig.toml -f toml
Generate Rust constants:
axconfig-gen myconfig.toml -o config.rs -f rust
CLI Workflow Diagram
flowchart TD INPUT["TOML Filesmyconfig.tomldefconfig.toml"] CLI["axconfig-gen CLI--output--fmt"] TOML_OUT["Generated TOML.axconfig.toml"] RUST_OUT["Generated Rustconfig.rs"] BUILD["Build System"] CLI --> RUST_OUT CLI --> TOML_OUT INPUT --> CLI RUST_OUT --> BUILD TOML_OUT --> BUILD
Sources: README.md(L8 - L34) README.md(L55 - L65)
Macro Quick Start
Inline Configuration Processing
Use parse_configs!
for inline TOML processing:
use axconfig_macros::parse_configs;
parse_configs!(r#"
debug = true # bool
port = 8080 # uint
name = "my-app" # str
[database]
url = "localhost" # str
pool-size = 10 # uint
"#);
// Generated constants are now available
println!("Debug mode: {}", DEBUG);
println!("Port: {}", PORT);
println!("DB URL: {}", database::URL);
File-Based Configuration Processing
Use include_configs!
for external TOML files:
use axconfig_macros::include_configs;
// Include from file path
include_configs!("config/app.toml");
// Include using environment variable
include_configs!(path_env = "APP_CONFIG_PATH");
// Include with fallback
include_configs!(
path_env = "APP_CONFIG_PATH",
fallback = "config/default.toml"
);
Macro Processing Flow
flowchart TD INLINE["parse_configs!Inline TOML string"] FILE["include_configs!External TOML file"] ENV["Environment VariablesPath resolution"] PARSE["Config::from_toml()"] VALIDATE["Type validationConfigType system"] GENERATE["Code generationpub const definitions"] COMPILE["Compile-time constantsEmbedded in binary"] ENV --> FILE FILE --> PARSE GENERATE --> COMPILE INLINE --> PARSE PARSE --> VALIDATE VALIDATE --> GENERATE
Sources: README.md(L67 - L108) README.md(L39 - L65)
Understanding Configuration Types
Type Annotation System
Configuration values require type annotations in comments for Rust code generation:
Type Annotation | Rust Type | Example |
---|---|---|
# bool | bool | debug = true # bool |
# int | isize | offset = -10 # int |
# uint | usize | size = 1024 # uint |
# str | &str | name = "test" # str |
# [uint] | &[usize] | ports = [80, 443] # [uint] |
# (uint, str) | (usize, &str) | pair = [8080, "http"] # (uint, str) |
Configuration Structure
flowchart TD CONFIG["Config structGlobal + Named tables"] GLOBAL["Global tableTop-level key-value pairs"] TABLES["Named tables[section] groups"] ITEMS["ConfigItemKey-value pairs"] VALUES["ConfigValueTyped values"] TYPES["ConfigTypeType annotations"] CONFIG --> GLOBAL CONFIG --> TABLES GLOBAL --> ITEMS ITEMS --> VALUES TABLES --> ITEMS VALUES --> TYPES
Sources: README.md(L35 - L36) example-configs/defconfig.toml(L1 - L63)
Common Usage Patterns
Development Workflow
- Create configuration specification with type annotations
- Test with CLI tool to verify output format
- Integrate with build system using CLI or macros
- Access constants in your Rust code
CLI Integration Example
# Generate configuration for build
axconfig-gen platform/x86_64.toml kernel/debug.toml -o .axconfig.toml
# Generate Rust constants
axconfig-gen .axconfig.toml -o src/config.rs -f rust
Macro Integration Example
// In your Rust project axconfig_macros::include_configs!( path_env = "AXCONFIG_PATH", fallback = "configs/defconfig.toml" ); // Use generated constants fn main() { println!("Architecture: {}", ARCH); println!("SMP cores: {}", SMP); println!("Kernel stack size: {}", kernel::TASK_STACK_SIZE); }
Output Format Comparison
Input TOML | Generated TOML | Generated Rust |
---|---|---|
debug = true # bool | debug = true | pub const DEBUG: bool = true; |
[kernel]size = 64 # uint | [kernel]size = 64 | pub mod kernel {pub const SIZE: usize = 64;} |
Sources: README.md(L55 - L65) README.md(L89 - L98)
Next Steps
- For CLI usage: See Command Line Interface for complete CLI documentation
- For library integration: See Library API for programmatic usage
- For macro details: See Macro Usage Patterns for advanced macro features
- For configuration examples: See Configuration Examples for comprehensive TOML format guide
- For development: See Development Guide for contributing and building locally
axconfig-gen Package
Relevant source files
This document covers the axconfig-gen
package, which provides both a command-line tool and a Rust library for TOML-based configuration generation in the ArceOS ecosystem. The package serves as the core processing engine for configuration management, offering type-safe conversion from TOML specifications to both TOML and Rust code outputs.
For procedural macro interfaces that build on this package, see axconfig-macros Package. For specific CLI usage patterns, see Command Line Interface. For programmatic API details, see Library API.
Package Architecture
The axconfig-gen
package operates as a dual-purpose tool, providing both standalone CLI functionality and a library API for integration into other Rust applications. The package implements a sophisticated configuration processing pipeline that handles TOML parsing, type inference, validation, and code generation.
Core Components
flowchart TD subgraph subGraph3["External Dependencies"] clap["clapCLI parsing"] toml_edit["toml_editTOML manipulation"] end subgraph subGraph2["Processing Core"] toml_parsing["TOML Parsingtoml_edit integration"] type_inference["Type InferenceComment-based types"] validation["Validationmerge() and update()"] code_generation["Code Generationdump() method"] end subgraph subGraph1["Library API"] Config["ConfigMain configuration container"] ConfigItem["ConfigItemIndividual config entries"] ConfigValue["ConfigValueTyped values"] ConfigType["ConfigTypeType system"] OutputFormat["OutputFormatToml | Rust"] end subgraph subGraph0["CLI Layer"] Args["Args structClap parser"] main_rs["main.rsCLI entry point"] end Args --> main_rs Config --> ConfigItem Config --> code_generation Config --> toml_parsing Config --> validation ConfigItem --> ConfigValue ConfigValue --> ConfigType ConfigValue --> type_inference code_generation --> OutputFormat main_rs --> Config main_rs --> ConfigValue main_rs --> OutputFormat main_rs --> clap toml_parsing --> toml_edit
Sources: axconfig-gen/src/main.rs(L1 - L175) axconfig-gen/README.md(L1 - L69)
Processing Pipeline
flowchart TD subgraph subGraph2["Output Generation"] Config_dump["Config::dump()Generate output"] file_output["File Output--output path"] stdout_output["Standard OutputDefault behavior"] end subgraph subGraph1["Core Processing"] Config_new["Config::new()Initialize container"] Config_from_toml["Config::from_toml()Parse TOML specs"] Config_merge["Config::merge()Combine specifications"] Config_update["Config::update()Apply old config values"] config_modification["CLI Read/WriteIndividual item access"] end subgraph subGraph0["Input Sources"] spec_files["Specification FilesTOML format"] oldconfig["Old Config FileOptional TOML"] cli_args["CLI Arguments--read, --write flags"] end Config_dump --> file_output Config_dump --> stdout_output Config_from_toml --> Config_merge Config_merge --> Config_update Config_new --> Config_merge Config_update --> config_modification cli_args --> config_modification config_modification --> Config_dump oldconfig --> Config_update spec_files --> Config_from_toml
Sources: axconfig-gen/src/main.rs(L76 - L174)
Key Capabilities
Configuration Management
The package provides comprehensive configuration management through the Config
struct, which serves as the central container for all configuration data. The configuration system supports:
Feature | Description | Implementation |
---|---|---|
Specification Loading | Load multiple TOML specification files | Config::from_toml()andConfig::merge() |
Value Updates | Apply existing configuration values | Config::update()method |
Individual Access | Read/write specific configuration items | config_at()andconfig_at_mut() |
Type Safety | Enforce types through comments and inference | ConfigTypeandConfigValue |
Global and Scoped | Support both global and table-scoped items | GLOBAL_TABLE_NAMEconstant |
CLI Interface
The command-line interface, implemented in axconfig-gen/src/main.rs(L5 - L40) provides extensive functionality for configuration management:
flowchart TD subgraph subGraph2["Output Operations"] format_selection["--fmt toml|rustChoose output format"] file_output["--output pathWrite to file"] backup_creation["Auto backup.old extension"] end subgraph subGraph0["Input Operations"] spec_loading["--spec filesLoad specifications"] oldconfig_loading["--oldconfig fileLoad existing config"] item_writing["--write table.key=valueSet individual items"] end subgraph subGraph1["Query Operations"] item_reading["--read table.keyGet individual items"] verbose_mode["--verboseDebug information"] end file_output --> backup_creation format_selection --> file_output item_reading --> verbose_mode item_writing --> format_selection oldconfig_loading --> format_selection spec_loading --> format_selection
Sources: axconfig-gen/src/main.rs(L5 - L40) axconfig-gen/README.md(L8 - L22)
Type System Integration
The package implements a sophisticated type system that bridges TOML configuration with Rust type safety:
Type Category | TOML Comment Syntax | Generated Rust Type |
---|---|---|
Primitives | # bool,# int,# uint,# str | bool,isize,usize,&str |
Collections | # [type] | &[type] |
Tuples | # (type1, type2, ...) | (type1, type2, ...) |
Inferred | No comment | Automatic from value |
Library API
The package exposes a clean programmatic interface for integration into other Rust applications. The core workflow follows this pattern:
- Configuration Creation: Initialize with
Config::new()
orConfig::from_toml()
- Specification Merging: Combine multiple sources with
Config::merge()
- Value Management: Update configurations with
Config::update()
- Output Generation: Generate code with
Config::dump(OutputFormat)
Integration Points
ArceOS Build System
The package integrates with the ArceOS build system through multiple pathways:
flowchart TD subgraph subGraph2["Generated Artifacts"] toml_configs["TOML Configurations.axconfig.toml files"] rust_constants["Rust Constantspub const definitions"] module_structure["Module Structurepub mod organization"] end subgraph subGraph1["axconfig-gen Processing"] cli_execution["CLI Executionaxconfig-gen binary"] library_usage["Library UsageProgrammatic API"] spec_processing["Specification ProcessingTOML merging"] end subgraph subGraph0["Build Time Integration"] build_scripts["Build Scriptsbuild.rs files"] cargo_workspace["Cargo WorkspaceMulti-crate builds"] env_resolution["Environment VariablesPath resolution"] end build_scripts --> cli_execution cargo_workspace --> library_usage cli_execution --> toml_configs env_resolution --> spec_processing library_usage --> rust_constants module_structure --> cargo_workspace rust_constants --> cargo_workspace spec_processing --> module_structure toml_configs --> cargo_workspace
Sources: axconfig-gen/src/main.rs(L87 - L95) axconfig-gen/README.md(L24 - L28)
External Tool Integration
The package supports integration with external configuration management tools through its file-based interface and backup mechanisms implemented in axconfig-gen/src/main.rs(L155 - L170) The automatic backup system ensures configuration history preservation during updates.
Sources: axconfig-gen/src/main.rs(L155 - L170) axconfig-gen/README.md(L34 - L62)
Command Line Interface
Relevant source files
This document provides comprehensive documentation for the axconfig-gen
command-line interface (CLI) tool. The CLI enables users to process TOML configuration files, merge specifications, update configurations, and generate output in various formats for the ArceOS operating system.
For information about using axconfig-gen as a Rust library, see Library API. For compile-time configuration processing using procedural macros, see axconfig-macros Package.
CLI Architecture Overview
The CLI tool is built around a structured argument parsing system that processes configuration specifications through a multi-stage pipeline.
CLI Argument Processing Architecture
flowchart TD subgraph subGraph3["Output Generation"] CONFIG_DUMP["config.dump()"] FILE_WRITE["std::fs::write()"] STDOUT["println!()"] end subgraph subGraph2["Configuration Operations"] CONFIG_NEW["Config::new()"] CONFIG_FROM["Config::from_toml()"] CONFIG_MERGE["config.merge()"] CONFIG_UPDATE["config.update()"] CONFIG_AT["config.config_at()"] CONFIG_AT_MUT["config.config_at_mut()"] end subgraph subGraph1["Input Processing"] PARSE_READ["parse_config_read_arg()"] PARSE_WRITE["parse_config_write_arg()"] FILE_READ["std::fs::read_to_string()"] end subgraph clap::Parser["clap::Parser"] ARGS["Args struct"] SPEC["spec: Vec"] OLD["oldconfig: Option"] OUT["output: Option"] FMT["fmt: OutputFormat"] READ["read: Vec"] WRITE["write: Vec"] VERB["verbose: bool"] end ARGS --> FMT ARGS --> OLD ARGS --> OUT ARGS --> READ ARGS --> SPEC ARGS --> VERB ARGS --> WRITE CONFIG_DUMP --> FILE_WRITE CONFIG_DUMP --> STDOUT CONFIG_FROM --> CONFIG_MERGE CONFIG_FROM --> CONFIG_NEW CONFIG_FROM --> CONFIG_UPDATE FILE_READ --> CONFIG_FROM FMT --> CONFIG_DUMP OLD --> FILE_READ OUT --> FILE_WRITE PARSE_READ --> CONFIG_AT PARSE_WRITE --> CONFIG_AT_MUT READ --> PARSE_READ SPEC --> FILE_READ WRITE --> PARSE_WRITE
Sources: axconfig-gen/src/main.rs(L5 - L40) axconfig-gen/src/main.rs(L76 - L174)
Command Line Arguments
The CLI accepts multiple arguments that control different aspects of configuration processing:
Argument | Short | Type | Required | Description |
---|---|---|---|---|
- | Vec | Yes | Paths to configuration specification files | |
--oldconfig | -c | Option | No | Path to existing configuration file for updates |
--output | -o | Option | No | Output file path (stdout if not specified) |
--fmt | -f | OutputFormat | No | Output format:tomlorrust(default:toml) |
--read | -r | Vec | No | Read config items with formattable.key |
--write | -w | Vec | No | Write config items with formattable.key=value |
--verbose | -v | bool | No | Enable verbose debug output |
Format Specification
The --fmt
argument accepts two values processed by a PossibleValuesParser
:
toml - Generate TOML configuration output
rust - Generate Rust constant definitions
Sources: axconfig-gen/src/main.rs(L7 - L40) axconfig-gen/README.md(L8 - L22)
Configuration Item Addressing
Configuration items use a dot-notation addressing system implemented in parsing functions:
Configuration Item Parsing Flow
Sources: axconfig-gen/src/main.rs(L42 - L62) axconfig-gen/src/main.rs(L136 - L147)
Usage Patterns
Basic Configuration Generation
Generate a configuration file from specifications:
axconfig-gen spec1.toml spec2.toml -o .axconfig.toml -f toml
This pattern loads multiple specification files, merges them using Config::merge()
, and outputs the result.
Configuration Updates
Update an existing configuration with new specifications:
axconfig-gen defconfig.toml -c existing.toml -o updated.toml
The update process uses Config::update()
which returns untouched and extra items for warning generation.
Runtime Configuration Queries
Read specific configuration values:
axconfig-gen defconfig.toml -r kernel.heap_size -r features.smp
Write configuration values:
axconfig-gen defconfig.toml -w kernel.heap_size=0x100000 -w features.smp=true
Rust Code Generation
Generate Rust constant definitions:
axconfig-gen defconfig.toml -f rust -o config.rs
Sources: axconfig-gen/README.md(L24 - L28) axconfig-gen/src/main.rs(L87 - L95) axconfig-gen/src/main.rs(L97 - L117)
Configuration Processing Pipeline
The CLI implements a sequential processing pipeline that handles merging, updating, and querying operations:
Configuration Processing Flow
flowchart TD subgraph subGraph5["Output Generation"] DUMP["config.dump(args.fmt)"] OUTPUT_CHECK["if args.output.is_some()"] BACKUP["create .old backup"] WRITE_FILE["std::fs::write(path, output)"] WRITE_STDOUT["println!(output)"] end subgraph subGraph4["Read Operations"] READ_LOOP["for arg in args.read"] PARSE_READ_ARG["parse_config_read_arg(arg)"] CONFIG_AT["config.config_at(table, key)"] PRINT_VALUE["println!(item.value().to_toml_value())"] EARLY_RETURN["return if read mode"] end subgraph subGraph3["Write Operations"] WRITE_LOOP["for arg in args.write"] PARSE_WRITE_ARG["parse_config_write_arg(arg)"] CONFIG_AT_MUT["config.config_at_mut(table, key)"] VALUE_UPDATE["item.value_mut().update(new_value)"] end subgraph subGraph2["Old Config Processing"] OLD_CHECK["if args.oldconfig.is_some()"] READ_OLD["std::fs::read_to_string(oldconfig)"] OLD_FROM_TOML["Config::from_toml(oldconfig_toml)"] UPDATE["config.update(oldconfig)"] WARN_UNTOUCHED["warn untouched items"] WARN_EXTRA["warn extra items"] end subgraph subGraph1["Specification Loading"] SPEC_LOOP["for spec in args.spec"] READ_SPEC["std::fs::read_to_string(spec)"] FROM_TOML["Config::from_toml(spec_toml)"] MERGE["config.merge(sub_config)"] end subgraph Initialization["Initialization"] CONFIG_NEW["Config::new()"] end BACKUP --> WRITE_FILE CONFIG_AT --> PRINT_VALUE CONFIG_AT_MUT --> VALUE_UPDATE CONFIG_NEW --> SPEC_LOOP DUMP --> OUTPUT_CHECK EARLY_RETURN --> DUMP FROM_TOML --> MERGE MERGE --> OLD_CHECK MERGE --> SPEC_LOOP OLD_CHECK --> READ_OLD OLD_CHECK --> WRITE_LOOP OLD_FROM_TOML --> UPDATE OUTPUT_CHECK --> BACKUP OUTPUT_CHECK --> WRITE_STDOUT PARSE_READ_ARG --> CONFIG_AT PARSE_WRITE_ARG --> CONFIG_AT_MUT PRINT_VALUE --> READ_LOOP READ_LOOP --> EARLY_RETURN READ_LOOP --> PARSE_READ_ARG READ_OLD --> OLD_FROM_TOML READ_SPEC --> FROM_TOML SPEC_LOOP --> READ_SPEC UPDATE --> WARN_EXTRA UPDATE --> WARN_UNTOUCHED VALUE_UPDATE --> WRITE_LOOP WARN_EXTRA --> WRITE_LOOP WRITE_LOOP --> DUMP WRITE_LOOP --> PARSE_WRITE_ARG WRITE_LOOP --> READ_LOOP
Sources: axconfig-gen/src/main.rs(L87 - L174)
Error Handling and Debugging
The CLI implements comprehensive error handling with a custom unwrap!
macro that provides clean error messages and exits gracefully:
Error Handling Mechanism
- File reading errors display the problematic file path
- TOML parsing errors show detailed syntax information
- Config merging conflicts are reported with item names
- Missing configuration items generate helpful error messages
Verbose Mode
When --verbose
is enabled, the CLI outputs debug information for:
- Configuration specification loading
- Old configuration processing
- Individual config item operations
- Output generation decisions
The verbose output uses a local debug!
macro that conditionally prints to stderr based on the args.verbose
flag.
Sources: axconfig-gen/src/main.rs(L64 - L74) axconfig-gen/src/main.rs(L79 - L85) axconfig-gen/src/main.rs(L89 - L90)
File Backup System
When writing to an existing output file, the CLI automatically creates backup files to prevent data loss:
- Backup files use the
.old
extension (e.g.,config.toml
→config.old.toml
) - Backups are only created if the new output differs from the existing file
- The comparison prevents unnecessary writes when output is identical
This backup mechanism ensures safe configuration updates while avoiding redundant file operations.
Sources: axconfig-gen/src/main.rs(L156 - L169)
Library API
Relevant source files
This document covers the programmatic interface for using axconfig-gen as a Rust library. The library API allows developers to programmatically parse TOML configurations, manipulate configuration data, and generate output in various formats within their own Rust applications. For information about using axconfig-gen as a command-line tool, see Command Line Interface. For details about the internal data structures, see Core Data Structures.
API Overview
The axconfig-gen library provides a high-level API centered around the Config
type, which manages configuration data parsed from TOML files. The library handles TOML parsing, type inference, validation, and output generation through a clean programmatic interface.
Sources: axconfig-gen/src/lib.rs(L13 - L16) axconfig-gen/src/config.rs(L9 - L18) axconfig-gen/src/config.rs(L95 - L100)
Basic Usage Pattern
The fundamental workflow for using the library API involves parsing TOML input, optionally manipulating the configuration, and generating output in the desired format.
flowchart TD subgraph subGraph0["Optional Operations"] Merge["config.merge(other)"] Update["config.update(other)"] Access["config.config_at(table, key)"] end TomlInput["TOML String Input"] ParseStep["Config::from_toml(toml_str)"] ConfigObject["Config Object"] OutputStep["config.dump(OutputFormat)"] Result["Generated String Output"] ConfigObject --> Access ConfigObject --> Merge ConfigObject --> OutputStep ConfigObject --> Update Merge --> ConfigObject OutputStep --> Result ParseStep --> ConfigObject TomlInput --> ParseStep Update --> ConfigObject
The library usage example from the README demonstrates this pattern:
Operation | Method | Purpose |
---|---|---|
Parse TOML | Config::from_toml() | Convert TOML string to Config object |
Generate Output | config.dump() | Convert Config to TOML or Rust code |
Access Data | config.config_at() | Retrieve specific configuration items |
Combine Configs | config.merge() | Merge multiple configurations |
Update Values | config.update() | Update existing configuration values |
Sources: axconfig-gen/README.md(L36 - L62) axconfig-gen/src/config.rs(L200 - L236) axconfig-gen/src/config.rs(L238 - L265)
Configuration Management
The Config
type provides methods for managing configuration data at both the table and item level. The configuration is organized into a global table and named tables, following TOML's structure.
Table Operations
Method | Return Type | Description |
---|---|---|
global_table() | &BTreeMap<String, ConfigItem> | Access global configuration items |
table_at(name) | Option<&BTreeMap<String, ConfigItem>> | Access named table by name |
table_at_mut(name) | Option<&mut BTreeMap<String, ConfigItem>> | Mutable access to named table |
table_comments_at(name) | Option<&str> | Get comments for a table |
Item Access
Method | Return Type | Description |
---|---|---|
config_at(table, key) | Option<&ConfigItem> | Access specific configuration item |
config_at_mut(table, key) | Option<&mut ConfigItem> | Mutable access to configuration item |
iter() | Iterator<Item = &ConfigItem> | Iterate over all configuration items |
table_iter() | Iterator<Item = (&str, &ConfigTable, &str)> | Iterate over all tables |
Sources: axconfig-gen/src/config.rs(L135 - L196)
Configuration Manipulation
The library provides two primary methods for combining and updating configurations: merge()
and update()
. These operations support different use cases for configuration management.
Merge Operation
The merge()
method combines two configurations, requiring that no duplicate keys exist:
Update Operation
The update()
method overwrites existing values and returns information about keys that were not matched:
Sources: axconfig-gen/src/config.rs(L267 - L284) axconfig-gen/src/config.rs(L286 - L322)
Output Generation
The library supports generating output in multiple formats through the OutputFormat
enum and dump()
method family.
Output Formats
Format | Method | Description |
---|---|---|
TOML | dump_toml() | Generate TOML configuration file |
Rust | dump_rs() | Generate Rust constant definitions |
Generic | dump(OutputFormat) | Generate output in specified format |
Output Generation Process
The output generation process iterates through all tables and items, formatting them according to the specified output format. For TOML output, it preserves comments and structure. For Rust output, it generates pub const
definitions with appropriate types.
Sources: axconfig-gen/src/config.rs(L238 - L265) axconfig-gen/src/lib.rs(L14)
Error Handling
The library uses a custom error type ConfigErr
with a specialized result type ConfigResult<T>
. All fallible operations return this result type for consistent error handling.
Error Types
Variant | Description | Common Causes |
---|---|---|
Parse(TomlError) | TOML parsing failure | Invalid TOML syntax |
InvalidValue | Invalid configuration value | Unsupported value types |
InvalidType | Invalid type annotation | Malformed type comments |
ValueTypeMismatch | Value doesn't match specified type | Type annotation conflicts |
Other(String) | General errors | Duplicate keys, reserved names |
Error Handling Pattern
The error type implements both Display
and Debug
traits for easy error reporting, and automatically converts from TomlError
for seamless integration with the underlying TOML parsing library.
Sources: axconfig-gen/src/lib.rs(L18 - L57)
Core Data Structures
Relevant source files
This page provides an in-depth examination of the three fundamental data structures that form the backbone of the axconfig-gen configuration system: Config
, ConfigItem
, and ConfigValue
. These structures work together to represent, validate, and manipulate TOML-based configurations throughout the parsing, processing, and output generation pipeline.
For information about the type system and how types are inferred and validated, see Type System. For details on how these structures are used in output generation, see Output Generation.
Structure Relationships
The core data structures form a hierarchical relationship where Config
serves as the top-level container, ConfigItem
represents individual configuration entries, and ConfigValue
wraps the actual values with type information.
Sources: axconfig-gen/src/config.rs(L1 - L331) axconfig-gen/src/value.rs(L1 - L289)
Config Structure
The Config
structure serves as the primary container for all configuration data, organizing items into a global table and named tables with associated comments.
Internal Organization
flowchart TD subgraph NamedTableItems["Named Table Items"] GlobalItem2["ConfigItem { table_name: '$GLOBAL', key: 'SMP', ... }"] PlatformTable["'platform' table"] PlatformItem1["ConfigItem { table_name: 'platform', key: 'FAMILY', ... }"] PlatformItem2["ConfigItem { table_name: 'platform', key: 'ARCH', ... }"] end subgraph Config["Config Structure"] GlobalItem1["ConfigItem { table_name: '$GLOBAL', key: 'LOG_LEVEL', ... }"] GlobalTable["global: ConfigTable(BTreeMap<String, ConfigItem>)"] NamedTables["tables: BTreeMap<String, ConfigTable>"] TableComments["table_comments: BTreeMap<String, String>"] end subgraph GlobalItems["Global Configuration Items"] GlobalItem1["ConfigItem { table_name: '$GLOBAL', key: 'LOG_LEVEL', ... }"] GlobalItem2["ConfigItem { table_name: '$GLOBAL', key: 'SMP', ... }"] NamedTables["tables: BTreeMap<String, ConfigTable>"] end NamedTables --> PlatformTable PlatformTable --> PlatformItem1 PlatformTable --> PlatformItem2
The Config
structure maintains a clear separation between global configuration items and those organized within named tables. The constant GLOBAL_TABLE_NAME
is set to "$GLOBAL"
to distinguish global items from named table items.
Sources: axconfig-gen/src/config.rs(L91 - L113) axconfig-gen/src/config.rs(L103 - L104)
Key Methods and Operations
Method | Purpose | Return Type |
---|---|---|
new() | Creates empty config instance | Config |
from_toml(toml: &str) | Parses TOML string into config | ConfigResult |
merge(other: &Config) | Merges configurations, fails on duplicates | ConfigResult<()> |
update(other: &Config) | Updates values, reports untouched/extra items | ConfigResult<(Vec |
dump(fmt: OutputFormat) | Generates output in specified format | ConfigResult |
The merge
operation at axconfig-gen/src/config.rs(L268 - L284) strictly prevents duplicate keys, while update
at axconfig-gen/src/config.rs(L292 - L322) allows value updates and reports which items were not updated.
Sources: axconfig-gen/src/config.rs(L106 - L322)
ConfigItem Structure
The ConfigItem
structure represents individual configuration entries, encapsulating the hierarchical context (table name), the key identifier, the typed value, and associated documentation comments.
Data Flow and Construction
flowchart TD TOMLTable["TOML Table with Key-Value Pairs"] TOMLValue["TOML Value with Decorations"] NewMethod["ConfigItem::new(table_name, table, key, value)"] NewGlobalMethod["ConfigItem::new_global(table, key, value)"] CommentExtraction["Comment Extractionprefix_comments() + suffix_comments()"] TypeInference["Type Inference from CommentsConfigType::new() if suffix exists"] ValueWrapping["ConfigValue Creationfrom_raw_value_type() or from_raw_value()"] ConfigItemInstance["ConfigItem {table_name: String,key: String,value: ConfigValue,comments: String}"] CommentExtraction --> TypeInference NewGlobalMethod --> ConfigItemInstance NewMethod --> ConfigItemInstance TOMLTable --> NewGlobalMethod TOMLTable --> NewMethod TOMLValue --> CommentExtraction TypeInference --> ValueWrapping ValueWrapping --> ConfigItemInstance
The construction process extracts type annotations from trailing comments (suffix comments starting with #
) and preserves documentation from prefix comments for later output generation.
Sources: axconfig-gen/src/config.rs(L20 - L51) axconfig-gen/src/config.rs(L324 - L330)
Naming and Identification
The item_name()
method at axconfig-gen/src/config.rs(L57 - L63) provides a unique identifier for each configuration item:
- Global items: Returns just the key (e.g.,
"SMP"
) - Named table items: Returns
"table.key"
format (e.g.,"platform.ARCH"
)
This naming scheme enables unambiguous reference to any configuration item within the system.
Sources: axconfig-gen/src/config.rs(L53 - L89)
ConfigValue Structure
The ConfigValue
structure wraps TOML values with optional type information, providing the foundation for type validation and code generation throughout the system.
Value and Type Relationship
The dual representation allows for both dynamically inferred types (when no annotation is provided) and explicitly declared types (from TOML comment annotations).
Sources: axconfig-gen/src/value.rs(L7 - L13) axconfig-gen/src/value.rs(L82 - L90)
Construction Patterns
Constructor | Purpose | Type Information |
---|---|---|
new(s: &str) | Parse TOML string, infer type | No explicit type |
new_with_type(s: &str, ty: &str) | Parse with explicit type validation | Explicit type provided |
from_raw_value(value: &Value) | Wrap existing TOML value | No explicit type |
from_raw_value_type(value: &Value, ty: ConfigType) | Wrap with type validation | Explicit type provided |
The validation logic at axconfig-gen/src/value.rs(L142 - L175) ensures that values match their declared types, with special handling for numeric strings that can represent multiple types.
Sources: axconfig-gen/src/value.rs(L14 - L50)
Value Update Semantics
The update
method at axconfig-gen/src/value.rs(L57 - L80) implements type-safe value updates with the following rules:
- Both have explicit types: Types must match exactly
- Current has type, new doesn't: New value must be compatible with current type
- Current has no type, new has type: Current value must be compatible with new type
- Neither has explicit type: No type constraint, update proceeds
This flexible approach allows for gradual type refinement while maintaining type safety where types are explicitly declared.
Sources: axconfig-gen/src/value.rs(L57 - L80)
Integration with TOML Processing
The core data structures integrate seamlessly with the toml_edit
crate to preserve formatting, comments, and metadata during parsing and generation.
TOML Parsing Pipeline
flowchart TD TOMLInput["TOML Input String"] DocumentMut["toml_edit::DocumentMut"] TableIteration["Iterate over top-level items"] ValueItems["Item::Value → Global ConfigItem"] TableItems["Item::Table → Named table ConfigItems"] CommentPreservation["Preserve prefix/suffix commentsfrom toml_edit::Decor"] TypeExtraction["Extract type annotationsfrom suffix comments"] ConfigStructure["Populated Config Structure"] CommentPreservation --> TypeExtraction DocumentMut --> TableIteration TOMLInput --> DocumentMut TableItems --> CommentPreservation TableIteration --> TableItems TableIteration --> ValueItems TypeExtraction --> ConfigStructure ValueItems --> CommentPreservation
The parsing implementation at axconfig-gen/src/config.rs(L200 - L236) carefully handles the distinction between global values, named tables, and validates that object arrays ([[table]]
syntax) are not supported in the current implementation.
Sources: axconfig-gen/src/config.rs(L200 - L236) axconfig-gen/src/config.rs(L324 - L330)
Type System
Relevant source files
Purpose and Scope
The Type System is the core component responsible for type inference, validation, and code generation in the axconfig-gen library. It provides mechanisms to parse type annotations from strings, infer types from TOML values, validate type compatibility, and generate appropriate Rust type declarations for configuration values.
For information about how types are used in configuration data structures, see Core Data Structures. For details on how the type system integrates with output generation, see Output Generation.
ConfigType Enumeration
The foundation of the type system is the ConfigType
enum, which represents all supported configuration value types in the system.
flowchart TD ConfigType["ConfigType enum"] Bool["Bool: boolean values"] Int["Int: signed integers"] Uint["Uint: unsigned integers"] String["String: string values"] Tuple["Tuple(Vec<ConfigType>): fixed-length heterogeneous sequences"] Array["Array(Box<ConfigType>): variable-length homogeneous sequences"] Unknown["Unknown: type to be inferred"] TupleExample["Example: (int, str, bool)"] ArrayExample["Example: [uint] or [[str]]"] Array --> ArrayExample ConfigType --> Array ConfigType --> Bool ConfigType --> Int ConfigType --> String ConfigType --> Tuple ConfigType --> Uint ConfigType --> Unknown Tuple --> TupleExample
Supported Type Categories
Type Category | ConfigType Variant | Rust Equivalent | TOML Representation |
---|---|---|---|
Boolean | Bool | bool | true,false |
Signed Integer | Int | isize | -123,0x80 |
Unsigned Integer | Uint | usize | 123,0xff |
String | String | &str | "hello" |
Tuple | Tuple(Vec | (T1, T2, ...) | [val1, val2, ...] |
Array | Array(Box | &[T] | [val1, val2, ...] |
Unknown | Unknown | N/A | Used for inference |
Sources: axconfig-gen/src/ty.rs(L4 - L22)
Type Parsing and String Representation
The type system supports parsing type specifications from string format, enabling type annotations in TOML comments and explicit type declarations.
flowchart TD TypeString["Type String Input"] Parser["ConfigType::new()"] BasicTypes["Basic Typesbool, int, uint, str"] TupleParser["Tuple Parser(type1, type2, ...)"] ArrayParser["Array Parser[element_type]"] TupleSplitter["split_tuple_items()Handle nested parentheses"] ElementType["Recursive parsingfor element type"] ConfigTypeResult["ConfigType instance"] DisplayFormat["Display traitHuman-readable output"] RustType["to_rust_type()Rust code generation"] ArrayParser --> ElementType BasicTypes --> ConfigTypeResult ConfigTypeResult --> DisplayFormat ConfigTypeResult --> RustType ElementType --> ConfigTypeResult Parser --> ArrayParser Parser --> BasicTypes Parser --> TupleParser TupleParser --> TupleSplitter TupleSplitter --> ConfigTypeResult TypeString --> Parser
Type String Examples
Input String | Parsed Type | Rust Output |
---|---|---|
"bool" | ConfigType::Bool | "bool" |
"[uint]" | ConfigType::Array(Box::new(ConfigType::Uint)) | "&[usize]" |
"(int, str)" | ConfigType::Tuple(vec![ConfigType::Int, ConfigType::String]) | "(isize, &str)" |
"[[bool]]" | ConfigType::Array(Box::new(ConfigType::Array(...))) | "&[&[bool]]" |
Sources: axconfig-gen/src/ty.rs(L24 - L61) axconfig-gen/src/ty.rs(L83 - L111) axconfig-gen/src/ty.rs(L113 - L134)
Type Inference System
The type inference system automatically determines types from TOML values, supporting both simple and complex nested structures.
flowchart TD TOMLValue["TOML Value"] BooleanValue["Boolean Value"] IntegerValue["Integer Value"] StringValue["String Value"] ArrayValue["Array Value"] BoolType["ConfigType::Bool"] IntegerCheck["Check sign"] NegativeInt["< 0: ConfigType::Int"] PositiveInt["Unsupported markdown: blockquote"] NumericString["is_num() check"] StringAsUint["Numeric: ConfigType::Uint"] StringAsStr["Non-numeric: ConfigType::String"] InferElements["Infer element types"] AllSame["All elements same type?"] ArrayResult["Yes: ConfigType::Array"] TupleResult["No: ConfigType::Tuple"] UnknownResult["Empty/Unknown: ConfigType::Unknown"] AllSame --> ArrayResult AllSame --> TupleResult AllSame --> UnknownResult ArrayValue --> InferElements BooleanValue --> BoolType InferElements --> AllSame IntegerCheck --> NegativeInt IntegerCheck --> PositiveInt IntegerValue --> IntegerCheck NumericString --> StringAsStr NumericString --> StringAsUint StringValue --> NumericString TOMLValue --> ArrayValue TOMLValue --> BooleanValue TOMLValue --> IntegerValue TOMLValue --> StringValue
Inference Rules
- Integers: Negative values infer as
Int
, non-negative asUint
- Strings: Numeric strings (hex, binary, octal, decimal) infer as
Uint
, others asString
- Arrays: Homogeneous arrays become
Array
, heterogeneous becomeTuple
- Nested Arrays: Recursively infer element types
Sources: axconfig-gen/src/value.rs(L177 - L224) axconfig-gen/src/value.rs(L114 - L125)
Type Validation and Matching
The type system provides comprehensive validation to ensure values conform to expected types, with special handling for string-encoded numbers and flexible integer types.
flowchart TD ValueTypeMatch["value_type_matches()"] BoolMatch["Boolean vs Bool"] IntMatch["Integer vs Int/Uint"] StringMatch["String value analysis"] ArrayMatch["Array structure validation"] StringNumeric["is_num() check"] StringAsNumber["Numeric stringmatches Int/Uint/String"] StringLiteral["Non-numeric stringmatches String only"] TupleValidation["Array vs TupleLength and element validation"] ArrayValidation["Array vs ArrayElement type validation"] Elementwise["Check each elementagainst corresponding type"] Homogeneous["Check all elementsagainst element type"] ArrayMatch --> ArrayValidation ArrayMatch --> TupleValidation ArrayValidation --> Homogeneous StringMatch --> StringNumeric StringNumeric --> StringAsNumber StringNumeric --> StringLiteral TupleValidation --> Elementwise ValueTypeMatch --> ArrayMatch ValueTypeMatch --> BoolMatch ValueTypeMatch --> IntMatch ValueTypeMatch --> StringMatch
Type Compatibility Matrix
TOML Value | Bool | Int | Uint | String | Notes |
---|---|---|---|---|---|
true/false | ✓ | ✗ | ✗ | ✗ | Exact match only |
123 | ✗ | ✓ | ✓ | ✗ | Integers match both Int/Uint |
-123 | ✗ | ✓ | ✓ | ✗ | Negative still matches Uint |
"123" | ✗ | ✓ | ✓ | ✓ | Numeric strings are flexible |
"abc" | ✗ | ✗ | ✗ | ✓ | Non-numeric strings |
Sources: axconfig-gen/src/value.rs(L142 - L175) axconfig-gen/src/value.rs(L58 - L80)
Integration with ConfigValue
The type system integrates closely with ConfigValue
to provide type-safe configuration value management.
flowchart TD ConfigValue["ConfigValue struct"] Value["value: toml_edit::Value"] OptionalType["ty: Option<ConfigType>"] ExplicitType["Explicit typefrom constructor"] InferredType["Inferred typefrom value analysis"] NoType["No typepure value storage"] TypeMethods["Type-related methods"] InferredTypeMethod["inferred_type()Get computed type"] TypeMatchesMethod["type_matches()Validate against type"] UpdateMethod["update()Type-safe value updates"] ToRustMethod["to_rust_value()Generate Rust code"] ConfigValue --> OptionalType ConfigValue --> TypeMethods ConfigValue --> Value OptionalType --> ExplicitType OptionalType --> InferredType OptionalType --> NoType TypeMethods --> InferredTypeMethod TypeMethods --> ToRustMethod TypeMethods --> TypeMatchesMethod TypeMethods --> UpdateMethod
ConfigValue Type Operations
- Construction: Values can be created with or without explicit types
- Inference: Types can be computed from TOML values automatically
- Validation: Updates are validated against existing or specified types
- Code Generation: Type information drives Rust code output format
Sources: axconfig-gen/src/value.rs(L7 - L12) axconfig-gen/src/value.rs(L52 - L103)
Rust Code Generation
The type system drives generation of appropriate Rust type declarations and value literals for compile-time configuration constants.
flowchart TD TypeToRust["Type → Rust Mapping"] BasicMapping["Basic Type Mapping"] ComplexMapping["Complex Type Mapping"] BoolToRust["Bool → bool"] IntToRust["Int → isize"] UintToRust["Uint → usize"] StringToRust["String → &str"] TupleToRust["Tuple → (T1, T2, ...)"] ArrayToRust["Array → &[T]"] TupleElements["Recursive element mapping"] ArrayElement["Element type mapping"] ValueToRust["Value → Rust Code"] LiteralMapping["Literal Value Mapping"] StructureMapping["Structure Mapping"] BoolLiteral["true/false"] NumericLiteral["Integer literals"] StringLiteral["String literals"] TupleStructure["(val1, val2, ...)"] ArrayStructure["&[val1, val2, ...]"] ArrayToRust --> ArrayElement BasicMapping --> BoolToRust BasicMapping --> IntToRust BasicMapping --> StringToRust BasicMapping --> UintToRust ComplexMapping --> ArrayToRust ComplexMapping --> TupleToRust LiteralMapping --> BoolLiteral LiteralMapping --> NumericLiteral LiteralMapping --> StringLiteral StructureMapping --> ArrayStructure StructureMapping --> TupleStructure TupleToRust --> TupleElements TypeToRust --> BasicMapping TypeToRust --> ComplexMapping ValueToRust --> LiteralMapping ValueToRust --> StructureMapping
Rust Generation Examples
ConfigType | Rust Type | Example Value | Generated Rust |
---|---|---|---|
Bool | bool | true | true |
Uint | usize | "0xff" | 0xff |
Array(String) | &[&str] | ["a", "b"] | &["a", "b"] |
Tuple([Uint, String]) | (usize, &str) | [123, "test"] | (123, "test") |
Sources: axconfig-gen/src/ty.rs(L62 - L81) axconfig-gen/src/value.rs(L243 - L288)
Output Generation
Relevant source files
This section covers the output generation system in axconfig-gen, which converts parsed configuration structures into formatted output strings. The system supports generating both TOML files and Rust source code from configuration data, handling proper formatting, type annotations, and structural organization.
For information about the core data structures being formatted, see Core Data Structures. For details about the type system that drives code generation, see Type System.
Purpose and Scope
The output generation system serves as the final stage of the configuration processing pipeline, transforming internal Config
, ConfigItem
, and ConfigValue
structures into human-readable and machine-consumable formats. It handles:
- Format Selection: Supporting TOML and Rust code output formats
- Structural Organization: Managing tables, modules, and hierarchical output
- Type-Aware Generation: Converting values according to their configured types
- Formatting and Indentation: Ensuring readable, properly formatted output
- Comment Preservation: Maintaining documentation and type annotations
Output Format Architecture
The system centers around the OutputFormat
enum and Output
writer, which coordinate the transformation process.
Output Format Selection
flowchart TD subgraph subGraph2["Generation Methods"] TABLE_BEGIN["table_begin()"] TABLE_END["table_end()"] WRITE_ITEM["write_item()"] PRINT_LINES["print_lines()"] end subgraph subGraph1["Writer System"] OUTPUT["Output"] WRITER_STATE["writer statefmt: OutputFormatindent: usizeresult: String"] end subgraph subGraph0["Format Selection"] OF["OutputFormat"] TOML_FMT["OutputFormat::Toml"] RUST_FMT["OutputFormat::Rust"] end TOML_OUT["[table]key = value # type"] RUST_OUT["pub mod table {pub const KEY: Type = value;}"] OF --> OUTPUT OF --> RUST_FMT OF --> TOML_FMT OUTPUT --> PRINT_LINES OUTPUT --> TABLE_BEGIN OUTPUT --> TABLE_END OUTPUT --> WRITER_STATE OUTPUT --> WRITE_ITEM RUST_FMT --> RUST_OUT TOML_FMT --> TOML_OUT
Sources: axconfig-gen/src/output.rs(L3 - L32) axconfig-gen/src/output.rs(L34 - L48)
Value Conversion Pipeline
The conversion from internal values to output strings follows a type-aware transformation process.
Value to Output Conversion Flow
Sources: axconfig-gen/src/value.rs(L92 - L102) axconfig-gen/src/value.rs(L226 - L241) axconfig-gen/src/value.rs(L243 - L288)
Output Writer Implementation
The Output
struct manages the formatting state and provides methods for building structured output.
Core Writer Methods
Method | Purpose | TOML Behavior | Rust Behavior |
---|---|---|---|
table_begin() | Start a configuration section | Writes[table-name]header | Createspub mod table_name { |
table_end() | End a configuration section | No action needed | Writes closing} |
write_item() | Output a key-value pair | key = value # type | pub const KEY: Type = value; |
print_lines() | Handle multi-line comments | Preserves#comments | Converts to///doc comments |
Sources: axconfig-gen/src/output.rs(L71 - L93) axconfig-gen/src/output.rs(L95 - L136)
Formatting and Indentation
The system maintains proper indentation for nested structures:
flowchart TD subgraph subGraph1["Rust Module Nesting"] MOD_BEGIN["table_begin() → indent += 4"] MOD_END["table_end() → indent -= 4"] CONST_WRITE["write_item() uses current indent"] end subgraph subGraph0["Indentation Management"] INDENT_STATE["indent: usize"] PRINTLN_FMT["println_fmt()"] SPACES["format!('{:indent$}', '')"] end CONST_WRITE --> INDENT_STATE INDENT_STATE --> PRINTLN_FMT MOD_BEGIN --> INDENT_STATE MOD_END --> INDENT_STATE PRINTLN_FMT --> SPACES
Sources: axconfig-gen/src/output.rs(L54 - L60) axconfig-gen/src/output.rs(L82 - L84) axconfig-gen/src/output.rs(L89 - L92)
Type-Specific Value Generation
Different value types require specialized conversion logic for both TOML and Rust output formats.
Primitive Types
Value Type | TOML Output | Rust Output |
---|---|---|
Boolean | true,false | true,false |
Integer | 42,0xdead_beef | 42,0xdead_beef |
String | "hello" | "hello" |
Numeric String | "0xff" | 0xff(converted to number) |
Sources: axconfig-gen/src/value.rs(L228 - L230) axconfig-gen/src/value.rs(L245 - L255)
Collection Types
Array Conversion Logic
flowchart TD subgraph subGraph2["Rust Output"] SIMPLE_RUST["&[elem1, elem2, elem3]"] NESTED_RUST["&[elem1,elem2,]"] end subgraph subGraph1["TOML Output"] SIMPLE_TOML["[elem1, elem2, elem3]"] NESTED_TOML["[elem1,elem2]"] end subgraph subGraph0["Array Processing"] ARRAY_VAL["Value::Array"] CHECK_NESTED["contains nested arrays?"] ELEMENTS["convert each element"] end ARRAY_VAL --> CHECK_NESTED CHECK_NESTED --> ELEMENTS CHECK_NESTED --> NESTED_RUST CHECK_NESTED --> NESTED_TOML CHECK_NESTED --> SIMPLE_RUST CHECK_NESTED --> SIMPLE_TOML ELEMENTS --> NESTED_RUST ELEMENTS --> NESTED_TOML ELEMENTS --> SIMPLE_RUST ELEMENTS --> SIMPLE_TOML
Sources: axconfig-gen/src/value.rs(L231 - L240) axconfig-gen/src/value.rs(L267 - L285)
Tuple vs Array Distinction
The system distinguishes between homogeneous arrays and heterogeneous tuples:
Configuration | Type | TOML Output | Rust Output |
---|---|---|---|
[1, 2, 3] | [uint] | [1, 2, 3] | &[1, 2, 3] |
[1, "a", true] | (uint, str, bool) | [1, "a", true] | (1, "a", true) |
Sources: axconfig-gen/src/value.rs(L256 - L265) axconfig-gen/src/value.rs(L267 - L285)
Name Transformation
The system applies consistent naming conventions when converting between formats:
Identifier Transformation Rules
flowchart TD subgraph subGraph0["TOML to Rust Naming"] TOML_KEY["TOML key: 'some-config-key'"] MOD_TRANSFORM["mod_name(): replace '-' with '_'"] CONST_TRANSFORM["const_name(): UPPERCASE + replace '-' with '_'"] RUST_MOD["Rust module: 'some_config_key'"] RUST_CONST["Rust constant: 'SOME_CONFIG_KEY'"] end CONST_TRANSFORM --> RUST_CONST MOD_TRANSFORM --> RUST_MOD TOML_KEY --> CONST_TRANSFORM TOML_KEY --> MOD_TRANSFORM
Sources: axconfig-gen/src/output.rs(L138 - L144)
Error Handling in Output Generation
The system provides comprehensive error handling for output generation scenarios:
Error Condition | When It Occurs | Error Type |
---|---|---|
Unknown type for key | Type inference fails and no explicit type | ConfigErr::Other |
Value type mismatch | Value doesn't match expected type | ConfigErr::ValueTypeMismatch |
Array length mismatch | Tuple and array length differ | ConfigErr::ValueTypeMismatch |
Sources: axconfig-gen/src/output.rs(L120 - L125) axconfig-gen/src/value.rs(L257 - L259)
The output generation system provides a robust foundation for transforming configuration data into multiple target formats while preserving type information and maintaining readable formatting conventions.
axconfig-macros Package
Relevant source files
This document covers the axconfig-macros
procedural macro crate, which provides compile-time TOML configuration processing for the ArceOS ecosystem. The package transforms TOML configuration data into Rust constant definitions during compilation, enabling zero-cost configuration access at runtime.
For information about the CLI tool and library API that provides the core processing functionality, see axconfig-gen Package. For practical usage examples and TOML format specifications, see Configuration Examples.
Overview
The axconfig-macros
package provides two procedural macros that convert TOML configuration data into Rust constants at compile time:
parse_configs!
- Processes inline TOML stringsinclude_configs!
- Reads and processes external TOML files
Both macros leverage the core processing functionality from axconfig-gen
to parse TOML, infer types, and generate Rust code, but operate during compilation rather than as a separate build step.
Sources: axconfig-macros/README.md(L1 - L49) axconfig-macros/src/lib.rs(L1 - L143)
Macro Processing Architecture
flowchart TD subgraph subGraph3["File System"] TOML_FILES["TOML Config Files"] ENV_VARS["Environment VariablesCARGO_MANIFEST_DIR, AX_CONFIG_PATH"] MANIFEST_DIR["Project RootPath resolution"] end subgraph subGraph2["Rust Compilation"] TOKEN_STREAM["TokenStreamGenerated code"] COMPILER_ERROR["compiler_errorError handling"] QUOTE_MACRO["quote!Code generation"] end subgraph subGraph1["Core Integration"] CONFIG_FROM_TOML["Config::from_tomlTOML parsing"] CONFIG_DUMP["Config::dumpOutputFormat::Rust"] OUTPUT_FORMAT["OutputFormat::RustCode generation"] end subgraph subGraph0["Compile-Time Processing"] PARSE_MACRO["parse_configs!Inline TOML"] INCLUDE_MACRO["include_configs!File-based TOML"] ARGS_PARSER["IncludeConfigsArgsArgument parsing"] end ARGS_PARSER --> ENV_VARS ARGS_PARSER --> TOML_FILES COMPILER_ERROR --> TOKEN_STREAM CONFIG_DUMP --> OUTPUT_FORMAT CONFIG_FROM_TOML --> CONFIG_DUMP INCLUDE_MACRO --> ARGS_PARSER INCLUDE_MACRO --> MANIFEST_DIR OUTPUT_FORMAT --> TOKEN_STREAM PARSE_MACRO --> CONFIG_FROM_TOML TOKEN_STREAM --> QUOTE_MACRO TOML_FILES --> CONFIG_FROM_TOML
This diagram shows how the procedural macros integrate with the axconfig-gen
core processing pipeline and the Rust compilation system.
Sources: axconfig-macros/src/lib.rs(L22 - L41) axconfig-macros/src/lib.rs(L58 - L87) axconfig-macros/src/lib.rs(L89 - L143)
Macro Usage Patterns
parse_configs! Macro
The parse_configs!
macro processes inline TOML strings and generates corresponding Rust constants:
Input Type | Generated Output | Example |
---|---|---|
Global constants | pub const NAME: TYPE = VALUE; | pub const ARE_YOU_OK: bool = true; |
Table sections | pub mod table_name { ... } | pub mod hello { ... } |
Typed values | Type-annotated constants | pub const VALUE: isize = 456; |
The macro supports type annotations through TOML comments and automatic type inference from values.
Sources: axconfig-macros/README.md(L7 - L23) axconfig-macros/src/lib.rs(L16 - L41)
include_configs! Macro
The include_configs!
macro provides three path resolution strategies:
Sources: axconfig-macros/src/lib.rs(L58 - L87) axconfig-macros/src/lib.rs(L76 - L77) axconfig-macros/src/lib.rs(L83 - L86)
Implementation Details
Argument Parsing System
The IncludeConfigsArgs
enum handles the three different invocation patterns for include_configs!
:
flowchart TD subgraph subGraph2["Error Handling"] MISSING_ENV["Missing path_env parameter"] DUPLICATE_PARAM["Duplicate parameter error"] UNEXPECTED_PARAM["Unexpected parameter error"] end subgraph subGraph1["Parse Implementation"] PEEK_LITSTR["input.peek(LitStr)Check for direct path"] PARSE_IDENT["Ident parsingpath_env, fallback"] PARSE_EQUALS["Token![=] parsing"] PARSE_STRING["LitStr parsing"] DUPLICATE_CHECK["Duplicate parameter detection"] end subgraph subGraph0["IncludeConfigsArgs Variants"] PATH_VARIANT["Path(LitStr)Direct file path"] ENV_VARIANT["PathEnv(LitStr)Environment variable only"] FALLBACK_VARIANT["PathEnvFallback(LitStr, LitStr)Environment + fallback"] end DUPLICATE_CHECK --> DUPLICATE_PARAM DUPLICATE_CHECK --> ENV_VARIANT DUPLICATE_CHECK --> FALLBACK_VARIANT PARSE_EQUALS --> PARSE_STRING PARSE_IDENT --> PARSE_EQUALS PARSE_IDENT --> UNEXPECTED_PARAM PARSE_STRING --> DUPLICATE_CHECK PARSE_STRING --> MISSING_ENV PEEK_LITSTR --> PATH_VARIANT
Sources: axconfig-macros/src/lib.rs(L89 - L143) axconfig-macros/src/lib.rs(L95 - L142)
Error Handling and Compilation Integration
The macro implementation includes comprehensive error handling that integrates with the Rust compiler's diagnostic system:
Error Type | Function | Generated Output |
---|---|---|
TOML parsing errors | compiler_error | Compile-time error with span information |
File read failures | compiler_error | Error message with file path context |
Environment variable errors | compiler_error | Missing environment variable details |
Token parsing errors | LexErrorhandling | Rust lexer error propagation |
Sources: axconfig-macros/src/lib.rs(L12 - L14) axconfig-macros/src/lib.rs(L39 - L40) axconfig-macros/src/lib.rs(L63 - L67) axconfig-macros/src/lib.rs(L79 - L81)
Integration with axconfig-gen Core
flowchart TD subgraph subGraph2["Generated Output"] RUST_CONSTANTS["pub const declarations"] MODULE_STRUCTURE["pub mod organization"] TYPE_ANNOTATIONS["Inferred and explicit types"] end subgraph subGraph1["axconfig-gen Integration"] CONFIG_STRUCT["Config::from_tomlCore parsing logic"] OUTPUT_FORMAT["OutputFormat::RustCode generation"] TYPE_SYSTEM["Type inferenceConfigType, ConfigValue"] end subgraph subGraph0["axconfig-macros Layer"] PROC_MACRO["Procedural Macroproc_macro::TokenStream"] SYNTAX_PARSING["Syntax Parsingsyn, quote, proc_macro2"] PATH_RESOLUTION["Path ResolutionEnvironment variables, file system"] end CONFIG_STRUCT --> TYPE_SYSTEM OUTPUT_FORMAT --> MODULE_STRUCTURE OUTPUT_FORMAT --> RUST_CONSTANTS OUTPUT_FORMAT --> TYPE_ANNOTATIONS PATH_RESOLUTION --> CONFIG_STRUCT PROC_MACRO --> SYNTAX_PARSING SYNTAX_PARSING --> PATH_RESOLUTION TYPE_SYSTEM --> OUTPUT_FORMAT
The macros act as a procedural macro frontend to the core axconfig-gen
processing pipeline, handling the compile-time integration and file system operations while delegating the actual TOML processing and code generation to the shared library.
Sources: axconfig-macros/src/lib.rs(L10) axconfig-macros/src/lib.rs(L34 - L35)
Type System Integration
The macros inherit the full type system capabilities from axconfig-gen
, including:
- Automatic type inference from TOML values
- Explicit type annotations via TOML comments
- Complex type support for tuples, arrays, and nested structures
- Rust-native type mapping for generated constants
The generated code maintains type safety and zero-cost abstractions by producing compile-time constants rather than runtime configuration lookups.
Sources: axconfig-macros/README.md(L25 - L38) axconfig-macros/src/lib.rs(L34 - L35)
Macro Usage Patterns
Relevant source files
This document provides practical guidance for using the procedural macros provided by the axconfig-macros
crate. It covers the two primary macros (parse_configs!
and include_configs!
), their various invocation patterns, and best practices for integrating TOML configuration processing into Rust code at compile time.
For implementation details of how these macros work internally, see Macro Implementation. For TOML format specifications and examples, see TOML Configuration Format.
Basic Macro Invocation Patterns
The axconfig-macros
crate provides two fundamental macros for compile-time TOML processing: parse_configs!
for inline TOML content and include_configs!
for external file processing.
flowchart TD subgraph subGraph2["Generated Output"] CONSTANTS["pub const declarations"] MODULES["pub mod declarations"] TYPES["Type annotations"] end subgraph subGraph1["Input Sources"] TOML_STRING["Inline TOML String"] TOML_FILE["External TOML File"] ENV_PATH["Environment Variable Path"] FALLBACK["Fallback File Path"] end subgraph subGraph0["Macro Invocation Types"] INLINE["parse_configs!"] INCLUDE["include_configs!"] end CONSTANTS --> MODULES CONSTANTS --> TYPES ENV_PATH --> CONSTANTS FALLBACK --> CONSTANTS INCLUDE --> ENV_PATH INCLUDE --> FALLBACK INCLUDE --> TOML_FILE INLINE --> TOML_STRING TOML_FILE --> CONSTANTS TOML_STRING --> CONSTANTS
Macro Processing Flow
Inline TOML Processing
The parse_configs!
macro processes TOML content directly embedded in Rust source code. This pattern is useful for small, static configurations that don't require external file management.
Basic usage pattern as shown in axconfig-macros/README.md(L8 - L16) :
axconfig_macros::parse_configs!(r#"
are-you-ok = true
one-two-three = 123
[hello]
"one-two-three" = "456" # int
array = [1, 2, 3] # [uint]
tuple = [1, "abc", 3]
"#);
This generates compile-time constants that can be accessed immediately after the macro invocation, as demonstrated in axconfig-macros/README.md(L18 - L22)
Sources: axconfig-macros/README.md:8-22
External File Processing
The include_configs!
macro reads TOML configuration from external files at compile time. This pattern is preferred for larger configurations or when configuration files are shared across multiple projects.
File Path Resolution Strategies
Sources: axconfig-macros/README.md:40-48
File Inclusion Patterns
Direct File Path
The simplest file inclusion pattern specifies a direct path relative to CARGO_MANIFEST_DIR
:
axconfig_macros::include_configs!("path/to/config.toml");
This pattern is demonstrated in axconfig-macros/tests/example_config.rs(L5) where the test loads configuration from a relative path.
Environment Variable Path Resolution
For flexible deployment scenarios, the macro can resolve file paths from environment variables:
axconfig_macros::include_configs!(path_env = "AX_CONFIG_PATH");
This pattern allows the configuration file location to be determined at build time through environment variables, enabling different configurations for different build environments.
Environment Variable with Fallback
The most robust pattern combines environment variable resolution with a fallback path:
axconfig_macros::include_configs!(
path_env = "AX_CONFIG_PATH",
fallback = "path/to/defconfig.toml"
);
This ensures the build succeeds even when the environment variable is not set, defaulting to a known configuration file.
Sources: axconfig-macros/README.md:42-47, axconfig-macros/tests/example_config.rs:5
Type Annotation Patterns
Explicit Type Specification
Type annotations are specified using TOML comments following configuration values. The type system supports several categories:
Type Category | Syntax | Example |
---|---|---|
Boolean | # bool | enabled = true # bool |
Signed Integer | # int | offset = -10 # int |
Unsigned Integer | # uint | size = 1024 # uint |
String | # str | name = "test" # str |
Array | # [type] | values = [1, 2, 3] # [uint] |
Tuple | # (type1, type2, ...) | pair = [1, "abc"] # (uint, str) |
As shown in axconfig-macros/README.md(L13 - L15) type annotations directly influence the generated Rust code types.
Type Inference
When no explicit type annotation is provided, the macro attempts to infer types from TOML values:
flowchart TD subgraph subGraph1["Generated Types"] BOOL_TYPE["bool"] UINT_TYPE["usize"] STR_TYPE["&str"] ARRAY_TYPE["&[usize]"] TUPLE_TYPE["(T1, T2, ...)"] end subgraph subGraph0["Type Inference Rules"] TOML_VALUE["TOML Value"] BOOL_CHECK["Boolean Value?"] INT_CHECK["Integer Value?"] STR_CHECK["String Value?"] ARRAY_CHECK["Array Value?"] end ARRAY_CHECK --> ARRAY_TYPE ARRAY_CHECK --> TUPLE_TYPE BOOL_CHECK --> BOOL_TYPE BOOL_CHECK --> INT_CHECK INT_CHECK --> STR_CHECK INT_CHECK --> UINT_TYPE STR_CHECK --> ARRAY_CHECK STR_CHECK --> STR_TYPE TOML_VALUE --> BOOL_CHECK
Type Inference Decision Tree
Sources: axconfig-macros/README.md:25
Generated Code Structure
Constant Generation
The macros generate pub const
declarations for top-level configuration items and pub mod
declarations for TOML tables, as shown in axconfig-macros/README.md(L29 - L38) :
flowchart TD subgraph subGraph1["Generated Rust Code"] PUB_CONST["pub const ITEM_NAME"] PUB_MOD["pub mod table_name"] MOD_CONST["pub const ITEM_NAME (in module)"] end subgraph subGraph0["TOML Structure"] ROOT_ITEMS["Root-level items"] TABLES["[table] sections"] NESTED_ITEMS["Table items"] end NESTED_ITEMS --> MOD_CONST PUB_MOD --> MOD_CONST ROOT_ITEMS --> PUB_CONST TABLES --> PUB_MOD
TOML to Rust Code Mapping
Identifier Transformation
TOML keys are transformed to valid Rust identifiers following these rules:
- Hyphens are converted to underscores
- Snake_case is preserved
- Quoted keys are handled appropriately
- Case conversion follows Rust naming conventions
As demonstrated in axconfig-macros/README.md(L18 - L20) the key "one-two-three"
becomes the identifier ONE_TWO_THREE
.
Sources: axconfig-macros/README.md:29-38, axconfig-macros/README.md:18-20
Integration Patterns
Module-scoped Configuration
A common pattern is to define configuration within a dedicated module to avoid namespace pollution:
mod config {
include_configs!("../example-configs/defconfig.toml");
}
This pattern is used in axconfig-macros/tests/example_config.rs(L4 - L6) and allows accessing configuration through qualified paths like config::ARCH
.
Conditional Compilation
The macros can be used with conditional compilation for different build configurations:
#[cfg(feature = "nightly")]
mod config2 {
parse_configs!(include_str!("../../example-configs/defconfig.toml"));
}
This pattern, shown in axconfig-macros/tests/example_config.rs(L8 - L11) demonstrates combining parse_configs!
with include_str!
for feature-gated builds.
Testing Integration
Configuration modules can be compared for testing purposes, as demonstrated in the comprehensive comparison macro in axconfig-macros/tests/example_config.rs(L17 - L75) which validates that different macro invocation methods produce identical results.
Sources: axconfig-macros/tests/example_config.rs:4-11, axconfig-macros/tests/example_config.rs:17-75
Macro Implementation
Relevant source files
This document covers the technical implementation details of the procedural macros in the axconfig-macros
crate, including their integration with the core axconfig-gen
library. It explains how the macros process TOML configurations at compile time and generate Rust code using the proc-macro infrastructure.
For information about how to use these macros in practice, see Macro Usage Patterns. For details about the underlying configuration processing logic, see Library API.
Procedural Macro Architecture
The axconfig-macros
crate implements two procedural macros that provide compile-time TOML configuration processing by leveraging the core functionality from axconfig-gen
. The architecture separates parsing and code generation concerns from the macro expansion logic.
flowchart TD subgraph subGraph3["File System Operations"] ENV_VAR_RESOLUTION["Environment VariableResolution"] FILE_READING["std::fs::read_to_stringTOML file loading"] PATH_RESOLUTION["CARGO_MANIFEST_DIRPath resolution"] end subgraph subGraph2["Proc-Macro Infrastructure"] TOKEN_STREAM["TokenStreamInput parsing"] SYN_PARSE["syn::parseSyntax tree parsing"] QUOTE_GEN["quote!Code generation"] COMPILE_ERROR["Error::to_compile_errorCompilation errors"] end subgraph subGraph1["Core Processing (axconfig-gen)"] CONFIG_FROM_TOML["Config::from_tomlTOML parsing"] CONFIG_DUMP["Config::dumpOutputFormat::Rust"] RUST_CODE_GEN["Rust Code Generationpub const, pub mod"] end subgraph subGraph0["Compile-Time Processing"] USER_CODE["User Rust Codeparse_configs! / include_configs!"] MACRO_INVOKE["Macro InvocationTokenStream input"] MACRO_IMPL["Macro Implementationlib.rs"] end COMPILE_ERROR --> USER_CODE CONFIG_DUMP --> RUST_CODE_GEN CONFIG_FROM_TOML --> CONFIG_DUMP ENV_VAR_RESOLUTION --> PATH_RESOLUTION FILE_READING --> CONFIG_FROM_TOML MACRO_IMPL --> COMPILE_ERROR MACRO_IMPL --> ENV_VAR_RESOLUTION MACRO_IMPL --> FILE_READING MACRO_IMPL --> TOKEN_STREAM MACRO_INVOKE --> MACRO_IMPL QUOTE_GEN --> USER_CODE RUST_CODE_GEN --> QUOTE_GEN SYN_PARSE --> CONFIG_FROM_TOML TOKEN_STREAM --> SYN_PARSE USER_CODE --> MACRO_INVOKE
Sources: axconfig-macros/src/lib.rs(L1 - L144)
Macro Implementation Details
parse_configs! Macro
The parse_configs!
macro processes inline TOML strings and expands them into Rust code at compile time. It handles both regular compilation and nightly compiler features for enhanced expression expansion.
flowchart TD INPUT_TOKENS["TokenStreamTOML string literal"] NIGHTLY_EXPAND["proc_macro::expand_exprNightly feature"] PARSE_LITSTR["parse_macro_input!as LitStr"] EXTRACT_VALUE["LitStr::valueExtract TOML content"] CONFIG_PARSE["Config::from_tomlParse TOML structure"] CONFIG_DUMP["Config::dumpOutputFormat::Rust"] TOKEN_PARSE["code.parseString to TokenStream"] ERROR_HANDLING["compiler_errorLexError handling"] FINAL_TOKENS["TokenStreamGenerated Rust code"] CONFIG_DUMP --> ERROR_HANDLING CONFIG_DUMP --> TOKEN_PARSE CONFIG_PARSE --> CONFIG_DUMP CONFIG_PARSE --> ERROR_HANDLING ERROR_HANDLING --> FINAL_TOKENS EXTRACT_VALUE --> CONFIG_PARSE INPUT_TOKENS --> NIGHTLY_EXPAND INPUT_TOKENS --> PARSE_LITSTR NIGHTLY_EXPAND --> PARSE_LITSTR PARSE_LITSTR --> EXTRACT_VALUE TOKEN_PARSE --> ERROR_HANDLING TOKEN_PARSE --> FINAL_TOKENS
The implementation includes conditional compilation for nightly features:
Feature | Functionality | Implementation |
---|---|---|
Nightly | Enhanced expression expansion | config_toml.expand_expr() |
Stable | Standard literal parsing | parse_macro_input!(config_toml as LitStr) |
Error Handling | Compilation error generation | compiler_error()function |
Sources: axconfig-macros/src/lib.rs(L22 - L41)
include_configs! Macro
The include_configs!
macro supports three different path specification methods and handles file system operations with comprehensive error handling.
flowchart TD subgraph subGraph2["File Processing"] FILE_READ["std::fs::read_to_stringTOML file reading"] PARSE_CONFIGS_CALL["parse_configs!Recursive macro call"] QUOTE_MACRO["quote!Token generation"] end subgraph subGraph1["Path Resolution"] ENV_VAR_LOOKUP["std::env::varEnvironment variable lookup"] FALLBACK_LOGIC["Fallback pathhandling"] CARGO_MANIFEST["CARGO_MANIFEST_DIRRoot directory resolution"] PATH_JOIN["std::path::Path::joinFull path construction"] end subgraph subGraph0["Argument Parsing"] ARGS_INPUT["TokenStreamMacro arguments"] PARSE_ARGS["IncludeConfigsArgs::parseCustom argument parser"] PATH_DIRECT["Path variantDirect file path"] PATH_ENV["PathEnv variantEnvironment variable"] PATH_ENV_FALLBACK["PathEnvFallback variantEnv var + fallback"] end ARGS_INPUT --> PARSE_ARGS CARGO_MANIFEST --> PATH_JOIN ENV_VAR_LOOKUP --> FALLBACK_LOGIC FALLBACK_LOGIC --> CARGO_MANIFEST FILE_READ --> PARSE_CONFIGS_CALL PARSE_ARGS --> PATH_DIRECT PARSE_ARGS --> PATH_ENV PARSE_ARGS --> PATH_ENV_FALLBACK PARSE_CONFIGS_CALL --> QUOTE_MACRO PATH_DIRECT --> CARGO_MANIFEST PATH_ENV --> ENV_VAR_LOOKUP PATH_ENV_FALLBACK --> ENV_VAR_LOOKUP PATH_JOIN --> FILE_READ
Sources: axconfig-macros/src/lib.rs(L58 - L87)
Argument Parsing Implementation
The IncludeConfigsArgs
enum and its Parse
implementation handle the complex argument parsing for the include_configs!
macro with proper error reporting.
stateDiagram-v2 [*] --> CheckFirstToken CheckFirstToken --> DirectPath : LitStr CheckFirstToken --> ParseParameters : Ident DirectPath --> [*] : Path(LitStr) ParseParameters --> ReadIdent ReadIdent --> CheckEquals : ident CheckEquals --> ReadString : = ReadString --> ProcessParameter : LitStr ProcessParameter --> SetPathEnv : "path_env" ProcessParameter --> SetFallback : "fallback" ProcessParameter --> Error : unknown parameter SetPathEnv --> CheckComma SetFallback --> CheckComma CheckComma --> ReadIdent : more tokens CheckComma --> ValidateResult : end of input ValidateResult --> PathEnvOnly : env only ValidateResult --> PathEnvWithFallback : env + fallback ValidateResult --> Error : invalid combination PathEnvOnly --> [*] : PathEnv(LitStr) PathEnvWithFallback --> [*] : PathEnvFallback(LitStr, LitStr) Error --> [*] : ParseError
The parsing logic handles these parameter combinations:
Syntax | Enum Variant | Behavior |
---|---|---|
"path/to/file.toml" | Path(LitStr) | Direct file path |
path_env = "ENV_VAR" | PathEnv(LitStr) | Environment variable only |
path_env = "ENV_VAR", fallback = "default.toml" | PathEnvFallback(LitStr, LitStr) | Environment variable with fallback |
Sources: axconfig-macros/src/lib.rs(L89 - L143)
Error Handling and Compilation
The macro implementation includes comprehensive error handling that generates meaningful compilation errors for various failure scenarios.
Compiler Error Generation
The compiler_error
function provides a centralized mechanism for generating compilation errors that integrate properly with the Rust compiler's diagnostic system.
flowchart TD subgraph subGraph2["Compiler Integration"] RUST_COMPILER["Rust CompilerError reporting"] BUILD_FAILURE["Build FailureClear error messages"] end subgraph subGraph1["Error Processing"] COMPILER_ERROR_FN["compiler_errorError::new_spanned"] TO_COMPILE_ERROR["Error::to_compile_errorTokenStream generation"] ERROR_SPAN["Span informationSource location"] end subgraph subGraph0["Error Sources"] TOML_PARSE_ERROR["TOML Parse ErrorConfig::from_toml"] FILE_READ_ERROR["File Read Errorstd::fs::read_to_string"] ENV_VAR_ERROR["Environment Variable Errorstd::env::var"] LEX_ERROR["Lexical ErrorTokenStream parsing"] SYNTAX_ERROR["Syntax ErrorArgument parsing"] end COMPILER_ERROR_FN --> ERROR_SPAN COMPILER_ERROR_FN --> TO_COMPILE_ERROR ENV_VAR_ERROR --> COMPILER_ERROR_FN FILE_READ_ERROR --> COMPILER_ERROR_FN LEX_ERROR --> COMPILER_ERROR_FN RUST_COMPILER --> BUILD_FAILURE SYNTAX_ERROR --> COMPILER_ERROR_FN TOML_PARSE_ERROR --> COMPILER_ERROR_FN TO_COMPILE_ERROR --> RUST_COMPILER
Sources: axconfig-macros/src/lib.rs(L12 - L14) axconfig-macros/src/lib.rs(L36 - L40) axconfig-macros/src/lib.rs(L63 - L67) axconfig-macros/src/lib.rs(L79 - L81)
Integration with Build System
The macros integrate with Cargo's build system through environment variable resolution and dependency tracking that ensures proper rebuilds when configuration files change.
Build-Time Path Resolution
The implementation uses CARGO_MANIFEST_DIR
to resolve relative paths consistently across different build environments:
Environment Variable | Purpose | Usage |
---|---|---|
CARGO_MANIFEST_DIR | Project root directory | Path resolution base |
User-defined env vars | Config file paths | Dynamic path specification |
Dependency Tracking
The file-based operations in include_configs!
automatically create implicit dependencies that Cargo uses for rebuild detection:
flowchart TD subgraph subGraph2["Build Outputs"] COMPILED_BINARY["Compiled binarywith embedded config"] BUILD_CACHE["Build cacheDependency information"] end subgraph subGraph1["Build Process"] MACRO_EXPANSION["Macro expansionFile read operation"] CARGO_TRACKING["Cargo dependency trackingImplicit file dependency"] REBUILD_DETECTION["Rebuild detectionFile modification time"] end subgraph subGraph0["Source Files"] RUST_SOURCE["Rust source filewith include_configs!"] TOML_CONFIG["TOML config filereferenced by macro"] end CARGO_TRACKING --> BUILD_CACHE CARGO_TRACKING --> REBUILD_DETECTION MACRO_EXPANSION --> CARGO_TRACKING REBUILD_DETECTION --> COMPILED_BINARY RUST_SOURCE --> MACRO_EXPANSION TOML_CONFIG --> MACRO_EXPANSION TOML_CONFIG --> REBUILD_DETECTION
Sources: axconfig-macros/src/lib.rs(L76 - L77) axconfig-macros/tests/example_config.rs(L4 - L5)
Testing Integration
The macro implementation includes comprehensive testing that validates both the generated code and the macro expansion process itself.
The test structure demonstrates proper integration between the macros and expected outputs:
flowchart TD subgraph subGraph2["Test Execution"] MOD_CMP_MACRO["mod_cmp! macroModule comparison"] ASSERT_STATEMENTS["assert_eq! callsValue validation"] TEST_FUNCTIONS["test functionsinclude and parse tests"] end subgraph subGraph1["Test Data"] DEFCONFIG_TOML["defconfig.tomlTest input"] OUTPUT_RS["output.rsExpected generated code"] INCLUDE_STR["include_str!String inclusion"] end subgraph subGraph0["Test Configuration"] TEST_FILE["example_config.rsTest definitions"] CONFIG_MODULE["config moduleinclude_configs! usage"] CONFIG2_MODULE["config2 moduleparse_configs! usage"] EXPECTED_MODULE["config_expect moduleExpected output"] end ASSERT_STATEMENTS --> CONFIG2_MODULE ASSERT_STATEMENTS --> CONFIG_MODULE ASSERT_STATEMENTS --> EXPECTED_MODULE CONFIG2_MODULE --> INCLUDE_STR CONFIG_MODULE --> DEFCONFIG_TOML EXPECTED_MODULE --> OUTPUT_RS INCLUDE_STR --> DEFCONFIG_TOML MOD_CMP_MACRO --> ASSERT_STATEMENTS TEST_FUNCTIONS --> MOD_CMP_MACRO
Sources: axconfig-macros/tests/example_config.rs(L1 - L87)
Configuration Examples
Relevant source files
This page demonstrates how the axconfig-gen system transforms TOML configuration files into various output formats. It provides concrete examples showing input configurations and their corresponding generated outputs in both TOML and Rust formats. For information about the TOML input format specification, see TOML Configuration Format. For detailed output format documentation, see Generated Output Examples.
Complete Configuration Example
The following example demonstrates a complete ArceOS configuration transformation, showing how a defconfig TOML file is processed into both normalized TOML and Rust code outputs.
Input Configuration Structure
Sources: example-configs/defconfig.toml(L1 - L63)
Transformation Pipeline
flowchart TD subgraph subGraph2["Output Generation"] TOML_GEN["TOML Generator"] RUST_GEN["Rust Code Generator"] TOML_OUT["output.toml"] RUST_OUT["output.rs"] end subgraph subGraph1["Core Processing"] CONFIG_STRUCT["Config Structure"] GLOBAL_TABLE["Global ConfigItem entries"] NAMED_TABLES["Named table sections"] VALIDATION["Type Validation"] end subgraph subGraph0["Input Processing"] TOML_IN["defconfig.toml"] PARSE["TOML Parser"] TYPE_INFER["Type Inference from Comments"] end CONFIG_STRUCT --> GLOBAL_TABLE CONFIG_STRUCT --> NAMED_TABLES GLOBAL_TABLE --> VALIDATION NAMED_TABLES --> VALIDATION PARSE --> TYPE_INFER RUST_GEN --> RUST_OUT TOML_GEN --> TOML_OUT TOML_IN --> PARSE TYPE_INFER --> CONFIG_STRUCT VALIDATION --> RUST_GEN VALIDATION --> TOML_GEN
Sources: example-configs/defconfig.toml(L1 - L63) example-configs/output.toml(L1 - L63) example-configs/output.rs(L1 - L66)
Data Type Transformations
The configuration system supports multiple data types with automatic conversion between TOML values and Rust types:
TOML Type | Example Input | Type Annotation | Rust Output |
---|---|---|---|
String | arch = "x86_64" | # str | pub const ARCH: &str = "x86_64"; |
Integer | smp = 1 | # uint | pub const SMP: usize = 1; |
Hex Integer | phys-memory-size = 0x800_0000 | # uint | pub const PHYS_MEMORY_SIZE: usize = 0x800_0000; |
String as Integer | kernel-base-vaddr = "0xffff_ff80_0020_0000" | # uint | pub const KERNEL_BASE_VADDR: usize = 0xffff_ff80_0020_0000; |
Array of Arrays | mmio-regions = [["0xb000_0000", "0x1000_0000"]] | # [(uint, uint)] | pub const MMIO_REGIONS: &[(usize, usize)] = &[(0xb000_0000, 0x1000_0000)]; |
Empty Array | virtio-mmio-regions = [] | # [(uint, uint)] | pub const VIRTIO_MMIO_REGIONS: &[(usize, usize)] = &[]; |
Sources: example-configs/defconfig.toml(L2 - L56) example-configs/output.rs(L2 - L27)
Structure Mapping Between Formats
The system maps TOML structure to Rust module hierarchy:
flowchart TD subgraph subGraph1["Rust Module Structure"] RUST_ROOT["Root constants"] RUST_KERNEL["pub mod kernel"] RUST_PLATFORM["pub mod platform"] RUST_DEVICES["pub mod devices"] end subgraph subGraph0["TOML Structure"] TOML_ROOT["Root level items"] TOML_KERNEL["[kernel] section"] TOML_PLATFORM["[platform] section"] TOML_DEVICES["[devices] section"] end subgraph subGraph2["Naming Conventions"] KEBAB["kebab-case"] SNAKE["SCREAMING_SNAKE_CASE"] end KEBAB --> SNAKE TOML_DEVICES --> RUST_DEVICES TOML_KERNEL --> RUST_KERNEL TOML_PLATFORM --> RUST_PLATFORM TOML_ROOT --> RUST_ROOT
Sources: example-configs/defconfig.toml(L11 - L46) example-configs/output.rs(L11 - L65)
Naming Convention Examples
TOML Name | Rust Constant |
---|---|
task-stack-size | TASK_STACK_SIZE |
phys-memory-base | PHYS_MEMORY_BASE |
kernel-base-vaddr | KERNEL_BASE_VADDR |
mmio-regions | MMIO_REGIONS |
pci-ecam-base | PCI_ECAM_BASE |
Sources: example-configs/defconfig.toml(L13 - L58) example-configs/output.rs(L13 - L57)
Comment and Documentation Preservation
The system preserves comments from TOML files as Rust documentation:
TOML Comments
# Architecture identifier.
arch = "x86_64" # str
# Stack size of each task.
task-stack-size = 0 # uint
# MMIO regions with format (`base_paddr`, `size`).
mmio-regions = [
["0xb000_0000", "0x1000_0000"], # PCI config space
["0xfe00_0000", "0xc0_0000"], # PCI devices
] # [(uint, uint)]
Generated Rust Documentation
/// Architecture identifier.
pub const ARCH: &str = "x86_64";
/// Stack size of each task.
pub const TASK_STACK_SIZE: usize = 0;
/// MMIO regions with format (`base_paddr`, `size`).
pub const MMIO_REGIONS: &[(usize, usize)] = &[
(0xb000_0000, 0x1000_0000),
(0xfe00_0000, 0xc0_0000),
// ... more entries
];
Sources: example-configs/defconfig.toml(L1 - L54) example-configs/output.rs(L1 - L35)
Array Processing Examples
The system handles complex array structures, converting TOML arrays to Rust slices:
Input Array Format
mmio-regions = [
["0xb000_0000", "0x1000_0000"], # PCI config space
["0xfe00_0000", "0xc0_0000"], # PCI devices
["0xfec0_0000", "0x1000"], # IO APIC
["0xfed0_0000", "0x1000"], # HPET
["0xfee0_0000", "0x1000"], # Local APIC
] # [(uint, uint)]
Generated Rust Array
pub const MMIO_REGIONS: &[(usize, usize)] = &[
(0xb000_0000, 0x1000_0000),
(0xfe00_0000, 0xc0_0000),
(0xfec0_0000, 0x1000),
(0xfed0_0000, 0x1000),
(0xfee0_0000, 0x1000),
];
Sources: example-configs/defconfig.toml(L48 - L54) example-configs/output.rs(L13 - L19)
Output Format Comparison
Both output formats maintain the same logical structure but serve different purposes:
Aspect | TOML Output | Rust Output |
---|---|---|
Purpose | Configuration interchange | Compile-time constants |
Structure | Flat sections with key-value pairs | Nested modules with constants |
Types | TOML native types with annotations | Strongly-typed Rust constants |
Usage | Runtime configuration loading | Direct code integration |
Comments | Preserved as TOML comments | Converted to doc comments |
The TOML output in example-configs/output.toml(L1 - L63) maintains the original structure while normalizing formatting. The Rust output in example-configs/output.rs(L1 - L66) provides type-safe compile-time access to the same configuration data.
Sources: example-configs/output.toml(L1 - L63) example-configs/output.rs(L1 - L66)
TOML Configuration Format
Relevant source files
This document specifies the TOML input format used by axconfig-gen for configuration processing. It covers the structure, type annotation system, supported data types, and ArceOS-specific conventions for writing configuration files.
For information about how configurations are processed into output formats, see Generated Output Examples. For details about the CLI tool that processes these TOML files, see Command Line Interface.
Basic TOML Structure
The axconfig-gen system processes TOML files organized into a two-level hierarchy: a global table for top-level configuration items and named tables for grouped configurations.
flowchart TD subgraph subGraph2["ConfigItem Internal Structure"] ITEM["ConfigItem"] VALUES["values: HashMap"] COMMENTS["comments: HashMap"] end subgraph subGraph1["Mapping to Config Structure"] CONFIG["Config"] GLOBAL_ITEMS["global: ConfigItem"] TABLE_ITEMS["tables: HashMap"] end subgraph subGraph0["TOML File Structure"] FILE["TOML Configuration File"] GLOBAL["Global Table(top-level keys)"] NAMED["Named Tables([section] headers)"] end CONFIG --> GLOBAL_ITEMS CONFIG --> TABLE_ITEMS FILE --> GLOBAL FILE --> NAMED GLOBAL --> CONFIG GLOBAL_ITEMS --> ITEM ITEM --> COMMENTS ITEM --> VALUES NAMED --> CONFIG TABLE_ITEMS --> ITEM
TOML Structure to Internal Representation Mapping
The parser maps TOML sections directly to the internal Config
structure, where global keys become part of the global
ConfigItem
and each [section]
becomes a named table entry.
Sources: README.md(L42 - L65) example-configs/defconfig.toml(L1 - L63)
Type Annotation System
Type information is specified through inline comments immediately following configuration values. This annotation system enables precise Rust code generation with correct type definitions.
flowchart TD subgraph subGraph2["Value Integration"] VALUE_PARSE["Value parsing"] CONFIG_VALUE["ConfigValue"] TYPE_ASSIGN["Type assignment"] FINAL["ConfigValue with ConfigType"] end subgraph subGraph1["Type Processing"] TYPE_STR["Type String:'uint', 'bool', '[int]', etc."] TYPE_PARSE["ConfigType::from_str()"] TYPE_OBJ["ConfigType enum variant"] end subgraph subGraph0["TOML Parsing Flow"] INPUT["TOML Line:key = value # type_annotation"] PARSE["toml_edit parsing"] EXTRACT["Comment Extraction"] end CONFIG_VALUE --> TYPE_ASSIGN EXTRACT --> TYPE_STR INPUT --> PARSE PARSE --> EXTRACT PARSE --> VALUE_PARSE TYPE_ASSIGN --> FINAL TYPE_OBJ --> TYPE_ASSIGN TYPE_PARSE --> TYPE_OBJ TYPE_STR --> TYPE_PARSE VALUE_PARSE --> CONFIG_VALUE
Type Annotation Processing Pipeline
The system extracts type annotations from comments and converts them to ConfigType
instances for type-safe code generation.
Type Annotation Syntax
Annotation | ConfigType | Rust Output Type | Example |
---|---|---|---|
# bool | ConfigType::Bool | bool | enabled = true # bool |
# int | ConfigType::Int | isize | offset = -10 # int |
# uint | ConfigType::Uint | usize | size = 1024 # uint |
# str | ConfigType::Str | &str | name = "test" # str |
# [uint] | ConfigType::Array(Uint) | &[usize] | ports = [80, 443] # [uint] |
# (uint, str) | ConfigType::Tuple([Uint, Str]) | (usize, &str) | pair = [1, "a"] # (uint, str) |
Sources: README.md(L35 - L36) README.md(L47 - L48) example-configs/defconfig.toml(L2 - L3)
Supported Data Types
The type system supports primitive types, collections, and nested structures to accommodate complex ArceOS configuration requirements.
flowchart TD subgraph subGraph3["ConfigType Mapping"] CONFIG_BOOL["ConfigType::Bool"] CONFIG_INT["ConfigType::Int"] CONFIG_UINT["ConfigType::Uint"] CONFIG_STR["ConfigType::Str"] CONFIG_ARRAY["ConfigType::Array(inner)"] CONFIG_TUPLE["ConfigType::Tuple(Vec)"] end subgraph subGraph2["Value Representations"] DECIMAL["Decimal: 1024"] HEX_INT["Hex Integer: 0x1000"] HEX_STR["Hex String: '0xffff_ff80_0000_0000'"] ARRAY_VAL["Array: [1, 2, 3]"] TUPLE_VAL["Tuple: [value1, value2]"] end subgraph subGraph1["Collection Types"] ARRAY["[type]homogeneous arrays"] TUPLE["(type1, type2, ...)heterogeneous tuples"] end subgraph subGraph0["Primitive Types"] BOOL["booltrue/false values"] INT["intsigned integers"] UINT["uintunsigned integers"] STR["strstring values"] end ARRAY --> CONFIG_ARRAY ARRAY_VAL --> ARRAY BOOL --> CONFIG_BOOL DECIMAL --> UINT HEX_INT --> UINT HEX_STR --> UINT INT --> CONFIG_INT STR --> CONFIG_STR TUPLE --> CONFIG_TUPLE TUPLE_VAL --> TUPLE UINT --> CONFIG_UINT
ConfigType System and Value Mappings
Complex Type Examples
Array Types: Support homogeneous collections with type-safe element access:
mmio-regions = [
["0xb000_0000", "0x1000_0000"],
["0xfe00_0000", "0xc0_0000"]
] # [(uint, uint)]
Tuple Types: Enable heterogeneous data grouping:
endpoint = ["192.168.1.1", 8080, true] # (str, uint, bool)
Sources: example-configs/defconfig.toml(L48 - L54) README.md(L47 - L49)
ArceOS Configuration Conventions
ArceOS configurations follow established patterns for organizing system, platform, and device specifications into logical groupings.
Standard Configuration Sections
flowchart TD subgraph subGraph4["Devices Section Details"] MMIO["mmio-regions: [(uint, uint)]"] VIRTIO["virtio-mmio-regions: [(uint, uint)]"] PCI["pci-ecam-base: uint"] end subgraph subGraph3["Platform Section Details"] PHYS_BASE["phys-memory-base: uint"] PHYS_SIZE["phys-memory-size: uint"] KERN_BASE["kernel-base-paddr: uint"] VIRT_MAP["phys-virt-offset: uint"] end subgraph subGraph2["Kernel Section Details"] STACK["task-stack-size: uint"] TICKS["ticks-per-sec: uint"] end subgraph subGraph1["Root Level Details"] ARCH["arch: strArchitecture identifier"] PLAT["plat: strPlatform identifier"] SMP["smp: uintCPU count"] end subgraph subGraph0["ArceOS Configuration Structure"] ROOT["Root Levelarch, plat, smp"] KERNEL["[kernel] SectionRuntime parameters"] PLATFORM["[platform] SectionMemory layout & hardware"] DEVICES["[devices] SectionHardware specifications"] end DEVICES --> MMIO DEVICES --> PCI DEVICES --> VIRTIO KERNEL --> STACK KERNEL --> TICKS PLATFORM --> KERN_BASE PLATFORM --> PHYS_BASE PLATFORM --> PHYS_SIZE PLATFORM --> VIRT_MAP ROOT --> ARCH ROOT --> PLAT ROOT --> SMP
ArceOS Standard Configuration Organization
Memory Address Conventions
ArceOS uses specific patterns for memory address specification:
Address Type | Format | Example | Purpose |
---|---|---|---|
Physical addresses | Hex integers | 0x20_0000 | Hardware memory locations |
Virtual addresses | Hex strings | "0xffff_ff80_0020_0000" | Kernel virtual memory |
Memory regions | Tuple arrays | [["0xb000_0000", "0x1000_0000"]] | MMIO ranges |
Size specifications | Hex integers | 0x800_0000 | Memory region sizes |
Sources: example-configs/defconfig.toml(L22 - L39) example-configs/defconfig.toml(L48 - L62)
Key Naming and Value Formats
The configuration system supports flexible key naming and multiple value representation formats to accommodate diverse configuration needs.
Key Naming Conventions
# Standard kebab-case keys
task-stack-size = 0x1000 # uint
# Quoted keys for special characters
"one-two-three" = 456 # int
# Mixed naming in different contexts
kernel-base-paddr = 0x20_0000 # uint
"phys-memory-base" = 0 # uint
Value Format Support
Value Type | TOML Representation | Internal Processing | Output |
---|---|---|---|
Decimal integers | 1024 | Direct parsing | 1024 |
Hex integers | 0x1000 | Hex parsing | 4096 |
Hex strings | "0xffff_ff80" | String + type hint | 0xffff_ff80_usize |
Underscore separators | 0x800_0000 | Ignored in parsing | 0x8000000 |
String literals | "x86_64" | String preservation | "x86_64" |
Boolean values | true/false | Direct mapping | true/false |
Type Inference Rules
When no explicit type annotation is provided:
flowchart TD VALUE["TOML Value"] CHECK_BOOL["Is boolean?"] CHECK_INT["Is integer?"] CHECK_STR["Is string?"] CHECK_ARRAY["Is array?"] INFER_BOOL["ConfigType::Bool"] INFER_UINT["ConfigType::Uint"] INFER_STR["ConfigType::Str"] INFER_ARRAY["ConfigType::Array(infer element type)"] CHECK_ARRAY --> INFER_ARRAY CHECK_BOOL --> CHECK_INT CHECK_BOOL --> INFER_BOOL CHECK_INT --> CHECK_STR CHECK_INT --> INFER_UINT CHECK_STR --> CHECK_ARRAY CHECK_STR --> INFER_STR VALUE --> CHECK_BOOL
Type Inference Decision Tree
The system defaults to unsigned integers for numeric values and attempts to infer array element types recursively.
Sources: README.md(L35 - L36) example-configs/defconfig.toml(L1 - L7) example-configs/defconfig.toml(L28 - L32)
Generated Output Examples
Relevant source files
This page demonstrates the output generation capabilities of axconfig-gen by showing concrete examples of how TOML input configurations are transformed into both cleaned TOML and Rust code outputs. The examples illustrate type mappings, naming conventions, structure preservation, and comment handling across different output formats.
For information about the input TOML configuration format and type annotation system, see TOML Configuration Format. For details on the underlying output generation implementation, see Output Generation.
Transformation Process Overview
The axconfig-gen system processes input TOML configurations through a structured pipeline that preserves semantic meaning while adapting to different output format requirements.
Transformation Pipeline
flowchart TD subgraph subGraph0["Format-Specific Processing"] TOML_CLEAN["TOML CleanupFormatting & Validation"] RUST_GEN["Rust Code GenerationConstants & Modules"] end INPUT["Input TOML(defconfig.toml)"] PARSE["TOML Parsing(toml_edit)"] STRUCT["Config Structure(Config, ConfigItem)"] TYPE["Type Processing(ConfigType, ConfigValue)"] TOML_OUT["Generated TOML(output.toml)"] RUST_OUT["Generated Rust(output.rs)"] INPUT --> PARSE PARSE --> STRUCT RUST_GEN --> RUST_OUT STRUCT --> TYPE TOML_CLEAN --> TOML_OUT TYPE --> RUST_GEN TYPE --> RUST_OUT TYPE --> TOML_CLEAN TYPE --> TOML_OUT
Sources: example-configs/defconfig.toml(L1 - L63) example-configs/output.toml(L1 - L63) example-configs/output.rs(L1 - L66)
Global Configuration Items
Global configuration items defined at the root level of the TOML are transformed into top-level constants in Rust output while being preserved with consistent formatting in TOML output.
Input TOML Configuration:
# Architecture identifier.
arch = "x86_64" # str
# Platform identifier.
plat = "x86_64-qemu-q35" # str
# Number of CPUs.
smp = 1 # uint
Generated TOML Output:
# Architecture identifier.
arch = "x86_64" # str
# Platform identifier.
plat = "x86_64-qemu-q35" # str
# Number of CPUs.
smp = 1 # uint
Generated Rust Output:
/// Architecture identifier.
pub const ARCH: &str = "x86_64";
/// Platform identifier.
pub const PLAT: &str = "x86_64-qemu-q35";
/// Number of CPUs.
pub const SMP: usize = 1;
Transformations Applied:
Aspect | Input | TOML Output | Rust Output |
---|---|---|---|
Naming | arch | arch | ARCH |
Type | "x86_64"(str) | "x86_64" # str | &str |
Comments | # Architecture identifier. | Preserved | /// Architecture identifier. |
Formatting | Variable spacing | Consistent spacing | Rust doc comments |
Sources: example-configs/defconfig.toml(L1 - L6) example-configs/output.toml(L1 - L6) example-configs/output.rs(L1 - L6)
Sectioned Configuration
TOML table sections are transformed into Rust modules, maintaining the hierarchical organization while adapting naming conventions for each output format.
Input TOML Configuration:
[kernel]
# Stack size of each task.
task-stack-size = 0 # uint
# Number of timer ticks per second (Hz).
ticks-per-sec = 0 # uint
Generated TOML Output:
[kernel]
# Stack size of each task.
task-stack-size = 0 # uint
# Number of timer ticks per second (Hz).
ticks-per-sec = 0 # uint
Generated Rust Output:
///
/// Kernel configs
///
pub mod kernel {
/// Stack size of each task.
pub const TASK_STACK_SIZE: usize = 0;
/// Number of timer ticks per second (Hz).
pub const TICKS_PER_SEC: usize = 0;
}
Structure Mapping:
flowchart TD TOML_TABLE["[kernel]TOML Table"] RUST_MOD["pub mod kernelRust Module"] TOML_ITEM1["task-stack-size = 0"] TOML_ITEM2["ticks-per-sec = 0"] RUST_CONST1["pub const TASK_STACK_SIZE: usize = 0"] RUST_CONST2["pub const TICKS_PER_SEC: usize = 0"] RUST_MOD --> RUST_CONST1 RUST_MOD --> RUST_CONST2 TOML_ITEM1 --> RUST_CONST1 TOML_ITEM2 --> RUST_CONST2 TOML_TABLE --> TOML_ITEM1 TOML_TABLE --> TOML_ITEM2
Sources: example-configs/defconfig.toml(L11 - L16) example-configs/output.toml(L32 - L37) example-configs/output.rs(L33 - L39)
Complex Data Types
Array and tuple types demonstrate sophisticated type mapping between TOML array syntax and Rust slice references, including proper handling of nested structures.
Input TOML Configuration:
[devices]
# MMIO regions with format (`base_paddr`, `size`).
mmio-regions = [
["0xb000_0000", "0x1000_0000"], # PCI config space
["0xfe00_0000", "0xc0_0000"], # PCI devices
["0xfec0_0000", "0x1000"], # IO APIC
] # [(uint, uint)]
Generated TOML Output:
[devices]
# MMIO regions with format (`base_paddr`, `size`).
mmio-regions = [
["0xb000_0000", "0x1000_0000"],
["0xfe00_0000", "0xc0_0000"],
["0xfec0_0000", "0x1000"],
] # [(uint, uint)]
Generated Rust Output:
pub mod devices {
/// MMIO regions with format (`base_paddr`, `size`).
pub const MMIO_REGIONS: &[(usize, usize)] = &[
(0xb000_0000, 0x1000_0000),
(0xfe00_0000, 0xc0_0000),
(0xfec0_0000, 0x1000),
];
}
Type System Mappings:
flowchart TD subgraph subGraph1["Rust Types"] RUST_SLICE["Slice Reference&[(usize, usize)]"] RUST_TUPLE["Tuple Literals(0xb000_0000, 0x1000_0000)"] RUST_TYPE["Rust Typeusize"] end subgraph subGraph0["TOML Types"] TOML_ARR["Array of Arrays[[str, str], ...]"] TOML_STR["String Literals'0xb000_0000'"] TOML_TYPE["Type Annotation# [(uint, uint)]"] end TOML_ARR --> RUST_SLICE TOML_STR --> RUST_TUPLE TOML_TYPE --> RUST_TYPE
Sources: example-configs/defconfig.toml(L48 - L54) example-configs/output.toml(L13 - L19) example-configs/output.rs(L13 - L19)
Type System and Naming Conventions
The transformation process applies consistent rules for type mapping and identifier naming across output formats.
Type Mapping Rules:
TOML Type Annotation | TOML Value | Rust Type | Rust Value |
---|---|---|---|
# str | "x86_64" | &str | "x86_64" |
# uint | 1 | usize | 1 |
# uint | "0xffff_ff80_0000_0000" | usize | 0xffff_ff80_0000_0000 |
# [(uint, uint)] | [["0x1000", "0x2000"]] | &[(usize, usize)] | &[(0x1000, 0x2000)] |
Naming Convention Transformations:
flowchart TD subgraph subGraph2["Rust Output"] SNAKE["SCREAMING_SNAKE_CASETASK_STACK_SIZE"] MODULE["snake_case modulesection_name"] end subgraph subGraph1["TOML Output"] KEBAB_OUT["kebab-casetask-stack-size"] SECTION_OUT["[section-name]"] end subgraph subGraph0["Input Identifiers"] KEBAB["kebab-casetask-stack-size"] SECTION["[section-name]"] end KEBAB --> KEBAB_OUT KEBAB --> SNAKE SECTION --> MODULE SECTION --> SECTION_OUT
Hexadecimal Value Processing:
Large hexadecimal values in TOML strings are parsed and converted to native Rust integer literals:
- Input:
kernel-base-vaddr = "0xffff_ff80_0020_0000"
- Output:
pub const KERNEL_BASE_VADDR: usize = 0xffff_ff80_0020_0000;
Sources: example-configs/defconfig.toml(L29) example-configs/output.rs(L52) example-configs/defconfig.toml(L32) example-configs/output.rs(L62)
Comment and Documentation Preservation
Comments from the input TOML are preserved and transformed appropriately for each output format, maintaining documentation context across transformations.
Comment Transformation Rules:
Input Location | TOML Output | Rust Output |
---|---|---|
Line comments | Preserved as-is | Converted to///doc comments |
Section headers | Preserved with formatting | Converted to module doc comments |
Type annotations | Preserved inline | Embedded in type signatures |
The generated outputs maintain full traceability to the original configuration intent while adapting to the idioms and conventions of their respective formats.
Sources: example-configs/defconfig.toml(L1 - L63) example-configs/output.toml(L1 - L63) example-configs/output.rs(L1 - L66)
Development Guide
Relevant source files
This document provides essential information for developers contributing to the axconfig-gen project. It covers the build system, dependency management, testing procedures, and continuous integration processes. This guide is intended for contributors who need to understand the project structure, build the codebase locally, run tests, and submit changes.
For usage instructions of the CLI tool and library API, see axconfig-gen Package. For procedural macro usage, see axconfig-macros Package. For detailed build system information, see Build System and Dependencies. For testing procedures, see Testing. For CI/CD pipeline details, see Continuous Integration.
Project Structure Overview
The axconfig-gen repository is organized as a Cargo workspace containing two complementary packages that provide different interfaces to the same core configuration processing functionality.
Workspace Architecture
flowchart TD subgraph subGraph3["Build Artifacts"] BIN["axconfig-gen binary"] CRATE["axconfig-gen crate"] PROC_CRATE["axconfig-macros crate"] end subgraph subGraph2["axconfig-macros Package"] MAC_LIB["src/lib.rsparse_configs!, include_configs!"] TESTS["tests/integration tests"] end subgraph subGraph1["axconfig-gen Package"] CLI["src/main.rsCLI entry point"] LIB["src/lib.rslibrary API"] CONFIG["src/config.rsConfig, ConfigItem"] VALUE["src/value.rsConfigValue"] TY["src/ty.rsConfigType"] OUTPUT["src/output.rsoutput generation"] end subgraph subGraph0["Cargo Workspace"] WS["Cargo.tomlworkspace root"] AG["axconfig-gen/CLI tool & library"] AM["axconfig-macros/procedural macros"] end AG --> CLI AG --> CONFIG AG --> LIB AG --> OUTPUT AG --> TY AG --> VALUE AM --> AG AM --> MAC_LIB AM --> TESTS CLI --> BIN LIB --> CRATE MAC_LIB --> PROC_CRATE WS --> AG WS --> AM
Workspace Configuration: The project uses a Cargo workspace with resolver = "2"
for improved dependency resolution. Both packages share common metadata including version, authors, and licensing information.
Sources: Cargo.toml(L1 - L18)
Dependency Management
flowchart TD subgraph subGraph2["Transitive Dependencies"] INDEXMAP["indexmap 2.9.0"] TOML_DATETIME["toml_datetime 0.6.11"] WINNOW["winnow 0.7.11"] CLAP_BUILDER["clap_builder 4.5.40"] CLAP_DERIVE["clap_derive 4.5.40"] end subgraph subGraph1["axconfig-macros Dependencies"] AM_PKG["axconfig-macros"] PROC_MACRO2["proc-macro2 1.0.95procedural macro support"] QUOTE["quote 1.0.40code generation"] SYN["syn 2.0.102Rust parsing"] end subgraph subGraph0["axconfig-gen Dependencies"] AG_PKG["axconfig-gen"] CLAP["clap 4.5.40CLI argument parsing"] TOML_EDIT["toml_edit 0.22.27TOML manipulation"] end AG_PKG --> CLAP AG_PKG --> TOML_EDIT AM_PKG --> AG_PKG AM_PKG --> PROC_MACRO2 AM_PKG --> QUOTE AM_PKG --> SYN CLAP --> CLAP_BUILDER CLAP --> CLAP_DERIVE TOML_EDIT --> INDEXMAP TOML_EDIT --> TOML_DATETIME TOML_EDIT --> WINNOW
Key Dependencies:
clap
: Provides command-line argument parsing with derive macrostoml_edit
: Enables TOML parsing and manipulation while preserving formattingproc-macro2
,quote
,syn
: Standard procedural macro toolkit for code generation
Sources: Cargo.lock(L56 - L71)
Development Workflow
Building the Project
The project can be built using standard Cargo commands from the workspace root:
# Build both packages
cargo build
# Build with optimizations
cargo build --release
# Build specific package
cargo build -p axconfig-gen
cargo build -p axconfig-macros
Development Environment Setup
flowchart TD subgraph subGraph2["Package Development"] CLI_DEV["axconfig-gen CLI testing"] LIB_DEV["library API development"] MACRO_DEV["procedural macro development"] end subgraph subGraph1["Build Process"] BUILD["cargo build"] TEST["cargo test"] CLIPPY["cargo clippy"] FMT["cargo fmt"] end subgraph subGraph0["Local Development"] CLONE["git clone repository"] RUSTUP["Rust toolchain 1.76+"] DEPS["cargo fetch dependencies"] end BUILD --> CLI_DEV BUILD --> LIB_DEV BUILD --> MACRO_DEV BUILD --> TEST CLIPPY --> FMT CLONE --> RUSTUP DEPS --> BUILD RUSTUP --> DEPS TEST --> CLIPPY
Minimum Rust Version: The project requires Rust 1.76 or later as specified in the workspace configuration.
Sources: Cargo.toml(L17)
Package-Specific Development
axconfig-gen Development
The axconfig-gen
package contains both the CLI tool and the core library functionality. Development typically involves:
- CLI Interface: Modifying axconfig-gen/src/main.rs for command-line argument handling
- Core Logic: Working with
Config
,ConfigItem
, andConfigValue
types in axconfig-gen/src/config.rs and axconfig-gen/src/value.rs - Type System: Extending
ConfigType
functionality in axconfig-gen/src/ty.rs - Output Generation: Modifying TOML and Rust code generation in axconfig-gen/src/output.rs
axconfig-macros Development
The axconfig-macros
package focuses on procedural macro implementation:
- Macro Implementation: Located in axconfig-macros/src/lib.rs
- Integration Testing: Tests in axconfig-macros/tests/
- Cross-Package Dependencies: Relies on
axconfig-gen
for core functionality
Code Organization Patterns
The codebase follows several key organizational patterns:
- Separation of Concerns: CLI tool, library API, and procedural macros are clearly separated
- Shared Core Logic: Both packages use the same core configuration processing logic
- Type-Driven Design: Strong type system with
ConfigType
,ConfigValue
, andConfig
abstractions - Dual Output Modes: Support for both file-based generation and compile-time code generation
Common Development Tasks
Adding New Configuration Types
When extending the type system, developers typically need to:
- Extend
ConfigType
enum in axconfig-gen/src/ty.rs - Update
ConfigValue
handling in axconfig-gen/src/value.rs - Modify output generation in axconfig-gen/src/output.rs
- Add corresponding tests for both CLI and macro interfaces
Extending Output Formats
New output formats require:
- Extending the output generation system in axconfig-gen/src/output.rs
- Adding CLI flags in axconfig-gen/src/main.rs
- Updating both file-based and macro-based code generation paths
Cross-Package Coordination
Since axconfig-macros
depends on axconfig-gen
, changes to the core library API require careful coordination:
- Make API changes in
axconfig-gen
first - Update
axconfig-macros
to use new API - Ensure backward compatibility or coordinate breaking changes
- Test both packages together to verify integration
Sources: Cargo.lock(L56 - L71) Cargo.toml(L1 - L18)
Build System and Dependencies
Relevant source files
This page documents the Cargo workspace structure, dependency management, and local build configuration for the axconfig-gen repository. It covers the multi-crate workspace organization, external dependency requirements, and the build process for both CLI tools and procedural macros.
For information about testing procedures, see Testing. For details about continuous integration workflows, see Continuous Integration.
Workspace Structure
The axconfig-gen repository is organized as a Cargo workspace containing two primary crates that work together to provide configuration processing capabilities.
Workspace Configuration
The workspace is defined in the root Cargo.toml(L1 - L18) with resolver = "2"
enabling the newer dependency resolver. This configuration establishes shared metadata across all workspace members including version 0.2.1
, Rust edition 2021
, and a minimum Rust version requirement of 1.76
.
flowchart TD subgraph subGraph2["Cargo Workspace"] ROOT["Cargo.tomlresolver = '2'"] subgraph subGraph1["Shared Metadata"] VER["version = '0.2.1'"] ED["edition = '2021'"] RUST["rust-version = '1.76'"] LIC["GPL-3.0 OR Apache-2.0 OR MulanPSL-2.0"] end subgraph subGraph0["Workspace Members"] AXGEN["axconfig-genCLI tool & library"] AXMAC["axconfig-macrosProcedural macros"] end end ROOT --> AXGEN ROOT --> AXMAC ROOT --> ED ROOT --> LIC ROOT --> RUST ROOT --> VER
Workspace Member Dependencies
The workspace defines an explicit dependency relationship where axconfig-macros
depends on axconfig-gen
, allowing the procedural macros to reuse the core configuration processing logic.
Sources: Cargo.toml(L1 - L18) Cargo.lock(L56 - L71)
Dependency Architecture
The project maintains a clean separation between CLI/library functionality and macro functionality through its dependency structure.
Core Dependencies by Crate
Crate | Direct Dependencies | Purpose |
---|---|---|
axconfig-gen | clap,toml_edit | CLI argument parsing and TOML manipulation |
axconfig-macros | axconfig-gen,proc-macro2,quote,syn | Procedural macro infrastructure and core logic reuse |
External Dependency Graph
flowchart TD subgraph subGraph3["axconfig-macros Dependencies"] AXMAC["axconfig-macrosv0.2.1"] PROC_MACRO2["proc-macro2v1.0.95"] QUOTE["quotev1.0.40"] SYN["synv2.0.102"] end subgraph subGraph2["axconfig-gen Dependencies"] AXGEN["axconfig-genv0.2.1"] CLAP["clapv4.5.40"] TOML_EDIT["toml_editv0.22.27"] subgraph subGraph1["toml_edit Dependencies"] INDEXMAP["indexmap"] TOML_DATETIME["toml_datetime"] WINNOW["winnow"] end subgraph subGraph0["clap Dependencies"] CLAP_BUILDER["clap_builder"] CLAP_DERIVE["clap_derive"] ANSTREAM["anstream"] end end AXGEN --> CLAP AXGEN --> TOML_EDIT AXMAC --> AXGEN AXMAC --> PROC_MACRO2 AXMAC --> QUOTE AXMAC --> SYN CLAP --> CLAP_BUILDER CLAP --> CLAP_DERIVE TOML_EDIT --> INDEXMAP TOML_EDIT --> TOML_DATETIME TOML_EDIT --> WINNOW
Key Dependency Roles
clap
(v4.5.40): Provides command-line argument parsing with derive macros for the CLI interfacetoml_edit
(v0.22.27): Enables TOML document parsing and manipulation while preserving formattingproc-macro2
(v1.0.95): Low-level procedural macro token stream manipulationquote
(v1.0.40): Template-based Rust code generation for macro expansionsyn
(v2.0.102): Rust syntax tree parsing for macro input processing
Sources: Cargo.lock(L56 - L61) Cargo.lock(L64 - L71) Cargo.lock(L74 - L81) Cargo.lock(L207 - L216)
Build Process and Requirements
Environment Requirements
The project requires Rust 1.76
or later as specified in Cargo.toml(L17) This minimum version ensures compatibility with the procedural macro features and dependency requirements used throughout the codebase.
Build Commands
Command | Purpose | Output |
---|---|---|
cargo build | Build all workspace members | Target binaries and libraries |
cargo build --bin axconfig-gen | Build only CLI tool | target/debug/axconfig-gen |
cargo build --release | Optimized build | Release binaries intarget/release/ |
cargo install --path axconfig-gen | Install CLI globally | System-wideaxconfig-gencommand |
Build Flow
flowchart TD subgraph subGraph3["Build Process"] START["cargo build"] subgraph subGraph2["Output Generation"] BIN["target/debug/axconfig-genexecutable"] LIB["libaxconfig_gen.rliblibrary"] PROC["libaxconfig_macros.soproc-macro"] end subgraph subGraph1["Workspace Compilation"] AXGEN_BUILD["Compile axconfig-genCLI + library"] AXMAC_BUILD["Compile axconfig-macrosproc-macros"] end subgraph subGraph0["Dependency Resolution"] LOCK["Cargo.lockdependency versions"] FETCH["Download & compileexternal dependencies"] end end AXGEN_BUILD --> AXMAC_BUILD AXGEN_BUILD --> BIN AXGEN_BUILD --> LIB AXMAC_BUILD --> PROC FETCH --> AXGEN_BUILD LOCK --> FETCH START --> LOCK
Cross-Crate Compilation Order
The build system automatically handles the dependency order, compiling axconfig-gen
first since axconfig-macros
depends on it. This ensures the library interface is available during procedural macro compilation.
Sources: Cargo.toml(L4 - L7) Cargo.lock(L56 - L71)
Development Dependencies
Transitive Dependency Analysis
The Cargo.lock(L1 - L317) reveals a total of 31 crates in the complete dependency tree. Key transitive dependencies include:
- Windows Support:
windows-sys
and related platform-specific crates for cross-platform CLI functionality - String Processing:
unicode-ident
,memchr
,utf8parse
for robust text handling - Data Structures:
hashbrown
,indexmap
for efficient key-value storage in TOML processing
Licensing Compatibility
The workspace uses a tri-license approach: GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0
as specified in Cargo.toml(L13) All external dependencies maintain compatible licensing terms, ensuring legal compliance for distribution.
Version Management
The workspace employs pinned versions in Cargo.lock(L1 - L317) to ensure reproducible builds across different environments. Major dependency versions are:
- CLI ecosystem:
clap
4.x series with stable API - TOML processing:
toml_edit
0.22.x with advanced editing capabilities - Macro ecosystem:
proc-macro2
,quote
,syn
1.x/2.x series with mature APIs
Sources: Cargo.toml(L9 - L17) Cargo.lock(L1 - L317)
Testing
Relevant source files
This page covers the testing infrastructure and test organization for the axconfig-gen project. It includes unit tests for core functionality, integration tests for end-to-end validation, and macro-specific tests for procedural macro behavior.
For information about the build system setup, see Build System and Dependencies. For details about the continuous integration pipeline, see Continuous Integration.
Test Architecture Overview
The testing strategy is organized into three main categories: unit tests for individual components, integration tests for complete workflows, and macro-specific tests for procedural macro functionality.
flowchart TD subgraph subGraph2["Test Data Sources"] EXAMPLE_CONFIGS["Example Configurationsexample-configs/defconfig.toml"] EXPECTED_OUTPUT["Expected Outputsexample-configs/output.rs"] INLINE_DATA["Inline Test DataHardcoded in test functions"] end subgraph subGraph1["Test Targets"] TYPE_SYS["Type SystemConfigType, ConfigValue"] VALUE_SYS["Value ProcessingTOML parsing, validation"] OUTPUT_SYS["Output GenerationRust/TOML formatting"] MACRO_SYS["Macro Functionalityinclude_configs!, parse_configs!"] end subgraph subGraph0["Test Categories"] UNIT["Unit Testsaxconfig-gen/src/tests.rs"] INTEGRATION["Integration TestsExample-based validation"] MACRO["Macro Testsaxconfig-macros/tests/"] end EXAMPLE_CONFIGS --> INTEGRATION EXAMPLE_CONFIGS --> MACRO EXPECTED_OUTPUT --> INTEGRATION EXPECTED_OUTPUT --> MACRO INLINE_DATA --> UNIT INTEGRATION --> OUTPUT_SYS INTEGRATION --> VALUE_SYS MACRO --> MACRO_SYS UNIT --> OUTPUT_SYS UNIT --> TYPE_SYS UNIT --> VALUE_SYS
Sources: axconfig-gen/src/tests.rs(L1 - L192) axconfig-macros/tests/example_config.rs(L1 - L87)
Unit Test Structure
The unit tests in axconfig-gen
cover individual components of the configuration system, focusing on type inference, value validation, and code generation.
Type System Testing
The type inference and validation system is tested through the test_type_infer
and test_type_match
functions, which verify that TOML values are correctly mapped to Rust types.
Sources: axconfig-gen/src/tests.rs(L23 - L59) axconfig-gen/src/tests.rs(L61 - L115)
Code Generation Testing
The test_to_rust
function validates that TOML values are correctly converted to Rust code with proper formatting and indentation.
Test Case | Input TOML | Expected Rust Output | Purpose |
---|---|---|---|
Nested Arrays | [[(uint, (str, str), uint)]] | &[&[(usize, (&str, &str), usize)]] | Complex nesting validation |
Mixed Arrays | [[(uint, [str], uint)]] | &[&[(usize, &[&str], usize)]] | Variable-length arrays |
Indentation | Multi-level arrays | Properly indented code | Formatting correctness |
Sources: axconfig-gen/src/tests.rs(L137 - L180)
Integration Testing
Integration tests verify the complete pipeline from TOML input to generated output, ensuring that all components work together correctly.
sequenceDiagram participant integration_test as "integration_test()" participant FileSystem as "File System" participant Configfrom_toml as "Config::from_toml()" participant Configdump as "Config::dump()" integration_test ->> FileSystem: Read defconfig.toml integration_test ->> FileSystem: Read expected output.toml integration_test ->> FileSystem: Read expected output.rs integration_test ->> Configfrom_toml: Parse TOML specification Configfrom_toml ->> Configdump: Generate TOML output Configfrom_toml ->> Configdump: Generate Rust output integration_test ->> integration_test: Assert outputs match expected
The integration test reads example configuration files and compares the generated outputs against reference files to ensure consistency across the entire processing pipeline.
Sources: axconfig-gen/src/tests.rs(L182 - L191)
Macro Testing Framework
The macro tests in axconfig-macros
verify that procedural macros generate the same output as the CLI tool, ensuring consistency between compile-time and build-time processing.
Test Structure
flowchart TD subgraph subGraph2["Test Configuration"] DEFCONFIG["../example-configs/defconfig.tomlInput configuration"] OUTPUT_RS["../../example-configs/output.rsExpected constants"] end subgraph subGraph1["Comparison Framework"] MOD_CMP["mod_cmp! macroField-by-field comparison"] ASSERT_EQ["assert_eq! callsValue verification"] end subgraph subGraph0["Macro Test Modules"] CONFIG_MOD["config moduleinclude_configs! macro"] CONFIG2_MOD["config2 moduleparse_configs! macro"] EXPECT_MOD["config_expect moduleExpected output include"] end CONFIG2_MOD --> MOD_CMP CONFIG_MOD --> MOD_CMP DEFCONFIG --> CONFIG2_MOD DEFCONFIG --> CONFIG_MOD EXPECT_MOD --> MOD_CMP MOD_CMP --> ASSERT_EQ OUTPUT_RS --> EXPECT_MOD
Sources: axconfig-macros/tests/example_config.rs(L4 - L15)
Comparison Strategy
The mod_cmp!
macro systematically compares all generated constants across different modules to ensure consistency:
Category | Constants Tested | Purpose |
---|---|---|
Global | ARCH,PLAT,SMP | Core system configuration |
Devices | MMIO_REGIONS,PCI_,VIRTIO_ | Hardware abstraction |
Kernel | TASK_STACK_SIZE,TICKS_PER_SEC | Runtime parameters |
Platform | KERNEL_,PHYS_ | Memory layout |
Sources: axconfig-macros/tests/example_config.rs(L17 - L75)
Running Tests
Test Execution Commands
# Run all tests in the workspace
cargo test
# Run unit tests only (axconfig-gen)
cargo test -p axconfig-gen
# Run macro tests only (axconfig-macros)
cargo test -p axconfig-macros
# Run specific test functions
cargo test test_type_infer
cargo test integration_test
Feature-Specific Testing
The macro tests include conditional compilation for nightly features:
#![allow(unused)] fn main() { #[cfg(feature = "nightly")] #[test] fn test_parse_configs() { mod_cmp!(config2, config_expect); } }
Sources: axconfig-macros/tests/example_config.rs(L82 - L86)
Test Data Management
Example Configuration Files
The test suite relies on shared example configuration files located in the example-configs
directory:
defconfig.toml
- Input TOML configurationoutput.toml
- Expected TOML outputoutput.rs
- Expected Rust code output
These files serve as the source of truth for both integration tests and macro tests, ensuring consistency across test categories.
Sources: axconfig-macros/tests/example_config.rs(L5) axconfig-gen/src/tests.rs(L184 - L186)
Adding New Tests
Unit Test Guidelines
When adding unit tests to axconfig-gen/src/tests.rs follow these patterns:
- Use the
check_type_infer()
helper for type inference tests - Use the
assert_err!
macro for error condition validation - Include both positive and negative test cases
- Test edge cases like empty arrays and complex nesting
Integration Test Additions
For new integration tests:
- Add test configuration files to
example-configs/
- Update the
integration_test()
function to include new scenarios - Ensure both TOML and Rust output validation
Macro Test Extensions
When extending macro tests:
- Update the
mod_cmp!
macro to include new configuration fields - Add conditional compilation for feature-specific functionality
- Maintain consistency with the expected output format
Sources: axconfig-gen/src/tests.rs(L14 - L21) axconfig-macros/tests/example_config.rs(L17 - L75)
Continuous Integration
Relevant source files
This document covers the automated continuous integration and deployment pipeline for the axconfig-gen repository. The CI system ensures code quality, runs comprehensive tests, and automatically deploys documentation. For information about manual testing procedures, see Testing. For details about the build system configuration, see Build System and Dependencies.
Pipeline Overview
The CI system is implemented using GitHub Actions with a workflow defined in .github/workflows/ci.yml(L1 - L53) The pipeline consists of two primary jobs that run on every push and pull request event.
CI Workflow Structure
flowchart TD subgraph Outputs["Outputs"] QUALITY["Code Quality Validation"] ARTIFACTS["Build Artifacts"] DOCS["Published Documentation"] PAGES["GitHub Pages Deployment"] end subgraph subGraph3["GitHub Actions Workflow"] WORKFLOW["ci.yml workflow"] subgraph subGraph2["doc job"] CHECKOUT_DOC["actions/checkout@v4"] RUST_SETUP_DOC["dtolnay/rust-toolchain@nightly"] BUILD_DOCS["cargo doc --no-deps --all-features"] DEPLOY["JamesIves/github-pages-deploy-action@v4"] end subgraph subGraph1["ci job"] CHECKOUT_CI["actions/checkout@v4"] RUST_SETUP["dtolnay/rust-toolchain@nightly"] VERSION_CHECK["rustc --version --verbose"] FORMAT_CHECK["cargo fmt --all -- --check"] CLIPPY["cargo clippy --target x86_64-unknown-linux-gnu --all-features"] BUILD["cargo build --target x86_64-unknown-linux-gnu --all-features"] TEST["cargo test --target x86_64-unknown-linux-gnu -- --nocapture"] end end subgraph subGraph0["Trigger Events"] PUSH["push event"] PR["pull_request event"] end BUILD --> ARTIFACTS BUILD --> TEST BUILD_DOCS --> DEPLOY BUILD_DOCS --> DOCS CHECKOUT_CI --> RUST_SETUP CHECKOUT_DOC --> RUST_SETUP_DOC CLIPPY --> BUILD DEPLOY --> PAGES FORMAT_CHECK --> CLIPPY PR --> WORKFLOW PUSH --> WORKFLOW RUST_SETUP --> VERSION_CHECK RUST_SETUP_DOC --> BUILD_DOCS TEST --> QUALITY VERSION_CHECK --> FORMAT_CHECK WORKFLOW --> CHECKOUT_CI WORKFLOW --> CHECKOUT_DOC
Sources: .github/workflows/ci.yml(L1 - L53)
Job Definitions and Matrix Strategy
The CI workflow uses a matrix strategy for the ci
job to support multiple Rust toolchains and target architectures, though currently configured for a single combination.
CI Job Configuration
Parameter | Value |
---|---|
runs-on | ubuntu-latest |
rust-toolchain | nightly |
targets | x86_64-unknown-linux-gnu |
fail-fast | false |
The job includes comprehensive Rust toolchain setup with required components:
flowchart TD subgraph subGraph1["Setup Action"] DTOLNAY["dtolnay/rust-toolchain@nightly"] end subgraph subGraph0["Rust Toolchain Components"] TOOLCHAIN["nightly toolchain"] RUST_SRC["rust-src component"] CLIPPY_COMP["clippy component"] RUSTFMT_COMP["rustfmt component"] TARGET["x86_64-unknown-linux-gnu target"] end DTOLNAY --> CLIPPY_COMP DTOLNAY --> RUSTFMT_COMP DTOLNAY --> RUST_SRC DTOLNAY --> TARGET DTOLNAY --> TOOLCHAIN
Sources: .github/workflows/ci.yml(L8 - L19)
Quality Assurance Process
The CI pipeline implements a multi-stage quality assurance process that validates code formatting, performs static analysis, builds the project, and runs tests.
Quality Gates Sequence
Stage | Command | Purpose |
---|---|---|
Version Check | rustc --version --verbose | Verify toolchain installation |
Format Check | cargo fmt --all -- --check | Enforce code formatting standards |
Static Analysis | cargo clippy --target x86_64-unknown-linux-gnu --all-features | Detect potential issues and style violations |
Build | cargo build --target x86_64-unknown-linux-gnu --all-features | Ensure compilation succeeds |
Test | cargo test --target x86_64-unknown-linux-gnu -- --nocapture | Execute unit and integration tests |
The pipeline uses the --all-features
flag to ensure both axconfig-gen
and axconfig-macros
packages are tested with all feature combinations enabled.
Sources: .github/workflows/ci.yml(L20 - L29)
Documentation Pipeline
The doc
job handles automated documentation generation and deployment to GitHub Pages with conditional execution based on the repository branch.
Documentation Workflow
flowchart TD subgraph subGraph3["GitHub Pages"] GH_PAGES["gh-pages branch"] SINGLE_COMMIT["single-commit: true"] end subgraph subGraph2["Deployment Logic"] BRANCH_CHECK["github.ref == refs/heads/main"] CONTINUE_ERROR["continue-on-error for non-main branches"] DEPLOY_ACTION["JamesIves/github-pages-deploy-action@v4"] end subgraph subGraph1["Documentation Generation"] CARGO_DOC["cargo doc --no-deps --all-features"] TARGET_DOC["target/doc output directory"] end subgraph subGraph0["Documentation Environment"] ENV_VAR["default-branch environment variable"] RUSTDOCFLAGS["RUSTDOCFLAGS with unstable options"] PERMISSIONS["contents: write permissions"] end BRANCH_CHECK --> DEPLOY_ACTION CARGO_DOC --> TARGET_DOC DEPLOY_ACTION --> GH_PAGES DEPLOY_ACTION --> SINGLE_COMMIT ENV_VAR --> BRANCH_CHECK RUSTDOCFLAGS --> CARGO_DOC TARGET_DOC --> DEPLOY_ACTION
The documentation build uses specific RUSTDOCFLAGS
configuration:
-Zunstable-options --enable-index-page
: Enables unstable documentation features-D rustdoc::broken_intra_doc_links
: Treats broken internal links as errors-D missing-docs
: Treats missing documentation as errors
Sources: .github/workflows/ci.yml(L31 - L53)
Deployment and Automation
The CI system implements conditional deployment logic that ensures documentation is only published from the main branch while allowing documentation builds to continue on other branches for validation.
Deployment Configuration
Setting | Value | Purpose |
---|---|---|
single-commit | true | Maintains clean gh-pages history |
branch | gh-pages | Target branch for documentation |
folder | target/doc | Source directory for documentation files |
The deployment uses the JamesIves/github-pages-deploy-action@v4
action with conditional execution based on github.ref == env.default-branch
to ensure only main branch changes trigger actual deployment.
Sources: .github/workflows/ci.yml(L46 - L52)
Overview
Relevant source files
Purpose and Scope
The page_table_multiarch
repository provides a generic, unified, architecture-independent page table management library for Rust systems programming. This library enables operating systems, hypervisors, and bare-metal applications to manage virtual memory translation across multiple hardware architectures through a single, consistent API.
The repository implements hardware abstraction for page table operations on x86_64, AArch64, RISC-V, and LoongArch64 architectures, providing both low-level page table entry manipulation and high-level page table management functionality. For detailed information about individual architecture implementations, see Architecture Support. For development and contribution guidelines, see Development Guide.
Sources: README.md(L1 - L16) Cargo.toml(L17 - L18)
System Architecture
The library implements a layered architecture that separates generic page table operations from architecture-specific implementations through Rust's trait system.
Core System Structure
flowchart TD subgraph subGraph3["Architecture Implementations"] X86_IMPL["X64PageTableX64PTEX64PagingMetaData"] ARM_IMPL["A64PageTableA64PTEA64PagingMetaData"] RV_IMPL["Sv39PageTable, Sv48PageTableRv64PTESvPagingMetaData"] LA_IMPL["LA64PageTableLA64PTELA64PagingMetaData"] end subgraph subGraph2["Trait Abstraction Layer"] PMD["PagingMetaData trait"] GPTE["GenericPTE trait"] PH["PagingHandler trait"] end subgraph subGraph1["Generic API Layer"] PT64["PageTable64<M,PTE,H>"] API["map(), unmap(), protect()"] FLAGS["MappingFlags"] end subgraph subGraph0["Application Layer"] OS["Operating Systems"] HV["Hypervisors"] BM["Bare Metal Code"] end API --> GPTE API --> PH API --> PMD BM --> PT64 GPTE --> ARM_IMPL GPTE --> LA_IMPL GPTE --> RV_IMPL GPTE --> X86_IMPL HV --> PT64 OS --> PT64 PH --> ARM_IMPL PH --> LA_IMPL PH --> RV_IMPL PH --> X86_IMPL PMD --> ARM_IMPL PMD --> LA_IMPL PMD --> RV_IMPL PMD --> X86_IMPL PT64 --> API PT64 --> FLAGS
Sources: Cargo.toml(L4 - L7) README.md(L5 - L10)
Workspace Structure
The repository is organized as a Rust workspace containing two interdependent crates that together provide the complete page table management functionality.
Crate Dependencies and Relationships
flowchart TD subgraph subGraph5["page_table_multiarch Workspace"] WS["Cargo.tomlworkspace root"] PT64_TYPE["PageTable64<M,PTE,H>"] GPTE_TRAIT["GenericPTE trait"] ARCH_TABLES["X64PageTableA64PageTableSv39PageTableSv48PageTableLA64PageTable"] PTE_TYPES["X64PTEA64PTERv64PTELA64PTE"] FLAGS_TYPE["MappingFlags"] subgraph subGraph4["External Dependencies"] MEMADDR["memory_addr"] LOG["log"] BITFLAGS["bitflags"] end subgraph subGraph3["page_table_entry crate"] PTE["page_table_entry"] PTE_LIB["lib.rs"] PTE_ARCH["arch/ modules"] subgraph subGraph2["Low-level Types"] GPTE_TRAIT["GenericPTE trait"] PTE_TYPES["X64PTEA64PTERv64PTELA64PTE"] FLAGS_TYPE["MappingFlags"] end end subgraph subGraph1["page_table_multiarch crate"] PTM["page_table_multiarch"] PTM_LIB["lib.rs"] PTM_BITS["bits64.rs"] PTM_ARCH["arch/ modules"] subgraph subGraph0["High-level Types"] PT64_TYPE["PageTable64<M,PTE,H>"] ARCH_TABLES["X64PageTableA64PageTableSv39PageTableSv48PageTableLA64PageTable"] end end end ARCH_TABLES --> PTE_TYPES GPTE_TRAIT --> FLAGS_TYPE PT64_TYPE --> GPTE_TRAIT PTE --> BITFLAGS PTE --> MEMADDR PTE_TYPES --> GPTE_TRAIT PTM --> LOG PTM --> MEMADDR PTM --> PTE WS --> PTE WS --> PTM
The page_table_multiarch
crate provides high-level page table management through the PageTable64
generic struct and architecture-specific type aliases. The page_table_entry
crate provides low-level page table entry definitions and the GenericPTE
trait that enables architecture abstraction.
Sources: Cargo.toml(L4 - L7) README.md(L12 - L15)
Supported Architectures
The library supports four major processor architectures, each with specific paging characteristics and implementation details.
Architecture Support Matrix
Architecture | Page Table Levels | Virtual Address Width | Physical Address Width | Implementation Types |
---|---|---|---|---|
x86_64 | 4 levels | 48-bit | 52-bit | X64PageTable,X64PTE |
AArch64 | 4 levels | 48-bit | 48-bit | A64PageTable,A64PTE |
RISC-V Sv39 | 3 levels | 39-bit | 56-bit | Sv39PageTable,Rv64PTE |
RISC-V Sv48 | 4 levels | 48-bit | 56-bit | Sv48PageTable,Rv64PTE |
LoongArch64 | 4 levels | 48-bit | 48-bit | LA64PageTable,LA64PTE |
Each architecture implementation provides the same generic interface through the trait system while handling architecture-specific page table formats, address translation mechanisms, and memory attribute encodings.
Sources: README.md(L5 - L10) CHANGELOG.md(L21)
Key Abstractions
The library's architecture independence is achieved through three core traits that define the interface between generic and architecture-specific code.
Trait System Overview
flowchart TD subgraph subGraph4["Generic Implementation"] PT64_STRUCT["PageTable64<M: PagingMetaData,PTE: GenericPTE,H: PagingHandler>"] end subgraph subGraph3["PagingHandler Methods"] PH_METHODS["alloc_frame()dealloc_frame()phys_to_virt()"] end subgraph subGraph2["GenericPTE Methods"] GPTE_METHODS["new_page()new_table()paddr()flags()is_present()is_huge()empty()"] end subgraph subGraph1["PagingMetaData Methods"] PMD_METHODS["PAGE_SIZEVADDR_SIZEPADDR_SIZEENTRY_COUNTMAX_LEVEL"] end subgraph subGraph0["Core Trait Definitions"] PMD_TRAIT["PagingMetaData"] GPTE_TRAIT["GenericPTE"] PH_TRAIT["PagingHandler"] end API_METHODS["map()unmap()protect()map_region()unmap_region()protect_region()"] GPTE_TRAIT --> GPTE_METHODS GPTE_TRAIT --> PT64_STRUCT PH_TRAIT --> PH_METHODS PH_TRAIT --> PT64_STRUCT PMD_TRAIT --> PMD_METHODS PMD_TRAIT --> PT64_STRUCT PT64_STRUCT --> API_METHODS
The PagingMetaData
trait defines architecture constants, GenericPTE
provides page table entry manipulation methods, and PagingHandler
abstracts memory allocation and address translation for the operating system interface.
Sources: README.md(L3) CHANGELOG.md(L7) CHANGELOG.md(L61 - L63)
System Architecture
Relevant source files
This document explains the overall design philosophy, abstraction layers, and architecture independence mechanisms of the page_table_multiarch
library. The purpose is to provide a comprehensive understanding of how the system achieves unified page table management across multiple processor architectures through a layered abstraction approach.
For detailed information about specific processor architectures, see Architecture Support. For implementation details of the core abstractions, see Core Concepts.
Design Philosophy
The page_table_multiarch
library implements a generic, unified, architecture-independent approach to page table management. The system separates architecture-specific concerns from generic page table operations through a trait-based abstraction layer that allows the same high-level API to work across x86_64, AArch64, RISC-V, and LoongArch64 platforms.
Core Abstraction Model
flowchart TD subgraph subGraph4["Hardware Layer"] X86HW["x86_64 MMU"] ARMHW["AArch64 MMU"] RVHW["RISC-V MMU"] LAHW["LoongArch64 MMU"] end subgraph subGraph3["Architecture Implementation Layer"] X86Meta["X64PagingMetaData"] ARMeta["A64PagingMetaData"] RVMeta["Sv39/Sv48MetaData"] LAMeta["LA64MetaData"] X86PTE["X64PTE"] ARMPTE["A64PTE"] RVPTE["Rv64PTE"] LAPTE["LA64PTE"] end subgraph subGraph2["Trait Abstraction Layer"] PMD["PagingMetaData trait"] GPTE["GenericPTE trait"] PH["PagingHandler trait"] end subgraph subGraph1["Generic Interface Layer"] PT64["PageTable64<M,PTE,H>"] API["Unified API Methods"] end subgraph subGraph0["Application Layer"] App["OS/Hypervisor Code"] end API --> GPTE API --> PH API --> PMD ARMPTE --> ARMHW ARMeta --> ARMHW App --> PT64 GPTE --> ARMPTE GPTE --> LAPTE GPTE --> RVPTE GPTE --> X86PTE LAMeta --> LAHW LAPTE --> LAHW PMD --> ARMeta PMD --> LAMeta PMD --> RVMeta PMD --> X86Meta PT64 --> API RVMeta --> RVHW RVPTE --> RVHW X86Meta --> X86HW X86PTE --> X86HW
Sources: page_table_multiarch/src/lib.rs(L9 - L19) page_table_multiarch/README.md(L9 - L20)
Workspace Architecture
The system is organized as a two-crate Cargo workspace that separates high-level page table management from low-level page table entry definitions:
Crate Dependency Structure
flowchart TD subgraph subGraph3["page_table_multiarch Workspace"] PTM["page_table_multiarch crate"] PTELib["lib.rs"] PTEArch["arch/ modules"] PTMLib["lib.rs"] PTMBits["bits64.rs"] subgraph subGraph2["External Dependencies"] MemAddr["memory_addr"] Log["log"] Bitflags["bitflags"] end subgraph subGraph0["PTM Modules"] PTE["page_table_entry crate"] PTMArch["arch/ modules"] subgraph subGraph1["PTE Modules"] PTM["page_table_multiarch crate"] PTELib["lib.rs"] PTEArch["arch/ modules"] PTMLib["lib.rs"] PTMBits["bits64.rs"] end end end PTE --> Bitflags PTE --> MemAddr PTELib --> PTEArch PTM --> Log PTM --> MemAddr PTM --> PTE PTMLib --> PTMArch PTMLib --> PTMBits
Crate | Purpose | Key Exports |
---|---|---|
page_table_multiarch | High-level page table abstractions | PageTable64,PagingMetaData,PagingHandler |
page_table_entry | Low-level page table entry definitions | GenericPTE,MappingFlags |
Sources: page_table_multiarch/src/lib.rs(L15 - L19) page_table_entry/src/lib.rs(L10)
Core Trait System
The architecture independence is achieved through three primary traits that define contracts between generic and architecture-specific code:
Trait Relationships and Responsibilities
flowchart TD subgraph subGraph2["Trait Methods & Constants"] PMDMethods["LEVELS: usizePA_MAX_BITS: usizeVA_MAX_BITS: usizeVirtAddr: MemoryAddrflush_tlb()"] GPTEMethods["new_page()new_table()paddr()flags()is_present()is_huge()"] PHMethods["alloc_frame()dealloc_frame()phys_to_virt()"] end subgraph subGraph1["Core Traits"] PMDTrait["PagingMetaData"] GPTETrait["GenericPTE"] PHTrait["PagingHandler"] end subgraph subGraph0["Generic Types"] PT64Struct["PageTable64<M,PTE,H>"] TlbFlush["TlbFlush<M>"] TlbFlushAll["TlbFlushAll<M>"] MFlags["MappingFlags"] PSize["PageSize"] end GPTETrait --> GPTEMethods GPTETrait --> MFlags PHTrait --> PHMethods PMDTrait --> PMDMethods PT64Struct --> GPTETrait PT64Struct --> PHTrait PT64Struct --> PMDTrait TlbFlush --> PMDTrait TlbFlushAll --> PMDTrait
Trait Responsibilities
Trait | Responsibility | Key Types |
---|---|---|
PagingMetaData | Architecture constants and TLB operations | LEVELS,PA_MAX_BITS,VA_MAX_BITS,VirtAddr |
GenericPTE | Page table entry manipulation | Entry creation, flag handling, address extraction |
PagingHandler | OS-dependent memory operations | Frame allocation, virtual-physical address translation |
Sources: page_table_multiarch/src/lib.rs(L40 - L92) page_table_entry/src/lib.rs(L38 - L68)
Architecture Independence Mechanisms
Generic Parameter System
The PageTable64<M, PTE, H>
struct uses three generic parameters to achieve architecture independence:
// From page_table_multiarch/src/lib.rs and bits64.rs
PageTable64<M: PagingMetaData, PTE: GenericPTE, H: PagingHandler>
M: PagingMetaData
- Provides architecture-specific constants and TLB operationsPTE: GenericPTE
- Handles architecture-specific page table entry formatsH: PagingHandler
- Abstracts OS-specific memory management operations
Architecture-Specific Implementations
Each supported architecture provides concrete implementations of the core traits:
Architecture | Metadata Type | PTE Type | Example Usage |
---|---|---|---|
x86_64 | X64PagingMetaData | X64PTE | X64PageTable |
AArch64 | A64PagingMetaData | A64PTE | A64PageTable |
RISC-V Sv39 | Sv39MetaData | Rv64PTE | Sv39PageTable |
RISC-V Sv48 | Sv48MetaData | Rv64PTE | Sv48PageTable |
LoongArch64 | LA64MetaData | LA64PTE | LA64PageTable |
Error Handling and Type Safety
The system defines a comprehensive error model through PagingError
and PagingResult
types:
flowchart TD subgraph subGraph1["Error Variants"] NoMemory["NoMemory"] NotAligned["NotAligned"] NotMapped["NotMapped"] AlreadyMapped["AlreadyMapped"] MappedToHuge["MappedToHugePage"] end subgraph subGraph0["Error Types"] PError["PagingError"] PResult["PagingResult<T>"] end PError --> AlreadyMapped PError --> MappedToHuge PError --> NoMemory PError --> NotAligned PError --> NotMapped PResult --> PError
Sources: page_table_multiarch/src/lib.rs(L21 - L38)
TLB Management Architecture
The system implements a type-safe TLB (Translation Lookaside Buffer) management mechanism through specialized wrapper types:
TLB Flush Types
flowchart TD subgraph subGraph2["Architecture Implementation"] FlushTLB["M::flush_tlb(Option<VirtAddr>)"] end subgraph subGraph1["TLB Operations"] FlushSingle["flush() - Single address"] FlushAll["flush_all() - Entire TLB"] Ignore["ignore() - Skip flush"] end subgraph subGraph0["TLB Management Types"] TlbFlush["TlbFlush<M>"] TlbFlushAll["TlbFlushAll<M>"] end FlushAll --> FlushTLB FlushSingle --> FlushTLB TlbFlush --> FlushSingle TlbFlush --> Ignore TlbFlushAll --> FlushAll TlbFlushAll --> Ignore
The #[must_use]
attribute ensures that TLB flush operations are not accidentally ignored, promoting system correctness.
Sources: page_table_multiarch/src/lib.rs(L130 - L172)
Page Size Support
The system supports multiple page sizes through the PageSize
enumeration:
Page Size | Value | Usage |
---|---|---|
Size4K | 4 KiB (0x1000) | Standard pages |
Size2M | 2 MiB (0x200000) | Huge pages |
Size1G | 1 GiB (0x40000000) | Huge pages |
The PageSize::is_huge()
method distinguishes between standard and huge pages, enabling architecture-specific optimizations for large memory mappings.
Sources: page_table_multiarch/src/lib.rs(L94 - L128)
Supported Platforms
Relevant source files
This document provides an overview of the hardware architectures supported by the page_table_multiarch
library and their specific implementation characteristics. The library provides a unified interface for page table management across multiple processor architectures through architecture-specific implementations.
For detailed information about the implementation architecture, see System Architecture. For information about building and testing across platforms, see Building and Testing.
Architecture Support Matrix
The page_table_multiarch
library supports four major processor architectures through conditional compilation and architecture-specific implementations. Each architecture provides different virtual address space configurations and page table structures.
Supported Architecture Matrix
Architecture | Virtual Address Bits | Physical Address Bits | Page Table Levels | Crate Dependencies |
---|---|---|---|---|
x86_64 | 48-bit | 52-bit | 4 levels | x86 |
AArch64 | 48-bit | 48-bit | 4 levels | aarch64-cpu |
RISC-V Sv39 | 39-bit | 56-bit | 3 levels | riscv |
RISC-V Sv48 | 48-bit | 56-bit | 4 levels | riscv |
LoongArch64 | 48-bit | 48-bit | 4 levels | Built-in |
Sources: page_table_multiarch/Cargo.toml(L20 - L24) page_table_entry/Cargo.toml(L22 - L26) CHANGELOG.md(L21)
Platform Implementation Structure
Architecture-Specific Type Mapping
flowchart TD subgraph LoongArch_Impl["LoongArch64 Implementation"] LA64PT["LA64PageTable"] LA64PTE["LA64PTE"] LA64MD["LA64MetaData"] end subgraph RISC_V_Impl["RISC-V Implementation"] SV39PT["Sv39PageTable"] SV48PT["Sv48PageTable"] RV64PTE["Rv64PTE"] SV39MD["Sv39MetaData"] SV48MD["Sv48MetaData"] end subgraph AArch64_Impl["AArch64 Implementation"] A64PT["A64PageTable"] A64PTE["A64PTE"] A64MD["A64PagingMetaData"] end subgraph x86_64_Impl["x86_64 Implementation"] X64PT["X64PageTable"] X64PTE["X64PTE"] X64MD["X64PagingMetaData"] end subgraph Generic_Interface["Generic Interface Layer"] PT64["PageTable64<M, PTE, H>"] GPTE["GenericPTE trait"] PMD["PagingMetaData trait"] end GPTE --> A64PTE GPTE --> LA64PTE GPTE --> RV64PTE GPTE --> X64PTE PMD --> A64MD PMD --> LA64MD PMD --> SV39MD PMD --> SV48MD PMD --> X64MD PT64 --> A64PT PT64 --> LA64PT PT64 --> SV39PT PT64 --> SV48PT PT64 --> X64PT
Sources: page_table_multiarch/Cargo.toml(L1 - L28) page_table_entry/Cargo.toml(L1 - L29)
Conditional Compilation Dependencies
flowchart TD subgraph Features["Optional Features"] ARM_EL2["arm-el2"] end subgraph Conditional_Deps["Conditional Dependencies"] X86_DEP["x86 = '0.52'"] X86_64_DEP["x86_64 = '0.15.2'"] ARM_DEP["aarch64-cpu = '10.0'"] RV_DEP["riscv = '0.12'"] end subgraph Build_Targets["Build Target Architecture"] X86_TARGET["x86_64"] ARM_TARGET["aarch64"] RV32_TARGET["riscv32"] RV64_TARGET["riscv64"] LA_TARGET["loongarch64"] DOC_TARGET["doc"] end ARM_TARGET --> ARM_DEP ARM_TARGET --> ARM_EL2 DOC_TARGET --> ARM_DEP DOC_TARGET --> ARM_EL2 DOC_TARGET --> RV_DEP DOC_TARGET --> X86_64_DEP DOC_TARGET --> X86_DEP RV32_TARGET --> RV_DEP RV64_TARGET --> RV_DEP X86_TARGET --> X86_64_DEP X86_TARGET --> X86_DEP
Sources: page_table_multiarch/Cargo.toml(L20 - L27) page_table_entry/Cargo.toml(L15 - L29)
x86_64 Platform Support
The x86_64 implementation provides support for Intel and AMD 64-bit processors using 4-level page tables with 48-bit virtual addresses and up to 52-bit physical addresses.
x86_64 Characteristics
- Page Table Structure: 4-level paging (PML4, PDPT, PD, PT)
- Virtual Address Space: 48 bits (256 TB)
- Physical Address Space: Up to 52 bits (4 PB)
- Page Sizes: 4 KB, 2 MB, 1 GB
- Hardware Dependencies:
x86
crate for low-level x86 functionality
Sources: page_table_multiarch/Cargo.toml(L20 - L21) page_table_entry/Cargo.toml(L25 - L26)
AArch64 Platform Support
The AArch64 implementation supports ARM 64-bit processors using the VMSAv8-64 translation table format with 4-level page tables.
AArch64 Characteristics
- Page Table Structure: 4-level translation tables (L0, L1, L2, L3)
- Virtual Address Space: 48 bits (256 TB)
- Physical Address Space: 48 bits (256 TB)
- Page Sizes: 4 KB, 2 MB, 1 GB
- Hardware Dependencies:
aarch64-cpu
crate for ARM-specific functionality - Special Features: EL2 privilege level support via
arm-el2
feature
Sources: page_table_entry/Cargo.toml(L15 - L23) CHANGELOG.md(L47)
RISC-V Platform Support
The RISC-V implementation supports both Sv39 (3-level) and Sv48 (4-level) virtual memory configurations, providing flexibility for different RISC-V implementations.
RISC-V Sv39 Characteristics
- Page Table Structure: 3-level page tables
- Virtual Address Space: 39 bits (512 GB)
- Physical Address Space: 56 bits (64 PB)
- Page Sizes: 4 KB, 2 MB, 1 GB
RISC-V Sv48 Characteristics
- Page Table Structure: 4-level page tables
- Virtual Address Space: 48 bits (256 TB)
- Physical Address Space: 56 bits (64 PB)
- Page Sizes: 4 KB, 2 MB, 1 GB, 512 GB
RISC-V Dependencies
- Hardware Dependencies:
riscv
crate v0.12 with default features disabled - Target Support: Both
riscv32
andriscv64
target architectures
Sources: page_table_multiarch/Cargo.toml(L23 - L24) CHANGELOG.md(L37)
LoongArch64 Platform Support
LoongArch64 support was added in version 0.5.1, providing page table management for LoongArch 64-bit processors with 4-level page tables by default.
LoongArch64 Characteristics
- Page Table Structure: 4-level page tables (default configuration)
- Virtual Address Space: 48 bits (256 TB)
- Physical Address Space: 48 bits (256 TB)
- Page Sizes: 4 KB, 2 MB, 1 GB
- Hardware Dependencies: Built-in support, no external crate dependencies
Sources: CHANGELOG.md(L13 - L21)
Build System Integration
The platform support is implemented through Cargo's conditional compilation features, ensuring that only the necessary architecture-specific dependencies are included in builds.
Target-Specific Dependencies
[target.'cfg(any(target_arch = "x86_64", doc))'.dependencies]
x86 = "0.52"
[target.'cfg(any(target_arch = "aarch64", doc))'.dependencies]
aarch64-cpu = "10.0"
[target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64", doc))'.dependencies]
riscv = { version = "0.12", default-features = false }
The doc
target ensures all architecture-specific code is available when building documentation, providing complete API documentation across all supported platforms.
Sources: page_table_multiarch/Cargo.toml(L20 - L27) page_table_entry/Cargo.toml(L22 - L29)
Workspace Structure
Relevant source files
This document explains the two-crate workspace structure of the page_table_multiarch
repository and how the crates interact to provide multi-architecture page table functionality. It covers the workspace configuration, crate responsibilities, dependency management, and conditional compilation strategy.
For detailed information about the individual crate implementations, see page_table_multiarch Crate and page_table_entry Crate.
Workspace Overview
The page_table_multiarch
repository is organized as a Cargo workspace containing two complementary crates that provide a layered approach to page table management across multiple hardware architectures.
Workspace Definition
The workspace is defined in the root Cargo.toml(L1 - L7) with two member crates:
Crate | Purpose | Role |
---|---|---|
page_table_multiarch | High-level page table abstractions | Main API providingPageTable64and unified interface |
page_table_entry | Low-level page table entries | Architecture-specific PTE implementations andGenericPTEtrait |
Workspace Structure Diagram
flowchart TD subgraph subGraph2["page_table_multiarch Workspace"] ROOT["Cargo.toml(Workspace Root)"] subgraph subGraph1["page_table_entry Crate"] PTE["page_table_entryPage table entry definitions"] PTE_CARGO["page_table_entry/Cargo.toml"] end subgraph subGraph0["page_table_multiarch Crate"] PTM_CARGO["page_table_multiarch/Cargo.toml"] PTM["page_table_multiarchGeneric page table structures"] end end PTE_CARGO --> PTE PTM --> PTE PTM_CARGO --> PTM ROOT --> PTE_CARGO ROOT --> PTM_CARGO
Sources: Cargo.toml(L4 - L7) page_table_multiarch/Cargo.toml(L2 - L3) page_table_entry/Cargo.toml(L2 - L3)
Shared Workspace Configuration
The workspace defines common package metadata in Cargo.toml(L9 - L19) that is inherited by both member crates:
- Version:
0.5.3
- synchronized across all crates - Edition:
2024
- uses latest Rust edition - Categories:
["os", "hardware-support", "memory-management", "no-std"]
- Keywords:
["arceos", "paging", "page-table", "virtual-memory"]
- Rust Version:
1.85
- minimum supported Rust version
Sources: Cargo.toml(L9 - L19) page_table_multiarch/Cargo.toml(L5 - L13) page_table_entry/Cargo.toml(L5 - L13)
Crate Interaction Architecture
The two crates form a layered architecture where page_table_multiarch
provides high-level abstractions while depending on page_table_entry
for low-level functionality.
Crate Dependency and Interaction Diagram
flowchart TD subgraph subGraph3["External Dependencies"] MEMORY_ADDR["memory_addr crateAddress types"] LOG["log crateLogging"] BITFLAGS["bitflags crateFlag manipulation"] ARCH_SPECIFIC["x86, riscv, aarch64-cpux86_64 crates"] end subgraph subGraph2["page_table_entry Crate"] PTE_TRAIT["GenericPTE trait"] PTE_IMPL["X64PTE, A64PTERv64PTE, LA64PTE"] PTE_FLAGS["MappingFlagsArchitecture conversion"] end subgraph subGraph1["page_table_multiarch Crate"] PTM_API["PageTable64"] PTM_TRAITS["PagingMetaDataPagingHandler traits"] PTM_IMPL["Architecture-specificPageTable implementations"] end subgraph subGraph0["Application Layer"] APP["Operating SystemsHypervisorsKernel Code"] end APP --> PTM_API PTE_IMPL --> ARCH_SPECIFIC PTE_IMPL --> BITFLAGS PTE_IMPL --> PTE_FLAGS PTE_TRAIT --> MEMORY_ADDR PTE_TRAIT --> PTE_FLAGS PTM_API --> LOG PTM_API --> MEMORY_ADDR PTM_API --> PTM_IMPL PTM_API --> PTM_TRAITS PTM_IMPL --> ARCH_SPECIFIC PTM_IMPL --> PTE_IMPL PTM_TRAITS --> PTE_TRAIT
Sources: page_table_multiarch/Cargo.toml(L15 - L18) page_table_entry/Cargo.toml(L18 - L20)
Dependency Relationship
The page_table_multiarch
crate explicitly depends on page_table_entry
as defined in page_table_multiarch/Cargo.toml(L18) :
page_table_entry = { path = "../page_table_entry", version = "0.5.2" }
This creates a clear separation of concerns:
- High-level operations (page table walking, mapping, unmapping) in
page_table_multiarch
- Low-level PTE manipulation (bit operations, flag conversions) in
page_table_entry
Sources: page_table_multiarch/Cargo.toml(L18)
Architecture-Specific Dependencies
Both crates use conditional compilation to include only the necessary architecture-specific dependencies based on the target platform.
page_table_multiarch Dependencies
Conditional Dependencies Diagram
flowchart TD subgraph subGraph0["page_table_multiarch Dependencies"] CORE["Core Dependencieslog, memory_addrpage_table_entry"] X86_TARGET["cfg(target_arch = x86_64)"] X86_DEP["x86 = 0.52"] RISCV_TARGET["cfg(target_arch = riscv32/64)"] RISCV_DEP["riscv = 0.12"] DOC_TARGET["cfg(doc)"] ALL_DEP["All arch dependenciesfor documentation"] end CORE --> DOC_TARGET CORE --> RISCV_TARGET CORE --> X86_TARGET DOC_TARGET --> ALL_DEP RISCV_TARGET --> RISCV_DEP X86_TARGET --> X86_DEP
Sources: page_table_multiarch/Cargo.toml(L20 - L24) page_table_multiarch/Cargo.toml(L26 - L27)
page_table_entry Dependencies
The page_table_entry
crate includes more architecture-specific dependencies:
Target Architecture | Dependencies | Purpose |
---|---|---|
x86_64 | x86_64 = "0.15.2" | x86-64 specific register and instruction access |
aarch64 | aarch64-cpu = "10.0" | ARM64 system register and instruction access |
riscv32/riscv64 | None (built-in) | RISC-V support uses standard library features |
loongarch64 | None (built-in) | LoongArch support uses standard library features |
page_table_entry Architecture Dependencies
flowchart TD subgraph subGraph2["page_table_entry Conditional Dependencies"] PTE_CORE["Core Dependenciesbitflags, memory_addr"] subgraph subGraph1["Feature Flags"] ARM_EL2["arm-el2 featureARM Exception Level 2"] end subgraph subGraph0["Architecture Dependencies"] AARCH64_CFG["cfg(target_arch = aarch64)"] X86_64_CFG["cfg(target_arch = x86_64)"] DOC_CFG["cfg(doc)"] AARCH64_DEP["aarch64-cpu = 10.0"] X86_64_DEP["x86_64 = 0.15.2"] DOC_DEP["All dependencies"] end end AARCH64_CFG --> AARCH64_DEP AARCH64_DEP --> ARM_EL2 DOC_CFG --> DOC_DEP PTE_CORE --> AARCH64_CFG PTE_CORE --> DOC_CFG PTE_CORE --> X86_64_CFG X86_64_CFG --> X86_64_DEP
Sources: page_table_entry/Cargo.toml(L22 - L26) page_table_entry/Cargo.toml(L15 - L16)
Documentation Configuration
Both crates include special configuration for documentation generation using docs.rs
:
- Documentation builds include all architecture dependencies via
cfg(doc)
- Rustc arguments add
--cfg doc
for conditional compilation during docs generation - This ensures complete documentation coverage across all supported architectures
The configuration in page_table_multiarch/Cargo.toml(L26 - L27) and page_table_entry/Cargo.toml(L28 - L29) enables this behavior.
Sources: page_table_multiarch/Cargo.toml(L26 - L27) page_table_entry/Cargo.toml(L28 - L29)
Workspace Benefits
The two-crate structure provides several architectural advantages:
- Separation of Concerns: High-level page table operations vs. low-level PTE manipulation
- Conditional Compilation: Architecture-specific dependencies are isolated and only included when needed
- Reusability: The
page_table_entry
crate can be used independently for PTE operations - Testing: Each layer can be tested independently with appropriate mocking
- Documentation: Clear API boundaries make the system easier to understand and document
Sources: Cargo.toml(L1 - L19) page_table_multiarch/Cargo.toml(L1 - L28) page_table_entry/Cargo.toml(L1 - L29)
page_table_multiarch Crate
Relevant source files
The page_table_multiarch
crate provides high-level, architecture-independent page table management abstractions for 64-bit platforms. This crate implements the generic PageTable64
structure and supporting traits that enable unified page table operations across multiple hardware architectures including x86_64, AArch64, RISC-V, and LoongArch64.
For low-level page table entry definitions and architecture-specific implementations, see page_table_entry Crate. For detailed architecture-specific support information, see Architecture Support.
Core Components
The crate centers around three main abstractions that work together to provide architecture-independent page table management:
Architecture Abstraction Through Generic Types
Sources: page_table_multiarch/src/bits64.rs(L28 - L31) page_table_multiarch/src/lib.rs(L40 - L92)
PageTable64 Implementation
The PageTable64
struct is the central component providing a unified interface for page table operations across all supported architectures:
Component | Type Parameter | Purpose |
---|---|---|
M | PagingMetaData | Architecture-specific constants and validation |
PTE | GenericPTE | Page table entry manipulation |
H | PagingHandler | OS-dependent memory management |
flowchart TD subgraph subGraph2["Bulk Operations"] MAP_REGION["map_region()"] UNMAP_REGION["unmap_region()"] PROTECT_REGION["protect_region()"] end subgraph subGraph1["Core Operations"] MAP["map()"] UNMAP["unmap()"] QUERY["query()"] PROTECT["protect()"] REMAP["remap()"] end subgraph subGraph0["PageTable64 Structure"] ROOT["root_paddr: PhysAddr"] PHANTOM["_phantom: PhantomData"] end subgraph subGraph3["Utility Operations"] WALK["walk()"] COPY["copy_from()"] TRY_NEW["try_new()"] end MAP --> MAP_REGION PROTECT --> PROTECT_REGION ROOT --> MAP ROOT --> QUERY ROOT --> UNMAP UNMAP --> UNMAP_REGION
Page Table Management Methods
Single Page Operations
The PageTable64
provides methods for managing individual page mappings:
map()
- Maps a virtual page to a physical frame with specified size and flags page_table_multiarch/src/bits64.rs(L59 - L72)unmap()
- Removes a mapping and returns the physical address page_table_multiarch/src/bits64.rs(L116 - L125)query()
- Retrieves mapping information for a virtual address page_table_multiarch/src/bits64.rs(L134 - L141)protect()
- Updates mapping flags without changing the physical address page_table_multiarch/src/bits64.rs(L99 - L110)remap()
- Updates both physical address and flags page_table_multiarch/src/bits64.rs(L81 - L91)
Bulk Region Operations
For efficient handling of large memory regions, bulk operations automatically detect and use huge pages when possible:
map_region()
- Maps contiguous virtual regions with automatic huge page detection page_table_multiarch/src/bits64.rs(L157 - L217)unmap_region()
- Unmaps contiguous regions page_table_multiarch/src/bits64.rs(L226 - L257)protect_region()
- Updates flags for entire regions page_table_multiarch/src/bits64.rs(L266 - L299)
Sources: page_table_multiarch/src/bits64.rs(L33 - L347)
Architecture Abstraction System
The crate achieves architecture independence through a trait-based abstraction system:
flowchart TD subgraph subGraph3["Entry Operations"] NEW_PAGE["new_page()"] NEW_TABLE["new_table()"] IS_PRESENT["is_present()"] IS_HUGE["is_huge()"] PADDR["paddr()"] FLAGS["flags()"] end subgraph subGraph2["OS Integration"] ALLOC["alloc_frame()"] DEALLOC["dealloc_frame()"] PHYS_TO_VIRT["phys_to_virt()"] end subgraph subGraph1["Architecture Constants"] LEVELS["LEVELS: usize"] PA_BITS["PA_MAX_BITS: usize"] VA_BITS["VA_MAX_BITS: usize"] VADDR_TYPE["VirtAddr: MemoryAddr"] end subgraph subGraph0["Trait System"] PMD_TRAIT["PagingMetaData trait"] PH_TRAIT["PagingHandler trait"] GPTE_TRAIT["GenericPTE trait"] end GPTE_TRAIT --> FLAGS GPTE_TRAIT --> IS_HUGE GPTE_TRAIT --> IS_PRESENT GPTE_TRAIT --> NEW_PAGE GPTE_TRAIT --> NEW_TABLE GPTE_TRAIT --> PADDR PH_TRAIT --> ALLOC PH_TRAIT --> DEALLOC PH_TRAIT --> PHYS_TO_VIRT PMD_TRAIT --> LEVELS PMD_TRAIT --> PA_BITS PMD_TRAIT --> VADDR_TYPE PMD_TRAIT --> VA_BITS
Trait Responsibilities
PagingMetaData Trait
Defines architecture-specific constants and validation logic:
LEVELS
- Number of page table levels (3 or 4)PA_MAX_BITS
/VA_MAX_BITS
- Maximum address widthsVirtAddr
- Associated type for virtual addressesflush_tlb()
- Architecture-specific TLB flushing
PagingHandler Trait
Provides OS-dependent memory management operations:
alloc_frame()
- Allocates 4K physical framesdealloc_frame()
- Deallocates physical framesphys_to_virt()
- Converts physical to virtual addresses for direct access
Sources: page_table_multiarch/src/lib.rs(L40 - L92)
Page Table Navigation
The PageTable64
implements multi-level page table navigation supporting both 3-level and 4-level configurations:
flowchart TD subgraph subGraph3["Page Sizes"] SIZE_1G["1GB Pages (P3 level)"] SIZE_2M["2MB Pages (P2 level)"] SIZE_4K["4KB Pages (P1 level)"] end subgraph subGraph2["Index Functions"] P4_IDX["p4_index(vaddr)"] P3_IDX["p3_index(vaddr)"] P2_IDX["p2_index(vaddr)"] P1_IDX["p1_index(vaddr)"] end subgraph subGraph1["3-Level Page Table (RISC-V Sv39)"] P3_3["P3 Table (Level 0)"] P2_3["P2 Table (Level 1)"] P1_3["P1 Table (Level 2)"] end subgraph subGraph0["4-Level Page Table (x86_64, AArch64)"] P4["P4 Table (Level 0)"] P3_4["P3 Table (Level 1)"] P2_4["P2 Table (Level 2)"] P1_4["P1 Table (Level 3)"] end P1_IDX --> P1_3 P1_IDX --> P1_4 P2_IDX --> P2_3 P2_IDX --> P2_4 P3_IDX --> P3_3 P3_IDX --> P3_4 P4 --> P3_4 P4_IDX --> P4
Virtual Address Translation
The implementation uses bit manipulation to extract table indices from virtual addresses:
- P4 Index: Bits 39-47 (4-level only) page_table_multiarch/src/bits64.rs(L8 - L10)
- P3 Index: Bits 30-38 page_table_multiarch/src/bits64.rs(L12 - L14)
- P2 Index: Bits 21-29 page_table_multiarch/src/bits64.rs(L16 - L18)
- P1 Index: Bits 12-20 page_table_multiarch/src/bits64.rs(L20 - L22)
Sources: page_table_multiarch/src/bits64.rs(L8 - L22) page_table_multiarch/src/bits64.rs(L401 - L484)
Error Handling and TLB Management
The crate provides comprehensive error handling and TLB management:
Error Type | Description | Usage |
---|---|---|
NoMemory | Frame allocation failure | Memory exhaustion scenarios |
NotAligned | Address alignment violation | Invalid page boundaries |
NotMapped | Missing page table entry | Query/unmap operations |
AlreadyMapped | Existing mapping conflict | Duplicate map operations |
MappedToHugePage | Huge page access conflict | Table navigation errors |
flowchart TD subgraph subGraph2["Operation Results"] SINGLE["Single page operations"] BULK["Bulk region operations"] end subgraph subGraph1["TLB Management"] TF["TlbFlush<M>"] TFA["TlbFlushAll<M>"] subgraph Operations["Operations"] FLUSH["flush()"] IGNORE["ignore()"] FLUSH_ALL["flush_all()"] end end BULK --> TFA SINGLE --> TF TF --> FLUSH TF --> IGNORE TFA --> FLUSH_ALL
TLB Flush Types
TlbFlush<M>
- Manages single page TLB invalidation page_table_multiarch/src/lib.rs(L135 - L151)TlbFlushAll<M>
- Manages complete TLB invalidation page_table_multiarch/src/lib.rs(L157 - L172)
Sources: page_table_multiarch/src/lib.rs(L21 - L38) page_table_multiarch/src/lib.rs(L130 - L172)
Dependencies and Integration
The crate integrates with the workspace through carefully managed dependencies:
Conditional Compilation
The build system includes architecture-specific dependencies only when targeting supported platforms or building documentation:
- x86 dependency: Included for
x86_64
targets and documentation builds page_table_multiarch/Cargo.toml(L20 - L21) - riscv dependency: Included for
riscv32
/riscv64
targets and documentation builds page_table_multiarch/Cargo.toml(L23 - L24)
Sources: page_table_multiarch/Cargo.toml(L15 - L28) page_table_multiarch/src/lib.rs(L15 - L19)
page_table_entry Crate
Relevant source files
This document covers the page_table_entry
crate, which provides low-level page table entry definitions and abstractions for multiple hardware architectures. This crate serves as the foundation layer that defines the GenericPTE
trait and architecture-specific page table entry implementations.
For information about the high-level page table management abstractions that build upon this crate, see page_table_multiarch Crate.
Purpose and Core Functionality
The page_table_entry
crate abstracts page table entry manipulation across different processor architectures through a unified trait-based interface. It provides architecture-specific implementations of page table entries while maintaining a common API for higher-level page table management code.
Core Components:
GenericPTE
trait defining unified page table entry operationsMappingFlags
bitflags for architecture-independent memory permissions- Architecture-specific PTE implementations:
X64PTE
,A64PTE
,Rv64PTE
,LA64PTE
Sources: page_table_entry/README.md(L7 - L18) page_table_entry/src/lib.rs(L38 - L68)
GenericPTE Trait Architecture
The GenericPTE
trait provides the core abstraction that enables architecture-independent page table entry manipulation. All architecture-specific page table entry types implement this trait.
GenericPTE Interface
classDiagram class GenericPTE { <<trait>> +new_page(paddr: PhysAddr, flags: MappingFlags, is_huge: bool) Self +new_table(paddr: PhysAddr) Self +paddr() PhysAddr +flags() MappingFlags +set_paddr(paddr: PhysAddr) +set_flags(flags: MappingFlags, is_huge: bool) +bits() usize +is_unused() bool +is_present() bool +is_huge() bool +clear() } class X64PTE { +new_page() +new_table() +paddr() +flags() } class A64PTE { +new_page() +new_table() +paddr() +flags() } class Rv64PTE { +new_page() +new_table() +paddr() +flags() } class LA64PTE { +new_page() +new_table() +paddr() +flags() } GenericPTE ..|> X64PTE GenericPTE ..|> A64PTE GenericPTE ..|> Rv64PTE LA64PTE ..|> A64PTE
Key Methods
Method | Purpose | Return Type |
---|---|---|
new_page() | Create PTE for terminal page/block mapping | Self |
new_table() | Create PTE pointing to next-level page table | Self |
paddr() | Extract mapped physical address | PhysAddr |
flags() | Get generic mapping flags | MappingFlags |
set_paddr() | Modify mapped physical address | () |
set_flags() | Update mapping permissions | () |
bits() | Get raw PTE bits | usize |
is_present() | Check if mapping is valid | bool |
is_huge() | Check if PTE maps large page | bool |
Sources: page_table_entry/src/lib.rs(L41 - L68)
MappingFlags System
The MappingFlags
bitflags provide architecture-independent representation of memory mapping permissions and attributes.
flowchart TD subgraph subGraph1["Architecture Conversion"] X86_R["x86: PRESENT"] X86_W["x86: WRITE"] X86_X["x86: !NO_EXECUTE"] X86_U["x86: USER"] ARM_R["ARM: AF + validity"] ARM_W["ARM: AP[1:0]"] ARM_X["ARM: !PXN/UXN"] ARM_D["ARM: AttrIndx"] RV_R["RISC-V: R"] RV_W["RISC-V: W"] RV_X["RISC-V: X"] RV_U["RISC-V: U"] end subgraph subGraph0["MappingFlags Bitflags"] READ["READ (1 << 0)"] WRITE["WRITE (1 << 1)"] EXECUTE["EXECUTE (1 << 2)"] USER["USER (1 << 3)"] DEVICE["DEVICE (1 << 4)"] UNCACHED["UNCACHED (1 << 5)"] end DEVICE --> ARM_D EXECUTE --> ARM_X EXECUTE --> RV_X EXECUTE --> X86_X READ --> ARM_R READ --> RV_R READ --> X86_R USER --> RV_U USER --> X86_U WRITE --> ARM_W WRITE --> RV_W WRITE --> X86_W
Flag Definitions
Flag | Bit Position | Purpose |
---|---|---|
READ | 0 | Memory is readable |
WRITE | 1 | Memory is writable |
EXECUTE | 2 | Memory is executable |
USER | 3 | User-mode accessible |
DEVICE | 4 | Device memory type |
UNCACHED | 5 | Uncached memory access |
Sources: page_table_entry/src/lib.rs(L12 - L30)
Architecture Implementation Matrix
The crate supports four major processor architectures through dedicated PTE implementations:
flowchart TD subgraph subGraph5["Hardware Dependencies"] X64_DEP["x86_64 crate v0.15.2"] ARM_DEP["aarch64-cpu crate v10.0"] RV_DEP["riscv crate (implied)"] end subgraph subGraph4["Architecture Support"] subgraph AArch64["AArch64"] A64PTE["A64PTE"] A64_BITS["64-bit entries"] A64_LEVELS["4-level translation"] A64_EL2["EL2 support (feature)"] end subgraph x86_64["x86_64"] X64PTE["X64PTE"] X64_BITS["64-bit entries"] X64_LEVELS["4-level paging"] end subgraph LoongArch64["LoongArch64"] LA64PTE["LA64PTE"] LA_BITS["64-bit entries"] LA_LEVELS["4-level paging"] Rv64PTE["Rv64PTE"] RV_BITS["64-bit entries"] RV_SV39["Sv39 (3-level)"] end subgraph RISC-V["RISC-V"] LA64PTE["LA64PTE"] LA_BITS["64-bit entries"] LA_LEVELS["4-level paging"] Rv64PTE["Rv64PTE"] RV_BITS["64-bit entries"] RV_SV39["Sv39 (3-level)"] RV_SV48["Sv48 (4-level)"] end end A64PTE --> ARM_DEP A64_EL2 --> ARM_DEP X64PTE --> X64_DEP
Conditional Compilation
The crate uses target-specific conditional compilation to include only necessary dependencies:
Target Architecture | Dependencies | Features |
---|---|---|
x86_64 | x86_64 = "0.15.2" | Standard x86_64 support |
aarch64 | aarch64-cpu = "10.0" | ARM64, optionalarm-el2 |
riscv32/riscv64 | Architecture support built-in | Sv39/Sv48 modes |
loongarch64 | Built-in support | LA64 paging |
doc | All dependencies | Documentation builds |
Sources: page_table_entry/Cargo.toml(L22 - L27) page_table_entry/Cargo.toml(L15 - L16)
Usage Patterns
Basic PTE Creation and Manipulation
The typical usage pattern involves creating PTEs through the GenericPTE
trait methods:
// Example from documentation
use memory_addr::PhysAddr;
use page_table_entry::{GenericPTE, MappingFlags, x86_64::X64PTE};
let paddr = PhysAddr::from(0x233000);
let pte = X64PTE::new_page(
paddr,
MappingFlags::READ | MappingFlags::WRITE,
false, // not huge page
);
PTE State Queries
stateDiagram-v2 [*] --> Unused : "clear()" Unused --> Present : "new_page() / new_table()" Present --> Modified : "set_paddr() / set_flags()" Modified --> Present : "architecture conversion" Present --> Unused : "clear()" Present --> QueryState : "is_present()" Present --> QueryAddr : "paddr()" Present --> QueryFlags : "flags()" Present --> QueryHuge : "is_huge()" Present --> QueryBits : "bits()" QueryState --> Present QueryAddr --> Present QueryFlags --> Present QueryHuge --> Present QueryBits --> Present
Sources: page_table_entry/README.md(L28 - L46) page_table_entry/src/lib.rs(L41 - L68)
Dependencies and Build Configuration
Core Dependencies
Dependency | Version | Purpose |
---|---|---|
bitflags | 2.6 | MappingFlagsimplementation |
memory_addr | 0.3 | PhysAddrtype for physical addresses |
Architecture-Specific Dependencies
Architecture-specific crates are conditionally included based on compilation target:
- x86_64: Uses
x86_64
crate for hardware-specific page table flag definitions - AArch64: Uses
aarch64-cpu
crate for ARM64 system register and memory attribute definitions - RISC-V: Built-in support for RISC-V page table formats
- LoongArch: Built-in support for LoongArch64 page table formats
The doc
configuration includes all architecture dependencies to enable complete documentation generation.
Sources: page_table_entry/Cargo.toml(L18 - L29)
Core Concepts
Relevant source files
This document explains the fundamental abstractions that enable page_table_multiarch
to provide a unified page table interface across multiple processor architectures. These core concepts form the foundation that allows the same high-level API to work with x86_64, AArch64, RISC-V, and LoongArch64 page tables.
For architecture-specific implementations of these concepts, see Architecture Support. For the high-level PageTable64 API that builds on these concepts, see PageTable64 Implementation.
Abstraction Architecture
The system achieves architecture independence through a layered abstraction model built on three core traits and several supporting types. This design separates architecture-specific details from the generic page table logic.
flowchart TD subgraph subGraph3["Architecture Implementations"] X86_MD["X64PagingMetaData"] ARM_MD["A64PagingMetaData"] RV_MD["Sv39MetaData"] LA_MD["LA64MetaData"] X86_PTE["X64PTE"] ARM_PTE["A64PTE"] RV_PTE["Rv64PTE"] LA_PTE["LA64PTE"] end subgraph subGraph2["Supporting Types"] MF["MappingFlags"] PS["PageSize"] PE["PagingError"] TLB["TlbFlush<M>"] end subgraph subGraph1["Abstraction Traits"] PMD["PagingMetaData"] GPTE["GenericPTE"] PH["PagingHandler"] end subgraph subGraph0["Generic Layer"] PT64["PageTable64<M,PTE,H>"] API["map() unmap() walk()"] end API --> MF API --> PE API --> PS API --> TLB GPTE --> ARM_PTE GPTE --> LA_PTE GPTE --> RV_PTE GPTE --> X86_PTE PMD --> ARM_MD PMD --> LA_MD PMD --> RV_MD PMD --> X86_MD PT64 --> GPTE PT64 --> PH PT64 --> PMD
Sources: page_table_multiarch/src/lib.rs(L11 - L19) page_table_multiarch/src/lib.rs(L42 - L92)
Architecture Independence Traits
Three core traits provide the interface between generic page table logic and architecture-specific implementations.
PagingMetaData Trait
The PagingMetaData
trait encapsulates architecture-specific constants and behaviors that vary between processor families.
classDiagram class PagingMetaData { +LEVELS: usize +PA_MAX_BITS: usize +VA_MAX_BITS: usize +PA_MAX_ADDR: usize +VirtAddr: MemoryAddr +paddr_is_valid(paddr: usize) bool +vaddr_is_valid(vaddr: usize) bool +flush_tlb(vaddr: Option~VirtAddr~) } class X64PagingMetaData { +LEVELS = 4 +PA_MAX_BITS = 52 +VA_MAX_BITS = 48 } class A64PagingMetaData { +LEVELS = 4 +PA_MAX_BITS = 48 +VA_MAX_BITS = 48 } class Sv39MetaData { +LEVELS = 3 +PA_MAX_BITS = 56 +VA_MAX_BITS = 39 } PagingMetaData --|> X64PagingMetaData PagingMetaData --|> A64PagingMetaData PagingMetaData --|> Sv39MetaData
Key responsibilities include:
- Page table structure:
LEVELS
defines the number of translation levels - Address space limits:
PA_MAX_BITS
andVA_MAX_BITS
specify supported address ranges - Address validation:
paddr_is_valid()
andvaddr_is_valid()
enforce architecture constraints - TLB management:
flush_tlb()
handles cache invalidation
Sources: page_table_multiarch/src/lib.rs(L42 - L79)
GenericPTE Trait
The GenericPTE
trait provides a unified interface for manipulating page table entries across different architectures.
classDiagram class GenericPTE { +new_page(paddr: PhysAddr, flags: MappingFlags, is_huge: bool) Self +new_table(paddr: PhysAddr) Self +paddr() PhysAddr +flags() MappingFlags +set_paddr(paddr: PhysAddr) +set_flags(flags: MappingFlags, is_huge: bool) +bits() usize +is_unused() bool +is_present() bool +is_huge() bool +clear() } class X64PTE { +bits: usize } class A64PTE { +bits: usize } class Rv64PTE { +bits: usize } GenericPTE --|> X64PTE GenericPTE --|> A64PTE GenericPTE --|> Rv64PTE
The trait supports two types of entries:
- Page entries: Created with
new_page()
, point to actual memory pages - Table entries: Created with
new_table()
, point to next-level page tables
Sources: page_table_entry/src/lib.rs(L38 - L68)
PagingHandler Trait
The PagingHandler
trait abstracts OS-dependent memory management operations.
Method | Purpose | Return Type |
---|---|---|
alloc_frame() | Allocate a 4K physical frame | Option |
dealloc_frame(paddr) | Free an allocated frame | () |
phys_to_virt(paddr) | Convert physical to virtual address | VirtAddr |
This trait allows the page table implementation to work with different memory allocators and virtual memory layouts without being tied to a specific operating system.
Sources: page_table_multiarch/src/lib.rs(L83 - L92)
Memory Management Types
MappingFlags
The MappingFlags
bitflags provide a generic representation of memory permissions and attributes that gets translated to architecture-specific page table entry bits.
flowchart TD subgraph subGraph1["Architecture Translation"] X86_BITS["x86_64 PTE bits"] ARM_BITS["AArch64 descriptor bits"] RV_BITS["RISC-V PTE bits"] end subgraph subGraph0["Generic MappingFlags"] READ["READ (1<<0)"] WRITE["WRITE (1<<1)"] EXECUTE["EXECUTE (1<<2)"] USER["USER (1<<3)"] DEVICE["DEVICE (1<<4)"] UNCACHED["UNCACHED (1<<5)"] end DEVICE --> ARM_BITS DEVICE --> RV_BITS DEVICE --> X86_BITS EXECUTE --> ARM_BITS EXECUTE --> RV_BITS EXECUTE --> X86_BITS READ --> ARM_BITS READ --> RV_BITS READ --> X86_BITS UNCACHED --> ARM_BITS UNCACHED --> RV_BITS UNCACHED --> X86_BITS USER --> ARM_BITS USER --> RV_BITS USER --> X86_BITS WRITE --> ARM_BITS WRITE --> RV_BITS WRITE --> X86_BITS
Sources: page_table_entry/src/lib.rs(L12 - L36)
PageSize Enumeration
The PageSize
enum defines supported page sizes across architectures:
Size | Value | Usage |
---|---|---|
Size4K | 0x1000 (4 KB) | Standard page size |
Size2M | 0x20_0000 (2 MB) | Huge page (x86_64, AArch64) |
Size1G | 0x4000_0000 (1 GB) | Giant page (x86_64) |
The enum provides utility methods:
is_huge()
: Returns true for sizes larger than 4Kis_aligned(addr)
: Checks address alignmentalign_offset(addr)
: Calculates alignment offset
Sources: page_table_multiarch/src/lib.rs(L95 - L128)
Error Handling
The PagingError
enum defines standard error conditions:
flowchart TD PE["PagingError"] NM["NoMemoryCannot allocate memory"] NA["NotAlignedAddress not page-aligned"] NMP["NotMappedMapping not present"] AM["AlreadyMappedMapping already exists"] HP["MappedToHugePagePTE is huge but target is 4K"] PE --> AM PE --> HP PE --> NA PE --> NM PE --> NMP
Sources: page_table_multiarch/src/lib.rs(L22 - L38)
TLB Management
Translation Lookaside Buffer (TLB) management is handled through two RAII types that ensure proper cache invalidation.
TlbFlush and TlbFlushAll
Both types are marked with #[must_use]
to ensure TLB invalidation is explicitly handled. Callers must either:
- Call
.flush()
or.flush_all()
to invalidate TLB entries - Call
.ignore()
if TLB flushing will be handled elsewhere
Sources: page_table_multiarch/src/lib.rs(L130 - L172)
Integration Model
The abstractions work together to provide a cohesive system where generic logic operates through trait interfaces while architecture-specific implementations handle the details.
flowchart TD subgraph Hardware/OS["Hardware/OS"] MEM_ALLOC["Memory allocator"] TLB_HW["TLB hardware"] PT_HW["Page table hardware"] end subgraph subGraph2["Trait Implementations"] MD_CHECK["M::vaddr_is_valid()"] PTE_NEW["PTE::new_page()"] FLAGS_CONV["flags → PTE bits"] TLB_FLUSH["M::flush_tlb()"] end subgraph PageTable64<M,PTE,H>["PageTable64<M,PTE,H>"] PT64_IMPL["Page table traversal logic"] ALLOC["H::alloc_frame()"] CONVERT["H::phys_to_virt()"] end subgraph subGraph0["Application Code"] APP["map(vaddr, paddr, flags)"] end ALLOC --> MEM_ALLOC APP --> PT64_IMPL FLAGS_CONV --> PT_HW MD_CHECK --> TLB_FLUSH PT64_IMPL --> ALLOC PT64_IMPL --> CONVERT PT64_IMPL --> MD_CHECK PT64_IMPL --> PTE_NEW PTE_NEW --> FLAGS_CONV TLB_FLUSH --> TLB_HW
This design allows the same PageTable64
implementation to work across all supported architectures by delegating architecture-specific operations to trait implementations while maintaining type safety and performance.
Sources: page_table_multiarch/src/lib.rs(L11 - L19) page_table_multiarch/src/bits64.rs
PageTable64 Implementation
Relevant source files
This document covers the core PageTable64
struct that provides the main page table management functionality for 64-bit platforms in the page_table_multiarch library. It explains how the generic implementation handles multi-level page tables, memory mapping operations, and TLB management across different architectures.
For information about the trait system that enables architecture abstraction, see Generic Traits System. For details about architecture-specific implementations, see Architecture Support.
Core Structure and Generic Parameters
The PageTable64
struct serves as the primary interface for page table operations. It uses three generic parameters to achieve architecture independence while maintaining type safety.
flowchart TD subgraph subGraph2["Core Operations"] MAP["map()Single page mapping"] UNMAP["unmap()Remove mapping"] QUERY["query()Lookup mapping"] PROTECT["protect()Change flags"] REGION["map_region()Bulk operations"] end subgraph subGraph1["Generic Parameters"] M["M: PagingMetaDataArchitecture constantsAddress validationTLB flushing"] PTE["PTE: GenericPTEPage table entryFlag manipulationAddress extraction"] H["H: PagingHandlerMemory allocationPhysical-virtual mapping"] end subgraph subGraph0["PageTable64 Structure"] PT64["PageTable64<M, PTE, H>"] ROOT["root_paddr: PhysAddr"] PHANTOM["_phantom: PhantomData<(M, PTE, H)>"] end H --> MAP M --> MAP MAP --> PROTECT MAP --> QUERY MAP --> REGION MAP --> UNMAP PT64 --> H PT64 --> M PT64 --> PHANTOM PT64 --> PTE PT64 --> ROOT PTE --> MAP
The struct maintains only the physical address of the root page table, with all other state managed through the generic trait system. This design enables the same implementation to work across x86_64, AArch64, RISC-V, and LoongArch64 architectures.
Sources: page_table_multiarch/src/bits64.rs(L24 - L31) page_table_multiarch/src/lib.rs(L40 - L92)
Page Table Hierarchy and Indexing
PageTable64
supports both 3-level and 4-level page table configurations through constant indexing functions. The implementation uses bit manipulation to extract page table indices from virtual addresses.
flowchart TD subgraph subGraph2["Page Table Levels"] ROOT["Root Table(P4 or P3)"] L3["Level 3 Table512 entries"] L2["Level 2 Table512 entriesCan map 2M pages"] L1["Level 1 Table512 entriesMaps 4K pages"] end subgraph subGraph1["Index Functions"] P4FUNC["p4_index()(vaddr >> 39) & 511"] P3FUNC["p3_index()(vaddr >> 30) & 511"] P2FUNC["p2_index()(vaddr >> 21) & 511"] P1FUNC["p1_index()(vaddr >> 12) & 511"] end subgraph subGraph0["Virtual Address Breakdown"] VADDR["Virtual Address (64-bit)"] P4["P4 Indexbits[48:39](4-level only)"] P3["P3 Indexbits[38:30]"] P2["P2 Indexbits[29:21]"] P1["P1 Indexbits[20:12]"] OFFSET["Page Offsetbits[11:0]"] end P1 --> P1FUNC P1FUNC --> L1 P2 --> P2FUNC P2FUNC --> L2 P3 --> P3FUNC P3FUNC --> L3 P4 --> P4FUNC P4FUNC --> ROOT VADDR --> OFFSET VADDR --> P1 VADDR --> P2 VADDR --> P3 VADDR --> P4
The system handles both 3-level configurations (like RISC-V Sv39) and 4-level configurations (like x86_64, AArch64) by checking M::LEVELS
at compile time and selecting the appropriate starting table level.
Sources: page_table_multiarch/src/bits64.rs(L6 - L22) page_table_multiarch/src/bits64.rs(L401 - L426)
Memory Mapping Operations
The core mapping functionality provides both single-page operations and bulk region operations. The implementation automatically handles huge page detection and creation of intermediate page tables.
sequenceDiagram participant User as User participant PageTable64 as PageTable64 participant get_entry_mut_or_create as get_entry_mut_or_create participant GenericPTE as GenericPTE participant PagingHandler as PagingHandler Note over User,PagingHandler: Single Page Mapping Flow User ->> PageTable64: "map(vaddr, paddr, size, flags)" PageTable64 ->> get_entry_mut_or_create: "get_entry_mut_or_create(vaddr, size)" alt "Entry doesn't exist" get_entry_mut_or_create ->> PagingHandler: "alloc_frame()" PagingHandler -->> get_entry_mut_or_create: "PhysAddr" get_entry_mut_or_create ->> GenericPTE: "new_table(paddr)" get_entry_mut_or_create -->> PageTable64: "&mut PTE" else "Entry exists" get_entry_mut_or_create -->> PageTable64: "&mut PTE" end alt "Entry is unused" PageTable64 ->> GenericPTE: "new_page(paddr, flags, is_huge)" PageTable64 -->> User: "TlbFlush" else "Entry already mapped" PageTable64 -->> User: "Err(AlreadyMapped)" end
The mapping process supports three page sizes through the PageSize
enum:
Size4K
(0x1000): Standard 4KB pages mapped at level 1Size2M
(0x200000): 2MB huge pages mapped at level 2Size1G
(0x40000000): 1GB huge pages mapped at level 3
Sources: page_table_multiarch/src/bits64.rs(L59 - L72) page_table_multiarch/src/bits64.rs(L455 - L484) page_table_multiarch/src/lib.rs(L94 - L128)
Region Operations and Huge Page Optimization
The map_region()
method provides optimized bulk mapping with automatic huge page selection when allow_huge
is enabled. The implementation analyzes address alignment and region size to choose the largest possible page size.
Page Size | Alignment Required | Minimum Size | Use Case |
---|---|---|---|
4K | 4KB | 4KB | General purpose mapping |
2M | 2MB | 2MB | Large allocations, stack regions |
1G | 1GB | 1GB | Kernel text, large data structures |
flowchart TD START["map_region(vaddr, size, allow_huge)"] CHECK_ALIGN["Is vaddr and paddr1G aligned?size >= 1G?"] USE_1G["Use PageSize::Size1G"] CHECK_2M["Is vaddr and paddr2M aligned?size >= 2M?"] USE_2M["Use PageSize::Size2M"] USE_4K["Use PageSize::Size4K"] MAP["map(vaddr, paddr, page_size, flags)"] UPDATE["vaddr += page_sizesize -= page_size"] DONE["size == 0?"] CHECK_2M --> USE_2M CHECK_2M --> USE_4K CHECK_ALIGN --> CHECK_2M CHECK_ALIGN --> USE_1G DONE --> CHECK_ALIGN DONE --> START MAP --> UPDATE START --> CHECK_ALIGN UPDATE --> DONE USE_1G --> MAP USE_2M --> MAP USE_4K --> MAP
The algorithm prioritizes larger page sizes when possible, reducing TLB pressure and improving performance for large memory regions.
Sources: page_table_multiarch/src/bits64.rs(L157 - L217) page_table_multiarch/src/bits64.rs(L181 - L197)
TLB Management and Flushing
The system provides fine-grained TLB management through the TlbFlush
and TlbFlushAll
types. These types enforce explicit handling of TLB invalidation to prevent stale translations.
flowchart TD subgraph subGraph2["Flush Actions"] FLUSH["flush()Immediate TLB invalidation"] IGNORE["ignore()Defer to batch flush"] end subgraph subGraph1["Operations Returning Flushes"] MAP["map()"] UNMAP["unmap()"] PROTECT["protect()"] REMAP["remap()"] MAP_REGION["map_region()"] UNMAP_REGION["unmap_region()"] end subgraph subGraph0["TLB Flush Types"] SINGLE["TlbFlush<M>Single page flushContains VirtAddr"] ALL["TlbFlushAll<M>Complete TLB flushNo address needed"] end ALL --> FLUSH ALL --> IGNORE MAP --> SINGLE MAP_REGION --> ALL PROTECT --> SINGLE REMAP --> SINGLE SINGLE --> FLUSH SINGLE --> IGNORE UNMAP --> SINGLE UNMAP_REGION --> ALL
The #[must_use]
attribute on flush types ensures that TLB management is never accidentally ignored, preventing subtle bugs from stale page table entries.
Sources: page_table_multiarch/src/lib.rs(L130 - L172) page_table_multiarch/src/bits64.rs(L65 - L91)
Memory Management and Cleanup
PageTable64
implements automatic memory management through the Drop
trait, ensuring all allocated page tables are freed when the structure is destroyed. The cleanup process uses recursive walking to identify and deallocate intermediate tables.
flowchart TD subgraph subGraph1["Walk Process"] VISIT["Visit each table entry"] RECURSE["Recurse to next level"] CLEANUP["Post-order cleanup"] end subgraph subGraph0["Drop Implementation"] DROP["Drop::drop()"] WALK["walk() with post_func"] CHECK["level < LEVELS-1&&entry.is_present()&&!entry.is_huge()"] DEALLOC["H::dealloc_frame(entry.paddr())"] DEALLOC_ROOT["H::dealloc_frame(root_paddr)"] end CHECK --> CLEANUP CHECK --> DEALLOC CLEANUP --> CHECK DEALLOC --> CLEANUP DROP --> WALK RECURSE --> CLEANUP VISIT --> RECURSE WALK --> DEALLOC_ROOT WALK --> VISIT
The implementation carefully avoids freeing leaf-level entries, which point to actual data pages rather than page table structures that were allocated by the page table system itself.
Sources: page_table_multiarch/src/bits64.rs(L525 - L539) page_table_multiarch/src/bits64.rs(L313 - L325)
Private Implementation Details
The PageTable64
implementation uses several private helper methods to manage the complexity of multi-level page table traversal and memory allocation.
Method | Purpose | Returns |
---|---|---|
alloc_table() | Allocate and zero new page table | PagingResult |
table_of() | Get immutable slice to table entries | &[PTE] |
table_of_mut() | Get mutable slice to table entries | &mut [PTE] |
next_table() | Follow entry to next level table | PagingResult<&[PTE]> |
next_table_mut_or_create() | Get next level, creating if needed | PagingResult<&mut [PTE]> |
get_entry() | Find entry for virtual address | PagingResult<(&PTE, PageSize)> |
get_entry_mut() | Find mutable entry for virtual address | PagingResult<(&mut PTE, PageSize)> |
The helper methods handle error conditions like unmapped intermediate tables (NotMapped
) and attempts to traverse through huge pages (MappedToHugePage
), providing clear error semantics for page table operations.
Sources: page_table_multiarch/src/bits64.rs(L350 - L523) page_table_multiarch/src/lib.rs(L21 - L38)
Generic Traits System
Relevant source files
This document describes the core trait system that enables architecture abstraction in the page_table_multiarch library. The traits define interfaces for architecture-specific metadata, OS-level memory management, and page table entry manipulation, allowing a single PageTable64
implementation to work across multiple processor architectures.
For information about how these traits are implemented for specific architectures, see the Architecture Support section 4. For details about the main page table implementation that uses these traits, see PageTable64 Implementation.
Core Trait Architecture
The generic traits system consists of three primary traits that decouple architecture-specific behavior from the common page table logic:
flowchart TD subgraph subGraph3["Architecture Implementations"] X86_MD["X64PagingMetaData"] ARM_MD["A64PagingMetaData"] RV_MD["Sv39MetaData/Sv48MetaData"] LA_MD["LA64MetaData"] X86_PTE["X64PTE"] ARM_PTE["A64PTE"] RV_PTE["Rv64PTE"] LA_PTE["LA64PTE"] end subgraph subGraph2["Page Table Implementation"] PT64["PageTable64<M,PTE,H>Generic page table"] end subgraph subGraph1["Supporting Types"] MF["MappingFlagsGeneric permission flags"] PS["PageSize4K, 2M, 1G page sizes"] TLB["TlbFlush/TlbFlushAllTLB invalidation"] end subgraph subGraph0["Generic Traits Layer"] PMD["PagingMetaDataArchitecture constants& address validation"] PH["PagingHandlerOS memory management& virtual mapping"] GPTE["GenericPTEPage table entrymanipulation"] end GPTE --> ARM_PTE GPTE --> LA_PTE GPTE --> RV_PTE GPTE --> X86_PTE PMD --> ARM_MD PMD --> LA_MD PMD --> RV_MD PMD --> X86_MD PT64 --> GPTE PT64 --> MF PT64 --> PH PT64 --> PMD PT64 --> PS PT64 --> TLB
Sources: page_table_multiarch/src/lib.rs(L42 - L92) page_table_entry/src/lib.rs(L41 - L68)
PagingMetaData Trait
The PagingMetaData
trait defines architecture-specific constants and validation logic for page table implementations:
Method/Constant | Purpose | Type |
---|---|---|
LEVELS | Number of page table levels | const usize |
PA_MAX_BITS | Maximum physical address bits | const usize |
VA_MAX_BITS | Maximum virtual address bits | const usize |
VirtAddr | Virtual address type for this architecture | Associated type |
paddr_is_valid() | Validates physical addresses | fn(usize) -> bool |
vaddr_is_valid() | Validates virtual addresses | fn(usize) -> bool |
flush_tlb() | Flushes Translation Lookaside Buffer | fn(OptionSelf::VirtAddr) |
Address Validation Logic
The trait provides default implementations for address validation that work for most architectures:
flowchart TD PADDR_CHECK["paddr_is_valid(paddr)"] PADDR_RESULT["paddr <= PA_MAX_ADDR"] VADDR_CHECK["vaddr_is_valid(vaddr)"] VADDR_MASK["top_mask = usize::MAX << (VA_MAX_BITS - 1)"] VADDR_SIGN["(vaddr & top_mask) == 0 ||(vaddr & top_mask) == top_mask"] PADDR_CHECK --> PADDR_RESULT VADDR_CHECK --> VADDR_MASK VADDR_MASK --> VADDR_SIGN
Sources: page_table_multiarch/src/lib.rs(L61 - L72)
PagingHandler Trait
The PagingHandler
trait abstracts OS-level memory management operations required by the page table implementation:
Method | Purpose | Signature |
---|---|---|
alloc_frame() | Allocate a 4K physical frame | fn() -> Option |
dealloc_frame() | Free a physical frame | fn(PhysAddr) |
phys_to_virt() | Get virtual address for physical memory access | fn(PhysAddr) -> VirtAddr |
This trait enables the page table implementation to work with different memory management systems by requiring the OS to provide these three fundamental operations.
Sources: page_table_multiarch/src/lib.rs(L83 - L92)
GenericPTE Trait
The GenericPTE
trait provides a unified interface for manipulating page table entries across different architectures:
flowchart TD subgraph subGraph4["Query Methods"] IS_UNUSED["is_unused() -> bool"] IS_PRESENT["is_present() -> bool"] IS_HUGE["is_huge() -> bool"] end subgraph subGraph3["Modification Methods"] SET_PADDR["set_paddr(paddr)"] SET_FLAGS["set_flags(flags, is_huge)"] CLEAR["clear()"] end subgraph subGraph2["Access Methods"] GET_PADDR["paddr() -> PhysAddr"] GET_FLAGS["flags() -> MappingFlags"] GET_BITS["bits() -> usize"] end subgraph subGraph1["Creation Methods"] NEW_PAGE["new_page(paddr, flags, is_huge)"] NEW_TABLE["new_table(paddr)"] end subgraph subGraph0["GenericPTE Interface"] CREATE["Creation Methods"] ACCESS["Access Methods"] MODIFY["Modification Methods"] QUERY["Query Methods"] end ACCESS --> GET_BITS ACCESS --> GET_FLAGS ACCESS --> GET_PADDR CREATE --> NEW_PAGE CREATE --> NEW_TABLE MODIFY --> CLEAR MODIFY --> SET_FLAGS MODIFY --> SET_PADDR QUERY --> IS_HUGE QUERY --> IS_PRESENT QUERY --> IS_UNUSED
Sources: page_table_entry/src/lib.rs(L41 - L68)
MappingFlags System
The MappingFlags
bitflags provide a generic representation of memory permissions and attributes:
Flag | Value | Purpose |
---|---|---|
READ | 1 << 0 | Memory is readable |
WRITE | 1 << 1 | Memory is writable |
EXECUTE | 1 << 2 | Memory is executable |
USER | 1 << 3 | Memory is user-accessible |
DEVICE | 1 << 4 | Memory is device memory |
UNCACHED | 1 << 5 | Memory is uncached |
Architecture-specific implementations convert between these generic flags and their hardware-specific representations.
Sources: page_table_entry/src/lib.rs(L12 - L30)
Page Size Support
The PageSize
enum defines supported page sizes across architectures:
flowchart TD subgraph subGraph0["Helper Methods"] IS_HUGE["is_huge() -> bool"] IS_ALIGNED["is_aligned(addr) -> bool"] ALIGN_OFFSET["align_offset(addr) -> usize"] end PS["PageSize enum"] SIZE_4K["Size4K = 0x1000(4 KiB)"] SIZE_2M["Size2M = 0x20_0000(2 MiB)"] SIZE_1G["Size1G = 0x4000_0000(1 GiB)"] PS --> SIZE_1G PS --> SIZE_2M PS --> SIZE_4K SIZE_1G --> IS_HUGE SIZE_2M --> IS_HUGE
Sources: page_table_multiarch/src/lib.rs(L95 - L128)
TLB Management Types
The system provides type-safe TLB invalidation through must-use wrapper types:
flowchart TD subgraph Actions["Actions"] FLUSH["flush() - Execute flush"] IGNORE["ignore() - Skip flush"] FLUSH_ALL["flush_all() - Execute full flush"] end subgraph subGraph0["TLB Flush Types"] TLB_FLUSH["TlbFlush<M>Single address flush"] TLB_FLUSH_ALL["TlbFlushAll<M>Complete TLB flush"] end TLB_FLUSH --> FLUSH TLB_FLUSH --> IGNORE TLB_FLUSH_ALL --> FLUSH_ALL TLB_FLUSH_ALL --> IGNORE
These types ensure that TLB flushes are not accidentally forgotten after page table modifications by using the #[must_use]
attribute.
Sources: page_table_multiarch/src/lib.rs(L130 - L172)
Trait Integration Pattern
The three core traits work together to parameterize the PageTable64
implementation:
This design allows compile-time specialization while maintaining a common interface, enabling zero-cost abstractions across different processor architectures.
Sources: page_table_multiarch/src/lib.rs(L42 - L92) page_table_entry/src/lib.rs(L41 - L68)
Memory Mapping Flags
Relevant source files
This document explains the memory mapping flags system that provides a generic abstraction layer for page table entry permissions and attributes across different processor architectures. The MappingFlags
type serves as the common interface that is converted to and from architecture-specific page table entry flags, enabling consistent memory management behavior regardless of the underlying hardware platform.
For information about the overall page table implementation, see PageTable64 Implementation. For details about the trait system that enables this abstraction, see Generic Traits System.
MappingFlags Structure
The MappingFlags
type is defined as a bitflags structure that represents generic memory permissions and attributes that are common across all supported architectures.
flowchart TD subgraph subGraph0["MappingFlags Bitfield Structure"] MF["MappingFlags (usize)"] READ["READ (1 << 0)Memory is readable"] WRITE["WRITE (1 << 1)Memory is writable"] EXECUTE["EXECUTE (1 << 2)Memory is executable"] USER["USER (1 << 3)Memory is user accessible"] DEVICE["DEVICE (1 << 4)Memory is device memory"] UNCACHED["UNCACHED (1 << 5)Memory is uncached"] end MF --> DEVICE MF --> EXECUTE MF --> READ MF --> UNCACHED MF --> USER MF --> WRITE
The flags are designed to capture the essential memory access properties that operating systems need to control:
Flag | Purpose | Typical Usage |
---|---|---|
READ | Memory can be read | All mapped pages typically have this |
WRITE | Memory can be written | Data pages, stack, heap |
EXECUTE | Memory can contain executable code | Code segments, shared libraries |
USER | Memory accessible from user mode | User space mappings |
DEVICE | Memory-mapped device registers | Hardware device interfaces |
UNCACHED | Memory should not be cached | Performance-critical or coherency-sensitive regions |
Sources: page_table_entry/src/lib.rs(L12 - L30)
Architecture Conversion System
Each supported architecture implements bidirectional conversion between MappingFlags
and its native page table entry flag representation through From
trait implementations.
flowchart TD subgraph subGraph3["RISC-V Architecture"] RVPTF["RV Page Flags"] RVPTE["Rv64PTE"] end subgraph subGraph2["AArch64 Architecture"] A64ATTR["DescriptorAttr"] A64PTE["A64PTE"] MATTR["MemAttr enum"] end subgraph subGraph1["x86_64 Architecture"] X86PTF["PageTableFlags (PTF)"] X86PTE["X64PTE"] end subgraph subGraph0["Generic Layer"] MF["MappingFlags"] GPTE["GenericPTE trait"] end A64ATTR --> A64PTE A64ATTR --> MATTR A64ATTR --> MF A64PTE --> GPTE MF --> A64ATTR MF --> RVPTF MF --> X86PTF RVPTE --> GPTE RVPTF --> MF RVPTF --> RVPTE X86PTE --> GPTE X86PTF --> MF X86PTF --> X86PTE
Sources: page_table_entry/src/arch/x86_64.rs(L10 - L52) page_table_entry/src/arch/aarch64.rs(L114 - L189)
x86_64 Flag Mapping
The x86_64 implementation uses the PageTableFlags
from the x86_64
crate and maps generic flags to hardware-specific bits:
flowchart TD subgraph subGraph0["MappingFlags to x86_64 PTF"] MF_READ["MappingFlags::READ"] PTF_PRESENT["PTF::PRESENT"] MF_WRITE["MappingFlags::WRITE"] PTF_WRITABLE["PTF::WRITABLE"] MF_EXEC["MappingFlags::EXECUTE"] PTF_NO_EXECUTE["PTF::NO_EXECUTE"] MF_USER["MappingFlags::USER"] PTF_USER_ACC["PTF::USER_ACCESSIBLE"] MF_DEVICE["MappingFlags::DEVICE"] PTF_NO_CACHE["PTF::NO_CACHE"] PTF_WRITE_THROUGH["PTF::WRITE_THROUGH"] MF_UNCACHED["MappingFlags::UNCACHED"] end MF_DEVICE --> PTF_NO_CACHE MF_DEVICE --> PTF_WRITE_THROUGH MF_EXEC --> PTF_NO_EXECUTE MF_READ --> PTF_PRESENT MF_UNCACHED --> PTF_NO_CACHE MF_UNCACHED --> PTF_WRITE_THROUGH MF_USER --> PTF_USER_ACC MF_WRITE --> PTF_WRITABLE
Key mapping behaviors:
- Any non-empty
MappingFlags
automatically getsPTF::PRESENT
- Execute permission is inverted: absence of
EXECUTE
setsNO_EXECUTE
- Both
DEVICE
andUNCACHED
flags result in cache-disabled memory - Table entries always get
PRESENT | WRITABLE | USER_ACCESSIBLE
regardless of flags
Sources: page_table_entry/src/arch/x86_64.rs(L32 - L52) page_table_entry/src/arch/x86_64.rs(L76 - L79)
AArch64 Memory Attribute System
AArch64 implements a more sophisticated memory attribute system using the Memory Attribute Indirection Register (MAIR) approach:
flowchart TD subgraph subGraph3["AArch64 Memory Attributes"] MF2["MappingFlags"] subgraph subGraph2["MAIR Configuration"] MAIR_0["MAIR[7:0] = Device-nGnRE"] MAIR_1["MAIR[15:8] = Normal WriteBack"] MAIR_2["MAIR[23:16] = Normal NonCacheable"] end subgraph subGraph1["Descriptor Attributes"] ATTR_INDX["ATTR_INDX[2:0]"] VALID["VALID"] AP_RO["AP_RO (read-only)"] AP_EL0["AP_EL0 (user access)"] UXN["UXN (user execute never)"] PXN["PXN (privileged execute never)"] end subgraph subGraph0["Memory Types"] DEVICE_MEM["MemAttr::Device"] NORMAL_MEM["MemAttr::Normal"] UNCACHED_MEM["MemAttr::NormalNonCacheable"] end end ATTR_INDX --> MAIR_0 ATTR_INDX --> MAIR_1 ATTR_INDX --> MAIR_2 DEVICE_MEM --> ATTR_INDX MF2 --> DEVICE_MEM MF2 --> NORMAL_MEM MF2 --> UNCACHED_MEM NORMAL_MEM --> ATTR_INDX UNCACHED_MEM --> ATTR_INDX
The AArch64 system requires careful handling of execute permissions based on privilege level:
MappingFlags State | EL0 (User) Access | EL1+ (Kernel) Access |
---|---|---|
USER + EXECUTE | AP_EL0, noUXN | PXNset |
USERonly | AP_EL0,UXNset | PXNset |
EXECUTEonly | NoAP_EL0,UXNset | NoPXN |
Neither | NoAP_EL0,UXNset | PXNset |
Sources: page_table_entry/src/arch/aarch64.rs(L99 - L112) page_table_entry/src/arch/aarch64.rs(L149 - L189) page_table_entry/src/arch/aarch64.rs(L167 - L186)
GenericPTE Integration
The GenericPTE
trait methods utilize MappingFlags
to create and manipulate page table entries in an architecture-neutral way:
sequenceDiagram participant ApplicationCode as "Application Code" participant GenericPTEImplementation as "GenericPTE Implementation" participant MappingFlags as "MappingFlags" participant ArchitectureFlags as "Architecture Flags" Note over ApplicationCode,ArchitectureFlags: Creating a Page Entry ApplicationCode ->> GenericPTEImplementation: new_page(paddr, flags, is_huge) GenericPTEImplementation ->> MappingFlags: Convert MappingFlags MappingFlags ->> ArchitectureFlags: From<MappingFlags> ArchitectureFlags ->> GenericPTEImplementation: Architecture-specific bits GenericPTEImplementation ->> ApplicationCode: Complete PTE Note over ApplicationCode,ArchitectureFlags: Reading Entry Flags ApplicationCode ->> GenericPTEImplementation: flags() GenericPTEImplementation ->> ArchitectureFlags: Extract raw bits ArchitectureFlags ->> MappingFlags: From<ArchFlags> MappingFlags ->> GenericPTEImplementation: Generic MappingFlags GenericPTEImplementation ->> ApplicationCode: MappingFlags
The trait provides these flag-related methods:
new_page(paddr, flags, is_huge)
- Creates page entries with specified permissionsflags()
- Extracts current permissions asMappingFlags
set_flags(flags, is_huge)
- Updates entry permissions
Sources: page_table_entry/src/lib.rs(L41 - L56) page_table_entry/src/arch/x86_64.rs(L69 - L95) page_table_entry/src/arch/aarch64.rs(L210 - L236)
Conditional Compilation Features
The flag conversion system supports conditional compilation for different execution environments:
Feature | Effect | Usage |
---|---|---|
arm-el2 | Modifies AArch64 execute permission handling | Hypervisor/EL2 execution |
Default | Standard EL0/EL1 permission model | Normal kernel/user operation |
When arm-el2
is enabled, the execute permission logic is simplified to only use the UXN
bit rather than the complex EL0/EL1+ distinction.
Sources: page_table_entry/src/arch/aarch64.rs(L123 - L139) page_table_entry/src/arch/aarch64.rs(L181 - L186)
Architecture Support
Relevant source files
This document provides an overview of how the page_table_multiarch
library supports multiple processor architectures through a unified abstraction layer. It covers the conditional compilation strategy, supported architectures, and the trait-based abstraction mechanism that enables architecture-independent page table management.
For detailed implementation specifics of individual architectures, see x86_64 Support, AArch64 Support, RISC-V Support, and LoongArch64 Support.
Conditional Compilation Strategy
The library uses Rust's conditional compilation features to include only the relevant architecture-specific code for the target platform. This approach minimizes binary size and compile time while maintaining support for multiple architectures in a single codebase.
Compilation Configuration
The architecture modules are conditionally compiled based on the target architecture:
Sources: page_table_entry/src/arch/mod.rs(L1 - L12) page_table_multiarch/src/arch/mod.rs(L1 - L12)
The doc
condition ensures all architecture modules are available during documentation generation, allowing comprehensive API documentation regardless of the build target.
Supported Architectures Overview
The library supports four major processor architectures, each with distinct paging characteristics and implementations:
Architecture | Page Table Levels | Virtual Address Width | Physical Address Width | Key Features |
---|---|---|---|---|
x86_64 | 4 levels (PML4) | 48 bits | 52 bits | Legacy support, complex permissions |
AArch64 | 4 levels (VMSAv8-64) | 48 bits | 48 bits | Memory attributes, EL2 support |
RISC-V | 3 levels (Sv39) / 4 levels (Sv48) | 39/48 bits | Variable | Multiple page table formats |
LoongArch64 | 4 levels | Variable | Variable | Page Walk Controller (PWC) |
Architecture Module Structure
flowchart TD subgraph subGraph2["Concrete Types"] X64PT["X64PageTable"] A64PT["A64PageTable"] SV39PT["Sv39PageTable"] SV48PT["Sv48PageTable"] LA64PT["LA64PageTable"] X64PTE["X64PTE"] A64PTE["A64PTE"] RV64PTE["Rv64PTE"] LA64PTE["LA64PTE"] end subgraph subGraph1["page_table_entry Crate"] PTE_ARCH["src/arch/"] PTE_X86["src/arch/x86_64.rs"] PTE_ARM["src/arch/aarch64.rs"] PTE_RV["src/arch/riscv.rs"] PTE_LA["src/arch/loongarch64.rs"] end subgraph subGraph0["page_table_multiarch Crate"] PTA_ARCH["src/arch/"] PTA_X86["src/arch/x86_64.rs"] PTA_ARM["src/arch/aarch64.rs"] PTA_RV["src/arch/riscv.rs"] PTA_LA["src/arch/loongarch64.rs"] end A64PT --> A64PTE LA64PT --> LA64PTE PTA_ARCH --> PTA_ARM PTA_ARCH --> PTA_LA PTA_ARCH --> PTA_RV PTA_ARCH --> PTA_X86 PTA_ARM --> A64PT PTA_LA --> LA64PT PTA_RV --> SV39PT PTA_RV --> SV48PT PTA_X86 --> X64PT PTE_ARCH --> PTE_ARM PTE_ARCH --> PTE_LA PTE_ARCH --> PTE_RV PTE_ARCH --> PTE_X86 PTE_ARM --> A64PTE PTE_LA --> LA64PTE PTE_RV --> RV64PTE PTE_X86 --> X64PTE SV39PT --> RV64PTE SV48PT --> RV64PTE X64PT --> X64PTE
Sources: page_table_entry/src/arch/mod.rs(L1 - L12) page_table_multiarch/src/arch/mod.rs(L1 - L12)
Architecture Abstraction Mechanism
The multi-architecture support is achieved through a trait-based abstraction layer that defines common interfaces while allowing architecture-specific implementations.
Core Abstraction Traits
flowchart TD subgraph subGraph5["Architecture Implementations"] subgraph LoongArch64["LoongArch64"] LA64MD["LA64MetaData"] LA64PTE_IMPL["LA64PTE"] end subgraph RISC-V["RISC-V"] SV39MD["Sv39MetaData"] SV48MD["Sv48MetaData"] RV64PTE_IMPL["Rv64PTE"] end subgraph AArch64["AArch64"] A64MD["A64PagingMetaData"] A64PTE_IMPL["A64PTE"] end subgraph x86_64["x86_64"] X64MD["X64PagingMetaData"] X64PTE_IMPL["X64PTE"] end end subgraph subGraph0["Generic Abstractions"] PT64["PageTable64<M, PTE, H>"] PMD["PagingMetaData trait"] GPTE["GenericPTE trait"] PH["PagingHandler trait"] MF["MappingFlags"] end GPTE --> A64PTE_IMPL GPTE --> LA64PTE_IMPL GPTE --> MF GPTE --> RV64PTE_IMPL GPTE --> X64PTE_IMPL PMD --> A64MD PMD --> LA64MD PMD --> SV39MD PMD --> SV48MD PMD --> X64MD PT64 --> GPTE PT64 --> PH PT64 --> PMD
Sources: page_table_entry/src/arch/mod.rs(L1 - L12) page_table_multiarch/src/arch/mod.rs(L1 - L12)
Generic Type Parameters
The PageTable64<M, PTE, H>
type uses three generic parameters to achieve architecture independence:
- M: Implements
PagingMetaData
- provides architecture-specific constants like page sizes, address widths, and level counts - PTE: Implements
GenericPTE
- handles page table entry creation, flag conversion, and address extraction - H: Implements
PagingHandler
- manages frame allocation and deallocation through OS-specific interfaces
Integration Between Crates
The workspace structure separates concerns between high-level page table management and low-level entry manipulation:
Crate Dependency Flow
flowchart TD subgraph subGraph2["External Dependencies"] MEMORY_ADDR["memory_addr"] HW_CRATES["Architecture-specifichardware crates"] end subgraph page_table_entry["page_table_entry"] LIB_PTE["lib.rs"] ARCH_PTE["arch/ modules"] PTE_TYPES["PTE Types:X64PTEA64PTERv64PTELA64PTE"] TRAITS["GenericPTE traitMappingFlags"] end subgraph page_table_multiarch["page_table_multiarch"] LIB_PTM["lib.rs"] BITS64["bits64.rs"] ARCH_PTM["arch/ modules"] PT_TYPES["Page Table Types:X64PageTableA64PageTableSv39PageTableSv48PageTableLA64PageTable"] end ARCH_PTE --> PTE_TYPES ARCH_PTM --> PT_TYPES LIB_PTE --> ARCH_PTE LIB_PTE --> TRAITS LIB_PTM --> ARCH_PTM LIB_PTM --> BITS64 PTE_TYPES --> HW_CRATES PTE_TYPES --> MEMORY_ADDR PT_TYPES --> PTE_TYPES PT_TYPES --> TRAITS
Sources: page_table_entry/src/arch/mod.rs(L1 - L12) page_table_multiarch/src/arch/mod.rs(L1 - L12)
This separation allows the page_table_multiarch
crate to focus on high-level page table operations while page_table_entry
handles the architecture-specific details of page table entry formats and flag mappings. The conditional compilation ensures that only the necessary code for the target architecture is included in the final binary.
x86_64 Support
Relevant source files
This document covers the implementation of x86_64 architecture support in the page_table_multiarch library. It details how x86_64-specific page table entries, paging metadata, and memory management features are integrated with the generic multi-architecture abstraction layer.
For information about other supported architectures, see AArch64 Support, RISC-V Support, and LoongArch64 Support. For details about the overall architecture abstraction system, see Generic Traits System.
x86_64 Architecture Overview
The x86_64 implementation supports Intel and AMD 64-bit processors using 4-level page translation with the following characteristics:
Property | Value | Description |
---|---|---|
Paging Levels | 4 | PML4, PDPT, PD, PT |
Physical Address Space | 52 bits | Up to 4 PB of physical memory |
Virtual Address Space | 48 bits | 256 TB of virtual memory |
Page Sizes | 4KB, 2MB, 1GB | Standard and huge page support |
x86_64 Paging Hierarchy
flowchart TD subgraph subGraph0["Virtual Address Breakdown"] VA["48-bit Virtual Address"] PML4_IDX["Bits 47-39PML4 Index"] PDPT_IDX["Bits 38-30PDPT Index"] PD_IDX["Bits 29-21PD Index"] PT_IDX["Bits 20-12PT Index"] OFFSET["Bits 11-0Page Offset"] end CR3["CR3 RegisterPML4 Base Address"] PML4["PML4 (Level 4)Page Map Level 4"] PDPT["PDPT (Level 3)Page Directory Pointer Table"] PD["PD (Level 2)Page Directory"] PT["PT (Level 1)Page Table"] Page["4KB Page"] HugePage2M["2MB Huge Page"] HugePage1G["1GB Huge Page"] CR3 --> PML4 PD --> HugePage2M PD --> PT PDPT --> HugePage1G PDPT --> PD PML4 --> PDPT PT --> Page VA --> OFFSET VA --> PDPT_IDX VA --> PD_IDX VA --> PML4_IDX VA --> PT_IDX
Sources: page_table_multiarch/src/arch/x86_64.rs(L10 - L12)
Page Table Entry Implementation
The X64PTE
struct implements x86_64-specific page table entries with a 64-bit representation that follows Intel's page table entry format.
X64PTE Structure and Bit Layout
flowchart TD subgraph subGraph2["GenericPTE Implementation"] NEW_PAGE["new_page()"] NEW_TABLE["new_table()"] PADDR["paddr()"] FLAGS_METHOD["flags()"] SET_METHODS["set_paddr(), set_flags()"] STATE_METHODS["is_present(), is_huge(), is_unused()"] end subgraph subGraph0["X64PTE Bit Layout (64 bits)"] RESERVED["Bits 63-52Reserved/Available"] PHYS_ADDR["Bits 51-12Physical Address(PHYS_ADDR_MASK)"] FLAGS["Bits 11-0Page Table Flags"] end subgraph subGraph1["Key Flag Bits"] PRESENT["Bit 0: Present"] WRITABLE["Bit 1: Writable"] USER["Bit 2: User Accessible"] WRITE_THROUGH["Bit 3: Write Through"] NO_CACHE["Bit 4: No Cache"] ACCESSED["Bit 5: Accessed"] DIRTY["Bit 6: Dirty"] HUGE_PAGE["Bit 7: Page Size (Huge)"] GLOBAL["Bit 8: Global"] NO_EXECUTE["Bit 63: No Execute (NX)"] end FLAGS --> FLAGS_METHOD FLAGS_METHOD --> STATE_METHODS NEW_PAGE --> SET_METHODS PHYS_ADDR --> NEW_PAGE
The X64PTE
struct uses a transparent representation over a u64
value and implements the GenericPTE
trait to provide architecture-neutral page table entry operations.
Sources: page_table_entry/src/arch/x86_64.rs(L54 - L66) page_table_entry/src/arch/x86_64.rs(L68 - L112)
GenericPTE Trait Implementation
The X64PTE
implements all required methods of the GenericPTE
trait:
Method | Purpose | Implementation Detail |
---|---|---|
new_page() | Create page mapping entry | Sets physical address and convertsMappingFlagstoPTF |
new_table() | Create page table entry | SetsPRESENT,WRITABLE,USER_ACCESSIBLEflags |
paddr() | Extract physical address | Masks bits 12-51 usingPHYS_ADDR_MASK |
flags() | Get mapping flags | ConvertsPTFflags to genericMappingFlags |
is_present() | Check if entry is valid | TestsPTF::PRESENTbit |
is_huge() | Check if huge page | TestsPTF::HUGE_PAGEbit |
Sources: page_table_entry/src/arch/x86_64.rs(L69 - L95) page_table_entry/src/arch/x86_64.rs(L97 - L112)
Paging Metadata Implementation
The X64PagingMetaData
struct defines x86_64-specific constants and operations required by the PagingMetaData
trait.
Architecture Constants and TLB Management
flowchart TD subgraph subGraph2["PageTable64 Integration"] PT64["PageTable64"] TYPE_ALIAS["X64PageTable"] end subgraph subGraph0["X64PagingMetaData Constants"] LEVELS["LEVELS = 44-level page tables"] PA_BITS["PA_MAX_BITS = 5252-bit physical addresses"] VA_BITS["VA_MAX_BITS = 4848-bit virtual addresses"] VIRT_ADDR["VirtAddr = memory_addr::VirtAddrVirtual address type"] end subgraph subGraph1["TLB Management"] FLUSH_TLB["flush_tlb()"] SINGLE_FLUSH["x86::tlb::flush(vaddr)Single page invalidation"] FULL_FLUSH["x86::tlb::flush_all()Complete TLB flush"] end FLUSH_TLB --> FULL_FLUSH FLUSH_TLB --> SINGLE_FLUSH LEVELS --> PT64 PA_BITS --> PT64 PT64 --> TYPE_ALIAS VA_BITS --> PT64 VIRT_ADDR --> PT64
The TLB flushing implementation uses the x86
crate to perform architecture-specific translation lookaside buffer invalidation operations safely.
Sources: page_table_multiarch/src/arch/x86_64.rs(L7 - L25) page_table_multiarch/src/arch/x86_64.rs(L28)
Flag Conversion System
The x86_64 implementation provides bidirectional conversion between generic MappingFlags
and x86-specific PageTableFlags
(PTF).
MappingFlags to PTF Conversion
Generic Flag | x86_64 PTF | Notes |
---|---|---|
READ | PRESENT | Always set for readable pages |
WRITE | WRITABLE | Write permission |
EXECUTE | !NO_EXECUTE | Execute permission (inverted logic) |
USER | USER_ACCESSIBLE | User-mode access |
UNCACHED | NO_CACHE | Disable caching |
DEVICE | NO_CACHE | WRITE_THROUGH | Device memory attributes |
PTF to MappingFlags Conversion
The reverse conversion extracts generic flags from x86-specific page table flags, handling the inverted execute permission logic and presence requirements.
flowchart TD subgraph subGraph1["Special Handling"] EMPTY_CHECK["Empty flags → Empty PTF"] PRESENT_REQ["Non-empty flags → PRESENT bit set"] EXECUTE_INVERT["EXECUTE → !NO_EXECUTE"] DEVICE_COMBO["DEVICE → NO_CACHE | WRITE_THROUGH"] end subgraph subGraph0["Flag Conversion Flow"] GENERIC["MappingFlags(Architecture-neutral)"] X86_FLAGS["PageTableFlags (PTF)(x86-specific)"] end GENERIC --> DEVICE_COMBO GENERIC --> EMPTY_CHECK GENERIC --> EXECUTE_INVERT GENERIC --> PRESENT_REQ GENERIC --> X86_FLAGS X86_FLAGS --> GENERIC
Sources: page_table_entry/src/arch/x86_64.rs(L10 - L30) page_table_entry/src/arch/x86_64.rs(L32 - L52)
Integration with Generic System
The x86_64 support integrates with the generic page table system through the X64PageTable
type alias, which specializes the generic PageTable64
struct with x86_64-specific types.
Type Composition
flowchart TD subgraph subGraph2["Concrete Type"] X64_PAGE_TABLE["X64PageTable =PageTable64"] end subgraph subGraph1["x86_64 Implementations"] X64_METADATA["X64PagingMetaData"] X64_PTE["X64PTE"] USER_HANDLER["H: PagingHandler(User-provided)"] end subgraph subGraph0["Generic Framework"] PT64_GENERIC["PageTable64"] METADATA_TRAIT["PagingMetaData trait"] PTE_TRAIT["GenericPTE trait"] HANDLER_TRAIT["PagingHandler trait"] end HANDLER_TRAIT --> USER_HANDLER METADATA_TRAIT --> X64_METADATA PT64_GENERIC --> X64_PAGE_TABLE PTE_TRAIT --> X64_PTE USER_HANDLER --> X64_PAGE_TABLE X64_METADATA --> X64_PAGE_TABLE X64_PTE --> X64_PAGE_TABLE
The X64PageTable<H>
provides a complete x86_64 page table implementation that maintains all the functionality of the generic PageTable64
while using x86_64-specific page table entries and metadata.
Sources: page_table_multiarch/src/arch/x86_64.rs(L28)
AArch64 Support
Relevant source files
This document covers the AArch64 (ARM64) architecture implementation in the page_table_multiarch library, including VMSAv8-64 translation table format support, memory attributes, and AArch64-specific page table operations. For information about other supported architectures, see Architecture Support.
Overview
The AArch64 implementation provides support for the VMSAv8-64 translation table format used by ARM64 processors. The implementation consists of two main components: the low-level page table entry definitions in the page_table_entry
crate and the high-level page table metadata and operations in the page_table_multiarch
crate.
AArch64 Implementation Architecture
flowchart TD subgraph subGraph3["Hardware Interaction"] TLBI["TLBI instructions"] MAIR_EL1_reg["MAIR_EL1 register"] VMSAv8["VMSAv8-64 format"] end subgraph subGraph2["Core Traits"] PagingMetaData_trait["PagingMetaData"] GenericPTE_trait["GenericPTE"] PageTable64_trait["PageTable64"] end subgraph page_table_entry/src/arch/aarch64.rs["page_table_entry/src/arch/aarch64.rs"] A64PTE["A64PTE"] DescriptorAttr["DescriptorAttr"] MemAttr["MemAttr"] MAIR_VALUE["MAIR_VALUE"] end subgraph page_table_multiarch/src/arch/aarch64.rs["page_table_multiarch/src/arch/aarch64.rs"] A64PageTable["A64PageTable<H>"] A64PagingMetaData["A64PagingMetaData"] FlushTLB["flush_tlb()"] end A64PTE --> DescriptorAttr A64PTE --> GenericPTE_trait A64PTE --> VMSAv8 A64PageTable --> PageTable64_trait A64PagingMetaData --> FlushTLB A64PagingMetaData --> PagingMetaData_trait DescriptorAttr --> MemAttr DescriptorAttr --> VMSAv8 FlushTLB --> TLBI MAIR_VALUE --> MAIR_EL1_reg MemAttr --> MAIR_VALUE
Sources: page_table_multiarch/src/arch/aarch64.rs(L1 - L38) page_table_entry/src/arch/aarch64.rs(L1 - L265)
Page Table Entry Implementation
The A64PTE
struct implements the GenericPTE
trait and represents VMSAv8-64 translation table descriptors. It uses the DescriptorAttr
bitflags to manage AArch64-specific memory attributes and access permissions.
AArch64 Page Table Entry Structure
flowchart TD subgraph subGraph3["Memory Attributes"] MemAttr_enum["MemAttr enum"] Device["Device (0)"] Normal["Normal (1)"] NormalNonCacheable["NormalNonCacheable (2)"] end subgraph subGraph2["GenericPTE Methods"] new_page["new_page()"] new_table["new_table()"] paddr["paddr()"] flags["flags()"] is_huge["is_huge()"] is_present["is_present()"] end subgraph subGraph1["DescriptorAttr Bitflags"] VALID["VALID (bit 0)"] NON_BLOCK["NON_BLOCK (bit 1)"] ATTR_INDX["ATTR_INDX (bits 2-4)"] AP_EL0["AP_EL0 (bit 6)"] AP_RO["AP_RO (bit 7)"] AF["AF (bit 10)"] PXN["PXN (bit 53)"] UXN["UXN (bit 54)"] end subgraph subGraph0["A64PTE Structure"] A64PTE_struct["A64PTE(u64)"] PHYS_ADDR_MASK["PHYS_ADDR_MASK0x0000_ffff_ffff_f000bits 12..48"] end A64PTE_struct --> flags A64PTE_struct --> is_huge A64PTE_struct --> is_present A64PTE_struct --> new_page A64PTE_struct --> new_table A64PTE_struct --> paddr AF --> new_page AP_EL0 --> flags AP_RO --> flags ATTR_INDX --> MemAttr_enum MemAttr_enum --> Device MemAttr_enum --> Normal MemAttr_enum --> NormalNonCacheable NON_BLOCK --> is_huge PHYS_ADDR_MASK --> paddr PXN --> flags UXN --> flags VALID --> is_present
Sources: page_table_entry/src/arch/aarch64.rs(L191 - L253) page_table_entry/src/arch/aarch64.rs(L9 - L58)
Key Methods and Bit Manipulation
The A64PTE
implementation provides several key methods for page table entry manipulation:
Method | Purpose | Physical Address Mask |
---|---|---|
new_page() | Creates page descriptor with mapping flags | 0x0000_ffff_ffff_f000 |
new_table() | Creates table descriptor for next level | 0x0000_ffff_ffff_f000 |
paddr() | Extracts physical address from bits 12-47 | 0x0000_ffff_ffff_f000 |
is_huge() | Checks if NON_BLOCK bit is clear | N/A |
is_present() | Checks VALID bit | N/A |
Sources: page_table_entry/src/arch/aarch64.rs(L200 - L253)
Memory Attributes and MAIR Configuration
The AArch64 implementation uses the Memory Attribute Indirection Register (MAIR) to define memory types. The MemAttr
enum defines three memory attribute types that correspond to MAIR indices.
Memory Attribute Configuration
flowchart TD subgraph subGraph2["DescriptorAttr Conversion"] from_mem_attr["from_mem_attr()"] INNER_SHAREABLE["INNER + SHAREABLEfor Normal memory"] end subgraph subGraph1["MAIR_EL1 Register Values"] MAIR_VALUE["MAIR_VALUE: 0x44_ff_04"] attr0["attr0: Device-nGnREnonGathering_nonReordering_EarlyWriteAck"] attr1["attr1: WriteBack_NonTransient_ReadWriteAllocInner + Outer"] attr2["attr2: NonCacheableInner + Outer"] end subgraph subGraph0["MemAttr Types"] Device_attr["Device (index 0)Device-nGnRE"] Normal_attr["Normal (index 1)WriteBack cacheable"] NormalNC_attr["NormalNonCacheable (index 2)Non-cacheable"] end Device_attr --> attr0 Device_attr --> from_mem_attr NormalNC_attr --> attr2 NormalNC_attr --> from_mem_attr Normal_attr --> attr1 Normal_attr --> from_mem_attr attr0 --> MAIR_VALUE attr1 --> MAIR_VALUE attr2 --> MAIR_VALUE from_mem_attr --> INNER_SHAREABLE
Sources: page_table_entry/src/arch/aarch64.rs(L62 - L112) page_table_entry/src/arch/aarch64.rs(L73 - L97)
Flag Conversion
The implementation provides bidirectional conversion between generic MappingFlags
and AArch64-specific DescriptorAttr
:
Flag Mapping Table
MappingFlags | DescriptorAttr | Description |
---|---|---|
READ | VALID | Entry is valid and readable |
WRITE | !AP_RO | Write access permitted (when AP_RO is clear) |
EXECUTE | !PXNor!UXN | Execute permissions based on privilege level |
USER | AP_EL0 | Accessible from EL0 (user space) |
DEVICE | MemAttr::Device | Device memory with ATTR_INDX = 0 |
UNCACHED | MemAttr::NormalNonCacheable | Non-cacheable normal memory |
Sources: page_table_entry/src/arch/aarch64.rs(L114 - L189)
Page Table Metadata
The A64PagingMetaData
struct implements the PagingMetaData
trait and defines AArch64-specific constants and operations for the VMSAv8-64 translation scheme.
Configuration Constants
Constant | Value | Description |
---|---|---|
LEVELS | 4 | Number of page table levels |
PA_MAX_BITS | 48 | Maximum physical address bits |
VA_MAX_BITS | 48 | Maximum virtual address bits |
Sources: page_table_multiarch/src/arch/aarch64.rs(L11 - L15)
Virtual Address Validation
The vaddr_is_valid()
method implements AArch64's canonical address validation:
fn vaddr_is_valid(vaddr: usize) -> bool {
let top_bits = vaddr >> Self::VA_MAX_BITS;
top_bits == 0 || top_bits == 0xffff
}
This ensures that virtual addresses are either in the lower canonical range (top 16 bits all zeros) or upper canonical range (top 16 bits all ones).
Sources: page_table_multiarch/src/arch/aarch64.rs(L17 - L20)
TLB Management
The AArch64 implementation provides TLB (Translation Lookaside Buffer) invalidation through assembly instructions in the flush_tlb()
method.
TLB Invalidation Operations
flowchart TD subgraph subGraph0["flush_tlb() Implementation"] flush_tlb_method["flush_tlb(vaddr: Option<VirtAddr>)"] vaddr_check["vaddr.is_some()?"] specific_tlbi["TLBI VAAE1ISTLB Invalidate by VAAll ASID, EL1, Inner Shareable"] global_tlbi["TLBI VMALLE1TLB Invalidate by VMIDAll at stage 1, EL1"] dsb_sy["DSB SYData Synchronization Barrier"] isb["ISBInstruction Synchronization Barrier"] end dsb_sy --> isb flush_tlb_method --> vaddr_check global_tlbi --> dsb_sy specific_tlbi --> dsb_sy vaddr_check --> global_tlbi vaddr_check --> specific_tlbi
Sources: page_table_multiarch/src/arch/aarch64.rs(L22 - L34)
TLB Instruction Details
Operation | Instruction | Scope | Description |
---|---|---|---|
Specific invalidation | tlbi vaae1is, {vaddr} | Single VA | Invalidates TLB entries for specific virtual address across all ASIDs |
Global invalidation | tlbi vmalle1 | All VAs | Invalidates all stage 1 TLB entries for current VMID |
Memory barrier | dsb sy; isb | System-wide | Ensures TLB operations complete before subsequent instructions |
Sources: page_table_multiarch/src/arch/aarch64.rs(L24 - L32)
EL2 Support
The implementation includes conditional compilation support for ARM Exception Level 2 (EL2) through the arm-el2
feature flag. This affects execute permission handling in the flag conversion logic.
Permission Handling Differences
Configuration | User Execute | Kernel Execute | Implementation |
---|---|---|---|
Withoutarm-el2 | UsesUXNwhenAP_EL0set | UsesPXNfor kernel-only | Separate handling for user/kernel |
Witharm-el2 | UsesUXNonly | UsesUXNonly | Simplified execute control |
Sources: page_table_entry/src/arch/aarch64.rs(L123 - L186)
Type Alias and Integration
The A64PageTable<H>
type alias provides the complete AArch64 page table implementation by combining the metadata, page table entry type, and handler:
pub type A64PageTable<H> = PageTable64<A64PagingMetaData, A64PTE, H>;
This creates a fully configured page table type that applications can use with their chosen PagingHandler
implementation.
Sources: page_table_multiarch/src/arch/aarch64.rs(L37)
RISC-V Support
Relevant source files
This document covers RISC-V architecture support in the page_table_multiarch library, including both Sv39 (3-level) and Sv48 (4-level) page table configurations. The implementation provides a unified interface for RISC-V memory management that abstracts the differences between these two virtual memory systems while maintaining compatibility with the generic PageTable64
interface.
For information about other processor architectures, see x86_64 Support, AArch64 Support, and LoongArch64 Support. For details about the underlying generic abstractions, see Generic Traits System.
RISC-V Virtual Memory Systems
The RISC-V architecture defines multiple virtual memory systems. This library supports the two most common configurations used in 64-bit RISC-V systems.
Sv39 vs Sv48 Comparison
Feature | Sv39 | Sv48 |
---|---|---|
Page Table Levels | 3 | 4 |
Virtual Address Bits | 39 | 48 |
Physical Address Bits | 56 | 56 |
Virtual Address Space | 512 GiB | 256 TiB |
Page Table Structure | Page Directory → Page Table → Page | Page Map Level 4 → Page Directory → Page Table → Page |
RISC-V Page Table Type Hierarchy
flowchart TD subgraph subGraph4["Virtual Address"] SVVA["SvVirtAddr trait"] VIRTADDR["memory_addr::VirtAddr"] end subgraph subGraph3["Entry Type"] RV64PTE["Rv64PTE"] end subgraph subGraph2["Metadata Types"] SV39_META["Sv39MetaData<VA>"] SV48_META["Sv48MetaData<VA>"] end subgraph subGraph1["RISC-V Implementations"] SV39["Sv39PageTable<H>"] SV48["Sv48PageTable<H>"] end subgraph subGraph0["Generic Layer"] PT64["PageTable64<M,PTE,H>"] end PT64 --> SV39 PT64 --> SV48 SV39 --> RV64PTE SV39 --> SV39_META SV39_META --> SVVA SV48 --> RV64PTE SV48 --> SV48_META SV48_META --> SVVA SVVA --> VIRTADDR
Sources: page_table_multiarch/src/arch/riscv.rs(L30 - L68)
Core Components
Page Table Type Definitions
The RISC-V implementation provides two concrete page table types that specialize the generic PageTable64
for RISC-V systems:
// Sv39: 3-level page table with 39-bit virtual addresses
pub type Sv39PageTable<H> = PageTable64<Sv39MetaData<memory_addr::VirtAddr>, Rv64PTE, H>;
// Sv48: 4-level page table with 48-bit virtual addresses
pub type Sv48PageTable<H> = PageTable64<Sv48MetaData<memory_addr::VirtAddr>, Rv64PTE, H>;
Both types use:
- The same page table entry type (
Rv64PTE
) - The same virtual address type (
memory_addr::VirtAddr
) - Different metadata types that define the paging characteristics
Metadata Implementations
The metadata structures implement the PagingMetaData
trait to define architecture-specific constants:
Metadata Type | Levels | PA Max Bits | VA Max Bits |
---|---|---|---|
Sv39MetaData | 3 | 56 | 39 |
Sv48MetaData | 4 | 56 | 48 |
RISC-V Metadata Structure
flowchart TD subgraph subGraph3["Virtual Address Support"] SVVA_TRAIT["SvVirtAddr trait"] FLUSH_METHOD["flush_tlb(vaddr: Option<Self>)"] end subgraph subGraph2["Sv48 Implementation"] SV48_META["Sv48MetaData<VA>"] SV48_CONSTANTS["LEVELS = 4PA_MAX_BITS = 56VA_MAX_BITS = 48"] end subgraph subGraph1["Sv39 Implementation"] SV39_META["Sv39MetaData<VA>"] SV39_CONSTANTS["LEVELS = 3PA_MAX_BITS = 56VA_MAX_BITS = 39"] end subgraph subGraph0["PagingMetaData Trait"] PMD["PagingMetaData"] PMD_METHODS["LEVELS: usizePA_MAX_BITS: usizeVA_MAX_BITS: usizeVirtAddr: typeflush_tlb(vaddr)"] end PMD --> PMD_METHODS PMD_METHODS --> SV39_META PMD_METHODS --> SV48_META SV39_META --> SV39_CONSTANTS SV39_META --> SVVA_TRAIT SV48_META --> SV48_CONSTANTS SV48_META --> SVVA_TRAIT SVVA_TRAIT --> FLUSH_METHOD
Sources: page_table_multiarch/src/arch/riscv.rs(L30 - L62)
Page Table Entry Implementation
Rv64PTE Structure
The Rv64PTE
struct implements the GenericPTE
trait and provides RISC-V-specific page table entry functionality. It uses a 64-bit representation with specific bit field layouts defined by the RISC-V specification.
RISC-V PTE Bit Layout
flowchart TD subgraph subGraph2["Physical Address"] PHYS_MASK["PHYS_ADDR_MASK= (1<<54) - (1<<10)= bits 10..54"] end subgraph subGraph1["Flag Bits (PTEFlags)"] FLAGS["V: Valid (bit 0)R: Read (bit 1)W: Write (bit 2)X: Execute (bit 3)U: User (bit 4)G: Global (bit 5)A: Accessed (bit 6)D: Dirty (bit 7)"] end subgraph subGraph0["Rv64PTE (64 bits)"] BITS["Bits 63-54: ReservedBits 53-10: Physical Page NumberBits 9-8: ReservedBits 7-0: Flags"] end BITS --> FLAGS BITS --> PHYS_MASK
Sources: page_table_entry/src/arch/riscv.rs(L77 - L89)
Flag Conversion
The implementation provides bidirectional conversion between RISC-V-specific PTEFlags
and generic MappingFlags
:
Generic Flag | RISC-V Flag | Description |
---|---|---|
READ | R | Page is readable |
WRITE | W | Page is writable |
EXECUTE | X | Page is executable |
USER | U | Page is accessible in user mode |
The conversion automatically sets additional RISC-V flags:
V
(Valid) flag is set for any non-empty mappingA
(Accessed) andD
(Dirty) flags are automatically set to avoid hardware page faults
Sources: page_table_entry/src/arch/riscv.rs(L33 - L75)
GenericPTE Implementation
The Rv64PTE
implements key GenericPTE
methods:
// Create page entry with proper flag conversion
fn new_page(paddr: PhysAddr, flags: MappingFlags, _is_huge: bool) -> Self
// Create page table entry (only Valid flag set)
fn new_table(paddr: PhysAddr) -> Self
// Extract physical address from PTE
fn paddr(&self) -> PhysAddr
// Convert RISC-V flags to generic flags
fn flags(&self) -> MappingFlags
// Check if entry represents a huge page (has R or X flags)
fn is_huge(&self) -> bool
Sources: page_table_entry/src/arch/riscv.rs(L91 - L131)
Virtual Address Management
SvVirtAddr Trait
The SvVirtAddr
trait extends the generic MemoryAddr
trait to add RISC-V-specific TLB management capabilities:
Virtual Address Trait Hierarchy
flowchart TD subgraph subGraph2["Concrete Implementation"] VIRTADDR["memory_addr::VirtAddr"] IMPL_FLUSH["calls riscv_flush_tlb()"] end subgraph subGraph1["RISC-V Extension"] SVVA["SvVirtAddr trait"] FLUSH_FUNC["flush_tlb(vaddr: Option<Self>)"] end subgraph subGraph0["Base Traits"] MEMADDR["memory_addr::MemoryAddr"] SEND["Send"] SYNC["Sync"] end MEMADDR --> SVVA SEND --> SVVA SVVA --> FLUSH_FUNC SVVA --> VIRTADDR SYNC --> SVVA VIRTADDR --> IMPL_FLUSH
Sources: page_table_multiarch/src/arch/riscv.rs(L17 - L28)
TLB Management
TLB Flushing Implementation
RISC-V TLB management uses the sfence.vma
instruction through the riscv
crate:
fn riscv_flush_tlb(vaddr: Option<memory_addr::VirtAddr>) {
unsafe {
if let Some(vaddr) = vaddr {
riscv::asm::sfence_vma(0, vaddr.as_usize()) // Flush specific address
} else {
riscv::asm::sfence_vma_all(); // Flush entire TLB
}
}
}
The TLB flush operation supports:
- Selective flushing: Flush TLB entries for a specific virtual address
- Global flushing: Flush all TLB entries when no address is specified
This functionality is integrated into the metadata types through the PagingMetaData::flush_tlb
method, which delegates to the SvVirtAddr::flush_tlb
implementation.
Sources: page_table_multiarch/src/arch/riscv.rs(L6 - L15) page_table_multiarch/src/arch/riscv.rs(L46 - L61)
LoongArch64 Support
Relevant source files
This page documents the LoongArch64 architecture support within the page_table_multiarch
library. LoongArch64 is implemented as one of the four supported architectures, providing 4-level page table management with architecture-specific features like Page Walk Controllers (PWC) and privilege level control.
For information about the overall multi-architecture design, see Architecture Support. For implementation details of other architectures, see x86_64 Support, AArch64 Support, and RISC-V Support.
Architecture Overview
LoongArch64 uses a 4-level page table structure with 48-bit physical and virtual addresses. The implementation leverages LoongArch64's Page Walk Controller (PWC) mechanism for hardware-assisted page table walking and provides comprehensive memory management capabilities including privilege level control and memory access type configuration.
Page Table Structure
LoongArch64 employs a 4-level hierarchical page table structure consisting of DIR3, DIR2, DIR1, and PT levels. The DIR4 level is ignored in this implementation, focusing on the standard 4-level configuration.
flowchart TD subgraph subGraph2["PWC Configuration"] PWCL["PWCL CSRLower Half Address SpacePTBase=12, PTWidth=9Dir1Base=21, Dir1Width=9Dir2Base=30, Dir2Width=9"] PWCH["PWCH CSRHigher Half Address SpaceDir3Base=39, Dir3Width=9"] end subgraph subGraph0["LoongArch64 4-Level Page Table"] VA["Virtual Address (48-bit)"] DIR3["DIR3 Table (Level 3)"] DIR2["DIR2 Table (Level 2)"] DIR1["DIR1 Table (Level 1)"] PT["Page Table (Level 0)"] PA["Physical Address (48-bit)"] end DIR1 --> PT DIR2 --> DIR1 DIR3 --> DIR2 PT --> PA PWCH --> DIR3 PWCL --> DIR1 PWCL --> DIR2 PWCL --> PT VA --> DIR3
Sources: page_table_multiarch/src/arch/loongarch64.rs(L11 - L41) page_table_multiarch/src/arch/loongarch64.rs(L43 - L46)
Page Table Entry Implementation
The LoongArch64 page table entry is implemented through the LA64PTE
struct, which provides a complete set of architecture-specific flags and integrates with the generic trait system.
LA64PTE Structure
The LA64PTE
struct wraps a 64-bit value containing both the physical address and control flags. The physical address occupies bits 12-47, providing support for 48-bit physical addressing.
flowchart TD subgraph subGraph0["LA64PTE Bit Layout (64 bits)"] RPLV["RPLV (63)Privilege Level Restricted"] NX["NX (62)Not Executable"] NR["NR (61)Not Readable"] UNUSED["Unused (60-13)"] G["G (12)Global (Huge Page)"] ADDR["Physical Address (47-12)36 bits"] W["W (8)Writable"] P["P (7)Present"] GH["GH (6)Global/Huge"] MATH["MATH (5)Memory Access Type High"] MATL["MATL (4)Memory Access Type Low"] PLVH["PLVH (3)Privilege Level High"] PLVL["PLVL (2)Privilege Level Low"] D["D (1)Dirty"] V["V (0)Valid"] end
Sources: page_table_entry/src/arch/loongarch64.rs(L11 - L59) page_table_entry/src/arch/loongarch64.rs(L121 - L133)
LoongArch64-Specific Flags
The PTEFlags
bitflags define comprehensive control over memory access and privilege levels:
Flag | Bit | Purpose |
---|---|---|
V | 0 | Page table entry is valid |
D | 1 | Page has been written (dirty bit) |
PLVL/PLVH | 2-3 | Privilege level control (4 levels) |
MATL/MATH | 4-5 | Memory Access Type (SUC/CC/WUC) |
GH | 6 | Global mapping or huge page indicator |
P | 7 | Physical page exists |
W | 8 | Page is writable |
G | 12 | Global mapping for huge pages |
NR | 61 | Page is not readable |
NX | 62 | Page is not executable |
RPLV | 63 | Privilege level restriction |
The Memory Access Type (MAT) field supports three modes:
00
(SUC): Strongly-ordered uncached01
(CC): Coherent cached10
(WUC): Weakly-ordered uncached11
: Reserved
Sources: page_table_entry/src/arch/loongarch64.rs(L16 - L58)
Flag Conversion System
The LoongArch64 implementation provides bidirectional conversion between architecture-specific PTEFlags
and generic MappingFlags
:
flowchart TD subgraph subGraph1["LoongArch64 to Generic"] PF["PTEFlags"] NR["!NR → READ"] W["W → WRITE"] NX["!NX → EXECUTE"] PLV["PLVL|PLVH → USER"] MAT["MAT bits → DEVICE/UNCACHED"] MF["MappingFlags"] READ["READ → !NR"] WRITE["WRITE → W"] EXECUTE["EXECUTE → !NX"] USER["USER → PLVH|PLVL"] DEVICE["DEVICE → MAT=00"] end subgraph subGraph0["Generic to LoongArch64"] PF["PTEFlags"] NR["!NR → READ"] W["W → WRITE"] NX["!NX → EXECUTE"] PLV["PLVL|PLVH → USER"] MAT["MAT bits → DEVICE/UNCACHED"] MF["MappingFlags"] READ["READ → !NR"] WRITE["WRITE → W"] EXECUTE["EXECUTE → !NX"] USER["USER → PLVH|PLVL"] DEVICE["DEVICE → MAT=00"] UNCACHED["UNCACHED → MAT=10"] CACHED["Default → MAT=01"] end MF --> CACHED MF --> DEVICE MF --> EXECUTE MF --> READ MF --> UNCACHED MF --> USER MF --> WRITE PF --> MAT PF --> NR PF --> NX PF --> PLV PF --> W
Sources: page_table_entry/src/arch/loongarch64.rs(L61 - L119)
Page Table Metadata
The LA64MetaData
struct implements the PagingMetaData
trait, providing architecture-specific constants and TLB management functionality.
Page Walk Controller Configuration
LoongArch64 uses Page Walk Controllers (PWC) to configure hardware page table walking. The implementation defines specific CSR values for both lower and upper address space translation:
flowchart TD subgraph subGraph2["PWCH Fields"] DIR3BASE["Dir3Base = 39Directory 3 Base"] DIR3WIDTH["Dir3Width = 9Directory 3 Width"] HPTW["HPTW_En = 0Hardware Page Table Walk"] end subgraph subGraph1["PWCL Fields"] PTBASE["PTBase = 12Page Table Base"] PTWIDTH["PTWidth = 9Page Table Width"] DIR1BASE["Dir1Base = 21Directory 1 Base"] DIR1WIDTH["Dir1Width = 9Directory 1 Width"] DIR2BASE["Dir2Base = 30Directory 2 Base"] DIR2WIDTH["Dir2Width = 9Directory 2 Width"] end subgraph subGraph0["PWC Configuration"] PWCL_REG["PWCL CSR (0x1C)Lower Half Address Space"] PWCH_REG["PWCH CSR (0x1D)Higher Half Address Space"] end PWCH_REG --> DIR3BASE PWCH_REG --> DIR3WIDTH PWCH_REG --> HPTW PWCL_REG --> DIR1BASE PWCL_REG --> DIR1WIDTH PWCL_REG --> DIR2BASE PWCL_REG --> DIR2WIDTH PWCL_REG --> PTBASE PWCL_REG --> PTWIDTH
Sources: page_table_multiarch/src/arch/loongarch64.rs(L11 - L41)
TLB Management
The LoongArch64 implementation provides sophisticated TLB (Translation Lookaside Buffer) management using the invtlb
instruction with data barrier synchronization:
flowchart TD subgraph Synchronization["Synchronization"] DBAR["dbar 0Data BarrierMemory ordering"] end subgraph subGraph0["TLB Flush Operations"] FLUSH_ALL["flush_tlb(None)Clear all entriesinvtlb 0x00"] FLUSH_ADDR["flush_tlb(Some(vaddr))Clear specific entryinvtlb 0x05"] end DBAR --> FLUSH_ADDR DBAR --> FLUSH_ALL
The implementation uses:
dbar 0
: Data barrier ensuring memory orderinginvtlb 0x00
: Clear all page table entriesinvtlb 0x05
: Clear entries matching specific virtual address with G=0
Sources: page_table_multiarch/src/arch/loongarch64.rs(L49 - L76)
Integration with Generic System
The LoongArch64 implementation seamlessly integrates with the generic trait system, providing the type alias LA64PageTable
and implementing all required traits.
Trait Implementation Structure
flowchart TD subgraph subGraph2["Constants & Methods"] LEVELS["LEVELS = 4"] PA_BITS["PA_MAX_BITS = 48"] VA_BITS["VA_MAX_BITS = 48"] FLUSH["flush_tlb()"] NEW_PAGE["new_page()"] NEW_TABLE["new_table()"] FLAGS["flags()"] PADDR["paddr()"] end subgraph subGraph1["LoongArch64 Implementation"] LA64MD["LA64MetaData"] LA64PTE["LA64PTE"] LA64PT["LA64PageTable<I>"] end subgraph subGraph0["Generic Traits"] PMD["PagingMetaData trait"] GPTE["GenericPTE trait"] PT64["PageTable64<M,PTE,H>"] end GPTE --> FLAGS GPTE --> NEW_PAGE GPTE --> NEW_TABLE GPTE --> PADDR LA64MD --> PMD LA64PT --> PT64 LA64PTE --> GPTE PMD --> FLUSH PMD --> LEVELS PMD --> PA_BITS PMD --> VA_BITS
Sources: page_table_multiarch/src/arch/loongarch64.rs(L43 - L76) page_table_entry/src/arch/loongarch64.rs(L135 - L177) page_table_multiarch/src/arch/loongarch64.rs(L85)
Type Aliases and Exports
The LoongArch64 support is exposed through a clean type alias that follows the library's naming conventions:
pub type LA64PageTable<I> = PageTable64<LA64MetaData, LA64PTE, I>;
This allows users to instantiate LoongArch64 page tables with their preferred PagingHandler
implementation while maintaining full compatibility with the generic PageTable64
interface.
Sources: page_table_multiarch/src/arch/loongarch64.rs(L78 - L85)
Development Guide
Relevant source files
This guide provides essential information for developers working with or contributing to the page_table_multiarch library. It covers the development environment setup, multi-architecture compilation strategy, testing approach, and documentation generation processes.
For detailed build instructions and testing procedures, see Building and Testing. For contribution guidelines and coding standards, see Contributing.
Development Environment Overview
The page_table_multiarch project uses a sophisticated multi-architecture development approach that requires specific toolchain configurations and conditional compilation strategies. The project is designed to work across five different target architectures while maintaining a unified development experience.
Development Architecture Matrix
flowchart TD subgraph subGraph2["CI Pipeline Components"] FORMAT["cargo fmt"] CLIPPY["cargo clippy"] BUILD["cargo build"] TEST["cargo test"] DOC["cargo doc"] end subgraph subGraph1["Target Architectures"] LINUX["x86_64-unknown-linux-gnu"] NONE["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] ARM["aarch64-unknown-none-softfloat"] LOONG["loongarch64-unknown-none-softfloat"] end subgraph subGraph0["Development Environment"] RUST["Rust Nightly Toolchain"] COMPONENTS["rust-src, clippy, rustfmt"] end ARM --> BUILD ARM --> CLIPPY COMPONENTS --> ARM COMPONENTS --> LINUX COMPONENTS --> LOONG COMPONENTS --> NONE COMPONENTS --> RISCV LINUX --> BUILD LINUX --> CLIPPY LINUX --> FORMAT LINUX --> TEST LOONG --> BUILD LOONG --> CLIPPY NONE --> BUILD NONE --> CLIPPY RISCV --> BUILD RISCV --> CLIPPY RUST --> COMPONENTS TEST --> DOC
Sources: .github/workflows/ci.yml(L10 - L32)
Workspace Dependency Structure
The development environment manages dependencies through a two-crate workspace with architecture-specific conditional compilation. The dependency resolution varies based on the target platform to include only necessary architecture support crates.
flowchart TD subgraph subGraph2["Hardware Support Crates"] TOCK["tock-registers v0.9.0"] RAW_CPUID["raw-cpuid v10.7.0"] BIT_FIELD["bit_field v0.10.2"] BIT_FIELD_2["bit_field v0.10.2"] VOLATILE["volatile v0.4.6"] CRITICAL["critical-section v1.2.0"] EMBEDDED["embedded-hal v1.0.0"] end subgraph subGraph1["page_table_entry Dependencies"] PTE_MEM["memory_addr v0.3.1"] AARCH64["aarch64-cpu v10.0.0"] BITFLAGS["bitflags v2.8.0"] X86_64["x86_64 v0.15.2"] end subgraph subGraph0["page_table_multiarch Dependencies"] PTM["page_table_multiarch v0.5.3"] LOG["log v0.4.25"] MEMORY["memory_addr v0.3.1"] PTE["page_table_entry v0.5.3"] RISCV_CRATE["riscv v0.12.1"] X86_CRATE["x86 v0.52.0"] end AARCH64 --> TOCK PTE --> AARCH64 PTE --> BITFLAGS PTE --> PTE_MEM PTE --> X86_64 PTM --> LOG PTM --> MEMORY PTM --> PTE PTM --> RISCV_CRATE PTM --> X86_CRATE RISCV_CRATE --> CRITICAL RISCV_CRATE --> EMBEDDED X86_CRATE --> BIT_FIELD X86_CRATE --> RAW_CPUID
Sources: Cargo.lock(L57 - L75) Cargo.lock(L5 - L149)
Multi-Architecture Development Strategy
The project employs a conditional compilation strategy that allows development and testing across multiple architectures while maintaining code clarity and avoiding unnecessary dependencies for specific targets.
Conditional Compilation System
The build system uses Cargo's target-specific dependencies and feature flags to include only the necessary architecture support:
Target Architecture | Included Dependencies | Special Features |
---|---|---|
x86_64-unknown-linux-gnu | x86,x86_64, full test suite | Unit testing enabled |
x86_64-unknown-none | x86,x86_64 | Bare metal support |
riscv64gc-unknown-none-elf | riscv | RISC-V Sv39/Sv48 support |
aarch64-unknown-none-softfloat | aarch64-cpu | ARM EL2 support available |
loongarch64-unknown-none-softfloat | Basic LoongArch support | Custom PWC configuration |
Documentation Build Configuration
Documentation generation requires special configuration to include all architecture-specific code regardless of the build target:
flowchart TD subgraph subGraph1["Architecture Inclusion"] ALL_ARCH["All architecture modules included"] X86_DOCS["x86_64 documentation"] ARM_DOCS["AArch64 documentation"] RISCV_DOCS["RISC-V documentation"] LOONG_DOCS["LoongArch documentation"] end subgraph subGraph0["Documentation Build Process"] DOC_ENV["RUSTFLAGS: --cfg doc"] DOC_FLAGS["RUSTDOCFLAGS: -Zunstable-options --enable-index-page"] DOC_BUILD["cargo doc --no-deps --all-features"] end ALL_ARCH --> ARM_DOCS ALL_ARCH --> LOONG_DOCS ALL_ARCH --> RISCV_DOCS ALL_ARCH --> X86_DOCS DOC_BUILD --> ALL_ARCH DOC_ENV --> DOC_BUILD DOC_FLAGS --> DOC_BUILD
Sources: .github/workflows/ci.yml(L40 - L49)
CI/CD Pipeline Architecture
The continuous integration system validates code across all supported architectures using a matrix build strategy. This ensures that changes work correctly across the entire supported platform ecosystem.
CI Matrix Strategy
flowchart TD subgraph subGraph3["Documentation Pipeline"] DOC_BUILD["cargo doc --no-deps --all-features"] PAGES_DEPLOY["GitHub Pages Deployment"] end subgraph subGraph2["Validation Steps"] VERSION_CHECK["rustc --version --verbose"] FORMAT_CHECK["cargo fmt --all -- --check"] CLIPPY_CHECK["cargo clippy --target TARGET --all-features"] BUILD_STEP["cargo build --target TARGET --all-features"] UNIT_TEST["cargo test --target x86_64-unknown-linux-gnu"] end subgraph subGraph1["Build Matrix"] NIGHTLY["Rust Nightly Toolchain"] TARGETS["5 Target Architectures"] end subgraph subGraph0["CI Trigger Events"] PUSH["git push"] PR["pull_request"] end BUILD_STEP --> UNIT_TEST CLIPPY_CHECK --> BUILD_STEP DOC_BUILD --> PAGES_DEPLOY FORMAT_CHECK --> CLIPPY_CHECK NIGHTLY --> TARGETS PR --> NIGHTLY PUSH --> NIGHTLY TARGETS --> VERSION_CHECK UNIT_TEST --> DOC_BUILD VERSION_CHECK --> FORMAT_CHECK
Sources: .github/workflows/ci.yml(L1 - L33) .github/workflows/ci.yml(L34 - L56)
Quality Gates
The CI pipeline enforces several quality gates that must pass before code integration:
Check Type | Command | Scope | Failure Behavior |
---|---|---|---|
Format | cargo fmt --all -- --check | All files | Hard failure |
Clippy | cargo clippy --target TARGET --all-features | Per target | Hard failure |
Build | cargo build --target TARGET --all-features | Per target | Hard failure |
Unit Tests | cargo test --target x86_64-unknown-linux-gnu | Linux only | Hard failure |
Documentation | cargo doc --no-deps --all-features | All features | Soft failure on non-default branch |
Development Workflow Integration
The development environment integrates multiple tools and processes to maintain code quality across architecture boundaries:
Toolchain Requirements
- Rust Toolchain: Nightly required for unstable features and cross-compilation support
- Components:
rust-src
for cross-compilation,clippy
for linting,rustfmt
for formatting - Targets: All five supported architectures must be installed for comprehensive testing
Feature Flag Usage
The project uses Cargo feature flags to control compilation of architecture-specific code:
flowchart TD subgraph subGraph1["Compilation Outcomes"] FULL_BUILD["Complete architecture support"] DOC_BUILD["Documentation with all architectures"] ARM_PRIVILEGE["ARM EL2 privilege level support"] end subgraph subGraph0["Feature Control"] ALL_FEATURES["--all-features"] DOC_CFG["--cfg doc"] ARM_EL2["arm-el2 feature"] end ALL_FEATURES --> FULL_BUILD ARM_EL2 --> ARM_PRIVILEGE DOC_CFG --> DOC_BUILD FULL_BUILD --> DOC_BUILD
Sources: .github/workflows/ci.yml(L25 - L27) .github/workflows/ci.yml(L42)
The development environment is designed to handle the complexity of multi-architecture support while providing developers with clear feedback about compatibility and correctness across all supported platforms. The CI pipeline ensures that every change is validated against the complete matrix of supported architectures before integration.
Building and Testing
Relevant source files
This page covers the development workflow for building and testing the page_table_multiarch library. It explains the multi-architecture build system, test execution across different targets, and the CI/CD pipeline that ensures code quality across all supported platforms.
For information about contributing code changes, see Contributing. For architectural details about the supported platforms, see Supported Platforms.
Build System Architecture
The project uses a Cargo workspace structure with conditional compilation to support multiple processor architectures. The build system automatically includes only the necessary dependencies and code paths based on the target architecture.
Workspace Structure
flowchart TD subgraph subGraph3["Conditional Dependencies"] TARGET_CFG["Target Architecture"] X86_DEPS["x86 v0.52x86_64 v0.15.1"] ARM_DEPS["aarch64-cpu v10.0"] RV_DEPS["riscv v0.12"] LA_DEPS["Built-in support"] DOC_DEPS["All dependenciesfor documentation"] end subgraph subGraph2["Cargo Workspace"] ROOT["Cargo.tomlWorkspace Root"] subgraph subGraph1["page_table_entry"] PTE_CARGO["page_table_entry/Cargo.toml"] PTE_LIB["lib.rs"] PTE_ARCH["arch/ entries"] end subgraph page_table_multiarch["page_table_multiarch"] PTM_CARGO["page_table_multiarch/Cargo.toml"] PTM_LIB["lib.rs"] PTM_ARCH["arch/ modules"] end end PTE_CARGO --> ARM_DEPS PTE_CARGO --> DOC_DEPS PTE_CARGO --> X86_DEPS PTM_CARGO --> ARM_DEPS PTM_CARGO --> LA_DEPS PTM_CARGO --> PTE_CARGO PTM_CARGO --> RV_DEPS PTM_CARGO --> X86_DEPS ROOT --> PTE_CARGO ROOT --> PTM_CARGO TARGET_CFG --> ARM_DEPS TARGET_CFG --> DOC_DEPS TARGET_CFG --> LA_DEPS TARGET_CFG --> RV_DEPS TARGET_CFG --> X86_DEPS
Architecture-Specific Dependencies
The build system conditionally includes dependencies based on the compilation target:
Target Architecture | Dependencies | Purpose |
---|---|---|
x86_64 | x86 v0.52,x86_64 v0.15.1 | x86-specific register and instruction access |
aarch64 | aarch64-cpu v10.0 | ARM system register manipulation |
riscv32/64 | riscv v0.12 | RISC-V CSR and instruction support |
loongarch64 | Built-in | Native LoongArch64 support |
Documentation | All dependencies | Complete API documentation |
Sources: Cargo.toml(L1 - L20) Cargo.lock(L1 - L150)
Development Environment Setup
Prerequisites
The project requires the Rust nightly toolchain with specific components and targets:
# Install nightly toolchain with required components
rustup toolchain install nightly
rustup component add --toolchain nightly rust-src clippy rustfmt
# Add target architectures for cross-compilation
rustup target add --toolchain nightly x86_64-unknown-none
rustup target add --toolchain nightly riscv64gc-unknown-none-elf
rustup target add --toolchain nightly aarch64-unknown-none-softfloat
rustup target add --toolchain nightly loongarch64-unknown-none-softfloat
Environment Configuration
For documentation builds and certain tests, set the doc
configuration flag:
export RUSTFLAGS="--cfg doc"
This enables all architecture-specific code paths during documentation generation, ensuring complete API coverage.
Sources: .github/workflows/ci.yml(L15 - L19) .github/workflows/ci.yml(L31 - L32) .github/workflows/ci.yml(L42)
Building the Project
Basic Build Commands
# Build for the host architecture (typically x86_64)
cargo build
# Build with all features enabled
cargo build --all-features
# Cross-compile for specific targets
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features
cargo build --target loongarch64-unknown-none-softfloat --all-features
Build Process Flow
flowchart TD subgraph Compilation["Compilation"] COMPILE["rustc compilation"] CFG_FLAGS["Conditional compilation#[cfg(target_arch)]"] ARCH_MODULES["Architecture-specific modules"] LINK["Link final binary/library"] end subgraph subGraph0["Dependency Resolution"] RESOLVE["Cargo resolves dependencies"] TARGET_CHECK["Target Architecture"] X86_PATH["Include x86/x86_64 crates"] ARM_PATH["Include aarch64-cpu crate"] RV_PATH["Include riscv crate"] LA_PATH["Built-in support only"] end START["cargo build --target TARGET"] ARCH_MODULES --> LINK ARM_PATH --> COMPILE CFG_FLAGS --> ARCH_MODULES COMPILE --> CFG_FLAGS LA_PATH --> COMPILE RESOLVE --> TARGET_CHECK RV_PATH --> COMPILE START --> RESOLVE TARGET_CHECK --> ARM_PATH TARGET_CHECK --> LA_PATH TARGET_CHECK --> RV_PATH TARGET_CHECK --> X86_PATH X86_PATH --> COMPILE
The build process uses Rust's conditional compilation features to include only the relevant code and dependencies for each target architecture.
Sources: .github/workflows/ci.yml(L26 - L27)
Running Tests
Test Execution Matrix
Tests are executed differently based on the target platform due to hardware and emulation constraints:
Target | Test Type | Execution Environment |
---|---|---|
x86_64-unknown-linux-gnu | Unit tests | Native execution |
x86_64-unknown-none | Build verification | Compile-only |
riscv64gc-unknown-none-elf | Build verification | Compile-only |
aarch64-unknown-none-softfloat | Build verification | Compile-only |
loongarch64-unknown-none-softfloat | Build verification | Compile-only |
Test Commands
# Run all tests (only works on x86_64-unknown-linux-gnu)
cargo test -- --nocapture
# Build verification for embedded targets
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features
cargo build --target loongarch64-unknown-none-softfloat --all-features
# Code quality checks
cargo fmt --all -- --check
cargo clippy --target TARGET --all-features -- -A clippy::new_without_default
Test Architecture
flowchart TD subgraph subGraph1["Target Matrix"] LINUX["x86_64-unknown-linux-gnu"] BARE_X86["x86_64-unknown-none"] RV["riscv64gc-unknown-none-elf"] ARM["aarch64-unknown-none-softfloat"] LOONG["loongarch64-unknown-none-softfloat"] end subgraph subGraph0["Test Pipeline"] FORMAT["cargo fmt --check"] CLIPPY["cargo clippy"] BUILD["cargo build"] UNIT_TEST["cargo test"] end BUILD --> ARM BUILD --> BARE_X86 BUILD --> LINUX BUILD --> LOONG BUILD --> RV BUILD --> UNIT_TEST CLIPPY --> BUILD FORMAT --> CLIPPY UNIT_TEST --> LINUX
Unit tests execute only on x86_64-unknown-linux-gnu
because the embedded targets lack standard library support required for the test harness.
Sources: .github/workflows/ci.yml(L28 - L32) .github/workflows/ci.yml(L24 - L25) .github/workflows/ci.yml(L22 - L23)
CI/CD Pipeline
GitHub Actions Workflow
The CI pipeline runs on every push and pull request, executing a comprehensive test matrix across all supported architectures.
flowchart TD subgraph Documentation["Documentation"] DOC_BUILD["cargo doc --no-deps"] DEPLOY["Deploy to GitHub Pages"] end subgraph subGraph3["CI Steps"] CHECKOUT["actions/checkout@v4"] TOOLCHAIN["Setup Rust nightly"] VERSION_CHECK["rustc --version"] FMT_CHECK["Format check"] CLIPPY_CHECK["Clippy analysis"] BUILD_STEP["Build verification"] TEST_STEP["Unit test execution"] end subgraph subGraph2["Build Matrix"] NIGHTLY["Rust Nightly Toolchain"] subgraph subGraph1["Target Matrix"] LINUX_GNU["x86_64-unknown-linux-gnu"] X86_NONE["x86_64-unknown-none"] RISCV64["riscv64gc-unknown-none-elf"] AARCH64["aarch64-unknown-none-softfloat"] LOONGARCH["loongarch64-unknown-none-softfloat"] end end subgraph subGraph0["CI Triggers"] PUSH["git push"] PR["Pull Request"] end AARCH64 --> BUILD_STEP BUILD_STEP --> TEST_STEP CHECKOUT --> DOC_BUILD CHECKOUT --> TOOLCHAIN CLIPPY_CHECK --> BUILD_STEP DOC_BUILD --> DEPLOY FMT_CHECK --> CLIPPY_CHECK LINUX_GNU --> TEST_STEP LOONGARCH --> BUILD_STEP PR --> CHECKOUT PUSH --> CHECKOUT RISCV64 --> BUILD_STEP TOOLCHAIN --> VERSION_CHECK VERSION_CHECK --> FMT_CHECK X86_NONE --> BUILD_STEP
CI Configuration Details
The workflow configuration includes specific settings for multi-architecture support:
Configuration | Value | Purpose |
---|---|---|
fail-fast | false | Continue testing other targets if one fails |
rust-toolchain | nightly | Required for unstable features |
components | rust-src, clippy, rustfmt | Development tools and source code |
RUSTFLAGS | --cfg doc | Enable documentation-specific code paths |
RUSTDOCFLAGS | -Zunstable-options --enable-index-page | Enhanced documentation features |
Sources: .github/workflows/ci.yml(L1 - L57)
Documentation Generation
Local Documentation Build
# Build documentation with all features
RUSTFLAGS="--cfg doc" cargo doc --no-deps --all-features
# Open generated documentation
open target/doc/page_table_multiarch/index.html
Documentation Pipeline
sequenceDiagram participant Developer as Developer participant GitHubActions as GitHub Actions participant CargoDoc as Cargo Doc participant GitHubPages as GitHub Pages Developer ->> GitHubActions: Push to main branch GitHubActions ->> CargoDoc: RUSTFLAGS=--cfg doc Note over CargoDoc: cargo doc --no-deps --all-features CargoDoc ->> GitHubActions: Generated documentation GitHubActions ->> GitHubPages: Deploy to gh-pages branch GitHubPages ->> Developer: Updated documentation site Note over GitHubActions,GitHubPages: Documentation URL:<br>https://arceos-org.github.io/page_table_multiarch
The documentation build process uses special configuration flags to ensure all architecture-specific APIs are documented, even when building on a single architecture.
Documentation Features
- Index page generation: Provides a unified entry point for all crates
- Broken link detection: Fails build on invalid cross-references
- Missing documentation warnings: Ensures comprehensive API coverage
- All-features documentation: Includes conditional compilation paths
Sources: .github/workflows/ci.yml(L34 - L57) .github/workflows/ci.yml(L43) Cargo.toml(L15)
Contributing
Relevant source files
This document provides guidelines for contributing to the page_table_multiarch project, including development environment setup, code quality standards, testing requirements, and the contribution workflow. For information about building and running tests, see Building and Testing.
Development Environment Setup
Required Tools
The project requires the nightly Rust toolchain with specific components and targets for multi-architecture support.
flowchart TD subgraph Targets["Targets"] LINUX["x86_64-unknown-linux-gnu"] BARE["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] ARM["aarch64-unknown-none-softfloat"] LOONG["loongarch64-unknown-none-softfloat"] end subgraph Components["Components"] RUSTSRC["rust-src"] CLIPPY["clippy"] RUSTFMT["rustfmt"] end subgraph subGraph0["Rust Toolchain Requirements"] NIGHTLY["nightly toolchain"] COMPONENTS["Required Components"] TARGETS["Target Architectures"] end DEV["Development Environment"] COMPONENTS --> CLIPPY COMPONENTS --> RUSTFMT COMPONENTS --> RUSTSRC DEV --> NIGHTLY NIGHTLY --> COMPONENTS NIGHTLY --> TARGETS TARGETS --> ARM TARGETS --> BARE TARGETS --> LINUX TARGETS --> LOONG TARGETS --> RISCV
Development Environment Setup
Install the required toolchain components as specified in the CI configuration:
Sources: .github/workflows/ci.yml(L15 - L19)
IDE Configuration
The project includes basic IDE configuration exclusions in .gitignore
for common development environments:
/target
- Rust build artifacts/.vscode
- Visual Studio Code settings.DS_Store
- macOS system files
Sources: .gitignore(L1 - L4)
Code Quality Standards
Formatting Requirements
All code must be formatted using cargo fmt
with the default Rust formatting rules. The CI pipeline enforces this requirement:
flowchart TD PR["Pull Request"] CHECK["Format Check"] PASS["Check Passes"] FAIL["Check Fails"] REJECT["PR Blocked"] CONTINUE["Continue CI"] CHECK --> FAIL CHECK --> PASS FAIL --> REJECT PASS --> CONTINUE PR --> CHECK
Formatting Enforcement
The CI runs cargo fmt --all -- --check
to verify formatting compliance.
Sources: .github/workflows/ci.yml(L22 - L23)
Linting Standards
Code must pass Clippy analysis with the project's configured lint rules:
flowchart TD subgraph subGraph1["Allowed Exceptions"] NEW_WITHOUT_DEFAULT["clippy::new_without_default"] end subgraph subGraph0["Lint Configuration"] ALLOW["Allowed Lints"] TARGETS_ALL["All Target Architectures"] FEATURES["All Features Enabled"] end CLIPPY["Clippy Analysis"] ALLOW --> NEW_WITHOUT_DEFAULT CLIPPY --> ALLOW CLIPPY --> FEATURES CLIPPY --> TARGETS_ALL
Clippy Configuration
The project allows the clippy::new_without_default
lint, indicating that new()
methods without corresponding Default
implementations are acceptable in this codebase.
Sources: .github/workflows/ci.yml(L24 - L25)
Testing Requirements
Test Execution Strategy
The project uses a targeted testing approach where unit tests only run on the x86_64-unknown-linux-gnu
target, while other targets focus on compilation validation:
flowchart TD subgraph subGraph1["Test Execution"] LINUX_ONLY["x86_64-unknown-linux-gnu only"] UNIT_TESTS["cargo test"] DOC_CFG["RUSTFLAGS: --cfg doc"] end subgraph subGraph0["Build Verification"] ALL_TARGETS["All Targets"] BUILD["cargo build"] CLIPPY_CHECK["cargo clippy"] end CI["CI Pipeline"] ALL_TARGETS --> BUILD ALL_TARGETS --> CLIPPY_CHECK CI --> ALL_TARGETS CI --> LINUX_ONLY LINUX_ONLY --> DOC_CFG LINUX_ONLY --> UNIT_TESTS
Test Environment Configuration
Unit tests run with RUSTFLAGS: --cfg doc
to enable documentation-conditional code paths.
Sources: .github/workflows/ci.yml(L28 - L32)
Multi-Architecture Validation
While unit tests only run on Linux, the CI ensures that all architecture-specific code compiles correctly across all supported targets:
Target | Purpose | Validation |
---|---|---|
x86_64-unknown-linux-gnu | Development and testing | Full test suite |
x86_64-unknown-none | Bare metal x86_64 | Build + Clippy |
riscv64gc-unknown-none-elf | RISC-V bare metal | Build + Clippy |
aarch64-unknown-none-softfloat | ARM64 bare metal | Build + Clippy |
loongarch64-unknown-none-softfloat | LoongArch64 bare metal | Build + Clippy |
Sources: .github/workflows/ci.yml(L10 - L12) .github/workflows/ci.yml(L24 - L27)
Documentation Standards
Documentation Generation
The project maintains comprehensive API documentation that is automatically built and deployed:
flowchart TD subgraph Deployment["Deployment"] BUILD["cargo doc --no-deps"] DEPLOY["GitHub Pages"] BRANCH["gh-pages"] end subgraph subGraph1["Documentation Flags"] INDEX["--enable-index-page"] BROKEN_LINKS["-D rustdoc::broken_intra_doc_links"] MISSING_DOCS["-D missing-docs"] end subgraph subGraph0["Build Configuration"] RUSTFLAGS["RUSTFLAGS: --cfg doc"] RUSTDOCFLAGS["RUSTDOCFLAGS"] ALL_FEATURES["--all-features"] end DOC_JOB["Documentation Job"] BUILD --> DEPLOY DEPLOY --> BRANCH DOC_JOB --> ALL_FEATURES DOC_JOB --> BUILD DOC_JOB --> RUSTDOCFLAGS DOC_JOB --> RUSTFLAGS RUSTDOCFLAGS --> BROKEN_LINKS RUSTDOCFLAGS --> INDEX RUSTDOCFLAGS --> MISSING_DOCS
Documentation Requirements
- All public APIs must have documentation comments
- Documentation links must be valid (enforced by
-D rustdoc::broken_intra_doc_links
) - Missing documentation is treated as an error (
-D missing-docs
) - Documentation is built with all features enabled
Sources: .github/workflows/ci.yml(L40 - L43) .github/workflows/ci.yml(L47 - L49)
Pull Request Workflow
CI Pipeline Execution
Every pull request triggers the complete CI pipeline across all supported architectures:
flowchart TD subgraph subGraph2["Documentation Execution"] DOC_BUILD["Build Documentation"] DOC_DEPLOY["Deploy to gh-pages"] end subgraph subGraph1["CI Matrix Execution"] FORMAT["Format Check"] CLIPPY_ALL["Clippy (All Targets)"] BUILD_ALL["Build (All Targets)"] TEST_LINUX["Unit Tests (Linux only)"] end subgraph subGraph0["Parallel CI Jobs"] CI_MATRIX["CI Matrix Job"] DOC_JOB["Documentation Job"] end PR["Pull Request"] CI_MATRIX --> BUILD_ALL CI_MATRIX --> CLIPPY_ALL CI_MATRIX --> FORMAT CI_MATRIX --> TEST_LINUX DOC_BUILD --> DOC_DEPLOY DOC_JOB --> DOC_BUILD PR --> CI_MATRIX PR --> DOC_JOB
CI Trigger Events
The CI pipeline runs on both push
and pull_request
events, ensuring comprehensive validation.
Sources: .github/workflows/ci.yml(L3) .github/workflows/ci.yml(L5 - L33) .github/workflows/ci.yml(L34 - L57)
Deployment Process
Documentation deployment occurs automatically when changes are merged to the default branch, using the JamesIves/github-pages-deploy-action@v4
action with single-commit deployment to the gh-pages
branch.
Sources: .github/workflows/ci.yml(L50 - L56)
Architecture-Specific Considerations
When contributing architecture-specific code, ensure that:
- Conditional Compilation: Use appropriate
cfg
attributes for target-specific code - Cross-Architecture Compatibility: Changes should not break compilation on other architectures
- Testing Coverage: While unit tests only run on x86_64, ensure your code compiles cleanly on all targets
- Documentation: Architecture-specific features should be clearly documented with appropriate
cfg_attr
annotations
The CI matrix validates all contributions across the full range of supported architectures, providing confidence that cross-platform compatibility is maintained.
Sources: .github/workflows/ci.yml(L10 - L12) .github/workflows/ci.yml(L24 - L27)
Overview
Relevant source files
This document provides a comprehensive overview of the x86_rtc
crate, a specialized Rust library that provides low-level hardware access to the Real Time Clock (RTC) on x86_64 systems via CMOS interface. The crate is designed for operating system kernels, embedded systems, and bare-metal applications that require direct hardware clock access without relying on operating system services.
For detailed API documentation and usage patterns, see RTC Driver API. For information about the underlying hardware protocols and register access, see CMOS Hardware Interface. For build configuration and dependency management, see Crate Definition and Metadata.
System Architecture
The x86_rtc
crate operates as a hardware abstraction layer between application code and the x86_64 CMOS hardware that maintains system time.
High-Level System Architecture
flowchart TD subgraph subGraph3["Physical Hardware"] CMOS["CMOS Chip"] RTC_HW["Real Time Clock"] BATTERY["Battery Backup"] end subgraph subGraph2["Hardware Abstraction"] X86_64["x86_64 crate"] PORTS["I/O Port Access"] CMD_PORT["CMOS_COMMAND_PORT"] DATA_PORT["CMOS_DATA_PORT"] end subgraph subGraph1["x86_rtc Crate"] API["Rtc struct"] TIMESTAMP["get_unix_timestamp()"] SETTER["set_unix_timestamp()"] CONVERSION["BCD/Binary Conversion"] end subgraph subGraph0["Application Layer"] APP["Application Code"] OS["Operating Systems"] EMBEDDED["Embedded Systems"] end API --> SETTER API --> TIMESTAMP APP --> API CMD_PORT --> CMOS CMOS --> BATTERY CMOS --> RTC_HW CONVERSION --> X86_64 DATA_PORT --> CMOS EMBEDDED --> API OS --> API PORTS --> CMD_PORT PORTS --> DATA_PORT SETTER --> CONVERSION TIMESTAMP --> CONVERSION
Sources: Cargo.toml(L1 - L22) README.md(L1 - L13)
Code Entity Mapping
This diagram maps the crate's public interface to its internal implementation and hardware dependencies, showing the relationship between user-facing APIs and underlying code constructs.
Code Entity Architecture
Sources: Cargo.toml(L14 - L18) README.md(L9 - L12)
Key Characteristics
Characteristic | Value | Description |
---|---|---|
Crate Name | x86_rtc | Primary identifier in Rust ecosystem |
Version | 0.1.1 | Current stable release |
Architecture Support | x86_64only | Hardware-specific implementation |
Standard Library | no_stdcompatible | Suitable for bare-metal environments |
Primary Dependencies | cfg-if,x86_64 | Minimal dependency footprint |
License | Triple-licensed | GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0 |
Target Applications | OS kernels, embedded systems | Low-level hardware access |
Sources: Cargo.toml(L1 - L13)
Integration Context
The x86_rtc
crate is part of the broader ArceOS ecosystem, as indicated by its homepage and keywords. It serves as a specialized hardware driver component that can be integrated into larger operating system or embedded system projects.
Ecosystem Integration
flowchart TD subgraph subGraph3["Use Cases"] SYSTEM_TIME["System Time Management"] BOOT_TIME["Boot Time Initialization"] TIMESTAMP["Event Timestamping"] end subgraph subGraph2["Target Platforms"] LINUX["x86_64-unknown-linux-gnu"] BARE_METAL["x86_64-unknown-none"] EMBEDDED["Embedded x86_64"] end subgraph subGraph1["x86_rtc Crate"] X86_RTC["x86_rtc v0.1.1"] RTC_API["RTC Hardware Interface"] end subgraph subGraph0["ArceOS Ecosystem"] ARCEOS["ArceOS Operating System"] DRIVERS["Hardware Drivers"] KERNEL["Kernel Components"] end ARCEOS --> DRIVERS BARE_METAL --> BOOT_TIME DRIVERS --> X86_RTC EMBEDDED --> TIMESTAMP KERNEL --> X86_RTC LINUX --> SYSTEM_TIME RTC_API --> BARE_METAL RTC_API --> EMBEDDED RTC_API --> LINUX X86_RTC --> RTC_API
Sources: Cargo.toml(L8 - L12) Cargo.toml(L11 - L12)
The crate provides essential time-keeping functionality for systems that need direct hardware access to the RTC, particularly in contexts where higher-level operating system time services are unavailable or insufficient. Its design prioritizes minimal dependencies, hardware-specific optimization, and compatibility with both hosted and bare-metal environments.
Crate Definition and Metadata
Relevant source files
This page provides a detailed examination of the x86_rtc
crate's configuration as defined in Cargo.toml
. It covers package metadata, dependency specifications, build configurations, and platform-specific settings that define the crate's behavior and integration requirements.
For implementation details of the RTC driver functionality, see RTC Driver API. For broader dependency analysis including transitive dependencies, see Dependency Analysis.
Package Identification and Core Metadata
The crate is defined with specific metadata that establishes its identity and purpose within the Rust ecosystem.
Field | Value | Purpose |
---|---|---|
name | "x86_rtc" | Unique crate identifier |
version | "0.1.1" | Semantic version following SemVer |
edition | "2021" | Rust language edition |
authors | ["Keyang Hu keyang.hu@qq.com"] | Primary maintainer contact |
Crate Metadata Structure
flowchart TD subgraph External_Links["External_Links"] HOMEPAGE["homepage: github.com/arceos-org/arceos"] REPOSITORY["repository: github.com/arceos-org/x86_rtc"] DOCUMENTATION["documentation: docs.rs/x86_rtc"] end subgraph Legal_Metadata["Legal_Metadata"] LICENSE["license: GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"] end subgraph Description_Fields["Description_Fields"] DESC["description: System RTC Drivers for x86_64"] KEYWORDS["keywords: [arceos, x86_64, rtc]"] CATEGORIES["categories: [os, hardware-support, no-std]"] end subgraph Package_Definition["Package_Definition"] NAME["name: x86_rtc"] VERSION["version: 0.1.1"] EDITION["edition: 2021"] AUTHOR["authors: Keyang Hu"] end AUTHOR --> LICENSE DESC --> HOMEPAGE NAME --> DESC VERSION --> KEYWORDS
Sources: Cargo.toml(L1 - L12)
Functional Classification and Discovery
The crate uses specific classification metadata to enable discovery and communicate its intended use cases.
The description
field Cargo.toml(L6) explicitly identifies this as "System Real Time Clock (RTC) Drivers for x86_64 based on CMOS", establishing both the hardware target (x86_64
) and the underlying technology (CMOS
).
The keywords
array Cargo.toml(L11) includes:
"arceos"
- Associates with the ArceOS operating system project"x86_64"
- Specifies the target architecture"rtc"
- Identifies the Real Time Clock functionality
The categories
array Cargo.toml(L12) places the crate in:
"os"
- Operating system development"hardware-support"
- Hardware abstraction and drivers"no-std"
- Embedded and kernel development compatibility
Sources: Cargo.toml(L6) Cargo.toml(L11 - L12)
Dependency Architecture and Conditional Compilation
The crate implements a two-tier dependency strategy with conditional compilation for platform-specific functionality.
Dependency Configuration Structure
flowchart TD subgraph Build_Matrix["Build_Matrix"] LINUX_TARGET["x86_64-unknown-linux-gnu"] BARE_TARGET["x86_64-unknown-none"] OTHER_ARCH["Other Architectures"] end subgraph Dependency_Purposes["Dependency_Purposes"] CFG_PURPOSE["Conditional Compilation Utilities"] X86_PURPOSE["Hardware Register Access"] end subgraph Platform_Conditional["Platform_Conditional"] TARGET_CONDITION["cfg(target_arch = x86_64)"] X86_64_DEP["x86_64 = 0.15"] end subgraph Unconditional_Dependencies["Unconditional_Dependencies"] CFG_IF["cfg-if = 1.0"] end CFG_IF --> CFG_PURPOSE TARGET_CONDITION --> OTHER_ARCH TARGET_CONDITION --> X86_64_DEP X86_64_DEP --> BARE_TARGET X86_64_DEP --> LINUX_TARGET X86_64_DEP --> X86_PURPOSE
Core Dependencies
cfg-if v1.0 Cargo.toml(L15)
- Provides conditional compilation utilities
- Included unconditionally across all platforms
- Enables clean platform-specific code organization
Platform-Specific Dependencies
x86_64 v0.15 Cargo.toml(L17 - L18)
- Only included when
target_arch = "x86_64"
- Provides low-level hardware register access
- Essential for CMOS port I/O operations
- Version constraint allows compatible updates within 0.15.x
Sources: Cargo.toml(L14 - L18)
Licensing and Legal Framework
The crate implements a triple-license strategy providing maximum compatibility across different use cases and legal requirements.
The license specification Cargo.toml(L7) uses the SPDX format: "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"
This allows users to choose from:
- GPL-3.0-or-later: Copyleft license for open-source projects
- Apache-2.0: Permissive license for commercial integration
- MulanPSL-2.0: Chinese-origin permissive license for regional compliance
Sources: Cargo.toml(L7)
Repository and Documentation Infrastructure
The crate establishes a distributed documentation and development infrastructure across multiple platforms.
Link Type | URL | Purpose |
---|---|---|
homepage | https://github.com/arceos-org/arceos | Parent project (ArceOS) |
repository | https://github.com/arceos-org/x86_rtc | Source code and issues |
documentation | https://docs.rs/x86_rtc | API documentation |
The separation between homepage
and repository
Cargo.toml(L8 - L9) indicates this crate is part of the larger ArceOS ecosystem while maintaining its own development repository.
Sources: Cargo.toml(L8 - L10)
Build Configuration and Code Quality
The crate defines specific linting rules to customize Clippy behavior for its use case.
Clippy Configuration Cargo.toml(L20 - L21)
[lints.clippy]
new_without_default = "allow"
This configuration allows the new_without_default
lint, permitting constructor functions named new()
without requiring a corresponding Default
implementation. This is appropriate for hardware drivers where default initialization may not be meaningful or safe.
Sources: Cargo.toml(L20 - L21)
Crate Architecture Summary
Complete Crate Definition Flow
flowchart TD subgraph Ecosystem_Integration["Ecosystem_Integration"] ARCEOS_PROJECT["ArceOS Operating System"] DOCS_RS["docs.rs Documentation"] CARGO_REGISTRY["crates.io Registry"] end subgraph Functionality["Functionality"] RTC_DRIVER["RTC Driver Implementation"] CMOS_ACCESS["CMOS Hardware Interface"] end subgraph Platform_Support["Platform_Support"] ARCH_CHECK["cfg(target_arch = x86_64)"] X86_ONLY["x86_64 hardware required"] end subgraph Identity["Identity"] CRATE_NAME["x86_rtc"] VERSION_NUM["v0.1.1"] end ARCEOS_PROJECT --> CARGO_REGISTRY ARCH_CHECK --> X86_ONLY CMOS_ACCESS --> DOCS_RS CRATE_NAME --> ARCH_CHECK RTC_DRIVER --> ARCEOS_PROJECT VERSION_NUM --> RTC_DRIVER X86_ONLY --> CMOS_ACCESS
The Cargo.toml
configuration establishes x86_rtc
as a specialized hardware driver crate with strict platform requirements, flexible licensing, and integration into the ArceOS ecosystem. The conditional dependency structure ensures the crate only pulls in hardware-specific dependencies when building for compatible targets.
Sources: Cargo.toml(L1 - L22)
Quick Start Guide
Relevant source files
This page provides essential information for getting started with the x86_rtc
crate, including basic usage patterns and immediate setup requirements. It covers the fundamental API calls needed to read and write system time via the x86_64 CMOS Real Time Clock interface.
For detailed information about the crate's configuration and dependencies, see Crate Definition and Metadata. For comprehensive API documentation, see RTC Driver API. For platform-specific requirements and architecture details, see Platform and Architecture Requirements.
Prerequisites
The x86_rtc
crate requires an x86_64 target architecture and operates at a low level that typically requires kernel or bare-metal environments. The crate is no_std
compatible and designed for system-level programming.
Platform Requirements
Requirement | Details |
---|---|
Architecture | x86_64 only |
Environment | Bare metal, kernel, or privileged user space |
Rust Edition | 2021 |
no_stdSupport | Yes |
Sources: src/lib.rs(L8) src/lib.rs(L196 - L226)
Basic Usage Pattern
The core workflow involves three main steps: instantiation, reading time, and optionally setting time.
Basic Time Reading Flow
flowchart TD USER["User Code"] NEW["Rtc::new()"] INSTANCE["Rtc struct instance"] GET["get_unix_timestamp()"] CMOS_READ["read_cmos_register()"] HARDWARE["CMOS Hardware"] TIMESTAMP["Unix timestamp (u64)"] CMOS_READ --> HARDWARE GET --> CMOS_READ GET --> TIMESTAMP INSTANCE --> GET NEW --> INSTANCE USER --> NEW
Sources: src/lib.rs(L96 - L101) src/lib.rs(L103 - L129) src/lib.rs(L206 - L211)
API Method Mapping
flowchart TD subgraph subGraph2["Hardware Layer"] COMMAND_PORT["CMOS_COMMAND_PORT (0x70)"] DATA_PORT["CMOS_DATA_PORT (0x71)"] TIME_REGISTERS["Time/Date Registers"] end subgraph subGraph1["Internal Implementation"] READ_REGISTER["read_cmos_register()"] WRITE_REGISTER["write_cmos_register()"] READ_VALUES["read_all_values()"] STATUS_CHECK["CMOS_STATUS_REGISTER_A"] FORMAT_DETECT["CMOS_STATUS_REGISTER_B"] end subgraph subGraph0["Public API"] NEW_METHOD["Rtc::new()"] GET_METHOD["get_unix_timestamp()"] SET_METHOD["set_unix_timestamp()"] end COMMAND_PORT --> TIME_REGISTERS DATA_PORT --> TIME_REGISTERS GET_METHOD --> READ_VALUES GET_METHOD --> STATUS_CHECK NEW_METHOD --> FORMAT_DETECT READ_REGISTER --> COMMAND_PORT READ_REGISTER --> DATA_PORT READ_VALUES --> READ_REGISTER SET_METHOD --> WRITE_REGISTER WRITE_REGISTER --> COMMAND_PORT WRITE_REGISTER --> DATA_PORT
Sources: src/lib.rs(L96 - L101) src/lib.rs(L103 - L129) src/lib.rs(L131 - L194) src/lib.rs(L199 - L201) src/lib.rs(L206 - L218)
Essential Usage Examples
Reading Current Time
The most basic operation is reading the current Unix timestamp:
use x86_rtc::Rtc;
let rtc = Rtc::new();
let current_time = rtc.get_unix_timestamp();
The Rtc::new()
method automatically detects the CMOS format configuration by reading CMOS_STATUS_REGISTER_B
, while get_unix_timestamp()
returns seconds since the Unix epoch (January 1, 1970).
Sources: src/lib.rs(L96 - L101) src/lib.rs(L103 - L129) README.md(L9 - L12)
Setting System Time
To update the hardware clock with a new Unix timestamp:
use x86_rtc::Rtc;
let rtc = Rtc::new();
let new_time = 1672531200; // Example: January 1, 2023
rtc.set_unix_timestamp(new_time);
The set_unix_timestamp()
method handles conversion from Unix time to the CMOS register format, including BCD conversion and format handling.
Sources: src/lib.rs(L131 - L194)
Critical Implementation Details
Interrupt Handling Requirement
The get_unix_timestamp()
method includes an important synchronization mechanism to ensure consistent reads:
Concern | Implementation | Register Used |
---|---|---|
Update in Progress | Wait for completion | CMOS_STATUS_REGISTER_A |
Consistency Check | Double-read verification | Multiple register reads |
Atomic Operation | Loop until consistent | Status flag monitoring |
The method comment explicitly states: "The call to this RTC method requires the interrupt to be disabled, otherwise the value read may be inaccurate."
Sources: src/lib.rs(L105) src/lib.rs(L107 - L129) src/lib.rs(L16) src/lib.rs(L19)
Hardware Format Detection
The Rtc
struct automatically adapts to different hardware configurations:
flowchart TD INIT["Rtc::new()"] READ_STATUS["Read CMOS_STATUS_REGISTER_B"] CHECK_24H["Check CMOS_24_HOUR_FORMAT_FLAG"] CHECK_BIN["Check CMOS_BINARY_FORMAT_FLAG"] FORMAT_24H["24-hour format detection"] FORMAT_BIN["Binary vs BCD format"] ADAPT["Automatic format adaptation"] CHECK_24H --> FORMAT_24H CHECK_BIN --> FORMAT_BIN FORMAT_24H --> ADAPT FORMAT_BIN --> ADAPT INIT --> READ_STATUS READ_STATUS --> CHECK_24H READ_STATUS --> CHECK_BIN
Sources: src/lib.rs(L98 - L100) src/lib.rs(L17) src/lib.rs(L20 - L21) src/lib.rs(L30 - L36)
Platform-Specific Behavior
The crate uses conditional compilation to handle different target architectures:
Target Architecture | Behavior | Implementation |
---|---|---|
x86/x86_64 | Full hardware access | Usesx86_64::instructions::port::Port |
Other architectures | Stub implementation | Returns zero/no-op |
The actual hardware interaction occurs through I/O port operations on CMOS_COMMAND_PORT
(0x70) and CMOS_DATA_PORT
(0x71).
Sources: src/lib.rs(L196 - L226) src/lib.rs(L198 - L201) src/lib.rs(L203 - L204)
Common Integration Patterns
Embedded/Bare Metal Usage
In no_std
environments, the crate integrates directly without additional dependencies:
#![no_std]
use x86_rtc::Rtc;
// Typical usage in kernel or embedded context
let rtc = Rtc::new();
let boot_time = rtc.get_unix_timestamp();
Error Handling Considerations
The current API does not return Result
types, operating under the assumption of successful hardware access. Error conditions are handled internally through retry loops and format detection.
Sources: src/lib.rs(L8) src/lib.rs(L107 - L129)
Next Steps
- For detailed API documentation and method signatures, see RTC Driver API
- For understanding the underlying CMOS protocol and hardware interface, see CMOS Hardware Interface
- For information about BCD conversion and time format handling, see Data Format Handling
- For development environment setup and testing, see Development Environment Setup
Sources: src/lib.rs(L1 - L277) README.md(L1 - L13)
Implementation
Relevant source files
This document provides a comprehensive overview of the x86_rtc crate's implementation architecture, covering the core RTC driver functionality, hardware abstraction mechanisms, and the interaction between different system components. For detailed API documentation, see RTC Driver API. For low-level hardware interface specifics, see CMOS Hardware Interface. For data format conversion details, see Data Format Handling.
Implementation Architecture
The x86_rtc implementation follows a layered architecture that abstracts hardware complexity while maintaining performance and safety. The core implementation centers around the Rtc
struct, which encapsulates CMOS format configuration and provides high-level time operations.
Core Implementation Flow
flowchart TD subgraph subGraph3["Low-Level Hardware"] COMMAND_PORT["COMMAND_PORT (0x70)"] DATA_PORT["DATA_PORT (0x71)"] CMOS_REGS["CMOS Registers"] end subgraph subGraph2["Hardware Abstraction"] READ_REG["read_cmos_register()"] WRITE_REG["write_cmos_register()"] CHECK_FORMAT["is_24_hour_format()is_binary_format()"] end subgraph subGraph1["Implementation Logic"] INIT["Read CMOS_STATUS_REGISTER_B"] SYNC["Synchronization Loop"] READ_ALL["read_all_values()"] CONVERT["Format Conversion"] CALC["Date/Time Calculations"] end subgraph subGraph0["Public API Layer"] NEW["Rtc::new()"] GET["get_unix_timestamp()"] SET["set_unix_timestamp()"] end CALC --> WRITE_REG CHECK_FORMAT --> READ_REG COMMAND_PORT --> CMOS_REGS CONVERT --> CALC DATA_PORT --> CMOS_REGS GET --> SYNC INIT --> READ_REG NEW --> INIT READ_ALL --> CHECK_FORMAT READ_REG --> COMMAND_PORT READ_REG --> DATA_PORT SET --> CONVERT SYNC --> READ_ALL WRITE_REG --> COMMAND_PORT WRITE_REG --> DATA_PORT
Sources: src/lib.rs(L24 - L194)
The implementation follows a clear separation of concerns where each layer handles specific responsibilities. The public API provides Unix timestamp operations, the implementation logic handles format detection and conversion, and the hardware abstraction layer manages safe access to CMOS registers.
Rtc Structure and State Management
The Rtc
struct maintains minimal state to optimize performance while ensuring thread safety through stateless operations where possible.
flowchart TD subgraph subGraph2["CMOS Format Flags"] FLAG_24H["CMOS_24_HOUR_FORMAT_FLAG (1<<1)"] FLAG_BIN["CMOS_BINARY_FORMAT_FLAG (1<<2)"] end subgraph subGraph1["Format Detection Methods"] IS_24H["is_24_hour_format()"] IS_BIN["is_binary_format()"] end subgraph subGraph0["Rtc State"] STRUCT["Rtc { cmos_format: u8 }"] end IS_24H --> FLAG_24H IS_BIN --> FLAG_BIN STRUCT --> IS_24H STRUCT --> IS_BIN
Sources: src/lib.rs(L24 - L36)
The cmos_format
field stores the value from CMOS_STATUS_REGISTER_B
during initialization, allowing efficient format checking without repeated hardware access.
Synchronization and Consistency Mechanisms
The implementation employs sophisticated synchronization to handle CMOS update cycles and ensure data consistency.
flowchart TD subgraph subGraph0["get_unix_timestamp() Flow"] START["Start"] CHECK_UPDATE["Check CMOS_UPDATE_IN_PROGRESS_FLAG"] SPIN_WAIT["spin_loop() wait"] READ_1["read_all_values() -> seconds_1"] CHECK_UPDATE_2["Check update flag again"] READ_2["read_all_values() -> seconds_2"] COMPARE["seconds_1 == seconds_2?"] RETURN["Return consistent value"] end CHECK_UPDATE --> CHECK_UPDATE CHECK_UPDATE --> READ_1 CHECK_UPDATE --> READ_2 CHECK_UPDATE --> SPIN_WAIT COMPARE --> CHECK_UPDATE COMPARE --> RETURN SPIN_WAIT --> CHECK_UPDATE START --> CHECK_UPDATE
Sources: src/lib.rs(L106 - L129)
This double-read verification pattern ensures that time values remain consistent even during CMOS hardware updates, which occur approximately once per second.
Hardware Interface Abstraction
The implementation uses conditional compilation to provide platform-specific hardware access while maintaining a consistent interface.
flowchart TD subgraph subGraph2["Fallback Implementation"] READ_STUB["read_cmos_register() -> 0"] WRITE_STUB["write_cmos_register() no-op"] end subgraph subGraph1["x86/x86_64 Implementation"] PORT_DEF["Port definitions"] COMMAND_PORT_IMPL["COMMAND_PORT: Port"] DATA_PORT_IMPL["DATA_PORT: Port"] READ_IMPL["read_cmos_register()"] WRITE_IMPL["write_cmos_register()"] end subgraph subGraph0["Platform Detection"] CFG_IF["cfg_if! macro"] X86_CHECK["target_arch x86/x86_64"] OTHER_ARCH["other architectures"] end CFG_IF --> OTHER_ARCH CFG_IF --> X86_CHECK OTHER_ARCH --> READ_STUB OTHER_ARCH --> WRITE_STUB PORT_DEF --> COMMAND_PORT_IMPL PORT_DEF --> DATA_PORT_IMPL X86_CHECK --> PORT_DEF X86_CHECK --> READ_IMPL X86_CHECK --> WRITE_IMPL
Sources: src/lib.rs(L196 - L226)
The conditional compilation ensures that the crate can be built on non-x86 platforms for testing purposes while providing no-op implementations for hardware functions.
Register Access Pattern
The CMOS register access follows a strict two-step protocol for all operations.
Operation | Step 1: Command Port | Step 2: Data Port |
---|---|---|
Read | Write register address + NMI disable | Read value |
Write | Write register address + NMI disable | Write value |
The CMOS_DISABLE_NMI
flag (bit 7) is always set to prevent Non-Maskable Interrupts during CMOS access, ensuring atomic operations.
Sources: src/lib.rs(L201 - L218)
Data Conversion Pipeline
The implementation handles multiple data format conversions in a structured pipeline:
flowchart TD subgraph subGraph0["Read Pipeline"] BCD_CONV["convert_bcd_value()"] subgraph subGraph1["Write Pipeline"] UNIX_TIME["Unix Timestamp"] DATE_CALC["Date Calculation"] FORMAT_CONV["Binary to BCD"] HOUR_FORMAT["Hour Format Handling"] CMOS_WRITE["CMOS Register Write"] RAW_READ["Raw CMOS Value"] FORMAT_CHECK["Binary vs BCD Check"] HOUR_LOGIC["12/24 Hour Conversion"] FINAL_VALUE["Final Binary Value"] end end BCD_CONV --> HOUR_LOGIC DATE_CALC --> FORMAT_CONV FORMAT_CHECK --> BCD_CONV FORMAT_CHECK --> HOUR_LOGIC FORMAT_CONV --> HOUR_FORMAT HOUR_FORMAT --> CMOS_WRITE HOUR_LOGIC --> FINAL_VALUE RAW_READ --> FORMAT_CHECK UNIX_TIME --> DATE_CALC
Sources: src/lib.rs(L38 - L48) src/lib.rs(L171 - L185)
Error Handling Strategy
The implementation prioritizes data consistency over error reporting, using several defensive programming techniques:
- Spin-wait loops for hardware synchronization rather than timeouts
- Double-read verification to detect inconsistent states
- Format detection caching to avoid repeated hardware queries
- Const functions where possible to enable compile-time optimization
The absence of explicit error types reflects the design philosophy that hardware RTC operations should either succeed or retry, as partial failures are generally not recoverable at the application level.
Sources: src/lib.rs(L107 - L128)
Calendar Arithmetic Implementation
The date conversion logic implements efficient calendar arithmetic optimized for the Unix epoch:
flowchart TD subgraph subGraph0["Unix Timestamp to Date"] UNIX_IN["Unix Timestamp Input"] TIME_CALC["Time Calculation (t % 86400)"] MONTH_LOOP["Month Iteration Loop"] DAYS_IN_MONTH["days_in_month() Check"] subgraph subGraph1["Date to Unix Timestamp"] DATE_IN["Date/Time Input"] EPOCH_CALC["Days Since Epoch"] MKTIME["seconds_from_date()"] UNIX_OUT["Unix Timestamp Output"] DAYS_CALC["Day Calculation (t / 86400)"] YEAR_LOOP["Year Iteration Loop"] LEAP_CHECK["is_leap_year() Check"] end end DATE_IN --> EPOCH_CALC DAYS_CALC --> YEAR_LOOP EPOCH_CALC --> MKTIME MKTIME --> UNIX_OUT MONTH_LOOP --> DAYS_IN_MONTH UNIX_IN --> DAYS_CALC UNIX_IN --> TIME_CALC YEAR_LOOP --> LEAP_CHECK YEAR_LOOP --> MONTH_LOOP
Sources: src/lib.rs(L147 - L166) src/lib.rs(L264 - L276) src/lib.rs(L228 - L245)
The implementation uses const functions for calendar utilities to enable compile-time optimization and follows algorithms similar to the Linux kernel's mktime64()
function for compatibility and reliability.
RTC Driver API
Relevant source files
This document covers the public interface of the x86_rtc crate's RTC driver, including the Rtc
struct and its methods for reading and setting system time via CMOS hardware. This page focuses on the high-level API design and usage patterns. For information about the underlying CMOS hardware protocol and register operations, see CMOS Hardware Interface. For details about data format conversions and timestamp calculations, see Data Format Handling.
API Structure Overview
The RTC driver provides a simple, safe interface to the x86_64 Real Time Clock through a single primary struct and three core methods.
Core API Components
flowchart TD subgraph subGraph2["Hardware Interface"] CMOS_REGS["CMOS RegistersSTATUS_REGISTER_B"] UNIX_TIME["Unix Timestampu64 seconds"] end subgraph subGraph1["Internal State"] CMOS_FORMAT["cmos_format: u8(line 26)"] end subgraph subGraph0["Public API Surface"] RTC_STRUCT["Rtc struct(lines 24-27)"] NEW_METHOD["new() -> Self(lines 96-101)"] GET_METHOD["get_unix_timestamp() -> u64(lines 103-129)"] SET_METHOD["set_unix_timestamp(u64)(lines 131-194)"] end GET_METHOD --> UNIX_TIME NEW_METHOD --> CMOS_REGS RTC_STRUCT --> CMOS_FORMAT RTC_STRUCT --> GET_METHOD RTC_STRUCT --> NEW_METHOD RTC_STRUCT --> SET_METHOD SET_METHOD --> UNIX_TIME
Sources: src/lib.rs(L24 - L194)
Rtc Struct
The Rtc
struct serves as the main entry point for all RTC operations. It maintains internal state about the CMOS hardware configuration.
Field | Type | Purpose |
---|---|---|
cmos_format | u8 | Cached CMOS Status Register B value containing format flags |
The struct stores hardware format information to avoid repeatedly querying CMOS registers during time operations. This includes whether the hardware uses 24-hour format and binary vs BCD encoding.
Sources: src/lib.rs(L24 - L27)
Constructor Method
Rtc::new() -> Self
Creates a new Rtc
instance by reading the current CMOS hardware configuration.
flowchart TD CALL["Rtc::new()"] READ_REG["read_cmos_register(CMOS_STATUS_REGISTER_B)"] STORE_FORMAT["Store format flags in cmos_format field"] RETURN_INSTANCE["Return Rtc instance"] CALL --> READ_REG READ_REG --> STORE_FORMAT STORE_FORMAT --> RETURN_INSTANCE
The constructor performs a single CMOS register read to determine hardware format configuration, which is then cached for the lifetime of the Rtc
instance. This eliminates the need to re-read format flags on every time operation.
Sources: src/lib.rs(L96 - L101)
Time Reading Method
get_unix_timestamp(&self) -> u64
Returns the current time as seconds since Unix epoch (January 1, 1970 00:00:00 UTC).
flowchart TD START["get_unix_timestamp() called"] WAIT_LOOP["Wait for update completion(CMOS_STATUS_REGISTER_A check)"] READ_TIME1["Read all CMOS time registers(read_all_values)"] CHECK_UPDATE["Check if update started(CMOS_UPDATE_IN_PROGRESS_FLAG)"] READ_TIME2["Read time again for verification"] COMPARE["Compare readings"] RETURN["Return consistent timestamp"] RETRY["Continue loop"] CHECK_UPDATE --> READ_TIME2 CHECK_UPDATE --> RETRY COMPARE --> RETRY COMPARE --> RETURN READ_TIME1 --> CHECK_UPDATE READ_TIME2 --> COMPARE RETRY --> WAIT_LOOP START --> WAIT_LOOP WAIT_LOOP --> READ_TIME1
The method implements a sophisticated synchronization protocol to ensure consistent readings despite hardware timing constraints. It uses a double-read verification pattern to detect and handle concurrent CMOS updates.
Implementation Note: The method requires interrupts to be disabled by the caller to ensure accurate timing, as documented in the method's comment at src/lib.rs(L105)
Sources: src/lib.rs(L103 - L129)
Time Setting Method
set_unix_timestamp(&self, unix_time: u64)
Sets the RTC to the specified Unix timestamp.
flowchart TD INPUT["Unix timestamp (u64)"] CONVERT_SECS["Convert to u32 seconds"] CALC_COMPONENTS["Calculate time components(hour, minute, second)"] CALC_DATE["Calculate date components(year, month, day)"] FORMAT_CHECK["Binary format?(is_binary_format)"] CONVERT_BCD["Convert values to BCD format"] HANDLE_HOUR["Special hour format handling(12/24 hour conversion)"] WRITE_REGS["Write to CMOS registers(SECOND, MINUTE, HOUR, DAY, MONTH, YEAR)"] CALC_COMPONENTS --> CALC_DATE CALC_DATE --> FORMAT_CHECK CONVERT_BCD --> HANDLE_HOUR CONVERT_SECS --> CALC_COMPONENTS FORMAT_CHECK --> CONVERT_BCD FORMAT_CHECK --> HANDLE_HOUR HANDLE_HOUR --> WRITE_REGS INPUT --> CONVERT_SECS
The method performs comprehensive date/time calculation including leap year handling and month-specific day counts. It respects the hardware's configured data format, converting between binary and BCD as needed.
Sources: src/lib.rs(L131 - L194)
API Usage Patterns
Basic Time Reading
use x86_rtc::Rtc;
let rtc = Rtc::new();
let current_time = rtc.get_unix_timestamp();
Time Setting
use x86_rtc::Rtc;
let rtc = Rtc::new();
rtc.set_unix_timestamp(1640995200); // Set to specific Unix timestamp
Thread Safety Considerations
The API is designed for single-threaded use in kernel or embedded contexts. Multiple concurrent accesses to the same Rtc
instance require external synchronization, particularly for the get_unix_timestamp()
method which requires interrupt-free execution.
Sources: README.md(L9 - L12) src/lib.rs(L105)
Error Handling
The API uses a panic-free design with no explicit error return types. Invalid operations or hardware communication failures result in:
- Reading operations return potentially incorrect but valid
u64
values - Writing operations complete without indication of success/failure
- Hardware communication errors are handled at the lower CMOS interface level
This design reflects the embedded/kernel context where panic-based error handling is preferred over Result types.
Sources: src/lib.rs(L103 - L194)
CMOS Hardware Interface
Relevant source files
This document explains the low-level hardware interface for accessing the Real Time Clock (RTC) through CMOS registers on x86_64 systems. It covers the I/O port protocol, register mapping, hardware synchronization, and platform-specific implementation details.
For high-level RTC API usage, see RTC Driver API. For data format conversion details, see Data Format Handling.
CMOS Register Map
The CMOS RTC uses a well-defined register layout accessible through I/O ports. The implementation defines specific register addresses and control flags for accessing time, date, and status information.
Time and Date Registers
flowchart TD subgraph subGraph3["Format Flags"] UIP["CMOS_UPDATE_IN_PROGRESS_FLAGBit 7"] H24["CMOS_24_HOUR_FORMAT_FLAGBit 1"] BIN["CMOS_BINARY_FORMAT_FLAGBit 2"] PM["CMOS_12_HOUR_PM_FLAG0x80"] end subgraph subGraph2["Status Registers"] DAY["CMOS_DAY_REGISTER0x07"] MONTH["CMOS_MONTH_REGISTER0x08"] YEAR["CMOS_YEAR_REGISTER0x09"] STATA["CMOS_STATUS_REGISTER_A0x0AUpdate Progress"] STATB["CMOS_STATUS_REGISTER_B0x0BFormat Control"] end subgraph subGraph0["Time Registers"] SEC["CMOS_SECOND_REGISTER0x00"] MIN["CMOS_MINUTE_REGISTER0x02"] HOUR["CMOS_HOUR_REGISTER0x04"] end subgraph subGraph1["Date Registers"] DAY["CMOS_DAY_REGISTER0x07"] MONTH["CMOS_MONTH_REGISTER0x08"] YEAR["CMOS_YEAR_REGISTER0x09"] STATA["CMOS_STATUS_REGISTER_A0x0AUpdate Progress"] end HOUR --> PM STATA --> UIP STATB --> BIN STATB --> H24
Sources: src/lib.rs(L10 - L23)
Register Access Pattern
Register | Address | Purpose | Format Dependencies |
---|---|---|---|
CMOS_SECOND_REGISTER | 0x00 | Current second (0-59) | BCD/Binary |
CMOS_MINUTE_REGISTER | 0x02 | Current minute (0-59) | BCD/Binary |
CMOS_HOUR_REGISTER | 0x04 | Current hour | BCD/Binary + 12/24-hour |
CMOS_DAY_REGISTER | 0x07 | Day of month (1-31) | BCD/Binary |
CMOS_MONTH_REGISTER | 0x08 | Month (1-12) | BCD/Binary |
CMOS_YEAR_REGISTER | 0x09 | Year (0-99, + 2000) | BCD/Binary |
CMOS_STATUS_REGISTER_A | 0x0A | Update status | Raw binary |
CMOS_STATUS_REGISTER_B | 0x0B | Format configuration | Raw binary |
I/O Port Protocol
The CMOS interface uses a two-port protocol where register selection and data transfer are performed through separate I/O ports.
Port Configuration
flowchart TD subgraph subGraph2["Access Functions"] READ["read_cmos_register()"] WRITE["write_cmos_register()"] end subgraph subGraph1["Port Operations"] CMDPORT["Port<u8>COMMAND_PORT"] DATAPORT["Port<u8>DATA_PORT"] end subgraph subGraph0["I/O Port Interface"] CMD["CMOS_COMMAND_PORT0x70Register Selection"] DATA["CMOS_DATA_PORT0x71Data Transfer"] end CMD --> CMDPORT CMDPORT --> READ CMDPORT --> WRITE DATA --> DATAPORT DATAPORT --> READ DATAPORT --> WRITE
Sources: src/lib.rs(L198 - L204)
Hardware Communication Protocol
The CMOS access protocol follows a strict sequence to ensure reliable register access:
sequenceDiagram participant CPU as CPU participant COMMAND_PORT0x70 as "COMMAND_PORT (0x70)" participant DATA_PORT0x71 as "DATA_PORT (0x71)" participant CMOSChip as "CMOS Chip" Note over CPU: Read Operation CPU ->> COMMAND_PORT0x70: "CMOS_DISABLE_NMI | register" COMMAND_PORT0x70 ->> CMOSChip: "Select Register" CPU ->> DATA_PORT0x71: "read()" DATA_PORT0x71 ->> CMOSChip: "Read Request" CMOSChip ->> DATA_PORT0x71: "Register Value" DATA_PORT0x71 ->> CPU: "Return Value" Note over CPU: Write Operation CPU ->> COMMAND_PORT0x70: "CMOS_DISABLE_NMI | register" COMMAND_PORT0x70 ->> CMOSChip: "Select Register" CPU ->> DATA_PORT0x71: "write(value)" DATA_PORT0x71 ->> CMOSChip: "Write Value"
Sources: src/lib.rs(L206 - L218)
Hardware Access Implementation
Register Read Operation
The read_cmos_register
function implements the low-level hardware access protocol:
flowchart TD subgraph subGraph1["Hardware Control"] NMI["CMOS_DISABLE_NMIBit 7 = 1Prevents interrupts"] REGSEL["Register SelectionBits 0-6"] end subgraph subGraph0["read_cmos_register Function"] START["Start: register parameter"] SELECTREG["Write to COMMAND_PORTCMOS_DISABLE_NMI | register"] READDATA["Read from DATA_PORT"] RETURN["Return u8 value"] end READDATA --> RETURN SELECTREG --> NMI SELECTREG --> READDATA SELECTREG --> REGSEL START --> SELECTREG
Sources: src/lib.rs(L206 - L211)
Register Write Operation
The write_cmos_register
function handles data updates to CMOS registers:
flowchart TD subgraph subGraph1["Safety Considerations"] UNSAFE["unsafe blockRaw port access"] RAWPTR["raw mut pointerStatic port references"] end subgraph subGraph0["write_cmos_register Function"] START["Start: register, value parameters"] SELECTREG["Write to COMMAND_PORTCMOS_DISABLE_NMI | register"] WRITEDATA["Write value to DATA_PORT"] END["End"] end SELECTREG --> UNSAFE SELECTREG --> WRITEDATA START --> SELECTREG WRITEDATA --> END WRITEDATA --> RAWPTR
Sources: src/lib.rs(L213 - L218)
Status Register Management
Update Synchronization
The CMOS chip updates its registers autonomously, requiring careful synchronization to avoid reading inconsistent values:
flowchart TD subgraph subGraph0["Update Detection Flow"] CHECK1["Read CMOS_STATUS_REGISTER_A"] TESTFLAG1["Test CMOS_UPDATE_IN_PROGRESS_FLAG"] WAIT["spin_loop() while updating"] READ1["Read all time registers"] CHECK2["Read CMOS_STATUS_REGISTER_A again"] TESTFLAG2["Test update flag again"] READ2["Read all time registers again"] COMPARE["Compare both readings"] RETURN["Return consistent value"] end read2["read2"] CHECK1 --> TESTFLAG1 CHECK2 --> TESTFLAG2 COMPARE --> CHECK1 COMPARE --> RETURN READ1 --> CHECK2 TESTFLAG1 --> READ1 TESTFLAG1 --> WAIT TESTFLAG2 --> CHECK1 TESTFLAG2 --> READ2 WAIT --> CHECK1 read2 --> COMPARE
Sources: src/lib.rs(L107 - L129)
Format Detection
The implementation reads CMOS_STATUS_REGISTER_B
during initialization to determine data format:
Flag | Bit Position | Purpose | Impact |
---|---|---|---|
CMOS_24_HOUR_FORMAT_FLAG | 1 | Hour format detection | Affects hour register interpretation |
CMOS_BINARY_FORMAT_FLAG | 2 | Number format detection | Determines BCD vs binary conversion |
Sources: src/lib.rs(L30 - L36)
Platform Abstraction
Conditional Compilation
The implementation uses conditional compilation to provide platform-specific functionality:
flowchart TD subgraph subGraph2["Fallback Implementation"] STUBREAD["Stub read_cmos_register()Returns 0"] STUBWRITE["Stub write_cmos_register()No operation"] end subgraph subGraph1["x86/x86_64 Implementation"] REALPORTS["Real I/O Port Accessx86_64::instructions::port::Port"] REALREAD["Actual CMOS register reads"] REALWRITE["Actual CMOS register writes"] end subgraph subGraph0["Compilation Targets"] X86["x86/x86_64target_arch"] OTHER["Other Architectures"] end OTHER --> STUBREAD OTHER --> STUBWRITE REALPORTS --> REALREAD REALPORTS --> REALWRITE X86 --> REALPORTS
Sources: src/lib.rs(L196 - L226)
Safety Considerations
The hardware access requires unsafe
code due to direct I/O port manipulation:
- Raw pointer access: Static
Port<u8>
instances require raw mutable references - Interrupt safety:
CMOS_DISABLE_NMI
flag prevents hardware interrupts during access - Atomic operations: The two-port protocol ensures atomic register selection and data transfer
Sources: src/lib.rs(L207 - L217)
Data Format Handling
Relevant source files
This document covers the data format conversion and handling mechanisms within the x86_rtc crate. The RTC driver must handle multiple data representation formats used by CMOS hardware, including BCD/binary encoding, 12/24-hour time formats, and Unix timestamp conversions. This section focuses specifically on the format detection, conversion algorithms, and data consistency mechanisms.
For hardware register access details, see CMOS Hardware Interface. For the high-level API usage, see RTC Driver API.
Format Detection and Configuration
The RTC hardware can store time values in different formats, and the driver must detect and handle these variations dynamically. The format configuration is stored in the CMOS Status Register B and cached in the Rtc
struct.
flowchart TD INIT["Rtc::new()"] READ_STATUS["read_cmos_register(CMOS_STATUS_REGISTER_B)"] CACHE["cmos_format: u8"] CHECK_24H["is_24_hour_format()"] CHECK_BIN["is_binary_format()"] FLAG_24H["CMOS_24_HOUR_FORMAT_FLAG"] FLAG_BIN["CMOS_BINARY_FORMAT_FLAG"] DECISION_TIME["Time Format Decision"] DECISION_DATA["Data Format Decision"] FORMAT_12H["12-Hour Format"] FORMAT_24H["24-Hour Format"] FORMAT_BCD["BCD Format"] FORMAT_BINARY["Binary Format"] CACHE --> CHECK_24H CACHE --> CHECK_BIN CHECK_24H --> FLAG_24H CHECK_BIN --> FLAG_BIN DECISION_DATA --> FORMAT_BCD DECISION_DATA --> FORMAT_BINARY DECISION_TIME --> FORMAT_12H DECISION_TIME --> FORMAT_24H FLAG_24H --> DECISION_TIME FLAG_BIN --> DECISION_DATA INIT --> READ_STATUS READ_STATUS --> CACHE
The format detection methods use bitwise operations to check specific flags:
Method | Flag Constant | Bit Position | Purpose |
---|---|---|---|
is_24_hour_format() | CMOS_24_HOUR_FORMAT_FLAG | Bit 1 | Determines 12/24 hour format |
is_binary_format() | CMOS_BINARY_FORMAT_FLAG | Bit 2 | Determines BCD/binary encoding |
Sources: src/lib.rs(L30 - L36) src/lib.rs(L20 - L21) src/lib.rs(L97 - L101)
BCD and Binary Format Conversion
The CMOS chip can store datetime values in either Binary-Coded Decimal (BCD) or pure binary format. The driver provides bidirectional conversion functions to handle both representations.
flowchart TD subgraph subGraph0["BCD to Binary Conversion"] subgraph subGraph1["Binary to BCD Conversion"] BIN_INPUT["Binary Value (23)"] BIN_FUNC["convert_binary_value()"] BIN_TENS["Tens: binary / 10"] BIN_ONES["Ones: binary % 10"] BIN_CALC["((binary / 10) << 4) | (binary % 10)"] BIN_OUTPUT["BCD Value (0x23)"] BCD_INPUT["BCD Value (0x23)"] BCD_FUNC["convert_bcd_value()"] BCD_EXTRACT["Extract Tens: (0xF0) >> 4"] BCD_ONES["Extract Ones: (0x0F)"] BCD_CALC["((bcd & 0xF0) >> 1) + ((bcd & 0xF0) >> 3) + (bcd & 0x0f)"] BCD_OUTPUT["Binary Value (23)"] end end BCD_CALC --> BCD_OUTPUT BCD_EXTRACT --> BCD_CALC BCD_FUNC --> BCD_EXTRACT BCD_FUNC --> BCD_ONES BCD_INPUT --> BCD_FUNC BCD_ONES --> BCD_CALC BIN_CALC --> BIN_OUTPUT BIN_FUNC --> BIN_ONES BIN_FUNC --> BIN_TENS BIN_INPUT --> BIN_FUNC BIN_ONES --> BIN_CALC BIN_TENS --> BIN_CALC
The read_datetime_register()
method automatically applies the appropriate conversion based on the detected format:
flowchart TD READ_REG["read_datetime_register(register)"] READ_CMOS["read_cmos_register(register)"] CHECK_FORMAT["is_binary_format()"] RETURN_DIRECT["Return value directly"] CONVERT_BCD["convert_bcd_value(value)"] OUTPUT["Final Value"] CHECK_FORMAT --> CONVERT_BCD CHECK_FORMAT --> RETURN_DIRECT CONVERT_BCD --> OUTPUT READ_CMOS --> CHECK_FORMAT READ_REG --> READ_CMOS RETURN_DIRECT --> OUTPUT
Sources: src/lib.rs(L38 - L48) src/lib.rs(L253 - L260) src/lib.rs(L171 - L177)
Hour Format Handling
Hour values require the most complex format handling due to the combination of BCD/binary encoding with 12/24-hour format variations and PM flag management.
flowchart TD READ_HOUR["read_cmos_register(CMOS_HOUR_REGISTER)"] CHECK_12H["is_24_hour_format()"] EXTRACT_PM["time_is_pm(hour)"] SKIP_PM["Skip PM handling"] MASK_PM["hour &= !CMOS_12_HOUR_PM_FLAG"] CHECK_BCD_12H["is_binary_format()"] CHECK_BCD_24H["is_binary_format()"] CONVERT_BCD_12H["convert_bcd_value(hour)"] HOUR_READY_12H["hour ready for 12h conversion"] CONVERT_BCD_24H["convert_bcd_value(hour)"] HOUR_FINAL["Final 24h hour"] CONVERT_12_TO_24["Convert 12h to 24h"] CHECK_NOON["hour == 12"] SET_ZERO["hour = 0"] KEEP_HOUR["Keep hour value"] CHECK_PM_FINAL["is_pm"] ADD_12["hour += 12"] CHECK_12H --> EXTRACT_PM CHECK_12H --> SKIP_PM CHECK_BCD_12H --> CONVERT_BCD_12H CHECK_BCD_12H --> HOUR_READY_12H CHECK_BCD_24H --> CONVERT_BCD_24H CHECK_BCD_24H --> HOUR_FINAL CHECK_NOON --> KEEP_HOUR CHECK_NOON --> SET_ZERO CHECK_PM_FINAL --> ADD_12 CHECK_PM_FINAL --> HOUR_FINAL CONVERT_BCD_12H --> CONVERT_12_TO_24 CONVERT_BCD_24H --> HOUR_FINAL EXTRACT_PM --> MASK_PM HOUR_READY_12H --> CONVERT_12_TO_24 KEEP_HOUR --> CHECK_PM_FINAL MASK_PM --> CHECK_BCD_12H READ_HOUR --> CHECK_12H SET_ZERO --> CHECK_PM_FINAL SKIP_PM --> CHECK_BCD_24H
The conversion logic handles these specific cases:
Input (12h) | is_pm | Converted (24h) | Logic |
---|---|---|---|
12:xx AM | false | 00:xx | hour = 0 |
01:xx AM | false | 01:xx | hour unchanged |
12:xx PM | true | 12:xx | hour = 0 + 12 = 12 |
01:xx PM | true | 13:xx | hour = 1 + 12 = 13 |
Sources: src/lib.rs(L56 - L84) src/lib.rs(L247 - L249) src/lib.rs(L22)
Unix Timestamp Conversion
The driver provides bidirectional conversion between CMOS datetime values and Unix timestamps (seconds since January 1, 1970).
Reading: CMOS to Unix Timestamp
flowchart TD GET_TIMESTAMP["get_unix_timestamp()"] WAIT_STABLE["Wait for !UPDATE_IN_PROGRESS"] READ_ALL["read_all_values()"] READ_YEAR["read_datetime_register(CMOS_YEAR_REGISTER) + 2000"] READ_MONTH["read_datetime_register(CMOS_MONTH_REGISTER)"] READ_DAY["read_datetime_register(CMOS_DAY_REGISTER)"] PROCESS_HOUR["Complex hour processing"] READ_MINUTE["read_datetime_register(CMOS_MINUTE_REGISTER)"] READ_SECOND["read_datetime_register(CMOS_SECOND_REGISTER)"] CALC_UNIX["seconds_from_date()"] CHECK_CONSISTENT["Verify consistency"] RETURN_TIMESTAMP["Return Unix timestamp"] CALC_UNIX --> CHECK_CONSISTENT CHECK_CONSISTENT --> RETURN_TIMESTAMP CHECK_CONSISTENT --> WAIT_STABLE GET_TIMESTAMP --> WAIT_STABLE PROCESS_HOUR --> CALC_UNIX READ_ALL --> PROCESS_HOUR READ_ALL --> READ_DAY READ_ALL --> READ_MINUTE READ_ALL --> READ_MONTH READ_ALL --> READ_SECOND READ_ALL --> READ_YEAR READ_DAY --> CALC_UNIX READ_MINUTE --> CALC_UNIX READ_MONTH --> CALC_UNIX READ_SECOND --> CALC_UNIX READ_YEAR --> CALC_UNIX WAIT_STABLE --> READ_ALL
Writing: Unix Timestamp to CMOS
flowchart TD SET_TIMESTAMP["set_unix_timestamp(unix_time)"] CALC_TIME["Calculate hour, minute, second"] CALC_DATE["Calculate year, month, day"] EXTRACT_HOUR["hour = t / 3600"] EXTRACT_MIN["minute = (t % 3600) / 60"] EXTRACT_SEC["second = t % 60"] YEAR_LOOP["Subtract full years since 1970"] MONTH_LOOP["Subtract full months"] CALC_DAY["Remaining days + 1"] CHECK_BIN_FORMAT["is_binary_format()"] CONVERT_TO_BCD["convert_binary_value() for each"] WRITE_REGISTERS["Write to CMOS registers"] WRITE_SEC["write_cmos_register(CMOS_SECOND_REGISTER)"] WRITE_MIN["write_cmos_register(CMOS_MINUTE_REGISTER)"] WRITE_HOUR["write_cmos_register(CMOS_HOUR_REGISTER)"] WRITE_DAY["write_cmos_register(CMOS_DAY_REGISTER)"] WRITE_MONTH["write_cmos_register(CMOS_MONTH_REGISTER)"] WRITE_YEAR["write_cmos_register(CMOS_YEAR_REGISTER)"] CALC_DATE --> YEAR_LOOP CALC_DAY --> CHECK_BIN_FORMAT CALC_TIME --> EXTRACT_HOUR CALC_TIME --> EXTRACT_MIN CALC_TIME --> EXTRACT_SEC CHECK_BIN_FORMAT --> CONVERT_TO_BCD CHECK_BIN_FORMAT --> WRITE_REGISTERS CONVERT_TO_BCD --> WRITE_REGISTERS EXTRACT_HOUR --> CHECK_BIN_FORMAT EXTRACT_MIN --> CHECK_BIN_FORMAT EXTRACT_SEC --> CHECK_BIN_FORMAT MONTH_LOOP --> CALC_DAY SET_TIMESTAMP --> CALC_DATE SET_TIMESTAMP --> CALC_TIME WRITE_REGISTERS --> WRITE_DAY WRITE_REGISTERS --> WRITE_HOUR WRITE_REGISTERS --> WRITE_MIN WRITE_REGISTERS --> WRITE_MONTH WRITE_REGISTERS --> WRITE_SEC WRITE_REGISTERS --> WRITE_YEAR YEAR_LOOP --> MONTH_LOOP
Sources: src/lib.rs(L106 - L129) src/lib.rs(L132 - L193) src/lib.rs(L264 - L276)
Data Consistency and Synchronization
The CMOS hardware updates its registers periodically, which can cause inconsistent reads if accessed during an update cycle. The driver implements a consistency check mechanism.
flowchart TD START_READ["Start get_unix_timestamp()"] CHECK_UPDATE1["Read CMOS_STATUS_REGISTER_A"] UPDATE_FLAG1["Check CMOS_UPDATE_IN_PROGRESS_FLAG"] SPIN_WAIT["core::hint::spin_loop()"] READ_VALUES1["Read all datetime values"] CHECK_UPDATE2["Check update flag again"] READ_VALUES2["Read all datetime values again"] COMPARE["Compare both readings"] RETURN_CONSISTENT["Return consistent value"] CHECK_UPDATE1 --> UPDATE_FLAG1 CHECK_UPDATE2 --> READ_VALUES2 CHECK_UPDATE2 --> START_READ COMPARE --> RETURN_CONSISTENT COMPARE --> START_READ READ_VALUES1 --> CHECK_UPDATE2 READ_VALUES2 --> COMPARE SPIN_WAIT --> CHECK_UPDATE1 START_READ --> CHECK_UPDATE1 UPDATE_FLAG1 --> READ_VALUES1 UPDATE_FLAG1 --> SPIN_WAIT
This double-read mechanism ensures that:
- No update is in progress before reading
- No update occurred during reading
- Two consecutive reads produce identical results
Sources: src/lib.rs(L107 - L129) src/lib.rs(L19)
Calendar Arithmetic Functions
The driver includes helper functions for calendar calculations needed during timestamp conversion:
Function | Purpose | Key Logic |
---|---|---|
is_leap_year() | Leap year detection | (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) |
days_in_month() | Days per month | Handles leap year February (28/29 days) |
seconds_from_date() | Date to Unix timestamp | Uses optimized algorithm from Linux kernel |
The seconds_from_date()
function uses an optimized algorithm that adjusts the year and month to simplify leap year calculations by treating March as the start of the year.
Sources: src/lib.rs(L228 - L245) src/lib.rs(L264 - L276)
Dependencies and Platform Support
Relevant source files
This document provides a comprehensive analysis of the x86_rtc
crate's dependencies, platform requirements, and architecture-specific constraints. It covers both direct and transitive dependencies, explains the conditional compilation strategy, and documents the specific platform support matrix.
For detailed crate configuration and metadata information, see Crate Definition and Metadata. For implementation-specific dependency usage, see Implementation.
Dependency Architecture Overview
The x86_rtc
crate follows a minimal dependency strategy, with only two direct dependencies and a small transitive dependency tree focused on hardware abstraction and conditional compilation.
flowchart TD subgraph subGraph3["Conditional Compilation"] ARCH_GATE["target_arch = x86_64"] end subgraph subGraph2["Transitive Dependencies"] BIT_FIELD["bit_field v0.10.2"] BITFLAGS["bitflags v2.6.0"] RUSTVERSION["rustversion v1.0.19"] VOLATILE["volatile v0.4.6"] end subgraph subGraph1["Direct Dependencies"] CFG_IF["cfg-if v1.0"] X86_64["x86_64 v0.15"] end subgraph subGraph0["x86_rtc Crate"] ROOT["x86_rtc v0.1.1"] end ARCH_GATE --> X86_64 ROOT --> ARCH_GATE ROOT --> CFG_IF
Sources: Cargo.toml(L14 - L18) Cargo.lock(L48 - L53)
Direct Dependencies
cfg-if v1.0
The cfg-if
crate provides ergonomic conditional compilation macros, essential for platform-specific code paths in the RTC driver.
Attribute | Value |
---|---|
Version | 1.0.0 |
Purpose | Conditional compilation utilities |
Usage | Platform-specific code branching |
License Compatibility | MIT/Apache-2.0 |
Sources: Cargo.toml(L15) Cargo.lock(L18 - L21)
x86_64 v0.15 (Conditional)
The x86_64
crate is the primary hardware abstraction layer, providing safe access to x86_64-specific instructions and memory-mapped I/O operations required for CMOS interaction.
Attribute | Value |
---|---|
Version | 0.15.2 |
Condition | target_arch = "x86_64" |
Purpose | Hardware abstraction for x86_64 architecture |
Key Features | I/O port access, memory safety primitives |
Sources: Cargo.toml(L17 - L18) Cargo.lock(L36 - L45)
Transitive Dependencies
The x86_64
crate brings in four transitive dependencies that provide low-level hardware access capabilities:
Hardware Access Stack
flowchart TD subgraph subGraph4["CMOS Hardware Interface"] PORT_ACCESS["I/O Port Operations"] REGISTER_FIELDS["CMOS Register Fields"] MEMORY_SAFETY["Volatile Memory Access"] end subgraph subGraph3["Hardware Abstraction Layers"] X86_CRATE["x86_64 crate"] subgraph subGraph2["Build-time Utilities"] RUSTVERSION_DEP["rustversion v1.0.19"] end subgraph subGraph1["Memory Safety"] VOLATILE_DEP["volatile v0.4.6"] end subgraph subGraph0["Register Management"] BIT_FIELD_DEP["bit_field v0.10.2"] BITFLAGS_DEP["bitflags v2.6.0"] end end BITFLAGS_DEP --> REGISTER_FIELDS BIT_FIELD_DEP --> REGISTER_FIELDS VOLATILE_DEP --> MEMORY_SAFETY VOLATILE_DEP --> PORT_ACCESS X86_CRATE --> BITFLAGS_DEP X86_CRATE --> BIT_FIELD_DEP X86_CRATE --> RUSTVERSION_DEP X86_CRATE --> VOLATILE_DEP
Sources: Cargo.lock(L40 - L44)
Dependency Details
Crate | Version | Purpose | Role in RTC |
---|---|---|---|
bit_field | 0.10.2 | Bit field manipulation | CMOS register field extraction |
bitflags | 2.6.0 | Type-safe bit flag operations | Status register flags |
volatile | 0.4.6 | Volatile memory access | Hardware register safety |
rustversion | 1.0.19 | Rust version detection | Conditional API compilation |
Sources: Cargo.lock(L6 - L45)
Platform Requirements
Architecture Specificity
The x86_rtc
crate is exclusively designed for x86_64 architecture due to its dependence on CMOS hardware implementation specific to x86-compatible systems.
Requirement | Specification |
---|---|
Target Architecture | x86_64only |
Hardware Interface | CMOS via I/O ports 0x70/0x71 |
Memory Model | Physical memory access required |
Privilege Level | Kernel/supervisor mode for I/O operations |
Sources: Cargo.toml(L17) Cargo.toml(L11)
no_std Compatibility
The crate is designed for no_std
environments, making it suitable for:
- Operating system kernels
- Embedded systems
- Bootloaders
- Hypervisors
Sources: Cargo.toml(L12)
Conditional Compilation Strategy
The crate employs a sophisticated conditional compilation strategy to ensure platform safety and optimal builds.
flowchart TD subgraph subGraph2["Compilation Decision Tree"] START["Build Process"] ARCH_CHECK["target_arch == x86_64?"] CFG_IF_USAGE["cfg-if macro usage"] subgraph subGraph1["Non-x86_64 Path"] EXCLUDE_X86["Exclude x86_64 crate"] COMPILE_ERROR["Compilation fails"] NO_IMPL["No RTC implementation"] end subgraph subGraph0["x86_64 Path"] INCLUDE_X86["Include x86_64 crate"] HARDWARE_ACCESS["Enable hardware access"] CMOS_INTERFACE["Compile CMOS interface"] end end ARCH_CHECK --> EXCLUDE_X86 ARCH_CHECK --> INCLUDE_X86 COMPILE_ERROR --> NO_IMPL EXCLUDE_X86 --> COMPILE_ERROR HARDWARE_ACCESS --> CMOS_INTERFACE INCLUDE_X86 --> HARDWARE_ACCESS START --> ARCH_CHECK START --> CFG_IF_USAGE
Sources: Cargo.toml(L17 - L18)
Conditional Dependency Declaration
The conditional dependency syntax in Cargo.toml
ensures that the x86_64
crate and its transitive dependencies are only included when building for compatible targets:
[target.'cfg(target_arch = "x86_64")'.dependencies]
x86_64 = "0.15"
Sources: Cargo.toml(L17 - L18)
Supported Build Targets
Primary Targets
Target Triple | Environment | Support Level |
---|---|---|
x86_64-unknown-linux-gnu | Standard Linux | Full support |
x86_64-unknown-none | Bare metal/kernel | Full support |
Unsupported Architectures
The following architectures are explicitly unsupported due to hardware constraints:
- ARM64 (aarch64)
- ARM32 (arm)
- RISC-V (riscv64)
- WebAssembly (wasm32)
Sources: Cargo.toml(L11) Cargo.toml(L17)
Version Constraints and Compatibility
The crate maintains conservative version constraints to ensure stability:
Direct Dependency Versions
Dependency | Constraint | Rationale |
---|---|---|
cfg-if | 1.0 | Stable API, wide compatibility |
x86_64 | 0.15 | Latest stable hardware abstraction |
Transitive Version Resolution
The locked dependency versions provide a stable foundation:
- All transitive dependencies use semantic versioning
- Version locks prevent unexpected breaking changes
- Regular updates maintain security and compatibility
Sources: Cargo.toml(L15) Cargo.toml(L18) Cargo.lock(L1 - L54)
Dependency Analysis
Relevant source files
This document provides a comprehensive analysis of the x86_rtc crate's dependency structure, including direct dependencies, transitive dependencies, version constraints, and conditional compilation requirements. This analysis covers both the explicit dependencies declared in the build configuration and the full resolved dependency tree.
For information about platform-specific compilation targets and architecture requirements, see Platform and Architecture Requirements. For details about the crate's overall configuration and metadata, see Crate Definition and Metadata.
Direct Dependencies
The x86_rtc crate declares two direct dependencies in its Cargo.toml
configuration:
Dependency | Version | Scope | Purpose |
---|---|---|---|
cfg-if | 1.0 | Unconditional | Conditional compilation utilities |
x86_64 | 0.15 | x86_64 architecture only | Hardware abstraction layer |
cfg-if Dependency
The cfg-if
crate provides conditional compilation macros that simplify platform-specific code organization. This dependency is declared unconditionally in Cargo.toml(L15) and resolves to version 1.0.0 in the lock file.
x86_64 Architecture Dependency
The x86_64
crate is conditionally included only when building for x86_64 architecture targets, as specified in Cargo.toml(L17 - L18) This dependency provides low-level hardware access primitives essential for CMOS register manipulation.
Dependency Graph - Direct Dependencies
Sources: Cargo.toml(L14 - L18) Cargo.lock(L48 - L53)
Transitive Dependencies
The x86_64 crate introduces four transitive dependencies that provide essential low-level functionality:
Dependency | Version | Source | Purpose |
---|---|---|---|
bit_field | 0.10.2 | x86_64 | Bit field manipulation for registers |
bitflags | 2.6.0 | x86_64 | Type-safe bit flag operations |
rustversion | 1.0.19 | x86_64 | Rust compiler version detection |
volatile | 0.4.6 | x86_64 | Memory-mapped I/O safety |
Hardware Abstraction Dependencies
The transitive dependencies fall into two categories:
Register Manipulation: bit_field
and bitflags
provide safe abstractions for manipulating hardware register bits and flags, essential for CMOS register operations.
Memory Safety: volatile
ensures proper memory-mapped I/O semantics, preventing compiler optimizations that could break hardware communication protocols.
Build-time Utilities: rustversion
enables conditional compilation based on Rust compiler versions, ensuring compatibility across different toolchain versions.
Complete Dependency Tree
flowchart TD subgraph subGraph1["Transitive Dependencies"] bit_field["bit_field v0.10.2"] bitflags["bitflags v2.6.0"] rustversion["rustversion v1.0.19"] volatile["volatile v0.4.6"] end subgraph subGraph0["Direct Dependencies"] cfg_if["cfg-if v1.0.0"] x86_64_crate["x86_64 v0.15.2"] end x86_rtc["x86_rtc v0.1.1"] x86_64_crate --> bit_field x86_64_crate --> bitflags x86_64_crate --> rustversion x86_64_crate --> volatile x86_rtc --> cfg_if x86_rtc --> x86_64_crate
Sources: Cargo.lock(L40 - L45) Cargo.lock(L5 - L53)
Version Constraints and Compatibility
Semantic Versioning Strategy
The crate uses semantic versioning with specific constraint patterns:
- Major version pinning:
cfg-if = "1.0"
allows patch updates within the 1.x series - Minor version pinning:
x86_64 = "0.15"
allows patch updates within the 0.15.x series
Resolved Version Analysis
The dependency resolution in Cargo.lock
shows the specific versions chosen within the declared constraints:
Code Entity Dependency Mapping
flowchart TD subgraph subGraph3["Transitive: Hardware Primitives"] bit_field_impl["BitField trait"] bitflags_impl["bitflags! macro"] volatile_impl["Volatile"] rustversion_impl["rustversion attributes"] end subgraph subGraph2["x86_64 v0.15.2"] port_read["Port::read()"] port_write["Port::write()"] instructions["instructions module"] end subgraph subGraph1["cfg-if v1.0.0"] cfg_if_macro["cfg_if! macro"] end subgraph subGraph0["Crate: x86_rtc"] rtc_new["Rtc::new()"] get_timestamp["get_unix_timestamp()"] set_timestamp["set_unix_timestamp()"] end get_timestamp --> port_read instructions --> rustversion_impl port_read --> bit_field_impl port_read --> volatile_impl port_write --> bitflags_impl port_write --> volatile_impl rtc_new --> cfg_if_macro set_timestamp --> port_write
Sources: Cargo.toml(L14 - L18) Cargo.lock(L36 - L45)
Conditional Compilation Structure
Architecture-Specific Dependencies
The dependency structure implements a conditional compilation pattern where core hardware dependencies are only included for supported architectures:
// From Cargo.toml structure
[target.'cfg(target_arch = "x86_64")'.dependencies]
x86_64 = "0.15"
This pattern ensures that:
- The crate can be parsed on non-x86_64 platforms without pulling in architecture-specific dependencies
- Hardware-specific functionality is only available when targeting compatible architectures
- Build times are optimized for cross-compilation scenarios
Dependency Resolution Matrix
Build Target | cfg-if | x86_64 | Transitive Dependencies |
---|---|---|---|
x86_64-unknown-linux-gnu | ✓ | ✓ | All included |
x86_64-unknown-none | ✓ | ✓ | All included |
other architectures | ✓ | ✗ | None included |
Conditional Dependency Flow
Sources: Cargo.toml(L17 - L18) Cargo.lock(L40 - L53)
Dependency Security and Maintenance
Version Stability
All dependencies use stable release versions:
- No pre-release or development versions
- Established crates with mature APIs
- Conservative version constraints that allow security updates
Maintenance Burden
The dependency tree is intentionally minimal:
- Only 6 total crates in the dependency graph
- Well-maintained crates from established authors
- Clear separation between conditional and unconditional dependencies
The focused dependency set reduces maintenance overhead while providing essential hardware abstraction capabilities required for x86_64 RTC operations.
Sources: Cargo.toml(L14 - L18) Cargo.lock(L1 - L53)
Platform and Architecture Requirements
Relevant source files
This document covers the platform-specific requirements and architectural constraints of the x86_rtc crate. It explains the x86_64 architecture dependency, conditional compilation strategies, and target platform support. For information about the crate's direct and transitive dependencies, see Dependency Analysis. For details about the core RTC implementation, see Implementation.
Architecture Specificity
The x86_rtc crate is exclusively designed for x86_64 architecture systems due to its reliance on x86-specific CMOS hardware interfaces. This architectural constraint is enforced through both crate metadata and conditional compilation directives.
x86_64 Hardware Dependency
The crate targets x86_64 systems because the Real Time Clock implementation requires direct access to CMOS registers via specific I/O ports that are part of the x86 architecture specification. The hardware interface is not portable to other architectures like ARM, RISC-V, or other platforms.
Sources: Cargo.toml(L6) Cargo.toml(L11) Cargo.toml(L17 - L18)
Conditional Compilation Strategy
The crate employs Rust's conditional compilation features to ensure the x86_64 dependency is only included on compatible target architectures. This prevents compilation errors on unsupported platforms while maintaining clean dependency management.
Target-Specific Dependencies
The conditional dependency specification ensures that the x86_64
crate is only pulled in when building for x86_64 targets:
Configuration | Dependency | Purpose |
---|---|---|
cfg(target_arch = "x86_64") | x86_64 = "0.15" | Hardware register access and I/O port operations |
All targets | cfg-if = "1.0" | Conditional compilation utilities |
Sources: Cargo.toml(L15) Cargo.toml(L17 - L18)
Target Platform Support
The crate supports multiple x86_64 target environments through its no_std
compatibility, enabling deployment in both hosted and bare-metal contexts.
Supported Build Targets
Target Triple | Environment | Use Case |
---|---|---|
x86_64-unknown-linux-gnu | Hosted Linux | Standard applications, system services |
x86_64-unknown-none | Bare metal | Operating system kernels, embedded systems |
No-Standard Library Compatibility
The crate's no_std
categorization enables usage in resource-constrained and bare-metal environments where the standard library is unavailable. This is essential for:
- Operating system kernel development
- Embedded systems programming
- Bootloader implementations
- Real-time systems
flowchart TD subgraph subGraph2["Target Applications"] USER_APPS["User Applications"] SYSTEM_SERVICES["System Services"] OS_KERNELS["OS Kernels"] BOOTLOADERS["Bootloaders"] EMBEDDED["Embedded Systems"] end subgraph subGraph1["Standard Library Usage"] STD_AVAILABLE["std available"] NO_STD["no_std only"] end subgraph subGraph0["Runtime Environments"] HOSTED["Hosted Environment"] BARE_METAL["Bare Metal Environment"] end BARE_METAL --> NO_STD HOSTED --> STD_AVAILABLE NO_STD --> BOOTLOADERS NO_STD --> EMBEDDED NO_STD --> OS_KERNELS STD_AVAILABLE --> SYSTEM_SERVICES STD_AVAILABLE --> USER_APPS
Sources: Cargo.toml(L12)
Hardware Abstraction Requirements
The platform requirements stem from the need to abstract x86_64-specific hardware features while maintaining safety and performance.
Register Access Patterns
The crate requires direct hardware register access capabilities that are only available through x86_64-specific instructions and I/O operations. This creates a hard dependency on the underlying architecture's instruction set and memory-mapped I/O capabilities.
Safety Constraints
Platform requirements also include memory safety considerations for hardware access:
- Volatile memory access for hardware registers
- Atomic operations for register synchronization
- Interrupt-safe register manipulation
- Proper handling of hardware timing constraints
Sources: Cargo.toml(L6) Cargo.toml(L17 - L18)
Build Configuration Impact
The platform requirements influence several aspects of the build configuration and crate capabilities.
Linting Adjustments
The crate includes specific clippy lint configurations that account for the hardware-specific nature of the implementation:
flowchart TD subgraph subGraph1["Code Patterns"] CONSTRUCTOR_PATTERN["Constructor without Default"] HARDWARE_INIT["Hardware initialization"] UNSAFE_BLOCKS["Unsafe hardware access"] end subgraph subGraph0["Build Configuration"] CLIPPY_LINTS["clippy lints"] NEW_WITHOUT_DEFAULT["new_without_default = allow"] HARDWARE_PATTERNS["Hardware-specific patterns"] end CLIPPY_LINTS --> NEW_WITHOUT_DEFAULT CONSTRUCTOR_PATTERN --> HARDWARE_INIT HARDWARE_INIT --> UNSAFE_BLOCKS HARDWARE_PATTERNS --> UNSAFE_BLOCKS NEW_WITHOUT_DEFAULT --> CONSTRUCTOR_PATTERN
Edition and Toolchain Requirements
The crate specifies Rust 2021 edition compatibility, ensuring access to modern language features while maintaining compatibility with the x86_64 hardware abstraction layer.
Sources: Cargo.toml(L4) Cargo.toml(L20 - L21)
Development Workflow
Relevant source files
This document covers the development, testing, and deployment processes for the x86_rtc crate. It details the automated CI/CD pipeline, development environment requirements, and the workflow for contributing to and maintaining the codebase.
For detailed information about the crate's dependencies and platform requirements, see Dependencies and Platform Support. For implementation details of the RTC driver functionality, see Implementation.
CI/CD Pipeline Overview
The x86_rtc crate uses GitHub Actions for continuous integration and deployment, configured through a comprehensive workflow that ensures code quality, cross-platform compatibility, and automated documentation deployment.
Pipeline Architecture
flowchart TD subgraph subGraph2["Documentation Job"] DOC_BUILD["cargo doc --no-deps --all-features"] INDEX_GEN["Generate index.html redirect"] DEPLOY["Deploy to gh-pages branch"] end subgraph subGraph1["Quality Checks"] FORMAT["cargo fmt --all -- --check"] CLIPPY["cargo clippy --target TARGET --all-features"] BUILD["cargo build --target TARGET --all-features"] TEST["cargo test --target x86_64-unknown-linux-gnu"] end subgraph subGraph0["CI Job Matrix"] SETUP_GNU["Setup Environmentx86_64-unknown-linux-gnu"] SETUP_NONE["Setup Environmentx86_64-unknown-none"] end TRIGGER["push/pull_request events"] BUILD --> TEST CLIPPY --> BUILD DOC_BUILD --> INDEX_GEN FORMAT --> CLIPPY INDEX_GEN --> DEPLOY SETUP_GNU --> FORMAT SETUP_NONE --> FORMAT TRIGGER --> DOC_BUILD TRIGGER --> SETUP_GNU TRIGGER --> SETUP_NONE
Sources: .github/workflows/ci.yml(L1 - L56)
Job Configuration Details
The CI pipeline consists of two primary jobs with specific configurations:
Job | Purpose | Matrix Strategy | Key Steps |
---|---|---|---|
ci | Code quality and cross-platform builds | 2 targets × 1 toolchain | Format check, clippy, build, test |
doc | Documentation generation and deployment | Single configuration | Doc build, GitHub Pages deployment |
CI Job Matrix Strategy
The ci
job uses a matrix strategy to test across multiple compilation targets:
strategy:
fail-fast: false
matrix:
rust-toolchain: [nightly]
targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none]
This configuration ensures compatibility with both standard Linux environments and bare-metal/kernel development contexts.
Sources: .github/workflows/ci.yml(L8 - L12)
Toolchain and Component Setup
The pipeline uses the nightly Rust toolchain with specific components:
flowchart TD subgraph subGraph1["Target Platforms"] GNU_TARGET["x86_64-unknown-linux-gnu"] NONE_TARGET["x86_64-unknown-none"] end subgraph subGraph0["Required Components"] RUST_SRC["rust-src"] CLIPPY["clippy"] RUSTFMT["rustfmt"] end TOOLCHAIN["dtolnay/rust-toolchain@nightly"] TOOLCHAIN --> CLIPPY TOOLCHAIN --> GNU_TARGET TOOLCHAIN --> NONE_TARGET TOOLCHAIN --> RUSTFMT TOOLCHAIN --> RUST_SRC
Sources: .github/workflows/ci.yml(L15 - L19)
Quality Assurance Steps
The CI pipeline enforces code quality through multiple sequential checks:
1. Code Formatting Verification
cargo fmt --all -- --check
Ensures all code adheres to standard Rust formatting conventions.
2. Linting with Clippy
cargo clippy --target ${{ matrix.targets }} --all-features -- -D warnings
Performs static analysis and treats all warnings as errors to maintain code quality.
3. Cross-Platform Build Verification
cargo build --target ${{ matrix.targets }} --all-features
Validates that the crate compiles successfully on both hosted and bare-metal targets.
4. Unit Testing
cargo test --target ${{ matrix.targets }} -- --nocapture
Executes unit tests, but only on x86_64-unknown-linux-gnu
target due to hardware dependencies.
Sources: .github/workflows/ci.yml(L22 - L30)
Documentation Deployment
The documentation job handles automated generation and deployment of API documentation:
sequenceDiagram participant Developer as "Developer" participant GitHub as "GitHub" participant GitHubActions as "GitHub Actions" participant GitHubPages as "GitHub Pages" Developer ->> GitHub: Push to default branch GitHub ->> GitHubActions: Trigger doc job GitHubActions ->> GitHubActions: cargo doc --no-deps --all-features GitHubActions ->> GitHubActions: Generate index.html redirect GitHubActions ->> GitHubPages: Deploy target/doc to gh-pages GitHubPages ->> Developer: Documentation available
Documentation Build Configuration
The documentation build uses specific flags to ensure quality:
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
These flags treat broken documentation links and missing documentation as errors.
Sources: .github/workflows/ci.yml(L40)
Conditional Deployment Logic
Documentation deployment occurs only on the default branch:
if: ${{ github.ref == env.default-branch }}
This prevents documentation pollution from feature branches while ensuring the main documentation stays current.
Sources: .github/workflows/ci.yml(L50)
Development Environment Setup
Required Tools and Configuration
Developers working on the x86_rtc crate need to configure their environment to match the CI pipeline requirements:
Rust Toolchain Requirements
- Toolchain: Nightly Rust (required for certain features)
- Components:
rust-src
,clippy
,rustfmt
- Targets:
x86_64-unknown-linux-gnu
,x86_64-unknown-none
Installation Commands
# Install nightly toolchain
rustup toolchain install nightly
# Add required components
rustup component add rust-src clippy rustfmt --toolchain nightly
# Add compilation targets
rustup target add x86_64-unknown-linux-gnu x86_64-unknown-none --toolchain nightly
Ignored Files and Artifacts
The repository uses .gitignore
to exclude development artifacts and environment-specific files:
flowchart TD subgraph subGraph0["Ignored Patterns"] TARGET["/targetRust build artifacts"] VSCODE["/.vscodeEditor configuration"] DSSTORE[".DS_StoremacOS system files"] end GITIGNORE[".gitignore"] GITIGNORE --> DSSTORE GITIGNORE --> TARGET GITIGNORE --> VSCODE
Sources: .gitignore(L1 - L4)
Local Development Workflow
Pre-commit Validation
Before submitting changes, developers should run the same checks as the CI pipeline:
# Format check
cargo fmt --all -- --check
# Linting
cargo clippy --target x86_64-unknown-linux-gnu --all-features -- -D warnings
cargo clippy --target x86_64-unknown-none --all-features -- -D warnings
# Build verification
cargo build --target x86_64-unknown-linux-gnu --all-features
cargo build --target x86_64-unknown-none --all-features
# Unit tests
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
Documentation Development
Local documentation can be built and reviewed using:
# Build documentation
cargo doc --no-deps --all-features --open
# Check for documentation issues
RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs" cargo doc
Contribution and Release Workflow
Branch Protection and Automation
The workflow is designed to support a development model where:
- All commits trigger format checking, linting, and cross-platform builds
- Pull requests receive the same validation without documentation deployment
- Default branch pushes additionally trigger documentation deployment to GitHub Pages
Integration Points
The development workflow integrates with several external systems:
Integration | Purpose | Configuration |
---|---|---|
GitHub Actions | CI/CD automation | .github/workflows/ci.yml |
GitHub Pages | Documentation hosting | Automated deployment togh-pagesbranch |
Rust toolchain | Build and validation | Nightly toolchain with specific components |
Sources: .github/workflows/ci.yml(L1 - L56) .gitignore(L1 - L4)
CI/CD Pipeline
Relevant source files
This document details the continuous integration and continuous deployment (CI/CD) pipeline for the x86_rtc crate, implemented through GitHub Actions. The pipeline ensures code quality, cross-platform compatibility, and automated documentation deployment.
For information about the development environment setup and local development workflows, see Development Environment Setup. For details about the crate's platform requirements and target architectures, see Platform and Architecture Requirements.
Pipeline Overview
The CI/CD pipeline is defined in .github/workflows/ci.yml(L1 - L56) and consists of two primary jobs: ci
for code quality and testing, and doc
for documentation generation and deployment.
flowchart TD subgraph Outputs["Outputs"] QUALITY["Code Quality Validation"] PAGES["GitHub Pages Documentation"] end subgraph subGraph3["GitHub Actions Workflow"] subgraph subGraph2["doc job"] CHECKOUT_DOC["actions/checkout@v4"] TOOLCHAIN_DOC["dtolnay/rust-toolchain@nightly"] DOC_BUILD["cargo doc --no-deps --all-features"] DEPLOY["JamesIves/github-pages-deploy-action@v4"] end subgraph subGraph1["ci job"] MATRIX["Matrix Strategy"] CHECKOUT_CI["actions/checkout@v4"] TOOLCHAIN_CI["dtolnay/rust-toolchain@nightly"] FORMAT["cargo fmt --all -- --check"] CLIPPY["cargo clippy"] BUILD["cargo build"] TEST["cargo test"] end end subgraph subGraph0["Trigger Events"] PUSH["push events"] PR["pull_request events"] end BUILD --> TEST CHECKOUT_CI --> TOOLCHAIN_CI CHECKOUT_DOC --> TOOLCHAIN_DOC CLIPPY --> BUILD DEPLOY --> PAGES DOC_BUILD --> DEPLOY FORMAT --> CLIPPY MATRIX --> CHECKOUT_CI PR --> CHECKOUT_DOC PR --> MATRIX PUSH --> CHECKOUT_DOC PUSH --> MATRIX TEST --> QUALITY TOOLCHAIN_CI --> FORMAT TOOLCHAIN_DOC --> DOC_BUILD
Sources: .github/workflows/ci.yml(L1 - L56)
CI Job Configuration
The ci
job implements a comprehensive testing strategy using a matrix approach to validate the crate across multiple target platforms.
Matrix Strategy
Parameter | Values |
---|---|
rust-toolchain | nightly |
targets | x86_64-unknown-linux-gnu,x86_64-unknown-none |
The matrix configuration in .github/workflows/ci.yml(L8 - L12) ensures the crate builds and functions correctly for both standard Linux environments and bare-metal/kernel environments.
flowchart TD subgraph subGraph2["Matrix Execution"] NIGHTLY["nightly toolchain"] GNU["x86_64-unknown-linux-gnu target"] NONE["x86_64-unknown-none target"] subgraph subGraph1["Job Instance 2"] J2_TOOLCHAIN["nightly"] J2_TARGET["x86_64-unknown-none"] J2_STEPS["All steps except Unit tests"] end subgraph subGraph0["Job Instance 1"] J1_TOOLCHAIN["nightly"] J1_TARGET["x86_64-unknown-linux-gnu"] J1_STEPS["All steps + Unit tests"] end end GNU --> J1_TARGET J1_TARGET --> J1_STEPS J1_TOOLCHAIN --> J1_STEPS J2_TARGET --> J2_STEPS J2_TOOLCHAIN --> J2_STEPS NIGHTLY --> J1_TOOLCHAIN NIGHTLY --> J2_TOOLCHAIN NONE --> J2_TARGET
Sources: .github/workflows/ci.yml(L8 - L12) .github/workflows/ci.yml(L29 - L30)
CI Job Steps
The CI job executes the following validation steps in sequence:
flowchart TD CHECKOUT["actions/checkout@v4Check out repository"] SETUP["dtolnay/rust-toolchain@nightlyInstall Rust nightly + components"] VERSION["rustc --version --verboseVerify Rust installation"] FORMAT["cargo fmt --all -- --checkValidate code formatting"] CLIPPY["cargo clippy --target TARGET --all-featuresLint analysis"] BUILD["cargo build --target TARGET --all-featuresCompilation check"] TEST["cargo test --target TARGET -- --nocaptureUnit testing (linux-gnu only)"] BUILD --> TEST CHECKOUT --> SETUP CLIPPY --> BUILD FORMAT --> CLIPPY SETUP --> VERSION VERSION --> FORMAT
Sources: .github/workflows/ci.yml(L13 - L30)
The Rust toolchain setup .github/workflows/ci.yml(L15 - L19) includes essential components:
rust-src
: Source code for cross-compilationclippy
: Linting toolrustfmt
: Code formatting tool
Unit tests are conditionally executed only for the x86_64-unknown-linux-gnu
target .github/workflows/ci.yml(L29 - L30) since the bare-metal target (x86_64-unknown-none
) cannot execute standard test harnesses.
Documentation Job
The doc
job handles automated documentation generation and deployment to GitHub Pages.
Documentation Build Process
flowchart TD subgraph subGraph2["Documentation Generation"] DOC_START["Job trigger"] CHECKOUT["actions/checkout@v4"] TOOLCHAIN["dtolnay/rust-toolchain@nightly"] BUILD_DOC["cargo doc --no-deps --all-features"] INDEX_GEN["Generate index.html redirect"] subgraph subGraph1["Conditional Deployment"] BRANCH_CHECK["Check if default branch"] DEPLOY["JamesIves/github-pages-deploy-action@v4"] PAGES_OUTPUT["GitHub Pages deployment"] end subgraph subGraph0["Build Configuration"] FLAGS["RUSTDOCFLAGS environment"] BROKEN_LINKS["-D rustdoc::broken_intra_doc_links"] MISSING_DOCS["-D missing-docs"] end end BRANCH_CHECK --> DEPLOY BROKEN_LINKS --> BUILD_DOC BUILD_DOC --> INDEX_GEN CHECKOUT --> TOOLCHAIN DEPLOY --> PAGES_OUTPUT DOC_START --> CHECKOUT FLAGS --> BROKEN_LINKS FLAGS --> MISSING_DOCS INDEX_GEN --> BRANCH_CHECK MISSING_DOCS --> BUILD_DOC TOOLCHAIN --> FLAGS
Sources: .github/workflows/ci.yml(L32 - L56)
Documentation Configuration
The documentation build enforces strict quality standards through RUSTDOCFLAGS
.github/workflows/ci.yml(L40) :
Flag | Purpose |
---|---|
-D rustdoc::broken_intra_doc_links | Treat broken internal documentation links as errors |
-D missing-docs | Require documentation for all public APIs |
The build process .github/workflows/ci.yml(L44 - L48) generates documentation using cargo doc --no-deps --all-features
and creates an automatic redirect index page pointing to the crate's documentation root.
Deployment Strategy
Documentation deployment follows a branch-based strategy:
flowchart TD subgraph subGraph0["Branch Logic"] TRIGGER["Documentation build"] DEFAULT_CHECK["github.ref == env.default-branch"] DEPLOY_ACTION["Deploy to gh-pages branch"] CONTINUE["continue-on-error for non-default"] SKIP["Skip deployment"] end CONTINUE --> SKIP DEFAULT_CHECK --> CONTINUE DEFAULT_CHECK --> DEPLOY_ACTION TRIGGER --> DEFAULT_CHECK
Sources: .github/workflows/ci.yml(L45) .github/workflows/ci.yml(L49 - L55)
Only builds from the default branch .github/workflows/ci.yml(L50) trigger deployment to GitHub Pages using the JamesIves/github-pages-deploy-action@v4
action with single-commit: true
to maintain a clean deployment history.
Pipeline Features
Error Handling and Robustness
The pipeline implements several error handling strategies:
- Fail-fast disabled .github/workflows/ci.yml(L9) : Matrix jobs continue execution even if one target fails
- Conditional execution .github/workflows/ci.yml(L29) : Unit tests only run where applicable
- Continue-on-error .github/workflows/ci.yml(L45) : Documentation builds for non-default branches don't fail the pipeline
Security and Permissions
The documentation job requires specific permissions .github/workflows/ci.yml(L36 - L37) :
contents: write
for GitHub Pages deployment
Build Optimization
Key optimization features include:
- No-deps documentation .github/workflows/ci.yml(L47) : Faster builds by excluding dependency documentation
- Single-commit deployment .github/workflows/ci.yml(L53) : Reduced repository size for GitHub Pages
- All-features builds .github/workflows/ci.yml(L25 - L47) : Comprehensive feature coverage
Sources: .github/workflows/ci.yml(L1 - L56)
Development Environment Setup
Relevant source files
Purpose and Scope
This document provides comprehensive guidelines for setting up a development environment for the x86_rtc crate, including toolchain requirements, project structure understanding, and development best practices. It covers the essential tools, configurations, and workflows needed to contribute effectively to this hardware-level RTC driver.
For information about the automated CI/CD pipeline that validates these development practices, see CI/CD Pipeline.
Prerequisites and Toolchain Requirements
The x86_rtc crate requires a specific Rust toolchain configuration to support its hardware-focused, no_std development model and target platforms.
Required Rust Toolchain
The project mandates the Rust nightly toolchain with specific components and targets:
Component | Purpose | Required |
---|---|---|
nightlytoolchain | Access to unstable features for hardware programming | Yes |
rust-src | Source code for cross-compilation | Yes |
clippy | Linting and code quality analysis | Yes |
rustfmt | Code formatting standards | Yes |
x86_64-unknown-linux-gnu | Standard Linux development target | Yes |
x86_64-unknown-none | Bare metal/kernel development target | Yes |
Toolchain Installation Commands
# Install nightly toolchain
rustup toolchain install nightly
# Add required components
rustup component add --toolchain nightly rust-src clippy rustfmt
# Add compilation targets
rustup target add --toolchain nightly x86_64-unknown-linux-gnu
rustup target add --toolchain nightly x86_64-unknown-none
# Set nightly as default (optional)
rustup default nightly
Sources: .github/workflows/ci.yml(L11 - L19)
Development Toolchain Architecture
flowchart TD subgraph subGraph3["Development Commands"] BUILD_CMD["cargo build"] TEST_CMD["cargo test"] FMT_CMD["cargo fmt"] CLIPPY_CMD["cargo clippy"] DOC_CMD["cargo doc"] end subgraph subGraph2["Target Platforms"] GNU_TARGET["x86_64-unknown-linux-gnuHosted development"] NONE_TARGET["x86_64-unknown-noneBare metal/kernel"] end subgraph subGraph1["Required Components"] RUST_SRC["rust-srcCross-compilation support"] CLIPPY["clippyCode linting"] RUSTFMT["rustfmtCode formatting"] end subgraph subGraph0["Rust Toolchain"] NIGHTLY["nightly toolchain"] COMPONENTS["Components"] TARGETS["Compilation Targets"] end CLIPPY --> CLIPPY_CMD COMPONENTS --> CLIPPY COMPONENTS --> RUSTFMT COMPONENTS --> RUST_SRC GNU_TARGET --> TEST_CMD NIGHTLY --> COMPONENTS NIGHTLY --> TARGETS NONE_TARGET --> BUILD_CMD RUSTFMT --> FMT_CMD RUST_SRC --> BUILD_CMD TARGETS --> GNU_TARGET TARGETS --> NONE_TARGET
Sources: .github/workflows/ci.yml(L15 - L19)
Repository Structure and Ignored Files
Understanding the project's file organization and what gets excluded from version control is essential for effective development.
Version Control Exclusions
The .gitignore
configuration maintains a clean repository by excluding build artifacts and development-specific files:
Path | Description | Reason for Exclusion |
---|---|---|
/target | Cargo build output directory | Generated artifacts, platform-specific |
/.vscode | Visual Studio Code workspace settings | IDE-specific, personal preferences |
.DS_Store | macOS file system metadata | Platform-specific system files |
Development File Structure
flowchart TD subgraph subGraph2["Build Process"] CARGO_BUILD["cargo build"] CARGO_TEST["cargo test"] CARGO_DOC["cargo doc"] end subgraph subGraph1["Generated/Ignored Files"] TARGET["/targetBuild artifacts"] VSCODE["/.vscodeIDE settings"] DSSTORE[".DS_StoremacOS metadata"] end subgraph subGraph0["Version Controlled Files"] SRC["src/lib.rsCore implementation"] CARGO["Cargo.tomlCrate configuration"] LOCK["Cargo.lockDependency versions"] CI["/.github/workflows/ci.ymlCI configuration"] README["README.mdDocumentation"] end CARGO --> CARGO_BUILD CARGO_BUILD --> TARGET CARGO_DOC --> TARGET CARGO_TEST --> TARGET SRC --> CARGO_BUILD TARGET --> DSSTORE TARGET --> VSCODE
Sources: .gitignore(L1 - L4)
Development Workflow and Code Quality
The development workflow is designed around the CI pipeline requirements, ensuring code quality and platform compatibility before integration.
Code Quality Pipeline
The development process follows a structured approach that mirrors the automated CI checks:
flowchart TD subgraph subGraph1["Quality Gates"] FMT_CHECK["Formatting Check"] LINT_CHECK["Linting Check"] BUILD_CHECK["Build Check"] TEST_CHECK["Test Check"] DOC_CHECK["Documentation Check"] end subgraph subGraph0["Development Cycle"] EDIT["Edit Code"] FORMAT["cargo fmt --all --check"] LINT["cargo clippy --all-features"] BUILD_GNU["cargo build --target x86_64-unknown-linux-gnu"] BUILD_NONE["cargo build --target x86_64-unknown-none"] TEST["cargo test --target x86_64-unknown-linux-gnu"] DOC["cargo doc --no-deps --all-features"] end BUILD_CHECK --> TEST BUILD_GNU --> BUILD_NONE BUILD_NONE --> BUILD_CHECK DOC --> DOC_CHECK EDIT --> FORMAT FMT_CHECK --> LINT FORMAT --> FMT_CHECK LINT --> LINT_CHECK LINT_CHECK --> BUILD_GNU TEST --> TEST_CHECK TEST_CHECK --> DOC
Sources: .github/workflows/ci.yml(L22 - L30)
Development Commands Reference
Command | Purpose | Target Platform | CI Equivalent |
---|---|---|---|
cargo fmt --all -- --check | Verify code formatting | All | Line 23 |
cargo clippy --target | Lint analysis | Both targets | Line 25 |
cargo build --target | Compilation check | Both targets | Line 27 |
cargo test --target x86_64-unknown-linux-gnu -- --nocapture | Unit testing | Linux only | Line 30 |
cargo doc --no-deps --all-features | Documentation generation | Default | Line 47 |
Local Testing Environment
Target-Specific Testing
Testing is platform-dependent due to the hardware-specific nature of the crate:
Test Type | Platform | Capability | Limitations |
---|---|---|---|
Unit Tests | x86_64-unknown-linux-gnu | Full test execution | Requires hosted environment |
Build Tests | x86_64-unknown-none | Compilation verification only | No test execution in bare metal |
Documentation | Default target | API documentation generation | No hardware interaction |
Hardware Testing Considerations
Since this crate provides hardware-level RTC access, comprehensive testing requires understanding of the limitations:
flowchart TD subgraph subGraph2["Hardware Interaction"] CMOS_ACCESS["CMOS Register AccessRequires privileged mode"] RTC_OPS["RTC OperationsHardware-dependent"] end subgraph subGraph1["Test Capabilities"] UNIT_TESTS["Unit TestsAPI verification"] BUILD_TESTS["Build TestsCompilation validation"] DOC_TESTS["Documentation TestsExample validation"] end subgraph subGraph0["Testing Environments"] HOSTED["Hosted Environmentx86_64-unknown-linux-gnu"] BARE["Bare Metal Environmentx86_64-unknown-none"] end BARE --> BUILD_TESTS CMOS_ACCESS --> HOSTED HOSTED --> BUILD_TESTS HOSTED --> DOC_TESTS HOSTED --> UNIT_TESTS RTC_OPS --> HOSTED UNIT_TESTS --> CMOS_ACCESS UNIT_TESTS --> RTC_OPS
Sources: .github/workflows/ci.yml(L28 - L30)
Documentation Development
Local Documentation Generation
The project uses strict documentation standards enforced through RUSTDOCFLAGS
:
# Generate documentation with strict checks
RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs" cargo doc --no-deps --all-features
The documentation generation process creates a redirect index for easy navigation:
# Generate redirect index (automated in CI)
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L47 - L48)
Best Practices Summary
- Always use nightly toolchain - Required for hardware programming features
- Test on both targets - Ensure compatibility with hosted and bare metal environments
- Run full quality pipeline locally - Mirror CI checks before committing
- Maintain documentation standards - All public APIs must be documented
- Respect platform limitations - Understand hardware testing constraints
- Keep repository clean - Respect
.gitignore
patterns for collaborative development
Sources: .github/workflows/ci.yml(L1 - L56) .gitignore(L1 - L4)
Overview
Relevant source files
This document provides an overview of the percpu crate ecosystem, a Rust library system that provides efficient per-CPU data management for bare-metal and kernel development. The system consists of two main crates that work together to enable safe, efficient access to CPU-local data structures across multiple processor architectures.
The percpu system solves the fundamental problem of managing data that needs to be accessible per-CPU without synchronization overhead, critical for kernel and hypervisor development. For detailed API usage patterns, see Basic Usage Examples. For implementation details of the code generation pipeline, see Code Generation Pipeline.
System Architecture
The percpu ecosystem is built around two cooperating crates that provide compile-time code generation and runtime per-CPU data management:
flowchart TD subgraph subGraph4["Architecture Registers"] X86_GS["x86_64: GS_BASE"] ARM_TPIDR["AArch64: TPIDR_EL1/EL2"] RISCV_GP["RISC-V: gp"] LOONG_R21["LoongArch: $r21"] end subgraph subGraph3["Memory Layout"] PERCPU_SECTION[".percpu section"] CPU_AREAS["Per-CPU data areasCPU 0, CPU 1, ..., CPU N"] LINKER_SCRIPT["_percpu_start_percpu_end_percpu_load_start"] end subgraph subGraph2["percpu Runtime Crate"] INIT_FUNC["init() -> usize"] INIT_REG["init_percpu_reg(cpu_id)"] AREA_ALLOC["percpu_area_base()"] REG_ACCESS["read_percpu_reg()write_percpu_reg()"] end subgraph subGraph1["percpu_macros Crate"] DEF_PERCPU["def_percpu proc macro"] CODE_GEN["Architecture-specificcode generation"] GENERATED["__PERCPU_CPU_IDCPU_ID_WRAPPERassembly blocks"] end subgraph subGraph0["Application Code"] USER_VAR["#[percpu::def_percpu]static CPU_ID: usize = 0"] USER_INIT["percpu::init()"] USER_ACCESS["CPU_ID.read_current()"] end AREA_ALLOC --> CPU_AREAS ARM_TPIDR --> CPU_AREAS CODE_GEN --> GENERATED CPU_AREAS --> LINKER_SCRIPT DEF_PERCPU --> CODE_GEN GENERATED --> PERCPU_SECTION GENERATED --> REG_ACCESS INIT_FUNC --> AREA_ALLOC INIT_REG --> ARM_TPIDR INIT_REG --> LOONG_R21 INIT_REG --> RISCV_GP INIT_REG --> X86_GS LOONG_R21 --> CPU_AREAS PERCPU_SECTION --> CPU_AREAS REG_ACCESS --> ARM_TPIDR REG_ACCESS --> LOONG_R21 REG_ACCESS --> RISCV_GP REG_ACCESS --> X86_GS RISCV_GP --> CPU_AREAS USER_ACCESS --> GENERATED USER_INIT --> INIT_FUNC USER_INIT --> INIT_REG USER_VAR --> DEF_PERCPU X86_GS --> CPU_AREAS
Sources: README.md(L40 - L52) Cargo.toml(L4 - L7)
Code Generation and Runtime Flow
The system transforms user-defined per-CPU variables into architecture-specific implementations through a multi-stage process:
flowchart TD subgraph subGraph3["Access Methods"] READ_CURRENT["read_current()"] WRITE_CURRENT["write_current()"] REMOTE_PTR["remote_ptr(cpu_id)"] REMOTE_REF["remote_ref(cpu_id)"] end subgraph subGraph2["Runtime Execution"] INIT_CALL["percpu::init()"] DETECT_CPUS["detect CPU count"] ALLOC_AREAS["allocate per-CPU areas"] COPY_TEMPLATE["copy .percpu templateto each CPU area"] SET_REGISTERS["init_percpu_reg(cpu_id)per CPU"] end subgraph subGraph1["Generated Artifacts"] STORAGE_VAR["__PERCPU_VARNAMEin .percpu section"] WRAPPER_STRUCT["VARNAME_WRAPPER"] PUBLIC_STATIC["VARNAME: VARNAME_WRAPPER"] ASM_BLOCKS["Architecture-specificinline assembly"] end subgraph subGraph0["Compile Time"] ATTR["#[percpu::def_percpu]"] PARSE["syn::parse_macro_input"] ARCH_DETECT["cfg(target_arch)"] QUOTE_GEN["quote! macro expansion"] end ALLOC_AREAS --> COPY_TEMPLATE ARCH_DETECT --> QUOTE_GEN ASM_BLOCKS --> READ_CURRENT ASM_BLOCKS --> WRITE_CURRENT ATTR --> PARSE COPY_TEMPLATE --> SET_REGISTERS DETECT_CPUS --> ALLOC_AREAS INIT_CALL --> DETECT_CPUS PARSE --> ARCH_DETECT PUBLIC_STATIC --> READ_CURRENT PUBLIC_STATIC --> REMOTE_PTR PUBLIC_STATIC --> REMOTE_REF PUBLIC_STATIC --> WRITE_CURRENT QUOTE_GEN --> ASM_BLOCKS QUOTE_GEN --> PUBLIC_STATIC QUOTE_GEN --> STORAGE_VAR QUOTE_GEN --> WRAPPER_STRUCT SET_REGISTERS --> READ_CURRENT SET_REGISTERS --> WRITE_CURRENT
Sources: README.md(L39 - L52) CHANGELOG.md(L8 - L12)
Platform Support Matrix
The percpu system provides cross-platform support through architecture-specific register handling:
Architecture | Register Used | Assembly Instructions | Feature Support |
---|---|---|---|
x86_64 | GS_BASE | mov gs:[offset], reg | Full support |
AArch64 | TPIDR_EL1/EL2 | mrs/msrinstructions | EL1/EL2 modes |
RISC-V | gp | mv gp, reg | 32-bit and 64-bit |
LoongArch | $r21 | lu12i.w/ori | 64-bit support |
The system adapts its code generation based on cfg(target_arch)
directives and provides feature flags for specialized environments:
sp-naive
: Single-processor fallback using global variablespreempt
: Preemption-safe access withNoPreemptGuard
integrationarm-el2
: Hypervisor mode support usingTPIDR_EL2
Sources: README.md(L19 - L35) README.md(L69 - L79)
Integration Requirements
Linker Script Configuration
The system requires manual linker script modification to define the .percpu
section layout:
_percpu_start = .;
_percpu_end = _percpu_start + SIZEOF(.percpu);
.percpu 0x0 (NOLOAD) : AT(_percpu_start) {
_percpu_load_start = .;
*(.percpu .percpu.*)
_percpu_load_end = .;
. = _percpu_load_start + ALIGN(64) * CPU_NUM;
}
Workspace Structure
The system is organized as a Cargo workspace containing:
percpu
: Runtime implementation and APIpercpu_macros
: Procedural macro implementation
Both crates share common metadata and versioning through workspace.package
configuration.
Sources: README.md(L54 - L67) Cargo.toml(L1 - L25)
Key API Entry Points
The primary user interface consists of:
percpu::def_percpu
: Procedural macro for defining per-CPU variablespercpu::init()
: Initialize per-CPU memory areas, returns CPU countpercpu::init_percpu_reg(cpu_id)
: Configure per-CPU register for specific CPU.read_current()
/.write_current()
: Access current CPU's data.remote_ptr(cpu_id)
/.remote_ref(cpu_id)
: Access remote CPU data
The system generates wrapper types that provide these methods while maintaining type safety and architecture-specific optimization.
Sources: README.md(L40 - L52) CHANGELOG.md(L8 - L12)
System Components
Relevant source files
Purpose and Scope
This document details the architectural components of the percpu crate ecosystem, focusing on the two main crates (percpu
and percpu_macros
) and their integration with dependencies. It covers the workspace structure, feature configuration, and how the components collaborate to provide per-CPU data management functionality.
For platform-specific implementation details, see Supported Platforms. For detailed API usage, see API Reference.
Core Components Architecture
Workspace Component Structure
flowchart TD subgraph percpu_workspace["percpu Workspace"] subgraph macro_deps["Macro Dependencies"] PROC_MACRO2["proc-macro2"] SYN["syn"] QUOTE["quote"] end subgraph dependencies["Core Dependencies"] CFG_IF["cfg-if"] KERNEL_GUARD["kernel_guard(optional)"] X86_DEP["x86(x86_64 only)"] SPIN_DEP["spin(non-none OS)"] end subgraph percpu_macros_crate["percpu_macros Crate"] MACRO_LIB["lib.rsProcedural Macros"] DEF_PERCPU["def_percpu macro"] CODE_GEN["Code Generation"] end subgraph percpu_crate["percpu Crate"] PERCPU_LIB["lib.rsRuntime Implementation"] PERCPU_ACCESS["Access Methods"] PERCPU_INIT["Initialization Functions"] end end CODE_GEN --> PERCPU_INIT DEF_PERCPU --> PERCPU_ACCESS MACRO_LIB --> PROC_MACRO2 MACRO_LIB --> QUOTE MACRO_LIB --> SYN PERCPU_LIB --> CFG_IF PERCPU_LIB --> KERNEL_GUARD PERCPU_LIB --> MACRO_LIB PERCPU_LIB --> SPIN_DEP PERCPU_LIB --> X86_DEP
Sources: Cargo.toml(L4 - L7) percpu/Cargo.toml(L27 - L36) percpu_macros/Cargo.toml(L27 - L30)
Runtime Implementation (percpuCrate)
The percpu
crate provides the runtime infrastructure for per-CPU data management. It handles memory allocation, initialization, and provides safe access methods to per-CPU variables.
Key Responsibilities
Component | Function | Dependencies |
---|---|---|
Initialization | Allocates per-CPU memory areas and configures CPU registers | cfg-if, architecture-specific registers |
Access Methods | Provides safe local and remote per-CPU data access | Generated bypercpu_macros |
Memory Management | Manages per-CPU memory layout and addressing | Linker script integration |
Preemption Safety | Optional preemption-safe operations | kernel_guard(whenpreemptfeature enabled) |
Feature Configuration
The runtime crate supports three main feature flags:
sp-naive
: Single-processor mode using global variables instead of per-CPU storagepreempt
: Enables preemption safety withkernel_guard
integrationarm-el2
: ARM-specific support for EL2 privilege level execution
Sources: percpu/Cargo.toml(L15 - L25)
Macro Implementation (percpu_macrosCrate)
The percpu_macros
crate provides compile-time code generation through procedural macros. It transforms user-defined per-CPU variable declarations into architecture-specific implementation code.
Macro Processing Pipeline
flowchart TD subgraph output_components["Generated Components"] STORAGE_VAR["__PERCPU_VAR(.percpu section)"] WRAPPER_STRUCT["VarWrapper(access methods)"] PUBLIC_STATIC["VAR: VarWrapper(public interface)"] end subgraph code_generation["Code Generation"] ARCH_DETECT["Architecture Detection"] FEATURE_CHECK["Feature Flag Processing"] QUOTE_GEN["quote! Templates"] end subgraph input_parsing["Input Parsing"] USER_CODE["#[def_percpu]static VAR: Type = init;"] SYN_PARSER["syn::parse"] AST_TOKENS["TokenStream AST"] end ARCH_DETECT --> FEATURE_CHECK AST_TOKENS --> ARCH_DETECT FEATURE_CHECK --> QUOTE_GEN QUOTE_GEN --> PUBLIC_STATIC QUOTE_GEN --> STORAGE_VAR QUOTE_GEN --> WRAPPER_STRUCT SYN_PARSER --> AST_TOKENS USER_CODE --> SYN_PARSER
Macro Dependencies
Dependency | Purpose | Usage |
---|---|---|
syn | AST parsing and manipulation | Parses#[def_percpu]macro input |
quote | Code generation templates | Generates architecture-specific access code |
proc-macro2 | Token stream processing | Handles procedural macro token manipulation |
Sources: percpu_macros/Cargo.toml(L27 - L30) percpu_macros/Cargo.toml(L32 - L33)
Component Integration and Data Flow
Inter-Component Communication
flowchart TD subgraph arch_specific["Architecture-Specific"] X86_SUPPORT["x86 crate(MSR operations)"] GS_BASE["GS_BASE register"] TPIDR_REGS["TPIDR_EL1/EL2"] GP_REG["gp register (RISC-V)"] end subgraph feature_integration["Feature Integration"] SP_NAIVE_MODE["sp-naive Mode(Global Variables)"] PREEMPT_GUARD["NoPreemptGuard(kernel_guard)"] ARM_EL2_REGS["EL2 Register Access(ARM)"] end subgraph runtime_components["Runtime Components"] INIT_FUNC["percpu::init()"] CPU_AREAS["Per-CPU Memory Areas"] ACCESS_METHODS["read_current/write_current"] REMOTE_ACCESS["remote_ptr/remote_ref"] end subgraph compile_time["Compile Time"] MACRO_INPUT["User Macro Invocation"] MACRO_PROC["percpu_macros Processing"] CODE_OUTPUT["Generated Runtime Code"] end ACCESS_METHODS --> GP_REG ACCESS_METHODS --> TPIDR_REGS ACCESS_METHODS --> X86_SUPPORT CODE_OUTPUT --> ARM_EL2_REGS CODE_OUTPUT --> INIT_FUNC CODE_OUTPUT --> PREEMPT_GUARD CODE_OUTPUT --> SP_NAIVE_MODE CPU_AREAS --> ACCESS_METHODS CPU_AREAS --> REMOTE_ACCESS INIT_FUNC --> CPU_AREAS MACRO_INPUT --> MACRO_PROC MACRO_PROC --> CODE_OUTPUT X86_SUPPORT --> GS_BASE
Workspace Configuration
The workspace is configured as a multi-crate project with shared metadata and dependency management:
- Version: 0.2.0 across all crates
- Edition: Rust 2021
- License: Triple-licensed (GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0)
- Target Platforms:
no-std
embedded systems and kernel environments
Sources: Cargo.toml(L9 - L24)
Feature Flag Synchronization
Both crates maintain identical feature flag definitions to ensure consistent behavior across the macro generation and runtime execution phases:
Feature | percpuCrate | percpu_macrosCrate | Effect |
---|---|---|---|
sp-naive | Enables global variable fallback | Controls code generation mode | Single-CPU optimization |
preempt | Addskernel_guarddependency | Generates preemption-safe code | Thread safety |
arm-el2 | Architecture-specific config | Selects EL2 register usage | Hypervisor support |
This synchronization ensures that macro-generated code matches the runtime environment configuration.
Sources: percpu/Cargo.toml(L18 - L25) percpu_macros/Cargo.toml(L18 - L25)
Supported Platforms
Relevant source files
This document provides a comprehensive overview of the CPU architectures and platforms supported by the percpu crate ecosystem. It covers architecture-specific per-CPU register usage, implementation details, and platform-specific considerations for integrating per-CPU data management.
For information about setting up these platforms in your build environment, see Installation and Setup. For details about the architecture-specific code generation mechanisms, see Architecture-Specific Code Generation.
Supported Architecture Overview
The percpu crate supports four major CPU architectures, each utilizing different registers for per-CPU data access:
Architecture | per-CPU Register | Register Type | Supported Variants |
---|---|---|---|
x86_64 | GS_BASE | Segment base register | Standard x86_64 |
AArch64 | TPIDR_ELx | Thread pointer register | EL1, EL2 modes |
RISC-V | gp | Global pointer register | 32-bit, 64-bit |
LoongArch | $r21 | General purpose register | 64-bit |
Platform Register Usage
flowchart TD subgraph subGraph2["Access Methods"] X86_ASM["mov gs:[offset]"] ARM_ASM["mrs/msr instructions"] RISCV_ASM["lui/addi with gp"] LOONG_ASM["lu12i.w/ori with $r21"] end subgraph subGraph1["Per-CPU Registers"] GS["GS_BASESegment Register"] TPIDR["TPIDR_ELxThread Pointer"] GP["gpGlobal Pointer"] R21["$r21General Purpose"] end subgraph subGraph0["Architecture Support"] X86["x86_64"] ARM["AArch64"] RISCV["RISC-V"] LOONG["LoongArch"] end ARM --> TPIDR GP --> RISCV_ASM GS --> X86_ASM LOONG --> R21 R21 --> LOONG_ASM RISCV --> GP TPIDR --> ARM_ASM X86 --> GS
Sources: README.md(L19 - L31) percpu_macros/src/arch.rs(L21 - L46)
Platform-Specific Implementation Details
x86_64 Architecture
The x86_64 implementation uses the GS_BASE
model-specific register to store the base address of the per-CPU data area. This approach leverages the x86_64 segment architecture for efficient per-CPU data access.
Key characteristics:
- Uses
GS_BASE
MSR for per-CPU base address storage - Accesses data via
gs:[offset]
addressing mode - Requires offset values ≤ 0xffff_ffff for 32-bit displacement
- Supports optimized single-instruction memory operations
flowchart TD subgraph subGraph1["Generated Assembly"] MOV_READ["mov reg, gs:[offset VAR]Direct read"] MOV_WRITE["mov gs:[offset VAR], regDirect write"] ADDR_CALC["mov reg, offset VARadd reg, gs:[__PERCPU_SELF_PTR]"] end subgraph subGraph0["x86_64 Implementation"] GS_BASE["GS_BASE MSRBase Address"] OFFSET["Symbol OffsetCalculated at compile-time"] ACCESS["gs:[offset] AccessSingle instruction"] end ACCESS --> ADDR_CALC ACCESS --> MOV_READ ACCESS --> MOV_WRITE GS_BASE --> ACCESS OFFSET --> ACCESS
Sources: percpu_macros/src/arch.rs(L66 - L76) percpu_macros/src/arch.rs(L131 - L150) percpu_macros/src/arch.rs(L232 - L251)
AArch64 Architecture
The AArch64 implementation uses thread pointer registers that vary based on the exception level. The system supports both EL1 (kernel) and EL2 (hypervisor) execution environments.
Key characteristics:
- Uses
TPIDR_EL1
by default,TPIDR_EL2
whenarm-el2
feature is enabled - Requires
mrs
/msr
instructions for register access - Offset calculations limited to 16-bit immediate values (≤ 0xffff)
- Two-instruction sequence for per-CPU data access
The register selection is controlled at compile time:
flowchart TD subgraph subGraph2["Access Pattern"] MRS["mrs reg, TPIDR_ELxGet base address"] CALC["add reg, offsetCalculate final address"] ACCESS["ldr/str operationsMemory access"] end subgraph subGraph1["Register Usage"] EL1["TPIDR_EL1Kernel Mode"] EL2["TPIDR_EL2Hypervisor Mode"] end subgraph subGraph0["AArch64 Register Selection"] FEATURE["arm-el2 feature"] DEFAULT["Default Mode"] end CALC --> ACCESS DEFAULT --> EL1 EL1 --> MRS EL2 --> MRS FEATURE --> EL2 MRS --> CALC
Sources: percpu_macros/src/arch.rs(L55 - L62) percpu_macros/src/arch.rs(L79 - L80) README.md(L33 - L35)
RISC-V Architecture
The RISC-V implementation uses the global pointer (gp
) register for per-CPU data base addressing. This is a deviation from typical RISC-V conventions where gp
is used for global data access.
Key characteristics:
- Uses
gp
register instead of standard thread pointer (tp
) - Supports both 32-bit and 64-bit variants
- Uses
lui
/addi
instruction sequence for address calculation - Offset values limited to 32-bit signed immediate range
Important note: The tp
register remains available for thread-local storage, while gp
is repurposed for per-CPU data.
flowchart TD subgraph subGraph2["Memory Operations"] LOAD["ld/lw/lh/lb instructionsBased on data type"] STORE["sd/sw/sh/sb instructionsBased on data type"] end subgraph subGraph1["Address Calculation"] LUI["lui reg, %hi(VAR)Load upper immediate"] ADDI["addi reg, reg, %lo(VAR)Add lower immediate"] FINAL["add reg, reg, gpAdd base address"] end subgraph subGraph0["RISC-V Register Usage"] GP["gp registerPer-CPU base"] TP["tp registerThread-local storage"] OFFSET["Symbol offset%hi/%lo split"] end ADDI --> FINAL FINAL --> LOAD FINAL --> STORE GP --> FINAL LUI --> ADDI OFFSET --> LUI
Sources: percpu_macros/src/arch.rs(L81 - L82) percpu_macros/src/arch.rs(L33 - L39) README.md(L28 - L31)
LoongArch Architecture
The LoongArch implementation uses the $r21
general-purpose register for per-CPU data base addressing. This architecture provides native support for per-CPU data patterns.
Key characteristics:
- Uses
$r21
general-purpose register for base addressing - Supports 64-bit architecture (LoongArch64)
- Uses
lu12i.w
/ori
instruction sequence for address calculation - Provides specialized load/store indexed instructions
flowchart TD subgraph subGraph1["Instruction Sequences"] LU12I["lu12i.w reg, %abs_hi20(VAR)Load upper 20 bits"] ORI["ori reg, reg, %abs_lo12(VAR)OR lower 12 bits"] LDX["ldx.d/ldx.w/ldx.h/ldx.buLoad indexed"] STX["stx.d/stx.w/stx.h/stx.bStore indexed"] end subgraph subGraph0["LoongArch Implementation"] R21["$r21 registerPer-CPU base"] CALC["Address calculationlu12i.w + ori"] INDEXED["Indexed operationsldx/stx instructions"] end CALC --> LU12I LU12I --> ORI ORI --> LDX ORI --> STX R21 --> INDEXED
Sources: percpu_macros/src/arch.rs(L83 - L84) percpu_macros/src/arch.rs(L40 - L46) percpu_macros/src/arch.rs(L114 - L129)
Continuous Integration and Testing Coverage
The percpu crate maintains comprehensive testing across all supported platforms through automated CI/CD pipelines. The testing matrix ensures compatibility and correctness across different target environments.
CI Target Matrix:
Target | Architecture | Environment | Test Coverage |
---|---|---|---|
x86_64-unknown-linux-gnu | x86_64 | Linux userspace | Full tests + unit tests |
x86_64-unknown-none | x86_64 | Bare metal/no_std | Build + clippy only |
riscv64gc-unknown-none-elf | RISC-V 64 | Bare metal/no_std | Build + clippy only |
aarch64-unknown-none-softfloat | AArch64 | Bare metal/no_std | Build + clippy only |
loongarch64-unknown-none-softfloat | LoongArch64 | Bare metal/no_std | Build + clippy only |
Testing Strategy:
flowchart TD subgraph subGraph2["Feature Combinations"] SP_NAIVE["sp-naive featureSingle-core testing"] PREEMPT["preempt featurePreemption safety"] ARM_EL2["arm-el2 featureHypervisor mode"] end subgraph subGraph1["Test Types"] BUILD["Build TestsAll targets"] UNIT["Unit Testsx86_64 Linux only"] LINT["Code QualityAll targets"] end subgraph subGraph0["CI Pipeline"] MATRIX["Target Matrix5 platforms tested"] FEATURES["Feature Testingpreempt, arm-el2"] TOOLS["Quality Toolsclippy, rustfmt"] end BUILD --> ARM_EL2 BUILD --> PREEMPT BUILD --> SP_NAIVE FEATURES --> ARM_EL2 FEATURES --> PREEMPT FEATURES --> SP_NAIVE MATRIX --> BUILD MATRIX --> LINT MATRIX --> UNIT
Sources: .github/workflows/ci.yml(L8 - L32)
Platform-Specific Limitations and Considerations
macOS Development Limitation
The crate includes a development-time limitation for macOS hosts, where architecture-specific assembly code is disabled and replaced with unimplemented stubs. This affects local development but not target deployment.
Offset Size Constraints
Each architecture imposes different constraints on the maximum offset values for per-CPU variables:
- x86_64: 32-bit signed displacement (≤ 0xffff_ffff)
- AArch64: 16-bit immediate value (≤ 0xffff)
- RISC-V: 32-bit signed immediate (split hi/lo)
- LoongArch: 32-bit absolute address (split hi20/lo12)
Register Conflicts and Conventions
- RISC-V: Repurposes
gp
register, deviating from standard ABI conventions - AArch64: Requires careful exception level management for register selection
- x86_64: Depends on GS segment register configuration by system initialization
Sources: percpu_macros/src/arch.rs(L4 - L13) percpu_macros/src/arch.rs(L23 - L46) README.md(L28 - L35)
Getting Started
Relevant source files
This page provides a step-by-step guide to integrating and using the percpu crate for per-CPU data management in your projects. It covers the essential workflow from dependency setup through basic usage patterns, serving as an entry point for developers new to the percpu system.
For detailed installation instructions and build environment configuration, see Installation and Setup. For comprehensive examples and advanced usage patterns, see Basic Usage Examples. For information about configuring features for different deployment scenarios, see Feature Flags Configuration.
Integration Workflow Overview
The percpu crate requires coordination between compile-time code generation, linker script configuration, and runtime initialization. The following diagram illustrates the complete integration workflow:
flowchart TD subgraph subGraph2["Generated Artifacts"] SECTION[".percpu sectionin binary"] WRAPPER["Generated wrapper structswith access methods"] AREAS["Per-CPU memory areasat runtime"] end subgraph subGraph1["Code Development"] DEFINE["Define per-CPU variables#[percpu::def_percpu]"] INIT["Initialize systempercpu::init()"] SETUP["Configure per-CPU registerspercpu::init_percpu_reg()"] ACCESS["Access variablesread_current(), write_current()"] end subgraph subGraph0["Build Configuration"] CARGO["Add percpu dependencyto Cargo.toml"] FEATURES["Configure feature flagssp-naive, preempt, arm-el2"] LINKER["Modify linker scriptAdd .percpu section"] end START["Start Integration"] AREAS --> ACCESS CARGO --> FEATURES DEFINE --> SECTION DEFINE --> WRAPPER FEATURES --> LINKER INIT --> AREAS LINKER --> DEFINE SECTION --> AREAS SETUP --> ACCESS START --> CARGO WRAPPER --> ACCESS
Sources: README.md(L39 - L52) percpu/tests/test_percpu.rs(L35 - L50)
Essential Components and Their Relationships
The percpu system consists of several key components that work together to provide efficient per-CPU data access. Understanding these relationships is crucial for proper integration:
flowchart TD subgraph subGraph3["Linker Script Space"] SCRIPT["_percpu_start_percpu_end_percpu_load_start"] SECTION[".percpu 0x0 (NOLOAD)section definition"] end subgraph subGraph2["Runtime System Space"] AREAS["percpu_area_base()memory allocation"] REGISTER["read_percpu_reg()write_percpu_reg()"] OFFSETS["CPU_ID.offset()address calculation"] end subgraph subGraph1["Generated Code Space"] STORAGE["__PERCPU_CPU_IDin .percpu section"] WRAPPER["CPU_ID_WRAPPERaccess methods"] INTERFACE["CPU_ID: CPU_ID_WRAPPERpublic interface"] end subgraph subGraph0["User Code Space"] USRVAR["#[percpu::def_percpu]static CPU_ID: usize = 0"] USRINIT["percpu::init()"] USRSETUP["percpu::init_percpu_reg(0)"] USRACCESS["CPU_ID.read_current()"] end AREAS --> SCRIPT INTERFACE --> USRACCESS SCRIPT --> SECTION STORAGE --> SCRIPT USRACCESS --> OFFSETS USRACCESS --> REGISTER USRINIT --> AREAS USRSETUP --> REGISTER USRVAR --> STORAGE USRVAR --> WRAPPER WRAPPER --> INTERFACE
Sources: README.md(L40 - L51) percpu/tests/test_percpu.rs(L7 - L31) percpu/test_percpu.x(L6 - L14)
Quick Start Example
The following table summarizes the minimal steps required to integrate percpu into a project:
Step | Action | Code Example | Purpose |
---|---|---|---|
1 | Add dependency | percpu = "0.1"in Cargo.toml | Enable percpu macros and runtime |
2 | Configure linker | Add.percpusection to linker script | Reserve memory for per-CPU data |
3 | Define variables | #[percpu::def_percpu] static VAR: Type = init; | Declare per-CPU storage |
4 | Initialize system | percpu::init(); | Allocate per-CPU memory areas |
5 | Setup registers | percpu::init_percpu_reg(cpu_id); | Configure architecture registers |
6 | Access data | VAR.read_current(),VAR.write_current(value) | Use per-CPU variables |
Here's a complete minimal example demonstrating the basic workflow:
// In your source code #[percpu::def_percpu] static CPU_COUNTER: usize = 0; fn main() { // Initialize the per-CPU system percpu::init(); // Configure per-CPU register for CPU 0 percpu::init_percpu_reg(0); // Access per-CPU data println!("Initial value: {}", CPU_COUNTER.read_current()); // prints "0" CPU_COUNTER.write_current(42); println!("Updated value: {}", CPU_COUNTER.read_current()); // prints "42" }
And the corresponding linker script addition:
. = ALIGN(4K);
_percpu_start = .;
_percpu_end = _percpu_start + SIZEOF(.percpu);
.percpu 0x0 (NOLOAD) : AT(_percpu_start) {
_percpu_load_start = .;
*(.percpu .percpu.*)
_percpu_load_end = .;
. = _percpu_load_start + ALIGN(64) * CPU_NUM;
}
. = _percpu_end;
Sources: README.md(L39 - L51) README.md(L54 - L67) percpu/test_percpu.x(L1 - L16)
Architecture-Specific Considerations
The percpu system supports multiple architectures with different per-CPU register mechanisms. The following table shows the supported platforms and their register usage:
Architecture | Per-CPU Register | Usage Notes |
---|---|---|
x86_64 | GS_BASE | Standard x86_64 per-CPU mechanism |
AArch64 | TPIDR_EL1/TPIDR_EL2 | EL1 by default, EL2 witharm-el2feature |
RISC-V | gp | Global pointer register (non-standard usage) |
LoongArch | $r21 | Architecture-specific register |
The system automatically generates appropriate assembly code for each architecture during macro expansion. No manual architecture-specific code is required in user applications.
Sources: README.md(L19 - L35)
Feature Configuration Overview
The percpu crate provides several feature flags to adapt to different deployment scenarios:
sp-naive
: Single-processor mode using global variables instead of per-CPU mechanismspreempt
: Preemption-safe mode with automatic guard integrationarm-el2
: Hypervisor mode support for AArch64 using EL2 registers
Choose features based on your target environment. For single-core systems, use sp-naive
. For preemptible kernels, enable preempt
. For ARM hypervisors, add arm-el2
.
Sources: README.md(L69 - L79)
Next Steps
After completing the basic integration:
- Detailed Setup: See Installation and Setup for comprehensive dependency management, build configuration, and linker script integration
- Advanced Usage: See Basic Usage Examples for complex data types, remote CPU access, and safety patterns
- Feature Configuration: See Feature Flags Configuration for choosing appropriate features for your deployment scenario
- Architecture Details: See Cross-Platform Abstraction for understanding platform-specific implementation details
- Safety Guidelines: See Safety and Preemption for proper usage patterns and avoiding common pitfalls
Sources: README.md percpu/tests/test_percpu.rs
Installation and Setup
Relevant source files
This document covers the installation and configuration requirements for integrating the percpu crate into your project. It focuses on dependency management, linker script configuration, and build system setup. For detailed usage examples, see Basic Usage Examples. For feature flag details, see Feature Flags Configuration. For understanding the underlying architecture, see Memory Layout and Initialization.
Overview of Setup Requirements
The percpu crate requires several configuration steps beyond typical Rust dependency management due to its low-level nature and custom memory layout requirements.
flowchart TD CARGO["Add percpu to Cargo.toml"] LINKER["Configure Linker Script"] FEATURES["Select Feature Flags"] BUILD["Build System Integration"] INIT["Runtime Initialization"] DEPS["Dependencies Added"] MEMORY["Memory Layout"] CONFIG["Platform Config"] COMPILE["Compilation Setup"] RUNTIME["Runtime Ready"] BUILD --> COMPILE BUILD --> INIT CARGO --> DEPS CARGO --> LINKER FEATURES --> BUILD FEATURES --> CONFIG INIT --> RUNTIME LINKER --> FEATURES LINKER --> MEMORY
Setup Flow: Each step depends on the previous ones being completed correctly
Sources: README.md(L1 - L80)
Adding Dependencies
Add the percpu crate to your Cargo.toml
dependencies section:
[dependencies]
percpu = "0.1"
percpu_macros = "0.1" # Optional: only if using macros directly
The core percpu
crate provides runtime functionality, while percpu_macros
contains the procedural macros (automatically included by the main crate).
Crate | Purpose | Required |
---|---|---|
percpu | Runtime implementation, initialization functions | Yes |
percpu_macros | def_percpumacro implementation | Included automatically |
Sources: README.md(L3 - L4)
Linker Script Configuration
The percpu crate requires manual linker script modification to create the .percpu
section. This is a mandatory step that cannot be automated.
Required Linker Script Sections
Add these sections to your linker script before the end of your existing sections:
flowchart TD ALIGN["ALIGN(4K)"] START["_percpu_start = ."] END["_percpu_end = _percpu_start + SIZEOF(.percpu)"] SECTION[".percpu 0x0 (NOLOAD) : AT(_percpu_start)"] LOAD_START["_percpu_load_start = ."] CONTENT["(.percpu .percpu.)"] LOAD_END["_percpu_load_end = ."] SIZE[". = _percpu_load_start + ALIGN(64) * CPU_NUM"] FINAL[". = _percpu_end"] ALIGN --> START CONTENT --> LOAD_END END --> SECTION LOAD_END --> SIZE LOAD_START --> CONTENT SECTION --> LOAD_START SIZE --> FINAL START --> END
Linker Script Memory Layout: The .percpu
section layout with required symbols
The complete linker script addition from percpu/test_percpu.x(L3 - L16) :
_percpu_start
: Marks the beginning of the per-CPU template area_percpu_end
: Marks the end of all per-CPU areas_percpu_load_start
/_percpu_load_end
: Template data boundariesCPU_NUM
: Must be defined as the maximum number of CPUsALIGN(64)
: Ensures proper cache line alignment for each CPU area
Example Integration
For a complete example, see the test linker script structure in percpu/test_percpu.x(L1 - L17) :
CPU_NUM = 4;
SECTIONS {
/* Your existing sections */
. = ALIGN(4K);
_percpu_start = .;
_percpu_end = _percpu_start + SIZEOF(.percpu);
.percpu 0x0 (NOLOAD) : AT(_percpu_start) {
_percpu_load_start = .;
*(.percpu .percpu.*)
_percpu_load_end = .;
. = _percpu_load_start + ALIGN(64) * CPU_NUM;
}
. = _percpu_end;
}
INSERT AFTER .bss;
Sources: README.md(L54 - L67) percpu/test_percpu.x(L1 - L17)
Feature Flags Configuration
Configure feature flags in Cargo.toml
based on your target environment:
Feature | Use Case | Effect |
---|---|---|
sp-naive | Single-core systems | Uses global variables instead of per-CPU registers |
preempt | Preemptible kernels | Integrates withkernel_guardfor atomic operations |
arm-el2 | ARM hypervisors | UsesTPIDR_EL2instead ofTPIDR_EL1 |
Feature Selection Matrix
Feature Selection Logic: Choose features based on your runtime environment
Example Configurations
# Single-core embedded system
[dependencies]
percpu = { version = "0.1", features = ["sp-naive"] }
# Multi-core preemptible kernel
[dependencies]
percpu = { version = "0.1", features = ["preempt"] }
# ARM hypervisor at EL2
[dependencies]
percpu = { version = "0.1", features = ["arm-el2"] }
# ARM hypervisor with preemption
[dependencies]
percpu = { version = "0.1", features = ["preempt", "arm-el2"] }
Sources: README.md(L69 - L79)
Build System Integration
Conditional Build Configuration
The build system may require additional configuration for certain targets. The percpu/build.rs(L1 - L9) shows platform-specific build requirements:
Build Configuration Logic: Platform-specific build steps for Linux targets
Required Build Steps
For Linux targets without sp-naive
feature:
-no-pie
: Disables position-independent executable-T{script}
: Specifies custom linker script path
The build system automatically handles:
- Linker script path resolution
- Test-specific linker arguments
- Feature flag conditional compilation
Sources: percpu/build.rs(L1 - L9)
Runtime Initialization
After build configuration, initialize the percpu system at runtime:
flowchart TD START["Program Start"] INIT["percpu::init()"] REG_INIT["percpu::init_percpu_reg(cpu_id)"] ACCESS["Per-CPU data access ready"] AREAS["Per-CPU memory areas"] TEMPLATE[".percpu section template"] REGISTERS["Architecture-specific registers"] X86["x86_64: GS_BASE"] ARM["AArch64: TPIDR_ELx"] RISCV["RISC-V: gp"] LOONG["LoongArch: $r21"] INIT --> AREAS INIT --> REG_INIT INIT --> TEMPLATE REGISTERS --> ARM REGISTERS --> LOONG REGISTERS --> RISCV REGISTERS --> X86 REG_INIT --> ACCESS REG_INIT --> REGISTERS START --> INIT
Initialization Sequence: Runtime setup steps for per-CPU data access
Initialization Functions
Function | Purpose | When to Call |
---|---|---|
percpu::init() | Allocate and initialize per-CPU areas | Once during system startup |
percpu::init_percpu_reg(cpu_id) | Configure per-CPU register | Once per CPU during CPU initialization |
Basic Initialization Example
// System initialization
percpu::init();
// Per-CPU initialization (call for each CPU)
for cpu_id in 0..num_cpus {
// Switch to CPU context, then:
percpu::init_percpu_reg(cpu_id);
}
Sources: README.md(L43 - L46)
Verification
Verify your setup by testing basic per-CPU variable access:
#[percpu::def_percpu]
static TEST_VAR: usize = 42;
// After initialization
assert_eq!(TEST_VAR.read_current(), 42);
TEST_VAR.write_current(100);
assert_eq!(TEST_VAR.read_current(), 100);
Common Setup Issues
Issue | Symptom | Solution |
---|---|---|
Missing linker script | Link errors about.percpusection | Add required linker script sections |
Wrong feature flags | Runtime panics or incorrect behavior | Review target environment and feature selection |
Missing initialization | Segmentation faults on access | Callpercpu::init()before any per-CPU access |
Register not set | Wrong CPU data accessed | Callpercpu::init_percpu_reg()for each CPU |
Sources: README.md(L39 - L52)
Basic Usage Examples
Relevant source files
This section demonstrates the fundamental patterns for defining and accessing per-CPU variables using the def_percpu
macro. It covers the essential initialization sequence, current CPU data access, and remote CPU data manipulation techniques. For detailed information about system architecture and memory layout, see Memory Layout and Initialization. For comprehensive API documentation, see def_percpu Macro.
Variable Definition and Initialization
The core workflow begins with defining per-CPU variables using the #[def_percpu]
attribute macro, followed by system initialization to set up memory areas and CPU registers.
Basic Variable Definition
Per-CPU variables are defined using the #[percpu::def_percpu]
attribute on static variables. The macro supports various primitive types and custom structures:
#[percpu::def_percpu]
static CPU_ID: usize = 0;
#[percpu::def_percpu]
static COUNTER: u64 = 0;
#[percpu::def_percpu]
static FLAG: bool = false;
Sources: README.md(L40 - L41) percpu/tests/test_percpu.rs(L7 - L23)
System Initialization Sequence
Before accessing per-CPU data, the system must be initialized with percpu::init()
and per-CPU registers configured with percpu::init_percpu_reg()
:
// Initialize per-CPU data areas
percpu::init();
// Set the thread pointer register to per-CPU data area 0
percpu::init_percpu_reg(0);
Generated Code Structure
flowchart TD subgraph subGraph2["Generated Methods"] OFFSET["offset() -> usize"] CURRENT_PTR["current_ptr() -> *const T"] READ["read_current() -> T"] WRITE["write_current(val: T)"] WITH["with_current(f: F)"] REMOTE_PTR["remote_ptr(cpu_id) -> *const T"] end subgraph subGraph1["Generated by def_percpu Macro"] INNER["__PERCPU_CPU_IDin .percpu section"] WRAPPER["CPU_ID_WRAPPERstruct with methods"] PUBLIC["CPU_ID: CPU_ID_WRAPPERpublic static instance"] end subgraph subGraph0["User Code"] USERVAR["#[def_percpu]static CPU_ID: usize = 0"] end USERVAR --> INNER USERVAR --> PUBLIC USERVAR --> WRAPPER WRAPPER --> CURRENT_PTR WRAPPER --> OFFSET WRAPPER --> READ WRAPPER --> REMOTE_PTR WRAPPER --> WITH WRAPPER --> WRITE
Sources: percpu_macros/src/lib.rs(L88 - L89) percpu_macros/src/lib.rs(L149 - L159) percpu_macros/src/lib.rs(L161 - L249)
Current CPU Data Access
The generated wrapper provides several methods for accessing per-CPU data on the current CPU. For primitive integer types, direct read/write methods are available.
Direct Read/Write Operations
For primitive types (bool
, u8
, u16
, u32
, u64
, usize
), the macro generates optimized read and write methods:
// Reading current CPU values
let value = CPU_ID.read_current();
let flag_state = FLAG.read_current();
// Writing to current CPU
CPU_ID.write_current(1);
FLAG.write_current(true);
COUNTER.write_current(0xdead_beef);
Sources: README.md(L48 - L51) percpu/tests/test_percpu.rs(L71 - L77) percpu_macros/src/lib.rs(L129 - L139)
Safe Reference-Based Access
For complex data types and when multiple operations are needed, use the with_current()
method which provides safe mutable access:
#[percpu::def_percpu]
static STATS: Struct = Struct { foo: 0, bar: 0 };
// Safe manipulation with automatic preemption handling
STATS.with_current(|stats| {
stats.foo = 0x2333;
stats.bar = 100;
});
Sources: percpu/tests/test_percpu.rs(L78 - L81) percpu_macros/src/lib.rs(L201 - L207)
Unsafe Raw Access
For performance-critical scenarios where preemption is manually managed, raw access methods are available:
unsafe {
// Caller must ensure preemption is disabled
let ptr = CPU_ID.current_ptr();
let value = CPU_ID.read_current_raw();
CPU_ID.write_current_raw(42);
}
Sources: percpu_macros/src/lib.rs(L113 - L125) percpu_macros/src/lib.rs(L174 - L197)
Remote CPU Data Access
The system supports accessing per-CPU data from other CPUs using CPU ID-based addressing. This is essential for cross-CPU coordination and debugging.
Remote Pointer Access
unsafe {
// Access data on CPU 1 from any CPU
let remote_ptr = CPU_ID.remote_ptr(1);
let remote_value = *remote_ptr;
// Modify remote CPU data
let remote_mut_ref = COUNTER.remote_ref_mut_raw(1);
*remote_mut_ref = 0xfeed_feed_feed_feed;
}
Sources: percpu/tests/test_percpu.rs(L110 - L122) percpu_macros/src/lib.rs(L217 - L246)
CPU Context Switching
The per-CPU register can be updated to change the current CPU context:
unsafe {
// Switch to CPU 1's context
percpu::write_percpu_reg(percpu::percpu_area_base(1));
// Now read_current() accesses CPU 1's data
let cpu1_value = CPU_ID.read_current();
}
Sources: percpu/tests/test_percpu.rs(L139 - L154)
Data Type Support Matrix
The def_percpu
macro handles different data types with varying levels of optimization:
Data Type | Direct Read/Write | with_current() | Remote Access | Notes |
---|---|---|---|---|
bool | ✓ | ✓ | ✓ | Optimized methods generated |
u8,u16,u32 | ✓ | ✓ | ✓ | Optimized methods generated |
u64,usize | ✓ | ✓ | ✓ | Optimized methods generated |
Custom structs | ✗ | ✓ | ✓ | Reference-based access only |
Arrays | ✗ | ✓ | ✓ | Reference-based access only |
Per-CPU Data Type Access Patterns
flowchart TD subgraph subGraph2["Access Methods"] DIRECT["read_current()write_current()"] WITH["with_current(|data| ...)"] RAW["current_ptr()current_ref_raw()"] REMOTE["remote_ptr(cpu_id)remote_ref_raw(cpu_id)"] end subgraph subGraph1["Complex Types"] COMPLEX["Custom structsArrays, etc."] end subgraph subGraph0["Primitive Types"] PRIM["bool, u8, u16, u32u64, usize"] end COMPLEX --> DIRECT COMPLEX --> RAW COMPLEX --> REMOTE COMPLEX --> WITH PRIM --> DIRECT PRIM --> RAW PRIM --> REMOTE PRIM --> WITH
Sources: percpu_macros/src/lib.rs(L91 - L93) percpu_macros/src/lib.rs(L100 - L145) percpu/tests/test_percpu.rs(L25 - L31)
Understanding Generated Methods
Each per-CPU variable generates a wrapper struct with a comprehensive set of access methods:
Core Methods (All Types)
offset() -> usize
- Returns variable offset within per-CPU areacurrent_ptr() -> *const T
- Raw pointer to current CPU's datawith_current<F>(f: F)
- Safe closure-based access with preemption handlingremote_ptr(cpu_id: usize) -> *const T
- Raw pointer to specified CPU's data
Primitive Type Methods
For bool
, u8
, u16
, u32
, u64
, usize
only:
read_current() -> T
- Direct value read with preemption handlingwrite_current(val: T)
- Direct value write with preemption handlingread_current_raw() -> T
- Unsafe direct read (no preemption handling)write_current_raw(val: T)
- Unsafe direct write (no preemption handling)
Feature-Dependent Behavior
The generated code adapts based on enabled cargo features:
preempt
feature: AddsNoPreemptGuard
to safe methodssp-naive
feature: Uses global variables instead of per-CPU registersarm-el2
feature: UsesTPIDR_EL2
instead ofTPIDR_EL1
on AArch64
Sources: percpu_macros/src/lib.rs(L94 - L98) README.md(L69 - L79)
Complete Usage Example
This example demonstrates the full workflow from definition to access:
use percpu::*; // Define various per-CPU variables #[def_percpu] static CPU_ID: usize = 0; #[def_percpu] static COUNTER: u64 = 0; struct CpuStats { interrupts: u64, context_switches: u64, } #[def_percpu] static STATS: CpuStats = CpuStats { interrupts: 0, context_switches: 0, }; fn main() { // Initialize the per-CPU system percpu::init(); percpu::init_percpu_reg(0); // Access current CPU data CPU_ID.write_current(0); COUNTER.write_current(100); STATS.with_current(|stats| { stats.interrupts += 1; stats.context_switches += 1; }); // Read values println!("CPU ID: {}", CPU_ID.read_current()); println!("Counter: {}", COUNTER.read_current()); // Access remote CPU data (unsafe) unsafe { *COUNTER.remote_ref_mut_raw(1) = 200; println!("CPU 1 counter: {}", *COUNTER.remote_ptr(1)); } }
Sources: percpu/tests/test_percpu.rs(L34 - L105) README.md(L39 - L52)
Feature Flags Configuration
Relevant source files
This document explains how to configure the percpu crate's feature flags to optimize per-CPU data management for different system architectures and use cases. It covers the three main feature flags (sp-naive
, preempt
, arm-el2
) and their interaction with dependencies and code generation.
For basic usage examples and integration steps, see Basic Usage Examples. For detailed implementation specifics of each feature, see Naive Implementation.
Overview of Feature Flags
The percpu crate provides three optional features that adapt the per-CPU data system to different operational environments and architectural requirements.
Feature Flag Architecture
flowchart TD subgraph subGraph2["Dependencies Activated"] KERNEL_GUARD["kernel_guard cratePreemption control"] MACRO_FLAGS["percpu_macros featuresCode generation control"] end subgraph subGraph1["Generated Code Variants"] MULTI_CPU["Multi-CPU ImplementationArchitecture-specific registers"] GLOBAL_VAR["Global Variable ImplementationNo per-CPU isolation"] GUARDED["Preemption-Safe ImplementationNoPreemptGuard wrapper"] EL2_IMPL["EL2 Register ImplementationTPIDR_EL2 usage"] end subgraph subGraph0["Feature Configuration"] DEFAULT["default = []Standard multi-CPU mode"] SP_NAIVE["sp-naiveSingle-core fallback"] PREEMPT["preemptPreemption safety"] ARM_EL2["arm-el2Hypervisor mode"] end ARM_EL2 --> EL2_IMPL ARM_EL2 --> MACRO_FLAGS DEFAULT --> MULTI_CPU PREEMPT --> GUARDED PREEMPT --> KERNEL_GUARD PREEMPT --> MACRO_FLAGS SP_NAIVE --> GLOBAL_VAR SP_NAIVE --> MACRO_FLAGS
Sources: percpu/Cargo.toml(L15 - L25) percpu_macros/Cargo.toml(L15 - L25) README.md(L69 - L79)
Feature Flag Descriptions
sp-naive Feature
The sp-naive
feature transforms per-CPU variables into standard global variables, eliminating per-CPU isolation for single-core systems.
Aspect | Standard Mode | sp-naive Mode |
---|---|---|
Variable Storage | Per-CPU memory areas | Global variables |
Register Usage | Architecture-specific (GS_BASE, TPIDR_EL1, etc.) | None |
Memory Layout | .percpu section with CPU multiplication | Standard .data/.bss sections |
Access Method | CPU register + offset calculation | Direct global access |
Initialization | percpu::init()andpercpu::init_percpu_reg() | Standard global initialization |
Configuration:
[dependencies]
percpu = { version = "0.2", features = ["sp-naive"] }
Sources: percpu/Cargo.toml(L18 - L19) percpu_macros/Cargo.toml(L18 - L19) README.md(L71 - L73)
preempt Feature
The preempt
feature enables preemption safety by integrating with the kernel_guard
crate to prevent data corruption during per-CPU access.
Feature Dependencies Chain
flowchart TD subgraph subGraph3["Generated Access Methods"] READ_CURRENT["read_current()Preemption-safe read"] WRITE_CURRENT["write_current()Preemption-safe write"] NO_PREEMPT_GUARD["NoPreemptGuardRAII guard"] end subgraph subGraph2["percpu_macros Crate"] MACROS_PREEMPT["preempt featurepercpu_macros/Cargo.toml:22"] CODE_GEN["Code GenerationNoPreemptGuard integration"] end subgraph subGraph1["percpu Crate"] PERCPU_PREEMPT["preempt featurepercpu/Cargo.toml:22"] KERNEL_GUARD_DEP["kernel_guard dependencyConditionally enabled"] end subgraph subGraph0["User Configuration"] USER_TOML["Cargo.tomlfeatures = ['preempt']"] end CODE_GEN --> NO_PREEMPT_GUARD CODE_GEN --> READ_CURRENT CODE_GEN --> WRITE_CURRENT KERNEL_GUARD_DEP --> NO_PREEMPT_GUARD MACROS_PREEMPT --> CODE_GEN PERCPU_PREEMPT --> KERNEL_GUARD_DEP PERCPU_PREEMPT --> MACROS_PREEMPT USER_TOML --> PERCPU_PREEMPT
Configuration:
[dependencies]
percpu = { version = "0.2", features = ["preempt"] }
Sources: percpu/Cargo.toml(L21 - L22) percpu_macros/Cargo.toml(L21 - L22) README.md(L74 - L76)
arm-el2 Feature
The arm-el2
feature configures AArch64 systems to use the EL2 (Exception Level 2) thread pointer register for hypervisor environments.
Register Usage | Default (arm-el2 disabled) | arm-el2 enabled |
---|---|---|
AArch64 Register | TPIDR_EL1 | TPIDR_EL2 |
Privilege Level | EL1 (kernel mode) | EL2 (hypervisor mode) |
Use Case | Standard kernel | Hypervisor/VMM |
Assembly Instructions | mrs/msrwithTPIDR_EL1 | mrs/msrwithTPIDR_EL2 |
Configuration:
[dependencies]
percpu = { version = "0.2", features = ["arm-el2"] }
Sources: percpu/Cargo.toml(L24 - L25) percpu_macros/Cargo.toml(L24 - L25) README.md(L33 - L35) README.md(L77 - L79)
Feature Combination Matrix
Compatible Feature Combinations
flowchart TD subgraph subGraph1["Architecture Applicability"] X86_COMPAT["x86_64 CompatibleGS_BASE register"] ARM_COMPAT["AArch64 CompatibleTPIDR_ELx registers"] RISCV_COMPAT["RISC-V Compatiblegp register"] LOONG_COMPAT["LoongArch Compatible$r21 register"] end subgraph subGraph0["Valid Configurations"] DEFAULT_ONLY["DefaultMulti-CPU, no preemption"] SP_NAIVE_ONLY["sp-naiveSingle-core system"] PREEMPT_ONLY["preemptMulti-CPU with preemption"] ARM_EL2_ONLY["arm-el2AArch64 hypervisor"] PREEMPT_ARM["preempt + arm-el2Hypervisor with preemption"] SP_NAIVE_PREEMPT["sp-naive + preemptSingle-core with preemption"] SP_NAIVE_ARM["sp-naive + arm-el2Single-core hypervisor"] ALL_FEATURES["sp-naive + preempt + arm-el2Single-core hypervisor with preemption"] end ARM_EL2_ONLY --> ARM_COMPAT DEFAULT_ONLY --> ARM_COMPAT DEFAULT_ONLY --> LOONG_COMPAT DEFAULT_ONLY --> RISCV_COMPAT DEFAULT_ONLY --> X86_COMPAT PREEMPT_ARM --> ARM_COMPAT PREEMPT_ONLY --> ARM_COMPAT PREEMPT_ONLY --> LOONG_COMPAT PREEMPT_ONLY --> RISCV_COMPAT PREEMPT_ONLY --> X86_COMPAT SP_NAIVE_ONLY --> ARM_COMPAT SP_NAIVE_ONLY --> LOONG_COMPAT SP_NAIVE_ONLY --> RISCV_COMPAT SP_NAIVE_ONLY --> X86_COMPAT
Sources: percpu/Cargo.toml(L15 - L25) percpu_macros/Cargo.toml(L15 - L25)
Configuration Examples
Bare Metal Single-Core System
For embedded systems or single-core environments:
[dependencies]
percpu = { version = "0.2", features = ["sp-naive"] }
This configuration eliminates per-CPU overhead and uses simple global variables.
Multi-Core Preemptible Kernel
For operating system kernels with preemptive scheduling:
[dependencies]
percpu = { version = "0.2", features = ["preempt"] }
This enables preemption safety with NoPreemptGuard
wrappers around access operations.
AArch64 Hypervisor
For hypervisors running at EL2 privilege level:
[dependencies]
percpu = { version = "0.2", features = ["arm-el2"] }
This uses TPIDR_EL2
instead of TPIDR_EL1
for per-CPU base address storage.
Multi-Core Hypervisor with Preemption
For complex hypervisor environments:
[dependencies]
percpu = { version = "0.2", features = ["preempt", "arm-el2"] }
This combines EL2 register usage with preemption safety mechanisms.
Conditional Compilation Impact
Feature-Dependent Code Paths
flowchart TD subgraph subGraph2["Generated Code Variants"] GLOBAL_IMPL["Global Variable ImplementationDirect access"] PERCPU_IMPL["Per-CPU ImplementationRegister-based access"] GUARDED_IMPL["Guarded ImplementationNoPreemptGuard wrapper"] EL2_IMPL["EL2 ImplementationTPIDR_EL2 usage"] end subgraph subGraph1["Feature Detection"] CFG_SP_NAIVE["#[cfg(feature = 'sp-naive')]"] CFG_PREEMPT["#[cfg(feature = 'preempt')]"] CFG_ARM_EL2["#[cfg(feature = 'arm-el2')]"] end subgraph subGraph0["Macro Expansion"] DEF_PERCPU["#[def_percpu]static VAR: T = init;"] end CFG_ARM_EL2 --> EL2_IMPL CFG_PREEMPT --> GUARDED_IMPL CFG_SP_NAIVE --> GLOBAL_IMPL CFG_SP_NAIVE --> PERCPU_IMPL DEF_PERCPU --> CFG_ARM_EL2 DEF_PERCPU --> CFG_PREEMPT DEF_PERCPU --> CFG_SP_NAIVE PERCPU_IMPL --> EL2_IMPL PERCPU_IMPL --> GUARDED_IMPL
Sources: percpu_macros/Cargo.toml(L15 - L25)
Architecture-Specific Considerations
x86_64 Systems
All feature combinations are supported. The arm-el2
feature has no effect on x86_64 targets.
Register Used: GS_BASE
(unless sp-naive
is enabled)
AArch64 Systems
The arm-el2
feature specifically affects AArch64 compilation:
- Default: Uses
TPIDR_EL1
register - With arm-el2: Uses
TPIDR_EL2
register
RISC-V Systems
The arm-el2
feature has no effect. Uses gp
register for per-CPU base address.
LoongArch Systems
The arm-el2
feature has no effect. Uses $r21
register for per-CPU base address.
Sources: README.md(L19 - L36)
Dependencies and Build Impact
Conditional Dependencies
The feature flags control which dependencies are included in the build:
Feature | Additional Dependencies | Purpose |
---|---|---|
sp-naive | None | Simplifies to global variables |
preempt | kernel_guard = "0.1" | ProvidesNoPreemptGuard |
arm-el2 | None | Only affects macro code generation |
Build Optimization
When sp-naive
is enabled, the generated code is significantly simpler and may result in better optimization for single-core systems since it eliminates the per-CPU indirection overhead.
Sources: percpu/Cargo.toml(L27 - L36)
Architecture and Design
Relevant source files
This document provides a comprehensive overview of the percpu crate's system architecture, including memory layout strategies, cross-platform abstraction mechanisms, and the compile-time code generation pipeline. It focuses on the core design principles that enable efficient per-CPU data management across multiple architectures while maintaining a unified programming interface.
For implementation-specific details of individual architectures, see Architecture-Specific Code Generation. For basic usage patterns and examples, see Getting Started.
System Architecture Overview
The percpu system is built around a dual-crate architecture that separates compile-time code generation from runtime memory management. This design enables both high-performance per-CPU data access and flexible cross-platform support.
Core Architecture
flowchart TD subgraph subGraph4["Hardware Registers"] X86_GS["x86_64: GS_BASE"] ARM_TPIDR["AArch64: TPIDR_ELx"] RISCV_GP["RISC-V: gp"] LOONG_R21["LoongArch: $r21"] end subgraph subGraph3["Memory Layout"] PERCPU_SECTION[".percpu section"] AREA_0["CPU 0 Data Area"] AREA_N["CPU N Data Area"] end subgraph subGraph2["percpu Runtime Crate"] INIT_FUNC["init()"] AREA_BASE["percpu_area_base()"] REG_FUNCS["read_percpu_reg()/write_percpu_reg()"] INIT_REG["init_percpu_reg()"] end subgraph subGraph1["percpu_macros Crate"] DEF_PERCPU["def_percpu macro"] ARCH_GEN["arch::gen_current_ptr()"] SYMBOL_OFFSET["percpu_symbol_offset!"] end subgraph subGraph0["User Code Layer"] USER_VAR["#[def_percpu] static VAR: T"] USER_ACCESS["VAR.read_current()"] USER_INIT["percpu::init()"] end ARCH_GEN --> REG_FUNCS DEF_PERCPU --> PERCPU_SECTION INIT_FUNC --> AREA_0 INIT_FUNC --> AREA_BASE INIT_FUNC --> AREA_N INIT_REG --> ARM_TPIDR INIT_REG --> LOONG_R21 INIT_REG --> RISCV_GP INIT_REG --> X86_GS REG_FUNCS --> ARM_TPIDR REG_FUNCS --> LOONG_R21 REG_FUNCS --> RISCV_GP REG_FUNCS --> X86_GS SYMBOL_OFFSET --> AREA_BASE USER_ACCESS --> ARCH_GEN USER_INIT --> INIT_FUNC USER_VAR --> DEF_PERCPU
Sources: README.md(L9 - L17) percpu/src/imp.rs(L1 - L179) percpu_macros/src/arch.rs(L1 - L264)
Component Responsibilities
Component | Primary Responsibility | Key Functions |
---|---|---|
percpu_macros | Compile-time code generation | def_percpu,gen_current_ptr,gen_read_current_raw |
percpuruntime | Memory management and register access | init,percpu_area_base,read_percpu_reg |
Linker integration | Memory layout definition | _percpu_start,_percpu_end,.percpusection |
Architecture abstraction | Platform-specific register handling | write_percpu_reg,init_percpu_reg |
Sources: percpu/src/imp.rs(L46 - L86) percpu_macros/src/arch.rs(L54 - L88)
Memory Layout and Initialization Architecture
The percpu system implements a template-based memory layout where a single .percpu
section in the binary serves as a template that gets replicated for each CPU at runtime.
Memory Organization
flowchart TD subgraph subGraph5["Initialization Process"] INIT_START["init()"] SIZE_CALC["percpu_area_size()"] NUM_CALC["percpu_area_num()"] COPY_LOOP["copy_nonoverlapping loop"] end subgraph subGraph4["Runtime Memory Areas"] BASE_CALC["percpu_area_base(cpu_id)"] subgraph subGraph3["CPU N Area"] CPUN_BASE["Base: _percpu_start + N * align_up_64(size)"] CPUN_VAR1["Variable 1 (copy)"] CPUN_VAR2["Variable 2 (copy)"] end subgraph subGraph2["CPU 1 Area"] CPU1_BASE["Base: _percpu_start + align_up_64(size)"] CPU1_VAR1["Variable 1 (copy)"] CPU1_VAR2["Variable 2 (copy)"] end subgraph subGraph1["CPU 0 Area"] CPU0_BASE["Base: _percpu_start"] CPU0_VAR1["Variable 1"] CPU0_VAR2["Variable 2"] end end subgraph subGraph0["Binary Layout"] PERCPU_TEMPLATE[".percpu Section Template"] LOAD_START["_percpu_load_start"] LOAD_END["_percpu_load_end"] end BASE_CALC --> CPU0_BASE BASE_CALC --> CPU1_BASE BASE_CALC --> CPUN_BASE COPY_LOOP --> CPU1_VAR1 COPY_LOOP --> CPU1_VAR2 COPY_LOOP --> CPUN_VAR1 COPY_LOOP --> CPUN_VAR2 INIT_START --> COPY_LOOP INIT_START --> NUM_CALC INIT_START --> SIZE_CALC PERCPU_TEMPLATE --> CPU0_VAR1 PERCPU_TEMPLATE --> CPU0_VAR2
Sources: percpu/src/imp.rs(L46 - L86) percpu/src/imp.rs(L21 - L44) README.md(L54 - L67)
Initialization Sequence
The initialization process follows a specific sequence to set up per-CPU memory areas:
- Size Calculation: The
percpu_area_size()
function calculates the template size using linker symbols percpu/src/imp.rs(L25 - L30) - Area Allocation:
percpu_area_num()
determines how many CPU areas can fit in the reserved space percpu/src/imp.rs(L21 - L23) - Template Copying: The
init()
function copies the template data to each CPU's area percpu/src/imp.rs(L76 - L84) - Alignment: Each area is aligned to 64-byte boundaries using
align_up_64()
percpu/src/imp.rs(L5 - L8)
Sources: percpu/src/imp.rs(L46 - L86)
Cross-Platform Register Abstraction
The system abstracts different CPU architectures' per-CPU register mechanisms through a unified interface while generating architecture-specific assembly code.
Register Mapping Strategy
flowchart TD subgraph subGraph3["Unified Interface"] READ_REG["read_percpu_reg()"] WRITE_REG["write_percpu_reg()"] INIT_REG["init_percpu_reg()"] end subgraph subGraph2["Access Pattern Generation"] X86_ASM["mov gs:[offset VAR]"] ARM_ASM["mrs reg, TPIDR_ELx"] RISCV_ASM["mv reg, gp + offset"] LOONG_ASM["move reg, $r21 + offset"] end subgraph subGraph1["Register Assignment"] X86_MAPPING["x86_64 → GS_BASE (MSR)"] ARM_MAPPING["aarch64 → TPIDR_EL1/EL2"] RISCV_MAPPING["riscv → gp register"] LOONG_MAPPING["loongarch64 → $r21"] end subgraph subGraph0["Architecture Detection"] TARGET_ARCH["cfg!(target_arch)"] FEATURE_FLAGS["cfg!(feature)"] end ARM_ASM --> READ_REG ARM_MAPPING --> ARM_ASM FEATURE_FLAGS --> ARM_MAPPING LOONG_ASM --> READ_REG LOONG_MAPPING --> LOONG_ASM READ_REG --> WRITE_REG RISCV_ASM --> READ_REG RISCV_MAPPING --> RISCV_ASM TARGET_ARCH --> ARM_MAPPING TARGET_ARCH --> LOONG_MAPPING TARGET_ARCH --> RISCV_MAPPING TARGET_ARCH --> X86_MAPPING WRITE_REG --> INIT_REG X86_ASM --> READ_REG X86_MAPPING --> X86_ASM
Sources: percpu/src/imp.rs(L91 - L168) percpu_macros/src/arch.rs(L54 - L88)
Platform-Specific Implementation Details
Architecture | Register | Assembly Pattern | Special Considerations |
---|---|---|---|
x86_64 | GS_BASE | mov gs:[offset VAR] | MSR access,SELF_PTRindirection |
AArch64 | TPIDR_EL1/EL2 | mrs reg, TPIDR_ELx | EL1/EL2 mode detection viaarm-el2feature |
RISC-V | gp | mv reg, gp | Usesgpinstead oftpregister |
LoongArch | $r21 | move reg, $r21 | Direct register access |
Sources: README.md(L19 - L36) percpu/src/imp.rs(L94 - L156)
Code Generation Pipeline Architecture
The macro expansion system transforms high-level per-CPU variable definitions into optimized, architecture-specific access code through a multi-stage pipeline.
Macro Expansion Workflow
flowchart TD subgraph subGraph4["Assembly Output"] X86_OUTPUT["x86: mov gs:[offset VAR]"] ARM_OUTPUT["arm: mrs + movz"] RISCV_OUTPUT["riscv: lui + addi"] LOONG_OUTPUT["loong: lu12i.w + ori"] end subgraph subGraph3["Architecture Code Gen"] GEN_OFFSET["gen_offset()"] GEN_CURRENT_PTR["gen_current_ptr()"] GEN_READ_RAW["gen_read_current_raw()"] GEN_WRITE_RAW["gen_write_current_raw()"] end subgraph subGraph2["Method Generation"] OFFSET_METHOD["offset() -> usize"] CURRENT_PTR["current_ptr() -> *const T"] READ_CURRENT["read_current() -> T"] WRITE_CURRENT["write_current(val: T)"] REMOTE_ACCESS["remote_ptr(), remote_ref()"] end subgraph subGraph1["Symbol Generation"] INNER_SYMBOL["__PERCPU_VAR"] WRAPPER_TYPE["VAR_WRAPPER"] PUBLIC_STATIC["VAR: VAR_WRAPPER"] end subgraph subGraph0["Input Processing"] USER_DEF["#[def_percpu] static VAR: T = init"] SYN_PARSE["syn::parse_macro_input"] EXTRACT["Extract: name, type, initializer"] end CURRENT_PTR --> GEN_CURRENT_PTR EXTRACT --> INNER_SYMBOL EXTRACT --> PUBLIC_STATIC EXTRACT --> WRAPPER_TYPE GEN_CURRENT_PTR --> X86_OUTPUT GEN_OFFSET --> ARM_OUTPUT GEN_OFFSET --> LOONG_OUTPUT GEN_OFFSET --> RISCV_OUTPUT GEN_OFFSET --> X86_OUTPUT GEN_READ_RAW --> X86_OUTPUT GEN_WRITE_RAW --> X86_OUTPUT OFFSET_METHOD --> GEN_OFFSET READ_CURRENT --> GEN_READ_RAW SYN_PARSE --> EXTRACT USER_DEF --> SYN_PARSE WRAPPER_TYPE --> CURRENT_PTR WRAPPER_TYPE --> OFFSET_METHOD WRAPPER_TYPE --> READ_CURRENT WRAPPER_TYPE --> REMOTE_ACCESS WRAPPER_TYPE --> WRITE_CURRENT WRITE_CURRENT --> GEN_WRITE_RAW
Sources: percpu_macros/src/arch.rs(L15 - L50) percpu_macros/src/arch.rs(L90 - L181) percpu_macros/src/arch.rs(L183 - L263)
Generated Code Structure
For each #[def_percpu]
declaration, the macro generates a complete wrapper structure:
// Generated inner symbol (placed in .percpu section)
#[link_section = ".percpu"]
static __PERCPU_VAR: T = init;
// Generated wrapper type with access methods
struct VAR_WRAPPER;
impl VAR_WRAPPER {
fn offset(&self) -> usize { /* architecture-specific assembly */ }
fn current_ptr(&self) -> *const T { /* architecture-specific assembly */ }
fn read_current(&self) -> T { /* optimized direct access */ }
fn write_current(&self, val: T) { /* optimized direct access */ }
// ... additional methods
}
// Public interface
static VAR: VAR_WRAPPER = VAR_WRAPPER;
Sources: percpu_macros/src/arch.rs(L54 - L88) percpu_macros/src/arch.rs(L90 - L181)
Optimization Strategies
The code generation pipeline implements several optimization strategies:
- Direct Assembly Generation: For primitive types, direct assembly instructions bypass pointer indirection percpu_macros/src/arch.rs(L90 - L181)
- Architecture-Specific Instruction Selection: Each platform uses optimal instruction sequences percpu_macros/src/arch.rs(L94 - L181)
- Register Constraint Optimization: Assembly constraints are tailored to each architecture's capabilities percpu_macros/src/arch.rs(L131 - L150)
- Compile-Time Offset Calculation: Variable offsets are resolved at compile time using linker symbols percpu_macros/src/arch.rs(L15 - L50)
Sources: percpu_macros/src/arch.rs(L90 - L263)
Feature Configuration Architecture
The system supports multiple operational modes through Cargo feature flags that modify both compile-time code generation and runtime behavior.
Feature Flag Impact Matrix
Feature | Code Generation Changes | Runtime Changes | Use Case |
---|---|---|---|
sp-naive | Global variables instead of per-CPU | No register usage | Single-core systems |
preempt | NoPreemptGuardintegration | Preemption disable/enable | Preemptible kernels |
arm-el2 | TPIDR_EL2instead ofTPIDR_EL1 | Hypervisor register access | ARM hypervisors |
Sources: README.md(L69 - L79) percpu_macros/src/arch.rs(L55 - L61)
This architecture enables the percpu system to maintain high performance across diverse deployment scenarios while providing a consistent programming interface that abstracts away platform-specific complexities.
Memory Layout and Initialization
Relevant source files
This document explains the per-CPU memory layout structure, initialization process, and linker script integration in the percpu crate. It covers how per-CPU data areas are organized in memory, the template-based initialization process, and the relationship between linker script symbols and runtime address calculations.
For architecture-specific register management details, see Cross-Platform Abstraction. For low-level memory management internals, see Memory Management Internals.
Memory Layout Structure
The percpu system organizes per-CPU data using a template-based approach where all per-CPU variables are first collected into a single template area, then replicated for each CPU with proper alignment.
Per-CPU Area Organization
flowchart TD subgraph subGraph2["Runtime Functions"] AREA_SIZE["percpu_area_size()"] AREA_NUM["percpu_area_num()"] AREA_BASE["percpu_area_base(cpu_id)"] ALIGN_UP["align_up_64()"] end subgraph subGraph1["Memory Layout"] TEMPLATE["Template Area (CPU 0)Size: percpu_area_size()"] CPU1_AREA["CPU 1 Area64-byte aligned"] CPU2_AREA["CPU 2 Area64-byte aligned"] CPUN_AREA["CPU N Area64-byte aligned"] end subgraph subGraph0["Linker Script Symbols"] START["_percpu_start"] END["_percpu_end"] LOAD_START["_percpu_load_start"] LOAD_END["_percpu_load_end"] end ALIGN_UP --> CPU1_AREA AREA_BASE --> CPU1_AREA AREA_NUM --> END AREA_SIZE --> TEMPLATE CPU1_AREA --> CPU2_AREA CPU2_AREA --> CPUN_AREA END --> CPUN_AREA LOAD_END --> TEMPLATE LOAD_START --> TEMPLATE START --> TEMPLATE TEMPLATE --> CPU1_AREA
The memory layout uses several key components:
Component | Purpose | Implementation |
---|---|---|
Template Area | Contains initial values for all per-CPU variables | Defined by.percpusection content |
Per-CPU Areas | Individual copies for each CPU | Created byinit()function |
64-byte Alignment | Cache line optimization | align_up_64()function |
Address Calculation | Runtime pointer arithmetic | percpu_area_base()function |
Sources: percpu/src/imp.rs(L5 - L44) percpu/test_percpu.x(L1 - L17) README.md(L54 - L67)
Address Calculation Functions
The system provides several functions for calculating memory layout parameters:
flowchart TD subgraph Calculations["Calculations"] CALC1["size = load_end - load_start"] CALC2["num = (end - start) / align_up_64(size)"] CALC3["base = start + cpu_id * align_up_64(size)"] end subgraph subGraph1["Linker Symbols"] SYMBOLS["_percpu_start_percpu_end_percpu_load_startUnsupported markdown: br _percpu_load_end"] end subgraph subGraph0["Core Functions"] SIZE["percpu_area_size()Returns template size"] NUM["percpu_area_num()Returns CPU count"] BASE["percpu_area_base(cpu_id)Returns CPU area address"] ALIGN["align_up_64(val)64-byte alignment"] end ALIGN --> CALC2 ALIGN --> CALC3 BASE --> CALC3 NUM --> CALC2 SIZE --> CALC1 SYMBOLS --> BASE SYMBOLS --> NUM SYMBOLS --> SIZE
Sources: percpu/src/imp.rs(L20 - L44)
Initialization Process
The initialization process occurs in two phases: global area setup via init()
and per-CPU register configuration via init_percpu_reg()
.
Global Initialization Flow
flowchart TD subgraph subGraph0["init() Function Flow"] START_INIT["init() called"] CHECK_INIT["Check IS_INIT atomic flag"] ALREADY_INIT["Already initialized?"] RETURN_ZERO["Return 0"] PLATFORM_CHECK["target_os == linux?"] ALLOC_LINUX["Allocate memory with std::alloc"] GET_PARAMS["Get base, size, num parameters"] SET_BASE["Set PERCPU_AREA_BASE"] COPY_LOOP["For each CPU 1..num"] COPY_DATA["copy_nonoverlapping(base, secondary_base, size)"] RETURN_NUM["Return number of areas"] end ALLOC_LINUX --> SET_BASE ALREADY_INIT --> PLATFORM_CHECK ALREADY_INIT --> RETURN_ZERO CHECK_INIT --> ALREADY_INIT COPY_DATA --> RETURN_NUM COPY_LOOP --> COPY_DATA GET_PARAMS --> COPY_LOOP PLATFORM_CHECK --> ALLOC_LINUX PLATFORM_CHECK --> GET_PARAMS SET_BASE --> GET_PARAMS START_INIT --> CHECK_INIT
The init()
function performs these key operations:
- Initialization Guard: Uses
IS_INIT
atomic boolean to prevent multiple initialization percpu/src/imp.rs(L58 - L63) - Platform-Specific Allocation: On Linux, allocates memory dynamically; on bare metal, uses linker-provided memory percpu/src/imp.rs(L65 - L71)
- Template Replication: Copies CPU 0's template data to all other CPU areas percpu/src/imp.rs(L76 - L84)
Sources: percpu/src/imp.rs(L46 - L86)
Per-CPU Register Initialization
flowchart TD subgraph subGraph1["Architecture-Specific Registers"] X86_REG["x86_64: GS_BASE via MSR or syscall"] ARM_REG["aarch64: TPIDR_EL1/EL2 via msr"] RISCV_REG["riscv: gp register via mv"] LOONG_REG["loongarch64: $r21 via move"] end subgraph init_percpu_reg(cpu_id)["init_percpu_reg(cpu_id)"] CALC_BASE["percpu_area_base(cpu_id)"] WRITE_REG["write_percpu_reg(tp)"] end CALC_BASE --> WRITE_REG WRITE_REG --> ARM_REG WRITE_REG --> LOONG_REG WRITE_REG --> RISCV_REG WRITE_REG --> X86_REG
The init_percpu_reg()
function configures the architecture-specific register to point to the appropriate per-CPU area base address.
Sources: percpu/src/imp.rs(L158 - L168) percpu/src/imp.rs(L119 - L156)
Linker Script Integration
The percpu system requires specific linker script modifications to reserve memory for per-CPU areas and define necessary symbols.
Required Linker Script Structure
The linker script must define a .percpu
section with specific symbols and layout:
. = ALIGN(4K);
_percpu_start = .;
_percpu_end = _percpu_start + SIZEOF(.percpu);
.percpu 0x0 (NOLOAD) : AT(_percpu_start) {
_percpu_load_start = .;
*(.percpu .percpu.*)
_percpu_load_end = .;
. = _percpu_load_start + ALIGN(64) * CPU_NUM;
}
. = _percpu_end;
Symbol Relationships
flowchart TD subgraph subGraph2["Memory Sections"] TEMPLATE_SEC[".percpu section contentAll per-CPU variables"] RESERVED_SPACE["Reserved spaceALIGN(64) * CPU_NUM"] end subgraph subGraph1["Runtime Usage"] AREA_SIZE_CALC["percpu_area_size()= load_end - load_start"] AREA_NUM_CALC["percpu_area_num()= (end - start) / align_up_64(size)"] BASE_CALC["percpu_area_base(cpu_id)= start + cpu_id * align_up_64(size)"] end subgraph subGraph0["Linker Script Symbols"] PERCPU_START["_percpu_startPhysical memory start"] PERCPU_END["_percpu_endPhysical memory end"] LOAD_START["_percpu_load_startTemplate data start"] LOAD_END["_percpu_load_endTemplate data end"] end LOAD_END --> AREA_SIZE_CALC LOAD_START --> AREA_SIZE_CALC PERCPU_END --> AREA_NUM_CALC PERCPU_START --> AREA_NUM_CALC PERCPU_START --> BASE_CALC RESERVED_SPACE --> PERCPU_END TEMPLATE_SEC --> LOAD_END TEMPLATE_SEC --> LOAD_START
Key linker script requirements:
Symbol | Purpose | Usage in Runtime |
---|---|---|
_percpu_start | Base address of all per-CPU areas | percpu_area_base()calculations |
_percpu_end | End of reserved per-CPU memory | Area count calculations |
_percpu_load_start | Start of template data | Template size calculations |
_percpu_load_end | End of template data | Template size calculations |
Sources: percpu/test_percpu.x(L1 - L17) README.md(L54 - L67) percpu/src/imp.rs(L13 - L18)
Platform-Specific Considerations
The initialization process varies based on the target platform:
Bare Metal (target_os = "none")
- Uses linker-provided memory directly via
_percpu_start
symbol - Memory layout is fixed at compile time
- No dynamic allocation required
Linux Userspace (target_os = "linux")
- Dynamically allocates memory using
std::alloc::alloc()
- Stores base address in
PERCPU_AREA_BASE
static variable - Uses
Once
synchronization for thread-safe initialization
Memory Alignment Strategy
The system uses 64-byte alignment for performance optimization:
flowchart TD subgraph Usage["Usage"] AREA_SPACING["Per-CPU area spacing"] CACHE_OPT["Cache line optimization"] end subgraph subGraph0["Alignment Function"] INPUT["Input: val (area size)"] CONST["SIZE_64BIT = 0x40"] CALC["(val + 0x3f) & !0x3f"] OUTPUT["Output: 64-byte aligned size"] end CALC --> OUTPUT CONST --> CALC INPUT --> CALC OUTPUT --> AREA_SPACING OUTPUT --> CACHE_OPT
The align_up_64()
function ensures each per-CPU area starts on a 64-byte boundary to optimize cache performance and prevent false sharing between CPUs.
Sources: percpu/src/imp.rs(L5 - L8) percpu/src/imp.rs(L36 - L44) percpu/src/imp.rs(L65 - L71)
Cross-Platform Abstraction
Relevant source files
This document explains how the percpu crate provides a unified interface across different CPU architectures while leveraging each platform's specific per-CPU register mechanisms. The abstraction layer enables portable per-CPU data management by generating architecture-specific assembly code at compile time and providing runtime functions that adapt to each platform's register conventions.
For details about memory layout and initialization processes, see Memory Layout and Initialization. For implementation specifics of the code generation process, see Architecture-Specific Code Generation.
Architecture Support Matrix
The percpu system supports four major CPU architectures, each using different registers for per-CPU data access:
Architecture | Per-CPU Register | Register Purpose | Feature Requirements |
---|---|---|---|
x86_64 | GS_BASE | MSR-based segment register | MSR read/write access |
AArch64 | TPIDR_EL1/EL2 | Thread pointer register | EL1 or EL2 privilege |
RISC-V | gp | Global pointer register | Custom convention |
LoongArch64 | $r21 | General purpose register | Native ISA support |
Architecture Register Abstraction
flowchart TD subgraph subGraph2["Runtime Selection"] CFGIF["cfg_if! macrotarget_arch conditions"] ASM["Inline Assemblycore::arch::asm!"] end subgraph subGraph1["Architecture-Specific Implementation"] X86["x86_64IA32_GS_BASE MSRrdmsr/wrmsr"] ARM["AArch64TPIDR_EL1/EL2mrs/msr"] RISCV["RISC-Vgp registermv instruction"] LOONG["LoongArch64$r21 registermove instruction"] end subgraph subGraph0["Unified API"] API["read_percpu_reg()write_percpu_reg()init_percpu_reg()"] end API --> CFGIF ARM --> ASM CFGIF --> ARM CFGIF --> LOONG CFGIF --> RISCV CFGIF --> X86 LOONG --> ASM RISCV --> ASM X86 --> ASM
Sources: README.md(L19 - L36) percpu/src/imp.rs(L88 - L156)
Runtime Register Management
The runtime system provides architecture-agnostic functions that internally dispatch to platform-specific register access code. Each architecture implements the same interface using its native register access mechanisms.
Runtime Register Access Functions
flowchart TD subgraph subGraph4["LoongArch64 Implementation"] LA_READ["move {}, $r21"] LA_WRITE["move $r21, {}"] end subgraph subGraph3["RISC-V Implementation"] RV_READ["mv {}, gp"] RV_WRITE["mv gp, {}"] end subgraph subGraph2["AArch64 Implementation"] ARM_READ["mrs TPIDR_EL1/EL2"] ARM_WRITE["msr TPIDR_EL1/EL2"] end subgraph subGraph1["x86_64 Implementation"] X86_READ["rdmsr(IA32_GS_BASE)or SELF_PTR.read_current_raw()"] X86_WRITE["wrmsr(IA32_GS_BASE)+ SELF_PTR.write_current_raw()"] end subgraph subGraph0["Public API"] READ["read_percpu_reg()"] WRITE["write_percpu_reg()"] INIT["init_percpu_reg()"] end INIT --> WRITE READ --> ARM_READ READ --> LA_READ READ --> RV_READ READ --> X86_READ WRITE --> ARM_WRITE WRITE --> LA_WRITE WRITE --> RV_WRITE WRITE --> X86_WRITE
Sources: percpu/src/imp.rs(L91 - L168)
Compile-Time Code Generation Abstraction
The macro system generates architecture-specific assembly code for accessing per-CPU variables. Each architecture requires different instruction sequences and addressing modes, which are abstracted through the code generation functions in percpu_macros/src/arch.rs
.
Code Generation Pipeline by Architecture
flowchart TD subgraph subGraph5["LoongArch64 Assembly"] LA_OFFSET["lu12i.w {0}, %abs_hi20({VAR})ori {0}, {0}, %abs_lo12({VAR})"] LA_PTR["move {}, $r21"] LA_READ["lu12i.w {0}, %abs_hi20({VAR})ori {0}, {0}, %abs_lo12({VAR})ldx.hu {0}, {0}, $r21"] LA_WRITE["lu12i.w {0}, %abs_hi20({VAR})ori {0}, {0}, %abs_lo12({VAR})stx.h {1}, {0}, $r21"] end subgraph subGraph4["RISC-V Assembly"] RV_OFFSET["lui {0}, %hi({VAR})addi {0}, {0}, %lo({VAR})"] RV_PTR["mv {}, gp"] RV_READ["lui {0}, %hi({VAR})add {0}, {0}, gplhu {0}, %lo({VAR})({0})"] RV_WRITE["lui {0}, %hi({VAR})add {0}, {0}, gpsh {1}, %lo({VAR})({0})"] end subgraph subGraph3["AArch64 Assembly"] ARM_OFFSET["movz {0}, #:abs_g0_nc:{VAR}"] ARM_PTR["mrs {}, TPIDR_EL1/EL2"] ARM_FALLBACK["*self.current_ptr()"] end subgraph subGraph2["x86_64 Assembly"] X86_OFFSET["mov {0:e}, offset {VAR}"] X86_PTR["mov {0}, gs:[offset __PERCPU_SELF_PTR]add {0}, offset {VAR}"] X86_READ["mov {0:x}, word ptr gs:[offset {VAR}]"] X86_WRITE["mov word ptr gs:[offset {VAR}], {0:x}"] end subgraph subGraph1["Generation Functions"] OFFSET["gen_offset()"] CURRENTPTR["gen_current_ptr()"] READRAW["gen_read_current_raw()"] WRITERAW["gen_write_current_raw()"] end subgraph subGraph0["Macro Input"] DEFPERCPU["#[def_percpu]static VAR: T = init;"] end CURRENTPTR --> ARM_PTR CURRENTPTR --> LA_PTR CURRENTPTR --> RV_PTR CURRENTPTR --> X86_PTR DEFPERCPU --> CURRENTPTR DEFPERCPU --> OFFSET DEFPERCPU --> READRAW DEFPERCPU --> WRITERAW OFFSET --> ARM_OFFSET OFFSET --> LA_OFFSET OFFSET --> RV_OFFSET OFFSET --> X86_OFFSET READRAW --> ARM_FALLBACK READRAW --> LA_READ READRAW --> RV_READ READRAW --> X86_READ WRITERAW --> ARM_FALLBACK WRITERAW --> LA_WRITE WRITERAW --> RV_WRITE WRITERAW --> X86_WRITE
Sources: percpu_macros/src/arch.rs(L15 - L263)
Feature Flag Configuration
The system uses Cargo features to adapt behavior for different deployment scenarios and platform capabilities:
Feature | Purpose | Effect on Abstraction |
---|---|---|
sp-naive | Single-core systems | Disables per-CPU registers, uses global variables |
preempt | Preemptible kernels | AddsNoPreemptGuardintegration |
arm-el2 | ARM hypervisors | UsesTPIDR_EL2instead ofTPIDR_EL1 |
The arm-el2
feature specifically affects the AArch64 register selection:
// From percpu_macros/src/arch.rs:55-61
let aarch64_tpidr = if cfg!(feature = "arm-el2") {
"TPIDR_EL2"
} else {
"TPIDR_EL1"
};
Feature-Based Configuration Flow
flowchart TD subgraph subGraph2["Implementation Selection"] REGSEL["Register Selection"] GUARDSEL["Guard Selection"] FALLBACK["Fallback Mechanisms"] end subgraph subGraph1["Feature Effects"] SPNAIVE["sp-naive→ Global variables→ No register access"] PREEMPT["preempt→ NoPreemptGuard→ kernel_guard crate"] ARMEL2["arm-el2→ TPIDR_EL2→ Hypervisor mode"] end subgraph subGraph0["Build Configuration"] FEATURES["Cargo.toml[features]"] CFGMACROS["cfg!() macros"] CONDITIONAL["Conditional compilation"] end ARMEL2 --> REGSEL CFGMACROS --> CONDITIONAL CONDITIONAL --> ARMEL2 CONDITIONAL --> PREEMPT CONDITIONAL --> SPNAIVE FEATURES --> CFGMACROS PREEMPT --> GUARDSEL SPNAIVE --> FALLBACK
Sources: README.md(L69 - L79) percpu_macros/src/arch.rs(L55 - L61) percpu/src/imp.rs(L105 - L108)
Platform-Specific Implementation Details
Each architecture has unique characteristics that the abstraction layer must accommodate:
x86_64 Specifics
- Uses Model-Specific Register (MSR)
IA32_GS_BASE
for per-CPU base pointer - Requires special handling for Linux userspace via
arch_prctl
syscall - Maintains
SELF_PTR
variable in per-CPU area for efficient access - Supports direct GS-relative addressing in assembly
AArch64 Specifics
- Uses Thread Pointer Identification Register (
TPIDR_EL1/EL2
) - EL1 for kernel mode, EL2 for hypervisor mode (controlled by
arm-el2
feature) - Limited offset range requires base+offset addressing for larger structures
- Falls back to pointer arithmetic for complex access patterns
RISC-V Specifics
- Repurposes Global Pointer (
gp
) register for per-CPU base - Thread Pointer (
tp
) remains available for thread-local storage - Uses
lui
/addi
instruction pairs for address calculation - Supports direct load/store with calculated offsets
LoongArch64 Specifics
- Uses general-purpose register
$r21
by convention - Native instruction support with
lu12i.w
/ori
for address formation - Indexed load/store instructions for efficient per-CPU access
- Full 32-bit offset support for large per-CPU areas
Sources: percpu/src/imp.rs(L94 - L156) percpu_macros/src/arch.rs(L21 - L263) README.md(L28 - L35)
Code Generation Pipeline
Relevant source files
This document details the compile-time transformation pipeline that converts user-defined per-CPU variable declarations into architecture-specific, optimized access code. The pipeline is implemented as a procedural macro system that analyzes user code, detects target platform characteristics, and generates efficient per-CPU data access methods.
For information about the runtime memory layout and initialization, see Memory Layout and Initialization. For details about the specific assembly code generated for each architecture, see Architecture-Specific Code Generation.
Pipeline Overview
The code generation pipeline transforms a simple user declaration into a comprehensive per-CPU data access system through multiple stages of analysis and code generation.
High-Level Transformation Flow
flowchart TD subgraph subGraph2["Output Components"] OUTPUT["Generated Components"] STORAGE["__PERCPU_VAR in .percpu section"] WRAPPER["VAR_WRAPPER struct"] INTERFACE["VAR: VAR_WRAPPER static"] end subgraph subGraph1["Generation Phase"] GENERATE["Code Generation"] STORAGE_GEN["__PERCPU_VAR generation"] WRAPPER_GEN["VAR_WRAPPER generation"] METHOD_GEN["Method generation"] ARCH_GEN["Architecture-specific code"] end subgraph subGraph0["Analysis Phase"] ANALYZE["Type & Feature Analysis"] TYPE_CHECK["is_primitive_int check"] FEATURE_CHECK["Feature flag detection"] SYMBOL_GEN["Symbol name generation"] end INPUT["#[def_percpu] static VAR: T = init;"] PARSE["syn::parse_macro_input"] ANALYZE --> FEATURE_CHECK ANALYZE --> GENERATE ANALYZE --> SYMBOL_GEN ANALYZE --> TYPE_CHECK GENERATE --> ARCH_GEN GENERATE --> METHOD_GEN GENERATE --> OUTPUT GENERATE --> STORAGE_GEN GENERATE --> WRAPPER_GEN INPUT --> PARSE OUTPUT --> INTERFACE OUTPUT --> STORAGE OUTPUT --> WRAPPER PARSE --> ANALYZE
Sources: percpu_macros/src/lib.rs(L72 - L252)
Input Parsing and Analysis
The pipeline begins with the def_percpu
procedural macro, which uses the syn
crate to parse the user's static variable declaration into an Abstract Syntax Tree (AST).
Parsing Stage
flowchart TD subgraph subGraph0["Extracted Components"] EXTRACT["Component Extraction"] ATTRS["attrs: &[Attribute]"] VIS["vis: &Visibility"] NAME["ident: &Ident"] TYPE["ty: &Type"] INIT["expr: &Expr"] end USER["#[def_percpu]static COUNTER: u64 = 0;"] AST["ItemStatic AST"] AST --> EXTRACT EXTRACT --> ATTRS EXTRACT --> INIT EXTRACT --> NAME EXTRACT --> TYPE EXTRACT --> VIS USER --> AST
The parsing logic extracts key components from the declaration:
Component | Purpose | Example |
---|---|---|
attrs | Preserve original attributes | #[no_mangle] |
vis | Maintain visibility | pub |
ident | Variable name | COUNTER |
ty | Type information | u64 |
expr | Initialization expression | 0 |
Sources: percpu_macros/src/lib.rs(L80 - L86)
Type Analysis and Symbol Generation
The pipeline performs type analysis to determine code generation strategy and generates internal symbol names:
flowchart TD subgraph subGraph1["Generated Symbols"] SYMBOL_GEN["Symbol Name Generation"] INNER["_PERCPU{name}"] WRAPPER["{name}_WRAPPER"] PUBLIC["{name}"] end subgraph subGraph0["Primitive Type Detection"] PRIMITIVE_CHECK["is_primitive_int check"] BOOL["bool"] U8["u8"] U16["u16"] U32["u32"] U64["u64"] USIZE["usize"] end TYPE_ANALYSIS["Type Analysis"] PRIMITIVE_CHECK --> BOOL PRIMITIVE_CHECK --> U16 PRIMITIVE_CHECK --> U32 PRIMITIVE_CHECK --> U64 PRIMITIVE_CHECK --> U8 PRIMITIVE_CHECK --> USIZE SYMBOL_GEN --> INNER SYMBOL_GEN --> PUBLIC SYMBOL_GEN --> WRAPPER TYPE_ANALYSIS --> PRIMITIVE_CHECK TYPE_ANALYSIS --> SYMBOL_GEN
The type analysis at percpu_macros/src/lib.rs(L91 - L92) determines whether to generate optimized assembly access methods for primitive integer types.
Sources: percpu_macros/src/lib.rs(L88 - L92)
Code Generation Stages
The pipeline generates three primary components for each per-CPU variable, with different methods and optimizations based on type and feature analysis.
Component Generation Structure
flowchart TD subgraph subGraph2["Interface Component"] INTERFACE["VAR static variable"] PUBLIC_API["Public API access point"] ZERO_SIZED["Zero-sized type"] end subgraph subGraph1["Wrapper Component"] WRAPPER["VAR_WRAPPER struct"] BASIC_METHODS["Basic access methods"] ARCH_METHODS["Architecture-specific methods"] PRIMITIVE_METHODS["Primitive type methods"] end subgraph subGraph0["Storage Component"] STORAGE["__PERCPU_VAR"] SECTION[".percpu section placement"] ATTRS_PRESERVE["Preserve original attributes"] INIT_PRESERVE["Preserve initialization"] end GENERATION["Code Generation"] GENERATION --> INTERFACE GENERATION --> STORAGE GENERATION --> WRAPPER INTERFACE --> PUBLIC_API INTERFACE --> ZERO_SIZED STORAGE --> ATTRS_PRESERVE STORAGE --> INIT_PRESERVE STORAGE --> SECTION WRAPPER --> ARCH_METHODS WRAPPER --> BASIC_METHODS WRAPPER --> PRIMITIVE_METHODS
Sources: percpu_macros/src/lib.rs(L149 - L251)
Method Generation Logic
The wrapper struct receives different sets of methods based on type analysis and feature configuration:
Method Category | Condition | Generated Methods |
---|---|---|
Basic Access | Always | offset(),current_ptr(),current_ref_raw(),current_ref_mut_raw(),with_current(),remote_ptr(),remote_ref_raw(),remote_ref_mut_raw() |
Primitive Optimized | is_primitive_int == true | read_current_raw(),write_current_raw(),read_current(),write_current() |
Preemption Safety | feature = "preempt" | AutomaticNoPreemptGuardin safe methods |
Sources: percpu_macros/src/lib.rs(L100 - L145) percpu_macros/src/lib.rs(L161 - L249)
Architecture-Specific Code Generation
The pipeline delegates architecture-specific code generation to specialized functions in the arch
module, which produce optimized assembly code for each supported platform.
Architecture Detection and Code Selection
flowchart TD subgraph subGraph2["Register Access Patterns"] X86_GS["x86_64: gs:[offset __PERCPU_SELF_PTR]"] ARM_TPIDR["aarch64: mrs TPIDR_EL1/EL2"] RISCV_GP["riscv: mv gp"] LOONG_R21["loongarch64: move $r21"] end subgraph subGraph1["Target Architecture Detection"] X86_OFFSET["x86_64: mov offset VAR"] ARM_OFFSET["aarch64: movz #:abs_g0_nc:VAR"] RISCV_OFFSET["riscv: lui %hi(VAR), addi %lo(VAR)"] LOONG_OFFSET["loongarch64: lu12i.w %abs_hi20(VAR)"] end subgraph subGraph0["Code Generation Functions"] ARCH_GEN["Architecture Code Generation"] OFFSET["gen_offset()"] CURRENT_PTR["gen_current_ptr()"] READ_RAW["gen_read_current_raw()"] WRITE_RAW["gen_write_current_raw()"] end ARCH_GEN --> CURRENT_PTR ARCH_GEN --> OFFSET ARCH_GEN --> READ_RAW ARCH_GEN --> WRITE_RAW CURRENT_PTR --> ARM_TPIDR CURRENT_PTR --> LOONG_R21 CURRENT_PTR --> RISCV_GP CURRENT_PTR --> X86_GS OFFSET --> ARM_OFFSET OFFSET --> LOONG_OFFSET OFFSET --> RISCV_OFFSET OFFSET --> X86_OFFSET
Sources: percpu_macros/src/arch.rs(L16 - L50) percpu_macros/src/arch.rs(L54 - L88)
Assembly Code Generation Patterns
The architecture-specific functions generate different assembly instruction sequences based on the target platform and operation type:
Architecture | Offset Calculation | Current Pointer Access | Direct Read/Write |
---|---|---|---|
x86_64 | mov {reg}, offset {symbol} | mov {reg}, gs:[offset __PERCPU_SELF_PTR] | mov gs:[offset {symbol}], {val} |
AArch64 | movz {reg}, #:abs_g0_nc:{symbol} | mrs {reg}, TPIDR_EL1 | Not implemented |
RISC-V | lui {reg}, %hi({symbol}) | mv {reg}, gp | lui + add + load/store |
LoongArch | lu12i.w {reg}, %abs_hi20({symbol}) | move {reg}, $r21 | lu12i.w + ori + load/store |
Sources: percpu_macros/src/arch.rs(L94 - L181) percpu_macros/src/arch.rs(L187 - L263)
Feature-Based Code Variations
The pipeline adapts code generation based on feature flags, creating different implementations for various use cases and platform configurations.
Feature Flag Processing
flowchart TD subgraph subGraph2["Generated Code Variations"] GLOBAL_VAR["Global variable access"] SAFE_METHODS["Preemption-safe methods"] HYP_MODE["Hypervisor mode support"] end subgraph subGraph1["Code Generation Impact"] NAIVE_IMPL["naive.rs implementation"] GUARD_GEN["NoPreemptGuard generation"] TPIDR_EL2["TPIDR_EL2 register selection"] end subgraph subGraph0["Core Features"] FEATURES["Feature Configuration"] SP_NAIVE["sp-naive"] PREEMPT["preempt"] ARM_EL2["arm-el2"] end ARM_EL2 --> TPIDR_EL2 FEATURES --> ARM_EL2 FEATURES --> PREEMPT FEATURES --> SP_NAIVE GUARD_GEN --> SAFE_METHODS NAIVE_IMPL --> GLOBAL_VAR PREEMPT --> GUARD_GEN SP_NAIVE --> NAIVE_IMPL TPIDR_EL2 --> HYP_MODE
The feature-based variations are configured at percpu_macros/Cargo.toml(L15 - L25) and affect code generation in several ways:
Feature | Impact | Code Changes |
---|---|---|
sp-naive | Single-processor fallback | Usesnaive.rsinstead ofarch.rs |
preempt | Preemption safety | GeneratesNoPreemptGuardin safe methods |
arm-el2 | Hypervisor mode | UsesTPIDR_EL2instead ofTPIDR_EL1 |
Sources: percpu_macros/src/lib.rs(L59 - L60) percpu_macros/src/lib.rs(L94 - L98) percpu_macros/src/arch.rs(L55 - L61)
Final Code Generation and Output
The pipeline concludes by assembling all generated components into the final token stream using the quote!
macro.
Output Structure Assembly
flowchart TD subgraph subGraph1["Quote Assembly"] ASSEMBLY["Token Stream Assembly"] QUOTE_MACRO["quote! { ... }"] TOKEN_STREAM["proc_macro2::TokenStream"] PROC_MACRO["TokenStream::into()"] end subgraph subGraph0["Component Assembly"] COMPONENTS["Generated Components"] STORAGE_TOKENS["Storage variable tokens"] WRAPPER_TOKENS["Wrapper struct tokens"] IMPL_TOKENS["Implementation tokens"] INTERFACE_TOKENS["Interface tokens"] end OUTPUT["Final Output"] ASSEMBLY --> OUTPUT ASSEMBLY --> QUOTE_MACRO COMPONENTS --> ASSEMBLY COMPONENTS --> IMPL_TOKENS COMPONENTS --> INTERFACE_TOKENS COMPONENTS --> STORAGE_TOKENS COMPONENTS --> WRAPPER_TOKENS QUOTE_MACRO --> TOKEN_STREAM TOKEN_STREAM --> PROC_MACRO
The final assembly process at percpu_macros/src/lib.rs(L149 - L251) combines:
- Storage Declaration:
static mut __PERCPU_{name}: {type} = {init};
with.percpu
section attribute - Wrapper Struct: Zero-sized struct with generated methods for access
- Implementation Block: All the generated methods for the wrapper
- Public Interface:
static {name}: {name}_WRAPPER = {name}_WRAPPER {};
The complete transformation ensures that a simple #[def_percpu] static VAR: T = init;
declaration becomes a comprehensive per-CPU data access system with architecture-optimized assembly code, type-safe interfaces, and optional preemption safety.
Sources: percpu_macros/src/lib.rs(L149 - L252)
API Reference
Relevant source files
This document provides a comprehensive reference for all public APIs, macros, and functions in the percpu crate ecosystem. It covers the main user-facing interfaces for defining and accessing per-CPU data structures.
For detailed information about the def_percpu
macro syntax and usage patterns, see def_percpu Macro. For runtime initialization and management functions, see Runtime Functions. For guidance on safe usage and preemption handling, see Safety and Preemption.
API Overview
The percpu crate provides a declarative interface for per-CPU data management through the def_percpu
attribute macro and a set of runtime functions for system initialization.
Complete API Surface
flowchart TD subgraph subGraph5["Internal APIs"] PERCPU_AREA_BASE["percpu_area_base(cpu_id) -> usize"] NOPREEMPT_GUARD["NoPreemptGuard"] PERCPU_SYMBOL_OFFSET["percpu_symbol_offset!"] end subgraph subGraph4["Generated Per-Variable APIs"] WRAPPER["VAR_WRAPPERZero-sized struct"] STATIC_VAR["VAR: VAR_WRAPPERStatic instance"] subgraph subGraph3["Primitive Type Methods"] READ_CURRENT_RAW["read_current_raw() -> T"] WRITE_CURRENT_RAW["write_current_raw(val: T)"] READ_CURRENT["read_current() -> T"] WRITE_CURRENT["write_current(val: T)"] end subgraph subGraph2["Raw Access Methods"] CURRENT_REF_RAW["current_ref_raw() -> &T"] CURRENT_REF_MUT_RAW["current_ref_mut_raw() -> &mut T"] REMOTE_PTR["remote_ptr(cpu_id) -> *const T"] REMOTE_REF_RAW["remote_ref_raw(cpu_id) -> &T"] REMOTE_REF_MUT_RAW["remote_ref_mut_raw(cpu_id) -> &mut T"] end subgraph subGraph1["Core Methods"] OFFSET["offset() -> usize"] CURRENT_PTR["current_ptr() -> *const T"] WITH_CURRENT["with_current(f: F) -> R"] end end subgraph subGraph0["User Interface"] DEF_PERCPU["#[def_percpu]Attribute Macro"] INIT["percpu::init()"] INIT_REG["percpu::init_percpu_reg(cpu_id)"] end DEF_PERCPU --> STATIC_VAR DEF_PERCPU --> WRAPPER READ_CURRENT --> NOPREEMPT_GUARD REMOTE_PTR --> PERCPU_AREA_BASE WITH_CURRENT --> NOPREEMPT_GUARD WRAPPER --> CURRENT_PTR WRAPPER --> CURRENT_REF_MUT_RAW WRAPPER --> CURRENT_REF_RAW WRAPPER --> OFFSET WRAPPER --> READ_CURRENT WRAPPER --> READ_CURRENT_RAW WRAPPER --> REMOTE_PTR WRAPPER --> REMOTE_REF_MUT_RAW WRAPPER --> REMOTE_REF_RAW WRAPPER --> WITH_CURRENT WRAPPER --> WRITE_CURRENT WRAPPER --> WRITE_CURRENT_RAW WRITE_CURRENT --> NOPREEMPT_GUARD
Sources: percpu_macros/src/lib.rs(L66 - L252) percpu/src/lib.rs(L5 - L17) README.md(L39 - L52)
Core API Components
Macro Definition Interface
The primary user interface is the def_percpu
attribute macro that transforms static variable definitions into per-CPU data structures.
Component | Type | Purpose |
---|---|---|
#[def_percpu] | Attribute Macro | Transforms static variables into per-CPU data |
percpu::init() | Function | Initializes per-CPU data areas |
percpu::init_percpu_reg(cpu_id) | Function | Sets up per-CPU register for given CPU |
Sources: percpu_macros/src/lib.rs(L66 - L78) percpu/src/lib.rs(L11) README.md(L44 - L46)
Generated Code Structure
For each variable defined with #[def_percpu]
, the macro generates multiple code entities that work together to provide per-CPU access.
flowchart TD subgraph subGraph3["Method Categories"] OFFSET_METHODS["Offset Calculationoffset()"] POINTER_METHODS["Pointer Accesscurrent_ptr(), remote_ptr()"] REFERENCE_METHODS["Reference Accesscurrent_ref_raw(), remote_ref_raw()"] VALUE_METHODS["Value Accessread_current(), write_current()"] FUNCTIONAL_METHODS["Functional Accesswith_current()"] end subgraph subGraph2["Section Placement"] PERCPU_SECTION[".percpu sectionLinker managed"] end subgraph subGraph1["Generated Entities"] INNER_SYMBOL["__PERCPU_VARstatic mut __PERCPU_VAR: T"] WRAPPER_STRUCT["VAR_WRAPPERstruct VAR_WRAPPER {}"] PUBLIC_STATIC["VARstatic VAR: VAR_WRAPPER"] end subgraph subGraph0["User Declaration"] USER_VAR["#[def_percpu]static VAR: T = init;"] end INNER_SYMBOL --> PERCPU_SECTION USER_VAR --> INNER_SYMBOL USER_VAR --> PUBLIC_STATIC USER_VAR --> WRAPPER_STRUCT WRAPPER_STRUCT --> FUNCTIONAL_METHODS WRAPPER_STRUCT --> OFFSET_METHODS WRAPPER_STRUCT --> POINTER_METHODS WRAPPER_STRUCT --> REFERENCE_METHODS WRAPPER_STRUCT --> VALUE_METHODS
Sources: percpu_macros/src/lib.rs(L88 - L159) percpu_macros/src/lib.rs(L33 - L51)
Method Categories and Safety
The generated wrapper struct provides methods organized into distinct categories based on their safety requirements and access patterns.
Method Safety Hierarchy
flowchart TD subgraph subGraph4["Preemption Guard Integration"] NOPREEMPT["NoPreemptGuardkernel_guard crate"] end subgraph subGraph3["Utility Methods (Always Safe)"] OFFSET_SAFE["offset() -> usizeAll types"] end subgraph subGraph2["Remote Access (Always Unsafe)"] REMOTE_PTR_UNSAFE["remote_ptr(cpu_id) -> *const TAll types"] REMOTE_REF_RAW_UNSAFE["remote_ref_raw(cpu_id) -> &TAll types"] REMOTE_REF_MUT_RAW_UNSAFE["remote_ref_mut_raw(cpu_id) -> &mut TAll types"] end subgraph subGraph1["Unsafe Methods (Manual Preemption Control)"] READ_CURRENT_RAW_UNSAFE["read_current_raw() -> TPrimitive types only"] WRITE_CURRENT_RAW_UNSAFE["write_current_raw(val: T)Primitive types only"] CURRENT_REF_RAW_UNSAFE["current_ref_raw() -> &TAll types"] CURRENT_REF_MUT_RAW_UNSAFE["current_ref_mut_raw() -> &mut TAll types"] CURRENT_PTR_UNSAFE["current_ptr() -> *const TAll types"] end subgraph subGraph0["Safe Methods (Preemption Handled)"] READ_CURRENT_SAFE["read_current() -> TPrimitive types only"] WRITE_CURRENT_SAFE["write_current(val: T)Primitive types only"] WITH_CURRENT_SAFE["with_current(f: F) -> RAll types"] end READ_CURRENT_SAFE --> NOPREEMPT READ_CURRENT_SAFE --> READ_CURRENT_RAW_UNSAFE REMOTE_PTR_UNSAFE --> OFFSET_SAFE REMOTE_REF_MUT_RAW_UNSAFE --> REMOTE_PTR_UNSAFE REMOTE_REF_RAW_UNSAFE --> REMOTE_PTR_UNSAFE WITH_CURRENT_SAFE --> CURRENT_REF_MUT_RAW_UNSAFE WITH_CURRENT_SAFE --> NOPREEMPT WRITE_CURRENT_SAFE --> NOPREEMPT WRITE_CURRENT_SAFE --> WRITE_CURRENT_RAW_UNSAFE
Sources: percpu_macros/src/lib.rs(L101 - L145) percpu_macros/src/lib.rs(L161 - L248) percpu/src/lib.rs(L14 - L17)
Type-Specific API Variations
The generated API surface varies depending on the type of the per-CPU variable, with additional optimized methods for primitive integer types.
API Method Availability Matrix
Method Category | All Types | Primitive Integer Types Only |
---|---|---|
offset() | ✓ | ✓ |
current_ptr() | ✓ | ✓ |
current_ref_raw() | ✓ | ✓ |
current_ref_mut_raw() | ✓ | ✓ |
with_current() | ✓ | ✓ |
remote_ptr() | ✓ | ✓ |
remote_ref_raw() | ✓ | ✓ |
remote_ref_mut_raw() | ✓ | ✓ |
read_current_raw() | ✗ | ✓ |
write_current_raw() | ✗ | ✓ |
read_current() | ✗ | ✓ |
write_current() | ✗ | ✓ |
Primitive integer types: bool
, u8
, u16
, u32
, u64
, usize
Sources: percpu_macros/src/lib.rs(L91 - L93) percpu_macros/src/lib.rs(L100 - L145)
Feature-Dependent Behavior
The API behavior changes based on enabled Cargo features, affecting both code generation and runtime behavior.
Feature Impact on API
Feature | Impact on Generated Code | Runtime Behavior |
---|---|---|
sp-naive | Uses global variables instead of per-CPU areas | No register setup required |
preempt | AddsNoPreemptGuardto safe methods | Preemption disabled during access |
arm-el2 | Changes register selection for AArch64 | UsesTPIDR_EL2instead ofTPIDR_EL1 |
Sources: percpu_macros/src/lib.rs(L94 - L98) percpu/src/lib.rs(L7 - L8) README.md(L69 - L79)
def_percpu Macro
Relevant source files
The def_percpu
macro is the primary interface for defining per-CPU static variables. It transforms a standard static variable declaration into a per-CPU data structure with architecture-specific access methods and automatic memory layout management.
For information about the runtime initialization and register management, see Runtime Functions. For details about preemption safety and guard mechanisms, see Safety and Preemption.
Syntax and Basic Usage
The def_percpu
macro is applied as an attribute to static variable declarations:
#[percpu::def_percpu]
static VARIABLE_NAME: Type = initial_value;
The macro accepts no parameters and must be applied to static
variables only. The original variable name becomes the public interface, while the macro generates the underlying implementation.
Code Transformation Overview
flowchart TD USER["#[def_percpu]static VAR: T = init;"] PARSE["syn::ItemStaticParser"] GEN["Code Generator"] STORAGE["__PERCPU_VAR(in .percpu section)"] WRAPPER["VAR_WRAPPER(access methods)"] PUBLIC["VAR: VAR_WRAPPER(public interface)"] METHODS["offset()current_ptr()with_current()read_current()write_current()"] GEN --> PUBLIC GEN --> STORAGE GEN --> WRAPPER PARSE --> GEN PUBLIC --> METHODS USER --> PARSE
Sources: percpu_macros/src/lib.rs(L71 - L91) percpu_macros/src/lib.rs(L149 - L159)
Generated Code Structure
For each variable X
with type T
, the macro generates three main components:
Component | Name Pattern | Purpose | Visibility |
---|---|---|---|
Storage Variable | __PERCPU_X | Actual data storage in.percpusection | Private |
Wrapper Struct | X_WRAPPER | Access methods container | Matches original |
Public Interface | X | User-facing API | Matches original |
Generated Items Relationship
flowchart TD USER_VAR["User Definition:static VAR: T = init"] STORAGE["__PERCPU_VAR: T(in .percpu section)"] WRAPPER_STRUCT["VAR_WRAPPER {}(zero-sized struct)"] PUBLIC_VAR["VAR: VAR_WRAPPER(public interface)"] CORE_METHODS["offset()current_ptr()current_ref_raw()current_ref_mut_raw()with_current()"] REMOTE_METHODS["remote_ptr()remote_ref_raw()remote_ref_mut_raw()"] PRIMITIVE_METHODS["read_current()write_current()read_current_raw()write_current_raw()"] OFFSET_CALC["Used for offset calculation"] STORAGE --> OFFSET_CALC USER_VAR --> PUBLIC_VAR USER_VAR --> STORAGE USER_VAR --> WRAPPER_STRUCT WRAPPER_STRUCT --> CORE_METHODS WRAPPER_STRUCT --> PRIMITIVE_METHODS WRAPPER_STRUCT --> REMOTE_METHODS
Sources: percpu_macros/src/lib.rs(L33 - L52) percpu_macros/src/lib.rs(L88 - L89) percpu_macros/src/lib.rs(L149 - L159)
Available Methods
The generated wrapper struct provides different sets of methods depending on the data type and enabled features.
Core Methods (All Types)
These methods are available for all per-CPU variables regardless of type:
Method | Return Type | Safety | Purpose |
---|---|---|---|
offset() | usize | Safe | Returns offset from per-CPU area base |
current_ptr() | *const T | Unsafe | Raw pointer to current CPU's data |
current_ref_raw() | &T | Unsafe | Reference to current CPU's data |
current_ref_mut_raw() | &mut T | Unsafe | Mutable reference to current CPU's data |
with_current<F, R>(f: F) -> R | R | Safe | Execute closure with mutable reference |
Remote Access Methods
These methods enable access to per-CPU data on other CPUs:
Method | Parameters | Return Type | Safety Requirements |
---|---|---|---|
remote_ptr(cpu_id) | usize | *const T | Valid CPU ID, no data races |
remote_ref_raw(cpu_id) | usize | &T | Valid CPU ID, no data races |
remote_ref_mut_raw(cpu_id) | usize | &mut T | Valid CPU ID, no data races |
Primitive Type Methods
Additional methods for primitive integer types (bool
, u8
, u16
, u32
, u64
, usize
):
Method | Parameters | Safety | Preemption Handling |
---|---|---|---|
read_current() | None | Safe | Automatic guard |
write_current(val) | T | Safe | Automatic guard |
read_current_raw() | None | Unsafe | Manual management |
write_current_raw(val) | T | Unsafe | Manual management |
Sources: percpu_macros/src/lib.rs(L161 - L248) percpu_macros/src/lib.rs(L100 - L145)
Preemption Safety Integration
The macro automatically integrates with the preemption safety system when the preempt
feature is enabled:
Preemption Guard Integration
flowchart TD READ_CALL["VAR.read_current()"] GUARD_CHECK["preempt feature?"] WRITE_CALL["VAR.write_current(val)"] WITH_CALL["VAR.with_current(|v| {...})"] CREATE_GUARD["NoPreemptGuard::new()"] NO_GUARD["No guard"] RAW_ACCESS["unsafe raw access"] ARCH_SPECIFIC["Architecture-specificregister + offset access"] CREATE_GUARD --> RAW_ACCESS GUARD_CHECK --> CREATE_GUARD GUARD_CHECK --> NO_GUARD NO_GUARD --> RAW_ACCESS RAW_ACCESS --> ARCH_SPECIFIC READ_CALL --> GUARD_CHECK WITH_CALL --> GUARD_CHECK WRITE_CALL --> GUARD_CHECK
Sources: percpu_macros/src/lib.rs(L94 - L98) percpu_macros/src/lib.rs(L129 - L139) percpu_macros/src/lib.rs(L201 - L207)
Data Type Support and Behavior
Primitive Integer Types
For bool
, u8
, u16
, u32
, u64
, and usize
, the macro generates optimized read/write methods:
// Example usage for primitive types
COUNTER.write_current(42);
let value = COUNTER.read_current();
The macro detects primitive types through string comparison of the type representation.
Custom Structures and Complex Types
For non-primitive types, only the core access methods are generated. Access requires using with_current()
or the raw pointer methods:
// Example usage for custom types
CUSTOM_STRUCT.with_current(|s| {
s.field1 = new_value;
s.field2 += increment;
});
Sources: percpu_macros/src/lib.rs(L91 - L93) percpu_macros/src/lib.rs(L100 - L145)
Architecture-Specific Code Generation
The macro delegates architecture-specific code generation to the arch
module, which varies based on the target platform:
Architecture Code Generation Pipeline
flowchart TD MACRO["def_percpu macro"] DETECT["Target Detection"] X86_GEN["x86_64 GeneratorGS_BASE + offset"] ARM_GEN["AArch64 GeneratorTPIDR_ELx + offset"] RISCV_GEN["RISC-V Generatorgp + offset"] LOONG_GEN["LoongArch Generator$r21 + offset"] NAIVE_GEN["Naive GeneratorGlobal variables"] INLINE_ASM["Inline Assemblymov gs:[offset]"] GLOBAL_ACCESS["Direct global access"] METHODS["Generated Methods"] ARM_GEN --> INLINE_ASM DETECT --> ARM_GEN DETECT --> LOONG_GEN DETECT --> NAIVE_GEN DETECT --> RISCV_GEN DETECT --> X86_GEN GLOBAL_ACCESS --> METHODS INLINE_ASM --> METHODS LOONG_GEN --> INLINE_ASM MACRO --> DETECT NAIVE_GEN --> GLOBAL_ACCESS RISCV_GEN --> INLINE_ASM X86_GEN --> INLINE_ASM
Sources: percpu_macros/src/lib.rs(L59 - L60) percpu_macros/src/lib.rs(L102 - L104) percpu_macros/src/lib.rs(L147 - L148)
Usage Examples
Basic Primitive Type
#[percpu::def_percpu]
static COUNTER: usize = 0;
// Safe access with automatic preemption handling
COUNTER.write_current(42);
let value = COUNTER.read_current();
Custom Structure
struct Stats {
operations: u64,
errors: u32,
}
#[percpu::def_percpu]
static CPU_STATS: Stats = Stats { operations: 0, errors: 0 };
// Access through closure
CPU_STATS.with_current(|stats| {
stats.operations += 1;
if error_occurred {
stats.errors += 1;
}
});
Remote CPU Access
// Access data on a specific CPU (unsafe)
unsafe {
let remote_value = *COUNTER.remote_ptr(cpu_id);
*COUNTER.remote_ref_mut_raw(cpu_id) = new_value;
}
Sources: README.md(L39 - L52) percpu/tests/test_percpu.rs(L7 - L32) percpu/tests/test_percpu.rs(L71 - L101)
Error Conditions
The macro will generate compile-time errors in the following cases:
Condition | Error Message |
---|---|
Non-empty attribute | expect an empty attribute: #[def_percpu] |
Non-static item | Parser error fromsyn::parse_macro_input! |
Invalid syntax | Various parser errors fromsyn |
Sources: percpu_macros/src/lib.rs(L73 - L78) percpu_macros/src/lib.rs(L80)
Feature-Specific Behavior
The macro behavior changes based on enabled Cargo features:
sp-naive
: Uses global variables instead of per-CPU areaspreempt
: Automatically includesNoPreemptGuard
in safe methodsarm-el2
: TargetsTPIDR_EL2
register instead ofTPIDR_EL1
For detailed information about these features, see Feature Flags Configuration.
Sources: percpu_macros/src/lib.rs(L94 - L98) README.md(L69 - L79)
Runtime Functions
Relevant source files
This page documents the core runtime functions provided by the percpu crate for managing per-CPU data areas, initializing the system, and handling architecture-specific register operations. These functions form the low-level foundation that supports the high-level def_percpu
macro interface.
For information about defining per-CPU variables, see def_percpu Macro. For safety considerations and preemption handling, see Safety and Preemption. For architectural details about memory layout, see Memory Layout and Initialization.
System Initialization
The percpu system requires explicit initialization before per-CPU variables can be accessed. The initialization process sets up memory areas for each CPU and prepares the system for per-CPU data access.
Core Initialization Function
The init()
function is the primary entry point for initializing the per-CPU data system:
#![allow(unused)] fn main() { pub fn init() -> usize }
This function performs the following operations:
- Prevents re-initialization using atomic synchronization
- Allocates per-CPU memory areas (on hosted platforms)
- Copies template data from the
.percpu
section to each CPU's area - Returns the number of CPU areas initialized
The function uses an atomic boolean IS_INIT
to ensure initialization occurs only once, returning 0 on subsequent calls.
Per-CPU Register Initialization
After system initialization, each CPU must initialize its architecture-specific register:
#![allow(unused)] fn main() { pub fn init_percpu_reg(cpu_id: usize) }
This function sets the per-CPU register to point to the appropriate memory area for the specified CPU. It is equivalent to calling write_percpu_reg(percpu_area_base(cpu_id))
.
flowchart TD Start["System Boot"] InitCall["percpu::init()"] CheckInit["Already initialized?"] SetFlag["Set IS_INIT atomic flag"] Return0["Return 0"] HostedCheck["Hosted platform?"] AllocMem["Allocate memory with std::alloc"] UseLinker["Use linker-provided _percpu_start"] CopyLoop["Copy template data to each CPU area"] ReturnNum["Return number of areas"] PerCpuInit["Each CPU calls init_percpu_reg(cpu_id)"] SetRegister["Set architecture register to area base"] Ready["System ready for per-CPU access"] AllocMem --> CopyLoop CheckInit --> Return0 CheckInit --> SetFlag CopyLoop --> ReturnNum HostedCheck --> AllocMem HostedCheck --> UseLinker InitCall --> CheckInit PerCpuInit --> SetRegister Return0 --> Ready ReturnNum --> PerCpuInit SetFlag --> HostedCheck SetRegister --> Ready Start --> InitCall UseLinker --> CopyLoop
Sources: percpu/src/imp.rs(L56 - L86) percpu/src/imp.rs(L165 - L168)
Memory Query Functions
The percpu system provides several functions for querying memory layout and dimensions of the per-CPU data areas.
Area Dimensions
Function | Return Type | Purpose |
---|---|---|
percpu_area_num() | usize | Number of per-CPU areas reserved |
percpu_area_size() | usize | Size in bytes of each CPU's data area |
percpu_area_base(cpu_id: usize) | usize | Base address of the specified CPU's area |
Memory Layout Calculation
The system calculates memory layout using several key components:
- Template size: Determined by
_percpu_load_end - _percpu_load_start
- Alignment: Each area is aligned to 64-byte boundaries using
align_up_64()
- Total areas: Calculated as
(_percpu_end - _percpu_start) / aligned_area_size
flowchart TD LinkerSyms["Linker Symbols"] SizeCalc["percpu_area_size()"] NumCalc["percpu_area_num()"] LoadStart["_percpu_load_start"] LoadEnd["_percpu_load_end"] Start["_percpu_start"] End["_percpu_end"] AlignUp["align_up_64()"] BaseCalc["percpu_area_base(cpu_id)"] CPUBase["CPU area base = base + (cpu_id * aligned_size)"] AlignUp --> BaseCalc BaseCalc --> CPUBase LinkerSyms --> NumCalc LinkerSyms --> SizeCalc NumCalc --> AlignUp NumCalc --> BaseCalc NumCalc --> End NumCalc --> Start SizeCalc --> BaseCalc SizeCalc --> LoadEnd SizeCalc --> LoadStart
Sources: percpu/src/imp.rs(L21 - L44) percpu/src/imp.rs(L5 - L8)
Register Management Functions
Per-CPU data access relies on architecture-specific registers that hold the base address of the current CPU's data area. The system provides functions to read and write these registers.
Register Access Functions
#![allow(unused)] fn main() { pub fn read_percpu_reg() -> usize pub unsafe fn write_percpu_reg(tp: usize) }
The read_percpu_reg()
function returns the current value of the per-CPU register, while write_percpu_reg()
sets it to a new value. The write function is marked unsafe
because it directly manipulates low-level CPU registers.
Architecture-Specific Register Mapping
Different CPU architectures use different registers and instruction sequences for per-CPU data access:
flowchart TD RegAccess["Per-CPU Register Access"] X86["x86_64"] ARM["AArch64"] RISCV["RISC-V"] LOONG["LoongArch64"] X86Read["Read: rdmsr(IA32_GS_BASE)"] X86Write["Write: wrmsr(IA32_GS_BASE)"] X86Linux["Linux: arch_prctl syscall"] ARMRead["Read: mrs TPIDR_EL1/EL2"] ARMWrite["Write: msr TPIDR_EL1/EL2"] ARMEL2["EL2 mode via arm-el2 feature"] RVRead["Read: mv reg, gp"] RVWrite["Write: mv gp, reg"] LRead["Read: move reg, $r21"] LWrite["Write: move $r21, reg"] ARM --> ARMEL2 ARM --> ARMRead ARM --> ARMWrite LOONG --> LRead LOONG --> LWrite RISCV --> RVRead RISCV --> RVWrite RegAccess --> ARM RegAccess --> LOONG RegAccess --> RISCV RegAccess --> X86 X86 --> X86Linux X86 --> X86Read X86 --> X86Write
Sources: percpu/src/imp.rs(L91 - L156)
Architecture-Specific Behavior
The percpu system adapts to different target platforms and privilege levels through conditional compilation and feature flags.
Platform-Specific Implementations
Platform | Memory Source | Register Access Method |
---|---|---|
target_os = "none" | Linker-provided.percpusection | Direct MSR/register access |
target_os = "linux" | std::allocallocated memory | System calls (x86_64) |
Other hosted | Platform-specific allocation | Platform-specific methods |
Feature Flag Behavior
The system behavior changes based on enabled feature flags:
arm-el2
: UsesTPIDR_EL2
instead ofTPIDR_EL1
on AArch64sp-naive
: Uses alternative implementation (see Naive Implementation)preempt
: Integrates withkernel_guard
for preemption safety
flowchart TD PlatformCheck["Target Platform"] Bare["target_os = none"] Linux["target_os = linux"] Other["Other hosted"] LinkerMem["Use _percpu_start from linker"] AllocMem["Allocate with std::alloc"] PlatformMem["Platform-specific allocation"] ArchCheck["Target Architecture"] X86Arch["x86_64"] ARMArch["aarch64"] RVArch["riscv32/64"] LoongArch["loongarch64"] EL2Check["arm-el2 feature?"] EL2Reg["Use TPIDR_EL2"] EL1Reg["Use TPIDR_EL1"] X86Reg["Use GS_BASE"] GPReg["Use gp register"] R21Reg["Use $r21 register"] ARMArch --> EL2Check ArchCheck --> ARMArch ArchCheck --> LoongArch ArchCheck --> RVArch ArchCheck --> X86Arch Bare --> LinkerMem EL2Check --> EL1Reg EL2Check --> EL2Reg Linux --> AllocMem LoongArch --> R21Reg Other --> PlatformMem PlatformCheck --> Bare PlatformCheck --> Linux PlatformCheck --> Other RVArch --> GPReg X86Arch --> X86Reg
Sources: percpu/src/imp.rs(L36 - L44) percpu/src/imp.rs(L94 - L155)
Usage Patterns
Runtime functions are typically used in specific patterns during system initialization and operation.
Initialization Sequence
// 1. Initialize the per-CPU system
let num_cpus = percpu::init();
// 2. Each CPU initializes its register
// (usually called in per-CPU startup code)
percpu::init_percpu_reg(cpu_id);
// 3. Query system information if needed
let area_size = percpu::percpu_area_size();
let cpu_base = percpu::percpu_area_base(cpu_id);
Register Management
// Read current per-CPU register value
let current_base = percpu::read_percpu_reg();
// Manually set per-CPU register (unsafe)
unsafe {
percpu::write_percpu_reg(new_base_address);
}
Integration with Generated Code
The runtime functions work together with the code generated by the def_percpu
macro. The macro-generated code uses these functions internally to calculate variable addresses and manage access patterns.
Sources: percpu/src/imp.rs(L1 - L179) percpu/src/lib.rs(L7 - L17)
Safety and Preemption
Relevant source files
This document covers the safety mechanisms and preemption handling in the percpu crate system. It explains how per-CPU data access is protected from preemption-related data races and provides guidance on choosing between different safety models. For information about memory layout and initialization safety, see Memory Layout and Initialization. For details about architecture-specific implementation safety, see Architecture-Specific Code Generation.
Preemption Safety Concepts
Per-CPU data access presents unique safety challenges in preemptible systems. When a task accesses per-CPU data and is then preempted and migrated to another CPU, it may inadvertently access data belonging to a different CPU, leading to data races and inconsistent state.
sequenceDiagram participant Task as "Task" participant CPU0 as "CPU 0" participant CPU1 as "CPU 1" participant PerCPUData as "Per-CPU Data" Task ->> CPU0: "Access per-CPU variable" CPU0 ->> PerCPUData: "Read CPU 0 data area" Note over Task,CPU0: "Task gets preempted" Task ->> CPU1: "Task migrated to CPU 1" Task ->> CPU1: "Continue per-CPU access" CPU1 ->> PerCPUData: "Access CPU 1 data area" Note over Task,PerCPUData: "Data inconsistency!"
The percpu crate provides two safety models to address this challenge:
Safety Model | Description | Use Case |
---|---|---|
Raw Methods | Require manual preemption control | Performance-critical code with existing preemption management |
Safe Methods | Automatic preemption disabling | General application code requiring safety guarantees |
Sources: percpu_macros/src/lib.rs(L94 - L98) percpu_macros/src/lib.rs(L109 - L140)
Safety Models and Method Types
Raw Methods (*_rawfunctions)
Raw methods provide direct access to per-CPU data without automatic preemption control. These methods are marked unsafe
and require the caller to ensure preemption is disabled.
flowchart TD subgraph Examples["Examples"] ReadRaw["read_current_raw()"] WriteRaw["write_current_raw()"] RefRaw["current_ref_raw()"] MutRefRaw["current_ref_mut_raw()"] end RawAccess["Raw Method Call"] PreemptCheck["Preemption Disabled?"] DataAccess["Direct Data Access"] UnsafeBehavior["Undefined Behavior"] PreemptCheck --> DataAccess PreemptCheck --> UnsafeBehavior RawAccess --> PreemptCheck
Key raw methods include:
read_current_raw()
- Returns value without preemption protectionwrite_current_raw()
- Sets value without preemption protectioncurrent_ref_raw()
- Returns reference without preemption protectioncurrent_ref_mut_raw()
- Returns mutable reference without preemption protection
Sources: percpu_macros/src/lib.rs(L113 - L125) percpu_macros/src/lib.rs(L184 - L197)
Safe Methods (Automatic Preemption Control)
Safe methods automatically disable preemption during the operation using the NoPreemptGuard
mechanism when the preempt
feature is enabled.
flowchart TD subgraph subGraph0["Safe Method Examples"] ReadCurrent["read_current()"] WriteCurrent["write_current()"] WithCurrent["with_current(closure)"] end SafeCall["Safe Method Call"] GuardCreate["NoPreemptGuard::new()"] PreemptDisabled["Preemption Disabled"] RawCall["Call corresponding _raw method"] GuardDrop["Guard dropped (RAII)"] PreemptReenabled["Preemption Re-enabled"] GuardCreate --> PreemptDisabled GuardDrop --> PreemptReenabled PreemptDisabled --> RawCall RawCall --> GuardDrop SafeCall --> GuardCreate
The code generation logic determines preemption handling based on feature flags:
flowchart TD FeatureCheck["preempt feature enabled?"] GuardGen["Generate NoPreemptGuard"] NoGuard["Generate empty block"] SafeMethod["Safe method implementation"] FeatureCheck --> GuardGen FeatureCheck --> NoGuard GuardGen --> SafeMethod NoGuard --> SafeMethod
Sources: percpu_macros/src/lib.rs(L129 - L139) percpu_macros/src/lib.rs(L201 - L207)
NoPreemptGuard Integration
The NoPreemptGuard
provides RAII-based preemption control through integration with the kernel_guard
crate. This integration is enabled by the preempt
feature flag.
Feature Configuration
Feature State | Behavior | Dependencies |
---|---|---|
preemptenabled | Automatic preemption guards | kernel_guardcrate |
preemptdisabled | No preemption protection | No additional dependencies |
Guard Implementation
flowchart TD UserCall["User calls safe method"] GuardCreation["let _guard = NoPreemptGuard::new()"] PreemptOff["Preemption disabled"] DataOperation["Per-CPU data access"] ScopeEnd["Method scope ends"] GuardDrop["_guard dropped (RAII)"] PreemptOn["Preemption re-enabled"] DataOperation --> ScopeEnd GuardCreation --> PreemptOff GuardDrop --> PreemptOn PreemptOff --> DataOperation ScopeEnd --> GuardDrop UserCall --> GuardCreation
The guard is implemented as a module re-export that conditionally includes the kernel_guard::NoPreempt
type:
Sources: percpu/src/lib.rs(L14 - L17) percpu/Cargo.toml(L21 - L22) percpu_macros/src/lib.rs(L94 - L98)
Remote Access Safety
Remote CPU access methods (remote_*
) require additional safety considerations beyond preemption control, as they access data belonging to other CPUs.
Remote Access Safety Requirements
Method | Safety Requirements |
---|---|
remote_ptr(cpu_id) | Valid CPU ID + No data races |
remote_ref_raw(cpu_id) | Valid CPU ID + No data races |
remote_ref_mut_raw(cpu_id) | Valid CPU ID + Exclusive access |
flowchart TD subgraph subGraph0["Remote Methods"] RemotePtr["remote_ptr(cpu_id)"] RemoteRef["remote_ref_raw(cpu_id)"] RemoteMut["remote_ref_mut_raw(cpu_id)"] end RemoteCall["Remote access method"] CPUValidation["CPU ID valid?"] RaceCheck["Data races possible?"] SafeAccess["Safe remote access"] UnsafeBehavior["Undefined behavior"] CPUValidation --> RaceCheck CPUValidation --> UnsafeBehavior RaceCheck --> SafeAccess RaceCheck --> UnsafeBehavior RemoteCall --> CPUValidation
Sources: percpu_macros/src/lib.rs(L209 - L246)
Best Practices
Choosing Safety Models
- Use safe methods by default - Automatic preemption protection prevents common bugs
- Use raw methods for performance-critical paths - When preemption is already managed externally
- Enable
preempt
feature in production - Unless running on single-CPU systems
Safe Usage Patterns
flowchart TD subgraph Recommendations["Recommendations"] Rec1["Prefer safe methods"] Rec2["Use with_current for atomicity"] Rec3["Minimize raw method usage"] end Pattern1["Single Operation"] Pattern2["Multiple Operations"] Pattern3["Complex Logic"] Method1["read_current() / write_current()"] Method2["with_current(closure)"] Method3["Manual guard management"] Pattern1 --> Method1 Pattern2 --> Method2 Pattern3 --> Method3
Feature Flag Configuration
For different deployment scenarios:
Scenario | Feature Configuration | Rationale |
---|---|---|
Production kernel | preemptenabled | Full safety guarantees |
Single-CPU embedded | sp-naiveenabled | No preemption needed |
Performance testing | preemptdisabled | Measure raw performance |
Sources: percpu/Cargo.toml(L15 - L25) percpu_macros/src/lib.rs(L100 - L145)
Implementation Details
Relevant source files
This page provides an overview of the internal implementation details of the percpu crate ecosystem for maintainers and advanced users. It covers the core implementation strategies, key code entities, and how the compile-time macro generation integrates with the runtime per-CPU data management system.
For detailed architecture-specific code generation, see Architecture-Specific Code Generation. For the simplified single-CPU implementation, see Naive Implementation. For low-level memory management specifics, see Memory Management Internals.
Core Implementation Strategy
The percpu system implements per-CPU data management through a two-phase approach: compile-time code generation via procedural macros and runtime memory area management. The system places all per-CPU variables in a special .percpu
linker section, then creates per-CPU memory areas by copying this template section for each CPU.
flowchart TD subgraph subGraph2["Linker Integration"] PERCPU_SEC[".percpu section"] PERCPU_START["_percpu_start"] PERCPU_END["_percpu_end"] LOAD_START["_percpu_load_start"] LOAD_END["_percpu_load_end"] end subgraph subGraph1["Runtime Phase"] INIT_FUNC["init()"] AREA_ALLOC["percpu_area_base()"] REG_INIT["init_percpu_reg()"] READ_REG["read_percpu_reg()"] end subgraph subGraph0["Compile-Time Phase"] DEF_PERCPU["def_percpu macro"] GEN_CODE["Code Generation Pipeline"] INNER_SYM["_PERCPU* symbols"] WRAPPER["*_WRAPPER structs"] end AREA_ALLOC --> REG_INIT DEF_PERCPU --> GEN_CODE GEN_CODE --> INNER_SYM GEN_CODE --> WRAPPER INIT_FUNC --> AREA_ALLOC INNER_SYM --> PERCPU_SEC LOAD_END --> AREA_ALLOC LOAD_START --> AREA_ALLOC PERCPU_END --> AREA_ALLOC PERCPU_SEC --> INIT_FUNC PERCPU_START --> AREA_ALLOC REG_INIT --> READ_REG WRAPPER --> AREA_ALLOC WRAPPER --> READ_REG
Sources: percpu_macros/src/lib.rs(L54 - L262) percpu/src/imp.rs(L1 - L179)
Runtime Implementation Architecture
The runtime implementation in imp.rs
manages per-CPU memory areas and provides architecture-specific register access. The core functions handle initialization, memory layout calculation, and register manipulation across different CPU architectures.
flowchart TD subgraph subGraph4["x86 Self-Pointer"] SELF_PTR["SELF_PTR: def_percpu static"] end subgraph subGraph3["Architecture-Specific Registers"] X86_GS["x86_64: IA32_GS_BASE"] ARM_TPIDR["aarch64: TPIDR_EL1/EL2"] RISCV_GP["riscv: gp register"] LOONG_R21["loongarch: $r21 register"] end subgraph subGraph2["Register Management"] READ_REG["read_percpu_reg()"] WRITE_REG["write_percpu_reg()"] INIT_REG["init_percpu_reg()"] end subgraph subGraph1["Memory Layout Functions"] AREA_SIZE["percpu_area_size()"] AREA_NUM["percpu_area_num()"] AREA_BASE["percpu_area_base(cpu_id)"] ALIGN_UP["align_up_64()"] end subgraph subGraph0["Initialization Functions"] INIT["init()"] IS_INIT["IS_INIT: AtomicBool"] PERCPU_AREA_BASE_STATIC["PERCPU_AREA_BASE: Once"] end AREA_BASE --> ALIGN_UP AREA_NUM --> ALIGN_UP AREA_SIZE --> ALIGN_UP INIT --> AREA_BASE INIT --> AREA_SIZE INIT --> IS_INIT INIT --> PERCPU_AREA_BASE_STATIC INIT_REG --> AREA_BASE INIT_REG --> WRITE_REG READ_REG --> ARM_TPIDR READ_REG --> LOONG_R21 READ_REG --> RISCV_GP READ_REG --> X86_GS WRITE_REG --> ARM_TPIDR WRITE_REG --> LOONG_R21 WRITE_REG --> RISCV_GP WRITE_REG --> X86_GS X86_GS --> SELF_PTR
Sources: percpu/src/imp.rs(L3 - L179)
Compile-Time Code Generation Architecture
The macro system in percpu_macros
transforms user-defined per-CPU variables into architecture-optimized access code. The def_percpu
macro generates wrapper structs with methods for safe and unsafe access patterns.
flowchart TD subgraph subGraph3["Feature Configuration"] SP_NAIVE_FEATURE["sp-naive feature"] PREEMPT_FEATURE["preempt feature"] ARM_EL2_FEATURE["arm-el2 feature"] NO_PREEMPT_GUARD["NoPreemptGuard"] end subgraph subGraph2["Generated Code Entities"] INNER_SYMBOL["_PERCPU{name}"] WRAPPER_STRUCT["{name}_WRAPPER"] WRAPPER_STATIC["{name}: {name}_WRAPPER"] OFFSET_METHOD["offset()"] CURRENT_PTR_METHOD["current_ptr()"] WITH_CURRENT_METHOD["with_current()"] READ_WRITE_METHODS["read/write_current*()"] REMOTE_METHODS["remote_*_raw()"] end subgraph subGraph1["Code Generation Functions"] GEN_OFFSET["arch::gen_offset()"] GEN_CURRENT_PTR["arch::gen_current_ptr()"] GEN_READ_RAW["arch::gen_read_current_raw()"] GEN_WRITE_RAW["arch::gen_write_current_raw()"] end subgraph subGraph0["Input Processing"] DEF_PERCPU_ATTR["def_percpu attribute"] ITEM_STATIC["ItemStatic AST"] PARSE_INPUT["syn::parse_macro_input"] end ARM_EL2_FEATURE --> GEN_CURRENT_PTR DEF_PERCPU_ATTR --> PARSE_INPUT GEN_CURRENT_PTR --> WRAPPER_STRUCT GEN_OFFSET --> INNER_SYMBOL GEN_READ_RAW --> READ_WRITE_METHODS GEN_WRITE_RAW --> READ_WRITE_METHODS ITEM_STATIC --> PARSE_INPUT NO_PREEMPT_GUARD --> READ_WRITE_METHODS NO_PREEMPT_GUARD --> WITH_CURRENT_METHOD PARSE_INPUT --> GEN_CURRENT_PTR PARSE_INPUT --> GEN_OFFSET PARSE_INPUT --> GEN_READ_RAW PARSE_INPUT --> GEN_WRITE_RAW PREEMPT_FEATURE --> NO_PREEMPT_GUARD SP_NAIVE_FEATURE --> GEN_OFFSET WRAPPER_STATIC --> WRAPPER_STRUCT WRAPPER_STRUCT --> CURRENT_PTR_METHOD WRAPPER_STRUCT --> OFFSET_METHOD WRAPPER_STRUCT --> READ_WRITE_METHODS WRAPPER_STRUCT --> REMOTE_METHODS WRAPPER_STRUCT --> WITH_CURRENT_METHOD
Sources: percpu_macros/src/lib.rs(L66 - L252) percpu_macros/src/arch.rs(L1 - L264)
Architecture-Specific Code Generation
The system generates different assembly code for each supported architecture to access per-CPU data efficiently. Each architecture uses different registers and instruction sequences for optimal performance.
Architecture | Register | Offset Calculation | Access Pattern |
---|---|---|---|
x86_64 | GS_BASE(IA32_GS_BASE) | offset symbol | mov gs:[offset VAR] |
AArch64 | TPIDR_EL1/TPIDR_EL2 | #:abs_g0_nc:symbol | mrs TPIDR_ELx+ offset |
RISC-V | gpregister | %hi(symbol)+%lo(symbol) | lui+addi+gp |
LoongArch | $r21register | %abs_hi20+%abs_lo12 | lu12i.w+ori+$r21 |
flowchart TD subgraph subGraph5["LoongArch Assembly"] LOONG_OFFSET["lu12i.w %abs_hi20 + ori %abs_lo12"] LOONG_R21_READ["move {}, $r21"] LOONG_R21_WRITE["move $r21, {}"] LOONG_LOAD_STORE["ldx./stx.with $r21"] end subgraph subGraph4["RISC-V Assembly"] RISCV_OFFSET["lui %hi + addi %lo"] RISCV_GP_READ["mv {}, gp"] RISCV_GP_WRITE["mv gp, {}"] RISCV_LOAD_STORE["ld/sd with gp offset"] end subgraph subGraph3["AArch64 Assembly"] ARM_OFFSET["movz #:abs_g0_nc:{VAR}"] ARM_TPIDR_READ["mrs TPIDR_EL1/EL2"] ARM_TPIDR_WRITE["msr TPIDR_EL1/EL2"] end subgraph subGraph2["x86_64 Assembly"] X86_OFFSET["mov {0:e}, offset {VAR}"] X86_READ["mov gs:[offset {VAR}]"] X86_WRITE["mov gs:[offset {VAR}], value"] X86_GS_BASE["IA32_GS_BASE MSR"] end subgraph subGraph1["Code Generation Functions"] GEN_OFFSET_IMPL["gen_offset()"] GEN_CURRENT_PTR_IMPL["gen_current_ptr()"] GEN_READ_IMPL["gen_read_current_raw()"] GEN_WRITE_IMPL["gen_write_current_raw()"] end subgraph subGraph0["Architecture Detection"] TARGET_ARCH["cfg!(target_arch)"] X86_64["x86_64"] AARCH64["aarch64"] RISCV["riscv32/riscv64"] LOONGARCH["loongarch64"] end AARCH64 --> GEN_OFFSET_IMPL GEN_CURRENT_PTR_IMPL --> ARM_TPIDR_READ GEN_CURRENT_PTR_IMPL --> LOONG_R21_READ GEN_CURRENT_PTR_IMPL --> RISCV_GP_READ GEN_OFFSET_IMPL --> ARM_OFFSET GEN_OFFSET_IMPL --> LOONG_OFFSET GEN_OFFSET_IMPL --> RISCV_OFFSET GEN_OFFSET_IMPL --> X86_OFFSET GEN_READ_IMPL --> LOONG_LOAD_STORE GEN_READ_IMPL --> RISCV_LOAD_STORE GEN_READ_IMPL --> X86_READ GEN_WRITE_IMPL --> LOONG_LOAD_STORE GEN_WRITE_IMPL --> RISCV_LOAD_STORE GEN_WRITE_IMPL --> X86_WRITE LOONGARCH --> GEN_OFFSET_IMPL RISCV --> GEN_OFFSET_IMPL TARGET_ARCH --> AARCH64 TARGET_ARCH --> LOONGARCH TARGET_ARCH --> RISCV TARGET_ARCH --> X86_64
Sources: percpu_macros/src/arch.rs(L15 - L264)
Integration Between Runtime and Macros
The compile-time macros and runtime functions work together through shared conventions and generated code that calls runtime functions. The macros generate code that uses runtime functions for memory calculations and remote access.
flowchart TD subgraph subGraph3["Memory Layout"] TEMPLATE_AREA["Template in .percpu"] CPU0_AREA["CPU 0 Data Area"] CPU1_AREA["CPU 1 Data Area"] CPUN_AREA["CPU N Data Area"] end subgraph subGraph2["Shared Data Structures"] PERCPU_SECTION[".percpu section"] LINKER_SYMBOLS["_percpu_start/_percpu_end"] INNER_SYMBOLS["_PERCPU* symbols"] CPU_REGISTERS["Architecture registers"] end subgraph subGraph1["Runtime Function Calls"] PERCPU_AREA_BASE_CALL["percpu::percpu_area_base(cpu_id)"] SYMBOL_OFFSET_CALL["percpu_symbol_offset! macro"] READ_PERCPU_REG_CALL["read_percpu_reg()"] end subgraph subGraph0["Macro-Generated Code"] WRAPPER_METHODS["Wrapper Methods"] OFFSET_CALC["self.offset()"] CURRENT_PTR_CALC["self.current_ptr()"] REMOTE_PTR_CALC["self.remote_ptr(cpu_id)"] ASSEMBLY_ACCESS["Architecture-specific assembly"] end ASSEMBLY_ACCESS --> CPU_REGISTERS ASSEMBLY_ACCESS --> READ_PERCPU_REG_CALL CURRENT_PTR_CALC --> ASSEMBLY_ACCESS INNER_SYMBOLS --> PERCPU_SECTION LINKER_SYMBOLS --> PERCPU_SECTION OFFSET_CALC --> SYMBOL_OFFSET_CALL PERCPU_AREA_BASE_CALL --> CPU0_AREA PERCPU_AREA_BASE_CALL --> CPU1_AREA PERCPU_AREA_BASE_CALL --> CPUN_AREA PERCPU_AREA_BASE_CALL --> LINKER_SYMBOLS PERCPU_SECTION --> TEMPLATE_AREA REMOTE_PTR_CALC --> PERCPU_AREA_BASE_CALL SYMBOL_OFFSET_CALL --> INNER_SYMBOLS TEMPLATE_AREA --> CPU0_AREA TEMPLATE_AREA --> CPU1_AREA TEMPLATE_AREA --> CPUN_AREA
Sources: percpu_macros/src/lib.rs(L216 - L221) percpu/src/imp.rs(L32 - L44) percpu_macros/src/lib.rs(L255 - L261)
Architecture-Specific Code Generation
Relevant source files
This document covers the architecture-specific assembly code generation system used by the percpu_macros
crate. The code generation pipeline transforms high-level per-CPU variable access patterns into optimized assembly instructions tailored for each supported CPU architecture.
For information about the macro expansion pipeline and user-facing API, see Code Generation Pipeline. For details about the naive single-CPU implementation, see Naive Implementation.
Overview
The architecture-specific code generation system consists of four main code generation functions that produce inline assembly blocks optimized for each target architecture. These functions are called during macro expansion to generate efficient per-CPU data access patterns.
Sources: percpu_macros/src/arch.rs(L16 - L50) percpu_macros/src/arch.rs(L54 - L88) percpu_macros/src/arch.rs(L94 - L181) percpu_macros/src/arch.rs(L187 - L263)
Offset Calculation Generation
The gen_offset
function generates architecture-specific assembly code to calculate the offset of a per-CPU variable within the .percpu
section. This offset is used for both local and remote CPU access patterns.
Architecture | Instruction Pattern | Offset Limit | Register Usage |
---|---|---|---|
x86_64 | mov {0:e}, offset VAR | ≤ 0xffff_ffff | 32-bit register |
AArch64 | movz {0}, #:abs_g0_nc:VAR | ≤ 0xffff | 64-bit register |
RISC-V | lui {0}, %hi(VAR)+addi {0}, {0}, %lo(VAR) | ≤ 0xffff_ffff | 64-bit register |
LoongArch64 | lu12i.w {0}, %abs_hi20(VAR)+ori {0}, {0}, %abs_lo12(VAR) | ≤ 0xffff_ffff | 64-bit register |
flowchart TD subgraph subGraph4["LoongArch Generation"] LA_INST1["lu12i.w {0}, %abs_hi20({VAR})"] LA_INST2["ori {0}, {0}, %abs_lo12({VAR})"] LA_OUT["out(reg) value"] end subgraph subGraph3["RISC-V Generation"] RV_INST1["lui {0}, %hi({VAR})"] RV_INST2["addi {0}, {0}, %lo({VAR})"] RV_OUT["out(reg) value"] end subgraph subGraph2["AArch64 Generation"] ARM_INST["movz {0}, #:abs_g0_nc:{VAR}"] ARM_OUT["out(reg) value"] end subgraph subGraph1["x86_64 Generation"] X86_INST["mov {0:e}, offset {VAR}"] X86_OUT["out(reg) value"] end subgraph subGraph0["Symbol Processing"] SYMBOL["symbol: &Ident"] VAR["VAR = sym #symbol"] end ARM_INST --> ARM_OUT LA_INST1 --> LA_INST2 LA_INST2 --> LA_OUT RV_INST1 --> RV_INST2 RV_INST2 --> RV_OUT SYMBOL --> VAR VAR --> ARM_INST VAR --> LA_INST1 VAR --> RV_INST1 VAR --> X86_INST X86_INST --> X86_OUT
Sources: percpu_macros/src/arch.rs(L16 - L50)
Current CPU Pointer Generation
The gen_current_ptr
function generates code to obtain a pointer to a per-CPU variable on the currently executing CPU. Each architecture uses a different approach based on available per-CPU base pointer registers.
Architecture-Specific Register Usage
The x86_64 architecture uses a special approach where the per-CPU base address is stored in the GS segment at a fixed offset (__PERCPU_SELF_PTR
), allowing direct addressing with a single instruction that combines base retrieval and offset addition.
Sources: percpu_macros/src/arch.rs(L54 - L88) percpu_macros/src/arch.rs(L55 - L62)
Optimized Read Operations
The gen_read_current_raw
function generates type-specific optimized read operations for primitive integer types. This avoids the overhead of pointer dereferencing for simple data types.
Type-Specific Assembly Generation
Type | x86_64 Instruction | RISC-V Instruction | LoongArch Instruction |
---|---|---|---|
bool,u8 | mov byte ptr gs:[offset VAR] | lbu | ldx.bu |
u16 | mov word ptr gs:[offset VAR] | lhu | ldx.hu |
u32 | mov dword ptr gs:[offset VAR] | lwu | ldx.wu |
u64,usize | mov qword ptr gs:[offset VAR] | ld | ldx.d |
The boolean type receives special handling by reading as u8
and converting the result to boolean through a != 0
comparison.
Sources: percpu_macros/src/arch.rs(L94 - L181) percpu_macros/src/arch.rs(L96 - L102) percpu_macros/src/arch.rs(L114 - L129) percpu_macros/src/arch.rs(L131 - L150)
Optimized Write Operations
The gen_write_current_raw
function generates type-specific optimized write operations that directly store values to per-CPU variables without intermediate pointer operations.
Write Instruction Mapping
flowchart TD subgraph subGraph4["Store Instructions"] subgraph LoongArch["LoongArch"] LA_STB["stx.b val, addr, $r21"] LA_STH["stx.h val, addr, $r21"] LA_STW["stx.w val, addr, $r21"] LA_STD["stx.d val, addr, $r21"] end subgraph RISC-V["RISC-V"] RV_SB["sb val, %lo(VAR)(addr)"] RV_SH["sh val, %lo(VAR)(addr)"] RV_SW["sw val, %lo(VAR)(addr)"] RV_SD["sd val, %lo(VAR)(addr)"] end subgraph x86_64["x86_64"] X86_BYTE["mov byte ptr gs:[offset VAR], val"] X86_WORD["mov word ptr gs:[offset VAR], val"] X86_DWORD["mov dword ptr gs:[offset VAR], val"] X86_QWORD["mov qword ptr gs:[offset VAR], val"] end end subgraph subGraph0["Input Processing"] VAL["val: &Ident"] TYPE["ty: &Type"] FIXUP["bool -> u8 conversion"] end FIXUP --> LA_STB FIXUP --> LA_STD FIXUP --> LA_STH FIXUP --> LA_STW FIXUP --> RV_SB FIXUP --> RV_SD FIXUP --> RV_SH FIXUP --> RV_SW FIXUP --> X86_BYTE FIXUP --> X86_DWORD FIXUP --> X86_QWORD FIXUP --> X86_WORD TYPE --> FIXUP VAL --> FIXUP
Sources: percpu_macros/src/arch.rs(L187 - L263) percpu_macros/src/arch.rs(L195 - L211) percpu_macros/src/arch.rs(L214 - L230) percpu_macros/src/arch.rs(L232 - L251)
Platform Compatibility Layer
The code generation system includes a compatibility layer for platforms that don't support inline assembly or per-CPU mechanisms. The macos_unimplemented
function wraps generated assembly with conditional compilation directives.
Platform | Behavior | Fallback |
---|---|---|
Non-macOS | Full assembly implementation | N/A |
macOS | Compile-time unimplemented panic | Pointer-based access |
Unsupported architectures | Fallback to pointer dereferencing | *self.current_ptr() |
Sources: percpu_macros/src/arch.rs(L4 - L13) percpu_macros/src/arch.rs(L64) percpu_macros/src/arch.rs(L171) percpu_macros/src/arch.rs(L253)
Integration with Macro System
The architecture-specific code generation functions are called from the main def_percpu
macro implementation, which determines whether to generate optimized assembly based on the variable type and enabled features.
Sources: percpu_macros/src/lib.rs(L59 - L60) percpu_macros/src/lib.rs(L92) percpu_macros/src/lib.rs(L101 - L105) percpu_macros/src/lib.rs(L147 - L148)
Naive Implementation
Relevant source files
Purpose and Scope
This document covers the sp-naive
feature implementation of the percpu crate, which provides a simplified fallback for single-CPU systems. This implementation treats all per-CPU variables as regular global variables, eliminating the need for architecture-specific per-CPU registers and memory area management.
For information about the full multi-CPU implementation details, see Memory Management Internals. For architecture-specific code generation in multi-CPU scenarios, see Architecture-Specific Code Generation.
Overview
The naive implementation is activated when the sp-naive
cargo feature is enabled. It provides a drastically simplified approach where:
- Per-CPU variables become standard global variables
- No per-CPU memory areas are allocated
- Architecture-specific registers (
GS_BASE
,TPIDR_ELx
,gp
,$r21
) are not used - All runtime functions become no-ops or return constant values
This approach is suitable for single-core embedded systems, testing environments, or scenarios where the overhead of true per-CPU data management is unnecessary.
Runtime Implementation
Naive Runtime Functions
The naive implementation provides stub versions of all runtime functions that would normally manage per-CPU memory areas and registers:
flowchart TD subgraph subGraph1["Return Values"] ZERO1["0"] ONE1["1"] ZERO2["0"] ZERO3["0"] NOOP1["no-op"] NOOP2["no-op"] ONE2["1"] end subgraph subGraph0["Naive Runtime Functions"] PSZ["percpu_area_size"] PNUM["percpu_area_num"] PBASE["percpu_area_base"] READ["read_percpu_reg"] WRITE["write_percpu_reg"] INIT_REG["init_percpu_reg"] INIT["init"] end INIT --> ONE2 INIT_REG --> NOOP2 PBASE --> ZERO2 PNUM --> ONE1 PSZ --> ZERO1 READ --> ZERO3 WRITE --> NOOP1
Function Stub Implementations
Function | Purpose | Naive Return Value |
---|---|---|
percpu_area_size() | Get per-CPU area size | Always0 |
percpu_area_num() | Get number of per-CPU areas | Always1 |
percpu_area_base(cpu_id) | Get base address for CPU | Always0 |
read_percpu_reg() | Read per-CPU register | Always0 |
write_percpu_reg(tp) | Write per-CPU register | No effect |
init_percpu_reg(cpu_id) | Initialize CPU register | No effect |
init() | Initialize per-CPU system | Returns1 |
Sources: percpu/src/naive.rs(L3 - L54)
Code Generation Differences
Macro Implementation Comparison
The naive implementation generates fundamentally different code compared to the standard multi-CPU implementation:
flowchart TD subgraph subGraph2["Naive Implementation"] NAV_OFFSET["gen_offsetaddr_of!(symbol)"] NAV_PTR["gen_current_ptraddr_of!(symbol)"] NAV_READ["gen_read_current_raw*self.current_ptr()"] NAV_WRITE["gen_write_current_rawdirect assignment"] end subgraph subGraph1["Standard Implementation"] STD_OFFSET["gen_offsetregister + offset"] STD_PTR["gen_current_ptrregister-based access"] STD_READ["gen_read_current_rawregister + offset"] STD_WRITE["gen_write_current_rawregister + offset"] end subgraph subGraph0["Source Code"] DEF["#[def_percpu]static VAR: T = init;"] end DEF --> NAV_OFFSET DEF --> STD_OFFSET NAV_OFFSET --> NAV_PTR NAV_PTR --> NAV_READ NAV_READ --> NAV_WRITE STD_OFFSET --> STD_PTR STD_PTR --> STD_READ STD_READ --> STD_WRITE
Key Code Generation Functions
The naive macro implementation in percpu_macros/src/naive.rs(L6 - L28) provides these core functions:
gen_offset()
: Returns::core::ptr::addr_of!(symbol) as usize
instead of calculating register-relative offsetsgen_current_ptr()
: Returns::core::ptr::addr_of!(symbol)
for direct global accessgen_read_current_raw()
: Uses*self.current_ptr()
for simple dereferencegen_write_current_raw()
: Uses direct assignment*(self.current_ptr() as *mut T) = val
Sources: percpu_macros/src/naive.rs(L6 - L28)
Memory Model Comparison
Standard vs Naive Memory Layout
flowchart TD subgraph subGraph0["Standard Multi-CPU Model"] TEMPLATE["Template .percpu Section"] CPU1_AREA["CPU 1 Data Area"] CPUN_AREA["CPU N Data Area"] CPU1_REG["CPU 1 Register"] CPUN_REG["CPU N Register"] subgraph subGraph1["Naive Single-CPU Model"] GLOBAL_VAR["Global Variable Storage"] DIRECT_ACCESS["Direct Memory Access"] CPU0_AREA["CPU 0 Data Area"] CPU0_REG["CPU 0 Register"] end end CPU0_REG --> CPU0_AREA CPU1_REG --> CPU1_AREA CPUN_REG --> CPUN_AREA GLOBAL_VAR --> DIRECT_ACCESS TEMPLATE --> CPU0_AREA TEMPLATE --> CPU1_AREA TEMPLATE --> CPUN_AREA
Memory Allocation Differences
Aspect | Standard Implementation | Naive Implementation |
---|---|---|
Memory Areas | Multiple per-CPU areas | Single global variables |
Initialization | Copy template to each area | No copying required |
Register Usage | Per-CPU register per core | No registers used |
Address Calculation | Base + offset | Direct symbol address |
Memory Overhead | area_size * num_cpus | Size of global variables only |
Sources: percpu/src/naive.rs(L1 - L54) README.md(L71 - L73)
Feature Integration
Build Configuration
The naive implementation is enabled through the sp-naive
cargo feature:
[dependencies]
percpu = { version = "0.1", features = ["sp-naive"] }
When this feature is active:
- All architecture-specific code paths are bypassed
- No linker script modifications are required for per-CPU sections
- The system operates as if there is only one CPU
Compatibility with Other Features
Feature Combination | Behavior |
---|---|
sp-naivealone | Pure global variable mode |
sp-naive+preempt | Global variables with preemption guards |
sp-naive+arm-el2 | Feature ignored, global variables used |
Sources: README.md(L69 - L79)
Use Cases and Limitations
Appropriate Use Cases
The naive implementation is suitable for:
- Single-core embedded systems: Where true per-CPU isolation is unnecessary
- Testing and development: Simplified debugging without architecture concerns
- Prototype development: Quick implementation without per-CPU complexity
- Resource-constrained environments: Minimal memory overhead requirements
Limitations
- No CPU isolation: All "per-CPU" variables are shared globally
- No scalability: Cannot be extended to multi-CPU systems without feature changes
- Limited performance benefits: No per-CPU cache locality optimizations
- Testing coverage gaps: May not expose multi-CPU race conditions during development
Sources: README.md(L71 - L73) percpu/src/naive.rs(L1 - L2)
Memory Management Internals
Relevant source files
This document covers the low-level memory management implementation within the percpu crate, focusing on per-CPU data area allocation, address calculation, and register management. This section details the internal mechanisms that support the per-CPU data abstraction layer.
For high-level memory layout concepts, see Architecture and Design. For architecture-specific code generation details, see Architecture-Specific Code Generation. For the single-CPU fallback implementation, see Naive Implementation.
Memory Area Lifecycle
The per-CPU memory management system follows a well-defined lifecycle from initialization through runtime access. The process begins with linker script integration and proceeds through dynamic allocation and template copying.
Initialization Process
The initialization process is controlled by the init()
function which ensures single initialization and handles platform-specific memory allocation:
flowchart TD START["init()"] CHECK["IS_INIT.compare_exchange()"] RETURN_ZERO["return 0"] PLATFORM_CHECK["Platform Check"] ALLOC["std::alloc::alloc()"] USE_LINKER["Use _percpu_start"] SET_BASE["PERCPU_AREA_BASE.call_once()"] COPY_LOOP["Copy Template Loop"] COPY_PRIMARY["copy_nonoverlapping(base, secondary_base, size)"] RETURN_NUM["return num"] END["END"] ALLOC --> SET_BASE CHECK --> PLATFORM_CHECK CHECK --> RETURN_ZERO COPY_LOOP --> COPY_PRIMARY COPY_PRIMARY --> COPY_LOOP COPY_PRIMARY --> RETURN_NUM PLATFORM_CHECK --> ALLOC PLATFORM_CHECK --> USE_LINKER RETURN_NUM --> END RETURN_ZERO --> END SET_BASE --> COPY_LOOP START --> CHECK USE_LINKER --> COPY_LOOP
Memory Area Initialization Sequence Sources: percpu/src/imp.rs(L56 - L86)
The IS_INIT
atomic boolean prevents re-initialization using compare-and-swap semantics. On Linux platforms, the system dynamically allocates memory since the .percpu
section is not loaded in ELF files. On bare metal (target_os = "none"
), the linker-defined symbols provide the memory area directly.
Template Copying Mechanism
Per-CPU areas are initialized by copying from a primary template area to secondary CPU areas:
flowchart TD TEMPLATE["Primary CPU Areabase = percpu_area_base(0)"] CPU1["CPU 1 Areapercpu_area_base(1)"] CPU2["CPU 2 Areapercpu_area_base(2)"] CPUN["CPU N Areapercpu_area_base(N)"] COPY1["copy_nonoverlapping(base, base+offset1, size)"] COPY2["copy_nonoverlapping(base, base+offset2, size)"] COPYN["copy_nonoverlapping(base, base+offsetN, size)"] CPU1 --> COPY1 CPU2 --> COPY2 CPUN --> COPYN TEMPLATE --> CPU1 TEMPLATE --> CPU2 TEMPLATE --> CPUN
Template to Per-CPU Area Copying Sources: percpu/src/imp.rs(L76 - L84)
Address Calculation and Layout
The memory layout uses 64-byte aligned areas calculated through several key functions that work together to provide consistent addressing across different platforms.
Address Calculation Functions
flowchart TD subgraph subGraph0["Platform Specific"] LINUX_BASE["PERCPU_AREA_BASE.get()"] BAREMETAL_BASE["_percpu_start as usize"] end START_SYM["_percpu_start"] AREA_BASE["percpu_area_base(cpu_id)"] SIZE_CALC["percpu_area_size()"] ALIGN["align_up_64()"] FINAL_ADDR["Final Address"] LOAD_START["_percpu_load_start"] LOAD_END["_percpu_load_end"] OFFSET["percpu_symbol_offset!()"] NUM_CALC["percpu_area_num()"] END_SYM["_percpu_end"] ALIGN --> AREA_BASE AREA_BASE --> FINAL_ADDR AREA_BASE --> NUM_CALC BAREMETAL_BASE --> AREA_BASE END_SYM --> NUM_CALC LINUX_BASE --> AREA_BASE LOAD_END --> SIZE_CALC LOAD_START --> SIZE_CALC SIZE_CALC --> ALIGN SIZE_CALC --> OFFSET START_SYM --> AREA_BASE
Address Calculation Dependencies Sources: percpu/src/imp.rs(L21 - L44)
The align_up_64()
function ensures all per-CPU areas are aligned to 64-byte boundaries for cache line optimization:
Function | Purpose | Return Value |
---|---|---|
percpu_area_size() | Template area size | Size in bytes from linker symbols |
percpu_area_num() | Number of CPU areas | Total section size / aligned area size |
percpu_area_base(cpu_id) | CPU-specific base address | Base + (cpu_id * aligned_size) |
align_up_64(val) | 64-byte alignment | (val + 63) & !63 |
Sources: percpu/src/imp.rs(L5 - L8) percpu/src/imp.rs(L25 - L30) percpu/src/imp.rs(L32 - L44) percpu/src/imp.rs(L20 - L23)
Register Management Internals
Each CPU architecture uses a dedicated register to hold the per-CPU data base address. The register management system provides unified access across different instruction sets.
Architecture-Specific Register Access
flowchart TD READ_REG["read_percpu_reg()"] ARCH_DETECT["Architecture Detection"] WRITE_REG["write_percpu_reg(tp)"] X86["target_arch = x86_64"] ARM64["target_arch = aarch64"] RISCV["target_arch = riscv32/64"] LOONG["target_arch = loongarch64"] X86_READ["rdmsr(IA32_GS_BASE)or SELF_PTR.read_current_raw()"] X86_WRITE["wrmsr(IA32_GS_BASE, tp)or arch_prctl(ARCH_SET_GS, tp)"] ARM_READ["mrs TPIDR_EL1/EL2"] ARM_WRITE["msr TPIDR_EL1/EL2, tp"] RISCV_READ["mv tp, gp"] RISCV_WRITE["mv gp, tp"] LOONG_READ["move tp, $r21"] LOONG_WRITE["move $r21, tp"] ARCH_DETECT --> ARM64 ARCH_DETECT --> LOONG ARCH_DETECT --> RISCV ARCH_DETECT --> X86 ARM64 --> ARM_READ ARM64 --> ARM_WRITE LOONG --> LOONG_READ LOONG --> LOONG_WRITE READ_REG --> ARCH_DETECT RISCV --> RISCV_READ RISCV --> RISCV_WRITE WRITE_REG --> ARCH_DETECT X86 --> X86_READ X86 --> X86_WRITE
Per-CPU Register Access by Architecture Sources: percpu/src/imp.rs(L88 - L117) percpu/src/imp.rs(L119 - L156)
Register Initialization Process
The init_percpu_reg(cpu_id)
function combines address calculation with register writing:
flowchart TD INIT_CALL["init_percpu_reg(cpu_id)"] CALC_BASE["percpu_area_base(cpu_id)"] GET_TP["tp = calculated_address"] WRITE_REG["write_percpu_reg(tp)"] REG_SET["CPU register updated"] CALC_BASE --> GET_TP GET_TP --> WRITE_REG INIT_CALL --> CALC_BASE WRITE_REG --> REG_SET
Register Initialization Flow Sources: percpu/src/imp.rs(L158 - L168)
The x86_64 architecture requires special handling with the SELF_PTR
variable that stores the per-CPU base address within the per-CPU area itself:
flowchart TD X86_SPECIAL["x86_64 Special Case"] SELF_PTR_DEF["SELF_PTR: usize = 0"] PERCPU_MACRO["#[def_percpu] static"] GS_ACCESS["gs:SELF_PTR access"] CIRCULAR["Circular reference for self-location"] GS_ACCESS --> CIRCULAR PERCPU_MACRO --> GS_ACCESS SELF_PTR_DEF --> PERCPU_MACRO X86_SPECIAL --> SELF_PTR_DEF
x86_64 Self-Pointer Mechanism Sources: percpu/src/imp.rs(L174 - L178)
Linker Integration Details
The linker script integration creates the necessary memory layout for per-CPU data areas through carefully designed section definitions.
Linker Script Structure
flowchart TD LINKER_START[".percpu section definition"] SYMBOLS["Symbol Generation"] START_SYM["_percpu_start = ."] END_SYM["_percpu_end = _percpu_start + SIZEOF(.percpu)"] SECTION_DEF[".percpu 0x0 (NOLOAD) : AT(_percpu_start)"] LOAD_START["_percpu_load_start = ."] CONTENT["(.percpu .percpu.)"] LOAD_END["_percpu_load_end = ."] ALIGN_EXPAND[". = _percpu_load_start + ALIGN(64) * CPU_NUM"] VARS["Per-CPU Variables"] SPACE_RESERVE["Reserved space for all CPUs"] ALIGN_EXPAND --> SPACE_RESERVE CONTENT --> VARS LINKER_START --> SECTION_DEF LINKER_START --> SYMBOLS SECTION_DEF --> ALIGN_EXPAND SECTION_DEF --> CONTENT SECTION_DEF --> LOAD_END SECTION_DEF --> LOAD_START SYMBOLS --> END_SYM SYMBOLS --> START_SYM
Linker Script Memory Layout Sources: percpu/test_percpu.x(L1 - L16)
Symbol | Purpose | Usage |
---|---|---|
_percpu_start | Section start address | Base address calculation |
_percpu_end | Section end address | Total size calculation |
_percpu_load_start | Template start | Size calculation for copying |
_percpu_load_end | Template end | Size calculation for copying |
The NOLOAD
directive ensures the section occupies space but isn't loaded from the ELF file, while AT(_percpu_start)
specifies the load address for the template data.
Build System Integration
The build system conditionally applies linker scripts based on target platform:
flowchart TD BUILD_CHECK["build.rs"] LINUX_CHECK["cfg!(target_os = linux)"] FEATURE_CHECK["cfg!(not(feature = sp-naive))"] APPLY_SCRIPT["rustc-link-arg-tests=-T test_percpu.x"] NO_PIE["rustc-link-arg-tests=-no-pie"] BUILD_CHECK --> LINUX_CHECK FEATURE_CHECK --> APPLY_SCRIPT FEATURE_CHECK --> NO_PIE LINUX_CHECK --> FEATURE_CHECK
Build System Linker Integration Sources: percpu/build.rs(L3 - L9)
Platform-Specific Considerations
Different target platforms require distinct memory management strategies due to varying levels of hardware access and memory management capabilities.
Platform Memory Allocation Matrix
Platform | Allocation Method | Base Address Source | Register Access |
---|---|---|---|
Linux userspace | std::alloc::alloc() | PERCPU_AREA_BASE | arch_prctl()syscall |
Bare metal | Linker symbols | _percpu_start | Direct MSR/register |
Kernel/Hypervisor | Linker symbols | _percpu_start | Privileged instructions |
The PERCPU_AREA_BASE
is a spin::once::Once<usize>
that ensures thread-safe initialization of the dynamically allocated base address on platforms where the linker section is not available.
Error Handling and Validation
The system includes several validation mechanisms:
- Atomic initialization checking with
IS_INIT
- Address range validation on bare metal platforms
- Alignment verification through
align_up_64()
- Platform capability detection through conditional compilation
Sources: percpu/src/imp.rs(L1 - L179) percpu/test_percpu.x(L1 - L16) percpu/build.rs(L1 - L9)
Development and Testing
Relevant source files
This document provides an overview of the development workflows, testing infrastructure, and continuous integration setup for the percpu crate ecosystem. It covers the automated testing pipeline, cross-platform validation, and contributor guidelines for maintaining code quality across multiple CPU architectures.
For detailed testing procedures and test case development, see Testing Guide. For build system configuration and cross-compilation setup, see Build System. For contribution guidelines and development environment setup, see Contributing.
Development Workflow Overview
The percpu project follows a comprehensive development workflow that ensures code quality and cross-platform compatibility through automated testing and validation. The system is designed to support multiple CPU architectures and feature configurations while maintaining strict quality standards.
Testing Infrastructure
The project uses a multi-layered testing approach that validates functionality across different target platforms and feature combinations:
Sources: percpu/tests/test_percpu.rs(L1 - L163) .github/workflows/ci.yml(L10 - L32)
CI/CD Pipeline Architecture
The continuous integration system validates all changes across multiple dimensions using GitHub Actions:
flowchart TD subgraph subGraph4["Target Matrix"] T1["x86_64-unknown-linux-gnu"] T2["x86_64-unknown-none"] T3["riscv64gc-unknown-none-elf"] T4["aarch64-unknown-none-softfloat"] T5["loongarch64-unknown-none-softfloat"] end subgraph Documentation["Documentation"] DOC_BUILD["cargo doc --no-deps"] PAGES_DEPLOY["GitHub Pages Deploy"] end subgraph subGraph2["Quality Checks"] FORMAT["cargo fmt --check"] CLIPPY["cargo clippy"] BUILD["cargo build"] TEST["cargo test"] end subgraph subGraph1["CI Matrix Strategy"] NIGHTLY["nightly Toolchain"] TARGETS["Matrix Targets"] FEATURES["Feature Combinations"] end subgraph subGraph0["GitHub Actions Workflow"] TRIGGER["Push/PR Trigger"] CI_JOB["ci Job"] DOC_JOB["doc Job"] end BUILD --> TEST CI_JOB --> FEATURES CI_JOB --> NIGHTLY CI_JOB --> TARGETS CLIPPY --> BUILD DOC_BUILD --> PAGES_DEPLOY DOC_JOB --> DOC_BUILD FEATURES --> BUILD FEATURES --> CLIPPY FEATURES --> TEST FORMAT --> CLIPPY NIGHTLY --> FORMAT TARGETS --> T1 TARGETS --> T2 TARGETS --> T3 TARGETS --> T4 TARGETS --> T5 TRIGGER --> CI_JOB TRIGGER --> DOC_JOB
Sources: .github/workflows/ci.yml(L1 - L56)
Test Execution Flow
The testing system follows a structured approach that validates both basic functionality and advanced features like remote CPU access:
Sources: percpu/tests/test_percpu.rs(L34 - L105) percpu/tests/test_percpu.rs(L107 - L162)
Cross-Platform Validation
The build system validates functionality across all supported architectures using a comprehensive matrix strategy:
Target Platform | Build | Clippy | Tests | Features |
---|---|---|---|---|
x86_64-unknown-linux-gnu | ✓ | ✓ | ✓ | sp-naive, default |
x86_64-unknown-none | ✓ | ✓ | - | preempt,arm-el2 |
riscv64gc-unknown-none-elf | ✓ | ✓ | - | preempt,arm-el2 |
aarch64-unknown-none-softfloat | ✓ | ✓ | - | preempt,arm-el2 |
loongarch64-unknown-none-softfloat | ✓ | ✓ | - | preempt,arm-el2 |
The CI pipeline ensures that all changes maintain compatibility across the entire target matrix before merging.
Sources: .github/workflows/ci.yml(L10 - L32)
Quality Assurance Tools
The project enforces code quality through multiple automated tools:
Formatting and Linting
- Rustfmt: Enforces consistent code formatting across the codebase [
cargo fmt --all -- --check
](https://github.com/arceos-org/percpu/blob/89c8a54c/cargo fmt --all -- --check
)() - Clippy: Provides additional linting and best practice enforcement <FileRef file-url="[https://github.com/arceos-org/percpu/blob/89c8a54c/
cargo](https://github.com/arceos-org/percpu/blob/89c8a54c/%60cargo) clippy --target ${{ matrix.targets }} --features "preempt,arm-el2"
" undefined file-path="cargo clippy --target ${{ matrix.targets }} --features "preempt,arm-el2"
">Hii()
Documentation Generation
- Rust Documentation: Automatically builds and deploys API documentation [
cargo doc --no-deps
](https://github.com/arceos-org/percpu/blob/89c8a54c/cargo doc --no-deps
)() - GitHub Pages: Hosts documentation with automatic updates on main branch changes [
.github/workflows/ci.yml(L49 - L55) ](https://github.com/arceos-org/percpu/blob/89c8a54c/
.github/workflows/ci.yml#L49-L55)
Feature Flag Testing
The system validates different feature combinations to ensure compatibility:
sp-naive
: Single-processor fallback mode testing <FileRef file-url="[https://github.com/arceos-org/percpu/blob/89c8a54c/cargo](https://github.com/arceos-org/percpu/blob/89c8a54c/%60cargo) test --target ${{ matrix.targets }} --features "sp-naive"
" undefined file-path="cargo test --target ${{ matrix.targets }} --features "sp-naive"
">Hii()preempt
andarm-el2
: Advanced feature testing across all build targets [.github/workflows/ci.yml(L25 - L27) ](https://github.com/arceos-org/percpu/blob/89c8a54c/
.github/workflows/ci.yml#L25-L27)
Sources: .github/workflows/ci.yml(L20 - L32) .github/workflows/ci.yml(L40 - L55)
Testing Guide
Relevant source files
This document explains how to run tests, understand the test structure, and add new test cases for the percpu crate ecosystem. The testing framework validates per-CPU data management across multiple architectures and feature configurations.
For information about the build system and CI/CD pipeline details, see Build System. For general development guidelines, see Contributing.
Test Structure Overview
The percpu crate uses a comprehensive testing strategy that validates functionality across different architectures, feature flags, and execution modes. The test suite is designed to work with both real per-CPU implementations and the single-CPU fallback mode.
Test Architecture
Sources: percpu/tests/test_percpu.rs(L1 - L163)
Linker Script Configuration
The test environment uses a custom linker script to set up the .percpu
section for testing with multiple simulated CPUs.
flowchart TD subgraph subGraph1["Memory Layout"] TEMPLATE["Template data"] CPU0_AREA["CPU 0 area"] CPU1_AREA["CPU 1 area"] CPU2_AREA["CPU 2 area"] CPU3_AREA["CPU 3 area"] end subgraph subGraph0["Linker Script Structure"] CPU_NUM["CPU_NUM = 4"] PERCPU_START["_percpu_start symbol"] PERCPU_SECTION[".percpu section"] PERCPU_END["_percpu_end symbol"] ALIGNMENT["64-byte alignment"] end ALIGNMENT --> CPU0_AREA ALIGNMENT --> CPU1_AREA ALIGNMENT --> CPU2_AREA ALIGNMENT --> CPU3_AREA CPU0_AREA --> CPU1_AREA CPU1_AREA --> CPU2_AREA CPU2_AREA --> CPU3_AREA CPU_NUM --> PERCPU_SECTION PERCPU_END --> CPU3_AREA PERCPU_START --> TEMPLATE TEMPLATE --> CPU0_AREA
Sources: percpu/test_percpu.x(L1 - L17)
Running Tests Locally
Basic Test Execution
To run the standard test suite on x86_64 Linux:
# Run with default features
cargo test -- --nocapture
# Run with sp-naive feature (single-CPU mode)
cargo test --features "sp-naive" -- --nocapture
The --nocapture
flag ensures that debug output from the tests is displayed, which includes offset information and per-CPU area details.
Architecture-Specific Testing
Tests can be run on different target architectures, though unit tests only execute on x86_64-unknown-linux-gnu
:
# Cross-compile for other architectures (build only)
cargo build --target riscv64gc-unknown-none-elf --features "preempt,arm-el2"
cargo build --target aarch64-unknown-none-softfloat --features "preempt,arm-el2"
cargo build --target loongarch64-unknown-none-softfloat --features "preempt,arm-el2"
Feature Flag Testing
The test suite validates different feature combinations:
Feature | Purpose | Test Impact |
---|---|---|
sp-naive | Single-CPU fallback | Disables remote CPU access tests |
preempt | Preemption safety | EnablesNoPreemptGuardintegration |
arm-el2 | AArch64 EL2 support | UsesTPIDR_EL2instead ofTPIDR_EL1 |
Sources: .github/workflows/ci.yml(L25 - L32)
Test Coverage and Scenarios
Data Type Coverage
The test suite validates per-CPU variables of different types and sizes:
flowchart TD subgraph subGraph2["Test Operations"] OFFSET_CALC["offset() method"] CURRENT_PTR["current_ptr() validation"] READ_CURRENT["read_current() values"] WRITE_CURRENT["write_current() updates"] WITH_CURRENT_OP["with_current() closures"] end subgraph subGraph1["Complex Types"] STRUCT_TEST["Struct: composite"] STRUCT_FIELDS["foo: usize, bar: u8"] end subgraph subGraph0["Primitive Types"] BOOL_TEST["bool: 1 byte"] U8_TEST["u8: 1 byte"] U16_TEST["u16: 2 bytes"] U32_TEST["u32: 4 bytes"] U64_TEST["u64: 8 bytes"] USIZE_TEST["usize: arch-dependent"] end BOOL_TEST --> OFFSET_CALC CURRENT_PTR --> READ_CURRENT OFFSET_CALC --> CURRENT_PTR READ_CURRENT --> WRITE_CURRENT STRUCT_FIELDS --> WITH_CURRENT_OP STRUCT_TEST --> OFFSET_CALC STRUCT_TEST --> STRUCT_FIELDS U16_TEST --> OFFSET_CALC U32_TEST --> OFFSET_CALC U64_TEST --> OFFSET_CALC U8_TEST --> OFFSET_CALC USIZE_TEST --> OFFSET_CALC WRITE_CURRENT --> WITH_CURRENT_OP
Sources: percpu/tests/test_percpu.rs(L7 - L31) percpu/tests/test_percpu.rs(L52 - L58)
Remote CPU Access Testing
When not using the sp-naive
feature, the test suite validates remote CPU access patterns:
flowchart TD subgraph subGraph2["CPU Simulation"] CPU0_BASE["percpu_area_base(0)"] CPU1_BASE["percpu_area_base(1)"] REG_SWITCH["write_percpu_reg()"] end subgraph subGraph1["Test Sequence"] WRITE_REMOTE["Write to CPU 1"] READ_REMOTE["Read from CPU 1"] SWITCH_CPU["Switch to CPU 1"] VERIFY_LOCAL["Verify local access"] end subgraph subGraph0["Remote Access Methods"] REMOTE_PTR["remote_ptr(cpu_id)"] REMOTE_REF["remote_ref_raw(cpu_id)"] REMOTE_MUT["remote_ref_mut_raw(cpu_id)"] end CPU1_BASE --> REG_SWITCH READ_REMOTE --> SWITCH_CPU REG_SWITCH --> SWITCH_CPU REMOTE_MUT --> WRITE_REMOTE REMOTE_PTR --> READ_REMOTE SWITCH_CPU --> VERIFY_LOCAL
Sources: percpu/tests/test_percpu.rs(L107 - L162)
CI/CD Pipeline
Architecture Matrix Testing
The GitHub Actions CI pipeline tests across multiple architectures and configurations:
flowchart TD subgraph subGraph3["Feature Testing"] PREEMPT_FEAT["preempt feature"] ARM_EL2_FEAT["arm-el2 feature"] SP_NAIVE_FEAT["sp-naive feature"] end subgraph subGraph2["CI Steps"] TOOLCHAIN["Setup Rust toolchain"] FORMAT_CHECK["Format check"] CLIPPY_CHECK["Clippy linting"] BUILD_STEP["Build step"] UNIT_TEST["Unit tests"] end subgraph subGraph1["Target Matrix"] X86_LINUX["x86_64-unknown-linux-gnu"] X86_NONE["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] AARCH64["aarch64-unknown-none-softfloat"] LOONGARCH["loongarch64-unknown-none-softfloat"] end subgraph subGraph0["CI Jobs"] CI_JOB["ci job"] DOC_JOB["doc job"] end AARCH64 --> BUILD_STEP ARM_EL2_FEAT --> CLIPPY_CHECK BUILD_STEP --> UNIT_TEST CI_JOB --> TOOLCHAIN CLIPPY_CHECK --> BUILD_STEP FORMAT_CHECK --> CLIPPY_CHECK LOONGARCH --> BUILD_STEP PREEMPT_FEAT --> CLIPPY_CHECK RISCV --> BUILD_STEP SP_NAIVE_FEAT --> UNIT_TEST TOOLCHAIN --> FORMAT_CHECK X86_LINUX --> UNIT_TEST X86_NONE --> BUILD_STEP
Sources: .github/workflows/ci.yml(L1 - L56)
Test Execution Strategy
Target | Format | Clippy | Build | Unit Tests |
---|---|---|---|---|
x86_64-unknown-linux-gnu | ✓ | ✓ | ✓ | ✓ |
x86_64-unknown-none | ✓ | ✓ | ✓ | ✗ |
riscv64gc-unknown-none-elf | ✓ | ✓ | ✓ | ✗ |
aarch64-unknown-none-softfloat | ✓ | ✓ | ✓ | ✗ |
loongarch64-unknown-none-softfloat | ✓ | ✓ | ✓ | ✗ |
Unit tests only run on x86_64-unknown-linux-gnu
because they require userspace Linux environment for per-CPU area simulation.
Sources: .github/workflows/ci.yml(L29 - L32)
Platform-Specific Considerations
Linux Testing Requirements
The test suite has specific platform requirements:
- Excluded platforms: macOS is explicitly excluded via
#![cfg(not(target_os = "macos"))]
- Linux-only tests: Main test function uses
#[cfg(target_os = "linux")]
- Userspace simulation: Tests simulate per-CPU areas in userspace using
init()
and manual register management
Test Environment Setup
The test environment requires:
- Per-CPU area initialization: Calls
init()
to allocate per-CPU memory areas - Register simulation: Uses
write_percpu_reg()
andread_percpu_reg()
to simulate CPU switching - Memory validation: Validates that calculated pointers match expected base + offset calculations
Architecture-Specific Limitations
Different architectures have varying test coverage:
- x86_64: Full unit test coverage including remote access
- AArch64/RISC-V/LoongArch: Build-time validation only
- Bare metal targets: No userspace test execution
Sources: percpu/tests/test_percpu.rs(L1) percpu/tests/test_percpu.rs(L33 - L34)
Adding New Test Cases
Test Structure Guidelines
When adding new test cases, follow the established patterns:
- Variable definition: Use
#[def_percpu]
with zero-initialized values - Offset validation: Test the
offset()
method for memory layout - Pointer validation: Verify
current_ptr()
calculations - Value operations: Test
read_current()
,write_current()
, andwith_current()
- Remote access: Include remote CPU access tests for non-naive mode
Example Test Pattern
New test cases should follow this structure:
// Define per-CPU variable
#[def_percpu]
static NEW_VAR: NewType = NewType::default();
// Test offset and pointer calculations
assert_eq!(base + NEW_VAR.offset(), NEW_VAR.current_ptr() as usize);
// Test value operations
NEW_VAR.write_current(test_value);
assert_eq!(NEW_VAR.read_current(), test_value);
// Test remote access (non-naive only)
#[cfg(not(feature = "sp-naive"))]
unsafe {
*NEW_VAR.remote_ref_mut_raw(1) = remote_value;
assert_eq!(*NEW_VAR.remote_ptr(1), remote_value);
}
Feature-Specific Testing
New tests should account for different feature configurations:
- Use
cfg!(feature = "sp-naive")
for conditional logic - Wrap remote access tests with
#[cfg(not(feature = "sp-naive"))]
- Consider preemption safety when adding tests for the
preempt
feature
Sources: percpu/tests/test_percpu.rs(L61 - L105) percpu/tests/test_percpu.rs(L107 - L162)
Build System
Relevant source files
This document covers the build process, CI/CD pipeline, and cross-compilation setup for the percpu crate ecosystem. The build system is designed to support multiple CPU architectures and testing environments while maintaining compatibility across bare-metal and hosted environments.
For information about feature flag configuration, see Feature Flags Configuration. For testing procedures and test structure, see Testing Guide.
Overview
The percpu build system consists of several components working together to support cross-platform compilation and testing:
- Cargo workspace with two crates:
percpu
(runtime) andpercpu_macros
(procedural macros) - Multi-target CI/CD pipeline testing across five different architectures
- Custom build scripts for linker integration and test configuration
- Feature-based conditional compilation for different deployment scenarios
Sources: .github/workflows/ci.yml(L1 - L56) Cargo.lock(L1 - L149) percpu/build.rs(L1 - L10)
Cross-Compilation Architecture
The build system supports a comprehensive matrix of target platforms, each requiring specific toolchain and runtime configurations:
flowchart TD subgraph subGraph2["Build Configurations"] FEATURES_PREEMPT["preempt,arm-el2Full Feature Set"] FEATURES_NAIVE["sp-naiveSingle-CPU Fallback"] CUSTOM_LINKER["Custom Linker Scriptstest_percpu.x"] end subgraph subGraph1["Toolchain Requirements"] NIGHTLY["Rust Nightly Toolchain"] COMPONENTS["rust-src, clippy, rustfmt"] TARGETS["Cross-compilation Targets"] end subgraph subGraph0["Target Platforms"] LINUX["x86_64-unknown-linux-gnuHosted Environment"] BARE_X86["x86_64-unknown-noneBare Metal x86_64"] BARE_RISCV["riscv64gc-unknown-none-elfBare Metal RISC-V"] BARE_ARM["aarch64-unknown-none-softfloatBare Metal AArch64"] BARE_LOONG["loongarch64-unknown-none-softfloatBare Metal LoongArch"] end COMPONENTS --> NIGHTLY CUSTOM_LINKER --> LINUX FEATURES_NAIVE --> LINUX FEATURES_PREEMPT --> BARE_ARM FEATURES_PREEMPT --> BARE_LOONG FEATURES_PREEMPT --> BARE_RISCV FEATURES_PREEMPT --> BARE_X86 NIGHTLY --> BARE_ARM NIGHTLY --> BARE_LOONG NIGHTLY --> BARE_RISCV NIGHTLY --> BARE_X86 NIGHTLY --> LINUX TARGETS --> NIGHTLY
Sources: .github/workflows/ci.yml(L12 - L19) .github/workflows/ci.yml(L25 - L27)
CI/CD Pipeline
The automated build and test pipeline runs on every push and pull request, ensuring code quality and cross-platform compatibility:
Build Matrix Configuration
Target Platform | Test Environment | Feature Set | Special Requirements |
---|---|---|---|
x86_64-unknown-linux-gnu | Ubuntu 22.04 | sp-naive+ default | Unit tests enabled |
x86_64-unknown-none | Ubuntu 22.04 | preempt,arm-el2 | Bare metal |
riscv64gc-unknown-none-elf | Ubuntu 22.04 | preempt,arm-el2 | Bare metal |
aarch64-unknown-none-softfloat | Ubuntu 22.04 | preempt,arm-el2 | Bare metal |
loongarch64-unknown-none-softfloat | Ubuntu 22.04 | preempt,arm-el2 | Bare metal |
Pipeline Stages
flowchart TD subgraph subGraph0["CI Job"] CLIPPY["cargo clippy --target TARGET --features preempt,arm-el2"] BUILD["cargo build --target TARGET --features preempt,arm-el2"] TEST["cargo test --target x86_64-unknown-linux-gnu"] subgraph subGraph1["Doc Job"] DOC_CHECKOUT["actions/checkout@v4"] DOC_TOOLCHAIN["dtolnay/rust-toolchain@nightly"] DOC_BUILD["cargo doc --no-deps"] DEPLOY["JamesIves/github-pages-deploy-action@v4"] CHECKOUT["actions/checkout@v4"] TOOLCHAIN["dtolnay/rust-toolchain@nightly"] VERSION_CHECK["rustc --version --verbose"] FORMAT["cargo fmt --all --check"] end end BUILD --> TEST CHECKOUT --> TOOLCHAIN CLIPPY --> BUILD DOC_BUILD --> DEPLOY DOC_CHECKOUT --> DOC_TOOLCHAIN DOC_TOOLCHAIN --> DOC_BUILD FORMAT --> CLIPPY TOOLCHAIN --> VERSION_CHECK VERSION_CHECK --> FORMAT
Sources: .github/workflows/ci.yml(L6 - L33) .github/workflows/ci.yml(L34 - L56)
Build Script Integration
The percpu
crate includes a custom build script that handles linker configuration for testing environments:
Conditional Linker Configuration
The build script in percpu/build.rs(L1 - L10) performs platform-specific setup:
// Pseudo-code representation of build.rs logic
if target_os == "linux" && !feature("sp-naive") {
// Add custom linker script for per-CPU section testing
add_link_arg("-no-pie");
add_link_arg("-T test_percpu.x");
}
This configuration ensures:
- Position-independent execution disabled (
-no-pie
) for consistent memory layout - Custom linker script (
test_percpu.x
) defines.percpu
section placement - Conditional application only for Linux userspace testing without naive fallback
Sources: percpu/build.rs(L4 - L8)
Dependency Management
The build system manages a carefully curated set of dependencies optimized for cross-platform compatibility:
Core Runtime Dependencies
Crate | Version | Purpose | Platform Support |
---|---|---|---|
cfg-if | 1.0.0 | Conditional compilation | All platforms |
kernel_guard | 0.1.2 | Preemption safety | no_std compatible |
spin | 0.9.8 | Synchronization primitives | no_std compatible |
x86 | 0.52.0 | x86-specific operations | x86_64 only |
Macro Dependencies
Crate | Version | Purpose |
---|---|---|
proc-macro2 | 1.0.93 | Token manipulation |
quote | 1.0.38 | Code generation |
syn | 2.0.96 | AST parsing |
Sources: Cargo.lock(L61 - L78)
Documentation Generation
The build system includes automated documentation generation and deployment:
Documentation Pipeline
flowchart TD subgraph Deployment["Deployment"] PAGES["GitHub Pages"] BRANCH["gh-pages Branch"] DEPLOY_ACTION["JamesIves/github-pages-deploy-action@v4"] end subgraph subGraph0["Documentation Build"] SOURCE["Source Code + Doc Comments"] RUSTDOC["cargo doc --no-deps"] FLAGS["RUSTDOCFLAGS Environment"] OUTPUT["target/doc Directory"] end BRANCH --> PAGES DEPLOY_ACTION --> BRANCH FLAGS --> RUSTDOC OUTPUT --> DEPLOY_ACTION RUSTDOC --> OUTPUT SOURCE --> RUSTDOC
Documentation Configuration
The documentation build uses specific flags for enhanced output:
-Zunstable-options --enable-index-page
- Enables unified index page-D rustdoc::broken_intra_doc_links
- Treats broken links as errors-D missing-docs
- Requires documentation for all public items
Sources: .github/workflows/ci.yml(L42 - L56)
Feature-Based Build Variants
The build system supports multiple feature combinations for different deployment scenarios:
Feature Matrix
Feature Set | Target Use Case | Platforms | Build Behavior |
---|---|---|---|
Default | Multi-CPU systems | Bare metal targets | Full per-CPU implementation |
sp-naive | Single-CPU systems | Linux userspace | Global variable fallback |
preempt | Preemption-aware | All platforms | NoPreemptGuard integration |
arm-el2 | Hypervisor mode | AArch64 only | EL2 register usage |
Build Commands by Use Case
# Bare metal development
cargo build --target x86_64-unknown-none --features "preempt,arm-el2"
# Linux userspace testing
cargo test --target x86_64-unknown-linux-gnu --features "sp-naive"
# Documentation generation
cargo doc --no-deps
Sources: .github/workflows/ci.yml(L25 - L32)
Contributing
Relevant source files
This page provides comprehensive guidelines for contributing to the percpu crate ecosystem, including development environment setup, code standards, testing requirements, and the pull request workflow. The information covers both runtime implementation contributions to the main percpu
crate and compile-time macro contributions to the percpu_macros
crate.
For information about the testing infrastructure and test execution, see Testing Guide. For details about the build system and CI/CD pipeline implementation, see Build System.
Development Environment Setup
Prerequisites and Toolchain
The percpu crate requires a nightly Rust toolchain due to its use of unstable features for low-level per-CPU data management. The development environment must support cross-compilation to multiple target architectures.
flowchart TD subgraph subGraph2["Development Tools"] FMT["cargo fmt --check"] CLIPPY["cargo clippy"] BUILD["cargo build"] TEST["cargo test"] DOC["cargo doc"] end subgraph subGraph1["Required Targets"] X86_LINUX["x86_64-unknown-linux-gnu"] X86_NONE["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] AARCH64["aarch64-unknown-none-softfloat"] LOONG["loongarch64-unknown-none-softfloat"] end subgraph subGraph0["Development Environment"] RUST["nightly toolchain"] COMPONENTS["rust-src, clippy, rustfmt"] TARGETS["Multi-target support"] end COMPONENTS --> CLIPPY COMPONENTS --> FMT RUST --> BUILD RUST --> COMPONENTS RUST --> DOC RUST --> TARGETS RUST --> TEST TARGETS --> AARCH64 TARGETS --> LOONG TARGETS --> RISCV TARGETS --> X86_LINUX TARGETS --> X86_NONE
Required Installation Commands:
rustup toolchain install nightly
rustup component add --toolchain nightly rust-src clippy rustfmt
rustup target add x86_64-unknown-linux-gnu x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf aarch64-unknown-none-softfloat
rustup target add loongarch64-unknown-none-softfloat
Sources: .github/workflows/ci.yml(L15 - L19)
Workspace Structure
The percpu workspace contains two main crates with distinct development considerations:
Crate | Purpose | Development Focus |
---|---|---|
percpu | Runtime implementation | Architecture-specific assembly, memory management, register access |
percpu_macros | Procedural macros | Code generation, AST manipulation, cross-platform abstraction |
Contributors must understand both the runtime behavior and compile-time code generation when making changes that affect the public API.
Sources: Based on system architecture diagrams
Code Quality Standards
Formatting and Linting Requirements
All contributions must pass automated code quality checks enforced by the CI pipeline:
flowchart TD subgraph subGraph1["Quality Gates"] FMT_PASS["Formatting Compliant"] CLIPPY_PASS["Lint-free Code"] BUILD_PASS["All Targets Compile"] TEST_PASS["Tests Pass"] end subgraph subGraph0["Code Quality Pipeline"] SUBMIT["Code Submission"] FMT_CHECK["cargo fmt --check"] CLIPPY_CHECK["cargo clippy"] BUILD_CHECK["Multi-target Build"] TEST_CHECK["Test Execution"] end BUILD_CHECK --> BUILD_PASS BUILD_PASS --> TEST_CHECK CLIPPY_CHECK --> CLIPPY_PASS CLIPPY_PASS --> BUILD_CHECK FMT_CHECK --> FMT_PASS FMT_PASS --> CLIPPY_CHECK SUBMIT --> FMT_CHECK TEST_CHECK --> TEST_PASS
Pre-submission Checklist:
- Run
cargo fmt --all -- --check
to verify formatting - Run
cargo clippy --target <TARGET> --features "preempt,arm-el2"
for all supported targets - Ensure all builds pass:
cargo build --target <TARGET> --features "preempt,arm-el2"
- Execute tests for applicable targets
Sources: .github/workflows/ci.yml(L23 - L27)
Documentation Standards
The crate enforces strict documentation requirements through RUSTDOCFLAGS
configuration. All public APIs must include comprehensive documentation with examples.
Documentation Requirements:
- All public functions, structs, and macros must have doc comments
- Examples should demonstrate real-world usage patterns
- Architecture-specific behavior must be clearly documented
- Breaking changes require CHANGELOG.md updates
Sources: .github/workflows/ci.yml(L42)
Architecture-Specific Contribution Guidelines
Cross-Platform Code Generation
When contributing to architecture-specific functionality, developers must understand the code generation pipeline for each supported platform:
flowchart TD subgraph subGraph2["Feature Variations"] NAIVE["sp-naive: Global variables"] PREEMPT["preempt: NoPreemptGuard"] ARM_EL2["arm-el2: EL2 registers"] end subgraph subGraph1["Supported Architectures"] X86_IMPL["x86_64: GS_BASE register"] ARM_IMPL["AArch64: TPIDR_EL1/EL2"] RISCV_IMPL["RISC-V: gp register"] LOONG_IMPL["LoongArch: r21 register"] end subgraph subGraph0["Architecture Contributions"] MACRO_GEN["Macro Code Generation"] ASM_GEN["Assembly Code Generation"] REG_ACCESS["Register Access Patterns"] end ASM_GEN --> ARM_IMPL ASM_GEN --> LOONG_IMPL ASM_GEN --> RISCV_IMPL ASM_GEN --> X86_IMPL MACRO_GEN --> ARM_IMPL MACRO_GEN --> LOONG_IMPL MACRO_GEN --> RISCV_IMPL MACRO_GEN --> X86_IMPL REG_ACCESS --> ARM_EL2 REG_ACCESS --> NAIVE REG_ACCESS --> PREEMPT
Architecture Addition Requirements:
- Implement register access patterns in
percpu_macros/src/lib.rs
- Add target-specific assembly generation
- Update feature flag handling for new architecture
- Add comprehensive tests for the new platform
- Update CI configuration to include new target
Sources: Based on system architecture and CHANGELOG.md(L25)
Feature Flag Considerations
New features must integrate with the existing feature flag system:
Feature | Impact | Testing Requirements |
---|---|---|
sp-naive | Single-CPU fallback implementation | Test with and without feature |
preempt | Preemption-safe operations | Test atomic behavior |
arm-el2 | AArch64 EL2 privilege level | Test on EL2-capable systems |
Sources: .github/workflows/ci.yml(L25) CHANGELOG.md(L43)
Testing and Validation
Multi-Target Testing Strategy
Contributors must validate changes across all supported target architectures. The CI system provides the definitive testing matrix:
flowchart TD subgraph subGraph2["Validation Criteria"] LINUX_NATIVE["Linux Native Execution"] CROSS_COMPILE["Cross-compilation Success"] FEATURE_COMPAT["Feature Compatibility"] end subgraph subGraph1["Test Execution"] UNIT["Unit Tests"] INTEGRATION["Integration Tests"] BUILD["Build Verification"] end subgraph subGraph0["Testing Matrix"] TARGETS["Target Architectures"] FEATURES["Feature Combinations"] ENVS["Execution Environments"] end BUILD --> FEATURE_COMPAT ENVS --> UNIT FEATURES --> UNIT INTEGRATION --> CROSS_COMPILE TARGETS --> UNIT UNIT --> LINUX_NATIVE
Testing Commands:
- Linux native:
cargo test --target x86_64-unknown-linux-gnu --features "sp-naive"
- Standard features:
cargo test --target x86_64-unknown-linux-gnu
- Cross-compilation:
cargo build --target <TARGET> --features "preempt,arm-el2"
Sources: .github/workflows/ci.yml(L29 - L32)
Test Coverage Requirements
New functionality requires comprehensive test coverage including:
- Unit tests for core functionality
- Integration tests for cross-component interactions
- Architecture-specific validation where applicable
- Feature flag combination testing
Pull Request Workflow
Submission Requirements
Pull requests must satisfy all CI checks before review consideration:
- Automated Checks: All CI jobs must pass successfully
- Code Review: At least one maintainer approval required
- Testing: Comprehensive test coverage for new functionality
- Documentation: Updated documentation for API changes
- Changelog: Version-appropriate changelog entries
API Evolution Guidelines
Based on the changelog history, API changes follow specific patterns:
Breaking Changes (Major Version):
- Function signature modifications
- Public API restructuring
- Initialization process changes
Compatible Changes (Minor Version):
- New architecture support
- Additional feature flags
- New accessor methods
Patch Changes:
- Bug fixes
- Internal optimizations
- Documentation improvements
Sources: CHANGELOG.md(L1 - L48)
Review Criteria
Maintainers evaluate contributions based on:
- Correctness: Proper per-CPU data isolation
- Safety: Memory safety and race condition prevention
- Performance: Minimal overhead for per-CPU access
- Portability: Cross-platform compatibility
- Maintainability: Clear, well-documented code
Documentation and Release Process
Documentation Generation
The project automatically generates and deploys documentation to GitHub Pages for the main branch. Contributors should ensure their changes integrate properly with the documentation pipeline.
Documentation Commands:
- Local generation:
cargo doc --no-deps
- Link checking: Automated through
RUSTDOCFLAGS
- Deployment: Automatic on main branch merge
Sources: .github/workflows/ci.yml(L34 - L55)
Release Coordination
Version releases require careful coordination across both crates in the workspace. Contributors should coordinate with maintainers for:
- API stability guarantees
- Cross-crate compatibility
- Architecture support matrix updates
- Feature flag deprecation schedules
This ensures the percpu ecosystem maintains consistent behavior across all supported platforms and use cases.
Sources: CHANGELOG.md(L1 - L48) .github/workflows/ci.yml(L1 - L56)
Overview
Relevant source files
Purpose and Scope
The axdriver_crates
repository provides a comprehensive device driver framework designed specifically for the ArceOS operating system in no_std
environments. This workspace contains modular driver abstractions, concrete hardware implementations, and infrastructure components that enable unified device management across block storage, networking, graphics, and virtualized devices.
This document covers the overall workspace structure, architectural patterns, and relationships between the driver crates. For detailed information about specific driver interfaces, see Foundation Layer (axdriver_base), Network Drivers, Block Storage Drivers, Display Drivers, and VirtIO Integration.
Sources: Cargo.toml(L1 - L29) README.md(L1 - L11)
Workspace Architecture
The axdriver_crates
workspace is organized as a hierarchical collection of six core crates, each serving distinct roles in the driver ecosystem:
Workspace Crate Structure
flowchart TD subgraph subGraph3["axdriver_crates Workspace"] WS["axdriver_cratesCargo Workspace"] subgraph Infrastructure["Infrastructure"] PCI["axdriver_pciPCI Bus Operations"] VIRTIO["axdriver_virtioVirtIO Device Wrappers"] end subgraph subGraph1["Device Abstractions"] BLOCK["axdriver_blockBlockDriverOps"] NET["axdriver_netNetDriverOps, NetBuf"] DISPLAY["axdriver_displayDisplayDriverOps"] end subgraph Foundation["Foundation"] BASE["axdriver_baseBaseDriverOps, DeviceType"] end end BASE --> BLOCK BASE --> DISPLAY BASE --> NET BASE --> PCI BASE --> VIRTIO WS --> BASE WS --> BLOCK WS --> DISPLAY WS --> NET WS --> PCI WS --> VIRTIO
Sources: Cargo.toml(L4 - L11) README.md(L5 - L10)
Crate Dependencies and Responsibilities
Crate | Primary Traits/Types | Purpose |
---|---|---|
axdriver_base | BaseDriverOps,DeviceType,DevResult | Foundation interfaces and error handling |
axdriver_block | BlockDriverOps | Block storage device abstractions |
axdriver_net | NetDriverOps,NetBuf,EthernetAddress | Network device interfaces and buffer management |
axdriver_display | DisplayDriverOps | Graphics and display device abstractions |
axdriver_pci | PCI device enumeration | PCI bus operations and device discovery |
axdriver_virtio | VirtIoBlkDev,VirtIoNetDev,VirtIoGpuDev | VirtIO device wrapper implementations |
Sources: Cargo.toml(L22 - L28) README.md(L5 - L10)
Driver Framework Architecture
The framework implements a trait-based architecture where all drivers extend common base functionality while implementing device-specific operations:
Trait Hierarchy and Implementation Pattern
flowchart TD subgraph subGraph2["Implementation Examples"] RAMDISK["RamDisk"] IXGBE["IxgbeNic"] VIRTIO_IMPL["VirtIoBlkDevVirtIoNetDevVirtIoGpuDev"] end subgraph subGraph1["Device-Specific Traits"] BLOCKOPS["BlockDriverOpsread_block()write_block()num_blocks()"] NETOPS["NetDriverOpsmac_address()transmit()receive()"] DISPLAYOPS["DisplayDriverOpsinfo()framebuffer()flush()"] end subgraph subGraph0["Core Foundation Types"] BASEOPS["BaseDriverOps"] DEVTYPE["DeviceType{Block, Net, Display, Char}"] DEVRESULT["DevResult"] DEVERROR["DevError"] end BASEOPS --> BLOCKOPS BASEOPS --> DISPLAYOPS BASEOPS --> NETOPS BLOCKOPS --> RAMDISK BLOCKOPS --> VIRTIO_IMPL DEVERROR --> DEVRESULT DEVRESULT --> BLOCKOPS DEVRESULT --> DISPLAYOPS DEVRESULT --> NETOPS DEVTYPE --> BASEOPS DISPLAYOPS --> VIRTIO_IMPL NETOPS --> IXGBE NETOPS --> VIRTIO_IMPL
Sources: Cargo.toml(L22 - L28) README.md(L5 - L10)
Compilation and Feature Management
The workspace is designed for selective compilation through Cargo features, enabling minimal deployments for resource-constrained environments:
Build Configuration Structure
flowchart TD subgraph subGraph2["Version Management"] VERSION["workspace.packageversion = '0.1.2'resolver = '2'"] end subgraph subGraph1["Target Environments"] EMBEDDED["Embedded SystemsCore traits only"] DESKTOP["Desktop/ServerFull driver set"] VIRTUALIZED["VM EnvironmentVirtIO focus"] end subgraph subGraph0["Workspace Dependencies"] WORKSPACE_DEPS["[workspace.dependencies]axdriver_base = {path, version}axdriver_block = {path, version}axdriver_net = {path, version}"] end VERSION --> WORKSPACE_DEPS WORKSPACE_DEPS --> DESKTOP WORKSPACE_DEPS --> EMBEDDED WORKSPACE_DEPS --> VIRTUALIZED
The workspace uses Cargo resolver version 2 and maintains consistent versioning across all member crates at version 0.1.2
. Each crate can be compiled independently or as part of larger driver collections based on deployment requirements.
Sources: Cargo.toml(L2) Cargo.toml(L13 - L14) Cargo.toml(L22 - L28)
Project Metadata and Licensing
The repository supports multiple licensing options (GPL-3.0-or-later, Apache-2.0, MulanPSL-2.0) and targets the categories ["os", "no-std", "hardware-support"]
, indicating its focus on operating system development in embedded and systems programming contexts.
Sources: Cargo.toml(L13 - L21)
Architecture and Design
Relevant source files
Purpose and Scope
This document describes the architectural foundations and design principles that unify the axdriver_crates framework. It covers the trait hierarchy, modular organization, error handling patterns, and compilation strategies that enable building device drivers for ArceOS across different hardware platforms and virtualized environments.
For detailed information about specific device types, see Foundation Layer (axdriver_base), Network Drivers, Block Storage Drivers, Display Drivers, and VirtIO Integration. For build system details, see Development and Build Configuration.
Core Architectural Principles
The axdriver_crates framework follows several key design principles that promote modularity, type safety, and cross-platform compatibility:
Trait-Based Driver Interface
All device drivers implement a common trait hierarchy starting with BaseDriverOps
. This provides a uniform interface for device management while allowing device-specific functionality through specialized traits.
Zero-Cost Abstractions
The framework leverages Rust's type system and compilation features to provide high-level abstractions without runtime overhead. Device-specific functionality is conditionally compiled based on cargo features.
Hardware Abstraction Layering
The framework separates device logic from hardware-specific operations through abstraction layers, enabling the same driver interface to work with both native hardware and virtualized devices.
Trait Hierarchy and Inheritance Model
The framework establishes a clear inheritance hierarchy where all drivers share common operations while extending with device-specific capabilities.
Base Driver Operations
flowchart TD BaseDriverOps["BaseDriverOps• device_name()• device_type()"] DeviceType["DeviceType enum• Block• Char• Net• Display"] DevResult["DevResult<T>Result<T, DevError>"] DevError["DevError enum• AlreadyExists• Again• BadState• InvalidParam• Io• NoMemory• ResourceBusy• Unsupported"] NetDriverOps["NetDriverOps• mac_address()• can_transmit/receive()• rx/tx_queue_size()• transmit/receive()• alloc_tx_buffer()"] BlockDriverOps["BlockDriverOps• num_blocks()• block_size()• read_block/write_block()• flush()"] DisplayDriverOps["DisplayDriverOps• info/framebuffer• flush display"] BaseDriverOps --> BlockDriverOps BaseDriverOps --> DevResult BaseDriverOps --> DeviceType BaseDriverOps --> DisplayDriverOps BaseDriverOps --> NetDriverOps DevResult --> DevError
Diagram: Core Trait Hierarchy and Type System
Sources: axdriver_base/src/lib.rs(L18 - L62) axdriver_net/src/lib.rs(L24 - L68) axdriver_block/src/lib.rs(L15 - L38)
Device-Specific Extensions
Each device type extends the base operations with specialized functionality:
Trait | Key Operations | Buffer Management | Hardware Integration |
---|---|---|---|
NetDriverOps | transmit(),receive(),mac_address() | NetBufPtr,NetBufPool | NIC hardware, packet processing |
BlockDriverOps | read_block(),write_block(),flush() | Direct buffer I/O | Storage controllers, file systems |
DisplayDriverOps | framebuffer(),flush() | Framebuffer management | Graphics hardware, display output |
Sources: axdriver_net/src/lib.rs(L24 - L68) axdriver_block/src/lib.rs(L15 - L38)
Device Type Abstraction System
The framework uses a centralized device type system that enables uniform device discovery and management across different hardware platforms.
flowchart TD subgraph subGraph2["Device Registration"] DriverManager["Device driver registration"] TypeMatching["Device type matching"] DriverBinding["Driver binding"] end subgraph subGraph1["VirtIO Device Discovery"] VirtIOProbe["probe_mmio_device()"] VirtIOPCIProbe["probe_pci_device()"] VirtIOTypeConv["as_dev_type()"] end subgraph subGraph0["Native Device Discovery"] PCIProbe["PCI device probing"] MMIOProbe["MMIO device discovery"] HardwareEnum["Hardware enumeration"] end DeviceType["DeviceType enum"] DeviceType --> TypeMatching DriverBinding --> DriverManager MMIOProbe --> DeviceType PCIProbe --> DeviceType TypeMatching --> DriverBinding VirtIOPCIProbe --> VirtIOTypeConv VirtIOProbe --> VirtIOTypeConv VirtIOTypeConv --> DeviceType
Diagram: Device Type Abstraction and Discovery Flow
The DeviceType
enum serves as the central abstraction that maps hardware devices to appropriate driver implementations. The VirtIO integration demonstrates this pattern through type conversion functions that translate VirtIO-specific device types to the common DeviceType
enumeration.
Sources: axdriver_base/src/lib.rs(L18 - L29) axdriver_virtio/src/lib.rs(L71 - L79) axdriver_virtio/src/lib.rs(L38 - L69)
Error Handling Design
The framework implements a unified error handling strategy through the DevResult<T>
type and standardized error codes in DevError
.
Error Code Mapping
The framework provides consistent error semantics across different hardware backends through error code translation:
flowchart TD subgraph subGraph2["Driver Operations"] NetOps["NetDriverOps methods"] BlockOps["BlockDriverOps methods"] BaseOps["BaseDriverOps methods"] end subgraph subGraph1["Error Types"] QueueFull["QueueFull → BadState"] NotReady["NotReady → Again"] InvalidParam["InvalidParam → InvalidParam"] DmaError["DmaError → NoMemory"] IoError["IoError → Io"] Unsupported["Unsupported → Unsupported"] end subgraph subGraph0["VirtIO Error Translation"] VirtIOError["virtio_drivers::Error"] ErrorMapping["as_dev_err() function"] DevError["DevError enum"] end BaseOps --> DevError BlockOps --> DevError ErrorMapping --> DevError ErrorMapping --> DmaError ErrorMapping --> InvalidParam ErrorMapping --> IoError ErrorMapping --> NotReady ErrorMapping --> QueueFull ErrorMapping --> Unsupported NetOps --> DevError VirtIOError --> ErrorMapping
Diagram: Unified Error Handling Architecture
This design enables consistent error handling across different driver implementations while preserving semantic meaning of hardware-specific error conditions.
Sources: axdriver_base/src/lib.rs(L31 - L53) axdriver_virtio/src/lib.rs(L82 - L97)
Modular Workspace Organization
The framework employs a modular workspace structure that promotes code reuse and selective compilation.
Workspace Dependency Graph
flowchart TD subgraph subGraph3["External Dependencies"] virtio_drivers["virtio-drivers crate"] hardware_crates["Hardware-specific cratesfxmac_rsixgbe-driver"] end subgraph subGraph2["Hardware Integration"] axdriver_pci["axdriver_pciPCI bus operations"] axdriver_virtio["axdriver_virtioVirtIO device wrappersTransport abstraction"] end subgraph subGraph1["Device Abstractions"] axdriver_net["axdriver_netNetDriverOpsNetBuf systemEthernetAddress"] axdriver_block["axdriver_blockBlockDriverOpsBlock I/O"] axdriver_display["axdriver_displayDisplayDriverOpsGraphics"] end subgraph Foundation["Foundation"] axdriver_base["axdriver_baseBaseDriverOpsDeviceTypeDevError"] end axdriver_base --> axdriver_block axdriver_base --> axdriver_display axdriver_base --> axdriver_net axdriver_base --> axdriver_pci axdriver_base --> axdriver_virtio axdriver_net --> hardware_crates axdriver_pci --> axdriver_block axdriver_pci --> axdriver_net axdriver_virtio --> virtio_drivers
Diagram: Workspace Module Dependencies
This organization allows applications to selectively include only the device drivers needed for their target platform, minimizing binary size and compilation time.
Sources: axdriver_base/src/lib.rs(L1 - L15) axdriver_net/src/lib.rs(L6 - L11) axdriver_block/src/lib.rs(L6 - L10) axdriver_virtio/src/lib.rs(L16 - L28)
Feature-Based Compilation Strategy
The framework uses cargo features to enable conditional compilation of device drivers and their dependencies.
Feature Organization Pattern
Crate | Features | Purpose |
---|---|---|
axdriver_net | ixgbe,fxmac | Specific NIC hardware support |
axdriver_block | ramdisk,bcm2835-sdhci | Storage device implementations |
axdriver_virtio | block,net,gpu | VirtIO device types |
This pattern enables several deployment scenarios:
- Minimal embedded: Only essential drivers compiled in
- Full server: All available drivers for maximum hardware compatibility
- Virtualized: Only VirtIO drivers for VM environments
- Development: All drivers for testing and validation
Sources: axdriver_net/src/lib.rs(L6 - L11) axdriver_block/src/lib.rs(L6 - L10) axdriver_virtio/src/lib.rs(L16 - L21)
Hardware Abstraction Patterns
The framework implements two primary patterns for hardware abstraction:
Direct Hardware Integration
For native hardware drivers, the framework integrates with hardware-specific crates that provide low-level device access. This pattern is used by drivers like FXmac and Intel ixgbe network controllers.
Virtualization Layer Integration
For virtualized environments, the framework provides adapter implementations that wrap external VirtIO drivers to implement the common trait interfaces. This enables the same driver API to work across both physical and virtual hardware.
The VirtIO integration demonstrates sophisticated error translation and type conversion to maintain API compatibility while leveraging mature external driver implementations.
Sources: axdriver_virtio/src/lib.rs(L38 - L97) axdriver_net/src/lib.rs(L6 - L11)
Foundation Layer (axdriver_base)
Relevant source files
Purpose and Scope
The axdriver_base
crate provides the foundational traits, types, and error handling mechanisms that underpin all device drivers in the ArceOS driver ecosystem. This crate defines the minimal common interface that every device driver must implement, regardless of device type or specific functionality.
This document covers the core abstractions and interfaces defined in axdriver_base
. For device-specific driver interfaces that extend these foundations, see Network Drivers, Block Storage Drivers, and Display Drivers. For information about how these foundations integrate with virtualized environments, see VirtIO Integration.
Core Device Type Classification
The foundation layer establishes a clear taxonomy of supported device types through the DeviceType
enumeration. This classification system enables type-safe device management and routing throughout the driver framework.
Device Type Enumeration
flowchart TD DeviceType["DeviceType enum"] Block["BlockStorage devices(disks, SSDs)"] Char["CharCharacter devices(serial ports)"] Net["NetNetwork devices(NICs, WiFi)"] Display["DisplayGraphics devices(GPUs, framebuffers)"] BlockDrivers["Block driver implementations"] CharDrivers["Character driver implementations"] NetDrivers["Network driver implementations"] DisplayDrivers["Display driver implementations"] Block --> BlockDrivers Char --> CharDrivers DeviceType --> Block DeviceType --> Char DeviceType --> Display DeviceType --> Net Display --> DisplayDrivers Net --> NetDrivers
The DeviceType
enum provides a standardized way to categorize hardware devices, enabling the driver framework to route operations to appropriate specialized driver interfaces.
Sources: axdriver_base/src/lib.rs(L18 - L29)
Error Handling System
The foundation layer establishes a unified error handling approach across all drivers through the DevError
enumeration and DevResult
type alias. This system provides consistent error semantics regardless of the underlying hardware or driver implementation.
Error Type Hierarchy
flowchart TD DevError["DevError enum"] AlreadyExists["AlreadyExistsEntity conflict"] Again["AgainNon-blocking retry"] BadState["BadStateInvalid driver state"] InvalidParam["InvalidParamBad arguments"] Io["IoHardware I/O failure"] NoMemory["NoMemoryDMA allocation failure"] ResourceBusy["ResourceBusyHardware unavailable"] Unsupported["UnsupportedUnimplemented operation"] DevResult["DevResult<T> = Result<T, DevError>"] DevError --> Again DevError --> AlreadyExists DevError --> BadState DevError --> DevResult DevError --> InvalidParam DevError --> Io DevError --> NoMemory DevError --> ResourceBusy DevError --> Unsupported
Error Categories
Error Type | Use Case | Typical Scenarios |
---|---|---|
AlreadyExists | Resource conflicts | Device already registered, buffer already allocated |
Again | Non-blocking operations | Queue full, temporary unavailability |
BadState | Driver state issues | Uninitialized device, invalid configuration |
InvalidParam | Parameter validation | Invalid buffer size, out-of-range values |
Io | Hardware failures | Device timeout, communication errors |
NoMemory | Memory allocation | DMA buffer allocation failure |
ResourceBusy | Concurrent access | Device locked by another process |
Unsupported | Feature availability | Optional hardware features not present |
Sources: axdriver_base/src/lib.rs(L31 - L53)
Base Driver Interface
All device drivers in the framework must implement the BaseDriverOps
trait, which provides the minimal set of operations required for device identification and type classification.
BaseDriverOps Trait Structure
flowchart TD subgraph subGraph1["Core Methods"] DeviceName["device_name() -> &str"] DeviceTypeMethod["device_type() -> DeviceType"] end subgraph subGraph0["Trait Requirements"] Bounds["Send + Sync bounds"] SendSync["Thread-safe driver instances"] end BaseDriverOps["BaseDriverOps trait"] NameString["Human-readable device identifier"] TypeEnum["DeviceType classification"] BaseDriverOps --> Bounds BaseDriverOps --> DeviceName BaseDriverOps --> DeviceTypeMethod Bounds --> SendSync DeviceName --> NameString DeviceTypeMethod --> TypeEnum
Implementation Requirements
The BaseDriverOps
trait enforces two critical requirements:
- Thread Safety: All drivers must implement
Send + Sync
, enabling safe concurrent access across threads - Device Identification: Every driver must provide both a human-readable name and a type classification
These requirements ensure that the driver framework can safely manage device instances in multi-threaded environments while maintaining clear device identification.
Sources: axdriver_base/src/lib.rs(L55 - L62)
Integration with Specialized Driver Types
The foundation layer serves as the building block for all specialized device driver interfaces. Each device-specific crate extends these core abstractions with domain-specific functionality.
Driver Trait Hierarchy
flowchart TD subgraph subGraph2["Concrete Implementations"] FXmacNic["FXmacNic"] IxgbeNic["IxgbeNic"] VirtIoNetDev["VirtIoNetDev"] RamDisk["RamDisk"] SDHCIDriver["SDHCIDriver"] VirtIoBlkDev["VirtIoBlkDev"] VirtIoGpuDev["VirtIoGpuDev"] end subgraph subGraph1["Specialized Interfaces"] NetDriverOps["NetDriverOps(axdriver_net)"] BlockDriverOps["BlockDriverOps(axdriver_block)"] DisplayDriverOps["DisplayDriverOps(axdriver_display)"] end subgraph subGraph0["Foundation Layer"] BaseDriverOps["BaseDriverOps(axdriver_base)"] DevError["DevError"] DevResult["DevResult<T>"] DeviceType["DeviceType"] end BaseDriverOps --> BlockDriverOps BaseDriverOps --> DisplayDriverOps BaseDriverOps --> NetDriverOps BlockDriverOps --> RamDisk BlockDriverOps --> SDHCIDriver BlockDriverOps --> VirtIoBlkDev DisplayDriverOps --> VirtIoGpuDev NetDriverOps --> FXmacNic NetDriverOps --> IxgbeNic NetDriverOps --> VirtIoNetDev
Extension Pattern
Each specialized driver interface follows a consistent extension pattern:
- Inherit from
BaseDriverOps
for basic device identification - Extend with device-specific operations (e.g.,
transmit()
for network devices) - Utilize the common
DevResult<T>
error handling system - Classify using the appropriate
DeviceType
variant
This pattern ensures consistency across all driver types while allowing for domain-specific optimizations and functionality.
Sources: axdriver_base/src/lib.rs(L1 - L15)
No-std Compatibility
The foundation layer is designed for embedded and systems programming environments, maintaining #![no_std]
compatibility throughout. This design choice enables the driver framework to operate in resource-constrained environments and kernel contexts where the standard library is not available.
The no_std
constraint influences several design decisions:
- Error types use
Debug
trait instead ofstd::error::Error
- No heap allocations in core types
- Compatible with embedded target architectures
Sources: axdriver_base/src/lib.rs(L16)
Network Drivers
Relevant source files
Purpose and Scope
This document covers the network driver subsystem within the axdriver_crates framework, which provides traits, types, and implementations for Network Interface Card (NIC) drivers. The network subsystem is the most sophisticated component in the driver framework, featuring advanced buffer management, multiple hardware implementations, and support for both physical and virtualized network devices.
For information about the foundational driver traits that network drivers extend, see Foundation Layer (axdriver_base). For VirtIO network device integration, see VirtIO Integration.
Network Driver Architecture
The network driver subsystem follows a layered architecture where hardware-specific implementations conform to standardized traits while maintaining optimized performance paths for different types of network hardware.
Core Network Driver Interface
The NetDriverOps
trait defined in axdriver_net/src/lib.rs(L25 - L68) serves as the primary interface that all network drivers must implement. This trait extends BaseDriverOps
and provides network-specific operations:
flowchart TD BaseDriverOps["BaseDriverOps• device_name()• device_type()"] NetDriverOps["NetDriverOps• mac_address()• can_transmit/receive()• rx/tx_queue_size()• transmit/receive()• buffer management"] EthernetAddress["EthernetAddress6-byte MAC address"] NetBufPtr["NetBufPtrRaw buffer pointer• raw_ptr• buf_ptr• len"] BaseDriverOps --> NetDriverOps NetDriverOps --> EthernetAddress NetDriverOps --> NetBufPtr
Title: Network Driver Interface Hierarchy
The interface provides both synchronous and asynchronous patterns through methods like can_transmit()
and can_receive()
for non-blocking operations, while receive()
returns DevError::Again
when no packets are available.
Sources: axdriver_net/src/lib.rs(L25 - L68)
Buffer Management System
Network drivers use a sophisticated buffer management system centered around NetBufPtr
and related types. The NetBufPtr
structure provides zero-copy buffer operations for high-performance networking:
Field | Type | Purpose |
---|---|---|
raw_ptr | NonNull | Pointer to original object for deallocation |
buf_ptr | NonNull | Pointer to actual network data |
len | usize | Length of network packet data |
flowchart TD NetBufPool["NetBufPoolMemory allocation pool"] NetBuf["NetBufManaged buffer wrapper"] NetBufBox["NetBufBoxOwned buffer container"] NetBufPtr["NetBufPtrRaw pointer wrapper• raw_ptr: NonNull<u8>• buf_ptr: NonNull<u8>• len: usize"] PacketData["&[u8] packet data"] PacketDataMut["&mut [u8] packet data"] RawPtr["*mut T raw pointer"] NetBuf --> NetBufBox NetBufBox --> NetBufPtr NetBufPool --> NetBuf NetBufPtr --> PacketData NetBufPtr --> PacketDataMut NetBufPtr --> RawPtr
Title: Network Buffer Management Architecture
The buffer system supports both pre-allocated buffer pools for high-performance drivers and dynamic allocation for simpler implementations. The separation between raw_ptr
and buf_ptr
enables complex memory layouts where the network data is embedded within larger structures.
Sources: axdriver_net/src/lib.rs(L19) axdriver_net/src/lib.rs(L70 - L108)
Hardware Driver Implementations
The network subsystem includes two primary hardware implementations with different architectural approaches:
Feature-Based Driver Selection
Network drivers are compiled conditionally based on Cargo features, enabling targeted builds for specific hardware platforms:
Feature | Driver | Hardware Target | External Dependency |
---|---|---|---|
ixgbe | Intel ixgbe | 10 Gigabit Ethernet | ixgbe-drivercrate |
fxmac | FXmac | PhytiumPi Ethernet | fxmac_rscrate |
flowchart TD subgraph subGraph1["axdriver_net Implementation"] IxgbeNic["ixgbe::IxgbeNicNetDriverOps impl"] FxmacNic["fxmac::FxmacNicNetDriverOps impl"] end subgraph subGraph0["Hardware Abstraction Layer"] IxgbeDriver["ixgbe-driverExternal crateLow-level hardware access"] FxmacRs["fxmac_rsExternal cratePhytiumPi HAL"] end CargoFeatures["Cargo Features"] NetDriverOps["NetDriverOps trait"] CargoFeatures --> FxmacRs CargoFeatures --> IxgbeDriver FxmacNic --> NetDriverOps FxmacRs --> FxmacNic IxgbeDriver --> IxgbeNic IxgbeNic --> NetDriverOps
Title: Hardware Driver Integration Pattern
Each hardware implementation wraps an external hardware abstraction layer (HAL) crate and adapts it to the NetDriverOps
interface, providing a consistent API while preserving hardware-specific optimizations.
Sources: axdriver_net/Cargo.toml(L14 - L24) axdriver_net/src/lib.rs(L6 - L11)
Driver-Specific Patterns
Different network drivers employ distinct patterns based on their hardware characteristics:
- Intel ixgbe Driver: Uses memory pool allocation with
NetBufPool
for efficient buffer management in high-throughput scenarios - FXmac Driver: Implements queue-based receive/transmit operations optimized for embedded ARM platforms
The drivers abstract these differences behind the uniform NetDriverOps
interface while maintaining their performance characteristics through the flexible buffer management system.
Error Handling and Device States
Network drivers use the standardized error handling from axdriver_base
through DevResult
and DevError
types. Network-specific error conditions include:
DevError::Again
: Returned byreceive()
when no packets are available- Standard device errors for hardware failures and invalid operations
- Buffer allocation failures through the
alloc_tx_buffer()
method
The state management pattern allows drivers to report their current capabilities through can_transmit()
and can_receive()
methods, enabling efficient polling-based network stacks.
Sources: axdriver_net/src/lib.rs(L17) axdriver_net/src/lib.rs(L29 - L33) axdriver_net/src/lib.rs(L61 - L63)
Integration Points
The network driver subsystem integrates with several other components in the axdriver ecosystem:
- Foundation Layer: All network drivers implement
BaseDriverOps
for consistent device identification - VirtIO Integration: VirtIO network devices are wrapped to implement
NetDriverOps
- PCI Bus Operations: Physical network cards are discovered and initialized through PCI enumeration
- Build System: Conditional compilation enables platform-specific driver selection
This modular design allows the same network driver interface to support both bare-metal hardware and virtualized environments while maintaining optimal performance for each use case.
Sources: axdriver_net/src/lib.rs(L17) axdriver_net/Cargo.toml(L22)
Network Driver Interface
Relevant source files
Purpose and Scope
This document covers the core network driver interface defined by the NetDriverOps
trait and related types in the axdriver_net
crate. This interface provides the foundational abstraction layer for all network device drivers in the ArceOS driver framework.
For detailed information about the underlying foundation traits and error handling, see Foundation Layer. For specific buffer management implementations and memory pool strategies, see Network Buffer Management. For concrete hardware driver implementations that use this interface, see Hardware Implementations.
Network Driver Trait Architecture
The network driver interface builds upon the foundation layer through a clear trait hierarchy that extends BaseDriverOps
with network-specific functionality.
Trait Inheritance Structure
flowchart TD BaseDriverOps["BaseDriverOps• device_name()• device_type()"] NetDriverOps["NetDriverOps• mac_address()• can_transmit/receive()• transmit/receive()• buffer management"] DeviceType["DeviceType::Net"] DevResult["DevResult"] DevError["DevError"] EthernetAddress["EthernetAddress"] NetBufPtr["NetBufPtr"] BaseDriverOps --> DeviceType BaseDriverOps --> NetDriverOps DevResult --> DevError NetDriverOps --> DevResult NetDriverOps --> EthernetAddress NetDriverOps --> NetBufPtr
Sources: axdriver_net/src/lib.rs(L16 - L17) axdriver_net/src/lib.rs(L25 - L68)
The NetDriverOps
trait extends BaseDriverOps
to provide network-specific operations while maintaining compatibility with the unified driver framework. All network drivers must implement both trait interfaces to ensure consistent device management and network functionality.
Core Type Definitions
Type | Purpose | Definition Location |
---|---|---|
EthernetAddress | MAC address representation | axdriver_net/src/lib.rs22 |
NetBufPtr | Raw network buffer pointer | axdriver_net/src/lib.rs71-108 |
NetDriverOps | Primary network driver trait | axdriver_net/src/lib.rs25-68 |
Sources: axdriver_net/src/lib.rs(L19 - L22) axdriver_net/src/lib.rs(L71)
Core Network Operations
The NetDriverOps
trait defines the essential operations that all network drivers must implement, organized into device information, queue management, and packet processing categories.
Device Information and Capabilities
flowchart TD Driver["Network Driver"] MacAddr["mac_address()Returns EthernetAddress"] CanTx["can_transmit()Returns bool"] CanRx["can_receive()Returns bool"] TxQSize["tx_queue_size()Returns usize"] RxQSize["rx_queue_size()Returns usize"] Driver --> CanRx Driver --> CanTx Driver --> MacAddr Driver --> RxQSize Driver --> TxQSize
Sources: axdriver_net/src/lib.rs(L26 - L39)
These methods provide essential device characteristics:
mac_address()
returns the hardware ethernet address as anEthernetAddress
structcan_transmit()
andcan_receive()
indicate current device capability statustx_queue_size()
andrx_queue_size()
return the capacity of transmit and receive queues
Packet Processing Operations
The trait defines a complete packet processing lifecycle with explicit buffer management:
Method | Purpose | Parameters | Return Type |
---|---|---|---|
transmit() | Send packet to network | tx_buf: NetBufPtr | DevResult |
receive() | Receive packet from network | None | DevResult |
alloc_tx_buffer() | Allocate transmission buffer | size: usize | DevResult |
recycle_rx_buffer() | Return buffer to receive queue | rx_buf: NetBufPtr | DevResult |
recycle_tx_buffers() | Reclaim transmitted buffers | None | DevResult |
Sources: axdriver_net/src/lib.rs(L41 - L68)
Buffer Management Interface
The network interface uses NetBufPtr
as the primary buffer abstraction, providing a safe wrapper around raw memory pointers for network packet data.
NetBufPtr Structure and Operations
flowchart TD NetBufPtr["NetBufPtr"] RawPtr["raw_ptr: NonNullOriginal object pointer"] BufPtr["buf_ptr: NonNullBuffer data pointer"] Len["len: usizeBuffer length"] NewMethod["new(raw_ptr, buf_ptr, len)"] RawPtrMethod["raw_ptr() -> *mut T"] PacketMethod["packet() -> &[u8]"] PacketMutMethod["packet_mut() -> &mut [u8]"] PacketLenMethod["packet_len() -> usize"] NetBufPtr --> BufPtr NetBufPtr --> Len NetBufPtr --> NewMethod NetBufPtr --> PacketLenMethod NetBufPtr --> PacketMethod NetBufPtr --> PacketMutMethod NetBufPtr --> RawPtr NetBufPtr --> RawPtrMethod
Sources: axdriver_net/src/lib.rs(L71 - L108)
The NetBufPtr
design separates the original object pointer from the buffer data pointer, enabling:
- Safe reference counting of the underlying buffer object
- Direct access to packet data without additional indirection
- Flexible buffer layout for different hardware requirements
Buffer Lifecycle Management
sequenceDiagram participant Application as "Application" participant NetDriver as "NetDriver" participant Hardware as "Hardware" Note over Application,Hardware: Transmission Flow Application ->> NetDriver: alloc_tx_buffer(size) NetDriver -->> Application: NetBufPtr Application ->> Application: Fill packet data Application ->> NetDriver: transmit(NetBufPtr) NetDriver ->> Hardware: Send to hardware queue NetDriver ->> NetDriver: recycle_tx_buffers() Note over Application,Hardware: Reception Flow Application ->> NetDriver: recycle_rx_buffer(NetBufPtr) NetDriver ->> Hardware: Add to receive queue Hardware ->> NetDriver: Packet received NetDriver ->> NetDriver: receive() NetDriver -->> Application: NetBufPtr with data
Sources: axdriver_net/src/lib.rs(L41 - L68)
The buffer lifecycle ensures efficient memory management:
- Allocation:
alloc_tx_buffer()
provides buffers for transmission - Population: Applications fill buffers with packet data via
packet_mut()
- Transmission:
transmit()
queues buffers to hardware - Recycling:
recycle_tx_buffers()
reclaims completed transmissions - Reception:
receive()
returns buffers with incoming packets - Return:
recycle_rx_buffer()
returns buffers to the receive queue
Data Flow and Network Driver Integration
The network driver interface integrates with the broader ArceOS driver framework through standardized error handling and device type classification.
Integration with Foundation Layer
flowchart TD subgraph subGraph2["Hardware Implementations"] FxmacNic["FxmacNic"] IxgbeNic["IxgbeNic"] VirtIoNetDev["VirtIoNetDev"] end subgraph subGraph1["Network Layer (axdriver_net)"] NetDriverOps["NetDriverOps"] EthernetAddress["EthernetAddress"] NetBufPtr["NetBufPtr"] NetBuf["NetBuf"] NetBufPool["NetBufPool"] end subgraph subGraph0["Foundation Layer (axdriver_base)"] BaseDriverOps["BaseDriverOps"] DeviceType["DeviceType::Net"] DevResult["DevResult"] DevError["DevError"] end BaseDriverOps --> NetDriverOps DevResult --> NetDriverOps DeviceType --> NetDriverOps NetBuf --> NetBufPtr NetBufPool --> NetBuf NetBufPtr --> NetDriverOps NetDriverOps --> FxmacNic NetDriverOps --> IxgbeNic NetDriverOps --> VirtIoNetDev
Sources: axdriver_net/src/lib.rs(L16 - L19) axdriver_net/src/lib.rs(L25)
The network driver interface serves as the bridge between the generic driver framework and specific hardware implementations, providing:
- Consistent error handling through
DevResult
andDevError
types - Device type classification through
DeviceType::Net
- Standardized buffer management abstractions
- Hardware-agnostic network operations
This design enables multiple network hardware implementations to coexist while maintaining a unified programming interface for upper-layer network protocols and applications.
Network Buffer Management
Relevant source files
This document covers the sophisticated buffer allocation and management system used for high-performance network operations in the axdriver framework. The network buffer management provides efficient memory allocation, automatic resource cleanup, and optimized buffer layouts for packet processing.
For information about the network driver interface that uses these buffers, see Network Driver Interface. For details on how specific hardware implementations utilize this buffer system, see Hardware Implementations.
Buffer Structure and Layout
The core of the network buffer system is the NetBuf
structure, which provides a flexible buffer layout optimized for network packet processing with separate header and packet regions.
NetBuf Memory Layout
flowchart TD subgraph subGraph1["Size Tracking"] HL["header_len"] PL["packet_len"] CAP["capacity"] end subgraph subGraph0["NetBuf Memory Layout"] BP["buf_ptr"] H["Header Region"] P["Packet Region"] U["Unused Region"] end BP --> H CAP --> U H --> P HL --> H P --> U PL --> P
The NetBuf
structure implements a three-region memory layout where the header region stores protocol headers, the packet region contains the actual data payload, and unused space allows for future expansion without reallocation.
Sources: axdriver_net/src/net_buf.rs(L19 - L38)
Core Buffer Operations
Operation | Method | Purpose |
---|---|---|
Header Access | header() | Read-only access to header region |
Packet Access | packet()/packet_mut() | Access to packet data |
Combined Access | packet_with_header() | Contiguous header + packet view |
Raw Buffer | raw_buf()/raw_buf_mut() | Access to entire buffer |
Size Management | set_header_len()/set_packet_len() | Adjust region boundaries |
The buffer provides const methods for efficient access patterns and maintains safety through debug assertions that prevent region overlap.
Sources: axdriver_net/src/net_buf.rs(L43 - L103)
Memory Pool Architecture
The NetBufPool
provides high-performance buffer allocation through pre-allocated memory pools, eliminating the overhead of individual allocations during packet processing.
Pool Allocation Strategy
flowchart TD subgraph subGraph2["Allocation Process"] ALLOC["alloc()"] OFFSET["pop offset from free_list"] NETBUF["create NetBuf"] end subgraph subGraph1["Memory Layout"] B0["Buffer 0"] B1["Buffer 1"] B2["Buffer 2"] BN["Buffer N"] end subgraph subGraph0["NetBufPool Structure"] POOL["pool: Vec"] FREELIST["free_list: Mutex>"] META["capacity, buf_len"] end ALLOC --> OFFSET B0 --> B1 B1 --> B2 B2 --> BN FREELIST --> OFFSET META --> NETBUF OFFSET --> NETBUF POOL --> B0
The pool divides a large contiguous memory allocation into fixed-size buffers, maintaining a free list of available buffer offsets for O(1) allocation and deallocation operations.
Sources: axdriver_net/src/net_buf.rs(L133 - L165)
Pool Configuration and Constraints
Parameter | Range | Purpose |
---|---|---|
MIN_BUFFER_LEN | 1526 bytes | Minimum Ethernet frame size |
MAX_BUFFER_LEN | 65535 bytes | Maximum buffer allocation |
capacity | > 0 | Number of buffers in pool |
The pool validates buffer sizes against network protocol requirements and prevents invalid configurations through compile-time constants and runtime checks.
Sources: axdriver_net/src/net_buf.rs(L8 - L152)
Buffer Lifecycle Management
The network buffer system implements RAII (Resource Acquisition Is Initialization) patterns for automatic memory management and integration with raw pointer operations for hardware drivers.
Allocation and Deallocation Flow
stateDiagram-v2 [*] --> PoolFree : "Pool initialized" PoolFree --> Allocated : "alloc() / alloc_boxed()" Allocated --> InUse : "NetBuf created" InUse --> BufPtr : "into_buf_ptr()" BufPtr --> Restored : "from_buf_ptr()" Restored --> InUse : "NetBuf restored" InUse --> Deallocated : "Drop trait" BufPtr --> Deallocated : "Drop from ptr" Deallocated --> PoolFree : "dealloc()" PoolFree --> [*] : "Pool destroyed"
The lifecycle supports both high-level RAII management through Drop
implementation and low-level pointer conversion for hardware driver integration.
Sources: axdriver_net/src/net_buf.rs(L105 - L131)
Raw Pointer Integration
The buffer system provides conversion to and from NetBufPtr
for integration with hardware drivers that require raw memory pointers:
Operation | Method | Safety Requirements |
---|---|---|
To Pointer | into_buf_ptr() | Consumes NetBuf, transfers ownership |
From Pointer | from_buf_ptr() | Unsafe, requires priorinto_buf_ptr()call |
Pointer Creation | NetBufPtr::new() | Hardware driver responsibility |
This dual-mode operation allows the same buffer to be used efficiently in both safe Rust code and unsafe hardware interaction contexts.
Sources: axdriver_net/src/net_buf.rs(L104 - L123)
Thread Safety and Concurrency
The buffer management system provides thread-safe operations through careful synchronization design:
flowchart TD subgraph subGraph1["Concurrent Operations"] T1["Thread 1: alloc()"] T2["Thread 2: alloc()"] T3["Thread 3: drop()"] end subgraph subGraph0["Thread Safety Design"] NETBUF["NetBuf: Send + Sync"] POOL["NetBufPool: Arc"] FREELIST["free_list: Mutex>"] end NETBUF --> POOL POOL --> FREELIST T1 --> FREELIST T2 --> FREELIST T3 --> FREELIST
The NetBufPool
uses Arc
for shared ownership and Mutex
protection of the free list, enabling concurrent allocation and deallocation from multiple threads while maintaining memory safety.
Sources: axdriver_net/src/net_buf.rs(L40 - L207)
Integration with Network Drivers
The buffer management system integrates with the broader network driver ecosystem through standardized interfaces and efficient memory patterns designed for high-throughput network operations.
Buffer Type Relationships
flowchart TD subgraph subGraph1["Driver Integration"] NETDRIVEROPS["NetDriverOps"] TRANSMIT["transmit()"] RECEIVE["receive()"] ALLOC["alloc_tx_buffer()"] RECYCLE["recycle_rx_buffer()"] end subgraph subGraph0["Buffer Types"] NETBUF["NetBuf"] NETBUFBOX["NetBufBox = Box"] NETBUFPTR["NetBufPtr"] POOL["NetBufPool"] end ALLOC --> NETBUFBOX NETBUF --> NETBUFBOX NETBUFBOX --> NETBUFPTR NETDRIVEROPS --> ALLOC NETDRIVEROPS --> RECEIVE NETDRIVEROPS --> RECYCLE NETDRIVEROPS --> TRANSMIT POOL --> NETBUF RECEIVE --> NETBUFBOX RECYCLE --> NETBUFBOX TRANSMIT --> NETBUFBOX
Network drivers utilize the buffer system through the NetDriverOps
trait methods, providing standardized buffer allocation and recycling operations that maintain pool efficiency across different hardware implementations.
Sources: axdriver_net/src/net_buf.rs(L1 - L208)
Hardware Implementations
Relevant source files
This document covers the concrete network hardware driver implementations that provide device-specific functionality within the axdriver network subsystem. These implementations demonstrate how the abstract NetDriverOps
trait is realized for specific network controllers, including buffer management strategies and hardware abstraction layer integration.
For information about the network driver interface abstractions, see Network Driver Interface. For details about buffer management patterns, see Network Buffer Management.
Overview
The axdriver network subsystem includes two primary hardware implementations that showcase different architectural approaches:
Driver | Hardware Target | Buffer Strategy | External Dependencies |
---|---|---|---|
FXmacNic | PhytiumPi Ethernet Controller | Queue-based RX buffering | fxmac_rscrate |
IxgbeNic | Intel 10GbE Controller | Memory pool allocation | ixgbe-drivercrate |
Both implementations provide the same NetDriverOps
interface while handling hardware-specific details internally.
FXmac Network Controller Implementation
Architecture and Integration
The FXmacNic
struct provides an implementation for PhytiumPi Ethernet controllers using a queue-based approach for receive buffer management.
flowchart TD subgraph subGraph2["Trait Implementation"] BASE_OPS["BaseDriverOps"] NET_OPS["NetDriverOps"] DEVICE_NAME["device_name()"] MAC_ADDR["mac_address()"] RECEIVE["receive()"] TRANSMIT["transmit()"] end subgraph subGraph1["External Hardware Layer"] FXMAC_RS["fxmac_rs crate"] XMAC_INIT["xmac_init()"] GET_MAC["FXmacGetMacAddress()"] RECV_HANDLER["FXmacRecvHandler()"] TX_FUNC["FXmacLwipPortTx()"] end subgraph subGraph0["FXmacNic Driver Structure"] FXMAC_STRUCT["FXmacNic"] INNER["inner: &'static mut FXmac"] HWADDR["hwaddr: [u8; 6]"] RX_QUEUE["rx_buffer_queue: VecDeque"] end BASE_OPS --> DEVICE_NAME FXMAC_STRUCT --> BASE_OPS FXMAC_STRUCT --> HWADDR FXMAC_STRUCT --> INNER FXMAC_STRUCT --> NET_OPS FXMAC_STRUCT --> RX_QUEUE HWADDR --> GET_MAC INNER --> XMAC_INIT NET_OPS --> MAC_ADDR NET_OPS --> RECEIVE NET_OPS --> TRANSMIT RECEIVE --> RECV_HANDLER TRANSMIT --> TX_FUNC
Sources: axdriver_net/src/fxmac.rs(L1 - L145)
Initialization and Hardware Integration
The FXmacNic::init()
function demonstrates the initialization pattern for hardware-dependent drivers:
flowchart TD subgraph subGraph0["Initialization Sequence"] MAPPED_REGS["mapped_regs: usize"] INIT_QUEUE["VecDeque::with_capacity(QS)"] GET_MAC_ADDR["FXmacGetMacAddress(&mut hwaddr, 0)"] XMAC_INIT_CALL["xmac_init(&hwaddr)"] CONSTRUCT["FXmacNic { inner, hwaddr, rx_buffer_queue }"] end GET_MAC_ADDR --> XMAC_INIT_CALL INIT_QUEUE --> GET_MAC_ADDR MAPPED_REGS --> INIT_QUEUE XMAC_INIT_CALL --> CONSTRUCT
The driver uses a fixed queue size of 64 (QS = 64
) for both receive and transmit operations, defined as a constant.
Sources: axdriver_net/src/fxmac.rs(L16) axdriver_net/src/fxmac.rs(L30 - L45)
Buffer Management Strategy
The FXmac implementation uses a simple queue-based approach for managing received packets:
- Receive Path: Uses
VecDeque<NetBufPtr>
to queue incoming packets fromFXmacRecvHandler()
- Transmit Path: Allocates
Box<Vec<u8>>
for each transmission - Buffer Recycling: Drops allocated boxes directly using
Box::from_raw()
The receive()
method demonstrates this pattern by first checking the local queue, then polling the hardware.
Sources: axdriver_net/src/fxmac.rs(L92 - L118) axdriver_net/src/fxmac.rs(L134 - L144)
Intel ixgbe Network Controller Implementation
Architecture and Memory Pool Integration
The IxgbeNic
struct implements a more sophisticated memory management approach using pre-allocated memory pools:
flowchart TD subgraph subGraph2["Generic Parameters"] HAL_PARAM["H: IxgbeHal"] QS_PARAM["QS: queue size"] QN_PARAM["QN: queue number"] end subgraph subGraph1["External Hardware Layer"] IXGBE_DRIVER["ixgbe-driver crate"] IXGBE_DEVICE["IxgbeDevice"] MEM_POOL_TYPE["MemPool"] IXGBE_NETBUF["IxgbeNetBuf"] NIC_DEVICE["NicDevice trait"] end subgraph subGraph0["IxgbeNic Driver Structure"] IXGBE_STRUCT["IxgbeNic"] INNER_DEV["inner: IxgbeDevice"] MEM_POOL["mem_pool: Arc"] RX_QUEUE["rx_buffer_queue: VecDeque"] end INNER_DEV --> IXGBE_DEVICE IXGBE_DEVICE --> IXGBE_NETBUF IXGBE_DEVICE --> NIC_DEVICE IXGBE_STRUCT --> HAL_PARAM IXGBE_STRUCT --> INNER_DEV IXGBE_STRUCT --> MEM_POOL IXGBE_STRUCT --> QN_PARAM IXGBE_STRUCT --> QS_PARAM IXGBE_STRUCT --> RX_QUEUE MEM_POOL --> MEM_POOL_TYPE
Sources: axdriver_net/src/ixgbe.rs(L18 - L28)
Memory Pool Configuration
The ixgbe driver uses predefined memory pool parameters for efficient buffer allocation:
Parameter | Value | Purpose |
---|---|---|
MEM_POOL | 4096 | Total memory pool entries |
MEM_POOL_ENTRY_SIZE | 2048 | Size per pool entry in bytes |
RX_BUFFER_SIZE | 1024 | Receive buffer queue capacity |
RECV_BATCH_SIZE | 64 | Batch size for receive operations |
These constants ensure optimal performance for high-throughput network operations.
Sources: axdriver_net/src/ixgbe.rs(L13 - L16)
NetBufPtr Conversion Pattern
The ixgbe implementation demonstrates a sophisticated buffer conversion pattern between IxgbeNetBuf
and NetBufPtr
:
flowchart TD subgraph subGraph0["Buffer Conversion Flow"] IXGBE_BUF["IxgbeNetBuf"] MANUALLY_DROP["ManuallyDrop::new(buf)"] PACKET_PTR["buf.packet_mut().as_mut_ptr()"] NET_BUF_PTR["NetBufPtr::new(raw_ptr, buf_ptr, len)"] subgraph subGraph1["Reverse Conversion"] NET_BUF_PTR_REV["NetBufPtr"] CONSTRUCT["IxgbeNetBuf::construct()"] IXGBE_BUF_REV["IxgbeNetBuf"] POOL_ENTRY["buf.pool_entry() as *mut u8"] end end CONSTRUCT --> IXGBE_BUF_REV IXGBE_BUF --> MANUALLY_DROP MANUALLY_DROP --> PACKET_PTR MANUALLY_DROP --> POOL_ENTRY NET_BUF_PTR_REV --> CONSTRUCT PACKET_PTR --> NET_BUF_PTR POOL_ENTRY --> NET_BUF_PTR
The From<IxgbeNetBuf>
implementation uses ManuallyDrop
to avoid premature deallocation while transferring ownership to the NetBufPtr
abstraction.
Sources: axdriver_net/src/ixgbe.rs(L143 - L162)
Trait Implementation Patterns
BaseDriverOps Implementation
Both drivers implement BaseDriverOps
with device-specific identification:
Driver | device_name()Return Value | Source |
---|---|---|
FXmacNic | "cdns,phytium-gem-1.0" | Hardware device tree compatible string |
IxgbeNic | self.inner.get_driver_name() | Delegated to underlying driver |
Sources: axdriver_net/src/fxmac.rs(L48 - L56) axdriver_net/src/ixgbe.rs(L50 - L58)
NetDriverOps Core Methods
The receive and transmit implementations showcase different architectural approaches:
flowchart TD subgraph subGraph1["Ixgbe Receive Pattern"] IXGBE_CAN_RECV["can_receive(): !queue.is_empty() || inner.can_receive(0)"] IXGBE_RECV_CHECK["Check local queue first"] IXGBE_BATCH_RECV["inner.receive_packets(0, RECV_BATCH_SIZE, closure)"] IXGBE_QUEUE_PUSH["Closure pushes to rx_buffer_queue"] FXMAC_CAN_RECV["can_receive(): !rx_buffer_queue.is_empty()"] FXMAC_RECV_CHECK["Check local queue first"] FXMAC_HW_POLL["FXmacRecvHandler(self.inner)"] FXMAC_QUEUE_PUSH["Push to rx_buffer_queue"] end subgraph subGraph0["FXmac Receive Pattern"] IXGBE_CAN_RECV["can_receive(): !queue.is_empty() || inner.can_receive(0)"] IXGBE_RECV_CHECK["Check local queue first"] IXGBE_BATCH_RECV["inner.receive_packets(0, RECV_BATCH_SIZE, closure)"] IXGBE_QUEUE_PUSH["Closure pushes to rx_buffer_queue"] FXMAC_CAN_RECV["can_receive(): !rx_buffer_queue.is_empty()"] FXMAC_RECV_CHECK["Check local queue first"] FXMAC_HW_POLL["FXmacRecvHandler(self.inner)"] FXMAC_QUEUE_PUSH["Push to rx_buffer_queue"] end FXMAC_CAN_RECV --> FXMAC_RECV_CHECK FXMAC_HW_POLL --> FXMAC_QUEUE_PUSH FXMAC_RECV_CHECK --> FXMAC_HW_POLL IXGBE_BATCH_RECV --> IXGBE_QUEUE_PUSH IXGBE_CAN_RECV --> IXGBE_RECV_CHECK IXGBE_RECV_CHECK --> IXGBE_BATCH_RECV
Sources: axdriver_net/src/fxmac.rs(L71 - L118) axdriver_net/src/ixgbe.rs(L73 - L124)
Hardware Abstraction Layer Integration
External Crate Dependencies
Both implementations demonstrate integration with external hardware abstraction crates:
Driver | External Crate | Key Functions Used |
---|---|---|
FXmacNic | fxmac_rs | xmac_init,FXmacGetMacAddress,FXmacRecvHandler,FXmacLwipPortTx |
IxgbeNic | ixgbe-driver | IxgbeDevice::init,MemPool::allocate,receive_packets,send |
This pattern allows the axdriver framework to leverage existing hardware-specific implementations while providing a unified interface.
Sources: axdriver_net/src/fxmac.rs(L7) axdriver_net/src/ixgbe.rs(L6 - L7)
Error Handling Translation
Both drivers translate hardware-specific errors to the common DevError
type:
flowchart TD subgraph subGraph1["Ixgbe Error Translation"] IXGBE_ERR["IxgbeError"] IXGBE_NOT_READY["IxgbeError::NotReady"] IXGBE_QUEUE_FULL["IxgbeError::QueueFull"] DEV_AGAIN["DevError::Again"] DEV_BAD_STATE["DevError::BadState"] FXMAC_RET["FXmacLwipPortTx return value"] FXMAC_CHECK["ret < 0"] FXMAC_ERROR["DevError::Again"] end subgraph subGraph0["FXmac Error Translation"] IXGBE_NOT_READY["IxgbeError::NotReady"] DEV_AGAIN["DevError::Again"] FXMAC_RET["FXmacLwipPortTx return value"] FXMAC_CHECK["ret < 0"] FXMAC_ERROR["DevError::Again"] end FXMAC_CHECK --> FXMAC_ERROR FXMAC_RET --> FXMAC_CHECK IXGBE_ERR --> DEV_BAD_STATE IXGBE_ERR --> IXGBE_NOT_READY IXGBE_ERR --> IXGBE_QUEUE_FULL IXGBE_NOT_READY --> DEV_AGAIN IXGBE_QUEUE_FULL --> DEV_AGAIN
Sources: axdriver_net/src/fxmac.rs(L127 - L131) axdriver_net/src/ixgbe.rs(L118 - L122) axdriver_net/src/ixgbe.rs(L130 - L134)
Block Storage Drivers
Relevant source files
Purpose and Scope
The block storage driver subsystem provides a unified interface for accessing block-oriented storage devices such as disks, flash storage, and RAM disks within the ArceOS driver framework. This subsystem defines common traits and types that allow different storage hardware implementations to be used interchangeably through a consistent API.
For information about the foundation layer that all drivers build upon, see Foundation Layer (axdriver_base). For detailed documentation of network drivers, see Network Drivers. For VirtIO block device integration, see VirtIO Integration.
Block Storage Architecture Overview
The block storage subsystem follows the same architectural patterns as other driver types in the axdriver_crates ecosystem, building upon the foundation provided by axdriver_base
while defining storage-specific operations.
Block Storage Component Architecture
flowchart TD subgraph subGraph2["Concrete Implementations"] RamDisk["ramdisk module(feature: ramdisk)"] BCM2835SDHCI["bcm2835sdhci module(feature: bcm2835-sdhci)"] VirtIOBlock["VirtIO Block(via axdriver_virtio)"] end subgraph subGraph1["axdriver_block Interface"] BlockDriverOps["BlockDriverOps"] BlockTraitMethods["num_blocks()block_size()read_block()write_block()flush()"] end subgraph subGraph0["axdriver_base Foundation"] BaseDriverOps["BaseDriverOps"] DevResult["DevResult"] DevError["DevError"] DeviceType["DeviceType"] end BaseDriverOps --> BlockDriverOps BlockDriverOps --> BCM2835SDHCI BlockDriverOps --> BlockTraitMethods BlockDriverOps --> RamDisk BlockDriverOps --> VirtIOBlock DevError --> BlockDriverOps DevResult --> BlockDriverOps DeviceType --> BlockDriverOps
Sources: axdriver_block/src/lib.rs(L1 - L39) axdriver_block/Cargo.toml(L1 - L23)
Core Block Storage Interface
The BlockDriverOps
trait defines the essential operations that all block storage devices must support. This trait extends BaseDriverOps
to inherit common driver functionality while adding storage-specific methods.
BlockDriverOps Trait Structure
flowchart TD subgraph subGraph2["Error Handling"] DevResult["DevResult"] DevError["DevError"] end subgraph subGraph1["BlockDriverOps Methods"] num_blocks["num_blocks()-> u64"] block_size["block_size()-> usize"] read_block["read_block()(block_id, buf)-> DevResult"] write_block["write_block()(block_id, buf)-> DevResult"] flush["flush()-> DevResult"] end subgraph BaseDriverOps["BaseDriverOps"] device_name["device_name()"] device_type["device_type()"] end DevResult --> DevError device_name --> num_blocks device_type --> num_blocks flush --> DevResult read_block --> DevResult write_block --> DevResult
Block Operations
The trait provides five core operations for block-level storage access:
Method | Purpose | Parameters | Return Type |
---|---|---|---|
num_blocks() | Get total number of blocks | None | u64 |
block_size() | Get size of each block in bytes | None | usize |
read_block() | Read data from storage blocks | block_id: u64, buf: &mut [u8] | DevResult |
write_block() | Write data to storage blocks | block_id: u64, buf: &[u8] | DevResult |
flush() | Ensure all pending writes are committed | None | DevResult |
The read and write operations support multi-block transfers when the buffer size exceeds a single block size, allowing for efficient bulk data operations.
Sources: axdriver_block/src/lib.rs(L16 - L38)
Available Block Storage Implementations
The block storage subsystem supports multiple implementation types through conditional compilation features, allowing systems to include only the drivers they need.
Implementation Feature Matrix
flowchart TD subgraph subGraph3["Target Use Cases"] embedded["Embedded SystemsBCM2835 SDHCI"] testing["Testing & DevelopmentRAM Disk"] virtualized["Virtualized EnvironmentsVirtIO Block"] end subgraph subGraph2["External Dependencies"] bcm2835_dep["bcm2835-sdhci crate(optional)"] end subgraph subGraph1["Implementation Modules"] ramdisk_mod["pub mod ramdisk"] sdhci_mod["pub mod bcm2835sdhci"] end subgraph subGraph0["Feature Gates"] ramdisk_feature["feature = 'ramdisk'"] sdhci_feature["feature = 'bcm2835-sdhci'"] default_feature["default = []"] end bcm2835_dep --> embedded ramdisk_feature --> ramdisk_mod ramdisk_mod --> testing sdhci_feature --> bcm2835_dep sdhci_feature --> sdhci_mod sdhci_mod --> embedded
Implementation Types
- RAM Disk (
ramdisk
feature)
- In-memory block storage for testing and temporary storage
- No external dependencies
- Useful for development and system testing
- BCM2835 SDHCI (
bcm2835-sdhci
feature)
- Hardware driver for BCM2835 (Raspberry Pi) SD card interface
- Depends on external
bcm2835-sdhci
crate - Targets embedded ARM systems
- VirtIO Block (via
axdriver_virtio
)
- Virtual block devices in virtualized environments
- Integrated through the VirtIO subsystem
- Supports both MMIO and PCI transport layers
Sources: axdriver_block/Cargo.toml(L14 - L22) axdriver_block/src/lib.rs(L6 - L10)
Integration with Driver Framework
The block storage subsystem integrates seamlessly with the broader axdriver framework through consistent use of foundation types and patterns.
Framework Integration Points
flowchart TD subgraph subGraph2["Runtime Polymorphism"] trait_objects["dyn BlockDriverOps"] dispatch["Dynamic dispatch"] end subgraph subGraph1["Driver Discovery"] device_type_method["device_type() -> DeviceType"] device_name_method["device_name() -> &str"] end subgraph subGraph0["Type System Integration"] DeviceType_Block["DeviceType::Block"] BaseDriverOps_impl["BaseDriverOps implementation"] Error_handling["DevResult/DevError"] end BaseDriverOps_impl --> device_name_method DeviceType_Block --> device_type_method Error_handling --> dispatch device_name_method --> trait_objects device_type_method --> trait_objects trait_objects --> dispatch
Framework Consistency
- Type Safety: All operations return
DevResult
for consistent error handling - Device Identification: Drivers report
DeviceType::Block
for proper categorization - Polymorphism: All implementations can be used through
dyn BlockDriverOps
trait objects - Feature Gating: Optional implementations keep the framework lightweight
The block storage subsystem exemplifies the modular design principles of the axdriver framework, providing a clean separation between interface definition and hardware-specific implementations while maintaining type safety and performance.
Sources: axdriver_block/src/lib.rs(L13 - L38)
Block Driver Interface
Relevant source files
Purpose and Scope
This document covers the core block storage driver interface defined in the axdriver_block
crate. The interface provides a standardized abstraction for block-oriented storage devices such as disks, RAM disks, and SD cards. This page focuses specifically on the BlockDriverOps
trait and fundamental block device abstractions.
For information about specific block device implementations like RAM disk and BCM2835 SDHCI drivers, see Block Device Implementations. For the foundation layer that all drivers build upon, see Foundation Layer (axdriver_base).
Trait Hierarchy and Base Integration
Block Driver Trait Structure
flowchart TD subgraph subGraph0["Core Methods"] NumBlocks["num_blocks()→ u64"] BlockSize["block_size()→ usize"] ReadBlock["read_block(block_id, buf)→ DevResult"] WriteBlock["write_block(block_id, buf)→ DevResult"] Flush["flush()→ DevResult"] end BaseDriverOps["BaseDriverOps(from axdriver_base)"] BlockDriverOps["BlockDriverOps(extends BaseDriverOps)"] DeviceType["DeviceType::Block(device classification)"] ErrorTypes["DevResult & DevError(error handling)"] BaseDriverOps --> BlockDriverOps BlockDriverOps --> BlockSize BlockDriverOps --> DeviceType BlockDriverOps --> ErrorTypes BlockDriverOps --> Flush BlockDriverOps --> NumBlocks BlockDriverOps --> ReadBlock BlockDriverOps --> WriteBlock
The BlockDriverOps
trait extends the base driver interface with block-specific operations. All block drivers must implement both the base operations (device identification) and block-specific operations (storage access).
Sources: axdriver_block/src/lib.rs(L15 - L38)
Type System Integration
Component | Purpose | Source |
---|---|---|
BlockDriverOps | Main trait for block device operations | axdriver_block/src/lib.rs16 |
BaseDriverOps | Inherited base driver interface | axdriver_block/src/lib.rs13 |
DevResult | Standard result type for all operations | axdriver_block/src/lib.rs13 |
DevError | Unified error type across drivers | axdriver_block/src/lib.rs13 |
DeviceType | Device classification enumeration | axdriver_block/src/lib.rs13 |
Sources: axdriver_block/src/lib.rs(L12 - L13)
Core Interface Methods
Device Geometry Methods
The interface provides two fundamental methods for determining the storage device's physical characteristics:
flowchart TD Device["Block Storage Device"] NumBlocks["num_blocks()Returns: u64"] BlockSize["block_size()Returns: usize"] TotalSize["Total Device Size= num_blocks() × block_size()"] BlockSize --> TotalSize Device --> BlockSize Device --> NumBlocks NumBlocks --> TotalSize
num_blocks()
: Returns the total number of addressable blocks in the storage device axdriver_block/src/lib.rs(L17 - L20)block_size()
: Returns the size of each block in bytes axdriver_block/src/lib.rs(L21 - L22)
The total device capacity is calculated as num_blocks() * block_size()
, providing a consistent way to determine storage limits across different hardware implementations.
Sources: axdriver_block/src/lib.rs(L17 - L22)
Data Access Methods
Block I/O Operations Flow
sequenceDiagram participant Client as Client participant BlockDriverOpsImplementation as "BlockDriverOps Implementation" participant StorageHardware as "Storage Hardware" Note over Client,StorageHardware: Read Operation Client ->> BlockDriverOpsImplementation: read_block(block_id, &mut buf) BlockDriverOpsImplementation ->> StorageHardware: Hardware-specific read StorageHardware -->> BlockDriverOpsImplementation: Raw data BlockDriverOpsImplementation -->> Client: DevResult (success/error) Note over Client,StorageHardware: Write Operation Client ->> BlockDriverOpsImplementation: write_block(block_id, &buf) BlockDriverOpsImplementation ->> StorageHardware: Hardware-specific write StorageHardware -->> BlockDriverOpsImplementation: Write acknowledgment BlockDriverOpsImplementation -->> Client: DevResult (success/error) Note over Client,StorageHardware: Flush Operation Client ->> BlockDriverOpsImplementation: flush() BlockDriverOpsImplementation ->> StorageHardware: Commit pending writes StorageHardware -->> BlockDriverOpsImplementation: Flush complete BlockDriverOpsImplementation -->> Client: DevResult (success/error)
The interface defines three primary data access operations:
Read Block Operation
- Signature:
read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult
- Purpose: Reads data from storage starting at the specified block ID
- Multi-block Support: Buffer size may exceed block size to read multiple contiguous blocks
- Error Handling: Returns
DevResult
for consistent error propagation
Write Block Operation
- Signature:
write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult
- Purpose: Writes data to storage starting at the specified block ID
- Multi-block Support: Buffer size may exceed block size to write multiple contiguous blocks
- Error Handling: Returns
DevResult
for consistent error propagation
Flush Operation
- Signature:
flush(&mut self) -> DevResult
- Purpose: Forces all pending write operations to be committed to persistent storage
- Consistency: Ensures data durability and consistency guarantees
Sources: axdriver_block/src/lib.rs(L24 - L37)
Error Handling and Result Types
The block driver interface uses the unified error handling system from axdriver_base
:
Type | Purpose | Usage Pattern |
---|---|---|
DevResult | Standard result type for all operations | fn operation() -> DevResult |
DevError | Unified error enumeration | Consistent error reporting across drivers |
All block operations return DevResult
, enabling consistent error handling across different hardware implementations and allowing higher-level code to handle errors uniformly regardless of the underlying storage technology.
Sources: axdriver_block/src/lib.rs(L13) axdriver_block/src/lib.rs(L28) axdriver_block/src/lib.rs(L34) axdriver_block/src/lib.rs(L37)
Design Patterns and Principles
Mutable Reference Pattern
All data-modifying operations (read_block
, write_block
, flush
) require a mutable reference to self
, ensuring exclusive access during I/O operations and preventing data races in concurrent scenarios.
Multi-Block Operation Support
The interface design explicitly supports multi-block operations through buffer sizing. When the provided buffer exceeds the device's block size, the implementation automatically handles reading or writing multiple contiguous blocks, improving I/O efficiency for large transfers.
Hardware Abstraction Layer
The trait provides a hardware-agnostic interface that abstracts away device-specific details while maintaining the essential block-oriented semantics that storage hardware typically provides.
Sources: axdriver_block/src/lib.rs(L15 - L38)
Feature-Based Compilation Integration
The crate supports conditional compilation for specific implementations:
This design allows systems to include only the block driver implementations they need, reducing binary size and compilation time for embedded or specialized deployments.
Sources: axdriver_block/src/lib.rs(L6 - L10)
Block Device Implementations
Relevant source files
This document covers the concrete implementations of block storage devices in the axdriver framework. These implementations demonstrate how the abstract BlockDriverOps
trait is realized for different types of storage hardware and virtual devices. For information about the block driver interface and trait definitions, see Block Driver Interface.
The implementations covered include both software-based virtual devices and hardware-specific drivers, showcasing the flexibility of the block driver abstraction layer.
Implementation Overview
The axdriver framework provides two primary block device implementations, each serving different use cases and demonstrating different implementation patterns.
flowchart TD subgraph subGraph3["Implementation Characteristics"] RamCharacteristics["• Synchronous operations• No initialization required• Perfect reliability• 512-byte blocks"] SDHCICharacteristics["• Hardware initialization• Error mapping required• Buffer alignment constraints• Variable block sizes"] end subgraph subGraph2["Hardware Implementation"] SDHCIDriver["SDHCIDriver"] EmmcCtl["EmmcCtl (from bcm2835_sdhci)"] SDCardHW["SD Card Hardware"] end subgraph subGraph1["Virtual Device Implementation"] RamDisk["RamDisk"] VecData["Vec<u8> data"] MemoryOps["In-memory operations"] end subgraph subGraph0["Block Driver Trait System"] BaseDriverOps["BaseDriverOps"] BlockDriverOps["BlockDriverOps"] end BaseDriverOps --> BlockDriverOps BlockDriverOps --> RamDisk BlockDriverOps --> SDHCIDriver EmmcCtl --> SDCardHW RamDisk --> MemoryOps RamDisk --> RamCharacteristics RamDisk --> VecData SDHCIDriver --> EmmcCtl SDHCIDriver --> SDHCICharacteristics
Block Device Implementation Architecture
Sources: axdriver_block/src/ramdisk.rs(L1 - L101) axdriver_block/src/bcm2835sdhci.rs(L1 - L89)
RamDisk Implementation
The RamDisk
implementation provides an in-memory block device primarily used for testing and development. It stores all data in a contiguous Vec<u8>
and performs all operations synchronously without any possibility of failure.
Core Structure and Initialization
flowchart TD subgraph subGraph2["Block Operations"] read_block["read_block()"] write_block["write_block()"] num_blocks["num_blocks()"] block_size["block_size()"] end subgraph subGraph1["RamDisk Creation Patterns"] new["RamDisk::new(size_hint)"] from["RamDisk::from(buf)"] default["RamDisk::default()"] align_up["align_up() function"] vec_alloc["vec![0; size]"] subgraph subGraph0["Internal State"] size_field["size: usize"] data_field["data: Vec<u8>"] end end align_up --> vec_alloc data_field --> read_block data_field --> write_block from --> align_up new --> align_up size_field --> num_blocks vec_alloc --> data_field vec_alloc --> size_field
RamDisk Internal Structure and Operations
The RamDisk
struct contains two fields: size
for the total allocated size and data
for the actual storage vector. All operations work directly with this vector using standard slice operations.
Method | Implementation Pattern | Key Characteristics |
---|---|---|
new() | Size alignment + zero-filled vector | Always succeeds, rounds up to block boundary |
from() | Copy existing data + size alignment | Preserves input data, pads to block boundary |
read_block() | Direct slice copy from vector | Validates bounds and block alignment |
write_block() | Direct slice copy to vector | Validates bounds and block alignment |
Sources: axdriver_block/src/ramdisk.rs(L12 - L46) axdriver_block/src/ramdisk.rs(L58 - L96)
Memory Management and Block Operations
The RamDisk
implementation uses a fixed block size of 512 bytes and enforces strict alignment requirements:
flowchart TD subgraph subGraph2["Data Operations"] ReadOp["buf.copy_from_slice(&self.data[offset..])"] WriteOp["self.data[offset..].copy_from_slice(buf)"] end subgraph subGraph1["Validation Rules"] BoundsCheck["offset + buf.len() <= self.size"] AlignmentCheck["buf.len() % BLOCK_SIZE == 0"] end subgraph subGraph0["Block Operation Flow"] ValidateParams["Validate Parameters"] CheckBounds["Check Bounds"] CheckAlignment["Check Block Alignment"] CopyData["Copy Data"] end CheckAlignment --> AlignmentCheck CheckAlignment --> CopyData CheckBounds --> BoundsCheck CheckBounds --> CheckAlignment CopyData --> ReadOp CopyData --> WriteOp ValidateParams --> CheckBounds
RamDisk Block Operation Validation and Execution
Sources: axdriver_block/src/ramdisk.rs(L69 - L91) axdriver_block/src/ramdisk.rs(L98 - L100)
SDHCI Implementation
The SDHCIDriver
provides access to SD cards on BCM2835-based systems (such as Raspberry Pi). This implementation demonstrates hardware integration patterns and error handling complexity.
Hardware Abstraction and Initialization
flowchart TD subgraph subGraph2["Driver Wrapper"] SDHCIDriver_struct["SDHCIDriver(EmmcCtl)"] deal_sdhci_err["deal_sdhci_err() mapper"] end subgraph subGraph1["Hardware Abstraction Layer"] try_new["SDHCIDriver::try_new()"] emmc_new["EmmcCtl::new()"] emmc_init["ctrl.init()"] bcm2835_sdhci["bcm2835_sdhci crate"] EmmcCtl_extern["EmmcCtl"] SDHCIError_extern["SDHCIError"] BLOCK_SIZE_extern["BLOCK_SIZE constant"] end subgraph subGraph0["Initialization Flow"] try_new["SDHCIDriver::try_new()"] emmc_new["EmmcCtl::new()"] emmc_init["ctrl.init()"] check_result["Check init result"] success["Ok(SDHCIDriver(ctrl))"] failure["Err(DevError::Io)"] bcm2835_sdhci["bcm2835_sdhci crate"] end EmmcCtl_extern --> SDHCIDriver_struct SDHCIError_extern --> deal_sdhci_err bcm2835_sdhci --> BLOCK_SIZE_extern bcm2835_sdhci --> EmmcCtl_extern bcm2835_sdhci --> SDHCIError_extern check_result --> failure check_result --> success emmc_init --> check_result emmc_new --> emmc_init try_new --> emmc_new
SDHCI Driver Hardware Integration Architecture
Sources: axdriver_block/src/bcm2835sdhci.rs(L12 - L24) axdriver_block/src/bcm2835sdhci.rs(L26 - L37)
Buffer Alignment and Hardware Operations
The SDHCI implementation requires careful buffer alignment and provides comprehensive error mapping:
flowchart TD subgraph subGraph2["Hardware Operations"] read_block_hw["ctrl.read_block(block_id, 1, aligned_buf)"] write_block_hw["ctrl.write_block(block_id, 1, aligned_buf)"] get_block_num["ctrl.get_block_num()"] get_block_size["ctrl.get_block_size()"] end subgraph subGraph1["Error Handling Pipeline"] sdhci_errors["SDHCIError variants"] deal_sdhci_err_fn["deal_sdhci_err()"] dev_errors["DevError variants"] end subgraph subGraph0["Buffer Alignment Process"] input_buf["Input buffer: &[u8] or &mut [u8]"] align_check["buf.align_to::()"] validate_alignment["Check prefix/suffix empty"] hardware_call["Call EmmcCtl methods"] end align_check --> validate_alignment deal_sdhci_err_fn --> dev_errors hardware_call --> get_block_num hardware_call --> get_block_size hardware_call --> read_block_hw hardware_call --> sdhci_errors hardware_call --> write_block_hw input_buf --> align_check sdhci_errors --> deal_sdhci_err_fn validate_alignment --> hardware_call
SDHCI Buffer Alignment and Hardware Operation Flow
Sources: axdriver_block/src/bcm2835sdhci.rs(L49 - L88)
Implementation Comparison
The two block device implementations demonstrate contrasting approaches to the same abstract interface:
Aspect | RamDisk | SDHCIDriver |
---|---|---|
Initialization | Always succeeds (new(),from()) | Can fail (try_new()returnsDevResult) |
Storage Backend | Vec | Hardware SD card viabcm2835_sdhci |
Error Handling | Minimal (only bounds/alignment) | Comprehensive error mapping |
Buffer Requirements | Any byte-aligned buffer | 32-bit aligned buffers required |
Block Size | Fixed 512 bytes (BLOCK_SIZEconstant) | Variable (ctrl.get_block_size()) |
Performance | Synchronous memory operations | Hardware-dependent timing |
Use Cases | Testing, temporary storage | Production SD card access |
Error Mapping Patterns
flowchart TD subgraph subGraph2["Common DevError Interface"] dev_error_unified["DevError enum"] end subgraph subGraph1["SDHCI Error Sources"] sdhci_hardware["Hardware failures"] sdhci_state["Device state issues"] sdhci_resources["Resource constraints"] sdhci_complex["9 distinct SDHCIError variants"] sdhci_mapped["Mapped to appropriate DevError"] end subgraph subGraph0["RamDisk Error Sources"] ram_bounds["Bounds checking"] ram_alignment["Block alignment"] ram_simple["Simple DevError::Io or DevError::InvalidParam"] end ram_alignment --> ram_simple ram_bounds --> ram_simple ram_simple --> dev_error_unified sdhci_complex --> sdhci_mapped sdhci_hardware --> sdhci_complex sdhci_mapped --> dev_error_unified sdhci_resources --> sdhci_complex sdhci_state --> sdhci_complex
Error Handling Strategy Comparison
The error mapping function deal_sdhci_err()
provides a complete translation between hardware-specific errors and the unified DevError
enum, ensuring consistent error handling across different block device types.
Sources: axdriver_block/src/ramdisk.rs(L69 - L95) axdriver_block/src/bcm2835sdhci.rs(L26 - L37) axdriver_block/src/bcm2835sdhci.rs(L49 - L88)
Display Drivers
Relevant source files
This document covers the display driver subsystem within the axdriver_crates framework. The display drivers provide a unified interface for graphics devices and framebuffer management in ArceOS. This subsystem handles graphics device initialization, framebuffer access, and display output operations.
For network device drivers, see Network Drivers. For block storage drivers, see Block Storage Drivers. For VirtIO display device integration, see VirtIO Integration.
Display Driver Architecture
The display driver subsystem follows the same architectural pattern as other device types in the framework, extending the base driver operations with display-specific functionality.
Display Driver Interface Overview
flowchart TD subgraph subGraph3["Integration Points"] VirtIOGpu["VirtIO GPU Device"] PCIGraphics["PCI Graphics Device"] MMIODisplay["MMIO Display Device"] end subgraph subGraph2["Core Operations"] info_op["info() -> DisplayInfo"] fb_op["fb() -> FrameBuffer"] need_flush_op["need_flush() -> bool"] flush_op["flush() -> DevResult"] end subgraph subGraph1["Display Interface"] DisplayDriverOps["DisplayDriverOps trait"] DisplayInfo["DisplayInfo struct"] FrameBuffer["FrameBuffer struct"] end subgraph Foundation["Foundation"] BaseDriverOps["BaseDriverOps"] DevResult["DevResult / DevError"] DeviceType["DeviceType::Display"] end BaseDriverOps --> DisplayDriverOps DevResult --> flush_op DisplayDriverOps --> MMIODisplay DisplayDriverOps --> PCIGraphics DisplayDriverOps --> VirtIOGpu DisplayDriverOps --> fb_op DisplayDriverOps --> flush_op DisplayDriverOps --> info_op DisplayDriverOps --> need_flush_op DisplayInfo --> info_op FrameBuffer --> fb_op
Sources: axdriver_display/src/lib.rs(L1 - L60)
The DisplayDriverOps
trait extends BaseDriverOps
and defines the core interface that all display drivers must implement. This trait provides four essential operations for display management.
Core Data Structures
The display subsystem defines two primary data structures for managing graphics device state and framebuffer access.
DisplayInfo Structure
flowchart TD subgraph Usage["Usage"] info_method["DisplayDriverOps::info()"] display_config["Display Configuration"] memory_mapping["Memory Mapping Setup"] end subgraph subGraph0["DisplayInfo Fields"] width["width: u32Visible display width"] height["height: u32Visible display height"] fb_base_vaddr["fb_base_vaddr: usizeFramebuffer virtual address"] fb_size["fb_size: usizeFramebuffer size in bytes"] end fb_base_vaddr --> memory_mapping fb_size --> memory_mapping height --> display_config info_method --> fb_base_vaddr info_method --> fb_size info_method --> height info_method --> width width --> display_config
Sources: axdriver_display/src/lib.rs(L8 - L19)
The DisplayInfo
struct contains essential metadata about the graphics device, including display dimensions and framebuffer memory layout. This information is used by higher-level graphics systems to configure rendering operations.
FrameBuffer Management
flowchart TD subgraph Integration["Integration"] fb_method["DisplayDriverOps::fb()"] graphics_rendering["Graphics Rendering"] pixel_operations["Pixel Operations"] end subgraph subGraph1["Safety Considerations"] unsafe_creation["Unsafe raw pointer access"] memory_validity["Memory region validity"] exclusive_access["Exclusive mutable access"] end subgraph subGraph0["FrameBuffer Construction"] from_raw_parts_mut["from_raw_parts_mut(ptr: *mut u8, len: usize)"] from_slice["from_slice(slice: &mut [u8])"] raw_field["_raw: &mut [u8]"] end exclusive_access --> raw_field fb_method --> graphics_rendering fb_method --> pixel_operations from_raw_parts_mut --> raw_field from_slice --> raw_field memory_validity --> from_raw_parts_mut raw_field --> fb_method unsafe_creation --> from_raw_parts_mut
Sources: axdriver_display/src/lib.rs(L21 - L44)
The FrameBuffer
struct provides safe access to the device framebuffer memory. It offers both safe construction from existing slices and unsafe construction from raw pointers for device driver implementations.
DisplayDriverOps Trait Implementation
Required Methods
Method | Return Type | Purpose |
---|---|---|
info() | DisplayInfo | Retrieve display configuration and framebuffer metadata |
fb() | FrameBuffer | Get access to the framebuffer for rendering operations |
need_flush() | bool | Determine if explicit flush operations are required |
flush() | DevResult | Synchronize framebuffer contents to the display |
Sources: axdriver_display/src/lib.rs(L46 - L59)
Display Driver Operation Flow
sequenceDiagram participant Application as "Application" participant DisplayDriverOpsImplementation as "DisplayDriverOps Implementation" participant GraphicsHardware as "Graphics Hardware" Application ->> DisplayDriverOpsImplementation: info() DisplayDriverOpsImplementation ->> GraphicsHardware: Query display configuration GraphicsHardware -->> DisplayDriverOpsImplementation: Display dimensions & framebuffer info DisplayDriverOpsImplementation -->> Application: DisplayInfo struct Application ->> DisplayDriverOpsImplementation: fb() DisplayDriverOpsImplementation -->> Application: FrameBuffer reference Note over Application: Render graphics to framebuffer Application ->> DisplayDriverOpsImplementation: need_flush() DisplayDriverOpsImplementation -->> Application: bool (hardware-dependent) opt If flush needed Application ->> DisplayDriverOpsImplementation: flush() DisplayDriverOpsImplementation ->> GraphicsHardware: Synchronize framebuffer GraphicsHardware -->> DisplayDriverOpsImplementation: Completion status DisplayDriverOpsImplementation -->> Application: DevResult end
Sources: axdriver_display/src/lib.rs(L46 - L59)
The need_flush()
method allows drivers to indicate whether they require explicit flush operations. Some hardware configurations automatically update the display when framebuffer memory is modified, while others require explicit synchronization commands.
Integration with Driver Framework
Base Driver Integration
The display driver system inherits core functionality from the axdriver_base foundation layer:
flowchart TD subgraph subGraph2["Error Handling"] DevResult["DevResult"] DevError["DevError enum"] end subgraph subGraph1["Display-Specific Extensions"] info["info() -> DisplayInfo"] fb["fb() -> FrameBuffer"] need_flush["need_flush() -> bool"] flush["flush() -> DevResult"] end subgraph subGraph0["Inherited from BaseDriverOps"] device_name["device_name() -> &str"] device_type["device_type() -> DeviceType"] end DisplayDriverOps["DisplayDriverOps"] DevResult --> DevError DisplayDriverOps --> fb DisplayDriverOps --> flush DisplayDriverOps --> info DisplayDriverOps --> need_flush device_name --> DisplayDriverOps device_type --> DisplayDriverOps flush --> DevResult
Sources: axdriver_display/src/lib.rs(L5 - L6) axdriver_display/src/lib.rs(L46 - L59)
Device Type Classification
Display drivers return DeviceType::Display
from their device_type()
method, enabling the driver framework to properly categorize and route display devices during system initialization.
Hardware Implementation Considerations
Memory Safety
The FrameBuffer
implementation provides both safe and unsafe construction methods to accommodate different hardware access patterns:
from_slice()
for pre-validated memory regionsfrom_raw_parts_mut()
for direct hardware memory mapping with explicit safety requirements
Flush Requirements
The need_flush()
method accommodates different hardware architectures:
- Immediate update hardware: Returns
false
, display updates automatically - Buffered hardware: Returns
true
, requires explicitflush()
calls - Memory-mapped displays: May return
false
for direct framebuffer access
Sources: axdriver_display/src/lib.rs(L54 - L58)
Dependencies and Build Configuration
The display driver crate has minimal dependencies, relying only on axdriver_base
for core driver infrastructure:
[dependencies]
axdriver_base = { workspace = true }
This lightweight dependency structure ensures that display drivers can be compiled efficiently and integrated into embedded systems with minimal overhead.
Sources: axdriver_display/Cargo.toml(L14 - L15)
VirtIO Integration
Relevant source files
The VirtIO integration layer provides a bridge between the external virtio-drivers
crate and the axdriver framework, enabling virtualized hardware devices to be accessed through the same standardized interfaces as physical hardware. This integration allows ArceOS to run efficiently in virtualized environments while maintaining consistent driver APIs.
For information about the underlying driver traits that VirtIO devices implement, see Foundation Layer (axdriver_base). For details on specific device type implementations, see Network Drivers, Block Storage Drivers, and Display Drivers.
Architecture Overview
The VirtIO integration follows an adapter pattern that wraps virtio-drivers
device implementations to conform to the axdriver trait system. This design allows the same application code to work with both physical and virtualized hardware.
flowchart TD subgraph Target_Traits["axdriver Traits"] BASE_OPS["BaseDriverOps"] BLOCK_OPS["BlockDriverOps"] NET_OPS["NetDriverOps"] DISPLAY_OPS["DisplayDriverOps"] end subgraph Bridge_Layer["axdriver_virtio Bridge"] PROBE_MMIO["probe_mmio_device()"] PROBE_PCI["probe_pci_device()"] TYPE_CONV["as_dev_type()"] ERR_CONV["as_dev_err()"] subgraph Device_Wrappers["Device Wrappers"] VIRTIO_BLK["VirtIoBlkDev"] VIRTIO_NET["VirtIoNetDev"] VIRTIO_GPU["VirtIoGpuDev"] end end subgraph External_VirtIO["External VirtIO Layer"] VD["virtio-drivers crate"] TRANSPORT["Transport Layer"] HAL["VirtIoHal trait"] end ERR_CONV --> BASE_OPS PROBE_MMIO --> TYPE_CONV PROBE_PCI --> TYPE_CONV TRANSPORT --> PROBE_MMIO TRANSPORT --> PROBE_PCI TYPE_CONV --> VIRTIO_BLK TYPE_CONV --> VIRTIO_GPU TYPE_CONV --> VIRTIO_NET VD --> PROBE_MMIO VD --> PROBE_PCI VIRTIO_BLK --> BASE_OPS VIRTIO_BLK --> BLOCK_OPS VIRTIO_GPU --> BASE_OPS VIRTIO_GPU --> DISPLAY_OPS VIRTIO_NET --> BASE_OPS VIRTIO_NET --> NET_OPS
Sources: axdriver_virtio/src/lib.rs(L1 - L98)
Transport Layer Support
The VirtIO integration supports both MMIO and PCI transport mechanisms, providing flexibility for different virtualization platforms and hardware configurations.
MMIO Transport
The probe_mmio_device
function discovers VirtIO devices mapped into memory regions, commonly used in embedded virtualization scenarios.
flowchart TD subgraph Detection_Process["Detection Process"] PROBE_FUNC["probe_mmio_device()"] DEVICE_TYPE["DeviceType"] TRANSPORT_OBJ["Transport Object"] end subgraph MMIO_Discovery["MMIO Device Discovery"] MEM_REGION["Memory Regionreg_base: *mut u8"] VIRTIO_HEADER["VirtIOHeader"] MMIO_TRANSPORT["MmioTransport"] end MEM_REGION --> VIRTIO_HEADER MMIO_TRANSPORT --> PROBE_FUNC PROBE_FUNC --> DEVICE_TYPE PROBE_FUNC --> TRANSPORT_OBJ VIRTIO_HEADER --> MMIO_TRANSPORT
The function performs device validation and type identification:
Step | Operation | Result |
---|---|---|
1 | CreateNonNull | Header pointer validation |
2 | InitializeMmioTransport | Transport layer setup |
3 | Callas_dev_type()on device type | Convert toDeviceType |
4 | Return tuple | (DeviceType, MmioTransport) |
Sources: axdriver_virtio/src/lib.rs(L38 - L53)
PCI Transport
The probe_pci_device
function discovers VirtIO devices on the PCI bus, typical for desktop and server virtualization environments.
flowchart TD subgraph Probing_Logic["Probing Logic"] VIRTIO_TYPE["virtio_device_type()"] TYPE_CONV["as_dev_type()"] PCI_TRANSPORT["PciTransport::new()"] end subgraph PCI_Discovery["PCI Device Discovery"] PCI_ROOT["PciRoot"] BDF["DeviceFunction(Bus/Device/Function)"] DEV_INFO["DeviceFunctionInfo"] end BDF --> PCI_TRANSPORT DEV_INFO --> VIRTIO_TYPE PCI_ROOT --> PCI_TRANSPORT TYPE_CONV --> PCI_TRANSPORT VIRTIO_TYPE --> TYPE_CONV
Sources: axdriver_virtio/src/lib.rs(L55 - L69)
Device Type Mapping
The integration includes a comprehensive type conversion system that maps VirtIO device types to axdriver device categories.
Type Conversion Function
The as_dev_type
function provides the core mapping between VirtIO and axdriver type systems:
flowchart TD subgraph Axdriver_Types["axdriver Device Types"] DEV_BLOCK["DeviceType::Block"] DEV_NET["DeviceType::Net"] DEV_DISPLAY["DeviceType::Display"] DEV_NONE["None"] end subgraph Mapping_Function["as_dev_type()"] MATCH_EXPR["match expression"] end subgraph VirtIO_Types["VirtIO Device Types"] VIRTIO_BLOCK["VirtIoDevType::Block"] VIRTIO_NET["VirtIoDevType::Network"] VIRTIO_GPU["VirtIoDevType::GPU"] VIRTIO_OTHER["Other VirtIO Types"] end MATCH_EXPR --> DEV_BLOCK MATCH_EXPR --> DEV_DISPLAY MATCH_EXPR --> DEV_NET MATCH_EXPR --> DEV_NONE VIRTIO_BLOCK --> MATCH_EXPR VIRTIO_GPU --> MATCH_EXPR VIRTIO_NET --> MATCH_EXPR VIRTIO_OTHER --> MATCH_EXPR
VirtIO Type | axdriver Type | Purpose |
---|---|---|
Block | DeviceType::Block | Storage devices |
Network | DeviceType::Net | Network interfaces |
GPU | DeviceType::Display | Graphics and display |
Other | None | Unsupported device types |
Sources: axdriver_virtio/src/lib.rs(L71 - L79)
Error Handling Integration
The VirtIO integration includes comprehensive error mapping to ensure consistent error handling across the driver framework.
Error Conversion System
The as_dev_err
function maps virtio_drivers::Error
variants to axdriver_base::DevError
types:
flowchart TD subgraph Axdriver_Errors["axdriver_base::DevError"] BAD_STATE["BadState"] AGAIN["Again"] ALREADY_EXISTS["AlreadyExists"] INVALID_PARAM_OUT["InvalidParam"] NO_MEMORY["NoMemory"] IO_OUT["Io"] UNSUPPORTED_OUT["Unsupported"] end subgraph Conversion["as_dev_err()"] ERROR_MATCH["match expression"] end subgraph VirtIO_Errors["virtio_drivers::Error"] QUEUE_FULL["QueueFull"] NOT_READY["NotReady"] WRONG_TOKEN["WrongToken"] ALREADY_USED["AlreadyUsed"] INVALID_PARAM["InvalidParam"] DMA_ERROR["DmaError"] IO_ERROR["IoError"] UNSUPPORTED["Unsupported"] CONFIG_ERRORS["Config Errors"] OTHER_ERRORS["Other Errors"] end ALREADY_USED --> ERROR_MATCH CONFIG_ERRORS --> ERROR_MATCH DMA_ERROR --> ERROR_MATCH ERROR_MATCH --> AGAIN ERROR_MATCH --> ALREADY_EXISTS ERROR_MATCH --> BAD_STATE ERROR_MATCH --> INVALID_PARAM_OUT ERROR_MATCH --> IO_OUT ERROR_MATCH --> NO_MEMORY ERROR_MATCH --> UNSUPPORTED_OUT INVALID_PARAM --> ERROR_MATCH IO_ERROR --> ERROR_MATCH NOT_READY --> ERROR_MATCH OTHER_ERRORS --> ERROR_MATCH QUEUE_FULL --> ERROR_MATCH UNSUPPORTED --> ERROR_MATCH WRONG_TOKEN --> ERROR_MATCH
Sources: axdriver_virtio/src/lib.rs(L81 - L97)
Feature-Based Compilation
The VirtIO integration uses feature flags to enable selective compilation of device types, allowing minimal builds for specific deployment scenarios.
Feature Configuration
Feature | Dependencies | Enabled Components |
---|---|---|
block | axdriver_block | VirtIoBlkDevwrapper |
net | axdriver_net | VirtIoNetDevwrapper |
gpu | axdriver_display | VirtIoGpuDevwrapper |
flowchart TD subgraph Public_Exports["Public API"] PUB_BLK["pub use VirtIoBlkDev"] PUB_NET["pub use VirtIoNetDev"] PUB_GPU["pub use VirtIoGpuDev"] end subgraph Conditional_Modules["Conditional Compilation"] MOD_BLK["mod blk"] MOD_NET["mod net"] MOD_GPU["mod gpu"] end subgraph Feature_Flags["Cargo Features"] FEAT_BLOCK["block"] FEAT_NET["net"] FEAT_GPU["gpu"] end FEAT_BLOCK --> MOD_BLK FEAT_GPU --> MOD_GPU FEAT_NET --> MOD_NET MOD_BLK --> PUB_BLK MOD_GPU --> PUB_GPU MOD_NET --> PUB_NET
Sources: axdriver_virtio/Cargo.toml(L14 - L24) axdriver_virtio/src/lib.rs(L16 - L28)
Public API Surface
The crate re-exports essential VirtIO components to provide a complete integration interface:
Core Re-exports
Export | Source | Purpose |
---|---|---|
pci | virtio_drivers::transport::pci::bus | PCI bus operations |
MmioTransport | virtio_drivers::transport::mmio | MMIO transport |
PciTransport | virtio_drivers::transport::pci | PCI transport |
VirtIoHal | virtio_drivers::Hal | Hardware abstraction trait |
PhysAddr | virtio_drivers::PhysAddr | Physical address type |
BufferDirection | virtio_drivers::BufferDirection | DMA buffer direction |
Sources: axdriver_virtio/src/lib.rs(L30 - L36)
VirtIO Core Abstraction
Relevant source files
This document covers the core abstraction layer that bridges the external virtio-drivers
crate with the axdriver ecosystem. It focuses on device probing mechanisms, type conversion, error handling, and the fundamental infrastructure that enables VirtIO device integration. For information about specific VirtIO device implementations (block, network, GPU), see VirtIO Device Implementations.
Overview
The VirtIO core abstraction in axdriver_virtio
serves as an adapter layer that translates between the virtio-drivers
crate's interfaces and the standardized axdriver trait system. This layer handles device discovery, type mapping, and error conversion while maintaining compatibility with both MMIO and PCI transport mechanisms.
Sources: axdriver_virtio/src/lib.rs(L1 - L98)
Device Probing Architecture
The VirtIO subsystem provides two primary device probing mechanisms that support different hardware configurations and virtualization environments.
MMIO Device Probing
flowchart TD A["probe_mmio_device()"] B["reg_base: *mut u8"] C["VirtIOHeader"] D["MmioTransport::new()"] E["Transport Valid?"] F["transport.device_type()"] G["Return None"] H["as_dev_type()"] I["Type Supported?"] J["Return (DeviceType, MmioTransport)"] A --> B B --> C C --> D D --> E E --> F E --> G F --> H H --> I I --> G I --> J
The probe_mmio_device
function creates a VirtIO MMIO transport from a memory-mapped region and identifies the device type. It validates the VirtIO header structure and extracts device type information for compatibility checking.
Sources: axdriver_virtio/src/lib.rs(L38 - L53)
PCI Device Probing
flowchart TD A["probe_pci_device()"] B["PciRoot, DeviceFunction, DeviceFunctionInfo"] C["virtio_device_type()"] D["as_dev_type()"] E["Type Supported?"] F["PciTransport::new()"] G["Return None"] H["Transport Created?"] I["Return (DeviceType, PciTransport)"] A --> B B --> C C --> D D --> E E --> F E --> G F --> H H --> G H --> I
The probe_pci_device
function handles PCI-based VirtIO devices using the PCI bus infrastructure. It requires a HAL implementation for DMA operations and device access.
Sources: axdriver_virtio/src/lib.rs(L55 - L69)
Type Conversion System
The core abstraction implements bidirectional type mapping between the virtio-drivers
crate and the axdriver ecosystem.
Device Type Mapping
VirtIO Device Type | axdriver Device Type | Support Status |
---|---|---|
VirtIoDevType::Block | DeviceType::Block | ✓ Supported |
VirtIoDevType::Network | DeviceType::Net | ✓ Supported |
VirtIoDevType::GPU | DeviceType::Display | ✓ Supported |
Other types | None | Not supported |
flowchart TD subgraph axdriver_base["axdriver_base"] AB["DeviceType::Block"] AN["DeviceType::Net"] AD["DeviceType::Display"] NONE["None"] end subgraph as_dev_type()["as_dev_type()"] CONV["Type Conversion"] end subgraph virtio_drivers["virtio_drivers"] VB["VirtIoDevType::Block"] VN["VirtIoDevType::Network"] VG["VirtIoDevType::GPU"] VO["Other Types"] end CONV --> AB CONV --> AD CONV --> AN CONV --> NONE VB --> CONV VG --> CONV VN --> CONV VO --> CONV
The as_dev_type
function provides compile-time type conversion that filters supported VirtIO device types and maps them to corresponding axdriver types.
Sources: axdriver_virtio/src/lib.rs(L71 - L79)
Error Handling Bridge
The abstraction layer provides comprehensive error conversion between VirtIO-specific errors and the standardized axdriver error types.
Error Mapping Table
VirtIO Error | axdriver DevError | Description |
---|---|---|
QueueFull | BadState | Device queue at capacity |
NotReady | Again | Device not ready, retry later |
WrongToken | BadState | Invalid operation token |
AlreadyUsed | AlreadyExists | Resource already in use |
InvalidParam | InvalidParam | Invalid parameter provided |
DmaError | NoMemory | DMA allocation failure |
IoError | Io | I/O operation failure |
Unsupported | Unsupported | Unsupported operation |
ConfigSpaceTooSmall | BadState | Insufficient config space |
ConfigSpaceMissing | BadState | Missing config space |
Default | BadState | Generic error state |
flowchart TD subgraph axdriver_base::DevError["axdriver_base::DevError"] DE1["BadState"] DE2["Again"] DE3["NoMemory"] DE4["Io"] DE5["Unsupported"] end subgraph as_dev_err()["as_dev_err()"] ECONVERT["Error Conversion"] end subgraph virtio_drivers::Error["virtio_drivers::Error"] VE1["QueueFull"] VE2["NotReady"] VE3["DmaError"] VE4["IoError"] VE5["Unsupported"] VEX["Others..."] end ECONVERT --> DE1 ECONVERT --> DE2 ECONVERT --> DE3 ECONVERT --> DE4 ECONVERT --> DE5 VE1 --> ECONVERT VE2 --> ECONVERT VE3 --> ECONVERT VE4 --> ECONVERT VE5 --> ECONVERT VEX --> ECONVERT
The as_dev_err
function provides semantic mapping of VirtIO errors to axdriver error categories, enabling consistent error handling across the driver ecosystem.
Sources: axdriver_virtio/src/lib.rs(L81 - L97)
Transport Abstraction Layer
The VirtIO core abstraction re-exports key transport components from the virtio-drivers
crate, providing a unified interface for different VirtIO transport mechanisms.
Core Transport Types
flowchart TD subgraph subGraph2["PCI Infrastructure"] PCIBUS["pci::bus module"] DEVFUNC["DeviceFunction"] DEVINFO["DeviceFunctionInfo"] PCIROOT["PciRoot"] end subgraph subGraph1["Supporting Types"] HAL["VirtIoHal Trait"] PADDR["PhysAddr"] BDIR["BufferDirection"] end subgraph subGraph0["Transport Layer"] TRANS["Transport Trait"] MMIO["MmioTransport"] PCI["PciTransport"] end HAL --> BDIR HAL --> PADDR MMIO --> HAL PCI --> HAL PCI --> PCIBUS PCIBUS --> DEVFUNC PCIBUS --> DEVINFO PCIBUS --> PCIROOT TRANS --> MMIO TRANS --> PCI
The transport abstraction provides:
- Transport Trait: Common interface for device communication
- MmioTransport: Memory-mapped I/O transport for embedded systems
- PciTransport: PCI bus transport for conventional systems
- VirtIoHal: Hardware abstraction layer for memory management
- PCI Infrastructure: Bus enumeration and device management
Sources: axdriver_virtio/src/lib.rs(L30 - L36)
HAL Requirements
The VirtIO integration requires implementation of the VirtIoHal
trait for hardware-specific operations:
flowchart TD subgraph subGraph1["axdriver Integration"] PROBE["Device Probing"] TRANSPORT["Transport Creation"] DEVOPS["Device Operations"] end subgraph subGraph0["VirtIoHal Requirements"] DMA["DMA Memory Allocation"] ADDR["Physical Address Translation"] CACHE["Cache Coherency"] SYNC["Memory Synchronization"] end ADDR --> TRANSPORT CACHE --> DEVOPS DMA --> PROBE SYNC --> DEVOPS
The HAL implementation must provide memory management capabilities that bridge between the host system's memory management and VirtIO device requirements.
Sources: axdriver_virtio/src/lib.rs(L4 - L7)
Conditional Compilation Support
The VirtIO core abstraction supports selective compilation through feature flags:
Feature | Purpose |
---|---|
block | Enable VirtIO block device support |
net | Enable VirtIO network device support |
gpu | Enable VirtIO GPU device support |
This design allows minimal binary size by including only required device types while maintaining the same core probing and abstraction infrastructure.
Sources: axdriver_virtio/src/lib.rs(L16 - L28)
VirtIO Device Implementations
Relevant source files
This page documents the specific VirtIO device wrapper implementations that provide ArceOS-compatible drivers for virtualized hardware. These implementations bridge the external virtio_drivers
crate with the axdriver trait system, enabling VirtIO devices to be used seamlessly alongside native hardware drivers.
For information about the core VirtIO abstraction layer and device probing mechanisms, see VirtIO Core Abstraction. For details about the underlying device traits these implementations fulfill, see Foundation Layer, Network Driver Interface, Block Driver Interface, and Display Drivers.
VirtIO Device Architecture Overview
The axdriver_virtio crate provides three main device wrapper implementations that adapt VirtIO devices to the axdriver trait system:
flowchart TD subgraph subGraph2["axdriver Traits"] BaseOps["BaseDriverOps"] BlockOps["BlockDriverOps"] NetOps["NetDriverOps"] DisplayOps["DisplayDriverOps"] end subgraph subGraph1["axdriver_virtio Wrappers"] BlkWrapper["VirtIoBlkDev"] NetWrapper["VirtIoNetDev"] GpuWrapper["VirtIoGpuDev"] end subgraph subGraph0["External VirtIO Layer"] VirtIOBlk["virtio_drivers::device::blk::VirtIOBlk"] VirtIONet["virtio_drivers::device::net::VirtIONetRaw"] VirtIOGpu["virtio_drivers::device::gpu::VirtIOGpu"] end BlkWrapper --> BaseOps BlkWrapper --> BlockOps GpuWrapper --> BaseOps GpuWrapper --> DisplayOps NetWrapper --> BaseOps NetWrapper --> NetOps VirtIOBlk --> BlkWrapper VirtIOGpu --> GpuWrapper VirtIONet --> NetWrapper
Sources: axdriver_virtio/src/blk.rs(L1 - L61) axdriver_virtio/src/net.rs(L1 - L194) axdriver_virtio/src/gpu.rs(L1 - L71)
VirtIO Block Device Implementation
The VirtIoBlkDev
provides block storage functionality through VirtIO's block device interface.
Structure and Initialization
flowchart TD subgraph subGraph1["Trait Implementation"] BaseImpl["BaseDriverOps:device_name() -> 'virtio-blk'device_type() -> Block"] BlockImpl["BlockDriverOps:num_blocks()block_size()read_block()write_block()flush()"] end subgraph subGraph0["VirtIoBlkDev Components"] Inner["inner: VirtIOBlk"] Params["Generic Parameters:H: HalT: Transport"] end Inner --> BaseImpl Inner --> BlockImpl Params --> Inner
The block device implementation is straightforward, directly delegating most operations to the underlying VirtIO block device:
Method | Implementation | Purpose |
---|---|---|
try_new() | CreatesVirtIOBlk::new(transport) | Device initialization |
num_blocks() | Returnsinner.capacity() | Total device capacity |
block_size() | ReturnsSECTOR_SIZEconstant | Fixed 512-byte sectors |
read_block() | Callsinner.read_blocks() | Synchronous block reads |
write_block() | Callsinner.write_blocks() | Synchronous block writes |
flush() | No-op returningOk(()) | VirtIO handles consistency |
Sources: axdriver_virtio/src/blk.rs(L7 - L22) axdriver_virtio/src/blk.rs(L34 - L60)
VirtIO GPU Device Implementation
The VirtIoGpuDev
provides display and graphics functionality with framebuffer management.
Structure and Framebuffer Setup
flowchart TD subgraph subGraph1["Initialization Process"] Create["VirtIOGpu::new(transport)"] Setup["setup_framebuffer()"] GetRes["resolution()"] BuildInfo["Build DisplayInfo"] end subgraph subGraph0["VirtIoGpuDev Structure"] InnerGpu["inner: VirtIOGpu"] DisplayInfo["info: DisplayInfowidth, heightfb_base_vaddrfb_size"] end BuildInfo --> DisplayInfo Create --> Setup GetRes --> BuildInfo InnerGpu --> DisplayInfo Setup --> GetRes
The GPU device performs complex initialization to establish the framebuffer:
- Device Creation: Initialize the underlying VirtIO GPU device
- Framebuffer Setup: Call
setup_framebuffer()
to allocate GPU memory - Resolution Query: Get device capabilities for width and height
- Info Structure: Build
DisplayInfo
with framebuffer details
The DisplayInfo
structure contains:
width
,height
: Display resolutionfb_base_vaddr
: Virtual address of framebuffer memoryfb_size
: Total framebuffer size in bytes
Sources: axdriver_virtio/src/gpu.rs(L17 - L40) axdriver_virtio/src/gpu.rs(L52 - L70)
VirtIO Network Device Implementation
The VirtIoNetDev
is the most complex VirtIO implementation, featuring sophisticated buffer management for high-performance networking.
Buffer Management Architecture
flowchart TD subgraph subGraph1["VirtIO Queue Operations"] ReceiveBegin["receive_begin()"] TransmitBegin["transmit_begin()"] PollReceive["poll_receive()"] PollTransmit["poll_transmit()"] end subgraph subGraph0["VirtIoNetDev Buffer System"] RxBuffers["rx_buffers: [Option; QS]"] TxBuffers["tx_buffers: [Option; QS]"] FreeTxBufs["free_tx_bufs: Vec"] BufPool["buf_pool: Arc"] end BufPool --> FreeTxBufs BufPool --> RxBuffers BufPool --> TxBuffers FreeTxBufs --> TransmitBegin ReceiveBegin --> PollReceive RxBuffers --> ReceiveBegin TransmitBegin --> PollTransmit TxBuffers --> TransmitBegin
Network Device Initialization Process
The network device initialization involves three main phases:
- RX Buffer Pre-allocation: Fill all
QS
receive buffer slots
- Allocate NetBufBox from pool
- Call receive_begin() with buffer
- Store buffer in rx_buffers[token]
- TX Buffer Preparation: Pre-allocate transmit buffers with headers
- Allocate NetBufBox from pool
- Fill VirtIO header using fill_buffer_header()
- Store in free_tx_bufs for later use
- Pool Management: Initialize
NetBufPool
with2 * QS
total buffers
Network Operations Flow
Operation | Buffer Flow | Queue Interaction |
---|---|---|
Transmit | free_tx_bufs.pop()→tx_buffers[token] | transmit_begin()returns token |
Receive | rx_buffers[token].take()→ return to caller | poll_receive()returns token |
TX Recycle | tx_buffers[token].take()→free_tx_bufs.push() | poll_transmit()+transmit_complete() |
RX Recycle | Caller returns →rx_buffers[token] | receive_begin()with recycled buffer |
Sources: axdriver_virtio/src/net.rs(L14 - L73) axdriver_virtio/src/net.rs(L85 - L193)
Common Implementation Patterns
All VirtIO device implementations follow consistent architectural patterns:
Generic Type Parameters
flowchart TD subgraph subGraph1["Usage in Devices"] BlkDev["VirtIoBlkDev"] GpuDev["VirtIoGpuDev"] NetDev["VirtIoNetDev"] end subgraph subGraph0["Shared Generic Structure"] H["H: Hal(Hardware Abstraction)"] T["T: Transport(Communication Layer)"] QS["QS: usize(Queue Size - Net only)"] end H --> BlkDev H --> GpuDev H --> NetDev QS --> NetDev T --> BlkDev T --> GpuDev T --> NetDev
Trait Implementation Pattern
Each device implements the same trait hierarchy:
Trait | Block Device | GPU Device | Network Device |
---|---|---|---|
BaseDriverOps | ✓ "virtio-blk" | ✓ "virtio-gpu" | ✓ "virtio-net" |
Device-specific | BlockDriverOps | DisplayDriverOps | NetDriverOps |
Thread Safety | Send + Sync | Send + Sync | Send + Sync |
Error Handling Convention
All implementations use the as_dev_err
function to convert VirtIO-specific errors to DevError
types, providing consistent error handling across the driver framework.
Sources: axdriver_virtio/src/blk.rs(L24 - L32) axdriver_virtio/src/gpu.rs(L42 - L50) axdriver_virtio/src/net.rs(L75 - L83)
PCI Bus Operations
Relevant source files
This document covers the PCI (Peripheral Component Interconnect) bus operations provided by the axdriver_pci
crate. This crate serves as a bridge between the ArceOS driver framework and PCI device discovery and management functionality. It provides the foundational infrastructure for PCI device enumeration, configuration space access, and memory-mapped I/O (MMIO) region allocation needed by hardware device drivers.
For information about specific device driver implementations that use PCI operations, see Network Drivers and Block Storage Drivers. For VirtIO device integration over PCI transport, see VirtIO Integration.
Architecture Overview
The PCI bus operations layer sits between the hardware abstraction and device-specific drivers, providing standardized access to PCI configuration and memory management.
PCI Integration Architecture
flowchart TD subgraph subGraph3["Device Drivers"] NETDRIVERS["Network Drivers(ixgbe, VirtIO-Net)"] BLOCKDRIVERS["Block Drivers(VirtIO-Block)"] DISPLAYDRIVERS["Display Drivers(VirtIO-GPU)"] end subgraph subGraph2["virtio-drivers Foundation"] VIRTIOPCI["virtio_drivers::transport::pci::bus"] PCITYPES["PCI Types & Operations"] end subgraph subGraph1["axdriver_pci Layer"] PCIROOT["PciRootBus enumeration"] DEVICEFUNC["DeviceFunctionDevice access"] ALLOCATOR["PciRangeAllocatorMMIO allocation"] BARINFO["BarInfoMemory region info"] end subgraph subGraph0["Hardware Layer"] PCIHW["PCI Hardware Bus"] DEVICES["PCI Devices(Network, Storage, Display)"] end ALLOCATOR --> BLOCKDRIVERS ALLOCATOR --> DISPLAYDRIVERS ALLOCATOR --> NETDRIVERS DEVICEFUNC --> BLOCKDRIVERS DEVICEFUNC --> DISPLAYDRIVERS DEVICEFUNC --> NETDRIVERS DEVICES --> VIRTIOPCI PCIHW --> VIRTIOPCI PCIROOT --> BLOCKDRIVERS PCIROOT --> DISPLAYDRIVERS PCIROOT --> NETDRIVERS PCITYPES --> ALLOCATOR VIRTIOPCI --> BARINFO VIRTIOPCI --> DEVICEFUNC VIRTIOPCI --> PCIROOT
Sources: axdriver_pci/src/lib.rs(L1 - L14) axdriver_pci/Cargo.toml(L14 - L15)
Core PCI Types and Operations
The axdriver_pci
crate primarily re-exports essential PCI types from the virtio-drivers
crate, providing a consistent interface for PCI device management across the ArceOS ecosystem.
Device Discovery and Access
Type | Purpose | Key Operations |
---|---|---|
PciRoot | Root PCI bus controller | Device enumeration and scanning |
DeviceFunction | Individual PCI device/function | Configuration space access |
DeviceFunctionInfo | Device metadata | Vendor ID, device ID, class codes |
CapabilityInfo | PCI capabilities | Extended feature discovery |
Memory and I/O Management
Type | Purpose | Usage |
---|---|---|
BarInfo | Base Address Register info | Memory region mapping |
MemoryBarType | Memory BAR classification | 32-bit vs 64-bit addressing |
Command | PCI command register | Bus mastering, memory enable |
Status | PCI status register | Capability support, error status |
PCI Type Relationships
flowchart TD subgraph Configuration["Configuration"] COMMAND["Command"] STATUS["Status"] CAPINFO["CapabilityInfo"] end subgraph subGraph1["Memory Management"] BARINFO["BarInfo"] MEMBARTYPE["MemoryBarType"] ALLOCATOR["PciRangeAllocator"] end subgraph subGraph0["Device Discovery"] PCIROOT["PciRoot"] DEVICEFUNC["DeviceFunction"] DEVINFO["DeviceFunctionInfo"] end BARINFO --> ALLOCATOR BARINFO --> MEMBARTYPE DEVICEFUNC --> BARINFO DEVICEFUNC --> CAPINFO DEVICEFUNC --> COMMAND DEVICEFUNC --> DEVINFO DEVICEFUNC --> STATUS PCIROOT --> DEVICEFUNC
Sources: axdriver_pci/src/lib.rs(L11 - L14)
MMIO Range Allocation
The PciRangeAllocator
provides a custom memory allocation system specifically designed for PCI Base Address Register (BAR) allocation. This allocator ensures proper alignment and prevents memory region conflicts.
Allocator Structure
The PciRangeAllocator
maintains a simple linear allocation strategy:
pub struct PciRangeAllocator {
_start: u64, // Base address of allocatable region
end: u64, // End boundary of allocatable region
current: u64, // Current allocation pointer
}
Allocation Algorithm
The allocator implements power-of-2 aligned allocation with the following constraints:
Property | Requirement | Rationale |
---|---|---|
Size alignment | Must be power of 2 | PCI BAR size requirements |
Address alignment | Multiple of size | Hardware addressing constraints |
Boundary checking | Within allocated range | Memory safety |
MMIO Allocation Flow
flowchart TD START["alloc(size: u64)"] CHECK_POW2["size.is_power_of_two()"] ALIGN["ret = align_up(current, size)"] CHECK_BOUNDS["ret + size > end?"] UPDATE["current = ret + size"] RETURN_ADDR["return Some(ret)"] RETURN_NONE["return None"] ALIGN --> CHECK_BOUNDS CHECK_BOUNDS --> RETURN_NONE CHECK_BOUNDS --> UPDATE CHECK_POW2 --> ALIGN CHECK_POW2 --> RETURN_NONE START --> CHECK_POW2 UPDATE --> RETURN_ADDR
Usage Example
The typical allocation pattern for PCI BARs:
- Create allocator with memory range:
PciRangeAllocator::new(base, size)
- Request aligned memory:
allocator.alloc(bar_size)
- Map returned address to device BAR
Sources: axdriver_pci/src/lib.rs(L17 - L53)
Integration with Device Drivers
The PCI operations integrate with device drivers through standardized patterns for device discovery, configuration, and memory mapping.
Typical Driver Integration Pattern
sequenceDiagram participant DeviceDriver as "Device Driver" participant PciRoot as "PciRoot" participant DeviceFunction as "DeviceFunction" participant PciRangeAllocator as "PciRangeAllocator" DeviceDriver ->> PciRoot: Enumerate devices PciRoot ->> DeviceDriver: Found matching device DeviceDriver ->> DeviceFunction: Read configuration DeviceFunction ->> DeviceDriver: Device info & BARs DeviceDriver ->> PciRangeAllocator: Request MMIO region PciRangeAllocator ->> DeviceDriver: Allocated address DeviceDriver ->> DeviceFunction: Configure BAR mapping DeviceFunction ->> DeviceDriver: Device ready
Error Handling
PCI operations use the PciError
type for consistent error reporting across device initialization and configuration operations.
Sources: axdriver_pci/src/lib.rs(L11)
Dependencies and External Integration
The axdriver_pci
crate builds upon the virtio-drivers
crate version 0.7.4, specifically leveraging its PCI transport layer. This dependency provides:
- Low-level PCI configuration space access
- Standard PCI type definitions
- Cross-platform PCI hardware abstraction
The integration allows ArceOS drivers to benefit from the mature PCI implementation in virtio-drivers
while maintaining the consistent ArceOS driver interface patterns.
Sources: axdriver_pci/Cargo.toml(L15) axdriver_pci/src/lib.rs(L3 - L7)
Development and Build Configuration
Relevant source files
This document explains the build system, continuous integration pipeline, and development workflow for the axdriver_crates repository. It covers the automated testing and validation processes, multi-target compilation support, and documentation generation that ensure code quality and maintain the driver framework across different hardware platforms.
For information about the overall architecture and design patterns, see Architecture and Design. For details about specific driver implementations and their usage, see the respective subsystem documentation (Network Drivers, Block Storage Drivers, etc.).
Build System Overview
The axdriver_crates workspace uses Cargo's workspace functionality to manage multiple related crates with unified build configuration. The build system supports compilation for embedded targets without standard library support, requiring careful dependency management and feature gating.
Workspace Structure
flowchart TD subgraph subGraph2["Build Steps"] FORMAT_CHECK["cargo fmt --checkCode Formatting"] CLIPPY["cargo clippyLinting"] BUILD["cargo buildCompilation"] TEST["cargo testUnit Testing"] end subgraph subGraph1["Build Targets"] X86_64_LINUX["x86_64-unknown-linux-gnuStandard Linux"] X86_64_NONE["x86_64-unknown-noneBare Metal x86"] RISCV64["riscv64gc-unknown-none-elfRISC-V Embedded"] AARCH64["aarch64-unknown-none-softfloatARM64 Embedded"] end subgraph subGraph0["Workspace Root"] CARGO_TOML["Cargo.tomlWorkspace Configuration"] CI_YML[".github/workflows/ci.ymlBuild Pipeline"] GITIGNORE[".gitignoreBuild Artifacts"] end BUILD --> AARCH64 BUILD --> RISCV64 BUILD --> X86_64_LINUX BUILD --> X86_64_NONE CARGO_TOML --> BUILD CI_YML --> BUILD CI_YML --> CLIPPY CI_YML --> FORMAT_CHECK CI_YML --> TEST
The build system validates code across multiple architectures to ensure compatibility with various embedded and desktop platforms that ArceOS supports.
Sources: .github/workflows/ci.yml(L1 - L58) .gitignore(L1 - L5)
Continuous Integration Pipeline
The CI pipeline implements a comprehensive validation strategy using GitHub Actions, ensuring code quality and cross-platform compatibility for all commits and pull requests.
CI Job Matrix Configuration
flowchart TD subgraph subGraph2["Validation Steps"] SETUP["Setup Toolchainrust-src, clippy, rustfmt"] FORMAT["Format Checkcargo fmt --all -- --check"] LINT["Lintingcargo clippy --all-features"] COMPILE["Buildcargo build --all-features"] UNIT_TEST["Unit Testscargo test (Linux only)"] end subgraph subGraph1["CI Matrix"] TOOLCHAIN["nightlyRust Toolchain"] TARGET_1["x86_64-unknown-linux-gnu"] TARGET_2["x86_64-unknown-none"] TARGET_3["riscv64gc-unknown-none-elf"] TARGET_4["aarch64-unknown-none-softfloat"] end subgraph subGraph0["CI Trigger Events"] PUSH["git push"] PR["Pull Request"] end COMPILE --> UNIT_TEST FORMAT --> LINT LINT --> COMPILE PR --> TOOLCHAIN PUSH --> TOOLCHAIN SETUP --> FORMAT TOOLCHAIN --> TARGET_1 TOOLCHAIN --> TARGET_2 TOOLCHAIN --> TARGET_3 TOOLCHAIN --> TARGET_4
The pipeline uses the fail-fast: false
strategy to ensure all target platforms are tested even if one fails, providing comprehensive feedback for cross-platform issues.
Sources: .github/workflows/ci.yml(L6 - L30)
Documentation Pipeline
The documentation build process generates and deploys API documentation automatically for the main branch:
flowchart TD subgraph subGraph2["Documentation Features"] INDEX_PAGE["--enable-index-page"] BROKEN_LINKS["-D rustdoc::broken_intra_doc_links"] MISSING_DOCS["-D missing-docs"] end subgraph subGraph1["Documented Crates"] BASE["axdriver_base"] BLOCK["axdriver_block"] NET["axdriver_net"] DISPLAY["axdriver_display"] PCI["axdriver_pci"] VIRTIO["axdriver_virtio"] end subgraph subGraph0["Documentation Job"] DOC_TRIGGER["Main Branch Push"] DOC_BUILD["rustdoc Generation"] DOC_DEPLOY["GitHub Pages Deploy"] end DOC_BUILD --> BASE DOC_BUILD --> BLOCK DOC_BUILD --> BROKEN_LINKS DOC_BUILD --> DISPLAY DOC_BUILD --> DOC_DEPLOY DOC_BUILD --> INDEX_PAGE DOC_BUILD --> MISSING_DOCS DOC_BUILD --> NET DOC_BUILD --> PCI DOC_BUILD --> VIRTIO DOC_TRIGGER --> DOC_BUILD
The documentation pipeline enforces strict documentation requirements with -D missing-docs
and validates all internal links to maintain high-quality API documentation.
Sources: .github/workflows/ci.yml(L32 - L58)
Target Platform Support
The framework supports multiple target platforms with different characteristics and constraints:
Target Platform | Purpose | Standard Library | Use Case |
---|---|---|---|
x86_64-unknown-linux-gnu | Development/Testing | Full | Unit tests, development |
x86_64-unknown-none | Bare Metal x86 | No-std | PC-based embedded systems |
riscv64gc-unknown-none-elf | RISC-V Embedded | No-std | RISC-V microcontrollers |
aarch64-unknown-none-softfloat | ARM64 Embedded | No-std | ARM-based embedded systems |
Compilation Configuration
The build system uses --all-features
to ensure maximum compatibility testing across all optional features. Target-specific compilation is managed through:
- Toolchain Requirements: Nightly Rust with
rust-src
component for no-std targets - Component Dependencies:
clippy
andrustfmt
for code quality validation - Feature Testing: All features enabled during compilation to catch integration issues
Sources: .github/workflows/ci.yml(L11 - L12) .github/workflows/ci.yml(L17 - L19) .github/workflows/ci.yml(L25 - L27)
Development Workflow
Code Quality Gates
The development workflow enforces strict quality standards through automated checks:
flowchart TD subgraph subGraph2["Quality Requirements"] NO_FORMAT_ERRORS["No Format Violations"] NO_LINT_ERRORS["No Clippy Warnings(except new_without_default)"] ALL_TARGETS_BUILD["All Targets Compile"] TESTS_PASS["Unit Tests Pass"] end subgraph subGraph1["Automated Validation"] FORMAT_GATE["Format Checkcargo fmt --all -- --check"] CLIPPY_GATE["Lint Checkcargo clippy -A clippy::new_without_default"] BUILD_GATE["Cross-Platform Build4 target platforms"] TEST_GATE["Unit TestsLinux target only"] end subgraph subGraph0["Developer Workflow"] CODE_CHANGE["Code Changes"] LOCAL_TEST["Local Testing"] COMMIT["Git Commit"] PUSH["Git Push/PR"] end BUILD_GATE --> ALL_TARGETS_BUILD BUILD_GATE --> TEST_GATE CLIPPY_GATE --> BUILD_GATE CLIPPY_GATE --> NO_LINT_ERRORS CODE_CHANGE --> LOCAL_TEST COMMIT --> PUSH FORMAT_GATE --> CLIPPY_GATE FORMAT_GATE --> NO_FORMAT_ERRORS LOCAL_TEST --> COMMIT PUSH --> FORMAT_GATE TEST_GATE --> TESTS_PASS
Clippy Configuration
The CI pipeline uses a relaxed clippy configuration with -A clippy::new_without_default
to accommodate embedded development patterns where implementing Default
may not be appropriate for hardware drivers.
Sources: .github/workflows/ci.yml(L22 - L30)
Build Artifacts and Caching
Ignored Files and Directories
The repository maintains a minimal .gitignore
configuration focusing on essential build artifacts:
/target # Cargo build output
/.vscode # VSCode workspace files
.DS_Store # macOS system files
Cargo.lock # Lock file (workspace)
The Cargo.lock
exclusion is intentional for library crates, allowing consumers to use their preferred dependency versions while maintaining reproducible builds during development.
Sources: .gitignore(L1 - L5)
Documentation Deployment
Documentation deployment uses a single-commit strategy to GitHub Pages, minimizing repository size while maintaining complete API documentation history through the gh-pages
branch.
Sources: .github/workflows/ci.yml(L51 - L57)
Feature Configuration Strategy
The build system uses --all-features
compilation to ensure comprehensive testing of all optional functionality. This approach validates:
- Feature Compatibility: All feature combinations compile successfully
- Cross-Platform Features: Features work across all supported target platforms
- Integration Testing: Optional components integrate properly with core functionality
This strategy supports the modular architecture where different embedded systems can selectively enable only required driver components while maintaining compatibility guarantees.
Sources: .github/workflows/ci.yml(L25) .github/workflows/ci.yml(L27) .github/workflows/ci.yml(L47 - L50)
Overview
Relevant source files
Purpose and Scope
This document provides an overview of the axio
crate, a library that implements std::io
-like I/O traits for no_std
environments. It covers the crate's architecture, core components, and design principles. For detailed information about specific I/O traits, see Core I/O Traits. For configuration options and feature gates, see Crate Configuration and Features. For concrete implementations, see Implementations.
What is axio?
The axio
crate provides a collection of I/O traits and implementations designed specifically for no_std
environments such as embedded systems, OS kernels, and resource-constrained applications. It serves as a drop-in replacement for Rust's std::io
module when the standard library is not available.
The crate name reflects its purpose: "ax" (from ArceOS ecosystem) + "io" (input/output operations). It maintains API compatibility with std::io
while being usable in environments without heap allocation or operating system support.
Key characteristics:
no_std
compatible by default- Optional
alloc
feature for enhanced functionality - Minimal dependency footprint
- API compatibility with
std::io
- Designed for embedded and kernel development
Sources: Cargo.toml(L1 - L20) README.md(L1 - L10)
System Architecture
The following diagram shows the high-level architecture of the axio
crate and its relationship to external dependencies and target environments:
System Context and Dependencies
flowchart TD subgraph targets["Target Environments"] embedded["Embedded Systems"] kernels["OS Kernels"] nostd_apps["no_std Applications"] end subgraph features["Feature Configuration"] default_feat["default = []"] alloc_feat["alloc feature"] end subgraph implementations["Trait Implementations"] impls_rs["src/impls.rs"] buffered_mod["src/buffered/"] end subgraph axio_core["axio Core Library"] lib_rs["src/lib.rs"] error_rs["src/error.rs"] prelude_rs["src/prelude.rs"] end subgraph external["External Dependencies"] axerrno["axerrno crate"] alloc_crate["alloc crate"] end alloc_crate --> lib_rs alloc_feat --> alloc_crate axerrno --> error_rs default_feat --> lib_rs error_rs --> lib_rs lib_rs --> buffered_mod lib_rs --> embedded lib_rs --> impls_rs lib_rs --> kernels lib_rs --> nostd_apps lib_rs --> prelude_rs
Sources: Cargo.toml(L14 - L20)
Core Trait Ecosystem
The axio
crate implements four fundamental I/O traits that mirror those found in std::io
. This diagram maps the natural language concepts to specific code entities:
I/O Trait Hierarchy and Code Entities
flowchart TD subgraph implementations["Concrete Implementations"] slice_impl["&[u8] impl Read"] BufReader["BufReader"] end subgraph supporting_types["Supporting Types"] SeekFrom["SeekFrom enum"] Result_type["Result"] Error_type["Error"] PollState["PollState"] end subgraph trait_methods["Key Trait Methods"] read_method["Read::read()"] read_exact["Read::read_exact()"] write_method["Write::write()"] write_all["Write::write_all()"] seek_method["Seek::seek()"] fill_buf["BufRead::fill_buf()"] end subgraph core_traits["Core I/O Traits"] Read["Read trait"] Write["Write trait"] Seek["Seek trait"] BufRead["BufRead trait"] end BufRead --> Read BufRead --> Result_type BufRead --> fill_buf BufReader --> BufRead BufReader --> Read Read --> Result_type Read --> read_exact Read --> read_method Result_type --> Error_type Seek --> SeekFrom Seek --> seek_method Write --> Result_type Write --> write_all Write --> write_method slice_impl --> Read
Sources: Cargo.toml(L6)
Target Environments and Use Cases
The axio
crate is specifically designed for environments where std::io
is not available:
Environment | Use Case | Key Benefits |
---|---|---|
Embedded Systems | Microcontroller I/O operations | Minimal memory footprint, no heap required |
OS Kernels | Kernel-level I/O abstractions | No standard library dependency |
no_std Applications | Resource-constrained applications | Predictable memory usage |
ArceOS Ecosystem | Operating system components | Seamless integration with ArceOS |
The crate maintains compatibility with std::io
APIs, allowing code to be portable between std
and no_std
environments with minimal changes.
Sources: Cargo.toml(L11 - L12)
Feature Configuration
The axio
crate uses a feature-driven compilation model to provide different levels of functionality:
Default Configuration
- Feature set:
default = []
- no features enabled by default - Provides core I/O traits without heap allocation
- Minimal dependency footprint with only
axerrno
Enhanced Configuration
- Feature set:
alloc
- enables dynamic memory operations - Adds methods that require heap allocation (e.g.,
read_to_string
,read_to_end
) - Maintains
no_std
compatibility while providing enhanced functionality
This design allows the crate to serve both minimal embedded environments and more capable systems that have dynamic memory allocation available.
Sources: Cargo.toml(L14 - L16)
Dependencies and Integration
The axio
crate maintains a lean dependency graph:
Required Dependencies:
axerrno = "0.1"
- Provides error types and codes compatible with the ArceOS ecosystem
Optional Dependencies:
alloc
crate - Enabled via thealloc
feature for dynamic memory operations
The crate integrates with the broader ArceOS ecosystem through shared error handling conventions and API design patterns. Error handling is centralized through the axerrno
crate, ensuring consistency across ArceOS components.
Sources: Cargo.toml(L18 - L19) Cargo.toml(L8 - L9)
Related Documentation
For detailed information about specific aspects of the axio
crate:
- Trait specifications: See Core I/O Traits for detailed trait method documentation
- Configuration options: See Crate Configuration and Features for build and feature information
- Concrete implementations: See Implementations for usage examples and implementation details
- Error handling: See Error Handling for error types and error handling patterns
- Development setup: See Development and Maintenance for contributor information
Core I/O Traits
Relevant source files
This page documents the four fundamental I/O traits that form the foundation of the axio
library: Read
, Write
, Seek
, and BufRead
. These traits provide a std::io
-compatible interface for performing I/O operations in no_std
environments.
The traits are designed to mirror Rust's standard library I/O traits while supporting resource-constrained environments through optional allocation-dependent features. For information about concrete implementations of these traits, see Implementations. For details about the crate's feature configuration and dependency management, see Crate Configuration and Features.
Trait Hierarchy and Relationships
The core I/O traits in axio
form a well-structured hierarchy where BufRead
extends Read
, while Write
and Seek
operate independently. The following diagram shows the relationships between traits and their key methods:
flowchart TD Read["Read"] ReadMethods["read()read_exact()read_to_end()read_to_string()"] Write["Write"] WriteMethods["write()flush()write_all()write_fmt()"] Seek["Seek"] SeekMethods["seek()rewind()stream_position()"] BufRead["BufRead"] BufReadMethods["fill_buf()consume()has_data_left()read_until()read_line()"] ResultType["Result"] SeekFrom["SeekFromStart(u64)End(i64)Current(i64)"] AllocFeature["alloc feature"] ReadToEnd["read_to_end()read_to_string()read_until()read_line()"] AllocFeature --> ReadToEnd BufRead --> BufReadMethods BufRead --> Read BufReadMethods --> ResultType Read --> ReadMethods ReadMethods --> ResultType Seek --> SeekMethods SeekMethods --> ResultType SeekMethods --> SeekFrom Write --> WriteMethods WriteMethods --> ResultType
Sources: src/lib.rs(L152 - L355)
The Read Trait
The Read
trait provides the fundamental interface for reading bytes from a source. It defines both required and optional methods, with some methods only available when the alloc
feature is enabled.
Core Methods
Method | Signature | Description | Feature Requirement |
---|---|---|---|
read | fn read(&mut self, buf: &mut [u8]) -> Result | Required method to read bytes into buffer | None |
read_exact | fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> | Read exact number of bytes to fill buffer | None |
read_to_end | fn read_to_end(&mut self, buf: &mut Vec | Read all bytes until EOF | alloc |
read_to_string | fn read_to_string(&mut self, buf: &mut String) -> Result | Read all bytes as UTF-8 string | alloc |
The read
method is the only required implementation. The read_exact
method provides a default implementation that repeatedly calls read
until the buffer is filled or EOF is reached. When EOF is encountered before the buffer is filled, it returns an UnexpectedEof
error.
flowchart TD ReadTrait["Read trait"] RequiredRead["read(&mut self, buf: &mut [u8])"] ProvidedMethods["Provided implementations"] ReadExact["read_exact()"] AllocMethods["Allocation-dependent methods"] ReadToEnd["read_to_end()"] ReadToString["read_to_string()"] ReadExactLoop["Loop calling read() until buffer filled"] DefaultReadToEnd["default_read_to_end() function"] AppendToString["append_to_string() helper"] UnexpectedEof["UnexpectedEof error if incomplete"] SmallProbe["Small probe reads for efficiency"] Utf8Validation["UTF-8 validation"] AllocMethods --> ReadToEnd AllocMethods --> ReadToString AppendToString --> Utf8Validation DefaultReadToEnd --> SmallProbe ProvidedMethods --> AllocMethods ProvidedMethods --> ReadExact ReadExact --> ReadExactLoop ReadExactLoop --> UnexpectedEof ReadToEnd --> DefaultReadToEnd ReadToString --> AppendToString ReadTrait --> ProvidedMethods ReadTrait --> RequiredRead
Sources: src/lib.rs(L152 - L188)
Memory-Efficient Reading Strategy
The default_read_to_end
function implements an optimized reading strategy that balances performance with memory usage. It uses several techniques:
- Small probe reads: Initial 32-byte reads to avoid unnecessary capacity expansion
- Adaptive buffer sizing: Dynamically adjusts read buffer size based on reader behavior
- Capacity management: Uses
try_reserve
to handle allocation failures gracefully - Short read detection: Tracks consecutive short reads to optimize buffer sizes
Sources: src/lib.rs(L26 - L150)
The Write Trait
The Write
trait provides the interface for writing bytes to a destination. Like Read
, it has one required method with several provided implementations.
Core Methods
Method | Signature | Description |
---|---|---|
write | fn write(&mut self, buf: &[u8]) -> Result | Required method to write bytes from buffer |
flush | fn flush(&mut self) -> Result<()> | Required method to flush buffered data |
write_all | fn write_all(&mut self, buf: &[u8]) -> Result<()> | Write entire buffer or return error |
write_fmt | fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> Result<()> | Write formatted output |
The write_all
method ensures that the entire buffer is written by repeatedly calling write
until completion. If write
returns 0 (indicating no progress), it returns a WriteZero
error.
The write_fmt
method enables formatted output support by implementing a bridge between fmt::Write
and the I/O Write
trait through an internal Adapter
struct.
flowchart TD WriteTrait["Write trait"] RequiredMethods["Required methods"] ProvidedMethods["Provided methods"] WriteMethod["write()"] FlushMethod["flush()"] WriteAll["write_all()"] WriteFmt["write_fmt()"] WriteLoop["Loop calling write() until complete"] WriteZeroError["WriteZero error if no progress"] AdapterStruct["Adapter struct"] FmtWriteBridge["Bridge to fmt::Write"] ErrorTranslation["Translate fmt errors to I/O errors"] AdapterStruct --> FmtWriteBridge FmtWriteBridge --> ErrorTranslation ProvidedMethods --> WriteAll ProvidedMethods --> WriteFmt RequiredMethods --> FlushMethod RequiredMethods --> WriteMethod WriteAll --> WriteLoop WriteFmt --> AdapterStruct WriteLoop --> WriteZeroError WriteTrait --> ProvidedMethods WriteTrait --> RequiredMethods
Sources: src/lib.rs(L190 - L249)
The Seek Trait
The Seek
trait provides cursor positioning within streams that support random access. It works with the SeekFrom
enum to specify different seeking strategies.
Methods and SeekFrom Enum
Method | Signature | Description |
---|---|---|
seek | fn seek(&mut self, pos: SeekFrom) -> Result | Required method to seek to specified position |
rewind | fn rewind(&mut self) -> Result<()> | Convenience method to seek to beginning |
stream_position | fn stream_position(&mut self) -> Result | Get current position in stream |
The SeekFrom
enum defines three positioning strategies:
Start(u64)
: Absolute position from beginningEnd(i64)
: Relative position from end (negative values seek backwards)Current(i64)
: Relative position from current location
flowchart TD SeekTrait["Seek trait"] SeekMethod["seek(pos: SeekFrom)"] ConvenienceMethods["Convenience methods"] Rewind["rewind()"] StreamPosition["stream_position()"] SeekFromEnum["SeekFrom enum"] StartVariant["Start(u64)"] EndVariant["End(i64)"] CurrentVariant["Current(i64)"] SeekStart["seek(SeekFrom::Start(0))"] SeekCurrent["seek(SeekFrom::Current(0))"] PositionResult["Returns new position as u64"] ConvenienceMethods --> Rewind ConvenienceMethods --> StreamPosition Rewind --> SeekStart SeekFromEnum --> CurrentVariant SeekFromEnum --> EndVariant SeekFromEnum --> StartVariant SeekMethod --> PositionResult SeekMethod --> SeekFromEnum SeekTrait --> ConvenienceMethods SeekTrait --> SeekMethod StreamPosition --> SeekCurrent
Sources: src/lib.rs(L252 - L301)
The BufRead Trait
The BufRead
trait extends Read
to provide buffered reading capabilities. It enables efficient line-by-line reading and delimiter-based parsing through its internal buffer management.
Buffer Management Methods
Method | Signature | Description | Feature Requirement |
---|---|---|---|
fill_buf | fn fill_buf(&mut self) -> Result<&[u8]> | Required method to access internal buffer | None |
consume | fn consume(&mut self, amt: usize) | Required method to mark bytes as consumed | None |
has_data_left | fn has_data_left(&mut self) -> Result | Check if more data is available | None |
read_until | fn read_until(&mut self, byte: u8, buf: &mut Vec | Read until delimiter found | alloc |
read_line | fn read_line(&mut self, buf: &mut String) -> Result | Read until newline found | alloc |
The buffered reading pattern follows a fill-consume cycle where fill_buf
exposes the internal buffer and consume
marks bytes as processed. This allows for efficient parsing without unnecessary copying.
flowchart TD BufReadTrait["BufRead trait"] ExtendsRead["extends Read"] RequiredBuffer["Required buffer methods"] ProvidedParsing["Provided parsing methods"] FillBuf["fill_buf() -> &[u8]"] Consume["consume(amt: usize)"] HasDataLeft["has_data_left()"] AllocParsing["Allocation-dependent parsing"] ReadUntil["read_until(delimiter)"] ReadLine["read_line()"] InternalBuffer["Access to internal buffer"] MarkProcessed["Mark bytes as processed"] DelimiterSearch["Search for delimiter byte"] NewlineSearch["Search for 0xA (newline)"] FillConsumeLoop["fill_buf/consume loop"] Utf8Append["UTF-8 string append"] AllocParsing --> ReadLine AllocParsing --> ReadUntil BufReadTrait --> ExtendsRead BufReadTrait --> ProvidedParsing BufReadTrait --> RequiredBuffer Consume --> MarkProcessed DelimiterSearch --> FillConsumeLoop FillBuf --> InternalBuffer NewlineSearch --> Utf8Append ProvidedParsing --> AllocParsing ProvidedParsing --> HasDataLeft ReadLine --> NewlineSearch ReadUntil --> DelimiterSearch RequiredBuffer --> Consume RequiredBuffer --> FillBuf
Sources: src/lib.rs(L303 - L355)
Error Handling in Buffered Reading
The read_until
method demonstrates sophisticated error handling, particularly for the WouldBlock
error case. When fill_buf
returns WouldBlock
, the method continues the loop rather than propagating the error, enabling non-blocking I/O patterns.
Sources: src/lib.rs(L320 - L347)
Supporting Types and Utilities
PollState Structure
The PollState
struct provides a simple interface for I/O readiness polling, commonly used in async I/O scenarios:
pub struct PollState {
pub readable: bool,
pub writable: bool,
}
This structure enables applications to check I/O readiness without blocking, supporting efficient event-driven programming patterns.
Helper Functions
The crate provides several utility functions that support the trait implementations:
default_read_to_end
: Optimized implementation for reading all data with size hintsappend_to_string
: UTF-8 validation wrapper for string operations
These functions are designed to be reusable across different implementations while maintaining the performance characteristics expected in no_std
environments.
Sources: src/lib.rs(L357 - L380)
Feature-Gated Functionality
Many methods in the core traits are gated behind the alloc
feature, which enables dynamic memory allocation. This design allows the library to provide enhanced functionality when memory allocation is available while maintaining core functionality in highly constrained environments.
flowchart TD CoreTraits["Core trait methods"] AlwaysAvailable["Always available"] AllocGated["alloc feature gated"] ReadBasic["read(), read_exact()"] WriteBasic["write(), flush(), write_all()"] SeekAll["All Seek methods"] BufReadBasic["fill_buf(), consume()"] ReadDynamic["read_to_end(), read_to_string()"] BufReadParsing["read_until(), read_line()"] AllocFeature["alloc feature"] NoStdBasic["no_std environments"] Enhanced["Enhanced environments"] AllocFeature --> AllocGated AllocGated --> BufReadParsing AllocGated --> ReadDynamic AlwaysAvailable --> BufReadBasic AlwaysAvailable --> ReadBasic AlwaysAvailable --> SeekAll AlwaysAvailable --> WriteBasic CoreTraits --> AllocGated CoreTraits --> AlwaysAvailable Enhanced --> AllocGated Enhanced --> AlwaysAvailable NoStdBasic --> AlwaysAvailable
Sources: src/lib.rs(L7 - L8) src/lib.rs(L21 - L22) src/lib.rs(L159 - L168) src/lib.rs(L320 - L355)
Crate Configuration and Features
Relevant source files
This document explains the configuration structure of the axio
crate, including its feature gates, dependencies, and compilation targets. It covers how the crate's modular design enables different functionality levels depending on the target environment's capabilities.
For information about the actual I/O traits and their implementations, see Core I/O Traits and Implementations. For details about the build system and CI configuration, see Build System and CI.
Crate Metadata and Configuration
The axio
crate is configured as a no_std
-first library designed for resource-constrained environments. The core metadata defines its purpose and compatibility:
Property | Value |
---|---|
Name | axio |
Version | 0.1.1 |
Edition | 2021 |
Description | std::io-like I/O traits forno_stdenvironment |
Categories | no-std |
Keywords | arceos,io,no-std |
The crate uses a dual-licensing model supporting GPL-3.0-or-later, Apache-2.0, and MulanPSL-2.0 licenses, making it suitable for both open source and commercial projects.
Sources: Cargo.toml(L1 - L13)
Feature System Overview
The axio
crate implements a minimal feature system with two defined features:
flowchart TD subgraph subGraph2["Optional Functionality"] DYNAMIC["Dynamic Memory Operations"] VECTORS["Vec Support"] STRINGS["String Operations"] end subgraph subGraph1["Core Library"] CORE["Core I/O Traits"] NOSTD["no_std Compatibility"] end subgraph subGraph0["Feature Configuration"] DEFAULT["default = []"] ALLOC["alloc = []"] end ALLOC --> DYNAMIC ALLOC --> STRINGS ALLOC --> VECTORS CORE --> DYNAMIC DEFAULT --> CORE DEFAULT --> NOSTD
Feature Gate Dependencies and Enabled Functionality
The feature system follows a progressive enhancement model:
default = []
: Provides core I/O traits without any optional featuresalloc = []
: Enables dynamic memory operations that require heap allocation
Sources: Cargo.toml(L14 - L16)
Dependencies
The crate maintains a minimal dependency footprint to support its no_std
target:
Dependency Graph with Feature Gates
Core Dependencies
axerrno
: Provides unified error handling across the ArceOS ecosystemalloc
: Standard library allocation primitives (feature-gated)
The axerrno
dependency is always present and provides the Error
type used throughout the I/O trait definitions. The alloc
dependency is conditionally included only when the alloc
feature is enabled.
Sources: Cargo.toml(L18 - L19) src/lib.rs(L7 - L8) src/lib.rs(L21 - L22)
Feature-Specific Functionality
The alloc
feature gate controls access to operations requiring dynamic memory allocation:
Core Functionality (Always Available)
flowchart TD subgraph subGraph0["Always Available"] SEEK_OPS["Seek operations"] BUFREAD_BASIC["BufRead::fill_buf()"] BUFREAD_CONSUME["BufRead::consume()"] BUFREAD_HAS_DATA["BufRead::has_data_left()"] subgraph subGraph1["Feature-Gated (alloc)"] READ_TO_END["Read::read_to_end()"] READ_TO_STRING["Read::read_to_string()"] BUFREAD_UNTIL["BufRead::read_until()"] BUFREAD_LINE["BufRead::read_line()"] DEFAULT_READ["default_read_to_end()"] READ_BASIC["Read::read()"] READ_EXACT["Read::read_exact()"] WRITE_BASIC["Write::write()"] WRITE_ALL["Write::write_all()"] WRITE_FMT["Write::write_fmt()"] end end
Trait Methods by Feature Availability
Alloc-Gated Operations
When the alloc
feature is enabled, several additional methods become available:
Trait Method | Function | Requirements |
---|---|---|
Read::read_to_end() | Reads all bytes to aVec | Dynamic allocation |
Read::read_to_string() | Reads UTF-8 data to aString | Dynamic allocation + UTF-8 validation |
BufRead::read_until() | Reads until delimiter toVec | Dynamic allocation |
BufRead::read_line() | Reads line toString | Dynamic allocation + UTF-8 validation |
The implementation uses conditional compilation to gate these features:
#![allow(unused)] fn main() { #[cfg(feature = "alloc")] fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> }
Sources: src/lib.rs(L159 - L162) src/lib.rs(L165 - L168) src/lib.rs(L320 - L347) src/lib.rs(L351 - L354)
Compilation Targets
The crate supports multiple compilation scenarios based on feature selection:
flowchart TD subgraph subGraph2["Available APIs"] BASIC_API["Basic I/O Operations"] DYNAMIC_API["Dynamic Memory Operations"] COMPAT_API["std::io Compatibility"] end subgraph subGraph1["Feature Combinations"] MIN["default only"] ENHANCED["default + alloc"] end subgraph subGraph0["Target Environments"] EMBEDDED["Embedded Systems"] KERNEL["OS Kernels"] NOSTD_APP["no_std Applications"] STD_APP["std Applications"] end ENHANCED --> BASIC_API ENHANCED --> COMPAT_API ENHANCED --> DYNAMIC_API ENHANCED --> NOSTD_APP ENHANCED --> STD_APP MIN --> BASIC_API MIN --> EMBEDDED MIN --> KERNEL MIN --> NOSTD_APP
Compilation Targets and Feature Combinations
No Features (Default)
Provides core I/O traits suitable for:
- Embedded systems without heap allocation
- Kernel-space code
- Applications with strict memory constraints
WithallocFeature
Enables enhanced functionality for:
- Applications with heap allocation available
- Code requiring
std::io
compatibility - Systems needing dynamic buffer operations
The crate uses #![cfg_attr(not(doc), no_std)]
to maintain no_std
compatibility while allowing documentation generation with full standard library support.
Sources: src/lib.rs(L3) src/lib.rs(L7 - L8)
Feature Implementation Details
The feature system implementation uses several Rust conditional compilation patterns:
External Crate Imports
#[cfg(feature = "alloc")]
extern crate alloc;
This pattern conditionally imports the alloc
crate only when needed, avoiding link-time dependencies in constrained environments.
Type Imports
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
Dynamic types are imported only when the corresponding feature is enabled, preventing compilation errors in no_std
environments.
Function Gating
Complex functions like default_read_to_end
demonstrate sophisticated feature gating with full implementation details available only when alloc
is enabled. This function includes optimizations for buffer management, probe reading, and dynamic capacity growth.
Sources: src/lib.rs(L31 - L150) src/lib.rs(L357 - L370)
Implementations
Relevant source files
This document provides an overview of the concrete implementations of I/O traits provided by the axio crate. These implementations offer ready-to-use functionality for common I/O operations in no_std environments. For detailed information about the core I/O traits themselves, see Core I/O Traits. For in-depth coverage of specific implementation categories, see Buffered I/O and Basic Type Implementations.
The axio crate provides two main categories of implementations: basic type implementations for fundamental Rust types, and buffered I/O implementations that add performance optimizations. These implementations are designed to work seamlessly in no_std environments while maintaining compatibility with std::io patterns.
Implementation Categories
The axio crate organizes its implementations into distinct categories based on functionality and feature requirements:
Category | Types | Traits Implemented | Feature Requirements |
---|---|---|---|
Basic Types | &[u8] | Read | None |
Buffered I/O | BufReader | Read,BufRead | None for core,allocfor enhanced methods |
Implementation Architecture
flowchart TD subgraph subGraph3["Source Files"] ImplsFile["src/impls.rs"] BufferedMod["src/buffered/mod.rs"] BufReaderFile["src/buffered/bufreader.rs"] end subgraph subGraph2["Buffered Implementations"] BufReaderImpl["BufReader impl Read"] BufReaderBufImpl["BufReader impl BufRead"] end subgraph subGraph1["Basic Implementations"] SliceImpl["&[u8] impl Read"] end subgraph subGraph0["Trait Definitions"] ReadTrait["Read trait"] WriteTrait["Write trait"] SeekTrait["Seek trait"] BufReadTrait["BufRead trait"] end BufReadTrait --> BufReaderBufImpl BufReaderFile --> BufReaderBufImpl BufReaderFile --> BufReaderImpl BufferedMod --> BufReaderBufImpl BufferedMod --> BufReaderImpl ImplsFile --> SliceImpl ReadTrait --> BufReaderImpl ReadTrait --> SliceImpl
Sources: src/impls.rs(L1 - L54) src/buffered/mod.rs(L1 - L4)
Feature-Gated Implementation Matrix
Different implementations provide varying levels of functionality depending on enabled cargo features:
flowchart TD subgraph Implementations["Implementations"] SliceRead["&[u8] Read impl"] BufReaderRead["BufReader Read impl"] end subgraph subGraph1["alloc Feature"] AllocMethods["Allocation Methodsread_to_end()read_to_string()"] end subgraph subGraph0["Default Features"] CoreMethods["Core Methodsread()read_exact()"] end AllocMethods --> BufReaderRead AllocMethods --> SliceRead CoreMethods --> BufReaderRead CoreMethods --> SliceRead
Sources: src/impls.rs(L47 - L53)
Implementation Details Overview
Basic Type Implementations
The &[u8]
implementation in src/impls.rs
provides a fundamental building block for reading from byte slices. This implementation includes performance optimizations and careful memory handling:
- Single-byte optimization: Special handling for single-byte reads to avoid
memcpy
overhead - Exact reading:
read_exact()
method with proper error handling for insufficient data - Feature-gated extensions:
read_to_end()
method available only withalloc
feature
The implementation uses axerrno::ax_err!
macro for consistent error reporting and advances the slice pointer after each read operation.
Buffered I/O Module Structure
The buffered I/O implementations are organized through a module hierarchy:
flowchart TD subgraph subGraph0["Public API"] BufReaderType["BufReader type"] ReadImpl["Read trait impl"] BufReadImpl["BufRead trait impl"] end BufferedMod["src/buffered/mod.rs"] BufReaderMod["src/buffered/bufreader.rs"] BufReaderExport["BufReader export"] BufReaderImpl["BufReader implementation"] BufReaderExport --> BufReaderType BufReaderImpl --> BufReadImpl BufReaderImpl --> ReadImpl BufReaderMod --> BufReaderImpl BufferedMod --> BufReaderExport
Sources: src/buffered/mod.rs(L1 - L4)
Implementation Characteristics
All implementations in axio share common design principles:
- no_std compatibility: No reliance on standard library components
- Zero-copy where possible: Efficient memory usage patterns
- Consistent error handling: Integration with
axerrno
error system - Feature-aware compilation: Conditional compilation based on cargo features
The implementations maintain API compatibility with std::io counterparts while providing the flexibility needed for resource-constrained environments.
Sources: src/impls.rs(L1 - L54) src/buffered/mod.rs(L1 - L4)
Buffered I/O
Relevant source files
This document covers the buffered I/O functionality provided by the axio crate, specifically focusing on the BufRead
trait and its concrete implementation BufReader<R>
. Buffered I/O enables efficient reading from sources by maintaining an internal buffer, reducing the number of system calls and improving performance for sequential read operations.
For information about basic I/O trait implementations for primitive types, see Basic Type Implementations. For details about the core I/O traits that form the foundation of this buffered system, see Core I/O Traits.
BufRead Trait
The BufRead
trait extends the basic Read
trait to provide buffered reading capabilities. It defines the interface for readers that maintain an internal buffer, allowing for more efficient reading patterns and specialized operations like reading until a delimiter.
Core Methods
The trait defines several key methods for buffer management and specialized reading:
Method | Purpose | Availability |
---|---|---|
fill_buf() | Returns buffered data, filling from source if empty | Always |
consume(amt) | Marks bytes as consumed from buffer | Always |
has_data_left() | Checks if more data is available | Always |
read_until(byte, buf) | Reads until delimiter or EOF | allocfeature |
read_line(buf) | Reads until newline | allocfeature |
flowchart TD Read["Read Trait"] BufRead["BufRead Trait"] fill_buf["fill_buf()"] consume["consume(amt)"] has_data_left["has_data_left()"] read_until["read_until(byte, buf)"] read_line["read_line(buf)"] alloc_feature["alloc feature"] buffer_ref["&[u8]"] bool_result["bool"] usize_result["usize"] BufRead --> consume BufRead --> fill_buf BufRead --> has_data_left BufRead --> read_line BufRead --> read_until Read --> BufRead fill_buf --> buffer_ref has_data_left --> bool_result read_line --> alloc_feature read_line --> usize_result read_until --> alloc_feature read_until --> usize_result
Sources: src/lib.rs(L305 - L355)
Buffer Management Protocol
The BufRead
trait establishes a protocol for buffer management through the interaction between fill_buf()
and consume()
:
fill_buf()
returns a reference to the internal buffer, filling it from the underlying source if necessary- Consumer reads from the returned buffer slice
consume(amt)
notifies the buffer thatamt
bytes have been processed and can be discarded
sequenceDiagram participant Consumer as "Consumer" participant BufReader as "BufReader" participant Source as "Source" Consumer ->> BufReader: "fill_buf()" alt "Buffer Empty" BufReader ->> Source: "read(internal_buf)" Source -->> BufReader: "data" end BufReader -->> Consumer: "&[u8] buffer_slice" Consumer ->> Consumer: "process data" Consumer ->> BufReader: "consume(amt)" BufReader ->> BufReader: "advance position"
Sources: src/lib.rs(L308 - L312) src/buffered/bufreader.rs(L146 - L158)
BufReader Implementation
The BufReader<R>
struct provides a concrete implementation of buffered reading for any type that implements Read
. It maintains a fixed-size internal buffer and implements both Read
and BufRead
traits.
Internal Structure
flowchart TD subgraph subGraph1["Buffer State"] consumed["Consumed Data[0..pos)"] available["Available Data[pos..filled)"] empty["Empty Space[filled..1024)"] end subgraph BufReader<R>["BufReader"] inner["inner: R"] pos["pos: usize"] filled["filled: usize"] buf["buf: [u8; 1024]"] end source["Underlying Reader"] filled --> available filled --> empty inner --> source pos --> available pos --> consumed
Sources: src/buffered/bufreader.rs(L8 - L26)
The BufReader
maintains state through three key fields:
inner
: The wrapped reader of typeR
pos
: Current read position within the bufferfilled
: Number of valid bytes in the bufferbuf
: Fixed 1KB buffer array
Buffer Management Implementation
The BufReader
implements sophisticated buffer management to optimize different reading patterns:
flowchart TD read_request["Read Request"] is_empty["Buffer Empty?"] large_read["Request >= 1KB?"] use_buffer["Use Buffered Data"] bypass["Bypass BufferDirect Read"] fill_buffer["Fill Internal Buffer"] consume_data["consume(amt)"] return_result["Return Result"] bypass --> return_result consume_data --> return_result fill_buffer --> use_buffer is_empty --> large_read is_empty --> use_buffer large_read --> bypass large_read --> fill_buffer read_request --> is_empty use_buffer --> consume_data
Sources: src/buffered/bufreader.rs(L68 - L83)
Read Trait Implementation
The Read
implementation for BufReader
includes several optimizations:
Large Read Bypass
When the buffer is empty and the requested read size is at least 1KB (the buffer capacity), BufReader
bypasses its internal buffer entirely and reads directly into the destination buffer.
Optimized read_exact
For small exact reads that fit within the current buffer, BufReader
provides an optimized path that avoids the loop in the default read_exact
implementation.
Delegation for read_to_end
The read_to_end
method drains the current buffer first, then delegates to the inner reader's potentially optimized implementation.
Sources: src/buffered/bufreader.rs(L89 - L108)
BufRead Trait Implementation
The BufRead
implementation manages the internal buffer state:
flowchart TD fill_buf_call["fill_buf() called"] buffer_check["is_empty()?"] read_inner["inner.read(&mut buf)"] return_current["Return current buffer"] update_state["pos = 0filled = read_len"] return_buffer["Return &buf[pos..filled]"] consume_call["consume(amt) called"] update_pos["pos = min(pos + amt, filled)"] buffer_check --> read_inner buffer_check --> return_current consume_call --> update_pos fill_buf_call --> buffer_check read_inner --> update_state return_current --> return_buffer update_state --> return_buffer
Sources: src/buffered/bufreader.rs(L146 - L158)
Buffer Allocation Strategies
The BufReader
uses a fixed-size buffer allocation strategy that balances memory usage with performance:
Aspect | Implementation | Rationale |
---|---|---|
Buffer Size | 1024 bytes (1KB) | Balances memory usage with read efficiency |
Allocation | Stack-allocated array | Avoids heap allocation, suitable forno_std |
Reuse | Buffer is reused across reads | Minimizes allocation overhead |
The buffer size constant is defined as DEFAULT_BUF_SIZE: usize = 1024
and cannot be configured at runtime, ensuring predictable memory usage in constrained environments.
Sources: src/buffered/bufreader.rs(L6) src/buffered/bufreader.rs(L13)
Performance Optimizations
Read Pattern Optimization
flowchart TD subgraph subGraph1["Large Reads"] exact["read_exact()"] fast_path["Fast Path forBuffer-Sized Reads"] copy["Single memcpy"] large["Unsupported markdown: blockquote"] direct["Direct to Destination"] minimal["Minimal Overhead"] small["< 1KB reads"] buffered["Use Internal Buffer"] efficient["Efficient forSequential Access"] subgraph subGraph2["Exact Reads"] subgraph subGraph0["Small Reads"] exact["read_exact()"] fast_path["Fast Path forBuffer-Sized Reads"] copy["Single memcpy"] large["Unsupported markdown: blockquote"] direct["Direct to Destination"] minimal["Minimal Overhead"] small["< 1KB reads"] buffered["Use Internal Buffer"] efficient["Efficient forSequential Access"] end end end buffered --> efficient direct --> minimal exact --> fast_path fast_path --> copy large --> direct small --> buffered
Sources: src/buffered/bufreader.rs(L73 - L76) src/buffered/bufreader.rs(L89 - L97)
String Reading Optimization
For read_to_string()
operations, BufReader
implements an optimization that avoids unnecessary memory copies when the target string is empty, allowing direct reading into the string's internal buffer.
Sources: src/buffered/bufreader.rs(L113 - L142)
Feature Dependencies
The buffered I/O implementation has conditional compilation based on the alloc
feature:
Feature State | Available Methods | Limitations |
---|---|---|
Withoutalloc | Core buffering, basic reads | No dynamic allocation methods |
Withalloc | All methods includingread_until,read_line | Full functionality |
Methods requiring dynamic allocation are conditionally compiled and only available when the alloc
feature is enabled.
Sources: src/buffered/bufreader.rs(L3 - L4) src/buffered/bufreader.rs(L101) src/buffered/bufreader.rs(L112)
Basic Type Implementations
Relevant source files
This section documents the concrete implementations of I/O traits for basic Rust types provided by the axio crate. These implementations enable fundamental data types to participate in the I/O trait ecosystem without requiring additional wrapper types.
The primary focus is on implementations for byte slices (&[u8]
), which provide efficient read operations directly from memory. For buffered I/O implementations like BufReader
, see Buffered I/O. For error handling across all implementations, see Error Handling.
Byte Slice Read Implementation
The axio crate provides a comprehensive Read
trait implementation for byte slices (&[u8]
), enabling direct reading from memory buffers. This implementation is found in src/impls.rs(L4 - L54) and provides all core Read
trait methods with performance optimizations.
Implementation Overview
The &[u8]
implementation supports three key methods from the Read
trait:
Method | Purpose | Feature Gate |
---|---|---|
read() | Pull bytes into a buffer | Always available |
read_exact() | Read exact number of bytes | Always available |
read_to_end() | Read all remaining bytes | allocfeature |
Sources: src/impls.rs(L4 - L54)
Core Read Method
The fundamental read()
method implementation uses an optimized approach for copying data from the byte slice to the destination buffer:
flowchart TD A["read(buf: &mut [u8])"] B["Calculate amt = min(buf.len(), self.len())"] C["amt == 1?"] D["Single byte copy: buf[0] = a[0]"] E["Slice copy: buf[..amt].copy_from_slice(a)"] F["Update slice: *self = &self[amt..]"] G["Return Ok(amt)"] A --> B B --> C C --> D C --> E D --> F E --> F F --> G
Byte Slice Read Operation Flow
The implementation includes a performance optimization that avoids the overhead of copy_from_slice
for single-byte reads, directly assigning buf[0] = a[0]
instead.
Sources: src/impls.rs(L6 - L22)
Exact Read Implementation
The read_exact()
method provides guaranteed completion semantics, either reading the exact requested amount or returning an error:
flowchart TD A["read_exact(buf: &mut [u8])"] B["buf.len() > self.len()?"] C["Return UnexpectedEof error"] D["Calculate amt = buf.len()"] E["amt == 1?"] F["Single byte: buf[0] = a[0]"] G["Slice copy: buf[..amt].copy_from_slice(a)"] H["Update slice: *self = &self[amt..]"] I["Return Ok(())"] A --> B B --> C B --> D D --> E E --> F E --> G F --> H G --> H H --> I
Exact Read Operation Flow
This method differs from read()
by guaranteeing that either all requested bytes are read or an UnexpectedEof
error is returned.
Sources: src/impls.rs(L25 - L44)
Allocation-Dependent Methods
When the alloc
feature is enabled, the byte slice implementation provides read_to_end()
, which efficiently transfers all remaining data:
flowchart TD A["read_to_end(buf: &mut Vec)"] B["buf.extend_from_slice(self)"] C["let len = self.len()"] D["*self = &self[len..]"] E["Return Ok(len)"] A --> B B --> C C --> D D --> E
Read to End Operation Flow
This implementation leverages Vec::extend_from_slice()
for optimal performance when copying all remaining data from the byte slice.
Sources: src/impls.rs(L47 - L53)
Trait Method Mapping
The following diagram shows how the generic Read
trait methods map to specific implementations for byte slices:
classDiagram note for Read "Optimized for memory efficiencySingle-byte copy optimizationDirect slice manipulation" note for ByteSliceImpl "Optimized for memory efficiencySingle-byte copy optimizationDirect slice manipulation" note for Read "Unsupported markdown: list" class Read { <<trait>> +read(buf: &mut [u8]) Result~usize~ +read_exact(buf: &mut [u8]) Result~() ~ +read_to_end(buf: &mut Vec~u8~) Result~usize~ } class ByteSliceImpl { +read(buf: &mut [u8]) Result~usize~ +read_exact(buf: &mut [u8]) Result~() ~ +read_to_end(buf: &mut Vec~u8~) Result~usize~ } class &[u8] { -data: bytes -position: usize } Read ..|> ByteSliceImpl : implements ByteSliceImpl --> ByteSliceImpl : operates on
Trait Implementation Relationship
Sources: src/lib.rs(L152 - L188) src/impls.rs(L4 - L54)
Performance Characteristics
The byte slice implementation includes several performance optimizations:
Memory Copy Optimization
The implementation uses conditional logic to optimize memory operations based on the amount of data being copied:
Scenario | Implementation | Rationale |
---|---|---|
Single byte (amt == 1) | Direct assignment:buf[0] = a[0] | Avoidsmemcpyoverhead |
Multiple bytes (amt > 1) | Slice copy:copy_from_slice(a) | Leverages optimizedmemcpy |
Slice Management
All methods update the source slice pointer (*self = b
) to reflect consumed data, maintaining zero-copy semantics where the slice reference advances through the underlying data without additional allocations.
Sources: src/impls.rs(L14 - L18) src/impls.rs(L36 - L40)
Error Handling Integration
The byte slice implementations integrate with the axio error system through the axerrno
crate:
flowchart TD A["&[u8] methods"] B["Validation check"] C["Successful operation"] D["axerrno::ax_err!"] E["Error::UnexpectedEof"] F["Result::Ok"] G["Result::Err"] A --> B B --> C B --> D C --> F D --> E E --> G
Error Flow in Byte Slice Operations
The primary error condition for byte slice operations is UnexpectedEof
, which occurs when read_exact()
is called with a buffer larger than the remaining slice data.
Sources: src/impls.rs(L27) src/error.rs
Supporting Systems
Relevant source files
This document provides an overview of the supporting modules that enable the core I/O functionality in the axio crate. These systems provide essential infrastructure for error handling, convenience imports, and integration with external dependencies. The supporting systems are designed to be minimal and focused, maintaining the crate's no_std
compatibility while providing necessary functionality.
For detailed information about error handling mechanisms, see Error Handling. For information about convenience imports and trait re-exports, see Prelude Module.
Supporting Module Architecture
The axio crate relies on two primary supporting modules that work together to enable the core I/O trait functionality. These modules provide a clean separation between error handling concerns and user-facing API convenience.
Supporting Systems Overview
flowchart TD subgraph subGraph3["Client Code"] user_imports["use axio::prelude::*"] error_handling["Result"] end subgraph subGraph2["Core I/O System"] lib_rs["src/lib.rs"] traits["Read, Write, Seek, BufRead"] end subgraph subGraph1["axio Supporting Systems"] error_rs["src/error.rs"] prelude_rs["src/prelude.rs"] end subgraph subGraph0["External Dependencies"] axerrno["axerrno crate"] end axerrno --> error_rs error_rs --> error_handling error_rs --> lib_rs lib_rs --> error_handling lib_rs --> traits prelude_rs --> user_imports traits --> prelude_rs
Sources: src/error.rs(L1 - L3) src/prelude.rs(L1 - L12)
Module Interaction Flow
The supporting systems create a clear data flow that separates concerns while maintaining a cohesive API surface.
flowchart TD subgraph subGraph2["Client Usage"] trait_methods["trait methods"] result_handling["Result handling"] end subgraph subGraph1["Import Path"] core_traits["BufRead, Read, Seek, Write"] prelude_exports["prelude.rs exports"] glob_import["use axio::prelude::*"] end subgraph subGraph0["Error Path"] axerrno_AxError["axerrno::AxError"] axerrno_AxResult["axerrno::AxResult"] error_rs_Error["error.rs::Error"] error_rs_Result["error.rs::Result"] end axerrno_AxError --> error_rs_Error axerrno_AxResult --> error_rs_Result core_traits --> prelude_exports error_rs_Result --> result_handling glob_import --> trait_methods prelude_exports --> glob_import trait_methods --> result_handling
Sources: src/error.rs(L1 - L2) src/prelude.rs(L11)
Error System Integration
The src/error.rs
module serves as a facade layer that re-exports error types from the axerrno
crate. This design provides a stable API surface while delegating the actual error handling implementation to the specialized axerrno
crate.
Component | Type | Purpose |
---|---|---|
Error | Type alias | Re-exportsaxerrno::AxErroras the primary error type |
Result | Type alias | Re-exportsaxerrno::AxResultas the standard result type |
The error system provides consistent error handling across all I/O operations without requiring clients to directly depend on the axerrno
crate.
Sources: src/error.rs(L1 - L2)
Prelude System Organization
The src/prelude.rs
module provides a convenience layer that re-exports the four core I/O traits. This follows the established Rust pattern of providing a prelude module for commonly used imports.
The prelude exports include:
BufRead
- Buffered reading operationsRead
- Basic reading operationsSeek
- Stream positioning operationsWrite
- Basic writing operations
This design allows clients to import all core functionality with a single glob import: use axio::prelude::*
.
Sources: src/prelude.rs(L11)
System Dependencies
The supporting systems maintain minimal external dependencies to preserve the crate's no_std
compatibility:
System | External Dependencies | Purpose |
---|---|---|
Error handling | axerrnocrate | Provides error types and result handling |
Prelude | None | Re-exports internal traits |
Both supporting systems are designed to be thin facade layers that add minimal overhead while providing essential functionality for the core I/O trait system.
Sources: src/error.rs(L1 - L2) src/prelude.rs(L1 - L12)
Error Handling
Relevant source files
This document covers error handling mechanisms within the axio
crate, explaining how errors are defined, propagated, and handled across I/O operations. The error system provides consistent error reporting for no_std
environments while maintaining compatibility with standard I/O error patterns.
For information about the core I/O traits that use these error types, see Core I/O Traits. For details about specific implementations that handle errors, see Implementations.
Error Type System
The axio
crate implements a minimalist error facade over the axerrno
crate, providing consistent error handling across all I/O operations. The error system consists of two primary types that are re-exported from axerrno
.
Core Error Types
Type | Source | Purpose |
---|---|---|
Error | axerrno::AxError | Represents all possible I/O error conditions |
Result | axerrno::AxResult | Standard result type for I/O operations |
flowchart TD subgraph subGraph2["I/O Trait Methods"] READ_METHOD["Read::read()"] WRITE_METHOD["Write::write()"] SEEK_METHOD["Seek::seek()"] FILL_BUF_METHOD["BufRead::fill_buf()"] end subgraph subGraph1["axerrno Crate"] AX_ERROR["AxError"] AX_RESULT["AxResult<T>"] end subgraph subGraph0["axio Error System"] ERROR_MOD["src/error.rs"] ERROR_TYPE["Error"] RESULT_TYPE["Result<T>"] end ERROR_MOD --> ERROR_TYPE ERROR_MOD --> RESULT_TYPE ERROR_TYPE --> AX_ERROR FILL_BUF_METHOD --> RESULT_TYPE READ_METHOD --> RESULT_TYPE RESULT_TYPE --> AX_RESULT SEEK_METHOD --> RESULT_TYPE WRITE_METHOD --> RESULT_TYPE
Sources: src/error.rs(L1 - L3) src/lib.rs(L19)
Error Categories and Usage Patterns
The axio
crate utilizes specific error variants through the ax_err!
macro to provide meaningful error information for different failure scenarios.
flowchart TD subgraph subGraph3["Buffered Operations"] READ_UNTIL["read_until()"] READ_LINE["read_line()"] WOULD_BLOCK["WouldBlock"] end subgraph subGraph2["Write Operations"] WRITE_ALL["write_all()"] WRITE_FMT["write_fmt()"] WRITE_ZERO["WriteZero"] INVALID_DATA["InvalidData"] end subgraph subGraph1["Read Operations"] READ_EXACT["read_exact()"] READ_TO_END["read_to_end()"] UNEXPECTED_EOF["UnexpectedEof"] end subgraph subGraph0["Memory Operations"] MEM_ALLOC["Memory Allocation"] NO_MEMORY["NoMemory"] end MEM_ALLOC --> NO_MEMORY READ_EXACT --> UNEXPECTED_EOF READ_LINE --> INVALID_DATA READ_TO_END --> NO_MEMORY READ_UNTIL --> WOULD_BLOCK WRITE_ALL --> WRITE_ZERO WRITE_FMT --> INVALID_DATA
Sources: src/lib.rs(L89) src/lib.rs(L183) src/lib.rs(L203) src/lib.rs(L244) src/lib.rs(L328) src/lib.rs(L366)
Error Creation and Propagation
Theax_err!Macro Pattern
The axio
crate consistently uses the ax_err!
macro from axerrno
to create structured errors with context information. This macro allows for both simple error creation and error wrapping.
Error Type | Usage Context | Location |
---|---|---|
NoMemory | Vector allocation failure | src/lib.rs89 |
UnexpectedEof | Incomplete buffer fill | src/lib.rs183 |
WriteZero | Failed complete write | src/lib.rs203 |
InvalidData | Format/UTF-8 errors | src/lib.rs244src/lib.rs366 |
Error Flow in I/O Operations
flowchart TD subgraph subGraph3["Error Propagation"] QUESTION_MARK["? operator"] EARLY_RETURN["Early return"] RESULT_TYPE_RETURN["Result<T> return"] end subgraph subGraph2["Error Creation"] AX_ERR_MACRO["ax_err! macro"] ERROR_CONTEXT["Error with context"] end subgraph subGraph1["Error Detection Points"] MEM_CHECK["Memory Allocation"] BUFFER_CHECK["Buffer State"] DATA_CHECK["Data Validation"] BLOCKING_CHECK["Non-blocking State"] end subgraph subGraph0["Operation Types"] READ_OP["Read Operation"] WRITE_OP["Write Operation"] SEEK_OP["Seek Operation"] BUF_OP["Buffered Operation"] end IO_OP["I/O Operation Initiated"] AX_ERR_MACRO --> QUESTION_MARK BLOCKING_CHECK --> ERROR_CONTEXT BUFFER_CHECK --> AX_ERR_MACRO BUF_OP --> BLOCKING_CHECK BUF_OP --> DATA_CHECK BUF_OP --> MEM_CHECK DATA_CHECK --> AX_ERR_MACRO EARLY_RETURN --> RESULT_TYPE_RETURN ERROR_CONTEXT --> QUESTION_MARK IO_OP --> BUF_OP IO_OP --> READ_OP IO_OP --> SEEK_OP IO_OP --> WRITE_OP MEM_CHECK --> AX_ERR_MACRO QUESTION_MARK --> EARLY_RETURN READ_OP --> BUFFER_CHECK WRITE_OP --> BUFFER_CHECK
Sources: src/lib.rs(L24) src/lib.rs(L89) src/lib.rs(L183) src/lib.rs(L203) src/lib.rs(L244) src/lib.rs(L328) src/lib.rs(L366)
Specific Error Handling Implementations
Memory Management Errors
The default_read_to_end
function demonstrates sophisticated error handling for memory allocation failures in no_std
environments.
flowchart TD subgraph subGraph1["Error Handling Flow"] ALLOCATION_FAIL["Allocation Fails"] ERROR_WRAPPING["Error Wrapping"] EARLY_RETURN["Return Err(Error)"] end subgraph subGraph0["default_read_to_end Function"] START["Function Entry"] TRY_RESERVE["buf.try_reserve(PROBE_SIZE)"] CHECK_RESULT["Check Result"] WRAP_ERROR["ax_err!(NoMemory, e)"] CONTINUE["Continue Operation"] end ALLOCATION_FAIL --> WRAP_ERROR CHECK_RESULT --> ALLOCATION_FAIL CHECK_RESULT --> CONTINUE ERROR_WRAPPING --> EARLY_RETURN START --> TRY_RESERVE TRY_RESERVE --> CHECK_RESULT WRAP_ERROR --> ERROR_WRAPPING
Sources: src/lib.rs(L88 - L90)
Buffer State Validation
The read_exact
method implements comprehensive error handling for incomplete read operations.
sequenceDiagram participant Client as Client participant read_exact as read_exact participant read_method as read_method participant ax_err_macro as ax_err_macro Client ->> read_exact: read_exact(&mut buf) loop "While buffer not loop empty" read_exact ->> read_method: read(buf) alt "Read returns 0" read_method -->> read_exact: Ok(0) read_exact ->> read_exact: break loop else "Read returns n > 0" read_method -->> read_exact: Ok(n) read_exact ->> read_exact: advance buffer else "Read returns error" read_method -->> read_exact: Err(e) read_exact -->> Client: Err(e) end end end alt "Buffer still has data" read_exact ->> ax_err_macro: ax_err!(UnexpectedEof, message) ax_err_macro -->> read_exact: Error read_exact -->> Client: Err(Error) else "Buffer fully consumed" read_exact -->> Client: Ok(()) end
Sources: src/lib.rs(L171 - L187)
Non-blocking I/O Error Handling
The read_until
method demonstrates special handling for WouldBlock
errors in non-blocking scenarios.
Error Type | Handling Strategy | Code Location |
---|---|---|
WouldBlock | Continue loop, retry operation | src/lib.rs328 |
Other errors | Propagate immediately | src/lib.rs329 |
flowchart TD subgraph subGraph0["read_until Error Handling"] FILL_BUF["fill_buf()"] MATCH_RESULT["Match Result"] WOULD_BLOCK_CASE["WouldBlock"] OTHER_ERROR["Other Error"] CONTINUE_LOOP["continue"] RETURN_ERROR["return Err(e)"] end FILL_BUF --> MATCH_RESULT MATCH_RESULT --> OTHER_ERROR MATCH_RESULT --> WOULD_BLOCK_CASE OTHER_ERROR --> RETURN_ERROR WOULD_BLOCK_CASE --> CONTINUE_LOOP
Sources: src/lib.rs(L325 - L329)
Integration with axerrno
The axio
error system serves as a thin facade over the axerrno
crate, providing domain-specific error handling while maintaining the underlying error infrastructure.
Re-export Pattern
The error module uses a simple re-export pattern to maintain API consistency while delegating actual error functionality to axerrno
.
flowchart TD subgraph subGraph2["axerrno Crate Features"] ERROR_CODES["Error Code Definitions"] ERROR_CONTEXT["Error Context Support"] MACRO_SUPPORT["ax_err! Macro"] end subgraph subGraph1["src/error.rs Implementation"] AXERROR_IMPORT["axerrno::AxError as Error"] AXRESULT_IMPORT["axerrno::AxResult as Result"] end subgraph subGraph0["axio Public API"] PUBLIC_ERROR["pub use Error"] PUBLIC_RESULT["pub use Result"] end AXERROR_IMPORT --> ERROR_CODES AXRESULT_IMPORT --> ERROR_CODES MACRO_SUPPORT --> ERROR_CONTEXT PUBLIC_ERROR --> AXERROR_IMPORT PUBLIC_RESULT --> AXRESULT_IMPORT
Sources: src/error.rs(L1 - L2) src/lib.rs(L24)
Error Handling Best Practices
The axio
codebase demonstrates several consistent patterns for error handling in no_std
I/O operations:
- Immediate Error Propagation: Use the
?
operator for most error conditions - Contextual Error Creation: Use
ax_err!
macro with descriptive messages - Special Case Handling: Handle
WouldBlock
errors differently from fatal errors - Memory Safety: Wrap allocation errors with appropriate context
- Data Validation: Validate UTF-8 and format correctness with
InvalidData
errors
These patterns ensure consistent error behavior across all I/O traits while maintaining the lightweight design required for no_std
environments.
Sources: src/lib.rs(L24) src/lib.rs(L89) src/lib.rs(L183) src/lib.rs(L203) src/lib.rs(L244) src/lib.rs(L328) src/lib.rs(L366) src/error.rs(L1 - L3)
Prelude Module
Relevant source files
The Prelude Module provides convenient glob imports for the core I/O traits in the axio crate. This module serves as a single import point for the four fundamental I/O traits (Read
, Write
, Seek
, and BufRead
), following the same pattern as Rust's standard library std::io::prelude
module.
For information about the individual I/O traits themselves, see Core I/O Traits. For details about error handling that works with these traits, see Error Handling.
Purpose and Design
The prelude module eliminates the need for multiple individual trait imports by providing a single glob import that brings all essential I/O traits into scope. This design pattern reduces boilerplate code in modules that perform extensive I/O operations.
Module Structure
flowchart TD subgraph subGraph3["Client Code"] CLIENT["use axio::prelude::*"] USAGE["All traits available"] end subgraph subGraph2["axio Crate Root"] LIB["src/lib.rs"] subgraph subGraph1["Prelude Module"] PRELUDE_MOD["src/prelude.rs"] REEXPORT["pub use super::{BufRead, Read, Seek, Write}"] end subgraph subGraph0["Core Traits Definition"] READ["Read trait"] WRITE["Write trait"] SEEK["Seek trait"] BUFREAD["BufRead trait"] end end BUFREAD --> REEXPORT CLIENT --> USAGE LIB --> BUFREAD LIB --> PRELUDE_MOD LIB --> READ LIB --> SEEK LIB --> WRITE PRELUDE_MOD --> CLIENT READ --> REEXPORT SEEK --> REEXPORT WRITE --> REEXPORT
Sources: src/lib.rs(L16) src/prelude.rs(L11)
Trait Re-export Implementation
The prelude module uses a simple re-export mechanism to make the core traits available through a single import path. The implementation consists of a single pub use
statement that imports all four traits from the parent module:
Trait | Purpose | Key Methods |
---|---|---|
Read | Reading bytes from a source | read(),read_exact(),read_to_end() |
Write | Writing bytes to a destination | write(),flush(),write_all() |
Seek | Positioning within a stream | seek(),rewind(),stream_position() |
BufRead | Buffered reading operations | fill_buf(),consume(),read_until() |
Sources: src/prelude.rs(L11) src/lib.rs(L152 - L355)
Usage Patterns
Standard Import Pattern
The prelude follows the conventional Rust pattern for convenience imports:
use axio::prelude::*;
This single import brings all four core I/O traits into scope, enabling their methods to be called on any type that implements them.
Integration with no_std Environment
The prelude module maintains compatibility with no_std
environments while providing conditional access to allocation-dependent features:
flowchart TD subgraph subGraph3["Feature Compilation"] NO_ALLOC["default features"] WITH_ALLOC["alloc feature enabled"] end subgraph subGraph2["Import Resolution"] GLOB["use axio::prelude::*"] subgraph subGraph1["Method Availability"] CORE_METHODS["Core methods always available"] ALLOC_METHODS["Allocation methods (feature-gated)"] end subgraph subGraph0["Available Traits"] READ_TRAIT["Read trait"] WRITE_TRAIT["Write trait"] SEEK_TRAIT["Seek trait"] BUFREAD_TRAIT["BufRead trait"] end end BUFREAD_TRAIT --> ALLOC_METHODS BUFREAD_TRAIT --> CORE_METHODS GLOB --> BUFREAD_TRAIT GLOB --> READ_TRAIT GLOB --> SEEK_TRAIT GLOB --> WRITE_TRAIT NO_ALLOC --> CORE_METHODS READ_TRAIT --> ALLOC_METHODS READ_TRAIT --> CORE_METHODS SEEK_TRAIT --> CORE_METHODS WITH_ALLOC --> ALLOC_METHODS WITH_ALLOC --> CORE_METHODS WRITE_TRAIT --> CORE_METHODS
Sources: src/prelude.rs(L1 - L12) src/lib.rs(L7 - L8) src/lib.rs(L21 - L22)
Implementation Details
Module Declaration
The prelude module is declared as a public module in the crate root, making it accessible to external users:
src/lib.rs(L16) - pub mod prelude;
Re-export Mechanism
The module uses Rust's pub use
syntax to re-export traits from the parent module scope:
src/prelude.rs(L11) - pub use super::{BufRead, Read, Seek, Write};
This creates public aliases for the traits defined in the parent module, allowing them to be imported through the prelude path.
Documentation Integration
The prelude module includes comprehensive documentation that explains its purpose and provides usage examples, following the same documentation style as std::io::prelude
:
src/prelude.rs(L1 - L9) - Contains module-level documentation with purpose explanation and usage example.
Relationship to Standard Library
The axio prelude module mirrors the design and purpose of Rust's standard library std::io::prelude
module, providing a familiar interface for developers transitioning between std
and no_std
environments:
flowchart TD subgraph subGraph2["Design Pattern"] PATTERN["Convenience re-exports"] GLOB_IMPORT["Glob import usage"] end subgraph axio["axio"] AXIO_PRELUDE["axio::prelude"] AXIO_TRAITS["axio I/O traits"] end subgraph std::io["std::io"] STD_PRELUDE["std::io::prelude"] STD_TRAITS["std I/O traits"] end AXIO_PRELUDE --> PATTERN AXIO_TRAITS --> AXIO_PRELUDE PATTERN --> GLOB_IMPORT STD_PRELUDE --> PATTERN STD_TRAITS --> STD_PRELUDE
Sources: src/prelude.rs(L8) src/prelude.rs(L11)
This design maintains API compatibility and familiar usage patterns while providing the no_std
compatibility that axio offers.
Development and Maintenance
Relevant source files
This document provides guidance for contributors and maintainers of the axio
crate, covering development workflows, build processes, and quality assurance practices. It outlines the automated systems that ensure code quality and compatibility across multiple target environments.
For detailed information about the CI pipeline and multi-target builds, see Build System and CI. For development environment setup and configuration files, see Project Configuration.
Development Workflow Overview
The axio
crate follows a rigorous development process designed to maintain compatibility across diverse no_std
environments. The development workflow emphasizes automated quality checks, multi-target validation, and comprehensive documentation.
Multi-Target Development Strategy
The crate targets multiple architectures and environments simultaneously, requiring careful consideration of platform-specific constraints:
flowchart TD DEV["Developer Changes"] PR["Pull Request"] PUSH["Direct Push"] CI["CI Pipeline"] SETUP["Toolchain Setup"] MATRIX["Target Matrix Execution"] LINUX["x86_64-unknown-linux-gnu(Linux with std)"] BARE_X86["x86_64-unknown-none(Bare metal x86_64)"] RISCV["riscv64gc-unknown-none-elf(RISC-V bare metal)"] ARM["aarch64-unknown-none-softfloat(ARM64 bare metal)"] CHECKS["Quality Checks"] FORMAT["cargo fmt --check"] CLIPPY["cargo clippy"] BUILD["cargo build"] TEST["cargo test"] DOC_JOB["Documentation Job"] DOC_BUILD["cargo doc"] DEPLOY["GitHub Pages Deploy"] ARM --> CHECKS BARE_X86 --> CHECKS CHECKS --> BUILD CHECKS --> CLIPPY CHECKS --> FORMAT CHECKS --> TEST CI --> DOC_JOB CI --> SETUP DEV --> PR DOC_BUILD --> DEPLOY DOC_JOB --> DOC_BUILD LINUX --> CHECKS MATRIX --> ARM MATRIX --> BARE_X86 MATRIX --> LINUX MATRIX --> RISCV PR --> CI PUSH --> CI RISCV --> CHECKS SETUP --> MATRIX TEST --> LINUX
Sources: .github/workflows/ci.yml(L1 - L56)
Quality Assurance Pipeline
The development process enforces multiple layers of quality assurance through automated checks:
Check Type | Tool | Purpose | Scope |
---|---|---|---|
Code Formatting | cargo fmt --check | Ensures consistent code style | All targets |
Linting | cargo clippy --all-features | Catches common mistakes and improvements | All targets |
Compilation | cargo build --all-features | Verifies code compiles successfully | All targets |
Unit Testing | cargo test | Validates functionality | Linux target only |
Documentation | cargo doc --no-deps --all-features | Ensures documentation builds correctly | All features |
The CI configuration uses specific flags to enforce documentation quality through RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
.github/workflows/ci.yml(L40) ensuring all public APIs are properly documented.
Toolchain Requirements
The project requires the nightly Rust toolchain with specific components and targets:
flowchart TD NIGHTLY["nightly toolchain"] COMPONENTS["Required Components"] RUST_SRC["rust-src(for no_std targets)"] CLIPPY["clippy(for linting)"] RUSTFMT["rustfmt(for formatting)"] TARGETS["Target Platforms"] STD_TARGET["x86_64-unknown-linux-gnu"] NOSTD_TARGETS["no_std targets"] X86_BARE["x86_64-unknown-none"] RISCV_BARE["riscv64gc-unknown-none-elf"] ARM_BARE["aarch64-unknown-none-softfloat"] COMPONENTS --> CLIPPY COMPONENTS --> RUSTFMT COMPONENTS --> RUST_SRC NIGHTLY --> COMPONENTS NIGHTLY --> TARGETS NOSTD_TARGETS --> ARM_BARE NOSTD_TARGETS --> RISCV_BARE NOSTD_TARGETS --> X86_BARE TARGETS --> NOSTD_TARGETS TARGETS --> STD_TARGET
Sources: .github/workflows/ci.yml(L15 - L19)
Documentation Generation and Deployment
The crate maintains automatically generated documentation deployed to GitHub Pages. The documentation build process includes:
- Strict Documentation Standards: The build fails on missing documentation or broken internal links
- Feature-Complete Documentation: Built with
--all-features
to include all available functionality - Automatic Deployment: Documentation is automatically deployed from the default branch
- Custom Index: Generates a redirect index page pointing to the main crate documentation
The documentation deployment uses a single-commit strategy to the gh-pages
branch, ensuring a clean deployment history .github/workflows/ci.yml(L53 - L55)
Development Environment Setup
Repository Structure
The development environment excludes certain files and directories from version control:
flowchart TD REPO["Repository Root"] TRACKED["Tracked Files"] IGNORED["Ignored Files"] SRC["src/ directory"] CARGO["Cargo.toml"] README["README.md"] CI[".github/workflows/"] TARGET["target/(build artifacts)"] VSCODE[".vscode/(editor config)"] DSSTORE[".DS_Store(macOS metadata)"] LOCK["Cargo.lock(dependency versions)"] IGNORED --> DSSTORE IGNORED --> LOCK IGNORED --> TARGET IGNORED --> VSCODE REPO --> IGNORED REPO --> TRACKED TRACKED --> CARGO TRACKED --> CI TRACKED --> README TRACKED --> SRC
Sources: .gitignore(L1 - L5)
Build Artifact Management
The target/
directory is excluded from version control as it contains build artifacts that are regenerated during compilation. The Cargo.lock
file is also ignored, following Rust library conventions where lock files are typically not committed for libraries to allow downstream consumers flexibility in dependency resolution.
Continuous Integration Architecture
The CI system uses a matrix strategy to validate the crate across multiple target environments simultaneously:
flowchart TD TRIGGER["CI Triggers"] EVENTS["Event Types"] PUSH["push events"] PR["pull_request events"] STRATEGY["Matrix Strategy"] FAIL_FAST["fail-fast: false"] RUST_VER["rust-toolchain: [nightly]"] TARGET_MATRIX["Target Matrix"] T1["x86_64-unknown-linux-gnu"] T2["x86_64-unknown-none"] T3["riscv64gc-unknown-none-elf"] T4["aarch64-unknown-none-softfloat"] PARALLEL["Parallel Execution"] JOB1["ci job"] JOB2["doc job"] VALIDATION["Code Validation"] DOC_GEN["Documentation Generation"] EVENTS --> PR EVENTS --> PUSH JOB1 --> VALIDATION JOB2 --> DOC_GEN PARALLEL --> JOB1 PARALLEL --> JOB2 STRATEGY --> FAIL_FAST STRATEGY --> PARALLEL STRATEGY --> RUST_VER STRATEGY --> TARGET_MATRIX TARGET_MATRIX --> T1 TARGET_MATRIX --> T2 TARGET_MATRIX --> T3 TARGET_MATRIX --> T4 TRIGGER --> EVENTS TRIGGER --> STRATEGY
Sources: .github/workflows/ci.yml(L5 - L12) .github/workflows/ci.yml(L32 - L36)
The fail-fast: false
configuration ensures that failures in one target don't prevent testing of other targets, providing comprehensive feedback about platform-specific issues .github/workflows/ci.yml(L9)
Contributing Guidelines
Code Style and Standards
All contributions must pass the automated quality checks:
- Formatting: Code must be formatted using
cargo fmt
with default settings - Linting: All
clippy
warnings must be addressed, with the exception ofclippy::new_without_default
which is explicitly allowed .github/workflows/ci.yml(L25) - Documentation: All public APIs must be documented to pass the strict documentation checks
- Testing: New functionality should include appropriate unit tests
Testing Strategy
The testing approach recognizes the constraints of different target environments:
- Unit Tests: Run only on
x86_64-unknown-linux-gnu
target due to standard library requirements .github/workflows/ci.yml(L29 - L30) - Compilation Tests: All targets must compile successfully to ensure
no_std
compatibility - Feature Testing: All tests run with
--all-features
to validate optional functionality
This testing strategy ensures that while functionality is validated thoroughly on one platform, compilation compatibility is verified across all supported targets.
Sources: .github/workflows/ci.yml(L24 - L30)
Build System and CI
Relevant source files
This document covers the build system configuration and continuous integration pipeline for the axio crate. It explains how the project is structured for compilation across multiple target platforms, the automated testing strategy, and the documentation deployment process. For information about the crate's feature configuration and dependencies, see Crate Configuration and Features.
Build Configuration
The axio crate uses a minimal build configuration designed for no_std
compatibility across diverse target platforms. The build system is defined primarily through Cargo.toml
and supports conditional compilation based on feature flags.
Package Metadata and Dependencies
The crate is configured as a library package with specific metadata targeting the embedded and OS kernel development ecosystem:
Configuration | Value |
---|---|
Package Name | axio |
Version | 0.1.1 |
Edition | 2021 |
License | Dual/Triple licensed (GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0) |
The dependency structure is intentionally minimal, with only one required dependency:
axerrno = "0.1"
- Provides error handling types compatible withno_std
environments
Feature Gate Configuration
The build system supports feature-based conditional compilation through two defined features:
flowchart TD subgraph subGraph2["Available APIs"] CORE_API["Core I/O Traitsread(), write(), seek()"] ALLOC_API["Dynamic Memory APIsread_to_end(), read_to_string()"] end subgraph subGraph1["Compilation Modes"] MINIMAL["Minimal Buildno_std only"] ENHANCED["Enhanced Buildno_std + alloc"] end subgraph subGraph0["Feature Configuration"] DEFAULT["default = []"] ALLOC["alloc = []"] end ALLOC --> ENHANCED DEFAULT --> MINIMAL ENHANCED --> ALLOC_API ENHANCED --> CORE_API MINIMAL --> CORE_API
Sources: Cargo.toml(L14 - L16)
CI Pipeline Architecture
The continuous integration system uses GitHub Actions to validate code quality, build compatibility, and documentation generation across multiple target platforms. The pipeline is defined in a single workflow file that orchestrates multiple jobs.
Workflow Trigger Configuration
The CI pipeline activates on two primary events:
- Push events to any branch
- Pull request events
Multi-Target Build Matrix
The CI system employs a matrix strategy to test compilation across diverse target platforms:
flowchart TD subgraph subGraph2["CI Steps"] CHECKOUT["actions/checkout@v4"] TOOLCHAIN_SETUP["dtolnay/rust-toolchain@nightly"] FORMAT_CHECK["cargo fmt --check"] CLIPPY_CHECK["cargo clippy"] BUILD_STEP["cargo build"] TEST_STEP["cargo test"] end subgraph subGraph1["Build Matrix"] TOOLCHAIN["nightly toolchain"] TARGET1["x86_64-unknown-linux-gnu"] TARGET2["x86_64-unknown-none"] TARGET3["riscv64gc-unknown-none-elf"] TARGET4["aarch64-unknown-none-softfloat"] end subgraph subGraph0["CI Workflow"] TRIGGER["push/pull_request events"] CI_JOB["ci job"] DOC_JOB["doc job"] end BUILD_STEP --> TEST_STEP CHECKOUT --> TOOLCHAIN_SETUP CI_JOB --> CHECKOUT CI_JOB --> TOOLCHAIN CLIPPY_CHECK --> BUILD_STEP FORMAT_CHECK --> CLIPPY_CHECK TOOLCHAIN --> TARGET1 TOOLCHAIN --> TARGET2 TOOLCHAIN --> TARGET3 TOOLCHAIN --> TARGET4 TOOLCHAIN_SETUP --> FORMAT_CHECK TRIGGER --> CI_JOB TRIGGER --> DOC_JOB
Sources: .github/workflows/ci.yml(L1 - L31)
Target Platform Categories
The build matrix validates compatibility across three categories of target platforms:
Target | Architecture | Environment | Purpose |
---|---|---|---|
x86_64-unknown-linux-gnu | x86_64 | Linux with std | Testing and validation |
x86_64-unknown-none | x86_64 | Bare metal | OS kernel development |
riscv64gc-unknown-none-elf | RISC-V | Bare metal | Embedded systems |
aarch64-unknown-none-softfloat | ARM64 | Bare metal | ARM-based embedded |
Sources: .github/workflows/ci.yml(L12)
Quality Assurance Pipeline
The CI system implements a comprehensive quality assurance strategy that validates code formatting, linting, compilation, and functional correctness.
Code Quality Checks
The quality assurance pipeline executes the following checks in sequence:
flowchart TD START["CI Job Start"] VERSION_CHECK["rustc --version --verbose"] FORMAT["cargo fmt --all -- --check"] CLIPPY["cargo clippy --target TARGET --all-features"] BUILD["cargo build --target TARGET --all-features"] TEST_CONDITION["target == x86_64-unknown-linux-gnu?"] UNIT_TEST["cargo test --target TARGET -- --nocapture"] END["Job Complete"] CLIPPY_CONFIG["clippy config:-A clippy::new_without_default"] BUILD --> TEST_CONDITION CLIPPY --> BUILD CLIPPY --> CLIPPY_CONFIG FORMAT --> CLIPPY START --> VERSION_CHECK TEST_CONDITION --> END TEST_CONDITION --> UNIT_TEST UNIT_TEST --> END VERSION_CHECK --> FORMAT
Sources: .github/workflows/ci.yml(L20 - L30)
Testing Strategy
Unit tests are executed only on the x86_64-unknown-linux-gnu
target, which provides a standard library environment suitable for test execution. The testing configuration includes:
- Test Command:
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
- Output Mode: No capture mode for detailed test output
- Conditional Execution: Only runs on Linux GNU target to avoid
no_std
test environment complications
Sources: .github/workflows/ci.yml(L28 - L30)
Documentation Generation and Deployment
The CI pipeline includes a separate job dedicated to documentation generation and automated deployment to GitHub Pages.
Documentation Build Process
flowchart TD subgraph subGraph2["Deploy Configuration"] BRANCH["gh-pages branch"] FOLDER["target/doc"] COMMIT_MODE["single-commit: true"] end subgraph subGraph1["Build Configuration"] RUSTDOCFLAGS["-D rustdoc::broken_intra_doc_links-D missing-docs"] PERMISSIONS["contents: write"] end subgraph subGraph0["Documentation Job"] DOC_TRIGGER["push/PR events"] DOC_CHECKOUT["actions/checkout@v4"] DOC_TOOLCHAIN["dtolnay/rust-toolchain@nightly"] DOC_BUILD["cargo doc --no-deps --all-features"] INDEX_GEN["Generate index.html redirect"] DEPLOY_CHECK["ref == default-branch?"] DEPLOY["JamesIves/github-pages-deploy-action@v4"] end DEPLOY --> BRANCH DEPLOY --> COMMIT_MODE DEPLOY --> FOLDER DEPLOY_CHECK --> DEPLOY DOC_BUILD --> INDEX_GEN DOC_CHECKOUT --> DOC_TOOLCHAIN DOC_TOOLCHAIN --> DOC_BUILD DOC_TRIGGER --> DOC_CHECKOUT INDEX_GEN --> DEPLOY_CHECK PERMISSIONS --> DEPLOY RUSTDOCFLAGS --> DOC_BUILD
Documentation Quality Enforcement
The documentation build process enforces strict quality standards through RUSTDOCFLAGS
:
- Broken Link Detection:
-D rustdoc::broken_intra_doc_links
treats broken documentation links as errors - Missing Documentation:
-D missing-docs
requires documentation for all public APIs
The index generation creates a redirect page using the crate name extracted from cargo tree
output, providing seamless navigation to the main documentation.
Sources: .github/workflows/ci.yml(L32 - L55)
Deployment Strategy
Documentation deployment follows a conditional strategy:
- Automatic Deployment: Occurs only on pushes to the default branch
- Target Branch:
gh-pages
branch for GitHub Pages hosting - Deployment Mode: Single commit to maintain clean history
- Content Source:
target/doc
directory containing generated documentation
Sources: .github/workflows/ci.yml(L49 - L55)
Project Configuration
Relevant source files
This document covers the development environment setup and project configuration files for the axio crate. It explains the structure and purpose of configuration files that control compilation, dependency management, and version control behavior.
For information about the build system and continuous integration, see Build System and CI. For details about feature gates and their impact on compilation, see Crate Configuration and Features.
Configuration File Structure
The axio project uses standard Rust project configuration with minimal additional setup requirements. The configuration is designed to support both standalone development and integration into larger systems like ArceOS.
Project Configuration Overview
flowchart TD subgraph subGraph4["Ignored Files"] TARGET["/target"] VSCODE["/.vscode"] DSSTORE[".DS_Store"] LOCK["Cargo.lock"] end subgraph subGraph3["External Links"] REPO["repository URL"] DOCS["documentation URL"] HOME["homepage URL"] end subgraph subGraph2["Build Configuration"] FEATURES["Features Section"] DEPS["Dependencies Section"] DEFAULT["default = []"] ALLOC["alloc = []"] end subgraph subGraph1["Package Metadata"] NAME["name = axio"] VERSION["version = 0.1.1"] EDITION["edition = 2021"] DESC["description"] LICENSE["Triple License"] end subgraph subGraph0["Project Root"] CARGO["Cargo.tomlMain Configuration"] GIT[".gitignoreVersion Control"] end CARGO --> DEPS CARGO --> FEATURES CARGO --> NAME CARGO --> REPO CARGO --> VERSION FEATURES --> ALLOC FEATURES --> DEFAULT GIT --> DSSTORE GIT --> LOCK GIT --> TARGET GIT --> VSCODE
Sources: Cargo.toml(L1 - L20) .gitignore(L1 - L5)
Cargo.toml Configuration
The main project configuration is contained in Cargo.toml
, which defines package metadata, dependencies, and feature flags.
Package Metadata
The package section defines essential project information and publishing configuration:
Field | Value | Purpose |
---|---|---|
name | "axio" | Crate name for cargo registry |
version | "0.1.1" | Semantic version number |
edition | "2021" | Rust edition for language features |
authors | ["Yuekai Jia equation618@gmail.com"] | Primary maintainer |
description | "std::io-like I/O traits forno_stdenvironment" | Brief functionality summary |
license | "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0" | Triple licensing scheme |
Sources: Cargo.toml(L1 - L12)
Repository and Documentation Links
The project maintains links to external resources:
- Repository:
https://github.com/arceos-org/axio
- Source code location - Homepage:
https://github.com/arceos-org/arceos
- Parent project - Documentation:
https://docs.rs/axio
- Published API documentation
These links connect the crate to the broader ArceOS ecosystem while maintaining independent documentation.
Sources: Cargo.toml(L8 - L10)
Feature Configuration
flowchart TD subgraph subGraph2["Available Functionality"] BASIC["Basic I/O TraitsRead, Write, Seek"] DYNAMIC["Dynamic OperationsVec-based methods"] end subgraph subGraph1["Compilation Modes"] MINIMAL["Minimal BuildNo Features"] ENHANCED["Enhanced Build--features alloc"] end subgraph subGraph0["Feature Definition"] DEFAULT["default = []"] ALLOC["alloc = []"] end ALLOC --> ENHANCED DEFAULT --> MINIMAL ENHANCED --> BASIC ENHANCED --> DYNAMIC MINIMAL --> BASIC
Sources: Cargo.toml(L14 - L16)
The feature configuration uses an intentionally minimal approach:
default = []
: No features enabled by default, ensuring maximum compatibilityalloc = []
: Empty feature flag that enablesalloc
crate integration through conditional compilation
This design allows the crate to function in the most constrained environments while providing enhanced functionality when allocation is available.
Dependencies
The dependency configuration maintains minimal external requirements:
flowchart TD subgraph subGraph2["Functionality Provided"] ERROR_TYPES["Error TypesAxError, AxResult"] COLLECTIONS["CollectionsVec, String"] end subgraph subGraph1["Conditional Dependencies"] ALLOC_CRATE["alloc crateFeature-gated"] end subgraph subGraph0["Direct Dependencies"] AXERRNO["axerrno = 0.1Error Handling"] end ALLOC_CRATE --> COLLECTIONS AXERRNO --> ERROR_TYPES
Sources: Cargo.toml(L18 - L19)
The single required dependency axerrno = "0.1"
provides standardized error types compatible with the ArceOS ecosystem. The alloc
crate dependency is implicit and controlled through feature gates rather than explicit declaration.
Git Configuration
The .gitignore
file excludes development artifacts and environment-specific files from version control.
Ignored File Categories
Pattern | Type | Reason |
---|---|---|
/target | Build artifacts | Generated by cargo build |
/.vscode | Editor configuration | VS Code specific settings |
.DS_Store | System files | macOS filesystem metadata |
Cargo.lock | Dependency lock | Not committed for libraries |
Sources: .gitignore(L1 - L4)
Version Control Strategy
flowchart TD subgraph subGraph2["Git Repository"] REPO["Version Control"] end subgraph subGraph1["Ignored Files"] TARGET["Build Output/target"] EDITOR["Editor Config/.vscode"] SYSTEM["System Files.DS_Store"] LOCK["Lock FileCargo.lock"] end subgraph subGraph0["Tracked Files"] SOURCE["Source Codesrc/*.rs"] CONFIG["ConfigurationCargo.toml"] DOCS["DocumentationREADME.md"] CI["CI Configuration.github/"] end CI --> REPO CONFIG --> REPO DOCS --> REPO EDITOR --> REPO LOCK --> REPO SOURCE --> REPO SYSTEM --> REPO TARGET --> REPO
Sources: .gitignore(L1 - L4)
The exclusion of Cargo.lock
follows Rust library conventions, allowing downstream consumers to resolve their own dependency versions. Build artifacts in /target
are excluded to avoid repository bloat, while editor-specific configurations are ignored to support diverse development environments.
Development Environment Setup
The minimal configuration requirements enable straightforward development setup:
- Rust Toolchain: Edition 2021 or later required
- Feature Testing: Use
cargo build --features alloc
to test enhanced functionality - Editor Support: Any Rust-compatible editor; VS Code configurations are gitignored
- Target Testing: Use
cargo build --target <target>
for cross-compilation verification
The configuration supports both local development and integration into larger build systems without requiring environment-specific modifications.
Sources: Cargo.toml(L1 - L20) .gitignore(L1 - L5)
Overview
Relevant source files
This document provides comprehensive documentation for the riscv_goldfish
repository, a specialized Real Time Clock (RTC) driver crate designed for RISC-V systems running on the Goldfish platform. The repository implements a no_std
compatible driver that provides Unix timestamp functionality through memory-mapped I/O operations.
The riscv_goldfish
crate serves as a hardware abstraction layer between operating system components and Goldfish RTC hardware, enabling time management capabilities in embedded and bare-metal RISC-V environments. This driver is specifically designed for integration with the ArceOS operating system ecosystem but maintains compatibility across multiple target architectures.
For detailed API documentation and usage examples, see API Reference. For information about cross-platform compilation and target support, see Target Platforms and Cross-Compilation.
Repository Purpose and Scope
The riscv_goldfish
crate provides a minimal, efficient RTC driver implementation with the following core responsibilities:
Component | Purpose | Code Entity |
---|---|---|
RTC Driver Core | Hardware abstraction and timestamp management | Rtcstruct |
Memory Interface | Direct hardware register access | MMIO operations |
Time Conversion | Unix timestamp to nanosecond conversion | get_unix_timestamp(),set_unix_timestamp() |
Platform Integration | Device tree compatibility | Base address configuration |
The driver operates in a no_std
environment, making it suitable for embedded systems, kernel-level components, and bare-metal applications across multiple architectures including RISC-V, x86_64, and ARM64.
Sources: Cargo.toml(L1 - L15) README.md(L1 - L33)
System Architecture
System Integration Architecture
flowchart TD subgraph subGraph3["Hardware Layer"] GOLDFISH_RTC["Goldfish RTC Hardware"] RTC_REGS["RTC Registers"] end subgraph subGraph2["Hardware Abstraction"] MMIO["Memory-Mapped I/O"] DEVICE_TREE["Device Tree Configuration"] BASE_ADDR["base_addr: 0x101000"] end subgraph subGraph1["Driver Layer"] RTC_CRATE["riscv_goldfish crate"] RTC_STRUCT["Rtc struct"] API_NEW["Rtc::new(base_addr)"] API_GET["get_unix_timestamp()"] API_SET["set_unix_timestamp()"] end subgraph subGraph0["Application Layer"] APP["User Applications"] ARCEOS["ArceOS Operating System"] end API_GET --> MMIO API_NEW --> MMIO API_SET --> MMIO APP --> ARCEOS ARCEOS --> RTC_CRATE BASE_ADDR --> API_NEW DEVICE_TREE --> BASE_ADDR MMIO --> RTC_REGS RTC_CRATE --> RTC_STRUCT RTC_REGS --> GOLDFISH_RTC RTC_STRUCT --> API_GET RTC_STRUCT --> API_NEW RTC_STRUCT --> API_SET
This architecture demonstrates the layered approach from applications down to hardware, with the Rtc
struct serving as the primary interface point. The base_addr
parameter from device tree configuration initializes the driver through the Rtc::new()
constructor.
Sources: README.md(L10 - L12) README.md(L24 - L28) Cargo.toml(L6)
Core Components and Data Flow
RTC Driver Component Mapping
flowchart TD subgraph subGraph4["Data Types"] UNIX_TIME["Unix timestamp (u64)"] NANOSECONDS["Nanoseconds (u64)"] NSEC_PER_SEC["NSEC_PER_SEC constant"] end subgraph subGraph3["Hardware Interface"] MMIO_OPS["MMIO Operations"] RTC_TIME_LOW["RTC_TIME_LOW register"] RTC_TIME_HIGH["RTC_TIME_HIGH register"] end subgraph subGraph2["Public API Methods"] NEW_METHOD["new(base_addr: usize)"] GET_TIMESTAMP["get_unix_timestamp()"] SET_TIMESTAMP["set_unix_timestamp()"] end subgraph subGraph1["Core Structures"] RTC_STRUCT["Rtc struct"] BASE_ADDR_FIELD["base_addr field"] end subgraph subGraph0["Source Files"] LIB_RS["src/lib.rs"] CARGO_TOML["Cargo.toml"] end CARGO_TOML --> LIB_RS GET_TIMESTAMP --> MMIO_OPS GET_TIMESTAMP --> UNIX_TIME LIB_RS --> RTC_STRUCT MMIO_OPS --> RTC_TIME_HIGH MMIO_OPS --> RTC_TIME_LOW NANOSECONDS --> NSEC_PER_SEC NEW_METHOD --> BASE_ADDR_FIELD RTC_STRUCT --> BASE_ADDR_FIELD RTC_STRUCT --> GET_TIMESTAMP RTC_STRUCT --> NEW_METHOD RTC_STRUCT --> SET_TIMESTAMP SET_TIMESTAMP --> MMIO_OPS SET_TIMESTAMP --> UNIX_TIME UNIX_TIME --> NANOSECONDS
The driver implements a clean separation between the public API surface and hardware-specific operations. The Rtc
struct encapsulates the base address and provides methods that handle the conversion between Unix timestamps and hardware nanosecond representations through direct register manipulation.
Sources: README.md(L10 - L12) Cargo.toml(L2)
Target Platform Support
The crate supports multiple target architectures through its no_std
design:
Target Architecture | Purpose | Compatibility |
---|---|---|
riscv64gc-unknown-none-elf | Primary RISC-V target | Bare metal, embedded |
x86_64-unknown-linux-gnu | Development and testing | Linux userspace |
x86_64-unknown-none | Bare metal x86_64 | Kernel, bootloader |
aarch64-unknown-none-softfloat | ARM64 embedded | Bare metal ARM |
The driver maintains cross-platform compatibility while providing hardware-specific optimizations for the Goldfish RTC implementation. The no-std
category classification enables deployment in resource-constrained environments typical of embedded systems.
Sources: Cargo.toml(L12) Cargo.toml(L6)
Integration with ArceOS Ecosystem
The riscv_goldfish
crate is designed as a component within the broader ArceOS operating system project. The driver provides essential time management capabilities required by kernel-level services and system calls. The crate's licensing scheme supports integration with both open-source and commercial projects through its triple license approach (GPL-3.0, Apache-2.0, MulanPSL-2.0).
Device tree integration follows standard practices, with the driver expecting a compatible string of "google,goldfish-rtc"
and memory-mapped register access at the specified base address. This standardized approach ensures compatibility across different RISC-V platform implementations that include Goldfish RTC hardware.
Sources: Cargo.toml(L7 - L8) Cargo.toml(L11) README.md(L24 - L28)
Architecture Overview
Relevant source files
This document provides a high-level view of the riscv_goldfish
system architecture, showing how the RTC driver components integrate from the hardware layer up to application interfaces. It covers the overall system stack, component relationships, and data flow patterns without diving into implementation details.
For detailed API documentation, see API Reference. For hardware register specifics, see Hardware Interface. For time conversion implementation details, see Time Conversion.
System Stack Architecture
The riscv_goldfish
driver operates within a layered architecture that spans from hardware registers to application-level time services:
flowchart TD subgraph subGraph4["Hardware Platform"] GOLDFISH_HW["Goldfish RTC Hardware"] end subgraph subGraph3["Hardware Registers"] RTC_TIME_LOW_REG["RTC_TIME_LOW (0x00)"] RTC_TIME_HIGH_REG["RTC_TIME_HIGH (0x04)"] end subgraph subGraph2["Hardware Abstraction"] MMIO_READ["read()"] MMIO_WRITE["write()"] BASE_ADDR["base_address field"] end subgraph subGraph1["Driver Layer (src/lib.rs)"] RTC_STRUCT["Rtc struct"] API_NEW["new()"] API_GET["get_unix_timestamp()"] API_SET["set_unix_timestamp()"] end subgraph subGraph0["Application Layer"] APP["Application Code"] OS["ArceOS Operating System"] end API_GET --> MMIO_READ API_SET --> MMIO_WRITE APP --> OS MMIO_READ --> BASE_ADDR MMIO_READ --> RTC_TIME_HIGH_REG MMIO_READ --> RTC_TIME_LOW_REG MMIO_WRITE --> BASE_ADDR MMIO_WRITE --> RTC_TIME_HIGH_REG MMIO_WRITE --> RTC_TIME_LOW_REG OS --> RTC_STRUCT RTC_STRUCT --> API_GET RTC_STRUCT --> API_NEW RTC_STRUCT --> API_SET RTC_TIME_HIGH_REG --> GOLDFISH_HW RTC_TIME_LOW_REG --> GOLDFISH_HW
This architecture demonstrates the clear separation of concerns between application logic, driver abstraction, hardware interface, and physical hardware. The Rtc
struct serves as the primary abstraction boundary, providing a safe interface to unsafe hardware operations.
Sources: src/lib.rs(L11 - L14) src/lib.rs(L26 - L33) src/lib.rs(L35 - L49)
Component Architecture
The core driver architecture centers around the Rtc
struct and its associated methods:
flowchart TD subgraph subGraph3["Hardware Interface"] READ_VOLATILE["core::ptr::read_volatile"] WRITE_VOLATILE["core::ptr::write_volatile"] end subgraph Constants["Constants"] RTC_TIME_LOW_CONST["RTC_TIME_LOW = 0x00"] RTC_TIME_HIGH_CONST["RTC_TIME_HIGH = 0x04"] NSEC_PER_SEC_CONST["NSEC_PER_SEC = 1_000_000_000"] end subgraph subGraph1["Private Implementation"] READ_METHOD["read(reg: usize) -> u32"] WRITE_METHOD["write(reg: usize, value: u32)"] BASE_FIELD["base_address: usize"] end subgraph subGraph0["Public API (src/lib.rs)"] RTC["Rtc"] NEW["new(base_address: usize)"] GET_TS["get_unix_timestamp() -> u64"] SET_TS["set_unix_timestamp(unix_time: u64)"] end GET_TS --> NSEC_PER_SEC_CONST GET_TS --> READ_METHOD NEW --> BASE_FIELD READ_METHOD --> BASE_FIELD READ_METHOD --> READ_VOLATILE READ_METHOD --> RTC_TIME_HIGH_CONST READ_METHOD --> RTC_TIME_LOW_CONST SET_TS --> NSEC_PER_SEC_CONST SET_TS --> WRITE_METHOD WRITE_METHOD --> BASE_FIELD WRITE_METHOD --> RTC_TIME_HIGH_CONST WRITE_METHOD --> RTC_TIME_LOW_CONST WRITE_METHOD --> WRITE_VOLATILE
The component design follows a minimal surface area principle with only three public methods exposed. The private read
and write
methods encapsulate all unsafe hardware interactions, while constants define the register layout and time conversion factors.
Sources: src/lib.rs(L6 - L9) src/lib.rs(L12 - L14) src/lib.rs(L17 - L23) src/lib.rs(L27 - L49)
Data Flow Architecture
The driver implements bidirectional data flow between Unix timestamps and hardware nanosecond values:
flowchart TD subgraph subGraph2["Hardware Layer"] HW_REGISTERS["64-bit nanosecond counter"] end subgraph subGraph1["Write Path (set_unix_timestamp)"] WRITE_START["set_unix_timestamp(unix_time)"] CONVERT_TO_NS["unix_time * NSEC_PER_SEC"] SPLIT_HIGH["(time_nanos >> 32) as u32"] SPLIT_LOW["time_nanos as u32"] WRITE_HIGH["write(RTC_TIME_HIGH, high)"] WRITE_LOW["write(RTC_TIME_LOW, low)"] end subgraph subGraph0["Read Path (get_unix_timestamp)"] READ_START["get_unix_timestamp()"] READ_LOW["read(RTC_TIME_LOW) -> u32"] READ_HIGH["read(RTC_TIME_HIGH) -> u32"] COMBINE["(high << 32) | low"] CONVERT_TO_SEC["result / NSEC_PER_SEC"] READ_RESULT["return u64"] end COMBINE --> CONVERT_TO_SEC CONVERT_TO_NS --> SPLIT_HIGH CONVERT_TO_NS --> SPLIT_LOW CONVERT_TO_SEC --> READ_RESULT READ_HIGH --> COMBINE READ_HIGH --> HW_REGISTERS READ_LOW --> COMBINE READ_LOW --> HW_REGISTERS READ_START --> READ_HIGH READ_START --> READ_LOW SPLIT_HIGH --> WRITE_HIGH SPLIT_LOW --> WRITE_LOW WRITE_HIGH --> HW_REGISTERS WRITE_LOW --> HW_REGISTERS WRITE_START --> CONVERT_TO_NS
The data flow demonstrates the critical conversion between user-space Unix timestamps (seconds) and hardware nanosecond representation. The 64-bit hardware value must be split across two 32-bit registers for write operations and reconstructed for read operations.
Sources: src/lib.rs(L36 - L40) src/lib.rs(L43 - L49) src/lib.rs(L9)
Integration Architecture
The driver integrates within the broader ArceOS ecosystem and cross-platform build system:
flowchart TD subgraph subGraph3["Driver Core"] RTC_DRIVER["riscv_goldfish::Rtc"] end subgraph subGraph2["Platform Integration"] DEVICE_TREE["Device Tree (base_addr: 0x101000)"] ARCEOS_OS["ArceOS"] COMPATIBLE["google,goldfish-rtc"] end subgraph subGraph1["Crate Features"] NO_STD["#![no_std]"] CARGO_TOML["Cargo.toml metadata"] end subgraph subGraph0["Build Targets"] X86_64_LINUX["x86_64-unknown-linux-gnu"] X86_64_NONE["x86_64-unknown-none"] RISCV64["riscv64gc-unknown-none-elf"] AARCH64["aarch64-unknown-none-softfloat"] end ARCEOS_OS --> RTC_DRIVER CARGO_TOML --> NO_STD COMPATIBLE --> RTC_DRIVER DEVICE_TREE --> RTC_DRIVER NO_STD --> AARCH64 NO_STD --> RISCV64 NO_STD --> X86_64_LINUX NO_STD --> X86_64_NONE RTC_DRIVER --> AARCH64 RTC_DRIVER --> RISCV64 RTC_DRIVER --> X86_64_LINUX RTC_DRIVER --> X86_64_NONE
The integration architecture shows how the no_std
design enables cross-compilation to multiple targets while maintaining compatibility with the ArceOS operating system. Device tree integration provides the necessary base_address
configuration for hardware discovery.
Sources: src/lib.rs(L4) README.md(L15 - L32) README.md(L5) README.md(L10 - L13)
RTC Driver Implementation
Relevant source files
This document covers the core implementation details of the RISC-V Goldfish RTC driver, including the main Rtc
struct, its methods, and the underlying hardware abstraction mechanisms. The implementation provides a clean interface for reading and writing Unix timestamps to Goldfish RTC hardware through memory-mapped I/O operations.
For complete API documentation and usage examples, see API Reference. For detailed hardware register mapping and MMIO specifics, see Hardware Interface. For in-depth time conversion algorithms, see Time Conversion.
Core Driver Structure
The RTC driver is implemented as a single Rtc
struct that encapsulates all functionality for interfacing with the Goldfish RTC hardware. The driver follows a minimalist design pattern suitable for embedded and bare-metal environments.
flowchart TD subgraph subGraph2["Hardware Constants"] RtcTimeLow["RTC_TIME_LOW = 0x00"] RtcTimeHigh["RTC_TIME_HIGH = 0x04"] NsecPerSec["NSEC_PER_SEC = 1_000_000_000"] end subgraph subGraph1["Internal Methods"] ReadMethod["read(reg: usize) -> u32"] WriteMethod["write(reg: usize, value: u32)"] end subgraph subGraph0["Rtc Struct Implementation"] RtcStruct["Rtc { base_address: usize }"] NewMethod["new(base_address: usize) -> Self"] GetMethod["get_unix_timestamp() -> u64"] SetMethod["set_unix_timestamp(unix_time: u64)"] end GetMethod --> NsecPerSec GetMethod --> ReadMethod NewMethod --> RtcStruct ReadMethod --> RtcTimeHigh ReadMethod --> RtcTimeLow SetMethod --> NsecPerSec SetMethod --> WriteMethod WriteMethod --> RtcTimeHigh WriteMethod --> RtcTimeLow
Sources: src/lib.rs(L11 - L50)
Memory-Mapped I/O Layer
The driver implements a low-level hardware abstraction through unsafe memory operations. All hardware access is channeled through two core methods that handle volatile memory operations to prevent compiler optimizations from interfering with hardware communication.
Method | Purpose | Safety | Return Type |
---|---|---|---|
read(reg: usize) | Read 32-bit value from hardware register | Unsafe - requires valid base address | u32 |
write(reg: usize, value: u32) | Write 32-bit value to hardware register | Unsafe - requires valid base address | () |
flowchart TD subgraph subGraph3["Physical Hardware"] RtcHardware["Goldfish RTC Registers"] end subgraph subGraph2["Hardware Layer"] VolatileRead["core::ptr::read_volatile()"] VolatileWrite["core::ptr::write_volatile()"] BaseAddr["(base_address + reg) as *const/*mut u32"] end subgraph subGraph1["Abstraction Layer"] ReadReg["read(reg)"] WriteReg["write(reg, value)"] end subgraph subGraph0["Software Layer"] GetTimestamp["get_unix_timestamp()"] SetTimestamp["set_unix_timestamp()"] end BaseAddr --> RtcHardware GetTimestamp --> ReadReg ReadReg --> VolatileRead SetTimestamp --> WriteReg VolatileRead --> BaseAddr VolatileWrite --> BaseAddr WriteReg --> VolatileWrite
Sources: src/lib.rs(L17 - L24)
Driver Initialization and Configuration
The Rtc::new()
constructor provides the single entry point for driver instantiation. The method requires a base memory address that corresponds to the hardware device's memory-mapped location, typically obtained from device tree parsing during system initialization.
flowchart TD subgraph subGraph1["Usage Pattern"] CreateInstance["let rtc = Rtc::new(0x101000)"] GetTime["rtc.get_unix_timestamp()"] SetTime["rtc.set_unix_timestamp(epoch)"] end subgraph subGraph0["Initialization Flow"] DeviceTree["Device Treertc@101000"] BaseAddr["base_addr = 0x101000"] NewCall["Rtc::new(base_addr)"] RtcInstance["Rtc { base_address: 0x101000 }"] end BaseAddr --> NewCall CreateInstance --> GetTime CreateInstance --> SetTime DeviceTree --> BaseAddr NewCall --> RtcInstance RtcInstance --> CreateInstance
The constructor performs no hardware validation or initialization - it simply stores the provided base address for future memory operations. This design assumes that hardware initialization and memory mapping have been handled by the system's device management layer.
Sources: src/lib.rs(L27 - L33) README.md(L10 - L16)
64-bit Register Handling
The Goldfish RTC hardware stores time as a 64-bit nanosecond value split across two consecutive 32-bit registers. The driver handles this split through careful register sequencing and bit manipulation to maintain data integrity during multi-register operations.
flowchart TD subgraph subGraph1["64-bit Write Operation"] subgraph subGraph0["64-bit Read Operation"] InputUnix["unix_time: u64"] ConvertNanos["unix_time * NSEC_PER_SEC"] SplitHigh["(time_nanos >> 32) as u32"] SplitLow["time_nanos as u32"] WriteHigh["write(RTC_TIME_HIGH, high)"] WriteLow["write(RTC_TIME_LOW, low)"] ReadLow["read(RTC_TIME_LOW) -> u32"] CombineBits["(high << 32) | low"] ReadHigh["read(RTC_TIME_HIGH) -> u32"] ConvertTime["nanoseconds / NSEC_PER_SEC"] ReturnUnix["return unix_timestamp"] end end CombineBits --> ConvertTime ConvertNanos --> SplitHigh ConvertNanos --> SplitLow ConvertTime --> ReturnUnix InputUnix --> ConvertNanos ReadHigh --> CombineBits ReadLow --> CombineBits SplitHigh --> WriteHigh SplitLow --> WriteLow
The write operation follows a specific sequence where the high register is written first, followed by the low register. This ordering ensures atomic updates from the hardware perspective and prevents temporal inconsistencies during timestamp updates.
Sources: src/lib.rs(L36 - L49)
Error Handling and Safety Model
The driver implementation uses Rust's unsafe
blocks to perform direct memory access while maintaining memory safety through controlled access patterns. The safety model relies on several assumptions:
- The provided
base_address
points to valid, mapped memory - The memory region corresponds to actual Goldfish RTC hardware
- Concurrent access is managed by the calling code
- The hardware registers maintain their expected layout
flowchart TD subgraph subGraph1["Caller Responsibilities"] ValidAddr["Provide valid base_address"] MemoryMap["Ensure memory mapping"] Concurrency["Handle concurrent access"] end subgraph subGraph0["Safety Boundaries"] SafeAPI["Safe Public API"] UnsafeInternal["Unsafe Internal Methods"] HardwareLayer["Hardware Memory"] end Concurrency --> SafeAPI MemoryMap --> SafeAPI SafeAPI --> UnsafeInternal UnsafeInternal --> HardwareLayer ValidAddr --> SafeAPI
The driver does not perform runtime validation of memory addresses or hardware responses, prioritizing performance and simplicity for embedded use cases where system-level guarantees about hardware presence and memory mapping are typically established during boot.
Sources: src/lib.rs(L17 - L24)
API Reference
Relevant source files
This document provides comprehensive reference documentation for the public API of the riscv_goldfish
crate. The API consists of a single Rtc
struct that provides a safe, high-level interface to the Goldfish RTC hardware through memory-mapped I/O operations.
For hardware-specific implementation details and register layouts, see Hardware Interface. For time conversion algorithms and data flow, see Time Conversion.
Public API Overview
The riscv_goldfish
crate exposes a minimal but complete API through the Rtc
struct and its associated methods. The API follows standard Rust conventions for hardware driver interfaces.
API Structure Diagram
flowchart TD subgraph subGraph2["Hardware Constants"] RtcTimeLow["RTC_TIME_LOW: 0x00"] RtcTimeHigh["RTC_TIME_HIGH: 0x04"] NsecPerSec["NSEC_PER_SEC: 1_000_000_000"] end subgraph subGraph1["Private Implementation"] ReadMethod["read(reg: usize) -> u32"] WriteMethod["write(reg: usize, value: u32)"] BaseAddr["base_address: usize"] end subgraph subGraph0["Public API"] RtcStruct["Rtc struct"] NewMethod["new(base_address: usize) -> Self"] GetMethod["get_unix_timestamp() -> u64"] SetMethod["set_unix_timestamp(unix_time: u64)"] end GetMethod --> NsecPerSec GetMethod --> ReadMethod NewMethod --> RtcStruct ReadMethod --> RtcTimeHigh ReadMethod --> RtcTimeLow RtcStruct --> BaseAddr SetMethod --> NsecPerSec SetMethod --> WriteMethod WriteMethod --> RtcTimeHigh WriteMethod --> RtcTimeLow
Sources: src/lib.rs(L11 - L50)
Constructor
Rtc::new
#![allow(unused)] fn main() { pub fn new(base_address: usize) -> Self }
Creates a new Rtc
instance that interfaces with Goldfish RTC hardware at the specified memory address.
Parameters:
base_address
: The physical base address of the RTC device's memory-mapped registers, typically obtained from device tree configuration
Returns:
- A new
Rtc
instance ready for timestamp operations
Safety:
The constructor itself is safe, but subsequent operations assume the provided base_address
points to valid, accessible RTC hardware registers. Invalid addresses will cause undefined behavior during read/write operations.
Example:
use riscv_goldfish::Rtc;
// Address from device tree: rtc@101000
let rtc = Rtc::new(0x101000);
Sources: src/lib.rs(L27 - L33) README.md(L12) README.md(L24 - L29)
Timestamp Operations
get_unix_timestamp
#![allow(unused)] fn main() { pub fn get_unix_timestamp(&self) -> u64 }
Reads the current time from the RTC hardware and returns it as seconds since the Unix epoch (January 1, 1970, 00:00:00 UTC).
Returns:
- Current time as Unix timestamp in seconds
Implementation Details:
The method performs two 32-bit register reads to construct a 64-bit nanosecond value, then converts to seconds by dividing by NSEC_PER_SEC
.
flowchart TD ReadLow["read(RTC_TIME_LOW)"] CombineRegs["Combine 32-bit registers"] ReadHigh["read(RTC_TIME_HIGH)"] Nanoseconds["64-bit nanoseconds"] DivideBy["÷ NSEC_PER_SEC"] UnixTime["Unix timestamp (seconds)"] CombineRegs --> Nanoseconds DivideBy --> UnixTime Nanoseconds --> DivideBy ReadHigh --> CombineRegs ReadLow --> CombineRegs
Sources: src/lib.rs(L35 - L40)
set_unix_timestamp
#![allow(unused)] fn main() { pub fn set_unix_timestamp(&self, unix_time: u64) }
Sets the RTC hardware clock to the specified Unix timestamp.
Parameters:
unix_time
: Time in seconds since Unix epoch
Implementation Details: The method converts the Unix timestamp to nanoseconds, then writes the value as two 32-bit register operations. The high-order register is written first, followed by the low-order register.
flowchart TD UnixInput["unix_time (seconds)"] MultiplyBy["× NSEC_PER_SEC"] Nanoseconds["64-bit nanoseconds"] SplitRegs["Split into 32-bit values"] WriteHigh["write(RTC_TIME_HIGH, high)"] WriteLow["write(RTC_TIME_LOW, low)"] MultiplyBy --> Nanoseconds Nanoseconds --> SplitRegs SplitRegs --> WriteHigh SplitRegs --> WriteLow UnixInput --> MultiplyBy
Sources: src/lib.rs(L42 - L49)
Usage Patterns
Basic Time Reading
use riscv_goldfish::Rtc;
let rtc = Rtc::new(0x101000);
let current_time = rtc.get_unix_timestamp();
println!("Current time: {} seconds since epoch", current_time);
Time Setting and Verification
use riscv_goldfish::Rtc;
let rtc = Rtc::new(0x101000);
// Set time to a specific timestamp
let target_time = 1640995200; // January 1, 2022, 00:00:00 UTC
rtc.set_unix_timestamp(target_time);
// Verify the time was set correctly
let actual_time = rtc.get_unix_timestamp();
assert_eq!(actual_time, target_time);
Device Tree Integration
The base address parameter is typically obtained from device tree parsing:
Property | Value | Description |
---|---|---|
compatible | "google,goldfish-rtc" | Device identification |
reg | <0x00 0x101000 0x00 0x1000> | Base address and size |
interrupts | <0x0b> | Interrupt number (unused by driver) |
Sources: README.md(L15 - L32)
Constants and Internal Implementation
Hardware Register Offsets
Constant | Value | Purpose |
---|---|---|
RTC_TIME_LOW | 0x00 | Lower 32 bits of nanosecond timestamp |
RTC_TIME_HIGH | 0x04 | Upper 32 bits of nanosecond timestamp |
Time Conversion
Constant | Value | Purpose |
---|---|---|
NSEC_PER_SEC | 1_000_000_000 | Nanoseconds per second conversion factor |
Memory-Mapped I/O Operations
The driver uses unsafe volatile operations for hardware access:
Method | Purpose | Safety Requirements |
---|---|---|
read(reg: usize) | Read 32-bit register value | Valid base address + register offset |
write(reg: usize, value: u32) | Write 32-bit register value | Valid base address + register offset |
Sources: src/lib.rs(L6 - L9) src/lib.rs(L17 - L24)
API Constraints and Limitations
Thread Safety
The Rtc
struct does not implement Send
or Sync
traits. Multiple threads accessing the same RTC instance require external synchronization.
Error Handling
The API does not return Result
types. Invalid memory addresses or hardware failures will cause undefined behavior rather than recoverable errors.
Platform Requirements
- Requires
no_std
environment compatibility - Assumes little-endian byte ordering for register operations
- Requires unsafe memory access capabilities
Sources: src/lib.rs(L4) src/lib.rs(L17 - L23)
Hardware Interface
Relevant source files
This document covers the low-level hardware interface implementation of the riscv_goldfish RTC driver, focusing on memory-mapped I/O operations, register layout, and the abstraction layer between software and the Goldfish RTC hardware. For higher-level API usage patterns, see API Reference. For time conversion logic, see Time Conversion.
Register Layout and Memory Mapping
The Goldfish RTC hardware exposes a simple memory-mapped interface consisting of two 32-bit registers that together form a 64-bit nanosecond timestamp counter. The driver defines the register offsets as compile-time constants and uses a base address provided during initialization.
Register Map
Offset | Register Name | Width | Purpose |
---|---|---|---|
0x00 | RTC_TIME_LOW | 32-bit | Lower 32 bits of nanosecond timestamp |
0x04 | RTC_TIME_HIGH | 32-bit | Upper 32 bits of nanosecond timestamp |
Memory Address Calculation
flowchart TD BaseAddr["base_address"] LowAddr["Low Register Address"] HighAddr["High Register Address"] LowReg["RTC_TIME_LOW Register"] HighReg["RTC_TIME_HIGH Register"] NS64["64-bit Nanosecond Value"] BaseAddr --> HighAddr BaseAddr --> LowAddr HighAddr --> HighReg HighReg --> NS64 LowAddr --> LowReg LowReg --> NS64
The driver calculates absolute register addresses by adding the register offset constants to the base address provided during Rtc::new()
construction.
Sources: src/lib.rs(L6 - L7) src/lib.rs(L31 - L33)
Memory-Mapped I/O Operations
The hardware interface implements volatile memory operations to ensure proper communication with the RTC hardware. The driver provides two core memory access functions that handle the low-level register reads and writes.
Volatile Memory Access Functions
flowchart TD ReadOp["read(reg: usize)"] AddrCalc["Address Calculation"] WriteOp["write(reg: usize, value: u32)"] ReadPtr["Read Pointer Cast"] WritePtr["Write Pointer Cast"] HardwareRead["Hardware Register Read"] HardwareWrite["Hardware Register Write"] ReadResult["Return Value"] WriteResult["Write Complete"] AddrCalc --> ReadPtr AddrCalc --> WritePtr HardwareRead --> ReadResult HardwareWrite --> WriteResult ReadOp --> AddrCalc ReadPtr --> HardwareRead WriteOp --> AddrCalc WritePtr --> HardwareWrite
The read()
function performs volatile reads to prevent compiler optimizations that might cache register values, while write()
ensures immediate hardware updates. Both operations are marked unsafe
due to raw pointer manipulation.
Sources: src/lib.rs(L17 - L23)
64-bit Value Handling Across 32-bit Registers
The Goldfish RTC hardware stores nanosecond timestamps as a 64-bit value, but exposes this through two separate 32-bit registers. The driver implements bidirectional conversion between the unified 64-bit representation and the split register format.
Read Operation Data Flow
flowchart TD subgraph subGraph2["Value Reconstruction"] Combine["(high << 32) | low"] UnixTime["Unix Timestamp"] end subgraph subGraph1["Driver Read Operations"] ReadLow["read(RTC_TIME_LOW)"] LowU64["low: u64"] ReadHigh["read(RTC_TIME_HIGH)"] HighU64["high: u64"] end subgraph subGraph0["Hardware Registers"] LowHW["RTC_TIME_LOW32-bit hardware"] HighHW["RTC_TIME_HIGH32-bit hardware"] end Combine --> UnixTime HighHW --> ReadHigh HighU64 --> Combine LowHW --> ReadLow LowU64 --> Combine ReadHigh --> HighU64 ReadLow --> LowU64
Write Operation Data Flow
flowchart TD subgraph subGraph3["Hardware Registers"] HighHWOut["RTC_TIME_HIGHupdated"] LowHWOut["RTC_TIME_LOWupdated"] end subgraph subGraph2["Hardware Write"] WriteHigh["write(RTC_TIME_HIGH, high)"] WriteLow["write(RTC_TIME_LOW, low)"] end subgraph subGraph1["Value Splitting"] HighPart["High 32 bits"] LowPart["Low 32 bits"] end subgraph subGraph0["Input Processing"] UnixIn["unix_time: u64"] Nanos["time_nanos: u64"] end HighPart --> WriteHigh LowPart --> WriteLow Nanos --> HighPart Nanos --> LowPart UnixIn --> Nanos WriteHigh --> HighHWOut WriteLow --> LowHWOut
The write operation updates the high register first, then the low register, to maintain temporal consistency during the split-register update sequence.
Sources: src/lib.rs(L36 - L40) src/lib.rs(L43 - L49)
Hardware Abstraction Layer
The Rtc
struct provides a clean abstraction over the raw hardware interface, encapsulating the base address and providing safe access patterns for register operations.
Driver Structure and Hardware Mapping
flowchart TD subgraph subGraph2["Physical Hardware"] GoldfishRTC["Goldfish RTC Device64-bit nanosecond counter"] end subgraph subGraph1["Hardware Abstraction"] BaseAddr["base_address field"] MemoryMap["Memory Mapping"] RegOffsets["RTC_TIME_LOWRTC_TIME_HIGH"] end subgraph subGraph0["Software Layer"] RtcStruct["Rtc { base_address: usize }"] PublicAPI["get_unix_timestamp()set_unix_timestamp()"] PrivateOps["read(reg)write(reg, value)"] end BaseAddr --> MemoryMap MemoryMap --> GoldfishRTC PrivateOps --> MemoryMap PublicAPI --> PrivateOps RegOffsets --> MemoryMap RtcStruct --> PublicAPI
The abstraction separates hardware-specific details (register offsets, volatile operations) from the public API, allowing safe high-level operations while maintaining direct hardware access efficiency.
Sources: src/lib.rs(L11 - L14) src/lib.rs(L26 - L50)
Safety Considerations
The hardware interface implements several safety measures to ensure reliable operation:
- Volatile Operations: All register access uses
read_volatile()
andwrite_volatile()
to prevent compiler optimizations that could interfere with hardware communication - Unsafe Boundaries: Raw pointer operations are confined to private methods, exposing only safe public APIs
- Type Safety: Register values are properly cast between
u32
hardware format andu64
application format - Address Validation: Base address is stored during construction and used consistently for all register calculations
The driver assumes the provided base address corresponds to valid, mapped Goldfish RTC hardware, typically obtained from device tree parsing during system initialization.
Sources: src/lib.rs(L17 - L23) src/lib.rs(L31 - L33)
Time Conversion
Relevant source files
This document explains the time conversion logic implemented in the RTC driver, covering the transformation between Unix timestamps (seconds) and hardware nanosecond representations. The conversion process handles bidirectional data flow between software time formats and the Goldfish RTC hardware's internal 64-bit nanosecond counter.
For details about the hardware register interface used in these conversions, see Hardware Interface. For the complete public API that exposes these conversion functions, see API Reference.
Time Representation Formats
The RTC driver handles two distinct time representation formats that require conversion between software and hardware layers.
Unix Timestamp Format
The driver's public API operates using Unix timestamps expressed as u64
values representing seconds since the Unix epoch (January 1, 1970, 00:00:00 UTC). This format provides a standard interface for operating system integration and application compatibility.
Hardware Nanosecond Format
The Goldfish RTC hardware internally maintains time as a 64-bit counter representing nanoseconds since the Unix epoch. This high-resolution format is stored across two 32-bit memory-mapped registers: RTC_TIME_HIGH
and RTC_TIME_LOW
.
Sources: src/lib.rs(L6 - L7) src/lib.rs(L35 - L39) src/lib.rs(L42 - L44)
Conversion Constants
The time conversion relies on a single fundamental constant that defines the relationship between seconds and nanoseconds.
Constant | Value | Purpose |
---|---|---|
NSEC_PER_SEC | 1_000_000_000 | Nanoseconds per second conversion factor |
This constant enables precise conversion between the second-granularity Unix timestamps and the nanosecond-granularity hardware representation without loss of precision in the direction from seconds to nanoseconds.
Sources: src/lib.rs(L9)
Time Conversion Data Flow
Reading Time from Hardware
flowchart TD A["RTC_TIME_LOW register"] C["Combine into 64-bit value"] B["RTC_TIME_HIGH register"] D["((high << 32) | low)"] E["Divide by NSEC_PER_SEC"] F["Unix timestamp (seconds)"] G["64-bit nanoseconds"] A --> C B --> C C --> D C --> G D --> E E --> F G --> E
Reading Time Conversion Process
The get_unix_timestamp()
function implements a multi-step process to extract time from hardware registers and convert to Unix timestamp format.
Sources: src/lib.rs(L36 - L40)
Writing Time to Hardware
flowchart TD A["Unix timestamp (seconds)"] B["Multiply by NSEC_PER_SEC"] C["64-bit nanoseconds"] D["Split into 32-bit values"] E["time_nanos >> 32"] F["time_nanos as u32"] G["Write to RTC_TIME_HIGH"] H["Write to RTC_TIME_LOW"] A --> B B --> C C --> D D --> E D --> F E --> G F --> H
Writing Time Conversion Process
The set_unix_timestamp()
function performs the inverse conversion, transforming Unix timestamps into the hardware's nanosecond format and distributing the value across two registers.
Sources: src/lib.rs(L43 - L49)
Bit Manipulation and Register Mapping
The conversion process requires careful handling of 64-bit values across 32-bit register boundaries, involving specific bit manipulation operations.
flowchart TD subgraph subGraph1["Hardware Registers"] RH["RTC_TIME_HIGH0x04"] RL["RTC_TIME_LOW0x00"] end subgraph subGraph0["64-bit Nanosecond Value"] H["High 32 bits[63:32]"] L["Low 32 bits[31:0]"] end subgraph subGraph3["Write Operations"] W4["write(RTC_TIME_LOW, low)"] subgraph subGraph2["Read Operations"] W1["time_nanos >> 32"] W2["time_nanos as u32"] W3["write(RTC_TIME_HIGH, high)"] R1["read(RTC_TIME_LOW) as u64"] R2["read(RTC_TIME_HIGH) as u64"] R3["(high << 32) | low"] end end H --> RH L --> RL
Register Bit Mapping and Operations
Sources: src/lib.rs(L37 - L39) src/lib.rs(L45 - L47)
Implementation Analysis
Precision and Range Considerations
The conversion implementation maintains full precision when converting from Unix seconds to nanoseconds, as the multiplication by NSEC_PER_SEC
is exact for integer values. However, the reverse conversion from nanoseconds to seconds uses integer division, which truncates sub-second precision.
Supported Time Range:
- Maximum representable time:
(2^64 - 1) / NSEC_PER_SEC
≈ 584 years from Unix epoch - Minimum granularity: 1 second (in Unix timestamp format)
- Hardware granularity: 1 nanosecond (internal representation)
Atomic Operation Considerations
The current implementation does not provide atomic guarantees when reading or writing the split 64-bit value across two 32-bit registers. The get_unix_timestamp()
function reads RTC_TIME_LOW
first, then RTC_TIME_HIGH
, while set_unix_timestamp()
writes RTC_TIME_HIGH
first, then RTC_TIME_LOW
. This ordering could potentially result in inconsistent reads if the hardware updates between register accesses.
Sources: src/lib.rs(L36 - L40) src/lib.rs(L43 - L49)
Project Configuration
Relevant source files
This document covers the comprehensive project configuration for the riscv_goldfish crate as defined in the Cargo.toml manifest. It details package metadata, licensing strategy, target platform specifications, and dependency management that enable cross-platform RTC driver functionality within the ArceOS ecosystem.
For implementation details of the RTC driver itself, see RTC Driver Implementation. For CI/CD build matrix and testing procedures, see CI/CD Pipeline.
Package Metadata and Identity
The project configuration establishes the fundamental identity and purpose of the riscv_goldfish crate through standardized Cargo package metadata.
Core Package Definition
flowchart TD subgraph subGraph2["External References"] HOMEPAGE["homepage: arceos GitHub"] REPOSITORY["repository: riscv_goldfish GitHub"] DOCUMENTATION["documentation: docs.rs"] end subgraph subGraph1["Project Description"] DESC["description: System Real Time Clock Drivers"] KEYWORDS["keywords: [arceos, riscv, rtc]"] CATEGORIES["categories: [os, hardware-support, no-std]"] end subgraph subGraph0["Package Identity"] NAME["name: riscv_goldfish"] VERSION["version: 0.1.1"] EDITION["edition: 2021"] AUTHOR["authors: Keyang Hu"] end CATEGORIES --> DOCUMENTATION DESC --> HOMEPAGE EDITION --> CATEGORIES KEYWORDS --> REPOSITORY NAME --> DESC VERSION --> KEYWORDS
Package Metadata Configuration
Field | Value | Purpose |
---|---|---|
name | riscv_goldfish | Crate identifier for Cargo registry |
version | 0.1.1 | Semantic versioning for API compatibility |
edition | 2021 | Rust language edition compatibility |
authors | Keyang Hu keyang.hu@qq.com | Primary maintainer contact |
The package name follows Rust naming conventions using underscores and clearly indicates both the target architecture (riscv
) and hardware platform (goldfish
). The version 0.1.1
indicates this is an early release with patch-level updates from the initial 0.1.0
version.
Sources: Cargo.toml(L1 - L6)
Project Classification and Discovery
The configuration includes strategic metadata for package discovery and classification within the Rust ecosystem.
flowchart TD subgraph subGraph2["Use Case Categories"] OS_DEV["Operating Systems"] HW_SUPPORT["Hardware Support"] NOSTD_EMBEDDED["no-std Embedded"] end subgraph subGraph1["Target Audiences"] ARCEOS_USERS["ArceOS Developers"] RISCV_DEVS["RISC-V Engineers"] RTC_DEVS["RTC Driver Authors"] end subgraph subGraph0["Discovery Metadata"] KEYWORDS["keywords"] CATEGORIES["categories"] DESCRIPTION["description"] end CATEGORIES --> HW_SUPPORT CATEGORIES --> NOSTD_EMBEDDED CATEGORIES --> OS_DEV DESCRIPTION --> CATEGORIES DESCRIPTION --> KEYWORDS KEYWORDS --> ARCEOS_USERS KEYWORDS --> RISCV_DEVS KEYWORDS --> RTC_DEVS
The categories
field strategically positions the crate in three key areas:
os
: Operating system components and kernel-level drivershardware-support
: Low-level hardware interface librariesno-std
: Embedded and bare-metal compatible crates
The keywords
array enables discovery by ArceOS ecosystem users (arceos
), RISC-V developers (riscv
), and those seeking RTC functionality (rtc
).
Sources: Cargo.toml(L6 - L12)
Licensing and Legal Framework
The project employs a triple licensing strategy to maximize compatibility across different legal and organizational requirements.
Multi-License Configuration
flowchart TD subgraph subGraph2["Use Cases"] OPEN_SOURCE["Open Source Projects"] COMMERCIAL["Commercial Integration"] CHINA_MARKET["Chinese Organizations"] end subgraph subGraph1["License Options"] GPL["GPL-3.0-or-laterCopyleft Protection"] APACHE["Apache-2.0Commercial Friendly"] MULAN["MulanPSL-2.0Chinese Legal Framework"] end subgraph subGraph0["Triple License Strategy"] LICENSE_FIELD["license: GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"] end APACHE --> COMMERCIAL GPL --> OPEN_SOURCE LICENSE_FIELD --> APACHE LICENSE_FIELD --> GPL LICENSE_FIELD --> MULAN MULAN --> CHINA_MARKET
The OR
operator in the license field allows users to choose any of the three licenses based on their specific requirements:
- GPL-3.0-or-later: Ensures derivative works remain open source
- Apache-2.0: Permits commercial use with patent grant protections
- MulanPSL-2.0: Provides compatibility with Chinese legal frameworks
This licensing strategy is particularly important for operating system components that may be integrated into diverse software stacks with varying legal requirements.
Sources: Cargo.toml(L7)
Target Platform and Architecture Support
The configuration enables cross-platform compatibility through careful dependency management and metadata specification.
No-Standard Library Configuration
flowchart TD subgraph subGraph2["Supported Architectures"] X86_64["x86_64-unknown-linux-gnu"] X86_NONE["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] ARM64["aarch64-unknown-none-softfloat"] end subgraph subGraph1["Target Compatibility"] NOSTD["no-std Category"] BARE_METAL["Bare Metal Targets"] EMBEDDED["Embedded Systems"] end subgraph subGraph0["Dependencies Configuration"] EMPTY_DEPS["[dependencies](empty section)"] end BARE_METAL --> ARM64 BARE_METAL --> RISCV BARE_METAL --> X86_64 BARE_METAL --> X86_NONE EMPTY_DEPS --> NOSTD NOSTD --> BARE_METAL NOSTD --> EMBEDDED
The absence of dependencies in the [dependencies]
section is intentional and critical for the crate's design:
- Zero Dependencies: Enables compilation for bare-metal targets without standard library
- no-std Compatibility: Listed in categories to indicate embedded system support
- Cross-Compilation: Supports multiple architecture targets through pure Rust implementation
This configuration allows the RTC driver to function in resource-constrained environments where the standard library is unavailable or undesirable.
Sources: Cargo.toml(L12 - L15)
Repository and Documentation Links
The configuration establishes clear pathways for users to access source code, documentation, and project information.
Link Type | URL | Purpose |
---|---|---|
homepage | https://github.com/arceos-org/arceos | ArceOS project ecosystem |
repository | https://github.com/arceos-org/riscv_goldfish | Source code repository |
documentation | https://docs.rs/riscv_goldfish | API documentation |
The homepage
points to the broader ArceOS ecosystem, indicating this crate's role as a component within a larger operating system project. The repository
field provides direct access to source code, while documentation
enables automatic API documentation generation and hosting on docs.rs.
Sources: Cargo.toml(L8 - L10)
Configuration Impact on Build System
The Cargo.toml configuration directly influences the build system's behavior and capabilities across different target platforms.
flowchart TD subgraph subGraph2["CI/CD Integration"] MULTIPLE_TARGETS["cargo build --target"] NO_STD_CHECK["no-std Validation"] DOC_GENERATION["cargo doc Generation"] end subgraph subGraph1["Build System Effects"] CROSS_COMPILE["Cross-Compilation Support"] TARGET_MATRIX["Multi-Target Building"] FEATURE_SET["Core Library Only"] end subgraph subGraph0["Cargo.toml Configuration"] NO_DEPS["Empty Dependencies"] NOSTD_CAT["no-std Category"] EDITION_2021["edition = 2021"] end CROSS_COMPILE --> MULTIPLE_TARGETS FEATURE_SET --> DOC_GENERATION NOSTD_CAT --> TARGET_MATRIX NO_DEPS --> CROSS_COMPILE TARGET_MATRIX --> NO_STD_CHECK
The configuration enables several critical build system behaviors:
- Dependency-Free Building: Empty dependencies section allows compilation without external crates
- Target Flexibility: no-std category signals compatibility with embedded toolchains
- Modern Rust: Edition 2021 ensures access to latest language features while maintaining compatibility
This configuration is essential for the CI/CD pipeline's ability to validate the driver across multiple architectures and deployment scenarios.
Sources: Cargo.toml(L4 - L15)
Target Platforms and Cross-Compilation
Relevant source files
This document covers the target platform support and cross-compilation configuration for the riscv_goldfish
crate. It details the supported architectures, no_std
compatibility requirements, and the automated validation pipeline that ensures cross-platform compatibility. For information about the core RTC driver implementation, see RTC Driver Implementation. For details about project licensing and distribution, see Licensing and Distribution.
Supported Target Platforms
The riscv_goldfish
crate is designed as a no_std
library that supports multiple target architectures through Rust's cross-compilation capabilities. The primary target is RISC-V, but the crate maintains compatibility with x86_64 and ARM64 platforms for development and testing purposes.
Target Architecture Matrix
Target Triple | Architecture | Environment | Testing | Purpose |
---|---|---|---|---|
riscv64gc-unknown-none-elf | RISC-V 64-bit | Bare metal | Build only | Primary target platform |
x86_64-unknown-linux-gnu | x86_64 | Linux userspace | Full tests | Development and testing |
x86_64-unknown-none | x86_64 | Bare metal | Build only | Bare metal validation |
aarch64-unknown-none-softfloat | ARM64 | Bare metal | Build only | Embedded ARM compatibility |
Target Platform Validation Pipeline
flowchart TD subgraph Components["Toolchain Components"] RUST_SRC["rust-src"] CLIPPY_COMP["clippy"] RUSTFMT["rustfmt"] end subgraph Validation_Steps["Validation Pipeline"] FMT["cargo fmt --all --check"] CLIPPY["cargo clippy --target TARGET"] BUILD["cargo build --target TARGET"] TEST["cargo test --target x86_64-unknown-linux-gnu"] end subgraph CI_Matrix["CI Build Matrix"] NIGHTLY["nightly toolchain"] RISCV["riscv64gc-unknown-none-elf"] X86_LINUX["x86_64-unknown-linux-gnu"] X86_NONE["x86_64-unknown-none"] ARM64["aarch64-unknown-none-softfloat"] end ARM64 --> FMT BUILD --> TEST CLIPPY --> BUILD FMT --> CLIPPY NIGHTLY --> CLIPPY_COMP NIGHTLY --> RUSTFMT NIGHTLY --> RUST_SRC RISCV --> FMT RUST_SRC --> BUILD X86_LINUX --> FMT X86_NONE --> FMT
Sources: .github/workflows/ci.yml(L8 - L30) Cargo.toml(L12)
Cross-Compilation Configuration
The crate leverages Rust's built-in cross-compilation support through the no_std
attribute and careful dependency management. The configuration enables compilation for embedded and bare-metal targets without requiring a standard library.
Package Configuration for Cross-Compilation
flowchart TD subgraph Cross_Compilation["Cross-Compilation Features"] BARE_METAL["Bare metal support"] EMBEDDED["Embedded targets"] KERNEL_SPACE["Kernel space compatibility"] end subgraph Target_Categories["Target Categories"] OS["os"] HW_SUPPORT["hardware-support"] NOSTD_CAT["no-std"] end subgraph Cargo_toml["Cargo.toml Configuration"] NO_STD["categories = no-std"] EDITION["edition = 2021"] NO_DEPS["dependencies = empty"] end EDITION --> BARE_METAL HW_SUPPORT --> EMBEDDED NOSTD_CAT --> BARE_METAL NO_DEPS --> BARE_METAL NO_STD --> NOSTD_CAT OS --> KERNEL_SPACE
Sources: Cargo.toml(L12) Cargo.toml(L14 - L15)
The crate maintains zero dependencies, which eliminates potential compatibility issues during cross-compilation. This design choice ensures that the RTC driver can be integrated into any environment that supports basic memory-mapped I/O operations.
Toolchain Requirements
Rust Nightly Toolchain
The project requires the Rust nightly toolchain to access unstable features necessary for bare-metal development. The CI pipeline specifically configures the following components:
rust-src
: Required for cross-compilation tono_std
targetsclippy
: Static analysis for all target platformsrustfmt
: Code formatting consistency across architectures
Target Installation
For local development, targets must be installed using rustup
:
rustup target add riscv64gc-unknown-none-elf
rustup target add x86_64-unknown-none
rustup target add aarch64-unknown-none-softfloat
CI Toolchain Configuration Flow
flowchart TD subgraph Components_Install["Component Installation"] RUST_SRC_COMP["rust-src"] CLIPPY_COMP["clippy"] RUSTFMT_COMP["rustfmt"] TARGET_INSTALL["targets: matrix.targets"] end subgraph Target_Matrix["Target Matrix Strategy"] FAIL_FAST["fail-fast: false"] MATRIX_TARGETS["matrix.targets"] MATRIX_TOOLCHAIN["matrix.rust-toolchain"] end subgraph Toolchain_Setup["Toolchain Setup"] CHECKOUT["actions/checkout@v4"] TOOLCHAIN["dtolnay/rust-toolchain@nightly"] VERSION_CHECK["rustc --version --verbose"] end CHECKOUT --> TOOLCHAIN FAIL_FAST --> MATRIX_TARGETS MATRIX_TARGETS --> TARGET_INSTALL MATRIX_TOOLCHAIN --> TOOLCHAIN TOOLCHAIN --> CLIPPY_COMP TOOLCHAIN --> RUSTFMT_COMP TOOLCHAIN --> RUST_SRC_COMP TOOLCHAIN --> TARGET_INSTALL TOOLCHAIN --> VERSION_CHECK
Sources: .github/workflows/ci.yml(L14 - L19) .github/workflows/ci.yml(L8 - L12)
Platform-Specific Considerations
RISC-V Target (riscv64gc-unknown-none-elf)
This is the primary target platform for the Goldfish RTC driver. The riscv64gc
specification includes:
- G: General-purpose integer and floating-point extensions
- C: Compressed instruction extension
- unknown-none-elf: Bare-metal environment with ELF binary format
Testing Strategy
The CI pipeline implements a pragmatic testing approach where unit tests only execute on x86_64-unknown-linux-gnu
due to the complexity of running tests in bare-metal environments. All other targets undergo build validation to ensure compilation compatibility.
flowchart TD subgraph Validation_Logic["Validation Logic"] IF_CONDITION["if: matrix.targets == x86_64-unknown-linux-gnu"] end subgraph Target_Actions["Target-Specific Actions"] UNIT_TESTS["cargo test --nocapture"] BUILD_CHECK["cargo build --all-features"] CLIPPY_CHECK["cargo clippy --all-features"] end subgraph Test_Strategy["Testing Strategy"] X86_LINUX_TEST["x86_64-unknown-linux-gnu"] BUILD_ONLY["Build-only validation"] end BUILD_ONLY --> BUILD_CHECK BUILD_ONLY --> CLIPPY_CHECK IF_CONDITION --> UNIT_TESTS X86_LINUX_TEST --> IF_CONDITION
Sources: .github/workflows/ci.yml(L28 - L30) .github/workflows/ci.yml(L25 - L27)
This approach ensures that the driver code compiles correctly for all supported architectures while maintaining practical CI execution times and avoiding the complexity of cross-architecture test execution environments.
Licensing and Distribution
Relevant source files
This document covers the licensing strategy and distribution mechanisms for the riscv_goldfish
crate. It explains the triple licensing scheme, distribution channels, package metadata configuration, and legal implications for different integration scenarios. For information about target platform configuration and cross-compilation setup, see Target Platforms and Cross-Compilation.
Licensing Strategy
The riscv_goldfish
crate employs a triple licensing approach that maximizes compatibility across different legal frameworks and use cases. The license specification uses an OR-based structure allowing users to choose the most appropriate license for their specific requirements.
Triple License Configuration
The crate is licensed under three alternative licenses as defined in Cargo.toml(L7) :
GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0
License Selection Framework
flowchart TD subgraph use_cases["Use Case Compatibility"] COPYLEFT["Copyleft Projects"] PERMISSIVE["Permissive Projects"] CHINESE["Chinese Legal Framework"] end subgraph license_options["Available License Options"] GPL["GPL-3.0-or-later"] APACHE["Apache-2.0"] MULAN["MulanPSL-2.0"] end subgraph riscv_goldfish_crate["riscv_goldfish Crate"] LICENSE_FIELD["license = GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"] end APACHE --> PERMISSIVE CHINESE --> MULAN COPYLEFT --> GPL GPL --> COPYLEFT LICENSE_FIELD --> APACHE LICENSE_FIELD --> GPL LICENSE_FIELD --> MULAN MULAN --> CHINESE PERMISSIVE --> APACHE
Sources: Cargo.toml(L7)
License Characteristics
License | Type | Primary Use Case | Key Features |
---|---|---|---|
GPL-3.0-or-later | Copyleft | Open source projects requiring strong copyleft | Patent protection, source disclosure requirements |
Apache-2.0 | Permissive | Commercial and proprietary integrations | Patent grant, trademark protection, business-friendly |
MulanPSL-2.0 | Permissive | Chinese legal jurisdictions | Domestic legal framework compliance, simplified terms |
Sources: Cargo.toml(L7)
Distribution Channels
The crate is distributed through multiple channels to ensure accessibility and discoverability across different platforms and communities.
Distribution Infrastructure
flowchart TD subgraph discovery["Discovery Metadata"] KEYWORDS["keywords = arceos, riscv, rtc"] CATEGORIES["categories = os, hardware-support, no-std"] end subgraph documentation["Documentation"] DOCS_RS["docs.rs/riscv_goldfish"] DOC_URL["documentation = docs.rs/riscv_goldfish"] end subgraph package_registry["Package Registry"] CRATES_IO["crates.io"] CARGO_TOML["Cargo.toml metadata"] end subgraph source_control["Source Control"] REPO_URL["repository = github.com/arceos-org/riscv_goldfish"] HOMEPAGE["homepage = github.com/arceos-org/arceos"] end CARGO_TOML --> CRATES_IO CATEGORIES --> CRATES_IO CRATES_IO --> DOCS_RS DOC_URL --> DOCS_RS HOMEPAGE --> CRATES_IO KEYWORDS --> CRATES_IO REPO_URL --> CRATES_IO
Sources: Cargo.toml(L8 - L12)
Package Metadata Configuration
The distribution strategy leverages comprehensive metadata to maximize discoverability and provide clear project context:
Core Metadata Fields
- Package Name:
riscv_goldfish
- clearly identifies the target platform and hardware component - Version:
0.1.1
- semantic versioning with patch-level updates - Authors:
Keyang Hu <keyang.hu@qq.com>
- primary maintainer contact - Description: System Real Time Clock (RTC) Drivers for riscv based on goldfish
Discovery and Classification
The crate uses targeted keywords and categories to ensure proper classification in the Rust ecosystem:
Keywords Cargo.toml(L11) :
arceos
- Associates with the ArceOS operating system projectriscv
- Identifies the target processor architecturertc
- Specifies the hardware component type
Categories Cargo.toml(L12) :
os
- Operating system componentshardware-support
- Hardware abstraction and driversno-std
- Embedded and bare-metal compatibility
Sources: Cargo.toml(L2 - L12)
Legal Implications and Use Cases
The triple licensing structure addresses different legal requirements and integration scenarios that may arise when incorporating the RTC driver into various projects.
Integration Scenarios
ArceOS Integration
The crate is designed for integration with the ArceOS operating system project, as indicated by the homepage reference Cargo.toml(L8) The licensing allows ArceOS to choose the most appropriate license based on their distribution strategy.
Commercial Embedded Systems
The Apache-2.0 option enables integration into proprietary embedded systems and commercial products without source disclosure requirements, making it suitable for industrial IoT and embedded applications.
Open Source Projects
The GPL-3.0-or-later option ensures compatibility with other GPL-licensed operating system components while providing future license version flexibility.
Chinese Market Compliance
The MulanPSL-2.0 option addresses specific legal framework requirements for distribution within Chinese jurisdictions, supporting domestic technology adoption.
License Compatibility Matrix
flowchart TD subgraph license_selection["License Selection"] CHOOSE_GPL["Choose GPL-3.0-or-later"] CHOOSE_APACHE["Choose Apache-2.0"] CHOOSE_MULAN["Choose MulanPSL-2.0"] end subgraph downstream_projects["Downstream Project Types"] GPL_PROJECT["GPL-licensed OS Components"] COMMERCIAL["Commercial Embedded Systems"] PERMISSIVE_PROJECT["MIT/BSD Licensed Projects"] CHINESE_PROJECT["Chinese Domestic Projects"] end CHINESE_PROJECT --> CHOOSE_MULAN CHOOSE_APACHE --> COMMERCIAL CHOOSE_APACHE --> PERMISSIVE_PROJECT CHOOSE_GPL --> GPL_PROJECT CHOOSE_MULAN --> CHINESE_PROJECT COMMERCIAL --> CHOOSE_APACHE GPL_PROJECT --> CHOOSE_GPL PERMISSIVE_PROJECT --> CHOOSE_APACHE
Sources: Cargo.toml(L7 - L8)
Distribution Workflow
The package distribution leverages Cargo's ecosystem integration to provide automated publishing and documentation generation:
- Source Publication: Code is maintained in the GitHub repository specified in Cargo.toml(L9)
- Package Registry: Automated publishing to crates.io using metadata from Cargo.toml(L1 - L12)
- Documentation Generation: Automatic docs.rs publication linked from Cargo.toml(L10)
- Discoverability: Search optimization through keywords and categories in Cargo.toml(L11 - L12)
The triple licensing approach ensures that regardless of the downstream project's legal requirements, there is a compatible license option available, maximizing the driver's utility across different integration scenarios while maintaining compliance with various legal frameworks.
Sources: Cargo.toml(L7 - L12)
Development Workflow
Relevant source files
This document provides an overview of the development workflow for the riscv_goldfish RTC driver, covering the essential processes that contributors and maintainers follow when working with the codebase. This includes code quality assurance, multi-platform building and testing, and documentation generation.
For detailed information about the automated CI/CD pipeline configuration and build matrix, see CI/CD Pipeline. For local development environment setup and tooling requirements, see Development Environment Setup.
Workflow Overview
The development workflow is built around a robust continuous integration system that ensures code quality and cross-platform compatibility. The process validates changes across multiple target architectures while maintaining strict code formatting and linting standards.
flowchart TD DEV["Developer"] COMMIT["git commit/push"] TRIGGER["GitHub Actions Trigger"] CI_JOB["ci job"] DOC_JOB["doc job"] MATRIX["Build Matrix Strategy"] TARGET1["x86_64-unknown-linux-gnu"] TARGET2["x86_64-unknown-none"] TARGET3["riscv64gc-unknown-none-elf"] TARGET4["aarch64-unknown-none-softfloat"] QUALITY["Code Quality Pipeline"] BUILD["cargo build"] TEST["cargo test"] DOC_BUILD["cargo doc"] PAGES["GitHub Pages Deploy"] SUCCESS["Workflow Success"] BUILD --> TEST CI_JOB --> MATRIX COMMIT --> TRIGGER DEV --> COMMIT DOC_BUILD --> PAGES DOC_JOB --> DOC_BUILD MATRIX --> TARGET1 MATRIX --> TARGET2 MATRIX --> TARGET3 MATRIX --> TARGET4 PAGES --> SUCCESS QUALITY --> BUILD TARGET1 --> QUALITY TARGET2 --> QUALITY TARGET3 --> QUALITY TARGET4 --> QUALITY TEST --> SUCCESS TRIGGER --> CI_JOB TRIGGER --> DOC_JOB
Development Workflow Overview
The workflow consists of two parallel jobs that validate different aspects of the codebase: the ci
job focuses on code quality and cross-compilation, while the doc
job handles documentation generation and deployment.
Sources: .github/workflows/ci.yml(L1 - L56)
Code Quality Pipeline
The quality assurance process enforces consistent coding standards and catches potential issues before they reach the main branch. Each target platform goes through the same rigorous validation steps.
flowchart TD START["Checkout Code"] TOOLCHAIN["Setup Rust Nightly"] VERSION["rustc --version --verbose"] FMT["cargo fmt --all -- --check"] CLIPPY["cargo clippy --target TARGET --all-features"] BUILD["cargo build --target TARGET --all-features"] UNIT_TEST["cargo test --target x86_64-unknown-linux-gnu"] FMT_FAIL["Format Check Failed"] CLIPPY_FAIL["Linting Failed"] BUILD_FAIL["Build Failed"] TEST_FAIL["Tests Failed"] PASS["Quality Check Passed"] BUILD --> BUILD_FAIL BUILD --> UNIT_TEST CLIPPY --> BUILD CLIPPY --> CLIPPY_FAIL FMT --> CLIPPY FMT --> FMT_FAIL START --> TOOLCHAIN TOOLCHAIN --> VERSION UNIT_TEST --> PASS UNIT_TEST --> TEST_FAIL VERSION --> FMT
Code Quality Validation Steps
The pipeline uses specific Rust toolchain components and flags to ensure comprehensive validation. The clippy
step includes a specific allow flag -A clippy::new_without_default
to suppress false positives for the RTC driver's constructor pattern.
Sources: .github/workflows/ci.yml(L15 - L30)
Multi-Target Build Matrix
The build process validates the driver against multiple target architectures to ensure broad compatibility across different embedded and bare-metal environments.
Target Platform | Purpose | Testing |
---|---|---|
x86_64-unknown-linux-gnu | Development and testing host | Unit tests enabled |
x86_64-unknown-none | Bare metal x86_64 | Build validation only |
riscv64gc-unknown-none-elf | Primary RISC-V target | Build validation only |
aarch64-unknown-none-softfloat | ARM64 embedded systems | Build validation only |
The matrix strategy uses fail-fast: false
to ensure all targets are tested even if one fails, providing comprehensive feedback about platform-specific issues.
flowchart TD NIGHTLY["rust-toolchain: nightly"] COMPONENTS["Components:rust-src, clippy, rustfmt"] T1["x86_64-unknown-linux-gnu(Host Platform)"] T2["x86_64-unknown-none(Bare Metal x86)"] T3["riscv64gc-unknown-none-elf(Primary Target)"] T4["aarch64-unknown-none-softfloat(ARM64 Embedded)"] FULL_TEST["Full Testing PipelineFormat + Lint + Build + Test"] BUILD_ONLY["Build ValidationFormat + Lint + Build"] COMPONENTS --> T1 COMPONENTS --> T2 COMPONENTS --> T3 COMPONENTS --> T4 NIGHTLY --> COMPONENTS T1 --> FULL_TEST T2 --> BUILD_ONLY T3 --> BUILD_ONLY T4 --> BUILD_ONLY
Build Target Matrix Configuration
The testing strategy recognizes that unit tests are most practical on the host platform (x86_64-unknown-linux-gnu
) while ensuring the driver compiles correctly for all target environments.
Sources: .github/workflows/ci.yml(L8 - L30)
Development Environment Configuration
The development environment is configured to exclude build artifacts and IDE-specific files from version control, maintaining a clean repository while supporting different development setups.
Ignored Path | Purpose | Impact |
---|---|---|
/target | Cargo build output | Prevents binary artifacts in VCS |
/.vscode | Visual Studio Code settings | Supports multiple IDE preferences |
.DS_Store | macOS system files | Cross-platform compatibility |
Cargo.lock | Dependency lock file | Library crate best practice |
The exclusion of Cargo.lock
follows Rust library crate conventions, allowing downstream consumers to resolve their own dependency versions while ensuring the crate builds with compatible dependency ranges.
Sources: .gitignore(L1 - L5)
Documentation Pipeline
The documentation system automatically generates and deploys API documentation to GitHub Pages, ensuring the public documentation stays synchronized with the codebase.
flowchart TD DOC_TRIGGER["doc job trigger"] CHECKOUT["actions/checkout@v4"] RUST_SETUP["dtolnay/rust-toolchain@nightly"] DOC_BUILD["cargo doc --no-deps --all-features"] INDEX_GEN["Generate index.html redirect"] CONDITION["github.ref == default-branch?"] DEPLOY["JamesIves/github-pages-deploy-action@v4"] SKIP["Skip deployment"] PAGES_UPDATE["GitHub Pages Updated"] ERROR_CHECK["Build successful?"] CONTINUE["continue-on-error: true"] FAIL["Pipeline fails"] CHECKOUT --> RUST_SETUP CONDITION --> DEPLOY CONDITION --> SKIP DEPLOY --> PAGES_UPDATE DOC_BUILD --> ERROR_CHECK DOC_BUILD --> INDEX_GEN DOC_TRIGGER --> CHECKOUT ERROR_CHECK --> CONTINUE ERROR_CHECK --> FAIL INDEX_GEN --> CONDITION RUST_SETUP --> DOC_BUILD
Documentation Generation and Deployment
The documentation pipeline uses strict linting flags (RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
) to ensure comprehensive and accurate documentation. Deployment only occurs from the default branch to prevent documentation pollution from feature branches.
Sources: .github/workflows/ci.yml(L32 - L56)
Contributing Process
Contributors should ensure their changes pass the complete quality pipeline before submitting pull requests. The automated checks provide immediate feedback on code formatting, linting violations, build failures, and test regressions.
Key requirements for contributions:
- Code must pass
cargo fmt
formatting checks - All
clippy
lints must be resolved (except explicitly allowed ones) - Changes must build successfully on all target platforms
- Unit tests must pass on the host platform
- Documentation must build without warnings or broken links
The workflow automatically triggers on both push
and pull_request
events, providing consistent validation regardless of how changes are submitted.
Sources: .github/workflows/ci.yml(L3)
CI/CD Pipeline
Relevant source files
This document covers the automated continuous integration and continuous deployment pipeline for the riscv_goldfish crate. The pipeline validates code quality, builds across multiple target architectures, runs tests, and automatically deploys documentation.
For information about local development environment setup, see Development Environment Setup. For details about supported target platforms and cross-compilation, see Target Platforms and Cross-Compilation.
Pipeline Overview
The CI/CD pipeline is implemented using GitHub Actions and consists of two primary workflows: code validation (ci
job) and documentation deployment (doc
job). The pipeline ensures code quality across multiple architectures while maintaining comprehensive documentation.
Workflow Trigger Configuration
flowchart TD PUSH["push events"] WORKFLOW["CI Workflow"] PR["pull_request events"] CI_JOB["ci job"] DOC_JOB["doc job"] PR --> WORKFLOW PUSH --> WORKFLOW WORKFLOW --> CI_JOB WORKFLOW --> DOC_JOB
Sources: .github/workflows/ci.yml(L3)
Multi-Architecture Build Matrix
The CI pipeline validates the codebase across four target architectures using a build matrix strategy. This ensures the no_std
crate compiles correctly for both hosted and bare-metal environments.
Target Architecture Matrix
Target | Purpose | Testing |
---|---|---|
x86_64-unknown-linux-gnu | Development and testing | Full unit tests |
x86_64-unknown-none | Bare-metal x86_64 | Build-only |
riscv64gc-unknown-none-elf | Primary RISC-V target | Build-only |
aarch64-unknown-none-softfloat | ARM64 embedded | Build-only |
Build Matrix Flow
flowchart TD MATRIX["Build Matrix Strategy"] NIGHTLY["nightly toolchain"] TARGET1["x86_64-unknown-linux-gnu"] TARGET2["x86_64-unknown-none"] TARGET3["riscv64gc-unknown-none-elf"] TARGET4["aarch64-unknown-none-softfloat"] STEPS["Validation Steps"] FMT["cargo fmt --all --check"] CLIPPY["cargo clippy"] BUILD["cargo build"] TEST["cargo test"] CONDITIONAL["if x86_64-unknown-linux-gnu"] BUILD --> TEST CLIPPY --> BUILD FMT --> CLIPPY MATRIX --> NIGHTLY NIGHTLY --> TARGET1 NIGHTLY --> TARGET2 NIGHTLY --> TARGET3 NIGHTLY --> TARGET4 STEPS --> FMT TARGET1 --> STEPS TARGET2 --> STEPS TARGET3 --> STEPS TARGET4 --> STEPS TEST --> CONDITIONAL
Sources: .github/workflows/ci.yml(L8 - L12) .github/workflows/ci.yml(L22 - L30)
Code Quality Validation Pipeline
The pipeline enforces code quality through multiple validation stages executed for each target architecture.
Validation Stage Details
flowchart TD subgraph subGraph2["Build and Test"] BUILD["cargo build --target TARGET --all-features"] UNITTEST["cargo test --target TARGET -- --nocapture"] end subgraph subGraph1["Quality Checks"] VERSION["rustc --version --verbose"] FORMAT["cargo fmt --all -- --check"] LINT["cargo clippy --target TARGET --all-features"] end subgraph Setup["Setup"] CHECKOUT["actions/checkout@v4"] TOOLCHAIN["dtolnay/rust-toolchain@nightly"] COMPONENTS["rust-src, clippy, rustfmt"] end BUILD --> UNITTEST CHECKOUT --> TOOLCHAIN COMPONENTS --> VERSION FORMAT --> LINT LINT --> BUILD TOOLCHAIN --> COMPONENTS VERSION --> FORMAT
Sources: .github/workflows/ci.yml(L14 - L30)
Clippy Configuration
The pipeline uses specific clippy configuration to suppress false positives while maintaining code quality:
- Allows
clippy::new_without_default
warnings since the RTC driver requires a base address parameter
Sources: .github/workflows/ci.yml(L25)
Documentation Generation and Deployment
The documentation workflow builds API documentation and automatically deploys it to GitHub Pages for the default branch.
Documentation Workflow
flowchart TD DOC_TRIGGER["Documentation Job"] PERMISSIONS["contents: write permissions"] ENV_VARS["Environment Variables"] DEFAULT_BRANCH["default-branch reference"] RUSTDOC_FLAGS["RUSTDOCFLAGS configuration"] BUILD_DOCS["cargo doc --no-deps --all-features"] INDEX_HTML["Generate index.html redirect"] BRANCH_CHECK["Check if default branch"] DEPLOY["JamesIves/github-pages-deploy-action@v4"] GH_PAGES["gh-pages branch"] TARGET_DOC["target/doc folder"] BRANCH_CHECK --> DEPLOY BUILD_DOCS --> INDEX_HTML DEFAULT_BRANCH --> BUILD_DOCS DEPLOY --> GH_PAGES DEPLOY --> TARGET_DOC DOC_TRIGGER --> PERMISSIONS ENV_VARS --> DEFAULT_BRANCH ENV_VARS --> RUSTDOC_FLAGS INDEX_HTML --> BRANCH_CHECK PERMISSIONS --> ENV_VARS RUSTDOC_FLAGS --> BUILD_DOCS
Sources: .github/workflows/ci.yml(L32 - L55)
Documentation Quality Enforcement
The pipeline enforces documentation quality through RUSTDOCFLAGS
:
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
This configuration:
- Treats broken internal documentation links as errors (
-D rustdoc::broken_intra_doc_links
) - Requires documentation for all public items (
-D missing-docs
)
Sources: .github/workflows/ci.yml(L40)
GitHub Pages Deployment Strategy
flowchart TD BUILD["cargo doc"] REDIRECT["index.html redirect generation"] CONDITIONAL["Branch validation"] DEPLOY_ACTION["JamesIves/github-pages-deploy-action@v4"] SINGLE_COMMIT["single-commit: true"] GH_PAGES_BRANCH["branch: gh-pages"] DOC_FOLDER["folder: target/doc"] BUILD --> REDIRECT CONDITIONAL --> DEPLOY_ACTION DEPLOY_ACTION --> DOC_FOLDER DEPLOY_ACTION --> GH_PAGES_BRANCH DEPLOY_ACTION --> SINGLE_COMMIT REDIRECT --> CONDITIONAL
The deployment only occurs for pushes to the default branch, with pull requests receiving build validation but no deployment.
Sources: .github/workflows/ci.yml(L49 - L55)
Pipeline Configuration Summary
Job Execution Environment
Both CI jobs execute on ubuntu-latest
runners with the following characteristics:
- Rust Toolchain: Nightly (required for
no_std
development) - Failure Strategy:
fail-fast: false
allows other matrix jobs to continue if one fails - Required Components:
rust-src
,clippy
,rustfmt
Testing Strategy
The pipeline implements a tiered testing approach:
- Format and Lint: Applied to all targets
- Build Validation: Applied to all targets
- Unit Testing: Limited to
x86_64-unknown-linux-gnu
due to test execution constraints
This strategy ensures cross-platform compatibility while providing comprehensive testing coverage where practically feasible.
Sources: .github/workflows/ci.yml(L1 - L56)
Development Environment Setup
Relevant source files
This document provides comprehensive instructions for setting up a local development environment for the riscv_goldfish RTC driver. It covers toolchain installation, environment configuration, and development workflow procedures necessary for contributing to the codebase.
For information about the CI/CD pipeline and automated testing, see CI/CD Pipeline. For details about target platform configuration and cross-compilation, see Target Platforms and Cross-Compilation.
Prerequisites and Toolchain Requirements
The riscv_goldfish driver requires a specific Rust toolchain configuration to support cross-compilation across multiple embedded targets. The development environment must be capable of building for bare-metal and no_std targets.
Rust Toolchain Setup
The project requires Rust nightly toolchain with specific components and target architectures. The CI pipeline defines the exact requirements that must be replicated locally.
flowchart TD subgraph subGraph2["Build Targets"] X86_LINUX["x86_64-unknown-linux-gnu"] X86_NONE["x86_64-unknown-none"] RISCV["riscv64gc-unknown-none-elf"] ARM64["aarch64-unknown-none-softfloat"] end subgraph subGraph1["Essential Components"] RUST_SRC["rust-src"] CLIPPY["clippy"] RUSTFMT["rustfmt"] end subgraph subGraph0["Rust Toolchain Installation"] NIGHTLY["nightly toolchain"] COMPONENTS["Components Required"] TARGETS["Target Architectures"] end COMPONENTS --> CLIPPY COMPONENTS --> RUSTFMT COMPONENTS --> RUST_SRC NIGHTLY --> COMPONENTS NIGHTLY --> TARGETS TARGETS --> ARM64 TARGETS --> RISCV TARGETS --> X86_LINUX TARGETS --> X86_NONE
Required Toolchain Installation Commands:
Component | Installation Command |
---|---|
Nightly Toolchain | rustup toolchain install nightly |
rust-src Component | rustup component add rust-src --toolchain nightly |
clippy Component | rustup component add clippy --toolchain nightly |
rustfmt Component | rustup component add rustfmt --toolchain nightly |
x86_64-unknown-linux-gnu | rustup target add x86_64-unknown-linux-gnu --toolchain nightly |
x86_64-unknown-none | rustup target add x86_64-unknown-none --toolchain nightly |
riscv64gc-unknown-none-elf | rustup target add riscv64gc-unknown-none-elf --toolchain nightly |
aarch64-unknown-none-softfloat | rustup target add aarch64-unknown-none-softfloat --toolchain nightly |
Sources: .github/workflows/ci.yml(L11 - L19)
Local Development Workflow
The development workflow mirrors the CI pipeline to ensure consistency between local development and automated testing. Each step corresponds to a specific quality gate that must pass before code submission.
flowchart TD subgraph subGraph2["Verification Commands"] VERSION_CHECK["rustc --version --verbose"] DOC_BUILD["cargo doc --no-deps --all-features"] end subgraph subGraph1["Quality Gates"] FMT_PASS["Format Check Pass"] LINT_PASS["Linting Pass"] BUILD_PASS["Build Success"] TEST_PASS["Tests Pass"] end subgraph subGraph0["Development Cycle"] EDIT["Code Editing"] FORMAT["cargo fmt --all --check"] CLIPPY["cargo clippy --target TARGET"] BUILD["cargo build --target TARGET"] TEST["cargo test --target x86_64-unknown-linux-gnu"] end BUILD --> BUILD_PASS BUILD_PASS --> TEST CLIPPY --> LINT_PASS EDIT --> FORMAT FMT_PASS --> CLIPPY FORMAT --> FMT_PASS LINT_PASS --> BUILD TEST --> TEST_PASS TEST_PASS --> DOC_BUILD VERSION_CHECK --> FORMAT
Development Command Reference
Development Task | Command | Target Requirement |
---|---|---|
Format Check | cargo fmt --all -- --check | All |
Auto Format | cargo fmt --all | All |
Linting | cargo clippy --target $TARGET --all-features -- -A clippy::new_without_default | Each target |
Build | cargo build --target $TARGET --all-features | Each target |
Unit Tests | cargo test --target x86_64-unknown-linux-gnu -- --nocapture | x86_64-unknown-linux-gnu only |
Documentation | cargo doc --no-deps --all-features | Any |
Version Check | rustc --version --verbose | Any |
Sources: .github/workflows/ci.yml(L20 - L30) .github/workflows/ci.yml(L44 - L48)
Build Artifacts and Ignored Files
The development environment generates various artifacts and temporary files that are excluded from version control through .gitignore
configuration.
flowchart TD subgraph subGraph2["Build Outputs"] DEBUG["target/debug/"] RELEASE["target/release/"] DOC["target/doc/"] DEPS["target/.rustc_info.json"] end subgraph subGraph1["Generated Artifacts (Ignored)"] TARGET["/target"] VSCODE["/.vscode"] DS_STORE[".DS_Store"] CARGO_LOCK["Cargo.lock"] end subgraph subGraph0["Workspace Structure"] ROOT["Project Root"] SRC["src/"] CARGO_TOML["Cargo.toml"] README["README.md"] end ROOT --> CARGO_LOCK ROOT --> CARGO_TOML ROOT --> DS_STORE ROOT --> README ROOT --> SRC ROOT --> TARGET ROOT --> VSCODE TARGET --> DEBUG TARGET --> DEPS TARGET --> DOC TARGET --> RELEASE
Ignored File Categories
File/Directory | Purpose | Reason for Ignoring |
---|---|---|
/target | Cargo build artifacts | Generated content, platform-specific |
/.vscode | VS Code workspace settings | Editor-specific configuration |
.DS_Store | macOS system metadata | Platform-specific system files |
Cargo.lock | Dependency lock file | Library crate, consumers manage versions |
Sources: .gitignore(L1 - L4)
Documentation Generation and Verification
Local documentation generation follows the same process as the CI pipeline, ensuring documentation quality and link integrity.
flowchart TD subgraph subGraph2["Output Structure"] CRATE_DOC["riscv_goldfish/index.html"] API_DOC["API Documentation"] REDIRECT["Root index.html redirect"] end subgraph subGraph0["Documentation Build Process"] SOURCE["src/lib.rs"] CARGO_DOC["cargo doc --no-deps --all-features"] TARGET_DOC["target/doc/"] INDEX_GEN["Auto-generated index.html"] end subgraph subGraph1["Documentation Flags"] RUSTDOCFLAGS["-D rustdoc::broken_intra_doc_links -D missing-docs"] BROKEN_LINKS["Broken Link Detection"] MISSING_DOCS["Missing Documentation Detection"] end CARGO_DOC --> TARGET_DOC INDEX_GEN --> API_DOC INDEX_GEN --> CRATE_DOC INDEX_GEN --> REDIRECT RUSTDOCFLAGS --> BROKEN_LINKS RUSTDOCFLAGS --> MISSING_DOCS SOURCE --> CARGO_DOC TARGET_DOC --> INDEX_GEN
Local Documentation Commands
Task | Command | Environment Variable |
---|---|---|
Build Documentation | cargo doc --no-deps --all-features | RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs" |
Open Documentation | cargo doc --no-deps --all-features --open | Same as above |
Check Links Only | cargo doc --no-deps | RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links" |
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L44 - L48)
Cross-Target Development Verification
Development workflow must verify functionality across all supported target architectures. This ensures the no_std
compatibility and cross-platform portability requirements are maintained.
Target-Specific Build Matrix
Target Architecture | Build Command | Test Support | Primary Use Case |
---|---|---|---|
x86_64-unknown-linux-gnu | cargo build --target x86_64-unknown-linux-gnu | ✅ Unit tests supported | Development and testing |
x86_64-unknown-none | cargo build --target x86_64-unknown-none | ❌ No std library | Bare metal x86_64 |
riscv64gc-unknown-none-elf | cargo build --target riscv64gc-unknown-none-elf | ❌ Cross-compilation | Primary target platform |
aarch64-unknown-none-softfloat | cargo build --target aarch64-unknown-none-softfloat | ❌ Cross-compilation | ARM64 embedded systems |
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L25 - L30)