嵌入式Linux中非阻塞UART读取在低优先级线程运行时阻塞问题
嵌入式Linux串口非阻塞读取异常问题分析与解决
问题描述
我有一个基于嵌入式Linux的项目,设备通过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标记设为非阻塞模式,但出现阻塞现象,核心原因并非串口配置错误,而是线程调度或同步问题:
- 线程优先级未正确配置:如果高优先级线程未使用实时调度策略(如
SCHED_FIFO/SCHED_RR),仅依赖CFS调度器的普通优先级,会和低优先级线程被公平调度,无法主动抢占CPU。 - 优先级反转:低优先级线程持有了高优先级线程需要的锁(全局互斥量、内核资源锁),且未启用优先级继承机制,导致高优先级线程被阻塞等待。
- 非抢占式内核限制:若内核未开启
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, ¶m); // 明确指定线程使用设置的调度参数,而非继承父线程 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
相关产品推荐
相关产品推荐

