Next: Handling C++
Up: Implementation
Previous: Select Implementation
The C-with-Ease
implementation can operate with a wide variety of levels of
system support for process scheduling, from fully preemptive processes
to coroutines requiring explicit context switches.
Each completely separate process address space we call a cell.
Each cell may contain 0 or more C-with-Ease
processes. Each process within a
cell we call a thread. Systems where threads may run simultaneously
(on multiple physical processes) or where context switches may occur at any
stage of execution are preemptive. Where context switches must be
explicitly requested by the thread, they are non-preemptive.
Messages are logically handled by the cell, not by the individual
threads that they are directed towards.
A thread is reused when the Ease
process that is using it finishes.
This improves efficiency, as it reduces the frequency of thread creation
and descrition. It also improves portability, as some threads libraries
do not have facilities for removing threads.
The library associates an integer identifier and
a structure with each thread. The thread structure contains the following:
- System thread identifier
- This is used when interacting with the host interface
(section 5.7).
- Integer Identifier
- This is used by other parts of the library when referring to
threads
- Created contexts list.
- When an Ease
process dies, its contexts die. This list keeps
track of the contexts a processes creates to facilitate this.
- Dead flag.
- When a Ease
process accesses a dead context then it
terminates. This flag tells a thread that it must terminate.
- Parent information.
- If this process is part of a cooperation the
parent must be informed when this process dies.
- Child count.
- This is the number of cooperating children that this process is
still waiting for.
- Data handling information.
- When a process is waiting for data from a @get,
@read, @resource or @select, it leaves sufficient
information here for the cell (i.e. another thread) to handle the
received data when it arrives. This includes a pointer to a buffer
which is expecting the data, the size of the buffer, conversion
functions (for C++ marshalling, section 5.6), etc.
In a non-preemptive system, the C-with-Ease
library controls all context
switches. An attempt is made to keep the number of context switches to a
minimum by only switching when the current thread can no longer run.
Messages (in a multi-cell system) are received and handled by the cell
(in the guise of the currently executing thread) on behalf of all
threads (and contexts).
A list of runnable threads is kept, and when a thread becomes
unblocked it is added to this list. If the current thread blocks, a
thread is removed from the runnable list and switched to. If there are
no runnable threads then the cell blocks and waits for a message. If the
host consists of a single cell and all threads in that cell block then
the Ease
programmer has produced deadlock. The library reports this and
aborts the program.
In a preemptive system concurrent access to shared resources (such as
contexts and process structures) must be prevented. A mutex and a
semaphore are added to the process structure to achieve this. (A
context only has a mutex).
The mutex is for short term locking of the process. It prevents other
threads from locking the process (and thereby accessing it) while the
lock is held. A mutex may be locked multiple times by the same process
and must be unlocked a corresponding number of times.
The semaphore is used for suspending and resuming the thread. The thread
waits on the semaphore and another thread signals it to
allow the waiting thread to resume. It is possible, and legal, for the
signal to precede the wait.
A list of running (and runnable) threads is not kept, the host or the
host interface is expected to take care of it. Any thread which is not
waiting on a semaphore or waiting for a lock on a mutex is
eligible to run.
The scheduler provides the following services to the Ease
library:
- int getThreadId()
- Returns the integer identifier of the current thread.
- runProc(thread)
- Signal a particular thread to continue.
- yieldProc()
- Causes the current thread to wait for a signal.
- receive(Data handling information)
- Waits for data to be supplied to the thread (into the supplied
location).
- localGive(thread, data)
- Supplies data to a thread that is waiting for it and signals
the thread.
- setSelectInfo(SelectInfo)
- Locks the current thread and supplies information about
the current @select information.
- waitSelect()
- Unlocks the thread and waits for the select to complete.
- SelectInfo selectInfo(thread)
- Locks the specified thread and returns its current select
information.
- unlockThread(thread)
- Unlocks the specified thread.
- yieldCycle()
- Yield to another thread. The current thread goes back into the
list of runnable threads. This is provided for Ease
programmers
who wish to modify the scheduling, not for the
Ease
library.
- setDeadFlag(thread)
- Set the dead flag for a specified thread. If the thread is
waiting then it is signalled. It is the thread's responsibility to
detect that it has been killed and abort its Ease
process (but
not the entire program).
- addContext(context)
- Add a context to the current thread's context list.
- handleMessage(message)
- Handle a message. See section 5.3.5 for more.
- initProcess(systemThreadId, main, arguments)
- Initialize the scheduler. The system thread identifier of the
current (and at the time only) thread is supplied. If this is the
main process then main will be true and its arguments
will be supplied. initProcess will call eMain for the
main process and exit when eMain exits. Otherwise, it will
wait for a Ease
process creation messages and call these
functions, never exiting until the entire program exits.
Next: Handling C++
Up: Implementation
Previous: Select Implementation
Tim MacKenzie <tym@cs.monash.edu.au>
Mon Apr 1 00:27:29 EST 1996