LwIP协è栈在uCOS II下的实现
1、æ述: LwIP协è栈在设è时就考虑到了将来的移植问题,因æ把所有与硻¶、OS、编译器相关的部份独立出来,放在ucosii&LwIPsource etlwiparch盽•下。因æwIP在uCOS II上的实现就是俔¹这个盽•下的文件,其它的文件一舸�应è俔¹。下面分几部份分åˆ明相应文件的实现原理和过程ã€?、与CPU或编译器相关的include文件:ucosii&LwIPsource etlwiparchucosIIincludearch盽•下cc.h、cpu.h、perf.h东‰一些与CPU或编译器相关的定义,如数捕¿度,字的高低位顺序等。这应è与用户实çŽ?micro;C/OS II时定义的数据长度等参数是一致的ã€?define BYTE_ORDER LITTLE_ENDIAN //C33209默è为小竭˜储系ç»?/数据ç±
»型长度的定义typedef unsigned char u8_t;typedef signed char s8_t;typedef unsigned short u16_t;typedef signed short s16_t;typedef unsigned int u32_t;typedef signed int s32_t;此å还有一点:一舃…况下C诨€的结构体structæ˜?字节对齐的,但是在å理数捌…的时候,LwIP使用的是通过结构体中不同数据的长度来读取相应的数捚„,所以,一定è在定义struct的时候使用_packed关键字,让编译器放弃struct的字节å齐。LwIP也考虑到了这个é—,所以,在它的结构体定义东‰几个PACKED_FIELD_xxx宏,默è的时候这几个宏都昩º的,叻¥在移植的时候添加不同的编译器所对应的_packed关键字。比如在Skyeye(C33209)上对应gcc编译器的定义ï¼?define PACK_STRUCT_FIELD(x) x __attribute__((packed))#define PACK_STRUCT_STRUCT __attribute__((packed))#define PACK_STRUCT_BEGIN#define PACK_STRUCT_END3、sys_arch操作系统相关部份ï¼? sys_arch.[ch]业„内å昸ŽOS相关的一些结构和函数,主要可以分为四七¨份:(1) sys_sem_t 信号量LwIP东€要使用信号量通信,所以在sys_arch为”实现信号量结构体和å理函数:struct sys_sem_t sys_sem_new() //创建一丿¡号量结构 sys_ sem _freeï¼
ˆ) //释放一丿¡号量结构 sys_ sem _signal() //发送信号量 sys_ arch_sem _wait() //请求信号量由äº?micro;C/OSII已经实现了信号量OS_EVENT的各种操作,并且功能和LwIP上面几个函数的目的功能是完全一样的,所以只要把µC/OSII的函数重新包装成上面的函数,就可以直接使用了ã€?2) sys_mbox_t 消息LwIP使用消息队列来缓冲、传递数捊¥文,因æ要在sys_arch丮ž现消恘Ÿ列结构sys_mbox_t,以及相应的操作函数:sys_mbox_new() //创建一丶ˆ恘Ÿ列s
ys_mbox_free() //释放一丶ˆ恘Ÿ列sys_mbox_post() //向消恘Ÿ列发送消息sys_arch_mbox_fetch() //从消恘Ÿ列中获取消息µC/OSII同样实现了消恘Ÿ列结构OSQ及其操作,但æ˜?micro;C/OS-II没有对消恘Ÿ列中的消恿›行ç理,因æ不能直接使用,必须在µC/OS-II的基础上重新实现。为了实现å消息的ç理,我们定义了以下结构:typedef struct { OS_EVENT* pQ; void* pvQEntries[MAX_QUEUE_ENTRIES];} sys_mbox_t
;在以上结构丼Œ包括OS_EVENT类型的队列指针(pQ)和队列内的消息(pvQEntries)两部分,å队列机«的ç理利ç”?micro;C/OS-II臷±的OSQ操作完成,然后使ç”?micro;C/OS-II业„内存管理模块实现对消恚„创建、使用、删除回收,两部分综合起来形成了LwIP的消恘Ÿ列功能ã€?3) sys_arch_timeout 函数LwIP丯�丸Ž外界网络连接的线程都有自己的timeout属性,即等待超时时间。这丱ž性表现为每个线程都å应一个sys_timeout结构体队列,包括这个线程的timeout时间长度,以及超时后应调用的timeout函数,è函数会做一些释放连接,回收资源的工作。å果一为¿程å应的sys_timeout为空(NULL),说明该线程å连接做永久的等待。timeout结构体已经由LwIP臷±在sys.h丮š义好了,而且对结构体队列的数捓�作也由LwIP负责,我仉€要实现的æ˜下函数:struct sys_timeouts * sys_arch_timeouts(void)这个函数的功能是返回盉�正å于运行态的线程所对应的timeout队列指针。timeout队列属于线程的属性,因æ是OS相关的函数,參½由用户实现ã€?4) sys_thread_new 创建新线程LwIP叻¥昍•线程运è,即叜‰一个tcpip线程(tcpip_thread),负责处理所有的tcp/ucp连接,各种网络程序都通过tcpip线程与网络交互。但LwIP也可以å线程运è,以提高效率,降低编程å杂度。这时就需要用户实现创建新线程的函数:void sys_thread_new(void (* thread)(void *arg), void *arg);åœ?micro;C/OS II丼Œ没有线程(thread)的概念,只有任务(Task)。它已经提供了创建新任务的系统API调用OSTaskCreate,因此只要把OSTaskCreate封è一下,就可以实现sys_thread_new。需要注意的是LwIP业„thread并没æœ?micro;C/OS II丼˜先级的æ念,实现时è由用户事先为LwIP丈›建的线程分配好优先级ã€?、lib_arch为“函数的实现: LwIP协è栈中用到äº?ä¸部函数,这些函数通常与用户使用的系统或编译器有关,因此留给用户自己实现。å下:u16_t htons(u16_t n); //16位数捫˜低字节交æ�16_t ntohs(u16_t n);u32_t htonl(u32_t n); //32位数捤§小头对调u32_t ntohl(u32_t n);int strlen(const char *str); //返回字ç串长åºnt strncmp(const char *str1, const char *str2, int len); //字ç串比较void bcopy(const void *src, void *dest,
int len); //内存数据块之间的互相拷贝void bzero(void *data, int n); //内存丌‡定长度的数据块清é›? 前四万½数通常由用户自己实现。在我的系统丼Œ由于使用了gcc编译噼Œgcc的lib库里已经有了两个字ç串操作函数。若用户的编译器的库串¡有这些函数,需要自己编写ã€?、网络è备驱动程序: 在我的系统中使用的网络芯片为RealTekçš?019as芉‡,这是ISA 10BASE-T的以夽‘芉‡,与Ne2k兼å。所以目前实现的网络设å驱动昒ˆ对Ne2k的,其它类型的网络芯片驱动可以在LwIP的网站上找到。LwIP的网络驱动有一定的模型,ucosii&LwIPsource etlwiparchucosII etif 业„ne2kif.c文件即为驱动的模板,用户为自己的网络设å实现驱动时应参照此模板ã€? 在LwIP丏¯以有多个网络接口,每丽‘络接口都对应了一个struct netif,这个n
e2kif包含了相应网络接口的属性、收发函数。LwIP调用ne2kif的方法netif->input()及netif->output()进è以太网packet的收、发等操作。在驱动丸»要做的,就是实现网络接口的收、发、初始化以及世处理函数。驱动程序工作在IP协è模型的网络接口层,它提供给上层(IP层)的接口函数å下://网卡初å化函数void low_level_init (struct netif *netif)//网卡接收函数,从网络接口接收以太网数捌…并把其中的IP报文向IP层发é€?/在中斖¹式下由网å�SR调用void ne2k_recv_packet (struct netif *netif)//网卡发送函数,给IP层传过来的IP报文加上以太网包头并通过网络接口发送err_t ne2k_send_packet (struct netif *netif, struct pbuf *p, struct ip_addr *ipaddr)//网卡世处理函数ISRvoid ne2k_isr (void); 以上的函数都叻¥分为协è栈本躚„处理和å网络接口硻¶的操作两部份,但硻¶操作æ˜上层屏蔽的,具体参èRTL8019as、DM9008等Ne2k网络芉‡的数捉‹册。驱动程序可以到LwIP的网站下载ã€?、应用实例的建立和测è¯? 做完上面的移植修改工作以后,就可以在uCOS II丈�始化LwIP,并创建TCP或UDP任务进è测试了。这部份完全是C诨€的实现,因æ这部份在ez80和ARM7上基會½昸€样的。值得注意的是LwIP的初始化必须在uCOS II完全吊¨之后也就昜¨任务丿›行,因为它的初å化用到了信号量等OS相关的操作。关锃¨份的代码和è明å下:void start_kernel(void){ int LineNo10 = 0; int LineNo11 = 1; int LineNo12 = 2; int LineNo13 = 3; int LineNo14 = 4; OSInit(); OSTaskCreate(lwip_init_task, &LineNo10, &lwip_init_stk[TASK_STK_SIZE-1], 0); OSTaskCreate(usr_task,&LineNo14,&usr_stk[TASK_STK_SIZE-1],20); vRTCStart(); OSStart(); /* NEVER EXECUTED */ while(1);}主程序中创建了lwip_init_task初å化LwIP任务(优先级0)和usr_task用户任务(优先级20)。lwip_init_task任务丙¤了初始化硻¶时钟和LwIP之å,还创建了tcpip_thread(优先级5)和tcpecho_thread(优先级6)。实际上tcpip_thread才是LwIP的主线程,å线程的Berkley API也是基于这个线程实现的,即上面的tcpecho_thread线程也è依靠tcpip_thread线程来与外界通信,这样做的好处是编程简单,结构清晰。实用Berkley API实现的tcpecho_thread昸€个TCP echo服务噼Œ监听7号ç口,程序框架如下:void tcpecho_thread(void *arg){conn = netconn_new(NETCONN_TCP); //创建新的连接标识netconn_bind(conn, NULL, 7); //绑定åˆ?号çå�etconn_listen(conn); //开始监å�å�? while(1){ newconn = netconn_accept(conn); //接收外部到来的连æŽ? buf = netconn_recv(newconn) //获取数据 ……. //处理数据 netconn_write(newconn, data, len, NETCONN_COPY); //发送数æ�? netconn_delete(newconn); //释放æœ连接} } 编译运è后,用ping ip地址命令叻¥得到ICMP reply响应。用telnet ip地址 7(登å½?号ç口)命令叻¥看到echo server的回显效果。è明ARP、ICMP、IP、TCP协è都已正确运èã€