When x86 boots, it's not strictly in classic real mode, it's in ["unreal" mode][1], with `CS.base` = 0xFFFF0000 and `CS.limit` = 64K. On 386 and later, memory addressing doesn't use the segment register *values* directly. Every segment register is associated with some internal state, a **base and limit**. For example, `mov eax, [edi]` calculates the linear address as `DS.base + edi` from the `DS:EDI` logical address encoded by the `[edi]` addressing mode. Many people call this a "descriptor cache", but **it's not just a cache**. It's guaranteed to keep its state, never reloading from a GDT entry that might have changed. Nothing ever invalidates it, only writes a new value. And its value doesn't always come from a descriptor. There's no way to write those internal segment base/limit values directly for most segments, only FS and GS bases in 64-bit mode via MSRs or the new [`wrfsbase` instructions](https://felixcloutier.com/x86/wrfsbase:wrgsbase). Or on 286 with `LOADALL`. Writing a value to a segment register updates the internal base in a way that depends on the mode you were in when you executed `mov ds, ax` or `pop ds`, or `retf` or `jmp far` or whatever for CS. (Or handling an interrupt and loading a new CS:EIP from the IDT (protected mode) or IVT (real mode), or `iret` or TSS stuff loading a new SS.) * In real mode, the `base = value << 4`. The limit is unchanged. * In protected mode, the segment register value is used as an index ("selector") into the GDT or LDT to load a new base and limit. (The low 3 bits of a [Segment selector][2] are 2-bit privilege level, and 1 bit LDT vs. GDT. The rest is a byte offset into the selected table.) * On power-on / reset, `CS.base` has an initial value of 0xFFFF0000. It didn't get that way by caching something else, it's just separate architectural state that has its own initial value. Changing modes (e.g. from real to protected or vice versa) does *not* reload or change segment register values, or their internal base and limit. It doesn't reinterpret the segment register values and use them to set new bases and limits or anything like that. For example, when a modern UEFI firmware wants to load a legacy 16-bit MBR bootloader (after having switched to long mode after power-on), it has to switch back to real mode. To do that, it needs GDT entries with limit = 64K to load each segment from, if it set new limits on its way from unreal reset state to long mode via protected mode. Anyway, the architectural model supports segment bases that don't match what you'd get from writing the segment register in the current mode. This is how we can have `CS = 0xF000` with `CS.base = 0xFFFF0000` as the reset state. Those are two independent values. --- ### The bootup linear address is the top 16 bytes of physical address space With EIP = 0x0000FFF0, that makes a linear address of 0xFFFFFFF0, the last 16 bytes of linear = physical address space. Same as how 8086 used the last 16 bytes of its 20-bit linear = physical address space. 8086 only supported real mode and didn't have separate internal state for base and limit. It's hard-wired to use `CS<<4` as the base for linear address calculations. (In hardware, a constant shift is just a matter of wiring the inputs to an adder.) So its CS on reset of `0xF000` gives it a linear base of `0xF0000`. (That's the same CS:IP value 386 still boots with, but 386 uses a different CS.base to get a different linear address.) Related: [a Q&A about the fact that it's only 386 and later that uses a 32-bit CS base](https://stackoverflow.com/questions/55330308/8086-reset-vector-above-20-bits-with-buses-of-20-bits). The idea is you wire up a ROM so it responds when all the address lines from A31 to A20 are asserted. This might be an "alias" for (part of) a larger ROM that's also mapped at another address, perhaps the end of the 1st MiB where typical 8086 systems put it. That makes it accessible via segment bases that you can set while in the initial real-ish mode, so for example the BIOS can enable interrupts. (Handling interrupts involves pushing a CS value and restoring it via `iret`, which applies real-mode rules for setting CS.base). And so a BIOS can access static data in the ROM via a DS or ES value it can set. (Although you can do that with `mov eax, [cs:si]` or use `cs rep movsd` to copy a block from ROM at CS:SI to somewhere in RAM at ES:DI, since the CS prefix lets you use it for data addressing.) As [ninjali commented][3], it was normal for PCs to have the ROM aliased to both places: * https://www.pcjs.org/blog/2015/04/16/ * https://martin.uy/blog/bios-execution-in-qemu-where-it-all-starts/ Those top 16 bytes could contain a near `jmp` to jump to earlier in the top 64KiB without touching CS, if the BIOS wants to run a bit more code before far-jumping to a new CS:IP and/or enabling interrupts, or even switch to protected mode to avoid ever needing any ROM taking up space in the low 1MiB of physical address space. ([Stephen Kitt comments][4] that this was the design rationale for 286 also resetting with code fetch coming from the top 16 bytes of its 24-bit physical address space). 386's CS.base = 0xFFFF0000 is 64KiB away from the highest possible address, just like 8086's initial base = 0xF0000, so that's how much room you have for code with just near jumps and no interrupts. Plenty of room for mov-immediate or `cs rep movsd` to set up a GDT if you want to switch out of real mode. As [Nate commented][5], [386 manuals][6] even describe this as "After RESET, address lines A{31-20} are automatically asserted for instruction fetches," until after the first jump or call to a new segment. Which, unless you switch to protected mode before doing so, will be in the low 1MiB because of how segment reg writes are interpreted in (un)real mode, which will de-assert those address lines. (With paging disabled, physical address = linear address. Paging is disabled in real/unreal mode, and is optional in protected mode. Mandatory for long mode. With paging enabled, 32-bit or 64-bit linear addresses are virtual, translated to physical by the page tables, and in 486 and later, by the TLB which is an actual cache of page-table entries (PTEs). Apparently 386 didn't have a TLB, so every memory access became 3 DRAM accesses when paging was enabled!) --- ##### Related: * [Segment size in x86 real mode](https://stackoverflow.com/questions/17786357/segment-size-in-x86-real-mode) re: how the limit gets updated or not and the fact that the limit always exists (except in long mode where it's ignored). * https://wiki.osdev.org/Unreal_Mode Fun fact: Unreal mode (setting base=0 / limit=-1 so you can use 32-bit address size to access a flat 4GiB of memory in 16-bit code) is somewhat durable since writing a segment reg in real mode doesn't update the limit, only base. So for a flat memory model you can use CS=DS=ES=SS=0, with limit=-1. You can still make BIOS calls like `int 0x10`; BIOS code that changes seg regs will restore your seg base when it restores your segment register value, without changing the limit. (But interrupts in real mode don't save/restore the upper half of EIP, so that's a limitation on using huge code; only convenient for 32-bit data addressing.) * Duplicate on Stack Overflow: [Software initialization code at 0xFFFFFFF0H](https://stackoverflow.com/questions/9210296/software-initialization-code-at-0xfffffff0h) There are some other x86 bootup questions on SO, since modern x86's reset state is unchanged from 386. Modern PCs have UEFI firmware that can load software from disk in 32 or 64-bit mode, but most are able to switch to real mode (CSM = compatibility support modules in your boot options) to run legacy BIOS MBR 512-bit boot sectors, providing the legacy `int 0x10` and `int 0x13` and other BIOS interfaces. ---- ### In a few years, the proposed x86S will finally drop 16-bit mode Interesting timing for asking this on retrocomputing instead of SO: this week, Intel proposed x86-S (simplified), which will drop support for legacy mode, keeping only long mode (and its 32-bit compat sub-mode, but without 16-bit address-size being possible, simplifying the decoders for machine code). See https://www.phoronix.com/news/Intel-X86-S-64-bit-Only and Intel's whitepaper: https://www.intel.com/content/www/us/en/developer/articles/technical/envisioning-future-simplified-architecture.html (It'll be at least some years before real CPUs that work this way are sold, if ever!) Until some version of that becomes a reality, this isn't only a retrocomputing question; current x86-64 CPUs still boot the same as 386. [1]: https://wiki.osdev.org/Unreal_Mode [2]: https://wiki.osdev.org/Segment_Selector [3]: https://retrocomputing.stackexchange.com/questions/27035/how-can-the-cpu-start-with-reset-vector-0xfffffff0-even-though-it-starts-in-16-b/27038?noredirect=1#comment95453_27037 [4]: https://retrocomputing.stackexchange.com/questions/27035/how-can-the-cpu-start-with-reset-vector-0xfffffff0-even-though-it-starts-in-16-b/27038?noredirect=1#comment95481_27038 [5]: https://retrocomputing.stackexchange.com/questions/27035/how-can-the-cpu-start-with-reset-vector-0xfffffff0-even-though-it-starts-in-16-b/27038?noredirect=1#comment95477_27038 [6]: https://www.scs.stanford.edu/05au-cs240c/lab/i386/s10_02.htm