DEV Community

Florent Herisson
Florent Herisson

Posted on

IRQs and the Art of Not Crashing

IRQs and the Art of Not Crashing

Dear diary, today I committed to building an operating system again. No, I'm not okay.

It's January 2nd, 2026, and while normal people are nursing hangovers and pretending they'll actually go to the gym this year, I'm sitting in my home office staring at a GitHub repository called "ChronOS." The afternoon light streams through my window, illuminating the graveyard of failed hobby projects scattered across my desk. An Arduino that was going to revolutionize my home automation. A Raspberry Pi that was definitely going to become a retro gaming console. A copy of "The Dragon Book" that I swore I'd read cover to cover.

But today feels different. Today, I have 4,974 lines of recovered code fragments and what can only be described as dangerous levels of optimism.

The plan is deceptively simple: boot on my Aorus X3 laptop, display some text on the 3200x1800 screen, and call it Milestone 1. How hard could it be? I've done this before. I mean, I've tried to do this before. Multiple times. The distinction is important.

I spend the first hour setting up the project structure, creating folders with names like "drivers" and "kernel" as if I'm going to actually fill them with something meaningful. There's something deeply satisfying about creating empty directories. It's like buying workout clothes – you feel productive without actually doing the hard part yet.

The recovered code gives me hope. Someone – presumably past me, though I have no memory of being this organized – actually implemented a bitmap font renderer. There's a complete 8x16 font, carefully crafted pixel by pixel. Looking at it now, I can almost remember the evening I spent squinting at character tables, painstakingly defining the letter 'Q' for what felt like the fortieth time.

I create types.h first, because you have to start somewhere and fixed-width integers feel like progress. u8, u16, u32, u64 – the building blocks of civilization, or at least the building blocks of something that will triple-fault spectacularly in about three hours.

The UEFI entry point comes next. This is where things get interesting, in the ancient Chinese curse sort of way. UEFI is supposed to be simpler than the old BIOS boot process. No more real mode, no more segments, no more 16-bit assembly that looks like it was written by someone having a stroke. Just good, clean C code that talks to a modern firmware interface.

The irony is that UEFI is somehow more confusing than BIOS ever was. At least with BIOS, you knew you were in hell. UEFI pretends to be civilized while hiding a labyrinth of protocols and handles that would make Kafka weep.

I write the framebuffer initialization code, and for a moment, I feel like I know what I'm doing. Graphics Output Protocol, acquire the framebuffer, store the base address in a global variable. Easy. The recovered code even has comments explaining the pixel format. Past me was clearly having a good day.

The console code is where I start to remember why I abandoned this project the last time. Rendering text to a framebuffer sounds straightforward until you actually try to do it. Each character is 16 bytes representing an 8x16 bitmap. At 2x scaling for the HiDPI display, that becomes 16x32 pixels per character. Simple multiplication, right?

Wrong. Because nothing is ever simple when you're working at the metal.

First, I get the byte order backwards. The framebuffer expects BGRA, but I'm thinking RGB like a rational human being. My carefully crafted white text appears as a suspicious yellow-green color that reminds me of something I'd rather not think about.

Then I discover that my font rendering has an off-by-one error that makes every character look like it's having an existential crisis. The letter 'A' develops a lean to the left. The letter 'O' becomes more of a philosophical concept than an actual circle.

It's 4 PM now, and I've been debugging font rendering for two hours. This is not how I planned to spend my day. I was supposed to have a working bootloader by now, ready to impress myself with a clean boot banner. Instead, I'm staring at a screen that looks like someone spilled alphabet soup on it.

The serial port code saves my sanity. At least, it saves what's left of it. COM1 at 115200 baud, 8N1, no flow control. The kind of simple, honest communication that doesn't pretend to be anything other than what it is. When the screen output looks like abstract art, I can still get debug messages through the serial port. It's like having a lifeline to the outside world.

I implement a proper logging system because I've learned from past mistakes. Screen output should be clean and user-friendly. Serial output can be verbose and technical. And – this is the clever part – USB file logging writes everything to BOOTLOG.TXT on the boot USB itself. No more trying to remember cryptic error messages or squinting at serial output while balancing a laptop on my knee.

The logging system grows more complex than I intended. Log levels, multiple outputs, formatted strings, hex value printing. Before I know it, I've written 200 lines of code just to say "hello world" in a slightly more sophisticated way. This is why hobby OS development takes forever. You start with a simple goal and end up implementing half of printf because you got carried away.

The Makefile is its own special form of torture. GNU-EFI has opinions about compiler flags. The linker has opinions about section layouts. UEFI has opinions about executable formats. Everyone has opinions, and none of them agree with each other.

I spend forty minutes figuring out why my bootloader won't... well, boot. The error message is spectacularly unhelpful: "Selected boot image did not authenticate." This could mean anything from "your code is garbage" to "you forgot to sacrifice a goat to the UEFI gods."

The answer, it turns out, is that I'm creating a partition table when I should be using the superfloppy format. The Aorus X3, in its infinite wisdom, refuses to boot from anything that looks like a hard drive. It wants a floppy disk image, even though no one has seen an actual floppy disk since the Clinton administration.

One line change in the Makefile. One line. I've been staring at this for forty minutes, and the fix is changing "mkfs.fat -F32" to "mkfs.fat -F32 -v". The -v flag creates a volume label, which apparently makes UEFI happy enough to actually run my code.

By 6 PM, I have something that compiles. Whether it runs is a different question entirely.

I write the bootable image to a USB drive and walk over to the Aorus X3. It's been sitting patiently on the shelf, accumulating dust and probably judging my life choices. I plug in the USB, power on, press F12 for the boot menu, and select the USB drive.

The screen goes black. This is either very good or very bad. In OS development, there's rarely a middle ground.

Then, slowly, like dawn breaking over a mountain range, text appears:

  CHRONO OS v4
  Time-Native Operating System

  Display: 3200x1800

  [OK] Boot successful
Enter fullscreen mode Exit fullscreen mode

It works. It actually works. The framebuffer is initialized, the font is rendering correctly, and my carefully crafted boot banner is displayed in crisp, 2x-scaled glory. For a moment, I feel like I could conquer the world.

The moment lasts exactly until I press a key and remember that I haven't implemented keyboard input yet. The system dutifully waits for a keypress, receives some sort of input event, logs it to the file, and then halts. Mission accomplished, in the most technical sense.

I retrieve the USB drive and copy BOOTLOG.TXT to my desktop. The file is perfect – every debug message, every hardware detail, every moment of the boot process captured in excruciating detail. Past me would be proud. Present me is mostly relieved that I don't have to debug this with printk statements and a serial cable.

The log shows exactly what happened: framebuffer acquired at address 0xD0000000, 3200x1800 resolution, 4 bytes per pixel, 23MB of VRAM. The GOP initialization succeeded, the console started correctly, and even the keypress was captured (scancode 0x0000, unicode 0x0020, which means I probably hit the space bar).

Milestone 1 is officially complete. I have a bootloader that can display text on a HiDPI screen. It's not much, but it's honest work.

Looking back at the recovered code, I realize this is further than I've ever gotten before. Previous attempts usually ended with triple faults or mysterious hangs during UEFI initialization. This time, everything just... worked. The font rendering is clean, the logging system is comprehensive, and the build process is repeatable.

Of course, this is just the beginning. Milestone 2 involves keyboard input, which means learning about UEFI input protocols. Milestone 3 is memory management, which means page tables and virtual memory. Milestone 4 is multitasking, which means I'll need to understand CPU state switching and scheduler algorithms.

But that's tomorrow's problem. Tonight, I have a bootloader that displays a boot banner and doesn't crash. In the world of hobby OS development, that's practically a Nobel Prize.

Dear diary, I may actually finish this project. The thought terrifies me more than failure ever could.


osdev #programming #interrupts #lowlevel #bootloader #uefi #memorymanagement #assembly #x86_64 #debugging

Top comments (0)