kmalloc VS kmem_cache_alloc

内核空间:kmalloc vmalloc 用户空间:malloc ptmalloc 一.地址映射流程 二.内核空间 在内核空间,通过malloc类似的两个系统调用来进行内存的分配,它们分别是kmalloc和vmalloc 1.kmalloc kmalloc用于为内核空间的直接内存映射区分配内存. kmaloc以字节为分配单位,通常用于分配小块内存,并且kmalloc确保分配的页在物理地址上是连续的(虚拟地址也必然是连续的),并且kmalloc为了防止内存碎片的问题,其底层页面分配算法是基于slab分配器实现的. 2.vmalloc vmalloc用于为内核空间中的动态内存映. 阅读详情
kmalloc VS kmem_cache_alloc
Linux内核为需要动态分配内存的内核程序提供了kmalloc/kfree/kcalloc/krealloc函数接口,它们分别对应于C标准库的malloc/free/calloc/krealloc。除此之外,Linux还提供了kmem_cache_xxx系列系统调用,以提供比上述接口更低的时间复杂度和空间复杂度,那么两者的效率究竟能差多少,它们又各自适合于何种场合呢?

Linux内存系统的层次结构

为了保证灵活性,Linux内存分配系统是分层设计的,这种层次设计使得整个设计更加明晰,每个层次都能简单而有效地进行局部优化,所以一般也能提供更好的性能,当然扩展性也更好了,不然 SLUB[1]的出现就不可能那么轻松自然了。Linux内存系统自上而下分成以下几部分:

单位
接口算法
动态大小
kmalloc/kfree/krealloc/kcalloc 按大小组织的缓存数组
固定大小
kmem_cache_create/kmem_cache_destroy
kmem_cache_alloc/kmem_cache_free
Slab[2]
2^n页alloc_pages/free_pages
__get_free_pages/__free_pages
伙伴算法

kmalloc相对于kmem_cache_alloc的时间复杂度

因为kmalloc是基于kmem_cache_create实现的,那么时间效率上kmalloc肯定是占不到任何便宜了,那么究竟能差多少呢?让我们先来看看相关代码:

    79  static inline void *kmalloc(size_t size, gfp_t flags)
    80  {
    81      if (__builtin_constant_p(size)) {
    82          int i = 0;
    83  #define CACHE(x) /
    84          if (size <= x) /
    85              goto found; /
    86          else /
    87              i++;
    88  #include "kmalloc_sizes.h"
    89  #undef CACHE
    90          {
    91              extern void __you_cannot_kmalloc_that_much(void);
    92              __you_cannot_kmalloc_that_much();
    93          }
    94  found:
    95          return kmem_cache_alloc((flags & GFP_DMA) ?
    96              malloc_sizes[i].cs_dmacachep :
    97              malloc_sizes[i].cs_cachep, flags);
    98      }
    99      return __kmalloc(size, flags);
   100  }

出于效率上的考虑,上述函数为被声明为内联编译,81行的__builtin_constant_p是gcc的内建函数[ 3],它能够在编译时判定它的参数是否是编译时常量,如果是它将返回真,因为此函数为内联函数,并且__builtin_constant_p的参数为整型变量,所以判定仍有效。这也就意味着,如果你用如下方式调用kmalloc:

ptr = kmalloc(128, GTP_KERNEL);

实际执行的将是82-79行的代码。为了能将这部分代码展开,我们不得不参考文件kmalloc_sizes.h的内容:

     1  #if (PAGE_SIZE == 4096)
     2          CACHE(32)
     3  #endif
     4          CACHE(64)
     5  #if L1_CACHE_BYTES < 64
     6          CACHE(96)
     7  #endif
     8          CACHE(128)
     9  #if L1_CACHE_BYTES < 128
    10          CACHE(192)
    11  #endif
    12          CACHE(256)
    13          CACHE(512)
    14          CACHE(1024)
    15          CACHE(2048)
    16          CACHE(4096)
    17          CACHE(8192)
    18          CACHE(16384)
    19          CACHE(32768)
    20          CACHE(65536)
    21          CACHE(131072)
    22  #ifndef CONFIG_MMU
    23          CACHE(262144)
    24          CACHE(524288)
    25          CACHE(1048576)
    26  #ifdef CONFIG_LARGE_ALLOCS
    27          CACHE(2097152)
    28          CACHE(4194304)
    29          CACHE(8388608)
    30          CACHE(16777216)
    31          CACHE(33554432)
    32  #endif /* CONFIG_LARGE_ALLOCS */
    33  #endif /* CONFIG_MMU */

代码展开后将是一系列的if-else语句块,顺序查找到最小的满足kmalloc大小要求的malloc_sizes的数组的索引。看到这里你也许会失望,顺序查找的时间复杂度不是O(n)么,既然malloc_sizes是数组,为啥不用更加高效的二分搜索呢?这个疑问先保留,不要忘了以上代码是size为常量的时候才会被执行的,其中肯定存在某些玄机。如果size为常量,那么每个if语句的值在编译时就应该能被算出,汇编的结果也证实了我的猜想,不仅仅是i的值在编译时就被算了出来,就连cs_cachep和cs_dmacachep的偏移量也是编译时计算出来的,编译器的智能程度远远地超出了我的预期。

    movl    malloc_sizes+60, %eax ; 60就是编译时计算出来的。
    andl    $-16, %esp
    subl    $16, %esp
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    kmem_cache_alloc

需要指明的一点是,以上行为只有在打开编译器优化的时候才会起效,否则连inline函数它都不会展开。说起来,Linux内核和gcc编译器还真的是“狼狈为奸”好多年,连这点儿优化诡计都被利用上了。

从以上分析来看如果是申请固定大小的内存空间,kmalloc和kmem_cache_alloc时间效率相当。如果申请动态大小的空间,编译时优化是指望不上了,但是因为kmem_cache_alloc也只能申请固定大小的对象空间,所以也只能把kmalloc赶上架了。

哦,因为size不是常量,所以kmalloc将不会走以上分支,这时它调用__kmalloc:

void *__kmalloc(size_t size, gfp_t flags)
{
    kmem_cache_t *cachep;

    /* If you want to save a few bytes .text space: replace
     * __ with kmem_.
     * Then kmalloc uses the uninlined functions instead of the inline
     * functions.
     */
    cachep = __find_general_cachep(size, flags);
    if (unlikely(cachep == NULL))
        return NULL;
    return __cache_alloc(cachep, flags);
}

static inline kmem_cache_t *__find_general_cachep(size_t size, gfp_t gfpflags)
{
    struct cache_sizes *csizep = malloc_sizes;

#if DEBUG
    /* This happens if someone tries to call
    * kmem_cache_create(), or __kmalloc(), before
    * the generic caches are initialized.
    */
    BUG_ON(malloc_sizes[INDEX_AC].cs_cachep == NULL);
#endif
    while (size > csizep->cs_size)
        csizep++;

    /*
     * Really subtle: The last entry with cs->cs_size==ULONG_MAX
     * has cs_{dma,}cachep==NULL. Thus no special case
     * for large kmalloc calls required.
     */
    if (unlikely(gfpflags & GFP_DMA))
        return csizep->cs_dmacachep;
    return csizep->cs_cachep;
}

看到顺序查找的代码是不是感觉挺差劲的?猜想它也是基于大多数内存块都不是很大这个事实,所以顺序查找的事实时间复杂度反而会比二分查找低。但是,这多少有些让人不爽,SLUB的代码针对这部分又做了优化,基于大多数内存块是按2^N大小连续分部这个事实,用查表加上位级别的大小对齐处理提高了时间效率。由于这部分与本文所讨论的东西关联不大,所以留给看客自己去分析吧!

kmalloc相对于kmem_cache_alloc的空间复杂度

除了时间复杂度,内存分配器的另外一个目标就是更好的空间复杂度,尽可能地减少内部碎片和外部碎片,充分利用已有内存空间。因为kmalloc所利用的内存块的大小是事先定义好的,所以很多情况下会产生内部碎片,浪费空间,而kmem_cache_alloc由于内存大小也是量身定做的缘故则不会。但是,有一点是kmem_cache_alloc所不能做到的,那就是动态大小的内存空间,这个任务是非kmalloc莫属。

其它

kmem_cache_create的参数列表中还有构造函数和析构函数(SLUB中已经去掉了析构函数),对构造函数的支持也是Slab相对于其它内存分配器的优势,他能够避免对一块内存的重复初始化,从而提高效率,而kmalloc则不能。

结论

动态创建固定大小的内存对象,虽然kmalloc的时间复杂度并不大,但是联系到空间复杂度,还是采用kmem_cache_alloc的好;而非固定大小的内存申请,只能经由kmalloc来解决。
 
Kmalloc详解 用于在kernel中为object申请小于一页的内存。object就是经常会用到的一些结构体,如task_struct。提前将内存分割成不同大小的块,用于一些常用的内存请求,尽可能避免了外部内存碎片,要知道从伙伴系统申请内存至少都要一页,就是4kb,这对于一些可能只需要几字节的需求来说太大了!具体可以去看下slab和伙伴系统的原理,这里不做详述。kmalloc是一个总的kernel内存申请的调用接口,根据申请内存的大小来判断是从伙伴系统申请还是从slab分配器申请。大于2。 阅读详情

相关推荐

Linux内存分页实战:从OOM故障到页表调优

内存分页是操作系统管理虚拟内存的基础机制,其核心在于通过页表实现虚拟地址到物理地址的映射,并支撑写时复制、内存共享与按需加载等关键能力。其原理涉及四级页表结构、TLB缓存加速、缺页异常处理及伙伴系统分配等底层逻辑,技术价值体现在内存隔离性、安全性与资源利用率三重保障。典型应用场景包括Java/Go服务内存抖动分析、Kubernetes节点OOM排查、数据库Page Cache优化及大页(HugePage)性能调优。本文聚焦真实线上故障——如RSS飙升触发OOM Killer、swap频繁却内存充足、fork

clugcpne10995的博客 361

内存分配接口汇总

对于160M的系统而言,vmalloc_start位置应在3G+160M附近(在物理内存映射区与vmalloc_start期间还存在一个8M的gap来防止跃界),vmalloc_end的位置接近4G(最后位置系统会保留一片128k大小的区域用于专用页面映射),kmalloc和get_free_page申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系,virt_to_phys()可以实现内核虚拟地址转化为物理地址。...

生活需要深度 1435

Linux内存管理(34):slub 分配器之kmem_cache_alloc

如《slub 分配器初始化》一文第3.1节 bootstrap()函数中调用 kmem_cache_zalloc() 从kmem_cache中获取object。又如《slub 分配器初始化》一文第 5 节create_kmalloc_caches() 函数中通过 kmem_cache_zalloc() 创建kmalloc cache 的object。又如一文中第6节通过 kmem_cache_alloc_node() 创建 kmem_cache_node 的object,最终调用到的都是。

私房菜 2038

Linux中的内存分配和释放之kmem_cache_alloc()函数分析

  记得上篇文章中我们提到了这个函数,在kmem_cache_create()函数调用它的语句是cachep = (kmem_cache_t *) kmem_cache_alloc(&cache_cache, SLAB_KERNEL);这里的cachep是我们要申请的高速缓存内存的描述结构体(kmem_cache_t),但是我们也需要高速缓存内存来存放这个结构体。就是cache_cache所描

satanwxd的专栏 2万+

kmalloc VS kmem_cache_alloc [转]

Linux内核为需要动态分配内存的内核程序提供了kmalloc/kfree/kcalloc/krealloc函数接口,它们分别对应于C标准库的malloc/free/calloc/krealloc。除此之外,Linux还提供了kmem_cache_xxx系列系统调用,以提供比上述接口更低的时间复杂度和空间复杂度,那么两者的效率究竟能差多少,它们又各自适合于何种场合呢? Linux内存系统的层次结

smartzzm的博客 2114

kmalloc vs kmem_cache_alloc

Linux内核为需要动态分配内存的内核程序提供了kmalloc/kfree/kcalloc/krealloc函数接口,它们分别对应于C标准库的malloc/free/calloc/krealloc。除此之外,Linux还提供了kmem_cache_xxx系列系统调用,以提供比上述接口更低的时间复杂度和空间复杂度,那么两者的效率究竟能差多少,它们又各自适合于何种场合呢?Linu...

weixin_34390996的博客 160

linux-内存相关

标志:必须指定 GFP_KERNEL(进程上下文,可休眠)或 GFP_ATOMIC(中断上下文,不可休眠)。然后从伙伴系统(Buddy System)中申请零散的物理页,最后通过修改内核页表将这些零散的物理页映射到这段连续的虚拟地址上。只有当你分配的内存大到 kmalloc 无法满足,或者你明确不需要物理连续性(如纯软件逻辑缓冲区)时,才考虑 vmallockmalloc 分配的是 内核逻辑地址。优势: 由于是线性映射,不需要修改 CPU 的页表,也不会引起 TLB(转换检测缓冲区)的频繁刷新。

s651665496的专栏 209

Visual Studio写Linux内核模块?VisualGDB+VisualKernel实战踩坑与性能调优指南

本文详细介绍了如何使用Visual Studio结合VisualGDB和VisualKernel工具开发Linux内核模块,特别针对ARM架构的嵌入式系统。从环境搭建、工具链配置到内核模块开发实战,再到高级调试技巧和性能调优,提供了全面的指南和避坑建议,帮助开发者提升开发效率和代码质量。

dengyiyu5280的博客 470

kmemleak 与 KASAN 对比:Linux 内核 2 大内存检测工具原理与适用场景解析

本文深入对比了Linux内核中两大内存检测工具Kmemleak与KASAN的核心原理、检测能力及适用场景。Kmemleak专注于内存泄漏检测,通过追踪和扫描机制识别未释放内存;而KASAN则实时检测越界访问、use-after-free等内存错误。文章详细分析了两者的性能开销和使用建议,帮助开发者在不同场景下做出合理选择。

weixin_30667649的博客 358

庖丁解vmCore · 第五篇 · 骨相之辨:内核内存的解剖学

前四篇建立的能力是读懂"事发瞬间"——panic header、寄存器转储、call trace 骨架。走完上述流程,通常能定位到崩溃发生在哪个函数、哪条指令、收到了什么参数。

深耕底层运维,沉淀实战干货,分享技术经验。 496

Linux内核笔记006 - 交换分区

1. 交换分区概念 一个cpu,同一时刻只能执行一条指令,执行某个进程时,其它进程一定是停止状态。那么,当cpu切换到进程B运行时,如果系统中已经没有空闲的物理内存页面了,内核就会选择一个其它进程占用的物理内存页面,将其内容备份到磁盘上,这样就可以先腾出来给当前正在运行的进程B使用了,这个过程叫做"页面换出",当切换到进程A运行时,再重新分配一个物理内存页面(如果也没有空闲物理内存页面,同样执行换出操作),并且从磁盘上恢复换出的内容,这个过程叫做"页面换入"。用于备份内存数据的磁盘空间,就是"交换..

北观止的博客 1045

kmem_cache_alloc函数使用

<br />为了更好的得到问题解决,也对问题的提出进点义务,在抛出我的问题前先简单介绍一下Linux下slab高速缓存的使用方法。<br /><br />在内核编程中,可能经常会有一些数据结构需要反复使用和释放,按照通常的思路,可能是使用kmalloc和kfree来实现。<br />但是这种方式效率不高,Linux为我们提供了更加高效的方法——Slab高速缓存管理器<br />通过先使用kmem_cache_create函数创建一个高速缓存的头指针——在内核中是struct kmem_cache结构,具体用

3175

内存管理API之kmem_cache_alloc

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)用于从缓存中申请内存。 其使用的例程如下: table->table = kmem_cache_alloc(amd_iommu_irq_cache, GFP_ATOMIC); if (!table->table) { kfree(table); table = N

jason的笔记 6924

Linux内核kmalloc分配内存大小(非常详细)

基于linux-5.15分析。

1万+

kmem_cache_alloc

***/void *slab_allocreturn ret;slab_allocvoid *objp;cachep))objp =return。

wmzjzwlzs的专栏 563

内存管理API之krealloc

void *krealloc(const void *p, size_t new_size, gfp_t flags) 用于重新为让p执行一段新申请的内存,但是保持p指针指向内存中的内容不变 通俗讲就是为p重新申请一段内存,再将p之前内存中的内容复制过来. void *krealloc(const void *p, size_t new_size, gfp_t flags) { void *re

jason的笔记 2629
上一篇: 原子操作
下一篇: linux 文件 Cache 管理
Exxos
博客等级 码龄19年 19粉丝 15原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值