LDD-Allocating Memory
kmalloc
kmalloc速度很快,分配的内存物理连续,但是分配的内存并未清零。kmalloc定义如下:
1 #include <linux/slab.h> 2 void *kmalloc(size_t size, int flags);
flags参数会在多个方面影响kmalloc的行为,定义在linux/gfp.h:
GFP_ATOMIC:用来在中断处理函数或者进程上下文中的其他代码使用,不会休眠
GFP_KERNEL:分配内核内存,可能休眠
GFP_USER:用来给用户空间分配内存,可能休眠
GFP_HIGHUSER:和GFP_USER类似,但是从高位内存分配
GFP_NOIO,GFP_NOFS:和GFP_KERNEL类似,限制内核满足请求所能采取的操作
Linux内核将内核分为三种:支持DMA的内存、普通内存、高内存。一般情况下,内存都从普通内存区域分配内存;高内存用来访问32位平台的大量内存。默认情况下,分配内存只会搜寻普通内存和支持DMA内存的区域。在NUMA系统上,内存分配器会首先在本处理器上分配内存。内存分配的方法实现在mm/page_alloc.c,内存区域的初始化实现在arch/mm/init.c。
kmalloc可以分配的最小的内存为32或64字节,取决于系统结构的页面的大小;kmalloc可以分配的最大内存取决于结构和内核的配置,若要开发可移植的驱动程序,最好不要超过128KB。
Lookaside Caches
驱动程序总是分配大量的大小相同内存,为了满足这种需求,内核实现了一个工具——lookaside cache。
内核中的高速缓存管理器被称为slab allocator(平板分配器),定义在linux/slab.h。高速缓存定义为kmem_cache_t,通过下列函数创建:
1 kmem_cache_t *kmem_cache_create(const char *name, size_t size, size_t offset, 2 unsigned long flags, void (*constructor)(void *, kmem_cache_t *, unsigned long flags), 3 void (*destructor)(void *, kmem_cache_t *, unsigned long flags));
创建的高速缓存对象能够保存任意数量的size大小的内存区域,name为cache的名称,不能含有空格。flags包含SLAB_NO_REAP,SLAB_HWCACHE_ALIGH(数据是cache line对齐)、SLAB_CACHE_DMA(分配在DMA内存)。这些标志都定义在linux/slab.h。constructor和destructor函数可以初始化和删除分配的对象。
要从创建的高速缓存中分配内存,以及释放:
1 void *kmem_cache_alloc(kmem_cache_t *cache, int flags); 2 void kmem_cache_free(kmem_cache_t *cache, const void *obj);
要销毁创建的高速缓存对象:
1 int kmem_cache_destroy(kmem_cache_t *cache);
销毁函数只有在高速缓存对象分配的所有内存都被释放后才会成功,因此驱动程序要判断kmem_cache_destroy函数的返回值。
使用lookaside cache的一个好处是内核会维护高速缓存用量的数据,可以通过/proc/slabinfo获得。
内核中有些地方的内存分配是不能失败的,为了保证,内核开发者设计了内存池(memory pool)——内存池是一个总是保有控线内存的lookaside cache。
内存池定义为mempool_t,定义在linux/mempool.h,创建函数
1 mempool_t *mempool_create(int min_nr, 2 mempool_alloc_t *alloc_fn, 3 mempool_free_t *free_fn, 4 void *pool_data);
min_nr声明内存池是所要保有的内存对象的最小值,alloc_fn和free_fn函数用来分配和释放内存对象
1 typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data); 2 typedef void (mempool_free_t)(void *element, void *pool_data);
pool_data是用来传递给分配函数和释放函数的参数。
相关的内存池函数还有
1 int mempool_resize(mempool_t *pool, int new_min_nr, int gfp_mask); 2 void mempool_destory(mempool_t *pool);
在销毁内存池前,必须先将其分配的对象返回,否则会导致内核oops。
书中原话:mempools allocate a chunk of memory that sits in a list, idle and unavailable for any real use。在驱动程序的代码中,尽量不要使用内存池。
get_free_page
要以页面为粒度分配内存,可以采用下列函数
1 get_zeroed_page(unsigned int flags); 2 __get_free_page(unsigned int flags); 3 __get_free_pages(unsigned int flags, unsigned int order); //页面的数量为2^order,最大值为10或11
/proc/buddyinfo包含每个内存区域中不同order可用的内存块的数量。
要释放页面
1 void free_page(unsigned long addr); 2 void free_pages(unsinged long addr, unsigned long order);
页面分配函数通过下列函数实现
1 struct page *alloc_pages_node(int nid, unsigned int flags, unsigned int order);
1 struct page *alloc_pages(unsigned int flags, unsigned int order); 2 struct page *alloc_page(unsigned int flags);
nid是NUMA的结点ID。要释放通过上述函数分配的页面,通过下列函数
1 void __free_page(struct page *page); 2 void __free_pages(struct page *page, unsigned int order); 3 void free_hot_page(struct page *page); //影响cache的行为 4 void free_cold_page(struct page *page);
vmalloc
vmalloc分配的内存虚拟地址连续,在很多情况下,尽量不要使用vmalloc函数,因为其效率较低,而且系统分配给vmlloc函数的内存较少。相关的函数定义如下:
1 #include <linux/vmalloc.h> 2 void *vmalloc(unsigned long size); 3 void vfree(void * addr); 4 void *ioremap(unsigned long offset, unsigned long size); 5 void iounmap(void * addr);
vmalloc函数返回的虚拟地址在asm/pgtable.h中定义的VMALLOC_START和VMALLOC_END范围内,大于kmalloc返回的地址。vmalloc函数不但检索内存,而且创建页表,比__get_free_pages开销更大。
ioremap和vmalloc都是页面对齐的,因此函数中的大小参数都会聚集到最近的页面边界。vmalloc不能在原子上下文中使用——函数内部调用可能休眠的kmalloc(GFP_KERNEL)。
Per-CPU Variables
每CPU变量在每个处理器上都有一个副本,因此在访问每CPU变量时不需要加锁。每CPU变量的访问可以通过各个处理器的高速缓存加速,对于需要频繁更新的变量有明显的性能提升。例如网络系统中对于各个类型数据包的统计,可以将变量声明为每CPU变量,若要各种数据包的统计结果,将每个处理器上的每CPU变量相加即可。
每CPU变量定义在linux/percpu.h,创建方法如下
1 DEFINE_PER_CPU(type, name); 2 DEFINE_PER_CPU(int[3], my_percpu_array);
虽然一般情况下每CPU可以在不加锁的情况下访问,但是如果在修改每CPU变量的临界区处理器被抢占,或者在访问每CPU变量时进程被迁移到另外一个处理器上,每CPU变量的访问就不再安全。为此,可以通过get_cpu_var和put_cpu_var访问每CPU变量,示例代码如下:
1 get_cpu_var(sockets_in_use)++; 2 put_cpu_var(sockets_in_use);
要访问另一个处理器的每CPU变量
1 per_cpu(variable, int cpu_id);
1 void *alloc_percpu(type); 2 void *__alloc_percpu(size_t size, size_t align);
访问动态创建的每CPU变量
1 per_cpu_ptr(void *per_cpu_var, int cpu_id);
1 int cpu; 2 cpu = get_cpu(); //防止cpu被抢占 3 ptr = per_cpu_ptr(per_cpu_var, cpu); 4 /* 操作每CPU变量*/ 5 put_cpu();
驱动程序中的每CPU变量需要通过下列宏声明和导出
1 DECLARE_PER_CPU(type, name); 2 EXPORT_PER_CPU_SYMBOL(per_cpu_var); 3 EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);
Obtaining Large Buffers
若要分配大量连续的物理内存,通常情况下,最好的方法在系统启动时分配内存,从而绕过__get_free_pages的限制。对于普通用户,这种方法并不使用——这种机制只有链接到内核映像中的代码才能使用,使用这种方法的驱动需要重新编译内核并且重启计算机才能安装。
boot-time memory通过下列函数分配
1 #include <linux/bootmem.h> 2 void *alloc_bootmem(unsigned long size); 3 void *alloc_bootmem_low(unsigned long size); 4 void *alloc_bootmem_pages(unsigned long size); 5 void *alloc_bootmem_low_pages(unsigned long size);
默认情况下函数从高内存区域中分配内存,除非调用带有_low的函数。boot-time内存一般不会释放;释放之后无法再收回。释放的函数如下
1 void free_bootmem(unsigned long addr, unsigned long size);
若要使用boot-time内存,必须将驱动程序直接链接到内核中,参见Documentation/kbuild。
作者:glob
出处:http://www.cnblogs.com/adera/
欢迎访问我的个人博客:https://blog.globs.site/
本文版权归作者和博客园共有,转载请注明出处。