You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

VxWorks下串口行模式高速发送数据时程序挂死问题求优化方案

串口高发送速率挂死问题解决方案

核心问题根因

当前实现存在3个直接导致高负载下挂死的典型问题:

  1. 全局文件描述符fd存在线程安全问题:接收线程打开串口赋值fd的动作和主线程调用write的动作无同步机制,当主线程在fd还未完成初始化(open、ioctl配置未结束)就执行写操作会直接触发异常,速率越高冲突概率越大
  2. 行模式下的读写逻辑不匹配:设置了OPT_LINE行模式后,read默认是阻塞调用,当串口接收缓冲区没有遇到换行符时read会一直阻塞,此时如果串口发送缓冲区被填满,write也会进入阻塞状态,两个线程同时阻塞在IO操作上就会呈现程序挂死的表象
  3. 读缓冲区长度配置不合理:单次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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 08:15:01