欢迎访问 生活随笔!

凯发k8官方网

当前位置: 凯发k8官方网 > 编程资源 > 编程问答 >内容正文

编程问答

lwip 之四 超时处理/定时器(timeouts.c/h) -凯发k8官方网

发布时间:2024/10/14 编程问答 8 豆豆
凯发k8官方网 收集整理的这篇文章主要介绍了 lwip 之四 超时处理/定时器(timeouts.c/h) 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

  目前,网络上多数文章所使用的lwip版本为1.4.1。最新版本为2.0.3。从1.4.1到2.0.3(貌似从2.0.0开始),lwip的源码有了一定的变化,甚至于源码的文件结构也不一样,内部的一些实现源文件也被更新和替换了。

  在lwip中很多时候都要用到超时处理,超时处理的实现是 tcp/ip 协议栈中一个重要部分。lwip为每个与外界网络连接的任务都有设定了 timeout 属性,即等待超时时间。超时处理的相关代码实现在timeouts.c/h中。
  在旧版本得lwip中,超时处理被称之为定时器,但是,在最新版的lwip中,原来的timer已经被删除,转而使用了timeouts来代替。实际的实现上也有一定的区别。
  在该文件中,针对有误操作系统(宏no_sys),对外提供的函数是不同的,下面看看这个文件的总结构。

/* 第一部分:定义lwip内部使用使用的循环定时器 */ const struct lwip_cyclic_timer lwip_cyclic_timers[]; /* 第二部分:各函数 */ #if lwip_timers && !lwip_timers_custom /* 使用 lwip提供的定时器 */ /* 对外提供的第一个函数*/ void tcp_timer_needed(void);void sys_timeouts_init(void); /* 初始化本模块 */void sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg) /* 注册函数 */#else /* 用户自定义定时器 */ void tcp_timer_needed(void) /* 必须由外部实现该函数 */ { } #endif

  在timeouts.c/h中,第一部分便是一个被称为lwip_cyclic_timer的结构。lwip使用该结构存放了其内部使用的循环定时器。这些定时器在lwip初始化时通过函数void sys_timeouts_init(void)调用定时器注册函数void sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg)注册进入超时管理链表中。在2.0.0之前的版本中,是没有该部分的。该部分被分开在了lwip内部,现在进行了统一处理。lwip_cyclic_timer的结构如下:

/** 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 functionthat has to be called at a defined interval */ struct lwip_cyclic_timer {u32_t interval_ms;lwip_cyclic_timer_handler handler; #if lwip_debug_timernamesconst char* handler_name; #endif /* lwip_debug_timernames */ };

  在timeouts.c中,有如下全局变量,存放了lwip内部使用的各定时器

/** 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 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 */ #endif /* lwip_ipv6 */ };

该部分通关函数void sys_timeouts_init(void);将内部使用的延时定时器注册进入链表中,如下图:

  超时定时器是按链表的形式进行组织的,且按时间长短进行排序,时间最短的永远在最前面。使用全局变量static struct sys_timeo *next_timeout;指示超时链表,该指针即为超时链表的头。超时链表需要通过函数void sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg)进行注册。链表的节点使用如下结构体表示:

/** 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_timernamesconst char* handler_name; #endif /* lwip_debug_timernames */ };

超时定时器注册

  下面详细分析一下void sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg)是如何对超时函数进行注册的。源码很简单。

u32_t now, diff;/* 1. 申请节点内存 */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;}/* 2.计算差值,至于为什么要额外搞个差值,暂时还没搞明白!!! */now = sys_now();if (next_timeout == null) {diff = 0;timeouts_last_time = now;} else {diff = now - timeouts_last_time;}/* 3. 节点各变量赋值 */timeout->next = null;timeout->h = handler;timeout->arg = arg;timeout->time = msecs diff; #if lwip_debug_timernamestimeout->handler_name = handler_name;lwip_debugf(timers_debug, ("sys_timeout: %p msecs=%"u32_f" handler=%s arg=%p\n",(void *)timeout, msecs, handler_name, (void *)arg)); #endif /* lwip_debug_timernames */ /* 4. 如果创建的是第一个定时器,则不用特殊处理,next_timeout是一个全局指针,指向定时器链表中第一个定时器 */if (next_timeout == null) {next_timeout = timeout;return;}/* 4. 从第二个定时器开始就要添加到链表中,添加原则是定时最短的定时器始终在前面。如果新添加的定时器时长小于当前链首定时器,则新添加的定时器成为链首,旧的链首定时器的定时值要减去新链首定时器定时值, 如果新添加的定时器大于等于当前链首定时器的时长,则要在整个链表里逐个比较, 最终插入到合适位置,当然其后定时器的定时值也要进行调整 */if (next_timeout->time > msecs) {next_timeout->time -= msecs;timeout->next = next_timeout;next_timeout = timeout;} else {for (t = next_timeout; t != null; t = t->next) {timeout->time -= t->time;if (t->next == null || t->next->time > timeout->time) {if (t->next != null) {t->next->time -= timeout->time;} else if (timeout->time > msecs) {/* if this is the case, 'timeouts_last_time' and 'now' differs too much.this can be due to sys_check_timeouts() not being called at the righttimes, but also when stopping in a breakpoint. anyway, let's assumethis is not wanted, so add the first timer's time instead of 'diff' */timeout->time = msecs next_timeout->time;}timeout->next = t->next;t->next = timeout;break;}}}

下面举例说明(由于在处理链表时,仅time 有用,因此下面的的其他参数用字母代替):

  • 插入第一个节点,参数为 time = 20, 其他两个参数用a、b表示
  • 插入第二个节点,参数为 time = 15, 其他两个参数用c、d表示,延时时间比之前的短
  • 插入第三个节点,参数为 time = 14, 其他两个参数用e、f表示,延时时间比之前的短。
  • 插入第四个节点,参数为 time = 30, 其他两个参数用g、h表示,延时时间比之前的长。
  • 插入第五个节点,参数为 time = 23, 其他两个参数用i、j表示,延时时间比之前的长。

      从上面的举例可以看出,如果新添加的定时器比头结点(next_timeout指向的第一个节点)的时间短,则直接往链表头插入,同时对头结点(next_timeout指向的第一个节点)的时间进行调整,后续节点的时间不动。
      如果新添加的定时器比头结点(next_timeout指向的第一个节点)的时间长,则需要遍历链表,查找合适位置(比其短的定时器之后,比其长的定时器之前)插入,其后定时器的定时值也要进行调整,其前的定时器无需调整。
  • 超时定时器删除

      从超时链表中删除指定的定时器时通过函数void sys_untimeout(sys_timeout_handler handler, void *arg)来完成的。源码很简单。

    void sys_untimeout(sys_timeout_handler handler, void *arg) {struct sys_timeo *prev_t, *t;/* 超时链表为空的判断 */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;}/* if not the last one, add time of this one back to next */if (t->next != null) {t->next->time = t->time;}memp_free(memp_sys_timeout, t); /* 释放节点资源 */return;}}return; }

    超时定时器检查

      不管是否有os支持,超时定时器都可以使用。lwip中如下两个函数可以实现对超时的处理:

    • void sys_check_timeouts(void):裸机应用程序在外部周期性调用该函数,每次进来检查定时器链表上定时最短的定时器是否到期,如果没有到期,直接退出该函数,否则,执行该定时器回调函数,并从链表上删除该定时器,然后继续检查下一个定时器,直到没有一个定时器到期退出。
    • void sys_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg):这个函数可在os线程中循环调用,主要是等待mbox消息,并可阻塞,如果等待mbox时超时,则会同时执行超时事件处理,即调用定时器回调函数,如果一直没有mbox消息,则会永久性地循环将所有超时定时器检查一遍(内部调用了void sys_check_timeouts(void)),一举两得。
        lwip中tcpip线程就是靠这种方法,即处理了上层及底层的mbox消息,同时处理了所有需要定时处理的事件。

    在检查超时定时器链表时,对于已经超时的则进行删除,下面以添加时的例子说明一下检查函数:

    • 第一次检查:
    • 第二次检查:
    • 第三次检查:

    还有部分没弄明白,后面更新!

    总结

    以上是凯发k8官方网为你收集整理的lwip 之四 超时处理/定时器(timeouts.c/h)的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得凯发k8官方网网站内容还不错,欢迎将凯发k8官方网推荐给好友。

    • 上一篇:
    • 下一篇:
    网站地图