前言
在 Linux 驱动开发里,不管是创建设备私有数据结构、接收用户空间数据,还是构造 DMA Buffer,几乎每一步都绕不开内存分配。很多驱动的 probe() 函数一上来就要申请一块内存保存设备状态。正因如此,掌握内核内存分配 API 是驱动开发非常基础的一环。内核中有多个内存分配函数,它们之间到底有什么区别?下面逐一拆解。
常用内存分配函数
kmalloc / kzalloc
void *kmalloc(size_t size, gfp_t flags)
void *kzalloc(size_t size, gfp_t flags)
这是内核中最常用的通用分配接口。分配出来的内存不仅在虚拟地址上连续,在物理地址上也是连续的。
- 分配机制:建立在 Slab / Slub 内存池 之上,可以避免小块内存分配造成的碎片。
- GFP 标志:
- GFP_KERNEL:最常用的标志,可能会挂起或睡眠以等待空闲页,不可在中断上下文使用。
- GFP_ATOMIC:原子分配,绝不睡眠,适合在中断处理程序或持有自旋锁时使用。
- kzalloc 的区别:相比 kmalloc,它会自动调用 memset(ptr, 0, size),常用于避免信息泄露或未初始化指针问题。
示例:
struct my_data *data;
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
vmalloc / vzalloc
void *vmalloc(unsigned long size)
void *vzalloc(unsigned long size)
分配在虚拟地址上连续、但物理地址不一定连续的大块内存。
- 分配机制:通过从 Buddy System 多次申请单个独立物理页,再在内核页表中构建连续映射。
- 优点:能够分配极大的连续虚拟空间,不容易因物理内存碎片而失败。
示例:
void *buf;
buf = vzalloc(16 * 1024 * 1024);
alloc_pages
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)
这是基于 Buddy System 的底层页分配接口。
- 分配机制:以阶(Order)为单位分配 2^order 个物理页,例如 order=0 表示 1 页,order=3 表示 8 页。
示例:
struct page *page;
void *addr;
// order = 2 分配页数 = 2^2 = 4 pages
page = alloc_pages(GFP_KERNEL, 2);
// 虚拟地址
addr = page_address(page);
dma_alloc_coherent()
void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp)
设备驱动中与硬件进行 DMA 交互时专用的内存分配接口。
- 特点:保证物理地址连续,同时自动建立 CPU 与 DMA 外设之间的 Cache 一致性映射。
示例:
void *cpu_addr; // CPU使用的地址
dma_addr_t dma_addr; // DMA使用的地址
cpu_addr = dma_alloc_coherent(dev, size, &dma_addr, GFP_KERNEL);
总结
| 函数 |
物理地址 |
常用分配大小 |
中断上下文安全 |
特点 |
| kmalloc |
连续 |
小内存(一般小于128KB) |
是(取决于 GFP 标志) |
基于 Slab/Slub 分配器;速度最快,含物理连续开销 |
| kzalloc |
连续 |
小内存(一般小于128KB) |
是(取决于 GFP 标志) |
同 kmalloc,但分配后自动清零内存内容 |
| vmalloc |
不连续 |
大内存 |
否 |
通过修改页表映射离散页;有页表重映射开销 |
| alloc_pages |
连续 |
按页分配 |
是(取决于 GFP 标志) |
内核最基础的页级分配器 |
| dma_alloc_coherent |
连续 |
根据设备 DMA 需求而定 |
否 |
硬件 DMA 专用;提供一致性映射 |
更多 Linux 内核与驱动开发话题,欢迎到 云栈社区 一起交流。