malloc、calloc、kmalloc、vmalloc 详解

🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习

🎬擅长领域:驱动开发,嵌入式软件开发,BSP开发

❄️作者主页:一个平凡而乐于分享的小比特的个人主页

✨收录专栏:Linux,本专栏目的在于,记录学习Linux操作系统的总结

欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖

malloc/calloc/kmalloc/vmalloc 详解

一、用户空间内存分配

malloc:动态内存分配的基石

c 复制代码
void *malloc(size_t size);

内部实现机制:

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

两种分配策略对比:

特性 brk分配(小内存≤128KB) mmap分配(大内存>128KB)
分配位置 堆区域 内存映射区(堆栈之间)
系统调用 brk()/sbrk() mmap()
内存连续性 连续扩展堆 独立映射段
释放行为 放回malloc内存池 直接归还OS
内存碎片 容易产生碎片 较少碎片
性能开销 较小 较大(用户/内核切换)

内存管理链表结构:

复制代码
内存块链表
┌─────┬─────┬─────────────┐
│元数据│used│ 用户数据区   │ ← 分配的内存块
├─────┼─────┼─────────────┤
│size │ 1   │  data...    │
└─────┴─────┴─────────────┘
    ▲
    │
┌─────┬─────┬─────────────┐
│元数据│used│ 用户数据区   │
├─────┼─────┼─────────────┤
│size │ 0   │ (空闲)      │ ← 空闲块
└─────┴─────┴─────────────┘

malloc函数家族

c 复制代码
// 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); // 在栈上分配,函数返回自动释放

内存分配流程示例:

c 复制代码
// 场景:分配小内存
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个未初始化

二、内核空间内存分配

内核地址空间布局

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

kmalloc vs vmalloc 详细对比

特性 kmalloc vmalloc
内存类型 物理连续内存 虚拟连续,物理可分散
分配大小 有限制(通常≤4MB) 可分配较大内存
性能 快(直接映射) 慢(建立页表)
适用场景 DMA、硬件交互 大块内存、模块加载
CPU缓存 可能利用缓存 可能刷新TLB
内存来源 内核slab分配器 获取独立页并映射
对齐保证 按大小对齐 页对齐
睡眠可能 可指定GFP_KERNEL允许睡眠 可能引起睡眠

kmalloc 深入解析

c 复制代码
#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分配器:

复制代码
物理内存页
┌─────────────────┐
│     Slab缓存    │ ← 专用缓存(如task_struct)
├─────────────────┤
│ 通用缓存(2^n)   │ ← kmalloc的8,16,32,...,4096字节缓存
│  8B│16B│32B│...│
└─────────────────┘
    │
    ▼
用户请求128字节 → 从128B缓存slab分配

vmalloc 深入解析

c 复制代码
#include <linux/vmalloc.h>

// vmalloc使用示例
void *ptr;

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

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

vmalloc分配过程:

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

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

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

三、实战场景分析

场景1:用户空间程序开发

c 复制代码
// 推荐实践
#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:内核驱动开发

c 复制代码
// 设备驱动中的内存使用
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:性能敏感场景

c 复制代码
// 网络数据包处理(不能睡眠)
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;
}

四、内存分配流程图

复制代码
用户空间分配(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. 内存管理本质:所有分配最初都只是虚拟地址,访问时才通过缺页中断获取物理页

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

相关推荐
EXtreme352 个月前
深入浅出数据结构:手把手实现动态顺序表,从此不再怕数组扩容!
c语言·顺序表·malloc·realloc
棐木6 个月前
【C语言】动态内存管理
c语言·free·malloc·realloc·calloc·动态内存
linweidong1 年前
小鹏汽车C++面试题及参考答案
c++·c++11·内存管理·大厂面试·牛客网·malloc·八股文面试
深山老宅2 年前
九浅一深Jemalloc5.3.0 -- ④浅*配置
内存管理·内存分配·malloc·jemalloc 5.3.0
繁星璀璨G2 年前
C++标准模板(STL)- C 内存管理库 - 分配并清零内存 (std::calloc)
c语言·开发语言·c++·stl·calloc
number=100862 年前
操作系统:malloc与堆区内存管理
操作系统·malloc·堆区
南风与鱼2 年前
高效利用内存资源之动态内存管理详解
c语言·柔性数组·malloc·realloc·calloc·动态内存函数
4U2472 年前
C语言定长数组 变长数组 柔性数组
c语言·柔性数组·free·malloc·realloc·定长数组·变长数组
4U2472 年前
C语言之动态内存管理(malloc calloc realloc)
c语言·开发语言·free·malloc·realloc·calloc