scheduler: Remove support for running different schedulers
Simple scheduler behaves exactly the same as affine scheduler with a single core. Obviously, affine scheduler is more complicated thus introduces greater overhead but quite a lot of multicore logic has been disabled on single core systems in the previous commit.
This commit is contained in:
@@ -24,86 +24,70 @@ typedef enum scheduler_mode {
|
|||||||
SCHEDULER_MODE_COUNT
|
SCHEDULER_MODE_COUNT
|
||||||
} scheduler_mode;
|
} scheduler_mode;
|
||||||
|
|
||||||
struct scheduler_ops {
|
|
||||||
/*! Enqueues the thread in the ready-to-run queue.
|
|
||||||
The caller must hold the scheduler lock (with disabled interrupts).
|
|
||||||
*/
|
|
||||||
void (*enqueue_in_run_queue)(Thread* thread);
|
|
||||||
|
|
||||||
/*! Selects a thread from the ready-to-run queue and, if that's not the
|
|
||||||
calling thread, switches the current CPU's context to run the selected
|
|
||||||
thread.
|
|
||||||
If it's the same thread, the thread will just continue to run.
|
|
||||||
In either case, unless the thread is dead or is sleeping/waiting
|
|
||||||
indefinitely, the function will eventually return.
|
|
||||||
The caller must hold the scheduler lock (with disabled interrupts).
|
|
||||||
*/
|
|
||||||
void (*reschedule)(void);
|
|
||||||
|
|
||||||
/*! Sets the given thread's priority.
|
|
||||||
The thread may be running or may be in the ready-to-run queue.
|
|
||||||
The caller must hold the scheduler lock (with disabled interrupts).
|
|
||||||
*/
|
|
||||||
void (*set_thread_priority)(Thread* thread, int32 priority);
|
|
||||||
bigtime_t (*estimate_max_scheduling_latency)(Thread* thread);
|
|
||||||
|
|
||||||
/*! Called when the Thread structure is first created.
|
|
||||||
Per-thread housekeeping resources can be allocated.
|
|
||||||
Interrupts must be enabled.
|
|
||||||
*/
|
|
||||||
status_t (*on_thread_create)(Thread* thread, bool idleThread);
|
|
||||||
|
|
||||||
/*! Called when a Thread structure is initialized and made ready for
|
|
||||||
use.
|
|
||||||
The per-thread housekeeping data structures are reset, if needed.
|
|
||||||
The caller must hold the scheduler lock (with disabled interrupts).
|
|
||||||
*/
|
|
||||||
void (*on_thread_init)(Thread* thread);
|
|
||||||
|
|
||||||
/*! Called when a Thread structure is freed.
|
|
||||||
Frees up any per-thread resources allocated on the scheduler's part. The
|
|
||||||
function may be called even if on_thread_create() failed.
|
|
||||||
Interrupts must be enabled.
|
|
||||||
*/
|
|
||||||
void (*on_thread_destroy)(Thread* thread);
|
|
||||||
|
|
||||||
/*! Called in the early boot process to start thread scheduling on the
|
|
||||||
current CPU.
|
|
||||||
The function is called once for each CPU.
|
|
||||||
Interrupts must be disabled, but the caller must not hold the scheduler
|
|
||||||
lock.
|
|
||||||
*/
|
|
||||||
void (*start)(void);
|
|
||||||
|
|
||||||
/*! Sets scheduler operation mode.
|
|
||||||
*/
|
|
||||||
status_t (*set_operation_mode)(scheduler_mode mode);
|
|
||||||
|
|
||||||
/*! Dumps scheduler specific thread information.
|
|
||||||
*/
|
|
||||||
void (*dump_thread_data)(Thread* thread);
|
|
||||||
};
|
|
||||||
|
|
||||||
extern struct scheduler_ops* gScheduler;
|
|
||||||
extern spinlock gSchedulerLock;
|
extern spinlock gSchedulerLock;
|
||||||
|
|
||||||
#define scheduler_enqueue_in_run_queue(thread) \
|
|
||||||
gScheduler->enqueue_in_run_queue(thread)
|
|
||||||
#define scheduler_set_thread_priority(thread, priority) \
|
|
||||||
gScheduler->set_thread_priority(thread, priority)
|
|
||||||
#define scheduler_reschedule() gScheduler->reschedule()
|
|
||||||
#define scheduler_start() gScheduler->start()
|
|
||||||
#define scheduler_on_thread_create(thread, idleThread) \
|
|
||||||
gScheduler->on_thread_create(thread, idleThread)
|
|
||||||
#define scheduler_on_thread_init(thread) \
|
|
||||||
gScheduler->on_thread_init(thread)
|
|
||||||
#define scheduler_on_thread_destroy(thread) \
|
|
||||||
gScheduler->on_thread_destroy(thread)
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*! Enqueues the thread in the ready-to-run queue.
|
||||||
|
The caller must hold the scheduler lock (with disabled interrupts).
|
||||||
|
*/
|
||||||
|
void scheduler_enqueue_in_run_queue(Thread* thread);
|
||||||
|
|
||||||
|
/*! Selects a thread from the ready-to-run queue and, if that's not the
|
||||||
|
calling thread, switches the current CPU's context to run the selected
|
||||||
|
thread.
|
||||||
|
If it's the same thread, the thread will just continue to run.
|
||||||
|
In either case, unless the thread is dead or is sleeping/waiting
|
||||||
|
indefinitely, the function will eventually return.
|
||||||
|
The caller must hold the scheduler lock (with disabled interrupts).
|
||||||
|
*/
|
||||||
|
void scheduler_reschedule(void);
|
||||||
|
|
||||||
|
/*! Sets the given thread's priority.
|
||||||
|
The thread may be running or may be in the ready-to-run queue.
|
||||||
|
The caller must hold the scheduler lock (with disabled interrupts).
|
||||||
|
*/
|
||||||
|
void scheduler_set_thread_priority(Thread* thread, int32 priority);
|
||||||
|
|
||||||
|
/*! Called when the Thread structure is first created.
|
||||||
|
Per-thread housekeeping resources can be allocated.
|
||||||
|
Interrupts must be enabled.
|
||||||
|
*/
|
||||||
|
status_t scheduler_on_thread_create(Thread* thread, bool idleThread);
|
||||||
|
|
||||||
|
/*! Called when a Thread structure is initialized and made ready for
|
||||||
|
use.
|
||||||
|
The per-thread housekeeping data structures are reset, if needed.
|
||||||
|
The caller must hold the scheduler lock (with disabled interrupts).
|
||||||
|
*/
|
||||||
|
void scheduler_on_thread_init(Thread* thread);
|
||||||
|
|
||||||
|
/*! Called when a Thread structure is freed.
|
||||||
|
Frees up any per-thread resources allocated on the scheduler's part. The
|
||||||
|
function may be called even if on_thread_create() failed.
|
||||||
|
Interrupts must be enabled.
|
||||||
|
*/
|
||||||
|
void scheduler_on_thread_destroy(Thread* thread);
|
||||||
|
|
||||||
|
/*! Called in the early boot process to start thread scheduling on the
|
||||||
|
current CPU.
|
||||||
|
The function is called once for each CPU.
|
||||||
|
Interrupts must be disabled, but the caller must not hold the scheduler
|
||||||
|
lock.
|
||||||
|
*/
|
||||||
|
void scheduler_start(void);
|
||||||
|
|
||||||
|
/*! Sets scheduler operation mode.
|
||||||
|
*/
|
||||||
|
status_t scheduler_set_operation_mode(scheduler_mode mode);
|
||||||
|
|
||||||
|
/*! Dumps scheduler specific thread information.
|
||||||
|
*/
|
||||||
|
void scheduler_dump_thread_data(Thread* thread);
|
||||||
|
|
||||||
void scheduler_add_listener(struct SchedulerListener* listener);
|
void scheduler_add_listener(struct SchedulerListener* listener);
|
||||||
void scheduler_remove_listener(struct SchedulerListener* listener);
|
void scheduler_remove_listener(struct SchedulerListener* listener);
|
||||||
|
|
||||||
|
|||||||
@@ -63,8 +63,6 @@ KernelMergeObject kernel_core.o :
|
|||||||
|
|
||||||
# scheduler
|
# scheduler
|
||||||
scheduler.cpp
|
scheduler.cpp
|
||||||
scheduler_affine.cpp
|
|
||||||
scheduler_simple.cpp
|
|
||||||
scheduler_tracing.cpp
|
scheduler_tracing.cpp
|
||||||
scheduling_analysis.cpp
|
scheduling_analysis.cpp
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2009, Rene Gollent, [email protected].
|
|
||||||
* Copyright 2008, Ingo Weinhold, [email protected].
|
|
||||||
* Distributed under the terms of the MIT License.
|
|
||||||
*/
|
|
||||||
#ifndef KERNEL_SCHEDULER_AFFINE_H
|
|
||||||
#define KERNEL_SCHEDULER_AFFINE_H
|
|
||||||
|
|
||||||
|
|
||||||
status_t scheduler_affine_init();
|
|
||||||
|
|
||||||
|
|
||||||
#endif // KERNEL_SCHEDULER_AFFINE_H
|
|
||||||
@@ -1,776 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2013, Paweł Dziepak, [email protected]
|
|
||||||
* Copyright 2008-2011, Ingo Weinhold, [email protected].
|
|
||||||
* Copyright 2002-2010, Axel Dörfler, [email protected].
|
|
||||||
* Copyright 2002, Angelo Mottola, [email protected].
|
|
||||||
* Distributed under the terms of the MIT License.
|
|
||||||
*
|
|
||||||
* Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
|
|
||||||
* Distributed under the terms of the NewOS License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/*! The thread scheduler */
|
|
||||||
|
|
||||||
|
|
||||||
#include <OS.h>
|
|
||||||
|
|
||||||
#include <AutoDeleter.h>
|
|
||||||
#include <cpu.h>
|
|
||||||
#include <debug.h>
|
|
||||||
#include <int.h>
|
|
||||||
#include <kernel.h>
|
|
||||||
#include <kscheduler.h>
|
|
||||||
#include <listeners.h>
|
|
||||||
#include <scheduler_defs.h>
|
|
||||||
#include <thread.h>
|
|
||||||
#include <timer.h>
|
|
||||||
#include <util/Heap.h>
|
|
||||||
#include <util/Random.h>
|
|
||||||
|
|
||||||
#include "RunQueue.h"
|
|
||||||
#include "scheduler_common.h"
|
|
||||||
#include "scheduler_tracing.h"
|
|
||||||
|
|
||||||
|
|
||||||
//#define TRACE_SCHEDULER
|
|
||||||
#ifdef TRACE_SCHEDULER
|
|
||||||
# define TRACE(...) dprintf_no_syslog(__VA_ARGS__)
|
|
||||||
#else
|
|
||||||
# define TRACE(...) do { } while (false)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
|
||||||
typedef RunQueue<Thread, THREAD_MAX_SET_PRIORITY> SimpleRunQueue;
|
|
||||||
static SimpleRunQueue* sRunQueue;
|
|
||||||
static SimpleRunQueue* sCPURunQueues;
|
|
||||||
|
|
||||||
|
|
||||||
struct scheduler_thread_data {
|
|
||||||
scheduler_thread_data() { Init(); }
|
|
||||||
void Init();
|
|
||||||
|
|
||||||
int32 priority_penalty;
|
|
||||||
int32 additional_penalty;
|
|
||||||
|
|
||||||
bool lost_cpu;
|
|
||||||
bool cpu_bound;
|
|
||||||
|
|
||||||
bigtime_t time_left;
|
|
||||||
bigtime_t stolen_time;
|
|
||||||
bigtime_t quantum_start;
|
|
||||||
|
|
||||||
bigtime_t went_sleep;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
scheduler_thread_data::Init()
|
|
||||||
{
|
|
||||||
priority_penalty = 0;
|
|
||||||
additional_penalty = 0;
|
|
||||||
|
|
||||||
time_left = 0;
|
|
||||||
stolen_time = 0;
|
|
||||||
|
|
||||||
went_sleep = 0;
|
|
||||||
|
|
||||||
lost_cpu = false;
|
|
||||||
cpu_bound = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline int
|
|
||||||
simple_get_minimal_priority(Thread* thread)
|
|
||||||
{
|
|
||||||
return min_c(thread->priority, 25) / 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline int32
|
|
||||||
simple_get_thread_penalty(Thread* thread)
|
|
||||||
{
|
|
||||||
int32 penalty = thread->scheduler_data->priority_penalty;
|
|
||||||
|
|
||||||
const int kMinimalPriority = simple_get_minimal_priority(thread);
|
|
||||||
if (kMinimalPriority > 0) {
|
|
||||||
penalty
|
|
||||||
+= thread->scheduler_data->additional_penalty % kMinimalPriority;
|
|
||||||
}
|
|
||||||
|
|
||||||
return penalty;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
if (!iterator.HasNext())
|
|
||||||
kprintf("Run queue is empty.\n");
|
|
||||||
else {
|
|
||||||
kprintf("thread id priority penalty name\n");
|
|
||||||
while (iterator.HasNext()) {
|
|
||||||
Thread* thread = iterator.Next();
|
|
||||||
kprintf("%p %-7" B_PRId32 " %-8" B_PRId32 " %-8" B_PRId32 " %s\n",
|
|
||||||
thread, thread->id, thread->priority,
|
|
||||||
simple_get_thread_penalty(thread), thread->name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
|
||||||
dump_run_queue(int argc, char** argv)
|
|
||||||
{
|
|
||||||
SimpleRunQueue::ConstIterator iterator = sRunQueue->GetConstIterator();
|
|
||||||
kprintf("Shared run queue:\n");
|
|
||||||
dump_queue(iterator);
|
|
||||||
|
|
||||||
int32 cpuCount = smp_get_num_cpus();
|
|
||||||
if (cpuCount < 2)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
for (int32 i = 0; i < cpuCount; i++) {
|
|
||||||
iterator = sCPURunQueues[i].GetConstIterator();
|
|
||||||
|
|
||||||
if (iterator.HasNext()) {
|
|
||||||
kprintf("\nCPU %" B_PRId32 " run queue:\n", i);
|
|
||||||
dump_queue(iterator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
|
||||||
dump_cpu_heap(int argc, char** argv)
|
|
||||||
{
|
|
||||||
kprintf("cpu 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
|
|
||||||
simple_dump_thread_data(Thread* thread)
|
|
||||||
{
|
|
||||||
scheduler_thread_data* schedulerThreadData = thread->scheduler_data;
|
|
||||||
|
|
||||||
kprintf("\tpriority_penalty:\t%" B_PRId32 "\n",
|
|
||||||
schedulerThreadData->priority_penalty);
|
|
||||||
|
|
||||||
int32 additionalPenalty = 0;
|
|
||||||
const int kMinimalPriority = simple_get_minimal_priority(thread);
|
|
||||||
if (kMinimalPriority > 0) {
|
|
||||||
additionalPenalty
|
|
||||||
= schedulerThreadData->additional_penalty % kMinimalPriority;
|
|
||||||
}
|
|
||||||
kprintf("\tadditional_penalty:\t%" B_PRId32 " (%" B_PRId32 ")\n",
|
|
||||||
additionalPenalty, schedulerThreadData->additional_penalty);
|
|
||||||
kprintf("\tstolen_time:\t\t%" B_PRId64 "\n",
|
|
||||||
schedulerThreadData->stolen_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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 = thread->scheduler_data;
|
|
||||||
int32 oldPenalty = schedulerThreadData->priority_penalty++;
|
|
||||||
|
|
||||||
ASSERT(thread->priority - oldPenalty >= B_LOWEST_ACTIVE_PRIORITY);
|
|
||||||
const int kMinimalPriority = simple_get_minimal_priority(thread);
|
|
||||||
if (thread->priority - oldPenalty <= kMinimalPriority) {
|
|
||||||
schedulerThreadData->priority_penalty = oldPenalty;
|
|
||||||
schedulerThreadData->additional_penalty++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
simple_cancel_penalty(Thread* thread)
|
|
||||||
{
|
|
||||||
scheduler_thread_data* schedulerThreadData = thread->scheduler_data;
|
|
||||||
|
|
||||||
if (schedulerThreadData->priority_penalty != 0)
|
|
||||||
TRACE("cancelling thread %ld penalty\n", thread->id);
|
|
||||||
schedulerThreadData->priority_penalty = 0;
|
|
||||||
schedulerThreadData->additional_penalty = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
simple_enqueue(Thread* thread, bool newOne)
|
|
||||||
{
|
|
||||||
thread->state = thread->next_state = B_THREAD_READY;
|
|
||||||
|
|
||||||
scheduler_thread_data* schedulerThreadData = thread->scheduler_data;
|
|
||||||
|
|
||||||
bigtime_t hasSlept = system_time() - schedulerThreadData->went_sleep;
|
|
||||||
if (newOne && hasSlept > kThreadQuantum)
|
|
||||||
simple_cancel_penalty(thread);
|
|
||||||
|
|
||||||
int32 threadPriority = simple_get_effective_priority(thread);
|
|
||||||
|
|
||||||
T(EnqueueThread(thread, threadPriority));
|
|
||||||
|
|
||||||
bool pinned = sCPURunQueues != NULL && thread->pinned_to_cpu > 0;
|
|
||||||
int32 pinnedCPU = -1;
|
|
||||||
if (pinned) {
|
|
||||||
pinnedCPU = thread->previous_cpu->cpu_num;
|
|
||||||
sCPURunQueues[pinnedCPU].PushBack(thread, threadPriority);
|
|
||||||
} else
|
|
||||||
sRunQueue->PushBack(thread, threadPriority);
|
|
||||||
|
|
||||||
schedulerThreadData->cpu_bound = true;
|
|
||||||
schedulerThreadData->time_left = 0;
|
|
||||||
schedulerThreadData->stolen_time = 0;
|
|
||||||
|
|
||||||
// notify listeners
|
|
||||||
NotifySchedulerListeners(&SchedulerListener::ThreadEnqueuedInRunQueue,
|
|
||||||
thread);
|
|
||||||
|
|
||||||
int32 thisCPU = smp_get_current_cpu();
|
|
||||||
int32 targetCPU = pinnedCPU;
|
|
||||||
|
|
||||||
if (!pinned) {
|
|
||||||
CPUHeapEntry* cpuEntry = sCPUHeap->PeekRoot();
|
|
||||||
ASSERT(cpuEntry != NULL);
|
|
||||||
|
|
||||||
targetCPU = cpuEntry->fCPUNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT(targetCPU >= 0);
|
|
||||||
|
|
||||||
Thread* targetThread = gCPU[targetCPU].running_thread;
|
|
||||||
int32 targetPriority = simple_get_effective_priority(targetThread);
|
|
||||||
|
|
||||||
ASSERT((targetCPU != thisCPU && targetThread != thread)
|
|
||||||
|| targetCPU == thisCPU);
|
|
||||||
|
|
||||||
if (!pinned) {
|
|
||||||
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(&sCPUEntries[targetCPU], threadPriority);
|
|
||||||
|
|
||||||
if (targetCPU == smp_get_current_cpu())
|
|
||||||
gCPU[targetCPU].invoke_scheduler = true;
|
|
||||||
else {
|
|
||||||
smp_send_ici(targetCPU, SMP_MSG_RESCHEDULE, 0, 0, 0, NULL,
|
|
||||||
SMP_MSG_FLAG_ASYNC);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*! Enqueues the thread into the run queue.
|
|
||||||
Note: thread lock must be held when entering this function
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
simple_put_back(Thread* thread)
|
|
||||||
{
|
|
||||||
bool pinned = sCPURunQueues != NULL && thread->pinned_to_cpu > 0;
|
|
||||||
|
|
||||||
if (!pinned)
|
|
||||||
sRunQueue->PushFront(thread, simple_get_effective_priority(thread));
|
|
||||||
else {
|
|
||||||
int32 pinnedCPU = thread->previous_cpu->cpu_num;
|
|
||||||
sCPURunQueues[pinnedCPU].PushFront(thread,
|
|
||||||
simple_get_effective_priority(thread));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*! Sets the priority of a thread.
|
|
||||||
Note: thread lock must be held when entering this function
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
simple_set_thread_priority(Thread* thread, int32 priority)
|
|
||||||
{
|
|
||||||
if (priority == thread->priority)
|
|
||||||
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) {
|
|
||||||
simple_cancel_penalty(thread);
|
|
||||||
thread->priority = priority;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The thread is in the run queue. We need to remove it and re-insert it at
|
|
||||||
// a new position.
|
|
||||||
|
|
||||||
T(RemoveThread(thread));
|
|
||||||
|
|
||||||
// notify listeners
|
|
||||||
NotifySchedulerListeners(&SchedulerListener::ThreadRemovedFromRunQueue,
|
|
||||||
thread);
|
|
||||||
|
|
||||||
// remove thread from run queue
|
|
||||||
sRunQueue->Remove(thread);
|
|
||||||
|
|
||||||
// set priority and re-insert
|
|
||||||
simple_cancel_penalty(thread);
|
|
||||||
thread->priority = priority;
|
|
||||||
|
|
||||||
simple_enqueue_in_run_queue(thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static bigtime_t
|
|
||||||
simple_estimate_max_scheduling_latency(Thread* thread)
|
|
||||||
{
|
|
||||||
// TODO: This is probably meant to be called periodically to return the
|
|
||||||
// current estimate depending on the system usage; we return fixed estimates
|
|
||||||
// per thread priority, though.
|
|
||||||
|
|
||||||
if (thread->priority >= B_REAL_TIME_DISPLAY_PRIORITY)
|
|
||||||
return kThreadQuantum / 4;
|
|
||||||
if (thread->priority >= B_DISPLAY_PRIORITY)
|
|
||||||
return kThreadQuantum;
|
|
||||||
|
|
||||||
return 2 * kThreadQuantum;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int32
|
|
||||||
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* thread= thread_get_current_thread();
|
|
||||||
|
|
||||||
thread->scheduler_data->lost_cpu = true;
|
|
||||||
thread->cpu->invoke_scheduler = true;
|
|
||||||
thread->cpu->preempted = 1;
|
|
||||||
return B_HANDLED_INTERRUPT;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline bool
|
|
||||||
simple_quantum_ended(Thread* thread, bool wasPreempted, bool hasYielded)
|
|
||||||
{
|
|
||||||
scheduler_thread_data* schedulerThreadData = thread->scheduler_data;
|
|
||||||
|
|
||||||
if (hasYielded) {
|
|
||||||
schedulerThreadData->time_left = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bigtime_t time_used = system_time() - schedulerThreadData->quantum_start;
|
|
||||||
schedulerThreadData->time_left -= time_used;
|
|
||||||
schedulerThreadData->time_left = max_c(0, schedulerThreadData->time_left);
|
|
||||||
|
|
||||||
// too little time left, it's better make the next quantum a bit longer
|
|
||||||
if (wasPreempted || schedulerThreadData->time_left <= kThreadQuantum / 50) {
|
|
||||||
schedulerThreadData->stolen_time += schedulerThreadData->time_left;
|
|
||||||
schedulerThreadData->time_left = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return schedulerThreadData->time_left == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline bigtime_t
|
|
||||||
simple_quantum_linear_interpolation(bigtime_t maxQuantum, bigtime_t minQuantum,
|
|
||||||
int32 maxPriority, int32 minPriority, int32 priority)
|
|
||||||
{
|
|
||||||
ASSERT(priority <= maxPriority);
|
|
||||||
ASSERT(priority >= minPriority);
|
|
||||||
|
|
||||||
bigtime_t result = (maxQuantum - minQuantum) * (priority - minPriority);
|
|
||||||
result /= maxPriority - minPriority;
|
|
||||||
return maxQuantum - result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline bigtime_t
|
|
||||||
simple_get_base_quantum(Thread* thread)
|
|
||||||
{
|
|
||||||
int32 priority = simple_get_effective_priority(thread);
|
|
||||||
|
|
||||||
if (priority >= B_URGENT_DISPLAY_PRIORITY)
|
|
||||||
return kThreadQuantum;
|
|
||||||
if (priority > B_NORMAL_PRIORITY) {
|
|
||||||
return simple_quantum_linear_interpolation(kThreadQuantum * 4,
|
|
||||||
kThreadQuantum, B_URGENT_DISPLAY_PRIORITY, B_NORMAL_PRIORITY,
|
|
||||||
priority);
|
|
||||||
}
|
|
||||||
return simple_quantum_linear_interpolation(kThreadQuantum * 64,
|
|
||||||
kThreadQuantum * 4, B_NORMAL_PRIORITY, B_IDLE_PRIORITY, priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline bigtime_t
|
|
||||||
simple_compute_quantum(Thread* thread)
|
|
||||||
{
|
|
||||||
scheduler_thread_data* schedulerThreadData = thread->scheduler_data;
|
|
||||||
|
|
||||||
bigtime_t quantum;
|
|
||||||
if (schedulerThreadData->time_left != 0)
|
|
||||||
quantum = schedulerThreadData->time_left;
|
|
||||||
else
|
|
||||||
quantum = simple_get_base_quantum(thread);
|
|
||||||
|
|
||||||
quantum += schedulerThreadData->stolen_time;
|
|
||||||
schedulerThreadData->stolen_time = 0;
|
|
||||||
|
|
||||||
schedulerThreadData->time_left = quantum;
|
|
||||||
schedulerThreadData->quantum_start = system_time();
|
|
||||||
|
|
||||||
return quantum;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline Thread*
|
|
||||||
simple_dequeue_thread(int32 thisCPU)
|
|
||||||
{
|
|
||||||
Thread* sharedThread = sRunQueue->PeekMaximum();
|
|
||||||
|
|
||||||
Thread* pinnedThread = NULL;
|
|
||||||
if (sCPURunQueues != NULL)
|
|
||||||
pinnedThread = sCPURunQueues[thisCPU].PeekMaximum();
|
|
||||||
|
|
||||||
if (sharedThread == NULL && pinnedThread == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
int32 pinnedPriority = -1;
|
|
||||||
if (pinnedThread != NULL)
|
|
||||||
pinnedPriority = simple_get_effective_priority(pinnedThread);
|
|
||||||
|
|
||||||
int32 sharedPriority = -1;
|
|
||||||
if (sharedThread != NULL)
|
|
||||||
sharedPriority = simple_get_effective_priority(sharedThread);
|
|
||||||
|
|
||||||
if (sharedPriority > pinnedPriority) {
|
|
||||||
sRunQueue->Remove(sharedThread);
|
|
||||||
return sharedThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
sCPURunQueues[thisCPU].Remove(pinnedThread);
|
|
||||||
return pinnedThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*! Runs the scheduler.
|
|
||||||
Note: expects thread spinlock to be held
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
simple_reschedule(void)
|
|
||||||
{
|
|
||||||
Thread* oldThread = thread_get_current_thread();
|
|
||||||
|
|
||||||
int32 thisCPU = smp_get_current_cpu();
|
|
||||||
|
|
||||||
TRACE("reschedule(): cpu %ld, current thread = %ld\n", thisCPU,
|
|
||||||
oldThread->id);
|
|
||||||
|
|
||||||
oldThread->state = oldThread->next_state;
|
|
||||||
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) {
|
|
||||||
case B_THREAD_RUNNING:
|
|
||||||
case B_THREAD_READY:
|
|
||||||
if (!schedulerOldThreadData->lost_cpu)
|
|
||||||
schedulerOldThreadData->cpu_bound = false;
|
|
||||||
|
|
||||||
if (simple_quantum_ended(oldThread, oldThread->cpu->preempted,
|
|
||||||
oldThread->has_yielded)) {
|
|
||||||
if (schedulerOldThreadData->cpu_bound)
|
|
||||||
simple_increase_penalty(oldThread);
|
|
||||||
|
|
||||||
TRACE("enqueueing thread %ld into run queue priority = %ld\n",
|
|
||||||
oldThread->id, simple_get_effective_priority(oldThread));
|
|
||||||
simple_enqueue(oldThread, false);
|
|
||||||
} else {
|
|
||||||
TRACE("putting thread %ld back in run queue priority = %ld\n",
|
|
||||||
oldThread->id, simple_get_effective_priority(oldThread));
|
|
||||||
simple_put_back(oldThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case B_THREAD_SUSPENDED:
|
|
||||||
schedulerOldThreadData->went_sleep = system_time();
|
|
||||||
TRACE("reschedule(): suspending thread %ld\n", oldThread->id);
|
|
||||||
break;
|
|
||||||
case THREAD_STATE_FREE_ON_RESCHED:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
schedulerOldThreadData->went_sleep = system_time();
|
|
||||||
TRACE("not enqueueing thread %ld into run queue next_state = %ld\n",
|
|
||||||
oldThread->id, oldThread->next_state);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
oldThread->has_yielded = false;
|
|
||||||
schedulerOldThreadData->lost_cpu = false;
|
|
||||||
|
|
||||||
// select thread with the biggest priority
|
|
||||||
if (oldThread->cpu->disabled) {
|
|
||||||
ASSERT(sCPURunQueues != NULL);
|
|
||||||
nextThread = sCPURunQueues[thisCPU].PeekMaximum();
|
|
||||||
if (nextThread != NULL)
|
|
||||||
sCPURunQueues[thisCPU].Remove(nextThread);
|
|
||||||
else {
|
|
||||||
nextThread = sRunQueue->GetHead(B_IDLE_PRIORITY);
|
|
||||||
if (nextThread != NULL)
|
|
||||||
sRunQueue->Remove(nextThread);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
nextThread = simple_dequeue_thread(thisCPU);
|
|
||||||
if (!nextThread)
|
|
||||||
panic("reschedule(): run queues are empty!\n");
|
|
||||||
|
|
||||||
TRACE("reschedule(): cpu %ld, next thread = %ld\n", thisCPU,
|
|
||||||
nextThread->id);
|
|
||||||
|
|
||||||
T(ScheduleThread(nextThread, oldThread));
|
|
||||||
|
|
||||||
// update CPU heap
|
|
||||||
sCPUHeap->ModifyKey(&sCPUEntries[thisCPU],
|
|
||||||
simple_get_effective_priority(nextThread));
|
|
||||||
|
|
||||||
// notify listeners
|
|
||||||
NotifySchedulerListeners(&SchedulerListener::ThreadScheduled,
|
|
||||||
oldThread, nextThread);
|
|
||||||
|
|
||||||
nextThread->state = B_THREAD_RUNNING;
|
|
||||||
nextThread->next_state = B_THREAD_READY;
|
|
||||||
|
|
||||||
// track kernel time (user time is tracked in thread_at_kernel_entry())
|
|
||||||
scheduler_update_thread_times(oldThread, nextThread);
|
|
||||||
|
|
||||||
// track CPU activity
|
|
||||||
if (!thread_is_idle_thread(oldThread)) {
|
|
||||||
atomic_add64(&oldThread->cpu->active_time,
|
|
||||||
(oldThread->kernel_time - oldThread->cpu->last_kernel_time)
|
|
||||||
+ (oldThread->user_time - oldThread->cpu->last_user_time));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!thread_is_idle_thread(nextThread)) {
|
|
||||||
oldThread->cpu->last_kernel_time = nextThread->kernel_time;
|
|
||||||
oldThread->cpu->last_user_time = nextThread->user_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextThread != oldThread || oldThread->cpu->preempted) {
|
|
||||||
timer* quantumTimer = &oldThread->cpu->quantum_timer;
|
|
||||||
if (!oldThread->cpu->preempted)
|
|
||||||
cancel_timer(quantumTimer);
|
|
||||||
|
|
||||||
oldThread->cpu->preempted = 0;
|
|
||||||
if (!thread_is_idle_thread(nextThread)) {
|
|
||||||
bigtime_t quantum = simple_compute_quantum(oldThread);
|
|
||||||
add_timer(quantumTimer, &reschedule_event, quantum,
|
|
||||||
B_ONE_SHOT_RELATIVE_TIMER | B_TIMER_ACQUIRE_SCHEDULER_LOCK);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextThread != oldThread)
|
|
||||||
scheduler_switch_thread(oldThread, nextThread);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static status_t
|
|
||||||
simple_on_thread_create(Thread* thread, bool idleThread)
|
|
||||||
{
|
|
||||||
thread->scheduler_data = new (std::nothrow)scheduler_thread_data;
|
|
||||||
if (thread->scheduler_data == NULL)
|
|
||||||
return B_NO_MEMORY;
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
simple_on_thread_init(Thread* thread)
|
|
||||||
{
|
|
||||||
thread->scheduler_data->Init();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
simple_on_thread_destroy(Thread* thread)
|
|
||||||
{
|
|
||||||
delete thread->scheduler_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*! This starts the scheduler. Must be run in the context of the initial idle
|
|
||||||
thread. Interrupts must be disabled and will be disabled when returning.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
simple_start(void)
|
|
||||||
{
|
|
||||||
SpinLocker schedulerLocker(gSchedulerLock);
|
|
||||||
|
|
||||||
simple_reschedule();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static scheduler_ops kSimpleOps = {
|
|
||||||
simple_enqueue_in_run_queue,
|
|
||||||
simple_reschedule,
|
|
||||||
simple_set_thread_priority,
|
|
||||||
simple_estimate_max_scheduling_latency,
|
|
||||||
simple_on_thread_create,
|
|
||||||
simple_on_thread_init,
|
|
||||||
simple_on_thread_destroy,
|
|
||||||
simple_start,
|
|
||||||
NULL,
|
|
||||||
simple_dump_thread_data
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// #pragma mark -
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
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;
|
|
||||||
if (sRunQueue == NULL)
|
|
||||||
return B_NO_MEMORY;
|
|
||||||
ObjectDeleter<SimpleRunQueue> runQueueDeleter(sRunQueue);
|
|
||||||
|
|
||||||
status_t result = sRunQueue->GetInitStatus();
|
|
||||||
if (result != B_OK)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
ArrayDeleter<SimpleRunQueue> cpuRunQueuesDeleter;
|
|
||||||
if (cpuCount > 1) {
|
|
||||||
sCPURunQueues = new(std::nothrow) SimpleRunQueue[cpuCount];
|
|
||||||
if (sCPURunQueues == NULL)
|
|
||||||
return B_NO_MEMORY;
|
|
||||||
cpuRunQueuesDeleter.SetTo(sCPURunQueues);
|
|
||||||
|
|
||||||
for (int i = 0; i < cpuCount; i++) {
|
|
||||||
result = sCPURunQueues[i].GetInitStatus();
|
|
||||||
if (result != B_OK)
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gScheduler = &kSimpleOps;
|
|
||||||
|
|
||||||
add_debugger_command_etc("run_queue", &dump_run_queue,
|
|
||||||
"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();
|
|
||||||
cpuRunQueuesDeleter.Detach();
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2008, Ingo Weinhold, [email protected].
|
|
||||||
* Distributed under the terms of the MIT License.
|
|
||||||
*/
|
|
||||||
#ifndef KERNEL_SCHEDULER_SIMPLE_H
|
|
||||||
#define KERNEL_SCHEDULER_SIMPLE_H
|
|
||||||
|
|
||||||
|
|
||||||
status_t scheduler_simple_init();
|
|
||||||
|
|
||||||
|
|
||||||
#endif // KERNEL_SCHEDULER_SIMPLE_H
|
|
||||||
@@ -1784,10 +1784,8 @@ _dump_thread_info(Thread *thread, bool shortInfo)
|
|||||||
kprintf("flags: 0x%" B_PRIx32 "\n", thread->flags);
|
kprintf("flags: 0x%" B_PRIx32 "\n", thread->flags);
|
||||||
kprintf("architecture dependant section:\n");
|
kprintf("architecture dependant section:\n");
|
||||||
arch_thread_dump_info(&thread->arch_info);
|
arch_thread_dump_info(&thread->arch_info);
|
||||||
if (gScheduler->dump_thread_data != NULL) {
|
kprintf("scheduler data:\n");
|
||||||
kprintf("scheduler data:\n");
|
scheduler_dump_thread_data(thread);
|
||||||
gScheduler->dump_thread_data(thread);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user