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!
303 lines
7.9 KiB
C++
303 lines
7.9 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/>.
|
|
|
|
thread.cpp
|
|
Describes a thread belonging to a process.
|
|
|
|
*******************************************************************************/
|
|
|
|
#include <sortix/kernel/platform.h>
|
|
#include <sortix/kernel/kthread.h>
|
|
#include <sortix/kernel/memorymanagement.h>
|
|
#include <sortix/mman.h>
|
|
#include <sortix/signal.h>
|
|
#include <libmaxsi/error.h>
|
|
#include <libmaxsi/memory.h>
|
|
#include "process.h"
|
|
#include "thread.h"
|
|
#include "scheduler.h"
|
|
#include "interrupt.h"
|
|
#include "time.h"
|
|
#include "syscall.h"
|
|
|
|
using namespace Maxsi;
|
|
|
|
namespace Sortix
|
|
{
|
|
Thread::Thread()
|
|
{
|
|
id = 0; // TODO: Make a thread id.
|
|
process = NULL;
|
|
prevsibling = NULL;
|
|
nextsibling = NULL;
|
|
schedulerlistprev = NULL;
|
|
schedulerlistnext = NULL;
|
|
state = NONE;
|
|
Maxsi::Memory::Set(®isters, 0, sizeof(registers));
|
|
stackpos = 0;
|
|
stacksize = 0;
|
|
kernelstackpos = 0;
|
|
kernelstacksize = 0;
|
|
kernelstackmalloced = false;
|
|
currentsignal = 0;
|
|
siglevel = 0;
|
|
sighandler = NULL;
|
|
terminated = false;
|
|
fpuinitialized = false;
|
|
// If malloc isn't 16-byte aligned, then we can't rely on offsets in
|
|
// our own class, so we'll just fix ourselves nicely up.
|
|
unsigned long fpuaddr = ((unsigned long) fpuenv+16UL) & ~(16UL-1UL);
|
|
fpuenvaligned = (uint8_t*) fpuaddr;
|
|
}
|
|
|
|
Thread::~Thread()
|
|
{
|
|
if ( process )
|
|
process->OnThreadDestruction(this);
|
|
ASSERT(CurrentThread() != this);
|
|
if ( kernelstackmalloced )
|
|
delete[] (uint8_t*) kernelstackpos;
|
|
terminated = true;
|
|
}
|
|
|
|
addr_t Thread::SwitchAddressSpace(addr_t newaddrspace)
|
|
{
|
|
bool wasenabled = Interrupt::SetEnabled(false);
|
|
addr_t result = addrspace;
|
|
addrspace = newaddrspace;
|
|
Memory::SwitchAddressSpace(newaddrspace);
|
|
Interrupt::SetEnabled(wasenabled);
|
|
return result;
|
|
}
|
|
|
|
// Last chance to clean up user-space things before this thread dies.
|
|
void Thread::LastPrayer()
|
|
{
|
|
Memory::UnmapRange(stackpos, stacksize);
|
|
Memory::Flush();
|
|
}
|
|
|
|
extern "C" void BootstrapKernelThread(void* user, ThreadEntry entry)
|
|
{
|
|
entry(user);
|
|
kthread_exit();
|
|
}
|
|
|
|
Thread* CreateKernelThread(Process* process, CPU::InterruptRegisters* regs)
|
|
{
|
|
ASSERT(process && regs && process->addrspace);
|
|
Thread* thread = new Thread;
|
|
if ( !thread ) { return NULL; }
|
|
|
|
thread->addrspace = process->addrspace;
|
|
thread->SaveRegisters(regs);
|
|
|
|
kthread_mutex_lock(&process->threadlock);
|
|
|
|
// Create the family tree.
|
|
thread->process = process;
|
|
Thread* firsty = process->firstthread;
|
|
if ( firsty ) { firsty->prevsibling = thread; }
|
|
thread->nextsibling = firsty;
|
|
process->firstthread = thread;
|
|
|
|
kthread_mutex_unlock(&process->threadlock);
|
|
|
|
return thread;
|
|
}
|
|
|
|
Thread* CreateKernelThread(Process* process, ThreadEntry entry, void* user,
|
|
size_t stacksize)
|
|
{
|
|
const size_t DEFAULT_KERNEL_STACK_SIZE = 64*8192UL;
|
|
if ( !stacksize ) { stacksize = DEFAULT_KERNEL_STACK_SIZE; }
|
|
uint8_t* stack = new uint8_t[stacksize];
|
|
if ( !stack ) { return NULL; }
|
|
|
|
CPU::InterruptRegisters regs;
|
|
SetupKernelThreadRegs(®s, entry, user, (addr_t) stack, stacksize);
|
|
|
|
Thread* thread = CreateKernelThread(process, ®s);
|
|
if ( !thread ) { delete[] stack; }
|
|
|
|
thread->kernelstackpos = (addr_t) stack;
|
|
thread->kernelstacksize = stacksize;
|
|
thread->kernelstackmalloced = true;
|
|
|
|
return thread;
|
|
}
|
|
|
|
Thread* CreateKernelThread(ThreadEntry entry, void* user, size_t stacksize)
|
|
{
|
|
return CreateKernelThread(CurrentProcess(), entry, user, stacksize);
|
|
}
|
|
|
|
void StartKernelThread(Thread* thread)
|
|
{
|
|
Scheduler::SetThreadState(thread, Thread::State::RUNNABLE);
|
|
}
|
|
|
|
Thread* RunKernelThread(Process* process, CPU::InterruptRegisters* regs)
|
|
{
|
|
Thread* thread = CreateKernelThread(process, regs);
|
|
if ( !thread ) { return NULL; }
|
|
StartKernelThread(thread);
|
|
return thread;
|
|
}
|
|
|
|
Thread* RunKernelThread(Process* process, ThreadEntry entry, void* user,
|
|
size_t stacksize)
|
|
{
|
|
Thread* thread = CreateKernelThread(process, entry, user, stacksize);
|
|
if ( !thread ) { return NULL; }
|
|
StartKernelThread(thread);
|
|
return thread;
|
|
}
|
|
|
|
Thread* RunKernelThread(ThreadEntry entry, void* user, size_t stacksize)
|
|
{
|
|
Thread* thread = CreateKernelThread(entry, user, stacksize);
|
|
if ( !thread ) { return NULL; }
|
|
StartKernelThread(thread);
|
|
return thread;
|
|
}
|
|
|
|
void Thread::HandleSignal(CPU::InterruptRegisters* regs)
|
|
{
|
|
int signum = signalqueue.Pop(currentsignal);
|
|
regs->signal_pending = 0;
|
|
|
|
if ( !signum )
|
|
return;
|
|
|
|
if ( !sighandler )
|
|
return;
|
|
|
|
if ( SIG_NUM_LEVELS <= siglevel )
|
|
return;
|
|
|
|
// Signals can't return to kernel mode because the kernel stack may have
|
|
// been overwritten by a system call during the signal handler. Correct
|
|
// the return state so it returns to userspace and not the kernel.
|
|
if ( !regs->InUserspace() )
|
|
HandleSignalFixupRegsCPU(regs);
|
|
|
|
if ( signum == SIGKILL )
|
|
{
|
|
// We need to run the OnSigKill method here with interrupts enabled
|
|
// and on our own stack. But this method this may have been called
|
|
// from the scheduler on any stack, so we need to do a little
|
|
// bootstrap and switch to our own stack.
|
|
GotoOnSigKill(regs);
|
|
return;
|
|
}
|
|
|
|
int level = siglevel++;
|
|
signums[level] = currentsignal = signum;
|
|
Maxsi::Memory::Copy(sigregs + level, regs, sizeof(*regs));
|
|
|
|
HandleSignalCPU(regs);
|
|
}
|
|
|
|
void Thread::HandleSigreturn(CPU::InterruptRegisters* regs)
|
|
{
|
|
if ( !siglevel )
|
|
return;
|
|
|
|
siglevel--;
|
|
|
|
currentsignal = siglevel ? signums[siglevel-1] : 0;
|
|
Maxsi::Memory::Copy(regs, sigregs + siglevel, sizeof(*regs));
|
|
regs->signal_pending = 0;
|
|
|
|
// Check if a more important signal is pending.
|
|
HandleSignal(regs);
|
|
}
|
|
|
|
extern "C" void Thread__OnSigKill(Thread* thread)
|
|
{
|
|
thread->OnSigKill();
|
|
}
|
|
|
|
void Thread::OnSigKill()
|
|
{
|
|
LastPrayer();
|
|
kthread_exit();
|
|
}
|
|
|
|
void SysRegisterSignalHandler(sighandler_t sighandler)
|
|
{
|
|
CurrentThread()->sighandler = sighandler;
|
|
}
|
|
|
|
void Thread::SetHavePendingSignals()
|
|
{
|
|
// TODO: This doesn't really work if Interrupt::IsCPUInterrupted()!
|
|
if ( CurrentThread() == this )
|
|
asm_signal_is_pending = 1;
|
|
else
|
|
registers.signal_pending = 1;
|
|
}
|
|
|
|
bool Thread::DeliverSignal(int signum)
|
|
{
|
|
if ( signum <= 0 || 128 <= signum ) { Error::Set(EINVAL); return false; }
|
|
|
|
bool wasenabled = Interrupt::SetEnabled(false);
|
|
signalqueue.Push(signum);
|
|
SetHavePendingSignals();
|
|
Interrupt::SetEnabled(wasenabled);
|
|
|
|
return true;
|
|
}
|
|
|
|
int SysKill(pid_t pid, int signum)
|
|
{
|
|
// Protect the system idle process.
|
|
if ( !pid ) { Error::Set(EPERM); return -1; }
|
|
|
|
// If we kill our own process, deliver the signal to this thread.
|
|
Thread* currentthread = CurrentThread();
|
|
if ( currentthread->process->pid == pid )
|
|
return currentthread->DeliverSignal(signum) ? 0 : -1;
|
|
|
|
// TODO: Race condition: The process could be deleted while we use it.
|
|
Process* process = Process::Get(pid);
|
|
if ( !process ) { Error::Set(ESRCH); return -1; }
|
|
|
|
// TODO: Protect init?
|
|
// TODO: Check for permission.
|
|
// TODO: Check for zombies.
|
|
|
|
return process->DeliverSignal(signum) ? 0 : -1;
|
|
}
|
|
|
|
int SysRaise(int signum)
|
|
{
|
|
return CurrentThread()->DeliverSignal(signum) ? 0 : -1;
|
|
}
|
|
|
|
void Thread::Init()
|
|
{
|
|
Syscall::Register(SYSCALL_KILL, (void*) SysKill);
|
|
Syscall::Register(SYSCALL_RAISE, (void*) SysRaise);
|
|
Syscall::Register(SYSCALL_REGISTER_SIGNAL_HANDLER, (void*) SysRegisterSignalHandler);
|
|
}
|
|
}
|