TCP 的建立连接超时、重传超时机制,IP 分片数据报的重装等待超时,ARP 缓存表项的时间管理、ping 接收数据包超时处理等等,都需要使用超时操作来处理。超时处理的相关代码在timeouts.c/h 中实现
相关宏
关键结构体
该文件主要定义了两个结构体,它们分别为 lwip_cyclic_timer 和 sys_timeo,第一个结构体定义了超时等待时间和超时处理函数,另外一个是管理这些超时的定时器,着两个结构体的原型如下所示:
注册超时事件
lwip_cyclic_timers 保存了 lwIP 所需的超时事件,这些超时事件由 sys_timeouts_init 函数插入到超时链表当中,该函数如下所示:
/**
* @brief 为 LWIP 协议栈设置一个绝对时间的一次性定时器
*
* @param abs_time 定时器的绝对到期时间
* @param handler 定时器到期时要执行的处理函数
* @param arg 传递给处理函数的参数
*
* @note 如果 LWIP_DEBUG_TIMERNAMES 被定义,则还会接受一个处理函数名称的字符串参数
*/
static void
#if LWIP_DEBUG_TIMERNAMES
sys_timeout_abs(u32_t abs_time, sys_timeout_handler handler, void *arg, const char *handler_name)
#else /* LWIP_DEBUG_TIMERNAMES */
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_TIMERNAMES
timeout->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 */
/* next_timeout为全局static变量:用于指示下一个将timeout的定时器 */
/* 如果之前没有其他的定时器,则赋值为当前的定时器,然后直接返回 */
if (next_timeout == NULL) {
next_timeout = timeout;
return;
}
/* 判断新插入的定时器的时间与next_timeout的实际,若更低则将新创建的定时器插入到最前面 */
if (TIME_LESS_THAN(timeout->time, next_timeout->time)) {
timeout->next = next_timeout;
next_timeout = timeout;
} else {
/* 否则,遍历所有定时器,按timeout值升序将新创建的定时器插入next_timeout链表中 */
for (t = next_timeout; t != NULL; t = t->next) {
/* 新创建的定时器的timeout最大或位于中间 */
if ((t->next == NULL) || TIME_LESS_THAN(timeout->time, t->next->time)) {
timeout->next = t->next;
t->next = timeout;
break;
}
}
}
}
/**
* Timer callback function that calls cyclic->handler() and reschedules itself.
*
* @param arg unused argument
*/
//lwip_cyclic_timers为一系列定时器间隔和回调函数的定义
/*
实际上就是sys_timeout_abs注册的函数仅在时间到期后调用一次,而将函数作为lwip_cyclic_timer的参数,在lwip_cyclic_timer中进行注册的话,每次回调函数调用完后,会再次调用sys_timeout_abs进行注册
其中current_timeout_due_time是sys_check_timeouts(后面介绍)中调用lwip_cyclic_timer之前获取的时间
TIME_LESS_THAN(next_timeout_time, now)表示在中间这一段代码执行的过程中花费的时间大于设置的interval了,就将定时器时间设置为当前时间+interval_ms
*/
#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_TIMERNAMES
LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: %s()\n", cyclic->handler_name));
#endif
/* 执行lwip_cyclic_timers中第二个参数的函数 */
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 */
#if LWIP_DEBUG_TIMERNAMES
sys_timeout_abs((u32_t)(now + cyclic->interval_ms), lwip_cyclic_timer, arg, cyclic->handler_name);
#else
sys_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_TIMERNAMES
sys_timeout_abs(next_timeout_time, lwip_cyclic_timer, arg, cyclic->handler_name);
#else
sys_timeout_abs(next_timeout_time, lwip_cyclic_timer, arg);
#endif
}
}
/** 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]));
}
}
/**
* Create a one-shot timer (aka timeout). Timeouts are processed in the
* following cases:
* - while waiting for a message using sys_timeouts_mbox_fetch()
* - by calling sys_check_timeouts() (NO_SYS==1 only)
*
* @param msecs time in milliseconds after that the timer should expire
* @param handler callback function to call when msecs have elapsed
* @param arg argument to pass to the callback function
*/
/**
* @brief 创建一个以毫秒为单位的一次性定时器
*
* @param msecs 定时器到期的毫秒数
* @param handler 定时器到期时要执行的处理函数
* @param arg 传递给处理函数的参数
*
* @note 如果 LWIP_DEBUG_TIMERNAMES 被定义,则还会接受一个处理函数名称的字符串参数
*/
#if LWIP_DEBUG_TIMERNAMES
void
sys_timeout_debug(u32_t msecs, sys_timeout_handler handler, void *arg, const char *handler_name)
#else /* LWIP_DEBUG_TIMERNAMES */
void
sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg)
#endif /* LWIP_DEBUG_TIMERNAMES */
{
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));
/* 获取系统当前定时器的Tick(ms),加上本定时器周期,即定时器下次触发的时间 */
next_timeout_time = (u32_t)(sys_now() + msecs); /* overflow handled by TIME_LESS_THAN macro */
#if LWIP_DEBUG_TIMERNAMES
sys_timeout_abs(next_timeout_time, handler, arg, handler_name);
#else
sys_timeout_abs(next_timeout_time, handler, arg);
#endif
}
sys_timeouts_init来进行超时的初始化,其中会通过for循环来查询所有的定义过得超时事件;通过sys_timeout来进行处理,其中有超时时间,超时回调函数以及lwip_cyclic_timers[i]的地址;sys_timeout会在系统节拍的基础上,加上超时时间得到下一次的超时时间;sys_timeout中,计算得到next_timeout_time(即下一次的超时时间),就会调用sys_timeout_abs函数;该函数中会申请一个sys_timeo的结构体,然后对这个结构体进行初始化;
初始化完之后,对单向链表进行操作:首先插入表头,然后按照每一个超时时间进行排序并插入单链表中,最终顺序是从小到大进行排列。
超时事件删除
超时事件查询
/**
* @ingroup lwip_nosys
* Handle timeouts for NO_SYS==1 (i.e. without using
* tcpip_thread/sys_timeouts_mbox_fetch(). Uses sys_now() to call timeout
* handler functions when timeouts expire.
*
* Must be called periodically from your main loop.
*/
void
sys_check_timeouts(void)
{
u32_t now;
LWIP_ASSERT_CORE_LOCKED();
/* Process only timers expired at the start of the function. */
now = sys_now();
do {
struct sys_timeo *tmptimeout;
sys_timeout_handler handler;
void *arg;
/* 用于接收数据时Out of Sequence数据的释放 */
PBUF_CHECK_FREE_OOSEQ();
tmptimeout = next_timeout;
/* 如果定时器链表中没有成员,则返回 */
if (tmptimeout == NULL) {
return;
}
/* 如果时间最小的那个定时器都没有到期,则返回 */
if (TIME_LESS_THAN(now, tmptimeout->time)) {
return;
}
/* Timeout has expired */
/* 定时器到期:如果有多个定时器到期,将会一直循环直到上面return */
/* 将定时器指针指向下一个 */
next_timeout = tmptimeout->next;
/* 暂时保存当前到期定时器的回调函数及其参数 */
handler = tmptimeout->h;
arg = tmptimeout->arg;
/* 保存当前到期定时器的时间,前面提到在lwip_cyclic_timer中有用到 */
current_timeout_due_time = tmptimeout->time;
#if LWIP_DEBUG_TIMERNAMES
if (handler != NULL) {
LWIP_DEBUGF(TIMERS_DEBUG, ("sct calling h=%s t=%"U32_F" arg=%p\n",
tmptimeout->handler_name, sys_now() - tmptimeout->time, arg));
}
#endif /* LWIP_DEBUG_TIMERNAMES */
/* 释放到期定时器分配的内存 */
memp_free(MEMP_SYS_TIMEOUT, tmptimeout);
/* 调用到期定时器的回调函数 */
if (handler != NULL) {
handler(arg);
}
/* 可以用此回调函数来做一些事,如喂狗 */
LWIP_TCPIP_THREAD_ALIVE();
/* Repeat until all expired timers have been called */
} while (1);
}
tcpip_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg)函数 这个函数在 OS 线程中循环执行的,主要等待 mbox 消息并可阻塞,如果等待 mbox 时超时,则会同时执行超时事件处理,即调用超时回调函数,否则一直没有收到 mbox 消息就会一直 等 待 直 到 下 一 个 超 时 时 间 并 循 环 将 所 有 超 时 定 时 器 检 查 一 遍 ( 内 部 调 用 了 void sys_check_timeouts(void)),lwIP 中 tcpip 线程就是靠这种方法,即处理了上层及底层的 mbox消息,同时处理了所有需要定时处理的事件。
简约步骤总结如下:
首先LWIP在 tcpip_init(NULL, NULL)中调用sys_timeouts_init()实现超时事件初始化
接着开启tcpip_thread 线程,TCPIP_MBOX_FETCH调用tcpip_timeouts_mbox_fetc函数,该函数会等待mboxy。也就是等待消息的接收,然后,如果等待超时依旧没有接收到消息,最后就会触发超时回调函数sys_timeout(),触发相应的超时事件回调函数。