Chapter 1: What is an Operating System?
- What an operating system is and why computers need one
- The difference between kernel space and user space
- How system calls let programs request services from the kernel
- The difference between monolithic and microkernel architectures
- How the boot process works at a high level
- How to set up a development environment and run a minimal kernel
1.1 What is an Operating System?
An operating system, or OS, is the most important program that runs on a computer. It manages all the hardware and software, and it provides a way for applications to interact with the hardware without needing to know the details of how that hardware works.
Without an operating system, a computer is just a collection of electronic parts. If you wanted to write a program without an OS, you would need to write code that directly controls the CPU, reads keystrokes from the keyboard byte by byte, sends pixel data to the screen one pixel at a time, manages your own memory to avoid overwriting other programs, and handles every network packet yourself. This would be incredibly difficult. Every program would need to be a complete operating system itself.
This is why operating systems exist. They provide three essential things:
Abstraction. The OS hides the complexity of hardware behind simple interfaces.
When you call fopen("file.txt", "r"), your program does not need to know anything
about the disk hardware. The OS handles all that complexity and just returns a file handle.
Resource management. A computer has limited resources: CPU time, memory, disk space, network bandwidth. Without an OS, one program could take all the CPU time and never let anything else run. The OS decides who gets what, when, and for how long.
Protection. Programs should not be able to interfere with each other. A bug in one program should not crash the entire computer. The OS creates boundaries between programs so they cannot access each other's memory without permission.
An Analogy: The Office Building
Imagine an office building with many companies working inside. Each company is like a program running on a computer.
| Office Building | Operating System |
|---|---|
| The building structure (walls, electricity, plumbing) | Hardware (CPU, memory, disk) |
| Building management | Operating system |
| Companies working in the building | Applications (programs) |
| Each company has its own locked office | Memory protection (programs isolated from each other) |
| Employees call building management for repairs | System calls (programs request OS services) |
| Building management schedules meeting room use | Scheduler (decides which program gets CPU time) |
| Security guards check badges at the door | Protection (prevents unauthorized access) |
Just as building management handles electricity, water, security, and cleaning so that companies can focus on their actual work, the operating system handles hardware details so that applications can focus on what they are designed to do.
1.2 What Does an Operating System Do?
Every operating system performs several key tasks. These are not optional; every general-purpose OS must handle them:
| Task | What It Does |
|---|---|
| Process management | Creates, runs, and stops programs. Decides which program gets the CPU at any moment. |
| Memory management | Tracks which memory is in use. Allocates memory to programs and reclaims it when they finish. |
| File system management | Organizes data on disks. Provides the file and directory abstraction. |
| Device management | Communicates with hardware devices through drivers. |
| Security and protection | Enforces boundaries between programs. Controls who can access what. |
| Networking | Manages network connections and data transfer between computers. |
These tasks are not independent. When you open a file, the file system finds the data on disk, the memory manager loads it into memory, the process manager schedules your program so it can process the data, and protection ensures no other program can read it without permission. The OS coordinates all of this automatically.
1.3 Kernel Space vs User Space
One of the most important concepts in operating systems is the division of memory into two regions: kernel space and user space.
Kernel space is where the kernel lives. The kernel is the core of the operating system. It runs with the highest privileges and has access to all hardware and all memory. Code running in kernel space can do anything.
User space is where applications run. Code in user space has limited privileges. It cannot access hardware directly, cannot access kernel memory, and cannot perform privileged operations. If a user-space program crashes, the kernel is not affected.
graph TD
subgraph User Space
A[Application 1]
B[Application 2]
end
subgraph Kernel Space
C[Kernel]
D[Device Drivers]
end
subgraph Hardware
E[CPU]
F[Memory]
G[Disk]
end
A -->|System Calls| C
B -->|System Calls| C
C --> E
C --> F
C --> G
Figure 1.1: User space and kernel space. Applications communicate with the kernel through system calls.
The separation exists for protection. If an application runs with full privileges and crashes, it could corrupt kernel data, damage hardware, or steal data from other programs. By limiting what user-space programs can do, the OS ensures that a buggy program can only hurt itself.
1.4 System Calls: The Bridge Between User and Kernel
When a user-space program needs something that requires higher privileges (reading a file, sending network data, allocating memory), it cannot just do it directly. Instead, it makes a system call.
A system call is a request from user space to kernel space. The program says, "Kernel, please perform this operation on my behalf." The kernel checks whether the request is valid, performs it, and returns the result.
Common system calls include:
| System Call | What It Does |
|---|---|
read() | Read data from a file or device |
write() | Write data to a file or device |
open() | Open a file for reading or writing |
close() | Close a file descriptor |
fork() | Create a new process |
exec() | Replace the current process with a new program |
mmap() | Map files or devices into memory |
When you call fopen() in C, the C library calls the open() system
call. The CPU switches from user mode to kernel mode, the kernel opens the file and returns a
handle, and the CPU switches back to user mode. Your program never knows this happened.
1.5 Monolithic vs Microkernel Architectures
Operating systems can be structured in different ways. The two main architectures are monolithic kernels and microkernels.
Monolithic Kernel
In a monolithic kernel, all operating system services run in kernel space. The file system, device drivers, networking stack, and memory manager all live together in the kernel binary. They can call each other's functions directly.
Examples: Linux, BSD Unix, MS-DOS.
| Advantages | Disadvantages |
|---|---|
| Fast: no message passing between components | Large: a bug in any component can crash the entire system |
| Simple design: all code can call each other directly | Hard to maintain as the codebase grows |
| Well-understood: many examples exist (Linux) | Driver bugs can bring down the whole kernel |
Microkernel
In a microkernel, the kernel is kept as small as possible. Only the most essential services run in kernel space: scheduling, basic memory management, and inter-process communication (IPC). Everything else (file systems, drivers, networking) runs as user-space programs.
Examples: Minix, QNX, seL4.
| Advantages | Disadvantages |
|---|---|
| Small kernel with fewer bugs | Slower: components communicate through message passing |
| A driver crash does not crash the system | Complex design with more moving parts |
| More secure: less code runs with high privilege | Harder to implement correctly |
1.6 How a Computer Boots
When you press the power button, a sequence of events begins that eventually loads and starts the operating system. Understanding this sequence is important because our kernel must handle every step after the bootloader.
graph LR
A[Power On] --> B[Firmware]
B --> C[Bootloader]
C --> D[Kernel Entry]
D --> E[Kernel Initialization]
E --> F[First Process]
F --> G[Shell / GUI]
Figure 1.2: The boot sequence from power-on to a running system.
- Power On. When power is applied, the CPU resets and begins executing code at a fixed address. This address points to the firmware.
- Firmware. The firmware (UEFI on modern systems) initializes basic hardware, runs self-tests, and then looks for a bootloader on disk.
- Bootloader. The bootloader loads the kernel from disk into memory, sets up basic system state, and jumps to the kernel entry point.
- Kernel Entry. The kernel begins executing. It must set up exception handlers, initialize memory management, configure the MMU, and prepare the system to run user-space code.
- Kernel Initialization. The kernel initializes all its subsystems: the scheduler, memory allocator, file system, device drivers, and networking stack.
- First Process. The kernel creates the first user-space process (traditionally
called
init) and starts it. - Shell / GUI. The first process starts the user interface, and the system is ready.
1.7 Our Implementation
Now that we understand what an operating system is and how it works, let us see how these concepts apply to the kernel we are going to build.
What We Are Building
We are building a monolithic kernel for the ARMv8-A architecture in 64-bit mode (AArch64). The kernel will run at Exception Level 1 (EL1), and user-space applications will run at Exception Level 0 (EL0).
Development Platform
We develop using QEMU, which emulates an ARM64 computer. This lets us test our kernel without needing real hardware. Later, we will port the kernel to run on Raspberry Pi 4 and Raspberry Pi 5.
Setting Up the Environment
Before we can write kernel code, we need a cross-compiler. A cross-compiler runs on your computer (which might be x86) but generates code for ARM64. We also need QEMU to run the kernel and GDB to debug it.
Install these tools:
# Linux (Ubuntu/Debian)
sudo apt install build-essential qemu-system-arm gdb-multiarch
sudo apt install gcc-aarch64-linux-gnu
# macOS (Homebrew)
brew install qemu arm-none-eabi-gcc gdb
Our First Kernel
Let us write the smallest possible kernel that prints a message and halts. This will be our starting point. As we progress through the book, we will add more features until we have a complete operating system.
The kernel consists of three files:
kernel.ld - The linker script that tells the linker where to place code in memory:
ENTRY(_start)
SECTIONS
{
. = 0x40000000;
.text : { *(.text._start) *(.text*) }
.rodata : { *(.rodata*) }
.data : { *(.data*) }
.bss : { *(.bss*) }
}
start.S - The assembly entry point that sets up the stack and jumps to C code:
.section .text._start
.global _start
_start:
ldr x0, =_stack_end
mov sp, x0
bl kernel_main
wfi
b halt
kernel.c - The C code that prints a message to the UART serial port:
#define UART_BASE 0x09000000
void kernel_main(void) {
volatile char *uart = (volatile char *)UART_BASE;
const char *msg = "Hello from our kernel!\r\n";
while (*msg) *uart = *msg++;
while (1) __asm__("wfi");
}
Build and run it:
aarch64-none-elf-gcc -c -O2 -ffreestanding -Wall -Wextra kernel.c -o kernel.o
aarch64-none-elf-as start.S -o start.o
aarch64-none-elf-ld -T kernel.ld start.o kernel.o -o kernel.elf
qemu-system-aarch64 -M virt -cpu cortex-a72 -nographic -kernel kernel.elf
If everything works, you will see Hello from our kernel! printed in the terminal.
Press Ctrl-A X to exit QEMU.
- The kernel loads at address
0x40000000(the default load address for QEMU virt) - The UART is at physical address
0x09000000; writing a byte to this address sends it to the serial port - The
volatilekeyword prevents the compiler from optimizing away the write to the UART address - The
wfiinstruction puts the CPU into a low-power wait state until an interrupt occurs - The
-ffreestandingflag tells the compiler that we are building a kernel, not a user-space program, so it should not assume a standard library is available
1.8 Exercises
Try these exercises to make sure you understand the material before continuing.
Exercise 1: Explain the Office Analogy
In your own words, explain how the office building analogy maps to an operating system. Write three to four sentences as if explaining to someone who has never heard of an OS.
Exercise 2: Kernel vs User Space
For each operation, state whether it runs in kernel space or user space:
- Typing text in a word processor
- Reading data from a hard drive
- Responding to a keyboard interrupt
- Creating a new process
- Running a web browser
Exercise 3: Monolithic vs Microkernel
Create a table with at least five differences between monolithic and microkernel architectures. Then write one paragraph explaining which you would choose for a medical device and why.
Exercise 4: Trace a System Call
When a program calls read(fd, buffer, size), describe the sequence of events
from the application to the kernel and back. Include the C library, the kernel's system call
handler, and the device driver.
Exercise 5: Extend the Hello Kernel
Modify the kernel.c file to print your name and a number counter from 1 to 10. You will need to implement a function that converts an integer to a string, since we cannot use printf.
Exercise 6: Fibonacci (Challenge)
Extend the kernel to print the first 20 Fibonacci numbers using the UART. Each number should be on a new line. This requires implementing integer-to-string conversion.
1.9 Summary
In this chapter, we learned that an operating system is a manager for computer hardware and software. It provides abstraction (hiding hardware complexity), resource management (sharing CPU, memory, and devices fairly), and protection (isolating programs from each other).
We explored the division between kernel space (where the privileged kernel runs) and user space (where applications run). System calls are the bridge between them: applications request services, and the kernel performs them.
We compared monolithic kernels, where all services run in kernel space, with microkernels, where only the essentials run in kernel space. Our kernel will use a monolithic design.
Finally, we set up our development environment and ran our first kernel. In the next chapter, we will take a deeper look at how computers are structured at the hardware level.