This code uses the cr0 task switched bit to disable the FPU upon task switch, which allows the kernel to delay copying the registers until another task starts using them. Or better yet, if no other thread actually uses the registers, then it won't need to do any copying at all!
298 lines
8.6 KiB
C++
298 lines
8.6 KiB
C++
/*******************************************************************************
|
|
|
|
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012.
|
|
|
|
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 <sortix/kernel/platform.h>
|
|
#include <sortix/kernel/memorymanagement.h>
|
|
#include <libmaxsi/error.h>
|
|
#include <libmaxsi/memory.h>
|
|
#include "x86-family/gdt.h"
|
|
#include "x86-family/float.h"
|
|
#include "syscall.h"
|
|
#include "interrupt.h"
|
|
#include "time.h"
|
|
#include "thread.h"
|
|
#include "process.h"
|
|
#include "signal.h"
|
|
#include "scheduler.h"
|
|
|
|
using namespace Maxsi;
|
|
|
|
namespace Sortix {
|
|
namespace Scheduler {
|
|
|
|
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;
|
|
|
|
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()
|
|
{
|
|
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 %i (0x%p) (backup: %i), had bad "
|
|
"address space variable: 0x%zx: not page-aligned "
|
|
"(backup: 0x%zx)\n", nextthread,
|
|
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);
|
|
|
|
LogEndSwitch(next, regs);
|
|
}
|
|
|
|
void Switch(CPU::InterruptRegisters* regs)
|
|
{
|
|
DoActualSwitch(regs);
|
|
if ( regs->signal_pending && regs->InUserspace() )
|
|
Signal::Dispatch(regs);
|
|
}
|
|
|
|
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 %u:%u (0x%p) to %u:%u (0x%p) \n",
|
|
current->process->pid, 0, current,
|
|
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");
|
|
}
|
|
}
|
|
|
|
static void InterruptYieldCPU(CPU::InterruptRegisters* regs, void* /*user*/)
|
|
{
|
|
Switch(regs);
|
|
}
|
|
|
|
static void ThreadExitCPU(CPU::InterruptRegisters* regs, void* /*user*/)
|
|
{
|
|
Float::NofityTaskExit(); // Can't use floating point instructions from now.
|
|
SetThreadState(currentthread, Thread::State::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, Thread::State::NONE);
|
|
SetCurrentThread(thread);
|
|
}
|
|
|
|
void SetDummyThreadOwner(Process* process)
|
|
{
|
|
dummythread->process = process;
|
|
}
|
|
|
|
void SetInitProcess(Process* init)
|
|
{
|
|
initprocess = init;
|
|
}
|
|
|
|
Process* GetInitProcess()
|
|
{
|
|
return initprocess;
|
|
}
|
|
|
|
void SetThreadState(Thread* thread, Thread::State state)
|
|
{
|
|
bool wasenabled = Interrupt::SetEnabled(false);
|
|
|
|
// Remove the thread from the list of runnable threads.
|
|
if ( thread->state == Thread::State::RUNNABLE &&
|
|
state != Thread::State::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 != Thread::State::RUNNABLE &&
|
|
state == Thread::State::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 != Thread::State::RUNNABLE || thread->schedulerlistprev);
|
|
ASSERT(thread->state != Thread::State::RUNNABLE || thread->schedulerlistnext);
|
|
|
|
Interrupt::SetEnabled(wasenabled);
|
|
}
|
|
|
|
Thread::State GetThreadState(Thread* thread)
|
|
{
|
|
return thread->state;
|
|
}
|
|
|
|
void SysSleep(size_t secs)
|
|
{
|
|
uintmax_t timetosleep = ((uintmax_t) secs) * 1000ULL * 1000ULL;
|
|
uint32_t wakeat = Time::MicrosecondsSinceBoot() + timetosleep;
|
|
do { Yield(); }
|
|
while ( Time::MicrosecondsSinceBoot() < wakeat );
|
|
}
|
|
|
|
void SysUSleep(size_t usecs)
|
|
{
|
|
uintmax_t timetosleep = (uintmax_t) usecs;
|
|
uint32_t wakeat = Time::MicrosecondsSinceBoot() + timetosleep;
|
|
do { Yield(); }
|
|
while ( Time::MicrosecondsSinceBoot() < wakeat );
|
|
}
|
|
|
|
extern "C" void yield_cpu_handler();
|
|
extern "C" void thread_exit_handler();
|
|
|
|
void Init()
|
|
{
|
|
// 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;
|
|
Maxsi::Memory::Set(dummythread, 0, sizeof(*dummythread));
|
|
dummythread->schedulerlistprev = dummythread;
|
|
dummythread->schedulerlistnext = dummythread;
|
|
currentthread = dummythread;
|
|
firstrunnablethread = NULL;
|
|
firstsleepingthread = NULL;
|
|
idlethread = NULL;
|
|
|
|
// Register our raw handler with user-space access. It calls our real
|
|
// handler after common interrupt preparation stuff has occured.
|
|
Interrupt::RegisterRawHandler(129, yield_cpu_handler, true);
|
|
Interrupt::RegisterHandler(129, InterruptYieldCPU, NULL);
|
|
Interrupt::RegisterRawHandler(132, thread_exit_handler, true);
|
|
Interrupt::RegisterHandler(132, ThreadExitCPU, NULL);
|
|
|
|
Syscall::Register(SYSCALL_SLEEP, (void*) SysSleep);
|
|
Syscall::Register(SYSCALL_USLEEP, (void*) SysUSleep);
|
|
}
|
|
|
|
} // namespace Scheduler
|
|
} // namespace Sortix
|