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.
mov
instruction. The1f
is the address of the label:
. So the JMP effectively jumps to the next instruction but sets CS at the same time. – Michael Petch