Back to Technology

Part 24: Capstone Projects

January 31, 2026 Wasil Zafar 45 min read

Apply your knowledge by building practical OS components—a Unix shell, thread pool, paging simulator, and more.

Table of Contents

  1. Introduction
  2. Project 1: Unix Shell
  3. Project 2: Thread Pool
  4. Project 3: Paging Simulator
  5. Project 4: CPU Scheduler
  6. Project 5: Simple File System
  7. Project 6: Memory Allocator
  8. Project Extensions
  9. Series Conclusion

Introduction

The best way to solidify your understanding of operating systems is to build OS components yourself. These capstone projects will challenge you to apply concepts from the entire series.

Series Context: This is Part 24 of 24—the final chapter of the Computer Architecture & Operating Systems Mastery series. We bring everything together through hands-on projects.

Computer Architecture & OS Mastery

Your 24-step learning path • Currently on Step 24
1
Part 1: Foundations of Computer Systems
System overview, architectures, OS role
2
Digital Logic & CPU Building Blocks
Gates, registers, datapath, microarchitecture
3
Instruction Set Architecture (ISA)
RISC vs CISC, instruction formats, addressing
4
Assembly Language & Machine Code
Registers, stack, calling conventions
5
Assemblers, Linkers & Loaders
Object files, ELF, dynamic linking
6
Compilers & Program Translation
Lexing, parsing, code generation
7
CPU Execution & Pipelining
Fetch-decode-execute, hazards, prediction
8
OS Architecture & Kernel Design
Monolithic, microkernel, system calls
9
Processes & Program Execution
Process lifecycle, PCB, fork/exec
10
Threads & Concurrency
Threading models, pthreads, race conditions
11
CPU Scheduling Algorithms
FCFS, RR, CFS, real-time scheduling
12
Synchronization & Coordination
Locks, semaphores, classic problems
13
Deadlocks & Prevention
Coffman conditions, Banker's algorithm
14
Memory Hierarchy & Cache
L1/L2/L3, cache coherence, NUMA
15
Memory Management Fundamentals
Address spaces, fragmentation, allocation
16
Virtual Memory & Paging
Page tables, TLB, demand paging
17
File Systems & Storage
Inodes, journaling, ext4, NTFS
18
I/O Systems & Device Drivers
Interrupts, DMA, disk scheduling
19
Multiprocessor Systems
SMP, NUMA, cache coherence
20
OS Security & Protection
Privilege levels, ASLR, sandboxing
21
Virtualization & Containers
Hypervisors, namespaces, cgroups
22
Advanced Kernel Internals
Linux subsystems, kernel debugging
23
Case Studies
Linux vs Windows vs macOS
24
Capstone Projects
Shell, thread pool, paging simulator
You Are Here
Learn By Building: These projects range from a few hundred to a few thousand lines of C code. Start simple, then add features incrementally.

Project 1: Unix Shell

Build a simple command-line shell supporting commands, pipes, and I/O redirection.

// Mini Shell - Core structure
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAX_LINE 1024
#define MAX_ARGS 64

void execute_command(char **args) {
    pid_t pid = fork();
    
    if (pid == 0) {
        // Child process
        execvp(args[0], args);
        perror("execvp failed");
        exit(1);
    } else if (pid > 0) {
        // Parent waits
        int status;
        waitpid(pid, &status, 0);
    } else {
        perror("fork failed");
    }
}

int main() {
    char line[MAX_LINE];
    char *args[MAX_ARGS];
    
    while (1) {
        printf("mysh> ");
        if (!fgets(line, MAX_LINE, stdin)) break;
        
        // Parse input into args array...
        // Handle built-ins (cd, exit)...
        // Handle pipes and redirection...
        
        execute_command(args);
    }
    return 0;
}
Features to Add: Built-in commands (cd, pwd, exit), pipes (cmd1 | cmd2), I/O redirection (>, <), background jobs (&), command history.

Project 2: Thread Pool

Implement a reusable thread pool for efficient task execution.

// Thread Pool - Core structure
#include <pthread.h>
#include <stdlib.h>

typedef struct {
    void (*function)(void *);
    void *argument;
} task_t;

typedef struct {
    pthread_t *threads;
    task_t *task_queue;
    int queue_size;
    int queue_front, queue_rear, queue_count;
    pthread_mutex_t lock;
    pthread_cond_t not_empty;
    pthread_cond_t not_full;
    int shutdown;
} threadpool_t;

void *worker_thread(void *arg) {
    threadpool_t *pool = (threadpool_t *)arg;
    
    while (1) {
        pthread_mutex_lock(&pool->lock);
        
        while (pool->queue_count == 0 && !pool->shutdown) {
            pthread_cond_wait(&pool->not_empty, &pool->lock);
        }
        
        if (pool->shutdown) {
            pthread_mutex_unlock(&pool->lock);
            break;
        }
        
        // Dequeue task and execute
        task_t task = pool->task_queue[pool->queue_front];
        pool->queue_front = (pool->queue_front + 1) % pool->queue_size;
        pool->queue_count--;
        
        pthread_cond_signal(&pool->not_full);
        pthread_mutex_unlock(&pool->lock);
        
        (task.function)(task.argument);
    }
    return NULL;
}

Project 3: Paging Simulator

Simulate virtual memory with page tables and replacement algorithms.

// Paging Simulator - Core structure
#include <stdio.h>
#include <stdlib.h>

#define NUM_PAGES 256
#define NUM_FRAMES 64

typedef struct {
    int valid;
    int frame_number;
    int referenced;
    int modified;
} page_table_entry_t;

typedef struct {
    int page_number;
    int loaded;
} frame_t;

page_table_entry_t page_table[NUM_PAGES];
frame_t physical_memory[NUM_FRAMES];
int page_faults = 0;

int clock_hand = 0;  // For CLOCK algorithm

int clock_replace() {
    while (1) {
        if (!page_table[physical_memory[clock_hand].page_number].referenced) {
            int victim = clock_hand;
            clock_hand = (clock_hand + 1) % NUM_FRAMES;
            return victim;
        }
        page_table[physical_memory[clock_hand].page_number].referenced = 0;
        clock_hand = (clock_hand + 1) % NUM_FRAMES;
    }
}

void access_page(int page_number) {
    if (!page_table[page_number].valid) {
        page_faults++;
        int frame = clock_replace();
        // Evict old page, load new page...
        page_table[page_number].valid = 1;
        page_table[page_number].frame_number = frame;
    }
    page_table[page_number].referenced = 1;
}

Project 4: CPU Scheduler

Simulate different scheduling algorithms and compare their performance.

// Scheduler Simulator
typedef struct {
    int pid;
    int arrival_time;
    int burst_time;
    int remaining_time;
    int completion_time;
    int waiting_time;
    int turnaround_time;
} process_t;

void round_robin(process_t *processes, int n, int quantum) {
    int time = 0;
    int completed = 0;
    int *queue = malloc(n * sizeof(int));
    int front = 0, rear = 0;
    
    // Add processes as they arrive
    // Execute for quantum or until burst complete
    // Track waiting time and turnaround time
    // Calculate average metrics
    
    printf("Avg Waiting Time: %.2f\n", avg_waiting);
    printf("Avg Turnaround Time: %.2f\n", avg_turnaround);
}

Project 5: Simple File System

Implement a basic file system with inodes, directories, and data blocks.

// Simple File System structures
#define BLOCK_SIZE 4096
#define NUM_BLOCKS 1024
#define NUM_INODES 128

typedef struct {
    int type;        // 0=free, 1=file, 2=directory
    int size;
    int blocks[12];  // Direct blocks
    int indirect;    // Indirect block
    char name[256];
} inode_t;

typedef struct {
    char data[BLOCK_SIZE];
} block_t;

inode_t inodes[NUM_INODES];
block_t disk[NUM_BLOCKS];
int free_block_bitmap[NUM_BLOCKS];

int fs_create(const char *name);
int fs_write(int fd, const void *buf, int size);
int fs_read(int fd, void *buf, int size);
int fs_delete(const char *name);

Project 6: Memory Allocator

Build your own malloc() implementation with different allocation strategies.

// Custom Memory Allocator
typedef struct block_header {
    size_t size;
    int free;
    struct block_header *next;
} block_header_t;

static block_header_t *free_list = NULL;
static void *heap_start = NULL;

void *my_malloc(size_t size) {
    // First-fit: find first block that fits
    block_header_t *curr = free_list;
    
    while (curr) {
        if (curr->free && curr->size >= size) {
            curr->free = 0;
            // Optionally split block if much larger
            return (void *)(curr + 1);
        }
        curr = curr->next;
    }
    
    // No suitable block, extend heap with sbrk()
    return extend_heap(size);
}

void my_free(void *ptr) {
    if (!ptr) return;
    block_header_t *header = (block_header_t *)ptr - 1;
    header->free = 1;
    // Coalesce with adjacent free blocks
}

Project Extensions

Challenge Extensions:
══════════════════════════════════════════════════════════════

Shell:
  • Tab completion
  • Signal handling (Ctrl+C, Ctrl+Z)
  • Shell scripting support

Thread Pool:
  • Dynamic resizing
  • Task priorities
  • Future/Promise pattern

Paging:
  • Working set model
  • Multi-level page tables
  • TLB simulation

File System:
  • Journaling
  • Symbolic links
  • FUSE integration (real mountable FS)

Series Conclusion

Congratulations on completing the Computer Architecture & Operating Systems Mastery series! You've journeyed from digital logic gates to kernel internals.

What You've Learned:
  • How CPUs execute instructions (fetch-decode-execute)
  • Memory hierarchy from registers to disk
  • Process and thread management
  • Scheduling, synchronization, deadlocks
  • Virtual memory and file systems
  • Virtualization and kernel internals
Next Steps: Build these projects. Read kernel source code. Contribute to open-source OS projects. The best systems programmers learn by building.