The kernel is the most trusted part of the operating system. Multiple rings of protection were among the most revolutionary concepts introduced by the Multics operating system, most general-purpose systems use only two rings, even if the hardware they run on provides more CPU modes than that. For example, Windows 7 and Windows Server 2008 (and their predecessors) use only two rings, with ring 0 corresponding to kernel mode and ring 3 to user mode, because earlier versions of Windows ran on processors that supported only two protection levels.
Many modern CPU architectures (including the popular Intel x86 architecture) include some form of ring protection, although the Windows NT operating system, like Unix, does not fully utilize this feature. Under DOS, the kernel, drivers and applications typically run on ring 3 (however, this is exclusive to the case where protected-mode drivers and/or DOS extenders are used; as a real-mode OS, the system runs with effectively no protection), whereas 386 memory managers such as EMM386 run at ring 0.
The x86-processors have four different modes divided into four different rings. Programs that run in Ring 0 can do anything with the system, and code that runs in Ring 3 should be able to fail at any time without impact to the rest of the computer system. Ring 1 and Ring 2 are rarely used, but could be configured with different levels of access. In most existing systems switching from “user mode” to “kernel mode” has a high cost in performance associated.
Windows Memory
Each process started on x86 version of Windows uses a flat memory model that ranges from 0x00000000 – 0xFFFFFFFF. The lower half of the memory, 0x00000000 – 0x7FFFFFFF, is reserved for user space code. While the upper half of the memory, 0x80000000 – 0xFFFFFFFF, is reserved for the kernel code. The Windows operating system also doesn’t use the segmentation (well actually it does, because it has to), but the segment table contains segment descriptors that use the entire linear address space. There are four segments, two for user and two for kernel mode, which describe the data and code for each of the modes. But all of the descriptors actually contain the same linear address space. This means they all point to the same segment in memory that is 0xFFFFFFFF bits long, proving that there is no segmentation on Windows systems.
The segmentation is actually not used by the Windows system. Therefore we can use the terms “virtual address space” and “linear address space” interchangeably, because they are the same in this particular case. Because of this, when talking about user space code being loaded in the virtual address space from 0x00000000 to 0x7FFFFFFF, we’re actually talking about linear addresses. Those addresses are then sent into the paging unit to be translated into physical addresses. We’ve just determined that even though each process uses a flat memory model that spans the entire 4GB linear address space, it can only use half of it. This is because the other half is reserved for kernel code: the program can thus use, at most, 2GB of memory.
Every process has its own unique value in the CR3 register that points to the process’ page directory table. Because each process has its own page directory table that is used to translate the linear address to physical address, two processes can use the same linear address, while their physical address is different. Okay, so each program has its own address space reserved only for that process, but what about the kernel space? Actually, the kernel is loaded only once in memory and each program must use it. This is why each process needs appropriate pointers set-up that do just that. It means that the PDEs stored in the page directory of every process point to the same kernel page table.
We know that the kernel’s memory is protected by various mechanisms, so the user process can’t call it directly, but can call into the kernel by using special gates that only allow certain actions to be requested of kernel. Thus, we can’t simply tell a program to go into kernel mode and do whatever it pleases, because the code that does whatever we want is in user space. Therefore, the programs can only request a special action that’s already provided by a kernel’s code to do it for them. If we would like to run our own code in kernel memory, we must first find a way to pass the code to the kernel and stay there. One such example would be the installation of some driver that requires kernel privileges to be run, because it’s interacting more or less directly with the hardware component for which the driver has been written. The user processes can then use that driver’s functionality to request certain actions to be done: but only those that have been implemented in the driver itself. I hope I made it clear that the functionality run in kernel mode is limited to the functionality of the code loaded in kernel mode, and thus the code from user mode is not allowed to be executed with kernel privileges.
In the image above we can see the HAL (Hardware Abstraction Layer), which is the first abstraction layer that abstracts the hardware details from the operating system. The operating system can then call the same API functions and the HAL takes care of how they are actually executed on the underlying hardware. Every driver that is loaded in the kernel mode uses HAL to interact with the hardware components, so even the drivers don’t interact with hardware directly. The kernel drivers can interact with the hardware directly, but usually they don’t need to, since they can use HAL API to execute some action. The hardware abstraction layer’s API is provided with the hal.dll library file that is located in the C:\WINDOWS\system32\ directory as can be seen below:
The rest of the system kernel components are provided by the following libraries and executables:
- exe : manages user processes and threads
- sys : user and graphics device driver (GDI)
- dll : access to resources like file system, devices, processes, threads and error handling in Windows systems
- dll : access to windows registry, shutdown/restart the system, start/stop/create services, manage user accounts
- dll : create and manage screen windows, buttons, scrollbars, receive mouse and keyboard input
- dll : outputs graphical content to monitors, printers and other output devices
- dll : dialog boxes for opening and saving files, choosing color and font
- dll : access to status bars, progress bars, tools, tabs
- dll : access the operating system shell
- dll : access to networking functions
The programs can use Windows API (Win 32 API) to implement the needed actions. But the WinAPI uses the described DLL libraries underneath. Right above the kernel mode is the user mode, where the most important library is ntdll.dll. That can be used as an entry point into the kernel if some process needs services of the kernel.