惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

嵌入式工程猫的博客

使用树莓派 4 和 Moonlight 串流游戏的实践 维护 Nginx 时,什么时候应该用 reload,什么时候应该用 restart? 批量修改 qbittorrent-nox 内种子的 tracker 地址 把 vim 的缩进设为 4 个字符,并且 tab 自动转空格 不用 snap,在 Ubuntu 上安装 certbot 在 Ubuntu 中启用 swap 让 Nginx 反向代理的程序获取客户端真实 IP 在 Linux 中显示所有正在监听的 TCP 端口 再一次理解 C++ 中的 extern "C" 航模舵机控制及其 PWM 调制的进一步理解 如何自签名带 SAN 字段的 SSL/TLS 证书 深入理解以太网网线原理 FreeBSD vs Linux:哪个开源操作系统更强大 - 嵌入式工程猫的博客 如何在 Markdown 中修改字体颜色 - 嵌入式工程猫的博客 如何在 ESP8266 上选用合适的引脚 如何 DIY 一个苏康码与行程码“双码合一”的健康码 APP 随便聊聊最近在本站折腾的那些东西 - 嵌入式工程猫的博客 分享一下我的家庭网络布局 Mapuino - 一个硬件极客风的 WEB 访客地图显示摆件 Topuino - 你愿意在办公桌上放一个监控服务器的小摆件吗? - 嵌入式工程猫的博客
我两周就写了三行代码 - ARM Cortex A9 中断与浮点数运算、FPU 问题
2022-03-08 · via 嵌入式工程猫的博客

问题出现

公司产品采用了 Xilinx Zynq 7z010 芯片,用于运动控制以及网络通讯。两周前,测试过程中发现网络通信会小概率出错,TCP 收到的数据 CRC 校验失败,无法稳定复现。

设备平台概述:

  1. CPU: Cortex-A9 双核
  2. RAM: 1GB DDR3
  3. 操作系统: FreeRTOS
  4. 网络协议栈: lwip211

定位过程

怀疑应用层数据处理问题

TCP 是二进制数据流,每个包的长度不固定,应用层也许会写错。于是我修改了应用层的处理方案,手动构造了定长的数据包,虽然会导致 TCP 流量大幅上涨,但是逻辑看起来更清晰。

然而,修改后,似乎由于流量变大了,原来小概率出现的错误,现在大概率会出现!这也给 Debug 带来了有利的一面。

怀疑网络通讯链路电磁干扰问题

但是这个怀疑方向很快就被否定了,因为我用了 TCP 协议,理论上只可能超时,不可能出错。

怀疑 lwip 接口调用问题

lwip 有多个 TCP API,之前用的 Socket API,我尝试换成了 RAW API,但是问题依旧。

在调试的过程中,我尝试在网络链路的每一层数据打印出来,惊奇地发现,在数据链路层,数据是正确的!然而 lwip 的代码冗杂且数 MB 数据中才会出现几个错误位,于是我暂时没有考虑一层层分析代码。

怀疑与其他线程之间存在干扰,或者存在数组越界访问

这样 Debug 就很简单了。我关闭了所有其他的线程,不出所料,Bug 消失了。

一点点放开线程,发现是一个运动控制的硬中断造成的 Bug。

然后再“二分法”排除代码,结果排除到最后,仅仅是一行代码:

1
2

long long a = (long long)((double)b * (double)c);

这让我大跌眼镜,因为实验证明,把这句话删了,TCP 通讯就正常了。

怀疑是浮点运算的问题

更加让我迷惑的是,把上述语句改下,同样也没问题了:

1
long long a = b * c;

进一步定位:我在另一个线程中添加了浮点运算,并把这个有影响的中断关闭,TCP 通讯同样出问题了。

就此,几乎可以确定是浮点数运算造成的问题了。

问题小结

一句话描述问题:在中断或某个线程中进行浮点数操作,会导致另一个 TCP 通讯线程数据出错。

说实话,我当时也没法理解其中的联系。

只不过我们用的芯片自带双精度 FPU(浮点运算单元),也许是 FPU 的问题?

解决过程

查找资料

关键词 lwip tcp receive wrong datazynq float process corrupt memory 等关键词,都没有找到有价值的解决方案。

求助 Xilinx 技术支持

果然用微信联系的技术支持不靠谱,上午说帮忙复现,下午就没信了。

求助朋友圈资深开发者

只可惜他们都是互联网界的大佬,只有我在嵌入式开发领域摸爬滚打,他山之玉难以攻石。

求助 V2EX 网友

发了帖子 在这里

V 站网友给了非常有价值的线索:

  1. 网友 A 称他们使用同样的平台出现过类似的问题。他们的解决方案是,进行浮点数操作之前,关闭所有的中断;
  2. 网友 B 分析可能 正在计算浮点数的时候,刚好发生了 systick 线程切换,但是线程切换过程中,没有保存 /恢复浮点寄存器
  3. 网友 C 更是找到了相关文章:

    “Some GCC libraries optimise memory copy and memory set (and possibly other) functions by making use of the wide floating point registers. Therefore, by default, any task that uses functions such as memcpy(), memcmp() or memset(), or uses a FreeRTOS API function such as xQueueSend() which itself uses memcpy(), will inadvertently corrupt the floating point registers.”

真可谓一针见血,TCP 协议栈中大量使用了 memcpy,而 memcpy 又使用了 FPU 的寄存器,极有可能在 TCP 处理数据的过程中,另一个中断来了,进行了浮点运算并修改了 FPU 的寄存器,以致 TCP 数据出错。

同样根据网友的指点,看了这篇文章 Using FreeRTOS on ARM Cortex-A9 Embedded Processors,原来 FreeRTOS 自身已经考虑了 FPU 与上下文切换相关的问题,只是要我们将 configUSE_TASK_FPU_SUPPORT 这个宏定义为 2 即可。

问题解决

花了些时间进行 FPU 寄存器相关的搜索,依照 这篇文章 ,对 FPU 的寄存器做了相关处理,总结起来就三行代码:

第一行代码

在中断响应函数开头添加以下代码:

1
__asm("VPUSH {d0-d15}"); 

第二行代码

在中断响应函数末尾添加以下代码:

1
__asm("VPOP {d0-d15}"); 

第三行代码

FreeRTOS 启用 FPU 支持相关宏:

1
#define configUSE_TASK_FPU_SUPPORT 2

至此,问题解决。