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
相关产品推荐
相关产品推荐

