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 thata
is equal tob
.unittest::assert_ne!(a, b)
: Asserts thata
is not equal tob
.unittest::assert_true!(expr)
: Asserts thatexpr
evaluates to true.unittest::assert_false!(expr)
: Asserts thatexpr
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 callconsume()
on aForeignBox
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 usingForeignList
.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.