IPC & Syscalls

Channels, event bus, message router, and the typed syscall framework with hooks & validation.

Profile Reference

IPC & Syscalls

The helix-core IPC subsystem provides three communication mechanisms for kernel components. Channels give you typed, bounded message passing. The Event Bus provides pub/sub broadcasting. The Message Router enables point-to-point RPC between named modules. On the userspace boundary, the syscall framework defines how user programs invoke kernel services.

IPC Mechanisms5N · 6E
send(msg)publish(event)request(msg)recv()broadcastroute + respondSenderProcess or module3Channel (MPSC)Bounded async messag…2Event BusPub/sub broadcast2Message RouterPoint-to-point RPC2Receiver(s)Destination module(s…3
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node

Channels

Channels are typed, bounded MPSC (multi-producer, single-consumer) queues. They're the simplest and most efficient way to pass data between kernel components.

core/src/ipc/channel.rs
rust
1
// Bounded MPSC channel — multiple senders, one receiver
4 refs
pub fn channel<T>(capacity: usize) -> (Sender<T>, Receiver<T>);
pub fn default_channel<T>() -> (Sender<T>, Receiver<T>); // capacity = 32
4
5
// Single-value oneshot — for request/response patterns
2 refs
pub fn oneshot<T>() -> (OneShotSender<T>, OneShotReceiver<T>);
7
4 refs
impl<T> Sender<T> {
2 refs
pub fn send(&self, value: T) -> IpcResult<()>; // Non-blocking, error if full
pub fn try_send(&self, value: T) -> IpcResult<()>;
pub fn is_closed(&self) -> bool;
3 refs
pub fn capacity(&self) -> usize;
pub fn len(&self) -> usize;
14
}
15
// Sender is Clone — multiple producers can share it
16
3 refs
impl<T> Receiver<T> {
2 refs
pub fn try_recv(&self) -> IpcResult<T>; // Non-blocking, error if empty
pub fn drain(&self) -> Vec<T>; // Take all pending messages
pub fn is_empty(&self) -> bool;
pub fn close(&self); // Close the channel
22
}
23
24
// Usage example
25
let (tx, rx) = channel::<u64>(16);
26
tx.send(42)?;
27
let value = rx.try_recv()?; // value == 42
Index

Event Bus

The event bus is a publish-subscribe system for broadcasting events across the kernel. Components subscribe to specific topics and receive matching events asynchronously. This is how timer ticks, process lifecycle events, and memory pressure notifications propagate through the system.

core/src/ipc/event_bus.rs
rust
pub enum Event {
2
Tick { timestamp_ns: u64, tick_number: u64 },
3
Shutdown,
4
MemoryPressure { level: MemoryPressureLevel, available_bytes: u64 },
5
CpuHotplug { cpu_id: u32, online: bool },
6
ProcessCreated { pid: u64, parent_pid: u64 },
7
ProcessExited { pid: u64, exit_code: i32 },
8
Interrupt { vector: u8, cpu_id: u32 },
9
Custom { name: String, data: Vec<u8> },
10
}
Index

Subscriptions use a builder pattern with topic filters and priority levels. High-priority subscribers are called first, allowing security hooks to inspect events before they reach normal handlers.

core/src/ipc/event_bus.rs
rust
1
// Subscribe to events
2
let subscription = EventSubscription::new(
3
"my_handler",
4
vec![EventTopic::Timer, EventTopic::Process],
5
Box::new(|event| {
6
match event {
7
Event::ProcessCreated { pid, .. } => {
8
log::info!("New process: {}", pid);
9
EventResponse::Handled
10
}
11
_ => EventResponse::Ignored
12
}
13
})
14
)
15
.with_priority(SubscriptionPriority::High);
16
17
let id = global_event_bus().subscribe(subscription);
18
19
// Publish events — all matching subscribers are notified
20
publish_event(Event::Custom {
21
name: "my.event".into(),
22
data: vec![1, 2, 3],
23
});
24
25
// Unsubscribe when done
26
global_event_bus().unsubscribe(id);

Event Topics

TopicEvents IncludedTypical Subscribers
TimerTick, timer expirationsScheduler, watchdogs, DIS
ProcessProcessCreated, ProcessExitedInit system, NEXUS, audit
MemoryMemoryPressure level changesAllocator, cache, OOM killer
InterruptHardware interrupt notificationsDrivers, profiler
SystemShutdown, state changesAll subsystems

Message Router

The message router provides point-to-point RPC between named kernel modules. Unlike the event bus (broadcast), the router delivers requests to a specific target and returns a response.

core/src/ipc/message_router.rs
rust
3 refs
pub struct Request {
2
from: ModuleAddress,
3
request_type: String,
4
payload: Vec<u8>,
5
priority: MessagePriority,
6
}
7
3 refs
impl Request {
2 refs
pub fn new(from: ModuleAddress, request_type: impl Into<String>) -> Self;
pub fn with_payload(self, payload: Vec<u8>) -> Self;
pub fn with_priority(self, priority: MessagePriority) -> Self;
12
}
13
pub enum Response {
15
Success(Vec<u8>),
16
Rejected(String),
17
Error(String),
18
NotSupported,
19
}
20
21
// Register a module as a message handler
22
global_router().register(module_id, "scheduler", handler_fn)?;
23
24
// Send a request and wait for response
25
let resp = send_to("scheduler", Request::new(
26
ModuleAddress::Id(my_id),
27
"get_stats",
28
))?;
29
30
// Broadcast to all registered modules
31
let results = global_router().broadcast(&request);
Index

Syscall Framework

The syscall framework defines the ABI boundary between userspace and kernel. Every system call is a numbered handler that receives raw register values and returns a result.

Syscall Pipeline6N · 5E
syscall instrkernel modevalidatedresultUser ProcessCalls syscall instru…1helix_syscall_entryKernel syscall entry…2Pre-HooksValidation & securit…2Dispatch (handler)Execute syscall hand…2Post-HooksPost-processing2Return to UserRestore user context1
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node
core/src/syscall/mod.rs
rust
pub type SyscallNumber = u64;
pub const MAX_SYSCALL_ARGS: usize = 6;
3
4 refs
pub struct SyscallArgs {
5
pub args: [u64; 6],
6
pub count: usize, // Number of valid arguments
7
}
8
4 refs
impl SyscallArgs {
pub fn get(&self, index: usize) -> Option<u64>;
pub fn as_ptr(&self, index: usize) -> Option<*const u8>;
pub fn as_mut_ptr(&self, index: usize) -> Option<*mut u8>;
13
}
14
2 refs
pub enum SyscallReturn {
16
Success(u64),
17
Error(SyscallError),
18
}
19
pub trait SyscallHandler: Send + Sync {
fn handle(&self, args: &SyscallArgs) -> SyscallReturn;
fn name(&self) -> &'static str;
fn arg_count(&self) -> usize;
fn validate(&self, _args: &SyscallArgs) -> Result<(), SyscallError> { Ok(()) }
25
}
Index

Defining Syscalls

The define_syscall! macro is the fastest way to register a new system call. It generates the handler struct, validation, and registration boilerplate.

core/src/syscall/macros.rs
rust
1
// Macro shorthand
2
define_syscall!(AddSyscall, 42, 2, |args| {
3
let a = args.get(0).unwrap();
4
let b = args.get(1).unwrap();
5
SyscallReturn::Success(a + b)
6
});
7
8
// Register in the syscall table
9
register_syscall(42, Arc::new(AddSyscall))?;
10
11
// Or register manually with a custom handler
12
syscall_registry().register(42, Arc::new(MyHandler))?;

Syscall Hooks & Validation

Hooks let you add security auditing or tracing to every syscall without modifying individual handlers. The validation builder ensures user pointers are safe before dereferencing.

core/src/syscall/validation.rs
rust
1
// Pre/post hooks — for security, auditing, tracing
pub trait SyscallHook: Send + Sync {
fn pre_syscall(&self, number: SyscallNumber, args: &SyscallArgs,
4
context: &SyscallContext) -> Option<SyscallReturn> { None }
fn post_syscall(&self, number: SyscallNumber, args: &SyscallArgs,
6
context: &SyscallContext, result: &SyscallReturn) {}
7
}
8
9
dispatcher.add_pre_hook(Arc::new(SecurityHook));
10
dispatcher.add_post_hook(Arc::new(AuditHook));
11
12
// Argument validation builder
13
ArgValidator::new()
14
.ptr(user_ptr, 64, false) // Read-only pointer, 64 bytes
15
.fd(fd_value) // Valid file descriptor
16
.flags(flags, ALLOWED_MASK) // Only allowed bits set
17
.range(value, 0, 100) // Value in range
18
.check()?;
Index

The syscall gateway (helix_syscall_entry) is an extern "C" function with #[no_mangle] — the raw ABI entry point that receives register values from the syscall instruction, dispatches through the registry, and returns the result. All user pointer validation happens before any kernel memory is touched.