Relationships between major views Component-Connector View and Context View Jianing Hu

advertisement
Relationships between major views
Jianing Hu
April, 2000
Component-Connector View and Context View
A context view is often drawn as a component-connector-like diagram, with the components as entities
and the relations as interactions. In this case, the “machine” in a context view is an abstraction of the
system described in the C-C view. Therefore the relationship between C-C view and context view is that
between an abstract and refined system representation. In a context view constraints could be attached to
the “machine”, (e.g., the protocol used by the machine to communicate with its environment), and we can
check if these constraints are satisfied in the C-C view.
For our compiler example we can check the following for consistency.
 The context view says the source file has to be a character stream. The C-C view is consistent with it
by stating the same. Here the format of source is described in plain English. We could also use other
techniques to describe the format and check the consistency, e.g., Wright protocol description and
checking.
 The context view says the compiler writes to storage via some interface. The C-C view doesn’t
specify this interface. We could extend the description of “Generator” to include the protocol it uses
to write compiled data to storage and can therefore check the consistency.
 The context view says the compiler displays status and error messages on some display device using
a display interface. The C-C view doesn’t show the display device at all. Suppose the representation
of each component in the C-C view describes how it interacts with the display device, we can check
the consistency of the interfaces of the display device in both views.
 The C-C view does not specify from where and how the compiler receives commands. The C-C view
we have is a highly abstracted one and does not contain much detailed information about the
compiler. Detailed information like how the compiler receives commands and the formats of the
commands could be declared in a refined C-C view. Thus we can check the consistency of commands
in both views.
 The context view could also specify performance and resource consumption constraints. These are
specified as system design requirements. The C-C view has to satisfy these constraints. E.g., if the
context view says the compiler needs no more than 32MB of memory to run, the C-C view should
show (probably not directly but through some kinds of analysis) that it requires less than 32 MB of
memory to run.
Component-Connector View and Hardware View
The C-C view shows aspects of the run-time structure of a software system. A hardware view shows the
underlying hardware structure of that system. Generally speaking, relationships between the two views
are resource allocation and performance constraints. Resource allocation shows which resource in the
hardware view is allocated to which element in the C-C view. That could include allocating memory and
processor time to a process, network bandwidth to a pipe, etc. Constraints can be attached to resource
allocation. A basic constraint that all resource allocation has to meet is that the sum of resources allocated
must not be more than the total amount of resources available in the hardware view. Specific constraints
could be specified, too. Performance constraints show the relationships between the performance
properties of the C-C view and those of the hardware view. Performance constraints, resource allocation
and the performance of the hardware together determine the performance of a software element. For
example, the execution speed of a process depends on how much memory and CPU time it is allocated
(resource allocation) and how fast the CPU runs (hardware performance property) and the relation
between the performance of the process and how fast the CPU runs (performance constraint). In the
compiler example we have the following relationships.
 Resource allocation: Memory, storage, and processor time consumption by processes, bandwidth
consumption by pipes. Besides the basic constraint, we could also constrain that the network
utilization to be under a certain limit, or a certain amount of CPU time be reserved for the operating
system, etc.
 Performance constraints: The throughput and latency of a pipe are constrained by the throughput and
latency of the network underneath, the performance of a process is constrained by the speed of
hardware (CPU, memory, backplane, etc.) on which it runs.
 Another analysis we can perform on these two views is finding the best resource allocation scheme.
Performance of a software component/connector could be affected by the amount of resources
allocated. One useful analysis is to find the resource allocation scheme that achieves the best overall
system performance.
Component-Connector View and Layered View
Layered views show the implementation structure of a system. It is not unusual that a system has multiple
layered views, each describing the implementation of a critical element or element type of the system.
The top layer of a layered view hides details of the implementation to the rest of the system. Therefore
when we consider relationships between a layered view and other views, we can focus on the top layer
and ignore the others.
The top layer of a layered view describes software elements that are used in a C-C view. It specifies their
interfaces and rules that have to be obeyed when using them, such as communication protocols. In the
C-C view these elements are used as building blocks and the C-C view might also specify the rules under
which these elements are used. It is very important to make sure the rules specified in both views agree
with each other.
When an element in the C-C view is composed of elements in the layered view, the performance of the
composed element depends on the performances of elements it uses, and the way it interacts with those
elements. The relations between performance properties can be captured as performance constraints.
Context View and Hardware View
Both views describe the environment in which the system runs, but from different points of view.
Generally there is no direct relationship between the two. In the compiler example, the context view has
components called “storage” and “display”, which also appear in the hardware view. The difference is
that in the context view these devices are considered abstract devices while in hardware view their
hardware implementation details are revealed. We can check the consistency between the devices. For
example, if the context view says the display is a GUI, then the display component in the hardware view
must be able to display graphics. Another example of consistency constraint is that the storage device in
the hardware view must be writable, because the context view says compiled code is written to it.
Context View and Layered View
No relationship is found between the two views for the compiler example. However, in general a
component in the context view might correspond to the top layer of a layered view. Suppose we have a
layered view of the display device, then its top layer would be the interface that the compiler calls and we
can then check the consistency of the interface specified in the layered view and that specified in the
context view.
Hardware View and Layered View
In some cases counterparts of layers can be found in the hardware view. In the compiler example, the
network stack’s CSMA/CD layer corresponds to LAN in the hardware view. Consistency can therefore
be checked. Consistency constraints between the two views include performance consistency, e.g.,
throughput of a CSMA/CD layer link has to be less than throughput of the LAN, and protocol consistency,
e.g., the LAN has to be an Ethernet LAN that implements CSMA/CS protocol.
Summary
Generally speaking, the constraints between views are all consistency constraints. Consistency
constraints ensure that different views don’t conflict each other. Consistency constraints can be further
divided into sub-categories. In the compiler example we see the following kinds of consistency
constraints:
-
Interface consistency: ensures two views agree on the interface of the same element, e.g., the
agreement between the C-C view and the context view on the display interface.
-
Typing consistency: constrains format/type of data, e.g., both the C-C view and the context view
must agree on the format of source files.
-
Protocol consistency: ensures communication protocols are held consistently across views. Formal
specification languages like Wright can be used in protocol specification and checking.
-
Existence consistency: different views have to agree on the existence of an element. E.g., the context
view and the hardware view must agree on the existence of the display device. This constraint does
not have to be a 1-1 relation.
-
Functionality consistency: different views have to agree on the functionality of an element. E.g., if
the context view says the display device is capable of displaying graphs, the hardware view should
specify so, too.
-
Performance consistency: declares associations between performance properties. It often shows
dependency of performance properties in different views. For example the performance of a pipe
depends on the performance of the LAN over which the pipe is created.
Question: How could constraints be violated and what are the consequences?
Answer: Constraints could be violated in various ways. Two views could be inconsistent because they
are developed by different people who have different knowledge of the system, or because one is updated
and the other is not, or even because of a typo. Inconsistency between views could lead to serious system
deficiency. A performance inconsistency could lead to a system that doesn't satisfy its performance
requirements; a protocol inconsistency might result in a system that can not communicate with its
environment correctly; a functionality inconsistency could make the system lack critical functionality.
Because major views are developed at the beginning of system development and used as guidance
through the development process, it is critical to find the inconsistencies early and keep checking them
when views are updated.
Pipe Layer Protocol
In this section we describe the calling protocol to functions pipe(), read(), write() and close(), in the pipe layer.
To facilitate understanding our descriptions, let us first introduce our notations of sets, tuples, and lists, as the
following:
{} : an empty set; ∪ : set union -- {A}∪{B} = {A,B}
[A,B] : a tuple that contains A and B
< > : an empty list; ^ : list concatenation -- <A>^<B> = <A,B>
States of the pipe layer are represented by processes with subscripts. Two processes represent different states if
they have different subscripts. Each subscript is a set of tuples. A tuple in the subscript set is in the form of
[[fd1,L1],[fd1,L2]]. It represents the state of a pipe, or, a pair of file descriptors, each represented by a tuple in
the form of [fd,L], respectively. The tuple [fd,L] represents the value of the file descriptor as an integer fd1,
and the data that has been written to the file descriptor but has not been read out at the other end of the pipe yet,
as a list L. The whole subscript set represents the states of all opened pipes in that pipe layer state.
SPEC = Open1{} where
Open1S = PipeS ▯ ReadS▯ WriteS▯ CloseS
PipeS = pipe()  ((return [fd1,fd2]Open1s∪{[[fd1,<>],[fd2,<>]]})
⊓ (return [-1,-1]Open1S))
(read(fd1)!x2Open1S'∪{[[fd1,L1'^<x1>],[fd2,L2']]})
▯ (read(fd2)!x1Open1S'∪{[[fd1,L1'],[fd2,L2'^<x2>]]}))
when S = S'∪{[[fd1,L1'^<x1>],[fd2,L2'^<x2>]]} and fd1 ≠ -1 and fd2 ≠ -1
ReadS =
read(fd1)!xOpen1S'∪{[[fd1,L1],[-1,L2']]}
when S = S'∪{[[fd1,L1],[-1,L2'^<x>]]} and fd1 ≠ -1
read(fd2)!xOpen1S'∪{[[-1,L1'],[fd2,L2]]}
when S = S'∪{[[-1,L1'^<x>],[fd2,L2]]} and fd2 ≠ -1
(write(fd1,x1)Open1S'∪{[[fd1,<x1>^L1],[fd2,L2]]})
▯ (write(fd2,x2)Open1S'∪{[[fd1,L1],[fd2,<x2>^L2]]}))
when S = S'∪{[[fd1,L1],[fd2,L2]]} and fd1 ≠ -1 and fd2 ≠ -1
WriteS =
write(fd1,x)Open1S'∪{[[fd1,<x>^L1],[-1,L2]]}
when S = S'∪{[[fd1,L1],[-1,L2]]} and fd1 ≠ -1
write(fd2,x)Open1S'∪{[[-1,L1],[fd2,<x>^L2]]}
when S = S'∪{[[-1,L1],[fd2,L2]]} and fd2 ≠ -1
(close(fd1)Open1S'∪{[[-1,L1],[fd2,L2]]})
▯ (close(fd2)Open1S'∪{[[fd1,L1],[-1,L2]]}))
when S = S'∪{[[fd1,L1],[fd2,L2]]} and fd1 ≠ -1 and fd2 ≠ -1
CloseS =
close(fd1)Open1S'
when S = S'∪{[[fd1,L1],[-1,L2]]} and fd1 ≠ -1
close(fd2)Open1S'
when S = S'∪{[[-1,L1],[fd2,L2]]} and fd2 ≠ -1
This specification described a basic function calling protocol:
 read() or write() to a file descriptor can not be called if that file descriptor has not bee allocated by calling
pipe()
 read() or write() to a file descriptor can not be called if that file descriptor is closed by calling close and has
not bee reallocated by another pipe() call.
 read() from a file descriptor can not be called if there is no data for that file descriptor to read in the pipe.
The protocol defined above describes some basic constraints on function call sequences. However, it is still not
the "real" protocol to call these functions, particularly, in this protocol one can call Write() at one end of a pipe
arbitrary number of times without calling read() at the other end. In reality a pipe has certain capacity. If it
reaches its full capacity, calling write() does not change its state any more.
To address the pipe capacity issue in our protocol, we need to add the capacity and load of a pipe to its state
representation. This is done by adding capacity and load elements to the tuple that represents a file descriptor.
Now the tuple is in the form of [fd,L,n,c], where fd and L have the same meanings as in the protocol described
above, n is the length of L, i.e., the load of fd, and c is the capacity of fd. The values of n and c have to satisfy
that n<=c at any time. Below we describe a protocol in which all file descriptor capacities are set to 5.
SPEC = Open2{} where
Open2S = PipeS ▯ ReadS▯ WriteS▯ CloseS
PipeS = pipe()  ( (return [fd1,fd2]Open2s∪{[[fd1,<>,0,5],[fd2,<>,0,5]]})
⊓ (return [-1,-1]Open2S) )
(read(fd1)!x2Open2S'∪{[[fd1,L1'^<x1>,n1,c1],[fd2,L2',n2-1,c2]]})
▯ (read(fd2)!x1Open2S'∪{[[fd1,L1',n1-1,c1],[fd2,L2'^<x2>]]}))
when S = S'∪{[[fd1,L1'^<x1>,n1,c1],[fd2,L2'^<x2>,n2,c2]]} and fd1 ≠ -1 and fd2 ≠
-1
ReadS =
read(fd1)!xOpen2S'∪{[[fd1,L1,n1,c1],[-1,L2',n2-1,c2]]}
when S = S'∪{[[fd1,L1,n1,c1],[-1,L2'^<x>,n2,c2]]} and fd1 ≠ -1
read(fd2)!xOpen2S'∪{[[-1,L1',n1-1,c1],[fd2,L2,n2,c2]]}
when S = S'∪{[[-1,L1'^<x>,n1,c1],[fd2,L2,n2,c2]]} and fd2 ≠ -1
(write(fd1,x1)Open2S'∪{[[fd1,<x1>^L1,n1+1,c1],[fd2,L2,n2,c2]]})
▯ (write(fd2,x2)Open2S'∪{[[fd1,L1,n1,c1],[fd2,<x2>^L2,n2+1,c2]]}))
when S = S'∪{[[fd1,L1,n1,c1],[fd2,L2,n2,c2]]} and fd1 ≠ -1 and fd2 ≠ -1 and n1 < c1
and n2 < c2
write(fd1,x)Open2 S'∪{[[fd1,<x>^L1,n1+1,c1],[fd2,L2,c2,c2]]}
when S = S'∪{[[fd1,L1,n1,c1],[fd2,L2,c2,c2]]} and fd1 ≠ -1 and n1 < c1
write(fd2,x)Open2 S'∪{[[fd1,<x>,c1,c1],[fd2,<x>^L2,n2+1,c2]]}
when S = S'∪{[[fd1,L1,c1,c1],[fd2,L2,n2,c2]]} and fd2 ≠ -1 and n2 < c2
WriteS =
write(fd1,x)Open2S'∪{[[fd1,<x>^L1,n1+1,c1],[-1,L2,n2,c2]]}
when S = S'∪{[[fd1,L1,n1,c1],[-1,L2,n2,c2]]} and fd1 ≠ -1 and n1 < c1
write(fd2,x)Open2S'∪{[[-1,L1,n1,c1],[fd2,<x>^L2,n2+1,c2]]}
when S = S'∪{[[-1,L1,n1,c1],[fd2,L2,n2,c2]]} and fd2 ≠ -1 and n2 < c2
(close(fd1)Open2S'∪{[[-1,L1,n1,c1],[fd2,L2,n2,c2]]})
▯ (close(fd2)Open2S'∪{[[fd1,L1,n1,c1],[-1,L2,n2,c2]]}))
when S = S'∪{[[fd1,L1,n1,c1],[fd2,L2,n2,c2]]} and fd1 ≠ -1 and fd2 ≠ -1
CloseS =
close(fd1)Open2S'
when S = S'∪{[[fd1,L1,n1,c1],[-1,L2,n2,c2]]} and fd1 ≠ -1
close(fd2)Open2S'
when S = S'∪{[[-1,L1,n1,c1],[fd2,L2,n2,c2]]} and fd2 ≠ -1
Compatibility Checking
With the layered view specifies how the functions it provides should be used, we could check if they are
used in the C-C view as they are supposed to be. Let us check if the pipe layer functions are correctly
called in the C-C view. Of course, to do that, we need to specify the pipe protocol in the C-C view, too.
Followed is a pipe protocol from [AG98].
connector Pipe =
role Writer = writeWriter Π close§
role Reader = let ExitOnly = close§
in let DoRead = (readReader▯read-eofExitOnly)
in DoRead Π ExitOnly
glue = let ReadOnly = Reader.readReadOnly
▯Reader.read-eofReader.close§
▯Reader.close§
in let WriteOnly = Writer.writeWriteOnly▯Writer.close§
in Writer.write->glue▯Reader.readglue▯Writer.closeReadOnly▯Reader.closeWriteOnly
Showing that the pipe protocol is a correct realization of the pipe layer protocol is a similar problem as
showing the compatibility of a role and a port. We basically require the pipe protocol to be a refinement of the
pipe layer protocol. Why is it the case? In short, for a process P to be a refinement of a process Q, P has to
satisfy all the properties of Q. That means, if the pipe protocol is a refinement of the pipe layer protocol, then
all traces of the pipe protocol can be "replayed" in the pipe layer protocol, i.e., each event trace of the pipe
protocol can find its corresponding function calling sequences in the pipe layer protocol. On the other hand, if
the pipe protocol is not a refinement of the pipe layer protocol, then there must be a trace of the pipe protocol
that can not be "replayed" in the pipe layer protocol. That means the pipe protocol tries to use these functions
defined in the pipe layer in a way that is not allowed by the pipe layer protocol.
Even without using a checker like FDR we can see that the pipe protocol we have here is not a refinement of
the pipe layer protocol described in the last section. Our pipe protocol here does not have initialization (calling
pipe()) and allows read and write at any time regardless of the load and capacity of the pipe. That is obviously
inconsistent with our pipe layer protocol which says: before reading or writing, one has to call pipe() to
allocate file descriptors; one can not read from an empty pipe, and, the number of writes allowed is constrained
by the capacity of the pipe.
However, such an inconsistency between the C-C view and layered view does not necessarily mean fault
designs. In fact, people all tend to (as a right practice) ignore implementation details like initialization,
protocol details, when they do the high level design. Unnecessary details in the high level design could make it
hairy and hard to understand and communicate. At some point people will turn the high level designs into
lower level designs that are more implementation oriented and that is when such inconsistencies should be
checked and resolved.
question: our pipe layer protocol has infinite states. Can it be checked? If it can't be, we can actually
make a finite state protocol. That could lead to even more analysis.
Download