Unsafe Code Conventions
Kernite is written in Rust 2024 edition with #![no_std] and no alloc crate.
Unsafe code is unavoidable in a kernel (hardware access, raw pointer manipulation, inline assembly), but every unsafe operation follows strict documentation conventions.
SAFETY Comments
Every unsafe {} block must carry a // SAFETY: comment immediately above or inside the block explaining the invariant that makes the operation sound:
// SAFETY: COM1 is a standard x86 serial port; outb to 0x3F8 is
// always valid in ring 0.
unsafe {
arch::outb(SERIAL_PORT, c);
}
The comment answers: "Why is this not undefined behavior?" It must cite the specific invariant, not restate the operation:
| Bad | Good |
|---|---|
|
|
|
|
|
|
Unsafe Functions
Every unsafe fn must carry a # Safety documentation section listing caller obligations:
/// Release object (called by delete_capability)
///
/// Decrements reference count and destroys object if it reaches zero.
///
/// # Safety
/// `obj` must be a valid pointer to a KernelObject.
pub unsafe fn release_object(obj: *mut KernelObject, obj_type: ObjectType) {
// ...
}
The # Safety section answers: "What must the caller guarantee for this call to be sound?"
Rust 2024 Edition Rules
Kernite uses --edition=2024. Three rules affect unsafe code:
unsafe_op_in_unsafe_fn
Every unsafe operation inside an unsafe fn requires an explicit unsafe {} block.
The function signature alone does not permit unsafe operations:
// Correct (Rust 2024):
pub unsafe fn init_slots(num_slots: usize) {
unsafe {
core::ptr::write_bytes(bitmap_virt, 0, bitmap_words);
}
}
// Wrong (compiles in Rust 2021, warning in 2024):
pub unsafe fn init_slots(num_slots: usize) {
core::ptr::write_bytes(bitmap_virt, 0, bitmap_words); // no unsafe block
}
No static mut References
Taking & or &mut of a static mut is disallowed.
Use core::ptr::addr_of! / addr_of_mut! for raw pointer access:
// Correct:
static mut SLOT_STATE: SlotArrayState = SlotArrayState { ... };
unsafe {
let state = &mut *(&raw mut SLOT_STATE);
state.num_slots = num_slots;
}
// Wrong:
unsafe {
SLOT_STATE.num_slots = num_slots; // implicit &mut of static mut
}
unsafe extern Blocks
Items declared in extern blocks require explicit unsafe or safe annotation:
unsafe extern "C" {
static _text_start: u8; // accessing requires unsafe
safe fn memset(...); // marked safe, no unsafe needed to call
}
RPIT Lifetime Capture
In edition 2024, → impl Trait return types capture all in-scope lifetimes by default.
Use + use<'a> to narrow the captured set when the impl should not capture every in-scope lifetime:
// Captures only 'a, not 'b (edition 2024):
fn iter_entries<'a, 'b>(buf: &'a [u8], key: &'b str) -> impl Iterator<Item = &'a u8> + use<'a> {
buf.iter()
}
This is relevant for unsafe patterns involving iterator or raw pointer lifetimes, where capturing an extra lifetime would extend the validity window beyond what is sound.
#[repr©] Requirements
All structures that cross boundaries must use #[repr©]:
- FFI boundaries
-
Any struct passed to or from C code or assembly.
- Assembly interop
-
Register save areas, context switch frames, IPC buffers.
- Hardware-defined layouts
-
Page table entries, GDT/IDT entries, APIC registers.
- Pointer casts
-
Any struct where
*mut Specificis cast to*mut KernelObject(theKernelObjectheader must be the first field).
#[repr(C)]
pub struct Endpoint {
pub header: KernelObject, // MUST be first for refcount via pointer cast
// ...
}
// Safe because header is at offset 0:
let obj = endpoint_ptr as *mut KernelObject;
unsafe { (*obj).ref_count.fetch_sub(1, AcqRel) };
#[unsafe(no_mangle)]
Functions called from assembly use #[unsafe(no_mangle)] (Rust 2024 syntax):
#[unsafe(no_mangle)]
pub unsafe extern "C" fn fastpath_call_rust(
cap_ptr: u64, msg_info: u64,
mr0: u64, mr1: u64, mr2: u64, mr3: u64,
) -> FastpathResult {
// ...
}
The unsafe in #[unsafe(no_mangle)] acknowledges that the attribute has safety implications (symbol name collisions, ABI requirements).
Atomic Ordering Conventions
| Pattern | Ordering |
|---|---|
Reference count increment/decrement |
|
Reference count read |
|
Spinlock acquire |
|
Spinlock release |
|
Notification bits |
|
|
|
Common Unsafe Patterns
Pointer-to-Reference Conversion
// SAFETY: obj is validated non-null above, and kernel objects are
// never freed (untyped lifetime), so the pointer remains valid.
let endpoint = unsafe { &mut *(obj as *mut Endpoint) };
Related Pages
-
Architecture — safety model overview
-
Lock Ordering — IRQ save/restore and lock hierarchy