MBus messaging IPC
This commit is contained in:
BIN
assets/img/mbus-diagram1.png
Normal file
BIN
assets/img/mbus-diagram1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
153
content/blog/MOP2/mbus-messaging-ipc.adoc
Normal file
153
content/blog/MOP2/mbus-messaging-ipc.adoc
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
= 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?).
|
||||||
Reference in New Issue
Block a user