使用POSIX信号读取串口数据遇问题,附串口、信号及共享内存初始化代码
嘿,我来帮你捋捋用POSIX信号读取串口数据时可能踩的坑,结合你给出的代码片段,整理了几个关键的排查点和优化方向:
信号处理函数的核心注意事项
信号处理函数的执行环境很特殊,必须遵循异步信号安全的规则:
- 绝对不能在信号处理函数里调用
cout、printf这类非安全函数,它们可能会导致死锁或者数据损坏。如果需要输出日志,建议用write这种异步安全的系统调用。 - 你的
sigaction结构体配置要到位:记得用sigemptyset清空信号掩码,设置SA_RESTART标志可以让被信号中断的系统调用自动重启,这对串口读取的稳定性很重要。
串口初始化的关键配置
从你的代码片段看,你定义了termios结构体,但很可能漏掉了关键的参数配置:
- 必须明确设置波特率、数据位、停止位、奇偶校验,还要把串口切换到非规范模式(raw模式),这样信号触发的读取才会更可靠。比如通过
cfsetispeed/cfsetospeed设置波特率,用c_cflag配置数据位等参数,最后调用tcsetattr(fd, TCSANOW, &termAttr)让配置立即生效。 - 打开串口时要使用正确的flags:
O_RDWR | O_NOCTTY是基础,O_NOCTTY确保串口不会成为进程的控制终端;如果用信号驱动I/O,不要加O_NDELAY,让读取处于阻塞状态,靠SIGIO信号唤醒。 - 别忘了配置异步I/O的核心步骤:用
fcntl(fd, F_SETOWN, getpid())把串口的所有者设为当前进程,再用fcntl(fd, F_SETFL, flags | O_ASYNC)开启异步I/O,这样串口有数据时才会发送SIGIO信号。
共享内存的安全访问
你用到了QSharedMemory,这里要注意信号处理时的线程安全问题:
- 信号可能在任意时刻触发,如果此时主线程或其他线程正在修改共享内存,会导致数据竞争。建议用
QMutex来保护共享内存的访问,但要注意信号处理函数里不能调用阻塞的锁操作,所以要用tryLock()这种非阻塞方式尝试锁定,避免信号处理被挂起。 - 更稳妥的方式是让信号处理函数只把数据写入一个线程安全的缓冲区(比如环形缓冲区),然后由主线程负责把缓冲区的数据同步到共享内存,这样可以避开信号处理里的锁操作。
完整优化后的代码示例
#include <unistd.h> #include <termios.h> #include <sys/signal.h> #include <stdint.h> #include <cassert> #include <cstdio> #include <fcntl.h> #include <cstring> #include <QSharedMemory> #include <QMutex> int fd; struct termios termAttr; struct sigaction saio; char buff[255]; QSharedMemory sharedMem; QMutex sharedMemMutex; // 异步信号安全的日志输出函数 void safe_log(const char* msg) { write(STDOUT_FILENO, msg, strlen(msg)); write(STDOUT_FILENO, "\n", 1); } // 信号处理函数,严格遵循异步信号安全规则 void signalHandler(int sig) { if (sig != SIGIO) return; ssize_t read_len = read(fd, buff, sizeof(buff) - 1); if (read_len > 0) { buff[read_len] = '\0'; // 确保字符串终止 // 非阻塞方式尝试锁定互斥锁,避免信号处理阻塞 if (sharedMemMutex.tryLock()) { if (sharedMem.isAttached()) { char* shm_data = static_cast<char*>(sharedMem.data()); memcpy(shm_data, buff, read_len); } sharedMemMutex.unlock(); } safe_log("SIGIO received: data read successfully"); } else if (read_len == -1) { safe_log("Error reading serial port in signal handler"); } } int main() { // 打开串口设备(根据实际设备路径修改) fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY); assert(fd != -1 && "Failed to open serial port"); // 初始化并配置串口参数 tcgetattr(fd, &termAttr); // 设置波特率为9600,可根据实际需求修改 cfsetispeed(&termAttr, B9600); cfsetospeed(&termAttr, B9600); // 配置串口核心参数 termAttr.c_cflag |= (CLOCAL | CREAD); // 忽略调制解调器状态,启用接收器 termAttr.c_cflag &= ~CSIZE; termAttr.c_cflag |= CS8; // 8位数据位 termAttr.c_cflag &= ~PARENB; // 无奇偶校验 termAttr.c_cflag &= ~CSTOPB; // 1位停止位 termAttr.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 非规范模式 termAttr.c_oflag &= ~OPOST; // 原始输出模式 termAttr.c_cc[VMIN] = 1; // 至少读取1字节触发 termAttr.c_cc[VTIME] = 0; // 无超时 // 应用串口配置 tcsetattr(fd, TCSANOW, &termAttr); // 配置SIGIO信号处理 saio.sa_handler = signalHandler; sigemptyset(&saio.sa_mask); saio.sa_flags = SA_RESTART; // 重启被中断的系统调用 sigaction(SIGIO, &saio, nullptr); // 开启串口异步I/O fcntl(fd, F_SETOWN, getpid()); int current_flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, current_flags | O_ASYNC); // 初始化共享内存 sharedMem.setKey("SerialDataSharedMem"); if (!sharedMem.create(sizeof(buff))) { safe_log("Failed to create shared memory segment"); close(fd); return 1; } // 主线程保持运行,可在此添加其他业务逻辑 safe_log("Serial port and signal setup complete, waiting for data..."); while (true) { sleep(1); // 可在此处读取共享内存的数据进行处理 } // 清理资源(实际运行中可能不会走到这里) close(fd); sharedMem.detach(); return 0; }
内容的提问来源于stack exchange,提问作者andrew28349
相关产品推荐
相关产品推荐

