自学内容网 自学内容网

400行程序写一个实时操作系统(六):内存管理算法(写一个malloc)

原因

我们在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<stdint.h>
#include<stdlib.h>

#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;
};

这是笔者习惯性的一种用法,也是对自己的一种编程规范,目的就是告诉自己:“这些代码是采用面向对象思想编写的,请尽量不要使用面向过程的思维来思考程序。”

小内存管理算法

小内存管理算法,通常也称为内存分配器,用于在内存资源有限的嵌入式系统中高效地管理内存。这些算法的目标是减少内存碎片,提高内存利用率,并确保分配和释放内存的操作尽可能快速、稳定。

它采用不断分割的方法对内存进行动态分配,分为初始化,分割,释放三个步骤,为了简洁起见,笔者直接放图:

初始化

我们需要把定义的大数组先初始化:

头部
初始大内存块
尾部
malloc分配

分配内存时,根据需要分配空间的大小,返回对应的地址,最终内存块会被不断切割:

已经分配的内存块
已经分配的内存块
已经分配的内存块
已经分配的内存块
头部
空闲内存块
空闲内存块
空闲内存块
尾部

最终读者会看到,空闲内存块会越来越小,而且也越来越不连续。

虽然通过指针,通常情况下我们可以把不连续的空间当成连续的空间使用,这些不连续的空间被称为碎片。

但是当不连续的空间越来越小时,我们很可能找不到一个足够大的空间碎片满足存储请求,尽管总的空闲空间可能仍然满足。

这样下去,系统绝对是会崩溃的。

为了避免碎片问题,也有一些方法被广泛采用。

分配时的策略

为了减少碎片,通常有两种策略:best-fit和first-fit。

best-fit算法
这个策略趋向于将大的碎片保留下来满足后续的请求。我们根据空闲空间块的大小,将它们分在若干个容器中,在容器中,存储块按照他们的大小进行排列,这使得寻找best-fit块变得更加容易。

first-fit算法
在这个策略中,对象被放置在地址最低的、能够容纳对象的碎片中。这种策略的优点是速度快,但是内存分配性能比较差。

free释放

释放内存块,就是将内存块加入空闲内存块表。同时会有一些方法减少碎片。

分配时的策略
合并内存块

同时,如果我们检测到free释放的内存块的地址与其他空闲内存块是连续的,我们会合并它们:

合并前:

已经分配的内存块
地址相邻
已经分配的内存块
头部
刚刚释放的内存块
空闲内存块
尾部

合并后:

已经分配的内存块
已经分配的内存块
头部
合并后更大的空闲内存块
尾部

这是内存管理算法的整体思想。

定义算法中的结构体

现在,我们已经知道内存管理包含哪些对象了,让我们接着写下这些程序:

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,如果能,那么就分割这块内存,如果不能,那么就不分割。

总结

本章笔者简单介绍了内存管理算法的基本概念,并且定义了一些基本的结构体和宏,希望读者能够领会内存管理算法的精髓。


原文地址:https://blog.csdn.net/2301_77061352/article/details/142923602

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!