From 04e1d7d1b78b958b1694a9da267e829d8f80eda7 Mon Sep 17 00:00:00 2001 From: Hui Su <3164683437@qq.com> Date: Thu, 20 Aug 2026 12:13:15 +0800 Subject: [PATCH] [kernel/signal] fix: respect suspend state when delivering signals Native signal delivery resumed every suspended thread, including RT_UNINTERRUPTIBLE IPC waiters. This could remove a waiter from a semaphore, mutex, event, mailbox, or message queue wait list without granting the corresponding resource, causing -RT_EINTR or inconsistent higher-level state. Inspect pending signals and suspend state under scheduler ownership. Wake interruptible waiters for common signals, and wake killable waiters only for SIGKILL and SIGSTOP according to RT-Thread suspend semantics. Defer signals not allowed by the current wait mode. Release the signal lock before waking the target thread. Also make scheduler-side pending-signal preprocessing respect the suspend mode, preventing SMP suspend-to-schedule races. Keep rt_signal_wait() blocked for non-matching signals and preserve the remaining timeout. Allow masked signals and NULL siginfo, handle suspend failures without starting a timer, and preserve the outer interrupted wait result when a signal handler invokes other kernel APIs. Add regression coverage for RT-Thread native signal semantics, including masked signals, non-matching signal waits, NULL siginfo, and RT_KILLABLE SIGSTOP handling. The parent implementation fails the regression test because uninterruptible and killable waits complete early after SIGUSR1, and signal_wait returns early for a non-matching signal. Validation: - UP QEMU: core.signal passed. - Dual-core SMP QEMU: core.signal passed. - scons -C bsp/qemu-vexpress-a9 -j$(nproc) passed. Signed-off-by: Hui Su <3164683437@qq.com> --- include/rtdef.h | 5 + src/scheduler_mp.c | 28 ++- src/signal.c | 298 ++++++++++++++++---------- src/utest/signal_tc.c | 470 ++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 679 insertions(+), 122 deletions(-) diff --git a/include/rtdef.h b/include/rtdef.h index ab97aeb917da..8f61d7b4cc8b 100644 --- a/include/rtdef.h +++ b/include/rtdef.h @@ -587,6 +587,11 @@ typedef struct rt_timer *rt_timer_t; typedef unsigned long rt_sigset_t; typedef siginfo_t rt_siginfo_t; typedef void (*rt_sighandler_t)(int signo); +#ifdef RT_USING_MUSLLIBC +#define RT_SIG_MASK(signo) ((rt_sigset_t)1 << ((signo) - 1)) +#else +#define RT_SIG_MASK(signo) ((rt_sigset_t)1 << (signo)) +#endif #endif /* RT_USING_SIGNALS */ /**@}*/ diff --git a/src/scheduler_mp.c b/src/scheduler_mp.c index 4bcbafad6dd6..0f7a4e48f3c9 100644 --- a/src/scheduler_mp.c +++ b/src/scheduler_mp.c @@ -713,6 +713,30 @@ static rt_thread_t _prepare_context_switch_locked(int cpu_id, } #ifdef RT_USING_SIGNALS +/* Check whether a signal may wake the thread in its suspend state. */ +static rt_bool_t _sched_signal_wakeup_allowed(struct rt_thread *thread) +{ + rt_uint8_t stat; + rt_sigset_t pending; + + stat = RT_SCHED_CTX(thread).stat; + pending = thread->sig_pending & thread->sig_mask; + + if (stat & RT_THREAD_STAT_SIGNAL_WAIT) + { + return RT_TRUE; + } + + if (!(stat & RT_SIGNAL_COMMON_WAKEUP_MASK)) + { + return RT_TRUE; + } + + return !(stat & RT_SIGNAL_KILL_WAKEUP_MASK) && + (pending & (RT_SIG_MASK(SIGKILL) | + RT_SIG_MASK(SIGSTOP))); +} + /** * @brief Preprocess pending signals for a suspended thread * @@ -727,7 +751,9 @@ static void _sched_thread_preprocess_signal(struct rt_thread *current_thread) if (rt_sched_thread_is_suspended(current_thread)) { /* if current_thread signal is in pending */ - if ((RT_SCHED_CTX(current_thread).stat & RT_THREAD_STAT_SIGNAL_MASK) & RT_THREAD_STAT_SIGNAL_PENDING) + if (((RT_SCHED_CTX(current_thread).stat & RT_THREAD_STAT_SIGNAL_MASK) & + RT_THREAD_STAT_SIGNAL_PENDING) && + _sched_signal_wakeup_allowed(current_thread)) { #ifdef RT_USING_SMART rt_thread_wakeup(current_thread); diff --git a/src/signal.c b/src/signal.c index ea3b283c8a03..fd5e2ed3631c 100644 --- a/src/signal.c +++ b/src/signal.c @@ -30,11 +30,6 @@ #define DBG_LVL DBG_WARNING #include -#ifdef RT_USING_MUSLLIBC - #define sig_mask(sig_no) (1u << (sig_no - 1)) -#else - #define sig_mask(sig_no) (1u << sig_no) -#endif #define sig_valid(sig_no) (sig_no >= 0 && sig_no < RT_SIG_MAX) static struct rt_spinlock _thread_signal_lock = RT_SPINLOCK_INIT; @@ -82,52 +77,84 @@ static void _signal_entry(void *parameter) #endif /* RT_USING_SMP */ } -/* - * To deliver a signal to thread, there are cases: - * 1. When thread is suspended, function resumes thread and - * set signal stat; - * 2. When thread is ready: - * - If function delivers a signal to self thread, just handle - * it. - * - If function delivers a signal to another ready thread, OS - * should build a slice context to handle it. - */ +/* Deliver pending signals according to the target thread's wait state. */ static void _signal_deliver(rt_thread_t tid) { rt_base_t level; + rt_sched_lock_level_t slvl; + rt_uint8_t stat; + rt_sigset_t pending; + rt_bool_t need_wakeup = RT_FALSE; level = rt_spin_lock_irqsave(&_thread_signal_lock); - - /* thread is not interested in pended signals */ - if (!(tid->sig_pending & tid->sig_mask)) + rt_sched_lock(&slvl); + stat = RT_SCHED_CTX(tid).stat; + + /* + * A signal_wait thread must also be woken for signals that are masked + * from normal asynchronous delivery. + */ + if (!(tid->sig_pending & tid->sig_mask) && + !(stat & RT_THREAD_STAT_SIGNAL_WAIT)) { + rt_sched_unlock(slvl); rt_spin_unlock_irqrestore(&_thread_signal_lock, level); return; } - if ((RT_SCHED_CTX(tid).stat & RT_THREAD_SUSPEND_MASK) == RT_THREAD_SUSPEND_MASK) + pending = tid->sig_pending & tid->sig_mask; + + if ((stat & RT_THREAD_SUSPEND_MASK) == RT_THREAD_SUSPEND_MASK) { - /* resume thread to handle signal */ + if ((stat & RT_THREAD_STAT_SIGNAL_WAIT) || + !(stat & RT_SIGNAL_COMMON_WAKEUP_MASK) || + ((pending & (RT_SIG_MASK(SIGKILL) | RT_SIG_MASK(SIGSTOP))) && + !(stat & RT_SIGNAL_KILL_WAKEUP_MASK))) + { + if (!(stat & RT_THREAD_STAT_SIGNAL_WAIT)) + { + tid->error = RT_EINTR; + } + + need_wakeup = RT_TRUE; + } + + if (!(stat & RT_THREAD_STAT_SIGNAL_WAIT)) + { + /* Mark signals for the generic handler before resuming the target. */ + RT_SCHED_CTX(tid).stat |= RT_THREAD_STAT_SIGNAL | + RT_THREAD_STAT_SIGNAL_PENDING; + } + rt_sched_unlock(slvl); + rt_spin_unlock_irqrestore(&_thread_signal_lock, level); + + if (need_wakeup) + { + /* Wake the thread only after releasing the signal lock. */ #ifdef RT_USING_SMART - rt_thread_wakeup(tid); + rt_thread_wakeup(tid); #else - rt_thread_resume(tid); + rt_thread_resume(tid); #endif - /* add signal state */ - RT_SCHED_CTX(tid).stat |= (RT_THREAD_STAT_SIGNAL | RT_THREAD_STAT_SIGNAL_PENDING); - rt_spin_unlock_irqrestore(&_thread_signal_lock, level); - - /* re-schedule */ - rt_schedule(); + /* Re-schedule after the wakeup attempt. */ + rt_schedule(); + } } else { - if (tid == rt_thread_self()) + if (RT_SCHED_CTX(tid).stat & RT_THREAD_STAT_SIGNAL_WAIT) + { + /* Keep pending signals for rt_signal_wait() to consume. */ + rt_sched_unlock(slvl); + rt_spin_unlock_irqrestore(&_thread_signal_lock, level); + } + else if (tid == rt_thread_self()) { /* add signal state */ RT_SCHED_CTX(tid).stat |= RT_THREAD_STAT_SIGNAL; + rt_sched_unlock(slvl); rt_spin_unlock_irqrestore(&_thread_signal_lock, level); /* do signal action in self thread context */ @@ -162,6 +189,7 @@ static void _signal_deliver(rt_thread_t tid) (void *)((char *)tid->sig_ret - 32), RT_NULL); #endif /* RT_USING_SMP */ + rt_sched_unlock(slvl); rt_spin_unlock_irqrestore(&_thread_signal_lock, level); LOG_D("signal stack pointer @ 0x%08x", tid->sp); @@ -170,6 +198,7 @@ static void _signal_deliver(rt_thread_t tid) } else { + rt_sched_unlock(slvl); rt_spin_unlock_irqrestore(&_thread_signal_lock, level); } } @@ -279,7 +308,7 @@ void rt_signal_mask(int signo) level = rt_spin_lock_irqsave(&_thread_signal_lock); - tid->sig_mask &= ~sig_mask(signo); + tid->sig_mask &= ~RT_SIG_MASK(signo); rt_spin_unlock_irqrestore(&_thread_signal_lock, level); } @@ -302,7 +331,7 @@ void rt_signal_unmask(int signo) level = rt_spin_lock_irqsave(&_thread_signal_lock); - tid->sig_mask |= sig_mask(signo); + tid->sig_mask |= RT_SIG_MASK(signo); /* let thread handle pended signals */ if (tid->sig_mask & tid->sig_pending) @@ -336,120 +365,166 @@ int rt_signal_wait(const rt_sigset_t *set, rt_siginfo_t *si, rt_int32_t timeout) rt_base_t level; rt_thread_t tid = rt_thread_self(); struct siginfo_node *si_node = RT_NULL, *si_prev = RT_NULL; + rt_tick_t deadline = 0; + rt_bool_t timed_wait; + rt_sigset_t deliver_pending = 0; /* current context checking */ RT_DEBUG_IN_THREAD_CONTEXT; /* parameters check */ - if (set == NULL || *set == 0 || si == NULL ) + if (set == NULL || *set == 0) { - ret = -RT_EINVAL; - goto __done_return; + return -RT_EINVAL; } /* clear siginfo to avoid unknown value */ - memset(si, 0x0, sizeof(rt_siginfo_t)); - - level = rt_spin_lock_irqsave(&_thread_signal_lock); - - /* already pending */ - if (tid->sig_pending & *set) goto __done; + if (si) + { + memset(si, 0x0, sizeof(rt_siginfo_t)); + } - if (timeout == 0) + timed_wait = timeout != RT_WAITING_FOREVER; + if (timed_wait && timeout > 0) { - ret = -RT_ETIMEOUT; - goto __done_int; + deadline = rt_tick_get() + (rt_tick_t)timeout; } - /* suspend self thread */ - rt_thread_suspend_with_flag(tid, RT_UNINTERRUPTIBLE); - /* set thread stat as waiting for signal */ - RT_SCHED_CTX(tid).stat |= RT_THREAD_STAT_SIGNAL_WAIT; + level = rt_spin_lock_irqsave(&_thread_signal_lock); - /* start timeout timer */ - if (timeout != RT_WAITING_FOREVER) + for (;;) { - rt_tick_t timeout_tick = timeout; - /* reset the timeout of thread timer and start it */ - rt_timer_control(&(tid->thread_timer), - RT_TIMER_CTRL_SET_TIME, - &timeout_tick); - rt_timer_start(&(tid->thread_timer)); - } - rt_spin_unlock_irqrestore(&_thread_signal_lock, level); + rt_bool_t matched = RT_FALSE; - /* do thread scheduling */ - rt_schedule(); + /* Return immediately when a requested signal is already pending. */ + if (!(tid->sig_pending & *set)) + { + rt_tick_t timeout_tick = 0; - level = rt_spin_lock_irqsave(&_thread_signal_lock); + if (timeout == 0 || + (timed_wait && + (rt_int32_t)(deadline - rt_tick_get()) <= 0)) + { + ret = -RT_ETIMEOUT; + break; + } - /* remove signal waiting flag */ - RT_SCHED_CTX(tid).stat &= ~RT_THREAD_STAT_SIGNAL_WAIT; + /* Suspend until a requested signal or the timeout arrives. */ + ret = rt_thread_suspend_with_flag(tid, RT_UNINTERRUPTIBLE); + if (ret != RT_EOK) + { + break; + } - /* check errno of thread */ - if (tid->error == -RT_ETIMEOUT) - { - tid->error = RT_EOK; - rt_spin_unlock_irqrestore(&_thread_signal_lock, level); + RT_SCHED_CTX(tid).stat |= RT_THREAD_STAT_SIGNAL_WAIT; - /* timer timeout */ - ret = -RT_ETIMEOUT; - goto __done_return; - } + if (timed_wait) + { + timeout_tick = deadline - rt_tick_get(); + if ((rt_int32_t)timeout_tick <= 0) + { + /* Use one tick so the timer path can resume the waiter. */ + timeout_tick = 1; + } -__done: - /* to get the first matched pending signals */ - si_node = (struct siginfo_node *)tid->si_list; - while (si_node) - { - int signo; + /* Restart the timer with the remaining wait interval. */ + rt_timer_control(&(tid->thread_timer), + RT_TIMER_CTRL_SET_TIME, + &timeout_tick); + rt_timer_start(&(tid->thread_timer)); + } + rt_spin_unlock_irqrestore(&_thread_signal_lock, level); + + /* do thread scheduling */ + rt_schedule(); - signo = si_node->si.si_signo; - if (sig_mask(signo) & *set) + level = rt_spin_lock_irqsave(&_thread_signal_lock); + + /* remove signal waiting flag */ + RT_SCHED_CTX(tid).stat &= ~RT_THREAD_STAT_SIGNAL_WAIT; + + /* Check whether the wait ended because of the timer. */ + if (tid->error == -RT_ETIMEOUT) + { + tid->error = RT_EOK; + ret = -RT_ETIMEOUT; + break; + } + } + + /* Find and consume the first requested pending signal. */ + si_prev = RT_NULL; + si_node = (struct siginfo_node *)tid->si_list; + while (si_node) { - *si = si_node->si; + int signo; - LOG_D("sigwait: %d sig raised!", signo); - if (si_prev) si_prev->list.next = si_node->list.next; - else + signo = si_node->si.si_signo; + if (RT_SIG_MASK(signo) & *set) { - struct siginfo_node *node_next; + if (si) + { + *si = si_node->si; + } + LOG_D("sigwait: %d sig raised!", signo); - if (si_node->list.next) + if (si_prev) { - node_next = (void *)rt_slist_entry(si_node->list.next, struct siginfo_node, list); - tid->si_list = node_next; + si_prev->list.next = si_node->list.next; } else { - tid->si_list = RT_NULL; + if (si_node->list.next) + { + tid->si_list = (void *)rt_slist_entry(si_node->list.next, + struct siginfo_node, + list); + } + else + { + tid->si_list = RT_NULL; + } } + + /* clear pending */ + tid->sig_pending &= ~RT_SIG_MASK(signo); + rt_mp_free(si_node); + matched = RT_TRUE; + break; } - /* clear pending */ - tid->sig_pending &= ~sig_mask(signo); - rt_mp_free(si_node); - break; + si_prev = si_node; + if (si_node->list.next) + { + si_node = (void *)rt_slist_entry(si_node->list.next, + struct siginfo_node, + list); + } + else + { + si_node = RT_NULL; + } } - si_prev = si_node; - if (si_node->list.next) - { - si_node = (void *)rt_slist_entry(si_node->list.next, struct siginfo_node, list); - } - else + if (matched) { - si_node = RT_NULL; + break; } - } + } -__done_int: + deliver_pending = tid->sig_pending & tid->sig_mask & ~(*set); rt_spin_unlock_irqrestore(&_thread_signal_lock, level); -__done_return: + if (deliver_pending) + { + /* Resume normal delivery for signals outside the wait set. */ + _signal_deliver(tid); + } + return ret; } +/* Handle pending signals and update interrupted wait results. */ void rt_thread_handle_sig(rt_bool_t clean_state) { rt_base_t level; @@ -465,7 +540,8 @@ void rt_thread_handle_sig(rt_bool_t clean_state) { while (tid->sig_pending & tid->sig_mask) { - int signo, error; + int signo; + rt_err_t error_before_handler; rt_sighandler_t handler; si_node = (struct siginfo_node *)tid->si_list; @@ -479,18 +555,22 @@ void rt_thread_handle_sig(rt_bool_t clean_state) signo = si_node->si.si_signo; handler = tid->sig_vectors[signo]; - tid->sig_pending &= ~sig_mask(signo); + error_before_handler = tid->error; + tid->sig_pending &= ~RT_SIG_MASK(signo); rt_spin_unlock_irqrestore(&_thread_signal_lock, level); LOG_D("handle signal: %d, handler 0x%08x", signo, handler); if (handler) handler(signo); level = rt_spin_lock_irqsave(&_thread_signal_lock); - error = -RT_EINTR; rt_mp_free(si_node); /* release this siginfo node */ - /* set errno in thread tcb */ - tid->error = error; + /* Do not overwrite a successful or timed-out wait. */ + if (error_before_handler == RT_EINTR || + error_before_handler == -RT_EINTR) + { + tid->error = -RT_EINTR; + } } /* whether clean signal status */ @@ -605,7 +685,7 @@ int rt_thread_kill(rt_thread_t tid, int sig) si.si_value.sival_ptr = RT_NULL; level = rt_spin_lock_irqsave(&_thread_signal_lock); - if (tid->sig_pending & sig_mask(sig)) + if (tid->sig_pending & RT_SIG_MASK(sig)) { /* whether already emits this signal? */ struct rt_slist_node *node; @@ -652,7 +732,7 @@ int rt_thread_kill(rt_thread_t tid, int sig) } /* a new signal */ - tid->sig_pending |= sig_mask(sig); + tid->sig_pending |= RT_SIG_MASK(sig); rt_spin_unlock_irqrestore(&_thread_signal_lock, level); } diff --git a/src/utest/signal_tc.c b/src/utest/signal_tc.c index eb7001e49e08..7f4981ae7974 100644 --- a/src/utest/signal_tc.c +++ b/src/utest/signal_tc.c @@ -133,6 +133,118 @@ static volatile int receive_sig = 0; static struct rt_semaphore _received_signal; +static struct rt_semaphore _signal_wait_done; +static struct rt_semaphore _sem_signal_wait; +static struct rt_semaphore _sem_signal_ready; +static struct rt_semaphore _sem_signal_done; +static struct rt_semaphore _signal_wait_repeat_ready; +static volatile rt_err_t _sem_signal_result; +static volatile rt_int32_t _sem_signal_timeout; +static volatile rt_err_t _signal_wait_result; +static volatile int _signal_wait_repeat_count; +static volatile rt_bool_t _signal_handler_delay; + +/* Record the native signal received by the worker thread. */ +static void signal_sem_handler(int signo) +{ + receive_sig = signo; + + if (_signal_handler_delay) + { + rt_thread_delay(1); + } +} + +/* Count signals handled after a signal-wait operation returns. */ +static void signal_wait_repeat_handler(int signo) +{ + RT_UNUSED(signo); + _signal_wait_repeat_count++; +} + +/* Wait on a semaphore with the requested signal interruption mode. */ +static void signal_sem_wait_thread(void *parameter) +{ + int suspend_flag; + + suspend_flag = (int)(rt_ubase_t)parameter; + rt_signal_install(SIGUSR1, signal_sem_handler); + rt_signal_install(SIGKILL, signal_sem_handler); + rt_signal_install(SIGSTOP, signal_sem_handler); + rt_signal_unmask(SIGUSR1); + rt_signal_unmask(SIGKILL); + rt_signal_unmask(SIGSTOP); + rt_sem_release(&_sem_signal_ready); + + if (suspend_flag == RT_INTERRUPTIBLE) + { + _sem_signal_result = rt_sem_take_interruptible(&_sem_signal_wait, + _sem_signal_timeout); + } + else if (suspend_flag == RT_KILLABLE) + { + _sem_signal_result = rt_sem_take_killable(&_sem_signal_wait, + _sem_signal_timeout); + } + else + { + _sem_signal_result = rt_sem_take(&_sem_signal_wait, + _sem_signal_timeout); + } + + rt_sem_release(&_sem_signal_done); +} + +/* Wait until the worker thread is suspended on the semaphore. */ +static rt_bool_t signal_sem_wait_until_suspended(rt_thread_t thread) +{ + rt_sched_lock_level_t slvl; + rt_bool_t suspended; + int count; + + for (count = 0; count < RT_TICK_PER_SECOND; count++) + { + rt_sched_lock(&slvl); + suspended = ((RT_SCHED_CTX(thread).stat & RT_THREAD_SUSPEND_MASK) == + RT_THREAD_SUSPEND_MASK); + rt_sched_unlock(slvl); + + if (suspended) + { + return RT_TRUE; + } + + rt_thread_mdelay(1); + } + + return RT_FALSE; +} + +/* Wait until a thread is suspended while waiting for a signal. */ +static rt_bool_t signal_wait_until_signal_wait(rt_thread_t thread) +{ + rt_sched_lock_level_t slvl; + rt_bool_t waiting; + int count; + + for (count = 0; count < RT_TICK_PER_SECOND; count++) + { + rt_sched_lock(&slvl); + waiting = ((RT_SCHED_CTX(thread).stat & RT_THREAD_SUSPEND_MASK) == + RT_THREAD_SUSPEND_MASK) && + ((RT_SCHED_CTX(thread).stat & RT_THREAD_STAT_SIGNAL_WAIT) != 0); + rt_sched_unlock(slvl); + + if (waiting) + { + return RT_TRUE; + } + + rt_thread_mdelay(1); + } + + return RT_FALSE; +} void sig_handle_default(int signo) { @@ -227,18 +339,24 @@ static void rt_signal_kill_test(void) void rt_signal_wait_thread(void *parm) { - sigset_t selectset; - siginfo_t recive_si; + rt_sigset_t selectset; + rt_siginfo_t recive_si; + rt_int32_t timeout; + + timeout = (rt_int32_t)(rt_ubase_t)parm; rt_signal_install(SIGUSR1, sig_handle_default); + rt_signal_install(SIGUSR2, sig_handle_default); rt_signal_unmask(SIGUSR1); + rt_signal_unmask(SIGUSR2); - (void)sigemptyset(&selectset); - (void)sigaddset(&selectset, SIGUSR1); + selectset = RT_SIG_MASK(SIGUSR1); - /* case 5:rt_signal_wait, two thread, thread1: install and unmask, then wait 1s; thread2: kill, should received. */ - if (rt_signal_wait((void *)&selectset, &recive_si, RT_TICK_PER_SECOND) != RT_EOK) + /* Wait for the configured signal timeout or until SIGUSR1 is received. */ + _signal_wait_result = rt_signal_wait(&selectset, &recive_si, timeout); + if (_signal_wait_result != RT_EOK) { + rt_sem_release(&_signal_wait_done); return; } @@ -246,6 +364,20 @@ void rt_signal_wait_thread(void *parm) LOG_I("received signal %d", receive_sig); rt_sem_release(&_received_signal); + rt_sem_release(&_signal_wait_done); +} + +/* Wait for a masked signal without requiring a signal handler. */ +static void rt_signal_wait_masked_thread(void *parameter) +{ + rt_sigset_t selectset; + + RT_UNUSED(parameter); + selectset = RT_SIG_MASK(SIGUSR1); + _signal_wait_result = rt_signal_wait(&selectset, + RT_NULL, + RT_WAITING_FOREVER); + rt_sem_release(&_signal_wait_done); } static void rt_signal_wait_test(void) @@ -253,17 +385,28 @@ static void rt_signal_wait_test(void) rt_thread_t t1; receive_sig = -1; - t1 = rt_thread_create("sig_t1", rt_signal_wait_thread, 0, 4096, 14, 10); + t1 = rt_thread_create("sig_t1", rt_signal_wait_thread, + (void *)(rt_ubase_t)RT_WAITING_FOREVER, + 4096, 14, 10); if (t1) { rt_thread_startup(t1); } - rt_thread_mdelay(1); - /* case 5:rt_signal_wait, two thread, thread1: install and unmask, then wait 1s; thread2: kill, should received. */ + uassert_true(t1 != RT_NULL); + if (t1 == RT_NULL) + { + return; + } + uassert_true(signal_wait_until_signal_wait(t1)); + /* Verify that SIGUSR1 wakes a thread waiting for the signal. */ uassert_int_equal(rt_thread_kill(t1, SIGUSR1), RT_EOK); rt_sem_take(&_received_signal, RT_WAITING_FOREVER); + uassert_int_equal(rt_sem_take(&_signal_wait_done, + RT_TICK_PER_SECOND), + RT_EOK); uassert_int_equal(receive_sig, SIGUSR1); + uassert_int_equal(_signal_wait_result, RT_EOK); return; } @@ -273,14 +416,31 @@ static void rt_signal_wait_test2(void) rt_thread_t t1; receive_sig = -1; - t1 = rt_thread_create("sig_t1", rt_signal_wait_thread, 0, 4096, 14, 10); + t1 = rt_thread_create("sig_t1", rt_signal_wait_thread, + (void *)(rt_ubase_t)RT_TICK_PER_SECOND, + 4096, 14, 10); if (t1) { rt_thread_startup(t1); } - /* case 6:rt_signal_wait, two thread, thread1: install and unmask, then wait 1s; thread2: sleep 2s then kill, should can't received. */ - rt_thread_mdelay(2000); + /* Verify that a non-matching signal does not cancel the timed wait. */ + uassert_true(t1 != RT_NULL); + if (t1 == RT_NULL) + { + return; + } + uassert_true(signal_wait_until_signal_wait(t1)); + uassert_int_equal(rt_thread_kill(t1, SIGUSR2), RT_EOK); + uassert_int_equal(rt_sem_take(&_signal_wait_done, + RT_TICK_PER_SECOND / 10), + -RT_ETIMEOUT); + uassert_int_equal(rt_sem_take(&_signal_wait_done, + 2 * RT_TICK_PER_SECOND), + RT_EOK); + uassert_int_equal(_signal_wait_result, -RT_ETIMEOUT); + /* The non-matching unmasked signal is delivered after the wait returns. */ + uassert_int_equal(receive_sig, SIGUSR2); uassert_int_equal(rt_thread_kill(t1, SIGUSR1), RT_EOK); uassert_int_not_equal( rt_sem_take(&_received_signal, 1), @@ -290,18 +450,298 @@ static void rt_signal_wait_test2(void) return; } +/* Verify that signal_wait accepts a masked signal and a NULL siginfo pointer. */ +static void rt_signal_wait_masked_test(void) +{ + rt_thread_t thread; + + _signal_wait_result = -RT_ERROR; + thread = rt_thread_create("sigmask", + rt_signal_wait_masked_thread, + RT_NULL, + 2048, + UTEST_THR_PRIORITY - 1, + 10); + uassert_true(thread != RT_NULL); + if (thread == RT_NULL) + { + return; + } + + rt_thread_startup(thread); + uassert_true(signal_wait_until_signal_wait(thread)); + uassert_int_equal(rt_thread_kill(thread, SIGUSR1), RT_EOK); + uassert_int_equal(rt_sem_take(&_signal_wait_done, + 2 * RT_TICK_PER_SECOND), + RT_EOK); + uassert_int_equal(_signal_wait_result, RT_EOK); +} + +/* Wait for one signal and verify deferred signal delivery after returning. */ +static void signal_wait_repeat_thread(void *parameter) +{ + rt_sigset_t selectset; + rt_siginfo_t receive_info; + rt_uint8_t priority; + + RT_UNUSED(parameter); + rt_signal_install(SIGUSR1, signal_wait_repeat_handler); + rt_signal_install(SIGUSR2, signal_wait_repeat_handler); + rt_signal_unmask(SIGUSR1); + rt_signal_unmask(SIGUSR2); + selectset = RT_SIG_MASK(SIGUSR1); + + if (rt_signal_wait(&selectset, &receive_info, RT_WAITING_FOREVER) == + RT_EOK) + { + priority = UTEST_THR_PRIORITY + 1; + rt_thread_control(rt_thread_self(), + RT_THREAD_CTRL_CHANGE_PRIORITY, + &priority); + rt_sem_release(&_signal_wait_repeat_ready); + } +} + +/* Verify deferred non-matching signals are delivered after signal_wait returns. */ +static void rt_signal_wait_repeat_test(void) +{ + rt_thread_t thread; + + _signal_wait_repeat_count = 0; + uassert_int_equal(rt_sem_init(&_signal_wait_repeat_ready, + "sigrep_r", + 0, + RT_IPC_FLAG_PRIO), + RT_EOK); + + thread = rt_thread_create("sigrep", + signal_wait_repeat_thread, + RT_NULL, + 2048, + UTEST_THR_PRIORITY - 1, + 10); + uassert_true(thread != RT_NULL); + if (thread == RT_NULL) + { + goto __cleanup; + } + + rt_thread_startup(thread); + uassert_true(signal_wait_until_signal_wait(thread)); + uassert_int_equal(rt_thread_kill(thread, SIGUSR2), RT_EOK); + uassert_int_equal(rt_sem_take(&_signal_wait_repeat_ready, + RT_TICK_PER_SECOND / 10), + -RT_ETIMEOUT); + uassert_int_equal(_signal_wait_repeat_count, 0); + uassert_int_equal(rt_thread_kill(thread, SIGUSR1), RT_EOK); + uassert_int_equal(rt_sem_take(&_signal_wait_repeat_ready, + 2 * RT_TICK_PER_SECOND), + RT_EOK); + uassert_int_equal(_signal_wait_repeat_count, 1); + +__cleanup: + rt_sem_detach(&_signal_wait_repeat_ready); +} + +/* Verify native signal wakeup behavior for each semaphore wait mode. */ +static void signal_sem_case(int suspend_flag, + int signo, + rt_err_t expected_result, + rt_bool_t expect_early_wakeup, + rt_bool_t release_semaphore, + rt_int32_t timeout) +{ + rt_thread_t thread = RT_NULL; + rt_err_t early_result; + rt_err_t done_result; + rt_bool_t thread_done = RT_FALSE; + + receive_sig = -1; + _sem_signal_result = -RT_ERROR; + _sem_signal_timeout = timeout; + uassert_int_equal(rt_sem_init(&_sem_signal_wait, + "sigsem", + 0, + RT_IPC_FLAG_PRIO), + RT_EOK); + uassert_int_equal(rt_sem_init(&_sem_signal_ready, + "sigready", + 0, + RT_IPC_FLAG_PRIO), + RT_EOK); + uassert_int_equal(rt_sem_init(&_sem_signal_done, + "sigd", + 0, + RT_IPC_FLAG_PRIO), + RT_EOK); + + thread = rt_thread_create("sigsem_t", + signal_sem_wait_thread, + (void *)(rt_ubase_t)suspend_flag, + 2048, + UTEST_THR_PRIORITY - 1, + 10); + uassert_true(thread != RT_NULL); + if (thread == RT_NULL) + { + goto __cleanup; + } + + if (rt_thread_startup(thread) != RT_EOK) + { + uassert_true(RT_FALSE); + goto __cleanup; + } + + if (rt_sem_take(&_sem_signal_ready, + 2 * RT_TICK_PER_SECOND) != RT_EOK) + { + uassert_true(RT_FALSE); + goto __cleanup; + } + + if (!signal_sem_wait_until_suspended(thread)) + { + uassert_true(RT_FALSE); + goto __cleanup; + } + + uassert_int_equal(rt_thread_kill(thread, signo), RT_EOK); + + early_result = rt_sem_take( + &_sem_signal_done, + expect_early_wakeup ? 2 * RT_TICK_PER_SECOND : RT_TICK_PER_SECOND / 20 + 1); + uassert_int_equal(early_result, + expect_early_wakeup ? RT_EOK : -RT_ETIMEOUT); + if (early_result == RT_EOK) + { + thread_done = RT_TRUE; + } + else + { + if (release_semaphore) + { + rt_sem_release(&_sem_signal_wait); + } + done_result = rt_sem_take(&_sem_signal_done, + 2 * RT_TICK_PER_SECOND); + uassert_int_equal(done_result, RT_EOK); + if (done_result == RT_EOK) + { + thread_done = RT_TRUE; + } + } + + LOG_I("signal sem mode=%d, result=%d, signal=%d, early=%d", + suspend_flag, + _sem_signal_result, + receive_sig, + early_result); + uassert_int_equal(receive_sig, signo); + uassert_int_equal(_sem_signal_result, expected_result); + +__cleanup: + if (thread != RT_NULL && !thread_done) + { + if (release_semaphore) + { + rt_sem_release(&_sem_signal_wait); + } + if (rt_sem_take(&_sem_signal_done, + 2 * RT_TICK_PER_SECOND) == RT_EOK) + { + thread_done = RT_TRUE; + } + } + + /* Detach the semaphores only after the worker stops using them. */ + if (thread_done) + { + rt_sem_detach(&_sem_signal_wait); + rt_sem_detach(&_sem_signal_ready); + rt_sem_detach(&_sem_signal_done); + } +} + +/* Verify signal wakeup behavior for uninterruptible waits. */ +static void rt_signal_uninterruptible_sem_test(void) +{ + signal_sem_case(RT_UNINTERRUPTIBLE, + SIGUSR1, + RT_EOK, + RT_FALSE, + RT_TRUE, + RT_WAITING_FOREVER); + signal_sem_case(RT_UNINTERRUPTIBLE, + SIGUSR1, + -RT_ETIMEOUT, + RT_FALSE, + RT_FALSE, + 10); +} + +/* Verify that a normal signal interrupts an interruptible wait. */ +static void rt_signal_interruptible_sem_test(void) +{ + signal_sem_case(RT_INTERRUPTIBLE, + SIGUSR1, + -RT_EINTR, + RT_TRUE, + RT_FALSE, + RT_WAITING_FOREVER); +} + +/* Verify that a handler cannot overwrite the interrupted wait result. */ +static void rt_signal_interruptible_handler_error_test(void) +{ + _signal_handler_delay = RT_TRUE; + signal_sem_case(RT_INTERRUPTIBLE, + SIGUSR1, + -RT_EINTR, + RT_TRUE, + RT_FALSE, + RT_WAITING_FOREVER); + _signal_handler_delay = RT_FALSE; +} + +/* Verify that killable waits defer normal signals and accept SIGKILL. */ +static void rt_signal_killable_sem_test(void) +{ + signal_sem_case(RT_KILLABLE, + SIGUSR1, + RT_EOK, + RT_FALSE, + RT_TRUE, + RT_WAITING_FOREVER); + signal_sem_case(RT_KILLABLE, + SIGKILL, + -RT_EINTR, + RT_TRUE, + RT_FALSE, + RT_WAITING_FOREVER); + signal_sem_case(RT_KILLABLE, + SIGSTOP, + -RT_EINTR, + RT_TRUE, + RT_FALSE, + RT_WAITING_FOREVER); +} + static rt_err_t utest_tc_init(void) { rt_sem_init(&_received_signal, "utest", 0, RT_IPC_FLAG_PRIO); + rt_sem_init(&_signal_wait_done, "sigwait_d", 0, RT_IPC_FLAG_PRIO); return RT_EOK; } static rt_err_t utest_tc_cleanup(void) { rt_sem_detach(&_received_signal); + rt_sem_detach(&_signal_wait_done); return RT_EOK; } +/* Run native signal tests. */ static void testcase(void) { #ifdef RT_USING_HEAP @@ -310,7 +750,13 @@ static void testcase(void) UTEST_UNIT_RUN(rt_signal_unmask_test); UTEST_UNIT_RUN(rt_signal_kill_test); UTEST_UNIT_RUN(rt_signal_wait_test); + UTEST_UNIT_RUN(rt_signal_wait_masked_test); UTEST_UNIT_RUN(rt_signal_wait_test2); + UTEST_UNIT_RUN(rt_signal_wait_repeat_test); + UTEST_UNIT_RUN(rt_signal_uninterruptible_sem_test); + UTEST_UNIT_RUN(rt_signal_interruptible_sem_test); + UTEST_UNIT_RUN(rt_signal_interruptible_handler_error_test); + UTEST_UNIT_RUN(rt_signal_killable_sem_test); #endif /* RT_USING_HEAP */ } UTEST_TC_EXPORT(testcase, "core.signal", utest_tc_init, utest_tc_cleanup, 1000);