[comp.lang.c++] Awesime tasking package info?

cmascott@world.std.com (Carl Mascott) (06/08/91)

Is anyone familiar with the Awesime tasking package
available from foobar.colorado.edu?  Could someone
describe it?  Thanks.
-- 
Carl Mascott
cmascott@world.std.com
uunet!world!cmascott

grunwald@foobar.colorado.edu (Dirk Grunwald) (06/13/91)

>>>>> On 8 Jun 91 15:46:52 GMT, cmascott@world.std.com (Carl Mascott) said:

CM> Is anyone familiar with the Awesime tasking package
CM> available from foobar.colorado.edu?  Could someone
CM> describe it?  Thanks.
CM> -- 
--

Hi - I hacked awesime, or most of it at least.

Basically, awesime attempts to provide:
	+ thread model
		- derived 'simulation'  model
	+ synchronization model
	+ some extra geegaws

A machine context (registers & stack) is represented by a
HardwareContext; each architecture has a unique HardwareContext,
although they're all largely similar.

A hardware context starts execution at an address and passes a single
argument. Threads are an abstraction with hardware contexts & extra
information; they have a virtual function called 'main'. They
initialize their HardwareContext to start execution at a launch point
that then calls the virtual main; the thread point is the single
argument.

Thus, user threads are subclasses of the Thread class; this differs
from AT&T C++ & is more OO in my mind -- it's also a hell of a lot
easier to pass a single argument to an arbitrary function rather than
an arbitrary number of arguments. This is why HardwareContexts are
implemented for the Mips, ns32K, m68K, SPARC, 88k and i386.

Threads are managed by a subclass of CpuMultiplexor. A CpuMultiplexor
contains a 'system' HardwareContext for safe synchronization
implementations. A SingleCpuMux multiplexes threads (stored in e.g., a
fifo or priority queue) using a single UNIX process & coroutine
switches. A MultiCpuMux does the same thing with multiple UNIX
processes & shared memory (at least on the encore -- I'm in the middle
of making it work on e.g., Solbourne machines).

Twe CpuMux classes simple execute available threads until there are no
more in the readyq. A subclass, SimulationMultiplexor (& SingleSimMux
& MultiSimMux) also provide a time-ordered event queue, allowing you
to do process oriented simulation.

Synchronization is done at two levels: spinning and relenquishing.

SpinLock is a simple spinlock (on sequent, encore, SPARC - mips has no
interlock insns's). SpinBarrier, SpinFetchAndOp and SpinEvent are
variants that all use SpinLocks. The relenquishing mechanisms are
Semaphore, Barrier, Gate and Event -- on multiprocessors, these
typically spin for a little while & then e.g. enqueue the current task
and reschedule things.

Additional things are e.g., Fifo's (locked w/a SpinLock on add/remove)
and subclasses LowerBoundedFifo (can't take out too much) and
BoundedFifo (or put in too much either).

There's a Random (provides distributions given random bits) and RNG
(generates random bits) classes that have also been given to libg++.
There's also statistics gathering code.

Now, what there isn't is a heck of a lot of doucmentation. There are
example programs. A student recently added a non-blocking I/O library,
and I need to update the master source tree to integrate his code.
Numerous minor things have also changed, slightly speeding things up
in most cases & providing some additional functionality.

Right now, it depends on 'g++' because I use the clever Gnu 'asm'
instructions to do context switching with no .s files -- however, that
will have to change unless g++ 2.0 gets more stable. Making it use
AT&T C++ isn't hard, it just means I need to hack specific subroutines
in assembly, something which I tend to avoid.

Here's a sample thread definition from the producers/consumers
example (this is a consumer) 

#ifndef Recv_h
#define Recv_h

#include "Thread.h"
#include "LowerBoundedFifo.h"

class Recv : public Thread {

    int pid;
    LowerBoundedFifo mailBox;
    char nameBuffer[128];

public:

    Recv(int xpid);

    virtual void main();

    void add(int message);
    int remove();
};

#define MAX_RECVS 5
extern Recv *theRecvs[MAX_RECVS];

#endif Recv_h


and here's his code...

#include <stream.h>
#include "SimulationMultiplexor.h"
#include "Main.h"
#include "Recv.h"
#include "assert.h"

Recv *theRecvs[MAX_RECVS];

// a thread constructor takes (name, stack space, priority, safety switch)
//
// For most machines, you can now enable an 'mprotect-ed' page below
// the thread stack, stopping problems of accidentally & invisibly over
// running your current stack -- however, this is an old example that
// says ``don't do any sanity checking on context switches''.
//
Recv::Recv(int xpid) : ( "RECV", 10000, (ThreadPriority)-xpid, (bool)0)
{
    pid = xpid;
    sprintf(nameBuffer,"Recv-%d", pid);
    name(nameBuffer);
}

//
// remember --- main is where execution begins...
//
void
Recv::main()
{
	
//
// this junk is needed in multiprocessor versions to get exclsuive access
// to e.g,.  cerr for periods of time -- obviously, everyone has to agree
// to use the same lock.
//
#ifndef NO_IO
    CpuCerrLock.reserve();
    cout << "Hi mom, I'm #" << pid << "\n";
    CpuCerrLock.release();
#endif

    while(1) {
	int msg = remove();

#ifndef NO_IO
	CpuCerrLock.reserve();
	cout << "#" << pid << " got message " << msg;
	cout << " at " << simulatedClock() << "\n";
	CpuCerrLock.release();
#endif

//
// 'hold' is a statement that delays the current task for a given amount
// of simulated time
//
	hold(1);
    }
}

void
Recv::add(int message)
{
    AwesimeFifoItem foo = (AwesimeFifoItem) message;

    CurrentThread() -> checkStack();

    mailBox.add( & foo );
}

int
Recv::remove()
{
    AwesimeFifoItem foo;
    mailBox.remove(&foo);
    return( (int) foo );
}
	


Here's the 'main' subroutine for the program...

#include <stream.h>
#include <math.h>
#include <string.h>

//
//	Things for the simulation environment
// 

#include "SingleSimMux.h"
#include "Statistic.h"

#include "Recv.h"
#include "Send.h"

main(int argc)
{
    int i;

    SingleSimMux Cpu( argc > 2 ); // debug?

    for (i = 0; i < MAX_RECVS; i++) {
	cout << "Create recv(" << i << ")\n";
	theRecvs[i] = new Recv(i);

	cout << "add recv(" << i << ") to cpu\n";
	Cpu.add(theRecvs[i]);
    }

    cout << "create and add send\n";
    Cpu.add( new Send(1,MAX_RECVS, 200000));

    cout << "call fireitup \n";
    Cpu.fireItUp(2,100 * 4196);		// this executes the scheduling loop
					// until there's nothing to do...

    cout << "All done!\n";
}