作者:Younger Liu,

本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可。

原文地址:http://lwn.net/Articles/396657/

1.  
 简介

连续内存分配器(CMA - Contiguous Memory Allocator)是一个框架,允许建立一个平台无关的配置,用于连续内存的管理。然后,设备所需内存都根据该配置进行分配。

这个框架的主要作用不是分配内存,而是解析和管理内存配置,以及作为在设备驱动程序和可插拔的分配器之间的中间组件。因此,它是与任何内存分配方法和分配策略没有依赖关系的。

2.  
 为什么需要?

在嵌入式设备中,很多设备都没有支持scatter-getter和IO map,都需要连续内存块的操作。如设备:摄像机,硬件视频解码器,编码器等。

这些设备往往需要较大的内存缓冲区(如:一个200万像素的高清帧摄像机,需要超过6M的内存),该kmalloc内存分配机制对于这么大的内存是没有效果的。

一些嵌入式设备对缓冲区有一些额外的要求,比如:在含有多个内存bank的设备中,要求只能在特定的bank中中分配内存;而还有一些要定内存边界对齐的缓存区。

近来,嵌入式设备有了较大的发展(特别是V4L领域),并且这些驱动都有自己的内存分配代码。它们众多的大多数都是采用bootmem分配方法。CMA框架企图采用统一的连续内存分配机制,并为这些设备驱动提供简单的API,而且是可以定制化和模块化的。

3.  
 设计

CMA主要设计目标是提供一个可定制的模块化框架,并且是可以配置的,以适应个别系统的需要。配置指定的内存区域,然后将这些内存分配给制定的设备。这些内存区域可以共享给多个设备驱动,也可以专门分配一个。这是通过以下方式实现的:

1. CMA的核心不是处理内存分配和空闲空间管理。专用分配器是用来处理内存分配和空闲内存管理的。因此,如果现有的解决方案不符合给定的系统,那么可以开发一种新的算法,这种算饭可以很容易地插入到CMA框架中。

所提出的解决方案中包括一个最适算法(best-fit)的一个实现。

2. CMA允许运行时配置即将分配的内存区域。内存区域都是经由命令行给出的,所以可以很容易地改变它,而不需要重新编译内核。

每个地区都有自己的大小,对齐标准,起始地址(物理地址)和对应该内存区域的内存分配算法。

这意味着同一时刻可以有多中机制在运行,如果在一个平台上同时运行多个不同的设备,这些设备具有不同的存储器使用特性,那么局可以匹配最好的算法。

3. 当设备请求内存时,设备必须“自我介绍”,即附带自己的信息以告知CMA。这样CMA可以知道谁分配内存。这允许系统架构师来指定哪个移动设备应该使用哪些存储区。

3a. 设备也可以指定一个“类”内存区域,这使得系统更容易配置,进而一个单一的设备可能使用来自不同内存区域的内存。例如,一个视频解码器驱动程序可能要分配一些共享的缓冲区,那么从第一个bank中分配一些,再从第二个bank中分配一些,可以获得尽可能高的内存吞吐量。

4.  
 使用场景

虚构一个使用了CMA的系统,来观察一下其是如何使用和配置的。

有一个携带硬件视频解码器和摄像机的平台,每个设备在最坏的情况下可能需要20M的内存。在该系统中,这两个设备是不会同时使用的,并且内存是可能共享的。使用下面的两个命令行:

cma=r=20M cma_map=video,camera=r

第一个CMA指令是分配20M的内存,并且内存分配器是有效的;第二个表示,名称为“video”和“camera”的两个驱动从之前定义的内存区域中分配内存。

因为两者共享同一内存区域,相比于每个设备保留20M的内存区域,使得系统节省了20M的内存空进。

但是随着系统的发展和进化,平台上可能同时运行视频解码器和摄像机,那么20M的内存区域就不能满足需要了。那么可以通过命令快速解决:

cma=v=20M,c=20M cma_map=video=v;camera=c

从该解决方案中也可以看出CMA是如何为每一个设备分配所需的私有内存池的。

分配机制也能通过一种相似的方式进行替换。在测试中发现,当给定的内存区域大小为40M时,系统运行一段时间后,碎片会成为一个问题。因此,为了满足所需要求的缓存区大小,需要预留一个较大的缓存区。

但是不幸的是,你需要w设置一个新的分配算法——Neat Allocation Algorithm(简写na),这两个设备对于内存有30M的需求:

cma=r=30M:na cma_map=video,camera=r

从上述示例可以看出,当CMA提供的算法不满足要求时,如何配置自己的分配算法,而不需要修改CMA或重编内核。

5.  
 技术细节

5.1           命令行参数

如上图所示,CMA有两个参数“cma”和“cma_map”;其中“cma”指定要为CMA保留的内存大小,参数“cma_map”用于指定该区域分配给为哪一个设备使用。

参数“cma”格式如下:

cma          ::=  "cma=" regions [ ';' ]

regions      ::= region [ ';' regions ]

region       ::= reg-name

'=' size

[ '@' start ]

[ '/' alignment ]

[ ':' [ alloc-name ] [ '(' alloc-params ')' ] ]

reg-name     ::= a sequence of letters and digits

//内存区的名称

size         ::= memsize   //内存区的大小

start        ::= memsize   //期望的内存区的起始地址

alignment    ::= memsize   //起始地址的对齐倍数

alloc-name   ::= a non-empty sequence of letters and digits

//
将要使用的分配器的名称

alloc-params ::= a sequence of chars other then ')' and ';'

//
分配器的参数

memsize      ::= whatever memparse() accepts

参数"cma_map" 的格式如下:

cma-map      ::=  "cma_map=" rules [ ';' ]

rules        ::= rule [ ';' rules ]

rule         ::= patterns '=' regions

patterns     ::= pattern [ ',' patterns ]

regions      ::= reg-name [ ',' regions ]

//
与device相匹配的内存区的名称

pattern      ::= dev-pattern [ '/' kind-pattern ]

| '/' kind-pattern

// pattern request must match for this rule to

// apply to it; the first rule that matches is

// applied; if dev-pattern part is omitted

// value identical to the one used in previous

// pattern is assumed

dev-pattern  ::= pattern-str

// pattern that device name must match for the

// rule to apply.

kind-pattern ::= pattern-str

// pattern that "kind" of memory (provided by

// device) must match for the rule to apply.

pattern-str  ::= a non-empty sequence of characters with '?'

meaning any character and possible '*' at

the end meaning to match the rest of the

string

示例 (whitespace added for better readability):

cma = r1 = 64M         // 64M区域

@512M       // 开始于512M (或尽可能接近)

/1M         // 确保内存区域1M对齐

:foo(bar);  // 使用带参bar的分配器foo

r2 = 64M         // 64M区域

/1M;        // 确保内存区域1M对齐

//
使用第一个有效分配器

r3 = 64M         // 64M区域

@512M       // 开始于512M (或尽可能接近)

:foo;       // 使用不带参的分配器foo

cma_map = foo = r1;    // kind==NULL的foo设备使用区域r1

foo/quaz = r2;  // OR:

/quaz = r2;  // kind == "quaz"的设备foo使用区域r2

foo/* = r3;     // OR:

/* = r3;        // 任何kinde的设备foo均可使用区域r3

bar/* = r1,r2;  // 任何kind的设备bar均可使用区域r1和r2

baz?/a* , baz?/b* = r3;

//
任何名如baz?且kind为a或b的设备都可以区域r3,其中?代表任意字符

5.2           设备与内存类别

设备名称来自device结构体。如果一个驱动没有注册为device,那么它是不能使用CMA的(通常,至少提供一个伪设备)。

无论设备何时申请内存,内存的类别都是一个可选的参数。在很多场景下,这个参数是可以忽略的,但是有时某些设备也可能需要。

比如:当前有两个内存bank,由于性能的原因,这两个内存bank设备都会使用,那么此时,设备驱动需要为这两个不同的buffer定义两个不同的名称。

cma=a=32M@0,b=32M@512M cma_map=foo/a=a;foo/b=b

但是无论何时,驱动分配内存都需要指定为某一个内存区域:

buffer1 = cma_alloc(dev, 1 << 20, 0, "a");

buffer2 = cma_alloc(dev, 1 << 20, 0, "b");

当然,如果需要(比如,当指定的内存已经使用完),需要允许驱动从其他的内存bank中分配。命令行参数可以改为:

cma=a=32M@0,b=32M@512M cma_map=foo/a=a,b;foo/b=b,a

换句话说,如果同一个设备在某一个系统上只使用一个内存bank,命令行参数:

cma=r=64M cma_map=foo/*=r

驱动无需做出任何更改。

5.3           API接口

CMA框架为设备提供了四个接口,分配接口cma_alloc():

unsigned long cma_alloc(const struct device *dev,

const char *kind,

unsigned long size,

unsigned long alignment);

如果需要,设备可能需要指定对齐规格(这个规格是内存chunk需要满足的),必须是2的幂次或0.而chunks一般至少一PAGE对齐的。(page大小一般为4k)。

参数kind指定内存区域名称,如果没有指定,则采用NULL。调用示例:

addr = cma_alloc(dev, NULL, size, 0);

该函数返回的是物理地址,一般需要判断返回值的有效性:

unsigned long addr = cma_alloc(dev, size);

if (IS_ERR_VALUE(addr))

return (int)addr;

/* Allocated */

(Make sure to include <linux/err.h> which contains the definition of the IS_ERR_VALUE() macro.)

释放函数cma_put():

int cma_put(unsigned long addr);

参数为内存块的物理地址,实现机制是递减引用计数,如果引用计数变为0,则释放该内存块。绝大部分时候,用户无需关注引用计数,只需要简单地调用cma_put()就可以了。

当该内存块共享给其他的驱动时,就需要调用cma_get()递增引用计数:

int cma_put(unsigned long addr);

最后一个函数是cma_info(),返回分配给指定(dev, kind)的内存缓存区的描述信息。原型如下:

int cma_info(struct cma_info *info,

const struct device *dev,

const char *kind);

通过这个函数可以获取内存区的边间,大小和对应于(dev, kind)内存区的个数。

5.4           分配操作

为CMA创建一个分配器需要实现四个函数。

前两个是用来初始化和卸载分配器:

int  cma_foo_init(struct cma_region *reg);

void cma_foo_done(struct cma_region *reg);

第一个函数在平台初始化时调用。结构体cma_region记录了缓存区的起始地址、大小,也会记录经由命令行传入的alloc_params域。

当调用函数cma_foo_done()时,均认为改缓存区的内存已经全部释放。

另外两个函数式块分配和释放函数:

struct cma_chunk *cma_foo_alloc(struct cma_region *reg,

unsigned long size,

unsigned long alignment);

void cma_foo_free(struct cma_chunk *chunk);

每一个函数,都是唯一线程访问的。因此,分配器不需要担心并发。

当分配器已经实现后,剩下的就是注册了,在文件"mm/cma-allocators.h"中定义如下语句:

CMA_ALLOCATOR("foo", foo)

第一个foo是命令行使用的分配器的名称,第二个是函数名字。

5.5           平台集成

在平台初始化过程,会调用函数cma_regions_allocate():

void cma_regions_allocate(int (*alloc)(struct cma_region *reg));

会遍历命令行提过所有的内存区并为他们保留内存。只有一个参数cma_region用于保留内存。传入NULL会调用cma_region_alloc()函数来通过bootmem来分配内存。

平台也会提过cma_defaults()提供默认的cma和cma_map参数:

int cma_defaults(const char *cma, const char *cma_map)

6.  
 展望

在将来,CMA机制能够实现:CMA空闲区域能够用于page cache,文件系统buffer或设备交换区。如此,内存将不再会被浪费。

因为CMA的内存是经由CMA框架分配和释放的,所以CMA框架能够知道哪些内存分配了,哪些内存释放了。因此,可以跟踪未使用的内存,使得CMA可以将未使用的内存用于其他目的,如page cache,IO缓存,交换缓存等等。

作者:Younger Liu,

本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可。

[内存管理]连续内存分配器(CMA)概述的更多相关文章

  1. 启动期间的内存管理之引导分配器bootmem--Linux内存管理(十)

    在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换到保护模式, 然后内核才能检 ...

  2. Linux内存管理之bootmem分配器

    为什么要使用bootmem分配器,内存管理不是有buddy系统和slab分配器吗?由于在系统初始化的时候需要执行一些内存管理,内存分配的任务,这个时候buddy系统,slab分配器等并没有被初始化好, ...

  3. SAP专家培训之Netweaver ABAP内存管理和内存调优最佳实践

    培训者:SAP成都研究院开发人员Jerry Wang 1. Understanding Memory Objects in ABAP Note1: DATA itab WITH HEADER LINE ...

  4. Linux内核笔记——内存管理之slab分配器

    内核版本:linux-2.6.11 内存区和内存对象 伙伴系统是linux用于满足对不同大小块内存分配和释放请求的解决方案,它为slab分配器提供页框分配请求的实现. 如果我们需要请求具有连续物理地址 ...

  5. Linux内存管理之slab分配器

    slab分配器是什么? 参考:http://blog.csdn.net/vanbreaker/article/details/7664296 slab分配器是Linux内存管理中非常重要和复杂的一部分 ...

  6. 内存管理之slab分配器

    基本思想 与传统的内存管理模式相比, slab 缓存分配器提供了很多优点.首先,内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配.slab 缓存分配器通过对类似大小的对象进行缓存而提 ...

  7. Linux内存管理 (5)slab分配器

    专题:Linux内存管理专题 关键词:slab/slub/slob.slab描述符.kmalloc.本地/共享对象缓冲池.slabs_partial/slabs_full/slabs_free.ava ...

  8. Linux内存管理 (25)内存sysfs节点解读

    1. General 1.1 /proc/meminfo /proc/meminfo是了解Linux系统内存使用状况主要接口,也是free等命令的数据来源. 下面是cat /proc/meminfo的 ...

  9. 启动期间的内存管理之bootmem_init初始化内存管理–Linux内存管理(十二)

    1. 启动过程中的内存初始化 首先我们来看看start_kernel是如何初始化系统的, start_kerne定义在init/main.c?v=4.7, line 479 其代码很复杂, 我们只截取 ...

随机推荐

  1. Octave Tutorial(《Machine Learning》)之第二课《数据移动》

    第二课 Moving Data 数据移动 常用内置函数 (1)加载文件 load 文件名.dat(或load('文件名.dat')) 接着输入文件名便可查看文件里的数据 (2)显示当前工作空间的所有变 ...

  2. springmvc框架原理

    1.  用户发送请求至前端控制器DispatcherServlet 2.  DispatcherServlet收到请求调用HandlerMapping处理器映射器. 3.  处理器映射器根据请求url ...

  3. Python中的变量

    多个变量赋值 Python允许你同时为多个变量赋值.例如: a = b = c = 1 以上实例,创建一个整型对象,值为1,三个变量被分配到相同的内存空间上. 您也可以为多个对象指定多个变量.例如: ...

  4. SLF4J 的几种实际应用模式--之三:JCL-Over-SLF4J+SLF4J

    我们前面已经讲过了 SLF4J 的两种用法:SLF4J+Log4J  和 SLF4J+Logback,那是在比较理想的情况下,所用组件只使用了 SLF4J 这一种统一日志框架的时候.可是 JCL 一直 ...

  5. VMware Ubuntu16.04虚拟机安装MATLAB R2016b

    因为这学期上的模式识别课程需要在Linux环境下使用Matlab,所以就在Windows10主机上的Vmware Workstation上的Ubuntu虚拟机上安装了最新版本的MATLAB. 环境: ...

  6. 在腾讯云上部署Hexo博客

    推荐理由 ----搭建个人的空间博客目前深受个人开发者的追捧,然而博客的种类和平台有很多,Hexo是一个开源的静态博客生成器.相比于其他博客而言它只要是web容器就能用.除了闷头专研技术之外,程序员还 ...

  7. 常见【十种】WEB攻击及防御技术总结

    最近参加了一个WEB安全培训,感觉WEB攻击跟防御都是挺有意思的.以下总结比较简短,仅供观赏哈. 一.XSS攻击 [介绍] xss攻击是跨站脚本攻击,例如在表单中提交含有可执行的javascript的 ...

  8. Oracle14~23

    14.查询所有学生的Sname.Cno和Degree列. 15.查询所有学生的Sno.Cname和Degree列. 16.查询所有学生的Sname.Cname和Degree列. 17. 查询“9503 ...

  9. 动态代理的两种实现方式(JDK/Cglib)

    =========================================== 原文链接: 动态代理的两种实现方式(JDK/Cglib) 转载请注明出处! ================== ...

  10. [SinGuLaRiTy] 平衡树

    [SinGuLaRiTy-1009] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. 二叉查找树 二叉查找树是指具有下列性质的非空二叉树: ⑴ ...