Memory map
Fundamentals
Definition and Purpose
A memory map is a schematic or tabular depiction of a computer's address space that illustrates how memory regions are divided and assigned to various components and uses, such as code storage, data areas, stack allocation, and hardware peripherals.[5] It serves as a structural blueprint bridging hardware and software, defining the layout of the address space to ensure organized access to system resources.[6] The primary purpose of a memory map in system design is to facilitate efficient resource allocation by designating specific address regions for distinct functions, thereby preventing overlaps that could lead to conflicts or errors.[5] It also aids debugging by providing a visual or documented overview of memory usage patterns, allowing developers to identify issues like fragmentation or unauthorized access. Additionally, memory maps optimize performance by enabling targeted optimizations, such as aligning critical code in fast-access regions or reserving space for high-priority hardware interactions.[6] Key components of a memory map typically include address ranges specifying starting and ending locations, region types categorizing the memory (e.g., ROM for read-only firmware, RAM for volatile data, or I/O for peripheral interfaces), and attributes defining access permissions and behaviors (e.g., read-only, executable, or write-protected).[6] These elements ensure that the processor can reliably interpret and utilize the address space without ambiguity. For instance, in the x86 PC architecture, low memory addresses are allocated for interrupt vectors and conventional RAM, high memory addresses for BIOS ROM routines during initial system bootstrapping, with extended memory available beyond 1 MB; operating system kernel and user applications are managed via virtual memory mappings as described in later sections. The following table represents a basic example of such a physical structure:| Region | Address Range Example | Type | Attributes |
|---|---|---|---|
| Real Mode IVT | 0x00000000 - 0x000003FF | RAM | Read-write |
| Conventional Memory | 0x00000400 - 0x0009FFFF | RAM | Read-write |
| BIOS ROM | 0x000F0000 - 0x000FFFFF | ROM | Read-only, Executable |
| Extended Memory | 0x00100000 - 0xFFFFFFFF | RAM | Read-write |
Historical Development
The origins of memory maps trace back to the 1940s and 1950s with the development of early mainframe computers, where manual address allocation was essential due to the absence of automated memory management. In the ENIAC, completed in 1945, memory was distributed across 20 accumulators and function tables, with no centralized storage or stored-program capability; programmers manually configured data pathways using switches, plugs, and cables on plugboards, documenting allocations through physical diagrams and panel settings to route signals between units.[8] Similarly, the UNIVAC I, delivered in 1951 as the first commercial stored-program computer, employed mercury delay-line memory organized into 1,000 words of 12 characters each, with programs and data loaded via punched cards that specified addresses explicitly, requiring manual documentation of memory layouts in operational manuals to track allocation across delay lines and drum storage.[9] Advancements in the 1960s and 1970s shifted memory mapping toward automation to support multitasking in minicomputers and early operating systems. The PDP-8, introduced by Digital Equipment Corporation in 1965, featured a fixed 12-bit address space of 4,096 words (expandable to 32,768), divided into 32 pages of 128 words each, where mapping was handled via page bits in instructions and indirect addressing through page 0, enabling basic automated relocation without full virtual memory.[10] This evolution culminated in systems like Multics, developed starting in 1964 and operational by 1969, which pioneered segmentation for dynamic memory mapping; segments were allocated variable-sized address spaces and mapped to physical memory via a descriptor table, allowing multitasking by isolating user processes in a hierarchical file-system-like structure.[11] In the 1980s and 1990s, memory maps became standardized in personal computing and Unix-like systems, facilitating broader adoption of virtual memory. The IBM PC, released in 1981, defined a 1 MB address space with a fixed memory map outlined in its technical reference, reserving low memory (up to 640 KB) for DOS applications, upper memory for drivers, and ROM at F0000h for BIOS, which automated basic mapping through interrupt vectors and segment registers in the Intel 8086.[12] A key milestone was the 1975 documentation of the Intel 8080's 64 KB linear address space in its microcomputer systems manual, which influenced PC designs by specifying memory regions for code, data, and I/O, paving the way for MS-DOS's real-mode mapping. Concurrently, Unix-like systems in the 1980s extended Multics' ideas with paging and segmentation in the 80386 architecture, enabling protected virtual address spaces for multitasking.[9] From the 2000s onward, memory maps integrated with 64-bit architectures, virtualization, and security features for dynamic and scalable environments, including mobile and embedded devices. The introduction of AMD64 in 2003 expanded the address space to 2^64 bytes while maintaining backward compatibility, allowing flat memory models with automated paging for large-scale applications. Virtualization platforms like VMware Workstation, released in 1999, advanced memory mapping by emulating guest physical addresses to host physical memory via shadow page tables, enabling isolated virtual machines on x86 hardware. In parallel, Address Space Layout Randomization (ASLR), first implemented in Linux via the PaX project in 2001, randomized memory map layouts at runtime to thwart exploits, becoming a standard in modern OSes for embedded and mobile systems like Android.[13]Mapping Techniques
Segmentation
Segmentation is a memory management technique that divides a process's virtual address space into variable-sized segments, each corresponding to logical units such as code, data, stack, or heap.[14] These segments are defined by base and limit registers (or bounds), where the base register specifies the starting physical address of the segment in memory, and the limit register indicates the size or ending boundary of the segment.[14] This approach enables non-contiguous allocation of segments in physical memory, allowing the operating system to place logical components independently without requiring the entire address space to be contiguous.[14] In implementation, segmentation typically employs a segment table or descriptor table to store entries for each segment, including the base address, limit, and protection attributes like read/write permissions.[15] For example, in the x86 architecture, segment descriptors reside in structures such as the Global Descriptor Table (GDT) or Local Descriptor Table (LDT), which are indexed by segment selectors in segment registers.[16] Address translation involves computing the effective (physical) address as the sum of the segment base and the offset within the segment, provided the offset does not exceed the limit to prevent out-of-bounds access:
This hardware-enforced check ensures memory protection at the segment level.[14]
The primary advantages of segmentation include its alignment with program structure, facilitating logical organization that matches how developers divide code and data, which simplifies relocation and module sharing across processes.[14] It also supports efficient sharing of segments, such as read-only code, among multiple processes with minimal overhead due to simple base-offset arithmetic.[14]
However, segmentation suffers from external fragmentation, where free memory becomes scattered in small blocks between allocated variable-sized segments, potentially leaving insufficient contiguous space for new allocations despite overall availability.[14] Additionally, the use of segment tables introduces lookup overhead for address translation, and managing variable sizes can complicate allocation strategies compared to fixed-size alternatives.[17]
Historically, segmentation originated in early 1960s systems and was prominently featured in the Multics operating system, which used segments for modular addressing and sharing as described in its 1968 design.[18] It gained widespread hardware support with the Intel 8086 microprocessor in 1978, which employed four segment registers for real-mode addressing to access up to 1 MB of memory.[19]
Paging
Paging is a memory management technique that divides the virtual address space of a process into fixed-size units called pages, typically 4 KB in size, and the physical memory into corresponding units known as frames or page frames.[20][21] This allows the operating system to map virtual pages to non-contiguous physical frames, enabling efficient allocation without requiring contiguous memory blocks.[20] Page tables, maintained by the operating system for each process, store the mappings from virtual page numbers (VPNs) to physical frame numbers (PFNs), with each entry indicating whether the page is present in memory and its access permissions.[20][21] One primary advantage of paging is the elimination of external fragmentation, as pages can be allocated to any available frame regardless of location, simplifying memory allocation and deallocation.[20] It also facilitates demand paging, where pages are loaded into memory only when accessed, and supports swapping of entire processes to secondary storage without regard to contiguity.[20] However, paging introduces internal fragmentation, where the last page of a process may hold unused space up to one page size minus one byte, and incurs overhead from the page table size, which can consume significant memory for large address spaces (e.g., 4 MB for a 32-bit address space with 4 KB pages).[20] In implementation, paging often uses hierarchical page tables to reduce memory usage and translation time; for example, the x86 architecture employs a two-level structure in 32-bit mode, consisting of a page directory (with 1024 entries) pointing to page tables (each with 1024 entries), or a four-level structure in 64-bit mode using a page map level 4 (PML4), page directory pointer table, page directory, and page table.[21] Virtual address translation involves splitting the address into a page number and offset, then using the page number to index the page tables and retrieve the frame number, which is combined with the offset to form the physical address.[20][21] The process can be expressed as:
where is the log base 2 of the page size (e.g., 12 for 4 KB pages), and the virtual address is similarly decomposed as .[20][21]
Key features include page faults, which occur when a referenced page is not present in physical memory (indicated by the present bit in the page table entry) or access violates permissions, triggering an operating system interrupt to load the page from storage.[20][21] To accelerate translations, translation lookaside buffers (TLBs) cache recent page table entries, reducing the need for multiple memory accesses during address resolution and supporting features like global pages to avoid flushing on context switches.[20][21]