DEV Community

Cover image for What Happens When Code Runs Without an OS? Exploring CPU Execution, Memory, and Interrupts
Bhushitha Hashan
Bhushitha Hashan

Posted on

What Happens When Code Runs Without an OS? Exploring CPU Execution, Memory, and Interrupts

When writing code that runs without an operating system "bare-metal code", you're not dealing with the luxuries of system calls, drivers, or protected memory. You're dealing directly with the CPU and memory as they are. Understanding how the CPU actually behaves in this state,how it executes, loops, crashes, and responds to interrupts is foundational for developers and systems programmers.

Let’s break this down from the ground up.


Instruction Flow: The CPU Moves Forward By Default

At its simplest, the CPU is a machine that fetches instructions from memory and executes them, one after another. This sequential behavior is driven by a special register: the Program Counter (PC), or in x86, the Instruction Pointer (IP).

The Program Counter holds the address of the next instruction to execute. After executing an instruction, the CPU increases the PC to point to the following one. That’s how programs “move forward” .line by line, byte by byte.

Example in x86 assembly:

mov eax, 5
add eax, 2
Enter fullscreen mode Exit fullscreen mode

After mov, the PC is incremented, and it proceeds to add. Unless told otherwise, the CPU just keeps marching forward.


Control Flow Is Yours: Loops, Jumps, Branches

But that "forward march" isn’t locked in. Assembly provides full control of program flow through jump instructions (jmp), function calls (call), loops (loop, jnz, etc.), and conditional branches.

For example, you can build a loop like this:

start:
  dec eax
  jnz start
Enter fullscreen mode Exit fullscreen mode

This code counts down from whatever value is in eax, repeatedly jumping back to start until eax hits zero. You’ve created a backward loop, manually. The CPU isn't "thinking" in directions it's just following whatever address it's told to fetch next.


Memory Isn’t Sequential: You Can Reuse and Overwrite Freely

The CPU also doesn't treat memory like a one-way street. It's just a flat space of addresses and you can write, erase, and jump around in it freely. For instance:

mov [0x1000], eax    ; write eax to memory
mov eax, [0x1000]    ; read it back
Enter fullscreen mode Exit fullscreen mode

That’s direct memory access. No protection, no guards unless you write them.

You could even store instructions themselves in RAM and jump between them as needed. This lets you build dynamic code flows, jump tables, or even self-modifying code (if you're careful and a little daring).


Manual Loops Across Memory Locations

You don’t even have to put your code in one spot. Here’s an example of a manual loop jumping between non-sequential locations:

; Assume:
; 0x0000: mov eax, 5
; 0x0003: jmp 0x0010
; ...
; 0x0010: add eax, 2
; 0x0013: jmp 0x0000
Enter fullscreen mode Exit fullscreen mode

In this flow:

  1. The CPU starts at 0x0000, sets eax to 5,
  2. Jumps to 0x0010, adds 2 to eax,
  3. Jumps back to 0x0000 — and loops.

This kind of manual control over memory and instruction flow is exactly how low-level systems work .The CPU is simply obeying pointers and doing what it’s told.


Crashes and CPU Chaos

If you mess up and overwrite your instructions, jump to invalid memory, or create an infinite loop there’s no safety net. No OS to save you. The CPU may freeze, reboot, or enter undefined behavior.

Here’s an infinite loop that’ll run forever:

start:
  jmp start
Enter fullscreen mode Exit fullscreen mode

And a more useful loop:

mov al, 0
loop_start:
  inc al
  cmp al, 10
  jne loop_start
hlt
Enter fullscreen mode Exit fullscreen mode

This code counts from 0 to 9 and then halts the CPU. That hlt instruction is essential otherwise the CPU would just keep reading random memory and likely crash.


The CPU Never Stops ,Unless You Tell It To!!

The CPU doesn’t know when to stop. It doesn’t assume your program ends after a few instructions. If you don’t explicitly stop it with hlt or reset it via hardware, it just keeps going. It’ll execute whatever’s next — even if that means garbage data in RAM.

This is why real-mode programs and BIOS routines are so strict about handling “what’s next.” In the absence of guardrails, everything is your responsibility.


Interrupts Still there Even Without an OS

Let’s take a deeper dive into interrupts. Yes, even with no OS at all, the hardware like a keyboard or timer can still fire interrupts.

For instance:

  • The keyboard fires IRQ1
  • The system timer fires IRQ0
  • These go to the Programmable Interrupt Controller (PIC)
  • The PIC forwards them to the CPU

The CPU, on receiving an interrupt, checks its Interrupt Vector Table (IVT) to find the address of the relevant handler.

But here's the catch: if you haven’t set that handler up, the CPU jumps into random memory. That’s an instant crash.


BIOS: The Unsung Hero of Early Execution

During early boot, you're not completely alone.if BIOS is still active, it provides basic interrupt services. You can use them like functions:

mov ah, 0
int 16h   ; BIOS keyboard input
Enter fullscreen mode Exit fullscreen mode

These BIOS interrupts work only in real mode. As soon as you leave that mode or overwrite the handlers, you're on your own.


The Interrupt Vector Table (IVT): What It Is and Where It Lives

The IVT is a fixed table in low memory, from 0x0000 to 0x03FF. It holds 256 entries, each 4 bytes (2 bytes offset + 2 bytes segment), mapping interrupt numbers to handler addresses.

So:

  • INT 0x09 → CPU looks at 0x0024 to 0x0027
  • Reads the pointer there
  • Jumps to that address to run the keyboard handler

The BIOS sets this up on boot, giving you basic services. But if you're writing your own OS or bootloader, you'll need to set your own IVT and do it carefully.


RAM Is Empty at Power-On! So Where Does IVT Come From?

RAM is volatile,and it holds nothing on startup. So how does the IVT get there?

It’s written by the BIOS.

Here’s what happens at power-on:

  1. Power is turned on
  2. The CPU begins executing at address 0xFFFF0 — in ROM
  3. BIOS code runs:
  • Initializes RAM, screen, keyboard
  • Writes the IVT into RAM
  • Loads a bootloader (e.g., GRUB or your own code)

This boot process is precise and hardware specific. Without BIOS or UEFI, you’d have to manually write this entire sequence, including the IVT.


Disabling Interrupts With CLI

Until your IVT is ready, interrupts are dangerous. A timer or keyboard interrupt might fire and jump to uninitialized memory.

To prevent this, use:

CLI   ; Clear Interrupt Flag
Enter fullscreen mode Exit fullscreen mode

It tells the CPU: “Ignore all hardware interrupts for now.”

Later, once your handlers are in place, re-enable interrupts:

STI   ; Set Interrupt Flag
Enter fullscreen mode Exit fullscreen mode

This flow is critical when setting up your own OS kernel or bootloader interrupts must be carefully managed from the start.


You Can’t Use All of RAM Freely in Real Mode

The IVT sits at 0x0000 – 0x03FF. That memory is off-limits unless you're replacing the table. Similarly, 0x0400 – 0x04FF is the BIOS Data Area also reserved. And 0xA0000 – 0xBFFFF is often used for video memory. Writing into these areas without knowing their purpose will break things fast.

Here's a rough map:

  • 0x0000 – 0x03FF: IVT
  • 0x0400 – 0x04FF: BIOS Data Area
  • 0xA0000 – 0xBFFFF: Video memory
  • 0xF0000 – 0xFFFFF: BIOS ROM mapping

Only areas outside these regions are safe, unless of cpurse you know exactly what you’re doing.


Final Thoughts: The CPU Is Obedient, Not Intelligent

The CPU has no opinion. It doesn’t know what a keyboard is, or what a loop is, or that you're trying to build an operating system. It obeys instructions, fetches bytes, and jumps wherever you tell it even into chaos.

This is your playground.

When you write bare-metal code, you’re writing the rules of the world. You’re building not just a program, but a miniature reality that the CPU inhabits it and it goes on until you tell it to stop.

And that, right there, is the power of low-level programming.


Wrapping Up

Diving into bare-metal programming feels like stepping into a new universe one where you control every bit and byte. This direct control is both thrilling and challenging because there’s no OS to hold your hand.

If you're passionate about systems programming or OS development, you're not alone. I’m learning from scattered books, online resources, and AI assists.piecing it all together step by step. If there are passionate experts out there who care to offer guidance, share insight, or steer me in the right direction, feel free to reach out.

And if you spot mistakes, oversights, or have corrections to offer, I genuinely welcome them . drop them in the comments. I’m here to learn, explore, and build this world from the ground up.

References: OSDev.org
: Programming from the Ground Up by Jonathan Bartlett

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.