TLDR; May read the example first. Condition: no_std, no heap
I want to implement a linked list in my kernel to maintain the raw memory, which ranges from 0x80020000 to 0x88000000. More specifically, when booting, the kernel first spilts the raw memory into a bunch of fix-sized(4KB) frames and then use a linked list to record the frames.
The kernel use this list to alloc and dealloc the frames and it only records the free frames, i.e., the linked list is a free list. Since the number of frames is large, I would like to reuse the memory of the free frames.
C code illustration:
struct frame {
struct frame *next;
}
static struct frame *freelist;
void *alloc() {/* pop a frame in the head of the freelist */}
void free(void *) {/* push the frame in the head of the freelist */}
In terms of Rust code, I first intend to use:
struct Frame {
next: Option<&mut Frame>
}
but then with this struct, I don't know how to somehow generate a new ownership of Frame from raw pointers. So I use this:
What the kernel does here is it uses Frame::new to convert from usize(like 0x80020000, 0x80021000, 0x80022000, ...) to *mut Frame and push it into the freelist.
But with this implementation, something weird happens when the kernel is initializing the free list. Aftering some debugging, I find that the kernel can just push a little frame into the freelist successfully. I don't know what happens exactly here, but it seems that something weird happens on variable with type *mut Frame in the free function.
/* simplified, originally wrapped in mutex in my kernel */
static mut free_list: Frame = Frame{ next: None };
pub fn free(addr: usize) {
let frame: *mut Frame = Frame::from(addr);
unsafe {
let next = free_list.take_next();
(*frame).set(next);
free_list.set(Some(frame));
}
}
While you say you aren't using the heap, the way your kernel has split memory sounds a lot like what an allocator would do internally. You may want to check out the Heap Allocation chapter from Philipp Oppermann's excellent Writing an OS in Rust series.
It doesn't directly answer your question about why your free() function isn't working correctly, but those links might give you a better high-level understanding of what you're trying to do.
If you don't mind you may also check my simple and lightweight allocator implementation for a RaspberryPi bare-metal OS. This also uses a single linked list to refer the diffferent allocated and free memory buckets... Checkout here: ruspiro-allocator
This might give further ideas to help you getting to your solution
Doing something so low-level is very easy to mess up; it's already hard to do right in C, but it is actually worse in Rust! Indeed, you'll be tempted to use &mut references instead of all that Cell verbiage, and that very easily leads to unsound code, given the uniqueness (no aliasing) invariant of &mut references, and how such invariant does not play well with the linked lists pattern (c.f., the .windows(2) initialization of the list; that pattern cannot be using &muts since the window[0] frame is aliased by the window[0].next of the previous iteration).
May I ask you a question?
We explicitly use 'kernel here to constrain that these two references have same lifetime, so that we can to some extent verify that this chunk is previously allocated by the kernel.
Is my understanding correct?
That was my intent, yes, but it's actually counter-productive in that it is actually not the case and thus gives a false sense of security. That's why I edited and removed those lifetime annotations in the post here (but forgot to update the playground).
Why is it not the case? Well, Rust happily shrinks a &'long [mut] reference into a &'short [mut] one, and also recursively so (it can shrink a KernelChunk<'long> into a KernelChunk<'short>; the fancy / savy way of saying this is "KernelChunk is covariant w.r.t its lifetime parameter")
So if we had:
// this is the `free` from your example
fn free_strict<'common> (
this: &'common Kernel,
chunk: KernelChunk<'common>,
)
// and a
fn free_apparently_loose<'kernel, 'chunk> (
this: &'kernel Kernel,
chunk: KernelChunk<'chunk>,
)
// (<=> `free_apparently_loose(this: &'_ Kernel, chunk: KernelChunk<'_>`)
then it turns out it is legal for free_apparently_loose to call free_strict: