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

嵌入式Linux中非阻塞UART读取在低优先级线程运行时阻塞问题

嵌入式Linux串口非阻塞读取异常问题分析与解决

问题描述

我有一个基于嵌入式Linux的项目,设备通过UART与另一设备通信。由高优先级线程周期性读取串口,串口已配置为非阻塞模式,运行状态正常,但当大量低优先级线程启动运行时,读取操作会阻塞至低优先级线程运行结束!

Uart读取阻塞截图

我已尝试多种串口配置选项,但均无效果,附上相关串口初始化代码:

bool UartCommDevice::OpenImpl(const char* portName, int baudRate, bool isBlockingMode, int timeoutBlock)
{
    m_serialPortName = portName;
    m_baudRate = baudRate;
    m_handle = open(m_serialPortName.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);

    if (m_handle < 0)
    {
        m_handle = 0;
        std::cout << "error opening " << m_serialPortName << std::endl;
        return false;
    }

    struct termios tty { 0 };

    if (tcgetattr(m_handle, &tty) != 0)
    {
        printf("Error from tcgetattr: %s\n", strerror(errno));
        return false;
    }

    // 设置串口波特率
    if (cfsetospeed(&tty, (speed_t) baudRate) != 0)
    {
        printf("Error from cfsetospeed: errno: %s\n", strerror(errno));
        return false;
    }

    if (cfsetispeed(&tty, (speed_t)baudRate) != 0)
    {
        printf("Error from cfsetispeed: errno: %s\n", strerror(errno));
        return false;
    }

    tty.c_cflag |= (CLOCAL | CREAD);    // 忽略调制解调器控制
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;         // 8位数据位
    tty.c_cflag &= ~PARENB;     // 无校验位
    tty.c_cflag &= ~CSTOPB;     // 1位停止位
    tty.c_cflag &= ~CRTSCTS;    // 禁用硬件流控

    // 配置为非规范模式
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
    tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    tty.c_oflag &= ~OPOST;

    // 有数据就立即读取
    tty.c_cc[VMIN] = 0;
    tty.c_cc[VTIME] = 0;

    tcflush(m_handle, TCIOFLUSH);

    if (tcsetattr(m_handle, TCSANOW, &tty) != 0)
    {
        printf("Error from tcsetattr: %s\n", strerror(errno));
        return false;
    }

    if (flock(m_handle, LOCK_EX | LOCK_NB) == -1) 
    {
        printf("Serial port with file descriptor is already locked by another process.\r\n");
    }
}

问题根源分析

串口已通过O_NONBLOCK标记设为非阻塞模式,但出现阻塞现象,核心原因并非串口配置错误,而是线程调度或同步问题:

  1. 线程优先级未正确配置:如果高优先级线程未使用实时调度策略(如SCHED_FIFO/SCHED_RR),仅依赖CFS调度器的普通优先级,会和低优先级线程被公平调度,无法主动抢占CPU。
  2. 优先级反转:低优先级线程持有了高优先级线程需要的锁(全局互斥量、内核资源锁),且未启用优先级继承机制,导致高优先级线程被阻塞等待。
  3. 非抢占式内核限制:若内核未开启CONFIG_PREEMPT,高优先级线程只能在低优先级线程主动让出CPU时才能执行,无法实时抢占。

解决步骤

1. 正确配置高优先级线程的调度策略

将高优先级线程设置为实时调度策略,并赋予足够高的优先级:

pthread_attr_t attr;
struct sched_param param;

pthread_attr_init(&attr);
// 使用SCHED_FIFO实时调度策略,一旦获得CPU则持续运行直到主动让出
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
// 实时优先级范围1-99,数值越高优先级越高,需确保进程有足够权限
param.sched_priority = 90;
pthread_attr_setschedparam(&attr, &param);
// 明确指定线程使用设置的调度参数,而非继承父线程
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);

// 创建串口读取线程时传入配置好的属性
pthread_create(&uart_read_thread, &attr, uart_read_loop, NULL);

pthread_attr_destroy(&attr);

2. 启用优先级继承锁解决优先级反转

如果高优先级线程和低优先级线程存在锁竞争,使用支持优先级继承的互斥量:

pthread_mutex_t global_mutex;
pthread_mutexattr_t mutex_attr;

pthread_mutexattr_init(&mutex_attr);
// 设置优先级继承协议,持有锁的低优先级线程会临时提升到高优先级线程的优先级
pthread_mutexattr_setprotocol(&mutex_attr, PTHREAD_PRIO_INHERIT);
pthread_mutex_init(&global_mutex, &mutex_attr);

pthread_mutexattr_destroy(&mutex_attr);

3. 优化串口读取逻辑,增加超时检测

在非阻塞读取前用select做超时检测,避免意外阻塞:

void* uart_read_loop(void* arg) {
    UartCommDevice* dev = static_cast<UartCommDevice*>(arg);
    char buf[256];
    fd_set read_fds;
    struct timeval tv;

    while (true) {
        FD_ZERO(&read_fds);
        FD_SET(dev->m_handle, &read_fds);
        // 设置1ms超时,确保线程能及时响应
        tv.tv_sec = 0;
        tv.tv_usec = 1000;

        int ret = select(dev->m_handle + 1, &read_fds, NULL, NULL, &tv);
        if (ret > 0 && FD_ISSET(dev->m_handle, &read_fds)) {
            ssize_t n = read(dev->m_handle, buf, sizeof(buf));
            if (n > 0) {
                // 处理读取到的数据
            }
        } else if (ret == -1) {
            // 处理select错误
            printf("select error: %s\n", strerror(errno));
        }
        // 周期性读取的其他逻辑
    }
    return NULL;
}

4. 检查内核配置

确认内核开启了CONFIG_PREEMPT(抢占式内核),如果使用的是非抢占式内核,需重新编译内核开启抢占选项,确保高优先级线程能实时抢占低优先级线程。

内容的提问来源于stack exchange,提问作者Y.H.Cohen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 12:59:59