• 《lwip学习1》-数据流篇


    函数介绍

    在ethernetif.c 文件中,已经有5个函数的框架,包括函数名、函数参数、函数内容等。我们要做的就是实现 ethernetif.c 中相关函数的编写。

    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)
    void ethernetif_input(void *pParams)
    err_t ethernetif_init(struct netif *netif)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    low_level_init() 为网卡的初始化函数,它主要用来完成网卡复位及参数初始化,比如网卡MAC地址长度等;
    low_level_output() 为网卡数据包的发送函数,这个函数的工作是将内核数据结构pbuf描述的数据包发送出去;
    low_level_input() 为网卡数据包接收函数,为了能让协议栈内核能过准确的处理数据,该函数必须将收到的数据封装为pbuf的形式;
    ethernetif_input() 的主要作用是调用网卡数据包接收函数 low_level_input 从网卡处读取一个数据包,然后解析该数据包的类型(ARP包或者IP包),最后将该数据包递交给相应的上层。
    ethernetif_init() 是上层在管理网络接口结构netif时会调用的函数。该函数主要完成netif结构中某些字段的初始化,并最终调用 low_level_init 完成网卡的初始化。
    low_level_init()伪代码:

    static void low_level_init(struct netif *netif)
    {
      status = Bsp_Eth_Init();  //以太网初始化
      if(status == OK)
      {
        /* Set netif link flag */  
        netif->flags |= NETIF_FLAG_LINK_UP;
      }
      Set_MACAddr();  //设置MAC地址
      //创建 ethernetif_input 任务
      sys_thread_new("ETHIN",
                      ethernetif_input,  /* 任务入口函数 */
                      netif,        	  /* 任务入口函数参数 */
                      NETIF_IN_TASK_STACK_SIZE,/* 任务栈大小 */
                      NETIF_IN_TASK_PRIORITY); /* 任务的优先级 */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    low_level_output() 实现比较复杂,要求移植者对网卡操作及协议栈数据包结构pbuf有详细的了解。
    在每个pbuf中,payload字段指向当前pbuf的数据区,len字段表示数据区中数据的长度,tot_len字段表示当前pbuf及后续所有pbuf区的数据总长度。
    当使用网卡发送数据时,需要发送链表p上所有pbuf中的数据:
    伪代码:

    static err_t low_level_output(struct netif *netif, struct pbuf *p)
    {
      struct pbuf *q;
      static uint8_t buffer[LWIP_FRAMER_MAXSIZE];
      for(q = p; q != NULL; q = q->next)
      {
        //循环将p中的数据拷贝到buffer中
      }
      //调用以太网发送函数将数据发送
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    low_level_input() 从网卡中读取数据,并将数据组装在pbuf中供内核取用
    伪代码:

    static struct pbuf * low_level_input(struct netif *netif)
    {
      struct pbuf *p = NULL;
      static uint8_t buffer[LWIP_FRAMER_MAXSIZE];
      len = ETH_GetReceivedFrame(buffer);  //获取一个以太网数据包
      p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);  //为p分配空间
      memcpy(p->payload, buffer, len);  //将以太网卡中的数据拷贝到p的数据域
      return p;  //返回p
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ethernetif_init() 函数为网卡的初始化函数,主要初始化 *netif 网卡。

    err_t ethernetif_init(struct netif *netif)
    {
      struct ethernetif *ethernetif;
      ethernetif = mem_malloc(sizeof(struct ethernetif));
      netif->state = ethernetif;
      netif->name[0] = IFNAME0;
      netif->name[1] = IFNAME1;  //网卡名字
      netif->output = etharp_output;  //暂不清楚,往后学习
      netif->linkoutput = low_level_output;  //注册底层发送函数
      low_level_init(netif);  //调用网卡底层初始化函数
      ethernetif->ethaddr = (struct eth_addr *) &(netif->hwaddr[0]); //MAC地址
      return ERR_OK;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    发送过程

    由 netif->linkoutput = low_level_output 可知,如果要发送网卡数据,在上层应用肯定将调用 netif->linkoutput() 函数,通过阅读源码可知,在 ethernet_output() 函数中调用 netif->linkoutput()。
    ethernet_output()函数原型如下:

    err_t
    ethernet_output(struct netif * netif, struct pbuf * p,
                    const struct eth_addr * src, const struct eth_addr * dst,
                    u16_t eth_type)
    {
      struct eth_hdr *ethhdr;
      u16_t eth_type_be = lwip_htons(eth_type);
      {
        if (pbuf_add_header(p, SIZEOF_ETH_HDR) != 0) {
          goto pbuf_header_failed;
        }
      }
      ethhdr = (struct eth_hdr *)p->payload;
      ethhdr->type = eth_type_be;
      SMEMCPY(&ethhdr->dest, dst, ETH_HWADDR_LEN);
      SMEMCPY(&ethhdr->src,  src, ETH_HWADDR_LEN);
      return netif->linkoutput(netif, p);  //将调用底层发送函数将数据发送出
    pbuf_header_failed:
      return ERR_BUF;  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    到这里,数据发送过程我们大致也清楚了,无非就是当有数据需要发送时,上层应用通过一系列的调用,最后调用:ethernet_output -> netif->linkoutput(netif, p) -> low_level_output() -> 调用最底层网卡数据发送。

    接收过程

    以野火f429为例,其接收方式为通过中断接收,当网卡来了一个数据帧之后,将触发以太网中断:在中断中就干了一件事情,发布 s_xSemaphore 信号量 通知 ethernetif_input() 线程去接收数据帧。
    简化伪代码:

    void ETH_IRQHandler(void)
    {
      xSemaphoreGiveFromISR( s_xSemaphore, &xHigherPriorityTaskWoken );
    }
    
    • 1
    • 2
    • 3
    • 4

    之后, ethernetif_input() 线程获取到信号量,开始执行。

    void ethernetif_input(void *pParams)
    {
      while(1)
      {
        if(xSemaphoreTake( s_xSemaphore, portMAX_DELAY ) == pdTRUE)
        {
    TRY_GET_NEXT_FRAGMENT:
          p = low_level_input(netif);  //调用底层数据帧获取函数获取网卡数据
          if(p != NULL)
          {
            if (netif->input(p, netif) != ERR_OK)  //递交网卡数据到上层
            {
              pbuf_free(p);
              p = NULL;
            }
            else
            {
              xSemaphoreTake( s_xSemaphore, 0);  //防止网卡中还有剩余数据
              goto TRY_GET_NEXT_FRAGMENT;
            }
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    也就是说,我们网卡将数据递交到上层应用是通过 netif->input(p, netif) 实现的,那么 netif->input(p, netif)函数原型又是什么?
    通过继续深扒发现:
    在 netif_add() 函数中,注册了 netif->input
    netif_add() 函数原型

    netif_add(struct netif *netif,
    #if LWIP_IPV4
              const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,
    #endif /* LWIP_IPV4 */
              void *state, netif_init_fn init, netif_input_fn input)
    {
      netif->state = state;
      netif->num = netif_num;
      netif->input = input;  //这里注册 input
      netif_set_addr(netif, ipaddr, netmask, gw);  //设置网络IP
      if (init(netif) != ERR_OK) {  //调用初始化函数
        return NULL;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    而在 void TCPIP_Init(void) 函数中,调用 netif_add();

    void TCPIP_Init(void)
    {
      tcpip_init(NULL, NULL);
      IP4_ADDR(&ipaddr,IP_ADDR0,IP_ADDR1,IP_ADDR2,IP_ADDR3);
      IP4_ADDR(&netmask,NETMASK_ADDR0,NETMASK_ADDR1,NETMASK_ADDR2,NETMASK_ADDR3);
      IP4_ADDR(&gw,GW_ADDR0,GW_ADDR1,GW_ADDR2,GW_ADDR3);
      netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    到这里,我们就可以看出来,将网卡数据投递到上层应用是用 tcpip_input() 实现的。

    err_t
    tcpip_input(struct pbuf *p, struct netif *inp)
    {
    #if LWIP_ETHERNET
      if (inp->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
        return tcpip_inpkt(p, inp, ethernet_input);
      } else
    #endif /* LWIP_ETHERNET */
        return tcpip_inpkt(p, inp, ip_input);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    而在 tcpip_input() 中,则是调用 tcpip_inpkt() 将数据递交到上层中。

    err_t
    tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn)
    {
      msg->type = TCPIP_MSG_INPKT;
      msg->msg.inp.p = p;
      msg->msg.inp.netif = inp;
      msg->msg.inp.input_fn = input_fn;
      if (sys_mbox_trypost(&tcpip_mbox, msg) != ERR_OK) {
        memp_free(MEMP_TCPIP_MSG_INPKT, msg);
        return ERR_MEM;
      }
      return ERR_OK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这儿我们可以看到,通过发布 tcpip_mbox 消息队列达到线程之间的同步。而哪个线程等到 tcpip_mbox 消息队列呢?那就是内核线程。

    static void
    tcpip_thread(void *arg)
    {
      struct tcpip_msg *msg;
      LWIP_UNUSED_ARG(arg);
    
      LWIP_MARK_TCPIP_THREAD();
    
      LOCK_TCPIP_CORE();
      if (tcpip_init_done != NULL) {
        tcpip_init_done(tcpip_init_done_arg);
      }
    
      while (1) {                          /* MAIN Loop */
        LWIP_TCPIP_THREAD_ALIVE();
        /* wait for a message, timeouts are processed while waiting */
        TCPIP_MBOX_FETCH(&tcpip_mbox, (void **)&msg);  //等待消息队列
        if (msg == NULL) {
          LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: NULL\n"));
          LWIP_ASSERT("tcpip_thread: invalid message", 0);
          continue;
        }
        tcpip_thread_handle_msg(msg);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    整个数据的接收过程就是 :
    ETH_IRQHandler() >> ethernetif_input() >> netif->input(p, netif) >> tcpip_input() >> tcpip_inpkt() >> tcpip_thread()
    这样,就实现了我们从网卡接收到的数据,最终流入内核线程中。
    而在内核中,又调用 ip_input() 函数,这样,数据就进入了 IP层(网络层)中。

  • 相关阅读:
    信息系统项目管理——资源管理
    【LeetCode】双指针求解和为s的两个数字
    CLIP微调方式
    10.8流水灯
    Vue项目的部署(服务器)
    什么?“裸辞”一个月拿到13家offer,网友:你是在找存在感吗···
    数据结构之图(概念)
    Java | Leetcode Java题解之第191题位1的个数
    【Flutter】Android Studio的Flutter环境配置
    微软10月补丁 | 修复103个漏洞,包括2个零日漏洞,13个严重漏洞
  • 原文地址:https://blog.csdn.net/qq_40831436/article/details/126778233