From 2afe9d1fd63f04efdb9b4a8c0db3004e9411af9d Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Wed, 21 Sep 2011 20:52:29 +0200 Subject: [PATCH] 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. --- libmaxsi/c/hsrc/unistd.h | 12 +- libmaxsi/hsrc/process.h | 3 + libmaxsi/memory.cpp | 6 +- libmaxsi/process.cpp | 18 + sortix/Makefile | 2 + sortix/descriptors.cpp | 25 +- sortix/descriptors.h | 1 + sortix/interrupt.cpp | 23 +- sortix/kernel.cpp | 68 +-- sortix/keyboard.cpp | 3 +- sortix/process.cpp | 233 ++++++++-- sortix/process.h | 51 ++- sortix/scheduler.cpp | 605 ++++++++----------------- sortix/scheduler.h | 95 +--- sortix/syscall.cpp | 2 +- sortix/syscallnum.h | 5 +- sortix/thread.cpp | 145 ++++++ sortix/thread.h | 93 +++- sortix/time.cpp | 22 +- sortix/time.h | 2 +- sortix/vga.cpp | 9 +- sortix/x86-family/memorymanagement.cpp | 4 +- sortix/x86/scheduler.cpp | 52 +++ sortix/x86/thread.cpp | 109 +++++ sortix/x86/x86.cpp | 12 + sortix/x86/x86.h | 8 +- utils/Makefile | 1 + utils/help.cpp | 1 + utils/idle.cpp | 6 + utils/init.cpp | 33 +- utils/ls.cpp | 1 + utils/mxsh.cpp | 5 + 32 files changed, 1037 insertions(+), 618 deletions(-) create mode 100644 sortix/thread.cpp create mode 100644 sortix/x86/scheduler.cpp create mode 100644 sortix/x86/thread.cpp create mode 100644 utils/idle.cpp 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);