From 82c26e1f1fefa9b91861d23368723618bd7bdd12 Mon Sep 17 00:00:00 2001 From: Pawel Dziepak Date: Mon, 7 Oct 2013 21:11:32 +0200 Subject: [PATCH] kernel: Punish CPU bound threads This patch appears to fix #8007. Thread that consume its whole quantum has its priority reduced. The penalty is cancelled when the thread voluntarily gives up CPU. Real-time threads are not affected. The problem of thread starvation is not solved completely. The worst case latency is still unbounded (even in systems with bounded number of threads). When a middle priority thread is constantly preempted by high priority threads it would not earn the penalty, thus the lower priority threads still can be starved. Moreover, the punishment is probably too aggressive as it reduces priority of virtually all CPU bound threads to 1. --- .../kernel/scheduler/scheduler_simple.cpp | 166 ++++++++++++------ 1 file changed, 114 insertions(+), 52 deletions(-) diff --git a/src/system/kernel/scheduler/scheduler_simple.cpp b/src/system/kernel/scheduler/scheduler_simple.cpp index 606a1f936a..6549dd42cc 100644 --- a/src/system/kernel/scheduler/scheduler_simple.cpp +++ b/src/system/kernel/scheduler/scheduler_simple.cpp @@ -46,6 +46,23 @@ const bigtime_t kThreadQuantum = 3000; static RunQueue* sRunQueue; +struct scheduler_thread_data { + scheduler_thread_data() { Init(); } + void Init(); + + int32 priority_penalty; + bool lost_cpu; +}; + + +void +scheduler_thread_data::Init() +{ + priority_penalty = 0; + lost_cpu = false; +} + + static int dump_run_queue(int argc, char **argv) { @@ -55,11 +72,15 @@ dump_run_queue(int argc, char **argv) if (!iterator.HasNext()) kprintf("Run queue is empty!\n"); else { - kprintf("thread id priority name\n"); + kprintf("thread id priority penalty name\n"); while (iterator.HasNext()) { Thread *thread = iterator.Next(); - kprintf("%p %-7" B_PRId32 " %-8" B_PRId32 " %s\n", thread, - thread->id, thread->priority, thread->name); + scheduler_thread_data* schedulerThreadData + = reinterpret_cast( + thread->scheduler_data); + kprintf("%p %-7" B_PRId32 " %-8" B_PRId32 " %-8" B_PRId32 " %s\n", + thread, thread->id, thread->priority, + schedulerThreadData->priority_penalty, thread->name); } } @@ -67,6 +88,57 @@ dump_run_queue(int argc, char **argv) } +static inline int32 +simple_get_effective_priority(Thread *thread) +{ + if (thread->priority == B_IDLE_PRIORITY) + return thread->priority; + + scheduler_thread_data* schedulerThreadData + = reinterpret_cast(thread->scheduler_data); + + int32 effectivePriority = thread->priority; + if (effectivePriority < B_FIRST_REAL_TIME_PRIORITY) + effectivePriority -= schedulerThreadData->priority_penalty; + + ASSERT(schedulerThreadData->priority_penalty >= 0); + ASSERT(effectivePriority >= B_LOWEST_ACTIVE_PRIORITY); + + return min_c(effectivePriority, thread->next_priority); +} + + +static inline void +simple_increase_penalty(Thread *thread) +{ + if (thread->priority <= B_LOWEST_ACTIVE_PRIORITY) + return; + if (thread->priority >= B_FIRST_REAL_TIME_PRIORITY) + return; + + TRACE(("increasing thread %ld penalty\n", thread->id)); + + scheduler_thread_data* schedulerThreadData + = reinterpret_cast(thread->scheduler_data); + int32 oldPenalty = schedulerThreadData->priority_penalty++; + + ASSERT(thread->priority - oldPenalty >= B_LOWEST_ACTIVE_PRIORITY); + if (thread->priority - oldPenalty <= B_LOWEST_ACTIVE_PRIORITY) + schedulerThreadData->priority_penalty = oldPenalty; +} + + +static inline void +simple_cancel_penalty(Thread *thread) +{ + scheduler_thread_data* schedulerThreadData + = reinterpret_cast(thread->scheduler_data); + if (schedulerThreadData->priority_penalty != 0) + TRACE(("cancelling thread %ld penalty\n", thread->id)); + schedulerThreadData->priority_penalty = 0; +} + + /*! Enqueues the thread into the run queue. Note: thread lock must be held when entering this function */ @@ -77,14 +149,21 @@ simple_enqueue_in_run_queue(Thread *thread) //T(EnqueueThread(thread, prev, curr)); - sRunQueue->PushBack(thread, thread->next_priority); + int32 threadPriority = simple_get_effective_priority(thread); + sRunQueue->PushBack(thread, threadPriority); thread->next_priority = thread->priority; // notify listeners NotifySchedulerListeners(&SchedulerListener::ThreadEnqueuedInRunQueue, thread); - if (thread->priority > thread_get_current_thread()->priority) { + Thread* currentThread = thread_get_current_thread(); + if (threadPriority > currentThread->priority) { + scheduler_thread_data* schedulerThreadData + = reinterpret_cast( + currentThread->scheduler_data); + + schedulerThreadData->lost_cpu = true; gCPU[0].invoke_scheduler = true; gCPU[0].invoke_scheduler_if_idle = false; } @@ -118,7 +197,9 @@ simple_set_thread_priority(Thread *thread, int32 priority) sRunQueue->Remove(thread); // set priority and re-insert + simple_cancel_penalty(thread); thread->priority = thread->next_priority = priority; + simple_enqueue_in_run_queue(thread); } @@ -144,9 +225,14 @@ reschedule_event(timer *unused) { // This function is called as a result of the timer event set by the // scheduler. Make sure the reschedule() is invoked. - thread_get_current_thread()->cpu->invoke_scheduler = true; - thread_get_current_thread()->cpu->invoke_scheduler_if_idle = false; - thread_get_current_thread()->cpu->preempted = 1; + Thread* thread= thread_get_current_thread(); + scheduler_thread_data* schedulerThreadData + = reinterpret_cast(thread->scheduler_data); + + schedulerThreadData->lost_cpu = true; + thread->cpu->invoke_scheduler = true; + thread->cpu->invoke_scheduler_if_idle = false; + thread->cpu->preempted = 1; return B_HANDLED_INTERRUPT; } @@ -174,11 +260,18 @@ simple_reschedule(void) TRACE(("reschedule(): current thread = %ld\n", oldThread->id)); oldThread->state = oldThread->next_state; + scheduler_thread_data* schedulerOldThreadData + = reinterpret_cast(oldThread->scheduler_data); switch (oldThread->next_state) { case B_THREAD_RUNNING: case B_THREAD_READY: + if (oldThread->cpu->preempted) + simple_increase_penalty(oldThread); + else if (!schedulerOldThreadData->lost_cpu) + simple_cancel_penalty(oldThread); + TRACE(("enqueueing thread %ld into run queue priority = %ld\n", - oldThread->id, oldThread->priority)); + oldThread->id, simple_get_effective_priority(oldThread))); simple_enqueue_in_run_queue(oldThread); break; case B_THREAD_SUSPENDED: @@ -192,46 +285,10 @@ simple_reschedule(void) break; } - while (true) { - // select thread with the biggest priority - nextThread = sRunQueue->PeekMaximum(); - if (!nextThread) - panic("reschedule(): run queue is empty!\n"); - -#if 0 - if (oldThread == nextThread && nextThread->was_yielded) { - // ignore threads that called thread_yield() once - nextThread->was_yielded = false; - prevThread = nextThread; - nextThread = nextThread->queue_next; - } -#endif - - // always extract real time threads - if (nextThread->priority >= B_FIRST_REAL_TIME_PRIORITY) - break; - - // find thread with the second biggest priority - Thread* lowerNextThread = sRunQueue->PeekSecondMaximum(); - - // never skip last non-idle normal thread - if (lowerNextThread == NULL - || lowerNextThread->priority == B_IDLE_PRIORITY) - break; - - int32 priorityDiff = nextThread->priority - lowerNextThread->priority; - if (priorityDiff > 15) - break; - - // skip normal threads sometimes - // (twice as probable per priority level) - if ((fast_random_value() >> (15 - priorityDiff)) != 0) - break; - - nextThread = lowerNextThread; - - break; - } + // select thread with the biggest priority + nextThread = sRunQueue->PeekMaximum(); + if (!nextThread) + panic("reschedule(): run queue is empty!\n"); sRunQueue->Remove(nextThread); @@ -244,6 +301,7 @@ simple_reschedule(void) nextThread->state = B_THREAD_RUNNING; nextThread->next_state = B_THREAD_READY; oldThread->was_yielded = false; + schedulerOldThreadData->lost_cpu = false; // track kernel time (user time is tracked in thread_at_kernel_entry()) scheduler_update_thread_times(oldThread, nextThread); @@ -282,7 +340,9 @@ simple_reschedule(void) static status_t simple_on_thread_create(Thread* thread, bool idleThread) { - // do nothing + thread->scheduler_data = new (std::nothrow)scheduler_thread_data; + if (thread->scheduler_data == NULL) + return B_NO_MEMORY; return B_OK; } @@ -290,14 +350,16 @@ simple_on_thread_create(Thread* thread, bool idleThread) static void simple_on_thread_init(Thread* thread) { - // do nothing + scheduler_thread_data* schedulerThreadData + = reinterpret_cast(thread->scheduler_data); + schedulerThreadData->Init(); } static void simple_on_thread_destroy(Thread* thread) { - // do nothing + delete thread->scheduler_data; }