= MBus - in-kernel messaging system Kamil Kowalczyk 2025-11-12 :jbake-type: post :jbake-tags: MOP2 osdev :jbake-status: published In this article I would like to present to you `MBus` - a kernel-space messaging IPC mechanism for the MOP2 operating system. == One-to-many messaging MBus is a one-to-many messaging system. This means that there's one sender/publisher and many readers/subscribers. Think of a YouTube channel - a person posts a video and their subscribers get a push notification that there's content to consume. image::/img/mbus-diagram1.png["One-to-many messaging diagram"] == User-space API This is the user-space API for MBus. The application can create a message bus for `objmax` messages of `objsize`. Message buses are indentified via a global string ID, for eg. the PS/2 keyboard driver uses ID "ps2kb". `ipc_mbusattch` and `ipc_mbusdttch` are used for attaching/detattching a consumer to/from the message bus. [source,c] ---- int32_t ipc_mbusmake(char *name, size_t objsize, size_t objmax); int32_t ipc_mbusdelete(char *name); int32_t ipc_mbuspublish(char *name, const uint8_t *const buffer); int32_t ipc_mbusconsume(char *name, uint8_t *const buffer); int32_t ipc_mbusattch(char *name); int32_t ipc_mbusdttch(char *name); ---- === Usage The usage of MBus can be found for eg. inside of TB - the MOP2's shell: .Initalizing interactive shell mode [source,c] ---- if (CONFIG.mode == MODE_INTERACTIVE) { ipc_mbusattch("ps2kb"); do_mode_interactive(); // ... ---- .Reading key presses [source,c] ---- // ... int32_t read = ipc_mbusconsume("ps2kb", &b); if (read > 0) { switch (b) { case C('C'): case 0xE9: uprintf("\n"); goto begin; break; case C('L'): uprintf(ANSIQ_CUR_SET(0, 0)); uprintf(ANSIQ_SCR_CLR_ALL); goto begin; break; } // ... ---- Previously reading the keyboard was done in a quite ugly manner via specialized functions of the `ps2kbdev` device object (`DEV_PS2KBDEV_ATTCHCONS` and `DEV_PS2KBDEV_READCH`). It was a one big hack, but the MBus API has turned out quite nicely ;). With the new MBus API, the PS/2 keyboard driver becomes way cleaner than before (you can dig through the commit history...). .Kernel-side code for the PS/2 keyboard driver [source,c] ---- // ... IpcMBus *PS2KB_MBUS; void ps2kbdev_intr(void) { int32_t c = ps2kb_intr(); if (c >= 0) { uint8_t b = c; ipc_mbuspublish("ps2kb", &b); } } void ps2kbdev_init(void) { intr_attchhandler(&ps2kbdev_intr, INTR_IRQBASE+1); Dev *ps2kbdev; HSHTB_ALLOC(DEVTABLE.devs, ident, "ps2kbdev", ps2kbdev); spinlock_init(&ps2kbdev->spinlock); PS2KB_MBUS = ipc_mbusmake("ps2kb", 1, 0x100); } ---- The messaging logic is ~20 lines of code now. == The tricky part The trickiest part to figure out while implementing MBus was to implement cleaning up dangling/dead consumers. In the current model, a message bus doesn't really know if a consumer has died without explicitly detattching itself from the bus. This is solved by going through each message bus and it's corresponding consumers and deleting the ones that aren't in the list of currently running processes. This operation is ran every cycle of the scheduler - you could say it's a form of garbage collection. All of this is implemented inside `ipc_mbustick`: [source,c] ---- void ipc_mbustick(void) { spinlock_acquire(&IPC_MBUSES.spinlock); // Go through all message buses for (size_t i = 0; i < LEN(IPC_MBUSES.mbuses); i++) { IpcMBus *mbus = &IPC_MBUSES.mbuses[i]; // Skip unused slots if (mbus->_hshtbstate != HSHTB_TAKEN) { continue; } IpcMBusCons *cons, *constmp; spinlock_acquire(&mbus->spinlock); // Go through every consumer of this message bus LL_FOREACH_SAFE(mbus->consumers, cons, constmp) { spinlock_acquire(&PROCS.spinlock); Proc *proc = NULL; LL_FINDPROP(PROCS.procs, proc, pid, cons->pid); spinlock_release(&PROCS.spinlock); // If not on the list of processes, purge! if (proc == NULL) { LL_REMOVE(mbus->consumers, cons); dlfree(cons->rbuf.buffer); dlfree(cons); } } spinlock_release(&mbus->spinlock); } spinlock_release(&IPC_MBUSES.spinlock); } ---- As you can see it's a quite heavy operation and thus not ideal - but still way ahead of what we had before. I guess the next step would be to figure out a way to optimize this further, although the system doesn't seem to take a noticable hit in performance (maybe do some benchmarks in the future?).