Guides#

pw_kernel: An experimental kernel for embedded systems

Note

This is an early draft. The content may change significantly over the next few months.

This section provides guides for specific features and tools within pw_kernel.

Unit testing#

pw_kernel includes a lightweight unit testing framework designed for both bare-metal and kernel-aware tests. This framework is crucial for ensuring the correctness and reliability of kernel primitives and application code.

Writing a test#

Tests are written as standard Rust functions annotated with the #[test] attribute from the unittest crate. The test function should return a unittest::Result<()>.

Here’s a simple example of a bare-metal test:

// in your_module/lib.rs or your_module/tests.rs
#[cfg(test)]
mod tests {
    use unittest::test;

    fn add(a: u32, b: u32) -> u32 {
        a + b
    }

    #[test]
    fn test_addition() -> unittest::Result<()> {
        unittest::assert_eq!(add(2, 2), 4);
        unittest::assert_ne!(add(2, 2), 5);
        unittest::assert_true!(add(1,1) == 2);
        Ok(())
    }
}

Assertions#

The unittest crate provides several assertion macros:

  • unittest::assert_eq!(a, b): Asserts that a is equal to b.

  • unittest::assert_ne!(a, b): Asserts that a is not equal to b.

  • unittest::assert_true!(expr): Asserts that expr evaluates to true.

  • unittest::assert_false!(expr): Asserts that expr evaluates to false.

Kernel-aware tests#

For tests that require kernel services (e.g., testing scheduler behavior, mutexes, or timers), you can mark them as needing the kernel by passing the needs_kernel argument to the #[test] attribute:

use kernel::sync::mutex::Mutex;
use unittest::test;

static MY_MUTEX: Mutex<u32> = Mutex::new(0);

#[test(needs_kernel)]
fn test_mutex_locking() -> unittest::Result<()> {
    let guard = MY_MUTEX.lock();
    unittest::assert_eq!(*guard, 0);
    // guard is dropped here, unlocking the mutex
    Ok(())
}

Running tests#

Tests are typically run via Bazel. See Run tests.

The test runner executes all discovered tests and reports their status. Bare-metal tests are run first, followed by kernel-aware tests if the kernel is initialized.

Panic detector#

pw_kernel includes a tool called panic detector to statically analyze a compiled Rust binary (ELF file) to identify all potential panic locations. This is crucial for ensuring reliability and can significantly reduce code size by eliminating panic-handling overhead.

For detailed instructions on how to integrate and use this tool, see panic_detector.

Intrusive linked lists#

pw_kernel provides a highly efficient and safe intrusive linked list implementation in //pw_kernel/lib/list. This is a fundamental data structure used throughout the kernel, particularly in the scheduler for managing threads in run queues and wait queues.

Example usage#

use kernel::lib::list::{self, Link, ForeignList, ForeignBox};
use core::ptr::NonNull;

// 1. Define your struct with an embedded `Link`.
struct MyListItem {
    data: u32,
    list_link: Link, // The intrusive link
}

// 2. Define an adapter using the `define_adapter!` macro.
//    This connects `MyListItem` and its `list_link` field to the list logic.
list::define_adapter!(MyListItemAdapter => MyListItem.list_link);

fn main_example() {
    // Create a list that can hold `MyListItem`s.
    let mut my_list = ForeignList::<MyListItem, MyListItemAdapter>::new();

    // Create some items. In a real scenario, these might be ForeignBox::new_from_ptr
    // from statically allocated buffers or a dedicated allocator.
    // For simplicity, we'll imagine they are correctly managed ForeignBox instances.
    // Note: ForeignBox requires that the underlying memory is valid for its
    // entire lifetime and that it is not deallocated by other means.
    let mut item1_storage = MyListItem { data: 10, list_link: Link::new() };
    let item1 = unsafe { ForeignBox::new_from_ptr(&mut item1_storage) };

    let mut item2_storage = MyListItem { data: 20, list_link: Link::new() };
    let item2 = unsafe { ForeignBox::new_from_ptr(&mut item2_storage) };

    // Add items to the list.
    my_list.push_back(item1);
    my_list.push_front(item2); // item2 is now at the head.

    // Iterate and access items (ForeignList provides safe iteration).
    my_list.for_each(|item| {
        // pw_log::info!("Item data: {}", item.data);
        Ok::<(), ()>(()) // Placeholder Ok for the closure
    }).unwrap();

    // Pop an item.
    if let Some(popped_item) = my_list.pop_head() {
        // pw_log::info!("Popped item data: {}", popped_item.data);
        // IMPORTANT: The popped_item (a ForeignBox) must be consumed
        // or it will panic on drop, ensuring ownership is handled.
        popped_item.consume();
    }

    // Clean up remaining items
    while let Some(item) = my_list.pop_head() {
        item.consume();
    }
}

Considerations#

  • ForeignBox: The ForeignBox type is a smart pointer that ensures the underlying memory is valid for its entire lifetime. It is crucial to call consume() on a ForeignBox when it is no longer needed to prevent panics on drop.

  • UnsafeList is unsafe: Requires careful handling to prevent dangling pointers or double-frees if not using ForeignList.

  • Single List Membership: An item can only be part of one list at a time using a single Link member.

The intrusive list is a powerful tool for performance-critical data structures within the kernel. You’ll see it used in //pw_kernel/kernel/scheduler.rs for managing thread queues.