在Linux系统中,内存管理涉及用户态与内核态两套体系,各自提供不同的函数来满足分配、映射和释放需求。内核开发者若混淆了这些接口的适用边界,轻则造成内存浪费,重则触发oops或死锁。本文围绕几个最关键的函数展开,说明它们背后的分配逻辑与真实工程中的用法。

kmalloc与kfree:内核小块物理连续内存
kmalloc是内核中最常用的分配函数之一,它从slab分配器中获取内存,保证返回的虚拟地址所对应的物理页是连续的。这一特性对DMA操作至关重要,因为硬件设备通常只能识别物理地址。函数原型为void *kmalloc(size_t size, gfp_t flags),其中flags控制分配行为,例如GFP_KERNEL允许睡眠,而GFP_ATOMIC用于中断上下文。
下面的代码展示了一个内核模块中分配与释放结构体的典型做法。注意错误分支必须调用kfree,否则会产生内存泄漏。
#include <linux/slab.h>
#include <linux/module.h>
struct my_data {
int id;
char buf[64];
};
static int __init demo_init(void)
{
struct my_data *p;
p = kmalloc(sizeof(struct my_data), GFP_KERNEL);
if (!p)
return -ENOMEM;
p->id = 1;
printk("alloc ok, addr=%pn", p);
kfree(p);
return 0;
}
module_init(demo_init);
使用kmalloc的缺点在于它受限于物理内存碎片,无法分配过大空间,通常建议不超过128KB。若需要更多内存且不需物理连续,应考虑vmalloc。
vmalloc与vfree:大块虚拟连续内存
vmalloc分配的是虚拟地址连续、物理地址未必连续的内存,它通过对页表进行映射来实现。因为要建立页表,分配开销比kmalloc大,且不能用于DMA。适合在模块加载时分配较大的缓冲区,例如网络子系统的一些表项。
以下片段演示了vmalloc的基本使用。分配失败同样需要处理,并且必须用vfree释放。
#include <linux/vmalloc.h>
static int __init vdemo_init(void)
{
void *buf;
buf = vmalloc(1024 * 1024);
if (!buf)
return -ENOMEM;
memset(buf, 0, 1024 * 1024);
printk("vmalloc 1MB at %pn", buf);
vfree(buf);
return 0;
}
vmalloc分配的地址访问稍慢,因为可能涉及多级页表查询,但在需要大块内存且物理连续不重要的场景里,它是唯一可行的内核选项。
mmap:用户态映射内核或文件资源
mmap是用户态系统调用,也能通过驱动中的file_operations->mmap在内核侧实现,把设备内存或内核缓冲暴露给用户进程,避免频繁copy_to_user。典型驱动用remap_pfn_range完成映射。
内核侧实现可参考下面简化代码,将一段保留内存映射到用户空间。
#include <linux/mm.h>
static int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
unsigned long pfn = virt_to_phys(kernel_buf) >> PAGE_SHIFT;
return remap_pfn_range(vma, vma->vm_start, pfn,
vma->vm_end - vma->vm_start,
vma->vm_page_prot);
}
用户态调用mmap时,参数prot与flags决定权限与是否共享。错误使用MAP_FIXED可能覆盖已有映射,应谨慎。借助mmap,用户程序能以内存读写方式操作硬件寄存器,大幅提升吞吐。
函数对比与选型建议
为方便理解,下面用表格列出三者核心差异:
| 函数 | 空间位置 | 物理连续 | 典型场景 |
|---|---|---|---|
| kmalloc | 内核 | 是 | 小对象、DMA |
| vmalloc | 内核 | 否 | 大缓冲 |
| mmap | 用户 | 依赖后端 | 设备访问 |
实践中,中断处理函数只能用GFP_ATOMIC的kmalloc;初始化阶段可用vmalloc预备大结构;驱动暴露资源给用户选mmap。明确这些边界,才能让内存管理既安全又高效。
linux_memory_managementmmapkmalloc修改时间:2026-08-09 15:51:27