I am currently studying low level organization of operating systems. In order to achive that I am trying to understand how Linux kernel is loaded.
A thing that I cannot comprehend is the transition from 16-bit (real mode) to 32-bit (protected mode). It happens in this file.
The protected_mode_jump
function performs various auxiliary calculations for 32-bit code that is executed later, then enables PE
bit in the CR0
reguster
movl %cr0, %edx
orb $X86_CR0_PE, %dl # Protected mode
movl %edx, %cr0
and after that performs long jump to 32-bit code:
# Transition to 32-bit mode
.byte 0x66, 0xea # ljmpl opcode
2: .long in_pm32 # offset
.word __BOOT_CS # segment
As far as I understand in_pm32
is the address of the 32-bit function which is declared right below the protected_mode_jump
:
.code32
.section ".text32","ax"
GLOBAL(in_pm32)
# some code
# ...
# some code
ENDPROC(in_pm32)
The __BOOT_CS
sector base is 0 (the GDT is set beforehand here), so that means that offset should be basically absolute address of the in_pm32
function.
That's the issue. During machine code generation the assembler/linker should not know the absolute address of the in_pm32
function, because it does not know where it will be loaded in the memory in the real mode (various bootloaders can occupy various amounts of space, and the real mode kernel is loaded just after a bootloader).
Moreover, the linker script (setup.ld
in the same folder) sets the origin of the code as 0, so seems like in_pm32
address will be the offset from the beginning of the real mode kernel. It should work just fine with 16-bit code because CS
register is set properly, but when long jump happens the CPU is already in protected mode, so a relative offset should not work.
So my question:
Why does the long jump in Protected Mode (.byte 0x66, 0xea
) sets the proper code position if the offset (.long in_pm32
) is relative?
Seems like I am missing something really important.