Primitives
Primitives
Message send and message receive communication primitives are denoted Send() and
Receive(), respectively.
A Send primitive has at least two parameters – the destination, and the buffer in the user
space, containing the data to be sent. Similarly, a Receive primitive has at least two parameters –
the source from which the data is to be received (this could be a wildcard), and the user buffer into
which the data is to be received.
There are two ways of sending data when the Send primitive is invoked – the buffered
option and the unbuffered option.
The buffered option which is the standard option copies the data from the user buffer to the
kernel buffer. The data later gets copied from the kernel buffer onto the network. In the unbuffered
option, the data gets copied directly from the user buffer onto the network.
For the Receive primitive, the buffered option is usually required because the data may
already have arrived when the primitive is invoked, and needs a storage place in the kernel.
The code for a non-blocking Send would look as shown in Figure. First, it can keep
checking (in a loop or periodically) if the handle has been flagged or posted. Second, it can issue
a Wait with a list of handles as parameters. The Wait call usually blocks until one of the parameter
handles is posted.
If at the time that Wait() is issued, the processing for the primitive (whether synchronous
or asynchronous) has completed, the Wait returns immediately. The completion of the processing
of the primitive is detectable by checking the value of handlek. If the processing of the primitive
has not completed, the Wait blocks and waits for a signal to wake it up. When the processing for
the primitive completes, the communication subsystem software sets the value of handlek and
wakes up (signals) any process with a Wait call blocked on this handlek This is called posting the
completion of the operation.
There are therefore four versions of the Send primitive – synchronous blocking,
synchronous non-blocking, asynchronous blocking, and asynchronous non-blocking. For the
Receive primitive, there are the blocking synchronous and non-blocking synchronous versions.
These versions of the primitives are illustrated in Figure using a timing diagram. Here the timelines
are shown for each process: (1) for the process execution, (2) for the user buffer from/to which
data is sent/received, and (3) for the kernel/communication subsystem.
Processor synchrony
Processor synchrony indicates that all the processors execute in lock-step with their clocks
synchronized. As this synchrony is not attainable in a distributed system, what is more generally
indicated is that for a large granularity of code, usually termed as a step, the processors are
synchronized. This abstraction is implemented using some form of barrier synchronization to
ensure that no processor begins executing the next step of code until all the processors have
completed executing the previous steps of code assigned to each of the processors.
There exists a wide range of primitives for message-passing. Many commercial software
products (banking, payroll, etc., applications) use proprietary primitive libraries supplied with the
software marketed by the vendors (e.g., the IBM CICS software which has a very widely installed
customer base worldwide uses its own primitives).
The message-passing interface (MPI) library and the PVM (parallel virtual machine)
library are used largely by the scientific community, but other alternative libraries exist.
Commercial software is often written using the remote procedure calls (RPC) mechanism
in which procedures that potentially reside across the network are invoked transparently to the
user, in the same manner that a local procedure is invoked.
Ideally, many programs want the processes to execute a series of instructions in rounds
(also termed as steps or phases) asynchronously, with the requirement that after each
round/step/phase, all the processes should be synchronized and all messages sent should be
delivered. This is the commonly understood notion of a synchronous execution. Within each
round/phase/step, there may be a finite and bounded number of sequential sub-rounds (or
subphases or sub-steps) that processes execute. Each sub-round is assumed to send at most one
message per process; hence the message(s) sent will reach in a single message hop.
Emulations
Figure: Emulations among the principal system classes in a failure free system.
There are four broad classes of programs, as shown in Figure. Using the emulations shown,
any class can be emulated by any other. If system A can be emulated by system B denoted A/B,
and if a problem is not solvable in B, then it is also not solvable in A. Likewise, if a problem is
solvable in A, it is also solvable in B. Hence, in a sense, all four classes are equivalent in terms of
“computability” – what can and cannot be computed – in failure-free systems.