Reputation: 21
# Load the GDT.
mov $gdt_descriptor, %ecx
lgdt (%ecx)
mov $0x10, %cx
mov %cx, %ds
mov %cx, %es
mov %cx, %fs
mov %cx, %gs
mov %cx, %ss
ljmp $0x8, $1f
1: mov $kernel_stack, %esp
I am unable to understand what this code does. Why mov $0x10 to cx and then subsequently to other registers after loading GDT? And what does ljmp instruction do?
Upvotes: 1
Views: 858
Reputation: 364842
It loads the segment descriptor caches (inside the CPU) from the new GDT which the lgdt
told the CPU about.
The segment descriptions inside the CPU don't update automatically when you change table entries, or change where the table points.
You can even switch back to real mode with DS base=0 limit=4GiB (and same for ES and SS), and use 32-bit addresses in real mode until the next mov ds, r16
or pop ds
instruction overwrites the cached segment description. (This is called big / huge unreal mode, huge if you do it for CS as well, but that's less convenient because interrupts in real mode only save IP, not EIP.)
ljmp
is a far jmp
, which sets CS (in this case to use a different descriptor than the data descriptors). x86 doesn't allow mov
or pop
to set CS, only far jump. Presumably the CPU isn't changing modes with this jump, otherwise the asm source would need to use a .code32
or .code16
directive.
The target is the 1:
label, in the f
orward direction. So the mov
to %esp
is decoded/run with whatever code-segment settings were in GDT index 1. (The low 3 bits of segment selectors are permission bits, so $8
is GDT index 1, and $0x10
is GDT index 2.)
It's a bit weird to separate the mov
to %ss
from the instruction that sets %esp
, because x86 automatically defers interrupts until the instruction after a mov
to SS
. This lets you atomically set SS:SP without using cli
/sti
, but probably this code runs with interrupts disabled already. This code probably only runs once during bootup, so it makes sense to just disable interrupts for as long as necessary to set up a new GDT and IDT.
Upvotes: 3