While reading about IPC mechanisms for microkernels, a question came up: would it be possible to exploit the 80x86 ISA in such a way that the problem of modularity is solved in another more effective way?
The problem in monolithic operating systems is that each module sees all other modules all the time. Microkernels solve this problem by completely isolating one module from the other, and using special mechanisms for communication between modules. But microkernels have their own complexity and set of well-known problems.
So how about doing a flat-address 'monolithic' kernel where each module is isolated from the others? on 80x86, it could be done by a trick:
manually changing the base and offset of the CS segment descriptor on an inter-module call.
A good question is why to change the contents of the CS segment descriptor manually when a far jump can do it automatically. Well, for the following reasons:
1) the LDT has not that many entries (perhaps 16K modules are not enough).
2) the CPU's caches are better utilized if the LDT is short.
3) the protection check is avoided.
When changing the CS segment descriptor manually, a problem may come up: the new CS:IP address might be invalid! so in order to avoid this problem, invocation of the inter-module procedure should be performed through another procedure which has the same IP address in both modules!
The pseudo-assembly for the inter-module invocation could be like this:
Code:
SWITCH_MODULE:
mov EAX, <target address> ;load target address, relative to the target module
mov ECS:EDX, <current CS descriptor> ;load current CS descriptor in 64-bit register
mov [<address of CS descriptor>], <target CS descriptor> ;change the CS descriptor
NO_MANS_LAND:
NOP
NOP
...
NOP
RETURN_POINT:
In the target module, invocation resumes with the following code (INVOKE_TARGET and NO_MANS_LAND have the same address relative to the start of each respective module):
Code:
INVOKE_TARGET:
call EAX ;call the target subroutine which saves ECX and EDX
mov [<address of CS descriptor>], ECS:EDX ;return to caller
END_INVOKE_TARGET:
After the last instruction is invoked, execution should continue from address RETURN_POINT in the caller's segment.
The benefits of this trick are:
1) the target module can't randomly invoke other modules. Any access outside the module will result in an exception.
2) the TLB is not flushed (not like when changing CR3).
3) there is no need for a full context switch.
All that it takes for this to work is to invoke the target procedure through another procedure which has a common address between the two modules.
The rest of the registers can be modified accordingly (including the data and stack segments).
What do you think? I would like your advice if it could work.