I am currently studying the low-level organization of operating systems. To achieve this, I am trying to understand how the Linux kernel boots.
What I cannot understand is the transition from 16-bit (real mode) to 32-bit (protected mode). This happens in this file .
The protected_mode_jump function performs various auxiliary calculations for the 32-bit code, which is executed later, then turns on the PE bit in the CR0 registry
movl %cr0, %edx orb $X86_CR0_PE, %dl
and after that makes a 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 declared below protected_mode_jump :
.code32 .section ".text32","ax" GLOBAL(in_pm32)
The base of the __BOOT_CS sector is 0 (GDT is pre-set here ), so this means that the offset should be basically the absolute address of the in_pm32 function.
This is problem. During the generation of machine code, the assembler / linker does not need to know the absolute address of the in_pm32 function, since it does not know where it will be loaded into memory in real mode (different loaders can take up different amounts of space, and the kernel of the real mode loads immediately after the loader).
In addition, the script linker ( setup.ld in the same folder) sets the beginning of the code to 0, so it seems that in_pm32 address will be the offset from the beginning of the real mode kernel. It should work fine with 16-bit code, because the CS register is set correctly, but when a long transition occurs, the CPU is already in protected mode, so the relative offset should not work.
So my question is: Why .byte 0x66, 0xea long jump in protected mode ( .byte 0x66, 0xea ) set the correct code position if the offset ( .long in_pm32 ) is relative?
It seems like I'm missing something really important.