我是靠谱客的博主 曾经小鸽子,这篇文章主要介绍LwIP源码分析(1):软件定时器,现在分享给大家,希望可以做个参考。

在LwIP中,实现了一个软件定时器,系统的超时重传、连接超时、Ping命令超时和IP数据报分段等操作都需要用到这个定时器,代码在timeouts.c中。它们都是调用了函数sys_timeout来添加一个定时器的,这里就从这个函数开始进行分析。

  • 在DEBUG模式下,可以打开宏LWIP_DEBUG_TIMERNAMES,这样定时器的数据结构、声明等都将变化;
  • 本文中的代码都假设LWIP_DEBUG_TIMERNAMES宏没有打开,然后关于参数合法性断言的代码都删除以更直观地分析代码
  • 操作系统:FreeRTOS & LwIP版本:2.2.0
复制代码
1
2
3
4
5
6
7
8
9
10
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进行初始化,具体见注释:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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默认初始化的一些定时器:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
调用时机: 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为一系列定时器间隔和回调函数的定义,根据用户设置的打开功能的宏定义进行声明,在我的项目中,打开了如下几个配置

复制代码
1
2
3
4
5
6
7
8
9
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,来看看这个函数:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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_timesys_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结构体,那么它是在哪里释放的呢?还有任务超时是在哪里判断的呢?

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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是在哪里被调用的:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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)内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(70)

评论列表共有 0 条评论

立即
投稿
返回
顶部