kernel: Add support for SMP systems to simple scheduler

In SMP systems simple scheduler will be used only when all logical
processors share all levels of cache and the number of CPUs is low.
In such systems we do not have to care about cache affinity and
the contention on the lock protecting shared run queue is low. Single
run queue makes load balancing very simple.
This commit is contained in:
Pawel Dziepak
2013-10-15 00:29:04 +02:00
parent 298314fe4b
commit f20ad54be2
4 changed files with 173 additions and 56 deletions
+17 -3
View File
@@ -79,6 +79,8 @@ public:
inline Element* PeekRoot(); inline Element* PeekRoot();
inline const Key& GetKey(Element* element) const;
inline void ModifyKey(Element* element, Key newKey); inline void ModifyKey(Element* element, Key newKey);
inline void RemoveRoot(); inline void RemoveRoot();
@@ -183,11 +185,22 @@ HEAP_CLASS_NAME::PeekRoot()
} }
HEAP_TEMPLATE_LIST
const Key&
HEAP_CLASS_NAME::GetKey(Element* element) const
{
HeapLink<Element, Key>* link = sGetLink(element);
ASSERT(link->fIndex >= 0 && link->fIndex < fLastElement);
return link->fKey;
}
HEAP_TEMPLATE_LIST HEAP_TEMPLATE_LIST
void void
HEAP_CLASS_NAME::ModifyKey(Element* element, Key newKey) HEAP_CLASS_NAME::ModifyKey(Element* element, Key newKey)
{ {
HeapLink<Element, Key> link = sGetLink(element); HeapLink<Element, Key>* link = sGetLink(element);
ASSERT(link->fIndex >= 0 && link->fIndex < fLastElement); ASSERT(link->fIndex >= 0 && link->fIndex < fLastElement);
Key oldKey = link->fKey; Key oldKey = link->fKey;
@@ -208,7 +221,7 @@ HEAP_CLASS_NAME::RemoveRoot()
#if KDEBUG #if KDEBUG
Element* element = PeekRoot(); Element* element = PeekRoot();
HeapLink<Element, Key> link = sGetLink(element); HeapLink<Element, Key>* link = sGetLink(element);
link->fIndex = -1; link->fIndex = -1;
#endif #endif
@@ -240,6 +253,8 @@ HEAP_CLASS_NAME::Insert(Element* element, Key key)
link->fIndex = fLastElement++; link->fIndex = fLastElement++;
link->fKey = key; link->fKey = key;
_MoveUp(link); _MoveUp(link);
return B_OK;
} }
@@ -266,7 +281,6 @@ HEAP_TEMPLATE_LIST
void void
HEAP_CLASS_NAME::_MoveUp(HeapLink<Element, Key>* link) HEAP_CLASS_NAME::_MoveUp(HeapLink<Element, Key>* link)
{ {
int i = link->fIndex;
while (true) { while (true) {
int parent = (link->fIndex - 1) / 2; int parent = (link->fIndex - 1) / 2;
if (link->fIndex > 0 if (link->fIndex > 0
+4 -10
View File
@@ -68,18 +68,12 @@ scheduler_init(void)
cpuCount != 1 ? "s" : ""); cpuCount != 1 ? "s" : "");
status_t result; status_t result;
if (cpuCount > 1) {
#if 0 #if 0
dprintf("scheduler_init: using affine scheduler\n"); dprintf("scheduler_init: using affine scheduler\n");
result = scheduler_affine_init(); result = scheduler_affine_init();
#else
dprintf("scheduler_init: using simple SMP scheduler\n");
result = scheduler_simple_smp_init();
#endif #endif
} else { dprintf("scheduler_init: using simple scheduler\n");
dprintf("scheduler_init: using simple scheduler\n"); result = scheduler_simple_init();
result = scheduler_simple_init();
}
if (result != B_OK) if (result != B_OK)
panic("scheduler_init: failed to initialize scheduler\n"); panic("scheduler_init: failed to initialize scheduler\n");
+147 -31
View File
@@ -25,6 +25,7 @@
#include <scheduler_defs.h> #include <scheduler_defs.h>
#include <thread.h> #include <thread.h>
#include <timer.h> #include <timer.h>
#include <util/Heap.h>
#include <util/Random.h> #include <util/Random.h>
#include "RunQueue.h" #include "RunQueue.h"
@@ -43,6 +44,14 @@
const bigtime_t kThreadQuantum = 1000; const bigtime_t kThreadQuantum = 1000;
struct CPUHeapEntry : public HeapLinkImpl<CPUHeapEntry, int32> {
int32 fCPUNumber;
};
static CPUHeapEntry* sCPUEntries;
typedef Heap<CPUHeapEntry, int32> SimpleCPUHeap;
static SimpleCPUHeap* sCPUHeap;
// The run queue. Holds the threads ready to run ordered by priority. // The run queue. Holds the threads ready to run ordered by priority.
typedef RunQueue<Thread, THREAD_MAX_SET_PRIORITY> SimpleRunQueue; typedef RunQueue<Thread, THREAD_MAX_SET_PRIORITY> SimpleRunQueue;
static SimpleRunQueue* sRunQueue; static SimpleRunQueue* sRunQueue;
@@ -104,11 +113,30 @@ simple_get_thread_penalty(Thread* thread)
} }
static inline void
static inline int32
simple_get_effective_priority(Thread* thread)
{
if (thread->priority == B_IDLE_PRIORITY)
return thread->priority;
if (thread->priority >= B_FIRST_REAL_TIME_PRIORITY)
return thread->priority;
int32 effectivePriority = thread->priority;
effectivePriority -= simple_get_thread_penalty(thread);
ASSERT(effectivePriority < B_FIRST_REAL_TIME_PRIORITY);
ASSERT(effectivePriority >= B_LOWEST_ACTIVE_PRIORITY);
return effectivePriority;
}
static void
dump_queue(SimpleRunQueue::ConstIterator& iterator) dump_queue(SimpleRunQueue::ConstIterator& iterator)
{ {
if (!iterator.HasNext()) if (!iterator.HasNext())
kprintf("Queue is empty.\n"); kprintf("Run queue is empty.\n");
else { else {
kprintf("thread id priority penalty name\n"); kprintf("thread id priority penalty name\n");
while (iterator.HasNext()) { while (iterator.HasNext()) {
@@ -124,15 +152,38 @@ dump_queue(SimpleRunQueue::ConstIterator& iterator)
static int static int
dump_run_queue(int argc, char** argv) dump_run_queue(int argc, char** argv)
{ {
SimpleRunQueue::ConstIterator iterator; SimpleRunQueue::ConstIterator iterator = sRunQueue->GetConstIterator();
kprintf("Current run queue:\n");
iterator = sRunQueue->GetConstIterator();
dump_queue(iterator); dump_queue(iterator);
return 0; return 0;
} }
static int
dump_cpu_heap(int argc, char** argv)
{
kprintf("\ncpu priority actual priority\n");
CPUHeapEntry* entry = sCPUHeap->PeekRoot();
while (entry) {
int32 cpu = entry->fCPUNumber;
kprintf("%3" B_PRId32 " %8" B_PRId32 " %15" B_PRId32 "\n", cpu,
sCPUHeap->GetKey(entry),
simple_get_effective_priority(gCPU[cpu].running_thread));
sCPUHeap->RemoveRoot();
entry = sCPUHeap->PeekRoot();
}
int32 cpuCount = smp_get_num_cpus();
for (int i = 0; i < cpuCount; i++) {
sCPUHeap->Insert(&sCPUEntries[i],
simple_get_effective_priority(gCPU[i].running_thread));
}
return 0;
}
static void static void
simple_dump_thread_data(Thread* thread) simple_dump_thread_data(Thread* thread)
{ {
@@ -154,24 +205,6 @@ simple_dump_thread_data(Thread* thread)
} }
static inline int32
simple_get_effective_priority(Thread* thread)
{
if (thread->priority == B_IDLE_PRIORITY)
return thread->priority;
if (thread->priority >= B_FIRST_REAL_TIME_PRIORITY)
return thread->priority;
int32 effectivePriority = thread->priority;
effectivePriority -= simple_get_thread_penalty(thread);
ASSERT(effectivePriority < B_FIRST_REAL_TIME_PRIORITY);
ASSERT(effectivePriority >= B_LOWEST_ACTIVE_PRIORITY);
return effectivePriority;
}
static inline void static inline void
simple_increase_penalty(Thread* thread) simple_increase_penalty(Thread* thread)
{ {
@@ -218,6 +251,7 @@ simple_enqueue(Thread* thread, bool newOne)
simple_cancel_penalty(thread); simple_cancel_penalty(thread);
int32 threadPriority = simple_get_effective_priority(thread); int32 threadPriority = simple_get_effective_priority(thread);
T(EnqueueThread(thread, threadPriority)); T(EnqueueThread(thread, threadPriority));
sRunQueue->PushBack(thread, threadPriority); sRunQueue->PushBack(thread, threadPriority);
@@ -230,11 +264,44 @@ simple_enqueue(Thread* thread, bool newOne)
NotifySchedulerListeners(&SchedulerListener::ThreadEnqueuedInRunQueue, NotifySchedulerListeners(&SchedulerListener::ThreadEnqueuedInRunQueue,
thread); thread);
Thread* currentThread = thread_get_current_thread(); // TODO: pinned threads
if (newOne && threadPriority > currentThread->priority) { // TODO: disabled CPUs
currentThread->scheduler_data->lost_cpu = true; CPUHeapEntry* cpuEntry = sCPUHeap->PeekRoot();
gCPU[0].invoke_scheduler = true; ASSERT(cpuEntry != NULL);
gCPU[0].invoke_scheduler_if_idle = false;
int32 thisCPU = smp_get_current_cpu();
int32 targetCPU = cpuEntry->fCPUNumber;
Thread* targetThread = gCPU[targetCPU].running_thread;
int32 targetPriority = simple_get_effective_priority(targetThread);
ASSERT((targetCPU != thisCPU && targetThread != thread)
|| targetCPU == thisCPU);
int32 currentThreadPriority
= simple_get_effective_priority(thread_get_current_thread());
if (targetPriority == currentThreadPriority) {
targetCPU = thisCPU;
targetPriority = currentThreadPriority;
}
TRACE("choosing CPU %ld with current priority %ld\n", targetCPU,
targetPriority);
if (threadPriority > targetPriority) {
targetThread->scheduler_data->lost_cpu = true;
// It is possible that another CPU schedules the thread before the
// target CPU. However, since the target CPU is sent an ICI it will
// reschedule anyway and update its heap key to the correct value.
sCPUHeap->ModifyKey(cpuEntry, threadPriority);
if (targetCPU == smp_get_current_cpu()) {
gCPU[targetCPU].invoke_scheduler = true;
gCPU[targetCPU].invoke_scheduler_if_idle = false;
} else {
smp_send_ici(targetCPU, SMP_MSG_RESCHEDULE, 0, 0, 0, NULL,
SMP_MSG_FLAG_ASYNC);
}
} }
} }
@@ -245,6 +312,8 @@ simple_enqueue(Thread* thread, bool newOne)
static void static void
simple_enqueue_in_run_queue(Thread* thread) simple_enqueue_in_run_queue(Thread* thread)
{ {
TRACE("enqueueing new thread %ld with static priority %ld\n", thread->id,
thread->priority);
simple_enqueue(thread, true); simple_enqueue(thread, true);
} }
@@ -258,7 +327,15 @@ simple_set_thread_priority(Thread* thread, int32 priority)
if (priority == thread->priority) if (priority == thread->priority)
return; return;
TRACE("changing thread %ld priority to %ld (old: %ld, effective: %ld)\n",
thread->id, priority, thread->priority,
simple_get_effective_priority(thread));
if (thread->state == B_THREAD_RUNNING)
sCPUHeap->ModifyKey(&sCPUEntries[thread->cpu->cpu_num], priority);
if (thread->state != B_THREAD_READY) { if (thread->state != B_THREAD_READY) {
simple_cancel_penalty(thread);
thread->priority = priority; thread->priority = priority;
return; return;
} }
@@ -408,11 +485,21 @@ simple_reschedule(void)
} }
} }
TRACE("reschedule(): current thread = %ld\n", oldThread->id); int32 thisCPU = smp_get_current_cpu();
TRACE("reschedule(): cpu %ld, current thread = %ld\n", thisCPU,
oldThread->id);
oldThread->state = oldThread->next_state; oldThread->state = oldThread->next_state;
scheduler_thread_data* schedulerOldThreadData = oldThread->scheduler_data; scheduler_thread_data* schedulerOldThreadData = oldThread->scheduler_data;
// update CPU heap so that old thread would have CPU properly chosen
Thread* nextThread = sRunQueue->PeekMaximum();
if (nextThread != NULL) {
sCPUHeap->ModifyKey(&sCPUEntries[thisCPU],
simple_get_effective_priority(nextThread));
}
switch (oldThread->next_state) { switch (oldThread->next_state) {
case B_THREAD_RUNNING: case B_THREAD_RUNNING:
case B_THREAD_READY: case B_THREAD_READY:
@@ -452,15 +539,20 @@ simple_reschedule(void)
schedulerOldThreadData->lost_cpu = false; schedulerOldThreadData->lost_cpu = false;
// select thread with the biggest priority // select thread with the biggest priority
Thread* nextThread = sRunQueue->PeekMaximum(); nextThread = sRunQueue->PeekMaximum();
if (!nextThread) if (!nextThread)
panic("reschedule(): run queues are empty!\n"); panic("reschedule(): run queues are empty!\n");
sRunQueue->Remove(nextThread); sRunQueue->Remove(nextThread);
TRACE("reschedule(): next thread = %ld\n", nextThread->id); TRACE("reschedule(): cpu %ld, next thread = %ld\n", thisCPU,
nextThread->id);
T(ScheduleThread(nextThread, oldThread)); T(ScheduleThread(nextThread, oldThread));
// update CPU heap
sCPUHeap->ModifyKey(&sCPUEntries[thisCPU],
simple_get_effective_priority(nextThread));
// notify listeners // notify listeners
NotifySchedulerListeners(&SchedulerListener::ThreadScheduled, NotifySchedulerListeners(&SchedulerListener::ThreadScheduled,
oldThread, nextThread); oldThread, nextThread);
@@ -556,6 +648,25 @@ static scheduler_ops kSimpleOps = {
status_t status_t
scheduler_simple_init() scheduler_simple_init()
{ {
int32 cpuCount = smp_get_num_cpus();
sCPUHeap = new SimpleCPUHeap;
if (sCPUHeap == NULL)
return B_NO_MEMORY;
ObjectDeleter<SimpleCPUHeap> cpuHeapDeleter(sCPUHeap);
sCPUEntries = new CPUHeapEntry[cpuCount];
if (sCPUEntries == NULL)
return B_NO_MEMORY;
ArrayDeleter<CPUHeapEntry> cpuEntriesDeleter(sCPUEntries);
for (int i = 0; i < cpuCount; i++) {
sCPUEntries[i].fCPUNumber = i;
status_t result = sCPUHeap->Insert(&sCPUEntries[i], B_IDLE_PRIORITY);
if (result != B_OK)
return result;
}
sRunQueue = new(std::nothrow) SimpleRunQueue; sRunQueue = new(std::nothrow) SimpleRunQueue;
if (sRunQueue == NULL) if (sRunQueue == NULL)
return B_NO_MEMORY; return B_NO_MEMORY;
@@ -569,7 +680,12 @@ scheduler_simple_init()
add_debugger_command_etc("run_queue", &dump_run_queue, add_debugger_command_etc("run_queue", &dump_run_queue,
"List threads in run queue", "\nLists threads in run queue", 0); "List threads in run queue", "\nLists threads in run queue", 0);
add_debugger_command_etc("cpu_heap", &dump_cpu_heap,
"List CPUs in CPU priority heap", "\nList CPUs in CPU priority heap",
0);
cpuHeapDeleter.Detach();
cpuEntriesDeleter.Detach();
runQueueDeleter.Detach(); runQueueDeleter.Detach();
return B_OK; return B_OK;
} }
+5 -12
View File
@@ -1425,7 +1425,7 @@ make_thread_unreal(int argc, char **argv)
continue; continue;
if (thread->priority > B_DISPLAY_PRIORITY) { if (thread->priority > B_DISPLAY_PRIORITY) {
thread->priority = B_NORMAL_PRIORITY; scheduler_set_thread_priority(thread, B_NORMAL_PRIORITY);
kprintf("thread %" B_PRId32 " made unreal\n", thread->id); kprintf("thread %" B_PRId32 " made unreal\n", thread->id);
} }
} }
@@ -1461,7 +1461,7 @@ set_thread_prio(int argc, char **argv)
Thread* thread = it.Next();) { Thread* thread = it.Next();) {
if (thread->id != id) if (thread->id != id)
continue; continue;
thread->priority = prio; scheduler_set_thread_priority(thread, prio);
kprintf("thread %" B_PRId32 " set to priority %" B_PRId32 "\n", id, prio); kprintf("thread %" B_PRId32 " set to priority %" B_PRId32 "\n", id, prio);
found = true; found = true;
break; break;
@@ -1920,7 +1920,7 @@ thread_exit(void)
panic("thread_exit() called with interrupts disabled!\n"); panic("thread_exit() called with interrupts disabled!\n");
// boost our priority to get this over with // boost our priority to get this over with
thread->priority = B_URGENT_DISPLAY_PRIORITY; scheduler_set_thread_priority(thread, B_URGENT_DISPLAY_PRIORITY);
if (team != kernelTeam) { if (team != kernelTeam) {
// Cancel previously installed alarm timer, if any. Hold the scheduler // Cancel previously installed alarm timer, if any. Hold the scheduler
@@ -3205,15 +3205,8 @@ set_thread_priority(thread_id id, int32 priority)
InterruptsSpinLocker schedulerLocker(gSchedulerLock); InterruptsSpinLocker schedulerLocker(gSchedulerLock);
if (thread == thread_get_current_thread()) { oldPriority = thread->priority;
// It's ourself, so we know we aren't in the run queue, and we can scheduler_set_thread_priority(thread, priority);
// manipulate our structure directly.
oldPriority = thread->priority;
thread->priority = priority;
} else {
oldPriority = thread->priority;
scheduler_set_thread_priority(thread, priority);
}
return oldPriority; return oldPriority;
} }