Ver Fonte

[add][thread]Add api:rt_thread_suspend_force allow suspend other threads.

Rbb666 há 1 semana atrás
pai
commit
e1c1a07c48

+ 1 - 0
examples/utest/testcases/kernel/SConscript

@@ -44,6 +44,7 @@ if GetDepend(['UTEST_MAILBOX_TC']):
 if GetDepend(['UTEST_THREAD_TC']):
     src += ['thread_tc.c']
     src += ['thread_overflow_tc.c']
+    src += ['thread_suspend_tc.c']
 
 if GetDepend(['UTEST_DEVICE_TC']):
     src += ['device_tc.c']

+ 478 - 0
examples/utest/testcases/kernel/thread_suspend_tc.c

@@ -0,0 +1,478 @@
+/*
+ * Copyright (c) 2025, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2025-09-02     Rbb666       utest case for rt_thread_suspend comprehensive tests
+ */
+
+#include <rtthread.h>
+#include <rtdevice.h>
+#include "utest.h"
+
+#define THREAD_STACK_SIZE    1024
+#define THREAD_TIMESLICE     5
+#define TEST_THREAD_PRIORITY 25
+
+/* Global variables for normal usage test */
+static rt_thread_t          target_thread     = RT_NULL;
+static rt_thread_t          monitor_thread    = RT_NULL;
+static rt_sem_t             sync_sem          = RT_NULL;
+static volatile rt_uint32_t work_counter      = 0;
+static volatile rt_bool_t   suspend_test_done = RT_FALSE;
+static volatile rt_bool_t   suspend_success   = RT_FALSE;
+static volatile rt_bool_t   resume_success    = RT_FALSE;
+
+/* Global variables for deadlock test */
+static rt_mutex_t           test_mutex         = RT_NULL;
+static rt_thread_t          holder_thread      = RT_NULL;
+static rt_thread_t          waiter_thread      = RT_NULL;
+static volatile rt_uint32_t shared_counter     = 0;
+static volatile rt_bool_t   holder_got_mutex   = RT_FALSE;
+static volatile rt_bool_t   deadlock_detected  = RT_FALSE;
+static volatile rt_bool_t   test_completed     = RT_FALSE;
+static volatile rt_bool_t   thread_started     = RT_FALSE;
+static volatile rt_bool_t   thread_should_exit = RT_FALSE;
+
+/* Target work thread - the thread to be suspended */
+static void target_work_thread(void *parameter)
+{
+    while (1)
+    {
+        if (!suspend_test_done)
+        {
+            work_counter++;
+        }
+        /* Yield CPU appropriately to simulate normal work */
+        rt_thread_mdelay(10);
+    }
+}
+
+/* Monitor thread - responsible for suspending and resuming target thread */
+static void monitor_control_thread(void *parameter)
+{
+    rt_uint32_t counter_before, counter_after;
+
+    /* Wait for target thread to start working */
+    rt_thread_mdelay(300);
+
+    /* Record counter value before suspend */
+    counter_before = work_counter;
+
+    /* Use rt_thread_suspend to suspend target thread */
+    if (rt_thread_suspend(target_thread) == RT_EOK)
+    {
+        suspend_success = RT_TRUE;
+
+        /* Trigger scheduling to ensure thread is suspended */
+        rt_schedule();
+
+        /* Wait for a while to verify thread is indeed suspended */
+        rt_thread_mdelay(500);
+
+        counter_after = work_counter;
+
+        /* Verify thread is indeed suspended (counter should stop changing) */
+        if (counter_after == counter_before)
+        {
+            /* Resume target thread */
+            if (rt_thread_resume(target_thread) == RT_EOK)
+            {
+                resume_success = RT_TRUE;
+                /* Wait for a while to verify thread resumes work */
+                rt_thread_mdelay(200);
+            }
+        }
+    }
+
+    /* End test */
+    suspend_test_done = RT_TRUE;
+
+    /* Send semaphore to notify test completion */
+    rt_sem_release(sync_sem);
+
+    /* Keep running until deleted */
+    while (1)
+    {
+        rt_thread_mdelay(100);
+    }
+}
+
+/* Thread that holds the mutex */
+static void mutex_holder_thread(void *parameter)
+{
+    if (rt_mutex_take(test_mutex, RT_WAITING_FOREVER) == RT_EOK)
+    {
+        holder_got_mutex = RT_TRUE;
+
+        /* Simulate critical section work */
+        for (int i = 0; i < 1000 && !test_completed; i++)
+        {
+            shared_counter++;
+            if (i % 200 == 0)
+            {
+                rt_thread_mdelay(10);
+            }
+        }
+
+        if (!test_completed)
+        {
+            rt_mutex_release(test_mutex);
+        }
+    }
+    rt_kprintf("Holder thread exiting\n");
+
+    /* Keep running until deleted */
+    while (1)
+    {
+        rt_thread_mdelay(100);
+    }
+}
+
+/* Thread that waits for the mutex */
+static void mutex_waiter_thread(void *parameter)
+{
+    /* Wait a bit to ensure holder gets mutex first */
+    rt_thread_mdelay(50);
+
+    rt_err_t result = rt_mutex_take(test_mutex, rt_tick_from_millisecond(1500));
+    if (result == RT_EOK)
+    {
+        shared_counter += 1000;
+        rt_mutex_release(test_mutex);
+    }
+    else
+    {
+        /* Timeout indicates deadlock - holder is suspended and cannot release lock */
+        deadlock_detected = RT_TRUE;
+        rt_kprintf("Deadlock detected: waiter timeout (holder suspended with mutex)\n");
+    }
+
+    /* Keep running until deleted */
+    while (1)
+    {
+        rt_thread_mdelay(100);
+    }
+}
+
+void simple_thread_entry(void *param)
+{
+    volatile rt_bool_t *flag = (volatile rt_bool_t *)param;
+    *flag                    = RT_TRUE;
+
+    /* Keep the thread running until it's suspended and deleted */
+    while (1)
+    {
+        rt_thread_mdelay(100);
+    }
+}
+
+/* Test normal usage of rt_thread_suspend function */
+static void test_suspend_force_normal_usage(void)
+{
+    /* Reset global variables */
+    work_counter      = 0;
+    suspend_test_done = RT_FALSE;
+    suspend_success   = RT_FALSE;
+    resume_success    = RT_FALSE;
+
+    /* Create synchronization semaphore */
+    sync_sem = rt_sem_create("sync", 0, RT_IPC_FLAG_FIFO);
+    uassert_not_null(sync_sem);
+
+    /* Create target work thread */
+    target_thread = rt_thread_create("target",
+                                     target_work_thread,
+                                     RT_NULL,
+                                     THREAD_STACK_SIZE,
+                                     TEST_THREAD_PRIORITY,
+                                     THREAD_TIMESLICE);
+
+    uassert_not_null(target_thread);
+
+    /* Create monitor thread */
+    monitor_thread = rt_thread_create("monitor",
+                                      monitor_control_thread,
+                                      RT_NULL,
+                                      THREAD_STACK_SIZE,
+                                      UTEST_THR_PRIORITY,
+                                      THREAD_TIMESLICE);
+
+    uassert_not_null(monitor_thread);
+
+    /* Start threads */
+    rt_thread_startup(target_thread);
+    rt_thread_startup(monitor_thread);
+
+    /* Wait for test completion */
+    rt_sem_take(sync_sem, RT_WAITING_FOREVER);
+
+    /* Wait for a while to ensure threads exit normally */
+    rt_thread_mdelay(100);
+
+    /* Verify test results */
+    uassert_true(suspend_success);
+    uassert_true(resume_success);
+    uassert_true(work_counter > 0);
+
+    /* Clean up resources */
+    if (sync_sem)
+    {
+        rt_sem_delete(sync_sem);
+        sync_sem = RT_NULL;
+    }
+
+    /* Delete threads */
+    if (target_thread != RT_NULL)
+    {
+        rt_thread_delete(target_thread);
+        target_thread = RT_NULL;
+    }
+
+    if (monitor_thread != RT_NULL)
+    {
+        rt_thread_delete(monitor_thread);
+        monitor_thread = RT_NULL;
+    }
+}
+
+/* Basic API test */
+static void test_suspend_force_api_basic(void)
+{
+    rt_thread_t api_thread;
+
+    /* Reset global variables */
+    thread_started     = RT_FALSE;
+    thread_should_exit = RT_FALSE;
+
+    /* Create a simple test thread */
+    api_thread = rt_thread_create("api_test",
+                                  simple_thread_entry,
+                                  (void *)&thread_started,
+                                  THREAD_STACK_SIZE,
+                                  UTEST_THR_PRIORITY,
+                                  THREAD_TIMESLICE);
+
+    uassert_not_null(api_thread);
+
+    rt_thread_startup(api_thread);
+    rt_thread_mdelay(50); /* Wait for thread to start */
+
+    uassert_true(thread_started);
+
+    /* Test basic suspend functionality */
+    rt_err_t result = rt_thread_suspend(api_thread);
+    uassert_true(result == RT_EOK);
+
+    rt_schedule();
+    rt_thread_mdelay(100);
+
+    /* Resume thread */
+    result = rt_thread_resume(api_thread);
+    uassert_true(result == RT_EOK);
+
+    rt_thread_mdelay(50);
+
+    /* Clean up - delete the thread directly */
+    if (api_thread != RT_NULL)
+    {
+        rt_thread_delete(api_thread);
+        api_thread = RT_NULL;
+    }
+
+    /* Reset global variables for next test */
+    thread_started     = RT_FALSE;
+    thread_should_exit = RT_FALSE;
+}
+
+/* Test suspend on thread that is created but not started */
+static void test_suspend_force_not_started_thread(void)
+{
+    rt_thread_t not_started_thread;
+
+    /* Create a thread but don't start it */
+    not_started_thread = rt_thread_create("not_started",
+                                          simple_thread_entry,
+                                          (void *)&thread_started,
+                                          THREAD_STACK_SIZE,
+                                          UTEST_THR_PRIORITY,
+                                          THREAD_TIMESLICE);
+
+    uassert_not_null(not_started_thread);
+
+    /* Verify thread is in INIT state */
+    uassert_true((RT_SCHED_CTX(not_started_thread).stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT);
+
+    /* Try to suspend a thread that hasn't been started yet */
+    rt_err_t suspend_result = rt_thread_suspend(not_started_thread);
+    rt_schedule();
+    uassert_true(suspend_result == -RT_ERROR);
+
+    /* Try to resume the not-started thread */
+    rt_err_t resume_result = rt_thread_resume(not_started_thread);
+    uassert_true(resume_result == -RT_EINVAL);
+
+    /* Now start the thread to see if it works normally */
+    rt_err_t startup_result = rt_thread_startup(not_started_thread);
+    uassert_true(startup_result == RT_EOK);
+
+    /* Wait a bit to see if thread starts normally */
+    rt_thread_mdelay(100);
+
+    /* The thread should have started successfully despite previous suspend/resume calls */
+    uassert_true(thread_started == RT_TRUE);
+
+    /* Clean up */
+    if (not_started_thread != RT_NULL)
+    {
+        rt_thread_delete(not_started_thread);
+        not_started_thread = RT_NULL;
+    }
+
+    /* Reset flag for next test */
+    thread_started = RT_FALSE;
+}
+
+/* Test deadlock risk */
+static void test_suspend_force_deadlock_risk(void)
+{
+    /* Reset global variables */
+    shared_counter    = 0;
+    holder_got_mutex  = RT_FALSE;
+    deadlock_detected = RT_FALSE;
+    test_completed    = RT_FALSE;
+
+    /* Create mutex */
+    test_mutex = rt_mutex_create("test_mutex", RT_IPC_FLAG_PRIO);
+    uassert_not_null(test_mutex);
+
+    /* Create and start holder thread */
+    holder_thread = rt_thread_create("holder", mutex_holder_thread, RT_NULL,
+                                     THREAD_STACK_SIZE, UTEST_THR_PRIORITY, THREAD_TIMESLICE);
+    uassert_not_null(holder_thread);
+    rt_thread_startup(holder_thread);
+
+    /* Create and start waiter thread */
+    waiter_thread = rt_thread_create("waiter", mutex_waiter_thread, RT_NULL,
+                                     THREAD_STACK_SIZE, UTEST_THR_PRIORITY + 1, THREAD_TIMESLICE);
+    uassert_not_null(waiter_thread);
+    /* Now start waiter thread, it will try to acquire mutex held by suspended thread */
+    rt_thread_startup(waiter_thread);
+
+    /* Wait for holder to get mutex */
+    int timeout = 100; /* 1 second timeout */
+    while (!holder_got_mutex && timeout-- > 0)
+    {
+        rt_thread_mdelay(10);
+    }
+
+    uassert_true(holder_got_mutex);
+
+    /* This is the critical test! Suspend thread that holds the mutex */
+    rt_err_t suspend_result = rt_thread_suspend(holder_thread);
+    uassert_true(suspend_result == RT_EOK);
+    rt_kprintf("Suspended holder thread (which holds the mutex)\n");
+
+    rt_schedule();
+
+    /* Wait for waiter thread to try acquiring lock */
+    rt_thread_mdelay(2000);
+
+    uassert_true(deadlock_detected == RT_TRUE);
+
+    /* Resume thread */
+    rt_err_t resume_result = rt_thread_resume(holder_thread);
+    uassert_true(resume_result == RT_EOK);
+    rt_kprintf("Resumed holder thread\n");
+
+    test_completed = RT_TRUE;
+
+    /* Wait for threads to complete */
+    rt_thread_mdelay(1000);
+
+    /* Verify rt_thread_suspend and rt_thread_resume executed successfully */
+    uassert_true(suspend_result == RT_EOK);
+    uassert_true(resume_result == RT_EOK);
+
+    /* Verify system didn't crash, threads can work normally */
+    uassert_true(shared_counter > 0);
+
+    /* Clean up resources */
+    if (test_mutex)
+    {
+        rt_mutex_delete(test_mutex);
+        test_mutex = RT_NULL;
+    }
+
+    /* Delete threads */
+    if (holder_thread != RT_NULL)
+    {
+        rt_thread_delete(holder_thread);
+        holder_thread = RT_NULL;
+    }
+
+    if (waiter_thread != RT_NULL)
+    {
+        rt_thread_delete(waiter_thread);
+        waiter_thread = RT_NULL;
+    }
+
+    /* Wait again to ensure threads are cleaned up */
+    rt_thread_mdelay(200);
+}
+
+static rt_err_t utest_tc_init(void)
+{
+    return RT_EOK;
+}
+
+static rt_err_t utest_tc_cleanup(void)
+{
+    /* Reset all global variables to ensure clean state between tests */
+    work_counter       = 0;
+    suspend_test_done  = RT_FALSE;
+    suspend_success    = RT_FALSE;
+    resume_success     = RT_FALSE;
+    shared_counter     = 0;
+    holder_got_mutex   = RT_FALSE;
+    deadlock_detected  = RT_FALSE;
+    test_completed     = RT_FALSE;
+    thread_started     = RT_FALSE;
+    thread_should_exit = RT_FALSE;
+
+    /* Clean up any remaining resources - safety check */
+    if (sync_sem != RT_NULL)
+    {
+        rt_sem_delete(sync_sem);
+        sync_sem = RT_NULL;
+    }
+
+    if (test_mutex != RT_NULL)
+    {
+        rt_mutex_delete(test_mutex);
+        test_mutex = RT_NULL;
+    }
+
+    /* Ensure all thread pointers are NULL */
+    target_thread  = RT_NULL;
+    monitor_thread = RT_NULL;
+    holder_thread  = RT_NULL;
+    waiter_thread  = RT_NULL;
+
+    /* Give system time to complete cleanup */
+    rt_thread_mdelay(50);
+
+    return RT_EOK;
+}
+
+static void testcase(void)
+{
+    UTEST_UNIT_RUN(test_suspend_force_api_basic);
+    UTEST_UNIT_RUN(test_suspend_force_not_started_thread);
+    UTEST_UNIT_RUN(test_suspend_force_normal_usage);
+    UTEST_UNIT_RUN(test_suspend_force_deadlock_risk);
+}
+UTEST_TC_EXPORT(testcase, "testcases.kernel.thread_suspend", utest_tc_init, utest_tc_cleanup, 30);
+

+ 31 - 3
src/thread.c

@@ -947,14 +947,19 @@ rt_err_t rt_thread_suspend_to_list(rt_thread_t thread, rt_list_t *susp_list, int
     /* parameter check */
     RT_ASSERT(thread != RT_NULL);
     RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
-    RT_ASSERT(thread == rt_thread_self());
 
     LOG_D("thread suspend:  %s", thread->parent.name);
 
     rt_sched_lock(&slvl);
 
     stat = rt_sched_thread_get_stat(thread);
-    if ((stat != RT_THREAD_READY) && (stat != RT_THREAD_RUNNING))
+    if (stat == RT_THREAD_SUSPEND)
+    {
+        rt_sched_unlock(slvl);
+        /* Already suspended, just set status to success.  */
+        return RT_EOK;
+    }
+    else if ((stat != RT_THREAD_READY) && (stat != RT_THREAD_RUNNING))
     {
         LOG_D("thread suspend: thread disorder, 0x%2x", RT_SCHED_CTX(thread).stat);
         rt_sched_unlock(slvl);
@@ -1042,7 +1047,30 @@ RTM_EXPORT(rt_thread_suspend_with_flag);
 /**
  * @brief   This function will suspend the specified thread and change it to suspend state.
  *
- * @param   thread Handle of the thread to be suspended.
+ * @note    This function can suspend both the current thread itself and other threads.
+ *          Please use this API with extreme caution when suspending other threads.
+ *
+ *          When suspending the current thread:
+ *              rt_thread_suspend(rt_thread_self());
+ *          This is generally safe as the thread voluntarily suspends itself.
+ *
+ *          When suspending other threads:
+ *          You have no way of knowing what code another thread is executing when you suspend it.
+ *          If you suspend a thread while it is sharing a resource with other threads and occupying
+ *          this resource (such as holding a mutex, semaphore, or other synchronization objects),
+ *          deadlock or resource starvation can occur very easily.
+ *
+ * @warning Suspending other threads arbitrarily can lead to:
+ *          - Deadlock situations
+ *          - Resource starvation
+ *          - System instability
+ *          - Unpredictable behavior
+ *
+ *          Only suspend other threads when you have full control over the system state
+ *          and understand the implications.
+ *
+ * @param   thread Handle of the thread to be suspended. Can be the current thread
+ *                 (rt_thread_self()) or any other thread.
  *
  * @return  Return the operation status. If the return value is `RT_EOK`, the
  *          function is successfully executed.