《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)

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); /* 任务的优先级 */
}

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中
  }
  //调用以太网发送函数将数据发送
}

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
}

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;
}

发送过程

由 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;  
}

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

接收过程

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

void ETH_IRQHandler(void)
{
  xSemaphoreGiveFromISR( s_xSemaphore, &xHigherPriorityTaskWoken );
}

之后, 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;
        }
      }
    }
  }
}

也就是说,我们网卡将数据递交到上层应用是通过 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;
  }
}

而在 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);
}

到这里,我们就可以看出来,将网卡数据投递到上层应用是用 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);
}

而在 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;
}

在这儿我们可以看到,通过发布 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);
  }
}

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

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大文梅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值