一、目的
在TCP/IP协议栈中ARP缓存的更新、IP数据包的重组、TCP的连接超时和超时重传等都需要超时处理模块(软件定时器)的参与。
本篇主要介绍LwIP中超时处理的实现细节。
上图为超时定时器链表,升序排序,其中next_timeout为链表头,指向超时定时器中的第一个定时器。
二、介绍
在介绍超时处理之前,我们先了解一下LwIP中与超时处理相关的数据结构
涉及的文件
timeouts.h
timeouts.c
相关宏
/** Returned by sys_timeouts_sleeptime() to indicate there is no timer, so we
* can sleep forever.
*/
#define SYS_TIMEOUTS_SLEEPTIME_INFINITE 0xFFFFFFFF
用于指示当前没有超时定时器链表为空
超时定时器实现
/** Function prototype for a timeout callback function. Register such a function
* using sys_timeout().
*
* @param arg Additional argument to pass to the function - set up by sys_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_TIMERNAMES
const char* handler_name;
#endif /* LWIP_DEBUG_TIMERNAMES */
};
各个字段含义:
next:下一个定时器指针
time:超时时间或者说是超时时刻,注意此处是绝对时间(即在此时刻超时事件发生)
h:超时回调函数
arg:回调函数入参
handler_name:超时事件描述(定义LWIP_DEBUG_TIMERNAMES宏时才定义)
因为LwIP内核中有各种超时事件,为了统一管理这些超时事件,故将各个超时事件定时器通过单链表的形式管理起来。
/** The one and only timeout list */
static struct sys_timeo *next_timeout;
定义超时定时器的链表头部
定时机制
一般实现定时器时我们可以使用相对时间或者绝对时间来表示;LwIP中的超时机制采用绝对时间的方式实现。
系统中有个tick计数器,例如每1ms,tick值增加一,当tick达到最大值时,重新从0开始计数。
假设用于记录绝对时间的数据类型为uint8_t,从0开始计数一直到255,然后再次从0开始。
假设当前时刻为255,那么如果设置定时时间12,那么255 + 12无符号数相加后的值为12,也就是超时的绝对时刻为12,当系统时间运行到12时,代表时间超时,此时需要处理超时函数。
创建超时定时器
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));
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_timeout_abs函数创建定时器
注意:超时时间不能超过LWIP_UINT32_MAX/4毫秒
#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 */
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;
}
}
}
}
①:从MEMP_SYS_TIMEOUT内存池中分配内存池结构
②:设置各个参数
③:超时链表如果为空,代表当前是第一个定时器
④:如果当前新创建的定时器超时时间比链表头部的第一个时间小,则将新创建的定时器放在头部,并更新next_timeout指针
⑤:遍历定时器列表
⑥:将新创建的定时器插入在正确的位置上
采用升序进行排序
删除定时器
/**
* Go through timeout list (for this task only) and remove the first matching
* entry (subsequent entries remain untouched), even though the timeout has not
* triggered yet.
*
* @param handler callback function that would be called by the timeout
* @param arg callback argument that would be passed to handler
*/
void
sys_untimeout(sys_timeout_handler handler, void *arg)
{
struct sys_timeo *prev_t, *t;
LWIP_ASSERT_CORE_LOCKED(); //①
if (next_timeout == NULL) {
return;
}
for (t = next_timeout, prev_t = NULL; t != NULL; prev_t = t, t = t->next) { //②
if ((t->h == handler) && (t->arg == arg)) {
/* We have a match */
/* Unlink from previous in list */
if (prev_t == NULL) {
next_timeout = t->next;
} else {
prev_t->next = t->next;
}
memp_free(MEMP_SYS_TIMEOUT, t); //③
return;
}
}
return;
}
①:检查互斥锁是否上锁,避免多线程问题
②:遍历超时链表找到匹配的定时器
③:将定时器占用的内存释放
周期定时器
在LwIP协议栈内部有些模块需要周期性定时,但是sys_timeo只是一个单次定时结构,为了实现周期性定时的功能,在此基础上定义了周期定时器
/** Function prototype for a stack-internal timer function that has to be
* called at a defined interval */
typedef void (* lwip_cyclic_timer_handler)(void);
/** This struct contains information about a stack-internal timer function
that has to be called at a defined interval */
struct lwip_cyclic_timer {
u32_t interval_ms;
lwip_cyclic_timer_handler handler;
#if LWIP_DEBUG_TIMERNAMES
const char* handler_name;
#endif /* LWIP_DEBUG_TIMERNAMES */
};
各个字段含义:
interval_ms:定时周期,单位ms
handler:回调函数
handler_name:超时事件描述(定义LWIP_DEBUG_TIMERNAMES宏时才定义)
系统定义的周期定时器列表
/** This array contains all stack-internal cyclic timers. To get the number of
* timers, use LWIP_ARRAYSIZE() */
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 and
is 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_ACD
{ACD_TMR_INTERVAL, HANDLER(acd_tmr)},
#endif /* LWIP_ACD */
#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 */
};
const int lwip_num_cyclic_timers = LWIP_ARRAYSIZE(lwip_cyclic_timers);
内部周期定时器组的初始化
/** 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]));
}
}
周期定时器通过回调函数lwip_cyclic_timer将定时器再次插入到定时器列表中
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
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
}
}
定时器的触发(超时检查)
/** Return the time left before the next timeout is due. If no timeouts are
* enqueued, returns 0xffffffff
*/
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;
} else {
u32_t ret = (u32_t)(next_timeout->time - now);
LWIP_ASSERT("invalid sleeptime", ret <= LWIP_MAX_TIMEOUT);
return ret;
}
}
①:检查互斥锁是否上锁,避免多线程问题
②:检查是否有定时器存在,没有定时器存在则返回SYS_TIMEOUTS_SLEEPTIME_INFINITE
③:检查下一次超时时间,如果已经超时返回0;否则超时的差值(比如当前时刻为20,超时时刻为25,那么距离超时的值为5)
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;
}
}
此tcpip_timeouts_mbox_fetch函数在tcpip_thread线程中被执行
①:检查互斥锁是否上锁,避免多线程问题
②:获取距离下一次超时的差值
③:没有定时器,则阻塞等待邮箱消息
④:已经有定时器超时,则调用sys_check_timeouts进行处理
⑤:超时等待消息邮箱进行处理
⑥:在邮箱超时或者有消息时再次检查定时器是否有超时事件需要处理
/**
* @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;
PBUF_CHECK_FREE_OOSEQ(); //①
tmptimeout = next_timeout;
if (tmptimeout == NULL) { //②
return;
}
if (TIME_LESS_THAN(now, tmptimeout->time)) { //③
return;
}
/* Timeout has expired */
next_timeout = tmptimeout->next;
handler = tmptimeout->h;
arg = tmptimeout->arg;
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);
}
①:检查互斥锁是否上锁,避免多线程问题
②:检查是否超时定时器链表是否为空
③:检查当前时刻是否小于定时器的超时时刻,如果大于则说明有超时事件需要处理
④:更新超时定时器链表
⑤:将当前超时的定时器释放
⑥:执行当前定时器的回调函数