slub数据结构

来源:互联网 发布:易娱网络待遇好吗 编辑:程序博客网 时间:2024/06/10 01:48
        手机测试过程中,发现某个场景下,手机会概率性死机,初步调试
分析发现内核打开CONFIG_SLUB_DEBUG后,死机问题消失。
最终经过分析定位确定内核某个模块使用内存时越界了一个字节,
导致了kernel panic。
这里面就涉及到了slub在内存中存储结构。

        slub可以认为是一块内存,在内核中除了大块内存按页框从伙伴系统获取外,
内核中还有大量频繁使用的数据结构,而且这些数据结构的大小不同,
占用的空间也不足一个page。
为了效率考虑以及避免内存频繁申请释放造成的碎片,内核设计了slub分配器。

         slub的核心是内核针对不同的数据结构,比如task_struct,
预先从伙伴系统申请一块连续内存块称为slub,之后内核需要申请内存存储
task_struct时,不必从伙伴系统申请,只需要从这块slub中申请即可,
task_struct释放的时候也是将内存释放到slub,避免了伙伴系统的内存碎片,
同时也提高了内存申请的效率。


slub分配器里面有三个主要的数据结构,下面依次看下。

/*

 * Slab cache management. 可当作一个特定内存对象的缓冲池 在 SLUB 分配器中,一个 slub 就是一组连续的物理内存页框,被划分成了固定数目的对象 size:size = 对象大小 + 对象后面紧跟的下个空闲对象指针+padding区。 object_size:对象大小 offset:对象首地址 + offset = 下个空闲对象指针地址 min_partial:node结点中部分空slab缓冲区数量不能小于这个值,如果小于这个值,空闲slab缓冲区则不能够进行释放,而是将空闲slab加入到node结点的部分空slab链表中 cpu_partial:同min_partial类似,只是这个值表示的是空闲对象数量,而不是部分空slab数量,即CPU的空闲对象数量不能小于这个值,小于的情况下要去对应node结点的部分空链表中获取若干个部分空slab */struct kmem_cache {struct kmem_cache_cpu __percpu *cpu_slab;//每个cpu的对象缓存/* Used for retriving partial slabs etc */unsigned long flags;//描述slub缓冲区标志,例如poison,redzoneunsigned long min_partial;// 每个NUMA node上至少存在的部分空的slab数int size;//包含元数据的对象大小int object_size; //对象大小,不包含元数据int offset;//存放空闲对象指针的偏移,指向下个空闲的对象(指向下个空闲object的指针距本object开头的偏移)int cpu_partial;//每个cpu上最多拥有的object数量struct kmem_cache_order_objects oo; /*存放分配给slab的页框的阶数(高16位)和                                                 slab中的对象数量(低16位)*//* Allocation and freeing of slabs */struct kmem_cache_order_objects max;struct kmem_cache_order_objects min;gfp_t allocflags;/* 分配物理页面时的标志*/int refcount;/* slub cache的引用计数 */void (*ctor)(void *);//创建对象的回调函数int inuse;//每个object中实际使用的大小int align;//slab对齐大小int reserved;/* slub末尾保留字节数 */const char *name;//slab名字struct list_head list;//所有slab 缓冲池的链表,链表头为slab_caches#ifdef CONFIG_SYSFSstruct kobject kobj;/* For sysfs */#endif#ifdef CONFIG_MEMCG_KMEMstruct memcg_cache_params *memcg_params;int max_attr_size; /* for propagation, maximum size of a stored attr */#endif#ifdef CONFIG_NUMA/* * Defragmentation by allocating from a remote node. */ /* 用于NUMA架构,该值越小,越倾向于在本结点分配对象 */int remote_node_defrag_ratio;#endifstruct kmem_cache_node *node[MAX_NUMNODES];//针对NUMA内存节点创建的slab 管理结构};//per-cpu cache object 每个cpu上缓存的本地slab 缓冲区//page指针指向当前使用的slab缓冲区描述符,内核中slab缓冲区描述符与页描述符共用一个struct page结构//tid主要用于检查是否有并发,对于一些操作,操作前读取其值,//操作结束后再检查其值是否与之前读取的一致,非一致则要进行一些相应的处理,这个tid一般是递增状态,每分配一次对象加1struct kmem_cache_cpu {void **freelist;/* Pointer to next available object *//* 指向下一个空闲对象,用于快速找到对象 */unsigned long tid;/* Globally unique transaction id *///cpu idstruct page *page; /* CPU当前所使用的slab缓冲区描述符,freelist会指向此slab的下一个空闲对象 */struct page *partial;//当前cpu 部分空slub链表#ifdef CONFIG_SLUB_STATSunsigned stat[NR_SLUB_STAT_ITEMS];#endif};/* * The slab lists for all objects. */struct kmem_cache_node {spinlock_t list_lock;#ifdef CONFIG_SLAB //slab分配器struct list_head slabs_partial;/* partial list first, better asm code */struct list_head slabs_full;struct list_head slabs_free;unsigned long free_objects;unsigned int free_limit;unsigned int colour_next;/* Per-node cache coloring */struct array_cache *shared;/* shared per node */struct array_cache **alien;/* on other nodes */unsigned long next_reap;/* updated without locking */int free_touched;/* updated without locking */#endif#ifdef CONFIG_SLUB //slub分配器unsigned long nr_partial;//节点中partial slab的数量,partial指的是这个slab还有未使用的objectstruct list_head partial; //partial list 双循环链表,指向struct page结构#ifdef CONFIG_SLUB_DEBUG //调试功能atomic_long_t nr_slabs;//所有slab数量atomic_long_t total_objects;//node中总object数量struct list_head full;//node full list#endif#endif};
这三个结构从层次上看,首先是内核为了支持NUMA,引入了node节点,
节点管理者所有的slub,然后内核会针对每种特定数据结构申请一种类型
kmem_cache进行管理,为了在多核上面提升效率,在每个cpu上都申请一个kmem_cache_cpu,
用来管理每个cpu的slub缓存。
具体参考下图:


文章开头我们提到的问题的解决主要是slub debug功能和内存存储结构有关。
slub object存储结构如图:


这个图中最前面首先是object payload区,开启debug功能里面的redzone后,
object会额外申请一个机器字存储redzone内容,在函数calculate_sizes中有体现。
if ((flags & SLAB_RED_ZONE) && size == s->object_size)
size += sizeof(void *);
redzone之后存储的是freepointer,指向下个object地址。
在上面死机的例子中,驱动通过kmalloc申请了4096个字节,
使用的时候越界溢出一个字节,如果开启redzone,将会改到redzone区,
如果没有开redzone功能,将盖到freepointer,导致取下个object地址错误,
内核直接panic。
slub完整的存储结构可以阅读calculate_sizes这个函数。


参考文章:

http://www.cnblogs.com/tolimit/p/4654109.html