Skip to content
The VGA Text Console
step 1/5

Reading — step 1 of 5

Read

~3 min readBoot & Memory

The VGA Text Console

The Boot Process lesson said it in passing: a kernel that can't print is a kernel you can't debug. Before paging, before the scheduler, before anything, the very first thing a kernel needs is a way to say "I'm alive." On a PC, the oldest and simplest answer is the VGA text buffer — and it's the closest thing to magic in all of kernel programming, because output is just writing to memory.

Memory that is a screen

At physical address 0xB8000 sits a special region: the VGA text-mode framebuffer. Write a byte there and a character appears on screen. No driver, no syscall, no library — the hardware continuously scans that memory and paints it. It's memory-mapped I/O (the Interrupts & I/O lesson's MMIO, in its friendliest form): a device register file that happens to look exactly like an array.

The layout is a grid, classically 80 columns × 25 rows, stored row-major. Each cell is two bytes:

byte 0:  the character (ASCII)
byte 1:  the attribute — foreground color (low nibble) + background (high nibble)

So the cell at (row, col) lives at 0xB8000 + 2 * (row * 80 + col). To print H in white-on-black at the top-left: *(uint16_t*)0xB8000 = 0x0F00 | 'H';. That single assignment is a working putchar. Kernels really do start this way — the first triumphant moment of every OS-from-scratch project is a character appearing because you wrote a number to an address.

The console is a state machine over the grid

Raw cells aren't a console — a console tracks a cursor (where the next character goes) and gives characters like newline their meaning. That logic, not the memory poke, is the actual lesson and your exercise:

  • Printing a character: place it at the cursor, advance the cursor one column.
  • Wrapping: cursor past the last column → back to column 0, down one row. (The hardware doesn't wrap; your code does — the buffer is just cells.)
  • Newline: cursor to column 0 of the next row. No carriage-return/line-feed distinction to honor here; one operation.
  • Scrolling: the interesting one. When the cursor would go below the last row, you can't — there's no row 25. So you scroll: copy every row up by one (row 1 → row 0, row 2 → row 1, …), blank the last row, and keep the cursor there. In real hardware that's a memmove of the whole buffer minus one row, then clearing the final row — a few microseconds, done constantly as output flows.

Why simulate it instead of poking 0xB8000?

Because the poke is trivial and the bookkeeping is where every bug lives — off-by-one wraps, scroll that drops the wrong row, a cursor that sits at column 80 and writes off-screen. Getting the state machine exactly right on a simulated grid is precisely the skill; swapping the grid for a real 0xB8000 write is a one-line change you'd make on real hardware. (Modern kernels have long since moved to a pixel framebuffer — the VGA text buffer is a legacy the hardware still honors for exactly this bring-up purpose, and QEMU shows it faithfully.)

Your exercise: VGA Text Buffer

SCREEN cols rows sets the grid; PRINT, NEWLINE, and SHOW drive the cursor. Every rule above is a test: wrapping at the right column, scrolling that shifts rows up and blanks the bottom, NEWLINE scrolling when already at the bottom, and continued printing landing where the cursor actually is across multiple SHOWs. Keep two integers — cursor row and column — and a 2D grid of characters, and handle each command as a small, exact transition. It's the humble beginning of every operating system's voice.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…

The VGA Text Console — Build an OS Kernel