LwIP TCP/IP协议栈设计与实现

        lwIP 是 TCP/IP 协议栈的实现。lwIP 协议栈实现的重点是减少内存使用量和代码大小,使 lwIP 适用于资源非常有限的小型客户端,例如嵌入式系统。为了减少处理和内存需求,lwIP 使用量身定制的 API,不需要任何数据复制。

        本文描述了 lwIP 的设计和实现。描述了协议实现和子系统(如存储器和缓冲区管理系统)中使用的算法和数据结构。本文还包括 lwIP API 的参考手册和使用 lwIP 的一些代码示例.

1 简介

在过去几年中,将计算机和支持计算机的设备连接到无线网络的正在稳步增加。计算机与日常设备越来越无缝集成,价格也在下降。与此同时,蓝牙[HNI+98]和IEEE 802.11b WLAN[BIG+97]等无线网络技术正在兴起。这在医疗保健、安全和安保、运输和加工业等领域产生了许多新的迷人场景。传感器等小型设备可以连接到现有的网络基础设施,如全球互联网,并从任何地方进行监控。

互联网技术已经证明自己具有足够的灵活性,可以适应过去几十年不断变化的网络环境。虽然最初是为ARPANET等低速网络开发的,但如今的互联网技术在广泛的链路技术上运行,在带宽和误码率方面具有截然不同®的特征。由于已经开发了大量使用互联网技术的应用程序,因此在未来的无线网络中使用现有的互联网技术是非常有利的。此外,全球互联网的大连通性是一个强大的激励因素。 

由于传感器等小型设备通常需要物理尺寸小且价格低廉,因此Internet协议的实现将不得不处理有限的计算资源和内存。此报告描述了称为 lwIP 的小型 TCP/IP 堆栈的设计和实现,该堆栈足够小,可以在最小的系统中使用。 

本报告的结构如下。第 2、3 和 4 节概述了 lwIP 堆栈,第 5 节介绍了操作系统仿真层,第 6 节介绍了内存和业务®管理。第 7 节介绍了 lwIP 的网络接口抽象,第 8、9 和 10 节介绍了 IP、UDP 和 TCP 协议的实现。第 11 节和第 12 节描述了如何与 lwIP 接口并介绍 lwIP API。第13节和第14节分析了实施情况。最后,第 15 节提供了 lwIP API 的参考手册,第 17 节和第 18 节展示了各种代码示例。

2 协议层次

TCP/IP 协议栈中的协议以分层方式设计,其中每个协议层解决通信问题的特定部分。这种分层可以作为设计协议实现的指南,因为每层协议都可以单独实现。但是,以严格分层的方式实现协议可能会导致协议层之间的通信开销降低整体性能的情况[Cla82a]。为了克服这些问题,协议的某些内部实现信息可以让其他层协议知道。必须小心,以便仅在不同协议层之间共享重要信息。


大多数 TCP/IP 实现在应用层和较低协议层之间保持严格的划分,而较低层之间可以或多或少地交错。在大多数操作系统中,下层协议作为操作系统内核的一部分实现,具有与应用层进程通信的入口点。应用程序以 TCP/IP 实现的抽象视图呈现,其中网络通信与进程间通信®或文件 I/O 的差异很小。这样做的含义是,由于应用程序不知道较低层使用的机制®,因此它无法利用此信息来重用®常用数据。此外,当应用程序发送数据时,此数据必须从应用程序进程的内存空间复制到内部缓存®中,然后才能由网络代码处理。


最小系统(如 lwIP 的目标系统)中使用的操作系统通常不会在内核和应用程序进程之间保持严格的保护屏障。这允许通过共享内存在应用程序和下层协议之间使用更宽松的方案进行通信。特别是,应用层可以了解较低层使用的处理®机制。因此,应用程序可以更有效地复用内存,此外,由于应用程序进程可以使用与网络代码相同的内存,因此应用程序可以直接读取和写入内部模块®,从而节省了执行复制的费用。

3 概述

与许多其他TCP/IP实现一样,分层协议设计已成为lwIP实现设计的指南。每个协议都作为自己的模块实现,一些函数充当每个协议的入口点。即使协议是单独实现的,如上所述,为了提高处理速度和内存使用方面的性能,也会存在一些违反分层的设计。例如,在验证传入 TCP 段的校验和以及解复用分段时,TCP 模块必须知道分段的源和目标 IP 地址。TCP 模块不是通过函数调用将这些地址传递给 TCP,而是知道 IP 协议头的结构,因此可以自行提取此信息。


lwIP 由几个模块组成。除了实现TCP/IP协议(IP,ICMP,UDP和TCP)的模块外,还实现了许多支持模块。支持模块由操作系统仿真层(在第 5 节中描述)、buffer 和内存管理子系统(在第 6 节中描述)、网络接口功能(在第 7 节中描述)以及用于计算 Internet 校验和的函数组成。lwIP 还包括一个抽象 API,在第 12 节中进行了描述。

4 进程模型

协议实现的过程模型描述了系统被划分为不同过程的方式。用于实现通信协议的一种进程模型是让每个协议作为独立进程运行。使用此模型,将强制实施严格的协议分层,并且必须严格定义协议之间的通信点。虽然这种方法有其优点,例如可以在运行时添加协议,理解代码和调试通常更容易,但也存在缺点。如前所述,严格的分层并不总是实现协议的最佳方式。此外,更重要的是,对于跨越的每一层,必须进行上下文切换。对于接收到的 TCP 段,这意味着三个上下文切换,从网络接口的设备驱动程序到 IP 进程,到 TCP 进程,最后到应用程序进程。在大多数操作系统中,上下文切换相当昂贵。

另一种常见的方法是让通信协议驻留在操作系统的内核中。在内核实现通信协议的的情况下,应用程序进程通过系统调用与协议进行通信。通信协议彼此之间没有严格划分,但可以使用跨协议分层的技术。

lwIP 使用进程模型为所有协议都驻留在单个进程中,因此与操作系统内核分离。应用程序可以驻留在 lwIP 进程中,也可以驻留在单独的进程中。TCP/IP 堆栈和应用程序之间的通信是通过函数调用(当应用程序与 lwIP 共享进程)或通过更抽象的 API 来完成的。

将 lwIP 实现为用户空间进程而不是在操作系统内核中实现既有优点也有缺点。将 lwIP 作为一个进程的主要优点是可以在不同的操作系统之间移植。由于 lwIP 设计为在通常不支持交换进程也不支持虚拟内存的小型操作系统中运行,因此如果将 lwIP 进程的一部分交换或分页到磁盘,则必须等待磁盘活动而导致的延迟不会成为问题。然而,在有机会为请求提供服务之前必须等待调度量程的问题仍然是一个问题,但是 lwIP 的设计中没有任何内容可以阻止它以后在操作系统内核中实现。

5 操作系统仿真层

为了使 lwIP 可移植,操作系统特定的函数调用和数据结构不直接在代码中使用。相反,当需要此类函数时,将使用操作系统仿真层。操作系统仿真层为操作系统服务(如计时器、进程同步和消息传递机制)提供统一的接口。原则上,将 lwIP 移植到其他操作系统时,只需要实现该特定操作系统的操作系统仿真层。

操作系统仿真层提供 TCP 使用的计时器功能。操作系统仿真层提供的计时器是粒度至少为 200 毫秒的一次性计时器,在发生超时时调用注册函数。

需要提供的唯一进程同步机制是信号量。即使信号量在底层操作系统中不可用,它们也可以由其他同步原语(如条件变量或锁)来模拟。

消息的传递是通过一个简单的机制完成的,该机制使用称为邮箱的抽象。邮箱有两个操作:发布和提取。发布操作不会阻塞该过程; 相反,发布到邮箱的消息由操作系统仿真层放入一个队列中,直到另一个进程提取它们。即使基础操作系统没有对邮箱机制的原生支持,也可以使用信号量轻松实现它们。

6 缓冲区和内存管理

通信系统中的内存和缓冲区管理系统必须准备好容纳大小非常不同的缓冲区,从包含具有数百字节数据的全尺寸 TCP 段的缓冲区到仅包含几个字节的短 ICMP 回显应答。此外,为了避免复制,应该可以让缓冲区的数据内容驻留在不受网络子系统管理的内存中,例如应用程序内存或 ROM。

6.1 数据包缓冲区 - pbufs

pbuf 是 lwIP 对数据包的内部表示形式,专为满足最小堆栈的特殊需求而设计。Pbufs 类似于 BSD 实现中使用的 mbufs。pbuf 结构既支持分配动态内存来保存数据包内容,也支持让数据包数据驻留在静态内存中。Pbufs 可以在一个链表中链接在一起,称为 pbuf 链,以便数据包可以跨越多个 pbuf。

Pbuf有三种类型:PBUF_RAM、PBUF_ROM和PBUF_POOL。图 1 中所示的 pbuf 表示PBUF_RAM类型,并将数据包数据存储在由 pbuf 子系统管理的内存中。图 2 中的 pbuf 是链式 pbuf 的示例,其中链中的第一个 pbuf 属于 PBUF_RAM 类型,第二个是 PBUF_ROM 类型,这意味着它的数据位于不受 pbuf 系统管理的内存中。第三种类型的 pbuf(PBUF_POOL)如图 3 所示,由从固定大小的 pbuf 池中分配的固定大小的 pbuf 组成。一个pbuf链可以由多种类型的pbuf组成。

这三种类型有不同的用途。类型 PBUF_POOL 的 Pbuf 主要由网络设备驱动程序使用,因为分配单个 pbuf 的操作速度很快,因此适合在中断处理程序中使用。当应用程序发送位于应用程序管理的内存中的数据时,将使用PBUF_ROM PBUF。在将 pbuf 移交给 TCP/IP 堆栈后,此数据可能无法修改,因此此 pbuf 类型的主要用途是当数据位于 ROM 中时(因此得名 PBUF_ROM)。附加到PBUF_ROM pbuf 中的数据前面的协议头存储在PBUF_RAM pbuf 中,该 pbuf 链接在PBUF_ROM pbuf 的前面,如图 2 所示。

当应用程序发送动态生成的数据时,也会使用 PBUF_RAM 类型的 Pbuf。在这种情况下,pbuf 系统不仅为应用程序数据分配内存,还为将附加到数据前面的标头分配内存。如图 1 所示。pbuf 系统无法提前知道哪些标头将附加到数据中,并假设最坏的情况。标头的大小在编译时是可配置的。

实质上,传入的 pbuf 属于 PBUF_POOL 型,传出的 pbuf 属于PBUF_ROM或PBUF_RAM型。

pbuf 的内部结构如图 1 至 3 所示。pbuf 结构由两个指针、两个长度字段、一个标志字段和一个引用计数组成。如果是 pbuf 链next字段是指向下一个 pbuf 的指针。payload指针指向 pbuf 中数据的开头。len 字段包含 pbuf 的数据内容的长度。tot_len字段包含当前 pbuf 的长度与 pbuf 链中后续 pbuf 的所有 len 字段的总和。换句话说,tot_len字段是 pbuf 链中下一个 pbuf 中的 len 字段和tot_len字段值的总和。flags字段指示 pbuf 的类型,ref字段包含引用计数。next和payload字段是本机指针,其大小因所使用的处理器体系结构而异。两个长度字段是 16 位无符号整数,flags和 ref 字段是 4 位宽。pbuf 结构的总大小取决于所使用的处理器体系结构中指针的大小以及处理器体系结构可能出现的最小对齐方式。在具有 32 位指针和 4 字节对齐的体系结构上,总大小为 16 字节,在具有 16 位指针和 1 字节对齐的体系结构上,总大小为 9 字节。

pbuf 模块提供了用于操作 pbuf 的函数。pbuf 的分配由函数 pbuf_alloc()完成,该函数可以分配上述三种类型中的任何一种的 pbuf。函数 pbuf_ref() 会增加引用计数。释放由函数 pbuf_free() 进行,该函数首先减少 pbuf 的引用计数。如果引用计数达到零,则释放 pbuf。函数 pbuf_realloc() 缩小 pbuf,使其占用足够的内存来覆盖数据的大小。函数 pbuf_header()调整有效负载指针和长度字段,以便可以在 pbuf 中的数据前面附加一个标头。函数 pbuf_chain() 和 pbuf_dechain()用于链接 pbuf。

6.2 内存管理

 pbuf 的内存管理器非常简单。它处理连续内存区域的分配和释放,并且可以缩小以前分配的内存块的大小。内存管理器使用系统中总内存的专用部分。这可确保网络系统不会使用所有可用内存,并且如果网络系统已使用其所有内存,也不会干扰其他程序的操作。

在内部,内存管理器通过在分配的每个内存块顶部放置一个小结构来跟踪分配的内存。此结构(图 4)在内存中保存两个指向下一个和上一个分配块的指针。它还有一个使用标志,指示分配块是否已分配。

 

通过在内存中搜索足够大的未使用分配块来分配内存。使用first-fit原则,以便使用足够大的第一个块。释放分配块时,使用的标志设置为零。为了防止碎片,检查下一个和上一个分配块的已用标志。如果其中任何一个未使用,则这些块将合并为一个较大的未使用块。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值