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) :
Capbitflags 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
Capbitflags 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 fnfor compile-time evaluation - Returns
trueif all requested capability bits are present - Returns
falseif 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 fnoptimization - Error Handling: Returns
Noneon 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_accessfor consistency - Zero-Cost Abstractions:
const fndeclarations 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
unsafeblock, 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:
Capbitflags 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
Cappermission 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_defaultdue to theWithCap::newdesign 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 testonx86_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, andrustfmtcomponents - Failure Strategy:
fail-fast: falseallows all targets to complete even if one fails - Testing Scope: Unit tests only run on
x86_64-unknown-linux-gnudue 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
DerefandDerefMut - 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
'alifetime ensures the guard cannot outlive the lock - Raw pointer storage: Direct
*mut Taccess 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
BaseGuardtrait 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::Statefield 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 returnsfalseforce_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 = truesrc/base.rs(L134)is_locked()method: SMP version loads atomic state src/base.rs(L113) single-core version returns constantfalsesrc/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
PAUSEinstruction, reducing power consumption - ARM: May use
YIELDor 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 -- --checkensures consistent code style .github/workflows/ci.yml(L24) - Linting:
cargo hack clippy --target ${{ matrix.targets }} --each-feature -- -D warningscatches potential issues .github/workflows/ci.yml(L26) - Building:
cargo hack build --target ${{ matrix.targets }} --each-featurevalidates compilation .github/workflows/ci.yml(L28) - Testing:
cargo hack test --target ${{ matrix.targets }} --each-featureruns 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:
BaseSpinLockincludes anAtomicBoolstate field- Lock operations use
compare_exchangeatomic operations - Full memory ordering constraints are enforced
- Spinning behavior is implemented for contention scenarios
When the smp feature is disabled:
BaseSpinLockhas 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, andNoPreemptIrqSaveguard 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:
--checkflag ensures CI fails if code is not properly formatted - Scope: All files (
--allflag)
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 buildviacargo-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
--nocapturefor 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: truekeeps 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: falseallows 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)
smpfeature 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
preemptfeature 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_preemptinNoPreempt::acquire() - src/lib.rs(L158 - L159) -
enable_preemptinNoPreempt::release() - src/lib.rs(L167 - L168) -
disable_preemptinNoPreemptIrqSave::acquire() - src/lib.rs(L176 - L177) -
enable_preemptinNoPreemptIrqSave::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
IrqSaveandNoPreemptIrqSaveguards call architecture-specific functions through the unifiedarch::interface - Feature Gates: The
cfg_ifdependency 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:
IrqSaveguard for interrupt-only protectionNoPreemptIrqSaveguard 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
pushfinstruction to push EFLAGS onto the stack - Uses
popinstruction to retrieve EFLAGS value into a register - Uses
cliinstruction 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
stiinstruction to set the Interrupt Flag if interrupts should be enabled - Uses
cliinstruction 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:
IrqSaveguard callslocal_irq_save_and_disable()on creation andlocal_irq_restore()on dropNoPreemptIrqSaveguard 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_BITin thesstatusregister - 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
flagsparameter in thesstatusregister - Uses
x0as 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_BITfor compile-time constant bit mask- All operations are marked
unsafedue 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}, 0x0clears the IE bit and returns the old CSR value - Restore Operation:
csrxchg {flags}, {IE_MASK}, 0x0sets the IE bit based on the flags parameter
The instruction syntax uses:
inout(reg) flagsfor read-modify-write of the flags variablein(reg) IE_MASKfor the bit mask input0x0as 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 docto 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:
NoPreemptcalls the interface in bothacquire()andrelease()methodsNoPreemptIrqSavecalls 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-docsflag requires all public items to be documented - Link Validation:
-D rustdoc::broken_intra_doc_linksprevents broken documentation links - Automatic Indexing: Dynamic index.html generation using
cargo treeoutput .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:
preemptfor 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 <= noware 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 aTimeValuedeadline 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 clippywith 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
rustfmtstandards - Lint Compliance: Must pass
clippyanalysis 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.htmlredirect 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
docjob requirescontents: writepermission 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 clippyfor 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.rsaffect the main API - Package Configuration: Changes to
Cargo.tomlaffect crate metadata and dependencies - CI Configuration: Changes to
.github/workflows/affect automated testing - Documentation: Changes to
README.mdaffect 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_stdcompatibility: 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 theHeapAllocatorvariant
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_addrmust be page-aligned (4096-byte boundary)\n-heap_sizemust be at least0x8000bytes (32KB)\n-heap_sizemust be a multiple of0x8000bytes\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 least0x8000bytes (32KB)\n-heap_sizemust be a multiple of0x8000` 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_addrmust be page-aligned (4096-byte boundary)heap_sizemust 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:
ptrmust 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-pagesbranch 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
Fromimplementations 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:
SIZEparameter 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 fmtandcargo 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_addrandpage_table_entrycrates 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/SYSRETinstructions - 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:
swapgsswitches 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_handlerwith the trap frame pointer - Context Restoration: Restores all registers and user context
- Return: Uses
sysretqto 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-simdfeature is enabled, saves current FP state and restores next task's FP state - Page Table Switching: If
uspacefeature is enabled and page tables differ, updatesttbr0_el1and flushes TLB - Register Context: Calls assembly
context_switchfunction 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::USERfor 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::USERfor 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_EL1to theexception_vector_basesymbol, which contains the exception handlers - User Space Protection: Sets
TTBR0_EL1to 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
STRmacro to store callee-saved registers from CPU to current task's context - Restore Phase: Uses
LDRmacro 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
uspacefeature
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
tpregister value before switching - Restoring Next State: Writing the next task's
tpvalue 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_basesymbol 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
usizefor 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- Usesmovfcsr2grto read FCSR andst.wto storeRESTORE_FCSR- Usesld.wto load andmovgr2fcsrto 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 = 0for unified exception entry - Sets
EENTRYto 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$tpvalue usingmoveinstructionwrite_thread_pointer()- Updates$tpregister 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.FPEenable_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 insstatussrc/riscv/uspace.rs(L19 - L29) - AArch64: Configures
SPSR_EL1withEL0tmode and appropriate interrupt masking src/aarch64/uspace.rs(L26 - L32) - LoongArch64: Sets
PPLV_UMODE(user privilege level) andPIE(previous interrupt enable) inprmdsrc/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
sscratchto store kernel stack pointer for trap handling src/riscv/uspace.rs(L79) - Sets
sepcwith user entry point src/riscv/uspace.rs(L80) - Executes
sretinstruction to return to user mode src/riscv/uspace.rs(L96)
AArch64 Implementation:
- Sets
sp_el0for user stack,elr_el1for entry point,spsr_el1for processor state src/aarch64/uspace.rs(L86 - L88) - Uses
eretinstruction to return to user mode src/aarch64/uspace.rs(L105)
LoongArch64 Implementation:
- Configures
eraregister with user entry point src/loongarch64/uspace.rs(L74) - Uses
ertninstruction 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
sscratchCSR 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_addrcratePageFaultFlags- Memory access flags frompage_table_entrycrate
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 --featuresto 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-registersv0.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 addfor 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) andWeakreferences 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_vfshas no dependencies on other filesystem crates in the workspace- Both
axfs_devfsandaxfs_ramfsdepend onaxfs_vfsto implement its interfaces - Application code can use any or all of these crates, typically interacting through the
axfs_vfsabstractions
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: ImplementsVfsOpsfor device filesDirNode: ImplementsVfsNodeOpsfor 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
lookupmethod inVfsNodeOpstrait - 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
VfsNodeOpsandVfsOps - 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 implementingVfsNodeOpsVfsNodeType: 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_devfsimplements the VFS interface to provide device files (/dev/null, /dev/zero, etc.)axfs_ramfsimplements the VFS interface to provide a RAM-based file system- Both implementations create their own node types that implement the
VfsNodeOpstrait
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
VfsOpstrait 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
lookupon 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
RwLockto 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
createandremovemethods 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
ZeroDevinstance - 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
DeviceFileSysteminstance - 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
BaseSchedulertrait 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 theVecDequesrc/round_robin.rs(L92 - L94)CFScheduler: Removes and returns the task with minimum virtual runtime fromBTreeMapsrc/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:
FifoScheduleruseslinked_list_r4l::Listfor optimal insertion/removal performanceRRSchedulerusesVecDequefor simple front/back operations but suffers from O(n) arbitrary removalCFSchedulerusesBTreeMapkeyed 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_slicefor preemption-safe time tracking src/round_robin.rs(L10 - L13)CFSTask:init_vruntime,delta,nice, andidfor 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
usizetask payload withFifoSchedulerandFifoTask - Round Robin:
usizepayload with time slice constant5forRRScheduler<usize, 5>andRRTask<usize, 5> - CFS:
usizepayload withCFSchedulerandCFSTaskfor 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-pagesbranch 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-gnutarget 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-gnuwith full testing support - Bare Metal x86:
x86_64-unknown-nonefor bootloader and kernel development - RISC-V Embedded:
riscv64gc-unknown-none-elffor RISC-V microcontrollers - ARM64 Embedded:
aarch64-unknown-none-softfloatfor 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 -- --checkensures consistent code style - Linting:
cargo clippywith custom configuration excludingnew_without_defaultwarnings - 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-pagesbranch
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_stdembedded 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
#docattributes
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.datamatchesData::Struct(strct)pattern at src/lib.rs(L13) - Field Type Check: Verifies
strct.fieldsmatchesFields::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 completeDeriveInputcontaining struct metadatastrct: Reference to theDataStructcontaining 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
implblock 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!withmutkeyword - 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
0and 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:
$itembecomes the field variable name you specify$tuplebecomes the tuple instance name you provide$codebecomes 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_eachand_enumeratesuffixes - 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:
BuddyByteAllocatorprovides good balance of performance and memory efficiency - Object-oriented systems with many same-sized allocations:
SlabByteAllocatorexcels at allocating uniform objects - Real-time systems:
TlsfByteAllocatoroffers deterministic allocation times - Systems requiring large contiguous memory blocks:
BitmapPageAllocatorefficiently 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_SIZEmust 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_SIZEtoMAX_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 = Noneinit(start, size)creates the underlyingHeapinstance
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
rlsfimplementation constraints
Integration Requirements
The allocator requires:
- Feature gate
tlsfto be enabled inCargo.toml - External dependency on
rlsfcrate version 0.2 - Unsafe operations for memory pool management
'staticlifetime 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::Allocatortrait - Thread-safe usage through
RefCellinterior 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
fullfeature is automatically enabled for development builds - Benchmarking:
criterionwith HTML reports for performance testing - Randomization:
randwithsmall_rngfeature 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
Allocatortrait - 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
Dropimplementation
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
rustfmtformatting checks - All code must pass
clippylinting withall-featuresenabled - 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-featuresenabled - 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
PhysAddrandVirtAddrwrappers 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:
MemorySetcollections for managing multiple memory areasMemoryAreastructures representing individual mapped regionsMappingBackendtrait 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:
Debugtrait with custom format stringLowerHexandUpperHextraits
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_SIZEis a power of 2 - Start Address Alignment: Verifies
startis aligned toPAGE_SIZE - End Address Alignment: Verifies
endis 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
startwithend - Value Return: If within bounds, return current
startvalue - Advance: Increment
startbyPAGE_SIZEusing theaddmethod - Termination: Return
Nonewhenstartreaches 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_sizebytes - Updates
va_range.startusingwrapping_addfor overflow safety - Returns
MappingError::BadStateif unmapping fails
split Implementation Details
- Validates that
posis within the area boundaries (exclusive) - Creates new MemoryArea for the right portion using
backend.clone() - Updates original area's end to
pos - Returns
Noneif 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:
boolindicating 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:
boolindicating 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:
boolindicating 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
Copytrait requirement - Backend sharing is possible through the
Clonerequirement
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 implementingDefaulttrait - 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
--nocaptureflag to display all output during CI execution - Target Restriction: Unit tests only execute on
x86_64-unknown-linux-gnudue 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_addressparameter 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_stdenvironments - 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
Rtcstruct 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 = truein Cargo.toml(L15) - No Default Features: chrono is imported with
default-features = falseto maintain minimal footprint - Feature Gate: The
dep:chronosyntax 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
u32viatry_into()may fail for out-of-range values - Methods return
Result<(), TryFromIntError>to propagate conversion errors - Core driver methods remain infallible for valid
u32inputs
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 = falseto 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 = falsefor 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-gnufor 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
rustfmtusing default settings
cargo fmt --all -- --check
- Linting: Code must pass
clippyanalysis 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
unsafefunctions 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
unsafecode 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-gnuwith--nocapturefor 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
chronoallow 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-featuresflag for complete analysis - Suppresses
clippy::new_without_defaultwarnings - 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-gnuonly - Command:
cargo test --target x86_64-unknown-linux-gnu -- --nocapture - Output: Verbose test output with
--nocaptureflag
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_interfacepreserves trait visibility: src/lib.rs(L38) src/lib.rs(L67)impl_interfacepreserves 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
&selfparameter 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::Fnitems - 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
SimpleIftrait - Module
__SimpleIf_modwith extern declarations - Functions
__SimpleIf_fooand__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
selfparameter from extern function signatures while preserving other parameters - Export Generation: Creates
#[export_name]attributes following the__TraitName_methodNameconvention - 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
&selffrom 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_interfacedeclarations - 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
synv2.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::TokenStreamfor all internal processing - Error Reporting: Leverage
syn::Errorfor 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:
/targetdirectory containing compilation outputs - Editor Configuration:
.vscodedirectory for development environment settings - System Files:
.DS_Storefor macOS filesystem metadata - Lock Files:
Cargo.lockwhich 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 returns456instead 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 clippyacross all target architectures - Compilation: Verifies successful builds for all target platforms
- Unit testing: Runs tests only on
x86_64-unknown-linux-gnuwith 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
RUSTDOCFLAGSto 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: trueto 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-stdenvironments 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
Nonerather 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
LazyInitinstance
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:
Sendis implemented whenT: Send, allowing transfer of ownership across threadsSyncis 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::Acquireload - 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-stdcompatible 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 -- --checkensures consistent code style - Linting:
cargo clippy --target ${{ matrix.targets }} --all-featureswith specific allowances forclippy::new_without_default - Compilation:
cargo build --target ${{ matrix.targets }} --all-featuresvalidates cross-platform compatibility - Testing:
cargo test --target ${{ matrix.targets }}runs only onx86_64-unknown-linux-gnutarget
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-docstreats documentation warnings as errors- Automated index page generation using
cargo treeoutput 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: truefor clean deployment historybranch: gh-pagesas the deployment targetfolder: target/doccontaining 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 nodeGetLinkstrait: Provides access to a type's embeddedLinks<T>instanceRawList<G>: Implements core unsafe linked list operationsList<G>(linked_list.rs): Safe wrapper managing ownership viaWrappertraitList<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
GetLinkstrait 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
GetLinksimplementations - 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
IteratorwithItem = &'a G::EntryType - Implements
DoubleEndedIteratorfor 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
IteratorandDoubleEndedIteratortraits - 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 + SyncwhenG::EntryType: Send + SyncLinks<T>isSend + SyncwhenT: 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 theLinksstruct 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 inUnsafeCellfor 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
insertedflag 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
headto 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
insertedfromfalsetotrue - Insertion during removal: Removal always succeeds once a node is inserted
- Memory ordering:
Acquireon insertion synchronizes withReleaseon 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
RawListoperations 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
nextpointers until reaching the head again - Backward movement: Follow
prevpointers 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: AnAtomicBooltracking 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 withAcquireorderingrelease_after_removal()usesReleaseordering 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 == trueis 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_stdcompatibility - 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
NonNullpointer provides guaranteed non-null access - Register operations through
tock_registersare 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
icrregister to0x7ffto clear all pending interrupts - FIFO Configuration: Sets
iflsregister to0for 1/8 RX FIFO and 1/8 TX FIFO trigger levels - Interrupt Enabling: Sets
imscregister bit 4 to enable receive interrupts (rxim) - UART Activation: Sets
crregister 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,Noneif 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
0x7ffto 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
SendandSyncfor multi-threaded environments - Uses
NonNullfor memory safety guarantees - Requires
unsafeimplementations 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:
Pl011Uartinstance ready for initialization
Safety:
- Marked as
const fnfor 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
frregister 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 (
frregister 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 (
misregister) - Checks receive interrupt bit (bit 4)
Sources: src/pl011.rs(L93 - L97)
ack_interrupts(&mut self)
Clears all pending UART interrupts.
Behavior:
- Writes
0x7ffto 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
0x7ffto clear all pending interrupts - FIFO Configuration: Sets IFLS register to
0for 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
Pl011UartRegsprovides 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
&selfmethods for reading status - Exclusive mutable access: Only one thread should have
&mutaccess 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
Pl011Uartinstance - 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-gnufor 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-pagesbranch 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_exchangeprevents race conditions during registration - Memory ordering:
Acquireordering ensures proper synchronization - Bounded unsafe code:
unsafetransmutation 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
trueif registration succeeds - Returns
falseif a handler is already registered at that index
Execution
handle(idx: usize)- Invokes the handler registered at the specified index- Returns
trueif a handler was found and executed - Returns
falseif 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
Noneif 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
falserather 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
AtomicUsizearray for lock-free operation - Stores function pointers as
usizevalues 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
Nslots to zero usingAtomicUsize::new(0) - Can be used in
constcontexts 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
0tohandler as usize - Uses
Ordering::Acquirefor success andOrdering::Relaxedfor 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
0usingOrdering::Acquire - If previous value was non-zero, transmutes back to
Handler - Returns
Noneif 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
Handlerand 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
usizeon the target platform - Function addresses remain valid for the lifetime of the
HandlerTable - The transmutation between
usizeandfn()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_stdcompatibility - 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
usizehave 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-docsdefault-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
rustfmtstandards - Lint Compliance: No
clippywarnings 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:
RUSTDOCFLAGSset to-D rustdoc::broken_intra_doc_links -D missing-docsto enforce documentation standards - Build Command:
cargo doc --no-deps --all-featuresto generate comprehensive API documentation - Index Generation: Automatic creation of a redirect
index.htmlpointing 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: writefor 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:
Frepresents memory flags (must implementCopy)PTrepresents the page table typeBrepresents 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
MappingBackendtrait 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
Cloneto support memory area splitting operations - Generic Flexibility: The
Ftype parameter allows different flag representations (e.g., bitfields, enums) - Page Table Agnostic: The
Ptype 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 usingVirtAddrRangeflags: Stores memory access permissions of typeFbackend: 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::BadStateon 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::BadStateon 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.startby 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.endby 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 < endto prevent empty areas - Left Area: Original area with
endupdated topos - Right Area: New area created with
backend.clone() - Return Value:
Some(new_area)on success,Noneon 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: Copyrequirement 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
MemoryAreawith 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=trueto 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
arceosfor 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- ForBTreeMapcollection 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
BTreeMapwhich 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-stdandalloc - 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 -- --checkto ensure consistent code formatting - Linting: Runs
cargo clippywith 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
--nocaptureflag
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-gnuwhere standard library features are available - Build Verification: All bare metal targets are compiled to ensure cross-platform compatibility
- Mock Testing: The crate includes
MockBackendimplementation 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_linksfails build on broken documentation links - Missing Documentation:
-D missing-docsrequires 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
u128division operations are required during multiplication - Bit-shift operations replace expensive division
- Precision is preserved through careful selection of
multandshiftvalues - 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 = 32and decreases untilmultfits inu32src/lib.rs(L63 - L71) - Precision Optimization: Uses
(numerator << shift + denominator/2) / denominatorfor 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
u128intermediate 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
shiftuntilmultfits inu32::MAX - The
+ denominator/2term provides rounding during the division
- Factor Reduction Phase src/lib.rs(L73 - L76) :
- Removes common factors of 2 from
multandshift - Continues while
multis even andshift > 0 - Optimizes for the smallest possible
multvalue
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 == 0andnumerator != 0 - Zero Numerator src/lib.rs(L53 - L60) : Returns a ratio with
mult = 0, shift = 0for any0/xcase - Zero Ratio src/lib.rs(L37 - L44) : Special
zero()constructor creates0/0ratio 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
denominatoris zero andnumeratoris non-zero - Returns a zero ratio if
numeratoris zero - Calculates optimal
multandshiftvalues 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) / denominatorshiftis 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 = 32to use all available bits - Constraint enforcement: Ensure
mult ≤ u32::MAXto 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::MAXanddenominator = 1, the result ismult = u32::MAX, shift = 0 - Minimum precision: When
numerator = 1anddenominator = 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:
nightlychannel - 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--nocaptureflag 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 testonx86_64-unknown-linux-gnutarget - Format Compliance: Execute
cargo fmt --all -- --check - Lint Compliance: Run
cargo clippy --all-featuresfor each target - Build Verification: Confirm
cargo buildsucceeds 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
CAPobjects, whereCAPis 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 capacityCAPis 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 capacityCAP` 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::errortrait or panic handling - Core library only: Restricted to
core::memand similar basic utilities
Memory Safety Mechanisms
MaybeUninit<T>wrapper: Safely handles uninitialized memory slots- Bitmap synchronization: Ensures
id_bitmapstate 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 capacityCAPmust 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
bitmapscrate (v3.2): ProvidesBitmap<CAP>implementationcore::mem: ProvidesMaybeUninitfor 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:
CAPmust not exceed 1024 (enforced at compile time)- Function is
const, enabling compile-time initialization - Zero-initializes the
id_bitmapsafely 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 < CAPand!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 returnsNonewithout 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
Nonefor 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
falseforid >= 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 thebitmapscrate - 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
Bitmaptype - 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 constructingTinstances 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:
0toCAP - 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 < CAPand!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
Nonefor 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
CAPlimit
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
getorget_mut - Availability checking before
add_atoperations - 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) == trueif and only ifobjects[i]contains an initialized valuecountequals 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) == trueif and only ifobjects[i]is initialized - Count Consistency:
countequalsid_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 onCAPand 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| Returnsfalse|\n|add_at(id, value)|id >= CAP| ReturnsErr(value)|\n|add_or_replace_at(id, value)|id >= CAP| ReturnsErr(None)|\n|get(id)/get_mut(id)| Viais_assigned(id)| ReturnsNone` |\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
FlattenObjectsin 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 withoutDefaulttrait - 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 -- --checkvalidates 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_stdenvironments have limited testing infrastructure- The
--nocaptureflag 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_defaultis allowed since the crate provides specialized constructors - Target-Specific: Clippy runs against each target platform independently
- Feature Complete:
--all-featuresensures 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-depsfocuses 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.2provides stable bitmap operations - Feature configuration:
default-features = falseensuresno_stdcompatibility - 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
corelibrary 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_Storesystem 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_RANGEconstant 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_RANGEconstant 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_RANGEconstant 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
0xffrepresents 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 fmtwith the--checkflag to ensure consistent code formatting - Linting: Runs
cargo clippywith all features enabled, specifically allowing theclippy::new_without_defaultlint - Building: Compiles the crate for each target architecture with all features enabled
- Testing: Executes unit tests, but only for the
x86_64-unknown-linux-gnutarget
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_linkstreats broken documentation links as errors - Missing Documentation:
-D missing-docsrequires 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-pagesbranch - Single Commit: Uses
single-commit: trueto maintain a clean deployment history - Source Folder: Deploys the
target/docdirectory 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.tomlor 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-macro2infrastructure - 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:
Configstruct for configuration representationConfigValueandConfigTypefor type-safe value handlingtoml_editintegration 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
.oldextension (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, andConfigValuetypes in axconfig-gen/src/config.rs and axconfig-gen/src/value.rs - Type System: Extending
ConfigTypefunctionality 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-genfor 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, andConfigabstractions - 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
ConfigTypeenum in axconfig-gen/src/ty.rs - Update
ConfigValuehandling 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-genfirst - Update
axconfig-macrosto 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-sysand related platform-specific crates for cross-platform CLI functionality - String Processing:
unicode-ident,memchr,utf8parsefor robust text handling - Data Structures:
hashbrown,indexmapfor 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:
clap4.x series with stable API - TOML processing:
toml_edit0.22.x with advanced editing capabilities - Macro ecosystem:
proc-macro2,quote,syn1.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:
x86crate 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-cpucrate for ARM-specific functionality - Special Features: EL2 privilege level support via
arm-el2feature
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:
riscvcrate v0.12 with default features disabled - Target Support: Both
riscv32andriscv64target 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 docfor 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_entrycrate 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_64targets and documentation builds page_table_multiarch/Cargo.toml(L20 - L21) - riscv dependency: Included for
riscv32/riscv64targets 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:
GenericPTEtrait defining unified page table entry operationsMappingFlagsbitflags 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_64crate for hardware-specific page table flag definitions - AArch64: Uses
aarch64-cpucrate 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:
LEVELSdefines the number of translation levels - Address space limits:
PA_MAX_BITSandVA_MAX_BITSspecify 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
MappingFlagsautomatically getsPTF::PRESENT - Execute permission is inverted: absence of
EXECUTEsetsNO_EXECUTE - Both
DEVICEandUNCACHEDflags result in cache-disabled memory - Table entries always get
PRESENT | WRITABLE | USER_ACCESSIBLEregardless 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 asMappingFlagsset_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-srcfor cross-compilation,clippyfor linting,rustfmtfor 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
cfgattributes 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_attrannotations
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
u64values - 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_NMIflag 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: writefor 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
.gitignorepatterns 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 withNoPreemptGuardintegrationarm-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_guardintegrationarm-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-stdembedded 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_BASEMSR 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_EL1by default,TPIDR_EL2whenarm-el2feature is enabled - Requires
mrs/msrinstructions 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
gpregister instead of standard thread pointer (tp) - Supports both 32-bit and 64-bit variants
- Uses
lui/addiinstruction 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
$r21general-purpose register for base addressing - Supports 64-bit architecture (LoongArch64)
- Uses
lu12i.w/oriinstruction 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
gpregister, 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:
preemptfeature: AddsNoPreemptGuardto safe methodssp-naivefeature: Uses global variables instead of per-CPU registersarm-el2feature: UsesTPIDR_EL2instead ofTPIDR_EL1on 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_EL1register - With arm-el2: Uses
TPIDR_EL2register
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_INITatomic 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_startsymbol - 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_BASEstatic variable - Uses
Oncesynchronization 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_BASEfor per-CPU base pointer - Requires special handling for Linux userspace via
arch_prctlsyscall - Maintains
SELF_PTRvariable 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-el2feature) - 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/addiinstruction pairs for address calculation - Supports direct load/store with calculated offsets
LoongArch64 Specifics
- Uses general-purpose register
$r21by convention - Native instruction support with
lu12i.w/orifor 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.percpusection 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 includesNoPreemptGuardin safe methodsarm-el2: TargetsTPIDR_EL2register 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
.percpusection 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_EL2instead ofTPIDR_EL1on AArch64sp-naive: Uses alternative implementation (see Naive Implementation)preempt: Integrates withkernel_guardfor 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
preemptfeature 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 usizeinstead 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()preemptandarm-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
preemptfeature
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.percpusection 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 -- --checkto 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
BaseDriverOpsfor 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
DeviceTypevariant
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
Debugtrait 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
NetBufPoolfor 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
BaseDriverOpsfor 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 anEthernetAddressstructcan_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
DevResultandDevErrortypes - 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 (
ramdiskfeature)
- In-memory block storage for testing and temporary storage
- No external dependencies
- Useful for development and system testing
- BCM2835 SDHCI (
bcm2835-sdhcifeature)
- Hardware driver for BCM2835 (Raspberry Pi) SD card interface
- Depends on external
bcm2835-sdhcicrate - 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
DevResultfor consistent error handling - Device Identification: Drivers report
DeviceType::Blockfor proper categorization - Polymorphism: All implementations can be used through
dyn BlockDriverOpstrait 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
DevResultfor 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
DevResultfor 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
falsefor 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
DisplayInfowith 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
QSreceive 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
NetBufPoolwith2 * QStotal 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-srccomponent for no-std targets - Component Dependencies:
clippyandrustfmtfor 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_stdcompatible by default- Optional
allocfeature 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_stdcompatibility 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:
alloccrate - Enabled via theallocfeature 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_reserveto 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::iocompatibility - 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
memcpyoverhead - Exact reading:
read_exact()method with proper error handling for insufficient data - Feature-gated extensions:
read_to_end()method available only withallocfeature
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
axerrnoerror 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 thatamtbytes 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 typeRpos: 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
WouldBlockerrors differently from fatal errors - Memory Safety: Wrap allocation errors with appropriate context
- Data Validation: Validate UTF-8 and format correctness with
InvalidDataerrors
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-featuresto 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 fmtwith default settings - Linting: All
clippywarnings must be addressed, with the exception ofclippy::new_without_defaultwhich 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-gnutarget due to standard library requirements .github/workflows/ci.yml(L29 - L30) - Compilation Tests: All targets must compile successfully to ensure
no_stdcompatibility - Feature Testing: All tests run with
--all-featuresto 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_stdenvironments
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_stdtest 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_linkstreats broken documentation links as errors - Missing Documentation:
-D missing-docsrequires 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-pagesbranch for GitHub Pages hosting - Deployment Mode: Single commit to maintain clean history
- Content Source:
target/docdirectory 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 enablesalloccrate 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 allocto 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_addresspoints 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
Rtcinstance 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_stdenvironment 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
u32hardware format andu64application 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_stdtargetsclippy: 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 fmtformatting checks - All
clippylints 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_defaultwarnings 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_stddevelopment) - Failure Strategy:
fail-fast: falseallows 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-gnudue 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)