Implemented the fork() system call and what it needed to work properly.

This commit got completely out of control.

Added the fork(), getpid(), getppid(), sleep(), usleep() system calls, and
aliases in the Maxsi:: namespace.

Fixed a bug where zero-byte allocation would fail.

Worked on the DescriptorTable class which now works and can fork.

Got rid of some massive print-registers statements and replaced them with
the portable InterruptRegisters::LogRegisters() function.

Removed the SysExecuteOld function and replaced it with Process::Execute().

Rewrote the boot sequence in kernel.cpp such that it now loads the system
idle process 'idle' as PID 0, and the initization process 'init' as PID 1.

Rewrote the SIGINT hack.

Processes now maintain a family-tree structure and keep track of their
threads. PIDs are now allocated using a simple hack. Virtual memory
per-process can now be allocated using a simple hack. Processes can now be
forked. Fixed the Process::Execute function such that it now resets the
stack pointer to where the stack actually is - not just a magic value.
Removed the old and ugly Process::_endcodesection hack.

Rewrote the scheduler into a much cleaner and faster version. Debug code is
now moved to designated functions. The noop kernel-thread has been replaced
by a simple user-space infinite-loop program 'idle'.

The Thread class has been seperated from the Scheduler except in Scheduler-
related code. Thread::{Save,Load}Registers has been improved and has been
moved to $(CPU)/thread.cpp. Threads can now be forked. A new CreateThread
function creates threads properly and portably.

Added a MicrosecondsSinceBoot() function.

Fixed a crucial bug in MemoryManagement::Fork().

Added an 'idle' user-space program that is a noop infinite loop, which is
used by the scheduler when there is nothing to do.

Rewrote the 'init' program such that it now forks off a shell, instead of
becoming the shell.

Added the $$ (current PID) and $PPID (parent PPID) variables to the shell.
This commit is contained in:
Jonas 'Sortie' Termansen 2011-09-21 20:52:29 +02:00
parent c705bf39ff
commit 2afe9d1fd6
32 changed files with 1037 additions and 618 deletions

View File

@ -100,7 +100,6 @@ int fchown(int, uid_t, gid_t);
int fchownat(int, const char*, uid_t, gid_t, int);
int fdatasync(int);
int fexecve(int, char* const [], char* const []);
pid_t fork(void);
long fpathconf(int, int);
int fsync(int);
int ftruncate(int, off_t);
@ -116,8 +115,6 @@ int getlogin_r(char*, size_t);
int getopt(int, char* const [], const char*);
pid_t getpgid(pid_t);
pid_t getpgrp(void);
pid_t getpid(void);
pid_t getppid(void);
pid_t getsid(pid_t);
uid_t getuid(void);
int isatty(int);
@ -144,7 +141,6 @@ int setregid(gid_t, gid_t);
int setreuid(uid_t, uid_t);
pid_t setsid(void);
int setuid(uid_t);
unsigned sleep(unsigned);
void swab(const void* restrict, void* restrict, ssize_t);
int symlink(const char*, const char*);
int symlinkat(const char*, int, const char*);
@ -167,6 +163,14 @@ extern char* optarg;
extern int opterr, optind, optopt;
#endif
pid_t fork(void);
pid_t getpid(void);
pid_t getppid(void);
unsigned sleep(unsigned);
#if __POSIX_OBSOLETE <= 200112
int usleep(useconds_t useconds);
#endif
__END_DECLS
#endif

View File

@ -33,6 +33,9 @@ namespace Maxsi
void PrintPathFiles();
void Abort();
void Exit(int code);
pid_t Fork();
pid_t GetPID();
pid_t GetParentPID();
}
}

View File

@ -38,13 +38,13 @@
#define IsGoodChunkPosition(Chunk) ((uintptr_t) Wilderness + WildernessSize <= (uintptr_t) (Chunk) && (uintptr_t) (Chunk) <= (uintptr_t) HeapStart)
#define IsGoodChunkInfo(Chunk) ( ( (UnusedChunkFooter*) (((byte*) (Chunk)) + (Chunk)->Size - sizeof(UnusedChunkFooter)) )->Size == (Chunk)->Size )
#define IsGoodChunk(Chunk) (IsGoodChunkPosition(Chunk) && (Chunk)->Size >= 32 && IsGoodChunkPosition((uintptr_t) (Chunk) + (Chunk)->Size) && IsGoodChunkInfo(Chunk) )
#define IsGoodChunk(Chunk) (IsGoodChunkPosition(Chunk) && (Chunk)->Size >= 16 && IsGoodChunkPosition((uintptr_t) (Chunk) + (Chunk)->Size) && IsGoodChunkInfo(Chunk) )
#define IsGoodUnusedChunk(Chunk) ( IsGoodChunk(Chunk) && ( Chunk->NextUnused == NULL || IsGoodChunkPosition(Chunk->NextUnused) ) )
#define IsGoodUsedChunk(Chunk) ( IsGoodChunk(Chunk) && Chunk->Magic == Magic )
#ifdef PLATFORM_X64
#define IsGoodBinIndex(Index) ( 4 <= Index && Index < 32 )
#define IsGoodBinIndex(Index) ( 3 <= Index && Index < 32 )
#else
#define IsGoodBinIndex(Index) ( 5 <= Index && Index < 64 )
#define IsGoodBinIndex(Index) ( 4 <= Index && Index < 64 )
#endif
#define IsAligned(Value, Alignment) ( (Value & (Alignment-1)) == 0 )

View File

@ -32,6 +32,9 @@ namespace Maxsi
{
DEFN_SYSCALL3(int, SysExecute, 10, const char*, int, const char**);
DEFN_SYSCALL0_VOID(SysPrintPathFiles, 11);
DEFN_SYSCALL0(pid_t, SysFork, 12);
DEFN_SYSCALL0(pid_t, SysGetPID, 13);
DEFN_SYSCALL0(pid_t, SysGetParentPID, 14);
int Execute(const char* filepath, int argc, const char** argv)
{
@ -60,6 +63,21 @@ namespace Maxsi
}
extern "C" void exit(int code) { return Exit(code); }
DUAL_FUNCTION(pid_t, fork, Fork, ())
{
return SysFork();
}
DUAL_FUNCTION(pid_t, getpid, GetPID, ())
{
return SysGetPID();
}
DUAL_FUNCTION(pid_t, getppid, GetParentPID, ())
{
return SysGetParentPID();
}
}
}

View File

@ -31,6 +31,8 @@ ifdef X86FAMILY
$(CPU)/interrupt.o \
$(CPU)/gdt.o \
$(CPU)/syscall.o \
$(CPU)/thread.o \
$(CPU)/scheduler.o \
x86-family/x86-family.o
CPUFLAGS:=$(CPUFLAGS) -mno-mmx -mno-sse -mno-sse2 -mno-sse3 -mno-3dnow
endif

View File

@ -67,11 +67,7 @@ namespace Sortix
if ( newlistlength == 0 ) { newlistlength = 8; }
Device** newlist = new Device*[newlistlength];
if ( newlist == NULL )
{
// TODO: Set errno!
return -1;
}
if ( newlist == NULL ) { return -1; }
if ( devices != NULL )
{
@ -110,8 +106,25 @@ namespace Sortix
{
ASSERT(index < index);
ASSERT(devices[index] != NULL);
ASSERT(devices[index] != reserveddevideptr);
ASSERT(devices[index] == reserveddevideptr);
devices[index] = object;
}
bool DescriptorTable::Fork(DescriptorTable* forkinto)
{
Device** newlist = new Device*[numdevices];
if ( newlist == NULL ) { return false; }
Memory::Copy(newlist, devices, sizeof(Device*) * numdevices);
// TODO: Possibly deal with a potential O_CLOFORK!
ASSERT(forkinto->devices == NULL);
forkinto->devices = newlist;
forkinto->numdevices = numdevices;
return true;
}
}

View File

@ -44,6 +44,7 @@ namespace Sortix
int Reserve();
void Free(int index);
void UseReservation(int index, Device* object);
bool Fork(DescriptorTable* forkinto);
public:
inline Device* Get(int index)

View File

@ -34,7 +34,8 @@ namespace Sortix
{
namespace Interrupt
{
const bool debugexception = true;
const bool DEBUG_EXCEPTION = false;
const bool DEBUG_IRQ = false;
size_t numknownexceptions = 20;
const char* exceptions[] =
@ -62,15 +63,7 @@ namespace Sortix
const char* message = ( regs->int_no < numknownexceptions )
? exceptions[regs->int_no] : "Unknown";
if ( debugexception )
{
Log::PrintF("cs=0x%x, eax=0x%zx, ebx=0x%zx, ecx=0x%zx, "
"edx=0x%zx, esi=0x%zx, edi=0x%zx, esp=0x%zx, "
"useresp=0x%zx, test=0x%zx\n",
regs->cs, regs->eax, regs->ebx, regs->ecx,
regs->edx, regs->esi, regs->edi, regs->esp,
regs->useresp, regs->useresp);
}
if ( DEBUG_EXCEPTION ) { regs->LogRegisters(); Log::Print("\n"); }
// Halt and catch fire if we are the kernel.
if ( (regs->cs & (0x4-1)) == 0 )
@ -86,8 +79,7 @@ namespace Sortix
Sound::Mute();
const char* programname = "sh";
regs->ebx = (uint32_t) programname;
SysExecuteOld(regs);
Process::Execute(programname, regs);
return;
}
@ -109,6 +101,13 @@ namespace Sortix
// See http://wiki.osdev.org/PIC for details (section Spurious IRQs).
if ( regs->int_no == 32 + 7 || regs->int_no == 32 + 15 ) { return; }
if ( DEBUG_IRQ )
{
Log::PrintF("IRQ%u ", regs->int_no-32);
regs->LogRegisters();
Log::Print("\n");
}
if ( regs->int_no < 32 || 48 < regs->int_no )
{
PanicF("IRQ eax=%u, int_no=%u, err_code=%u, eip=%u!",

View File

@ -33,6 +33,8 @@
#include "keyboard.h"
#include "multiboot.h"
#include "memorymanagement.h"
#include "thread.cpp"
#include "process.h"
#include "scheduler.h"
#include "syscall.h"
#include "pci.h"
@ -237,38 +239,50 @@ namespace Sortix
// Initialize the scheduler.
Scheduler::Init();
Thread::Entry initstart = NULL;
// Create an address space for the first process.
addr_t addrspace = Memory::Fork();
// Use the new address space!
Memory::SwitchAddressSpace(addrspace);
// Create the first process!
Process* process = new Process(addrspace);
if ( process == 0 ) { Panic("kernel.cpp: Could not allocate the first process!"); }
// Set up the initial ram disk.
InitRD::Init(initrd, initrdsize);
const char* initname = "init";
size_t programsize = 0;
byte* program = InitRD::Open(initname, &programsize);
if ( program == NULL ) { PanicF("initrd did not contain '%s'", initname); }
// Alright, now the system's drivers are loaded and initialized. It is
// time to load the initial user-space programs and start execution of
// the actual operating system.
initstart = (Thread::Entry) ELF::Construct(process, program, programsize);
if ( initstart == NULL )
{
Panic("kernel.cpp: Could not construct ELF program");
}
byte* program;
size_t programsize;
// HACK: This should be determined from other information!
process->_endcodesection = 0x400000UL;
// Create an address space for the idle process.
addr_t idleaddrspace = Memory::Fork();
if ( !idleaddrspace ) { Panic("could not fork an idle process"); }
if ( Scheduler::CreateThread(process, initstart) == NULL )
{
Panic("Could not create a sample thread!");
}
// Create an address space for the initial process.
addr_t initaddrspace = Memory::Fork();
if ( !initaddrspace ) { Panic("could not fork an initial process"); }
// Create the system idle process.
Process* idle = new Process;
if ( !idle ) { Panic("could not allocate idle process"); }
idle->addrspace = idleaddrspace;
Memory::SwitchAddressSpace(idleaddrspace);
Scheduler::SetDummyThreadOwner(idle);
program = InitRD::Open("idle", &programsize);
if ( program == NULL ) { PanicF("initrd did not contain 'idle'"); }
addr_t idlestart = ELF::Construct(idle, program, programsize);
if ( !idlestart ) { Panic("could not construct ELF image for idle process"); }
Thread* idlethread = CreateThread(idlestart);
if ( !idlethread ) { Panic("could not create thread for the idle process"); }
Scheduler::SetIdleThread(idlethread);
// Create the initial process.
Process* init = new Process;
if ( !init ) { Panic("could not allocate init process"); }
init->addrspace = initaddrspace;
Memory::SwitchAddressSpace(initaddrspace);
Scheduler::SetDummyThreadOwner(init);
program = InitRD::Open("init", &programsize);
if ( program == NULL ) { PanicF("initrd did not contain 'init'"); }
addr_t initstart = ELF::Construct(init, program, programsize);
if ( !initstart ) { Panic("could not construct ELF image for init process"); }
Thread* initthread = CreateThread(initstart);
if ( !initthread ) { Panic("could not create thread for the init process"); }
// Lastly set up the timer driver and we are ready to run the OS.
Time::Init();

View File

@ -29,7 +29,6 @@
#include "panic.h"
#include "keyboard.h"
#include "interrupt.h"
#include "process.h"
#include "scheduler.h"
#include "syscall.h"
@ -688,7 +687,7 @@ namespace Sortix
if ( CodePoint == SIGINT )
{
SigInt();
Scheduler::SigIntHack();
return;
}

View File

@ -25,6 +25,7 @@
#include "platform.h"
#include <libmaxsi/memory.h>
#include <libmaxsi/string.h>
#include "thread.h"
#include "process.h"
#include "memorymanagement.h"
#include "initrd.h"
@ -51,12 +52,48 @@ namespace Sortix
return false;
}
Process::Process(addr_t addrspace)
ProcessSegment* ProcessSegment::Fork()
{
_addrspace = addrspace;
_endcodesection = 0x400000UL;
ProcessSegment* nextclone = NULL;
if ( next )
{
nextclone = next->Fork();
if ( nextclone == NULL ) { return NULL; }
}
ProcessSegment* clone = new ProcessSegment();
if ( clone == NULL )
{
while ( nextclone != NULL )
{
ProcessSegment* todelete = nextclone;
nextclone = nextclone->next;
delete todelete;
}
return NULL;
}
next->prev = nextclone;
clone->next = nextclone;
clone->position = position;
clone->size = size;
return clone;
}
Process::Process()
{
addrspace = 0;
segments = NULL;
sigint = false;
parent = NULL;
prevsibling = NULL;
nextsibling = NULL;
firstchild = NULL;
firstthread = NULL;
mmapfrom = 0x80000000UL;
pid = AllocatePID();
}
Process::~Process()
@ -83,10 +120,125 @@ namespace Sortix
segments = NULL;
}
int SysExecute(const char* programname)
Process* Process::Fork()
{
// TODO: Validate that filepath is a user-space readable string!
ASSERT(CurrentProcess() == this);
Process* clone = new Process;
if ( !clone ) { return NULL; }
ProcessSegment* clonesegments = NULL;
// Fork the segment list.
if ( segments )
{
clonesegments = segments->Fork();
if ( clonesegments == NULL ) { delete clone; return NULL; }
}
// Fork address-space here and copy memory somehow.
clone->addrspace = Memory::Fork();
if ( !clone->addrspace )
{
// Delete the segment list, since they are currently bogus.
ProcessSegment* tmp = clonesegments;
while ( tmp != NULL )
{
ProcessSegment* todelete = tmp;
tmp = tmp->next;
delete todelete;
}
delete clone; return NULL;
}
// Now it's too late to clean up here, if anything goes wrong, the
// cloned process should be queued for destruction.
clone->segments = clonesegments;
// Remember the relation to the child process.
clone->parent = this;
if ( firstchild )
{
firstchild->prevsibling = clone;
clone->nextsibling = firstchild;
firstchild = clone;
}
else
{
firstchild = clone;
}
// Fork the file descriptors.
if ( !descriptors.Fork(&clone->descriptors) )
{
Panic("No error handling when forking FDs fails!");
}
Thread* clonethreads = ForkThreads(clone);
if ( !clonethreads )
{
Panic("No error handling when forking threads fails!");
}
clone->firstthread = clonethreads;
// Copy variables.
clone->mmapfrom = mmapfrom;
// Now that the cloned process is fully created, we need to signal to
// its threads that they should insert themselves into the scheduler.
for ( Thread* tmp = clonethreads; tmp != NULL; tmp = tmp->nextsibling )
{
tmp->Ready();
}
return clone;
}
Thread* Process::ForkThreads(Process* processclone)
{
Thread* result = NULL;
Thread* tmpclone = NULL;
for ( Thread* tmp = firstthread; tmp != NULL; tmp = tmp->nextsibling )
{
Thread* clonethread = tmp->Fork();
if ( clonethread == NULL )
{
while ( tmpclone != NULL )
{
Thread* todelete = tmpclone;
tmpclone = tmpclone->prevsibling;
delete todelete;
}
return NULL;
}
clonethread->process = processclone;
if ( result == NULL ) { result = clonethread; }
if ( tmpclone != NULL )
{
tmpclone->nextsibling = clonethread;
clonethread->prevsibling = tmpclone;
}
tmpclone = clonethread;
}
return result;
}
void Process::ResetForExecute()
{
// TODO: Delete all threads and their stacks.
// TODO: Unmap any process memory segments.
}
int Process::Execute(const char* programname, CPU::InterruptRegisters* regs)
{
size_t programsize = 0;
byte* program = InitRD::Open(programname, &programsize);
if ( !program ) { return -1; }
@ -100,41 +252,70 @@ namespace Sortix
Panic("Couldn't create the shell process");
}
return SysExecute("sh");
return Execute("sh", regs);
}
// This is a hacky way to set up the thread!
CPU::InterruptRegisters* regs = Syscall::InterruptRegs();
// TODO: This may be an ugly hack!
// TODO: Move this to x86/process.cpp.
regs->eip = entry;
regs->useresp = 0x80000000UL;
regs->ebp = 0x80000000UL;
regs->useresp = CurrentThread()->stackpos + CurrentThread()->stacksize;
regs->ebp = CurrentThread()->stackpos + CurrentThread()->stacksize;
return 0;
}
void SysExecuteOld(CPU::InterruptRegisters* R)
int SysExecute(const char* programname)
{
#ifdef PLATFORM_X86
const char* programname = (const char*) R->ebx;
// TODO: Validate that filepath is a user-space readable string!
size_t programsize = 0;
byte* program = InitRD::Open(programname, &programsize);
if ( program == NULL ) { R->eax = -1; return; }
addr_t entry = ELF::Construct(CurrentProcess(), program, programsize);
if ( entry == 0 )
{
PanicF("Could not create process '%s'", programname);
}
// This is a hacky way to set up the thread!
R->eip = entry;
R->useresp = 0x80000000UL;
R->ebp = 0x80000000UL;
#endif
return Process::Execute(programname, Syscall::InterruptRegs());
}
pid_t SysFork()
{
// Prepare the state of the clone.
Syscall::SyscallRegs()->result = 0;
CurrentThread()->SaveRegisters(Syscall::InterruptRegs());
Process* clone = CurrentProcess()->Fork();
if ( !clone ) { return -1; }
return clone->pid;
}
pid_t SysGetPID()
{
return CurrentProcess()->pid;
}
pid_t SysGetParentPID()
{
Process* parent = CurrentProcess()->parent;
if ( !parent ) { return -1; }
return parent->pid;
}
pid_t nextpidtoallocate;
pid_t Process::AllocatePID()
{
return nextpidtoallocate++;
}
void Process::Init()
{
Syscall::Register(SYSCALL_EXEC, (void*) SysExecute);
Syscall::Register(SYSCALL_FORK, (void*) SysFork);
Syscall::Register(SYSCALL_GETPID, (void*) SysGetPID);
Syscall::Register(SYSCALL_GETPPID, (void*) SysGetParentPID);
nextpidtoallocate = 0;
}
addr_t Process::AllocVirtualAddr(size_t size)
{
return (mmapfrom -= size);
}
}

View File

@ -33,6 +33,8 @@ namespace Sortix
class Process;
struct ProcessSegment;
const size_t DEFAULT_STACK_SIZE = 64*1024;
struct ProcessSegment
{
public:
@ -45,21 +47,38 @@ namespace Sortix
size_t size;
public:
bool Intersects(ProcessSegment* segments);
bool Intersects(ProcessSegment* segments);
ProcessSegment* Fork();
};
class Process
{
public:
Process(addr_t addrspace);
Process();
~Process();
public:
static void Init();
static int Execute(const char* programname, CPU::InterruptRegisters* regs);
private:
addr_t _addrspace;
static pid_t AllocatePID();
public:
addr_t addrspace;
public:
pid_t pid;
public:
Process* parent;
Process* prevsibling;
Process* nextsibling;
Process* firstchild;
public:
Thread* firstthread;
public:
DescriptorTable descriptors;
@ -69,20 +88,32 @@ namespace Sortix
bool sigint;
public:
addr_t _endcodesection; // HACK
public:
addr_t GetAddressSpace() { return _addrspace; }
void ResetAddressSpace();
public:
bool IsSane() { return _addrspace != 0; }
bool IsSane() { return addrspace != 0; }
public:
Process* Fork();
private:
Thread* ForkThreads(Process* processclone);
public:
void ResetForExecute();
public:
inline size_t DefaultStackSize() { return DEFAULT_STACK_SIZE; }
private:
addr_t mmapfrom;
public:
addr_t AllocVirtualAddr(size_t size);
};
Process* CurrentProcess();
void SysExecuteOld(CPU::InterruptRegisters* R);
}
#endif

View File

@ -17,510 +17,275 @@
You should have received a copy of the GNU General Public License along
with Sortix. If not, see <http://www.gnu.org/licenses/>.
scheduler.h
Handles the creation and management of threads.
scheduler.cpp
Handles context switching between tasks and deciding when to execute what.
******************************************************************************/
#include "platform.h"
#include <libmaxsi/memory.h>
#include "panic.h"
#include "thread.h"
#include "process.h"
#include "time.h"
#include "scheduler.h"
#include "multiboot.h"
#include "memorymanagement.h"
#include "descriptor_tables.h"
#include "syscall.h"
#include "log.h"
#include "sound.h" // HACK
namespace Sortix
{
const bool LOG_SWITCHING = false;
// Internal forward-declarations.
namespace Scheduler
{
// This is a very small thread that does absoluting nothing!
char NoopThreadData[sizeof(Thread)];
Thread* NoopThread;
const size_t NoopThreadStackLength = 8;
size_t NoopThreadStack[NoopThreadStackLength];
void NoopFunction() { while ( true ) { } }
// Linked lists that implements a very simple scheduler.
Thread* currentThread;
Thread* firstRunnableThread;
Thread* firstUnrunnableThread;
Thread* firstSleeping;
size_t AllocatedThreadId;
void InitCPU();
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);
}
Thread::Thread(Process* process, size_t id, addr_t stack, size_t stackLength)
{
ASSERT(process != NULL);
ASSERT(process->IsSane());
_process = process;
_id = id;
_stack = stack;
_stackLength = stackLength;
_state = INFANT;
_prevThread = this;
_nextThread = this;
_inThisList = NULL;
_nextSleepingThread = NULL;
}
Thread::~Thread()
{
Unlink();
Page::Put(_stack);
}
void Thread::Unlink()
{
if ( _inThisList != NULL && *_inThisList == this )
{
*_inThisList = ( _nextThread != this ) ? _nextThread : NULL;
_inThisList = NULL;
}
if ( _nextThread != this ) { _prevThread->_nextThread = _nextThread; _nextThread->_prevThread = _prevThread; _prevThread = this; _nextThread = this; }
}
void Thread::Relink(Thread** list)
{
Unlink();
_inThisList = list;
if ( *list != NULL )
{
(*list)->_prevThread->_nextThread = this;
_prevThread = (*list)->_prevThread;
(*list)->_prevThread = this;
_nextThread = *list;
}
else
{
*list = this;
}
}
void Thread::SetState(State newState)
{
if ( newState == _state ) { return; }
if ( newState == RUNNABLE )
{
Relink(&Scheduler::firstRunnableThread);
}
else if ( ( newState == UNRUNNABLE ) /*&& ( State != UNRUNNABLE || State != WAITING )*/ )
{
Relink(&Scheduler::firstUnrunnableThread);
}
_state = newState;
}
void Thread::SaveRegisters(CPU::InterruptRegisters* Src)
{
#ifdef PLATFORM_X86
_registers.eip = Src->eip; _registers.useresp = Src->useresp; _registers.eax = Src->eax; _registers.ebx = Src->ebx; _registers.ecx = Src->ecx; _registers.edx = Src->edx; _registers.edi = Src->edi; _registers.esi = Src->esi; _registers.ebp = Src->ebp;
#else
#warning "No threads are available on this arch"
while(true);
#endif
}
void Thread::LoadRegisters(CPU::InterruptRegisters* Dest)
{
#ifdef PLATFORM_X86
Dest->eip = _registers.eip; Dest->useresp = _registers.useresp; Dest->eax = _registers.eax; Dest->ebx = _registers.ebx; Dest->ecx = _registers.ecx; Dest->edx = _registers.edx; Dest->edi = _registers.edi; Dest->esi = _registers.esi; Dest->ebp = _registers.ebp;
#else
#warning "No threads are available on this arch"
while(true);
#endif
}
void Thread::Sleep(uintmax_t miliseconds)
{
if ( miliseconds == 0 ) { return; }
_nextSleepingThread = NULL;
Thread* Thread = Scheduler::firstSleeping;
if ( Thread == NULL )
{
Scheduler::firstSleeping = this;
}
else if ( miliseconds < Thread->_sleepMilisecondsLeft )
{
Scheduler::firstSleeping = this;
_nextSleepingThread = Thread;
Thread->_sleepMilisecondsLeft -= miliseconds;
}
else
{
while ( true )
{
if ( Thread->_nextSleepingThread == NULL )
{
Thread->_nextSleepingThread = this; break;
}
else if ( miliseconds < Thread->_nextSleepingThread->_sleepMilisecondsLeft )
{
Thread->_nextSleepingThread->_sleepMilisecondsLeft -= miliseconds;
_nextSleepingThread = Thread->_nextSleepingThread;
Thread->_nextSleepingThread = this; break;
}
else
{
miliseconds -= Thread->_sleepMilisecondsLeft;
Thread = Thread->_nextSleepingThread;
}
}
}
_sleepMilisecondsLeft = miliseconds;
SetState(UNRUNNABLE);
}
bool sigintpending = false;
void SigInt() { sigintpending = true; }
namespace Scheduler
{
// Initializes the scheduling subsystem.
byte dummythreaddata[sizeof(Thread)];
Thread* dummythread = (Thread*) &dummythreaddata;
Thread* currentthread;
Thread* idlethread;
Thread* firstrunnablethread;
Thread* firstsleepingthread;
bool hacksigintpending = false;
void Init()
{
sigintpending = false;
currentThread = NULL;
firstRunnableThread = NULL;
firstUnrunnableThread = NULL;
firstSleeping = NULL;
AllocatedThreadId = 1;
// Create an address space for the idle process.
addr_t noopaddrspace = Memory::Fork();
// Create the noop process.
Process* noopprocess = new Process(noopaddrspace);
// Initialize the thread that does nothing.
NoopThread = new ((void*) NoopThreadData) Thread(noopprocess, 0, NULL, 0);
NoopThread->SetState(Thread::State::NOOP);
#ifdef PLATFORM_X86
NoopThread->_registers.useresp = (uint32_t) NoopThreadStack + NoopThreadStackLength;
NoopThread->_registers.ebp = (uint32_t) NoopThreadStack + NoopThreadStackLength;
NoopThread->_registers.eip = (uint32_t) &NoopFunction; // NoopFunction;
#else
#warning "No scheduler are available on this arch"
while(true);
#endif
// Allocate and set up a stack for the kernel to use during interrupts.
addr_t KernelStackPage = Page::Get();
if ( KernelStackPage == 0 ) { Panic("scheduler.cpp: could not allocate kernel interrupt stack for tss!"); }
uintptr_t MapTo = 0x80000000;
Memory::MapKernel((addr_t) KernelStackPage, MapTo);
Memory::InvalidatePage(KernelStackPage);
GDT::SetKernelStack((size_t*) (MapTo+4096));
// 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.
currentthread = dummythread;
firstrunnablethread = NULL;
idlethread = NULL;
hacksigintpending = false;
Syscall::Register(SYSCALL_SLEEP, (void*) SysSleep);
Syscall::Register(SYSCALL_USLEEP, (void*) SysUSleep);
InitCPU();
}
// 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 SetInitialProcess(Process* init)
{
dummythread->process = init;
}
// Once the init process is spawned and IRQ0 is enabled, this process
// simply awaits an IRQ0 and then we shall be scheduling.
void MainLoop()
{
// Simply wait for the next IRQ0 and then the OS will run.
while ( true ) { }
// 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);
}
Thread* CreateThread(Process* Process, Thread::Entry Start, void* Parameter1, void* Parameter2, size_t StackSize)
void Switch(CPU::InterruptRegisters* regs)
{
ASSERT(Process != NULL);
ASSERT(Process->IsSane());
ASSERT(Start != NULL);
LogBeginContextSwitch(currentthread, regs);
// The current default stack size is 4096 bytes.
if ( StackSize == SIZE_MAX ) { StackSize = 4096; }
if ( hacksigintpending ) { HandleSigIntHack(regs); }
// TODO: We only support stacks of up to one page!
if ( 4096 < StackSize ) { StackSize = 4096; }
WakeSleeping();
// Allocate a stack for this thread.
size_t StackLength = StackSize / sizeof(size_t);
addr_t PhysStack = Page::Get();
if ( PhysStack == 0 )
Thread* nextthread = PopNextThread();
if ( !nextthread ) { Panic("had no thread to switch to"); }
LogContextSwitch(currentthread, nextthread);
if ( nextthread == currentthread ) { return; }
currentthread->SaveRegisters(regs);
nextthread->LoadRegisters(regs);
addr_t newaddrspace = nextthread->process->addrspace;
Memory::SwitchAddressSpace(newaddrspace);
currentthread = nextthread;
LogEndContextSwitch(currentthread, 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 )
{
return NULL;
Log::PrintF("Switching from 0x%p", current);
state->LogRegisters();
Log::Print("\n");
}
}
// Create a new thread data structure.
Thread* thread = new Thread(Process, AllocatedThreadId++, PhysStack, StackLength);
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);
}
}
#ifndef PLATFORM_X86
#warning "No threads are available on this arch"
while(true);
#endif
#ifdef PLATFORM_X86
uintptr_t StackPos = 0x80000000UL;
uintptr_t MapTo = StackPos - 4096UL;
addr_t OldAddrSpace = Memory::SwitchAddressSpace(Process->GetAddressSpace());
Memory::MapUser(PhysStack, MapTo);
size_t* Stack = (size_t*) StackPos;
// Prepare the parameters for the entry function (C calling convention).
//Stack[StackLength - 1] = (size_t) 0xFACE; // Parameter2
Stack[-1] = (size_t) Parameter2; // Parameter2
Stack[-2] = (size_t) Parameter1; // Parameter1
Stack[-3] = (size_t) thread->GetId(); // This thread's id.
Stack[-4] = (size_t) 0x0; // Eip
thread->_registers.ebp = thread->_registers.useresp = (uint32_t) (StackPos - 4*sizeof(size_t)); // Point to the last word used on the stack.
thread->_registers.eip = (uint32_t) Start; // Point to our entry function.
// Mark the thread as running, which adds it to the scheduler's linked list.
thread->SetState(Thread::State::RUNNABLE);
// Avoid side effects by restoring the old address space.
Memory::SwitchAddressSpace(OldAddrSpace);
#endif
return thread;
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()
{
//Log::PrintF("PopNextThread(): currentThread = 0x%p, firstRunnableThread = 0x%p, firstRunnableThread->PrevThread = 0x%p, firstRunnableThread->NextThread = 0x%p\n", currentThread, firstRunnableThread, firstRunnableThread->PrevThread, firstRunnableThread->NextThread);
if ( firstRunnableThread == NULL )
{
return NoopThread;
}
else if ( currentThread == firstRunnableThread )
{
firstRunnableThread = firstRunnableThread->_nextThread;
}
return firstRunnableThread;
if ( !firstrunnablethread ) { return idlethread; }
Thread* result = firstrunnablethread;
firstrunnablethread = firstrunnablethread->schedulerlistnext;
return result;
}
void Switch(CPU::InterruptRegisters* R, uintmax_t TimePassed)
void SetThreadState(Thread* thread, Thread::State state)
{
//Log::PrintF("Scheduling while at eip=0x%p...", R->eip);
//Log::Print("\n");
if ( thread->state == state ) { return; }
if ( currentThread != NoopThread && currentThread->GetProcess() && sigintpending )
if ( thread->state == Thread::State::RUNNABLE )
{
#ifdef PLATFORM_X86
Sound::Mute();
const char* programname = "sh";
R->ebx = (uint32_t) programname;
SysExecuteOld(R);
sigintpending = false;
Log::Print("^C\n");
#else
#warning "Sigint is not available on this arch"
#endif
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;
}
WakeSleeping(TimePassed);
// Find the next thread to be run.
Thread* NextThread = PopNextThread();
//if ( NextThread == NoopThread ) { PanicF("Going to NoopThread! Noop=0x%p, First=0x%p -> 0x%p, Unrun=0x%p", NoopThread, firstRunnableThread, firstRunnableThread->NextThread, firstUnrunnableThread); }
// If the next thread happens to be the current one, simply do nothing.
if ( currentThread != NextThread )
// Insert the thread into the scheduler's carousel linked list.
if ( state == Thread::State::RUNNABLE )
{
// Save the hardware registers of the current thread.
if ( currentThread != NULL )
{
currentThread->SaveRegisters(R);
}
if ( LOG_SWITCHING && NextThread != NoopThread )
{
Log::PrintF("Switching from thread at 0x%p to thread at 0x%p\n", currentThread);
}
// If applicable, switch the virtual address space.
Memory::SwitchAddressSpace(NextThread->GetProcess()->GetAddressSpace());
currentThread = NextThread;
// Load the hardware registers of the next thread.
//Log::PrintF("Switching to thread at 0x%p\n", NextThread);
NextThread->LoadRegisters(R);
if ( firstrunnablethread == NULL ) { firstrunnablethread = thread; }
thread->schedulerlistprev = firstrunnablethread->schedulerlistprev;
thread->schedulerlistnext = firstrunnablethread;
firstrunnablethread->schedulerlistprev = thread;
thread->schedulerlistprev->schedulerlistnext = thread;
}
else
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 )
{
if ( LOG_SWITCHING )
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 )
{
//Log::PrintF("Staying in thread 0x%p\n", currentThread);
thread->nextsleepingthread = tmp->nextsleepingthread;
tmp->nextsleepingthread = thread;
return;
}
}
}
#ifdef PLATFORM_X86
void WakeSleeping()
{
uintmax_t now = Time::MicrosecondsSinceBoot();
// TODO: HACK: Find a more accurate way to test for kernel code.
if ( R->eip >= 0x400000UL )
while ( firstsleepingthread && firstsleepingthread->sleepuntil < now )
{
uint32_t RPL = 0x3;
// Jump into user-space!
R->ds = 0x20 | RPL; // Set the data segment and Requested Privilege Level.
R->cs = 0x18 | RPL; // Set the code segment and Requested Privilege Level.
R->ss = 0x20 | RPL; // Set the stack segment and Requested Privilege Level.
}
else
{
uint32_t RPL = 0x0;
// Jump into kernel-space!
R->ds = 0x10 | RPL; // Set the data segment and Requested Privilege Level.
R->cs = 0x08 | RPL; // Set the code segment and Requested Privilege Level.
R->ss = 0x10 | RPL; // Set the stack segment and Requested Privilege Level.
}
R->eflags |= 0x200; // Enable the enable interrupts flag in EFLAGS!
#else
#warning "No threads are available on this arch"
while(true);
#endif
//Log::PrintF("ds=0x%x, edi=0x%x, esi=0x%x, ebp=0x%x, esp=0x%x, ebx=0x%x, edx=0x%x, ecx=0x%x, eax=0x%x, int_no=0x%x, err_code=0x%x, eip=0x%x, cs=0x%x, eflags=0x%x, useresp=0x%x, ss=0x%x\n", R->ds, R->edi, R->esi, R->ebp, R->esp, R->ebx, R->edx, R->ecx, R->eax, R->int_no, R->err_code, R->eip, R->cs, R->eflags, R->useresp, R->ss);
#if 0
size_t* Stack = (size_t*) R->useresp;
// TODO: HACK: Currently ESP is not properly set after we return
// from the interrupt. So we call a helper function that restores
// it by storing it in EAX, then restoring EAX, and restoring EIP,
// then we should have returned successfully.
Stack[-1] = (size_t) &PrintRegistersAndDie; // R->eip;
Stack[-2] = (size_t) R->eax;
R->eax = (uint32_t) (Stack - 2);
R->eip = (uint32_t) &RestoreStack;
Log::PrintF("ds=0x%x, edi=0x%x, esi=0x%x, ebp=0x%x, esp=0x%x, ebx=0x%x, edx=0x%x, ecx=0x%x, eax=0x%x, int_no=0x%x, err_code=0x%x, eip=0x%x, cs=0x%x, eflags=0x%x, useresp=0x%x, ss=0x%x\n", R->ds, R->edi, R->esi, R->ebp, R->esp, R->ebx, R->edx, R->ecx, R->eax, R->int_no, R->err_code, R->eip, R->cs, R->eflags, R->useresp, R->ss);
#endif
//Log::PrintF("Stack = {0x%p, 0x%p, 0x%p, 0x%p, 0x%p, 0x%p, 0x%p, 0x%p, 0x%p, 0x%p, 0x%p, 0x%p, 0x%p, 0x%p, 0x%p, 0x%p}\n", Stack[0], Stack[1], Stack[2], Stack[3], Stack[4], Stack[5], Stack[6], Stack[7], Stack[8], Stack[9], Stack[10], Stack[11], Stack[12], Stack[13], Stack[14], Stack[15]);
//Log::PrintF(" resuming at eip=0x%p\n", R->eip);
}
void WakeSleeping(uintmax_t TimePassed)
{
while ( firstSleeping != NULL )
{
if ( TimePassed < firstSleeping->_sleepMilisecondsLeft ) { firstSleeping->_sleepMilisecondsLeft -= TimePassed; break; }
TimePassed -= firstSleeping->_sleepMilisecondsLeft;
firstSleeping->_sleepMilisecondsLeft = 0;
firstSleeping->SetState(Thread::State::RUNNABLE);
Thread* Next = firstSleeping->_nextSleepingThread;
firstSleeping->_nextSleepingThread = NULL;
firstSleeping = Next;
SetThreadState(firstsleepingthread, Thread::State::RUNNABLE);
firstsleepingthread = firstsleepingthread->nextsleepingthread;
}
}
void ExitThread(Thread* Thread, void* Result)
void HandleSigIntHack(CPU::InterruptRegisters* regs)
{
//Log::PrintF("<ExitedThread debug=\"1\" thread=\"%p\"/>\n", Thread);
// TODO: What do we do with the result parameter?
Thread->~Thread();
//Log::PrintF("<ExitedThread debug=\"2\" thread=\"%p\"/>\n", Thread);
delete Thread;
//Log::PrintF("<ExitedThread debug=\"3\" thread=\"%p\"/>\n", Thread);
if ( currentthread == idlethread ) { return; }
if ( Thread == currentThread ) { currentThread = NULL; }
hacksigintpending = false;
Log::PrintF("^C\n");
Process::Execute("sh", regs);
}
#define ASSERDDD(invariant) \
if ( unlikely(!(invariant)) ) \
{ \
Sortix::Log::PrintF("Assertion failure: %s:%u : %s %s\n", __FILE__, __LINE__, __PRETTY_FUNCTION__, #invariant); \
while ( true ) { } \
}
#define LOL(what) #what
void SysCreateThread(CPU::InterruptRegisters* R)
void SigIntHack()
{
#ifdef PLATFORM_X86
Thread* Thread = CreateThread(CurrentProcess(), (Thread::Entry) R->ebx, (void*) R->ecx, (void*) R->edx, (size_t) R->edi);
R->eax = (Thread != NULL) ? Thread->GetId() : 0;
//Log::PrintF("<CreatedThread id=\"%p\"/>\n", Thread);
#else
#warning "This syscall is not supported on this arch"
while(true);
#endif
}
void SysExitThread(CPU::InterruptRegisters* R)
{
//Log::PrintF("<ExitedThread id=\"%p\"/>\n", CurrentThread());
#ifdef PLATFORM_X86
ExitThread(CurrentThread(), (void*) R->ebx);
Switch(R, 0);
#else
#warning "This syscall is not supported on this arch"
while(true);
#endif
//Log::PrintF("<ExitedThread nextthread=\"%p\"/>\n", CurrentThread());
hacksigintpending = true;
}
void SysSleep(size_t secs)
{
//Log::PrintF("<SysSleep>\n");
intmax_t TimeToSleep = ((uintmax_t) secs) * 1000ULL;
if ( TimeToSleep == 0 ) { return; }
Thread* thread = currentthread;
uintmax_t timetosleep = ((uintmax_t) secs) * 1000ULL * 1000ULL;
if ( timetosleep == 0 ) { return; }
PutThreadToSleep(thread, timetosleep);
Syscall::Incomplete();
CurrentThread()->Sleep(TimeToSleep);
Switch(Syscall::InterruptRegs(), 0);
//Log::PrintF("</SysSleep>\n");
}
void SysUSleep(size_t usecs)
{
intmax_t TimeToSleep = ((uintmax_t) usecs) / 1000ULL;
if ( TimeToSleep == 0 ) { return; }
Thread* thread = currentthread;
uintmax_t timetosleep = usecs;
if ( timetosleep == 0 ) { return; }
PutThreadToSleep(thread, timetosleep);
Syscall::Incomplete();
CurrentThread()->Sleep(TimeToSleep);
Switch(Syscall::InterruptRegs(), 0);
}
}
Thread* CurrentThread()
{
return Scheduler::currentThread;
return Scheduler::currentthread;
}
Process* CurrentProcess()
{
if ( Scheduler::currentThread != NULL ) { return Scheduler::currentThread->GetProcess(); } else { return NULL; }
return Scheduler::currentthread->process;
}
}

View File

@ -18,104 +18,31 @@
with Sortix. If not, see <http://www.gnu.org/licenses/>.
scheduler.h
Handles the creation and management of threads.
Handles context switching between tasks and deciding when to execute what.
******************************************************************************/
#ifndef SORTIX_SCHEDULER_H
#define SORTIX_SCHEDULER_H
#include "process.h"
#include "descriptors.h"
#include "thread.h"
namespace Sortix
{
class Thread;
class Thread
{
public:
enum State { INFANT, RUNNABLE, UNRUNNABLE, NOOP };
typedef void* (*Entry)(void* Parameter);
public:
Thread(Process* process, size_t id, addr_t stack, size_t stackLength);
~Thread();
public:
size_t GetId() { return _id; }
Process* GetProcess() { return _process; }
private:
size_t _id;
addr_t _stack;
size_t _stackLength;
Process* _process;
State _state;
public:
uintmax_t _sleepMilisecondsLeft;
Thread* _nextSleepingThread;
public:
void Sleep(uintmax_t Miliseconds);
public:
Thread* _prevThread;
Thread* _nextThread;
Thread** _inThisList;
public:
void SaveRegisters(CPU::InterruptRegisters* Src);
void LoadRegisters(CPU::InterruptRegisters* Dest);
public:
CPU::InterruptRegisters _registers;
private:
void Relink(Thread** list);
void Unlink();
public:
void SetState(State NewState);
State GetState();
private:
bool _syscall;
public:
bool _sysParamsInited;
size_t _sysParams[16];
public:
void BeginSyscall(CPU::InterruptRegisters* currentRegisters);
void SysReturn(size_t result);
void SysReturnError(size_t result);
void OnSysReturn();
};
namespace Scheduler
{
void Init();
void Switch(CPU::InterruptRegisters* R, uintmax_t TimePassed);
SORTIX_NORETURN void MainLoop();
void WakeSleeping(uintmax_t TimePassed);
void MainLoop() SORTIX_NORETURN;
void Switch(CPU::InterruptRegisters* regs);
void SetIdleThread(Thread* thread);
void SetDummyThreadOwner(Process* init);
// Thread management
Thread* CreateThread(Process* Process, Thread::Entry Start, void* Parameter1 = NULL, void* Parameter2 = NULL, size_t StackSize = SIZE_MAX);
void ExitThread(Thread* Thread, void* Result = NULL);
// System Calls.
void SysCreateThread(CPU::InterruptRegisters* R);
void SysExitThread(CPU::InterruptRegisters* R);
void SetThreadState(Thread* thread, Thread::State state);
Thread::State GetThreadState(Thread* thread);
void PutThreadToSleep(Thread* thread, uintmax_t usecs);
void SigIntHack();
}
// Scheduling
Thread* CurrentThread();
// HACK
void SigInt();
}
#endif

View File

@ -75,7 +75,7 @@ namespace Sortix
CPU::InterruptRegisters* regs = InterruptRegs();
CurrentThread()->SaveRegisters(regs);
// TODO: Make the thread blocking if not already.
Scheduler::Switch(regs, 0);
Scheduler::Switch(regs);
}
CPU::InterruptRegisters* InterruptRegs()

View File

@ -37,7 +37,10 @@
#define SYSCALL_SET_FREQUENCY 9
#define SYSCALL_EXEC 10
#define SYSCALL_PRINT_PATH_FILES 11
#define SYSCALL_MAX_NUM 12 /* index of highest constant + 1 */
#define SYSCALL_FORK 12
#define SYSCALL_GETPID 13
#define SYSCALL_GETPPID 14
#define SYSCALL_MAX_NUM 15 /* index of highest constant + 1 */
#endif

145
sortix/thread.cpp Normal file
View File

@ -0,0 +1,145 @@
/******************************************************************************
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/>.
thread.cpp
Describes a thread belonging to a process.
******************************************************************************/
#include "platform.h"
#include <libmaxsi/memory.h>
#include "process.h"
#include "thread.h"
#include "scheduler.h"
#include "memorymanagement.h"
#include "time.h"
namespace Sortix
{
Thread::Thread()
{
id = 0; // TODO: Make a thread id.
process = NULL;
prevsibling = NULL;
nextsibling = NULL;
sleepuntil = 0;
nextsleepingthread = 0;
schedulerlist = NULL;
schedulerlistprev = NULL;
schedulerlistnext = NULL;
state = NONE;
Maxsi::Memory::Set(&registers, 0, sizeof(registers));
ready = false;
}
Thread::Thread(const Thread* forkfrom)
{
id = forkfrom->id;
process = NULL;
prevsibling = NULL;
nextsibling = NULL;
state = forkfrom->state;
sleepuntil = forkfrom->sleepuntil;
Maxsi::Memory::Copy(&registers, &forkfrom->registers, sizeof(registers));
ready = false;
stackpos = forkfrom->stackpos;
stacksize = forkfrom->stacksize;
}
Thread::~Thread()
{
ASSERT(CurrentProcess() == process);
Memory::UnmapRangeUser(stackpos, stacksize);
}
Thread* Thread::Fork()
{
ASSERT(ready);
Thread* clone = new Thread(this);
if ( !clone ) { return NULL; }
return clone;
}
void CreateThreadCPU(Thread* thread, addr_t entry);
Thread* CreateThread(addr_t entry, size_t stacksize)
{
Process* process = CurrentProcess();
if ( stacksize == 0 ) { stacksize = process->DefaultStackSize(); }
// TODO: Find some unused virtual address space of the needed size
// somewhere in the current process.
addr_t stackpos = process->AllocVirtualAddr(stacksize);
if ( !stackpos ) { return NULL; }
if ( !Memory::MapRangeUser(stackpos, stacksize) )
{
// TODO: Free the reserved virtual memory area.
return NULL;
}
Thread* thread = new Thread();
if ( !thread )
{
Memory::UnmapRangeUser(stackpos, stacksize);
// TODO: Free the reserved virtual memory area.
return NULL;
}
thread->stackpos = stackpos;
thread->stacksize = stacksize;
// Set up the thread state registers.
CreateThreadCPU(thread, entry);
// Create the family tree.
thread->process = process;
Thread* firsty = process->firstthread;
if ( firsty ) { firsty->prevsibling = thread; }
thread->nextsibling = firsty;
process->firstthread = thread;
thread->Ready();
Scheduler::SetThreadState(thread, Thread::State::RUNNABLE);
return thread;
}
void Thread::Ready()
{
if ( ready ) { return; }
ready = true;
if ( Time::MicrosecondsSinceBoot() < sleepuntil )
{
uintmax_t howlong = sleepuntil - Time::MicrosecondsSinceBoot();
Scheduler::PutThreadToSleep(this, howlong);
}
else if ( state == State::RUNNABLE )
{
state = State::NONE; // Since we are in no linked list.
Scheduler::SetThreadState(this, State::RUNNABLE);
}
}
}

View File

@ -1 +1,92 @@
#include "scheduler.h"
/******************************************************************************
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/>.
thread.h
Describes a thread belonging to a process.
******************************************************************************/
#ifndef SORTIX_THREAD_H
#define SORTIX_THREAD_H
namespace Sortix
{
class Process;
class Thread;
// Adds a thread to the current process.
Thread* CreateThread(addr_t entry, size_t stacksize = 0);
Thread* CurrentThread();
class Thread
{
friend Thread* CreateThread(addr_t entry, size_t stacksize);
public:
enum State { NONE, RUNNABLE, BLOCKING };
private:
Thread();
Thread(const Thread* forkfrom);
public:
~Thread();
public:
size_t id;
Process* process;
Thread* prevsibling;
Thread* nextsibling;
// These are some things used internally by the scheduler and should not be
// touched by anything but it. Consider it private.
public:
Thread** schedulerlist;
Thread* schedulerlistprev;
Thread* schedulerlistnext;
State state;
uintmax_t sleepuntil;
Thread* nextsleepingthread;
public:
addr_t stackpos;
size_t stacksize;
// After being created/forked, a thread is not inserted into the scheduler.
// Whenever the thread has been safely established within a process, then
// call Ready() to finalize the creation and insert it into the scheduler.
private:
bool ready;
public:
void Ready();
private:
CPU::InterruptRegisters registers;
public:
Thread* Fork();
void SaveRegisters(const CPU::InterruptRegisters* src);
void LoadRegisters(CPU::InterruptRegisters* dest);
};
}
#endif

View File

@ -26,8 +26,10 @@
#include "platform.h"
#include "time.h"
#include "interrupt.h"
#include "process.h"
#include "scheduler.h"
#include "log.h"
#include "sound.h"
#ifdef PLATFORM_SERIAL
#include <libmaxsi/keyboard.h>
@ -44,11 +46,16 @@ namespace Sortix
{
namespace Time
{
uintmax_t Ticks;
uintmax_t Miliseconds;
uintmax_t ticks;
uintmax_t microsecondssinceboot;
const uint32_t Frequency = 100; // 100 Hz
uintmax_t MicrosecondsSinceBoot()
{
return microsecondssinceboot;
}
void OnInt177(CPU::InterruptRegisters* Regs)
{
#ifdef PLATFORM_X86
@ -85,8 +92,8 @@ namespace Sortix
void Init()
{
// Initialize our variables.
Ticks = 0;
Miliseconds = 0;
ticks = 0;
microsecondssinceboot = 0;
// First, register our timer callback.
Interrupt::RegisterHandler(Interrupt::IRQ0, &OnIRQ0);
@ -123,7 +130,7 @@ namespace Sortix
if ( isEsc ) { isEsc = false; isEscDepress = false; continue; }
if ( c == '\e' ) { c = ESC; }
if ( c == ('\e' | (1<<7)) ) { c = ESC | DEPRESSED; }
if ( c == 3 ) { SigInt(); continue; }
if ( c == 3 ) { Scheduler::SigIntHack(); continue; }
if ( c == 127 ) { c = '\b'; }
if ( c & (1<<7) )
{
@ -136,11 +143,12 @@ namespace Sortix
UART::RenderVGA((VGA::Frame*) 0xB8000);
#endif
Ticks++;
ticks++;
microsecondssinceboot += (1000*1000)/Frequency;
// Let the scheduler switch to the next task.
// TODO: Let the scheduler know how long has passed.
Scheduler::Switch(Regs, 1000/Frequency);
Scheduler::Switch(Regs);
// TODO: There is a horrible bug that causes Sortix to only receive
// one IRQ0 on my laptop, but it works in virtual machines. But

View File

@ -33,7 +33,7 @@ namespace Sortix
void Init();
void OnIRQ0(CPU::InterruptRegisters* Registers);
float GetTimeSinceBoot();
intmax_t GetMilisecondsSinceBoot();
uintmax_t MicrosecondsSinceBoot();
}
}

View File

@ -28,6 +28,7 @@
#include "memorymanagement.h"
#include "scheduler.h"
#include "syscall.h"
#include "process.h"
using namespace Maxsi;
@ -75,7 +76,7 @@ namespace Sortix
if ( !page ) { return 0; }
Process* process = CurrentProcess();
addr_t mapto = Page::AlignUp(process->_endcodesection);
addr_t mapto = process->AllocVirtualAddr(0x1000UL);
UserFrame* userframe = (UserFrame*) mapto;
// TODO: Check if mapto collides with any other memory section!
@ -108,8 +109,6 @@ namespace Sortix
frame->physical = page;
frame->userframe = userframe;
process->_endcodesection = mapto + 0x1000UL;
return mapto;
}
@ -144,7 +143,7 @@ namespace Sortix
if ( currentframe->process != process )
{
Memory::SwitchAddressSpace(currentframe->process->GetAddressSpace());
Memory::SwitchAddressSpace(currentframe->process->addrspace);
}
// Remap the pages in the owning process.
@ -158,7 +157,7 @@ namespace Sortix
if ( currentframe->process != process )
{
Memory::SwitchAddressSpace(process->GetAddressSpace());
Memory::SwitchAddressSpace(process->addrspace);
}
currentframe->onscreen = false;

View File

@ -476,8 +476,10 @@ namespace Sortix
if ( level == 1 )
{
size_t offset = pmloffset * ENTRIES + pos;
// Determine the source page's address.
const void* src = (const void*) (pmloffset * 4096UL);
const void* src = (const void*) (offset * 4096UL);
// Determine the destination page's address.
void* dest = (void*) (FORKPML + level - 1);

52
sortix/x86/scheduler.cpp Normal file
View File

@ -0,0 +1,52 @@
/******************************************************************************
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/>.
x86/scheduler.cpp
CPU specific things related to the scheduler.
******************************************************************************/
#include "platform.h"
#include "scheduler.h"
#include "../memorymanagement.h"
#include "descriptor_tables.h"
namespace Sortix
{
namespace Scheduler
{
const size_t KERNEL_STACK_SIZE = 256UL * 1024UL;
const addr_t KERNEL_STACK_END = 0x80001000UL;
const addr_t KERNEL_STACK_START = KERNEL_STACK_END + KERNEL_STACK_SIZE;
void InitCPU()
{
// TODO: Prevent the kernel heap and other stuff from accidentally
// also allocating this virtual memory region!
if ( !Memory::MapRangeKernel(KERNEL_STACK_END, KERNEL_STACK_SIZE) )
{
PanicF("could not create kernel stack (%zx to %zx)",
KERNEL_STACK_END, KERNEL_STACK_START);
}
GDT::SetKernelStack((size_t*) KERNEL_STACK_START);
}
}
}

109
sortix/x86/thread.cpp Normal file
View File

@ -0,0 +1,109 @@
/******************************************************************************
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/>.
thread.cpp
x86 specific parts of thread.cpp.
******************************************************************************/
#include "platform.h"
#include "thread.h"
namespace Sortix
{
void Thread::SaveRegisters(const CPU::InterruptRegisters* src)
{
registers.eip = src->eip;
registers.useresp = src->useresp;
registers.eax = src->eax;
registers.ebx = src->ebx;
registers.ecx = src->ecx;
registers.edx = src->edx;
registers.edi = src->edi;
registers.esi = src->esi;
registers.ebp = src->ebp;
registers.cs = src->cs;
registers.ds = src->ds;
registers.ss = src->ss;
registers.eflags = src->eflags;
}
void Thread::LoadRegisters(CPU::InterruptRegisters* dest)
{
dest->eip = registers.eip;
dest->useresp = registers.useresp;
dest->eax = registers.eax;
dest->ebx = registers.ebx;
dest->ecx = registers.ecx;
dest->edx = registers.edx;
dest->edi = registers.edi;
dest->esi = registers.esi;
dest->ebp = registers.ebp;
dest->cs = registers.cs;
dest->ds = registers.ds;
dest->ss = registers.ss;
dest->eflags = registers.eflags;
}
const size_t FLAGS_CARRY = (1<<0);
const size_t FLAGS_RESERVED1 = (1<<1); /* read as one */
const size_t FLAGS_PARITY = (1<<2);
const size_t FLAGS_RESERVED2 = (1<<3);
const size_t FLAGS_AUX = (1<<4);
const size_t FLAGS_RESERVED3 = (1<<5);
const size_t FLAGS_ZERO = (1<<6);
const size_t FLAGS_SIGN = (1<<7);
const size_t FLAGS_TRAP = (1<<8);
const size_t FLAGS_INTERRUPT = (1<<9);
const size_t FLAGS_DIRECTION = (1<<10);
const size_t FLAGS_OVERFLOW = (1<<11);
const size_t FLAGS_IOPRIVLEVEL = (1<<12) | (1<<13);
const size_t FLAGS_NESTEDTASK = (1<<14);
const size_t FLAGS_RESERVED4 = (1<<15);
const size_t FLAGS_RESUME = (1<<16);
const size_t FLAGS_VIRTUAL8086 = (1<<17);
const size_t FLAGS_ALIGNCHECK = (1<<18);
const size_t FLAGS_VIRTINTR = (1<<19);
const size_t FLAGS_VIRTINTRPEND = (1<<20);
const size_t FLAGS_ID = (1<<21);
void CreateThreadCPU(Thread* thread, addr_t entry)
{
const uint32_t CS = 0x18;
const uint32_t DS = 0x20;
const uint32_t RPL = 0x3;
CPU::InterruptRegisters regs;
regs.eip = entry;
regs.useresp = thread->stackpos + thread->stacksize;
regs.eax = 0;
regs.ebx = 0;
regs.ecx = 0;
regs.edx = 0;
regs.edi = 0;
regs.esi = 0;
regs.ebp = thread->stackpos + thread->stacksize;
regs.cs = CS | RPL;
regs.ds = DS | RPL;
regs.ss = DS | RPL;
regs.eflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID;
thread->SaveRegisters(&regs);
}
}

View File

@ -24,10 +24,22 @@
#include <libmaxsi/platform.h>
#include "x86.h"
#include "log.h"
namespace Sortix
{
namespace X86
{
void InterruptRegisters::LogRegisters() const
{
Log::PrintF("[cr2=0x%zx,ds=0x%zx,edi=0x%zx,esi=0x%zx,ebp=0x%zx,"
"esp=0x%zx,ebx=0x%zx,edx=0x%zx,ecx=0x%zx,eax=0x%zx,"
"int_no=0x%zx,err_code=0x%zx,eip=0x%zx,cs=0x%zx,"
"eflags=0x%zx,useresp=0x%zx,ss=0x%zx]",
cr2, ds, edi, esi, ebp,
esp, ebx, edx, ecx, eax,
int_no, err_code, eip, cs,
eflags, useresp, ss);
}
}
}

View File

@ -38,13 +38,17 @@ namespace Sortix
uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // Pushed by pusha.
uint32_t int_no, err_code; // Interrupt number and error code (if applicable)
uint32_t eip, cs, eflags, useresp, ss; // Pushed by the processor automatically.
public:
void LogRegisters() const;
};
struct SyscallRegisters
{
uint32_t cr2; // For compabillity with below, may be removed soon.
uint32_t cr2; // For compabillity with above, may be removed soon.
uint32_t ds;
uint32_t di, si, bp, trash, b, d, c, a;
uint32_t di, si, bp, trash, b, d, c; union { uint32_t a; uint32_t result; };
uint32_t int_no, err_code; // Also compabillity.
uint32_t ip, cs, flags, sp, ss;
};

View File

@ -12,6 +12,7 @@ clear \
ls \
help \
uname \
idle \
BINARIES:=$(addprefix $(INITRDDIR)/,$(LOCALBINARIES))

View File

@ -1,4 +1,5 @@
#include <stdio.h>
#include <libmaxsi/platform.h>
#include <libmaxsi/process.h>
int main(int argc, char* argv[])

6
utils/idle.cpp Normal file
View File

@ -0,0 +1,6 @@
int main(int argc, char* argv[])
{
// It is crucial that this process never terminates and always is runnable.
while(true);
return 0;
}

View File

@ -1,15 +1,38 @@
#include <stdio.h>
#include <libmaxsi/platform.h>
#include <libmaxsi/process.h>
#include <libmaxsi/thread.cpp>
using namespace Maxsi;
int parent(pid_t childid)
{
// TODO: wait for child to finish.
while ( true ) { Thread::Sleep(100000); }
}
int child()
{
const char* programname = "sh";
const char* newargv[] = { programname };
Process::Execute(programname, 1, newargv);
return 1;
}
int main(int argc, char* argv[])
{
// Reset the terminal's color and the rest of it.
printf("\r\e[m\e[J");
const char* programname = "sh";
const char* newargv[] = { programname };
pid_t childpid = Process::Fork();
if ( childpid < 0 )
{
printf("init: unable to fork a child\n");
return 1;
}
Maxsi::Process::Execute(programname, 1, newargv);
return 1;
return ( childpid == 0 ) ? child() : parent(childpid);
}

View File

@ -1,4 +1,5 @@
#include <stdio.h>
#include <libmaxsi/platform.h>
#include <libmaxsi/process.h>
int main(int argc, char* argv[])

View File

@ -1,6 +1,8 @@
#include <stdio.h>
#include <libmaxsi/platform.h>
#include <libmaxsi/process.h>
#include <libmaxsi/sortix-keyboard.h>
#include <libmaxsi/string.h>
using namespace Maxsi;
@ -39,6 +41,9 @@ void command()
if ( command[0] == '\0' ) { return; }
if ( String::Compare(command, "$$") == 0 ) { printf("%u\n", Process::GetPID()); return; }
if ( String::Compare(command, "$PPID") == 0 ) { printf("%u\n", Process::GetParentPID()); return; }
// Replace the current process with another process image.
Process::Execute(command, 0, NULL);