作者: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. java 学习资源

    1.tomcat版本http://tomcat.apache.org/whichversion.html 2.Servlet 2.5规范https://jcp.org/aboutJava/commun ...

  2. centos7 docker安装详解

    查看内核和操作系统版本[root@prod3 ~]# uname -r3.10.0-327.el7.x86_64[root@prod3 ~]# cat /etc/redhat-release Cent ...

  3. android-自定义广告轮播Banner(无限循环实现)

    关于广告轮播,大家肯定不会陌生,它在现手机市场各大APP出现的频率极高,它的优点在于"不占屏",可以仅用小小的固定空位来展示几个甚至几十个广告条,而且动态效果很好,具有很好的用户& ...

  4. 第37篇 Asp.Net源码解析(二)--详解HttpApplication

    这篇文章花了点时间,差点成烂到电脑里面,写的过程中有好几次修改,最终的这个版本也不是很满意,东西说的不够细,还需要认真的去看下源码才能有所体会,先这样吧,后面有时间把细节慢慢的再修改.顺便对于开发的学 ...

  5. ubuntu如何进入local、bin目录

    回到home目录,输入命令:cd /usr/local 若要进入bin目录,输入命令:cd /usr/local/bin

  6. CSS3实现DIV垂直居中+水平居中的四种方法

    <div class="div1"> <div class="div2"></div> </div> html结 ...

  7. WPF+AE开发小结--TOCControl右键菜单删除图层

    1.WPF项目中添加toccontrol控件,如何添加,网上有很多方法,可自行搜索,这里不再赘述,代码如下 <Window x:Class="AE.MainWindow" x ...

  8. vuejs2.0子组件改变父组件的数据

    在vue2.0之后的版本中,不允许子组件直接改变父组件的数据,在1.0的版本中可以这样操作的,但是往往项目需求需要改变父组件的数据,2.0也是可一个,区别是,当我们把父元素的数据给子组件时,需要传一个 ...

  9. JavaEE开发之Spring中的事件发送与监听以及使用@Profile进行环境切换

    本篇博客我们就来聊一下Spring框架中的观察者模式的应用,即事件的发送与监听机制.之前我们已经剖析过观察者模式的具体实现,以及使用Swift3.0自定义过通知机制.所以本篇博客对于事件发送与监听的底 ...

  10. python 之栈的实现

    #!/usr/bin/env python # --------------------------------------- # author : Geng Jie # email : gengji ...