X86 memory segmentation
x86 memory segmentation is a term for the kind of memory segmentation characteristic of the Intel x86 computer instruction set architecture. The x86 architecture has supported memory segmentation since the original Intel 8086, but x86 memory segmentation is a plainly descriptive retronym. The introduction of memory segmentation mechanisms in this architecture reflects the legacy of earlier 80xx processors, which initially could only address 16 KB, or later 64 KB of memory, and whose instructions and registers were optimised for the latter. Dealing with larger addresses and more memory was thus comparably slower, as that capability was somewhat grafted-on in the Intel 8086. Memory segmentation could keep programs compatible, relocatable in memory, and by confining significant parts of a program's operation to 64 KB segments, the program could still run faster.
In 1982, the Intel 80286 added support for virtual memory and memory protection; the original mode was renamed real mode, and the new version was named protected mode. The x86-64 architecture, introduced in 2003, has largely dropped support for segmentation in 64-bit mode.
In both real and protected modes, the system uses 16-bit segment registers to derive the actual memory address. In real mode, the registers CS, DS, SS, and ES point to the currently used program code segment, the current data segment, the current stack segment, and one extra segment determined by the system programmer. The Intel 80386, introduced in 1985, adds two additional segment registers, FS and GS, with no specific uses defined by the hardware. The way in which the segment registers are used differs between the two modes.
The choice of segment is normally defaulted by the processor according to the function being executed. Instructions are always fetched from the code segment. Any data reference to the stack, including any stack push or pop, uses the stack segment; data references indirected through the BP register typically refer to the stack and so they default to the stack segment. The extra segment is the mandatory destination for string operations ; for this one purpose only, the automatically selected segment register cannot be overridden. All other references to data use the data segment by default. The data segment is the default source for string operations, but it can be overridden. The instruction format allows an optional segment prefix byte which can be used to override the default segment for selected instructions if desired.
Real mode
In real mode or V86 mode, the fundamental size of a segment is 65,536 bytes, with individual bytes being addressed using 16-bit offsets.The 16-bit segment selector in the segment register is interpreted as the most significant 16 bits of a linear 20-bit address, called a segment address, of which the remaining four least significant bits are all zeros. The segment address is always added to a 16-bit offset in the instruction to yield a linear address, which is the same as physical address in this mode. For instance, the segmented address 06EFh:9234h has a segment selector of 06EFh, representing a segment address of 06EF0h, to which the offset is added, yielding the linear address 06EF0h + 9234h = 10124h.
Because of the way the segment address and offset are added, a single linear address can be mapped to up to 212 = 4096 distinct segment:offset pairs. For example, the linear address 08124h can have the segmented addresses 06EFh:1234h, 0812h:0004h, 0000h:8124h, etc. This could be confusing to programmers accustomed to unique addressing schemes, but it can also be used to advantage, for example when addressing multiple nested data structures.
While real mode segments are technically always 64 KB long, the practical effect is only that no segment can be longer than 64 KB, rather than that every segment as actually used in a program must be treated as 64 KB long – dealing with effectively smaller segments is possible: usable sizes range from 16 through 65,536 bytes, in 16-byte steps. Because there is no protection or privilege limitation in real mode, it is still entirely up to the program to coordinate and keep within the bounds of any segments. This is true both when a segment is programmatically treated as smaller than, or the full 64 KB, but it is also true that any program can always access any memory by just changing segments, since it can arbitrarily set segment selectors to change segment addresses with absolutely no supervision. Therefore, while real mode can be thought of as allowing different segment lengths, and as allowing segments to be overlapping or non-overlapping as desired, none of this is restrictively enforced by the CPU.
The effective 20-bit address space of PC/XT-generation CPUs limits the addressable memory to 220 bytes, or 1,048,576 bytes. This derived directly from the hardware design of the Intel 8086, which had exactly 20 address pins.
Each segment begins at a multiple of 16 bytes, called a paragraph, from the beginning of the linear address space. That is, at 16 byte intervals. Since all segments are technically 64 KB long, this explains how overlap can occur between segments and why any location in the linear memory address space can be accessed with many segment:offset pairs. The actual location of the beginning of a segment in the linear address space can be calculated with segment × 16. Such address translations are carried out by the segmentation unit of the CPU.
End-of-address-space quirkiness
The last segment, FFFFh, begins at linear address FFFF0h, 16 bytes before the end of the 20-bit address space, and thus can access, with an offset of up to 65,536 bytes, up to 65,520 bytes past the end of the 20-bit address space of the 8086 or 8088 CPU. A further 4,094 next-highest 64K-segments also still cross that 1MB-threshold, but by less and less. On the 8086 and 8088 CPUs, these address accesses were wrapped around to the beginning of the address space such that 65535:16 would access address 0, and e.g. 65533:1000 would access address 952 of the linear address space. The fact that some programs written for the 8088 and 8086 relied on this quirky wrap-around as a feature led to the Gate A20 compatibility issues in later CPU generations, with the Intel 286 and above, where the linear address space was expanded past 20 bits.In 16-bit real mode, enabling applications to make use of multiple memory segments for a single data structure is quite complex, but was viewed as a necessary evil for all but the smallest tools. The root of the problem is that no appropriate address-arithmetic instructions suitable for flat addressing of the entire memory range are available. Flat addressing is possible by applying multiple instructions, which however leads to slower programs.
The memory model concept derives from the setup of the segment registers. For example, in the tiny model CS=DS=SS, that is the program's code, data, and stack are all contained within a single 64 KB segment. In the small memory model DS=SS, so both data and stack reside in the same segment; CS points to a different code segment of up to 64 KB.
Protected mode
80286 protected mode
The 80286's protected mode extends the processor's address space to 224 bytes, but not by adjusting the shift value used to calculate a segment address from the value in a segment register. Instead, each 16-bit segment register now contains an index into a table of segment descriptors containing 24-bit base addresses to which offsets are added. To support old software, the processor starts up in "real mode", a mode in which it uses the segmented addressing model of the 8086. There is a small difference though: the resulting physical address is no longer truncated to 20 bits, so real mode pointers can now refer to addresses from 10000016 through 10FFEF16. This nearly 64-kilobyte region of memory was known as the High Memory Area, and later versions of DOS could use it to increase the available "conventional" memory, by moving parts of DOS from conventional memory into the HMA. With the addition of the HMA, the total address space is approximately 1.06 MB. Though the 80286 does not truncate real-mode addresses to 20 bits, a system containing an 80286 can do so with hardware external to the processor, by gating off the 21st address line, the A20 line. The IBM PC AT provided the hardware to do this, and so all subsequent "AT-class" PC clones did as well.286 protected mode was seldom used as it would have excluded the large body of users with 8086/88 machines. Moreover, it still necessitated dividing memory into 64k segments like was done in real mode. This limitation can be worked around on 32-bit CPUs which permit the use of memory pointers greater than 64k in size, however as the Segment Limit field is only 24-bit long, the maximum segment size that can be created is 16MB. This method was commonly used on Windows 3.x applications to produce a flat memory space, although as the OS itself was still 16-bit, API calls could not be made with 32-bit instructions. Thus, it was still necessary to place all code that performs API calls in 64k segments.
Once 286 protected mode is invoked, it could not normally be exited except by performing a hardware reset. Machines following the rising IBM PC/AT standard could feign a reset to the CPU via the standardised keyboard controller, but this was significantly sluggish. Windows 3.x worked around both of these problems by intentionally triggering a triple fault in the interrupt-handling mechanisms of the CPU, which would cause the IBM AT-compatible hardware to reset the CPU, nearly instantly, thus causing it to drop back into real mode.
Detailed segmentation unit workflow
A logical address consists of a 16-bit segment selector and a 16-bit offset. The segment selector must be located in one of the segment registers. That selector consists of a 2-bit Requested Privilege Level, a 1-bit Table Indicator, and a 13-bit index.When attempting address translation of a given logical address, the processor reads the 64-bit segment descriptor structure from either the Global Descriptor Table when TI=0 or the Local Descriptor Table when TI=1. It then performs the privilege check:
where CPL is the current privilege level, RPL is the requested privilege level from the segment selector, and DPL is the descriptor privilege level of the segment. All privilege levels are integers in the range 0–3, where the lowest number corresponds to the highest privilege.
If the inequality is false, the processor generates a general protection fault. Otherwise, address translation continues. The processor then takes the 16-bit offset and compares it against the segment limit specified in the segment descriptor. If it is larger, a GP fault is generated. Otherwise, the processor adds the 24-bit segment base, specified in descriptor, to the offset, creating a linear physical address.
The privilege check is done only when the segment register is loaded, because segment descriptors are cached in hidden parts of the segment registers.