A comprehensive journey from OS fundamentals to the architecture and history of modern systems like Windows, macOS, Linux, and BSD.
April 2026
At its most fundamental level, an Operating System (OS) is a collection of software that manages computer hardware resources and provides common services for computer programs. It acts as an intermediary between users/applications and the computer hardware. Without an OS, every programmer would need to write code to directly manipulate disk read-heads, manage voltage levels for memory cells, and handle the intricate timing of network hardware.
An operating system typically fulfills four primary roles:
A critical concept in modern OS design is the distinction between User Mode and Kernel Mode. This is hardware-supported (via a “mode bit” in the CPU) and is essential for system stability.
When an application needs to perform a privileged operation (like reading a file or sending a packet), it must perform a System Call. This triggers a transition from User Mode to Kernel Mode, where the OS validates the request, performs the action, and then returns control to the application.
While internal architectures vary, most systems share these core components:
The “heart” of the OS. It is the first part of the OS to load and remains in memory. It manages the CPU, memory, and devices. Modern kernels are often categorized as Monolithic (like Linux/Windows) or Microkernels (like Mach/Minix).
The user interface. The shell is a command-line interpreter (like bash or PowerShell), while the GUI (Graphical User Interface) provides windows, icons, and menus.
These are standard functions that applications use to interact with the kernel (e.g., libc in Unix-like systems or the Win32 API in Windows).
Specialized programs that allow the kernel to communicate with specific hardware devices. The driver “translates” generic OS commands into device-specific instructions.
How does the OS start? When you press the power button, the CPU is in a primitive state and must be “bootstrapped” into a fully functional environment. This involves a hand-off between several layers of software:
For a deep dive into these mechanics, see the System Initialization & Booting module later in this course.
Operating systems have evolved from simple “batch processing” systems in the 1950s—which ran one job at a time—to the highly sophisticated, distributed, and real-time systems we use today across laptops, smartphones, and cloud servers. In the following modules, we will dive deeper into the mechanics of how these components work together to create the seamless experience of modern computing.
The kernel is the “nucleus” of the operating system. It defines the fundamental way that software interacts with hardware. Over the decades, several competing philosophies have emerged regarding how a kernel should be structured. The choice of architecture impacts everything from system performance and security to the ease of development.
In a mono-lithic (meaning “single stone”) architecture, the entire operating system runs in kernel space. This includes the scheduler, memory management, file systems, and device drivers.
The primary disadvantage is fragility. Because every component has full kernel privileges, a bug in a single printer driver can access the memory of the filesystem or the scheduler, leading to a total system crash (the dreaded “Kernel Panic”). Furthermore, as the kernel grows, it becomes increasingly complex and difficult to maintain.
The microkernel philosophy, pioneered by systems like Mach and QNX, takes the opposite approach. It aims to keep the kernel as small as possible. Only the absolute essentials—address space management, thread management, and Inter-Process Communication (IPC)—remain in the kernel.
The main issue with microkernels is IPC overhead. If an application wants to read a file, it must send a message to the microkernel, which then context-switches to the file-system server, which might then send another message to a disk-driver server. These multiple context switches can significantly slow down the system.
Most modern commercial operating systems utilize a Hybrid Kernel architecture. This design attempts to combine the performance of a monolithic kernel with the modularity of a microkernel.
An exokernel provides almost no abstractions. Instead of “managing” hardware, it simply “multiplexes” it, giving applications raw access to disk sectors and memory pages. The application itself (using a “Library OS”) decides how to manage those resources. This allows for extreme optimization (e.g., a database that knows exactly how to layout data on disk).
An even smaller version of a microkernel, often providing only hardware abstraction and nothing else, sometimes not even thread management.
| Feature | Monolithic | Microkernel | Hybrid |
|---|---|---|---|
| Code in Kernel | Entire OS | Minimal | Core + Performance modules |
| Performance | Excellent (low IPC) | Slower (high IPC) | Very Good |
| Reliability | Low (driver can crash OS) | High (isolated servers) | Medium |
| Complexity | High (intertwined) | High (IPC logic) | Very High |
| Modern Usage | Linux, Server OSs | Embedded, RTOS | Windows, macOS |
Understanding these architectures helps explain why a Windows update might feel different from a Linux kernel update, or why a driver crash on your laptop might sometimes freeze the whole screen while other times just flickering and recovering. In the next module, we will explore the lifecycle of a process—the fundamental unit of work within these kernels.
If the kernel is the conductor of an orchestra, then Processes and Threads are the musicians. Managing these units of execution is one of the most complex tasks an operating system performs. A modern computer might have hundreds of programs “running” at once, even though it only has a handful of physical CPU cores. The illusion of simultaneous execution is maintained through clever scheduling and context switching.
A Process is a program in execution. It is more than just the machine code (the “Text” segment); it includes the current activity, as represented by:
To manage a process, the OS maintains a data structure called the Process Control Block (PCB). Think of this as the “manifest” for the process. When the OS stops one process to start another, it saves the current CPU registers and state into the PCB so it can resume exactly where it left off later.
A process moves through various states during its life.
The act of stopping the current process, saving its state (the PCB), and loading the state of a new process is called a Context Switch. This is “pure overhead”—while the CPU is context switching, it isn’t doing any useful work for the user. System designers strive to minimize this overhead, but it is the price we pay for multitasking.
A Thread is a “lightweight” unit of execution within a process.
Imagine a web browser:
If these were separate processes, they would struggle to share the data of the webpage efficiently. By being threads, they can all access the same global variables and buffers directly.
The “shared memory” that makes threads efficient also makes them dangerous. If two threads try to increment a counter at the exact same time, a Race Condition can occur, leading to corrupted data. Developers must use synchronization tools like Mutexes (Mutual Exclusion locks) and Semaphores to manage this.
How does the OS decide which “Ready” process gets to run next? This is the job of the Scheduler.
Even though processes are isolated, they often need to talk to each other.
ls | grep .txt in Linux).Managing processes and threads is a balancing act between responsiveness (the user interface shouldn’t lag) and throughput (as much work as possible should get done). In the next module, we’ll see how the OS provides the memory “sandbox” that these processes live in.
Memory is arguably the most precious resource in a computer. Every instruction executed by the CPU must be fetched from memory, and every piece of data processed must reside there. In the early days of computing, programs were limited by the physical amount of RAM installed. Today, the Operating System uses a sophisticated layer of abstraction called Virtual Memory to provide each process with its own private, massive address space.
Initially, OSs used Contiguous Allocation: a program was loaded into a single block of memory. This led to two major problems:
Virtual Memory separates the Logical Addresses used by the programmer from the Physical Addresses of the RAM hardware.
The Memory Management Unit (MMU) is a hardware component in the CPU that translates virtual addresses to physical addresses on-the-fly.
Modern systems use a technique called Paging. Memory is divided into fixed-size blocks:
The OS maintains a Page Table for each process. This table is a “map” that tells the MMU: “Virtual Page 7 is currently located in Physical Frame 42.”
msvcrt.dll or libc.so), the OS can map their virtual pages to the same physical frame to save space.What happens when you run out of RAM?
This is why your computer slows down when you have too many tabs open—your CPU is spending all its time moving data between the fast RAM and the slow SSD/HDD.
While paging uses fixed-size blocks, Segmentation uses variable-sized blocks based on the logic of the program (e.g., a “Code Segment,” a “Data Segment,” and a “Stack Segment”).
Modern Reality: Most systems (x86_64) use a hybrid: “Paging within Segments” or simply flat paging with segment-like protections applied at the page level.
Memory management is also a security feature.
system()) is located.Looking up the Page Table for every single memory access would be too slow. To solve this, CPUs have a tiny, super-fast cache called the TLB (Translation Lookaside Buffer). It stores the most recent virtual-to-physical translations. A “TLB Hit” happens in less than a nanosecond, while a “TLB Miss” might require several cycles to walk the page table in RAM.
Understanding memory management is the key to understanding why “8GB of RAM” might feel fast on one OS and slow on another. It’s not just about how much you have, but how intelligently the OS shuffles it.
A disk drive is essentially a massive array of addressable blocks (traditionally 512 bytes or 4 KB each). To a human, this raw data is useless. The File System is the component of the operating system that provides the abstraction we know and love: organized files and nested directories.
A “File” is a named collection of related information that is recorded on secondary storage. To the user, a file is a single object. To the OS, a file is a collection of logical blocks mapped to physical disk sectors.
Every file has metadata, which is “data about data.” This includes:
How does the OS keep track of which blocks belong to “vacation-photo.jpg”?
The file is stored in a single, unbroken sequence of blocks.
Each block contains a “pointer” to the next block in the file (like a linked list).
The OS creates an Index Block (called an inode in Unix) that contains a list of all the block addresses for that file.
A directory (folder) is actually just a special type of file. Instead of containing user data, it contains a list of filenames and their corresponding inode numbers. When you type cd Documents, the OS reads the “Documents” file, looks for the entry you want, and finds its inode.
What happens if the power goes out while the OS is in the middle of writing a large file? In older systems, this would lead to “corrupted” disks where the directory list said a file existed, but the blocks themselves contained garbage.
Modern file systems use Journaling (e.g., NTFS, Ext4, APFS).
If the system crashes, upon reboot, the OS checks the journal. If it finds a “Fixing” entry that wasn’t “Committed,” it can either complete the task or safely undo it, ensuring the disk is never in an inconsistent state.
| Name | Primary OS | Key Features |
|---|---|---|
| FAT32 | Windows/Legacy | Universal compatibility, but no security and 4GB file size limit. |
| NTFS | Windows | Journaling, compression, encryption, and granular permissions. |
| Ext4 | Linux | Extremely stable, handles massive files, very performant. |
| APFS | macOS/iOS | Designed for SSDs, features “snapshots” and fast directory sizing. |
| ZFS | BSD/Solaris | ”The God File System”: protects against data rot (silent corruption). |
In many OSs, there is a layer called the VFS. This allows the OS to support many different types of file systems simultaneously. An application just tells the VFS “open file X,” and the VFS figures out whether that file is on a USB drive (FAT32), a Linux partition (Ext4), or even a network drive (NFS/SMB).
In the next section, we will leave the abstract theory behind and look at the real-world history and “family tree” of the operating systems we use every day.
Before an operating system can manage memory or schedule processes, it must be brought into existence by the hardware. This process, known as booting (short for bootstrapping), is the sequence of events that occurs from the moment you press the power button until the kernel takes control.
When power is applied, the CPU is “reset” to a fixed execution state. On x86 systems, it begins in Real Mode (16-bit) and jumps to a specific memory address (usually 0xFFFFFFF0, the “reset vector”) where the firmware resides.
The firmware—either a Legacy BIOS or a modern UEFI—first performs the POST. This verifies that the hardware is functional: it checks the CPU, initializes the memory controller (RAM), and detects storage devices.
For decades, the BIOS (Basic Input/Output System) was the standard. It is limited by its 16-bit origins.
The BIOS looks at the first 512 bytes of the bootable disk. This sector is the MBR.
0x55AA. If this is missing, the BIOS won’t boot the disk.INT 0x13 for disk I/O).The UEFI (Unified Extensible Firmware Interface) replaced BIOS to address these limitations. It is essentially a miniature operating system itself.
.efi executable files.| Feature | MBR (BIOS) | GPT (UEFI) |
|---|---|---|
| Max Disk Size | 2 TB | 9.4 ZB |
| Max Partitions | 4 Primary | 128 (Default) |
| Redundancy | None (Single sector) | Header and Table Backups |
| Execution Mode | 16-bit Real Mode | 32/64-bit Protected Mode |
In the world of Legacy BIOS, how does the firmware know that a disk is actually bootable?
/* The last two bytes of a bootable MBR must be */\n0xWhether you are using a 30-year-old server or a brand-new laptop, understanding this initial handshake is the first step in “operating” the system. In the next lesson, we’ll see how the Bootloader bridges the gap between this firmware and the Operating System kernel.
If the BIOS/UEFI is the “ignition,” the Bootloader is the “starter motor.” Its job is to find the Operating System kernel on the disk, load it into memory, and jump to its starting address.
You might wonder: Why doesn’t the BIOS just load the kernel directly?
On Linux and many hobbyist OSs, GRUB 2 is the standard. It works in stages:
/boot/grub/grub.cfg, and allows you to select a kernel.To prevent every OS from needing its own custom bootloader, the Multiboot Specification was created. It provides a standard way for a bootloader to talk to a kernel.
A Multiboot-compliant kernel has a “Header” in its first 8KB that contains:
0x1BADB002 (for Multiboot 1).When the bootloader jumps to the kernel, it provides critical information in CPU registers:
EAX: Contains the magic value 0x2BADB002 (confirming a Multiboot boot).EBX: A pointer to a Multiboot Information Structure (containing the memory map, command line, and list of loaded modules).Once the kernel has control, it performs its own initialization:
systemd, launchd). This process has a Process ID (PID) of 1.When writing a kernel entry point in assembly, you must ensure the bootloader recognizes it. What magic number must be in the EAX register when the kernel starts?
/* The bootloader places this magic value in EAX */\n0xBAD002
By understanding this chain, you now have the conceptual blueprint to write your own “Hello World” kernel—a journey that begins with a few bytes of assembly and ends with a fully functioning operating system.
Operating systems did not emerge in a vacuum. Every OS you use today is the result of decades of “biological” evolution—ideas were shared, companies were sued, projects were abandoned, and successful kernels were “forked” into new lineages. To understand why Windows uses backslashes \ and Linux uses forward slashes /, or why macOS feels like a Unix system, we must look at the family tree.
In the 1960s, computers were massive mainframes. Users didn’t “use” them; they submitted decks of punched cards.
Unix was written in C, making it portable. Soon, it began to split into two major branches:
While the giants were fighting over Unix, a small company called Microsoft bought a hobbyist OS called QDOS (Quick and Dirty OS) and turned it into MS-DOS for the IBM PC.
Microsoft realized that the DOS lineage (Win 95/98) was too unstable. They hired Dave Cutler (the architect of VMS) to build a new, modern, secure kernel from scratch: Windows NT (New Technology).
In 1991, Linus Torvalds, a Finnish student, was frustrated that he couldn’t afford a expensive Unix workstation. He decided to write his own Unix-like kernel from scratch. He used the “GPL” license, which meant anyone could use and improve it for free.
After almost going bankrupt, Apple bought Steve Jobs’ company NeXT. NeXT had built an OS called NeXTSTEP, which was based on the Mach microkernel and BSD Unix.
ls and grep—it literally is a Unix system under the hood.Today, the world is divided into three major architectural camps:
In the next few modules, we will dive into each of these systems to see what makes them unique and how they actually function “under the hood.”
Unix is not just an operating system; it is a way of thinking about software. Created at Bell Labs in the late 1960s and 70s, Unix introduced concepts that were radical at the time but are now considered “best practices” throughout the industry.
The Unix philosophy was famously summarized by Doug McIlroy (the inventor of the Unix pipe):
|Before Unix, if you wanted a program that could search for text in a list of files and then sort those files by size, you had to write a single, massive program to do all of that.
In Unix, you use three small, independent tools and “pipe” them together:
find . -type f | xargs grep "search_term" | sort
Each tool is “stupid” on its own, but when combined, they become a powerful engine. This is known as Composability.
This is perhaps the most profound abstraction in Unix. In a Unix-like system, the kernel exposes almost every resource as if it were a file in the directory tree.
/dev/sda or /dev/nvme0n1. You can literally use cat to read the raw bits from your hard drive!/proc and /sys directories are “virtual” file systems. If you want to see how much memory your CPU is using, you just cat /proc/meminfo. If you want to change your CPU’s fan speed, you might echo 1 > /sys/class/thermal/....This uniformity means that a single set of tools (ls, cp, mv, grep, cat) can be used to manage the entire computer.
Unix was designed for programmers, by programmers. It assumes the user knows what they are doing.
rm -rf / (remove everything starting from root), the system will diligently attempt to delete its own soul without asking “Are you sure?“.As Unix forked into dozens of versions (HP-UX, IRIX, AIX, Solaris, BSD), software became hard to port. A program written for IBM’s Unix wouldn’t run on Sun’s Unix.
To solve this, the industry created POSIX (Portable Operating System Interface). POSIX defines a standard set of system calls (like open, read, fork, exec) that any OS claiming to be “Unix-like” must support.
Today, Linux, macOS, and FreeBSD are all mostly POSIX-compliant. This is why a program written for a Linux server can usually be compiled on a Mac with very little effort.
The shell is not the OS; it is just a program that talks to the OS. Because of the Unix philosophy, the shell is also a powerful programming language.
$PATH).command > file: Send output to a file instead of the screen.command < file: Read input from a file instead of the keyboard.& to a command tells the kernel: “Run this, but give me my prompt back immediately.”Unix proved that a portable, modular OS written in a high-level language was superior to custom assembly code written for a specific machine. It gave us the Internet (via BSD’s sockets), it gave us Open Source (via the reaction to AT&T’s lawsuits), and it forms the DNA of almost every device in your pocket or data center today.
Historically, Unix was the “professional” OS, while Windows was the “personal” OS. In the next module, we’ll see how Microsoft built its own professional-grade system to compete: the Windows NT story.
The history of Windows is a story of two very different software lineages that eventually merged into the system we use today. Understanding this history explains why Windows “feels” different from Unix and why it carries so much “legacy baggage.”
In 1981, Microsoft released MS-DOS. It was a primitive, single-tasking OS.
These versions were massive leaps forward. They introduced the Start Menu, the Desktop, and TrueType fonts. However, underneath the shiny new interface, they were still built on top of the ancient DOS foundation. This made them notoriously unstable; the “Blue Screen of Death” (BSOD) was a common occurrence because any application could accidentally overwrite the kernel’s memory.
While the public was using Windows 95, Microsoft was internally developing a completely different beast: Windows NT (New Technology). Microsoft hired Dave Cutler, who had previously designed the VMS operating system for Digital Equipment Corporation. His goal was to create a modern, high-end OS for servers and workstations that was:
Unlike the “Everything is a File” Unix approach, Windows NT was built on an Object-Oriented philosophy. Everything in the kernel—processes, threads, files, registry keys—is treated as an “Object.”
By the late 90s, Microsoft had two incompatible products: the “Consumer” Windows (98/Me) and the “Professional” Windows (NT 4.0/2000). Consumers wanted the games and apps of Win 98, but the stability of NT. Windows XP was the bridge. It was the first consumer OS built entirely on the NT kernel. It successfully killed off the DOS lineage forever.
Instead of Unix’s thousands of individual text configuration files (like /etc/passwd), Windows uses a single, massive, hierarchical database called the Registry.
Windows uses Drive Letters. Each physical partition is a separate “root” (C:\, D:\). In Unix, there is only one root (/), and drives are “mounted” into folders (e.g., your D drive might be at /mnt/data).
Microsoft is obsessed with not breaking old software. You can often run a program from the 1990s on Windows 11. This is achieved through “shims” and a massive amount of legacy code kept in the System32 and SysWOW64 folders. This makes Windows larger and more complex than its rivals, but it’s the reason why business and industry rely on it.
The NT kernel has been refined over the decades.
Today, Windows is no longer just for PCs. The same NT kernel powers the Xbox, the Surface, and millions of servers in Azure. It has even embraced its old rival by including WSL (Windows Subsystem for Linux), allowing you to run a full Linux kernel inside Windows.
The story of macOS is perhaps the greatest “comeback” in tech history. In the mid-1990s, Apple’s operating system was technically bankrupt. It lacked protected memory, it lacked preemptive multitasking, and it crashed constantly. Today, macOS is a rock-solid, high-performance Unix system that powers everything from the MacBook Air to the highest-end Mac Studio.
The “Classic” Mac OS was a masterpiece of user interface design, but a nightmare of engineering.
Apple tried several times to build a successor (projects like Copland and Gershwin), but they all failed.
Steve Jobs, who had been fired from Apple, started a new company called NeXT. They built a workstation OS called NeXTSTEP. NeXTSTEP was revolutionary. It used:
In 1997, Apple bought NeXT, Steve Jobs returned, and NeXTSTEP became the foundation for Mac OS X (now just macOS).
Underneath the shiny “Aqua” interface of macOS lies Darwin. Darwin is an open-source operating system that Apple maintains. Its heart is the XNU Kernel (X is Not Unix).
The PlantUML generation failed (remote + local).
@startuml
package "User Interface (Aqua)" {
[Finder / Dock]
}
package "Application Frameworks" {
[SwiftUI / Cocoa]
[Metal (Graphics)]
[Quartz / Core Animation]
}
package "Darwin (Open Source Core)" {
package "XNU Kernel" {
[Mach (Microkernel)]
[BSD (Monolithic Logic)]
[I/O Kit (Drivers)]
}
}
node "Hardware (Apple Silicon / Intel)"
Darwin -> Hardware
@enduml
XNU is a hybrid kernel.
One of Apple’s unique strengths is its ability to transition the entire OS to new hardware architectures while keeping old software running.
Because macOS is built on a highly modular Unix foundation, these transitions were relatively seamless for the user, even though they involved rewriting the very bottom layers of the operating system.
It is a common misconception that iOS is “different” from macOS. In reality, iOS is macOS. It uses the same Darwin core, the same XNU kernel, and many of the same frameworks (like Core Animation and Metal). The only real difference is the “Upper Shell” (SpringBoard vs Finder) and the input methods (Touch vs Mouse).
In 2017, Apple replaced the aging HFS+ with the Apple File System (APFS). It is optimized for Flash/SSD storage and supports “Cloning” (making a copy of a file takes zero space until you change one of the copies) and “Snapshots” (the ability to revert your whole disk to how it looked 10 minutes ago).
macOS is heavily “hardened.” System Integrity Protection (SIP) prevents even the “root” user from modifying critical system files. This prevents malware from embedding itself into the OS kernel.
Today, macOS stands as the only mainstream OS that combines a high-end, polished commercial UI with a deep, standards-compliant Unix core. In the next module, we will explore the system that took those same Unix foundations but made them free for everyone: the Linux revolution.
In 1991, software was mostly a product you bought from a big corporation (Microsoft, IBM, AT&T). Today, the most critical piece of software on the planet—the Linux Kernel—is a collaborative effort between tens of thousands of individuals and companies, given away for free. This revolution changed not just how operating systems are built, but how all modern software is developed.
On August 25, 1991, 21-year-old Linus Torvalds sent a message to a newsgroup:
“I’m doing a (free) operating system (just a hobby, won’t be big and professional like gnu) for 386(486) AT clones…”
Linus didn’t set out to conquer the world. He just wanted a Unix-like system to run on his cheap home PC, because the professional versions of Unix cost thousands of dollars.
At the same time, Richard Stallman and the Free Software Foundation (FSF) had been working on the GNU Project. Their goal was to create a 100% free Unix-like system. They had already built the compiler (GCC), the shell (Bash), and the libraries (glibc). The only thing they were missing was a working Kernel. Linus had written a kernel, but he was missing the tools. It was a perfect match. The resulting system is technically GNU/Linux, though most people just call it “Linux.”
The Linux kernel was released under the GNU General Public License (GPL). The GPL has a “copyleft” clause: you can use, modify, and sell the code, but if you distribute a modified version, you must also release the source code for those modifications. This created a virtuous cycle:
Linux is a Monolithic Kernel. Every driver, every file system, and every network protocol runs in kernel space. Critics (like Andrew Tanenbaum, the creator of Minix) argued this was “obsolete” and that microkernels were the future. However, Linux solved the “complexity” problem by being Modular. You can load and unload drivers (called Kernel Modules) while the system is running.
The PlantUML generation failed (remote + local).
@startuml
package "User Space" {
[App] -> [System Libraries (glibc)]
}
package "Linux Kernel" {
[System Call Interface]
[Virtual File System]
[Process Management]
[Memory Management]
package "Loadable Kernel Modules" {
[Nvidia Driver]
[WiFi Driver]
[Ext4 Module]
}
}
[System Libraries (glibc)] -> [System Call Interface]
@enduml
Because Linux is just a kernel, it’s not very useful on its own. You need a “Distribution” (Distro) which bundles the kernel with the GNU tools, a desktop environment, and a package manager.
Today, Linux powers:
It won because it was “open.” If you are a developer at Amazon and you find a bug in the Windows kernel, you can do nothing but file a ticket and wait. If you find a bug in the Linux kernel, you can fix it yourself and have that fix running on your servers 10 minutes later.
In the next section, we will stop talking about history and start doing a “Deep Dive” into the internals of these systems to see how they handle files, security, and hardware.
Windows is often misunderstood as just a “GUI OS.” In reality, the Windows NT Executive is one of the most sophisticated pieces of engineering ever created. While Linux follows the Unix “Everything is a File” philosophy, Windows follows an Object-Based philosophy.
In the NT architecture, there is a clear distinction between the “Kernel” and the “Executive.”
When a Windows program wants to open a file, it doesn’t just get a file descriptor (like in Unix). It asks the Object Manager to create a Handle.
\Device\HarddiskVolume1\Windows\System32) or unnamed.The PlantUML generation failed (remote + local).
@startuml rectangle "User Application" as app rectangle "Object Manager" as om rectangle "Specific Object (e.g. File)" as obj app -> om : "Open File 'data.txt'" om -> om : "Check Permissions" om -> obj : "Create Reference" om --> app : "Return Handle (e.g. 0x4A2)" @enduml
The Registry is the central database that stores all configuration data for the hardware, software, and users.
SYSTEM, SOFTWARE, and SAM).Windows NT was originally designed to run multiple types of apps: OS/2 apps, POSIX apps, and Win32 apps. Each had its own “Subsystem.” Today, only the Win32 subsystem remains (and technically the Linux subsystem, WSL).
When you call a function like CreateFile(), it’s actually just a “wrapper.” The DLL translates CreateFile() into a native kernel system call (like NtCreateFile()) and traps into the Executive.
Writing drivers for Windows is notoriously difficult.
One of Microsoft’s smartest moves was the creation of User-Mode Drivers. For devices that aren’t critical (like a USB lamp or a simple sensor), the driver runs in User Mode. If it crashes, it doesn’t BSOD the whole system; the OS just restarts the driver process.
Windows security is based on Tokens. When you log in, the OS creates an Access Token for you. Every process you start inherits a copy of this token. When you try to access an object, the SRM compares your Token to the DACL (Discretionary Access Control List) on that object.
Understanding these internals is why “Task Manager” in Windows looks and behaves so differently from “Top” in Linux. Windows is a system of managers and objects, while Unix is a system of streams and files.
The macOS kernel, XNU, is a fascinating “Frankenstein” of operating system design. It takes two very different philosophies—the Mach Microkernel and FreeBSD—and fuses them into a single address space. This blend allows macOS to have the advanced IPC and task management of a microkernel while maintaining the high performance of a monolithic system.
Mach is the “bottom half” of XNU. Its job is to handle the absolute essentials:
Everything in Mach is done via Messages. If one part of the system wants to talk to another, it sends a message to a Port.
The PlantUML generation failed (remote + local).
@startuml
package "Task A" {
[Client Code]
}
package "Task B" {
[Service Code]
}
queue "Mach Port" as port
[Client Code] -> port : Send Message (Sync/Async)
port -> [Service Code] : Deliver Message
@enduml
While Mach is efficient, its API is “alien” to most programmers. This is where BSD (FreeBSD) comes in. It sits right next to Mach in the kernel and provides the “personality” that makes macOS feel like Unix.
fork()/exec() calls.When you call open() in a C program on a Mac, it goes to the BSD layer. The BSD layer might then send a Mach message to a disk driver to actually get the data.
One of the most unique parts of XNU is the I/O Kit. Writing drivers in C is dangerous and prone to memory leaks. I/O Kit is a framework for writing drivers in a subset of C++ (no exceptions, no RTTI).
In most OSs, managing threads is a manual process. You create 10 threads and hope for the best.
Apple invented GCD (also known as libdispatch).
Instead of creating threads, you create Queues. You just tell the OS: “Here is a task I need done.” The OS looks at the current CPU temperature, the battery level, and how many cores are free, and then it decides when and where to run that task. This leads to much smoother UI performance.
When Apple moved to Apple Silicon (ARM64), they needed a way to run old Intel (x86_64) apps. Unlike regular emulation, Rosetta 2 is a Static Binary Translator.
On modern Macs, the main OS doesn’t even handle your fingerprint or password. That is handled by the Secure Enclave—a completely separate computer-within-a-computer with its own processor and its own secure OS. The main XNU kernel can ask the Enclave “Is this fingerprint valid?”, and the Enclave says “Yes” or “No,” but it never shares the actual fingerprint data with the kernel.
In the next module, we will dive into the heart of the open-source world: the modular architecture of the Linux Kernel and its vast ecosystem of distributions.
The Linux kernel is the most flexible operating system ever built. It can run on a $5 microcontroller or a million-dollar supercomputer. This flexibility comes from its modular architecture and two key technologies that have revolutionized modern computing: Cgroups and Namespaces.
The Completely Fair Scheduler (CFS) is the heart of Linux multitasking. Unlike older schedulers that used complex heuristics to guess which task was “interactive,” CFS uses a simple mathematical model: it tries to give every process a “fair” share of the CPU over a period of time.
The VFS is what allows Linux to support hundreds of different file systems (Ext4, Btrfs, XFS, FAT32) simultaneously. It provides a common interface for all file-related system calls.
Why is Linux so dominant in the cloud? Because it invented the building blocks for Containers (like Docker).
Namespaces allow the OS to “lie” to a process.
While Namespaces provide isolation (hiding things), Cgroups provide resource limits.
The PlantUML generation failed (remote + local).
@startuml
package "Physical Server" {
[Linux Kernel]
package "Container A (Namespace 1)" {
[Process 1]
[Local IP 10.0.0.1]
}
package "Container B (Namespace 2)" {
[Process 1]
[Local IP 10.0.0.2]
}
}
[Linux Kernel] --> [Cgroups (Limits CPU/RAM)]
@enduml
A “Linux Distribution” is the combination of the kernel, a package manager, and a set of default tools.
apt (using .deb files).dnf / yum (using .rpm files).pacman.Linux is monolithic, but it is highly programmable. Technologies like eBPF (Extended Berkeley Packet Filter) allow developers to inject small bits of code into the kernel while it’s running—without recompiling it and without crashing it. This effectively gives Linux the modularity of a microkernel with the performance of a monolithic one.
Understanding Linux is about understanding that the “OS” is just a platform. You choose the kernel features you want, you choose the distribution that fits your needs, and you build exactly the system you require. In the next module, we’ll look at the “alternative” Unix family: the rock-stable and secure BSD distributions.
While Linux is the most popular open-source OS, the BSD (Berkeley Software Distribution) systems are often considered the most refined. Unlike Linux, which is just a kernel, each BSD is developed as a “Complete OS”—the kernel, the drivers, and the core tools (like the shell and compiler) are all managed by a single team in a single code repository.
There are dozens of BSD variants, but they almost all descend from the work of UC Berkeley. The “Big Three” each focus on a specific engineering goal.
FreeBSD is the most widely used BSD. It is designed for high-performance servers and workstations.
OpenBSD’s motto is: “Only two remote holes in the default install, in a heck of a long time.”
NetBSD’s motto is: “Of course it runs NetBSD.”
The biggest difference between Linux and BSD isn’t the code; it’s the License.
Because of the permissive license, many companies use BSD as the “base” for their proprietary products:
In Linux, if you want to know what version of ls you are using, you have to check if it’s the GNU version or the Alpine version. In BSD, ls is part of the “Base System.”
The PlantUML generation failed (remote + local).
@startuml
package "BSD Project Repository" {
[Kernel Source]
[Standard Libraries (libc)]
[System Utilities (ls, cp, ssh)]
[Documentation (Man pages)]
}
package "External Ports/Packages" {
[Firefox]
[Python]
[PostgreSQL]
}
@enduml
This centralized approach means that BSD systems feel more “cohesive.” The documentation (the Man Pages) is legendary for its accuracy because the person who wrote the code is usually the one who wrote the documentation in the same repo.
Long before Docker existed, FreeBSD introduced Jails in the year 2000. A Jail is a way to partition a computer into several independent “mini-computers.” Each jail has its own files, users, and network address, but they all share the same kernel. This provided a level of security and resource efficiency that took Linux nearly a decade to match with Namespaces.
In the next module, we’ll look at the systems in your pocket: the architectures of Android and iOS, and how they modified these desktop-class kernels for mobile devices.
A smartphone is a supercomputer that lives in your pocket, runs on a battery, and is constantly connected to the internet. To make this possible, the operating systems we’ve studied—Linux and Darwin—had to be radically modified to handle two massive constraints: Power Management and Security.
On a desktop, if a process uses 10% of the CPU while you’re not looking, it’s a minor annoyance. On a phone, it means your battery will be dead by lunch. Mobile OSs use a technique called Aggressive Suspension.
Android is built on the Linux Kernel. However, if you log into an Android phone via the terminal, it won’t look like Ubuntu.
The PlantUML generation failed (remote + local).
@startuml
package "System Apps / User Apps" {
[Camera / Settings / Apps]
}
package "Java API Framework" {
[Window Manager]
[Activity Manager]
}
package "Native Libraries & Android Runtime (ART)" {
[bionic (libc)]
[Media Framework]
}
package "HAL (Hardware Abstraction Layer)" {
[Graphics HAL]
[Camera HAL]
}
package "Linux Kernel" {
[Display Driver]
[Flash Memory Driver]
[Binder (IPC)]
}
@enduml
In standard Linux, IPC is done via pipes or shared memory. In Android, almost everything goes through a special kernel driver called the Binder. It is a fast, efficient, and highly secure message-passing system that allows different apps and system services to talk to each other.
As we saw, iOS is built on the Darwin (XNU) kernel. Its primary focus is User Security and Predictability.
| Feature | Android | iOS |
|---|---|---|
| Kernel | Linux | XNU (Darwin) |
| Customization | High (Change shell, icons, ROMs) | Low (Locked by Apple) |
| App Language | Java / Kotlin / C++ | Swift / Objective-C |
| Updates | Dependent on Manufacturer | Controlled by Apple |
| File Access | User-accessible system | Hidden from user |
Mobile OSs also have to act like Real-Time Operating Systems (RTOS) for certain tasks.
We are currently seeing a “Merging of the Worlds.”
In the next section, we will leave the theory behind and learn the actual “Basics” of interacting with these systems using the most powerful tool available: the Command Line.
To a casual user, the computer is a collection of icons. To a power user or a developer, the computer is a Shell. The shell is a special program that takes keyboard commands and passes them to the operating system to execute. On most Unix-like systems (Linux, macOS, BSD), the default shell is Bash or Zsh.
Imagine your files are a tree. The root of the tree is /.
pwd: Print Working Directory. Shows you exactly where you are.ls: List files in the current folder.
ls -l: Long format (shows sizes and permissions).ls -a: Show hidden files (those starting with a .).cd: Change Directory.
cd Documents: Go into a folder.cd ..: Go “up” one level.cd ~: Go to your “Home” folder.mkdir: Make Directory.touch filename.txt: Create a new empty file.cp source destination: Copy a file.
cp -r folder1 folder2: Copy an entire folder recursively.mv old_name new_name: Move or rename a file.rm filename: Remove (delete) a file.
rm a file, it is gone forever.rm -rf folder: Forcefully and recursively delete a folder. Use with extreme caution.cat file: Catenate and print the whole file to the screen.less file: Open a file in a viewer that lets you scroll up and down (press q to quit).head -n 10 file: Show the first 10 lines.tail -n 10 file: Show the last 10 lines.grep "search_term" file: Search for text within a file.
grep -r "error" /var/log: Search for the word “error” in every log file.This is where the Unix philosophy shines.
ls > files.txt: Write the output of ls into a file named files.txt (overwrites).ls >> files.txt: Append the output to the end of the file.command 2> errors.txt: Save only the error messages to a file.|The pipe takes the output of the command on the left and feeds it as the input to the command on the right.
ls -l | grep ".jpg"
Translation: List all files in long format, then search that list for anything ending in .jpg.
Unix is multi-user. Every file has an owner and a group.
If you type ls -l, you will see something like -rwxr-xr--.
The letters are grouped in threes: Owner, Group, and Others.
chmod 755 script.sh: Make a script executable by everyone.chown user:group file: Change who owns the file.sudo command: Substitute User Do. Run a command with the powers of the “Root” (Administrator) user.top: Real-time view of running processes (like Task Manager).ps aux: Snapshot of every running process.kill PID: Stop a process by its ID.killall name: Stop every process with a certain name (e.g., killall chrome).| Command | Purpose | Example |
|---|---|---|
cd | Change directory | cd /var/log |
ls | List files | ls -lh |
cat | View file content | cat config.json |
grep | Search text | grep "TODO" main.c |
find | Find files | find . -name "*.pdf" |
ssh | Secure Shell (Remote access) | ssh user@server.com |
man | Manual (Help) | man grep |
The best way to learn the shell is to use it. Try to perform your daily tasks—organizing photos, searching for documents—using only these commands for an hour. You will quickly realize why professional developers find the GUI so limiting.
In the next module, we’ll look at the Windows alternative: PowerShell, and see how it differs from the Unix approach.
For decades, the Windows command prompt (cmd.exe) was a source of mockery for Unix developers. It was clumsy, lacked basic features, and its scripting language (Batch) was a nightmare.
In 2006, Microsoft released PowerShell. It wasn’t just a better shell; it was a completely new paradigm for system administration.
In a Unix shell (like Bash), everything is a String of Text.
If you run ls -l, you get a table of text. If you want to find only files larger than 1MB, you have to use complex text-processing tools like awk or sed to “cut out” the column of text that represents the file size and compare it.
In PowerShell, everything is a .NET Object.
When you run Get-ChildItem (the PowerShell version of ls), you don’t get text. You get an array of FileInfo objects. Each object has properties like .Name, .Length (size), and .LastWriteTime.
The PlantUML generation failed (remote + local).
@startuml rectangle "Command: Get-Service" as cmd rectangle "Pipeline" as pipe rectangle "Output Object" as obj note right of obj Status: Running Name: wuauserv DisplayName: Windows Update end note cmd -> pipe pipe -> obj @enduml
PowerShell uses a very strict naming convention: Verb-Noun. This makes it extremely “discoverable.”
Get-Service: List all services.Stop-Service -Name "wuauserv": Stop the Windows Update service.New-Item -Path "C:\test.txt" -ItemType "File": Create a file.If you don’t know the command you need, you can search for it:
Get-Command *process*
Because the pipeline | passes objects instead of text, you can perform powerful operations with almost no code.
Get-Process | Where-Object { $_.CPU -gt 100 } | Stop-Process
Translation: Get all processes, filter for those using more than 100 units of CPU, and stop them.
Notice how we don’t have to specify “which column” the CPU usage is in. We just access the .CPU property of the object.
PowerShell includes “aliases” to make Unix users feel at home, but underneath, they are different commands.
| Concept | PowerShell Command | Alias |
|---|---|---|
| List files | Get-ChildItem | ls, dir |
| Change directory | Set-Location | cd |
| View file | Get-Content | cat, type |
| Search text | Select-String | grep |
| Process list | Get-Process | ps |
| Copy item | Copy-Item | cp |
PowerShell is the “Sovereign” of the enterprise.
Invoke-Command.Originally, PowerShell only ran on Windows. However, Microsoft eventually open-sourced it and created PowerShell Core. It now runs on Linux and macOS. While it will never replace Bash for local scripting on Linux, it is becoming a popular choice for “DevOps” engineers who need to manage multi-cloud environments (AWS + Azure) using a single, consistent language.
In the next module, we will explore the “layers” above the command line: Package Management and how OSs keep themselves up to date.
In the early days of Windows, if you wanted to install software, you went to a website, downloaded a .exe or .msi file, ran it, and clicked “Next, Next, Agree, Finish.”
In the Unix world, this was considered archaic. Instead, Unix used Package Managers.
Today, every major OS has adopted the package manager model for developers and power users.
A package is a compressed archive containing:
Instead of searching the whole internet, your OS looks at a Repository—a secure, curated “library” of software maintained by the OS developers.
The PlantUML generation failed (remote + local).
@startuml
node "Your Computer" {
[Package Manager]
}
cloud "Public Repository" {
[App A v1.2]
[Library B v3.0]
[App C v0.9]
}
[Package Manager] -> [Public Repository] : "Update List"
[Public Repository] --> [Package Manager] : "Metadata Index"
[Package Manager] -> [Public Repository] : "Download App A"
@enduml
In Linux, you almost never download software from a website.
apt): sudo apt update && sudo apt install vlcdnf): sudo dnf install gitpacman): sudo pacman -S nodejsThe genius of these managers is that they solve “Dependency Hell.” If you install a video editor that requires a specific graphics library, the package manager will say: “I see you need Library X. I will download that for you first.” It builds a Dependency Graph and installs everything in the correct order.
Apple provides the “App Store” for consumers, but it is too restrictive for developers. The community created Homebrew (“The missing package manager for macOS”).
brew install pythonFor a long time, Windows lacked a built-in package manager. The community filled the gap with:
apt for Windows.Recently, Microsoft released winget (Windows Package Manager). It is now built into Windows 10 and 11.
winget install "Visual Studio Code"
A new trend in OS design is moving away from shared dependencies (which can break if one library updates) toward “Self-Contained” formats.
The most important feature of a package manager is Trust.
When you run apt install, your OS verifies the GPG Signature of the package. This proves that the code came from the official Ubuntu developers and hasn’t been tampered with by a hacker. This is why Linux is generally considered much more secure than “downloading random .exe files from the web.”
In the next module, we will explore the “meta” level of operating systems: Virtualization and Containers, and how we can run an OS inside another OS.
In the old days, “one computer = one OS.” If you wanted to run a Linux web server and a Windows database server, you had to buy two physical boxes. Today, we use Virtualization to run dozens, or even hundreds, of isolated environments on a single physical machine.
To run multiple operating systems, we need a “Hypervisor” (also called a Virtual Machine Monitor or VMM).
The hypervisor is the only thing running on the hardware. It is a tiny, highly efficient kernel whose only job is to manage “Guest” OSs (VMs).
The hypervisor runs like an application inside a normal OS (like Windows or Mac).
The PlantUML generation failed (remote + local).
@startuml
package "Type 1 (Bare Metal)" {
[Hardware] as hw1
[Hypervisor] as hyp1
package "VM 1 (Linux)" {
[Kernel 1]
[Apps 1]
}
package "VM 2 (Windows)" {
[Kernel 2]
[Apps 2]
}
hw1 -> hyp1
hyp1 -> [Kernel 1]
hyp1 -> [Kernel 2]
}
package "Type 2 (Hosted)" {
[Hardware] as hw2
[Host OS (macOS)] as host
[Hypervisor (VirtualBox)] as hyp2
package "VM (Ubuntu)" {
[Guest Kernel]
}
hw2 -> host
host -> hyp2
hyp2 -> [Guest Kernel]
}
@enduml
A Virtual Machine (VM) virtualizes the Hardware.
A Container (like Docker) virtualizes the Operating System.
In 2020, Microsoft released WSL2 (Windows Subsystem for Linux 2). Unlike WSL1 (which translated Linux calls to Windows calls), WSL2 includes a real Linux kernel provided by Microsoft.
Virtualization is what made the “Cloud” possible. When you “rent a server” from Amazon (AWS EC2), you aren’t getting a physical computer. You are getting a slice of a massive 128-core server. Because of the hypervisor, you can’t see the other users on that machine, and they can’t see you.
The next step is “Serverless.” Instead of a VM or even a container, you just upload a single function (calculate_tax). The OS/Hypervisor spins up a micro-container, runs your function for 100 milliseconds, and then destroys it. You only pay for the exact 100ms of CPU time you used.
Early virtualization was slow because the software had to “emulate” every single instruction. Today, almost every CPU (Intel VT-x, AMD-V) has Hardware-Assisted Virtualization. The CPU itself has special instructions that allow the Guest OS to run directly on the physical cores at near-native speed, only trapping back to the hypervisor when it needs to access a sensitive resource like the Network or Disk.
In our final module, we will look at Modern Trends and the “Next Generation” of operating systems that are being built for the era of AI and the Edge.
The “Standard” operating systems we use today (Windows, Linux, macOS) were all designed in an era of slow networks, spinning hard drives, and predictable user input. Today, we face new challenges: ultra-fast 5G networks, non-volatile memory (NVM), massive AI models, and a global security landscape that is more hostile than ever. The OS must adapt.
In the previous module, we saw that Containers are fast but less secure, while VMs are secure but slow. MicroVMs are the solution.
A Unikernel goes even further. Instead of having an OS and an Application, you compile your application into a tiny OS.
As we put computers into cars, drones, and medical devices, “average” performance doesn’t matter. What matters is Worst-Case Performance.
The current trend in OS security is Zero Trust.
How will AI change the OS?
find . -name "*.pdf" | xargs rm, but instead just type “Delete all the PDFs I haven’t opened in a year.”We have traveled from the single-user mainframes of the 60s, through the “Unix Wars,” the rise of the PC and Windows, the open-source revolution of Linux, and the mobile dominance of Android and iOS.
The goal of an operating system remains the same: To hide the messy complexity of hardware and provide a clean, secure, and efficient playground for our ideas. Whether that playground is a smartwatch, a laptop, or a Mars rover, the principles of process management, memory allocation, and hardware abstraction remain the bedrock of modern civilization.
Congratulations on completing the Operating Systems Internals course! You now have the foundational knowledge to look at any computer—no matter how small or large—and understand the invisible conductor managing the orchestra of bits and bytes inside.
The Portable Operating System Interface (POSIX) is a family of standards specified by the IEEE Computer Society for maintaining compatibility between operating systems. It defines a standard application programming interface (API), command-line shells, and utility interfaces for software compatibility with variants of Unix and other operating systems.
In the 1980s, the Unix landscape became increasingly fragmented due to the “Unix Wars,” where commercial vendors shipped divergent versions (System V vs. BSD). This fragmentation meant that a C application compiled on SunOS might fail to run or compile on HP-UX without significant changes. The motivation for POSIX emerged to ensure developers could write software intended to run seamlessly across any compliant system by agreeing on a unified core interface.
POSIX is heavily centered around the C programming language. Key standards cover:
open(), read(), write(), close(), mkdir(), readdir().fork(), exec(), wait(), kill().#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("Child process running with POSIX fork.\n");
} else if (pid > 0) {
printf("Parent process started child with PID %d.\n", pid);
}
return 0;
}
This code snippet illustrates standard POSIX process creation using fork(). It compiles on Linux, macOS, and any BSD variant inherently due to POSIX compliance.
Operating systems fall into different categories regarding POSIX compliance:
A developer has written a daemon in C that utilizes standard POSIX APIs such as `fork()`, `read()`, and `write()`. This code was originally compiled and extensively tested on a Linux server. The developer is now asked to port this application to run natively on a fleet of machines running macOS.
Interprocess Communication (IPC) is the mechanism provided by an operating system that allows processes to manage shared data and synchronize actions. In UNIX and POSIX-compliant systems, processes operate in isolated memory spaces, necessitating structured methods to exchange information securely and efficiently.
UNIX provides several distinct methodologies for communication between processes, each tailored for different use cases regarding data structures, synchronization, and scope.
Pipes (|) provide a unidirectional data flow from one process to another, typically forming pipelines on the command line. They are accessed via file descriptors returned by the pipe() system call in C.
#include <unistd.h>
#include <stdio.h>
int main() {
int fd[2];
pipe(fd); // fd[0] is read end, fd[1] is write end
if (fork() == 0) {
close(fd[0]);
write(fd[1], "Hello from child", 16);
close(fd[1]);
} else {
close(fd[1]);
char buffer[20];
read(fd[0], buffer, 16);
printf("Parent read: %s\n", buffer);
close(fd[0]);
}
return 0;
}
Pipes are highly efficient and are fundamental to the UNIX philosophy of creating modular programs that compose elegantly (e.g., ls -la | grep "txt").
While anonymous pipes only connect related processes (parent and child), named pipes allow communication between unrelated processes. They exist as physical files in the filesystem, created using mkfifo. Processes open them like standard files, though the kernel handles the data transit in memory.
UNIX domain sockets (UDS) are data communications endpoints for exchanging data between processes executing on the same host operating system. Unlike pipes, sockets are bidirectional. They use the file system as their address space (e.g., /tmp/mysocket.sock), allowing access control through POSIX file permissions.
This is fundamentally the mechanism utilized by background services and daemons connecting components locally, like X Server (X11) connecting graphical applications with the display server.
Shared memory is the fastest form of IPC available in UNIX. The kernel designates a block of memory accessible by multiple independent processes. Since data does not have to be copied between process memory and kernel memory buffers, the overhead is minimal.
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
// Example setup of shared memory object mapping
int fd = shm_open("/my_shm", O_CREAT | O_RDWR, 0666);
ftruncate(fd, 4096);
void *ptr = mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
While blazingly fast, shared memory lacks implicit synchronization. Developers must employ semaphores, mutexes, or similar constructs to prevent race conditions during concurrent accesses.
A backend system architect is designing an architecture where a web server process and a specialized background database-logging process must constantly exchange highly structured data. Both processes run on the exact same physical server machine. The security team mandates that interaction must be securely restricted using standard file-system user permissions. The development team wants to avoid dealing with manual memory synchronization (like mutexes) to prevent race conditions.
A daemon is a computer program that runs as a background process, rather than being under the direct control of an interactive user. In UNIX systems, they are essential for long-running services like web servers, database engines, and network monitors. They are typically designated by appending a ‘d’ at the end of their name (e.g., sshd for SSH daemon, httpd for HTTP daemon).
To function properly across the lifetime of an operating system session, daemons must establish a specific environmental state:
setsid() to become the leader of a new process group and session. This severs its connection to the controlling terminal (TTY), making it immune to terminal-generated signals./dev/null or dedicated log files, as the daemon has no terminal to interact with./ is set as the working directory to prevent the daemon from blocking the unmounting of file systems where it was started.#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
void daemonize() {
pid_t pid = fork();
if (pid < 0) exit(EXIT_FAILURE);
if (pid > 0) exit(EXIT_SUCCESS); // Parent exits
if (setsid() < 0) exit(EXIT_FAILURE);
// Default permissions
umask(0);
// Change to root directory
chdir("/");
// Close standard file descriptors (FD)
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// Proceed to core daemon loop
while(1) {
// Core service logic
sleep(60);
}
}
This snippet illustrates the standard POSIX pattern executed by traditional C programs transforming into daemon background services.
Modern UNIX variants handle daemons through init systems like SysVinit, systemd, or launchd (macOS). These managers abstract the “daemonization” process, allowing developers to write software as standard, foreground-running programs.
Under systemd, developers define a unit service file:
[Unit]
Description=My Custom Web Service
After=network.target
[Service]
ExecStart=/usr/local/bin/my_web_service
Restart=always
User=nobody
Group=nogroup
[Install]
WantedBy=multi-user.target
Here, systemd inherently executes the application and manages the STDOUT redirection, user execution context, and backgrounding mechanics on behalf of the application developer, removing the necessity of the manual fork() and setsid() code pattern.
A junior system administrator is writing a custom C daemon to monitor disk usage continuously. They successfully use `fork()` to create a background child process and exit the parent. However, the administrator notices that whenever they close the SSH terminal session they used to launch the program, the monitoring daemon unexpectedly terminates.
UNIX file permissions fundamentally control what users and processes can do with files and directories. The basic read, write, and execute bits (rwx) for Owner, Group, and Others provide a robust security layer. However, complex real-world scenarios require advanced mechanisms beyond the standard 777 numeric notation.
Three special bits exist to handle necessary privilege escalation or restricted deletion.
When the SUID bit is set on an executable file, the process executing the file assumes the privileges of the file’s owner, not the user running it.
passwd command requires root access to modify /etc/shadow. Since /usr/bin/passwd is owned by root and has the SUID bit set, any user can run it to change their password securely.-rwsr-xr-x.chmod u+s /path/to/executableSimilar to SUID, SGID allows a process to assume the group privileges of the file’s group. When applied to a directory, it influences inheritance: any new file created within that directory automatically inherits the group ownership of the directory rather than the primary group of the user who created it.
drwxrwsr-x.chmod g+s /path/to/directoryHistorically used to keep executables “sticky” in RAM, the sticky bit is now primarily used on directories to restrict file deletion. When applied, only the file’s owner, the directory’s owner, or the root user can rename or delete files within it.
/tmp directory must be writable by everyone (chmod 777), but a user shouldn’t be able to delete another user’s temporary files.drwxrwxrwt.chmod +t /tmpStandard UNIX permissions are inherently limited to a single owner user and a single group. If a file needs to grant read access to “User A” and write access to “User B” who are not in the same group, traditional chmod fails. Access Control Lists (ACLs) solve this fine-grained security requirement.
Using setfacl and getfacl, administrators establish granular rules mapped to specific users and groups irrespective of the POSIX owner/group logic.
# View standard and potentially ACL assignments
ls -l project_data.txt
-rw-r--r--+ 1 admin staff 1024 Mar 14 project_data.txt
# The '+' indicates ACLs are present
getfacl project_data.txt
# file: project_data.txt
# owner: admin
# group: staff
user::rw-
user:alice:r--
user:bob:rw-
group::r--
mask::rw-
other::r--
In this scenario, Alice is explicitly granted read-only access, while Bob has read and write capabilities, overriding the default generic POSIX attributes.
A corporate server has a sensitive configuration file located at `/etc/application/config.yaml`. The file is currently owned by the `root` user and the `sysadmin` group. A new requirement mandates that an intern named Alice (in the group `interns`) and a contractor named Bob (in the group `contractors`) both need read and write access to exactly this file, but neither should be added to the powerful `sysadmin` group.
In POSIX-compliant operating systems, a signal is a limited form of inter-process communication (IPC) typically used in asynchronous environments. It is essentially a software interrupt delivered to a process by the OS or another process. Signals inform a process that a specific event has occurred.
When a signal is sent to a process, the OS interrupts the process’s normal flow of execution. Depending on the specific signal, the process can:
Here is a common subset of the POSIX standard signals:
SIGINT (2): Interrupt from the keyboard (typically Ctrl+C). Default action is termination.SIGKILL (9): Forced termination (“kill -9”). Cannot be caught, blocked, or ignored.SIGSEGV (11): Invalid memory reference (Segmentation fault). Usually terminates and optionally drops a core dump.SIGTERM (15): Polled termination signal. Typically instructs a process to shut down gracefully. This is the default signal sent by the kill command.A developer can use the POSIX sigaction API to define custom signal handlers.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void graceful_shutdown(int signum) {
printf("\nReceived signal %d, cleaning up resources...\n", signum);
// Perform cleanup like closing databases or temporary files
exit(0);
}
int main() {
struct sigaction sa;
sa.sa_handler = graceful_shutdown;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
// Register handler for SIGINT (Ctrl+C) and SIGTERM
if (sigaction(SIGINT, &sa, NULL) == -1 || sigaction(SIGTERM, &sa, NULL) == -1) {
perror("Error binding signal handler");
exit(1);
}
printf("Running daemon. Waiting for signals (Ctrl+C to exit)...\n");
while (1) {
// Main program loop
sleep(1);
}
return 0;
}
This ensures the application performs necessary teardown operations rather than terminating abruptly, preventing corrupted data or lingering lock files.
Writing robust signal handlers requires extreme care. Because signals are asynchronous, a handler can interrupt the main program at any point, even in the middle of executing standard library functions like printf() or malloc().
If a main program is halfway through modifying a shared data structure inside malloc(), and a signal handler also calls malloc(), the heap structure can become irreparably corrupted. POSIX guarantees only a specific set of functions (like write(), _exit(), and fsync()) as “async-signal-safe,” meaning they can be called safely within handlers.
A developer is writing a high-performance custom data-processing application in C. To ensure data isn't lost if the user presses Ctrl+C, they register a custom `SIGINT` software interrupt handler using `sigaction`. Inside this handler, the developer attempts to gracefully allocate a new block of memory using `malloc()` to store a final crash dump structure.
A distributed operating system (DOS) manages a group of independent computers and makes them appear to users as a single, coherent system. Unlike network operating systems where each node is aware of the others but operates autonomously, a distributed system fundamentally abstracts the physical locations of resources and processing power.
Distributed systems architecture describes how nodes collaborate and share computational responsibilities:
Designing a distributed OS involves solving complex problems that do not exist in single-node systems.
In a distributed system, each node has its own physical clock. Because network delays are unpredictable and clocks drift at different rates, determining the absolute global order of events is impossible.
Systems use logical clocks (like Lamport timestamps or Vector clocks) to define a partial ordering of events based on causality (“happened-before” relationships) rather than absolute physical time. For closer physical time synchronization, protocols like the Network Time Protocol (NTP) or Precision Time Protocol (PTP) are utilized.
To ensure high availability and fault tolerance, data is often replicated across multiple nodes. This introduces the challenge of data consistency.
When nodes must agree on a single value or state (e.g., electing a master node, committing a distributed transaction), they use consensus algorithms.
The CAP Theorem (Brewer’s Theorem) states that a distributed data store can provide at most two of the following three guarantees simultaneously:
Because network partitions (P) are inevitable in distributed systems, designers must choose between emphasizing Consistency (CP systems, like banking databases) or Availability (AP systems, like shopping carts or caching layers).
A global e-commerce company is designing the data architecture for its user shopping cart system. The carts are distributed across multiple regional data centers. A severe fiber-optic cable cut causes a hard network partition, immediately severing communication between the North American and European data centers, though both centers remain fully online for their local users.
The kernel is the core component of any operating system. It manages memory, processes, device drivers, and system calls. However, as the demands and complexity of computer systems have evolved, the architectural approach to kernel design has diverged into several radically different methodologies.
In a monolithic kernel, the entire operating system, including device drivers, file systems, network stacks, and core process management, runs in exactly the same memory space as the kernel itself—the highest privilege level (Ring 0 on x86). This structure offers several advantages:
However, the significant drawback of the monolithic model is stability and security. A single flaw in any subsystem—a poorly written audio driver or a bug in a networking protocol stack—can crash the entire system. Linux and BSD variants (like FreeBSD) are the most prominent examples of monolithic kernels, although modern implementations support loadable kernel modules (LKMs) that can be inserted dynamically.
Conversely, a microkernel strips the kernel down to its absolute bare minimum. The microkernel itself handles only the most critical functions: basic inter-process communication (IPC), minimal memory management, and elemental CPU scheduling. All other traditional OS services—file systems, network stacks, and device drivers—are moved out of the kernel space and run as isolated, unprivileged background processes (servers) in user space (Ring 3).
The primary disadvantage is the significant performance penalty. Because services are isolated in separate memory spaces, they must communicate heavily using IPC (like messages) routed through the microkernel. A simple file read operation might involve multiple context switches between user mode and kernel mode as messages are passed from the application to the virtual file system server, then to the disk driver server, and back.
Early implementations (like early versions of Mach) suffered heavily from this IPC overhead. Modern microkernel designs (like L4) have relentlessly optimized IPC paths, demonstrating performance comparable to monolithic kernels for many workloads.
To balance the high performance of monolithic kernels with the modularity of microkernels, operating system architects developed hybrid kernels. These designs typically maintain a monolithic architecture for critical path performance (keeping essential file systems and networking in kernel space) but adopt microkernel concepts for extensibility, moving non-critical or volatile components (like certain peripheral drivers or distinct subsystems) into user space.
Windows NT (the kernel powering modern Windows 10/11) and XNU (the kernel for macOS and iOS) are notable examples. XNU explicitly combines a Mach microkernel core (handling IPC and scheduling) with substantial BSD monolithic components (handling networking and POSIX APIs) in the same address space.
A software security contractor is testing vulnerability profiles for two distinct operating systems. OS Alpha utilizes a traditional Monolithic kernel, while OS Beta operates on a strict Microkernel design. The contractor purposefully injects a fatal buffer overflow vulnerability into a third-party audio device driver on both systems, triggering an illegal memory access.
An embedded operating system (EOS) is structurally distinct from traditional desktop or server OSs (like Windows or full-fat Linux distributions). It is highly specialized, designed explicitly to support applications running on embedded computer systems—often with severe constraints on processing power, memory footprint, and power consumption.
Embedded architectures prioritize efficiency and reliability over general-purpose flexibility. Devices ranging from digital watches and anti-lock braking systems (ABS) to smart thermostats and complex industrial controllers all rely on embedded operating systems.
Key differences from general-purpose OSs include:
The embedded landscape is broadly divided into Real-Time Operating Systems (RTOS) and minimal Linux derivatives.
For devices requiring robust networking (TCP/IP stacks), graphical user interfaces (GUIs), or complex file systems, developers often turn to Embedded Linux. Projects like Yocto or Buildroot allow engineers to strip away unnecessary desktop components (like display servers or extensive package managers) and compile a highly customized, minimal Linux kernel and user space tailored exactly to the target hardware (e.g., ARM Cortex-A processors in smart TVs or automotive infotainment systems).
The primary advantage is leveraging the massive ecosystem of existing Linux drivers, libraries, and security patches. However, even a minimal Linux footprint is fundamentally non-deterministic and requires significantly more RAM and processing power than a bare-metal microcontroller OS.
For extreme resource constraints (e.g., battery-powered IoT edge sensors using ARM Cortex-M or RISC-V microcontrollers with only kilobytes of SRAM), running a full kernel is impossible.
In these environments, developers use specialized bare-metal programming or a minimal RTOS like FreeRTOS or Zephyr. These are structurally essentially static libraries linked directly with the application code. They provide elemental features:
// Conceptually, a FreeRTOS application resembles a single infinite loop
#include "FreeRTOS.h"
#include "task.h"
void vTaskFunction( void * pvParameters ) {
for( ;; ) {
// Perform highly specific, repetitive task
ReadSensorData();
vTaskDelay( pdMS_TO_TICKS( 1000 ) ); // Yield execution
}
}
int main( void ) {
xTaskCreate( vTaskFunction, "SensorTask", 1000, NULL, 1, NULL );
vTaskStartScheduler(); // The RTOS takes control here
return 0; // Should never reach this point
}
A biomedical engineering team is developing a prototype for a highly restricted, battery-powered wearable heart-rate detector. The hardware logic board utilizes a low-power ARM microcontroller with exactly 64 Kilobytes of SRAM memory. Their software requires sampling an analog sensor exactly once every 10 milliseconds without fail, to mathematically derive pulse patterns before pushing logs over simple serial communication.
A Real-Time Operating System (RTOS) fundamentally differs from a general-purpose operating system (GPOS) in its primary design objective. While a GPOS (like Windows or Linux) strives to maximize overall system throughput and ensure fairness among competing processes, an RTOS is engineered to guarantee determinism and strict adherence to timing constraints.
In real-time environments, calculating the correct logical result is insufficient; the result must be produced within a specific, absolute timeframe. A logically correct answer delivered late represents a critical system failure.
Real-time systems are categorized by the severity of the consequences when a timing deadline is missed:
To achieve hard timing guarantees, an RTOS employs distinct process scheduling algorithms. General-purpose schedulers (like the Completely Fair Scheduler in Linux) dynamically adjust process priorities to balance CPU time. RTOS schedulers are inflexible and highly predictable.
Deterministic scheduling introduces complex race conditions, most notably priority inversion. This occurs when a high-priority task is blocked from executing, continuously preempted by a lower-priority task.
Imagine three tasks: High (), Medium (), and Low ().
The scenario: (the most critical task) is effectively blocked by (a mid-priority task), completely subverting the deterministic priority hierarchy. This exact bug famously crashed the NASA Mars Pathfinder probe in 1997.
To solve priority inversion, RTOS kernels implement priority inheritance. When blocks on a resource held by , the operating system temporarily elevates the priority of to match . This prevents from preempting . finishes its operation rapidly at the elevated priority, releases the lock, and returns to its true low priority, allowing to immediately execute and meet its deadline.
A robotics engineer is designing the flight controller firmware for an autonomous planetary reconnaissance drone. The embedded kernel must juggle exactly two continuous periodic tasks. `Task A` processes gyroscopic stabilization readings and runs every 20 milliseconds. `Task B` handles compressing and writing telemetry metadata to internal flash storage, running every 100 milliseconds. The system uses a strict priority-based scheduling loop.
Operating systems have historically evolved from simple batch-processing monitors into massive, complex millions-of-lines-of-code monolithic architectures. As computing hardware radically shifts toward cloud infrastructure, edge devices, and specialized AI accelerators, the fundamental assumptions surrounding OS design are shifting in response.
Current cloud computing relies heavily on hypervisors running complete guest operating systems (e.g., a full Linux kernel) just to host single-application containers or microservices. This entails massive redundancy; the guest OS duplicates the scheduling, filesystem, and networking stacks already handled by the hypervisor or host OS beneath it.
Unikernels eliminate this redundancy through extreme specialization. By using a Library Operating System architecture, an application developer selectively compiles only the specific OS components (like a TCP/IP stack or minimal memory allocator) their single application requires, statically linking them together.
The resulting artifact is a single, highly optimized, non-preemptible bootable image.
bash or ssh), or user-space separation (everything runs in kernel mode), an attacker has virtually no tools available if they manage to compromise the application.Originally designed to run high-performance C/C++ or Rust code securely within web browsers, WebAssembly (Wasm) is evolving into a universal, system-agnostic bytecode format.
Through the WebAssembly System Interface (WASI), Wasm modules can now interact with the underlying host operating system (accessing files, networking, and clocks) safely. This creates a highly secure, truly “write once, run anywhere” sandbox. Operating system researchers are increasingly viewing Wasm not just as an application format, but as a potential foundational security layer to replace traditional POSIX process boundaries, enabling incredibly fast, secure execution of untrusted code directly by the OS or specialized edge computing networks.
The vast majority of critical vulnerabilities in major monolithic kernels (Linux, Windows NT, XNU) originate from memory safety bugs (buffer overflows, use-after-free errors) inherent to the C and C++ programming languages used to build them.
A major paradigm shift is underway replacing vulnerable C code with memory-safe languages—specifically Rust.
The strict compile-time borrow checker in Rust eliminates entire classes of runtime memory vulnerabilities without incurring the performance penalty of a garbage collector, aligning perfectly with strict kernel performance requirements.
A cloud architect is designing an execution platform for a massive 'serverless' computing environment (similar to AWS Lambda). The platform will constantly spawn and destroy millions of tiny, independent, untrusted code functions per second based on user triggers. The architect must isolate these functions to limit their attack surface if compromised, but also guarantee near-instantaneous (sub-millisecond) boot times because deploying a heavy, duplicated kernel for every function severely tanks the server capacity.
To build an operating system, you cannot use standard libraries like stdio.h because they rely on an underlying OS that doesn’t exist yet. Instead, we must talk directly to the hardware. In this laboratory, we will create a minimal Multiboot-compliant kernel.
boot.s)The bootloader (like GRUB) needs a specific “header” to recognize our binary as a kernel. We use x86 assembly to define this header and set up a basic execution environment.
; Declare constants for the Multiboot header.
MAGIC equ 0x1BADB002
FLAGS equ 0x00
CHECKSUM equ -(MAGIC + FLAGS)
; The Multiboot header must be within the first 8KB of the file.
section .multiboot
dd MAGIC
dd FLAGS
dd CHECKSUM
section .text
extern kmain
global _start
_start:
; Set up a stack (the kernel needs one for C functions)
mov esp, stack_top
; Call the C kernel entry point
call kmain
; If kmain returns, just halt the CPU
cli
.hang:
hlt
jmp .hang
section .bss
align 16
stack_bottom:
resb 16384 ; 16 KB for the stack
stack_top:
kernel.c)In a “bare metal” environment, we print to the screen by writing directly to VGA Text Buffer at memory address 0xB8000. Each character on the screen is represented by two bytes: one for the ASCII character and one for the color attribute.
// The VGA text buffer starts at 0xB8000.
volatile char* vga_buffer = (volatile char*)0xB8000;
void kmain(void) {
const char* str = "Hello, OS World!";
int i = 0;
int j = 0;
// Clear the screen (filling with black background)
for (i = 0; i < 80 * 25 * 2; i += 2) {
vga_buffer[i] = ' ';
vga_buffer[i+1] = 0x07; // Light grey on black
}
// Write "Hello, OS World!" to the top-left corner
i = 0;
while (str[i] != '\0') {
vga_buffer[j] = str[i]; // ASCII character
vga_buffer[j+1] = 0x0F; // Color: White on Black
i++;
j += 2;
}
}
In kernel.c, we use the volatile keyword. This tells the C compiler that the value at 0xB8000 can change outside of the program’s control (it’s hardware memory). Without volatile, an aggressive optimizer might skip writing to that memory because it doesn’t see any “read” operation.
C functions rely on a Stack to store return addresses and local variables. Since the hardware doesn’t provide a stack automatically, our assembly code (boot.s) must manually allocate 16KB of space and point the esp (Stack Pointer) register to the top of it before calling kmain.
What is the magic memory address where we must write data to see text on an x86 PC in text mode?
/* The base address for VGA text memory */\n0x800
In the next lesson, we will see how to link these two files together and run them in a virtual machine.
Having written the code (boot.s and kernel.c), we now need to transform it into a single binary file that a computer can execute. This requires a specialized tool: a Linker Script.
linker.ld)Standard programs are placed in memory wherever the OS feels like. For a kernel, we must be much more precise. Most bootloaders expect the kernel to be loaded at 1 Megabyte (0x100000).
/* The entry point defined in boot.s */
ENTRY(_start)
SECTIONS {
/* Most kernels start at 1MB */
. = 1M;
/* First, the Multiboot header and the code */
.text BLOCK(4K) : ALIGN(4K) {
*(.multiboot)
*(.text)
}
/* Read-only data */
.rodata BLOCK(4K) : ALIGN(4K) {
*(.rodata)
}
/* Read-write data (initialized) */
.data BLOCK(4K) : ALIGN(4K) {
*(.data)
}
/* Read-write data (uninitialized) and stack */
.bss BLOCK(4K) : ALIGN(4K) {
*(COMMON)
*(.bss)
}
}
To compile these, you need a “bare-metal” cross-compiler. If you are on Linux, you can use the standard gcc and nasm.
nasm -f elf32 boot.s -o boot.o
We use special flags to tell GCC not to use any standard C libraries or OS-dependent features.
gcc -m32 -c kernel.c -o kernel.o -ffreestanding -O2 -Wall -Wextra
This combines the object files using our linker script.
ld -m elf_i386 -T linker.ld boot.o kernel.o -o myos.bin
Developing an OS on real hardware is slow (and can be dangerous). Instead, we use an Emulator like QEMU. It simulates an x86 PC in software.
To run your new kernel, simply execute:
qemu-system-i386 -kernel myos.bin
If everything worked, a new window will appear with a black screen and the text “Hello, OS World!” in the top-left corner. You have successfully booted your own code!
| Tool | Purpose | Output |
|---|---|---|
| NASM | Assembler | Object File (.o) |
| GCC | C Compiler (with -ffreestanding) | Object File (.o) |
| LD | Linker (with -T linker.ld) | Executable Binary (.bin) |
| QEMU | Emulator | Simulated PC Execution |
Which line in the linker script tells the computer where to start executing the code when the kernel is loaded?
()
Congratulations! You have moved from understanding how kernels manage processes to actually writing a “heart” that beats on its own. This is the first step on a long journey that leads to building schedulers, file systems, and eventually, a fully functional operating system.