LWIP源码学习---动态内存分配(分配策略概述)

写在前面

这是第一篇原创博客,想要从这里开始自己博客之路。由于最近项目中有用到STM32F4+LWIP实现LXI协议,故想借此机会学习LWIP源码,拓展自己在软件方面的知识面(本人方向较侧重于嵌入式硬件),由于了解不深,文章中有错误的地方还望大家批评指正,多多交流,一同进步。

lwip---轻量级软件协议栈

一、概述

LWIP,Low Weight (轻量)IP协议是瑞典计算机研究院开发的小型开源轻量级软件IP协议栈,旨在系统满足TCP协议的基础上,尽可能地减小系统内存的使用,仅需几十K的RAM和ROM就能够运行,因此适合在小型的嵌入式系统中使用。也正是因为他在节约内存方面的突出贡献,该源码中的数据结构以及内存分配很是值得大家学习。

结合自己的学习进度,本章将通过源码注释的方式来分析其内存分配的原理。

二、动态内存分配

1、内存分区以及字节对齐

讲lwip源码动态内存的开始,有必要记录一下程序运行时都有哪些内存分配的策略,概括的说有三种:静态、栈式和堆式。他们之间具体的区别和原理就不在这里展开来讲了,大家可以参考其他博主的文章,一搜一大把哈哈。这里我主要列出程序运行时的C/C++将内存分为的四个区(我觉得这个理解起来简单些嘿嘿,等自己把前面的知识点了解清楚了再补上):

堆区:由程序员进行分配和释放(动态申请和释放),如果程序员没有主动释放(free),程序结束后由系统进行回收;

栈区:由编译器自动分配释放,存放函数的参数值以及局部变量等,函数执行完毕后就会被自动释放;

全局/静态存储区:存放程序中的静态变量和全局变量,还有一个常量区,字符串常量和一些其他常量存在这里,操作系统进行释放;

代码区:这个就很好理解了,程序的代码就是存放在这个区,这个区是不允许程序操作的,由操作系统所有。

看完以上内存的分区,我们就能明白我们所要看的LWIP的动态内存分配也就是程序对堆区内存空间的分配管理了。

对了,再放出源码之前,最最最后还有一个知识点需要提到,就是内存对齐的操作,大概就是我们申请的内存的大小以及我们需要操作的内存的大小必须是符合我们系统的内存操作的宽度的要求的,这就是对齐。举个例子:2byte操作的内存,起始地址是0,那我们读内存的地址应该就是0,2,4,。。。取这些地址的数据操作就很简单,但是如果我们要读1,3,5这样的位置,就是难为系统了,就需要操作两次(这个是我的理解,如果有大神觉得有问题,欢迎评论区指正)。这个就是不对齐的后果--增加负担,复杂的情况好像还会崩系统了。更具体专业的请参考这个博主的文章--为什么要内存对齐

上代码!!!!!!!!!!!!

接上面的内容,我们先看看源码中对内存对齐的操作:

我看的是lwip2.0.2版本源码,该对齐操作在lwip2.0.2/src/core/include/lwip/arch.h中定义,主要包括三个操作:

LWIP_MEM_ALIGN_SIZE(size)、LWIP_MEM_ALIGN_BUFFER(size)和LWIP_MEM_ALIGN(addr):

/** Calculate memory size for an aligned buffer - returns the next highest
 * multiple of MEM_ALIGNMENT (e.g. LWIP_MEM_ALIGN_SIZE(3) and
 * LWIP_MEM_ALIGN_SIZE(4) will both yield 4 for MEM_ALIGNMENT == 4).
 */
#ifndef LWIP_MEM_ALIGN_SIZE
/*将size进行对齐的计算 如对齐规则为4字节对齐,申请内存为9时,根据如下定义,对齐后的大小为12*/
#define LWIP_MEM_ALIGN_SIZE(size) (((size) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT-1U))
#endif

/** Calculate safe memory size for an aligned buffer when using an unaligned
 * type as storage. This includes a safety-margin on (MEM_ALIGNMENT - 1) at the
 * start (e.g. if buffer is u8_t[] and actual data will be u32_t*)
 */
/*这里对buffer的操作我也不是特别理解,大概猜测意义就是给申请的内存大小补足一个对齐的余量,以免因为申请内存的时候因为对齐操作导致之前申请的内存大小不够,因此在size的基础基础上加了一个MEM_ALIGNMENT-1,这样,至少满足最近的对齐了,例如对于4字节对齐的规则,同样size为9,那么9+4=13 > 12 就满足了12对齐的最低要求,大概这个12就是那个安全界限(safety-margin),如果有懂的朋友烦请留言交流学习!*/
#ifndef LWIP_MEM_ALIGN_BUFFER
#define LWIP_MEM_ALIGN_BUFFER(size) (((size) + MEM_ALIGNMENT - 1U))
#endif

/** Align a memory pointer to the alignment defined by MEM_ALIGNMENT
 * so that ADDR % MEM_ALIGNMENT == 0
 */
#ifndef LWIP_MEM_ALIGN
/*对地址进行对齐的计算 对齐的基本原理及效果和size一样*/
#define LWIP_MEM_ALIGN(addr) ((void *)(((mem_ptr_t)(addr) + MEM_ALIGNMENT - 1) & ~(mem_ptr_t)(MEM_ALIGNMENT-1)))
#endif

接下来,我们来看看lwip动态内存分配的几种分配策略,以及他们之间的关系,了解清除了他们之间的关系结构才能帮助我们更好的理解其中动态内存分配的原理。

2、lwip中内存分配策略结构

lwip主要使用了三种内存分配策略,即程序运行时C库自带的内存分配策略、内存堆分配策略以及内存池分配策略。关于C库自带的内存分配策略这里就不多说了(其实我自己也不是很了解。。),主要是在嵌入式系统程序中,更多的是使用动态内存堆和内存池这两种分配策略,原因猜测是因为嵌入式操作系统对动态内存分配的要求是具备快速性可靠性和高效性,而C库自带的内存分配策略不具备这种特性。

内存堆:内存浪费小,管理简单,缺点就是容易产生内存碎片,适合做小内存的管理

内存池:内存浪费小,分配释放效率高速度快,利于防止内存碎片的产生。

下面以两张图来形象的描述内存对和内存池分配策略的区别:

概括的说,lwip中动态内存的分配包括程序运行时C库自带的内存分配策略、内存堆分配策略以及内存池分配策略三种,这三种内存分配方式由三种宏定义(MEM_LIBC_MALLOC、MEM_USE_POOLS、MEMP_MEM_MALLOC)分别决定,因此,由他们划分的程序结构(mem.c、memp.c)如下:

#if MEM_LIBC_MALLOC
/* 使用C库时的部分 */
#elif MEM_USE_POOLS
/* 使用内存池方式实现的内存堆函数 */
#else
/* 使用内存堆实现分配函数 */
#endif

/*关于宏值MEMP_MEM_MALLOC,他影响的是memp.c中内存池的实现*/

通过上面程序的结构可以看出编译器可以根据用户给定的宏值不同而选择不同的内存分配方式。关于内存池分配的实现,他本就在memp.c中独立实现,无论如何都会编译,唯一不同的是关于内存堆的实现,他可以根据宏值MEM_USE_POOLS的不同而具备两种实现方法——独立实现或根据内存池方法实现。因此,本章中,我们不对C库部分的分配策略做分析。我们依照程序结构,先分析内存堆独立实现的方法,再研究内存池的实现方法,最后在这个基础上再去看基于内存池的内存堆的分配策略。

3、内存堆的独立实现

从上节描述可得,独立实现的内存堆是在条件编译#else之后进行的。

分析代码之前,我们先解释用到的关键的宏定义以及一些结构体变量。先讲讲第一个结构体mem:

struct mem {
  /** index (-> ram[next]) of the next struct */
  mem_size_t next;//指向下一个内存块的头
  /** index (-> ram[prev]) of the previous struct */
  mem_size_t prev;//指向前一个内存块的头
  /** 1: this area is used; 0: this area is unused */
  u8_t used;//该内存快是否被使用,1代表被使用
};

所谓的内存分配,实际上就是对一个已经定义好的数组(空间)进行恰当管理。在这里,LWIP使用的是链表的形式对用户申请的内存块进行管理。每个内存块都具有一个表头,该表头的内容结构也就是通过上述的结构体来描述。

对于程序中使用的宏定义仪器其他全局变量,这里贴上代码统一注释:(太晚了,没来得及注释,有空继续更。。)

/** All allocated blocks will be MIN_SIZE bytes big, at least!
 * MIN_SIZE can be overridden to suit your needs. Smaller values save space,
 * larger values could prevent too small blocks to fragment the RAM too much. */
#ifndef MIN_SIZE
#define MIN_SIZE             12
#endif /* MIN_SIZE */
/* some alignment macros: we define them here for better source code layout */
#define MIN_SIZE_ALIGNED     LWIP_MEM_ALIGN_SIZE(MIN_SIZE)
#define SIZEOF_STRUCT_MEM    LWIP_MEM_ALIGN_SIZE(sizeof(struct mem))
#define MEM_SIZE_ALIGNED     LWIP_MEM_ALIGN_SIZE(MEM_SIZE)

/** If you want to relocate the heap to external memory, simply define
 * LWIP_RAM_HEAP_POINTER as a void-pointer to that location.
 * If so, make sure the memory at that location is big enough (see below on
 * how that space is calculated). */
#ifndef LWIP_RAM_HEAP_POINTER
/** the heap. we need one struct mem at the end and some room for alignment */
LWIP_DECLARE_MEMORY_ALIGNED(ram_heap, MEM_SIZE_ALIGNED + (2U*SIZEOF_STRUCT_MEM));
#define LWIP_RAM_HEAP_POINTER ram_heap
#endif /* LWIP_RAM_HEAP_POINTER */

/** pointer to the heap (ram_heap): for alignment, ram is now a pointer instead of an array */
static u8_t *ram;
/** the last entry, always unused! */
static struct mem *ram_end;
/** pointer to the lowest free block, this is used for faster search */
static struct mem *lfree;

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值