深入了解物理内存管理 - 伙伴 (Buddy) 算法

用户头像
ShenDu_Linux
关注
发布于: 2020 年 11 月 30 日
深入了解物理内存管理-伙伴(Buddy)算法

一、伙伴关系



伙伴关系的定义为:由一个母实体分成的两个各方面属性一致的两个子实体,这两个子实体就处于伙伴关系。在操作系统分配内存的过程中,一个内存块经常被分成两个大小相等的内存块,这两个大小相等的内存块就处于伙伴关系。它满足3个条件:两个块具有相同大小;物理地址是连续的;从同一个大块中拆分出来。



二、伙伴算法的实现原理



为了便于页面的维护,将多个页面组成内存块,每个内存块都有2的方幂个页,方幂的指数被称为阶。在操作内存时,经常将这些内存块分成大小相等的两个块,分成的两个内存块被称为伙伴块,采用一位二进制数来表示它们的伙伴关系。当这个位为1,表示其中一块在使用;当这个位为0,表示两个页面块都空闲或者都在使用。系统根据该位为0或位为1来决定是否使用或者分配该页面块。系统每次分配和回收伙伴块时都要对它们的伙伴位跟1进行异或运算。所谓异或是指刚开始时,两个伙伴块都空闲,它们的伙伴位为0,如果其中一块被使用,异或后得1;如果另一块也被使用,异或后得0;如果前面一块回收了异或后得1;如果另一块也回收了异或后得0。



三、Buddy算法的优缺点



1)尽管伙伴内存算法在内存碎片问题上已经做的相当出色,但是该算法中,一个很小的块往往会阻碍一个大块的合并,一个系统中,对内存块的分配,大小是随机的,一片内存中仅一个小的内存块没有释放,旁边两个大的就不能合并。





2)算法中有一定的浪费现象,伙伴算法是按2的幂次方大小进行分配内存块,当然这样做是有原因的,即为了避免把大的内存块拆的太碎,更重要的是使分配和释放过程迅速。但是他也带来了不利的一面,如果所需内存大小不是2的幂次方,就会有部分页面浪费。有时还很严重。比如原来是1024个块,申请了16个块,再申请600个块就申请不到了,因为已经被分割了。



3)另外拆分和合并涉及到 较多的链表和位图操作,开销还是比较大的。





四、Buddy(伙伴的定义)



这里给出伙伴的概念,满足以下三个条件的称为伙伴:



  • 1)两个块大小相同;

  • 2)两个块地址连续;

  • 3)两个块必须是同一个大块中分离出来的;



五、Buddy算法的分配原理



假如系统需要4(22)个页面大小的内存块,该算法就到free_area[2]中查找,如果链表中有空闲块,就直接从中摘下并分配出去。如果没有,算法将顺着数组向上查找free_area[3],如果free_area[3]中有空闲块,则将其从链表中摘下,分成等大小的两部分,前四个页面作为一个块插入free_area[2],后4个页面分配出去,free_area[3]中也没有,就再向上查找,如果free_area[4]中有,就将这16(2222)个页面等分成两份,前一半挂如free_area[3]的链表头部,后一半的8个页等分成两等分,前一半挂free_area[2]的链表中,后一半分配出去。假如free_area[4]也没有,则重复上面的过程,知道到达free_area数组的最后,如果还没有则放弃分配。





六、Buddy算法的释放原理



内存的释放是分配的逆过程,也可以看作是伙伴的合并过程。当释放一个块时,先在其对应的链表中考查是否有伙伴存在,如果没有伙伴块,就直接把要释放的块挂入链表头;如果有,则从链表中摘下伙伴,合并成一个大块,然后继续考察合并后的块在更大一级链表中是否有伙伴存在,直到不能合并或者已经合并到了最大的块(222222222个页面)。





整个过程中,位图扮演了重要的角色,如图2所示,位图的某一位对应两个互为伙伴的块,为1表示其中一块已经分配出去了,为0表示两块都空闲。伙伴中无论是分配还是释放都只是相对的位图进行异或操作。分配内存时对位图的



是为释放过程服务,释放过程根据位图判断伙伴是否存在,如果对相应位的异或操作得1,则没有伙伴可以合并,如果异或操作得0,就进行合并,并且继续按这种方式合并伙伴,直到不能合并为止。



七、Buddy内存管理的实现



提到buddy 就会想起linux 下的物理内存的管理 ,这里的memory pool 上实现的 buddy 系统



和linux 上按page 实现的buddy系统有所不同的是,他是按照字节的2的n次方来做block的size



实现的机制中主要的结构如下:



整个buddy 系统的结构:



struct mem_pool_table
{
#define MEM_POOL_TABLE_INIT_COOKIE (0x62756479)
uint32 initialized_cookie; /* Cookie 指示内存已经被初始化后的魔数, 如果已经初始化设置为0x62756479*/
uint8 *mem_pool_ptr;/* 指向内存池的地址*/
uint32 mem_pool_size; /* 整个pool 的size,下面是整个max block size 的大小*/
uint32 max_block_size ; / * 必须是2的n次方 ,表示池中最大块的大小 * /
boolean assert_on_empty ; / * 如果该值被设置成TRUE ,内存分配请求没有完成就返回 并输出出错信息 * /
uint32 mem_remaining ; / * 当前内存池中剩余内存字节数 * /
uint32 max_free_list_index ; / * 最大freelist 的下标 , * /
struct mem_free_hdr_type *free_lists [MAX_LEVELS ] ; / * 这个就是伙伴系统的level数组 * /

#ifdef FEATURE_MEM_CHECK
uint32 max_block_requested ;
uint32 min_free_mem ; / * 放mem_remaining * /
#endif / * FEATURE_ONCRPC_MEM_CHECK * /
} ;
1234567891011121314151617



这个结构是包含在free node 或alloc node 中的结构:



其中check 和 fill 都被设置为某个pattern 用来检查该node 的合法性



#define MEM_HDR_CHECK_PATTERN ((uint16)0x3CA4) #define MEM_HDR_FILL_PATTERN ((uint8)0x5C)



typedef struct tagBuddyMemBlockHeadType

{
mem_pool_type pool; /*回指向内存池*/
uint16 check;
uint8 state; /* bits 0-3 放该node 属于那1级 bit 7 如果置1,表示已经分配(not free)
uint8 fill;
} BUDDY_MEM_BLOCK_HEAD_TYPE;
12345678



这个结构就是包含node 类型结构的 free header 的结构:



typedef struct tagBuddyMemHeadType
{
mem_node_hdr_type hdr;
struct mem_free_hdr_type * pNext; /* next,prev,用于连接free header的双向 list*/

struct mem_free_hdr_type * pPrev;
} mem_free_hdr_type;
1234567



这个结构就是包含node 类型结构的 alloc header 的结构:



已分配的mem 的node 在内存中就是这样表示的



typedef struct mem_alloc_hdr_type

{
mem_node_hdr_type hdr;

#ifdef FEATURE_MEM_CHECK_OVERWRITE
uint32 in_use_size;
#endif

} mem_alloc_hdr_type;
12345678910



其中用in_use_size 来表示如果请求分配的size 所属的level上实际用了多少

比如申请size=2000bytes, 按size to level 应该是2048,实际in_use_size

为2000,剩下48byte 全部填充为某一数值,然后在以后free 是可以check

是否有overwite 到着48byte 中的数值,一般为了速度,只 检查8到16byte



另外为什么不把这剩下的48byte 放到freelist 中其他level 中呢,这个可能



因为本来buddy 系统的缺点就是容易产生碎片,这样的话就更碎了



关于free or alloc node 的示意图:



假设:最小块为2^4=16,着是由mem_alloc_hdr_type (12byte)决定的, 实际可分配4byte



如果假定最大max_block_size =1024,如果pool 有mem_free_hdr_type[0]上挂了两个1024的block node,上图是free node, 下图紫色为alloc node





接下来主要是buddy 系统的操作主要包括pool init , mem alloc ,mem free



pool init :



  1. 将实际pool 的大小去掉mem_pool_table 结构大小后的size 放到mem_pool_size, 并且修改实际mem_pool_ptr指向前进mem_pool_table结构大小的地址

  2. 接下来主要将mem_pool_size 大小的内存,按最大块挂到free_lists 上level 为0的list 上,然后小于该level block size 部分,继续挂大下一级,循环到全部处理完成 (感觉实际用于pool的size ,应该为减去mem_pool_table 的大小,然后和最大块的size 对齐,这样比较好,但没有实际测试过)。



mem alloc:



这部分相当简单,先根据请求mem的size ,实际分配时需要加上mem_alloc_hdr_type这12byte ,然后根据调整后的size,计算实际应该在那个 level上分配,如果有相应级很简单,直接返回,如果没有,一级一级循环查找,找到后,把省下的部分,在往下一级一级插入到对应级的freelist 上。



mem free:



其中free 的地址,减去12 就可以获得mem_alloc_hdr_type 结构,然后确定buddy 在该被free block 前,还是后面, 然后合并buddy,循环寻找上一级的buddy ,有就再合并,只到最大block size 那级,关于这个算法,在<> vol 1,的,动态存储分配中有描述,对于那些只有OSAL 的小系统,该算法相当有用。





发布于: 2020 年 11 月 30 日阅读数: 27
用户头像

ShenDu_Linux

关注

还未添加个人签名 2020.11.26 加入

还未添加个人简介

评论 (1 条评论)

发布
用户头像
666
2020 年 11 月 30 日 17:00
回复
没有更多了
深入了解物理内存管理-伙伴(Buddy)算法