RT-Thread论坛
直播中

颜立歆

9年用户 1033经验值
擅长:控制/MCU
私信 关注
[问答]

rtthread 4.1.1 lwip 2.1.2 由于系统计数溢出导致的发送超时如何处理?

在看基于rtthread的lwip源码时,发现有这样的代码:

  if ((conn->send_timeout != 0) &&
     ((s32_t)(sys_now() - conn->current_msg->msg.w.time_started) >= conn->send_timeout))   {
    write_finished = 1;
    if (conn->current_msg->msg.w.offset == 0) {
      /* nothing has been written */
      err = ERR_WOULDBLOCK;
    } else {
      /* partial write */
      err = ERR_OK;
    }
  }
当系统计数器溢出时,不是会导致退出超时么?有什么处理比较好的处理方法?

回帖(1)

梁宏满

2025-6-19 18:03:42

在 RT-Thread 4.1.1 和 lwIP 2.1.2 中,处理系统时间计数溢出(sys_now() 返回的 32 位毫秒时间溢出周期约 49.7 天)导致的发送超时问题,代码中已通过 有符号时间差比较 的方式规避。以下是分析和解决方案:


关键代码分析


if ((conn->send_timeout != 0) &&
    ((s32_t)(sys_now() - conn->current_msg->msg.w.time_started) >= conn->send_timeout))
{
    // 超时处理逻辑
}



  • 机制

    sys_now() - time_started 的结果被强制转换为 s32_t(有符号 32 位整数)。当时间差在 [0, 0x7FFFFFFF] 毫秒(约 24.85 天)内时,计算结果正确;超过此范围时,因有符号整数溢出变成负数(<0),但比较条件(>= timeout)自然不成立,不会误判为超时




  • 合理性

    网络操作的超时时间(conn->send_timeout)通常为秒级(如 30 秒、2 分钟),远小于 24.85 天。因此,在时间溢出周期内:



    • 正常超时:时间差在 24.85 天内,计算正确。

    • 避免误判:时间差超 24.85 天(如系统启动后首次溢出时),计算结果为负,条件不成立,不会误触发超时。




潜在问题及优化建议




  1. 超时时间过长



    • send_timeout 设置 ≥ 24.85 天(极少见),系统时间溢出后,实际未超时但可能被延迟判定。

    • 建议:避免设置过大的超时值(例如不超过 1 天)。网络操作应使用合理的超时(秒/分钟级)。




  2. 无符号时间差比较(错误示例)


    // 错误:若 sys_now() 溢出回绕,将导致超大正数差,错误触发超时
    if ((u32_t)(sys_now() - time_started) > timeout)  // 不要使用这种方式


    • 确保代码使用 s32_t 转换 进行有符号比较(如您提供的代码)。




  3. 连接持续等待问题



    • 当时间差 > 24.85 天时,因有符号差为负,连接可能长期等待而不超时。

    • 解决方案:定期重置起始时间(time_started),避免操作跨越多个溢出周期。
      // 在发送循环中添加检查:若等待超过安全阈值(如 24小时),重置时间
      #define SAFE_TIME_INTERVAL (24 * 60 * 60 * 1000) // 24小时(毫秒)
      u32_t now = sys_now();
      s32_t diff = (s32_t)(now - time_started);
      if (diff < 0) { // 发生溢出回绕
      // 计算真实无符号差值(规避有符号溢出的负值)
      u32_t actual_diff = (0xFFFFFFFFU - time_started) + now + 1;
      if (actual_diff > SAFE_TIME_INTERVAL) {
           // 重置起始时间,防止永久等待
           conn->current_msg->msg.w.time_started = now;
      }
      }




推荐处理策略




  1. 保持当前逻辑

    现有 s32_t 比较在常规场景下安全有效,无需修改核心逻辑。




  2. 优化边界问题



    • 添加安全重置机制(如上示例),防止连接在溢出后永久等待。

    • 限制超时上限
      // 设置超时时,限制在安全范围内(如 24 小时)
      #define MAX_SEND_TIMEOUT (24 * 60 * 60 * 1000)
      if (timeout > MAX_SEND_TIMEOUT) {
      timeout = MAX_SEND_TIMEOUT;
      }
      conn->send_timeout = timeout;




  3. 系统级改进

    若平台支持,将 sys_now() 升级为 64 位时间戳(需修改 RT-Thread 时钟源),彻底消除溢出问题。




总结



  • 当前代码安全:依赖 s32_t 比较的设计合理规避了时间溢出误判。

  • 增强健壮性:通过 重置长时间等待的连接起始时间 + 限制超时最大值,可解决潜在连接冻结问题。

  • 彻底方案:升级为 64 位时间戳(如 rt_tick_get() 扩展为 64 位或使用 rt_tick_t 计算差值)。


建议优先在应用层添加超时上限和溢出后的时间重置,确保在长期运行系统中稳定处理网络超时。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分