diff --git a/libmaxsi/c/hsrc/unistd.h b/libmaxsi/c/hsrc/unistd.h index 0b5d020c..32fe2209 100644 --- a/libmaxsi/c/hsrc/unistd.h +++ b/libmaxsi/c/hsrc/unistd.h @@ -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 diff --git a/libmaxsi/hsrc/process.h b/libmaxsi/hsrc/process.h index 74fde6e8..3e925b0c 100644 --- a/libmaxsi/hsrc/process.h +++ b/libmaxsi/hsrc/process.h @@ -33,6 +33,9 @@ namespace Maxsi void PrintPathFiles(); void Abort(); void Exit(int code); + pid_t Fork(); + pid_t GetPID(); + pid_t GetParentPID(); } } diff --git a/libmaxsi/memory.cpp b/libmaxsi/memory.cpp index bd4686d1..a2090879 100644 --- a/libmaxsi/memory.cpp +++ b/libmaxsi/memory.cpp @@ -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 ) diff --git a/libmaxsi/process.cpp b/libmaxsi/process.cpp index 6228b5ce..028a2efb 100644 --- a/libmaxsi/process.cpp +++ b/libmaxsi/process.cpp @@ -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(); + } } } diff --git a/sortix/Makefile b/sortix/Makefile index 1e71b4fb..6b6bd551 100644 --- a/sortix/Makefile +++ b/sortix/Makefile @@ -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 diff --git a/sortix/descriptors.cpp b/sortix/descriptors.cpp index cfc232b0..8243caea 100644 --- a/sortix/descriptors.cpp +++ b/sortix/descriptors.cpp @@ -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; + } } diff --git a/sortix/descriptors.h b/sortix/descriptors.h index 71a11504..8b02a005 100644 --- a/sortix/descriptors.h +++ b/sortix/descriptors.h @@ -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) diff --git a/sortix/interrupt.cpp b/sortix/interrupt.cpp index 764bb332..c1423e71 100644 --- a/sortix/interrupt.cpp +++ b/sortix/interrupt.cpp @@ -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!", diff --git a/sortix/kernel.cpp b/sortix/kernel.cpp index 6afa58e4..bb9967aa 100644 --- a/sortix/kernel.cpp +++ b/sortix/kernel.cpp @@ -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(); diff --git a/sortix/keyboard.cpp b/sortix/keyboard.cpp index e5aaab8e..2e3dc39e 100644 --- a/sortix/keyboard.cpp +++ b/sortix/keyboard.cpp @@ -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; } diff --git a/sortix/process.cpp b/sortix/process.cpp index d5f48ecc..de9e4bb4 100644 --- a/sortix/process.cpp +++ b/sortix/process.cpp @@ -25,6 +25,7 @@ #include "platform.h" #include #include +#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); } } diff --git a/sortix/process.h b/sortix/process.h index ae8f6513..97dd2209 100644 --- a/sortix/process.h +++ b/sortix/process.h @@ -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 diff --git a/sortix/scheduler.cpp b/sortix/scheduler.cpp index a3f1d707..f7cb66e7 100644 --- a/sortix/scheduler.cpp +++ b/sortix/scheduler.cpp @@ -17,510 +17,275 @@ You should have received a copy of the GNU General Public License along with Sortix. If not, see . - 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 #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("\n", Thread); - // TODO: What do we do with the result parameter? - Thread->~Thread(); - //Log::PrintF("\n", Thread); - delete Thread; - //Log::PrintF("\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("\n", Thread); -#else - #warning "This syscall is not supported on this arch" - while(true); -#endif - } - - void SysExitThread(CPU::InterruptRegisters* R) - { - //Log::PrintF("\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("\n", CurrentThread()); + hacksigintpending = true; } void SysSleep(size_t secs) { - //Log::PrintF("\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("\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; } } - diff --git a/sortix/scheduler.h b/sortix/scheduler.h index 9961a16f..e65dbe52 100644 --- a/sortix/scheduler.h +++ b/sortix/scheduler.h @@ -18,104 +18,31 @@ with Sortix. If not, see . 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 diff --git a/sortix/syscall.cpp b/sortix/syscall.cpp index be76510a..1b423778 100644 --- a/sortix/syscall.cpp +++ b/sortix/syscall.cpp @@ -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() diff --git a/sortix/syscallnum.h b/sortix/syscallnum.h index b84a3d2a..776ceed6 100644 --- a/sortix/syscallnum.h +++ b/sortix/syscallnum.h @@ -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 diff --git a/sortix/thread.cpp b/sortix/thread.cpp new file mode 100644 index 00000000..76edb29f --- /dev/null +++ b/sortix/thread.cpp @@ -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 . + + thread.cpp + Describes a thread belonging to a process. + +******************************************************************************/ + +#include "platform.h" +#include +#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(®isters, 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(®isters, &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); + } + } +} diff --git a/sortix/thread.h b/sortix/thread.h index 5f2dd91c..f5f5a10b 100644 --- a/sortix/thread.h +++ b/sortix/thread.h @@ -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 . + + 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 + diff --git a/sortix/time.cpp b/sortix/time.cpp index 05c3d856..49dd0ce8 100644 --- a/sortix/time.cpp +++ b/sortix/time.cpp @@ -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 @@ -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 diff --git a/sortix/time.h b/sortix/time.h index a78882c2..a6f33b3a 100644 --- a/sortix/time.h +++ b/sortix/time.h @@ -33,7 +33,7 @@ namespace Sortix void Init(); void OnIRQ0(CPU::InterruptRegisters* Registers); float GetTimeSinceBoot(); - intmax_t GetMilisecondsSinceBoot(); + uintmax_t MicrosecondsSinceBoot(); } } diff --git a/sortix/vga.cpp b/sortix/vga.cpp index 43afe66d..6fbe7a82 100644 --- a/sortix/vga.cpp +++ b/sortix/vga.cpp @@ -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; diff --git a/sortix/x86-family/memorymanagement.cpp b/sortix/x86-family/memorymanagement.cpp index 81a901ad..c1868793 100644 --- a/sortix/x86-family/memorymanagement.cpp +++ b/sortix/x86-family/memorymanagement.cpp @@ -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); diff --git a/sortix/x86/scheduler.cpp b/sortix/x86/scheduler.cpp new file mode 100644 index 00000000..ab4c4c9f --- /dev/null +++ b/sortix/x86/scheduler.cpp @@ -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 . + + 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); + } + } +} diff --git a/sortix/x86/thread.cpp b/sortix/x86/thread.cpp new file mode 100644 index 00000000..92884634 --- /dev/null +++ b/sortix/x86/thread.cpp @@ -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 . + + 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(®s); + } +} diff --git a/sortix/x86/x86.cpp b/sortix/x86/x86.cpp index da711f8b..f855ac0a 100644 --- a/sortix/x86/x86.cpp +++ b/sortix/x86/x86.cpp @@ -24,10 +24,22 @@ #include #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); + } } } diff --git a/sortix/x86/x86.h b/sortix/x86/x86.h index 7b099dfa..04c0d8ab 100644 --- a/sortix/x86/x86.h +++ b/sortix/x86/x86.h @@ -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; }; diff --git a/utils/Makefile b/utils/Makefile index 724b4997..0033c559 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -12,6 +12,7 @@ clear \ ls \ help \ uname \ +idle \ BINARIES:=$(addprefix $(INITRDDIR)/,$(LOCALBINARIES)) diff --git a/utils/help.cpp b/utils/help.cpp index b129d06d..bd64123a 100644 --- a/utils/help.cpp +++ b/utils/help.cpp @@ -1,4 +1,5 @@ #include +#include #include int main(int argc, char* argv[]) diff --git a/utils/idle.cpp b/utils/idle.cpp new file mode 100644 index 00000000..ddec8cea --- /dev/null +++ b/utils/idle.cpp @@ -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; +} diff --git a/utils/init.cpp b/utils/init.cpp index de059a90..f0481d7f 100644 --- a/utils/init.cpp +++ b/utils/init.cpp @@ -1,15 +1,38 @@ #include +#include #include +#include + +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); } diff --git a/utils/ls.cpp b/utils/ls.cpp index 5bda7289..c053b2b2 100644 --- a/utils/ls.cpp +++ b/utils/ls.cpp @@ -1,4 +1,5 @@ #include +#include #include int main(int argc, char* argv[]) diff --git a/utils/mxsh.cpp b/utils/mxsh.cpp index f6d150e2..9d9020b6 100644 --- a/utils/mxsh.cpp +++ b/utils/mxsh.cpp @@ -1,6 +1,8 @@ #include +#include #include #include +#include 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);