先行阅读:Linux 内存管理机制(内核空间层面分析)
在内存管理中
唉,我原先以为这两个不一样,面试的时候就说只知道Buddy,不知道伙伴,原来这两个是一样的,尴尬尴尬啊!!!!
关于这个算法,我前面的文章有讲过可以先看看,这里我也就简单总结一下即可.
解决的问题是频繁地请求和释放不同大小的一组连续页框,必然导致在已分配页框的块内分散了许多小块的空闲页面,由此带来的问题是,即使有足够的空闲页框可以满足请求,但要分配一个大块的连续页框可能无法满足请求。
比如申请5个.
伙伴算法:
算法将所有空闲的页面分组划分为MAX_ORDER(11)
个页面块链表进行管理,其中MAX_ORDER定义:
/include/linux/mmzone.h
/* Free memory management - zoned buddy allocator. */
#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))
1: struct zone{
2: ....
3: struct free_area free_area[MAX_ORDER];
4: ....
5: }
地址连续,双向链表组织,最小4K,最大4M(2^10).
Linux2.6为每个管理区使用不同的伙伴系统,内核空间分为三种区,DMA,NORMAL,HIGHMEM,对于每一种区,都有对应的伙伴算法
每一条链表的第一个页框的物理地址是该块大小的整数倍。例如,大小为 16个页框的块,其起始地址是 16 * 2^12 (2^12 = 4096,这是一个常规页的大小)的倍数。
//对应于每一个链表
struct free_area {
struct list_head free_list[MIGRATE_TYPES];//空闲块双向链表
unsigned long nr_free;//空闲块的数目
};
看链接
页框块在释放时,会主动将两个连续大小的页框块合并为一个较大的页框块。
满足以下条件的两个块称为伙伴:
伙伴算法把满足以上条件的两个块合并为一个块,该算法是迭代算法,如果合并后的块还可以跟相邻的块进行合并,那么该算法就继续合并。
深入分析的话有:链表的一些操作和位图(本来不想再深入了,但表示想了一下还是 再往下研究一下吧)
它的基本思想是将内核中经常使用的对象放到高速缓存中,并且由系统保持为初始的可利用状态。比如进程描述符,文件描述符等,内核中会频繁对此数据进行申请和释放
需要注意的是slab分配器只管理内核的常规地址空间(直接被映射到内核地址空间的ZONE_NORMAL和ZONE_DMA)。
采用了slab分配器后,在释放内存时,slab分配器将释放的内存块保存在一个列表中,而不是返回给伙伴系统。在下一次内核申请同样类型的对象时,会使用该列表中的内存
。slab分配器分配的优点:
缺点:
OK,以上就是其基本思想了.呼~终于理解了!!!下面来介绍一下其内部实现:
图 1 给出了 slab 结构的高层组织结构。在最高层是 cache_chain,这是一个 slab 缓存的链接列表。可以用来查找最适合所需要的分配大小的缓存(遍历列表)。cache_chain 的每个元素都是一个 kmem_cache 结构的引用(称为一个 cache)。它定义了一个要管理的给定大小的对象池(固定大小,即分门别类)。(kmem_cache == 对象池
)
每个缓存都包含了一个 slabs 列表,这是一段连续的内存块(通常都是页面)。存在 3 种 slab:
slab 列表中的每个 slab 都是一个连续的内存块(一个或多个连续页),它们被划分成一个个对象。这些对象是从特定缓存中进行分配和释放的基本元素。注意 slab 是 slab 分配器进行操作的最小分配单位,通常来说,每个 slab 被分配为多个对象。
由于对象是从 slab 中进行分配和释放的,因此单个 slab 可以在 slab 列表之间进行移动。例如,当一个 slab 中的所有对象都被使用完时,就从 slabs_partial 列表中移动到 slabs_full 列表中。
/*kmem_cache 结构包含了每个中央处理器单元(CPU)的数据、
一组可调整的(可以通过 proc 文件系统访问)参数、
统计信息和管理 slab 缓存所必须的元素。*/
struct kmem_cache *
kmem_cache_create(const char *name,
unsigned int size,
unsigned int align,
slab_flags_t flags,
void (*ctor)(void *))
{
return kmem_cache_create_usercopy(name, size, align, flags, 0, 0,
ctor);
}
name 参数定义了缓存名称,proc 文件系统(在 /proc/slabinfo 中)使用它标识这个缓存。 size 参数指定了为这个缓存创建的对象的大小, align 参数定义了每个对象必需的对齐。 flags 参数指定了为缓存启用的选项。这些标志如表 1 所示。
后面的参数ctor
定义了一个对象构造器。构造器和析构器是用户提供的回调函数。当从缓存中分配新对象(kmem_cache_alloc函数)时,可以通过构造器进行初始化。
在创建缓存之后, kmem_cache_create 函数会返回对它的引用。注意这个函数并没有向缓存分配任何内存。一切的分配添加都是通过kmem_cache_alloc函数执行的.
void kmem_cache_destroy(struct kmem_cache *s)
{
int err;
if (unlikely(!s))
return;
flush_memcg_workqueue(s);
get_online_cpus();
get_online_mems();
mutex_lock(&slab_mutex);
s->refcount--;
if (s->refcount)
goto out_unlock;
err = shutdown_memcg_caches(s);
if (!err)
err = shutdown_cache(s);
if (err) {
pr_err("kmem_cache_destroy %s: Slab cache still has objects\n",
s->name);
dump_stack();
}
out_unlock:
mutex_unlock(&slab_mutex);
put_online_mems();
put_online_cpus();
}
kmem_cache_zalloc函数就是会memset操作一下
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
这个函数从缓存中返回一个对象。注意如果缓存目前为空,那么这个函数就会调用 cache_alloc_refill 向缓存中增加内存。
void kmem_cache_free( struct kmem_cache *cachep, void *objp );
kmalloc
函数来申请)如果不涉及到特定类型的内存,而只是普通类型的内存,可以使用kmalloc和kfree来申请和释放缓存。
循环遍历可用缓存来查找可以满足大小限制的缓存。找到之后,就(使用 __kmem_cache_alloc
)分配一个对象。要使用 kfree 释放对象,从中分配对象的缓存可以通过调用 virt_to_cache
确定。这个函数会返回一个缓存引用,然后在 __cache_free
调用中使用该引用释放对象。
过程实例:
kmem_cache_create
kmem_cache_alloc
kmem_cache_destroy //调用者必须确保在执行销毁操作过程中,不要从缓存中分配对象。
参考:https://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/index.html