Reading — step 1 of 5
Read
GDT, IDT & Mode Switching
Two tables rule protected-mode x86: the GDT decides what memory segments exist and who may use them, the IDT decides where the CPU jumps when the world interrupts it. Neither is optional — the CPU literally consults them on every privilege check and every interrupt. Your kernel must build both, byte-exact, and in this lesson's exercise you'll encode real GDT descriptors down to the bit.
Why segmentation still exists
In real mode, an address is segment × 16 + offset — a 1978 hack to reach 1 MiB with 16-bit registers. Protected mode replaced raw segment numbers with selectors that index into a table of descriptors, each describing a region: where it starts (base), how big it is (limit), and what's allowed inside it (access rights).
Modern 64-bit kernels use the flat model: every segment has base 0 and maximum limit, so segmentation checks all trivially pass and paging does the real protection. So why still build a GDT? Because the CPU refuses to run without one:
- The current privilege level lives in the CS register's low bits — ring 0 vs ring 3 is a segment selector property.
- Interrupt handlers need a kernel code selector to run in ring 0.
- The TSS (needed for stack switching on interrupts — chapter 3) is a GDT entry.
The GDT is vestigial the way a foundation is vestigial.
The descriptor layout (your exercise)
Each GDT entry is 8 bytes, and its layout is a museum of backwards compatibility — the base and limit are scattered because the 386 extended the 286's 6-byte entries without moving old fields:
bits 0–15 limit[15:0]
bits 16–39 base[23:0]
bits 40–47 access byte
bits 48–51 limit[19:16]
bits 52–55 flags (G, D/B, L, AVL)
bits 56–63 base[31:24]
The access byte, bit by bit:
P(bit 7) — present. 0 means "descriptor unused"; loading it faults.DPL(bits 5–6) — descriptor privilege level, 0–3. The ring you must be in (or entering) to use this segment.S(bit 4) — 1 for code/data segments, 0 for system entries (TSS, gates).Type(bits 0–3) — for code: executable(1), conforming, readable, accessed. For data: executable(0), grow-direction, writable, accessed.
The flags nibble:
G— granularity. 0: limit counts bytes (max 1 MiB). 1: limit counts 4 KiB pages — how a 20-bit limit field describes 4 GiB (0xFFFFFpages ≈ 4 GiB).D/B— 1 for 32-bit segments.L— 1 marks a 64-bit code segment (and then D must be 0).
Entry 0 must be the null descriptor — all zeroes, never usable; loading selector 0 into a data register is legal, using it faults. It's the table's built-in trap for uninitialized selectors.
A classic flat 32-bit kernel code segment: base 0, limit 0xFFFFF, access 0x9A (present, DPL 0, code, exec+read), flags 0xC (G=1, D=1) → the canonical bytes FF FF 00 00 00 9A CF 00... except you'll compute that yourself, bit by bit, in the exercise — and after it, GDT dumps in a debugger will read like text.
The IDT: 256 doorways
The IDT has 256 entries — one per interrupt vector — each a gate holding: the handler's address, the code selector to run it with (your ring-0 kernel code segment — this is where the tables meet), and a DPL.
- Vectors 0–31 are CPU exceptions, fixed by Intel: 0 divide error, 6 invalid opcode, 13 general protection, 14 page fault…
- 32–255 are yours. Hardware IRQs get remapped here (timer at 32 by convention — chapter 3 does this to the PIC).
Gate DPL matters for one thing: whether ring 3 may deliberately trigger the vector with int n. Handlers still run in ring 0 regardless — that's the syscall trick, and it's also why every gate's DPL must be chosen on purpose.
The switching sequence
The full real→protected ceremony, in order and with no steps skippable: enable the A20 line (another compatibility ghost — address bit 20 is gated off at boot so 1985 software wraps addresses like an 8086), lgdt your table, set CR0.PE, then immediately far jump — because the far jump reloads CS from your new GDT and flushes the prefetched real-mode instructions. Then reload the data segment registers. For long mode, add: page tables into CR3, set EFER.LME, enable CR0.PG, far jump into an L=1 code segment. Get one byte of a descriptor wrong and the far jump triple-faults the machine — which is exactly why you're learning to encode them exactly.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…