Kernel那些事儿之内存管理(9) --- Slab(下)

有了Slab的数据结构和操作方法之后,就可以创建一个指定大小的cache,然后在这个cache中申请和释放object。这个做法很适合这种应用场景:频繁地使用一固定大小的内存空间。

如果只是偶尔使用某个大小的内存空间,为此新建一个cache就有点得不偿失。针对于这种应用场景,内核提供了一系列 general caches。今天我们就来看一看这些general caches是怎么工作的。

其思想很简单:这些general caches会在系统初始化时提前创建好,且其大小按照2的幂次方由小到大分布,即 2^5, 2^6, ..., 2^25,其上限由 KMALLOC_MAX_SIZE 决定。当申请某一大小 size 的内存空间时,只需找出比size大的最小的general cache,然后从中分配object就可以了。这样可以保证内碎片总是小于50%。

1. malloc_sizes

那这些已经创建好的general caches放在哪里了呢?它们放在了数组 malloc_sizes 中。

 640 struct cache_sizes malloc_sizes[] = {
 641 #define CACHE(x) { .cs_size = (x) },
 642 #include <linux/kmalloc_sizes.h>
 643     CACHE(ULONG_MAX)
 644 #undef CACHE
 645 };

这个数组不大容易看清楚。我们一点点来。

首先,该数组中成员变量的类型是 struct cache_sizes。

 19 struct cache_sizes {
 20     size_t          cs_size;
 21     struct kmem_cache   *cs_cachep;
 22 #ifdef CONFIG_ZONE_DMA
 23     struct kmem_cache   *cs_dmacachep;
 24 #endif
 25 };

cs_size 指定了这一项中cache的object大小。每一项都包含两个cache,一个提供用于DMA操作的内存,一个提供用于正常操作的内存。

其次,数组 malloc_sizes 中包含了一个头文件,这个头文件就定义了该数组都有哪些 size 的general cache。该头文件内容为:

  1 #if (PAGE_SIZE == 4096)
  2     CACHE(32)
  3 #endif
  4     CACHE(64)
  5 #if L1_CACHE_BYTES < 64
  6     CACHE(96)
  7 #endif
  8     CACHE(128)
  9 #if L1_CACHE_BYTES < 128
 10     CACHE(192)
 11 #endif
 12     CACHE(256)
 13     CACHE(512)
 14     CACHE(1024)
 15     CACHE(2048)
 16     CACHE(4096)
 17     CACHE(8192)
 18     CACHE(16384)
 19     CACHE(32768)
 20     CACHE(65536)
 21     CACHE(131072)
 22 #if KMALLOC_MAX_SIZE >= 262144
 23     CACHE(262144)
 24 #endif
 25 #if KMALLOC_MAX_SIZE >= 524288
 26     CACHE(524288)
 27 #endif
 28 #if KMALLOC_MAX_SIZE >= 1048576
 29     CACHE(1048576)
 30 #endif
 31 #if KMALLOC_MAX_SIZE >= 2097152
 32     CACHE(2097152)
 33 #endif
 34 #if KMALLOC_MAX_SIZE >= 4194304
 35     CACHE(4194304)
 36 #endif
 37 #if KMALLOC_MAX_SIZE >= 8388608
 38     CACHE(8388608)
 39 #endif
 40 #if KMALLOC_MAX_SIZE >= 16777216
 41     CACHE(16777216)
 42 #endif
 43 #if KMALLOC_MAX_SIZE >= 33554432
 44     CACHE(33554432)
 45 #endif

最后,数组 malloc_sizes 的最后定义了一个大小为 ULONG_MAX 的general cache。这个cache在系统初始化的时候不会被创建,而是赋值为NULL。这样,如果有人申请了大于头文件 kmalloc_sizes.h 中定义的最大长度的内存时,就会得到NULL。

2. kmalloc/kfree

这些general caches对外提供的API就是kmalloc和kfree。

 31 static inline void *kmalloc(size_t size, gfp_t flags)
 32 {
 33     if (__builtin_constant_p(size)) {
 34         int i = 0;
 35
 36         if (!size)
 37             return ZERO_SIZE_PTR;
 38
 39 #define CACHE(x)  40         if (size <= x)  41             goto found;  42         else  43             i++;
 44 #include "kmalloc_sizes.h"
 45 #undef CACHE
 46         {
 47             extern void __you_cannot_kmalloc_that_much(void);
 48             __you_cannot_kmalloc_that_much();
 49         }
 50 found:
 51 #ifdef CONFIG_ZONE_DMA
 52         if (flags & GFP_DMA)
 53             return kmem_cache_alloc(malloc_sizes[i].cs_dmacachep,
 54                         flags);
 55 #endif
 56         return kmem_cache_alloc(malloc_sizes[i].cs_cachep, flags);
 57     }
 58     return __kmalloc(size, flags);
 59 }

kmalloc首先看一下参数中的size是不是一个常量。这种情况下,要使用的general cache在编译阶段就可以确定了,这对性能很有好处。

如果size不是常量,会最终调用到函数 __do_kmalloc()。

3714 static __always_inline void *__do_kmalloc(size_t size, gfp_t flags,
3715                       void *caller)
3716 {
3717     struct kmem_cache *cachep;

3724     cachep = __find_general_cachep(size, flags);
3725     if (unlikely(ZERO_OR_NULL_PTR(cachep)))
3726         return cachep;
3727     return __cache_alloc(cachep, flags, caller);
3728 }

该函数利用 __find_general_cachep() 找到合适的general cache,然后调用我们之前讲过的__cache_alloc()来从找到的cache中分配一个object出来。

 765 static inline struct kmem_cache *__find_general_cachep(size_t size,
 766                             gfp_t gfpflags)
 767 {
 768     struct cache_sizes *csizep = malloc_sizes;
 
 777     if (!size)
 778         return ZERO_SIZE_PTR;
 779
 780     while (size > csizep->cs_size)
 781         csizep++;
 782
 783     /*
 784      * Really subtle: The last entry with cs->cs_size==ULONG_MAX
 785      * has cs_{dma,}cachep==NULL. Thus no special case
 786      * for large kmalloc calls required.
 787      */
 788 #ifdef CONFIG_ZONE_DMA
 789     if (unlikely(gfpflags & GFP_DMA))
 790         return csizep->cs_dmacachep;
 791 #endif
 792     return csizep->cs_cachep;
 793 }

该函数根据size来找出合适的general cache。

780 ~ 781意思很明显,要找到比size大的最小的general cache。

3780 void kfree(const void *objp)
3781 {
3782     struct kmem_cache *c;
3783     unsigned long flags;
3784
3785     if (unlikely(ZERO_OR_NULL_PTR(objp)))
3786         return;
3787     local_irq_save(flags);
3788     kfree_debugcheck(objp);
3789     c = virt_to_cache(objp);
3790     debug_check_no_locks_freed(objp, obj_size(c));
3791     __cache_free(c, (void *)objp);
3792     local_irq_restore(flags);
3793 }

3789行根据object的地址,找出其对应的cache的地址。这一点并不难,因为cache的地址保存在了object所在页面的页描述符中的lru.next中了。

3791行调用我们之前讲过的__cache_free()来把object释放回前面找到的cache中。

时间: 2024-10-14 01:56:11

Kernel那些事儿之内存管理(9) --- Slab(下)的相关文章

Kernel那些事儿之内存管理(7) --- Slab(上)

前面讲的buddy system算法,分配内存的最小单位是一个页面(例如 4K).这对于大的内存申请比较适用.可是实际生活中,Kernel经常需要分配小的内存空间,比如几十个字节,这个时候怎么办呢? 不同的人可能会想到不同的解决办法. 对于财大气粗的富人一族,办法很简单:申请一个页面,只用其中的几十字节,剩下的直接丢掉. 对于锱铢必较的穷人一族,就不敢这么挥霍了:申请一个页面,然后记录好页面内哪些内存区用了,哪些还没用,没用的部分可以用来满足其他的内存分配请求.总之务必做到物尽其用. 很不幸,K

Kernel那些事儿之内存管理(8) --- Slab(中)

上篇讲了Slab中的数据结构,这篇该讲Slab中的操作了. 既然是内存管理,那操作无非就两点:allocate 和 free. 1. 申请一个object 在Slab中,申请一个object是通过函数 kmem_cache_alloc() 来完成的. 3618 void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags) 3619 { 3620     return __cache_alloc(cachep, flags, __bu

Kernel那些事儿之内存管理(3) --- 久别重逢

上次我们讲到page frame是物理内存的基本组成单位.那Kernel就必须要有一套机制来管理空闲的page frames.这一点不难理解.每个县长必须要把本县可用的劳动力登记在册,这样哪天皇帝要征兵了,你才不至于手忙脚乱. 这个问题看似简单,实则不然.因为这里面有一个外碎片的问题. 在物理内存中,连续的物理内存页有时是很重要的.例如在DMA操作中,由于大部分DMA处理器都没有分页机制,它们会直接访问物理内存地址,因此DMA 所用的缓冲区在物理地址空间必须连续:再例如,使用连续的物理内存页,可

Kernel那些事儿之内存管理(1)

有人的地方就有江湖.要介绍内存管理这个江湖,首先还得从这里面的主要人物讲起. 在NUMA结构中,物理内存首先被分成若干nodes.每一个node进一步被分成若干zones.每一个zone又关联了一个描述page frames的数组,该数组包含了属于该zone的所有page frame的描述符. 不难看出,在这个江湖里主要有三位重要人物:nodes, zones 和 page frames.这三者的关系和地位大体可以用下图来描述(该图取自"Professional Linux Kernel Arc

Kernel那些事儿之内存管理(6) --- 衣带渐宽终不悔(下)

接着上篇写,继续介绍zone allocator.上一篇介绍了周边,现在来看看它的全貌 --- 函数__alloc_pages(). Kernel源代码里是这样注释函数__alloc_pages()的.其重要地位可见一斑. 1451 /* 1452  * This is the 'heart' of the zoned buddy allocator. 1453  */ __alloc_pages()的工作模式很清晰:利用函数get_page_from_freelist()多次遍历zonelis

Kernel那些事儿之内存管理(2) --- 百闻不如一见

上次介绍了物理内存管理中三位主要人物中的node 和zone.这两位是当官的,一个是县长,一个是里长,不敢不先介绍啊.接下来出场的就是我们的老百姓了 --- page frame. Page frame是物理内存的基本组成单位,在Kernel中由结构体 struct page 来描述. struct page {     unsigned long flags;          atomic_t _count;          union {         atomic_t _mapcou

Kernel那些事儿之内存管理(13) --- 内核映射(下)

前面讲过,针对于内核地址空间中后面的128MB空间,Kernel提供了三种机制来映射物理内存.之前讲过了两种,即持久内核映射和临时内核映射.这两种机制的目的都是一样的:使Kernel能够访问到高端内存. 今天讲一下第三种机制:非连续内存分配,也就是vmalloc.这个机制同样可以使Kernel能够访问到高端内存,不过这不是该机制的主要目的.该机制的主要目的是:把物理上不连续的页面映射到连续的内核线性地址空间中. 非连续内存区域管理 既然是映射,肯定会涉及到三个元素:集合L,集合P,映射M. 集合

Kernel那些事儿之内存管理(11) --- 内核映射(上)

前面简单地介绍了三种不同的地址空间,接下来重点讲述线性地址空间到物理地址空间的映射. 我们先从32位系统开始. 在32位系统中,线性地址空间的大小为 2^32,即4GB.Kernel一般会按照 3:1 的比例,把线性地址空间分为两部分: 0~3GB 用户地址空间 3GB~4GB 内核地址空间. 用户地址空间的管理和映射是个大的topic.我们后面再详细讲述. 内核地址空间只有1GB大小,最多只能映射1GB的物理内存.那问题来了:1GB之外物理内存怎么办? Kernel 给出的解决办法是,把1GB

Kernel那些事儿之内存管理(4) --- 未雨绸缪

上次讲的buddy system算法虽然效率很高,但是要从buddy system中分配出一个内存页块来,还是要做不少工作的,有时想想都会觉得很累. 在系统运行过程中,Kernel经常会有单个页面的申请和释放操作.为了进一步提高性能,也为了让生活变得轻松一点,Kernel采用了这样一种cache机制: Memory zone为每个CPU定义了page frame cache.Kernel会在适当的时机提前从buddy system中分配好若干单页,放在这些cache中.以后Kernel若要申请单