C-with-Ease has two forms of process creation: cooperation and subordination. Subordination is like fork, the process executing the subordinate statement may continue as soon as the process is created (although the actual order of execution is undefined). Cooperation differs in that the creating (parent) process waits for all created (child) processes to complete before returning.
An Ease process creation statement allows processes to be created with any number and type of parameters. Since the created process will execute within any thread on the Ease host a flexible system for passing parameters to the created process is required.
Initial attempts at parameter passing to created processes used the thread creation function's knowledge of how parameters were placed on the stack by a C function call. This required the thread creation function to know the total size of the function's parameters on the stack. While this system worked with one threads package (the Sun lwp library), it was unsuitable for use on a distributed memory machine or with most other threads libraries (some of which only provide for supplying a single parameter to the created thread's function).
The mechanism developed uses a number of generated stub functions for
each function that is used in Ease
process creation statements. The
stub_in function takes all of its subject function's parameters and
packs them into a structure. This structure and its size are then passed
to the Ease
library which can then pass it on to another cell in a
message or to a new or existing thread. A generated stub_out function
takes the structure as a parameter and call its subject function with
the parameters from the structure.
Since the stub function mechanism uses no special knowledge about the host architecture's function calling mechanisms or stack layout it is completely portable. Is has the added advantage over other systems (such as using varargs) of providing compile time checking of function parameter types.
Recall the syntax for process creation is as follows:
The C-with-Ease
code @subordinate func1(param); is translated into:
{
void *_ec_childs=0;
_ec_childs = _stub_in_func1(_ec_childs,0,param);
}
A new block is created to encapsulate the variable _ec_childs. This
variable is used in cooperation statements to help generate a list of
processes for which the parent must wait to complete. _ec_childs is
present in subordination statements to simplify the translator - much of
the translation is the same as that for cooperation.
The stub function, _stub_in_func1 in this example, is generated from
the process declaration for the corresponding function. This
function packs the function's arguments into a structure which can then
be passed to the process that is created. The stub functions are
described in section 5.2.2.
The subordination with replication statement
@subordinate (i @for 10) func1(i); is translated into:
{
void *_ec_childs=0;
int ___REPLIC(i, 10, 0, 1)
_ec_childs = _stub_in_func1(_ec_childs,0 ,i);
}
The macro ___REPLIC is used to simplify the translator. Its arguments
are the variable to use, the number of processes, the initial value for
the variable and the step value. The macro definition is:
#define ___REPLIC(var,count,base,step) var; \
int __step = step; int __base = base; int __count = count; \
for (var = __base; var < __base + __count*__step; var += __step)
The variables __step etc are used to ensure that the expressions
passed as arguments to the macro are only calculated once.
The cooperation @cooperate { func1(param); func2(param); }
is translated into:
{
void *_ec_childs=0;
_ec_childs = _stub_in_func1(_ec_childs,1,param) ;
_ec_childs = _stub_in_func2(_ec_childs,1,param);
__ease_wait_for_children(_ec_childs);
}
Here the _ec_childs variable is used to build up a list of the
processes. __ease_wait_for_children() will return once all of the
child processes in the list have completed.
In order to use a function in an Ease
process creation statement
appropriate stub functions must be generated to assist in packing
function arguments into structures and for unpacking and calling the
function in the created process. The @process declaration is used for
this.
In C-with-Ease
, the declaration @process func2(int i, double d) { /* ... */ }
is translated into the following: a structure to hold the arguments, a
forward declaration, a _stub_out function, a _stub_in function and
finally the function preamble itself.
The structure to hold the arguments is as follows:
struct _stub_struct_func2
{
_ec_special _ec_specials;
int i;
double d;
};
Note that the structure also contains the an _ec_special
structure, which contains a pointer to the stub out function and a flag
to indicate whether this particular process is part of a cooperation.
The Ease
library expects to be passed an _ec_special so this
component is at the start of the structure.
Following the structure is a prototype for the subject function and the stub out function, as follows:
void func2(int i, double d);
void
_stub_out_func2 (struct _stub_struct_func2 *p, int len)
{
func2(p->i, p->d);
}
The stub_out function simply unpacks the structure and
calls the correct function. The forward declaration for the function is
provided to keep C (and C++
) compilers happy. This function is called
in the created process.
The stub_in function is called in response to a process creation
statement.
void *
_stub_in_func2 (void *_ec_childs, int _ec_parent, int i, double d)
{
struct _stub_struct_func2 _stub_var =
{
{(void (*)(void *, int)) _stub_out_func2, _ec_parent},
i, d
};
return _ec_create_process (_ec_childs, "func2",
(_ec_special *) & _stub_var, sizeof (_stub_var));
}
The _stub_in function packs all the arguments into the
appropriate structure and calls _ec_create_process. The packing of
parameters into the structure is a binary copy, so using pointer
parameters will not always work as the process they are passed to may be
in a separate address space. A string with the name of the function is
passed for debugging purposes and also for future expansion: currently
the system assumes that functions have the same address in all
processes, using the name string will allow functions to be at any
address provided they have been registered.
Finally the function itself is output:
void func2 (int i, double d) { /* ... */ }
Note that Ease
processes return void since they do not return
a value to the caller (except via contexts).
xxx Move this section to C++ -with-Ease implem section
In C++ -with-Ease , the generated code for process declarations is somewhat different. This is due to the use of marshalling (further described in section 3.3). This allows variable sized structures (such as lists or arrays) to be passed if appropriate marshalling functions are supplied.
The code generated for @process func2(int i, double d) { /* ... */ }
in C++ -with-Ease
does
not contain a structure declaration like the C-with-Ease
code. It begins with
a forward declaration and the stub_out function:
void func2 (int &i, double &d);
void _stub_out_func2 (void *_ec_p, int _ec_len)
{
umStream _ec_s (_ec_p, _ec_len);
_ec_special _ec_d;
_ec_s.remove (&_ec_d, sizeof _ec_d);
_ec_noinit ();
int i;
_ec_s >> i;
double d;
_ec_s >> d;
_ec_allowinit ();
func2 (i, d);
}
The function definition is changed so that it uses reference paramaters
rather than value parameters. This avoids a copy on calling the actual
function from the _stub_out function (which could be substantial for
larger structures). An unmarshal stream, umStream, is created and
initialized with the supplied data. The parameters are then extracted
from the stream. The _ec_noinit()/_ec_allowinit() pair switch off
automatic context creation during unmarshalling. Usually a new context
is created when a context variable is constructed but in this situation
the context will be initialized from the marshal stream immediately
after construction. These functions also lock the context creation
mutex to ensure that only contexts created by the current thread are
affected by the _ec_noinit.
To facilitate extension of the Ease
library to handle architectures
which may have different addresses for a function on different cells, a
static structure is generated to register the stub_out function by
name:
static _ec_Process _ec_proc_func2 (_stub_out_func2, "func2");
The class contains no data - it is only used so that its constructor is called upon process initialization.
Using this facility it is quite feasible to make C++ -with-Ease work over a heterogeneous network. The Ease programmer would have to do some extra work with the marshalling functions to ensure that all architectures could marshal into the same binary stream.
The _stub_in function is similar to that for C-with-Ease
, except that it
uses the marshal stream to pack data rather than a structure:
void *
_stub_in_func2 (void *_ec_childs, int _ec_parent,
const int &i, const double &d)
{
_ec_special _ec_d;
_ec_d.func = _stub_out_func2;
_ec_d.parent = _ec_parent;
mStream _ec_s;
_ec_s.add (&_ec_d, sizeof _ec_d);
_ec_s << i << d;
void *_ec_p;
int _ec_l;
_ec_p = _ec_s.get (_ec_l);
return _ec_create_process (_ec_childs, "func2",
(_ec_special *) _ec_p, _ec_l);
}
Finally, the function definition is output:
void func2 (int &i, double &d) { /* ... */ }
The messages involved in process handling are quite simple. A message of
type TYPE_CREATE results in a process being started in the receiving
cell. The content of the message is passed directly to the appropriate
_stub_out function. A TYPE_CHILD message is sent to the creator of a
cooperating process when the process exits.
If a process interacts with a context that is dead (that is, the
context's
creating process has exited), then that process may be sent a
TYPE_KILL message, which will cause the process to abort.
The selection of the cell for a new process is host dependent. Ideally a cell would be selected so as to minimize communication costs while maximizing processor utilization (inter cell communication could be minimized by placing all processes on the same cell but this does not make good use of the processors available).
Currently, no attempt is made to minimize communication, an attempt is made to maximize processor utilization by spreading processes evenly over available cells. Since any process may create a new process facilities must be provided on all cells which select a candidate cell. In the current implementations, two schemes are used: a centralized scheme where a single cell (the "main" cell) keeps track of current allocation and other cells interact with it in order to be allocated a cell, and a global scheme where all cells know the current state of the host and select a new cell using their knowledge. The global scheme is really only useful on hosts which have a cheap broadcast facility, such as the Fujitsu AP1000 which has a special broadcast network.
Next: Context Implementation
Up: Implementation
Previous: Host model