- I should probably break up common:
- "porcelain" goes to move to "mnemos-std"
- common just becomes the "syscall ICD" crate
- Maybe "mnemos-abi"?
- I should probably make "kernel" a library, rather than an application
- This would work towards having separate hardware support
- Progressive step towards this would be to move more of the "kernel" bits into the lib.rs
Adding bbqueue, I'm not sure who should "own" the queue. I guess I might want to have the application own it, so it can allocate an appropriate amount of size. However, for now it's probably simple to let the kernel allocate it, and provide the pointers via the header or some other fixed address. Maybe reconsider this later.
I should wrap the entire Porcelain struct somehow. For now I could always re-take the producer/consumer, but that seems like it could be hairy, though safe.
Maybe not, you'd probably want to do something like:
let block = std::block::open();
not
let std = std::Std::new();
std.block.open();
Oh, syscall messages definitely need to be owned, at least by the time they hit the kernel. Since the kernel doesn't have a heap, it's probably better for the userspace to allocate buffer space, and give ownership to the kernel.
That being said: that feels like it opens up a whole "TOCTOU" kind of thing. Like:
- Userspace requests to open file "/totally/legit/safe.txt"
- Kernel gets the message (as a heap alloc)
- Kernel says "yes okay you can access that file"
- Kernel waits
- Userspace resumes, changes it to "/not/legit/private.txt"
- Kernel runs again, opens "private.txt"
But how to get around this? It seems like my options are:
- Just trust userspace (hmmmm)
- Kernel has it's own heap, does allocations
- What if Kernel OOM?
- Kernel has no heap, but has some kind of fixed max
- Same, what about OOM?
- Kernel has to borrow and create it's own structures
Hmmm.
I guess in the case of OOM, the kernel just replies "busy" and NAKs the request.
Lifetimes in general in the ringbuffer are bad, because the ringbuffer can't continue until the lifetime is extinguished.
Thinking about the kinds of lifetimes I have in the syscall api:
- I have "slice buffers", e.g. data to write to a file, or a receive buffer for file data
- these strongly assume lifetimes
- It's maybe possibe I could still keep these on the stack, but probably subject to the "lol leak" sort of deal.
- It's probably better to make these always be heap allocations. Then a leak just OOMs.
Actually that's really it.
- Serial
- Send/Recv buffers - heap+future
- Blocks
- Read/Write buffers - heap+future
- Info dest buffer - heap+future
- Close !!! name buffer - probably fixed max clone storage?
- System
- Rand fill buffer - heap
It's prooooobably also better to never turn pointers into slices in the kernel. At least not across "yield" boundaries. It's probably fine within one.
Okay, so let's start reworking the heap type. The current one is kind of bad.
The whole <T>
thing was just wrong to begin with.
I think everything that crosses the syscall boundary should just be a u8
array. Everything else has concerns. At some point, it might make sense to have strings too, but let's leave that for now.
#[repr(C)]
struct ArcBytes<const N: usize> {
refcnt: AtomicU32,
// when N == 0, we can use the size to
// get the slice
sz_bytes: u32,
payload: [u8; N],
}
impl<const N: usize> Deref for ArcBytes<N> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.payload.as_slice()
}
}
impl ArcBytes<0> {
fn deref_dyn(&self) -> &[u8] {
unsafe {
core::slice::from_raw_parts(
self.payload.as_ptr(),
self.sz_bytes as usize,
)
}
}
}
// This is the "stable future" item
#[repr(C)]
struct FutureBytes {
refcnt: AtomicU32,
status: AtomicU8,
ex_taken: AtomicBool,
// TODO: Include a "kind" type in case
// We want something OTHER than ArcBytes?
payload: AtomicPtr<ArcBytes<0>>,
}
There COULD be cases where the kernel DOES need to know the layout and alignment of a type. This would be in the case of "send only" data, like serial data.
In this case, we really have two choices:
- The kernel is sent the layout and align data (or - it is only sent specific types?), it sends back a "drop" message, the executor passes it on.
- The userspace executor holds on to 'send only' handles, waits for them to drop, and then passes to the allocator to be dropped