目 录CONTENT

文章目录

💻工作经历:程序调优 - 机载软件重启时间过长

TalentQ
2025-09-04 / 0 评论 / 0 点赞 / 5 阅读 / 0 字

1 问题描述

这是在一个 生产者-消费者 场景下的问题。消费者A不断从生产者B那里读取数据,在收到的数据乱序时,A会重启B,即A向B先后发送 stop 和 start 指令。

这里的逻辑是,A处理数据发现数据乱序则发送 stop 指令,置标志位,在下一次处理数据时检测标志位和ACK,并发送 start。

预期两条指令相差1s 左右,但压测中发现,时差达到了 10s,严重影响性能。

2 问题排查

怀疑:在问题排查过程中,首先对外部因素进行了初步排查,例如怀疑生产者未及时对 stop 指令返回 ACK 响应。经核实,该可能性被排除。

验证:随后转向对接收机制的验证,确认本方系统实际已收到 ACK,但通过日志记录发现其接收时间显著延迟。

定位:进一步定位至底层串口读写操作,即基于 POSIX 标准的 read()write() 系统调用。问题根源在于 read() 调用处于阻塞状态没能及时返回。猜测是由于 ACK 报文长度较短,未达到读取阈值,导致函数无法及时返回。在此期间,消费者 A 逐渐累积收到若干心跳信号(同样为短帧),直至总数据量满足最小读取要求,read() 才得以返回。

验证:为验证该判断,将 read(int fd, void *buf, size_t count) 的 count 参数调整成1后,问题得到暂时解决。但该调整会导致 read() 调用频繁返回,增加系统处理负荷,对整体性能产生显著压力,不具备可行性。进一步尝试中发现,即便逐步降低 count,等待时间仍稳定在 10 秒左右并未减少,该现象与预期不符。后来发现,串口 read() 的 conut 参数仅代表希望读取的最大字节数,即使数据量比 count 小,理论上也能正常返回。

最终:在源文件的某个角落藏着两个配置参数 VMIN 和 VTIME,它俩控制这 read() 的超时机制,分别为 最小读取字节数 和 超时时间。根本原因找到,将超时时间改成一个合理的小的值,问题解决。

3 系统调用 read()

3.1 read() 函数

read() 是一个用于从文件描述符(fd)中读取数据的系统调用。它不仅可以用于普通文件,也常用于设备(如串口)、管道、socket等。

函数原型

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

参数

  • fd:文件描述符,标识要读取的对象(如文件、串口)

  • buf:指向存储读取数据的缓冲区指针

  • count:期望读取的最大字节数

返回值

成功时返回实际读取到的字节数;

如果到达文件末尾,返回i0;

失败时返回 -1,并设置 errno 以指示错误类型

代码示例

int fd = open("/dev/ttyS0", O_RDONLY);
char buffer[100];
ssize_t bytesRead = read(fd, buffer, sizeod(buffer));
if (byteRead > 0) {
  // ...
}

3.2 串口控制参数 VMIN、VTIME

在串口编程中(如termios接口),VIM和VTIME是用来控制非范式模式和(即原始模式)下 read() 行为的两个重要参数。

termios 结构体:

struct termios {
  ...
  cc_t c_cc[NCCS]; // 控制字符数组
  ...
}

其中,c_cc[VMIN] 和 c_cc[VTIME] 分别表示最小读取字节数和超时时间。

VMIN:指定了 read() 调用时,必须读取到的最小字节数才返回。

VTIME:指定了等待数据的超时时间,以 0.1秒 为单位。

组合行为:

组合

描述

VMIN > 0, VTIME > 0

read() 至少读取到 VMIN 个字节,或者在第一个字节到达后,等待 VTIME*0.1 秒,期间若有更多字节则继续读,直到满足 VMIN 或超时

VMIN == 0, VTIME > 0

非阻塞模式,read() 立即返回已读取的字节(可能为0),如果没有数据则等待最多 VTIME*0.1 秒

VMIN > 0, VTIME == 0

阻塞模式,直到读取到 VMIN 个字节

VMIN == 0, VTIME == 0

非阻塞。read() 立即返回i,无论是否有数据

代码示例:

#include <termios.h>

struct termios options;
tcgetattr(fd, &options);

options.c_cc[VMIN] = 5;  // 最少读取5字节
options.c_cc[VTIME] = 2;  // 超时 2*0.1 秒

tcsetattr(fd, TCSANOW, &options);

0

评论区