嵌入式系统设计师之存储管理

目录

一、Flat存储管理方式(I)

二、分区存储管理(II)

1、固定分区

2、可变分区

3、内存保护

三、地址重定位 (II)

1、逻辑地址

2、物理地址 

四、页式存储管理 (II)

五、虚拟存储管理(II)

1、程序局部性原理

2、虚拟页式存储管理

3、页面置换算法

1、最优页面置换算法 

2、最近最久未使用算法 

3、最不常用算法

4、先进先出算法

5、时钟页面置换算法 


一、Flat存储管理方式(I)

        在嵌入式系统中,Flat存储管理方式是一种简单而有效的内存管理机制。它没有复杂的内存分区和分页机制,而是将整个内存空间视为一个连续的地址范围。应用程序可以直接使用物理地址进行内存访问,无需进行虚拟地址转换。

        Flat存储管理方式的优点包括简单高效、易于实现、占用资源少等。它适用于内存容量较小实时性要求较高的嵌入式系统。在这些系统中,复杂的内存管理机制可能会引入额外的开销,影响系统的性能和实时性。

        然而,Flat存储管理方式也存在一些局限性。例如,它无法实现内存隔离和保护,多个应用程序可能存在内存冲突的风险。此外,Flat存储管理方式也无法实现动态内存分配和回收,这需要在设计应用程序时进行充分的规划和分配。

        总之,Flat存储管理方式在嵌入式系统中是一种简单而有效的内存管理机制,适用于内存容量较小、实时性要求较高的场景。在使用时需要充分考虑其局限性和适用范围,并采取相应的措施来避免潜在的风险和问题。

二、分区存储管理(II)

1、固定分区

        各个用户分区的个数、位置和大小一旦确定后, 就固定不变,不能再修改了。

        例如,在系统启动时,由管理员来手工划分出若干个分区, 并确定各个分区的起始地址和大小等参数。然后,在系统的整个运行期间,这些参数就 固定下来,不再改变。

        当一个新任务到来时,需要根据它的大小,把它放置到相应的输入队列中去,等待合适的空闲分区。在具体的实现上,主要有两种实现方式,即多个输入队列单个输入队列    

        图4-27 (a)   是多个输入队列的一个例子。整个内存被分成五个区,包括四个用户分 区和一个系统分区。操作系统放在内存地址低端,占用了100KB 。分区1、分区2 和分 区3的大小分别是100KB 200KB 和300KB,   分区4的大小是100KB 。在多个输入队列 的方式下,对于每一个用户分区,都有一个相应的输入队列。在分区1 的输入队列中有 三个任务在待,在分区2的输入队列中有一个任务在等待。分区3的输入队列是空的, 而分区4 的输入队列中有两个任务在等待。当一个新任务到来时,就把它加入到某一个 输入队列中去。要求这个输入队列所对应的分区,是能够装得下该任务的最小分区。例 如,假设现在又来了一个新任务,它的大小是180KB,    那么应该把它加入到分区2的输 入队列中去。因为在当前情下,能够装得下该任务的只有分区2 和分区3,而分区2比分区3要小,所以它更合适。

嵌入式系统设计师之存储管理_第1张图片

        这种为每一个分区都设置一个输入队列的做法,有一个很大的缺点,它可能会出现

内存利用率不高的问题,即小分区的输入队列是满的,而大分区的输入队列却是空的。

        例如,在图4-27 (a) 的这个例子中,在分区1的输入队列中,有3个任务在等待,而分 区 3 的输入队列却是空的。也就是说, 一方面,有很多小任务在等着进入内存;而另一 方面,在内存中却存在着大量的空闲空间。事实上,如果把这300KB 的空闲空间平均分给这三个任务,那么它们就都能进入内存了。

        为了解决这个问题,人们又提出了单个输入队列的方法。它的基本思路是:对于所有的用户分区,只设置一个统一的输入队列。当一个新任务到来时,就把它加入到这个输入队列当中。然后当某个分区变得空闲的时候,就从这个队列中选择合适的任务去占用该分区。在任务的选择上,可以有两种方法。 第一种方法是选择离队首最近的、能够装入这个分区的任务。 如果选中的是一个比较小的任务,那么就会浪费大量的内存空间。 第二种方法是先搜索整个队列,从中选择能够装入这个分区的最大任务,这样就能尽可能地减少所浪费的空间。

2、可变分区

        分区不是预先划分好的固定区域,而是动态创建的。在装 入一个程序时,系统将根据它的需求和内存空间的使用情况来决定是否分配。具体来说, 在系统生成后,操作系统会占用内存的一部分空间,这一般是放在内存地址的最低端,  其余的空间则成为一个完整的大空闲区。当一个程序要求装入内存运行时,系统就会从这个空闲区当中划出一块来,分配给它,当程序运行结束后会释放所占用的存储区域。  随着一系列的内存分配和回收,原来的一整块的大空闲区就会形成若干个占用区和空闲区相间的布局。

        图4-28 是一个可变分区的例子。在系统当中,整个内存区域有1024KB 。如图4-28 (a)   所示,在初始化的时候,操作系统占用了内存地址最低端的128KB 。剩下的空间就 成为一个完整的大空闲区,总共是896KB 。然后,任务1进入了内存,它需要的内存空 间是320KB,   因此紧挨着操作系统,给它分配了一块大小为320KB 的内存,也就是说, 创建了一个新的用户分区,该分区的起始地址是128KB, 大小是320KB 此时,剩余的 内存空间还有576KB,   而且还是连续的一整块。接下来,任务2和任务3先后进入内存, 它们需要的内存空间分别是224KB 和288KB,   因此紧挨着任务1,给它们分配了两块内 存空间,大小分别是224KB 和288KB,   也就是说,又创建了两个新的用户分区。此时, 在内存中总共有3个任务,连同操作系统,总共占用了960KB 的内存空间。因此,在地址空间的最高端,只剩下一小64KB 的空闲区域,如图4-28 (b)   所示。接下来,任务2运行结束,系统回收了它所占用的内存分区。然后任务4进入内存,它的大小为128KB,  因此就把刚才存放任务2 的那个分区, 一分为二, 一部分用来存放任务4,另一部分是 96KB 的空闲区。接下来,任务1也运行结束,系统回收了它所占用的分区。因此,内存 空间的最后状态如图4-28 (c) 所示。从图中可以看出, 随着系统不断运行,空闲区就变得越来越小,越来越碎了,它们被分隔在内存的不同位置,形成了占用区和空闲区交错在一起的局面。 

嵌入式系统设计师之存储管理_第2张图片

        与固定分区相比,在可变分区当中,分区的个数、位 置和大小都是随着任务的进出而动态变化的,非常灵活。当一个任务要进入内存的时候, 就在空闲的地方创建一个分区,把它装进来;当任务运行结束后,就把它所占用的内存分区给释放掉。这样,就避免了在固定分区当中由于分区的大小不当所造成的内碎片,  从而提高了内存的利用效率。在可变分区的存储管理当中,不会出现内碎片的现象,因为每个分区都是按需分配的,分区的大小正好等于任务的大小,因此不会有内碎片。

        可变分区存储管理的缺点是:可能会存在外碎片。所谓的外碎片,就是在各个占用的分区之间,难以利用的一些空闲分区,这通常是一些比较小的空闲分区。例如, 在图4-28 (c) 当中,对于64KB 这个空闲区,它只能分配给那些不超过64KB 的任务, 如果所有的任务都大于64KB  的话,那么它就用不上了,变成了一个外碎片。另外,这种可变分区的办法,使得内存的分配、回收和管理变得更加复杂了。

3、内存保护

        在嵌入式微处理器当中,存储管理单元 (Memory    Management     Unit,MMU) 了一种内存保护的硬件机制。操作系统通常利用 MMU来实现系统内核与应用程序的隔离,以及应用程序与应用程序之间的隔离。这样可以防止应用程序去破坏操作系统和其他应用程序的代码和数据,防止应用程序对硬件的直接访问。内存保护包含两个方面的内容: 一是防止地址越界,每个应用程序都有自己独立的地址空间,当一个应用程序要访问某个内存单元时,由硬件检查该地址是否在限定的地址空间内,如果不 是的话就要进行地址越界处理;二是防止操作越权,对于允许多个应用程序共享的某 块存储区域,每个应用程序都有自己的访问权限,如果违反了权限规定,则要进行操作越权处理。

三、地址重定位 (II)

1、逻辑地址

        逻辑地址也叫相对地址或虚地址。用户的程序经过汇编或编译后形成目标代码,而目标代码通常采用的就是相对地址的形式。其首地址为0,其余指 令中的地址都是相对于这个首地址来编址的。

2、物理地址 

        物理地址也叫内存地址、绝对地址或实地址。 把系统内存分割成很多个大小相等的存储单元,如字节或字,每个单元给它一个编号,这个编号就称为物理地址。 物理地址的集合就称为物理地址空间,或者内存地址空间,它是一个 一维的线性空间。例如,假设内存大小为256MB,  那么它的内存地址空间是从0x0  Ox0FFFFFFF。

        为了保证 CPU 在执行指令的时候,可以正确地访问内存单元,需要将用户程序中的逻辑地址转换为运行时由机器直接寻址的物理地址,这个过程就称为地址映射。地址映射是由存储管理单元MMU来完成的

 嵌入式系统设计师之存储管理_第3张图片

        当一条指令在 CPU 当中执行时,它可能需要去访问内存,因此就发送一个逻辑地址给 MMU,MMU负责把这个逻辑地址转换为相应的物理地址,并根据这个物理地址去访问内存。

        

        图4-32 是一个地址映射的例子。图4-32 (a) 是一段简单的C 语言程序,首先定义了两个整型变量x  y,  然后把x 赋值为5,再把x 加上3并赋值给 y 经过编译链接后, 得到的指令形式类似于图4-32 (b) 。 在它的逻辑地址空间中,首地址为0,代码存放在 起始地址为100的地方,数据则放在起始地址为200的地方,第一条指令str5[200], 常量5保存到地址为200的地方,这条指令对应于源代码中的x=5 也就是说,经过编译和链接后,像 x y 这样的符号变量都会被具体的逻辑地址所代替, x  的逻辑地址是200,y 的逻辑地址是204。接下来的三条指令,对应于源代码中的 y=x+3。

嵌入式系统设计师之存储管理_第4张图片

         假设这个程序即将开始运行,先要把它装入到内存。如果系统采用的是固定分区的存储管理方法,这个程序将被装入到某个空闲分区当中。假设该分区的起始地址是1000, 如图4-32 (c)  所示,这是装进去以后的情形。由于程序已经在内存当中,所以现在的地址都是实际的物理地址。但问题立刻就出现了,在程序指令中,它们所采用的地址,还是刚才的逻辑地址,如200、204,但是 CPU 在执行指令的时候,是按照物理地址来进行 的,因此会将200 和204 当成是内存的物理地址去访问,从而导致出错,因为在物理地 址为200 和204 的地方,存放的很可能是操作系统的内容,如果对这些内容进行读写操 作,可能会对操作系统造成破坏,从而引起系统的崩溃。其实这里的本意并非如此,它实际上是想去访问变量 x  y,   但它们在内存当中的地址是1200和1204,而不是200 和204。另外,如果这个程序被装入另外一个分区,起始地址不是1000,那么所有的这些地址又都会发生变化。     

        因此,为了保证CPU 在执行指令时可以正确地访问存储单元,系统在装入一个用户程序后,必须对它进行地址映射,把程序当中的逻辑地址转换为物理地址,然后才能运行。地址映射主要有两种方式:静态地址映射和动态地址映射。

四、页式存储管理 (II)

        它的基本出发点是打破存储分配的连续性,使一个程序的逻辑地址空间可以分布在若干个离散的内存块上,从而达到充分利用内存,提高内存利用率的目的。

        页式存储管理的基本思路是: 一方面,把物理内存划分为许多个固定大小的内存块, 称为物理页面 (physical    page),  或页框 (page    frame)。另一方面,把逻辑地址空间也划 分为大小相同的块,称为逻辑页面 (logical page),或简称为页面 (page)。 页面的大小要 求是2”,一般在512B 到 8KB 之间。当一个用户程序被装入内存时,不是以整个程序为 单位,把它存放在一整块连续的区域,而是以页面为单位来进行分配。对于一个大小为 N 个页面的程序,需要有N 个空闲的物理页面把它装进来,当然,这些物理页面不一定是连续的。

五、虚拟存储管理(II)

        在操作系统的支持下, MMU 还提供虚拟存储功能。即使一个任务所需要的内存空间

超过了系统所能提供的内存空间,也能够正常运行。

1、程序局部性原理

        程序的局部性原理:指的是程序在执行过程中的一个较短时期内,它所执行的指令和访问的存储空间,分别局限在一定的区域内。这可以表现在时间和空间两个方面。

        ●时间局限性: 一条指令的一次执行和下一次执行, 一个数据的一次访问和下一次 访问,都集中在一个较短的时期内

        ●空间局限性:如果程序执行了某条指令,则它相邻的几条指令也可能马上被执行; 如果程序访问了某个数据,则它相邻的几个数据也可能马上被访问。

        程序局部性原理的具体表现:

        ●程序在执行时,大部分都是顺序执行的指令,只有少部分是跳转和函数调用指令。 而顺序执行就意味着在一小段时间内, CPU 所执行的若干条指令在地址空间当中 是连续的,集中在一个很小的区域内;

        ●程序中存在着相当多的循环结构,在这些循环结构的循环体当中,只有少量的指 令,它们会被多次地执行;

        ●程序中存在着相当多对一定数据结构的操作,这些操作往往局限在比较小的范围 内。例如数组操作,数组是连续分配的,各个数组元素之间是相邻的。

        程序的局部性原理说明,在一个程序的运行过程中,在某一段时间内,这个程序只 有一小部分的内容是处于活跃状态,正在被使用,而其他的大部分内容可能都处于一种 休眠状态,没有在使用,而这就意味着,从理论上来说,虚拟存储技术能够实现且能产 生较好的效果。实际上,在很多地方都已经用到了程序的局部性原理。例如,页式地址映射当中的 TLB CPU 里面的 Cache 等,都是基于局部性原理。

2、虚拟页式存储管理

        虚拟页式存储管理:在页式存储管理的基础上,增加了请求调页和页面置换的功 能。它的基本思路是:当一个用户程序需要调入内存去运行时,不是将这个程序的所有页面都装入内存,而是只装入部分的页面,就可以启动这个程序去运行。在运行过程中, 如果发现要执行的指令或者要访问的数据不在内存当中,就向系统发出缺页中断请求,然后系统在处理这个中断请求时,就会将保存在外存中的相应页面调入内存,从而使该程序能够继续运行。

        在单纯的页式存储管理当中,页表的功能就是把逻辑页面号映射为相应的物理页面号。因此,对于每一个页表项来说,它只需要两个信息, 一个是逻辑页面号,另一个是 与之相对应的物理页面号。但是在虚拟页式存储管理当中,除了这两个信息之外,还要增加其他的一些信息,包括驻留位、保护位、修改位和访问位。

        ●驻留位:表示这个页面现在是在内存还是在外存。如果这一位等于1,表示页面 位于内存中,即页表项是有效的;如果这一位等于0,表示页面还在外存中,即 页表项是无效的。如果此时去访问,将会导致缺页中断;

        ●保护位:表示允许对这个页面做何种类型的访问,如只读、可读写、可执行等;

        ●修改位:表示这个页面是否曾经被修改过。如果该页面的内容被修改过, CPU  自动地把这一位的值设置为1;

        ●访问位:如果这个页面曾经被访问过,包括读操作、写操作等,那么这一位就会被硬件设置为1。这个信息主要是用在页面置换算法当中。

3、页面置换算法

        如前所述,系统在处理缺页中断时,需要调入新的页面。如果此时内存已满,就要采用某种页面置换算法,从内存中选择某一个页面,把它置换出去。最简单的做法是随机地进行选择,但这显然不是一个令人满意的方法。例如,假设随机选中的是一个经常要访问的页面,当它被置换出去后,可能马上又得把它换进来,而这种换进换出是需要开销的。所以,对于一个好的页面置换算法来说,它应该尽可能地减少页面的换进换出次数,或者说,尽可能地减少缺页中断的次数,从而减少系统在这方面的开销。具体来说,它应该把那些将来不再使用的,或者短期内较少使用的页面换出去,而把那些经常 要访问的页面保留下来。不过,在通常的情形下,我们不可能完全做到这一点。因此,通常的做法,就是在程序局部性原理的指导下,依据过去的统计数据来对将来进行预测。

        常用的页面置换算法包括:最优页面置换算法、最近最久未使用算法、最不常用算法、先进先出算法和时钟页面置换算法。

1、最优页面置换算法 

        最优页面置换算法的基本思路是: 当一个缺页中断发生时,对于内存中的每一个逻辑页面,计算在它的下一次访问之前,还要等待多长的时间,然后从中选择等待时间最长的那个,来作为被置换的页面。 从算法本身来看,这的确是一个最优算法,它能保证缺页中断的发生次数是最少的。但是,这个算法只是一种理想化的算法,在实际的系统中是无法实现的,因为操作系统无从知道,每一个页面还要等待多长的时间,才会被再次地访问。因此,该算法通常是用作其他算法的性能评价依据

2、最近最久未使用算法 

        最近最久未使用算法的基本思路是:当一个缺页中断发生时,从内存中选择最近最 久没有被使用的那个页面,把它淘汰出局。 LRU  算法实质上是对最优页面置换算法的个近似,它的理论依据就是程序的局部性原理。也就是说,如果在最近一小段时间内,某些页面被频繁地访问,那么在将来的一小段时间内,这些页面可能会再次被频繁地访问。反之,如果在过去一段时间内,某些页面长时间没有被访问,那么在将来,它们还可能会长时间得不到访问。 OPT算法寻找的是将来长时间内得不到访问的那个页面,而LRU 算法寻找的是过去长时间内没有被访问的那个页面。

        LRU 算法需要记录各个页面在使用时间上的先后顺序,因此系统的开销比较大。在具体实现上,主要有两种方法。

        ●链表法:由系统来维护一个页面链表,把最近刚刚使用过的页面作为首结点,把 最久没有使用的页面作为尾结点。在每一次访问内存的时候,找到相应的逻辑页 面,把它从链表中摘下来,移动到链表的开头,成为新的首结点。然后,当发生 缺页中断的时候,总是淘汰链表末尾的那个页面,因为它就是最久未使用的。

        ●栈方法:由系统来设置一个页面栈,每当访问一个逻辑页面时,就把相应的页面号压入到栈顶,然后考察栈内是否有与之相同的页面号,如果有就把它抽出来。当需要淘汰一个页面时,总是选择栈底的页面,因为它就是最久未使用的。

3、最不常用算法

        最不常用算法的基本思路是:当一个缺页中断发生时,选择访问次数最少的那个页面,把它淘汰出局。在具体实现上,需要对每一个页面都设置一个访问计数器。每当一 个页面被访问时,就把它的计数器的值加1。然后在发生缺页中断的时候,选择计数值最 小的那个页面,把它置换出去。LFU 算法和LRU 算法类似,都是基于程序的局部性原理 通过分析过去的访问情况来预测将来的访问情况。两者的区别在于: LRU 考察的是访问 的时间间隔,即对于每一个页面,从它的上一次访问到现在,经历了多长的时间。而 LFU考察的是访问的频度,即对于每一个页面,在最近一段时间内,它总共被访问了多少次。

4、先进先出算法

        先进先出算法的基本思路是:选择在内存中驻留时间最长的页面,把它淘汰出局。 在具体实现上,系统会维护一个链表,里面记录了内存当中的所有页面。从链表的排列顺序来看,链首页面的驻留时间最长,链尾页面的驻留时间最短。当发生一个缺页中断时,把链首的页面淘汰出局,然后把新来的页面添加到链表的末尾。FIFO 算法的性能比较差,因为它每次淘汰的都是驻留时间最长的页面,而这样的页面并不一定就是不受欢 迎的页面,相反,可能是一些经常要访问的页面。如果现在把它们置换出去,将来可能还要把它们再换回来。

5、时钟页面置换算法 

        FIFO 算法的缺点在于:它是根据页面的驻留时间来做出选择,而没有去考虑页面的访问情况。时钟页面置换算法对此进行了改进,把页面的访问情况也作为淘汰页面的一 个依据。Clock 算法需要用到页表项当中的访问位。当一个页面在内存当中的时候,如果它被访问了(不管是读操作还是写操作),那么它的访问位就会被CPU 设置为1。算法的基本思路是:把各个页面组织成环形链表的形式,类似于一个时钟的表面,然后把指针 指向最古老的那个页面,即最先进来的那个页面。当发生一个缺页中断的时候,考察指 针所指向的那个页面。如果它的访问位的值等于0,说明这个页面的驻留时间最长,而且 没有被访问过,因此理所当然地把它淘汰出局。如果访问位的值等于1,这说明这个页面 的驻留时间虽然是最长的,但是在这一段时间内,它曾经被访问过了。因此,在这种情 形下,就暂不淘汰这个页面,但要把它的访问位的值设置为0。然后把指针往下移动一格, 去考察下一个页面。就这样一直进展下去,直到发现某一个页面,它的访问位的值等于0,因此就把它淘汰掉。

你可能感兴趣的:(嵌入式系统,存储管理)