malloc相关函数

2024-11-30 161 0

Linux 内核中,内存分配是一个核心任务,系统提供了多个 API 函数来分配不同类型的内存。这些函数包括 kmallocvmallockvmalloc 和其他派生函数,例如用户态的 malloccalloc。以下是对这些函数及其相关内容的详细解析。

kmalloc

kmallocLinux 内核中最常用的内存分配函数。它用于分配内核中的物理连续内存块。底层通过 伙伴系统(Buddy System) 分配物理页。

void *kmalloc(size_t size, gfp_t flags);

size: 需要分配的字节数。
flags: 内存分配标志(GFP_KERNEL, GFP_ATOMIC 等),控制分配行为。

对应的物理页分配函数

alloc_pageskmalloc 底层调用的核心函数之一,用于分配物理页。

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

kvmallockmallocvmalloc 的组合,它首先尝试使用 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 动态(kmallocvmalloc alloc_pages 动态调整 动态映射
函数 是否清零 虚拟地址连续 物理地址连续 分配大小 特点与场景
kmalloc ❌ 否 ✅ 是 ✅ 是 快速,DMA/驱动/结构体
kzalloc ✅ 是 ✅ 是 ✅ 是 清零版 kmalloc
vmalloc ❌ 否 ✅ 是 ❌ 否 非DMA,大数据缓冲
vzalloc ✅ 是 ✅ 是 ❌ 否 清零版 vmalloc
kvmalloc ❌ 否 ✅ 是 ✅/❌ 自动选择 中大 自动选用 kmalloc 或 vmalloc
kvzalloc ✅ 是 ✅ 是 ✅/❌ 自动选择 中大 自动选用 kzalloc 或 vzalloc

malloc,calloc 和 realloc

这些函数属于用户态函数,在 C 标准库中定义,与内核空间无关。

区别

函数 区别
malloc 分配内存但不初始化
calloc 分配内存并将内存初始化为零
realloc 调整已分配内存的大小

相关函数

free:释放 mallocrealloccalloc 分配的内存。

使用示例

#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 等)。

大块内存优先使用 vmallockvmalloc 避免使用 kmalloc 申请大块内存,容易失败。

通过选择合适的内存分配函数,能够有效管理内存并优化性能。

相关文章

发布评论