sortix-mirror/sortix/scheduler.cpp

379 lines
10 KiB
C++

/******************************************************************************
COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011.
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
Handles context switching between tasks and deciding when to execute what.
******************************************************************************/
#include <sortix/kernel/platform.h>
#include <libmaxsi/memory.h>
#include <sortix/kernel/panic.h>
#include "thread.h"
#include "process.h"
#include "time.h"
#include "scheduler.h"
#include <sortix/kernel/memorymanagement.h>
#include "syscall.h"
#include "sound.h" // HACK FOR SIGINT
#include "x86-family/gdt.h"
namespace Sortix
{
void SysExit(int status); // HACK FOR SIGINT
// Internal forward-declarations.
namespace Scheduler
{
Thread* PopNextThread();
void WakeSleeping();
void LogBeginContextSwitch(Thread* current, const CPU::InterruptRegisters* state);
void LogContextSwitch(Thread* current, Thread* next);
void LogEndContextSwitch(Thread* current, const CPU::InterruptRegisters* state);
void SysSleep(size_t secs);
void SysUSleep(size_t usecs);
void HandleSigIntHack(CPU::InterruptRegisters* regs);
}
namespace Scheduler
{
byte dummythreaddata[sizeof(Thread)];
Thread* dummythread;
Thread* currentthread;
Thread* idlethread;
Thread* firstrunnablethread;
Thread* firstsleepingthread;
Process* initprocess;
bool hacksigintpending = false;
void Init()
{
// We use a dummy so that the first context switch won't crash when
// currentthread is accessed. This lets us avoid checking whether
// currentthread is NULL (which it only will be once) which gives
// simpler code.
dummythread = (Thread*) &dummythreaddata;
Maxsi::Memory::Set(dummythread, 0, sizeof(*dummythread));
currentthread = dummythread;
firstrunnablethread = NULL;
firstsleepingthread = NULL;
idlethread = NULL;
hacksigintpending = false;
Syscall::Register(SYSCALL_SLEEP, (void*) SysSleep);
Syscall::Register(SYSCALL_USLEEP, (void*) SysUSleep);
addr_t stackstart = Memory::GetKernelStack();
size_t stacksize = Memory::GetKernelStackSize();
addr_t stackend = stackstart - stacksize;
if ( !Memory::MapRangeKernel(stackend, stacksize) )
{
PanicF("could not create kernel stack (%zx to %zx)",
stackend, stackstart);
}
GDT::SetKernelStack((size_t*) stackstart);
}
// The no operating thread is a thread stuck in an infinite loop that
// executes absolutely nothing, which is only run when the system has
// nothing to do.
void SetIdleThread(Thread* thread)
{
ASSERT(idlethread == NULL);
idlethread = thread;
SetThreadState(thread, Thread::State::NONE);
}
void SetDummyThreadOwner(Process* process)
{
dummythread->process = process;
}
void SetInitProcess(Process* init)
{
initprocess = init;
}
Process* GetInitProcess()
{
return initprocess;
}
void MainLoop()
{
// Wait for the first hardware interrupt to trigger a context switch
// into the first task! Then the init process should gracefully
// start executing.
while(true);
}
void Switch(CPU::InterruptRegisters* regs)
{
LogBeginContextSwitch(currentthread, regs);
if ( hacksigintpending ) { HandleSigIntHack(regs); }
WakeSleeping();
Thread* nextthread = PopNextThread();
if ( !nextthread ) { Panic("had no thread to switch to"); }
if ( nextthread->terminated ) { PanicF("Running a terminated thread 0x%p", nextthread); }
LogContextSwitch(currentthread, nextthread);
if ( nextthread == currentthread ) { return; }
currentthread->SaveRegisters(regs);
nextthread->LoadRegisters(regs);
addr_t newaddrspace = nextthread->process->addrspace;
if ( unlikely(newaddrspace != Page::AlignDown(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,
nextthread->pidbackup, newaddrspace,
nextthread->addrspacebackup);
}
Memory::SwitchAddressSpace(newaddrspace);
currentthread = nextthread;
nextthread->HandleSignal(regs);
LogEndContextSwitch(currentthread, regs);
if ( currentthread->scfunc ) { Syscall::Resume(regs); }
}
void ProcessTerminated(CPU::InterruptRegisters* regs)
{
currentthread = dummythread;
Switch(regs);
}
const bool DEBUG_BEGINCTXSWITCH = false;
const bool DEBUG_CTXSWITCH = false;
const bool DEBUG_ENDCTXSWITCH = false;
void LogBeginContextSwitch(Thread* current, const CPU::InterruptRegisters* state)
{
if ( DEBUG_BEGINCTXSWITCH && current->process->pid != 0 )
{
Log::PrintF("Switching from 0x%p", current);
state->LogRegisters();
Log::Print("\n");
}
}
void LogContextSwitch(Thread* current, Thread* next)
{
if ( DEBUG_CTXSWITCH && current != next )
{
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 LogEndContextSwitch(Thread* current, const CPU::InterruptRegisters* state)
{
if ( DEBUG_ENDCTXSWITCH && current->process->pid != 0 )
{
Log::PrintF("Switched to 0x%p", current);
state->LogRegisters();
Log::Print("\n");
}
}
Thread* PopNextThread()
{
if ( !firstrunnablethread ) { return idlethread; }
Thread* result = firstrunnablethread;
firstrunnablethread = firstrunnablethread->schedulerlistnext;
return result;
}
void SetThreadState(Thread* thread, Thread::State state)
{
if ( thread->state == state ) { return; }
if ( thread->state == Thread::State::RUNNABLE )
{
if ( thread == firstrunnablethread ) { firstrunnablethread = thread->schedulerlistnext; }
if ( thread == firstrunnablethread ) { firstrunnablethread = NULL; }
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 ( 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;
}
Thread::State GetThreadState(Thread* thread)
{
return thread->state;
}
void PutThreadToSleep(Thread* thread, uintmax_t usecs)
{
SetThreadState(thread, Thread::State::BLOCKING);
thread->sleepuntil = Time::MicrosecondsSinceBoot() + usecs;
// We use a simple linked linked list sorted after wake-up time to
// keep track of the threads that are sleeping.
if ( firstsleepingthread == NULL )
{
thread->nextsleepingthread = NULL;
firstsleepingthread = thread;
return;
}
if ( thread->sleepuntil < firstsleepingthread->sleepuntil )
{
thread->nextsleepingthread = firstsleepingthread;
firstsleepingthread = thread;
return;
}
for ( Thread* tmp = firstsleepingthread; tmp != NULL; tmp = tmp->nextsleepingthread )
{
if ( tmp->nextsleepingthread == NULL ||
thread->sleepuntil < tmp->nextsleepingthread->sleepuntil )
{
thread->nextsleepingthread = tmp->nextsleepingthread;
tmp->nextsleepingthread = thread;
return;
}
}
}
void EarlyWakeUp(Thread* thread)
{
uintmax_t now = Time::MicrosecondsSinceBoot();
if ( thread->sleepuntil < now ) { return; }
thread->sleepuntil = now;
SetThreadState(thread, Thread::State::RUNNABLE);
if ( firstsleepingthread == thread )
{
firstsleepingthread = thread->nextsleepingthread;
thread->nextsleepingthread = NULL;
return;
}
for ( Thread* tmp = firstsleepingthread; tmp->nextsleepingthread != NULL; tmp = tmp->nextsleepingthread )
{
if ( tmp->nextsleepingthread == thread )
{
tmp->nextsleepingthread = thread->nextsleepingthread;
thread->nextsleepingthread = NULL;
return;
}
}
}
void WakeSleeping()
{
uintmax_t now = Time::MicrosecondsSinceBoot();
while ( firstsleepingthread && firstsleepingthread->sleepuntil < now )
{
SetThreadState(firstsleepingthread, Thread::State::RUNNABLE);
Thread* next = firstsleepingthread->nextsleepingthread;
firstsleepingthread->nextsleepingthread = NULL;
firstsleepingthread = next;
}
}
void HandleSigIntHack(CPU::InterruptRegisters* regs)
{
if ( currentthread == idlethread ) { return; }
hacksigintpending = false;
// HACK: Don't crash init or sh.
Process* process = CurrentProcess();
if ( process->pid < 3 ) { return; }
Sound::Mute();
Log::PrintF("^C\n");
process->Exit(130);
currentthread = dummythread;
}
void SigIntHack()
{
hacksigintpending = true;
}
void SysSleep(size_t secs)
{
Thread* thread = currentthread;
uintmax_t timetosleep = ((uintmax_t) secs) * 1000ULL * 1000ULL;
if ( timetosleep == 0 )
{
Switch(Syscall::InterruptRegs());
Syscall::AsIs();
return;
}
PutThreadToSleep(thread, timetosleep);
Syscall::Incomplete();
}
void SysUSleep(size_t usecs)
{
Thread* thread = currentthread;
uintmax_t timetosleep = usecs;
if ( timetosleep == 0 )
{
Switch(Syscall::InterruptRegs());
Syscall::AsIs();
return;
}
PutThreadToSleep(thread, timetosleep);
Syscall::Incomplete();
}
}
Thread* CurrentThread()
{
return Scheduler::currentthread;
}
Process* CurrentProcess()
{
return Scheduler::currentthread->process;
}
}