Dynamic pipe storage using PipeManager { pipes: Mutex<Vec<Option<Mutex<PipeInner>>>>, kobj_ids: Mutex<Vec<Option<ObId>>> } (since v0.41). Each PipeInner contains a boxed ring buffer (Box<[u8; 4096]>), read/write cursors, and reference counts.
Reference-counted: auto-freed when all reader/writer file descriptors are closed. pipe_close() decrements refcount; the buffer is freed when it reaches 0.
- sys_pipe (was RAX 5, REMOVED): Was pipe creation. Replaced by
ob_create(Pipe)(RAX 41) + pipe fd. - sys_close (RAX 23) on a pipe fd: Removes the handle entry, decrements refcount. Frees the pipe when the refcount reaches 0.
- sys_dup2 (RAX 22): Copies an fd with refcount increment. Both old and new fd share the same pipe buffer.
When a pipe buffer is empty: reader sets ThreadState::Blocked { waiting_for: 0xFFFF_0000 | pipe_id }, sets NEED_RESCHED flag, and returns -EAGAIN. The scheduler will not schedule the thread again until the blocking condition is cleared.
After a writer writes data, it calls wake_pipe_readers(pipe_id) which scans the scheduler's thread list for any thread blocked on that pipe and sets them back to Ready state.
pub struct PipeManager {
pipes: Mutex<Vec<Option<Mutex<PipeInner>>>>,
kobj_ids: Mutex<Vec<Option<ObId>>>,
}
impl PipeManager {
pub fn alloc(&self) -> Option<u8>;
pub fn inc_read_ref(&self, pipe_id: u8);
pub fn inc_write_ref(&self, pipe_id: u8);
pub fn dec_read_ref(&self, pipe_id: u8);
pub fn dec_write_ref(&self, pipe_id: u8);
pub fn read(&self, pipe_id: u8, buf: &mut [u8]) -> Result<usize, ()>;
pub fn write(&self, pipe_id: u8, buf: &[u8]) -> Result<usize, ()>;
}
pub fn pipe_peek_read_ready(pipe_id: u8) -> Option<bool>;
pub fn block_current_for_pipe(pipe_id: u8);writereturnsErr(())if the read end is closed or the buffer is full.readreturnsOk(0)(EOF) when the write end is closed and the buffer is empty, orErr(())when no data is available.- Both return the number of bytes transferred on success.
MAX_PIPES = 16; pipe IDs areu8.
Per-EPROCESS HandleTable stores a Vec<HandleEntry>. Grows dynamically; alloc_handle() returns None past 255 entries (fd indices are u8).
HandleEntry is a plain struct (not an enum):
pub struct HandleEntry {
pub object_id: ObId, // 0 = closed; stdin/stdout/stderr sentinels; else ObObject id
pub offset: u64, // per-handle file offset
}Sentinel IDs: HANDLE_CLOSED=0, HANDLE_STDIN=ObId::MAX,
HANDLE_STDOUT=ObId::MAX-1, HANDLE_STDERR=ObId::MAX-2. Pipes are referenced
through their ObObject id, not a dedicated variant.
- FD 0: stdin (Stdin)
- FD 1: stdout (Stdout)
- FD 2: stderr (Stderr)
- FD 3+: allocated by
alloc_handle()via O(n) scan for firstClosedslot
File handles carry a per-open offset field (u64) allowing independent read/write positioning for each fd referencing the same ObObject. sys_read/sys_write advance the offset; sys_ob_set_info(Seek) or ob_query_info can reposition.
sys_exit iterates all HandleEntry values and:
- Closes pipe readers/writers (decrements refcount)
- Calls
ob_closeon ObObject handles
IrpOp (u8 enum):
Read(0),Write(1),Flush(2),Discard(3),Ioctl(4)
IrpStatus (u8 enum):
Pending(0),Completed(1),Error(2)
Irp (#[repr(C)] struct):
op: IrpOp— operation typebuffer: *mut u8— data bufferlength: usize— buffer lengthlba: u64— logical block address (for block devices)count: u64— sector count (for block devices)status: IrpStatus— completion statuscallback: Option<fn(ctx: *mut u8, status: IrpStatus)>— completion callbackctx: *mut u8— opaque callback contextchain_next: Option<IrpId>— chained IRP to dispatch on completionwaiting_pid: u64— PID waiting on this IRP, or 0
64 IRP slots with sequential IDs via AtomicU32. irp_alloc() returns IrpId (1-indexed u32). irp_free(id) returns to pool.
Per-device FIFO ring buffer of 32 entries. Used by block device drivers to queue operations. irp_queue_push(queue, id) enqueues; irp_queue_pop(queue) dequeues for processing.
- Device driver calls
irp_complete(id, status). - Sets
Irp::statustoCompletedorError. - Wakes waiting thread:
waiting_pidmatched against scheduler; thread state restored fromBlockedtoReady. - Dispatches callback via
WORK_QUEUE.push_high(callback_fn, ctx)if callback exists.
irp_complete calls irp_wake_waiter(id) to find and unblock the thread waiting on the IRP.
pub trait BlockDevice {
fn submit_irp(&self, irp_id: IrpId);
fn poll_irp(&self, irp_id: IrpId) -> Option<IrpStatus>;
}5 implementors:
RamDisk— synthetic in-memory diskBootAta— legacy ATA PIO driverAhciDriver— AHCI SATA driverNvmeDriver— NVMe driverNemBlockDevice— NEM module wrapping a block device
11 unit tests covering alloc/free, status transitions, error codes, queue FIFO ordering, queue wraparound, and callback dispatch ordering.
Two-level priority queue:
- High-priority: Processed on syscall return (in
clear_need_resched()), before returning to user mode. - Low-priority: Processed in idle loop, before executing
HLTinstruction.
Lock-free SPSC (Single Producer, Single Consumer) ring buffer with 64 slots per level.
WorkEntry: (fn(*mut u8), *mut u8) — function pointer and opaque data argument.
pending: AtomicBool flag avoids unnecessary processing when queue is empty.
impl WorkQueue {
pub fn push_high(&self, func: WorkFn, data: *mut u8) -> Result<(), ()>;
pub fn push_low(&self, func: WorkFn, data: *mut u8) -> Result<(), ()>;
pub fn process_high(&self) -> usize; // returns count processed
pub fn process_low(&self) -> usize; // returns count processed
}push_* returns Err(()) if queue is full (backpressure handling).
- Syscall return:
clear_need_resched()→ `WORK_QUEUE.process_high() - Idle loop: Before HLT → `WORK_QUEUE.process_low(); WORK_QUEUE.process_high()
- Timer interrupt: Periodic low-priority drain
Used for DPC dispatch, event bus delivery, background I/O completion callbacks.
6 tests: push/pop ordering, FIFO semantics, empty queue, overflow behavior, high/low isolation, pending flag correctness.
#[repr(C)] 56-byte struct with ABI-stable layout for NEM driver compatibility:
| Field | Type | Description |
|---|---|---|
event_id |
u64 | Unique event ID |
event_type |
u32 | Event type constant |
source |
u32 | Event source |
timestamp |
u64 | TSC timestamp |
device_id |
u32 | Device identifier |
driver_target |
u32 | Targeted driver ID |
data0 |
u64 | Payload word 0 |
data1 |
u64 | Payload word 1 |
flags |
u32 | Event flags |
| Constant | Value | Description |
|---|---|---|
TIMER_TICK |
0 | Periodic PIT/HPET timer |
KEYBOARD_INPUT |
1 | PS/2 or USB key event |
SERIAL_DATA |
2 | Serial port data available |
DISK_IO_COMPLETE |
3 | Disk IRP completion |
PROCESS_EXIT |
4 | Process terminated |
DRIVER_LOADED |
5 | NEM driver loaded |
DRIVER_CRASH |
6 | NEM driver fault |
POLICY_VIOLATION |
7 | Security policy check failed |
FS_MOUNTED |
8 | Filesystem mounted |
KEYB_LAYOUT |
9 | Keyboard layout changed |
EVENT_RTC_READ |
10 | RTC read request |
EVENT_RTC_DATA |
11 | RTC data ready |
EVENT_SHUTDOWN |
12 | System shutdown in progress |
EVENT_DRIVER_UNLOAD |
13 | Driver unload request |
EVENT_DRIVER_UNLOAD_ACK |
14 | Driver unload acknowledgment |
EVENT_NMI_WATCHDOG |
15 | NMI watchdog timeout |
EVENT_MOUSE_INPUT |
16 | PS/2 mouse raw bytes |
EVENT_NETWORK_PACKET |
17 | NIC received a packet |
EVENT_PS2_SHUTDOWN |
18 | PS/2 shutdown request |
EVENT_PM_SUSPEND |
19 | Power management suspend |
EVENT_PM_RESUME |
20 | Power management resume |
EVENT_PM_BATTERY |
21 | Battery status change |
EVENT_PM_THERMAL |
22 | Thermal event |
EVENT_PM_DISPLAY |
23 | Display power state |
EVENT_PM_IDLE |
24 | System idle notification |
EVENT_PM_POWER_BUTTON |
25 | Power button pressed |
EVENT_PM_SLEEP_BUTTON |
26 | Sleep button pressed |
EVENT_KEYDOWN |
27 | Key pressed (scancode in data0) |
EVENT_KEYUP |
28 | Key released (scancode in data0) |
EVENT_KEY_CHAR |
29 | Character typed (Unicode codepoint in data0) |
EVENT_KBD_MODIFIER |
30 | Modifier state change (new mods byte in data0) |
EVENT_KBD_REPEAT |
31 | Key repeat event |
EVENT_RTC_WRITE |
32 | RTC write request (kernel → rtc.nem) |
USER |
0x1000+ | User-defined event types |
SOURCE_HAL(0)SOURCE_DRIVER(1)SOURCE_KERNEL(2)SOURCE_USERLAND(3)
Two lock-free SPSC ring buffers:
- High priority: 16 slots (for time-sensitive events like keyboard input)
- Normal priority: 64 slots (for general events like disk completion)
register_handler_v2(filter, callback, name) with EventFilter struct:
pub struct EventFilter {
pub event_type: u16, // exact match, or 0 for any
pub source_mask: u16, // bitmask of SOURCE_* values
pub device_id: u32, // exact match, or 0 for any
}Max 64 handlers. Unregister by callback pointer (unregister_handler(callback)) or by name (unregister_handler_by_name(name)).
push_event_with_dyn_payload() allocates a copy of arbitrary data and stores it as data0/data1 (padded to 16 bytes). Auto-freed after all handlers have been dispatched.
If the target queue is full, push_event() and push_event_with_dyn_payload() return Err(()). The caller must decide retry or drop. No events are silently dropped by the event bus itself.
dispatch_one(): Dispatch a single event from high queue (preferred) or normal queue.dispatch_pending(): Drain all pending events (high first, then normal).
Called from:
clear_need_resched()— high-priority dispatch on syscall return- Idle loop — drain all pending before HLT
- Shell input loop — periodic dispatch during user interaction
TimerTickevents pushed from PIT/HPET timer interrupt handler.KeyboardInputevents pushed from PS/2 IRQ1 handler.- Both use normal priority queue.
17 tests covering: v2 queue operations, subscription filter matching, dynamic payload lifecycle, backpressure when queues full, priority dispatch ordering (high before normal), handler registration/unregistration, and simultaneous multi-handler delivery.