The design of the ES pure component kernel
ES operating system has a pure component operating system kernel, which meta-processes system calls and upcalls defined in OMG IDL interface to join software components running in userland including our own ECMAScript interpreter esjs. In many microkernel researches, they tends to try to build a simplified, decoupled UNIX implementation on top of them, which is not a goal in the ES operating system. Even though many principal posix functions can be used from ES user processes, they are just for importing the highly portable, existing open source libraries like Cairo, FreeType, etc. The primary goal has been making it possible that developers can easily create rich applications, like 3D games, using ECMAScript. So we've been designing and developing new, simple interface definitions for file I/O, socket, threads, name spaces, etc. using IDL in a way that can be used from ECMAScript.
Note: Please See the summary of the guest talk at the XV Semana Informática do Instituto Superior Técnico for the concept behind the ES operating system design.
Introduction
The ES kernel has some resemblances to Sun's CORBA-based Spring microkernel [
Hamilton]
, and Microsoft's COM-based MMLite and Rialto kernels [
Draves]
. The ES kernel only handles object APIs defined in IDL like Railto, and it allows exchanging object pointers (or interface pointers in COM terminology) among user processes. Each interface pointer acts like an capability associated with a specific object interface. However, unlike those older microkernels, the ES kernel is fully written in C++ in more portable manner following the recent standardization effort of the C++ programing language and the C++ ABI. For example, a C++ language exception with an integer error code triggered in a server process is correctly reported back to the callee in the client process as an ordinary C++ exception in ES.
Unlike most other microkernels, a message passing is not a primitive operation in the ES kernel; RPC stubs are not used at all. Instead, the ES kernel collects the metadata of the interface definitions generated by the IDL compiler inside the kernel. Remote object calls are immediately intercepted by the kernel. Then the ES kernel processes them at the meta-level and invokes the appropriate target object's method. For local RPC, the ES kernel uses a similar approach to the Spring's shuttle model, in which the calling thread directly moves to the server address space and makes an upcall to the server object. The extensibility of the ES kernel is assured by allowing the dynamic installation of the new interface metadata into the kernel. The object calls are the the first-class operations in the ES operating system.
Using IDL occasionally discouraged the developers due to its potentially cumbersome, extra procedures particularly while Microsoft tried to widely spread COM to their developers. But today, even though there are several deficiencies, the most important APIs for web applications have already been defined in OMG IDL in the DOM, HTML5 and other specifications. In ES, developers no longer have to link each RPC stub to their applications, the overall development cycle has been further simplified.
ES Programing Environment
In ES, the kernel, user and scripting environments are reasonably unified. Application programs should be written in C++, ECMAScript, or any other contemporary programming languages accessing to the same services without any difficulties.
For example, the way to write into a stream should be as simple as like this:
size = stream->write(buffer, len); // in C++
size = stream.write(buffer, len); // in ECMAScript
The stream
variable appears to be an ordinary interface pointer to an instance object, but it is actually a communication port associated with a particular system object and with a specific set of object methods like read, write, seek, etc. It is also used as a capability object and can be copied to the other processes or modules as a return value or an argument for a cross doming method call; if a module does not have any interface pointer to directories or files, the module simply cannot access any directories or files. The legacy "C" calling conventions should not be used for defining the system interfaces; otherwise, separate bridge runtime programs need to be manually written for each system interface in other programming languages:
? size = write(stream, buffer, len); // in C (NG)
We've demonstrated that we can implement the traditional posix APIs on top of the object APIs well suited for ECMAScript, etc., rather than implementing the object APIs on top of the posix APIs. ES's patch for newlib has many fundamental posix functions, including several pthread functions, under the newlib-1.15.0/libgloss/i386-es directory, that sufficiently support very useful libraries including the standard C library in Newlib, libstdc++-v3 in GCC, Cairo, and FreeType 2, etc.
Inside the kernel
System call
ES uses the global data structures consisted of a virtual pointer table and a method table within each user process to make a system call based on a C++ virtual function call model.
In userland, interface pointers associated with a system object point to a single entry in the global vptr table to trap the virtual function calls into the kernel space. Each virtual pointer points to the same single method table, and each method simply traps into the kernel with the method number.
See also: broker.h, syscall.cpp, system.cpp.
Upcall
ES also employs an upcall mechanism which allows the kernel modules to invoke objects' methods implemented in userland.
Inside the kernel, interface pointers associated with a user object point to a single entry in the vptr table to make upcalls to the target user process.
ES's upcall mechanism is implemented in this way; each application process registers its focus function to the kernel using the setFocus method in ICurrentProcess interface. The focus function is like the mouth of a wormhole where threads come out to invoke an object's method within the user process as illustrated below:
``` void* focus(void*) { try { for (;;) { Method** object; int method;
trap(&object, &method);
(*object)[method]();
}
} catch (...) { // Store exception number } return (exception number); } ```
In ES, focus uses a different trap from the system call trap for better performance, and parameters are copied to the upcalling thread's user stack upon return from the trap by the kernel.
See also: upcall.cpp, system.cpp.
Local RPC
By combining the system call path and the upcall path, ES realizes a lightweight remote procedure call path [
Bershad]
.
Local RPC = System call + Upcall
For local RPC, interface pointers associated with a remote object point to a single entry in the global vptr table of the process, which associated with an entry in the kernel object table that just points to entries in the vptr table for upcall.
ECMAScript interpreter - esjs
The ECMAScript interpreter esjs converts function calls and attribute accesses into system calls, and upcalls into event dispatches using reflection.
canvas = new CanvasRenderingContext2D(unknown);
canvas.lineWidth = 3;
See also: interface.cpp.
References
[
Bershad]
Brian N. Bershad, Thomas E. Anderson, Edward D. Lazowska, and Henry M. Levy. Lightweight Remote Procedure Call. ACM Trans. on Computer Systems 8(1), February 1990, pp.37-55.
[
Draves]
R. Draves and S. Cutshall. Unifying the user and kernel environments. Technical Report MSR-TR-97-10, Microsoft Research, March 1997.
[
Hamilton]
The Spring Nucleus: A Microkernel for Objects. Graham Hamilton. Panos Kougiouris. SMLI TR-93-14. April 1993