内核代码阅读(9) - 内核缓冲区的管理slab上

简介: 内核缓冲区的管理slab上

内核缓冲区的管理

关于slab

1) 内存分配的最小单位是page。
2) slab是为了加速内核中常用结构体的分配,对象池。
3) 每种slab有 constructor 和 destrutor。所以从slab种获取的对象可以直接使用。
4) 对象分为:
   大对象:结构体的大小比一个page页还要大。
   小对象:一个page页可以装下n个结构体。
5) 一个slab可能1,2,4,8。。。最多32个page构成。
6) 每个slab的前端是 slab_t 结构体,同一类型对象的slab通过 slab_t 链在一起。
7) 每个slab都有对象区,是对象的数组。
8) 每个slab上还有一个对象连接数组,用来把对象数组链接起来。
9) 每个slab头都有一个着色区,用来调整slab,使得和 cache line 对齐。

slab_t结构体

typedef struct slab_s {
        struct list_head        list;
        unsigned long                colouroff;
        void                        *s_mem;
        unsigned int                inuse;
        kmem_bufctl_t                free;
    } slab_t;
1) slab通过list链入3个队列中的一个:
   fully-used, partital, fully-free
2) 管理的是一个slab。

cache_cache

typedef struct kmem_cache_s kmem_cache_t;
    static kmem_cache_t cache_cache = {
        slabs:                LIST_HEAD_INIT(cache_cache.slabs),
        firstnotfull:        &cache_cache.slabs,
        objsize:        sizeof(kmem_cache_t),
        flags:                SLAB_NO_REAP,
        spinlock:        SPIN_LOCK_UNLOCKED,
        colour_off:        L1_CACHE_BYTES,
        name:                "kmem_cache",
    };
1) kmem_cache_t:
   同一种对象被slab_t组织成list,而链表的头部就是 kmem_cache_t
2) 所有的 kmem_cache_t 也是在slab中管理,而这种特殊的slab也是被slab_t链起来。
3) 特殊的slab的链表头就是 cache_cache。

slab_cache

并不是所有的数据结构都从cache_cache(专用的缓冲队列)中分配,不常用的构造开销也不大的从通用缓冲区队列申请,即为 slab_cache。
slab_cache 管理的是不同大小的对象32, 64, 最大到128K。

内核内存分配

1) 在专用缓冲区(cache_cache)中分配对象,需要指定一个队列 kmem_cache_t。
   void *kmem_cache_alloc(kmem_cache_t *cachep, int flags);
   void *kmem_cache_free(kmem_cache_t *cachep, void *objp);
2) 在通用缓冲区分配的函数,需要指定大小:
   void *kmalloc(size_t size, int flags);
   void kfree(const void *objp);
3) 如果要分配的数据结构大小接近一个页面,可以直接申请一个page
   struct page * alloc_pages(int gfp_mask, unsigned long order);
4) 内核虚存的分配
   void* vmalloc(unsigned long size);
   void vfree(void *addr);
   从内核的虚存空间(3G以上)分配一块虚存和相应的物理内存,这块内存kswapd不可见。所以不能被swap出去。

专用缓冲区 cache_cache 队列的建立

数据结构负责初始化自己的队列

void __init skb_init(void)
    {
        int i;
        skbuff_head_cache = kmem_cache_create("skbuff_head_cache",
                                              sizeof(struct sk_buff),
                                              0,
                                              SLAB_HWCACHE_ALIGN,
                                              skb_headerinit, NULL);
        if (!skbuff_head_cache)
                panic("cannot create skbuff cache");
        for (i=0; i<NR_CPUS; i++)
                skb_queue_head_init(&skb_head_pool[i].list);
    }
1) 为网络驱动创建 sk_buff 数据结构的专用缓冲区队列。
2) 名字是 skbuff_head_cache。
3) SLAB_HWCACHE_ALIGN
   要求对齐 cache_line
4) 构造函数是 skb_headerinit。
5) static kmem_cache_t *skbuff_head_cache;
   kmem_cache_create 的返回值是skbuff的slab的队列头,存放放在static变量里。
   使用 kmem_cache_alloc 分配skubuff的时候要传递这个队列头。

kmem_cache_create

1) 获取一个队列头
    cachep = (kmem_cache_t *) kmem_cache_alloc(&cache_cache, SLAB_KERNEL);
2) 初始化 kmem_cache_t 中的成员
   color:头部的着色区
   size:一个对象的大小
   num:一个slab中可以装下几个obj
   flags:决定 slab_t 是放在slab上还是独立存放。

专用缓冲区 kmem_cache_alloc 缓冲区的分配

struct sk_buff *alloc_skb(unsigned int size,int gfp_mask)
    {
        struct sk_buff *skb;
        skb = kmem_cache_alloc(skbuff_head_cache, gfp_mask);
        return skb;
    }
1) 分配的时候要把slab的队列头结构 skbuff_head_cache 传递给 kmem_cache_alloc。
static inline void * __kmem_cache_alloc (kmem_cache_t *cachep, int flags)
    {
        unsigned long save_flags;
        void* objp;
        kmem_cache_alloc_head(cachep, flags);
    try_again:
        local_irq_save(save_flags);
        objp = kmem_cache_alloc_one(cachep);
        local_irq_restore(save_flags);
        return objp;
    alloc_new_slab:
        local_irq_restore(save_flags);
        if (kmem_cache_grow(cachep, flags))
                goto try_again;
        return NULL;
    }
1) kmem_cache_alloc_head
   检查当前的cachep是否支持DMA
2) kmem_cache_alloc_one
   从cachep上分配一个object
3) 如果返回值为空,说明cachep的slab队列没有slab可供分配,则空过 kmem_cache_grow 增长cachep队列的大小。

kmem_cache_alloc_one

#define kmem_cache_alloc_one(cachep)                                \
    ({                                                                \
        slab_t        *slabp;                                        \
                                                                \
        /* Get slab alloc is to come from. */                        \
        {                                                        \
                struct list_head* p = cachep->firstnotfull;        \
                if (p == &cachep->slabs)                        \
                        goto alloc_new_slab;                        \
                slabp = list_entry(p,slab_t, list);        \
        }                                                        \
        kmem_cache_alloc_one_tail(cachep, slabp);                \
    })
1) 这是一段宏。
2) cachep->firstnotfull
   从cachep中找到第一个空闲的slab的list
3) slabp = list_entry(p,slab_t, list)
   通过list找到slabp的开头。
4) if (p == &cachep->slabs)
   如果cachep中没有空闲的slab了,则goto到 alloc_new_slab,进行分配。
5) 找到了一个slabp之后,kmem_cache_alloc_one_tail进行初始化。
static inline void * kmem_cache_alloc_one_tail (kmem_cache_t *cachep,
                                                         slab_t *slabp)
    {
        void *objp;
        STATS_INC_ALLOCED(cachep);
        STATS_INC_ACTIVE(cachep);
        STATS_SET_HIGH(cachep);
        slabp->inuse++;
        objp = slabp->s_mem + slabp->free*cachep->objsize;
        slabp->free=slab_bufctl(slabp)[slabp->free];
        if (slabp->free == BUFCTL_END)
                cachep->firstnotfull = slabp->list.next;
        return objp;
    }
1) 首先跟新cachep中的统计
2) slabp->inuse++;
   更新slabp中的obj的使用计数。
3) objp = slabp->s_mem + slabp->free*cachep->objsize;
   计算出obj的起始地址。
   s_mem: 是slab中obj的起始偏移量。
   free: 是链接数组的头部,第一个可以使用的obj的下标。
   cachep->objsize: 一个obj的大小
4) slabp->free=slab_bufctl(slabp)[slabp->free]
   free的下标指向下一个。
   slab_bufctl: 一个记录obj下标的数组。

kmem_cache_grow

static int kmem_cache_grow (kmem_cache_t * cachep, int flags)
    {
        slab_t        *slabp;
        struct page        *page;
        void                *objp;
        size_t                 offset;
        unsigned int         i, local_flags;
        unsigned long         ctor_flags;
        unsigned long         save_flags;
        offset = cachep->colour_next;
        cachep->colour_next++;
        if (cachep->colour_next >= cachep->colour)
                cachep->colour_next = 0;
        offset *= cachep->colour_off;
        cachep->dflags |= DFLGS_GROWN;
        cachep->growing++;
        objp = kmem_getpages(cachep, flags);
        slabp = kmem_cache_slabmgmt(cachep, objp, offset, local_flags);
        i = 1 << cachep->gfporder;
        page = virt_to_page(objp);
        do {
                SET_PAGE_CACHE(page, cachep);
                SET_PAGE_SLAB(page, slabp);
                PageSetSlab(page);
                page++;
        } while (--i);
        kmem_cache_init_objs(cachep, slabp, ctor_flags);
        spin_lock_irqsave(&cachep->spinlock, save_flags);
        cachep->growing--;
        list_add_tail(&slabp->list,&cachep->slabs);
        if (cachep->firstnotfull == &cachep->slabs)
                cachep->firstnotfull = &slabp->list;
        STATS_INC_GROWN(cachep);
        cachep->failures = 0;
        return 1;
    }
1) offset = cachep->colour_next;
   计算着色区的便移量。目的是使各个slab中的obj在cache line中错开位子。
   TODO: 研究一下 cache line
2) objp = kmem_getpages(cachep, flags);
   申请物理页,返回物理页的首地址。
   addr = (void*) __get_free_pages(flags, cachep->gfporder);
3) slabp = kmem_cache_slabmgmt(cachep, objp, offset, local_flags);
   初始化slabp中的slab_t结构。
4) i = 1 << cachep->gfporder;
   page = virt_to_page(objp);
   do {
           SET_PAGE_CACHE(page, cachep);
           SET_PAGE_SLAB(page, slabp);
           PageSetSlab(page);
           page++;
   } while (--i);
   这个while循环,把本次分配的page中的list的
       list.next = cachep;
       list.prev = slabp
   这个时候page已经从buddy的链表中脱离出来,list可以挪做它用了。
   记录一个page所属的cachep和slabp,为了在释放一个obj的时候,
   根据obj所在的物理页找到mem_map中的page结构体,然后根据list.prev, list.next找到cachep和slabp。
5) kmem_cache_init_objs(cachep, slabp, ctor_flags)
   初始化slab中的obj和ctl数组。
6) list_add_tail(&slabp->list,&cachep->slabs)
   把新的slabp挂在cachep后面,激活。
7) cachep->firstnotfull = &slabp->list;
   更新第一个空闲的slab
相关文章
|
3月前
|
Ubuntu Linux 数据安全/隐私保护
内核实验(七):使用内核KFIFO环形缓冲区机制
本文通过一个内核模块实验,演示了如何在Linux内核中使用KFIFO环形缓冲区机制,包括定义KFIFO、编写驱动程序以及在Qemu虚拟机中进行编译、部署和测试,展示了KFIFO在无需额外加锁的情况下如何安全地在读者和写者线程间进行数据传输。
135 0
内核实验(七):使用内核KFIFO环形缓冲区机制
|
6月前
|
消息中间件 存储 缓存
Linux内存映射mmap
Linux内存映射mmap
80 0
|
存储 索引 Windows
驱动开发:内核物理内存寻址读写
在某些时候我们需要读写的进程可能存在虚拟内存保护机制,在该机制下用户的`CR3`以及`MDL`读写将直接失效,从而导致无法读取到正确的数据,本章我们将继续研究如何实现物理级别的寻址读写。首先,驱动中的物理页读写是指在驱动中直接读写物理内存页(而不是虚拟内存页)。这种方式的优点是它能够更快地访问内存,因为它避免了虚拟内存管理的开销,通过直接读写物理内存,驱动程序可以绕过虚拟内存的保护机制,获得对系统中内存的更高级别的访问权限。
7066 1
驱动开发:内核解析内存四级页表
当今操作系统普遍采用64位架构,CPU最大寻址能力虽然达到了64位,但其实仅仅只是用到了48位进行寻址,其内存管理采用了`9-9-9-9-12`的分页模式,`9-9-9-9-12`分页表示物理地址拥有四级页表,微软将这四级依次命名为PXE、PPE、PDE、PTE这四项。关于内存管理和分页模式,不同的操作系统和体系结构可能会有略微不同的实现方式。9-9-9-9-12的分页模式是一种常见的分页方案,其中物理地址被分成四级页表:PXE(Page Directory Pointer Table Entry)、PPE(Page Directory Entry)、PDE(Page Table Entry)
10834 0
|
运维 Linux 开发者
Kernel SIG直播:让人头疼的“内核内存被改”和“内存泄露”怎么解?|第13期
内存发生故障问题让你抓狂?明天(周三)将有效地解决线上内核内存被改和内存泄露这两大难题。
Kernel SIG直播:让人头疼的“内核内存被改”和“内存泄露”怎么解?|第13期
|
Linux 调度
block设备驱动之内核机制
block设备驱动之内核机制
block设备驱动之内核机制