(11)从1开始写一个操作系统

第十一章

小内存管理

因为小内存的限制,不可能使用系统标准的malloc和free,这里介绍3种小内存管理方法。

第一种就是预先申请一块大内存。

然后使用这个内存来动态分配,并在分配时使用一个头来做分配记录。

在使用过程中会出现碎片,也就是会发生不连续的可用内存。

当有连续释放的内存是会进行合并。

这种方法时最简单的方法也是小内存中最容易实现的,但是有一个致命的缺点就是长时间使用会导致内存碎片,而且并没有方法能够进行清理,只有重新启动。

第二种是slab 管理算法

DragonFly BSD 创始人 Matthew Dillon 实现的 slab。最原始的 slab 算法是 Jeff Bonwick 为 Solaris 操作系统而引入的一种高效内核内存分配算法。slab 分配器会根据对象的大小分成多个区(zone),也可以看成每类对象有一个内存池,如下图所示:

这种方法就是预先按照8,16,32…分配好内存块,如果需要申请相应大小的内存就到对应内存块中去申请,这种方法明显有很多浪费的地方。但是好处就是不需要进行内存整理,不会有内存碎片。

第三种方法是memheap 管理算法

memheap 工作机制如下图所示,首先将多块内存加入 memheap_item 链表进行粘合。当分配内存块时,会先从默认内存堆去分配内存,当分配不到时会查找 memheap_item 链表,尝试从其他的内存堆上分配内存块。应用程序不用关心当前分配的内存块位于哪个内存堆上,就像是在操作一个内存堆。

这种方法同样会有指针空间消耗。

我们的STC单片机可怜的内存只有3k,所以我们选择第一种方法,主要也是学习一下内存管理的原理,我们也可以在申请不到内存时让任务挂起,这部分暂时不去实现,有兴趣的可以自己试试。

下面我把我们的内存实现做一个简单描述,我已经把内存管理的结构体精简到了最简,只占用2个字节来记录内存,这也是根据STC单片机3k字节sram定制的。

我们记录内存的结构就是2个字节,一共16位,其中最高位是否为1来表示是否这块内存被使用,剩下的15位代表数字,是下一个内存结构的地址(数组脚标)。

例如:我们有1个30个字节的内存,也就是我们需要申请一个30个字节的数组,我们定义数组的前两位为内存开始的头,那么数组中的数据如下图:

当我们第一次申请了一个4字节的内存后,这个内存就会变成这样:

地址0中的1表示内存被占用,地址1中的6表示下一个内存结构在地址6中,x代表数据。

地址6中的0表示内存没被占用,地址7中的30表示下一个内存结构在30,但是30已经是全部长度了,所以表示这是最后一块内存了。

申请的时候就是不停的一个接一个的内存结构去查找能够满足申请要求的没被占用的内存。

释放内存就是相反的过程,将就近的没有被占用的内存合并。

下面我们会先实现代码,然后用单片机仿真来看这个过程。

我们来写几个例子测试一下程序:

运行每个函数之后的内存截图如下:

0x1D7是mem的内存地址,从0x1D7开始的前两位是内存结构体,首先它的最高位不为1,说明这块内存没有被占用,数据大小是30个,正好是全部动态内存大小,所以只有一块未使用的动态内存。

a = os_malloc(4);

申请4字节后,第一个内存结构的高位为1,所以是128,然后下一个结构的地址在6上,到地址为6的地方看,最高位为0,说明未被使用,下一个结构地址为30,说明这是最后一个内存结构。

*a = 7;

在第一个内存的第一个地址写入7。

b = os_malloc(6);

同上,申请内存。

*b = 8;

同上赋值。

    c = os_malloc(6);

    *c = 8;

    d = os_malloc(6);

*d = 9;

此时我们已经把30个内存空间使用完了。

os_free(a);

第一个内存结构被释放,并且指向下一个结构6。

os_free(b);

第二个内存结构被释放,并且与第一个合并,第一个指向14 。

    os_free(c);

os_free(d);

最终全部释放,第一个内存结构指向30,说明这是一个完整的没被用过的内存。

下面我们测试一下realloc功能:

    a = os_malloc(4);

    *a = 7;

    b = os_malloc(6);

    *b = 8;

    c = os_malloc(4);

    *c = 8;

    d = os_malloc(6);

os_memset(d, 9, 6);

前边基本相同,我们来看最后这一步,可以看出我们已经在动态内存d中写入了999999。

d = os_realloc(d, 8);

使用之后内存d的空间变为了指向30,说明它变大了。

    os_free(a);

    os_free(b);

    os_free(c);

    os_free(d);

上面这个是重复地址的特例,我们来看一下,一般情况的。

    a = os_malloc(4);

    *a = 7;

    b = os_malloc(8);

    *b = 8;

    c = os_malloc(4);

    *c = 8;

    d = os_malloc(4);

    os_memset(d, 9, 4);

os_free(b);

上面的过程不细说了,到这里我们释放了b。可以看到b的内存块被释放,并且指向16。

d = os_realloc(d, 6);

对比可以看出,原来的d被释放,原来的b被赋值了d的值,并且指向了14,但是14这个位置直接指向了16,也就是说这一小块内存被内存结构占用了。

到这里我们小内存管理基本完成,我们的操作系统的基本功能都实现了,下面就是对shell的移植。

 

©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页