linux 大页分配 内存池,内存分配kmalloc,内存池mempool,页分配get_free_page,虚拟分配vmallo...

本文介绍了Linux内核中的多种内存管理方法,包括kmalloc、后备高速缓存、内存池、面向页的分配技术和vmalloc等。重点讲解了每种方法的特点、适用场景及使用限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(1)内核内存分配函数kmalloc

是一个功能强大且高速(除非被阻塞)的工具,所分配到的内存在物理内存中连续且保持原有的数据(不清零)。原型:#include

void *kmalloc(size_t size, int flags);

size参数说明:内核管理系统的物理内存,物理内存只能按页面进行分配。kmalloc和典型的用户空间malloc在实际上有很大的差别,内核使用特

殊的基于页的分配技术,以最佳的方式利用系统RAM。Linux处理内存分配的方法:创建一系列内存对象集合,每个集合内的内存块大小是固定。处理分配请

求时,就直接在包含有足够大内存块的集合中传递一个整块给请求者。必须注意的是:内核只能分配一些预定义的、固定大小的字节数组。kmalloc能够处理

的最小内存块是32或64字节(体系结构依赖),而内存块大小的上限随着体系和内核配置而变化。考虑到移植性,不应分配大于128

KB的内存。若只需多于几个KB的内存块,最好使用其他方法。

flags参数说明:内存分配最终总是调用__get_free_pages 来进行实际的分配,这就是 GFP_ 前缀的由来。主要的标志包括:

GFP_KERNEL:最常用的标志,意思是这个分配代表运行在内核空间的进程进行。内核正常分配内存。当空闲内存较少时,可能进入休眠来等待一个页面。当前进程休眠时,内核会采取适当的动作来获取空闲页。

GFP_ATOMIC:内核通常会为原子性的分配预留一些空闲页。当当前进程不能被置为睡眠时,应使用 GFP_ATOMIC,这样kmalloc 甚至能够使用最后一个空闲页。如果连这最后一个空闲页也不存在,则分配返回失败。

GFP_USER:用来为用户空间分配内存页,可能睡眠。

__GFP_DMA:要求分配可用于DMA的内存。

__GFP_HIGHMEM:分配的内存可以位于高端内存。

后备高速缓存:内核为驱动程序常常需要反复分配许多相同大小内存块的情况,增加了一些特殊的内存池,称为后备高速缓存(lookaside

cache)。设备驱动程序通常不会涉及后备高速缓存,但是也有例外:在 Linux 2.6 中USB 和SCSI 驱动。Linux

内核的高速缓存管理器有时称为“slab 分配器”。slab 分配器实现的高速缓存具有 kmem_cache_t 类型。对应的函数有:

kmem_cache_t *kmem_cache_create(const char *name, size_t size,size_t offset,unsigned long flags,

void

(*constructor)(void *, kmem_cache_t *,unsigned long flags), void

(*destructor)(void *, kmem_cache_t *, unsigned long

flags));/*创建一个可以容纳任意数目内存区域的、大小都相同的高速缓存对象*/

通过调用kmem_cache_alloc 从已创建的后备高速缓存中分配对象:

void *kmem_cache_alloc(kmem_cache_t *cache, int flags);

/*cache 参数是刚创建的缓存,flags 是和kmalloc 的相同*/

使用kmem_cache_free释放一个对象:

void kmem_cache_free(kmem_cache_t *cache, const void *obj);

当驱动用完这个后备高速缓存(通常在当模块被卸载时),释放缓存:

int kmem_cache_destroy(kmem_cache_t *cache);

/*只在从这个缓存中分配的所有的对象都已返时才成功。因此,应检查 kmem_cache_destroy 的返回值:失败指示模块存在内存泄漏*/

(2)内存池:为了确保在内存分配不允许失败情况下成功分配内存,内核提供了称为内存池( "mempool"

)的抽象,它其实是某种后备高速缓存。它为了紧急情况下的使用。所以使用时必须注意:mempool会分配一些内存块,使其空闲而不真正使用,所以容易消

耗大量内存。而且不要使用mempool处理可失败的分配。应避免在驱动代码中使用mempool。对应的函数有:

mempool_t *mempool_create(int min_nr,mempool_alloc_t *alloc_fn,mempool_free_t *free_fn,void *pool_data);

/*min_nr 参数是内存池应当一直保留的最小数量的分配对象,实际的分配和释放对象由alloc_fn和free_fn处理*/

typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data);

typedef void (mempool_free_t)(void *element, void *pool_data);

/*给 mempool_create 最后的参数 *pool_data 被传递给 alloc_fn 和 free_fn */

创建内存池后,分配和释放对象:

void *mempool_alloc(mempool_t *pool, int gfp_mask);

void mempool_free(void *element, mempool_t *pool);

若不再需要内存池,则返回给系统:

void mempool_destroy(mempool_t *pool);

/*在销毁 mempool 之前,必须返回所有分配的对象,否则会产生 oops*/

给 mempool_create 最后的参数 ( pool_data ) 被传递给 alloc_fn 和 free_fn.

如果需要, 你可编写特殊用途的函数来处理 mempool 的内存分配. 常常, 但是, 你只需要使内核 slab 分配器为你处理这个任务. 有 2 个函数

( mempool_alloc_slab 和 mempool_free_slab) 来进行在内存池分配原型和 kmem_cache_alloc 和

kmem_cache_free 之间的感应淬火. 因此, 设置内存池的代码常常看来如此:struct stucent

{

int age;

char name[32];

};

#define MY_POOL_MININUM sizeof(struct student)*1024

kmem_cache_t *cache = kmem_cache_create("cache", sizeof(struct student), 0, SLAB_HWCACHE_ALIGN, NULL, NULL););

mempool_t *pool = mempool_create(MY_POOL_MINIMUM,mempool_alloc_slab, mempool_free_slab, cache);

一旦已创建了内存池, 可以分配和释放对象,使用:void *mempool_alloc(mempool_t *pool, int gfp_mask);

void mempool_free(void *element, mempool_t *pool);

当内存池创建了, 分配函数将被调用足够的次数来创建一个预先分配的对象池. 因此, 对 mempool_alloc 的调用试图从分配函数请求额外的对象;

如果那个分配失败, 一个预先分配的对象(如果有剩下的)被返回. 当一个对象被用 mempool_free 释放, 它保留在池中,

如果对齐预分配的对象数目小于最小量; 否则, 它将被返回给系统.

(3)get_free_page:如果一个模块需要分配大块的内存,最好使用面向页的分配技术。

__get_free_page(unsigned int flags); /*返回一个指向新页的指针, 未清零该页*/

get_zeroed_page(unsigned int flags); /*类似于__get_free_page,但用零填充该页*/

__get_free_pages(unsigned int flags, unsigned int order);

/*分配若干(物理连续的)页面并返回指向该内存区域的第一个字节的指针,该内存区域未清零*/

当程序不需要页面时,它可用下列函数之一来释放它们。

void free_page(unsigned long addr);

void free_pages(unsigned long addr, unsigned long order);

与kmalloc的区别:从用户的角度,可感觉到的区别主要是速度提高和更好的内存利用率(因为没有内部的内存碎片)。但主要优势实际不是速度,而是更有

效的内存利用。__get_free_page 函数的最大优势是获得的页完全属于调用者,且理论上可以适当的设置页表将其合并成一个线性的区域。

__get_free_pages(GFP_KERNEL, 2);返回的是虚拟地址

alloc_pages(GFP_KERNEL, 2); 返回的是页结构指针unsigned long __get_free_pages(int gfp_mask, unsigned long order)

{

struct page * page;

page = alloc_pages(gfp_mask, order);

if (!page)

return 0;

return (unsigned long) page_address(page);

}

其中:

#define page_address(page) ((page)->virtual)

而page->virtual表示该页面在内核空间的虚拟地址,所以__get_free_pages返回的虚拟地址。

alloc_page返回的是page结构的指针,而:

#define mk_pte(page, pgprot)__mk_pte((page) - mem_map, (pgprot))

#define __mk_pte(page_nr,pgprot) __pte(((page_nr) << PAGE_SHIFT) |

pgprot_val(pgprot))

可见,mk_pte是通过这个指针在其所属zone的page结构数组中的偏移量,设置了页表项。

(4)vmalloc是一个基本的Linux内存分配机制,它在虚拟内存空间分配一块连续的内存区,尽管这些页在物理内存中不连续

(使用一个单独的alloc_page 调用来获得每个页)

,但内核认为它们地址是连续的。应当注意的是:vmalloc在大部分情况下不推荐使用。因为在某些体系上留给vmalloc

的地址空间相对小,且效率不高。函数原型如下:

#include void *vmalloc(unsigned long size);

void vfree(void * addr);

vmalloc使用的地址范围完全是虚拟的,且每次分配都要通过适当地设置页表来建立(虚拟)内存区域。vmalloc可获得的地址在从

VMALLOC_START到 VAMLLOC_END

的范围中。vmalloc分配的地址只在处理器有MMU之上才有意义。当驱动需要真正的物理地址时,就不能使用vmalloc。调用vmalloc的正确

场合是分配一个大的、只存在于软件中的、用于缓存的内存区域时。注意:vamlloc比__get_free_pages要更多开销,因为它必须即获取内

存又建立页表。因此,调用vmalloc

来分配仅仅一页是不值得的。vmalloc的一个小的缺点在于它无法在原子上下文中使用。因为它内部使用kmalloc(GFP_KERNEL)

来获取页表的存储空间,因此可能休眠。vmalloc是面向页的 (它们会修改页表) ,重定位的或分配的空间都会被上调到最近的页边界。

(5) alloc_pages

alloc_pages 接口

为完整起见, 我们介绍另一个内存分配的接口, 尽管我们不会准备使用它直到 15 章. 现在, 能够说 struct page

是一个描述一个内存页的内部内核结构. 如同我们将见到的, 在内核中有许多地方有必要使用页结构; 它们是特别有用的, 在任何你可能处理高端内存的情况下,

高端内存在内核空间中没有一个常量地址.

Linux 页分配器的真正核心是一个称为 alloc_pages_node 的函数:struct page *alloc_pages_node(int nid, unsigned int flags,

unsigned int order);

这个函数页有 2 个变体(是简单的宏); 它们是你最可能用到的版本:struct page *alloc_pages(unsigned int flags, unsigned int order);

struct page *alloc_page(unsigned int flags);

核心函数, alloc_pages_node, 使用 3 个参数, nid 是要分配内存的 NUMA 节点 ID[], flags 是通常的 GFP_ 分配标志, 以及 order

是分配的大小. 返回值是一个指向描述分配的内存的第一个(可能许多)页结构的指针, 或者, 如常, NULL 在失败时.

alloc_pages 简化了情况, 通过在当前 NUMA 节点分配内存( 它使用 numa_node_id 的返回值作为 nid 参数调用

alloc_pages_node). 并且, 当然, alloc_pages 省略了 order 参数并且分配一个单个页.

为释放这种方式分配的页, 你应当使用下列一个:void __free_page(struct page *page);

void __free_pages(struct page *page, unsigned int order);

void free_hot_page(struct page *page);

void free_cold_page(struct page *page);

如果你对一个单个页的内容是否可能驻留在处理器缓存中有特殊的认识, 你应当使用 free_hot_page (对于缓存驻留的页) 或者

free_cold_page 通知内核. 这个信息帮助内存分配器在系统中优化它的内存使用.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值