RT-Thread 内存管理

2023-12-05

在计算机系统中,通常存储空间可以分为两种: 内部存储空间和外部存储空间。

内部存储空间通常访问速度比较快,能够按照变量地址随机访问,也就是我们通常所说的RAM(随机存储器),可以把它理解为电脑的内存。

而外部存储空间内所保存的内容相对来说比较固定,及时掉电后数据也不会丢失,这就是通常讲的ROM(只读存储器),可以把它理解为电脑的硬盘。

计算机系统中,变量、中间数据一般存放在RAM中,只有在实际使用时才将它们从RAM调入到CPU中进行运算。

一些数据需要的内存大小需要在程序运行过程中根据实际情况确定,这就要求系统具有对内存空间进行动态管理的能力。
用户需要一段内存空间时,向系统申请,系统选择一段合适的内存空间分配给用户,用户使用完毕后,再释放回系统,以便系统将该段内存空间回收再利用。

内存管理的功能特点

由于实时系统中对时间的要求非常严格,内存管理往往要比通用操作系统要求苛刻得多:

  1. 分配内存的时间必须是确定的。一般内存管理算法是根据需要存储的数据的长度在内存在去寻找一个与这段数据相适应的空闲内存块,然后将数据存储在里面。而寻找这样一个空闲块所耗费的时间是不确定的,因此对于实时系统来说,这是不可接受的,实时系统必须要保证内存块的分配过程在可预测的确定时间内完成,否则实时任务对外部事件的响应也将变得不可确定。
  2. 随着内存不断被分配和释放,整个内存区域会产生越来越多的碎片(因为在使用过程中,申请了一些内存,其中一些释放了,导致内存空间中存在一些小的内存块,它们地址不连续,不能够作为一整块的大内存分配出去)。系统中还有足够的空闲内存,但因为它们地址并非连续,不能组成一块连续的完整内存块,会使得程序不能申请到大的内存。
    对于通用系统而言,这种不恰当的内存分配算法可以通过重新启动系统来解决(每个月或者数个月进行一次),但是对于那些常年不间断地工作于野外的嵌入式系统来说,就变得让人无法接受了。
  3. 嵌入式系统的资源环境也是不尽相同,有些系统的资源比较紧张,只有数十KB的内存可供分配,而有些系统则存在数MB的内存,如何为这些不同的系统,选择适合它们的高效率的内存分配算法,就将变得复杂化。

RT-Thread操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法。总体上可分为两类:内存堆管理与内存池管理,而内存堆管理又根据具体内存设备划分为三种情况:

  1. 第一种是针对小内存块的分配管理(小内存管理算法)。
  2. 第二种是针对大内存块的分配管理(slab管理算法)。
  3. 第三种是针对多内存堆的分配情况(memheap管理算法)。

RT-Thread程序内存分布

一般MCU包含的程序存储空间有:片内Flash与片内RAM,RAM相当于内存,Flash相当于硬盘。
编译器会将一个程序分类为好几个部分,分别存储在MCU不同的存储区。

keil工程在编译完之后,会有相应的程序所占用的空间提示信息。

linking...
Program Size: Code=48008 RO-data=5660 RW-data=604 ZI-data=2124
After Build - User command \#1: fromelf --bin.\\build\\rtthread-stm32.axf--output rtthread.bin
".\\build\\rtthread-stm32.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:07
  1. Code:代码段,存放程序的代码部分。
  2. RO-data:只读数据段,存放程序中定义的常量。
  3. RW-data:读写数据段,存放初始化为非0值的全局变量。
  4. ZI-data:0数据段,存放未初始化的全局变量以及初始化为0的全局变量。

编译完工程会生成一个.map文件,该文件说明了各个函数占用的尺寸和地址,在文件的最后几行也说明了上面几个字段的关系:

Total RO Size (Code + RO Data) 53668 ( 52.41kB)
Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB)
Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB)
  1. RO Size,表示程序占用Flash空间的大小。
  2. RW Size,表示运行时占用RAM的大小。
  3. ROM Size,表示程序烧写时占用Flash的大小。

程序运行之前,需要有文件实体被烧录到STM32的Flash中,一般是bin或hex文件,该被烧录文件称为可执行映像文件。

在这里插入图片描述
左边:可执行映像文件烧录到STM32后的内存分布,它包含RO和RW段两个部分,其中 RO 段中保存了 Code、RO-data 的数据,RW 段保存了 RW-data 的数据,由于ZI-data都是0,所以未包含在映像文件中。

STM32在上电启动之后默认从Flash启动,启动之后会将RW段中的Rw-data(初始化的全局变量)搬运到RAM中,但不会搬运R0段,即CPU的执行代码从Flash中读取,另外根据编译器给出的ZI地址和大小分配出ZI段,并将这块RAM区域清零。

其中动态内存堆为未使用的RAM空间,应用程序申请和释放的空间块都来自该空间。

rt_uint8_t *msg_ptr;
msg_ptr = (rt_uint8_t *)rt_malloc(128);
rt_memset(msg_ptr,0,128);

内存堆管理

RT-Thread将“ZI段结尾处”到内存尾部的空间用作内存堆。

内存堆可以在当前资源满足的情况下,根据用户的需求分配任意大小的内存块。而当用户不需要再使用这些内存块时,又可以释放回堆中供其它应用分配使用。

小内存管理算法主要针对系统资源比较少,一般用于小于2MB内存空间的系统。

这几类内存堆管理算法在系统运行时只能选择其中之一或者完全不使用内存堆管理,它们提供给应用程序的API接口完全相同。

小内存管理算法

小内存管理算法是一个简单的内存分配算法。
初始时,它是一块大的内存。当需要分配内存块时,将这个大的空闲块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。
通过这个头把使用块与空闲块用双向链表的方式链接起来。
在这里插入图片描述
每个内存块(不管是已分配的内存块还是空闲的内存块)都包含一个数据头,其中包括:

  1. magic:变数(或成为幻数),它会被初始化成为0x1ea0,用于标记这个内存块是一个内存管理用的内存数据块。变数不仅仅用于标识这个数据块是一个内存管理用的内存数据块,实质也是一个内存保护字:如果这个区域被改写,那么也就意味着这块内存块被非法改写(正常情况下只有内存管理器才会去碰这块内存)。
  2. used:指示出当前内存块是否已经分配。

如下图所示的内存分配情况,空闲链表指针 lfree 初始指向 32 字节的内存块。当用户线程要再分配一个 64 字节的内存块时,但此 lfree 指针指向的内存块只有 32 字节并不能满足要求,内存管理器会继续寻找下一内存块,当找到再下一块内存块,128 字节时,它满足分配的要求。因为这个内存块比较大,分配器将把此内存块进行拆分,余下的内存块(52 字节)继续留在 lfree 链表中,如下图分配 64 字节后的链表结构所示。

在这里插入图片描述
每次分配内存块前,都会留出12字节数据头用于magic、used信息及链表节点使用。返回给应用的地址实际上是这块内存块12字节以后的地址。

内存堆配置和初始化

在使用内存堆时,必须要在系统初始化的时候进行堆的初始化,可以通过下面的函数接口完成:

void rt_system_heap_init(void *begin_addr, void *end_addr)
{
	rt_ubase_t begin_align = RT_ALIGN((rt_ubase_t)begin_addr, RT_ALIGN_SIZE);
    rt_ubase_t end_align   = RT_ALIGN_DOWN((rt_ubase_t)end_addr, RT_ALIGN_SIZE);

    RT_ASSERT(end_align > begin_align);

    /* Initialize system memory heap */
    _MEM_INIT("heap", begin_addr, end_align - begin_align);
    /* Initialize multi thread contention lock */
    _heap_lock_init();
}

内存池

内存堆管理器可以分配任意大小的内存块,非常灵活和方便。但也存在明显的缺点,一是分配效率不高,每次分配时,都要查找空闲内存块;二是容易产生内存碎片。

为了提高内存分配的效率,并且避免内存碎片,提供了另外一种内存管理方法:内存池(Memory Pool)。
内存池是一种内存分配方式,用于分配大量大小相同的小内存块,它可以极大加快内存分配与释放的速度,且能尽量避免内存碎片化。

内存池控制块
内存池控制块是操作系统用于管理内存池的一个数据结构,它会存放内存池的一些信息,例如内存池数据区域开始地址,内存块大小和内存块列表等,也包含内存块与内存块之间连接用的链表结构,因内存块不可用而挂起的线程等待事件集合等。。

内存块分配机制
内存池在创建时先向系统申请一大块内存,然后分成同样大小的多个小内存块,小内存块直接通过链表连接起来(此链表也称为空闲链表)。每次分配的时候,从空闲链表中取出链头上第一个内存块,提供给申请者。从下图中可以看到,物理内存中允许存在多个大小不同的内存池,每一个内存池又由多个空闲内存块组成,内核用它们来进行内存管理。当一个内存池对象被创建时,内存池对象就被分配给了一个内存池控制块,内存控制块的参数包括内存池名,内存缓冲区,内存块大小,块数以及一个等待线程队列。

内核负责给内存池分配内存池控制块,它同时也接收用户线程的分配内存块申请,当获得这些信息后,内核就可以从内存池中为内存池分配内存。内存池一旦初始化完成,内部的内存块大小将不能再做调整。

每一个内存池对象由上述结构组成,其中 suspend_thread 形成了一个申请线程等待列表,即当内存池中无可用内存块,并且申请线程允许等待时,申请线程将挂起在 suspend_thread 链表上。

内存池控制块是一个结构体,其中含有内存池相关的重要参数,在内存池各种状态间起到纽带的作用。内存池的相关接口如下图所示,对内存池的操作包含:创建 / 初始化内存池、申请内存块、释放内存块、删除 / 脱离内存池,但不是所有的内存池都会被删除,这与设计者的需求相关,但是使用完的内存块都应该被释放。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

RT-Thread 内存管理 的相关文章

  • 算法题-简单系列-02-合并两个排序的算法

    文章目录 1 题目 1 1 迭代 1 题目 输入两个递增的链表 单个链表的长度为n 合并这两个链表并使新链表中的节点仍然是递增排序的 1 1 迭代 设置result为哑结点 放置于新链表之前 最后返回的就是result next 设置cur

随机推荐

  • C/C++ 谓词 lambda表达式

    文章目录 前言 1 引例 2 谓词的含义 2 1 谓词运用 总结 前言 最近看lambda相关知识点 发现这个概念比较难以理解 看了几遍 可能是第一次正式接触STL的原因 对标准库的泛型编程理解不够深刻 这篇博客就写一下lambda的相关
  • centos7 设置静态ip

    文章目录 设置VMware 主机设置 centos7 设置 设置VMware 主机设置 cen
  • 算法题-简单系列-05-两个链表的第一个公共结点

    文章目录 1 题目 1 1 思路1 循环遍历 1 题目 输入两个无环的单向链表 找出它们的第一个公共结点 如果没有公共节点则返回空 1 1 思路1 循环遍历 使用两个指针N1 N2 一个从链表1的头节点开始遍历 我们记为N1 一个从链表2的
  • 计算机组成与设计:硬件/软件接口,第二章详细梳理,附思维导图

    文章目录 二 指令 计算机的语言 章节导图 一 MIPS概述 计算机的组成 MIPS的设计思想 MIPS 32中的通用寄存器 二 三类汇编指令
  • JAVA打印日志规范实践

    前言 日常开发 日志打印尤为重要 记录程序运行情况 方便快速定位问题 一份实用的日志打印规范能极大的帮助我们日常开发 一 日志介绍 1 弄懂日志 SpringBoot启动日志 2 什么是日志 日志 维基百科中对其的定义是 一个或多个由服务器
  • 当班主任应该具备什么条件

    当班主任需要具备什么条件 这个问题其实可以从多个角度来回答 下面我列举一些我认为比较重要的条件 责任心和爱心 班主任的职责是关注学生的成长 帮助学生解决学习和生活中的问题 这需要班主任具备强烈的责任心和爱心 只有真正关心学生的成长和发展 才
  • 算法题-简单系列-01-链表反转

    文章目录 1 题目 1 1 使用栈解决 1 2 反转链表 1 题目 给定一个单链表的头结点pHead 该头节点是有值的 比如在下图 它的val是1 长度为n 反转该链表后 返回新链表的表头 如当输入链表 1 2 3 时 经反转后 原链表变为
  • 数字化转型浪潮中,施耐德电气如何用技术革新引领未来?

    作为一家187年的老牌企业 施耐德电气不仅见证了科技的演进 也是数字化转型潮流中的先行者 在近日的施耐德电气数字化战略暨软件创新沟通会上 施耐德电气全球执行副总裁 首席数字官 Peter Weckesser 施耐德电气副总裁 数字化服务业务
  • 零基础上手,秒识别检测,IDEA研究院发布全新T-Rex模型

    目标检测作为当前计算机视觉落地的热点技术之一 已被广泛应用于自动驾驶 智慧园区 工业检测和卫星遥感等场景 开发者在研究相关目标检测技术时 通常需熟练掌握图像目标检测框架 如通用目标检测框架 YOLO 系列 旋转目标检测框架 R3Det 等技
  • 什么是野指针?

    什么是野指针 指针指向了非法的地址空间 称之为野指针
  • Pytorch深度强化学习1-5:详解蒙特卡洛强化学习原理

    目录 0 专栏介绍 1 蒙特卡洛强化学习 2 策略评估原理 3 策略改进原理 3 1 同轨蒙特卡洛强化学习 3 2 离轨蒙特卡洛强化学习 0 专栏介绍 本专栏重点介绍强化学习技术的数学原理 并且 采用Pytorch框架对常见的强化学习算法
  • 云原生之深入解析如何限制Kubernetes集群中文件描述符与线程数量

    一 背景 linux 中为了防止进程恶意使用资源 系统使用 ulimit 来限制进程的资源使用情况 包括文件描述符 线程数 内存大小等 同样地在容器化场景中 需要限制其系统资源的使用量 ulimit docker 默认支持 ulimit 设
  • 算法题-简单系列-06-删除有序链表中重复的元素

    文章目录 1 题目 1 1 循环遍历 1 题目 1 1 循环遍历 既然连续相同的元素只留下一个 我们留下哪一个最好呢 当然是遇到的第一个元素了 因为第一个元素直接就与前面的链表节点连接好了 前面就不用管了 只需要跳过后面重复的元素 连接第一
  • (附源码)springboot网上书城小程序 计算机毕设38707

    目 录 摘要 1 绪论 1 1 研究背景及意义 1 2
  • Go 程序编译过程(基于 Go1.21)

    版本说明 Go 1 21 官方文档 Go 语言官方文档详细阐述了 Go 语言编译器的具体执行过程 Go1 21 版本可以看这个 https github com golang go tree release branch go1 21 sr
  • (附源码)python兴农购物网站系统 计算机毕设38256

    Django兴农购物网站系统 摘 要 助农工作是当前我国全面建成小康社会的重点工作 由于我国农村地域广大 贫困人口多 区域差异大 因此 不同区域的扶贫方法也是不一样的 近年来 随着网络的普及 许多农村地区物产丰富 但由于销售渠道不畅等原因
  • 如何导出iPhone喜马拉雅下载的音频

    如何导出iPhone喜马拉雅下载的音频 坑 网上没有啥正规的教程 坑
  • JDK1.8在LINUX下安装教程

    在Linux下安装JDK 1 8是为了开发和运行Java应用程序的必要步骤 以下是简明的JDK 1 8安装教程 下载JDK 1 8 首先 您需要从官方网站 例如Oracle或OpenJDK 下载JDK 1 8的安装包 请确保选择适合您Lin
  • 常见的降维库有哪些?

    常见的降维库有哪些 计算生物学领域 做基因数据分析的用得比较多 发的paper比较好 1 sklearn库里的ISOMAP and umap 2 Scanpy 库 这个最猛 scanpy tl pcascanpy tl tsne scanp
  • RT-Thread 内存管理

    在计算机系统中 通常存储空间可以分为两种 内部存储空间和外部存储空间 内部存储空间通常访问速度比较快 能够按照变量地址随机访问 也就是我们通常所说的RAM 随机存储器 可以把它理解为电脑的内存 而外部存储空间内所保存的内容相对来说比较固定