linux设备驱动程序学习(8) 分配内存

 kmalloc函数

#include<linux/slab.h>

void *kmalloc(size_t size,int flags);

1.不会对所申请的内存清零,保留原有数据

2.参数:size:分配大小

                flags:kmalloc行为

3.flags:GFP_KERNEL :内核内存通常的分配方法,可能引起休眠

                 GFP_ATOMIC :用于在中断处理例程或其它运行于进程上下文之外的代码中分配内存,不会休眠

                 GFP_USER:用于为用户空间分配内存,可能会引起休眠

                 GFP_HIGHUSER:类似于GFP_USER,不过如果有高端内存的话就从那里分配

                 GFP_NOIO:在GFP_KERNEL的基础上,禁止任何I/O的初始化

                 GFG_NOFS:在GFP_KERNEL的基础上,不允许执行任何文件系统的调用

                 另外有一些分配标志与上述“或”起来使用

                 __GFP_DMA:

                 __GFP_HIGHMEM:

                 __GFP_COLD:

                 __GFP_NOWARN:

                 __GFP_HIGH:

                 __GFP_REPEAT:

                 __GFP_NOFAIL:

                 __GFP_NORETRY:

 4.内存区段

linux通常把内存分成三个区段:

可用于DMA内存存在于特别的地址范围
常规内存 
高端内存32位平台为访问(相对)大量内存而存在的一种机制

5.size

linux处理内存分配:创建一系列的内存对象池,每个池中的内存块大小是固定一致的。处理分配请求时,就直接在包含有足够大的内存块的池中传递一个整块给请求者。

 内核只能分配一些预定义的,固定大小的字节数组。若申请任意数量的内存,则得到的可能会多一些,最多可以得到申请数量的两倍。

下限32或者64,取决于当前体系
上限128kb,使用的页面的大小
            

 

后备高速缓存

驱动程序常常需要反复分配许多相同大小内存块的情况,增加了一些特殊的内存池,称为后备高速缓存(lookaside cache)。 设备驱动程序通常不会涉及后备高速缓存,但是也有例外:在 Linux 2.6 中 USB 和 SCSI 驱动。Linux 内核的高速缓存管理器有时称为“slab 分配器”,相关函数和类型在 <linux/slab.h> 中声明。slab 分配器实现的高速缓存具有 kmem_cache_t 类型。

1.实现过程如下:

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));
/*创建一个可以容纳任意数目内存区域的、大小都相同的高速缓存对象,这些区域的大小都相同,由size参数决定。*/

参数*name: 一个指向 name 的指针,name和这个后备高速缓存相关联,功能是管理信息以便追踪问题;通常设置为被缓存的结构类型的名字,不能包含空格。

参数size:每个内存区域的大小。

参数offset:页内第一个对象的偏移量;用来确保被分配对象的特殊对齐,0 表示缺省值。

参数flags:控制分配方式的位掩码:

SLAB_NO_REAP        保护缓存在系统查找内存时不被削减,不推荐。 SLAB_HWCACHE_ALIGN  所有数据对象跟高速缓存行对齐,平台依赖,可能浪费内存。 SLAB_CACHE_DMA      每个数据对象在 DMA 内存区段分配.。

2.一旦某个对象的高速缓存被创建,就可以调用kmem_cache_alloc从中分配内存对象:

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

cache是之前创建的高速缓存,flags和传递给kmalloc的相同,并且当需要分配更多的内存来满足kmem_cache_alloc时,高速缓存还会利用这个参数

3.释放一个内存对象,使用kmem_cache_free:

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

4.如果驱动程序代码中和高速缓存有关的部分已经处理完了(典型情况:模块被卸载的时候),这时驱动程序应该释放它的高速缓存:

int kmem_cache_destroy(kmem_cache_t *cache);

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

 

内存池

 

为了确保在内存分配不允许失败情况下成功分配内存,内核提供了称为内存池( "mempool" )的抽象,它其实是某种后备高速缓存。它为了紧急情况下的使用,尽力一直保持空闲内存。所以使用时必须注意: mempool 会分配一些内存块,使其空闲而不真正使用,所以容易消耗大量内存 。而且不要使用 mempool 处理可能失败的分配。应避免在驱动代码中使用 mempool。

内存池的类型为 mempool_t ,在 <linux/mempool.h> ,使用方法如下:

1.创建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 */

你可编写特殊用途的函数来处理 mempool 的内存分配,但通常只需使用 slab 分配器为你处理这个任务:mempool_alloc_slab 和 mempool_free_slab的原型和上述内存池分配原型匹配,并使用 kmem_cache_alloc 和 kmem_cache_free 处理内存的分配和释放。

典型的设置内存池的代码如下:

cache = kmem_cache_create(. . .);
pool = mempool_create(MY_POOL_MINIMUM,mempool_alloc_slab, mempool_free_slab, cache);

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

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

 在创建mempool时,分配函数将被调用多次来创建预先分配的对象。因此,对 mempool_alloc 的调用是试图用分配函数请求额外的对象,如果失败,则返回预先分配的对象(如果存在)。用 mempool_free 释放对象时,若预分配的对象数目小于最小量,就将它保留在池中,否则将它返回给系统。

可用一下函数重定义mempool预分配对象的数量:

int mempool_resize(mempool_t *pool, int new_min_nr, int gfp_mask);
/*若成功,内存池至少有 new_min_nr 个对象*/

 

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

void mempool_destroy(mempool_t *pool);
/*在销毁 mempool 之前,必须返回所有分配的对象,否则会产生 oops*/

 

get_free_page与相关函数

1.如果模块需要分配大块的内存,使用面向页的分配技术会更好一些,就是整页的分配。

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

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

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

/*参数flags 与 kmalloc 的用法相同;
参数order 是请求或释放的页数以 2 为底的对数。若其值过大(没有这么大的连续区可用), 则分配失败*/

2.get_order 函数可以用来从一个整数参数 size(必须是 2 的幂) 中提取 order,函数也很简单:

/* Pure 2^n version of get_order */
static __inline__ __attribute_const__ int get_order(unsigned long size)
{
    int order;

    size = (size - 1) >> (PAGE_SHIFT - 1);
    order = -1;
    do {
        size >>= 1;
        order++;
    } while (size);
    return order;
}

3.通过/proc/buddyinfo 可以知道系统中每个内存区段上的每个 order 下可获得的数据块数目。

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

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

它们的关系是:

#define __get_free_page(gfp_mask) \
        __get_free_pages((gfp_mask),0)

若试图释放和你分配的数目不等的页面,会破坏内存映射关系,系统会出错。

 

alloc_pages 接口

1.struct page 是一个描述一个内存页的内部内核结构,定义在<linux/Mm_types.h>

2.

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

/*他们的关系是:*/
#define alloc_pages(gfp_mask, order) \
        alloc_pages_node(numa_node_id(), gfp_mask, order)
#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)

参数nid 是要分配内存的 NUMA 节点 ID,
参数flags 是 GFP_ 分配标志,
参数order 是分配内存的大小.
返回值是一个指向第一个(可能返回多个页)page结构的指针, 失败时返回NULL。

alloc_pages 通过在当前 NUMA 节点分配内存( 它使用 numa_node_id 的返回值作为 nid 参数调用 alloc_pages_node)简化了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(对于没有驻留在缓存中的页) 通知内核,帮助分配器优化内存使用*/

 

vmalloc 和 ioremap

vmalloc 是一个基本的 Linux 内存分配机制,它在虚拟内存空间分配一块连续的内存区,尽管这些页在物理内存中不连续 (使用一个单独的 alloc_page 调用来获得每个页),但内核认为它们地址是连续的。 应当注意的是:vmalloc 在大部分情况下不推荐使用。因为在某些体系上留给 vmalloc 的地址空间相对小,且效率不高。函数原型如下:

#include <linux/vmalloc.h>
void *vmalloc(unsigned long size);
void vfree(void * addr);
void *ioremap(unsigned long offset, unsigned long size);
void iounmap(void * addr);


kmalloc 和 _get_free_pages 返回的内存地址也是虚拟地址,其实际值仍需 MMU 处理才能转为物理地址。vmalloc和它们在使用硬件上没有不同,不同是在内核如何执行分配任务上:kmalloc 和 __get_free_pages 使用的(虚拟)地址范围和物理内存是一对一映射的, 可能会偏移一个常量 PAGE_OFFSET 值,无需修改页表。

而vmalloc 和 ioremap 使用的地址范围完全是虚拟的,且每次分配都要通过适当地设置页表来建立(虚拟)内存区域。 vmalloc 可获得的地址在从 VMALLOC_STARTVAMLLOC_END 的范围中,定义在 <asm/patable.h> 中。vmalloc 分配的地址只在处理器的 MMU 之上才有意义。当驱动需要真正的物理地址时,就不能使用 vmalloc。 调用 vmalloc 的正确场合是分配一个大的、只存在于软件中的、用于缓存的内存区域时。注意:vamlloc 比 __get_free_pages 要更多开销,因为它必须即获取内存又建立页表。因此, 调用 vmalloc 来分配仅仅一页是不值得的。vmalloc 的一个小的缺点在于它无法在原子上下文中使用。因为它内部使用 kmalloc(GFP_KERNEL) 来获取页表的存储空间,因此可能休眠。


ioremap 也要建立新页表,但它实际上不分配任何内存,其返回值是一个特殊的虚拟地址可用来访问特定的物理地址区域。
为了保持可移植性,不应当像访问内存指针一样直接访问由 ioremap 返回的地址,而应当始终使用 readb 和 其他 I/O 函数。

ioremap 和 vmalloc 是面向页的(它们会修改页表),重定位的或分配的空间都会被上调到最近的页边界。ioremap 通过将重映射的地址下调到页边界,并返回第一个重映射页内的偏移量来模拟一个非对齐的映射。


per-CPU变量

per-CPU 变量是一个有趣的 2.6 内核特性,定义在 <linux/percpu.h> 中。当创建一个per-CPU变量,系统中每个处理器都会获得该变量的副本。其优点是对per-CPU变量的访问(几乎)不需要加锁,因为每个处理器都使用自己的副本。per-CPU 变量也可存在于它们各自的处理器缓存中,这就在频繁更新时带来了更好性能

在编译时间创建一个per-CPU变量使用如下宏定义:

DEFINE_PER_CPU(type, name);
/*若变量( name)是一个数组,则必须包含类型的维数信息,例如一个有 3 个整数的per-CPU 数组创建如下: */
DEFINE_PER_CPU(int[3], my_percpu_array);

虽然操作per-CPU变量几乎不必使用锁定机制。 但是必须记住 2.6 内核是可抢占的,所以在修改一个per-CPU变量的临界区中可能被抢占。并且还要避免进程在对一个per-CPU变量访问时被移动到另一个处理器上运行。所以必须显式使用 get_cpu_var 宏来访问当前处理器的变量副本, 并在结束后调用 put_cpu_var。 对 get_cpu_var 的调用返回一个当前处理器变量版本的 lvalue ,并且禁止抢占。又因为返回的是lvalue,所以可被直接赋值或操作。例如:

get_cpu_var(sockets_in_use)++;
put_cpu_var(sockets_in_use);

当要访问另一个处理器的变量副本时, 使用:

per_cpu(variable, int cpu_id);

 

当代码涉及到多处理器的per-CPU变量,就必须实现一个加锁机制来保证访问安全。
 
动态分配per-CPU变量方法如下:

void *alloc_percpu(type);
void *__alloc_percpu(size_t size, size_t align);/*需要一个特定对齐的情况下调用*/
void free_percpu(void *per_cpu_var); /* 将per-CPU 变量返回给系统*/

/*访问动态分配的per-CPU变量通过 per_cpu_ptr 来完成,这个宏返回一个指向给定 cpu_id 版本的per_cpu_var变量的指针。若操作当前处理器版本的per-CPU变量,必须保证不能被切换出那个处理器:*/
per_cpu_ptr(void *per_cpu_var, int cpu_id);

/*通常使用 get_cpu 来阻止在使用per-CPU变量时被抢占,典型代码如下:*/

int cpu;
cpu = get_cpu()
ptr = per_cpu_ptr(per_cpu_var, cpu);
/* work with ptr */
put_cpu();

/*当使用编译时的per-CPU 变量, get_cpu_var 和 put_cpu_var 宏将处理这些细节。动态per-CPU变量需要更明确的保护*/


per-CPU变量可以导出给模块, 但必须使用一个特殊的宏版本:

EXPORT_PER_CPU_SYMBOL(per_cpu_var);
EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);


/*要在模块中访问这样一个变量,声明如下:*/
DECLARE_PER_CPU(type, name);

 
注意:在某些体系架构上,per-CPU变量的使用是受地址空间有限的。若在代码中创建per-CPU变量, 应当尽量保持变量较小.

 

 

获得大的缓冲区
大量连续内存缓冲的分配是容易失败的。到目前止执行大 I/O 操作的最好方法是通过离散/聚集操作 。

在引导时获得专用缓冲区
 
若真的需要大块连续的内存作缓冲区,最好的方法是在引导时来请求内存来分配。在引导时分配是获得大量连续内存页(避开 __get_free_pages 对缓冲大小和固定颗粒双重限制)的唯一方法。一个模块无法在引导时分配内存,只有直接连接到内核的驱动才可以。 而且这对普通用户不是一个灵活的选择,因为这个机制只对连接到内核映象中的代码才可用。要安装或替换使用这种分配方法的设备驱动,只能通过重新编译内核并且重启计算机。

当内核被引导, 它可以访问系统种所有可用物理内存,接着通过调用子系统的初始化函数, 允许初始化代码通过减少留给常规系统操作使用的 RAM 数量来分配私有内存缓冲给自己。
 
在引导时获得专用缓冲区要通过调用下面函数进行:

#include <linux/bootmem.h>
/*分配不在页面边界上对齐的内存区*/
void *alloc_bootmem(unsigned long size);
void *alloc_bootmem_low(unsigned long size); /*分配非高端内存。希望分配到用于DMA操作的内存可能需要,因为高端内存不总是支持DMA*/

/*分配整个页*/
void *alloc_bootmem_pages(unsigned long size);
void *alloc_bootmem_low_pages(unsigned long size);/*分配非高端内存*/

/*很少在启动时释放分配的内存,但肯定不能在之后取回它。注意:以这个方式释放的部分页不返回给系统*/
void free_bootmem(unsigned long addr, unsigned long size);

 

 

 

 

 

   

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值