虚拟内存 | 原理、硬件地址转换、运行机制与 Linux 应用

注:本文为 "虚拟内存" 相关译文合辑。

英文引文,机翻未校。

如有内容异常,请看原文。


Virtual Memory 虚拟内存

Optional readings for this topic from Operating Systems: Principles and Practice : Chapter 8.

本章节拓展阅读:《操作系统:原理与实践》第 8 章

How can one memory be shared among several concurrent processes?

如何让多个并发进程共享物理内存?

But first, let's think about ...

首先我们先来分析基础场景......

Single-tasking (no sharing):

单任务模式(无内存共享)

  • Highest memory holds OS.
    内存高地址区域存放操作系统。
  • Process is allocated memory starting at 0, up to the OS area.
    进程内存从地址 0 开始分配,直至操作系统所在区域。
  • Examples: early batch monitors where only one job ran at a time. It could corrupt the OS, which would be rebooted by an operator. Some early personal computers were similar.
    示例:早期批处理监控系统,同一时刻仅运行一个作业。作业可能破坏操作系统,需由管理员重启设备。部分早期个人计算机也采用该架构。

Goals for sharing memory:

内存共享的设计目标

  • Multitasking: allow multiple processes to be memory-resident at once.
    多任务:支持多个进程同时驻留内存。
  • Transparency: no process should need to be aware of the fact that memory is shared. Each must run regardless of the number and/or locations of processes.
    透明性:进程无需感知内存共享状态,无论系统内进程数量与内存分布如何,均可正常运行。
  • Isolation: processes mustn't be able to corrupt each other.
    隔离性:进程之间无法相互破坏数据。
  • Efficiency (both of CPU and memory) shouldn't be degraded badly by sharing. After all, the purpose of sharing is to increase overall efficiency.
    高效性:内存共享不会大幅降低 CPU 与内存的运行效率,共享内存的目的就是提升系统整体性能。

Load-time relocation:

加载时重定位

  • Highest memory holds OS.

    内存高地址区域存放操作系统。

  • First process loaded at 0; others fill empty spaces.

    首个进程从地址 0 加载,其余进程依次占用空闲内存。

  • Use first-fit or best-fit allocation to manage available memory.

    采用首次适应算法或最佳适应算法管理空闲内存。

  • When a process is loaded, relocate it so that it can run in its allocated memory area, similar to linking:

    进程加载时完成地址重定位,使其在分配的内存区间内运行,原理与链接过程类似:

    • Linker outputs information about addresses in executable files
      链接器在可执行文件中记录地址相关信息
    • Similar to unresolved references in object files: indicates which locations contain memory addresses
      类似于目标文件中的未解析引用,用于标记所有内存地址所在位置
    • OS modifies addresses when it loads process (add base address)
      操作系统加载进程时,通过追加基地址完成地址修正
  • What are the problems with this approach?

    该方案存在哪些问题?

    • Any process can corrupt any other process and/or the operating system.
      任意进程都可能破坏其他进程或操作系统。
    • Only one region per process.
      每个进程仅能使用一块连续内存区域。
    • Must declare size statically.
      内存空间大小需要静态定义。
    • Can't grow regions if adjacent space is in use.
      若相邻内存已被占用,进程区域无法扩容。
    • Can't move once loaded.
      进程加载完成后,内存位置无法变更。
    • Memory fragmentation.
      会产生内存碎片。

Dynamic Address Translation

动态地址转换

Instead of translating addresses statically when a program is loaded, add hardware (memory management unit ) that changes addresses dynamically during every memory reference .

不再在程序加载阶段执行静态地址转换,而是增设硬件内存管理单元(MMU) ,在每次内存访问时动态完成地址转换。

Draw picture of processor and memory, with MMU in between. Show address and data lines.

绘制处理器与内存的连接示意图,将 MMU 置于二者之间,并标注地址总线与数据总线。

Each address generated by a thread (called a virtual address ) is translated in hardware to a physical address. This happens during every memory reference.

线程生成的地址称为虚拟地址 ,硬件会将其转换为物理地址,该转换在每次内存访问时执行。

Results in two views of memory, called address spaces :

由此产生两种内存视图,统称为地址空间

  • Virtual address space is what the program sees
    虚拟地址空间:程序所能感知的内存视图
  • Physical address space is the actual allocation of memory
    物理地址空间:内存的实际物理分配情况

Draw virtual address space and physical address space under processor/memory diagram, show correspondences.

在处理器-内存示意图下方绘制虚拟地址空间与物理地址空间,并标注二者映射关系。

Base and Bound Translation

基址-限界地址转换

Two hardware registers:

系统配备两个硬件寄存器:

  • Base: physical address corresponding to virtual address 0.
    基址寄存器:对应虚拟地址 0 的物理地址。
  • Bound: highest allowable virtual address.
    限界寄存器:进程可访问的最大虚拟地址。

On each memory reference:

每次内存访问时执行以下操作:

  • Compare virtual address to bound register, trap if >=
    将虚拟地址与限界寄存器数值比对,若超出范围则触发异常
  • Add virtual address to base to produce physical address.
    虚拟地址与基址寄存器数值相加,得到物理地址。

Each process has its own base and bound values, which are saved in the process control block. Draw address spaces.

每个进程都拥有独立的基址与限界数值,相关信息存储在进程控制块中。绘制对应地址空间示意图。

Each process appears to have a completely private memory whose size is determined by the bound register.

每个进程都独占一块私有内存,内存大小由限界寄存器决定。

Processes are isolated from each other and OS.

进程之间、进程与操作系统之间实现相互隔离。

No address relocation is necessary when a process is loaded.

进程加载时无需额外执行地址重定位。

Go through base & bound example:

基址-限界机制示例讲解:

  • Show memory, ip, sp, base, bound
    展示内存、指令指针、栈指针、基址寄存器、限界寄存器
  • Initially: base = 6000, bound = 2000
    初始参数:基址 = 6000,限界 = 2000
  • Ask about physical locations for stack, current code
    分析栈与当前代码对应的物理地址
  • Walk through "CALL 140" instruction, pushing the return address onto the stack.
    逐步解析 CALL 140 指令执行过程,包括将返回地址压入栈的操作。
  • Note how the address is sent to memory on the data lines?
    说明地址如何通过数据总线传输至内存。
  • Now stop the process and relocate the process to 3000
    暂停进程,并将进程重定位至物理地址 3000 处
  • Walk through the return.
    逐步解析函数返回流程。
  • Point out that physical addresses are never visible to the thread.
    强调线程无法感知物理地址。

Philosophy of meaning: comes only from use ; it's not innate. Hmmm, not clear this fits here; skip?

语义哲学:含义源于使用而非先天固有。该内容与本节关联较弱,略过。

OS runs with relocation disabled, so it can access all of memory (a bit in the processor status word controls relocation).

操作系统运行时关闭地址重定位功能,因此可访问全部内存(处理器状态字中的一位用于控制重定位开关)。

  • Must prevent users from turning off relocation or modifying the base and bound registers (another bit in PS for user/kernel mode).
    禁止用户态程序关闭重定位功能或修改基址、限界寄存器(处理器状态字另有一位区分用户态与内核态)。

Problem: how does OS regain control once it has given it up? Must do two things simultaneously:

问题:操作系统交出控制权后如何重新收回?需要同步完成两项操作:

  • Branch into or out of the OS.
    跳转进入或退出操作系统
  • Change protection (turn relocation on or off).
    切换权限模式(开启/关闭地址重定位)
  • Special instructions control this. Note that the branch address must be carefully controlled. This is like a new form of procedure call that saves and restores more than just the IP.
    该操作由专用指令实现,跳转地址需要严格管控。这是一种特殊的过程调用,除指令指针外,还会保存和恢复更多状态信息。

Base & bound is cheap (only 2 hardware registers) and fast: the add and compare can be done in parallel.

基址-限界机制硬件成本低(仅需两个寄存器)、运行速度快,地址加法与边界比对可并行执行。

Explain how swapping can work.

讲解内存交换的实现原理。

What's wrong with base and bound relocation?

基址-限界重定位机制存在哪些缺陷?

  • Only one contiguous region per program.
    每个程序仅能使用一块连续内存区域。
  • How to manage separate stack?
    无法独立管理栈空间。
  • Can't support read-only information (e.g., code)
    不支持只读数据段(例如程序代码)。
  • How to share memory between processes (e.g., code)?
    难以实现进程间内存共享(例如共享代码段)。
  • Memory fragmentation.
    容易产生内存碎片。

Multiple segments

分段机制

Each process is split among several variable-size areas of memory, called segments.

将每个进程的内存划分为多个大小可变的区域,这些区域称为

  • E.g. one segment for code, one segment for data and heap, one segment for stack.
    示例:代码段、数据与堆段、栈段相互独立。
  • Segments correspond roughly to linker sections.
    分段结构与链接器的段划分基本对应。

Segment map holds the bases and bounds for all the segments of a process, plus protection bit for each segment: read-write versus read-only.

段表记录进程所有段的基址、限界,同时为每个段配置权限位,区分读写权限与只读权限。

  • The segment map is stored in the MMU; modified during context switches
    段表存放于内存管理单元中,进程上下文切换时会更新段表内容

Memory mapping logic consists of table lookup + add + compare.

内存映射逻辑:查表 + 地址相加 + 边界校验。

Each memory reference must indicate a segment number and offset :

每次内存访问需要指定段号段内偏移

  • Top bits of address select segment, low bits the offset.
    地址高位用于选择段,低位表示段内偏移。
  • Example: PDP-10 with high and low segments selected by high-order address bit.
    示例:PDP-10 计算机通过地址最高位区分高段与低段。
  • Or, segment can be selected implicitly by the instruction (e.g. code vs. data, stack vs. data, or x86 prefixes). This approach was most commonly used to expand the address space of machines with very small addresses, such as PDP-11s
    也可通过指令隐式选择段(如代码段、数据段、栈段,或 x86 指令前缀)。该方式多用于拓展早期小型计算机(如 PDP-11)的地址空间。

Advantage of segmentation: flexibility

分段机制优势:灵活性强

  • Manage each segment separately:
    可对每个段进行独立管理:
    • Grow and shrink independently
      各段可独立扩容、缩容
    • Swap to disk
      可单独将段交换至磁盘
  • Can share segments between processes (e.g., shared code).
    支持进程间共享段(如共享代码段)。
  • Can move segments to compact memory and eliminate fragmentation.
    可移动段来整理内存,消除内存碎片。

Talk about stacks growing downward? This takes about 15 minutes... probably not worth it. Save for an exam question?

讲解栈向下增长的原理?该内容耗时较长,暂不展开,留作课后考题。

What's wrong with segmentation?

分段机制存在的问题:

  • Variable length results in memory fragmentation.
    段长度可变,依然会产生内存碎片。
  • Fixed number of segments still creates limits: can't mmap files.
    段的数量固定,存在使用限制,无法实现文件内存映射。
  • Address space is rigidly divided.
    地址空间划分方式僵化。

Paging

分页机制

Divide virtual and physical memory into fixed-sized chunks called pages . The most common size is 4 Kbytes. Unfortunately, this is way too small today.

将虚拟内存与物理内存划分为固定大小的块,这些块称为。传统页大小普遍为 4KB,该尺寸在当下已无法满足需求。

For each process, a page map specifies the physical page for of that process' virtual pages pages, along with read-only and "present" bits. No need for bound registers, since all pages are always the same size.

每个进程配备页表,页表记录虚拟页对应的物理页编号,同时包含只读位与存在位。由于所有页面大小统一,不再需要限界寄存器。

Page map stored in contiguous memory (with base register in hardware).

页表存放于连续内存中,硬件设有页表基址寄存器。

Translation process: page number always comes directly from the address. Since page size is a power of two, no comparison or addition is necessary. Just do table lookup and bit concatenation.

地址转换流程:页号直接从地址中提取。页面大小为 2 的整数次幂,因此无需加法与比对运算,仅需查表并拼接地址位即可。

Easy to allocate: keep a free list of available pages and grab the first one. Easy to swap since everything is the same size, which is usually the same size as disk blocks.

内存分配简单:维护空闲页链表,分配时直接取用链表首节点。页面大小统一,且通常与磁盘块尺寸一致,内存交换操作也更简便。

Flexible management: Can represent a segment with a collection of pages, starting on any page boundary.

管理方式灵活:可使用一组页面来构成逻辑段,段起始位置可位于任意页边界。

Problem: for modern machines, page maps can be very large:

存在问题:在现代计算机中,页表体量会变得极大:

  • Consider x86-64 addressing architecture: 48-bit addresses, 4096-byte pages.
    以 x86-64 架构为例:地址总长度 48 位,页面大小 4096 字节。
  • Draw diagram.
    绘制对应示意图。
  • Class exercise: compute size of page map (assuming 8 bytes per entry): 2**39 bytes (500 GB)!
    课堂练习:计算页表大小(假设每项占 8 字节),总容量可达 2^39 字节(约 500GB)。
  • Ideally, each page map should fit in a page.
    理想状态下,整张页表应能存入一个物理页中。
  • Most processes are small, so most page map entries are unused.
    多数进程占用内存较小,页表中大部分表项处于空闲状态。
  • Even large processes use their address space sparsely (e.g., code at the bottom, stack at the top)
    即便是大进程,地址空间的使用也较为稀疏(例如代码段位于低地址,栈段位于高地址)。

Derive the full Intel x86-64 architecture bottom-up:

逐层解析英特尔 x86-64 完整地址架构:

  • PML1 (how big is top-level page map?)
    一级页表(顶层页表容量为多少?)
  • PML2 ... PML4
    二级页表......四级页表
  • Make it clear that the page maps form a tree structure
    明确页表整体构成树形结构

Solution: multi-level page maps. Intel x86-64 addressing architecture:

解决方案:多级页表。英特尔 x86-64 地址架构说明:

  • 4 Kbyte pages: low-order 12 bits of virtual address hold offset within page.
    4KB 页面:虚拟地址低 12 位表示页内偏移。
  • 4 levels of page map, each indexed with 9 bits of virtual address.
    共四级页表,每一级使用虚拟地址中的 9 位作为索引。
  • Each page map fits in one page (page map entries are 8 bytes).
    每一级页表都可存入单个物理页(页表项占 8 字节)。
  • Can omit empty page maps.
    可省略无内容的空页表。

Have class go through 3 memory references:

课堂实操:分析三类内存访问场景:

  • Code page 0
    0 号代码页访问
  • Data page
    数据页访问
  • Highest stack page
    最高地址栈页访问

What subset of the page map structure is needed for the simplest process with code, data, and stack?

一个包含代码、数据、栈的最简进程,需要用到页表结构的哪些部分?

What needs to happen if the data segment grows to two pages?

若数据段扩容至两个页面,系统需要执行哪些操作?

Note that the virtual address space need not be the same size as the physical address space.

注意:虚拟地址空间与物理地址空间的容量无需保持一致。

Note that x86 processors actually support several different translation schemes.

注意:x86 处理器支持多种不同的地址转换方案。

With paging, processes can share memory flexibly:

基于分页机制,进程可灵活实现内存共享:

  • A single page
    共享单个页面
  • An entire page table at any level (PML1, PML2, etc.)
    共享任意一级完整页表(一级、二级页表等)

Next problem: page maps are too large to load into fast memory in the MMU.

新的问题:页表体积过大,无法全部存入内存管理单元的高速存储中。

  • Page maps kept in main memory
    页表存放在主存中
  • Relocation unit holds base address for top-level page map
    地址转换单元仅保存顶层页表的基地址
  • With x86-64 architecture, must make 4 memory references to translate a virtual address!
    在 x86-64 架构下,完成一次虚拟地址转换需要访问 4 次主存!

Translation Lookaside Buffers (TLBs)

转换旁视缓冲区(TLB)

Solution to page translation overhead: create a small hardware cache of recent translations.

解决页地址转换开销的方案:使用小型硬件缓存,存放近期的地址转换记录。

  • Each cache entry stores the page number portion of a virtual address (36 bits for x86-64) and the corresponding physical page number (40 bits for x86-64).
    每个缓存项存储虚拟地址的页号部分(x86-64 架构为 36 位)以及对应的物理页号(x86-64 架构为 40 位)。
  • Typical TLB sizes: 64-2048 entries.
    TLB 常规容量:64 ~ 2048 个表项。
  • On each memory reference, compare the page number from the virtual address with the virtual page numbers in every TLB entry (in parallel).
    每次内存访问时,将虚拟页号与 TLB 内所有表项并行比对。
  • If there is a match, use the corresponding physical page number.
    若匹配成功,直接使用对应的物理页号。
  • If no match, perform the full address translation and save the information in the TLB (replace one of the existing entries).
    若匹配失败,则执行完整地址转换,并将结果存入 TLB(替换原有表项)。
  • TLB "hit rates" typically 95% or more.
    TLB 命中率通常可达 95%及以上。
  • Mention that the scheme above is fully associative, so it requires one comparator for each entry. Other schemes are possible, but beyond the scope of this class.
    上述结构为全相联缓存,每个表项都配备独立比较器。还存在其他缓存架构,本节课不作讲解。

TLB complications:

TLB 带来的相关问题:

  • When context switching, must invalidate all of the entries in the TLB (mappings will be different for the next process). Chip hardware does this automatically when the page map base register is changed.
    进程上下文切换时,必须清空 TLB 所有表项(下一进程的地址映射完全不同)。修改页表基址寄存器时,硬件会自动完成清空操作。
  • If virtual memory mappings change for the current process (e.g. page moved), must invalidate some TLB entries. Special hardware instruction for this.
    若当前进程的虚拟内存映射发生变更(如页面移动),需要作废部分 TLB 表项,该操作由专用硬件指令完成。

Miscellaneous Topics

补充知识点

How does the operating system get information from user memory? E.g. I/O buffers, parameter blocks. Note that the user passes the OS a virtual address .

操作系统如何读取用户内存中的数据?例如 I/O 缓冲区、参数块。注意:用户程序传递给操作系统的是虚拟地址

  • In some systems the OS just runs unmapped:
    部分系统中,操作系统运行时不启用地址映射:
    • OS reads page maps and translates user addresses in software.
      操作系统通过软件读取页表,手动完成用户地址转换。
    • Addresses that are contiguous in the virtual address space may not be contiguous physically. Thus I/O operations may have to be split up into multiple blocks. Show example from slides.
      虚拟地址空间中连续的地址,在物理内存中未必连续。因此 I/O 操作需要拆分为多个数据块执行,详见课件示例。
  • Most newer systems include kernel and user memory in same virtual address space (but kernel memory not accessible in user mode (special bit in page map entries)). This makes life easier for the kernel, although it doesn't solve the I/O problem.
    多数新式系统将内核内存与用户内存纳入同一虚拟地址空间(用户态无法访问内核内存,页表项中通过专用权限位进行限制)。该设计简化了内核操作,但并未解决 I/O 拆分问题。

Aliasing : the same physical address can appear at multiple virtual locations

地址别名:同一个物理地址可对应多个不同的虚拟地址。

Another issue with paging: internal fragmentation .

分页机制的另一问题:内部碎片

  • Can't allocate partial pages, so for small chunks of information only part of the page will be used

    系统无法分配半页内存,存储少量数据时,页面仅部分空间被使用。

  • Result: wasted space at the ends of some pages

    最终造成页面尾部空间浪费。

  • Define external fragmentation, compare with internal fragmentation.

    Define external fragmentation : free memory exists as many small non-contiguous blocks scattered throughout the address space. Even though total free memory is sufficient, a large contiguous memory request cannot be satisfied.

    定义外部碎片:空闲内存被分割为大量零散、不连续的块分布在地址空间中。即便空闲内存总量充足,也无法满足大块连续内存的申请需求。

    Comparison between internal fragmentation and external fragmentation:

    内部碎片与外部碎片对比:

    • Internal fragmentation: Wasted space inside an allocated page/segment. Caused by fixed-size page allocation. Common in paging systems.
      内部碎片:已分配的页/段内部产生空间浪费。由固定大小的页面分配机制导致,普遍存在于分页系统中。
    • External fragmentation: Wasted space consists of unallocated free blocks scattered between allocated regions. Caused by variable-size allocation. Common in segmentation and base-and-bound systems.
      外部碎片:未被分配的空闲块散落于已占用内存区域之间,属于整体空间浪费。由可变长度内存分配机制导致,普遍存在于分段、基址-限界系统中。
  • Not much of a problem in today's systems:

    在现代系统中,该问题影响较小:

    Ask students why:

    课堂提问:原因是什么?

    • Logical segments such as code or stack tend to be much larger than a page.
      代码、栈等逻辑段的大小通常远大于单个页面。
    • Percentage wasted space from fragmentation is small.
      碎片造成的空间占用占比很低。
  • What happens if page sizes grow?

    若增大页面大小,会产生哪些影响?

    Larger page sizes increase internal fragmentation, as more unused space will exist inside each allocated page. Meanwhile, external fragmentation is alleviated.

    页面尺寸增大会加剧内部碎片,每个已分配页面内的闲置空间随之变多;但外部碎片问题会得到缓解。


Mastering Linux Virtual Memory: Concepts, Usage, and Best Practices

精通 Linux 虚拟内存:概念、使用方法与最佳实践

Last Updated: Jan 16, 2026

In the realm of Linux systems, virtual memory is a cornerstone of efficient memory management. It serves as a bridge between the limited physical memory (RAM) and the potentially vast memory requirements of modern applications. By using virtual memory, Linux can run larger programs and handle more concurrent processes than the physical memory alone would allow. This blog post aims to provide a comprehensive guide to Linux virtual memory, covering fundamental concepts, usage methods, common practices, and best practices.

在 Linux 系统中,虚拟内存是实现高效内存管理的重要机制。它衔接容量有限的物理内存(随机存取存储器)与现代应用程序庞大的内存需求。借助虚拟内存,Linux 能够运行超出物理内存承载能力的大型程序,同时支持更多并发进程。本文将全面讲解 Linux 虚拟内存,内容涵盖基础概念、使用方式、常规运维手段以及优化准则。

Fundamental Concepts of Linux Virtual Memory

Linux 虚拟内存基础概念

Virtual Address Space

虚拟地址空间

Each process in a Linux system has its own virtual address space, which is a range of memory addresses that the process can use. This virtual address space is divided into different regions, such as the code segment, data segment, and stack segment. The virtual address space provides a level of isolation between processes, ensuring that one process cannot access the memory of another process without proper authorization.

Linux 系统内的每个进程都拥有独立的虚拟地址空间,这是进程可访问的一段内存地址范围。虚拟地址空间划分为多个区域,包括代码段、数据段与栈段。该机制可实现进程间内存隔离,未经授权的进程无法访问其他进程的内存数据。

Page Tables

页表

To map virtual addresses to physical addresses, Linux uses page tables. A page table is a data structure that stores the mapping information between virtual pages and physical pages. When a process accesses a virtual address, the CPU first checks the page table to find the corresponding physical address. If the page is not present in physical memory, a page fault occurs, and the operating system fetches the page from disk.

Linux 通过页表完成虚拟地址到物理地址的映射。页表是一种数据结构,用于存储虚拟页与物理页之间的映射关系。当进程访问虚拟地址时,中央处理器会先查询页表,获取对应的物理地址。若目标页面不在物理内存中,系统会触发缺页异常,并从磁盘读取该页面数据。

Swapping and Paging

交换与分页

Swapping and paging are two techniques used by Linux to manage virtual memory. Swapping involves moving entire processes or parts of processes from physical memory to disk when the system is running low on memory. Paging, on the other hand, involves moving individual pages of memory between physical memory and disk. Linux uses a combination of swapping and paging to optimize memory usage and ensure that the system runs smoothly.

交换与分页是 Linux 管理虚拟内存的两种技术。系统内存资源不足时,交换操作会将整个进程或进程的部分数据从物理内存转移至磁盘;分页操作则以单个内存页面为单位,在物理内存与磁盘之间迁移数据。Linux 结合两种方式优化内存资源利用率,保障系统稳定运行。

Usage Methods

使用方法

Checking Memory Usage

查看内存使用情况

To check the memory usage of a Linux system, you can use the free command. The free command displays the amount of free and used memory in the system, as well as the amount of swap space. Here is an example:

可使用 free 命令查看 Linux 系统内存状态,该命令会展示系统已用内存、空闲内存以及交换空间的容量。示例如下:

bash 复制代码
$ free -h
              total        used        free      shared  buff/cache   available
Mem:           7.7G        2.2G        3.1G        104M        2.4G        4.9G
Swap:          2.0G          0B        2.0G

In this example, the system has 7.7GB of physical memory, 2.2GB of which is used, 3.1GB is free, and 2.4GB is used for buffering and caching. The system also has 2.0GB of swap space, all of which is free.

该示例中,系统物理内存总容量为 7.7 GB,已使用 2.2 GB,空闲 3.1 GB,缓冲区与缓存占用 2.4 GB。系统配置的交换空间为 2.0 GB,当前处于完全空闲状态。

Adjusting Swappiness

调整交换倾向参数

Swappiness is a parameter that controls how aggressively Linux swaps memory to disk. The swappiness value ranges from 0 to 100, where 0 means that Linux will only swap memory when it absolutely has to, and 100 means that Linux will swap memory as aggressively as possible. To adjust the swappiness value, you can use the following command:

swappiness 是控制内存交换频率的参数,取值范围为 0 ~ 100。取值为 0 时,系统仅在内存耗尽时才进行交换;取值为 100 时,系统会频繁将内存数据置换到磁盘。执行以下命令可修改该参数:

bash 复制代码
$ sudo sysctl vm.swappiness=10

This command sets the swappiness value to 10, which means that Linux will only swap memory when it is really necessary. To make this change permanent, you can add the following line to the /etc/sysctl.conf file:

上述命令将交换倾向参数设置为 10,系统仅在必要时执行内存交换。若需永久生效,可在 /etc/sysctl.conf 文件中添加如下内容:

bash 复制代码
vm.swappiness = 10

Common Practices

常规运维手段

Monitoring Memory with Tools

内存监控工具

There are several tools available for monitoring memory usage in Linux, such as top, htop, and vmstat. These tools provide real-time information about the memory usage of the system, as well as the memory usage of individual processes. Here is an example of using the top command:

Linux 平台提供多款内存监控工具,例如 tophtopvmstat,这类工具可实时输出系统整体内存状态与单个进程的内存占用情况。top 命令使用示例如下:

bash 复制代码
$ top

The top command displays a list of the processes that are currently running on the system, sorted by CPU usage. You can press the M key to sort the processes by memory usage.

top 命令默认按中央处理器占用率排序,展示系统当前运行的进程列表。按下 M 键,即可将进程按照内存占用量重新排序。

Tuning Memory for Applications

应用程序内存调优

Different applications have different memory requirements, and it is important to tune the memory settings of the system to optimize the performance of these applications. For example, some applications may require a large amount of memory to run efficiently, while others may be able to run with less memory. You can use the ulimit command to set limits on the amount of memory that a process can use. Here is an example:

不同应用程序的内存需求存在差异,合理调整系统内存配置可以优化应用运行表现。部分应用需要大容量内存才能高效运行,也有应用可在低内存环境下正常工作。可使用 ulimit 命令限制单个进程可使用的内存上限,示例如下:

bash 复制代码
$ ulimit -v 1024000

This command sets the maximum amount of virtual memory that a process can use to 1024000 kilobytes (1GB).

该命令将单个进程可使用的虚拟内存上限设置为 1024000 千字节,即 1 GB。

Best Practices

最佳实践

Properly Allocating Swap Space

合理分配交换空间

Swap space is an important part of virtual memory management, and it is important to allocate enough swap space to ensure that the system can handle memory-intensive tasks. As a general rule, you should allocate at least as much swap space as you have physical memory. However, if you have a large amount of physical memory, you may not need as much swap space. You can use the mkswap and swapon commands to create and enable swap space. Here is an example:

交换空间是虚拟内存管理的重要组成部分,充足的交换空间可保障系统平稳运行内存密集型任务。通用配置标准为:交换空间容量至少等同于物理内存容量。若系统配备大容量物理内存,则可适当缩减交换空间。可通过 mkswapswapon 命令创建并启用交换空间,示例如下:

bash 复制代码
$ sudo fallocate -l 2G /swapfile
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile

This command creates a 2GB swap file, sets the appropriate permissions, formats it as swap space, and enables it. To make this change permanent, you can add the following line to the /etc/fstab file:

以上命令创建一个 2 GB 的交换文件、配置访问权限、将文件格式化为交换分区并完成启用。若需永久生效,可在 /etc/fstab 文件中添加如下内容:

bash 复制代码
/swapfile none swap defaults 0 0

Avoiding Unnecessary Memory Consumption

减少无效内存占用

To optimize memory usage, it is important to avoid unnecessary memory consumption. This can be done by closing unused applications, disabling unnecessary services, and using lightweight applications whenever possible. You can also use tools like systemd-analyze to identify and disable unnecessary services. Here is an example:

优化内存使用率需要减少无效资源占用,可关闭闲置应用、禁用非必需服务,并优先选用轻量级程序。借助 systemd-analyze 工具可以排查并关闭多余服务,示例如下:

bash 复制代码
$ systemd-analyze blame

This command displays a list of the services that are taking the most time to start up. You can use the systemctl disable command to disable any services that you do not need.

该命令会列出启动耗时较长的服务,执行 systemctl disable 命令即可关闭不需要的服务。

Conclusion

总结

Linux virtual memory is a powerful and complex feature that plays a crucial role in the performance and stability of Linux systems. By understanding the fundamental concepts of virtual memory, using the appropriate usage methods, following common practices, and implementing best practices, you can optimize the memory usage of your Linux system and ensure that it runs smoothly. Remember to monitor your system regularly and make adjustments as needed to keep your system running at its best.

Linux 虚拟内存功能强大且逻辑复杂,对系统运行表现与稳定性有着重要作用。理解虚拟内存基础概念、掌握对应操作方式、遵循常规运维方法与优化准则,能够有效优化 Linux 系统内存使用状态,保障系统平稳运行。建议定期监控系统状态,并根据实际情况调整配置,维持系统处于良好运行状态。


虚拟内存 (Virtual Memory)

KuoGavin 原创于 2021-05-31 21:51:39 发布

本文阐述虚拟内存的运行机制,涵盖 DRAM 缓存、页表、页命中与缺页、局部性原理等内容。同时介绍 Linux 虚拟内存体系,包含内存区域、缺页异常处理、内存映射与动态内存分配。虚拟内存依托按需调度与地址翻译,为各个进程分配独立地址空间,完成内存管理与内存保护工作,多级页表可提升存储空间的利用效率。Linux 内核借助页表与 TLB 完成地址翻译,通过 mmap 与动态内存分配器管控内存,处理缺页异常,保障系统稳定运行。

本文参照 CSAPP 内容框架,将虚拟内存相关内容划分为两个部分:

  • 虚拟内存的工作机制
  • 虚拟内存的实践与应用

虚拟内存可为计算机提供三类能力:

  • 将主存作为磁盘地址空间的高速缓存,依据局部性特征,仅在主存中留存活跃数据区域,按需完成磁盘与主存间的数据传输,合理利用主存空间;
  • 为每个进程配置统一的地址空间,简化内存管理流程;
  • 隔离各进程地址空间,避免进程间的非法访问与数据篡改。

虚拟内存的工作机制

物理地址 (Physical Address, PA) 对应 DRAM 内的每一个存储字节,系统内每个字节拥有唯一的物理地址。CPU 根据物理地址访问存储单元的过程,称为物理寻址 (Physical Addressing) 。早期个人计算机与低端嵌入式设备普遍采用物理寻址,现代处理器则统一使用虚拟寻址 (Virtual Addressing)

CPU 运行时生成虚拟地址 (Virtual Address, VA) 以访问主存,虚拟地址在送入主存前,会经由内存管理单元 (Memory Management Unit, MMU) 完成地址翻译 (Address Translation),转换为物理地址。地址翻译的依据为存储在 DRAM 中的查询表。

地址空间 (Address Space) 定义为非负整数地址构成的有序集合。搭载虚拟内存的系统中,CPU 从容量为 N = 2 n N = 2^n N=2n 的地址空间生成虚拟地址,该空间称为虚拟地址空间 (Virtual Address Space) , n n n 代表处理器位数(相关概念参考:程序的机器级表示)。系统同时存在物理地址空间 (Physical Address Space) ,其容量对应物理内存总字节数 M M M。

下文对基础概念展开详细说明。

1. 虚拟内存的相关概念和缓存流程

磁盘数据以固定大小的块作为传输单元,磁盘与 DRAM 之间的数据交互均以块为单位。对应到内存体系中,该存储单元定义为页 (Page) 。存储于磁盘的页称为虚拟页 (Virtual Page, VP) ,缓存于 DRAM 的页称为物理页 (Physical Page, PP) ,也可称作页帧 (Page Frame)

虚拟页在运行过程中分为三类状态:

  • 未分配:虚拟内存系统未创建与分配的页,此类页不占用磁盘存储空间,无关联数据;
  • 分配未缓存:已完成分配,但未加载至物理内存的页;
  • 分配已缓存:已完成分配,且加载至物理内存的页。

虚拟内存缓存的执行流程如下:

虚拟内存系统需要判定目标虚拟页是否缓存于 DRAM 中。若判定为命中,系统定位该虚拟页对应的物理页位置;若判定为不命中,系统查找虚拟页在磁盘中的存储位置,在物理内存中选取置换页,将目标虚拟页从磁盘拷贝至 DRAM,完成页面替换(详见 1.3 节 页命中、缺页)。

下文对地址翻译、页面加载等流程涉及的数据结构与运行细节进行说明。

1.1 DRAM 缓存的组织结构

DRAM 即计算机主存。CPU 与 DRAM 之间设置有 SRAM,对应处理器内部的 L1、L2、L3 高速缓存(相关概念参考:计算机组成概略------0.2.2 内存)。存储器层次结构如下图所示。

VM 系统使用 DRAM 作缓存的举例

存储器层次结构图

存储器层次结构的设计用于适配 CPU 与磁盘之间的速度差异,提升 CPU 利用率。硬件读写速度存在明确差距:DRAM 的访问速度较 SRAM 慢约 10 倍,磁盘的访问速度较 DRAM 慢约 100000 倍。SRAM 缓存不命中带来的性能损耗,远低于 DRAM 缓存不命中。

受限于 SRAM 制造成本高、存储容量小的特性,结合程序运行的局部性特征,系统选择 DRAM 作为虚拟内存的缓存载体。针对 DRAM 缓存不命中的问题,操作系统为 DRAM 缓存配置专用页面替换算法,同时缓存数据统一采用写回策略。当前主流计算机中,基于 DRAM 缓存的虚拟内存系统已得到广泛应用。

1.2 页表

地址翻译功能由软硬件协同实现,参与组件包含操作系统、MMU 地址翻译硬件,以及存储在物理内存的页表 (Page Table)。页表实现虚拟页到物理页的映射关系。地址翻译硬件执行虚拟地址转换时,会读取页表数据,操作系统负责维护页表内容,以及磁盘与 DRAM 之间的页面传输。

一个有 8 个虚拟页和 4 个物理页的系统的页表

页表本质为页表条目 (Page Table Entry, PTE) 组成的数组。所有页表条目拥有统一的数据结构与占用空间,每一个条目对应虚拟地址空间内唯一的页。

单个页表条目由 1 位有效位与 n n n 位地址字段构成。有效位用于标识虚拟页的缓存状态:

  • 有效位为 0 0 0:
    1. 地址字段为 null \text{null} null:对应虚拟页处于未分配状态;
    2. 地址字段非 null \text{null} null:对应虚拟页处于分配未缓存状态,地址字段记录该虚拟页在磁盘的起始地址。
  • 有效位为 1 1 1:对应虚拟页处于分配已缓存状态,地址字段记录该虚拟页在 DRAM 中对应物理页的起始地址。

页面分配的执行流程:在磁盘中划分存储空间,创建并更新页表条目,使条目指向磁盘内新建的页面。

1.3 页命中、缺页
页命中

CPU 读取数据时,地址翻译硬件检索页表。若目标虚拟页对应的有效位为 1 1 1,表明该页已缓存至 DRAM,该状态称为页命中。系统读取对应页表条目内的物理内存地址,拼接生成完整物理地址,完成数据访问。

缺页

DRAM 缓存未命中的情况称为缺页 (Page Fault) ,是按需调度机制的典型体现。CPU 访问分配未缓存的虚拟页时,硬件检测到有效位为 0 0 0,触发缺页异常。

缺页异常处理程序启动后,执行如下流程:

  1. 选取物理内存中的置换页;
  2. 若置换页存在数据修改记录,内核将页面数据写回磁盘;若未修改,仅更新置换页对应的页表条目;
  3. 从磁盘读取目标虚拟页数据,写入至选定的物理页,同步更新页表条目;
  4. 异常处理程序退出,原进程重新执行触发缺页的指令,再次发起虚拟地址翻译,进入页命中流程。

磁盘与内存之间的页面传输行为,称为交换 (swapping)页面调度 (paging) 。页面从磁盘载入 DRAM 称为页面调入,从 DRAM 写回磁盘称为页面调出。仅在缓存不命中时执行页面调入的策略,称为按需页面调度(demand paging)。现代计算机系统均采用该调度策略。预加载页面的预测式调度方式未得到普及。

1.4 虚拟内存缓存高效能的保证------局部性

DRAM 作为虚拟内存缓存载体,缺页会造成明显的性能损耗,但虚拟内存仍可稳定运行,原因在于程序运行具备局部性 (locality) 特征。程序运行过程中,约 80 % 80\% 80% 的运行时长,仅执行约 20 % 20\% 20% 的代码。

程序运行期间引用的页面总量,可超出物理内存容量。局部性特征使程序在运行时,仅对数量有限的活动页面 (active page) 进行访问,该页面集合称为工作集 (working set) ,也称作常驻集合 (resident set)。工作集页面载入内存后,后续访问大多触发页命中,不会产生额外的磁盘读写操作。

若程序局部性表现较差,或工作集容量超出物理内存上限,会出现页面频繁换入换出的现象,该状态称为抖动 (thrashing) 。此时虚拟内存功能正常,但进程运行速率大幅下降。Linux 系统可调用 getrusage 函数统计缺页次数。

2. 虚拟内存进行内存管理相关机制

结合按需页面调度与独立虚拟地址空间,虚拟内存可优化系统内存使用与管理流程,相关机制说明如下:

  • 简化链接:统一的虚拟地址空间格式,不受物理内存实际分布影响,各进程内存映像可采用相同基础结构;
  • 简化加载 :虚拟内存降低可执行文件与共享对象文件的加载难度。加载器为进程的 .text 段与 .data 段分配虚拟页,将页表有效位设为 0 0 0,地址字段指向目标文件对应位置。加载过程不执行数据拷贝,遵循按需调度规则;
  • 简化共享:统一的虚拟地址空间,为操作系统提供进程间、进程与内核间的内存共享方案。进程私有代码、数据、堆、栈区域,由操作系统配置页表,将虚拟页映射至离散的物理页;
  • 简化内存分配 :进程申请堆空间(如调用 malloc)时,操作系统分配连续的虚拟页,并将其映射至任意位置的 k k k 个物理页。物理页无需保证地址连续。

下文对细分机制展开说明。

2.1 内存保护

操作系统需要实现多项访问控制:禁止进程修改只读 .rodata 段、禁止进程篡改内核代码与数据、禁止进程读写其他进程私有内存、限制共享页面的非法修改。

地址翻译是虚拟地址访问的必经流程,基于该特性,可扩展页表条目,增加权限控制位,实现页面级内存保护。

利用 PTE 拓展提供的页面级内存保护

指令违反页面访问权限规则时,CPU 触发保护故障,Linux 系统中将该故障定义为段故障,对应系统信号为 SIGSEGV

2.2 地址翻译

地址翻译由 MMU 硬件与操作系统软件协同完成,实现虚拟地址空间到物理地址空间的映射,映射关系表示为:

MAP : VAS → PAS ∪ ∅ \text{MAP}: \text{VAS} \to \text{PAS} \cup \varnothing MAP:VAS→PAS∪∅

得到物理页面地址

页表基址寄存器 (Page Table Base Register, PTBR) 存储当前进程页表的起始地址。MMU 根据虚拟页号检索对应页表条目,提取条目内的物理页号 (PPN)。由于虚拟页与物理页的字节容量一致,虚拟页偏移 (VPO) 与物理页偏移 (PPO) 数值相等。将物理页号与页偏移拼接,即可得到完整物理地址。

使用页表的地址翻译

CPU 获取物理页面模型和步骤

下图分别展示页面命中与页面缺页的执行流程。

页面命中情形步骤和图解 页面未命中情形步骤和图解

CPU 每生成一个虚拟地址,MMU 都需要读取页表条目,单次操作会额外占用数十至数百个机器周期。系统在 MMU 内部集成专用缓存,用于存放页表条目,该缓存称为翻译后备缓存器 (Translation Lookaside Buffer, TLB)。TLB 以虚拟页号作为检索依据,并附加控制字段。依托硬件缓存替代内存查表,可将地址翻译的耗时压缩至 1~2 个机器周期。

高速缓存和虚拟内存的结合

TLB 命中与不命中情形的步骤和流程图

上述图解中,地址翻译对应的 MMU 模块,在高速缓存架构中,a) 对应 L1 高速缓存,b) 对应存储器层次结构中非虚线框的全部模块。

2.3 多级页表

单层页表会占用大量物理内存空间,为降低内存开销,系统引入多级页表 结构。多级页表可理解为嵌套式页表,仅为已分配的虚拟页创建下级页表,缩减空白页表占用的内存。 k k k 级页表架构下,访问单个物理页需要依次读取 k k k 个页表条目。该设计将页表固定内存开销限制在一级页表范围内。

探究和应用虚拟内存

3. Linux 虚拟内存系统

Linux 为每个进程分配独立的虚拟地址空间,即进程内存快照。进程虚拟地址空间划分为内核虚拟内存与进程私有虚拟内存两部分。

一个 Linux 进程的虚拟内存

未讲解虚拟内存的进程内存快照

加载器是如何映射用户地址空间的区域的

内核虚拟内存包含两类区域:一类映射至所有进程共享的物理页,系统会将与 DRAM 总容量相等的连续虚拟页,映射至对应连续物理页;另一类为进程独有区域。

进程虚拟内存区域存储进程专属数据,包含页表、进程上下文栈、虚拟地址空间管理结构体等内容。

3.1 Linux 虚拟内存区域

Linux 将已分配的虚拟内存划分为若干区域 ,也称作。区域是连续的虚拟内存块,块内页面具备关联属性。所有已分配虚拟页均隶属于对应区域,未归属任何区域的虚拟页处于未分配状态,进程无法访问。

段结构允许虚拟地址空间存在空闲区间,操作系统无需维护未分配虚拟页的相关数据,不会占用内存、磁盘等系统资源,这也是多级页表实现内存压缩的基础。

进程内已分配虚拟页分属不同段,由段统一管理页面,该内存管理模式称为段页式内存管理

Linux 内核为每个进程创建 task_struct 任务结构体,结构体存储进程运行所需全部信息,包含进程标识、栈指针、程序名称、程序计数器等。task_struct 内存在指针成员,指向 mm_struct 结构体,该结构体记录虚拟内存整体状态:

  • pgd:指向一级页表(页全局目录)基地址,基地址同时存放于 CR3 基址寄存器;
  • 链表指针:指向 vm_area_structs 区域结构体链表,链表每个节点对应一个内存段,节点字段定义如下:
    • vm_start:内存区域起始地址;
    • vm_end:内存区域结束地址;
    • vm_prot:区域内页面的读写执行权限;
    • vm_flags:标识页面为进程私有或多进程共享,同时记录其他属性;
    • vm_next:指向链表内下一个区域结构体。
3.2 Linux 缺页异常处理

MMU 翻译虚拟地址 A A A 时触发缺页异常,Linux 系统按如下流程处理:

  1. 合法性校验:判断虚拟地址 A A A 是否隶属于某一内存区域,系统采用树形结构管理内存区域;地址无归属则终止异常处理;
  2. 权限校验:校验当前进程对地址 A A A 对应页面的读写执行权限,权限不匹配则触发保护异常,终止进程运行;
  3. 正常缺页:按照 1.3 节描述的流程完成缺页处理。
3.3 内存映射

内存映射 (memory mapping) 指将虚拟内存区域与磁盘对象建立关联,完成虚拟内存区域的数据初始化。虚拟内存区域可映射至两类磁盘对象:

  • 普通文件:虚拟区域映射至文件系统内的普通磁盘文件,例如可执行目标文件。文件内容按页大小拆分,作为虚拟页的初始数据。基于按需页面调度,页面不会提前载入物理内存,仅在进程首次访问对应虚拟地址时执行加载。若虚拟区域容量大于文件容量,剩余空间以二进制零填充;
  • 匿名文件 :由内核创建的特殊文件,文件内容全部为二进制零。进程首次访问该类虚拟页时,内核选取物理内存中的置换页,若置换页存在修改记录则执行写回操作,随后以二进制零覆盖页面数据,更新页表并标记页面为已缓存。该过程无磁盘数据传输,对应页面称为请求二进制零的页 (demand-zero page)
共享对象的实现

多个进程需要访问同一份只读代码(如系统库、公共程序代码)时,可将文件对象映射为共享段,节省物理内存资源。内核依据文件名称区分重复对象,后续进程的页表条目直接指向已有物理页。

一个共享对象 (图中物理页面实际不一定连续)

Linux 采用写时复制 (copy-on-write) 机制管理私有映射对象。共享段被标记为只读,区域结构体标记为写时复制。进程尝试修改段内页面时,触发保护故障。异常处理程序执行如下操作:

  1. 在物理内存中创建目标页面的副本;
  2. 更新当前进程页表条目,指向新建副本;
  3. 恢复页面可写权限,进程继续执行写操作。

写时复制机制延迟数据拷贝时机,提升内存利用率。

一个私有的写时复制的过程

fork() 函数再探

进程调用 fork() 后,内核为新进程分配进程标识,复制原进程的 mm_struct、区域结构体与页表。所有页面统一标记为只读,所有内存区域标记为写时复制。新进程虚拟内存与原进程完全一致。任意进程执行写操作时,写时复制机制自动创建页面副本,保证各进程地址空间相互独立。

execve() 函数再探

execve() 函数在当前进程内加载并运行可执行文件,替换原有程序,执行步骤如下:

  1. 清理原有区域:删除进程用户空间内全部已存在的内存区域结构体;
  2. 映射私有区域:为新程序的代码段、数据段、bss 段、栈、堆创建私有写时复制区域。代码段与数据段映射至可执行文件的 .text 段与 .data 段;bss 段、栈、堆映射至匿名文件,初始长度为 0;
  3. 映射共享区域:若程序链接动态共享库,将库文件映射至进程共享内存区域;
  4. 重置程序计数器:将程序计数器指向可执行文件入口地址,进程下次调度时从入口地址开始运行,页面依据按需调度规则载入内存。
3.4 mmap 函数进行用户级内存映射

详细内容参考:mmap() 共享内存详解

cpp 复制代码
// 头文件引用
#include <unistd.h>
#include <sys/mman.h>

/**
 * @brief  创建内存映射区域
 * @param  start    映射区域起始地址(建议值,通常设为 null)
 * @param  length   映射区域字节长度
 * @param  prot     页面访问权限
 * @param  flags    映射类型标识
 * @param  fd       文件描述符
 * @param  offset   文件内偏移量
 * @return 成功返回映射区域指针,失败返回 MAP_FAILED(-1)
 */
void *mmap(void *start, size_t length, int prot, int flags,
           int fd, off_t offset);

/**
 * @brief  解除内存映射
 * @param  start    映射区域起始地址
 * @param  length   映射区域字节长度
 * @return 成功返回 0,失败返回 -1
 */
int munmap(void *start, size_t length);

prot 参数可选宏定义:

  • PROT_EXEC:区域内页面可执行;
  • PROT_READ:区域内页面可读;
  • PROT_WRITE:区域内页面可写;
  • PROT_NONE:区域内页面禁止访问。

flags 参数可选宏定义:

  • MAP_PRIVATE:私有映射,启用写时复制;
  • MAP_SHARED:多进程共享映射。

4. 动态内存分配

mmapmunmap 可手动创建、销毁虚拟内存区域,动态内存分配器在其基础上进一步封装,使用更便捷,跨平台兼容性更强。

动态内存分配器管理进程内的堆 (heap) 区域。堆属于请求二进制零的内存段,起始位置紧邻未初始化数据段,地址向高地址方向增长。内核通过 brk 变量记录堆的顶部位置。

动态内存分配器分为两类,划分依据为内存块的释放主体:

  • 显式分配器 (explicit allocator) :由应用程序主动释放内存块。C 语言 malloc / free、C++ 语言 new / delete 均属于显式分配器;
  • 隐式分配器 (implicit allocator) :分配器自动识别无用内存块并完成释放,也称作垃圾收集器 (garbage collector),该机制在 Java 等高级语言中广泛应用。

程序运行期间,部分数据结构的容量仅能在运行阶段确定,动态内存分配可适配该场景。

4.1 堆分配过程中的碎片

内存碎片会降低堆空间利用率,分为两类:

  • 内部碎片 (internal fragmentation):为满足内存对齐规则,在分配块内填充空闲字节造成的空间浪费;
  • 外部碎片 (external fragmentation):空闲内存总容量可满足分配请求,但单个空闲块容量不足,导致内存无法正常分配。
4.2 malloc、free 和 allocator 分配器的实现

相关实现细节可参考对应博文专栏:内存管理

  1. C++内存管理原始工具 (primitives):介绍 C/C++ 基础内存分配接口与重载接口;
  2. std::allocator------以 GNU2.9 为例:解析 STL 标准分配器的数据结构、实现逻辑与碎片优化方案;
  3. malloc / free:SBH(Small Block Heap)------以 VC6 为例:说明 VC6 环境下小块内存分配的实现方案;
  4. loki::allocator:简易分配器的实现思路;
  5. GNU C++ Allocator 分类总结与归纳:梳理 GNU 体系下各类 C++ 分配器的特性与设计思想。
4.3 垃圾收集器/隐式分配器

垃圾收集器将堆内存抽象为有向可达图 (reachability graph) 。图中节点分为根节点与堆节点,堆节点对应堆内已分配内存块。有向边 p → q p \to q p→q 表示块 p p p 内的指针指向块 q q q。根节点为堆外存储单元,包含寄存器、栈变量、全局数据区变量等。

若从任意根节点出发,存在到达节点 P P P 的有向路径,则节点 P P P 判定为可达节点。不可达节点对应无效内存,垃圾收集器定期回收此类内存块,将空间放回空闲链表。

C++ 支持垃圾收集器,主流实现为保守垃圾收集器 (conservative garbage collector)。该类收集器可保证所有可达节点正常标记,存在少量不可达节点被误标记为可达的情况。

4.4 动态内存分配中常出现的错误
  • 访问非法指针:指针指向不可读或不存在的内存空间;
  • 读取未初始化内存:堆内存未做默认清零处理,未初始化指针会引发异常;
  • 栈缓冲区溢出:输入数据长度超出缓冲区容量,属于系统安全研究范畴;
  • 指针运算误用:指针算术运算以指向数据类型的字节长度为单位,而非统一按字节计算;
  • 内存泄漏:内存分配与释放接口未成对调用,堆内存无法回收,长期运行的程序会受明显影响。

总结及归纳

虚拟内存是主存的抽象机制。搭载虚拟内存的处理器采用虚拟寻址方式访问主存,CPU 生成的虚拟地址,经软硬件协同完成翻译后转换为物理地址。硬件依托页表执行地址翻译,操作系统负责维护页表内容。

虚拟内存具备多项功能。虚拟内存将磁盘数据缓存至主存,缓存单元定义为页。访问未缓存页面会触发缺页异常,操作系统执行页面置换与数据读写。虚拟内存可简化链接、程序加载、进程共享、内存分配等流程。依托页表条目内的权限位,可实现页面级内存保护。

地址翻译流程与硬件缓存体系深度结合。TLB 作为页表条目专用缓存,降低地址翻译的时间开销。

内存映射机制将虚拟内存区域与磁盘文件关联,完成空间初始化,广泛应用于数据共享、进程创建、程序加载等场景。应用程序可调用 mmap 手动管理虚拟内存区域,多数程序选择动态内存分配器管理堆空间。动态内存分配器分为显式与隐式两类,显式分配器由应用主动释放内存,隐式分配器依靠垃圾收集器自动回收内存。

C 语言体系下的虚拟内存使用流程较为复杂,易出现各类程序异常,常见问题包含非法指针访问、未初始化内存读取、栈缓冲区溢出、指针运算错误、内存泄漏等。


reference

相关推荐
atomicmaker12 天前
操作系统 — 内存管理
操作系统·内存管理·虚拟内存·段页式
kidwjb3 个月前
虚拟内存的使用
虚拟内存
kidwjb3 个月前
虚拟内存的运作
虚拟内存
燃于AC之乐4 个月前
【Linux系统编程】进程地址空间完全指南:页表、写时拷贝与虚拟内存管理
linux·操作系统·虚拟内存·进程地址空间
柏木乃一5 个月前
进程(8)虚拟地址空间/虚拟内存概述.part1
linux·服务器·c++·进程·虚拟内存·fork
Trouvaille ~6 个月前
【Linux】虚拟内存揭秘:地址空间的魔法
linux·运维·服务器·系统·入门·虚拟内存·进程地址空间
元亓亓亓6 个月前
考研408--操作系统--day8--操作系统--虚拟内存&请求分页&页面置换/分配
android·java·开发语言·虚拟内存
赖small强6 个月前
【Linux内存管理】Linux虚拟内存系统详解
linux·虚拟内存·tlb
linweidong6 个月前
网易ios面试题及参考答案(上)
ios·cdn·进程状态·虚拟内存·raii·网络链路·dns系统