引言
随着物联网技术的发展,普适计算理念[1]的深化和大数据处理能力的增强,更多数据将通过网络进行集中化、智能化处理。在此背景下,对小型嵌入式设备提出了具有网络通信能力的更高要求[2]。通过在实时操作系统上移植网络协议栈lwIP[3],将为此类设备的研发提供具有网络功能的操作系统支持。
lwIP是一款优秀的轻量级嵌入式网络组件,为各领域众多嵌入式设备提供了小容量低成本的TCP/IP网络协议栈。当前众多知名半导体芯片厂商提供的软件开发包中,都毫无例外地选用lwIP为网络支持组件[4]。但是,不同芯片厂商的软件开发包基于不同的操作系统,需要切换不同的嵌入式开发环境,给开发人员学习和使用带来了额外工作量[5],延长了开发周期。μTenux作为开源免费的实时操作系统,支持ST、ATMEL、NXP、Freescale、TI等多家芯片厂商ARM CortexM构架的主流芯片,应用该操作系统进行产品研发可缩短开发周期,降低商业成本[6]。
1 lwIP协议栈
lwIP(lightweight IP)是瑞士计算机科学院的Adam Dunkels设计编写而后全世界开源贡献者共同升级维护的开源代码。该组件没有严格地遵循TCP/IP协议的分层思想,采用各个协议层交叉存取和内存共享的构架方式,是一个以占用尽量小的资源代价实现TCP/IP协议栈的轻量型中间件。
lwIP的主要特点如下[7]:
① 实现了包括IP、ICMP、UDP、TCP、IGMP、ARP、PPPoS、PPPoE在内的诸多网络协议;
② 协议栈支持DHCP客户端,DNS客户端,AutoIP/APIPA(零配置网络)和SNMP代理(私有的MIB支持);
③ 拥有增强功能的独有API接口和可选的Berkeley风格的Socket API接口;
④ 支持多网络接口、TCP拥塞控制、延时估计和快速恢复、快速重传机制;
⑤ 包含HTTP服务器、SNTP客户端、SMTP客户端、ping、NetBIOS域名解析器等应用示例。
从上述特点以看出,lwIP是一个功能全面的协议栈。同时,因为lwIP仅仅占用RAM空间几十KB,ROM空间40 KB左右,所以该组件成为ARM构架32位嵌入式设备中网络中间件的首选。
1.3.2 版本的lwIP从代码的组织结构上可分为4个层次,如图1所示,其中netif是和底层物理环境紧密相关的网络接口驱动部分,该部分主要实现网络控制器初始化和数据包收发的功能。arch是lwIP为移植设计的操作系统模拟层,移植时需要创建sys_arch.c文件,并在该文件中实现多任务,任务间消息传递的邮箱和信号量,同步以及超时保护等机制。core和api包含了lwIP核心代码和应用编程接口代码,移植时不需要修改。值得指出的是,lwIP在没有操作系统的支持下也可以运行,此时不需要arch组件的支持,但是lwIP只能运行在一个线程下,会降低性能。
图1 lwIP组件结构图
2 μTenux实时操作系统
μTenux是由TEngine中国开源社区研发推出的适用于32位ARM CortexM系列内核的嵌入式多任务硬实时操作系统。该操作系统采用可移植、可裁剪、任务可剥夺的μTKernel作为系统内核规范,允许开发人员在小规模嵌入式系统上进行优化和裁剪。图2给出了μTenux操作系统构架,其中内核μT/Kernel由三部分(μT/OS、μT/SM、μT/DS)构成[8],分别为操作系统内核、管理内核和调试内核。底层包含完整的驱动库和标准C库文件,中间件和上层接口采用主流的第三方组件[6]。本文是μTenux中μT/Subass研发的一部分,开源代码会随μTenux软件包一起在TEngine中国开源社区公布并不断完善升级。
图2μTenux 操作系统构架
3 移植过程
lwIP基于操作系统的移植主要分为三个方面[9]:①根据基于的CPU构架不同,需要定义和硬件相关的数据类型与结构以及调试、测量等函数实现方法;②实现操作系统模拟层的多任务、邮箱、信号量、同步、时间超限等函数;③根据使用的网络控制器芯片不同,以及和主控芯片通信的协议不同,需要对底层以太网驱动进行移植。结合μTenux操作系统中的对象特点,本部分对lwIP移植过程中的几个要点进行分析。
3.1 lwIP邮箱函数的移植
当产生一次数据包处理时,lwIP中各个协议层之间通过维护同一个数据空间来减少数据包复制带来的RAM损耗和时间损耗,该数据包所占空间的起始地址在各个协议层之间通过邮箱机制进行传递。
lwIP的邮箱机制需要实现邮箱的创建、删除、发送(堵塞函数,直到发送成功)、尝试发送(发送后立即返回状态)、接收(带有定时参数,有时间限制)、尝试接收(接收后立即返回状态)等6个函数。按照lwIP的移植要求,在sys_arch.c中要实现以上函数。μTenux本身的邮箱机制对消息结构有特殊规定,本文采用消息缓冲区机制实现lwIP的邮箱函数封装。μTenux和其他操作系统功能上的区别可以从消息缓冲区的实现机制上得以体现。μTenux提供的相关函数有:建立和删除消息缓冲区,发送和接收消息以及查询消息缓冲区的状态。
图3是μTenux缓冲区机制消息传递过程的示意图,每个消息缓冲区都包含一个等待发送消息的任务队列和一个等待接收消息的任务队列[9]。图中,以两个任务队列各有一个任务为例,发送任务和接收任务通过动作①和②实现消息的传递。每当创建一个新的消息缓冲区时会产生一个连续的空间,内核通过MBFCB(消息缓冲区控制块)进行管理,实现缓冲区的环状消息结构。在创建、使用和释放消息缓冲区的过程中,μT/Kernel内核始终对内存进行着最优的管理,这个过程中不会产生内存空间碎片。消息缓冲区机制可用来传递大小可变的消息,因为要用此机制实现lwIP的邮箱函数,所以封装时对lwIP的消息取地址,将地址作为μTenux缓冲区的内容传递。
图3 μTenux 的消息缓冲区机制
以邮箱的创建为例,可以直接对tk_cre_mbf函数进行封装,其中maxmsz(最大消息长度)的参数为sizeof (void *), 是因为邮箱传递的参数是数据指针,所以这里最大消息长度规定不会超过这个长度。代码略——编者注。
lwIP邮箱的其他实现函数也是对消息缓冲区相关函数的封装,需要注意两点,μTenux的消息缓冲区函数中与时限有关的参数和lwIP中给出的定义不同;使用μTenux消息缓冲区机制传递的是lwIP邮箱消息的二重指针。
3.2 lwIP信号量函数的移植
lwIP的信号量机制需要实现信号量的创建、删除、占用、等待等4个函数。信号量机制在操作系统中很常见,它一般用来指示可用资源,并将可用资源以数值的形式表示出来,多任务系统可以通过信号量实现任务间的同步和互斥控制。μTenux的信号量机制中包含一个资源计数器和一个等待信号量的任务队列。一个任务可以发出事件通知,释放返回m个资源,此时,信号量资源计数加m。相反,当该任务正在等待事件获得n个资源时,信号量资源计数器将减n。如果信号量资源的数量不够,信号量计数器可以变成负值,则尝试获取资源的任务进入等待状态,直至下次有资源释放返回。等待信号量资源的任务会被置入信号量任务队列中。μTenux为防止过多资源同时返回到信号量,在信号量中设置一个最大信号量计数器,当返回到信号量的资源造成最大计数的超出时报错[8]。lwIP的信号量实现可以直接使用μTenux的信号量机制。以信号量的创建为例,可以直接对tk_cre_sem函数进行封装,其中maxsem由用户在sys_arch.h中设定,配置成CFN_MAX_SEMID(μTenux信号量的配置值),使μTenux中的资源配置直接用于lwIP。代码略——编者注。
lwIP信号量的其他实现函数也是对相关函数的封装,和邮箱机制的实现方法一样,要考虑时限的对应关系。
3.3 lwIP网络控制器驱动函数的移植
3.3.1 lwIP协议栈数据包结构
在lwIP中,每次数据包处理时占用的数据空间采用pbuf结构。该结构同BSD中的mbuf结构类似,是网络控制器驱动程序实现的核心[9]。
图4 pbuf结构
lwIP的实现同样基于TCP/IP协议的分层思想,在lwIP的core组件中包含单独命名的模块,例如udp.c、tcp.c、dhcp.c等,它们彼此共享一个共同的数据空间,各个层次之间传递的是数据地址而不是数据本身,这个数据采用的是上文提到的pbuf结构,如图4所示。pbuf结构根据数据所在的内存区域和申请方式不同,有4种不同的类型,本文以典型的PBUF_RAM类型为例,通过图4分析pbuf的结构定义。
pbuf结构中有7个参数,通过next指针组成pbuf链表。一个数据包往往包含多个pbuf结构的数据,lwIP在运行中涉及消息的接收、发送、传递、丢弃等操作,要通过这几个参数中的长度和标志位来识别判断。这种实现机制为lwIP协议栈节省了内存空间和处理时间,却为以太网驱动的实现增加了复杂度。
3.3.2 网络接口函数的实现
该部分与底层硬件联系紧密,需实现5个基本函数:网络接口初始化函数、网络接口接收函数、底层网络控制器初始化函数、底层网络控制器接收数据包函数、底层网络控制器发送数据包函数。
本文以Microchip公司生产的基于SPI接口的以太网控制器芯片ENC28J60[11]为例,其基本操作驱动库代码采用美国SourceForge.net平台AVRNET[12]开源项目中的代码(enc28j60.c)。lwIP底层网络接口文件取名enc28j60if.c,需实现上面提到的5个函数。其中,底层网络控制器接收数据包函数和底层网络控制器发送数据包函数由于考虑到lwIP的pbuf结构,所以不能直接使用enc28j60.c中的数据包收发函数,这部分需要进行改写。以底层网络控制器接收数据包函数为例,具体实现分为4步:①判断enc28j60中是否有数据接收;②配置接收缓冲区大小;③判断数据合法性并申请pbuf结构;④接收数据到pbuf结构中。代码略——编者注。
ENC28J60接收的数据包由4部分组成(下一个数据包的指针、接收的状态向量、接收的有效数据、CRC校验)。底层网络控制器发送数据包函数用到的也是这样的思想。此外,网络接口初始化函数和网络接口接收函数直接使用lwIP提供的模版函数,底层网络控制器初始化函数配置lwIP的netif接口后调用enc28j60.c中的初始化函数即可。
4 lwIP移植的简单测试用例
以STM32F407VG[13]和ENC28J60为例,在μTenux软件包中,已经对STM32F407VG的时钟和串口进行了初始化设置,添加SPI接口初始化函数即可进行lwIP的移植测试。本部分先介绍lwIP的应用编程接口,然后给出测试实例和结果。
4.1 lwIP的API接口说明
图5 lwIP依赖关系
lwIP为用户编写上层应用代码提供了三种不同的API接口,分别是原始API接口、高性能专用的sequential API接口和Berkeley风格的Socket API接口。当不提供操作系统支持的时候只能使用原始API接口,当提供操作系支持时,三种接口方式都可以使用。
基于前文,当完成了lwIP基于μTenux的操作系统模拟层移植和ENC28J60的以太网网络接口的函数封装,建立了如图5所示的依赖关系,这时就可以进行程序编写和测试了。
4.2 一个简单的HTTP服务器实现
为了检验移植后的lwIP是否能够正常运行,使用lwIP的sequential API写一个简单的HTTP服务器程序[14]。
程序中,将html语言的字符串存储在一个静态变量htmltestdata中,在主函数中创建一个名为http_task的进程函数,在该进程函数中完成以下几步:①初始化lwIP协议栈,注册网络接口;②允许STM32F407VG接收SPI外部中断,使ENC28J60接收到数据之后可以马上处理;③新建一个TCP链接,并监听端口号80;④当监听到客户端连接后处理客户端接收到的数据。完成上述4步之后,当发现客户端请求的数据并且能正确解析时,则将htmltestdata返回,这样就实现了一个简单的HTTP服务器。
建立TCP连接部分代码略——编者注。
编译成功并运行后,通过在Windows控制台上执行ping命令,并且打开浏览器在地址栏中输入网络接口初始化时绑定的IP地址,图6和图7的测试结果表明ARP、ICMP、IP和TCP协议运行正常。
图6 ping命令测试
图7 HTTP应用示例
结语
lwIP是一款优秀的免费开源的TCP/IP网络中间件,基于μTenux的lwIP移植结合了操作系统的优势,可以用于嵌入式设备的产品研发上。本文下一步研究工作考虑结合μTenux实时系统的消息邮箱和内存管理特性,以改进lwIP中pbuf结构的内存申请方式,缩减移植后的lwIP代码量,降低运行时ROM和RAM占用量,进一步优化lwIP的实时性和稳定性。