概述
在LwIP中,实现了一个软件定时器,系统的超时重传、连接超时、Ping命令超时和IP数据报分段等操作都需要用到这个定时器,代码在timeouts.c
中。它们都是调用了函数sys_timeout
来添加一个定时器的,这里就从这个函数开始进行分析。
- 在DEBUG模式下,可以打开宏
LWIP_DEBUG_TIMERNAMES
,这样定时器的数据结构、声明等都将变化; - 本文中的代码都假设
LWIP_DEBUG_TIMERNAMES
宏没有打开,然后关于参数合法性断言的代码都删除以更直观地分析代码 - 操作系统:FreeRTOS & LwIP版本:2.2.0
typedef void (* sys_timeout_handler)(void *arg);
/* handler为回调函数,arg为回调函数参数 */
void sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg)
{
u32_t next_timeout_time;
/* 获取系统当前定时器的Tick(ms),加上本定时器周期,即定时器下次触发的时间 */
next_timeout_time = (u32_t)(sys_now() + msecs);
sys_timeout_abs(next_timeout_time, handler, arg);
}
可以看到,该函数最终调用sys_timeout_abs
进行初始化,具体见注释:
struct sys_timeo {
struct sys_timeo *next; /* 指向下一个定时器结构体 */
u32_t time; /* 超时时间 */
sys_timeout_handler h; /* 回调函数 */
void *arg; /* 回调函数参数 */
};
static struct sys_timeo *next_timeout;
#define LWIP_MAX_TIMEOUT 0x7fffffff
#define TIME_LESS_THAN(t, compare_to) ( (((u32_t)((t)-(compare_to))) > LWIP_MAX_TIMEOUT) ? 1 : 0 )
static void sys_timeout_abs(u32_t abs_time, sys_timeout_handler handler, void *arg)
{
struct sys_timeo *timeout, *t;
/* 分配一个定时器的结构体 */
timeout = (struct sys_timeo *)memp_malloc(MEMP_SYS_TIMEOUT);
if (timeout == NULL) {
return;
}
/* 填入定时器参数 */
timeout->next = NULL;
timeout->h = handler;
timeout->arg = arg;
timeout->time = abs_time;
/* 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;
}
}
}
}
对于TIME_LESS_THAN
来说,是使用uint32_t
判断两个时间的大小的:
- 比如对于0和
0xffffffff
来说,0更大,因为0xffffffff+1=0
- 这里用
0x7fffffff
作判断,表明定时器的周期不能大于0xffffffff-0x7fffffff=0x80000000
好了,这样就将定时器加入到next_timeout
中了。现在还有两个问题:
(1)sys_timeout
在哪被调用?
通过搜索可以发现,在各个具体的文件中,比如使能了ppp就要添加一个timer,比如使能了Ping
功能,就要添加一个timer。
这里不对具体的某个功能开启的定时器进行分析,我们来看看timeouts.c
默认初始化的一些定时器:
调用时机:
tcpip_init
lwip_init
sys_timeouts_init
void sys_timeouts_init(void)
{
size_t i;
for (i = (LWIP_TCP ? 1 : 0); i < LWIP_ARRAYSIZE(lwip_cyclic_timers); i++) {
sys_timeout(lwip_cyclic_timers[i].interval_ms, lwip_cyclic_timer, LWIP_CONST_CAST(void *, &lwip_cyclic_timers[i]));
}
}
LWIP_TCP
为数组lwip_cyclic_timers
第一个,其运行tcp_tmr
。这里LWIP_TCP为1时
反而不初始化它,因为tcp_tmr
会在tcp_timer_needed
初始化的tcpip_tcp_timer
中调用。
其中lwip_cyclic_timers
为一系列定时器间隔和回调函数的定义,根据用户设置的打开功能的宏定义进行声明,在我的项目中,打开了如下几个配置
const struct lwip_cyclic_timer lwip_cyclic_timers[] = {
{TCP_TMR_INTERVAL, HANDLER(tcp_tmr)},
{IP_TMR_INTERVAL, HANDLER(ip_reass_tmr)},
{ARP_TMR_INTERVAL, HANDLER(etharp_tmr)},
{DHCP_COARSE_TIMER_MSECS, HANDLER(dhcp_coarse_tmr)},
{DHCP_FINE_TIMER_MSECS, HANDLER(dhcp_fine_tmr)},
{ACD_TMR_INTERVAL, HANDLER(acd_tmr)},
};
可以看到这些配置的回调函数都设置为lwip_cyclic_timer
,来看看这个函数:
static u32_t current_timeout_due_time;
static 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;
/* 执行lwip_cyclic_timers中第二个参数的函数 */
cyclic->handler();
/* 根据系统当前时间, */
now = sys_now();
next_timeout_time = (u32_t)(current_timeout_due_time + cyclic->interval_ms);
if (TIME_LESS_THAN(next_timeout_time, now)) {
sys_timeout_abs((u32_t)(now + cyclic->interval_ms), lwip_cyclic_timer, arg);
} else {
sys_timeout_abs(next_timeout_time, lwip_cyclic_timer, arg);
}
}
- 实际上就是
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
(2)next_timeout
在哪里遍历,或者说某个任务超时的时候回调函数在哪被调用?
我们发现在lwip_cyclic_timer
中又调用了sys_timeout_abs
,而在sys_timeout_abs
中每次都分配了一个sys_timeo
结构体,那么它是在哪里释放的呢?还有任务超时是在哪里判断的呢?
void sys_check_timeouts(void)
{
u32_t now;
/* 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;
}
/* 定时器到期:如果有多个定时器到期,将会一直循环直到上面return */
/* 将定时器指针指向下一个 */
next_timeout = tmptimeout->next;
/* 暂时保存当前到期定时器的回调函数及其参数 */
handler = tmptimeout->h;
arg = tmptimeout->arg;
/* 保存当前到期定时器的时间,前面提到在lwip_cyclic_timer中有用到 */
current_timeout_due_time = tmptimeout->time;
/* 释放到期定时器分配的内存 */
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);
}
最后我们想知道sys_check_timeouts
是在哪里被调用的:
tcpip_thread
TCPIP_MBOX_FETCH
-----------
#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:
/* 计算下一个到期的定时器的时间和当前时间的差 */
sleeptime = sys_timeouts_sleeptime();
if (sleeptime == SYS_TIMEOUTS_SLEEPTIME_INFINITE) {
/* 系统中没有的定时器 */
UNLOCK_TCPIP_CORE();
/* 阻塞等待mbox的消息:若阻塞的时候有新的定时器创建,
* 是否也会有一个mbox消息,否则一直阻塞?(todo:后续阅读相关代码) */
sys_arch_mbox_fetch(mbox, msg, 0);
LOCK_TCPIP_CORE();
return;
} else if (sleeptime == 0) {
/* 有定时器已经到期 */
sys_check_timeouts();
/* 回去重新检查是否有多个定时器同时到期 */
goto again;
}
/* 没有定时器到期:这段时间(sleeptime)来检查mbox有没有消息 */
UNLOCK_TCPIP_CORE();
res = sys_arch_mbox_fetch(mbox, msg, sleeptime);
LOCK_TCPIP_CORE();
if (res == SYS_ARCH_TIMEOUT) {
/* 等待sleeptime后还是没有mbox到期,此时肯定有定时器到期,去检查 */
sys_check_timeouts();
/* 再回去检查mbox中是否有数据 */
goto again;
}
}
tcpip_thread
为处理tcpip消息创建的一个任务tcpip_timeouts_mbox_fetch
的作用检查系统timeout的定时器,因为下一个定时器到期的时间是确定的,所以在这个间隔内可以等待mbox的消息。- 由于
tcpip_thread
中需要tcpip_timeouts_mbox_fetch
中返回一个msg去处理,所以该函数的退出时机是等到了mbox中的消息。
最后
以上就是曾经小鸽子为你收集整理的LwIP源码分析(1):软件定时器的全部内容,希望文章能够帮你解决LwIP源码分析(1):软件定时器所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复