Merge remote-tracking branch 'Kernel/master'

This commit is contained in:
EnderIce2
2024-11-20 05:00:33 +02:00
468 changed files with 112800 additions and 1 deletions

332
Kernel/tasking/process.cpp Normal file
View File

@ -0,0 +1,332 @@
/*
This file is part of Fennix Kernel.
Fennix Kernel 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.
Fennix Kernel 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 Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/
#include <task.hpp>
#include <dumper.hpp>
#include <signal.hpp>
#include <convert.h>
#include <lock.hpp>
#include <printf.h>
#include <smp.hpp>
#include <io.h>
#include "../kernel.h"
#if defined(a64)
#include "../arch/amd64/cpu/apic.hpp"
#include "../arch/amd64/cpu/gdt.hpp"
#elif defined(a32)
#include "../arch/i386/cpu/apic.hpp"
#elif defined(aa64)
#endif
// #define DEBUG_TASKING 1
#ifdef DEBUG_TASKING
#define tskdbg(m, ...) \
debug(m, ##__VA_ARGS__); \
__sync
#else
#define tskdbg(m, ...)
#endif
using namespace vfs;
namespace Tasking
{
TCB *PCB::GetThread(TID ID)
{
auto it = std::find_if(this->Threads.begin(), this->Threads.end(),
[ID](TCB *t)
{ return t->ID == ID; });
return it != this->Threads.end() ? *it : nullptr;
}
int PCB::SendSignal(int sig)
{
return this->Signals.SendSignal((enum Signals)sig);
}
void PCB::SetState(TaskState state)
{
this->State.store(state);
if (this->Threads.size() == 1)
this->Threads.front()->State.store(state);
}
void PCB::SetExitCode(int code)
{
this->ExitCode.store(code);
if (this->Threads.size() == 1)
this->Threads.front()->ExitCode.store(code);
}
void PCB::Rename(const char *name)
{
assert(name != nullptr);
assert(strlen(name) > 0);
trace("Renaming thread %s to %s",
this->Name, name);
if (this->Name)
{
this->AllocatedMemory -= strlen(this->Name) + 1;
delete[] this->Name;
}
this->Name = new char[strlen(name) + 1];
this->AllocatedMemory += strlen(name) + 1;
strcpy((char *)this->Name, name);
}
void PCB::SetWorkingDirectory(FileNode *node)
{
trace("Setting working directory of process %s to %#lx (%s)",
this->Name, node, node->Name.c_str());
CWD = node;
FileNode *cwd = fs->GetByPath("cwd", ProcDirectory);
if (cwd)
fs->Remove(cwd);
cwd = fs->CreateLink("cwd", ProcDirectory, node);
if (cwd == nullptr)
error("Failed to create cwd link");
}
void PCB::SetExe(const char *path)
{
trace("Setting exe %s to %s",
this->Name, path);
Executable = fs->GetByPath(path, ProcDirectory);
FileNode *exe = fs->GetByPath("exe", ProcDirectory);
if (exe)
fs->Remove(exe);
exe = fs->CreateLink("exe", ProcDirectory, path);
if (exe == nullptr)
error("Failed to create exe link");
}
size_t PCB::GetSize()
{
size_t ret = this->AllocatedMemory;
ret += this->vma->GetAllocatedMemorySize();
for (size_t i = 0; i < this->Threads.size(); i++)
ret += sizeof(TCB);
for (size_t i = 0; i < this->Children.size(); i++)
ret += sizeof(PCB);
return ret;
}
PCB::PCB(Task *ctx, PCB *Parent, const char *Name,
TaskExecutionMode ExecutionMode,
bool UseKernelPageTable,
uint16_t UserID, uint16_t GroupID)
: Signals(this)
{
debug("+ %#lx", this);
assert(ctx != nullptr);
assert(Name != nullptr);
assert(strlen(Name) > 0);
assert(ExecutionMode >= _ExecuteModeMin);
assert(ExecutionMode <= _ExecuteModeMax);
FileNode *procDir = fs->GetByPath("/proc", nullptr);
assert(procDir != nullptr);
/* d r-x r-x r-x */
mode_t mode = S_IROTH | S_IXOTH |
S_IRGRP | S_IXGRP |
S_IRUSR | S_IXUSR |
S_IFDIR;
ProcDirectory = fs->Create(procDir, std::to_string(ctx->NextPID).c_str(), mode);
assert(ProcDirectory != nullptr);
this->ctx = ctx;
this->ID = ctx->NextPID++;
if (this->Name) /* Prevent memory leak */
delete[] this->Name;
this->Name = new char[strlen(Name) + 1];
strcpy((char *)this->Name, Name);
this->ExitCode = KILL_CRASH;
/* Check parent */
if (Parent == nullptr)
this->Parent = ctx->GetCurrentProcess();
else
this->Parent = Parent;
/* Set uid & gid */
if (this->Parent &&
UserID == UINT16_MAX &&
GroupID == UINT16_MAX)
{
UserID = this->Parent->Security.Real.UserID;
GroupID = this->Parent->Security.Real.GroupID;
debug("Inherited uid & gid from parent process %s(%d) with uid %d and gid %d",
this->Parent->Name, this->Parent->ID, UserID, GroupID);
}
this->Security.Real.UserID = UserID;
this->Security.Real.GroupID = GroupID;
this->Security.Effective.UserID = UserID;
this->Security.Effective.GroupID = GroupID;
this->Security.ExecutionMode = ExecutionMode;
switch (ExecutionMode)
{
case TaskExecutionMode::System:
fixme("Mode not supported.");
[[fallthrough]];
case TaskExecutionMode::Kernel:
{
this->Security.IsCritical = true;
break;
}
case TaskExecutionMode::User:
{
break;
}
default:
assert(false);
}
this->FileDescriptors = new FileDescriptorTable(this);
this->tty = nullptr;
/* If create page table */
if (UseKernelPageTable == false)
{
OwnPageTable = true;
this->PageTable = KernelPageTable->Fork();
debug("Process %s(%d) has page table at %#lx",
this->Name, this->ID, this->PageTable);
}
else
this->PageTable = KernelPageTable;
this->vma = new Memory::VirtualMemoryArea(this->PageTable);
this->ProgramBreak = new Memory::ProgramBreak(this->PageTable, this->vma);
debug("Process page table: %#lx", this->PageTable);
debug("Created %s process \"%s\"(%d). Parent \"%s\"(%d)",
ExecutionMode == TaskExecutionMode::User ? "user" : "kernel",
this->Name, this->ID,
Parent ? this->Parent->Name : "None",
Parent ? this->Parent->ID : 0);
this->AllocatedMemory += strlen(Name) + 1;
this->AllocatedMemory += sizeof(PCB);
this->AllocatedMemory += sizeof(FileDescriptorTable);
this->AllocatedMemory += FROM_PAGES(TO_PAGES(sizeof(Memory::PageTable) + 1));
this->AllocatedMemory += sizeof(Memory::VirtualMemoryArea);
this->AllocatedMemory += sizeof(Memory::ProgramBreak);
this->AllocatedMemory += sizeof(SymbolResolver::Symbols);
this->Info.SpawnTime = TimeManager->GetCounter();
if (Parent)
Parent->Children.push_back(this);
ctx->PushProcess(this);
}
PCB::~PCB()
{
debug("- %#lx", this);
debug("Destroying process \"%s\"(%d)",
this->Name, this->ID);
debug("Removing from process list");
/* Remove us from the process list so we
don't get scheduled anymore */
ctx->PopProcess(this);
debug("Freeing allocated memory");
delete this->ProgramBreak;
delete this->vma;
debug("Closing file descriptors");
delete this->FileDescriptors;
debug("Deleting tty");
// if (this->tty)
// delete ((TTY::TeletypeDriver *)this->tty);
fixme("remove workarounds for stdio and tty");
/* FIXME: DON'T DELETE THE TTY
spawn.cpp is using this as workaround
tty == KernelConsole::CurrentTerminal.load();
*/
/* If we own the pointer to the
PageTable, we need to free it */
if (this->PageTable && OwnPageTable)
{
debug("Freeing page table");
size_t PTPgs = TO_PAGES(sizeof(Memory::PageTable) + 1);
KernelAllocator.FreePages(this->PageTable, PTPgs);
}
/* Exit all children processes */
foreach (auto pcb in this->Children)
{
if (pcb == nullptr)
{
warn("Process is null? Kernel bug");
continue;
}
debug("Destroying child process \"%s\"(%d)",
pcb->Name, pcb->ID);
delete pcb;
}
/* Exit all threads */
foreach (auto tcb in this->Threads)
{
if (tcb == nullptr)
{
warn("Thread is null? Kernel bug");
continue;
}
debug("Destroying thread \"%s\"(%d)",
tcb->Name, tcb->ID);
delete tcb;
}
/* Free Name */
delete[] this->Name;
debug("Removing from parent process");
if (likely(this->Parent))
{
this->Parent->Children.erase(std::find(this->Parent->Children.begin(),
this->Parent->Children.end(), this));
}
debug("Process \"%s\"(%d) destroyed",
this->Name, this->ID);
}
}

View File

@ -0,0 +1,792 @@
/*
This file is part of Fennix Kernel.
Fennix Kernel 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.
Fennix Kernel 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 Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/
#include <scheduler.hpp>
#include <dumper.hpp>
#include <convert.h>
#include <lock.hpp>
#include <printf.h>
#include <smp.hpp>
#include <io.h>
#include "../kernel.h"
#if defined(a64)
#include "../arch/amd64/cpu/apic.hpp"
#include "../arch/amd64/cpu/gdt.hpp"
#elif defined(a32)
#include "../arch/i386/cpu/apic.hpp"
#include "../arch/i386/cpu/gdt.hpp"
#elif defined(aa64)
#endif
// #define DEBUG_SCHEDULER 1
// #define DEBUG_GET_NEXT_AVAILABLE_PROCESS 1
// #define DEBUG_GET_NEXT_AVAILABLE_THREAD 1
// #define DEBUG_FIND_NEW_PROCESS 1
// #define DEBUG_SCHEDULER_SEARCH_PROCESS_THREAD 1
// #define DEBUG_WAKE_UP_THREADS 1
/* Global */
#ifdef DEBUG_SCHEDULER
#define DEBUG_GET_NEXT_AVAILABLE_PROCESS 1
#define DEBUG_GET_NEXT_AVAILABLE_THREAD 1
#define DEBUG_FIND_NEW_PROCESS 1
#define DEBUG_SCHEDULER_SEARCH_PROCESS_THREAD 1
#define DEBUG_WAKE_UP_THREADS 1
#define schedbg(m, ...) \
debug(m, ##__VA_ARGS__); \
__sync
#else
#define schedbg(m, ...)
#endif
/* GetNextAvailableThread */
#ifdef DEBUG_GET_NEXT_AVAILABLE_PROCESS
#define gnap_schedbg(m, ...) \
debug(m, ##__VA_ARGS__); \
__sync
#else
#define gnap_schedbg(m, ...)
#endif
/* GetNextAvailableProcess */
#ifdef DEBUG_GET_NEXT_AVAILABLE_THREAD
#define gnat_schedbg(m, ...) \
debug(m, ##__VA_ARGS__); \
__sync
#else
#define gnat_schedbg(m, ...)
#endif
/* FindNewProcess */
#ifdef DEBUG_FIND_NEW_PROCESS
#define fnp_schedbg(m, ...) \
debug(m, ##__VA_ARGS__); \
__sync
#else
#define fnp_schedbg(m, ...)
#endif
/* SchedulerSearchProcessThread */
#ifdef DEBUG_SCHEDULER_SEARCH_PROCESS_THREAD
#define sspt_schedbg(m, ...) \
debug(m, ##__VA_ARGS__); \
__sync
#else
#define sspt_schedbg(m, ...)
#endif
/* WakeUpThreads */
#ifdef DEBUG_WAKE_UP_THREADS
#define wut_schedbg(m, ...) \
debug(m, ##__VA_ARGS__); \
__sync
#else
#define wut_schedbg(m, ...)
#endif
__naked __used nsa void __custom_sched_idle_loop()
{
#if defined(a86)
asmv("IdleLoop:");
asmv("hlt");
asmv("jmp IdleLoop");
#elif defined(aa64)
asmv("IdleLoop:");
asmv("wfe");
asmv("b IdleLoop");
#endif
}
namespace Tasking::Scheduler
{
bool Custom::RemoveThread(TCB *Thread)
{
debug("Thread \"%s\"(%d) removed from process \"%s\"(%d)",
Thread->Name, Thread->ID, Thread->Parent->Name,
Thread->Parent->ID);
delete Thread;
return true;
}
bool Custom::RemoveProcess(PCB *Process)
{
if (Process->State == Terminated)
{
delete Process;
return true;
}
foreach (TCB *Thread in Process->Threads)
{
if (Thread->State == Terminated)
RemoveThread(Thread);
}
return true;
}
PCB *Custom::GetProcessByID(TID ID)
{
foreach (auto p in ProcessList)
{
if (p->ID == ID)
return p;
}
return nullptr;
}
TCB *Custom::GetThreadByID(TID ID, PCB *Parent)
{
if (unlikely(Parent == nullptr))
return nullptr;
foreach (auto t in Parent->Threads)
{
if (t->ID == ID)
return t;
}
return nullptr;
}
void Custom::StartIdleProcess()
{
IdleProcess = ctx->CreateProcess(nullptr, (char *)"Idle",
TaskExecutionMode::Kernel, true);
for (int i = 0; i < SMP::CPUCores; i++)
{
TCB *thd = ctx->CreateThread(IdleProcess, IP(__custom_sched_idle_loop));
char IdleName[16];
sprintf(IdleName, "Idle Thread %d", i);
thd->Rename(IdleName);
thd->SetPriority(Idle);
for (int j = 0; j < MAX_CPU; j++)
thd->Info.Affinity[j] = false;
thd->Info.Affinity[i] = true;
if (unlikely(i == 0))
IdleThread = thd;
}
}
std::vector<PCB *> &Custom::GetProcessList()
{
return ProcessList;
}
void Custom::StartScheduler()
{
#if defined(a86)
if (Interrupts::apicTimer[0])
{
((APIC::Timer *)Interrupts::apicTimer[0])->OneShot(CPU::x86::IRQ16, 100);
/* FIXME: The kernel is not ready for multi-core tasking. */
return;
APIC::InterruptCommandRegister icr{};
bool x2APIC = ((APIC::APIC *)Interrupts::apic[0])->x2APIC;
if (likely(x2APIC))
{
icr.x2.VEC = s_cst(uint8_t, CPU::x86::IRQ16);
icr.x2.MT = APIC::Fixed;
icr.x2.L = APIC::Assert;
icr.x2.DES = 0xFFFFFFFF; /* Broadcast IPI to all local APICs. */
((APIC::APIC *)Interrupts::apic[0])->ICR(icr);
}
else
{
icr.VEC = s_cst(uint8_t, CPU::x86::IRQ16);
icr.MT = APIC::Fixed;
icr.L = APIC::Assert;
for (int i = 0; i < SMP::CPUCores; i++)
{
icr.DES = uint8_t(i);
((APIC::APIC *)Interrupts::apic[i])->ICR(icr);
}
}
}
#endif
}
void Custom::Yield()
{
/* This will trigger the IRQ16
instantly so we won't execute
the next instruction */
#if defined(a86)
asmv("int $0x30");
#elif defined(aa64)
asmv("svc #0x30");
#endif
}
void Custom::PushProcess(PCB *pcb)
{
this->ProcessList.push_back(pcb);
}
void Custom::PopProcess(PCB *pcb)
{
auto it = std::find(this->ProcessList.begin(),
this->ProcessList.end(), pcb);
if (it == this->ProcessList.end())
{
debug("Process %d not found in the list", pcb->ID);
return;
}
this->ProcessList.erase(it);
}
std::pair<PCB *, TCB *> Custom::GetIdle()
{
return std::make_pair(IdleProcess, IdleThread);
}
/* --------------------------------------------------------------- */
nsa void Custom::OneShot(int TimeSlice)
{
if (TimeSlice == 0)
TimeSlice = Tasking::TaskPriority::Normal;
#ifdef DEBUG
if (DebuggerIsAttached)
TimeSlice += 10;
#endif
#if defined(a86)
((APIC::Timer *)Interrupts::apicTimer[GetCurrentCPU()->ID])->OneShot(CPU::x86::IRQ16, TimeSlice);
#elif defined(aa64)
#endif
}
nsa void Custom::UpdateUsage(TaskInfo *Info, TaskExecutionMode Mode, int Core)
{
UNUSED(Core);
uint64_t CurrentTime = TimeManager->GetCounter();
uint64_t TimePassed = Info->LastUpdateTime - CurrentTime;
Info->LastUpdateTime = CurrentTime;
if (Mode == TaskExecutionMode::User)
Info->UserTime += TimePassed;
else
Info->KernelTime += TimePassed;
}
nsa NIF bool Custom::FindNewProcess(void *CPUDataPointer)
{
CPUData *CurrentCPU = (CPUData *)CPUDataPointer;
fnp_schedbg("%d processes", ProcessList.size());
#ifdef DEBUG_FIND_NEW_PROCESS
foreach (auto process in ProcessList)
fnp_schedbg("Process %d %s", process->ID,
process->Name);
#endif
foreach (auto process in ProcessList)
{
switch (process->State.load())
{
case TaskState::Ready:
fnp_schedbg("Ready process (%s)%d",
process->Name, process->ID);
break;
default:
fnp_schedbg("Process \"%s\"(%d) status %d",
process->Name, process->ID,
process->State);
/* We don't actually remove the process. RemoveProcess
firstly checks if it's terminated, if not, it will
loop through Threads and call RemoveThread on
terminated threads. */
RemoveProcess(process);
continue;
}
foreach (auto thread in process->Threads)
{
if (thread->State.load() != TaskState::Ready)
continue;
if (thread->Info.Affinity[CurrentCPU->ID] == false)
continue;
CurrentCPU->CurrentProcess = process;
CurrentCPU->CurrentThread = thread;
return true;
}
}
fnp_schedbg("No process to run.");
return false;
}
nsa NIF bool Custom::GetNextAvailableThread(void *CPUDataPointer)
{
CPUData *CurrentCPU = (CPUData *)CPUDataPointer;
size_t ThreadsSize = CurrentCPU->CurrentProcess->Threads.size();
for (size_t i = 0; i < ThreadsSize; i++)
{
if (CurrentCPU->CurrentProcess->Threads[i] == CurrentCPU->CurrentThread.load())
{
size_t TempIndex = i;
RetryAnotherThread:
if (TempIndex + 1 >= ThreadsSize)
break;
TCB *nextThread = CurrentCPU->CurrentProcess->Threads[TempIndex + 1];
gnat_schedbg("\"%s\"(%d) and next thread is \"%s\"(%d)",
CurrentCPU->CurrentProcess->Threads[i]->Name,
CurrentCPU->CurrentProcess->Threads[i]->ID,
nextThread->Name, nextThread->ID);
if (nextThread->State.load() != TaskState::Ready)
{
gnat_schedbg("Thread %d is not ready", nextThread->ID);
TempIndex++;
goto RetryAnotherThread;
}
if (nextThread->Info.Affinity[CurrentCPU->ID] == false)
continue;
CurrentCPU->CurrentThread = nextThread;
gnat_schedbg("[thd 0 -> end] Scheduling thread %d parent of %s->%d Procs %d",
nextThread->ID, nextThread->Parent->Name,
ThreadsSize, ProcessList.size());
return true;
}
#ifdef DEBUG
else
{
gnat_schedbg("Thread %d is not the current one",
CurrentCPU->CurrentProcess->Threads[i]->ID);
}
#endif
}
return false;
}
nsa NIF bool Custom::GetNextAvailableProcess(void *CPUDataPointer)
{
CPUData *CurrentCPU = (CPUData *)CPUDataPointer;
bool Skip = true;
foreach (auto process in ProcessList)
{
if (process == CurrentCPU->CurrentProcess.load())
{
Skip = false;
gnap_schedbg("Found current process %#lx", process);
continue;
}
if (Skip)
{
gnap_schedbg("Skipping process %#lx", process);
continue;
}
if (process->State.load() != TaskState::Ready)
{
gnap_schedbg("Process %d is not ready", process->ID);
continue;
}
foreach (auto thread in process->Threads)
{
if (thread->State.load() != TaskState::Ready)
{
gnap_schedbg("Thread %d is not ready", thread->ID);
continue;
}
if (thread->Info.Affinity[CurrentCPU->ID] == false)
continue;
CurrentCPU->CurrentProcess = process;
CurrentCPU->CurrentThread = thread;
gnap_schedbg("[cur proc+1 -> first thd] Scheduling thread %d %s->%d (Total Procs %d)",
thread->ID, thread->Name, process->Threads.size(), ProcessList.size());
return true;
}
}
gnap_schedbg("No process to run.");
return false;
}
nsa NIF bool Custom::SchedulerSearchProcessThread(void *CPUDataPointer)
{
CPUData *CurrentCPU = (CPUData *)CPUDataPointer;
foreach (auto process in ProcessList)
{
if (process->State.load() != TaskState::Ready)
{
sspt_schedbg("Process %d is not ready", process->ID);
continue;
}
foreach (auto thread in process->Threads)
{
if (thread->State.load() != TaskState::Ready)
{
sspt_schedbg("Thread %d is not ready", thread->ID);
continue;
}
if (thread->Info.Affinity[CurrentCPU->ID] == false)
continue;
CurrentCPU->CurrentProcess = process;
CurrentCPU->CurrentThread = thread;
sspt_schedbg("[proc 0 -> end -> first thd] Scheduling thread %d parent of %s->%d (Procs %d)",
thread->ID, thread->Parent->Name, process->Threads.size(), ProcessList.size());
return true;
}
}
return false;
}
nsa NIF void Custom::UpdateProcessState()
{
foreach (auto process in ProcessList)
{
if (process->State.load() == TaskState::Terminated)
continue;
if (process->Threads.size() == 1)
{
process->State.exchange(process->Threads.front()->State.load());
continue;
}
bool AllThreadsSleeping = true;
foreach (auto thread in process->Threads)
{
if (thread->State.load() == TaskState::Terminated)
continue;
if (thread->State.load() != TaskState::Sleeping)
{
AllThreadsSleeping = false;
break;
}
}
if (AllThreadsSleeping)
process->State.store(TaskState::Sleeping);
else if (process->State.load() == TaskState::Sleeping)
process->State.store(TaskState::Ready);
}
}
nsa NIF void Custom::WakeUpThreads()
{
foreach (auto process in ProcessList)
{
Tasking::TaskState pState = process->State.load();
if (pState != TaskState::Ready &&
pState != TaskState::Sleeping &&
pState != TaskState::Blocked)
continue;
foreach (auto thread in process->Threads)
{
if (likely(thread->State.load() != TaskState::Sleeping))
continue;
/* Check if the thread is ready to wake up. */
if (unlikely(thread->Info.SleepUntil < TimeManager->GetCounter()))
{
if (pState == TaskState::Sleeping)
process->State.store(TaskState::Ready);
thread->State.store(TaskState::Ready);
thread->Info.SleepUntil = 0;
wut_schedbg("Thread \"%s\"(%d) woke up.", thread->Name, thread->ID);
}
else
{
wut_schedbg("Thread \"%s\"(%d) is not ready to wake up. (SleepUntil: %d, Counter: %d)",
thread->Name, thread->ID, thread->Info.SleepUntil, TimeManager->GetCounter());
}
}
}
}
nsa NIF void Custom::CleanupTerminated()
{
foreach (auto pcb in ProcessList)
{
if (pcb->State.load() == TaskState::Terminated)
{
debug("Found terminated process %s(%d)", pcb->Name, pcb->ID);
delete pcb;
continue;
}
foreach (TCB *tcb in pcb->Threads)
{
if (tcb->State == Terminated)
delete tcb;
}
}
}
nsa NIF void Custom::Schedule(CPU::SchedulerFrame *Frame)
{
if (unlikely(StopScheduler))
{
warn("Scheduler stopped.");
return;
}
bool ProcessNotChanged = false;
uint64_t SchedTmpTicks = TimeManager->GetCounter();
this->LastTaskTicks.store(size_t(SchedTmpTicks - this->SchedulerTicks.load()));
CPUData *CurrentCPU = GetCurrentCPU();
this->LastCore.store(CurrentCPU->ID);
schedbg("Scheduler called on CPU %d.", CurrentCPU->ID);
if (unlikely(!CurrentCPU->CurrentProcess.load() ||
!CurrentCPU->CurrentThread.load()))
{
schedbg("Invalid process or thread. Finding a new one.");
ProcessNotChanged = true;
if (this->FindNewProcess(CurrentCPU))
goto Success;
else
goto Idle;
}
else
{
CurrentCPU->CurrentThread->Registers = *Frame;
CPU::x64::fxsave(&CurrentCPU->CurrentThread->FPU);
#ifdef a64
CurrentCPU->CurrentThread->ShadowGSBase = CPU::x64::rdmsr(CPU::x64::MSR_SHADOW_GS_BASE);
CurrentCPU->CurrentThread->GSBase = CPU::x64::rdmsr(CPU::x64::MSR_GS_BASE);
CurrentCPU->CurrentThread->FSBase = CPU::x64::rdmsr(CPU::x64::MSR_FS_BASE);
#else
CurrentCPU->CurrentThread->ShadowGSBase = uintptr_t(CPU::x32::rdmsr(CPU::x32::MSR_SHADOW_GS_BASE));
CurrentCPU->CurrentThread->GSBase = uintptr_t(CPU::x32::rdmsr(CPU::x32::MSR_GS_BASE));
CurrentCPU->CurrentThread->FSBase = uintptr_t(CPU::x32::rdmsr(CPU::x32::MSR_FS_BASE));
#endif
if (CurrentCPU->CurrentProcess->State.load() == TaskState::Running)
CurrentCPU->CurrentProcess->State.store(TaskState::Ready);
if (CurrentCPU->CurrentThread->State.load() == TaskState::Running)
CurrentCPU->CurrentThread->State.store(TaskState::Ready);
this->CleanupTerminated();
schedbg("Passed CleanupTerminated");
this->UpdateProcessState();
schedbg("Passed UpdateProcessState");
this->WakeUpThreads();
schedbg("Passed WakeUpThreads");
if (this->SchedulerUpdateTrapFrame)
{
debug("Updating trap frame");
this->SchedulerUpdateTrapFrame = false;
CurrentCPU->CurrentProcess->State.store(TaskState::Running);
CurrentCPU->CurrentThread->State.store(TaskState::Running);
*Frame = CurrentCPU->CurrentThread->Registers;
this->SchedulerTicks.store(size_t(TimeManager->GetCounter() - SchedTmpTicks));
return;
}
if (this->GetNextAvailableThread(CurrentCPU))
{
ProcessNotChanged = true;
goto Success;
}
schedbg("Passed GetNextAvailableThread");
if (this->GetNextAvailableProcess(CurrentCPU))
{
goto Success;
}
schedbg("Passed GetNextAvailableProcess");
if (SchedulerSearchProcessThread(CurrentCPU))
{
schedbg("Passed SchedulerSearchProcessThread");
goto Success;
}
else
{
schedbg("SchedulerSearchProcessThread failed. Going idle.");
goto Idle;
}
}
assert(!"Unwanted code execution");
Idle:
ProcessNotChanged = true;
CurrentCPU->CurrentProcess = IdleProcess;
CurrentCPU->CurrentThread = IdleThread;
Success:
schedbg("Process \"%s\"(%d) Thread \"%s\"(%d) is now running on CPU %d",
CurrentCPU->CurrentProcess->Name, CurrentCPU->CurrentProcess->ID,
CurrentCPU->CurrentThread->Name, CurrentCPU->CurrentThread->ID, CurrentCPU->ID);
if (!ProcessNotChanged)
UpdateUsage(&CurrentCPU->CurrentProcess->Info,
CurrentCPU->CurrentProcess->Security.ExecutionMode,
CurrentCPU->ID);
UpdateUsage(&CurrentCPU->CurrentThread->Info,
CurrentCPU->CurrentThread->Security.ExecutionMode,
CurrentCPU->ID);
CurrentCPU->CurrentProcess->State.store(TaskState::Running);
CurrentCPU->CurrentThread->State.store(TaskState::Running);
if (CurrentCPU->CurrentThread->Registers.cs != GDT_KERNEL_CODE)
CurrentCPU->CurrentThread->Registers.ppt = (uint64_t)(void *)CurrentCPU->CurrentProcess->PageTable;
else
CurrentCPU->CurrentThread->Registers.ppt = (uint64_t)(void *)KernelPageTable;
// if (!SchedulerUpdateTrapFrame) {} // TODO
*Frame = CurrentCPU->CurrentThread->Registers;
#ifdef a64
GlobalDescriptorTable::SetKernelStack((void *)((uintptr_t)CurrentCPU->CurrentThread->Stack->GetStackTop()));
CPU::x64::fxrstor(&CurrentCPU->CurrentThread->FPU);
CPU::x64::wrmsr(CPU::x64::MSR_SHADOW_GS_BASE, CurrentCPU->CurrentThread->ShadowGSBase);
CPU::x64::wrmsr(CPU::x64::MSR_GS_BASE, CurrentCPU->CurrentThread->GSBase);
CPU::x64::wrmsr(CPU::x64::MSR_FS_BASE, CurrentCPU->CurrentThread->FSBase);
#else
GlobalDescriptorTable::SetKernelStack((void *)((uintptr_t)CurrentCPU->CurrentThread->Stack->GetStackTop()));
CPU::x32::fxrstor(&CurrentCPU->CurrentThread->FPU);
CPU::x32::wrmsr(CPU::x32::MSR_SHADOW_GS_BASE, CurrentCPU->CurrentThread->ShadowGSBase);
CPU::x32::wrmsr(CPU::x32::MSR_GS_BASE, CurrentCPU->CurrentThread->GSBase);
CPU::x32::wrmsr(CPU::x32::MSR_FS_BASE, CurrentCPU->CurrentThread->FSBase);
#endif
CurrentCPU->CurrentProcess->Signals.HandleSignal(Frame, CurrentCPU->CurrentThread.load());
if (!ProcessNotChanged)
(&CurrentCPU->CurrentProcess->Info)->LastUpdateTime = TimeManager->GetCounter();
(&CurrentCPU->CurrentThread->Info)->LastUpdateTime = TimeManager->GetCounter();
this->OneShot(CurrentCPU->CurrentThread->Info.Priority);
if (CurrentCPU->CurrentThread->Security.IsDebugEnabled &&
CurrentCPU->CurrentThread->Security.IsKernelDebugEnabled)
{
#ifdef a64
trace("%s[%ld]: RIP=%#lx RBP=%#lx RSP=%#lx",
CurrentCPU->CurrentThread->Name, CurrentCPU->CurrentThread->ID,
CurrentCPU->CurrentThread->Registers.rip,
CurrentCPU->CurrentThread->Registers.rbp,
CurrentCPU->CurrentThread->Registers.rsp);
#else
trace("%s[%ld]: EIP=%#lx EBP=%#lx ESP=%#lx",
CurrentCPU->CurrentThread->Name, CurrentCPU->CurrentThread->ID,
CurrentCPU->CurrentThread->Registers.eip,
CurrentCPU->CurrentThread->Registers.ebp,
CurrentCPU->CurrentThread->Registers.esp);
#endif
}
this->SchedulerTicks.store(size_t(TimeManager->GetCounter() - SchedTmpTicks));
}
nsa NIF void Custom::OnInterruptReceived(CPU::SchedulerFrame *Frame)
{
SmartCriticalSection(SchedulerLock);
this->Schedule(Frame);
}
Custom::Custom(Task *ctx) : Base(ctx), Interrupts::Handler(16) /* IRQ16 */
{
#if defined(a86)
// Map the IRQ16 to the first CPU.
((APIC::APIC *)Interrupts::apic[0])->RedirectIRQ(0, CPU::x86::IRQ16 - CPU::x86::IRQ0, 1);
#endif
}
Custom::~Custom()
{
foreach (PCB *Process in ProcessList)
{
foreach (TCB *Thread in Process->Threads)
{
if (Thread == GetCurrentCPU()->CurrentThread.load())
continue;
ctx->KillThread(Thread, KILL_SCHEDULER_DESTRUCTION);
}
if (Process == GetCurrentCPU()->CurrentProcess.load())
continue;
ctx->KillProcess(Process, KILL_SCHEDULER_DESTRUCTION);
}
debug("Waiting for processes to terminate");
uint64_t timeout = TimeManager->CalculateTarget(20, Time::Units::Seconds);
while (this->GetProcessList().size() > 0)
{
trace("Waiting for %d processes to terminate", this->GetProcessList().size());
int NotTerminated = 0;
foreach (PCB *Process in this->GetProcessList())
{
trace("Process %s(%d) is still running (or waiting to be removed state %#lx)",
Process->Name, Process->ID, Process->State);
if (Process->State == TaskState::Terminated)
{
debug("Process %s(%d) terminated", Process->Name, Process->ID);
continue;
}
NotTerminated++;
}
if (NotTerminated == 1)
break;
ctx->Sleep(1000);
debug("Current working process is %s(%d)",
ctx->GetCurrentProcess()->Name,
ctx->GetCurrentProcess()->ID);
if (TimeManager->GetCounter() > timeout)
{
error("Timeout waiting for processes to terminate");
break;
}
this->OneShot(100);
}
}
}

771
Kernel/tasking/signal.cpp Normal file
View File

@ -0,0 +1,771 @@
/*
This file is part of Fennix Kernel.
Fennix Kernel 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.
Fennix Kernel 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 Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/
#include <signal.hpp>
#include <dumper.hpp>
#include <task.hpp>
#include <errno.h>
#if defined(a64)
#include "../arch/amd64/cpu/gdt.hpp"
#elif defined(a32)
#include "../arch/i386/cpu/gdt.hpp"
#elif defined(aa64)
#endif
#include "../kernel.h"
#ifdef DEBUG
const char *sigStr[] = {
/* 0 */ "INVALID",
/* 1 */ "SIGABRT",
/* 2 */ "SIGALRM",
/* 3 */ "SIGBUS",
/* 4 */ "SIGCHLD",
/* 5 */ "SIGCONT",
/* 6 */ "SIGFPE",
/* 7 */ "SIGHUP",
/* 8 */ "SIGILL",
/* 9 */ "SIGINT",
/* 10 */ "SIGKILL",
/* 11 */ "SIGPIPE",
/* 12 */ "SIGQUIT",
/* 13 */ "SIGSEGV",
/* 14 */ "SIGSTOP",
/* 15 */ "SIGTERM",
/* 16 */ "SIGTSTP",
/* 17 */ "SIGTTIN",
/* 18 */ "SIGTTOU",
/* 19 */ "SIGUSR1",
/* 20 */ "SIGUSR2",
/* 21 */ "SIGPOLL",
/* 22 */ "SIGPROF",
/* 23 */ "SIGSYS",
/* 24 */ "SIGTRAP",
/* 25 */ "SIGURG",
/* 26 */ "SIGVTALRM",
/* 27 */ "SIGXCPU",
/* 28 */ "SIGXFSZ",
/* 29 */ "SIGCOMP1",
/* 30 */ "SIGCOMP2",
/* 31 */ "SIGCOMP3",
/* 32 */ "SIGRTMIN",
/* 33 */ "SIGRT_1",
/* 34 */ "SIGRT_2",
/* 35 */ "SIGRT_3",
/* 36 */ "SIGRT_4",
/* 37 */ "SIGRT_5",
/* 38 */ "SIGRT_6",
/* 39 */ "SIGRT_7",
/* 40 */ "SIGRT_8",
/* 41 */ "SIGRT_9",
/* 42 */ "SIGRT_10",
/* 43 */ "SIGRT_11",
/* 44 */ "SIGRT_12",
/* 45 */ "SIGRT_13",
/* 46 */ "SIGRT_14",
/* 47 */ "SIGRT_15",
/* 48 */ "SIGRT_16",
/* 49 */ "SIGRT_17",
/* 50 */ "SIGRT_18",
/* 51 */ "SIGRT_19",
/* 52 */ "SIGRT_20",
/* 53 */ "SIGRT_21",
/* 54 */ "SIGRT_22",
/* 55 */ "SIGRT_23",
/* 56 */ "SIGRT_24",
/* 57 */ "SIGRT_25",
/* 58 */ "SIGRT_26",
/* 59 */ "SIGRT_27",
/* 60 */ "SIGRT_28",
/* 61 */ "SIGRT_29",
/* 62 */ "SIGRT_30",
/* 63 */ "SIGRT_31",
/* 64 */ "SIGRTMAX",
};
const char *dispStr[] = {
"SIG_TERM",
"SIG_IGN ",
"SIG_CORE",
"SIG_STOP",
"SIG_CONT",
};
#endif
extern "C" uintptr_t _sig_native_trampoline_start, _sig_native_trampoline_end;
extern "C" uintptr_t _sig_linux_trampoline_start, _sig_linux_trampoline_end;
static const struct
{
Signals Signal;
SignalDispositions Disposition;
} SignalDisposition[] = {
{SIGHUP, SIG_TERM},
{SIGINT, SIG_TERM},
{SIGQUIT, SIG_TERM},
{SIGILL, SIG_CORE},
{SIGTRAP, SIG_CORE},
{SIGABRT, SIG_CORE},
{SIGBUS, SIG_CORE},
{SIGFPE, SIG_CORE},
{SIGKILL, SIG_TERM},
{SIGUSR1, SIG_TERM},
{SIGSEGV, SIG_CORE},
{SIGUSR2, SIG_TERM},
{SIGPIPE, SIG_TERM},
{SIGALRM, SIG_TERM},
{SIGTERM, SIG_TERM},
{SIGCOMP1, SIG_IGN},
{SIGCHLD, SIG_IGN},
{SIGCONT, SIG_CONT},
{SIGSTOP, SIG_STOP},
{SIGTSTP, SIG_STOP},
{SIGTTIN, SIG_STOP},
{SIGTTOU, SIG_STOP},
{SIGURG, SIG_IGN},
{SIGXCPU, SIG_CORE},
{SIGXFSZ, SIG_CORE},
{SIGVTALRM, SIG_TERM},
{SIGPROF, SIG_TERM},
{SIGCOMP2, SIG_IGN},
{SIGPOLL, SIG_TERM},
{SIGCOMP3, SIG_IGN},
{SIGSYS, SIG_CORE},
{SIGRTMIN, SIG_IGN},
{SIGRT_1, SIG_IGN},
{SIGRT_2, SIG_IGN},
{SIGRT_3, SIG_IGN},
{SIGRT_4, SIG_IGN},
{SIGRT_5, SIG_IGN},
{SIGRT_6, SIG_IGN},
{SIGRT_7, SIG_IGN},
{SIGRT_8, SIG_IGN},
{SIGRT_9, SIG_IGN},
{SIGRT_10, SIG_IGN},
{SIGRT_11, SIG_IGN},
{SIGRT_12, SIG_IGN},
{SIGRT_13, SIG_IGN},
{SIGRT_14, SIG_IGN},
{SIGRT_15, SIG_IGN},
{SIGRT_16, SIG_IGN},
{SIGRT_17, SIG_IGN},
{SIGRT_18, SIG_IGN},
{SIGRT_19, SIG_IGN},
{SIGRT_20, SIG_IGN},
{SIGRT_21, SIG_IGN},
{SIGRT_22, SIG_IGN},
{SIGRT_23, SIG_IGN},
{SIGRT_24, SIG_IGN},
{SIGRT_25, SIG_IGN},
{SIGRT_26, SIG_IGN},
{SIGRT_27, SIG_IGN},
{SIGRT_28, SIG_IGN},
{SIGRT_29, SIG_IGN},
{SIGRT_30, SIG_IGN},
{SIGRT_31, SIG_IGN},
{SIGRTMAX, SIG_IGN},
};
SignalDispositions GetDefaultSignalDisposition(Signals sig)
{
foreach (auto var in SignalDisposition)
{
if (var.Signal == sig)
return var.Disposition;
}
error("Invalid signal: %d", sig);
return SIG_TERM;
}
/* subsystem/linux/syscall.cpp */
extern int ConvertSignalToLinux(Signals sig);
namespace Tasking
{
bool Signal::LinuxSig()
{
return ((PCB *)ctx)->Info.Compatibility == Linux;
}
int Signal::MakeExitCode(Signals sig)
{
if (this->LinuxSig())
return 128 + ConvertSignalToLinux(sig);
else
return 100 + sig;
}
void Signal::InitTrampoline()
{
if (unlikely(TrampAddr))
return;
PCB *pcb = (PCB *)ctx;
debug("Trampoline not set up yet");
switch (pcb->Info.Compatibility)
{
case Native:
{
debug("%#lx - %#lx",
&_sig_native_trampoline_end,
&_sig_native_trampoline_start);
TrampSz = (size_t)&_sig_native_trampoline_end -
(size_t)&_sig_native_trampoline_start;
TrampAddr = pcb->vma->RequestPages(TO_PAGES(TrampSz), true);
memcpy((void *)TrampAddr,
(void *)&_sig_native_trampoline_start,
TrampSz);
debug("Trampoline at %#lx with size %lld",
TrampAddr, TrampSz);
break;
}
case Linux:
{
debug("%#lx - %#lx",
&_sig_linux_trampoline_end,
&_sig_linux_trampoline_start);
TrampSz = (size_t)&_sig_linux_trampoline_end -
(size_t)&_sig_linux_trampoline_start;
TrampAddr = pcb->vma->RequestPages(TO_PAGES(TrampSz), true);
memcpy((void *)TrampAddr,
(void *)&_sig_linux_trampoline_start,
TrampSz);
debug("Trampoline at %#lx with size %lld",
TrampAddr, TrampSz);
break;
}
case Windows:
{
assert(!"Windows compatibility not implemented");
break;
}
default:
/* Process not fully initalized. */
return;
}
}
/* ------------------------------------------------------ */
int Signal::AddWatcher(Signal *who, Signals sig)
{
SignalInfo info;
info.sig = sig;
info.val.sival_ptr = who;
SmartLock(SignalLock);
Watchers.push_back(info);
return 0;
}
int Signal::RemoveWatcher(Signal *who, Signals sig)
{
SmartLock(SignalLock);
forItr(itr, Watchers)
{
if (itr->sig == sig &&
itr->val.sival_ptr == who)
{
Watchers.erase(itr);
return 0;
}
}
return -ENOENT;
}
int Signal::AddSignal(Signals sig, union sigval val, pid_t tid)
{
SignalInfo info{.sig = sig, .val = val, .tid = tid};
Queue.push_back(info);
return 0;
}
int Signal::RemoveSignal(Signals sig)
{
size_t n = Queue.remove_if([sig](SignalInfo &info)
{ return info.sig == sig; });
debug("Removed %d signals", n);
return n ? 0 : -ENOENT;
}
Signal::SignalInfo Signal::GetAvailableSignal(void *thread)
{
forItr(itr, Queue)
{
// if (GlobalMask.test(itr->sig - 1))
// {
// debug("Signal %s is blocked by global mask",
// sigStr[itr->sig]);
// continue;
// }
if (((TCB *)thread)->Signals.Mask.test(itr->sig - 1))
{
debug("Signal %s is blocked by thread mask",
sigStr[itr->sig]);
continue;
}
if (((TCB *)thread)->ID != itr->tid && itr->tid != -1)
{
debug("Signal %s is not for this thread",
sigStr[itr->sig]);
continue;
}
assert(sa[itr->sig].sa_handler.Disposition != SAD_IGN);
assert(sa[itr->sig].sa_handler.Disposition != SAD_DFL);
Queue.erase(itr);
debug("Signal %s is available", sigStr[itr->sig]);
return *itr;
}
debug("No signal available");
return {};
}
bool Signal::HandleSignal(CPU::SchedulerFrame *tf, void *thread)
{
/* We don't want to do this in kernel mode */
if (tf->cs != GDT_USER_CODE)
return false;
if (Queue.empty())
return false;
debug("We have %d signals to handle", Queue.size());
SmartLock(SignalLock);
SignalInfo sigI = GetAvailableSignal(thread);
if (sigI.sig == SIG_NULL)
return false;
uintptr_t _p_rsp = ((PCB *)ctx)->PageTable->Get(tf->rsp);
uint64_t paRsp = _p_rsp;
paRsp &= ~0xF; /* Align */
paRsp -= 128; /* Red zone */
/* Calculate the virtual rsp */
uintptr_t _v_rsp = tf->rsp;
_v_rsp &= ~0xF; /* Align */
_v_rsp -= 128; /* Red zone */
uint64_t *vRsp = (uint64_t *)(_v_rsp - sizeof(StackInfo));
vRsp--; /* Alignment */
vRsp--; /* Handler Address */
assert(!((uintptr_t)vRsp & 0xF));
/* Add the stack info */
StackInfo si{};
CPU::x64::fxsave(&si.fx);
si.tf = *tf;
si.GSBase = CPU::x64::rdmsr(CPU::x64::MSR_GS_BASE);
si.FSBase = CPU::x64::rdmsr(CPU::x64::MSR_FS_BASE);
si.ShadowGSBase = CPU::x64::rdmsr(CPU::x64::MSR_SHADOW_GS_BASE);
si.SignalMask = ((TCB *)thread)->Signals.Mask.to_ulong();
si.Compatibility = ((PCB *)ctx)->Info.Compatibility;
debug("gs: %#lx fs: %#lx shadow: %#lx",
si.GSBase, si.FSBase, si.ShadowGSBase);
/* Copy the stack info */
uint64_t *pRsp = (uint64_t *)(paRsp - sizeof(StackInfo));
memcpy(pRsp, &si, sizeof(StackInfo));
/* Set the handler address */
pRsp--; /* Alignment */
pRsp--;
*pRsp = uint64_t(sa[sigI.sig].sa_handler.Handler);
assert(!((uintptr_t)pRsp & 0xF));
int cSig = LinuxSig() ? ConvertSignalToLinux((Signals)sigI.sig) : sigI.sig;
#ifdef DEBUG
DumpData("Stack Data", (void *)pRsp,
paRsp - uint64_t(pRsp));
debug("initial stack tf->rsp: %#lx after: %#lx",
tf->rsp, uint64_t(vRsp));
debug("sig: %d -> %d", sigI.sig, cSig);
#endif
tf->rsp = uint64_t(vRsp);
tf->rip = uint64_t(TrampAddr);
/* void func(int signo); */
/* void func(int signo, siginfo_t *info, void *context); */
tf->rdi = cSig;
if (sa[sigI.sig].Flags & SA_SIGINFO)
{
fixme("SA_SIGINFO not implemented");
siginfo_t *info = 0;
void *context = 0;
tf->rsi = uint64_t(info);
tf->rdx = uint64_t(context);
tf->rcx = 0;
tf->r8 = 0;
tf->r9 = 0;
}
else
{
tf->rsi = 0;
tf->rdx = 0;
tf->rcx = 0;
tf->r8 = 0;
tf->r9 = 0;
}
((TCB *)thread)->Signals.Mask = sa[sigI.sig].Mask;
assert(TrampAddr != nullptr);
return true;
}
void Signal::RestoreHandleSignal(SyscallsFrame *sf, void *thread)
{
debug("Restoring signal handler");
SmartLock(SignalLock);
gsTCB *gs = (gsTCB *)CPU::x64::rdmsr(CPU::x64::MSR_GS_BASE);
uint64_t *sp = (uint64_t *)((PCB *)ctx)->PageTable->Get(gs->TempStack);
sp++; /* Alignment */
sp++; /* Handler Address */
assert(!((uintptr_t)sp & 0xF));
StackInfo *si = (StackInfo *)sp;
assert(si != nullptr);
sf->r15 = si->tf.r15;
sf->r14 = si->tf.r14;
sf->r13 = si->tf.r13;
sf->r12 = si->tf.r12;
sf->r11 = si->tf.r11;
sf->r10 = si->tf.r10;
sf->r9 = si->tf.r9;
sf->r8 = si->tf.r8;
sf->rbp = si->tf.rbp;
sf->rdi = si->tf.rdi;
sf->rsi = si->tf.rsi;
sf->rdx = si->tf.rdx;
sf->rcx = si->tf.rcx;
sf->rbx = si->tf.rbx;
sf->rax = si->tf.rax;
sf->Flags = si->tf.rflags.raw;
sf->ReturnAddress = si->tf.rip;
gs->TempStack = (void *)si->tf.rsp;
((TCB *)thread)->Signals.Mask = si->SignalMask;
CPU::x64::fxrstor(&si->fx);
CPU::x64::wrmsr(CPU::x64::MSR_GS_BASE, si->ShadowGSBase);
CPU::x64::wrmsr(CPU::x64::MSR_FS_BASE, si->FSBase);
CPU::x64::wrmsr(CPU::x64::MSR_SHADOW_GS_BASE, si->GSBase);
debug("gs: %#lx fs: %#lx shadow: %#lx",
si->GSBase, si->FSBase, si->ShadowGSBase);
// ((PCB *)ctx)->GetContext()->Yield();
// __builtin_unreachable();
/* Return because we will restore at sysretq */
}
int Signal::SetAction(Signals sig, const SignalAction *act)
{
SmartLock(SignalLock);
if ((size_t)sig > sizeof(sa) / sizeof(sa[0]))
{
debug("Invalid signal: %d", sig);
return -EINVAL;
}
if ((long)act->sa_handler.Disposition == SAD_IGN)
{
Disposition[sig] = SIG_IGN;
debug("Set disposition for %s to SIG_IGN", sigStr[sig]);
debug("Discarding pending signals %s", sigStr[sig]);
bool found = false;
forItr(itr, Queue)
{
if (itr->sig == sig)
{
Queue.erase(itr);
found = true;
break;
}
}
if (!found)
{
debug("No pending signal %s", sigStr[sig]);
}
}
if ((long)act->sa_handler.Disposition == SAD_DFL)
{
Disposition[sig] = GetDefaultSignalDisposition(sig);
debug("Set disposition for %s to %s (default)", sigStr[sig],
dispStr[Disposition[sig]]);
}
sa[sig].sa_handler.Handler = act->sa_handler.Handler;
sa[sig].Mask = act->Mask;
sa[sig].Flags = act->Flags;
sa[sig].Restorer = act->Restorer;
debug("Set action for %s with handler %#lx, mask %#lx and flags %#lx",
sigStr[sig], sa[sig].sa_handler.Handler, sa[sig].Mask, sa[sig].Flags);
return 0;
}
int Signal::GetAction(Signals sig, SignalAction *act)
{
SmartLock(SignalLock);
if ((size_t)sig > sizeof(sa) / sizeof(sa[0]))
{
debug("Invalid signal: %d", sig);
return -EINVAL;
}
act->sa_handler.Handler = sa[sig].sa_handler.Handler;
act->Mask = sa[sig].Mask;
act->Flags = sa[sig].Flags;
act->Restorer = sa[sig].Restorer;
debug("Got action for %s with handler %#lx, mask %#lx and flags %#lx",
sigStr[sig], sa[sig].sa_handler.Handler, sa[sig].Mask, sa[sig].Flags);
return 0;
}
int Signal::SendSignal(Signals sig, sigval val, pid_t tid)
{
SmartLock(SignalLock);
PCB *pcb = (PCB *)ctx;
LastSignal = sig;
debug("Sending signal %s to %s(%d)",
sigStr[sig], pcb->Name, pcb->ID);
if (sa[sig].sa_handler.Handler)
{
if (pcb->Security.ExecutionMode == Kernel)
{
info("Kernel processes cannot have signal handlers");
return -EINVAL;
}
if (sa[sig].sa_handler.Disposition == SAD_IGN)
{
debug("Ignoring signal %s", sigStr[sig]);
return 0;
}
debug("sa_handler: %#lx", sa[sig].sa_handler.Handler);
debug("Adding signal %s to queue", sigStr[sig]);
goto CompleteSignal;
}
else
{
debug("No handler for signal %s", sigStr[sig]);
}
if (thisThread->Signals.Mask.test(sig - 1))
{
debug("Signal %s is blocked by thread mask",
sigStr[sig]);
return 0;
}
debug("Signal disposition: %s", dispStr[Disposition[sig]]);
switch (Disposition[sig])
{
case SIG_TERM:
{
if (unlikely(pcb->Security.IsCritical))
{
debug("Critical process %s received signal %s(%d): Terminated",
pcb->Name, sigStr[sig], sig);
// int3;
}
pcb->SetExitCode(MakeExitCode(sig));
debug("We have %d watchers", this->Watchers.size());
if (this->Watchers.size() > 0)
pcb->SetState(Zombie);
else
pcb->SetState(Terminated);
return val.sival_int == Tasking::KILL_CRASH ? -EFAULT : 0;
}
case SIG_IGN:
{
debug("Ignoring signal %d", sig);
return 0;
}
case SIG_CORE:
{
fixme("core dump");
if (unlikely(pcb->Security.IsCritical))
{
debug("Critical process %s received signal %s(%d): Core dumped",
pcb->Name, sigStr[sig], sig);
// int3;
}
pcb->SetExitCode(MakeExitCode(sig));
debug("We have %d watchers", this->Watchers.size());
if (this->Watchers.size() > 0)
pcb->SetState(CoreDump);
else
pcb->SetState(Terminated);
return val.sival_int == Tasking::KILL_CRASH ? -EFAULT : 0;
}
case SIG_STOP:
{
debug("Stopping process %s(%d) with signal %s(%d)",
pcb->Name, pcb->ID, sigStr[sig], sig);
pcb->SetState(Stopped);
return 0;
}
case SIG_CONT:
{
debug("Continuing process %s(%d) with signal %s(%d)",
pcb->Name, pcb->ID, sigStr[sig], sig);
pcb->SetState(Ready);
return 0;
}
default:
assert(!"Invalid signal disposition");
return -EINVAL;
};
CompleteSignal:
if (pcb->Security.ExecutionMode == Kernel)
{
debug("Kernel process %s received signal %s(%d)! Ignoring... (with exceptions)",
pcb->Name, sigStr[sig], sig);
return 0;
}
this->InitTrampoline();
debug("Signal %s(%d) completed", sigStr[sig], sig);
if (Disposition[sig] != SIG_IGN)
{
foreach (auto info in Watchers)
{
Signal *who = (Signal *)info.val.sival_ptr;
assert(who != nullptr);
debug("Sending SIGCHLD to %s(%d)",
((PCB *)who->GetContext())->Name,
((PCB *)who->GetContext())->ID);
who->SendSignal(SIGCHLD, val);
}
}
debug("Adding signal to queue");
Queue.push_back({.sig = sig, .val = val, .tid = tid});
return 0;
}
int Signal::WaitAnySignal()
{
debug("Waiting for any signal");
size_t oldSize = Queue.size();
Reset:
while (Queue.size() == oldSize)
TaskManager->Yield();
if (Queue.size() > oldSize)
{
debug("Added signal to queue %d > %d",
Queue.size(), oldSize);
oldSize = Queue.size();
goto Reset;
}
debug("Signal received");
return -EINTR;
}
int Signal::WaitSignal(Signals sig, union sigval *val)
{
assert(!"WaitSignal not implemented");
return 0;
}
int Signal::WaitSignalTimeout(Signals sig, union sigval *val, uint64_t timeout)
{
assert(!"WaitSignalTimeout not implemented");
return 0;
}
Signal::Signal(void *_ctx)
{
assert(_ctx != nullptr);
this->ctx = _ctx;
// for (int i = 1; i < SIGNAL_MAX; i++)
// Disposition[i] = GetDefaultSignalDisposition(i);
#ifdef DEBUG
static int once = 0;
if (!once++)
{
for (int i = 1; i < SIGNAL_MAX; i++)
debug("%s: %s",
sigStr[i],
dispStr[Disposition[(Signals)i]]);
}
#endif
}
Signal::~Signal() {}
sigset_t ThreadSignal::Block(sigset_t sig)
{
sigset_t oldMask = Mask.to_ulong();
Mask |= sig;
debug("%#lx -> %#lx", oldMask, Mask);
return oldMask;
}
sigset_t ThreadSignal::Unblock(sigset_t sig)
{
sigset_t oldMask = Mask.to_ulong();
Mask &= ~sig;
debug("%#lx -> %#lx", oldMask, Mask);
return oldMask;
}
sigset_t ThreadSignal::SetMask(sigset_t sig)
{
sigset_t oldMask = Mask.to_ulong();
Mask = sig;
debug("%#lx -> %#lx", oldMask, Mask);
return oldMask;
}
sigset_t ThreadSignal::GetMask()
{
return Mask.to_ulong();
}
}

281
Kernel/tasking/task.cpp Normal file
View File

@ -0,0 +1,281 @@
/*
This file is part of Fennix Kernel.
Fennix Kernel 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.
Fennix Kernel 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 Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/
#include <task.hpp>
#include <scheduler.hpp>
#include <dumper.hpp>
#include <convert.h>
#include <lock.hpp>
#include <printf.h>
#include <smp.hpp>
#include <io.h>
#include "../kernel.h"
#if defined(a64)
#include "../arch/amd64/cpu/apic.hpp"
#include "../arch/amd64/cpu/gdt.hpp"
#elif defined(a32)
#include "../arch/i386/cpu/apic.hpp"
#elif defined(aa64)
#endif
// #define DEBUG_TASKING 1
#ifdef DEBUG_TASKING
#define tskdbg(m, ...) \
debug(m, ##__VA_ARGS__); \
__sync
#else
#define tskdbg(m, ...)
#endif
namespace Tasking
{
PCB *Task::GetCurrentProcess()
{
return GetCurrentCPU()->CurrentProcess.load();
}
TCB *Task::GetCurrentThread()
{
return GetCurrentCPU()->CurrentThread.load();
}
PCB *Task::GetProcessByID(TID ID)
{
return ((Scheduler::Base *)Scheduler)->GetProcessByID(ID);
}
TCB *Task::GetThreadByID(TID ID, PCB *Parent)
{
return ((Scheduler::Base *)Scheduler)->GetThreadByID(ID, Parent);
}
std::vector<PCB *> Task::GetProcessList()
{
return ((Scheduler::Base *)Scheduler)->GetProcessList();
}
void Task::Panic()
{
((Scheduler::Base *)Scheduler)->StopScheduler.store(true);
}
bool Task::IsPanic()
{
return ((Scheduler::Base *)Scheduler)->StopScheduler;
}
void Task::Yield()
{
((Scheduler::Base *)Scheduler)->Yield();
}
void Task::UpdateFrame()
{
((Scheduler::Base *)Scheduler)->SchedulerUpdateTrapFrame = true;
((Scheduler::Base *)Scheduler)->Yield();
}
void Task::PushProcess(PCB *pcb)
{
((Scheduler::Base *)Scheduler)->PushProcess(pcb);
}
void Task::PopProcess(PCB *pcb)
{
((Scheduler::Base *)Scheduler)->PopProcess(pcb);
}
void Task::WaitForProcess(PCB *pcb)
{
if (pcb == nullptr)
return;
if (pcb->State == TaskState::UnknownStatus)
return;
debug("Waiting for process \"%s\"(%d)",
pcb->Name, pcb->ID);
while (pcb->State != TaskState::Terminated &&
pcb->State != TaskState::Zombie &&
pcb->State != TaskState::CoreDump)
this->Yield();
}
void Task::WaitForThread(TCB *tcb)
{
if (tcb == nullptr)
return;
if (tcb->State == TaskState::UnknownStatus)
return;
debug("Waiting for thread \"%s\"(%d)",
tcb->Name, tcb->ID);
while (tcb->State != TaskState::Terminated &&
tcb->State != TaskState::Zombie &&
tcb->State != TaskState::CoreDump)
this->Yield();
}
void Task::WaitForProcessStatus(PCB *pcb, TaskState status)
{
if (pcb == nullptr)
return;
if (pcb->State == TaskState::UnknownStatus)
return;
debug("Waiting for process \"%s\"(%d) to reach status: %d",
pcb->Name, pcb->ID, status);
while (pcb->State != status)
this->Yield();
}
void Task::WaitForThreadStatus(TCB *tcb, TaskState status)
{
if (tcb == nullptr)
return;
if (tcb->State == TaskState::UnknownStatus)
return;
debug("Waiting for thread \"%s\"(%d) to reach status: %d",
tcb->Name, tcb->ID, status);
while (tcb->State != status)
this->Yield();
}
void Task::Sleep(uint64_t Milliseconds, bool NoSwitch)
{
TCB *thread = this->GetCurrentThread();
PCB *process = thread->Parent;
thread->SetState(TaskState::Sleeping);
{
SmartLock(TaskingLock);
if (process->Threads.size() == 1)
process->SetState(TaskState::Sleeping);
thread->Info.SleepUntil =
TimeManager->CalculateTarget(Milliseconds,
Time::Units::Milliseconds);
}
// #ifdef DEBUG
// uint64_t TicksNow = TimeManager->GetCounter();
// #endif
// debug("Thread \"%s\"(%d) is going to sleep until %llu, current %llu, diff %llu",
// thread->Name, thread->ID, thread->Info.SleepUntil,
// TicksNow, thread->Info.SleepUntil - TicksNow);
if (!NoSwitch)
this->Yield();
}
void Task::SignalShutdown()
{
debug("Current process is %s(%d) and thread is %s(%d)",
GetCurrentProcess()->Name, GetCurrentProcess()->ID,
GetCurrentThread()->Name, GetCurrentThread()->ID);
foreach (auto pcb in((Scheduler::Base *)Scheduler)->GetProcessList())
{
if (pcb->State == TaskState::Terminated ||
pcb->State == TaskState::Zombie)
continue;
if (pcb == GetCurrentProcess())
continue;
debug("Sending SIGTERM to process \"%s\"(%d)",
pcb->Name, pcb->ID);
pcb->SendSignal(SIGTERM);
}
// TODO: wait for processes to terminate with timeout.
}
__no_sanitize("undefined")
TCB *Task::CreateThread(PCB *Parent, IP EntryPoint,
const char **argv, const char **envp,
const std::vector<AuxiliaryVector> &auxv,
TaskArchitecture arch, TaskCompatibility Compatibility,
bool ThreadNotReady)
{
SmartLock(TaskingLock);
return new TCB(this, Parent, EntryPoint,
argv, envp, auxv, arch,
Compatibility, ThreadNotReady);
}
PCB *Task::CreateProcess(PCB *Parent, const char *Name,
TaskExecutionMode ExecutionMode, bool UseKernelPageTable,
uint16_t UserID, uint16_t GroupID)
{
SmartLock(TaskingLock);
return new PCB(this, Parent, Name, ExecutionMode,
UseKernelPageTable, UserID, GroupID);
}
void Task::StartScheduler()
{
((Scheduler::Base *)Scheduler)->StartScheduler();
debug("Tasking Started");
}
Task::Task(const IP EntryPoint)
{
/* I don't know if this is the best way to do this. */
Scheduler::Custom *custom_sched = new Scheduler::Custom(this);
Scheduler::Base *sched = r_cst(Scheduler::Base *, custom_sched);
__sched_ctx = custom_sched;
Scheduler = sched;
KernelProcess = CreateProcess(nullptr, "Kernel",
TaskExecutionMode::Kernel, true);
KernelProcess->PageTable = KernelPageTable;
TCB *kthrd = CreateThread(KernelProcess, EntryPoint,
nullptr, nullptr,
std::vector<AuxiliaryVector>(), GetKArch());
kthrd->Rename("Main Thread");
debug("Created Kernel Process: %s and Thread: %s",
KernelProcess->Name, kthrd->Name);
if (!CPU::Interrupts(CPU::Check))
{
error("Interrupts are not enabled.");
CPU::Interrupts(CPU::Enable);
}
((Scheduler::Base *)Scheduler)->StartIdleProcess();
debug("Tasking is ready");
}
Task::~Task()
{
delete (Scheduler::Custom *)__sched_ctx;
}
}

681
Kernel/tasking/thread.cpp Normal file
View File

@ -0,0 +1,681 @@
/*
This file is part of Fennix Kernel.
Fennix Kernel 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.
Fennix Kernel 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 Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/
#include <task.hpp>
#include <filesystem/ioctl.hpp>
#include <dumper.hpp>
#include <convert.h>
#include <lock.hpp>
#include <printf.h>
#include <smp.hpp>
#include <io.h>
#include "../kernel.h"
#if defined(a64)
#include "../arch/amd64/cpu/apic.hpp"
#include "../arch/amd64/cpu/gdt.hpp"
#elif defined(a32)
#include "../arch/i386/cpu/apic.hpp"
#include "../arch/i386/cpu/gdt.hpp"
#elif defined(aa64)
#endif
// #define DEBUG_TASKING 1
#ifdef DEBUG_TASKING
#define tskdbg(m, ...) \
debug(m, ##__VA_ARGS__); \
__sync
#else
#define tskdbg(m, ...)
#endif
/* For kernel threads only */
void ThreadDoExit()
{
CPUData *CPUData = GetCurrentCPU();
Tasking::TCB *CurrentThread = CPUData->CurrentThread.load();
CurrentThread->SetState(Tasking::Terminated);
debug("\"%s\"(%d) exited with code: %#lx",
CurrentThread->Name,
CurrentThread->ID,
CurrentThread->ExitCode);
CPU::Halt(true);
}
namespace Tasking
{
int TCB::SendSignal(int sig)
{
return this->Parent->Signals.SendSignal((enum Signals)sig, {0}, this->ID);
}
void TCB::SetState(TaskState state)
{
this->State.store(state);
if (this->Parent->Threads.size() == 1)
this->Parent->State.store(state);
}
void TCB::SetExitCode(int code)
{
this->ExitCode.store(code);
if (this->Parent->Threads.size() == 1)
this->Parent->ExitCode.store(code);
}
void TCB::Rename(const char *name)
{
assert(name != nullptr);
assert(strlen(name) > 0);
trace("Renaming thread %s to %s",
this->Name, name);
if (this->Name)
{
this->AllocatedMemory -= strlen(this->Name) + 1;
delete[] this->Name;
}
this->Name = new char[strlen(name) + 1];
this->AllocatedMemory += strlen(name) + 1;
strcpy((char *)this->Name, name);
}
void TCB::SetPriority(TaskPriority priority)
{
assert(priority >= _PriorityMin);
// assert(priority <= _PriorityMax);
trace("Setting priority of thread %s to %d",
this->Name, priority);
Info.Priority = priority;
}
void TCB::SetCritical(bool Critical)
{
trace("Setting criticality of thread %s to %s",
this->Name, Critical ? "true" : "false");
Security.IsCritical = Critical;
if (this->Parent->Threads.size() == 1)
this->Parent->Security.IsCritical = Critical;
}
void TCB::SetDebugMode(bool Enable)
{
trace("Setting debug mode of thread %s to %s",
this->Name, Enable ? "true" : "false");
Security.IsDebugEnabled = Enable;
if (this->Parent->Threads.size() == 1)
this->Parent->Security.IsDebugEnabled = Enable;
}
void TCB::SetKernelDebugMode(bool Enable)
{
trace("Setting kernel debug mode of thread %s to %s",
this->Name, Enable ? "true" : "false");
Security.IsKernelDebugEnabled = Enable;
if (this->Parent->Threads.size() == 1)
this->Parent->Security.IsKernelDebugEnabled = Enable;
}
size_t TCB::GetSize()
{
size_t ret = this->AllocatedMemory;
ret += this->vma->GetAllocatedMemorySize();
ret += this->Stack->GetSize();
return ret;
}
void TCB::SYSV_ABI_Call(uintptr_t Arg1, uintptr_t Arg2,
uintptr_t Arg3, uintptr_t Arg4,
uintptr_t Arg5, uintptr_t Arg6,
void *Function)
{
#if defined(a64)
this->Registers.rdi = Arg1;
this->Registers.rsi = Arg2;
this->Registers.rdx = Arg3;
this->Registers.rcx = Arg4;
this->Registers.r8 = Arg5;
this->Registers.r9 = Arg6;
if (Function != nullptr)
this->Registers.rip = (uint64_t)Function;
#elif defined(a32)
this->Registers.eax = Arg1;
this->Registers.ebx = Arg2;
this->Registers.ecx = Arg3;
this->Registers.edx = Arg4;
this->Registers.esi = Arg5;
this->Registers.edi = Arg6;
if (Function != nullptr)
this->Registers.eip = (uint32_t)Function;
#else
#warning "SYSV ABI not implemented for this architecture"
#endif
}
__no_sanitize("undefined") void TCB::SetupUserStack_x86_64(const char **argv,
const char **envp,
const std::vector<AuxiliaryVector> &auxv,
TaskCompatibility Compatibility)
{
size_t argvLen = 0;
if (argv)
while (argv[argvLen] != nullptr)
argvLen++;
size_t envpLen = 0;
if (envp)
while (envp[envpLen] != nullptr)
envpLen++;
debug("argvLen: %d", argvLen);
debug("envpLen: %d", envpLen);
/* https://articles.manugarg.com/aboutelfauxiliaryvectors.html */
/* https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf#figure.3.9 */
/* rsp is the top of the stack */
char *pStack = (char *)this->Stack->GetStackPhysicalTop();
/* Temporary stack pointer for strings */
char *stackStr = (char *)pStack;
char *stackStrVirtual = (char *)this->Stack->GetStackTop();
/* Store string pointers for later */
uintptr_t *argvStrings = nullptr;
uintptr_t *envpStrings = nullptr;
if (argvLen > 0)
argvStrings = new uintptr_t[argvLen];
if (envpLen > 0)
envpStrings = new uintptr_t[envpLen];
for (size_t i = 0; i < argvLen; i++)
{
/* Subtract the length of the string and the null terminator */
stackStr -= strlen(argv[i]) + 1;
stackStrVirtual -= strlen(argv[i]) + 1;
/* Store the pointer to the string */
argvStrings[i] = (uintptr_t)stackStrVirtual;
/* Copy the string to the stack */
strcpy(stackStr, argv[i]);
debug("argv[%d]: %s", i, argv[i]);
}
for (size_t i = 0; i < envpLen; i++)
{
/* Subtract the length of the string and the null terminator */
stackStr -= strlen(envp[i]) + 1;
stackStrVirtual -= strlen(envp[i]) + 1;
/* Store the pointer to the string */
envpStrings[i] = (uintptr_t)stackStrVirtual;
/* Copy the string to the stack */
strcpy(stackStr, envp[i]);
debug("envp[%d]: %s", i, envp[i]);
}
/* Align the stack to 16 bytes */
stackStr -= (uintptr_t)stackStr & 0xF;
/* Set "pStack" to the new stack pointer */
pStack = (char *)stackStr;
/* If argv and envp sizes are odd then we need to align the stack */
pStack -= (argvLen + envpLen) % 2;
debug("odd align: %#lx | %#lx -> %#lx",
(argvLen + envpLen) % 2,
stackStr, pStack);
/* Ensure StackPointerReg is aligned to the closest lower 16 bytes boundary */
uintptr_t lower16Align = (uintptr_t)pStack;
lower16Align &= ~0xF;
debug("before: %#lx ; after: %#lx", pStack, lower16Align);
pStack = (char *)lower16Align;
/* We need 8 bit pointers for the stack from here */
uintptr_t *Stack64 = (uintptr_t *)pStack;
assert(Stack64 != nullptr);
/* Store the null terminator */
StackPush(Stack64, uintptr_t, AT_NULL);
/* auxv_array is initialized with auxv elements.
If the array is empty then we add a null terminator */
std::vector<AuxiliaryVector> auxv_array = auxv;
if (auxv_array.size() == 0)
auxv_array.push_back({.archaux = {.a_type = AT_NULL, .a_un = {.a_val = 0}}});
/* Store auxillary vector */
foreach (AuxiliaryVector av in auxv_array)
{
/* Subtract the size of the auxillary vector */
Stack64 -= sizeof(Elf_auxv_t) / sizeof(uintptr_t);
/* Store the auxillary vector */
POKE(Elf_auxv_t, Stack64) = av.archaux;
/* TODO: Store strings to the stack */
}
/* Store the null terminator */
StackPush(Stack64, uintptr_t, AT_NULL);
/* Store envpStrings[] to the stack */
Stack64 -= envpLen; /* (1 Stack64 = 8 bits; Stack64 = 8 * envpLen) */
for (size_t i = 0; i < envpLen; i++)
{
*(Stack64 + i) = (uintptr_t)envpStrings[i];
debug("envpStrings[%d]: %#lx",
i, envpStrings[i]);
}
/* Store the null terminator */
StackPush(Stack64, uintptr_t, AT_NULL);
/* Store argvStrings[] to the stack */
Stack64 -= argvLen; /* (1 Stack64 = 8 bits; Stack64 = 8 * argvLen) */
for (size_t i = 0; i < argvLen; i++)
{
*(Stack64 + i) = (uintptr_t)argvStrings[i];
debug("argvStrings[%d]: %#lx",
i, argvStrings[i]);
}
/* Store the argc */
StackPush(Stack64, uintptr_t, argvLen);
/* Set "pStack" to the new stack pointer */
pStack = (char *)Stack64;
/* We need the virtual address but because we are in the kernel we can't use the process page table.
So we modify the physical address and store how much we need to subtract to get the virtual address for RSP. */
uintptr_t SubtractStack = (uintptr_t)this->Stack->GetStackPhysicalTop() - (uintptr_t)pStack;
debug("SubtractStack: %#lx", SubtractStack);
/* Set the stack pointer to the new stack */
uintptr_t StackPointerReg = ((uintptr_t)this->Stack->GetStackTop() - SubtractStack);
// assert(!(StackPointerReg & 0xF));
#if defined(a64)
this->Registers.rsp = StackPointerReg;
#elif defined(a32)
this->Registers.esp = StackPointerReg;
#elif defined(aa64)
this->Registers.sp = StackPointerReg;
#endif
if (argvLen > 0)
delete[] argvStrings;
if (envpLen > 0)
delete[] envpStrings;
#ifdef DEBUG
DumpData("Stack Data",
(void *)((uintptr_t)this->Stack->GetStackPhysicalTop() - (uintptr_t)SubtractStack),
SubtractStack);
#endif
if (Compatibility != Native)
return;
#if defined(a64)
this->Registers.rdi = (uintptr_t)argvLen; // argc
this->Registers.rsi = (uintptr_t)(this->Registers.rsp + 8); // argv
this->Registers.rcx = (uintptr_t)envpLen; // envc
this->Registers.rdx = (uintptr_t)(this->Registers.rsp + 8 + (8 * argvLen) + 8); // envp
#elif defined(a32)
this->Registers.eax = (uintptr_t)argvLen; // argc
this->Registers.ebx = (uintptr_t)(this->Registers.esp + 4); // argv
this->Registers.ecx = (uintptr_t)envpLen; // envc
this->Registers.edx = (uintptr_t)(this->Registers.esp + 4 + (4 * argvLen) + 4); // envp
#elif defined(aa64)
this->Registers.x0 = (uintptr_t)argvLen; // argc
this->Registers.x1 = (uintptr_t)(this->Registers.sp + 8); // argv
this->Registers.x2 = (uintptr_t)envpLen; // envc
this->Registers.x3 = (uintptr_t)(this->Registers.sp + 8 + (8 * argvLen) + 8); // envp
#endif
}
void TCB::SetupUserStack_x86_32(const char **argv,
const char **envp,
const std::vector<AuxiliaryVector> &auxv,
TaskCompatibility Compatibility)
{
fixme("Not implemented");
}
void TCB::SetupUserStack_aarch64(const char **argv,
const char **envp,
const std::vector<AuxiliaryVector> &auxv,
TaskCompatibility Compatibility)
{
fixme("Not implemented");
}
void TCB::SetupThreadLocalStorage()
{
if (Parent->TLS.pBase == 0x0)
return;
this->TLS = Parent->TLS;
debug("msz: %ld fsz: %ld",
this->TLS.Size, this->TLS.fSize);
// size_t pOffset = this->TLS.vBase - std::tolower(this->TLS.vBase);
size_t tlsFullSize = sizeof(uintptr_t) + this->TLS.Size;
/* 2 guard pages */
size_t tlsPages = 1 + TO_PAGES(tlsFullSize) + 1;
void *opTLS = this->vma->RequestPages(tlsPages);
void *pTLS = (void *)(PAGE_SIZE + uintptr_t(opTLS));
this->TLS.pBase = uintptr_t(pTLS);
memcpy(pTLS, (void *)this->TLS.pBase,
this->TLS.Size);
size_t restBytes = this->TLS.Size - this->TLS.fSize;
if (restBytes)
{
memset((void *)(uintptr_t(pTLS) + this->TLS.Size),
0, restBytes);
}
Memory::Virtual vmm(this->Parent->PageTable);
/* Map guard pages */
vmm.Remap((void *)(uintptr_t(pTLS) - PAGE_SIZE), opTLS, Memory::P);
vmm.Remap((void *)(uintptr_t(pTLS) + this->TLS.Size), opTLS, Memory::P);
/* Map TLS */
vmm.Map(pTLS, pTLS, this->TLS.Size, Memory::RW | Memory::US);
uintptr_t *pTLSPointer = (uintptr_t *)this->TLS.pBase + this->TLS.Size;
*pTLSPointer = this->TLS.pBase + this->TLS.Size;
this->GSBase = r_cst(uintptr_t, pTLSPointer);
this->FSBase = r_cst(uintptr_t, pTLSPointer);
}
TCB::TCB(Task *ctx, PCB *Parent, IP EntryPoint,
const char **argv, const char **envp,
const std::vector<AuxiliaryVector> &auxv,
TaskArchitecture Architecture,
TaskCompatibility Compatibility,
bool ThreadNotReady)
: Signals(Parent->Signals)
{
debug("+ %#lx", this);
assert(ctx != nullptr);
assert(Architecture >= _ArchitectureMin);
assert(Architecture <= _ArchitectureMax);
assert(Compatibility >= _CompatibilityMin);
assert(Compatibility <= _CompatibilityMax);
if (Parent == nullptr)
{
this->Parent = ctx->GetCurrentProcess();
assert(this->Parent != nullptr);
}
else
this->Parent = Parent;
this->ctx = ctx;
this->ID = (TID)this->Parent->ID + (TID)this->Parent->Threads.size();
if (Compatibility == TaskCompatibility::Linux)
{
if (Parent->Threads.size() == 0)
this->Linux.tgid = Parent->ID;
else
this->Linux.tgid = Parent->Threads.front()->Linux.tgid;
}
if (this->Name)
delete[] this->Name;
this->Name = new char[strlen(this->Parent->Name) + 1];
strcpy((char *)this->Name, this->Parent->Name);
this->EntryPoint = EntryPoint;
this->ExitCode = KILL_CRASH;
if (ThreadNotReady)
this->SetState(Waiting);
else
this->SetState(Ready);
this->vma = this->Parent->vma;
#if defined(a64)
this->Registers.rip = EntryPoint;
#elif defined(a32)
this->Registers.eip = EntryPoint;
#elif defined(aa64)
this->Registers.pc = EntryPoint;
#endif
switch (this->Parent->Security.ExecutionMode)
{
case TaskExecutionMode::System:
fixme("System mode not supported.");
[[fallthrough]];
case TaskExecutionMode::Kernel:
{
this->Security.IsCritical = true;
this->Stack = new Memory::StackGuard(false, this->vma);
#if defined(a64)
this->ShadowGSBase =
CPU::x64::rdmsr(CPU::x64::MSRID::MSR_SHADOW_GS_BASE);
this->GSBase = CPU::x64::rdmsr(CPU::x64::MSRID::MSR_GS_BASE);
this->FSBase = CPU::x64::rdmsr(CPU::x64::MSRID::MSR_FS_BASE);
this->Registers.cs = GDT_KERNEL_CODE;
this->Registers.ss = GDT_KERNEL_DATA;
this->Registers.rflags.AlwaysOne = 1;
this->Registers.rflags.IF = 1;
this->Registers.rflags.ID = 1;
this->Registers.rflags.AC = 0;
this->Registers.rsp = ((uintptr_t)this->Stack->GetStackTop());
POKE(uintptr_t, this->Registers.rsp) = (uintptr_t)ThreadDoExit;
#elif defined(a32)
this->Registers.cs = GDT_KERNEL_CODE;
this->Registers.r3_ss = GDT_KERNEL_DATA;
this->Registers.eflags.AlwaysOne = 1;
this->Registers.eflags.IF = 1;
this->Registers.eflags.ID = 1;
this->Registers.eflags.AC = 0;
this->Registers.esp = ((uintptr_t)this->Stack->GetStackTop());
POKE(uintptr_t, this->Registers.esp) = (uintptr_t)ThreadDoExit;
#elif defined(aa64)
this->Registers.pc = EntryPoint;
this->Registers.sp = ((uintptr_t)this->Stack->GetStackTop());
POKE(uintptr_t, this->Registers.sp) = (uintptr_t)ThreadDoExit;
#endif
break;
}
case TaskExecutionMode::User:
{
this->Stack = new Memory::StackGuard(true, this->vma);
Memory::VirtualAllocation::AllocatedPages gst = this->ctx->va.RequestPages(TO_PAGES(sizeof(gsTCB)));
this->ctx->va.MapTo(gst, this->Parent->PageTable);
gsTCB *gsT = (gsTCB *)gst.PhysicalAddress;
#ifdef DEBUG
gsT->__stub = 0xFFFFFFFFFFFFFFFF;
#endif
gsT->ScPages = TO_PAGES(STACK_SIZE);
Memory::VirtualAllocation::AllocatedPages ssb = this->ctx->va.RequestPages(gsT->ScPages);
this->ctx->va.MapTo(ssb, this->Parent->PageTable);
gsT->SyscallStackBase = ssb.VirtualAddress;
gsT->SyscallStack = (void *)((uintptr_t)gsT->SyscallStackBase + STACK_SIZE - 0x10);
debug("New syscall stack created: %#lx (base: %#lx) with gs base at %#lx",
gsT->SyscallStack, gsT->SyscallStackBase, gsT);
gsT->TempStack = 0x0;
gsT->t = this;
#if defined(a64)
this->ShadowGSBase = (uintptr_t)gst.VirtualAddress;
this->GSBase = 0;
this->FSBase = 0;
this->Registers.cs = GDT_USER_CODE;
this->Registers.ss = GDT_USER_DATA;
this->Registers.rflags.AlwaysOne = 1;
this->Registers.rflags.IF = 1;
this->Registers.rflags.ID = 1;
this->Registers.rflags.AC = 0;
/* We need to leave the libc's crt
to make a syscall when the Thread
is exited or we are going to get
an exception. */
this->SetupUserStack_x86_64(argv, envp, auxv, Compatibility);
#elif defined(a32)
this->Registers.cs = GDT_USER_CODE;
this->Registers.r3_ss = GDT_USER_DATA;
this->Registers.eflags.AlwaysOne = 1;
this->Registers.eflags.IF = 1;
this->Registers.eflags.ID = 1;
this->Registers.eflags.AC = 0;
/* We need to leave the libc's crt
to make a syscall when the Thread
is exited or we are going to get
an exception. */
this->SetupUserStack_x86_32(argv, envp, auxv);
#elif defined(aa64)
this->SetupUserStack_aarch64(argv, envp, auxv);
#endif
#ifdef DEBUG_TASKING
DumpData(this->Name, this->Stack, STACK_SIZE);
#endif
break;
}
default:
assert(false);
}
SetupThreadLocalStorage();
this->Info.Architecture = Architecture;
this->Info.Compatibility = Compatibility;
this->Security.ExecutionMode = this->Parent->Security.ExecutionMode;
switch (Compatibility)
{
case TaskCompatibility::Native:
// this->Info.RootNode = fs->FileSystemRoots->GetChildren()[0];
break;
case TaskCompatibility::Linux:
// this->Info.RootNode = fs->FileSystemRoots->GetChildren()[1];
break;
case TaskCompatibility::Windows:
// this->Info.RootNode = fs->FileSystemRoots->GetChildren()[2];
break;
default:
assert(!"Invalid compatibility mode");
break;
}
/* FIXME */
this->Info.RootNode = fs->GetRoot(0);
if (this->Parent->Threads.size() == 0)
{
this->Parent->Info.Architecture = this->Info.Architecture;
this->Parent->Info.Compatibility = this->Info.Compatibility;
this->Parent->Info.RootNode = this->Info.RootNode;
}
// TODO: Is really a good idea to use the FPU in kernel mode?
this->FPU.mxcsr = 0b0001111110000000;
this->FPU.mxcsrmask = 0b1111111110111111;
this->FPU.fcw = 0b0000001100111111;
#ifdef DEBUG
#ifdef a64
debug("Thread EntryPoint: %#lx => RIP: %#lx",
this->EntryPoint, this->Registers.rip);
if (this->Parent->Security.ExecutionMode == TaskExecutionMode::User)
debug("Thread stack region is %#lx-%#lx (U) and rsp is %#lx",
this->Stack->GetStackBottom(), this->Stack->GetStackTop(),
this->Registers.rsp);
else
debug("Thread stack region is %#lx-%#lx (K) and rsp is %#lx",
this->Stack->GetStackBottom(), this->Stack->GetStackTop(),
this->Registers.rsp);
#elif defined(a32)
debug("Thread EntryPoint: %#lx => RIP: %#lx",
this->EntryPoint, this->Registers.eip);
if (Parent->Security.ExecutionMode == TaskExecutionMode::User)
debug("Thread stack region is %#lx-%#lx (U) and rsp is %#lx",
this->Stack->GetStackBottom(), this->Stack->GetStackTop(),
this->Registers.esp);
else
debug("Thread stack region is %#lx-%#lx (K) and rsp is %#lx",
this->Stack->GetStackBottom(), this->Stack->GetStackTop(),
this->Registers.esp);
#elif defined(aa64)
#endif
debug("Created %s thread \"%s\"(%d) in process \"%s\"(%d)",
this->Security.ExecutionMode == TaskExecutionMode::User ? "user" : "kernel",
this->Name, this->ID, this->Parent->Name,
this->Parent->ID);
#endif
this->AllocatedMemory += sizeof(TCB);
this->AllocatedMemory += strlen(this->Parent->Name) + 1;
this->AllocatedMemory += sizeof(Memory::StackGuard);
this->Info.SpawnTime = TimeManager->GetCounter();
this->Parent->Threads.push_back(this);
if (this->Parent->Threads.size() == 1 &&
this->Parent->State == Waiting &&
ThreadNotReady == false)
{
this->Parent->SetState(Ready);
debug("Setting process \"%s\"(%d) to ready",
this->Parent->Name, this->Parent->ID);
}
}
TCB::~TCB()
{
debug("- %#lx", this);
/* Remove us from the process list so we
don't get scheduled anymore */
this->Parent->Threads.erase(std::find(this->Parent->Threads.begin(),
this->Parent->Threads.end(),
this));
/* Free CPU Stack */
delete this->Stack;
/* Free Name */
delete[] this->Name;
debug("Thread \"%s\"(%d) destroyed",
this->Name, this->ID);
}
}