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; **2**
};msg **1**
union {
struct {
...
}api_msg; **3**
struct {
...
}api_call;
struct {
...
}inp;
struct {
...
}cb;
struct {
...
}tmo;
}msg; **2**
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想怎麼玩就怎麼玩,我們下期再見.