网卡接收数据的流程

以太网接收完数据后产生一个中断,然后释放一个信号量通知网卡接收线程去处理这些接收的数据,然后将数据这些数据封装成消息,投递到 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_TIMERNAMESconst 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_TIMERNAMEStimeout->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 andis 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_TIMERNAMESLWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: %s()\n", cyclic->handler_name));
#endifcyclic->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_TIMERNAMESsys_timeout_abs((u32_t)(now + cyclic->interval_ms), lwip_cyclic_timer, arg, cyclic->handler_name);
#elsesys_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_TIMERNAMESsys_timeout_abs(next_timeout_time, lwip_cyclic_timer, arg, cyclic->handler_name);
#elsesys_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 occurredbefore 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;}//一直阻塞elsewait_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_LOCKINGcase 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_INPUTcase 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_inputpbuf_free(msg->msg.inp.p);}memp_free(MEMP_TCPIP_MSG_INPKT, msg);break;
#endif /* !LWIP_TCPIP_CORE_LOCKING_INPUT */#if LWIP_TCPIP_TIMEOUT && LWIP_TIMERScase 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_ETHERNETif (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 semaphorewhich 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_SNDTIMEOu32_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_LINGERu32_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_BACKLOGstruct {u8_t backlog;} lb;
#endif /* TCP_LISTEN_BACKLOG */} msg;
#if LWIP_NETCONN_SEM_PER_THREADsys_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_THREADapimsg->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_LOCKINGLWIP_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 的作者也是这样子建议的。

《lwip学习5》-- lwip一探究竟相关推荐

  1. LWIP学习 (1) LWIP简介

    文章目录 LWIP 简介 LWIP 文件说明 core内核文件简介 LWIP 三种编程接口 RAW NETCONN SOCKET API ETH(以太网) 介绍 SMI接口 MII接口 RMII接口 ...

  2. 【LWIP】LWIP协议|相关知识汇总|LWIP学习笔记

    这里作为一个汇总帖把,把以前写过的LWIP相关的博客文章汇总到一起,方便自己这边查找一些资料. 收录于: [LWIP]LWIP协议|相关知识汇总|LWIP学习笔记 LWIP协议 [LWIP]LWIP网 ...

  3. LwIP学习笔记——STM32 ENC28J60移植与入门

    0.前言 去年(2013年)的整理了LwIP相关代码,并在STM32上"裸奔"成功.一直没有时间深入整理,在这里借博文整理总结.LwIP的移植过程细节很多,博文也不可能一一详解个别 ...

  4. 软件测试自学网站有哪些?不妨一探究竟

    一:前言 相信各位在学习培训的时候,无论学什么,都会习惯性地找自学网站.各位在自学软件测试的时候,也不会例外.那么,软件测试学习培训网站有哪些?我们不妨一探究竟. "我们需要去哪个网站学习培 ...

  5. 初识百态.末路归正:前方迷雾已散,待我一探究竟.《一》

    起源 { 本人从小非常喜欢计算机,由于我是98年的,刚好长大时计算机流行了起来,而且当时游戏正待巅峰时期.,列入[穿越火线,QQ飞车,等等....]随着对游戏的热爱, 我发现了外挂的存在.当时恍如发现 ...

  6. android有多个活动,Android活动一探究竟

    作为Android的四大组件之一,活动最先走进我们的视野,其重要性不言而喻,今天就抽出时间来专门对Android活动一探究竟. 什么是活动 活动即Activity,是一种可以包含用户界面的组件,And ...

  7. 为何要配置环境变量?带你一探究竟

    一.前言 干了这么多年Java,配置环境变量都是第一步要做的,但是为什么要配置环境变量呢,又有什么用呢,今天哪吒就带你一探究竟. 二.百度百科 有事没事找百度,百度解释名词这一块做的是真的好. 1.环 ...

  8. 平常人可以漂亮到什么程度?教你爬取知乎大神们的回答一探究竟!

    大家好,今天才哥带大家看看知乎这个高达14.3万关注,2.6亿浏览,回答数超过1.27万的问题<平常人可以漂亮到什么程度?>. 最近呢,可能是因为写了几篇关于爬虫获取美女照片的文章的缘故? ...

  9. 一探究竟之PullZoomView

    我们先看效果图: 这是仿苹果app的一种下拉head拉伸的效果,这种弹性伸缩能给用户一种良好的体验,因此我们在各大主流app上也能看的到这种效果,而PullZoomView是一款在安卓能实现上述效果并 ...

  10. 一探究竟:MyBatis的mapper查询接口返回的list会不会是null?

    文章目录 1 背景 2 debug一探究竟 2.1 mybatis-spring包中的SqlSessionTemplate 2.2 sqlSessionProxy对象 2.3 SqlSessionIn ...

最新文章

  1. printf输出字符串的一些格式
  2. linux下安装redmine1.2.1全记录
  3. python中栈_Python中的栈
  4. Chapter 1:Introduction
  5. 有了这个运维方案,让IT信息化人员头疼的系统宕机再也没出现
  6. Win10+caffe+CUDA9.1+vs2013+Matlab2018b+GPU环境,跑通faster_rcnn-master
  7. java spring定时器_Spring定时器的两种实现方式
  8. 风尚云网学习-js实现禁用右键以及F12
  9. 图片在线转换成word免费版
  10. BatchNorm和LayerNorm——通俗易懂的理解
  11. Valine-实现QQ邮箱识别生成头像地址(完美解决头像问题)
  12. wifi辐射知多少【解疑答惑篇】
  13. KB奇遇记(3):信息化沙漠
  14. IT人,更应该要注意保养!!
  15. 【Ubuntu升级报错】“the following signatures couldn’t be verified because the public key is not available”
  16. PCI-DSS安全认证
  17. 冒泡排序(Bubble Sort)
  18. 实验 基本交换机设置
  19. 字符串abcd逆序c语言,c编写:输入一个字符串以回车符为结束,将其逆序重新存放。例如abcde变为edcba...
  20. 北京交通大学计算机学院保研,北京交通大学计算机与信息技术学院(专业学位)计算机技术保研条件...

热门文章

  1. xp mode安装详细教程
  2. cc:to me 让你的邮箱成为Instapaper的替代品
  3. java 子类继承父类各方法的执行顺序
  4. MFC CString显示中文为乱码问题
  5. 2022软考网工笔记(Linux篇)
  6. 今天开始学习日语了!
  7. 网约车行业格局加速重构 T3出行如何打开增长新通道?
  8. Psr(psr1+psr2+psr3)
  9. 【Java项目】美食旅行博客
  10. 英国4岁女童患“iPad成瘾症”接受治疗 引担忧