LDFLAGS += -T arch/$(ARCHDIR)/link.ld
OBJ = $(addprefix arch/$(ARCHDIR)/,$(A_OBJ))
-OBJ += heap.o messages.o debug.o modules.o lib.o syscalls.o system.o
+OBJ += heap.o messages.o debug.o modules.o lib.o syscalls.o system.o threads.o
OBJ += binary.o bin/elf.o
OBJ += vfs/main.o vfs/open.o vfs/acls.o vfs/dir.o vfs/io.o vfs/mount.o vfs/memfile.o vfs/nodecache.o
OBJ += vfs/fs/root.o vfs/fs/devfs.o vfs/fs/fat.o
// === IMPORTS ===
extern void MM_PageFault(Uint Addr, Uint ErrorCode, tRegs *Regs);
-extern void Proc_DumpThreads();
+extern void Threads_Dump();
// === CODE ===
/**
Warning(" CR3: 0x%08x", cr);
// Dump running threads
- Proc_DumpThreads();
+ Threads_Dump();
for(;;) __asm__ __volatile__ ("hlt");
}
#define LOCK(lockptr) do {int v=1;\
while(v)__asm__ __volatile__("lock xchgl %%eax, (%%edi)":"=a"(v):"a"(1),"D"(lockptr));}while(0)
#define RELEASE(lockptr) __asm__ __volatile__("lock andl $0, (%%edi)"::"D"(lockptr));
+#define HALT() __asm__ __volatile__ ("hlt")
// === TYPES ===
typedef unsigned int Uint; // Unsigned machine native integer
Uint8 BaseHi;
} __attribute__ ((packed)) tGDT;
+typedef struct {
+ #if USE_PAE
+ Uint PDPT[4];
+ #else
+ Uint CR3;
+ #endif
+} tMemoryState;
+
+typedef struct {
+ Uint EIP, ESP, EBP;
+} tTaskState;
+
// --- Interface Flags & Macros
#define CLONE_VM 0x10
#ifndef _PROC_H
#define _PROC_H
+#include <threads.h>
+
// === CONSTANTS ===
#define GETMSG_IGNORE ((void*)-1)
// === TYPES ===
-typedef struct sMessage {
- struct sMessage *Next;
- Uint Source;
- Uint Length;
- Uint8 Data[];
-} tMsg; // sizeof = 12+
-
-typedef struct sThread {
- struct sThread *Next;
- int IsLocked;
- int Status; //!< Thread Status
-
- Uint TID; //!< Thread ID
- Uint TGID; //!< Thread Group (Process)
- Uint UID, GID; //!< User and Group
- char *ThreadName; //!< Name of thread
-
- Uint ESP, EBP, EIP; //!< State on switch
- #if USE_PAE
- Uint64 PML4[3]; //!< Address Space
- #else
- Uint CR3; //!< Memory Space
- #endif
-
- Uint KernelStack; //!< Thread's Kernel Stack
-
- tMsg *Messages; //!< Message Queue
- tMsg *LastMessage; //!< Last Message (speeds up insertion)
-
- int Quantum, Remaining; //!< Quantum Size and remaining timesteps
- int NumTickets; //!< Priority - Chance of gaining CPU
-
- Uint Config[NUM_CFG_ENTRIES]; //!< Per-process configuration
-} tThread; // sizeof = 68
-
-enum {
- THREAD_STAT_NULL,
- THREAD_STAT_ACTIVE,
- THREAD_STAT_SLEEPING,
- THREAD_STAT_WAITING,
- THREAD_STAT_DEAD
-};
typedef struct sTSS {
Uint32 Link;
Uint16 Resvd, IOPB; // IO Permissions Bitmap
} tTSS;
-// === GLOBALS ===
-extern tThread *gCurrentThread;
-
// === FUNCTIONS ===
extern void Proc_Start();
extern int Proc_Clone(Uint *Err, Uint Flags);
-extern void Proc_Exit();
-extern void Proc_Yield();
-extern void Proc_Sleep();
-extern void Proc_SetTickets(int Num);
-extern tThread *Proc_GetThread(Uint TID);
-extern void Thread_Wake(tThread *Thread);
#endif
extern void MM_PreinitVirtual();
extern void MM_Install(tMBoot_Info *MBoot);
extern void MM_InstallVirtual();
-extern void Proc_Start();
+extern void Threads_Init();
extern Uint Proc_Clone(Uint *Err, Uint Flags);
-extern void Proc_Sleep();
-extern void Proc_Exit();
+extern void Threads_Sleep();
+extern void Threads_Exit();
// === GLOBALS ===
Log("Starting Multitasking...");
// Start Multitasking
- Proc_Start();
+ Threads_Init();
Log("Starting VFS...");
// Load Virtual Filesystem
System_Init( (char*)(MbInfo->CommandLine + KERNEL_BASE) );
// Sleep forever (sleeping beauty)
- for(;;) Proc_Sleep();
+ for(;;) Threads_Sleep();
return 0;
}
Uint MM_Clone()
{
Uint i, j;
- Uint kStackBase = gCurrentThread->KernelStack - KERNEL_STACK_SIZE;
+ Uint kStackBase = Proc_GetCurThread()->KernelStack - KERNEL_STACK_SIZE;
void *tmp;
//ENTER("");
return TEMP_MAP_ADDR + (i << 12);
}
RELEASE( &gilTempMappings );
- Proc_Yield();
+ Threads_Yield();
}
}
// === CONSTANTS ===
#define SWITCH_MAGIC 0xFFFACE55 // There is no code in this area
-#define DEFAULT_QUANTUM 10
-#define DEFAULT_TICKETS 5
-#define MAX_TICKETS 10
#define TIMER_DIVISOR 11931 //~100Hz
// === IMPORTS ===
extern Uint GetEIP(); // start.asm
extern Uint32 gaInitPageDir[1024]; // start.asm
extern void Kernel_Stack_Top;
+extern volatile int giThreadListLock;
+extern int giNumCPUs;
+extern int giNextTID;
+extern int giTotalTickets;
+extern int giNumActiveThreads;
+extern tThread *gActiveThreads;
+extern tThread *gSleepingThreads;
+extern tThread *gDeleteThreads;
// === PROTOTYPES ===
-void Proc_Start();
+void ArchThreads_Init();
+tThread *Proc_GetCurThread();
void Proc_ChangeStack();
int Proc_Clone(Uint *Err, Uint Flags);
-void Proc_Exit();
-void Proc_Yield();
-void Proc_Sleep();
-static tThread *Proc_int_GetPrevThread(tThread **List, tThread *Thread);
void Proc_Scheduler();
-Sint64 now();
-Uint rand();
// === GLOBALS ===
-// -- Core Thread --
-tThread gThreadZero = {
- NULL, 0, // Next, Lock
- THREAD_STAT_ACTIVE, // Status
- 0, 0, // TID, TGID
- 0, 0, // UID, GID
- "ThreadZero", // Name
- 0, 0, 0, // ESP, EBP, EIP (Set on switch)
- #if USE_PAE
- {0,0,0}, // PML4 Entries
- #else
- (Uint)&gaInitPageDir-KERNEL_BASE, // CR3
- #endif
- (Uint)&Kernel_Stack_Top, // Kernel Stack (Unused as it is PL0)
- NULL, NULL, // Messages, Last Message
- DEFAULT_QUANTUM, DEFAULT_QUANTUM, // Quantum, Remaining
- DEFAULT_TICKETS,
- {0} // Default config to zero
- };
-// -- Processes --
-// --- Locks ---
-volatile int giThreadListLock = 0; ///\note NEVER use a heap function while locked
// --- Current State ---
#if USE_MP
tThread **gCurrentThread = NULL;
-# define CUR_THREAD gCurrentThread[0]
#else
tThread *gCurrentThread = NULL;
-# define CUR_THREAD gCurrentThread
#endif
-volatile int giNumActiveThreads = 0;
-volatile int giTotalTickets = 0;
-volatile Uint giNextTID = 1;
-// --- Thread Lists ---
-tThread *gActiveThreads = NULL; // Currently Running Threads
-tThread *gSleepingThreads = NULL; // Sleeping Threads
-tThread *gDeleteThreads = NULL; // Threads to delete
// --- Multiprocessing ---
- int giNumCPUs = 1;
#if USE_MP
tMPInfo *gMPTable = NULL;
#endif
// === CODE ===
/**
- * \fn void Proc_Start()
+ * \fn void ArchThreads_Init()
* \brief Starts the process scheduler
*/
-void Proc_Start()
+void ArchThreads_Init()
{
Uint pos = 0;
-
#if USE_MP
// -- Initialise Multiprocessing
// Find MP Floating Table
outb(0x40, TIMER_DIVISOR&0xFF); // Low Byte of Divisor
outb(0x40, (TIMER_DIVISOR>>8)&0xFF); // High Byte
- // Create Initial Task
- gActiveThreads = &gThreadZero;
- gCurrentThread = &gThreadZero;
- giTotalTickets = gThreadZero.NumTickets;
- giNumActiveThreads = 1;
-
// Create Per-Process Data Block
MM_Allocate(MM_PPD_CFG);
// Change Stacks
Proc_ChangeStack();
- #if 1
- // Create Idle Task
- if(Proc_Clone(0, 0) == 0)
- {
- gCurrentThread->ThreadName = "Idle Thread";
- Proc_SetTickets(0); // Never called randomly
- gCurrentThread->Quantum = 1; // 1 slice quantum
- for(;;) __asm__ __volatile__ ("hlt"); // Just yeilds
- }
- #endif
-
// Start Interrupts (and hence scheduler)
__asm__ __volatile__("sti");
}
+/**
+ * \fn tThread *Proc_GetCurThread()
+ * \brief Gets the current thread
+ */
+tThread *Proc_GetCurThread()
+{
+ #if USE_MP
+ return NULL;
+ #else
+ return gCurrentThread;
+ #endif
+}
+
/**
* \fn void Proc_ChangeStack()
* \brief Swaps the current stack for a new one (in the proper stack reigon)
// Initialise Memory Space (New Addr space or kernel stack)
if(Flags & CLONE_VM) {
newThread->TGID = newThread->TID;
- newThread->CR3 = MM_Clone();
+ newThread->MemState.CR3 = MM_Clone();
} else {
Uint tmpEbp, oldEsp = esp;
newThread->Next = NULL;
newThread->IsLocked = 0;
newThread->TID = giNextTID++;
+ newThread->PTID = gCurrentThread->TID;
// Clear message list (messages are not inherited)
newThread->Messages = NULL;
newThread->Remaining = newThread->Quantum;
// Save core machine state
- newThread->ESP = esp;
- newThread->EBP = ebp;
+ newThread->SavedState.ESP = esp;
+ newThread->SavedState.EBP = ebp;
eip = GetEIP();
if(eip == SWITCH_MAGIC) {
outb(0x20, 0x20); // ACK Timer and return as child
}
// Set EIP as parent
- newThread->EIP = eip;
-
- //Log(" Proc_Clone: giTimestamp = %i.%07i", (Uint)giTimestamp, (Uint)giPartMiliseconds/214);
+ newThread->SavedState.EIP = eip;
// Lock list and add to active
LOCK( &giThreadListLock );
return newThread->TID;
}
+#if 0
/**
- * \fn void Proc_SetThreadName()
- * \brief Sets the thread's name
+ * \fn void Proc_SetSignalHandler(int Num, void *Handler)
+ * \brief Sets the signal handler for a signal
*/
-void Proc_SetThreadName(char *NewName)
+void Proc_SetSignalHandler(int Num, void *Handler)
{
- if( (Uint)CUR_THREAD->ThreadName > 0xC0400000 )
- free( CUR_THREAD->ThreadName );
- CUR_THREAD->ThreadName = malloc(strlen(NewName)+1);
- strcpy(CUR_THREAD->ThreadName, NewName);
+ if(Num < 0 || Num >= NSIG) return;
+
+ gCurrentThread->SignalHandlers[Num] = Handler;
+}
+
+/**
+ * \fn void Proc_SendSignal(int TID, int Num)
+ */
+void Proc_SendSignal(int TID, int Num)
+{
+ tThread *thread = Proc_GetThread(TID);
+ void *handler;
+
+ if(!thread) return ;
+
+ handler = thread->SignalHandlers[Num];
+
+ // Panic?
+ if(handler == SIG_ERR) {
+ Proc_Kill(TID);
+ return ;
+ }
+ // Dump Core?
+ if(handler == -2) {
+ Proc_Kill(TID);
+ return ;
+ }
+ // Ignore?
+ if(handler == -2) return;
+
+ // Check the type and handle if the thread is already in a signal
+ if(thread->CurSignal != 0) {
+ if(Num < _SIGTYPE_FATAL)
+ Proc_Kill(TID);
+ } else {
+ while(thread->CurSignal != 0)
+ Proc_Yield();
+ }
+ }
+
+ //TODO:
}
+#endif
+
/**
* \fn Uint Proc_MakeUserStack()
* \brief Creates a new user stack
for(;;);
}
-/**
- * \fn void Proc_Exit()
- * \brief Kill the current process
- */
-void Proc_Exit()
-{
- tThread *thread;
- tMsg *msg;
-
- ///\note Double lock is needed due to overlap of locks
-
- // Lock thread (stop us recieving messages)
- LOCK( &gCurrentThread->IsLocked );
-
- // Lock thread list
- LOCK( &giThreadListLock );
-
- // Get previous thread on list
- thread = Proc_int_GetPrevThread( &gActiveThreads, gCurrentThread );
- if(!thread) {
- Warning("Proc_Exit - Current thread is not on the active queue");
- return;
- }
-
- // Clear Message Queue
- while( gCurrentThread->Messages )
- {
- msg = gCurrentThread->Messages->Next;
- free( gCurrentThread->Messages );
- gCurrentThread->Messages = msg;
- }
-
- gCurrentThread->Remaining = 0; // Clear Remaining Quantum
- gCurrentThread->Quantum = 0; // Clear Quantum to indicate dead thread
- thread->Next = gCurrentThread->Next; // Remove from active
-
- // Add to delete queue
- if(gDeleteThreads) {
- gCurrentThread->Next = gDeleteThreads;
- gDeleteThreads = gCurrentThread;
- } else {
- gCurrentThread->Next = NULL;
- gDeleteThreads = gCurrentThread;
- }
-
- giNumActiveThreads --;
- giTotalTickets -= gCurrentThread->NumTickets;
-
- // Mark thread as sleeping
- gCurrentThread->Status = THREAD_STAT_DEAD;
-
- // Release spinlocks
- RELEASE( &gCurrentThread->IsLocked ); // Released first so that it IS released
- RELEASE( &giThreadListLock );
- __asm__ __volatile__ ("hlt");
-}
-
-/**
- * \fn void Proc_Yield()
- * \brief Yield remainder of timeslice
- */
-void Proc_Yield()
-{
- gCurrentThread->Quantum = 0;
- __asm__ __volatile__ ("hlt");
-}
-
-/**
- * \fn void Proc_Sleep()
- * \brief Take the current process off the run queue
- */
-void Proc_Sleep()
-{
- tThread *thread;
-
- //Log("Proc_Sleep: %i going to sleep", gCurrentThread->TID);
-
- // Acquire Spinlock
- LOCK( &giThreadListLock );
-
- // Get thread before current thread
- thread = Proc_int_GetPrevThread( &gActiveThreads, gCurrentThread );
- if(!thread) {
- Warning("Proc_Sleep - Current thread is not on the active queue");
- return;
- }
-
- // Don't sleep if there is a message waiting
- if( gCurrentThread->Messages ) {
- RELEASE( &giThreadListLock );
- return;
- }
-
- // Unset remaining timeslices (force a task switch on timer fire)
- gCurrentThread->Remaining = 0;
-
- // Remove from active list
- thread->Next = gCurrentThread->Next;
-
- // Add to Sleeping List (at the top)
- gCurrentThread->Next = gSleepingThreads;
- gSleepingThreads = gCurrentThread;
-
- // Reduce the active count & ticket count
- giNumActiveThreads --;
- giTotalTickets -= gCurrentThread->NumTickets;
-
- // Mark thread as sleeping
- gCurrentThread->Status = THREAD_STAT_SLEEPING;
-
- // Release Spinlock
- RELEASE( &giThreadListLock );
-
- __asm__ __volatile__ ("hlt");
-}
-
-/**
- * \fn void Thread_Wake( tThread *Thread )
- * \brief Wakes a sleeping/waiting thread up
- */
-void Thread_Wake(tThread *Thread)
-{
- tThread *prev;
- switch(Thread->Status)
- {
- case THREAD_STAT_ACTIVE: break;
- case THREAD_STAT_SLEEPING:
- LOCK( &giThreadListLock );
- prev = Proc_int_GetPrevThread(&gSleepingThreads, Thread);
- prev->Next = Thread->Next; // Remove from sleeping queue
- Thread->Next = gActiveThreads; // Add to active queue
- gActiveThreads = Thread;
- Thread->Status = THREAD_STAT_ACTIVE;
- RELEASE( &giThreadListLock );
- break;
- case THREAD_STAT_WAITING:
- Warning("Thread_Wake - Waiting threads are not currently supported");
- break;
- case THREAD_STAT_DEAD:
- Warning("Thread_Wake - Attempt to wake dead thread (%i)", Thread->TID);
- break;
- default:
- Warning("Thread_Wake - Unknown process status (%i)\n", Thread->Status);
- break;
- }
-}
-
/**
* \fn int Proc_Demote(Uint *Err, int Dest, tRegs *Regs)
* \brief Demotes a process to a lower permission level
return 0;
}
-/**
- * \fn void Proc_SetTickets(int Num)
- * \brief Sets the 'priority' of a task
- */
-void Proc_SetTickets(int Num)
-{
- if(Num < 0) return;
- if(Num > MAX_TICKETS) Num = MAX_TICKETS;
-
- LOCK( &giThreadListLock );
- giTotalTickets -= gCurrentThread->NumTickets;
- gCurrentThread->NumTickets = Num;
- giTotalTickets += Num;
- //LOG("giTotalTickets = %i", giTotalTickets);
- RELEASE( &giThreadListLock );
-}
-
-/**
- * \fn tThread *Proc_GetThread(Uint TID)
- * \brief Gets a thread given its TID
- */
-tThread *Proc_GetThread(Uint TID)
-{
- tThread *thread;
-
- // Search Active List
- for(thread = gActiveThreads;
- thread;
- thread = thread->Next)
- {
- if(thread->TID == TID)
- return thread;
- }
-
- // Search Sleeping List
- for(thread = gSleepingThreads;
- thread;
- thread = thread->Next)
- {
- if(thread->TID == TID)
- return thread;
- }
-
- return NULL;
-}
-
-/**
- * \fn static tThread *Proc_int_GetPrevThread(tThread *List, tThread *Thread)
- * \brief Gets the previous entry in a thead linked list
- */
-static tThread *Proc_int_GetPrevThread(tThread **List, tThread *Thread)
-{
- tThread *ret;
- // First Entry
- if(*List == Thread) {
- return (tThread*)List;
- } else {
- for(ret = *List;
- ret->Next && ret->Next != Thread;
- ret = ret->Next
- );
- // Error if the thread is not on the list
- if(!ret->Next || ret->Next != Thread) {
- return NULL;
- }
- }
- return ret;
-}
-
-/**
- * \fn void Proc_DumpThreads()
- * \brief Dums a list of currently running threads
- */
-void Proc_DumpThreads()
-{
- tThread *thread;
-
- Log("Active Threads:");
- for(thread=gActiveThreads;thread;thread=thread->Next)
- {
- Log(" %i (%i) - %s", thread->TID, thread->TGID, thread->ThreadName);
- Log(" %i Tickets, Quantum %i", thread->NumTickets, thread->Quantum);
- Log(" CR3 0x%x, KStack 0x%x", thread->CR3, thread->KernelStack);
- }
- Log("Sleeping Threads:");
- for(thread=gSleepingThreads;thread;thread=thread->Next)
- {
- Log(" %i (%i) - %s", thread->TID, thread->TGID, thread->ThreadName);
- Log(" %i Tickets, Quantum %i", thread->NumTickets, thread->Quantum);
- Log(" CR3 0x%x, KStack 0x%x", thread->CR3, thread->KernelStack);
- }
-}
-
/**
* \fn void Proc_Scheduler(int CPU)
* \brief Swap current thread and clears dead threads
if(eip == SWITCH_MAGIC) return; // Check if a switch happened
// Save machine state
- gCurrentThread->ESP = esp;
- gCurrentThread->EBP = ebp;
- gCurrentThread->EIP = eip;
+ gCurrentThread->SavedState.ESP = esp;
+ gCurrentThread->SavedState.EBP = ebp;
+ gCurrentThread->SavedState.EIP = eip;
// Special case: 1 thread
if(giNumActiveThreads == 1)
performSwitch:
// Set address space
//MM_SetCR3( gCurrentThread->CR3 );
- __asm__ __volatile__ ("mov %0, %%cr3"::"a"(gCurrentThread->CR3));
+ __asm__ __volatile__ ("mov %0, %%cr3"::"a"(gCurrentThread->MemState.CR3));
// Switch threads
__asm__ __volatile__ (
"mov %1, %%esp\n\t"
"mov %2, %%ebp\n\t"
"jmp *%3" : :
- "a"(SWITCH_MAGIC), "b"(gCurrentThread->ESP),
- "d"(gCurrentThread->EBP), "c"(gCurrentThread->EIP));
+ "a"(SWITCH_MAGIC), "b"(gCurrentThread->SavedState.ESP),
+ "d"(gCurrentThread->SavedState.EBP), "c"(gCurrentThread->SavedState.EIP));
for(;;); // Shouldn't reach here
}
-
-// --- Process Structure Access Functions ---
-int Proc_GetPID()
-{
- return gCurrentThread->TGID;
-}
-int Proc_GetTID()
-{
- return gCurrentThread->TID;
-}
-int Proc_GetUID()
-{
- return gCurrentThread->UID;
-}
-int Proc_GetGID()
-{
- return gCurrentThread->GID;
-}
ret\r
\r
[extern _Proc_Clone]\r
-[extern _Proc_Exit]\r
+[extern _Threads_Exit]\r
[global _SpawnTask]\r
_SpawnTask:\r
; Call Proc_Clone with Flags=0\r
; Child\r
push edx ; Argument\r
call ebx ; Function\r
- call _Proc_Exit ; Kill Thread\r
+ call _Threads_Exit ; Kill Thread\r
\r
.parent:\r
ret\r
\r
// === IMPORTS ===\r
extern int Proc_Clone(Uint *Err, Uint Flags);\r
-extern void Proc_SetThreadName(char *Name);\r
+extern void Threads_SetName(char *Name);\r
extern Uint MM_ClearUser();\r
-extern void Proc_Exit();\r
+extern void Threads_Exit();\r
extern void Proc_StartUser(Uint Entrypoint, Uint *Bases, int ArgC, char **ArgV, char **EnvP, int DataSize);\r
extern tKernelSymbol gKernelSymbols[];\r
extern void gKernelSymbolsEnd;\r
strcpy(savedFile, File);\r
\r
// --- Set Process Name\r
- Proc_SetThreadName(File);\r
+ Threads_SetName(File);\r
\r
// --- Clear User Address space\r
MM_ClearUser();\r
if(bases[0] == 0)\r
{\r
Warning("Proc_Execve - Unable to load '%s'", File);\r
- Proc_Exit();\r
+ Threads_Exit();\r
for(;;);\r
}\r
\r
#define E9(ch) __asm__ __volatile__ ("outb %%al, $0xe9"::"a"(((Uint8)ch)))
// === IMPORTS ===
-extern void Proc_DumpThreads();
+extern void Threads_Dump();
// === GLOBALS ===
int gDebug_Level = 0;
va_end(args);
E9('\n');
- Proc_DumpThreads();
+ Threads_Dump();
__asm__ __volatile__ ("xchg %bx, %bx");
__asm__ __volatile__ ("cli;\n\thlt");
ATA_int_BusMasterWriteByte( cont << 3, 9 ); // Read and start
// Wait for transfer to complete
- while( gaATA_IRQs[cont] == 0 ) Proc_Yield();
+ while( gaATA_IRQs[cont] == 0 ) Threads_Yield();
// Complete Transfer
ATA_int_BusMasterWriteByte( cont << 3, 0 ); // Write and stop
case VT_MODE_TEXT8:
while(pos < Length)
{
- while(term->InputRead == term->InputWrite) Proc_Yield();
+ while(term->InputRead == term->InputWrite) Threads_Yield();
while(term->InputRead != term->InputWrite)
{
- LOG("WriteUTF8(%p, 0x%x)", Buffer+pos, term->InputBuffer[term->InputRead]);
pos += WriteUTF8(Buffer+pos, term->InputBuffer[term->InputRead]);
term->InputRead ++;
term->InputRead %= MAX_INPUT_CHARS;
case VT_MODE_TEXT32:
while(pos < Length)
{
- while(term->InputRead == term->InputWrite) Proc_Yield();
+ while(term->InputRead == term->InputWrite) Threads_Yield();
while(term->InputRead != term->InputWrite)
{
((Uint32*)Buffer)[pos] = term->InputBuffer[term->InputRead];
return NULL;
}
+/**
+ * \fn int IsHeap(void *Ptr)
+ * \brief Checks if an address is a heap address
+ */
+int IsHeap(void *Ptr)
+{
+ tHeapHead *head;
+ if((Uint)Ptr < (Uint)gHeapStart) return 0;
+ if((Uint)Ptr > (Uint)gHeapEnd) return 0;
+
+ head = (void*)( (Uint)Ptr - sizeof(tHeapHead) );
+ if(head->Magic != MAGIC_USED && head->Magic != MAGIC_FREE)
+ return 0;
+
+ return 1;
+}
+
#if WARNINGS
void Heap_Dump()
{
extern void itoa(char *buf, Uint num, int base, int minLength, char pad);
extern int ReadUTF8(Uint8 *str, Uint32 *Val);
extern int WriteUTF8(Uint8 *str, Uint32 Val);
+extern Uint rand();
// --- Heap ---
extern void *malloc(size_t size);
extern void *realloc(void *ptr, size_t size);
extern void free(void *Ptr);
+extern int IsHeap(void *Ptr);
// --- Modules ---
extern int Module_LoadMem(void *Buffer, Uint Length, char *ArgStr);
extern int Module_LoadFile(char *Path, char *ArgStr);
extern Sint64 now();
// --- Threads ---
extern int Proc_Spawn(char *Path);
-extern void Proc_Exit();
-extern void Proc_Yield();
-extern int Proc_GetCfg(int Index);
-extern int Proc_GetUID();
-extern int Proc_GetGID();
+extern void Threads_Exit();
+extern void Threads_Yield();
+extern void Threads_Sleep();
+extern int Threads_GetCfg(int Index);
+extern int Threads_GetUID();
+extern int Threads_GetGID();
extern int SpawnTask(tThreadFunction Function, void *Arg);
#include <binary_ext.h>
--- /dev/null
+/*
+ */
+#ifndef _THREADS_H_
+#define _THREADS_H_
+
+#include <arch.h>
+#include <signal.h>
+
+typedef struct sMessage
+{
+ struct sMessage *Next;
+ Uint Source;
+ Uint Length;
+ Uint8 Data[];
+} tMsg; // sizeof = 12+
+
+typedef struct sThread
+{
+ struct sThread *Next; //!< Next thread in list
+ int IsLocked; //!< Thread's spinlock
+ int Status; //!< Thread Status
+ int RetStatus; //!< Return Status
+
+ Uint TID; //!< Thread ID
+ Uint TGID; //!< Thread Group (Process)
+ Uint PTID; //!< Parent Thread ID
+ Uint UID, GID; //!< User and Group
+ char *ThreadName; //!< Name of thread
+
+ tVAddr KernelStack; //!< Kernel Stack Base
+
+ //! Memory Manager State
+ tMemoryState MemState;
+
+ //! State on task switch
+ tTaskState SavedState;
+
+ int CurSignal; //!< Signal currently being handled (0 for none)
+ tVAddr SignalHandlers[NSIG]; //!< Signal Handler List
+ tTaskState SignalState; //!< Saved state for signal handler
+
+ tMsg *Messages; //!< Message Queue
+ tMsg *LastMessage; //!< Last Message (speeds up insertion)
+
+ int Quantum, Remaining; //!< Quantum Size and remaining timesteps
+ int NumTickets; //!< Priority - Chance of gaining CPU
+
+ Uint Config[NUM_CFG_ENTRIES]; //!< Per-process configuration
+} tThread;
+
+
+enum {
+ THREAD_STAT_NULL,
+ THREAD_STAT_ACTIVE,
+ THREAD_STAT_SLEEPING,
+ THREAD_STAT_WAITING,
+ THREAD_STAT_ZOMBIE,
+ THREAD_STAT_DEAD
+};
+
+// === FUNCTIONS ===
+extern tThread *Proc_GetCurThread();
+extern tThread *Threads_GetThread(Uint TID);
+extern void Threads_Wake(tThread *Thread);
+
+#endif
}
// Get thread
- thread = Proc_GetThread( Dest );
+ thread = Threads_GetThread( Dest );
// Error check
if(!thread) { return -1; }
// Create message
msg = malloc( sizeof(tMsg)+Length );
msg->Next = NULL;
- msg->Source = gCurrentThread->TID;
+ msg->Source = Proc_GetCurThread()->TID;
msg->Length = Length;
memcpy(msg->Data, Data, Length);
RELEASE(&thread->IsLocked);
- Thread_Wake( thread );
+ Threads_Wake( thread );
return 0;
}
{
int ret;
void *tmp;
+ tThread *cur = Proc_GetCurThread();
// Check if queue has any items
- if(!gCurrentThread->Messages) {
+ if(!cur->Messages) {
return 0;
}
- LOCK( &gCurrentThread->IsLocked );
+ LOCK( &cur->IsLocked );
if(Source)
- *Source = gCurrentThread->Messages->Source;
+ *Source = cur->Messages->Source;
// Get message length
if( !Buffer ) {
- ret = gCurrentThread->Messages->Length;
- RELEASE( &gCurrentThread->IsLocked );
+ ret = cur->Messages->Length;
+ RELEASE( &cur->IsLocked );
return ret;
}
// Get message
if(Buffer != GETMSG_IGNORE)
- memcpy(Buffer, gCurrentThread->Messages->Data, gCurrentThread->Messages->Length);
- ret = gCurrentThread->Messages->Length;
+ memcpy(Buffer, cur->Messages->Data, cur->Messages->Length);
+ ret = cur->Messages->Length;
// Remove from list
- tmp = gCurrentThread->Messages->Next;
- free(gCurrentThread->Messages);
- gCurrentThread->Messages = tmp;
+ tmp = cur->Messages->Next;
+ free(cur->Messages);
+ cur->Messages = tmp;
- RELEASE( &gCurrentThread->IsLocked );
+ RELEASE( &cur->IsLocked );
return ret;
}
// === IMPORTS ===
extern int Proc_Clone(Uint *Err, Uint Flags);
+extern int Threads_WaitTID(int TID, int *status);
extern Uint Proc_SendMessage(Uint *Err, Uint Dest, Uint Length, void *Data);
extern int Proc_GetMessage(Uint *Err, Uint *Source, void *Buffer);
extern int Proc_Execve(char *File, char **ArgV, char **EnvP);
extern Uint Binary_Load(char *file, Uint *entryPoint);
extern int VFS_FInfo(int FD, void *Dest, int MaxACLs);
+extern int Threads_SetName(char *NewName);
+extern int Threads_GetPID();
+extern int Threads_GetTID();
+extern int Threads_GetUID();
+extern int Threads_GetGID();
// === CODE ===
void SyscallHandler(tSyscallRegs *Regs)
switch(Regs->Num)
{
// -- Exit the current thread
- case SYS_EXIT: Proc_Exit(); break;
+ case SYS_EXIT: Threads_Exit(); break;
// -- Put the current thread to sleep
- case SYS_SLEEP: Proc_Sleep(); Log(" SyscallHandler: %i is alive", gCurrentThread->TID); break;
+ case SYS_SLEEP: Threads_Sleep(); break;
// -- Yield current timeslice
- case SYS_YIELD: Proc_Yield(); break;
+ case SYS_YIELD: Threads_Yield(); break;
// -- Clone the current thread
case SYS_CLONE:
ret = -1;
break;
+ // -- Wait for a thread
+ case SYS_WAITTID:
+ ret = Threads_WaitTID(Regs->Arg1, (void*)Regs->Arg2);
+ break;
+
case SYS_GETPHYS:
ret = MM_GetPhysAddr(Regs->Arg1);
break;
case SYS_UNMAP: MM_Deallocate(Regs->Arg1); break;
// -- Get Thread/Process IDs
- case SYS_GETTID: ret = gCurrentThread->TID; break;
- case SYS_GETPID: ret = gCurrentThread->TGID; break;
+ case SYS_GETTID: ret = Threads_GetTID(); break;
+ case SYS_GETPID: ret = Threads_GetPID(); break;
// -- Get User/Group IDs
- case SYS_GETUID: ret = gCurrentThread->UID; break;
- case SYS_GETGID: ret = gCurrentThread->GID; break;
+ case SYS_GETUID: ret = Threads_GetUID(); break;
+ case SYS_GETGID: ret = Threads_GetGID(); break;
// -- Send Message
case SYS_SENDMSG:
case SYS_SETNAME:
// Sanity Check
if(!Regs->Arg1) { ret = -1; err = -EINVAL; break; }
- // Lock Process
- LOCK( &gCurrentThread->IsLocked );
- // Free old name
- if(gCurrentThread->ThreadName && (Uint)gCurrentThread->ThreadName > 0xE0800000)
- free(gCurrentThread->ThreadName);
- // Change name
- gCurrentThread->ThreadName = malloc( strlen( (char*)Regs->Arg1 ) + 1 );
- strcpy(gCurrentThread->ThreadName, (char*)Regs->Arg1);
- // Unlock
- RELEASE( &gCurrentThread->IsLocked );
+ Threads_SetName( (void*)Regs->Arg1 );
break;
// ---
--- /dev/null
+/*
+ * Acess2
+ * threads.c
+ * - Common Thread Control
+ */
+#include <common.h>
+#include <threads.h>
+
+// === CONSTANTS ===
+#define DEFAULT_QUANTUM 10
+#define DEFAULT_TICKETS 5
+#define MAX_TICKETS 10
+
+// === IMPORTS ===
+extern void ArchThreads_Init();
+extern tThread *Proc_GetCurThread();
+extern int Proc_Clone(Uint *Err, Uint Flags);
+
+// === PROTOTYPES ===
+void Threads_Init();
+void Threads_SetName(char *NewName);
+void Threads_SetTickets(int Num);
+ int Threads_WaitTID(int TID, int *status);
+tThread *Threads_GetThread(Uint TID);
+tThread *Threads_int_GetPrev(tThread **List, tThread *Thread);
+void Threads_Exit(int TID, int Status);
+void Threads_Kill(tThread *Thread, int Status);
+void Threads_Yield();
+void Threads_Sleep();
+void Threads_Wake(tThread *Thread);
+ int Threads_GetPID();
+ int Threads_GetTID();
+ int Threads_GetUID();
+ int Threads_GetGID();
+void Threads_Dump();
+
+// === GLOBALS ===
+// -- Core Thread --
+tThread gThreadZero = {
+ NULL, 0, // Next, Lock
+ THREAD_STAT_ACTIVE, // Status
+ 0, // Exit Status
+ 0, 0, // TID, TGID
+ 0, 0, // UID, GID
+ 0, // Parent Thread ID
+ "ThreadZero", // Name
+
+ 0, // Kernel Stack
+ {0}, // Saved State
+ {0}, // VM State
+
+ 0, {0}, {0}, // Signal State
+
+ NULL, NULL, // Messages, Last Message
+ DEFAULT_QUANTUM, DEFAULT_QUANTUM, // Quantum, Remaining
+ DEFAULT_TICKETS,
+ {0} // Default config to zero
+ };
+// -- Processes --
+// --- Locks ---
+volatile int giThreadListLock = 0; ///\note NEVER use a heap function while locked
+// --- Current State ---
+volatile int giNumActiveThreads = 0;
+volatile int giTotalTickets = 0;
+volatile Uint giNextTID = 1;
+// --- Thread Lists ---
+tThread *gActiveThreads = NULL; // Currently Running Threads
+tThread *gSleepingThreads = NULL; // Sleeping Threads
+tThread *gDeleteThreads = NULL; // Threads to delete
+ int giNumCPUs = 1;
+
+// === CODE ===
+/**
+ * \fn void Threads_Init()
+ * \brief Initialse the thread list
+ */
+void Threads_Init()
+{
+ ArchThreads_Init();
+
+ // Create Initial Task
+ gActiveThreads = &gThreadZero;
+ giTotalTickets = gThreadZero.NumTickets;
+ giNumActiveThreads = 1;
+
+ #if 1
+ // Create Idle Task
+ if(Proc_Clone(0, 0) == 0)
+ {
+ tThread *cur = Proc_GetCurThread();
+ cur->ThreadName = "Idle Thread";
+ Threads_SetTickets(0); // Never called randomly
+ cur->Quantum = 1; // 1 slice quantum
+ for(;;) __asm__ __volatile__ ("hlt"); // Just yeilds
+ }
+ #endif
+}
+
+/**
+ * \fn void Threads_SetName(char *NewName)
+ * \brief Sets the current thread's name
+ */
+void Threads_SetName(char *NewName)
+{
+ tThread *cur = Proc_GetCurThread();
+ if( IsHeap(cur->ThreadName) )
+ free( cur->ThreadName );
+ cur->ThreadName = malloc(strlen(NewName)+1);
+ strcpy(cur->ThreadName, NewName);
+}
+
+/**
+ * \fn void Threads_SetTickets(int Num)
+ * \brief Sets the 'priority' of a task
+ */
+void Threads_SetTickets(int Num)
+{
+ tThread *cur = Proc_GetCurThread();
+ if(Num < 0) return;
+ if(Num > MAX_TICKETS) Num = MAX_TICKETS;
+
+ LOCK( &giThreadListLock );
+ giTotalTickets -= cur->NumTickets;
+ cur->NumTickets = Num;
+ giTotalTickets += Num;
+ //LOG("giTotalTickets = %i", giTotalTickets);
+ RELEASE( &giThreadListLock );
+}
+
+/**
+ * \fn void Threads_WaitTID(int TID, int *status)
+ * \brief Wait for a task to change state
+ */
+int Threads_WaitTID(int TID, int *status)
+{
+ // Any Child
+ if(TID == -1) {
+
+ return -1;
+ }
+
+ // Any peer/child thread
+ if(TID == 0) {
+
+ return -1;
+ }
+
+ // TGID = abs(TID)
+ if(TID < -1) {
+ return -1;
+ }
+
+ // Specific Thread
+ if(TID > 0) {
+ tThread *t = Threads_GetThread(TID);
+ int initStatus = t->Status;
+ int ret;
+ while(t->Status == initStatus) Threads_Yield();
+ ret = t->RetStatus;
+ switch(t->Status)
+ {
+ case THREAD_STAT_ZOMBIE:
+ t->Status = THREAD_STAT_DEAD;
+ *status = 0;
+ break;
+ default:
+ *status = -1;
+ break;
+ }
+ return ret;
+ }
+
+ return -1;
+}
+
+/**
+ * \fn tThread *Threads_GetThread(Uint TID)
+ * \brief Gets a thread given its TID
+ */
+tThread *Threads_GetThread(Uint TID)
+{
+ tThread *thread;
+
+ // Search Active List
+ for(thread = gActiveThreads;
+ thread;
+ thread = thread->Next)
+ {
+ if(thread->TID == TID)
+ return thread;
+ }
+
+ // Search Sleeping List
+ for(thread = gSleepingThreads;
+ thread;
+ thread = thread->Next)
+ {
+ if(thread->TID == TID)
+ return thread;
+ }
+
+ return NULL;
+}
+
+/**
+ * \fn tThread *Threads_int_GetPrev(tThread *List, tThread *Thread)
+ * \brief Gets the previous entry in a thead linked list
+ */
+tThread *Threads_int_GetPrev(tThread **List, tThread *Thread)
+{
+ tThread *ret;
+ // First Entry
+ if(*List == Thread) {
+ return (tThread*)List;
+ } else {
+ for(ret = *List;
+ ret->Next && ret->Next != Thread;
+ ret = ret->Next
+ );
+ // Error if the thread is not on the list
+ if(!ret->Next || ret->Next != Thread) {
+ return NULL;
+ }
+ }
+ return ret;
+}
+
+/**
+ * \fn void Threads_Exit(int TID, int Status)
+ * \brief Exit the current process
+ */
+void Threads_Exit(int TID, int Status)
+{
+ Threads_Kill( Proc_GetCurThread(), (Uint)Status & 0xFF );
+}
+
+/**
+ * \fn void Threads_Kill(tThread *Thread, int Status)
+ * \brief Kill a thread
+ * \param TID Thread ID (0 for current)
+ */
+void Threads_Kill(tThread *Thread, int Status)
+{
+ tThread *prev;
+ tMsg *msg;
+
+ // Kill all children
+ #if 0
+ {
+ tThread *child;
+ for(child = gActiveThreads;
+ child;
+ child = child->Next)
+ {
+ if(child->PTID == gCurrentThread->TID)
+ Threads_Kill(child, -1);
+ }
+ }
+ #endif
+
+ ///\note Double lock is needed due to overlap of locks
+
+ // Lock thread (stop us recieving messages)
+ LOCK( &Thread->IsLocked );
+
+ // Lock thread list
+ LOCK( &giThreadListLock );
+
+ // Get previous thread on list
+ prev = Threads_int_GetPrev( &gActiveThreads, Thread );
+ if(!prev) {
+ Warning("Proc_Exit - Current thread is not on the active queue");
+ return;
+ }
+
+ // Clear Message Queue
+ while( Thread->Messages )
+ {
+ msg = Thread->Messages->Next;
+ free( Thread->Messages );
+ Thread->Messages = msg;
+ }
+
+ Thread->Remaining = 0; // Clear Remaining Quantum
+ Thread->Quantum = 0; // Clear Quantum to indicate dead thread
+ prev->Next = Thread->Next; // Remove from active
+
+ giNumActiveThreads --;
+ giTotalTickets -= Thread->NumTickets;
+
+ // Mark thread as a zombie
+ Thread->RetStatus = Status;
+
+ // Don't Zombie if we are being killed as part of a tree
+ if(Status == -1)
+ {
+ Thread->Status = THREAD_STAT_DEAD;
+ // Add to delete queue
+ if(gDeleteThreads) {
+ Thread->Next = gDeleteThreads;
+ gDeleteThreads = Thread;
+ } else {
+ Thread->Next = NULL;
+ gDeleteThreads = Thread;
+ }
+ } else {
+ Thread->Status = THREAD_STAT_ZOMBIE;
+ }
+
+ // Release spinlocks
+ RELEASE( &Thread->IsLocked ); // Released first so that it IS released
+ RELEASE( &giThreadListLock );
+ if(Status != -1) HALT();
+}
+
+/**
+ * \fn void Threads_Yield()
+ * \brief Yield remainder of timeslice
+ */
+void Threads_Yield()
+{
+ Proc_GetCurThread()->Quantum = 0;
+ HALT();
+}
+
+/**
+ * \fn void Threads_Sleep()
+ * \brief Take the current process off the run queue
+ */
+void Threads_Sleep()
+{
+ tThread *cur = Proc_GetCurThread();
+ tThread *thread;
+
+ //Log("Proc_Sleep: %i going to sleep", gCurrentThread->TID);
+
+ // Acquire Spinlock
+ LOCK( &giThreadListLock );
+
+ // Get thread before current thread
+ thread = Threads_int_GetPrev( &gActiveThreads, cur );
+ if(!thread) {
+ Warning("Proc_Sleep - Current thread is not on the active queue");
+ return;
+ }
+
+ // Don't sleep if there is a message waiting
+ if( cur->Messages ) {
+ RELEASE( &giThreadListLock );
+ return;
+ }
+
+ // Unset remaining timeslices (force a task switch on timer fire)
+ cur->Remaining = 0;
+
+ // Remove from active list
+ thread->Next = cur->Next;
+
+ // Add to Sleeping List (at the top)
+ cur->Next = gSleepingThreads;
+ gSleepingThreads = cur;
+
+ // Reduce the active count & ticket count
+ giNumActiveThreads --;
+ giTotalTickets -= cur->NumTickets;
+
+ // Mark thread as sleeping
+ cur->Status = THREAD_STAT_SLEEPING;
+
+ // Release Spinlock
+ RELEASE( &giThreadListLock );
+
+ HALT();
+}
+
+
+/**
+ * \fn void Threads_Wake( tThread *Thread )
+ * \brief Wakes a sleeping/waiting thread up
+ */
+void Threads_Wake(tThread *Thread)
+{
+ tThread *prev;
+ switch(Thread->Status)
+ {
+ case THREAD_STAT_ACTIVE: break;
+ case THREAD_STAT_SLEEPING:
+ LOCK( &giThreadListLock );
+ prev = Threads_int_GetPrev(&gSleepingThreads, Thread);
+ prev->Next = Thread->Next; // Remove from sleeping queue
+ Thread->Next = gActiveThreads; // Add to active queue
+ gActiveThreads = Thread;
+ Thread->Status = THREAD_STAT_ACTIVE;
+ RELEASE( &giThreadListLock );
+ break;
+ case THREAD_STAT_WAITING:
+ Warning("Thread_Wake - Waiting threads are not currently supported");
+ break;
+ case THREAD_STAT_DEAD:
+ Warning("Thread_Wake - Attempt to wake dead thread (%i)", Thread->TID);
+ break;
+ default:
+ Warning("Thread_Wake - Unknown process status (%i)\n", Thread->Status);
+ break;
+ }
+}
+
+// --- Process Structure Access Functions ---
+int Threads_GetPID()
+{
+ return Proc_GetCurThread()->TGID;
+}
+int Threads_GetTID()
+{
+ return Proc_GetCurThread()->TID;
+}
+int Threads_GetUID()
+{
+ return Proc_GetCurThread()->UID;
+}
+int Threads_GetGID()
+{
+ return Proc_GetCurThread()->GID;
+}
+
+/**
+ * \fn void Threads_Dump()
+ * \brief Dums a list of currently running threads
+ */
+void Threads_Dump()
+{
+ tThread *thread;
+
+ Log("Active Threads:");
+ for(thread=gActiveThreads;thread;thread=thread->Next)
+ {
+ Log(" %i (%i) - %s", thread->TID, thread->TGID, thread->ThreadName);
+ Log(" %i Tickets, Quantum %i", thread->NumTickets, thread->Quantum);
+ Log(" KStack 0x%x", thread->KernelStack);
+ }
+ Log("Sleeping Threads:");
+ for(thread=gSleepingThreads;thread;thread=thread->Next)
+ {
+ Log(" %i (%i) - %s", thread->TID, thread->TGID, thread->ThreadName);
+ Log(" %i Tickets, Quantum %i", thread->NumTickets, thread->Quantum);
+ Log(" KStack 0x%x", thread->KernelStack);
+ }
+}
int VFS_CheckACL(tVFS_Node *Node, Uint Permissions)
{
int i;
- int uid = Proc_GetUID();
- int gid = Proc_GetGID();
+ int uid = Threads_GetUID();
+ int gid = Threads_GetGID();
// Root can do anything
if(uid == 0) return 1;
cBUILTINS[length].fcn(iArgCount-1, &saArgs[1]);\r
break;\r
}\r
- }
- // Calling a file\r
- if(length == BUILTIN_COUNT)\r
- {\r
- GeneratePath(saArgs[1], gsCurrentDirectory, gsTmpBuffer);\r
- // Use length in place of fp\r
- length = open(gsTmpBuffer, 0);\r
- // Check file existence\r
- if(length == -1) {\r
- Print("Unknown Command: `");Print(saArgs[1]);Print("'\n"); // Error Message\r
- continue;\r
- }\r
- // Check if the file is a directory\r
- finfo( length, &info );
- close( length );\r
- if(info.flags & FILEFLAG_DIRECTORY) {\r
- Print("`");Print(saArgs[1]); // Error Message\r
- Print("' is a directory.\n");\r
- continue;\r
- }\r
- pid = clone(CLONE_VM, 0);\r
- if(pid == 0) execve(gsTmpBuffer, &saArgs[1], NULL);\r
- if(pid <= 0) {\r
- Print("Unablt to create process: `");Print(gsTmpBuffer);Print("'\n"); // Error Message
- //SysDebug("pid = %i\n", pid);\r
- }\r
- else {\r
- //waitpid(pid, K_WAITPID_DIE);\r
- }\r
+ }\r
+ \r
+ if(length != BUILTIN_COUNT) continue;\r
+
+ // - Calling a file\r
+ GeneratePath(saArgs[1], gsCurrentDirectory, gsTmpBuffer);\r
+ // Use length in place of fp\r
+ length = open(gsTmpBuffer, 0);\r
+ // Check file existence\r
+ if(length == -1) {\r
+ Print("Unknown Command: `");Print(saArgs[1]);Print("'\n"); // Error Message\r
+ continue;\r
+ }\r
+ // Check if the file is a directory\r
+ finfo( length, &info );
+ close( length );\r
+ if(info.flags & FILEFLAG_DIRECTORY) {\r
+ Print("`");Print(saArgs[1]); // Error Message\r
+ Print("' is a directory.\n");\r
+ continue;\r
+ }\r
+ pid = clone(CLONE_VM, 0);\r
+ if(pid == 0) execve(gsTmpBuffer, &saArgs[1], NULL);\r
+ if(pid <= 0) {\r
+ Print("Unablt to create process: `");Print(gsTmpBuffer);Print("'\n"); // Error Message
+ //SysDebug("pid = %i\n", pid);\r
+ }\r
+ else {\r
+ //waitpid(pid, K_WAITPID_DIE);\r
}\r
}\r
}\r