home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
BMUG PD-ROM 1995 Fall
/
PD-ROM F95.toast
/
Programming
/
Programming Utilities
/
ThreadLibrary 1.0 ƒ
/
Source
/
Libraries
/
ThreadLibrary.c
< prev
next >
Encoding:
Amiga
Atari
Commodore
DOS
FM Towns/JPY
Macintosh
Macintosh JP
NeXTSTEP
RISC OS/Acorn
Shift JIS
UTF-8
Wrap
Text File
|
1995-05-04
|
59.7 KB
|
1,826 lines
|
[
TEXT/MPCC
]
/* See the file Distribution for distribution terms.
(c) Copyright 1994 Ari Halberstadt */
/* Thread Library implements nonpreemptive multiple thread execution within
a single application.
950403 aih - added custom scheduler function
950328 aih - removed user-settable fpu save flag
950313 aih - ported to native ppc
950220 aih - added stack size accessor function
- added memory types for memory allocation callbacks
950219 aih - added sleep interval accessor function
950214 aih - general code review
- added missing check for thread->enabled in calculation
of yield interval
950203 aih - removed stack sniffer code
- added memory allocation callbacks
- added register a6 access (this may go away again in the future)
941207 aih - removed almost all compiler dependencies
- added ifdef's to the few (3) places where powerpc-specific
code is needed--a native PPC version is around the corner!
941020 aih - added an enabled flag
941012 aih - added an exit function; the exit function is called
after the suspend function and before the thread is
disposed of
941007 aih - fixed passing wrong data parameter to suspend function.
940706 aih - moved context switching for exceptions and the profiler,
which are used in winter shell, out of thread library
and into a separate file that will be distributed with
winter shell.
940629 aih - added support for universal headers
940617 aih - fixed error in detecting events that was introduced on 940609
- added support for context-switching a profiler
- improved context switches for exceptions (made faster)
- removed status field and operations
- added ThreadErrorType and defined some error codes
- fixed order of execution of actions taken when a thread
is resumed (the stack sniffer was being resumed before
the low-memory globals were restored)
940609 aih - to prevent context switches from threads other than the
main thread, I removed the call to EventAvail, and replaced
it with checks of the CurActive/CurDeactive low-memory
globals and a call to CheckUpdate.
940531 aih - changed ExceptionType to ExceptionStructure; this change
was made for syntactic compatability with the current
version of Winter Shell, and has no impact on the
functionality of Thread Library.
940316 aih - changed ThreadType to ThreadStructure, and ThreadSNType
to ThreadType.
940311 aih - made gThread and gStackSniffer static variables
940309 aih - in v1.0d3 i wrote the stack sniffer sentinel value right over
the heap zone information for the application heap. this of
course resulted in a damaged heap. what a stupid mistake.
now the main thread doesn't use a stack sniffer sentinel
value, but all other threads still use the stack sniffer
sentinel value.
9403?? aih - release 1.0d3
940228 aih - fixed error in setting thread error code in ThreadFromSN
- added sentinel value to stack sniffer VBL task
940225 aih - will compile with either "LoMem.h" or "SysEqu.h"
940223 aih - fixed an error in the private routine ThreadStackFrame
940219 aih - added ThreadSleepSet, ThreadData, ThreadDataSet
- added doubly-linked list circular queue of threads
- all external functions refer to threads using thread serial
numbers instead of thread pointers
- when a thread is activated it is moved to the end of the
queue of threads, so that the round-robbin scheduling is
fairer (since the main thread has the highest priority).
940218 aih - release 1.0d2.1
- to reduce the possibility of conflict with user-defined types,
uses ThreadTicksType instead of TicksType, THREAD_TICKS_MAX
instead of TICKS_MAX, and THREAD_TICKS_SEC instead of
TICKS_SEC
- includes actual headers instead of using THINK C's
non-standard MacHeaders. this was done primarily so I
could use "SysEqu.h" instead of "LoMem.h", but it will
also make porting to another compiler easier.
- added THREAD_DEBUG so thread debug code can be selectively
disabled without defining NDEBUG and surrounded debug
functions with conditional compile directives to reduce
dead-code size in non-debug version
- uses the OS routines Enqueue and Dequeue instead of my
own linked-list code. this made the code cleaner and will
make the object code even more compact.
- made defines with prefix "LM" for all low-memory globals
940217 aih - put back THREAD_SET_GLOBALS code in attempt to fix
update problems
940217 aih - added string to assertion debug statement, and added ifdef
around assertfailed function to keep it from being compiled
into non-debug version
940217 aih - release 1.0d2
- for efficiency, defined TickCount as Ticks low-memory global
- for greater ease in adding ThreadLib to other people's
applications, removed use of exceptions. this also increases
the efficiency of context switches in applications that
don't use exceptions
940215 aih - removed THREAD_SET_GLOBALS code since it didn't seem to be
needed
940214 aih - added thread serial numbers
- added precondition for ThreadEnd
- fixed problem with defer and combine stack adjusts
940213 aih - added stack sniffer VBL task
- added functions for determining the minimum and the default
stack sizes for threads and removed THREAD_STACK_SIZE
940212 aih - made threads use pointers instead of handles. using pointers
increases the efficiency of Thread Library. this won't
significantly increase memory fragmentation since stacks
for threads are already allocated as pointers. (while no
stack is allocated for the main thread, the main thread
is created very early in the application and usually
remains in existence until the application exits.)
- limited release 1.0d1.1
940211 aih - added thread status field
940210 aih - release 1.0d1; got threads to work without crashing! yay! :-)
940204 aih - created */
/*----------------------------------------------------------------------------*/
/* include statements */
/*----------------------------------------------------------------------------*/
#include <Errors.h>
#include <Events.h>
#include <Gestalt.h>
#include <LowMem.h>
#include <Memory.h>
#include <OSUtils.h>
#include <Windows.h>
#include "ThreadLibrary.h"
/*----------------------------------------------------------------------------*/
/* compiler-specific code */
/*----------------------------------------------------------------------------*/
#if ! defined(THINK_C) && ! defined(__MWERKS__) && ! defined(MPW)
/* For compilers other than THINK C, you should be careful when turning
on the optimizer. For instance, I found that the "defer and combine
stack adjusts" option was incompatible with Thread Library. I
recommend first getting Thread Library to work with all
optimizations disabled. Once you know that it runs under the new
compiler, you can enable the optimizations. If it then crashes,
you will know that one or more of the optimizations performed by
the compiler are incompatible with Thread Library and should
therefore be disabled. */
#error tested with THINK C 7.0, MetroWerks CW4, MPW 3.3.1
#endif
#ifdef THINK_C
/* Thread Library will not work correctly (the heap will become
corrupted) when the THINK C "defer and combine stack adjusts"
optimization option is enabled. We therefore disable the option
for this file. All other THINK C optimizations work fine with
Thread Library. */
#pragma options(! defer_adjust)
/* Thread Library will not work if profiling is enabled. During
context switches, the profiler's state is inconsistent, since the
profiler's internal state may refer to the prior executing thread
while the new thread is being switched in. This could result in a
nasty crash. */
#pragma options(! profile)
#endif /* THINK_C */
#ifdef __MWERKS__
/* See note for THINK C profiler. */
#pragma profiler off
#endif /* __MWERKS__ */
/*----------------------------------------------------------------------------*/
/* debug declarations */
/*----------------------------------------------------------------------------*/
/* Define THREAD_DEBUG as 1 to enable debug code, or 0 to disable debug code.
You can also define NDEBUG to disable debug code. Debug code is enabled
by default if both THREAD_DEBUG and NDEBUG are undefined. */
#ifndef THREAD_DEBUG
#ifndef NDEBUG
#define THREAD_DEBUG (1)
#else
#define THREAD_DEBUG (0)
#endif
#endif /* THREAD_DEBUG */
#if THREAD_DEBUG
#define ThreadAssert(x) ((void) ((x) || ThreadAssertFailed(#x)))
static int ThreadAssertFailed(const char *msg)
{
const char *prompt = "ThreadAssertFailed:";
Str255 pmsg;
short i, j;
for (i = 0; i < 255 && prompt[i]; i++)
pmsg[i+1] = prompt[i];
for (j = 0; i+j < 255 && msg[j]; j++)
pmsg[i+j+1] = msg[j];
pmsg[0] = i+j;
DebugStr(pmsg);
return(0);
}
#else /* THREAD_DEBUG */
#define ThreadAssert(x) ((void) 0)
#endif /* THREAD_DEBUG */
#define require(x) ThreadAssert(x)
#define check(x) ThreadAssert(x)
#define ensure(x) ThreadAssert(x)
/*----------------------------------------------------------------------------*/
/* low-memory globals */
/*----------------------------------------------------------------------------*/
/* Two low-memory globals are not defined in "LowMem.h".
The low-memory global variable HiHeapMark must be set when threads
are switched so that QuickDraw will operate correctly. This may
no longer be necessary with the Modern Memory Manager, or with
the PowerPC version of QuickDraw. At least, that's what I'm hoping
is the case. Otherwise, we may have to "break" the rules a bit
to munge HiHeapMark into what we need.
The low-memory global variable StkLowPt must be cleared when any
thread other than the main thread is switched in. This is necessary
to disable the system's stack sniffer VBL task, which otherwise
would cause system error #28. */
#define HiHeapMark (0x0BAE)
#define StkLowPt (0x0110)
#define LMGetHiHeapMark() (*(Ptr *) HiHeapMark)
#define LMSetHiHeapMark(p) ((void) (*(Ptr *) HiHeapMark = (p)))
#define LMGetStkLowPt(p) (*(Ptr *) StkLowPt)
#define LMSetStkLowPt(p) ((void) (*(Ptr *) StkLowPt = (p)))
/*----------------------------------------------------------------------------*/
/* register access */
/*----------------------------------------------------------------------------*/
#ifdef powerc
#include "regppc.h"
/* indexes to special registers */
#define REGISTER_FP (1)
#define REGISTER_SP (3)
typedef struct { ThreadRegistersPPCType gp; } ThreadRegistersType;
/* save the registers */
#define ThreadRegistersFPSave(registers) ((void) 0)
#define ThreadRegistersGPSave(registers) ThreadRegistersPPCSave(registers)
/* restore the registers */
#define ThreadRegistersFPRestore(registers) ((void) 0)
#define ThreadRegistersGPRestore(registers) ThreadRegistersPPCRestore((registers), 1)
#else /* powerc */
/* indexes to special registers */
#define REGISTER_FP (10)
#define REGISTER_SP (11)
/* arrays of registers */
typedef long ThreadRegistersGPType[12];
typedef long ThreadRegistersFPType[12];
/* all of the registers saved by threads */
typedef struct {
ThreadRegistersGPType gp; /* general purpose registers */
ThreadRegistersFPType fp; /* floating point registers */
} ThreadRegistersType;
/* save the general purpose registers (similar to setjmp) */
#pragma parameter __D0 ThreadRegistersGPSave(__A0)
static long ThreadRegistersGPSave(ThreadRegistersGPType registers) =
{
/*
asm {
lea @1, a1
moveq #0, d0
movem.l d3-d7/a1-a7, (a0)
@1
}
*/
0x43FA, 0x0008,
0x7000,
0x48D0, 0xFEF8
};
/* restore the general purpose registers and resume execution of the
saved thread (similar to longjmp) */
#pragma parameter ThreadRegistersGPRestore(__A0)
static void ThreadRegistersGPRestore(ThreadRegistersGPType registers) =
{
/*
asm {
moveq #1, d0
movem.l (a0), d3-d7/a1-a7
jmp (a1)
}
*/
0x7001,
0x4CD0, 0xFEF8,
0x4ED1
};
/* save the floating point registers */
#pragma parameter ThreadRegistersFPSave(__A0)
static void ThreadRegistersFPSave(ThreadRegistersFPType registers) =
{
/*
asm {
fmovem.l fp4-fp7, (a0)
}
*/
0xF210, 0xF00F
};
/* restore the floating point registers */
#pragma parameter ThreadRegistersFPRestore(__A0)
static void ThreadRegistersFPRestore(ThreadRegistersFPType registers) =
{
/* asm { fmovem.l (a0), fp4-fp7 } */
0xF210, 0xD00F
};
#endif /* powerc */
/* return the value of the stack pointer */
static ThreadStackPtr ThreadRegisterStackPointer(void)
{
ThreadRegistersType registers;
(void) ThreadRegistersGPSave(registers.gp);
return((ThreadStackPtr) registers.gp[REGISTER_SP]);
}
/* return the value of the stack frame pointer or link register */
static ThreadStackPtr ThreadRegisterStackFramePointer(void)
{
ThreadRegistersType registers;
(void) ThreadRegistersGPSave(registers.gp);
return((ThreadStackPtr) registers.gp[REGISTER_FP]);
}
/*----------------------------------------------------------------------------*/
/* type definitions, global variables, etc. */
/*----------------------------------------------------------------------------*/
/* structure describing a thread */
typedef struct ThreadStructure {
/* queue links */
struct ThreadStructure *next; /* next thread in queue */
struct ThreadStructure *prev; /* previous thread in queue */
/* internal state */
ThreadRegistersType registers; /* saved registers */
ThreadErrorType error; /* error code from last function called */
ThreadTicksType wake; /* when to wake thread */
ThreadType sn; /* thread's serial number */
Boolean enabled; /* true if thread is enabled */
Boolean fpu; /* true if saves floating point state */
Boolean main; /* true if the main thread */
/* thread's stack */
struct {
ThreadStackPtr top; /* highest address in thread's stack */
ThreadStackPtr limit; /* lowest address in thread's stack */
ThreadSizeType size; /* size of thread's stack */
} stack;
/* low-memory globals */
struct {
Ptr heapEnd; /* value of HeapEnd low-memory global */
Ptr applLimit; /* value of ApplLimit low-memory global */
Ptr hiHeapMark; /* value of HiHeapMark low-memory global */
} lm;
/* application defined functions and data */
struct {
ThreadProcBeginEndType begin; /* called when thread is started */
ThreadProcBeginEndType end; /* called when thread is terminated */
ThreadProcType resume; /* called when thread is resumed */
ThreadProcType suspend; /* called when thread is suspended */
ThreadProcType entry; /* thread's entry point */
void *data; /* data to pass to thread callbacks */
} appl;
} ThreadStructure, *ThreadPtr;
/* queue of threads */
typedef struct {
ThreadPtr head; /* head of queue */
ThreadPtr tail; /* tail of queue */
long nelem; /* number of elements in queue */
} ThreadQueueStructure, *ThreadQueuePtr;
/* structure describing state of thread library */
typedef struct {
ThreadQueueStructure queue; /* queue of threads */
ThreadErrorType error; /* error code from last function called */
ThreadType lastsn; /* serial number of last thread created */
ThreadPtr main; /* main thread */
ThreadPtr active; /* currently active thread */
ThreadPtr zombie; /* thread to dispose of */
ThreadProcScheduleType schedule; /* scheduler callback */
ThreadProcAllocateType allocate; /* memory allocation callback */
ThreadProcDisposeType dispose; /* memory disposal callback */
} ThreadStateStructure;
/* state of thread library */
static ThreadStateStructure gThread;
/* a few forward-declarations of functions */
static ThreadPtr ThreadFromSN(register ThreadType tsn);
static void ThreadDispose(ThreadPtr thread);
/*----------------------------------------------------------------------------*/
/* more register access code */
/*----------------------------------------------------------------------------*/
/* return thread's stack pointer */
static ThreadStackPtr ThreadStackPointer(ThreadPtr thread)
{
ThreadStackPtr sp = NULL;
if (thread == gThread.active)
sp = ThreadRegisterStackPointer();
else
sp = (ThreadStackPtr) thread->registers.gp[REGISTER_SP];
return(sp);
}
/* return thread's register stack frame pointer */
ThreadStackPtr ThreadStackFramePointer(ThreadType tsn)
{
ThreadStackPtr fp = NULL;
ThreadPtr thread;
thread = ThreadFromSN(tsn);
if (thread == gThread.active)
fp = ThreadRegisterStackFramePointer();
else
fp = (ThreadStackPtr) thread->registers.gp[REGISTER_FP];
return(fp);
}
/*----------------------------------------------------------------------------*/
/* memory allocation */
/*----------------------------------------------------------------------------*/
/* This header is inserted at the start of every block of memory allocated
by Thread Library. Among other information, the header also contains a
pointer to the disposal function to use when disposing of the block of
memory. Storing a pointer to the disposal function with each block allows
the application to change the memory allocator or disposal functions after
the block is allocated, thus eliminating a possible source of errors. */
typedef struct {
#if THREAD_DEBUG
ThreadTypeType type; /* type of block */
ThreadSizeType size; /* size of block */
#endif
ThreadProcDisposeType dispose;/* function to use to dispose of block */
} ThreadMemoryHeaderType;
#if THREAD_DEBUG
/* These functions are used while debugging to help validate memory
and to ensure that discarded memory is unuseable. */
/* Return the size of a pointer. */
static ThreadSizeType PtrSize(void *p)
{
return(((ThreadMemoryHeaderType *) p)[-1].size);
}
/* Return the type of a pointer. */
static ThreadSizeType PtrType(void *p)
{
return(((ThreadMemoryHeaderType *) p)[-1].type);
}
/* Fill a pointer with garbage. */
static void *PtrJunk(void *p)
{
char *q;
ThreadSizeType i;
ThreadSizeType n;
q = p;
n = PtrSize(p);
for (i = 0; i < n; i++)
q[i] = ~0;
return(p);
}
#else /* THREAD_DEBUG */
#define PtrJunk(p) ((void) 0)
#endif /* THREAD_DEBUG */
/* Returns the amount of memory needed to store a block of the specified
size. This includes the overhead of the block header. */
static ThreadSizeType PtrSizeNew(ThreadSizeType size, ThreadTypeType type)
{
return(size + sizeof(ThreadMemoryHeaderType));
}
/* Allocate n contiguous bytes using an application supplied allocator,
or the default allocator (NewPtr) if no allocator was supplied by
the application. The disposal function installed at the time the
memory is allocated is saved with the block, so that it may be
called to dispose of the block even the application subsequently
changes the memory allocator. */
static void *PtrBegin(ThreadSizeType size, ThreadTypeType type)
{
ThreadMemoryHeaderType *hdr;
hdr = NULL;
if (gThread.allocate) {
hdr = gThread.allocate(PtrSizeNew(size, type), type);
if (hdr)
hdr->dispose = gThread.dispose;
}
if (! hdr && ! ThreadError()) {
hdr = (ThreadMemoryHeaderType *) NewPtr(PtrSizeNew(size, type));
if (hdr)
hdr->dispose = NULL;
else
ThreadErrorSet(MemError() ? MemError() : memFullErr);
}
#if THREAD_DEBUG
if (hdr) {
hdr->type = type;
hdr->size = size;
}
#endif
if (! hdr && ! ThreadError())
ThreadErrorSet(memFullErr);
return(hdr ? hdr + 1 : NULL);
}
/* allocate and clear memory */
static void *PtrBeginClear(ThreadSizeType size, ThreadTypeType type)
{
ThreadSizeType i;
char *p;
p = PtrBegin(size, type);
if (p) {
for (i = 0; i < size; i++)
p[i] = 0;
}
return(p);
}
/* dispose of the memory */
static void PtrEnd(void *p, ThreadSizeType size, ThreadTypeType type)
{
ThreadMemoryHeaderType *hdr;
if (p) {
PtrJunk(p);
hdr = p;
hdr--;
#if THREAD_DEBUG
check(hdr->size == size && hdr->type == type);
hdr->type = THREAD_TYPE_FREE;
hdr->size = -1;
#endif
if (hdr->dispose)
hdr->dispose(hdr, PtrSizeNew(size, type), type);
else
DisposePtr((Ptr) hdr);
}
}
/*----------------------------------------------------------------------------*/
/* Everything up to this point was just definitions and utility functions.
This is where the real meat of the code begins. The code from here on
is much cleaner and contains few ugly preprocessor directives. */
/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
/* Thread Validation */
/*----------------------------------------------------------------------------*/
#if THREAD_DEBUG
/* ThreadValid returns true if the 'thread' parameter is a valid thread.
This function is primarily for use during debugging, and is called
by the preconditions to most of the functions in Thread Library. You
will usually not need to call this function. So that this function can
easily be called from within other thread functions, the error code is not
set by this function. */
static Boolean ThreadValid(ThreadPtr thread)
{
if (! thread || PtrSize(thread) != sizeof(ThreadStructure)) return(false);
if (PtrType(thread) != THREAD_TYPE_THREAD) return(false);
if (thread->sn <= 0 || gThread.lastsn < thread->sn) return(false);
if (gThread.main) {
if (thread->main) {
if (thread->appl.entry) return(false);
}
else {
if (! thread->appl.entry) return(false);
if (! thread->stack.limit) return(false);
if (PtrType(thread->stack.limit) != THREAD_TYPE_STACK) return(false);
}
}
return(true);
}
/* Validate all threads. */
static Boolean ThreadValidAll(void)
{
Boolean valid;
ThreadPtr thread;
ThreadPtr first;
valid = true;
thread = first = gThread.queue.head;
if (thread) {
do {
valid = ThreadValid(thread);
thread = thread->next;
} while (thread != first && valid);
}
return(valid);
}
#endif /* THREAD_DEBUG */
/*----------------------------------------------------------------------------*/
/* Private Queue Operations */
/*----------------------------------------------------------------------------*/
/* Threads are kept in a circular queue of threads. A doubly-linked list is
used to make removal of an arbitrary thread (not just the head of the
queue) efficient. */
#if THREAD_DEBUG
/* ThreadQueueValid returns true if the queue is valid. */
static Boolean ThreadQueueValid(ThreadQueuePtr queue)
{
if (! queue) return(false);
if (queue->nelem < 0) return(false);
if (queue->nelem == 0 && (queue->head || queue->tail)) return(false);
if (queue->nelem > 0 && (! queue->head || ! queue->tail)) return(false);
if (queue->nelem == 1 && queue->head != queue->tail) return(false);
if (queue->nelem > 1 && queue->head == queue->tail) return(false);
if (queue->head && ! ThreadValid(queue->head)) return(false);
if (queue->tail && ! ThreadValid(queue->tail)) return(false);
return(true);
}
#endif /* THREAD_DEBUG */
/* ThreadEnqueue adds the thread to the end of the queue. */
static void ThreadEnqueue(ThreadQueuePtr queue, ThreadPtr thread)
{
require(ThreadQueueValid(queue));
require(ThreadValid(thread));
require(! ThreadValid(thread->next));
require(! ThreadValid(thread->prev));
if (! queue->head) {
check(! queue->tail);
queue->head = queue->tail = thread;
thread->prev = thread->next = thread;
}
else {
check(queue->tail != NULL);
queue->tail->next = thread;
thread->prev = queue->tail;
thread->next = queue->head;
queue->head->prev = thread;
queue->tail = thread;
}
queue->nelem++;
ensure(queue->tail == thread);
ensure(ThreadValid(thread->next));
ensure(ThreadValid(thread->prev));
ensure(ThreadQueueValid(queue));
}
/* ThreadDequeue removes the thread from the queue. */
static void ThreadDequeue(ThreadQueuePtr queue, ThreadPtr thread)
{
require(ThreadQueueValid(queue));
require(ThreadValid(thread));
require(ThreadValid(thread->next));
require(ThreadValid(thread->prev));
require(queue->nelem > 0);
if (thread == queue->head || thread == queue->tail) {
if (queue->nelem == 1)
queue->head = queue->tail = NULL;
else {
if (thread == queue->head)
queue->head = thread->next;
else
queue->tail = thread->prev;
queue->tail->next = queue->head;
queue->head->prev = queue->tail;
}
}
else {
check(thread->prev != thread && thread->next != thread);
check(queue->nelem > 0);
thread->prev->next = thread->next;
thread->next->prev = thread->prev;
}
thread->next = thread->prev = NULL;
queue->nelem--;
ensure(! ThreadValid(thread->next));
ensure(! ThreadValid(thread->prev));
ensure(ThreadQueueValid(queue));
}
/*----------------------------------------------------------------------------*/
/* Error Handling */
/*----------------------------------------------------------------------------*/
/* returns the last error that occurred, or THREAD_ERROR_NONE
if the last routine completed successfully */
ThreadErrorType ThreadError(void)
{
return(gThread.active ? gThread.active->error : gThread.error);
}
/* sets the error code that will be returned by a
subsequent call to ThreadError */
void ThreadErrorSet(ThreadErrorType error)
{
gThread.error = error;
if (gThread.active)
gThread.active->error = error;
ensure(ThreadError() == error);
}
/*----------------------------------------------------------------------------*/
/* Thread Serial Numbers */
/*----------------------------------------------------------------------------*/
/* Every thread is assigned a unique serial number. Serial numbers are used
to refer to threads, rather than using a pointer, since there is always
the possiblity that a thread may have terminated before a thread pointer
is used, which would make a thread pointer invalid. The specific
assignment of serial numbers to threads is not defined by the interface,
though every valid thread is guaranteed a non-zero serial number.
Note: you should not assume that any thread will have a specific
serial number. */
/* returns the thread's serial number; the error code is not changed. */
static ThreadType ThreadSN(ThreadPtr thread)
{
require(! thread || ThreadValid(thread));
return(thread ? thread->sn : THREAD_NONE);
}
/* Given the serial number of a thread, ThreadFromSN returns the corresponding
thread pointer, or NULL if there is no thread with the specified serial
number. If the thread is found, the error code is cleared, otherwise it
is set to THREAD_ERROR_NOT_FOUND. Since the error code is always either
set or cleared, it is unnecessary to set the error code in many simple
accessor functions that use ThreadFromSN. */
static ThreadPtr ThreadFromSN(register ThreadType tsn)
{
register ThreadPtr thread;
register long nthread;
require(0 <= tsn && tsn <= gThread.lastsn);
thread = gThread.queue.head;
nthread = gThread.queue.nelem;
while (nthread-- > 0 && thread->sn != tsn)
thread = thread->next;
if (thread && thread->sn != tsn)
thread = NULL;
ThreadErrorSet(thread ? THREAD_ERROR_NONE : THREAD_ERROR_NOT_FOUND);
ensure(! thread || (ThreadValid(thread) && thread->sn == tsn));
return(thread);
}
/*----------------------------------------------------------------------------*/
/* Accessing the Queue of Threads */
/*----------------------------------------------------------------------------*/
/* returns the number of threads in the queue */
long ThreadCount(void)
{
ThreadErrorSet(THREAD_ERROR_NONE);
return(gThread.queue.nelem);
}
/* returns the main thread, or THREAD_NONE if there are no
threads */
ThreadType ThreadMain(void)
{
ThreadErrorSet(THREAD_ERROR_NONE);
return(ThreadSN(gThread.main));
}
/* returns the currently active thread, or THREAD_NONE if
there are no threads */
ThreadType ThreadActive(void)
{
ThreadErrorSet(THREAD_ERROR_NONE);
return(ThreadSN(gThread.active));
}
/* returns the first thread in the queue of threads, or
THREAD_NONE if there are no threads */
ThreadType ThreadFirst(void)
{
ThreadErrorSet(THREAD_ERROR_NONE);
return(ThreadSN(gThread.queue.head));
}
/* returns the next thread in the circular queue of threads,
or THREAD_NONE if there are no threads */
ThreadType ThreadNext(ThreadType tsn)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
return(thread ? ThreadSN(thread->next) : THREAD_NONE);
}
/* returns true if the specified thread exists */
Boolean ThreadExists(ThreadType tsn)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
if (ThreadError() == THREAD_ERROR_NOT_FOUND)
ThreadErrorSet(THREAD_ERROR_NONE);
return(thread != NULL);
}
/*----------------------------------------------------------------------------*/
/* Floating Point Unit */
/*----------------------------------------------------------------------------*/
/* true if there's a floating point unit */
static Boolean ThreadHasFPU(void)
{
long response;
Boolean result;
result = false;
if (Gestalt(gestaltFPUType, &response) == noErr)
result = (response != gestaltNoFPU);
return(result);
}
/*----------------------------------------------------------------------------*/
/* Application Defined Data */
/*----------------------------------------------------------------------------*/
/* ThreadData returns the data field of the thread. The application can
use the thread's data field for its own purposes. */
void *ThreadData(ThreadType tsn)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
return(thread ? thread->appl.data : NULL);
}
/* ThreadDataSet sets the data field of the thread. The application can
use the thread's data field for its own purposes. */
void ThreadDataSet(ThreadType tsn, void *data)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
if (thread)
thread->appl.data = data;
ensure(! thread || ThreadData(tsn) == data);
}
/*----------------------------------------------------------------------------*/
/* Application Defined Scheduler Function */
/*----------------------------------------------------------------------------*/
/* returns the function used to schedule threads
or NULL if the default function is being used */
ThreadProcScheduleType ThreadProcSchedule(void)
{
ThreadErrorSet(THREAD_ERROR_NONE);
return(gThread.schedule);
}
/* sets the function used to schedule threads; if you
specify NULL, then the default function will be used. */
void ThreadProcScheduleSet(ThreadProcScheduleType schedule)
{
ThreadErrorSet(THREAD_ERROR_NONE);
gThread.schedule = schedule;
ensure(ThreadProcSchedule() == schedule);
}
/*----------------------------------------------------------------------------*/
/* Application Defined Memory Allocation Functions */
/*----------------------------------------------------------------------------*/
/* returns the function used to allocate memory
or NULL if the default function is being used */
ThreadProcAllocateType ThreadProcAllocate(void)
{
ThreadErrorSet(THREAD_ERROR_NONE);
return(gThread.allocate);
}
/* sets the function used to allocate memory; if you
specify NULL, then the default function will be used. */
void ThreadProcAllocateSet(ThreadProcAllocateType allocate)
{
ThreadErrorSet(THREAD_ERROR_NONE);
gThread.allocate = allocate;
ensure(ThreadProcAllocate() == allocate);
}
/* returns the function used to dispose of memory,
or NULL if the default function is being used */
ThreadProcDisposeType ThreadProcDispose(void)
{
ThreadErrorSet(THREAD_ERROR_NONE);
return(gThread.dispose);
}
/* sets the function used to dispose of memory; if you
specify NULL, then the default function will be used. */
void ThreadProcDisposeSet(ThreadProcDisposeType dispose)
{
gThread.dispose = dispose;
ThreadErrorSet(THREAD_ERROR_NONE);
ensure(ThreadProcDispose() == dispose);
}
/*----------------------------------------------------------------------------*/
/* Application Defined Protocol Functions */
/*----------------------------------------------------------------------------*/
/* returns the begin function for the thread */
ThreadProcBeginEndType ThreadProcBegin(ThreadType tsn)
{
ThreadPtr thread;
require(tsn != ThreadMain());
thread = ThreadFromSN(tsn);
return(thread ? thread->appl.begin : NULL);
}
/* sets the begin function for the thread */
void ThreadProcBeginSet(ThreadType tsn, ThreadProcBeginEndType begin)
{
ThreadPtr thread;
require(tsn != ThreadMain());
thread = ThreadFromSN(tsn);
if (thread)
thread->appl.begin = begin;
ensure(! thread || ThreadProcBegin(tsn) == begin);
}
/* returns the end function for the thread */
ThreadProcBeginEndType ThreadProcEnd(ThreadType tsn)
{
ThreadPtr thread;
require(tsn != ThreadMain());
thread = ThreadFromSN(tsn);
return(thread ? thread->appl.end : NULL);
}
/* sets the end function for the thread */
void ThreadProcEndSet(ThreadType tsn, ThreadProcBeginEndType end)
{
ThreadPtr thread;
require(tsn != ThreadMain());
thread = ThreadFromSN(tsn);
if (thread)
thread->appl.end = end;
ensure(! thread || ThreadProcEnd(tsn) == end);
}
/* returns the resume function for the thread */
ThreadProcType ThreadProcResume(ThreadType tsn)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
return(thread ? thread->appl.resume : NULL);
}
/* sets the resume function for the thread */
void ThreadProcResumeSet(ThreadType tsn, ThreadProcType resume)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
if (thread)
thread->appl.resume = resume;
ensure(! thread || ThreadProcResume(tsn) == resume);
}
/* returns the suspend function for the thread */
ThreadProcType ThreadProcSuspend(ThreadType tsn)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
return(thread ? thread->appl.suspend : NULL);
}
/* sets the suspend function for the thread */
void ThreadProcSuspendSet(ThreadType tsn, ThreadProcType suspend)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
if (thread)
thread->appl.suspend = suspend;
ensure(! thread || ThreadProcSuspend(tsn) == suspend);
}
/* returns the entry function for the thread */
ThreadProcType ThreadProcEntry(ThreadType tsn)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
return(thread ? thread->appl.entry : NULL);
}
/* sets the entry function for the thread */
void ThreadProcEntrySet(ThreadType tsn, ThreadProcType entry)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
if (thread)
thread->appl.entry = entry;
ensure(! thread || ThreadProcEntry(tsn) == entry);
}
/*----------------------------------------------------------------------------*/
/* Information About the Stack */
/*----------------------------------------------------------------------------*/
/* returns the recommended minimum stack size for a thread */
ThreadSizeType ThreadStackMinimum(void)
{
ThreadErrorSet(THREAD_ERROR_NONE);
return(LMGetMinStack());
}
/* returns the default stack size for a thread */
ThreadSizeType ThreadStackDefault(void)
{
ThreadErrorSet(THREAD_ERROR_NONE);
return(LMGetDefltStack());
}
/* returns the total size of the thread's stack */
ThreadSizeType ThreadStackSize(ThreadType tsn)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
return(thread ? thread->stack.size : 0);
}
/* returns the amount of space remaining in the thread's stack */
ThreadSizeType ThreadStackSpace(ThreadType tsn)
{
ThreadSizeType result;
ThreadStackPtr sp;
ThreadPtr thread;
result = 0;
thread = ThreadFromSN(tsn);
if (thread) {
sp = ThreadStackPointer(thread);
check(sp >= thread->stack.limit);
result = sp - thread->stack.limit;
}
ensure(result >= 0);
return(result);
}
/*----------------------------------------------------------------------------*/
/* Scheduling */
/*----------------------------------------------------------------------------*/
/* The three functions ThreadSchedule, ThreadActivate, and ThreadYield
handle the scheduling and context switching of threads. These functions
will be executed the most often of any of the functions in this file, and
therefore will have the greatest impact on the efficiency of Thread
Library. If you find Thread Library's context switches too slow, you
could improve the efficiency of these functions. */
/* Returns true if the thread is enabled. An enabled thread is eligible for
scheduling, while a disabled thread is not scheduled. */
Boolean ThreadEnabled(ThreadType tsn)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
return(thread ? thread->enabled : false);
}
/* enables or disables the thread */
void ThreadEnabledSet(ThreadType tsn, Boolean enabled)
{
ThreadPtr thread;
require(tsn != ThreadMain());
require(enabled == true || enabled == false);
thread = ThreadFromSN(tsn);
if (thread)
thread->enabled = enabled;
ensure(thread ? ThreadEnabled(tsn) == enabled :
ThreadEnabled(tsn) == false);
}
/* returns the time at which the thread will become active */
ThreadTicksType ThreadWakeTime(ThreadType tsn)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
return(thread ? thread->wake : 0);
}
/* sets the time at which the thread will become active */
void ThreadWakeTimeSet(ThreadType tsn, ThreadTicksType wake)
{
ThreadPtr thread;
require(0 <= wake && wake <= THREAD_TICKS_MAX);
thread = ThreadFromSN(tsn);
if (thread)
thread->wake = wake;
ensure(ThreadWakeTime(tsn) == wake);
}
/* this is identical to ThreadSleepIntervalSet, but for greater efficiency it
takes a pointer to a thread. */
static void ThreadSleepIntervalSetPtr(ThreadPtr thread, ThreadTicksType sleep, ThreadTicksType ticks)
{
require(ThreadValid(thread));
require(0 <= sleep && sleep <= THREAD_TICKS_MAX);
/* Set the thread's wakeup time, being careful with overflow since the
sleep parameter could be THREAD_TICKS_MAX. */
if (sleep > THREAD_TICKS_MAX - ticks)
thread->wake = THREAD_TICKS_MAX;
else
thread->wake = ticks + sleep;
}
/* sets the amount of time that the specified thread will remain inactive */
void ThreadSleepIntervalSet(ThreadType tsn, ThreadTicksType sleep)
{
ThreadPtr thread;
thread = ThreadFromSN(tsn);
if (thread)
ThreadSleepIntervalSetPtr(thread, sleep, LMGetTicks());
}
/* ThreadSleepSet is the same as ThreadSleepIntervalSet, but is included
for compatability with prior versions of Thread Library. */
void ThreadSleepSet(ThreadType tsn, ThreadTicksType sleep)
{
ThreadSleepIntervalSet(tsn, sleep);
}
/* EventPending returns true if an event is pending. Most events are posted
to the event queue, so we can determine if an event is pending simply
by examining the head of the event queue. Update events are not posted
to the event queue, so we need to call CheckUpdate to detect them. Since
CheckUpdate needs to traverse the window list and could therefore be
relatively slow, we make the interval between calls to CheckUpdate as
large as possible without severely degrading response time. Once every
1/4 second seems like a good interval to call CheckUpdate.
Activate and deactivate events are also not posted to the event queue.
Instead, the system just sets the low-memory globals CurActivate and
CurDeactive. However, these globals are not cleared once the event is
handled. Therefore, I haven't figured out a reliable method to test
for activate and deactivate events, so those events are just ignored.
Checking for events using this method, rather than using EventAvail, is
preferrable for two reasons. First, and most importantly, EventAvail
can let the application be switched out, which could result in some
confusing information being displayed about the application's memory
partition in the Finder's About window and possibly other
incompatabilities. Second, EventAvail is a fairly slow trap, and we
want context switches to be as fast as possible. */
static Boolean EventPending(ThreadTicksType ticks)
{
#define UPDATE_PENDING_INTERVAL (15)
#define EVENT_PENDING_INTERVAL (1)
static ThreadTicksType nextUpdate;
static ThreadTicksType nextEvent;
EventRecord event;
Boolean pending;
pending = false;
if (ticks >= nextEvent) {
pending = (LMGetEventQueue()->qHead != NULL);
nextEvent = ticks + EVENT_PENDING_INTERVAL;
if (! pending && ticks >= nextUpdate) {
pending = CheckUpdate(&event);
nextUpdate = ticks + UPDATE_PENDING_INTERVAL;
}
}
return(pending);
}
/* ThreadSchedulePtr is identical to ThreadSchedule, except it returns a
pointer to a thread instead of a thread serial number. This makes context
switches triggered via ThreadYield more efficient, since we already have
direct access to the relevant thread pointers and so do not need to waste
time converting to and from thread serial numbers. */
static ThreadPtr ThreadSchedulePtr(register ThreadTicksType ticks)
{
register ThreadPtr active; /* active thread */
register ThreadPtr newthread; /* thread to switch to */
ThreadType suggestedSN; /* serial number of thread suggested by application */
ThreadPtr suggestedPtr; /* pointer to thread suggested by application */
require(ThreadValid(gThread.active));
if (EventPending(ticks)) {
/* an event is pending, so return main thread */
newthread = gThread.main;
}
else {
/* round-robbin search for a thread that needs to be woken up */
active = gThread.active;
newthread = active->next;
check(ThreadValid(newthread));
while (newthread != active &&
(newthread->wake > ticks ||
! newthread->enabled))
{
newthread = newthread->next;
check(ThreadValid(newthread));
}
if (newthread == active &&
(newthread->wake > ticks ||
! newthread->enabled))
{
/* no thread needs to be woken up, so return main thread */
newthread = gThread.main;
}
}
if (gThread.schedule) {
/* use application-defined callback */
suggestedSN = gThread.schedule(newthread->sn);
if (suggestedSN != THREAD_NONE && suggestedSN != newthread->sn) {
suggestedPtr = ThreadFromSN(suggestedSN);
if (suggestedPtr && suggestedPtr->enabled)
newthread = suggestedPtr;
}
}
ensure(ThreadValid(newthread));
ensure(newthread->enabled);
return(newthread);
}
/* returns the next thread to activate */
ThreadType ThreadSchedule(void)
{
ThreadErrorSet(THREAD_ERROR_NONE);
return(ThreadSN(ThreadSchedulePtr(LMGetTicks())));
}
/* ThreadResume restores the context of the active thread. It is called
before a thread resumes execution, but after the thread's stack has
been restored. */
static void ThreadResume(register ThreadPtr thread)
{
register ThreadQueuePtr queue; /* for faster access to queue */
require(thread == gThread.active && ThreadValid(thread));
/* for greater efficiency, put things into registers */
queue = &gThread.queue;
/* Set the low-memory globals for the thread. We bypass any
traps or glue (like SetApplLimit) to keep the OS from
preventing us from changing these globals and to keep
context switches fast. */
LMSetHeapEnd(thread->lm.heapEnd);
LMSetApplLimit(thread->lm.applLimit);
LMSetHiHeapMark(thread->lm.hiHeapMark);
/* Move the thread to the tail of the queue so that it is rescheduled
to run after all other threads (though scheduling also depends on
wake times and priority). Functionally, the move-to-tail is always
equivalent to a dequeue followed by an enqueue, but it's optimized
for the most common case where the thread is already at the front
of the queue. Since the queue is circular, we can just advance the
head and tail pointers to achieve the same result. We do the
optimization in-line since the function call overhead could be
significant over many context switches. */
if (thread == queue->head) {
queue->head = thread->next;
queue->tail = thread;
}
else if (thread != queue->tail) {
ThreadDequeue(queue, thread);
ThreadEnqueue(queue, thread);
}
/* call the application's resume function -- this must be called prior to
disposing of a zombie thread so that the application can switch its
context to the new thread before destroying the data in the old thread */
if (thread->appl.resume)
thread->appl.resume(thread->appl.data);
/* dispose of the memory allocated for a previously terminated
thread (see ThreadEndPtr) */
if (gThread.zombie) {
ThreadDispose(gThread.zombie);
gThread.zombie = NULL;
}
}
/* ThreadSuspend saves the context of the active thread. It is called
when a thread is deactivated, but before the next thread's stack
or environment have been restored. */
static void ThreadSuspend(register ThreadPtr thread)
{
require(thread == gThread.active && ThreadValid(thread));
/* save low-memory globals */
thread->lm.heapEnd = LMGetHeapEnd();
thread->lm.applLimit = LMGetApplLimit();
thread->lm.hiHeapMark = LMGetHiHeapMark();
/* call the application's suspend function */
if (thread->appl.suspend)
thread->appl.suspend(thread->appl.data);
}
/* ThreadActivatePtr is identical to ThreadActivate, except it takes a pointer
to a thread instead of a thread serial number. This makes context switches
triggered via ThreadYield more efficient, since we already have direct
access to the relevant thread pointers and so don't need to waste time
converting to and from thread serial numbers.
The context switch is accomplished by saving the CPU context with
ThreadRegistersSave and then calling ThreadRegistersRestore, which
jumps to the environment saved with ThreadRegistersSave when the
thread being activated was last suspended. We don't have to use any
assembly language glue since ThreadRegistersSave saved the value of
the stack pointer, which at the time of the call to ThreadRegistersSave
pointed somewhere in the thread's stack. The ThreadRegistersRestore
function will restore the value of the stack pointer and will jump
to the statement from which to resume the thread. ThreadRegistersRestore
also restores all other registers. */
static void ThreadActivatePtr(register ThreadPtr oldthread, register ThreadPtr newthread)
{
require(! oldthread || (oldthread == gThread.active && ThreadValid(gThread.active)));
require(ThreadValid(newthread));
if (oldthread != newthread) {
/* save thread's registers */
if (oldthread && oldthread->fpu)
ThreadRegistersFPSave(oldthread->registers.fp);
if (oldthread && ThreadRegistersGPSave(oldthread->registers.gp)) {
/* The thread is being activated, so restore the thread's context.
We need to get the thread from the global variable gThread.active,
since local variables are not yet defined. */
ThreadResume(gThread.active);
}
else {
/* suspend the current thread */
if (oldthread)
ThreadSuspend(oldthread);
/* Jump to the specified thread. This suspends the current thread
and returns at the ThreadRegistersGPSave statement above (unless this
is the first time the thread is being executed, in which case it
returns at the ThreadRegistersGPSave statement in ThreadBegin). The
contents of the stack will be correct as soon as
ThreadRegistersGPRestore has completed, but ThreadResume must be called
before the thread can start executing again. */
gThread.active = newthread;
if (newthread->fpu)
ThreadRegistersFPRestore(newthread->registers.fp);
ThreadRegistersGPRestore(newthread->registers.gp);
check(false); /* doesn't return */
}
}
/* can't evaluate this postcondition--no local variables */
/* ensure(gThread.active == thread); */
}
/* deactivates the currently active thread and makes the specified thread the
active thread */
void ThreadActivate(ThreadType tsn)
{
ThreadPtr thread;
require(ThreadValid(gThread.active));
thread = ThreadFromSN(tsn);
if (thread)
ThreadActivatePtr(gThread.active, thread);
/* ensure(! thread || ThreadActive() == tsn); */ /* can't evaluate this postcondition */
}
/* activates the next scheduled thread */
void ThreadYield(ThreadTicksType sleep)
{
ThreadTicksType ticks;
require(ThreadValid(gThread.active));
/* We set the active thread's wakeup time before we run the scheduler and
before the active thread is suspended. We need to set the wakeup time
so that the thread scheduler will work correctly. Also, the thread
scheduler could take several ticks to complete, while we want to
calculate the wakeup time to be as close as possible to the wakeup
time specified by the sleep parameter */
ticks = LMGetTicks();
ThreadErrorSet(THREAD_ERROR_NONE);
ThreadSleepIntervalSetPtr(gThread.active, sleep, ticks);
ThreadActivatePtr(gThread.active, ThreadSchedulePtr(ticks));
}
/* returns the maximum time till the next call to ThreadYield */
ThreadTicksType ThreadYieldInterval(void)
{
ThreadPtr thread; /* for iterating through queue of threads */
ThreadPtr active; /* currently active thread */
ThreadTicksType ticks; /* current tick count */
ThreadTicksType interval; /* interval till next call to ThreadYield */
require(ThreadValid(gThread.active));
ThreadErrorSet(THREAD_ERROR_NONE);
ticks = LMGetTicks();
active = gThread.active;
thread = active->next;
interval = THREAD_TICKS_MAX;
check(ThreadValid(thread));
while (thread != active && interval) {
if (thread->enabled) {
if (thread->wake <= ticks)
interval = 0;
else if (thread->wake - ticks < interval)
interval = thread->wake - ticks;
}
thread = thread->next;
check(ThreadValid(thread));
}
ensure(0 <= interval && interval <= THREAD_TICKS_MAX);
return(interval);
}
/*----------------------------------------------------------------------------*/
/* Thread Creation and Destruction */
/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
/* Destroying Threads */
/*----------------------------------------------------------------------------*/
/* ThreadDispose disposes of the memory allocated for the thread. The thread
must not be active when this is called. ThreadDispose can be called either
to dispose of a partially created thread (whose creation failed), or to
dispose of a thread that has terminated. */
static void ThreadDispose(ThreadPtr thread)
{
require(! thread || thread != gThread.active);
if (thread) {
if (thread->stack.limit && ! thread->main)
PtrEnd(thread->stack.limit, thread->stack.size, THREAD_TYPE_STACK);
PtrEnd(thread, sizeof(ThreadStructure), THREAD_TYPE_THREAD);
}
}
/* ThreadEndPtr is identical in function to ThreadEnd (see below), but it
takes a pointer to a thread rather than a thread serial number. */
static void ThreadEndPtr(ThreadPtr thread)
{
require(ThreadValid(thread));
if (thread) {
/* suspend the thread if it's the active thread */
if (thread == gThread.active)
ThreadSuspend(thread);
/* call the application's end function */
if (thread->appl.end)
thread->appl.end(thread->sn, thread->appl.data);
/* remove the thread from the queue so that it is no longer
accessible and will not be scheduled for execution */
ThreadDequeue(&gThread.queue, thread);
if (thread == gThread.main) {
/* We're disposing of the main thread, so this is a good time
to do any final cleanup of Thread Library. */
/* clear globals */
check(gThread.active == thread);
check(gThread.queue.nelem == 0);
gThread.main = gThread.active = NULL;
}
else if (thread == gThread.active) {
/* We're disposing of the active thread, but we can't dispose of
the thread's stack since we're still using that stack. So
we delay disposal of the thread until the next thread is
activated; the thread will be disposed of in ThreadResume,
which is executed when the next scheduled thread is activated. */
check(! gThread.zombie);
gThread.zombie = thread;
/* activate the main thread */
ThreadActivatePtr(NULL, gThread.main);
check(false); /* doesn't return */
}
else {
/* dispose of the memory allocated for the thread */
ThreadDispose(thread);
}
}
}
/* ThreadEnd removes the thread from the queue and disposes of the memory
allocated for the thread. If the thread is the active thread then the
next scheduled thread is activated. All threads (other than the main
thread) must be disposed of before the main thread can be disposed of. */
void ThreadEnd(ThreadType tsn)
{
ThreadPtr thread;
require(tsn == THREAD_NONE ||
(ThreadCount() > 1 ? tsn != ThreadMain() : tsn == ThreadMain()));
if (tsn != THREAD_NONE) {
thread = ThreadFromSN(tsn);
if (thread)
ThreadEndPtr(thread);
}
/* ensure(! ThreadValid(ThreadFromSN(tsn))); */ /* executing this would set the error code */
}
/* ThreadEndAnyNext returns the next thread to be disposed of. Any thread
other than the main thread may be returned. */
static ThreadType ThreadEndAnyNext(void)
{
ThreadType tsn;
tsn = THREAD_NONE;
if (ThreadCount() > 1) {
tsn = ThreadFirst();
if (tsn == ThreadMain()) {
tsn = ThreadNext(tsn);
check(tsn != ThreadFirst());
}
check(tsn != ThreadMain());
}
ensure( ( ThreadCount() <= 1 && tsn == THREAD_NONE) ||
( ThreadCount() > 1 &&
ThreadValid(ThreadFromSN(tsn)) &&
tsn != ThreadMain()));
return(tsn);
}
/* ThreadEndAny disposes of any one thread other than the main thread. */
static void ThreadEndAny(void)
{
ThreadEnd(ThreadEndAnyNext());
/* ensure((old ThreadCount()) == 0 || ThreadCount() == (old ThreadCount()) - 1); */
}
/* ThreadEndAllExceptMain disposes of all threads other than the main thread. */
static void ThreadEndAllExceptMain(void)
{
while (ThreadCount() > 1) {
ThreadEndAny();
/* check(ThreadCount() == (old ThreadCount()) - 1); */
}
ensure(ThreadCount() <= 1);
}
/* ThreadEndAll disposes of all threads, including the main thread.
ThreadEndAll is useful when your application is terminating and
you want to dispose of any threads that may still exist.
ThreadEndAll can be called only from within the main thread. */
void ThreadEndAll(void)
{
require(ThreadActive() == ThreadMain());
ThreadEndAllExceptMain();
ThreadEnd(ThreadMain());
ensure(ThreadCount() == 0);
}
/*----------------------------------------------------------------------------*/
/* Creating Threads */
/*----------------------------------------------------------------------------*/
/* ThreadBeginMain creates the main application thread and returns the main
thread's serial number. */
ThreadType ThreadBeginMain(ThreadProcType suspend, ThreadProcType resume,
void *data)
{
ThreadPtr thread; /* the new thread */
Boolean success; /* true if thread was created successfully */
require(! gThread.main);
/* This is always the first routine called for Thread Library,
so this is a good place to do any one-time initializations
of the library. Conversely, when the main thread is disposed
of is a good time to do any final cleanup of the library. */
thread = NULL;
success = false;
ThreadErrorSet(THREAD_ERROR_NONE);
/* allocate thread structure */
thread = PtrBeginClear(sizeof(ThreadStructure), THREAD_TYPE_THREAD);
if (! thread && ! ThreadError())
ThreadErrorSet(memFullErr);
if (thread) {
/* initialize thread structure */
thread->fpu = ThreadHasFPU();
thread->enabled = true;
thread->main = true;
thread->appl.suspend = suspend;
thread->appl.resume = resume;
thread->appl.data = data;
thread->sn = ++gThread.lastsn;
thread->stack.limit = LMGetApplLimit();
thread->stack.top = LMGetCurStackBase();
thread->stack.size = thread->stack.top - thread->stack.limit;
/* save values of low-memory globals */
thread->lm.heapEnd = LMGetHeapEnd();
thread->lm.applLimit = LMGetApplLimit();
thread->lm.hiHeapMark = LMGetHiHeapMark();
/* now that the thread is ready to use, append it to the queue of
threads so that it can be scheduled for execution */
ThreadEnqueue(&gThread.queue, thread);
/* make this thread the active and main thread */
gThread.active = thread;
gThread.main = thread;
/* we've successfully created the main thread */
success = true;
}
/* cleanup if failed */
if (! success && thread) {
ThreadDispose(thread);
thread = NULL;
}
ensure(thread ? ThreadValid(thread) && ! ThreadError() : ThreadError());
ensure(thread == gThread.main);
ensure(gThread.active == gThread.main);
return(ThreadSN(thread));
}
/* called to start executing a new thread; must be a separate
function to work on powerpc */
static void ThreadStart(void)
{
/* call the thread's begin function */
if (gThread.active->appl.begin)
gThread.active->appl.begin(gThread.active->sn, gThread.active->appl.data);
/* set up the thread's context */
ThreadResume(gThread.active);
/* call the thread's entry point */
gThread.active->appl.entry(gThread.active->appl.data);
/* dispose of the thread and switch to the next scheduled thread */
ThreadEndPtr(gThread.active);
ensure(false); /* never returns */
}
/* ThreadBegin creates a new thread and returns the thread's serial number.
You must create the main thread with ThreadBeginMain before you can call
ThreadBegin. */
ThreadType ThreadBegin(ThreadProcType entry,
ThreadProcType suspend,
ThreadProcType resume,
void *data, ThreadSizeType stack_size)
{
ThreadPtr thread; /* new thread */
Boolean success; /* true if thread was created successfully */
require(ThreadValid(gThread.main));
require(entry != NULL);
require(0 <= stack_size);
/* clear results */
thread = NULL;
success = false;
ThreadErrorSet(THREAD_ERROR_NONE);
/* allocate thread structure */
thread = PtrBeginClear(sizeof(ThreadStructure), THREAD_TYPE_THREAD);
if (thread) {
/* The main thread uses the application's regular stack, while
nonrelocatable blocks are allocated to contain the stacks of
all other threads. The thread's stack size is aligned to
a double-word size (8 byte boundary) for compatability
with the PowerPC architecture. */
if (! stack_size)
stack_size = ThreadStackDefault();
stack_size += stack_size % 8;
thread->stack.limit = PtrBegin(stack_size, THREAD_TYPE_STACK);
if (thread->stack.limit) {
/* initialize thread structure */
thread->fpu = ThreadHasFPU();
thread->enabled = true;
thread->sn = ++gThread.lastsn;
thread->appl.entry = entry;
thread->appl.suspend = suspend;
thread->appl.resume = resume;
thread->appl.data = data;
thread->stack.size = stack_size;
thread->stack.top = thread->stack.limit + stack_size;
/* Since all threads other than the main thread use stacks
allocated in the application's heap, we need to disable the
stack sniffer VBL task by setting the low-memory global
variable StkLowPt to 0. Otherwise, the stack sniffer would
generate system error #28. */
LMSetStkLowPt(NULL);
/* Certain low-memory globals divide the stack and heap.
We change these globals when a thread other than the
main thread is activated so that certain OS traps will
work correctly. */
thread->lm.heapEnd = thread->stack.limit;
thread->lm.applLimit = thread->stack.limit;
thread->lm.hiHeapMark = thread->stack.limit;
/* Set up the new thread's registers so that we'll jump
here when the thread is first activated. */
if (thread->fpu)
ThreadRegistersFPSave(thread->registers.fp);
if (ThreadRegistersGPSave(thread->registers.gp)) {
/* We're now executing the *new* thread (I know, it doesn't
look like it, but it's all due to the magic [hell?] of
non-local gotos and global variables). At this point,
the stack is empty, so we can't access any local variables.
All subsequent executions of the thread will go through the
ThreadRegistersSave call in ThreadActivate. */
ThreadStart();
check(false); /* never returns */
}
/* Threads other than the main thread have their own private stack.
To be able to switch stacks, the first time the registers are saved
we need to set the stack pointer register that we saved in the
general purpose register array to the top of the thread's private
stack. We do this by knowing the index of the stack pointer in the
array of general purpose registers. For the M68K, we also need to
set the frame pointer to NULL. For the powerpc, we need to leave
space from the top of the stack for the linkage area. */
#ifdef powerc
thread->registers.gp[REGISTER_SP] = (long) (thread->stack.top - 12);
#else /* powerc */
thread->registers.gp[REGISTER_SP] = (long) thread->stack.top;
thread->registers.gp[REGISTER_FP] = 0;
#endif /* powerc */
/* now that the thread is ready to use, append it to the queue of
threads so that it can be scheduled for execution */
ThreadEnqueue(&gThread.queue, thread);
/* We've now successfully created a new thread and set things up so
that the first time the thread is invoked we'll call the thread's
entry point. We let the application call ThreadYield in its own
time to switch contexts. In other words, the new thread doesn't
start executing until it is scheduled to start or it is
specifically activated. */
success = true;
}
}
/* cleanup if failed */
if (! success && thread) {
ThreadDispose(thread);
thread = NULL;
}
ensure(thread ? ThreadValid(thread) && ! ThreadError() : ThreadError());
return(ThreadSN(thread));
}