• LWIP轻量级TCPIP协议栈的移植


    转自:http://blog.csdn.net/ygrx/article/details/8020516

    好久没有做过技术工作了,前几天因为一些需要,要在ST的OS20平台上进行了LWIP的移植,有一些心得,写出来供大家参考。


    LWIP的背景我就不介绍了,相信能看到这篇文章的人都对其背景有过了解了。

    LWIP的模块化还是很强的,所以移植起来没有想象的那么多困难,一个协议栈在某个平台上移植,其实主要来说包括两个大的部分接口,注意一下,我使用的是最新的LWIP1.4.0

    1.与系统相关的接口,比如多线程,信号量,互斥锁,系统时间等,当然,LWIP还有一种无操作系统模式,那这些接口就不用实现了。

    2.与硬件相关的接口,实际上就是网卡驱动啦,当然,如果你的物理层不是网卡而是别的什么,那就需要的是另外那个硬件设备的驱动啦,比如Docsis设备之类的。


    好了,开始移植吧。

    首先是操作系统部分:

    1.opt.h

    首先看这个文件,这个文件里面包含了LWIP的模块选项,可以在这里选择哪些模块需要编译,那些模块不编译,分成几个部分,mem,arp,icmp,igmp,ppp,dhcp等,这里可以根据自己的需要修改编译选项,如果是带操作系统的,还要修改栈空间,优先级之类的选项。

    2.cc.h

    这个文件是没有的,你需要建立一个目录arch,然后在下面添加cc.h,这里里面主要是一些定义,包括数据类型,大小头端之类的,我的cc.h比较简单

    /*数据类型宏定义*/

    typedef unsigned char u8_t;

    typedef unsigned short u16_t;

    typedef unsigned int u32_t;

    typedef  char s8_t;

    typedef  short s16_t;

    typedef  int s32_t;

    typedef int mem_ptr_t;

    typedef semaphore_t sys_sem_t;

    typedef mutex_t sys_mutex_t;

    typedef message_queue_t sys_mbox_t;

    typedef u8_t sys_thread_t;

    #define BYTE_ORDER LITTLE_ENDIAN   //大小头端选择

    /*调试信息宏定义*/

    #define U16_F "hu"

    #define S16_F  "d"

    #define X16_F "hx"

    #define U32_F "u"

    #define S32_F "d"

    #define X32_F "x"

    #define SZT_F "uz"

    就这些。


    3.arch.h

    这个地方需要注意就是字节对齐,字节对齐在某些编译器上是用的#pragam pack(n)来实现的,那需要把相应的宏定义修改:

    #ifndef PACK_STRUCT_BEGIN

    #define PACK_STRUCT_BEGIN   #pragma pack(1)

    #endif /* PACK_STRUCT_BEGIN */

    #ifndef PACK_STRUCT_END      #pragma pack()

    #define PACK_STRUCT_END //#

    而ST的st2cc编译器是在编译的时候加了个参数来进行字节对齐的,所以这里不用修改


    4.sys.h

    这个文件就比较关键了,这是你移植过程中要写最多代码的地方,在LWIP中,如果是有操作系统的编译版本,那么需要实现线程和与线程通信的相关代码,其中包括:线程建立,互斥锁,信号量,消息队列(一般叫MailBox),系统当前时间,需要实现的东西都在sys.h中,一般情况下,我们为了文件清晰,会在arch目录下新建一个文件sys_arch.c,当然,你也可以直接在根目录下建立这个文件,注意的是要在该文件中include sys.h,然后你就开始挨个实现sys.h中的系统函数吧,实际上也挺简单,就是把系统的API调用封装成LWIP的模式。

        线程部分:

            sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread, void *arg, int stacksize, int prio)  线程建立

        互斥锁:

            err_t sys_mutex_new(sys_mutex_t *mutex)                  新建锁

            void sys_mutex_lock(sys_mutex_t *mutex)                  获取锁

            void sys_mutex_unlock(sys_mutex_t *mutex)             释放锁

            void sys_mutex_free(sys_mutex_t *mutex)                   删除锁

            int sys_mutex_valid(sys_mutex_t *mutex)                    查询锁是否可用

            void sys_mutex_set_invalid(sys_mutex_t *mutex)        设置锁为失效

        信号量:

            err_t sys_sem_new(sys_sem_t *sem, u8_t count)                 新建信号量

            void sys_sem_signal(sys_sem_t *sem)                                  发送信号量

            u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout) 等待信号量

            void sys_sem_free(sys_sem_t *sem)                                        删除信号量

            int sys_sem_valid(sys_sem_t *sem)                                        查询是否可用

            void sys_sem_set_invalid(sys_sem_t *sem)                            设置为失效

        消息队列(MailBox):

            这个东西稍微复杂一点,如果操作系统的API中没有现成的消息队列机制,那么你需要自己实现,其实无非就是个链表的带锁操作,在OS20中是自己带有MailBox的API的,所以我也没自己实现,直接使用了,需要实现的函数在sys.h中也都有,我就不列举了。

        系统时间:

            这个我返回的是系统调用的系统时间,就是系统启动后多少秒

            u32_t sys_now(void)

    好了,这几个文件都实现完了,那基本上操作系统部分就差不多了,我的建议是,在opt.h和cc.h修改完以后,你就开始编译,如果有错误几乎都应该是没有include某个文件造成的,及时修改错误,然后直到编译得没有错误了,再来添加sys_arch.c,这样可以减少一些麻烦。

    总得来说就是,首先,你要修改opt.h,把你需要的模块添加上去,把栈空间,mailbox大小之类的定义好,然后编译,因为LWIP是标准的C写的,理论上应该很容易编译成功,我只是有两个地方没有.h文件,添加一下就编译过了。

    当然,编译过了只是第一步,完了以后就要手写sys_arch.c了,sys.h里面的所有函数都要实现,当然,你要偷懒的话有些也可以直接返回。这个文件是和你系统API相关的,写好以后记得加入到Makefile里面,然后再编译,找错误,编译,直到成功。

    其实还有临界保护,主要是

    #define SYS_ARCH_DECL_PROTECT(lev)

    #define SYS_ARCH_PROTECT(lev)

    #define SYS_ARCH_UNPROTECT(lev)

    这三个宏,定义成你的系统的临界保护调用就行了,我偷懒定义的空的,暂时没发现问题。


    好了,操作系统部分大概就这么一些,接下来就是底层的硬件驱动了,驱动的具体实现我就不说了,各自的平台不一样,网卡硬件也不一样,但是基本的三个东西一定要有,1.初始化网卡,2.接收数据,3.发送数据。

    在LWIP1.4.0中,其实已经跟我们写好驱动的框架,我们所需要做的就是填充这个框架。框架对应的文件是:ethernetif.c

    打开这个文件后你会发现这个文件被#if 0包起来了,首先改成#if 1,这样,你会发现这个文件中有如下几个函数:

    static void low_level_init(struct netif *netif)  //底层硬件初始化

    static err_t low_level_output(struct netif *netif, struct pbuf *p)  //底层硬件发送数据函数

    static struct pbuf * low_level_input(struct netif *netif)  //底层硬件接收数据函数

    err_t ethernetif_input(struct pbuf *pp,struct netif *netif)  //硬件抽象层接收数据

    err_t ethernetif_init(struct netif *netif)  //硬件抽象层初始化

    这些函数里面已经实现了一部分代码,主要是硬件抽象层的代码,我们需要把具体的驱动程序添加进去,我们按调用的顺序来说一下:

          当LWIP协议栈启动的时候,协议栈本身会首先调用ethernetif_init,然后在ethernetif_init函数中会调用low_level_init,在low_level_init中实际上就是你的网卡的初始化程序了,当然,为了更加结构化,你可以另外写一个函数比如DM9003_init,在low_level_init调用他,总之low_level_init中就要实现你的网卡初始化,参数就是netif,用来告诉你这个设备需要绑定一个句柄。

        当协议栈要有数据发送出去的时候,协议栈会自动调用low_level_output,参数包括netif,这是的句柄,用来区分应该从哪个网卡发走,p就是数据包内容。

        当有数据进来的时候,这时就和你的实现有关了,如果你底层获取数据是中断形式,那么就是中断来了以后在中断中调用ethernetif_input,然后在ethernetif_input会调用low_level_input去硬件获取数据,然后把数据通过消息队列的形式发给LWIP协议栈,所以这一部分中,low_level_input是需要实现的实际的硬件接收数据驱动程序,并且,需要驱动在适当的时候调用ethernetif_input来把数据传给上层协议栈,参数还是netif,实际上这个netif一直是个全局变量,稍后我们会说在哪里声明他。ethernetif_input 里调用 ethernet_input

        数据接收这一部分有很大的灵活性,总的原则就是当数据确实的到达以后,由驱动在适当的时候调用ethernetif_input,而low_level_input是具体的获取数据函数,完了ethernetif_input会简单的判断一下数据类型然后决定是否传到上层协议栈。

        由于OS20的速度有限,在中断中直接调用ethernetif_input时间消耗很长,我的实现是,在初始化时启动了中断模式,并且在初始化时同时启动了一个轮询线程,中断来了数据以后直接把网卡的数据存到内存中去,然后给轮询的线程发一个信号量,轮询线程先阻塞在信号量上,收到信号以后,查询该内存块,有数据的话就在线程中调用ethernetif_input把数据发给上层协议栈,然后重新阻塞住。


    好了,至此,底层部分结构也说完了,现在让我们看看初始化,LWIP是如何工作的,其实上面两部分移植好了以后,要使用LWIP很简单,只有两个步骤。

    1.找到netif.c,在前面声明一个全局变量: struct netif eth_netif;

                            然后找到void netif_init(void)函数,在该函数最后面加上(静态IP):

                           ip_addr_t loop_ipaddr, loop_netmask, loop_gw;

                        IP4_ADDR(&loop_gw, 192,168,3,1);  设置网关的IP    

                           IP4_ADDR(&loop_ipaddr, 192,168,3,100);设置本机IP

                           IP4_ADDR(&loop_netmask, 255,255,255,0);设置子网掩码

                /*将IP绑定到eth_netif上,并且告诉协议栈,初始化网卡的函数是ethernetif_init,网卡接收到数据以后调用tcpip_input来处理数据*/

                        netif_add(&eth_netif, &loop_ipaddr, &loop_netmask, &loop_gw, 0, ethernetif_init, tcpip_input);

                        netif_set_up(&eth_netif);  启动该网络设备


    这里的eth_netif就是上一部分中的netif参数,设置成一个全局变量,驱动就能用了,具体是怎么对接的仔细看看netif_add就明白了,tcpip_input函数是协议栈自带的,不用管,填上就行。


    2.在你的main函数里调用tcpip_init();记得include "tcpip.h"。

    OK,结束。

    如果你按这个步骤一步一步来的话,并且驱动程序正确,那么这时候你应该可以ping通你的设备了,接下来你可以试试网络上传下载的速度啦,后面的东西你就自己微调吧,怎么样,够简单吧?


  • 相关阅读:
    nginx proxy_cache 缓存配置[转]
    MongoDB使用小结:一些常用操作分享
    PHP读取Mongodb数据报错,Cannot natively represent the long 8331412483000 on this platform
    MongoDB 学习笔记(python操作)
    Python 中 os.path模板
    Python 优雅的操作字典【转】
    MongoDB 一对多关系建模
    nginx之location配置
    MongoDB安装Windows服务
    .net4.0 请求HTTPS出错:未能创建 SSL/TLS 安全通道
  • 原文地址:https://www.cnblogs.com/alan666/p/8312136.html
Copyright © 2020-2023  润新知