VGA & Framebuffers
Getting output onto a screen is one of the earliest milestones in kernel development, and x86 offers two quite different mechanisms for it: legacy VGA text mode, a fixed, character-oriented display requiring almost no setup, and a linear framebuffer, a block of memory representing individual pixels directly, on which every modern graphics mode (and every operating system’s actual graphical output) ultimately relies.
VGA text mode
Section titled “VGA text mode”Text mode presents the screen as an 80-column by 25-row grid of characters, backed by memory starting at the fixed physical address 0xB8000, which BIOS-based firmware conventionally leaves already active on boot; a kernel can write to this memory immediately, with no mode-setting step required, making it the simplest possible display output for early development. Each character cell occupies two consecutive bytes: the ASCII character code, followed by an attribute byte encoding foreground and background color.
volatile uint16_t *vga_buffer = (uint16_t *)0xB8000;
void vga_put_char(int col, int row, char c, uint8_t color) { vga_buffer[row * 80 + col] = (uint16_t)c | ((uint16_t)color << 8);}The attribute byte splits into a 4-bit foreground color (bits 0–3) and a 3-bit background color (bits 4–6), with bit 7 historically either a further background color bit or a blink-enable flag depending on the specific VGA hardware mode configured (a detail rarely relevant to a kernel doing nothing more than printing ordinary text). The hardware cursor’s position is separately controlled through a pair of VGA CRTC index/data ports (0x3D4/0x3D5), written as a 16-bit row-major offset into the same 80×25 grid the buffer itself uses.
Linear framebuffers
Section titled “Linear framebuffers”Text mode has no equivalent for drawing arbitrary graphics: anything beyond fixed character glyphs requires a genuine pixel-addressable framebuffer, a block of memory, generally far larger than text mode’s tiny buffer, where each pixel corresponds to a fixed-size group of bytes (commonly 32 bits per pixel, holding red, green, blue, and sometimes an unused alpha channel) at a location computable directly from its x/y coordinates and the framebuffer’s configured pitch (the number of bytes per row, which is not always simply width times bytes-per-pixel, since hardware sometimes pads each row to a convenient alignment).
void put_pixel(struct framebuffer *fb, int x, int y, uint32_t color) { uint8_t *pixel = fb->address + y * fb->pitch + x * (fb->bpp / 8); *(uint32_t *)pixel = color;}Unlike text mode’s fixed address, a framebuffer’s location, resolution, and pixel format are not architecturally fixed values a kernel can simply assume; they must be discovered from firmware, or explicitly requested and configured, before they can be used at all.
Discovering a framebuffer
Section titled “Discovering a framebuffer”On BIOS-based systems, the VESA BIOS Extensions (VBE) provide a real-mode interrupt interface for querying available graphics modes and their framebuffer address, resolution, and pixel layout, and for switching into a chosen mode, necessarily performed while still in real mode, since VBE is a BIOS service, before any protected or long mode transition takes the CPU past where BIOS calls remain usable. UEFI systems instead use the Graphics Output Protocol (GOP), a Boot Services protocol returning equivalent information (framebuffer address, resolution, pixel format) through ordinary UEFI protocol calls rather than a real-mode interrupt, consistent with UEFI’s generally more structured interface described under BIOS vs. UEFI. A Multiboot-compliant bootloader can additionally set up a framebuffer and pass its details directly in the Multiboot information structure, sparing the kernel from implementing either VBE or GOP querying itself.
Implementation notes
Section titled “Implementation notes”Framebuffer memory should be mapped write-combining rather than either fully cacheable or fully uncacheable, when the underlying hardware and page-table format support it: write-combining lets the CPU batch individual pixel writes into larger bus transactions, which noticeably improves performance for anything doing substantial per-pixel drawing, while still avoiding the correctness hazards a fully cached mapping would introduce for memory the display hardware reads directly. A common mistake when switching from VGA text mode to a graphical framebuffer is forgetting that they are entirely separate display paths with no shared state: code written to print debug text via 0xB8000 stops having any visible effect at all once a different video mode has been set, silently, rather than producing any indication of why output has stopped appearing.
References
Section titled “References”- ^ OSDev Wiki, “VGA Hardware”: a detailed hobbyist reference covering VGA register-level programming beyond text mode.
- ^ VESA, VBE Core Function Specification: defines the BIOS-era mode query and framebuffer discovery interface.
See also
Section titled “See also”- Serial Port: a simpler output path commonly used before a display driver exists at all.
- PCI: how a discrete graphics card is discovered before its framebuffer can be configured.
- Console and Terminal Emulation: the escape-sequence interpreter most kernels build directly on top of this drawing layer.
- Writing a UEFI Bootloader: the Boot Services flow GOP framebuffer discovery is one call within.