Skip to content
Lesson 3 of 24

Step 1 of 5 · Reading · ~4 min

Read

Boot & Memory

The VGA Text Console

A kernel that cannot print is a kernel you cannot debug, so the first thing every kernel builds is a way to say "I'm alive". On a PC the oldest answer is the VGA text buffer, and it is the closest thing to magic in kernel programming because output is just writing to memory. In this lesson your program drives a simulated text screen exactly the way a kernel's console driver drives the real one: cells, attributes, a cursor that wraps and a screen that scrolls.

Memory that is a screen

At physical address 0xB8000 sits the text-mode framebuffer. The hardware scans it continuously and paints whatever bytes it finds. It is memory-mapped I/O in its friendliest form: a device that looks exactly like an array. The grid is classically 80 columns by 25 rows, stored row by row, and every cell is two bytes:

ByteHolds
even addressthe character code (code page 437, ASCII for letters)
odd addressthe attribute: foreground colour in the low nibble, background in the high nibble

The address of the cell at (row, column) is 0xB8000 + 2 * (row * columns + column). Cell (2, 5) of an 80-column screen is 0xB8000 + 2 * 165 = 0xB814A.

Because the x86 is little-endian, one 16-bit store writes both bytes. White on black H is the word 0x0F48: attribute 0x0F, character 0x48. The default grey-on-black attribute is 0x07, so a blank cell is 0x0720.

ColourCodeColourCode
black0red4
blue1magenta5
green2brown6
cyan3light grey7

Add 8 for the bright variant, so white is 0xF. Attribute 0x1F is white on blue, the colour of a panic screen.

The console is a state machine over the grid

Raw cells are not a console. A console tracks a cursor and gives characters their meaning:

  • Print a character: store attribute << 8 | char at the cursor and move one column right.
  • Wrap: the hardware does not wrap; your code does. This console uses deferred wrap, like a VT100: after the last column is filled the cursor sits one past the end, and only the next printed character moves it to column 0 of the next row. Filling the very last cell of the screen therefore does not scroll yet.
  • Newline: column 0 of the next row.
  • Scroll: when the cursor would move below the last row, copy every row up by one (row 1 to row 0, and so on), fill the last row with blank cells and leave the cursor on that row. The real thing is a memmove of (rows - 1) * columns * 2 bytes (3,840 bytes for 80x25) plus a fill.
  • Blank cells are a space written with the current attribute.

Worked trace on a 4x2 screen, printing abcd then ef:

StepRowsCursor
abcdabcd / blankrow 0, column 4 (pending wrap)
eabcd / erow 1, column 1
fabcd / efrow 1, column 2

Print ghij next: gh fills the row, i needs a new row, so the screen scrolls to ef / i and then j follows i.

Common mistakes

  • Scrolling as soon as the last cell is filled instead of when the next character arrives.
  • Forgetting to restore the cursor to the bottom row after a scroll, or scrolling twice for one character.
  • Blanking the new row with attribute 0x07 instead of the current attribute.
  • Computing the address with row * 80 instead of the screen's column count, or forgetting the factor of 2.

Your exercise

The commands are listed in the exercise text. The starter gives you the screen, SHOW, CELL, ADDR and the command loop; you write the cursor logic: put_char, newline and scroll. The memory write is trivial; the bookkeeping is where every console bug lives.

Up nextVirtual Memory & PagingBoot & Memory

Discussion

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

Sign in to post a comment or reply.

Loading…