伙伴(buddy)算法浅析
1、伙伴算法引入
内核频繁请求和释放不同大小的一组连续页框,必然会导致在已经分配的块内分散了许多小块的空闲页框,带来的问题是,及时有足够的空闲页框可以满足请求,但是要分配一大块连续页框就无法满足,所以内核应该为分配一组连续的页框而建立一种健壮高效的分配策略。伙伴算法就是基于此。
2、伙伴算法的思想
linux把所有空闲页框分组为11个块链表,每个链表上的页框块是固定的,第i条链表中,每个页框块都包含2i个连续页,其中i称为分配阶,
假设要申请28个页,先从28即256个页框中查找空闲块,没有就去512个页框的链表中找,找到了则将页框分为2个256个页框的块,一个分配给应用,一个移到256个页框的链表中去,如果512个页框仍没有空闲块,继续向1024个页框链表查找,如果存在空闲块,则将其中256页框作为请求返回 ,剩余的768分成256和512分别插到相应的链表中,如果仍然没有,则返回错误。
3、伙伴算法在内核中的数据结构
struct zone{struct free_area freearea[MAX_ORDER];};//其中,define内核中为#define MAX_ORDER 11 //即上文中提到的11个块链表
free_area[]数组是一个struct free_area的结构体,其在内核中的定义为:
struct free_area
{struct list_head free_list[MIGRATE_TYPES];/*说明了页的属性*/unsigned long nr_free;/*来说明每个介中有多少个自由的页*/
};
在这个函数中有两个域,第一个是free_list,是个链表,它表示的就是当前分配阶所对应的页框块的链表。第二个nr_free指的是当前链表中空闲页框块的数目,比如free_area[2]中nr_free的值为5,表示有5个大小为4的页框块,那么总的空闲页为20.
对于free_list中的MIGRATE_TYPES表示的是迁移页的类型,在内核中的定义为:
#define MIGRATE_UNMOVABLE 0 //不可移动页:在内存中有固定位置,不能移动到其他地方
#define MIGRATE_RECLAIMABLE 1 //可回收页:不能直接移动,但可以删除,其内容可以从某些源重新生成
#define MIGRATE_MOVABLE 2 //可移动页:可以随意的移动
#define MIGRATE_RESERVE 3 //如果向具有特定可移动性地列表请求分配内存失败,这种紧急情况下可以从MIGRATE_RESERVE分配内存
#define MIGRATE_ISOLATE 4 //是一个特殊的虚拟区域,用于跨域NUMA结点移动物理内存页,在大型系统上,它有益于将物理内存页移动到接近于是用该页最频繁的CPU
#define MIGRATE_TYPES 5 //只是表示迁移类型的数目,不代表具体的区域
4.伙伴算法在内核分配页框时的实现
内核中分配页框的函数入口是:alloc_pages函数:
#define alloc_pages(gfp_mask, order) \alloc_pages_node(numa_node_id(), gfp_mask, order)
lloc_pages_node()函数有三个参数:numa_node_id这个参数下文讲,gfp_mask这个参数指的是分配器的标志,也就是说分配的内存块所具有的属性(如果还是不懂,那就看看Linux内核设计与实现这本书,这里姑且认为是分配的内存块的属性),order指的是伙伴中的阶数。
介绍下numa_node_id
从硬件角度看,存在两种机器,uma和numa
uma多个cpu共享一个内存,numa,每个cpu有自己本地内存,然后处理器通过总线连接起来,进而可以访问其他cpu的本地内存。
(在多核系统中,如果物理内存对所有CPU来说没有区别,每个CPU访问内存的方式也一样,则这种体系结构被称为Uniform Memory Access(UMA)。如果物理内存是分布式的,由多个cell组成(比如每个核有自己的本地内存),那么CPU在访问靠近它的本地内存的时候就比较快,访问其他CPU的内存或者全局内存的时候就比较慢,这种体系结构被称为Non-Uniform Memory Access(NUMA)。)
Linux引入了一个概念称为node,一个node对应一个内存块,对于UMA系统,只有一个Node.其对应的数据结构为struct pglist_data.对于NUMA系统来讲,整个系统的内存由一个名为Node_data的struct pglist_data(page_data_t)指针数组来管理。
typedef struct pglist_data {int nr_zones;struct zone node_zones[MAX_NR_ZONES];struct zonelist node_zonelists[MAX_ZONELIST];unsigned long node_size;struct page *node_mem_map;int node_id;unsigned long node_start_paddr;struct pglist_data *node_next;spinlock_t lru_lock;...
} pg_data_t;
node_zones:当前节点中内存管理区的描述符数组,node_list;指的是zonelist结构的数组。
由以上结构体可以推断每个node中又被分成多个zone(区),每个zone对应一片内存区域。NUMA系统的内存划分如图:
enum zone_type {#ifdef CONFIG_ZONE_DMAZONE_DMA,#endif#ifdef CONFIG_ZONE_DMA32ZONE_DMA32,#endifZONE_NORMAL,#ifdef CONFIG_HIGHMEMZONE_HIGHMEM,#endifZONE_MOVABLE,__MAX_NR_ZONES
};
ZONE_DMA:可用作DMA的内存区域。该类型的内存区域在物理内存的低端,主要是ISA设备只能用低端的地址做DMA操作。
ZONE_NORMAL:直接被内核直接映射到自己的虚拟地址空间的地址。
ZONE_HIGHMEM:不能被直接映射到内核的虚拟地址空间的地址。
ZONE_MOVABLE:伪zone,在防止物理内存碎片机制中使用
MAX_NR_ZONES:结束标记
很显然,当我们在分配内存时,是按区进行分配的,一般我们会从zone_normal这个区上申请。
4.1 __alloc_pages()函数
追踪内核代码,进入alloc_pages()函数后,发现调用的是__alloc_pages()函数
static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask,unsigned int order)
{/* Unknown node is current node */if (nid < 0)nid = numa_node_id();return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask));
}
我们发现其调用的是__alloc_pages()函数,在这个函数中调用了node_zonelist()函数,这个函数会根据节点号找到对应的zone,正好验证了我们上边说的:每个node又分成了好多个区,我们的内存其实是从具体的区上拿的。
4.2 __alloc_pages_nodemask()函数
在__alloc_pages函数中没有做任何的事情,就进入了__alloc_pages_nodemask()函数:
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,struct zonelist *zonelist, nodemask_t *nodemask)
{enum zone_type high_zoneidx = gfp_zone(gfp_mask);struct zone *preferred_zone;struct page *page;int migratetype = allocflags_to_migratetype(gfp_mask);gfp_mask &= gfp_allowed_mask;lockdep_trace_alloc(gfp_mask);might_sleep_if(gfp_mask & __GFP_WAIT);if (should_fail_alloc_page(gfp_mask, order))return NULL;/** Check the zones suitable for the gfp_mask contain at least one* valid zone. It's possible to have an empty zonelist as a result* of GFP_THISNODE and a memoryless node*/if (unlikely(!zonelist->_zonerefs->zone))return NULL;/* The preferred zone is used for statistics later */first_zones_zonelist(zonelist, high_zoneidx, nodemask, &preferred_zone);if (!preferred_zone)return NULL;/* First allocation attempt核心函数 */page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order,zonelist, high_zoneidx, ALLOC_WMARK_LOW|ALLOC_CPUSET,preferred_zone, migratetype);if (unlikely(!page))page = __alloc_pages_slowpath(gfp_mask, order,zonelist, high_zoneidx, nodemask,preferred_zone, migratetype);trace_mm_page_alloc(page, order, gfp_mask, migratetype);return page;
}
在这个函数中,首先,gfp_zone()会根据gfp_mask选取适当类型的zone.下面接着是几项参数的检测以及安全性的检测。然后通过zonelist->_zonerefs->zone判断zone是否为空,由unlike知道,一般情况下不会是空。接着是first_zones_zonelist()函数来确定在该zone中优先分配内存的内存管理区。可以发现核心的分配的函数是在get_page_from_freelist()这个函数中。
4.3 get_page_from_freelist
这个函数可以看作是计入伙伴算法的前置函数,它通过传递进来的分配标志和分配阶,寻找适合的区(zone),如果找到了满足条件的区,则进行伙伴算法,进行分配。我们来看下这个函数的主要功能:
首先是通过函数for_each_zone_zonelist_nodemask()函数来遍历zonelist
for_each_zone_zonelist_nodemask(zone, z, zonelist,high_zoneidx, nodemask) {if (NUMA_BUILD && zlc_active &&!zlc_zone_worth_trying(zonelist, z, allowednodes))/*检查zlc(zone list cache),当前zone是否有freepage*/continue;if ((alloc_flags & ALLOC_CPUSET) && /*当前分配标志不允许在该管理区中分配页面*/!cpuset_zone_allowed_softwall(zone, gfp_mask))goto try_next_zone;BUILD_BUG_ON(ALLOC_NO_WATERMARKS < NR_WMARK);if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {unsigned long mark;int ret;mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];if (zone_watermark_ok(zone, order, mark,/*如果freepage够,开始伙伴算法*/classzone_idx, alloc_flags))goto try_this_zone;
首先是对zlc(zone list cache)的检测,检测当前zone是否有足够的freepage,如果没有,直接continue,进入下一个zone .
接着是对分配标志位的一个检测,检查当前分配标志是否允许在该管理区中分配页面。接着是对当前zone的一个水位线的获取,这个水位线是什么呢?来看下内核的定义:
enum zone_watermarks {WMARK_MIN, //说明当前可以用的内存已经达到最低限度了WMARK_LOW, //可用内存很少了,但是还没有最底线,不是很紧急的情况下,就不要占用内存了WMARK_HIGH, //剩余的内存还有很多,大家放心使用吧NR_WMARK
};
可以知道,水位线其实就是个标志,标志这个区域的内存的剩余量情况。那么zone_watermark_ok函数的作用就很明显了,就是检查这个区域的水位线是否有足够的free_page.如果够的话,就直接try_this_zone,进入我们的伙伴算法。
继续跟着我们的代码走:
4.4 buffered_rmqueue伙伴算法入口函数
4.5 __rmqueue_smallest函数
4.6 expand()函数
总结
https://www.cnblogs.com/wangzahngjun/p/4943518.html
伙伴(buddy)算法浅析相关推荐
- 操作系统学习之用C语言模拟伙伴(Buddy)算法
前言 学到了操作系统的的虚拟内存部分,硬件不太好的我学起来有些吃力,概念性知识点太多,所以我决定用软件的方式,实现一下虚拟内存常用的算法,因为用到了指针,暂时用C语言写一下Buddy算法.FIFO算法 ...
- 内存算法-伙伴(buddy)算法
伙伴(buddy)算法,它不能根据需要从被管理内存的开头部分创建新内存.它有明确的共性,就是各个内存块可分可合,但不是任意的分与合.每个块都有个朋友,或叫"伙伴",既可与之分开,又 ...
- fat32文件系统的实现与buddy算法
报告一 FAT32文件系统的实现 文件系统(File System)是计算机系统必不可少的组成部分,可以说除了部分结构简单的单片机系统之外,文件系统是支撑每一个计算机系统运行的最重要的支撑,无论 ...
- 卡尔曼滤波器求速度matlab,卡尔曼滤波器算法浅析及matlab实战
原标题:卡尔曼滤波器算法浅析及matlab实战 作者:Liu_LongPo 出处:Liu_LongPo的博客 卡尔曼滤波器是一种利用线性系统状态方程,通过系统输入输出观测数据,对系统状态进行最优估计的 ...
- SIFT(尺度不变特征变换)算法浅析
SIFT(尺度不变特征变换)算法浅析 SIFT简介 SIFT,即尺度不变特征变换(Scale-invariant feature transform,SIFT),是用于图像处理领域的一种算法,这是一种 ...
- Paxos 算法浅析
Paxos 算法浅析 xiewen 关注 2016.07.19 17:24* 字数 3613 阅读 3740评论 0喜欢 22 持续更新 如何浅显易懂地解说 Paxos 的算法? 参考资料 #8:知 ...
- buddy内存分配算法浅析
因为今天遇到这个问题,所以上网搜了下,看了觉得还是很有用处,便写了这篇博文. buddy内存分配算法技术是一种内存分配算法,将内存划分分区,试图以适当地满足内存请求.buddy内存分配算法是比较容易实 ...
- c语言数据交换的算法流程图,C语言冒泡排序算法浅析
C语言泡排冒序算浅析 法中刘旭 ( 江师范丽等专高学科校数与计算机学科学系 ) [ 摘]要泡冒排序算法 C语言常见是排序算法之,一该算法的优点 逻辑是清晰,代码简洁,点缺是时复杂度间高较本文介.绍了统 ...
- 【算法浅析NO.00004】递归算法浅析(un-accomplished version) by arttnba3
递归算法浅析-(un-accomplished version) 0x00.绪论 0x01.什么是递归(recursion)? 0x02.递归算法的简单应用-part1 一.求阶乘 二.汉诺塔问题 三 ...
最新文章
- UA MATH523A 实分析1 集合论基础6 一些点集拓扑基本概念
- Stack Pointer Tracker
- C0301 代码块{}的使用,重定向, 从文件中读取行
- 【译】Tablix指南----通向报表服务的阶梯系列(四)
- android正则判断两个符号之间,Android字母、数字、字符任意两种组合正则验证
- 数据结构上机测试2-1:单链表操作A
- Windows10安装Jmeter(图文教程)
- AE+C#实现:在SceneControl里打开和保存
- VisualStudio2022创建.ASP.NET应用程序
- bp神经网络回归预测模型(python实现)_bp神经网络预测代码python
- Unable to read entire header; 80 bytes read; expected 512 bytes
- mac的mysql关机后打不开了_mysql for mac服务无法启动
- 在ipad上播放flash大集合
- java.lang.NoSuchMethodException: tk.mybatis.mapper.provider.SpecialProvider 使用MySqlMapper的问题
- 《剑指Offer》力扣刷题笔记(03-10)
- macOS 安装 aria2 下载 BitTorrent 替代迅雷
- 阿里开源消息中间件MetaQ(RocketMQ)简介
- 如何查阅NLP资料 转自https://blog.csdn.net/qq_27009517/article/details/80841146
- 《做最好的员工》第一章:好员工才会成功
- 支付接口——WeChat / Alipay