kmalloc详解与分配大小的限制

本文深入探讨了Linux内核中的kmalloc函数,详细解析了其通过cache分配内存的机制,包括kmalloc_sizes.h文件中定义的内存大小范围,以及__find_general_cachep函数如何确定合适的cache。

kmalloc是通过cache来实现的, 只不过每次kmalloc的大小不同, 因此是从不同的cache中分配:

/* include/linux/slab.h */
// 注意kmalloc是在头文件中定义的
static inline void *kmalloc(size_t size, gfp_t flags)
{
 if (__builtin_constant_p(size)) {
/*__builtin_constant_p 用于判断一个值是否为编译时常数,如果参数size的值是常数,函数返
* 回 1,否则返回 0。__builtin_constant_p是GCC的内建函数*/
// 以下是找一个对象大小刚好大于等于size的cache
  int i = 0;
#define CACHE(x) \
  if (size <= x) \
   goto found; \
  else \
   i++;
#include "kmalloc_sizes.h"
#undef CACHE
  {
   extern void __you_cannot_kmalloc_that_much(void);
   __you_cannot_kmalloc_that_much();
  }
found:
// 实际还是通过kmem_cache_alloc来分配内存空间, 因此也是cache
  return kmem_cache_alloc((flags & GFP_DMA) ?
   malloc_sizes[i].cs_dmacachep :
   malloc_sizes[i].cs_cachep, flags);
 }
// 通过该函数最后也是由__cache_alloc()函数来分配空间
 return __kmalloc(size, flags);
}

// 这是kmalloc_sizes.h文件内容, 实际就是定义CACHE中可用的对象大小
// 普通情况下最大是128K, 也就是kmalloc能分配的最大内存量

#if (PAGE_SIZE == 4096)
 CACHE(32)
#endif
 CACHE(64)
#if L1_CACHE_BYTES < 64
 CACHE(96)
#endif
 CACHE(128)
#if L1_CACHE_BYTES < 128
 CACHE(192)
#endif
 CACHE(256)
 CACHE(512)
 CACHE(1024)
 CACHE(2048)
 CACHE(4096)
 CACHE(8192)
 CACHE(16384)
 CACHE(32768)
 CACHE(65536)
 CACHE(131072)
#if (NR_CPUS > 512) || (MAX_NUMNODES > 256) || !defined(CONFIG_MMU)
 CACHE(262144)
#endif
#ifndef CONFIG_MMU
 CACHE(524288)
 CACHE(1048576)
#ifdef CONFIG_LARGE_ALLOCS
 CACHE(2097152)
 CACHE(4194304)
 CACHE(8388608)
 CACHE(16777216)
 CACHE(33554432)
#endif /* CONFIG_LARGE_ALLOCS */
#endif /* CONFIG_MMU

继续调用

void *__kmalloc(size_t size, gfp_t flags)
{
 return __do_kmalloc(size, flags, NULL);
}
 
static __always_inline void *__do_kmalloc(size_t size, gfp_t flags,
       void *caller)
{
 struct kmem_cache *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, caller);
}
 
static inline struct kmem_cache *__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;
}

__find_general_cachep确定可以分配的空间大小由malloc_sizes结构体数组指定,在阅读mm/slab.c中它的定义如下

struct cache_sizes malloc_sizes[] = {
#define CACHE(x) { .cs_size = (x) },
#include
 CACHE(ULONG_MAX)
#undef CACHE
};

在对一个结构体进行赋值的时候,第一次看到了这个用法,赶紧来仔细看看代码。
首先在slab_def.h中找到cache_sizes的定义如下:

struct cache_sizes {
size_t cs_size;
struct kmem_cache *cs_cachep;
#ifdef CONFIG_ZONE_DMA
struct kmem_cache *cs_dmacachep;
#endif
};

可以看出,无论cache_sizes的如何变,至少都要给cs_size分配一个值。函数一就是对malloc_sizes这个数组进行初始化。在初始化的时候却多了3行代码:

代码一:#define CACHE(x) { .cs_size = (x) }, 作用:定义一个CACHE(x)的宏,作用在include里面。

代码二:#include
作用:引用kmalloc_size.h文件。使malloc_sizes[]得到初始化的值。换个角度看,把函数初始化和依据附加条件分开,使得程序的逻辑更加清晰。在函数中,CACHE仅仅作为一个宏,与具体的实现无关。

代码三:#undef CACHE
作用:撤销代码一中的定义。

综合来讲,实现的功能就是:
#include 把kmalloc_sizes.h文件里面的内容原封不动地复制到数组中, 定义了一个宏CACHE(x),用完之后取消。
数组展开之后便成:

struct cache_sizes malloc_sizes[] = {
       { .cs_size = (32) },
       { .cs_size = (64) },
       { .cs_size = (96) },
       ...
        { .cs_size = (ULONG_MAX) }
};

在linux/kmalloc_sizes.h中可以看到,所有的代码做的事,仅仅是针对不同的硬件环境作出不同的反应。除了默认的分配CHACH(x)运算外,针对内存页面、一级缓存大小,和是否支持内存管理模块,是否允许kmalloc分配大于1MB的内存等,分别作出了不同的动作。

在面向对象的编程中,最基本的三个概念为:继承、封装、抽象。继承可以理解为在在当前已经存在的基础上进行革新;封装也就是说将一些内部的东西不给你看,你只需要知道如何操作;抽象的意思就是程序有能力忽略正在处理中信息的某些方面,即对信息主要方面关注的能力。oop对于类的编程一般是这个流程:首先是对当前对象进行初始化,接着针对对象进行一系列的操作,最后对当前对象进行销毁

再回到文首的三个代码运行流程,代码一首先定义一个宏;代码二接收到这个值,并且进行一系列的操作,是内部的,在slab.c中没有描述;代码三,销毁代码一定义的宏。同时,在mm/slab.c和include/linux/slab_def.h中可以多次看到上述三个代码,只是在每个代码的实现中,CACHE(x)定义为不同的宏。

用于kmalloc可分配的内存大小范围在32~131027(128k)字节,并且由于它用slab分配器来分配内存的,所以,得到的内存大小可能比你申请的要大一些(它向上取2的N次幂整数)。而且如果开启了CONFIG_LARGE_ALLOCS选项,这个值可以更大,可以达到了32M。

<think>我们正在讨论Linux内核内存分配函数kzalloc和kmalloc的区别及使用场景。首先,我们需要明确这两个函数的基本定义和功能。 kmalloc是Linux内核中用于分配连续物理内存的函数,其原型为:void*kmalloc(size_t size, gfp_tflags);它分配的内存是物理地址连续的,大小至少为指定的大小,并且分配后内存的内容是未初始化的(即可能包含随机数据)。kzalloc是kmalloc的一个变种,其原型为: void*kzalloc(size_tsize,gfp_t flags);它除了分配内存外,还会将分配的内存初始化为零。实际上,kzalloc相当于用kmalloc分配内存后,再调用memset将内存置零。因此,主要区别在于:1.初始化:kzalloc会将分配的内存初始化为0,而kmalloc不会。2.性能:由于kzalloc需要额外的清零操作,因此它比kmalloc稍慢。但在很多情况下,这种性能差异可以忽略不计,特别是当安全性要求较高时。使用场景:-当需要分配内存并确保其内容不被之前的数据污染时(例如,分配结构体内存,并且希望所有成员初始为0),应使用kzalloc。-如果分配的内存将立即被完全覆盖,或者不需要初始化,那么使用kmalloc更高效。另外,kzalloc实际上是使用kmalloc实现的,只是它使用了标志位__GFP_ZERO,该标志位要求分配的内存被清零。因此,我们也可以手动使用kmalloc并指定__GFP_ZERO标志来达到同样的效果。例如:使用kzalloc:ptr= kzalloc(size,GFP_KERNEL);等价于:ptr =kmalloc(size,GFP_KERNEL| __GFP_ZERO);但是,使用kzalloc更简洁,并且避免了手动设置标志位的麻烦。在驱动开发中,通常推荐使用kzalloc来分配结构体,因为这样可以避免未初始化成员带来的安全问题(例如,未初始化的指针可能导致内核崩溃)。而对于缓冲区等可能立即被覆盖的数据,可以使用kmalloc。另外,需要注意的是,这两个函数分配的内存都需要使用kfree来释放。下面我们通过一个简单的例子来说明:假设我们有一个结构体:struct my_struct{int a;char b;void*c;};如果我们使用kmalloc分配:structmy_struct*p= kmalloc(sizeof(struct my_struct),GFP_KERNEL);那么p指向的内存中,成员a、b和c的值都是未定义的。而使用kzalloc:struct my_struct *p =kzalloc(sizeof(struct my_struct),GFP_KERNEL);则p->a为0,p->b为0,p->c为NULL。这样,在后续使用中,我们可以安全地检查p->c是否为NULL,而不会因为随机值导致误判。总结:-kzalloc=kmalloc+ memset(0)-需要初始化内存为零时用kzalloc,否则用kmalloc(并确保后续会覆盖或不需要初始化)在性能敏感的路径上,如果确定不需要清零,使用kmalloc可以避免不必要的清零开销。引用说明:[^1]:Linux内核源码中关于kmalloc和kzalloc的实现,以及内存分配标志的定义。[^2]:Linux设备驱动开发详解(书籍)中关于内存分配的建议。生成相关问题:根据这个主题,我们可以提出以下问题:1.kzalloc和kmalloc在内存分配失败时的行为是否相同?2.除了kzalloc和kmalloc,Linux内核还有哪些内存分配函数?3.在什么情况下必须使用kmalloc而不是kzalloc?4.如何选择kmalloc的标志(如GFP_KERNEL, GFP_ATOMIC)?5. kzalloc是否会导致额外的性能开销?在哪些场景下这种开销是值得的?注意:引用标识在回答中自然添加,如上述回答中已经添加了[^1]和[^2]。</think>### Linux内核中kzallockmalloc的区别及使用场景 在Linux内核开发中,`kmalloc`和`kzalloc`都是动态内存分配函数,但存在关键差异: #### 1. **核心区别** - **`kmalloc`** 分配指定大小的内存块,**不初始化内存内容**(可能包含随机数据)。 函数原型:`void *kmalloc(size_t size, gfp_t flags);` 示例:`ptr = kmalloc(100, GFP_KERNEL);` - **`kzalloc`** 分配内存并**自动初始化为全零**(等效于`kmalloc + memset(0)`)。 函数原型:`void *kzalloc(size_t size, gfp_t flags);` 内部实现等价于:`kmalloc(size, flags | __GFP_ZERO);` #### 2. **性能对比** | 函数 | 初始化开销 | 速度 | 内存内容 | |----------|------------|------------|--------------| | `kmalloc` | 无 | ⚡ **更快** | 未初始化数据 | | `kzalloc` | 有清零操作 | 稍慢 | 全零 | #### 3. **典型使用场景** - **优先使用`kzalloc`当**: - 分配结构体或对象需默认零值(如`task_struct`) - 避免未初始化敏感数据(如加密密钥) - 简化代码(省去手动`memset`) ```c // 安全初始化结构体 struct device *dev = kzalloc(sizeof(*dev), GFP_KERNEL); ``` - **优先使用`kmalloc`当**: - 内存将立即被完全覆盖(如DMA缓冲区) - 性能关键路径(减少清零开销) - 分配大型非敏感缓冲区 ```c // 快速分配将被填充的缓冲区 char *buf = kmalloc(BUF_SIZE, GFP_ATOMIC); memcpy(buf, source_data, BUF_SIZE); ``` #### 4. **关键注意事项** 1. **释放方式相同**:两者均需用`kfree()`释放 2. **物理连续性**:均保证物理地址连续(适用于DMA) 3. **标志位兼容**:支持相同的`GFP_*`内存分配标志(如`GFP_KERNEL`/`GFP_ATOMIC`) 4. **大小限制**:最大分配尺寸通常为4MB(依赖`CONFIG_FRAG_ALLOC`) > **安全建议**:在驱动开发中优先使用`kzalloc`,可避免未初始化内存漏洞(如内核信息泄漏)[^1]。 --- ### 相关问题 1. `kzalloc`的零初始化在内核启动阶段是否会产生性能瓶颈? 2. 如何通过`kmalloc`手动实现等效于`kzalloc`的功能? 3. 什么场景下必须使用`GFP_ATOMIC`标志而非`GFP_KERNEL`? 4. `vmalloc``kmalloc`有何区别?何时应选择`vmalloc`? 5. Linux内核如何处理`kzalloc/kmalloc`的内存碎片问题? [^1]: Linux内核文档`memory-allocation.txt`强调零初始化对安全性的重要性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值