从本篇开始,将花大量篇幅介绍Zynq在裸机环境下以太网的使用。裸机时最方便的就是使用SDK已经集成了的lwIP 1.4.1库,我们将先了解lwIP的相关知识,然后再以实例的方式学习TCP、UDP的程序设计方法。
研究背景
在过去几年里,将计算机和计算机支持的设备连接到无线网络的需求逐渐增长。计算机与日常设备之间的集成度越来越高,价格也在下降。同时,蓝牙、IEEE 802.11b/g(俗称“wifi”)等无线网络技术已经非常普遍。这导致在医疗保健、安全保障、交通业、加工业等领域出现了许多新颖的场景。传感器等小型设备可以连接到现有的网络设施中(如全球互联网),人们可以在任何地方监控它们。
互联网技术非常灵活,能够适应过去几十年不断变化的网络环境。互联网技术虽然最初是为阿帕网(ARPANET)等低速网络开发的,但现在可以在一个很大的链路技术频谱上运行,在带宽和误码率方面由截然不同的特性。由于现在已经开发了大量使用互联网技术的应用程序,能在未来的无线网络中使用现有的互联网技术是非常有利的。诸如传感器之类的小型设备通常需要体积小且价格便宜,因此不得不在有限的计算资源和内存上实现互联网协议。
lwIP最早由Adam Dunkels编写,目前由Kieran Mansley带领的团队开发(开发者主页http://savannah.nongnu.org/projects/lwip )。lwIP是一个小型TCP/IP栈,可以在嵌入式系统中使用。lwIP采取模块化设计,核心栈是IP协议的实现,用户可以在其上选择添加TCP、UDP、DHCP等其它协议,包括这些协议的各种特性。当然这样会导致代码量增加、复杂性提高,需要根据用户的需求进行调整。此外,lwIP在有无操作系统、支持或不支持线程的情况下都可以运行,适用于8位或32位微处理器,支持小端和大端系统。
支持协议
lwIP是模块化设计,且支持多种协议,大部分协议在无需使用时可以将其移除,减小代码量。lwIP支持的链路层和网络层协议包括:
- ARP:一种链路层协议,用于将本地硬件地址(即MAC地址)转译为IP地址。
- IPv4:目前互联网中使用的主要的网络层协议。
- IPv6:Ipv4的下一代,IP地址大小扩展到了128位。
- ICMP:一种IP的控制协议。
- IGMP:一种IP中多点传播的管理协议。
lwIP支持的传输层协议包括:
- UDP:一种没有可靠性机制的无连接socket协议。
- TCP:一种面向连接的“流”协议。
lwIP支持的高层次协议包括:
- DHCP:一种带服务器的IP地址获取方法。
- AUTOIP:一种没有服务器的IP地址选择方法。
- SNMP:用于监控网络状况。
- PPP:在两个节点之间创建直接连接。
应用程序接口
lwIP提供了三种应用程序接口(Xilinx只支持RAW API和socket API两种),用于程序和TCP/IP代码之间的通信:
- 低层次的、基于“核”和“回调”的RAW API
- 两种高层次的、基于“顺序”的API:netconn API和socket API
顺序型API类似于BSD socket API,执行模型是基于阻塞的开-读-写-关(open-read-write-close)模式。由于TCP/IP栈本质上是基于事件的,所以TCP/IP代码和应用程序必须在不同的线程中。API之间的差别如下,据此选择使用哪种API:
- netconn API和raw API只能用于lwIP中,用这些API编写的代码不能移植到其它栈中重用。
- socket API的目的是能与其它posix操作系统/栈之间可移植,但会降低吞吐量。
- socket API和netconn API是需要线程的顺序型API
- raw API基于回调机制,比如当新数据到达时调用已注册的回调函数。由于它不需要切换线程,所以由最佳的性能表现。
- raw API和netconn API支持TX和RX的“零拷贝(zero-copy)”。
带或不带操作系统的lwIP
lwIP可以在裸机环境下运行,也可以在多线程操作系统中运行。
当在没有操作系统的单线程环境中运行lwIP时,只需要IP、ICMP、UDP和TCP协议的实现、缓冲区和内存管理等核心组件,当然也可以添加DHCP、DNS等组件,但它们不是必须的。甚至可以只编译UDP或TCP。在Xilinx中使用的lwIP,如果运行TCP,需要每250ms调用依次tcp_tmr,它会执行重传等TCP定时器的处理工作。UDP则没有必要。
单线程的主循环中需要调用链路层驱动程序(Xilinx适配器有专用函数),依次处理IP数据报,然后调用上层协议处理程序,最后调用应用程序的回调函数。
多线程系统中应用程序在并发线程中运行。可以让所有TCP/IP的处理在一个线程中完成,用户应用程序的线程通过API函数与TCP/IP线程通信。
最大化吞吐量
一般设计者想尽可能减少代码量,并让lwIP工作在最大吞吐量状态,有诸多原因会影响到使用lwIP的以太网设备的性能。架构设计方面包括:
- 由于网络字节顺序是大端模式,因此最好选择大端模式的系统,可以省略其中的转换。
- 系统的一个瓶颈是以太网MAC驱动程序(lwIP中称作netif-driver),应尽可能使用中断和DMA。通常驱动程序可以编写为偏向于TX或RX,如果应用程序中某个传输方向更重要,应确保在高负载情况下首选该方向。在硬件允许的情况下,确保驱动程序支持分散收集(scatter-gather)。
- 另一个瓶颈是TCP和UDP的校验和计算,发送数据时生成校验和,接收数据时检查校验和。如果硬件支持,则将校验和的生成和检验留给硬件完成。如果硬件不支持,则确保有一个优化的计算校验和的软件架构。
对于在Xilinx中实现,第一点我们无法选择,第二点Xilinx已经帮你解决了。我们需要注意的就是第三点。lwIP的配置也会影响到吞吐量性能,SDK中如何配置的相关部分查看本系列其它文章。
如果希望最大化吞吐量,应用程序应该使用RAW API,而不是netconn/socket API。设计程序前,我们首先要选择使用UDP还是TCP。
- UDP:优点是开销更少,设计者自己选择消息大小;缺点是没有提供安全的通信路径,该协议不能通知用户对方是否收到了消息。
- TCP:优点是提供了一个安全的通信路径,当对方成功收到消息时用户会收到通知;缺点是开销更大,还会自动选择消息大小。
选定协议后,设计者要决定应用程序如何通过网络传递数据:
- UDP:确保传递的数据块不会小于网络所允许的最大数据包,比如在标准以太网中,使用udp_send一次发送1472个字节,以最大化一个包中数据字节和报头字节的比,同时最小化网络中包间的间隔。
- TCP:虽然TCP可以将多个tcp_write调用的数据合并到一个包中,但由于这个包被分割到多个pbuf中,可能会降低性能。由于TCP需要将数据包存储起来重新传输,直到远程主机发出应答信号,所以在tcp_write/tcp_output返回后花费几秒的时间。如果要发送小块数据,应该关掉nagle算法,让堆栈立即发送数据,而不是等待更多数据形成更大的数据包后才发送数据。应该避免发送小块数据,总是等待应答会降低性能。