embedded-alloc

repository·master·Indexed 19 days ago

https://github.com/rust-embedded/embedded-alloc

A heap allocator for embedded systems designed for no_std Rust applications. It provides a global allocator implementation allowing the use of heap-allocated types like Box and Vec. The crate offers two heap implementation strategies: LlffHeap (Linked List First Fit) and TlsfHeap (Two-Level Segregated Fit). It includes an init! macro for simple setup and a manual .init() method for specific memory control, requiring an implementation of the critical-section crate.

Tokens
2.5K
Snippets
9
Records
12
Agent score
65%

What's inside embedded-alloc

  1. Choose between `LlffHeap` and `TlsfHeap`

    master

    The crate provides two different heap implementation strategies. The choice depends on your specific application requirements:

    • LlffHeap: A Linked List First Fit heap.
    • TlsfHeap: A Two-Level Segregated Fit heap.

    Refer to the project's discussion on heap selection for more detailed performance and behavior comparisons.

  2. Set up `embedded-alloc` as a global allocator

    master

    To use embedded-alloc in a no_std environment, you must define a static instance of a heap type (either LlffHeap or TlsfHeap) and mark it with the #[global_allocator] attribute.

    Important Requirements:

    • You must provide an implementation of the critical-section crate.
    • For Cortex-M CPUs, you can satisfy this requirement by enabling the critical-section-single-core feature in the cortex-m crate.

    After defining the static allocator, you must initialize it before any heap-allocating types (like Box or Vec) are used.

    #![no_std]
    #![no_main]
    
    extern crate alloc;
    
    use cortex_m_rt::entry;
    use embedded_alloc::LlffHeap as Heap;
    
    #[global_allocator]
    static HEAP: Heap = Heap::empty();
    
    #[entry]
    fn main() -> ! {
        // Initialize the allocator BEFORE you use it
        unsafe {
            embedded_alloc::init!(HEAP, 1024);
        }
    
        loop { /* .. */ }
    }
  3. Initialize the heap allocator

    master

    There are two primary ways to initialize the heap:

    1. Using the init! macro

    This is the simplest method. It takes the static allocator instance and the desired heap size in bytes.

    unsafe {
        embedded_alloc::init!(HEAP, 1024);
    }

    2. Manual initialization

    If you need to control exactly where the heap memory resides (e.g., using a specific static array), you can call .init() directly on the heap instance. This requires passing the raw pointer to the memory and the size.

    use core::mem::MaybeUninit;
    const HEAP_SIZE: usize = 1024;
    static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
    
    unsafe {
        HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE)
    }
    #![no_std]
    #![no_main]
    
    extern crate alloc;
    
    use cortex_m_rt::entry;
    use embedded_alloc::LlffHeap as Heap;
    
    #[global_allocator]
    static HEAP: Heap = Heap::empty();
    
    #[entry]
    fn main() -> ! {
        // Method 1: Using the init! macro
        unsafe {
            embedded_alloc::init!(HEAP, 1024);
        }
    
        // Method 2: Manual initialization with specific memory
        {
            use core::mem::MaybeUninit;
            const HEAP_SIZE: usize = 1024;
            static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
            unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
        }
    
        loop { /* .. */ }
    }
  4. Available Heap implementations

    master

    Depending on the enabled features, embedded-alloc provides different heap management algorithms. You can use these as your #[global_allocator].

    • LlffHeap: Available when the llff feature is enabled.
    • TlsfHeap: Available when the tlsf feature is enabled.
  5. Initialize a Heap using `Heap::empty` and `init`

    master

    To use embedded-alloc as a heap, you must first create an uninitialized heap instance using Heap::empty() and then initialize it with a specific memory region using the init method.

    Important Safety and Usage Notes:

    • Initialization Timing: You must call init before any code that performs allocations runs.
    • Memory Direction: The heap grows
    // Example setup pattern
    static HEAP: Heap = Heap::empty();
    
    fn main() {
        // Safety: Ensure start_addr and size represent a valid, unused memory region
        unsafe {
            HEAP.init(0x2000_0000, 64 * 1024);
        }
        // Now you can use the allocator
    }
  6. Create an uninitialized heap with `Heap::empty()`

    master

    Use Heap::empty() to create a new, uninitialized heap allocator. This is a const fn, making it suitable for defining a global allocator instance.

    Note: You must call init on the resulting instance before attempting to allocate any memory.

    pub struct Heap {
        heap: Mutex<RefCell<(LLHeap, bool)>>,
    }
    
    impl Heap {
        pub const fn empty() -> Heap {
            Heap {
                heap: Mutex::new(RefCell::new((LLHeap::empty(), false))),
            }
        }
    }
  7. Monitor heap usage with `used` and `free`

    master

    The Heap struct provides methods to query the current state of memory allocation:

    • used(): Returns the total number of bytes currently allocated by the allocator.
    • free(): Returns the total number of bytes currently available for allocation.

    These methods use a critical section to ensure thread-safe access to the heap state.

    let used_bytes = HEAP.used();
    let free_bytes = HEAP.free();
  8. Initialize the Heap with `init`

    master

    Before using the allocator, you must initialize it using the init method. This method sets up the memory region where the heap will reside.

    Parameters

    • start_addr: The starting address of the heap. The heap grows upwards towards larger addresses.
    • size: The total size of the heap in bytes.

    Memory Bounds

    • The smallest address used is start_addr.
    • The largest address used is start_addr + size - 1.

    Safety and Constraints

    • Safety: You must ensure start_addr points to valid memory and that size is correct.
    • Panics: The function will panic if called more than once or if size == 0.
    • Uniqueness: This must be called exactly once before any code that uses the allocator runs.
    unsafe {
        // Example initialization
        HEAP.init(0x2000_0000, 0x10000);
    }
  9. Initialize the global heap with the init! macro

    master

    The init! macro is used to set up a global heap for use in no_std environments. It creates a static, uninitialized memory buffer of a specified size and initializes the provided heap instance with that buffer.

    Usage Requirements

    • Call Order: This macro must be called before any heap operations occur.
    • Frequency: It must be called exactly once.
    • Safety: It is an unsafe operation because it initializes a static mutable buffer.
    • Size: The $size parameter must be an expression evaluating to a usize greater than zero.

    Parameters

    • $heap:ident: The identifier of the global heap instance (e.g., the name of your #[global_allocator] static).
    • $size:expr: The size of the static memory buffer in bytes.

    Panics

    • If called more than once.
    • If $size is 0.
    use cortex_m_rt::entry;
    use embedded_alloc::LlffHeap as Heap;
    
    #[global_allocator]
    static HEAP: Heap = Heap::empty();
    
    #[entry]
    fn main() -> ! {
        // Initialize the allocator BEFORE you use it
        unsafe {
            embedded_alloc::init!(HEAP, 1024);
        }
        let mut xs = Vec::new();
        // ...
    }
  10. Check heap usage with `used()` and `free()`

    master

    The Heap struct provides methods to estimate the current state of the memory pool:

    • used(): Returns an estimate of the number of bytes currently in use.
    • free(): Returns an estimate of the number of bytes currently available for allocation.
  11. Implement `GlobalAlloc` for a custom heap

    master

    The Heap struct implements the core::alloc::GlobalAlloc trait. This allows you to use the Heap instance as the global allocator for your entire embedded application by using the #[global_allocator] attribute.

    Note: Because init is an unsafe operation that must be called at runtime, you typically declare a static heap and initialize it early in your program's entry point.

    use embedded_alloc::Heap;
    
    #[global_allocator]
    static ALLOCATOR: Heap = Heap::empty();
    
    fn main() {
        unsafe {
            ALLOCATOR.init(0x2000_0000, 64 * 1024);
        }
        // Now standard library/core collections like Box or Vec can work
    }
  12. Use the `allocator_api` feature with `Heap`

    master

    If the allocator_api feature is enabled, Heap implements the core::alloc::Allocator trait. This allows you to use the heap with collections that accept a specific allocator instance (e.g., Vec::new_in(&heap)).

    // Requires feature "allocator_api"
    // use core::alloc::Allocator;
    // let mut vec = Vec::new_in(&HEAP);