1 / 36

数据结构 第八章 动态存储管理

数据结构 第八章 动态存储管理. 本章内容 8.1 动态存储管理概述 8.2 可利用空间表及分配方法 8.3 边界标识法 8.4 伙伴系统. 8.1 动态存储管理概述. 存储管理 —— 每一种数据结构都必须研究该结构的存储结构,但它是借助于某一高级语言中的变量说明来加以描述的,并没有涉及到具体的存储分配。 实际上,结构中的每个数据元素都占有一定的内存位置,在程序执行的过程中,数据元素的存取是通过对应的存储单元来进行的。 研究 数据存储 与 内存单元 对应问题,就是存储管理问题。. 8.1 动态存储管理概述. 动态存储管理的基本问题

cassie
Download Presentation

数据结构 第八章 动态存储管理

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 数据结构第八章 动态存储管理

  2. 本章内容 8.1动态存储管理概述 8.2可利用空间表及分配方法 8.3边界标识法 8.4伙伴系统

  3. 8.1 动态存储管理概述 • 存储管理——每一种数据结构都必须研究该结构的存储结构,但它是借助于某一高级语言中的变量说明来加以描述的,并没有涉及到具体的存储分配。 实际上,结构中的每个数据元素都占有一定的内存位置,在程序执行的过程中,数据元素的存取是通过对应的存储单元来进行的。 研究数据存储与内存单元对应问题,就是存储管理问题。 8-3

  4. 8.1 动态存储管理概述 • 动态存储管理的基本问题 • 如何根据用户提出的“请求”来分配内存。 • 如何收回被用户“释放”的内存,以备新的“请求”产生时重新进行分配。 8-4

  5. 内存的初始状态 U1 U2 U3 U4 系统运行初期 系统运行若干时后 U2 U4 8.1 动态存储管理概述 • 存储原理 • 计算机内存在刚工作时,空闲部分是一个整块的连续区域; • 不断运行程序,多次申请和释放内存以后,空闲内存不再连续,形成多个不连续的空闲区。 • 动态存储管理:指系统随机地根据用户程序申请空间的大小,进行分配空间和回收不用空间所进行的内存管理。 • 占用块:将系统已分配给用户使用的地址连续的内存区域为“占用块”; • 空闲块:称未曾分配的地址连续的内存区为“空闲块”。 8-5

  6. 25000 39000 0 10000 31000 59000 99999 内存状态 目录表 10000 15000 空闲 31000 8000 空闲 59000 41000 空闲 31000 59000 10000 av 0 8000 0 41000 ^ 0 15000 8.1 动态存储管理概述 • 可利用空间表 内存空间的所有可利用的空闲空间的情况记录表。有两种结构: • 目录表; • 链表:一个结点表示一个空闲块。 链表 8-6

  7. 8.2 可利用空间表及分配方法 本节主要讨论利用可利用空间表进行动态存储分配的方法。目录表法比较简单,在《操作系统》课程中已详细介绍。本节仅讨论链表方法分配内存。 8-7

  8. 8.2 可利用空间表及分配方法 • 三种结构形式: • 第一种情况:系统运行期间所有用户请求分配的存储量大小相同;具体作法是:开始运行时将内存区分割成若干大小相同的块,形成一可利用链表,分配和回收操作如同一般链表。 • 第二种情况:系统运行期间用户请求分配的存储量有若干种大小的规格;具体作法是:先建立若干个可利用空间表,同一链表中的结点小相同,分配/回收情况: • 结点大小与请求分配量相同时; • 结点大小比请求量大时; • 结点大小比请求量小时。 8-8

  9. av2 … 0 0 0 0 0 0 av4 … 0 1 0 1 0 1 0 空闲块 1 占用块 tag = av8 … 0 节点大小为2字节 1 节点大小为4字节 2 节点大小为8字节 0 0 0 0 0 0 type = 可利用空间表 8.2 可利用空间表及分配方法 8-9

  10. 8.2 可利用空间表及分配方法 • 第三种情况:系统在运行期间分配给用户的内存块的大小不固定,可以随请求而变。 • 工作情况:系统刚开始工作时,整个内存空间是一个空闲块,随时着分配和回收的进行,可利用空间表中的结点大小和个数也随之而变。 8-10

  11. 8.2 可利用空间表及分配方法 • 分配方法 • 若某用户需大小为n的内存,而可利用空间仅有一块大小为 m≥n 的空闲块,则只需将其中大小为n 的一部分分配给申请的用户,同时将乘余的 m-n 的部分作为一个结点留在链表中即可。 • 若可利用空间表有若干个不小于n的空闲块时,可有三种不同的分配方案: 8-11

  12. 8.2 可利用空间表及分配方法 • 首次拟合法 • 分配找到的第一个不小于n的空闲块的一部分。 • 操作方便,查找快捷; • 最佳拟合法 • 分配不小于n且最接近n的空闲块的一部分。 • 尽可能将大的空闲块留给大程序使用; • 最坏拟合法 • 分配不小于n且是最大的空闲块的一部分。 • 尽可能减少分配后无用碎片; 8-12

  13. 8.2 可利用空间表及分配方法 • 内存的分配与回收 • 分配 • 根据申请内存大小利用相应分配策略分配给用户所需空间; • 若分配块大小与申请大小相差不多,则将此块全部分给用户; • 否则,将分配块分为两部分,一部分给用户使用,另一部分继续留在可利用空间表中。 • 回收 • 测试回收块前后相邻内存块是否空闲; • 若是则需将回收块与相邻空闲块合并成较大的空闲块,再链入可利用空间表中。 8-13

  14. 8.3 边界标识法 • 用以进行动态分区分配的一种管理方法 • 节点结构 • 可利用空间表中的节点结构定义 • type struct WORD { //WORD,内存数据类型 • union { //head 和 foot 分别是节点的第一个和最后一个字 • WORD *llink; //头部域,指向前趋节点 • WORD *rlink; //底部域,指向本节点头部 • }; • int tag; //块标志: 0空闲,1占用.头部和尾部均有 • int size; //头部域,块大小 • WORD *rlink; //头部域,指向后继节点 • otherType other; //字的其他部分 • } WORD, head, foot, *Space; //*Space: 可利用空间指针类型 • #define FootLoc(p) p+p->size-1 //指向p所指节点的底部 可利用空间表中的节点结构图 8-14

  15. 8.3 边界标识法 • 分配算法:采取首次拟合法进行分配。有两个约定: • 假设待分配的内存空闲块容量为m 个字,若每次分配只从中分配n个字(n<m)给用户,剩余m-n个字的节点仍留在表中,若干次分配后,链表中存在大量容量极小,总分配不出去的空闲块。解决的办法是:确定一个常量e,当m-n<e时,就将容量为m的空闲块整块分配给用户,否则只分配其中n个字的块,同时为了避免修改指针,约定将该节点中的高地址部分分配给用户。 • 若每次分配都从同一节点开始查找,会使存储量小的节点密集在头指针pav所指节点附近,这会增大寻找到较大空间块的时间。避免的方法是,每次从不同的节点开始查找,使剩余小块均匀地分布在链表中。实现方法是,每次分配后,令指针pav指向刚进行过分配的节点的后继节点。 8-15

  16. 8.3 边界标识法 • 分配算法 (见教材第200页) Space AlloctionBoundTag(Space &pav, int n) { //若有不小于n 的空闲块,则分配之, //并返回首地址;否则返回NULL. //若分配后可利用的空间表不空, //则pav指向表中刚分配过的节点后继节点 for (p=pav; p&&p->size<n && p->rlink!=pav; p=p->rlink;) //如果查找不小于n的空闲块,找不到返回NULL if (!p || p->size<n) pav = NULL; else //p指向找到的空闲块 { f=FootLoc(p); //指向底部 pav=p->rlink; //pav指向*p节点的后继 if (p->size-n<=e) //整块分配,不保留<e的剩余量 { if (pav==p) pav=NULL;//可利用空间表为空 else //在表中删除分配的节点 { pav->llink=p->llink;p->llink->rlink =pav; } p->tg=f->tag=1; //修改分配节点的头部和底部标志 } else//分配该块后的n个字 { f->tag=1; //修改分配块的底部标志 p->size - =n; //修改剩余块大小 f=FootLoc(p); //指向剩余块底部 f->tag=0; f->uplink=p; //设置剩余块底部 p=f+1; //指向分配块头部 p->tag=1; p->size=n; //设置分配块头部 } return p; //返回分配块的首地址 } } 8-16

  17. 8.3 边界标识法 • 回收算法 用户释放占用块后,系统需立即回收以备新的请求产生时进行再分配。为了使物理地址毗邻的空闲块结合成一个尽可能大的结点,则首先需要检查刚释放的占用块的左、右紧邻是否为空闲块。 假设用户释放的内存区的头部地址为p,则 p-1=与其低地址紧邻的内存区的底部地址,即左邻区; p+p->size=与其高地址紧邻的内存区的头部地址,即右邻区。 8-17

  18. 8.3 边界标识法 • 释放块的左、右邻区均为占用块 此时只要作简单插入即可。由于边界标识法在按首次拟合进行分配时对可利用空间表的结构没有任何要求,则新的空闲块插入在表中任何位置均可。简单的做法就是插入在pav指针所指结点之前(之后),描述如下: p->tag = 1= 0; FootLoc (p)->uplink = p; FootLoc (p)->tag = 0; if (!pav) pav = p->llink = p->rlink = p; else { q = pav->llink; p->rlink = pav; p->llink = q; q->rlink = pav->llink = p; pav = p; //令刚释放的结点为下次分配时的最先查询的结点 } 8-18

  19. 8.3 边界标识法 • 释放块的左邻区为空闲块,而右邻区为占用块 由于释放块的头部和左邻空闲块的底部毗邻,因此只要改变左邻空闲块的结点:增加结点的size域的值且重新设置结点的底部即可。描述如下 n = p->size; //释放块的大小 s = (p-1)->uplink; //左邻空闲块的头部地址 s->size + = n; //设置新的空闲块大小 f = p + n-1; //设置新的空闲块底部 f->uplink = s; f->tag = 0; 8-19

  20. 8.3 边界标识法 • 释放块的右邻区为空闲块,而左邻区为占用块 由于释放块的底部和右邻区空闲块的头部毗邻,因此,当表中结点由原来的右邻空闲块变成合并后的大空闲块时,结点的底部位置不变,但头部要变,由此,链表中的指针也要变。描述如下: t = p + p->size; //右邻空闲块的头部地址 p->tag = 0; //p为合并后的结点头部地址 q = t->llink; //q为*t结点在可利用空间表中的前驱结点的头部地址 p->llink = q; //q指向*p的前驱 q->rlink = p; q1 = t->rlink; //q1为*t结点在可利用空间表中的后继结点的头部地址 p->rlink = q1; //q1指向*p的后继 q1->llink = p; p->size + = t->size; //新的空闲块的大小 FootLoc (t)->uplink = p; //底部指针指向新结点的头部 8-20

  21. 8.3 边界标识法 • 释放块的左、右邻区均为空闲块 为使3个空闲块连接在一起成为一个大结点留在可利用空间表中,只要增加左邻空闲块的space容量,同时在链表中删去右邻空闲块结点即可。所作改变可描述如下: n = p->size; //释放块的大小 s = (p-1)->uplink; //指向左邻块 t = p + p->size; //指向右邻块 s->size + = n + t->rlink; //设置新结点的大小 q = t->llink; //q != q1 q1 = t->rlink; q->rlink = q1; //删去右邻空闲块结点 q1->llink = q; FootLoc (t)->uplink = s; //新结点底部指针指向其头部 8-21

  22. 20 000 30 000 0 30 000 1 20 000 左邻区 释放块 1 0 0 35 000 0 15 000 右邻区 释放块 右邻区 0 8.3 边界标识法 例如,在上述情况下可利用空间表的变化如图8.6所示。 释放块 (a) 释放的存储块 (b) 左邻区是空闲块的情况 (c) 右邻区是空闲块的情况 8-22

  23. 0 15 000 0 45 000 左邻区 右邻区 释放块 右邻区 0 8.3 边界标识法 (d) 左、右邻区均是空闲块的情况 回收存储块后的可利用空间表 8-23

  24. 8.3 边界标识法 • 边界表示法的问题 • 查找适合需要的块,需要较多的时间 • 查找适合需要的块的策略(最先/最佳/最坏),每种都有缺陷 • 碎片问题 8-24

  25. 8.4伙伴系统 • 伙伴系统(buddy system):是操作系统中用到的一种动态存储管理方法。它和边界标识法类似,在用户提出申请时,分配一块大小“恰当”的内存区给用户;反之,在用户释放内存区时即收回。 • 伙伴系统的特点:无论是占用块或空闲块,其大小均为2的k次幂(k为某个正整数)。 8-25

  26. head llink tag kval rlink nodesize first space 表头结点 8.4伙伴系统 • 结点结构 • 结点:右头部head和space域组成。 • head:为结点头部,是一个由4个域组成的记录。 • llink: 链域,指向同一链表中的前驱结点。 • rlink: 链域,指向同一链表中的后继结点。 • tag: 标志域,值为“0”表示空闲块,值为“1”表示占用块。 • kval: 其值为2的幂次k。 • space:数据域,是一个大小为2k-1个字的连续内存空间。 • 表头结点:由两个域组成。 • nodesize:表示该链表中空闲块的大小。 • first:该链表的表头指针。 8-26

  27. 8.4伙伴系统 • 可利用空间表的结构C语言描述 #define m 16 //可利用空间总容量64k字的2的幂次,子表的个数为m+1 typedef struct WORD_b { WORD_b *llink; //头部域,指向前驱结点 int tag; //块标志,0:空闲,1:占用。 int skval; //块大小,值为2的幂次k WORD_b *rlink; //头部域,指向后继结点 OtherType other; //字的其他部分 } WORD_b, head; //WORD:内存字类型,结点的第一个字也称head typedef struct HeadNode { int nodesize; //该链表的空闲块的大小 WORD_b *first; //该链表的表头指针 } FreeList[m + 1]; //表头向量类型 8-27

  28. 20 21 2k 0 m 2m nodesize first (a) 表的初始状态 8.4伙伴系统 • 示例:可利用空间表的初始状态如下图所示,其中m个子表都为空表,只有大小为2m的链表中有一个结点,即整个存储空间。 伙伴系统中的可利用空间表 8-28

  29. 8.4伙伴系统 • 分配算法 当用户提出大小为n的内存请求时,首先在可利用表上寻找结点大小与n相匹配的子表,若此子表非空,则将子表中任意一个结点分配之即可;若此子表为空,则需从结点更大的非空子表中去查找,直至找到一个空闲块,则将其中一部分分配给用户,而将剩余部分插入相应的子表中 8-29

  30. 8.4伙伴系统 • 算法实现 WORD_b AllocBuddy (FreList &avail, int n) { //avail[0..m]为可利用空间表,n为申请分配量,若有不小于n的空 //闲/块,则分配相应的存储块,并返回其首地址;否则返回NULL for (k = 0; k <= m && (avail[k].nodesize<n+1||!avail[k].first);++k); //查找满足分配要求的子表 if (k > m) //分配失败返回NULL return NULL; else //进行分配 { pa = avail[k].first; //指向可分配子表的第一个结点 pre = pa->llink; //分别指向前驱和后继 suc = pa->rlink; if (pa = = suc) //分配后该子表变成空表 avail[k].first = NULL; else { //从子表中删去*pa结点 pre->rlink = suc; suc->llink = pre; avail[k].first = suc; } for (i=1; avail[k-i].nodesize>=n+1; ++i) { pi = pa + 2k-i; pi->rlink = pi; pi->llink = pi; pi->tag = 0; pi->kval = k-i; avail[k-i].first = pi; } //将剩余块插入相应子表 pa->tag = 1; pa->kval = k-(――i); } // else return pa; } // AllocBuddy 8-30

  31. 20 2k-1 2k 0 K 0 K 0 K 2m 分配前的表 8.4伙伴系统 • 例子: • 假设分配前的可利用空间表的状态如下图所示。若2k-1<n≤2k-1,又第k+1个子表不空,则只要删除此链表中第一个结点并分配给用户即可; 8-31

  32. 0 K-1 20 2k-1 2k 0 K 0 K 2m (b) 分配后的表 8.4伙伴系统 • 若2k-2 < n ≤2k-1-1,此时由于结点大小为2k-1的子表为空,则需从结点大小为2k的子表中取出一块,将其中一半分配给用户,剩余的一半作为一个新结点插入在结点大小为2k-1的子表中,如下图所示。 8-32

  33. 8.4伙伴系统 • 若2k-i-1 < n ≤2k-i-1(i为小于k的整数),并且所有结点小于2k的子表均为空,则同样需从结点大小为2k的子表中取出一块,将其中2k-i的一部分分配给用户,剩余部分分割成若干个结点分别插入在结点大小为2k-i、2k-i+1、…、2k-1的子表中。 8-33

  34. p + 2k (若p MOD 2k+1 = 0) buddy (p, k) = p-2k (若p MOD 2k+1 = 2k) 8.4伙伴系统 • 回收算法 • 伙伴:是指由同一个大的空闲块分裂成的两个大小相等的存储区,这两个由同一大块分裂出来的小块就称之“互为伙伴”。 • 起始地址为p,大小为2k的内存块,其伙伴的起始地址为: 8-34

  35. 8.4伙伴系统 • 算法思想 回收空闲块时,首先判别其伙伴释放为空闲块,若否,则只要将释放的空闲块简单插入在相应子表中即可;若是,则需在相应子表中找到其伙伴并删除之,然后再判别合并后的空闲块的伙伴是否是空闲块。依此重复,直到归并所得空闲块的伙伴不是空闲块时,再插入到相应的子表中去。 • 例子 例如,假设整个可利用内存区大小为210=1024(地址从0到1023),则大小为28,起始地址为512的伙伴块的起始地址为768;大小为27,起始地址为384的伙伴块的起始地址为256。 8-35

  36. 习题 • 本章习题参见教师网页: http://staff.ustc.edu.cn/~leeyi 8-36

More Related