LWIP_簡記(7.tcpip_init()之tcpip_thread)

LWIP一句話記住就行:
一項工程,兩份配置,三種內存分配,四套操作API,五步初始化,六個"數據流",七個數據結構
-------------------------------------------

接着上一篇的講:

/**
 * @ingroup lwip_os
 * Initialize this module:
 * - initialize all sub modules
 * - start the tcpip_thread
 *
 * @param initfunc a function to call when tcpip_thread is running and finished initializing
 * @param arg argument to pass to initfunc
 */
void
tcpip_init(tcpip_init_done_fn initfunc, void *arg)
{
  lwip_init();
 
  tcpip_init_done = initfunc;
  tcpip_init_done_arg = arg;
  if (sys_mbox_new(&tcpip_mbox, TCPIP_MBOX_SIZE) != ERR_OK) {
    LWIP_ASSERT("failed to create tcpip_thread mbox", 0); 
  }
#if LWIP_TCPIP_CORE_LOCKING
  if (sys_mutex_new(&lock_tcpip_core) != ERR_OK) {
    LWIP_ASSERT("failed to create lock_tcpip_core", 0); 
  }
#endif /* LWIP_TCPIP_CORE_LOCKING */
 
  sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, RT_THREAD_PRIORITY_MAX-1);
}

通過上一篇的分析,清楚地知道tcpip_init()中的四步曲,重要的兩步就是1/4,lwip_init()後續會有一大堆文章襲來.今天先把第四步整通透.

sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, RT_THREAD_PRIORITY_MAX-1);
先簡單說一下這個函數:靜態創建一個線程,其實還是調用了rt_thread_create()
arg1:線程名字,一個宏定義,其實就是tcpip() (這裏需要強調一下,有些同志看到tcp就想到了傳輸層協議,看到ip就想到了網絡層協議.以至於在分析代碼時經常被tcpip_init(),tcp_init()弄暈乎,大家留意一下就好,tcpip, tcp, ip是完全不同的.)
arg2:tcpip_thread具體的線程函數,後面會講.
arg3:原型中是*arg,表示線程函數需要的參數傳遞.
arg4:TCPIP_THREAD_STACKSIZE 看到stack就應該想到是棧大小了,這裏宏定義爲4096
arg5:RT_THREAD_PRIORITY_MAX-1 看到priority就應該想到優先級了,這裏宏定義爲MAX 32

好了,來看看tcpip_thread的廬山真面目吧.

/**                                                                                                                                     
 * The main lwIP thread. This thread has exclusive access to lwIP core functions                                                        
 * (unless access to them is not locked). Other threads communicate with this                                                           
 * thread using message boxes.                                                                                                          
 *                                                                                                                                                                     
 * It also starts all the timers to make sure they are running in the right                                                             
 * thread context.                                                                                                                      
 *                                                                                                                                      
 * @param arg unused argument                                                                                                           
 */                                                                                                                                     
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);                                                                                                       
  }                                                                                                                                     
}             

這裏先給大家一個建議,閱讀源碼時,特別是針對函數一定要先看看前面的註釋,基本功能和參數至少都描述的很清楚了.
功能:這是lwip的主線程,和其他線程之間通過郵箱機制通信.
不是一般性,我總結爲以下幾步:

1.定義一個tcpip_msg用於後續通信.

struct tcpip_msg {
  enum tcpip_msg_type type;
  union {
#if !LWIP_TCPIP_CORE_LOCKING
    struct {
      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;
#endif /* LWIP_TCPIP_CORE_LOCKING */
#if !LWIP_TCPIP_CORE_LOCKING_INPUT
    struct {
      struct pbuf *p;
      struct netif *netif;
      netif_input_fn input_fn;
    } inp;
#endif /* !LWIP_TCPIP_CORE_LOCKING_INPUT */
    struct {
      tcpip_callback_fn function;
      void *ctx;
    } cb;
#if LWIP_TCPIP_TIMEOUT && LWIP_TIMERS
    struct {
      u32_t msecs;
      sys_timeout_handler h;
      void *arg;
    } tmo;
#endif /* LWIP_TCPIP_TIMEOUT && LWIP_TIMERS */
  } msg;
};

條件編譯的先不看,那麼改消息結構體主要就兩個:消息類型,回調函數

enum tcpip_msg_type {
#if !LWIP_TCPIP_CORE_LOCKING
  TCPIP_MSG_API,
  TCPIP_MSG_API_CALL,
#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
};       

瞭解一下,後面會用到.

2.上把鎖
LOCK_TCPIP_CORE();
本質就是調用了互斥鎖:
#define LOCK_TCPIP_CORE() sys_mutex_lock(&lock_tcpip_core)

3.tcpip_init_done我還是理解爲初始化
我們回憶一下調用流程:
board_xxx_init()—>tcpip_init()
tcpip_init(tcpip_init_done_fn initfunc, void *arg)原型是要傳遞一個函數和一些參數的,我們稱爲鉤子函數.
顯然一開始直接調用tcpip_init(NULL,NULL)所以
tcpip_init_done = initfunc;
tcpip_init_done_arg = arg;
其實
是因爲沒有傳遞inifunc,也沒arg,所以前面init_done和init_done_arg兩句就沒用的.如果給他傳遞了,會在tcpip_thread裏面先執行init_done的鉤子函數.屬於初始化後的結果.

4.MAIN LOOP通過郵箱機制等待消息來激活

/**
 * Define this to something that triggers a watchdog. This is called from
 * tcpip_thread after processing a message.
 */
#if !defined LWIP_TCPIP_THREAD_ALIVE || defined __DOXYGEN__
#define LWIP_TCPIP_THREAD_ALIVE()                                                                                                                                      
#endif

接下來就是最總要的:

#if !LWIP_TIMERS                        
/* wait for a message with timers disabled (e.g. pass a timer-check trigger into tcpip_thread) */
#define TCPIP_MBOX_FETCH(mbox, msg) sys_mbox_fetch(mbox, msg)                                                                                                          
#else /* !LWIP_TIMERS */                
/* wait for a message, timeouts are processed while waiting */
#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 = sys_timeouts_sleeptime();
  if (sleeptime == SYS_TIMEOUTS_SLEEPTIME_INFINITE) {
    UNLOCK_TCPIP_CORE();
    sys_arch_mbox_fetch(mbox, msg, 0); 
    LOCK_TCPIP_CORE();
    return;
  } else if (sleeptime == 0) {
    sys_check_timeouts();
    /* We try again to fetch a message from the mbox. */
    goto again;
  }    
  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;
  }
}       

等待郵箱中收到消息,然後就去取.同時也會做超時處理.
消息爲空就一直continue.
一旦取到消息就會跳出循環進入操作句柄函數.
tcpip_thread_handle_msg(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
    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:   
      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   
    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;                            
    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 */
                                        
    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;                            
  }                                     
}  

還記得前面提到的msg結構體的兩個重要成員嗎?類型和回調函數.
句柄函數框架上很好理解,就是根據取到的消息類型調用對應的回調函數進一步處理.
但是如果要深入追代碼,會調函數的流程還是比較複雜的,這裏我們以第一個類型TCPIP_MSG_API嘗試追一下回調函數.

5.具體消息回調函數

 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;              

一陣暈乎,理一下結構體吧.
msg->msg.api_msg.function
第一個msg->: tcpip_msg結構體的名字.
第二個msg->msg: tcpip_msg結構體的一個成員變量,是unio類型,也記爲msg.
第三個msg->msg.api_msg: unio類型msg的一個成員變量,也是結構體.
第四個msg->msg.api_msg.function: api_msg的成員變量.
可能還是很暈,下面我簡化一下:

struct tcpip_msg {
	enum tcpip_msg_type type;
	unio {
		...
	}msg; ****
};msg  ****
union {
	struct {
	...
	}api_msg;  **3**
	struct {
	...
	}api_call;
	struct {
	...
	}inp;
	struct {
	...
	}cb;
	struct {
	...
	}tmo;
}msg;  ****
    struct {
      tcpip_callback_fn function;   **4**
      void* msg;   **4**
    } api_msg;   **3**

說實話就這樣看我也布吉島function最後在哪裏賦值的.所以一陣grep後發現:

api/tcpip.c:453:  TCPIP_MSG_VAR_REF(msg).msg.api_msg.function = fn;

被tcpip_send_msg_wait_sem()調用,接着grep

1.api/api_lib.c:131:  err = tcpip_send_msg_wait_sem(fn, apimsg, LWIP_API_MSG_SEM(apimsg));
2.api/api_lib.c:1325:  cberr = tcpip_send_msg_wait_sem(lwip_netconn_do_gethostbyname, &API_VAR_REF(msg), API_EXPR_REF(API_VAR_REF(msg).sem));
先看看1:
api/api_lib.c中被netconn_apimsg(tcpip_callback_fn fn, struct api_msg *apimsg)函數調用.
繼續grep

```c
api/api_lib.c:118:netconn_apimsg(tcpip_callback_fn fn, struct api_msg *apimsg)
api/api_lib.c:161:    err = netconn_apimsg(lwip_netconn_do_newconn, &API_MSG_VAR_REF(msg));
api/api_lib.c:214:  err = netconn_apimsg(lwip_netconn_do_delconn, &API_MSG_VAR_REF(msg));
api/api_lib.c:282:  err = netconn_apimsg(lwip_netconn_do_getaddr, &API_MSG_VAR_REF(msg));
api/api_lib.c:288:  err = netconn_apimsg(lwip_netconn_do_getaddr, &msg);
api/api_lib.c:335:  err = netconn_apimsg(lwip_netconn_do_bind, &API_MSG_VAR_REF(msg));
api/api_lib.c:361:  err = netconn_apimsg(lwip_netconn_do_bind_if, &API_MSG_VAR_REF(msg));
api/api_lib.c:395:  err = netconn_apimsg(lwip_netconn_do_connect, &API_MSG_VAR_REF(msg));
api/api_lib.c:418:  err = netconn_apimsg(lwip_netconn_do_disconnect, &API_MSG_VAR_REF(msg));
api/api_lib.c:450:  err = netconn_apimsg(lwip_netconn_do_listen, &API_MSG_VAR_REF(msg));
api/api_lib.c:548:  netconn_apimsg(lwip_netconn_do_accepted, &API_MSG_VAR_REF(msg));
api/api_lib.c:685:  return netconn_apimsg(lwip_netconn_do_recv, msg);
api/api_lib.c:953:  err = netconn_apimsg(lwip_netconn_do_send, &API_MSG_VAR_REF(msg));
api/api_lib.c:1064:  err = netconn_apimsg(lwip_netconn_do_write, &API_MSG_VAR_REF(msg));
api/api_lib.c:1111:  err = netconn_apimsg(lwip_netconn_do_close, &API_MSG_VAR_REF(msg));
api/api_lib.c:1207:  err = netconn_apimsg(lwip_netconn_do_join_leave_group, &API_MSG_VAR_REF(msg));
api/api_lib.c:1249:  err = netconn_apimsg(lwip_netconn_do_join_leave_group_netif, &API_MSG_VAR_REF(msg));

雖然有點兒多,但總算是找到了,其實也可以理解,最初就提到過tcpip_thread作爲主線程會和其他線程通信,所以其他線程不同時對應api_msg.function賦值也不一樣.所謂的其他線程這裏就可以理解爲,netconn這套api實現的connect,listen,b bind,send,recv,close…

好了找到這些回調函數就行了,具體回調函數的處理暫時就不做講解了.後續一定會再碰到的,比如數據要收發,肯定的建立一條通信鏈路吧.標準的流程再見.

-------------------------------------------
這期就到這裏了,LWIP想怎麼玩就怎麼玩,我們下期再見.

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