3.4 Code Migration: Processes Chap. 3
3.4 Code Migration: Processes Chap. 3
3.4 Code Migration: Processes Chap. 3
Before taking a look at the different forms of code migration, let us first con-
sider why it may be useful to migrate code.
This same reason can be used for migrating parts of the server to the client.
For example, in many interactive database applications, clients need to fill in
forms that are subsequently translated into a series of database operations. Proc-
essing the form at the client side, and sending only the completed form to the
server, can sometimes avoid that a relatively large number of small messages
need to cross the network. The result is that the client perceives better perfor-
mance, while at the same time the server spends less time on form processing and
communication.
Support for code migration can also help improve performance by exploiting
parallelism, but without the usual intricacies related to parallel programming. A
typical example is searching for information in the Web. It is relatively simple to
implement a search query in the form of a small mobile program that moves from
site to site. By making several copies of such a program, and sending each off to
different sites, we may be able to achieve a linear speed-up compared to using just
a single program instance.
Besides improving performance, there are other reasons for supporting code
migration as well. The most important one is that of flexibility. The traditional
approach to building distributed applications is to partition the application into
different parts, and deciding in advance where each part should be executed. This
approach, for example, has led to the different multitiered client-server applica-
tions discussed in Chap. 1.
However, if code can move between different machines, it becomes possible
to dynamically configure distributed systems. For example, suppose a server
implements a standardized interface to a file system. To allow remote clients to
access the file system, the server makes use of a proprietary protocol. Normally,
the client-side implementation of the file system interface, which is based on that
protocol, would need to be linked with the client application. This approach
requires that the software be readily available to the client at the time the client
application is being developed.
An alternative is to let the server provide the clients implementation no
sooner than is strictly necessary, that is, when the client binds to the server. At
that point, the client dynamically downloads the implementation, goes through the
necessary initialization steps, and subsequently invokes the server. This principle
is shown in Fig. 3-7. This model of dynamically moving code from a remote site
does require that the protocol for downloading and initializing code is standard-
ized. Also, it is necessary that the downloaded code can be executed on the
clients machine. Different solutions are discussed below and in later chapters.
The important advantage of this model of dynamically downloading client-
side software, is that clients need not have all the software preinstalled to talk to
servers. Instead, the software can be moved in as necessary, and likewise, dis-
carded when no longer needed. Another advantage is that as long as interfaces are
standardized, we can change the client-server protocol and its implementation as
often as we like. Changes will not affect existing client applications that rely on
160 PROCESSES CHAP. 3
Although code migration suggests that we move only code between machines,
the term actually covers a much richer area. Traditionally, communication in dis-
tributed systems is concerned with exchanging data between processes. Code
migration in the broadest sense deals with moving programs between machines,
with the intention to have those programs be executed at the target. In some cases,
as in process migration, the execution status of a program, pending signals, and
other parts of the environment must be moved as well.
To get a better understanding of the different models for code migration, we
use a framework described in (Fugetta et al., 1998). In this framework, a process
consists of three segments. The code segment is the part that contains the set of
instructions that make up the program that is being executed. The resource seg-
ment contains references to external resources needed by the process, such as
files, printers, devices, other processes, and so on. Finally, an execution segment
is used to store the current execution state of a process, consisting of private data,
the stack, and the program counter.
The bare minimum for code migration is to provide only weak mobility. In
this model, it is possible to transfer only the code segment, along with perhaps
some initialization data. A characteristic feature of weak mobility is that a
transferred program is always started from its initial state. This is what happens,
for example, with Java applets. The benefit of this approach is its simplicity.
SEC. 3.4 CODE MIGRATION 161
Weak mobility requires only that the target machine can execute that code, which
essentially boils down to making the code portable. We return to these matters
when discussing migration in heterogeneous systems.
In contrast to weak mobility, in systems that support strong mobility the exe-
cution segment can be transferred as well. The characteristic feature of strong
mobility is that a running process can be stopped, subsequently moved to another
machine, and then resume execution where it left off. Clearly, strong mobility is
much more powerful than weak mobility, but also much harder to implement. An
example of a system that supports strong mobility is DAgents, which we discuss
later in this section.
Irrespective of whether mobility is weak or strong, a further distinction can be
made between sender-initiated and receiver-initiated migration. In sender-
initiated migration, migration is initiated at the machine where the code currently
resides or is being executed. Typically, sender-initiated migration is done when
uploading programs to a compute server. Another example is sending a search
program across the Internet to a Web database server to perform the queries at
that server. In receiver-initiated migration, the initiative for code migration is
taken by the target machine. Java applets are an example of this approach.
Receiver-initiated migration is often simpler to implement than sender-
initiated migration. In many cases, code migration occurs between a client and a
server, where the client takes the initiative for migration. Securely uploading code
to a server, as is done in sender-initiated migration, often requires that the client
has previously been registered and authenticated at that server. In other words, the
server is required to know all its clients, the reason being is that the client will
presumably want access to the servers resources such as its disk. Protecting such
resources is essential. In contrast, downloading code as in the receiver-initiated
case, can often be done anonymously. Moreover, the server is generally not
interested in the clients resources. Instead, code migration to the client is done
only for improving client-side performance. To that end, only a limited number of
resources need to be protected, such as memory and network connections. We
return to secure code migration extensively in Chap. 8.
In the case of weak mobility, it also makes a difference if the migrated code is
executed by the target process, or whether a separate process is started. For exam-
ple, Java applets are simply downloaded by a Web browser and are executed in
the browsers address space. The benefit of this approach is that there is no need
to start a separate process, thereby avoiding communication at the target machine.
The main drawback is that the target process needs to be protected against mali-
cious or inadvertent code executions. A simple solution is to let the operating sys-
tem take care of that by creating a separate process to execute the migrated code.
Note that this solution does not solve the resource-access problems just men-
tioned.
Instead of moving a running process, also referred to as process migration,
strong mobility can also be supported by remote cloning. In contrast to process
162 PROCESSES CHAP. 3
migration, cloning yields an exact copy of the original process, but now running
on a different machine. The cloned process is executed in parallel to the original
process. In UNIX systems, remote cloning takes place by forking off a child proc-
ess and letting that child continue on a remote machine. The benefit of cloning is
that the model closely resembles the one that is already used in many applications.
The only difference is that the cloned process is executed on a different machine.
In this sense, migration by cloning is a simple way to improve distribution tran-
sparency.
The various alternatives for code migration are summarized in Fig. 3-8.
Execute at
Sender-initiated target process
mobility Execute in
separate process
Weak mobility
Execute at
Receiver-initiated target process
mobility Execute in
separate process
Mobility mechanism
Migrate process
Sender-initiated
mobility
Clone process
Strong mobility
Migrate process
Receiver-initiated
mobility
Clone process
So far, only the migration of the code and execution segment has been taken
into account. The resource segment requires some special attention. What often
makes code migration so difficult, is that the resource segment cannot always be
simply transferred along with the other segments without being changed. For
example, suppose a process holds a reference to a specific TCP port through
which it was communicating with other (remote) processes. Such a reference is
held in its resource segment. When the process moves to another location, it will
have to give up the port and request a new one at the destination. In other cases,
transferring a reference need not be a problem. For example, a reference to a file
by means of an absolute URL will remain valid irrespective of the machine where
the process that holds the URL resides.
To understand the implications that code migration has on the resource seg-
ment, Fuggetta et al. distinguish three types of process-to-resource bindings. The
strongest binding is when a process refers to a resource by its identifier. In that
SEC. 3.4 CODE MIGRATION 163
case, the process requires precisely the referenced resource, and nothing else. An
example of such a binding by identifier is when a process uses a URL to refer to
a specific Web site or when it refers to an FTP server by means of that servers
Internet address. In the same line of reasoning, references to local communication
endpoints also lead to a binding by identifier.
A weaker form of process-to-resource binding is when only the value of a
resource is needed. In that case, the execution of the process would not be
affected if another resource would provide that same value. A typical example of
binding by value is when a program relies on standard libraries, such as those for
programming in C or Java. Such libraries should always be locally available, but
their exact location in the local file system may differ between sites. Not the
specific files, but their content is important for the proper execution of the proc-
ess.
Finally, the weakest form of binding is when a process indicates it needs only
a resource of a specific type. This binding by type is exemplified by references to
local devices, such as monitors, printers, and so on.
When migrating code, we often need to change the references to resources,
but cannot affect the kind of process-to-resource binding. If, and exactly how a
reference should be changed, depends on whether that resource can be moved
along with the code to the target machine. More specifically, we need to consider
the resource-to-machine bindings, and distinguish the following cases. Unat-
tached resources can be easily moved between different machines, and are typi-
cally (data) files associated only with the program that is to be migrated. In con-
trast, moving or copying a fastened resource may be possible, but only at rela-
tively high costs. Typical examples of fastened resources are local databases and
complete Web sites. Although such resources are, in theory, not dependent on
their current machine, it is often infeasible to move them to another environment.
Finally, fixed resources are intimately bound to a specific machine or environ-
ment and cannot be moved. Fixed resources are often local devices. Another
example of a fixed resource is a local communication endpoint.
Combining three types of process-to-resource bindings, and three types of
resource-to-machine bindings, leads to nine combinations that we need to con-
sider when migrating code. These nine combinations are shown in Fig. 3-9.
Let us first consider the possibilities when a process is bound to a resource by
identifier. When the resource is unattached, it is generally best to move it along
with the migrating code. However, when the resource is shared by other
processes, an alternative is to establish a global reference, that is, a reference that
can cross machine boundaries. An example of such a reference is a URL. When
the resource is fastened or fixed, the best solution is also to establish a global
reference.
It is important to realize that establishing a global reference may be more than
just making use of URLs, and that the use of such a reference is sometimes prohi-
bitively expensive. Consider, for example, a program that generates high-quality
164 PROCESSES CHAP. 3
Resource-to-machine binding
222222222222222222222222222222222222222222222222222222222222
1 1 Unattached 1 Fastened 1 Fixed 1
1222222222222222222222222222222222222222222222222222222222222
1 1 1 1
Process- 1 By identifier 1 MV (or GR) 1 GR (or MV) 1 GR 1
to-resource 1 By value 1 CP (or MV,GR) 1 GR (or CP) 1 GR 1
binding 11222222222222222222222222222222222222222222222222222222222222
By type 1 RB (or MV,CP) 1 RB (or GR,CP) 1 RB (or GR) 1
1 1 1 1
GR Establish a global systemwide reference
MV Move the resource
CP Copy the value of the resource
RB Rebind process to locally available resource
be copied, as may be the case with dictionaries and thesauruses in text processing
systems.
The easiest case is when dealing with unattached resources. The best solution
is to copy (or move) the resource to the new destination, unless it is shared by a
number of processes. In the latter case, establishing a global reference is the only
option.
The last case deals with bindings by type. Irrespective of the resource-to-
machine binding, the obvious solution is to rebind the process to a locally avail-
able resource of the same type. Only when such a resource is not available, will
we need to copy or move the original one to the new destination, or establish a
global reference.
So far, we have tacitly assumed that the migrated code can be easily executed
at the target machine. This assumption is in order when dealing with homogene-
ous systems. In general, however, distributed systems are constructed on a hetero-
geneous collection of platforms, each having their own operating system and
machine architecture. Migration in such systems requires that each platform is
supported, that is, that the code segment can be executed on each platform,
perhaps after recompiling the original source. Also, we need to ensure that the
execution segment can be properly represented at each platform.
Problems can be somewhat alleviated when dealing only with weak mobility.
In that case, there is basically no runtime information that needs to be transferred
between machines, so that it suffices to compile the source code, but generate dif-
ferent code segments, one for each potential target platform.
In the case of strong mobility, the major problem that needs to be solved is the
transfer of the execution segment. The problem is that this segment is highly
dependent on the platform on which the process is being executed. In fact, only
when the target machine has the same architecture and is running exactly the
same operating system, is it possible to migrate the execution segment without
having to alter it.
The execution segment contains data that is private to the process, its current
stack, and the program counter. The stack will partly consist of temporary data,
such as values of local variables, but may also contain platform-dependent infor-
mation such as register values. The important observation is that if we can avoid
having execution depend on platform-specific data, it would be much easier to
transfer the segment to a different machine, and resume execution there.
A solution that works for procedural languages such as C and Java is shown in
Fig. 3-10 and works as follows. Code migration is restricted to specific points in
the execution of a program. In particular, migration can take place only when a
next subroutine is called. A subroutine is a function in C, a method in Java, and so
166 PROCESSES CHAP. 3
on. The runtime system maintains its own copy of the program stack, but in a
machine-independent way. We refer to this copy as the migration stack. The
migration stack is updated when a subroutine is called, or when execution returns
from a subroutine.
When a subroutine is called, the runtime system marshals the data that have
been pushed onto the stack since the last call. These data represent values of local
variables, along with parameter values for the newly called subroutine. The
marshaled data are then pushed onto the migration stack, along with an identifier
for the called subroutine. In addition, the address where execution should continue
when the caller returns from the subroutine is pushed in the form of a jump label
onto the migration stack as well.
Push marshalled
Local stack procedure call onto
operations B migration stack
Local
Procedure B variables B
Return label
(jump) to A
proc factorial n {
if { $n 1 } { return 1; } # fac(1) = 1
expr $n * [ factorial [ expr $n 1] ] # fac(n) = n * fac(n1)
}
agent 3 submit $machine procs factorial vars number script { factorial $number }
agent 3 receive ... # receive the results (left unspecified for simplicity)
call to agent 3 jump. The process that was running the agent at the source
machine, exits.
# Create a migrating agent by submitting the script to this machine, from where
# it will jump to all the others in $machines.
agent 3 submit $this 3 machine procs all 3 users vars machines \
script { all 3 users $machines }
agent 3 receive ... # receive the results (left unspecified for simplicity)
by the procedure all 3 users. It maintains a list of users that is initially empty. The
set of hosts that it should visit is given by the parameter machines. The agent
jumps to each host, puts the results of executing who in the variable users, and
appends that to its list. In the main program, the agent is created on the current
machine by submission, that is, using the previously discussed mechanisms for
weak mobility. In this case, agent 3 submit is requested to execute the script
all 3 users $machines
and is given the procedure and set of hosts as additional parameters.
Finally, process cloning is supported by means of the agent 3 fork command.
This command behaves almost the same as agent 3 jump, except that the process
running the agent at the source machine simply continues with the instruction fol-
lowing its call to agent 3 fork. Like the fork operation in UNIX, agent 3 fork returns
a value by which the caller can check whether it is the cloned version
(corresponding to the child in UNIX), or the original caller (i.e., the parent).
Implementation Issues
5 Agents
Tcl/Tk Scheme Java
4
interpreter interpreter interpreter
2 Server
1 TCP/IP E-mail
The next layer consists of a server that runs at each machine where DAgents
agents are executing. The server is responsible for agent management, authentica-
tion, and management of communication between agents. For the latter, the server
assigns a location-unique identifier to each agent. Using the network address of
the server, each agent can then be referred to by an (address, local-id)-pair. This
low-level name is used to set up communication between two agents.
SEC. 3.4 CODE MIGRATION 171
The third layer is at the heart of the DAgents system, and consists of a
language-independent core that supports the basic model of agents. For example,
this layer contains implementations to start and end an agent, implementations of
the various migration operations, and facilities for interagent communication.
Clearly, the core operates closely with the server, but, in contrast to the server, is
not responsible for managing a collection of agents running on the same machine.
The fourth layer consists of interpreters, one for each language supported by
DAgents. Each interpreter consists of a component for language interpretation, a
security module, an interface to the core layer, and a separate module to capture
the state of a running agent. This last module is essential for supporting strong
mobility, and is discussed in more detail below.
The highest-level layer consists of agents written in one of the supported
languages. Each agent in DAgents is executed by a separate process. For exam-
ple, when an agent migrates to machine A, the server there forks a process that
will execute the appropriate interpreter for the migrating agent. The new process
is then handed the state of the migrating agent, after which it continues where the
agent had previously left off. The server keeps track of the processes it created
using a local pipe, so that it can pass incoming calls to the appropriate process.
The more difficult part in the implementation of DAgents, is capturing the
state of a running agent and shipping that state to another machine. In the case of
Tcl, the state of an agent consists of the parts shown in Fig. 3-14. Essentially,
there are four tables containing global definitions of variables and scripts, and two
stacks for keeping track of the execution status.
2222222222222222222222222222222222222222222222222222222222222222222222222222222222
1 State 1 Description 1
2222222222222222222222222222222222222222222222222222222222222222222222222222222222
1 1 1
12222222222222222222222222222222222222222222222222222222222222222222222222222222222
Global interpreter variables 1 Variables needed by the interpreter of an agent 1
1 Global system variables 1 Return codes, error codes, error strings, etc. 1
12222222222222222222222222222222222222222222222222222222222222222222222222222222222
1 1
12222222222222222222222222222222222222222222222222222222222222222222222222222222222
Global program variables 1 User-defined global variables in a program 1
1 1 1
Procedure definitions Definitions of scripts to be executed
12222222222222222222222222222222222222222222222222222222222222222222222222222222222
1 by an agent 1
12222222222222222222222222222222222222222222222222222222222222222222222222222222222
Stack of commands 1 Stack of commands currently being executed 1
1 1 1
Stack of call frames 1 Stack of activation records, one for each running command 1
12222222222222222222222222222222222222222222222222222222222222222222222222222222222
The more interesting parts related to agent migration are the two stacks by
which an accurate account is kept of the actual execution status of an agent. Basi-
cally, an agent is considered as a series of Tcl commands, possibly embedded in
constructions such as loops, case statements, and so on. In addition, commands
may be grouped into procedures. As is normal for any interpreted language, an
agent is executed command by command.
First consider what happens when a basic Tcl command is executed, that is, a
command that is not a call to a user-defined procedure. The interpreter parses the
command and builds a record that is to be pushed onto what is called the com-
mand stack. Such a record contains all the necessary fields to actually execute
the command, such as its parameter values, a pointer to a procedure implementing
the command, and so on. This record is then pushed onto the stack, after which it
can be handed over to the component responsible for actually executing the com-
mand. In other words, the command stack gives a precise account of the current
execution status of an agent.
Tcl also supports user-defined procedures. In addition to the command stack,
the runtime environment of DAgents keeps track of a stack of activation records,
also called call frames. A call frame in DAgents contains a table of variables
local to the procedure, along with the names and values of the parameters by
which the procedure was called. A call frame is created only as the result of a pro-
cedure call, and as such is related to a procedure-call command as pushed onto the
command stack. The call frame keeps a reference to its associated command.
Now consider what happens, for example, when an agent calls agent 3 jump,
by which the agent migrates to another machine. At that point, the complete state
of the agent as just described is marshaled into a series of bytes. In other words,
all four tables and the two stacks are put together into a single array of bytes and
shipped to the target machine. The DAgents server on the target machine subse-
quently creates a new process running the Tcl interpreter. That process is handed
the marshaled data, which it then unmarshals into the state the agent was in when
it called agent 3 jump. By simply popping the command from the top of the com-
mand stack, execution continues exactly where it had left off.