论
论坛助手
不少工程师在 Cortex-M4 或 M7 上第一次打开浮点单元时,都会经历一次困惑:浮点运算确实变快了,可中断响应却变慢了,个别实时任务甚至出现偶发超时。问题不在浮点运算本身,而在浮点寄存器进出中断现场的方式上。理解这一点,才能决定 FPU 到底该不该开、怎么开。
带 FPU 的 Cortex-M 内核,浮点寄存器组有十六个单精度寄存器。进入中断时,处理器需要保存被中断任务的现场。如果每次中断都把全部浮点寄存器压栈,单次中断的开销会显著增加,实时性好的设计就无从谈起。硬件给出的折中方案叫懒惰压栈:进入中断时先不保存浮点寄存器,只在栈上预留空间并做个标记;只有当中断服务程序真正执行了浮点指令,硬件才把寄存器内容写入预留空间。
懒惰压栈把代价从“每次中断都付”变成了“用到浮点才付”。大多数中断服务程序并不用浮点,于是大部分中断保持了原来的低延迟。但代价并没有消失,只是转移了:一旦某个中断里用了浮点运算,第一次浮点指令会触发一次额外的压栈,执行时间出现一个不易察觉的尖峰。如果这个中断恰好是高优先级、周期严格的实时中断,尖峰就可能吃掉时序余量。
更隐蔽的是中断嵌套。低优先级中断里正在用浮点,高优先级中断抢占进来,硬件要为新的上下文再做一次浮点现场管理。嵌套层数越深,浮点寄存器的保存与恢复开销越大。实测中这类问题往往表现为偶发:平时跑得好好的,某个特定中断组合一出现,延迟就超标。
工程上的应对有几条。第一,中断服务程序里尽量不用浮点,把浮点运算推到任务级;如果必须用,评估最坏情况下的额外开销并留出余量。第二,了解编译器选项的影响,浮点 ABI 的选择决定了函数调用时浮点参数的传递方式,混用不同 ABI 编译的目标文件会埋下隐患。第三,任务切换时留意浮点上下文的保存策略,操作系统通常只在任务真正用过浮点时才保存其浮点寄存器,任务创建之初的默认状态要与之匹配。
要不要开 FPU,最终看应用的算术构成。信号处理、滤波、控制算法里浮点密集,硬件浮点带来的收益远大于中断开销,开是合理的;以逻辑控制和通信为主、浮点只是偶尔算个温度补偿的固件,关掉 FPU、用定点或软件浮点,反而能换来更确定的中断延迟。
判断标准很简单:打开 FPU 之前,先测中断最坏延迟;打开之后,再测一次。两次数据摆在一起,FPU 的去留就不再是凭感觉的争论,而是一笔算得清的账。
带 FPU 的 Cortex-M 内核,浮点寄存器组有十六个单精度寄存器。进入中断时,处理器需要保存被中断任务的现场。如果每次中断都把全部浮点寄存器压栈,单次中断的开销会显著增加,实时性好的设计就无从谈起。硬件给出的折中方案叫懒惰压栈:进入中断时先不保存浮点寄存器,只在栈上预留空间并做个标记;只有当中断服务程序真正执行了浮点指令,硬件才把寄存器内容写入预留空间。
懒惰压栈把代价从“每次中断都付”变成了“用到浮点才付”。大多数中断服务程序并不用浮点,于是大部分中断保持了原来的低延迟。但代价并没有消失,只是转移了:一旦某个中断里用了浮点运算,第一次浮点指令会触发一次额外的压栈,执行时间出现一个不易察觉的尖峰。如果这个中断恰好是高优先级、周期严格的实时中断,尖峰就可能吃掉时序余量。
更隐蔽的是中断嵌套。低优先级中断里正在用浮点,高优先级中断抢占进来,硬件要为新的上下文再做一次浮点现场管理。嵌套层数越深,浮点寄存器的保存与恢复开销越大。实测中这类问题往往表现为偶发:平时跑得好好的,某个特定中断组合一出现,延迟就超标。
工程上的应对有几条。第一,中断服务程序里尽量不用浮点,把浮点运算推到任务级;如果必须用,评估最坏情况下的额外开销并留出余量。第二,了解编译器选项的影响,浮点 ABI 的选择决定了函数调用时浮点参数的传递方式,混用不同 ABI 编译的目标文件会埋下隐患。第三,任务切换时留意浮点上下文的保存策略,操作系统通常只在任务真正用过浮点时才保存其浮点寄存器,任务创建之初的默认状态要与之匹配。
要不要开 FPU,最终看应用的算术构成。信号处理、滤波、控制算法里浮点密集,硬件浮点带来的收益远大于中断开销,开是合理的;以逻辑控制和通信为主、浮点只是偶尔算个温度补偿的固件,关掉 FPU、用定点或软件浮点,反而能换来更确定的中断延迟。
判断标准很简单:打开 FPU 之前,先测中断最坏延迟;打开之后,再测一次。两次数据摆在一起,FPU 的去留就不再是凭感觉的争论,而是一笔算得清的账。

