The central kernel (or kernel land) necessarily runs on Ring 0
In x86 architecture, “rings” are levels of CPU privilege. Ring 0 has complete control over the hardware, privileged instructions, and physical memory. If your kernel code were not in Ring 0, it would be unable to handle interrupts, manage the GDT (Global Descriptor Table), or control paging
- Monolithic Kernel (Linux or BSD Type)
- In this model, the kernel is a single giant binary that contains everything: scheduler, memory management, drivers, and file systems
- Microkernel (Minix or QNX type)
- Here the kernel (Ring 0) is minimal. It only handles inter-process communication (IPC) and basic interrupt management
- The drivers, file system, and network stack run as separate processes in Ring 3 (User Mode)
- If the video driver fails, the system does not die (there is no Kernel Panic), only that server “process” restarts
The Kernel: This is the code that “grants permission.” It is loaded by a bootloader (such as GRUB or a custom one). It is not an app because it doesn’t have an underlying operating system managing it; it is the system
Apps (Processes): These are programs that reside in user space (Ring 3). They cannot directly access the hardware. When an app wants to write a file, it makes a System Call, which causes the CPU to switch from Ring 3 to Ring 0 so that your kernel can do the dirty work
If you’re starting with C and a kernel.c file:
- Your resulting binary is usually an ELF file or a flat binary
- Everything is a single execution unit until you manage to implement process separation and the jump to Ring 3
modularize
The key is not to confuse how the code is organized (modularization) with how it is executed on the CPU (kernel architecture)
When you program in C at the system level (kernel), “communication” is not like sending a message via WhatsApp or using an internet API; it is much cruder, basically about sharing memory or jumping to memory addresses
If you have a file memory.c and a file keyboard.c, the first one doesn’t know that the second one exists until the Linker does its job
- External Symbols: In your kernel code, you use the
externkeyword. This tells the compiler, “I don’t know where this function is, but trust that it will appear later” - The Symbol Table: The Linker searches all your
.ofiles (objects), finds the memory addresses of each function, and pastes them - Result: Communication is direct. One module simply calls a function from another module as if they were in the same file
Communication in Ring 0 (Shared Memory)
Since your entire kernel resides in the kernel’s memory space, all modules see the same memory map. Communication typically occurs through Global Data Structures.
- Example: You can have a central structure called System that contains pointers to all the modules
- The flow: The keyboard module receives an interrupt, writes the key code to a buffer (an array in memory), and the console module reads from that same buffer
The “Service Registry” (Pointer Exchange)
If you want your kernel to be more modular (so that one module doesn’t rigidly depend on another), you use a service registry
- Module A initializes and registers its functions in a central table (an array of function pointers).
- Module B asks the kernel, “Where is the function to write to the screen?”
- The kernel returns the function pointer.
- Module B jumps to that memory address.
What does this look like in code?
Imagine you’re in Ring 0. Here’s how they “communicate”:
// --- modulo_video.h ---
void escribir_pantalla(char* texto);
// --- modulo_teclado.c ---
#include "modulo_video.h"
void interrupcion_teclado() {
// Comunicándose directamente llamando a la función
// El Linker se encargará de encontrar la dirección de memoria de esta función
escribir_pantalla("Tecla presionada");
}
In a kernel, communication is about sharing pointers. Since you own the hardware (Ring 0), there’s no one to stop you from reading or writing to any part of RAM. The difficulty isn’t “how to send the message,” but how to organize memory so that one module doesn’t accidentally overwrite what another is using
Monolithic Kernel
A monolithic kernel is an operating system architecture where all core software runs in a single memory space and under the same privilege level (the famous Ring 0)
Instead of having separate pieces that communicate via “messages”, in a monolithic system all components are part of a single giant binary that has total control over the hardware
It’s like a racing engine where all the parts are welded together to gain speed: it’s incredibly efficient, but if one small part fails, the whole engine explodes.
Less design complexity: You don’t need to program complex messaging systems (IPC) between parties
Performance: Being a single block, internal calls are almost instantaneous
Instability: A single error in any module brings down the entire system
Size: The kernel binary is usually larger because it contains almost everything.
Imagine the kernel as an office. In a monolithic kernel, the boss (the process scheduler), the secretary (memory management), the maintenance person (hardware drivers), and the receptionist (file systems) are all sitting at the same desk
- If one needs something from the other, it simply asks directly (a function call in C). It’s extremely fast because there are no walls between them
Modularity (The “Modern Monolith”)
Today, “monolithic” does not mean that the code is a mess. Systems like Linux are monolithic, but they are modular
- You can load and unload modules (such as video drivers) while the system is powered on
- However, once a module is loaded, it’s integrated with the kernel and runs in Ring 0 with the same risk and speed as the rest
Microkernel (Technical purity)
The philosophy is: “Keep Ring 0 as small as possible.” Only basic memory management, threads, and IPC (Inter-Process Communication) reside in the kernel
- Where do the drivers live? In user space (Ring 3), just like regular programs
- Communication: If the keyboard wants to send a letter to the monitor, it has to go through the kernel, which acts as a “messenger” (IPC). It’s slow due to the constant context switching, but very reliable
Hybrid Kernel (The balance)
It is essentially a microkernel that, for performance reasons, puts certain critical components (such as video drivers or the file system) “inside” Ring 0
- The concept: It looks like a microkernel, but it behaves somewhat like a monolithic kernel to avoid the slowness of constantly sending messages between Ring 3 and Ring 0
- Examples: Windows NT and macOS (XNU)
Can a monolithic kernel be modular?
Yes, and in fact almost all of them are! Don’t confuse “monolithic” with “a single file of spaghetti code”
- Logical modularization: In C, you separate your code into filesystem.c, network.c, memory.c. When compiling, the Linker joins them into a single large binary
- LKM (Loadable Kernel Modules): Similar to Linux. You can load a video card driver after the system has booted
- The key difference: Although the code is in separate files (modules), once loaded, they all run in the same memory space (Ring 0). If the video driver module has a pointer error in C, it destroys the entire kernel (Kernel Panic/Blue Screen)
Is modularization the same as using microservices?
In a Microkernel (Yes, it’s like Microservices)
- Each component (Driver, FS, Network) is an isolated process
- They communicate via messages
- If the network microservice fails, the rest of the system remains operational
In a Monolithic Kernel (It’s like a Monolithic App with Libraries)
- You have many .c files, but they’re all compiled or loaded into the same massive process
- The “communication” is simply calling a function (function_from_another_module())
- If one “module” fails, the entire infrastructure collapses because they all share the same memory

I need to create a “mail” or “message orchestrator”
is the IPC (Inter-Process Communication)
The kernel ceases to be a “task executor” and becomes a “mail carrier”.
This is where the microservices analogy fits perfectly: the kernel is the Message Broker (like a RabbitMQ or a Data Bus) that connects the different services
In C, you don’t send a text message, but rather manage message queues or ports
What happens if the “Mail” (Kernel) goes down?
If the orchestrator (the kernel in Ring 0) fails, the entire system dies
There’s no way to recover from that because the kernel has the privileges to manage the CPU. It’s what we call a Single Point of Failure
Therefore, the Microkernel code must be as simple, minimalist and perfect as possible: the fewer lines of code the “postman” has, the less likely it is to make a mistake
What if a module (a “Service”) goes down?
This is where the Microkernel shines and beats the Monolithic
- In a Monolithic: If the video driver (which is inside the kernel) tries to access a NULL pointer, the CPU throws an exception in Ring 0 and the system freezes (Kernel Panic)
- In a microkernel: If the video driver (which is a process in Ring 3) crashes, the “postman” (kernel) simply notices that the process died. The kernel remains alive, so it can restart the driver process without the user having to restart the computer
Microkernel: It’s much harder to get started because you have to program the entire “messaging” system (IPC) before you can see a single letter on the screen, but it’s much more elegant and “robust”
Monolithic: It’s easier to program (you call functions and that’s it), but a small error in your driver code will force you to constantly restart your virtual machine (QEMU/Bochs)
You’ll just need to “extract” the modules to Ring 3 and have a communication logic