当前位置: 首页 > news >正文

一个经典嵌入式问题:如何安全读取 64 位计时器

在一个嵌入式系统中,需要读取一个持续自增的 64-bit 硬件计时器。

由于系统只能通过 32-bit MMIO register 访问该计时器,所以硬件把它拆成两个 32-bit 寄存器:

#define TIMER_LOW_ADDR 0x40001000 #define TIMER_HIGH_ADDR 0x40001010

其中:

TIMER_LOW 表示 timer[31:0] TIMER_HIGH 表示 timer[63:32]

计时器会持续递增。当低 32 位从:

0xFFFFFFFF -> 0x00000000

发生回绕时,高 32 位会加 1。

请实现:

uint64_t read_timer(void);

要求安全地返回一个一致的 64-bit timer 值。

这个题的考点主要是读取高低位时可能发生的race condition. 考虑以下情况:

硬件寄存器一开始的值比如
HIGH = 0x00000001
LOW = 0xffffffff

先读取HIGH: 读到 0x00000001

然后在读 LOW 之前,timer tick 了一次:

HIGH = 0x00000002
LOW = 0x00000000

再读LOW: 读到 0x00000000

如果直接拼:

0x00000001_00000000

这就是错的,因为真实时间已经至少是:

0x00000002_00000000

同样的,如果我们先读LOW: 读到 0xffffffff

然后在读 HIGH 之前,timer tick 了一次:

HIGH = 0x00000002
LOW = 0x00000000

再读HIGH: 读到 0x00000002

如果直接拼:

0x00000002_ffffffff

这也是错的,因为我们的LOW已经wrap, 应该读成:

0x00000002_00000000

解决核心是多读一次 HIGH,并比较两次 HIGH 的值。

如果 hi1 == hi2,说明在读取 LOW 的前后,HIGH 没有变化,也就是 LOW 没有发生 wrap-around 进位。因此 LOW 是在同一个 high epoch 下读取到的,可以安全拼接:

(hi1 << 32) | low

如果 hi1 != hi2,说明在两次读取 HIGH 之间,LOW 发生了 overflow,导致 HIGH 被更新。此时读到的 LOW 可能属于新的 high epoch,而第一次 HIGH 属于旧的 epoch,所以组合结果不一致,需要重新读取,直到两次 HIGH 相等。

为什么不多读一次 LOW?

因为 LOW 一直在变。多读 LOW 只能告诉你“时间过了”,但不能可靠判断有没有跨过0xffffffff -> 0x00000000这个边界。

真正有意义的标志是:HIGH 有没有变化。 因为 HIGH 只在 LOW wrap 时变化。

有了核心思路,实现就变得很简单。

#define TIMER_LOW_ADDR 0x40001000u #define TIMER_HIGH_ADDR 0x40001010u #define LOW32_REG (*(volatile uint32_t *)TIMER_LOW_ADDR) #define HIGH32_REG (*(volatile uint32_t *)TIMER_HIGH_ADDR) uint64_t read_timer(void) { uint32_t hi1, hi2, lo; do { hi1 = HIGH32_REG; lo = LOW32_REG; hi2 = HIGH32_REG; } while (hi1 != hi2); return (((uint64_t)hi1 << 32) | lo); }

代码的细节有几点要注意:

  1. 访问MMIO 寄存器

有了 MMIO memory address 后,需要把地址 cast 成对应宽度的指针:

(volatile uint32_t *)TIMER_LOW_ADDR

这里uint32_t *表示一次读取 32-bit;volatile表示这个地址背后的值可能被硬件随时改变,编译器不能缓存、合并或省略读取。

然后通过解引用读取寄存器值:

*(volatile uint32_t *)TIMER_LOW_ADDR

所以通常写成宏:

#define LOW32_REG (*(volatile uint32_t *)TIMER_LOW_ADDR)

2. 拼接 64-bit 结果

高 32 位要先 cast 成uint64_t,再左移 32 bit:

((uint64_t)hi1 << 32)

然后用 bitwise OR 拼接低 32 位:

return (((uint64_t)hi1 << 32) | lo);
http://www.jsqmd.com/news/839051/

相关文章:

  • Supabase 自建:开源的 Firebase 替代品,带数据库的后端服务
  • 5分钟掌握魔兽世界GSE宏编辑器:游戏操作效率提升300%
  • 互联网大厂 Java 求职面试:Spring Boot 构建微服务的挑战
  • AI冲击下程序员大批失业,为啥做网安反而越混越吃香?
  • 音乐标签管理终极革命:如何用3大黑科技拯救你的混乱音乐库?
  • CSS3 媒体查询完全指南:响应式设计的核心利器
  • Tensility电源连接器替代品牌与应用实践分析
  • Freqtrade开源量化交易框架:从策略开发到实盘部署全解析
  • AI智能体技能化架构:从模块化设计到工程化实践
  • Linux安全沙箱实战:基于seccomp与namespace隔离不可信程序
  • 3分钟搞定音乐库歌词:ZonyLrcToolsX让你的每首歌都有完美歌词
  • 开源RISC-V汽车芯片联盟:嵌入式开发者的机遇与挑战
  • 波粒互补性与信息双重性:论信息存储的离散性与传播的连续性之统一
  • Honey Select 2 HF Patch:一站式游戏增强与汉化终极指南
  • 如何为你的开源项目在GitHub Actions中集成Taotoken API
  • Godot 4高级运动系统:模块化设计实现丝滑3D角色移动
  • MASA Mods 中文汉化包:为Minecraft技术玩家消除语言障碍的专业解决方案
  • 第94篇:Vibe Coding时代:多语言项目 Agent 支持实战,解决只会 Python 无法处理真实混合技术栈的问题
  • ComfyUI ControlNet Aux终极指南:新手必学的图像预处理完整解决方案
  • 合肥大牌包包闲置出手|2026回收探店高价无套路 - 奢侈品回收测评
  • AzurLaneAutoScript:碧蓝航线智能自动化助手终极指南
  • AI时代哲学工作者必争的思维主权,NotebookLM辅助研究全链路拆解,含8个未公开Prompt工程技巧
  • 3分钟高效解密RPG游戏资源:浏览器端专业解密工具完全指南
  • Windows文件管理器终极图标扩展:3分钟让APK文件显示原生应用图标
  • 3个创意玩法:用Power BI主题模板解锁数据可视化隐藏技能
  • 基于本地大模型的RAG应用实战:从LangChain到Ollama的智能对话搭建
  • 涉密首选!2026降ai率工具推荐排行 涉密安全/双语适配/本地化部署 - 极欧测评
  • 【信息科学与工程学】计算机科学与自动化———第六十四篇 内存 系列一 内存算法05
  • 模块化设计与工程实践:从Advanced_Part看高质量可复用模块开发
  • STM32驱动段码屏实战:手把手教你用HT1621B做个简易电子钟(附完整代码)