[Linux]学习笔记系列 -- [mm][mmzone]

文章目录


在这里插入图片描述

https://github.com/wdfk-prog/linux-study

mm/vmscan.c lruvec 结构体 页面回收的核心数据结构

lruvec 是 Linux 内核中用于管理 LRU(Least Recently Used,最近最少使用)页面列表的一个核心数据结构。它是内存管理子系统的一部分,定义于 mm/vmscan.c 相关头文件中,主要作用是在页面回收(Page Reclamation)过程中,对不同类型、不同状态的内存页面进行分组和跟踪,是内核决定回收哪些页面的关键依据。

历史与背景

这项技术是为了解决什么特定问题而诞生的?

lruvec 的诞生是为了解决在现代计算机体系结构下,如何高效、精确、可扩展地管理内存页面以供回收的问题。

  • 区分页面类型:不同类型的内存页面回收成本和策略完全不同。例如,文件页(File-backed pages)如果内容未被修改(是干净的),可以直接丢弃,需要时再从磁盘读回;而匿名页(Anonymous pages,如进程堆栈、malloc分配的内存)则必须先交换到交换空间(Swap Area)才能释放,成本更高。lruvec 需要将它们分开管理。
  • 近似LRU算法:真正的LRU算法要求跟踪每个页面的访问时间,这在拥有数十GB甚至TB内存的现代系统中,开销是无法接受的。因此,内核采用了一种近似LRU的策略,即“二次机会”(Second Chance)或“Active/Inactive”两级列表,lruvec 就是这个策略的容器。
  • NUMA架构扩展性:在非统一内存访问(NUMA)系统中,全局共享的LRU列表会成为严重的性能瓶颈,因为所有CPU都需要竞争同一个锁来更新它。为提高扩展性,内存管理被设计为 per-node(每个NUMA节点一个)的方式,lruvec 正是 per-node 数据结构 pg_data_t 的一个关键成员。
  • 资源隔离(Memory Cgroups):在容器化环境中,必须对不同容器(Cgroup)的内存使用进行隔离和限制。当某个容器内存超限时,系统应只回收该容器的内存,而不是全局回收。因此,每个内存Cgroup也拥有自己的 lruvec,实现了 per-cgroup 的页面回收。
它的发展经历了哪些重要的里程碑或版本迭代?

lruvec 和其管理的LRU机制是逐步演进的。

  • 单LRU列表:非常早期的内核只有一个简单的全局LRU列表。
  • Active/Inactive双列表:后来引入了Active和Inactive两个列表,形成了经典的近似LRU算法,大大提高了页面回收的准确性。
  • NUMA和Cgroup集成:随着NUMA和Cgroups的出现,LRU列表的管理从全局演变为 per-nodeper-memcglruvec 结构体应运而生,将一个节点或一个Cgroup所需的所有LRU列表集合在一起。
  • 多代LRU(MGLRU):经典的双列表LRU在某些工作负载下仍表现不佳(例如,一次性的大文件读写会“污染”Active列表,导致真正需要的热数据被错误地回收)。从Linux 6.1内核开始,引入了多代LRU(Multi-Generational LRU, MGLRU)作为一个可选的、更先进的页面回收机制。MGLRU在lruvec内部维护了多个“代”(generations)来更精确地追踪页面的“热度”,在多数场景下能做出更好的回收决策,并减少性能开销。
目前该技术的社区活跃度和主流应用情况如何?

lruvec 是Linux内存管理子系统的基石,是内核中最核心、最稳定的数据结构之一。它不是一个可选功能,而是系统内存管理所必需的。随着MGLRU等新技术的不断合入,围绕 lruvec 的代码和逻辑仍在积极地开发和优化中。它被应用于所有运行Linux的设备上,从嵌入式设备到大型服务器,是系统在内存压力下能够保持稳定运行的根本保障。

核心原理与设计

它的核心工作原理是什么?

lruvec 本质上是一个LRU列表的集合。在经典的LRU机制中,每个lruvec包含以下几个主要的链表(list_head):

  • LRU_INACTIVE_ANON: 非活跃的匿名页。
  • LRU_ACTIVE_ANON: 活跃的匿名页。
  • LRU_INACTIVE_FILE: 非活跃的文件页。
  • LRU_ACTIVE_FILE: 活跃的文件页。
  • LRU_UNEVICTABLE: 不可回收的页(例如被mlock()锁定的内存)。

其工作流程如下:

  1. 页面添加:当一个新页面被换入内存时,它通常被添加到**非活跃(Inactive)**列表的头部。
  2. 页面访问与激活(Promotion):当一个位于Inactive列表中的页面被访问时,内核会将其标记为“已引用”。当页面回收扫描器(kswapd等)检查到这个被引用的页面时,不会立即回收它,而是将其从Inactive列表移动到**活跃(Active)**列表的头部,这给了它“第二次机会”。
  3. 页面老化与降级(Demotion):Active列表中的页面如果长时间未被访问,扫描器会清除它的“已引用”标记。如果再次扫描时发现该标记仍未被设置,说明它已经变“冷”,于是该页面会被从Active列表移回Inactive列表的头部。
  4. 页面回收:当系统需要释放内存时,页面回收扫描器会从非活跃(Inactive)列表的尾部开始扫描。因为尾部的页面是“最近最少使用”的。如果页面是干净的文件页,则直接释放;如果是脏文件页或匿名页,则将其换出到存储设备后释放。

lruvec 结构体还包含了保护这些列表的自旋锁(spinlock)以及各个列表的页面计数等统计信息。

它的主要优势体现在哪些方面?
  • 开销与效率的平衡:Active/Inactive双列表机制是对真实LRU算法的一个高效近似,避免了跟踪每个页面访问的巨大开销。
  • 策略分离:将匿名页和文件页分开管理,允许内核对不同成本的页面采用不同的回收策略和优先级。
  • 可扩展性:per-node的lruvec设计避免了全局锁竞争,在多核、多节点的NUMA服务器上表现出良好的伸缩性。
  • 资源隔离:per-memcg的lruvec为容器等技术提供了坚实的内存隔离基础,确保一个进程组的内存压力不会直接影响到其他进程组。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
  • 近似算法的缺陷:在某些工作负载下(如循环扫描大文件),双列表LRU可能会做出错误的判断,将真正需要的热数据降级并回收,而保留了只被访问一次的冷数据,造成性能抖动。这就是MGLRU试图解决的核心问题。
  • 颠簸(Thrashing):在内存压力极大时,页面可能在Active和Inactive列表之间被频繁地移动,消耗大量CPU资源,而实际回收的效率不高。
  • 复杂性:围绕lruvec的页面老化、平衡和扫描逻辑非常复杂,是内核中最难理解和调试的部分之一。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

lruvec是内核内置机制,并非一个可供选择的“解决方案”。以下是它发挥作用的关键场景:

  • 后台内存回收(kswapd):当系统空闲内存低于某个“低水位线”(low watermark)时,内核会唤醒每个NUMA节点对应的kswapd内核线程。kswapd会专门扫描其所属节点的lruvec中的Inactive列表,尝试异步地、在后台释放一些内存,以避免系统陷入内存不足的境地。
  • 直接内存回收(Direct Reclaim):当一个进程请求分配内存,但系统空闲内存已经低于“最低水位线”(min watermark)时,该进程的上下文会同步地触发直接回收。它会直接扫描当前NUMA节点和cgroup的lruvec,紧急释放足够的内存以满足本次分配请求。这个过程会阻塞进程,是造成应用出现性能延迟的常见原因。
  • 容器内存限制:当一个Docker容器或虚拟机(使用了memory cgroup)的内存使用量达到其配置的硬限制时,内核会专门针对该cgroup的lruvec启动直接回收,强制释放该容器内的内存页面,而不会影响系统中的其他容器或进程。
是否有不推荐使用该技术的场景?为什么?

用户无法选择“不使用”lruvec。但可以通过一些方式来规避其管理:

  • 使用mlock()系统调用:对于延迟极度敏感的应用(如硬实时系统、某些数据库的内存池),可以通过mlock()将关键内存区域锁定。被锁定的页面会被放入LRU_UNEVICTABLE列表,从而完全避免被页面回收机制扫描和换出,代价是这部分内存将永远无法被系统回收利用。

对比分析

请将其 与 其他相似技术 进行详细对比。

lruvec所承载的近似LRU算法,可以与其他页面置换算法进行对比。

特性Linux经典近似LRU (基于lruvec)MGLRU (Multi-Generational LRU)真实LRU (理论算法)
实现方式Active/Inactive双列表。通过PG_referenced位和扫描器进行页面的升级和降级。将页面组织在多个“代”(generation)中,新页面位于最新的代。扫描器从最老的代开始回收。为每个页面维护一个精确的最后访问时间戳,每次访问都需要更新。回收时选择时间戳最老的页面。
性能开销中等。链表操作和扫描有一定开销,在高压力下可能出现颠簸。较低。扫描开销更小,不易颠簸,整体CPU占用率通常更低。非常高。每次内存访问都可能需要更新时间戳并排序,开销巨大,不适用于通用操作系统。
资源占用每个lruvec维护5个链表头和计数器。每个lruvec维护多代(通常是8代)的元数据。需要为每个物理页面帧存储额外的时间戳数据。
回收准确性中等。能处理大部分常见负载,但在某些场景下会出错。。通过多代机制,能更精确地识别冷热页面,回收决策更优。最高(理论上)。
启动速度/延迟在直接回收场景下,扫描可能耗时较长,导致应用延迟。直接回收的效率更高,通常能降低应用延迟。-

include/linux/mmzone.h

first_zones_zonelist

/**
 * next_zones_zonelist - 使用区域列表中的光标作为起点,返回允许的节点掩码内highest_zoneidx或以下的下一个区域
 * @z:用作搜索起点的光标
 * @highest_zoneidx:要返回的最高区域的区域索引
 * @nodes:用于过滤区域列表的可选 nodemask
 *
 * 此函数使用游标作为搜索的起点,返回位于允许的节点掩码内的给定区域索引或以下的下一个区域。返回的 zoneref 是一个游标,表示当前正在检查的区域。在再次调用 next_zones_zonelist 之前,它应该提前 1 次。
 *
 * 返回:使用区域列表中的光标作为起点,在允许的节点掩码内highest_zoneidx或以下的下一个区域
 */
static __always_inline struct zoneref *next_zones_zonelist(struct zoneref *z,
					enum zone_type highest_zoneidx,
					nodemask_t *nodes)
{
	if (likely(!nodes && zonelist_zone_idx(z) <= highest_zoneidx))
		return z;
	return __next_zones_zonelist(z, highest_zoneidx, nodes);
}
/**
 * first_zones_zonelist - 返回区域列表中允许的节点掩码内highest_zoneidx或低于的第一个区域
 * @zonelist:用于搜索合适区域的区域列表
 * @highest_zoneidx:要返回的最高区域的区域索引
 * @nodes:用于过滤区域列表的可选 nodemask
 *
 * 此函数返回位于允许的节点掩码内的给定区域索引或低于给定区域索引的第一个区域。返回的 zoneref 是一个游标,可用于在调用之前将其前进 1 来迭代带有 next_zones_zonelist 的 zonelist。
 *
 * 当找不到符合条件的区域时,zoneref->zone 为 NULL(zoneref 本身永远不会为 NULL)。这可能是真的,也可能是由于 cpuset 修改导致的并发 nodemask 更新。
 *
 * 返回:找到的第一个合适区域的 zoneref 指针
 */
static inline struct zoneref *first_zones_zonelist(struct zonelist *zonelist,
					enum zone_type highest_zoneidx,
					nodemask_t *nodes)
{
	return next_zones_zonelist(zonelist->_zonerefs,
							highest_zoneidx, nodes);
}

wmark_pages 水印页面

enum zone_watermarks {
	WMARK_MIN,
	WMARK_LOW,
	WMARK_HIGH,
	WMARK_PROMO,
	NR_WMARK
};

static inline unsigned long wmark_pages(const struct zone *z,
					enum zone_watermarks w)
{
	return z->_watermark[w] + z->watermark_boost;
}

static inline unsigned long min_wmark_pages(const struct zone *z)
{
	return wmark_pages(z, WMARK_MIN);
}

static inline unsigned long low_wmark_pages(const struct zone *z)
{
	return wmark_pages(z, WMARK_LOW);
}

static inline unsigned long high_wmark_pages(const struct zone *z)
{
	return wmark_pages(z, WMARK_HIGH);
}

static inline unsigned long promo_wmark_pages(const struct zone *z)
{
	return wmark_pages(z, WMARK_PROMO);
}

for_each_zone 用于迭代所有在线节点的辅助宏

/**
 * for_each_online_pgdat - 用于迭代所有在线节点的辅助宏
 * @pgdat:指向 pg_data_t 变量的指针
 */
#define for_each_online_pgdat(pgdat)			\
	for (pgdat = first_online_pgdat();		\
	     pgdat;					\
	     pgdat = next_online_pgdat(pgdat))
/**
 *for_each_zone - 用于迭代所有内存区域的辅助宏
 * @zone:指向 struct zone 变量的指针
 *
 * 用户只需要声明 zone 变量,for_each_zone
 * 填充它。
 */
#define for_each_zone(zone)			        \
	for (zone = (first_online_pgdat())->node_zones; \
	     zone;					\
	     zone = next_zone(zone))

#define for_each_populated_zone(zone)		        \
	/* contig_page_data->node_zones */
	for (zone = (first_online_pgdat())->node_zones; \
	     zone;					\
	     zone = next_zone(zone))			\
		if (!populated_zone(zone))		\
			; /* do nothing */		\
		else

mm/mmzone.h 物理内存区域管理(Physical Memory Zones) NUMA架构内存组织的基础

mm/mmzone.h 是定义Linux内核中物理内存组织方式的核心头文件之一。它定义了两个至关重要的数据结构:struct zonestruct pglist_data (通常用 pg_data_t 别名),它们共同构成了Linux管理物理内存的基础层次结构。这个机制的核心目的是将物理内存划分为具有不同属性和访问特性的区域,以满足多样化的硬件需求并优化性能,尤其是在NUMA(非统一内存访问)架构下。

历史与背景

这项技术是为了解决什么特定问题而诞生的?

物理内存区域(Zones)和节点(Nodes)的划分是为了解决计算机体系结构发展过程中遇到的几个核心问题:

  • 硬件的寻址限制
    • DMA限制:在早期的PC架构中,一些老的ISA设备只能对物理内存的前16MB进行直接内存访问(DMA)。因此,内核必须能够专门从这个区域为这些设备分配内存。这催生了 ZONE_DMA
    • 32位系统的“高端内存”问题:在32位CPU上,内核虚拟地址空间有限(通常为1GB)。这意味着内核无法直接、永久地映射所有物理内存。超过内核直接映射范围的物理内存被称为“高端内存”(High Memory)。内核需要特殊的临时映射才能访问这部分内存,因此需要将其与可直接映射的“普通内存”区分开,这分别对应 ZONE_HIGHMEMZONE_NORMAL
  • NUMA架构的性能问题:在多CPU插槽的服务器中,每个CPU访问其本地连接的内存速度非常快,而访问连接在其他CPU上的远程内存则要慢得多。这就是NUMA。为了获得高性能,内核必须尽可能地为运行在某个CPU上的进程分配其本地内存。这就要求内核将物理内存按CPU亲和性划分为“节点”(Nodes),每个节点包含独立的内存管理结构。
  • 内存碎片问题:长时间运行的系统会产生内存碎片,导致难以分配大的连续物理内存块。为了缓解这个问题,内核需要一种机制来将容易移动的页面(如用户进程的匿名页)和难以移动的页面(如内核数据结构)分开管理。
它的发展经历了哪些重要的里程碑或版本迭代?
  • 从扁平到分区:最初的内核内存模型是扁平的。随着硬件限制的出现,ZONE_DMAZONE_HIGHMEM 被引入,形成了内存的初步分区。
  • NUMA的引入:对NUMA架构的支持是内存管理子系统的一次重大重构。struct pglist_data (node) 被引入,成为管理内存的最高层级。每个节点拥有自己的一套Zones、lruveckswapd线程等,使得内存管理从全局模型演变为高度并行的分布式模型。
  • 64位架构的普及:在64位系统上,巨大的虚拟地址空间使得内核可以轻松地直接映射海量的物理内存,因此 ZONE_HIGHMEM 的问题基本不复存在。但该区域的定义仍然保留,主要用于某些32位场景或特殊架构。同时,ZONE_DMA32 被引入,用于支持能在32位地址空间(4GB)内进行DMA的设备。
  • 反碎片机制的增强:为了对抗内存碎片,ZONE_MOVABLE 被引入。这是一个逻辑上的区域,用于汇集可迁移的页面。这使得内核更容易在其他区域(如ZONE_NORMAL)中找到连续的、不可移动的物理内存块,满足内核对大块内存的需求。
目前该技术的社区活跃度和主流应用情况如何?

zonepg_data_t 是Linux物理内存管理的基石,是内核中最稳定、最核心的部分。它不是一个可选功能,而是所有现代Linux系统运行的基础。虽然核心结构稳定,但围绕它的分配策略、回收算法(如MGLRU)以及与Cgroups的交互仍在不断演进和优化。

核心原理与设计

它的核心工作原理是什么?

Linux内核通过一个清晰的层次结构来组织物理内存:Nodes -> Zones -> Pages

  1. 节点 (Node, struct pglist_data)

    • 这是最高层级,对应一个NUMA节点。在一个NUMA系统中,一个Node就是一组CPU及其本地内存的集合。对于普通的单插槽PC(UMA架构),整个系统只有一个Node 0。
    • 每个Node都是一个相对独立的内存管理单元,它包含一个zone数组、一个lruvec、一个kswapd线程等。所有针对该节点的内存分配和回收操作都首先在其内部进行。
  2. 区域 (Zone, struct zone)

    • 每个Node内的内存被进一步划分为多个Zone。一个Zone代表一段具有特定属性的连续物理内存。主要的Zone类型包括:
      • ZONE_DMA: 用于满足老旧设备<16MB的DMA需求。
      • ZONE_DMA32: 用于满足设备<4GB的DMA需求。
      • ZONE_NORMAL: 内核可以正常直接访问的普通内存。在64位系统上,这是最大也是最主要的区域。
      • ZONE_HIGHMEM: (主要用于32位) 内核不能直接永久映射的“高端内存”。
      • ZONE_MOVABLE: 一个特殊的虚拟区域,用于隔离可迁移的页面以对抗碎片。
    • 水位线 (Watermarks):每个Zone内部定义了三个关键的水位线 (pages_min, pages_low, pages_high)。它们是页面回收机制的触发器。
      • 当可用内存低于 pages_high 时,kswapd 开始在后台异步回收内存。
      • 当可用内存低于 pages_low 时,内存分配会进入“节流”模式。
      • 当可用内存低于 pages_min 时,内存分配器会触发成本高昂的“直接回收”(Direct Reclaim),即请求分配的进程自己必须先同步地释放一些内存。
  3. 区域列表 (Zonelist)

    • 当一个内存分配请求到来时,内核不会只在一个Zone中查找。它会根据一个预先计算好的、有序的区域列表 (Zonelist) 来进行尝试。
    • 这个列表定义了分配的备用(fallback)顺序。例如,一个在Node 0上的普通分配请求,其Zonelist通常是这样排序的:Node 0/ZONE_NORMAL -> Node 0/ZONE_DMA32 -> Node 0/ZONE_DMA -> Node 1/ZONE_NORMAL -> …。
    • 这个机制确保了内核首先尝试在最理想的区域(本地节点的ZONE_NORMAL)分配,如果失败,则逐步降级到次优区域,最后甚至可以尝试从远程NUMA节点分配(这会带来性能损失)。
它的主要优势体现在哪些方面?
  • NUMA性能优化:Node结构是实现NUMA感知内存分配的基础,能最大化内存访问的局部性。
  • 硬件兼容性:Zone的划分完美地解决了不同设备对内存地址的特殊要求。
  • 主动的内存管理:水位线机制使得内核可以在内存完全耗尽前,主动、异步地进行内存回收,提高了系统的响应能力。
  • 有效的碎片管理ZONE_MOVABLE机制是内核对抗物理内存碎片化的重要武器。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
  • 复杂性:Node和Zone的交互,以及Zonelist的 fallback 策略,使得内存分配路径非常复杂,给调试和性能分析带来挑战。
  • 静态边界:Zone之间的边界在系统启动时基本就确定了。如果一个Zone(如ZONE_NORMAL)耗尽,而另一个Zone(如ZONE_DMA)还有大量空闲,内核也无法轻易地将后者的内存用于前者的分配请求(除非Zonelist允许),可能导致分配失败。
  • 跨节点访问的性能悬崖:Zonelist的 fallback 机制虽然保证了分配的成功率,但一旦跨越NUMA节点进行分配,应用程序的性能可能会出现急剧、非线性的下降。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

这是内核的底层机制,总是处于活动状态。以下是它发挥作用的具体场景:

  • 为旧网卡分配DMA缓冲区:当一个老的ISA网卡驱动需要内存时,它会使用 GFP_DMA 标志调用内存分配函数。内核的分配器会根据这个标志,只在Zonelist中查找 ZONE_DMA 类型的区域。
  • NUMA数据库优化:一个运行在多节点服务器上的数据库,如果被正确地绑定到某个Node的CPU上,它的内存分配请求将自动优先使用该Node的本地内存,从而获得最佳性能。
  • 申请大块连续内存:当内核需要分配一个巨大的、不可移动的缓冲区时(例如用于某些硬件),ZONE_MOVABLE的存在使得ZONE_NORMAL中保留了更多未被用户空间页面“污染”的连续空间,从而提高了这种分配的成功率。
是否有不推荐使用该技术的场景?为什么?

不能“不使用”这个技术。但可以“错误使用”它。例如,一个驱动程序如果错误地或不必要地请求了 GFP_DMA 标志,它会无谓地消耗宝贵的ZONE_DMA内存,可能导致真正需要它的老旧设备无法工作。正确的做法是,只有在硬件明确要求时,才使用带有特定Zone限制的分配标志。

对比分析

请将其 与 其他相似技术 进行详细对比。

最主要的对比是Linux所采用的这种NUMA感知的多区域内存模型与传统的统一内存访问(UMA)扁平模型

特性NUMA感知的多区域模型 (Linux采用)UMA扁平内存模型
物理组织内存被划分为多个Node,每个Node再划分为多个Zone。所有物理内存被视为一个单一的、统一的池。可能仍有Zone用于DMA,但没有Node的概念。
访问延迟不统一。访问本地Node内存快,访问远程Node内存慢。统一。任何CPU访问任何内存地址的延迟都基本相同。
实现复杂性。需要复杂的Node和Zone数据结构,以及精心设计的Zonelist fallback策略。。内存分配器逻辑相对简单。
可扩展性非常高。通过将管理分布到各个Node,大大减少了全局锁的竞争,可以很好地扩展到拥有数十个CPU插槽和TB级内存的服务器。有限。在多CPU插槽的系统上,单一的内存池会成为性能瓶颈。
适用硬件多CPU插槽的服务器,大型数据中心。单CPU插槽的台式机、笔记本电脑、嵌入式设备。
性能特点性能高度依赖于内存局部性。优化的好,性能极高;优化不好(跨节点访问过多),性能可能很差。性能可预测,但无法利用多节点硬件的局部性优势。

lruvec_init LRU集合初始化

  • LRU(Least Recently Used,最近最少使用)页面列表
  • vec 是 “vector” 的缩写,表示一个集合或数组
void lruvec_init(struct lruvec *lruvec)
{
	enum lru_list lru;

	memset(lruvec, 0, sizeof(struct lruvec));
	spin_lock_init(&lruvec->lru_lock);
	zswap_lruvec_state_init(lruvec);

	for_each_lru(lru)
		INIT_LIST_HEAD(&lruvec->lists[lru]);
	/*
	 * “不可驱逐的 LRU”是虚构的:虽然它的大小保持不变,但永远不会被扫描,
     * 并且不可驱逐的页面不会在其上嵌套(因此它们的 LRU 字段可以被重用来保存mlock_count)。
     * 对其列表 head 下毒,以便对其执行任何作都会崩溃。
	 */
	list_del(&lruvec->lists[LRU_UNEVICTABLE]);
    //无执行代码
	lru_gen_init_lruvec(lruvec);
}

__next_zones_zonelist

static inline int zonelist_zone_idx(struct zoneref *zoneref)
{
	return zoneref->zone_idx;
}

/* Returns the next zone at or below highest_zoneidx in a zonelist */
struct zoneref *__next_zones_zonelist(struct zoneref *z,
					enum zone_type highest_zoneidx,
					nodemask_t *nodes)
{
	/*
	 * 找到下一个合适的区域用于分配。
	 * 仅根据 nodemask 进行筛选(如果已设置)
	 */
	if (unlikely(nodes == NULL))	//表示不需要基于节点掩码进行筛选
		while (zonelist_zone_idx(z) > highest_zoneidx)	//检查当前区域的索引是否高于 highest_zoneidx
			z++;	//如果高于,则移动游标 z 到下一个区域,直到找到符合条件的区域
	else
		while (zonelist_zone_idx(z) > highest_zoneidx ||
				(zonelist_zone(z) && !zref_in_nodemask(z, nodes)))
			z++;

	return z;
}

next_online_pgdat

struct pglist_data *next_online_pgdat(struct pglist_data *pgdat)
{
	int nid = next_online_node(pgdat->node_id);

	if (nid == MAX_NUMNODES)
		return NULL;
	return NODE_DATA(nid);
}

next_zone for_each_zone() 的辅助魔术

/*
 * next_zone - for_each_zone() 的辅助魔术
 */
struct zone *next_zone(struct zone *zone)
{
	pg_data_t *pgdat = zone->zone_pgdat;

	if (zone < pgdat->node_zones + MAX_NR_ZONES - 1)
		zone++;
	else {
		pgdat = next_online_pgdat(pgdat);
		if (pgdat)
			zone = pgdat->node_zones;
		else
			zone = NULL;
	}
	return zone;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值