手机测试过程中,发现某个场景下,手机会概率性死机,初步调试
分析发现内核打开CONFIG_SLUB_DEBUG后,死机问题消失。
最终经过分析定位确定内核某个模块使用内存时越界了一个字节,
导致了kernel panic。
这里面就涉及到了slub在内存中存储结构。
slub可以认为是一块内存,在内核中除了大块内存按页框从伙伴系统获取外,
内核中还有大量频繁使用的数据结构,而且这些数据结构的大小不同,
占用的空间也不足一个page。
为了效率考虑以及避免内存频繁申请释放造成的碎片,内核设计了slub分配器。
slub的核心是内核针对不同的数据结构,比如task_struct,
预先从伙伴系统申请一块连续内存块称为slub,之后内核需要申请内存存储
task_struct时,不必从伙伴系统申请,只需要从这块slub中申请即可,
task_struct释放的时候也是将内存释放到slub,避免了伙伴系统的内存碎片,
同时也提高了内存申请的效率。
节点管理者所有的slub,然后内核会针对每种特定数据结构申请一种类型
kmem_cache进行管理,为了在多核上面提升效率,在每个cpu上都申请一个kmem_cache_cpu,
用来管理每个cpu的slub缓存。
具体参考下图:
分析发现内核打开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,redzone
unsigned 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_SYSFS
struct kobject kobj; /* For sysfs */
#endif
#ifdef CONFIG_MEMCG_KMEM
struct 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;
#endif
struct kmem_cache_node *node[MAX_NUMNODES];//针对NUMA内存节点创建的slab 管理结构
};
//per-cpu cache object 每个cpu上缓存的本地slab 缓冲区
//page指针指向当前使用的slab缓冲区描述符,内核中slab缓冲区描述符与页描述符共用一个struct page结构
//tid主要用于检查是否有并发,对于一些操作,操作前读取其值,
//操作结束后再检查其值是否与之前读取的一致,非一致则要进行一些相应的处理,这个tid一般是递增状态,每分配一次对象加1
struct kmem_cache_cpu {
void **freelist; /* Pointer to next available object *//* 指向下一个空闲对象,用于快速找到对象 */
unsigned long tid; /* Globally unique transaction id *///cpu id
struct page *page; /* CPU当前所使用的slab缓冲区描述符,freelist会指向此slab的下一个空闲对象 */
struct page *partial; //当前cpu 部分空slub链表
#ifdef CONFIG_SLUB_STATS
unsigned 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还有未使用的object
struct 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