① 电脑对于多通道内存是怎么分配的
加8g的话,其中4g用了组成双通道,剩余4g是单通道,称为非对称双通道。个人建议买4g的,毕竟老电脑了,加上之后变为8g让它苦苦支持吧。
② 二级分配器的内存池有几个空闲链表
模块主要由以下部分组成。
ObjectPool 对象池
MemPool 内存池
FastMemAlloc : 高速的大内存分配
FastSmallAlloc : 快速的小内存分配。
FixMemAlloc : 小浪费空间的大内存分配。
FixSmallMemAlloc : 小浪费空间的小内存分配。基本不浪费空间。
MemState。具有统计内存状态。(mpMemState)和检测内存越界mpBound。
除了FastMemAlloc外,所有的分配器内存都由MemState记录。 FastMemAlloc的内存状态由他自己记录,你可以调用FastMemAlloc::diagnostic()和FastMemAlloc::mpBound()来分析内存。
③ vc中如何用bitblt函数将内存位图分块
关于Bitblt函数,从内存中拷贝到内存中,下面的代码可以给你参考:
// 取得窗口客户区域大小
CRect WndRect ;
this->GetWindowRect ( &WndRect ) ;
this->ScreenToClient ( &WndRect ) ;
CClientDC cdc(this) ;
CDC mdc, TempDc ; // 内存DC
BITMAP BmpInfo ;
CBitmap ClientBmp, *pOldBmp ;
// 创建与设备DC兼容的内存DC
mdc.CreateCompatibleDC ( &cdc ) ;
TempDc.CreateCompatibleDC ( &cdc ) ;
// 创建与设备DC兼容的位图对象
ClientBmp.CreateCompatibleBitmap ( &cdc, WndRect.right, WndRect.bottom ) ;
mdc.SelectObject ( &ClientBmp ) ;
// 依次把位图贴到内存DC
for ( int i = 0; i <= 8; i++ )
{
TempDc.SelectObject ( &this->MyBmp[i].bmp ) ;
this->MyBmp[i].bmp.GetBitmap ( &BmpInfo ) ;
if ( i == 0 )
mdc.BitBlt (0, 0, WndRect.Width(), WndRect.Height(), &TempDc, 0, 0, SRCCOPY ) ;
else
mdc.TransparentBlt ( this->MyBmp[i].rect.left, \
this->MyBmp[i].rect.top, BmpInfo.bmWidth, BmpInfo.bmHeight, \
&TempDc, 0, 0, BmpInfo.bmWidth, BmpInfo.bmHeight, RGB(255,255,255) ) ;
}
// 把内存DC贴到设备DC上
cdc.BitBlt ( 0, 0, WndRect.right, WndRect.bottom, &mdc, 0, 0, SRCCOPY ) ;
// 环境清理
ClientBmp.DeleteObject () ;
mdc.DeleteDC () ;
④ 求解 内存分配(Memory Allocate) 问题
1)malloc.h 是的,VC 6.0 用 #include <stdlib.h> 就可以了。
2)写法都对。用 malloc 或 calloc 与个人习惯 有关(各人喜欢,c语言历史有这2函数)。realloc 用于 随时可以 增加 动态分配 或 减小 动态分配 空间。另2个函数无此功能。
3) 加条件判断做释放: if ( p) free(p);
⑤ 可变分区管理内存分配算法有那些,各有什么有缺点
连续分配: 首次适应算法(较快,简单,碎片多),最大适应分配算法(以期不留下小碎片), 最佳适应分配算法(慢,复杂,碎片少)。 都需要碎片整理。
离散分配:分段管理(逻辑性好),分页管理,段页式管理(最好,当然也复杂)。
⑥ 简述内存管理中buddy算法和slab机制的区别
1、Buddy算法
linux对空闲内存空间管理采取buddy算法,
Buddy算法:
把内存中所有页面按照2^n划分,其中n=0~5,每个内存空间按1个页面、2个页面、4个页面、8个页面、16个页面、32个页面进行六次划分。划分后形成了大小不等的存储块,称为页面块,简称页块,包含一个页面的页块称为1页块,包含2个页面的称为2页块,依次类推。
每种页块按前后顺序两两结合成一对Buddy“伙伴”。系统按照Buddy关系把具有相同大小的空闲页面块组成页块组,即1页块组、2页块组……32页块组。 每个页块组用一个双向循环链表进行管理,共有6个链表,分别为1、2、4、8、16、32页块链表。分别挂到free_area[] 数组上。
位图数组
用于标记内存页面使用情况,第0组每一位表示单个页面使用情况,1表示使用,0表示空闲,第二组每一位表示比邻的两个页面使用情况,一次类推。默认为10个数组,当一对Buddy的两个页面中有一个事空闲的,而另一个全部或部分被占用时,该位置1.两个页面块都是空闲,对应位置0.
内存分配和释放过程
内存分配时,系统按照Buddy算法,根据请求的页面数在free_area[]对应的空闲页块组中搜索。 若请求页面数不是2的整数次幂,则按照稍大于请求数的2的整数次幂的值搜索相应的页面块组。
当相应页块组中没有可使用的空闲页面块时就查询更大一些的页块组,在找到可用的页块后分配所需要的页面。当某一空闲页面被分配后,若仍有剩余的空闲页面,则根据剩余页面的大小把他们加入到相应页面组中。
内存页面释放时,系统将其作为空闲页面看待,检查是否存在与这些页面相邻的其他空闲页块,若存在,则合为一个连续的空闲区按Buddy算法重新分组。
2、Slab算法
采用buddy算法,解决了外碎片问题,这种方法适合大块内存请求,不适合小内存区请求。如:几十个或者几百个字节。Linux2.0采用传统内存分区算法,按几何分布提供内存区大小,内存区以2的幂次方为单位。虽然减少了内碎片,但没有显着提高系统效率。
Linux2.4采用了slab分配器算法,该算法比传统的分配器算法有更好性能和内存利用率,最早在solaris2.4上使用。
Slab分配器思想
1)小对象的申请和释放通过slab分配器来管理。
2)slab分配器有一组高速缓存,每个高速缓存保存同一种对象类型,如i节点缓存、PCB缓存等。
3)内核从它们各自的缓存种分配和释放对象。
4)每种对象的缓存区由一连串slab构成,每个slab由一个或者多个连续的物理页面组成。这些页面种包含了已分配的缓存对象,也包含了空闲对象。
⑦ 二维数组[5][10],请指出下面初始化时内存该分配多少 int **p int *p[5] int (*p)[10]
int **p 定义一个指向指针的指针,是一个指针类型。占内存4字节
int *p[5] 定义了一个整形数组,里面存放了5个指针类型的元素,占5×4=20字节
int (*p)[10] 定义了一个指向10个整形数组的指针,也是一个指针,占4字节
当然了,你也可以做个程序测试下:
#include <iostream>
using namespace std;
void main()
{
int **p; //在实际应用中这3个定义都要初始化
int *a[5];
int (*b)[10];
cout<<sizeof(p)<<endl;
cout<<sizeof(a)<<endl;
cout<<sizeof(b)<<endl;
}
⑧ 【我的笔记】内存管理(二)分区方法(静态、动态、伙伴、Slab)
由操作系统或系统管理员预先将内存划分成若干个分区。在系统运行过程中,分区的边界不再改变。分配时,找一个空闲且足够大的分区。如没有合适的分区:①让申请者等待。②先换出某分区的内容,再将其分配出去。
为申请者分配指定的分区或任选一个分区。如果没有空闲分区,可将一个分区的内容换出。可能需要重定位。
会出现内部碎片,无法满足大内存的需求。
可减少内部碎片。减少对大内存需求的限制。
①固定分配:只分配某种尺寸的特定分区,如分区已被使用,申请者必须等待。
可能出现不公平等待:虽有更大尺寸的空闲分区,却必须等待。
②最佳适应分配:分配能满足需要的最小尺寸的空闲分区,只有当所有分区都已用完时,申请者才需要等待。灵活,但可能产生较大的内部碎片。
3、静态分区:内存利用率低,产生内部碎片;尺寸和分区数量难以确定。
1、不预先确定分区的大小和数量,将分区工作推迟到实际分配内存时进行。 Lazy
初始情况下,把所有的空闲内存看成一个大分区。分配时,按申请的尺寸,找一块足够大的空闲内存分区,临时从中划出一块构成新分区。新分区的尺寸与申请的大小相等,不会出现内部碎片。回收时,尽可能与邻近的空闲分区合并。在内存紧缺时,可将某个选定的分区换出。
2、会产生外部碎片,如下图(内部碎片是指 eg:要 1M,分了 8M,产生 7M 的碎片):
移动内存中的进程,将碎片集中起来,重新构成大的内存块。需要运行时的动态重定位,费时。
(1)紧缩方向:向一头紧缩,向两头紧缩。
(2)紧缩时机:①在释放分区时,如果不能与空闲分区合并,则立刻进行紧缩。
好处是不存在外部碎片,坏处是费时。
②在内存分配时,如果剩余的空闲空间总量能满足要求但没有一个独立的空闲块能满足要求,则进行紧缩。
好处是减少紧缩次数。Lazy。
①最先适应算法(First fit):从头开始,在满足要求的第一个空闲块中分配。
分区集中在内存的前部,大内存留在后面,便于释放后的合并。
②最佳适应算法(Best fit):遍历空闲块,在满足要求的最小空闲块中分配。
留下的碎片最小,基本无法再用,需要更频繁地紧缩。
③下一个适应算法(Next fit):从上次分配的位置开始,在满足要求的下一个空闲块中分配。
对内存的使用较平均,不容易留下大的空闲块。
④最差适应算法(Worst Fit):遍历空闲块,在满足要求的最大空闲块中分配。
留下的碎片较大,但不会剩余大内存。
最先适应算法较优,最佳适应算法较差。
伙伴算法:将动态分区的大小限定为 2^k 字节,分割方式限定为平分,分区就会变得较为规整,分割与合并会更容易,可以减少一些外部碎片。平分后的两块互称伙伴。
1、
分配时可能要多次平分,释放时可能要多次合并。举例:
记录大小不同的空闲页:
示意图:
2、
伙伴算法是静态分区和动态分区法的折中,比静态分区法灵活,不受分区尺寸及个数的限制;比动态分区法规范,不易出现外部碎片。会产生内部碎片,但比静态分区的小。
Linux、Windows、Ucore等都采用伙伴算法管理物理内存。
一般情况下,将最小尺寸定为 2^12 字节(1页,4K=4096B),最大尺寸定为1024页,11个队列。
Linux、Windows、Ucore 等都将伙伴的最小尺寸限定为1页。
ucore 用 page,在内存初始化函数 page_init 中为系统中的每个物理页建立一个 page 结构。
页块(pageblock)是一组连续的物理页。
5、
(1)判断伙伴关系/寻找伙伴
最后两行是指,B1和B2只有第i位相反。
(2)判断伙伴是否空闲:
ucore 用 free_area[ ]数组定义空闲页块队列。
(3)确定伙伴是否在 order 队列中:
7、
(1)解决内部碎片过大问题(eg:申请5页,分配8页,浪费3页):
ucore 在前部留下需要的页数,释放掉尾部各页。每次释放1页,先划分成页块,再逐个释放。
(2) 解决切分与合并过于频繁的问题:
用得较多的是单个页。位于处理器Cache中页称为热页(hot page),其余页称为冷页(cold page)。处理器对热页的访问速度要快于冷页。
可建一个热页队列(per_cpu_page),暂存刚释放的单个物理页,将合并工作向后推迟 Lazy。总是试图从热页队列中分配单个物理页。分配与释放都在热页队列的队头进行。
(3)解决内存碎化(有足够多的空闲页,但是没有大页块)问题:①将页块从一个物理位置移动到另一个物理位置,并保持移动前后逻辑地址不变(拷贝页块内容);②逻辑内存管理器。
(4)满足大内存的需求:
(5)物理内存空间都耗尽的情况:
在任何情况下,都应该预留一部分空闲的物理内存以备急需。定义两条基准线low和high,当空闲内存量小于low时,应立刻开始回收物理内存,直到空闲内存量大于high。
(6)回收物理内存:
法一:启动一个守护进程,专门用于回收物理内存。周期性启动,也可被唤醒。
法二:申请者自己去回收内存。实际是由内存分配程序回收。回收的方法很多,如释放缓冲区、页面淘汰等。
1、
伙伴算法最小分配内存为页,对于更小的内存的管理 --> Slab 算法
内和运行过程中经常使用小内存(小于1页)eg:建立数据结构、缓冲区
内核对小内存的使用极为频繁、种类繁多、时机和数量难以预估。所以难以预先分配,只能动态地创建和撤销
2、
Slab 向伙伴算法申请大页块(批发),将其划分成小对象分配出去(零售);将回收的小对象组合成大页块后还给伙伴算法。
Slab 采用等尺寸静态分区法,将页块预先划分成一组大小相等的小块,称为内存对象。
具有相同属性的多个Slab构成一个Cache,一个Cache管理一种类型(一类应该是指一个大小)的内存对象。当需要小内存时,从预建的Cache中申请内存对象,用完之后再将其还给Cache。当Cache中缺少对象时,追加新的Slab;当物理内存紧缺时,回收完全空闲的Slab。
Slab 算法的管理结构:
① Cache 管理结构:管理Slab,包括Slab的创建和销毁。
② Slab 管理结构:管理内存对象,包括小对象的分配与释放。
(Cache结构和Slab结构合作,共同实现内存对象的管理)
3、
(1)描述各个内存对象的使用情况
可以用位图标识空闲的内存对象。也可以将一个Slab中的空闲内存对象组织成队列,并在slab结构中记录队列的队头。
早期的Linux在每个内存对象的尾部都加入一个指针,用该指针将空闲的内存对象串联成一个真正的队列。(对象变长、不规范,空间浪费)
改进:将指针集中在一个数组中,用数组内部的链表模拟内存对象队列。
再改进:将数组中的指针换成对象序号,利用序号将空闲的内存对象串成队列。序号数组是动态创建的。
序号数组可以位于 Slab 内部,也可以位于 Slab 外部
(2)一个Cache会管理多个Slab,可以将所有Slab放在一个队列中。
Ucore为每个Cache准备了两个slab结构队列:全满的和不满的。Linux为每个Cache准备了三个slab结构队列:部分满的、完全满的和完全空闲的。
Linux允许动态创建Cache,Ucore不许。Ucore预定了对象大小,分别是32、64、128、256、512、1K、2K(4K、8K、16K、32K、64K、128K)。为每一种大小的对象预建了Cache。
(3)Slab是动态创建的,当Cache中没有空闲的内存对象时,即为其创建一个新的Slab。
Slab所需要的内存来自伙伴算法,大小是 2^page_order 个连续页。
4、小对象的尺寸
如按处理器一级缓存中缓存行(Cache Line)的大小(16、32字节)取齐,可使对象的开始位置都位于缓存行的边界处。
在将页块划分成内存对象的过程中,通常会剩余一小部分空间,位于所有内存对象之外,称为外部碎片。
Slab算法选用碎片最小的实现方案。
5、
(1)对象分配 kmalloc
① 根据size确定一个Cache。
② 如果Cache的slabs_notfull为空,则为其创建一个新的Slab。
③ 选中slabs_notfull中第一个Slab,将队头的小对象分配出去,并调整队列。
④ 对象的开始地址是:objp = slabp->s_mem + slabp->free * cachep->objsize;
(2)对象释放 kfree
① 算出对象所在的页号,找到它的 Page 结构。
② 根据 Page 找到所属的 Cache 和 Slab。
③ 算出对象序号:objnr = (objp - slabp->s_mem) / cachep->objsize;
④将序号插入Slab的free队列。
⑤整Slab所属队列。
⑨ 求C++实现的几个内存分配算法源代码。
http://wenku..com/view/2869ce80ec3a87c24028c46f.html
这里面有C语言实现的上面的两种算法,如果运行不了的话,你可以改改,如果运行成功了的话,你可以根据这个来写出另一个算法的代码。
⑩ [编程知识]如何分配内存 内存碎片处理技术
内存碎片是一个很棘手的问题。如何分配内存决定着内存碎片是否会、何时会、如何会成为一个问题。 即使在系统中事实上仍然有许多空闲内存时,内存碎片还会最终导致出现内存用完的情况。一个不断产生内存碎片的系统,不管产生的内存碎片多么小,只要时间足够长,就会将内存用完。这种情况在许多嵌入式系统中,特别是在高可用性系统中是不可接受的。有些软件环境,如 OSE 实时操作系统已经备有避免内存碎片的良好工具,但个别程序员做出的选择仍然会对最终结果形成影响。 “碎片的内存”描述一个系统中所有不可用的空闲内存。这些资源之所以仍然未被使用,是因为负责分配内存的分配器使这些内存无法使用。这一问题通常都会发生,原因在于空闲内存以小而不连续方式出现在不同的位置。由于分配方法决定内存碎片是否是一个问题,因此内存分配器在保证空闲资源可用性方面扮演着重要的角色。 编译时间与运行时间 在许多情况下都会出现内存分配问题。程序员可以通过编译程序和链接程序,为结构、并集、数组和标量(用作局部变量、静态变量或全局变量)方面的数据分配内存,程序员还可以在运行时间使用诸如 malloc()调用命令动态地分配内存。当用编译程序和链接程序完成内存分配功能时,就不会出现内存碎片,因为编译程序了解数据寿命。掌握可供使用的数据寿命,好处在于可以使数据以后进先出的方式叠加起来。这样就可以使内存分配程序工作效率更高,而不会出现内存碎片。一般来说,运行时间内的内存分配是不可叠加的。内存分配在时间上是独立的,从而使得碎片问题难以解决。 图1,内存碎片的几种形式。 内存分配程序浪费内存的基本方式有三种:即额外开销、内部碎片以及外部碎片(图 1)。内存分配程序需要存储一些描述其分配状态的数据。这些存储的信息包括任何一个空闲内存块的位置、大小和所有权,以及其它内部状态详情。一般来说,一个运行时间分配程序存放这些额外信息最好的地方是它管理的内存。内存分配程序需要遵循一些基本的内存分配规则。例如,所有的内存分配必须起始于可被 4、8 或 16 整除(视处理器体系结构而定)的地址。内存分配程序把仅仅预定大小的内存块分配给客户,可能还有其它原因。当某个客户请求一个 43 字节的内存块时,它可能会获得 44字节、48字节 甚至更多的字节。由所需大小四舍五入而产生的多余空间就叫内部碎片。 外部碎片的产生是当已分配内存块之间出现未被使用的差额时,就会产生外部碎片。例如,一个应用程序分配三个连续的内存块,然后使中间的一个内存块空闲。内存分配程序可以重新使用中间内存块供将来进行分配,但不太可能分配的块正好与全部空闲内存一样大。倘若在运行期间,内存分配程序不改变其实现法与四舍五入策略,则额外开销和内部碎片在整个系统寿命期间保持不变。虽然额外开销和内部碎片会浪费内存,因此是不可取的,但外部碎片才是嵌入系统开发人员真正的敌人,造成系统失效的正是分配问题。 定义内存碎片的方法有几种,其中最常用的是: 这一方法适用于外部碎片,但可以修改这一公式使之包括内部碎片,办法是把内部碎片加入到分母中。内存碎片是一个介于 0 和 1 之间的分数。一个碎片为 1(100%)的系统就是把内存全用完了。如果所有空闲内存都在一个内存块(最大内存块)中,碎片为 0%。当所有空闲内存的四分之一在最大内存块中时,碎片为 75%。例子如下:一个系统有 5M 字节的空闲内存,当它可用来分配的最大内存块为 50 k 字节时,其内存碎片为99%。这个 99%内存碎片实例来自开发嵌入式软实时系统期间出现的一种真实情况。当这种碎片程度发生一秒后,系统就崩溃了。该系统在碎片率达到 99% 之前,已经进行了约两周的连续现场测试。这种情况是如何发生的?为什么会发现得如此晚?当然,系统都经过测试,但测试很少超过两个小时。交付前的最后压力测试持续了一个周末。在这样短的测试周期内未必会产生内存碎片的后果,所以就发生了内存碎片需要多长时间才会达到临界值,这一问题很难回答。对某些应用来说,在某些情况下,系统会在用完内存前达到一种稳定状态。而对于另一些应用来说,系统则不会及时达到稳定状态(图 2)。只要消除不确定性因素和风险因素,不产生碎片的内存分配程序(图 3)就能快速达到一种稳定状态,从而有助于开发人员夜晚安稳睡觉。在开发数月甚至数年不再重新启动的长期运行系统时,快速收敛到稳定状态是一个重要因素。在比系统连续运行周期短的时间内,对系统进行适当的测试,这是必不可少的。 图2,这一案例研究把最先适合内存分配程序用于一个嵌入系统项目。系统在现场测试中连续运行了两周,然后碎片率达到 99%。图3,一个不产生碎片的内存分配程序一旦试验应用程序的全部,它就能达到稳定状态。 很难确定哪种内存分配算法更胜一筹,因为每种算法在不同的应用中各有所长(表 1)。最先适合内存分配算法是最常用的一种。它使用了四个指针:MSTART 指向被管理内存的始端;MEND 指向被管理内存的末尾;MBREAK 指向 MSTART 和 MEND 之间已用内存的末端; PFREE 则指向第一个空闲内存块(如果有的话)。 在系统开始运行时,PFREE 为 NULL,MBREAK 指向 MSTART。当一个分配请求来到时,分配程序首先检查 PFREE有无空闲内存块。由于 PFREE 为 NULL,一个具有所请求存储量加上管理标题的内存块就脱离 MBREAK ,然后MBREAK就更新。这一过程反复进行,直至系统使一个内存块空闲,管理标题包含有该存储块的存储量为止。此时,PFREE 通过头上的链接表插入项被更新为指向该内存块,而块本身则用一个指向旧 PFREE 内容的指针进行更新,以建立一个链接表。下一次出现分配请求时,系统就会搜索空闲内存块链接表,寻找适合请求存储量的第一个空闲内存块。一旦找到合适的内存块,它将此内存块分成两部分,一部分返还给系统,另一部分则送回给自由表。 最先适合内存分配算法实现起来简单,而且开始时很好用。但是,经过一段时间后,会出现如下的情况:当系统将内存交给自由表时,它会从自由表的开头部分去掉大内存块,插入剩余的小内存块。最先适合算法实际上成了一个排序算法,即把所有小内存碎片放在自由表的开头部分。因此,自由表会变得很长,有几百甚至几千个元素。因此,内存分配变得时间很长又无法预测,大内存块分配所花时间要比小内存块分配来得长。另外,内存块的无限制拆分使内存碎片程度很高。有些实现方法在使内存空闲时会将邻近的空闲内存块连接起来。这种方法多少有些作用,而最先适合算法与时间共处算法(time co-location)和空间共处算法(spatial co-location)不同,它在使内存块空闲时,无法提高相邻内存块同时空闲的概率。 最佳适合与最差适合分配程序 最佳适合算法在功能上与最先适合算法类似,不同之处是,系统在分配一个内存块时,要搜索整个自由表,寻找最接近请求存储量的内存块。这种搜索所花的时间要比最先适合算法长得多,但不存在分配大小内存块所需时间的差异。最佳适合算法产生的内存碎片要比最先适合算法多,因为将小而不能使用的碎片放在自由表开头部分的排序趋势更为强烈。由于这一消极因素,最佳适合算法几乎从来没有人采用过。 最差适合算法也很少采用。最差适合算法的功能与最佳适合算法相同,不同之处是,当分配一个内存块时,系统在整个自由表中搜索与请求存储量不匹配的内存快。这种方法比最佳适合算法速度快,因为它产生微小而又不能使用的内存碎片的倾向较弱。始终选择最大空闲内存块,再将其分为小内存块,这样就能提高剩余部分大得足以供系统使用的概率。 伙伴(buddy)分配程序与本文描述的其它分配程序不同,它不能根据需要从被管理内存的开头部分创建新内存。它有明确的共性,就是各个内存块可分可合,但不是任意的分与合。每个块都有个朋友,或叫“伙伴”,既可与之分开,又可与之结合。伙伴分配程序把内存块存放在比链接表更先进的数据结构中。这些结构常常是桶型、树型和堆型的组合或变种。一般来说,伙伴分配程序的工作方式是难以描述的,因为这种技术随所选数据结构的不同而各异。由于有各种各样的具有已知特性的数据结构可供使用,所以伙伴分配程序得到广泛应用。有些伙伴分配程序甚至用在源码中。伙伴分配程序编写起来常常很复杂,其性能可能各不相同。伙伴分配程序通常在某种程度上限制内存碎片。 固定存储量分配程序有点像最先空闲算法。通常有一个以上的自由表,而且更重要的是,同一自由表中的所有内存块的存储量都相同。至少有四个指针:MSTART 指向被管理内存的起点,MEND 指向被管理内存的末端,MBREAK 指向 MSTART 与 MEND 之间已用内存的末端,而 PFREE[n] 则是指向任何空闲内存块的一排指针。在开始时,PFREE[*] 为 NULL,MBREAK 指针为 MSTART。当一个分配请求到来时,系统将请求的存储量增加到可用存储量之一。然后,系统检查 PFREE[ 增大后的存储量 ] 空闲内存块。因为 PFREE[ 增大后的存储量 ] 为 NULL,一个具有该存储量加上一个管理标题的内存块就脱离 MBREAK,MBREAK 被更新。 这些步骤反复进行,直至系统使一个内存块空闲为止,此时管理标题包含有该内存块的存储量。当有一内存块空闲时,PFREE[ 相应存储量 ] 通过标题的链接表插入项更新为指向该内存块,而该内存块本身则用一个指向 PFREE[ 相应存储量 ] 以前内容的指针来更新,以建立一个链接表。下一次分配请求到来时,系统将 PFREE[ 增大的请求存储量 ] 链接表的第一个内存块送给系统。没有理由搜索链接表,因为所有链接的内存块的存储量都是相同的。 固定存储量分配程序很容易实现,而且便于计算内存碎片,至少在块存储量的数量较少时是这样。但这种分配程序的局限性在于要有一个它可以分配的最大存储量。固定存储量分配程序速度快,并可在任何状况下保持速度。这些分配程序可能会产生大量的内部内存碎片,但对某些系统而言,它们的优点会超过缺点。 减少内存碎片 内存碎片是因为在分配一个内存块后,使之空闲,但不将空闲内存归还给最大内存块而产生的。最后这一步很关键。如果内存分配程序是有效的,就不能阻止系统分配内存块并使之空闲。即使一个内存分配程序不能保证返回的内存能与最大内存块相连接(这种方法可以彻底避免内存碎片问题),但你可以设法控制并限制内存碎片。所有这些作法涉及到内存块的分割。每当系统减少被分割内存块的数量,确保被分割内存块尽可能大时,你就会有所改进。 这样做的目的是尽可能多次反复使用内存块,而不要每次都对内存块进行分割,以正好符合请求的存储量。分割内存块会产生大量的小内存碎片,犹如一堆散沙。以后很难把这些散沙与其余内存结合起来。比较好的办法是让每个内存块中都留有一些未用的字节。留有多少字节应看系统要在多大程度上避免内存碎片。对小型系统来说,增加几个字节的内部碎片是朝正确方向迈出的一步。当系统请求1字节内存时,你分配的存储量取决于系统的工作状态。 如果系统分配的内存存储量的主要部分是 1 ~ 16 字节,则为小内存也分配 16 字节是明智的。只要限制可以分配的最大内存块,你就能够获得较大的节约效果。但是,这种方法的缺点是,系统会不断地尝试分配大于极限的内存块,这使系统可能会停止工作。减少最大和最小内存块存储量之间内存存储量的数量也是有用的。采用按对数增大的内存块存储量可以避免大量的碎片。例如,每个存储量可能都比前一个存储量大 20%。在嵌入式系统中采用“一种存储量符合所有需要”对于嵌入式系统中的内存分配程序来说可能是不切实际的。这种方法从内部碎片来看是代价极高的,但系统可以彻底避免外部碎片,达到支持的最大存储量。 将相邻空闲内存块连接起来是一种可以显着减少内存碎片的技术。如果没有这一方法,某些分配算法(如最先适合算法)将根本无法工作。然而,效果是有限的,将邻近内存块连接起来只能缓解由于分配算法引起的问题,而无法解决根本问题。而且,当内存块存储量有限时,相邻内存块连接可能很难实现。 有些内存分配器很先进,可以在运行时收集有关某个系统的分配习惯的统计数据,然后,按存储量将所有的内存分配进行分类,例如分为小、中和大三类。系统将每次分配指向被管理内存的一个区域,因为该区域包括这样的内存块存储量。较小存储量是根据较大存储量分配的。这种方案是最先适合算法和一组有限的固定存储量算法的一种有趣的混合,但不是实时的。 有效地利用暂时的局限性通常是很困难的,但值得一提的是,在内存中暂时扩展共处一地的分配程序更容易产生内存碎片。尽管其它技术可以减轻这一问题,但限制不同存储量内存块的数目仍是减少内存碎片的主要方法。 现代软件环境业已实现各种避免内存碎片的工具。例如,专为分布式高可用性容错系统开发的 OSE 实时操作系统可提供三种运行时内存分配程序:内核 alloc(),它根据系统或内存块池来分配;堆 malloc(),根据程序堆来分配; OSE 内存管理程序 alloc_region,它根据内存管理程序内存来分配。 从 许多方面来看,Alloc就是终极内存分配程序。它产生的内存碎片很少,速度很快,并有判定功能。你可以调整甚至去掉内存碎片。只是在分配一个存储量后,使之空闲,但不再分配时,才会产生外部碎片。内部碎片会不断产生,但对某个给定的系统和八种存储量来说是恒定不变的。 Alloc 是一种有八个自由表的固定存储量内存分配程序的实现方法。系统程序员可以对每一种存储量进行配置,并可决定采用更少的存储量来进一步减少碎片。除开始时以外,分配内存块和使内存块空闲都是恒定时间操作。首先,系统必须对请求的存储量四舍五入到下一个可用存储量。就八种存储量而言,这一目标可用三个 如果 语句来实现。其次,系统总是在八个自由表的表头插入或删除内存块。开始时,分配未使用的内存要多花几个周期的时间,但速度仍然极快,而且所花时间恒定不变。 堆malloc() 的内存开销(8 ~ 16 字节/分配)比 alloc小,所以你可以停用内存的专用权。malloc() 分配程序平均来讲是相当快的。它的内部碎片比alloc()少,但外部碎片则比alloc()多。它有一个最大分配存储量,但对大多数系统来说,这一极限值足够大。可选的共享所有权与低开销使 malloc() 适用于有许多小型对象和共享对象的 C++ 应用程序。堆是一种具有内部堆数据结构的伙伴系统的实现方法。在 OSE 中,有 28 个不同的存储量可供使用,每种存储量都是前两种存储量之和,于是形成一个斐波那契(Fibonacci)序列。实际内存块存储量为序列数乘以 16 字节,其中包括分配程序开销或者 8 字节/分配(在文件和行信息启用的情况下为 16 字节)。 当你很少需要大块内存时,则OSE内存管理程序最适用。典型的系统要把存储空间分配给整个系统、堆或库。在有 MMU 的系统中,有些实现方法使用 MMU 的转换功能来显着降低甚至消除内存碎片。在其他情况下,OSE 内存管理程序会产生非常多的碎片。它没有最大分配存储量,而且是一种最先适合内存分配程序的实现方法。内存分配被四舍五入到页面的偶数——典型值是 4 k 字节。(T111)