Operating Systems · Boot and Firmware
The Reset Vector and the First Instruction Executed
Why an x86 processor begins executing at 0xFFFFFFF0 in real mode, how that address reaches the firmware ROM, and what the sixteen bytes at the top of memory must contain.
Removing and restoring power to a processor is not a warm reboot with everything cleared. It is a defined architectural event with a defined outcome, and the outcome is unusually specific: the processor commits to an instruction address before it has read a single byte of memory, and it commits to that address in a mode of operation that no modern operating system otherwise uses. Everything the machine does afterwards is reachable from that one address.
The address is 0xFFFFFFF0 on every x86 processor from the 386 onward. It is not configurable, it is not read from a pin, and it does not depend on what the mainboard designer wanted. That immovability is the whole point, and it is also the source of most of the awkwardness in early boot, which continues through every boot loader stage that follows.
The architectural state at the reset signal
When the reset pin is asserted and then released, the processor samples no configuration and runs no microcode patch from flash. It starts from a state fixed by the architecture. CR0 has PE clear and PG clear, so the processor is in real mode with paging disabled. The IDTR and GDTR have a base of zero and a limit of 0xFFFF, which means interrupts are vectored through the bottom of physical memory and no descriptor table is usable yet. The segment registers are loaded with values whose visible parts look ordinary but whose hidden descriptor caches are not: CS holds selector 0xF000 while its cached base is 0xFFFF0000, and the data segment bases are zero with a limit of 0xFFFF.
That CS base is the interesting part. In real mode, the effective address of an instruction is normally segment * 16 + offset, which for selector 0xF000 would give a base of 0x000F0000. At reset the cached base is not that value. It is 0xFFFF0000, the top 64 KiB of the 4 GiB address space, and the instruction pointer starts at 0xFFF0. The first fetch therefore reads the linear address 0xFFFFFFF0, sixteen bytes below the 4 GiB boundary.
This is the only moment in the machine’s life when a real-mode segment base is not selector << 4. The processor is deliberately executing in a 16-bit addressing mode while pointing at an address that only a 32-bit address space can express. Firmware that wants to keep running in real mode must therefore leave that region almost immediately, because the 64 KiB window it can address is not where the rest of its code lives.
Why the top of memory and not address zero
Placing the entry point at the very top of the address space rather than at zero is a decision that has paid for itself repeatedly. Address zero is where a real-mode program expects to find interrupt vectors, and the IVT occupies the first 1 KiB of physical memory; a reset entry point at zero would collide with the table the processor needs in order to take an interrupt. The top of the 4 GiB space, by contrast, is a region no real-mode program can reach through ordinary segment arithmetic, so it is free.
The second reason is chipset topology. The firmware ROM is mapped by the chipset into a region ending at 0xFFFFFFFF, typically the top 64 KiB or a larger window such as the top 16 MiB on parts that support descriptor-based region decoding. When the processor fetches from 0xFFFFFFF0, the chipset’s decode logic routes that access to the flash part rather than to DRAM. No software has configured this; the decode is the power-on default, because the machine cannot be configured by software that has not run yet.
Because real-mode addressing cannot reach above 1 MiB, the same flash contents are also aliased at 0x000F0000 to 0x000FFFFF. The firmware can therefore run in real mode using CS = 0xF000, jump to code inside the legacy window, and continue. The alias and the top-of-memory window are the same bytes viewed through two decoders.
What the sixteen bytes contain
Sixteen bytes is not enough for an entry routine. It is enough for a jump. The conventional content of the reset vector is a far jump into the body of the firmware, plus a few bytes of identification:
; bytes at physical 0xFFFFFFF0, executing with CS base 0xFFFF0000, EIP 0xFFF0
reset_vector:
ljmp $0xF000, $post_entry ; 5 bytes: EA <off16> <seg16>
; 0xFFFFFFF5 .. 0xFFFFFFFF: board and chipset identification,
; often a date code or a "reset vector present" signature
The far jump clears the special cached base and reloads CS with 0xF000, whose base in real mode is 0x000F0000. Control lands at 0xF0000 + post_entry, inside the legacy alias, and from that point the processor is in ordinary real mode with ordinary segment arithmetic. The first instruction of the firmware proper is thus the first instruction after the jump, and the sixteen bytes exist only to get there.
The jump target must be inside the 64 KiB segment, so firmware larger than 64 KiB needs a second hop: the code at the target switches to protected mode, or performs another far jump after programming the chipset to unalias more ROM. Firmware images routinely begin with a small real-mode shim whose only job is to establish 32-bit flat addressing.
Running before RAM exists
The firmware’s first instructions cannot use DRAM. Memory controllers are not trained, the DRAM geometry is unknown, and on many designs the controller has not been released from reset. There is no stack, and call, push, and interrupt delivery all require one.
The standard resolution is to use the processor’s own cache as memory. A region of the cache is configured through the fixed-range MTRRs as write-back cacheable, and the corresponding physical range is left with no DRAM behind it. Stores to that range then allocate lines in the cache, and the region is configured so that lines are neither evicted nor written back, because there is no memory to write them back to. Reads of lines that have been written hit in the cache and never reach the bus. Reads of lines that have not been written do reach the bus, which is why the technique only works for storage the firmware writes before it reads. The result is a small amount of scratch storage, typically a few tens to a few hundred kilobytes, usable as a stack and for early variables before any DRAM access is legal.
This is delicate. Any access that misses the cache, any eviction caused by a competing region, or any snoop from another bus master can produce a machine check or silent corruption. The firmware must tear the arrangement down once real memory is trained, copying anything it wants to keep into DRAM first, and the teardown has to happen before any code that relies on the cache being a cache runs.
On architectures that do not inherit a 1978 addressing model, the same problem is solved differently. ARM cores reset to a vector table whose base is either zero or a value latched from a configuration pin or a system register, and RISC-V implementations choose their own reset address. Those designs avoid the segment arithmetic, but they still need a fixed, non-volatile location that the chip selects at power-on, and they still need scratch storage before DRAM is usable.
Where the arrangement fails
The reset vector’s rigidity converts a single corrupt byte into an unbootable machine. If the flash part cannot be read, or the first jump is garbage, the processor faults on its first fetch with no stack to report the fault on and no way to recover except a reset. Mainboards mitigate this with a protected boot block that the normal flash update path refuses to erase, and with a recovery mechanism in the chipset that can expose the flash over an external bus so that another machine can reprogram it.
A second failure mode is more subtle: the firmware assumes the top-of-memory decode belongs to it. A misconfigured chipset strap, or a region claimed by another bus master, redirects the first fetch somewhere else. Some platforms allow a second processor socket or a management controller to claim that window; the resulting behaviour is a hang at the first instruction with no diagnostic output, because producing output requires code that has not run.
The handoff from this state to a kernel is a long sequence, and the first thing the firmware must do after it has memory is describe that memory to whatever comes next. That description, and the conventions that make it usable across firmware generations, is the subject of how firmware hands a memory map to the kernel.
References
- Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 3: System Programming Guide — Intel Corporation
- AMD64 Architecture Programmer's Manual, Volume 2: System Programming — AMD
- Unified Extensible Firmware Interface Specification — UEFI Forum