当前位置: 首页 > news >正文

Linux中伙伴系统页面回收free_pages_bulk和分配函数__rmqueue的实现

伙伴系统核心设计思想

分层管理架构

  • order(2的幂次)分层管理内存块
  • 每个order维护独立空闲链表和位图
  • 支持从4KB到最大块的高效分配

伙伴关系定义

  • 相邻且大小相同的块互为伙伴
  • 伙伴块可以合并成更大的块
  • 通过位运算快速定位伙伴:page_idx ^ (1 << order)

位图精妙设计

index = page_idx >> (1 + order)
  • 每个位对应两个相邻块的合并状态
  • 位值=0:两个块状态相同(都空闲或都分配)
  • 位值=1:一个空闲一个分配(不能合并)
  • 通过 1 + order 位移确保伙伴共享同一位(area->map)

内存分配机制(__rmqueue

最佳适配策略

  • 从请求order开始向上搜索第一个可用块
  • 避免小order占用大块造成的碎片

拆分机制(expand)

while (high > low) {// 将后半块加入低order空闲链表list_add(&page[size].lru, &area->free_list);
}
  • 大块拆分成需要大小 + 剩余小块
  • 剩余部分返回相应order空闲链表
  • 保持内存池完整性

内存释放机制(__free_pages_bulk)

合并算法

while (order < MAX_ORDER-1) {if (!__test_and_change_bit(index, area->map))// 原来为0,这里必定表示两个相邻块都是分配的// 所以回收当前块以后变成一个空闲,一个已分配,不能合并break; // 合并并升级到下一order
}
  • 原子检查伙伴位图
  • 逐级向上合并直到遇到不能合并的伙伴,即一个空闲,一个已分配
  • 有效减少外部碎片

总结

Linux伙伴系统的精妙之处在于:

  1. 数学完备性:基于2的幂次的数学特性确保算法正确性
  2. 空间效率:用最少位图空间跟踪合并可能性
  3. 时间效率:O(1)的分配和释放平均时间复杂度
  4. 并发安全:细粒度锁+原子操作支持多处理器
  5. 自愈能力:通过合并机制自动减少内存碎片

批量释放物理内存页面到指定的内存区域free_pages_bulk

static int
free_pages_bulk(struct zone *zone, int count,struct list_head *list, unsigned int order)
{unsigned long flags;struct free_area *area;struct page *base, *page = NULL;int ret = 0;base = zone->zone_mem_map;area = zone->free_area + order;spin_lock_irqsave(&zone->lock, flags);zone->all_unreclaimable = 0;zone->pages_scanned = 0;while (!list_empty(list) && count--) {page = list_entry(list->prev, struct page, lru);/* have to delete it as __free_pages_bulk list manipulates */list_del(&page->lru);__free_pages_bulk(page, base, zone, area, order);ret++;}spin_unlock_irqrestore(&zone->lock, flags);return ret;
}

函数功能概述

free_pages_bulk 函数用于批量释放物理内存页面到指定的内存区域(zone)的空闲列表中,是内核页面分配器的核心组成部分

代码逐段详解

变量声明和初始化

static int
free_pages_bulk(struct zone *zone, int count,struct list_head *list, unsigned int order)
{unsigned long flags;struct free_area *area;struct page *base, *page = NULL;int ret = 0;
  • zone: 目标内存区域,页面将被释放到这里
  • count: 要释放的最大页面块数量
  • list: 包含待释放页面的链表
  • order: 页面块的数量(2^order 个页面)
  • flags: 保存中断状态的变量
  • area: 指向对应order的空闲区域描述符
  • base: 内存区域的基础页面指针
  • page: 当前正在处理的页面指针
  • ret: 返回值,记录实际释放的页面块数量

基础设置

	base = zone->zone_mem_map;area = zone->free_area + order;
  • base = zone->zone_mem_map: 获取该内存区域的起始页面描述符数组
  • area = zone->free_area + order: 计算对应order的空闲区域地址
    • free_area 是不同order空闲链表的数组
    • 通过指针运算找到特定order的空闲区域

临界区保护

	spin_lock_irqsave(&zone->lock, flags);zone->all_unreclaimable = 0;zone->pages_scanned = 0;
  • spin_lock_irqsave(&zone->lock, flags):
    • 获取zone的自旋锁,防止并发访问
    • 保存当前中断状态到flags,并禁用本地CPU中断
  • zone->all_unreclaimable = 0: 标记该zone为可回收状态
  • zone->pages_scanned = 0: 重置页面扫描计数器,为后续内存回收做准备

核心释放循环

	while (!list_empty(list) && count--) {page = list_entry(list->prev, struct page, lru);/* have to delete it as __free_pages_bulk list manipulates */list_del(&page->lru);__free_pages_bulk(page, base, zone, area, order);ret++;}
  • 循环条件: 链表不为空且还有释放配额(count–)
  • page = list_entry(list->prev, struct page, lru):
    • 从链表尾部获取页面
    • list_entry 宏通过成员指针获取包含它的结构体指针
  • list_del(&page->lru): 将页面从输入链表中移除
  • __free_pages_bulk(page, base, zone, area, order):
    • 核心释放函数,处理页面合并等复杂逻辑
  • ret++: 统计成功释放的页面块数量

清理和返回

	spin_unlock_irqrestore(&zone->lock, flags);return ret;
}
  • spin_unlock_irqrestore(&zone->lock, flags):
    • 释放zone锁
    • 恢复之前的中断状态
  • return ret: 返回实际释放的页面块数量

将物理页面释放到空闲链表__free_pages_bulk

static void destroy_compound_page(struct page *page, unsigned long order)
{int i;int nr_pages = 1 << order;if (!PageCompound(page))return;if (page[1].index != order)bad_page(__FUNCTION__, page);for (i = 0; i < nr_pages; i++) {struct page *p = page + i;if (!PageCompound(p))bad_page(__FUNCTION__, page);if (p->private != (unsigned long)page)bad_page(__FUNCTION__, page);ClearPageCompound(p);}
}
static inline void __free_pages_bulk (struct page *page, struct page *base,struct zone *zone, struct free_area *area, unsigned int order)
{unsigned long page_idx, index, mask;if (order)destroy_compound_page(page, order);mask = (~0UL) << order;page_idx = page - base;if (page_idx & ~mask)BUG();index = page_idx >> (1 + order);zone->free_pages += 1 << order;while (order < MAX_ORDER-1) {struct page *buddy1, *buddy2;BUG_ON(area >= zone->free_area + MAX_ORDER);if (!__test_and_change_bit(index, area->map))/** the buddy page is still allocated.*/break;/* Move the buddy up one level. */buddy1 = base + (page_idx ^ (1 << order));buddy2 = base + page_idx;BUG_ON(bad_range(zone, buddy1));BUG_ON(bad_range(zone, buddy2));list_del(&buddy1->lru);mask <<= 1;order++;area++;index >>= 1;page_idx &= mask;}list_add(&(base + page_idx)->lru, &area->free_list);
}

函数功能概述

__free_pages_bulk 是伙伴系统的核心释放函数,负责将物理页面释放到空闲链表,并尝试与相邻的空闲块合并形成更大的连续块

代码逐段详解

复合页面销毁检查

static inline void __free_pages_bulk (struct page *page, struct page *base,struct zone *zone, struct free_area *area, unsigned int order)
{unsigned long page_idx, index, mask;if (order)destroy_compound_page(page, order);
  • 参数:
    • page: 要释放的页面起始地址
    • base: zone的起始页面地址
    • zone: 所属内存区域
    • area: 对应的free_area
    • order: 页面块大小级别
  • if (order) destroy_compound_page(page, order): 如果order>0(复合页面),先销毁复合页面结构

destroy_compound_page 函数详解

static void destroy_compound_page(struct page *page, unsigned long order)
{int i;int nr_pages = 1 << order;if (!PageCompound(page))return;if (page[1].index != order)bad_page(__FUNCTION__, page);for (i = 0; i < nr_pages; i++) {struct page *p = page + i;if (!PageCompound(p))bad_page(__FUNCTION__, page);if (p->private != (unsigned long)page)bad_page(__FUNCTION__, page);ClearPageCompound(p);}
}
  • nr_pages = 1 << order: 计算总页面数(2^order)
  • if (!PageCompound(page)) return: 检查页面标记为复合页面
  • page[1].index != order: 验证第二个页面中存储的order值
  • 循环检查每个子页面:
    • if (!PageCompound(p)): 每个子页面都应有复合标记
    • p->private != (unsigned long)page: private字段应指向首页面
    • ClearPageCompound(p): 清除复合页面标记

页面索引计算和验证

	mask = (~0UL) << order;page_idx = page - base;if (page_idx & ~mask)BUG();index = page_idx >> (1 + order);
  • mask = (~0UL) << order: 创建掩码,低位order位为0
  • page_idx = page - base: 计算页面在zone中的索引
  • if (page_idx & ~mask) BUG(): 验证页面对齐
    • 索引必须能被2^order整除
    • 如果希望批量释放页面的话,需要确保首页地址是对应order阶层的页面
  • index = page_idx >> (1 + order): 计算在位图中的索引
    • 这里加1获取的是上一阶层的索引,用于后面判断当前层级的相邻块的使用状态

更新zone空闲页面计数

	zone->free_pages += 1 << order;
  • 增加zone的空闲页面计数器,增加数量为 2^order 个页面

伙伴合并核心循环

	while (order < MAX_ORDER-1) {struct page *buddy1, *buddy2;BUG_ON(area >= zone->free_area + MAX_ORDER);if (!__test_and_change_bit(index, area->map))/** the buddy page is still allocated.*/break;
  • 循环条件: 当前order小于最大order-1

  • BUG_ON(area >= zone->free_area + MAX_ORDER): 确保area指针有效

  • if (!__test_and_change_bit(index, area->map)) break:

    • __test_and_change_bit 原子地测试并翻转位图位
    • 位图代表的意思如下
    块大小: 2页 = 2^1 页
    相邻块数: 2个块
    位图粒度: 4页 = 2^(1+1) 页页面索引: 0 1 | 2 3 | 4 5 | 6 7 | ...
    位图索引:     0           1       ...计算: index >> (1 + 1) = index >> 2
    index=0: 0 >> 2 = 0
    index=2: 2 >> 2 = 0  (与块0共享同一位)
    index=4: 4 >> 2 = 1
    index=6: 6 >> 2 = 1  (与块4共享同一位)
    位为0表示两个相邻块都是空闲或者都是已分配
    位为1表示两个相邻块一个空闲一个已分配
    
    • 这里index对应的位如果原来是0,只可能是下面两个相邻块都是已分配,不可能都是空闲,不然怎么会回收页面
    • 所以当这个块回收以后就变成一个空闲,一个已分配,那么index对应的位肯定要变1,符合翻转后的逻辑
    • 这里index对应的位如果原来是1,说明下面两个相邻块一个空闲,一个已分配
    • 那么这个块回收以后就变成都是空闲了,可以继续下面的合并块操作

伙伴块查找和验证

		/* Move the buddy up one level. */buddy1 = base + (page_idx ^ (1 << order));buddy2 = base + page_idx;BUG_ON(bad_range(zone, buddy1));BUG_ON(bad_range(zone, buddy2));list_del(&buddy1->lru);
  • buddy1 = base + (page_idx ^ (1 << order)): 计算伙伴块的地址
    • 使用XOR运算切换第order位来找到伙伴
  • buddy2 = base + page_idx: 当前块的地址
  • BUG_ON(bad_range(zone, buddy1/buddy2): 验证伙伴块在有效范围内
  • list_del(&buddy1->lru): 从当前order的空闲链表中删除伙伴块

升级到下一级

		mask <<= 1;order++;area++;index >>= 1;page_idx &= mask;}
  • mask <<= 1: 掩码左移一位,扩大对齐范围
  • order++: 提升到更大的order级别
  • area++: 移动到下一级的free_area
  • index >>= 1: 位图索引减半(因为块大小加倍)
  • page_idx &= mask: 对齐页面索引到新的order级别

最终插入空闲链表

	list_add(&(base + page_idx)->lru, &area->free_list);
}
  • 将合并后的块(或原始块)插入对应order的空闲链表

测试位并翻转__test_and_change_bit

static inline int __test_and_change_bit(int nr, volatile unsigned long *addr)
{int oldbit;__asm__ __volatile__("btcl %2,%1\n\tsbbl %0,%0":"=r" (oldbit),"=m" (ADDR):"Ir" (nr) : "memory");return oldbit;
}

函数功能概述

__test_and_change_bit 原子地测试指定位的当前值,然后翻转该位(0变1,1变0),并返回翻转前的旧值

代码逐段详解

函数声明和变量

static inline int __test_and_change_bit(int nr, volatile unsigned long *addr)
{int oldbit;
  • static inline: 内联函数,避免函数调用开销
  • int nr: 要操作的位号(0-31等)
  • volatile unsigned long *addr: 指向位图的内存地址
  • int oldbit: 存储操作前的位值

汇编指令

		"btcl %2,%1\n\tsbbl %0,%0"

第一条:btcl %2,%1

  • btc: Bit Test and Complement(位测试并取反)
  • l: Long操作(32位)
  • %2: 第三个操作数(nr - 位号)
  • %1: 第二个操作数(*addr - 位图地址)
  • 功能: 测试第nr位的值(存入CF标志位),然后翻转该位

第二条:sbbl %0,%0

  • sbb: Subtract with Borrow(带借位减法)
  • l: Long操作
  • %0,%0: 对第一个操作数(oldbit)自身相减
  • 功能: oldbit = oldbit - oldbit - CF
    • 等价于 oldbit = -CF
    • 如果CF=1,结果就是-1
    • 如果CF=0,结果就是0

输出操作数

		:"=r" (oldbit),"=m" (ADDR)
  • "=r" (oldbit): 输出操作数0
    • =r 表示输出到通用寄存器
    • 结果存储到oldbit变量
  • "=m" (ADDR): 输出操作数1
    • =m 表示输出到内存
    • ADDR 是位图地址

输入操作数

		:"Ir" (nr) : "memory");
  • "Ir" (nr): 输入操作数2
    • I 表示立即数
    • r 表示寄存器
    • nr 是位号参数
  • "memory": 编译器内存屏障
    • 告诉编译器内存可能被修改,不要缓存寄存器中的值
	return oldbit;
}
  • 返回操作前的位值(1或0)

分配页并拆分__rmqueue/expand

static struct page *__rmqueue(struct zone *zone, unsigned int order)
{struct free_area * area;unsigned int current_order;struct page *page;unsigned int index;for (current_order = order; current_order < MAX_ORDER; ++current_order) {area = zone->free_area + current_order;if (list_empty(&area->free_list))continue;page = list_entry(area->free_list.next, struct page, lru);list_del(&page->lru);index = page - zone->zone_mem_map;if (current_order != MAX_ORDER-1)MARK_USED(index, current_order, area);zone->free_pages -= 1UL << order;return expand(zone, page, index, order, current_order, area);}return NULL;
}
static inline struct page *
expand(struct zone *zone, struct page *page,unsigned long index, int low, int high, struct free_area *area)
{unsigned long size = 1 << high;while (high > low) {area--;high--;size >>= 1;BUG_ON(bad_range(zone, &page[size]));list_add(&page[size].lru, &area->free_list);MARK_USED(index + size, high, area);}return page;
}
#define MARK_USED(index, order, area) \__change_bit((index) >> (1+(order)), (area)->map)
static inline void __change_bit(int nr, volatile unsigned long * addr)
{__asm__ __volatile__("btcl %1,%0":"=m" (ADDR):"Ir" (nr));
}

函数功能概述

__rmqueue 是伙伴系统的核心分配函数,负责从指定order开始查找可用的内存块,如果找到的块比请求的大,就通过expand将其拆分。

__rmqueue 函数详解

变量声明和初始化

static struct page *__rmqueue(struct zone *zone, unsigned int order)
{struct free_area * area;unsigned int current_order;struct page *page;unsigned int index;
  • zone: 目标内存区域
  • order: 请求的内存块大小级别
  • area: 当前搜索的区域阶层指针
  • current_order: 当前搜索的order级别
  • page: 找到的内存页面
  • index: 页面在zone中的索引

搜索循环

	for (current_order = order; current_order < MAX_ORDER; ++current_order) {area = zone->free_area + current_order;if (list_empty(&area->free_list))continue;
  • 从请求的order开始,向更大块搜索
  • area = zone->free_area + current_order: 获取对应order的free_area
  • list_empty(&area->free_list): 检查该order是否有空闲块
  • 如果没有,继续搜索更大的块

找到可用块

		page = list_entry(area->free_list.next, struct page, lru);list_del(&page->lru);index = page - zone->zone_mem_map;
  • list_entry(area->free_list.next, struct page, lru): 从链表头部获取页面
  • list_del(&page->lru): 从空闲链表中删除该页面
  • index = page - zone->zone_mem_map: 计算页面在zone中的索引

更新位图和计数器

		if (current_order != MAX_ORDER-1)MARK_USED(index, current_order, area);zone->free_pages -= 1UL << order;return expand(zone, page, index, order, current_order, area);}return NULL;
}
  • MARK_USED(index, current_order, area): 标记该块在位图中为已使用
  • zone->free_pages -= 1UL << order: 减少zone的空闲页面计数
  • expand(zone, page, index, order, current_order, area): 如果找到的块比请求的大,进行拆分

MARK_USED 和 __change_bit 详解

MARK_USED 宏

#define MARK_USED(index, order, area) \__change_bit((index) >> (1+(order)), (area)->map)
  • 计算位图位置: (index) >> (1+(order))
    • (area)->map的位表示两个相邻块是否都分配或者空闲
    • 这里在order层级分配了一个块出去,那么就需要到上一层级的对应位进行翻转
    • 如果之前为1,表示一个块分配,一个块空闲
    • 现在分配了一个块,说明两个相邻块都分配了,就可以翻转为0了,反之亦然
  • 调用__change_bit翻转指定位

__change_bit 函数

static inline void __change_bit(int nr, volatile unsigned long * addr)
{__asm__ __volatile__("btcl %1,%0":"=m" (ADDR):"Ir" (nr));
}
  • btcl %1,%0: Bit Test and Complement Long
    • 测试并翻转指定位
    • %1是位号(nr),%0是位图地址(addr)
  • 原子操作,不需要返回值

expand 函数详解

函数参数和初始化

static inline struct page *
expand(struct zone *zone, struct page *page,unsigned long index, int low, int high, struct free_area *area)
{unsigned long size = 1 << high;
  • low: 请求的order
  • high: 找到的块的order
  • size: 当前块的大小(页面数)

拆分循环

	while (high > low) {area--;high--;size >>= 1;
  • high > low: 当找到的块比需要的大时继续拆分
  • area--: 移动到更低order的free_area
  • high--: 降低order级别
  • size >>= 1: 块大小减半

处理后半部分块

		BUG_ON(bad_range(zone, &page[size]));list_add(&page[size].lru, &area->free_list);MARK_USED(index + size, high, area);}return page;
}
  • BUG_ON(bad_range(zone, &page[size])): 验证块范围有效性
  • list_add(&page[size].lru, &area->free_list): 将后半块加入空闲链表
  • MARK_USED(index + size, high, area): 对上一层级的指定位进行翻转
  • 返回前半块给调用者

关键设计要点

  1. 最佳适配: 从请求大小开始搜索,找到第一个可用的块
  2. 块拆分: 将大块拆分成需要的大小,剩余部分放回空闲链表
  3. 位图管理: 精确跟踪每个块的使用状态
http://www.dtcms.com/a/553674.html

相关文章:

  • 33.点赞功能
  • 网站怎么快速做排名个人在线免费公司注册
  • 微信官网网站模板百度站长平台网页版
  • 黑马点评学习笔记03(Hutool工具库)
  • 太原模板建站系统无障碍网站建设标准
  • 企业级SQL审核优化工具 PawSQL 介绍(3)- 审核工单管理
  • XML文档差异分析工具:深入解析Word XML结构变化
  • 门户网站域名长治房产网站建设
  • 神经网络如何预测仓库拥堵?
  • 基于EasyExcel的动态列映射读取方案
  • 非凡免费建网站平台大型网站开发项目合同
  • 瑞芯微RK35xx升级RGA驱动版本和API版本
  • C++ 与 Go 相互调用实践指南:基于 SWIG + Director 模式
  • 做音乐的网站设计网站版权设置
  • SAP ABAP 视图表/表 表维护视图字段更新
  • 汇编、反汇编和机器码
  • 网站做跳转链接馆陶网站推广
  • 逻辑回归正则化解释性实验报告:L2 正则对模型系数收缩的可视化分析
  • LeetCode 412 - Fizz Buzz
  • 大型门户网站建设特点怎么知道一个网站是谁做的
  • IDC报告:阿里云市场份额升至26.8%,连续5季度上涨
  • 佛山房地产网站建设51建模网官方网站
  • 字格格子模板合集:多样练字格硬笔书法训练模板(可打印)
  • Arduino ESP32-C3 串口使用注意事项
  • MCP是什么及如何开发一个MCPServer
  • 程序逆向分析
  • 卷绕设备与叠片设备
  • 个人可以做购物网站吗西安曲江文化园区建设开发有限公司网站
  • 网站当前链接深圳坪地网站建设 自助建站 五合一建站平台
  • GD32F407VE天空星开发板的ADC按键(ADKey)的实现