lwIP架构分析

知识 150 0

在物联网应用方兴未艾之际,lwIP协议栈的应用是比较多的,它在稳定性和资源利用上都比较优秀,很多芯片厂商甚至把lwIP移植了到自己的片上系统里。最近由于有个项目需要用到lwIP,我在网上找相关的资料,发现广告太多,有用的信息太难找到,于是花了点时间阅读lwIP的源码,把主要的框架梳理了一下。经过几天的努力基本弄明白了,现在把理解到的内容分享一下。

我阅读的代码是最新的2.1.2版本,下载链接如下:

lwIP架构分析

http://download.savannah.nongnu.org/releases/lwip/

工作调试环境是正点原子STM32F407开发板,一边在开发板上调试,一边printf打印输出。

Lwip整体架构如下图所示:

一、 MCU的业务层

这一层是lwip提供服务的对象,就是自己的业务代码使用lwip的地方。业务代码从这里开始通过netconn或是lwip_api接口开始调用lwip的功能。比如,典型的TCP客户端首先通过netconn_new创建一个struct netconn对象,接着调用netconn_connect连接服务器,连接成功后调用netconn_write向服务器发送数据,或者调用netconn_recv接收数据,最后,通过netconn_close关闭连接,释放资源。

二、lwIP的api层

这一层是netconn的功能代码所在地方,提供netconn的api给上层代码使用。如果习惯了使用socket的同学可以用lwip_socket等函数按标准socket的方式调用lwip的功能。而且,在新的版本里增加了http,mqtt等应用的代码,这些附加的应用对现在的物联网通讯来说真的是方便了不少。

三、lwip的核心层

这一层是TCP/IP的协议栈的核心代码所在的地方,不单实现了几乎全部的TCP,UDP功能,还实现了ICMP,IGMP等协议。同时,还有mem内存管理,netif网络接口功能。这一层提供了一个针对各平台移植的sys_arch模块,以便于把lwIP移植到不同的操作系统中去。比如,实现线程的创建,信号灯,消息队列等功能,这些都是和平台密切相关的,与操作系统相关的接口定义写在lwip/include/sys.h文件里。

四、硬件驱动层

这一层提供了PHY芯片的驱动,以供lwip的使用。lwip会调用这一层的代码把组好的数据包发送到网络,同时也会通过这一层实现从网络收到数据包并加以分析各协议,实现通讯功能。

Lwip功能很多也很强大,下面简单地讲一下TCP数据收发的一个过程

发送消息,如下图所示:

(a) 应用代码调用netconn_write接口,把数据等参数传入,lwip检查连接是不是阻塞等,如果不是阻塞的直接返回错误,如果是阻塞则发送消息然后等待,通过消息队列切换执行流,由lwip线程把执行流走到lwip_netconn_do_write函数中去。这个函数检查当前数据有效性等,假设一切都正确,将调用tcp_write函数,由tcp模块进一步处理和打包数据。处理完成后,调用tcp_output向下层发送数据。

(b) TCP模块从tcp_write进入后,将执行tcp协议栈的具体功能,里面的逻辑比较多也比较复杂,如果只是出于对代码移植和使用,这些代码根本不需要改动。在tcp模块功能完成后比如准备缓冲器,填写校验和等,成功返回。

TCP模块从tcp_output进入后,将根据本地地址,目标地址等找到适合的路由的网络接口netif实例,把netif接口当作参数一起调用到ip模块,让数据往下发送。在lwip的设计中,一个实际的网卡PHY芯片就是对应一个netif实例。

(c) IP模块从ip_output_if函数进入后,检查如果是ipv4的数据则调用ip4_output_if,准备好ip包头,可选的ip包头等信息,然后,调用网络接口netif->output函数继续发送数据包。

(d) 网络接口netif模块根据PCB配置的网卡通过netif_add函数加入到lwip中,根据网络硬件的不同而实现不同的功能,如果网卡是以太网phy芯片,则把netif->output函数设置成etharp_output函数,由以太网ethernet模块去组装以太网帧,管理arp地址列表等。

(e) ethernet模块负责数据链路层的数据分析与组包,当数据组好以太网帧后,调用网络接口netif->linkeoutput函数,把数据通过网口芯片发送出去。

(f) 正点原子STM32F407的开发板的网络PHY芯片把网络接口netif->linkeoutput函数设置成low_level_output,所有数据最后通过low_level_output函数把它发送到PHY芯片并最终发送到网络。

接收消息,如下图所示:

1、应用代码调用netconn_recv接收网络数据,此函数分配一块缓冲器,然后通过调用sys_arch_mbox_fetch等待消息队列的返回。数据收到后,从消息队列返回,并包含有数据的缓存区。

2、sys_arch_mbox_fetch等待消息队列中的消息,是lwip需要实现的一个与平台相关的功能,在sys_arch模块中实现和管理,负责实现操作系统消息队列。

另一方面网络数据到达网卡的数据流如下:

a、数据通过网络到达网卡PHY芯片,这时触发MCU的一个中断。

b、 中断例程通过调用ethernetif_input函数通知lwip数据到达netif网络接口。

c、通过low_level_input函数获取网络数据。

d、ETH_Rx_Packet函数实现PHY的驱动,获取数据,每种芯片可能有不同的实现。

e、网络接口netif模块根据网卡芯片的不同,配置不同的netif->input函数,如果是以太网phy芯片,则配成是ethernet_input,因为phy芯片每一帧都是以太网帧,里面包含以太帧的全部信息,包括目的MAC地址及源MAC地址等。

f、ethernet模块把以太网帧的包头等信息去掉,并检查数据正确性之后,如果是IP包,则调用ip4_input函数,把数据往上传。IP的上层协议比较多,有udp,tcp,icmp,igmp等等,如果是TCP数据,则调用tcp_input把继续把数据往上传。

g、TCP模块比较复杂,收到TCP包后,不单要发送ack,还有可能要发送重传信息等。同时,为了网络性能,还要调整滑动窗口。数据处理完并且都正确后继续往上传。

h、数据通过TCP_EVENT_RECV宏通知lwip有TCP事件到达。TCP_EVENT_RECV宏定义成通过tcp_pcb->recv函数执行事件。此函数在netconn中配成recv_tcp。在recv_tcp函数里面调用sys_mbox_trypost函数通知数据接收完毕。这时上层代码可以唤醒接收数据了。

i、sys_mbox_trypost函数是一个与操作系统相关的函数,大部分的操作系统基本都有消息队列的功能,但每个操作系统的实现方式都不一样。

至此,lwip基本的收发功能基本走了一遍,协议栈中更详细的逻辑没有分析,这和移植没有什么关系了,以后有更深入的项目的需要,再去研究里面的代码吧。

标签: lwip

抱歉,评论功能暂时关闭!