3 ms·
This probably no longer applies in any way to modern processors, but the 286 and 386 did have a "prefetch queue" (containing opcode bytes fetched from CS:eIP),
by rep_lodsb 5y ago
This probably no longer applies in any way to modern processors, but the 286 and 386 did have a "prefetch queue" (containing opcode bytes fetched from CS:eIP), as well as a "decoded instruction queue".
Up to 3 instructions could be pre-decoded and stored in that queue, while the execution unit was still busy with some other instruction (keep in mind that back in the day it was multiple clock cycles per instruction, not multiple instructions per cycle!)
Each decoded instruction included a microcode entry point address, depending on the opcode and the state of CR0 at the time of decoding. On the 486 this was further optimized to actually store the first micro-op in that queue.
If you set the "Protect Enable" bit in CR0, it does not affect instructions that have already been through the decode stage, so they would not run with protected mode semantics. A far jump would actually not work in that case, because it would load the CS base with (selector << 4) instead of going through the descriptor table. So you should first do a near jump to flush the pipeline, and only then jump to the protected mode CS.
In practice, you could get away with not doing this step, depending on the exact timing of the code used to switch to protected mode. A lot of instructions are short enough that the processor never has enough time to pre-decode anything following.
The Intel manuals did (and still do?) document you have to do this near jump first, though not the exact details of why.
- knorker 5y agoOh yeah, thanks for the context. Checking my old OS it seems I did do that near jump too. I'd forgotten about that. It looks like the author of this PDF confused the two jumps. Looks like Linux nowadays does the far jump immediately after setting cr0, thus doing two in one: https://github.com/torvalds/linux/blob/master/arch/x86/boot/pmjump.S https://github.com/torvalds/linux/blob/master/arch/x86/boot/... But the near and far jump (if you use both) still have VERY different purposes. And the CPU is not really in 32bit mode until all the segment descriptors have been loaded manually. Linux didn't used to combine them: https://elixir.bootlin.com/linux/2.2.26/source/arch/i386/boot/setup.S#L708 https://elixir.bootlin.com/linux/2.2.26/source/arch/i386/boo... Here we see the near instruction-flushing jump, then a bunch of code (that is still in 16bit mode!), and then the long jump that switches the instruction set to 32bit. The comments in that last link explains it well, too.
- rep_lodsb 5y agoThe point is that the CPU might still be in real mode at the point in time when the far jump is decoded, and thus execute it with real mode semantics. Coding a seemingly useless near jump was the way to prevent that. Whoever wrote the modern Linux code doesn't seem to understand any of this. There is even a jump before the switch to protected mode, with the comment "Short jump to serialize on 386/486". Cargo-cult programming at its finest :) As I said before, it may have always worked in practice without the jump, and modern Linux doesn't even support the 386 anymore. Actually, with all the crazy out-of-order speculative execution going on, one could expect newer CPUs to have similar requirements, but I guess they also had to get better at providing the illusion that such internal details don't matter. Making LMSW / MOV CR0 a serializing instruction seems to be an easy way to do it.
- knorker 5y ago> the CPU might still be in real mode at the point in time when the far jump is decoded, It definitely is in real mode when the far jump is decoded. Or is that not the right technical definition of "real mode"? Do we leave real mode as soon as CR0 bit 1 is set, even though nothing has changed until the code puts a new value into a segment register? I've always thought of it as when CS becomes 32bit, meaning the long jump. Anyway, that's just terminology. > "Short jump to serialize on 386/486". Cargo-cult programming at its finest :) That's weird. I tracked down the commit: https://github.com/torvalds/linux/commit/2ee2394b682c0ee99b0f083abe6c57727e6edb69 https://github.com/torvalds/linux/commit/2ee2394b682c0ee99b0... > Actually, with all the crazy out-of-order speculative execution going on I would expect those to detect that dependency and not take effect. But I could be wrong. Edit: Interesting. The new Intel docs explicitly say to do the far jump "Immediately" after setting the PE bit in CR0. https://www.intel.co.uk/content/www/uk/en/architecture-and-technology/64-ia-32-architectures-software-developer-system-programming-manual-325384.html https://www.intel.co.uk/content/www/uk/en/architecture-and-t... section 9.9.1. Whereas the example code for the original 386 programming manual has the short jump, many other instructions, and then the far jump. Haha, but then the actual example code on page 9-20 (new manual) still has the "clear prefetch queue" near jump.