From 75bc159896eb4383fbf2008f1cacae624d3a3dbf Mon Sep 17 00:00:00 2001 From: ouyangxiangzhen Date: Thu, 3 Sep 2026 14:10:04 +0800 Subject: [PATCH] sched/tickless: Fix SCHED_RR timeslice accounting on preemption In tickless mode, the scheduler timer is stopped whenever the currently running task requires no time slicing (CLOCK_MAX). When a SCHED_RR task was later switched in, nothing re-armed the timer, so the task could run indefinitely without round-robin rotation. Also, when a SCHED_RR task was preempted, its timeslice counter was not decremented for the time already consumed, effectively giving the task "bonus" CPU time when resumed. Solve both by performing RR accounting on context switches: - nxsched_suspend_roundrobin() charges the elapsed execution time against the timeslice of the RR task being switched out - nxsched_resume_roundrobin() restarts the scheduler timer for the remaining timeslice of the RR task being switched in, so the timer is always armed while an RR task is running This also removes the previous workaround in nxsched_process_timer that triggered the scheduler on every timer tick. Assisted-by: Zhipu GLM-5.3 Signed-off-by: ouyangxiangzhen --- include/nuttx/sched.h | 4 ++ sched/sched/sched.h | 6 ++ sched/sched/sched_processtickless.c | 87 ++++++++++++----------------- sched/sched/sched_roundrobin.c | 59 +++++++++++++++++++ sched/sched/sched_switchcontext.c | 21 +++++-- 5 files changed, 121 insertions(+), 56 deletions(-) diff --git a/include/nuttx/sched.h b/include/nuttx/sched.h index a0534906621..2c89ebf83fa 100644 --- a/include/nuttx/sched.h +++ b/include/nuttx/sched.h @@ -639,6 +639,10 @@ struct tcb_s int32_t timeslice; /* RR timeslice OR Sporadic budget */ /* interval remaining */ #endif +#if CONFIG_RR_INTERVAL > 0 && defined(CONFIG_SCHED_TICKLESS) + clock_t rr_starttime; /* Time when RR task was last */ + /* accounted */ +#endif #ifdef CONFIG_SCHED_SPORADIC FAR struct sporadic_s *sporadic; /* Sporadic scheduling parameters */ #endif diff --git a/sched/sched/sched.h b/sched/sched/sched.h index 653d7ebaef6..f1a8776fcf1 100644 --- a/sched/sched/sched.h +++ b/sched/sched/sched.h @@ -349,8 +349,10 @@ int nxsched_reprioritize(FAR struct tcb_s *tcb, int sched_priority); #ifdef CONFIG_SCHED_TICKLESS void nxsched_reassess_timer(void); +void nxsched_timer_start(clock_t now, clock_t delay); #else # define nxsched_reassess_timer() +# define nxsched_timer_start(now, delay) #endif /* Scheduler policy support */ @@ -358,6 +360,10 @@ void nxsched_reassess_timer(void); #if CONFIG_RR_INTERVAL > 0 clock_t nxsched_process_roundrobin(FAR struct tcb_s *tcb, clock_t ticks, bool noswitches); +# ifdef CONFIG_SCHED_TICKLESS +void nxsched_suspend_roundrobin(FAR struct tcb_s *tcb); +void nxsched_resume_roundrobin(FAR struct tcb_s *tcb); +# endif #endif #ifdef CONFIG_SCHED_SPORADIC diff --git a/sched/sched/sched_processtickless.c b/sched/sched/sched_processtickless.c index 8332d81c911..4ffe7c12e4b 100644 --- a/sched/sched/sched_processtickless.c +++ b/sched/sched/sched_processtickless.c @@ -79,7 +79,6 @@ static clock_t nxsched_cpu_scheduler(int cpu, clock_t ticks, #endif static clock_t nxsched_process_scheduler(clock_t ticks, clock_t elapsed, bool noswitches); -static void nxsched_timer_start(clock_t ticks, clock_t interval); /**************************************************************************** * Private Data @@ -203,6 +202,7 @@ static clock_t nxsched_cpu_scheduler(int cpu, clock_t ticks, * timeslice. */ + rtcb->rr_starttime = ticks; ret = nxsched_process_roundrobin(rtcb, elapsed, noswitches); } #endif @@ -314,35 +314,6 @@ clock_t nxsched_process_scheduler(clock_t ticks, clock_t elapsed, return minslice; } -/**************************************************************************** - * Name: nxsched_timer_start - * - * Description: - * Start the interval timer. - * - * Input Parameters: - * ticks - The number of ticks defining the timer interval to setup. - * interval - The number of ticks to use when setting up the next timer. - * - * Returned Value: - * None - * - ****************************************************************************/ - -static void nxsched_timer_start(clock_t ticks, clock_t interval) -{ - if (interval != CLOCK_MAX) - { - DEBUGASSERT(interval <= UINT32_MAX); - wd_start_abstick(&g_sched_event, ticks + interval, - nxsched_wdog_expiration, 0u); - } - else - { - wd_cancel(&g_sched_event); - } -} - /**************************************************************************** * Public Functions ****************************************************************************/ @@ -362,13 +333,6 @@ static void nxsched_timer_start(clock_t ticks, clock_t interval) * Base code implementation assumes that this function is called from * interrupt handling logic with interrupts disabled. * - * Note: - * The current SCHED_RR implementation has an issue: if a round-robin task - * is preempted, its timeslice counter does not decrement properly. - * Therefore, we must trigger the scheduler on each timer expiration to - * minimize the occurrence of this problem. - * This workaround can be removed once the SCHED_RR behavior is fixed. - * ****************************************************************************/ void nxsched_process_timer(void) @@ -380,15 +344,6 @@ void nxsched_process_timer(void) clock_update_sched_ticks(ticks); hrtimer_process(nsec); - -# if CONFIG_RR_INTERVAL > 0 - /* Workaround for SCHED_RR, see the note. */ - - irqstate_t flags = enter_critical_section(); - nxsched_process_event(ticks, true); - leave_critical_section(flags); -# endif - #else irqstate_t flags; clock_t ticks; @@ -405,12 +360,6 @@ void nxsched_process_timer(void) clock_update_sched_ticks(ticks); -#if CONFIG_RR_INTERVAL > 0 - /* Workaround for SCHED_RR, see the note. */ - - nxsched_process_event(ticks, true); -#endif - wd_timer(ticks); leave_critical_section(flags); @@ -466,6 +415,40 @@ void nxsched_reassess_timer(void) nxsched_process_event(g_wdexpired, true); } +/**************************************************************************** + * Name: nxsched_timer_start + * + * Description: + * Start the interval timer for the scheduler event. Called during + * context switches to set up the timer for the newly running RR task's + * remaining timeslice. + * + * Input Parameters: + * now - The current time in ticks. + * delay - The number of ticks to wait until the timer expires. + * + * Returned Value: + * None + * + * Assumption: + * This function is called from the critical section. + * + ****************************************************************************/ + +void nxsched_timer_start(clock_t now, clock_t delay) +{ + if (delay != CLOCK_MAX) + { + DEBUGASSERT(delay <= UINT32_MAX); + wd_start_abstick(&g_sched_event, now + delay, + nxsched_wdog_expiration, 0u); + } + else + { + wd_cancel(&g_sched_event); + } +} + /**************************************************************************** * Name: nxsched_get_next_expired * diff --git a/sched/sched/sched_roundrobin.c b/sched/sched/sched_roundrobin.c index 43dc6fcfdf1..8400b400557 100644 --- a/sched/sched/sched_roundrobin.c +++ b/sched/sched/sched_roundrobin.c @@ -229,4 +229,63 @@ clock_t nxsched_process_roundrobin(FAR struct tcb_s *tcb, clock_t ticks, return ret; } +#ifdef CONFIG_SCHED_TICKLESS + +/**************************************************************************** + * Name: nxsched_suspend_roundrobin + * + * Description: + * Called when a task using round-robin scheduling is being switched out. + * Accounts for the time consumed since the timeslice was last assessed + * so that only actual CPU execution time is charged against the task. + * + * Input Parameters: + * tcb - The TCB of the thread that is being suspended. + * + * Returned Value: + * None + * + * Assumption: + * This function is called from the critical section. + * + ****************************************************************************/ + +void nxsched_suspend_roundrobin(FAR struct tcb_s *tcb) +{ + clock_t now = clock_systime_ticks(); + clock_t elapsed = now - tcb->rr_starttime; + + nxsched_process_roundrobin(tcb, elapsed, true); +} + +/**************************************************************************** + * Name: nxsched_resume_roundrobin + * + * Description: + * Called when a task using round-robin scheduling is being switched in. + * Restarts the scheduler timer for the task's remaining timeslice so + * that the timer is always armed while an RR task is running. + * + * Input Parameters: + * tcb - The TCB of the thread that is being resumed. + * + * Returned Value: + * None + * + * Assumption: + * This function is called from the critical section. + * + ****************************************************************************/ + +void nxsched_resume_roundrobin(FAR struct tcb_s *tcb) +{ + clock_t now = clock_systime_ticks(); + + DEBUGASSERT(tcb->timeslice >= 0); + tcb->rr_starttime = now; + nxsched_timer_start(now, tcb->timeslice); +} + +#endif /* CONFIG_SCHED_TICKLESS */ + #endif /* CONFIG_RR_INTERVAL > 0 */ diff --git a/sched/sched/sched_switchcontext.c b/sched/sched/sched_switchcontext.c index f2a708f5c4f..882f4f097a1 100644 --- a/sched/sched/sched_switchcontext.c +++ b/sched/sched/sched_switchcontext.c @@ -51,6 +51,17 @@ void nxsched_switch_context(FAR struct tcb_s *from, FAR struct tcb_s *to) { nxsched_checkstackoverflow(from); +#if CONFIG_RR_INTERVAL > 0 && defined(CONFIG_SCHED_TICKLESS) + /* If the task being switched out uses round-robin scheduling, account + * for the time it has consumed from its timeslice. + */ + + if ((from->flags & TCB_FLAG_POLICY_MASK) == TCB_FLAG_SCHED_RR) + { + nxsched_suspend_roundrobin(from); + } +#endif + #ifdef CONFIG_SCHED_SPORADIC /* Perform sporadic schedule operations */ @@ -65,12 +76,14 @@ void nxsched_switch_context(FAR struct tcb_s *from, FAR struct tcb_s *to) } #endif -#if defined(CONFIG_SCHED_TICKLESS) && CONFIG_RR_INTERVAL > 0 - /* Before the context switch, we should set the timer for RR. */ +#if CONFIG_RR_INTERVAL > 0 && defined(CONFIG_SCHED_TICKLESS) + /* If the task being switched in uses round-robin scheduling, restart + * the timer for its remaining timeslice. + */ - if (from != to && (to->flags & TCB_FLAG_POLICY_MASK) == TCB_FLAG_SCHED_RR) + if ((to->flags & TCB_FLAG_POLICY_MASK) == TCB_FLAG_SCHED_RR) { - nxsched_reassess_timer(); + nxsched_resume_roundrobin(to); } #endif