对于嵌入式系统而言,内存管理始终是最重要的一环,内存管理的选择将从根本上决定内存分配和回收效率,最终决定系统的性能。LWIP为使用者提供两种简单却又高效的内存管理机制:动态内存池管理、动态内存堆管理。
动态内存池管理策略
动态内存池是相当简单高效的一种分配策略,原理就类似我们去买鞋子,因为大家的脚无非就是这几种码数,所以厂商就先生产好确定码数的鞋子,比如这种球鞋厂商就生产39、40、41、42、43、44码,客户来买鞋子,直接试穿就可以买走了。所以直观的特点就是分配相当简单,相当快速。
设计目的
LWIP中存在很多固定的数据结构,这些结构的特点就是在使用之前就已经知道了数据结构的大小,而且这些是在使用的过程中不会发生大小改变的。比如在建立一个TCP连接的时候,LWIP需要使用一种叫做TCP控制块的数据结构,这种数据结构大小是固定的。所以为了满足这些数据类型分配的需要,在内存初始化的时候就建立了一定数量的动态内存池POOL。原理探析
内存块就好像上面提到的鞋子,系统会根据用户的宏定义确定下初始化时需要预先生产确定数量和类型的内存块(就好像生产多少数量和类型的鞋子一样)。但是生产出来的内存块不能乱放,因为到时用户过来取内存块的时候你要很快的分配相应的内存块给用户。所以LWIP将相同类型的内存块放在一起,并用链表进行串起来,比如在初始化的时候用户确定下在使用的过程中,我大概会用10字节内存块3个,20字节内存块4个,30内存块2个,那么就会有如下组织示意图:
当用户正在过来说,我想要使用20字节内存块的时候,系统就会立马找到链表头2,直接将头两个已经初始化好的20字节内存块分配给用户,相当简单快捷。当用户使用完了释放内存块时,就会直接插入到队头就好。这就是动态内存池的逻辑结构策略实现
到这里,基本上大家都有比较深入的了解了,那么真正的实现又做了什么额外的工作呢?
其实LWIP实现这一策略额外做的工作并不算复杂,既然逻辑结构已经分析过了,那么我们关注动态内存池实现起来的物理结构,有一下几点注意的地方:
a、LWIP开辟出一块连续的内存区域用于存放所有种类的固定内存块,就好像我把所有的鞋子放到一个店铺里销售。
b、为了管理的方便,LWIP设置了相关的数据结构来记录动态内存池的状态,就好像我们卖鞋子,我们需要一张库存表,当顾客进来买鞋子,我们可以通过库存表来查看顾客所需要的鞋子码数是否还有库存,假如有的话,这种码数的鞋子放在店里的哪个地方。这样做的目的无非就是提高了分配的效率。其中具体的数据结构大抵如下图所示:
说到这里应该比较明白了,当系统初始化后,关于内存池的空间布局大抵如下:
在LWIP中,使用一大块连续的内存区域:memp_memory来存放所有种类的固定内存块,并且使用指针将连续的内存块串联起来形成动态内存池的逻辑结构。最后将指向各种类型内存块的链表头通过指针数组(memp_tab)的方式存储起来。
- 动态内存池的优缺点
动态内存池的显著优点是相当简单,内存分配和释放的效率高,但是缺点也是比较明显的:
a、这样的设计必须事先确定在系统运行过程中所使用到的固定内存块的数量和种类,而这可能对于某些场景并不能准确的确定下来内存使用的需求,最后发现某些类型的内存块可能不足,甚至没有。
b、无法很好的满足一些异类的请求。比如之前说的鞋子的案例,假如一个人的脚正好穿42.5码,那么买43码又太大,买42码又太小。用户的需要也是如此,假如用户需要25字节的内存容量,分配30字节又太大(部分内存浪费),分配20字节又不够。
基于以上的缺陷,LWIP采用了另外一种策略:动态内存堆,也就是说你的脚很另类是吧,可以,你来我店里,我直接给你量身定做。
动态内存堆管理策略
上回说到你的脚很另类,并且不确定这个月有多少顾客过来买鞋子和买多少鞋子,这样我直接不提前生产鞋子了,直接你顾客过来,我现场帮你量脚定制,现场将鞋子加工出来卖给顾客。所以直观的特点就是有效组织了内存块的管理,但是管理的效率会比动态内存池低,因为你我顾客来了你现场才开始量身定制,那不是要顾客等很久吗?为了更好的形容这种策略,下面用顾客买衣服的案例来辅助说明。
设计目的
这种策略的设计目的比较明显,就是为了弥补在动态内存池中的种种不足之处,保证能满足各类不同的内存需求。原理探析
顾客需要买衣服,来店里量身定制,那么店家需要准备什么?是不是需要准备一大块布料,量出顾客的体型,从一整块布料剪出合适的布料进行裁缝,为顾客进行制作衣服。那么动态内存堆也是如此,先准备连续的一大块内存块,比如2K字节,那么用户需要25字节,LWIP直接就在2K字节中分配25字节的内存给用户就可以了,是不是相当简单~~。策略实现
当然,说着原理很简单,但是实现起来还需要做一些额外的工作,首先看一下动态内存堆初始化后的样子:
LWIP将一大块连续的内存块组织成上述形式,其中lfree是为了方便寻找空闲内存块而设立的指针。
另外,对于内存管理,总得记录下那些内存是已经分配的,那些内存是尚未分配的,LWIP采用了双向链表将所有切割过的内存块串起来,并使用一个used位(0、1)来表示这个内存块目前状态是否是已用的还是未用的,如下图所示,动态内存堆每分配的内存块就串起来。
其中黑色的代表已经分配出去的内存块,而白色代表未分配的内存块,在每个内存块的头部均有used位来表示目前内存块的状态。动态内存堆的优缺点
动态内存堆的显著优点是相当“人性化”,能满足用户不同的需求,但是缺点也是比较明显的:
a、分配和释放的效率比较低
b、需要考虑内存合并的问题
明白了LWIP内存管理的基本策略后,可以结合源码进一步研究各种细节性的实现。当然,也有很多对这两种基本策略的改进,可以参考相关操作系统书籍以及相关方面的论文文献,但是对于轻量级协议栈LWIP而言,这两种简单而又不失高效的内存组织方式依旧有不小的优势。
PS:本文相关图片部分来源于《嵌入式网络那些事》,很好的一本书。