伙伴系统:
背景:
分配内存时,内核分配完整的页。
如果进程所需大小小于完整页,则用户空间标准库会进一步拆分为目标大小区域。
如果进程所需连续页大小内存,则内核使用快速检测内存中连续区域的伙伴系统。
概念:
系统中的空闲内存区域总是两两分组,每组中2个内存区域称为伙伴。
伙伴的分配可以是彼此独立的,但如果两个伙伴都是空闲的,内核会将其合并为一个更大的内存区域,将其作为下一阶内存区域的伙伴。
内核对所有大小相同的伙伴都放置到同一个列表中管理,即free_area数组的free_list链表同一阶成员中。
由此可知,最初内存都在最顶阶链表中,随着内存分配,最顶阶链表成员会逐渐拆分,剩下的内存区域会降级链入低阶链表中,而使用的内存区域则从链表中消失,将地址传递回应用程序。
随着内存释放,内核检查地址,判断能否和低阶链表中成员合并为一组伙伴,并合并为更大内存区域晋级为高阶伙伴。
查询:
cat /proc/buddyinfo
Node 0, zone Normal 2^1阶 2^2阶 2^3阶 2^4阶 2^5阶 2^6阶 2^7阶 2^8阶 2^9阶 2^10阶 2^11阶
Node 0, zone HighMem 2^1阶 2^2阶 2^3阶 2^4阶 2^5阶 2^6阶 2^7阶 2^8阶 2^9阶 2^10阶 2^11阶
通常,格式如上,其中各阶值为当前运行状态下阶数空闲页数,如果其他架构或者自定义阶数,会有些许不同。
分配:
1.如果一个内存区在分配期间分解为两半,内核会自动将未用的一半加入到对应的free_area数组空闲页链表中。
2.在首选内存区或节点无法满足内存分配请求时,先尝试同节点的另一个内存区,不满足再尝试另一个节点,直至满足。
释放:
1.如果一个内存区在释放期间可以通过地址判断两个空闲状态的内存区是否为伙伴。
碎片:
长期运行情况下,应用程序会频繁分配和释放页,导致若干页是空闲的,但分布在物理地址空间的各处。如此导致缺乏连续页组成的较大内存区域,虽然应用程序无法看到虚拟地址连续的内存实际是物理地址不连续的内存页组成,但读写性能却下降了。
针对碎片处理,内核将不同页类型做不同处理:
不可移动页(MIGRATE_UNMOVABLE宏):内存中有固定位置,无法移动,内核代码占用。
可回收页(MIGRATE_RECLAIMABLE宏):不能直接移动,但可删除,内容可从某些源重新生成,如映射自文件的数据。
kswapd守护进程根据可回收页访问的频繁程度,周期性释放此类内存。
可移动页(MIGRATE_MOVABLE宏):可随意移动,用户空间应用程序占用。页表映射的空间,将其复制到新位置,页表项相应更新。
如果向具有特定可移动性的列表请求分配内存失败,这种紧急情况下可从MIGRATE_RESERVE分配内存。
MIGRATE_ISOLATE是一个特殊的虚拟区域,用于跨越NUMA节点移动物理内存页。(有益于将物理内存页移动到接近于使用该页最频繁的CPU)
MIGRATE_TYPES只是表示迁移类型的数目,也不代表具体的区域。
slab缓存:
背景:
应用程序可以通过内核获取内存页,并通过标准库函数进行裁剪获得小于内存页的内存。
但内核无法使用标准库函数,所以内核再伙伴系统上封装了一层内存管理层,以便将内存页裁剪。
分配:
kmalloc、kfree
伙伴系统与slab缓存的关系: