Memory Addresses
When dealing with 80x86 microprocessor, we have to distinguish three kinds of addresses: logical address, linear address (also known as virtual address) and physical address.
Segmentation in Hardware
Segment Selectors and Segmentation Registers
A logical address consists of two parts: a segment identifier and an offset that specifies the relative address within the segment. The segment identifier is a 16-bit field called the Segment Selector, while the offset is a 32-bit field.
Segment Descriptors
Each segment is represented by an 8-byte Segment Descriptor that describe the segment characteristics. Segment Descriptors are stored either in the Global Descriptor Table (GDT) or in the Local Descriptor Table (LDT).
The address and size of the GDT in main memory are contained in the gdtr control register, while the address and size of the currently used LDT are contained in the ldtr control register.
There are several types of segments, and thus several types of Segment Descriptors (Code Segment Descriptor, Data Segment Descriptor, Task State Segment Descriptor, and Local Descriptor Table Descriptor).
Fast Access to Segment Descriptors
Every time a Segment Selector is loaded in a segment register, the corresponding Segment Descriptor is loaded from memory into the matching nonprogrammable CPU register.
Segmentation Unit
The segmentation unit performs the following operations:
- Examines the TI field of the Segment Selector to determine which Descriptor Table stores the Segment Descriptor.
- Computes the address of the Segment Descriptor from the index field of the Segment Selector.
- Adds the offset of the logical address to the Base field of the Segment Descriptor, thus obtaining the linear address.
Segmentation in Linux
Linux uses segmentation in a very limited way.
In fact, segmentation and paging are somewhat redundant, because both can be used to separate the physical address space of processes: segmentation can assign a different linear address space to each process, while paging can map the same linear address space into different physical address space.
All Linux processes running in User Mode use the same pair of segments to address instructions and data. These segments are called user code segment and user data segment, respectively. Similarly, all Linux processes running in Kernel Mode use the same pair of segments to address instructions and data: they are called kernel code segement and kernel data segment, respectively.
The Linux GDT
In uniprocessor systems there is only one GDT, while in multiprocessor systems there is one GDT for every CPU in the system.
Each GDT includes 18 segment descriptors and 14 null, unused, or reserved entries.
The 18 segment descriptors included in each GDT point to the following segments:
- Four user and kernel code and data segments.
- A Task State Segment (TSS), different for each processor in the system.
- A segment including the default Local Descriptor Table (LDT), usually shared by all processes.
- Three Thread-Local Storage (TLS) segments.
- Three segments related to Advanced Power Management (APM).
- Five segments related to Plug and Play (PnP) BIOS services.
- A special TSS segment used by the kernel to handle "Double fault" exceptions.
The Linux LDTs
Most Linux User Mode applications do not make use of a Local Descriptor Table, thus the kernel defines a default LDT to be shared by most processes.
Call gates are a mechanism provided by 80x86 microprocessors to change the privilege level of the CPU while invoking a predefined function.
Paging in Hardware
The data structures that map linear to physical addresses are called page tables; they are stored in main memory and must be properly initialized by the kernel before enabling the paging uint.
Regular Paging
Starting with the 80386, the paging unit of Intel processors handles 4KB pages.
The 32 bits of a linear address are divided into three fields: Directory (the most significant 10 bits), Table (the intermediate 10 bits), Offset (the least significant 12 bits).
The translation of linear addresses is accomplished in two steps, each based on a type of translation table. The first translation table is called the Page Directory, and the second is called the Page Table.
The entries of Page Directories and Page Tables have the same structure. Each entry includes the following fields: Present flag, Field containing the 20 most significant bits of a page frame physical address, Accessed flag, Dirty flag, Read/Write flag, User/Supervisor flags, PCD and PWT flags, Page Size flag, Global flag.
Extended Paging
Starting with the Pentium model, 80x86 microprocessors introduce extended paging, which allows page frames to be 4 MB instead of 4 KB in size.
Hardware Protection Scheme
Only two privilege levels are associated with pages and Page Tables.
Only two types of access rights (Read and Write) are associated with pages.
The Physical Address Extension (PAE) Paging Mechanism
PAE is activated by setting the Physical Address Extension (PAE) flag in the cr4 control register. The Page Size (PS) flag in the page directory entry enables large page sizes (2 MB when PAE is enabled).
Paging for 64-bit Architectures
Two-level paging is not suitable for computers that adopt a 64-bit architecture.
Hardware Cache
Hardware cache memories were introduced to reduce the speed mismatch between CPU and RAM. They are based on the well-known locality principle, which holds both for programs and data structures.
Translation Lookaside Buffers (TLB)
Besides general-purpose hardware caches, 80x86 processors include another cache called Translation Lookaside Buffers (TLB) to speed up linear address translation.
Paging in Linux
Linux's handling of processes relies heavily on paging. In fact, the automatic translation of linear addresses into physical ones makes the following design objectives feasible:
- Assign a different physical address space to each process, ensuring an efficient protection against addressing errors.
- Distinguish pages (groups of data) from page frames (physical addresses in main memory).
The Linear Address Fields
PAGE_SHIFT Specifies the length in bits of the Offset field; when applied to 80x86 processors, it yields the value 12.
PMD_SHIFT The total length in bits of the Offset and Table fields of a linear address; in order words, the logarithm of the size of the area a Page Middle Directory entry can map.
PUD_SHIFT Determines the logarithm of the size of the area a Page Upper Directory entry can map.
PGDIR_SHIFT Determines the logarithm of the size of the area that a Page Global Directory entry can map.
PTRS_PER_PTE, PTRS_PER_PMD, PTRS_PER_PUD, and PTRS_PER_PGD Computer the number of entries in the Page Table, Page Middle Directory, Page Upper Directory, and Page Global Directory.
Page Table Handling
Physical Memory Layout
As a general rule, the Linux kernel is installed in RAM starting from the physical address 0x00100000 -- i.e., from the second megabyte.
A typical configuration yields a kernel that can be loaded in less than 3 MB of RAM.
Process Page Tables
The linear address space of a process is divided into two parts:
- Linear addresses from 0x00000000 to 0xbfffffff can be addressed when the process runs in either User or Kernel Mode.
- Linear addresses from 0xc0000000 to 0xffffffff can be addressed only when the process runs in Kernel Mode.
Kernel Page Tables
Provisional kernel Page Tables
A provisional Page Global Directory is initialized statically during kernel compilation, while the provisional Page Tables are initialized by the startup_32() assembly language function defined in arch/i386/kernel/head.S.
Final kernel Page Table when RAM size is less than 896 MB
Final kernel Page Table when RAM size is between 896 MB and 4096 MB
Final kernel Page Table when RAM size is more than 4096 MB
Fix-Mapped Linear Addresses
Basically, a fix-mapped linear address is a constant linear address like 0xffffc000 whose conrresponding physical address does not have to be the linear address minus 0xc0000000, but rather a physical address set in an arbitrary way.
Handling the Hardware Cache and the TLB