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 層去處理,這就是一個數據從網卡到內核的過程

有圖亦可知,用戶程序與內核是完全獨立的,只是通操作系統的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);
}

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_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]));
  }
}

每個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 */
}

 

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_TCPIP_CORE_LOCKING 宏定義設置爲1, 則無需操作系統郵箱與信號量的參與,直接在用戶線程中通過回調函數調用對應的處理,當然在這個過程中,內核線程是無法獲得互斥量而運行的,因爲是通過互斥量進行保護用戶線程的處理,當然,LwIP 也是這樣建議的。見 上面tcpip_send_msg_wait_sem()源碼,LWIP_TCPIP_CORE_LOCKING ==1.

 

總結,通過以上分析,從底層數據包輸入到內核,從應用程序到內核間的數據交互,都依賴操作系統的IPC 通信機制。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章