diff --git a/src/scheduler_mp.c b/src/scheduler_mp.c index 4bcbafad6dd..55d3d4a93ff 100644 --- a/src/scheduler_mp.c +++ b/src/scheduler_mp.c @@ -39,8 +39,8 @@ #include #include -#define DBG_TAG "kernel.scheduler" -#define DBG_LVL DBG_INFO +#define DBG_TAG "kernel.scheduler" +#define DBG_LVL DBG_INFO #include rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]; @@ -48,16 +48,18 @@ static struct rt_spinlock _mp_scheduler_lock; #define SCHEDULER_LOCK_FLAG(percpu) ((percpu)->sched_lock_flag) -#define SCHEDULER_ENTER_CRITICAL(curthr) \ - do \ - { \ - if (curthr) RT_SCHED_CTX(curthr).critical_lock_nest++; \ +#define SCHEDULER_ENTER_CRITICAL(curthr) \ + do \ + { \ + if (curthr) \ + RT_SCHED_CTX(curthr).critical_lock_nest++; \ } while (0) -#define SCHEDULER_EXIT_CRITICAL(curthr) \ - do \ - { \ - if (curthr) RT_SCHED_CTX(curthr).critical_lock_nest--; \ +#define SCHEDULER_EXIT_CRITICAL(curthr) \ + do \ + { \ + if (curthr) \ + RT_SCHED_CTX(curthr).critical_lock_nest--; \ } while (0) #define SCHEDULER_CONTEXT_LOCK(percpu) \ @@ -202,7 +204,7 @@ rt_inline rt_base_t _get_global_highest_ready_prio(void) return highest_ready_priority; } -rt_inline rt_base_t _get_local_highest_ready_prio(struct rt_cpu* pcpu) +rt_inline rt_base_t _get_local_highest_ready_prio(struct rt_cpu *pcpu) { rt_ubase_t number; rt_ubase_t local_highest_ready_priority; @@ -226,7 +228,7 @@ rt_inline rt_base_t _get_global_highest_ready_prio(void) return __rt_ffs(rt_thread_ready_priority_group) - 1; } -rt_inline rt_base_t _get_local_highest_ready_prio(struct rt_cpu* pcpu) +rt_inline rt_base_t _get_local_highest_ready_prio(struct rt_cpu *pcpu) { return __rt_ffs(pcpu->priority_group) - 1; } @@ -236,11 +238,11 @@ rt_inline rt_base_t _get_local_highest_ready_prio(struct rt_cpu* pcpu) /* * get the highest priority thread in ready queue */ -static struct rt_thread* _scheduler_get_highest_priority_thread(rt_ubase_t *highest_prio) +static struct rt_thread *_scheduler_get_highest_priority_thread(rt_ubase_t *highest_prio) { struct rt_thread *highest_priority_thread; rt_ubase_t highest_ready_priority, local_highest_ready_priority; - struct rt_cpu* pcpu = rt_cpu_self(); + struct rt_cpu *pcpu = rt_cpu_self(); highest_ready_priority = _get_global_highest_ready_prio(); local_highest_ready_priority = _get_local_highest_ready_prio(pcpu); @@ -282,11 +284,13 @@ static void _sched_insert_thread_locked(struct rt_thread *thread) int cpu_id; int bind_cpu; rt_uint32_t cpu_mask; + rt_uint8_t stat_before; + rt_bool_t insert_at_head; if ((RT_SCHED_CTX(thread).stat & RT_THREAD_STAT_MASK) == RT_THREAD_READY) { /* already in ready queue */ - return ; + return; } else if (RT_SCHED_CTX(thread).oncpu != RT_CPU_DETACHED) { @@ -295,13 +299,26 @@ static void _sched_insert_thread_locked(struct rt_thread *thread) * this is a RUNNING thread. So here we reset it's status and let it go. */ RT_SCHED_CTX(thread).stat = RT_THREAD_RUNNING | (RT_SCHED_CTX(thread).stat & ~RT_THREAD_STAT_MASK); - return ; + return; } /* READY thread, insert to ready queue */ - RT_SCHED_CTX(thread).stat = RT_THREAD_READY | (RT_SCHED_CTX(thread).stat & ~RT_THREAD_STAT_MASK); + stat_before = RT_SCHED_CTX(thread).stat; + RT_SCHED_CTX(thread).stat = RT_THREAD_READY | (stat_before & ~RT_THREAD_STAT_MASK); + + /* A thread that was preempted while it still had time slices left keeps the + * head of the ready list, so that it is scheduled first next time. + * + * Every other case -- time slice consumed (@c RT_THREAD_STAT_YIELD set), + * resumed from an IPC pending list, or freshly started -- is appended to the + * tail. Inserting those at the head would reverse the order in which + * same-priority threads became ready, which breaks the FIFO fairness of the + * ready list (see RT-Thread issue #8459). + */ + insert_at_head = (((stat_before & RT_THREAD_STAT_MASK) == RT_THREAD_RUNNING) && + ((stat_before & RT_THREAD_STAT_YIELD_MASK) == 0)); - cpu_id = rt_hw_cpu_id(); + cpu_id = rt_hw_cpu_id(); bind_cpu = RT_SCHED_CTX(thread).bind_cpu; /* insert thread to ready list */ @@ -312,17 +329,15 @@ static void _sched_insert_thread_locked(struct rt_thread *thread) #endif /* RT_THREAD_PRIORITY_MAX > 32 */ rt_thread_ready_priority_group |= RT_SCHED_PRIV(thread).number_mask; - /* there is no time slices left(YIELD), inserting thread before ready list*/ - if((RT_SCHED_CTX(thread).stat & RT_THREAD_STAT_YIELD_MASK) != 0) + if (insert_at_head) { - rt_list_insert_before(&(rt_thread_priority_table[RT_SCHED_PRIV(thread).current_priority]), - &RT_THREAD_LIST_NODE(thread)); + rt_list_insert_after(&(rt_thread_priority_table[RT_SCHED_PRIV(thread).current_priority]), + &RT_THREAD_LIST_NODE(thread)); } - /* there are some time slices left, inserting thread after ready list to schedule it firstly at next time*/ else { - rt_list_insert_after(&(rt_thread_priority_table[RT_SCHED_PRIV(thread).current_priority]), - &RT_THREAD_LIST_NODE(thread)); + rt_list_insert_before(&(rt_thread_priority_table[RT_SCHED_PRIV(thread).current_priority]), + &RT_THREAD_LIST_NODE(thread)); } cpu_mask = RT_CPU_MASK ^ (1 << cpu_id); @@ -337,17 +352,15 @@ static void _sched_insert_thread_locked(struct rt_thread *thread) #endif /* RT_THREAD_PRIORITY_MAX > 32 */ pcpu->priority_group |= RT_SCHED_PRIV(thread).number_mask; - /* there is no time slices left(YIELD), inserting thread before ready list*/ - if((RT_SCHED_CTX(thread).stat & RT_THREAD_STAT_YIELD_MASK) != 0) + if (insert_at_head) { - rt_list_insert_before(&(rt_cpu_index(bind_cpu)->priority_table[RT_SCHED_PRIV(thread).current_priority]), - &RT_THREAD_LIST_NODE(thread)); + rt_list_insert_after(&(rt_cpu_index(bind_cpu)->priority_table[RT_SCHED_PRIV(thread).current_priority]), + &RT_THREAD_LIST_NODE(thread)); } - /* there are some time slices left, inserting thread after ready list to schedule it firstly at next time*/ else { - rt_list_insert_after(&(rt_cpu_index(bind_cpu)->priority_table[RT_SCHED_PRIV(thread).current_priority]), - &RT_THREAD_LIST_NODE(thread)); + rt_list_insert_before(&(rt_cpu_index(bind_cpu)->priority_table[RT_SCHED_PRIV(thread).current_priority]), + &RT_THREAD_LIST_NODE(thread)); } if (cpu_id != bind_cpu) @@ -435,15 +448,15 @@ void rt_system_scheduler_init(void) rt_spin_lock_init(&_mp_scheduler_lock); - for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++) + for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset++) { rt_list_init(&rt_thread_priority_table[offset]); } for (cpu = 0; cpu < RT_CPUS_NR; cpu++) { - struct rt_cpu *pcpu = rt_cpu_index(cpu); - for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++) + struct rt_cpu *pcpu = rt_cpu_index(cpu); + for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset++) { rt_list_init(&pcpu->priority_table[offset]); } @@ -566,7 +579,9 @@ rt_err_t rt_sched_lock(rt_sched_lock_level_t *plvl) { rt_base_t level; if (!plvl) + { return -RT_EINVAL; + } SCHEDULER_LOCK(level); *plvl = level; @@ -642,8 +657,7 @@ static rt_thread_t _prepare_context_switch_locked(int cpu_id, if ((RT_SCHED_CTX(current_thread).stat & RT_THREAD_STAT_MASK) == RT_THREAD_RUNNING) { /* check if current thread can be running on current core again */ - if (RT_SCHED_CTX(current_thread).bind_cpu == RT_CPUS_NR - || RT_SCHED_CTX(current_thread).bind_cpu == cpu_id) + if (RT_SCHED_CTX(current_thread).bind_cpu == RT_CPUS_NR || RT_SCHED_CTX(current_thread).bind_cpu == cpu_id) { /* if current_thread is the highest runnable thread */ if (RT_SCHED_PRIV(current_thread).current_priority < highest_ready_priority) @@ -777,12 +791,12 @@ static void _sched_thread_process_signal(struct rt_thread *current_thread) /* lock is released above */ } -#define SCHED_THREAD_PREPROCESS_SIGNAL(pcpu, curthr) \ - do \ - { \ - SCHEDULER_CONTEXT_LOCK(pcpu); \ - _sched_thread_preprocess_signal(curthr); \ - SCHEDULER_CONTEXT_UNLOCK(pcpu); \ +#define SCHED_THREAD_PREPROCESS_SIGNAL(pcpu, curthr) \ + do \ + { \ + SCHEDULER_CONTEXT_LOCK(pcpu); \ + _sched_thread_preprocess_signal(curthr); \ + SCHEDULER_CONTEXT_UNLOCK(pcpu); \ } while (0) #define SCHED_THREAD_PREPROCESS_SIGNAL_LOCKED(curthr) \ _sched_thread_preprocess_signal(curthr) @@ -822,12 +836,12 @@ rt_err_t rt_sched_unlock_n_resched(rt_sched_lock_level_t level) { struct rt_thread *to_thread; struct rt_thread *current_thread; - struct rt_cpu *pcpu; - int cpu_id; - rt_err_t error = RT_EOK; + struct rt_cpu *pcpu; + int cpu_id; + rt_err_t error = RT_EOK; cpu_id = rt_hw_cpu_id(); - pcpu = rt_cpu_index(cpu_id); + pcpu = rt_cpu_index(cpu_id); current_thread = pcpu->current_thread; if (!current_thread) @@ -923,15 +937,15 @@ void rt_schedule(void) rt_base_t level; struct rt_thread *to_thread; struct rt_thread *current_thread; - struct rt_cpu *pcpu; - int cpu_id; + struct rt_cpu *pcpu; + int cpu_id; /* enter ciritical region of percpu scheduling context */ level = rt_hw_local_irq_disable(); /* get percpu scheduling context */ cpu_id = rt_hw_cpu_id(); - pcpu = rt_cpu_index(cpu_id); + pcpu = rt_cpu_index(cpu_id); current_thread = pcpu->current_thread; /* whether do switch in interrupt */ @@ -939,7 +953,7 @@ void rt_schedule(void) { pcpu->irq_switch_flag = 1; rt_hw_local_irq_enable(level); - return ; /* -RT_ESCHEDISR */ + return; /* -RT_ESCHEDISR */ } /* forbid any recursive entries of schedule() */ @@ -1015,16 +1029,16 @@ void rt_schedule(void) */ void rt_scheduler_do_irq_switch(void *context) { - int cpu_id; - rt_base_t level; - struct rt_cpu *pcpu; + int cpu_id; + rt_base_t level; + struct rt_cpu *pcpu; struct rt_thread *to_thread; struct rt_thread *current_thread; level = rt_hw_local_irq_disable(); cpu_id = rt_hw_cpu_id(); - pcpu = rt_cpu_index(cpu_id); + pcpu = rt_cpu_index(cpu_id); current_thread = pcpu->current_thread; /* forbid any recursive entries of schedule() */ @@ -1147,7 +1161,7 @@ void rt_sched_thread_init_priv(struct rt_thread *thread, rt_uint32_t tick, rt_ui /* priority init */ RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX); - RT_SCHED_PRIV(thread).init_priority = priority; + RT_SCHED_PRIV(thread).init_priority = priority; RT_SCHED_PRIV(thread).current_priority = priority; /* don't add to scheduler queue as init thread */ @@ -1166,7 +1180,6 @@ void rt_sched_thread_init_priv(struct rt_thread *thread, rt_uint32_t tick, rt_ui /* lock init */ RT_SCHED_CTX(thread).critical_lock_nest = 0; #endif /* RT_USING_SMP */ - } /** @@ -1220,7 +1233,7 @@ void rt_sched_thread_startup(struct rt_thread *thread) */ void rt_sched_post_ctx_switch(struct rt_thread *thread) { - struct rt_cpu* pcpu = rt_cpu_self(); + struct rt_cpu *pcpu = rt_cpu_self(); rt_thread_t from_thread = pcpu->current_thread; RT_ASSERT(rt_hw_interrupt_is_disabled()); @@ -1269,13 +1282,13 @@ void rt_exit_critical_safe(rt_base_t critical_level) int dummy = 1; _critical_error_occurred = 1; - rt_kprintf("%s: un-compatible critical level\n" \ + rt_kprintf("%s: un-compatible critical level\n" "\tCurrent %d\n\tCaller %d\n", __func__, RT_SCHED_CTX(current_thread).critical_lock_nest, critical_level); rt_backtrace(); - while (dummy) ; + while (dummy); } } rt_exit_critical(); @@ -1427,7 +1440,9 @@ void rt_exit_critical(void) FREE_THREAD_SELF(level); if (need_resched) + { rt_schedule(); + } } else { diff --git a/src/scheduler_up.c b/src/scheduler_up.c index 8560166fd17..80084b6e1ab 100644 --- a/src/scheduler_up.c +++ b/src/scheduler_up.c @@ -39,8 +39,8 @@ #include #include -#define DBG_TAG "kernel.scheduler" -#define DBG_LVL DBG_INFO +#define DBG_TAG "kernel.scheduler" +#define DBG_LVL DBG_INFO #include rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]; @@ -101,7 +101,7 @@ void rt_scheduler_switch_sethook(void (*hook)(struct rt_thread *tid)) * @{ */ -static struct rt_thread* _scheduler_get_highest_priority_thread(rt_ubase_t *highest_prio) +static struct rt_thread *_scheduler_get_highest_priority_thread(rt_ubase_t *highest_prio) { struct rt_thread *highest_priority_thread; rt_ubase_t highest_ready_priority; @@ -144,7 +144,9 @@ rt_err_t rt_sched_lock(rt_sched_lock_level_t *plvl) { rt_base_t level; if (!plvl) + { return -RT_EINVAL; + } level = rt_hw_interrupt_disable(); *plvl = level; @@ -215,7 +217,7 @@ void rt_system_scheduler_init(void) LOG_D("start scheduler: max priority 0x%02x", RT_THREAD_PRIORITY_MAX); - for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++) + for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset++) { rt_list_init(&rt_thread_priority_table[offset]); } @@ -310,8 +312,7 @@ void rt_schedule(void) { to_thread = curr_thread; } - else if (RT_SCHED_PRIV(curr_thread).current_priority == highest_ready_priority - && (RT_SCHED_CTX(curr_thread).stat & RT_THREAD_STAT_YIELD_MASK) == 0) + else if (RT_SCHED_PRIV(curr_thread).current_priority == highest_ready_priority && (RT_SCHED_CTX(curr_thread).stat & RT_THREAD_STAT_YIELD_MASK) == 0) { to_thread = curr_thread; } @@ -325,7 +326,7 @@ void rt_schedule(void) { /* if the destination thread is not the same as current thread */ rt_current_priority = (rt_uint8_t)highest_ready_priority; - from_thread = curr_thread; + from_thread = curr_thread; rt_cpu_self()->current_thread = to_thread; RT_OBJECT_HOOK_CALL(rt_scheduler_hook, (from_thread, to_thread)); @@ -346,11 +347,11 @@ void rt_schedule(void) /* switch to new thread */ interrupt_nest = rt_interrupt_get_nest(); LOG_D("[%d]switch to priority#%d " - "thread:%.*s(sp:0x%08x), " - "from thread:%.*s(sp: 0x%08x)", - interrupt_nest, highest_ready_priority, - RT_NAME_MAX, to_thread->parent.name, to_thread->sp, - RT_NAME_MAX, from_thread->parent.name, from_thread->sp); + "thread:%.*s(sp:0x%08x), " + "from thread:%.*s(sp: 0x%08x)", + interrupt_nest, highest_ready_priority, + RT_NAME_MAX, to_thread->parent.name, to_thread->sp, + RT_NAME_MAX, from_thread->parent.name, from_thread->sp); RT_SCHEDULER_STACK_CHECK(to_thread); @@ -361,7 +362,7 @@ void rt_schedule(void) RT_OBJECT_HOOK_CALL(rt_scheduler_switch_hook, (from_thread)); rt_hw_context_switch((rt_uintptr_t)&from_thread->sp, - (rt_uintptr_t)&to_thread->sp); + (rt_uintptr_t)&to_thread->sp); /* enable interrupt */ rt_hw_interrupt_enable(level); @@ -392,7 +393,7 @@ void rt_schedule(void) LOG_D("switch in interrupt"); rt_hw_context_switch_interrupt((rt_uintptr_t)&from_thread->sp, - (rt_uintptr_t)&to_thread->sp, from_thread, to_thread); + (rt_uintptr_t)&to_thread->sp, from_thread, to_thread); } } else @@ -465,7 +466,7 @@ void rt_sched_thread_init_priv(struct rt_thread *thread, rt_uint32_t tick, rt_ui /* priority init */ RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX); - RT_SCHED_PRIV(thread).init_priority = priority; + RT_SCHED_PRIV(thread).init_priority = priority; RT_SCHED_PRIV(thread).current_priority = priority; /* don't add to scheduler queue as init thread */ @@ -491,6 +492,7 @@ void rt_sched_thread_init_priv(struct rt_thread *thread, rt_uint32_t tick, rt_ui void rt_sched_insert_thread(struct rt_thread *thread) { rt_base_t level; + rt_uint8_t stat_before; RT_ASSERT(thread != RT_NULL); @@ -505,17 +507,27 @@ void rt_sched_insert_thread(struct rt_thread *thread) } /* READY thread, insert to ready queue */ - RT_SCHED_CTX(thread).stat = RT_THREAD_READY | (RT_SCHED_CTX(thread).stat & ~RT_THREAD_STAT_MASK); - /* there is no time slices left(YIELD), inserting thread before ready list*/ - if((RT_SCHED_CTX(thread).stat & RT_THREAD_STAT_YIELD_MASK) != 0) + stat_before = RT_SCHED_CTX(thread).stat; + RT_SCHED_CTX(thread).stat = RT_THREAD_READY | (stat_before & ~RT_THREAD_STAT_MASK); + + /* A thread that was preempted while it still had time slices left keeps the + * head of the ready list, so that it is scheduled first next time. + * + * Every other case -- time slice consumed (@c RT_THREAD_STAT_YIELD set), + * resumed from an IPC pending list, or freshly started -- is appended to the + * tail. Inserting those at the head would reverse the order in which + * same-priority threads became ready, which breaks the FIFO fairness of the + * ready list (see RT-Thread issue #8459). + */ + if (((stat_before & RT_THREAD_STAT_MASK) == RT_THREAD_RUNNING) && + ((stat_before & RT_THREAD_STAT_YIELD_MASK) == 0)) { - rt_list_insert_before(&(rt_thread_priority_table[RT_SCHED_PRIV(thread).current_priority]), - &RT_THREAD_LIST_NODE(thread)); + rt_list_insert_after(&(rt_thread_priority_table[RT_SCHED_PRIV(thread).current_priority]), + &RT_THREAD_LIST_NODE(thread)); } - /* there are some time slices left, inserting thread after ready list to schedule it firstly at next time*/ else { - rt_list_insert_after(&(rt_thread_priority_table[RT_SCHED_PRIV(thread).current_priority]), + rt_list_insert_before(&(rt_thread_priority_table[RT_SCHED_PRIV(thread).current_priority]), &RT_THREAD_LIST_NODE(thread)); } @@ -605,13 +617,13 @@ void rt_exit_critical_safe(rt_base_t critical_level) int dummy = 1; _critical_error_occurred = 1; - rt_kprintf("%s: un-compatible critical level\n" \ + rt_kprintf("%s: un-compatible critical level\n" "\tCurrent %d\n\tCaller %d\n", __func__, rt_scheduler_lock_nest, critical_level); rt_backtrace(); - while (dummy) ; + while (dummy); } } rt_hw_interrupt_enable(level); @@ -686,7 +698,7 @@ void rt_exit_critical(void) /* disable interrupt */ level = rt_hw_interrupt_disable(); - rt_scheduler_lock_nest --; + rt_scheduler_lock_nest--; if (rt_scheduler_lock_nest <= 0) { rt_scheduler_lock_nest = 0; diff --git a/src/utest/SConscript b/src/utest/SConscript index 52aca3dcf69..2f4451dd293 100644 --- a/src/utest/SConscript +++ b/src/utest/SConscript @@ -65,6 +65,7 @@ if GetDepend(['RT_UTEST_SCHEDULER']): src += ['sched_timed_mtx_tc.c'] src += ['sched_mtx_tc.c'] src += ['sched_sem_tc.c', 'sched_thread_tc.c'] + src += ['sched_ready_order_tc.c'] group = DefineGroup('utestcases', src, depend = ['RT_USING_UTESTCASES'], CPPPATH = CPPPATH) diff --git a/src/utest/sched_ready_order_tc.c b/src/utest/sched_ready_order_tc.c new file mode 100644 index 00000000000..71a29b78332 --- /dev/null +++ b/src/utest/sched_ready_order_tc.c @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-10-20 Yunlk the first version + */ + +/** + * Test Case Name: Ready List Insertion Order Test (core.scheduler_ready_order) + * + * Test Objectives: + * - Verify that same-priority threads are inserted into the ready list in a + * first-in-first-out manner, so that the execution order matches the order in + * which they became ready. + * - Cover the two paths that currently bypass the FIFO order: + * - Threads resumed from an IPC (semaphore) pending list. + * - Threads started back-to-back via rt_thread_startup(). + * - List specific functions or APIs to be tested: + * - rt_sem_create / rt_sem_take / rt_sem_release + * - rt_thread_create / rt_thread_startup + * - rt_schedule_insert_thread (indirectly, through the two paths above) + * + * Test Scenarios: + * - **Semaphore Wakeup Order:** + * 1. Create a semaphore with RT_IPC_FLAG_PRIO and two same-priority worker threads. + * 2. Start the worker A first and let it block on the semaphore, then start B and + * let it block as well. The pending list is therefore deterministically A -> B. + * 3. Release the semaphore twice, which wakes A first and then B. + * 4. Let the workers run and record their execution order. + * + * - **Startup Order:** + * 1. Create two same-priority worker threads C and D. + * 2. Startup C and then D without yielding in between. + * 3. Let them run and record their execution order. + * + * Verification Metrics: + * - **Pass:** The semaphore case records "AB", i.e. the thread woken first runs first. + * - **Pass:** The startup case records "CD", i.e. the thread started first runs first. + * - On SMP the workers are bound to the CPU running this test case, so that the + * insertion order is observable without cross-core scheduling noise. + * + * Dependencies: + * - Hardware requirements + * - None. A single core is enough; more cores are also supported by binding. + * (This is met by the qemu-vexpress-a9 / qemu-virt64-riscv BSP). + * - Software configuration + * - `RT_USING_UTEST` must be enabled (`RT-Thread Utestcases`). + * - `Scheduler Test` must be enabled (`RT-Thread Utestcases` -> `Kernel Core` -> 'Scheduler Test'). + * - `RT_USING_SEMAPHORE` must be enabled. + * - Environmental assumptions + * - `RT_THREAD_PRIORITY_MAX` must leave at least one priority level between the + * utest thread and the idle thread. + * - Run the test case from the msh prompt: + * `utest_run core.scheduler_ready_order` + * + * Expected Results: + * - The console prints the observed execution order for both scenarios. + * - Final Output: `[ PASSED ] [ result ] testcase (core.scheduler_ready_order)` + */ + +#define __RT_IPC_SOURCE__ + +#include +#include "rthw.h" +#include "utest.h" + +/* worker threads must be lower priority than the utest thread, but above idle */ +#define TEST_THREAD_PRIORITY (UTEST_THR_PRIORITY + 2) + +#if TEST_THREAD_PRIORITY >= RT_THREAD_PRIORITY_MAX - 1 +#error "rtconfig: not enough priority levels between utest thread and idle thread" +#endif + +#define TEST_ORDER_MAX 8 + +static rt_sem_t _order_sem; +static char _run_order[TEST_ORDER_MAX]; +static rt_size_t _run_count; + +static void _order_reset(void) +{ + rt_size_t i; + + for (i = 0; i < TEST_ORDER_MAX; i++) + { + _run_order[i] = '\0'; + } + _run_count = 0; +} + +/* the workers run one at a time on the same core, but keep it interrupt-safe */ +static void _order_record(char c) +{ + rt_base_t level; + + level = rt_hw_interrupt_disable(); + if (_run_count < TEST_ORDER_MAX - 1) + { + _run_order[_run_count++] = c; + } + rt_hw_interrupt_enable(level); +} + +static void _start_worker(rt_thread_t thread) +{ +#ifdef RT_USING_SMP + /* keep the worker on this core, otherwise it may be picked up by an idle + * core immediately and the insertion order cannot be observed at all */ + rt_thread_control(thread, RT_THREAD_CTRL_BIND_CPU, + (void *)(rt_ubase_t)rt_hw_cpu_id()); +#endif /* RT_USING_SMP */ + + rt_thread_startup(thread); +} + +static void _worker_wait_sem(void *parameter) +{ + rt_sem_take(_order_sem, RT_WAITING_FOREVER); + _order_record((char)(rt_ubase_t)parameter); +} + +static void _worker_run(void *parameter) +{ + _order_record((char)(rt_ubase_t)parameter); +} + +/* ------------------------------------------------------------------ */ + +static void _sem_wakeup_order(void) +{ + rt_thread_t worker_a; + rt_thread_t worker_b; + + _order_reset(); + + _order_sem = rt_sem_create("order", 0, RT_IPC_FLAG_PRIO); + uassert_not_null(_order_sem); + + worker_a = rt_thread_create("owA", _worker_wait_sem, (void *)'A', + UTEST_THR_STACK_SIZE, TEST_THREAD_PRIORITY, 5); + worker_b = rt_thread_create("owB", _worker_wait_sem, (void *)'B', + UTEST_THR_STACK_SIZE, TEST_THREAD_PRIORITY, 5); + uassert_not_null(worker_a); + uassert_not_null(worker_b); + + /* start A and let it block first, then B: pending list is A -> B */ + _start_worker(worker_a); + rt_thread_mdelay(50); + _start_worker(worker_b); + rt_thread_mdelay(50); + + /* wake them in pending order, A first and B second */ + rt_sem_release(_order_sem); + rt_sem_release(_order_sem); + + /* let the ready threads run */ + rt_thread_mdelay(100); + + LOG_I("semaphore wakeup order: \"%s\", expect \"AB\"", _run_order); + uassert_str_equal(_run_order, "AB"); + + rt_sem_delete(_order_sem); + _order_sem = RT_NULL; +} + +static void _startup_order(void) +{ + rt_thread_t worker_c; + rt_thread_t worker_d; + + _order_reset(); + + worker_c = rt_thread_create("owC", _worker_run, (void *)'C', + UTEST_THR_STACK_SIZE, TEST_THREAD_PRIORITY, 5); + worker_d = rt_thread_create("owD", _worker_run, (void *)'D', + UTEST_THR_STACK_SIZE, TEST_THREAD_PRIORITY, 5); + uassert_not_null(worker_c); + uassert_not_null(worker_d); + + /* startup C then D without yielding in between */ + _start_worker(worker_c); + _start_worker(worker_d); + + rt_thread_mdelay(100); + + LOG_I("startup order: \"%s\", expect \"CD\"", _run_order); + uassert_str_equal(_run_order, "CD"); +} + +static rt_err_t utest_tc_init(void) +{ + _order_sem = RT_NULL; + _order_reset(); + return RT_EOK; +} + +static rt_err_t utest_tc_cleanup(void) +{ + return RT_EOK; +} + +static void testcase(void) +{ + UTEST_UNIT_RUN(_sem_wakeup_order); + UTEST_UNIT_RUN(_startup_order); +} +UTEST_TC_EXPORT(testcase, "core.scheduler_ready_order", utest_tc_init, utest_tc_cleanup, 10);