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


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值