Global Descriptor Table (GDT)
Memory Segmentation in x86_64
Overview
The Global Descriptor Table (GDT) is a fundamental data structure that defines memory segments and their protection levels in x86 and x86_64 architectures. In AOS, the GDT establishes the foundation for protected mode operation and 64-bit long mode execution.
What is the GDT?
The GDT is a table in memory that contains segment descriptors. Each descriptor defines:
- Base Address - Where the segment starts in memory
- Limit - How large the segment can be
- Access Rights - Read/Write permissions and privilege levels
- Flags - Additional properties (32-bit, 64-bit, page granularity)
In 64-bit mode, the GDT plays a reduced role compared to 32-bit systems, but it's still mandatory.
Purpose in AOS
| Purpose | Impact |
|---|---|
| Establish memory protection | Prevents user programs from accessing kernel memory |
| Define privilege levels | Separates Ring 0 (kernel) from Ring 3 (user mode) |
| Mandatory for processor | CPU requires GDT to function in protected mode |
| Context switching | Loaded when switching between kernel and user code |
GDT Structure in AOS
Descriptor Table Definition
// From include/gdt.h
struct gdt_entry {
uint16_t limit_low; // Lower 16 bits of limit
uint16_t base_low; // Lower 16 bits of base address
uint8_t base_middle; // Middle 8 bits of base address
uint8_t access; // Access byte (privilege, type)
uint8_t granularity; // Granularity and upper limit
uint8_t base_high; // Upper 8 bits of base address
} __attribute__((packed));
struct gdt_ptr {
uint16_t limit; // Limit of the GDT itself
uint64_t base; // Pointer to GDT in memory
} __attribute__((packed));
Access Byte Breakdown
Access Byte Format
- Bit 7: Present (1 = segment is valid)
- Bit 6: Privilege Level (0 = Ring 0, 3 = Ring 3)
- Bit 5: User-defined
- Bits 4-0: Type (Code, Data, etc.)
AOS GDT Configuration
AOS uses a minimal 3-entry GDT:
| Index | Type | Purpose | Access | Flags |
|---|---|---|---|---|
| 0 | Null | Required (must be 0) | 0x00 | 0x00 |
| 1 | Code | Kernel code segment | 0x9A | 0x20 (64-bit) |
| 2 | Data | Kernel data segment | 0x92 | 0x00 |
Access Values Explained
- Bit 7: Present (1)
- Bit 6: Ring 0 (0)
- Bit 4: Executable (1)
- Bit 7: Present (1)
- Bit 6: Ring 0 (0)
- Bit 1: Read/Write (1)
Implementation Walkthrough
Step 1: Define GDT and Pointer
// From src/arch/x86_64/gdt.c
struct gdt_entry gdt[3]; // Our 3-entry GDT
struct gdt_ptr gp; // Pointer for LGDT instruction
Step 2: GDT Gate Setting Function
void gdt_set_gate(int num, uint32_t base, uint32_t limit,
uint8_t access, uint8_t gran) {
// Set base address
gdt[num].base_low = (base & 0xFFFF); // Bits 0-15
gdt[num].base_middle = (base >> 16) & 0xFF; // Bits 16-23
gdt[num].base_high = (base >> 24) & 0xFF; // Bits 24-31
// Set limit
gdt[num].limit_low = (limit & 0xFFFF); // Bits 0-15
gdt[num].granularity = (limit >> 16) & 0x0F; // Bits 16-19
// Set granularity flags
gdt[num].granularity |= gran & 0xF0;
// Set access byte
gdt[num].access = access;
}
⚠️ Important: Initialization Order
The GDT must be initialized FIRST before any other CPU structures (IDT, TSS, etc.). The CPU requires a valid GDT to function in protected mode.
Step 3: Initialize GDT
void init_gdt() {
// Set GDT pointer (limit and base address)
gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
gp.base = (uint64_t)&gdt;
// Entry 0: Null descriptor (required)
gdt_set_gate(0, 0, 0, 0, 0);
// Entry 1: Kernel code segment (64-bit)
gdt_set_gate(1, 0, 0,
GDT_ACCESS_PRESENT | GDT_ACCESS_RING0 | GDT_ACCESS_CODE,
GDT_FLAG_64BIT);
// Entry 2: Kernel data segment
gdt_set_gate(2, 0, 0,
GDT_ACCESS_PRESENT | GDT_ACCESS_RING0 | GDT_ACCESS_DATA,
0);
// Load GDT into CPU via assembly instruction
gdt_flush((uint64_t)&gp);
}
Segment Selectors
When you load a segment register (CS, DS, ES, etc.), you provide a segment selector:
| Bits | Name | Purpose |
|---|---|---|
| 15-3 | Index | Which GDT entry (0-8191) |
| 2 | TI | 0 = GDT, 1 = LDT |
| 1-0 | RPL | Privilege Level (0-3) |
Examples:
0x08= Index 1, GDT, Ring 0 (kernel code)0x10= Index 2, GDT, Ring 0 (kernel data)0x1B= Index 3, GDT, Ring 3 (user code)
Memory Addresses in 64-bit Mode
In 64-bit long mode, base addresses and limits are effectively ignored:
- Base Address: Always 0 (flat memory address space)
- Limit: Effectively 264 (entire address space)
The GDT's primary function becomes privilege level control rather than memory segmentation.
Key Takeaways
- ✓ GDT is mandatory for processor operation
- ✓ AOS uses minimal 3-entry table
- ✓ Segment descriptors define memory protection
- ✓ 64-bit mode simplifies segmentation
- ✓ LGDT instruction loads the GDT into CPU
- ✓ Far JMP/RET instructions update CS after GDT load
- ✓ Future Ring 3 support will require additional entries