内存分配API
Linux 内核提供了多种内存分配 API,适应不同的内存分配需求。kmalloc 是用于分配物理连续内存的快速 API,适合小内存块的分配。vmalloc 用于分配较大的虚拟内存块,适合对物理连续性不要求的场景。kmap 和 kmap_atomic 则用于映射物理内存到内核虚拟地址空间,适用于直接操作物理内存的场景。
kmalloc
kmalloc 是 Linux 内核中最常用的内存分配函数之一,用于分配物理连续的内存区域。
特点
- 物理连续内存:
kmalloc分配的内存是物理上连续的,这使得它适用于需要直接访问硬件设备的场景(如DMA操作),以及需要性能优化的场景(例如缓存、数据传输等)。 - 快速分配:由于
kmalloc使用的是内核的伙伴系统(Buddy System),其性能非常高效,尤其适合小内存分配。 - 内存大小限制:
kmalloc支持的最大分配尺寸通常与系统的页面大小(一般为4KB)以及系统的内存管理有关。kmalloc分配的内存块通常是以页为单位进行分配的。 - 返回指针:
kmalloc返回的是直接可访问的虚拟内存地址。
使用示例
#include <linux/slab.h>
void *ptr = kmalloc(1024, GFP_KERNEL);
if (!ptr) {
// 处理分配失败
}底层实现
kmalloc使用内核的 伙伴系统(Buddy System)来分配物理连续内存。它将内存划分为若干个大小为2的幂次方的内存块,并通过合并和拆分的方式进行内存分配和释放。
vmalloc
vmalloc 用于分配虚拟内存区域,但它分配的内存不要求物理连续。也就是说,vmalloc 分配的内存是虚拟地址上连续的,但其在物理内存中可能是非连续的。
特点
- 虚拟地址连续:
vmalloc分配的内存区域在虚拟地址空间中是连续的,但在物理内存中不需要是连续的。 - 适用于大内存块:
vmalloc适合于分配较大内存块(超过kmalloc支持的大小),尤其是在物理内存无法提供连续的空闲区域时。 - 较慢的分配:由于
vmalloc使用了页表来映射虚拟地址与物理地址之间的关系,分配速度相对较慢,尤其是在大量内存分配时。 - 适用场景:适用于需要较大内存块且不要求物理连续的情况,如内核模块的缓冲区、大型数据结构等。
使用示例
#include <linux/vmalloc.h>
void *ptr = vmalloc(1024 * 1024); // 分配 1MB 的内存
if (!ptr) {
// 处理分配失败
}底层实现
vmalloc使用了 页表映射 的机制,它将虚拟内存地址映射到物理内存页。这些物理页面在物理内存中可能不连续,但虚拟地址是连续的。- 内核将虚拟地址空间划分为多个区域,通过 页表 来管理虚拟地址和物理内存的映射。
kmap / kmap_atomic
kmap 和 kmap_atomic 用于映射页框(Page Frame)到内核的虚拟地址空间。这两个函数的作用是将物理页面映射为内核空间的虚拟地址,使得内核可以直接访问物理内存中的某个区域。kmap_atomic 是 kmap 的原子版本,通常在中断上下文中使用。
特点
- kmap:将物理页面映射到内核虚拟地址空间中,使内核能够访问物理页面。适用于大部分需要直接访问物理内存的场景。
- kmap_atomic:是
kmap的原子版本,主要用于中断上下文或禁止页表切换的场景。由于它不会发生上下文切换,因此需要限制每次只能映射少量的内存。 - 内存保护:通过内核页表映射,内存的保护机制依然有效。映射后的虚拟地址可以像普通指针一样使用。
使用示例
#include <linux/highmem.h>
void *virtual_address;
physical_page = ... // 获取物理页面的指针
// 在进程上下文中使用 kmap
virtual_address = kmap(physical_page);
// 在中断上下文中使用 kmap_atomic
virtual_address = kmap_atomic(physical_page, KM_IRQ0);
// 访问完成后,释放映射
kunmap(virtual_address);
kunmap_atomic(virtual_address);底层实现
kmap和kmap_atomic都使用了内核中的 高端内存(High Memory)区域,通常用于访问那些不直接映射到内核地址空间的物理内存页。它们通过修改页表实现物理页和虚拟地址的映射。kmap_atomic通常用于上下文切换不可见的场景(例如中断上下文),并使用了内核的原子操作来避免线程切换。
kmalloc 与 vmalloc 的对比
| 特性 | kmalloc | vmalloc |
|---|---|---|
| 内存分配方式 | 物理连续内存 | 虚拟地址连续,物理地址不连续 |
| 适用场景 | 小内存块、需要物理连续的场景 | 大内存块、对物理内存不连续不敏感的场景 |
| 速度 | 较快(基于伙伴系统) | 较慢(使用页表映射) |
| 分配大小限制 | 通常为一页内存(4KB),最大分配几 MB | 支持较大的内存分配(几 MB 或更多) |
| 底层实现 | 使用伙伴系统(Buddy System) | 使用页表映射,访问高端内存 |
| 分配的内存能否直接用于 DMA | 可以 | 不可以(需要通过其他机制映射) |
kmalloc 和 kmap/vmalloc 的选择
- 使用
kmalloc:当需要快速分配较小、物理连续的内存时,kmalloc是首选,尤其是当内存分配大小较小(通常为几 KB 或更小)时,kmalloc性能较高。 - 使用
vmalloc:当需要分配较大的内存块,且不关心物理地址的连续性时,vmalloc是合适的选择,尤其是在系统没有足够的物理连续内存时。 - 使用
kmap或kmap_atomic:用于在内核空间访问高端内存或者在中断上下文中访问内存。kmap_atomic适用于不能进行上下文切换的场景。