内核代码阅读(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
相关文章
|
网络协议 Linux
如何在服务器上进行双网卡双IP双网关配置?
最近,有个想法,在服务器中配置双网卡双IP双网关,双网卡配置一个上外网,一个上内网。不多说了,直接进入今天的主题。
1759 0
|
3月前
|
分布式计算 Java 关系型数据库
SpringBoot集成powerJob实战派
PowerJob 是全新一代分布式任务调度与计算框架,支持可视化管理、多种定时策略、丰富的执行模式(如单机、广播、Map/MapReduce),并提供工作流编排、在线日志、高可用及分布式计算能力,适用于定时任务、集群执行、延迟处理等场景。
460 1
SpringBoot集成powerJob实战派
|
人工智能 并行计算 流计算
【AI系统】GPU 架构与 CUDA 关系
本文介绍了英伟达GPU硬件基础概念,重点解析了A100 GPU架构中的GPC、TPC、SM等组件及其功能。接着深入讲解了CUDA并行计算平台和编程模型,特别是CUDA线程层次结构。最后,文章探讨了如何根据CUDA核心数量、核心频率等因素计算GPU的算力峰值,这对于评估大模型训练的算力需求至关重要。
978 3
|
11月前
|
数据采集 存储 Serverless
5 分钟复刻你的声音,一键实现 GPT-Sovits 模型部署
想象一下,只需简单几步操作,就能生成逼真的语音效果,无论是为客户服务还是为游戏角色配音,都能轻松实现。GPT-Sovits 模型,其高效的语音生成能力为实现自然、流畅的语音交互提供了强有力的技术支持。本文将详细介绍如何利用函数计算平台部署 GPT-Sovits 模型,以构建一个高效、可扩展的 AI 语音交互系统。通过这一部署方案,开发者和企业能够快速集成语音合成功能,实现从文本到语音的无缝转换,进而推动智能语音应用的创新和发展。
1981 11
|
存储 缓存 固态存储
|
分布式计算 数据处理 流计算
【原理】Flink如何巧用WaterMark机制解决乱序问题
【原理】Flink如何巧用WaterMark机制解决乱序问题
|
前端开发 JavaScript 应用服务中间件
个人博客网站如何实现https重定向(301)到http
对于个人网站站注册比较少的,服务器配置不是很好的,没必要https,https跳转到http是要时间的,会影响网站打开的速度。免费的https每年都要更换。
560 2
|
Linux 测试技术 调度
Linux调度器何时需触发抢占?—— 从hackbench谈起
作者:何惟禹 吴一昊一、背景:性能之战“不服跑个分”虽然已经沦为手机行业的调侃用语,但在操作系统领域仍然是最重要的评价方式之一。本文的故事也源于一次 Alinux3 与 CentOS8 的一次跑分的较量。当然比分较量并不是目的,更重要的是发现存在的回归缺陷并进行修复,最终让 Alinux3 全方位持平或超过 CentOS8。在本次较量中,我们使用 hackbench 作为跑分软件,我们在测试过程中
3032 0
Linux调度器何时需触发抢占?—— 从hackbench谈起
|
运维 网络协议 Linux
Linux(28) Linux双网卡配置为连接到Linux主机的PC提供外网访问
Linux(28) Linux双网卡配置为连接到Linux主机的PC提供外网访问
836 1
|
Web App开发 测试技术
loadrunner入门教程(10)--代理录制
使用代理录制录制脚本
373 1
loadrunner入门教程(10)--代理录制