使用termios与sigaction时UART触发异常问题求助
问题分析与解决方案
核心问题
- 重复打开/关闭串口设备:每次SIGIO触发时都重新
open/close/dev/ttymxc4,会破坏串口的异步通知(O_ASYNC)属性和内核接收缓冲区状态,导致SIGIO触发逻辑紊乱。 - 串口配置缺失关键属性:复制的
ttymxc0配置中CREAD为0,这会导致串口不启用接收功能,属于异常状态。 - 文件状态标志覆盖:
fcntl(fd, F_SETFL)被调用两次,第二次设置O_ASYNC时会覆盖之前的FNDELAY,导致非阻塞模式失效。 - 信号处理未配置重启标志:
nanosleep是可中断系统调用,SIGIO触发后会直接返回错误,主循环会快速执行,加剧SIGIO触发的异常。
分步修复
1. 全局管理串口文件描述符
将串口fd改为全局变量,初始化时打开一次,避免重复open/close:
#include <fcntl.h> #include <unistd.h> #include <signal.h> #include <termios.h> #include <sys/types.h> #include <stdio.h> #include <string.h> #include <stdbool.h> #define RxBufsize 256 int uart_fd = -1; // 全局串口fd uint8_t read_buf[RxBufsize] = {0}; ssize_t SizeOfRxBuffer = 0; bool newPactAvaible = false;
2. 修正UART初始化函数
- 合并文件状态标志设置,保留
FNDELAY和O_ASYNC - 确保串口启用接收(设置
CREAD) - 复用其他串口配置后修正关键属性:
void initUART(){ uart_fd = open("/dev/ttymxc4", O_RDWR | O_NOCTTY | O_NDELAY); if (uart_fd == -1) { perror("Failed to open UART"); return; } // 配置SIGIO信号处理 struct sigaction saio; saio.sa_handler = signal_SIGIO_of_TTY; saio.sa_flags = SA_RESTART; // 让可中断系统调用自动重启 sigemptyset(&saio.sa_mask); saio.sa_restorer = NULL; sigaction(SIGIO, &saio, NULL); // 设置异步通知属性 fcntl(uart_fd, F_SETOWN, getpid()); fcntl(uart_fd, F_SETFL, FNDELAY | O_ASYNC); // 合并设置非阻塞和异步 // 配置串口参数(复用ttymxc0的配置,修正CREAD) struct termios termAttr; int ttymxc0 = open("/dev/ttymxc0", O_RDWR | O_NOCTTY | O_NDELAY); if (ttymxc0 != -1) { tcgetattr(ttymxc0, &termAttr); close(ttymxc0); // 强制启用接收功能 termAttr.c_cflag |= CREAD; // 配置VMIN和VTIME:固定数据包长度44字节,超时1秒(单位0.1秒) termAttr.c_cc[VMIN] = 44; termAttr.c_cc[VTIME] = 10; tcsetattr(uart_fd, TCSANOW, &termAttr); } else { perror("Failed to open ttymxc0 for config"); // 手动配置串口参数备选逻辑 termAttr.c_cflag = B115200 | CS8 | CLOCAL | CREAD; termAttr.c_iflag = IGNPAR; termAttr.c_oflag = 0; termAttr.c_lflag = 0; termAttr.c_cc[VMIN] = 44; termAttr.c_cc[VTIME] = 10; tcflush(uart_fd, TCIFLUSH); tcsetattr(uart_fd, TCSANOW, &termAttr); } }
3. 修正UART读取函数
直接使用全局fd读取,只处理实际接收到的数据:
void UART_read_tty(){ if (uart_fd == -1) return; // 清空缓冲区 memset(read_buf, 0, sizeof(read_buf)); SizeOfRxBuffer = read(uart_fd, read_buf, RxBufsize); if (SizeOfRxBuffer > 0) { newPactAvaible = true; printf("SIG received\n"); for(ssize_t i=0; i < SizeOfRxBuffer; i++){ printf("%x ", read_buf[i]); } printf("\n"); } else { // 读取失败或无数据,重置标志 newPactAvaible = false; } }
4. 修正信号处理函数
void signal_SIGIO_of_TTY(int status){ (void) status; UART_read_tty(); }
5. 主循环优化
处理nanosleep被信号打断的情况,确保休眠逻辑稳定:
#include <errno.h> int main(){ initUART(); struct timespec ts = {0, 10 * 1000000}; // 10ms休眠 while(1){ if(newPactAvaible){ getReadBuf_UART(); evaluateCMD_UART(); newPactAvaible = false; // 处理完后重置标志 } do_read_system_V_Queue(); // 处理nanosleep被信号打断的情况,自动重启休眠 while(nanosleep(&ts, &ts) == -1 && errno == EINTR); } // 退出时关闭串口 if (uart_fd != -1) close(uart_fd); return 0; }
关键说明
- VMIN和VTIME配置:如果数据包长度固定为44字节,设置
VMIN=44、VTIME=10(超时1秒),内核会在接收到完整数据包后触发SIGIO,或超时后返回已接收数据,避免提前触发。 - SA_RESTART标志:让
nanosleep等可中断系统调用在被SIGIO打断后自动重启,保证主循环休眠逻辑稳定,不会因信号打断导致CPU占用异常。 - 避免重复open/close:串口的异步通知属性和接收缓冲区状态绑定在文件描述符上,每次close后状态丢失,重新open需重新配置,这是SIGIO异常的核心原因。
内容的提问来源于stack exchange,提问作者Julian Müller
相关产品推荐
相关产品推荐

