* Replaced scheduler_remove_from_run_queue() by
scheduler_set_thread_priority(). Setting the thread priority was the only situation in which it was used. * Renamed scheduler.cpp to scheduler_simple.cpp. * The scheduler functions are no longer called directly. Instead there's an operation vector now, which is initialized at kernel init time. This allows for picking the most suitable scheduler for the machine (e.g. a non-SMP scheduler on a non-SMP machine). git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@28262 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
parent
997c3d8afb
commit
53892c92a0
@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
* Copyright 2005, Axel Dörfler, axeld@pinc-software.de.
|
* Copyright 2005, Axel Dörfler, axeld@pinc-software.de.
|
||||||
* Distributed under the terms of the MIT License.
|
* Distributed under the terms of the MIT License.
|
||||||
*/
|
*/
|
||||||
@ -13,16 +14,29 @@ struct scheduling_analysis;
|
|||||||
struct thread;
|
struct thread;
|
||||||
|
|
||||||
|
|
||||||
|
struct scheduler_ops {
|
||||||
|
void (*enqueue_in_run_queue)(struct thread* thread);
|
||||||
|
void (*reschedule)(void);
|
||||||
|
void (*set_thread_priority)(struct thread* thread, int32 priority);
|
||||||
|
|
||||||
|
void (*start)(void);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct scheduler_ops* gScheduler;
|
||||||
|
|
||||||
|
#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()
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void scheduler_enqueue_in_run_queue(struct thread *thread);
|
|
||||||
void scheduler_remove_from_run_queue(struct thread *thread);
|
|
||||||
void scheduler_reschedule(void);
|
|
||||||
|
|
||||||
void scheduler_init(void);
|
void scheduler_init(void);
|
||||||
void scheduler_start(void);
|
|
||||||
|
|
||||||
status_t _user_analyze_scheduling(bigtime_t from, bigtime_t until, void* buffer,
|
status_t _user_analyze_scheduling(bigtime_t from, bigtime_t until, void* buffer,
|
||||||
size_t size, struct scheduling_analysis* analysis);
|
size_t size, struct scheduling_analysis* analysis);
|
||||||
|
@ -51,6 +51,7 @@ KernelMergeObject kernel_core.o :
|
|||||||
|
|
||||||
# scheduler
|
# scheduler
|
||||||
scheduler.cpp
|
scheduler.cpp
|
||||||
|
scheduler_simple.cpp
|
||||||
scheduler_tracing.cpp
|
scheduler_tracing.cpp
|
||||||
scheduling_analysis.cpp
|
scheduling_analysis.cpp
|
||||||
|
|
||||||
|
@ -163,6 +163,8 @@ _start(kernel_args *bootKernelArgs, int currentCPU)
|
|||||||
smp_init_post_generic_syscalls();
|
smp_init_post_generic_syscalls();
|
||||||
TRACE("init cbuf\n");
|
TRACE("init cbuf\n");
|
||||||
cbuf_init();
|
cbuf_init();
|
||||||
|
TRACE("init scheduler\n");
|
||||||
|
scheduler_init();
|
||||||
TRACE("init threads\n");
|
TRACE("init threads\n");
|
||||||
thread_init(&sKernelArgs);
|
thread_init(&sKernelArgs);
|
||||||
TRACE("init ports\n");
|
TRACE("init ports\n");
|
||||||
@ -178,8 +180,6 @@ _start(kernel_args *bootKernelArgs, int currentCPU)
|
|||||||
TRACE("init VM threads\n");
|
TRACE("init VM threads\n");
|
||||||
vm_init_post_thread(&sKernelArgs);
|
vm_init_post_thread(&sKernelArgs);
|
||||||
low_resource_manager_init_post_thread();
|
low_resource_manager_init_post_thread();
|
||||||
TRACE("init scheduler\n");
|
|
||||||
scheduler_init();
|
|
||||||
TRACE("init notification services\n");
|
TRACE("init notification services\n");
|
||||||
notifications_init();
|
notifications_init();
|
||||||
TRACE("init VFS\n");
|
TRACE("init VFS\n");
|
||||||
|
@ -1,371 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
|
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
* Copyright 2002-2007, Axel Dörfler, axeld@pinc-software.de.
|
|
||||||
* Copyright 2002, Angelo Mottola, a.mottola@libero.it.
|
|
||||||
* Distributed under the terms of the MIT License.
|
* 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 <cpu.h>
|
|
||||||
#include <int.h>
|
|
||||||
#include <kernel.h>
|
|
||||||
#include <kscheduler.h>
|
#include <kscheduler.h>
|
||||||
#include <scheduler_defs.h>
|
|
||||||
#include <smp.h>
|
|
||||||
#include <thread.h>
|
|
||||||
#include <timer.h>
|
|
||||||
#include <user_debugger.h>
|
|
||||||
|
|
||||||
#include "scheduler_tracing.h"
|
#include "scheduler_simple.h"
|
||||||
|
|
||||||
|
|
||||||
//#define TRACE_SCHEDULER
|
struct scheduler_ops* gScheduler;
|
||||||
#ifdef TRACE_SCHEDULER
|
|
||||||
# define TRACE(x) dprintf x
|
|
||||||
#else
|
|
||||||
# define TRACE(x) ;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
// The run queue. Holds the threads ready to run ordered by priority.
|
|
||||||
static struct thread *sRunQueue = NULL;
|
|
||||||
static cpu_mask_t sIdleCPUs = 0;
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
|
||||||
_rand(void)
|
|
||||||
{
|
|
||||||
static int next = 0;
|
|
||||||
|
|
||||||
if (next == 0)
|
|
||||||
next = system_time();
|
|
||||||
|
|
||||||
next = next * 1103515245 + 12345;
|
|
||||||
return (next >> 16) & 0x7FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
|
||||||
dump_run_queue(int argc, char **argv)
|
|
||||||
{
|
|
||||||
struct thread *thread;
|
|
||||||
|
|
||||||
thread = sRunQueue;
|
|
||||||
if (!thread)
|
|
||||||
kprintf("Run queue is empty!\n");
|
|
||||||
else {
|
|
||||||
kprintf("thread id priority name\n");
|
|
||||||
while (thread) {
|
|
||||||
kprintf("%p %-7ld %-8ld %s\n", thread, thread->id,
|
|
||||||
thread->priority, thread->name);
|
|
||||||
thread = thread->queue_next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*! Enqueues the thread into the run queue.
|
|
||||||
Note: thread lock must be held when entering this function
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
scheduler_enqueue_in_run_queue(struct thread *thread)
|
|
||||||
{
|
|
||||||
if (thread->state == B_THREAD_RUNNING) {
|
|
||||||
// The thread is currently running (on another CPU) and we cannot
|
|
||||||
// insert it into the run queue. Set the next state to ready so the
|
|
||||||
// thread is inserted into the run queue on the next reschedule.
|
|
||||||
thread->next_state = B_THREAD_READY;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
thread->state = thread->next_state = B_THREAD_READY;
|
|
||||||
|
|
||||||
struct thread *curr, *prev;
|
|
||||||
for (curr = sRunQueue, prev = NULL; curr
|
|
||||||
&& curr->priority >= thread->next_priority;
|
|
||||||
curr = curr->queue_next) {
|
|
||||||
if (prev)
|
|
||||||
prev = prev->queue_next;
|
|
||||||
else
|
|
||||||
prev = sRunQueue;
|
|
||||||
}
|
|
||||||
|
|
||||||
T(EnqueueThread(thread, prev, curr));
|
|
||||||
|
|
||||||
thread->queue_next = curr;
|
|
||||||
if (prev)
|
|
||||||
prev->queue_next = thread;
|
|
||||||
else
|
|
||||||
sRunQueue = thread;
|
|
||||||
|
|
||||||
thread->next_priority = thread->priority;
|
|
||||||
|
|
||||||
if (thread->priority != B_IDLE_PRIORITY) {
|
|
||||||
int32 currentCPU = smp_get_current_cpu();
|
|
||||||
if (sIdleCPUs != 0) {
|
|
||||||
if (thread->pinned_to_cpu > 0) {
|
|
||||||
// thread is pinned to a CPU -- notify it, if it is idle
|
|
||||||
int32 targetCPU = thread->previous_cpu->cpu_num;
|
|
||||||
if ((sIdleCPUs & (1 << targetCPU)) != 0) {
|
|
||||||
sIdleCPUs &= ~(1 << targetCPU);
|
|
||||||
smp_send_ici(targetCPU, SMP_MSG_RESCHEDULE_IF_IDLE, 0, 0,
|
|
||||||
0, NULL, SMP_MSG_FLAG_ASYNC);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Thread is not pinned to any CPU -- take it ourselves, if we
|
|
||||||
// are idle, otherwise notify the next idle CPU. In either case
|
|
||||||
// we clear the idle bit of the chosen CPU, so that the
|
|
||||||
// scheduler_enqueue_in_run_queue() won't try to bother the
|
|
||||||
// same CPU again, if invoked before it handled the interrupt.
|
|
||||||
cpu_mask_t idleCPUs = CLEAR_BIT(sIdleCPUs, currentCPU);
|
|
||||||
if ((sIdleCPUs & (1 << currentCPU)) != 0) {
|
|
||||||
sIdleCPUs = idleCPUs;
|
|
||||||
} else {
|
|
||||||
int32 targetCPU = 0;
|
|
||||||
for (; targetCPU < B_MAX_CPU_COUNT; targetCPU++) {
|
|
||||||
cpu_mask_t mask = 1 << targetCPU;
|
|
||||||
if ((idleCPUs & mask) != 0) {
|
|
||||||
sIdleCPUs &= ~mask;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
smp_send_ici(targetCPU, SMP_MSG_RESCHEDULE_IF_IDLE, 0, 0,
|
|
||||||
0, NULL, SMP_MSG_FLAG_ASYNC);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*! Removes a thread from the run queue.
|
|
||||||
Note: thread lock must be held when entering this function
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
scheduler_remove_from_run_queue(struct thread *thread)
|
|
||||||
{
|
|
||||||
struct thread *item, *prev;
|
|
||||||
|
|
||||||
T(RemoveThread(thread));
|
|
||||||
|
|
||||||
// find thread in run queue
|
|
||||||
for (item = sRunQueue, prev = NULL; item && item != thread;
|
|
||||||
item = item->queue_next) {
|
|
||||||
if (prev)
|
|
||||||
prev = prev->queue_next;
|
|
||||||
else
|
|
||||||
prev = sRunQueue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT(item == thread);
|
|
||||||
|
|
||||||
if (prev)
|
|
||||||
prev->queue_next = item->queue_next;
|
|
||||||
else
|
|
||||||
sRunQueue = item->queue_next;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
context_switch(struct thread *fromThread, struct thread *toThread)
|
|
||||||
{
|
|
||||||
if ((fromThread->flags & THREAD_FLAGS_DEBUGGER_INSTALLED) != 0)
|
|
||||||
user_debug_thread_unscheduled(fromThread);
|
|
||||||
|
|
||||||
toThread->previous_cpu = toThread->cpu = fromThread->cpu;
|
|
||||||
fromThread->cpu = NULL;
|
|
||||||
|
|
||||||
arch_thread_set_current_thread(toThread);
|
|
||||||
arch_thread_context_switch(fromThread, toThread);
|
|
||||||
|
|
||||||
// Looks weird, but is correct. fromThread had been unscheduled earlier,
|
|
||||||
// but is back now. The notification for a thread scheduled the first time
|
|
||||||
// happens in thread.cpp:thread_kthread_entry().
|
|
||||||
if ((fromThread->flags & THREAD_FLAGS_DEBUGGER_INSTALLED) != 0)
|
|
||||||
user_debug_thread_scheduled(fromThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int32
|
|
||||||
reschedule_event(timer *unused)
|
|
||||||
{
|
|
||||||
if (thread_get_current_thread()->keep_scheduled > 0)
|
|
||||||
return B_HANDLED_INTERRUPT;
|
|
||||||
|
|
||||||
// this function is called as a result of the timer event set by the
|
|
||||||
// scheduler returning this causes a reschedule on the timer event
|
|
||||||
thread_get_current_thread()->cpu->preempted = 1;
|
|
||||||
return B_INVOKE_SCHEDULER;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*! Runs the scheduler.
|
|
||||||
Note: expects thread spinlock to be held
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
scheduler_reschedule(void)
|
|
||||||
{
|
|
||||||
struct thread *oldThread = thread_get_current_thread();
|
|
||||||
struct thread *nextThread, *prevThread;
|
|
||||||
|
|
||||||
TRACE(("reschedule(): cpu %d, cur_thread = %ld\n", smp_get_current_cpu(), thread_get_current_thread()->id));
|
|
||||||
|
|
||||||
oldThread->cpu->invoke_scheduler = false;
|
|
||||||
|
|
||||||
oldThread->state = oldThread->next_state;
|
|
||||||
switch (oldThread->next_state) {
|
|
||||||
case B_THREAD_RUNNING:
|
|
||||||
case B_THREAD_READY:
|
|
||||||
TRACE(("enqueueing thread %ld into run q. pri = %ld\n", oldThread->id, oldThread->priority));
|
|
||||||
scheduler_enqueue_in_run_queue(oldThread);
|
|
||||||
break;
|
|
||||||
case B_THREAD_SUSPENDED:
|
|
||||||
TRACE(("reschedule(): suspending thread %ld\n", oldThread->id));
|
|
||||||
break;
|
|
||||||
case THREAD_STATE_FREE_ON_RESCHED:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
TRACE(("not enqueueing thread %ld into run q. next_state = %ld\n", oldThread->id, oldThread->next_state));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextThread = sRunQueue;
|
|
||||||
prevThread = NULL;
|
|
||||||
|
|
||||||
if (oldThread->cpu->disabled) {
|
|
||||||
// CPU is disabled - just select an idle thread
|
|
||||||
while (nextThread && nextThread->priority > B_IDLE_PRIORITY) {
|
|
||||||
prevThread = nextThread;
|
|
||||||
nextThread = nextThread->queue_next;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
while (nextThread) {
|
|
||||||
// select next thread from the run queue
|
|
||||||
while (nextThread && nextThread->priority > B_IDLE_PRIORITY) {
|
|
||||||
#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
|
|
||||||
|
|
||||||
// skip thread, if it doesn't want to run on this CPU
|
|
||||||
if (nextThread->pinned_to_cpu > 0
|
|
||||||
&& nextThread->previous_cpu != oldThread->cpu) {
|
|
||||||
prevThread = nextThread;
|
|
||||||
nextThread = nextThread->queue_next;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// always extract real time threads
|
|
||||||
if (nextThread->priority >= B_FIRST_REAL_TIME_PRIORITY)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// never skip last non-idle normal thread
|
|
||||||
if (nextThread->queue_next && nextThread->queue_next->priority == B_IDLE_PRIORITY)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// skip normal threads sometimes (roughly 20%)
|
|
||||||
if (_rand() > 0x1a00)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// skip until next lower priority
|
|
||||||
int32 priority = nextThread->priority;
|
|
||||||
do {
|
|
||||||
prevThread = nextThread;
|
|
||||||
nextThread = nextThread->queue_next;
|
|
||||||
} while (nextThread->queue_next != NULL
|
|
||||||
&& priority == nextThread->queue_next->priority
|
|
||||||
&& nextThread->queue_next->priority > B_IDLE_PRIORITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextThread->cpu
|
|
||||||
&& nextThread->cpu->cpu_num != oldThread->cpu->cpu_num) {
|
|
||||||
panic("thread in run queue that's still running on another CPU!\n");
|
|
||||||
// ToDo: remove this check completely when we're sure that this
|
|
||||||
// cannot happen anymore.
|
|
||||||
prevThread = nextThread;
|
|
||||||
nextThread = nextThread->queue_next;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!nextThread)
|
|
||||||
panic("reschedule(): run queue is empty!\n");
|
|
||||||
|
|
||||||
// extract selected thread from the run queue
|
|
||||||
if (prevThread)
|
|
||||||
prevThread->queue_next = nextThread->queue_next;
|
|
||||||
else
|
|
||||||
sRunQueue = nextThread->queue_next;
|
|
||||||
|
|
||||||
T(ScheduleThread(nextThread, oldThread));
|
|
||||||
|
|
||||||
nextThread->state = B_THREAD_RUNNING;
|
|
||||||
nextThread->next_state = B_THREAD_READY;
|
|
||||||
oldThread->was_yielded = false;
|
|
||||||
|
|
||||||
// track kernel time (user time is tracked in thread_at_kernel_entry())
|
|
||||||
bigtime_t now = system_time();
|
|
||||||
oldThread->kernel_time += now - oldThread->last_time;
|
|
||||||
nextThread->last_time = now;
|
|
||||||
|
|
||||||
// track CPU activity
|
|
||||||
if (!thread_is_idle_thread(oldThread)) {
|
|
||||||
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) {
|
|
||||||
bigtime_t quantum = 3000; // ToDo: calculate quantum!
|
|
||||||
timer *quantumTimer = &oldThread->cpu->quantum_timer;
|
|
||||||
|
|
||||||
if (!oldThread->cpu->preempted)
|
|
||||||
cancel_timer(quantumTimer);
|
|
||||||
|
|
||||||
oldThread->cpu->preempted = 0;
|
|
||||||
add_timer(quantumTimer, &reschedule_event, quantum,
|
|
||||||
B_ONE_SHOT_RELATIVE_TIMER | B_TIMER_ACQUIRE_THREAD_LOCK);
|
|
||||||
|
|
||||||
// update the idle bit for this CPU in the CPU mask
|
|
||||||
int32 cpuNum = smp_get_current_cpu();
|
|
||||||
if (nextThread->priority == B_IDLE_PRIORITY)
|
|
||||||
sIdleCPUs = SET_BIT(sIdleCPUs, cpuNum);
|
|
||||||
else
|
|
||||||
sIdleCPUs = CLEAR_BIT(sIdleCPUs, cpuNum);
|
|
||||||
|
|
||||||
if (nextThread != oldThread)
|
|
||||||
context_switch(oldThread, nextThread);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
scheduler_init(void)
|
scheduler_init(void)
|
||||||
{
|
{
|
||||||
add_debugger_command_etc("run_queue", &dump_run_queue,
|
scheduler_simple_init();
|
||||||
"List threads in run queue", "\nLists threads in run queue", 0);
|
|
||||||
|
|
||||||
#if SCHEDULER_TRACING
|
#if SCHEDULER_TRACING
|
||||||
add_debugger_command_etc("scheduler", &cmd_scheduler,
|
add_debugger_command_etc("scheduler", &cmd_scheduler,
|
||||||
@ -375,19 +24,3 @@ scheduler_init(void)
|
|||||||
" <thread> - ID of the thread.\n", 0);
|
" <thread> - ID of the thread.\n", 0);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*! This starts the scheduler. Must be run under the context of
|
|
||||||
the initial idle thread.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
scheduler_start(void)
|
|
||||||
{
|
|
||||||
cpu_status state = disable_interrupts();
|
|
||||||
GRAB_THREAD_LOCK();
|
|
||||||
|
|
||||||
scheduler_reschedule();
|
|
||||||
|
|
||||||
RELEASE_THREAD_LOCK();
|
|
||||||
restore_interrupts(state);
|
|
||||||
}
|
|
||||||
|
413
src/system/kernel/scheduler/scheduler_simple.cpp
Normal file
413
src/system/kernel/scheduler/scheduler_simple.cpp
Normal file
@ -0,0 +1,413 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Copyright 2002-2007, Axel Dörfler, axeld@pinc-software.de.
|
||||||
|
* Copyright 2002, Angelo Mottola, a.mottola@libero.it.
|
||||||
|
* 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 <cpu.h>
|
||||||
|
#include <int.h>
|
||||||
|
#include <kernel.h>
|
||||||
|
#include <kscheduler.h>
|
||||||
|
#include <scheduler_defs.h>
|
||||||
|
#include <smp.h>
|
||||||
|
#include <thread.h>
|
||||||
|
#include <timer.h>
|
||||||
|
#include <user_debugger.h>
|
||||||
|
|
||||||
|
#include "scheduler_tracing.h"
|
||||||
|
|
||||||
|
|
||||||
|
//#define TRACE_SCHEDULER
|
||||||
|
#ifdef TRACE_SCHEDULER
|
||||||
|
# define TRACE(x) dprintf x
|
||||||
|
#else
|
||||||
|
# define TRACE(x) ;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// The run queue. Holds the threads ready to run ordered by priority.
|
||||||
|
static struct thread *sRunQueue = NULL;
|
||||||
|
static cpu_mask_t sIdleCPUs = 0;
|
||||||
|
|
||||||
|
|
||||||
|
static int
|
||||||
|
_rand(void)
|
||||||
|
{
|
||||||
|
static int next = 0;
|
||||||
|
|
||||||
|
if (next == 0)
|
||||||
|
next = system_time();
|
||||||
|
|
||||||
|
next = next * 1103515245 + 12345;
|
||||||
|
return (next >> 16) & 0x7FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int
|
||||||
|
dump_run_queue(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct thread *thread;
|
||||||
|
|
||||||
|
thread = sRunQueue;
|
||||||
|
if (!thread)
|
||||||
|
kprintf("Run queue is empty!\n");
|
||||||
|
else {
|
||||||
|
kprintf("thread id priority name\n");
|
||||||
|
while (thread) {
|
||||||
|
kprintf("%p %-7ld %-8ld %s\n", thread, thread->id,
|
||||||
|
thread->priority, thread->name);
|
||||||
|
thread = thread->queue_next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*! Enqueues the thread into the run queue.
|
||||||
|
Note: thread lock must be held when entering this function
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
simple_enqueue_in_run_queue(struct thread *thread)
|
||||||
|
{
|
||||||
|
if (thread->state == B_THREAD_RUNNING) {
|
||||||
|
// The thread is currently running (on another CPU) and we cannot
|
||||||
|
// insert it into the run queue. Set the next state to ready so the
|
||||||
|
// thread is inserted into the run queue on the next reschedule.
|
||||||
|
thread->next_state = B_THREAD_READY;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread->state = thread->next_state = B_THREAD_READY;
|
||||||
|
|
||||||
|
struct thread *curr, *prev;
|
||||||
|
for (curr = sRunQueue, prev = NULL; curr
|
||||||
|
&& curr->priority >= thread->next_priority;
|
||||||
|
curr = curr->queue_next) {
|
||||||
|
if (prev)
|
||||||
|
prev = prev->queue_next;
|
||||||
|
else
|
||||||
|
prev = sRunQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
T(EnqueueThread(thread, prev, curr));
|
||||||
|
|
||||||
|
thread->queue_next = curr;
|
||||||
|
if (prev)
|
||||||
|
prev->queue_next = thread;
|
||||||
|
else
|
||||||
|
sRunQueue = thread;
|
||||||
|
|
||||||
|
thread->next_priority = thread->priority;
|
||||||
|
|
||||||
|
if (thread->priority != B_IDLE_PRIORITY) {
|
||||||
|
int32 currentCPU = smp_get_current_cpu();
|
||||||
|
if (sIdleCPUs != 0) {
|
||||||
|
if (thread->pinned_to_cpu > 0) {
|
||||||
|
// thread is pinned to a CPU -- notify it, if it is idle
|
||||||
|
int32 targetCPU = thread->previous_cpu->cpu_num;
|
||||||
|
if ((sIdleCPUs & (1 << targetCPU)) != 0) {
|
||||||
|
sIdleCPUs &= ~(1 << targetCPU);
|
||||||
|
smp_send_ici(targetCPU, SMP_MSG_RESCHEDULE_IF_IDLE, 0, 0,
|
||||||
|
0, NULL, SMP_MSG_FLAG_ASYNC);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Thread is not pinned to any CPU -- take it ourselves, if we
|
||||||
|
// are idle, otherwise notify the next idle CPU. In either case
|
||||||
|
// we clear the idle bit of the chosen CPU, so that the
|
||||||
|
// simple_enqueue_in_run_queue() won't try to bother the
|
||||||
|
// same CPU again, if invoked before it handled the interrupt.
|
||||||
|
cpu_mask_t idleCPUs = CLEAR_BIT(sIdleCPUs, currentCPU);
|
||||||
|
if ((sIdleCPUs & (1 << currentCPU)) != 0) {
|
||||||
|
sIdleCPUs = idleCPUs;
|
||||||
|
} else {
|
||||||
|
int32 targetCPU = 0;
|
||||||
|
for (; targetCPU < B_MAX_CPU_COUNT; targetCPU++) {
|
||||||
|
cpu_mask_t mask = 1 << targetCPU;
|
||||||
|
if ((idleCPUs & mask) != 0) {
|
||||||
|
sIdleCPUs &= ~mask;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
smp_send_ici(targetCPU, SMP_MSG_RESCHEDULE_IF_IDLE, 0, 0,
|
||||||
|
0, NULL, SMP_MSG_FLAG_ASYNC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*! Sets the priority of a thread.
|
||||||
|
Note: thread lock must be held when entering this function
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
simple_set_thread_priority(struct thread *thread, int32 priority)
|
||||||
|
{
|
||||||
|
if (priority == thread->priority)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (thread->state != B_THREAD_READY) {
|
||||||
|
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));
|
||||||
|
|
||||||
|
// find thread in run queue
|
||||||
|
struct thread *item, *prev;
|
||||||
|
for (item = sRunQueue, prev = NULL; item && item != thread;
|
||||||
|
item = item->queue_next) {
|
||||||
|
if (prev)
|
||||||
|
prev = prev->queue_next;
|
||||||
|
else
|
||||||
|
prev = sRunQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(item == thread);
|
||||||
|
|
||||||
|
// remove the thread
|
||||||
|
if (prev)
|
||||||
|
prev->queue_next = item->queue_next;
|
||||||
|
else
|
||||||
|
sRunQueue = item->queue_next;
|
||||||
|
|
||||||
|
// set priority and re-insert
|
||||||
|
thread->priority = priority;
|
||||||
|
simple_enqueue_in_run_queue(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
context_switch(struct thread *fromThread, struct thread *toThread)
|
||||||
|
{
|
||||||
|
if ((fromThread->flags & THREAD_FLAGS_DEBUGGER_INSTALLED) != 0)
|
||||||
|
user_debug_thread_unscheduled(fromThread);
|
||||||
|
|
||||||
|
toThread->previous_cpu = toThread->cpu = fromThread->cpu;
|
||||||
|
fromThread->cpu = NULL;
|
||||||
|
|
||||||
|
arch_thread_set_current_thread(toThread);
|
||||||
|
arch_thread_context_switch(fromThread, toThread);
|
||||||
|
|
||||||
|
// Looks weird, but is correct. fromThread had been unscheduled earlier,
|
||||||
|
// but is back now. The notification for a thread scheduled the first time
|
||||||
|
// happens in thread.cpp:thread_kthread_entry().
|
||||||
|
if ((fromThread->flags & THREAD_FLAGS_DEBUGGER_INSTALLED) != 0)
|
||||||
|
user_debug_thread_scheduled(fromThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int32
|
||||||
|
reschedule_event(timer *unused)
|
||||||
|
{
|
||||||
|
if (thread_get_current_thread()->keep_scheduled > 0)
|
||||||
|
return B_HANDLED_INTERRUPT;
|
||||||
|
|
||||||
|
// this function is called as a result of the timer event set by the
|
||||||
|
// scheduler returning this causes a reschedule on the timer event
|
||||||
|
thread_get_current_thread()->cpu->preempted = 1;
|
||||||
|
return B_INVOKE_SCHEDULER;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*! Runs the scheduler.
|
||||||
|
Note: expects thread spinlock to be held
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
simple_reschedule(void)
|
||||||
|
{
|
||||||
|
struct thread *oldThread = thread_get_current_thread();
|
||||||
|
struct thread *nextThread, *prevThread;
|
||||||
|
|
||||||
|
TRACE(("reschedule(): cpu %d, cur_thread = %ld\n", smp_get_current_cpu(), thread_get_current_thread()->id));
|
||||||
|
|
||||||
|
oldThread->cpu->invoke_scheduler = false;
|
||||||
|
|
||||||
|
oldThread->state = oldThread->next_state;
|
||||||
|
switch (oldThread->next_state) {
|
||||||
|
case B_THREAD_RUNNING:
|
||||||
|
case B_THREAD_READY:
|
||||||
|
TRACE(("enqueueing thread %ld into run q. pri = %ld\n", oldThread->id, oldThread->priority));
|
||||||
|
simple_enqueue_in_run_queue(oldThread);
|
||||||
|
break;
|
||||||
|
case B_THREAD_SUSPENDED:
|
||||||
|
TRACE(("reschedule(): suspending thread %ld\n", oldThread->id));
|
||||||
|
break;
|
||||||
|
case THREAD_STATE_FREE_ON_RESCHED:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
TRACE(("not enqueueing thread %ld into run q. next_state = %ld\n", oldThread->id, oldThread->next_state));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextThread = sRunQueue;
|
||||||
|
prevThread = NULL;
|
||||||
|
|
||||||
|
if (oldThread->cpu->disabled) {
|
||||||
|
// CPU is disabled - just select an idle thread
|
||||||
|
while (nextThread && nextThread->priority > B_IDLE_PRIORITY) {
|
||||||
|
prevThread = nextThread;
|
||||||
|
nextThread = nextThread->queue_next;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while (nextThread) {
|
||||||
|
// select next thread from the run queue
|
||||||
|
while (nextThread && nextThread->priority > B_IDLE_PRIORITY) {
|
||||||
|
#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
|
||||||
|
|
||||||
|
// skip thread, if it doesn't want to run on this CPU
|
||||||
|
if (nextThread->pinned_to_cpu > 0
|
||||||
|
&& nextThread->previous_cpu != oldThread->cpu) {
|
||||||
|
prevThread = nextThread;
|
||||||
|
nextThread = nextThread->queue_next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// always extract real time threads
|
||||||
|
if (nextThread->priority >= B_FIRST_REAL_TIME_PRIORITY)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// never skip last non-idle normal thread
|
||||||
|
if (nextThread->queue_next && nextThread->queue_next->priority == B_IDLE_PRIORITY)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// skip normal threads sometimes (roughly 20%)
|
||||||
|
if (_rand() > 0x1a00)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// skip until next lower priority
|
||||||
|
int32 priority = nextThread->priority;
|
||||||
|
do {
|
||||||
|
prevThread = nextThread;
|
||||||
|
nextThread = nextThread->queue_next;
|
||||||
|
} while (nextThread->queue_next != NULL
|
||||||
|
&& priority == nextThread->queue_next->priority
|
||||||
|
&& nextThread->queue_next->priority > B_IDLE_PRIORITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextThread->cpu
|
||||||
|
&& nextThread->cpu->cpu_num != oldThread->cpu->cpu_num) {
|
||||||
|
panic("thread in run queue that's still running on another CPU!\n");
|
||||||
|
// ToDo: remove this check completely when we're sure that this
|
||||||
|
// cannot happen anymore.
|
||||||
|
prevThread = nextThread;
|
||||||
|
nextThread = nextThread->queue_next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextThread)
|
||||||
|
panic("reschedule(): run queue is empty!\n");
|
||||||
|
|
||||||
|
// extract selected thread from the run queue
|
||||||
|
if (prevThread)
|
||||||
|
prevThread->queue_next = nextThread->queue_next;
|
||||||
|
else
|
||||||
|
sRunQueue = nextThread->queue_next;
|
||||||
|
|
||||||
|
T(ScheduleThread(nextThread, oldThread));
|
||||||
|
|
||||||
|
nextThread->state = B_THREAD_RUNNING;
|
||||||
|
nextThread->next_state = B_THREAD_READY;
|
||||||
|
oldThread->was_yielded = false;
|
||||||
|
|
||||||
|
// track kernel time (user time is tracked in thread_at_kernel_entry())
|
||||||
|
bigtime_t now = system_time();
|
||||||
|
oldThread->kernel_time += now - oldThread->last_time;
|
||||||
|
nextThread->last_time = now;
|
||||||
|
|
||||||
|
// track CPU activity
|
||||||
|
if (!thread_is_idle_thread(oldThread)) {
|
||||||
|
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) {
|
||||||
|
bigtime_t quantum = 3000; // ToDo: calculate quantum!
|
||||||
|
timer *quantumTimer = &oldThread->cpu->quantum_timer;
|
||||||
|
|
||||||
|
if (!oldThread->cpu->preempted)
|
||||||
|
cancel_timer(quantumTimer);
|
||||||
|
|
||||||
|
oldThread->cpu->preempted = 0;
|
||||||
|
add_timer(quantumTimer, &reschedule_event, quantum,
|
||||||
|
B_ONE_SHOT_RELATIVE_TIMER | B_TIMER_ACQUIRE_THREAD_LOCK);
|
||||||
|
|
||||||
|
// update the idle bit for this CPU in the CPU mask
|
||||||
|
int32 cpuNum = smp_get_current_cpu();
|
||||||
|
if (nextThread->priority == B_IDLE_PRIORITY)
|
||||||
|
sIdleCPUs = SET_BIT(sIdleCPUs, cpuNum);
|
||||||
|
else
|
||||||
|
sIdleCPUs = CLEAR_BIT(sIdleCPUs, cpuNum);
|
||||||
|
|
||||||
|
if (nextThread != oldThread)
|
||||||
|
context_switch(oldThread, nextThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*! This starts the scheduler. Must be run under the context of
|
||||||
|
the initial idle thread.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
simple_start(void)
|
||||||
|
{
|
||||||
|
cpu_status state = disable_interrupts();
|
||||||
|
GRAB_THREAD_LOCK();
|
||||||
|
|
||||||
|
simple_reschedule();
|
||||||
|
|
||||||
|
RELEASE_THREAD_LOCK();
|
||||||
|
restore_interrupts(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static scheduler_ops kSimpleOps = {
|
||||||
|
simple_enqueue_in_run_queue,
|
||||||
|
simple_reschedule,
|
||||||
|
simple_set_thread_priority,
|
||||||
|
simple_start
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark -
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
scheduler_simple_init()
|
||||||
|
{
|
||||||
|
gScheduler = &kSimpleOps;
|
||||||
|
|
||||||
|
add_debugger_command_etc("run_queue", &dump_run_queue,
|
||||||
|
"List threads in run queue", "\nLists threads in run queue", 0);
|
||||||
|
}
|
12
src/system/kernel/scheduler/scheduler_simple.h
Normal file
12
src/system/kernel/scheduler/scheduler_simple.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
#ifndef KERNEL_SCHEDULER_SIMPLE_H
|
||||||
|
#define KERNEL_SCHEDULER_SIMPLE_H
|
||||||
|
|
||||||
|
|
||||||
|
void scheduler_simple_init();
|
||||||
|
|
||||||
|
|
||||||
|
#endif // KERNEL_SCHEDULER_SIMPLE_H
|
@ -228,7 +228,7 @@ public:
|
|||||||
fRemainingBytes = (addr_t)fHashTable - (addr_t)fBuffer;
|
fRemainingBytes = (addr_t)fHashTable - (addr_t)fBuffer;
|
||||||
|
|
||||||
image_info info;
|
image_info info;
|
||||||
if (elf_get_image_info_for_address((addr_t)&scheduler_start, &info)
|
if (elf_get_image_info_for_address((addr_t)&scheduler_init, &info)
|
||||||
== B_OK) {
|
== B_OK) {
|
||||||
fKernelStart = (addr_t)info.text;
|
fKernelStart = (addr_t)info.text;
|
||||||
fKernelEnd = (addr_t)info.data + info.data_size;
|
fKernelEnd = (addr_t)info.data + info.data_size;
|
||||||
|
@ -2523,14 +2523,7 @@ set_thread_priority(thread_id id, int32 priority)
|
|||||||
thread = thread_get_thread_struct_locked(id);
|
thread = thread_get_thread_struct_locked(id);
|
||||||
if (thread) {
|
if (thread) {
|
||||||
oldPriority = thread->priority;
|
oldPriority = thread->priority;
|
||||||
thread->next_priority = priority;
|
scheduler_set_thread_priority(thread, priority);
|
||||||
if (thread->state == B_THREAD_READY && thread->priority != priority) {
|
|
||||||
// if the thread is in the run queue, we reinsert it at a new position
|
|
||||||
scheduler_remove_from_run_queue(thread);
|
|
||||||
thread->priority = priority;
|
|
||||||
scheduler_enqueue_in_run_queue(thread);
|
|
||||||
} else
|
|
||||||
thread->priority = priority;
|
|
||||||
} else
|
} else
|
||||||
oldPriority = B_BAD_THREAD_ID;
|
oldPriority = B_BAD_THREAD_ID;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user