Linux内核空间内存申请函数kmalloc、kzalloc、vmalloc的区别[转]

本文介绍了Linux内核中kmalloc(), kzalloc(), vmalloc()等内存分配函数的使用方法及其区别。详细解释了不同函数的应用场景,如kmalloc()适用于小于128KB的内存分配,vmalloc()适用于大块内存分配。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自:https://www.cnblogs.com/sky-heaven/p/7390370.html

我们都知道在用户空间动态申请内存用的函数是 malloc(),这个函数在各种操作系统上的使用是一致的,对应的用户空间内存释放函数是 free()。注意:动态申请的内存使用完后必须要释放,否则会造成内存泄漏,如果内存泄漏发生在内核空间,则会造成系统崩溃。 

  那么,在内核空间中如何申请内存呢?一般我们会用到 kmalloc()、kzalloc()、vmalloc() 等,下面我们介绍一下这些函数的使用以及它们之间的区别。
kmalloc()

函数原型:

void *kmalloc(size_t size, gfp_t flags);

  kmalloc() 申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB。 
   
较常用的 flags(分配内存的方法):

  • GFP_ATOMIC —— 分配内存的过程是一个原子过程,分配内存的过程不会被(高优先级进程或中断)打断;
  • GFP_KERNEL —— 正常分配内存;
  • GFP_DMA —— 给 DMA 控制器分配内存,需要使用该标志(DMA要求分配虚拟地址和物理地址连续)。

flags 的参考用法: 
 |– 进程上下文,可以睡眠     GFP_KERNEL 
 |– 进程上下文,不可以睡眠    GFP_ATOMIC 
 |  |– 中断处理程序       GFP_ATOMIC 
 |  |– 软中断          GFP_ATOMIC 
 |  |– Tasklet         GFP_ATOMIC 
 |– 用于DMA的内存,可以睡眠   GFP_DMA | GFP_KERNEL 
 |– 用于DMA的内存,不可以睡眠  GFP_DMA |GFP_ATOMIC 
   
对应的内存释放函数为:

void kfree(const void *objp);
kzalloc()

  kzalloc() 函数与 kmalloc() 非常相似,参数及返回值是一样的,可以说是前者是后者的一个变种,因为 kzalloc() 实际上只是额外附加了 __GFP_ZERO 标志。所以它除了申请内核内存外,还会对申请到的内存内容清零。

/** * kzalloc - allocate memory. The memory is set to zero. * @size: how many bytes of memory are required. * @flags: the type of memory to allocate (see kmalloc). */static inline void *kzalloc(size_t size, gfp_t flags){    return kmalloc(size, flags | __GFP_ZERO);}

kzalloc() 对应的内存释放函数也是 kfree()。

vmalloc()

函数原型:

void *vmalloc(unsigned long size);

  vmalloc() 函数则会在虚拟内存空间给出一块连续的内存区,但这片连续的虚拟内存在物理内存中并不一定连续。由于 vmalloc() 没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,如果需要申请较大的内存空间就需要用此函数了。

对应的内存释放函数为:

void vfree(const void *addr);

注意:vmalloc() 和 vfree() 可以睡眠,因此不能从中断上下文调用。 
  

总结

kmalloc()、kzalloc()、vmalloc() 的共同特点是:

  1. 用于申请内核空间的内存;
  2. 内存以字节为单位进行分配;
  3. 所分配的内存虚拟地址上连续;

kmalloc()、kzalloc()、vmalloc() 的区别是:

  1. kzalloc 是强制清零的 kmalloc 操作;(以下描述不区分 kmalloc 和 kzalloc)
  2. kmalloc 分配的内存大小有限制(128KB),而 vmalloc 没有限制;
  3. kmalloc 可以保证分配的内存物理地址是连续的,但是 vmalloc 不能保证;
  4. kmalloc 分配内存的过程可以是原子过程(使用 GFP_ATOMIC),而 vmalloc 分配内存时则可能产生阻塞;
  5. kmalloc 分配内存的开销小,因此 kmalloc 比 vmalloc 要快;

一般情况下,内存只有在要被 DMA 访问的时候才需要物理上连续,但为了性能上的考虑,内核中一般使用 kmalloc(),而只有在需要获得大块内存时才使用 vmalloc()。例如,当模块被动态加载到内核当中时,就把模块装载到由 vmalloc() 分配的内存上。


转自:http://www.cnblogs.com/sky-heaven/p/8144423.html

内存映射结构:

1.32位地址线寻址4G的内存空间,其中0-3G为用户程序所独有,3G-4G为内核占有。

2.struct page:整个物理内存在初始化时,每个4kb页面生成一个对应的struct page结构,这个page结构就独一无二的代表这个物理内存页面,并存放在mem_map全局数组中。

3.段式映射:首先根据代码段选择子cs为索引,以GDT值为起始地址的段描述表中选择出对应的段描述符,随后根据段描述符的基址,本段长度,权限信息等进行校验,校验成功后。cs:offset中的32位偏移量直接与本段基址相累加,得出最终访问地址。

0-3G与mem_map的映射方式:
因linux中采用的段式映射为flat模式,所以从逻辑地址到线性地址没有变化。从段式出来进入页式,每个用户进程都独自拥有一个页目录表(pdt),运行时存放于CR3。  CR3(页目录) + 前10位 =>  页面表基址 + 中10位 => 页表项 + 后12位 => 物理页面地址

3G-4G与mem_map的映射方式:
分为三种类型:低端内存/普通内存/高端内存。
低端内存:3G-3G+16M 用于DMA        __pa线性映射
普通内存:3G+16M-3G+896M          __pa线性映射 (若物理内存<896M,则分界点就在3G+实际内存)
高端内存:3G+896-4G               采用动态的分配方式

4.高端内存(假设3G+896为高端内存起址)
作用:访问到1G以外的物理内存空间。
线性地址共分为三段:vmalloc段/kmap段/kmap_atomic段(针对与不同的内存分配方式)


从内存分配函数的结构来看主要分为下面几个部分:
a.伙伴算法(最原始的面向页的分配方式)
alloc_pages 接口:
    struct page * alloc_page(unsigned int gfp_mask)——分配一页物理内存并返回该页物理内存的page结构指针。
    struct page * alloc_pages(unsigned int gfp_mask, unsigned int order)——分配 个连续的物理页并返回分配的第一个物理页的page结构指针。
    <释放函数:__free_page(s)>
    
    内核中定义:#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)   
    最终都是调用 __alloc_pages.
    其中MAX_ORDER 11,及最大分配到到页面个数为2^10(即4M)。
    分配页后还不能直接用,需要得到该页对应的虚拟地址:
    void *page_address(struct page *page);
    低端内存的映射方式:__va((unsigned long)(page  -  mem_map)  <<  12)
    高端内存到映射方式:struct page_address_map分配一个动态结构来管理高端内存。(内核是访问不到vma的3G以下的虚拟地址的) 具体映射由kmap / kmap_atomic执行。
    
get_free_page接口:(alloc_pages接口两步的替代函数)
    unsigned long get_free_page(unsigned int gfp_mask) 
    unsigned long __get_free_page(unsigned int gfp_mask) 
    Unsigned long __get_free_pages(unsigned int gfp_mask, unsigned int order)
    <释放函数:free_page>
    与alloc_page(s)系列最大的区别是无法申请高端内存,因为它返回到是一个线性地址,而高端内存是需要额外映射才可以访问的。

b.slab高速缓存(反复分配很多同一大小内存)   注:使用较少
    kmem_cache_t* xx_cache;
    创建: xx_cache = kmem_cache_create("name", sizeof(struct xx), SLAB_HWCACHE_ALIGN, NULL, NULL);
    分配: kmem_cache_alloc(xx_cache, GFP_KERNEL);
    释放: kmem_cache_free(xx_cache, addr);
  内存池
      mempool 不使用。
  
c.kmalloc(最常用的分配接口)         注:必须小于128KB
    GFP_ATOMIC 不休眠,用于中断处理等情况
    GFP_KERNEL 会休眠,一般状况使用此标记
    GFP_USER   会休眠
    __GFP_DMA  分配DMA内存
    kmalloc/kfree
    
d.vmalloc/vfree
    vmalloc采用高端内存预留的虚拟空间来收集内存碎片引起的不连续的物理内存页,是用于非连续物理内存分配。
当kmalloc分配不到内存且无物理内存连续的需求时,可以使用。(优先从高端内存中查找)
    
e.ioremap()/iounmap()
  ioremap()的作用是把device寄存器和内存的物理地址区域映射到内核虚拟区域,返回值为内核的虚拟地址。使用的线性地址区间也在vmmlloc段
注:
vmalloc()与 alloc_pages(_GFP_HIGHMEM)+kmap();前者不连续,后者只能映射一个高端内存页面
__get_free_pages与alloc_pages(NORMAL)+page_address(); 两者完全等同
内核地址通过 __va/__pa进行中低内存的直接映射
高端内存采用kmap/kmap_atomic的方式来映射

    
个人总结如下:
a.在<128kB的一般内存分配时,使用kmalloc
    允许睡眠:GFP_KERNEL
    不允许睡眠:GFP_ATOMIC
b.在>128kB的内存分配时,使用get_free_pages,获取成片页面,直接返回虚拟地址(<4M)(或alloc_pages + page_address)
c.b失败,
    如果要求分配高端内存:alloc_pages(_GFP_HIGHMEM)+kmap(仅能映射一个页面)
    如果不要求内存连续: 则使用vmalloc进行分配逻辑连续的大块页面.(不建议)/分配速度较慢,访问速率较慢。
d.频繁创建和销毁很多较大数据结构,使用slab.
e.高端内存映射:
    允许睡眠:kmap              (永久映射)
    不允许睡眠:kmap_atomic      (临时映射)会覆盖以前到映射(不建议)

<think>嗯,用户想了解Linux内核kzallockmalloc和zmalloc的区别和使用场景。这个问题涉及内核内存管理的核心机制,需要从实现原理和实际应用两个层面分析。 用户可能是内核开发者或系统程序员,正在调试内存相关的问题。ta特意提到三个容易混淆的函数,说明可能遇到了内存初始化或性能问题。需要特别注意zmalloc在内核中的特殊性——它其实不是原生API。先拆解kmalloc:这是最基础的内存分配器,源于Slab/Slub分配机制。它只分配不初始化,返回的是void指针。用户需要知道flags参数的重要性,比如GFP_KERNEL允许睡眠而GFP_ATOMIC用于中断上下文。这里要强调内存对齐特性,比如ARM64架构下默认16字节对齐。 kzalloc本质是kmalloc的封装版,关键区别在于初始化。它通过memset清零内存,这对安全敏感的驱动初始化特别有用。但要注意性能损耗——清零大块内存可能触发cache刷新。用户如果开发字符设备驱动,kzalloc能避免泄漏上次使用的数据。 zmalloc是个陷阱函数Linux内核根本没有这个API。用户可能混淆了用户空间的zmalloc(比如glibc)或某些旧版驱动私有实现。必须明确告知这一点,避免误用。使用场景对比需要结合实例:比如网络驱动收包时用kmalloc快速分配skb,而加密模块必须用kzalloc防止密钥残留。性能方面,中断处理路径优先选kmalloc+手动初始化,因为kzalloc可能调用睡眠函数。 突然想到用户可能被Android源码误导,某些厂商内核确实自定义了zmalloc。需要补充说明:若在内核代码看到zmalloc,务必检查是否为自定义宏。最后要提醒内存泄漏检测工具的使用,比如kmemleak对kzalloc分配的追踪方式不同。</think>以下是Linux内核内存分配函数`kzalloc`、`kmalloc``zmalloc`的详细对比及使用场景分析: --- ### **1. kmalloc** - **功能**:分配**未初始化**的物理连续内存块 - **函数原型**: ```c void *kmalloc(size_t size, gfp_t flags); ``` - **特点**: - 分配**物理地址连续**的内存(对DMA操作关键) - 内存内容**未初始化**(可能包含随机数据) - 支持多种内存分配标志(`GFP_KERNEL`, `GFP_ATOMIC`等) - **使用场景**: - 需要物理连续内存的驱动(如DMA缓冲区) - 性能敏感路径(避免清零开销) - 需手动初始化的临时数据结构 ```c // 示例:分配未初始化缓冲区 char *buf = kmalloc(1024, GFP_KERNEL); if (!buf) return -ENOMEM; memset(buf, 0, 1024); // 手动初始化 ``` --- ### **2. kzalloc** - **功能**:分配**已清零**的物理连续内存 - **本质**:`kmalloc` + `memset`的封装 - **函数原型**: ```c void *kzalloc(size_t size, gfp_t flags); ``` - **特点**: - 返回**零初始化**的内存(所有字节设为`0x00`) - 保证物理地址连续性 - **使用场景**: - 安全敏感场景(防止未初始化数据泄漏) - 结构体/数组初始化(避免随机值导致逻辑错误) - 驱动初始化(如设备状态结构体) ```c // 示例:安全初始化设备结构体 struct device *dev = kzalloc(sizeof(*dev), GFP_KERNEL); ``` --- ### **3. zmalloc(特别注意!)** - **关键事实**: **Linux内核原生API中不存在`zmalloc`函数!** 此名称可能源于以下混淆: 1. **用户空间库**(如glibc的`zmalloc`) 2. **第三方内核模块自定义实现**(非标准API) 3. **历史代码或误用**(如旧版驱动私有实现) --- ### **对比总结** | **函数** | 物理连续 | 内存初始化 | 内核标准API | 典型用途 | |------------|----------|------------|-------------|------------------------------| | `kmalloc` | ✅ | ❌ | ✅ | DMA缓冲区、性能敏感路径 | | `kzalloc` | ✅ | ✅(清零) | ✅ | 安全初始化、数据结构零填充 | | `zmalloc` | ❌ | ❌ | ❌ | **内核中不存在此标准函数** | --- ### **使用场景深度解析** 1. **`kmalloc`适用场景** - **DMA传输**:硬件要求物理连续内存(如网卡驱动收发包缓冲区) - **中断上下文**:配合`GFP_ATOMIC`标志分配(不可睡眠) - **高频小内存分配**:Slab分配器优化小对象性能 2. **`kzalloc`适用场景** - **密码学模块**:密钥缓冲区必须清零防泄漏 - **驱动探测函数**:初始化设备状态结构体(避免残留数据) - **安全敏感数据**:用户凭证、权限标识等 3. **关于`zmalloc`的澄清** 若在内核代码中看到`zmalloc`,通常是以下情况: - **自定义封装**:开发者实现的`kzalloc`别名(需检查代码定义) - **移植代码错误**:从用户空间误移植的库函数 ```c // 错误示例:非标准用法 // 正确做法应替换为kzallockmalloc+memset void *ptr = zmalloc(100); ``` --- ### **性能安全权衡** - **清零开销**: `kzalloc`比`kmalloc`多一次`memset`操作,分配大内存时(>4KB)需注意性能影响[^1]。 - **内存连续性**: 需物理连续时**不可用`vmalloc`**(仅虚拟连续),尤其对DMA设备[^2]。 - **初始化必要性**: 敏感数据必须用`kzalloc`,临时缓冲区可先用`kmalloc`再局部初始化。 --- ### **最佳实践建议** 1. 优先用`kzalloc`除非有明确性能需求 2. 中断上下文使用`GFP_ATOMIC`标志(不可用睡眠标志) 3. 检查分配结果:`if (!ptr) handle_oom();` 4. 大内存(>4MB)考虑`vmalloc`(但牺牲物理连续性和访问速度) > ⚠️ 关键提醒:**内核开发中切勿直接使用`zmalloc`**,除非明确其为当前项目的自定义函数。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值