首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >malloc、calloc、kmalloc、vmalloc 详解

malloc、calloc、kmalloc、vmalloc 详解

作者头像
一个平凡而乐于分享的小比特
发布2026-02-02 15:25:28
发布2026-02-02 15:25:28
1370
举报

🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习 🎬擅长领域:驱动开发,嵌入式软件开发,BSP开发 ❄️作者主页:一个平凡而乐于分享的小比特的个人主页 ✨收录专栏:Linux,本专栏目的在于,记录学习Linux操作系统的总结 欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖

在这里插入图片描述
在这里插入图片描述

malloc/calloc/kmalloc/vmalloc 详解

一、用户空间内存分配

malloc:动态内存分配的基石
代码语言:javascript
复制
void *malloc(size_t size);

内部实现机制:

代码语言:javascript
复制
用户进程地址空间
┌─────────────────────┐
│     栈(stack)       │ ← 向下生长
├─────────────────────┤
│         ↓           │
│        空           │
│        间           │
├─────────────────────┤
│  内存映射区(mmapped)│ ← 大内存分配(>128KB)
│                     │   直接映射到文件/匿名页
├─────────────────────┤
│         ↑           │
│        堆(heap)     │ ← 小内存分配(≤128KB)
│                     │   通过brk调整堆顶
├─────────────────────┤
│   未初始化数据(BSS) │
├─────────────────────┤
│   已初始化数据      │
├─────────────────────┤
│      代码段(text)   │
└─────────────────────┘

两种分配策略对比:

特性

brk分配(小内存≤128KB)

mmap分配(大内存>128KB)

分配位置

堆区域

内存映射区(堆栈之间)

系统调用

brk()/sbrk()

mmap()

内存连续性

连续扩展堆

独立映射段

释放行为

放回malloc内存池

直接归还OS

内存碎片

容易产生碎片

较少碎片

性能开销

较小

较大(用户/内核切换)

内存管理链表结构:

代码语言:javascript
复制
内存块链表
┌─────┬─────┬─────────────┐
│元数据│used│ 用户数据区   │ ← 分配的内存块
├─────┼─────┼─────────────┤
│size │ 1   │  data...    │
└─────┴─────┴─────────────┘
    ▲
    │
┌─────┬─────┬─────────────┐
│元数据│used│ 用户数据区   │
├─────┼─────┼─────────────┤
│size │ 0   │ (空闲)      │ ← 空闲块
└─────┴─────┴─────────────┘
malloc函数家族
代码语言:javascript
复制
// 1. malloc - 基本分配
void *ptr = malloc(100); // 分配100字节,内容未初始化

// 2. calloc - 分配并清零
void *ptr = calloc(10, sizeof(int)); // 分配10个int,全部初始化为0
// 相当于: malloc(10 * sizeof(int)) + memset(ptr, 0, 10 * sizeof(int))

// 3. realloc - 调整大小
void *new_ptr = realloc(ptr, 200); // 调整到200字节
// 可能原地扩展、移动数据、或分配新区域

// 4. alloca - 栈分配(不常用)
void *ptr = alloca(100); // 在栈上分配,函数返回自动释放

内存分配流程示例:

代码语言:javascript
复制
// 场景:分配小内存
int *arr1 = malloc(1024);  // 1KB → 使用brk分配
// 场景:分配大内存
int *arr2 = malloc(1024*256); // 256KB → 使用mmap分配

// 初始化数组
int *zeros = calloc(100, sizeof(int));
// zeros[0]~zeros[99] 都是0

// 扩展内存
zeros = realloc(zeros, 200 * sizeof(int));
// 前100个元素保留,后100个未初始化

二、内核空间内存分配

内核地址空间布局
代码语言:javascript
复制
内核虚拟地址空间
┌─────────────────────┐
│   vmalloc区域       │ ← vmalloc分配
│                     │   虚拟连续,物理不一定连续
├─────────────────────┤
│   持久内核映射区    │
├─────────────────────┤
│   固定映射区        │
├─────────────────────┤
│   kmalloc区域       │ ← kmalloc分配
│                     │   虚拟和物理都连续
├─────────────────────┤
│   低端内存直接映射区 │
│   (3:1或2:2划分)    │
└─────────────────────┘
kmalloc vs vmalloc 详细对比

特性

kmalloc

vmalloc

内存类型

物理连续内存

虚拟连续,物理可分散

分配大小

有限制(通常≤4MB)

可分配较大内存

性能

快(直接映射)

慢(建立页表)

适用场景

DMA、硬件交互

大块内存、模块加载

CPU缓存

可能利用缓存

可能刷新TLB

内存来源

内核slab分配器

获取独立页并映射

对齐保证

按大小对齐

页对齐

睡眠可能

可指定GFP_KERNEL允许睡眠

可能引起睡眠

kmalloc 深入解析
代码语言:javascript
复制
#include <linux/slab.h>

// GFP标志示例
void *ptr;

// 常用分配方式
ptr = kmalloc(1024, GFP_KERNEL);      // 可能睡眠
ptr = kmalloc(1024, GFP_ATOMIC);      // 原子分配,不睡眠
ptr = kzalloc(1024, GFP_KERNEL);      // 分配并清零(类似calloc)

// 硬件相关分配
ptr = kmalloc(1024, GFP_DMA);         // DMA可用内存
ptr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);

// 释放内存
kfree(ptr);

kmalloc内存来源 - Slab分配器:

代码语言:javascript
复制
物理内存页
┌─────────────────┐
│     Slab缓存    │ ← 专用缓存(如task_struct)
├─────────────────┤
│ 通用缓存(2^n)   │ ← kmalloc的8,16,32,...,4096字节缓存
│  8B│16B│32B│...│
└─────────────────┘
    │
    ▼
用户请求128字节 → 从128B缓存slab分配
vmalloc 深入解析
代码语言:javascript
复制
#include <linux/vmalloc.h>

// vmalloc使用示例
void *ptr;

ptr = vmalloc(1024 * 1024);  // 分配1MB
ptr = vzalloc(1024 * 1024);  // 分配并清零

// 使用后释放
vfree(ptr);

vmalloc分配过程:

代码语言:javascript
复制
1. 在vmalloc区域找虚拟地址空间
   ┌─────────┐
   │ 虚拟地址 │← 找到连续虚拟空间
   └─────────┘

2. 分配物理页框(可能不连续)
   ┌───┐ ┌───┐ ┌───┐
   │ 4K │ │ 4K │ │ 4K │ ← 分散的物理页
   └───┘ └───┘ └───┘

3. 建立页表映射
   虚拟地址 → 页表 → 物理地址

三、实战场景分析

场景1:用户空间程序开发
代码语言:javascript
复制
// 推荐实践
#define SMALL_SIZE  (1024)      // 1KB - brk分配
#define LARGE_SIZE  (200 * 1024) // 200KB - mmap分配

void process_data() {
    // 小内存:用calloc自动初始化
    int *buffer = calloc(SMALL_SIZE, sizeof(int));
    
    // 大内存:用malloc + 手动初始化
    char *large_buf = malloc(LARGE_SIZE);
    if (large_buf) {
        memset(large_buf, 0, LARGE_SIZE);
    }
    
    // 调整内存
    buffer = realloc(buffer, SMALL_SIZE * 2);
    
    free(buffer);
    free(large_buf);
}
场景2:内核驱动开发
代码语言:javascript
复制
// 设备驱动中的内存使用
struct my_device {
    dma_addr_t dma_handle;
    void *cpu_addr;
    void *large_buffer;
};

static int my_probe(struct device *dev) {
    struct my_device *md = kmalloc(sizeof(*md), GFP_KERNEL);
    
    // DMA内存:必须物理连续
    md->cpu_addr = dma_alloc_coherent(dev, 
                                       DMA_SIZE,
                                       &md->dma_handle,
                                       GFP_KERNEL);
    
    // 大缓冲区:不需要物理连续
    md->large_buffer = vmalloc(LARGE_BUFFER_SIZE);
    
    return 0;
}

static void my_remove(struct device *dev) {
    // 按分配方式反向释放
    vfree(md->large_buffer);
    dma_free_coherent(dev, DMA_SIZE, 
                      md->cpu_addr, md->dma_handle);
    kfree(md);
}
场景3:性能敏感场景
代码语言:javascript
复制
// 网络数据包处理(不能睡眠)
void process_packet(struct sk_buff *skb) {
    // 原子分配,不会导致进程睡眠
    void *temp = kmalloc(TEMP_SIZE, GFP_ATOMIC);
    
    // 快速处理...
    
    kfree(temp);
}

// 模块初始化(可以睡眠)
static int __init my_module_init(void) {
    // 常规分配,允许页面回收
    void *data = kmalloc(DATA_SIZE, GFP_KERNEL);
    
    // 大内存分配
    void *lookup_table = vmalloc(LOOKUP_TABLE_SIZE);
    
    // 初始化...
    
    return 0;
}

四、内存分配流程图

代码语言:javascript
复制
用户空间分配(malloc)
      │
      ├── size ≤ 128KB?
      │     ├── 是 → 使用brk()扩展堆
      │     │       从空闲链表查找合适块
      │     │       或分裂大块内存
      │     │
      │     └── 否 → 使用mmap()创建映射
      │             在堆栈间找空闲区域
      │             建立匿名内存映射
      │
      ├── 更新内存管理链表
      ├── 返回虚拟地址
      └── 首次访问时触发缺页中断
            ↓
           物理内存实际分配

内核空间分配
      │
      ├── 需要物理连续?
      │     ├── 是 → kmalloc()
      │     │      从slab缓存分配
      │     │      检查GFP标志
      │     │      返回直接映射地址
      │     │
      │     └── 否 → vmalloc()
      │             找虚拟地址空间
      │             分配分散物理页
      │             建立页表映射
      │
      └── 返回内核虚拟地址

五、关键要点总结

  1. malloc的智能策略:根据大小自动选择brk或mmap,平衡性能和碎片
  2. kmalloc的局限性:物理连续性要求限制了最大分配大小,但性能优异
  3. vmalloc的灵活性:可分配大内存但性能较低,适合不要求物理连续的场景
  4. 选择原则
    • 用户空间:让malloc自动选择策略
    • 内核小内存:kmalloc + 合适GFP标志
    • 内核大内存/不要求连续:vmalloc
    • DMA操作:kmalloc with GFP_DMA 或 dma_alloc_coherent
  5. 内存管理本质:所有分配最初都只是虚拟地址,访问时才通过缺页中断获取物理页

理解这些分配器的差异和适用场景,能够帮助开发者在不同环境下做出最优的内存管理决策。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • malloc/calloc/kmalloc/vmalloc 详解
    • 一、用户空间内存分配
      • malloc:动态内存分配的基石
      • malloc函数家族
    • 二、内核空间内存分配
      • 内核地址空间布局
      • kmalloc vs vmalloc 详细对比
      • kmalloc 深入解析
      • vmalloc 深入解析
    • 三、实战场景分析
      • 场景1:用户空间程序开发
      • 场景2:内核驱动开发
      • 场景3:性能敏感场景
    • 四、内存分配流程图
    • 五、关键要点总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档