内存在进行申请和释放内存的情况下,难免会产生碎片。Linux采用伙伴系统解决外部碎片的问题,采用slab解决内部碎片的问题。

什么是碎片

  • 内部碎片是由于采用固定大小的内存分区,即以固定的大小块为单位来分配,采用这种方法,进程所分配的内存可能会比所需要的大,这多余的部分便是内部碎片。
  • 外部碎片是由于未分配的连续内存区域太小,以至于不能满足任意进程所需要的内存分配请求,这些小片段且不连续的内存空间被称为外部碎片。

伙伴系统

有两种方法可以有效的避免外部碎片:

方案一:采用分页技术将不连续的空闲页面映射到连续的线性地址空间中。

方案二:开发一种合适的技术来跟踪内存,保证内核在申请一小块内存的情况下,不会从大块的连续空间内截取一小段,从而保证了大块内存的连续性和完整性。

如果采用方案一,势必在每一次映射都要改写内核的页表,进而刷新TLB,这使得分配的速度大大减小。因此,Linux采用方案二来解决外部碎片问题,也就是伙伴系统。

伙伴系统的宗旨是用最小的内存块来满足内核对于内存的请求。内核中所有空闲页面被分为10个块列表,分别包含1、2、4、8、16、32、64、128、256和512个连续页面框架。块的第一个页帧的物理地址是组大小的倍数,例如,一个16页帧的块的初始地址是$16*2^{12}$的倍数。

内核在基本的伙伴分配器的基础上做了一些扩展。

  1. 支持内存节点和区域,称为分区的伙伴分配器(zoned buddy allocator)。
  2. 为了预防内存碎片,把物理页根据可移动性分组。
  3. 针对分配单页做了性能优化,为了减少处理器之间的锁竞争,在内存区域增加了一个每处理器页集合。

分配和回收

假设有一组128个连续页帧的请求,该算法首先检查128页帧列表中是否有空闲块,如果没有则会寻找下一个更大的块——256页帧,内核会分配256页帧的块中的128个以满足请求,然后将剩余的128页帧插入到空闲的128页帧列表中。如果没有空闲的256页帧,则会查找下一个更大块(即空闲的512页帧的块),如果存在这样的块,就分配512页帧中的128个以满足请求,将剩余的384个页帧中的前256个插入到空闲的256页帧列表中,后128页帧插入到空闲的128页帧列表中。如果512页帧列表也为空,则该算法放弃查找并发出错误状态信号。

在释放内存的操作中,内核将大小为b的空闲伙伴块对合并为大小为2b的单个块,但是成为伙伴块对还需要满足下面两个条件:

  • 位于连续的物理地址中。
  • 第一个块的第一个页帧的物理地址是$2b2^{12}$的倍数。

该算法是迭代的,如果成功合并了已释放的块,则块大小翻倍,然后再次检测是否能合并成更大的块。

相关数据结构

分区的伙伴分配器专注于某个内存节点的某个区域。在内存域结构体中,用结构体成员free_area来维护空闲页块,数组下标对应页块的阶数。结构体free_area的成员free_list是空闲页块的链表,nr_free是空闲页块的数量。内存域结构体中的managed_pages是伙伴分配器管理的物理页的数量,不包括引导内存分配器的物理页。

/* include/linux/mmzone.h */
struct zone {unsigned long       managed_pages;unsigned long       spanned_pages;      /* 总页数,包含空洞 */unsigned long       present_pages;      /* 可用页数,不好含空洞 *//* free areas of different sizes *//* 管理区中的空闲页框块,主要是在伙伴系统中使用,free_area结构体中free_list是伙伴系统最为常用的一个列表,将在后续介绍 */struct free_area    free_area[MAX_ORDER];
}struct free_area {struct list_head    free_list[MIGRATE_TYPES];unsigned long       nr_free;
};

MAX_ORDER是最大的阶数,默认值是11,而实际的可分配的最大阶数需要减去1,即伙伴分配器一次最多可以分配$2^{10}$页。也可以使用配置宏CONFIG_FORCE_MAX_ZONEORDER指定最大阶数。

/* include/linux/mmzone.h */
#ifndef CONFIG_FORCE_MAX_ZONEORDER
#define MAX_ORDER 11
#else
#define MAX_ORDER CONFIG_FORCE_MAX_ZONEORDER
#endif
#define MAX_ORDER_NR_PAGES (1 << (MAX_ORDER - 1))

free_area共有MAX_ORDER个元素,其中第order个元素记录的是$2^{order}$个空闲页帧组成的空闲块,这些空闲块在free_list中以双向链表的形式连接起来,并通过nr_free来记录个数。对于同等大小的空闲块,每个空闲块的起始页帧用于与链表的节点进行相连,这些节点对应的是page结构体中的lru成员。

/* include/linux/mm_types.h */
struct page {struct list_head lru;
}

具体的连接方式如图所示:

伙伴之间不必彼此连接,如果一个空闲块在分配期间被分解成了两半,内核将会自动将剩下的一半加入到对应的链表中。同样,在未来某一个时刻,内存释放了内存块,两个内存块均处于空闲状态,则可通过地址来判断是否为伙伴,这样大大减少了内核的管理工作。

可移动的伙伴分配器

然而,在系统长期运行过程中,还可能出现这样的情况:系统中虽有较多空闲块,但这些空闲块都不连续即都不是彼此的伙伴,根据伙伴系统算法,这些空闲块无法合并成一个更大的空闲块而形成了内部碎片。在内核版本2.6.24之后,增加了可移动的伙伴分配器来防止这种碎片。

为了预防内存碎片,内核根据可移动性把物理内存分为3类:

  1. 不可移动页:位置必须固定,不能移动到其他位置,直接映射到内核虚拟地址空间的页属于这一类。
  2. 可移动页:使用页表映射的页属于这一类,可以移动到其他位置,然后通过修改页表来映射。
  3. 可回收页:不能直接移动,但可以回收,其内容可以从数据源重新获取。
/* include/linux/mmzone.h */
num migratetype {MIGRATE_UNMOVABLE,  /* 不可移动 */MIGRATE_MOVABLE,    /* 可移动 */MIGRATE_RECLAIMABLE,    /* 可回收 */MIGRATE_PCPTYPES,   /* the number of types on the pcp lists 定义内存区域的每处理器页集合中链表的数量 */MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,  /* 高阶原子分配,即阶数大于0,并且分配页时不能睡眠等待 */
#ifdef CONFIG_CMAMIGRATE_CMA,    /* 连续内存分配器 */
#endif
#ifdef CONFIG_MEMORY_ISOLATIONMIGRATE_ISOLATE,    /* can't allocate from here 隔离,不能从这里分配 */
#endifMIGRATE_TYPES
};

对伙伴分配器的数据结构的主要调整是把空闲链表分解为MIGRATE_TYPE个列表。

/* include/linux/mmzone.h */
struct free_area {struct list_head    free_list[MIGRATE_TYPES];unsigned long       nr_free;
};

从最初开始,内存并未划分为可移动的不同分区,这些都是在运行过程中形成的。内核通过全局变量page_group_mobility_disabled表示能否使用可移动性分组。其中,vm_total_pages是所有内存区域里面高水线以上的物理页总数,pageblock_order是按可移动性分组的阶数,pageblock_nr_pages是pageblock_order对应的页数。如果所有内存区域里面高水线以上的物理页总数小于pageblock_nr_pages*迁移类型数量,那么禁用可移动性分组。

void __ref build_all_zonelists(pg_data_t *pgdat)
{...if (vm_total_pages < (pageblock_nr_pages * MIGRATE_TYPES))page_group_by_mobility_disabled = 1;elsepage_group_by_mobility_disabled = 0;...
}

分配与回收的实现

1.分配页

_rmqueue_smallest函数是分配页的核心函数,通过循环,按递增顺序遍历内存域的各个特定迁移类型的空闲页列表,直至找到合适的一项。

/* mm/page_alloc.c */
static __always_inline
struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,int migratetype)
{unsigned int current_order;struct free_area *area;struct page *page;/* Find a page of the appropriate size in the preferred list */for (current_order = order; current_order < MAX_ORDER; ++current_order) {area = &(zone->free_area[current_order]);page = list_first_entry_or_null(&area->free_list[migratetype],struct page, lru);if (!page)continue;list_del(&page->lru);rmv_page_order(page);area->nr_free--;expand(zone, page, order, current_order, area, migratetype);set_pcppage_migratetype(page, migratetype);return page;}return NULL;
}

循环从申请阶数到最大阶数,在每次遍历分配阶的链表中,根据参数迁移类型选择正确的迁移队列,如果指定迁移类型的空闲链表不为空,则从链表取出第一个页块。一旦选定在当前分配阶链表上的分配页框后,需要通过list_del和rmv_page_order从链表中移除一个内存块,并更新nr_free值。如果页块阶数比申请阶数大,那么分裂页块,把后一半插入到低一阶的空闲链表中,直至获得一个大小为申请阶数的页块。分裂的过程由expand函数完成。

/* mm/page_alloc.c */
static inline void expand(struct zone *zone, struct page *page,int low, int high, struct free_area *area,int migratetype)
{unsigned long size = 1 << high;while (high > low) {area--;high--;size >>= 1;VM_BUG_ON_PAGE(bad_range(zone, &page[size]), &page[size]);if (set_page_guard(zone, &page[size], high, migratetype))continue;list_add(&page[size].lru, &area->free_list[migratetype]);area->nr_free++;set_page_order(&page[size], high);}
}

其中,low是申请的低阶,high是在遍历中所选定的高阶。从高阶开始递减向低阶遍历,也就是说从较大的页块开始依次分裂,然后再次运行_rmqueue_smallest函数,再分裂,直至出现满足申请阶数的页框。

2.回收页

回收页的核心函数是__free_pages函数。

/* mm/page_alloc.c */
void __free_pages(struct page *page, unsigned int order)
{if (put_page_testzero(page))free_the_page(page, order);
}static inline void free_the_page(struct page *page, unsigned int order)
{if (order == 0)     /* Via pcp? */free_unref_page(page);else__free_pages_ok(page, order);
}

首先需要把引用计数器减1,只有当页的引用计数器为零,才能真正回收页。如果阶数为零,不还给伙伴分配器,而是当作缓存热页添加到每处理器页集合中;如果阶数大于0,调用函数__free_pages_ok函数来回收页。

函数__free_pages_ok负责回收阶数大于0的页块,最终调用核心函数 __free_one_page,算法过程是:如果伙伴是空闲的,并且伙伴在同一个内存区域,那么和伙伴合并,其中,如果是隔离类型或是其他类型的页块是不能合并的。最后合并后的阶数是order,如果order小于MAX_ORDER-2,则检查order+1阶的伙伴,如果空闲且order阶的伙伴正在释放,之后可以合并成order+2阶的页块,为了防止此时当前页块被分配出去,把当前页块添加到空闲链表尾部。

static inline void __free_one_page(struct page *page,unsigned long pfn,struct zone *zone, unsigned int order,int migratetype)
{unsigned long combined_pfn;unsigned long uninitialized_var(buddy_pfn);struct page *buddy;unsigned int max_order;max_order = min_t(unsigned int, MAX_ORDER, pageblock_order + 1);...continue_merging:/* 如果伙伴是空闲的,则和伙伴合并,重复这个操作,直到阶数等于max_order-1 */while (order < max_order - 1) {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);} else {/* 伙伴是空闲的,把伙伴从空闲链表中删除 */list_del(&buddy->lru);zone->free_area[order].nr_free--;rmv_page_order(buddy);}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);/** 最后合成的页块阶数是order,如果order小于MAX_ORDER-2,* 则检查order+1阶的伙伴是否空闲,如果空闲,那么order阶的伙伴可能正在释放,* 很快就可以合并成order+2阶的页块,为了防止当前页块很快被分配出去,* 把当前页块添加到空闲链表尾部。*/if ((order < MAX_ORDER-2) && pfn_valid_within(buddy_pfn)) {struct page *higher_page, *higher_buddy;combined_pfn = buddy_pfn & pfn;higher_page = page + (combined_pfn - pfn);buddy_pfn = __find_buddy_pfn(combined_pfn, order + 1);higher_buddy = higher_page + (buddy_pfn - combined_pfn);if (pfn_valid_within(buddy_pfn) &&page_is_buddy(higher_page, higher_buddy, order + 1)) {list_add_tail(&page->lru,&zone->free_area[order].free_list[migratetype]);goto out;}}/* 添加到空闲链表的尾部 */list_add(&page->lru, &zone->free_area[order].free_list[migratetype]);
out:zone->free_area[order].nr_free++;
}

总结

伙伴系统解决的是外部碎片问题,之后将讨论如何解决内部碎片问题。

内存管理 | 伙伴系统相关推荐

  1. 深入浅出内存管理-- 伙伴系统(buddy system)

    buddy system 伙伴系统是内核中用来管理物理内存的一种算法,我们知道内存中有一些是被内核代码占用,还有一些是被特殊用途所保留,那么剩余的空闲内存都会交给内核内存管理系统来进行统一管理和分配, ...

  2. 内存管理 -- 伙伴系统(buddy system)

    一.简介 伙伴系统是内核用来管理物理内存的一种算法(需要注意的是它是用来管理物理内存的,而不是映射后的虚拟内存),在物理内存中会除了内核和一些特殊用途的内存外,其余的空闲内存就会交给内核内存管理系统统 ...

  3. Linux 内存管理 | 物理内存、内存碎片、伙伴系统、SLAB分配器

    文章目录 物理内存 物理内存分配 外部碎片 内部碎片 伙伴系统(buddy system) slab分配器 物理内存 在Linux中,内核将物理内存划分为三个区域. 在解释DMA内存区域之前解释一下什 ...

  4. 伙伴系统之避免碎片--Linux内存管理(十六)

    原文链接:https://blog.csdn.net/gatieme/article/details/52694362 日期 内核版本 架构 作者 GitHub CSDN 2016-09-28 Lin ...

  5. 伙伴系统之伙伴系统概述--Linux内存管理(十五)

    日期 内核版本 架构 作者 GitHub CSDN 2016-09-02 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理 博文 ...

  6. 内存管理笔记十、buddy伙伴系统

    内存管理笔记十.buddy伙伴系统 引言:上一篇笔记中,我们介绍了段页式的内存管理方式其不仅获得分段和分页的好处,又规避了单纯分段和分页的缺陷.这看似是一个完美的解决方案.但每次申请内存,均要完成虚拟 ...

  7. Linux内存管理、伙伴系统(buddy system)等知识点

    引入 之前写过一篇文章将伙伴系统,可以参考:内存池算法简介 物理内存由页分配器(page allocator)接管. 内存块的申请.释放过程. 伙伴算法.阶数. 2^0 为1 ,链表上存放的是一个pa ...

  8. Linux内存管理之slab 1:slab原理(+buddy伙伴系统)

    Linux内存管理之slab 1:slab原理(+buddy伙伴系统) 1. 为什么有了Buddy(伙伴系统)还需要slab? 1.1 什么是伙伴系统? 1.1.1 伙伴系统思想 1.2 伙伴系统例子 ...

  9. Windows内存管理和linux内存管理

    windows内存管理 windows 内存管理方式主要分为:页式管理,段式管理,段页式管理. 页式管理的基本原理是将各进程的虚拟空间划分为若干个长度相等的页:页式管理把内存空间按照页的大小划分成片或 ...

最新文章

  1. SQLite的sqlite_sequence表
  2. python从入门到实践django看不懂_Python编程:从入门到实践踩坑记 Django
  3. ajax原理 博客,AJAX工作基本原理
  4. cpu开机就是60℃_铅锤哥:十五种电脑开机黑屏的原因与解决思路
  5. 2017中国人工智能峰会即将开启,和30位AI大咖一起头脑风暴
  6. eclipse远程连接hadoop_hadoop集群搭建详细方法
  7. macOS中安装docker
  8. Spring : @ComponentScan注解
  9. 农行支付php,ECSHOP教程:农行支付接口开发(含手机端)
  10. Storm入门之第8章事务性拓扑
  11. TeamViewer安全证书过期,解决办法
  12. 关于“嵌入式系统设计师”的了结。
  13. 大学生计算机自我鉴定500字,大学生计算机专业的自我鉴定范文
  14. 浏览器打开pdf乱码
  15. Android 高仿微信群聊头像
  16. 计算机域名是什么域名?
  17. MNIST导入图片数据集
  18. TP-LINK路由器怎么删除DDNS创建的域名
  19. DANN困扰解决-交替训练数据
  20. 如何使用CE提供的汉化文件

热门文章

  1. 通用的结构化数据流通工具
  2. html 文字倒映效果,如何用css实现文字渐变倒影?求教怎么做?
  3. 职场、书、技术、球友
  4. history`pushState和window`onpopstate实现tab切换效果
  5. 神马搜索移动网站优化指南
  6. 智能车的转弯部分_10个极品智能车方案合辑,夏日避暑进阶两不误
  7. three.js学习二
  8. Python 通过浏览器 打开指定网址
  9. Oracle:11g服务详细介绍及,哪些服务是必须开启的
  10. 【Unity3D】顶点和片元着色器