博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《Linux Device Driver》——分配内存
阅读量:4157 次
发布时间:2019-05-26

本文共 9538 字,大约阅读时间需要 31 分钟。

kmalloc函数

kmalloc不对所获得的内存空间清零,分配给它的区域保持原有的数据,所分配的区域在物理内存中也是连续的。


flags参数

#include
void *kmalloc(size_t size,int flags);

kmalloc的第一个参数是要分配的块的大小,第二个参数是分配的标志(flags),它能以多种方式控制kmalloc的行为。

最常用的标志是GFP_KERNEL,表示内存分配(最终总是调用get_free_pages来实现实际的分配,这就是GFP_前缀的由来)是代表运行在内核空间的进程执行的。这意味着调用它的函数正代表某给进程执行系统调用。

flags 描述
GFP_KERNEL 内核内存通常的分配方法,可能引起休眠
GFP_ATOMIC 用于在中断处理例程或其它运行于进程上下文之外的代码中分配内存,不会休眠
GFP_USER 用于为用户空间分配内存,可能会引起休眠
GFP_HIGHUSER 类似于GFP_USER,不过如果有高端内存的话就从那里分配
GFP_NOIO 在GFP_KERNEL的基础上,禁止任何I/O的初始化
GFG_NOFS 在GFP_KERNEL的基础上,不允许执行任何文件系统的调用

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

flags 描述
__GFP_DMA 请求分配发生在DMA的内存区段。与使用平台有关
__GFP_HIGHMEM 要分配内存位于高端内存。与使用平台有关
__GFP_COLD 通常,内存分配器会在试图返回“缓存热(cache warm)”页面,即在处理器缓存中找到的页面。相反,这个标志请求尚未使用的“冷”页面。对用于DMA读取的页面分配,可使用这个标志
__GFP_NOWARN 该标准很少使用。它可以避免内核在无法满足分配请求时产生警告(使用printk)
__GFP_HIGH 标记一个高优先级请求,允许未紧急状况而小号由内核保留的最后一些页面
__GFP_REPEAT 告诉分配器在满足分配请求而遇到困难时应采取何种行为:表示“努力再尝试一次”,它会尝试重新分配,但仍可能失败
__GFP_NOFAIL 告诉分配器在满足分配请求而遇到困难时应采取何种行为:始终不返回失败,它会努力满足分配请求(不鼓励使用)
__GFP_NORETRY 告诉分配器在满足分配请求而遇到困难时应采取何种行为:如果所请求的内存不可获得,就立即返回

内存区段

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

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

如果指定了__GFP_DMA标志,则只有DMA区段会被搜索:如果低地址段上没有可用内存,分配就会失败。

如果没有指定特定的标志,即常规区域和DMA区域都会被搜索。
如果设置了__GFP_HIGHMEM标志,则所有三个区段都会被搜索以获取一个空闲页。

**注意:**kmalloc不能分配高端内存


size参数

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

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

size
下限
上限

后备高速缓冲区

驱动程序常常需要反复分配许多相同大小内存块的情况,增加了一些特殊的内存池,称为后备高速缓存(lookaside cache)。 设备驱动程序通常不会涉及后备高速缓存,但是也有例外:在 Linux 2.6 中 USB 和 SCSI 驱动。

Linux 内核的高速缓存管理器有时称为“slab 分配器”,相关函数和类型在 <linux/slab.h> 中声明。slab 分配器实现的高速缓存具有 kmem_cache_t 类型。可以通过kmem_cache_create创建:

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:控制分配方式的位掩码:

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

constructor和destructor参数时可选的函数(但是不能只有destructor而没有constructor);前者用于初始化新分配的对象,后者用于“清除”对象——在内存空间被整个释放给系统之前。

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

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

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

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

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

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 个对象*/

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

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

**注意:**mempool会分配一些内存块,空闲且不会真正得到使用。


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及其辅助函数

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


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 
/*分配不在页面边界上对齐的内存区*/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);

转载地址:http://qezxi.baihongyu.com/

你可能感兴趣的文章
Unifrax宣布新建SiFAB™生产线
查看>>
艾默生纪念谷轮™在空调和制冷领域的百年创新成就
查看>>
NEXO代币持有者获得20,428,359.89美元股息
查看>>
JavaSE_day14 集合中的Map集合_键值映射关系
查看>>
异常 Java学习Day_15
查看>>
Mysql初始化的命令
查看>>
MySQL关键字的些许问题
查看>>
浅谈HTML
查看>>
css基础
查看>>
Servlet进阶和JSP基础
查看>>
servlet中的cookie和session
查看>>
过滤器及JSP九大隐式对象
查看>>
软件(项目)的分层
查看>>
【Python】学习笔记——-7.0、面向对象编程
查看>>
【Python】学习笔记——-7.2、访问限制
查看>>
【Python】学习笔记——-7.3、继承和多态
查看>>
【Python】学习笔记——-7.5、实例属性和类属性
查看>>
git中文安装教程
查看>>
虚拟机 CentOS7/RedHat7/OracleLinux7 配置静态IP地址 Ping 物理机和互联网
查看>>
Jackson Tree Model Example
查看>>