RT-Thread优化的全功能型LwIP TCP/IP网络协议栈是RT-Thread RTOS引以为傲的组件,LwIP TCP/IP协议栈之全体现在:
- 完善的IPv4、ARP、ICMP协议支持
- 完善的UDP协议支持(还包括试验性质的UDP-lite扩展)
- 完善的TCP协议支持,包括了拥塞控制、RTT估计、报文分段与重组和快速恢复/快速重传
- 完善的DHCP、DNS协议支持
- 完善的PPP协议支持
- 于BSD Socket兼容的接口(兼容度非常高)
而RT-Thread针对LwIP优化体现在:
- 更低的内存占用,这点是lwIP用于小型系统的最大障碍,因为原始的lwIP版本大约会用到20 - 30k的内存。而RT-Thread的版本仅使用5k左右的内存占用。
- 采用发送与接收分离的方式处理lwIP底层的数据收发,更适合与RTOS的环境。
- 整合BSD Socket API接口到RT-Thread线程模型中:原版的lwIP中的socket操作(也包括其他的一些API)只能用于lwIP创建的线程,整合后可以在任意RT-Thread线程中使用。
- 其他一些lwIP问题修正。
在开源的TCP/IP网络协议中,另一著名的实现是uIP(lwIP作者Adam Dunkels的另一作品,大牛!),它是完全针对微小控制器准备的,但是小的体积也带来了功能上的损失,这也是RT-Thread并没选择支持uip的原因:
- API上,如果应用的数据有错误产生,应用需要自己负责进行重传;lwIP在接收了应用的数据后就会自行进行错误处理及重发;
- lwIP支持多个网络接口,并且也支持包转发;
- uIP的TCP数据传输速度奇低:大约是5kB/s。相对应的,lwIP(在RT-Thread系统上测试得到)能够达到1MB/s
好了,开始说LPC1768的移植
如前一篇文章,RT-Thread已经包括了LPC1768的基本移植,通常RT-Thread的做法是,只提供独立的工程,并且在工程中力图把最多的特性都打开了,而不是类似一些做开发板的,循序渐进的提供一些例子。当然,在RT-Thread极为优秀的剪裁能力基础上,能够化繁为简,或层层叠加形成一个复杂的系统:
>>使能LwIP TCP/IP网络组件,仅需要在rtconfig.h中定义RT_USING_LWIP宏。
有了LwIP TCP/IP协议栈,当然还需要相应的网络驱动,LPC1768芯片内置EMAC,外围电路还需要一个PHY,我的开发板上使用DP83848C。
RT-Thread的网络驱动主要框架包括:
void ENET_IRQHandler(void);
这个是EMAC外设的中断处理函数。
static rt_err_t lpc17xx_emac_init(rt_device_t dev);
static rt_err_t lpc17xx_emac_open(rt_device_t dev, rt_uint16_t oflag);
static rt_err_t lpc17xx_emac_close(rt_device_t dev);
static rt_size_t lpc17xx_emac_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size);
static rt_size_t lpc17xx_emac_write (rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);
static rt_err_t lpc17xx_emac_control(rt_device_t dev, rt_uint8_t cmd, void *args);
上面这些和SD卡的驱动很像,是RT-Thread的6大设备驱动接口。但是对于类似read/write这样的接口,它们的参数类型并不符合网络报文传输的需求,所以针对与以太网设备,额外定义了如下两个接口:
rt_err_t lpc17xx_emac_tx( rt_device_t dev, struct pbuf* p);
struct pbuf *lpc17xx_emac_rx(rt_device_t dev);
tx接口用于发送报文,它是阻塞性的,即如果底层容纳不下相应的报文,将阻塞调用这个函数的线程。当底层能够进行处理时,需要唤醒发送线程做下一步操作。入口参数p是lwIP用到的0拷贝的pbuf指针。
rx接口用于接收EMAC设备上的报文,返回的是pbuf类型报文,这个函数是非阻塞性的,即如果底层没有报文接收到时,将返回RT_NULL。
void lpc17xx_emac_hw_init(void);
这个函数用于注册一个网络设备驱动。
接口是这样,那么整个RT-Thread/LwIP的框架图最终会是:
tcp是lwIP协议栈的线程;erx和etx分别是面向底层以太网的两个收发线程;
当有EMAC接收到数据包时,中断被触发。在中断服务程序中将向erx线程通知一个设备就绪的消息;erx线程获得设备就绪的请求后,将主动去读取底层的网络报文。这里对系统做了一个优化,在中断服务程序中(如果是接收中断的情况下),它将先行关闭接收中断,而erx线程激活后将转为轮询方式poll设备(因为在读取报文的中间,即使有新的报文到达,erx也已经在服务了,新的中断新的设备就绪请求已经变成了一种浪费,所以这里直接转为轮询方式(仅针对于接收)),这对提高系统的吞吐量是非常有利的。erx线程会读空EMAC中接收到的报文,当EMAC发现已经没有报文需要向上层返回时,它将再行打开接收中断。
上面这个过程也就是lpc17xx_emac_rx函数的处理过程。
再来看lpc17xx_emac_tx函数。在lpc1768中,SRAM分成了两个部分,一段是常用的程序SRAM,地址位于0x10000000 - 0x10008000总计32KB。另一段是用于USB和ethernet的SRAM,地址位于0x2007C000 - 0x20084000。EMAC用的RAM放在了0x20080000 - 0x20084000,总计16K。
它是由两个ring构成的,一个是接收用的ring,一个是发送用的ring:
/* EMAC Memory Buffer configuration for 16K Ethernet RAM. */
#define NUM_RX_FRAG 4 /* Num.of RX Fragments 4*1536= 6.0kB */
#define NUM_TX_FRAG 3 /* Num.of TX Fragments 3*1536= 4.6kB */
#define ETH_FRAG_SIZE 1536 /* Packet Fragment size 1536 Bytes */
/* EMAC variables located in 16K Ethernet SRAM */
#define RX_DESC_BASE 0x20080000
#define RX_STAT_BASE (RX_DESC_BASE + NUM_RX_FRAG*8)
#define TX_DESC_BASE (RX_STAT_BASE + NUM_RX_FRAG*8)
#define TX_STAT_BASE (TX_DESC_BASE + NUM_TX_FRAG*8)
#define RX_BUF_BASE (TX_STAT_BASE + NUM_TX_FRAG*4)
#define TX_BUF_BASE (RX_BUF_BASE + NUM_RX_FRAG*ETH_FRAG_SIZE)
针对于发送,总计有3个 buffer 空间可用(每个buffer空间的大小是1536字节)。
就协议底层的etx线程而言,它需要调用lpc17xx_emac_tx函数把数据包写到tx ring上。但是写入到tx ring上并不代表着这个报文已经被成功发送出去,而需要等待EMAC的发送完成中断。这个过程与etx线程是异步的环境。如果etx线程等待EMAC发送完成无疑将会对系统的数据吞吐量造成巨大的影响,RT-Thread for LPC1768的做法是,建立一个计数器为NUM_TX_FRAG的semaphore,在每次发送报文到tx ring时,去获得这个semaphore;如果正确获得,那么表示tx ring的资源充足。如果获取失败,那么tx ring上已经全部放满了数据等待发送。当EMAC发送完成产生中断时,在中断服务例程中将释放一个semaphore,如果这个时候etx线程挂起在sempahore上,将把etx线程唤醒继续etx线程的发送动作。<这是一个最典型的把semaphore应用于资源计数的模型>
最后看看RT-Thread for LPC1768的一些配置说明:
1. EMAC初始化及lwIP初始化是在init线程中处理,因为默认采用PHY自动协商的方式去决定是10M还是100M,自动协商会需要一定的时间,放在init线程中能够把这部分时间用于其他线程继续执行。
2. rtconfig.h中能够配置lwip更多的选项,ip地址、网关等也是在这里进行配置。同样也能够在这里设置成DHCP自动获得IP地址、DNS服务器地址的方式。
3. RT-Thread同样包括一些基本的上层应用,例如telnet服务端,tftp client,NFSv3文件系统,GoAhead WebServer,ping小程序等,这些可以从RT-Thread的google svn服务器上或论坛上获得相应的源代码。
----
RT-Thread RTOS是一个国产的开源实时操作系统。RT-Thread/LPC17xx的代码已经放在Google SVN上,当前已经包括内核、shell、文件系统、以太网的支持。查看如下网址以获得最终的源代码。
http://code.google.com/p/rt-thread/source/checkout
目前仅支持Keil MDK编译器,欢迎使用。以上的完整代码请见bsp/lpc176x/emac.c