Linux怎么利用自带的内存池接口的编写和调用自己的系统调用---内存分配

 上传我的文档
 下载
 收藏
该文档贡献者很忙,什么也没留下。
 下载此文档
正在努力加载中...
linux系统内存碎片最小化研究
下载积分:2000
内容提示:linux系统内存碎片最小化研究,linux查看系统内存,linux系统内存,最小化系统,linux 碎片整理,内存碎片,安卓系统碎片化,win7系统碎片整理,内存碎片问题,linux操作系统
文档格式:PDF|
浏览次数:44|
上传日期: 02:36:22|
文档星级:
该用户还上传了这些文档
linux系统内存碎片最小化研究
官方公共微信您所在的位置: &
Linux嵌入式系统的内存管理方法详细介绍
Linux嵌入式系统的内存管理方法详细介绍
嵌入式系统中的快速性,可靠性和高效性是系统中对内存分配的重要要求。
1 嵌入式系统中对内存分配的要求
①快速性。嵌入式系统中对实时性的保证,要求内存分配过程要尽可能地快。因此在嵌入式系统中,不可能采用通用操作系统中复杂而完善的内存分配策略,一般都采用简单、快速的内存分配方案。当然,对实性要求的程序不同,分配方案也有所不同。例如,VxWorks采用简单的最先匹配如立即聚合方法;VRTX中采用多个固定尺寸的binning方案。
②可靠性。也就是内存分配的请求必须得到满足,如果分配失败可能会带来灾难性的后果。嵌入式系统应用的环境千变万化,其中有一些是对可靠性要求极高的。比如,汽车的自动驾驶系统中,系统检测到即将撞车,如果因为内存分配失败而不能相应的操作,就会发生车毁人亡的事故,这是不能容忍的。
③高效性。内存分配要尽可能地少浪费。不可能为了保证满足所有的内存分配请求而将内存配置得无限大。一方面,嵌入式系统对成本的要求使得内存在其中只是一种很有限的资源;另一方面,即使不考虑成本的因素,系统有限的空间和有限的板面积决定了可配置的内存容量是很限的。
2 静态分配与动态分配
究竟应用使用静态分配还是动态分配,一直是嵌入式系统设计中一个争论不休的总是。
当然,最合适的答案是对于不同的系统采用不同的方案。如果是系统对于实时性和可靠性的要求极高(硬实时系统),不能容忍一点延时或者一次分配失败,当然需要采用静态分配方案,也就是在程序编译时所需要的内存都已经分配好了。例如,火星探测器上面的嵌入式系统就必须采用静态分配的方案。
另外,WindRiver公司的一款专门用于汽车电子和工业自动化领域的实时操作系统OSEKWorks中就不支持内存的动态分配。在这样的应用场合,成本不支持内存的动态分配。在这样的应用场合,成本不是优先考虑的对象,实时性和可靠性才是必须保证的。当然,采用静态分配一个不可避免的总是就是系统失去了灵活性,必须在设计阶段就预先知道所需要的内存并对之作出分配;必须在设计阶段就预先考虑到所有可能的情况,因为一旦出现没有考虑到的情况,系统就无法处理。
这样的分配方案必须导致很大的浪费,因为内存分配必须按照最坏情况进行最大的配置,而实际上在运行中可能使用的只是其中的一小部分;而且在硬件平台不变的情况下,不可能灵活地为系统添加功能,从而使得系统的升级变得困难。 大多数的系统是硬实时系统和软实时系统的综合。也就是说,系统中的一部分任务有严格的时限要求,而另一部分只是要求完成得越快越好。
按照RMS(Rate Monotoin Scheduling)理论,这样的系统必须采用抢先式任务调度;而在这样的系统中,就可以采用动态内存分配来满足那一部分可靠性和实时性要求不那么高的任务。采用动态内存分配的好处就是给设计者很大的灵活性,可以方便地将原来运行于非嵌入式操作系统的程序移植到嵌入式系统中,比如,许多嵌入式系统中使用的网络协议栈。如果必须采用静态内存分配,移植这样的协议栈就会困难得多。另外,采用动态内存分配可以使设计者在不改变基本的硬件平台的情况下,比较灵活地调整系统的功能,在系统中各个功能之间作出权衡。例如,可以在支持的VLAN数和支持的路由条目数之间作出调整,或者不同的版本支持不同的协议。
说到底,动态内存分配给了嵌入式系统的程序设计者在比较少的限制和较大的自由。因此,大多数实时操作系统提供了动态内存分配接口,例如malloc和free函数。
3 RTOS提供的内存分配接口
不同的RTOS由于其不同的定位,采用不同的内存分配策略。
例如VRTX中,采用类似于GNU C中由Doug Lea开发的内存分配方案,即Binning算法,系统内存被分成了一些固定尺寸的内存块的算法,系统内存被分成了一些固定尺寸的内存块的集合。这种方法的优点是查找速度快而且不会产生内存碎片。但是,它的缺点也很明显,就是容易造成浪费,因为内存块的尺寸只有有限个,分配时只能取较大的内存块来满足一个较小的需求,累积起来,浪费就很大了;而且操作系统管理这样一个内存分配表也是一个很大的负担。 下面详细介绍一下我们常用的RTOS――美国风河公司(WindRiver)的VxWorks中采用的内存分配策略。
VxWorks的前身就是VRTX,据说VxWorks的名称来自make vrtx work。 VxWorks的内存管理函数存在于2个库中;memPartLib(紧凑的内存分区管理器)和memLib(完整的内存分区管理器)。前者(memPartLib)提供的工具用于从内存分区中分配内存块。该库包含两类程序,一类是通用工具创建和管理内存分区并从这些分区中分配和管理内存块;另一类是标准的malloc/free程序提供与内存分区的接口。系统内存分区(其ID为memSysPartId是一个全局变量)在内核初始化时由usrRoot调用memInit创建。其开始地址为RAM中紧接着VxWorks的BSS段之后,大小为所有空闲内存,如图1所示。
当创建其它分区时,一般需要先调用malloc从系统内存分区中分配一段内存才能创建。内存分区的结构定义为mem_part,包含1个对象标记,1个双向链表管理空闲块,1个信号量保护该分区及一些统计信息,如总尺寸、最大块尺寸、调试选项、已分配的块数、已分配的尺寸等。其语句如下:
typedef struct mem_part
{ OBJ_CORE objC /*对象标志*/
DL-LIST freeList; /*空闲链表*/
SEMAPHORE /*保护分区的信号量*/
Unsigned totalWords; /*分区中字(WORD)数*/
Unsigned minBlockW /*以字为单位的最小块尺寸*/
U /*选项,用于调试或统计*/ /*分配统计*/
unsigned curBlocksA /*当前分配的块数*/
unsigned curWorkdA /*当前分配的字数*/
unsigned cumBlockAllocated; /*累积分配的块数*/
unsigned cumWordsAllocated; /*累积分配的字数*/
}PARTITION;
一般系统中只有1个内存分区,即系统分区,所有任务所需要的内存直接调用malloc从其中分配。分配采用First-Fit算法(注意这种算法容易导致大量碎片),通过free释放的内存将被聚合以形成更大的空闲块。这就是VxWorks的内存分配机理。分配时可以要求一定的对齐格式。注意,不同的CPU架构有不同的对齐要求。
为了优化性能,malloc返回的指针是经过对齐的,为此的开销随构不同而不同。例如,68K为4字节对齐,开销8字节;SPARC为8字节对齐,开销12字节;MIPS为16字节对齐,开销12字节;I960为16字节对齐,开销16字节。
MemLib库中提供了增强的内存分区管理工具,并且增加了一些接口,而且可以设置调试选项。可以检测2类错误:①尝试分配太大的内存;②释放内存时发现坏块。有4种错误处理选项,当发生错误时记录消息或挂起任务。但是,使用动态内存分配malloc/free时要注意到以下几方面的限制。①因为系统内存分区是一种临界资源,由信号量保护,使用malloc会导致当前调用挂起,因此它不能用于中断服务程序;②因为进行内存分配需要执行查找算法,其执行时间与系统当前的内存使用情况相关,是不确定的,因此对于有规定时限的操作它是不适宜的;③由于采用简单的最先匹配算法,容易导致系统中存在大量的内存碎片,降低内存使用效率和系统性能。
针对这种情况,一般在系统设计时采用静态分配与动态分配相结合的方法。也就是对于重要的应用,在系统初始化时分配好所需要的内存。在系统运行过程中不再进行内存的分配/释放,这样就避免了因内存的分配释放带来的总是。而且在系统初始化,因为没有内存碎片,对于大的内存块的需求容易满足。对于其它的应用,在运行时进行动态内存分配。尤其是某些应用所要求的大量固定尺寸的小内存块,这时就可以采用一次分配多次使用的内存分配方案。下面详细介绍这种内存分配方案及其应用场合。
4 一次分配多次使用的内存分配方案
在嵌入式系统设计中,经常有一些类似于内存数据库的应用。
这些应用的特点是在内存中管理一些树,比如以太网交换机中的MAC地址表、VLAN表等,或者路由器中的路由表。这些树是由许多相同尺寸的节点组成的。这样,就可以每次分配一个大的缓冲池,比如包含多个内存单元的数组,每个内存单元用于1个节点。我们用一个空闲链表来管理该数组中的空闲内存单元。每次程序需要分配内存以创建1个新的节点时,就从空闲链表中取1个单元给调用者。程序删除节点并释放内存时,将释放的内存单元返还给空闲链表。
如果链表中的空闲内存单元取空了,就再次调用malloc从系统内存中分配一个大的内存块作为新的缓冲池。采用这样一种方案主要有如下优点:①减少了malloc/free的调用次数,从而降低了风险,减少了碎片;②因为从缓冲池中取一个内存单元是时间确定的(当然,如果缓冲池耗尽从而需要重新调用malloc分配除外),因此它可以用于严格时限的场合从而保证实时性;③它给用户以自由来添加一些用于内存分配和释放的调试函数以及一些统计功能,更好地监测系统中内存的使用情况。 这种方案必然涉及到一个缓冲池的结构。一般缓冲池的结构由以下几部分组成:单元尺寸、块尺寸(或者单元数目)、缓冲池指针、空闲链表、用于统计和调试的参数等。对缓冲池的操作包括创建缓冲池、释放缓冲池、从缓冲池中分配1个内存单元、释放内存单元回缓冲池等。下面举2个例子说明一下该方案的具体使用情况。
4.1 Intel交换机驱动程序中内存分配
在以Intel的交换芯片为基础的交换机方案中,因为采用的是软件地址学习的方式,需要在内存中维护许多数据,如MAC地址表的软拷贝、VLAN表、静态单播地址表、组播地址表等。这些表都是由一些树组成,每个树由一些固定尺寸的节点组成。一般每个节点几十个字节,每棵树的节点数是可增长的,少则几十,最多可到16K个节点。因此,很适合于采用该方案,具体的实现如下:
(1)缓冲池结构BlockMemMgr typedef struct{ MemSize data_cell_ /*数据单元的尺寸*/ MemSize block_ /*块尺寸*/ /*下面的变量为预定义的每个管理器最多包含的块数,如64 MAX_BLOCKS_OF_MEM_SIZE*/ Unsigned short blocks_being_/*已使用的块数*/ Void mem_ptr[PAX_BLOCKS_OF_MEM_SIZE]; /*块数组*/ SLList free_data_cells_ /*空闲链表*/ }BlockMemM 结构中的参数包括:单元尺寸、块尺寸、已用块数、所有块的地址、空闲链表(单向链表)。
(2)缓冲池的管理函数
◆block_mem_create:创建块内存管理器,参数包括内存指针(如为NULL,表示自己分配)、块尺寸、单元尺寸、返回管理器指针。 过程如下: ①检验参数合法性。 ②单元尺寸4字节对齐,计算每个块中的单元数。对内存指针进行4字节对齐或者分配内存指针。 ③初始化结构BlockMemMgr,包括单元尺寸和块尺寸。设置第1个内存块的指针。如果内存是外来的,设置块已用标志(已用为0),表示不能增加块;否则,已用块数设为1。 ④创建空闲链表,将块内所有单元添加到链表中,最后一个单元处于链表的最前面。 ⑤返回BlockMemMgr。
◆block_mem_destroy:解构一个块内存管理器,释放它所分配的所有内存,调用者负责外部内存的释放。参数为BlockMemMgr。返回成功失败标志。 ①参数合法性检测。 ②删除单向链表(设链表指针为NULL)。 ③如果块是动态分配的,释放它们。 ④释放结构BlockMemMgr。
◆block_malloc:从块内存管理器中分配1个单元。参数为BlockMemMgr,返回数据单元指针。 ①参数合法性检测。 ②判断空闲链表是否为空(是否为NULL)。如果为空,判断是否可以动态分配块,如果不能,返回失败;如果可以动态分配块,则分配1个块,执行与block_mem_create一样的操作。 ③从空闲链表中分配第1个单元,返回其指针。 注意这里有一个小技巧,即数据单元在空闲时其中存放空闲链表的节点信息,而分配后则存放数据内容。
◆block_free:释放1个数据单元,返回块内存管理器。小心不要对1个单元释放2次。参数为BlockMemMgr和单元指针。 ①参数合法性检测。 ②地址比较,判断数据单元属于哪个块。 ③判断数据单元的内容是否为空闲链表节点信息(也就是块内某单元的地址),从而确定是否为2次释放。 ④将该数据单元插入到空闲链表的前面。 ⑤引用该单元的指针设为NULL。
内存管理代码遵守如下约定: ①管理的内存是实际可写的内存;②分配内存是4字节或32位对齐;③block_malloc、block_free在中断级调用是部分安全的,除非BLOCK中已经没有空闲CELL,需要重新调用malloc分配新的BLOCK(而malloc和free就不是安全的,因为其中使用了信号量和搜索算法,容易引起中断服务程序阻塞)。当然,block_mem_create和block_mem_destroy必须在进程级调用。
&4.2 TMS中的内存分配
TMS是WindRiver公司为可管理式交换机推出的开发包。它用用IDB来管理各种协议的数据,比如STP和GVRP等。为了支持IDB,它建立了自己的缓冲池管理方案,程序在bufPoolLib.c中。该程序包含用于缓冲池管理的函数,这些函数允许从1个池中分配固定数目和大小的缓冲区。
通过预先分配一定数目固定大小的缓冲区,避免了反复的小的内存块分配/释放相关联的内存碎片和浪费。既然它从1个单一的块中分配缓冲池,也比对每一个缓冲区执行1次分配有更高的空间效率。模块对每个缓冲区加上1个标记(MAGIC),释放时会检查标记。模块给用户提供分配和释放操作定义回调函数的能力。
这样可以做到自动的对象创建和解构,同时允许由多个缓冲池分配的成员组成的对象做为1个单一的实体删除。这类似于C++中自动的对象构建和解构,不过是用C语言并且没有堆栈分配的负担。模块既允许从堆栈中分配缓冲池(通过calloc),也可以在用户分配的空间中创建它们。模块用1个单向链表来维护未分配的缓冲区,但不跟踪已分配的缓冲区。模块并不是任务安全的,用户需要用信号时来保护缓冲池。
(1)缓冲池结构
typedef struct
{ ulong_ /*用于一致性检测的特殊标记*/
Boolean localA /*内存是否在创建缓冲区时分配*/
SL_LIST freeL /*空闲链表*/
V /*缓冲区指向的内存指针*/
STATUS(*createFn)(void*,ulong_t argl); /*创建缓冲区时的回调函数指针*/
STATUS(*destroyFn)(void*,ulong_targl);/*释放缓冲区时的回调函数指针*/
Ulong_t argV/*回调函数的参数*/
} buf_pool_t;
结构中的参数包括检查标记MAGIC、是否本地分配、空闲链表、内存指针、创建缓冲池的回调函数指针、释放时的回调函数指针、回调函数参数。
(2)相关函数
◆BufPoolInitializeStorage:分配和初始化存储区。参数包括存储区地址(如为NULL,则本地分配)、缓冲区大小、缓冲区个数。 ①根据缓冲区大小和个数获得所需的内存大小。 ②如果指针为NULL,则调用calloc分配内存。设置本地分配标志。 ③初始化内存为0。 ④初始化指针。分配的内存块最前面为缓冲池结构buf_pool_t。实际的存储区紧随其后。Buf_pool_t包含参数检查标记、是否本地分配、存储区地址、分配时回调函数、释放时回调函数、回调函数变量。此时只设置存储区指针。
◆BufPoolCreate:创建缓冲池。参数为内存制止。缓冲区尺寸和个数,创建时回调函数、释放时回调函数、回调函数参数。 ①尺寸对齐。 ②调用bufPoolInitializeStorage初始化内存区和buf_pool_t结构。 ③用传入参数填充buf_pool_t结构。 ④将缓冲区添加到空闲链表中,最后的缓冲区在最前面。
◆BufPoolDestroy:删除缓冲池。参数为buf_pool_t指针。 ①检查缓冲池结构中的MAGIC字段是否被个性。 ②如果是本地分配的则翻放内存区。
◆BufPoolAlloc:从缓冲池中分配一个缓冲区,参数为缓冲池结构指针。如果存在空闲缓冲区,则从空闲链表中除并提供给调用者,执行创建时回调函数。如果回调函数返回错误,则将缓冲区返还给空闲链表。 ①检查缓冲池结构中的MAGIC标记是否完好。 ②从空闲链表中取出头一个节点。 ③如果节点不为空,清空节点,以其地址为参数调用回调函数。 ④如果回调函数返回错误,则将节点还给空闲链表。 ⑤返回得到空闲缓冲区地址。
◆BufPoolFree:将缓冲区返回给缓冲池。如果定义了回调函数,将在归还缓冲之间调用回调函数。参数为缓冲池结构和缓冲区指针。 ①缓冲池MAGIC标记是否完好。 ②如果定义回调函数、调用之。如果返回错误,则设置错误号。 ③将缓冲区添加到空闲链表中头部。 注意该函数有2点:①回调函数返回错误,照样归还缓冲区。②没有检查缓冲区是否二次释放,这一点与Intel的驱动程序不同。 另外,TMS的缓冲池没有BLOCK要领,不需要判断哪个CELL属于哪个BLOCK,简化 了操作。
许多嵌入式应用在RTOS提供的malloc/free的基础上编写自己的内存管理方案。
编写这样的内存管理方案,目的无非有两个:一是减少对malloc/free的依赖,从而避免由之带来的内存碎片、时间不确定等总是;另一个是增强程序的查错能力,送还内存使用错误。对于在嵌入式系统中广泛存在的数据库类型的内存需求,即分配多个固定尺寸的内存单元的要求,“一闪分配,多次使用”的方案无疑是一种很好的解决之道。文中介绍的2个例子很好地体现了它的优越性。
【责任编辑: TEL:(010)3】
关于&&&&的更多文章
Linux系统的魅力之一就是你可以直接从终端使用命令行来管理整个
Linux界极具活力,面向不同的用户可以使用不同的Linux发行版,比如适合新手和游戏爱好者等。
讲师: 42人学习过讲师: 38人学习过讲师: 12人学习过
DevOps是一组过程、方法与系统的统称,当企业希望将原
日,代号为“Trusty Tahr”(值得信赖的塔
日,微软公司正式发布Windows XP操作系统
本书作者结合自己多年实践经验,从Oracle开发应用中遇到的问题着手,全面系统地介绍Oracle的安装与卸载、数据字典、安全管理以及
51CTO旗下网站这篇文章看后感觉不错,和我在glibc下的hurdmalloc.c文件里关于malloc的实现基本意思相同,同时,这篇文章还介绍了一些内存管理方面的知识,值得推荐。原文链接地址为:/developerworks/cn/linux/l-memory/原文如下:为什么必须管理内存内存管理是计算机编程最为基本的领域之一。在很多脚本语言中,您不必担心内存是如何管理的,这并不能使得内存管理的重要性有一点点降低。对实际编程来说,理解您的内存管理器的能力与 局限性至关重要。在大部分系统语言中,比如 C 和 C++,您必须进行内存管理。本文将介绍手工的、 半手工的以及自动的内存管理实践的基本概念。追溯到在 Apple II 上进行汇编语言编程的时代,那时内存管理还不是个大问题。您实际上在运行整个系统。系统有 多少内存,您就有多少内存。您甚至不必费心思去弄明白它有多少内存,因为每一台机器的内存数量都相同。 所以,如果内存需要非常固定,那么您只需要选择一个内存范围并使用它即可。不过,即使是在这样一个简单的计算机中,您也会有问题,尤其是当您不知道程序的每个部分将需要多少 内存时。如果您的空间有限,而内存需求是变化的,那么您需要一些方法来满足这些需求:确定您是否有足够的内存来处理数据。从可用的内存中获取一部分内存。向可用内存池(pool)中返回部分内存,以使其可以由程序的其他部分或者其他程序使用。实现这些需求的程序库称为&分配程序(allocators),因为它们负责分配和回收内存。程序的动态性越强,内存 管理就越重要,您的内存分配程序的选择也就更重要。让我们来了解可用于内存管理的不同方法,它们的 好处与不足,以及它们最适用的情形。&回页首C 风格的内存分配程序C 编程语言提供了两个函数来满足我们的三个需求:malloc:该函数分配给定的字节数,并返回一个指向它们的指针。 如果没有足够的可用内存,那么它返回一个空指针。free:该函数获得指向由&malloc&分配的内存片段 的指针,并将其释放,以便以后的程序或操作系统使用(实际上,一些malloc&实现只能将内存归还给程序,而无法将内存归还给操作系统)。物理内存和虚拟内存要理解内存在程序中是如何分配的,首先需要理解如何将内存从操作系统分配给程序。计算机上的每一个进程都认为自己可以访问所有的物理内存。显然,由于同时在运行多个程序,所以每个进程不可能拥有全部内存。实际上,这些进程使用的是&虚拟内存。只是作为一个例子,让我们假定您的程序正在访问地址为 629 的内存。不过,虚拟内存系统不需要将其存储在位置为 629 的 RAM 中。实际上,它甚至可以不在 RAM 中 && 如果物理 RAM 已经满了,它甚至 可能已经被转移到硬盘上!由于这类地址不必反映内存所在的物理位置,所以它们被称为虚拟内存。操作系统维持着一个虚拟地址到物理地址的转换的表,以便计算机硬件可以正确地响应地址请求。并且,如果地址在硬盘上而不是在 RAM 中,那么操作系统将暂时停止您的进程,将其他内存转存到硬盘中,从硬盘上加载被请求的内存, 然后再重新启动您的进程。这样,每个进程都获得了自己可以使用的地址空间,可以访问比您物理上安装的内存更多的内存。在 32-位 x86 系统上,每一个进程可以访问 4 GB 内存。现在,大部分人的系统上并没有 4 GB 内存, 即使您将 swap 也算上,&每个进程所使用的内存也肯定少于 4 GB。因此,当加载一个进程时, 它会得到一个取决于某个称为&系统中断点(system break)的特定地址的初始内存分配。该地址之后是未被映射的内存 && 用于在 RAM 或者硬盘中没有分配相应物理位置的内存。因此,如果一个进程运行超出了 它初始分配的内存,那么它必须请求操作系统&映射进来(map in)&更多的内存。(映射是一个表示一一对应关系的数学术语 && 当内存的虚拟地址有一个对应的物理地址来存储内存内容时,该内存将被映射。)基于 UNIX 的系统有两个可映射到附加内存中的基本系统调用:brk:&brk()&是一个非常简单的系统调用。 还记得系统中断点吗?该位置是进程映射的内存边界。&brk()&只是简单地 将这个位置向前或者向后移动,就可以向进程添加内存或者从进程取走内存。mmap:&mmap(),或者说是&内存映像&,类似于&brk(),但是更为灵活。首先,它可以映射任何位置的内存, 而不单单只局限于进程。其次,它不仅可以将虚拟地址映射到物理的 RAM 或者 swap,它还可以将 它们映射到文件和文件位置,这样,读写内存将对文件中的数据进行读写。不过,在这里,我们只关心&mmap&向进程添加被映射的内存的能力。&munmap()&所做的事情与mmap()&相反。如您所见,&brk()&或者&mmap()&都可以用来向我们的 进程添加额外的虚拟内存。在我们的例子中将使用&brk(),因为它更简单,更通用。实现一个简单的分配程序如果您曾经编写过很多 C 程序,那么您可能曾多次使用过&malloc()&和&free()。不过,您可能没有用一些时间去思考它们在您的操作系统中 是如何实现的。本节将向您展示&malloc&和&free&的 一个最简化实现的代码,来帮助说明管理内存时都涉及到了哪些事情。要试着运行这些示例,需要先&复制本代码清单,并将其粘贴到一个名为 malloc.c 的文件中。接下来,我将一次一个部分地对该清单进行解释。在大部分操作系统中,内存分配由以下两个简单的函数来处理:void *malloc(long numbytes):该函数负责分配&numbytes&大小的内存,并返回指向第一个字节的指针。void free(void *firstbyte):如果给定一个由先前的&malloc&返回的指针,那么该函数会将分配的空间归还给进程的&空闲空间&。malloc_init&将是初始化内存分配程序的函数。它要完成以下三件事: 将分配程序标识为已经初始化,找到系统中最后一个有效内存地址,然后建立起指向我们管理 的内存的指针。这三个变量都是全局变量:清单 1. 我们的简单分配程序的全局变量
int has_initialized = 0;void *managed_memory_void *last_valid_
&如前所述,被映射的内存的边界(最后一个有效地址)常被称为系统中断点或者&当前中断点。 在很多 UNIX& 系统中,为了指出当前系统中断点,必须使用&sbrk(0)&函数。&sbrk&根据参数中给出的字节数移动当前系统中断点,然后返回新的系统中断点。 使用参数&0只是返回当前中断点。这里是我们的&malloc&初始化代码,它将找到当前中断点并初始化我们的变量:清单 2. 分配程序初始化函数
/* Include the sbrk function */#include &unistd.h&void malloc_init(){ /* grab the last valid address from the OS */ last_valid_address = sbrk(0); /* we don't have any memory to manage yet, so
*just set the beginning to be last_valid_address
*/ managed_memory_start = last_valid_ /* Okay, we're initialized and ready to go */
has_initialized = 1;}
&现在,为了完全地管理内存,我们需要能够追踪要分配和回收哪些内存。在对内存块进行了&free&调用之后,我们需要做的是诸如将它们标记为未被使用的等事情,并且,在调用&malloc&时, 我们要能够定位未被使用的内存块。因此,&malloc&返回的每块内存的起始处首先要有这个 结构:清单 3. 内存控制块结构定义
struct mem_control_block { int is_ };
&现在,您可能会认为当程序调用&malloc&时这会引发问题 && 它们如何知道这个结构? 答案是它们不必知道;在返回指针之前,我们会将其移动到这个结构之后,把它隐藏起来。这使得返回的指针 指向没有用于任何其他用途的内存。那样,从调用程序的角度来看,它们所得到的全部是空闲的、开放的内存。然后,当通过&free()&将该指针传递回来时,我们只需要倒退几个内存字节就可以再次找到这个结构。在讨论分配内存之前,我们将先讨论释放,因为它更简单。为了释放内存,我们必须要做的惟一一件事情就是, 获得我们给出的指针,回退&sizeof(struct mem_control_block)&个字节,并将其 标记为可用的。这里是对应的代码:清单 4. 解除分配函数
void free(void *firstbyte) { struct mem_control_block * /* Backup from the given pointer to find the
* mem_control_block
*/ mcb = firstbyte - sizeof(struct mem_control_block); /* Mark the block as being available */ mcb-&is_available = 1; /* That's It!
We're done. */ }
&如您所见,在这个分配程序中,内存的释放使用了一个非常简单的机制,在固定时间内完成内存释放。 分配内存稍微困难一些。以下是该算法的略述:清单 5. 主分配程序的伪代码
1. If our allocator has not been initialized, initialize it.2. Add sizeof(struct mem_control_block) to the size requested.3. start at managed_memory_start.4. Are we at last_valid address?5. If we are:
A. We didn't find any existing space that was large enough
-- ask the operating system for more and return that.6. Otherwise:
A. Is the current space available (check is_available from
the mem_control_block)?
B. If it is:
Is it large enough (check "size" from the
mem_control_block)?
a. Mark it as unavailable
b. Move past mem_control_block and return the
iii) Otherwise:
a. Move forward "size" bytes
b. Go back go step 4
C. Otherwise:
Move forward "size" bytes
Go back to step 4
&我们主要使用连接的指针遍历内存来寻找开放的内存块。这里是代码:清单 6. 主分配程序
void *malloc(long numbytes) { /* Holds where we are looking in memory */ void *current_ /* This is the same as current_location, but cast to a
* memory_control_block
*/ struct mem_control_block *current_location_ /* This is the memory location we will return.
* be set to 0 until we find something suitable
*/ void *memory_ /* Initialize if we haven't already done so */ if(! has_initialized)
malloc_init(); } /* The memory we search for has to include the memory
* control block, but the users of malloc don't need
* to know this, so we'll just add it in for them.
*/ numbytes = numbytes + sizeof(struct mem_control_block); /* Set memory_location to 0 until we find a suitable
* location
*/ memory_location = 0; /* Begin searching at the start of managed memory */ current_location = managed_memory_ /* Keep going until we have searched all allocated space */ while(current_location != last_valid_address) {
/* current_location and current_location_mcb point
* to the same address.
However, current_location_mcb
* is of the correct type, so we can use it as a struct.
* current_location is a void pointer so we can use it
* to calculate addresses.
current_location_mcb =
(struct mem_control_block *)current_
if(current_location_mcb-&is_available)
if(current_location_mcb-&size &= numbytes)
/* Woohoo!
We've found an open,
* appropriately-size location.
/* It is no longer available */
current_location_mcb-&is_available = 0;
/* We own it */
memory_location = current_
/* Leave the loop */
/* If we made it here, it's because the Current memory
* move to the next one
current_location = current_location +
current_location_mcb-& } /* If we still don't have a valid location, we'll
* have to ask the operating system for more memory
*/ if(! memory_location) {
/* Move the program break numbytes further */
sbrk(numbytes);
/* The new memory will be where the last valid
* address left off
memory_location = last_valid_
/* We'll move the last valid address forward
* numbytes
last_valid_address = last_valid_address +
/* We need to initialize the mem_control_block */
current_location_mcb = memory_
current_location_mcb-&is_available = 0;
current_location_mcb-&size = } /* Now, no matter what (well, except for error conditions),
* memory_location has the address of the memory, including
* the mem_control_block
*/ /* Move the pointer past the mem_control_block */ memory_location = memory_location + sizeof(struct mem_control_block); /* Return the pointer */ return memory_ }
&这就是我们的内存管理器。现在,我们只需要构建它,并在程序中使用它即可。运行下面的命令来构建&malloc&兼容的分配程序(实际上,我们忽略了&realloc()&等一些函数,不过,&malloc()&和&free()&才是最主要的函数):清单 7. 编译分配程序
gcc -shared -fpic malloc.c -o malloc.so
&该程序将生成一个名为&malloc.so&的文件,它是一个包含有我们的代码的共享库。在 UNIX 系统中,现在您可以用您的分配程序来取代系统的&malloc(), 做法如下:清单 8. 替换您的标准的 malloc
LD_PRELOAD=/path/to/malloc.soexport LD_PRELOAD
&LD_PRELOAD&环境变量使动态链接器在加载任何可执行程序之前,先加载给定的共享库 的符号。它还为特定库中的符号赋予优先权。因此,从现在起,该会话中的任何应用程序都将使用我们的&malloc(),而不是只有系统的应用程序能够使用。有一些应用程序不使用malloc(), 不过它们是例外。其他使用&realloc()&等其他内存管理函数的应用程序,或者 错误地假定&malloc()&内部行为的那些应用程序,很可能会崩溃。ash shell 似乎可以 使用我们的新&malloc()&很好地工作。如果您想确保&malloc()&正在被使用,那么您应该通过向函数的入口点添加&write()&调用来进行测试。我们的内存管理器在很多方面都还存在欠缺,但它可以有效地展示内存管理需要做什么事情。它的某些缺点包括:由于它对系统中断点(一个全局变量)进行操作,所以它不能与其他分配程序或者&mmap&一起使用。当分配内存时,在最坏的情形下,它将不得不遍历&全部进程内存;其中可能包括位于硬盘上的很多内存, 这意味着操作系统将不得不花时间去向硬盘移入数据和从硬盘中移出数据。没有很好的内存不足处理方案(&malloc&只假定内存分配是成功的)。它没有实现很多其他的内存函数,比如&realloc()。由于&sbrk()&可能会交回比我们请求的更多的内存,所以在堆(heap)的末端 会遗漏一些内存。虽然&is_available&标记只包含一位信息,但它要使用完整的 4-字节 的字。分配程序不是线程安全的。分配程序不能将空闲空间拼合为更大的内存块。分配程序的过于简单的匹配算法会导致产生很多潜在的内存碎片。我确信还有很多其他问题。这就是为什么它只是一个例子!其他 malloc 实现malloc()&的实现有很多,这些实现各有优点与缺点。在设计一个分配程序时, 要面临许多需要折衷的选择,其中包括:分配的速度。回收的速度。有线程的环境的行为。内存将要被用光时的行为。局部缓存。簿记(Bookkeeping)内存开销。虚拟内存环境中的行为。小的或者大的对象。实时保证。每一个实现都有其自身的优缺点集合。在我们的简单的分配程序中,分配非常慢,而回收非常快。 另外,由于它在使用虚拟内存系统方面较差,所以它最适于处理大的对象。还有其他许多分配程序可以使用。其中包括:Doug Lea Malloc:Doug Lea Malloc 实际上是完整的一组分配程序,其中包括 Doug Lea 的原始分配程序,GNU libc 分配程序和&ptmalloc。 Doug Lea 的分配程序有着与我们的版本非常类似的基本结构,但是它加入了索引,这使得搜索速度更快,并且可以将多个没有被使用的块组合为一个大的块。它还支持缓存,以便更快地再次使用最近释放的内存。&ptmalloc&是 Doug Lea Malloc 的一个扩展版本,支持多线程。在本文后面的&参考资料部分中,有一篇描述 Doug Lea 的 Malloc 实现的文章。BSD Malloc:BSD Malloc 是随 4.2 BSD 发行的实现,包含在 FreeBSD 之中,这个分配程序可以从预先确实大小的对象构成的池中分配对象。它有一些用于对象大小的 size 类,这些对象的大小为 2 的若干次幂减去某一常数。所以,如果您请求给定大小的一个对象,它就简单地分配一个与之匹配的 size 类。这样就提供了一个快速的实现,但是可能会浪费内存。在&参考资料部分中,有一篇描述该实现的文章。Hoard:编写&Hoard 的目标是使内存分配在多线程环境中进行得非常快。因此,它的构造以锁的使用为中心,从而使所有进程不必等待分配内存。它可以显著地加快那些进行很多分配和回收的多线程进程的速度。 在&参考资料部分中,有一篇描述该实现的文章。众多可用的分配程序中最有名的就是上述这些分配程序。如果您的程序有特别的分配需求,那么您可能更愿意编写一个定制的能匹配您的程序内存分配方式的分配程序。不过,如果不熟悉分配程序的设计,那么定制分配程序通常会带来比它们解决的问题更多的问题。要获得关于该主题的适当的介绍,请参阅 Donald Knuth 撰写的&The Art of Computer Programming Volume 1: Fundamental Algorithms&中的第 2.5 节&Dynamic Storage Allocation&(请参阅&参考资料中的链接)。它有点过时,因为它没有考虑虚拟内存环境,不过大部分算法都是基于前面给出的函数。在 C++ 中,通过重载&operator new(),您可以以每个类或者每个模板为单位实现自己的分配程序。在 Andrei Alexandrescu 撰写的Modern C++ Design&的第 4 章(&Small Object Allocation&)中,描述了一个小对象分配程序(请参阅&参考资料中的链接)。基于 malloc() 的内存管理的缺点不只是我们的内存管理器有缺点,基于&malloc()&的内存管理器仍然也有很多缺点,不管您使用的是哪个分配程序。对于那些需要保持长期存储的程序使用&malloc()&来管理内存可能会非常令人失望。如果您有大量的不固定的内存引用,经常难以知道它们何时被释放。生存期局限于当前函数的内存非常容易管理,但是对于生存期超出该范围的内存来说,管理内存则困难得多。而且,关于内存管理是由进行调用的 程序还是由被调用的函数来负责这一问题,很多 API 都不是很明确。因为管理内存的问题,很多程序倾向于使用它们自己的内存管理规则。C++ 的异常处理使得这项任务更成问题。有时好像致力于管理内存分配和清理的代码比实际完成计算任务的代码还要多!因此,我们将研究内存管理的其他选择。&回页首半自动内存管理策略引用计数引用计数是一种&半自动(semi-automated)的内存管理技术,这表示它需要一些编程支持,但是它不需要您确切知道某一对象何时不再被使用。引用计数机制为您完成内存管理任务。在引用计数中,所有共享的数据结构都有一个域来包含当前活动&引用&结构的次数。当向一个程序传递一个指向某个数据结构指针时,该程序会将引用计数增加 1。实质上,您是在告诉数据结构,它正在被存储在多少个位置上。然后,当您的进程完成对它的使用后,该程序就会将引用计数减少 1。结束这个动作之后,它还会检查计数是否已经减到零。如果是,那么它将释放内存。这样做的好处是,您不必追踪程序中某个给定的数据结构可能会遵循的每一条路径。每次对其局部的引用,都将导致计数的适当增加或减少。这样可以防止在使用数据结构时释放该结构。不过,当您使用某个采用引用计数的数据结构时,您必须记得运行引用计数函数。另外,内置函数和第三方的库不会知道或者可以使用您的引用计数机制。引用计数也难以处理发生循环引用的数据结构。要实现引用计数,您只需要两个函数 && 一个增加引用计数,一个减少引用计数并当计数减少到零时释放内存。一个示例引用计数函数集可能看起来如下所示:清单 9. 基本的引用计数函数
/* Structure Definitions*//* Base structure that holds a refcount */struct refcountedstruct{ }/* All refcounted structures must mirror struct * refcountedstruct for their first variables *//* Refcount maintenance functions *//* Increase reference count */void REF(void *data){ struct refcountedstruct * rstruct = (struct refcountedstruct *) rstruct-&refcount++;}/* Decrease reference count */void UNREF(void *data){ struct refcountedstruct * rstruct = (struct refcountedstruct *) rstruct-&refcount--; /* Free the structure if there are no more users */ if(rstruct-&refcount == 0) {
free(rstruct); }}
&REF&和&UNREF&可能会更复杂,这取决于您想要做的事情。例如,您可能想要为多线程程序增加锁,那么您可能想扩展refcountedstruct,使它同样包含一个指向某个在释放内存之前要调用的函数的指针(类似于面向对象语言中的析构函数 && 如果您的结构中包含这些指针,那么这是&必需的)。当使用&REF&和&UNREF&时,您需要遵守这些指针的分配规则:UNREF&分配前左端指针(left-hand-side pointer)指向的值。REF&分配后左端指针(left-hand-side pointer)指向的值。在传递使用引用计数的结构的函数中,函数需要遵循以下这些规则:在函数的起始处 REF 每一个指针。在函数的结束处 UNREF 第一个指针。以下是一个使用引用计数的生动的代码示例:清单 10. 使用引用计数的示例
/* EXAMPLES OF USAGE *//* Data type to be refcounted */struct mydata{
/* same as refcountedstruct */ int datafield1; /* Fields specific to this struct */ int datafield2; /* other declarations would go here as appropriate */};/* Use the functions in code */void dosomething(struct mydata *data){ REF(data); /* Process data */ /* when we are through */ UNREF(data);}struct mydata *globalvar1;/* Note that in this one, we don't decrease the * refcount since we are maintaining the reference * past the end of the function call through the * global variable */void storesomething(struct mydata *data){ REF(data); /* passed as a parameter */ globalvar1 = REF(data); /* ref because of Assignment */ UNREF(data); /* Function finished */}
&由于引用计数是如此简单,大部分程序员都自已去实现它,而不是使用库。不过,它们依赖于&malloc&和&free&等低层的分配程序来实际地分配和释放它们的内存。在 Perl 等高级语言中,进行内存管理时使用引用计数非常广泛。在这些语言中,引用计数由语言自动地处理,所以您根本不必担心它,除非要编写扩展模块。由于所有内容都必须进行引用计数,所以这会对速度产生一些影响,但它极大地提高了编程的安全性和方便性。以下是引用计数的益处:实现简单。易于使用。由于引用是数据结构的一部分,所以它有一个好的缓存位置。不过,它也有其不足之处:要求您永远不要忘记调用引用计数函数。无法释放作为循环数据结构的一部分的结构。减缓几乎每一个指针的分配。尽管所使用的对象采用了引用计数,但是当使用异常处理(比如&try&或&setjmp()/&longjmp()) 时,您必须采取其他方法。需要额外的内存来处理引用。引用计数占用了结构中的第一个位置,在大部分机器中最快可以访问到的就是这个位置。在多线程环境中更慢也更难以使用。C++ 可以通过使用&智能指针(smart pointers)来容忍程序员所犯的一些错误,智能指针可以为您处理引用计数等指针处理细节。不过,如果不得不使用任何先前的不能处理智能指针的代码(比如对 C 库的联接),实际上,使用它们的后果通实比不使用它们更为困难和复杂。因此,它通常只是有益于纯 C++ 项目。如果您想使用智能指针,那么您实在应该去阅读 Alexandrescu 撰写的&Modern C++ Design&一书中的&Smart Pointers&那一章。内存池内存池是另一种半自动内存管理方法。内存池帮助某些程序进行自动内存管理,这些程序会经历一些特定的阶段,而且每个阶段中都有分配给进程的特定阶段的内存。例如,很多网络服务器进程都会分配很多针对每个连接的内存 && 内存的最大生存期限为当前连接的存在期。Apache 使用了池式内存(pooled memory),将其连接拆分为各个阶段,每个阶段都有自己的内存池。在结束每个阶段时,会一次释放所有内存。在池式内存管理中,每次内存分配都会指定内存池,从中分配内存。每个内存池都有不同的生存期限。在 Apache 中,有一个持续时间为服务器存在期的内存池,还有一个持续时间为连接的存在期的内存池,以及一个持续时间为请求的存在期的池,另外还有其他一些内存池。因此,如果我的一系列函数不会生成比连接持续时间更长的数据,那么我就可以完全从连接池中分配内存,并知道在连接结束时,这些内存会被自动释放。另外,有一些实现允许注册&清除函数(cleanup functions),在清除内存池之前,恰好可以调用它,来完成在内存被清理前需要完成的其他所有任务(类似于面向对象中的析构函数)。要在自己的程序中使用池,您既可以使用 GNU libc 的 obstack 实现,也可以使用 Apache 的 Apache Portable Runtime。GNU obstack 的好处在于,基于 GNU 的 Linux 发行版本中默认会包括它们。Apache Portable Runtime 的好处在于它有很多其他工具,可以处理编写多平台服务器软件所有方面的事情。要深入了解 GNU obstack 和 Apache 的池式内存实现,请参阅&参考资料部分中指向这些实现的文档的链接。下面的假想代码列表展示了如何使用 obstack:清单 11. obstack 的示例代码
#include &obstack.h&#include &stdlib.h&/* Example code listing for using obstacks *//* Used for obstack macros (xmalloc is
a malloc function that exits if memory
is exhausted */#define obstack_chunk_alloc xmalloc#define obstack_chunk_free free/* Pools *//* Only permanent allocations should go in this pool */struct obstack *global_/* This pool is for per-connection data */struct obstack *connection_/* This pool is for per-request data */struct obstack *request_void allocation_failed(){ exit(1);}int main(){ /* Initialize Pools */ global_pool = (struct obstack *)
xmalloc (sizeof (struct obstack)); obstack_init(global_pool); connection_pool = (struct obstack *)
xmalloc (sizeof (struct obstack)); obstack_init(connection_pool); request_pool = (struct obstack *)
xmalloc (sizeof (struct obstack)); obstack_init(request_pool); /* Set the error handling function */ obstack_alloc_failed_handler = &allocation_ /* Server main loop */ while(1) {
wait_for_connection();
/* We are in a connection */
while(more_requests_available())
/* Handle request */
handle_request();
/* Free all of the memory allocated
* in the request pool
obstack_free(request_pool, NULL);
/* We're finished with the connection, time
* to free that pool
obstack_free(connection_pool, NULL); }}int handle_request(){ /* Be sure that all object allocations are allocated
* from the request pool
*/ int bytes_i_need = 400; void *data1 = obstack_alloc(request_pool, bytes_i_need); /* Do stuff to process the request */ /* return */ return 0;}
&基本上,在操作的每一个主要阶段结束之后,这个阶段的 obstack 会被释放。不过,要注意的是,如果一个过程需要分配持续时间比当前阶段更长的内存,那么它也可以使用更长期限的 obstack,比如连接或者全局内存。传递给&obstack_free()&的&NULL&指出它应该释放 obstack 的全部内容。可以用其他的值,但是它们通常不怎么实用。使用池式内存分配的益处如下所示:应用程序可以简单地管理内存。内存分配和回收更快,因为每次都是在一个池中完成的。分配可以在 O(1) 时间内完成,释放内存池所需时间也差不多(实际上是 O(n) 时间,不过在大部分情况下会除以一个大的因数,使其变成 O(1))。可以预先分配错误处理池(Error-handling pools),以便程序在常规内存被耗尽时仍可以恢复。有非常易于使用的标准实现。池式内存的缺点是:内存池只适用于操作可以分阶段的程序。内存池通常不能与第三方库很好地合作。如果程序的结构发生变化,则不得不修改内存池,这可能会导致内存管理系统的重新设计。您必须记住需要从哪个池进行分配。另外,如果在这里出错,就很难捕获该内存池。&回页首垃圾收集垃圾收集(Garbage collection)是全自动地检测并移除不再使用的数据对象。垃圾收集器通常会在当可用内存减少到少于一个具体的阈值时运行。通常,它们以程序所知的可用的一组&基本&数据 && 栈数据、全局变量、寄存器 && 作为出发点。然后它们尝试去追踪通过这些数据连接到每一块数据。收集器找到的都是有用的数据;它没有找到的就是垃圾,可以被销毁并重新使用这些无用的数据。为了有效地管理内存,很多类型的垃圾收集器都需要知道数据结构内部指针的规划,所以,为了正确运行垃圾收集器,它们必须是语言本身的一部分。收集器的类型复制(copying):&这些收集器将内存存储器分为两部分,只允许数据驻留在其中一部分上。它们定时地从&基本&的元素开始将数据从一部分复制到另一部分。内存新近被占用的部分现在成为活动的,另一部分上的所有内容都认为是垃圾。另外,当进行这项复制操作时,所有指针都必须被更新为指向每个内存条目的新位置。因此,为使用这种垃圾收集方法,垃圾收集器必须与编程语言集成在一起。标记并清理(Mark and sweep):每一块数据都被加上一个标签。不定期的,所有标签都被设置为 0,收集器从&基本&的元素开始遍历数据。当它遇到内存时,就将标签标记为 1。最后没有被标记为 1 的所有内容都认为是垃圾,以后分配内存时会重新使用它们。增量的(Incremental):增量垃圾收集器不需要遍历全部数据对象。因为在收集期间的突然等待,也因为与访问所有当前数据相关的缓存问题(所有内容都不得不被页入(page-in)),遍历所有内存会引发问题。增量收集器避免了这些问题。保守的(Conservative):保守的垃圾收集器在管理内存时不需要知道与数据结构相关的任何信息。它们只查看所有数据类型,并假定它们&可以全部都是指针。所以,如果一个字节序列可以是一个指向一块被分配的内存的指针,那么收集器就将其标记为正在被引用。有时没有被引用的内存会被收集,这样会引发问题,例如,如果一个整数域中包含一个值,该值是已分配内存的地址。不过,这种情况极少发生,而且它只会浪费少量内存。保守的收集器的优势是,它们可以与任何编程语言相集成。Hans Boehm 的保守垃圾收集器是可用的最流行的垃圾收集器之一,因为它是免费的,而且既是保守的又是增量的,可以使用&--enable-redirect-malloc&选项来构建它,并且可以将它用作系统分配程序的简易替代者(drop-in replacement)(用&malloc/&free代替它自己的 API)。实际上,如果这样做,您就可以使用与我们在示例分配程序中所使用的相同的&LD_PRELOAD&技巧,在系统上的几乎任何程序中启用垃圾收集。如果您怀疑某个程序正在泄漏内存,那么您可以使用这个垃圾收集器来控制进程。在早期,当 Mozilla 严重地泄漏内存时,很多人在其中使用了这项技术。这种垃圾收集器既可以在 Windows& 下运行,也可以在 UNIX 下运行。垃圾收集的一些优点:您永远不必担心内存的双重释放或者对象的生命周期。使用某些收集器,您可以使用与常规分配相同的 API。其缺点包括:使用大部分收集器时,您都无法干涉何时释放内存。在多数情况下,垃圾收集比其他形式的内存管理更慢。垃圾收集错误引发的缺陷难于调试。如果您忘记将不再使用的指针设置为 null,那么仍然会有内存泄漏。&回页首结束语一切都需要折衷:性能、易用、易于实现、支持线程的能力等,这里只列出了其中的一些。为了满足项目的要求,有很多内存管理模式可以供您使用。每种模式都有大量的实现,各有其优缺点。对很多项目来说,使用编程环境默认的技术就足够了,不过,当您的项目有特殊的需要时,了解可用的选择将会有帮助。下表对比了本文中涉及的内存管理策略。表 1. 内存分配策略的对比策略分配速度回收速度局部缓存易用性通用性实时可用SMP 线程友好定制分配程序取决于实现取决于实现取决于实现很难无取决于实现取决于实现简单分配程序内存使用少时较快很快差容易高否否GNU&malloc中快中容易高否中Hoard中中中容易高否是引用计数N/AN/A非常好中中是(取决于&malloc&实现)取决于实现池中非常快极好中中是(取决于&malloc&实现)取决于实现垃圾收集中(进行收集时慢)中差中中否几乎不增量垃圾收集中中中中中否几乎不增量保守垃圾收集中中中容易高否几乎不&参考资料您可以参阅本文在 developerWorks 全球站点上的&英文原文。&Web 上的文档GNU C Library 手册的 obstacks 部分&提供了 obstacks 编程接口。&Apache Portable Runtime 文档&描述了它们的池式分配程序的接口。&基本的分配程序Doug Lea 的 Malloc&是最流行的内存分配程序之一。&BSD Malloc&用于大部分基于 BSD 的系统中。&ptmalloc&起源于 Doug Lea 的 malloc,用于 GLIBC 之中。&Hoard&是一个为多线程应用程序优化的&malloc&实现。&GNU Memory-Mapped Malloc(GDB 的组成部分)&是一个基于&mmap()&的&malloc&实现。&池式分配程序GNU Obstacks(GNU Libc 的组成部分)是安装最多的池式分配程序,因为在每一个基于 glibc 的系统中都有它。&Apache 的池式分配程序(Apache Portable Runtime 中)&是应用最为广泛的池式分配程序。&Squid&有其自己的池式分配程序。&NetBSD&也有其自己的池式分配程序。&talloc&是一个池式分配程序,是 Samba 的组成部分。&智能指针和定制分配程序Loki C++ Library&有很多为 C++ 实现的通用模式,包括智能指针和一个定制的小对象分配程序。&垃圾收集器Hahns Boehm Conservative Garbage Collector&是最流行的开源垃圾收集器,它可以用于常规的 C/C++ 程序。&关于现代操作系统中的虚拟内存的文章Marshall Kirk McKusick 和 Michael J. Karels 合著的&A New Virtual Memory Implementation for Berkeley UNIX&讨论了 BSD 的 VM 系统。&Mel Gorman's Linux VM Documentation&讨论了 Linux VM 系统。&关于 malloc 的文章Poul-Henning Kamp 撰写的&Malloc in Modern Virtual Memory Environments&讨论的是&malloc&以及它如何与 BSD 虚拟内存交互。&Berger、McKinley、Blumofe 和 Wilson 合著的&Hoard -- a Scalable Memory Allocator for Multithreaded Environments&讨论了 Hoard 分配程序的实现。&Marshall Kirk McKusick 和 Michael J. Karels 合著的&Design of a General Purpose Memory Allocator for the 4.3BSD UNIX Kernel&讨论了内核级的分配程序。&Doug Lea 撰写的&A Memory Allocator&给出了一个关于设计和实现分配程序的概述,其中包括设计选择与折衷。&Emery D. Berger 撰写的&Memory Management for High-Performance Applications&讨论的是定制内存管理以及它如何影响高性能应用程序。&关于定制分配程序的文章Doug Lea 撰写的&Some Storage Management Techniques for Container Classes&描述的是为 C++ 类编写定制分配程序。&Berger、Zorn 和 McKinley 合著的&Composing High-Performance Memory Allocators&讨论了如何编写定制分配程序来加快具体工作的速度。&Berger、Zorn 和 McKinley 合著的&Reconsidering Custom Memory Allocation&再次提及了定制分配的主题,看是否真正值得为其费心。&关于垃圾收集的文章Paul R. Wilson 撰写的&Uniprocessor Garbage Collection Techniques&给出了垃圾收集的一个基本概述。&Benjamin Zorn 撰写的&The Measured Cost of Garbage Collection&给出了关于垃圾收集和性能的硬数据(hard data)。&Hans-Juergen Boehm 撰写的&Memory Allocation Myths and Half-Truths&给出了关于垃圾收集的神话(myths)。&Hans-Juergen Boehm 撰写的&Space Efficient Conservative Garbage Collection&是一篇描述他的用于 C/C++ 的垃圾收集器的文章。&Web 上的通用参考资料内存管理参考&中有很多关于内存管理参考资料和技术文章的链接。&关于内存管理和内存层级的 OOPS Group Papers&是非常好的一组关于此主题的技术文章。&C++ 中的内存管理讨论的是为 C++ 编写定制的分配程序。&Programming Alternatives: Memory Management&讨论了程序员进行内存管理时的一些选择。&垃圾收集 FAQ&讨论了关于垃圾收集您需要了解的所有内容。&Richard Jones 的 Garbage Collection Bibliography&有指向任何您想要的关于垃圾收集的文章的链接。&书籍Michael Daconta 撰写的&C++ Pointers and Dynamic Memory Management&介绍了关于内存管理的很多技术。&Frantisek Franek 撰写的&Memory as a Programming Concept in C and C++&讨论了有效使用内存的技术与工具,并给出了在计算机编程中应当引起注意的内存相关错误的角色。&Richard Jones 和 Rafael Lins 合著的&Garbage Collection: Algorithms for Automatic Dynamic Memory Management&描述了当前使用的最常见的垃圾收集算法。&在 Donald Knuth 撰写的&The Art of Computer Programming&第 1 卷&Fundamental Algorithms&的第 2.5 节&Dynamic Storage Allocation&中,描述了实现基本的分配程序的一些技术。&在 Donald Knuth 撰写的&The Art of Computer Programming&第 1 卷&Fundamental Algorithms&的第 2.3.5 节&Lists and Garbage Collection&中,讨论了用于列表的垃圾收集算法。&Andrei Alexandrescu 撰写的&Modern C++ Design&第 4 章&Small Object Allocation&描述了一个比 C++ 标准分配程序效率高得多的一个高速小对象分配程序。&Andrei Alexandrescu 撰写的&Modern C++ Design&第 7 章&Smart Pointers&描述了在 C++ 中智能指针的实现。&Jonathan 撰写的&Programming from the Ground Up&第 8 章&Intermediate Memory Topics&中有本文使用的简单分配程序的一个汇编语言版本。&来自 developerWorks自我管理数据缓冲区内存&(developerWorks,2004 年 1 月)略述了一个用于管理内存的自管理的抽象数据缓存器的伪 C (pseudo-C)实现。&A framework for the user defined malloc replacement feature&(developerWorks,2002 年 2 月)展示了如何利用 AIX 中的一个工具,使用自己设计的内存子系统取代原有的内存子系统。&掌握 Linux 调试技术&(developerWorks,2002 年 8 月)描述了可以使用调试方法的 4 种不同情形:段错误、内存溢出、内存泄漏和挂起。&在&处理 Java 程序中的内存漏洞&(developerWorks,2001 年 2 月)中,了解导致 Java 内存泄漏的原因,以及何时需要考虑它们。&在&developerWorks Linux 专区中,可以找到更多为 Linux 开发人员准备的参考资料。&从 developerWorks 的&Speed-start your Linux app&专区中,可以下载运行于 Linux 之上的 IBM 中间件产品的免费测试版本,其中包括 WebSphere& Studio Application Developer、WebSphere Application Server、DB2& Universal Database、Tivoli& Access Manager 和 Tivoli Directory Server,查找 how-to 文章和技术支持。&通过参与&developerWorks blogs&加入到 developerWorks 社区。&可以在 Developer Bookstore Linux 专栏中定购&打折出售的 Linux 书籍。

我要回帖

更多关于 cvi调用vb 编写dll 的文章

 

随机推荐