- 作者:老汪软件技巧
- 发表时间:2024-10-15 04:00
- 浏览量:
本节我们将会开始具体的程序编写。我们首先要开发的模块是内存管理算法部分。为什么要从它开始呢?
原因
我们在c语言中,经常要用到malloc申请内存,然后再free释放内存,但是c语言中的malloc放在RTOS中是相当危险的,因为它并没有针对实时性进行优化,所以,为RTOS设计一个内存管理算法是有必要的。
前言
先简单复习一下指针:
指针的本质是要求值必须为地址的变量。
int a,代表在内存中开辟一片空间,空间存放a的值。int *a,代表在内存中开辟一片空间,空间存放a的值,只不过此时a的值是一个地址,可以通过这个值找到另一块内存空间。
当我们进行 int *a = malloc(sizeof(xxx))的操作时,实际上就是改变这片空间存储的值。此时a就是一个有意义的值,它代表一个存在且可以被存放的内存空间的地址。
那么,当我们随机定义一个指针并且要对这片内存操作时,会发生什么呢?例如:
int *a;
*a = 1;
当我们定义指针a时,a的值是随机的,那么我们解引用a,计算机找不到这片空间,此时错误就会发生了。
也就是说,如果我们要手动管理内存,那么必须要分配空闲的内存地址,必须对内存进行严格的管理。
写下第一行程序
sparrow.c
#include "core_cm3.h"
#include
#include
#define config_heap 8*1024
#define Class(class) \
typedef struct class class;\
struct class
static uint8_t allheap[config_heap];
如上所述,我们需要定义一个大数组。它由编译器分配,位于内存的全局区。这就是我们手动管理的内存空间。
简单介绍一下Class这个宏,我们知道c语言宏定义的本质是替换,可以看看替换前后:
替换前:
Class(Person){
int a;
};
替换后:
typedef struct Person Person;
struct Person {
int a;
};
这是笔者习惯性的一种用法,也是对自己的一种编程规范,目的就是告诉自己:“这些代码是采用面向对象思想编写的,请尽量不要使用面向过程的思维来思考程序。”
小内存管理算法
小内存管理算法,通常也称为内存分配器,用于在内存资源有限的嵌入式系统中高效地管理内存。这些算法的目标是减少内存碎片,提高内存利用率,并确保分配和释放内存的操作尽可能快速、稳定。
它采用不断分割的方法对内存进行动态分配,分为初始化,分割,释放三个步骤,为了简洁起见,笔者直接放图:
初始化
我们需要把定义的大数组先初始化:
graph LR
A(头部)-->B(初始大内存块)-->C(尾部)
malloc分配
分配内存时,根据需要分配空间的大小,返回对应的地址,最终内存块会被不断切割:
graph LR
B(头部)--已经分配的内存块-->C(空闲内存块)--已经分配的内存块-->D(空闲内存块)--已经分配的内存块-->E(空闲内存块)--已经分配的内存块-->T(尾部)
最终读者会看到,空闲内存块会越来越小,而且也越来越不连续。
虽然通过指针,通常情况下我们可以把不连续的空间当成连续的空间使用,这些不连续的空间被称为碎片。
但是当不连续的空间越来越小时,我们很可能找不到一个足够大的空间碎片满足存储请求,尽管总的空闲空间可能仍然满足。
这样下去,系统绝对是会崩溃的。
为了避免碎片问题,也有一些方法被广泛采用。
分配时的策略
为了减少碎片,通常有两种策略:best-fit和first-fit。
best-fit算法 这个策略趋向于将大的碎片保留下来满足后续的请求。我们根据空闲空间块的大小,将它们分在若干个容器中,在容器中,存储块按照他们的大小进行排列,这使得寻找best-fit块变得更加容易。
first-fit算法 在这个策略中,对象被放置在地址最低的、能够容纳对象的碎片中。这种策略的优点是速度快,但是内存分配性能比较差。
free释放
释放内存块,就是将内存块加入空闲内存块表。同时会有一些方法减少碎片。
分配时的策略合并内存块
同时,如果我们检测到free释放的内存块的地址与其他空闲内存块是连续的,我们会合并它们:
合并前:
graph LR
B(头部)--已经分配的内存块-->P(刚刚释放的内存块)--地址相邻-->C(空闲内存块)--已经分配的内存块-->T(尾部)
合并后:
graph LR
B(头部)--已经分配的内存块-->C(合并后更大的空闲内存块)--已经分配的内存块-->T(尾部)
这是内存管理算法的整体思想。
定义算法中的结构体
现在,我们已经知道内存管理包含哪些对象了,让我们接着写下这些程序:
sparrow.c
#define MIN_size ((size_t) (heapstructSize << 1))
Class(heap_node){
heap_node *next;
size_t blocksize;
};
Class(xheap){
heap_node head;
heap_node *tail;
size_t allsize;
};
xheap theheap = {
.tail = NULL,
.allsize = config_heap,
};
static const size_t heapstructSize = (sizeof(heap_node) + (size_t)(aligment_byte)) &~(aligment_byte);
每个内存块都会有一个头部,也就是heap_node,同时为了管理整个内存块,我们还要定义一个xheap并且对其进行初始化。 MIN_size是为了判断分配的内存块的空间被切割后,剩下的内存能够不能够放得下头部heap_node,如果能,那么就分割这块内存,如果不能,那么就不分割。
总结
本章笔者简单介绍了内存管理算法的基本概念,并且定义了一些基本的结构体和宏,希望读者能够领会内存管理算法的精髓。
Sparrow源码:skaiui2/SKRTOS_sparrow at source ()
已经移植好的工程,包括hal库版本和标准库版本:skaiui2/SKRTOS_sparrow: Lightweight rtos inspired by SKRTOS ()