Both components and connectors have a specification part and an implementation part.
Components are specified by an interface. The interface defines the computational commitments the component can make and the constraints on the way the component can be used. It also provides the guarantees that will hold of the performance and behavior of the component. The interface contains three types of information:
Connectors are specified by a protocol. The protocol defines the allowable interactions among a collection of components and provides guarantees about those interactions. It contains three types of information:
Component implementations have two different forms: primitive and composite. A primitive implementation consists of a pointer to a source document external to the UniCon language that contains the implementation. The source document may be source code in your favorite programming language (at present, the UniCon toolset only supports C language source code), object code, a Unix archive file containing object code, a binary executable, a shell sdript, a sequential data file, or a C language include file. The UniCon toolset knows how to construct systems using this variety of source documents.
A composite implementation is a description of a configuration of other UniCon-defined components and connectors. It contains three types of information:
Connector implementations are built into the language; they have specific implementations which are provided by the UniCon toolset. No mechanism exists in UniCon to allow for the definition of user-defined connector implementations.
A component is an abstraction that represents a locus of computation and state in a system. It roughly corresponds to a compilation unit in a conventional programming language, a process in an operating system environment, or a user-level object such as a file in a filesystem. Components behave in identifiable, distinct ways, and they also interact with other components in similarly distinct and identifiable ways. These distinctions separate components into categories, or types. A component type captures the semantics of a component's behavior, the kind of functionality it implements, its performance characteristics, and its expectations of the style of interaction with other components.
A component has a specification, called an interface, and an implementation. The interface defines the computational commitments the component can make, and constraints on the way the component is to be used. It provides the guarantees that will hold of the behavior and performance of the component. It should be possible to use the component by reference to the interface alone.
The interface defines:
Properties can be thought of as attributes of a definition which are used to further describe (i.e., specify) it. They are global assertions and constraints that apply to the definition as a whole. There are many places in UniCon where lists of properties are used to further specify a definition. In component interfaces, properties are used to specify information such as the name of the processor that a process will run on, or the priority that a process will have in the run-time environment.
The interface defines the units of association used in system composition. These are named entities called players, and they are what get connected during system composition by connectors. They are the visible points through which a component interacts, requests or provides services, or is influenced by external state or events. Players are further specified by their own property lists.
The implementation of a component describes how it is constructed. Components have either primitive implementations, or composite implementations. A primitive implementation describes a component that is implemented by some source code or data that is external to UniCon (e.g., source code of a programming language, scripts of commands of an operating system shell, or data in a file in the file system).
A composite implementation describes a configuration of components and connectors. This mechanism in UniCon allows system designers to progressively build larger subsystems from collections of smaller components or subsystems, or more abstract components from less abstract ones.
<component> :==The properties in a <property_list> are separated by whitespace (i.e., spaces, tabs, and carriage returns). A <property_list> can be empty (in many cases), or it may contain one or more properties. A <property> is a name-value pair that specifies an attribute, an assertion, or a constraint pertaining to a definition. It is used to further specify that definition. The syntax of a property is as follows:
COMPONENT <identifier>
<interface>
<component_implementation>
END <identifier> <interface> :==
INTERFACE IS
TYPE <component_type>
<property_list>
<player_list>
END INTERFACE <component_type> :==
Module
| Computation
| SharedData
| SeqFile
| Filter
| Process
| SchedProcess
| General
<property> :== <name> <value> <name> :== <identifier> <value> :==The <name> in a property has significance in UniCon (i.e., it conveys meaningful information about a definition); the <value> associates specific information with the property name in the given context. Here, the term context refers to the given language element of the given type (e.g., player, of type GlobalDataDef). The <value> may be NULL, or it may contain a <value_part> enclosed in parentheses. The <value_part> has a syntax that is specific to the property, in a given context. There are eleven common syntaxes for the values of UniCon properties.
EMPTY
| (<value_part>)
The set of properties that can be legally specified in a property list depends on which definition the property list further specifies. For example, the set of properties legal for an interface property list is different from the set that is legal for an implementation's property list.
The players in a <player_list>, the variants in a <variant_list>, and the statements in a <composite_statement_list> are all separated by whitespace.
<component_implementation> :==
<primitive_implementation>
| <composite_implementation> <primitive_implementation> :==
IMPLEMENTATION IS
<property_list>
<variant_list>
END IMPLEMENTATION <composite_implementation> :==
IMPLEMENTATION IS
<property_list>
<composite_statement_list>
END IMPLEMENTATION
Every component definition has a type. In UniCon, there are eight pre-defined component types. The specification of the type comes as the first UniCon statement inside a component interface.
<component_type> :==
Module
| Computation
| SharedData
| SeqFile
| Filter
| Process
| SchedProcess
| General
The type Filter is a departure from the previous three types we've already seen: Module, Computation, and SharedData. Whereas these three types describe compilation units developed in some programming language, Filter describes a process in the Unix environment.
All ports that the filter will read from and write to are assumed to be already open at process creation time. This means that if, for example, the program assumes that there will be input on port 4, then port 4 will already have been opened for input by the time the first line of code in the program is executed.
A Process component can behave in one of two ways. It can behave like a "server" process, providing services to other processes by exporting remote procedure definitions, or it can behave like a "client" process, requesting services from other processes via remote procedure calls. The distinction between client processes and server processes is important in UniCon because certain properties (e.g., the EntryPoint property) have different semantics based on the kind of Process being defined. To formalize the definitions, a client process is one that exports no remote procedure definitions, only calls. A server process is one that exports at least one remote procedure definition. A process that exports remote procedure definitions and calls is still considered to be a server (i.e., one that requires services from other processes in order to accomplish its work).
In addition to the static, compile-time "glue code," remote procedure calls require run-time support. Each process must perform some initialization in the environment before starting to perform its tasks. This initialization takes the form of looking up (client) or registering (server) services with a common "name server" process that executes in the environment. This is how client processes are able to locate services provided by server processes in the run-time environment. The UniCon compiler generates this run-time initialization code automatically for each process. It generates a "main" program that performs the initialization and calls the initial entrypoint in the source code provided by the system designer. The designer specifies this entrypoint name using the EntryPoint property.
The source code implementation of a client process must be a function or a procedure.
UniCon currently only supports implementations in the C programming language, so for now it must be a C function. The function can be named "main," however it will be renamed in this case since UniCon produces a "main" function for each process. The "main" function that UniCon produces performs any initialization required to support remote procedure calls between this and other processes, and then it calls the function that implements the client process. The process terminates when this function returns, so some sort of infinite processing (i.e., loop) may be required to keep the process active.
The source code implementation of a server process is different. It must be a collection of C functions, called service functions, plus optionally one additional worker function. The worker function, if present, will run in parallel with a UniCon-generated "main" program. It performs application-specific processing. The service functions become the callable remote procedures at run-time. UniCon generates a "main" function that performs initialization required to support remote procedure calls between this and other processes and loops forever waiting for service requests from other processes. This main function will call the appropriate service function to perform a requested service, and then send the results back to the client process that made the request.
The worker function must be implemented as a simple C function. Under Mach, the worker function may or may not loop forever. In RPCGen-based servers, the worker thread must not loop forever, and will be called periodically (after performing each service request, and every 10 milliseconds if no requests are forthcoming). The presence or absence of a worker function is specified by the presence or absence of the EntryPoint property in the interface property list of a server Process component. The name of the worker function is supplied as the value of this property.
Each service function must be a simple C function that does not loop forever, but rather eventually returns.
One final note: remote procedure calls on Mach systems require that the functions pass results back to client processes via the arguments of the function (i.e., the return value is passed back to the caller by modifying the location pointed to by one of the input arguments), rather than as the return value of the function. This is not the case for RPCGen-based services.
In addition to RPCCall and RPCDef players, a SchedProcess component exports players of type RTLoad. A player of this type represents a load that is to be placed on a particular processor (CPU) in the operating system environment. An RTLoad player is defined by a single trigger, and a set of segments, both of which are specified as properties in the player definition.
A trigger is an event that causes a process to become active and to perform its work. A segment is a well-defined block of that work. A trigger usually describes the expiration of a timer associated with a process in the operating system environment. The real-time scheduling algorithm interprets this as an event that requires the activation of the given process. Triggers are periodic; they happen over and over again at a given rate. For example, if a trigger has a period of one second, then the event occurs once per second. If the period is one half second, then the event occurs twice per second, and so on.
A SchedProcess component is broken down into a set of one or more work blocks called segments, each of which requires a specific amount of CPU time to execute. Collectively, the total execution time of the set of segments in an RTLoad player represents the load placed on the CPU by the SchedProcess component each time the event described by the trigger occurs.
In a real-time application described by a system of SchedProcess components, communication via remote procedure calls adds a level of complexity to the notion of an event. In such a system, a trigger activates only a client process. A server process becomes active whenever a request is made for a service it provides. When a process makes a remote procedure call, work blocks in a server process are executed in addition to the work blocks in the calling process. This means that, before work is completed in response to a given trigger, segments from a set of SchedProcess components will be executed if some of the processes are performing remote procedure calls. The specific sequence of segments that executes in response to a trigger is called the event trace. In systems of SchedProcess components performing RPCs, it is the load associated with an event trace that is considered during scheduling. To summarize, the RTLoad player in a SchedProcess component only describes the portion of a trace applicable to a single process. Since a trace can span multiple processes, the load placed on the CPU in response to an event comes from multiple RTLoad players.
In a SchedProcess component, triggers are defined and associated with periods by specifying a TriggerDef property in the property list of the component interface. Segments are defined and associated with execution times by specifying a SegmentDef property in the interface property list. Lastly, the specific trigger in an RTLoad player is specified by the Trigger property in the player's property list, and the set of segments is specified by the SegmentSet property in the same list. The trigger in the Trigger property must have been previously defined in a TriggerDef property, and the segments in the SegmentSet property must also have been previously defined in SegmentDef properties.
In real-time systems, not all of the work performed by SchedProcess components has the same priority. Some of the work is more urgent, and must always get priority over other work. To facilitate this pattern of execution, real-time operating systems make provisions for assigning priorities to processes. In UniCon, a priority is assigned to a SchedProcess component via the Priority property in the interface property list.
Client processes must be implemented by C functions or procedures that do not loop forever. Since the real-time scheduling algorithm reschedules (i.e., reactivates) the code to execute over and over again, it must finish its work and return control to the operating system each time it is activated.
In addition to performing initialization to support remote procedure calls between processes, the "main" program for a process in a real-time environment must also initialize the process to run at a particular priority, and according to a particular rate (period). The priority information can be specified by the system designer in a SchedProcess component instantiation via the Priority property. The period information is specified via the floating point value in the TriggerDef property when defining a trigger in a SchedProcess component definition.
The danger in using this type is that any component can be described using the type
General, and so it can be misused to bypass the checks that guarantee that a component behaves according to the semantics defined by one of the other pre-defined component types in UniCon.
A proper use of the General component type is to describe systems or subsystems that do not exhibit the behavior described by a single one of any of the other pre-defined component types in UniCon.
A player definition contains:
Players are further specified by properties in the property list of the player definition. Properties can be thought of as attributes of a definition which are used to further describe (i.e., specify) it. For players, they are attributes, requirements, and constraints that apply to the player as a whole. There are many places in UniCon where lists of properties are used to further specify a definition. In players, properties are used to specify information such as the signature of the arguments in a routine definition, the port in a Unix process through which a stream player will communicate, or the number of player/role associations that are allowed for a given player in a component.
<player> :==Players can be defined with and without properties. In the latter case, the player definition appears as follows:
PLAYER <identifier> IS <player_type>
<optional_end_player_syntax> <player_type> :==
GlobalDataDef
| GlobalDataUse
| PLBundle
| ReadFile
| ReadNext
| RoutineCall
| RoutineDef
| RPCCall
| RPCDef
| RTLoad
| StreamIn
| StreamOut
| WriteFile
| WriteNext <optional_end_player_syntax> :==
EMPTY
| <property_list> END <identifier>
PLAYER my_read_player IS ReadNextNotice that if no properties are specified in a player definition (i.e., the property list is empty), the END statement is omitted.
In the former case, the player definition appears as follows:
PLAYER my_input_player IS StreamInThe END statement is required in this case. The <identifier> in the END statement must be identical to the <identifier> following the keyword PLAYER.
SIGNATURE ("line")
PORTBINDING (stdin)
END my_input_player
Every player definition has a type. In UniCon, there are fourteen pre-defined player types. The specification of the type appears immediately following the keyword IS in a UniCon player definition.
<player_type> :==
GlobalDataDef
| GlobalDataUse
| PLBundle
| ReadFile
| ReadNext
| RoutineCall
| RoutineDef
| RPCCall
| RPCDef
| RTLoad
| StreamIn
| StreamOut
| WriteFile
| WriteNext
A GlobalDataDef player does not correspond to a shared memory location in the operating system environment that can be accessed by code in different processes. It does represent data that can be accessed by any code within a process.
/* defined anywhere in a C source code such a definition is legal */
extern int my_global_variable; /* defined at the outer-most scope of a C source code file */
int another_global_variable;
/* defined, for example, in C include file my_application_globals.h */
extern int a_global_variable; /* defined, for example, in C source code file my_utility_file.c */
#include "my_application_globals.h"
static int a_local_variable = a_global_variable;
When designers think about the architecture of a system, they often think about the use of such collections of players, rather than about individual players. The PLBundle player type in UniCon allows the system designer to create a collection of players, to attach significance to a group of routines and data in an architectural description of a system. The name PLBundle denotes a programing language bundle; the bundle may only contain players of types that have corresponding programming language implementations: GlobalDataDef,
GlobalDataUse, RoutineCall, and RoutineDef players.
The individual players in a PLBundle are defined using the Member property. The value associated with this property has three fields, each of which corresponds to a portion of a UniCon player definition. The first field is the player name, the second is the player type, and the third is the player property list. PLBundle players must contain at least one member player definition, and there is no upper limit imposed for member players in a PLBundle.
Refer to the descriptions of the GlobalDataDef, GlobalDataUse, RoutineCall, and
RoutineDef player types for implementation guidance.
#include <stdio.h>
#include <fcntl.h>
#define MAX_BUF_LEN 256
#define FOR_INPUT "r"
/* external data definitions required to support the code fragments below */
char input_buffer[MAX_BUF_LEN + 1];
int i, fd;
fd = open ("datafile", O_RDONLY);
for (i = 0;
i < MAX_BUF_LEN;
i++)
if (read (fd, &input_buffer[i], ONE_CHARACTER) == 0)
break;
close (fd);
Data can be read sequentially from a file in UniCon by a Filter component via a Pipe connection, and by a Module component via a FileIO connection. The ReadNext player type describes data being read from a file in either situation.
A RoutineCall player does not describe a call to a function or service located in a remote process. It does describe a call to a function or procedure within a single process.
/* assume the function returns an integer result, and the variable is of type integer */
an_integer = a_function_call (parameter1, parameter2); /* the result of the function call below is discarded, making it look like a procedure call */
a_C_procedure_call (parameter1, parameter2);
A RoutineDef player does not describe a definition or service that can be accessed by a remote process. It does describe a routine that can be called locally with a single process.
/* assume that "pointer" is a global integer variable
and that "stack" is a global character array */
int stack_is_empty (void) {
return (pointer == 0);
} void push (char *value) {
stack[pointer++] = value;
}
An RPCCall player describes a call made in one process to a service located in a remote process. It does not describe a call to a function or procedure defined within the same process. Remote procedure calls are made between RPCCall and RPCDef players defined in Process or SchedProcess components.
/* assume the function returns an integer result, and the variable is of type integer */NOTE: The UniCon compiler generates glue code that supports two forms of remote procedure calls: InterProcess Communication (IPC) calls on Mach platforms, and RPCGen-based remote procedure calls on SunOS platforms. The Mach IPC facility does not permit remote function calls, only remote procedure calls. Therefore, no remote procedure call in a Mach application may return a value (i.e., all remote procedure calls must look like example number two, above). If results must be returned in a Mach application, they must be returned via the arguments in the argument list.
an_integer = a_remote_function_call (parameter1, parameter2); /* the result of the remote function call below is discarded,
making it look like a remote procedure call */
a_remote_procedure_call (parameter1, parameter2);
An RPCDef player describes a definition of a service that is accessed by a remote process. It does not describe a routine that can be called locally within a single process.
#include <stdio.h>NOTE: The UniCon compiler generates glue code that supports two forms of remote procedure calls: InterProcess Communication (IPC) calls on Mach platforms, and RPCGen-based remote procedure calls on SunOS platforms. The Mach IPC facility does not permit remote function definitions, only remote procedure definitions. Therefore, no remote procedure definition in a Mach application may return a value (i.e., all remote procedure definitions must look like definitions one and two in the example above). If results must be returned in a Mach application, they must be returned via the arguments in the argument list (as in definition number one, above).
#include <time.h>
void timeget_Mach(int *get_time) {
time(get_time); /* get the system time */
}
void timeshow(void) {
int show_time;
time(&show_time); /* get the system time */
/* display the system time */
fprintf(stdout, "Server: %s\n", ctime(&show_time));
}
int timeget_RPCGen(void) {
int get_time;
time(&get_time); /* get the system time */
return get_time;
}
In a real-time environment, client schedulable processes are periodic. They do not run forever. When a client process is activated, it runs to completion and returns control to the environment. Server processes, however, do run forever, but control is given to a server process only via remote procedure calls from clients or other servers. During execution, the thread of control is given to a client schedulable process by the environment. The process performs some work, and it may return or it may make a remote procedure call to another process (a server process). If it makes a remote procedure call, the thread of control is transferred to the server process, which performs some work and returns (or makes another remote procedure call, and so on). Control is eventually returned back to the original client process, where work continues in a similar fashion until control is returned back to the environment. This entire thread of control is called an event in UniCon, and can be described by a trace. A trace is a specification of the event. It specifies the trigger, the mechanism by which control is first given to a client schedulable process from the environment, and the sequence of segments that execute as a result of the trigger in the order in which they are executed.
A trigger is an abstraction. As mentioned above, it describes the mechanism by which control is transferred from the environment to a schedulable process. Only client processes can define triggers, because they begin execution by receiving control from the environment. Server processes do not have triggers because they execute continuously and receive control only as a result of remote procedure calls. Since client processes execute to completion and return to the environment, they can be reactivated by the environment according to some rate (i.e., some number of times per second). Therefore, triggers have associated rates of initiation. Triggers are defined in the interface property list of SchedProcess components via the TriggerDef property. The value part of the property defines the name of the trigger and associates with it the rate (in seconds) at which the trigger is initiated.
A segment is also an abstraction. It describes a chunk of code in the implementation of the schedulable process. The chunk of code it describes may take any form that the designer chooses. For example, it may describe a function, a set of functions, a portion of a single function, or even a few statements within a given function. The designer typically defines segments based on structural and execution time properties of the code in order to facilitate certain types of analyses that can be done on sets of schedulable processes in a real-time environment (more on this later). Segments are defined in the interface property list of SchedProcess components via the SegmentDef property. The value part of the property defines the name of the segment and associates an execution time (in seconds) with it. Work done in a schedulable process is described by a sequence of one or more segments.
As described above an RTLoad player corresponds to the load that a schedulable process places on the CPU of a given processor. A load is the total amount of execution time that a single schedulable process requires for execution of the code that it contains that applies to a particular event. An RTLoad player consists of a trigger (possibly) and a sequence of segments. This is described further below, but the discussion requires an example for clarification.
Assume that some real-time application consists of two schedulable processes: a client and a server. Assume further that the application consists of a single event in which the client calls the server for a service that it exports. The event is characterized by a trigger transferring control to the client process from the environment followed by: some code executing in the client, a remote procedure call from the client to the server, some code executing in the server, return of control to the client, some code executing in the client, and finally a return of control to the environment.
In this example, the client process defines a trigger. It is defined in the client SchedProcess via the TriggerDef property in the interface property list.
Additionally, each schedulable process has one or more segments. For simplicity we choose to define the segments as the chunks of code in each process that execute from the time control is given to the process until control is yielded. According to this scheme, the client process has two segments: the chunk of code that executes from when control is given to it by the environment until control is relinquished via the remote procedure call to the server, and the chunk of code that executes from when control is returned by the server until the client finishes execution. The server has only one segment: the chunk of code that executes from when control is given to it by the client via the remote procedure call until the server returns control back to the client. These segments are defined in each SchedProcess component via the SegmentDef property in the interface property list.
Lastly, each schedulable process defines an RTLoad player. The RTLoad player is specified with the Trigger and SegmentSet properties in the player property list. In the client
SchedProcess, the RTLoad player specifies a Trigger property containing the name of the trigger as it was defined in the TriggerDef property in the interface property list. It also specifies the SegmentSet property containing the names of the two segments that were defined in the SegmentDef properties in the interface. The RTLoad player in the server process specifies only a SegmentSet property with a single segment name in it, the segment that was defined with the SegmentDef property in the interface property list of the server. RTLoad players in server schedulable processes will typically never contain Trigger properties because they execute continuously. UniCon allows the system designer to define and specify triggers in server processes, however the period for such a trigger must be defined to be asynchronous.
To summarize, a load is described as the total execution time that a single SchedProcess component places on the CPU while executing code associated with a particular event, an event which executes in response to a specific trigger (occurring repeatedly according to a specific rate) in the environment. A load is described in UniCon by an RTLoad player, and there will typically be one or more RTLoad players defined in a schedulable process for each distinct event. RTLoad players are further specified by Trigger and SegmentSet properties.
NOTE: As mentioned above, segments are defined by designers based on structural and execution time properties of the underlying source code in order to facilitate certain types of analyses on sets of schedulable processes. One such type of analysis is a rate monotonic analysis (RMA). An RMA determines whether or not a set of events, scheduled in the environment according to a rate monotonic scheduling algorithm, will meet their deadlines. This analysis requires the rate information from the trigger of the event and the execution time information from all of the segments.
UniCon StreamIn and StreamOut players allow system designers to view streams of data as entities in a design, and to apply data typing semantics to these entities. System designers can, therefore, describe and reason about properties of streams in ways that are not possible by simply focusing on the source code implementations of the filters.
NOTE: UniCon generates a program that performs the initialization of a pipe and filter system at run-time. This program creates all of the pipes, and hooks each end of each pipe to the appropriate port in one of the filters in the system. Therefore, a filter should never open or close a port; it should assume that the open is done automatically prior to execution of the first line of code in the filter, and that the close is done automatically after the last line of code in the filter is executed. Therefore, when using the read system call to implement the input stream, no calls to open and close are necessary. When any of the formatted I/O routines are used, such as fgets and fgetc, no calls to fopen and fclose are necessary - however in this case one call to fdopen is required to wrap the input port up as a file pointer, the data structure expected by the formatted I/O routines.
The following are examples of StreamIn players in the implementation of a filter:
#include <stdio.h>
#define PORT_3 3
#define PORT_5 5
#define MAX_BUF_LEN 256
#define FOR_INPUT "r"
/* external data definitions required to support the code fragments below */
char input_buffer[MAX_BUF_LEN];
FILE *fp;
/* the following line of code performs stream input from port 3
using the read system call (inside the implementation of a filter) */
read (PORT_3, input_buffer, MAX_BUF_LEN);
/* the following lines of code performs stream input from port 5 using
the fgets formatted I/O routine (inside the implementation of a filter) */
fp = fdopen (PORT_5, FOR_INPUT); /* this call happens only once */
fgets (input_buffer, MAX_BUF_LEN, fp);
UniCon StreamIn and StreamOut players allow system designers to view streams of data as entities in a design, and to apply data typing semantics to these entities. System designers can, therefore, describe and reason about properties of streams in ways that are not possible by simply focusing on the source code implementations of the filters.
NOTE: UniCon generates a program that performs the initialization of a pipe and filter system at run-time. This program creates all of the pipes, and hooks each end of each pipe to the appropriate port in one of the filters in the system. Therefore, a filter should never open or close a port; it should assume that the open is done automatically prior to execution of the first line of code in the filter, and that the close is done automatically after the last line of code in the filter is executed. Therefore, when using the write system call to implement the output stream, no calls to open and close are necessary. When any of the formatted
I/O routines are used, such as fputc and fprintf, no calls to fopen and fclose are necessary - however in this case one call to fdopen is required to wrap the output port up as a file pointer, the data structure expected by the formatted I/O routines.
The following are examples of StreamOut players in the implementation of a filter:
#include <stdio.h>
#define PORT_3 3
#define PORT_5 5
#define MAX_BUF_LEN 256
#define FOR_OUTPUT "w"
#define ONE_CHARACTER 1
/* external data definitions required to support the code fragments below */
char output_buffer[MAX_BUF_LEN + 1];
FILE *fp;
int i;
/* in each example below the code writes from output_buffer until
the end of the buffer is reached, or the value `\0' is encountered */
/* the following lines of code perform stream output to port 3, one character at
a time, using the write system call (inside the implementation of a filter) */
for (i = 0;
output_buffer[i] != `\0' && i < MAX_BUF_LEN;
i++)
write (PORT_3, &output_buffer[i], ONE_CHARACTER);
/* the following lines of code perform stream output to port 5 using
the fprintf formatted I/O routine (inside the implementation of a filter) */
fp = fdopen (PORT_5, FOR_OUTPUT); /* this call happens only once */
output_buffer[MAX_BUF_LEN] = `\0';
fprintf (fp, "%s", output_buffer);
#include <stdio.h>
#include <fcntl.h>
#define MAX_BUF_LEN 256
#define ONE_CHARACTER 1
/* external data definitions required to support the code fragments below */
char output_buffer[MAX_BUF_LEN + 1];
int i, fd;
fd = open ("datafile", O_WRONLY);
for (i = 0;
output_buffer[i] != `\0' && i < MAX_BUF_LEN;
i++)
write (fd, &output_buffer[i++], ONE_CHARACTER);
close (fd);
Data can be written sequentially to a file in UniCon by a Filter component via a Pipe connection, and by a Module component via a FileIO connection. The WriteNext player type describes data being written to a file in either situation.
A composite implementation describes a configuration of components and connectors. This mechanism in UniCon allows system designers to progressively build larger subsystems from collections of smaller components or subsystems, or more abstract components from less abstract ones.
<component_implementation> :==
<primitive_implementation>
| <composite_implementation> <primitive_implementation> :==
IMPLEMENTATION IS
<property_list>
<variant_list>
END IMPLEMENTATION <variant> :== VARIANT <identifier> IN "<filespec>"
<property_list>
END <identifier> <composite_implementation> :==
IMPLEMENTATION IS
<property_list>
<composite_statement_list>
END IMPLEMENTATION <composite_statement> :==
<instantiation>
| <connection>
| <bind>
| <establish>
The implementation of a primitive component consists of a list of implementation variants. A variant is simply an alternative implementation. If a component has more than one variant defined in its primitive implementation, the specific variant to be used during system construction can be selected using the Variant property when the component is instantiated. A variant definition consists of a name, a pointer to a file in the operating system containing the source document that implements it, and (optionally) properties that further specify the variant. Below is an example of a primitive component implementation in UniCon. Assume that it is from the UniCon definition of a stack component. The source document referenced in the variant is a file containing C source code that implements a stack abstract data type:
IMPLEMENTATION ISNOTE: It is important to note that the UniCon compiler does not check that the source document is consistent with the component definition. For example, if there exists a player of type RoutineDef named "push" in the interface of the component, the UniCon compiler does not open up the file "stack.c" to check that there is a function definition named push defined in the source code. Consistency between the source document implementing a component and the component's interface is the responsibility of the system designer.
VARIANT stack IN "stack.c"
IMPLTYPE (Source)
END stack
END IMPLEMENTATION
<primitive_implementation> :==
IMPLEMENTATION IS
<property_list>
<variant_list>
END IMPLEMENTATION <variant> :==
VARIANT <identifier> IN "<filespec>"
<property_list>
END <identifier> <filespec> :==
a Unix filename, optionally prepended with a path
(e.g., /usr/gz/src/stack.c)
stack.cTypically, the system designer will specify the ImplType property in the <variant> property list. By default, if this property is not specified, the UniCon compiler assumes that the implementation type of the file named in the <filespec> is Source.
/usr/gz/src/stack.c
../../../src/stack.c
<variant> :==
VARIANT <identifier> IN "<filespec>"
<property_list>
END <identifier> <filespec> :==
a Unix filename, optionally prepended with a path
There are three types of information required in a composite implementation:
The configuration description is the set of instructions for hooking up the components and connectors. These instructions represent the connections in the implementation. Connections are accomplished with the CONNECT statement in UniCon, or with the role/player associations of an ESTABLISH (i.e., the AS statements).
The abstraction information is the specification of how the more abstract players in the interface of the component are implemented by the more concrete players in the composite implementation. These specifications are accomplished with the BIND statement. Every player in the interface of a component with a composite implementation must be mapped to one or more players in the implementation.
<composite_implementation> :==
IMPLEMENTATION IS
<property_list>
<composite_statement_list>
END IMPLEMENTATION <composite_statement> :==
<instantiation>
| <connection>
| <bind>
| <establish>
More than one instance of the same element (i.e., a component or a connector) may be used in the implementation of a single component, so UniCon definitions are considered to be templates from which instances can be created. In other words, a new instance of a component or connector is created from a given definition each time that component or connector is instantiated in a composite implementation. Each instantiation makes a copy of the component or connector described by the definition. When a component is instantiated, its players are instantiated as well. Similarly, when a connector is instantiated, its roles are instantiated too.
The first <identifier> in an <instantiation> is the name of the instance. All instance names in a composite implementation must be unique. The second
<identifier> is the name of the component or connector as it appears in its UniCon definition.
When instantiating a component, the keyword INTERFACE is required after the first <identifier>. When instantiating a connector, the keyword PROTOCOL is required.
When instantiating a component, all of the properties in the <interface> property list that further specify the component definition apply to the instantiated component as well. However, the designer may further constrain the component instantiation by specifying properties in the <instantiation> property list. The properties in the instantiation and the properties in the definition are merged into a single list of properties, which is then used to specify the instantiation. Any duplicate specifications of the same property are resolved according to the merge rule for that property. In the case of a merge rule value of REPLACE, the value of the instantiation property overrides that of the definition property. In the case of the merge rule value of ERROR, the value of the instantiation property is ignored and a warning is issued by the UniCon compiler (i.e., the value of the definition property is used). In the case of a merge rule value of MERGE, the values of the definition property are merged with the values of the instantiation property, and the instantiation property is used.
The semantics for the property list for connector instantiations are the same as for component instantiations except that it is the <protocol> property list that is merged with the instantiation property list instead of the <interface> property list.
NOTE: Not all properties that are legal in an <interface> property list of a component definition are legal in the <instantiation> property list, and vice versa. The same is true about connectors. Refer to the individual component type descriptions for more information about the properties that are legal in either of these property lists.
<instantiation> :==Syntactically, the END <identifier> statement is disallowed unless a property list has been specified in the <instantiation>.
USES <identifier> <interface_or_protocol> <identifier>
<optional_end_instantiation_syntax>
<interface_or_protocol> :==
INTERFACE | PROTOCOL
<optional_end_instantiation_syntax> :==
EMPTY
|
<property_list>
END <identifier>
USES RTClient INTERFACE Real_Time_Client
PRIORITY (10)
PROCESSOR ("TESTBED.XX.CMU.EDU")
ENTRYPOINT (client)
END RTClient USES RTServer INTERFACE Real_Time_Server
PRIORITY (11)
PROCESSOR ("TESTBED.XX.CMU.EDU")
RPCTYPEDEF (new_type; struct; 12)
RPCTYPESIN ("unicon.h")
END RTServer USES libmachrt INTERFACE Mach_Real_Time_Library USES RTM_RTScheduler PROTOCOL Real_Time_Scheduler
ALGORITHM (rate_monotonic)
PROCESSOR ("TESTBED.XX.CMU.EDU")
TRACE (RTClient.load.trigger,
RTClient.load.segment1,
RTServer.load.segment,
RTServer.load.segment2)
END RTM_RTScheduler
Each dot-separated pair of identifiers in a <connection> must name either a role or a player. There is no requirement regarding which one should come first. The
<connection> statement must name exactly one player and exactly one role; it may not name two players or two roles. If the pair of identifiers names a player, the first
<identifier> in the dot-separated pair must name a component instantiation, and the second must name a player in that component instantiation. If the pair of identifiers names a role, the first <identifier> must name a connector instantiation, and the second must name a role in that connector instantiation. In both cases, the component and connector named by the first <identifier> must have been instantiated in <instantiation> statements prior to the specification of the <connection> statement.
<connection> :==
CONNECT <identifier>.<identifier> TO
<identifier>.<identifier>
CONNECT RTClient.rt_load TO RTM_RTScheduler.load
CONNECT RTServer.rt_load TO RTM_RTScheduler.load
There are two forms of the <bind> statement, the simple bind, and the abstraction bind. A <simple_bind> is an association of a player in the interface with a single player of the same type in the implementation. The bind, in this case, can almost be thought of as an aliasing operation, rather than as an abstraction, since the players are of the same type. In a simple bind, no special support is required at UniCon compile time to translate the implementation mechanism of the player in the implementation into anything else since the player in the interface is of the same type.
In a simple bind, the player in the implementation must not be bound to any other player.
An <abstraction_bind> is an association of a player in the interface with a collection of players in the implementation (not necessarily having the same type as the interface player), or a single player in the implementation having a different type than that of the interface player. Abstraction binds often are necessary when the type of the player in the interface is a UniCon (or operating system supported) abstraction that is not directly supported by the programming language in the underlying implementation (e.g., a Unix stream of data, represented by the UniCon player types StreamIn and StreamOut, often implemented in a programming language by a series of calls to routines in environment-specific software libraries). In other cases, abstraction binds are necessary when a mapping from one player to another involves a type change (e.g., binding an RPCDef player in an interface to a RoutineDef player in an implementation). This type of mapping requires UniCon compile-time support to wrap up the implementation mechanism of the player in the implementation so that it becomes the implementation mechanism dictated by the type of the interface player.
The <abstraction_bind> requires the use of the MapsTo property in its property list to specify the list of players in the implementation being collectively bound to the interface player. The players in the MapsTo list must be players in component instantiations in the containing composite implementation that have not been previously bound.
NOTE: In both simple binds and abstraction binds, the implementation players must not be involved in a connection anywhere in the composite implementation, except if the player is of one of the following types:
<bind> :==A simple bind maps a player in the interface to only one player in the implementation. The player in the interface is specified by the single <identifier> following the keyword BIND. The player in the implementation is specified by a dot-separated pair of
<simple_bind>
|
<abstraction_bind> <simple_bind> :==
BIND <identifier> TO <identifier>.<identifier>
<optional_end_simple_bind_syntax> <optional_end_simple_bind_syntax> :==
EMPTY
| <property_list>
END <identifier> <abstraction_bind> :==
BIND <identifier> TO ABSTRACTION
<property_list>
END <identifier>
An abstraction bind maps a player in the interface to a collection of players in the implemenation, or a single player in the implementation having a different type. The player in the interface is specified by the <identifier> after the keyword BIND. The MapsTo property is used to specify the names of the implementation players. It is required to be in the property list; this means that the property list will never be empty for an abstraction bind and therefore the END <identifier> statement is always required. The
<identifier> in the END statement must be identical to the one after the word BIND, including the case of the letters.
In an abstraction bind, all of the players in the implementation that are being bound to the player in the interface must be specified in the MapsTo property, regardless of whether there is a single player or multiple players. Multiple specifications of the MapsTo property are allowed; UniCon interprets the collection as a single MapsTo property whose list of players is the union of the players in the lists of all of the MapsTo properties in the <bind> statement. The syntax of the value part of the MapsTo property is a semicolon-separated list of dot-separated pairs (or triples) of <identifier>s. The first
<identifier> in the pair or triple is the name of the component instantiation in which the player named by the second <identifier> is defined. If there exists a third
<identifier>, the second <identifier> must name a player of type PLBundle, and the third must name a Member player in that PLBundle.
BIND my_input_stream TO sort_filter.inputThe following are two examples of abstraction binds. Assume for the sake of the example that the players in the left-hand sides of the binds are of type StreamIn and StreamOut respectively, and that the players in the MapsTo property are of type RoutineCall:
RENAME
END my_input_stream BIND output_stream TO sort_filter.output_stream
BIND input TO ABSTRACTION
MAPSTO (libc.buffered_io.fgets)
END input BIND output TO ABSTRACTION
MAPSTO (libc.buffered_io.fprintf,
libc.buffered_io.fputc)
END output
The <establish> statement can simplify the task of instantiating a connector and associating players with its roles. In one statement, both tasks are accomplished. Whereas the syntax of the <connection> statement allows the associations between players and roles to appear in any order in a composite implementation, it is often helpful, however, to group together all the <connection> statements for one connector instantiation in one place. The syntax of the <establish> statement requires the designer to perform this grouping.
When grouping player/role associations in this manner, the connector instance name is purely local to the group. The <establish> statement eliminates this name by implicitly instantiating the connector. The connector is not given a name by which it can be referred to. Therefore, the roles of such a connector cannot be referred to outside the scope of the <establish> statement, forcing the designer to specify all player/role associations involving roles in the connector inside the given <establish> statement.
NOTE: The syntax of an <establish> is not symmetrical with respect to components; it cannot be used to instantiate components and associate roles with its players.
The property list of an <establish> statement has identical semantics to the property list of a connector <instantiation>. This means that properties that are legal and illegal in a property list of a connector <instantiation> of a given type are legal and illegal, respectively, in an <establish> of the same type of connector.
<establish> :==The <identifier> following the word ESTABLISH must name a connector definition, not an instantiation. This is because the <establish> statement performs the connector instantiation.
ESTABLISH <identifier> WITH
<establish_association_list>
<property_list>
END <identifier> <establish_association> :==
<identifier>.<identifier> AS <identifier>
The <establish_association_list> is a whitespace-separated list of <establish_association>s, and the list must contain at least one association. The <establish_association> looks remarkably similar to the association part of a <connection> statement, However, there are differences. Since there is no connector instantiation name, only one identifier is needed to identify the role in the association. Therefore, the dot-separated pair of <identifier>s before the word AS must name a player, and the single <identifier> following the AS must name a role in the connector named by the <identifier> following the word ESTABLISH. There is no pre-defined language limit to the number of <establish_association>s that can be present in the list.
ESTABLISH C-proc-call WITHThe above example assumes that the "libc" component contains a RoutineDef player named "malloc," and that the "my_main_program" component contains a RoutineCall player named "malloc." It also assumes that there exists a UniCon definition for a ProcedureCall connector named "C-proc-call."
my_main_program.malloc AS Caller
libc.malloc AS Definer
END C-proc-call
ESTABLISH Real_Time_Scheduler WITH
RTClient.load AS load
RTServer.load AS load
ALGORITHM (rate_monotonic)
PROCESSOR ("TESTBED.XX.CMU.EDU")
TRACE (RTClient.load.trigger,
RTClient.load.segment1,
RTServer.load.segment,
RTServer.load.segment2)
END Real_Time_Scheduler
A connector is an abstraction that represents the locus of definition for a relation among components. A connector mediates interactions among components; that is, it establishes the rules that govern component interactions and specifies any auxiliary implementation mechanisms required for realizing the interactions in the final system. A connector does not in general correspond individually to a compilation unit, but rather manifests itself in the final system as table entries, buffers, instructions to a linker, dynamic data structures, sequences of system calls embedded in source code, initialization parameters, etc.
Components interact with other components in a system in very distinct ways. These distinctions separate component interactions into classes, or types. A connector type captures the semantics of a particular class of interactions, assertions about that class, and the responsibilities and requirements that players in components must satisfy in an interaction from the class.
A connector has a specification, called a protocol, and an implementation. The protocol defines the allowable interactions among a collection of components and provides guarantees about those interactions.
The protocol defines:
Properties can be thought of as attributes of a definition which are used to further describe (i.e., specify) it. They are global assertions and constraints that apply to the definition as a whole. There are many places in UniCon where lists of properties are used to further specify a definition. In connector protocols, properties are used to specify information such as rules about timing or ordering.
Roles are the visible semantic units through which the connector mediates the interactions among components. Their types are primitive typing units used to identify the players that must cooperate in a successful interaction. It is through associations of players to roles that interactions of components are mediated by connectors. Roles define the kinds of interactions a connector can establish - the kinds of components that can interact, and the player types that are involved. Roles form the bulk of the protocol.
The implementation of a connector can be primitive or composite, however only primitive implementations are supported at present - we do not fully understand composite implementations in a connector yet. We are currently engaged in research to understand the implementation implications of first-class connectors; we hope to someday use the results to specify and generate connectors and their implementations from specifications in semi-formal notations. This will give us insight into the problem of how to specify connectors with composite and user-defined implementations.
<connector> :==The properties in a <property_list> are separated by whitespace (i.e., spaces, tabs, and carriage returns). A <property_list> can be empty (in many cases), or it may contain one or more properties. A <property> is a name-value pair that specifies an attribute, an assertion, or a constraint pertaining to a definition. It is used to further specify that definition. The syntax of a property is as follows:
CONNECTOR <identifier>
<protocol>
<connector_implementation>
END <identifier> <protocol> :==
PROTOCOL IS
TYPE <connector_type>
<property_list>
<role_list>
END PROTOCOL <connector_type> :==
DataAccess
| FileIO
| Pipe
| PLBundler
| ProcedureCall
| RemoteProcCall
| RTScheduler <connector_implementation> :==
IMPLEMENTATION IS
BUILTIN
END IMPLEMENTATION
<property> :== <name> <value> <name> :== <identifier> <value> :==The <name> in a property has significance in UniCon (i.e., it conveys meaningful information about a definition); the <value> associates specific information with the property name in the given context. Here, the term context refers to the given language element of the given type (e.g., player, of type GlobalDataDef). The <value> may be NULL, or it may contain a <value_part> enclosed in parentheses. The <value_part> has a syntax that is specific to the property, in a given context. There are eleven common syntaxes for the values of UniCon properties.
EMPTY
| (<value_part>)
The set of properties that can be legally specified in a property list depends on which definition the property list further specifies. For example, the set of properties legal for a protocol property list is different from the set that is legal for an interface property list.
The roles in a <role_list> are separated by whitespace.
A connector type expresses the designer's intention about the general class of connection to be provided by the connector; it restricts the numbers, types, and specifications of properties and roles. In particular some properties may or may not be required for a given connector type; additionally, some roles may require associations with players, some may not require them but constrain them if present, and some may be restricted to match players of certain types.
Every connector definition has a type. In UniCon, there are seven pre-defined connector types. The specification of the type comes as the first UniCon statement inside a connector protocol.
<connector_type> :==
DataAccess
| FileIO
| Pipe
| PLBundler
| ProcedureCall
| RemoteProcCall
| RTScheduler
The system designer may specify the Rename property in the property list of an instantiation of a DataAccess connector to bypass the semantic check on the names of the players (meaning that they do not have to be the same). In such a case, the UniCon compiler detects and repairs the name mismatch in the underlying system implementation.
If a name mismatch is detected in a DataAccess connection and the Rename property was specified, the UniCon compiler renames both identifiers in the source code of the implementation to a third, UniCon-generated identifier to resolve the mismatch. The renaming occurs at compile-time via macro names supplied with the -D option to invocations of the C language compiler.
The system designer may use the IOMode property in the protocol property list to constrain the behavior of the FileIO connector to readonly, readwrite, or writeonly operations.
The system designer, however, may choose to constrain the implementation mechanism for data flowing between two filters by specifying the PipeType property in the protocol property list. The implementation mechanism that UniCon chooses in this case is a Unix mechanism called a pipe. A pipe is literally a buffer used to store data temporarily as it flows from one process to another. Pipes can be implemented in two ways in Unix: as relatively small, unnamed buffers internal to the Unix operating system, or as named files that exist in the file system. The advantage of using files over internal Unix buffers is that they are much larger in size. For all intents and purposes, they are virtually unconstrained, whereas the internal Unix buffers for unnamed pipes are small (4K to 8K bytes, depending on the system). Advantages to using the internal buffers over files is that they are less cumbersome to implement and more efficient at run-time. The system designer can use the PipeType property to specify the use of Named (files) or Unnamed (internal buffers) pipes.
Unnamed pipes are nameless buffers in the Unix operating system environment that have a fixed size (usually between 4K and 8K bytes, depending on your system). Unnamed pipes implement the following semantics:
The call to pipe returns two file descriptors, one opened for reading and the other for writing, which provide access to the buffer. The buffer is circular. Data is written sequentially and follows previously written bytes. When the buffer fills up, the process performing the write operations is suspended until data is read from the pipe and space becomes available in the buffer. When this happens, the writing process is resumed by the operating system and continues writing. Data is read from the buffer sequentially, in the order that the bytes were written. If all of the data in the buffer has been read and the reading process wishes to continue reading data, then the reading process is suspended until more data is written to the pipe. When this happens, the reading process is resumed and continues. If the writing process closes its end of the pipe, the reading process will receive an EOF after all data has been read from the buffer. If the reading process closes its end of the pipe, the writing process will receive a SIGPIPE signal from the operating system.
Named pipes are implemented as named files in the operating system. The advantage to using a named pipe instead of an unnamed pipe is that the size of the buffer is virtually unlimited, since the size of a file in the file system is unlimited (for all practical purposes). The semantics of the behavior of a named pipe are identical to the semantics of an unnamed pipe, except that each process must open the file after it is created by the call to mkfifo or mknod (one must open it for reading, and the other for writing), and the writing process is suspended after opening it until another process opens it for reading (and vice versa).
The implementation of named and unnamed pipes in the final system is transparent to the system designer. The only difference between named and unnamed pipes discernible to the system designer is the behavior of a pipe at run-time - the amount of data that can flow through a pipe during system execution.
If establishing interaction between a Filter and a SeqFile, a Pipe connection is realized by a call to the open C library routine in the process implementing the Filter. This call opens the file implementing the SeqFile for reading and/or writing, as specified in the UniCon description. Again, the implementation of this mechanism in the final system is transparent to the system designer.
When a system is constructed of many pipes, filters, and files, the UniCon compiler creates an initialization routine that, at run-time, creates all the pipes and starts up the filters with all the proper port bindings. It handles arbitrary topologies correctly.
The PLBundler connector type supports the abstraction for connections of bundles of routine and data definitions, calls, and uses to other such bundles. Concretely, the PLBundler connector connects two or more PLBundle players. It abstracts from ProcedureCall and DataAccess connectors in the same way that the PLBundle player abstracts from the corresponding player definitions.
In the implementation of the abstraction, the UniCon compiler makes connections between individual Member players within the PLBundles. The compiler connects RoutineCall players in one PLBundle with their corresponding RoutineDef players in another PLBundle, and GlobalDataUse players in one PLBundle with their corresponding GlobalDataDef players in another.
The system designer can control how the UniCon compiler performs the connections by specifying the Match property in the property list of a PLBundler connector instantiation. The value of the Match property has two distinct forms. The first form consists simply of the word "by_name". This is the default value if the Match property is not specified in a PLBundler connection. If matching by name is specified, the compiler looks through every player in the PLBundler connection and attempts to find a match, based on player names, for every GlobalDataUse and RoutineCall player that it encounters. The second form of the Match property value is a comma-separated list of relations. A relation is a pair of player names, separated by a comma and enclosed in parentheses, that represent a connection to be made. The names in the relations must identify players or members of players in the PLBundler connection in which the Match property appears. With this form, the names of the players in the relations do not have to match. The name mismatches in this case will be resolved in the underlying system implementation.
If the value of the Match property is used to specify relations (i.e., it has the second form, as described above), then any RoutineCall or GlobalDataUse player in a PLBundler connection that is not connected explicitly in a relation will be automatically matched by name with a corresponding RoutineDef or GlobalDataDef player in the connection.
When performing matching by name, the UniCon compiler will report all instances of
GlobalDataUse and RoutineCall players that do not have corresponding matches in the connection.
The system designer may specify the Rename property in the property list of an instantiation of a ProcedureCall connector to bypass the semantic check on the names of the players (meaning that they do not have to be the same). In such a case, the UniCon compiler detects and repairs such name mismatches in the underlying system implementation.
If a name mismatch is detected in a ProcedureCall connection and the Rename property is specified, the UniCon compiler renames both identifiers in the source code of the implementation to a third, UniCon-generated identifier to resolve the mismatch. The renaming occurs at compile-time via macro names supplied with the -D option to invocations of the C language compiler.
The system designer may specify the Rename property in the property list of an instantiation of a RemoteProcCall connector to bypass the semantic check on the names of the players (meaning that they do not have to be the same). In such a case, the UniCon compiler detects and repairs such name mismatches in the underlying system implementation.
The underlying mechanism that implements both the Mach and RPCGen message passing facilities is essentially the same. Both assume that applications implement remote procedure and function calls as local routine calls, and remote function and procedure definitions as local routine definitions. This model requires that processes making remote procedure calls contain source code that intercepts the local routine calls implementing the RPCs, builds a message containing the arguments of the call, and hands the message off to the operating system for delivery to the process containing the routine definition. It also requires that processes exporting remote routine definitions contain source code that extracts the arguments from service request messages from calling processes, makes the local routine call, builds a return message containing any results from the routine call, and hands the return message off to the operating system for delivery to the calling process. Generating this "glue code" in each process can be time consuming, intricate, and error prone. Therefore, both the Mach facility and the RPCGen facility provide a code generator for generating this code from their own brand of interface definition language (IDL). The Mach IDL is called the Mach Interface Generator (MIG) language. The RPCGen language is called Remote Procedure Call Language (RPCL).
UniCon automatically generates the IDL specification of the RPC interface between two Process or SchedProcess components directly from the UniCon definitions of the components. Then, at system construction time, the correct generation tool (i.e., MIG or RPCGen) produces the source for the "glue code" from the IDL specification, and the code is compiled and linked into the correct processes. The system designer is spared having to know the details of implementing the glue code directly.
In addition to the glue code required between processes making RPCs, each communication model requires processes making RPCs to communicate once during system initialization with a "name" server that is executing in the operating system environment. This name server is the mechanism by which server processes register their services (i.e., the remote procedure definitions) for general use, and by which client processes locate the services they will request. UniCon generates the initialization code in each process that is responsible for registering or locating services at run-time. This code runs as the "main" program in the environment; it registers/locates all of the services exported/imported by the Process or SchedProcess component implementing the process, and then calls the local routine definition that implements the main functionality of the component (recall that Process and SchedProcess components are implemented as function or procedure definitions).
The system designer need not know any details whatsoever regarding the implementation of the underlying mechanisms for RemoteProcCall connections. Indeed, remote procedure calls and definitions in the underlying implementation are identical to local routine calls and definitions. There literally is no difference. UniCon does all of the work in turning them into remote procedure calls and definitions. Similarly, at the UniCon architecture description level, RemoteProcCall connectors are nearly identical to ProcedureCall connectors in how they are specified, with the only exception being that RemoteProcCall connections require the specification of the IDLType property.
If a name mismatch is detected in a RemoteProcCall connection and the Rename property is specified, the UniCon compiler renames both identifiers in the source code of the implementation to a third, UniCon-generated identifier to resolve the mismatch. The renaming occurs at compile-time via macro names supplied with the -D option to invocations of the C language compiler.
The RTScheduler connector type corresponds to the mediation of this interaction between real-time processes in contention for the CPU resource. It requires an operating system with appropriate real-time capabilities; the UniCon compiler currently supports implementation mechanisms for the Real-Time Mach operating system. The processor resource in the Real-Time Mach kernel can be managed according to one of six different algorithms: the rate monotonic, deadline monotonic, earliest deadline first, fifo fixed priority, round robin fixed priority, or timesharing algorithm. The system designer can specify the particular algorithm with the Algorithm property in an RTScheduler connector instantiation. The system designer can also specify the particular processor to be managed with the Processor property in the connector instantiation.
If the system designer specifies the rate monotonic scheduling algorithm for the processor, UniCon will automatically prepare some input for a rate monotonic analysis (RMA) tool that will determine if the set of real-time processes in the RTScheduler connection will all meet their deadlines (i.e., complete execution before they must run again). To facilitate this, the system designer is required to specify one Trace property in the connector instantiation for each event that is being managed in the RTScheduler connection. The RMA is performed on events rather than processes; the schedulability of the events implies the schedulability of the processes.
An event in UniCon is a complete thread of execution in a real-time system that can span multiple processes because of the presence of RPC calls. An event describes execution of a thread from the time control is first given to a client process by the operating system via a trigger (the mechanism by which control is transferred; e.g., an timer interrupt), until control is returned to the operating system as a result of the client finishing its work. An event consists of a trigger, followed by a specific sequence of smaller chunks of code called segments. Segments are atomic blocks of work within a given process, atomic meaning that the thread of control within the block is continuous (i.e., not transferred to another process). The sequence of segments in an event are executed in an order that is defined by the thread of control in the application. For example, a client process gets activated by the operating system. The client does some work (a segment), and then, perhaps, makes an RPC call to a server process. The server process does some work (another segment) and either returns to the client, or makes an RPC call itself. This continues until control is eventually returned back to the client, which does more work (another segment). Work proceeds this way until control is returned back to the operating system.
There can be potentially many events in a single RTScheduler connection. There will be one for each trigger that transfers control to a client process in the set of application processes whose interaction is mitigated by the RTScheduler connector. The system designer must specify a Trace property for each one of these events. The value in the Trace property is a list of names. The first names a trigger, and the rest name the segments, in the proper sequence. Each name consists of three dot-separated <identifier>s. The third names the trigger or segment, the second names the RTLoad player in which the trigger/segment is defined, and the first names the component instantiation in which the RTLoad player is defined.
In addition to the process initialization code, the UniCon compiler generates a program executable that initializes the real-time scheduler in the operating system environment prior to the creation of the processes in the application.
Lastly, the UniCon compiler generates a Unix shell script that invokes the scheduler initialization program and starts the application processes at run-time, in the correct order (i.e., all processes providing services via remote procedure call are started before the processes that require these services).
All of this is done automatically. No additional support is required to be implemented by the system designer in order to realize an RTScheduler connector.
The RMA tool is an Excel spreadsheet program that runs on IBM PCs, so for now the invocation of the tool is carried out manually.
A role definition contains:
Roles are further specified by properties in the property list of the role definition. Properties can be thought of as attributes of a definition which are used to further describe (i.e., specify) it. For roles, they are attributes that apply to the role as a whole. There are many places in UniCon where lists of properties are used to further specify a definition. In roles, properties are used to specify information such as the legal component/player type combinations of players that can be associated with a given role in a connection, and the minimum and maximum number of player/role associations involving a given role in a connection.
<role> :==Roles can be defined with and without properties. In the latter case, for example, the role definition appears as follows:
ROLE <identifier> IS <role_type>
<optional_end_role_syntax> <role_type> :==
Caller
| Definer
| Load
| Participant
| Readee
| Reader
| Sink
| Source
| User
| Writee
| Writer <optional_end_role_syntax> :==
EMPTY
| <property_list>
END <identifier>
ROLE Unix_Pipe_Input_End IS SourceNotice that if no properties are specified in a role definition (i.e., the property list is empty), the END statement is omitted.
In the former case, the role definition appears as follows:
ROLE Unix_Pipe_Input_End IS SourceThe END statement is required in this case. The <identifier> in the END statement must be identical to the <identifier> following the keyword ROLE, including the case of the letters.
MINCONNS (1)
MAXCONNS (1)
ACCEPT (Filter.StreamOut, General.StreamOut,
SeqFile.ReadNext, General.ReadNext)
END Unix_Pipe_Input_End
Every role definition has a type. In UniCon, there are eleven pre-defined role types. The specification of the type appears immediately following the keyword IS in a UniCon role definition.
<role_type> :==
Caller
| Definer
| Load
| Participant
| Readee
| Reader
| Sink
| Source
| User
| Writee
| Writer
In a ProcedureCall connection there must be at least one caller (i.e., one Caller role/player association), however there may be infinitely many. The same applies in the case of a RemoteProcCall connection.
In ProcedureCall, DataAccess, and RemoteProcCall connections there must be at most one definition (i.e., one Definer role/player association).
There must always be at least two, but there may be potentially infinite, participants in a PLBundler connection.
In a FileIO connection whose IOMode is either ReadOnly or ReadWrite, there must be exactly one data block to be read next from the sequential file (i.e., one Readee role/player association). This data block may be NULL.
In a FileIO connection whose IOMode is either ReadOnly or ReadWrite, there must be at least one reader (i.e., one Reader role/player association) of sequential file data blocks, however, there may be infinitely many.
In a Pipe connection there must be exactly one stream of data being extracted from the pipe or written to the file (i.e., one Sink role/player association).
In a Pipe connection there must be exactly one stream of data being written to the pipe or being read from the file (i.e., one Source role/player association).
In a DataAccess connection there must be at least one reference to a data object (i.e., one User role/player association), however there may be infinitely many.
In a FileIO connection whose IOMode is either WriteOnly or ReadWrite, there must be exactly one data block that gets written next to the sequential file (i.e., one Writee
role/player association).
In a FileIO connection whose IOMode is either WriteOnly or ReadWrite, there must be at least one writer (i.e., one Writer role/player association) of sequential file data blocks, however, there may be infinitely many.
Refer to the UniCon Implementor's Guide for more details on the implementations of
UniCon connector abstractions.
<connector_implementation> :==
IMPLEMENTATION IS
BUILTIN
END IMPLEMENTATION
<interface> <component_implementation> <instantiation> <protocol> <establish> <role> <player> <variant> <abstraction_bind> <simple_bind>All properties in all <property_list>s in UniCon are separated by whitespace (i.e., spaces, tabs, and carriage returns). A <property_list> can be empty (in many cases), or it may contain one or more properties.
The syntax of a property is as follows:
<property> :== <name> <value> <name> :== <identifier> <value> :==The <name> in a property has significance in UniCon (i.e., it conveys meaningful information about a definition). There are 35 pre-defined property names in the UniCon language, and the property list associated with each language element listed above accepts only a certain subset of the pre-defined properties. Additionally, a given pre-defined property may be accepted in the property lists of more than one distinct UniCon language element. All property lists accept user-defined properties.
EMPTY
| (<value_part>)
The <value> in a property associates specific information with the given property name in the given context. Here, the term context refers to the given language element of the given type (e.g., player, of type GlobalDataDef). The <value> may be NULL, or it may contain a <value_part> enclosed in parentheses. The <value_part> has a syntax that is specific to the property, in a given context. There are eleven common syntaxes for the values of UniCon properties - the syntaxes for all 35 pre-defined UniCon properties in all contexts fall into these eleven categories.
Examples of properties with and without <value_part>s are shown below:
LIBRARY
SIGNATURE ("int")
Each pre-defined UniCon property has a required rule associated with it. The property is either always required to be specified in the property list for which it is designated, or it is optional. If it is required to be present in a property list and the system designer does not specify it, the UniCon compiler will generate an error message and prevent system construction. If a property is optional in its designated list, the UniCon compiler will provide a default value.Each pre-defined UniCon property has a merge rule associated with it. The merge rule tells the UniCon compiler how to resolve duplicate property specifications in a single property lists. For example, what does the compiler do when it encounters two Accept property specifications in the same property list? Does it throw out the first set of values, or the second? Or does it use the union of both lists? It depends on the value of the merge rule; the rule can take on one of the following three values: ERROR, REPLACE, or MERGE.
A merge rule value of ERROR instructs the UniCon compiler to ignore each subsequent specification of the given property in the same list, and treat it as an error (Warning). A merge rule value of REPLACE instructs the UniCon compiler to accept the last specification of a given property in the same list, and treat each prior specification as an error (Warning). A merge rule value of MERGE instructs the UniCon compiler to take the union of the elements in the value portions of all specifications of a given property in the same list.
NOTE: When instantiating a component in UniCon, the value part of a given property in a component <instantiation> property list overrides the value part of the same property in the <interface> property list in the component definition if the merge rule is REPLACE. The one in the <interface> overrides the one in the <instantiation> if the merge rule is ERROR. The values in both lists are merged together into one specification if the merge rule is MERGE. The same semantics apply for connector definitions and instantiations as well.
Each role type in UniCon has a default accept list specifying a distinct set of type combinations. The Accept property can only be used in a role definition to restrict this default set; the default set cannot be extended in any way.
The default values depend upon the <role_type> in the <role> definition. Please refer to the descriptions of the UniCon role types in this manual for information on the default values for the Accept property in each case.
More than one specification of the Accept property in a single property list is treated as a single specification with the union of all values in all of the specifications.
ROLE Unix_Pipe_Input_End IS Source
ACCEPT (General.ReadNext, SeqFile.ReadNext,
General.StreamOut, Filter.StreamOut)
END Unix_Pipe_Input_End
An RTScheduler connector governs the interaction of SchedProcess components contending for a single processor resource in a real-time environment. This connector corresponds to a scheduler in the kernel of a real-time operating system. The scheduler's responsibility is to provide each schedulable process in a system of such processes with access to the processor resource. This is done according to some algorithm.
In the Real-Time Mach operating system (the target evironment for instantiations of RTScheduler connectors in UniCon systems) there are six distinct real-time scheduling algorithms:
If the Algorithm property in an RTScheduler connector instantiation specifies the rate_monotonic scheduling algorithm, the UniCon compiler can facilitate a Rate Monotonic Analysis (RMA) on the events described by the Trace properties in that connector instantiation (see the Description for the Trace property for more information on events). For a RMA, the UniCon compiler collects the segment names, their execution times, and the period information for each event associated with a given processor; it also collects the priority information for each schedulable process associated with that same processor. It then formats this information and prepares a file containing input to a RMA tool which performs the analysis. This tool is invoked manually and reports a simple answer to indicate whether or not all of the events will meet their respective deadlines. The file that UniCon prepares with the input to the RMA tool is named RMA_<processor_name>, where <processor_name> is taken from the Processor property associated with the RTScheduler connector instantiation in which the Trace properties are specified. An RMA input file is generated by the UniCon compiler for each RTScheduler connector instantiation whose Algorithm property specifies a rate_monotonic scheduler.
The default value for the Algorithm property is rate_monotonic.
Subsequent specifications of the Algorithm property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
ESTABLISH RTM-RealTime-Sched WITH
client.load AS load
server.load AS load
PROCESSOR (TESTBED.XX.CMU.EDU)
ALGORITHM (rate_monotonic)
TRACE (client.load.trigger,
client.load.segment1,
server.load.segment,
client.load.segment2)
END RTM-RealTime-Sched
There is no default value for the BuildOption property.
More than one specification of the BuildOption property in a single property list is treated as a single specification with the union of all values in all of the specifications.
VARIANT My_Stack IN "stack.c"An alternative, yet equivalent definition:
IMPLTYPE (Source)
BUILDOPTION ("+define=DEBUG_ENABLE")
BUILDOPTION ("+gnu") /* specifies "gcc" */
END My_Stack
VARIANT My_Stack IN "stack.c"
IMPLTYPE (Source)
BUILDOPTION ("+define=DEBUG_ENABLE", "+gnu")
END My_Stack
The value of the EntryPoint property is the name of the entrypoint in the implementation that is to receive control at run-time after initialization of the process containing the source code is completed. For example, in UniCon the system designer must implement a Process component as a function or a collection of functions in some programming language (e.g., the C language). UniCon generates the main program that initializes the Process component at run-time and then transfers control to the function specified in the EntryPoint property.
For Computation and Module components, UniCon ignores the EntryPoint property for now. This is because Unix processes generated from Module or Computation components need no additional run-time initialization, and therefore UniCon does not need to produce a main program for them. For these components, the entrypoint is assumed to be "main" for now, since UniCon only handles implementations in the C language to date.
For Process and SchedProcess components, the value of the EntryPoint property has a different set of semantics for client processes and server processes. In UniCon, a process is a client process if it makes remote routine calls and does not export remote routine definitions (services) to be called. A process is a server process if it exports remote routine definitions. Processes not involved in remote procedure calls have the same semantics as client processes.
For client processes, the value of the EntryPoint property must name the function that will get control once the main program generated by the UniCon compiler to initialize the Unix process at run-time finishes its initialization. The name supplied as the entrypoint can be "main," but the UniCon compiler will rename this to some generated name when building the code for the process. This is because the main program it generates to do the initialization must be named "main."
For server processes, the value of the EntryPoint property must name a "worker function" that performs application-specific work not directly related to the task of servicing requests from clients. For server processes, the UniCon compiler generates a "main" program for initialization, and it also generates a function that polls for messages from clients and calls the appropriate functions that provide requested services. This polling function gets control from the "main" program after initialization and does not return. The function named by the value of the EntryPoint property will be invoked to perform work in parallel with the polling function.
In the Mach and Real-Time Mach environments, the worker function is turned into a light-weight process that runs in parallel with the light-weight process that polls for service requests. In these environments, the function must be designed so that it never returns. If it does, then the light-weight process will never be rescheduled to run. In environments that support RPCGen such as SunOS, the function is called periodically from the heavyweight process that polls for the service requests. The worker function is called after each service request, and once every 10 milliseconds when no service requests are made. In this environment, the worker function must be designed to return. If it does not, then the polling function will never regain control and the server will appear to "hang."
The default value for the EntryPoint property for Process and SchedProcess client components is "main." Client also refers to a component that does not make RPCs.
There is no default value for the EntryPoint property for Process and SchedProcess server components.
There is no default value for the EntryPoint property for Module and Computation components.
Subsequent specifications of the EntryPoint property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
If an EntryPoint property is specified, but none of the files implementing the component are source files, then the check is abandoned. In this case, the UniCon compiler will attempt to build the executable for the process, but no guarantees are made about the viability of such an executable at run-time.
USES Client INTERFACE RT_Client
PRIORITY (10)
ENTRYPOINT (client)
END Client
A RemoteProcCall connector governs the interaction between SchedProcess or Process components performing remote procedure calls. The realization of this connector in the system at run-time is usually some message passing mechanism.
UniCon supports two different models of communication for RPC connections: the Mach InterProcess Communication (IPC) facility and the RPCGen remote procedure call facility. Both models use message passing as the underlying mechanism for transmitting data between processes.
Both of these models of communication are nearly identical. Both have a specification language in which the services exported by a server process are specified. Both have a compiler for the specification language that compiles the specifications of the services into "glue code." The glue code gets compiled and linked into both client and server processes; it facilitates the creation, passing, and decoding of messages using the arguments and return values of the routine calls. Lastly, both models make use of a "name server" in the run-time environment. The name server is an independent process running on the target machine that facilitates the message passing between clients and servers. Servers register their services with the name server, and clients use the name server to "look up" the services they need.
The system designer uses the IDLType property to specify the underlying model of communication for each RPC connection. For the Mach IPC facility, the system designer specifies "mach" as the value in the IDLType property. For the RPCGen facility, the designer specifies "rpcl":
The default value for the IDLType property is rpcl.
Subsequent specifications of the IDLType property in a single property list are reported as errors. The UniCon compiler uses the first of the duplicate IDLType properties it encounters.
ESTABLISH RTM-RemoteProc-Call WITH
client.timeget AS caller
server.timeget AS definer
IDLTYPE (mach)
END RTM-RemoteProc-Call
For example, if the file is a Source file, the UniCon compiler assumes it contains source code in a conventional programming language and instructs Odin to compile it with the appropriate programming language compiler. If it is Object code, the UniCon compiler instructs Odin to simply link it with the other code into the executable that it is building. If the external source file is an executable, the UniCon compiler generates no construction instructions, since it has already been built.
The following forms of external source are recognized in UniCon:
The default value for the ImplType property is "source".
Subsequent specifications of the ImplType property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
VARIANT My_Stack IN "stack.c"
IMPLTYPE (Source)
END My_Stack VARIANT My_Stack IN "stack.o"
IMPLTYPE (Object)
END My_Stack VARIANT My_Stack IN "stack.a"
IMPLTYPE (ObjectLibrary)
END My_Stack
Only one InitActuals property is accepted by the UniCon compiler for a given executable. Therefore, if an InitActuals property is specified in both the <component_implementation> and the <variant> property lists in the primitive implementation of a component, the UniCon compiler accepts only the one in the <component_implementation> property list.
The InitActuals property is typically specified in the <component_implementation> property list when the component has a composite implementation. It is otherwise generally found in the <variant> property list.
There is no default value for the InitActuals property.
Subsequent specifications of the InitActuals property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
VARIANT My_Sort_Filter IN "sort"The second example is embedded in the <component_implementation> property list of a composite implementation:
IMPLTYPE (Executable)
INITACTUALS ("-f")
END My_Sort_Filter
IMPLEMENTATION IS
INITACTUALS ("-f")
USES main INTERFACE Sort_Main
USES sort INTERFACE Sort
USES libc INTERFACE Libc
BIND input TO ABSTRACTION
MAPSTO (main.libc.fgets)
END input
BIND output TO ABSTRACTION
MAPSTO (main.libc.fprintf)
END output
ESTABLISH C-PLBundler WITH
main.sort_routines AS participant
sort.sort_routines AS participant
END C-PLBundler
ESTABLISH C-PLBundler WITH
main.libc AS participant
libc.libc AS participant
END C-PLBundler
END IMPLEMENTATION
The InitRoutine property is used to specify the entrypoint (i.e., name) of a parameterless function, supplied in the code of the implementation, that will perform application-specific initialization in a process or schedulable process before control is handed from the "main" program to the function(s) implementing the process itself. The UniCon compiler will generate the code to call this routine at the appropriate time before control is handed to the function(s) implementing the process.
If this property is present, a parameterless function call will be generated in the implementation. If no corresponding function definition is supplied, the system construction will fail.
There is no default value for the InitRoutine property.
Subsequent specifications of the InitRoutine property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
If an InitRoutine property is specified, but none of the files implementing the component are source files, then the check is abandoned. In this case, the UniCon compiler will attempt to build the executable for the process, but no guarantees are made about the viability of such an executable at run-time.
USES Client INTERFACE RT_Client
PRIORITY (10)
ENTRYPOINT (client)
INITROUTINE (initialize_client)
END Client
The string variables that are defined with the InstFormals property have no use in a UniCon definition to date. In the future, UniCon may provide a means for obtaining the string values and semantics for using them.
There is no default value for the InstFormals property.
More than one specification of the InstFormals property in a single property list is treated as a single specification with the union of all values in all of the specifications.
INTERFACE IS
TYPE Module
INSTFORMALS (parameter1 = "my string",
parameter2 = "this string" &
" is broken over a line",
parameter3 = NODEFAULT)
PLAYER timeget IS RoutineCall
SIGNATURE ("new_type"; "void")
END timeget
PLAYER timeshow IS RoutineCall
SIGNATURE (; "void")
END timeshow
END INTERFACE
The specification of IOMode in a FileIO connector constrains the types of the roles that can be defined in the connector. Readonly FileIO connectors must define one role each of the types Readee and Reader. Readwrite connectors must define one role each of the following types: Readee, Reader, Writee, and Writer. Writeonly connectors must define one role each of the types Writee and Writer.
WARNING: The IOMode property can be specified in a connector instantiation, but this is not recommended because the role types are fixed in the connector definition, not the instantiation. The UniCon compiler will report the appropriate errors if the IOMode specification in the instantiation does not match the set of role types in the definition.
The default value for the IOMode property is readwrite.
Subsequent specifications of the IOMode property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
CONNECTOR Unix_FileIO
PROTOCOL IS
TYPE FileIO
IOMODE (readonly)
ROLE Readee IS Readee
ROLE Reader IS Reader
END PROTOCOL
IMPLEMENTATION IS
BUILTIN
END IMPLEMENTATION
END Unix_FileIO
The property may be specified in the definition or instantiation of one of these components, although it makes more sense to further specify a definition of a component - whether or not a collection of routines acts like a library is usually an attribute of the source code implementation that does not change from instantiation to instantiation.
The Library property also instructs UniCon to create a Unix archive file from the constituent source and object files when the property is applied to a component with a composite implementation. This is effectively an intermediate build step, where the UniCon compiler (1) collects the sources and objects that implement the individual components in the configuration in a composite implementation, (2) compiles the sources, and (3) adds all of the objects to a Unix archive file. The UniCon compiler uses the archive file in later parts of the build process and leaves the file around for the system designer as an artifact of the build process.
There is no default value for the Library property.
For the Library property, the merge rule has no effect since there is no value part.
INTERFACE IS
TYPE Module
LIBRARY
PLAYER timeget IS RoutineDef
SIGNATURE ("new_type"; "void")
END timeget
PLAYER timeshow IS RoutineDef
SIGNATURE (; "void")
END timeshow
END INTERFACE
The MapsTo property associates the set of internal players specified in its value part to the external player named in the abstraction bind. An external player is a player exposed in the interface of a component, and an internal player is one that is exposed in the interface of another component that has been instantiated inside the implementation of a component. A bind, therefore, maps a player in the interface of a component to one or more players in its composite implementation.
There are two forms of the <bind> statement: the simple bind, and the abstraction bind. When a player in the interface is implemented by a single player in the implementation having the same type, then the system designer uses the simple bind syntax. This form of the bind is a simple aliasing operation.
When the player in the interface is implemented by a collection of players in the implementation, or a single player with a different type, then the system designer uses the abstraction bind syntax. An abstraction bind does not represent a simple aliasing operation. It is an association of players in the implementation to a player in the interface. The list of players in the MapsTo property represents an abstraction of some form. For example, the players may collectively represent some abstract operation as a group - an operation that is accessible via a single player in the interface of the component. In other cases, the player(s) in the MapsTo list may represent a concrete implementation of a more abstract behavior of a player in the interface (i.e., a behavior not directly supported in a given programming language). For example, a stream player in the interface of a Filter component that has a composite implementation may actually be implemented by procedure call players in the implementation (e.g., to routines like fgets or fprintf in the C language). The behavior of a stream in the interface is actually an abstraction implemented by procedure calls in the implementation in this case.
The MapsTo property is required in an abstraction bind. It is an integral part of the specification of the bind itself.
More than one specification of the MapsTo property in a single property list is treated as a single specification with the union of all values in all of the specifications.
IMPLEMENTATION IS
USES main INTERFACE Sort_Main
USES sort INTERFACE Sort
USES libc INTERFACE Libc
BIND input TO ABSTRACTION
MAPSTO (main.libc.fgets)
END input
BIND output TO ABSTRACTION
MAPSTO (main.libc.fprintf)
END output
ESTABLISH C-PLBundler WITH
main.sort_routines AS participant
sort.sort_routines AS participant
END C-PLBundler
ESTABLISH C-PLBundler WITH
main.libc AS participant
libc.libc AS participant
END C-PLBundler
END IMPLEMENTATION
The Match property for an abstraction bind is used when the external player is of type PLBundle. Specifying the Match property in any other type of abstraction bind will be reported as an error by the UniCon compiler. This property specifies how the Member players of the PLBundle are matched to the players in the value of the MapsTo property. This matching is necessary because the UniCon compiler must know ultimately how each player in the interface of a component is implemented. This applies to the Member players of external PLBundle players as well. The Match property is used to provide the UniCon compiler with the mapping of the Member players to the MapsTo list entries.
In the abstraction bind, there are two forms of syntax for the value of the Match property. The first form is simply the keyword by_name. This is actually the default value if no Match property is specified. In this case, the UniCon compiler generates a list containing the players in the PLBundle. It then generates a list containing the players in the MapsTo property. If a player in a MapsTo property is a PLBundle, then the Member players are added to the list rather than the player itself. The UniCon compiler then processes the list of players from the MapsTo property. For each of these players, it searches the list of Member players from the external PLBundle player for a matching player (by looking for a name match). If it finds a match, the UniCon compiler attempts to bind the two players. If no match is found, the compiler does not bind them. In both cases, the compiler continues until each player in the MapsTo list has been examined. In the end, if there are any Member players in the external PLBundle that have not been bound, the UniCon compiler will report errors.
The second form of syntax for the value of the Match property for an abstraction bind is a list of relations. A relation is a comma-separated pair of player names. Each pair represents a specific match that the system designer has explicitly specified. The names of the players in each pair need not match. With this form of syntax for the Match property, the UniCon compiler binds each pair of players, regardless of whether or not the player names match. Then, it uses the Match (by_name) algorithm outlined above to form binds from the remaining players not specified in explicit matches.
The Match property for a PLBundler connector instantiation has the same two forms of value syntax as those of the abstraction bind: the value by_name, and a list of relations. The algorithm for matching with relations in a PLBundler connector instantiation is identical to the algorithm for matching with relations in an abstraction bind. The UniCon compiler treats each pair as an attempt at a connection with the appropriate connector type. For example, if a relation specifies a pair of players where one is of type RoutineDef and the other is of type RoutineCall, then the UniCon compiler will attempt to connect them with a ProcedureCall connector. The compiler will report appropriate error messages for any pairs that do not specify well-formed connections. Name mismatches are allowed in relations; the UniCon compiler will fix these mismatches at runtime by renaming both identifiers in the source code to a third, common identifier. This is done using C language macros, specified on the command line with the -D option during the source code compile of each file involved in the connection. When explicit matching using relations is completed, the
UniCon compiler attempts to create matches from the remaining players in the connection using the Match (by_name) algorithm for PLBundler connector instantiations outlined below.
For PLBundler connector instantations, the Match (by_name) algorithm is slightly different than the one for abstraction binds. This is because there may potentially be more than two sets of players involved that get matched one-for-one. There can potentially be many more than two participants in a PLBundler connection. Strewn throughout these participants, for example, may be many calls or uses of a particular routine or global data definition. A match must be made from these mini-sets of players that all share the same name. The algorithm is further compilated by the fact that not all participants in a PLBundler connection are PLBundle players; they can also be GlobalDataDef, GlobalDataUse,
RoutineCall, and RoutineDef players.
The algorithm for matching by name in a PLBundler connector instantiation proceeds as follows. Every Member player of every PLBundle participant, and every other participant in the PLBundler connection, is placed in a list. This list of players is sorted by name. The UniCon compiler then attempts to make a connection from each set of players with the same name from that list. The compiler will report errors associated with malformed connections. For example, each connection must have exactly one definition (i.e., RoutineDef or GlobalDataDef player) and at least one corresponding RoutineCall or GlobalDataUse player. Each set must also exclusively contain players of compatible types; all players must correspond to routine players, or they all must correspond to global data players.
The default value of the Match property is the keyword: by_name.
More than one specification of the Match property in a single property list is treated as a single specification with the union of all values in all of the specifications. However, if more than one Match property is specified, they must all have the same value syntax. In other words, they must all have the value by_name, or they must all be lists of relations.
ESTABLISH C-PLBundler WITHThe following is equivalent to the above example:
main.sort_routines AS participant
sort.sort_routines AS participant
/* with no Match attribute,
Match (by_name) is assumed */
END C-PLBundler
ESTABLISH C-PLBundler WITHNow, assume that main.sort_routines is a PLBundle player containing the following RoutineCall playerts: bubble, quick, merge, and sort. Also, assume that sort.sort_routines is a PLBundle player containing corresponding RoutineDef players with the following names: bubble_sort, quick_sort, merge_sort, and sort.
main.sort_routines AS participant
sort.sort_routines AS participant
MATCH (by_name)
END C-PLBundler
ESTABLISH C-PLBundler WITHIn the above example, the UniCon compiler uses the Match (by_name) algorithm to form a ProcedureCall connection with the sort routines from each PLBundle player.
main.sort_routines AS participant
sort.sort_routines AS participant
MATCH ((main.sort_routines.bubble,
sort.sort_routines.bubble_sort),
(main.sort_routines.quick,
sort.sort_routines.quick_sort),
(main.sort_routines.merge,
sort.sort_routines.merge_sort))
END C-PLBundler
Lastly, the example below shows the Match property in an abstraction bind. Assume that the component in which this bind is defined has an external PLBundle player named Sort_Bundle. Assume further that this player defines five RoutineDef Member players: bubble, quick, merge, sort, and topological. Assume that the player
utilities.topological is a RoutineDef player.
BIND Sort_Bundle TO ABSTRACTIONIn the above example, the UniCon compiler uses the Match (by_name) algorithm to bind Sort_Bundle.sort to sort.sort_routines.sort, and to bind Sort_Bundle.topological to utilities.topological.
MAPSTO (sort.sort_routines,
utilities.topological)
MATCH ((Sort_Bundle.bubble,
sort.sort_routines.bubble_sort),
(Sort_Bundle.quick,
sort.sort_routines.quick_sort),
(Sort_Bundle.merge,
sort.sort_routines.merge_sort))
END C-PLBundler
WARNING: In a system described by UniCon, when two players are involved in a bind the external player inherits the number of connections made to an internal player. This may have a bearing on the MaxAssocs value that the system designer chooses for external players in components with composite implementations.
Additionally, the system designer must not choose a MaxAssocs value for an external player which represents an increase in the maximum number of connections to the internal player as specified by its MaxAssocs value. This would defeat the purpose of the
MaxAssocs value of the player to which the external player is ultimately bound - the player corresponding to the source code that implements it in the component with the primitive implementation.
Each player type has its own default:
---------------------- Player Type Default ---------------------- GlobalDataDef 65535 GlobalDataUse 1 PLBundle 65535 RPCCall 1 RPCDef 65535 ReadFile 1 ReadNext 1 RoutineCall 1 RoutineDef 65535 RTLoad 65535 StreamIn 1 StreamOut 1 WriteFile 1 WriteNext 1 ----------------------
Subsequent specifications of the MaxAssocs property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
PLAYER heap_sort IS RoutineCallThe above example prevents the heap_sort RoutineCall player from being connected more than once per instantiation of the component in which it is defined.
SIGNATURE ("int *"; "int *")
MAXASSOCS (1)
END heap_sort
Each role type has its own default:
-------------------- Role Type Default -------------------- Caller 65535 Definer 1 Load 65535 Participant 65535 Readee 1 Reader 65535 Sink 1 Source 1 User 65535 Writee 1 Writer 65535 --------------------
Subsequent specifications of the MaxConns property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
ROLE C-Bundler-Participant IS ParticipantThe above example prevents the C-Bundler-Particpant Participant role from being connected more than twenty-five times per instantiation of the connector in which it is defined.
MAXCONNS (25)
END C-Bundler-Participant
The PLBundle player type in UniCon allows the system designer to create a collection of players, to attach significance to a group of routines and data in an architectural description of a system. The name PLBundle denotes a programing language bundle; the bundle may only contain players of types that have corresponding programming language implementations: GlobalDataDef, GlobalDataUse, RoutineCall, and RoutineDef players.
Each Member player in a PLBundle must be unique. In other words, there may not be duplicate member player definitions in a single PLBundle. Two member players are duplicates if the contents of their first fields are identical (the double-quotes are insignificant, so if one is a "string" and one an <identifier> they are identical if the names are the same - case of the letters is significant).
The same player may, however, be defined in multiple different PLBundle players in the same component. In this case, these Member player definitions refer to the same player in the source code implementation, so the specifications of all Member properties pertaining to this player must be identical inside all of the PLBundle players in which they appear.
This feature of the UniCon language allows the set of players in the source code implementation of a component to be grouped into overlapping subsets of players (i.e., via PLBundle player definitions) in the UniCon description of that component. The PLBundle player definitions can then become useful abstractions to the system designer, and a single player can be a member of more than one abstraction. For example, the C standard library contains over 700 players. A UniCon component definition corresponding to this library may export any number of PLBundle and non-PLBundle player definitions. A system designer may choose to define a PLBundle player for all input/output operations, for example, and another one for only low-level input/output operations. As you can imagine, the write routine definition might exist as a member in both of these bundles.
At least one Member property specification must be present in a UniCon definition of a PLBundle player.
The merge rule value for a given property tells the UniCon compiler what to do when a duplicate specification of that property occurs in the same property list. Usually a duplicate constitutes another property specification with the same name part. For example, any two specifications of the MaxAssocs property in the same property list, regardless of the specific contents of their respective value parts, are considered duplicates. For the Member property, a specification is not a duplicate unless the value part is equivalent to the value part of a previously specified Member property. More specifically, two Member properties are duplicates if the first field in each is the same. The case of the letters in this check is significant, but the presence or absence of double-quotes is not.
Subsequent duplicate specifications of the Member property in a single property list are reported as errors. The UniCon compiler uses the first of the duplicate Member properties it encounters.
PLAYER Sort_Bundle IS PLBundleIn the example above, each Member property defines a Member player in the PLBundle player. Each Member player is of type RoutineDef, and each is further specified by
MEMBER (bubble; RoutineDef;
SIGNATURE ("int *"; "int *")
MINASSOCS (0))
MEMBER (quick; RoutineDef;
SIGNATURE ("int *"; "int *")
MINASSOCS (0))
MEMBER (merge; RoutineDef;
SIGNATURE ("int *"; "int *")
MINASSOCS (0))
MEMBER (sort; RoutineDef;
SIGNATURE ("int *"; "int *")
MINASSOCS (0))
MEMBER (topological; RoutineDef;
SIGNATURE ("int *"; "int *")
MINASSOCS (0))
END Sort_Bundle
The PLBundle player below might be defined in the same component. It may contain some of the same Member players defined in other bundles.
PLAYER Fast_Sort_Bundle IS PLBundle
MEMBER (quick; RoutineDef;
SIGNATURE ("int *"; "int *")
MINASSOCS (0))
MEMBER (merge; RoutineDef;
SIGNATURE ("int *"; "int *")
MINASSOCS (0))
END Fast_Sort_Bundle
WARNING: In a system described by UniCon, when two players are involved in a bind the external player inherits the number of connections made to an internal player. This may have a bearing on the MinAssocs value that the system designer chooses for external players in components with composite implementations.
Additionally, the system designer must not choose a MinAssocs value for an external player which represents a decrease in the minimum number of connections to the internal player as specified by its MinAssocs value. This would defeat the purpose of the MinAssocs value of the player to which the external player is ultimately bound - the player corresponding to the source code that implements it in the component with the primitive implementation.
Each player type has its own default:
---------------------- Player Type Default ---------------------- GlobalDataDef 1 GlobalDataUse 1 PLBundle 0 RPCCall 1 RPCDef 1 ReadFile 1 ReadNext 1 RoutineCall 1 RoutineDef 1 RTLoad 0 StreamIn 1 StreamOut 1 WriteFile 1 WriteNext 1 ----------------------
Subsequent specifications of the MinAssocs property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
PLAYER heap_sort IS RoutineDefThe above example allows the heap_sort RoutineDef player to be left unconnected in a system.
SIGNATURE ("int *"; "int *")
MINASSOCS (0)
END heap_sort
Each role type has its own default:
-------------------- Role Type Default -------------------- Caller 1 Definer 1 Load 1 Participant 2 Readee 1 Reader 1 Sink 1 Source 1 User 1 Writee 1 Writer 1 --------------------
Subsequent specifications of the MinConns property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
ROLE C-Bundler-Participant IS ParticipantThe above example requires the C-Bundler-Particpant Participant role to be associated with at least four players per instantiation of the connector in which it is defined.
MINCONNS (4)
END C-Bundler-Participant
A file opened with O_APPEND causes the current file offset to be initialized to the number of bytes from the beginning of the file. Normally, when a file is opened, the current offset is initialized to zero. Reading and writing normally start at the current file offset and the offset is incremented by the number of bytes read or written. A file opened with O_TRUNC causes the contents of the file to be deleted when it is opened. A file opened with O_CREAT is created if it does not exist in the file system at the time it is opened.
The OpenMode property can be specified in both a component instantiation and a component definition. The set of values for any OpenMode properties in a SeqFile component definition are merged with the values in the OpenMode properties in instantiations of the component. This merged set is then used as the set of open mode options for the file when it is opened in the system during execution.
There is no default value for the OpenMode property. If the property is not specified, none of the three open mode options are supplied as parameters during the open.
More than one specification of the OpenMode property in a single property list is treated as a single specification with the union of all values in all of the specifications.
COMPONENT Data_FileBelow is another example of a specification of an OpenMode property. It is embedded in an instantiation of the above SeqFile component:
INTERFACE IS
TYPE SeqFile
OPENMODE (create)
RECORDFORMAT ("line")
PLAYER Next_Line IS ReadNext
END INTERFACE
IMPLEMENTATION IS
VARIANT Data_File IN "datafile"
IMPLTYPE (Data)
END Data_File
END IMPLEMENTATION
END Data_File
USES My_Data_File INTERFACE Data_FileThe OpenMode value for the My_Data_File SeqFile instantiation is create and truncate.
OPENMODE (truncate)
END My_Data_File
Both an unnamed pipe buffer and a fifo file exhibit nearly identical behavior. They both are opened simultaneously for reading and writing. Both buffers are circular; when reading or writing beyond the end of the buffer, the read or write buffer pointer is reset to the beginning of the buffer and I/O continues if there is room left (when writing) or data left (when reading). Data is written to the buffer sequentially, and placed in the buffer immediately after previously written bytes. The write buffer pointer is advanced after each byte that is written. Data is read from the buffer sequentially, and the read buffer pointer is advanced after each byte that is read. Reader processes are suspended when there is no more data to be read from the buffer (i.e., both the read and write buffer pointers are in the same position in the buffer). The processes are resumed when data is written to the buffer. Writer processes are suspended when the buffer becomes full. The processes are resumed when data is read from the buffer. If all writer processes close their end of the buffer, the reader processes get the value EOF (end-of-file) during their next read operations. If all reader processes close their end of the buffer, the writer processes receive the SIGPIPE signal from the Unix operating system.
There are two main differences between the two mechanisms. The first is the size of the buffer. An unnamed pipe buffer in the Unix kernel ranges between 4K and 8K bytes, depending on the particular version of Unix. A fifo file is a named file in the file system, and therefore it has nearly unlimited size. Size is the distinctive characteristic for selecting a particular PipeType value. The second main difference is the way that the buffers are created. An unnamed buffer is created with the pipe function in the C standard library. Two file descriptors are returned to the application as a result of the call; they are both open - one for reading and one for writing. A named fifo file is created in the file system with either the mknod or mkfifo function in the C standard library (most Unix systems have the mknod function; only a few have the mkfifo function - the usage of both functions is nearly identical). Once the fifo file is created, it must be opened manually (i.e., using the open function in the C standard library) by both the reader processes and the writer processes. All reader processes will be suspended until at least one process opens the fifo file for writing, and vice versa.
The default value for the PipeType property is named.
Subsequent specifications of the PipeType property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
CONNECTOR Unix_PipeBelow is another example of a specification of a PipeType property. It is embedded in an instantiation of the above Pipe connector:
PROTOCOL IS
TYPE Pipe
PIPETYPE (unnamed)
ROLE Source IS Source
ROLE Sink IS Sink
END PROTOCOL
IMPLEMENTATION IS
BUILTIN
END IMPLEMENTATION
END Unix_Pipe
USES My_Pipe PROTOCOL Unix_PipeThe PipeType value named in the My_Pipe Pipe instantiation overrides the PipeType value unnamed in the Unix_Pipe Pipe definition.
PIPETYPE (named)
END My_Pipe
In a Unix process, input and output is accomplished through ports. There are typically 64 ports in a Unix process, although there may be more depending upon the version of Unix. A port is represented as an integer value (the values are zero based, ranging between 0 and 63) in the source code of the process, and this value is referred to as a file descriptor.
Unix filter processes perform input and output in streams, using designated ports. The ports for streams in a Unix filter are half-duplex (when hooked to named or unnamed pipes) so a filter usually performs input and output on separate ports. The choice of port numbers is a significant part of the design of a given filter; the choices are not made randomly. Most Unix filters perform input from port 0, and output to port 1. Error messages are written using port 2. These choices are a well-known convention among Unix developers. This design allows for filters to be easily composed with pipes on the Unix command line into larger, more complex tools, that are themselves filters.
In the UniCon language, the description of a StreamIn or StreamOut player captures the system designer's intentions regarding particular streams of data in a filter process. Information such as the structure of the data in the stream and the port number over which the stream of data will flow is captured for each player. The PortBinding property is used to specify the port number over which the data in the stream will flow. This property is required in the property list of a StreamIn and StreamOut player.
Subsequent specifications of the PortBinding property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
COMPONENT Sort
INTERFACE IS
TYPE Filter
PLAYER input IS StreamIn
SIGNATURE ("line")
PORTBINDING (stdin)
END input
PLAYER output IS StreamOut
SIGNATURE ("line")
PORTBINDING (1)
END output
PLAYER error IS StreamOut
SIGNATURE ("line")
PORTBINDING ("stderr")
END error
END INTERFACE
IMPLEMENTATION IS
VARIANT sort IN "sort"
IMPLTYPE (Executable)
END sort
END IMPLEMENTATION
END Sort
Real-time applications are cyclic in nature, and usually consist of processes that must execute well-defined chunks of work at specific rates. These chunks of work usually have strict deadlines to meet within the period of the process, meaning that each process requires a certain amount of the processor resource during its period. The requirements for each schedulable process can come into conflict with each other when system designers try to schedule a set of such processes to run on a single processor. Therefore, the system of processes must be designed carefully so that each schedulable process in the application gets the right amount of processor resource during its period. When designing such an application, the system designer must pay strict attention to the amount of execution time required by each process and the rate at which each process gets reactivated. These design points are made explicit in UniCon definitions of SchedProcess components (the SegmentDef property provides the system designer with the means to specify the amount of execution time required by the schedulable process, and the TriggerDef property provides the system designer with the means to specify the rate at which a schedulable process component gets reactivated).
The system designer must also take into account the available scheduling algorithms for a processor in the real-time environment when designing a system (the Algorithm property, specified in the definition or instantiation of an RTScheduler connector, provides the system designer with the means for specifying the particular real-time scheduling algorithm in
UniCon). Each available algorithm places a set of requirements on the schedulable processes. Some algorithms, for example, require that each schedulable process execute with a particular priority at run-time. For example, in the Real-Time Mach operating system, the rate monotonic scheduling algorithm gives the highest priority process that is ready for execution access to the processor resource immediately. For this algorithm to work properly, the schedulable processes in the system must be given priorities in accordance with their rates of execution. Processes with faster rates receive higher priorities.
The Priority property provides the system designer with the means for specifying the priority at which the schedulable process will execute in the environment at run-time. The value of the property is a non-negative <integer> in the range from 0 to 31. This range reflects the legal set of priorities that a process can have in the Real-Time Mach operating system.
The default value for the Priority property is the highest priority, 0.
Subsequent specifications of the Priority property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
INTERFACE ISBelow is another example of a specification of a Priority property. It is embedded in an instantiation of the above SchedProcess component:
TYPE SchedProcess
PRIORITY (3)
PROCESSOR ("TESTBED.XX.CMU.EDU")
TRIGGERDEF (external_interrupt1; 1.0)
TRIGGERDEF (external_interrupt2; 0.5)
SEGMENTDEF (work_block1; 0.02)
SEGMENTDEF (work_block2; 0.03)
SEGMENTDEF (work_block3; 0.05)
PLAYER timeget IS RPCCall
SIGNATURE ("new_type *"; "void")
END timeget
PLAYER timeshow IS RPCCall
SIGNATURE ("void"; "void")
END timeshow
END INTERFACE
USES Client INTERFACE RT_ClientThe Priority value 2 in the Client SchedProcess instantiation overrides the Priority value 3 in the RT_Client SchedProcess definition.
PRIORITY (2)
ENTRYPOINT (client)
END Client
Although the Processor property can be specified in a component or (RTScheduler) connector definition, it is typically specified in the instantiation. This makes the definition more general and, therefore, parameterizable at instantiation time.
The UniCon compiler also uses the value of the Processor property to determine the correct processor on which to build an application executable in the environment. Components designated for a particular processor are built on that processor. UniCon uses the "rsh" remote shell command in Unix to remotely execute the Odin construction utility to build the binary executable associated with a given component.
The default value for the Processor property is the processor on which the UniCon compiler is analyzing and building the system from the set of UniCon definitions.
Subsequent specifications of the Processor property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
ESTABLISH RTM-RealTime-Sched WITH
client.load AS load
server.load AS load
PROCESSOR (TESTBED.XX.CMU.EDU)
ALGORITHM (rate_monotonic)
TRACE (client.load.trigger,
client.load.segment1,
server.load.segment,
client.load.segment2)
END RTM-RealTime-Sched
A sequential file can be viewed as containing "records" of data. A record of data is a block of data that has a specific format (i.e., byte-level organization). For example, a record might have a format that is as simple as a sequence of characters, or it might have a more complex format such as a sequence of data of mixed data types (e.g., "int", "char", "double"). A sequential file can be viewed as being comprised of data in records that have a specific format. The input/output operations on a sequential file operate on the data as records, not individual bytes. The data, as records, have meaning in the application, and therefore records represent an abstraction of the data in the file.
The RecordFormat property is used to capture the organization of data in a sequential file in terms of records with a specific format. This property specifies the system designer's view of the structure of the contents of a sequential file. A specification of the RecordFormat property provides users of a SeqFile component with an indication of how the data in the file should be interpreted or used.
The default value for the RecordFormat property is "line".
Subsequent specifications of the RecordFormat property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
INTERFACE ISThe data type "line" in the RecordFormat property indicates that the sequential file contains records that have the structure of lines (i.e., sequences of characters terminated by a carriage return). Data streams that are read from the file should be read and interpreted as lines, and data streams written to the file should be written as lines. Below is another example:
TYPE SeqFile
RECORDFORMAT ("line")
PLAYER read IS ReadNext
PLAYER write IS WriteNext
END INTERFACE
INTERFACE ISIn the above RecordFormat property, the value contains three data type specifications. This means that the file contains data organized into records, where each record has three members of the given data types. Data streams are read/written from/to the file using this data record format.
TYPE SeqFile
RECORDFORMAT ("integer", "char", "double")
PLAYER read IS ReadNext
PLAYER write IS WriteNext
END INTERFACE
In a simple bind, the names and the types of the two players involved must be identical, or the UniCon compiler will report these conditions as errors and will not perform the bind. The system designer can use the Rename property in the property list of a simple bind to resolve a name mismatch, however. When the UniCon compiler encounters the Rename property in a simple bind, it allows a name mismatch to be legal in the definition, and it resolves the name mismatch in the source code implementation during the system build. The Rename property is not necessary (and therefore not legal) in the property list of an abstraction bind because name mismatches are resolved by using the Match property.
In ProcedureCall, DataAccess, and RemoteProcCall connections, the names of all players in the connection must be identical. If they are not, the UniCon compiler reports this as an error and does not perform the connection. The system designer can use the Rename property in the property list of a connector instantiation or definition to resolve a name mismatch, however. When the UniCon compiler encounters the Rename property in a
ProcedureCall, DataAccess, or RemoteProcCall connection, it does not report name mismatches as errors, and it resolves the mismatches in the source code implementation of the system. The compiler renames all of the <identifier>s in the implementation that are associated with the players in the connection to a common, generated name. It does this during system construction with C language macros supplied in the compilation step of each source code module involved in the connection (with the -D compiler option). The Rename property is only legal in connectors of type ProcedureCall, DataAccess, and RemoteProcCall.
Although the Rename property can be specified in the <protocol> property list of a connector definition, it is typically specified in the the property list of a connector instantiation (i.e., in the property list of an <establish> or an <instantiation> of a connector). If specified in a connector definition, then every instantiation of that connector will allow name mismatches to be legal. Leaving this property out of a connector definition makes it more general, and it can be parameterized with the property during an instantiation.
There is no default value for the Rename property; it has no value part.
The merge rule for the Rename property has no effect, since the Rename property has no value part. The UniCon compiler simply views the property has either having been specified or not.
ESTABLISH RTM-remote-proc-call WITHThe UniCon compiler will rename the identifiers timeget and timeget_routine in the implementation to a third, generated identifier. It will also generate the correct "glue code" for the remote procedure call connection and then build the system successfully. The Rename property must be present in order for the UniCon compiler to do the renaming. If it is not present, the compiler will report the name mismatch as an error.
client.timeget AS Caller
server.timeget_routine AS Definer
RENAME
IDLTYPE (mach)
END RTM-remote-proc-call
Below is an example of the Rename property embedded in a simple bind. Assume for the sake of the example that this simple bind is contained in the composite implementation of a component that is exporting a RoutineDef player called timeget in the interface. Assume also that in the composite implementation, before this simple bind definition, there is an instantiation of a Module component containing a RoutineDef player called timeget_routine. Assume that the instantiation name of the Module component is clientapp:
BIND timeget TO clientapp.timeget_routine
RENAME
END timeget The Rename property is required to resolve a name mismatch in a simple bind. If this prop erty were not specified in the example above, the UniCon compiler would report the name mismatch as an error.
The UniCon compiler uses a message passing mechanism to implement remote procedure calls (RPCs) in the source code implementation of a system. The compiler makes use of either of two well-known RPC facilities, depending upon the target operating system of the application: the Mach InterProcess Communication (IPC) facility for Mach and Real-Time Mach applications, and the RPCGen-based facility for applications running on Sun platforms.
Implementation of message passing for RPCs requires the production of glue code for both client processes and server processes performing RPCs. A client process is one that makes remote procedure calls, but does not export remote procedure definitions that can be called by other processes. A server process is one that does export remote procedure definitions. Glue code is source code that is not part of the application logic, but is required in a process to facilitate the message passing mechanism of the RPC facility. The glue code gets compiled and linked into both client and server processes that perform RPCs; it facilitates the creation, passing, and decoding of messages constructed from the arguments and return values of the routine calls.
Both RPC facilities mentioned above provide system designers with an IDL-like language to specify remote procedure definitions, which includes the data types of the arguments. These RPC facilities provide compilers for their respective languages that compile an IDL description of a set of services into the glue code needed by both client and server processes. The IDL compilers are not smart enough to be able to support complex data types of RPC arguments that can be constructed in a given programming language. The compilers usually only support simple data types like integers and character strings, and simple array types. Definitions of the complex data types, therefore, must be provided to the RPC facility, and these are usually specified in the IDL-like language in terms of the simple data types and array types.
NOTE: Since data in remote procedure calls is intended to flow between processes, data values cannot be passed by reference in the argument lists of remote routine calls. This is because the address space of a process is distinct, and not visible to another process. The values, therefore, must be passed by copy between processes. The data types of the arguments in the Signature of an RPC must reflect this restriction. Characters strings in the C language, for example, must be passed as arrays of characters, and not as pointers to character strings. Similarly pointers to other types of data are disallowed. This is why most RPC facilities support only simple data types and array types.
The RPCTypeDef property provides the system designer with the means for providing the RPC facility with definitions of the complex data types of arguments in remote procedure definitions. The UniCon compiler constructs the complex data type out of simple data types and array types in the IDL specifications of remote procedure definitions, and then uses these specifications to generate the glue code for a system.
The following is a list of standard data types in the C programming language that are recognized by the IDL compilers of both RPC facilities. If the arguments of the remote procedure calls in an application have the following type names, then no RPCTypeDef properties are necessary. The name of the type and the size (in bytes) of an object of that type are provided for each standard C type:
----------------- C data type Size ----------------- short 2 int 4 long 4 float 4 double 8 char 1 unsigned 4 -----------------The default sizes for the standard C types are the defaults for the "cc" compiler hosted on SunOS 4.1.3 systems. These defaults can be overriden (see below).
Character strings are considered complex data types to most RPC facilities, since they must be passed as arrays of characters (see above). To pass character strings as arguments in remote procedure calls, the application programmer must define complex C language data types representing arrays of the data type char. For example, character strings of length 10 can be defined as follows in an application:
typedef char string_10_type[10];To use this data type in an RPC, the system designer must specify an RPCTypeDef property in the property list of an instantiation or definition of the SchedProcess or Process component in which the RPCCall or RPCDef player is defined that uses the data type for one of its arguments. It is recommended that for formal arguments representing variable length character strings in remote routine definitions, a fixed length character array of maximum size be used. The RPC facility only copies character string data until it encounters the null character, so this is not an inefficient way of passing character data.
The value part of the RPCTypeDef property has three fields: the type name, the base type name, and the size, in that order. The type name is the name of the data type as it is defined in the source code implementation of the system. For example, in the Example section below, the name of the type being defined is new_type, which is the name of the type in the typedef in the C source code fragment also shown in the example.
The base type name is the name of the data type upon which the newly defined type is based. For example, if a remote procedure call contains an argument of the type my_integer, which is defined in the C language as follows:
typedef int my_integer;then the base type is the C standard type int. The RPCTypeDef property describing my_integer to the RPC facility would appear as follows:
RPCTYPEDEF (my_integer; int)The base type name can name a standard C language data type, the type name in another RPCTypeDef property in the same property list, or the keyword struct. Many complex C language data types are defined as structs containing members of other data types. If the base type name is the type name in another RPCTypeDef property, then no size specification is required in the RPCTypeDef property. UniCon uses the size specification from the RPCTypeDef property defining that type. If the base type name is a standard C language data type, no size specification is required either, since the UniCon compiler knows the default size associated with each C type. If the sizes of the standard C language types are different on the target machine for a given application than the defaults, specifying a size in the third field overrides the default that UniCon has for that type. Lastly, if the base type name is struct, then a size specification is required in the RPCTypeDef property. This must represent the size of the struct as given by the sizeof operator in the C programming language. The reason for this is that the sizeof operator takes into account the gaps between members of structs due to the compiler acquiring storage space for struct members on word boundaries.
The third field specifies the size (in bytes) that an object of the given base type name consumes. This field consists of some optional array bounds, followed by a byte size. The array bounds are integers surrounded by square brackets; there can be any number of array bound specifications, from none to an infinite number. Each array bound represents another dimension in the array type being defined, with the rightmost array bound representing the subscript that varies fastest as elements in the array are accessed in storage order. The byte size part of the third field is required, if the third field is non-empty. The byte size represents the size of an array element. If no array bounds are specified, then it represents the true size (in bytes) of on object of the base type.
There is no default value for the RPCTypeDef property.
The merge rule value for a given property tells the UniCon compiler what to do when a duplicate specification of that property occurs in the same property list. Usually a duplicate constitutes another property specification with the same name part. For example, any two specifications of the MaxAssocs property in the same property list, regardless of the specific contents of their respective value parts, are considered duplicates. For the
RPCTypeDef property, a specification is not a duplicate unless the value part is equivalent to the value part of a previously specified RPCTypeDef property. More specifically, two RPCTypeDef properties are duplicates if the first field in each is the same. The case of the letters in this check is significant, but the presence or absence of double-quotes is not.
Subsequent duplicate specifications of the RPCTypeDef property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
USES Server INTERFACE RTServerThe corresponding type definitions in the file "server_app.h" are as follows:
PRIORITY (2)
RPCTYPEDEF (new_type; struct; 12)
RPCTYPEDEF (another_type; new_type)
RPCTYPEDEF (an_array_type; char; [12] 25)
RPCTYPESIN ("server_app.h")
END Server
typedef struct {
int member1;
short member2;
int member3;
} new_type;
typedef new_type another_type;
typedef char an_array_type[12][25];
On a SunOS 4.1.3 system, the new_type struct takes up 12 bytes of space, so the <size> specification is the <integer> value 12. On a SunOs 4.1.3 system, an int requires 4 bytes of storage space, and a short requires 2 bytes. The reason that the total size is 12 bytes and not 10 is because SunOs always starts new storage allocation on a
The glue code generated to support remote procedure calls between processes (see the Description for the RPCTypeDef property) requires visibility of the type definitions for all the data types of the arguments in the signatures of the remote routine calls and definitions. The UniCon compiler, therefore, must know the location of the actual data type definitions in the source code implementation of a system so that it can pass the information along to the IDL compiler of the appropriate RPC facility in order to generate the glue code. The RPCTypesIn property allows the system designer to specify the names and locations of these files. As with the RPCTypeDef property, the RPCTypesIn property is only necessary if the data types of some of the arguments in the remote routine calls and definitions are complex data types (i.e., data types that are not one of the pre-defined simple C standard data types: short, int, long, float, double, char, and unsigned; character string data types are considered to be complex. See the Description for the RPCTypeDef property for more information).
It is recommended that all complex data type definitions be placed in C language include files, and that these files be included in the source code implementing a Process or
SchedProcess component with the #include C language preprocessor directive. This allows the data type definitions to be easily included in the generated glue code in the same way (the UniCon compiler does the inclusion automatically via the IDL specification of the remote routine definitions).
Each file name in the RPCTypesIn list can either be fully qualified with an absolute or relative path specification, or it may simply be the name of the file. During the semantic checking phase of compilation, the UniCon compiler checks for the existance of each file in the file system. If the file name does not contain path information, the UniCon compiler first looks in the current directory for the file (i.e., the current working directory in the Unix environment in which the system designer invoked the compiler). If the file is not found there, it searches all of the directories specified in the CPATH environment variable for the file. In the Unix environment, the CPATH environment variable can be set as follows:
setenv CPATH /usr/gz/include:/usr/include:/includeAfter performing the above command at the Unix shell prompt, the CPATH variable contains a colon-separated list of directory paths. The UniCon compiler searches each directory until it finds the file. It searches the directories from left to right, so in the example above it would first search the directory /usr/gz/include. If the file did not exist there, the compiler would next search the directory /usr/include, and so on. If the UniCon compiler does not find the file in the current directory or in the directories in the CPATH environment variable, it reports an error message stating that the file does not exist.
NOTE: The CPATH environment variable must be set in the Unix environment prior to the invocation of the UniCon compiler.
Lastly, relative path names are interpreted beginning from the current working directory in the Unix environment in which the UniCon compiler is invoked. Relative path names in a UniCon definition are not recommended. The location from which the UniCon compiler is invoked cannot usually be guaranteed from compilation to compilation. Either absolute path names should be specified in the file name, or no path names at all.
There is no default value for the RPCTypesIn property.
More than one specification of the RPCTypesIn property in a single property list is treated as a single specification with the union of all values in all of the specifications.
USES Server INTERFACE RTServer
PRIORITY (2)
RPCTYPEDEF (new_type; struct; 12)
RPCTYPEDEF (another_type; new_type)
RPCTYPEDEF (an_array_type; char; [12] 25)
RPCTYPESIN ("server_app1.h")
RPCTYPESIN ("/usr/gz/include/server_app2.h")
END Server
A segment is an abstraction. It describes a chunk of code in the source code implementation of the schedulable process. The chunk of code it describes may take any form that the designer chooses. For example, it may describe a function, a set of functions, a portion of a single function, or even a few statements within a given function. The designer typically defines segments based on structural and execution time properties of the code in order to facilitate certain types of analyses that can be done on sets of schedulable processes in a real-time environment (e.g., a rate monotonic analysis). Segments are defined in the interface property list of SchedProcess components via the SegmentDef property. The value part of the property defines the name of the segment and associates an execution time (in seconds) with it. Work done in a schedulable process is described by a sequence of one or more segments.
Segments usually represent blocks of code in a process between points where flow of control is transferred. For example, a client process initially receives control from the operating system environment. It performs work until control is transferred either back to the operating system or to some server process (via a remote procedure call). A system designer typcally defines the code that executes between these two points as one or more discrete segments. Each of these segments has an associated execution time which is captured in the second field of the SegmentDef property that defines the segment.
Segment names are used in the value part of the SegmentSet property. This property further specifies RTLoad players by indicating which chunks of code get executed as a result of placing the associated real-time load on a processor in the real-time environment at run-time.
Segment names are also used in the value part of the Trace property. This property further specifies RTScheduler connector instantiations by describing traces of events that occur in the system of schedulable processes mediated by the connector. An event is a thread of control in a system of such processes. For example, a client process initially receives control from the operating system. This process performs some work, and it may return or it may make a remote procedure call to another process (a server process). If it makes a remote procedure call, the thread of control is transferred to the server process, which performs some work and returns (or makes another remote procedure call, and so on). Control is eventually returned back to the original client process, where work continues in a similar fashion until control is eventually returned back to the environment. This entire thread of control is called an event in UniCon, and can be described by a trace. A trace is a specification of the event. It specifies the trigger, the mechanism by which control is first given to a client schedulable process from the environment, and the sequence of segments that execute as a result of the trigger in the order in which they are executed.
The execution times associated with segments in a Trace property are used in a rate monotonic analysis (RMA) of the schedulable processes mediated by an RTScheduler connector if the algorithm of choice is the rate monotonic algorithm. The <floating point number> in the value of the SegmentDef property represents the execution time (in milliseconds) of the chunk of code.
There is no default value for the SegmentDef property.
Subsequent duplicate specifications of the SegmentDef property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses). Two SegmentDef properties are duplicates if the <identifier> or "string" in the first field of both are identical. The case of the letter in the check is significant, however the absence or presence of double-quotes is not.
INTERFACE IS
TYPE SchedProcess
PROCESSOR ("cubistic.art.cs.cmu.edu")
SEGMENTDEF (work_block1; 0.04)
SEGMENTDEF (work_block2; 0.03)
PLAYER services IS RTLoad
SEGMENTSET (work_block1, work_block2)
END services
PLAYER timeget IS RPCDef
SIGNATURE ("new_type *"; "void")
END timeget
PLAYER timeshow IS RPCDef
SIGNATURE ("void"; "void")
END timeshow
END INTERFACE
An RTLoad player corresponds to the load that a schedulable process places on a given processor. A load is the total amount of execution time that a single schedulable process requires for execution of the code it contains that applies to a particular event (see the Description section for the RTLoad player type for more information on events). An RTLoad player consists of a trigger and a set of segments. The set of segments describes the total amount of execution time required for the portion of the event that the RTLoad player is responsible for, and the trigger describes how often the set of segments is reactivated per unit of time. Together this information specifies the load that an RTLoad player places on a processor.
The SegmentSet property is used to specify the part of the RTLoad player consisting of the set of segments. As described above, an RTLoad player specifies a load associated with a portion of an event - that portion of the event implemented by the schedulable process that contains the given RTLoad player (recall that an event can consist of code from more than one schedulable process). The SegmentSet property of an RTLoad player lists the set of segments defined in the enclosing SchedProcess component that implement the portion of the event of which the RTLoad player is a part. The segments named in the value of the SegmentSet property must each be defined in a SegmentDef property in the
<interface> property list of the component containing the RTLoad player.
More than one SegmentSet property may be specified in a single RTLoad player. The
UniCon compiler will treat these multiple specifications as a single SegmentSet property with the value portion being the union of all values in all of the specifications.
There is no default value for the SegmentSet property.
More than one specification of the SegmentSet property in a single property list is treated as a single specification with the union of all values in all of the specifications.
PLAYER services IS RTLoadAlso refer to the Example section for the SegmentDef property.
SEGMENTSET (work_block1, work_block2)
END services
There is no default value for the Signature property.
Subsequent specifications of the Signature property in a single property list are reported as errors. The UniCon compiler uses the first of the duplicate Signature properties it encounters.
PLAYER input_data_stream IS StreamIn
SIGNATURE ("line")
END input_data_stream PLAYER output_data_stream IS StreamOut
SIGNATURE ("line")
END output_data_stream PLAYER global_variable IS GlobalDataDef
SIGNATURE ("int")
END global_variable PLAYER global_variable IS GlobalDataUse
SIGNATURE ("int")
END global_variable PLAYER local_routine IS RoutineCall
SIGNATURE ("int", "char *"; "void")
END local_routine PLAYER local_routine IS RoutineDef
SIGNATURE ("int", "char *"; "void")
END local_routine PLAYER remote_routine IS RPCCall
SIGNATURE ("int", "double"; "void")
END remote_routine PLAYER remote_routine IS RPCDef
SIGNATURE ("int", "double"; "void")
END remote_routine PLAYER read IS ReadFile
SIGNATURE ("line")
END read PLAYER write IS WriteFile
SIGNATURE ("line")
END write
The Trace property is used to specify an event in UniCon. An event is a thread of control through a set of schedulable processes running on a common processor that is initiated by some triggering mechanism in the environment and that eventually returns back to the environment. During execution, the thread of control is passed to a client schedulable process by the environment as the result of the firing of some triggering mechanism (e.g., a timer associated with a given process expiring in the kernel causing the scheduler to interrupt the currently executing process and transfer control to the given one). The process performs some work, and then it may return or it may make a remote procedure call to another process (a server process). If it makes a remote procedure call, the thread of control is transferred to the server process, which performs some work and returns (or makes another remote procedure call, and so on). Control is eventually returned back to the original client process, where work continues in a similar fashion until control is returned back to the environment. This entire thread of control is called an event in UniCon. A trace is a specification of an event. It specifies the trigger, the mechanism by which control is first given to a client schedulable process from the environment, and the sequence of segments that execute (in the order in which they execute).
A trigger is an abstraction. As mentioned above, it describes the mechanism by which control is transferred from the environment to a schedulable process. Only client processes can define triggers, because they begin execution by receiving control from the environment. Server processes do not have triggers because they execute continuously and receive control only as the result of remote procedure calls. Since client processes execute to completion and return to the environment, they can be reactivated by the environment according to some rate (i.e., some number of times per second). Therefore, triggers have associated rates of initiation. Triggers are defined in the interface property list of SchedProcess components via the TriggerDef property. The value part of the property defines the name of the trigger and associates with it the rate (in seconds) at which the trigger is initiated.
A segment is also an abstraction. It describes a chunk of code in the implementation of the schedulable process. The chunk of code it describes may take any form that the designer chooses. For example, it may describe a function, a set of functions, a portion of a single function, or even a few statements within a given function. The designer typically defines segments based on structural and execution time properties of the code in order to facilitate certain types of analyses that can be done on sets of schedulable processes in a real-time environment. Segments are defined in the interface property list of SchedProcess components via the SegmentDef property. The value part of the property defines the name of the segment and associates an execution time (in seconds) with it. Work done in a schedulable process is described by a sequence of one or more segments.
The Trace property specifies a complete event. The value part of the property lists the trigger that initiates the event, followed by the segments that execute until control is returned to the environment (in the order that they execute). Although the SchedProcess components are what get managed by the real-time scheduler in the operating environment, the events are what are of interest to the real-time application developer. The events define the work that must be done in response to a trigger - the events have the associated periods, execution times, and deadlines. The real-time application developer designs an application so that all of the events in the system meet their respective deadlines. The SchedProcess components are assigned the appropriate work and priorities so that this will happen.
If the Algorithm property in an RTScheduler connector instantiation specifies the rate_monotonic scheduling algorithm, the UniCon compiler can facilitate a Rate Monotonic Analysis (RMA) on the events described by the Trace properties in that connector instantiation. For a RMA, the UniCon compiler collects the segment names, their execution times, and the period information for each event associated with a given processor; it also collects the priority information for each schedulable process associated with that same processor. It then formats this information and prepares a file containing input to a RMA tool which performs the analysis. This tool is invoked manually and reports a simple answer to indicate whether or not all of the events will meet their respective deadlines. The file that UniCon prepares with the input to the RMA tool is named RMA_<processor_name>, where <processor_name> is taken from the Processor property associated with the RTScheduler connector instantiation in which the Trace properties are specified. An RMA input file is generated by the UniCon compiler for each RTScheduler connector instantiation whose Algorithm property specifies a rate_monotonic scheduler.
There is no default value for the Trace property.
Subsequent specifications of the Trace property in a single property list are reported as errors. The UniCon compiler uses the first of the duplicate Trace properties it encounters. A duplicate Trace property is one in which the value part contains the same list of elements as a previously specified Trace property. Each Trace property value contains the same number of elements, and the elements in corresponding positions in the lists name the same trigger or segment.
USES Scheduler PROTOCOL RTM-Real-Time-SchedulerThe above example assumes that "client" and "server" are SchedProcess components that have been instantiated prior to the instantiation of the connector. Client has an RTLoad player named application that has a trigger named external_interrupt and three segments named work_block1, work_block2, and work_block3. Server has an RTLoad player named services that has two segments named work_block1 and work_block2. The example below makes the same assumptions.
ALGORITHM (rate_monotonic)
PROCESSOR ("TESTBED.XX.CMU.EDU")
TRACE (client.application.external_interrupt,
client.application.work_block1,
server.services.work_block1,
client.application.work_block2,
server.services.work_block2,
client.application.work_block3)
END Scheduler
CONNECT client.application TO Scheduler.load
CONNECT server.services TO Scheduler.load
ESTABLISH RTM-Real-Time-Scheduler WITH
client.application AS load
server.services AS load
ALGORITHM (rate_monotonic)
PROCESSOR ("TESTBED.XX.CMU.EDU")
TRACE (client.application.external_interrupt,
client.application.work_block1,
server.services.work_block1,
client.application.work_block2,
server.services.work_block2,
client.application.work_block3)
END RTM-Real-Time-Scheduler
An RTLoad player corresponds to the load that a schedulable process places on a given processor. A load is the total amount of execution time that a single schedulable process requires for execution of the code it contains that applies to a particular event (see the Description section for the RTLoad player type for more information on events). An RTLoad player consists of a trigger and a set of segments. The set of segments describes the total amount of execution time required for the portion of the event that the RTLoad player is responsible for, and the trigger describes how often the set of segments is reactivated per unit of time. Together this information specifies the load that an RTLoad player places on a processor.
The Trigger property is used to specify the trigger part of the RTLoad player. An RTLoad player may only specify one Trigger. The value of the Trigger property is the name of a trigger that is defined in a TriggerDef property in the <interface> property list of the component containing the RTLoad player.
Typically, RTLoad players in client processes will contain Trigger properties, and those in server processes will not. This is because server processes are assumed to have asynchronous triggers which are not used anywhere in UniCon. Only the periodic triggers defined in client processes are used in UniCon, and they are used in the value of the Trace property (see the Description section for the TriggerDef property for more information).
There is no default value for the Trigger property.
Subsequent duplicate specifications of the Trigger property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
PLAYER application1 IS RTLoadAlso refer to the Example section for the TriggerDef property.
TRIGGER (external_interrupt1)
SEGMENTSET (work_block1, work_block2,
work_block3)
END application1
In a real-time environment, client schedulable processes are periodic: they do not run forever, and they are periodically reactivated. When a client process is activated, it runs to completion and returns control to the environment. The mechanism by which control is transferred to a client process from the operating system at run-time is called a trigger in UniCon. On the other hand, server processes run continuously. Control is passed to a server process only via remote procedure calls from clients or other servers, meaning that they are aperiodic.
A trigger is an abstraction that describes any number of implementation mechanisms by which control is transferred from the environment. An example of a trigger in the run-time environment is an interrupt generated as a result of a clock timer associated with a process counting down to zero. Only client processes can define triggers, because they begin execution by receiving control from the environment. Server processes do not have triggers because they execute continuously and receive control only as a result of remote procedure calls. Since client processes execute to completion and return to the environment, they can be reactivated by the environment according to some rate (i.e., some number of times per unit of time). Therefore, triggers have associated rates of initiation. Triggers are defined in the interface property list of SchedProcess components via the TriggerDef property. The value part of the property defines the name of the trigger and associates with it the rate (in seconds) at which the trigger is initiated.
NOTE: The UniCon language does not prevent a server process from defining a trigger via a TriggerDef property; however the value of the second field in this case must be asynchronous since server processes run continuously, responding to RPC requests asynchronously.
Trigger names defined in TriggerDef properties are used in the value part of the Trigger property. This property further specifies RTLoad players by indicating which trigger in a SchedProcess component initiates the given real-time load on a processor.
Trigger names are also used in the value part of the Trace property. This property further specifies RTScheduler connector instantiations by describing traces of events that occur in the system of schedulable processes mediated by the connector. An event is a thread of control in a system of such processes. For example, a client process initially receives control from the operating system. This process performs some work, and it may return or it may make a remote procedure call to another process (a server process). If it makes a remote procedure call, the thread of control is transferred to the server process, which performs some work and returns (or makes another remote procedure call, and so on). Control is eventually returned back to the original client process, where work continues in a similar fashion until control is eventually returned back to the environment. This entire thread of control is called an event in UniCon, and can be described by a trace. A trace is a specification of the event. It specifies the trigger, the mechanism by which control is first given to a client schedulable process from the environment, and the sequence of segments that execute as a result of the trigger in the order in which they are executed.
The <floating point number> in the value of the TriggerDef property represents the period (in seconds) of the triggering mechanism. When a trigger is used in a Trace property, the UniCon compiler uses this period value to initialize the schedulable process in the real-time environment to run at the specified rate. The period associated with a trigger in a Trace property is also used in a rate monotonic analysis (RMA) of the schedulable processes mediated by an RTScheduler connector if the algorithm of choice is the rate monotonic algorithm.
There is no default value for the TriggerDef property.
Subsequent duplicate specifications of the TriggerDef property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses). Two TriggerDef properties are duplicates if the <identifier> or "string" in the first field of both are identical. The case of the letter in the check is significant, however the absence or presence of double-quotes is not.
INTERFACE IS
TYPE SchedProcess
PROCESSOR ("cubistic.art.cs.cmu.edu")
TRIGGERDEF (external_interrupt1; 1.0)
TRIGGERDEF (external_interrupt2; 0.5)
SEGMENTDEF (work_block1; 0.02)
SEGMENTDEF (work_block2; 0.03)
SEGMENTDEF (work_block3; 0.05)
PLAYER application1 IS RTLoad
TRIGGER (external_interrupt1)
SEGMENTSET (work_block1, work_block2,
work_block3)
END application1
PLAYER application2 IS RTLoad
TRIGGER (external_interrupt2)
SEGMENTSET (work_block1, work_block2,
work_block3)
END application2
PLAYER timeget IS RPCCall
SIGNATURE ("new_type *"; "void")
END timeget
PLAYER timeshow IS RPCCall
SIGNATURE ("void"; "void")
END timeshow
END INTERFACE
Primitive implementations are composed of a list of variants. A variant is an alternative implementation. If a component has only one variant, the choice of alternative to use during system construction is obvious. If a component has more than one variant, the UniCon compiler must choose one of them. In this case, if the system designer does not specify a Variant property in either the component definition or its instantation, the UniCon compiler chooses the first variant in the list as the alternative implementation to use during system construction. If the system designer does specify a Variant property, the UniCon compiler uses the variant named in the value of that property as the alternative implementation.
The value of the Variant property must name a variant in the component definition that is being defined or instantiated. The Variant property is not permitted to further specify a component with a composite implementation in UniCon.
The default value for the Variant property is the name of the first variant in the list of variants in the primitive implementation of the component. If the component only has one variant, the name of that variant is the default.
Subsequent specifications of the Variant property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses).
COMPONENT My_Sort_LibraryThis next example is embedded in an instantiation of the above component. The Variant property in the instantiation overrides the one in the definition:
INTERFACE IS
TYPE Computation
LIBRARY
VARIANT (sort_library)
PLAYER bubble IS RoutineDef
SIGNATURE ("int *"; "int *")
END bubble
PLAYER quick IS RoutineDef
SIGNATURE ("int *"; "int *")
END quick
PLAYER merge IS RoutineDef
SIGNATURE ("int *"; "int *")
END merge
END INTERFACE
IMPLEMENTATION IS
VARIANT sort_library IN "libs.a"
IMPLTYPE (ObjectLibrary)
END sort_library
VARIANT debug_sort_library IN "libsdb.a"
IMPLTYPE (ObjectLibrary)
END debug_sort_library
END IMPLEMENTATION
END My_Sort_Library
USES sortlib INTERFACE My_Sort_Library
VARIANT (debug_sort_library)
END sortlib
The information specified in a user-defined property is stored in the parse tree that the
UniCon compiler creates while processing UniCon definitions. At an appropriate step in the compilation or build process, the compiler can collect the information associated with a user-defined property and ship it off to an external tool that might need the information.
NOTE: For now, the means for communicating with external tools is builtin to the compiler. Therefore, adding a new tool to the suite of external tools that are known to UniCon requires modification of the UniCon compiler source code. Since this is not a trivial task, this may require help from the UniCon compiler developers.
As mentioned above, the UniCon compiler does not parse or further interpret the value part of a user-defined property. There are hooks in the source code of the compiler, however, for the user to supply C source code fragments to parse and check the semantics of the value part. The UniCon compiler will execute these code fragments at the appropriate times during the compilation and build process.
The UniCon compiler calls two functions when it encounters a user-defined property. The first is a function that executes user-supplied C source code fragments to parse the value part of user-defined properties. The second is a function that executes user-supplied C source code fragments to check the semantics of the value part of user-defined properties.
The first function, uni_user_parse, is the hook in the UniCon compiler for users to add C source code that parses the value of a user-defined property. If the user wishes to have the UniCon compiler parse the value of a user-defined property, (s)he must supply a parsing function with the source code of the UniCon compiler that can be compiled and linked with the other UniCon source when building it. The user adds a C source code fragment directly to the first function, uni_user_parse, in the UniCon source file uni_user_defined.c to parse the value of a user-defined property. The following is an example of such a fragment:
if (uni_strcmp_l (av_pair->attribute_str->string,In the above example, the function uni_strcmp_l is a utility function provided by the UniCon compiler. The letter "l" in the name refers to the leftmost argument in the argument list. This function performs a string comparison using the C standard library routine strcmp, but first it converts the value of the leftmost argument to lower case letters. The UniCon compiler also provides the two functions uni_strcmp_r and uni_strcmp_b which convert the value of the rightmost argument, and the values of both arguments, to lower case letters, respectively. The return values of these three functions have the same semantics as the return value of the strcmp function.
"some_user_defined_property") == 0)
return my_user_defined_property_parser (
av_pair->value.user);
Also, notice that my_user_defined_property_parser is a user-supplied function that parses the value of the property. All of the information about a particular property is contained in the argument named av_pair, supplied as input to the uni_user_parse routine. av_pair->attribute_str->string points to the string value that is the name part of the property, and av_pair->value.user points to the string value that is the value part of the property. The return value of the user-supplied property parser must be a pointer value. It can be a pointer to an object of any type, since UniCon does not further interpret this value.
The second function, uni_user_semantics, is the hook in the UniCon compiler for users to add C source code that checks the semantics of the value of a user-defined property. If the user wishes to have the UniCon compiler check the semantics of the value of a user-defined property, (s)he must supply a semantics checking function with the source code of the UniCon compiler that can be compiled and linked with the other UniCon source when building it. The user adds a C source code fragment directly to the second function, uni_user_semantics, in the UniCon source file uni_user_defined.c to check the semantics of the value of a user-defined property. The following is an example of such a fragment:
if (uni_strcmp_l (av_pair->attribute_str->string,Notice that check_my_user_defined_property_semantics is a user-supplied function that checks the semantics of the value part of the property. Also, notice that the argument supplied to this function is av_pair->value.user_temp, rather than av_pair->value.user. This is because the function uni_user_parse places the pointer to the parsed value part of a user-defined property there. The return value of the user-supplied property semantic checker must be a pointer value. It can be a pointer to an object of any type, since UniCon does not further interpret this value.
"some_user_defined_property") == 0)
return check_my_user_defined_property_semantics (
av_pair->value.user_temp);
To include the source files containing the user-supplied parsing and semantic checking functions in the build process, modify the Odinfile in the following UniCon installation directory: ...UniCon/uparse/src. Add a separate line for each file to the uparse system model in the following way. Assume you wish to add the functions in the source files my_parsers.c and my_checkers.c to the UniCon compiler. The system model used to build the original compiler looks as follows:
%uparse.c.sm == <<Simply add the names of the files as separate lines to the end of the system model so that it looks as follows. Be sure to include full directory path information for the new files. It is not recommended that you keep the source in the same directory with the other UniCon source. This is because when you delete an old UniCon installation in order to build a new one, you may forget your source files are there and accidentally delete them:
uni_error.c +define=UNI_ERROR_ROUTINE
uni_lexer.l
uni_main.c
uni_parser.y
uni_tree_builder.c
uni_semantic.c
uni_list.c
uni_user_defined.c
uni_builder.c
uni_datause.c
uni_fileio.c
uni_proccall.c
uni_rproccall.c
uni_pipe.c
uni_rts.c
uni_unparse.c
uni_utility.c +define=UNI_PROGRESS_ROUTINE
%uparse.c.sm == <<Then, change directory to the root of the installation directory (i.e., ...UniCon) and invoke odin to build uparse: "odin %install_uparse".
uni_error.c +define=UNI_ERROR_ROUTINE
uni_lexer.l
uni_main.c
uni_parser.y
uni_tree_builder.c
uni_semantic.c
uni_list.c
uni_user_defined.c
uni_builder.c
uni_datause.c
uni_fileio.c
uni_proccall.c
uni_rproccall.c
uni_pipe.c
uni_rts.c
uni_unparse.c
uni_utility.c +define=UNI_PROGRESS_ROUTINE
/usr/gz/my_parsers.c
/usr/gz/my_checkers.c
User-defined properties, of course, are always optional. If specified, however, there must be a corresponding entry for the propery in a file named "user_defined_properties" in the same directory in the file system from which the UniCon compiler is invoked (i.e., the "local" directory). The entry must be a single line with three fields. The first field is the name of the property, and the other two fields can be anything. All three fields are separated from each other by whitespace. The purpose of the second and third fields has not yet been fixed. These are place holders for future enhancements. The case of the letters for the name in the first field is insignificant.
Subsequent specifications of a user-defined property in a single property list replace earlier specifications (i.e., the last specification is the one that the UniCon compiler uses). Two user-defined attributes are duplicates if the name part is identical (the case of the letters is insignificant) and the value part is identical (the case of the letters is significant for the value part).
COMPONENT KWIC
INTERFACE IS
TYPE Filter
PLAYER input IS StreamIn
SIGNATURE ("line")
PORTBINDING (stdin)
END input
PLAYER output IS StreamOut
SIGNATURE ("line")
PORTBINDING (stdout)
END output
PLAYER error IS StreamOut
SIGNATURE ("line")
PORTBINDING (stderr)
END error
END INTERFACE
IMPLEMENTATION IS
GUI-SCREEN-SIZE ("(list :real-width 800 :width-unit "" :real-height 350 :height-unit "")")
DIRECTORY ("(list "/usr/examples/ upcase.uni" "/usr/examples/cshift.uni" "/usr/examples/ data.uni" "/usr/examples/converge.uni" "/usr/examples/ sort.uni" "/usr/examples/unix-pipe.uni" "/usr/examples/ reverse-f.uni")")
USES caps INTERFACE upcase
GUI-SCREEN-POSITION ("(list :posi tion (@pos 68 123) :player-positions (list (cons "input" (cons `left 0.5)) (cons "error" (cons `right 0.6625)) (cons "output" (cons `right 0.3375))))")
END caps
(remaining definition omitted)
END IMPLEMENTATION
END KWIC