"); //-->
By Toradex Vitor Soares
起初只是关于 linux-serial-test(一款用于通过发送和接收数据流对串行端口进行压力测试的开源工具)的一份简单问题报告,却演变成了一个涉及回归分析、交叉编译的差异、glibc 版本不兼容以及更底层的 POSIX 合规性问题等多层面的调试挑战。该工具被用于我们的持续集成(CI)管道中,以验证 Toradex 模块上的 UART 功能;但在多台设备上报告出现故障后,这促使我们对整个工具链展开了更广泛的调查。
这是一个关于我们 master build 中突发故障(起初怀疑是内核回归)的故事:它将我们引入了优化迷局,然后遭遇了工具链问题,最终发现了一个显而易见却被忽视的POSIX合规性违规问题。

问题
由于多个设备上都出现了故障,最初怀疑是最近的一次上游提交。上游提交 77320571e63c (“检查端口上是否已配置 RS485”)引入了一项出于良好意图的更改:优先使用通过设备树在端口上已配置的 RS485 设置。
然而,它却违背了一个基本假设: 应始终尊重用户的命令行意图.
if (rs485.flags & SER_RS485_ENABLED) {
printf("RS485 already enabled on port, ignoring delays if set\n");
} else {
// Configure RS485 based on user flags
}为什么会出问题
用户无法实现下面操作:
如果 RS485 此前已被启用,则将其禁用
更改延迟设置
运行 UART 环回测试(此操作需要禁用 RS485)
正确测试流量控制
教训一:优先考虑用户意图,而非设备配置。

if (_cl_rs485) {
/* User explicitly wants RS485 */
if ((_cl_rs485_after_delay < 0) && (rs485.flags & SER_RS485_ENABLED)) {
printf("RS485 already enabled on port\n");
} else {
/* Configure RS485 with user settings */
// ... configuration code ...
}
} else {
/* User wants RS485 disabled - ALWAYS disable */
rs485.flags &= ~(SER_RS485_ENABLED | ...);
// ... disable code ...
}这效果非常理想。测试全部通过。一切都很顺利。
但接着出现了 第二个问题.
相同的源代码,在本地使用 aarch64-none-linux-gnu-gcc 编译时运行正常。但在通过 OpenEmbedded(OE)构建时,-c 标志(RTS/CTS 流程控制) 在无提示的情况下失效了:
tx=272, rx=0 ← Data sent but not received!
误区一:加固标志
-D_FORTIFY_SOURCE=2 -fstack-protector-strong -mbranch-protection=standard
我们已将其禁用,但仍然失败了。
误区二:编译优化级别
OE: -O2 Local: default
我们改成了 -O0,但仍然失败。
误区三:PIE(Position Independent Executable)
Local: "executable" (no PIE) OE: "pie executable" (PIE enabled)
我们通过以下方式禁用了 PIE: -fno-pie -no-pie. 还是失败了。
教训: 不要只治症状,而要找出根本原因。
最后,我们对比了正常运行和无法运行的二进制文件中的实际符号:
nm ./linux-serial-test-local | sort > local.sym nm ./linux-serial-test-oe | sort > oe.sym diff local.sym oe.sym
太棒了!发现区别:
Local (works): - strtol (C99) - cxa_finalize@GLIBC_2.17 OE (fails): - __isoc23_strtol (ISO C23!) - __wrap_main - Missing cxa_finalize
OE 工具链在编译的开启了 支持 ISO C23,从而改变了运行时的基本行为。
我们在本地构建了 OE SDK,以重现 CI 环境:
bitbake -c populate_sdk core-image-minimal source /opt/sdk/environment-setup-aarch64-oe-linux
现在我们能够重现该故障了, 本地 使用相同的工具链。这一点至关重要,它证明了问题出在工具链上,而非 OE 构建基础设施中。
由于符号分析指向了 glibc 方面的问题,我们需要了解运行时究竟发生了什么。此时,strace 就显得尤为重要。
strace linux-serial-test -o2 -i3 -b 115200 -p /dev/verdin-uart1 -c -a10 -w16
我们的发现:
ioctl(3, TCSETS2,
{c_iflag=, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|,
c_cflag=B115200|B115200<<IBSHIFT|CS8|CREAD|CLOCAL|CRTSCTS,
c_lflag=,
...}) = 0ioctl(3, TCSETS2,
{c_iflag=, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|,
c_cflag=B0|B50<<IBSHIFT|CS8|CREAD|PARODD|CLOCAL|CRTSCTS|0xc000, ← WRONG DATA!
c_lflag=,
...}) = 0关键点: 写操作正常进行,但读取操作返回的是乱码或空值。这将问题锁定在串口配置步骤上,具体而言是 TCSETS2 ioctl 操作。
我们可以看到实际的 termios 结构被设置。波特率字段(在使用旧方法时通常包含在 c_cflag 中)并未被正确设置。
这证实了我们的假设:旧代码 newtio.c_cflag = baud | CS8 | CLOCAL | CREAD 在新的 glibc 环境下会悄无声息地失败,而串口使用了默认但错误的波特率设置,导致 RTS/CTS 时序出现问题。
经过大量排查、对比头文件和运行时行为,尤其是利用 strace 观察实际发生了什么,我们终于找到了问题所在: glibc 2.42 及更高版本中 termios 波特率的处理方式发生了变化.
其根本原因在于 该提交 ,它在 termios 中实现了任意速率和预定义速率。这意味着波特率不是通过位掩码表示的,而是直接用整数表示的。
newtio.c_cflag = baud | CS8 | CLOCAL | CREAD;
这会直接与波特率常数进行“或”运算(B115200, B9600,等等)填入标志字段中。当这些是位模式时,这种方法是有效的,但 现代 glibc 将其视为普通的整数 以提高灵活性。
newtio.c_cflag = CS8 | CLOCAL | CREAD; cfsetispeed(&newtio, baud); cfsetospeed(&newtio, baud);
RTS/CTS为何受到影响:
旧方法可能会在未报错的情况下无法正确设置波特率,从而导致时序异常,进而破坏 RTS/CTS 握手协议。
static void setup_serial_port(int baud)
{
struct termios newtio;
tcgetattr(_fd, &newtio);
// OLD WAY (broken with glibc 2.42+):
// newtio.c_cflag = baud | CS8 | CLOCAL | CREAD;
// NEW WAY (POSIX-compliant):
newtio.c_cflag = CS8 | CLOCAL | CREAD;
cfsetispeed(&newtio, baud);
cfsetospeed(&newtio, baud);
tcsetattr(_fd, TCSANOW, &newtio);
}额外修复:类型安全
// OLD: ssize_t actual_write_size // NEW: size_t actual_write_size
该 write() 系统调用预期 size_t (unsigned),但当时我们正路过 ssize_t (signed),这会在启用严格类型检查时触发编译器警告。
linux-serial-test/linux-serial-test.c:634:29: warning: 'write' specified size between 9223372036854775808 and 18446744073709551615 exceeds maximum object size 9223372036854775807 [-Wstringop-overflow=] 634 | ssize_t c = write(_fd, _write_data, actual_write_size); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| # | 教训 | 描述 |
| 1 | 尊重用户意图 | 命令行选项需覆盖设备树的默认设置。 |
| 2 | 调查二进制文件,而不仅仅是代码 | nm, objdump,以及 strace 揭示运行时差异。 |
| 3 | 优先考虑符合 POSIX 标准 | 使用 cfsetispeed() / cfsetospeed() 用于串行设置。 |
| 4 | 使用真正的工具链 | OE SDK 复现问题可避免假阴性结果。 |
| 5 | 避免陷于编译标志符 flag | 编译优化和 PIE 极少会导致细微的运行时不匹配。 |
起初看似是我们整个设备系列中普遍存在的内核回归问题,实际上却是工具链和库的兼容性问题。这一案例提醒我们,在现代嵌入式系统的调试过程中,问题很少出现在最初检查的地方。
此次调查证实了 API 正确性比走捷径更为重要,符号分析和运行时分析工具的价值,以及为支持未来的维护人员而必须确保合规性和完善文档的必要性。
RS485
Linux SDK
专栏文章内容及配图由作者撰写发布,仅供工程师学习之用,如有侵权或者其他违规问题,请联系本站处理。 联系我们
相关推荐
经典RS485通讯电路图
IBM 与 Arm:拓展 Arm 生态,开启企业计算新时代
ARM嵌入式系统开发
ARM开发详解
RS232-TTL-RS485的电路图
安谋将扩大采用阿尔蒂斯安全技术
ARM嵌入式软件编程经验谈
RS232与RS485的通讯转换电路图
Arm 与 Unity 中国达成战略合作,以 Arm 神经技术加速中国手游创新
WinCE+ARM开发及关键技术 上
基于 Arm 平台的 NVIDIA RTX Spark 重新定义智能体时代的 PC
RS232-TTl-RS485转接口电路
最新ARM技术和嵌入式技术发展动态 中
RS485通信经典电路(for初学者)
IBM公布双架构大型机处理器路线:单核原生并发执行IBM Z与Arm指令
从Token 到任务:智能体AI如何改变基础设施的关注焦点
Linux内核源代码的阅读和工具介绍(aqian转)
ARM嵌入式系统开发:软件设计与优化
贸泽推出全新电子书探讨如何利用热门Arduino UNO Q开发板进行AI嵌入式设计
最新ARM技术和嵌入式技术发展动态 下
安谋科技Arm China闪耀WAIC AI前瞻分享
嵌入式LINUX开发套件常见问题解答
uClinux系统分析
arm学习资料
[转帖]NeuLinux嵌入式Linux开发平台
机器人迈向自适应智能:为什么系统能力正在成为新的竞争焦点
英伟达新一代Rosa处理器搭载参宿四 Rigel(Arm v9.2)内核
uClinux系统分析
ARM 展示小型低功耗上网本样品
WinCE+ARM开发及关键技术 下