在 Linux
内核中,内存分配是一个核心任务,系统提供了多个 API 函数来分配不同类型的内存。这些函数包括 kmalloc
、vmalloc
、kvmalloc
和其他派生函数,例如用户态的 malloc
和 calloc
。以下是对这些函数及其相关内容的详细解析。
kmalloc
kmalloc
是 Linux
内核中最常用的内存分配函数。它用于分配内核中的物理连续内存块。底层通过 伙伴系统(Buddy System) 分配物理页。
void *kmalloc(size_t size, gfp_t flags);
size
: 需要分配的字节数。
flags
: 内存分配标志(GFP_KERNEL
, GFP_ATOMIC
等),控制分配行为。
对应的物理页分配函数
alloc_pages
是 kmalloc
底层调用的核心函数之一,用于分配物理页。
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);
gfp_mask
: 分配标志,与 kmalloc
使用的 GFP 标志一致。
order
: 分配的页阶数,order = log~2(size / PAGE_SIZE)。
返回值是一个指向分配的 struct page
的指针。
内存映射
alloc_pages
分配的内存以页为单位,kmalloc
通过内核直接映射到虚拟地址空间,返回虚拟地址。
示例
直接调用 alloc_pages
分配物理页:
#include <linux/gfp.h>
#include <linux/mm.h>
struct page *page = alloc_pages(GFP_KERNEL, 0); // 分配一个页(4 KB)
if (!page) {
printk(KERN_ERR "Failed to allocate pages\n");
}
// 将 page 转换为虚拟地址
void *virt_addr = page_address(page);
// 使用分配的内存...
__free_pages(page, 0); // 释放页
特点
分配的内存是物理上连续的。 用于对性能要求较高的场景,例如 DMA(直接内存访问)。 内存分配可能会阻塞,具体取决于分配标志。
使用示例
#include <linux/slab.h>
void *buffer = kmalloc(1024, GFP_KERNEL);
if (!buffer) {
printk(KERN_ERR "Failed to allocate memory\n");
}
// 使用 buffer
kfree(buffer);
常见分配标志
GFP_KERNEL
: 常规内存分配,可能会引发调度。
GFP_ATOMIC
: 原子性分配,不会引发调度,适用于中断上下文。
GFP_DMA
: 分配适用于 DMA 的内存。
vmalloc
vmalloc
用于分配虚拟地址空间的内存,分配的内存块不需要物理连续。
void *vmalloc(unsigned long size);
size
: 需要分配的内存大小。
对应的物理页分配函数
alloc_pages
或 __get_free_pages
vmalloc
会通过 alloc_pages
分配多个物理页。
这些物理页的虚拟地址通过页表映射到一个连续的虚拟地址空间。
内存映射
内核调用 vm_map_ram
或类似机制,将分配的物理页映射到虚拟地址。
使用 vmalloc
时,vmalloc_area_struct
记录了虚拟地址范围及其对应的物理页。
示例
直接调用 alloc_pages
并构造 vmalloc
风格的虚拟地址映射:
#include <linux/vmalloc.h>
#include <linux/mm.h>
// 使用 vmalloc
void *buffer = vmalloc(1024 * 1024); // 分配 1 MB
if (!buffer) {
printk(KERN_ERR "Failed to allocate virtual memory\n");
}
// vmalloc 内部会使用 alloc_pages 分配多个物理页,并将其映射到 buffer 的虚拟地址
vfree(buffer); // 释放内存
特点
分配的内存是虚拟连续的,但物理内存可能是分散的。 适用于大块内存分配,但性能稍差,因为访问需要页表转换, 常用于对物理连续性要求不高的场景,例如大块缓冲区。
使用示例
#include <linux/vmalloc.h>
void *buffer = vmalloc(1024 * 1024);
if (!buffer) {
printk(KERN_ERR "Failed to allocate memory\n");
}
// 使用 buffer
vfree(buffer);
kvmalloc
kvmalloc
是 kmalloc
和 vmalloc
的组合,它首先尝试使用 kmalloc
分配内存(物理连续),如果失败则退回到 vmalloc
(虚拟连续)。
void *kvmalloc(size_t size, gfp_t flags);
size
: 分配的大小。
flags
: 分配标志,与 kmalloc
的标志一致。
特点
提供了一种统一的内存分配接口。
推荐在不确定分配大小是否适合 kmalloc 时使用。
使用示例
#include <linux/mm.h>
void *buffer = kvmalloc(1024 * 1024, GFP_KERNEL);
if (!buffer) {
printk(KERN_ERR "Failed to allocate memory\n");
}
// 使用 buffer
kvfree(buffer);
内核页分配总结
分配函数 | 分配方式 | 对应物理页分配函数 | 连续性 | 页映射方式 |
---|---|---|---|---|
kmalloc |
物理连续 | alloc_pages |
物理连续 | 直接映射 |
vmalloc |
虚拟连续 | alloc_pages |
物理不连续 | 页表映射 |
kvmalloc |
动态(kmalloc 或 vmalloc ) |
alloc_pages |
动态调整 | 动态映射 |
函数 | 是否清零 | 虚拟地址连续 | 物理地址连续 | 分配大小 | 特点与场景 |
---|---|---|---|---|---|
kmalloc | ❌ 否 | ✅ 是 | ✅ 是 | 小 | 快速,DMA/驱动/结构体 |
kzalloc | ✅ 是 | ✅ 是 | ✅ 是 | 小 | 清零版 kmalloc |
vmalloc | ❌ 否 | ✅ 是 | ❌ 否 | 大 | 非DMA,大数据缓冲 |
vzalloc | ✅ 是 | ✅ 是 | ❌ 否 | 大 | 清零版 vmalloc |
kvmalloc | ❌ 否 | ✅ 是 | ✅/❌ 自动选择 | 中大 | 自动选用 kmalloc 或 vmalloc |
kvzalloc | ✅ 是 | ✅ 是 | ✅/❌ 自动选择 | 中大 | 自动选用 kzalloc 或 vzalloc |
malloc,calloc 和 realloc
这些函数属于用户态函数,在 C 标准库中定义,与内核空间无关。
区别
函数 | 区别 |
---|---|
malloc |
分配内存但不初始化 |
calloc |
分配内存并将内存初始化为零 |
realloc |
调整已分配内存的大小 |
相关函数
free
:释放 malloc
,realloc
和 calloc
分配的内存。
使用示例
#include <stdlib.h>
int *arr = malloc(10 * sizeof(int));
if (!arr) {
perror("Failed to allocate memory");
exit(1);
}
// 使用 arr
free(arr);
比较和选择
函数 | 分配内存位置 | 连续性要求 | 大小限制 | 性能 | 常见用途 |
---|---|---|---|---|---|
kmalloc |
内核空间 | 物理连续 | 小块内存(通常 <= PAGE_SIZE) | 高 | DMA 缓冲区,小内存分配 |
vmalloc |
内核空间 | 虚拟连续 | 无限制 | 较低 | 大块内存分配 |
kvmalloc |
内核空间 | 动态(物理或虚拟) | 无限制 | 动态调整 | 统一接口,适合通用场景 |
malloc |
用户空间 | 虚拟地址 | 无限制 | 高 | 用户态小块或大块分配 |
calloc |
用户空间 | 虚拟地址 | 无限制 | 高 | 分配并初始化为零的内存 |
注意事项
内核空间内存分配限制
内核空间内存分配会消耗系统资源,不建议长期分配大块内存。
避免频繁分配和释放内存,可能导致内存碎片化。
释放内存
kmalloc
-> kfree
vmalloc
-> vfree
kvmalloc
-> kvfree
分配标志
根据代码运行环境选择合适的标志(GFP_KERNEL
, GFP_ATOMIC
等)。
大块内存优先使用 vmalloc
或 kvmalloc
避免使用 kmalloc
申请大块内存,容易失败。
通过选择合适的内存分配函数,能够有效管理内存并优化性能。