嵌入式实时操作系统的设计与开发(七)

内存管理机制

内存管理就是把物理的存储资源用一定的规则和手段管理起来,以供给操作系统和应用程序使用。
主要的操作:内存的分配和内存的回收。
内存的利用率、分配回收的效率和稳定性成为了评价内存管理模块的主要依据。
内存分配又包括静态和动态两种:

  • 静态分配的方式很简单,但必须事先知道程序的运行全貌(代码大小、数据大小等),这样的系统缺乏灵活性。
  • 动态分配的方式比较灵活,也是常见的分配方式,但动态分配会耗费一些内存管理过程中额外的空间和时间开销,所以在内存特别小、实时性要求特别高的情况下还是会采用静态分配的方法。

当要分配6B的内存时,其实操作系统分配了8B,这样就会浪费2B的内存,这就是内存碎片。当多次使用malloc()后,可能会出现即使系统中的累积空闲内存远远大于1MB,但系统仍然无法分配要申请的连续的1MB空间,因为经过多次malloc()后,这1MB的内存已经被分得七零八散,这样零散的但又不能供用户使用的内存块称为外碎片。

  • 内部碎片就是已经被分配出去(明确指出属于哪个线程),却不能被利用的内存空间。
  • 外部碎片就是没有被分配出去(不属于任何线程),但由于太小了无法分配给申请内存空间的新线程的内存空闲区域。

内碎片和外碎片是内存管理的一对矛盾,减少外碎片就可能增加内碎片,除非增加很多限制条件,同时外碎片没法完全避免,只是多少的问题。
伙伴系统就是减少外碎片的内存管理算法,但是也只能将外碎片降低到最大1/2总内存大小,但是该算法会产生很大的内碎片,因此伙伴系统只能在特定场合和特定应用中使用。

主流内存管理机制

对于一些应用简单、任务数目事先确定的嵌入式系统或强实时系统,为了减少内存分配在时间上可能带来的不确定性,可采用静态内存分配方式。
静态内存分配方式在系统启动时,为系统中所有任务都分配了内存空间,系统运行过程中不会有新的内存请求,因此,操作系统不需要进行专门的内存管理操作。这种系统使用内存的效率比较低,只适合于应用简单、任务数目事先确定的嵌入式系统或强实时系统。

大多数系统都使用动态内存管理机制,而当前主流的内存管理机制又分为固定大小存储管理和可变大小存储管理。

  1. 固定大小存储管理
    固定大小存储管理方式中,内存是由一段连续的内存构成的,这段内存被分为多个大小一样的固定块,这种管理方式可以很好地解决外部碎片,因为每次分配的都是固定大小的内存,只要有空闲内存,肯定可以分配某一固定大小的空间。
    但此方法无法解决内部碎片,若开发人员只想要20B的内存,它也会分配256B。
  2. 可变大小存储管理
    又分为两种:
    (1)任意大小分配的存储管理。用户需要多少,就分配多少,解决了内碎片问题。该方式实现比较简单,在整个受管理的内存中找到一块大于用户需求的内存块,然后一分为二,一块是用户申请的内存块大小,剩下的一块是空闲的,用于以后的分配。但用户申请内存、释放内存的时间是随机的,而每一次的申请和释放都有可能产生新的空闲内存块,之后系统可能会将已有的空闲块回收、合并成新的空闲块。因此,系统中的空闲内存块数、地址、大小是时刻变化的,所以采用链表来寻找空闲块和合并空闲块。正是因为链表,且链表的内存块大小是不定的,从而内存分配和回收的时间无法确定。
    (2)带固定大小特性的可变大小的存储管理。固定大小,是指大小只能为2的k次幂,又可以分配2i大小的内存,如伙伴系统。

嵌入式系统对内存管理的特殊要求

  1. 内存能快速申请和释放,嵌入式系统的实时性保证要求内存分配过程要尽可能地快,这要求算法简单。
  2. 内存应该各尽其用,要尽可能减少浪费。
    以上两点存在矛盾。

aCoral的内存管理机制

在伙伴系统基础上,采用位图法方式提高内存分配和回收的速度,更能满足系统实时性的需求。
首先,aCoral内存管理分为两级,上一级采用改进的伙伴系统,负责要分配的内存大小,下一级根据上一级确定的大小进行具体物理内存分配。
第一级内存管理总会分配2N大小的内存,第二级采用了固定块和可变大小两种内存管理方式,除内核外,应用程序一般直接使用第一级的伙伴系统。

第一级内存管理算法

伙伴算法及实现上的改进:如果整个可用空间内存由2m个字节组成,那么在系统中对每个大小为2n的内存块建立一个对应的可用块链表。
假设内存地址从(0~2m-1),刚开始,整个2m个字节空间都是可用的。
假如应用程序申请2K个字节的内存空间,如果系统中没有2K大小的内存块时,就把更大的可用内存块分成两部分,最终得到两个大小为2K的内存空间。
当一块分为两块时,这两块就成为彼此的伙伴。
某个时刻两个伙伴都空闲时,又可以合并成一个大的空闲块。
该方式的分配和回收速度快,算法简单。
但此算法存在一些缺点:

  1. 尽管大小为2K内存块回收时只需要搜索2K字节大小的块来判断是否需要合并,但是时间无法确定,因为链表只能顺序搜索。
  2. 内存控制块的组织问题。一般的实现方式是在内存块的开始部分预留出一段内容来作为内存控制块。
    在这里插入图片描述

这样做有一个缺点,若用户分配512B的内存块,内存管理系统经过计算后知道需要的内存块其实为512+控制块的大小(10B)= 522B,因此系统会分配一个1024的内存块。
内存控制块本身也属于内存块的一部分,把控制块单独抽出来,而真正可以的内存块留给用户就可以解决该问题,这就是改进的伙伴系统。
在这里插入图片描述

aCoral伙伴算法的实现思路

首先真正的物理内存被分成了两部分。

  • 一部分被内存控制结构使用,内存初始化函数buddy_init()将逐个初始化这些结构。
  • 剩下的内存是用户可用内存。这些内存被划分为众多基本块,每个基本块的大小可以通过BLOCK_SIZE设置(默认1<<7字节),这样内存的分配和回收都是基于序列的了,换句话说,这些基本内存块是从0到n逐个标记的。在逻辑上,这些内存块被组成了m层,最大层数m层可以通过LEVEL配置(默认14)。

第0层每个内存块的大小为BLOCK_SIZE,第1层每个内存块的大小为2*BLOCK_SIZE,第n层内存块的大小为BLOCK_SIZE<<n。(BLOCK_SIZE x 2n)。

  1. 当需要分配2i个字节大小的内存块时,用公式:log(2i/BLOCK_SIZE)可求出应该从哪一个逻辑层分配内存。
  2. 如果该层有空闲块,即可分配。
  3. 当一这层没有空闲块时,就向上层申请,最终会得到两个空闲的2i字节大小的内存块。

这里需要注意的是为了最终得到对应的物理内存块,这些逻辑的内存块始终是有序号标记的。

以8个基本内存块大小的内存来举例,开始的时候8个基本内存块全空闲(其序号分别是0,…,7),内存初始化时可能将这8个基本内存块注册第3层(Buddy_init(),第0层的基本内存块可能都不能直接使用,用户只感觉到第3层的内存块(该层的内存块大小就是8个基本内存块大小(BLOCKSIZE<<3)))。
此时,若系统申请一个BLOCK_SIZE大小的内存块,根据公式可得到:应该从第0层分配,而这时除了第3层,其余层的内存块都不可用(内存初始化时设定)。那么第0层向第1层申请,第1层向第2层申请,直到第3层。
第3层将唯一的一个内存块分成两个,供第2层使用,第2层取出一个(通常是序号小的)分配给第1层,另外一个标记为空闲。依次向下,0层有了两个空闲块,即基本内存块0、1。
根据基本内存块的序号(0)转换成相应的物理地址返回给调用函数。
内存回收的时候,传入的参数是地址,先把地址转换成序号,再做回收。
回收的同时如果发现伙伴也是空闲,则向上合并成一个大的空闲块,从而减少外碎片。

为了标记每个逻辑内存块的空闲状态和快速找到一个空闲块,每层需要一个内存状态位图块、空闲内存块链表数组、空闲内存块链表头三个结构。
同时,为了回收的效率,还需要为每个基本内存块存储逻辑层信息,即原来从哪一个逻辑层分配。

/*内存控制块*/
typedef struct{
	acoral_32 *free_list[LEVEL]; //空闲内存块链表数组
	acoral_u32 *bitmap[LEVEL]; //内存状态位图块
	acoral_32 free_cur[LEVEL]; //该层空闲内存块链表头
	acoral_u32 num[LEVEL]; //各层基本内存块(BLOCK_SIZE)个数
	acoral_8 level; //伙伴系统的层数
	acoral_u32 start_adr; //伙伴系统管理的内存的起始地址
	acoral_u32 end_adr; //伙伴系统管理的内存的末尾地址
	acoral_u32 block_num; //基本内存块的数量,等于num[0]
	acoral_u32 free_num; //剩余的基本内存块的数量
	acoral_u32 block_size; // 基本内存块的大小
	acoral_spinlock_t lock; //自旋锁
}acoral_block_ctr_t;

内存控制块acoral_block_ctr_t是aCoral进行内存分配和回收过程的关键数据结构,其中的一个重要成员“acoral_u32 *bitmap[LEVEL]”是描述内存块状态的状态位图数组。
bitmap实际上是一个二维数组bitmap[m][n],第一个下标代表位图块所在的层数m,第二个下标代表该层的第n个位图块。
状态位图块数组bitmap[m][n]的每个值是32位,而每一位代表相邻两块内存块,所以每个值可以表示64个内存块的分配情况。
0~(m-1)层中,相邻的两块内存块由空闲位图块中的一位来标识是否空闲,对于第i层,每个空闲块由空闲内存块中的一位来标识是否空闲。
1位只有0和1两种状态,而两块内存块有四种状态:两块都空闲、没有空闲块、只有奇数空闲块、只有偶数空闲块,而正常情况下不存在两块都空闲的状态,因为会合并,所以0~(m-1)层的相邻的两块内存块只有两种状态:没有空闲块,有一块是空闲的。
在这里插入图片描述

虽然通过状态位图块解决了回收时复杂度O(n)的问题,但是没有解决空闲内存块分配问题,即分配内存时如何查找某一层空闲的内存块。
所以通过增加空闲内存块链表数组“acoral_32 *free_list[LEVEL]”来实现分配时O(1)复杂度,同时还可以解决内存控制块占用部分内存块导致的问题。

首先定义了空闲位图块链表头数组“acoral_32 *free_cur[LEVEL]”,该数组元素指向第一个空闲位图块的标号,空闲内存块链表数组free_list[LEVEL]的值指出了下一个空闲位图块。

对于第0层,free_cur[0]等于2,那么读取free_list[0][2]得到下一个空闲位图块,假设其值为4;则读取free_list[0][4],再得到下一个空闲位图块,依次往后,形成一个表示内存状态位图块的表链,这里的2和4表示第0层内存状态位图块的标号,2表示此时标号为2的内存状态位图块中的32位有非0位。

根据前面的描述,只要根据第m层的free_cur[m]找出空闲位图块的标号i,然后读取内存状态位图块bitmap[m][i],再判断首先出现“1”的那一位,并找到该位对应的内存块序号,便可确定该内存块对应的基本内存块标号,最后得到相应的物理地址,返回给用户,并将刚才的“1”设置为“0”,如果此时该内存状态位图块变为0,则更改free_cur[m]=free_lisy[m][i]。

系统回收内存块时,传送的是地址,根据地址可以知道这个内存地址开始地址对应的基本内存块的标号。但如何知道内存块对应的大小呢?不同层分配出去的内存块的起始地址可能一样,这就需要一个数据结构来保存基本内存块i的起始地址所对应的逻辑内存大小,aCoral用最小内存控制块acoral_block_t来保存某个基本内存块的分配情况。

由于标号为奇数的基本内存块肯定是从第0层分配出去的,偶数的基本内存块由非零层分配出去。因此,不用保存其是从第几层分配出去的。
如果基本块的值尚未被分出去,则level的值为-1。
level的值同时也可用来区分内存块位图管理时的两块兄弟内存块的状态,根据前面的描述1~m-1层,

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
《基于嵌入式实时操作系统的程序设计技术.pdf》是一本关于嵌入式实时操作系统(RTOS)程序设计技术的参考书。嵌入式系统是一类专门设计用于特定应用领域的计算机系统,它们通常具有实时性要求和资源受限的特点。 该书首先介绍了嵌入式系统的概念、特点和发展历程,对嵌入式实时操作系统的基本原理进行了详细解释。它涵盖了RTOS的基本概念、任务管理、调度算法、中断处理、进程通信、资源管理等关键技术。 在任务管理方面,该书详细介绍了任务的创建、删除、挂起和恢复等操作,以及任务优先级、任务同步、任务通信等相关内容。调度算法部分包括了常见的调度算法如先来先服务(FCFS)、最短作业优先(SJF)、优先级调度、轮转调度等,并对每种算法的适用场景和实现方式进行了详细阐述。 该书还针对中断处理机制进行了深入讲解,包括中断优先级、中断嵌套和中断服务程序设计等方面的内容。进程通信章节介绍了进程间的消息传递、信号量、互斥锁和邮箱等通信机制。 最后,资源管理方面主要介绍了内存管理、定时器管理和硬件设备驱动等相关技术。书中还提供了丰富的代码示例和实践案例,帮助读者更好地理解和应用所学知识。 总的来说,《基于嵌入式实时操作系统的程序设计技术.pdf》是一本系统全面介绍嵌入式实时操作系统程序设计技术的权威参考书,适合嵌入式系统开发人员和学习者阅读和参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

饼干饼干圆又圆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值