/* TMThread.c, Copyright (c) by Christian Conrad, Swiss Federal Institute of Technology, Computer Engineering and Networks Laboratory. TOPSY -- A Teachable Operating System. Implementation of a tiny and simple micro kernel for teaching purposes. For further information, please visit http://www.tik.ee.ethz.ch/~topsy This software is provided under the terms of the GNU General Public Licence. A full copy of the GNU GPL is provided in the file COPYING found in the development root of Topsy. */ /* File: $Source: /usr/drwho/vault/cvs/topsy/Topsy/Threads/TMThread.c,v $ Author(s): Christian Conrad Affiliation: ETH Zuerich, TIK Version: $Revision: 1.62 $ Creation Date: Last Date of Change: $Date: 1999/12/13 21:48:33 $ by: $Author: ruf $ $Log: TMThread.c,v $ Revision 1.62 1999/12/13 21:48:33 ruf GNU General Public Licence Update Revision 1.61 1999/09/22 13:23:38 gfa *** empty log message *** Revision 1.60 1999/09/22 10:29:49 gfa optimized threadYield: restore not necessary, it's called later anyway Revision 1.59 1999/06/06 20:55:12 jeker putting everything together for Topsy 2.0 Revision 1.58 1999/04/08 11:40:16 jeker added some new files, modified some others for unix port Revision 1.57 1999/02/02 23:04:03 cjeker latest fixes Revision 1.56 1999/01/14 18:40:06 cjeker threadStart and threadBuild have now one more argument: priority Revision 1.55 1999/01/12 09:37:20 cjeker bugfix Revision 1.54 1999/01/08 21:57:36 cjeker bug fix Revision 1.53 1999/01/07 16:15:55 cjeker removed lightWeight Threads and added new syscall tmGetIDInfo Revision 1.52 1998/06/08 20:05:19 gfa changed limits.h to Limits.h Revision 1.51 1998/06/08 19:43:18 gfa changed to ULONG_MAX Revision 1.50 1998/06/05 14:36:32 gfa added get time (as info syscall) Revision 1.49 1998/03/26 20:35:52 gfa added getFirst/getNext to threadInfo Revision 1.48 1997/04/21 07:25:17 conrad cleanup * Revision 1.47 1997/04/20 17:26:51 gfa * removed superfluous console messages * * Revision 1.46 1997/04/18 16:37:48 conrad * *** empty log message *** * * Revision 1.45 1997/04/18 16:35:54 conrad * *** empty log message *** * * Revision 1.44 1997/04/13 17:00:48 conrad * no longer startMinimalThread(), standard use of threadBuild() instead * * Revision 1.43 1997/04/09 16:22:36 conrad * *** empty log message *** * * Revision 1.42 1997/04/07 13:59:44 conrad * no longer PANIC in threadKill if inexistant thread id * * Revision 1.41 1997/04/06 18:54:54 gfa * source cleanup and change to tmExit (added vmCleanup) * * Revision 1.40 1997/03/31 20:32:36 gfa * moved almost all register accesses to TMHal and used functions here * also adapted this module to the changes in scheduler. * * Revision 1.39 1997/03/28 13:29:52 conrad * adding of get info * * Revision 1.38 1997/03/28 12:37:56 conrad * adding of TM_INFO * * Revision 1.37 1997/03/27 17:28:13 gfa * changed list usage to hinting lists * * Revision 1.36 1997/03/27 13:54:38 conrad * *** empty log message *** * * Revision 1.35 1997/03/26 10:50:32 gfa * cosmetics * * Revision 1.34 1997/03/24 10:58:18 conrad * *** empty log message *** * * Revision 1.33 1997/03/22 12:51:27 conrad * added comments on threadYield() * * Revision 1.32 1997/03/21 20:15:06 conrad * *** empty log message *** * * Revision 1.31 1997/03/21 20:06:15 conrad * *** empty log message *** * * Revision 1.30 1997/03/21 20:01:53 conrad * new idle thread * * Revision 1.29 1997/03/21 19:29:19 conrad * Prepared for introducing an idle thread * * Revision 1.28 1997/03/21 19:14:06 conrad * cosmetics * * Revision 1.27 1997/03/21 16:13:23 conrad * changed restoreContext() to check if a new threadPtr is NULL, if yes, restore * of a 'fake' idle context * * Revision 1.26 1997/03/21 13:54:54 conrad * *** empty log message *** * * Revision 1.25 1997/03/21 13:02:49 conrad * *** empty log message *** * * Revision 1.24 1997/03/20 14:02:03 conrad * fixed exit code * * Revision 1.23 1997/03/20 10:35:22 conrad * threadBuild() removes threadStartStatic (for consistency reasons) * * Revision 1.22 1997/03/19 21:38:57 conrad * total confusion of stackStart and stackEnd (...) * threadKill and threadExit not yet fully correct * * Revision 1.21 1997/03/18 16:29:14 conrad * Adding of an exceptionContextFlag to distinguish between * normal thread and exception contexts * * Revision 1.20 1997/03/16 22:11:04 gfa * major revision, fixed threadStart * * Revision 1.19 1997/03/14 16:33:21 conrad * &threadPtr instead of threadPtr (bug corrected) * * Revision 1.18 1997/03/14 10:51:17 conrad * Adding of debug information * * Revision 1.17 1997/03/13 11:46:40 conrad * cosmetics * * Revision 1.16 1997/03/12 17:53:48 conrad * Debugging version * * Revision 1.15 1997/03/11 20:07:06 conrad * First version to be debugged with Simulator * * Revision 1.14 1997/03/10 13:45:00 gfa * changes for hmAlloc/hmFree * * Revision 1.13 1997/03/10 11:31:53 conrad * some improvements * * Revision 1.12 1997/03/09 20:43:56 gfa * *** empty log message *** * * Revision 1.11 1997/03/07 13:00:38 conrad * Adding Error module * * Revision 1.10 1997/03/04 16:56:01 conrad * minor changes * * Revision 1.9 1997/02/27 14:38:08 conrad * minor changes * * Revision 1.8 1997/02/23 17:39:51 gfa * corrected hashlist get paramter order * * Revision 1.7 1997/02/21 11:38:02 conrad * Correct use of syscalls * * Revision 1.6 1997/02/21 10:40:29 conrad * Forgot to use syscalls for hmAlloc() and vmAlloc() :-( * * Revision 1.5 1997/02/21 09:15:13 conrad * Intermediate status (not yet completed) * * Revision 1.4 1997/02/19 17:46:24 conrad * *** empty log message *** * * Revision 1.3 1997/02/18 17:25:37 conrad * *** empty log message *** * * Revision 1.2 1997/02/13 15:44:51 conrad * First compilation/linking of complete environment (all modules) * * Revision 1.1 1997/02/12 15:35:07 conrad * Initial revision * */ #include "TMThread.h" #include "TMScheduler.h" #include "TMHal.h" #include "TMIPC.h" #include "TMMain.h" #include "HashList.h" #include "List.h" #include "Lock.h" #include "Syscall.h" #include "Support.h" #include "Limits.h" #include "MMMapping.h" #define noRunningThreadString "(in init)" /* Global variables */ HashList threadHashList; /* Hash list of all threads */ extern List threadList; /* Contains all threads (user & kernel) */ LockDesc threadLockDesc; /* lock to protect global thread lists */ Lock threadLock = &threadLockDesc; extern ThreadId nextUserThreadId; /* from TMMain */ extern ThreadId nextKernelThreadId; int exceptionContextFlag = 0; /* Set to 1 for exception context */ /* * Get a new thread id */ ThreadId getThreadId(AddressSpace space) { Thread* threadPtr; int wraps = 0; Error ret; if (space == KERNEL) { while (TRUE) { if (nextKernelThreadId > 0) { /* negative kernel id underflow */ wraps++; if (wraps == 2) PANIC("out of kernel thread id's"); nextKernelThreadId = FIRST_KERNELTHREAD; } ret = hashListGet(threadHashList, (void**)(&threadPtr), nextKernelThreadId); if (ret == HASHNOTFOUND) return nextKernelThreadId--; nextKernelThreadId--; } } else { while (TRUE) { if (nextUserThreadId < 0) { /* positive user id overflow */ wraps++; if (wraps == 2) PANIC("out of user thread id's"); nextUserThreadId = FIRST_USERTHREAD; } ret = hashListGet(threadHashList, (void**)(&threadPtr), nextUserThreadId); if (ret == HASHNOTFOUND) return nextUserThreadId++; nextUserThreadId++; } } return 0; } /* This procedure initializes a thread structure. resources are passed as * arguments so threadBuild doesn't need to know about memory allocation. */ void threadBuild( ThreadId id, ThreadId parentId, char* name, ProcContext* contextPtr, Address stackBaseAddress, /* static address or * as obtained via vmAlloc */ unsigned int stackSize, ThreadMainFunction mainFunction, ThreadArg parameter, AddressSpace space, Thread* threadPtr, ThreadPriority priority) { Message threadExitMsg = {TMTHREADID, TM_EXIT, {{NULL, NULL, NULL}}}; int exitCodeLength = endAutomaticThreadExit - automaticThreadExit; Register mode; Register sp; threadPtr->id = id; threadPtr->parentId = parentId; if (name != NULL) { stringNCopy(threadPtr->name, name, sizeof(threadPtr->name)); } else { stringNCopy(threadPtr->name, "no name", sizeof(threadPtr->name)); } initMsgQueue( &(threadPtr->msgQueue)); /* prepare the value of the status register of the new threads context * it is adjusted that the new thread runs either in user or kernel * mode with interrupts enabled. */ if (space == USER) { schedulerInsert(threadPtr, priority); mode = STATUS_INT_ENABLE_USER_PREV; } else { schedulerInsert(threadPtr, priority); mode = STATUS_INT_ENABLE_KERNEL_PREV; } threadPtr->stackStart = (char*)(stackBaseAddress) + stackSize - 4; threadPtr->stackEnd = stackBaseAddress; threadPtr->contextPtr = contextPtr; /* here we setup the context. register access is machine dependent, * so this is done in TMHal */ #warning __________added_tmSetMachineDependentRegisters_______ /* this function accesses the context first, e.g. to copy templates */ tmSetMachineDependentRegisters(threadPtr->contextPtr, space); sp = (Register) ((char*)(threadPtr->stackStart) - sizeof(Message) - exitCodeLength); tmSetStackPointer(threadPtr->contextPtr, sp); /* the code at sp+4 does an automatic exit() and will be executed * after the main procedure of the thread does a return without an exit() * see also the byteCopies a few lines below... */ tmSetReturnAddress(threadPtr->contextPtr, sp + THREADRETURNADDR); tmSetProgramCounter(threadPtr->contextPtr, (Register)mainFunction); tmSetFramePointer(threadPtr->contextPtr, 0); tmSetArgument0(threadPtr->contextPtr, (Register)parameter); tmSetStatusRegister(threadPtr->contextPtr, mode); enableAllInterruptsInContext(threadPtr->contextPtr); byteCopy((char*)(threadPtr->stackStart) - sizeof(Message) + THREADRETURNADDR, &threadExitMsg, sizeof(Message)); byteCopy((char*)(threadPtr->stackStart) - sizeof(Message) - exitCodeLength + THREADRETURNADDR, automaticThreadExit, exitCodeLength); } /* This procedure destroys a thread structure. resources are in the ThreadHashList * so threadDestroy doesn't need to know about memory allocation. */ Error threadDestroy( ThreadId id) { Thread* threadPtr; /* Lookup in hash table to find pointer onto destination thread */ if (hashListGet( threadHashList, (void**)(&threadPtr), id) != HASHOK) { /* inexistent threads can't call exit! */ WARNING("threadExit(): could not find threadId"); return -1; } /* Clean up hash table, global thread list and scheduler list */ lock(threadLock); { hashListRemove(threadHashList, id); listRemove(threadList, threadPtr, NULL); } unlock(threadLock); schedulerRemove(threadPtr); /* Deallocate stack and thread descriptor. * stackEnd is the basis address ! */ vmFree((Address)(threadPtr->stackEnd)); vmCleanup(threadPtr->id); hmFree((Address)threadPtr); /* Wake all threads that were blocked waiting for * a message from exited thread */ ipcFreeBlockedThreads( id); return TM_OK; } /* * */ ThreadId threadStart( ThreadMainFunction fctnAddr, ThreadArg parameter, AddressSpace space, char* name, ThreadId parentId, ThreadPriority priority) { Thread* threadPtr; Address spaceFrom; unsigned long spaceSize; Address stackBaseAddress; /* users may pass illegal pointers to name. this has to be checked here. * we check if the user buffer is inside the user address space and * watch for overflow! */ if (THREAD_MODE(parentId) == USER) { mmAddressSpaceRange(USER, &spaceFrom, &spaceSize); if ((Address)name < spaceFrom || (unsigned long)name + MAXNAMESIZE > (unsigned long)spaceFrom + spaceSize || ULONG_MAX - (unsigned long)name < MAXNAMESIZE) { name = NULL; } } /* Preparation of a new Thread entry, memory allocation for both Thread * and ProcContext structure. For simplicity, these structures are made * contiguous in a single call to hmAlloc(). */ if (hmAlloc( (Address*)&threadPtr, sizeof(Thread)+sizeof(ProcContext)) == HM_ALLOCFAILED) { ERROR("couldn't create thread structure"); return TM_THREADSTARTFAILED; } /* Requesting an execution stack for new thread */ if (vmAlloc( &stackBaseAddress, TM_DEFAULTTHREADSTACKSIZE) == VM_ALLOCFAILED) { ERROR("couldn't create stack for new thread"); /* Deallocate of threadPtr */ hmFree((Address)threadPtr); return TM_THREADSTARTFAILED; } /* Three things may fail: we're out of thread id's or we can't insert * the thread into either the hash list or the global thread list: * in all cases we release the already allocated resources and return * a syscall failure. */ threadPtr->id = getThreadId(space); lock(threadLock); { if ((threadPtr->id == 0) || (hashListAdd( threadHashList, threadPtr, threadPtr->id) != HASHOK) || (listAddInFront( threadList, threadPtr, NULL) != LIST_OK)) { unlock(threadLock); /* Deallocate stack and thread descriptor */ vmFree(stackBaseAddress); hmFree((Address)threadPtr); ERROR("got invalid thread id or list operations failed"); return TM_THREADSTARTFAILED; } } unlock(threadLock); /* If a user thread is required, stack has to be moved to user space */ if (space == USER) { if (vmMove(&stackBaseAddress, threadPtr->id) == VM_MOVEFAILED) { ERROR("user stack could not be moved from kernel space"); vmFree(stackBaseAddress); hmFree((Address)threadPtr); return TM_THREADSTARTFAILED; } } /* we should check the priority first */ #warning __no_priority_check__ /* Building of the thread structure */ threadBuild( threadPtr->id, parentId, name, (ProcContextPtr)((char*)threadPtr+sizeof(Thread)), stackBaseAddress, TM_DEFAULTTHREADSTACKSIZE, fctnAddr, parameter, space, threadPtr, priority); /* Last page of stack is protected to catch most overflows */ /* vmProtect(...); */ /* New thread is put in ready status */ schedulerSetReady(threadPtr); /* at this point the new thread is born and may be scheduled (i.e. even * before the parent receives the child id as a reply message */ return threadPtr->id; } /* * Handle tmExit() syscall */ void threadExit(ThreadId threadId) { threadDestroy( threadId); } /* * This procedure is EXCLUSIVELY invoked in an exception context. * Thus it is possible to invoke the restoreContext() function. */ void threadYield() { /* The next runnable thread is computed */ schedule(); /* next, msgDispatcher returns and a regular restoreContext() is * performed */ } Error threadKill( ThreadId killedId, ThreadId killerId) { /*Thread* threadPtr; int exitCodeLength = endAutomaticThreadExit - automaticThreadExit;*/ INFO(" Entering threadKill"); /* Not allowed for a user thread to kill a kernel thread */ if (THREAD_MODE(killerId) == USER && THREAD_MODE(killedId) == KERNEL) { /* The faulting user thread is killed (recursively) without * remorse. the new killer id is TMTHREADID so this will hopefully * happen... */ if (threadKill(killerId, TMTHREADID) == TM_KILLFAILED) { return TM_THREADKILLFAILED; } return TM_OK; } threadDestroy(killedId); return TM_OK; } /* checking for a valid user space address */ static Boolean tmCheckTMInfoAddress(ThreadId id, Address address) { Address spaceFrom; unsigned long spaceSize; static unsigned long size = sizeof(struct ThreadInfo_t) ; if (THREAD_MODE(id) == KERNEL) return TRUE; /* buffer must be in user space (at least). otherwise users could * access kernel memory. * * an exact test would require the address to be inside a vmRegion, * but this would be quite an expensive test */ mmAddressSpaceRange(USER, &spaceFrom, &spaceSize); if (address < spaceFrom || (unsigned long)address + size > (unsigned long)spaceFrom + spaceSize || ULONG_MAX - (unsigned long)address < size) return FALSE; else return TRUE; } /* answers questions about a thread's identity, nice, huh? */ Error threadInfo( ThreadId id, ThreadId aboutId, ThreadInfoKind kind, ThreadInfo* infoPtr, long int values[]) { Thread* threadPtr; int ret; switch (kind) { case SPECIFIC_ID: if (aboutId == SELF) { aboutId = id; } /* Lookup in hash table to find pointer onto aboutId thread */ if (hashListGet( threadHashList, (void**)(&threadPtr), aboutId) != HASHOK) { return TM_FAILED; } values[0] = aboutId; /* own thread id */ values[1] = threadPtr->parentId; /* parent thread id */ break; case GETFIRST: if (listGetFirst(threadList, (void **)(&threadPtr)) != LIST_OK) { return TM_FAILED; } if (threadPtr == NULL) { return TM_FAILED; } if (tmCheckTMInfoAddress(id, infoPtr) == FALSE) { return TM_FAILED; } infoPtr->tid = threadPtr->id; infoPtr->ptid = threadPtr->parentId; stringCopy(infoPtr->name,threadPtr->name); infoPtr->status = threadPtr->schedInfo.status; break; case GETNEXT: if (listGetNext(threadList, (void **)(&threadPtr)) != LIST_OK) { return TM_FAILED; } if (threadPtr == NULL) { return TM_FAILED; } if (tmCheckTMInfoAddress(id, infoPtr) == FALSE) { return TM_FAILED; } infoPtr->tid = threadPtr->id; infoPtr->ptid = threadPtr->parentId; stringCopy(infoPtr->name, threadPtr->name); infoPtr->status = threadPtr->schedInfo.status; break; case THREAD_BY_NAME: if (tmCheckTMInfoAddress(id, infoPtr) == FALSE) { return TM_FAILED; } ret = listGetFirst(threadList, (void **)(&threadPtr)); while (threadPtr != NULL) { if (isStringEqual((char*)infoPtr, threadPtr->name, MAXNAMESIZE)){ values[0] = threadPtr->id; return TM_OK; } ret = listGetNext(threadList, (void **)(&threadPtr)); } values[0] = ANY; return TM_FAILED; break; case GET_ID_INFO: if (aboutId == SELF) { aboutId = id; } /* Lookup in hash table to find pointer onto aboutId thread */ if (hashListGet( threadHashList, (void**)(&threadPtr), aboutId) != HASHOK) { return TM_FAILED; } if (threadPtr == NULL) { return TM_FAILED; } if (tmCheckTMInfoAddress(id, infoPtr) == FALSE) { return TM_FAILED; } infoPtr->tid = threadPtr->id; infoPtr->ptid = threadPtr->parentId; stringCopy(infoPtr->name, threadPtr->name); infoPtr->status = threadPtr->schedInfo.status; break; default: WARNING("invalid info requested"); return TM_FAILED ; break; } return TM_OK; } /* * Returns name of current running thread. Could be replace by a direct * reference to (schedulerRunning())->name, however, such a function * provides more transparency (the global scheduler variable would have * to be declared everywhere where PANIC or WARNING is used ! */ char* getCurrentThreadName() { if (schedulerRunning() == NULL) { return noRunningThreadString; } else { return schedulerRunning()->name; } }