lwIP 是 TCP/IP 协议套件的一个小型独立实现。lwIP TCP/IP 实现的重点是减少 RAM 使用同时仍然有一个完整的 TCP。 这使得 lwIP 适合使用在具有数 10 千字节的可用 RAM 和空间大约 40 千字节的代码 ROM 的嵌入式系统中。
lwIP 最开始由 Adam Dunkels 在瑞典计算机科学研究所(SICS)的计算机与网络体系结构(CNA)实验室时开发,现在由全球开发人员网络开发和维护。
版本变更
2.1.x
LWIP 从 2.0.3 版本,直接跳到了 2.1.0 版本,又是一个大的版本更新。增加了一些功能!同时源码的目录结构也有了一定的变化(增加了一些文件)! 按照 LWIP 的发布策略,以后 2.1.x 都是从 2.1.0 版本开始的 BUG 修复版本,最新的 BUG 修复版本是 2.1.2。具体变化参见源码目录下的 CHANGELOG 文件。下图显示了 2.0.3 版 和 2.1.2 版的文件对比差异:
关于这个版本,官网有个这么一句话 “ Oh, and this will be the last release with a separate contrib repository. I’ll be merging the files from contrib into the main repository soon after the release. In the git world, having two separate repositories just doesn’t work nice. ” 。大意就是,这是带有独立 contrib 库的最后一版本,后续会将 contrib 合并到 LWIP 主版本库中。
其次,如果详细去看 LWIP 的说明文档,会发现文档中有些描述还是之前的,并没有跟随版本变更而改变!例如在doc
目录下,原来的系统结构模板文件名为sys_arch.txt
,在此版本没有了这个文件,但是说明文档中,仍然有这部分的介绍!
2.0.x
LWIP 从 1.4.1 版本,直接跳到了 2.0.x 版本,是一个大的版本更新。源码结构变化比较大! 按照 LWIP 的发布策略,以后 2.0.x 都是从 2.0.0 版本开始的 BUG 修复版本,最终的 BUG 修复版本是 2.0.3。2.0.3 版本修复自 2.0.2 以来的一些错误,没有任何功能的变化。具体如下:
- 2017-09-11: Simon Goldschmidt
- tcp_in.c: fix bug #51937 (leaking tcp_pcbs on passive close with unacked data)
- 2017-08-02: Abroz Bizjak/Simon Goldschmidt
- multiple fixes in IPv4 reassembly (leading to corrupted datagrams received)
- 2017-03-30: Simon Goldschmidt
- dhcp.c: return ERR_VAL instead of asserting on offset-out-of-pbuf
- 2017-03-23: Dirk Ziegelmeier
- dhcp.h: fix bug #50618 (dhcp_remove_struct() macro does not work)
1.x.x
最终版本为 1.4.1。
源码目录文件
要使用 LWIP 的源码由两部分组成,分别为 LWIP 和 contrib 。这两个是由两个独立的版本库,并且由不同的人来负责的!我们在实际使用 LWIP 时,这两部分都是需要使用的!
其中,contrib 中是一些和平台移植相关的代码,LWIP 则是 TCP/IP 协议栈的核心源码!
下面我就以 2.0.3 版为例!从 1.4.1 到 2.0.3(从 2.0.0 开始),LwIP 的源码有了一定的变化,甚至于源码的文件结构也不一样,内部的一些实现源文件也被更新和替换了。其源码目录结构如下所示(对于简单的文件以注释的形式给出,核心源码下文会详细说明):
LWIP-2.0.3
│ CHANGELOG // 版本更新记录,从中可以看到 LwIP 不同版本的变化
│ COPYING // 版权说明
│ FILES // 其中说明了其所在目录下的各目录或文件的用途。在不同的目录下会有不同的该文件
│ README // 简介文档
│ UPGRADING // 版本升级后可能出现不兼容,该文档记录了从老版本升级需要修改的地方。对于升级自己使用的 LwIP 版本时很有用处。
├─doc
│ │ contrib.txt // LwIP 作为开源软件,如果想要为其做贡献,则需要遵循一定的准则,例如:提交代码的风格、报告Bug等。该文档给出了详细的贡献准则。
│ │ doxygen_docs.zip // 用 doxygen 生成的 LwIP 的配套文档
│ │ FILES // 其中说明了该目录下的每个文件的用途
│ │ mdns.txt // MDNS 的说明文档
│ │ mqtt_client.txt
│ │ NO_SYS_SampleCode.c
│ │ ppp.txt // lwIP的PPP接口文档
│ │ rawapi.txt // 告诉读者怎样使用协议栈的 Raw/Callback API 进行编程
│ │ savannah.txt // 说明了如何获取当前的开发源代码
│ │ sys_arch.txt // 在有操作系统的移植的时候会被使用到,包含了移植说明,规定了移植者需要实现的函数、宏定义等,后面有详细说明。
│ └─doxygen // doxygen 脚本,主要用来维护 LwIP 的配套文档。对于使用LwIP来说用不到
│ │ generate.bat
│ │ generate.sh
│ │ lwip.Doxyfile
│ │ main_page.h
│ └─output
│ index.html
├─src /* 源码文件部分下面独立详细说明 */
│ │ Filelists.mk
│ │ FILES // 主要记录了该目录下每个文件、目录的用途
│ ├─api
│ │ api_lib.c
│ │ api_msg.c
│ │ err.c
│ │ netbuf.c
│ │ netdb.c
│ │ netifapi.c
│ │ sockets.c
│ │ tcpip.c
│ ├─apps
│ │ ├─httpd
│ │ │ │ fs.c
│ │ │ │ fsdata.c
│ │ │ │ fsdata.h
│ │ │ │ httpd.c
│ │ │ │ httpd_structs.h
│ │ │ ├─fs
│ │ │ │ │ 404.html
│ │ │ │ │ index.html
│ │ │ │ └─img
│ │ │ │ sics.gif
│ │ │ └─makefsdata
│ │ │ makefsdata
│ │ │ makefsdata.c
│ │ │ readme.txt
│ │ ├─lwiperf
│ │ │ lwiperf.c
│ │ ├─mdns
│ │ │ mdns.c
│ │ ├─mqtt
│ │ │ mqtt.c
│ │ ├─netbiosns
│ │ │ netbiosns.c
│ │ ├─snmp
│ │ │ snmpv3.c
│ │ │ snmpv3_dummy.c
│ │ │ snmpv3_mbedtls.c
│ │ │ snmpv3_priv.h
│ │ │ snmp_asn1.c
│ │ │ snmp_asn1.h
│ │ │ snmp_core.c
│ │ │ snmp_core_priv.h
│ │ │ snmp_mib2.c
│ │ │ snmp_mib2_icmp.c
│ │ │ snmp_mib2_interfaces.c
│ │ │ snmp_mib2_ip.c
│ │ │ snmp_mib2_snmp.c
│ │ │ snmp_mib2_system.c
│ │ │ snmp_mib2_tcp.c
│ │ │ snmp_mib2_udp.c
│ │ │ snmp_msg.c
│ │ │ snmp_msg.h
│ │ │ snmp_netconn.c
│ │ │ snmp_pbuf_stream.c
│ │ │ snmp_pbuf_stream.h
│ │ │ snmp_raw.c
│ │ │ snmp_scalar.c
│ │ │ snmp_table.c
│ │ │ snmp_threadsync.c
│ │ │ snmp_traps.c
│ │ ├─sntp
│ │ │ sntp.c
│ │ └─tftp
│ │ tftp_server.c
│ ├─core
│ │ │ def.c
│ │ │ dns.c
│ │ │ inet_chksum.c
│ │ │ init.c
│ │ │ ip.c
│ │ │ mem.c
│ │ │ memp.c
│ │ │ netif.c
│ │ │ pbuf.c
│ │ │ raw.c
│ │ │ stats.c
│ │ │ sys.c
│ │ │ tcp.c
│ │ │ tcp_in.c
│ │ │ tcp_out.c
│ │ │ timeouts.c
│ │ │ udp.c
│ │ ├─ipv4
│ │ │ autoip.c
│ │ │ dhcp.c
│ │ │ etharp.c
│ │ │ icmp.c
│ │ │ igmp.c
│ │ │ ip4.c
│ │ │ ip4_addr.c
│ │ │ ip4_frag.c
│ │ └─ipv6
│ │ dhcp6.c
│ │ ethip6.c
│ │ icmp6.c
│ │ inet6.c
│ │ ip6.c
│ │ ip6_addr.c
│ │ ip6_frag.c
│ │ mld6.c
│ │ nd6.c
│ ├─include
│ │ ├─lwip
│ │ │ │ api.h
│ │ │ │ arch.h
│ │ │ │ autoip.h
│ │ │ │ debug.h
│ │ │ │ def.h
│ │ │ │ dhcp.h
│ │ │ │ dhcp6.h
│ │ │ │ dns.h
│ │ │ │ err.h
│ │ │ │ errno.h
│ │ │ │ etharp.h
│ │ │ │ ethip6.h
│ │ │ │ icmp.h
│ │ │ │ icmp6.h
│ │ │ │ igmp.h
│ │ │ │ inet.h
│ │ │ │ inet_chksum.h
│ │ │ │ init.h
│ │ │ │ ip.h
│ │ │ │ ip4.h
│ │ │ │ ip4_addr.h
│ │ │ │ ip4_frag.h
│ │ │ │ ip6.h
│ │ │ │ ip6_addr.h
│ │ │ │ ip6_frag.h
│ │ │ │ ip_addr.h
│ │ │ │ mem.h
│ │ │ │ memp.h
│ │ │ │ mld6.h
│ │ │ │ nd6.h
│ │ │ │ netbuf.h
│ │ │ │ netdb.h
│ │ │ │ netif.h
│ │ │ │ netifapi.h
│ │ │ │ opt.h
│ │ │ │ pbuf.h
│ │ │ │ raw.h
│ │ │ │ sio.h
│ │ │ │ snmp.h
│ │ │ │ sockets.h
│ │ │ │ stats.h
│ │ │ │ sys.h
│ │ │ │ tcp.h
│ │ │ │ tcpip.h
│ │ │ │ timeouts.h
│ │ │ │ udp.h
│ │ │ ├─apps
│ │ │ │ FILES
│ │ │ │ fs.h
│ │ │ │ httpd.h
│ │ │ │ httpd_opts.h
│ │ │ │ lwiperf.h
│ │ │ │ mdns.h
│ │ │ │ mdns_opts.h
│ │ │ │ mdns_priv.h
│ │ │ │ mqtt.h
│ │ │ │ mqtt_opts.h
│ │ │ │ netbiosns.h
│ │ │ │ netbiosns_opts.h
│ │ │ │ snmp.h
│ │ │ │ snmpv3.h
│ │ │ │ snmp_core.h
│ │ │ │ snmp_mib2.h
│ │ │ │ snmp_opts.h
│ │ │ │ snmp_scalar.h
│ │ │ │ snmp_table.h
│ │ │ │ snmp_threadsync.h
│ │ │ │ sntp.h
│ │ │ │ sntp_opts.h
│ │ │ │ tftp_opts.h
│ │ │ │ tftp_server.h
│ │ │ ├─priv
│ │ │ │ api_msg.h
│ │ │ │ memp_priv.h
│ │ │ │ memp_std.h
│ │ │ │ nd6_priv.h
│ │ │ │ tcpip_priv.h
│ │ │ │ tcp_priv.h
│ │ │ └─prot
│ │ │ autoip.h
│ │ │ dhcp.h
│ │ │ dns.h
│ │ │ etharp.h
│ │ │ ethernet.h
│ │ │ icmp.h
│ │ │ icmp6.h
│ │ │ igmp.h
│ │ │ ip.h
│ │ │ ip4.h
│ │ │ ip6.h
│ │ │ mld6.h
│ │ │ nd6.h
│ │ │ tcp.h
│ │ │ udp.h
│ │ ├─netif
│ │ │ │ etharp.h
│ │ │ │ ethernet.h
│ │ │ │ lowpan6.h
│ │ │ │ lowpan6_opts.h
│ │ │ │ slipif.h
│ │ │ └─ppp
│ │ │ │ ccp.h
│ │ │ │ chap-md5.h
│ │ │ │ chap-new.h
│ │ │ │ chap_ms.h
│ │ │ │ eap.h
│ │ │ │ ecp.h
│ │ │ │ eui64.h
│ │ │ │ fsm.h
│ │ │ │ ipcp.h
│ │ │ │ ipv6cp.h
│ │ │ │ lcp.h
│ │ │ │ magic.h
│ │ │ │ mppe.h
│ │ │ │ ppp.h
│ │ │ │ pppapi.h
│ │ │ │ pppcrypt.h
│ │ │ │ pppdebug.h
│ │ │ │ pppoe.h
│ │ │ │ pppol2tp.h
│ │ │ │ pppos.h
│ │ │ │ ppp_impl.h
│ │ │ │ ppp_opts.h
│ │ │ │ upap.h
│ │ │ │ vj.h
│ │ │ └─polarssl
│ │ │ arc4.h
│ │ │ des.h
│ │ │ md4.h
│ │ │ md5.h
│ │ │ sha1.h
│ │ └─posix
│ │ │ errno.h
│ │ │ netdb.h
│ │ └─sys
│ │ socket.h
│ └─netif
│ │ ethernet.c
│ │ ethernetif.c
│ │ FILES
│ │ lowpan6.c
│ │ slipif.c
│ └─ppp
│ │ auth.c
│ │ ccp.c
│ │ chap-md5.c
│ │ chap-new.c
│ │ chap_ms.c
│ │ demand.c
│ │ eap.c
│ │ ecp.c
│ │ eui64.c
│ │ fsm.c
│ │ ipcp.c
│ │ ipv6cp.c
│ │ lcp.c
│ │ magic.c
│ │ mppe.c
│ │ multilink.c
│ │ ppp.c
│ │ pppapi.c
│ │ pppcrypt.c
│ │ PPPD_FOLLOWUP
│ │ pppoe.c
│ │ pppol2tp.c
│ │ pppos.c
│ │ upap.c
│ │ utils.c
│ │ vj.c
│ └─polarssl
│ arc4.c
│ des.c
│ md4.c
│ md5.c
│ README
│ sha1.c
└─test // 一些协议栈内核测试程序.在实际使用时一般用不到!可直接删除。
├─fuzz
│ │ config.h
│ │ fuzz.c
│ │ lwipopts.h
│ │ Makefile
│ │ output_to_pcap.sh
│ │ README
│ └─inputs
│ ├─arp
│ │ arp_req.bin
│ ├─icmp
│ │ icmp_ping.bin
│ ├─ipv6
│ │ neighbor_solicitation.bin
│ │ router_adv.bin
│ ├─tcp
│ │ tcp_syn.bin
│ └─udp
│ udp_port_5000.bin
└─unit
│ lwipopts.h
│ lwip_check.h
│ lwip_unittests.c
├─core
│ test_mem.c
│ test_mem.h
│ test_pbuf.c
│ test_pbuf.h
├─dhcp
│ test_dhcp.c
│ test_dhcp.h
├─etharp
│ test_etharp.c
│ test_etharp.h
├─ip4
│ test_ip4.c
│ test_ip4.h
├─mdns
│ test_mdns.c
│ test_mdns.h
├─tcp
│ tcp_helper.c
│ tcp_helper.h
│ test_tcp.c
│ test_tcp.h
│ test_tcp_oos.c
│ test_tcp_oos.h
└─udp
test_udp.c
test_udp.h
在LwIP的源码包中,一共包含三个目录:doc
、src
、test
。分别对应:源码的文档、源码、测试代码。最新版的源码与早期源码在某些目录文件是有区别的。以下主要说明 src
,即:源码部分。
api目录
LwIP 提供了两种类型的 API : Callback-style APIs 和 Sequential-style APIs 。其中,Callback-style APIs 即为LwIP最底层的接口,被称为Raw API或者Native API;而Sequential-style APIs主要是对底层接口进行了封装,主要包含:Netconn API、NETIF API和Socket API。在实际使用中,使用者可以任选一种API来使用。
api目录下主要包含对底层API(raw API)封装后的高级API的代码。 如果直接使用底层的的Raw API,则不需要该目录下的文件。而封装后的高级别的这两种API实现的原理都是通过引进邮箱和信号量等通信与同步机制,来实现对内核中***Raw API(native API)***函数的封装和调用。要使用这两种类型的API,需要底层操作系统的支持。
- Raw API:(有时称为native API)是一个设计用于在没有操作系统时,实现零拷贝发送和接收的事件驱动的API。 这个API也被核心堆栈用于各种协议之间的交互。 这是在没有操作系统的情况下,运行lwIP时唯一可用的API。
因为Callback/Raw API是协议栈提供的三种编程接口中最复杂的一种, 它通过直接与协议栈内 核函数交互以实现编程,所以整个过程比较复杂。源码的doc目录
下有一个专门的文档:rawapi.txt说明了具体如何使用Raw API。 - Netconn API: 为普通的、顺序的程序提供了使用lwIP栈的方法。 线程安全,仅从非TCPIP线程调用。 基于网络缓冲区(包含数据包缓冲区(PBUF))的TX / RX处理,以避免复制数据。这与BSD Socket API非常相似。 执行模型基于 打开-读取-写入-关闭 范例。 由于TCP / IP堆栈本质上是事件,所以TCP / IP代码和应用程序必须驻留在不同的执行上下文(线程)中。
- Socket API: 它是建立在Netconn API之上的。其主要是由于BSD Socket API是网络通信的一个实现。线程安全,仅从非TCPIP线程调用。BSD Socket API已经是网络套接字的事实上的抽象标准。目前,所有主流操作系统均实现了BSD Socket API。出于此,LwIP也提供了一套BSD Socket API。但是,标准 socket 库中的部分函数仍无法直接通过封装 Netconn API 来实现,因此 LwIP 中提供的socket 函数并不完整,用户最好不要使用它进行实际应用程序开发。对应文件为
posix/sys/socket.h
。
以下为每个文件的具体说明:
- api_lib.c: 包含 对外提供的 sequential API 函数的实现。函数名均以
netconn_
开头。主要分为三组API:同时可用于TCP和UDP的API、只能用于TCP的API、只能用于UDP的API。 - api_msg.c: 包含sequential API内部自己调用的函数的实现。主要包含API消息的封装和处理函数
- err.c: 错误管理模块
- netbuf.c: 包含了上层数据包管理函数的实现。应用程序描述待发送数据和已接收数据的基本结构。该结构只是对内核 pbuf 的简单封装,避免了数据的拷贝。缓冲区不能在多个线程之间共享。
- netdb.c: 包含与主机名字转换相关的函数,主要在 socket 中被使用到
- netifapi.c: 包含了上层网络接口管理函数的实现
- sockets.c: 包含了 Socket API 函数的实现
- tcpip.c: 包含了上层 API 与协议栈内核交互的函数,它是整个上层 API 功能得以实现的一个枢纽,其实现的功能可以简单理解为:从 API 函数处接收消息,然后将消息递交给内核函数,内核函数根据消息做出相应的处理。
apps目录
使用lwIP低级raw API编写的高层应用程序。
core目录
TCP/IP 协议栈的核心部分。主要包含协议实现、内存和缓冲区管理以及底层raw API的实现。它包含了IP、ICMP、IGMP、TCP、UDP 等核 心协议以及建立在它们基础上的 DNS、DHCP、SNMP 等上层应用协议。内核源代码可以单独运行,且不需要操作系统的支持。即:直接使用 raw API 编程。
-
ipv4目录: 包含了IPv4 标准中与IP层数据包处理相关的所有代码
-
autoip.c: 这是lwIP TCP / IP协议栈的AutoIP实现。 它旨在符合RFC 3927。
-
dhcp.c: 实现了DHCP 客户端的所有代码,DHCP 称为动态主机配置协议,DHCP 可以使计算机使用者不必为主机的IP 地址的分配问题而烦恼。DHCP 也是一个上层应 用程序, 通常DHCP客户端通过使用UDP提供的功能来实现与DHCP服务器的通信, 从DHCP 服务器处获得一个有效的IP 地址
-
etharp.c: 包含了ARP 协议实现的相关函数,ARP 协议是以太网通信中的重要部分, 主要用来实现主机以太网物理地址到IP 地址的映射。这点是非常必要的,以太网中底层数据包的发送是基于网卡物理地址的,而不是主机的IP地址。 通过ARP协议, 主机可以发送请求, 得到邻居节点的IP 地址与物理地址等信息,为以太网数据包交互提供保证。
-
icmp.c: 包含了ICMP 协议实现的相关函数,ICMP 协议为IP 数据包传递过程中的差错 报告、差错纠正以及目的地址可达性测试提供了支持,常见的Ping 命令就属于ICMP 应用的 一种
-
igmp.c: 包含了网络组管理协议IGMP 的实现,IGMP 为网络中的多播数据传输提供了 支持,主机加入某个多播组后,可以接收到该组的UDP 多播数据
-
ip4_addr.c: 实现了几个比较简单 的IP 地址处理函数,如判断一个IP 地址是否为广播地址的函数,以及32 位IP 地址与点分十 进制地址间的转换函数等
-
ip4_frag.c: 提供了IP 层数据包分片与重组相关的函数的实现。
-
Ip4.c: 包含了IPv4 协议实现的相关函数,如数据包的接收、递交、发送等
-
ipv6目录: 包含了IPv6 标准中和IP层数据包处理相关的所有代码
-
mld6.c: IPv6的多播侦听器发现。旨在符合RFC 2710。
-
nd6.c: IPv6的邻居发现和无状态地址自动配置。 旨在符合RFC 4861(邻居发现)和RFC 4862(地址自动配置)。
-
Iinet6.c: 目前没有啥实际内容。
-
其他文件与IPv4目录下的功能相同,不过是在IPv6版上的实现
-
def.c: 文件包含了IP 层使用到的一些功能函数的定义,如IP 地址的转换、网络字节序与 主机字节序转换等
-
dns.c: 实现了DNS 客户端的所有代码,DNS 称为域名系统,通常用户要访问某个外 部的主机时,可能只知道该主机的名字,而不知道该主机的IP 地址。常理也是这样,用户可 以简单地记得某个主机的名字,如www.baidu.com,却很难记得这个主机的IP 地址。通过使 用DNS,可以解决这个问题,通过访问DNS 服务器,我们可以得到与主机名对应的IP 地址, 这为用户的使用带来了方便。值得指出的是,DNS 也是一个上层应用程序,它通常基于UDP 来传输数据。
-
inet_chksum.c: 这些是校验和算法的一些参考实现,其目标是简单,正确和完全便携。 Checksumming是您希望为您的平台优化的第一件事。 如果您创建自己的版本,请将其链接到cc.h中
-
init.c: 主要包含了一个与LwIP 协议栈初始化密切相关的函数,以及一些 协议栈配置信息的检查与输出
-
ip.c: IPv4和IPv6共用的代码。
-
mem.c: 包含了协议栈内存堆管理函数的实现。
(1)LwIP 支持的动态内存管理机制主要有三种:C库自带内存分配策略(malloc/free/realloc)、动态内存堆(Heap)分配策略、动态内存池(pool)分配策略。
(2)此文件即LwIP实现的动态内存堆(Heap)分配策略。
(3)默认不使用C库自带内存分配策略。 在lwipopts.h
使用宏值MEM_LIBC_MALLOC = 1
选择使用C库自带内存分配策略 -
memp.c: 包含了协议栈内存池管理函数的实现。
(1)LwIP 支持的动态内存管理机制主要有三种:C库自带内存分配策略(malloc/free/realloc)、动态内存堆(HEAP)分配策略、动态内存池(POOL)分配策略。
(2)此文件即LwIP实现的动态内存池(POOL)分配策略
(3)默认不使用动态内存堆(HEAP)分配策略。 在lwipopts.h
使用宏值MEMP_MEM_MALLOC = 1
选择使用动态内存堆(HEAP)分配策略 -
netif.c: 包含了协议栈网络接口管理的相关函数,协议栈支持多个网络接口,例如以太网接口、SLIP接口等,协议栈内部对每个接口用一个对应的netif数据结构进行描述,并通过使用
netif.c
中的函数进行统一管理。同时,netif.c
还包含了环回接口管理和数据处理的相关函数,使用环回接口可以实现同一主机上两个应用程序之间的数据交换。 -
pbuf.c: 包含了协议栈内核使用的数据包管理函数,数据包pbuf管理的实现是整个协议栈中很有特色的地方,采用特殊的数据包pbuf 结构,可以避免数据在各个层次之间递交时的拷贝,这既提高了数据递交效率,也节省了内存空间。
-
raw.c: 为应用层提供了一种直接和IP 数据包交互的方式,这类似于Socket 编程中原始套接字的概念,它同TCP、UDP 处于同一等级,享受IP 层提供的服务。使用原始套接字, 可以直接读取IP 层接收到的数据包,例如ICMP 包、UDP 包等,也可以自行构造ping包等, 为用户的程序编写提供了很大的方便。这是LwIP最底层的API部分。使用这一级别的api进行编程需要对TCP/IP相当了解。
-
stats.c: 包含了协议栈内部数据统计与显示的相关函数,如内存使用状况、邮箱、信号量等信息。
-
sys.c: 实现了一个简单的函数
void sys_msleep(u32_t ms);
,它借助操作系统模拟层的信号量机制完成睡眠一定时间的功能,该函数主要在PPP 中使用。前面曾提到过,若需要使用协议栈的Sequential API和Socket API,则必须使用底层操作系统提供的邮箱与信号量机制,这时内核要求移植者提供一个称为sys_arch.c
的操作系统模拟层文件,该文件主要完成对操作系统中邮 箱与信号量函数的封装。需要注意,只有在提供文件sys_arch.c
的基础上,文件`sys.c 才有效,换句话说,在无操作系统环境下运行LwIP 时,sys.c文件都不会被编译。 -
tcp_in.c: 包含了TCP 协议中数据接收、处理相关的函数,最重要的TCP 状态机函数也在这个文件中
-
tcp_out.c: 包含了TCP 中数据发送相关的函数,例如数据包发送函数、超时重传函数等。
-
tcp: 包含了对TCP 控制块操作的函数,也包括了TCP 定时处理函数
-
timeouts.c: 统一完成了对内核各个协议定时事件处理函数的封装,同时对各个注册的定时事件进行处理。在有无操作系统模拟层的环境下,
timeouts.c
采用不同的方法来实现定时:在无操作系统模拟层时,timeouts.c
使用系统时钟函数sys_now
(不同的平台的都需要移植该函数)来获得当前系统时间,从而可以判断出各个事件是否超时;在有操作系统模拟层时,timers.c 实现了对操作系统模拟层邮箱等待函数的再次封装,得到一个具有协议栈特色的邮箱操作函数。 所谓特色,就是在邮箱等待函数中加入一种机制,在邮箱上等待消息的同时,可以同时实现协议栈中各个定时事件的正确处理。在2.0.0之前的版本中,该文件名为
timer.c
。新的timeouts.c
对原来的内容进行了一定的封装,简化。 -
udp.c: 包含了实现UDP 协议的相关函数,包括UDP 控制块管理、UDP 数据包发送函数、UDP 数据包接收函数等
include目录
LwIP使用的各种头文件。与各源码目录相对应。
- netif文件夹: 主要是针对netif目录中源文件的各种头文件。
- posix文件夹: 主要是针对POSIX标准,进行的封装,里面的文件非常简单,基本都是对外部的引用。
- lwip文件夹: LwIP的各种头文件,其中需要注意的有
opt.h
文件,它包含了所有LwIP内核参数的默认配置值;还有init.h
文件,它包含了与当前 LwIP 源代码信息相关的宏定义,如协议版本号、是否为官方版等。 - apps文件夹: 主要是针对源码中 app目录的各头文件。
- priv文件夹: LwIP内部使用的头文件,禁止外部使用
- prot文件夹: 这个文件夹中主要是针对TCP/IP规约的各种定义。与netif文件夹中同名文件区别在于netif文件夹中的文件均为对外提供的API,本目录才是对规约层各种结构的定义。
netif目录
该目录下主要包含通用网络接口设备驱动程序。实现最底层的相关协议,该部分的多数源码基本已经到了最底层和硬件直接打交道了。
- ppp文件夹: 包含了PPP 协议实现的源代码。PPP 协议即点对点协议,它提供了 一种在点对点线路上传输多协议数据包的标准格式,PPP 协议为链路的建立、控制与认证提供 了标准。起初PPP 主要用来替代SLIP 这种简单的串行链路数据传输协议,但是由于其完整的认证机制,后来在以太网上也引入了PPP 机制,即PPPoE,它已成为近年来小区宽带拨号上网的主要方式。使用PPPoE,为用户的上网计费、配置、接入等提供了方便。LwIP 提供了对PPPoE 的支持,在ppp文件夹下的PPPoE.c文件中有相关的函数实现。
- ethernet.c: 以太网接口的共享代码。目前就两个函数:用来从网卡接收以太网数据包的函数和在网卡上发送以太网数据包的函数。
- ethernetif.c : 包含了与以太网网卡密切相关的初始化、发送、接收等函数的实现。注意:这个文件夹中的函数并不能使用, 它们都是一个框架性的结构, 移植者需要根据自己使用的网 卡特性来完成这些函数。
- slipif.c: 和
ethernetif.c
很相似,SLIP即串行链路IP,它提供了一种在串行链路上传送IP 数据包的函数定义。SLIP 协议比较简单,它只是定义了一系列的字符,以实现对链路上的IP数据包封装和发送,除此之外,它不提供任何寻址、错误检测、包类型识别机制,因此相关驱动程序的实现也比较简单。它需要一个sio(串行I / O)模块才能工作。移植者需要根据自己使用的串行线路特性(如串口)来实现以下这些函数(通常在另一个文件中,不要直接修改该文件):u32_t sio_read(sio_fd_t fd, u8_t *data, u32_t len);
u32_t sio_tryread(sio_fd_t fd, u8_t *data, u32_t len);
void sio_send(u8_t c, sio_fd_t fd);
sio_fd_t sio_open(u8_t devnum);
- lowpan6.c: 6LoWPAN协议的实现文件。6LoWPAN是一种基于IPv6的低速无线个域网标准,即IPv6 over IEEE 802.15.4。
移植说明
LWIP 的手动移植可以说非常麻烦,需要我们自己实现好多文件。最主要的是,需要实现的文件还没有个统一的说明,必须要东拼西凑! LWIP 的移植由两部分组成,分别为 LWIP 协议栈和 contrib 应用实例 。目前,这两部分是由两个独立的版本库控制,并且由不同的人来负责的(好消息是从2.1.x之后,这俩要合并了)!我们在移植使用 LWIP 时,需要从官网分别下载这两部分的源代码!
其中,contrib 中是一些和平台移植相关的代码以及一些使用 LWIP 实现的应用,在移植时非常有用;LWIP 则是 TCP/IP 协议栈的核心源码!综上所述,LwIP 的移植主要分为以下这些部分:
- 运行环境的搭建: 有其需要注意的是,针对嵌入式的移植时,无论哪种模式,都需要首先需要处理硬件资源,例如在 STM32Fx 系列芯片移植时,需要提供文件:
stm32f4x7_eth_bsp.c/h
,已处理使用的硬件资源。当然,如果是在 Windows 上做测试使用,则没有这一步。 - 源代码文件的整理: 主要就是东拼西凑LWIP需要的文件(LWIP需要用户实现很多文件)
- LwIP 的基本配置: 根据自己的需要来配置功能
- 与 CPU 或编译器相关源文件: LWIP 需要用户的文件之一
- 与操作系统的接口源文件(仅在系统模式下使用): LWIP 需要用户的文件之一
- 与底层网卡驱动的接口源文件。: LWIP 需要用户的文件之一
- 初始化 LwIP,然后正常使用各功能即可。:
源码文件整理
源代码文件的整理需要注意目录结构,因为 LWIP 源码的头文件的包含路径是包含路径的。否则我们就必须更改LWIP 的源代码!整理完成基本就是以下这个结构:
LWIP
│ lwipopts.h /* 这四个文件属于用户文件,每个用户可能是不同的,通常放在用户自己的目录*/
│ perf.c
│ sys_arch.c
| ethernetif.c
├─arch /* 这个需要我们自己建立目录,存放以下头文件!注意,根据平台,以下文件并不是全部都需要 */
│ bpstruct.h
│ cc.h
│ cpu.h
│ epstruct.h
│ perf.h
│ sys_arch.h
└─lwip /* 这个目录下就是 LWIP 源码目录下的 src 目录中的所有文件!根据功能需要,里面的部分文件其实是可以不需要的 */
├─api
├─apps
├─core
├─include
└─netif
lwIP 可以用于两种基本模式:主循环模式(通过配置 NO_SYS == 1,在目标系统上运行没有 OS/RTOS)或OS 模式(TCPIP 线程,目标系统上有一个操作系统)。在两种模式下的移植时区别还是很大的!
- Mainloop Mode: 即裸机下的死循环模式。在主循环模式下,只能使用回调式 API(Raw API)。需要确保确保在 lwIP 中一次只有一个执行环境。
- OS Mode: 此时,使用者需要提供一个名为
sys_arch.c/h
的文件。因为在系统模式下,LwIP 需要系统的信号量、邮箱队列等。LWIP 提供了这两个文件的模板,用户只需要在模板文件中进行实现各预定义的接口即可。
第一步:LwIP的基本配置
LwIP 的配置主要通过文件 lwipopts.h
实现,移植时需要根据自己的需要,进行相关的配置,然后在自己的项目中添加该文件。一个在 STM32F4 芯片移植是的配置文件如下:
/**
******************************************************************************
* @file lwipopts.h
* @author MCD Application Team
* @version V1.1.0
* @date 31-July-2013
* @brief lwIP Options Configuration.
* This file is based on Utilities\lwip_v1.4.1\src\include\lwip\opt.h
* and contains the lwIP configuration for the STM32F4x7 demonstration.
******************************************************************************
* @attention
*
* <h2><center>© COPYRIGHT 2013 STMicroelectronics</center></h2>
*
* Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.st.com/software_license_agreement_liberty_v2
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************
*/
#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__
/**
* SYS_LIGHTWEIGHT_PROT==1: if you want inter-task protection for certain
* critical regions during buffer allocation, deallocation and memory
* allocation and deallocation.
*/
#define SYS_LIGHTWEIGHT_PROT 0
#define ETHARP_TRUST_IP_MAC 0
#define IP_REASSEMBLY 0
#define IP_FRAG 0
#define ARP_QUEUEING 0
/**
* NO_SYS==1: Provides VERY minimal functionality. Otherwise,
* use lwIP facilities.
*/
#define NO_SYS 0
/* ---------- Memory options ---------- */
/* MEM_ALIGNMENT: should be set to the alignment of the CPU for which
lwIP is compiled. 4 byte alignment -> define MEM_ALIGNMENT to 4, 2
byte alignment -> define MEM_ALIGNMENT to 2. */
#define MEM_ALIGNMENT 4
/* MEM_SIZE: the size of the heap memory. If the application will send
a lot of data that needs to be copied, this should be set high. */
#define MEM_SIZE (5*1024)
/* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
sends a lot of data out of ROM (or other static memory), this
should be set high. */
#define MEMP_NUM_PBUF 100
/* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
per active UDP "connection". */
#define MEMP_NUM_UDP_PCB 6
/* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP
connections. */
#define MEMP_NUM_TCP_PCB 10
/* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP
connections. */
#define MEMP_NUM_TCP_PCB_LISTEN 5
/* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP
segments. */
#define MEMP_NUM_TCP_SEG 20
/* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active
timeouts. */
#define MEMP_NUM_SYS_TIMEOUT 10
/* ---------- Pbuf options ---------- */
/* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */
#define PBUF_POOL_SIZE 20
/* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */
#define PBUF_POOL_BUFSIZE 500
/* ---------- TCP options ---------- */
#define LWIP_TCP 1
#define TCP_TTL 255
/* Controls if TCP should queue segments that arrive out of
order. Define to 0 if your device is low on memory. */
#define TCP_QUEUE_OOSEQ 0
/* TCP Maximum segment size. */
#define TCP_MSS (1500 - 40) /* TCP_MSS = (Ethernet MTU - IP header size - TCP header size) */
/* TCP sender buffer space (bytes). */
#define TCP_SND_BUF (5*TCP_MSS)
/* TCP_SND_QUEUELEN: TCP sender buffer space (pbufs). This must be at least
as much as (2 * TCP_SND_BUF/TCP_MSS) for things to work. */
#define TCP_SND_QUEUELEN (4* TCP_SND_BUF/TCP_MSS)
/* TCP receive window. */
#define TCP_WND (2*TCP_MSS)
/* ---------- ICMP options ---------- */
#define LWIP_ICMP 1
/* ---------- DHCP options ---------- */
/* Define LWIP_DHCP to 1 if you want DHCP configuration of
interfaces. DHCP is not implemented in lwIP 0.5.1, however, so
turning this on does currently not work. */
#define LWIP_DHCP 1
/* ---------- UDP options ---------- */
#define LWIP_UDP 1
#define UDP_TTL 255
/* ---------- Statistics options ---------- */
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1
/* ---------- link callback options ---------- */
/* LWIP_NETIF_LINK_CALLBACK==1: Support a callback function from an interface
* whenever the link changes (i.e., link down)
*/
#define LWIP_NETIF_LINK_CALLBACK 1
/*
--------------------------------------
---------- Checksum options ----------
--------------------------------------
*/
/*
The STM32F4x7 allows computing and verifying the IP, UDP, TCP and ICMP checksums by hardware:
- To use this feature let the following define uncommented.
- To disable it and process by CPU comment the the checksum.
*/
/*
注意:校验和可以选择 由硬件来处理。如果硬件支持该功能,则加载其配置文件(其中需要定义 CHECKSUM_BY_HARDWARE )
*/
#include "stm32f4x7_eth_conf.h"
#ifdef CHECKSUM_BY_HARDWARE /* 如果芯片的配置中开启了硬件处理校验和,则对LwIP进行配置 */
/* CHECKSUM_GEN_IP==0: Generate checksums by hardware for outgoing IP packets.*/
#define CHECKSUM_GEN_IP 0
/* CHECKSUM_GEN_UDP==0: Generate checksums by hardware for outgoing UDP packets.*/
#define CHECKSUM_GEN_UDP 0
/* CHECKSUM_GEN_TCP==0: Generate checksums by hardware for outgoing TCP packets.*/
#define CHECKSUM_GEN_TCP 0
/* CHECKSUM_CHECK_IP==0: Check checksums by hardware for incoming IP packets.*/
#define CHECKSUM_CHECK_IP 0
/* CHECKSUM_CHECK_UDP==0: Check checksums by hardware for incoming UDP packets.*/
#define CHECKSUM_CHECK_UDP 0
/* CHECKSUM_CHECK_TCP==0: Check checksums by hardware for incoming TCP packets.*/
#define CHECKSUM_CHECK_TCP 0
/* CHECKSUM_CHECK_ICMP==0: Check checksums by hardware for incoming ICMP packets.*/
#define CHECKSUM_GEN_ICMP 0
#else
/* CHECKSUM_GEN_IP==1: Generate checksums in software for outgoing IP packets.*/
#define CHECKSUM_GEN_IP 1
/* CHECKSUM_GEN_UDP==1: Generate checksums in software for outgoing UDP packets.*/
#define CHECKSUM_GEN_UDP 1
/* CHECKSUM_GEN_TCP==1: Generate checksums in software for outgoing TCP packets.*/
#define CHECKSUM_GEN_TCP 1
/* CHECKSUM_CHECK_IP==1: Check checksums in software for incoming IP packets.*/
#define CHECKSUM_CHECK_IP 1
/* CHECKSUM_CHECK_UDP==1: Check checksums in software for incoming UDP packets.*/
#define CHECKSUM_CHECK_UDP 1
/* CHECKSUM_CHECK_TCP==1: Check checksums in software for incoming TCP packets.*/
#define CHECKSUM_CHECK_TCP 1
/* CHECKSUM_CHECK_ICMP==1: Check checksums by hardware for incoming ICMP packets.*/
#define CHECKSUM_GEN_ICMP 1
#endif
/*
----------------------------------------------
---------- Sequential layer options ----------
----------------------------------------------
*/
/**
* LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
*/
#define LWIP_NETCONN 1
/*
------------------------------------
---------- Socket options ----------
------------------------------------
*/
/**
* LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
*/
#define LWIP_SOCKET 0
/*
-----------------------------------
---------- DEBUG options ----------
-----------------------------------
*/
#define LWIP_DEBUG 0
/*
---------------------------------
---------- OS options ----------
---------------------------------
*/
#define TCPIP_THREAD_NAME "TCP/IP"
#define TCPIP_THREAD_STACKSIZE 1000
#define TCPIP_MBOX_SIZE 5
#define DEFAULT_UDP_RECVMBOX_SIZE 2000
#define DEFAULT_TCP_RECVMBOX_SIZE 2000
#define DEFAULT_ACCEPTMBOX_SIZE 2000
#define DEFAULT_THREAD_STACKSIZE 500
#define TCPIP_THREAD_PRIO (configMAX_PRIORITIES - 2)
#define LWIP_COMPAT_MUTEX 1
#define LWIP_COMPAT_MUTEX_ALLOWED 1
#endif /* __LWIPOPTS_H__ */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
第二步:与 CPU 或编译器相关接口
与 CPU 或编译器相关的源文件,主要包括数据长度,字的高低位顺序,编译器对c语言中struct结构字节对齐问题(c语言中struct结构是四字节对对齐的,但lwip中是根据struct结构中不同数据的长度来读取数据的)。主要有以下文件组成(仅仅是个例子,实际不一定有全部文件):
上图例子中,处理了针对目前主流的编译器(VC、ARMCC、IAR等)的各种宏定义,比较全面,文件也比较多。自己在移植时文件个数/名字可能不同。但是最基本的cc.h、perf.h、sys_arch.h
是 LwIP 规定的文件名,必须一致。
第三步:与操作系统的接口源文件
LwIP为了适应不同的操作系统,在代码中没有使用和某一个操作系统相关的系统调用和数据结构。而是在LwIP和操作系统之间增加了一个操作系统封装层。操作系统封装层为操作系统服务(定时,进程同步,消息传递)提供了一个统一的接口。在Lwip中进程同步使用semaphone和消息传递采用"mbox"。
因此,使用者必须根据自己使用的系统,完成sys_arch.h 和sys_arch.c
这两个文件。关于这个文件,在LwIP源码的doc/sys_arch.txt
有详细的说明。当然,如果在裸机下使用,则不需要该步骤。
注意:
无论是否有系统,其中有个函数sys_now
都必须被提供,具体可参见 LwIP 之 sys_arch
第四步:与底层网卡驱动的接口源文件
该部分主要是针对文件ethernetif.c
和slipif.c
。移植时,需要根据自己使用的网卡或者串行链路修改/实现内部的各函数。
ethernetif.c
LwIP的网络驱动有一定的模型,/src/netif/ethernetif.c
文件即为驱动的模版,用户为自己的网络设备实现驱动时应参照此模块。在 LwIP中可以有多个网络接口,每个网络接口都对应了一个struct netif。这个netif包含了相应网络接口的属性、收发函数。
slipif.c
和ethernetif.c很相似,SLIP即串行链路IP,它提供了一种在串行链路上传送IP 数据包的函数定义。SLIP 协议比较简单,它只是定义了一系列的字符,以实现对链路上的IP数据包封装和发送,除此之外,它不提供任何寻址、错误检测、包类型识别机制,因此相关驱动程序的实现也比较简单。它需要一个sio(串行I / O)模块才能工作。 移植者需要根据自己使用的串行线路特性(如串口)来实现以下这些函数(通常在另一个文件中,不要直接修改该文件):
u32_t sio_read(sio_fd_t fd, u8_t *data, u32_t len);
u32_t sio_tryread(sio_fd_t fd, u8_t *data, u32_t len);
void sio_send(u8_t c, sio_fd_t fd);
sio_fd_t sio_open(u8_t devnum);
通常,在移植时不会使用该文件。
第五步:初始化LwIP
在处理完以上需要移植的个文件后,接下来就可以在自己的项目中建立初始化函数来对LwIP进行初始化了,以下为使用系统时的一个初始化例子。
void LwIP_Init(void)
{
ip_addr_t ipaddr;
ip_addr_t netmask;
ip_addr_t gw;
#ifdef USE_DHCP
uint8_t iptab[4] = {0};
uint8_t iptxt[20];
#endif
/* 创建tcp_ip线程,初始化LwIP的各部分 */
tcpip_init(NULL, NULL);
/* 设置IP地址等信息 */
#ifdef USE_DHCP
ipaddr.addr = 0;
netmask.addr = 0;
gw.addr = 0;
#else
IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);
IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3);
IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);
#endif
/* 以下就是添加网络接口了 */
/* - netif_add(struct netif *netif, struct ip_addr *ipaddr,
struct ip_addr *netmask, struct ip_addr *gw,
void *state, err_t (* init)(struct netif *netif),
err_t (* input)(struct pbuf *p, struct netif *netif))
Adds your network interface to the netif_list. Allocate a struct
netif and pass a pointer to this structure as the first argument.
Give pointers to cleared ip_addr structures when using DHCP,
or fill them with sane numbers otherwise. The state pointer may be NULL.
The init function pointer must point to a initialization function for
your ethernet netif interface. The following code illustrates it's use.*/
netif_add(&xnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
/* Registers the default network interface.*/
netif_set_default(&xnetif);
if (EthStatus == (ETH_INIT_FLAG | ETH_LINK_FLAG))
{
/* Set Ethernet link flag */
xnetif.flags |= NETIF_FLAG_LINK_UP;
/* When the netif is fully configured this function must be called.*/
netif_set_up(&xnetif);
#ifdef USE_DHCP
DHCP_state = DHCP_START;
#endif /* USE_DHCP */
}
else
{
/* When the netif link is down this function must be called.*/
netif_set_down(&xnetif);
#ifdef USE_DHCP
DHCP_state = DHCP_LINK_DOWN;
#endif /* USE_DHCP */
}
/* Set the link callback function, this function is called on change of link status*/
netif_set_link_callback(&xnetif, ETH_link_callback);
}
首先看以下tcpip_init
函数,该函数负责建立tcp_ip线程,并且初始化LwIP的各部分功能,具体看以下图片。
设置IP地址这块没啥可说的,接下来就是添加网络接口,具体参见 LwIP 之 ethernetif.c。