《linux2.4 内存管理》:第 2 章 描述物理内存

Linux 适用于多种体系结构,需用体系结构无关方式描述内存。本章介绍影响 VM 行为的内存簇、页面和标志位结构。

  1. 非一致内存访问(NUMA):在 VM 中,大型机器内存分簇,依簇与处理器距离,访问代价不同。可将内存簇指派给处理器或设备。每个簇是一个节点,Linux 中 struct pg_data_t 体现此概念,用于一致内存访问(UMA)体系结构,系统节点链接成 pgdat_list 链表,节点通过 pg_data_tnode_next 字段连接。像 PC 采用 UMA 结构的机器,使用 contig_page_data 的静态 pg_data_t 结构。
  2. 管理区(zone)
    • 内存中节点分成管理区,由 struct zone_struct 描述,类型为 zone_t ,包括 ZONE_DMAZONE_NORMALZONE_HIGHMEM 。不同管理区适用于不同用途。
    • ZONE_DMA 指低端物理内存,供某些 ISA 设备使用,x86 机器中存放前 16MB 内存。
    • ZONE_NORMAL 是内核直接映射到线性地址空间的较高部分,x86 机器范围是 16MB - 896MB ,许多内核操作依赖此区,对系统性能影响重大。
    • ZONE_HIGHMEM 是系统预留的可用内存空间,不被内核直接映射,x86 机器从 896MB 到末尾 。
  1. 页面帧:系统内存划分成页面帧,由 struct page 描述,结构存储在全局 mem_map 数组,通常在 ZONE_NORMAL 首部或小内存系统预留区域。后续章节将讨论其细节及内存结构间基本关系。

2.2 管理区

在Linux系统中,每个管理区由struct zone_t描述,zone_structs用于跟踪页面使用情况统计数、空闲区域信息和锁信息等 ,在<linux/mmzone.h>中声明如下:

typedef struct zone_struct {spinlock_t lock;unsigned long free_pages;unsigned long pages_min, pages_low, pages_high;int need_balance;free_area_t free_area[MAX_ORDER];wait_queue_head_t * wait_table;unsigned long wait_table_size;unsigned long wait_table_shift;struct pglist_data *zone_pgdat;struct page *zone_mem_map;unsigned long zone_start_paddr;unsigned long zone_start_mapnr;char *name;unsigned long size;
} zone_t;
  • lock:并行访问时保护该管理区的自旋锁。
  • free_pages:管理区中空闲页面的总数。
  • pages_min, pages_low, pages_high:管理区极值,用于跟踪管理区压力情况 。
  • need_balance:标志位,通知页面换出kswapd平衡管理区,当可用页面数量达到管理区极值某值时,需平衡管理区。
  • free_area:空闲区域位图,由伙伴分配器使用。
  • wait_table:等待队列的哈希表,由等待页面释放的进程组成,对wait_on_page()unlock_page()重要 。
  • wait_table_size:哈希表大小,是2的幂。
  • wait_table_shift:定义为一个long型对应的位数减去该表大小的二进制对数。
  • zone_pgdat:指向父pg_data_t
  • zone_mem_map:涉及的管理区在全局mem_map中的第一页。
  • zone_start_paddr:同node_start_paddr
  • zone_start_mapnr:同node_start_mapnr
  • name:管理区字符串名字,如“DMA”“Normal”或“HighMem” 。
  • size:管理区大小,以页面数计算。

以下是关于Linux内核mmzone.h头文件的内存管理架构解析(整理为中文):

一、Linux内存管理与mmzone.h概述

Linux内核将物理内存划分为节点(Node,适用于NUMA系统)区域(Zone,基于内存类型)

mmzone.h定义了管理这些内存区域的核心数据结构和常量,尤其是分区伙伴分配器(zoned buddy allocator)——内核用于分配物理内存页的主要机制。

核心架构概念:
  1. 内存节点(Memory Nodes)
    在NUMA系统中,物理内存按节点划分,每个节点与一个或一组处理器关联,通过pg_data_t结构管理。
  2. 内存区域(Memory Zones)
    每个节点内的内存进一步按用途划分为区域(如DMA、Normal、Highmem),用于处理硬件限制(如DMA设备的地址范围限制)或性能特性。
  3. 伙伴分配器(Buddy Allocator)
    内核通过伙伴系统管理每个区域的空闲页,将页按2的幂次块(Order)分组,减少内存碎片。
  4. NUMA感知(NUMA Awareness)
    内核优化内存分配,优先使用本地节点内存以提升性能。
  5. 页面回收(Page Reclamation)
    区域跟踪活跃(Active)和非活跃(Inactive)页,用于内存不足时的回收(如交换或释放页)。

mmzone.h是这些机制的核心,定义了内存的组织、分配和回收逻辑。

二、mmzone.h中的关键数据结构

a. struct free_area

struct free_area {struct list_head free_list;  // 同大小空闲块链表(按Order分组)unsigned long nr_free;       // 该大小的空闲块数量
};
  • 作用:表示伙伴分配器中特定大小(Order)的空闲内存块列表。
  • 架构角色:每个区域维护一个free_area数组(每个Order对应一项),伙伴系统通过分裂/合并块来管理内存,减少外部碎片。
  • 面试要点:解释伙伴系统如何通过分裂(如将4页块拆分为2个2页块)和合并块满足分配请求。

b. struct per_cpu_pages

struct per_cpu_pages {int count;       // 缓存中的页数int low, high;   // 触发缓存填充/清空的水位线int batch;       // 单次操作添加/移除的页数struct list_head list; // 页链表
};
  • 作用:管理 per-CPU 页缓存,减少分配时对全局区域锁的竞争。
  • 架构角色:CPU可直接从本地缓存分配/释放页,避免锁竞争。热缓存(Hot)和冷缓存(Cold)区分近期使用页,优化缓存局部性。
  • 面试要点:讨论per-CPU缓存如何提升SMP系统性能,以及缓存空间与锁竞争的权衡。

c. struct per_cpu_pageset

struct per_cpu_pageset {struct per_cpu_pages pcp[2];  // 0: 热页,1: 冷页
#ifdef CONFIG_NUMAunsigned long numa_hit;       // 在目标节点分配成功数unsigned long numa_miss;      // 在非目标节点分配数// 其他NUMA统计字段
#endif
} ____cacheline_aligned_in_smp;
  • 作用:扩展per-CPU页缓存,增加NUMA统计和热/冷页分离。
  • 架构角色:通过NUMA统计(如numa_hit)优化节点分配策略,缓存行对齐(____cacheline_aligned_in_smp)减少SMP系统中的伪共享(False Sharing)。
  • 面试要点:解释NUMA对内存分配的影响,内核如何优先本地节点分配以降低延迟。

d. struct zone

struct zone {unsigned long free_pages;       // 区域空闲页数struct free_area free_area[];   // 伙伴分配器的空闲块数组spinlock_t lock;                // 保护伙伴分配器的自旋锁struct per_cpu_pageset pageset[]; // per-CPU页缓存struct list_head active_list, inactive_list; // LRU链表// 其他字段(回收统计、水位线、节点关联等)
} ____cacheline_maxaligned_in_smp;
  • 作用:表示节点内的一个内存区域(如DMA、Normal、Highmem)。
  • 核心功能
    • 分配:通过free_arealock管理伙伴系统。
    • 回收:通过LRU链表(active_list/inactive_list)和kswapd线程实现页面回收。
    • 元数据zone_start_pfn(区域起始页帧号)、lowmem_reserve(保留低内存防止耗尽)。
  • 架构角色:区域是节点内存管理的核心,平衡分配效率与硬件约束(如DMA的地址限制)。
  • 面试要点:说明区域如何处理硬件限制(如Highmem在32位系统的作用),以及lowmem_reserve如何保障关键区域内存。

e. struct zonelist

struct zonelist {struct zone *zones[MAX_NUMNODES * MAX_NR_ZONES + 1]; // 按优先级排序的区域列表(NULL结尾)
};
  • 作用:定义内存分配时的区域优先级列表(如优先本地节点,再 fallback 其他节点)。
  • 架构角色:结合GFP标志(如__GFP_DMA)实现NUMA感知分配和故障转移策略。
  • 面试要点:解释zonelist如何支持NUMA分配,以及不同分配标志(如DMA/Highmem)如何影响区域选择。

f. struct pglist_data (pg_data_t)

typedef struct pglist_data {struct zone node_zones[];       // 节点内的所有区域(如DMA、Normal)struct zonelist node_zonelists[]; // 分配时的区域优先级列表struct page *node_mem_map;      // 节点页结构数组struct task_struct *kswapd;     // 节点的页面回收线程// 其他字段(节点ID、内存范围、启动内存数据等)
} pg_data_t;
  • 作用:表示NUMA系统中的一个内存节点,封装节点内的所有内存管理逻辑。
  • 架构角色:链接区域(node_zones)和分配策略(node_zonelists),通过kswapd线程实现节点级内存回收。
  • 面试要点:说明pg_data_t如何支持NUMA,以及节点链表(pgdat_next)如何遍历所有节点。

三、关键常量与宏

  1. MAX_ORDER
    • 定义伙伴分配器的最大块大小(默认11,即2^11页,约8MB/4KB页),可通过CONFIG_FORCE_MAX_ZONEORDER配置。
    • 面试要点:讨论增大MAX_ORDER的权衡(支持大分配 vs. 增加碎片风险)。
  1. 区域类型(ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM)
    • DMA:适用于ISA设备(<16MB,x86系统)。
    • Normal:内核直接映射的内存(16MB~896MB,x86 32位)。
    • Highmem:内核无法永久映射的高端内存(>896MB,仅32位系统需要)。
    • 面试要点:解释Highmem存在的原因(32位内核地址空间限制)及管理方式(动态映射如kmap)。
  1. GFP_ZONEMASKGFP_ZONETYPES
    • GFP_ZONEMASK:分配标志中指定目标区域的位掩码(如0x03)。
    • GFP_ZONETYPES:计算所需zonelist数量,优化内存使用。
    • 面试要点:说明GFP标志如何影响区域选择(如__GFP_HIGHMEM允许从Highmem分配)。

四、架构设计原则

  1. 模块化与抽象
    • 通过zonepg_data_t抽象节点和区域,支持跨架构(NUMA/UMA、32/64位)移植。
    • zonelist解耦分配逻辑与硬件细节。
  1. 性能优化
    • Per-CPU缓存:减少锁竞争,提升SMP系统吞吐量。
    • 缓存行对齐:通过____cacheline_aligned_in_smp避免伪共享。
    • NUMA感知:优先本地节点分配,降低内存访问延迟。
  1. 可扩展性
    • 伙伴分配器的对数复杂度(与MAX_ORDER相关)支持大内存系统。
    • NUMA支持扩展至多节点架构(MAX_NODES_SHIFT)。
  1. 可靠性
    • lowmem_reserve保障关键区域(如DMA)内存,避免饥饿。
    • LRU链表和kswapd线程防止OOM(内存不足)。
  1. 灵活性
    • 通过宏(如CONFIG_DISCONTIGMEMCONFIG_NUMA)适配不同内存架构。
    • wait_table支持进程等待页可用,缓解分配竞争。

五、面试常见问题与解答

  1. 区域(Zone)在内存管理中的作用?
    • 答:区域按硬件约束划分内存(如DMA的地址限制、Highmem的内核映射限制),每个区域独立管理分配(伙伴系统)和回收(LRU链表),提升硬件兼容性和分配效率。
  1. 伙伴分配器如何工作?MAX_ORDER的意义?
    • 答:伙伴系统将空闲页按2的幂次块分组(Order 0~MAX_ORDER-1),分配时分裂大块,释放时合并相邻块以减少碎片。MAX_ORDER决定最大块大小,平衡大分配支持与碎片风险。
  1. Linux如何处理NUMA系统的内存分配?
    • 答:通过pg_data_t管理节点,zonelist优先本地节点区域分配,利用numa_hit/numa_miss统计优化策略,kswapd线程按节点独立回收内存,降低跨节点访问延迟。
  1. lowmem_reserve的作用?
    • 答:在低内存区域(如DMA、Normal)预留内存,防止高内存区域(如Highmem)的分配耗尽关键区域内存,保障硬件设备(如DMA设备)的正常运行。
  1. 内核如何减少内存分配的锁竞争?
    • 答:使用per-CPU页缓存(per_cpu_pageset)避免全局锁,缓存行对齐减少伪共享,自旋锁(如locklru_lock)保护共享结构,缩小锁持有时长。

六、深入理解要点

  • 缓存行对齐____cacheline_maxaligned_in_smp确保关键结构(如自旋锁)独占缓存行,避免多CPU访问不同变量时的缓存一致性开销。
  • Highmem挑战:32位系统中,Highmem页需动态映射(如kmap),增加管理复杂度,而64位系统因地址空间充足可直接映射。
  • 回收策略:LRU链表通过prev_priority调整扫描优先级,内存压力大时加强回收,避免OOM。

七、面试准备建议

  1. 阅读关联代码:结合mm/page_alloc.c(分配逻辑)和mm/vmscan.c(回收逻辑)理解mmzone.h结构的实际应用。
  2. 掌握GFP标志:理解__GFP_DMA__GFP_HIGHMEM等标志如何与区域和zonelist交互。
  3. 图示辅助解释:用示意图描述节点-区域-zonelist的层级关系,或伙伴系统的块分裂过程。
  4. 硬件约束分析:明确DMA、Highmem等区域存在的硬件背景(如ISA设备限制、32位地址空间限制)。

八、总结

mmzone.h是Linux内存管理的核心,通过节点、区域、伙伴系统和NUMA感知设计,实现了跨架构的内存分配与回收。面试中需重点掌握:

  • 数据结构角色zonepg_data_tzonelist的职责。
  • 性能优化机制:per-CPU缓存、伙伴系统、NUMA感知分配。
  • 硬件与架构适配:区域如何处理DMA/Highmem等限制,32位与64位系统的差异。

思考:linux 内核如何感知硬件是否是 NUMA 架构?

思考:mmzone.h 中和伙伴系统相关的数据结构和函数是什么?

思考: per_cpu_pages 是什么意思?有什么用?为什么需要 per_cpu_pages?

以下是对你的三个问题的详细解答,结合 mmzone.h 头文件,从 Linux 内核内存管理的架构视角进行分析,旨在帮助你深入理解并为面试做好准备。答案将使用中文,并尽量清晰、简洁,同时涵盖技术细节。


问题 1:Linux 内核如何感知硬件是否是 NUMA 架构?

Linux 内核通过以下机制感知硬件是否是 NUMA(Non-Uniform Memory Access)架构:

  1. 硬件初始化阶段的探测
    • ACPI(高级配置与电源接口):现代系统通常通过 ACPI 表(如 SRAT 和 SLIT)获取 NUMA 拓扑信息。SRAT(System Resource Affinity Table)描述了内存和处理器的亲和性,列出每个 NUMA 节点的内存范围和 CPU 关联;SLIT(System Locality Information Table)提供节点间访问延迟信息。
    • BIOS 或固件:在系统启动时,BIOS 或 UEFI 固件会通过 ACPI 或其他机制向内核传递硬件拓扑信息。内核通过解析这些表来识别 NUMA 节点及其内存分布。
    • 设备树(Device Tree):在嵌入式系统或非 ACPI 系统(如某些 ARM 架构)上,设备树描述硬件拓扑,包括 NUMA 节点的内存和 CPU 分布。
  1. 内核初始化代码
    • 内核在启动过程中会调用与 NUMA 相关的初始化函数(如 mm/numa.c 中的 numa_init),根据 ACPI 或设备树信息构建 pglist_data 结构(对应每个 NUMA 节点)。
    • 如果系统检测到多个 NUMA 节点(MAX_NUMNODES > 1),内核会启用 NUMA 支持,初始化每个节点的 pg_data_t 结构,并填充 node_zonesnode_zonelists
    • 如果只有一个节点(UMA 系统),内核会退化为单一节点模式,pglist_data 只包含一个实例(如 contig_page_data)。
  1. 宏和配置选项
    • CONFIG_NUMA:内核编译时通过配置选项启用 NUMA 支持。如果未启用,内核假设 UMA(Uniform Memory Access)架构。
    • numa_node_id():返回当前 CPU 所属的 NUMA 节点 ID,基于硬件探测结果(如 cpu_to_node 映射)。
    • MAX_NUMNODESMAX_NODES_SHIFT:定义支持的最大节点数,限制 NUMA 系统的规模(32 位系统最多 64 节点,64 位系统最多 1024 节点)。
  1. 运行时验证
    • 内核通过 node_present_pages(nid)node_spanned_pages(nid) 检查每个节点的内存是否存在和大小,确认 NUMA 拓扑。
    • 运行时统计(如 numa_hitnuma_miss)帮助内核监控 NUMA 分配效率,进一步验证拓扑的有效性。

总结
Linux 内核通过 ACPI 表、设备树或固件信息在启动时探测 NUMA 拓扑,初始化 pglist_data 结构来管理每个节点。如果只有一个节点或未启用 CONFIG_NUMA,内核按 UMA 模式运行。面试时,可以强调 ACPI 的 SRAT/SLIT 表和 numa_init 函数的角色,以及 NUMA 对性能优化的重要性。


🌍 思考:mmzone.h 中和伙伴系统相关的数据结构和函数是什么?

伙伴系统(Buddy Allocator)是 Linux 内核用于管理物理内存页的主要分配器,mmzone.h 定义了与其相关的核心数据结构和少量函数。

相关数据结构

struct free_area

struct free_area {struct list_head free_list;unsigned long nr_free;
};
  • 作用:表示特定大小(order)的空闲页面块列表,用于伙伴系统的内存管理。
  • 字段
    • free_list:一个双向链表,存储同一 order(2^order 页)的空闲页面块。
    • nr_free:记录该 order 下空闲块的数量。
  • 在伙伴系统中的角色
    • 每个 zone 包含一个 free_area 数组(free_area[MAX_ORDER]),从 order 0(1 页)到 order MAX_ORDER-1(默认 2^10 页,即 4MB)。
    • 伙伴系统通过 free_area 管理空闲页面,分配时从合适 order 的 free_list 获取,释放时合并相邻块(伙伴)到更高 order。

struct zone(部分字段)

struct zone {unsigned long free_pages;spinlock_t lock;struct free_area free_area[MAX_ORDER];...
};
  • 相关字段
    • free_pages:跟踪区域内总的空闲页面数。
    • lock:自旋锁,保护 free_areafree_pages 的并发访问。
    • free_area:伙伴系统的核心,存储不同 order 的空闲页面列表。
  • 在伙伴系统中的角色
    • zone 是伙伴系统的工作单元,每个区域独立管理自己的空闲页面。
    • 分配请求通过 zonefree_area 查找合适大小的块,必要时分裂大块。
    • lock 确保多核系统下的线程安全。

struct per_cpu_pages(间接相关)

struct per_cpu_pages {int count;int low;int high;int batch;struct list_head list;
};
  • 作用:虽然主要是 per-CPU 缓存,伙伴系统通过它快速分配/释放页面。
  • 在伙伴系统中的角色
    • 当 per-CPU 缓存不足(count < low),从 free_area 批量获取页面(batch 控制批量大小)。
    • 当缓存过满(count > high),将页面批量归还到 free_area。(这段代码在哪里?)

相关函数

mmzone.h 本身主要是数据结构定义,伙伴系统的核心逻辑在 mm/page_alloc.c 中实现,但 mmzone.h 中定义了以下与伙伴系统相关的函数:

  1. zone_watermark_ok
int zone_watermark_ok(struct zone *z, int order, unsigned long mark,int alloc_type, int can_try_harder, int gfp_high);
    • 作用:检查区域是否满足分配请求的内存水位要求。
    • 参数
      • z:目标区域。
      • order:请求的页面块大小(2^order 页)。
      • mark:水位标记(如 pages_lowpages_high)。
      • alloc_type:分配类型(如 GFP 标志)。
      • can_try_harder:是否允许更激进的分配尝试。
      • gfp_high:是否为高优先级分配。
    • 在伙伴系统中的角色
      • 在分配页面之前,检查区域是否有足够的空闲页面(free_pages)和满足水位条件,防止过度分配导致内存耗尽。
      • 如果水位不足,可能触发 kswapd 回收页面。
  1. wakeup_kswapd
void wakeup_kswapd(struct zone *zone, int order);
    • 作用:唤醒 kswapd 线程以回收页面,增加区域的空闲页面。
    • 在伙伴系统中的角色
      • 当伙伴系统无法满足分配请求(例如,free_area[order] 为空),触发 kswapd 回收页面,补充 free_area 的空闲块。

数据结构free_area 是伙伴系统的核心,管理空闲页面块;zone 提供分配环境;per_cpu_pages 优化分配性能。

函数zone_watermark_ok 确保分配安全,wakeup_kswapd 支持内存回收以补充空闲页面。

面试建议:准备解释伙伴系统的分裂与合并机制,如何通过 free_area 查找页面,以及水位检查如何防止内存耗尽。

🌍 思考:per_cpu_pages 是什么意思?有什么用?为什么需要 per_cpu_pages?

per_cpu_pages 是什么?

per_cpu_pages 是一个数据结构,定义在 mmzone.h 中,用于实现每个 CPU 的页面缓存:

struct per_cpu_pages {int count;      /* 缓存中的页面数量 */int low;        /* 低水位,触发补充 */int high;       /* 高水位,触发归还 */int batch;      /* 批量补充/归还的页面数量 */struct list_head list; /* 缓存的页面链表 */
};

结构说明

  • 每个 CPU 核有一个 per_cpu_pages 实例,嵌入在 per_cpu_pageset 中(pcp[0] 为热页面,pcp[1] 为冷页面)。
  • list 存储缓存的页面,count 跟踪数量。
  • lowhigh 是水位标记,控制缓存的填充和清空。
  • batch 决定从伙伴系统批量获取或归还的页面数。

per_cpu_pages 的主要作用是:

提高分配效率

  • 通过为每个 CPU 维护一个本地页面缓存,内核可以在不获取全局 zone->lock 的情况下快速分配/释放页面。
  • 热页面(pcp[0])优先分配以优化缓存局部性(cache locality),提高性能。

减少锁竞争

  • 在多核(SMP)系统中,多个 CPU 同时访问 zone->lock 会导致严重的锁竞争,降低分配性能。
  • per_cpu_pages 允许 CPU 在本地缓存中操作,减少对全局锁的依赖。

NUMA 优化

  • 在 NUMA 系统下,per_cpu_pagesper_cpu_pageset 的 NUMA 统计字段(如 numa_hit)结合,优先从本地节点分配页面,降低跨节点访问的延迟。

批量操作

  • 通过 batch 参数,内核可以批量从伙伴系统获取或归还页面,减少频繁的锁操作和伙伴系统交互。

为什么需要 per_cpu_pages?

多核系统中的锁竞争

    • 在 SMP 系统中,多个 CPU 可能同时请求页面,频繁获取 zone->lock 会导致性能瓶颈。
    • per_cpu_pages 提供本地缓存,CPU 大多数时候可以直接从缓存分配页面,避免锁竞争。

性能优化

    • 内存分配是内核的热点路径(hot path),尤其在高负载场景(如服务器或虚拟化)。
    • 本地缓存减少了访问全局数据结构的开销,热页面优先分配利用 CPU 缓存,提高效率。

NUMA 局部性

    • NUMA 系统要求尽量从本地节点分配内存以降低延迟。per_cpu_pages 配合 per_cpu_pageset 的 NUMA 统计,确保分配优先考虑本地页面。

动态内存管理

    • 水位机制(lowhigh)动态调整缓存大小,避免缓存过多占用内存或过少导致频繁访问伙伴系统。
    • 批量操作(batch)平衡了分配效率和内存使用率。

分配流程

当 CPU 需要分配页面时,先检查 per_cpu_pageslist。如果缓存中有页面(count > 0),直接从 list 分配。

如果缓存不足(count < low),从 zone->free_area 批量获取 batch 个页面,需获取 zone->lock

释放流程

    • 释放页面时,优先放入 per_cpu_pageslist
    • 如果缓存过满(count > high),批量归还页面到 zone->free_area,可能触发伙伴合并。

NUMA 支持

  • per_cpu_pageset 中的 NUMA 统计字段(如 numa_hitnuma_miss)跟踪分配是否来自目标节点,帮助优化 NUMA 策略。

🌍 思考:struct page 和 Zone 是如何联系到一起的?

mm/page_alloc.c 文件

这段代码是 Linux 内核内存管理子系统中与页面分配和回收相关的核心实现,位于 mm/page_alloc.c 文件中,结合 mmzone.h 头文件,实现了 zoned buddy allocator(分区伙伴分配器),这是 Linux 内核管理物理内存页的主要机制。以下从顶层设计、设计思想、页面分配与回收的流程以及相关函数的角度,详细解答你的问题,力求清晰、全面,并帮助你为面试准备。

1. 顶层设计

Linux 内核的内存管理基于 节点(Node)分区(Zone) 的分层架构,使用 伙伴系统(Buddy Allocator) 进行页面分配和回收,结合 per-CPU 页面缓存NUMA 优化 来提升性能。以下是顶层设计的要点:

  1. 内存分层
    • 节点(Node):在 NUMA 架构中,物理内存被划分为节点(struct pglist_data),每个节点对应一个 CPU 或 CPU 组及其本地内存。
    • 分区(Zone):每个节点内进一步划分为分区(如 ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM),以适应不同的硬件约束(如 DMA 的 <16MB 限制)。
    • 页面(Page):内存的最小分配单位是页面(通常 4KB),通过 struct page 表示。
  1. 伙伴系统
    • 伙伴系统是核心分配机制,将内存组织为 2order 大小的块(order 从 0 到 MAX_ORDER-1,默认最大 210 页,即 4MB)。
    • 每个分区(struct zone)维护一个 free_area 数组,存储不同 order 的空闲页面块链表。
  1. Per-CPU 缓存
    • 每个 CPU 维护一个 per_cpu_pageset 结构,包含热(hot)和冷(cold)页面缓存,用于快速分配和释放页面,减少对全局 zone->lock 的竞争。
  1. NUMA 优化
    • 使用 zonelist 优先从本地节点分配内存,fallback 到其他节点,优化 NUMA 系统中的内存访问延迟。
    • 统计字段(如 numa_hitnuma_miss)跟踪分配效率。
  1. 页面回收
    • 当内存不足时,kswapd 线程或直接回收机制(try_to_free_pages)通过 LRU(Least Recently Used)列表回收页面,补充空闲页面。
    • 水位线(pages_minpages_lowpages_high)控制分配和回收的触发。
  1. 初始化
    • 内核启动时通过 free_area_initbuild_all_zonelists 初始化节点、分区和 zonelist,确保内存管理子系统就绪。

顶层设计图示

[Node 0 (pg_data_t)]├── Zone DMA: free_area[0..MAX_ORDER-1], pageset[CPU0..CPUn], LRU lists├── Zone Normal: free_area[0..MAX_ORDER-1], pageset[CPU0..CPUn], LRU lists├── Zone Highmem: free_area[0..MAX_ORDER-1], pageset[CPU0..CPUn], LRU lists└── Zonelist: [Zone Normal (Node 0), Zone DMA (Node 0), Zone Normal (Node 1), ...]
[Node 1 (pg_data_t)]└── ...

2. 设计思想

Linux 内核页面分配与回收的设计思想围绕 性能可扩展性可靠性灵活性 展开:

  1. 性能优化
    • Per-CPU 缓存:通过 per_cpu_pages 减少锁竞争,快速分配/释放页面。
    • NUMA 局部性:优先分配本地节点内存,降低跨节点访问延迟。
    • Cacheline 对齐zoneper_cpu_pageset 使用 cacheline 对齐,减少 SMP 系统中的伪共享。
    • 批量操作:批量分配/释放页面(如 rmqueue_bulkfree_pages_bulk)减少锁操作。
  1. 可扩展性
    • 分层架构:节点和分区设计支持 NUMA 和 UMA 系统,适应不同硬件规模。
    • 伙伴系统:通过 power-of-2 块管理内存,适合大内存系统,复杂度为 O(log n)。
    • 模块化宏:通过 CONFIG_NUMACONFIG_DISCONTIGMEM 等宏支持不同架构。
  1. 可靠性
    • 水位线管理pages_minpages_lowpages_high 确保内存分配不会耗尽关键区域(如 ZONE_DMA)。
    • 低内存保护lowmem_reserve 防止低端内存被高阶分配耗尽。
    • 错误检查bad_pagebad_range 检测页面状态异常,增强健壮性。
  1. 灵活性
    • GFP 标志:通过 gfp_mask(如 __GFP_DMA__GFP_HIGHMEM)支持不同分配需求。
    • 可配置参数sysctl_lowmem_reserve_ratiomin_free_kbytes 允许动态调整内存策略。
    • 支持大页面:通过 CONFIG_HUGETLB_PAGE 支持复合页面(compound pages)。
  1. 内存回收
    • 使用 LRU 列表(active_listinactive_list)和 kswapd 线程动态回收页面,平衡内存压力。
    • 优先级机制(prev_prioritytemp_priority)根据内存压力调整回收强度。

3. 页面分配的流程和函数

页面分配的核心函数是 __alloc_pages,它协调伙伴系统、per-CPU 缓存和 zonelist 来满足分配请求。以下是流程和关键函数:

分配流程

  1. 入口__alloc_pages(gfp_mask, order, zonelist)
    • 根据 gfp_mask(分配标志,如 __GFP_DMA)和 order(请求的页面块大小,2^order 页)选择合适的 zonelist。
    • 遍历 zonelist 中的分区,检查水位线(zone_watermark_ok)以确保有足够空闲页面。
  1. 尝试分配
    • 调用 buffered_rmqueue(zone, order, gfp_mask) 从目标分区分配页面:
      • order == 0:优先从 per-CPU 缓存(per_cpu_pages)分配单页。
      • order > 0:直接从 zone->free_area 的伙伴系统分配。
    • 如果 per-CPU 缓存不足,调用 rmqueue_bulk 从伙伴系统补充页面。
  1. 水位检查
    • zone_watermark_ok 检查分区空闲页面是否满足 pages_lowpages_min(根据 gfp_maskcan_try_harder 调整)。
    • 如果水位不足,唤醒 kswapdwakeup_kswapd)回收页面。
  1. 回收和重试
    • 如果分配失败,调用 try_to_free_pages 进行同步回收,释放页面到 free_area
    • 如果仍失败,可能触发 OOM(Out of Memory)杀进程(out_of_memory)或返回 NULL。
  1. 页面准备
    • 分配成功后,调用 prep_new_page 初始化页面(清除标志、设置引用计数)。
    • 如果需要(__GFP_ZERO__GFP_COMP),调用 prep_zero_page 清零页面或 prep_compound_page 设置复合页面。

关键分配函数

  • __alloc_pages(gfp_mask, order, zonelist)
    • 核心分配函数,遍历 zonelist,调用 buffered_rmqueue 分配页面,处理水位检查、回收和 NUMA 统计。
  • buffered_rmqueue(zone, order, gfp_mask)
    • 从 per-CPU 缓存(order == 0)或伙伴系统(order > 0)分配页面,处理页面初始化。
  • __rmqueue(zone, order)
    • 从伙伴系统的 free_area 分配页面,必要时分裂大块(通过 expand)。
  • rmqueue_bulk(zone, order, count, list)
    • 批量分配页面到指定链表,用于补充 per-CPU 缓存。
  • prep_new_page(page, order)
    • 初始化新分配的页面,清除标志,设置引用计数。
  • prep_compound_page(page, order)
    • 为大页面(compound pages)设置标志和元数据(如 PG_compound)。
  • zone_watermark_ok(z, order, mark, classzone_idx, can_try_harder, gfp_high)
    • 检查分区是否满足分配的水位要求,调整空闲页面计算。

4. 页面回收的流程和函数

页面回收通过释放页面回伙伴系统或 per-CPU 缓存完成,涉及以下流程和函数:

回收流程

  1. 入口__free_pages(page, order)
    • 检查页面是否可释放(put_page_testzero),如果是 order == 0,调用 free_hot_page;否则调用 __free_pages_ok.
  1. 释放到 per-CPU 缓存
    • 对于单页(order == 0),free_hot_cold_page(page, cold) 将页面加入 per-CPU 缓存(per_cpu_pages 的 hot 或 cold 链表)。
    • 如果缓存超过 high 水位,调用 free_pages_bulk 批量归还页面到伙伴系统。
  1. 释放到伙伴系统
    • __free_pages_bulk(page, zone, order) 将页面归还到 zone->free_area[order].free_list,尝试与伙伴合并到更高 order。
    • 如果伙伴也是空闲的,递归合并(通过 __page_find_buddy__find_combined_index)。
  1. 状态检查
    • free_pages_check 检查页面状态(映射、引用计数、标志等),防止释放非法页面。
  1. 特殊场景
    • 在电源管理(CONFIG_PM)或 CPU 热插拔(CONFIG_HOTPLUG_CPU)时,drain_local_pages__drain_pages 清空 per-CPU 缓存,归还页面到伙伴系统。

关键回收函数

  • __free_pages(page, order)
    • 页面释放的入口,决定调用 free_hot_page__free_pages_ok
  • free_hot_cold_page(page, cold)
    • 将单页释放到 per-CPU 缓存,区分热/冷页面。
  • __free_pages_ok(page, order)
    • 处理多页释放,检查页面状态后调用 free_pages_bulk
  • free_pages_bulk(zone, count, list, order)
    • 批量释放页面到伙伴系统,更新 free_areafree_pages
  • __free_pages_bulk(page, zone, order)
    • 单页面块的释放逻辑,执行伙伴合并,更新 free_area
  • page_is_buddy(page, order)
    • 检查页面是否是空闲伙伴,可用于合并。
  • __page_find_buddy(page, page_idx, order)
    • 查找页面的伙伴页面,用于合并。
  • destroy_compound_page(page, order)
    • 释放复合页面,清除 PG_compound 标志。

5. 关键函数总结

以下是页面分配与回收的核心函数及其作用:

函数

作用

场景

__alloc_pages

核心分配函数,协调 zonelist 和水位检查

分配任意 order 的页面

buffered_rmqueue

从 per-CPU 缓存或伙伴系统分配页面

单页或多页分配

__rmqueue

从伙伴系统分配页面,执行分裂

多页分配

rmqueue_bulk

批量分配页面到链表

补充 per-CPU 缓存

prep_new_page

初始化新分配页面

分配后准备

prep_compound_page

设置复合页面元数据

大页面分配

zone_watermark_ok

检查分区水位是否满足分配

分配前检查

__free_pages

页面释放入口

释放任意 order 页面

free_hot_cold_page

释放单页到 per-CPU 缓存

单页释放

__free_pages_ok

释放多页到伙伴系统

多页释放

free_pages_bulk

批量释放页面到伙伴系统

批量回收

__free_pages_bulk

单页面块释放,执行伙伴合并

伙伴系统回收

drain_local_pages

清空 per-CPU 缓存

电源管理/CPU 热插拔

6. 页面分配与回收的详细流程

分配流程示例

假设请求分配 2 页(order=1),GFP 标志为 GFP_KERNEL

  1. 调用 __alloc_pages(GFP_KERNEL, 1, zonelist)
  2. 遍历 zonelist->zones,检查每个分区(优先 ZONE_NORMAL)。
  3. zone_watermark_ok 确认分区有足够空闲页面(free_pages > pages_low)。
  4. 调用 buffered_rmqueue(zone, 1, GFP_KERNEL)
    • 因 order > 0,直接调用 __rmqueue(zone, 1)
    • __rmqueuefree_area[1] 获取 2 页块,若为空,从更高 order 分裂(expand)。
  1. 初始化页面(prep_new_page),设置复合页面(prep_compound_page 如果需要)。
  2. 更新 NUMA 统计(zone_statistics),返回页面。

回收流程示例

释放 2 页(order=1):

调用 __free_pages(page, 1),检查引用计数(put_page_testzero)。

调用 __free_pages_ok(page, 1)

    • free_pages_check 验证页面状态。
    • 调用 free_pages_bulk(zone, 1, list, 1)

free_pages_bulk 调用 __free_pages_bulk(page, zone, 1)

    • 将页面加入 free_area[1].free_list
    • 检查伙伴页面(__page_find_buddy),若空闲,合并到 free_area[2],递归直到无法合并。

更新 zone->free_pagesfree_area[order].nr_free

7. 面试准备建议

  1. 核心概念
    • 理解伙伴系统的分裂与合并(expand__free_pages_bulk)。
    • 掌握 per-CPU 缓存的作用(buffered_rmqueuefree_hot_cold_page)。
    • 熟悉 NUMA 优化的 zonelist 和统计机制(zone_statistics)。
  1. 常见问题
    • 如何处理内存不足? 答:通过 wakeup_kswapdtry_to_free_pages 回收页面,必要时触发 OOM。
    • GFP 标志的作用? 答:控制分配的分区(__GFP_DMA__GFP_HIGHMEM)和行为(__GFP_ZERO__GFP_NOFAIL)。
    • 伙伴系统如何减少碎片? 答:通过 power-of-2 块和合并机制,减少外部碎片。
  1. 代码分析
    • 熟悉 __alloc_pages__free_pages 的逻辑,结合 mmzone.h 的数据结构(如 free_areaper_cpu_pages)。
    • 理解水位线(zone_watermark_ok)和低内存保护(lowmem_reserve)的实现。
  1. 实践建议
    • 阅读 mm/page_alloc.cmm/vmscan.c 的相关代码。
    • 用图示解释分配流程(如 zonelist -> zone -> free_area)。
    • 准备讨论 NUMA 和 SMP 场景下的优化。

总结

mm/page_alloc.c 实现了 Linux 内核的页面分配与回收,基于伙伴系统、per-CPU 缓存和 NUMA 优化的分层架构。核心函数包括 __alloc_pages(分配)、__free_pages(回收)、buffered_rmqueue(缓存分配)、free_hot_cold_page(缓存回收)等。设计思想强调性能(缓存、NUMA 局部性)、可扩展性(节点/分区)、可靠性(水位线、错误检查)和灵活性(GFP 标志)。通过理解这些机制和函数,你可以深入掌握 Linux 内存管理的架构,并在面试中自信应对相关问题。

2.2.1 管理区极值

系统可用内存少时,守护程序kswapd被唤醒释放页面。内存压力大时,进程会同步释放内存(direct - reclaim路径)。影响页面换出行为的参数与FreeBSD、Solaris、MM01中所用参数类似。
每个管理区有三个极值pages_lowpages_minpages_high

  • pages_low:空闲页面数达到该值时,伙伴分配器唤醒kswapd释放页面,默认值是pages_min的两倍 。
  • pages_min:达到该值时,分配器同步启动kswapd ,在Solaris中无等效参数。
  • pages_high:kswapd被唤醒并开始释放页面后,在释放pages_high个页面以前,不认为管理区“平衡”,达到该极值后,kswapd再次睡眠,默认值是pages_min的三倍 。 这些极值用于决定页面换出守护程序或页面换出进程释放页面的频繁程度。

2.2.2 计算管理区大小

  1. 计算位置与流程
    管理区大小在setup_memory()函数中计算。图2.3展示了setup_memory的调用关系,其调用链包含find_max_pfnfind_max_low_pfninit_bootmemregister_bootmem_low_pagesfind_smp_config等多个函数,各函数在内存初始化等环节发挥作用。
  2. 关键变量及含义
    • PFN(物理页帧号):用于物理内存映射,以页面计算偏移量。系统中第一个可用的PFN(min_low_pfn)分配在被导入的内核映象末尾_end后的第一个页面位置,存储在mm/bootmem.c文件中,与引导内存分配器配合使用。
    • max_low_pfn:在x86情况下,通过find_max_pfn()函数计算出ZONE_NORMAL管理区的结束位置值。此管理区是内核可直接访问的物理内存,通过PAGE_OFFSET标记内核/用户空间划分,该值存储在mm/bootmem.c文件。
    • max_pfn:表示系统中最后一个页面帧的编号。在内存少的机器上,max_pfn值等于max_low_pfn
  1. 计算用途
    通过min_low_pfnmax_low_pfnmax_pfn这三个变量,可确定高端内存起始和结束位置(相关变量highstart_pfnhighend_pfn存储在arch/i286/mm/init.c中 ),这些值被物理页面分配器用于初始化高端内存页面。
     

2.2.3 管理区等待队列表

  1. 等待队列机制原理
    当页面进行I/O操作(如页面换入/换出 )时,为防止访问不一致数据,使用页面的进程需在I/O访问前通过wait_on_page()添加到等待队列,I/O完成后页面经UnlockPage()解锁,等待队列上进程被唤醒。理论上每个页面都应有等待队列,但这会耗费大量内存,Linux将等待队列存储在zone_t中。
  2. 解决惊群效应
    若管理区只有一个等待队列,页面解锁时所有等待进程都会被唤醒,产生惊群效应。Linux的解决办法是将等待队列的哈希表存储在zone_t->wait_table中,虽哈希冲突时仍可能有无故唤醒进程,但冲突频率降低。
  3. 哈希表相关计算
    • wait_table_size计算:在free_area_init_core()时初始化,通过wait_table_size()计算 ,存储在zone_t->wait_table_size。等待队列表最多4096个,其大小取NoPages/PAGE_PER_WAITQUEUE个队列数和2的幂次方的最小值,计算公式为:
      [ wait_table_size = \log_2\left(\frac{NoPages \times 2}{PAGE_PER_WAITQUEUE} - 1\right) ]
      其中NoPages是管理区页面数 ,PAGE_PER_WAITQUEUE定义为256。
    • zone_t->wait_table_shift计算:通过页面地址的比特数右移得到其在表中的索引 。
    • page_waitqueue()函数:用于返回某管理区中一个页面对应的等待队列,一般采用基于已被哈希的struct page虚拟地址的乘积哈希算法,通常需用GOLDEN_RATIO_PRIME乘以该地址,并将结果右移得到在哈希表中的索引,GOLDEN_RATIO_PRIME [Lev00]是系统中最接近所能表达的最大整数的golden ratio[Knu68]的最大素数。

2.3 管理区初始化

  1. 初始化时机
    管理区的初始化在内核页表通过paging_init()完全建立起来后进行 ,页表初始化在3.6节涉及。不同系统执行此任务方式有别,但目标一致,即向UMA结构中的free_area_init()或NUMA结构中的free_area_init_node()传递合适参数。
  2. 所需参数
    • nid:被初始化管理区中节点的逻辑标识符(NodeID )。
    • pgdat:指向pg_data_t ,在UMA结构里为contig_page_data
    • pmap:被free_area_init_core()函数用于设置指向分配给节点的局部lmem_map数组的指针 。在UMA结构中常被忽略,因NUMA将mem_map处理为起始于PAGE_OFFSET的虚拟数组,而UMA中该指针指向全局mem_map变量,目前相关mem_map都在UMA中被初始化。
    • zones_sizes:包含每个管理区大小的数组,管理区大小以页面为单位计算。
    • zone_start_paddr:每个管理区的起始物理地址。
    • zone_holes:包含管理区中所有空闲洞大小的数组。
  1. 核心函数作用
    核心函数free_area_init_core()用于向每个zone_t填充相关信息,并为节点分配mem_map数组 。此时不考虑释放管理区中哪些页面的信息,该信息在引导内存分配器用完之后才可知,将在第5章讨论。

2.4 初始化mem_map

  1. 初始化方式
    • NUMA系统:全局mem_map被视为起始于PAGE_OFFSET的虚拟数组。free_area_init_node()函数由每个活动节点调用,在节点初始化时分配数组的一部分 。
    • UMA系统free_area_init()使用contig_page_data作为节点,将全局mem_map当作该节点的局部mem_map
  1. 核心函数及内存分配
    核心函数free_area_init_core()为已初始化的节点分配局部lmem_map ,其内存通过引导内存分配器中的alloc_bootmem_node()分配(第5章介绍 )。在UMA结构中,新分配内存成为全局mem_map,与NUMA结构存在差异。
  2. NUMA与UMA的差异
    • NUMA:分配给lmem_map的内存位于各自内存节点。全局mem_map未明确分配,被处理成虚拟数组。局部映射地址存储在pg_data_t->node_mem_map和虚拟mem_map中。对节点内每个管理区,虚拟mem_map中管理区地址存储在zone_t->zone_mem_map ,其余节点将mem_map视为真实数组,仅有效管理区被节点使用。

2.5 页面

在Linux系统中,每个物理页面都由struct page关联记录状态,在内核2.2版本中,该结构在<linux/mm.h>中声明如下:

typedef struct page {struct list_head lru;struct list_head list;struct address_space *mapping;unsigned long index;struct page *next_hash;atomic_t count;unsigned long flags;struct list_head lru;struct page *pprev_hash;struct buffer_head *buffers;# if defined(CONFIG_HIGHMEM) || defined(WANT_PAGE_VIRTUAL)void *virtual;# endif /* CONFIG_HIGHMEM || WANT_PAGE_VIRTUAL */mem_map_t;
}

各字段介绍:

  • list:页面可属于多个列表,此字段用作列表首部。在slab分配器中,存储指向管理页面的slab和高速缓存结构指针 ,用于链接空闲页面块。
  • mapping:若文件或设备映射到内存,其索引节点有相关address_space。若页面属于该文件,此字段指向该address_space;匿名页面且设置了mapping,则address_space是交换地址空间的swapper_space
  • index:有两个用途,若是文件映射部分,是页面在文件中的偏移;若是交换高速缓存部分,是在交换地址空间中swapper_space的偏移量。若包含页面的块被释放,释放块的顺序(被释放页面2的幂 )存于其中,在free_pages_ok()中设置。
  • next_hash:属于文件映射并散列到索引节点及偏移中的页面,将共享相同哈希桶的页面链接一起。
  • count:页面被引用数目,减到0时释放,被多个进程使用或内核用到时增大。
  • flags:描述页面状态标志位,在<linux/mm.h>声明,部分在表2.1列出 ,许多已定义宏用于测试、清空和设置,最有用的是SetPageUptodate(),若设置位前已定义,会调用体系结构相关函数arch_set_page_uptodate()
  • lru:根据页面替换策略,可能被交换出内存的页面存于page_alloc.c声明的active_listinactive_list中,是最近最少使用(LRU )链表的链表首部。
  • pprev_hash:是next_hash的补充,使哈希链表可双向链表工作。
  • buffers:若页面有相关块设备缓冲区,此字段跟踪buffer_head。匿名页面有后援交换文件时,进程映射的该匿名页面也有相关buffer_head,缓冲区必不可少,因页面须与后援存储器中的文件系统块同步。
  • virtual:通常情况下,只有来自ZONE_NORMAL的页面才由内核直接映射。为定位ZONE_HIGHMEM中的页面,kmap()用于为内核映射页面(第9章讨论 ),只有一定数量页面会被映射,页面被映射时,这是其虚拟地址。
  • mem_map_t:是对struct page的类型定义,在mem_map数组中方便使用。

表2.1 描述页面状态的标志位

位名

描述

PG_active

位于LRU active_list链表上的页面被设置,页面移除时清除,标记页面是否处于活动状态

PG_arch_1

直接从代码中引用,是体系结构相关的页面状态位,一般代码保证第一次进入页面高速缓存时清除,使体系结构可延迟到页面被进程映射后才进行D - Cache刷盘

PG_checked

仅由Ext2文件系统使用

PG_dirty

显示页面是否需刷新到磁盘上,磁盘上对应页面进行写操作后设置,保证脏页面写出前不会被释放

PG_error

在磁盘I/O发生错误时设置

PG_fs_1

被文件系统保留,目前只有NFS用它表示一个页面是否和远程服务器同步

PG_highmem

高端内存的页面标记,高端内存页面在mem_init()时通过该位来标记

PG_launder

只对页面替换策略重要,VM要换出页面时设置,并调用writepage()函数。在扫描过程中,若遇到设置了该位以及PG_locked的页面,需要等待I/O完成

PG_locked

页面在磁盘I/O所在内存时设置,I/O启动时设置,I/O完成时释放

PG_lru

当页面存在于active_listinactive_list其中之一时设置

PG_referenced

页面被映射且被映射的索引哈希表引用时设置,在LRU链表上移动页面做页面替换时使用

PG_reserved

禁止换出的页面,系统初始化时由引导内存分配器设置,标志页面或不存在

PG_slab

被slab分配器使用的页面

PG_skip

某些Space体系结构用于跳过部分地址空间,现在不再使用

PG_unused

表示没有被使用

PG_uptodate

当一个页面从磁盘无错误读出时设置

表2.2 用于测试、设置和清除page->flags的宏

位名

设置

测试

清除

PG_active

SetPageActive()

PageActive()

ClearPageActive()

PG_arch_1

None

None

None

PG_checked

SetPageChecked()

PageChecked()

None

PG_dirty

SetPageDirty()

PageDirty()

ClearPageDirty()

PG_error

SetPageError()

PageError()

ClearPageError()

PG_highmem

None

PageHighMem()

None

PG_launder

SetPageLaunder()

PageLaunder()

ClearPageLaunder()

PG_locked

LockPage()

PageLocked()

UnlockPage()

PG_lru

TestSetPageLRU()

PageLRU()

ClearPageLRU()

PG_referenced

SetPageReferenced()

PageReferenced()

ClearPageReferenced()

PG_reserved

SetPageReserved()

PageReserved()

ClearPageReserved()

PG_skip

None

None

None

PG_slab

PageSetSlab()

PageSlab()

PageClearSlab()

PG_unused

None

None

None

PG_uptodate

SetPageUptodate()

PageUptodate()

ClearPageUptodate()

2.6 页面映射到管理区

  1. 页面管理区映射方式演变
    在内核2.4.18版本,struct page曾存储指向对应管理区的指针page->zone ,但因大量struct page存在时该指针消耗内存多,在更新内核版本中被删除,改用page->flags的最高ZONE_SHIFT位(x86下是8位 )记录页面所属管理区。
  2. 管理区相关数据结构及函数
    • zone_table:在linux/page_alloc.c中声明,用于建立管理区 ,定义如下:
zone_t * zone_table[MAX_NR_ZONES * MAX_NR_NODES];
EXPORT_SYMBOL(zone_table);

其中MAX_NR_ZONES是一个节点中能容纳管理区的最大数(如3个 ),MAX_NR_NODES是可存在节点的最大数。EXPORT_SYMBOL()函数使zone_table能被载入模块访问,该表类似多维数组。
- free_area_init_core()函数:在该函数中,一个节点中的所有页面会被初始化。设置zone_table值的方式为:

zone_table[nid * MAX_NR_ZONES + j] = zone;

这里nid是节点ID,j是管理区索引号,zonezone_t结构。
- set_page_zone()函数:用于为每个页面设置所属管理区,调用方式为:

set_page_zone(page, nid * MAX_NR_ZONES + j);

其中page是要设置管理区的页面 ,通过这种方式,zone_table中的索引被显式存储在页面中。

2.7 高端内存

  1. 引入原因:内核ZONE_NORMAL中可用地址空间有限,所以Linux内核支持高端内存概念。
  2. 32位x86系统阈值及相关限制
    • 4 GB阈值:与32位物理地址定位的内存容量有关。为访问1 GB - 4 GB之间内存,内核通过kmap()将高端内存页面临时映射成ZONE_NORMAL,第9章深入讨论。
    • 64 GB阈值:与物理地址扩展(PAE )有关,PAE是Intel发明用于允许32位系统使用RAM的技术,通过附加4位定位内存地址,实现2⁶⁴字节(64 GB)内存定位 。
  1. 理论与实际情况差异
    理论上PAE允许处理器寻址到64 GB,但Linux中进程虚拟地址空间仍是4 GB,试图用malloc()分配所有RAM的用户会失望。
  2. 地址扩展空间(PAE)的影响
    PAE不允许内核自身拥有大量可用RAM。描述页面的struct page需44字节,占用ZONE_NORMAL内核虚拟地址空间 。描述1 GB内存需约11 MB内核内存,描述16 GB内存需176 MB,给ZONE_NORMAL带来巨大压力。像页面表项(PTE )这类小数据结构最坏情况下也需约16 MB。所以在x86机器上Linux可用物理内存被限制为16 GB,若需访问更多内存,建议使用64位机器。

1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/84665.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据湖是什么?数据湖和数据仓库的区别是什么?

目录 一、数据湖是什么 &#xff08;一&#xff09;数据湖的定义 &#xff08;二&#xff09;数据湖的特点 二、数据仓库是什么 &#xff08;一&#xff09;数据仓库的定义 &#xff08;二&#xff09;数据仓库的特点 三、数据湖和数据仓库的区别 &#xff08;一&#…

Smart Form Adobe form

强制更改内表:TNAPR se16-> Smart Form总览 Smart form 变量格式说明: &symbol& (括号中,小写字母为变量) &symbol& 屏蔽从第一位开始的N位 &symbol (n)& 只显示前N位 &symbol (S)& 忽略正负号 &symbol (<)& 符号在…

Linux 内核学习(11) --- Linux 链表结构

文章目录 Linked List 简介Linked List 操作方法链表头结点初始化创建链表节点添加节点到链表中从链表中删除节点从链表中替换节点移动链表中的节点检查链表链表遍历demo 实例 Linked List 简介 链表是一种数据结构&#xff0c;由一系列节点组成&#xff0c;每个节点包含数据部…

一分钟部署nginx-公网IP访问内网

前言 服务器内网下有nacos cluster&#xff08;3个节点&#xff09;&#xff0c;开放到公网并指定公司网络访问需要配置三次IP白名单&#xff0c;因此需要简化流程&#xff0c;通过nginx反向代理只配置1次IP白名单。 现在通过docker容器模拟环境&#xff0c;准备1台云服务器。…

C 语言分支与循环

目录 一. 分支结构&#xff1a;if 语句与 switch 语句 1. if 语句 2. switch 语句 二、关系操作符、条件操作符与逻辑操作符 1. 关系操作符 2. 条件操作符 3. 逻辑操作符 三、循环结构&#xff1a;while 循环、for 循环与 do - while 循环 1. while 循环 2. for 循…

【一文看懂Spring Boot2.x升级Spring Boot3.x】springboot2.x升级springboot3.x

springboot2.x升级springboot3.x 背景升级jdk版本为17以上springboot版本修改javax包更新mybatis-plus升级swagger升级springdocspringdoc配置背景 当前项目是springboot2.5.9版本的springboot+mybatis-plus项目,需要升级到springboot3.5.0项目。 升级jdk版本为17以上 Spri…

阳台光伏防逆流电表革新者:安科瑞ADL200N-CT/D16-WF

——为家庭能源管理提供高精度、智能化解决方案 一、阳台光伏爆发的背景 在全球能源转型与碳中和目标的驱动下&#xff0c;阳台光伏正以革命性姿态重塑家庭能源消费模式。从欧洲的“微型发电站”到中国的“万亿蓝海”&#xff0c;这一创新技术不仅撬动了能源市场的结构性变革…

美团完整面经

面试岗位 面试的岗位 - 2025春季校招 【转正实习】软件服务工程师-后端方向&#xff08;成都 - 软硬件服务-SaaS事业部&#xff09; 一面&#xff08;业务初试 - 30min&#xff09; 问题 自我介绍 Java基础 HashMap底层用的数据结构是什么&#xff1f;是线程安全的吗&…

pysnmp 操作流程和模块交互关系的可视化总结

1. SNMP GET 操作序列图 #mermaid-svg-KALvv8WkHJTsNCeu {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-KALvv8WkHJTsNCeu .error-icon{fill:#552222;}#mermaid-svg-KALvv8WkHJTsNCeu .error-text{fill:#552222;str…

关于 /proc/net/tcp 与 /proc/$pid/net/tcp 的关系分析

关于 /proc/net/tcp 与 /proc/$pid/net/tcp 的关系分析 1. 基础概念 在 Linux 系统中&#xff0c;每个进程必定归属于一个且仅一个网络命名空间&#xff08;Network Namespace&#xff09;。这是 Linux 命名空间隔离机制的核心特性之一。 /proc/net/tcp 显示当前网络命名空间…

微信小程序 - 保存手机号等信息到通讯录

主要使用小程序 wx.addPhoneContact 这个api 一、界面 <view class"tab-item" bindtap"addToPhoneContacts">保存</view> 二、js 逻辑文件中 addToPhoneContacts() {wx.addPhoneContact({firstName: this.data.firstName, // 姓名mobilePh…

计算机视觉一些定义解析

1.GCT&#xff08;Gated Channel Transformation&#xff09; 定义 GCT&#xff08;Gated Channel Transformation&#xff09;是一种用于增强卷积神经网络特征提取能力的模块。它的核心思想是通过门控机制对特征图的通道进行动态调整&#xff0c;从而突出对任务更有帮助的特…

美团NoCode的Database 使用指南

系列文章目录 第一篇&#xff1a;美团NoCode设计网站的尝试经验分 第二篇&#xff1a;美团NoCode中的Dev Mode 使用指南 文章目录 系列文章目录Database 适用场景一、什么是 Database&#xff1f;二、准备流程1. 申请账号 三、使用流程1.申请资源的同时可搭建 NoCode 页面&…

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…

1.11 HTTP 文件上传的核心协议

HTTP 文件上传是 Web 开发中的常见需求&#xff0c;涉及到特殊的请求格式和处理机制。 一、HTTP 文件上传的核心协议 1. 两种主要方式 multipart/form-data&#xff08;主流&#xff09; 支持二进制文件和表单字段混合传输&#xff0c;由 Content-Type 头部标识。applicatio…

安装 Poppler(Windows)

下载 Poppler&#xff08;Windows&#xff09;&#xff1a;https://github.com/oschwartz10612/poppler-windows/releases/ 解压在自己目录下 配置系统环境变量&#xff1a;把 poppler-xx.x.x\bin 目录加入你的环境变量 PATH 中。 检查是否配置成功 pdfinfo

Java学习笔记之:初识nginx

Java学习笔记之&#xff1a;初识nginx PS&#xff1a;虽然总结的都很简单&#xff0c;但是作为初学者并且本人记忆力较差所以每次学习新知识点后习惯性记录下来&#xff0c;这样加深一遍记忆并且便于日后复习。 介绍&#xff1a; Nginx是一款轻量级的Web服务器/反向代理服务器…

Middleware

中间件的定义&#xff1a;中间件是位于操作系统和应用程序之间的软件层&#xff0c;用于解决分布式系统中通信、数据共享、资源管理等共性问题。消息队列属于通信中间件&#xff0c;用于在分布式系统中传递消息&#xff0c;实现应用解耦、异步通信和流量削峰。解耦系统&#xf…

Mac如何配置ZSH并使用Oh-my-zsh?让你的终端更加实用、美观

前言 现在&#xff0c;越来越多的人趋向使用ZSH取代(Linux)原本的Bash作为自己的终端Shell。的确&#xff0c;ZSH才是适用于现代的Shell&#xff1a; 更丰富的命令提示更鲜明的演示标记更强大的插件支持 什么是ZSH 回答什么是ZSH前&#xff0c;我们先解释什么是Bash&#x…

C++11新标准

重点 auto 类型推导范围 for 迭代初始化列表变参模板 新类型 C11新增了类型 long long 和 unsigned long long&#xff0c;以支持64位(或更宽)的整型;新增了类型 char16_t和 char32_t&#xff0c;以支持 16位和 32 位的字符表示;还新增了“原始”字符串。 常量 nullptr nu…