linux驱动内存共享,分配内存 - Linux设备驱动程序学习笔记_Linux编程_Linux公社-Linux系统门户网站...

参数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 ,在 ,使用方法如下:

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 是一个描述一个内存页的内部内核结构,定义在

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

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_START 到 VAMLLOC_END 的范围中,定义在 中。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 内核特性,定义在 中。当创建一个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

/*分配不在页面边界上对齐的内存区*/

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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值