[内存管理]连续内存分配器(CMA)概述
作者: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)概述的更多相关文章
- 启动期间的内存管理之引导分配器bootmem--Linux内存管理(十)
在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换到保护模式, 然后内核才能检 ...
- Linux内存管理之bootmem分配器
为什么要使用bootmem分配器,内存管理不是有buddy系统和slab分配器吗?由于在系统初始化的时候需要执行一些内存管理,内存分配的任务,这个时候buddy系统,slab分配器等并没有被初始化好, ...
- SAP专家培训之Netweaver ABAP内存管理和内存调优最佳实践
培训者:SAP成都研究院开发人员Jerry Wang 1. Understanding Memory Objects in ABAP Note1: DATA itab WITH HEADER LINE ...
- Linux内核笔记——内存管理之slab分配器
内核版本:linux-2.6.11 内存区和内存对象 伙伴系统是linux用于满足对不同大小块内存分配和释放请求的解决方案,它为slab分配器提供页框分配请求的实现. 如果我们需要请求具有连续物理地址 ...
- Linux内存管理之slab分配器
slab分配器是什么? 参考:http://blog.csdn.net/vanbreaker/article/details/7664296 slab分配器是Linux内存管理中非常重要和复杂的一部分 ...
- 内存管理之slab分配器
基本思想 与传统的内存管理模式相比, slab 缓存分配器提供了很多优点.首先,内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配.slab 缓存分配器通过对类似大小的对象进行缓存而提 ...
- Linux内存管理 (5)slab分配器
专题:Linux内存管理专题 关键词:slab/slub/slob.slab描述符.kmalloc.本地/共享对象缓冲池.slabs_partial/slabs_full/slabs_free.ava ...
- Linux内存管理 (25)内存sysfs节点解读
1. General 1.1 /proc/meminfo /proc/meminfo是了解Linux系统内存使用状况主要接口,也是free等命令的数据来源. 下面是cat /proc/meminfo的 ...
- 启动期间的内存管理之bootmem_init初始化内存管理–Linux内存管理(十二)
1. 启动过程中的内存初始化 首先我们来看看start_kernel是如何初始化系统的, start_kerne定义在init/main.c?v=4.7, line 479 其代码很复杂, 我们只截取 ...
随机推荐
- java 学习资源
1.tomcat版本http://tomcat.apache.org/whichversion.html 2.Servlet 2.5规范https://jcp.org/aboutJava/commun ...
- centos7 docker安装详解
查看内核和操作系统版本[root@prod3 ~]# uname -r3.10.0-327.el7.x86_64[root@prod3 ~]# cat /etc/redhat-release Cent ...
- android-自定义广告轮播Banner(无限循环实现)
关于广告轮播,大家肯定不会陌生,它在现手机市场各大APP出现的频率极高,它的优点在于"不占屏",可以仅用小小的固定空位来展示几个甚至几十个广告条,而且动态效果很好,具有很好的用户& ...
- 第37篇 Asp.Net源码解析(二)--详解HttpApplication
这篇文章花了点时间,差点成烂到电脑里面,写的过程中有好几次修改,最终的这个版本也不是很满意,东西说的不够细,还需要认真的去看下源码才能有所体会,先这样吧,后面有时间把细节慢慢的再修改.顺便对于开发的学 ...
- ubuntu如何进入local、bin目录
回到home目录,输入命令:cd /usr/local 若要进入bin目录,输入命令:cd /usr/local/bin
- CSS3实现DIV垂直居中+水平居中的四种方法
<div class="div1"> <div class="div2"></div> </div> html结 ...
- WPF+AE开发小结--TOCControl右键菜单删除图层
1.WPF项目中添加toccontrol控件,如何添加,网上有很多方法,可自行搜索,这里不再赘述,代码如下 <Window x:Class="AE.MainWindow" x ...
- vuejs2.0子组件改变父组件的数据
在vue2.0之后的版本中,不允许子组件直接改变父组件的数据,在1.0的版本中可以这样操作的,但是往往项目需求需要改变父组件的数据,2.0也是可一个,区别是,当我们把父元素的数据给子组件时,需要传一个 ...
- JavaEE开发之Spring中的事件发送与监听以及使用@Profile进行环境切换
本篇博客我们就来聊一下Spring框架中的观察者模式的应用,即事件的发送与监听机制.之前我们已经剖析过观察者模式的具体实现,以及使用Swift3.0自定义过通知机制.所以本篇博客对于事件发送与监听的底 ...
- python 之栈的实现
#!/usr/bin/env python # --------------------------------------- # author : Geng Jie # email : gengji ...