mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
30cd318c17
Note: This is an incompatible ABI change.
344 lines
9.7 KiB
C++
344 lines
9.7 KiB
C++
/*******************************************************************************
|
|
|
|
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014.
|
|
|
|
This file is part of Sortix.
|
|
|
|
Sortix is free software: you can redistribute it and/or modify it under the
|
|
terms of the GNU General Public License as published by the Free Software
|
|
Foundation, either version 3 of the License, or (at your option) any later
|
|
version.
|
|
|
|
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
details.
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
Sortix. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
scheduler.cpp
|
|
Decides the order to execute threads in and switching between them.
|
|
|
|
*******************************************************************************/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <assert.h>
|
|
#include <msr.h>
|
|
#include <string.h>
|
|
|
|
#include <timespec.h>
|
|
|
|
#include <sortix/clock.h>
|
|
#include <sortix/timespec.h>
|
|
|
|
#include <sortix/kernel/interrupt.h>
|
|
#include <sortix/kernel/kernel.h>
|
|
#include <sortix/kernel/memorymanagement.h>
|
|
#include <sortix/kernel/process.h>
|
|
#include <sortix/kernel/scheduler.h>
|
|
#include <sortix/kernel/signal.h>
|
|
#include <sortix/kernel/syscall.h>
|
|
#include <sortix/kernel/thread.h>
|
|
#include <sortix/kernel/time.h>
|
|
|
|
#include "x86-family/gdt.h"
|
|
#include "x86-family/float.h"
|
|
|
|
namespace Sortix {
|
|
namespace Scheduler {
|
|
|
|
const uint32_t SCHED_MAGIC = 0x1234567;
|
|
|
|
volatile unsigned long premagic;
|
|
static Thread* currentthread;
|
|
} // namespace Scheduler
|
|
Thread* CurrentThread() { return Scheduler::currentthread; }
|
|
Process* CurrentProcess() { return CurrentThread()->process; }
|
|
namespace Scheduler {
|
|
|
|
uint8_t dummythreaddata[sizeof(Thread)] __attribute__ ((aligned (8)));
|
|
Thread* dummythread;
|
|
Thread* idlethread;
|
|
Thread* firstrunnablethread;
|
|
Thread* firstsleepingthread;
|
|
Process* initprocess;
|
|
volatile unsigned long postmagic;
|
|
|
|
static inline void SetCurrentThread(Thread* newcurrentthread)
|
|
{
|
|
currentthread = newcurrentthread;
|
|
}
|
|
|
|
void LogBeginSwitch(Thread* current, const CPU::InterruptRegisters* regs);
|
|
void LogSwitch(Thread* current, Thread* next);
|
|
void LogEndSwitch(Thread* current, const CPU::InterruptRegisters* regs);
|
|
|
|
static Thread* PopNextThread()
|
|
{
|
|
if ( !firstrunnablethread ) { return idlethread; }
|
|
Thread* result = firstrunnablethread;
|
|
firstrunnablethread = firstrunnablethread->schedulerlistnext;
|
|
return result;
|
|
}
|
|
|
|
static Thread* ValidatedPopNextThread()
|
|
{
|
|
assert(premagic == SCHED_MAGIC);
|
|
assert(postmagic == SCHED_MAGIC);
|
|
Thread* nextthread = PopNextThread();
|
|
if ( !nextthread ) { Panic("Had no thread to switch to."); }
|
|
if ( nextthread->terminated )
|
|
{
|
|
PanicF("Running a terminated thread 0x%p", nextthread);
|
|
}
|
|
addr_t newaddrspace = nextthread->process->addrspace;
|
|
if ( !Page::IsAligned(newaddrspace) )
|
|
{
|
|
PanicF("Thread 0x%p, process %ji (0x%p) (backup: %i), had bad "
|
|
"address space variable: 0x%zx: not page-aligned "
|
|
"(backup: 0x%zx)\n", nextthread,
|
|
(intmax_t) nextthread->process->pid, nextthread->process,
|
|
-1/*nextthread->pidbackup*/, newaddrspace,
|
|
(addr_t)-1 /*nextthread->addrspacebackup*/);
|
|
}
|
|
return nextthread;
|
|
}
|
|
|
|
static void DoActualSwitch(CPU::InterruptRegisters* regs)
|
|
{
|
|
Thread* current = CurrentThread();
|
|
LogBeginSwitch(current, regs);
|
|
|
|
Thread* next = ValidatedPopNextThread();
|
|
LogSwitch(current, next);
|
|
|
|
if ( current == next ) { return; }
|
|
|
|
current->SaveRegisters(regs);
|
|
next->LoadRegisters(regs);
|
|
|
|
Float::NotityTaskSwitch();
|
|
|
|
addr_t newaddrspace = next->addrspace;
|
|
Memory::SwitchAddressSpace(newaddrspace);
|
|
SetCurrentThread(next);
|
|
|
|
addr_t stacklower = next->kernelstackpos;
|
|
size_t stacksize = next->kernelstacksize;
|
|
addr_t stackhigher = stacklower + stacksize;
|
|
assert(stacklower && stacksize && stackhigher);
|
|
GDT::SetKernelStack(stacklower, stacksize, stackhigher);
|
|
|
|
#if defined(__x86_64__)
|
|
current->fsbase = (unsigned long) rdmsr(MSRID_FSBASE);
|
|
current->gsbase = (unsigned long) rdmsr(MSRID_GSBASE);
|
|
wrmsr(MSRID_FSBASE, (uint64_t) next->fsbase);
|
|
wrmsr(MSRID_GSBASE, (uint64_t) next->gsbase);
|
|
#elif defined(__i386__)
|
|
current->fsbase = (unsigned long) GDT::GetFSBase();
|
|
current->gsbase = (unsigned long) GDT::GetGSBase();
|
|
GDT::SetFSBase((uint32_t) next->fsbase);
|
|
GDT::SetGSBase((uint32_t) next->gsbase);
|
|
#endif
|
|
|
|
LogEndSwitch(next, regs);
|
|
}
|
|
|
|
void Switch(CPU::InterruptRegisters* regs)
|
|
{
|
|
assert(premagic == SCHED_MAGIC);
|
|
assert(postmagic == SCHED_MAGIC);
|
|
DoActualSwitch(regs);
|
|
assert(premagic == SCHED_MAGIC);
|
|
assert(postmagic == SCHED_MAGIC);
|
|
if ( regs->signal_pending && regs->InUserspace() )
|
|
{
|
|
Interrupt::Enable();
|
|
Signal::DispatchHandler(regs, NULL);
|
|
}
|
|
}
|
|
|
|
const bool DEBUG_BEGINCTXSWITCH = false;
|
|
const bool DEBUG_CTXSWITCH = false;
|
|
const bool DEBUG_ENDCTXSWITCH = false;
|
|
|
|
void LogBeginSwitch(Thread* current, const CPU::InterruptRegisters* regs)
|
|
{
|
|
bool alwaysdebug = false;
|
|
bool isidlethread = current == idlethread;
|
|
bool dodebug = DEBUG_BEGINCTXSWITCH && !isidlethread;
|
|
if ( alwaysdebug || dodebug )
|
|
{
|
|
Log::PrintF("Switching from 0x%p", current);
|
|
regs->LogRegisters();
|
|
Log::Print("\n");
|
|
}
|
|
}
|
|
|
|
void LogSwitch(Thread* current, Thread* next)
|
|
{
|
|
bool alwaysdebug = false;
|
|
bool different = current == idlethread;
|
|
bool dodebug = DEBUG_CTXSWITCH && different;
|
|
if ( alwaysdebug || dodebug )
|
|
{
|
|
Log::PrintF("switching from %ji:%u (0x%p) to %ji:%u (0x%p) \n",
|
|
(intmax_t) current->process->pid, 0, current,
|
|
(intmax_t) next->process->pid, 0, next);
|
|
}
|
|
}
|
|
|
|
void LogEndSwitch(Thread* current, const CPU::InterruptRegisters* regs)
|
|
{
|
|
bool alwaysdebug = false;
|
|
bool isidlethread = current == idlethread;
|
|
bool dodebug = DEBUG_BEGINCTXSWITCH && !isidlethread;
|
|
if ( alwaysdebug || dodebug )
|
|
{
|
|
Log::PrintF("Switched to 0x%p", current);
|
|
regs->LogRegisters();
|
|
Log::Print("\n");
|
|
}
|
|
}
|
|
|
|
void InterruptYieldCPU(CPU::InterruptRegisters* regs, void* /*user*/)
|
|
{
|
|
Switch(regs);
|
|
}
|
|
|
|
void ThreadExitCPU(CPU::InterruptRegisters* regs, void* /*user*/)
|
|
{
|
|
// Can't use floating point instructions from now.
|
|
Float::NofityTaskExit(currentthread);
|
|
SetThreadState(currentthread, ThreadState::DEAD);
|
|
InterruptYieldCPU(regs, NULL);
|
|
}
|
|
|
|
// The idle thread serves no purpose except being an infinite loop that does
|
|
// nothing, which is only run when the system has nothing to do.
|
|
void SetIdleThread(Thread* thread)
|
|
{
|
|
assert(!idlethread);
|
|
idlethread = thread;
|
|
SetThreadState(thread, ThreadState::NONE);
|
|
SetCurrentThread(thread);
|
|
}
|
|
|
|
void SetDummyThreadOwner(Process* process)
|
|
{
|
|
dummythread->process = process;
|
|
}
|
|
|
|
void SetInitProcess(Process* init)
|
|
{
|
|
initprocess = init;
|
|
}
|
|
|
|
Process* GetInitProcess()
|
|
{
|
|
return initprocess;
|
|
}
|
|
|
|
Process* GetKernelProcess()
|
|
{
|
|
return idlethread->process;
|
|
}
|
|
|
|
void SetThreadState(Thread* thread, ThreadState state)
|
|
{
|
|
bool wasenabled = Interrupt::SetEnabled(false);
|
|
|
|
// Remove the thread from the list of runnable threads.
|
|
if ( thread->state == ThreadState::RUNNABLE &&
|
|
state != ThreadState::RUNNABLE )
|
|
{
|
|
if ( thread == firstrunnablethread ) { firstrunnablethread = thread->schedulerlistnext; }
|
|
if ( thread == firstrunnablethread ) { firstrunnablethread = NULL; }
|
|
assert(thread->schedulerlistprev);
|
|
assert(thread->schedulerlistnext);
|
|
thread->schedulerlistprev->schedulerlistnext = thread->schedulerlistnext;
|
|
thread->schedulerlistnext->schedulerlistprev = thread->schedulerlistprev;
|
|
thread->schedulerlistprev = NULL;
|
|
thread->schedulerlistnext = NULL;
|
|
}
|
|
|
|
// Insert the thread into the scheduler's carousel linked list.
|
|
if ( thread->state != ThreadState::RUNNABLE &&
|
|
state == ThreadState::RUNNABLE )
|
|
{
|
|
if ( firstrunnablethread == NULL ) { firstrunnablethread = thread; }
|
|
thread->schedulerlistprev = firstrunnablethread->schedulerlistprev;
|
|
thread->schedulerlistnext = firstrunnablethread;
|
|
firstrunnablethread->schedulerlistprev = thread;
|
|
thread->schedulerlistprev->schedulerlistnext = thread;
|
|
}
|
|
|
|
thread->state = state;
|
|
|
|
assert(thread->state != ThreadState::RUNNABLE || thread->schedulerlistprev);
|
|
assert(thread->state != ThreadState::RUNNABLE || thread->schedulerlistnext);
|
|
|
|
Interrupt::SetEnabled(wasenabled);
|
|
}
|
|
|
|
ThreadState GetThreadState(Thread* thread)
|
|
{
|
|
return thread->state;
|
|
}
|
|
|
|
static void SleepUntil(struct timespec wakeat)
|
|
{
|
|
while ( timespec_lt(Time::Get(CLOCK_BOOT), wakeat) )
|
|
Yield();
|
|
}
|
|
|
|
// TODO: This function has been obsoleted by the clock_nanosleep system call.
|
|
static int sys_sleep(size_t secs)
|
|
{
|
|
struct timespec delay = timespec_make(secs, 0);
|
|
struct timespec now = Time::Get(CLOCK_BOOT);
|
|
SleepUntil(timespec_add(now, delay));
|
|
return 0;
|
|
}
|
|
|
|
// TODO: This function has been obsoleted by the clock_nanosleep system call.
|
|
static int sys_usleep(size_t usecs)
|
|
{
|
|
size_t secs = usecs / 1000000;
|
|
size_t nsecs = (usecs % 1000000) * 1000;
|
|
struct timespec delay = timespec_make(secs, nsecs);
|
|
struct timespec now = Time::Get(CLOCK_BOOT);
|
|
SleepUntil(timespec_add(now, delay));
|
|
return 0;
|
|
}
|
|
|
|
static int sys_sched_yield(void)
|
|
{
|
|
return kthread_yield(), 0;
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
premagic = postmagic = SCHED_MAGIC;
|
|
|
|
// We use a dummy so that the first context switch won't crash when the
|
|
// current thread is accessed. This lets us avoid checking whether it is
|
|
// NULL (which it only will be once), which gives simpler code.
|
|
dummythread = (Thread*) &dummythreaddata;
|
|
memset(dummythread, 0, sizeof(*dummythread));
|
|
dummythread->schedulerlistprev = dummythread;
|
|
dummythread->schedulerlistnext = dummythread;
|
|
currentthread = dummythread;
|
|
firstrunnablethread = NULL;
|
|
firstsleepingthread = NULL;
|
|
idlethread = NULL;
|
|
|
|
Syscall::Register(SYSCALL_SLEEP, (void*) sys_sleep);
|
|
Syscall::Register(SYSCALL_USLEEP, (void*) sys_usleep);
|
|
Syscall::Register(SYSCALL_SCHED_YIELD, (void*) sys_sched_yield);
|
|
}
|
|
|
|
} // namespace Scheduler
|
|
} // namespace Sortix
|