LWIP之软件定时器及超时处理

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(),触发相应的超时事件回调函数。

 

 

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值