[Bug] [libc] timer_gettime 拆秒前将总纳秒截断为 uint32_t,剩余时间查询错误

未关闭 适合新手
#11,924 7 条评论 0 个 reaction 已指派 1 人 在 GitHub 查看

维护者通常 1 天内回复

@Yunlk 已经在做这个了。

开始于 2026年10月24日。

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
68/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
c

调研方向

阅读 components/libc/compilers/common/ctime.c,特别是报告行号 1337–1375 附近的 timer_gettime(),并跟踪计数器剩余 tick 如何转换为纳秒。独立的算术示例展示了截断;请复现该示例,然后验证更改后剩余时间为 10 秒时的结果。保留对计数器回绕和中间乘法范围的检查,并确认 POSIX 定时器配置会启用此代码。

由索引模型根据 Issue 内容生成。

描述

in progress
RT-Thread Version

master, commit a59e72fb5c2f272761a2813fc9bd9fe18eb29d7d.

Affected area

libc / POSIX timers, components/libc/compilers/common/ctime.c, timer_gettime().

Hardware / Architecture / Toolchain

采用源码级算术隔离复现,不依赖特定开发板。该提交中的受影响实现由 RT_USING_POSIX_TIMER && RT_USING_CLOCK_TIME 启用。本报告尚未进行目标板复现。

问题描述 / Describe the bug

timer_gettime() 将总剩余纳秒存入 rt_uint32_t,之后才拆分秒和纳秒:

rt_uint32_t seconds, nanoseconds;
/* ... */
nanoseconds = ((remain_cnt - rt_clock_time_get_counter())
               * rt_clock_time_get_res_scaled()) / RT_CLOCK_TIME_RESMUL;
seconds     = nanoseconds / NANOSECOND_PER_SECOND;
nanoseconds = nanoseconds % NANOSECOND_PER_SECOND;

固定版本源码 L1337–1375

即使计数器表达式正确算出 10 秒,即 10,000,000,000 ns,赋给 32 位无符号变量时也会变成 1,410,065,408 ns。查询结果因此是 {tv_sec=1, tv_nsec=410065408},而不是 {10, 0}。

可运行的隔离复现 / Standalone arithmetic reproduction

下面明确模拟计数器换算已经正确得到总纳秒的情况,不调用宿主 libc 的 timer_gettime():

#include <assert.h>
#include <stdint.h>
#include <stdio.h>

int main(void)
{
    uint64_t converted_ns = UINT64_C(10000000000);
    uint32_t seconds, nanoseconds;

    nanoseconds = converted_ns;
    seconds = nanoseconds / 1000000000;
    nanoseconds %= 1000000000;

    printf("%u.%09u\n", (unsigned)seconds, (unsigned)nanoseconds);
    assert(seconds == 1 && nanoseconds == 410065408);
    return 0;
}

输出:1.410065408;预期剩余时间为 10.000000000。

此前还在本地旧版同一函数的隔离计数器桩中复现过该结果;最新上游同样保留上述窄化顺序。完整目标端验证仍待进行。

Scope / Suggested direction
  • 问题从总纳秒超过 UINT32_MAX(约 4.295 秒)起出现,不是几十年后的时间范围问题。
  • 这是剩余时间查询结果错误,不表示定时器一定提前或延后触发。
  • 后面的 (rt_int32_t)seconds 不是这里的根因,数据在拆秒之前已经丢失。
  • 建议总纳秒使用足够宽的类型,再拆分秒和纳秒;或在安全的单位下先拆秒、再换算余数。还应保留对计数器回绕及中间乘法范围的检查。
引入提交和版本 / Regression history
  • 引入提交:0eb75ced70,2023-07-17,[time]时钟框架重构 (#7794);首个包含该提交的正式 release 为 v5.0.2。
  • 此前使用 64 位 remaining 保存总纳秒,再分别赋值给秒和纳秒字段;该提交改为将总纳秒直接赋给 32 位 nanoseconds,然后再拆秒。
  • rt_uint32_t seconds, nanoseconds 的声明本身在更早实现中已经存在,但当时用于保存拆分后的字段,并非这次回归的来源。
  • 后续 743b614875 的时间子系统重构更换了计数接口名称,但保留了该窄化顺序。
其他实现参考 / Comparison

Linux 使用 64 位 ktime_t 转 timespec64;Zephyr 则先从剩余毫秒中拆秒,再将余数转换为纳秒。两种方式均避免这里的总纳秒 32 位中转。这不是对这些 OS 所有计时边界行为的保证。

主要语言
C
星标
12.3k
派生
5.5k
平均合并
4 天 9 小时
30 天内合并 PR
38

环境准备

在 Codespaces 中打开

在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

RT-Thread/rt-thread 的其他 Issue

查看 RT-Thread/rt-thread 的全部 Issue

相似的 Issue

更多 C Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。