Skip to content

The #![no_std] Universe Bare Metal

Khi Standard Library không tồn tại

1. What Happens When You Disable std?

Standard Library Architecture

                    RUST LIBRARY HIERARCHY
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                      std (Standard)                     │   │
│   │  ┌───────────┬───────────┬───────────┬───────────────┐  │   │
│   │  │    I/O    │ Threading │ Networking│   OS APIs     │  │   │
│   │  │  println! │  threads  │  TcpStream│   env vars    │  │   │
│   │  └───────────┴───────────┴───────────┴───────────────┘  │   │
│   └─────────────────────────────────────────────────────────┘   │
│                              ↑ Requires OS                      │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                      alloc (Heap)                       │   │
│   │  ┌───────────┬───────────┬───────────┬───────────────┐  │   │
│   │  │    Vec    │   String  │    Box    │      Rc       │  │   │
│   │  │  vec![]   │  "hello"  │  Box::new │   Rc::new     │  │   │
│   │  └───────────┴───────────┴───────────┴───────────────┘  │   │
│   └─────────────────────────────────────────────────────────┘   │
│                              ↑ Requires Allocator               │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                      core (Always)                      │   │
│   │  ┌───────────┬───────────┬───────────┬───────────────┐  │   │
│   │  │  Option   │  Result   │  Iterator │    Slices     │  │   │
│   │  │  Some(x)  │  Ok(x)    │  .map()   │    &[T]       │  │   │
│   │  └───────────┴───────────┴───────────┴───────────────┘  │   │
│   └─────────────────────────────────────────────────────────┘   │
│                              ↑ No dependencies                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Khai báo #![no_std]

rust
// src/main.rs hoặc src/lib.rs

#![no_std]  // Không sử dụng standard library
#![no_main] // Không có main() function thông thường

// Bạn vẫn có core library
use core::panic::PanicInfo;

// Entry point phải được định nghĩa riêng
#[no_mangle]
pub extern "C" fn _start() -> ! {
    // Kernel hoặc firmware entry point
    loop {}
}

// Panic handler bắt buộc phải có
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

Những gì MẤT khi dùng no_std

MấtVì saoThay thế
println!Cần stdout (OS)UART, RTT, custom
Vec, StringCần heap allocatorCung cấp allocator
std::threadCần OS schedulerBare metal scheduling
std::fsCần filesystemCustom FS hoặc không có
std::netCần network stacksmoltcp, custom

Những gì GIỮ từ core

rust
#![no_std]

// Tất cả những thứ này vẫn hoạt động
use core::option::Option;           // Some, None
use core::result::Result;           // Ok, Err
use core::slice;                    // &[T]
use core::str;                      // &str
use core::iter::Iterator;           // .map(), .filter()
use core::mem;                      // size_of, transmute
use core::ptr;                      // read_volatile, write_volatile
use core::sync::atomic::AtomicBool; // Atomic operations

2. Custom Heap Allocator

Tại sao cần Custom Allocator?

                    MEMORY REGIONS IN EMBEDDED
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│   Flash (Read-only)                                             │
│   ┌───────────────────────────────────────────────────────────┐ │
│   │  .text (code)  │  .rodata (constants)  │  .data init     │ │
│   └───────────────────────────────────────────────────────────┘ │
│                                                                 │
│   RAM                                                           │
│   ┌───────────────────────────────────────────────────────────┐ │
│   │    .data     │    .bss     │    HEAP    │     STACK      │ │
│   │  (globals)   │  (zeroed)   │    ↓↓↓     │      ↑↑↑       │ │
│   └───────────────────────────────────────────────────────────┘ │
│                               │             │                   │
│                               └─────────────┘                   │
│                               Managed by Allocator              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Implementing với linked_list_allocator

rust
#![no_std]
#![feature(alloc_error_handler)]

extern crate alloc;

use alloc::vec::Vec;
use linked_list_allocator::LockedHeap;

// Global allocator
#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();

// Khởi tạo heap (gọi một lần khi boot)
pub fn init_heap() {
    // Vùng nhớ dành cho heap
    const HEAP_SIZE: usize = 16 * 1024; // 16 KB
    static mut HEAP_MEMORY: [u8; HEAP_SIZE] = [0; HEAP_SIZE];
    
    unsafe {
        ALLOCATOR.lock().init(
            HEAP_MEMORY.as_ptr() as usize,
            HEAP_SIZE
        );
    }
}

// Alloc error handler
#[alloc_error_handler]
fn alloc_error(_layout: core::alloc::Layout) -> ! {
    panic!("Heap allocation failed!");
}

// Bây giờ có thể dùng Vec, String, Box...
fn use_heap() {
    let mut vec = Vec::new();
    vec.push(1);
    vec.push(2);
    
    let boxed = alloc::boxed::Box::new(42);
}

Simple Bump Allocator (Minimal Implementation)

rust
use core::alloc::{GlobalAlloc, Layout};
use core::cell::UnsafeCell;
use core::ptr;

/// Simple bump allocator - chỉ allocate, không free
pub struct BumpAllocator {
    heap_start: UnsafeCell<usize>,
    heap_end: usize,
    next: UnsafeCell<usize>,
}

unsafe impl Sync for BumpAllocator {}

impl BumpAllocator {
    pub const fn new(start: usize, size: usize) -> Self {
        Self {
            heap_start: UnsafeCell::new(start),
            heap_end: start + size,
            next: UnsafeCell::new(start),
        }
    }
}

unsafe impl GlobalAlloc for BumpAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        let next = self.next.get();
        let alloc_start = (*next + layout.align() - 1) & !(layout.align() - 1);
        let alloc_end = alloc_start + layout.size();
        
        if alloc_end > self.heap_end {
            return ptr::null_mut(); // Out of memory
        }
        
        *next = alloc_end;
        alloc_start as *mut u8
    }
    
    unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
        // Bump allocator không free memory
        // Tất cả memory được giải phóng khi reset
    }
}

3. Panic Handlers

Basic Panic Handler

rust
use core::panic::PanicInfo;

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    // Trong môi trường thực, có thể:
    // 1. Bật LED đỏ
    // 2. Gửi message qua UART
    // 3. Reset device
    // 4. Enter debug mode
    
    // Minimal: infinite loop
    loop {
        // Prevent optimization
        core::hint::spin_loop();
    }
}

Panic Handler với Debug Output

rust
use core::panic::PanicInfo;
use core::fmt::Write;

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    // Giả sử có UART writer
    let mut uart = unsafe { UartWriter::new() };
    
    writeln!(uart, "PANIC!").ok();
    
    if let Some(location) = info.location() {
        writeln!(
            uart,
            "  at {}:{}:{}",
            location.file(),
            location.line(),
            location.column()
        ).ok();
    }
    
    if let Some(message) = info.message() {
        writeln!(uart, "  {}", message).ok();
    }
    
    // Halt
    loop {
        core::hint::spin_loop();
    }
}

Panic với RTT (Real-Time Transfer)

rust
use rtt_target::{rprintln, rtt_init_print};
use core::panic::PanicInfo;

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    rprintln!("PANIC: {}", info);
    loop {}
}

fn main() {
    rtt_init_print!();
    rprintln!("Hello from embedded!");
}

4. Volatile Operations

Tại sao cần Volatile?

                    VOLATILE VS NORMAL ACCESS
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│   Normal Read:                                                  │
│   ─────────────                                                 │
│   let x = *pointer;                                             │
│   let y = *pointer;  // Compiler có thể optimize: y = x         │
│                                                                 │
│   Volatile Read:                                                │
│   ───────────────                                               │
│   let x = read_volatile(pointer);                               │
│   let y = read_volatile(pointer);  // PHẢI đọc lại từ memory    │
│                                                                 │
│   WHY? Hardware registers có thể thay đổi bất cứ lúc nào!       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Volatile Read/Write

rust
use core::ptr::{read_volatile, write_volatile};

// Hardware register address
const GPIO_OUTPUT_REG: *mut u32 = 0x4000_1000 as *mut u32;
const GPIO_INPUT_REG: *const u32 = 0x4000_1004 as *const u32;

/// Đọc GPIO input pins
fn read_gpio() -> u32 {
    // PHẢI dùng volatile vì hardware có thể thay đổi value
    unsafe { read_volatile(GPIO_INPUT_REG) }
}

/// Set GPIO output pins
fn write_gpio(value: u32) {
    // PHẢI dùng volatile để đảm bảo write thực sự xảy ra
    unsafe { write_volatile(GPIO_OUTPUT_REG, value) }
}

/// Toggle LED (bit 0)
fn toggle_led() {
    let current = read_gpio();
    write_gpio(current ^ 0x01);
}

Volatile Wrapper Type

rust
use core::ptr::{read_volatile, write_volatile};

/// Wrapper cho volatile access
#[repr(transparent)]
pub struct Volatile<T: Copy> {
    value: T,
}

impl<T: Copy> Volatile<T> {
    pub fn read(&self) -> T {
        unsafe { read_volatile(&self.value) }
    }
    
    pub fn write(&mut self, value: T) {
        unsafe { write_volatile(&mut self.value, value) }
    }
    
    pub fn update<F>(&mut self, f: F)
    where
        F: FnOnce(T) -> T,
    {
        let value = self.read();
        self.write(f(value));
    }
}

// Usage
#[repr(C)]
struct GpioRegs {
    output: Volatile<u32>,
    input: Volatile<u32>,
    direction: Volatile<u32>,
}

fn configure_gpio() {
    let gpio = unsafe { &mut *(0x4000_1000 as *mut GpioRegs) };
    
    gpio.direction.write(0xFF); // All outputs
    gpio.output.write(0x01);    // LED on
    
    let input = gpio.input.read();
}

5. Linker Scripts Basics

Memory Layout Definition

ld
/* linker.ld */
MEMORY
{
    FLASH : ORIGIN = 0x08000000, LENGTH = 256K
    RAM   : ORIGIN = 0x20000000, LENGTH = 64K
}

SECTIONS
{
    /* Code goes in Flash */
    .text :
    {
        *(.text.reset_handler)  /* Entry point first */
        *(.text*)               /* All other code */
    } > FLASH

    /* Read-only data */
    .rodata :
    {
        *(.rodata*)
    } > FLASH

    /* Initialized data (copy from Flash to RAM) */
    .data :
    {
        _sdata = .;
        *(.data*)
        _edata = .;
    } > RAM AT > FLASH

    /* Zero-initialized data */
    .bss :
    {
        _sbss = .;
        *(.bss*)
        _ebss = .;
    } > RAM

    /* Stack at end of RAM */
    _stack_top = ORIGIN(RAM) + LENGTH(RAM);
}

Rust Build Configuration

toml
# .cargo/config.toml
[target.thumbv7em-none-eabihf]
rustflags = [
    "-C", "link-arg=-Tlinker.ld",
    "-C", "link-arg=--nmagic",
]

[build]
target = "thumbv7em-none-eabihf"

🎯 no_std Checklist

rust
#![no_std]
#![no_main]

// ✅ 1. Panic handler
#[panic_handler]
fn panic(_: &PanicInfo) -> ! { loop {} }

// ✅ 2. Entry point
#[no_mangle]
pub extern "C" fn _start() -> ! { loop {} }

// ✅ 3. (Optional) Allocator cho alloc crate
#[global_allocator]
static ALLOC: MyAllocator = MyAllocator::new();

// ✅ 4. (Optional) Alloc error handler
#[alloc_error_handler]
fn alloc_error(_: Layout) -> ! { panic!() }

💡 DEVELOPMENT TIP

Sử dụng cargo build --release cho embedded — debug builds thường quá lớn cho Flash memory.