• 《lwip学习5》-- lwip一探究竟


    网卡接收数据的流程

    以太网接收完数据后产生一个中断,然后释放一个信号量通知网卡接收线程去处理这些接收的数据,然后将数据这些数据封装成消息,投递到 tcpip_mbox 邮箱中, LwIP 内核线程得到这个消息,就对消息进行解析,根据消息中数据包类型进行处理,实际上是调用 ethernet_input()函数决定是否递交到 IP 层,如果是 ARP 包,内核就不会递交给 IP 层,而是更新 ARP 缓存表,对于IP 数据包则递交给 IP 层去处理,这就是一个数据从网卡到内核的过程。
    在这里插入图片描述

    内核超时处理

    在 LwIP 中很多时候都要用到超时处理,例如 ARP 缓存表项的时间管理、 IP 分片数据报的重装等待超时、 TCP 中的建立连接超时、重传超时机制等, 因此超时处理的实现是TCP/IP 协议栈中一个重要部分, LwIP 为每个与外界网络连接的任务都有设定了 timeout 属性,即等待超时时间, 超时处理的相关代码实现在 timeouts.c 与 timeouts.h 中。

    sys_timeo 结构体与超时链表

    LwIP 通过一个 sys_timeo 类型的数据结构管理与超时链表相关的所有超时事件。 LwIP使用这个结构体记录下内核中所有被注册的超时事件, 这些结构体会以链表的形式一个个连接在超时链表中, 而内核中只有一条超时链表,那么怎么对超时链表进行管理呢? LwIP定义了一个 sys_timeo 类型的指针 next_timeout,并且将 next_timeout 指向当前内核中链表头部,所有被注册的超时事件都会按照被处理的先后顺序排列在超时链表上。

    typedef void (* sys_timeout_handler)(void *arg);
    
    struct sys_timeo {
      struct sys_timeo *next; //指向下一个超时事件的指针,用于超时链表的连接
      u32_t time; //当前超时事件的等待时间。
      sys_timeout_handler h; //指向超时的回调函数,该事件超时后就执行对应的回调函数
      void *arg; //向回调函数传入参数。
    #if LWIP_DEBUG_TIMERNAMES
      const char* handler_name;
    #endif /* LWIP_DEBUG_TIMERNAMES */
    };
    
    
    /** The one and only timeout list */
    static struct sys_timeo *next_timeout; //有且只有一条超时链表
    

    注册超时事件

    LwIP 虽然使用超时链表进行管理所有的超时事件,那么它首先需要知道有哪些超时事件才能去管理,而这些超时事件就是通过注册的方式被挂载在链表上,简单来说就是这些超时事件要在内核中登记一下,内核才会去处理, LwIP 中注册超时事件的函数是sys_timeout(),但是实际上是调用 sys_timeout_abs()函数。

    void
    sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg)
    #endif /* LWIP_DEBUG_TIMERNAMES */
    {
      u32_t next_timeout_time;
      next_timeout_time = (u32_t)(sys_now() + msecs); /* overflow handled by TIME_LESS_THAN macro */ 
      sys_timeout_abs(next_timeout_time, handler, arg);
    }
    
    static void
    sys_timeout_abs(u32_t abs_time, sys_timeout_handler handler, void *arg)
    #endif
    {
      struct sys_timeo *timeout, *t;
    
      timeout = (struct sys_timeo *)memp_malloc(MEMP_SYS_TIMEOUT);
      if (timeout == NULL) {
        LWIP_ASSERT("sys_timeout: timeout != NULL, pool MEMP_SYS_TIMEOUT is empty", timeout != NULL);
        return;
      }
    //填写对应的超时事件信息,超时回调函数、函数参数、 超时的 时间。
      timeout->next = NULL;
      timeout->h = handler;
      timeout->arg = arg;
      timeout->time = abs_time;
    
    #if LWIP_DEBUG_TIMERNAMES
      timeout->handler_name = handler_name;
      LWIP_DEBUGF(TIMERS_DEBUG, ("sys_timeout: %p abs_time=%"U32_F" handler=%s arg=%p\n",
                                 (void *)timeout, abs_time, handler_name, (void *)arg));
    #endif /* LWIP_DEBUG_TIMERNAMES */
    
      if (next_timeout == NULL) {
      //如果超时链表中没有超时事件,那么新添加的事件就是链表的第一个    
        next_timeout = timeout;
        return;
      }
      if (TIME_LESS_THAN(timeout->time, next_timeout->time)) {
        timeout->next = next_timeout;//如果新插入的超时事件比链表上第一个事件的时间短,则将新插入的超时事件设置成链表的第一个
        next_timeout = timeout;
      } else {
        for (t = next_timeout; t != NULL; t = t->next) {
        //遍历链表,寻找合适的插入节点,超时链表根据超时事件的时间升序排列。
          if ((t->next == NULL) || TIME_LESS_THAN(timeout->time, t->next->time)) {
            timeout->next = t->next;
            t->next = timeout;
            break;
          }
        }
      }
    }
    

    在 timeouts.c 中, 有一个名字为 lwip_cyclic_timer 的结构, LwIP 使用该结构存放了其内部使用的循环超时事件。这些超时事件在 LwIP 初始化时通过函数 sys_timeouts_init()调用定时器注册函数 sys_timeout()注册进入超时链表中, lwip_cyclic_timer 的结构具体见代码

    const struct lwip_cyclic_timer lwip_cyclic_timers[] = {
    #if LWIP_TCP
      /* The TCP timer is a special case: it does not have to run always and
         is triggered to start from TCP using tcp_timer_needed() */
      {TCP_TMR_INTERVAL, HANDLER(tcp_tmr)},
    #endif /* LWIP_TCP */
    #if LWIP_IPV4
    #if IP_REASSEMBLY
      {IP_TMR_INTERVAL, HANDLER(ip_reass_tmr)},
    #endif /* IP_REASSEMBLY */
    #if LWIP_ARP
      {ARP_TMR_INTERVAL, HANDLER(etharp_tmr)},
    #endif /* LWIP_ARP */
    #if LWIP_DHCP
      {DHCP_COARSE_TIMER_MSECS, HANDLER(dhcp_coarse_tmr)},
      {DHCP_FINE_TIMER_MSECS, HANDLER(dhcp_fine_tmr)},
    #endif /* LWIP_DHCP */
    #if LWIP_AUTOIP
      {AUTOIP_TMR_INTERVAL, HANDLER(autoip_tmr)},
    #endif /* LWIP_AUTOIP */
    #if LWIP_IGMP
      {IGMP_TMR_INTERVAL, HANDLER(igmp_tmr)},
    #endif /* LWIP_IGMP */
    #endif /* LWIP_IPV4 */
    #if LWIP_DNS
      {DNS_TMR_INTERVAL, HANDLER(dns_tmr)},
    #endif /* LWIP_DNS */
    #if LWIP_IPV6
      {ND6_TMR_INTERVAL, HANDLER(nd6_tmr)},
    #if LWIP_IPV6_REASS
      {IP6_REASS_TMR_INTERVAL, HANDLER(ip6_reass_tmr)},
    #endif /* LWIP_IPV6_REASS */
    #if LWIP_IPV6_MLD
      {MLD6_TMR_INTERVAL, HANDLER(mld6_tmr)},
    #endif /* LWIP_IPV6_MLD */
    #if LWIP_IPV6_DHCP6
      {DHCP6_TIMER_MSECS, HANDLER(dhcp6_tmr)},
    #endif /* LWIP_IPV6_DHCP6 */
    #endif /* LWIP_IPV6 */
    };
    

    lwip_cyclic_timers 数组中存放了每个周期性的超时事件回调函数及超时时间, 在 LwIP
    初始化的时候就将这些事件一个个插入超时链表中

    void sys_timeouts_init(void)
    {
      size_t i;
      /* tcp_tmr() at index 0 is started on demand */
      for (i = (LWIP_TCP ? 1 : 0); i < LWIP_ARRAYSIZE(lwip_cyclic_timers); i++) {
        /* we have to cast via size_t to get rid of const warning
          (this is OK as cyclic_timer() casts back to const* */
        sys_timeout(lwip_cyclic_timers[i].interval_ms, lwip_cyclic_timer, LWIP_CONST_CAST(void *, &lwip_cyclic_timers[i]));
      }
    }
    

    在这里插入图片描述
    每个 sys_timeo 结构体中的 h 成员变量记录着对应的超时回调函数, 对于周期性的回调函数, LwIP 是这样子处理的: 在初始化的时候将他们注册到 lwip_cyclic_timer()函数中,每次在处理回调函数之后, 就调用 sys_timeout_abs()函数将其重新注册到超时链表中,具体见代码清单

    #if !LWIP_TESTMODE
    static
    #endif
    void
    lwip_cyclic_timer(void *arg)
    {
      u32_t now;
      u32_t next_timeout_time;
      const struct lwip_cyclic_timer *cyclic = (const struct lwip_cyclic_timer *)arg;
    
    #if LWIP_DEBUG_TIMERNAMES
      LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: %s()\n", cyclic->handler_name));
    #endif
      cyclic->handler(); //执行各项超时处理函数  lwip_cyclic_timers[] 
    
      now = sys_now();
      next_timeout_time = (u32_t)(current_timeout_due_time + cyclic->interval_ms);  /* overflow handled by TIME_LESS_THAN macro */ 
      if (TIME_LESS_THAN(next_timeout_time, now)) {
        /* timer would immediately expire again -> "overload" -> restart without any correction */
    #if LWIP_DEBUG_TIMERNAMES
        sys_timeout_abs((u32_t)(now + cyclic->interval_ms), lwip_cyclic_timer, arg, cyclic->handler_name); 
    #else
        sys_timeout_abs((u32_t)(now + cyclic->interval_ms), lwip_cyclic_timer, arg); 再次重新注册
    #endif
    
      } else {
        /* correct cyclic interval with handler execution delay and sys_check_timeouts jitter */
    #if LWIP_DEBUG_TIMERNAMES
        sys_timeout_abs(next_timeout_time, lwip_cyclic_timer, arg, cyclic->handler_name);
    #else
        sys_timeout_abs(next_timeout_time, lwip_cyclic_timer, arg);
    #endif
      }
    }
    

    超时检查

    void sys_check_timeouts(void): 这是用于裸机的函数,用户需要在裸机应用程序中周期性调用该函数,每次调用的时候 LwIP 都会检查超时链表上第一个 sys_timeo 结构体是否到期,如果没有到期,直接退出该函数,否则,执行 sys_timeo 结构体中对应的超时回调函数,并从链表上删除它,然后继续检查下一个 sys_timeo 结构体,直到 sys_timeo 结构体没有超时才退出。
    tcpip_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg): 这个函数在操作系统的线程中循环调用,主要是等待 tcpip_mbox 消息,是可阻塞的,如果在等待 tcpip_mbox 的过程中发生超时事件,则会同时执行超时事件处理,即调用超时回调函数。 LwIP 是这样子处理的,如果已经发生超时, LwIP 就会内部调用 sys_check_timeouts()函数去检查超时的sys_timeo 结构体并调用其对应的回调函数, 如果没有发生超时,那就一直等待消息,其等待的时间为下一个超时时间的时间, 一举两得。

    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);
      }
    }
    
    #define TCPIP_MBOX_FETCH(mbox, msg) tcpip_timeouts_mbox_fetch(mbox, msg)
    
    static void
    tcpip_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg)
    {
      u32_t sleeptime, res;
    
    again:
      LWIP_ASSERT_CORE_LOCKED();
    
      sleeptime = sys_timeouts_sleeptime(); //得到距离事件超时的时间并保存在 sleeptime 变量中
      if (sleeptime == SYS_TIMEOUTS_SLEEPTIME_INFINITE) {
        UNLOCK_TCPIP_CORE();
        sys_arch_mbox_fetch(mbox, msg, 0); //无超时事件发生,一直等待mbox消息,参数0表示一直等。其实就是等待是否有输入、输出数据
        LOCK_TCPIP_CORE();
        return;
      } else if (sleeptime == 0) {
        sys_check_timeouts(); //有事件发生超时,检查发生超时的事件
        /* We try again to fetch a message from the mbox. */
        goto again; //执行完本次事件之后,检查链表中的下一个超时事件是否超时
      }
     //若到了这一步,说明需要等待sleeptime的事件,则将发生超时事件
      UNLOCK_TCPIP_CORE();
      res = sys_arch_mbox_fetch(mbox, msg, sleeptime);
      LOCK_TCPIP_CORE();
      if (res == SYS_ARCH_TIMEOUT) {
        /* If a SYS_ARCH_TIMEOUT value is returned, a timeout occurred
           before a message could be fetched. */
        sys_check_timeouts(); //检查发生超时的事件
        /* We try again to fetch a message from the mbox. */
        goto again;
      }
    }
    
    //返回值:成功等待到消息,则返回等待的事件;否则返回超时标志
    u32_t
    sys_arch_mbox_fetch(sys_mbox_t *q, void **msg, u32_t timeout)
    {
      void *dummyptr;
      u32_t wait_tick = 0;
      u32_t start_tick = 0 ;
      
      if ( msg == NULL )  //看看存储消息的地方是否有效
    		msg = &dummyptr;
      
      //首先获取开始等待信号量的时钟节拍
      start_tick = sys_now();
      
      //timeout != 0,需要将ms换成系统的时钟节拍
      if(timeout != 0)
      {
        //将ms转换成时钟节拍
        wait_tick = timeout / portTICK_PERIOD_MS;
        if (wait_tick == 0)
          wait_tick = 1;
      }
      //一直阻塞
      else
        wait_tick = portMAX_DELAY;
      
      //等待成功,计算等待的时间,否则就表示等待超时
      if(xQueueReceive(*q,&(*msg), wait_tick) == pdTRUE)
        return ((sys_now() - start_tick)*portTICK_PERIOD_MS);
      else
      {
        *msg = NULL;
        return SYS_ARCH_TIMEOUT;
      }
    }
    
    u32_t
    sys_timeouts_sleeptime(void)
    {
      u32_t now;
    
      LWIP_ASSERT_CORE_LOCKED();
    
      if (next_timeout == NULL) {
        return SYS_TIMEOUTS_SLEEPTIME_INFINITE; //无超时事件,返回
      }
      now = sys_now();
      if (TIME_LESS_THAN(next_timeout->time, now)) {
        return 0; //本此超时事件已经发生超时,返回0
      } else {
        u32_t ret = (u32_t)(next_timeout->time - now);
        LWIP_ASSERT("invalid sleeptime", ret <= LWIP_MAX_TIMEOUT); //计算下次事件超时的时间
        return ret;
      }
    }
    

    tcpip_thread 线程

    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); //等待到消息就对消息进行处理
      }
    }
    
    static void
    tcpip_thread_handle_msg(struct tcpip_msg *msg)
    {
      switch (msg->type) {
    #if !LWIP_TCPIP_CORE_LOCKING
        case TCPIP_MSG_API:
          LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg));
          msg->msg.api_msg.function(msg->msg.api_msg.msg);
          break;
        case TCPIP_MSG_API_CALL:
          LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API CALL message %p\n", (void *)msg));
          msg->msg.api_call.arg->err = msg->msg.api_call.function(msg->msg.api_call.arg);
          sys_sem_signal(msg->msg.api_call.sem);
          break;
    #endif /* !LWIP_TCPIP_CORE_LOCKING */
    
    #if !LWIP_TCPIP_CORE_LOCKING_INPUT
        case TCPIP_MSG_INPKT: //对于 TCPIP_MSG_INPKT 类型, 直接交给 ARP 层处理
          LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));
          if (msg->msg.inp.input_fn(msg->msg.inp.p, msg->msg.inp.netif) != ERR_OK) { //input_fn实际上是ethernet_input
            pbuf_free(msg->msg.inp.p);
          }
          memp_free(MEMP_TCPIP_MSG_INPKT, msg);
          break;
    #endif /* !LWIP_TCPIP_CORE_LOCKING_INPUT */
    
    #if LWIP_TCPIP_TIMEOUT && LWIP_TIMERS
        case TCPIP_MSG_TIMEOUT: //对于 TCPIP_MSG_TIMEOUT 类型, 表示上层注册一个超时事件,直接执行注册超时事件即可
          LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: TIMEOUT %p\n", (void *)msg));
          sys_timeout(msg->msg.tmo.msecs, msg->msg.tmo.h, msg->msg.tmo.arg);
          memp_free(MEMP_TCPIP_MSG_API, msg);
          break;
        case TCPIP_MSG_UNTIMEOUT: //对于 TCPIP_MSG_ UNTIMEOUT 类型, 表示上层删除一个超时事件, 直接执行删除超时事件即可。
          LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: UNTIMEOUT %p\n", (void *)msg));
          sys_untimeout(msg->msg.tmo.h, msg->msg.tmo.arg);
          memp_free(MEMP_TCPIP_MSG_API, msg);
          break;
    #endif /* LWIP_TCPIP_TIMEOUT && LWIP_TIMERS */
    
        case TCPIP_MSG_CALLBACK: //上层通过回调方式执行一个回调函数, 那么就执行对应的回调函数即可
          LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK %p\n", (void *)msg));
          msg->msg.cb.function(msg->msg.cb.ctx);
          memp_free(MEMP_TCPIP_MSG_API, msg);
          break;
    
        case TCPIP_MSG_CALLBACK_STATIC: //上层通过回调方式执行一个回调函数, 那么就执行对应的回调函数即可
          LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK_STATIC %p\n", (void *)msg));
          msg->msg.cb.function(msg->msg.cb.ctx);
          break;
    
        default:
          LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: %d\n", msg->type));
          LWIP_ASSERT("tcpip_thread: invalid message", 0);
          break;
      }
    }
    

    消息结构

    LwIP 中消息是有多种结构的的, 对于不同的消息类型其封装是不一样的, tcpip_thread 线程是通过 tcpip_msg 描述消息的,tcpip_thread 线程接收到消息后,根据消息的类型进行不同的处理。 LwIP 中使用tcpip_msg_type 枚举类型定义了系统中可能出现的消息的类型,消息结构 msg 字段是一个共用体,其中定义了各种消息类型的具体内容,每种类型的消息对应了共用体中的一个字段,其中注册与删除事件的消息使用了同一个 tmo 字段。 LwIP 中的 API 相关的消息内容很多,不适合直接放在 tcpip_msg 中,所以 LwIP 用一个 api_msg 结构体来描述 API 消息,在tcpip_msg 中只存放指向 api_msg 结构体的指针。

    enum tcpip_msg_type {
      TCPIP_MSG_API,
      TCPIP_MSG_API_CALL,      //API函数调用
      TCPIP_MSG_INPKT,         //底层数据包输入
      TCPIP_MSG_TIMEOUT,       //注册超时事件
      TCPIP_MSG_UNTIMEOUT,     //删除超时事件
      TCPIP_MSG_CALLBACK,      
      TCPIP_MSG_CALLBACK_STATIC //执行回调函数
    };
    
    struct tcpip_msg {
      enum tcpip_msg_type type; //消息的类型
      union {
        struct { //api消息,一部分表示内核执行的API函数,另一部分是执行函数的时候的参数
          tcpip_callback_fn function;
          void* msg;
        } api_msg;
        struct {
          tcpip_api_call_fn function;
          struct tcpip_api_call_data *arg;
          sys_sem_t *sem; //用于同步的信号量
        } api_call;
        struct {
          struct pbuf *p; //指向接收到的数据包
          struct netif *netif; //表示接收到数据包的网卡
          netif_input_fn input_fn; //表示输入的函数接口
        } inp; //记录数据包消息的内容
        struct { //用于记录回调函数与其对应的形参
          tcpip_callback_fn function;
          void *ctx;
        } cb;
        struct { //记录超时相关信息,如超时的时间,超时回调函数,参数等。
          u32_t msecs;
          sys_timeout_handler h;
          void *arg;
        } tmo;
      } msg;
    };
    

    对于每种类型的消息, LwIP 内核都必须有一个产生与之对应的消息函数,在产生该类型的消息后就将其投递到系统邮箱 tcpip_mbox 中,这样子 tcpip_thread 线程就会从邮箱中得到消息并且处理,从而能使内核完美运作,从第一幅图中我们可以很直观看到对应数据包的消息, 是通过 tcpip_input()函数对消息进行构造并且投递的, 但是真正执行这些操作的函数是 tcpip_inpkt()

    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);
    }
    
    err_t
    tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn)
    {
      struct tcpip_msg *msg;
      LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(tcpip_mbox));
      msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_INPKT);
      if (msg == NULL) {
        return ERR_MEM;
      }
      /*构造消息*/
      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;
    }
    

    无论是裸机编程还是操作系统,都是通过 ethernet_input()函数去处理接收到的数据包,只不过操作系统通过线程与线程间数据通信,使用了消息进行传递,这样子能使接收线程与内核线程互不干扰,相互独立开,在操作系统环境下,接收线程只负责接收数据包、构造消息并且完成投递消息即可,这样子处理完又能接收下一个数据包,这样子的效率更加高效,而内核根据这些消息做对应处理即可。
    在这里插入图片描述

    API消息

    struct api_msg {
      /** The netconn which to process - always needed: it includes the semaphore
          which is used to block the application thread until the function finished. */
      struct netconn *conn; //当前连接
      /** The return value of the function executed in tcpip_thread. */
      err_t err; //执行结果
      /** Depending on the executed function, one of these union members is used */
      union {
        /** used for lwip_netconn_do_send */
        struct netbuf *b; //执行 lwip_netconn_do_send 需要的参数,待发送数据
        /** used for lwip_netconn_do_newconn */
        struct {
          u8_t proto; //执行 lwip_netconn_do_newconn 需要的参数,连接类型
        } n;
        /** used for lwip_netconn_do_bind and lwip_netconn_do_connect */
        struct {
          API_MSG_M_DEF_C(ip_addr_t, ipaddr); //ip地址
          u16_t port; //端口号
          u8_t if_idx;
        } bc;
        /** used for lwip_netconn_do_getaddr */
        //执行 lwip_netconn_do_getaddr 需要的参数
        struct {
          ip_addr_t API_MSG_M_DEF(ipaddr);
          u16_t API_MSG_M_DEF(port);
          u8_t local;
        } ad;
        /** used for lwip_netconn_do_write */
        struct {
          /** current vector to write */
          const struct netvector *vector; //要写入的当前向量
          /** number of unwritten vectors */
          u16_t vector_cnt; //未写入的向量的数量
          /** offset into current vector */
          size_t vector_off; //偏移到当前向量
          /** total length across vectors */
          size_t len; //总长度
          /** offset into total length/output of bytes written when err == ERR_OK */
          size_t offset; //偏移量
          u8_t apiflags;
    #if LWIP_SO_SNDTIMEO
          u32_t time_started;
    #endif /* LWIP_SO_SNDTIMEO */
        } w;
        /** used for lwip_netconn_do_recv */
        struct {
          size_t len; //执行lwip_netconn_do_recv 需要的变量
        } r;
    #if LWIP_TCP
        /** used for lwip_netconn_do_close (/shutdown) */
        struct {
          u8_t shut;
    #if LWIP_SO_SNDTIMEO || LWIP_SO_LINGER
          u32_t time_started;
    #else /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */
          u8_t polls_left;
    #endif /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */
        } sd;
    #endif /* LWIP_TCP */
    #if LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD)
        /** used for lwip_netconn_do_join_leave_group */
        struct {
          API_MSG_M_DEF_C(ip_addr_t, multiaddr);
          API_MSG_M_DEF_C(ip_addr_t, netif_addr);
          u8_t if_idx;
          enum netconn_igmp join_or_leave;
        } jl;
    #endif /* LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) */
    #if TCP_LISTEN_BACKLOG
        struct {
          u8_t backlog;
        } lb;
    #endif /* TCP_LISTEN_BACKLOG */
      } msg;
    #if LWIP_NETCONN_SEM_PER_THREAD
      sys_sem_t* op_completed_sem;
    #endif /* LWIP_NETCONN_SEM_PER_THREAD */
    };
    

    api_msg 只包含 3 个字段,描述连接信息的 conn、内核返回的执行结果 err、还有 msg,msg 是一个共用体,根据不一样 的 API 接口使用不一样的数据结构。在 conn 中,它保存了当前连接的重要信息,如信号量、邮箱等, lwip_netconn_do_xxx(xxx 表示不一样的NETCONN API 接口) 类型的函数执行需要用这些信息来完成与应用线程的通信与同步;内核执行 lwip_netconn_do_xxx 类型的函数返回结果会被记录在 err 中; msg 的各个产业记录各个函数执行时需要的详细参数。
    对于上层的 API 函数,想要与内核进行数据交互,也是通过 LwIP 的消息机制, API 消息由用户线程发出,与内核进行交互,因为用户的应用程序并不是与内核处于同一线程中, 简单来说就是用户使用 NETCONN API 接口的时候, LwIP 会将对应 API 函数与参数构造成消息传递到 tcpip_thread 线程中,然后根据对应的 API 函数执行对应的操作, LwIP 这样子处理是为了简单用户的编程,这样子就不要求用户对内核很熟悉,与数据包消息类似, 也是有独立的 API 消息投递函数去处理,那就是netconn_apimsg()函数,在 NETCONN API 中构造完成数据包,就会调用 netconn_apimsg()函数进行投递消息

    err_t
    netconn_bind(struct netconn *conn, const ip_addr_t *addr, u16_t port)
    {
      API_MSG_VAR_DECLARE(msg); //定义一个 api_msg 消息
      err_t err;
    
      LWIP_ERROR("netconn_bind: invalid conn", (conn != NULL), return ERR_ARG;);
    
    #if LWIP_IPV4
      /* Don't propagate NULL pointer (IP_ADDR_ANY alias) to subsequent functions */
      if (addr == NULL) {
        addr = IP4_ADDR_ANY; //如果传入的地址为NULL,则绑定 IP4_ADDR_ANY
      }
    #endif /* LWIP_IPV4 */
    //构造 api_msg 消息
      API_MSG_VAR_ALLOC(msg);
      API_MSG_VAR_REF(msg).conn = conn;
      API_MSG_VAR_REF(msg).msg.bc.ipaddr = API_MSG_VAR_REF(addr);
      API_MSG_VAR_REF(msg).msg.bc.port = port;
      err = netconn_apimsg(lwip_netconn_do_bind, &API_MSG_VAR_REF(msg));
      API_MSG_VAR_FREE(msg);
    
      return err;
    }
    
    static err_t
    netconn_apimsg(tcpip_callback_fn fn, struct api_msg *apimsg)
    {
      err_t err;
    
    #ifdef LWIP_DEBUG
      /* catch functions that don't set err */
      apimsg->err = ERR_VAL;
    #endif /* LWIP_DEBUG */
    
    #if LWIP_NETCONN_SEM_PER_THREAD
      apimsg->op_completed_sem = LWIP_NETCONN_THREAD_SEM_GET();
    #endif /* LWIP_NETCONN_SEM_PER_THREAD */
    
      err = tcpip_send_msg_wait_sem(fn, apimsg, LWIP_API_MSG_SEM(apimsg));
      if (err == ERR_OK) {
        return apimsg->err;
      }
      return err;
    }
    
    err_t
    tcpip_send_msg_wait_sem(tcpip_callback_fn fn, void *apimsg, sys_sem_t *sem)
    {
    #if LWIP_TCPIP_CORE_LOCKING
      LWIP_UNUSED_ARG(sem);
      LOCK_TCPIP_CORE();
      fn(apimsg); //直接调用 fn 函数,即 lwip_netconn_do_bind,推荐做法
      UNLOCK_TCPIP_CORE();
      return ERR_OK;
    #else /* LWIP_TCPIP_CORE_LOCKING */
      TCPIP_MSG_VAR_DECLARE(msg);
    
      LWIP_ASSERT("semaphore not initialized", sys_sem_valid(sem));
      LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(tcpip_mbox));
    
      TCPIP_MSG_VAR_ALLOC(msg);
      TCPIP_MSG_VAR_REF(msg).type = TCPIP_MSG_API;
      TCPIP_MSG_VAR_REF(msg).msg.api_msg.function = fn;
      TCPIP_MSG_VAR_REF(msg).msg.api_msg.msg = apimsg;
      sys_mbox_post(&tcpip_mbox, &TCPIP_MSG_VAR_REF(msg)); //投递 API消息,内核进行处理
      sys_arch_sem_wait(sem, 0);
      TCPIP_MSG_VAR_FREE(msg);
      return ERR_OK;
    #endif /* LWIP_TCPIP_CORE_LOCKING */
    }
    

    用户的应用线程与内核也是相互独立的,依赖操作系统的 IPC 通信机制进行数据交互与同步(邮箱、信号量等), LwIP 提供上层 NETCONN API 接口,会自动帮我们处理这些事情,只需要我们根据 API 接口传递正确的参数接口
    在这里插入图片描述
    其实这个运作示意图并不是最优的,这种运作的方式在每次发送数据的时候,会进行一次线程的调度,这无疑是增大了系统的开销,而将 LWIP_TCPIP_CORE_LOCKING 宏定义设置为 1 则无需操作系统邮箱与信号量的参与,直接在用户线程中通过回调函数调用对应的处理,当然在这个过程中,内核线程是无法获得互斥量而运行的,因为是通过互斥量进行保护用户线程的处理, 当然, LwIP 的作者也是这样子建议的。

  • 相关阅读:
    游戏资讯查询易语言代码
    『无为则无心』Python基础 — 44、对文件和文件夹的操作
    【Centos7.9通过systemctl启动zookeeper和kafka失败】
    React实现一个简易版Swiper
    复习单片机:中断系统(内含1.中断概念+2 中断结构及相关寄存器)(注:相关寄存器是重点)
    c#仿热血江湖
    Java项目--书评网信息系统
    C/C++算术表达式求值
    阿里熬一个月肝出这份Java面试手册 MyBatis篇
    Flutter Widgets 之 RubyText
  • 原文地址:https://blog.csdn.net/qq_40831436/article/details/127068514