buddy算法是内核中比较古老的一个模块,很好的解决了相邻物理内存碎片的问题即"内碎片问题”,同时有兼顾内存申请和释放效率问题,内核从引入该算法之后一直都能够在各种设备上完好运行,证明了其良好性能及很好兼容行。

buddy核心思想

buddy算法思想核心如下(注意下面这段解释来自于《计算机程序设计艺术》卷1:基本算法 第三全 动态存储分配 一章中):

该方法的设计思想是,为每个尺寸(0km)分布维护可用列表,整个被分配的内存空间池由个字组成,可假定这些字的地址为0到-1。最初个字的整个块是可用的,然后,当需要字的块没有这样大小的可用块时,就把一个较大的可用块平均分成两部分;最终,将会出现一个大小刚好为的块,当一个块分成两块(每块都为原来的一半大小)时,这两块就称为伙伴(buddy)。当两个伙伴都再次可用时,它们又合并成一个块。因此,这个过程可以无限维持下去,直到某一时刻用完内存空间为止。

奠定这一方法实用价值基础的关键事实在于,如果知道一个块的地址(它的第一个字的内存位置)和大小,就知道其伙伴的地址。例如,已知一个大小为16的块是从二进制位置101110010110000开始,则它的伙伴是从二进制位置1011100100000开始的块。为什么?我们首先观察到,当算法进行时,大小为的块地址时是 的倍数,也就是说,地址的二进制表示在右边至少K个0. 

在linux中对于物理内存的管理最小单位为页 page,每个页都通过memmap数组进行管理(注意不同内存模型 memmap数组形式稍微不一样),这样每个页的地址以及pfn是固定,如果知道一个块的地址(它的第一个字的内存位置)和大小,就知道其伙伴的地址,从而提高分配和释放效率。buddy算法被完美应用到物理内存管理中,极大提高整个内存效率。

buddy管理架构

linux 内核中buddy整个管理加入如下图所示:

buddy整个管理按照级别进行划分如下:

  • zone: buddy所管理的内存属于zone内,每个zone内的物理内都归free_ares数组进行管理。
  • struct free_are free_area[] : free_area数组负责管理zone内的所有物理内存,按照buffy算法进行管理。其中free_area数组下标index,作为相应的order,负责按照2的order次方个页page作为一个块进行管理,order最大为MAX_ORDER-1。struct free_are结构如下:
struct free_area {struct list_head   free_list[MIGRATE_TYPES];unsigned long      nr_free;
};
  • struct list_head    free_list[MIGRATE_TYPES]: 为了解决随着系统长时间运行,随机的内存碎片问题。内核又将同一个order内的内存按照内存类型MIGRATE_TYPES进行划分,同一个order的内存有不同的内存类型,主要分为MIGRATE_UNMOVABLE、MIGRATE_MOVABLE、MIGRATE_RECLAIMABLE等类型。
  • struct list_head    free_list: 通过链表将属于同一个oder且属于同一个类型的空闲内存用链表串联起来,用于分配物理内存。

buddy 初始化

整个buddy 初始化流程较长,整个过程如下:

--->start_kernel()--->setup_arch()--->mm_init()--->mem_init()--->memblock_free_all()--->memblock_free_all()--->free_low_memory_core_early()--->__free_memory_core()--->__free_pages_memory()--->memblock_free_pages()--->__free_pages_core()--->__free_pages()--->free_the_page()--->__free_pages_ok()--->free_one_pages()--->__free_one_page--->add_to_free_list()   /add_to_free_list_tai()

整体流程较长,但是只需要关注几个主要处理函数,就能对buddy有个清晰的了解。

关键处理函数

free_low_memory_core_early()

该函数可以认为是buddy初始化的一个入口流程,位于mm\memblock.c文件中,位于早期初始化内存流程:


static unsigned long __init free_low_memory_core_early(void)
{unsigned long count = 0;phys_addr_t start, end;u64 i;memblock_clear_hotplug(0, -1);for_each_reserved_mem_region(i, &start, &end)reserve_bootmem_region(start, end);/** We need to use NUMA_NO_NODE instead of NODE_DATA(0)->node_id*  because in some case like Node0 doesn't have RAM installed*  low ram will be on Node1*/for_each_free_mem_range(i, NUMA_NO_NODE, MEMBLOCK_NONE, &start, &end,NULL)count += __free_memory_core(start, end);return count;
}

该函数位于memblock中,由memblock发起,在系统较早起始阶段,遍历每个节点的memory 内存信息,获取到起始start和结束end 页, 调用__free_memory_core()进行一个核心的内存初始化。

__free_pages_memory()

__free_pages_memory() 该函数为一个比较重要的函数,函数源码如下:

static void __init __free_pages_memory(unsigned long start, unsigned long end)
{int order;while (start < end) {order = min(MAX_ORDER - 1UL, __ffs(start));while (start + (1UL << order) > end)order--;memblock_free_pages(pfn_to_page(start), start, order);start += (1UL << order);}
}
  • 该函数 主要是按照起始的start pfn循环初始化所有的物理页,一个比较关键的处理就是:order = min(MAX_ORDER - 1UL, __ffs(start)),该处理就是计算当前start 页对应的oder.
  • __ffs(start) 函数就是计算start 中的转成二进制对应的第一个为0的位置,例如如果是start 为384=0x180, 第一个为1的位置为7, 则算出0x180对齐的order为7,如果算出的order大于10,则最后与MAX_ORDER做比较,不能超过MAX_ORDER,得出真正的order为 当前start 所处于那个级别。
  • 在系统其中过程中可能刚开始start 不是MAX_ORDER对齐的,通过不断调整oder,最后得出每个start对应的order
  • 接下来调用memblock_free_pages(),通知处理【start, start + 2 的oder次方页】加入到对应的buddy管理中。

 __free_one_page

__free_one_page() 为将page 加入到buddy中关键处理函数,里面包含对附近buffy合并处理,同时该函数也是释放page的关键处理函数,处理流程如下:

  __free_one_page源码分析

需要结合  __free_one_page分析整个过程:


/** Freeing function for a buddy system allocator.** The concept of a buddy system is to maintain direct-mapped table* (containing bit values) for memory blocks of various "orders".* The bottom level table contains the map for the smallest allocatable* units of memory (here, pages), and each level above it describes* pairs of units from the levels below, hence, "buddies".* At a high level, all that happens here is marking the table entry* at the bottom level available, and propagating the changes upward* as necessary, plus some accounting needed to play nicely with other* parts of the VM system.* At each level, we keep a list of pages, which are heads of continuous* free pages of length of (1 << order) and marked with PageBuddy.* Page's order is recorded in page_private(page) field.* So when we are allocating or freeing one, we can derive the state of the* other.  That is, if we allocate a small block, and both were* free, the remainder of the region must be split into blocks.* If a block is freed, and its buddy is also free, then this* triggers coalescing into a block of larger size.** -- nyc*/static inline void __free_one_page(struct page *page,unsigned long pfn,struct zone *zone, unsigned int order,int migratetype, bool report)
{struct capture_control *capc = task_capc(zone);unsigned long uninitialized_var(buddy_pfn);unsigned long combined_pfn;unsigned int max_order;struct page *buddy;bool to_tail;max_order = min_t(unsigned int, MAX_ORDER, pageblock_order + 1);VM_BUG_ON(!zone_is_initialized(zone));VM_BUG_ON_PAGE(page->flags & PAGE_FLAGS_CHECK_AT_PREP, page);VM_BUG_ON(migratetype == -1);if (likely(!is_migrate_isolate(migratetype)))__mod_zone_freepage_state(zone, 1 << order, migratetype);VM_BUG_ON_PAGE(pfn & ((1 << order) - 1), page);VM_BUG_ON_PAGE(bad_range(zone, page), page);continue_merging:while (order < max_order - 1) {if (compaction_capture(capc, page, order, migratetype)) {__mod_zone_freepage_state(zone, -(1 << order),migratetype);return;}buddy_pfn = __find_buddy_pfn(pfn, order);buddy = page + (buddy_pfn - pfn);if (!pfn_valid_within(buddy_pfn))goto done_merging;if (!page_is_buddy(page, buddy, order))goto done_merging;/** Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,* merge with it and move up one order.*/if (page_is_guard(buddy))clear_page_guard(zone, buddy, order, migratetype);elsedel_page_from_free_list(buddy, zone, order);combined_pfn = buddy_pfn & pfn;page = page + (combined_pfn - pfn);pfn = combined_pfn;order++;}if (max_order < MAX_ORDER) {/* If we are here, it means order is >= pageblock_order.* We want to prevent merge between freepages on isolate* pageblock and normal pageblock. Without this, pageblock* isolation could cause incorrect freepage or CMA accounting.** We don't want to hit this code for the more frequent* low-order merging.*/if (unlikely(has_isolate_pageblock(zone))) {int buddy_mt;buddy_pfn = __find_buddy_pfn(pfn, order);buddy = page + (buddy_pfn - pfn);buddy_mt = get_pageblock_migratetype(buddy);if (migratetype != buddy_mt&& (is_migrate_isolate(migratetype) ||is_migrate_isolate(buddy_mt)))goto done_merging;}max_order++;goto continue_merging;}done_merging:set_page_order(page, order);if (is_shuffle_order(order))to_tail = shuffle_pick_tail();elseto_tail = buddy_merge_likely(pfn, buddy_pfn, page, order);if (to_tail)add_to_free_list_tail(page, zone, order, migratetype);elseadd_to_free_list(page, zone, order, migratetype);/* Notify page reporting subsystem of freed page */if (report)page_reporting_notify_free(order);
}

该函数的处理流程:

  • 获取当前系统所支持的最大max_order.
  • 判断当前page所属zone,是否已经初始化完毕,之后初始化完毕才能进行后续处理。
  • 对page flags进行判断,page flag主要分两个部分基础部分(小于NR_PAGEFLAGS)和扩展部分被(大于NR_PAGEFLAGS部分)(详细了解《linux内核那些事之struct page》,其中基础部分此时除了__PG_HWPOISON标记为之外,其他不能设置,如果设置说明page处于脏页还有数据未刷新到磁盘中,或者还有进程在引用等等场景,此时该page还不能释放直接返回错误。否则则继续下一步
  • page是否属于物理isolate隔离,如果不属于则更正zone 统计。
  • 接下来进入处理循环处理主要部分((order < max_order - 1)):该部分主要功能是查找page对应的buddy_pfn即寻找要加入的page 向对应的oder中是否有伙伴,如果有伙伴且伙伴处于空闲状态,则表明需要和该伙伴合并成更高一级的order,进行order+1,继续在更高一级的order查找是否还能够合并,如此循环,只要不能够合并或者达到MAX_ORDER为止。
  • 在上述循环合并过程中如果发现能合并则需要调用del_page_from_free_list函数将该buddy从当前order中删除,以便能够加入到更高一级order中去。
  • 上述循环一直合并到不能合并为止,或者合并后order已经等于max_order - 1。
  • 如果合并后order 等于max_order - 1,则需要对isolate pagelblock处理。
  • 最后进行合并处理,oder能够合并后的最终orer,将释放page 中调用add_to_free_list或者add_to_free_list_tail函数加入到对应order和migratetype类中的freelist中,由buddy系统进行管理 。

add_to_free_list

将当前空闲页加入到buddy中:

static inline void add_to_free_list(struct page *page, struct zone *zone,unsigned int order, int migratetype)
{struct free_area *area = &zone->free_area[order];list_add(&page->lru, &area->free_list[migratetype]);area->nr_free++;
}
  • 将该page 加入到对应的内存类型的free_list中,其中migratetype为从page的flag中取出,调用的是 get_pfnblock_migratetype(page, pfn)函数,具体对pageblock_flags可以见《linux内核那些事之pg_data_t、zone结构初始化》详细解释。
  • 此函数还有一个要特别注意到的是page->lru,针对page 不同的使用状态及用途lru会挂载不同的链表中,所以lru是一个复用结构成员,此地方为接触的lru第一个用途,当页面空闲是lru 挂载到free_list链表中。
  • area->nr_free++ 将对应的area中页计数 +1, 同样add_to_free_list_tail()处理思路相同

__find_buddy_pfn()

__find_buddy_pfn() 是在buddy算法中是一个常用的函数,主要是根据当前的page所处的order 计算出当前page的伙伴。

以大小32的块为例, 它具有形如xx...x0000的地址(其中x表示为0或者1);如果它被拆分,则新形成的伙伴块地址为xx...x00000和xx.xx10000,一般而言,令=地址为x,大小为的块的伙伴地址,有x + 或者x-  两种情即

上述这两种情况可以直接通过异或得出 。 

__find_buddy_pfn源码如下:

static inline unsigned long
__find_buddy_pfn(unsigned long page_pfn, unsigned int order)
{return page_pfn ^ (1 << order);
}

page_is_buddy

page_is_buddy()对是否是一个buffy 进行判断:

static inline bool page_is_buddy(struct page *page, struct page *buddy,unsigned int order)
{if (!page_is_guard(buddy) && !PageBuddy(buddy))return false;if (page_order(buddy) != order)return false;/** zone check is done late to avoid uselessly calculating* zone/node ids for pages that could never merge.*/if (page_zone_id(page) != page_zone_id(buddy))return false;VM_BUG_ON_PAGE(page_count(buddy) != 0, buddy);return true;
}

检查page 与buddy 是否是真正的伙伴,需要同时满足以下条件:

  • buddy 不能是hole 空洞,必须是真实存在的物理内存。
  • buddy 的真正物理内存存在,且处于空闲状态出现在buddy 系统中
  • page 和buffy必须处于同一个order中
  • 必须处于同一个zone中

只有以上条件全部满足才是真正buddy。

查看buddy 分布

可以通过/porc/buddyinfo 查看整个每个zone的 空闲页在buddy的分布状况:

参考资料

《计算机程序设计艺术 卷1:基本算法》(第3版)

linux内核那些事之buddy相关推荐

  1. linux内核那些事之buddy(慢速申请内存__alloc_pages_slowpath)(5)

    内核提供__alloc_pages_nodemask接口申请物理内存主要分为两个部分:快速申请物理内存get_page_from_freelist(linux内核那些事之buddy(快速分配get_p ...

  2. linux内核那些事之buddy(anti-fragment机制)(4)

    程序运行过程中,有些内存是短暂的驻留 用完一段时间之后就可以将内存释放以供后面再次使用,但是有些内存一旦申请之后,会长期使用而得不到释放.长久运行有可能造成碎片.以<professional l ...

  3. linux内核那些事之buddy(anti-fragment机制-steal page)(5)

    继<linux内核那些事之buddy(anti-fragment机制)(4)>,在同一个zone内指定的migrate type中没有足够内存,会启动fallback机制,从fallbac ...

  4. linux内核那些事之pg_data_t、zone结构初始化

    free_area_init 继续接着<linux内核那些事之ZONE>,分析内核物理内存初始化过程,zone_sizes_init()在开始阶段主要负责对各个类型zone 大小进行计算, ...

  5. linux内核那些事之Sparse vmemmap

    <inux内核那些事之物理内存模型之SPARCE(3)>中指出在传统的sparse 内存模型中,每个mem_section都有一个属于自己的section_mem_map,如下图所示: 而 ...

  6. linux内核那些事之mmap_region流程梳理

    承接<linux内核那些事之mmap>,mmap_region()是申请一个用户进程虚拟空间 并根据匿名映射或者文件映射做出相应动作,是实现mmap关键函数,趁这几天有空闲时间 整理下mm ...

  7. linux内核那些事之struct page

    struct page page(页)是linux内核管理物理内存的最小单位,内核将整个物理内存按照页对齐方式划分成千上万个页进行管理,内核为了管理这些页将每个页抽象成struct page结构管理每 ...

  8. linux内核那些事之ZONE

    struct zone 从linux 三大内存模型中可以了解到,linux内核将物理内存按照实际使用用途划分成不同的ZONE区域,ZONE管理在物理内存中占用重要地位,在内核中对应的结构为struct ...

  9. linux内核那些事之物理内存模型之FLATMEM(1)

    linux内核中物理内存管理是其中比较重要的一块,随着内核从32位到64位发展,物理内存管理也不断进行技术更新,按照历史演进共有FLATMEM.DISCONTIGMEM以及SPRARSEMEM模型.( ...

最新文章

  1. OAuth认证协议原理分析及使用方法
  2. 2018年第九届省赛C/C++A组第4题——第几个幸运数
  3. 数据库BCP命令导入导出数据
  4. Linux目录遍历实现,列出目录下文件,可使用部分参数
  5. 2017-06-27
  6. Python中面向对象初识到进阶
  7. python-gui-pyqt5的使用方法-7--partial 传递参数的方法:
  8. SpringBoot验证码
  9. linux usb挂载日志,linux系统usb挂载
  10. Full CAN与Basic CAN主要区别
  11. 本人使用的IDEA插件截图
  12. 计算机音乐数字乐谱星星点灯,星星点灯-郑智化-和弦谱-《弹吧》官网tan8.com-和弦谱大全,学吉他,秀吉他...
  13. dreamweaver cs5 注册码及防激活
  14. 【数据库】 MySQL备份恢复
  15. Project Professional安装:Windows Installer(MSI)与即点即用
  16. OA系统都能为企业带来什么
  17. android v4l2 4路视频,美菲特4路HDMI视频采集卡
  18. 独家记忆孙嘉灵海棠首发 婉转乐曲演绎动心爱情
  19. golang使用iris框架全局异常捕获
  20. Cesium 与百度全景API联动

热门文章

  1. Linux系统(Centos)下安装nodejs并配置环境
  2. 【平台兼容性】jeecg部署weblogic 测试,修改配置方法
  3. TOMCAT内存溢出问题
  4. 架构设计:分布式结构下,服务部署发布
  5. Linux IPC实践(13) --System V IPC综合实践
  6. 单元测试:unittest.TestCase
  7. 工作总结 EntityFramework中出现DateTime2异常的完美解决办法
  8. Linux 第20天: (09月12日) Linux启动和内核管理
  9. 贪心 Codeforces Round #273 (Div. 2) C. Table Decorations
  10. C语言三个数排序,普通方法及进阶(不引入第三变量交换数值法)