VxWorks下串口行模式高速发送数据时程序挂死问题求优化方案
串口高发送速率挂死问题解决方案
核心问题根因
当前实现存在3个直接导致高负载下挂死的典型问题:
- 全局文件描述符
fd存在线程安全问题:接收线程打开串口赋值fd的动作和主线程调用write的动作无同步机制,当主线程在fd还未完成初始化(open、ioctl配置未结束)就执行写操作会直接触发异常,速率越高冲突概率越大 - 行模式下的读写逻辑不匹配:设置了
OPT_LINE行模式后,read默认是阻塞调用,当串口接收缓冲区没有遇到换行符时read会一直阻塞,此时如果串口发送缓冲区被填满,write也会进入阻塞状态,两个线程同时阻塞在IO操作上就会呈现程序挂死的表象 - 读缓冲区长度配置不合理:单次
read仅读取10字节,但NMEA报文最长可达82字节+换行符,过短的读长度会导致接收线程处理效率跟不上,大量报文积压在驱动缓冲区,最终触发读写双向阻塞
最优解决步骤
- 第一步:修复线程安全问题
增加初始化完成同步信号量,保证主线程仅在串口配置完成后才执行写操作,同时给串口增加非阻塞标识避免读写永久阻塞,修改后代码示例:#include <iostream> #include <chrono> #include <cstring> #include <fcntl.h> #include <unistd.h> #include <sioLib.h> #include <tyLib.h> #include <semLib.h> int fd = -1; SEM_ID initSem; // 新增初始化同步信号量 void recvthread() { // 打开串口时新增O_NONBLOCK非阻塞标志 fd = open("/tyCo/2", O_RDWR | O_NOCTTY | O_NONBLOCK, 0); if (fd != -1) { int nRes = ioctl(fd, FIOBAUDRATE, 9600); if (nRes == -1) perror("Error in setting Baudrate for /tyCo/2"); int options = ioctl(fd, FIOGETOPTIONS, 0); if (options == -1) options = 0; options |= CREAD | CLOCAL; options &= ~CSIZE; options |= CS8; options &= ~STOPB; options &= ~PARENB; options |= OPT_LINE; nRes = ioctl(fd, FIOSETOPTIONS, options); if (nRes == -1) perror("Error in setting options for /tyCo/2"); semGive(initSem); // 配置完成释放信号量 char buff[512]; while (true) { int len = read(fd, buff, sizeof(buff)-1); if (len > 0) { buff[len] = '\0'; // 此处处理接收数据 } else if (len == EAGAIN) { // 无数据时短休眠避免占满CPU std::this_thread::sleep_for(std::chrono::milliseconds(1LL)); } } } } int main() { initSem = semBCreate(SEM_Q_FIFO, SEM_EMPTY); std::thread t(recvthread); semTake(initSem, WAIT_FOREVER); // 等待串口初始化完成再执行写操作 char buff[512]; while (true) { // 写之前先查询发送缓冲区剩余空间,避免写阻塞 int txFree = 0; ioctl(fd, FIOGETOSIZE, &txFree); if (txFree >= 56) { int len = write(fd, buff, 56); } std::this_thread::sleep_for(std::chrono::milliseconds(250LL)); } } - 第二步:调整串口缓冲区大小
tews驱动默认的串口缓冲区一般为1024字节,高速率下容易溢出,在FIOSETOPTIONS配置完成后新增两行代码调整缓冲区:ioctl(fd, FIORBUFSET, 4096); // 接收缓冲区设为4K ioctl(fd, FIOWBUFSET, 2048); // 发送缓冲区设为2K - 第三步:替换临时刷新缓冲区方案
不需要主动刷新缓冲区,只要保证接收线程处理速度高于报文接收速度即可,非阻塞读+足够的缓冲区配置可以完全替代刷缓冲区的临时方案。
额外优化建议
如果NMEA报文解析逻辑复杂度较高,建议把接收和解析拆分到两个线程,接收线程只负责把完整行报文写入无锁队列,解析线程从队列取数据处理,进一步降低接收阻塞的概率。
内容的提问来源于stack exchange,提问作者Harry
相关产品推荐
相关产品推荐

