DEV Community

Cover image for How I made my own Operating System (OS)
Uthsob Chakraborty
Uthsob Chakraborty

Posted on

How I made my own Operating System (OS)

I made my Own Operating System named “NO OS”; Here is the log of NO OS.

Why is this done?

The main reason behind building NO OS is learning.

In our Operating System course, we were told that the best way to understand how an OS really works is to try building one. While NO OS is not a fully functional operating system, it helps in understanding:

  • Boot process: how a CPU starts and how the control passes from bootloader → kernel.
  • Kernel basics: setting up memory, stack, and jumping into C code from assembly.
  • Memory management: simulating frame allocation, freeing memory, and showing system usage.
  • Low-level programming: working without libraries, writing directly to hardware like VGA text memory.

For programmers (especially students like me), it’s very easy to forget what we were trying to do after a few weeks/months.

That’s why documenting why this project exists is almost more important than the code itself — so future contributors (or even my future self) know the intention:

This project is not meant to be a production OS, but a playground for learning OS concepts step by step.

What is this?

Imagine turning on your computer: you see the screen light up, the operating system loads, and then you can open apps, browse the internet, or play music.

NO OS is a baby version of that idea.

But instead of doing all those complicated things, NO OS only does two very simple things right now:

  1. Shows text on the screen — so the system can "say hello" when it starts.
  2. Manages memory in a toy way — like keeping track of which parts of your desk are empty and which parts are already used.

That’s it. NO OS doesn’t run apps, doesn’t connect to the internet, and doesn’t have graphics. It’s called “NO OS” because it’s really a Non-Operational Operating System, a joke name to remind us that it doesn’t do much yet.

But it’s a stepping stone: with this base, future improvements like adding keyboard input, handling files, or running small programs could be built.

So for someone who has never programmed:

  • Think of NO OS as a blank notebook with the first two pages written.
  • It doesn’t have stories yet, but the notebook is ready for you (or others) to start filling it.

Background:

In this semester (Summer 2025), I got the course Operating System. And in the Lab of same course we required to build a project. Our Faculty said - "If you really want to understand how a Operating System Works you should build it." So here i'm building my own Operating System called "NO OS";

Just like the name suggests, No OS has no work. It’s truly “Non Operational” OS. But what i learned from building this-

Firstly we need to know what a Operating System is and what it does-

Definition: A operating System is a system software that manages computer hardware and software resources, providing common services for computer programs.

Work:

An Operating System basically does following works;

  • Resource Management
  • Process Management
  • File Management
  • User Interface
  • Input/Output (I/O) Management
  • Security

But building such a fully working OS with all functions is very hard; especially single-handedly and when you have time constrains and you also have other work and personal life. So in that meantime possibly i built tiny portion or base of a Operating System.

Here is how NO OS works and what it does-

First look at the file structure. We have following files;

NoOS/
├── bochsrc.txt
├── iso/
   └── boot/
       └── grub/
           └── grub.cfg
├── kernel.c
├── link.ld
├── loader.s
├── Makefile
├── memory.c
├── memory.h
├── README.md
└── types.h

Enter fullscreen mode Exit fullscreen mode

Here in if you look at the github repo you’ll find the boot-loader folder as well; i thought about creating the boot loader from scratch but didn’t have the courage to build that too. So just used grub.cfg that just points to boot from /boot/kernel.elf file; Oh also speaking of grub we used Bochs to emulate and run our OS in development mode. That's what the configuration file kept in bochsrc.txt it simulates an x86 architecture PC. In out config we just pointed and gave 32 Megabyte (MB) of RAM and uses SDL as display library to display.

Loader Script: Anyway, loader.s It’s the real entry point for this operating system after the boot-loader (GRUB) passes control to the kernel. This file takes care of the low-level setup before calling C kernel code. This is assembly code that normally has 4 core responsibilities-

  1. Multiboot Header:
  2. Stack Setup:
  3. Calls the Kernel:
  4. Halts the CPU:
global loader

extern kernel_main

MAGIC_NUMBER equ 0x1BADB002     
FLAGS        equ 0x0            
CHECKSUM     equ -MAGIC_NUMBER  

section .text
    align 4                     
    dd MAGIC_NUMBER             
    dd FLAGS                    
    dd CHECKSUM               

loader:
    mov esp, kernel_stack_top      ; Set up the stack pointer
    call kernel_main               ; Call the C kernel entry point

    cli                            ; Disable interrupts
.hang:
    hlt                            ; Halt CPU
    jmp .hang                      ; Infinite halt loop

section .bss
    align 4
    kernel_stack_bottom:
        resb 16384                 ; Reserve 16KB for the stack
    kernel_stack_top:
Enter fullscreen mode Exit fullscreen mode

Kernel and Memory Management:

Kernel is the MAGIC file the core of an OS. It’s the main file that does all the magic of a OS in the behind. Including all the core job of OS. The NO OS kernel is written in C.

As i have explained what are the issue for me to build a full kernel. But to call it a OS i have included only 3 things.

  • Managing or Showing Output/Display Management.
  • User Interface though just static
  • And Memory Management (to be specific how it’s allocating memory)

Let’s discuss on more detailed about each one;

As its allocate memory so i have created a separate code file for manage memory on memory.c and memory.h that simulates-

  • Physical memory frame allocation using bitmap
  • Simple heap allocator for kernel memory
  • Memory information display
void *allocate_frame(void)
{
    for (uint32_t i = 0; i < pmm.total_frames; i++)
    {
        uint32_t byte = i / 32;
        uint32_t bit = i % 32;

        if (!(pmm.bitmap[byte] & (1 << bit)))
        {
            pmm.bitmap[byte] |= (1 << bit);
            pmm.used_frames++;
            return (void *)(i * FRAME_SIZE);
        }
    }
    return NULL; // Out of memory
}
Enter fullscreen mode Exit fullscreen mode

This function is a helper function for frame allocation

and then this code check the Free frame-

void free_frame(void *frame)
{
    uint32_t frame_num = (uint32_t)frame / FRAME_SIZE;
    uint32_t byte = frame_num / 32;
    uint32_t bit = frame_num % 32;

    pmm.bitmap[byte] &= ~(1 << bit);
    pmm.used_frames--;
}
Enter fullscreen mode Exit fullscreen mode

And this is for the UI to show memory information. and finally get the free memory block.

void show_memory_info(void)
{
    uint32_t free_mem = get_free_memory();
    printf("Memory Info:\n");
    printf("  Total: %d KB\n", pmm.memory_size / 1024);
    printf("  Used:  %d KB\n", (pmm.memory_size - free_mem) / 1024);
    printf("  Free:  %d KB\n", free_mem / 1024);
    printf("  Frames: %d total, %d used\n", pmm.total_frames, pmm.used_frames);
}

uint32_t get_free_memory(void)
{
    return (pmm.total_frames - pmm.used_frames) * FRAME_SIZE;
}

Enter fullscreen mode Exit fullscreen mode

All these memory management code come handy to current memory management simulation and future memory related task.

Let’s get back to the kernel.c-

#include "types.h"
#include "memory.h"

#define VGA_WIDTH 80
#define VGA_HEIGHT 25
#define VGA_MEMORY 0xB8000

static uint16_t *vga_buffer = (uint16_t *)VGA_MEMORY;
static size_t vga_row = 0;
static size_t vga_column = 0;
static uint8_t vga_color = 15;

void clear_screen(void);
void putchar(char c);
void print(const char *str);
void printf(const char *format, ...);
void print_number(uint32_t num);
void print_hex(uint32_t num);
void demonstrate_memory_operations(void);
Enter fullscreen mode Exit fullscreen mode

On the entry of the kernel we just called all the required function including memory file; And then defined our UI/display size with VGA (Video Graphics Array) that does the display management and the basic display usage. How it shows with the display are in code if you read it carefully you will understand. And then finally the UI; So to make it minimal kept just as CLI UI

void kernel_main(void)
{
    clear_screen();

    print("====================\n");
    print("  Welcome to NO OS!\n\n");
    print("====================\n");
    print("This is NO OS that has no work! That does nothing currently but simulate-\n");
    print("- Display text on screen\n");
    print("- Handle basic memory management\n");
    print("\n");

    print("Initializing memory manager...\n");
    init_memory_manager(32 * 1024 * 1024);
    print("Memory manager initialized successfully!\n\n");

    demonstrate_memory_operations();

    print("\nSimulation of Memory management!!\n");
    print("Debug: NoOS is running successfully! The system is alive and operational.\n");
    while (1)
    {
        asm volatile("hlt");
    }
}
Enter fullscreen mode Exit fullscreen mode

As there is no code implemented to interrupt handling here so it’s just static screen showing basic UI and dynamic memory allocation part. Here is a screenshot of the working NO OS.

That’s the whole overview of NO OS;

Makefile and Linker:

As we have left with these 2 file now lets discuss about them briefly.

link.ld tells the linker how to arrange the kernel binary in memory, sets the load address to 1MB, aligns sections to 4KB, and specifies the entry point so the OS boots and runs correctly.

and then the Makefile is the custom make command that creates the ISO, it has some other commands to clean, run and generating the ISO.

Totally completed the system.

OHHH
**

Contribution

NO OS is a learning project, and there’s a lot of room to grow. If you’re interested in operating system development or just want to experiment with low-level programming, you’re welcome to contribute!

Here are some ideas for contributions:

  • Add Interrupt Handling – implement a simple Interrupt Descriptor Table (IDT) and handle basic hardware interrupts (keyboard, timer).
  • Improve Memory Management – extend the frame allocator into a proper paging/virtual memory system.
  • Scheduler – add process structures and a round-robin (or any) scheduling algorithm.
  • Filesystem – experiment with a basic in-memory filesystem or integrate FAT12/16 for disk I/O.
  • Drivers – simple keyboard, timer, and screen drivers.
  • Documentation & Tutorials – improve explanations, add diagrams, or create step-by-step setup instructions for beginners.

If you’d like to contribute:

  1. Fork the repository. — https://github.com/uthsobcb/NoOS
  2. Open a Pull Request.

Top comments (0)