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