OS-lwip

    技术2022-05-20  26

    lwip是一个轻型的网络协议栈,基本满足大部分应用,参考ucos的代码框架,lwip目录下arch/ucos-iiucos对于lwip的接口,这个接口是严格按照lwip的文档来做的;其他目录不需要修改任何文件,直接下载lwip代码即可,这样的代码可读性很强,非常的直观;

    协议栈portingucos,主要涉及到:

    lwip需要的任务接口,信号量接口,消息队列接口,时钟接口,这些wip官方的基于ucosSDK,已经做了,主要功能就是和ucosapi做一个封装;

    另外一方面就是lwip的底层硬件接口,也就是网卡驱动接口;

    所以需要做的工作,一是lwip的初始化以及起lwip任务,二就是封装底层网卡驱动

     

    1.        lwip接口分析

    SDK做了工作就是把ucosAPI封装成lwip自己采用的API,下面就是lwipAPI,包括任务,信号量,邮箱(类似消息队列),时钟,这些函数需要重新封装的,用ucosAPI来实现,这些函数几乎和ucos的类似,区别的只是有一些参数的少许区别,arch/ucos-ii/sys_arch.c就是SDK的核心接口,用来实现封装,如果不是ucos,比如nucleus或者rtems等之类的,这个文件也是唯一需要更改的

     

    #define sys_init()

    #define sys_timeout(m,h,a)

    #define sys_untimeout(m,a)

    #define sys_sem_new(c) c

    #define sys_sem_signal(s)

    #define sys_sem_wait(s)

    #define sys_sem_free(s)

    #define sys_mbox_new() 0

    #define sys_mbox_fetch(m,d)

    #define sys_mbox_post(m,d)

    #define sys_mbox_free(m)

     

    #define sys_thread_new(t,a,p)   

    这里sys_timeoutsys_untimeout是定时器函数,sys_sem_waitsys_mbox_fetch实现有点技巧的,因为lwip的增加定时函数,这就涉及到等待时间到后的处理,等待时间为0的时候持续等待,如果不为0,时间到后就就绪状态;

    在创建lwip task的函数中维护每个一个指针数组,根据任务数目来的;这样每一个任务就维护一个定时事件链表,因为可能一个task中有几个定时事件,定时事件链表中就维护几个

     

    struct timeoutlist {

      struct sys_timeouts timeouts;

      INT8U prio;

    };

     

    static struct timeoutlist timeoutlist[LWIP_MAX_TASKS];

    sys_thread_t

    sys_thread_new(void (* function)(void *arg), void *arg, int prio)

    {

    ......

        timeoutlist[sys_thread_no].timeouts.next = NULL;

        timeoutlist[sys_thread_no].prio = prio;

     

        ++sys_thread_no; /* next task created will be one lower to this one */

    ......

    }

     

    sys_arch_timeouts返回值是对应任务的定时事件链表开始指针,默认创建的时候timeouts->next为空,所以第一个定时事件的timeout挂接后,增加一个定时事件就返回的

    当第二个定时事件时执行时就要对定时事件排序了,当第二个定时时间短的话,第二个定时事件直接插入链首,并且next的时间取减去第一个和第二个定时值的时间差;第三个定时事件的时候,按照时间排序插到链表合适位置;这段代码处理算是蛮有技巧的,当然sys_untimeout是删除定时事件,过程刚好和创建相反,从链表中删除定时事件

     

      timeouts = sys_arch_timeouts();

     

      if (timeouts->next == NULL) {

        timeouts->next = timeout;

        return;

      }

     

      if (timeouts->next->time > msecs) {

        timeouts->next->time -= msecs;

        timeout->next = timeouts->next;

        timeouts->next = timeout;

      } else {

        for(t = timeouts->next; t != NULL; t = t->next) {

          timeout->time -= t->time;

          if (t->next == NULL || t->next->time > timeout->time) {

            if (t->next != NULL) {

              t->next->time -= timeout->time;

            }

            timeout->next = t->next;

            t->next = timeout;

            break;

          }

        }

      }

     

    sys_mbox_fetch函数的实现也是非常的有技巧,将定时函数和消息队列一起处理,非常有意思的一代代码,参考下面注释

    void

    sys_mbox_fetch(sys_mbox_t mbox, void **msg)

    {

      u32_t time;

      struct sys_timeouts *timeouts;

      struct sys_timeout *tmptimeout;

      sys_timeout_handler h;

      void *arg;

     

     

     again:

      timeouts = sys_arch_timeouts();

     

      if (!timeouts || !timeouts->next) {

        sys_arch_mbox_fetch(mbox, msg, 0);

        /* 没有定时事件最好了,直接无条件的持续等待消息 */

      } else {

        if (timeouts->next->time > 0) {

          time = sys_arch_mbox_fetch(mbox, msg, timeouts->next->time);

            /* 一个定时事件大于0*/

        } else {

          time = SYS_ARCH_TIMEOUT;

            /* 定时事件到,实际就是定时器函数可以跑了 */

        }

     

        if (time == SYS_ARCH_TIMEOUT) {

          /* If time == SYS_ARCH_TIMEOUT, a timeout occured before a message

       could be fetched. We should now call the timeout handler and

       deallocate the memory allocated for the timeout. */

            /**/

          tmptimeout = timeouts->next;

          timeouts->next = tmptimeout->next;

          h = tmptimeout->h;

          arg = tmptimeout->arg;

          memp_free(MEMP_SYS_TIMEOUT, tmptimeout);

          if (h != NULL) {

            LWIP_DEBUGF(SYS_DEBUG, ("smf calling h=%p(%p)/n", (void *)h, (void *)arg));

              h(arg);

            /* 执行定时事件的函数 */

          }

     

          /* We try again to fetch a message from the mbox. */

            /* 运行到这里:或者是定时函数的定时事件完成;或者是sys_arch_mbox_fetch超时没有收到消息,返回开始继续进入等待状态,和OSQPend超时没收到消息一样的处理 */

          goto again;

        } else {

          /* If time != SYS_ARCH_TIMEOUT, a message was received before the timeout

       occured. The time variable is set to the number of

       milliseconds we waited for the message. */

            /* 运行到这里是sys_arch_mbox_fetch在规定时间收到消息了,刷新时间参数 */

          if (time <= timeouts->next->time) {

      timeouts->next->time -= time;

          } else {

      timeouts->next->time = 0;

          }

        }

     

      }

    }

     

     

    arch/ucos-ii/include/lwipopts.h主要是lwip的功能开关,函数的使能,类似ucosos_cfg.h,默认的即可

     

    2.        底层工作

     

    底层网卡要工作起来,以及网卡数据接收丢给上层,上层丢数据给网卡,这个接口需要做,主要就是ucos-ii/netif的两个文件来实现的,还好lwip都提供了框架,自己只需要按照接口把函数填进去,下面是lwip提供的接口

     

    err_t

    ethernetif_init(struct netif *netif)

     

    static void

    ethernetif_input(struct netif *netif)

     

    static err_t

    ethernetif_output(struct netif *netif, struct pbuf *p,

          struct ip_addr *ipaddr)

    下面是网卡API,需要封装到lwipAPI上,ethernetif_initlow_level_init用做网卡的初始化;中断到来的时候ne2k_isrethernetif_inputlow_level_input)接收包最终丢给三层处理;ethernetif_outputlow_level_output发送包

     

    static void

    low_level_init(struct netif *netif)

     

    static struct pbuf *

    low_level_input(struct netif *netif)

     

    static err_t

    low_level_output(struct netif *netif, struct pbuf *p)

     

    /* void NICISR(void) interrupt */

    void ne2k_isr(void)

    下面是ethernetif_input收到ARPetharp_arp_input或者IPetharp_ip_input后的处理,之后就到lwip协议栈的工作了,API的工作到此为止

      switch (htons(ethhdr->type)) {

        case ETHTYPE_IP:

          etharp_ip_input(netif, p);

          pbuf_header(p, -14);

          netif->input(p, netif);

          break;

         

        case ETHTYPE_ARP:

          etharp_arp_input(netif, ethernetif->ethaddr, p);

          break;

        default:

          pbuf_free(p);

          p = NULL;

          break;

      }

    这里驱动采用的SEND COMMAND方式(个人觉得比Remote DMA READ方式要简单)

    初始化bnry读指针 = curr写指针 = PSTART

    有数据包要读时,向DMA长度寄存器1写入0x0f(奇怪???不明白)

    执行SEND COMMAND命令

    然后网卡自动做了如下工作:

    用当前的bnry读指针指向的地址写入DMA起址寄存器0,1

    用以太网包头中的包长度初始化DMA长度寄存器0,1

    先读18个字节的包头看看要不要这个包(4B的网卡物理状态信息,14B的以太网头)

    0x10端口读数据

    可以看到这里处理和u-boot的轮询处理不太一样,是因为这里直接读以太网头出来了,就判断arp还是ip开始丢给协议栈处理了,u-boot那边是拿到整个包,在上层应用中再做处理的

     

    3.        lwip应用

    lwip协议栈初始化及任务调用,按照lwip的例子代码,起一个task单独给lwip协议栈(当然实际上lwip协议栈内部另外再起了一些任务的),这个任务主要做一些协议栈,以及TCPIP的初始化之后,就添加硬件设备了,如下代码,这样的话,这个网卡就可以工作了;ping 192.168.18.119 -t,一切通信正常

     

        //add loop interface

        loop_netif = mem_malloc(sizeof(struct netif));

        IP4_ADDR(&gw, 127,0,0,1);

        IP4_ADDR(&ipaddr, 127,0,0,1);

        IP4_ADDR(&netmask, 255,0,0,0);

        netif_add(loop_netif, &ipaddr, &netmask, &gw, NULL, loopif_init, tcpip_input);

           

        //add rtl8019 interface

          rtl8019_netif = mem_malloc(sizeof(struct netif));

        IP4_ADDR(&gw, 192,168,10,1);

        IP4_ADDR(&ipaddr, 192,168,10,119);

        IP4_ADDR(&netmask, 255,255,255,0);

        netif_set_default(netif_add(rtl8019_netif, &ipaddr, &netmask, &gw, NULL, ethernetif_init, tcpip_input));

    接着可以写一个小的socket代码测试协议栈的健壮性

     

    4.        lwip协议

     

    这张图就是lwip协议栈的整个结构,网络层和传输层就是lwip做的工作,主要做包判断,填数据头,剥数据头,本身工作就是网络接口层的封装以及和底层驱动,当然还有就是黄色部分的mboxos封装

     

    5.        小结

    编译就搞了好个晚上,这个SDK是基于lwip-1.3.0来实现的,我用的还是老的1.1.0,一些lwipapi参数不匹配了,不过还好,都是一些小问题

    中断不建议边沿触发,建议电平触发,奇怪边沿触发经常无法进中断

    lwip的定时函数代码加得有参考价值,移植到ucos是个非常不错的功能(ucos2 2.83增加了定时管理os_tmr.c 1)

     

     


    最新回复(0)