拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

page_alloc node_states

page_alloc node_states

全局节点状态数组和几个与分配相关的全局变量。它们是内核内存管理初始化阶段的基础设施。逐块拆解。


一、node_states[NR_NODE_STATES]—— 节点状态数组

nodemask_t node_states[NR_NODE_STATES] __read_mostly = { [N_POSSIBLE] = NODE_MASK_ALL, [N_ONLINE] = { { [0] = 1UL } }, #ifndef CONFIG_NUMA [N_NORMAL_MEMORY] = { { [0] = 1UL } }, #ifdef CONFIG_HIGHMEM [N_HIGH_MEMORY] = { { [0] = 1UL } }, #endif [N_MEMORY] = { { [0] = 1UL } }, [N_CPU] = { { [0] = 1UL } }, #endif /* NUMA */ }; EXPORT_SYMBOL(node_states);

1. 类型:nodemask_t

  • nodemask_t是节点位图,每个 bit 对应一个 NUMA 节点。

  • 类似于cpumask_t之于 CPU,nodemask_t描述"哪些节点属于某个集合"。

  • 通常定义为struct { DECLARE_BITMAP(bits, MAX_NUMNODES); }。

2. 数组维度:NR_NODE_STATES

node_states[]是按"状态"索引的数组,每个元素是一个节点位图,表示"处于该状态的节点集合"。

NR_NODE_STATES是状态总数,状态枚举通常包括:

状态含义
N_POSSIBLE系统可能存在的节点(由固件/ACPI 报告)
N_ONLINE已经上线的节点
N_NORMAL_MEMORY有普通内存(ZONE_NORMAL)的节点
N_HIGH_MEMORY有高端内存(ZONE_HIGHMEM)的节点
N_MEMORY有任何可管理内存的节点
N_CPU有CPU的节点

(具体枚举在include/linux/nodemask.h。)

3. 初始化值

[N_POSSIBLE] = NODE_MASK_ALL,
  • NODE_MASK_ALL:所有位都置 1,表示"所有可能的节点都可能存在"。

  • 这是最宽松的初始假设,后续启动过程中根据实际探测收紧(例如node_set_online()、node_set_state())。

[N_ONLINE] = { { [0] = 1UL } },
  • 只把节点 0置位([0] = 1UL表示位图第一个unsigned long的第 0 位为 1)。

  • 即"初始只有节点 0 在线"。

  • 后续启动时,内核探测到其他节点再逐个node_set_online()。

4.#ifndef CONFIG_NUMA分支——非 NUMA 系统的简化

在非 NUMA(单节点)系统上,这些状态都指向节点 0:

[N_NORMAL_MEMORY] = { { [0] = 1UL } }, #ifdef CONFIG_HIGHMEM [N_HIGH_MEMORY] = { { [0] = 1UL } }, #endif [N_MEMORY] = { { [0] = 1UL } }, [N_CPU] = { { [0] = 1UL } },
  • 非 NUMA 下只有一个节点(node 0),所以"有普通内存"、"有高端内存"、"有内存"、"有 CPU"都指 node 0。

  • CONFIG_HIGHMEM只在某些 32 位架构(如 x86_32)存在,所以N_HIGH_MEMORY有条件编译。

  • NUMA 系统下这些状态不在这里初始化,而是在启动时根据实际拓扑用node_set_state()动态设置。

5.__read_mostly

  • 告诉内核:这个变量读多写少,应放在.data..read_mostly段。

  • 好处:与经常写的变量分开,减少 cache line 伪共享,提高读性能。

  • 节点状态确实如此:启动时设置,运行期几乎只读。

6.EXPORT_SYMBOL(node_states)

  • 导出给模块,模块可以用node_state(node, state)等宏查询。

7. 典型使用

/* 遍历所有在线节点 */ for_each_node_state(n, N_ONLINE) { ... } /* 遍历有内存的节点 */ for_each_node_state(n, N_MEMORY) { ... } /* 查询某节点是否有 CPU */ if (node_state(n, N_CPU)) { ... }

分配器(如__alloc_pages)选择节点、zone 时会用到这些状态。


二、gfp_allowed_mask—— 全局 GFP 允许掩码

gfp_t gfp_allowed_mask __read_mostly = GFP_BOOT_MASK;

1. 作用

  • 一个全局的 GFP 标志掩码,用于在启动阶段限制哪些 GFP 标志可用。

  • 分配时实际使用的标志会被gfp_allowed_mask按位与:

    gfp &= gfp_allowed_mask;

    或类似逻辑,确保不会使用当前阶段还不允许的标志。

2. 为什么需要它?

  • 启动早期,很多子系统还没初始化好:

    • 不能睡眠(调度器/中断未就绪);

    • 不能做内存回收(kswapd/vmscan 未就绪);

    • 不能做直接回收、规整、OOM 等。

  • GFP_BOOT_MASK就是启动阶段允许的标志集合,去掉那些依赖未就绪子系统的标志(如__GFP_DIRECT_RECLAIM、__GFP_KSWAPD_RECLAIM、__GFP_NOFAIL等)。

  • 随着初始化推进,通过set_gfp_allowed_mask()逐步放宽,最终变成GFP_KERNEL等完整标志。

3. 典型流程

/* 启动早期 */ gfp_allowed_mask = GFP_BOOT_MASK; /* 某个初始化节点后放宽 */ set_gfp_allowed_mask(__GFP_DIRECT_RECLAIM | __GFP_KSWAPD_RECLAIM | ...);
  • __read_mostly:启动后基本只读。

  • 全局单一变量,非 per-CPU。


三、pageblock_order—— 可变页块阶数

#ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE unsigned int pageblock_order __read_mostly; #endif

1. 什么是 pageblock?

  • pageblock是伙伴系统之上、用于反碎片(anti-fragmentation)的页块粒度。

  • 页块的迁移类型(migratetype)按 pageblock 设置(set_pageblock_migratetype())。

  • 内存规整(compaction)、CMA 等也以 pageblock 为单位。

2.pageblock_order的含义

  • 表示一个 pageblock 是2^pageblock_order 个页。

  • 默认(!CONFIG_HUGETLB_PAGE_SIZE_VARIABLE)通常是编译期常量:

    #define pageblock_order min(HUGETLB_PAGE_ORDER, MAX_ORDER - 1)

    即取"大页阶数"和"伙伴系统最大阶减一"的较小值,保证 pageblock 至少能容纳一个大页。

3.CONFIG_HUGETLB_PAGE_SIZE_VARIABLE

  • 某些架构(如PPC64)支持多种大页尺寸,大页的阶数在运行时才知道(取决于实际配置/硬件)。

  • 这时pageblock_order不能是编译期常量,必须是一个运行期变量,在启动时根据实际大页尺寸设定。

  • 所以该配置开启时,pageblock_order被定义为unsigned int __read_mostly,而非宏。

4.__read_mostly

  • 启动时确定,运行期只读。

  • 与node_states、gfp_allowed_mask一样,放在 read-mostly 段。

5. 典型使用

/* 一个 pageblock 的页数 */ 1UL << pageblock_order /* 计算 PFN 所属 pageblock 的起始 PFN */ pfn & ~((1UL << pageblock_order) - 1) /* 判断是否对齐到 pageblock */ IS_ALIGNED(pfn, 1 << pageblock_order)

四、三个变量的共性

变量类型作用写入时机
node_statesnodemask_t[]各状态下的节点集合启动初始化 + 热插拔
gfp_allowed_maskgfp_t启动阶段允许的 GFP 标志启动逐步放宽
pageblock_orderunsigned int(条件)pageblock 的阶数启动根据大页尺寸设定

共同点:

  • 都是全局、启动时确定、运行期几乎只读;

  • 都用__read_mostly(node_states显式标注,另两个也是);

  • 都服务于分配器的初始化与约束。


五、小结

代码含义
node_states[NR_NODE_STATES]按状态索引的节点位图数组,描述哪些节点可能/在线/有内存/有 CPU
N_POSSIBLE = NODE_MASK_ALL初始假设所有节点都可能存在
N_ONLINE = {[0]=1}初始只有节点 0 在线
#ifndef CONFIG_NUMA分支非 NUMA 下所有状态都指向节点 0
EXPORT_SYMBOL(node_states)导出给模块
gfp_allowed_mask启动阶段允许的 GFP 标志掩码,初始GFP_BOOT_MASK,逐步放宽
pageblock_order(条件)大页尺寸可变时的 pageblock 阶数,运行期变量而非宏

一句话总结

node_states以位图数组形式记录各状态下(可能/在线/有内存/有 CPU)的 NUMA 节点集合,初始只让节点 0 在线、其余按启动探测填充;gfp_allowed_mask在启动早期限制可用 GFP 标志并随初始化逐步放宽;pageblock_order在大页尺寸可变的架构上是运行期变量,决定 pageblock 的阶数——三者都是启动确定、运行期只读的分配器基础设施。

返回列表