diff --git a/libmaxsi/error.cpp b/libmaxsi/error.cpp index 9b6fbc21..307ff24e 100644 --- a/libmaxsi/error.cpp +++ b/libmaxsi/error.cpp @@ -1,6 +1,6 @@ /****************************************************************************** - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011, 2012. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of LibMaxsi. @@ -37,12 +37,9 @@ extern "C" { int global_errno = 0; } extern "C" { errno_location_func_t errno_location_func = NULL; } #ifndef SORTIX_KERNEL -DEFN_SYSCALL1(int, SysRegisterErrno, SYSCALL_REGISTER_ERRNO, int*); - extern "C" void init_error_functions() { global_errno = 0; - SysRegisterErrno(&global_errno); } #endif diff --git a/libmaxsi/include/libmaxsi/signal.h b/libmaxsi/include/libmaxsi/signal.h deleted file mode 100644 index e0b8e760..00000000 --- a/libmaxsi/include/libmaxsi/signal.h +++ /dev/null @@ -1,42 +0,0 @@ -/****************************************************************************** - - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. - - This file is part of LibMaxsi. - - LibMaxsi is free software: you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) - any later version. - - LibMaxsi 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 Lesser General Public License for - more details. - - You should have received a copy of the GNU Lesser General Public License - along with LibMaxsi. If not, see . - - signal.h - Handles the good old unix signals. - -******************************************************************************/ - -#ifndef LIBMAXSI_SIGNAL_H -#define LIBMAXSI_SIGNAL_H - -#include "signalnum.h" - -namespace Maxsi -{ - namespace Signal - { - typedef void (*Handler)(int); - - void Init(); - Handler RegisterHandler(int signum, Handler handler); - } -} - -#endif - diff --git a/libmaxsi/include/libmaxsi/signalnum.h b/libmaxsi/include/libmaxsi/signalnum.h deleted file mode 100644 index 5dc0085f..00000000 --- a/libmaxsi/include/libmaxsi/signalnum.h +++ /dev/null @@ -1,71 +0,0 @@ -/****************************************************************************** - - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. - - This file is part of LibMaxsi. - - LibMaxsi is free software: you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) - any later version. - - LibMaxsi 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 Lesser General Public License for - more details. - - You should have received a copy of the GNU Lesser General Public License - along with LibMaxsi. If not, see . - - signalnum.h - Declares the signal-related constants. - -******************************************************************************/ - -#ifndef LIBMAXSI_SIGNALNUM_H -#define LIBMAXSI_SIGNALNUM_H - -namespace Maxsi -{ - namespace Signal - { - const int HUP = 1; /* Hangup */ - const int INT = 2; /* Interrupt */ - const int QUIT = 3; /* Quit */ - const int ILL = 4; /* Illegal Instruction */ - const int TRAP = 5; /* Trace/Breakpoint Trap */ - const int ABRT = 6; /* Abort */ - const int EMT = 7; /* Emulation Trap */ - const int FPE = 8; /* Arithmetic Exception */ - const int KILL = 9; /* Killed */ - const int BUS = 10; /* Bus Error */ - const int SEGV = 11; /* Segmentation Fault */ - const int SYS = 12; /* Bad System Call */ - const int PIPE = 13; /* Broken Pipe */ - const int ALRM = 14; /* Alarm Clock */ - const int TERM = 15; /* Terminated */ - const int USR1 = 16; /* User Signal 1 */ - const int USR2 = 17; /* User Signal 2 */ - const int CHLD = 18; /* Child Status */ - const int PWR = 19; /* Power Fail/Restart */ - const int WINCH = 20; /* Window Size Change */ - const int URG = 21; /* Urgent Socket Condition */ - const int STOP = 23; /* Stopped (signal) */ - const int TSTP = 24; /* Stopped (user) */ - const int CONT = 25; /* Continued */ - const int TTIN = 26; /* Stopped (tty input) */ - const int TTOU = 27; /* Stopped (tty output) */ - const int VTALRM = 28; /* Virtual Timer Expired */ - const int XCPU = 30; /* CPU time limit exceeded */ - const int XFSZ = 31; /* File size limit exceeded */ - const int WAITING = 32; /* All LWPs blocked */ - const int LWP = 33; /* Virtual Interprocessor Interrupt for Threads Library */ - const int AIO = 34; /* Asynchronous I/O */ - const int NUMSIGNALS = 35; - - typedef void (*Handler)(int); - } -} - -#endif - diff --git a/libmaxsi/include/libmaxsi/syscall.h b/libmaxsi/include/libmaxsi/syscall.h index adca9c63..71c4eeed 100644 --- a/libmaxsi/include/libmaxsi/syscall.h +++ b/libmaxsi/include/libmaxsi/syscall.h @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of LibMaxsi. @@ -11,8 +11,8 @@ LibMaxsi 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 Lesser General Public License for - more details. + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. You should have received a copy of the GNU Lesser General Public License along with LibMaxsi. If not, see . @@ -20,7 +20,7 @@ syscall.h Assembly stubs for declaring system calls in libmaxsi. -******************************************************************************/ +*******************************************************************************/ #ifdef SORTIX_KERNEL #warning === @@ -32,6 +32,7 @@ #define LIBMAXSI_SYSCALL_H #include +#include namespace Maxsi { @@ -48,7 +49,10 @@ namespace Maxsi inline type fn() \ { \ type a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ return a; \ } @@ -56,7 +60,10 @@ namespace Maxsi inline type fn(P1 p1) \ { \ type a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num), "b" ((size_t)p1)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ return a; \ } @@ -64,7 +71,10 @@ namespace Maxsi inline type fn(P1 p1, P2 p2) \ { \ type a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num), "b" ((size_t)p1), "c" ((size_t)p2)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ return a; \ } @@ -72,7 +82,10 @@ namespace Maxsi inline type fn(P1 p1, P2 p2, P3 p3) \ { \ type a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num), "b" ((size_t)p1), "c" ((size_t)p2), "d" ((size_t)p3)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ return a; \ } @@ -80,15 +93,21 @@ namespace Maxsi inline type fn(P1 p1, P2 p2, P3 p3, P4 p4) \ { \ type a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num), "b" ((size_t)p1), "c" ((size_t)p2), "d" ((size_t)p3), "D" ((size_t)p4)); \ - return a; \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ + return a; \ } #define DEFN_SYSCALL5(type, fn, num, P1, P2, P3, P4, P5) \ inline type fn(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) \ { \ type a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num), "b" ((size_t)p1), "c" ((size_t)p2), "d" ((size_t)p3), "D" ((size_t)p4), "S" ((size_t)p5)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ return a; \ } @@ -96,14 +115,20 @@ namespace Maxsi inline void fn() \ { \ size_t a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ } #define DEFN_SYSCALL1_VOID(fn, num, P1) \ inline void fn(P1 p1) \ { \ size_t a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num), "b" ((size_t)p1)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ } #define DEFN_SYSCALL2_VOID(fn, num, P1, P2) \ @@ -111,27 +136,38 @@ namespace Maxsi { \ size_t a; \ asm volatile("int $0x80" : "=a" (a) : "0" (num), "b" ((size_t)p1), "c" ((size_t)p2)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ } #define DEFN_SYSCALL3_VOID(fn, num, P1, P2, P3) \ inline void fn(P1 p1, P2 p2, P3 p3) \ { \ size_t a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num), "b" ((size_t)p1), "c" ((size_t)p2), "d" ((size_t)p3)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ } #define DEFN_SYSCALL4_VOID(fn, num, P1, P2, P3, P4) \ inline void fn(P1 p1, P2 p2, P3 p3, P4 p4) \ { \ size_t a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num), "b" ((size_t)p1), "c" ((size_t)p2), "d" ((size_t)p3), "D" ((size_t)p4)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ } #define DEFN_SYSCALL5_VOID(fn, num, P1, P2, P3, P4, P5) \ inline void fn(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) \ { \ size_t a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num), "b" ((size_t)p1), "c" ((size_t)p2), "d" ((size_t)p3), "D" ((size_t)p4), "S" ((size_t)p5)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ } #else @@ -143,7 +179,10 @@ namespace Maxsi type fn() \ { \ type a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ return a; \ } @@ -151,7 +190,10 @@ namespace Maxsi type fn(P1) \ { \ type a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ return a; \ } @@ -159,7 +201,10 @@ namespace Maxsi type fn(P1, P2) \ { \ type a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ return a; \ } @@ -167,7 +212,10 @@ namespace Maxsi type fn(P1, P2, P3) \ { \ type a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ return a; \ } @@ -175,7 +223,10 @@ namespace Maxsi type fn(P1, P2, P3, P4) \ { \ type a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ return a; \ } @@ -183,7 +234,10 @@ namespace Maxsi type fn(P1, P2, P3, P4, P5) \ { \ type a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ return a; \ } @@ -191,42 +245,60 @@ namespace Maxsi void fn() \ { \ size_t a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ } #define DEFN_SYSCALL1_VOID(fn, num, P1) \ void fn(P1) \ { \ size_t a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ } #define DEFN_SYSCALL2_VOID(fn, num, P1, P2) \ void fn(P1, P2) \ { \ size_t a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ } #define DEFN_SYSCALL3_VOID(fn, num, P1, P2, P3) \ void fn(P1, P2, P3) \ { \ size_t a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ } #define DEFN_SYSCALL4_VOID(fn, num, P1, P2, P3, P4) \ void fn(P1, P2, P3, P4) \ { \ size_t a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ } #define DEFN_SYSCALL5_VOID(fn, num, P1, P2, P3, P4, P5) \ void fn(P1, P2, P3, P4, P5) \ { \ size_t a; \ + int reterrno; \ asm volatile("int $0x80" : "=a" (a) : "0" (num)); \ + asm volatile("movl %%edx, %0" : "=r"(reterrno)); \ + if ( reterrno ) { errno = reterrno; } \ } #endif diff --git a/libmaxsi/include/signal.h b/libmaxsi/include/signal.h index 80b76f2d..af317428 100644 --- a/libmaxsi/include/signal.h +++ b/libmaxsi/include/signal.h @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of LibMaxsi. @@ -11,8 +11,8 @@ LibMaxsi 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 Lesser General Public License for - more details. + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. You should have received a copy of the GNU Lesser General Public License along with LibMaxsi. If not, see . @@ -20,7 +20,7 @@ signal.h Signals. -******************************************************************************/ +*******************************************************************************/ /* TODO: This does not fully implement POSIX 2008-1 yet! */ @@ -28,42 +28,10 @@ #define _SIGNAL_H 1 #include +#include __BEGIN_DECLS -#define SIGHUP 1 /* Hangup */ -#define SIGINT 2 /* Interrupt */ -#define SIGQUIT 3 /* Quit */ -#define SIGILL 4 /* Illegal Instruction */ -#define SIGTRAP 5 /* Trace/Breakpoint Trap */ -#define SIGABRT 6 /* Abort */ -#define SIGEMT 7 /* Emulation Trap */ -#define SIGFPE 8 /* Arithmetic Exception */ -#define SIGKILL 9 /* Killed */ -#define SIGBUS 10 /* Bus Error */ -#define SIGSEGV 11 /* Segmentation Fault */ -#define SIGSYS 12 /* Bad System Call */ -#define SIGPIPE 13 /* Broken Pipe */ -#define SIGALRM 14 /* Alarm Clock */ -#define SIGTERM 15 /* Terminated */ -#define SIGUSR1 16 /* User Signal 1 */ -#define SIGUSR2 17 /* User Signal 2 */ -#define SIGCHLD 18 /* Child Status */ -#define SIGPWR 19 /* Power Fail/Restart */ -#define SIGWINCH 20 /* Window Size Change */ -#define SIGURG 21 /* Urgent Socket Condition */ -#define SIGSTOP 23 /* Stopped (signal) */ -#define SIGTSTP 24 /* Stopped (user) */ -#define SIGCONT 25 /* Continued */ -#define SIGTTIN 26 /* Stopped (tty input) */ -#define SIGTTOU 27 /* Stopped (tty output) */ -#define SIGVTALRM 28 /* Virtual Timer Expired */ -#define SIGXCPU 30 /* CPU time limit exceeded */ -#define SIGXFSZ 31 /* File size limit exceeded */ -#define SIGWAITING 32 /* All LWPs blocked */ -#define SIGLWP 33 /* Virtual Interprocessor Interrupt for Threads Library */ -#define SIGAIO 34 /* Asynchronous I/O */ - @include(pid_t.h); typedef void (*sighandler_t)(int); diff --git a/libmaxsi/init.cpp b/libmaxsi/init.cpp index dd742aed..d8b96920 100644 --- a/libmaxsi/init.cpp +++ b/libmaxsi/init.cpp @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of LibMaxsi. @@ -11,20 +11,19 @@ LibMaxsi 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 Lesser General Public License for - more details. + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. You should have received a copy of the GNU Lesser General Public License along with LibMaxsi. If not, see . init.cpp - Initializes the process by setting up the heap, signal handling, - static memory and other useful things. + Initializes the process by setting up the heap, signal handling, static + memory and other useful things. -******************************************************************************/ +*******************************************************************************/ #include -#include #include #include #include @@ -36,6 +35,7 @@ namespace Maxsi extern "C" void init_error_functions(); extern "C" void init_stdio(); + extern "C" void init_signal(); extern "C" void initialize_standard_library(int argc, char* argv[]) { @@ -46,7 +46,7 @@ namespace Maxsi init_error_functions(); // It's probably best to initialize the Unix signals early on. - Signal::Init(); + init_signal(); // Initialize the dynamic heap. Memory::Init(); diff --git a/libmaxsi/signal.cpp b/libmaxsi/signal.cpp index 5feb3841..19bf3ad3 100644 --- a/libmaxsi/signal.cpp +++ b/libmaxsi/signal.cpp @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011, 2012. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of LibMaxsi. @@ -11,8 +11,8 @@ LibMaxsi 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 Lesser General Public License for - more details. + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. You should have received a copy of the GNU Lesser General Public License along with LibMaxsi. If not, see . @@ -26,7 +26,6 @@ #include #include #include -#include #include #include @@ -34,6 +33,8 @@ namespace Maxsi { namespace Signal { + typedef void (*Handler)(int); + void Core(int signum) { Process::Exit(128 + signum); @@ -41,33 +42,33 @@ namespace Maxsi extern "C" void SIG_DFL(int signum) { - if ( signum == Signal::HUP ) { Process::Exit(128 + signum); } else - if ( signum == Signal::INT ) { Process::Exit(128 + signum); } else - if ( signum == Signal::QUIT ) { Core(signum); } else - if ( signum == Signal::TRAP ) { Core(signum); } else - if ( signum == Signal::ABRT ) { Core(signum); } else - if ( signum == Signal::EMT ) { Core(signum); } else - if ( signum == Signal::FPE ) { Core(signum); } else - if ( signum == Signal::KILL ) { Process::Exit(128 + signum); } else - if ( signum == Signal::BUS ) { Core(signum); } else - if ( signum == Signal::SEGV ) { Core(signum); } else - if ( signum == Signal::SYS ) { Core(signum); } else - if ( signum == Signal::PIPE ) { Process::Exit(128 + signum); } else - if ( signum == Signal::ALRM ) { Process::Exit(128 + signum); } else - if ( signum == Signal::TERM ) { Process::Exit(128 + signum); } else - if ( signum == Signal::USR1 ) { Process::Exit(128 + signum); } else - if ( signum == Signal::USR2 ) { Process::Exit(128 + signum); } else - if ( signum == Signal::CHLD ) { /* Ignore this signal. */ } else - if ( signum == Signal::PWR ) { /* Ignore this signal. */ } else - if ( signum == Signal::WINCH ) { /* Ignore this signal. */ } else - if ( signum == Signal::URG ) { /* Ignore this signal. */ } else - if ( signum == Signal::CONT ) { /* Ignore this signal. */ } else - if ( signum == Signal::VTALRM ) { /* Ignore this signal. */ } else - if ( signum == Signal::XCPU ) { Core(signum); } else - if ( signum == Signal::XFSZ ) { Core(signum); } else - if ( signum == Signal::WAITING ) { /* Ignore this signal. */ } else - if ( signum == Signal::LWP ) { /* Ignore this signal. */ } else - if ( signum == Signal::AIO ) { /* Ignore this signal. */ } else + if ( signum == SIGHUP ) { Process::Exit(128 + signum); } else + if ( signum == SIGINT ) { Process::Exit(128 + signum); } else + if ( signum == SIGQUIT ) { Core(signum); } else + if ( signum == SIGTRAP ) { Core(signum); } else + if ( signum == SIGABRT ) { Core(signum); } else + if ( signum == SIGEMT ) { Core(signum); } else + if ( signum == SIGFPE ) { Core(signum); } else + if ( signum == SIGKILL ) { Process::Exit(128 + signum); } else + if ( signum == SIGBUS ) { Core(signum); } else + if ( signum == SIGSEGV ) { Core(signum); } else + if ( signum == SIGSYS ) { Core(signum); } else + if ( signum == SIGPIPE ) { Process::Exit(128 + signum); } else + if ( signum == SIGALRM ) { Process::Exit(128 + signum); } else + if ( signum == SIGTERM ) { Process::Exit(128 + signum); } else + if ( signum == SIGUSR1 ) { Process::Exit(128 + signum); } else + if ( signum == SIGUSR2 ) { Process::Exit(128 + signum); } else + if ( signum == SIGCHLD ) { /* Ignore this signal. */ } else + if ( signum == SIGPWR ) { /* Ignore this signal. */ } else + if ( signum == SIGWINCH ) { /* Ignore this signal. */ } else + if ( signum == SIGURG ) { /* Ignore this signal. */ } else + if ( signum == SIGCONT ) { /* Ignore this signal. */ } else + if ( signum == SIGVTALRM ) { /* Ignore this signal. */ } else + if ( signum == SIGXCPU ) { Core(signum); } else + if ( signum == SIGXFSZ ) { Core(signum); } else + if ( signum == SIGWAITING ) { /* Ignore this signal. */ } else + if ( signum == SIGLWP ) { /* Ignore this signal. */ } else + if ( signum == SIGAIO ) { /* Ignore this signal. */ } else { /* Ignore this signal. */ } } @@ -94,10 +95,10 @@ namespace Maxsi } DEFN_SYSCALL1_VOID(SysRegisterSignalHandler, SYSCALL_REGISTER_SIGNAL_HANDLER, sighandler_t); - DEFN_SYSCALL0_VOID(SysSigReturn, SYSCALL_SIGRETURN); DEFN_SYSCALL2(int, SysKill, SYSCALL_KILL, pid_t, int); + DEFN_SYSCALL1(int, SysRaise, SYSCALL_RAISE, int); - void Init() + extern "C" void init_signal() { for ( int i = 0; i < MAX_SIGNALS; i++ ) { @@ -126,7 +127,7 @@ namespace Maxsi extern "C" int raise(int signum) { - kill(getpid(), signum); + return SysRaise(signum); } } } diff --git a/libmaxsi/x64/signal.s b/libmaxsi/x64/signal.s index 5d4bd395..6c15df82 100644 --- a/libmaxsi/x64/signal.s +++ b/libmaxsi/x64/signal.s @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of LibMaxsi. @@ -11,8 +11,8 @@ LibMaxsi 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 Lesser General Public License for - more details. + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. You should have received a copy of the GNU Lesser General Public License along with LibMaxsi. If not, see . @@ -32,7 +32,5 @@ SignalHandlerAssembly: # The kernel put the signal id in edi. call SignalHandler - # Return control to the kernel, so normal computation can resume normally. - movl $30, %eax # SysSigReturn - int $0x80 - + # Return control to the kernel, so normal execution can continue. + int $131 diff --git a/libmaxsi/x86/signal.s b/libmaxsi/x86/signal.s index aa311db4..acc1ad95 100644 --- a/libmaxsi/x86/signal.s +++ b/libmaxsi/x86/signal.s @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of LibMaxsi. @@ -11,8 +11,8 @@ LibMaxsi 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 Lesser General Public License for - more details. + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. You should have received a copy of the GNU Lesser General Public License along with LibMaxsi. If not, see . @@ -20,7 +20,7 @@ signal.s An assembly stub that for handling unix signals. -******************************************************************************/ +*******************************************************************************/ .globl SignalHandlerAssembly @@ -36,8 +36,5 @@ SignalHandlerAssembly: # Restore the stack as it was. addl $4, %esp - # Now that the stack is intact, return control to the kernel, so normal - # computation can resume normally. - movl $30, %eax # SysSigReturn - int $0x80 - + # Return control to the kernel, so normal execution can continue. + int $131 diff --git a/sortix/Makefile b/sortix/Makefile index eb99894f..c22e2ab9 100644 --- a/sortix/Makefile +++ b/sortix/Makefile @@ -101,6 +101,7 @@ memorymanagement.o \ calltrace.o \ $(CPU)/calltrace.o \ kthread.o \ +$(CPU)/kthread.o \ interlock.o \ $(CPU)/interlock.o \ panic.o \ @@ -132,7 +133,6 @@ elf.o \ process.o \ initrd.o \ thread.o \ -event.o \ io.o \ pipe.o \ filesystem.o \ diff --git a/sortix/com.cpp b/sortix/com.cpp index 7523f200..6ecaee84 100644 --- a/sortix/com.cpp +++ b/sortix/com.cpp @@ -26,10 +26,10 @@ #include #include #include "interrupt.h" -#include "event.h" #include "stream.h" #include "syscall.h" #include "thread.h" +#include "signal.h" #include "fs/devfs.h" #include "com.h" diff --git a/sortix/cpu.h b/sortix/cpu.h index 1aa6cd5d..7c134f09 100644 --- a/sortix/cpu.h +++ b/sortix/cpu.h @@ -35,4 +35,17 @@ #include "x64/x64.h" #endif +namespace Sortix { +namespace CPU { + +extern "C" void load_registers(InterruptRegisters* regs, size_t size) SORTIX_NORETURN; +inline void LoadRegisters(InterruptRegisters* regs) SORTIX_NORETURN; +inline void LoadRegisters(InterruptRegisters* regs) +{ + load_registers(regs, sizeof(*regs)); +} + +} // namespace CPU +} // namespace CPU + #endif diff --git a/sortix/event.cpp b/sortix/event.cpp deleted file mode 100644 index 41f12c6f..00000000 --- a/sortix/event.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/****************************************************************************** - - 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 . - - event.cpp - Each thread can wait for an event to happen and be signaled when it does. - -******************************************************************************/ - -#include -#include "thread.h" -#include "syscall.h" -#include "event.h" - -namespace Sortix -{ - Event::Event() - { - waiting = NULL; - } - - Event::~Event() - { - if ( waiting ) - { - Panic("Thread was waiting on event, but it went out of scope"); - } - } - - void Event::Register() - { - Thread* thread = CurrentThread(); - if ( thread->event ) - { - Panic("Thread tried to wait on an event, but was already waiting"); - } - thread->event = this; - thread->eventnextwaiting = waiting; - waiting = thread; - } - - void Event::Signal() - { - while ( waiting ) - { - waiting->event = NULL; - Syscall::ScheduleResumption(waiting); - waiting = waiting->eventnextwaiting; - } - } - - // TODO: Okay, I realize this is O(N), refactor this to a linked list. - void Event::Unregister(Thread* thread) - { - if ( thread->event != this ) { return; } - thread->event = NULL; - if ( waiting == thread ) { waiting = thread->eventnextwaiting; return; } - for ( Thread* tmp = waiting; tmp; tmp = tmp->eventnextwaiting ) - { - if ( tmp->eventnextwaiting == thread ) - { - tmp->eventnextwaiting = thread->eventnextwaiting; - break; - } - } - thread->eventnextwaiting = NULL; - } -} - diff --git a/sortix/event.h b/sortix/event.h deleted file mode 100644 index 6e7a8481..00000000 --- a/sortix/event.h +++ /dev/null @@ -1,50 +0,0 @@ -/****************************************************************************** - - 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 . - - event.h - Each thread can wait for an event to happen and be signaled when it does. - -******************************************************************************/ - -#ifndef SORTIX_EVENT_H -#define SORTIX_EVENT_H - -namespace Sortix -{ - class Thread; - - class Event - { - public: - Event(); - ~Event(); - - public: - void Register(); - void Signal(); - void Unregister(Thread* thread); - - private: - Thread* waiting; - - }; -} - -#endif - diff --git a/sortix/include/sortix/kernel/kthread.h b/sortix/include/sortix/kernel/kthread.h index 20c992f5..a808ae2d 100644 --- a/sortix/include/sortix/kernel/kthread.h +++ b/sortix/include/sortix/kernel/kthread.h @@ -18,29 +18,39 @@ Sortix. If not, see . kthread.h - Fake header providing noop threading functions. This is simply forward - compatibility with the upcoming kthread branch and to ease merging. + Utility and synchronization mechanisms for kernel threads. *******************************************************************************/ #ifndef SORTIX_KTHREAD_H #define SORTIX_KTHREAD_H -#define GOT_FAKE_KTHREAD -#warning Using noop kthread functions +#include +#include "../../../signal.h" + +#define GOT_ACTUAL_KTHREAD namespace Sortix { extern "C" { +inline static void kthread_yield(void) { asm volatile ("int $129"); } +void kthread_exit(void* param = NULL) SORTIX_NORETURN; typedef unsigned kthread_mutex_t; const kthread_mutex_t KTHREAD_MUTEX_INITIALIZER = 0; unsigned kthread_mutex_trylock(kthread_mutex_t* mutex); void kthread_mutex_lock(kthread_mutex_t* mutex); unsigned long kthread_mutex_lock_signal(kthread_mutex_t* mutex); void kthread_mutex_unlock(kthread_mutex_t* mutex); -typedef unsigned kthread_cond_t; -const kthread_cond_t KTHREAD_COND_INITIALIZER = 0; +struct kthread_cond_elem; +typedef struct kthread_cond_elem kthread_cond_elem_t; +struct kthread_cond +{ + kthread_cond_elem_t* first; + kthread_cond_elem_t* last; +}; +typedef struct kthread_cond kthread_cond_t; +const kthread_cond_t KTHREAD_COND_INITIALIZER = { NULL, NULL }; void kthread_cond_wait(kthread_cond_t* cond, kthread_mutex_t* mutex); unsigned long kthread_cond_wait_signal(kthread_cond_t* cond, kthread_mutex_t* mutex); void kthread_cond_signal(kthread_cond_t* cond); @@ -74,6 +84,7 @@ public: { this->mutex = mutex; this->acquired = kthread_mutex_lock_signal(mutex); + ASSERT(acquired || Signal::IsPending()); } ~ScopedLockSignal() diff --git a/sortix/include/sortix/kernel/memorymanagement.h b/sortix/include/sortix/kernel/memorymanagement.h index 218a70a6..d02f5e12 100644 --- a/sortix/include/sortix/kernel/memorymanagement.h +++ b/sortix/include/sortix/kernel/memorymanagement.h @@ -65,7 +65,9 @@ namespace Sortix addr_t Fork(); addr_t GetAddressSpace(); addr_t SwitchAddressSpace(addr_t addrspace); - void DestroyAddressSpace(); + void DestroyAddressSpace(addr_t fallback = 0, + void (*func)(addr_t, void*) = NULL, + void* user = NULL); bool Map(addr_t physical, addr_t mapto, int prot); addr_t Unmap(addr_t mapto); addr_t Physical(addr_t mapto); diff --git a/sortix/include/sortix/signal.h b/sortix/include/sortix/signal.h new file mode 100644 index 00000000..07a449c6 --- /dev/null +++ b/sortix/include/sortix/signal.h @@ -0,0 +1,79 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. + + This file is part of Sortix. + + Sortix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Sortix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . + + signal.h + Defines the numeric values for the various supported signals. + +*******************************************************************************/ + +#ifndef SORTIX_INCLUDE_SIGNAL_H +#define SORTIX_INCLUDE_SIGNAL_H + +/* TODO: Yes, signals are implemented in a non-standard manner for now. */ + +#include + +__BEGIN_DECLS + +#define SIGHUP 1 /* Hangup */ +#define SIGINT 2 /* Interrupt */ +#define SIGQUIT 3 /* Quit */ +#define SIGILL 4 /* Illegal Instruction */ +#define SIGTRAP 5 /* Trace/Breakpoint Trap */ +#define SIGABRT 6 /* Abort */ +#define SIGEMT 7 /* Emulation Trap */ +#define SIGFPE 8 /* Arithmetic Exception */ +#define SIGKILL 9 /* Killed */ +#define SIGBUS 10 /* Bus Error */ +#define SIGSEGV 11 /* Segmentation Fault */ +#define SIGSYS 12 /* Bad System Call */ +#define SIGPIPE 13 /* Broken Pipe */ +#define SIGALRM 14 /* Alarm Clock */ +#define SIGTERM 15 /* Terminated */ +#define SIGUSR1 16 /* User Signal 1 */ +#define SIGUSR2 17 /* User Signal 2 */ +#define SIGCHLD 18 /* Child Status */ +#define SIGPWR 19 /* Power Fail/Restart */ +#define SIGWINCH 20 /* Window Size Change */ +#define SIGURG 21 /* Urgent Socket Condition */ +#define SIGSTOP 23 /* Stopped (signal) */ +#define SIGTSTP 24 /* Stopped (user) */ +#define SIGCONT 25 /* Continued */ +#define SIGTTIN 26 /* Stopped (tty input) */ +#define SIGTTOU 27 /* Stopped (tty output) */ +#define SIGVTALRM 28 /* Virtual Timer Expired */ +#define SIGXCPU 30 /* CPU time limit exceeded */ +#define SIGXFSZ 31 /* File size limit exceeded */ +#define SIGWAITING 32 /* All LWPs blocked */ +#define SIGLWP 33 /* Virtual Interprocessor Interrupt for Threads Library */ +#define SIGAIO 34 /* Asynchronous I/O */ +#define SIG__NUM_DECLARED 35 +#define SIG_MAX_NUM 128 + +#define SIG_PRIO_NORMAL 0 +#define SIG_PRIO_HIGH 1 +#define SIG_PRIO_STOP 2 +#define SIG_PRIO_CORE 3 +#define SIG_PRIO_KILL 4 +#define SIG_NUM_LEVELS 5 + +__END_DECLS + +#endif + diff --git a/sortix/include/sortix/syscallnum.h b/sortix/include/sortix/syscallnum.h index ab219adc..9ae864de 100644 --- a/sortix/include/sortix/syscallnum.h +++ b/sortix/include/sortix/syscallnum.h @@ -52,7 +52,6 @@ #define SYSCALL_UNLINK 27 #define SYSCALL_REGISTER_ERRNO 28 #define SYSCALL_REGISTER_SIGNAL_HANDLER 29 -#define SYSCALL_SIGRETURN 30 #define SYSCALL_KILL 31 #define SYSCALL_MEMSTAT 32 #define SYSCALL_ISATTY 33 @@ -75,7 +74,8 @@ #define SYSCALL_PWRITE 50 #define SYSCALL_SFORKR 51 #define SYSCALL_TCGETWINSIZE 52 -#define SYSCALL_MAX_NUM 53 /* index of highest constant + 1 */ +#define SYSCALL_RAISE 53 +#define SYSCALL_MAX_NUM 54 /* index of highest constant + 1 */ #endif diff --git a/sortix/interrupt.cpp b/sortix/interrupt.cpp index 3bb01d81..ecd51cc7 100644 --- a/sortix/interrupt.cpp +++ b/sortix/interrupt.cpp @@ -1,6 +1,6 @@ /******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011, 2012. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -23,14 +23,16 @@ *******************************************************************************/ #include +#include +#include #include "x86-family/idt.h" #include "interrupt.h" +#include "scheduler.h" +#include "syscall.h" -#include "process.h" // Hack for SIGSEGV #include "sound.h" // Hack for SIGSEGV -#include "thread.h" // HACK FOR SIGSEGV -#include "syscall.h" // HACK FOR SIGSEGV -#include "scheduler.h" // HACK FOR SIGSEGV + +using namespace Maxsi; namespace Sortix { void SysExit(int status); // HACK @@ -53,8 +55,10 @@ const uint8_t PIC_MODE_BUF_SLAVE = 0x08; // Buffered mode/slave const uint8_t PIC_MODE_BUF_MASTER = 0x0C; // Buffered mode/master const uint8_t PIC_MODE_SFNM = 0x10; // Special fully nested (not) +extern "C" { unsigned long asm_is_cpu_interrupted = 0; } const bool DEBUG_EXCEPTION = false; const bool DEBUG_IRQ = false; +const bool DEBUG_ISR = false; bool initialized; const size_t NUM_KNOWN_EXCEPTIONS = 20; @@ -237,12 +241,20 @@ void CrashHandler(CPU::InterruptRegisters* regs) Sound::Mute(); - CurrentProcess()->Exit(139); - Scheduler::ProcessTerminated(regs); + // Exit the process with the right error code. + // TODO: Sent a SIGINT, SIGBUS, or whatever instead. + SysExit(139); } void ISRHandler(Sortix::CPU::InterruptRegisters* regs) { + if ( DEBUG_ISR ) + { + Log::PrintF("ISR%u ", regs->int_no); + regs->LogRegisters(); + Log::Print("\n"); + } + if ( regs->int_no < 32 ) { CrashHandler(regs); @@ -289,6 +301,109 @@ extern "C" void interrupt_handler(Sortix::CPU::InterruptRegisters* regs) else { ISRHandler(regs); } } +// TODO: This implementation is a bit hacky and can be optimized. + +uint8_t* queue; +uint8_t* storage; +volatile size_t queueoffset; +volatile size_t queueused; +size_t queuesize; + +struct Package +{ + size_t size; + size_t payloadoffset; + size_t payloadsize; + WorkHandler handler; // TODO: May not be correctly aligned on some systems. + uint8_t payload[0]; +}; + +void InitWorker() +{ + const size_t QUEUE_SIZE = 4UL*1024UL; + STATIC_ASSERT(QUEUE_SIZE % sizeof(Package) == 0); + queue = new uint8_t[QUEUE_SIZE]; + if ( !queue ) { Panic("Can't allocate interrupt worker queue"); } + storage = new uint8_t[QUEUE_SIZE]; + if ( !storage ) { Panic("Can't allocate interrupt worker storage"); } + queuesize = QUEUE_SIZE; + queueoffset = 0; + queueused = 0; +} + +static void WriteToQueue(const void* src, size_t size) +{ + const uint8_t* buf = (const uint8_t*) src; + size_t writeat = (queueoffset + queueused) % queuesize; + size_t available = queuesize - writeat; + size_t count = available < size ? available : size; + Memory::Copy(queue + writeat, buf, count); + queueused += count; + if ( count < size ) { WriteToQueue(buf + count, size - count); } +} + +static void ReadFromQueue(void* dest, size_t size) +{ + uint8_t* buf = (uint8_t*) dest; + size_t available = queuesize - queueoffset; + size_t count = available < size ? available : size; + Memory::Copy(buf, queue + queueoffset, count); + queueused -= count; + queueoffset = (queueoffset + count) % queuesize; + if ( count < size ) { ReadFromQueue(buf + count, size - count); } +} + +static Package* PopPackage(uint8_t** payloadp, Package* /*prev*/) +{ + Package* package = NULL; + uint8_t* payload = NULL; + Interrupt::Disable(); + + if ( !queueused ) { goto out; } + + package = (Package*) storage; + ReadFromQueue(package, sizeof(*package)); + payload = storage + sizeof(*package); + ReadFromQueue(payload, package->payloadsize); + *payloadp = payload; + +out: + Interrupt::Enable(); + return package; +} + +void WorkerThread(void* /*user*/) +{ + ASSERT(Interrupt::IsEnabled()); + uint8_t* payload = NULL; + Package* package = NULL; + while ( true ) + { + package = PopPackage(&payload, package); + if ( !package ) { Scheduler::Yield(); continue; } + size_t payloadsize = package->payloadsize; + package->handler(payload, payloadsize); + } +} + +bool ScheduleWork(WorkHandler handler, void* payload, size_t payloadsize) +{ + ASSERT(!Interrupt::IsEnabled()); + + Package package; + package.size = sizeof(package) + payloadsize; + package.payloadoffset = 0; // Currently unused + package.payloadsize = payloadsize; + package.handler = handler; + + size_t queuefreespace = queuesize - queueused; + if ( queuefreespace < package.size ) { return false; } + + WriteToQueue(&package, sizeof(package)); + WriteToQueue(payload, payloadsize); + return true; +} + } // namespace Interrupt } // namespace Sortix diff --git a/sortix/interrupt.h b/sortix/interrupt.h index 64769973..8f93d77f 100644 --- a/sortix/interrupt.h +++ b/sortix/interrupt.h @@ -1,6 +1,6 @@ /******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011, 2012. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -48,10 +48,12 @@ const unsigned IRQ14 = 46; const unsigned IRQ15 = 47; extern "C" unsigned long asm_interrupts_are_enabled(); +extern "C" unsigned long asm_is_cpu_interrupted; inline bool IsEnabled() { return asm_interrupts_are_enabled(); } inline void Enable() { asm volatile("sti"); } inline void Disable() { asm volatile("cli"); } +inline bool IsCPUInterrupted() { return asm_is_cpu_interrupted != 0; } inline bool SetEnabled(bool isenabled) { bool wasenabled = IsEnabled(); @@ -66,6 +68,11 @@ typedef void (*RawHandler)(void); void RegisterRawHandler(unsigned index, RawHandler handler, bool userspace); void Init(); +void InitWorker(); +void WorkerThread(void* user); + +typedef void(*WorkHandler)(void* payload, size_t payloadsize); +bool ScheduleWork(WorkHandler handler, void* payload, size_t payloadsize); } // namespace Interrupt } // namespace Sortix @@ -103,6 +110,8 @@ extern "C" void isr29(); extern "C" void isr30(); extern "C" void isr31(); extern "C" void isr128(); +extern "C" void isr130(); +extern "C" void isr131(); extern "C" void irq0(); extern "C" void irq1(); extern "C" void irq2(); diff --git a/sortix/kernel.cpp b/sortix/kernel.cpp index 41fac5e1..81752840 100644 --- a/sortix/kernel.cpp +++ b/sortix/kernel.cpp @@ -24,16 +24,19 @@ *******************************************************************************/ #include +#include +#include +#include #include #include #include #include +#include +#include #include #include #include -#include -#include -#include +#include #include "kernelinfo.h" #include "x86-family/gdt.h" #include "time.h" @@ -43,6 +46,7 @@ #include "thread.h" #include "process.h" #include "scheduler.h" +#include "signal.h" #include "syscall.h" #include "ata.h" #include "com.h" @@ -67,7 +71,8 @@ using namespace Maxsi; // Keep the stack size aligned with $CPU/base.s -extern "C" { size_t stack[64*1024 / sizeof(size_t)] = {0}; } +const size_t STACK_SIZE = 64*1024; +extern "C" { size_t stack[STACK_SIZE / sizeof(size_t)] = {0}; } namespace Sortix { @@ -97,6 +102,11 @@ void DoWelcome() Log::Print(" BOOTING OPERATING SYSTEM... "); } +// Forward declarations. +static void BootThread(void* user); +static void InitThread(void* user); +static void SystemIdleThread(void* user); + static size_t PrintToTextTerminal(void* user, const char* str, size_t len) { return ((TextTerminal*) user)->Print(str, len); @@ -146,7 +156,7 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo) addr_t initrd = NULL; size_t initrdsize = 0; - uint32_t* modules = (uint32_t*) bootinfo->mods_addr; + uint32_t* modules = (uint32_t*) (addr_t) bootinfo->mods_addr; for ( uint32_t i = 0; i < bootinfo->mods_count; i++ ) { initrdsize = modules[2*i+1] - modules[2*i+0]; @@ -170,6 +180,9 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo) // Initialize the kernel heap. Maxsi::Memory::Init(); + // Initialize the interrupt worker. + Interrupt::InitWorker(); + // Initialize the list of kernel devices. DeviceFS::Init(); @@ -212,6 +225,12 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo) // Initialize the scheduler. Scheduler::Init(); + // Initialize Unix Signals. + Signal::Init(); + + // Initialize the worker thread data structures. + Worker::Init(); + // Initialize the kernel information query syscall. Info::Init(); @@ -230,59 +249,139 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo) // Initialize the BGA driver. BGA::Init(); - // 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. + // Now that the base system has been loaded, it's time to go threaded. First + // we create an object that represents this thread. + Process* system = new Process; + if ( !system ) { Panic("Could not allocate the system process"); } + addr_t systemaddrspace = Memory::GetAddressSpace(); + system->addrspace = systemaddrspace; - uint32_t inode; - byte* program; - size_t programsize; - - // Create an address space for the idle process. - addr_t idleaddrspace = Memory::Fork(); - if ( !idleaddrspace ) { Panic("could not fork an idle process address space"); } - - // Create an address space for the initial process. - addr_t initaddrspace = Memory::Fork(); - if ( !initaddrspace ) { Panic("could not fork an initial process address space"); } - - // 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); - inode = InitRD::Traverse(InitRD::Root(), "idle"); - if ( inode == NULL ) { PanicF("initrd did not contain 'idle'"); } - program = InitRD::Open(inode, &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"); } + // We construct this thread manually for bootstrap reasons. We wish to + // create a kernel thread that is the current thread and isn't put into the + // scheduler's set of runnable threads, but rather run whenever there is + // _nothing_ else to run on this CPU. + Thread* idlethread = new Thread; + idlethread->process = system; + idlethread->kernelstackpos = (addr_t) stack; + idlethread->kernelstacksize = STACK_SIZE; + idlethread->kernelstackmalloced = false; + system->firstthread = idlethread; 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); - inode = InitRD::Traverse(InitRD::Root(), "init"); - if ( inode == NULL ) { PanicF("initrd did not contain 'init'"); } - program = InitRD::Open(inode, &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"); } - Scheduler::SetInitProcess(init); + // Let's create a regular kernel thread that can decide what happens next. + // Note that we don't do the work here: should it block, then there is + // nothing to run. Therefore we must become the system idle thread. + RunKernelThread(BootThread, NULL); - // Lastly set up the timer driver and we are ready to run the OS. + // The time driver will run the scheduler on the next timer interrupt. Time::Init(); - // Run the OS. - Scheduler::MainLoop(); + // Become the system idle thread. + SystemIdleThread(NULL); +} + +static void SystemIdleThread(void* /*user*/) +{ + // Alright, we are now the system idle thread. If there is nothing to do, + // then we are run. Note that we must never do any real work here. + while(true); +} + +static void BootThread(void* /*user*/) +{ + // Hello, threaded world! You can now regard the kernel as a multi-threaded + // process with super-root access to the system. Before we boot the full + // system we need to start some worker threads. + + // Let's create the interrupt worker thread that executes additional work + // requested by interrupt handlers, where such work isn't safe. + Thread* interruptworker = RunKernelThread(Interrupt::WorkerThread, NULL); + if ( !interruptworker ) + Panic("Could not create interrupt worker"); + + // Create a general purpose worker thread. + Thread* workerthread = RunKernelThread(Worker::Thread, NULL); + if ( !workerthread ) + Panic("Unable to create general purpose worker thread"); + + // Finally, let's transfer control to a new kernel process that will + // eventually run user-space code known as the operating system. + addr_t initaddrspace = Memory::Fork(); + if ( !initaddrspace ) { Panic("Could not create init's address space"); } + + Process* init = new Process; + if ( !init ) { Panic("Could not allocate init process"); } + + CurrentProcess()->AddChildProcess(init); + + init->addrspace = initaddrspace; + Scheduler::SetInitProcess(init); + + Thread* initthread = RunKernelThread(init, InitThread, NULL); + if ( !initthread ) { Panic("Coul not create init thread"); } + + // Wait until init init is done and then shut down the computer. + int status; + pid_t pid = CurrentProcess()->Wait(init->pid, &status, 0); + if ( pid != init->pid ) + PanicF("Waiting for init to exit returned %i (errno=%i)", pid, errno); + + switch ( status ) + { + case 0: CPU::ShutDown(); + case 1: CPU::Reboot(); + default: + PanicF("Init returned with unexpected return code %i", status); + } +} + +static void InitThread(void* /*user*/) +{ + // We are the init process's first thread. Let's load the init program from + // the init ramdisk and transfer execution to it. We will then become a + // regular user-space program with root permissions. + + Thread* thread = CurrentThread(); + Process* process = CurrentProcess(); + + uint32_t inode = InitRD::Traverse(InitRD::Root(), "init"); + if ( !inode ) { Panic("InitRD did not contain an 'init' program."); } + + size_t programsize; + uint8_t* program = InitRD::Open(inode, &programsize); + if ( !program ) { Panic("InitRD did not contain an 'init' program."); } + + const size_t DEFAULT_STACK_SIZE = 64UL * 1024UL; + + size_t stacksize = 0; + if ( !stacksize ) { stacksize = DEFAULT_STACK_SIZE; } + + addr_t stackpos = process->AllocVirtualAddr(stacksize); + if ( !stackpos ) { Panic("Could not allocate init stack space"); } + + int prot = PROT_FORK | PROT_READ | PROT_WRITE | PROT_KREAD | PROT_KWRITE; + if ( !Memory::MapRange(stackpos, stacksize, prot) ) + { + Panic("Could not allocate init stack memory"); + } + + thread->stackpos = stackpos; + thread->stacksize = stacksize; + + int argc = 1; + const char* argv[] = { "init", NULL }; + int envc = 0; + const char* envp[] = { NULL }; + CPU::InterruptRegisters regs; + + if ( process->Execute("init", program, programsize, argc, argv, envc, envp, + ®s) ) + { + Panic("Unable to execute init program"); + } + + // Now become the init process and the operation system shall run. + CPU::LoadRegisters(®s); } } // namespace Sortix diff --git a/sortix/kthread.cpp b/sortix/kthread.cpp index a0998acb..39a9d0e3 100644 --- a/sortix/kthread.cpp +++ b/sortix/kthread.cpp @@ -18,52 +18,107 @@ Sortix. If not, see . kthread.cpp - Fake header providing noop threading functions. This is simply forward - compatibility with the upcoming kthread branch and to ease merging. + Utility and synchronization mechanisms for kernel threads. *******************************************************************************/ #include #include +#include +#include +#include "signal.h" +#include "thread.h" +#include "scheduler.h" namespace Sortix { -// This isn't as bad as it looks. Kernel code traditionally runs with interrupts -// disabled so there are no race conditions. - -extern "C" unsigned kthread_mutex_trylock(kthread_mutex_t* mutex) +// The kernel thread needs another stack to delete its own stack. +static void kthread_do_kill_thread(void* user) { + Thread* thread = (Thread*) user; + while ( thread->state != Thread::State::DEAD ) + kthread_yield(); + delete thread; } -extern "C" void kthread_mutex_lock(kthread_mutex_t* mutex) +extern "C" void kthread_exit(void* /*param*/) { + Worker::Schedule(kthread_do_kill_thread, CurrentThread()); + Scheduler::ExitThread(); } -unsigned long kthread_mutex_lock_signal(kthread_mutex_t* mutex) +struct kthread_cond_elem { - return 1; -} - -extern "C" void kthread_mutex_unlock(kthread_mutex_t* mutex) -{ -} + kthread_cond_elem_t* next; + volatile unsigned long woken; +}; extern "C" void kthread_cond_wait(kthread_cond_t* cond, kthread_mutex_t* mutex) { + kthread_cond_elem_t elem; + elem.next = NULL; + elem.woken = 0; + if ( cond->last ) { cond->last->next = &elem; } + if ( !cond->last ) { cond->last = cond->first = &elem; } + while ( !elem.woken ) + { + kthread_mutex_unlock(mutex); + Scheduler::Yield(); + kthread_mutex_lock(mutex); + } } extern "C" unsigned long kthread_cond_wait_signal(kthread_cond_t* cond, kthread_mutex_t* mutex) { + if ( Signal::IsPending() ) + return 0; + kthread_cond_elem_t elem; + elem.next = NULL; + elem.woken = 0; + if ( cond->last ) { cond->last->next = &elem; } + if ( !cond->last ) { cond->last = cond->first = &elem; } + while ( !elem.woken ) + { + if ( Signal::IsPending() ) + { + if ( cond->first == &elem ) + { + cond->first = elem.next; + if ( cond->last == &elem ) + cond->last = NULL; + } + else + { + kthread_cond_elem_t* prev = cond->first; + while ( prev->next != &elem ) + prev = prev->next; + prev->next = elem.next; + if ( cond->last == &elem ) + cond->last = prev; + } + // Note that the thread still owns the mutex. + return 0; + } + kthread_mutex_unlock(mutex); + Scheduler::Yield(); + kthread_mutex_lock(mutex); + } return 1; } extern "C" void kthread_cond_signal(kthread_cond_t* cond) { + kthread_cond_elem_t* elem = cond->first; + if ( !elem ) { return; } + if ( !(cond->first = elem->next) ) { cond->last = NULL; } + elem->next = NULL; + elem->woken = 1; } extern "C" void kthread_cond_broadcast(kthread_cond_t* cond) { + while ( cond->first ) { kthread_cond_signal(cond); } } } // namespace Sortix diff --git a/sortix/logterminal.cpp b/sortix/logterminal.cpp index 7a61dae8..96588301 100644 --- a/sortix/logterminal.cpp +++ b/sortix/logterminal.cpp @@ -24,10 +24,12 @@ #include #include +#include #include #include #include "utf8.h" #include "keyboard.h" +#include "process.h" #include "scheduler.h" #include "terminal.h" #include "logterminal.h" @@ -130,7 +132,10 @@ namespace Sortix if ( kbkey == -KBKEY_LCTRL ) { control = false; } if ( mode & TERMMODE_SIGNAL && control && kbkey == KBKEY_C ) { - Scheduler::SigIntHack(); + pid_t pid = Process::HackGetForegroundProcess(); + Process* process = Process::Get(pid); + if ( process ) + process->DeliverSignal(SIGINT); return; } if ( mode & TERMMODE_SIGNAL && control && kbkey == KBKEY_D ) @@ -299,7 +304,6 @@ namespace Sortix return -1; } #endif - return sofar; } diff --git a/sortix/logterminal.h b/sortix/logterminal.h index 74147dfe..fc4339e2 100644 --- a/sortix/logterminal.h +++ b/sortix/logterminal.h @@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License along with Sortix. If not, see . - logterminal.h A simple terminal that writes to the kernel log. diff --git a/sortix/panic.cpp b/sortix/panic.cpp index 6b1e76fe..ed5898ee 100644 --- a/sortix/panic.cpp +++ b/sortix/panic.cpp @@ -25,6 +25,7 @@ #include #include #include +#include "interrupt.h" #include #include "calltrace.h" #include @@ -43,6 +44,7 @@ namespace Sortix void PanicInit() { + Interrupt::Disable(); if ( panicing ) { Log::PrintF("Panic while panicing:\n"); diff --git a/sortix/pipe.cpp b/sortix/pipe.cpp index 4073211e..f345fdcb 100644 --- a/sortix/pipe.cpp +++ b/sortix/pipe.cpp @@ -24,11 +24,13 @@ #include #include +#include #include #include #ifdef GOT_FAKE_KTHREAD #include "event.h" #endif +#include "signal.h" #include "thread.h" #include "process.h" #include "syscall.h" @@ -159,8 +161,7 @@ namespace Sortix } if ( !anyreading ) { - // TODO: Implement better signal support and uncomment. - //CurrentThread()->DeliverSignal(SIGPIPE); + CurrentThread()->DeliverSignal(SIGPIPE); Error::Set(EPIPE); return -1; } diff --git a/sortix/process.cpp b/sortix/process.cpp index d9fbf752..c00905a7 100644 --- a/sortix/process.cpp +++ b/sortix/process.cpp @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,15 +14,19 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . process.cpp - Describes a process belonging to a subsystem. + A named collection of threads. -******************************************************************************/ +*******************************************************************************/ #include +#include +#include +#include +#include #include #include #include @@ -37,7 +41,6 @@ #include "filesystem.h" #include "directory.h" #include "scheduler.h" -#include #include "initrd.h" #include "elf.h" #include "syscall.h" @@ -84,7 +87,8 @@ namespace Sortix return NULL; } - next->prev = nextclone; + if ( nextclone ) + nextclone->prev = clone; clone->next = nextclone; clone->position = position; clone->size = size; @@ -96,15 +100,20 @@ namespace Sortix { addrspace = 0; segments = NULL; - sigint = false; parent = NULL; prevsibling = NULL; nextsibling = NULL; firstchild = NULL; zombiechild = NULL; + parentlock = KTHREAD_MUTEX_INITIALIZER; + childlock = KTHREAD_MUTEX_INITIALIZER; + zombiecond = KTHREAD_COND_INITIALIZER; + zombiewaiting = 0; + iszombie = false; + nozombify = false; firstthread = NULL; + threadlock = KTHREAD_MUTEX_INITIALIZER; workingdir = NULL; - errno = NULL; mmapfrom = 0x80000000UL; exitstatus = -1; pid = AllocatePID(); @@ -113,20 +122,156 @@ namespace Sortix Process::~Process() { + ASSERT(!zombiechild); + ASSERT(!firstchild); + ASSERT(!addrspace); + ASSERT(!segments); + Remove(this); + delete[] workingdir; + } + + void Process__OnLastThreadExit(void* user); + + void Process::OnThreadDestruction(Thread* thread) + { + ASSERT(thread->process == this); + kthread_mutex_lock(&threadlock); + if ( thread->prevsibling ) + thread->prevsibling->nextsibling = thread->nextsibling; + if ( thread->nextsibling ) + thread->nextsibling->prevsibling = thread->prevsibling; + if ( thread == firstthread ) + firstthread = thread->nextsibling; + if ( firstthread ) + firstthread->prevsibling = NULL; + thread->prevsibling = thread->nextsibling = NULL; + bool threadsleft = firstthread; + kthread_mutex_unlock(&threadlock); + + // We are called from the threads destructor, let it finish before we + // we handle the situation by killing ourselves. + if ( !threadsleft ) + ScheduleDeath(); + } + + void Process::ScheduleDeath() + { + // All our threads must have exited at this point. + ASSERT(!firstthread); + Worker::Schedule(Process__OnLastThreadExit, this); + } + + // Useful for killing a partially constructed process without waiting for + // it to die and garbage collect its zombie. It is not safe to access this + // process after this call as another thread may garbage collect it. + void Process::AbortConstruction() + { + nozombify = true; + ScheduleDeath(); + } + + void Process__OnLastThreadExit(void* user) + { + return ((Process*) user)->OnLastThreadExit(); + } + + void Process::OnLastThreadExit() + { + LastPrayer(); + } + + static void SwitchCurrentAddrspace(addr_t addrspace, void* user) + { + ((Thread*) user)->SwitchAddressSpace(addrspace); + } + + void Process::LastPrayer() + { + ASSERT(this); + // This must never be called twice. + ASSERT(!iszombie); + + // This must be called from a thread using another address space as the + // address space of this process is about to be destroyed. + Thread* curthread = CurrentThread(); + ASSERT(curthread->process != this); + + // This can't be called if the process is still alive. + ASSERT(!firstthread); + + // We need to temporarily reload the correct addrese space of the dying + // process such that we can unmap and free its memory. + addr_t prevaddrspace = curthread->SwitchAddressSpace(addrspace); ResetAddressSpace(); + descriptors.Reset(); - // Avoid memory leaks. - ASSERT(segments == NULL); + // Destroy the address space and safely switch to the replacement + // address space before things get dangerous. + Memory::DestroyAddressSpace(prevaddrspace, + SwitchCurrentAddrspace, + curthread); + addrspace = 0; - delete[] workingdir; + // Init is nice and will gladly raise our orphaned children and zombies. + Process* init = Scheduler::GetInitProcess(); + ASSERT(init); + kthread_mutex_lock(&childlock); + while ( firstchild ) + { + ScopedLock firstchildlock(&firstchild->parentlock); + ScopedLock initlock(&init->childlock); + Process* process = firstchild; + firstchild = process->nextsibling; + process->parent = init; + process->prevsibling = NULL; + process->nextsibling = init->firstchild; + if ( init->firstchild ) + init->firstchild->prevsibling = process; + init->firstchild = process; + } + // Since we have no more children (they are with init now), we don't + // have to worry about new zombie processes showing up, so just collect + // those that are left. Then we satisfiy the invariant !zombiechild that + // applies on process termination. + bool hadzombies = zombiechild; + while ( zombiechild ) + { + ScopedLock zombiechildlock(&zombiechild->parentlock); + ScopedLock initlock(&init->childlock); + Process* zombie = zombiechild; + zombiechild = zombie->nextsibling; + zombie->prevsibling = NULL; + zombie->nextsibling = init->zombiechild; + if ( init->zombiechild ) + init->zombiechild->prevsibling = zombie; + init->zombiechild = zombie; + } + kthread_mutex_unlock(&childlock); - // TODO: Delete address space! + if ( hadzombies ) + init->NotifyNewZombies(); + + iszombie = true; + + bool zombify = !nozombify; + + // This class instance will be destroyed by our parent process when it + // has received and acknowledged our death. + kthread_mutex_lock(&parentlock); + if ( parent ) + parent->NotifyChildExit(this, zombify); + kthread_mutex_unlock(&parentlock); + + // If nobody is waiting for us, then simply commit suicide. + if ( !zombify ) + delete this; } void Process::ResetAddressSpace() { + ASSERT(Memory::GetAddressSpace() == addrspace); ProcessSegment* tmp = segments; while ( tmp != NULL ) { @@ -137,7 +282,154 @@ namespace Sortix } segments = NULL; - errno = NULL; + } + + void Process::NotifyChildExit(Process* child, bool zombify) + { + kthread_mutex_lock(&childlock); + + if ( child->prevsibling ) + child->prevsibling->nextsibling = child->nextsibling; + if ( child->nextsibling ) + child->nextsibling->prevsibling = child->prevsibling; + if ( firstchild == child ) + firstchild = child->nextsibling; + if ( firstchild ) + firstchild->prevsibling = NULL; + + if ( zombify ) + { + if ( zombiechild ) + zombiechild->prevsibling = child; + child->prevsibling = NULL; + child->nextsibling = zombiechild; + zombiechild = child; + } + + kthread_mutex_unlock(&childlock); + + if ( zombify ) + NotifyNewZombies(); + } + + void Process::NotifyNewZombies() + { + ScopedLock lock(&childlock); + // TODO: Send SIGCHLD here? + if ( zombiewaiting ) + kthread_cond_broadcast(&zombiecond); + } + + pid_t Process::Wait(pid_t thepid, int* status, int options) + { + // TODO: Process groups are not supported yet. + if ( thepid < -1 || thepid == 0 ) { Error::Set(ENOSYS); return -1; } + + ScopedLock lock(&childlock); + + // A process can only wait if it has children. + if ( !firstchild && !zombiechild ) { Error::Set(ECHILD); return -1; } + + // Processes can only wait for their own children to exit. + if ( 0 < thepid ) + { + // TODO: This is a slow but multithread safe way to verify that the + // target process has the correct parent. + bool found = false; + for ( Process* p = firstchild; !found && p; p = p->nextsibling ) + if ( p->pid == thepid ) + found = true; + for ( Process* p = zombiechild; !found && p; p = p->nextsibling ) + if ( p->pid == thepid ) + found = true; + if ( !found ) { Error::Set(ECHILD); return -1; } + } + + Process* zombie = NULL; + while ( !zombie ) + { + for ( zombie = zombiechild; zombie; zombie = zombie->nextsibling ) + if ( thepid == -1 || thepid == zombie->pid ) + break; + if ( zombie ) + break; + zombiewaiting++; + kthread_cond_wait(&zombiecond, &childlock); + zombiewaiting--; + } + + if ( zombie->prevsibling ) + zombie->prevsibling->nextsibling = zombie->nextsibling; + if ( zombie->nextsibling ) + zombie->nextsibling->prevsibling = zombie->prevsibling; + if ( zombiechild == zombie ) + zombiechild = zombie->nextsibling; + if ( zombiechild ) + zombiechild->prevsibling = NULL; + + thepid = zombie->pid; + + int exitstatus = zombie->exitstatus; + if ( exitstatus < 0 ) + exitstatus = 0; + + // TODO: Validate that status is a valid user-space int! + if ( status ) + *status = exitstatus; + + // And so, the process was fully deleted. + delete zombie; + + return thepid; + } + + pid_t SysWait(pid_t pid, int* status, int options) + { + return CurrentProcess()->Wait(pid, status, options); + } + + void Process::Exit(int status) + { + ScopedLock lock(&threadlock); + // Status codes can only contain 8 bits according to ISO C and POSIX. + if ( exitstatus == -1 ) + exitstatus = status % 256; + + // Broadcast SIGKILL to all our threads which will begin our long path + // of process termination. We simply can't stop the threads as they may + // be running in kernel mode doing dangerous stuff. This thread will be + // destroyed by SIGKILL once the system call returns. + for ( Thread* t = firstthread; t; t = t->nextsibling ) + t->DeliverSignal(SIGKILL); + } + + void SysExit(int status) + { + CurrentProcess()->Exit(status); + } + + bool Process::DeliverSignal(int signum) + { + // TODO: How to handle signals that kill the process? + if ( firstthread ) + return firstthread->DeliverSignal(signum); + Error::Set(EINIT); + return false; + } + + void Process::AddChildProcess(Process* child) + { + ScopedLock mylock(&childlock); + ScopedLock itslock(&child->parentlock); + ASSERT(!child->parent); + ASSERT(!child->nextsibling); + ASSERT(!child->prevsibling); + child->parent = this; + child->nextsibling = firstchild; + child->prevsibling = NULL; + if ( firstchild ) + firstchild->prevsibling = child; + firstchild = child; } Process* Process::Fork() @@ -172,88 +464,36 @@ namespace Sortix 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. + // Now it's too late to clean up here, if anything goes wrong, we simply + // ask the process to commit suicide before it goes live. 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; - } + AddChildProcess(clone); + + bool failure = false; - // Fork the file descriptors. if ( !descriptors.Fork(&clone->descriptors) ) - { - Panic("No error handling when forking FDs fails!"); - } + failure = true; - Thread* clonethreads = ForkThreads(clone); - if ( !clonethreads ) - { - Panic("No error handling when forking threads fails!"); - } - - clone->firstthread = clonethreads; - - // Copy variables. clone->mmapfrom = mmapfrom; - clone->errnop = errnop; - if ( workingdir ) { clone->workingdir = String::Clone(workingdir); } - else { clone->workingdir = NULL; } - // 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 ) + clone->workingdir = NULL; + if ( workingdir && !(clone->workingdir = String::Clone(workingdir)) ) + failure = true; + + // If the proces creation failed, ask the process to commit suicide and + // not become a zombie, as we don't wait for it to exit. It will clean + // up all the above resources and delete itself. + if ( failure ) { - tmp->Ready(); + clone->AbortConstruction(); + return NULL; } 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. @@ -315,104 +555,14 @@ namespace Sortix stackpos = envppos - envpsize; - ExecuteCPU(argc, stackargv, envc, stackenvp, stackpos, entry, regs); - descriptors.OnExecute(); + ExecuteCPU(argc, stackargv, envc, stackenvp, stackpos, entry, regs); + return 0; } - class SysExecVEState - { - public: - char* filename; - DevBuffer* dev; - byte* buffer; - size_t count; - size_t sofar; - int argc; - int envc; - char** argv; - char** envp; - - public: - SysExecVEState() - { - filename = NULL; - dev = NULL; - buffer = NULL; - count = 0; - sofar = 0; - argc = 0; - argv = NULL; - envc = 0; - envp = NULL; - } - - ~SysExecVEState() - { - delete[] filename; - if ( dev ) { dev->Unref(); } - delete[] buffer; - for ( int i = 0; i < argc; i++ ) { delete[] argv[i]; } - delete[] argv; - for ( int i = 0; i < envc; i++ ) { delete[] envp[i]; } - delete[] envp; - } - - }; - - int SysExevVEStage2(SysExecVEState* state) - { - if ( !state->dev->IsReadable() ) { Error::Set(EBADF); delete state; return -1; } - - byte* dest = state->buffer + state->sofar; - size_t amount = state->count - state->sofar; - ssize_t bytesread = state->dev->Read(dest, amount); - - // Check for premature end-of-file. - if ( bytesread == 0 && amount != 0 ) - { - Error::Set(EIO); delete state; return -1; - } - - // We actually managed to read some data. - if ( 0 <= bytesread ) - { - state->sofar += bytesread; - if ( state->sofar <= state->count ) - { - CPU::InterruptRegisters* regs = Syscall::InterruptRegs(); - Process* process = CurrentProcess(); - int result = process->Execute(state->filename, state->buffer, - state->count, state->argc, - state->argv, state->envc, - state->envp, regs); - if ( result == 0 ) { Syscall::AsIs(); } - delete state; - return result; - } - - return SysExevVEStage2(state); - } - - if ( Error::Last() != EBLOCKING ) { delete state; return -1; } - - // The stream will resume our system call once progress has been - // made. Our request is certainly not forgotten. - - // Resume the system call with these parameters. - Thread* thread = CurrentThread(); - thread->scfunc = (void*) SysExevVEStage2; - thread->scstate[0] = (size_t) state; - thread->scsize = sizeof(state); - - // Now go do something else. - Syscall::Incomplete(); - return 0; - } - - DevBuffer* OpenProgramImage(const char* progname) + DevBuffer* OpenProgramImage(const char* progname, const char* wd, const char* path) { char* abs = Directory::MakeAbsolute("/", progname); if ( !abs ) { Error::Set(ENOMEM); return NULL; } @@ -426,112 +576,126 @@ namespace Sortix return (DevBuffer*) dev; } - int SysExecVE(const char* filename, char* const argv[], char* const envp[]) + int SysExecVE(const char* _filename, char* const _argv[], char* const _envp[]) { - // TODO: Validate that all the pointer-y parameters are SAFE! - - // Use a container class to store everything and handle cleaning up. - SysExecVEState* state = new SysExecVEState; - if ( !state ) { return -1; } - - // Make a copy of argv and filename as they are going to be destroyed - // when the address space is reset. - state->filename = String::Clone(filename); - if ( !state->filename ) { delete state; return -1; } - - int argc; for ( argc = 0; argv && argv[argc]; argc++ ); - int envc; for ( envc = 0; envp && envp[envc]; envc++ ); - - state->argv = new char*[argc+1]; - if ( !state->argv ) { delete state; return -1; } - state->argc = argc; - Maxsi::Memory::Set(state->argv, 0, sizeof(char*) * (state->argc+1)); - - for ( int i = 0; i < state->argc; i++ ) - { - state->argv[i] = String::Clone(argv[i]); - if ( !state->argv[i] ) { delete state; return -1; } - } - - state->envp = new char*[envc+1]; - if ( !state->envp ) { delete state; return -1; } - state->envc = envc; - Maxsi::Memory::Set(state->envp, 0, sizeof(char*) * (state->envc+1)); - - for ( int i = 0; i < state->envc; i++ ) - { - state->envp[i] = String::Clone(envp[i]); - if ( !state->envp[i] ) { delete state; return -1; } - } - + char* filename; + int argc; + int envc; + char** argv; + char** envp; + DevBuffer* dev; + uintmax_t needed; + size_t sofar; + size_t count; + uint8_t* buffer; + int result = -1; Process* process = CurrentProcess(); - state->dev = OpenProgramImage(state->filename); - if ( !state->dev ) { delete state; return -1; } + CPU::InterruptRegisters regs; + Maxsi::Memory::Set(®s, 0, sizeof(regs)); - state->dev->Refer(); // TODO: Rules of GC may change soon. - uintmax_t needed = state->dev->Size(); - if ( SIZE_MAX < needed ) { Error::Set(ENOMEM); delete state; return -1; } + filename = String::Clone(_filename); + if ( !filename ) { goto cleanup_done; } - state->count = needed; - state->buffer = new byte[state->count]; - if ( !state->buffer ) { delete state; return -1; } + for ( argc = 0; _argv && _argv[argc]; argc++ ); + for ( envc = 0; _envp && _envp[envc]; envc++ ); - return SysExevVEStage2(state); + argv = new char*[argc+1]; + if ( !argv ) { goto cleanup_filename; } + Maxsi::Memory::Set(argv, 0, sizeof(char*) * (argc+1)); + + for ( int i = 0; i < argc; i++ ) + { + argv[i] = String::Clone(_argv[i]); + if ( !argv[i] ) { goto cleanup_argv; } + } + + envp = new char*[envc+1]; + if ( !envp ) { goto cleanup_argv; } + envc = envc; + Maxsi::Memory::Set(envp, 0, sizeof(char*) * (envc+1)); + + for ( int i = 0; i < envc; i++ ) + { + envp[i] = String::Clone(_envp[i]); + if ( !envp[i] ) { goto cleanup_envp; } + } + + dev = OpenProgramImage(filename, process->workingdir, "/bin"); + if ( !dev ) { goto cleanup_envp; } + + dev->Refer(); // TODO: Rules of GC may change soon. + needed = dev->Size(); + if ( SIZE_MAX < needed ) { Error::Set(ENOMEM); goto cleanup_dev; } + + if ( !dev->IsReadable() ) { Error::Set(EBADF); goto cleanup_dev; } + + count = needed; + buffer = new byte[count]; + if ( !buffer ) { goto cleanup_dev; } + sofar = 0; + while ( sofar < count ) + { + ssize_t bytesread = dev->Read(buffer + sofar, count - sofar); + if ( bytesread < 0 ) { goto cleanup_buffer; } + if ( bytesread == 0 ) { Error::Set(EEOF); return -1; } + sofar += bytesread; + } + + result = process->Execute(filename, buffer, count, argc, argv, envc, + envp, ®s); + + cleanup_buffer: + delete[] buffer; + cleanup_dev: + dev->Unref(); + cleanup_envp: + for ( int i = 0; i < envc; i++) { delete[] envp[i]; } + delete[] envp; + cleanup_argv: + for ( int i = 0; i < argc; i++) { delete[] argv[i]; } + delete[] argv; + cleanup_filename: + delete[] filename; + cleanup_done: + if ( !result ) { CPU::LoadRegisters(®s); } + return result; } pid_t SysSForkR(int flags, sforkregs_t* regs) { + if ( Signal::IsPending() ) { Error::Set(EINTR); return -1; } + // TODO: Properly support sforkr(2). if ( flags != SFFORK ) { Error::Set(ENOSYS); return -1; } CPU::InterruptRegisters cpuregs; - Maxsi::Memory::Set(&cpuregs, 0, sizeof(cpuregs)); -#if defined(PLATFORM_X64) - cpuregs.rip = regs->rip; - cpuregs.userrsp = regs->rsp; - cpuregs.rax = regs->rax; - cpuregs.rbx = regs->rbx; - cpuregs.rcx = regs->rcx; - cpuregs.rdx = regs->rdx; - cpuregs.rdi = regs->rdi; - cpuregs.rsi = regs->rsi; - cpuregs.rbp = regs->rbp; - cpuregs.r8 = regs->r8; - cpuregs.r9 = regs->r9; - cpuregs.r10 = regs->r10; - cpuregs.r11 = regs->r11; - cpuregs.r12 = regs->r12; - cpuregs.r13 = regs->r13; - cpuregs.r14 = regs->r14; - cpuregs.r15 = regs->r15; - cpuregs.cs = 0x18 | 0x3; - cpuregs.ds = 0x20 | 0x3; - cpuregs.ss = 0x20 | 0x3; - //cpuregs.rflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; - cpuregs.rflags = (1<<1) | (1<<9) | (1<<21); -#elif defined(PLATFORM_X86) - cpuregs.eip = regs->eip; - cpuregs.useresp = regs->esp; - cpuregs.eax = regs->eax; - cpuregs.ebx = regs->ebx; - cpuregs.ecx = regs->ecx; - cpuregs.edx = regs->edx; - cpuregs.edi = regs->edi; - cpuregs.esi = regs->esi; - cpuregs.ebp = regs->ebp; - cpuregs.cs = 0x18 | 0x3; - cpuregs.ds = 0x20 | 0x3; - cpuregs.ss = 0x20 | 0x3; - //cpuregs.eflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; - cpuregs.eflags = (1<<1) | (1<<9) | (1<<21); -#else - #error SysSForkR needs to know about your platform -#endif + InitializeThreadRegisters(&cpuregs, regs); - CurrentThread()->SaveRegisters(&cpuregs); + // TODO: Is it a hack to create a new kernel stack here? + Thread* curthread = CurrentThread(); + uint8_t* newkernelstack = new uint8_t[curthread->kernelstacksize]; + if ( !newkernelstack ) { return -1; } Process* clone = CurrentProcess()->Fork(); - if ( !clone ) { return -1; } + if ( !clone ) { delete[] newkernelstack; return -1; } + + // If the thread could not be created, make the process commit suicide + // in a manner such that we don't wait for its zombie. + Thread* thread = CreateKernelThread(clone, &cpuregs); + if ( !thread ) + { + clone->AbortConstruction(); + return -1; + } + + thread->kernelstackpos = (addr_t) newkernelstack; + thread->kernelstacksize = curthread->kernelstacksize; + thread->kernelstackmalloced = true; + thread->stackpos = curthread->stackpos; + thread->stacksize = curthread->stacksize; + thread->sighandler = curthread->sighandler; + + StartKernelThread(thread); return clone->pid; } @@ -541,21 +705,43 @@ namespace Sortix return CurrentProcess()->pid; } - pid_t SysGetParentPID() + pid_t Process::GetParentProcessId() { - Process* parent = CurrentProcess()->parent; - if ( !parent ) { return -1; } - + ScopedLock lock(&parentlock); + if( !parent ) + return 0; return parent->pid; } + pid_t SysGetParentPID() + { + return CurrentProcess()->GetParentProcessId(); + } + pid_t nextpidtoallocate; + kthread_mutex_t pidalloclock; pid_t Process::AllocatePID() { + ScopedLock lock(&pidalloclock); return nextpidtoallocate++; } + // TODO: This is not thread safe. + pid_t Process::HackGetForegroundProcess() + { + for ( pid_t i = nextpidtoallocate; 1 <= i; i-- ) + { + Process* process = Get(i); + if ( !process ) + continue; + if ( process->pid <= 1 ) + continue; + return i; + } + return 0; + } + int ProcessCompare(Process* a, Process* b) { if ( a->pid < b->pid ) { return -1; } @@ -574,6 +760,7 @@ namespace Sortix Process* Process::Get(pid_t pid) { + ScopedLock lock(&pidalloclock); size_t index = pidlist->Search(ProcessPIDCompare, pid); if ( index == SIZE_MAX ) { return NULL; } @@ -582,212 +769,19 @@ namespace Sortix bool Process::Put(Process* process) { + ScopedLock lock(&pidalloclock); return pidlist->Add(process); } void Process::Remove(Process* process) { + ScopedLock lock(&pidalloclock); size_t index = pidlist->Search(process); ASSERT(index != SIZE_MAX); pidlist->Remove(index); } - void Process::OnChildProcessExit(Process* process) - { - ASSERT(process->parent == this); - - for ( Thread* thread = firstthread; thread; thread = thread->nextsibling ) - { - if ( thread->onchildprocessexit ) - { - thread->onchildprocessexit(thread, process); - } - } - } - - void Process::Exit(int status) - { - // Status codes can only contain 8 bits according to ISO C and POSIX. - status %= 256; - - ASSERT(this == CurrentProcess()); - - Process* init = Scheduler::GetInitProcess(); - - if ( pid == 0 ) { Panic("System idle process exited"); } - - // If the init process terminated successfully, time to halt. - if ( this == init ) - { - switch ( status ) - { - case 0: CPU::ShutDown(); - case 1: CPU::Reboot(); - default: PanicF("The init process exited abnormally with status code %u\n", status); - } - } - - // Take care of the orphans, so give them to init. - while ( firstchild ) - { - Process* orphan = firstchild; - firstchild = orphan->nextsibling; - if ( firstchild ) { firstchild->prevsibling = NULL; } - orphan->parent = init; - orphan->prevsibling = NULL; - orphan->nextsibling = init->firstchild; - if ( orphan->nextsibling ) { orphan->nextsibling->prevsibling = orphan; } - init->firstchild = orphan; - } - - // Remove the current process from the family tree. - if ( !prevsibling ) - { - parent->firstchild = nextsibling; - } - else - { - prevsibling->nextsibling = nextsibling; - } - - if ( nextsibling ) - { - nextsibling->prevsibling = prevsibling; - } - - // Close all the file descriptors. - descriptors.Reset(); - - // Make all threads belonging to process unrunnable. - for ( Thread* t = firstthread; t; t = t->nextsibling ) - { - Scheduler::EarlyWakeUp(t); - Scheduler::SetThreadState(t, Thread::State::NONE); - } - - // Delete the threads. - while ( firstthread ) - { - Thread* todelete = firstthread; - firstthread = firstthread->nextsibling; - delete todelete; - } - - // Now clean up the address space. - ResetAddressSpace(); - - // TODO: Actually delete the address space. This is a small memory leak - // of a couple pages. - - exitstatus = status; - nextsibling = parent->zombiechild; - if ( parent->zombiechild ) { parent->zombiechild->prevsibling = this; } - parent->zombiechild = this; - - // Notify the parent process that the child has become a zombie. - parent->OnChildProcessExit(this); - - // Now, as a final operation, get rid of the address space. This should - // return us to the original kernel address space containing nothing - // but the kernel. - Memory::DestroyAddressSpace(); - } - - void SysExit(int status) - { - CurrentProcess()->Exit(status); - - // And so, the process had vanished from existence. But as fate would - // have it, soon a replacement took its place. - Scheduler::ProcessTerminated(Syscall::InterruptRegs()); - Syscall::AsIs(); - } - - struct SysWait_t - { - union { size_t align1; pid_t pid; }; - union { size_t align2; int* status; }; - union { size_t align3; int options; }; - }; - - STATIC_ASSERT(sizeof(SysWait_t) <= sizeof(Thread::scstate)); - - void SysWaitCallback(Thread* thread, Process* exitee) - { - // See if this process matches what we are looking for. - SysWait_t* state = (SysWait_t*) thread->scstate; - if ( state->pid != -1 && state->pid != exitee->pid ) { return; } - - thread->onchildprocessexit = NULL; - - Syscall::ScheduleResumption(thread); - } - - pid_t SysWait(pid_t pid, int* status, int options) - { - Thread* thread = CurrentThread(); - Process* process = thread->process; - - if ( pid != -1 ) - { - Process* waitingfor = Process::Get(pid); - if ( !waitingfor ) { Error::Set(ECHILD); return -1; } - if ( waitingfor->parent != process ) { Error::Set(ECHILD); return -1; } - } - - // Find any zombie children matching the search description. - for ( Process* zombie = process->zombiechild; zombie; zombie = zombie->nextsibling ) - { - if ( pid != -1 && pid != zombie->pid ) { continue; } - - pid = zombie->pid; - // TODO: Validate that status is a valid user-space int! - if ( status ) { *status = zombie->exitstatus; } - - if ( zombie == process->zombiechild ) - { - process->zombiechild = zombie->nextsibling; - if ( zombie->nextsibling ) { zombie->nextsibling->prevsibling = NULL; } - } - else - { - zombie->prevsibling->nextsibling = zombie->nextsibling; - if ( zombie->nextsibling ) { zombie->nextsibling->prevsibling = zombie->prevsibling; } - } - - // And so, the process was fully deleted. - delete zombie; - - return pid; - } - - // The process needs to have children, otherwise we are waiting for - // nothing to happen. - if ( !process->firstchild ) { Error::Set(ECHILD); return -1; } - - // Resumes this system call when the wait condition has been met. - thread->onchildprocessexit = SysWaitCallback; - - // Resume the system call with these parameters. - thread->scfunc = (void*) SysWait; - SysWait_t* state = (SysWait_t*) thread->scstate; - state->pid = pid; - state->status = status; - state->options = options; - thread->scsize = sizeof(SysWait_t); - - // Now go do something else. - Syscall::Incomplete(); - return 0; - } - - int SysRegisterErrno(int* errnop) - { - CurrentProcess()->errnop = errnop; - return 0; - } - void* SysSbrk(intptr_t increment) { Process* process = CurrentProcess(); @@ -843,10 +837,10 @@ namespace Sortix Syscall::Register(SYSCALL_GETPPID, (void*) SysGetParentPID); Syscall::Register(SYSCALL_EXIT, (void*) SysExit); Syscall::Register(SYSCALL_WAIT, (void*) SysWait); - Syscall::Register(SYSCALL_REGISTER_ERRNO, (void*) SysRegisterErrno); Syscall::Register(SYSCALL_SBRK, (void*) SysSbrk); Syscall::Register(SYSCALL_GET_PAGE_SIZE, (void*) SysGetPageSize); + pidalloclock = KTHREAD_MUTEX_INITIALIZER; nextpidtoallocate = 0; pidlist = new SortedList(ProcessCompare); diff --git a/sortix/process.h b/sortix/process.h index f5518706..5e059d13 100644 --- a/sortix/process.h +++ b/sortix/process.h @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,19 +14,21 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . process.h - Describes a process belonging to a subsystem. + A named collection of threads. -******************************************************************************/ +*******************************************************************************/ #ifndef SORTIX_PROCESS_H #define SORTIX_PROCESS_H #include "descriptors.h" #include "cpu.h" +#include +#include namespace Sortix { @@ -34,7 +36,6 @@ namespace Sortix class Process; struct ProcessSegment; - const size_t DEFAULT_STACK_SIZE = 64*1024; const int SEG_NONE = 0; const int SEG_TEXT = 1; const int SEG_DATA = 2; @@ -61,6 +62,8 @@ namespace Sortix class Process { + friend void Process__OnLastThreadExit(void*); + public: Process(); ~Process(); @@ -73,28 +76,35 @@ namespace Sortix public: addr_t addrspace; - int exitstatus; char* workingdir; pid_t pid; - int* errnop; - public: + private: + // A process may only access its parent if parentlock is locked. A process + // may only use its list of children if childlock is locked. A process may + // not access its sibling processes. Process* parent; Process* prevsibling; Process* nextsibling; Process* firstchild; Process* zombiechild; + kthread_mutex_t childlock; + kthread_mutex_t parentlock; + kthread_cond_t zombiecond; + size_t zombiewaiting; + bool iszombie; + bool nozombify; + addr_t mmapfrom; + int exitstatus; public: Thread* firstthread; + kthread_mutex_t threadlock; public: DescriptorTable descriptors; ProcessSegment* segments; - public: - bool sigint; - public: int Execute(const char* programname, const byte* program, size_t programsize, int argc, const char* const* argv, @@ -102,36 +112,33 @@ namespace Sortix CPU::InterruptRegisters* regs); void ResetAddressSpace(); void Exit(int status); - - public: - bool IsSane() { return addrspace != 0; } + pid_t Wait(pid_t pid, int* status, int options); + bool DeliverSignal(int signum); + void OnThreadDestruction(Thread* thread); + int GetParentProcessId(); + void AddChildProcess(Process* child); + void ScheduleDeath(); + void AbortConstruction(); public: Process* Fork(); private: - Thread* ForkThreads(Process* processclone); void ExecuteCPU(int argc, char** argv, int envc, char** envp, addr_t stackpos, addr_t entry, CPU::InterruptRegisters* regs); + void OnLastThreadExit(); + void LastPrayer(); + void NotifyChildExit(Process* child, bool zombify); + void NotifyNewZombies(); public: void ResetForExecute(); - - public: - inline size_t DefaultStackSize() { return DEFAULT_STACK_SIZE; } - - private: - addr_t mmapfrom; - - public: addr_t AllocVirtualAddr(size_t size); - public: - void OnChildProcessExit(Process* process); - public: static Process* Get(pid_t pid); + static pid_t HackGetForegroundProcess(); private: static bool Put(Process* process); @@ -139,6 +146,8 @@ namespace Sortix }; + void InitializeThreadRegisters(CPU::InterruptRegisters* regs, + const sforkregs_t* requested); Process* CurrentProcess(); } diff --git a/sortix/scheduler.cpp b/sortix/scheduler.cpp index 280c5271..1074db12 100644 --- a/sortix/scheduler.cpp +++ b/sortix/scheduler.cpp @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,367 +14,280 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . scheduler.cpp - Handles context switching between tasks and deciding when to execute what. + Decides the order to execute threads in and switching between them. -******************************************************************************/ +*******************************************************************************/ #include -#include +#include +#include #include -#include +#include "x86-family/gdt.h" +#include "syscall.h" +#include "interrupt.h" +#include "time.h" #include "thread.h" #include "process.h" -#include "time.h" +#include "signal.h" #include "scheduler.h" -#include -#include "syscall.h" -#include "sound.h" // HACK FOR SIGINT -#include "x86-family/gdt.h" -namespace Sortix +using namespace Maxsi; + +namespace Sortix { +namespace Scheduler { + +static Thread* currentthread; +} // namespace Scheduler +Thread* CurrentThread() { return Scheduler::currentthread; } +Process* CurrentProcess() { return CurrentThread()->process; } +namespace Scheduler { + +uint8_t dummythreaddata[sizeof(Thread)] __attribute__ ((aligned (8))); +Thread* dummythread; +Thread* idlethread; +Thread* firstrunnablethread; +Thread* firstsleepingthread; +Process* initprocess; + +static inline void SetCurrentThread(Thread* newcurrentthread) { - void SysExit(int status); // HACK FOR SIGINT + currentthread = newcurrentthread; +} - // Internal forward-declarations. - namespace Scheduler +void LogBeginSwitch(Thread* current, const CPU::InterruptRegisters* regs); +void LogSwitch(Thread* current, Thread* next); +void LogEndSwitch(Thread* current, const CPU::InterruptRegisters* regs); + +static Thread* PopNextThread() +{ + if ( !firstrunnablethread ) { return idlethread; } + Thread* result = firstrunnablethread; + firstrunnablethread = firstrunnablethread->schedulerlistnext; + return result; +} + +static Thread* ValidatedPopNextThread() +{ + Thread* nextthread = PopNextThread(); + if ( !nextthread ) { Panic("Had no thread to switch to."); } + if ( nextthread->terminated ) { - 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); + PanicF("Running a terminated thread 0x%p", nextthread); } - - namespace Scheduler + addr_t newaddrspace = nextthread->process->addrspace; + if ( !Page::IsAligned(newaddrspace) ) { - byte dummythreaddata[sizeof(Thread)]; - Thread* dummythread; - Thread* currentthread; - Thread* idlethread; - Thread* firstrunnablethread; - Thread* firstsleepingthread; - Process* initprocess; - bool hacksigintpending = false; - - void Init() - { - // 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. - dummythread = (Thread*) &dummythreaddata; - Maxsi::Memory::Set(dummythread, 0, sizeof(*dummythread)); - currentthread = dummythread; - firstrunnablethread = NULL; - firstsleepingthread = NULL; - idlethread = NULL; - hacksigintpending = false; - - Syscall::Register(SYSCALL_SLEEP, (void*) SysSleep); - Syscall::Register(SYSCALL_USLEEP, (void*) SysUSleep); - - addr_t stackhigher = Memory::GetKernelStack(); - size_t stacksize = Memory::GetKernelStackSize(); - addr_t stacklower = stackhigher - stacksize; - int prot = PROT_KREAD | PROT_KWRITE; - if ( !Memory::MapRange(stacklower, stacksize, prot) ) - { - PanicF("could not create kernel stack (%zx to %zx)", - stacklower, stackhigher); - } - - GDT::SetKernelStack(stacklower, stacksize, stackhigher); - } - - // 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 SetInitProcess(Process* init) - { - initprocess = init; - } - - Process* GetInitProcess() - { - return initprocess; - } - - void MainLoop() - { - // 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); - } - - void Switch(CPU::InterruptRegisters* regs) - { - LogBeginContextSwitch(currentthread, regs); - - if ( hacksigintpending ) { HandleSigIntHack(regs); } - - WakeSleeping(); - - Thread* nextthread = PopNextThread(); - if ( !nextthread ) { Panic("had no thread to switch to"); } - if ( nextthread->terminated ) { PanicF("Running a terminated thread 0x%p", nextthread); } - - LogContextSwitch(currentthread, nextthread); - if ( nextthread == currentthread ) { return; } - - currentthread->SaveRegisters(regs); - nextthread->LoadRegisters(regs); - - addr_t newaddrspace = nextthread->process->addrspace; - if ( unlikely(newaddrspace != Page::AlignDown(newaddrspace)) ) - { - PanicF("Thread 0x%p, process %i (0x%p) (backup: %i), had bad " - "address space variable: 0x%zx: not page-aligned " - "(backup: 0x%zx)\n", nextthread, - nextthread->process->pid, nextthread->process, - nextthread->pidbackup, newaddrspace, - nextthread->addrspacebackup); - } - Memory::SwitchAddressSpace(newaddrspace); - currentthread = nextthread; - - nextthread->HandleSignal(regs); - - LogEndContextSwitch(currentthread, regs); - - if ( currentthread->scfunc ) { Syscall::Resume(regs); } - } - - void ProcessTerminated(CPU::InterruptRegisters* regs) - { - currentthread = dummythread; - Switch(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 ) - { - Log::PrintF("Switching from 0x%p", current); - state->LogRegisters(); - Log::Print("\n"); - } - } - - 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); - } - } - - 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() - { - if ( !firstrunnablethread ) { return idlethread; } - Thread* result = firstrunnablethread; - firstrunnablethread = firstrunnablethread->schedulerlistnext; - return result; - } - - void SetThreadState(Thread* thread, Thread::State state) - { - if ( thread->state == state ) { return; } - - if ( thread->state == Thread::State::RUNNABLE ) - { - 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; - } - - // Insert the thread into the scheduler's carousel linked list. - if ( state == Thread::State::RUNNABLE ) - { - if ( firstrunnablethread == NULL ) { firstrunnablethread = thread; } - thread->schedulerlistprev = firstrunnablethread->schedulerlistprev; - thread->schedulerlistnext = firstrunnablethread; - firstrunnablethread->schedulerlistprev = thread; - thread->schedulerlistprev->schedulerlistnext = thread; - } - - 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 ) - { - 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 ) - { - thread->nextsleepingthread = tmp->nextsleepingthread; - tmp->nextsleepingthread = thread; - return; - } - } - } - - void EarlyWakeUp(Thread* thread) - { - uintmax_t now = Time::MicrosecondsSinceBoot(); - if ( thread->sleepuntil < now ) { return; } - thread->sleepuntil = now; - - SetThreadState(thread, Thread::State::RUNNABLE); - - if ( firstsleepingthread == thread ) - { - firstsleepingthread = thread->nextsleepingthread; - thread->nextsleepingthread = NULL; - return; - } - - for ( Thread* tmp = firstsleepingthread; tmp->nextsleepingthread != NULL; tmp = tmp->nextsleepingthread ) - { - if ( tmp->nextsleepingthread == thread ) - { - tmp->nextsleepingthread = thread->nextsleepingthread; - thread->nextsleepingthread = NULL; - return; - } - } - } - - void WakeSleeping() - { - uintmax_t now = Time::MicrosecondsSinceBoot(); - - while ( firstsleepingthread && firstsleepingthread->sleepuntil < now ) - { - SetThreadState(firstsleepingthread, Thread::State::RUNNABLE); - Thread* next = firstsleepingthread->nextsleepingthread; - firstsleepingthread->nextsleepingthread = NULL; - firstsleepingthread = next; - } - } - - void HandleSigIntHack(CPU::InterruptRegisters* regs) - { - if ( currentthread == idlethread ) { return; } - - hacksigintpending = false; - - // HACK: Don't crash init or sh. - Process* process = CurrentProcess(); - if ( process->pid < 3 ) { return; } - - Sound::Mute(); - Log::PrintF("^C\n"); - - process->Exit(130); - currentthread = dummythread; - } - - void SigIntHack() - { - hacksigintpending = true; - } - - void SysSleep(size_t secs) - { - Thread* thread = currentthread; - uintmax_t timetosleep = ((uintmax_t) secs) * 1000ULL * 1000ULL; - if ( timetosleep == 0 ) - { - Switch(Syscall::InterruptRegs()); - Syscall::AsIs(); - return; - } - PutThreadToSleep(thread, timetosleep); - Syscall::Incomplete(); - } - - void SysUSleep(size_t usecs) - { - Thread* thread = currentthread; - uintmax_t timetosleep = usecs; - if ( timetosleep == 0 ) - { - Switch(Syscall::InterruptRegs()); - Syscall::AsIs(); - return; - } - PutThreadToSleep(thread, timetosleep); - Syscall::Incomplete(); - } + PanicF("Thread 0x%p, process %i (0x%p) (backup: %i), had bad " + "address space variable: 0x%zx: not page-aligned " + "(backup: 0x%zx)\n", nextthread, + nextthread->process->pid, nextthread->process, + -1/*nextthread->pidbackup*/, newaddrspace, + (addr_t)-1 /*nextthread->addrspacebackup*/); } + return nextthread; +} - Thread* CurrentThread() - { - return Scheduler::currentthread; - } +static void DoActualSwitch(CPU::InterruptRegisters* regs) +{ + Thread* current = CurrentThread(); + LogBeginSwitch(current, regs); - Process* CurrentProcess() + Thread* next = ValidatedPopNextThread(); + LogSwitch(current, next); + + if ( current == next ) { return; } + + current->SaveRegisters(regs); + next->LoadRegisters(regs); + + addr_t newaddrspace = next->addrspace; + Memory::SwitchAddressSpace(newaddrspace); + SetCurrentThread(next); + + addr_t stacklower = next->kernelstackpos; + size_t stacksize = next->kernelstacksize; + addr_t stackhigher = stacklower + stacksize; + ASSERT(stacklower && stacksize && stackhigher); + GDT::SetKernelStack(stacklower, stacksize, stackhigher); + + LogEndSwitch(next, regs); +} + +void Switch(CPU::InterruptRegisters* regs) +{ + DoActualSwitch(regs); + if ( regs->signal_pending && regs->InUserspace() ) + Signal::Dispatch(regs); +} + +const bool DEBUG_BEGINCTXSWITCH = false; +const bool DEBUG_CTXSWITCH = false; +const bool DEBUG_ENDCTXSWITCH = false; + +void LogBeginSwitch(Thread* current, const CPU::InterruptRegisters* regs) +{ + bool alwaysdebug = false; + bool isidlethread = current == idlethread; + bool dodebug = DEBUG_BEGINCTXSWITCH && !isidlethread; + if ( alwaysdebug || dodebug ) { - return Scheduler::currentthread->process; + Log::PrintF("Switching from 0x%p", current); + regs->LogRegisters(); + Log::Print("\n"); } } + +void LogSwitch(Thread* current, Thread* next) +{ + bool alwaysdebug = false; + bool different = current == idlethread; + bool dodebug = DEBUG_CTXSWITCH && different; + if ( alwaysdebug || dodebug ) + { + Log::PrintF("switching from %u:%u (0x%p) to %u:%u (0x%p) \n", + current->process->pid, 0, current, + next->process->pid, 0, next); + } +} + +void LogEndSwitch(Thread* current, const CPU::InterruptRegisters* regs) +{ + bool alwaysdebug = false; + bool isidlethread = current == idlethread; + bool dodebug = DEBUG_BEGINCTXSWITCH && !isidlethread; + if ( alwaysdebug || dodebug ) + { + Log::PrintF("Switched to 0x%p", current); + regs->LogRegisters(); + Log::Print("\n"); + } +} + +static void InterruptYieldCPU(CPU::InterruptRegisters* regs, void* /*user*/) +{ + Switch(regs); +} + +static void ThreadExitCPU(CPU::InterruptRegisters* regs, void* /*user*/) +{ + SetThreadState(currentthread, Thread::State::DEAD); + InterruptYieldCPU(regs, NULL); +} + +// The idle thread serves no purpose except being an infinite loop that does +// nothing, which is only run when the system has nothing to do. +void SetIdleThread(Thread* thread) +{ + ASSERT(!idlethread); + idlethread = thread; + SetThreadState(thread, Thread::State::NONE); + SetCurrentThread(thread); +} + +void SetDummyThreadOwner(Process* process) +{ + dummythread->process = process; +} + +void SetInitProcess(Process* init) +{ + initprocess = init; +} + +Process* GetInitProcess() +{ + return initprocess; +} + +void SetThreadState(Thread* thread, Thread::State state) +{ + bool wasenabled = Interrupt::SetEnabled(false); + + // Remove the thread from the list of runnable threads. + if ( thread->state == Thread::State::RUNNABLE && + state != Thread::State::RUNNABLE ) + { + if ( thread == firstrunnablethread ) { firstrunnablethread = thread->schedulerlistnext; } + if ( thread == firstrunnablethread ) { firstrunnablethread = NULL; } + ASSERT(thread->schedulerlistprev); + ASSERT(thread->schedulerlistnext); + thread->schedulerlistprev->schedulerlistnext = thread->schedulerlistnext; + thread->schedulerlistnext->schedulerlistprev = thread->schedulerlistprev; + thread->schedulerlistprev = NULL; + thread->schedulerlistnext = NULL; + } + + // Insert the thread into the scheduler's carousel linked list. + if ( thread->state != Thread::State::RUNNABLE && + state == Thread::State::RUNNABLE ) + { + if ( firstrunnablethread == NULL ) { firstrunnablethread = thread; } + thread->schedulerlistprev = firstrunnablethread->schedulerlistprev; + thread->schedulerlistnext = firstrunnablethread; + firstrunnablethread->schedulerlistprev = thread; + thread->schedulerlistprev->schedulerlistnext = thread; + } + + thread->state = state; + + ASSERT(thread->state != Thread::State::RUNNABLE || thread->schedulerlistprev); + ASSERT(thread->state != Thread::State::RUNNABLE || thread->schedulerlistnext); + + Interrupt::SetEnabled(wasenabled); +} + +Thread::State GetThreadState(Thread* thread) +{ + return thread->state; +} + +void SysSleep(size_t secs) +{ + uintmax_t timetosleep = ((uintmax_t) secs) * 1000ULL * 1000ULL; + uint32_t wakeat = Time::MicrosecondsSinceBoot() + timetosleep; + do { Yield(); } + while ( Time::MicrosecondsSinceBoot() < wakeat ); +} + +void SysUSleep(size_t usecs) +{ + uintmax_t timetosleep = (uintmax_t) usecs; + uint32_t wakeat = Time::MicrosecondsSinceBoot() + timetosleep; + do { Yield(); } + while ( Time::MicrosecondsSinceBoot() < wakeat ); +} + +extern "C" void yield_cpu_handler(); +extern "C" void thread_exit_handler(); + +void Init() +{ + // We use a dummy so that the first context switch won't crash when the + // current thread is accessed. This lets us avoid checking whether it is + // NULL (which it only will be once), which gives simpler code. + dummythread = (Thread*) &dummythreaddata; + Maxsi::Memory::Set(dummythread, 0, sizeof(*dummythread)); + dummythread->schedulerlistprev = dummythread; + dummythread->schedulerlistnext = dummythread; + currentthread = dummythread; + firstrunnablethread = NULL; + firstsleepingthread = NULL; + idlethread = NULL; + + // Register our raw handler with user-space access. It calls our real + // handler after common interrupt preparation stuff has occured. + Interrupt::RegisterRawHandler(129, yield_cpu_handler, true); + Interrupt::RegisterHandler(129, InterruptYieldCPU, NULL); + Interrupt::RegisterRawHandler(132, thread_exit_handler, true); + Interrupt::RegisterHandler(132, ThreadExitCPU, NULL); + + Syscall::Register(SYSCALL_SLEEP, (void*) SysSleep); + Syscall::Register(SYSCALL_USLEEP, (void*) SysUSleep); +} + +} // namespace Scheduler +} // namespace Sortix diff --git a/sortix/scheduler.h b/sortix/scheduler.h index 1cb5c22d..c8630e32 100644 --- a/sortix/scheduler.h +++ b/sortix/scheduler.h @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,40 +14,34 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . scheduler.h - Handles context switching between tasks and deciding when to execute what. + Decides the order to execute threads in and switching between them. -******************************************************************************/ +*******************************************************************************/ #ifndef SORTIX_SCHEDULER_H #define SORTIX_SCHEDULER_H #include "thread.h" -namespace Sortix -{ - namespace Scheduler - { - void Init(); - void MainLoop() SORTIX_NORETURN; - void Switch(CPU::InterruptRegisters* regs); - void ProcessTerminated(CPU::InterruptRegisters* regs); - void SetIdleThread(Thread* thread); - void SetDummyThreadOwner(Process* process); - void SetInitProcess(Process* init); - Process* GetInitProcess(); +namespace Sortix { +namespace Scheduler { - void SetThreadState(Thread* thread, Thread::State state); - Thread::State GetThreadState(Thread* thread); - void PutThreadToSleep(Thread* thread, uintmax_t usecs); - void EarlyWakeUp(Thread* thread); +void Init(); +void Switch(CPU::InterruptRegisters* regs); +inline static void Yield() { asm volatile ("int $129"); } +inline static void ExitThread() { asm volatile ("int $132"); } +void SetThreadState(Thread* thread, Thread::State state); +Thread::State GetThreadState(Thread* thread); +void SetIdleThread(Thread* thread); +void SetDummyThreadOwner(Process* process); +void SetInitProcess(Process* init); +Process* GetInitProcess(); - void SigIntHack(); - } -} +} // namespace Scheduler +} // namespace Sortix #endif - diff --git a/sortix/serialterminal.h b/sortix/serialterminal.h index 7e47968c..49bb94e0 100644 --- a/sortix/serialterminal.h +++ b/sortix/serialterminal.h @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,13 +14,13 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . serialterminal.h A terminal on a serial line. -******************************************************************************/ +*******************************************************************************/ #ifndef SORTIX_SERIALTERMINAL_H #define SORTIX_SERIALTERMINAL_H diff --git a/sortix/signal.cpp b/sortix/signal.cpp index 94d869dd..665b37c0 100644 --- a/sortix/signal.cpp +++ b/sortix/signal.cpp @@ -1,6 +1,6 @@ /****************************************************************************** - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -23,222 +23,114 @@ ******************************************************************************/ #include -#include #include +#include +#include +#include "interrupt.h" +#include "thread.h" #include "signal.h" using namespace Maxsi; -namespace Sortix +namespace Sortix { + +// A per-cpu value whether a signal is pending in the running task. +extern "C" { volatile unsigned long asm_signal_is_pending = 0; } + +namespace Signal { + +const int PRIORITIES[SIG__NUM_DECLARED] = { - const int PRIORITY_NORMAL = 0; - const int PRIORITY_HIGH = 1; - const int PRIORITY_STOP = 2; - const int PRIORITY_CORE = 3; - const int PRIORITY_KILL = 4; + SIG_PRIO_NORMAL, // unused + SIG_PRIO_NORMAL, // SIGHUP + SIG_PRIO_NORMAL, // SIGINT + SIG_PRIO_NORMAL, // SIGQUIT + SIG_PRIO_CORE, // SIGILL + SIG_PRIO_CORE, // SIGTRAP + SIG_PRIO_CORE, // SIGABRT + SIG_PRIO_CORE, // SIGEMT + SIG_PRIO_CORE, // SIGFPE + SIG_PRIO_KILL, // SIGKILL + SIG_PRIO_CORE, // SIGBUS + SIG_PRIO_CORE, // SIGSEGV + SIG_PRIO_CORE, // SIGSYS + SIG_PRIO_NORMAL, // SIGPIPE + SIG_PRIO_NORMAL, // SIGALRM + SIG_PRIO_NORMAL, // SIGTERM + SIG_PRIO_NORMAL, // SIGUSR1 + SIG_PRIO_NORMAL, // SIGUSR2 + SIG_PRIO_NORMAL, // SIGCHLD + SIG_PRIO_HIGH, // SIGPWR + SIG_PRIO_NORMAL, // SIGWINCH + SIG_PRIO_NORMAL, // SIGURG + SIG_PRIO_NORMAL, // obsolete + SIG_PRIO_STOP, // SIGSTOP + SIG_PRIO_STOP, // SIGTSTP + SIG_PRIO_STOP, // SIGCONT + SIG_PRIO_STOP, // SIGTTIN + SIG_PRIO_STOP, // SIGTTOU + SIG_PRIO_NORMAL, // SIGVTALRM + SIG_PRIO_NORMAL, // obsolete + SIG_PRIO_CORE, // SIGXCPU + SIG_PRIO_CORE, // SIGXFSZ + SIG_PRIO_NORMAL, // SIGCORE + SIG_PRIO_NORMAL, // SIGLWP + SIG_PRIO_NORMAL, // SIGAIO +}; - const int PRIORITIES[Maxsi::Signal::NUMSIGNALS] = - { - PRIORITY_NORMAL, // unused - PRIORITY_NORMAL, // SIGHUP - PRIORITY_NORMAL, // SIGINT - PRIORITY_NORMAL, // SIGQUIT - PRIORITY_CORE, // SIGILL - PRIORITY_CORE, // SIGTRAP - PRIORITY_CORE, // SIGABRT - PRIORITY_CORE, // SIGEMT - PRIORITY_CORE, // SIGFPE - PRIORITY_KILL, // SIGKILL - PRIORITY_CORE, // SIGBUS - PRIORITY_CORE, // SIGSEGV - PRIORITY_CORE, // SIGSYS - PRIORITY_NORMAL, // SIGPIPE - PRIORITY_NORMAL, // SIGALRM - PRIORITY_NORMAL, // SIGTERM - PRIORITY_NORMAL, // SIGUSR1 - PRIORITY_NORMAL, // SIGUSR2 - PRIORITY_NORMAL, // SIGCHLD - PRIORITY_HIGH, // SIGPWR - PRIORITY_NORMAL, // SIGWINCH - PRIORITY_NORMAL, // SIGURG - PRIORITY_NORMAL, // obsolete - PRIORITY_STOP, // SIGSTOP - PRIORITY_STOP, // SIGTSTP - PRIORITY_STOP, // SIGCONT - PRIORITY_STOP, // SIGTTIN - PRIORITY_STOP, // SIGTTOU - PRIORITY_NORMAL, // SIGVTALRM - PRIORITY_NORMAL, // obsolete - PRIORITY_CORE, // SIGXCPU - PRIORITY_CORE, // SIGXFSZ - PRIORITY_NORMAL, // SIGCORE - PRIORITY_NORMAL, // SIGLWP - PRIORITY_NORMAL, // SIGAIO - }; - - // Returns true of the exact ordering of this signal version others aren't - // important - if it returns false, then this signal will be put in the - // queue according to priority, instead of being merged into another signal - // with the same signum. - bool Unifiable(int /*signum*/) - { - return true; - } - - // Returns whether a specific signal is more important to deliver than - // another. This is used to schedule signals. - int CompareSignalPriority(int siga, int sigb) - { - int prioa = PRIORITY_NORMAL; - int priob = PRIORITY_NORMAL; - - if ( siga < Maxsi::Signal::NUMSIGNALS ) { prioa = PRIORITIES[siga]; } - if ( sigb < Maxsi::Signal::NUMSIGNALS ) { priob = PRIORITIES[sigb]; } - - if ( prioa < priob ) { return -1; } else - if ( prioa > priob ) { return 1; } - return 0; - } - - SignalQueue::SignalQueue() - { - queue = NULL; - } - - SignalQueue::~SignalQueue() - { - while ( queue ) - { - Signal* todelete = queue; - queue = queue->nextsignal; - delete todelete; - } - } - - // Queues the signal and schedules it for processing. - bool SignalQueue::Push(int signum) - { - ASSERT(0 <= signum && signum < 128); - - if ( Unifiable(signum) ) - { - for ( Signal* signal = queue; signal != NULL; signal = signal->nextsignal ) - { - if ( signal->signum != signum ) { continue; } - - signal->numpending++; - return true; - } - } - - Signal* signal = new Signal; - if ( !signal ) { return false; } - - signal->signum = signum; - signal->numpending = 1; - signal->nextsignal = NULL; - signal->returncode = 128 + signum; - - Insert(signal); - - return true; - } - - // Insert the signal in O(N), which is pretty fast for small Ns. - void SignalQueue::Insert(Signal* signal) - { - if ( !queue ) - { - queue = signal; - last = signal; - return; - } - - // If the signal is to be inserted last, then just do it quickly. - if ( last != NULL && 0 <= CompareSignalPriority(last->signum, signal->signum) ) - { - last->nextsignal = signal; - signal->nextsignal = NULL; - last = signal; - return; - } - - // Check if the signal should be inserted first. - if ( queue != NULL && CompareSignalPriority(queue->signum, signal->signum) < 0 ) - { - signal->nextsignal = queue; - queue = signal; - return; - } - - // Find where the signal should be inserted. - for ( Signal* tmp = queue; tmp != NULL; tmp = tmp->nextsignal ) - { - Signal* next = tmp->nextsignal; - - if ( next != NULL && CompareSignalPriority(next->signum, signal->signum) < 0 ) - { - tmp->nextsignal = signal; - signal->nextsignal = next; - return; - } - - if ( next == NULL ) - { - tmp->nextsignal = signal; - signal->nextsignal = NULL; - last = signal; - return; - } - } - } - - // Given the stack of currently processing signals, return a new signal if - // it is more important to handle at this point. - Signal* SignalQueue::Pop(Signal* current) - { - if ( queue == NULL ) { return NULL; } - - bool returnqueue = false; - - // If we are currently handling no signal, then just return the first. - if ( current == NULL ) { returnqueue = true; } - - // If we are handling a signal, only override it with another if it is - // more important. - else if ( CompareSignalPriority(current->signum, queue->signum) < 0 ) - { - returnqueue = true; - } - - if ( returnqueue ) - { - Signal* result = queue; - queue = queue->nextsignal; - result->nextsignal = NULL; - return result; - } - - return NULL; - } - - Signal* Signal::Fork() - { - Signal* clone = new Signal(); - if ( !clone ) { return NULL; } - - Memory::Copy(clone, this, sizeof(Signal)); - - Signal* nextsignalclone = NULL; - if ( nextsignal ) - { - nextsignalclone = nextsignal->Fork(); - if ( !nextsignalclone ) { delete clone; return NULL; } - } - - clone->nextsignal = nextsignalclone; - - return clone; - } +int Priority(int signum) +{ + ASSERT(0 <= signum && signum < SIG_MAX_NUM); + if ( !signum ) + return -1; + if ( SIG__NUM_DECLARED <= signum ) + return SIG_PRIO_NORMAL; + return PRIORITIES[signum]; } +Queue::Queue() +{ + for ( int i = 1; i < SIG_MAX_NUM; i++ ) + pending[i] = false; +} + +void Queue::Push(int signum) +{ + ASSERT(0 < signum && signum < SIG_MAX_NUM); + pending[signum] = true; +} + +int Queue::Pop(int cursig) +{ + int best = 0; + int bestprio = Priority(cursig); + for ( int i = 1; i < SIG_MAX_NUM; i++ ) + if ( pending[i] && bestprio < Priority(i) ) + { + best = i; + bestprio = Priority(i); + } + pending[best] = false; + return best; +} + +void Dispatch(CPU::InterruptRegisters* regs, void* /*user*/) +{ + return CurrentThread()->HandleSignal(regs); +} + +void Return(CPU::InterruptRegisters* regs, void* /*user*/) +{ + return CurrentThread()->HandleSigreturn(regs); +} + +void Init() +{ + Interrupt::RegisterRawHandler(130, isr130, true); + Interrupt::RegisterHandler(130, Dispatch, NULL); + Interrupt::RegisterRawHandler(131, isr131, true); + Interrupt::RegisterHandler(131, Return, NULL); +} + +} // namespace Signal +} // namespace Sortix diff --git a/sortix/signal.h b/sortix/signal.h index c8683957..e3f6d2eb 100644 --- a/sortix/signal.h +++ b/sortix/signal.h @@ -1,6 +1,6 @@ /****************************************************************************** - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -25,76 +25,39 @@ #ifndef SORTIX_SIGNAL_H #define SORTIX_SIGNAL_H -#include #include "cpu.h" -namespace Sortix +namespace Sortix { + +extern "C" volatile unsigned long asm_signal_is_pending; + +namespace Signal { + +class Queue { - #define SIGHUP 1 /* Hangup */ - #define SIGINT 2 /* Interrupt */ - #define SIGQUIT 3 /* Quit */ - #define SIGILL 4 /* Illegal Instruction */ - #define SIGTRAP 5 /* Trace/Breakpoint Trap */ - #define SIGABRT 6 /* Abort */ - #define SIGEMT 7 /* Emulation Trap */ - #define SIGFPE 8 /* Arithmetic Exception */ - #define SIGKILL 9 /* Killed */ - #define SIGBUS 10 /* Bus Error */ - #define SIGSEGV 11 /* Segmentation Fault */ - #define SIGSYS 12 /* Bad System Call */ - #define SIGPIPE 13 /* Broken Pipe */ - #define SIGALRM 14 /* Alarm Clock */ - #define SIGTERM 15 /* Terminated */ - #define SIGUSR1 16 /* User Signal 1 */ - #define SIGUSR2 17 /* User Signal 2 */ - #define SIGCHLD 18 /* Child Status */ - #define SIGPWR 19 /* Power Fail/Restart */ - #define SIGWINCH 20 /* Window Size Change */ - #define SIGURG 21 /* Urgent Socket Condition */ - #define SIGSTOP 23 /* Stopped (signal) */ - #define SIGTSTP 24 /* Stopped (user) */ - #define SIGCONT 25 /* Continued */ - #define SIGTTIN 26 /* Stopped (tty input) */ - #define SIGTTOU 27 /* Stopped (tty output) */ - #define SIGVTALRM 28 /* Virtual Timer Expired */ - #define SIGXCPU 30 /* CPU time limit exceeded */ - #define SIGXFSZ 31 /* File size limit exceeded */ - #define SIGWAITING 32 /* All LWPs blocked */ - #define SIGLWP 33 /* Virtual Interprocessor Interrupt for Threads Library */ - #define SIGAIO 34 /* Asynchronous I/O */ +public: + Queue(); - struct Signal - { - int signum; - int returncode; - unsigned numpending; - CPU::InterruptRegisters regs; - Signal* nextsignal; +// TODO: This is the wrong data structure for the problem! +private: + bool pending[SIG_MAX_NUM]; - public: - Signal* Fork(); +// TODO: This is not SMP ready: +// To avoid race conditions, these should be called with interrupts off. +public: + void Push(int signum); + int Pop(int cursig); - }; +}; - class SignalQueue - { - public: - SignalQueue(); - ~SignalQueue(); +void Init(); +int Priority(int signum); +void Dispatch(CPU::InterruptRegisters* regs, void* user = NULL); +void Return(CPU::InterruptRegisters* regs, void* user = NULL); +inline bool IsPending() { return asm_signal_is_pending != 0; } - private: - Signal* queue; - Signal* last; - - public: - bool Push(int signum); - Signal* Pop(Signal* current); - - private: - void Insert(Signal* signal); - - }; -} +} // namespace Signal +} // namespace Sortix #endif diff --git a/sortix/syscall.cpp b/sortix/syscall.cpp index a98f6564..bd865ab0 100644 --- a/sortix/syscall.cpp +++ b/sortix/syscall.cpp @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,13 +14,13 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . syscall.h - Handles system calls from userspace safely. + Handles system calls from userspace. -******************************************************************************/ +*******************************************************************************/ #include #include @@ -39,15 +39,14 @@ namespace Sortix { extern "C" { - CPU::SyscallRegisters* syscall_state_ptr; - unsigned system_was_incomplete; size_t SYSCALL_MAX; volatile void* syscall_list[SYSCALL_MAX_NUM]; } int BadSyscall() { - // TODO: Send signal, set errno, or crash/abort process? + Log::PrintF("I am the bad system call!\n"); + // TODO: Send signal, set errnx o, or crash/abort process? return -1; } @@ -70,87 +69,6 @@ namespace Sortix syscall_list[index] = funcptr; } - - void Incomplete() - { - Thread* thread = CurrentThread(); - - system_was_incomplete = 1; - - CPU::InterruptRegisters* regs = InterruptRegs(); - thread->SaveRegisters(regs); - Scheduler::SetThreadState(thread, Thread::State::BLOCKING); - Scheduler::Switch(regs); - } - - void Yield() - { - Panic("Syscall::Yield() is not implemented because it caused " - "instability and other issues."); - } - - void AsIs() - { - system_was_incomplete = 1; - } - - void ScheduleResumption(Thread* thread) - { - Scheduler::SetThreadState(thread, Thread::State::RUNNABLE); - } - - extern "C" void update_userspace_errno() - { - int error = Error::Last(); - if ( !error ) { return; } - Process* process = CurrentProcess(); - if ( !process->errnop ) { return; } - // TODO: Validate that process->errno is in userspace memory! - *process->errnop = error; - } - - extern "C" size_t resume_syscall(void* scfunc, size_t scsize, size_t* scstate); - - void Resume(CPU::InterruptRegisters* regs) - { - Thread* thread = CurrentThread(); - - syscall_state_ptr = (CPU::SyscallRegisters*) regs; - - ASSERT(thread->scfunc); - - size_t* scstate = thread->scstate; - size_t scsize = thread->scsize; - void* scfunc = thread->scfunc; - - system_was_incomplete = 0; - Error::Set(0); - - size_t result = resume_syscall(scfunc, scsize, scstate); - - bool incomplete = (system_was_incomplete); - - system_was_incomplete = 1; - - if ( !incomplete ) - { - syscall_state_ptr->result = result; - update_userspace_errno(); - return; - } - - Incomplete(); - } - - CPU::InterruptRegisters* InterruptRegs() - { - return (CPU::InterruptRegisters*) syscall_state_ptr; - } - - CPU::SyscallRegisters* SyscallRegs() - { - return syscall_state_ptr; - } } } diff --git a/sortix/syscall.h b/sortix/syscall.h index c0dffef8..a2d20424 100644 --- a/sortix/syscall.h +++ b/sortix/syscall.h @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,13 +14,13 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . syscall.h - Handles system calls from userspace safely. + Handles system calls from userspace. -******************************************************************************/ +*******************************************************************************/ #ifndef SORTIX_SYSCALL_H #define SORTIX_SYSCALL_H @@ -36,33 +36,6 @@ namespace Sortix { void Init(); void Register(size_t index, void* funcptr); - - // Aborts the current system call such that the current thread is marked - // as blocking, and control is transferred to the next runnable thread. - // This allows the thread to wait for an condition that makes the thread - // runnable again and stores the return value in the proper register. - // Once called, you may safely return from the system call in good faith - // that its return value shall be discarded. - void Incomplete(); - - // Call this prior to Incomplete() to signal that the scheduler should - // go run something else for a moment. The current thread will not be - // marked as blocking. - void Yield(); - - // For when you want the syscall exit code not to modify registers. - void AsIs(); - - // Retries a system call by making the thread runnable and then calling - // the system call code whenever the thread is scheduled to run. - void ScheduleResumption(Thread* thread); - - // Retries a system call based on the Thread::sc* values of the current - // thread and if it succeeds, sets the proper registers. - void Resume(CPU::InterruptRegisters* regs); - - CPU::InterruptRegisters* InterruptRegs(); - CPU::SyscallRegisters* SyscallRegs(); } } diff --git a/sortix/textterminal.cpp b/sortix/textterminal.cpp index 87aeaaaa..8a5c3bf3 100644 --- a/sortix/textterminal.cpp +++ b/sortix/textterminal.cpp @@ -36,6 +36,7 @@ const uint16_t ATTR_CHAR = 1U << 0U; TextTerminal::TextTerminal(TextBufferHandle* textbufhandle) { this->textbufhandle = textbufhandle; textbufhandle->Refer(); + this->termlock = KTHREAD_MUTEX_INITIALIZER; Reset(); } @@ -57,6 +58,7 @@ void TextTerminal::Reset() size_t TextTerminal::Print(const char* string, size_t stringlen) { + ScopedLock lock(&termlock); TextBuffer* textbuf = textbufhandle->Acquire(); for ( size_t i = 0; i < stringlen; i++ ) PutChar(textbuf, string[i]); @@ -67,6 +69,7 @@ size_t TextTerminal::Print(const char* string, size_t stringlen) size_t TextTerminal::Width() const { + ScopedLock lock(&termlock); TextBuffer* textbuf = textbufhandle->Acquire(); size_t width = textbuf->Width(); textbufhandle->Release(textbuf); @@ -75,6 +78,7 @@ size_t TextTerminal::Width() const size_t TextTerminal::Height() const { + ScopedLock lock(&termlock); TextBuffer* textbuf = textbufhandle->Acquire(); size_t height = textbuf->Height(); textbufhandle->Release(textbuf); diff --git a/sortix/textterminal.h b/sortix/textterminal.h index 90738bcd..7c42ada5 100644 --- a/sortix/textterminal.h +++ b/sortix/textterminal.h @@ -25,6 +25,8 @@ #ifndef SORTIX_TEXTTERMINAL_H #define SORTIX_TEXTTERMINAL_H +#include + namespace Sortix { class TextBufferHandle; @@ -51,6 +53,7 @@ private: private: mutable TextBufferHandle* textbufhandle; + mutable kthread_mutex_t termlock; uint8_t vgacolor; unsigned column; unsigned line; diff --git a/sortix/thread.cpp b/sortix/thread.cpp index 83ef1901..a1733d4b 100644 --- a/sortix/thread.cpp +++ b/sortix/thread.cpp @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,23 +14,25 @@ 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 . + 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 +#include +#include #include +#include #include #include -#include "event.h" #include "process.h" #include "thread.h" #include "scheduler.h" -#include +#include "interrupt.h" #include "time.h" #include "syscall.h" @@ -44,135 +46,64 @@ namespace Sortix process = NULL; prevsibling = NULL; nextsibling = NULL; - event = NULL; - eventnextwaiting = NULL; - sleepuntil = 0; - nextsleepingthread = NULL; schedulerlistprev = NULL; schedulerlistnext = NULL; state = NONE; Maxsi::Memory::Set(®isters, 0, sizeof(registers)); - ready = false; - scfunc = NULL; - currentsignal = NULL; + stackpos = 0; + stacksize = 0; + kernelstackpos = 0; + kernelstacksize = 0; + kernelstackmalloced = false; + currentsignal = 0; + siglevel = 0; sighandler = NULL; - pidbackup = -1; - addrspacebackup = 0UL; terminated = false; - ResetCallbacks(); - } - - Thread::Thread(const Thread* forkfrom) - { - id = forkfrom->id; - process = NULL; - prevsibling = NULL; - nextsibling = NULL; - state = forkfrom->state; - event = NULL; - eventnextwaiting = NULL; - sleepuntil = forkfrom->sleepuntil; - Maxsi::Memory::Copy(®isters, &forkfrom->registers, sizeof(registers)); - ready = false; - stackpos = forkfrom->stackpos; - stacksize = forkfrom->stacksize; - nextsleepingthread = NULL; - schedulerlistprev = NULL; - schedulerlistnext = NULL; - scfunc = NULL; - sighandler = forkfrom->sighandler; - pidbackup = -1; - addrspacebackup = 0UL; - terminated = false; - ResetCallbacks(); - } - - void Thread::ResetCallbacks() - { - onchildprocessexit = NULL; } Thread::~Thread() { - ASSERT(CurrentProcess() == process); - ASSERT(nextsleepingthread == NULL); - - if ( event ) { event->Unregister(this); } - - // Delete information about signals being processed. - while ( currentsignal ) - { - Signal* todelete = currentsignal; - currentsignal = currentsignal->nextsignal; - delete todelete; - } - - Memory::UnmapRange(stackpos, stacksize); - + if ( process ) + process->OnThreadDestruction(this); + ASSERT(CurrentThread() != this); + if ( kernelstackmalloced ) + delete[] (uint8_t*) kernelstackpos; terminated = true; } - Thread* Thread::Fork() + addr_t Thread::SwitchAddressSpace(addr_t newaddrspace) { - ASSERT(ready); - - Signal* clonesignal = NULL; - if ( currentsignal ) - { - clonesignal = currentsignal->Fork(); - if ( !clonesignal ) { return NULL; } - } - - Thread* clone = new Thread(this); - if ( !clone ) - { - while ( clonesignal ) - { - Signal* todelete = clonesignal; - clonesignal = clonesignal->nextsignal; - delete todelete; - } - return NULL; - } - - clone->currentsignal = clonesignal; - - return clone; + bool wasenabled = Interrupt::SetEnabled(false); + addr_t result = addrspace; + addrspace = newaddrspace; + Memory::SwitchAddressSpace(newaddrspace); + Interrupt::SetEnabled(wasenabled); + return result; } - void CreateThreadCPU(Thread* thread, addr_t entry); - - Thread* CreateThread(addr_t entry, size_t stacksize) + // Last chance to clean up user-space things before this thread dies. + void Thread::LastPrayer() { - Process* process = CurrentProcess(); + Memory::UnmapRange(stackpos, stacksize); + Memory::Flush(); + } - if ( stacksize == 0 ) { stacksize = process->DefaultStackSize(); } + extern "C" void BootstrapKernelThread(void* user, ThreadEntry entry) + { + entry(user); + kthread_exit(); + } - // 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; } + Thread* CreateKernelThread(Process* process, CPU::InterruptRegisters* regs) + { + ASSERT(process && regs && process->addrspace); + Thread* thread = new Thread; + if ( !thread ) { return NULL; } - int prot = PROT_FORK | PROT_READ | PROT_WRITE | PROT_KREAD | PROT_KWRITE; - if ( !Memory::MapRange(stackpos, stacksize, prot) ) - { - // TODO: Free the reserved virtual memory area. - return NULL; - } + thread->addrspace = process->addrspace; + thread->SaveRegisters(regs); - Thread* thread = new Thread(); - if ( !thread ) - { - Memory::UnmapRange(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); + kthread_mutex_lock(&process->threadlock); // Create the family tree. thread->process = process; @@ -181,63 +112,128 @@ namespace Sortix thread->nextsibling = firsty; process->firstthread = thread; - thread->Ready(); - - Scheduler::SetThreadState(thread, Thread::State::RUNNABLE); + kthread_mutex_unlock(&process->threadlock); return thread; } - void Thread::Ready() + Thread* CreateKernelThread(Process* process, ThreadEntry entry, void* user, + size_t stacksize) { - if ( ready ) { return; } - ready = true; + const size_t DEFAULT_KERNEL_STACK_SIZE = 64*8192UL; + if ( !stacksize ) { stacksize = DEFAULT_KERNEL_STACK_SIZE; } + uint8_t* stack = new uint8_t[stacksize]; + if ( !stack ) { return NULL; } - this->pidbackup = process->pid; - this->addrspacebackup = process->addrspace; + CPU::InterruptRegisters regs; + SetupKernelThreadRegs(®s, entry, user, (addr_t) stack, stacksize); - 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); - } + Thread* thread = CreateKernelThread(process, ®s); + if ( !thread ) { delete[] stack; } + + thread->kernelstackpos = (addr_t) stack; + thread->kernelstacksize = stacksize; + thread->kernelstackmalloced = true; + + return thread; + } + + Thread* CreateKernelThread(ThreadEntry entry, void* user, size_t stacksize) + { + return CreateKernelThread(CurrentProcess(), entry, user, stacksize); + } + + void StartKernelThread(Thread* thread) + { + Scheduler::SetThreadState(thread, Thread::State::RUNNABLE); + } + + Thread* RunKernelThread(Process* process, CPU::InterruptRegisters* regs) + { + Thread* thread = CreateKernelThread(process, regs); + if ( !thread ) { return NULL; } + StartKernelThread(thread); + return thread; + } + + Thread* RunKernelThread(Process* process, ThreadEntry entry, void* user, + size_t stacksize) + { + Thread* thread = CreateKernelThread(process, entry, user, stacksize); + if ( !thread ) { return NULL; } + StartKernelThread(thread); + return thread; + } + + Thread* RunKernelThread(ThreadEntry entry, void* user, size_t stacksize) + { + Thread* thread = CreateKernelThread(entry, user, stacksize); + if ( !thread ) { return NULL; } + StartKernelThread(thread); + return thread; } void Thread::HandleSignal(CPU::InterruptRegisters* regs) { - Signal* override = signalqueue.Pop(currentsignal); - if ( !override ) { return; } - if ( !sighandler ) { delete override; return; } + int signum = signalqueue.Pop(currentsignal); + regs->signal_pending = 0; - if ( override->signum == SIGKILL ) + if ( !signum ) + return; + + if ( !sighandler ) + return; + + if ( SIG_NUM_LEVELS <= siglevel ) + return; + + // Signals can't return to kernel mode because the kernel stack may have + // been overwritten by a system call during the signal handler. Correct + // the return state so it returns to userspace and not the kernel. + if ( !regs->InUserspace() ) + HandleSignalFixupRegsCPU(regs); + + if ( signum == SIGKILL ) { - + // We need to run the OnSigKill method here with interrupts enabled + // and on our own stack. But this method this may have been called + // from the scheduler on any stack, so we need to do a little + // bootstrap and switch to our own stack. + GotoOnSigKill(regs); + return; } - - override->nextsignal = currentsignal; - Maxsi::Memory::Copy(&override->regs, regs, sizeof(override->regs)); - currentsignal = override; + int level = siglevel++; + signums[level] = currentsignal = signum; + Maxsi::Memory::Copy(sigregs + level, regs, sizeof(*regs)); HandleSignalCPU(regs); } - void SysSigReturn() + void Thread::HandleSigreturn(CPU::InterruptRegisters* regs) { - Thread* thread = CurrentThread(); - if ( !thread->currentsignal ) { return; } + if ( !siglevel ) + return; - CPU::InterruptRegisters* dest = Syscall::InterruptRegs(); - CPU::InterruptRegisters* src = &thread->currentsignal->regs; + siglevel--; - Maxsi::Memory::Copy(dest, src, sizeof(CPU::InterruptRegisters)); - thread->currentsignal = thread->currentsignal->nextsignal; - Syscall::AsIs(); + currentsignal = siglevel ? signums[siglevel-1] : 0; + Maxsi::Memory::Copy(regs, sigregs + siglevel, sizeof(*regs)); + regs->signal_pending = 0; + + // Check if a more important signal is pending. + HandleSignal(regs); + } + + extern "C" void Thread__OnSigKill(Thread* thread) + { + thread->OnSigKill(); + } + + void Thread::OnSigKill() + { + LastPrayer(); + kthread_exit(); } void SysRegisterSignalHandler(sighandler_t sighandler) @@ -245,13 +241,38 @@ namespace Sortix CurrentThread()->sighandler = sighandler; } + void Thread::SetHavePendingSignals() + { + // TODO: This doesn't really work if Interrupt::IsCPUInterrupted()! + if ( CurrentThread() == this ) + asm_signal_is_pending = 1; + else + registers.signal_pending = 1; + } + + bool Thread::DeliverSignal(int signum) + { + if ( signum <= 0 || 128 <= signum ) { Error::Set(EINVAL); return false; } + + bool wasenabled = Interrupt::SetEnabled(false); + signalqueue.Push(signum); + SetHavePendingSignals(); + Interrupt::SetEnabled(wasenabled); + + return true; + } + int SysKill(pid_t pid, int signum) { - if ( signum < 0 || 128 <= signum ) { Error::Set(EINVAL); return -1; } - // Protect the system idle process. - if ( pid == 0 ) { Error::Set(EPERM); return -1; } + if ( !pid ) { Error::Set(EPERM); return -1; } + // If we kill our own process, deliver the signal to this thread. + Thread* currentthread = CurrentThread(); + if ( currentthread->process->pid == pid ) + return currentthread->DeliverSignal(signum) ? 0 : -1; + + // TODO: Race condition: The process could be deleted while we use it. Process* process = Process::Get(pid); if ( !process ) { Error::Set(ESRCH); return -1; } @@ -259,31 +280,18 @@ namespace Sortix // TODO: Check for permission. // TODO: Check for zombies. - Thread* currentthread = CurrentThread(); - Thread* thread = NULL; - if ( currentthread->process->pid == pid ) { thread = currentthread; } - if ( !thread ) { thread = process->firstthread; } - if ( !thread ) { Error::Set(ESRCH); return -1; /* TODO: Zombie? */ } + return process->DeliverSignal(signum) ? 0 : -1; + } - // TODO: If thread is not runnable, wake it and runs its handler? - if ( !thread->signalqueue.Push(signum) ) - { - // TODO: Possibly kill the process? - } - - if ( thread == currentthread ) - { - Syscall::SyscallRegs()->result = 0; - thread->HandleSignal(Syscall::InterruptRegs()); - } - - return 0; + int SysRaise(int signum) + { + return CurrentThread()->DeliverSignal(signum) ? 0 : -1; } void Thread::Init() { Syscall::Register(SYSCALL_KILL, (void*) SysKill); + Syscall::Register(SYSCALL_RAISE, (void*) SysRaise); Syscall::Register(SYSCALL_REGISTER_SIGNAL_HANDLER, (void*) SysRegisterSignalHandler); - Syscall::Register(SYSCALL_SIGRETURN, (void*) SysSigReturn); } } diff --git a/sortix/thread.h b/sortix/thread.h index 14f0b25d..ebe3327a 100644 --- a/sortix/thread.h +++ b/sortix/thread.h @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,45 +14,75 @@ 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 . + 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 +#include #include "signal.h" +typedef struct multiboot_info multiboot_info_t; + namespace Sortix { - class Event; class Process; class Thread; - // Adds a thread to the current process. - Thread* CreateThread(addr_t entry, size_t stacksize = 0); - Thread* CurrentThread(); + extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo); + + typedef void (*ThreadEntry)(void* user); + + // Simply exits the kernel thread. + void KernelThreadExit() SORTIX_NORETURN; + + // Internally used as a kernel thread entry point that exits the thread + // upon the actual thread entry returning. + extern "C" void BootstrapKernelThread(void* user, ThreadEntry entry) SORTIX_NORETURN; + + // These functions create a new kernel process but doesn't start it. + Thread* CreateKernelThread(Process* process, CPU::InterruptRegisters* regs); + Thread* CreateKernelThread(Process* process, ThreadEntry entry, void* user, + size_t stacksize = 0); + Thread* CreateKernelThread(ThreadEntry entry, void* user, size_t stacksize = 0); + + // This function can be used to start a thread from the above functions. + void StartKernelThread(Thread* thread); + + // Alternatively, these functions both create and start the thread. + Thread* RunKernelThread(Process* process, CPU::InterruptRegisters* regs); + Thread* RunKernelThread(Process* process, ThreadEntry entry, void* user, + size_t stacksize = 0); + Thread* RunKernelThread(ThreadEntry entry, void* user, size_t stacksize = 0); + + void SetupKernelThreadRegs(CPU::InterruptRegisters* regs, ThreadEntry entry, + void* user, addr_t stack, size_t stacksize); + + extern "C" void Thread__OnSigKill(Thread* thread); typedef void (*sighandler_t)(int); class Thread { - friend Thread* CreateThread(addr_t entry, size_t stacksize); + friend Thread* CreateKernelThread(Process* process, + CPU::InterruptRegisters* regs); + friend void KernelInit(unsigned long magic, multiboot_info_t* bootinfo); + friend void Thread__OnSigKill(Thread* thread); public: - enum State { NONE, RUNNABLE, BLOCKING }; + enum State { NONE, RUNNABLE, BLOCKING, DEAD }; public: static void Init(); private: Thread(); - Thread(const Thread* forkfrom); - void ResetCallbacks(); public: ~Thread(); @@ -60,64 +90,53 @@ namespace Sortix public: size_t id; Process* process; - pid_t pidbackup; - addr_t addrspacebackup; bool terminated; Thread* prevsibling; Thread* nextsibling; - // These are used internally when a thread is waiting for an Event to - // happen. Consider them private. - public: - Event* event; - Thread* eventnextwaiting; - // These are some things used internally by the scheduler and should not be // touched by anything but it. Consider it private. public: Thread* schedulerlistprev; Thread* schedulerlistnext; - State state; - uintmax_t sleepuntil; - Thread* nextsleepingthread; + volatile State state; public: + addr_t addrspace; addr_t stackpos; size_t stacksize; - Signal* currentsignal; - SignalQueue signalqueue; sighandler_t sighandler; - - // 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(); + addr_t kernelstackpos; + size_t kernelstacksize; + bool kernelstackmalloced; private: CPU::InterruptRegisters registers; + Signal::Queue signalqueue; + int currentsignal; + int siglevel; + int signums[SIG_NUM_LEVELS]; + CPU::InterruptRegisters sigregs[SIG_NUM_LEVELS]; public: - Thread* Fork(); void SaveRegisters(const CPU::InterruptRegisters* src); void LoadRegisters(CPU::InterruptRegisters* dest); void HandleSignal(CPU::InterruptRegisters* regs); + void HandleSigreturn(CPU::InterruptRegisters* regs); + bool DeliverSignal(int signum); + addr_t SwitchAddressSpace(addr_t newaddrspace); private: + void GotoOnSigKill(CPU::InterruptRegisters* regs); + void OnSigKill() SORTIX_NORETURN; + void LastPrayer(); + void SetHavePendingSignals(); + void HandleSignalFixupRegsCPU(CPU::InterruptRegisters* regs); void HandleSignalCPU(CPU::InterruptRegisters* regs); - public: - void* scfunc; - size_t scsize; - size_t scstate[8]; - - public: - void (*onchildprocessexit)(Thread*, Process*); - }; + + Thread* CurrentThread(); } #endif diff --git a/sortix/x64/interrupt.s b/sortix/x64/interrupt.s index 8832d641..7f3af21d 100644 --- a/sortix/x64/interrupt.s +++ b/sortix/x64/interrupt.s @@ -1,6 +1,6 @@ /******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011, 2012. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -255,6 +255,20 @@ isr128: pushq $0 # err_code pushq $128 # int_no jmp interrupt_handler_prepare +.global isr130 +.type isr130, @function +isr130: + cli + pushq $0 # err_code + pushq $130 # int_no + jmp interrupt_handler_prepare +.global isr131 +.type isr131, @function +isr131: + cli + pushq $0 # err_code + pushq $131 # int_no + jmp interrupt_handler_prepare .global irq0 .type irq0, @function irq0: @@ -367,8 +381,24 @@ irq15: pushq $0 # err_code pushq $47 # int_no jmp interrupt_handler_prepare +.global yield_cpu_handler +.type yield_cpu_handler, @function +yield_cpu_handler: + cli + pushq $0 # err_code + pushq $129 # int_no + jmp interrupt_handler_prepare +.global thread_exit_handler +.type thread_exit_handler, @function +thread_exit_handler: + cli + pushq $0 # err_code + pushq $132 # int_no + jmp interrupt_handler_prepare interrupt_handler_prepare: + movq $1, asm_is_cpu_interrupted + pushq %r15 pushq %r14 pushq %r13 @@ -401,10 +431,27 @@ interrupt_handler_prepare: movq %cr2, %rbp pushq %rbp + # Push the current kernel errno value. + movl global_errno, %ebp + pushq %rbp + + # Push whether a signal is pending. + movq asm_signal_is_pending, %rbp + pushq %rbp + # Now call the interrupt handler. movq %rsp, %rdi call interrupt_handler +load_interrupted_registers: + # Restore whether signals are pending. + popq %rbp + movq %rbp, asm_signal_is_pending + + # Restore the previous kernel errno. + popq %rbp + movl %ebp, global_errno + # Remove CR2 from the stack. addq $8, %rsp @@ -418,7 +465,7 @@ interrupt_handler_prepare: popq %rdi popq %rsi popq %rbp - popq %rsp + addq $8, %rsp # Don't pop %rsp, may not be defined. popq %rbx popq %rdx popq %rcx @@ -435,6 +482,8 @@ interrupt_handler_prepare: # Remove int_no and err_code addq $16, %rsp + movq $0, asm_is_cpu_interrupted + # Return to where we came from. iretq @@ -451,3 +500,10 @@ asm_interrupts_are_enabled: andq $0x000200, %rax # FLAGS_INTERRUPT retq +.global load_registers +.type load_registers, @function +load_registers: + # Let the register struct become our temporary stack + movq %rdi, %rsp + jmp load_interrupted_registers + diff --git a/sortix/x64/kthread.s b/sortix/x64/kthread.s new file mode 100644 index 00000000..3921e9bd --- /dev/null +++ b/sortix/x64/kthread.s @@ -0,0 +1,85 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2012. + + This file is part of Sortix. + + Sortix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Sortix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . + + x64/kthread.s + Utilities and synchronization mechanisms for x64 kernel threads. + +*******************************************************************************/ + +.section .text + +.global kthread_mutex_trylock +.type kthread_mutex_trylock, @function +kthread_mutex_trylock: + pushq %rbp + movq %rsp, %rbp + movl $-1, %eax + xchgl (%rdi), %eax + not %eax + leaveq + retq + +.global kthread_mutex_lock +.type kthread_mutex_lock, @function +kthread_mutex_lock: + pushq %rbp + movq %rsp, %rbp +kthread_mutex_lock_retry: + movl $-1, %eax + xchgl (%rdi), %eax + testl %eax, %eax + jnz kthread_mutex_lock_failed + leaveq + retq +kthread_mutex_lock_failed: + int $0x81 # Yield the CPU. + jmp kthread_mutex_lock_retry + +.global kthread_mutex_lock_signal +.type kthread_mutex_lock_signal, @function +kthread_mutex_lock_signal: + pushq %rbp + movq %rsp, %rbp +kthread_mutex_lock_signal_retry: + movq asm_signal_is_pending, %rax + testq %rax, %rax + jnz kthread_mutex_lock_signal_pending + movl $-1, %eax + xchgl (%rdi), %eax + testl %eax, %eax + jnz kthread_mutex_lock_signal_failed + inc %eax +kthread_mutex_lock_signal_out: + leaveq + retq +kthread_mutex_lock_signal_failed: + int $0x81 # Yield the CPU. + jmp kthread_mutex_lock_signal_retry +kthread_mutex_lock_signal_pending: + xorl %eax, %eax + jmp kthread_mutex_lock_signal_out + +.global kthread_mutex_unlock +.type kthread_mutex_unlock, @function +kthread_mutex_unlock: + pushq %rbp + movq %rsp, %rbp + movl $0, (%rdi) + leaveq + retq diff --git a/sortix/x64/memorymanagement.cpp b/sortix/x64/memorymanagement.cpp index 0473adc0..5a073c5c 100644 --- a/sortix/x64/memorymanagement.cpp +++ b/sortix/x64/memorymanagement.cpp @@ -1,6 +1,6 @@ /******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011, 2012. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -23,11 +23,11 @@ *******************************************************************************/ #include +#include #include #include "multiboot.h" -#include -#include #include "x86-family/memorymanagement.h" +#include "interrupt.h" namespace Sortix { @@ -120,17 +120,18 @@ namespace Sortix PML* pml = PMLS[level] + offset; for ( size_t i = 0; i < ENTRIES; i++ ) { - if ( !(pml->entry[i] & PML_PRESENT) ) { continue; } - if ( !(pml->entry[i] & PML_USERSPACE) ) { continue; } - if ( !(pml->entry[i] & PML_FORK) ) { continue; } + addr_t entry = pml->entry[i]; + if ( !(entry & PML_PRESENT) ) { continue; } + if ( !(entry & PML_USERSPACE) ) { continue; } + if ( !(entry & PML_FORK) ) { continue; } if ( level > 1 ) { RecursiveFreeUserspacePages(level-1, offset * ENTRIES + i); } addr_t addr = pml->entry[i] & PML_ADDRESS; - pml->entry[i] = 0; - Page::Put(addr); + // No need to unmap the page, we just need to mark it as unused. + Page::PutUnlocked(addr); } } - void DestroyAddressSpace() + void DestroyAddressSpace(addr_t fallback, void (*func)(addr_t, void*), void* user) { // Look up the last few entries used for the fractal mapping. These // cannot be unmapped as that would destroy the world. Instead, we @@ -143,17 +144,31 @@ namespace Sortix addr_t fractal1 = (PMLS[2] + 510UL * 512UL + 510UL)->entry[510]; addr_t dir = currentdir; - // First let's do the safe part. Garbage collect any PML1/0's left - // behind by user-space. These are completely safe to delete. + // We want to free the pages, but we are still using them ourselves, + // so lock the page allocation structure until we are done. + Page::Lock(); + + // In case any pages wasn't cleaned at this point. +#warning Page::Put calls may internally Page::Get and then reusing pages we are not done with just yet RecursiveFreeUserspacePages(TOPPMLLEVEL, 0); // Switch to the address space from when the world was originally // created. It should contain the kernel, the whole kernel, and // nothing but the kernel. PML* const BOOTPML4 = (PML* const) 0x21000UL; - SwitchAddressSpace((addr_t) BOOTPML4); + if ( !fallback ) + fallback = (addr_t) BOOTPML4; - // Now safely mark the pages as unused. + if ( func ) + func(fallback, user); + else + SwitchAddressSpace(fallback); + + // Ok, now we got marked everything left behind as unused, we can + // now safely let another thread use the pages. + Page::Unlock(); + + // These are safe to free since we switched address space. Page::Put(fractal3 & PML_ADDRESS); Page::Put(fractal2 & PML_ADDRESS); Page::Put(fractal1 & PML_ADDRESS); diff --git a/sortix/x64/process.cpp b/sortix/x64/process.cpp index 9de05cf9..dd74a3cb 100644 --- a/sortix/x64/process.cpp +++ b/sortix/x64/process.cpp @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,15 +14,17 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . x64/process.cpp CPU-specific process code. -******************************************************************************/ +*******************************************************************************/ #include +#include +#include #include "process.h" namespace Sortix @@ -31,12 +33,47 @@ namespace Sortix addr_t stackpos, addr_t entry, CPU::InterruptRegisters* regs) { + const uint64_t CS = 0x18; + const uint64_t DS = 0x20; + const uint64_t RPL = 0x3; + regs->rdi = argc; regs->rsi = (size_t) argv; regs->rdx = envc; regs->rcx = (size_t) envp; regs->rip = entry; regs->userrsp = stackpos & ~(15UL); - regs->rbp = stackpos; + regs->rbp = regs->userrsp; + regs->cs = CS | RPL; + regs->ds = DS | RPL; + regs->ss = DS | RPL; + regs->rflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; + } + + void InitializeThreadRegisters(CPU::InterruptRegisters* regs, + const sforkregs_t* requested) + { + Maxsi::Memory::Set(regs, 0, sizeof(*regs)); + regs->rip = requested->rip; + regs->userrsp = requested->rsp; + regs->rax = requested->rax; + regs->rbx = requested->rbx; + regs->rcx = requested->rcx; + regs->rdx = requested->rdx; + regs->rdi = requested->rdi; + regs->rsi = requested->rsi; + regs->rbp = requested->rbp; + regs->r8 = requested->r8; + regs->r9 = requested->r9; + regs->r10 = requested->r10; + regs->r11 = requested->r11; + regs->r12 = requested->r12; + regs->r13 = requested->r13; + regs->r14 = requested->r14; + regs->r15 = requested->r15; + regs->cs = 0x18 | 0x3; + regs->ds = 0x20 | 0x3; + regs->ss = 0x20 | 0x3; + regs->rflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; } } diff --git a/sortix/x64/syscall.s b/sortix/x64/syscall.s index 2fe79704..4460c666 100644 --- a/sortix/x64/syscall.s +++ b/sortix/x64/syscall.s @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,73 +14,41 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . - syscall.s + x64/syscall.s An assembly stub that acts as glue for system calls. -******************************************************************************/ +*******************************************************************************/ .global syscall_handler -.global resume_syscall .section .text .type syscall_handler, @function syscall_handler: - cli + # The processor disabled interrupts during the int $0x80 instruction, + # however Sortix system calls runs with interrupts enabled such that they + # can be pre-empted. + sti - # Compabillity with InterruptRegisters. - pushq $0x0 - pushq $0x80 - - pushq %r15 - pushq %r14 - pushq %r13 - pushq %r12 - pushq %r11 - pushq %r10 - pushq %r9 - pushq %r8 - pushq %rax - pushq %rcx - pushq %rdx - pushq %rbx - pushq %rsp + movl $0, global_errno # Reset errno pushq %rbp - pushq %rsi - pushq %rdi - # Push the user-space data segment. + # Grant ourselves kernel permissions to the data segment. movl %ds, %ebp pushq %rbp - - # Load the kernel data segment. movw $0x10, %bp movl %ebp, %ds movl %ebp, %es movl %ebp, %fs movl %ebp, %gs - # Compabillity with InterruptRegisters. - movq %cr2, %rbp - pushq %rbp - - # Store the state structure's pointer so the call can modify it if needed. - movq %rsp, syscall_state_ptr - - # By default, assume the system call was complete. - movl $0, system_was_incomplete - - # Reset the kernel errno. - movl $0, global_errno - - # Make sure the requested system call is valid. + # Make sure the requested system call is valid, if not, then fix it. cmp SYSCALL_MAX, %rax - jb valid_rax - xorq %rax, %rax + jae fix_syscall -valid_rax: +valid_syscall: # Read a system call function pointer. xorq %rbp, %rbp movq syscall_list(%rbp,%rax,8), %rax @@ -88,72 +56,40 @@ valid_rax: # Oh how nice, user-space put the parameters in: rdi, rsi, rdx, rcx, r8, r9 # Call the system call. - callq *%rax + callq *%rax - # Test if the system call was incomplete - movl system_was_incomplete, %ebx - testl %ebx, %ebx - - # If the system call was incomplete, the value in %eax is meaningless. - jg return_to_userspace - - # The system call was completed, so store the return value. - movq %rax, 72(%rsp) - - # Don't forget to update userspace's errno value. - call update_userspace_errno - -return_to_userspace: - # Compabillity with InterruptRegisters. - addq $8, %rsp - - # Restore the user-space data segment. + # Restore the previous permissions to data segment. popq %rbp movl %ebp, %ds movl %ebp, %es movl %ebp, %fs movl %ebp, %gs - popq %rdi - popq %rsi + # Return to user-space, system call result in %rax, errno in %edx. popq %rbp - popq %rsp - popq %rbx - popq %rdx - popq %rcx - popq %rax - popq %r8 - popq %r9 - popq %r10 - popq %r11 - popq %r12 - popq %r13 - popq %r14 - popq %r15 + movl global_errno, %edx - # Compabillity with InterruptRegisters. - addq $16, %rsp + # If any signals are pending, fire them now. + movq asm_signal_is_pending, %rdi + testq %rdi, %rdi + jnz call_signal_dispatcher - # Return to user-space. iretq -.type resume_syscall, @function -resume_syscall: - pushq %rbp - movq %rsp, %rbp - - movq %rdi, %rax - movq %rdx, %r11 - - movq 0(%r11), %rdi - movq 8(%r11), %rsi - movq 16(%r11), %rdx - movq 24(%r11), %rcx - movq 32(%r11), %r8 - movq 40(%r11), %r9 - - callq *%rax - - leaveq - retq +fix_syscall: + # Call the null system call instead. + xorq %rax, %rax + jmp valid_syscall +call_signal_dispatcher: + # We can't return to this location after the signal, since if any system + # call is made this stack will get reused and all our nice temporaries wil + # be garbage. We therefore pass the kernel the state to return to and it'll + # handle it for us when the signal is over. + movq 0(%rsp), %rdi # userspace rip + movq 16(%rsp), %rsi # userspace rflags + movq 24(%rsp), %rcx # userspace rsp, note %rdx is used for errno + int $130 # Deliver pending signals. + # If we end up here, it means that the signal didn't override anything and + # that we should just go ahead and return to userspace ourselves. + iretq diff --git a/sortix/x64/thread.cpp b/sortix/x64/thread.cpp index c973e444..4f092d00 100644 --- a/sortix/x64/thread.cpp +++ b/sortix/x64/thread.cpp @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,13 +14,13 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . thread.cpp x64 specific parts of thread.cpp. -******************************************************************************/ +*******************************************************************************/ #include #include "thread.h" @@ -50,6 +50,8 @@ namespace Sortix registers.ds = src->ds; registers.ss = src->ss; registers.rflags = src->rflags; + registers.kerrno = src->kerrno; + registers.signal_pending = src->signal_pending; } void Thread::LoadRegisters(CPU::InterruptRegisters* dest) @@ -75,65 +77,78 @@ namespace Sortix dest->ds = registers.ds; dest->ss = registers.ss; dest->rflags = registers.rflags; + dest->kerrno = registers.kerrno; + dest->signal_pending = registers.signal_pending; } - 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) + void SetupKernelThreadRegs(CPU::InterruptRegisters* regs, ThreadEntry entry, + void* user, addr_t stack, size_t stacksize) { - const uint64_t CS = 0x18; - const uint64_t DS = 0x20; - const uint64_t RPL = 0x3; + // Instead of directly calling the desired entry point, we call a small + // stub that calls it for us and then destroys the kernel thread if + // the entry function returns. Note that since we use a register based + // calling convention, we call BootstrapKernelThread directly. + regs->rip = (addr_t) BootstrapKernelThread; + regs->userrsp = stack + stacksize; + regs->rax = 0; + regs->rbx = 0; + regs->rcx = 0; + regs->rdx = 0; + regs->rdi = (addr_t) user; + regs->rsi = (addr_t) entry; + regs->rbp = 0; + regs->r8 = 0; + regs->r9 = 0; + regs->r10 = 0; + regs->r11 = 0; + regs->r12 = 0; + regs->r13 = 0; + regs->r14 = 0; + regs->r15 = 0; + regs->cs = KCS | KRPL; + regs->ds = KDS | KRPL; + regs->ss = KDS | KRPL; + regs->rflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; + regs->kerrno = 0; + regs->signal_pending = 0; + } - CPU::InterruptRegisters regs; - regs.rip = entry; - regs.userrsp = thread->stackpos + thread->stacksize; - regs.rax = 0; - regs.rbx = 0; - regs.rcx = 0; - regs.rdx = 0; - regs.rdi = 0; - regs.rsi = 0; - regs.rbp = thread->stackpos + thread->stacksize; - regs.r8 = 0; - regs.r9 = 0; - regs.r10 = 0; - regs.r11 = 0; - regs.r12 = 0; - regs.r13 = 0; - regs.r14 = 0; - regs.r15 = 0; - regs.cs = CS | RPL; - regs.ds = DS | RPL; - regs.ss = DS | RPL; - regs.rflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; - - thread->SaveRegisters(®s); + void Thread::HandleSignalFixupRegsCPU(CPU::InterruptRegisters* regs) + { + if ( regs->InUserspace() ) + return; + regs->rip = regs->rdi; + regs->rflags = regs->rsi; + regs->userrsp = regs->rcx; + regs->cs = UCS | URPL; + regs->ds = UDS | URPL; + regs->ss = UDS | URPL; } void Thread::HandleSignalCPU(CPU::InterruptRegisters* regs) { - regs->rdi = currentsignal->signum; + const size_t STACK_ALIGNMENT = 16UL; + const size_t RED_ZONE_SIZE = 128UL; + regs->userrsp -= RED_ZONE_SIZE; + regs->userrsp &= ~(STACK_ALIGNMENT-1UL); + regs->rbp = regs->userrsp; + regs->rdi = currentsignal; regs->rip = (size_t) sighandler; + regs->rflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; + regs->kerrno = 0; + regs->signal_pending = 0; + } + + void Thread::GotoOnSigKill(CPU::InterruptRegisters* regs) + { + regs->rip = (unsigned long) Thread__OnSigKill; + regs->rdi = (unsigned long) this; + regs->userrsp = regs->rbp = kernelstackpos + kernelstacksize; + regs->rflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; + regs->cs = KCS | KRPL; + regs->ds = KDS | KRPL; + regs->ss = KDS | KRPL; + regs->kerrno = 0; + regs->signal_pending = 0; } } diff --git a/sortix/x64/x64.h b/sortix/x64/x64.h index 49a37827..56ef2f14 100644 --- a/sortix/x64/x64.h +++ b/sortix/x64/x64.h @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,13 +14,13 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . x64.h CPU stuff for the x64 platform. -******************************************************************************/ +*******************************************************************************/ #ifndef SORTIX_X64_H #define SORTIX_X64_H @@ -33,7 +33,7 @@ namespace Sortix { struct InterruptRegisters { - uint64_t cr2; + uint64_t signal_pending, kerrno, cr2; uint64_t ds; // Data segment selector uint64_t rdi, rsi, rbp, rsp, rbx, rdx, rcx, rax; uint64_t r8, r9, r10, r11, r12, r13, r14, r15; @@ -42,18 +42,17 @@ namespace Sortix public: void LogRegisters() const; - }; + bool InUserspace() const { return (cs & 0x3) != 0; } - struct SyscallRegisters - { - uint64_t cr2; // For compabillity with above, may be removed soon. - uint64_t ds; - uint64_t di, si, bp, trash, b, d, c; union { uint64_t a; uint64_t result; }; - uint64_t r8, r9, r10, r11, r12, r13, r14, r15; - uint64_t int_no, err_code; // Also compabillity. - uint64_t ip, cs, flags, sp, ss; }; } + + const uint64_t KCS = 0x08; + const uint64_t KDS = 0x10; + const uint64_t KRPL = 0x0; + const uint64_t UCS = 0x18; + const uint64_t UDS = 0x20; + const uint64_t URPL = 0x3; } #endif diff --git a/sortix/x86-family/memorymanagement.cpp b/sortix/x86-family/memorymanagement.cpp index 27e02d9d..60d3f3f1 100644 --- a/sortix/x86-family/memorymanagement.cpp +++ b/sortix/x86-family/memorymanagement.cpp @@ -192,6 +192,8 @@ namespace Sortix "restrictions.\n", Page::pagesnotonstack * 0x1000UL); } + Memory::Unmap(0x0); // Remove NULL. + // Finish allocating the top level PMLs for the kernels use. AllocateKernelPMLs(); } diff --git a/sortix/x86-family/x86-family.h b/sortix/x86-family/x86-family.h index 11b079dd..e2e0ba2a 100644 --- a/sortix/x86-family/x86-family.h +++ b/sortix/x86-family/x86-family.h @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,13 +14,13 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . x86-family.h CPU stuff for the x86 CPU family. -******************************************************************************/ +*******************************************************************************/ #ifndef SORTIX_X86_FAMILY_H #define SORTIX_X86_FAMILY_H @@ -38,6 +38,28 @@ namespace Sortix void Reboot(); void ShutDown(); } + + const size_t FLAGS_CARRY = (1<<0); // 0x000001 + const size_t FLAGS_RESERVED1 = (1<<1); // 0x000002, read as one + const size_t FLAGS_PARITY = (1<<2); // 0x000004 + const size_t FLAGS_RESERVED2 = (1<<3); // 0x000008 + const size_t FLAGS_AUX = (1<<4); // 0x000010 + const size_t FLAGS_RESERVED3 = (1<<5); // 0x000020 + const size_t FLAGS_ZERO = (1<<6); // 0x000040 + const size_t FLAGS_SIGN = (1<<7); // 0x000080 + const size_t FLAGS_TRAP = (1<<8); // 0x000100 + const size_t FLAGS_INTERRUPT = (1<<9); // 0x000200 + const size_t FLAGS_DIRECTION = (1<<10); // 0x000400 + const size_t FLAGS_OVERFLOW = (1<<11); // 0x000800 + const size_t FLAGS_IOPRIVLEVEL = (1<<12) | (1<<13); + const size_t FLAGS_NESTEDTASK = (1<<14); // 0x004000 + const size_t FLAGS_RESERVED4 = (1<<15); // 0x008000 + const size_t FLAGS_RESUME = (1<<16); // 0x010000 + const size_t FLAGS_VIRTUAL8086 = (1<<17); // 0x020000 + const size_t FLAGS_ALIGNCHECK = (1<<18); // 0x040000 + const size_t FLAGS_VIRTINTR = (1<<19); // 0x080000 + const size_t FLAGS_VIRTINTRPEND = (1<<20); // 0x100000 + const size_t FLAGS_ID = (1<<21); // 0x200000 } #endif diff --git a/sortix/x86/interrupt.s b/sortix/x86/interrupt.s index fb09f746..bcc1ae1f 100644 --- a/sortix/x86/interrupt.s +++ b/sortix/x86/interrupt.s @@ -1,6 +1,6 @@ /******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011, 2012. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -255,6 +255,20 @@ isr128: pushl $0 # err_code pushl $128 # int_no jmp interrupt_handler_prepare +.global isr130 +.type isr130, @function +isr130: + cli + pushl $0 # err_code + pushl $130 # int_no + jmp interrupt_handler_prepare +.global isr131 +.type isr131, @function +isr131: + cli + pushl $0 # err_code + pushl $131 # int_no + jmp interrupt_handler_prepare .global irq0 .type irq0, @function irq0: @@ -367,8 +381,29 @@ irq15: pushl $0 # err_code pushl $47 # int_no jmp interrupt_handler_prepare +.global yield_cpu_handler +.type yield_cpu_handler, @function +yield_cpu_handler: + cli + pushl $0 # err_code + pushl $129 # int_no + jmp interrupt_handler_prepare +.global thread_exit_handler +.type thread_exit_handler, @function +thread_exit_handler: + cli + pushl $0 # err_code + pushl $132 # int_no + jmp interrupt_handler_prepare interrupt_handler_prepare: + movl $1, asm_is_cpu_interrupted + + # Check if an interrupt happened while having kernel permissions. + testw $0x3, 12(%esp) # cs + jz fixup_relocate_stack +fixup_relocate_stack_complete: + pushl %eax pushl %ecx pushl %edx @@ -393,11 +428,28 @@ interrupt_handler_prepare: movl %cr2, %ebp pushl %ebp + # Push the current kernel errno value. + movl global_errno, %ebp + pushl %ebp + + # Push whether a signal is pending. + movl asm_signal_is_pending, %ebp + pushl %ebp + # Now call the interrupt handler. pushl %esp call interrupt_handler addl $4, %esp +load_interrupted_registers: + # Restore whether signals are pending. + popl %ebp + movl %ebp, asm_signal_is_pending + + # Restore the previous kernel errno. + popl %ebp + movl %ebp, global_errno + # Remove CR2 from the stack. addl $4, %esp @@ -411,7 +463,7 @@ interrupt_handler_prepare: popl %edi popl %esi popl %ebp - popl %esp + addl $4, %esp # Don't pop %esp, may not be defined. popl %ebx popl %edx popl %ecx @@ -420,9 +472,107 @@ interrupt_handler_prepare: # Remove int_no and err_code addl $8, %esp + movl $0, asm_is_cpu_interrupted + + # If interrupted with kernel permissions we may need to switch stack. + testw $0x3, 4(%esp) # int_no and err_code now gone, so cs is at 4(%esp). + jz fixup_switch_stack +fixup_switch_stack_complete: + # Return to where we came from. iret +fixup_relocate_stack: + # Ok, so some genius at Intel decided that if the permission level does not + # change during an interrupt then the CPU won't push the stack pointer and + # it won't reload it during iret. This seriously messes up the scheduler + # that wants to preempt kernel threads each with their own stack. The + # scheduler will attempt to read (and modify) the stack value which doesn't + # exist and worse: the value at that location is likely used by the + # interrupted kernel thread. A quick and dirty solution is to simply move + # the stack 8 bytes down the stack. Right now there are the 5 elements on + # the stack (eflags, cs, eip, err_code, int_no) of 5 bytes each. + mov %eax, -4-8(%esp) # Save eax + mov 0(%esp), %eax # int_no + mov %eax, 0-8(%esp) + mov 4(%esp), %eax # err_code + mov %eax, 4-8(%esp) + mov 8(%esp), %eax # eip + mov %eax, 8-8(%esp) + mov 12(%esp), %eax # cs + mov %eax, 12-8(%esp) + mov 16(%esp), %eax # eflags + mov %eax, 16-8(%esp) + # Next up we have to fake what the CPU should have done: pushed ss and esp. + mov %esp, %eax + addl $5*4, %eax # Calculate original esp + mov %eax, 20-8(%esp) + mov %ss, %eax + mov %eax, 24-8(%esp) + # Now that we moved the stack, it's time to really handle the interrupt. + mov -4-8(%esp), %eax + subl $8, %esp + jmp fixup_relocate_stack_complete + +fixup_switch_stack: + # Yup, we also have to do special processing when we return from the + # interrupt. The problem is that if the iret instruction won't load a new + # stack if interrupted with kernel permissions and that the scheduler may + # wish to change the current stack during a context switch. We will then + # switch the stack before calling iret; but iret needs the return + # information on the stack (and now it isn't), so we'll copy our stack onto + # our new stack and then fire the interrupt and everyone is happy. + + # In the following code, %esp will point our fixed iret return parameters + # that has stack data. However, the processor does not expect this + # information as cs hasn't changed. %ebx will point to the new stack plus + # room for three 32-bit values (eip, cs, eflags) that will be given to the + # actual iret. We will then load the new stack and copy the eip, cs and + # eflags to the new stack. However, we have to be careful in the case that + # we are switching to the same stack (in which case stuff on the same + # horizontal location in the diagram is actually on the same memory + # location). We therefore copy to the new stack and carefully avoid + # corrupting the destination if %esp + 8 = %ebx, This diagram show the + # structure of the stacks and where temporaries will be stored: + # -12 -8 -4 %esp 4 8 12 16 20 + # old: IECX IEBX IEAX EIP CS EFLAGS ESP SS ... + # new: IECX IEBX IEAX - - EIP CS EFLAGS ... + # -20 -16 -12 -8 -4 %ebx 4 8 12 + + mov %eax, -4(%esp) # IEAX, Clobbered as copying temporary + mov %ebx, -8(%esp) # IEBX, Clobbered as pointer to new stack + mov %ecx, -12(%esp) # IECX, Clobbered as new stack selector + + mov 12(%esp), %ebx # Pointer to new stack + sub $3*4, %ebx # Point to eip on the new stack (see diagram) + movw 16(%esp), %cx # New ss + + # The order of these does not matter if we are switching to the same stack, + # as the memory would be copied to the same location (see diagram). + mov -4(%esp), %eax # interrupted eax value + mov %eax, -12(%ebx) + mov -8(%esp), %eax # interrupted ebx value + mov %eax, -16(%ebx) + mov -12(%esp), %eax # interrupted ecx value + mov %eax, -20(%ebx) + + # The order of these three copies matter if switching to the same stack. + mov 8(%esp), %eax # eflags + mov %eax, 8(%ebx) + mov 4(%esp), %eax # cs + mov %eax, 4(%ebx) + mov 0(%esp), %eax # eip + mov %eax, 0(%ebx) + + mov %cx, %ss # Load new stack selector + mov %ebx, %esp # Load new stack pointer + + mov -12(%esp), %eax # restore interrupted eax value + mov -16(%esp), %ebx # restore interrupted ebx value + mov -20(%esp), %ecx # restore interrupted ecx value + + jmp fixup_switch_stack_complete + .global interrupt_handler_null .type interrupt_handler_null, @function interrupt_handler_null: @@ -436,3 +586,10 @@ asm_interrupts_are_enabled: andl $0x000200, %eax # FLAGS_INTERRUPT retl +.global load_registers +.type load_registers, @function +load_registers: + # Let the register struct become our temporary stack + movl 4(%esp), %esp + jmp load_interrupted_registers + diff --git a/sortix/x86/kthread.s b/sortix/x86/kthread.s new file mode 100644 index 00000000..9d311ecb --- /dev/null +++ b/sortix/x86/kthread.s @@ -0,0 +1,104 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2012. + + This file is part of Sortix. + + Sortix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Sortix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . + + x64/kthread.s + Utilities and synchronization mechanisms for x86 kernel threads. + +*******************************************************************************/ + +.section .text + +.global kthread_mutex_trylock +.type kthread_mutex_trylock, @function +kthread_mutex_trylock: + pushl %ebp + movl %esp, %ebp + movl 8(%ebp), %edx + movl $-1, %eax + xchgl (%edx), %eax + not %eax + leavel + retl + +.global kthread_mutex_lock +.type kthread_mutex_lock, @function +kthread_mutex_lock: + pushl %ebp + movl %esp, %ebp + movl 8(%ebp), %edx +kthread_mutex_lock_retry: + movl $-1, %eax + xchgl (%edx), %eax + testl %eax, %eax + jnz kthread_mutex_lock_failed + leavel + retl +kthread_mutex_lock_failed: + int $0x81 # Yield the CPU. + jmp kthread_mutex_lock_retry + +.global kthread_mutex_lock_signal +.type kthread_mutex_lock_signal, @function +kthread_mutex_lock_signal: + pushl %ebp + movl %esp, %ebp + movl 8(%ebp), %edx +kthread_mutex_lock_signal_retry: + movl asm_signal_is_pending, %eax + testl %eax, %eax + jnz kthread_mutex_lock_signal_pending + movl $-1, %eax + xchgl (%edx), %eax + testl %eax, %eax + jnz kthread_mutex_lock_signal_failed + inc %eax +kthread_mutex_lock_signal_out: + leavel + retl +kthread_mutex_lock_signal_failed: + int $0x81 # Yield the CPU. + jmp kthread_mutex_lock_signal_retry +kthread_mutex_lock_signal_pending: + xorl %eax, %eax + jmp kthread_mutex_lock_signal_out + +.global kthread_mutex_unlock +.type kthread_mutex_unlock, @function +kthread_mutex_unlock: + pushl %ebp + movl %esp, %ebp + movl 8(%ebp), %edx + movl $0, (%edx) + leavel + retl + +.global asm_call_BootstrapKernelThread +.type asm_call_BootstrapKernelThread, @function +asm_call_BootstrapKernelThread: + pushl %esi + pushl %edi + call BootstrapKernelThread + # BootstrapKernelThread is noreturn, no need for code here. + +.global asm_call_Thread__OnSigKill +.type asm_call_Thread__OnSigKill, @function +asm_call_Thread__OnSigKill: + pushl %edi + call Thread__OnSigKill + # Thread__OnSigKill is noreturn, no need for code here. diff --git a/sortix/x86/memorymanagement.cpp b/sortix/x86/memorymanagement.cpp index 57f28ee6..024f0d39 100644 --- a/sortix/x86/memorymanagement.cpp +++ b/sortix/x86/memorymanagement.cpp @@ -114,45 +114,53 @@ namespace Sortix PML* pml = PMLS[level] + offset; for ( size_t i = 0; i < ENTRIES; i++ ) { - if ( !(pml->entry[i] & PML_PRESENT) ) { continue; } - if ( !(pml->entry[i] & PML_USERSPACE) ) { continue; } - if ( !(pml->entry[i] & PML_FORK) ) { continue; } + addr_t entry = pml->entry[i]; + if ( !(entry & PML_PRESENT) ) { continue; } + if ( !(entry & PML_USERSPACE) ) { continue; } + if ( !(entry & PML_FORK) ) { continue; } if ( level > 1 ) { RecursiveFreeUserspacePages(level-1, offset * ENTRIES + i); } addr_t addr = pml->entry[i] & PML_ADDRESS; - pml->entry[i] = 0; - Page::Put(addr); + // No need to unmap the page, we just need to mark it as unused. + Page::PutUnlocked(addr); } } - void DestroyAddressSpace() + void DestroyAddressSpace(addr_t fallback, void (*func)(addr_t, void*), void* user) { - // First let's do the safe part. Garbage collect any PML1/0's left - // behind by user-space. These are completely safe to delete. - RecursiveFreeUserspacePages(TOPPMLLEVEL, 0); - - // Let's destroy the current address space! Oh wait. If we do that, - // hell will break loose half-way when we start unmapping this piece - // of code. - // Instead, let's just mark the relevant pages as unused and switch - // to another address space as fast as humanely possible. Any memory - // allocation could potentially modify the current paging structures - // and overwrite their contents causing a tripple-fault! - - // Make sure Page::Put does NOT cause any Page::Get's internally! - const size_t NUM_PAGES = 2; - size_t pagestackfree = Page::stacklength - Page::stackused; - if ( pagestackfree < NUM_PAGES ) { Page::ExtendStack(); } - + // Look up the last few entries used for the fractal mapping. These + // cannot be unmapped as that would destroy the world. Instead, we + // will remember them, switch to another adress space, and safely + // mark them as unused. Also handling the forking related pages. addr_t fractal1 = PMLS[2]->entry[1022]; + addr_t dir = currentdir; - Page::Put(fractal1 & PML_ADDRESS); - Page::Put(currentdir & PML_ADDRESS); + // We want to free the pages, but we are still using them ourselves, + // so lock the page allocation structure until we are done. + Page::Lock(); + + // In case any pages wasn't cleaned at this point. +#warning Page::Put calls may internally Page::Get and then reusing pages we are not done with just yet + RecursiveFreeUserspacePages(TOPPMLLEVEL, 0); // Switch to the address space from when the world was originally // created. It should contain the kernel, the whole kernel, and // nothing but the kernel. PML* const BOOTPML2 = (PML* const) 0x11000UL; - SwitchAddressSpace((addr_t) BOOTPML2); + if ( !fallback ) + fallback = (addr_t) BOOTPML2; + + if ( func ) + func(fallback, user); + else + SwitchAddressSpace(fallback); + + // Ok, now we got marked everything left behind as unused, we can + // now safely let another thread use the pages. + Page::Unlock(); + + // These are safe to free since we switched address space. + Page::Put(fractal1 & PML_ADDRESS); + Page::Put(dir & PML_ADDRESS); } const size_t KERNEL_STACK_SIZE = 256UL * 1024UL; diff --git a/sortix/x86/process.cpp b/sortix/x86/process.cpp index e0424fec..a9e39d0f 100644 --- a/sortix/x86/process.cpp +++ b/sortix/x86/process.cpp @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,20 +14,21 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . x86/process.cpp CPU-specific process code. -******************************************************************************/ +*******************************************************************************/ #include +#include +#include #include "process.h" namespace Sortix { - void Process::ExecuteCPU(int argc, char** argv, int envc, char** envp, addr_t stackpos, addr_t entry, CPU::InterruptRegisters* regs) @@ -37,7 +38,30 @@ namespace Sortix regs->edx = envc; regs->ecx = (size_t) envp; regs->eip = entry; - regs->useresp = stackpos; - regs->ebp = stackpos; + regs->useresp = stackpos & ~(15UL); + regs->ebp = regs->useresp; + regs->cs = UCS | URPL; + regs->ds = UDS | URPL; + regs->ss = UDS | URPL; + regs->eflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; + } + + void InitializeThreadRegisters(CPU::InterruptRegisters* regs, + const sforkregs_t* requested) + { + Maxsi::Memory::Set(regs, 0, sizeof(*regs)); + regs->eip = requested->eip; + regs->useresp = requested->esp; + regs->eax = requested->eax; + regs->ebx = requested->ebx; + regs->ecx = requested->ecx; + regs->edx = requested->edx; + regs->edi = requested->edi; + regs->esi = requested->esi; + regs->ebp = requested->ebp; + regs->cs = UCS | URPL; + regs->ds = UDS | URPL; + regs->ss = UDS | URPL; + regs->eflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; } } diff --git a/sortix/x86/syscall.s b/sortix/x86/syscall.s index 439ffe9e..418bf92c 100644 --- a/sortix/x86/syscall.s +++ b/sortix/x86/syscall.s @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,129 +14,84 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . syscall.s An assembly stub that acts as glue for system calls. -******************************************************************************/ +*******************************************************************************/ .global syscall_handler -.global resume_syscall .section .text .type syscall_handler, @function syscall_handler: - cli + # The processor disabled interrupts during the int $0x80 instruction, + # however Sortix system calls runs with interrupts enabled such that they + # can be pre-empted. + sti - # Compabillity with InterruptRegisters. - pushl $0x0 - pushl $0x80 - - # Push eax, ecx, edx, ebx, esp, ebp, esi, edi - pushal - - # Push the user-space data segment. - movl %ds, %ebp + movl $0, global_errno # Reset errno pushl %ebp - # Load the kernel data segment. + # Grant ourselves kernel permissions to the data segment. + movl %ds, %ebp + pushl %ebp movw $0x10, %bp movl %ebp, %ds movl %ebp, %es movl %ebp, %fs movl %ebp, %gs - # Compabillity with InterruptRegisters. - movl %cr2, %ebp - pushl %ebp - - # Store the state structure's pointer so the call can modify it if needed. - mov %esp, syscall_state_ptr - - # By default, assume the system call was complete. - movl $0, system_was_incomplete - - # Reset the kernel errno. - movl $0, global_errno - # Make sure the requested system call is valid. cmp SYSCALL_MAX, %eax - jb valid_eax - xorl %eax, %eax + jae fix_syscall -valid_eax: +valid_syscall: # Read a system call function pointer. xorl %ebp, %ebp movl syscall_list(%ebp,%eax,4), %eax - # Give the system call function the values given by user-space. + # Call the system call. pushl %esi pushl %edi pushl %edx pushl %ecx pushl %ebx - - # Call the system call. calll *%eax - - # Clean up after the call. addl $20, %esp - # Test if the system call was incomplete - movl system_was_incomplete, %ebx - testl %ebx, %ebx - - # If the system call was incomplete, the value in %eax is meaningless. - jg return_to_userspace - - # The system call was completed, so store the return value. - movl %eax, 36(%esp) - - # Don't forget to update userspace's errno value. - call update_userspace_errno - -return_to_userspace: - # Compabillity with InterruptRegisters. - addl $4, %esp - - # Restore the user-space data segment. + # Restore the previous permissions to data segment. popl %ebp movl %ebp, %ds movl %ebp, %es movl %ebp, %fs movl %ebp, %gs - popal + # Return to user-space, system call result in %eax, errno in %edx. + popl %ebp + movl global_errno, %edx - # Compabillity with InterruptRegisters. - addl $8, %esp + # If any signals are pending, fire them now. + movl asm_signal_is_pending, %ecx + testl %ecx, %ecx + jnz call_signal_dispatcher - # Return to user-space. iretl -.type resume_syscall, @function -resume_syscall: - pushl %ebp - movl %esp, %ebp - - movl 8(%esp), %eax - movl 16(%esp), %ecx - - pushl 28(%ecx) - pushl 24(%ecx) - pushl 20(%ecx) - pushl 16(%ecx) - pushl 12(%ecx) - pushl 8(%ecx) - pushl 4(%ecx) - pushl 0(%ecx) - - call *%eax - - addl $32, %esp - - leavel - retl +fix_syscall: + # Call the null system call instead. + xorl %eax, %eax + jmp valid_syscall +call_signal_dispatcher: + # We can't return to this location after the signal, since if any system + # call is made this stack will get reused and all our nice temporaries wil + # be garbage. We therefore pass the kernel the state to return to and it'll + # handle it for us when the signal is over. + movl %esp, %ecx + int $130 # Deliver pending signals. + # If we end up here, it means that the signal didn't override anything and + # that we should just go ahead and return to userspace ourselves. + iretl diff --git a/sortix/x86/thread.cpp b/sortix/x86/thread.cpp index 657d25be..fc0680e4 100644 --- a/sortix/x86/thread.cpp +++ b/sortix/x86/thread.cpp @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,13 +14,13 @@ 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 . + 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 #include "thread.h" @@ -42,6 +42,8 @@ namespace Sortix registers.ds = src->ds; registers.ss = src->ss; registers.eflags = src->eflags; + registers.kerrno = src->kerrno; + registers.signal_pending = src->signal_pending; } void Thread::LoadRegisters(CPU::InterruptRegisters* dest) @@ -59,57 +61,80 @@ namespace Sortix dest->ds = registers.ds; dest->ss = registers.ss; dest->eflags = registers.eflags; + dest->kerrno = registers.kerrno; + dest->signal_pending = registers.signal_pending; } - 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); + extern "C" void asm_call_BootstrapKernelThread(void); - void CreateThreadCPU(Thread* thread, addr_t entry) + void SetupKernelThreadRegs(CPU::InterruptRegisters* regs, ThreadEntry entry, + void* user, addr_t stack, size_t stacksize) { - const uint32_t CS = 0x18; - const uint32_t DS = 0x20; - const uint32_t RPL = 0x3; + // Instead of directly calling the desired entry point, we call a small + // stub that calls it for us and then destroys the kernel thread if + // the entry function returns. Note that since we use a stack based + // calling convention, we go through a proxy that uses %edi and %esi + // as parameters and pushes them to the stack and then does the call. + regs->eip = (addr_t) asm_call_BootstrapKernelThread; + regs->useresp = stack + stacksize; + regs->eax = 0; + regs->ebx = 0; + regs->ecx = 0; + regs->edx = 0; + regs->edi = (addr_t) user; + regs->esi = (addr_t) entry; + regs->ebp = 0; + regs->cs = KCS | KRPL; + regs->ds = KDS | KRPL; + regs->ss = KDS | KRPL; + regs->eflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; + regs->kerrno = 0; + regs->signal_pending = 0; + } - 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); + void Thread::HandleSignalFixupRegsCPU(CPU::InterruptRegisters* regs) + { + if ( regs->InUserspace() ) + return; + uint32_t* params = (uint32_t*) regs->ecx; + regs->eip = params[0]; + regs->eflags = params[2]; + regs->useresp = params[3]; + regs->cs = UCS | URPL; + regs->ds = UDS | URPL; + regs->ss = UDS | URPL; } void Thread::HandleSignalCPU(CPU::InterruptRegisters* regs) { - regs->edi = currentsignal->signum; + const size_t STACK_ALIGNMENT = 16UL; + const size_t RED_ZONE_SIZE = 128UL; + regs->useresp -= RED_ZONE_SIZE; + regs->useresp &= ~(STACK_ALIGNMENT-1UL); + regs->ebp = regs->useresp; + regs->edi = currentsignal; regs->eip = (size_t) sighandler; + regs->eflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; + regs->kerrno = 0; + regs->signal_pending = 0; + } + + extern "C" void asm_call_Thread__OnSigKill(void); + + void Thread::GotoOnSigKill(CPU::InterruptRegisters* regs) + { + regs->eip = (unsigned long) asm_call_Thread__OnSigKill; + regs->edi = (unsigned long) this; + // TODO: HACK: The -256 is because if we are jumping to the safe stack + // we currently are on, this may not be fully supported by interrupt.s + // that is quite aware of this (but isn't perfect). If our destination + // is further down the stack, then we are probably safe. + regs->useresp = regs->ebp = kernelstackpos + kernelstacksize - 256; + regs->eflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; + regs->cs = KCS | KRPL; + regs->ds = KDS | KRPL; + regs->ss = KDS | KRPL; + regs->kerrno = 0; + regs->signal_pending = 0; } } diff --git a/sortix/x86/x86.h b/sortix/x86/x86.h index 27056614..0990d434 100644 --- a/sortix/x86/x86.h +++ b/sortix/x86/x86.h @@ -1,6 +1,6 @@ -/****************************************************************************** +/******************************************************************************* - COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. This file is part of Sortix. @@ -14,13 +14,13 @@ 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 . + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . x86.h CPU stuff for the x86 platform. -******************************************************************************/ +*******************************************************************************/ #ifndef SORTIX_X86_H #define SORTIX_X86_H @@ -33,7 +33,7 @@ namespace Sortix { struct InterruptRegisters { - uint32_t cr2; + uint32_t signal_pending, kerrno, cr2; uint32_t ds; // Data segment selector 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) @@ -41,18 +41,17 @@ namespace Sortix public: void LogRegisters() const; + bool InUserspace() const { return (cs & 0x3) != 0; } }; - - struct SyscallRegisters - { - uint32_t cr2; // For compabillity with above, may be removed soon. - uint32_t ds; - 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; - }; } + + const uint64_t KCS = 0x08; + const uint64_t KDS = 0x10; + const uint64_t KRPL = 0x0; + const uint64_t UCS = 0x18; + const uint64_t UDS = 0x20; + const uint64_t URPL = 0x3; } #endif