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

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 │ (空闲) │ ← 空闲块
└─────┴─────┴─────────────┘// 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); // 在栈上分配,函数返回自动释放内存分配流程示例:
// 场景:分配小内存
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 | vmalloc |
|---|---|---|
内存类型 | 物理连续内存 | 虚拟连续,物理可分散 |
分配大小 | 有限制(通常≤4MB) | 可分配较大内存 |
性能 | 快(直接映射) | 慢(建立页表) |
适用场景 | DMA、硬件交互 | 大块内存、模块加载 |
CPU缓存 | 可能利用缓存 | 可能刷新TLB |
内存来源 | 内核slab分配器 | 获取独立页并映射 |
对齐保证 | 按大小对齐 | 页对齐 |
睡眠可能 | 可指定GFP_KERNEL允许睡眠 | 可能引起睡眠 |
#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分配#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. 建立页表映射
虚拟地址 → 页表 → 物理地址// 推荐实践
#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);
}// 设备驱动中的内存使用
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);
}// 网络数据包处理(不能睡眠)
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()
│ 找虚拟地址空间
│ 分配分散物理页
│ 建立页表映射
│
└── 返回内核虚拟地址理解这些分配器的差异和适用场景,能够帮助开发者在不同环境下做出最优的内存管理决策。