《嵌入式 - Lwip开发指南》第1章 LWIP概述

Posted Bruceoxl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《嵌入式 - Lwip开发指南》第1章 LWIP概述相关的知识,希望对你有一定的参考价值。

1.1 LwIP简介

LwIP 全名为 Light weight IP,意思是轻量化的 TCP/IP 协议,是瑞典计算机科学院(SICS)的 Adam Dunkels 开发的一个小型开源的 TCP/IP 协议栈。LwIP 的设计初衷是:用少量的资源消耗(RAM)实现一个较为完整的 TCP/IP 协议栈,其中“完整”主要指的是 TCP 协议的完整性, 实现的重点是在保持 TCP 协议主要功能的基础上减少对 RAM 的占用。此外 LwIP既可以移植到操作系统上运行,也可以在无操作系统的情况下独立运行。

LwIP有无操作系统的支持都可以运行。LwIP实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用,它只需十几KB的RAM和40K左右的ROM就可以运行,这使LwIP协议栈适合在低端的嵌入式系统中使用。

lwIP协议栈主要关注的是怎么样减少内存的使用和代码的大小,这样就可以让lwIP适用于资源有限的小型平台例如嵌入式系统。为了简化处理过程和内存要求,lwIP对API进行了裁减,可以不需要复制一些数据。

1.2 LwIP的特性

1.2.1 LwIP 的主要特性

(1)支持 ARP 协议(以太网地址解析协议)。

(2)支持 ICMP 协议(控制报文协议),用于网络的调试与维护。

(3)支持 IGMP 协议(互联网组管理协议),可以实现多播数据的接收。

(4)支持 UDP 协议(用户数据报协议)。

(5)支持 TCP 协议(传输控制协议),包括阻塞控制、 RTT 估算、快速恢复和快速转发。

(6)支持 PPP 协议(点对点通信协议) ,支持 PPPoE。

(7)支持 DNS(域名解析)。

(8)支持 DHCP 协议,动态分配 IP 地址。

(9)支持 IP 协议,包括 IPv4、 IPv6 协议,支持 IP 分片与重装功能,多网络接口下的数据包转发。

(10)支持 SNMP 协议(简单网络管理协议)。

(11)支持 AUTOIP,自动 IP 地址配置。

(12)提供专门的内部回调接口(Raw API),用于提高应用程序性能。

(13)提供可选择的 Socket API、 NETCONN API (在多线程情况下使用) 。



1.2.2 LwIP 的优点

LwIP 在嵌入式中使用有以下优点:

(1)资源开销低,即轻量化。 LwIP 内核有自己的内存管理策略和数据包管理策略, 使得内核处理数据包的效率很高。另外, LwIP 高度可剪裁,一切不需要的功能都可以通过宏编译选项去掉。 LwIP 的流畅运行需要 40KB 的代码 ROM 和几十 KB 的RAM, 这让它非常适合用在内存资源受限的嵌入式设备中。

(2)支持的协议较为完整。几乎支持 TCP/IP 中所有常见的协议,这在嵌入式设备中早已够用。

(3)实现了一些常见的应用程序: DHCP 客户端、 DNS 客户端、 HTTP 服务器、MQTT 客户端、 TFTP 服务器、 SNTP 客户端等等。

(4)同时提供了三种编程接口: RAW API、 NETCONN API(注: NETCONN API 即为 Sequential API, 为了统一,下文均采用 NETCONN API) 和 Socket API。 这三种 API 的执行效率、易用性、可移植性以及时空间的开销各不相同,用户可以根据实际需要,平衡利弊,选择合适的 API 进行网络应用程序的开发。

(5)高度可移植。其源代码全部用 C 实现,用户可以很方便地实现跨处理器、跨编译器的移植。另外,它对内核中会使用到操作系统功能的地方进行了抽象,使用了一套自定义的 API,用户可以通过自己实现这些 API,从而实现跨操作系统的移植工作。

(6)开源、免费,用户可以不用承担任何商业风险地使用它。

(7) 相比于嵌入式领域其它的 TCP/IP 协议栈,比如 uC-TCP/IP、 FreeRTOS-TCP 等,LwIP 的发展历史要更悠久一些,得到了更多的验证和测试。 LwIP 被广泛用在嵌入式网络设备中, 国内一些物联网公司推出的物联网操作系统,其 TCP/IP 核心就是 LwIP;物联网知名的 WiFi 模块 ESP8266,其
TCP/IP 固件,使用的就是 LwIP。

LwIP 尽管有如此多的优点,但它毕竟是为嵌入式而生, 所以并没有很完整地实现TCP/IP 协议栈。相比于 Linux 和 Windows 系统自带的 TCP/IP 协议栈, LwIP 的功能不算完整和强大。 但对于大多数物联网领域的网络应用程序,LwIP 已经足够使用了。



1.3 LwIP源码下载及文件说明

1.3.1 LwIP 源码下载

LwIP 的代码已经交给 Savannah 托管。

LwIP 项目地址

点击主页的“Project Homepage”。

将会跳转到 LwIP 的官方说明文档,如下图所示,我们可以通过这个网页获得关于 LwIP 的很多信息,包括 LwIP 的使用注意、数据的拷贝、系统初始化流程、多线程中要注意的问题、优化方法、内核模块的分类介绍、内核数据结构、内核重要全局变量、内核源码文件等。这些内容专业性比较强,不建议初学时在它上面花费精力。

点击“Download Area”,可以下载到 LwIP所有版本的源代码包和 contrib 包。你每点击一个红色字体的资源链接,浏览器就会开启一个 ftp连接,帮助你下载想要的文件到电脑中。

LWIP下载链接

Lwip源码最新的版本是2.1.2。

【注】如果无法访问,请用以下镜像:

镜像一

镜像二

另外,还要下载contrib包,最新版本是2.1.0。contrib 包里面装的是移植和应用 LwIP 的一些 demo,即应用示例。contrib包不属于LwIP 内核的一部分,里面的很多内容来自开源社区的贡献,因此 contrib 包的版本管理不像内核源码那样严格和规范,但也是很有参考价值的。



1.3.2 LwIP文件说明

在前面,我们下载两个包: lwip-2.1.2.zip(源码包)和 contrib-2.1.0.zip(contrib 包)。解压以后会得到两个文件夹,如下图所示。

1.3.2.1 源码文件

我们先打开“lwip-2.1.2”文件夹,如下图所示

(1)CHANGELOG 文件记录了 LwIP 在版本升级过程中源代码发生的变化。

(2)COPYING 文件记录了 LwIP 这个开源软件的 license。一个软件开源,不代表你能无限制地使用它,你需要在使用它的过程中遵守一定的规则,这些规则就是 license。大家可以用记事本打开这个 COPYING 文件看看它的内容。开源软件的 license 有很多种, LwIP 的属于 BSD License。LwIP 的开源程度是很高的,你几乎可以无限制地使用它。

(3)FILES 文件用于介绍当前目录下的目录信息。

(4)README 文件对 LwIP 进行了一个简单的介绍。

(5)UPGRADING 文件记录了 LwIP 每个大版本的更新,会对用户使用和移植 LwIP 造成的影响。所谓大版本更新指的是: 1.3.x - 1.4.x –2.0.x –2.1.x。小版本更新,比如 2.0.1 –2.0.2 –2.0.3,这个过程只是一些 bug 的修复和性能的改善,不会对用户的使用造成影响。用户只要将原有工程的目录中与 LwIP 相关的旧版本文件替换成新版本的文件,重新编译,就能直接使用。

(6) doc 文件夹里面是关于 LwIP 的一些文档,可以看成是应用和移植 LwIP 的指南。但是这些文档比较零散,不成体系,而且纯文本阅读起来很费劲,阅读意义不是很大。

(7) test 文件夹里面是测试 LwIP 内核性能的源码,将它们和 LwIP 源码加入到工程中一起编译,调用它们提供的函数,可以获得许多与 LwIP 内核性能有关的指标。这种内核性能测试功能,只有非常专业的人士才用的到。

(8) src 文件夹里面就是我们最关心的 LwIP 源码文件。文件目录如下:

  • api 文件夹里面装的是 NETCONN API 和 Socket API 相关的源文件,只有在操作系统的环境中,才能被编译。
  • apps 文件夹里面装的是应用程序的源文件,包括常见的应用程序,如 httpd、mqtt、tftp、sntp、snmp等。
  • core 文件夹里面是 LwIP 的内核源文件。

ipv4 文件夹里面是与 IPv4 模块相关的源文件,它们实现了 IPv4 协议规定的对数据包的各种操作。 ipv4 文件夹中还包括一些并非属于 IP 协议,但会受 IP 协议影响的协议源文件,包括 DHCP、ARP、 ICMP、 IGMP。

ipv6 文件夹里面是与 IPv6 模块相关的源文件,它们实现了 IPv6 协议规定的对数据包的各种操作。 ipv6 文件夹中还包括一些并非属于 IP 协议,但会受 IP 协议影响的协议源文件,包括 DHCP、ARP、 ICMP、 IGMP。

altcp.c、 altcp_alloc.c、 altcp_tcp.c 等文件是应用程序分层 TCP 连接 API,从 TCPIP 线程使用,是一个抽象层,可以模拟应用程序的 tcp 回调 API,同时防止直接链接,这样,应用程序可以使用其他应用程序层协议在 TCP 之上而不知道细节(例如 TLS,代理连接),此类接口我们并没有怎么使用,或者如果选择使用安全的加密传输的话,可以配合 mbed TLS 使用。

def.c 文件定义了一些基础类函数,比如主机序和网络序的转换、字符串的查找和比较、整数转换成字符串等,这些函数会被 LwIP 内核的很多模块所调用。在 include 目录里面的 def.h 文件对外声明了 def.c 所实现的函数,同时定义了许多宏,能实现一些基础操作,比如取最大值、取最小值、计算数组长度等,这些宏同样也被内核的许多模块所调用。我们经常可以看到某个内核的源文件在开始的地方 #include “def.h”。

dns.c 文件实现了域名解析的功能,有了它,用户就可以在知道服务器域名的情况下,获得该服务器的 IP 地址。很多时候我们只记得服务器域名而不记得服务器IP地址,例如“www.baidu.com”就是一个域名,通过 dns 功能,我们就可以得到与服务器域名对应的 IP 地址,这给用户使用带来很大的方便。
inet_chksum.c 文件提供了 LwIP 所需的校验和功能,在 IP、 UDP、 TCP 协议的实现中,需要计算校验和。

init.c 文件对 LwIP 的用户宏配置进行了检查,会将配置错误和不合理的地方,通过编译器的 #error和 #warning 功能表示出来。另外, init.c 定义了 lwip_init 初始化函数,这个函数会依次对 LwIP 的各个模块进行初始化。

ip.c 文件实现了 IP 协议相关的函数,但只是封装了 ipv4 和 ipv6 文件夹中的函数。

mem.c 文件实现了动态内存池管理机制,使得 LwIP 内核的各个模块可以灵活地申请和释放内存。

memp.c 文件实现了静态内存堆管理机制,使得 LwIP 内核的各个模块可以快速地申请和释放内存。

netif.c 文件实现了网卡的操作,比如注册/删除网卡、使能/禁能网卡、设置网卡 IP 地址等等。 netif.c与 include 目录中的 netif.h 文件共同构成了 LwIP 的 netif 模块,它对网卡进行了抽象,使得 LwIP内核可以方便地管理多个特性各异的物理网卡。

pbuf.c 文件实现了 LwIP 对网络数据包的各种操作。网络数据包在 LwIP 内核中以 pbuf 结构体的形式存在,这提高了 LwIP 内核对数据包处理效率,以及提高了数据包在各层之间递交的效率。pbuf 结构体也是我们使用 RAW/Callback API 进行网络应用程序开发的关键。

raw.c 文件实现了一个传输层协议的框架,我们可以在它的基础上修改和添加代码,实现自定义的传输层协议,与 UDP/TCP 一样,它可以与 IP 层直接进行交互。这类似 RAW Socket。在实际的应用中,我们常用 UDP 和 TCP 作为传输层协议。但有时,底层网络开发人员会嫌 UDP的可靠性太差,或者 TCP 虽然可靠性强,但是很耗费时间和内存,他们需要根据实际需求,平衡利弊,定义自己的传输层协议。 LwIP 的 raw 模块可以满足他们的需求。

stat.c 文件实现了 LwIP 内核的统计功能,使用户可以实时地查看 LwIP 内核对网络数据包的处理情况。

sys.c 文件构成了 LwIP 的 sys 模块,它提供了与临界区相关的操作。
tcp.c、 tcp_in.c 和 tcp_out.c 文件实现了 TCP 协议,包括对 TCP 连接的操作、对 TCP 数据包的输入输出操作和 TCP 定时器,它们和 include 目录中名称带 tcp 的头文件共同构成了 LwIP 的 TCP 模块。 TCP 模块的实现是 LwIP 的最大特点,它以很小的资源开销几乎实现了 TCP 协议中规定的全部内容。 TCP 协议是非常复杂的协议,这几个与 TCP 模块相关的文件占据了 LwIP 内核的绝大部分。

timeouts.c文件 定义了 LwIP 内核的超时处理机制。 LwIP 内核中多个模块的实现需要借助超时处理机制,包括 ARP 表项的时间统计、 IP 分片报文的重装、 TCP 的各种定时器、实现各种应用层协议需要的超时处理。

udp.c 文件实现了 UDP 协议,包括对 UDP 连接的操作和 UDP 数据包的操作。
 include 文件夹里面是 LwIP 所有模块对应的头文件。
 netif 文件夹里面是与网卡移植有关的文件,这些文件为我们移植网卡提供了模板,我们可以直接使用。

总的来说,LwIP 内核是由一系列模块组合而成的,包括: TCP/IP 协议栈的各种协议、内存管理模块、数据包管理模块、网卡管理模块、网卡接口模块、基础功能类模块、 API 模块。每个模块是由相关的几个源文件和头文件组成的,通过头文件对外声明一些函数、宏、数据类型,使得其它模块可以方便地调用此模块的功能。而构成每个模块的头文件都被组织在了 include 目录中,而源文件则根据类型被分散地组织在 api、 apps、 core、 netif 目录中。



1.3.2.2 contrib文件

我们打开之前下载好的 contrib-2.1.0 文件夹,目录内容如下:

(1) addons 目录。 LwIP 中很多模块的实现,都是可以由用户干预的,比如校验和、 TCP 初始序列号。 LwIP 的内核代码,通过宏编译选项的设置,可以将内核中某些模块的实现方法配置成 LwIP默认的方法,或者用户自定义的方法。用户自定义的方法通常需要用户在钩子函数中实现。在实际应用中,我们采用内核默认的方法就足够了,只有在非常特定的场合下,为了性能、资源开销等因素的考虑,我们可能会需要自己实现相关的模块,或者说编写相应的钩子函数。

(2) apps 目录里实现了很多应用层协议。 LwIP 源码包中也有 apps 目录,但源码包中 apps 目录下的应用程序全部用 RAW/Callback API 实现,属于内核代码的一部分。而此 apps 目录里的应用程序可以是由三种 API 中的任何一种实现的。读者可以把它看成是内核源码所提供的应用程序的一个补充。

(3) examples 目录里是一些 LwIP 的应用示例。在使用 LwIP 开发应用程序时会出现的典型问题,比如如何移植网卡、如何使用 LwIP 的 API、如何使用源码中提供的应用程序,对于这些问题,这个目录为我们提供了参考。我们在后续的章节中,会使用这个目录中的例子来讲解 LwIP 的应用程序。

(4) ports 目录里是一些移植文件,它可以帮助我们将 LwIP 移植到某个具体的操作系统中。目前这个目录所提供的移植文件,支持 RTOS、 UNIX、 Win32。



1.4 LwIP编程接口

LwIP 提供了三种编程接口,分别为 RAW/Callback API、 NETCONN API、 SOCKET/ BSD API。它们的易用性从左到右依次提高,而执行效率从左到右依次降低,用户可以根据实际情况,平衡利弊,选择合适的 API 进行网络应用程序的开发。

1.4.1 RAW API

RAW/Callback API 是 LwIP 的一大特色,在没有操作系统支持的裸机环境中,只能使用这种 API进行开发,同时这种 API 也可以用在操作系统环境中。RAW API把协议栈和应用程序放到一个进程里边,该接口基于函数回调技术,使用该接口的应用程序可以不用进行连续操作。为了接收数据,应用程序会向协议栈注册一个回调函数。该回调函数与特定的连接相关联,当该关联的连接到达一个信息包,该回调函数就会被协议栈调用。而且,为了能够保存程序的特定状态,可以向回调函数传递一个指定的状态,并且这个指定的状态是独立于 TCP/IP 协议栈的。在有操作系统的环境中,如果使用 RAW/Callback API,用户的应用程序就以回调函数的形式成为了内核代码的一部分,用户应用程序和内核程序会处于同一个线程之中,这就省去了任务间通信和切换任务的开销了。
RAW API既有优点也有缺点。

优点

(1) RAW API既支持无操作系统的环境有支持有操作系统的环境。
(2) 在有操作系统的环境中,应用程序和TCP/IP协议栈驻留在同一个进程中,那么发送和接收数据就不再产生进程切换。可以提高应用程序的效率,大大节省内存开销。

缺点

(1) 基于回调函数开发应用程序时的思维过程比较复杂,这就使得应用程序编写难度加大且代码不易被理解。
(2) 应用程序不能使自己陷入长期的连续运算中,这样会导致通讯性能下降,从而造成丢包的现。原因是TCP/IP处理与连续运算是不能并行发生的。这个缺点可以通过把应用程序分为两部分来克服,一部分处理通讯,一部分处理运算。

1.4.2 NETCONN API

在操作系统环境中,可以使用 NETCONN API 或者 Socket API 进行网络应用程序的开发。 NETCONN API 是基于操作系统的 IPC 机制(即信号量和邮箱机制)实现的,它的设计将 LwIP 内核代码和网络应用程序分离成了独立的线程。如此一来, LwIP 内核线程就只负责数据包的 TCP/IP封装和拆封,而不用进行数据的应用层处理,大大提高了系统对网络数据包的处理效率。

在操作系统环境中, LwIP 内核会被实现为一个独立的线程,名为 tcpip_thread,使用 NETCONNAPI 或者 Socket API 的应用程序处在不同的线程中,我们可以根据任务的重要性,分配不同的优先级给这些线程,从而保证重要任务的时效性,分配优先级的原则具体见下表。

线程优先级
LwIP内核线程tcpip_thread很高
重要的网络应用程序
不太重要而且处理数据比较耗时的网络应用程序

NETCONN API 使用了操作系统的 IPC 机制,对网络连接进行了抽象,用户可以像操作文件一样操作网络连接(打开/关闭、读/写数据)。但是 NETCONN API 并不如操作文件的 API 那样简单易用。举个例子,调用 f_read 函数读文件时,读到的数据会被放在一个用户指定的数组中,用户操作起来很方便,而 NETCONN API 的读数据 API,就没有那么人性化了。用户获得的不是一个数组,而是一个特殊的数据结构 netbuf,用户如果想使用好它,就需要对内核的 pbuf 和 netbuf 结构体有所了解,我们会在后续的章节中对它们进行讲解。 NETCONN API 之所以采取这种不人性的设计,是为了避免数据包在内核程序和应用程序之间发生拷贝,从而降低程序运行效率。当然,用户如果不在意数据递交时的效率问题,也可以把 netbuf 中的数据取出来拷贝到一个数组中,然后去处理这个数组。

NETCONN API 的优缺点如下:
(1)相较于 RAW/Callback API, NETCONN API 简化了编程工作,使用户可以按照操作文件的方式来操作网络连接。但是,内核程序和网络应用程序之间的数据包传递,需要依靠操作系统的信号量和邮箱机制完成,这需要耗费更多的时间和内存,另外还要加上任务切换的时间开销,效率较低。
(2)相较于 Socket API, NETCONN API 避免了内核程序和网络应用程序之间的数据拷贝,提高了数据递交的效率。但是, NETCONN API 的易用性不如 Socket API 好,它需要用户对 LwIP 内核所使用数据结构有一定的了解。

1.4.3 SOCKET API

Socket,即套接字,它对网络连接进行了高级的抽象,使得用户可以像操作文件一样操作网络连接。Socket 已经成为了网络编程的标准。在不同的系统中,运行着不同的 TCP/IP 协议,但是只要它实现了 Socket 的接口,那么用Socket 编写的网络应用程序就能在其中运行。

不同的系统有自己的一套 Socket 接口。 Windows 系统中支持的是 WinSock, UNIX/Linux 系统中支持的是 BSD Socket,它们虽然风格不一致,但大同小异。 LwIP 中的 Socket API 是 BSD Socket。
SOCKET/BSD API提供了基于open-read-write-close模型的UNIX标准API,它的最大特点是使应用程序移植到其它系统时比较容易,但用在嵌入式系统中效率比较低,占用资源多。这对于我们的嵌入式应用有时是不能容忍的。





欢迎访问我的网站

BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书


欢迎订阅我的微信公众号

以上是关于《嵌入式 - Lwip开发指南》第1章 LWIP概述的主要内容,如果未能解决你的问题,请参考以下文章

《嵌入式 - Lwip开发指南》第3章 移植LWIP(无系统)

《嵌入式 - Lwip开发指南》第3章 移植LWIP(无系统)

《嵌入式 - Lwip开发指南》第2章 LWIP开发环境简介

《嵌入式 - Lwip开发指南》第2章 LWIP开发环境简介

《嵌入式 - Lwip开发指南》第5章 LWIP测速

《嵌入式 - Lwip开发指南》第4章 移植LWIP(基于RT-Thead系统-以太网+Wifi)