# 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
我无法理解这段代码的作用。为什么在加载GDT后将mov $ 0x10转换为cx然后再转到其他寄存器? ljmp指令有什么作用?
它从lgdt
告诉CPU的新GDT加载段描述符缓存(在CPU内部)。
更改表条目或更改表指向的位置时,CPU内部的段描述不会自动更新。
您甚至可以切换回实模式,DS base = 0 limit = 4GiB(ES和SS相同),并在实模式下使用32位地址,直到下一个mov ds, r16
或pop ds
指令覆盖缓存的段描述。 (这被称为big / huge unreal mode,如果你也为CS做的那么大,但这不太方便,因为实模式中的中断只保存IP,而不是EIP。)
ljmp
是一个far jmp
,它设置CS(在这种情况下使用与数据描述符不同的描述符)。 x86不允许mov
或pop
设置CS,只是跳远。据推测,CPU不会通过此跳转来改变模式,否则asm源需要使用.code32
或.code16
指令。
目标是1:
标签,在f
orward方向。所以mov
到%esp
用GDT索引1中的任何代码段设置进行解码/运行。(段选择器的低3位是权限位,因此$8
是GDT索引1,$0x10
是GDT索引2。)
将mov
与%ss
从设置%esp
的指令中分离出来有点奇怪,因为x86会自动推迟中断,直到mov
之后的指令到SS
。这允许您在不使用cli
/ sti
的情况下以原子方式设置SS:SP,但可能此代码在已禁用中断的情况下运行。此代码可能仅在启动期间运行一次,因此只需在设置新GDT和IDT所需的时间内禁用中断即可。