lwIP TCP/IP 协议栈笔记之十: LwIP 数据流框架

目录

1. 网卡数据接收流程

2. 内核超时处理

2.1 sys_timeo 结构体与超时链表

2.2 注册超时事件

2.3 超时检查

3. tcpip_thread 线程

4. LwIP 中的消息

4.1 消息结构

4.2 数据包消息

4.3 API 消息


通信过程中,本质上是数据的交互,数据传递,理解LwIP的框架,就可以更清晰的理解数据传递过程、原理。

注:以下皆为有操作系统环境下,网卡数据传递到内核的运作流程

1. 网卡数据接收流程

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

lwIP TCP/IP 协议栈笔记之十: LwIP 数据流框架_第1张图片

有图亦可知,用户程序与内核是完全独立的,只是通操作系统的IPC 通信机制进行数据交互。 

2. 内核超时处理

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

LwIP 采用软件定时器对这些超时进行处理,因为软件定时器很容易维护,并且与平台无关,只需要用户提供一个较为准确的时基即可。

2.1 sys_timeo 结构体与超时链表

LwIP 通过一个sys_timeo 类型的数据结构管理与超时链表相关的所有超时事件。LwIP使用这个结构体记录下内核中所有被注册的超时事件,这些结构体会以链表的形式一个个连接在超时链表中,而内核中只有一条超时链表。

LwIP定义了一个sys_timeo 类型的指针next_timeout,并且将next_timeout 指向当前内核中链表头部,所有被注册的超时事件都会按照被处理的先后顺序排列在超时链表上。

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;    // 指向超时链表第一个超时事件

2.2 注册超时事件

LwIP 虽然使用超时链表进行管理所有的超时事件,那么它首先需要知道有哪些超时事件才能去管理,而这些超时事件就是通过注册的方式被挂载在链表上.

超时事件要在内核中登记一下,内核才会去处理,LwIP 中注册超时事件的函数是sys_timeout(),但是实际上是调用sys_timeout_abs()函数.

sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg)
{
  u32_t next_timeout_time;

  LWIP_ASSERT_CORE_LOCKED();

  LWIP_ASSERT("Timeout time too long, max is LWIP_UINT32_MAX/4 msecs", msecs <= (LWIP_UINT32_MAX / 4));

  /* 根据当前时间计算超时时间 */
  next_timeout_time = (u32_t)(sys_now() + msecs); /* overflow handled by TIME_LESS_THAN macro */ 
  
  /* 当前事件插入超时链表 */
  sys_timeout_abs(next_timeout_time, handler, arg);
}

lwIP TCP/IP 协议栈笔记之十: LwIP 数据流框架_第2张图片

sys_timeout_abs(u32_t abs_time, sys_timeout_handler handler, void *arg)
{
  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 (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 TCP/IP 协议栈笔记之十: LwIP 数据流框架_第3张图片

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

/** Initialize this module */
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]));
  }
}

lwIP TCP/IP 协议栈笔记之十: LwIP 数据流框架_第4张图片

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

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;

  cyclic->handler();

  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 */

    sys_timeout_abs((u32_t)(now + cyclic->interval_ms), lwip_cyclic_timer, arg);

  } else {
    /* correct cyclic interval with handler execution delay and sys_check_timeouts jitter */
    sys_timeout_abs(next_timeout_time, lwip_cyclic_timer, arg);

  }
}

2.3 超时检查

LwIP 实现了超时处理,那么无论我们的开发平台是否使用操作系统,都可以对其进行超时检查并且去处理,lwip 中以下两个函数可以实现对超时的处理。

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.c)

这个函数在操作系统的线程中循环调用,主要是等待tcpip_mbox 消息,是可阻塞的,如果在等待tcpip_mbox 的过程中发生超时事件,则会同时执行超时事件处理,即调用超时回调函数。LwIP 是这样子处理的,如果已经发生超时,LwIP 就会内部调用sys_check_timeouts()函数去检查超时的sys_timeo 结构体并调用其对应的回调函数,如果没有发生超时,那就一直等待消息,其等待的时间为下一个超时时间的时间,一举两得。 LwIP 中tcpip 线程就是靠这种方法,即处理了上层及底层的tcpip_mbox 消息,同时处理了所有需要超时处理的事件。

#define TCPIP_MBOX_FETCH(mbox, msg) tcpip_timeouts_mbox_fetch(mbox, msg)
/**
 * Wait (forever) for a message to arrive in an mbox.
 * While waiting, timeouts are processed.
 *
 * @param mbox the mbox to fetch the message from
 * @param msg the place to store the message
 */
static void
tcpip_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg)
{
  u32_t sleeptime, res;

again:
  LWIP_ASSERT_CORE_LOCKED();

  /* 得到距离事件超时的时间并保存在sleeptime 变量中 */
  sleeptime = sys_timeouts_sleeptime();
  if (sleeptime == SYS_TIMEOUTS_SLEEPTIME_INFINITE) {
    /* 无超时事件,那只需一直等待mbox 消息即可 */
    UNLOCK_TCPIP_CORE();
    sys_arch_mbox_fetch(mbox, msg, 0);
    LOCK_TCPIP_CORE();
    return;
  } else if (sleeptime == 0) {
    /* 0 表示已经发生超时了,那就调用sys_check_timeouts()去检查一下,并处理 */
    sys_check_timeouts();
    /* We try again to fetch a message from the mbox. */
    goto again;
  }

  UNLOCK_TCPIP_CORE();

  /* 对于其他时间,LwIP 就在等待tcpip_mbox 的消息的同时就去处理超时事件, 
    等待tcpip_mbox 的消息的时间为sleeptime,然后在时间到达的时候就处理超时事件*/
  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;
  }
}

3. tcpip_thread 线程

LwIP 在操作系统的环境下,LwIP 内核是作为操作系统的一个线程运行的,在协议栈初始化的时候就会创建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);
  }
}
/* Handle a single tcpip_msg
 * This is in its own function for access by tests only.
 */
static void
tcpip_thread_handle_msg(struct tcpip_msg *msg)
{
  switch (msg->type) {
#if !LWIP_TCPIP_CORE_LOCKING
    /* TCPIP_MSG_API 和 TCPIP_MSG_API_CALL
        根据消息中的不同类型进行不同的处理,对于TCPIP_MSG_API类型,就执行对应的API 函数 */
    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
    /* TCPIP_MSG_INPKT 类型,直接交给ARP 层处理。 */
    case TCPIP_MSG_INPKT:
      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) {
        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
    /* TCPIP_MSG_TIMEOUT 类型,表示上层注册一个超时事件,直接执行注册超时事件即可。 */
    case 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;
    
    /* TCPIP_MSG_ UNTIMEOUT 类型,表示上层删除一个超时事件,直接执行删除超时事件即可。 */
    case 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 */

    /* TCPIP_MSG_CALLBACK 或者是TCPIP_MSG_CALLBACK_STATIC 类型,
        表示上层通过回调方式执行一个回调函数,那么就执行对应的回调函数即可*/
    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;
  }
}

4. LwIP 中的消息

4.1 消息结构

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 {
#if !LWIP_TCPIP_CORE_LOCKING
  TCPIP_MSG_API,                                
  TCPIP_MSG_API_CALL,                            // API 函数调用
#endif /* !LWIP_TCPIP_CORE_LOCKING */
#if !LWIP_TCPIP_CORE_LOCKING_INPUT
  TCPIP_MSG_INPKT,                               // 底层数据包输入
#endif /* !LWIP_TCPIP_CORE_LOCKING_INPUT */
#if LWIP_TCPIP_TIMEOUT && LWIP_TIMERS
  TCPIP_MSG_TIMEOUT,                             // 注册超时事件
  TCPIP_MSG_UNTIMEOUT,                           // 删除超时事件
#endif /* LWIP_TCPIP_TIMEOUT && LWIP_TIMERS */
  TCPIP_MSG_CALLBACK,                                
  TCPIP_MSG_CALLBACK_STATIC                      // 执行回调函数
};
struct tcpip_msg {
  enum tcpip_msg_type type;               // 消息的类型
  union {
#if !LWIP_TCPIP_CORE_LOCKING
    
    /* API 消息主要由两部分组成,
        一部分是用于表示内核执行的API 函数,
        另一部分是执行函数时候的参数,都会被记录在api_msg 中*/
    struct {
      tcpip_callback_fn function;
      void* msg;
    } api_msg;

    /* 与API 消息差不多,也是由两部分组成,
        一部分是tcpip_api_call_fn类型的函数,
        另一部分是其对应的形参,
        此外还有用于同步的信号量 */
    struct {
      tcpip_api_call_fn function;
      struct tcpip_api_call_data *arg;
      sys_sem_t *sem;
    } api_call;
#endif /* LWIP_TCPIP_CORE_LOCKING */
#if !LWIP_TCPIP_CORE_LOCKING_INPUT

    /* inp 用于记录数据包消息的内容,
        p 指向接收到的数据包;
        netif 表示接收到数据包的网卡;
        input_fn 表示输入的函数接口,在tcpip_inpkt 进行配置。
*/
    struct {
      struct pbuf *p;
      struct netif *netif;
      netif_input_fn input_fn;
    } inp;
#endif /* !LWIP_TCPIP_CORE_LOCKING_INPUT */

    /* cb 用于记录回调函数与其对应的形参 */
    struct {
      tcpip_callback_fn function;
      void *ctx;
    } cb;

#if LWIP_TCPIP_TIMEOUT && LWIP_TIMERS
    /* mo 用于记录超时相关信息,如超时的时间,超时回调函数,参数等 */
    struct {
      u32_t msecs;
      sys_timeout_handler h;
      void *arg;
    } tmo;
#endif /* LWIP_TCPIP_TIMEOUT && LWIP_TIMERS */
  } msg;
};

4.2 数据包消息

数据包的消息,是通过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)
{
#if LWIP_TCPIP_CORE_LOCKING_INPUT
  err_t ret;
  LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_inpkt: PACKET %p/%p\n", (void *)p, (void *)inp));
  LOCK_TCPIP_CORE();
  ret = input_fn(p, inp);
  UNLOCK_TCPIP_CORE();
  return ret;
#else /* LWIP_TCPIP_CORE_LOCKING_INPUT */
  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;
  /* 构造消息完成,就调用sys_mbox_trypost 进行投递消息 */
  if (sys_mbox_trypost(&tcpip_mbox, msg) != ERR_OK) {
    memp_free(MEMP_TCPIP_MSG_INPKT, msg);
    return ERR_MEM;
  }
  return ERR_OK;
#endif /* LWIP_TCPIP_CORE_LOCKING_INPUT */
}

 

lwIP TCP/IP 协议栈笔记之十: LwIP 数据流框架_第5张图片

4.3 API 消息

LwIP 使用api_msg 结构体描述一个API 消息的内容。(api_msg.h)

/** This struct includes everything that is necessary to execute a function
    for a netconn in another thread context (mainly used to process netconns
    in the tcpip_thread context to be thread safe). */
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 */
    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;
    } 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 函数执行对应的操作。

这样处理是为了简单用户的编程,这样就不要求用户对内核很熟悉,与数据包消息类似,也是有独立的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);
  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;
  }
#endif /* LWIP_IPV4 */

#if LWIP_IPV4 && LWIP_IPV6
  /* "Socket API like" dual-stack support: If IP to bind to is IP6_ADDR_ANY,
   * and NETCONN_FLAG_IPV6_V6ONLY is 0, use IP_ANY_TYPE to bind
   */
  if ((netconn_get_ipv6only(conn) == 0) &&
      ip_addr_cmp(addr, IP6_ADDR_ANY)) {
    addr = IP_ANY_TYPE;
  }
#endif /* LWIP_IPV4 && LWIP_IPV6 */

  /* 初始化 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;
  /* 投递 msg ,需要等待tcpip_thread 回应*/
  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);
  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_API消息 */
  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()函数向内核进行投递消息 */
  sys_mbox_post(&tcpip_mbox, &TCPIP_MSG_VAR_REF(msg));

  /* 同时调用sys_arch_sem_wait()函数等待消息处理完毕 */
  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 TCP/IP 协议栈笔记之十: LwIP 数据流框架_第6张图片

 这个运作示意图并不是最优的,这种运作的方式在每次发送数据的时候,会进行一次线程的调度,这无疑是增大了系统的开销

LWIP_TCPIP_CORE_LOCKING 宏定义设置为1, 则无需操作系统邮箱与信号量的参与,直接在用户线程中通过回调函数调用对应的处理,当然在这个过程中,内核线程是无法获得互斥量而运行的,因为是通过互斥量进行保护用户线程的处理,当然,LwIP 也是这样建议的。见 上面tcpip_send_msg_wait_sem()源码,LWIP_TCPIP_CORE_LOCKING ==1.

 

总结,通过以上分析,从底层数据包输入到内核,从应用程序到内核间的数据交互,都依赖操作系统的IPC 通信机制。

你可能感兴趣的:(TCP/IP,嵌入式开发,LWIP)