内存管理学习

内存管理

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

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

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

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

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

内存管理的功能特点

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

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

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

  1. 针对小内存的分配管理(小内存管理算法);
  2. 针对大内存的分配管理(slab管理算法);
  3. 针对多内存堆的分配情况(memheap管理算法)

BIN文件与HEX文件

HEX文件是包括地址信息的,而BIN文件格式只包括了数据本身。
在烧写或下载HEX文件的时候,一般不需要用户指定地址,因为HEX文件内部的信息已经包括了地址。
而烧写BIN文件的时候,用户是一定需要指定地址信息的。

BIN文件格式
对二进制文件而言,其实没有“格式”。文件只是包括了纯粹的二进制数据。

程序内存分布

一般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

上面提到的Program Size包含以下几个部分:

  1. Code:代码段,存放程序的代码部分。
  2. RO-data:只读数据段,存放程序中定义的常量。
  3. RW-data:读写数据段,存放初始化为非零值的全局变量。
  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包含了Code及RO-data,表示程序占用Flash空间的大小。
  2. RW Size包含了RW-data及ZI-data,表示运行时占用的RAM的大小。
  3. ROM Size包含了Code、RO-data以及RW-data,表示烧写程序时占用的Flash空间的大小。

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

在这里插入图片描述
左边部分,是可执行映像文件烧录到STM32后的内存分布,它包含RO段和RW段两个部分:

  • 其中RO段中保存了Code、RO-data的数据,
  • RW段保存了RW-data的数据,
  • 由于ZI-data都是0,所以未包含在映像文件中。

STM32在上电启动之后默认从Flash启动,启动之后会将RW段中的RW-data(初始化的全局变量)搬运到RAM中,但不会搬运RO段,即CPU的执行代码,即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);

代码中的msg_ptr指针指向的128字节内存空间位于动态内存堆空间中。

内存堆管理

内存堆管理用于管理一段连续的内存空间。
RT-Thread将ZI段结尾处到内存尾部的空间用作内存堆。

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

为了满足不同的需求,提供了不同的内存管理算法,分别是小内存管理算法,slab管理算法和memheap管理算法。

小内存管理算法主要针对系统资源比较少,一般用于小于2MB内存空间的系统;而slab内存管理算法则主要是在系统资源比较丰富时,提供了一种近似多内存池管理算法的快速算法。
除上述之外,RT-Thread还有一种针对多内存堆的管理算法,即memheap管理算法。memheap方法适用于系统存在多个内存堆的情况它可以将多个内存“粘贴”在一起,形成一个大的内存堆,用户使用起来会非常方便。

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

小内存管理算法

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

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

内存管理的表现主要体现在内存的分配与释放上,小型内存管理算法可以用以下例子体现出来。

在这里插入图片描述

在这里插入图片描述
空闲链表指针lfree初始指向32字节的内存块。
当用户线程要再分配一个64字节的内存块时,但此lfree指针指向的内存块只有32字节并不能满足要求,内存管理器会继续寻找下一内存块,当找到再下一块内存块,128字节时,它满足分配的要求。

因为这个内存块比较大,分配器将把此内存块进行拆分,余下的内存块(52字节)继续留在lfree链表中。

在每次分配内存块前,都会留出12字节数据头用于magic、used信息及链表节点使用。
返回给应用的地址实际上是这块内存块12字节以后的地址,前面的12字节数据头是用户永远不应该碰的部分。

释放时则是相反的过程,但分配器会查看前后相邻的内存块是否空闲,如果空闲则合并成一个大的空闲内存块。

slab管理算法

RT-Thread 的 slab 分配器是在 DragonFly BSD 创始人 Matthew Dillon 实现的slab分配器基础上,针对嵌入式系统优化的内存分配算法。

RT-Thread的slab分配器实现主要是去掉了其中的对象构造及析构过程,只保留了纯粹的缓冲型的内存池算法。

slab分配器会根据对象的大小分成多个区(zone),也可以看成每类对象有一个内存池,如下图所示:
在这里插入图片描述
一个zone的大小在32K到128K字节之间,分配器会在堆初始化时根据堆的大小自动调整。
系统中的zone最多包括72种对象,一次最大能够分配16K的内存空间,如果超出了16K,那么直接从页分配器中分配。

每个zone上分配的内存块大小是固定的,能够分配相同大小内存块的zone会链接在一个链表中,而72种对象的zone链表则放在一个数组(zone_array[])中统一管理。

下面是内存分配器主要的两种操作:
内存分配
假设分配一个32字节的内存,slab内存分配器会先按照32字节的值,从zone array链表头数组中找到相应的zone链表。

如果这个链表是空的,则向页分配器分配一个新的zone,然后从zone中返回第一个空闲内存块。

如果链表非空,则这个zone链表中的第一个zone节点必然有空闲块存在(否则它就不应该放在这个链表中),那么就取相应的空闲块。

如果分配完成后,zone中所有空闲内存块都使用完毕,那么分配器需要把这个zone节点从链表中删除。

内存释放
分配器需要找到内存块所在的zone节点,然后把内存块链接到zone的空闲内存块链表中。
如果此时zone的空闲链表指示出zone的所有内存块都已经释放,则zone是完全空闲的,那么当zone链表中全空闲zone达到一定数目后,系统就会把这个全空闲的zone释放到页面分配其中去。

memheap管理算法

memheap管理算法适用于系统含有多个地址可不连续的内存堆。使用memheap内存管理可以简化系统存在多个内存堆时的使用:当系统中存在多个内存堆的时候,用户只需要在系统初始化时将多个所需的 memheap 初始化,并开启 memheap 功能就可以很方便地把多个 memheap(地址可不连续)粘合起来用于系统的 heap 分配。

memheap工作机制如图所示,首先将多块内存加入memheap_item链表进行粘合。

当分配内存块时,会先从默认内存堆去分配内存,当分配不到时会查找memheap_item链表,尝试从其它的内存堆上分配内存块。应用程序不用关心当前分配的内存块位于哪个内存堆上,就像是在操作一个内存堆。
在这里插入图片描述

内存堆配置和初始化

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

void rt_system_heap_init(void *begin_addr, void *end_addr);

这个函数会把参数begin_addr,end_addr区域的内存空间作为内存堆来使用。

  • begin_addr:堆内存区域起始地址
  • end_addr:堆内存区域结束地址

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

rt_err_t rt_memheap_init(struct rt_memheap *memheap, const char *name, void *start_addr, rt_uint32_t size)

如果有多个不连续的memheap可以多次调用该函数将其初始化加入memheap_item链表。

内存堆的管理方式

对内存堆的操作如下图所示,包含:初始化、申请内存块、释放内存,所有使用完成后的动态内存都应该被释放,以供其它程序的申请使用。

在这里插入图片描述
分配和释放内存块
从内存堆上分配用户指定大小的内存块,函数接口如下:

void *rt_malloc(rt_size_t nbytes);

rt_malloc函数会从系统堆空间中找到合适大小的内存块,然后把内存块可用地址返回给用户。

对rt_malloc的返回值进行判空是非常有必要的。应用程序使用完从内存分配器中申请的内存后,必须及时释放,否则会造成内存泄漏。

内存池

内存堆管理器可以分配任意大小的内存块,非常灵活和方便。
但其也存在明显的缺点:

  1. 分配效率不高,每次分配时,都要空闲内存块查找。
  2. 容易产生内存碎片,为了提高内存分配的效率,并且避免内存碎片。

内存池(Memory Pool)
内存池是一种内存分配方式,用于分配大量大小相同的小内存块,它可以极大地加快内存分配与释放的速度,且能尽量避免内存碎片化。

内存池控制块

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

在RT-Thread实时操作系统中,内存池控制块由结构体struct rt_mempool表示。另外一种C表达方式rt_mp_t,表示的是内存块句柄,在C语言中的实现是指向内存池控制块的指针,详细定义情况以下代码:

struct rt_mempool
{
	struct rt_object parent;
	void *start_address; //内存池数据区域开始地址
	rt_size_t size; //内存池数据区域大小

	rt_size_t block_size; //内存块大小
	rt_uint8_t *block_list; //内存块链表

	/* 内存池数据区域中能够容纳的最大内存块数 */
	rt_size_t block_total_count;
	/* 内存池空闲的内存块数 */
	rt_size_t block_free_count;
	/* 因为内存块不可用而挂起的线程列表 */
	rt_list_t suspend_thread;
	/* 因为内存块不可用而挂起的线程数 */
	rt_size_t suspend_thread_count;
}
typedef struct rt_mempool* rt_mp_t;

内存块分配机制

内存池在创建时先向系统申请一大块内存,然后分成同样大小的多个小内存块,小内存块直接通过链表连接起来(此链表也称为空闲链表)。

每次分配的时候

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/263997.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【原理图PCB专题】原理图图纸锁定/解锁与PCB文件加密方式

在工作中我们会遇到需要冻结原理图进行评审和加密图纸防止被他人盗用的需求。那么在OrCAD Capture中如何对图纸进行锁定与解锁,如何在Allegro中对PCB工程进行加密呢? 原理图锁定与解锁 打开原理图,在图纸中单击右键,选择lock/unlock就可以进行锁定与解锁。 锁定时图纸图…

PostGIS学习教程十四:更多的空间连接

PostGIS学习教程十四:更多的空间连接 在上一节中,我们看到了ST_Centroid(geometry)和ST_Union([geometry])函数,以及一些简单的示例。在本节中,我们将用它们做一些更详细的事情。 提示:写完文章后,目录可以…

OCC:第一个程序,对话框中显示一个BOX

1. OCC库的获取 从github上获取 gitgithub.com:tpaviot/oce.git,自己编译官网获取二进制包(获取下来的只有release 版本的,而且VS版本不一定适合自己)官网源码,然后自己编译(稍微折腾点,建议按…

带大家做一个,易上手的家常辣椒炒肉

先拿一块猪肉泡水解冻 然后 拿四个螺丝椒 螺丝椒切片 放入四个干辣椒 猪肉切片 三瓣左右蒜 如下图大小的一块姜 姜蒜切小块 将辣椒单独倒入锅中 翻炒出辣味 闻到辣味后将辣椒捞出 这里千万不要洗锅不然就把辣味洗掉了 直接起锅烧油 下入肉片翻炒 猪肉变色后 下入姜蒜…

UG阵列面、阵列集合特征和阵列特征的区别

阵列面 对面进行阵列,当实体中被切除特征的时候可以使用阵列面,当这个命令去阵列一个实体的时候,阵列的是一个片体,优点是速度快,缺点是功能较简单; 阵列几何特征 对实体进行阵列,可以一次性选…

Linux 一键部署二进制Gitea

gitea 前言 Gitea 是一个轻量级的 DevOps 平台软件。从开发计划到产品成型的整个软件生命周期,他都能够高效而轻松的帮助团队和开发者。包括 Git 托管、代码审查、团队协作、软件包注册和 CI/CD。它与 GitHub、Bitbucket 和 GitLab 等比较类似。 Gitea 最初是从 Gogs 分支而来…

数据结构 | 东北大学厦门大学期末试卷查漏补缺

Prim变型算法(不会) 有人给出求解最小生成树的另外一种算法:将连通图中的边按其权值从大到小顺序逐个删除直至不可再删,删除要遵循的原则是:保证在删除该边后各个顶点之间应该是连通的。请问该算法是正确的吗&#xf…

ElasticSearch 的基础概念与入门使用

ElasticSearch 的基础概念与入门使用 前言 elasticsearch 是一款非常强大的开源搜索引擎,具备非常多强大的功能,可以帮助我们从海量的数据中快速找到需要的内容。 例如: 在 Github 中搜索代码 在电商网站搜索商品 在 Google 搜索答案 …

过采样技术基本原理

本文介绍过采样技术基本原理。 过采样技术在ADC信号采集过程中使用还是比较多的。某些使用场景下,对采样速度要求并不是那么高(或ADC采样速度过剩),但是想要获取较高的分辨率,就会用到这种技术,如针对温度…

设计模式:循序渐进走入工厂模式

文章目录 前言一、引入二、简单工厂模式1.实现2.优缺点3.扩展 三、工厂方法模式1.实现2.优缺点 四、抽象工厂模式1.实现2.优缺点3.使用场景 五、模式扩展六、JDK源码解析总结 前言 软件设计模式之工厂模式。 一、引入 需求:设计一个咖啡店点餐系统。 设计一个咖啡类…

在MongoDB中使用数组字段和子文档字段进行索引

本文主要介绍在MongoDB使用数组字段和子文档字段进行索引。 目录 MongoDB的高级索引一、索引数组字段二、索引子文档字段 MongoDB的高级索引 MongoDB是一个面向文档的NoSQL数据库,它提供了丰富的索引功能来加快查询性能。除了常规的单字段索引之外,Mong…

只更新软件,座椅为何能获得加热功能?——一文读懂OTA

2020年,特斯拉发布过一次OTA更新,车主可以通过这次系统更新获得座椅加热功能。当时,这则新闻震惊了车圈和所有车主,彼时的大家还没有把汽车当作可以“升级”的智能设备。 如今3年过去了,车主对各家车企的OTA升级早已见…

华为OD机试真题-园区参观路径-2023年OD统一考试(C卷)

题目描述:园区某部门举办了Family Day,邀请员工及其家属参加;将公司园区视为一个矩形,起始园区设置在左上角,终点园区设置在右下角;家属参观园区时,只能向右和向下园区前进;求从起始园区到终点园区会有多少条不同的参观路径; 输入描述:第一行为园区长和宽;后面每一行…

【ITK库学习】使用itk库进行图像配准:内插器(插值)

目录 1、itkNearestNeighborInterpolateImageFunction 最近点插值2、itkLinearInterpolateImageFunction 线性插值3、itkBSplineInterpolateImageFunction B样条插值4、itkWindowedSincInterpolateImageFunction 窗口化Sinc插值5、itkRayCastInterpolateImageFunction 投射插值…

充电桩MOS如何选型

• 充电桩是大功率 AC-DC 转换电源,用于给新能源电动汽车快速充电。 • 目前非 800V系统充电桩采用三相维也纳整流 LLC 电路,其中 PFC 整流可以采用二 极管,PFC 升压可以采用650V IGBT 或者 SJ MOSFET, LLC 采用 650V SJ MOSFET。…

Megatron模型并行研究

Megatron模型并行研究 1. 技术调研 a. Megatron-LM Megatron-LM针对的是特别大的语言模型,使用的是模型并行的训练方式。但和普通的模型并行不同,他采用的其实是张量并行的形式,具体来说就是将一个层切开放到不同的GPU上,属于层…

阿里云赵大川:弹性计算推理解决方案拯救 AIGC 算力危机

云布道师 本篇文章围绕弹性计算推理解决方案 DeepGPU 实例如何支持 Stable Diffusion 文生图推理、Stable Diffusion 推理演示示例等相关话题展开。 赵大川 阿里云弹性计算高级技术专家 GPU 云服务器推理解决方案的提出背景 随着 AIGC 时代的到来,两个重要应用应…

element-table表格中插入颜色块显示数据状态

dom部分&#xff1a; <el-table-column label"是否异常"><template slot-scope"scope"><div class"dcs_sf_red" v-if"scope.row.sfyc 0"></div><div class"dcs_sf_green" v-if"scope.row…

CAS机制

Java中提供了很多原子操作类来保证共享变量操作的原子性。这些原子操作的底层原理都是使用了CAS机制。在使用一门技术之前&#xff0c;了解这个技术的底层原理是非常重要的&#xff0c;所以本篇文章就先来讲讲什么是CAS机制&#xff0c;CAS机制存在的一些问题以及在Java中怎么使…

分子生成工具 - ResGen 评测

ResGen 模型是浙江大学药学院侯廷军老师课题组2023年发表在nature machine intelligence期刊上文章Nature Machine Intelligence | Volume 5 | September 2023 | 1020–1030&#xff0c;题目为&#xff1a;《ResGen is a pocket-aware 3D molecular generation model based on …