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.
This commit is contained in:
parent
6d7e291233
commit
82c26e1f1f
@ -46,6 +46,23 @@ const bigtime_t kThreadQuantum = 3000;
|
||||
static RunQueue<Thread, THREAD_MAX_SET_PRIORITY>* 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<scheduler_thread_data*>(
|
||||
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<scheduler_thread_data*>(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<scheduler_thread_data*>(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<scheduler_thread_data*>(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<scheduler_thread_data*>(
|
||||
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<scheduler_thread_data*>(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<scheduler_thread_data*>(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<scheduler_thread_data*>(thread->scheduler_data);
|
||||
schedulerThreadData->Init();
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
simple_on_thread_destroy(Thread* thread)
|
||||
{
|
||||
// do nothing
|
||||
delete thread->scheduler_data;
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user