Basic Setup

A typical Linux kernel divides the 4GB virtual address space like follows:

linux.gif

Here the upper 1GB is used for kernel memory, and the lower 3GB are available for user programs. In the Ring Cycle, the virtual address space looks like this:

ringcycleaddress.gif

The upper 1GB is still only for the ring 0 kernel. The 3rd GB is split into 2 halfs with the upper half being used for ring 1 drivers, and the lower half used for ring 2 drivers. The user address space is lowered to 2GB (which is how big the user address space is in Windows NT).

The current linux kernel barely uses x86 segmentation, with only 4 main segments defined (KERNEL_CS, KERNEL_DS, USER_CS, USER_DS). They are "flat" segments that span the entire 4GB address space.

The Ring Cycle includes 4 more segments (RING1_CS, RING1_DS, RING2_CS, RING2_DS) which have a base of 0, but a limit that prevents ring 2 code from accessing memory above 2.5GB and prevents ring 1 code from accessing memory above 3GB. The DPL of these segments are set to the appropriate levels so user code cannot access them.

How driver calls work

The Ring Cycle does not change how drivers that are built into the kernel at compile time work. It only affects drivers loaded at runtime with commands like insmod. When a module is loaded with insmod, the Ring Cycle kernel allocates space in the appropriate ring (footnote) and loads the module's code and data in that memory. The kernel also allocates a thread pool of threads that will run in the appropriate ring with the appropriate segments.

When a user program calls a method on a file like: open("/dev/device", O_RDWR) the sys_open kernel function looks up the device specific open function for /dev/device and calls it. However, the x86 protection model prevents kernel code from calling lesser privileged code directly. So the Ring Cycle does the following:

  1. Find which driver the open function pointer corresponds to.
  2. Find an available thread in the driver's thread pool.
  3. Modify the kernel stack of the driver thread so it will call the appropriate routine with the appropriate arguments. Map any necessary kernel data structures to memory in the driver's segment.
  4. Mark the driver thread as runnable.
  5. The user process then goes to sleep on a waitqueue waiting for the driver thread to finish.
  6. The scheduler then picks the driver thread to run.
  7. When the driver thread finishes, it executes a system call into the kernel and the kernel returns the thread to the driver's thread pool and wakes up the user process.
  8. The user process is then activated and countinues living a life of political and technical fullfillment.

Some important points about this is that since the code and data for the driver code is mapped into all process' address space, NO TLB FLUSH is needed when switching to the driver thread. The memory map of the driver is set to the same as the user process before control is switched so in addition to not flushing the tlb's (and hurting performance), the driver thread can access the user's memory (through copy_to_user, etc.) normally. This lack of tlb flushing is a key point that distinguishes the Ring Cycle from other driver isolation techniques including Nooks.

So how do drivers access kernel services and functions? This is done through a new layer called the Driver API (DAPI). The DAPI is a header file that includes special implementations of kernel functions for drivers. When a driver is compiled for the Ring Cycle kernel, it uses these implemetations. The DAPI implementations replace all these function calls with assembly instructions for pushing the parameters appropriately and executing a call through an x86 call gate into the kernel. Once in the kernel the normal implementations of these functions are used and the result is returned to the driver. Because the kernel runs in ring 0, it can access all resources that the driver can access.

The important point here is that drivers DO NOT need to be rewritten to work with the Ring Cycle kernel. They must be simply recompiled just like they do when you upgrade your kernel.

Protection

How is the kernel protected from drivers? Drivers can no longer access kernel functions or data structures directly since these symbols are beyond their segment. If a driver tries to access such memory, it will get a general protection fault and the kernel will kill the offending thread and proceed to unload the module (cleanly if possible).

For more details, please see the following: