章节列表

存储块的原理与创建

2018-09-11 05:38:32 +0000 李述铜

从本小节开始介绍存储块模块的实现。

存储块是一种非常简单、但是快速高效地内存分配方案。简而言之,预先分配若干固定大小的存储块,然后在需要时候从中取出即可。这就比需要时,再完全重新分配要简单快速很多。

视频课程中有讲malloc()的问题,这篇指南中还会补充一点其它知识。

主要内容

以下内容,作为视频内容的补充,从更宽的角度说明内存分配相关的几种方案,请根据需要参考学习。

一般来讲,在我们写代码过程中,能够看得见的几种涉及内存分配的情况如下。

全局/static变量

这类变量会在整个工程编译时,由编译器自动在RW区(可以简单理解为内存)预先分配好一块内存,然后每次使用时,直接读写这块固定的内存。

这种方案的好处是非常简单,分配的内存根据我们的代码写法可大可小,地址固定,非常灵活。缺点也比较明显,分配了就没办法回收利用。

块变量

这里的块变量指的是在函数内部分配,或者在语句块中分配的变量,例如:

{
    int var[100];
}

这类变量可能分配到CPU的内核寄存器,也可能在当前堆栈中开辟一小块空间。这种方案的好处是非常简单灵活,分配大小可大可小(注意不要过大导致堆栈溢出),在退出函数/语句块时,这些内存空间会自动回收。

以上两种分配方式,能够解决绝大多数的内存管理问题,且其是由编译器自动管理的,我们不需要刻意去关注。但是在某些情况下,我们还需要这样的分配方式:

希望能够动态分配多个持续存在的存储区

全局/static是预先分配的,无法满足动态分配条件;块变量的只能在块内访问,退出退则消息。所以这两种方式都不满足这种条件。所以就有了下面两种方式。

malloc/free

一般来说,这种方式是从“堆”中分配,但实际上也不一定。

malloc/free是C标准库的规定的接口。但是在有些简单的工具中,并不提供缺省的malloc/free实现,所以你可以自行实现。大体上说,malloc/free是从一大块内存中分配,这个内存一定是堆吗?不一定。

至少,有些os中是直接定义了一个全局的字节数组,然后提供了类似malloc/free接口从中分配内存。

使用这种方式,可以指定想要分配的大小,然后分配时会返回相应的存储区(实际可能会多点,多余空间用做一些标记)。

课程中提到,这种方式可能带来的问题是:

  • 大量频繁的分配不同大小的内存块,可能导致内存碎片。
  • 这种方案要求能够灵活分配不同大小的内存块,这就导致其在实现上较复杂、且执行分配时间也较长。

虽然有以上缺点,但是不能因此就觉得这种方案不好,毕竟这种问题只是在某些情况下才出现。

固定块大小分配

这种方案是本课程中所介绍的,这种方面主要面向系统中所需分配的内存块大小各类不多的情况。其思想是:

与其使用malloc执行复杂耗时的分配操作,还不如预先把需要的总的存储空间先分配好。

然后,将这块大空间划分成实际所需的小的块集合。最后在需要时,直接从这个集合中立即取。

相比之下,我们可以用更简单快速地方式对这个集合进行管理,这就比用malloc要简单高效很多。

那么,在本课程中,我们提供的方式如下:

  • 预先分配所需的大内存块。课程演示中使用的是定义全局变量,但实际也可用malloc分配;
  • 将大内存块划分为多个小内存块,然后用链表链接起来。

这个链表就是前面提到的”块集合”。对该集合的操作只涉及到链表头部的插入和删除,通过代码可以看到,这个过程简单快速。

当然这种方式也有缺点:分配大小只能是固定的。

重点难点

本章的难点可能在于理解怎样将一个大内存块划分成多个小块,然后用链表串接起来的过程。

请在学习的时候,画下图,嗯,是的。如果你看代码无法想像其过程的话,画图是最有效的方式。具体请参考上图。

注意事项

内存对齐问题

在目前的实现中,并没有考虑到“内存对齐”的问题。

当我们从存储块中分配一个小块之后,一般是将其转换为某种结构,例如(struct msg *)mem。但是对mem的某些成员访问可能要求内存对齐,比如首个成员mem->addr(int32),此时块的首地址可能并不是4字节对齐,在某些处理器上不对齐访问可能导致性能问题、甚至触发异常。

有关这块问题的具体细节,我暂时没有空去研究。如果你感兴趣,可以深入研究下。

块大小

正如视频课程中所说,如果你无法确定块在固定大小,但是能确定最大值;可以考虑分配时按最大值设定块大小。

如果觉得设定成最大值会导致较大的浪费;也可以考虑再划分几种大小的块。具体根据情况调整。

或者有兴趣的话,可以针对这种问题研究更灵活的方案。

应用实例

常见问题