无法通过SIGIO信号唤醒POSIX线程问题求助
嘿,我看了你的代码,马上发现几个关键问题,这应该就是你多线程下SIGIO没反应的原因——单线程时信号处理器能正常工作,但换成sigwaitinfo的多线程架构,信号掩码和线程配置逻辑完全不一样,咱们一步步来理:
核心问题1:信号掩码配置搞反了
你在InitThreads里调用了sigprocmask(SIG_UNBLOCK, &SigSet, NULL),这是完全错误的!sigwaitinfo的工作逻辑是:它只能等待那些被当前线程阻塞的信号。你把SIGIO解除阻塞后,操作系统会把这个信号随机递送到进程中任意一个未阻塞该信号的线程,根本轮不到你的GNSS线程用sigwaitinfo捕获。
正确的做法是:在主线程里把SIGIO设置为进程级阻塞,这样所有新创建的线程都会继承这个掩码,然后在GNSS线程里用sigwaitinfo等待这个被阻塞的信号。
核心问题2:变量名不匹配
你在InitThreads里初始化的是SigSet,但在GNSSThreadFn里调用sigwaitinfo(&WaitSet, &SigInfo)——这里的WaitSet根本没初始化过!这属于低级笔误,直接导致sigwaitinfo在等一个空的信号集合,肯定没法触发。
额外问题:多线程下的F_SETOWN与信号投递
虽然你设置了F_SETOWN为进程ID,但多线程下如果没有正确阻塞信号,SIGIO还是会乱跑。另外,如果你想让信号精准投递到GNSS线程,其实可以用pthread_sigmask在其他线程里把SIGIO阻塞,只留GNSS线程处理,但更简单的是先让进程全局阻塞SIGIO,再让GNSS线程用sigwaitinfo等待。
修改后的ThreadFn.cpp关键部分
sigset_t SigSet; void InitThreads() { pthread_t GNSSThreadID, TestThreadID; // 1. 先在进程级阻塞SIGIO,让所有新线程继承这个掩码 sigemptyset(&SigSet); sigaddset(&SigSet, SIGIO); sigprocmask(SIG_BLOCK, &SigSet, NULL); // 这里改成SIG_BLOCK,不是UNBLOCK! // 配置串口触发SIGIO fcntl(Config.GNSSDataLinkLayer->GetFD(), F_SETSIG, SIGIO); fcntl(Config.GNSSDataLinkLayer->GetFD(), F_SETFL, O_ASYNC); fcntl(Config.GNSSDataLinkLayer->GetFD(), F_SETOWN, getpid()); pthread_create(&GNSSThreadID, NULL, GNSSThreadFn, NULL); pthread_create(&TestThreadID, NULL, TestThreadFn, NULL); pthread_join(TestThreadID, NULL); } void *GNSSThreadFn(void *param) { cout << "Configuring GNSS Thread" << endl; siginfo_t SigInfo; mutex mtx; cout << "Entering GNSS Thread while loop" << endl; while(1){ cout << "Before sigwait" << endl; // 2. 用正确的SigSet变量,而不是未初始化的WaitSet int ret = sigwaitinfo(&SigSet, &SigInfo); if (ret == -1) { perror("sigwaitinfo failed"); continue; } cout << "After sigwait" << endl; // 检查信号来源和类型 if(SigInfo.si_fd == Config.GNSSDataLinkLayer->GetFD() && SigInfo.si_code == POLL_IN){ cout << "GNSSThreadMaster::ThreadLoop: Serial port read" << endl; mtx.lock(); Config.NMEATransportLayer->ReadTalker(); mtx.unlock(); }else if(SigInfo.si_fd == Config.GNSSDataLinkLayer->GetFD() && SigInfo.si_code == POLL_OUT){ tcdrain(Config.GNSSDataLinkLayer->GetFD()); // 这里的usleep尽量去掉,可以用更优雅的方式,比如确认写入完成 // usleep(20000); }else{ cout << "ThreadLoop: Signal error, si_signo=" << SigInfo.si_signo << ", si_fd=" << SigInfo.si_fd << endl; } } return NULL; }
额外建议
- 建议在调用sigwaitinfo后检查返回值,用
perror打印错误,方便后续排查问题。 - 那个
usleep(20000)是临时 workaround,其实可以通过检查串口的输出缓冲区状态来替代,避免不必要的延迟。 - 如果你TestThread不需要处理SIGIO,也可以在
TestThreadFn里用pthread_sigmask再次确认阻塞SIGIO,确保信号只会被GNSS线程捕获。
内容的提问来源于stack exchange,提问作者user9657895

