slub中的kmalloc和kfree学习笔记

完全转自:  http://linux.chinaunix.net/bbs/thread-1110127-1-1.html

2.6.26中的内存管理大概分为3个层次 SLUB,伙伴系统和ZONE,其中SLUB在最高层,这里通过分析kmalloc和kfree来分析SLUB的模型,在内存管理中还有NUMA系统,但是NUMA不是必须得,所以以下笔记建立在无SMP和不使用NUMA的环境下,并且不运行DEBUG设置

SLUB主要对1页以下的内存进行管理,将1页内存分成相同大小的块,SLUB将这些块称为object,内核进行内存申请时则分配1个块,也就是1个object

在x86下的32位处理器中,SLUB由13个缓冲结构组成,每个缓冲结构管理大小不同的object,其中0号归NUMA使用,其它12个按顺序分别为96,192,8,16,32,64,128,256,512,1024,2048,4096,如下图所示:

 

举例来说,第5个缓冲结构管理object大小为16的页面,对于该缓冲来说,将1页内存按16的大小分成了256项,当内核申请1个大小为9-16大小的内存时,SLUB就根据第5个缓冲结构中的空闲object指针freelist取出1个object交给内核
这里可以发现,申请大小为9时,返回16的大小,申请大小为15时,也返回16的大小,大家会认为如果申请的内存都在9到10大小左右徘徊的时候就会浪费大概50%的内存空间,对的,所以学习SLUB就更有必要了嘛,如果都在9-10大小的话就自己更改SLUB缓冲的结构,设置1个9-10大小的缓冲区,专门负责这些内存申请.避免浪费
对每页内存进行分块,虽然在一定程度上浪费了内存,但是方便了内存的申请与回收,提高了效率,下面就对SLUB的这种管理进行分析

首先是SLUB的缓冲结构kmalloc_caches[]数组的初始化,该初始化在kmem_cache_init中进行
kmem_cache_init在/mm/slub.c中,代码如下:

void __init kmem_cache_init(void)
{
    int i;
    int caches = 0;

    init_alloc_cpu();
#ifdef CONFIG_NUMA
    create_kmalloc_cache(&kmalloc_caches[0], "kmem_cache_node",
        sizeof(struct kmem_cache_node), GFP_KERNEL);
    kmalloc_caches[0].refcount = -1;
    caches++;
    hotplug_memory_notifier(slab_memory_callback, SLAB_CALLBACK_PRI);
#endif
    slab_state = PARTIAL;
    //如果kmalloc的最小object小于64
    //则初始化1号和2号kmalloc_caches的大小为96和192
    if (KMALLOC_MIN_SIZE <= 64) 
    {
        create_kmalloc_cache(&kmalloc_caches[1],
                "kmalloc-96", 96, GFP_KERNEL);
        caches++;
        
        create_kmalloc_cache(&kmalloc_caches[2],
                "kmalloc-192", 192, GFP_KERNEL);
        caches++;
    }
    //按照kmalloc的最小object初始化kmalloc_caches
    for (i = KMALLOC_SHIFT_LOW; i <= PAGE_SHIFT; i++) 
    {
        create_kmalloc_cache(&kmalloc_caches[i],
            "kmalloc", 1 << i, GFP_KERNEL);
        caches++;
    }
    BUILD_BUG_ON(KMALLOC_MIN_SIZE > 256 ||
        (KMALLOC_MIN_SIZE & (KMALLOC_MIN_SIZE - 1)));
    //按照kmalloc的最小object重新设置size_index
    for (i = 8; i < KMALLOC_MIN_SIZE; i += 8)
        size_index[(i - 1) / 8] = KMALLOC_SHIFT_LOW;

    if (KMALLOC_MIN_SIZE == 128) 
    {
        for (i = 128 + 8; i <= 192; i += 8)
            size_index[(i - 1) / 8] = 8;
    }
    slab_state = UP;
    //按照kmalloc的最小object重新设置kmalloc_caches的名字
    for (i = KMALLOC_SHIFT_LOW; i <= PAGE_SHIFT; i++)
        kmalloc_caches[i]. name =
            kasprintf(GFP_KERNEL, "kmalloc-%d", 1 << i);
#ifdef CONFIG_SMP
    register_cpu_notifier(&slab_notifier);
    kmem_size = offsetof(struct kmem_cache, cpu_slab) +
                nr_cpu_ids * sizeof(struct kmem_cache_cpu *);
#else
    kmem_size = sizeof(struct kmem_cache);
#endif
    printk(KERN_INFO
        "SLUB: Genslabs=%d, HWalign=%d, Order=%d-%d, MinObjects=%d,"
        " CPUs=%d, Nodes=%d/n",
        caches, cache_line_size(),
        slub_min_order, slub_max_order, slub_min_objects,
        nr_cpu_ids, nr_node_ids);
}

由于不使用NUMA系统,所以这里不会执行#ifdef CONFIG_NUMA中的代码,也就不会初始化0号缓冲
所有缓冲结构的初始化都是由create_kmalloc_cache负责
create_kmalloc_cache在/mm/slub.c中,代码如下:

static struct kmem_cache *create_kmalloc_cache(struct kmem_cache *s,
        const char *name, int size, gfp_t gfp_flags)
{
    unsigned int flags = 0;

    //检测是否为DMA缓冲结构
    if (gfp_flags & SLUB_DMA)
        //是则加上DMA标志
        flags = SLAB_CACHE_DMA;
    down_write(&slub_lock);
    //分配一个缓冲
    if (!kmem_cache_open(s, gfp_flags, name, size, ARCH_KMALLOC_MINALIGN,flags, NULL))
        goto panic;
    //将该缓冲挂载到slab_caches链表中
    list_add(&s->list, &slab_caches);
    up_write(&slub_lock);
    if (sysfs_slab_add(s))
        goto panic;
    return s;
panic:
    panic("Creation of kmalloc slab %s size=%d failed./n", name, size);
}

主要的初始化在kmem_cache_open中进行
kmem_cache_open在/mm/slub.c中,代码如下:

static int kmem_cache_open(struct kmem_cache *s, gfp_t gfpflags,
        const char *name, size_t size,
        size_t align, unsigned long flags,
        void (*ctor)(struct kmem_cache *, void *))
{
    //初始化kmem缓冲,将内容全部清零
    memset(s, 0, kmem_size);
    //设置缓冲的名字
    s->name = name;
    //设置缓冲的object初始化函数
    s->ctor = ctor;
    //设置缓冲的object大小
    s->objsize = size;
    //设置缓冲的对齐
    s->align = align;
    //设置缓冲的标志
    s->flags = kmem_cache_flags(size, flags, name, ctor);
    //根据object的大小计算对应的object数目
    if (!calculate_sizes(s, -1))
        goto error;
    s->refcount = 1;
#ifdef CONFIG_NUMA
    s->remote_node_defrag_ratio = 100;
#endif
    //初始化邻居页面链表
    if (!init_kmem_cache_nodes(s, gfpflags & ~SLUB_DMA))
        goto error;
    //初始化CPU的私有kmem缓冲
    if (alloc_kmem_cache_cpus(s, gfpflags & ~SLUB_DMA))
        return 1;
    free_kmem_cache_nodes(s);
error:
    if (flags & SLAB_PANIC)
        panic("Cannot create slab %s size=%lu realsize=%u "
            "order=%u offset=%u flags=%lx/n",
            s->name, (unsigned long)size, s->size, oo_order(s->oo),
            s->offset, flags);
    return 0;
}

calculate_sizes负责计算object的大小,就是对kmem_cache结构中oo,max,min成员的赋值,以及对object大小进行边界和字对齐,但是对边界和字对齐还不熟悉,所以我就不分析了 = 3=
接下来是init_kmem_cache_nodes, init_kmem_cache_nodes负责对邻居页面链表进行初始化
init_kmem_cache_nodes在/mm/slub.c中,由于这里不使用NUMA系统,所以代码如下:

static int init_kmem_cache_nodes(struct kmem_cache *s, gfp_t gfpflags)
{
    init_kmem_cache_node(&s->local_node);
    return 1;
}

init_kmem_cache_node在mm/slub.c中,代码如下:

static void init_kmem_cache_node(struct kmem_cache_node *n)
{
    n->nr_partial = 0;
    spin_lock_init(&n->list_lock);
    INIT_LIST_HEAD(&n->partial);
#ifdef CONFIG_SLUB_DEBUG
    atomic_long_set(&n->nr_slabs, 0);
    INIT_LIST_HEAD(&n->full);
#endif
}

主要进行了一下初始化工作
回到kmem_cache_open中,现在到alloc_kmem_cache_cpus, alloc_kmem_cache_cpus负责CPU私有缓冲的初始化工作
alloc_kmem_cache_cpus在mm/slub.c中,由于不使用SMP,所以代码如下:

static inline int alloc_kmem_cache_cpus(struct kmem_cache *s, gfp_t flags)
{
    init_kmem_cache_cpu(s, &s->cpu_slab);
    return 1;
}

init_kmem_cache_cpu在mm/slub.c中,代码如下

static void init_kmem_cache_cpu(struct kmem_cache *s,
            struct kmem_cache_cpu *c)
{
    c->page = NULL;
    c->freelist = NULL;
    c->node = 0;
    c->offset = s->offset / sizeof(void *);
    c->objsize = s->objsize;
#ifdef CONFIG_SLUB_STATS
    memset(c->stat, 0, NR_SLUB_STAT_ITEMS * sizeof(unsigned));
#endif
}

初始化完成后, kmem_cache_open和create_kmalloc_cache也执行完了,返回到kmem_cache_init中,接下来kmem_cache_init主要执行缓冲名字的设置工作
下图是第6个缓冲结构,也就是object大小为32的缓冲初始化后的结构图


kmem_cache_init执行完成后,SLUB的初始化就完成了,就接下来我们就能使用kmalloc进行内存的分配了
假设kmalloc申请的大小为32,标志为GFP_KKERNEL,GFP_KERNEL是标志_GFP_WAIT , _GFP_IO 和 _GFP_FS的集合,也就是kmalloc(32,GFP_KERNEL)
下面就进入到kmalloc的分析中
kmalloc在mmslub.c中,代码如下

static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
    //也就是检测size是变量还是常量
    //为常量则执行if
    if (__builtin_constant_p(size)) 
    {
        //检测申请的大小是否超过1页内存的大小
        if (size > PAGE_SIZE)
            //调用大块内存分配
            return kmalloc_large(size, flags);
        //检测申请的内存是否用于DMA
        if (!(flags & SLUB_DMA)) 
        {
            //根据申请的大小选取对应的缓冲结构
            struct kmem_cache *s = kmalloc_slab(size);
            //检测kmem缓冲取得是否成功
            if (!s)
                return ZERO_SIZE_PTR;
            //使用缓冲结构取得内存
            return kmem_cache_alloc(s, flags);
        }
    }
    //变量及DMA使用__kmalloc分配内存
    return __kmalloc(size, flags);
}


__builtin_constant_p检测参数是变量还是常量,举个例子说kmalloc(i,GFP_KERNEL)就是变量,kmalloc(32,GFP_KERNEL)就是常量
这里先看常量,进入if中,这里先说一下kmalloc_large, kmalloc_large负责超过1页内存的申请,超过1页的内存分配由伙伴系统进行,不由SLUB进行.
接下来到if (!(flags & SLUB_DMA)),这里我们申请的内存标志为GFP_KERNEL,没有DMA标志,所以进入到if中
首先根据申请的大小选取对应的缓冲序号,进入到kmalloc_slab中
kmalloc_slab在include linuxslub_def.h中,代码如下

static __always_inline struct kmem_cache *kmalloc_slab(size_t size)
{
    //根据申请的大小取得对应kmem缓冲的序号
    int index = kmalloc_index(size);
    if (index == 0)
        return NULL;
    //根据序号取得对应的kmem缓冲

    return &kmalloc_caches[index];
}

继续进入到kmalloc_index
kmalloc_index在include /linux/slub_def.h中,代码如下:
static __always_inline int kmalloc_index(size_t size)
{
    //检测大小是否为0
    if (!size)
        //为0则返回0
        return 0;
    //检测大小是否小于kmalloc的最小object
    if (size <= KMALLOC_MIN_SIZE)
        //小于则返回最小object的对数
        return KMALLOC_SHIFT_LOW;
//检测kmalloc的最小object是否小于64
#if KMALLOC_MIN_SIZE <= 64
    //大于64而小于96则使用1号kmem
    if (size > 64 && size <= 96)
        return 1;
    //大于128而小于192则使用2号kmem
    if (size > 128 && size <= 192)
        return 2;
#endif
    //以下根据大小的不同,返回对应的kmem缓冲号
    if (size <= 8) return 3;
    if (size <= 16) return 4;
    if (size <= 32) return 5;
    if (size <= 64) return 6;
    if (size <= 128) return 7;
    if (size <= 256) return 8;
    if (size <= 512) return 9;
    if (size <= 1024) return 10;
    if (size <= 2 * 1024) return 11;
    if (size <= 4 * 1024) return 12;
     
    //以下是对于分页大于4K所使用的检测
    if (size <= 8 * 1024) return 13;
    if (size <= 16 * 1024) return 14;
    if (size <= 32 * 1024) return 15;
    if (size <= 64 * 1024) return 16;
    if (size <= 128 * 1024) return 17;
    if (size <= 256 * 1024) return 18;
    if (size <= 512 * 1024) return 19;
    if (size <= 1024 * 1024) return 20;
    if (size <= 2 * 1024 * 1024) return 21;
    return -1;
}

得到缓冲结构后,就来到了kmem_cache_alloc中
kmem_cache_alloc在mm/slub.c中
void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags)
{
    return slab_alloc(s, gfpflags, -1, __builtin_return_address(0));
}

简单的调用, __builtin_return_address产生的值用于DEBUG,这里我们并不会使用到,继续来到slab_alloc中
slab_alloc在mm/slub.c中,代码如下:
static __always_inline void *slab_alloc(struct kmem_cache *s,
        gfp_t gfpflags, int node, void *addr)
{
    void **object;
    struct kmem_cache_cpu *c;
    unsigned long flags;
    unsigned int objsize;
    //保存并关闭中断
    local_irq_save(flags);
    //取得kmem缓冲中对应当前CPU序号的私有kmem缓冲
    c = get_cpu_slab(s, smp_processor_id());
    //取得缓冲中object的大小
    objsize = c->objsize;
    //检测CPU的私有kmem缓冲的空闲object指针是否为空
    if (unlikely(!c->freelist || !node_match(c, node)))
        //为空则新申请1块页面
        object = __slab_alloc(s, gfpflags, node, addr, c);
    //不为空则使用object指针所指的object
    else 
    {
        //取得空闲的object
        object = c->freelist;
        //object指针指向下1个空闲的object
        c->freelist = object[c->offset];
        //设置ALLOC_FASTPATH状态计数器加1
        stat(c, ALLOC_FASTPATH);
    }
    //恢复中断
    local_irq_restore(flags);
    //检测是否需要清零object
    //需要的话检测当先object是否为空
    if (unlikely((gfpflags & __GFP_ZERO) && object))
        memset(object, 0, objsize);
    return object;
}

get_cpu_slab负责取得CPU的私有kmem缓冲

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值