内存管理之二

4. slab层

为了便于数据结构的频繁分配和回收,会用到一个空闲链表。它相当于对象高速缓存以便快速存储频繁使用的对象类型。在内核中,空闲链表面临的主要问题是不能全局控制。为了弥补这一缺陷,Linux内核提供了slab层(slab分配器)。

 

4.1. slab层的设计

slab层把不同的对象划为所谓高速缓存(cache)组,其中每个高速缓存都存放不同类型的对象。每种对象类型对应一个高速缓存。这些高速缓存又被划为slab。slab由一个或多个物理上连续的页组成。每个slab都包含一些对象成员,这里的对象指的是被缓存的数据结构。每个slab处于三种状态之一:满、部分满和空。当内核的某一部分需要一个新的对象时,先从部分满的slab中分配,如果没有部分满的slab,就从空的slab中分配,如果没有空的slab,就要创建一个slab了。

每个高速缓存都是用kmem_cache_t结构来表示。slab描述符用struct slab来表示。

总之,slab层的关键就是避免繁琐分配和释放页。

 

4.2. slab分配器的接口

一个新的高速缓存是通过下面函数创建:

kmem_cache_t *kmem_cache_create(const char *name, size_t size, size_t align,

unsigned long flags,

void (*ctor)(void*, kmem_cache_t*, unsigned long),

void (*dtor)(void*, kmem_cache_t*, unsigned long));

第一个参数是存放高速缓存的名字。第二个参数就是高速缓存中每个元素的大小。第三个参数就是高速缓存内第一个对象的偏移,用来确保在页内进行特定的对齐。第四个参数是可选的设置项,用来控制高速缓存的行为。最后两个参数分别是高速缓存的构造和析构函数。

要销毁一个高速缓存,则调用:

int kmem_cache_destory(kmem_cache_t *cachep);

调用该函数之前必须确保以下两个条件:

1) 高速缓存中的所有slab必须为空。

2) 在调用kmem_cache_destroy期间不再访问这个高速缓存。

创建高速缓存后,可以通过下列函数从中获取对象:

void *kmem_cache_alloc(kmem_cache_t *cachep,int flags);

该函数从给定的高速缓存中返回一个指向对象的指针。如果高速缓存的所有slab中都没有空闲的对象,那么slab层必须通过kmem_getpages获取新的页,flags的值传递给__get_free_pages函数。

最后释放一个对象,并把它返回给原先的slab,使用函数:

void kmem_cache_free(kmem_cache_t *cachep, void *objp);

 

5. 在栈上的静态分配

我们知道,用户空间能够奢侈地负担起非常大的栈,而且空间还可以动态增长,但是内核却不能这么奢侈,因为内核栈大小固定。当给每个进程分配一个固定大小的小栈,不但可以减少内存的消耗,而且内核耶无需负担太重的栈管理任务。

每个进程的内核栈大小既取决于体系结构,也与编译时的选项有关。通常,每个进程都有两页的内核栈,32位和64位体系结构的页面大小分别是4KB和8KB,所以内核栈大小分别是8KB和16KB当然,可以在编译时设置单页内核栈,当这个选项被激活时,每个进程的内核栈就只有一页那么大了,这样做,可以让每个进程减少内存消耗,给分配连续的页带来了好处。历史上,中断处理程序耶要放在内核栈中,同时增加了更加严格的约束条件。

为了纠正这问题,实现了一个附加选项:中断栈。中断栈为每个进程提供一个用于中断处理程序的栈。

总的来说,内核栈可以是1页,也可以是2页。当1页栈的选项被激活时,中断处理程序获得了自己的栈。在任何情况下,无限制的递归和alloca是不允许的。所以,在任意一个函数中,你都必须尽量节省栈资源,在具体的函数中让所有局部变量所占空间之和不要超过几百字节。

 

6. 高端内存的映射

根据定义,在高端内存中的页不能永久地映射到内核地址空间。因此,通过alloc_pages函数以__GFP_HIGHMEM标志获得的页不可能有逻辑地址。

 

6.1. 永久映射

要映射以给定的page结构到内核地址空间,可以使用:

void *kmap(struct page *page);

这个函数在高端内存或低端内存上都能用。如果page结构对应的是低端内存中的一页,函数只会单纯地返回该页的虚拟地址。如果页位于高端内存,则会建立一个永久映射,在返回地址。这个函数可以睡眠,只能用于进程上下文中。

因为允许永久映射的数量是有限的,当不再需要高端内存时,应该解除映射:

void kunmap(struct page *page);

 

6.2. 临时映射

当必须创建一个映射而当前上下文不能睡眠时,内核提供了临时映射(原子映射):

void *kmap_atomic(struct page *page, enum_km_type type);

参数type定义在<asm/kmap_types.h>中,这个函数不会阻塞,禁止内核抢占,可以用在中断上下文中和其他不能重新调度的地方。

取消映射:

void kunmap_atomic(void *kvaddr, enum_km_type type);

 

7. 每个CPU的分配

支持SMP的现代操作系统使用了每个CPU上的数据,对于给定的处理器其数据时唯一的。一般来说,每个CPU的数据存放在一个数组中,数组中的每一项对应着系统上一个存在的处理器。这就是2.4内核的处理方式。该情况下,不再需要锁,而是关注内核抢占来防止并发访问。例如:

unsigned long my_percpu[NR_CPUS];

int cpu;

cpu=get_cpu();

my_ percpu[cpu]++;

put_cpu();

 

7.1. 新编译时的每个CPU接口

2.6内核为了方便创建和操作每个CPU数据,从而引进了新的操作接口,称着percpu。头文件<linux/percpu.h>有声明,文件mm/slab.c和<asm/percpu.h>中有定义。

DEFINE_PER_CPU(type,name);

这个语句为系统的每个处理器都创建了一个类型为type,名字为name的变量实例,如果你需要在别处声明变量,可以使用:

DECLARE_PER_CPU(type,name);

调用get_cpu_var返回当前处理器上指定的变量,同时禁止内核抢占;调用put_cpu_var将相应地重新激活抢占。

get_cpu_var(name)++;/*增加当前处理器变量的值*/

put_cpu_var(name);/*释放CPU*/

可以调用下面这个接口处理指定CPU的数据:
per_cpu(name,cpu);

函数per_cpu函数既不会禁止内核抢占,也不会提供任何形式的锁保护

注意,这些编译时每个CPU数据的例子并不能在模块使用,因为连接程序实际上将它们创建在一个唯一的可执行段中(.data.percpu)。

 

7.2. 新运行时的每个CPU接口

该例程为系统上的每个处理器创建所需内存的实例,其原型在<linux/percpu.h>中。

void *alloc_percpu(type);

void *__alloc_percpu(size_t size, size_t align);

void *free_percpu(const void *);

宏alloc_percpu给系统中的每个处理器分配一个指定类型对象的实例。它其实是__alloc_percpu的一个封装例程。这个原始宏接收的参数有两个:一个事要分配的实际字节数;一个是分配时要按多少字节对齐。而封装后的__alloc_percpu按照单字节对齐--按照给定类型的自然边界对齐。例如:

struct rabid_cheetah = alloc_percpu(struct rabid_cheetab);

等价于:

struct rabid_cheetab = = __alloc_percpu(sizeof(struct rabid_cheetab),

__alignof__( struct rabid_cheetab));

__alignof__结构式gcc的一个功能,它会返回指定类型或lvalue所需的对齐字节数。

无论是alloc_percpu还是__alloc_percpu都返回一个指针,用来间接引用动态创建的每个CPU数据,内核提供了两个宏利用指针来获取每个CPU数据:

get_cpu_ptr(ptr);

put_cpu_ptr(ptr);

函数get_cpu_ptr返回一个指向当前处理器数据的特殊实例,同时禁止内核抢占;而put_cpu_ptr会重新激活内核抢占。

最后,函数per_cpu_ptr返回指定处理器的唯一数据:

per_cpu_ptr(ptr);

该函数不会禁止内核抢占。

 

7.3. 使用每个CPU数据的原因

第一,减少了数据锁定。

第二,使用每个CPU数据可以大大减少缓存失效。失效发生在处理器试图使它们的缓存保持同步时。

 

8. 分配函数的选择

1) 如果需要连续的物理页,就可以使用某个低级页分配器或kmalloc。但是需要注意GFP_KERNEL和GFP_ATOMIC标志的使用。

2) 如果需要从高端内存进行分配,就使用alloc_pages。它返回一个指向struct page结构的指针,而不是指向某个逻辑地址的指针。为了获得真正的指针,需要调用kmap函数,把高端内存映射到内核的逻辑地址空间。

3) 如果不需要物理页上连续的页,而仅仅需要虚拟地址连续的页,就使用vmalloc(有一定性能损失)。

4) 如果需要创建和销毁很多较大的数据结构,那么可以使用slab高速缓存。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值