POSIX线程临界区信号未屏蔽/意外解除屏蔽问题排查
问题分析与修复方案
你遇到的核心问题是信号屏蔽逻辑存在两处关键错误,导致上下文切换的临界区被SIGALRM(模拟中断信号)打断,进而破坏了任务有效性校验的原子性,出现“数据合法但校验失败”的矛盾现象。
1. 信号掩码保存与恢复逻辑错误
你的uxPortSetInterruptMaskFromISR函数仅保存了一个标志位(原掩码是否包含目标信号),而非完整的原信号掩码,且恢复时无法准确还原线程原本的信号屏蔽状态。更严重的是,使用SIG_BLOCK叠加屏蔽集的方式可能导致预期外的信号未被屏蔽。
修复方案:精准屏蔽目标信号并完整恢复原状态
将信号屏蔽逻辑改为仅针对configPORT_SIGNAL_FOR_IRQ(即SIGALRM)进行操作,避免影响其他信号,同时准确保存和恢复原状态:
// 全局变量,用于保存原信号掩码中目标信号的状态 static portUnsignedBaseType uxOriginalMaskState = 0U; portUnsignedBaseType uxPortSetInterruptMaskFromISR( void ) { sigset_t xTargetMask; sigset_t xCurrentMask; // 初始化仅包含目标信号的掩码 sigemptyset(&xTargetMask); sigaddset(&xTargetMask, configPORT_SIGNAL_FOR_IRQ); // 获取当前线程的信号掩码 pthread_sigmask(SIG_BLOCK, NULL, &xCurrentMask); // 记录原状态:是否已屏蔽目标信号 uxOriginalMaskState = sigismember(&xCurrentMask, configPORT_SIGNAL_FOR_IRQ) ? 1U : 0U; // 仅当原状态未屏蔽时,才屏蔽目标信号 if (uxOriginalMaskState == 0U) { pthread_sigmask(SIG_BLOCK, &xTargetMask, NULL); } return uxOriginalMaskState; } void vPortClearInterruptMaskFromISR( portUnsignedBaseType xInterruptsAlreadyMasked ) { sigset_t xTargetMask; sigemptyset(&xTargetMask); sigaddset(&xTargetMask, configPORT_SIGNAL_FOR_IRQ); // 仅当原状态未屏蔽时,才解除屏蔽目标信号 if (xInterruptsAlreadyMasked == 0U) { pthread_sigmask(SIG_UNBLOCK, &xTargetMask, NULL); } }
2. 信号处理程序的sa_mask设置不当
在vPortInitialiseINTC中,你将信号处理程序的sa_mask设为空集,这会导致信号处理程序运行时,其他信号(包括同类型的SIGALRM)可以打断它,进而可能触发嵌套的上下文切换,破坏临界区的原子性。
修复方案:在信号处理程序中屏蔽目标信号
修改vPortInitialiseINTC中的sa_mask配置,确保处理SIGALRM时不会被同类型信号打断:
void vPortInitialiseINTC( void ) { struct sigaction xSigActIRQ, xTestSigActIRQ; if( iPortIsrSetupDefaultThreadSignalMask() != 0 ) { portERRORHOOKSTR( NULL, "Failed to set up default signal mask", errINVALID_PARAMETERS ); } xSigActIRQ.sa_flags = SA_SIGINFO; xSigActIRQ.sa_sigaction = vPortIsrIRQHandler; // 屏蔽目标信号,避免处理时被同类型信号打断 sigemptyset( &xSigActIRQ.sa_mask ); sigaddset( &xSigActIRQ.sa_mask, configPORT_SIGNAL_FOR_IRQ ); if( sigaction( configPORT_SIGNAL_FOR_IRQ, &xSigActIRQ, NULL ) ) { portERRORHOOKSTR( NULL, "Failed to set up simulated interrupt system", errINVALID_PARAMETERS ); } }
3. 可选优化:指定定时器信号的递送目标
默认情况下,timer_create创建的定时器信号会递送到进程内任意未屏蔽该信号的线程。如果你的程序存在多线程,可能导致信号被递送到非预期线程,间接影响临界区。可以指定信号仅递送到调度器线程:
portWEAK_FUNCTION void vApplicationSetupTickInterruptHook( portUInt32Type ulClockHz, portUInt32Type ulRateHz ) { struct sigevent xTimerEv; struct itimerspec xSysTickTimerSpec; int iSysRet; long lNanosecondsPerTick; ( void )ulClockHz; /* Unused in this port */ portcompatASSERT_PRIVILEGED_MODE(); lNanosecondsPerTick = ( long ) PORT_ONE_SECOND_IN_NS / ( long ) ulRateHz; vPortDisableInterrupts(); xTimerEv.sigev_notify = SIGEV_SIGNAL; xTimerEv.sigev_signo = configPORT_SIGNAL_FOR_IRQ; xTimerEv.sigev_value.sival_int = configPORT_SYSTICK_INTERRUPT_NUM; xTimerEv.sigev_notify_function = NULL; // 指定信号递送到当前线程(调度器初始化线程) #ifdef _POSIX_THREAD_SIGNALS xTimerEv._sigev_un._tid = pthread_self(); #endif iSysRet = timer_create( CLOCK_MONOTONIC, &xTimerEv, &xPortSysTickTimer ); if(iSysRet) { portERRORHOOKSTR( NULL, "Failed to create tick timer", errINVALID_PARAMETERS ); } xSysTickTimerSpec.it_interval.tv_sec = 0; xSysTickTimerSpec.it_interval.tv_nsec = lNanosecondsPerTick; xSysTickTimerSpec.it_value.tv_sec = 0; xSysTickTimerSpec.it_value.tv_nsec = lNanosecondsPerTick; iSysRet = timer_settime( xPortSysTickTimer, 0, &xSysTickTimerSpec, NULL ); if(iSysRet) { portERRORHOOKSTR( NULL, "failed to set & start tick timer", errINVALID_PARAMETERS ); } }
验证方法
修复后,重新运行程序并在xPortIsTaskHandleValid的无限循环处设置断点,确认信号处理程序的断点不会触发,说明临界区的信号屏蔽已生效。同时检查任务有效性校验是否能正常通过。
内容的提问来源于stack exchange,提问作者jonny123
相关产品推荐
相关产品推荐

