MMU support for microkernels
Posted: Thu Sep 26, 2013 5:36 pm
I've been pondering the advantages and disadvantages of microkernels, and one of the things that strikes me is that many of the services traditionally provided by a kernel are best implemented as libraries, and a monolithic kernel does indeed function as a big library or set of libraries, whereas a microkernel, in offloading services into userspace, tends to make full processes out of them, which strikes me as inelegant and creates the context switch overhead that is generally cited (rightly or wrongly) as the primary disadvantage of microkernels.
Now, the reason microkernels tend to implement userspace services as full processes is that the capabilities of most (all?) current MMUs don't provide the capability to isolate services to the degree desirable for a microkernel architecture in any other way. So then the question is "what hardware features does a microkernel need"?
To begin with, I can see at least three types of addressing resources that an OS should differentiate and where hardware support for such differentiation would be incredibly helpful:
1) Thread state (stacks, etc)
2) Code
3) Data sets shared between threads (files, process state, IPC buffers, etc)
The x86 MMU is the only one I know of that allows for differentiation between all three types, but the implementation of the Intel segmentation model leaves much to be desired and the ability to do non-flat address spaces has been abandoned with x86-64. But a microkernel would need something like x86 segmentation or z/Architecture access registers.
One difference from the Intel implementation would be that instead of a segment being determined by an base and a limit in an underlying paged address space, each segment descriptor would point to a page directory for that segment. This would allow page table lookups to be done immediately, rather than requiring that they be deferred until the proper segment base had been added to the offset. Ideally, the descriptor for a segment wouldn't even have to be checked if translation information for an segment:offset pair was cached in the TLB.
Building on this, a really critical feature for microkernels would be a means of preventing unprivileged code from accessing segments it doesn't have access to without leaving those segments completely unmapped and forcing context switches when switching between code that does or doesn't have access to a segment. The way I see this being done is this: the kernel establishes a system-wide segment table, roughly equivalent to the Intel GDT, containing descriptors for every segment on the system. The kernel can load segment registers directly from this table, but user-space programs cannot. Instead, each code or thread state segment descriptor contains a pointer to a "virtual segment table". When a user program performs a segment load, the segment selector it uses is translated using the VST into a selector that points into the global segment table, from which the corresponding descriptor is then loaded. The high bit of the virtual selector used by the program would determine whether the VST used would be that of the current code segment or tha
t of the current thread state segment. Once a segment had been loaded, the translated selctor value would be stored in the segment register instead of
the untranslated value, allowing different programs to use different selector values to load a given segment without invalidating TLB entries corresponding to that segment, and allowing a caller to grant a callee one-time access to one of the caller's segments simply by leaving that segment loaded in
a segment register while making the call.
The goal is to make it so that device drivers and other traditional kernel services can be implemented in userspace and given access to only what they need to do their jobs, as in a microkernel, while being called directly by code that needs to use them without any kind of message passing or process or thread switch, as in a monolithic kernel, and without the involvement of the kernel to the extent possible.
Can anybody think of other hardware features that would be useful for the implementation of a microkernel?
Now, the reason microkernels tend to implement userspace services as full processes is that the capabilities of most (all?) current MMUs don't provide the capability to isolate services to the degree desirable for a microkernel architecture in any other way. So then the question is "what hardware features does a microkernel need"?
To begin with, I can see at least three types of addressing resources that an OS should differentiate and where hardware support for such differentiation would be incredibly helpful:
1) Thread state (stacks, etc)
2) Code
3) Data sets shared between threads (files, process state, IPC buffers, etc)
The x86 MMU is the only one I know of that allows for differentiation between all three types, but the implementation of the Intel segmentation model leaves much to be desired and the ability to do non-flat address spaces has been abandoned with x86-64. But a microkernel would need something like x86 segmentation or z/Architecture access registers.
One difference from the Intel implementation would be that instead of a segment being determined by an base and a limit in an underlying paged address space, each segment descriptor would point to a page directory for that segment. This would allow page table lookups to be done immediately, rather than requiring that they be deferred until the proper segment base had been added to the offset. Ideally, the descriptor for a segment wouldn't even have to be checked if translation information for an segment:offset pair was cached in the TLB.
Building on this, a really critical feature for microkernels would be a means of preventing unprivileged code from accessing segments it doesn't have access to without leaving those segments completely unmapped and forcing context switches when switching between code that does or doesn't have access to a segment. The way I see this being done is this: the kernel establishes a system-wide segment table, roughly equivalent to the Intel GDT, containing descriptors for every segment on the system. The kernel can load segment registers directly from this table, but user-space programs cannot. Instead, each code or thread state segment descriptor contains a pointer to a "virtual segment table". When a user program performs a segment load, the segment selector it uses is translated using the VST into a selector that points into the global segment table, from which the corresponding descriptor is then loaded. The high bit of the virtual selector used by the program would determine whether the VST used would be that of the current code segment or tha
t of the current thread state segment. Once a segment had been loaded, the translated selctor value would be stored in the segment register instead of
the untranslated value, allowing different programs to use different selector values to load a given segment without invalidating TLB entries corresponding to that segment, and allowing a caller to grant a callee one-time access to one of the caller's segments simply by leaving that segment loaded in
a segment register while making the call.
The goal is to make it so that device drivers and other traditional kernel services can be implemented in userspace and given access to only what they need to do their jobs, as in a microkernel, while being called directly by code that needs to use them without any kind of message passing or process or thread switch, as in a monolithic kernel, and without the involvement of the kernel to the extent possible.
Can anybody think of other hardware features that would be useful for the implementation of a microkernel?