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

Linux下UART阻塞读取线程超时退出实现方法

给Linux UART读取线程添加超时退出机制

在Linux环境下,阻塞式read()调用会让线程持续等待UART数据,要实现超时退出,最可靠的方式是用select()系统调用监听UART文件描述符的可读事件,同时设置超时时间。以下是完整的修改实现:

核心思路

  • 用select()替代直接调用read(),通过struct timeval设置超时时长(根据需求设为3秒)
  • 若select()返回0,说明超时触发,直接退出线程
  • 若select()返回大于0,再调用read()读取数据,避免无意义阻塞

修改后的完整代码

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/select.h>
#include <errno.h>
#include <fcntl.h>
#include <termios.h>

// 定义设备参数结构体
typedef struct {
    int fd;
} devpara_t;

// 实际打开串口的实现
int openuart(const char* port) {
    int fd = open(port, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (fd < 0) {
        perror("open uart failed");
        return -1;
    }
    return fd;
}

// 配置串口参数的实现
void configuart(int fd, int baud, int databits, int stopbits, int parity, int unused) {
    struct termios options;
    tcgetattr(fd, &options);

    // 设置波特率
    speed_t baud_rate;
    switch(baud) {
        case 115200: baud_rate = B115200; break;
        default: baud_rate = B9600;
    }
    cfsetispeed(&options, baud_rate);
    cfsetospeed(&options, baud_rate);

    // 设置数据位
    options.c_cflag &= ~CSIZE;
    switch(databits) {
        case 8: options.c_cflag |= CS8; break;
        case 7: options.c_cflag |= CS7; break;
        default: options.c_cflag |= CS8;
    }

    // 设置停止位
    if(stopbits == 1) {
        options.c_cflag &= ~CSTOPB;
    } else {
        options.c_cflag |= CSTOPB;
    }

    // 设置奇偶校验
    if(parity == 0) { // NOPAR
        options.c_cflag &= ~PARENB;
    } else if(parity == 1) { // ODD
        options.c_cflag |= (PARODD | PARENB);
    } else { // EVEN
        options.c_cflag |= PARENB;
        options.c_cflag &= ~PARODD;
    }

    // 应用配置
    tcflush(fd, TCIOFLUSH);
    tcsetattr(fd, TCSANOW, &options);
}

void* recvthread(void *para){
    unsigned char buf[128]={0};
    int ret = -1;
    devpara_t *localpara = (devpara_t *)para;
    fd_set read_fds;
    struct timeval timeout;

    for(;;){
        FD_ZERO(&read_fds);
        FD_SET(localpara->fd, &read_fds);

        // 设置超时时间为3秒(可根据需求调整)
        timeout.tv_sec = 3;
        timeout.tv_usec = 0;

        // 监听UART文件描述符的可读事件
        ret = select(localpara->fd + 1, &read_fds, NULL, NULL, &timeout);
        if(ret < 0){
            // 处理信号中断错误,继续循环等待
            if(errno == EINTR)
                continue;
            perror("select failed");
            break;
        }else if(ret == 0){
            // 超时触发,退出线程
            printf("UART read timeout, exiting thread\n");
            break;
        }else{
            // 有数据可读,执行读取操作
            if(FD_ISSET(localpara->fd, &read_fds)){
                ret = read(localpara->fd, buf, sizeof(buf));
                if(ret < 0){
                    perror("read failed");
                    break;
                }else if(ret == 0){
                    // 串口被远端关闭
                    printf("UART port closed by peer\n");
                    break;
                }else{
                    // 打印读取到的数据(实际场景替换为业务处理逻辑)
                    printf("Read %d bytes: ", ret);
                    for(int i=0; i<ret; i++){
                        printf("%02x ", buf[i]);
                    }
                    printf("\n");
                }
            }
        }
    }
    pthread_exit(NULL);
}

int main(){
    int ret = 0;
    pthread_t wtid;
    devpara_t *dpara = malloc(sizeof(devpara_t));
    
    if(!dpara){
        perror("malloc failed");
        return -1;
    }
    
    dpara->fd = openuart("/dev/ttyS0");
    if(dpara->fd < 0){
        free(dpara);
        return -1;
    }
    configuart(dpara->fd, 115200, 8, 1, 0, 1);
        
    ret = pthread_create(&wtid, NULL, recvthread, (void *)dpara);
    if(ret < 0){
        perror("pthread_create failed");
        close(dpara->fd);
        free(dpara);
        return -1;
    }
    
    ret = pthread_join(wtid, NULL);
    if(ret < 0){
        perror("pthread_join failed");
    }
    
    close(dpara->fd);
    if(dpara)
        free(dpara);
    
    return ret;
}

关键细节说明

  • select()的第一个参数必须是监听的最大文件描述符值加1,否则无法正确监控目标fd
  • 每次循环都要重新初始化fd_set和timeval,因为select()会修改这两个结构的内容
  • 处理EINTR错误:当select()被信号中断时会返回-1并设置errno为EINTR,此时可继续循环等待
  • 超时时间可按需调整,比如设置timeout.tv_sec = 1; timeout.tv_usec = 0;即可实现1秒超时

内容的提问来源于stack exchange,提问作者Rui

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 04:55:39