加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 LwIP协议栈源码详解

LwIP协议栈源码详解.pdf

LwIP协议栈源码详解

jyk008
2011-08-14 0人阅读 举报 0 0 暂无简介

简介:本文档为《LwIP协议栈源码详解pdf》,可适用于IT/计算机领域

Email:forrestfoxmailcom老衲五木出品LwIP协议栈源码详解TCPIP协议的实现Createdby老衲五木atUESTCContactmeforrestfoxmailcomqqcomEmail:forrestfoxmailcom老衲五木出品前言最近一个项目用到LwIP恰好看到网上讨论的人比较多所以有了写这篇学习笔记的冲动一是为了打发点发呆的时间二是为了吹过的那些NB。往往决定做一件事是简单的而坚持做完这件事却是漫长曲折的但终究还是写完了时间开销大概为四个月内存开销无法估计。。这篇文章覆盖了LwIP协议大部分的内容但是并不全面。它主要讲解了LwIP协议最重要也是最常被用到的部分包括内存管理底层网络接口管理ARP层IP层TCP层API层等这些部分是LwIP的典型应用中经常涉及到的。而LwIP协议的其他部分包括UDPDHCP,DNS,IGMP,SNMP,PPP等不具有使用共性的部分这篇文档暂时未涉及。原来文章是发在空间中的每节每节依次更新后来又改发为博客再后来就干脆懒得发了。现在终于搞定于是将所有文章汇总。绞尽脑汁的想写一段空前绝后人见人爱的序言但越写越觉得像是猫儿抓的一样。就这样PS:由于本人文笔有限情商又低下里巴人一枚所以文中的很多语句可能让您很纠结您可以通过邮箱与我联系。共同探讨才是进步的关键。最后欢迎读者以任何方式使用与转载但请保留作者相关信息酱紫!码字。。。世界上最痛苦的事情莫过于此。。。老衲五木Email:forrestfoxmailcom老衲五木出品目录移植综述动态内存管理数据包pbufpbuf释放网络接口结构以太网数据接收ARP表ARP表查询ARP层流程IP层输入IP分片重装IP分片重装ICMP处理TCP建立与断开TCP状态转换TCP控制块TCP建立流程TCP状态机TCP输入输出函数TCP输入输出函数TCP滑动窗口TCP超时与重传TCP慢启动与拥塞避免TCP快速恢复重传和Nagle算法TCP坚持与保活定时器TCP定时器TCP终结与小结API实现及相关数据结构API消息机制API函数及编程实例Email:forrestfoxmailcom老衲五木出品移植综述移植综述移植综述移植综述如果你认为所谓的毅力是每分每秒的“艰苦忍耐”式的奋斗那这是一种很不足的心理状态。毅力是一种习惯毅力是一种状态毅力是一种生活。看了这么久的代码觉得是不是该写点东西了不然怎么对得起某人口中所说的科研人员这个光荣称号。初见这如山如海的代码着实看出了一身冷汗。现在想想其实也不是那么难那么多革命先辈经过N长时间才搞出来的东东怎么可能让你个毛小子几周之内搞懂。我见到的只是冰川的一小角万里长征的一小步九头牛身上的一小毛…再用某人的话说写吧昏写瞎写胡写乱写写写就懂了。我想我很适合当一个歌颂者青春在风中飘着。你知道就算大雨让这座城市颠倒我会给你怀抱受不了看见你背影来到写下我度秒如年难捱的离骚就算整个世界被寂寞绑票我也不会奔跑逃不了最后谁也都苍老写下我时间和琴声交错的城堡。我正在听的歌。扯远了…正题嵌入式产品连入Internet网这个MS是个愈演愈烈的趋势。想想你可以足不出户对你的产品进行配置并获取你关心的数据信息多好。这也许也是物联网世界最基本的雏形。当然你的产品要有如此功能那可不容易至少它得有个目前很Fashion的TCPIP协议栈。LWIP是一套用于嵌入式系统的开放源代码TCPIP协议栈。在你的嵌入式处理器不是很NB内部Flash和Ram不是很强大的情况下用它还是很合适滴。LWIP的设计者为像我这样的懒惰者提供了详细的移植说明文档当然这还不够他们还尽可能的包揽了大部分工作懒人们只要做很少的工作就功德圆满了。纵观整个移植过程使用者需要完成以下几个方面的东西:首先是LWIP协议内部使用的数据类型的定义如utstutut等等等等。由于所移植平台处理器的不同和使用的编译器的不同这些数据类型必须重新定义。想想一个int型数据在位处理器上其长度为个字节在位处理器上为个字节而在位处理器上就只有两个字节了。因此这部分需要使用者根据处理器位数和和使用的编译器的特点来编写。所以在ARM处理器上使用的typedefunsignedintut移植语句用在位处理器的移植过程中那肯定是行不通的了。其次是实现与信号量和邮箱操作相关的函数比如建立、删除、等待、释放等。如果在裸机上直接跑LWIP这点实现起来比较麻烦使用者必须自己去建立一套信号量和邮箱相关的机制。一般情况下在使用LWIP的嵌入式系统中都会有操作系统的支持而在操作系统中信号量和邮箱往往是最基本的进程通信机制了。UCOSII应该算是最简单的嵌入式操作系统了吧它也无例外的能够提供信号量和邮箱机制只要我们将UCOSII中的相关函数做相应的封装就可满足LWIP的需求。LWIP使用邮箱和信号量来实现上层应用与协议栈间、下层硬件驱动与协议栈间的信息交互。LWIP协议模拟了TCPIP协议的分层思想表面上看LWIP也是有分层思想的但从实现上看LWIP只在一个进程内实现了各个层次的所有工作。具体如下:LWIP完成相关初始化后会阻塞在一个邮箱上等待接收数据进行处理。这个邮箱内的数据可能来自底层硬件驱动接收到的数据包也可能来自应用程序。当在该邮箱内取得数据后LWIP会对数据进行解析然后再依次调用协议栈内部上层相关处理函数处理数据。处理结束后LWIP继续阻塞在邮箱上等待下一批数据。当然LWIP还有一大串的内存管理机制用以避免在各层间交互数据时大量的时间和内存开销这将在后续讲解中慢慢道来。当然但这样的设计使得代码理解难度加大这一点让人头大。信号量也Email:forrestfoxmailcom老衲五木出品可以用在应用程序与协议栈的互相通信中。比如应用程序要发送数据了它先把数据发到LWIP阻塞的邮箱上然后它挂起在一个信号量上LWIP从邮箱上取得数据处理后释放一个信号量告诉应用程序你要发的数据我已经搞定了此后应用程序得到信号量继续运行而LWIP继续阻塞在邮箱上等待下一批处理数据。其其次就是与等待超时相关的函数。上面说到LWIP协议栈会阻塞在邮箱上等待接收数据的到来。这种等待在外部看起来是一直进行的但其实不然。一般在初始化LWIP进程的时候都会同时的初始化一些超时事件即当某些事件等待超时后它们会自动调用一些超时处理函数做相关处理以满足TCPIP协议栈的需求。这样看来当LWIP协议栈阻塞等待邮箱之前它会精明的计算到底应该等待多久如果LWIP进程中没有初始化任何超时事件那好这种情况最简单了永远的挂起进程就可以了这时的等待就可以看做是天长地久的…有点暧昧了。如果LWIP进程中有初始化的超时事件这时就不能一直等了因为这样超时事件没有任何被执行的机会。LWIP是这样做的等待邮箱的时间设置为第一个超时事件的时间长度如果时间到了还没等到数据那好直接跳出邮箱等待转而执行超时事件当执行完成超时事件后再按照上述的方法继续阻塞邮箱。可以看出对一个LWIP进程需要用一个链表来管理这些超时事件。这个链表的大部分工作已经被LWIP的设计者完成了使用者只需要实现的仅有一个函数:该函数能够返回当前进程个超时事件链表的首地址。LWIP内部协议要利用该首地址来查找完成相关超时事件。其其其次如果LWIP是建立在多线程操作系统之上的话则要实现创建一个新线程的函数。不支持多线程的操作系统汗…表示还没听过。不过UCOSII显然是支持多线程的地球人都知道。这样一个典型的LWIP应用系统包括这样的三个进程:首先启动的是上层应用程序进程然后是LWIP协议栈进程最后是底层硬件数据包接收发送进程。通常LWIP协议栈进程是在应用程序中调用LWIP协议栈初始化函数来创建的。注意LWIP协议栈进程一般具有最高的优先级以便实时正确的对数据进行响应。其其其其次其他一些细节之处。比如临界区保护函数用于LWIP协议栈处理某些临界区时使用一般通过进临界区关中断、出临界区开中断的方式来实现又如结构体定义时用到的结构体封装宏LWIP的实现基于这样一种机制即上层协议已经明确知道了下层所传上来的数据的结构特点上层直接使用相关取地址计算得到想要的数据而避免了数据递交时的复制与缓冲所以定义结构体封装宏禁止编译器的地址自动对齐是必须的还有诸如调试输出、测量记录方面的宏不做讲解。最后也是比较重要的地方。底层网络驱动函数的实现。这取决于你嵌入式硬件系统所使用的网络接口芯片也就是网卡芯片常见的有RTLBL、ENCJ等等。不同的接口芯片厂商都会提供丰富的驱动函数。我们只要将这些发送接收接口函数做相应的封装将接收到得数据包封装为LWIP协议栈熟悉的数据结构、将发送的数据包分解为芯片熟悉的数据结构就基本搞定了。最起码的发送一个数据包函数和接收一个数据包函数需要被实现。那就这样了吧虽然写得草草但终于在撤退之前搞定。好的开始是成功的一半那这暂且先算四分之一吧。不晓得一个月、两个月或者更多时间能写完否。预知后事如何请见下回分解。Email:forrestfoxmailcom老衲五木出品动态内存管理动态内存管理动态内存管理动态内存管理最近电力局很不给力啊隔三差五的停电害得我们老是痛苦的双扣斗地主不带这样的啊!今天还写吗?写必须的。昨天把LWIP的移植工作框架说了一下网上也有一大筐的关于移植细节的文档。有兴趣的童鞋不妨去找找。这里我很想探究LWIP内部协议实现的细节以及所有盘根错节的问题的来龙去脉。以后的讨论研究将按照LWIP英文说明文档《DesignandImplementationoftheLWIP:TCPIPStack》的结构组织展开。这里讨论LWIP的动态内存管理机制。总的来说LWIP的动态内存管理机制可以有三种:C运行时库自带的内存分配策略、动态内存堆(HEAP)分配策略和动态内存池(POOL)分配策略。动态内存堆分配策略和C运行时库自带的内存分配策略具有很大的相似性这是LWIP模拟运行时库分配策略实现的。这两种策略使用者只能从中选择一种这通过头文件lwippoolsh中的宏定义MEMLIBCMALLOC来实现的当它被定义为时则使用标准C运行时库自带的内存分配策略而为时则使用LWIP自身的动态内存堆分配策略。一般情况下我们选择使用LWIP自身的动态内存堆分配策略这里不对C运行时库自带的内存分配策略进行讨论。同时动态内存堆分配策略可以有两种实现方式纠结…第一种就是如前所述的通过开辟一个内存堆然后通过模拟C运行时库的内存分配策略来实现。第二种就是通过动态内存池的方式来实现也即动态内存堆分配函数通过简单调用动态内存池(POOL)分配函数来完成其功能(太敷衍了事了)在这种情况下用户需要在头文件lwippoolsh中定义宏MEMUSEPOOLS和MEMUSECUSTOMPOOLS为同时还要开辟一些额外的缓冲池区如下:LWIPMALLOCMEMPOOLSTARTLWIPMALLOCMEMPOOL(,)LWIPMALLOCMEMPOOL(,)LWIPMALLOCMEMPOOL(,)LWIPMALLOCMEMPOOLEND这几句摘自LWIP源码注释部分表示为动态内存堆相关功能函数分配个字节长度的内存块个字节的内存块个字节的内存块。内存池管理会根据以上的宏自动在内存中静态定义一个大片内存用于内存池。在内存分配申请的时候自动根据所请求的大小选择最适合他长度的池里面去申请如果启用宏MEMUSEPOOLSTRYBIGGERPOOL那么如果上述的最适合长度的池中没有空间可以用了分配器将从更大长度的池中去申请不过这样会浪费更多的内存。晕乎乎…就这样了这种方式一般不会被用到。哎就最后这句话给力。下面讨论动态内存堆分配策略的第一种实现方式这也是一般情况下被使用的方式。这部分讨论主要参照网上Oldtom’sBlogTA写得很好(但是也有一点小小的错误)所以一不小心被我借用了。动态内存堆分配策略原理就是在一个事先定义好大小的内存块中进行管理其内存分配的策略是采用最快合适(FirstFit)方式只要找到一个比所请求的内存大的空闲块就从中切割出合适的块并把剩余的部分返回到动态内存堆中。分配的内存块有个最小大小的限Email:forrestfoxmailcom老衲五木出品制要求请求的分配大小不能小于MINSIZE否则请求会被分配到MINSIZE大小的内存空间。一般MINSIZE为字节在这个字节中前几个字节会存放内存分配器管理用的私有数据该数据区不能被用户程序修改否则导致致命问题。内存释放的过程是相反的过程但分配器会查看该节点前后相邻的内存块是否空闲如果空闲则合并成一个大的内存空闲块。采用这种分配策略其优点就是内存浪费小比较简单适合用于小内存的管理其缺点就是如果频繁的动态分配和释放可能会造成严重的内存碎片如果在碎片情况严重的话可能会导致内存分配不成功。对于动态内存的使用比较推荐的方法就是分配>释放>分配>释放这种使用方法能够减少内存碎片。下面具体来看看LWIP是怎么来实现这些函数的。meminit()内存堆的初始化函数主要是告知内存堆的起止地址以及初始化空闲列表由lwip初始化时自己调用该接口为内部私有接口不对用户层开放。memmalloc()申请分配内存。将总共需要的字节数作为参数传递给该函数返回值是指向最新分配的内存的指针而如果内存没有分配好则返回值是分配的空间大小会收到内存对齐的影响可能会比申请的略大。返回的内存是“没有“初始化的。这块内存可能包含任何随机的垃圾你可以马上用有效数据或者至少是用零来初始化这块内存。内存的分配和释放不能在中断函数里面进行。内存堆是全局变量因此内存的申请、释放操作做了线程安全保护如果有多个线程在同时进行内存申请和释放那么可能会因为信号量的等待而导致申请耗时较长。memcalloc()是对memmalloc()函数的简单包装他有两个参数分别为元素的数目和每个元素的大小这两个参数的乘积就是要分配的内存空间的大小与memmalloc()不同的是它会把动态分配的内存清零。有经验的程序员更喜欢使用memcalloc()因为这样的话新分配内存的内容就不会有什么问题调用memcalloc()肯定会清并且可以避免调用memset()。休息…………动态内存池(POOL)分配策略可以说是一个比较笨的分配策略了但其分配策略实现简单内存的分配、释放效率高可以有效防止内存碎片的产生。不过他的缺点是会浪费部分内存。为什么叫POOL?这点很有趣POOL有很多种而这点依赖于用户配置LWIP的方式。例如用户在头文件opth文件中定义LWIPUDP为则在编译的时候与UDP类型内存池就会被建立定义LWIPTCP为则在编译的时候与TCP类型内存池就会被建立。另外还有很多其他类型的内存池如专门存放网络包数据信息的PBUFPOOL、还有上面讲解动态内存堆分配策略时提到的CUSTOMPOOLS等等等等。某种类型的POOL其单个大小是固定的而分配该类POOL的个数是可以用户配置的用户应该根据协议栈实际使用状况进行配置。把协议栈中所有的POOL挨个放到一起并把它们放在一片连续的内存区域这呈现给用户的就是一个大的缓冲池。所以所谓的缓冲池的内部组织应该是这样的:开始处放了A类型的POOL池a个紧接着放上B类型的POOL池b个再接着放上C类型的POOL池c个…直至最后N类型的POOL池n个。这一点很像UCOSII中进程控制块和事件控制块先开辟一堆各种类型的放那你要用直接来取就是了。注意这里的分配必须是以单个缓冲池为基本单位的在这样的情况下可能导致内存浪费的情况。这是很明显的啊不解释。下面我来看看在LWIP实现中是怎么开辟出上面所论述的大大的缓冲池的(‘的’这个字今天让我们一群人笑了很久)。基本上绝大部分人看到这部分代码都会被打得晕头转向完全不晓得作者是在干啥但是仔细理解后你不得不佩服作者超凡脱俗的代码写能力差一点用了沉鱼落雁这个词罪过。上代码:Email:forrestfoxmailcom老衲五木出品staticutmempmemorystaticutmempmemorystaticutmempmemorystaticutmempmemoryMEMALIGNMENTMEMALIGNMENTMEMALIGNMENTMEMALIGNMENT#defineLWIPMEMPOOL(name,num,size,desc)#defineLWIPMEMPOOL(name,num,size,desc)#defineLWIPMEMPOOL(name,num,size,desc)#defineLWIPMEMPOOL(name,num,size,desc)((num)*(MEMPSIZEMEMPALIGNSIZE(size)))((num)*(MEMPSIZEMEMPALIGNSIZE(size)))((num)*(MEMPSIZEMEMPALIGNSIZE(size)))((num)*(MEMPSIZEMEMPALIGNSIZE(size)))#include"lwipmempstdh"#include"lwipmempstdh"#include"lwipmempstdh"#include"lwipmempstdh"上面的代码定义了缓冲池所使用的内存缓冲区很多人肯定会怀疑这到底是不是一个数组的定义。定义一个数组里面居然还有define和include关键字。解决问题的关键就在于头文件mempstdh它里面的东西可以被简化为诸多条LWIPMEMPOOL(name,num,size,desc)。又由于用了define关键字将LWIPMEMPOOL(name,num,size,desc)定义为((num)*(MEMPSIZEMEMPALIGNSIZE(size)))所以mempstdh被编译后就为一条一条的()()()()…所以最终的数组mempmemory等价定义为:staticutmempmemoryMEMALIGNMENT–()()…如果此刻你还没懂只能说明我的表述能力有问题了。当然还有个小小的遗留问题为什么数组要比实际需要的大MEMALIGNMENT–?作者考虑的是编译器的字对齐问题到此打住这个问题不能深究啊以后慢慢讲。复制上面的数组建立的方法协议栈还建立了一些与缓冲池管理的全局变量:mempnum:这个静态数组用于保存各种类型缓冲池的成员数目mempsizes:这个静态数组用于保存各种类型缓冲池的结构大小memptab:这个指针数组用于指向各种类型缓冲池当前空闲节点接下来就是理所当然的实现函数了:mempinit():内存池的初始化主要是为每种内存池建立链表memptab其链表是逆序的此外如果有统计功能使能的话也把记录了各种内存池的数目。mempmalloc():如果相应的memptab链表还有空闲的节点则从中切出一个节点返回否则返回空。mempfree():把释放的节点添加到相应的链表memptab头上。从上面的三个函数可以看出动态内存池分配过程时相当的简洁直观啊。HC:百度说是胡扯的意思。哈哈……Email:forrestfoxmailcom老衲五木出品数据包数据包数据包数据包pbuf高的地方总是很冷。孤独可以让人疯狂。没人能懂你!昨天讲过了LWIP的内存分配机制。再来总之一下LWIP中常用到的内存分配策略有两种一种是内存堆分配一种是内存池分配。前者可以说能随心所欲的分配我们需要的合理大小的内存块(又是‘的’)缺点是当经过多次的分配释放后内存堆中间会出现很多碎片使得需要分配较大内存块时分配失败后者分配速度快就是简单的链表操作因为各种类型的POOL是我们事先建立好的但是采用POOL会有些情况下会浪费掉一定的内存空间。在LWIP中将这两种分配策略混合使用达到了很好的内存使用效率。下面我们将来看看LWIP中是怎样合理利用这两种分配策略的。这就顺利的过渡到了这节要讨论的话题:LWIP的数据包缓冲的实现。在协议栈中移动的数据包最无疑的是整个内存管理中最重要的部分了。数据包的种类和大小也可以说是五花八门数数首先从网卡上来的原始数据包它可以是长达上千个字节的TCP数据包也可以是仅有几个字节的ICMP数据包再从要发送的数据包看上层应用可能将自己要发送的千奇百怪形态各异的数据包递交给LWIP协议栈发送这些数据可能存在于应用程序管理的内存空间内也可能存在于某个ROM上。注意这里有个核心的东西是当数据在各层之间传递时LWIP极力禁止数据的拷贝工作因为这样会耗费大量的时间和内存。综上LWIP必须有个高效的数据包管理核心它即能海纳百川似的兼容各种类型的数据又能避免在各层之间的复制数据的巨大开销。数据包管理机构采用数据结构pbuf来描述数据包其源码如下structpbuf{structpbuf*nextvoid*payloaduttotlenutlenuttypeutflagsutref}这个看似简单的数据结构却够我讲一大歇的了!next字段指针指向下一个pbuf结构因为实际发送或接收的数据包可能很大而每个pbuf能够管理的数据可能很少所以往往需要多个pbuf结构才能完全描述一个数据包。所以所有的描述同一个数据包的pbuf结构Email:forrestfoxmailcom老衲五木出品需要链在一个链表上这一点用next实现。payload是数据指针指向该pbuf管理的数据的起始地址这里数据的起始地址可以是紧跟在pbuf结构之后的RAM也可能是在ROM上的某个地址而决定这点的是当前pbuf是什么类型的即type字段的值这在下面将继续讨论。len字段表示当前pbuf中的有效数据长度而totlen表示当前pbuf和其后所有pbuf的有效数据的长度。显然totlen字段是len字段与pbuf链中随后一个pbuf的totlen字段的和pbuf链中第一个pbuf的totlen字段表示整个数据包的长度而最后一个pbuf的totlen字段必和len字段相等。type字段表示pbuf的类型主要有四种类型这点基本上涉及到pbuf管理中最难的部分将在下节仔细讨论。文档上说flags字段也表示pbuf的类型不懂type字段不是说明了pbuf的类型吗?不过在源代码里初始化一个pbuf的时候是将该字段的值设为而在其他地方也没有用到该字段所以这里直接忽略掉。最后ref字段表示该pbuf被引用的次数。这里又是一个纠结的地方啊。初始化一个pbuf的时候ref字段值被设置为当有其他pbuf的next指针指向该pbuf时该pbuf的ref字段值加一。所以要删除一个pbuf时ref的值必须为才能删除成功否则删除失败。pbuf的类型令人很晕的东西。pbuf有四类:PBUFRAM、PBUFROM、PBUFREF和PBUFPOOL。下面一个一个的来看看各种类型的特点。PBUFRAM类型的pbuf主要通过内存堆分配得到的。这种类型的pbuf在协议栈中是用得最多的。协议栈要发送的数据和应用程序要传递的数据一般都采用这个形式。申请PBUFRAM类型时协议栈会在内存堆中分配相应的大小注意这里的大小包括如前所述的pbuf结构头大小和相应数据缓冲区他们是在一片连续的内存区的。下面来看看源代码是怎样申请PBUFRAM型的。其中p是pbuf型指针。p=(structpbuf*)memmalloc(LWIPMEMALIGNSIZE(SIZEOFSTRUCTPBUFoffset)LWIPMEMALIGNSIZE(length))可以看出系统是调用内存堆分配函数memmalloc进行内存分配的。分配空间的大小包括:pbuf结构头大小SIZEOFSTRUCTPBUF需要的数据存储空间大小length还有一个offset。关于这个offset也有一大堆可以讨论的东西不过先到此打住。总之分配成功的PBUFRAM类型的pbuf如下图:Email:forrestfoxmailcom老衲五木出品从图中可看出pbuf头和相应数据在同一片连续的内存区种注意payload并没有指向pbuf头结束即ref字段之后而是隔了一定的区域。这段区域就是上面的offset的大小这段区域用来存储数据的包头如TCP包头IP包头等。当然offset也可以是具体值是多少那就要看你是怎么个申请法了。如果还要深究你肯定会更晕了。PBUFPOOL类型和PBUFRAM类型的pbuf有很大的相似之处但它主要通过内存池分配得到的。这种类型的pbuf可以在极短的时间内得到分配。在接受数据包时LWIP一般采用这种方式包装数据。申请PBUFPOOL类型时协议栈会在内存池中分配适当的内存池个数以满足需要的申请大小。下面来看看源代码是怎样申请PBUFPOOL型的。其中p是pbuf型指针。p=mempmalloc(MEMPPBUFPOOL)可以看出系统是调用内存池分配函数mempmalloc进行内存分配的。分配成功的PBUFPOOL类型的pbuf如下图:图中是分配指定大小的数据缓冲的结果系统调用会分配多个固定大小的PBUFPOOL类型pbuf并把这些pbufs链成一个链表以满足用户的分配空间请求。PBUFROM和PBUFREF类型的pbuf基本相同它们的申请都是在内存堆中分配一个相应的pbuf结构头而不申请数据区的空间。这就是它们与PBUFRAM和PBUFPOOL的最大区别。PBUFROM和PBUFREF类型的区别在于前者指向ROM空间内的某段数据而后者指向RAM空间内的某段数据。下面来看看源代码是怎样申请PBUFROM和PBUFREF类型的。其中p是pbuf型指针。p=mempmalloc(MEMPPBUF)可以看出系统是调用内存池分配函数mempmalloc进行内存分配的。而此刻请求的内存池类型为MEMPPBUF而不是MEMPPBUFPOOL晕啊…这个太让人郁闷了。MEMPPBUF类型的内存池大小恰好为一个pbuf头的大小因为这种池是LWIP专为PBUFROM和PBUFREF类型的pbuf量身制作的。LWIP还是真的很周到啊它会为不同的数据结构量身定做不同类型的池。正确分配的PBUFROM或PBUFREF类型的pbuf其结构如下图:Email:forrestfoxmailcom老衲五木出品注:以上所有图片都来自文档《DesignandImplementationoftheLWIP:TCPIPStack》这些图都有个共同的错误即len和totlen字段位置搞反了窃喜。最后说明对于一个数据包它可能使用上述的任意的pbuf类型很可能的情况是一大串不同类型的pbufs连在一起用以保存一个数据包的数据。广告……下节看点关于pbuf的内存释放问题。Email:forrestfoxmailcom老衲五木出品pbuf释放释放释放释放牢骚发完GoOn。昨天说到了数据缓冲pbuf的内存申请今天继续来探究一下它的内存释放过程。由于pbuf的申请主要是通过内存堆分配和内存池分配来实现所以pbuf的释放也必须按照这两种情况分别讨论。别慌在展开讨论之前还得说说某个pbuf能被释放的前提。在LWIP中这点很容易判断因为前节说到pbuf的ref字段表示该pbuf被引用的次数当pbuf被创建时该字段的初始值为由此可判断当pbuf的ref字段为时该pbuf才可以被删除所以位于pbufs链表中间的pbuf结构是不会被删除成功的因为他们的ref值至少是。由此总之一下能被删除的pbuf必然是某个pbufs链的首节点。当然pbuf的删除工作远不如此的简单其中另一个需要特别注意的地方是想想很可能的情况是某个pbufs链的首节点删除成功后该pbufs链的第二个节点就自然的成为该pbufs链的首节点此时该节点的ref值可能变为(该节点没有被引用了)这种情况下该节点也会被删除因为LWIP认为它和第一个节点一起存储同一数据包。当第二个节点也被删除后LWIP又会去看看第三个节点是否满足删除条件…就这样一直删一下去。当然如果首节点删除后第二个节点的ref值大于表示该节点还在其他地方被引用不能再被删除删除工作至此结束。这段话写的很口水不如我们举个例子来看看这个删除过程。假如现在我们的pbufs链表有ABC三个pbuf结构连接起来结构为A>B>C利用pbuffree(A)函数来删除pbuf结构下面用ABC的几组不同ref值来看看删除结果:()>>函数执行后变为>节点BC仍在()>>函数执行后变为>>节点ABC仍在()>>函数执行后变为节点C仍在()>>函数执行后变为>>节点ABC仍在()>>函数执行后变为节点全部被删除。如果您能说醍醐灌顶那将是我最大的动力。当可以删除某个pbuf结构时LWIP首先检查这个pbuf是属于前节讲到的四个类型中的哪种根据类型的不同调用不同的内存释放函数进行删除。PBUFPOOL类型和PBUFROM类型、PBUFREF类型需要通过mempfree()函数删除PBUFRAM类型需要通过memfree()函数删除原因不解释。PBUFRAM类型来自于内存堆所以需通过memfree()函数将pbuf释放回内存堆。这里先得来看看内存堆的组织结构见下图在内存堆内部内存堆管理模块通过在每一个内存分配块的顶部放置一个比较小的结构体来保存内存分配纪录(注意这个小小的结构体是内存管理模块自动附加上去的独立于用户的申请大小)。这个结构体拥有三个成员变量两个指针和一个标志如图。next与prev分别指向内存的下一个和上一个分配块used标志表示该内存块是否已被分配。图中需要注意的两个地方first图中每个内存块的大小是不同且可能随时变化的。Second当系统初始化的时候整个内存堆就是一个内存块下图中是经过多次分配释放后内存堆呈现出来的结果。内存堆管理模块根据所申请分配的大小来搜索所有未被使用的内存分配块检索到的最先满足条件的内存块将分配给申请者注意这里并不包括前面说到的那个小小结构体所以用户得到的是used后的那个地址。当分配成功后内存管理模块会马上在已经分配走了的数据区后面再插一个小小的结构体并用next和prev指针将这个结构体串起来以便于Email:forrestfoxmailcom老衲五木出品下次分配。经过几次的申请与释放我们就看到了图中的内存堆组织模型。当内存释放的时候为了防止内存碎片的产生上一个与下一个分配块的使用标志会被检查如果他们中的任何一个还未被使用这个内存块将被合并到一个更大的未使用内存块中。内存堆管理模块是这样做的它根据用户提供的释放地址退后几个字节去寻找这个小小的结构体利用这个结构体来实现内存堆得合并等操作。已经分配的内存块被回收后使用标志used清零。当然如果上一个与下一个分配块都已被使用这时的释放就是最简单的情况但这也是产生内存堆碎片问题的根源。哎要了老

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

文档小程序码

使用微信“扫一扫”扫码寻找文档

1

打开微信

2

扫描小程序码

3

发布寻找信息

4

等待寻找结果

我知道了
评分:

/19

LwIP协议栈源码详解

仅供在线阅读

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利