slab源码分析--从slab初始化说起
来源:互联网 发布:免费数据恢复大师 编辑:程序博客网 时间:2024/04/28 00:07
上次说了 slab 的主要数据结构,这次从初始化开始进行源码剖析。
slab 的初始化,当然是从内核启动就开始了。内核启动的 start_kernel() 函数:
//内核的启动程序啊:)asmlinkage void __init start_kernel(void){ ... mem_init(); //内存相关初始化 kmem_cache_init(); //slab 初始化 ...}
其中调用 kmem_cache_init() 函数,这就是要进行 slab 的初始化。
/* * Initialisation. Called after the page allocator have been initialised and * before smp_init(). */ //这个函数用来建立通用缓存器,否则我们无法使用kmallocvoid __init kmem_cache_init(void){ size_t left_over; struct cache_sizes *sizes; struct cache_names *names; int i; int order; int node; //如果节点数目只有1个,那么就不能使用其他节点的shared cache if (num_possible_nodes() == 1) //貌似当前这个版本还不支持这个,参见#define num_possible_nodes() 1 use_alien_caches = 0; //后续linux版本会支持 //在slab初始化好之前,无法通过kmalloc分配初始化过程中的一些必要对象,只能使用静态的全局变量 //待slab初始化后期,再使用kmalloc动态分配对象替换全局变量!! //如前所述,先借用 initkem_list3 代替要用到的三链,每个节点对应一组三链 //initkmem_list3 是个三链数组,这里循环初始化每个节点的三链 for (i = 0; i < NUM_INIT_LISTS; i++) { //FIXME: NUM_INIT_LIST 是怎么求的,为什么等于2 * MAX_NODES+1 ? 有的版本是 3 * MAX_NODES kmem_list3_init(&initkmem_list3[i]); if (i < MAX_NUMNODES) //全局变量cache_cache指向的slab cache包含所有的kmem_cache对象,不包含cache_cache本身 //这里初始化所有内存节点kmem_cache的slab三链为空 cache_cache.nodelists[i] = NULL; } /* * Fragmentation resistance on low memory - only use bigger * page orders on machines with more than 32MB of memory. */ //全部变量slab_break_gfp_order为每个slab最多占用几个页面,用来减少碎片。 //总结起来意思就是是: (1)如果物理可用内存大于32MB,也就是可用内存充裕的时候,BREAK_GFP_ORDER_HI这个宏的值是1, //这个时候每个slab最多占用两个页面,不过此时不能横跨3个页面,除非对象大小大于8192K时才可以(一个页面大小是4K,也就是4096); //(2)如果可用内存不大于32MB,那么BREAK_GFP_ORDER_HI值为0,最多也就是允许一个页面,除非对象超了,否则不能横跨 if (num_physpages > (32 << 20) >> PAGE_SHIFT) slab_break_gfp_order = BREAK_GFP_ORDER_HI; //用来确定slab的最大大小 /* Bootstrap is tricky, because several objects are allocated * from caches that do not exist yet: * 1) initialize the cache_cache cache: it contains the struct * kmem_cache structures of all caches, except cache_cache itself: * cache_cache is statically allocated. * Initially an __init data area is used for the head array and the * kmem_list3 structures, it's replaced with a kmalloc allocated * array at the end of the bootstrap. * 2) Create the first kmalloc cache. * The struct kmem_cache for the new cache is allocated normally. * An __init data area is used for the head array. * 3) Create the remaining kmalloc caches, with minimally sized * head arrays. * 4) Replace the __init data head arrays for cache_cache and the first * kmalloc cache with kmalloc allocated arrays. * 5) Replace the __init data for kmem_list3 for cache_cache and * the other cache's with kmalloc allocated memory. * 6) Resize the head arrays of the kmalloc caches to their final sizes. */ node = numa_node_id(); //获取节点id,取得的值为0,因为初始化程序单CPU执行,且是0号 /* 1) create the cache_cache */ //初始化cache_chain 为 kmem_cache 链表头部 INIT_LIST_HEAD(&cache_chain); list_add(&cache_cache.next, &cache_chain); //设置cache着色的偏移量基本值,也就是L1缓存行的大小 cache_cache.colour_off = cache_line_size(); //宏定义L1缓存行的大小 #define cache_line_size() L1_CACHE_BYTES //初始化cache_cache的per-CPU cache,同样这里也不能使用kmalloc,需要使用静态分配的全局变量initarray_cache cache_cache.array[smp_processor_id()] = &initarray_cache.cache; //初始化slab链表,用全局变量,这里CACHE_CACHE值为0,是因为cache_cache就是系统第一个的缓存器 cache_cache.nodelists[node] = &initkmem_list3[CACHE_CACHE]; /* * struct kmem_cache size depends on nr_node_ids, which * can be less than MAX_NUMNODES. */ //buffer_size原指用来分配的对象大小,由于cache_cache是用来做 kmem_cache 的分配器的,所以 buffer_size 的大小就是 kmem_cache 的大小 //注意柔性数组的计算方法,nodelists不占据 kmem_cache的大小,所以要分开计算,并且注意nodelists数组在UMA架构只有一个节点,所以只有1个kmem_list3的指针 cache_cache.buffer_size = offsetof(struct kmem_cache, nodelists) + nr_node_ids * sizeof(struct kmem_list3 *);#if 0 #if DEBUG cache_cache.obj_size = cache_cache.buffer_size;#endif#endif //注意这里又一次计算了 buffer_size 的大小,通过这次计算将buffer_size以 缓存行 为单位进行上边界对齐 //计算分配的对象与cache line的大小对齐后的大小 cache_cache.buffer_size = ALIGN(cache_cache.buffer_size, cache_line_size()); //计算对象大小的倒数,用于计算对象在slab中的索引 cache_cache.reciprocal_buffer_size = reciprocal_value(cache_cache.buffer_size); //计算cache_cache的剩余空间以及slab中对象的数目,order决定了slab的大小(PAGE_SIZEE<<order) for (order = 0; order < MAX_ORDER; order++) { //#define MAX_ORDER 11 cache_estimate(order, cache_cache.buffer_size, //buffer_size已经和cache line对齐过 cache_line_size(), 0, &left_over, &cache_cache.num); //计算cache_cache的对象数目 if (cache_cache.num) //num不为0意味着struct kmem_cache对象创建成功,退出,所以在此处就会确定order的大小,以赋值给gfporder break; } BUG_ON(!cache_cache.num); //断言 cache_cache.gfporder = order; //gfporder表示本slab包含2^gfproder个页面,注意这里就从上面的order赋值给 gfporder //colour_off 就是上面初始化的 L1_CACHE_BYTES,既然已经知道 L1 缓存行的大小,我们上步又计算出了浪费空间的大小 //那么用浪费空间的大小 / L1 缓存行的大小,就得出当前可用 colour 的数目,这个数目是累加且循环的,可以为0 cache_cache.colour = left_over / cache_cache.colour_off; //确定可用 colour 的数目,单位是 colour_off //确定slab描述符以及kmem_bufctl_t数组 针对缓存行对齐后的大小 cache_cache.slab_size = ALIGN(cache_cache.num * sizeof(kmem_bufctl_t) + sizeof(struct slab), cache_line_size()); /* 2+3) create the kmalloc caches */ sizes = malloc_sizes; //malloc_sizes数组保存着要分配的大小 names = cache_names; //cache_name保存cache名 /* * Initialize the caches that provide memory for the array cache and the * kmem_list3 structures first. Without this, further allocations will * bug. */ //首先创建struct array_cache 和 struct kmem_list3 所用的通用缓存器general cache,它们是后续初始化动作的基础 //INDEX_AC是计算local cache所用的struct arraycache_init对象在kmalloc size中的索引,即属于哪一级别大小的general cache,创建此大小级别的cache为local cache所用 sizes[INDEX_AC].cs_cachep = kmem_cache_create(names[INDEX_AC].name, sizes[INDEX_AC].cs_size, ARCH_KMALLOC_MINALIGN, ARCH_KMALLOC_FLAGS|SLAB_PANIC, //#define ARCH_KMALLOC_FLAGS SLAB_HWCACHE_ALIGN,已经对齐过的标记 NULL, NULL); if (INDEX_AC != INDEX_L3) { //如果struct kmem_list3 和 struct arraycache_init对应的kmalloc size索引不同,即大小属于不同的级别, //则创建struct kmem_list3所用的cache,否则共用一个cache sizes[INDEX_L3].cs_cachep = kmem_cache_create(names[INDEX_L3].name, sizes[INDEX_L3].cs_size, ARCH_KMALLOC_MINALIGN, ARCH_KMALLOC_FLAGS|SLAB_PANIC, NULL, NULL); } slab_early_init = 0;//创建完上述两个通用缓存器后,slab early init阶段结束,在此之前,不允许创建外置式slab //sizes->cs_size 初值为是malloc_sizes[0],值应该是从32开始 while (sizes->cs_size != ULONG_MAX) { //循环创建kmalloc各级别的通用缓存器,ULONG_MAX 是最大索引值, /* * For performance, all the general caches are L1 aligned. * This should be particularly beneficial on SMP boxes, as it * eliminates(消除) "false sharing". * Note for systems short on memory removing the alignment will * allow tighter(紧的) packing of the smaller caches. */ if (!sizes->cs_cachep) { sizes->cs_cachep = kmem_cache_create(names->name, sizes->cs_size, ARCH_KMALLOC_MINALIGN, ARCH_KMALLOC_FLAGS|SLAB_PANIC, NULL, NULL); }#ifdef CONFIG_ZONE_DMA //如果配置DMA,那么为每个kmem_cache 分配两个,一个DMA,一个常规 sizes->cs_dmacachep = kmem_cache_create( names->name_dma, sizes->cs_size, ARCH_KMALLOC_MINALIGN, ARCH_KMALLOC_FLAGS|SLAB_CACHE_DMA| SLAB_PANIC, NULL, NULL);#endif sizes++; //都是数组名,直接++,进行循环迭代,由小到大分配各个大小的general caches,最大为ULONG_MAX names++; } /* 4) Replace the bootstrap head arrays */ { struct array_cache *ptr; //现在要申请arraycache替换之前的initarray_cache ptr = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL); //GFP_KERNEL 可睡眠申请 //关中断 local_irq_disable(); BUG_ON(cpu_cache_get(&cache_cache) != &initarray_cache.cache); memcpy(ptr, cpu_cache_get(&cache_cache), sizeof(struct arraycache_init)); //将cache_cache中per-cpu对应的array_cache拷贝到ptr /* * Do not assume that spinlocks can be initialized via memcpy: */ spin_lock_init(&ptr->lock); cache_cache.array[smp_processor_id()] = ptr; //再让它指向ptr? local_irq_enable(); ptr = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL); local_irq_disable(); BUG_ON(cpu_cache_get(malloc_sizes[INDEX_AC].cs_cachep) != &initarray_generic.cache); memcpy(ptr, cpu_cache_get(malloc_sizes[INDEX_AC].cs_cachep), sizeof(struct arraycache_init)); /* * Do not assume that spinlocks can be initialized via memcpy: */ spin_lock_init(&ptr->lock); malloc_sizes[INDEX_AC].cs_cachep->array[smp_processor_id()] = ptr; local_irq_enable(); } /* 5) Replace the bootstrap kmem_list3's */ { int nid; /* Replace the static kmem_list3 structures for the boot cpu */ init_list(&cache_cache, &initkmem_list3[CACHE_CACHE], node); for_each_online_node(nid) { init_list(malloc_sizes[INDEX_AC].cs_cachep, &initkmem_list3[SIZE_AC + nid], nid); if (INDEX_AC != INDEX_L3) { init_list(malloc_sizes[INDEX_L3].cs_cachep, &initkmem_list3[SIZE_L3 + nid], nid); } } } /* 6) resize the head arrays to their final sizes */ { struct kmem_cache *cachep; mutex_lock(&cache_chain_mutex); list_for_each_entry(cachep, &cache_chain, next) if (enable_cpucache(cachep)) //这个函数先暂时不剖析,是对本地缓存的处理 BUG(); mutex_unlock(&cache_chain_mutex); } /* Annotate slab for lockdep -- annotate the malloc caches */ init_lock_keys(); /* Done! */ g_cpucache_up = FULL; /* * Register a cpu startup notifier callback that initializes * cpu_cache_get for all new cpus */ register_cpu_notifier(&cpucache_notifier); /* * The reap timers are started later, with a module init call: That part * of the kernel is not yet operational. */}
这个函数就是 slab 初始化的主干。
执行流程:
- 使用 initkmem_list3 和 intarray_cache 两个静态量,再加上手动填充cache_cache 的其他变量 ,完成对 cache_cache 的初始化。这是用来缓存 kmem_cache 的,相当于是缓存器的缓存器:)
- 创建 kmalloc 所用的其他通用缓存器:
- cache 的名称和大小放在 names[] 和 malloc_sizes[] 两个数组中,对应大小的 cache 可以从 malloc_sizes[] 中找到,索引就是要分配对象的大小。
- 先创建 INDEX_AC 和 INDEX_L3 下标的 cache(array_cache 和 三链,如果大小一样,只创建一次)。
- 循环创建 size 数组中各个大小的 cache。
- 替换静态本地 cache 全局变量:
- 替换 malloc_sizes[INDEX_AC].cs_cachep 的 local cache,原本指向静态变量 initarray_cache.cache。
- 替换 malloc_sizes[INDEX_AC].cx_cachep 的 local cache,原本指向静态变量 initarray_generic.cache。 ( kmem_cache_create() 中调用 setup_cpu_cache() 函数会使用该静态变量)。
- 替换静态三链:
- 替换 cache_cache 三链,原本指向静态变量的 initkmem_list3。
- 替换 malloc_sizes[INDEX_AC].cs_cachep 三链,原本指向静态变量 initkmem_list3。
- 更新初始化进度
静态初始化的结构比如:
/* internal cache of cache description objs *///这就是静态定义了第一个即通用缓存器static struct kmem_cache cache_cache = { .batchcount = 1, .limit = BOOT_CPUCACHE_ENTRIES, .shared = 1, .buffer_size = sizeof(struct kmem_cache), .name = "kmem_cache", //卧槽,名字就叫 kmem_cache};
或者
static struct arraycache_init initarray_cache /*__initdata*/ = { {0, BOOT_CPUCACHE_ENTRIES, 1, 0} };static struct arraycache_init initarray_generic = { {0, BOOT_CPUCACHE_ENTRIES, 1, 0} };
该函数其他辅助函数有:
1. 三链初始化函数
static void kmem_list3_init(struct kmem_list3 *parent){ INIT_LIST_HEAD(&parent->slabs_full); INIT_LIST_HEAD(&parent->slabs_partial); INIT_LIST_HEAD(&parent->slabs_free); parent->shared = NULL; parent->alien = NULL; parent->colour_next = 0; spin_lock_init(&parent->list_lock); parent->free_objects = 0; parent->free_touched = 0;}
这个没什么卵用,就只是初始化。
2. 对齐宏
#define ALIGN(x,a) __ALIGN_MASK(x,(typeof(x))(a)-1)#define __ALIGN_MASK(x,mask) (((x)+(mask))&~(mask))
这个宏用来控制内存以 a 的大小为基本单位对齐,0~7为一组,以此类推。
关于这个宏详细解释可参照:内核宏ALIGN的含义
3. cache_estimate()函数
这个函数用来计算每个 slab 中对象的数目以及浪费空间的大小,注释中的管理对象就是前面所说的管理者。
/* * Calculate the number of objects and left-over bytes for a given buffer size. */static void cache_estimate(unsigned long gfporder, size_t buffer_size, size_t align, int flags, size_t *left_over, unsigned int *num){ int nr_objs; size_t mgmt_size; //slab 大小为 2^gfporder 个页面 size_t slab_size = PAGE_SIZE << gfporder; //#define PAGE_SIZE 0x400 (即1024),FIXME: 难道一个页面是 1K ,怎么可能? /* * The slab management structure can be either off the slab or //有off-slab和on-slab两种方式 * on it. For the latter case, the memory allocated for a * slab is used for: //这段内存被用来存储: * * - The struct slab //slab结构体 * - One kmem_bufctl_t for each object //每个对象的kmem_bufctl_t * - Padding to respect alignment of @align //对齐的大小 * - @buffer_size bytes for each object //每个对象的大小 * * If the slab management structure is off the slab, then the * alignment will already be calculated into the size. Because //如果是off-slab,align早已被计算出来 * the slabs are all pages aligned, the objects will be at the //因为所有的页面对齐过了,对象申请时会处在正确的位置 * correct alignment when allocated. */ //对于外置slab,没有slab管理对象问题,直接用申请空间除以对象大小就是对象个数 if (flags & CFLGS_OFF_SLAB) { //外置slab不存在管理对象,全部用于存储slab对象 mgmt_size = 0; //所以对象个数 = slab大小 / 对象大小 nr_objs = slab_size / buffer_size; //注意buffer_size已经和cache line对齐过了 //对象个数不许超限 if (nr_objs > SLAB_LIMIT) nr_objs = SLAB_LIMIT; } else { /* * Ignore padding for the initial guess. The padding * is at most @align-1 bytes, and @buffer_size is at * least @align. In the worst case, this result will * be one greater than the number of objects that fit * into the memory allocation when taking the padding * into account. */ //内置式slab,slab管理对象与slab对象都在一片内存中,此时slab页面包含: //一个struct slab 对象,一个kmem_bufctl_t 类型数组(kmem_bufctl_t 数组的项数和slab对象数目相同) //slab大小需要减去管理对象大小,所以对象个数为 剩余大小 / (每个对象大小 + sizeof(kmem_bufctl_t)), 它们是一一匹配的关系 nr_objs = (slab_size - sizeof(struct slab)) / (buffer_size + sizeof(kmem_bufctl_t)); /* * This calculated number will be either the right * amount, or one greater than what we want. */ //如果对齐后超过slab 总大小 ,需要减去一个对象 if (slab_mgmt_size(nr_objs, align) + nr_objs*buffer_size > slab_size) nr_objs--; //对象个数不许超限 if (nr_objs > SLAB_LIMIT) nr_objs = SLAB_LIMIT; //计算 管理对象以缓存行 对齐后的总大小 mgmt_size = slab_mgmt_size(nr_objs, align); } //得出slab最终对象个数 *num = nr_objs; //前面已经得到了slab管理对象大小(外置为0,内置也已计算),这样就可以最终的出slab最终浪费空间大小 *left_over = slab_size - nr_objs*buffer_size - mgmt_size;}
5. malloc_sizes[]表
我们的需要通过 kmem_cache_create() 函数创建通用缓存器,最终实际会向伙伴系统申请。那创建多大呢?这个有一个很有意思的地方:
首先看 INDEX_AC:
#define INDEX_AC index_of(sizeof(struct arraycache_init))
而 index_of 是这样的:
/* * This function must be completely optimized away if a constant is passed to * it. Mostly the same as what is in linux/slab.h except it returns an index. */static __always_inline int index_of(const size_t size) //这个函数用来选择大小的,可作为mallloc_size的参数 { extern void __bad_size(void); if (__builtin_constant_p(size)) { int i = 0;#define CACHE(x) \ if (size <=x) \ //适配一个刚足够容纳size的大小 return i; \ else \ i++; //不成功,增大继续适配#include "linux/kmalloc_sizes.h"#undef CACHE __bad_size(); } else __bad_size(); return 0;}
为什么说 index_of 能确定大小呢,这真是有点玄幻了。注意这一句
#include "linux/kmalloc_sizes.h"
而 linux/kmalloc_sizes.h 中的内容是这样的:
#if (PAGE_SIZE == 4096) CACHE(32)#endif CACHE(64)#if L1_CACHE_BYTES < 64 CACHE(96)#endif CACHE(128)#if L1_CACHE_BYTES < 128 CACHE(192)#endif CACHE(256) CACHE(512) CACHE(1024) CACHE(2048) CACHE(4096) CACHE(8192) CACHE(16384) CACHE(32768) CACHE(65536) CACHE(131072)#if KMALLOC_MAX_SIZE >= 262144 CACHE(262144)#endif#if KMALLOC_MAX_SIZE >= 524288 CACHE(524288)#endif#if KMALLOC_MAX_SIZE >= 1048576 CACHE(1048576)#endif#if KMALLOC_MAX_SIZE >= 2097152 CACHE(2097152)#endif#if KMALLOC_MAX_SIZE >= 4194304 CACHE(4194304)#endif#if KMALLOC_MAX_SIZE >= 8388608 CACHE(8388608)#endif#if KMALLOC_MAX_SIZE >= 16777216 CACHE(16777216)#endif#if KMALLOC_MAX_SIZE >= 33554432 CACHE(33554432)#endif
在 index_of 函数中局部定义了 CACHE(x) 宏,并把这个头文件引入,你可以理解为把头文件中所有的内容都加入到了 index_of 函数的那一句所在的位置。那么,程序接下来会不停地执行者个局部宏 CACHE(X),而这个宏又是有函数意义的,当匹配到正确的正确的size,会直接返回 i 值(真是神一样的技巧,相当于用宏来循环,我算是服了)。通过这种方式就确定了要申请的大小对应在 malloc_sizes 表中下标 i 的值。
那么只知道下标 i 的值可不行,malloc_sizes[]表是什么时候初始化的呢? 答案是它是静态初始化的。
/* * These are the default caches for kmalloc. Custom caches can have other sizes. */struct cache_sizes malloc_sizes[] = { //通用缓存器的大小由malloc_size表决定#define CACHE(x) { .cs_size = (x) },#include <linux/kmalloc_sizes.h> //终于明白这是什么用法了 CACHE(ULONG_MAX)#undef CACHE};
在上面的代码中,同样定义了 CACHE(X),不过这个可和之前的那个作用不一样,它们俩都是局部宏,使用完即 undef,不会产生影响。
在这里注意,这个宏被定义为 “{ .cs_size = (x) },”,注意到这个逗号了没有?下面会同样引入 kmalloc_size 头文件中的一大堆 CACHE(x),每个都是这种形式,这不正是数组的初始化方式吗?只不过每个数据类型都是 cache_sizes 类型罢了。相当于int array[] = { {1}, {2}, …},大概就是这样。cache_names[] 表同理。
cache_sizes 类型如下:
/* Size description struct for general caches. */struct cache_sizes { size_t cs_size; //通用缓存器的大小 struct kmem_cache *cs_cachep; //通用缓存器的描述符指针#ifdef CONFIG_ZONE_DMA struct kmem_cache *cs_dmacachep;#endif};
于是,通过上面这种初始化方式,就把 malloc_sizes[] 表中按照由小到大顺序将全部 cache_sizes的 cs_size 成员初始化了。所以 malloc_size[i] 对应的就是描述大小为 i 的缓存器的 cache_sizes 结构,通过 .cs_cachep 就可以得到对应大小的通用缓存器!
如果是 DMA,那么会配置两种通用缓存器。
6. kmem_cache_create()函数
这个函数是核心,对该函数的详细分析会在下一篇博客出现,很长的:)
7. kmalloc()函数
也是重点,以后会单独剖析。
8. init_list()函数
/* * swap the static kmem_list3 with kmalloced memory */static void init_list(struct kmem_cache *cachep, struct kmem_list3 *list, int nodeid){ struct kmem_list3 *ptr; ptr = kmalloc_node(sizeof(struct kmem_list3), GFP_KERNEL, nodeid); BUG_ON(!ptr); local_irq_disable(); memcpy(ptr, list, sizeof(struct kmem_list3)); /* * Do not assume that spinlocks can be initialized via memcpy: */ spin_lock_init(&ptr->list_lock); MAKE_ALL_LISTS(cachep, ptr, nodeid); cachep->nodelists[nodeid] = ptr; local_irq_enable();}
这是利用 kmalloc 申请三链和静态三链交换的函数,为什么说是利用 kmalloc 呢,且看它调用的 kmalloc_node() 函数:
static inline void *kmalloc_node(size_t size, gfp_t flags, int node){ return kmalloc(size, flags);}
就是这样,我们为了解决“鸡与蛋”的问题,首先设定几个静态变量,比如三链 initkmem_list3,先用它合成我们的 cache_cache,然后有了 cache_cache,这是为缓存器制定规则的缓存器,通过它我们就可以随意合成大小其他缓存器了。有了各种大小的缓存器,在 kmem_cache_init() 函数后半部分就可以用缓存器来 kmalloc 分配对象,替换之前所有用来辅助 cache_cache 的静态数据结构(如 initarray_cache, initkmem_list3),以后这些静态数据结构就不再使用了。
cache_cache 为缓存器大小制定了一种规则,这说明所有缓存器自身大小也是一样的,只不过它们的 buffer_size 字段描述的用来分配的对象大小是不一样的,以后不同大小的对象就靠这个找不同的缓存器分配就行了。
参考:
- kmem_cache_init初始化文字解析
- Linux Slab分配器(一)–概述
PS: 最近是怎么了,有点慌,觉得学了几天才学了这么一点东西,感觉好没有成就感。嗯,没慌,搞技术的要静的下心来。
- slab源码分析--从slab初始化说起
- memcache源码分析:slab结构与初始化
- slab源码分析--setup_cpu_cache函数
- slab源码分析--销毁函数
- memcached源码分析-----slab automove和slab rebalance
- slab
- slab源码分析--主要数据结构分析
- slab源码分析--kmalloc函数分析
- LDD3源码分析之slab高速缓存
- LDD3源码分析之slab高速缓存
- LDD3源码分析之slab高速缓存
- LDD3源码分析之slab高速缓存
- memcached源码分析-----slab内存分配器
- linux内存源码分析 - SLAB分配器概述
- linux内存源码分析 - SLAB分配器概述
- slab源码分析--缓存器的创建
- LDD3源码分析之slab高速缓存
- 详解slab机制(4) slab初始化
- NS2入门学习(五)之分裂对象模型和TclCL
- python类中super()和__init__()的区别
- Three.JS学习 7:使用Canvas画一个时钟
- Java,你用它的理由
- javascript链表
- slab源码分析--从slab初始化说起
- JSP中application用法
- HBase的rowkey设计
- HTML5 自定义属性 data-*属性名一定要小写吗?
- Gauss型求积公式
- 关于智能家居的一点想法
- 计算机视觉整理库
- 将faad2转码方法移植到C++
- uImage zImage