技术标签: struct Linux 内存管理 cache linux module list object
五:kmem_cache_create()分析
我们以一个例子来跟踪分析一下slab的机制:
下面是一个测试模块的代码:
#include <linux/config.h>
#include <linux/module.h>
#include <linux/slab.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ericxiao <[email protected]>");
MODULE_DESCRIPTION("slab test module");
static kmem_cache_t *test_cachep = NULL;
struct slab_test
{
int val;
};
void fun_ctor(struct slab_test *object , kmem_cache_t *cachep , unsigned long flags )
{
printk("in ctor fuction .../n");
object->val = 1;
}
void fun_dtor(struct slab_test *object , kmem_cache_t *cachep , unsigned long flags)
{
printk("in dtor fuction .../n");
object -> val = 0;
}
static int __init init(void)
{
struct slab_test *object = NULL;
printk("slab test moudle init ... /n");
test_cachep = kmem_cache_create("test_cachep",sizeof(struct slab_test),0,SLAB_HWCACHE_ALIGN, /
fun_ctor, fun_dtor);
if(!test_cachep)
return;
object = kmem_cache_alloc( test_cachep, GFP_KERNEL );
if(object)
{
printk("alloc one val = %d/n",object->val);
kmem_cache_free( test_cachep, object );
object = NULL;
}else
return;
object = kmem_cache_alloc( test_cachep, GFP_KERNEL );
if(object)
{
printk("alloc two val = %d/n",object->val);
kmem_cache_free( test_cachep, object );
object = NULL;
}else
return;
}
static void fini(void)
{
printk("test moudle exit .../n");
if(test_cachep)
kmem_cache_destroy( test_cachep );
}
module_init(init);
module_exit(fini);
我们把模块加载之后,用dmesg的命令可以看到如下的输出信息:
slab test moudle init ...
in ctor fuction ...
in ctor fuction ...
……
alloc one val = 1
alloc two val = 1
将模块卸载之后可以看到:
test moudle exit ...
in dtor fuction ...
……
从上我们可以看到,当从cache中分配一个对象时,会初始化很多object(dmesg输出信息中,出现多次in ctor fuction ...),当一个对象释放时,并没有马上调用其析构函数。
我们来看看具体的代码
kmem_cache_create()是创建一个专用cache.同样的,所有专用缓冲区头部也由一个slab分配器维护,它的名字叫:cache_cache。其中每个大个对象的大小均为sizeof(cache).它是静态初始化的:
static kmem_cache_t cache_cache = {
.lists = LIST3_INIT(cache_cache.lists),
.batchcount = 1,
.limit = BOOT_CPUCACHE_ENTRIES,
.objsize = sizeof(kmem_cache_t),
.flags = SLAB_NO_REAP,
.spinlock = SPIN_LOCK_UNLOCKED,
.name = "kmem_cache",
#if DEBUG
.reallen = sizeof(kmem_cache_t),
#endif
};
Kmem_cache_creat的代码在slab.c中,如下所示:
//参数含义:
//name:cache名字。Align:对齐量.flags:分配标志,ctor:初始化函数 ,dtor析构函数
kmem_cache_t *kmem_cache_create (const char *name, size_t size, size_t align,
unsigned long flags, void (*ctor)(void*, kmem_cache_t *, unsigned long),
void (*dtor)(void*, kmem_cache_t *, unsigned long))
{
size_t left_over, slab_size;
kmem_cache_t *cachep = NULL;
//参数检测名字不能为空,有析构函数,必须要用初始化函数,不能在中断中,对像不能太大也不能太小(不//能超过2^5个页)
if ((!name) ||in_interrupt() ||(size < BYTES_PER_WORD) ||
(size > (1<<MAX_OBJ_ORDER)*PAGE_SIZE) ||
(dtor && !ctor)) {
printk(KERN_ERR "%s: Early error in slab %s/n",
__FUNCTION__, name);
BUG();
}
if (flags & SLAB_DESTROY_BY_RCU)
BUG_ON(dtor);
//flag参数的有效性检查
if (flags & ~CREATE_MASK)
BUG();
//align参数的调整。如无特别要求,align设为零,flag设为SLAB_HWCACHE_ALIGN。按照处理器缓//存对齐
if (align) {
flags &= ~(SLAB_RED_ZONE|SLAB_STORE_USER);
} else {
if (flags & SLAB_HWCACHE_ALIGN) {
//cache_line_size取得处理平始的cache line.前面已经分析过
align = cache_line_size();
//如果对象太小,为了提高利用了,取cache line半数对齐
while (size <= align/2)
align /= 2;
} else {
align = BYTES_PER_WORD;
}
}
//从cache_cache中分得一个缓存描述符 kmem_cache_alloc函数在后面讲述
cachep = (kmem_cache_t *) kmem_cache_alloc(&cache_cache, SLAB_KERNEL);
if (!cachep)
goto opps;
//初始化
memset(cachep, 0, sizeof(kmem_cache_t));
//把大小按照BYTES_PER_WORD 对齐。BYTES_PER_WORD也即处理器的地址单元,在i32 为32
if (size & (BYTES_PER_WORD-1)) {
size += (BYTES_PER_WORD-1);
size &= ~(BYTES_PER_WORD-1);
}
//如果size 大于1/8 个页面。就把slab放到缓存区的外面
if (size >= (PAGE_SIZE>>3))
flags |= CFLGS_OFF_SLAB;
//使size按照align对齐
size = ALIGN(size, align);
if ((flags & SLAB_RECLAIM_ACCOUNT) && size <= PAGE_SIZE) {
cachep->gfporder = 0;
cache_estimate(cachep->gfporder, size, align, flags,
&left_over, &cachep->num);
} else {
//在这里,为cache中每个slab的大小以及slab中的对象个数取得一个平衡点
do {
unsigned int break_flag = 0;
cal_wastage:
//cache_estimate:指定slab的大小后,返回slab中的对像个数
//以及剩余空间数
cache_estimate(cachep->gfporder, size, align, flags,
&left_over, &cachep->num);
if (break_flag)
break;
if (cachep->gfporder >= MAX_GFP_ORDER)
break;
if (!cachep->num)
goto next;
/*offslab_limit 指定了kmalloc分配的内存块中,可以与一个struct slab实例共同存储的kmem_bufctl_t实例的最大数目。
如果slab中对象的数目超出该值,则无法分配所需的空间,因此需要将gfp_order减1,重新计算,然后退出循环。
*/
if (flags & CFLGS_OFF_SLAB &&
cachep->num > offslab_limit) {
/* This num of objs will cause problems. */
cachep->gfporder--;
break_flag++;
goto cal_wastage;
}
/*
* Large num of objs is good, but v. large slabs are
* currently bad for the gfp()s.
*/
if (cachep->gfporder >= slab_break_gfp_order)
break;
if ((left_over*8) <= (PAGE_SIZE<<cachep->gfporder))
break; /* Acceptable internal fragmentation. */
next:
cachep->gfporder++;
} while (1);
}
if (!cachep->num) {
//出现意外,打印出常现的oops错误
printk("kmem_cache_create: couldn't create cache %s./n", name);
kmem_cache_free(&cache_cache, cachep);
cachep = NULL;
goto opps;
}
使slab大小按照align对齐
slab_size = ALIGN(cachep->num*sizeof(kmem_bufctl_t)
+ sizeof(struct slab), align);
if (flags & CFLGS_OFF_SLAB && left_over >= slab_size) {
//如果剩余空间足间大,就把slab描述符放到缓存区里面
flags &= ~CFLGS_OFF_SLAB;
left_over -= slab_size;
}
if (flags & CFLGS_OFF_SLAB) {
//如果slab描述符依然只能放到缓存区外面。则取slab_size大小的实际值
//也就是说不需要与alin 对齐了
slab_size = cachep->num*sizeof(kmem_bufctl_t)+sizeof(struct slab);
}
//着色偏移量,至少为一个cache_size.若align值是自己指定的,且超出了一个cache size.这样//值就会取设定的align
cachep->colour_off = cache_line_size();
if (cachep->colour_off < align)
cachep->colour_off = align;
//颜色的总数,为剩余的空间数/着色偏移量
//从这里我们可以看到,如果偏移量太少,着色机制是没有任何意义的
//这是值得提醒的是colour_next没有被特别赋值,即为默认值0
cachep->colour = left_over/cachep->colour_off;
//colour,剩余空间left_over以align:cachep->colour_off大小平分,能分的的份数。
//这样每个slab的偏移:着色就等于i*align+dsize i是0..colour,dszie是slab描述符的大小。
//加入left_over=100,colour_off=10,dsize=0;那么slab0的偏移就是0,slab1--10,slab2--20,slab3--30。。。
//各种成员的初始化
cachep->slab_size = slab_size;
cachep->flags = flags;
cachep->gfpflags = 0;
if (flags & SLAB_CACHE_DMA)
cachep->gfpflags |= GFP_DMA;
spin_lock_init(&cachep->spinlock);
cachep->objsize = size;
/* NUMA */
INIT_LIST_HEAD(&cachep->lists.slabs_full);
INIT_LIST_HEAD(&cachep->lists.slabs_partial);
INIT_LIST_HEAD(&cachep->lists.slabs_free);
//如果slab描述符是放在缓存区外面的。那就为slab描述符指定一个分配缓存
if (flags & CFLGS_OFF_SLAB)
cachep->slabp_cache = kmem_find_general_cachep(slab_size,0);
cachep->ctor = ctor;
cachep->dtor = dtor;
cachep->name = name;
/* Don't let CPUs to come and go */
lock_cpu_hotplug();
//g_cpucache_up:判断普通缓存是否就绪的标志
//NONE是初始值 PARTIAL:是一个中间的状态,即普通缓存正在初始化
//FULL:普通缓存已经初始化完成
if (g_cpucache_up == FULL) {
enable_cpucache(cachep);
} else {
if (g_cpucache_up == NONE) {
/* Note: the first kmem_cache_create must create
* the cache that's used by kmalloc(24), otherwise
* the creation of further caches will BUG().
*/
cachep->array[smp_processor_id()] =
&initarray_generic.cache;
g_cpucache_up = PARTIAL;
} else {
cachep->array[smp_processor_id()] =
kmalloc(sizeof(struct arraycache_init),
GFP_KERNEL);
}
BUG_ON(!ac_data(cachep));
ac_data(cachep)->avail = 0;
ac_data(cachep)->limit = BOOT_CPUCACHE_ENTRIES;
ac_data(cachep)->batchcount = 1;
ac_data(cachep)->touched = 0;
cachep->batchcount = 1;
cachep->limit = BOOT_CPUCACHE_ENTRIES;
cachep->free_limit = (1+num_online_cpus())*cachep->batchcount
+ cachep->num;
}
cachep->lists.next_reap = jiffies + REAPTIMEOUT_LIST3 +
((unsigned long)cachep)%REAPTIMEOUT_LIST3;
//查看是否有相同名字的cache
down(&cache_chain_sem);
{
struct list_head *p;
mm_segment_t old_fs;
old_fs = get_fs();
set_fs(KERNEL_DS);
list_for_each(p, &cache_chain) {
kmem_cache_t *pc = list_entry(p, kmem_cache_t, next);
char tmp;
/*
* This happens when the module gets unloaded and
* doesn't destroy its slab cache and noone else reuses
* the vmalloc area of the module. Print a warning.
*/
#ifdef CONFIG_X86_UACCESS_INDIRECT
if (__direct_get_user(tmp,pc->name)) {
#else
if (__get_user(tmp,pc->name)) {
#endif
printk("SLAB: cache with size %d has lost its "
"name/n", pc->objsize);
continue;
}
if (!strcmp(pc->name,name)) {
printk("kmem_cache_create: duplicate "
"cache %s/n",name);
up(&cache_chain_sem);
unlock_cpu_hotplug();
BUG();
}
}
set_fs(old_fs);
}
//将cache挂至cache_chain链
list_add(&cachep->next, &cache_chain);
up(&cache_chain_sem);
unlock_cpu_hotplug();
opps:
if (!cachep && (flags & SLAB_PANIC))
panic("kmem_cache_create(): failed to create slab `%s'/n",
name);
return cachep;
}
首先我们遇到的问题是第一个鸡与鸡蛋的问题:新建cache描述符是从cache_cache中分配cache描述符,那cache_cache是从何而来呢?cache_cache是静态定义的一个数据结构,只要静态初始化它的成员就可以了。另一个鸡与鸡蛋的问题就是cache中array数组的初始化问题。例如:
cachep->array[smp_processor_id()] =
kmalloc(sizeof(struct arraycache_init),
GFP_KERNEL);
也就是说从普通缓存中分得空间,那普通缓存区中的arry如何取得空间呢?这也是一个静态定义的数组:initarray_generic.cache。我们以后再详细分析内存各子系统的初始化过程。详情请关注本站更新。
另外,我们也接触到了着色部份的代码。如下所示:
cachep->colour_off = cache_line_size();
if (cachep->colour_off < align)
cachep->colour_off = align;
cachep->colour = left_over/cachep->colour_off;
着色的原理在前面已经分析过了。Colour_off:每一个slab中偏移值。以colour:颜色的总数,即最大的偏移位置,它的大小为剩余大小/偏移值,colour_next初始化为零。
举例说明:
Colour_off = 32 colour = 2; colour_next = 0
第一个slab偏移colour_next* Colour_off = 0*32 = 0 然后colour_next加1。即为1
第二个slab偏移colour_next* Colour_off = 1*32 = 32然后colour_next加1。即为2
第三个slab偏移colour_next* Colour_off = 2*32 = 64然后colour_next加1。即为3,由于colour为2。所以,colour_next = 0;
第四个slab偏移colour_next* Colour_off = 0*32 = 0
……
另外:要注意的是slab大小计算的时候:
slab_size = ALIGN(cachep->num*sizeof(kmem_bufctl_t) + sizeof(struct slab), align);
虽然在struct slab里没有定义kmem_bufctl_t.但在为slab申请空间的时候申请了num个kmem_bufctl_t的多余空间,也就是说kmem_bufctl_t数组紧放在slab描述符之后
此外,array被初始化了arraycache_init大小。
struct arraycache_init {
struct array_cache cache;
void * entries[BOOT_CPUCACHE_ENTRIES];
};
为什么要这样做?我们在后面再给出分析
1M,作为云服务器最低的带宽配置,到底能承受多大的流量?在选配云服务器带宽的时候,看到带宽大小的时候,头疼病总是发作,带宽买小了,网站太卡,用户体验不好影响业务,带宽买大了,又实在浪费。那么云服务器的带宽,到底多大够用?1M的带宽,流量承受极限是多少?带宽知识扫盲首先普及一下带宽的一些基础知识。云服务器的带宽,指的是出网带宽,用户发起请求,服务器发送数据给终端时,会占用这一部分的带宽。假如云服务器...
缘由今天阅读 深入理解nginx 的第五章,前半部分讲解了upstream的问题,并且指导完成了一个例子。upstream先来回答upstream拿来做什么:客户端请求nginx,nginx将请求转发给第三方服务器(也称上游服务器),第三方服务器返回响应,nginx将响应转发给客户端。这时,nginx作为了一个代理服务器。一般,我们只是把第三方服务器的内容原封不动的返回给客户端
说明: 1、moren
网上查看分段查询的例子,用的最多的是LAG和LEAD统计函数,Lag和Lead函数可以在一次查询中取出同一字段的前N行的数据和后N行的值。这种操作可以使用对相同表的表连接来实现,不过使用LAG和LEAD有更高的效率。例如:create table TEST( GRADE NUMBER not null, STUID VARCHAR2(4));insert into test (GR
1、前台JSP页面<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> ECharts // 配置路径 require.config({ paths: { echarts: '${ctx}/plugins/echarts-
数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读。下面通过事例一一阐述它们的概念与联系。Read uncommitted读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。事例:老板要给程序员发工资...
功能 描述 :功能需求 1 实现核心库文件: 每个文件名为拼音本身.dat, 内容为对应的汉字 和 汉字的频次, 每个占一行。需求分析1 :我们可以手动建立 库文件, 然后将手动给文件中设置一些汉字, 默认频次设置为1 , 最大不超过10(效率显得有点低)注意: 文件中的空行可以跳过, 文件中如果某行出现的格式问题,可以忽略该行现在库文件 创建好了, 我们可以模拟根据拼音查找汉字了。创建库文件内容的时候 : 为了保险起见,1 采用 strings.Join(slice, substr) 将
一、JAXP DOM解析 javax.xml.parsers 1、获得document对象 //获得实例工厂 *javax.xml.parsers.DocumentBuilderFactory DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //获得解析 *javax.xml.parsers.D...
返回的直接是星期一到日,如有需要把星期修改为周即可。+ (NSString *)dateisweek:(NSDate *)date { NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; NSDateComponents *...
增强现实(Augmented Reality, AR)技术将虚拟对象叠加在真实世界之上,允许用户同时看到虚拟世界和真实世界,可以与虚拟对象进行交互。
RPC:在服务中由第三方完成自己的任务需求的过程,叫做远程过程调用通俗易懂的理解一个阳光明媚的早晨,老婆又在翻看我订阅的技术杂志。“老公,什么是RPC呀,为什么你们程序员那么多黑话!”,老婆还是一如既往的好奇。“RPC,就是Remote Procedure Call的简称呀,翻译成中文就是远程过程调用嘛”,我一边看着书,一边漫不经心的回答着。“啥?你在说啥?谁不知道翻译成中文是什么意思?你个废柴,快给我滚去洗碗!”“我去。。。”,我如梦初醒,我对面坐着的可不是一个程序员,为了不去洗碗,我瞬间调
DNS解析过程详解 前言 最近这几天学习一下DNS相关的知识,为啥要学习DNS的知识呢?这个问题问得好,在回答这个问题之前,你得先明白1+1为啥2,如果你明白了,就不需要学DNS了,因为你可以去学数学了,不用走程序员这条路了,如果你不知道1+1为啥等于2,那就来吧,一起学. 根域 就是所谓的’.’(点号),其实我们的网