How to go to microkernel from monolith.
How to go to microkernel from monolith.
I wrote a basic kernel https://github.com/JustVic/melisa_kernel. It has paging, interrupts, processes, userspace, and filesystem(something around fat12), demo shell. All this was implemented in the simplest and most basic way. Currently I writing simple ata driver. But how to go to microkernel from this? I'm not a very smart person. I don't know where to start. If possible, try to describe how one can try to implement a microkernel in some steps. Or how are you trying to go to the microkernel. Tell us your experience if you have.
-
- Member
- Posts: 426
- Joined: Tue Apr 03, 2018 2:44 am
Re: How to go to microkernel from monolith.
A microkernel is, in essence, a basic kernel, that provides very little other than mechanisms on which further policy can be built.JustVic wrote:I wrote a basic kernel https://github.com/JustVic/melisa_kernel. It has paging, interrupts, processes, userspace, and filesystem(something around fat12), demo shell. All this was implemented in the simplest and most basic way. Currently I writing simple ata driver. But how to go to microkernel from this? I'm not a very smart person. I don't know where to start. If possible, try to describe how one can try to implement a microkernel in some steps. Or how are you trying to go to the microkernel. Tell us your experience if you have.
In OS context, policy are things like:
- User space API (POSIX, Win32 etc.)
- Filesystem drivers
- Device drivers.
Whereas mechanism are things like:
- Syscall mechanism provided by the CPU
- Interrupt handling.
- MMU programming and abstraction
- Thread context switching and synchronisation.
I've not looked at your kernel, but I'd be surprised if you had much beyond the latter mechanism layer. You have a FAT based filesystem in the kernel, but chances are you'll need at least a small, simple filesystem in the kernel just to bootstrap the system, else you'd be relying on GRUB to load all your root device drivers and filesystem as modules (not impossible, and very much how something like Windows bootstraps.)
You'll need to decide how micro you want your microkernel to be. Some microkernels provide very little in the way of mechanism in kernel space, basically providing only the facilities that cannot be provided by a user space module, such as direct access to hardware ports, some CPU registers such as the MMU. All device drivers are user space, and communicate with the hardware by way of message passing via kernel system calls. Systems such as L4 and Minix have a tiny user/kernel system call interface, basically limited to sending/receiving messages, with functionality like paging achieved by sending messages from the kernel page fault handler to a pager user level process to provide the desired page as the message response. Everything is communicated in terms of messages and/or shared memory (for big messages like mass memory transfers)
Similarly, an OS API is build on top of messages, so a POSIX client application would invoke a POSIX system call by crafting an IPC message to the POSIX server, which itself would be a user level process that handles the message and responds in a manner consistent with the POSIX interface. For example, an open system call would encode the file name to open, the mode to use and any flags into an IPC message. It would then call the microkernel to send the IPC message to the POSIX server (which might have a well known rendezvous port identifier to send messages to), and the POSIX server would respond with an IPC message containing the resulting file handle that the POSIX client uses for I/O in the future.
An obvious side effect of this is that handling a synchronous system call like open, necessarily results in a task switch from the POSIX client to the POSIX server, and then back again with the response. This clearly has performance implications, and the consequences of these performance issues, real or perceived, has hindered adoption of such pure microkernels.
A more hybrid approach, which seems to be the model used in Windows for one, is to have the OS personality in kernel space, rather than as a full user space server process. Messages are still used to invoke system calls, but process level context switch is skipped, and the IPC message handled internally to the kernel. This has the benefit of reducing the overhead of task and especially address space switching, while keeping the conceptual isolation between the client and the server (the server still works in messages, and doesn't directly manipulate the client process except via IPC responses and calls to the microkernel.)
Whether using a pure or hybrid microkernel, or even a largely/mostly monolithic kernel, it can still be useful to provide a user level interface to providing OS level services, such as filesystems and device drivers. You retain a kernel side proxy, which marshals internal filesystem or device independent requests into messages that are sent to user space processes to handle, which respond to the kernel which unmarshals the required data from the response. The kernel essentially remains filesystem and device agnostic, and leaves specific policy to user space processes. This is essentially how FUSE filesystems work, and means you can remove functionality piecemeal from your kernel. The added benefit is the fast path of things like page fault resolution can remain entirely in the kernel, if your VFS caches mapped file information in a filesystem independent manner, and you only switch to user mode servers when you're already resigned to slow device IO to resolve the page fault (in which case the context switch is not that significant.)
TL; DR
If your kernel is already simple and provides little in functionality in kernel space, define yourself a FUSE and device driver kernel/user space message interface, and extend functionality in user space. Separate your system call interface into mechanism (the trap mechanism) and format (format of messages) and have system calls handled in a message passing/response manner.