zl程序教程

您现在的位置是:首页 >  其他

当前栏目

LwIP系列--软件定时器(超时处理)详解

软件 详解 处理 -- 系列 超时 定时器 LwIP
2023-09-11 14:21:44 时间

一、目的

在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

用于指示当前超时定时器链表为空,也就没有超时定时器需要处理

/* Check if timer's expiry time is greater than time and care about u32_t wraparounds */
#define TIME_LESS_THAN(t, compare_to) ( (((u32_t)((t)-(compare_to))) > LWIP_MAX_TIMEOUT) ? 1 : 0 )

用于比较t是否小于compare_to;这边需要注意的是u32_t的溢出问题;该宏主要用于比较两个定时器哪个先触发


超时定时器实现

/** 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宏时才定义)

注意sys_timeo描述的是单次触发定时器,如果需要周期定时器,一般做法在超时处理中再次添加此定时器到超时链表中

因为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无符号数相加后的值为11(即mod 256之后的值),也就是超时的绝对时刻为11,当系统时间运行到11时,代表时间超时,此时需要处理超时函数。

依照上图,如果当前时刻在[0, 12]之间,那么第一个触发的定时器就是12;如果当前时刻在(132, 255]之间,那么第一个触发的定时器就是255,最后触发的定时器就是132。

创建超时定时器

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);
}

①:检查互斥锁是否上锁,避免多线程问题

②:检查是否超时定时器链表是否为空

③:检查当前时刻是否小于定时器的超时时刻,如果大于则说明有超时事件需要处理

④:更新超时定时器链表

⑤:将当前超时的定时器释放

⑥:执行当前定时器的回调函数