内存分配方法 kmalloc()、vmalloc()、__get_free_pages()

Copyright: 该文章版权由潘云登所有。可在非商业目的下任意传播和复制。

对于商业目的下对本文的任何行为需经作者同意。


kmalloc

#include <linux/slab.h>

void *kmalloc(size_t size, int flags);

void kfree(const void *ptr);

使用kmalloc可以获得以字节为单位的一块内核内存,它不对所获取的内存空间清零。这个函数返回一个指向内存块的指针,其内存块至少要有size大小。所分配的内存区在物理上是连续的。在出错时,它返回NULL。因此,在调用kmalloc()后,必须检查返回的是不是NULL。由kmalloc分配的内存,使用kfree方法进行释放。

kmalloc的第一个参数是要分配块的大小,第二个参数是分配器标志。这个标志在<linux/gfp.h>文件中声明,可以分为三类:行为修饰符、区修饰符以及类型标识。行为修饰符表示内核应当如何分配所需的内存,主要有:

__GFP_WAIT    分配器可以休眠

__GFP_HIGH    分配器可以访问紧急事件缓冲池

__GFP_IO       分配器可以启动磁盘I/O

__GFP_FS       分配器可以启动文件系统I/O

区修饰符表示从哪里分配内存:可用于DMA的内存(__GFP_DMA)、常规内存以及高端内存(__GFP_HIGHMEM)。类型标志组合了行为修饰符和区修饰符,将各种可能用到的组合归纳为不同类型,简化了修饰符的使用。因此,最常使用的flags参数为GFP_KERNEL和GFP_ATOMIC。后者用在中断处理程序或其它运行于进程上下文之外的代码中,这时kmalloc方法不会休眠。

#include <linux/gfp.h>

#define GFP_KERNEL         (__GFP_WAIT | __GFP_IO | __GFP_FS)

#define GFP_ATOMIC        (__GFP_HIGH)

实际上,kmalloc是建立在后面介绍的后备高速缓存之上的。因此,内核只能分配一些预定义的、固定大小的字节数组。根据当前体系结构使用的页面大小,kmalloc能处理的最小内存块是32或者64。如果希望代码具有完整的可移植性,则不应该分配大于128KB的内存。

示例:

char *buf;

buf = kmalloc(BUF_SIZE, GFP_ATOMIC);

if (!buf)

/* 内存分配出错! */

kfree(buf);


后备高速缓存

分配和释放数据结构是所有内核中最普遍的操作之一。为了便于数据的频繁分配和回收,内核提供了后备高速缓存机制,称为“slab分配器”。slab分配器实现的高速缓存具有kmem_cache_t类型,可通过调用kmem_cache_create创建。

#include <linux/slab.h>

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));

参数name存放高速缓存的名字。size是高速缓存中每个元素的大小。offset是高速缓存中第一个对象的偏移,用来确保在页内进行特定的对齐,一般取0。flags是一个位掩码,用来控制高速缓存的行为。constructor和destructor为高速缓存的构造和析构函数,分别在新页追加到高速缓存和从高速缓存中删去页时调用,通常取NULL。kmem_cache_create在成功时返回一个指向所创建高速缓存的指针,否则,返回NULL。在创建高速缓存后,使用kmem_cache_alloc从中分配内存对象。释放一个内存对象时使用kmem_cache_free。要销毁一个高速缓存,则调用kmem_cache_destroy。

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

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

int kmem_cache_destroy(kmem_cache_t *cache);

示例:

kmem_cache_t *task_struct_cachep;

/*创建高速缓存,SLAB_PANIC标志在分配失败时提醒slab层,

如果没有提供SLAB_PANIC 标志,必须自己检查返回值*/

task_struct_cachep = kmem_cache_create("task_struct",

sizeof(struct task_struct),

ARCH_MIN_TASKALIGN,

SLAB_PANIC,

NULL,

NULL);

/*分配内存对象*/

struct task_struct *tsk;

tsk = kmem_cache_alloc(task_struct_cachep, GFP_KERNEL);

if (!tsk)

return NULL;

/*释放内存对象*/

kmem_cache_free(task_struct_cachep, tsk);

/*撤销高速缓存*/

int err;

err = kmem_cache_destroy(task_struct_cachep);

if (err)

/*撤销高速缓存出错*/


分配页

如果模块需要分配大块的内存,使用面向页的分配技术会更好些。分配页面可以使用下面的函数:

#include <linux/gfp.h>

unsigned long get_zeroed_page(unsigned int gfp_mask);

unsigned long __get_free_page(unsigned int gfp_mask);

unsigned long __get_free_pages(unsigned int gfp_mask, unsigned int order);

它们返回内存区域第一个字节的指针。其中,get_zeroed_page将分配所得的页面清零。参数gfp_mask为分配器标志,与kmaloc中的一样。参数order是要分配的页面数的以2为底的对数。当用字节表示内存大小时,需要使用get_order函数进行转换,而不应该对页面大小进行任何假设,如:

#include <asm/page.h>

/*驱动程序需要16KB空间*/

/* get_order的参数必须是2的幂*/

int order = get_order(16*1024);

buf = get_free_pages(GFP_KERNEL, order);

当程序不再需要使用页面时,可以使用下列函数进行释放:

void free_page(unsigned long addr);

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

示例:

unsigned long page;

page = __get_free_pages(GFP_KERNEL, 3);

if (!page) {

/*没有足够的内存,你必须处理这种错误! */

return ENOMEM;

}

/* 'page'现在指向8个连续页中第1个页的地址*/

free_pages(page, 3);


vmalloc

#include <linux/vmalloc.h>

void * vmalloc(unsigned long size);

void vfree(void *addr);

vmalloc函数的工作方式类似于kmalloc,只不过前者分配的内存虚拟地址是连续的,而物理地址则无需连续。它通过分配非连续的物理内存块,再修改页表,把内存映射到逻辑地址空间的连续区域中。通过vmalloc获得的页必须一个一个地进行映射,效率不高,因此,只在不得已(一般是为了获得大块内存)时使用。vmalloc函数返回一个指针,指向逻辑上连续的一块内存区,其大小至少为size。在发生错误时,函数返回NULL。vmalloc可能睡眠,因此,不能从中断上下文中进行调用,也不能从其它不允许阻塞的情况下调用。要释放通过vmalloc所获得的内存,应使用vfree函数。

示例:

char *buf;

buf = vmalloc(16 * PAGE_SIZE); /*获得16页*/

if (!buf)

/*错误,不能分配内存*/

vfree(buf);

上一篇:electron 编译 sqlite3避坑指南---尾部链接有已经编译成功的sqlite3


下一篇:Leetcode 299 Bulls and Cows 字符串处理 统计