内存管理-slab[原理]

历史简介

linux内核运行需要动态分配内存,其中有两种分配方案:

第一种是以页为单位分配内存,即一次分配内存的大小必须是页的整数倍;第二种是按需分配内存,一次分配内存的大小是随机的。

第一种分配方案通过buddy系统实现,第二种分配方案就是通过slab子系统实现。slab子系统随内核的发展衍生出slub和slob,最新应用于服务器的内核一般默认使用slub来实现第二种内存分配方案。slob一般用在移动端和嵌入式系统。相对较老的内核默认用slab实现第二种分配方案。slab,slob,slub就功能来说相同,内核提供了编译选项供用户选择使用哪种子系统来实现第二种内存分配方案。由于slab系统是内核使用的原始方案,经过相当长一段时间的演进。因此本系列博客就以slab开始讲解linux实现的第二种内存分配方案。充分理解slab后,对slub和slob就很简单了。

 slab和buddy的关系,以及slab存在的必要性

slab是基于buddy系统实现:调用slab接口分配随机大小的内存时,slab内部会调用buddy系统以页为单位申请整数个物理上连续的页,然后将这些页拆分成更小的单元,取一个合适大小的单元,将这段内存的地址返回。如图,buddy系统实现了物理内存管理,抽象出内核如下两个主要内核接口函数

1 static inline struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)//分配2的order次方个连续物理页,返回第一个页的page描述符虚的拟地址
2 void free_pages(unsigned long addr, unsigned int order) //释放2的order个连续物理页,addr指向第一个物理页第一个byte的虚拟地址

内核slab子系统对外提供了如下两个主要接口:

static __always_inline void *kmalloc(size_t size, gfp_t flags)//分配size字节大小的内存,返回这段内存的虚拟地址 
void kfree(const void *objp)//释放objp指向的一段内存 

既然有了buddy为什么又基于buddy实现slab呢?答案就是处于时间效率和空间效率的考虑:

出于空间效率:举个例子:在fork系统调用种进程管理子系统需要动态申请task_struct来作为新建进程的进程描述符,而task_struct需要的内存远远小于一个物理页,如果没有slab子系统,那进程管理子系统就只有通过buddy系统申请一个完整的物理页。这样一来有两种方法处理这个页内剩余的内存,其一:进程管理子系统自己将这个页划分若干个更小的单元管理,将剩下的内存用作其他的用途,这样有一个弊端,进程管理子系统必须自己管理剩余内存,task_struct仅仅时一个例子,试想如果内核种所有的子系统都要再设计一个“剩余内存管理的功能”显然是不科学的。其二:就是将每一个task_struct装到一个页中,这个页剩余的空间不要了,这样会浪费内存空间,但进程管理子系统就不必自己管理剩余单元。

出处于时间的考虑:buddy系统相对于slab系统要复杂很多,每一次调用alloc_pages和free_pages需要付出惨重的代价。内核中有些代码又必须频繁的申请释放内存。slab其实充当内核各个子系统和buddy系统之间的一个空闲内存的“缓冲池”。当内存通过kfree被释放后,短时期停留在slab的”缓冲池”中,如果再次kmalloc时直接从“缓冲池”中将空闲内存取出来返回即可。这样就避免每次内存分配都要付出alloc_pages和free_pages的代价。

slab是怎么将通过alloc_pages申请到的页组织成更小的单元?又是怎么将这些个更小的单元管理起来的呢?让我们沿着正常人的思路一步一步推演slab的设计

实现kmalloc和kfree需要达成的目标无非就是时间和空间两方面:给定size必须快速找到适合的空闲内存段(时间方面)。适合的空闲内存段可以这么理解:长度必须大于等于size,能等于最好,实在不行可以大于,在大于的情况下尽可能要“不要大太多,就是大的越少越好“(空间方面)

以下图为例子:假设slab通过alloc_pages分配到了一个页(order=0),slab会将这个页划分成若干个object和一个head。head用slab描述这个页中的哪些object是空闲的。这样以来slab数据结构一个很直观的设计如下:

 

猜你喜欢

转载自www.cnblogs.com/DoOrDie/p/9769062.html
今日推荐