在C语言中,动态内存分配通常用于在运行时申请内存。在内核编程中,动态内存分配与用户空间有所不同,因为内核需要更谨慎地处理内存,且不能使用用户空间的库(如glibc)。下面我们将详细分析Linux内核中动态申请内存的函数及其区别。
Linux内核中动态内存分配函数
1. kmalloc
kmalloc
函数用于在内核空间申请一块连续的内存区域。它的原型如下:
void *kmalloc(size_t size, gfp_t flags);
- 参数:
size
:要分配的内存大小(以字节为单位)。flags
:分配标志,用于指定分配内存的行为和内存类型(例如GFP_KERNEL
、GFP_ATOMIC
等)。
- 返回值:成功时返回指向分配内存的指针,失败时返回
NULL
。 - 特点:
- 分配的内存是物理上连续的(在虚拟地址空间也是连续的)。
- 分配的大小有限制(通常最大为4MB,但具体取决于配置和架构)。
- 适用于需要小块连续内存的情况(如结构体、缓冲区等)。
2. kzalloc
kzalloc
是kmalloc
的一个变种,它在分配内存的同时将内存初始化为0。原型如下:
void *kzalloc(size_t size, gfp_t flags);
- 它等价于先用
kmalloc
分配内存,然后用memset
清零。 - 参数和返回值与
kmalloc
相同。
3. vmalloc
vmalloc
用于分配大块连续虚拟内存(物理内存不一定连续)。原型如下:
void *vmalloc(unsigned long size);
- 参数:
size
:要分配的内存大小(以字节为单位)。
- 返回值:成功时返回虚拟地址连续的指针,失败时返回
NULL
。 - 特点:
- 分配的内存虚拟地址连续,但物理地址可能不连续。
- 分配的内存可以很大(远大于
kmalloc
的限制)。 - 访问速度可能比
kmalloc
慢,因为需要建立页表映射,且可能引起TLB抖动。 - 适用于需要大块内存且不需要物理连续性的情况(如模块加载、大型缓冲区等)。
4. kcalloc
kcalloc
用于分配数组内存,并将内存初始化为0。原型如下:
void *kcalloc(size_t n, size_t size, gfp_t flags);
- 参数:
n
:数组元素个数。size
:每个元素的大小。flags
:同kmalloc
。
- 返回值:成功返回指针,失败返回
NULL
。 - 它分配的内存大小为
n * size
,并初始化为0。
5. alloc_pages
/ __get_free_pages
这两个函数用于直接分配页面(以页为单位)。
alloc_pages
返回struct page *
,而__get_free_pages
返回虚拟地址。- 原型:
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order); unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
- 参数:
gfp_mask
:分配标志。order
:指定分配页数的对数(即分配页数为2^order
)。
- 它们分配的内存是物理连续的,适用于大块内存需求(但比
vmalloc
高效,因为物理连续)。
6. kmem_cache_alloc
用于从特定的内存缓存中分配对象。内核中经常需要频繁分配和释放相同大小的内存块(如结构体),使用slab分配器可以提高效率。
- 首先需要创建一个缓存:
struct kmem_cache *kmem_cache_create(const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void *));
- 然后从缓存中分配:
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);
- 释放对象到缓存:
void kmem_cache_free(struct kmem_cache *cachep, void *objp);
- 销毁缓存:
void kmem_cache_destroy(struct kmem_cache *cachep);
区别对比
函数 | 物理连续 | 虚拟连续 | 最大大小 | 适用场景 | 速度 |
---|---|---|---|---|---|
kmalloc/kzalloc | 是 | 是 | 较小(如4MB) | 小内存、需要物理连续 | 快 |
vmalloc | 否 | 是 | 很大(如多个GB) | 大内存、不需要物理连续 | 慢 |
kcalloc | 是 | 是 | 同kmalloc | 数组分配并初始化为0 | 快 |
alloc_pages 等 | 是 | 是 | 较大(多页) | 大块物理连续内存 | 快 |
kmem_cache_alloc | 是(通常) | 是 | 由缓存对象决定 | 频繁分配释放固定大小对象 | 非常快 |
分配标志(gfp_t flags)常用值
GFP_KERNEL
:常规分配,可能睡眠(阻塞),适用于进程上下文。GFP_ATOMIC
:原子分配,不会睡眠,适用于中断上下文或不能睡眠的地方。GFP_USER
:为用户空间分配内存。GFP_HIGHUSER
:从高端内存为用户空间分配。GFP_NOIO
:禁止I/O操作(在存储管理代码中使用)。GFP_NOFS
:禁止文件系统操作(在文件系统代码中使用)。GFP_DMA
:分配可用于DMA的内存(物理地址在16MB以下)。GFP_DMA32
:分配可用于DMA的内存(物理地址在4GB以下)。
使用注意事项
- 上下文要求:在中断上下文、软中断、持有自旋锁等原子上下文中,不能使用可能睡眠的函数(如
GFP_KERNEL
),而应使用GFP_ATOMIC
。 - 内存泄漏:内核内存不会自动释放,必须显式释放,否则会导致内存泄漏。
- 碎片问题:频繁分配释放内存可能导致碎片,使用slab分配器可以减少碎片。
- 性能考量:
vmalloc
分配的内存访问速度相对较慢,应避免在性能关键路径中使用。
释放内存的函数
kmalloc/kzalloc/kcalloc
分配的内存用kfree
释放。vmalloc
分配的内存用vfree
释放。__get_free_pages
分配的内存用free_pages
释放。alloc_pages
分配的内存用__free_pages
释放。kmem_cache_alloc
分配的内存用kmem_cache_free
释放。
示例代码片段
// 使用kmalloc
void *ptr = kmalloc(100, GFP_KERNEL);
if (ptr) {// 使用内存kfree(ptr);
}
// 使用vmalloc
void *vptr = vmalloc(1000000);
if (vptr) {// 使用内存vfree(vptr);
}
// 使用内存缓存
struct kmem_cache *my_cache = kmem_cache_create("my_cache", sizeof(struct my_struct), 0, 0, NULL);
struct my_struct *obj = kmem_cache_alloc(my_cache, GFP_KERNEL);
if (obj) {// 使用对象kmem_cache_free(my_cache, obj);
}
kmem_cache_destroy(my_cache);
以上是Linux内核中动态内存分配的主要函数及其区别。在实际使用中,应根据具体需求选择合适的内存分配函数。