Metta
Review
Re: Review of my OS’s microkernel
by berkus on February 3rd, 2009, 1:22 am
I’ll try to depict how my (much more unfinished) microkernel is supposed to work.
First, there is glue code. Glue code is very limited - its task is to switch protection domains. This allows threads to migrate though PD borders and execute in context of other tasks. Glue code with with page directory, other things that might need switching when PD changes and also records thread activations - points visible to the accepting protection domain as thread itself. In the donating domain thread record is marked as waiting IPC reply (IPC can be idempotent, then thread is marked correspondingly as waiting idempotent IPC reply). Glue code works together with Portals - sections of JIT generated code, that performs argument marshalling and request PD switch. Portals are generated by portal manager, which retrieves portal specifications from Interface Definition files. Portal manager is free to perform some specific optimizations, like portal short-circuiting - one famous example of this is getpid() call, which does not really need to cross PD borders and is usually reduced to “mov eax, PID_constant /ret”.
The kernel is just a privileged bit of code that handles interrupts mostly, by forwarding interrupt requests to handler threads. This part is still relatively vague and waits for threads implementation. Kernel does not do any scheduling decisions, this is delegated to user code. Kernel contains a little bit of support for donating cpu time to other threads in case a currently running thread blocks. Threads work by voluntarily donating their time to other threads. This is CPU inheritance scheduling. Some threads act as schedulers for other threads, with minimal help from kernel side.
Device drivers usually run in userspace and are coupled with interrupt handlers - usually implemented inside the same driver and simply waiting on an interrupt semaphore. Drivers communicate using the same portals, generated from interface definition files. Usually there is a rich interface defined, not only get bytes/send bytes but much more semantic information about what is going on and why is being maintained by either a driver or an abstraction level above it. This allows drivers to make much more educated decisions about, for example, read-ahead strategy to use for particular clients. This makes things a bit more complicated to implement, but in reality there will be some sort of more abstract and user-task-oriented metadriver just above more dumb and hardware-oriented “executive drivers”.
There are some system components, like security server, that implement trusted computing base and maintain overall integrity of the system. In case of dynamic reconfiguration and continuous upgrades this is vital. Hotswapping of components would be highly desirable to have, although at this point this is not planned at all.
Sort of coupled with the dynamic reconfiguration is Dynamic Object Loader - a flexible, JIT-enabled system for building components from available building blocks like static or dynamic library files, separate object files or source files. According to requirements it is able to make differently built versions of the same component at different times (or even at the same time, if this is required). It is similar to OMOS object server.
Higher level components make the user interface part. Some handle input events and make them available as signals to gui components, some handle drawing in virtual clipped areas called windows, one component posesses exclusive access to the video hardware, the Compositor, it maintains the scene visibility graph of existing windows, composes them and outputs via graphics card. This is more complex behind the scenes, but since gui part is pretty much far off, i’ll skip it. The applications run inside nested virtual machines. Level of nesting depends on level of control needed and amount of emulation required to be able to run application.
Native applications receive a COM-like interface pointer on startup, called Parent. This interface allows process to inspect environment around it and get access to other available interfaces, like filesystem or memory_manager. On top of this a libc can be built. Legacy applications could possibly run without rebuilding, because dynamic loader is able to replace most of the libc calls with calls to forwarding stubs, unless they were inlined, of course. But that’s not much help to them, really, since the basic filesystem in Metta is not going to be a hierarchical filesystem, but associative with rather different interface to find files.
Applications can use Parent interface to query for different interfaces they need to function. For example, a browser plugin would be looking for some sort of browser_host interface etcetc. Requests for finding other interfaces go through security server, which decides what applications should see what. It is not uncommon for application to request, e.g. a memory_manager interface. It receives it, but in reality, all its memory requests go through logging_debugging_memory_manager and the application is actually being debugged without knowing about it. This can happen for all application interactions with its environment, which effectively puts every application into a highly controlled sandbox, if necessary. Which, for an internet-enabled mobile OS is quite a requirement.