Initiating a Design Pattern Catalog for Embedded Network Systems

3 downloads 82 Views 238KB Size Report
In this paper, we initiate a pattern catalog for embedded network software. .... source of design patterns for embedded network systems. We present each pattern ...
Initiating a Design Pattern Catalog for Embedded Network Systems Sally K. Wahba

Jason O. Hallstrom

Neelam Soundarajan

School of Computing Clemson University Clemson, SC 29634 USA

School of Computing Clemson University Clemson, SC 29634 USA

Computer Science and Eng. Ohio State University Columbus, OH 43210 USA

[email protected]

[email protected]

[email protected]

ABSTRACT

a range of systems. The benefits are both prescriptive, since patterns inform good design choices based on time-tested solutions; and descriptive, since patterns provide an extended vocabulary for documenting software design. Naturally, patterns continue to play a prominent role in the design of every major desktop system, class library, and programming language. The concept is simple, the benefits profound. The state of the art in embedded network system design presents a stark contrast: The tremendous benefits afforded by patterns have yet to reach developers working in this new domain. The constituent systems are composed of tiny wireless nodes that sense, process, communicate, and react to environmental stimuli. The application areas are broad, ranging from in situ earth observation [30], to civil infrastructure monitoring [12], to wildfire management [6], to cite just a few examples. With burgeoning interest across disparate fields, it is natural to question why the benefits of design patterns have yet to be extended to the domain. The explanation is simple: Embedded network systems are fundamentally different than desktop systems; many, if not most, existing patterns do not apply. The embedded networking domain is characterized by highly distributed, reactive systems that execute on hardware Categories and Subject Descriptors platforms with significant resource limitations. Typical “moD.2.2 [Software Engineering]: Design Tools and Techte”-class platforms (e.g., [20]) provide orders of magnitude niques; D.2.10 [Software Engineering]: Design; D.2.11 less resource than standard desktop machines in every salient [Software Engineering]: Reusable Software; D.2.11 [Software dimension — processing power, storage space, bandwidth, Engineering]: Software Architecture; C.2.4 [Computerand energy. In many cases, these limitations preclude the Communication Networks]: Distributed Systems application of existing patterns. Design patterns for traditional networked systems [26], for instance, largely neglect General Terms the resource capacity of target hardware platforms; they Design, Documentation, Languages tend to rely on design elements that are inappropriate for small embedded processors (e.g., heavy-weight data strucKeywords tures, runtime memory management, dynamic dispatch). Patterns, embedded networks, sensor networks, nesC, TinyOS Further, emerging languages and support libraries for embedded network construction are tailored to accommodate 1. INTRODUCTION a high degree of concurrency and favor the use of call-back Design patterns [3, 5, 9, 26] have left an indelible mark on mechanisms that allow the processor to enter a low-power the state of software practice. They distill expert knowlstate while waiting on operations to complete. The resulting edge in the form of problem-solution pairs applicable across programming models are fundamentally inconsistent with the prescriptive advice codified in existing patterns. Yet even if the platforms were to evolve significantly and programming models were to align more closely with traditional Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are object- and component-based paradigms, a gap in the patnot made or distributed for profit or commercial advantage and that copies tern foundation would remain. Embedded network systems bear this notice and the full citation on the first page. To copy otherwise, to present a combination of design challenges that are unique republish, to post on servers or to redistribute to lists, requires prior specific to the domain. Despite the broad base of catalogs in the permission and/or a fee. literature, these challenges are yet to be addressed. EMSOFT’10, October 24–29, 2010, Scottsdale, Arizona, USA.

In the domain of desktop software, design patterns have had a profound impact; they are applied ubiquitously across a broad range of applications. Patterns serve both to promulgate expert knowledge and as a vocabulary for documenting software design. The result is higher-quality software, reduced development effort, and improved documentation. This impact has yet to be felt in the emerging domain of embedded network software. The applications are characterized by highly distributed, reactive computations executed over resource-constrained processors; they present a host of new design challenges. Without a coherent repository of expert solutions, best practices do not emerge, design flaws are repeated, and developer communication is hindered. In this paper, we initiate a pattern catalog for embedded network software. The contributions are two-fold. First, we present three new design patterns distilled from a careful analysis of expert-crafted software. Second, we present a formalism for specifying the structural requirements of such patterns. The resulting specifications provide a rigorous foundation for pattern identification and documentation.

Copyright 2010 ACM 978-1-60558-904-6/10/10 ...$10.00.

In this paper, we seek to address these problems by initiating a new pattern catalog. We present three new design patterns culled from a careful analysis of expert-crafted systems. The first is focused on minimizing the storage overhead of recurring data elements. The second is focused on reducing the cognitive burden of control flow and synchronization management across coordinated interrupt handlers. Finally, the third is focused on the design of pin-level control interfaces to support device driver adaptability across hardware platforms. The objective is for these patterns to serve as a catalyst for the development of a larger community resource of design patterns for embedded network systems. We present each pattern using a stylized narrative format similar to that introduced by Gamma et al. in their seminal catalog [9]. In addition, we introduce a first-order logic formalism for characterizing the structural requirements of these (and other) patterns. The formalism is used to supplement the informal descriptions with formal characterizations of each pattern and its use in existing systems. This supplemental documentation provides a rigorous basis for documenting pattern requirements, validating structural conformance, and identifying pattern instances in candidate systems. While designed to support the codification of the newly identified patterns, the formalism is a significant contribution in its own right. Paper Organization. Section 2 surveys related work in pattern identification and briefly considers related pattern formalization efforts. Section 3 summarizes our approach to pattern identification and summarizes our documentation format, including the first-order logic formalism. Sections 4, 5, and 6 present the three new patterns, respectively. Finally, Section 7 concludes with a summary of contributions.

2.

RELATED WORK

In the province of desktop system design, patterns are ubiquitous across application domains. Here we restrict our survey to patterns for traditional embedded systems. Konrad and Cheng [17] introduce the notion of a requirements pattern to capture the functional and non-functional requirements of a recurring system feature. While similar to design patterns, these patterns provide relatively abstract advice; design guidelines are omitted. The authors present several such patterns. Fault Handler, for instance, describes the requirements to be satisfied when integrating a set of fault handlers intended to increase system reliability. Each handler is responsible for detecting a particular type of fault and initiating the appropriate recovery actions. While Konrad and Cheng do not focus on embedded networks explicitly, their patterns bear relevance to the domain. Pont [22] presents a catalog of design patterns for increasing the safety and reliability of applications developed for the popular 8051 family of microcontrollers. Loop Timeout, for example, describes an approach to preventing a system from becoming unresponsive due to a busy-wait condition on a failed hardware operation. The pattern prescribes the use of a counter variable that triggers wait-loop termination on overflow. Pont et al. expand on this catalog with additional patterns specific to 8051- and ARM-based systems, as well as patterns to support PC-based prototyping of such systems [21, 23–25]. Heartbeat LED and Error LED, for example, describe techniques for providing status and debugging information when developing on a headless device. Port Wrapper describes an approach to managing the

allocation and initialization of port pins to ensure proper initialization. The pattern targets large projects where pin conflicts may occur among developers. While Pont et al.’s focus on embedded system design aligns with our focus on embedded network systems, many of the patterns they identify are specific to the 8051 and ARM microcontroller families. By contrast, our patterns are platformindependent. Pont et al. consider applications developed in C, while our patterns are language independent. Further, the authors largely emphasize hardware architecture over software design, in contrast to the emphasis of our efforts. Closest to our work is that of Gay et al. [11], who describe a set of idioms for TinyOS-based systems [15]. Many of these are adaptations of design patterns identified by Gamma et al. [9] and Buschman et al. [3], modified to suit the TinyOS programming model. New idioms not adapted from other patterns include Placeholder, Service Instance, Keysets, and Keymap. Placeholder prescribes an approach to preventing the inclusion of incompatible service implementations by ensuring the use of a single implementation throughout a system. Conversely, Service Instance prescribes an approach to using multiple implementations of a given service while supporting efficient interaction among them. Keysets is focused on efficient namespace management for program entities, such as protocols and operations. For example, the nesC constant generator assigns each instance of TimerC a unique identifier. These keys constitute a keyset used by Service Instance to determine the particular timer instance to service. The pattern is focused on local identifier managment. Keymap is focused on mapping between different keysets using nesC wiring statements. While the goals of Gay et al. are similar to ours, their work is specific to TinyOS. While most of the work on patterns, including the work of the above authors, follows the informal narrative style introduced by Gamma et al. [9], a few authors have explored formalisms that allow more precise documentation of patterns for object-oriented systems. Helm et al. [14] were among the first to consider pattern formalization in this context. The authors describe a pattern contract notation used to capture the canonical roles of a pattern. In their approach, each role is documented in terms of the required fields and method signatures, as well as pre/post specifications that capture the associated behaviors. An important aspect of their approach is the ability to specify calling conditions among methods: That one method eventually invoke another may be specified as part of a calling method’s post-condition. Our prior work in pattern formalization [13, 28] follows a similar style, but improves upon the approach of Helm et al. in a number of significant ways. Most notably, the formalism improves specification precision by adopting the notion of a trace as a formal mathematical representation of method calling behaviors. At the same time, the specifications are parameterized along a number of structural and behavioral dimensions, allowing the specifications to be tailored as appropriate to particular systems. Hence, the formalism balances the inherent tension between specification precision and pattern flexibility. Other authors have focused on specifying structural requirements. Eden [7] presents a higher-order logic formalism for specifying structural pattern properties. In his approach, each pattern is represented by a quantified assertion that identifies the participating classes, methods, and inheritance hierarchies, as well as the relations among these ele-

ments. Taibi and Ngo [29] follow an analogous approach, but adopt a simplified first-order logic notation [1]. The work of Kim and Carrington [16] is similar, but conducted in the context of Object-Z [27]. While the formalism introduced in this manuscript builds on the work of these authors — Taibi and Ngo, in particular— it is tailored for applications written in nesC1 [10] and C, commonly used programming languages for embedded network system construction. The programming models are fundamentally incompatible with the object-oriented paradigm.

3.

IDENTIFICATION, DOCUMENTATION

Before presenting the new patterns, it is useful to summarize our identification approach and documentation format. With regard to the former, we are involved in the construction of a large-scale watershed monitoring system [8], which requires the development of low-level platform and networking libraries for a new embedded device. Over time, we discovered that we were encountering recurring design obstacles and applying variations on solutions that had proven effective previously. These recurring problem-solution pairs are natural candidates for consideration as new patterns. For each candidate, we attempt to identify variations of the solution in systems developed independently of our group. While system selection is subjective, we attempt to focus on expert-crafted systems, evidenced by the qualifications of the development team (if available) or the level of system adoption. To validate our understanding of the candidate and its use in existing systems, we develop a formal specification of the pattern’s structural requirements and validate the conformance of the pattern applications to these requirements. While behavioral requirements are also examined, the conformance checks are performed informally. This validation process is useful in two ways. First, it helps us to refine our understanding of the candidate pattern, which results in an improved pattern description — one that captures the essential details without limiting flexibility dimensions that should be preserved. Second, it helps to ensure that the system examples do indeed exhibit the candidate structure. Our documentation format is based on that of Gay et al. [11], which is in turn based on the narrative style popularized by Gamma et al. [9]. This format has proven valuable to developers in the object-oriented space, and we expect the same to be true in this new context. We additionally supplement each description with a formal characterization of the pattern’s structural requirements and corresponding specifications of the pattern’s use in example systems.

3.1

Structural Formalism

We now consider the specification formalism. In this notation, adapted from the object-oriented work of Taibi and Ngo [29], each pattern is represented by an assertion in firstorder logic. The terms of the formalism correspond to structural pattern elements (e.g., operations, variables), while the relations capture the relationships among them (e.g., calling relationships, containment relationships). The terms of the formalism are as follows: constants (C), variables (V), operations (O), header files (Fh ), implementation files (Fi ) (C or nesC implementations), and (nesC) 1

While TinyOS refers to an operating system and nesC refers to a language, the two are so tightly coupled that the names are used interchangeably.

configuration files (Fn )2 . Table 1 summarizes the relations that may hold among these elements: Implemented in(o,f) denotes that an operation o is implemented in a file f, which may either be a header file or an implementation file. The relations Defined in(c,f) and Defined in(v,f) denote that a constant c or a variable v, respectively, is defined in a file f, which may either be a header file or an implementation file. The relations Accessed in(c,o) and Accessed in(v,o) denote that a constant c or a variable v, respectively, is accessed in operation o. Global(v) denotes that a variable v is global in scope. Invocation(o1 ,o2 ) denotes that an operation o1 invokes another operation o2 . The relations Passed to(c,o) and Passed to(v,o) denote that a constant c or a variable v, respectively, is passed as an argument to an operation o in some call to the operation. Includes(f1 ,f2 ) denotes that a file f1 , representing a nesC configuration file, header file, or implementation file, includes a header file f2 . Computed from(o,v,c) and Computed from(o,v,v1 ) denote that in operation o, the value of variable v is computed from (or depends on) constant c or variable v1 , respectively. Reachable(o1 ,o2 ) denotes that operation o1 is reachable from operation o2 (i.e., there is a static call chain between the operations). Finally, Externalizes(o) denotes the existence of a call chain from operation o to a specially-designated operation that communicates with an external device (e.g., a remote basestation). While communication requirements are, in general, behavioral in nature (and thus not statically checkable), we recast this property as structural by assuming a priori determination of the operations that communicate with external devices. It is worth emphasizing that each structural specification serves as a kind of template. For a given pattern, the corresponding structural specification is represented by a quantified assertion that posits the existence of various structural elements and relationships. By substituting applicationlevel elements (e.g., operations, variables) for the unbound variables in the assertion, developers can arrive at applicationspecific implementation requirements for a pattern. If these requirements are satisfied by the application, the structural demands of the pattern are met. We will see examples of this substitution-based conformance validation procedure in subsequent sections.

4. BASESTATION DICTIONARY 4.1 Intent Provide a mapping from space-efficient keys (used by an embedded application) to larger constants (used by a basestation application) to overcome storage, bandwidth, and processing capacity limitations.

4.2

Motivation

Embedded platforms are characterized by limited computational resources. Storage, bandwidth, and processing constraints demand efficient data representations. Consider the storage of string constants. Storing long strings or long string sequences can quickly exhaust RAM resources. Less obvious is that long string literals can have a significant impact on ROM usage since they are stored as part of the compiled program image. Moreover, copying strings between storage locations, including non-volatile EEPROM storage, consumes precious cycles and energy. Finally, transmitting 2 When configuration files and the corresponding relations are omitted, a C-based dialect of the notation results.

Relation Implemented in(o,f ) Defined in(c,f ) Defined in(v,f ) Accessed in(c, o) Accessed in(v, o) Global(v) Invocation(o1 ,o2 ) Passed to(c,o) Passed to(v,o) Includes(f1 ,f2 ) Computed from(o, v, c) Computed from(o, v, v1 ) Reachable(o1 ,o2 ) Externalizes(o)

Domain O x Fh,i C x Fh,i V x Fh,i CxO VxO V OxO CxO VxO Fh,i,n x Fh OxVxC OxVxV OxO O

Intent Denotes Denotes Denotes Denotes Denotes Denotes Denotes Denotes Denotes Denotes Denotes Denotes Denotes Denotes

that that that that that that that that that that that that that that

operation o is implemented in file f constant c is defined in file f variable v is defined in file f a constant c is accessed in operation o a variable v is accessed in operation o a variable v is global operation o1 invokes operation o2 constant c is passed as an argument to operation o variable v is passed as an argument to operation o file f1 includes file f2 in operation o the value of variable v is computed from a constant in operation o the value of variable v is computed from variable v1 operation o1 is reachable from operation o2 operation o communicates a message to an external device

c

Table 1: Structural Relations string constants between network nodes can swamp network bandwidth. While the impact of storing string constants on resource consumption is not unique to embedded network systems, the effects are amplified in this context due to the severe resource limitations of the target hardware platforms. The Basestation Dictionary pattern describes an approach to replacing large program constants (e.g., strings) with spaceefficient keys (e.g., integers) without sacrificing the human readability of the constants by end-users. The embedded application uses keys in place of the larger constants, deferring their interpretation to a resource-rich basestation device. When a basestation receives a particular key, it uses its local dictionary to translate the key to its appropriate human-readable representation. Whether stored in EEPROM, copied, or sent over the network, the keys consume fewer resources than would the original constants.

4.3

Applicability

Use Basestation Dictionary when human-readable constants are processed, stored, or communicated to a basestation from an embedded network.

4.4

Participants

Basestation Dictionary: A basestation component that maps small integral keys to larger data constants. The component is used to translate key values received from an embedded device to corresponding values. Embedded Key User: An embedded component that uses space-efficient keys to store, process, and communicate information to a basestation.

4.5

Sample Code Listing 1: Basestation Dictionary Example

1 2 3 4 5 6 7 8 9 10 11

// header.h #define MESSAGE_1 1 /* "test 3 passed in state 12" */ #define MESSAGE_2 2 /* "check failed" */ ... // Client.nc #include header.h ... module Client { uses interface Logger; ... } implementation { ... task void checkState() { ... if (state == 12 && testPassed) { call Logger.writeMessage(MESSAGE_1); } ... } }

Sample code for an Embedded Key User is provided in Listing 1. Lines 2–3 define the keys used by the component and mapped to the Basestation Dictionary. The comments indicate the corresponding string values to which the keys

∃ client, sender, communicator ∈ Fi ; keys ∈ Fh ; key 1 ... key n ∈ C; operation 1 ... operation n, communicate, send ∈ O Defined in(key 1, keys) ∧ ... ∧ Defined in(key n, keys) ∧ Implemented in(operation 1, client) ∧ ... ∧ Implemented in(operation n, client) ∧ Implemented in(communicate, communicator) ∧ Implemented in(send, sender) ∧ Externalizes(send) ∧ Includes(client, keys) ∧ (Accessed in(key 1, operation 1) ∨ ... ∨ Accessed in(key n, operation 1)) ∧ (Accessed in(key 1, operation n) ∨ ... ∨ Accessed in(key n, operation n)) ∧ Invocation(operation 1, communicate) ∧ ... ∧ Invocation(operation n, communicate) ∧ Passed to(key 1, communicate) ∧ ... ∧ Passed to(key n, communicate) ∧ Reachable(send, communicate) Table 2: Basestation Dictionary Struct. Spec. are mapped. For example, Message 1 is mapped to and used in place of the string “Test 3 passed in state 12”. Lines 10 –11 illustrate one possible use of the defined keys. In the example code, the embedded device checks whether a given test has passed in a particular state of the application. If so, the fact is registered through a data logging component, which may transmit directly to the basestation or store the information in an EEPROM log for later analysis. Sample code for the Basestation Dictionary participant has been omitted due to space constraints. When the basestation receives a message from a network node or examines the contents of a stored log, it refers to its map to decipher the keys and present the appropriate string values.

4.6

Structural Specification

Table 2 shows the structural specification of the Embedded Key User participant. (Due to space limitations, we omit the structural specification of the Basestation Dictionary participant.) The top portion of the table denotes the different entities comprising the structure of the Embedded Key User; the second defines the relationships among them. The top portion asserts the existence of three implementation files: client contains the client application and uses the various keys, sender externalizes the messages sent from an embedded device, and communicator creates the messages sent by the sender. The header file keys contains the declarations of the keys used in the application. An arbitrary number of constants key 1 – key n are used to represent the

amradio.c, eventLogger.c, serialPrint.c ∈ Fi ; eventLogger.h ∈ Fh ; event 1, event 2 ∈ C; usartPrint, addTrace, amsend, received ∈ O; Defined in(event 1, eventLogger.h) ∧ Defined in(event 2, eventLogger.h) ∧ Implemented in(amsend, amradio.c) ∧ Implemented in(received, amradio.c) ∧ Implemented in(addTrace, eventLogger.c) ∧ Implemented in(usartPrint, serialPrint.c) ∧ Externalizes(usartPrint) ∧ Includes(amradio.c, eventLogger.h) Accessed in(event 1, amsend) ∧ Accessed in(event 2, received) ∧ Invocation(amsend, addTrace) ∧ Invocation(received, addTrace) ∧ Passed to(event 1, addTrace) ∧ Passed to(event 2, addTrace) ∧ Reachable(usartPrint, addTrace) ∧ key 1/event 1, key n/event 2, keys/eventLogger.h, client/amradio.c, sender/serialPrint.c, communicator/eventLogger.c, operation 1/amsend, operation n/received, communicate/addTrace, send/usartPrint, Table 3: LiteOS Structural Specification keys. There are a number of operations, operation 1() – operation n() that use the various keys to communicate with an external device. Finally, there are two other operations: communicate() uses the keys to create the messages sent from an embedded device and send() externalizes those messages. The bottom portion of the table shows the relations among these elements. The various constants key 1 – key n must be defined in the header file keys. The operations operation 1() – operation n() are implemented in client; communicate() and send() are implemented in files communicator and sender, respectively. The operation send() communicates a message to an external device. The implementation file client includes the header file keys. The various keys key 1 – key n are accessed in operations operation 1() – operation n(), which invoke communicate() to communicate the keys to an external device. Finally, the constants key 1 – key n are passed as arguments to communicate(), which in turn calls send() (possibly indirectly).

4.7

Known Uses

Basestation Dictionary is used multiple times in the design of the LiteOS operating system [4]. In one application of the pattern, LiteOS uses the pattern to provide its event logging functionality. LiteOS uses a 1-byte key mapped to up to 256 different events. When an event occurs, the key is recorded to external flash. In a later stage, the resulting log can be extracted by the basestation for analysis, where each recorded key is mapped to the corresponding event description. In another application of the pattern, LiteOS uses the pattern for debugging purposes. When an event occurs, LiteOS passes the event number in a serial message to the basestation for debugging purposes. Table 3 shows the structural specification of Basestation Dictionary as used in LiteOS and its conformance to the structural specification of the pattern described in Table 2. The table shows the application of the pattern for logging radio messages. LiteOS serially sends event keys to the basestation as radio events occur. The top portion of the table lists three implementation files, one header file, two constants, and four operations. The files amradio.c, eventLogger.c, and serialPrint.c are implementation files written in

C, used for sending and receiving radio messages, logging events, and sending serial messages to the basestation, respectively. The file eventLogger.h is a header file, which includes the different event keys used by the logger. The constants event 1 and event 2 denote two different events used by the debugging feature. Finally, the operations usartPrint(), addTrace(), amsend(), and received() serve to send messages serially to the basestation, record event keys, send radio messages, and receive radio messages, respectively. For the sake of presentation, we replace the actual event names by event 1 and event 2 and substitute the operation amsend() for the actual name. We show only two representative radio operations and their respective events. The middle portion of Table 3 shows the relations among these elements. The two event keys event 1 and event 2 are defined in the header file eventLogger.h. Both operations amsend() and receive() are implemented in amradio.c. The operation addTrace() is implemented in eventLogger.c, while usartPrint() is implemented in serialPrint.c. Operation usartPrint() sends a serial message to the basestation. The implementation file amradio.c includes the header file eventLogger.h. The different operations amsend() and receive() access the appropriate event keys event 1 and event 2. Both operations invoke addTrace() to log the event numbers. The two event keys event 1 and event 2 are passed as arguments to operation addTrace() to be communicated to the basestation. Finally, operation addTrace() calls usartPrint() directly (i.e., a call chain of length 1). The bottom portion of Table 3 shows the mapping of elements from the structural specification of LiteOS to those of Basestation Dictionary. The constants key 1 and key n are mapped to event 1 and event 2, respectively; the header file keys is substituted by eventLogger.h; etc. Using these substitutions in the predicates that specify the structure of Basestation Dictionary yields an application-specific requirements specification — one that is satisfied by LiteOS. A similar application of the pattern appears in the design of the prototyping module provided by the Mantis operating system [2]. The module is used for application testing prior to deployment. When used to build an application, events are recorded as keys and transmitted over a serial connection to a basestation, where they are mapped to the appropriate strings before being displayed. We omit a detailed treatment due to space constraints.

4.8

Consequences

When implementing Basestation Dictionary, the developer needs to ensure that each constant is associated with a unique key. Additional key-to-value mappings may be added if the value set changes dynamically. However, introducing dynamism in the mapping process requires that care be taken to maintain consistency between the keys used by an embedded device and the corresponding map stored at the basestation. In some cases, it is possible for the basestation to generate the key definitions used within the network. When a change is required, the embedded application can be automatically regenerated and reinstalled; see, for example, the approach described in [31]. The pattern imposes overhead on the basestation since the device must maintain the requisite data structure and perform the mapping process when keys are received from the network. However, this is usually of limited concern given the resource capacity of typical basestations.

5. CONTROL FLOW MEDIATOR 5.1 Intent

5.5

Simplify control flow and synchronization logic by linearizing interrupt-triggered events, while minimizing execution in interrupt context.

1

Listing 2: Control Flow Mediator Example 2 3 4 5

5.2

Motivation

Embedded network systems are inherently reactive. Program logic is scattered throughout a collection of event handlers executed in response to hardware and software interrupts. As a result of the scattered processing logic and the hidden control state that governs this logic, tracing the set of execution paths in a given system can be overwhelming. This situation is exacerbated by two factors. First, in all but the most basic applications, individual handlers must coordinate through shared state to accomplish application goals. Hence, appropriate synchronization actions are required. Second, long-running event handlers, especially those that disable interrupts to ensure atomic state access, can cause critical interrupts to be missed. To summarize, there are three problems in this scenario: (i) application logic is scattered throughout the program text, (ii) state synchronization among handlers is difficult to manage, and (iii) long-running events can introduce poor system performance or even compromise correctness. The Control Flow Mediator pattern describes an approach to linearizing program control flow, with side-effects that include simplifying the management of shared state and limiting the time spent in interrupt context. The embedded application declares shared variables that capture program control state, thereby making this state explicit. When an event is triggered, the corresponding handler need only update the relevant control state. The original processing logic is factored out of the handlers into a single Mediator function (e.g., main()). The function implements a series of conditionals expressed over the application control state and executed periodically; each performs appropriate application actions for the corresponding state. The resulting textual representation mirrors the flow of control, simplifying program comprehension. Interrupt handlers are also minimized and shared state is accessed from within a single function.

Sample Code

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

// states.h enum State {OFF, INITING, INITED, STARTING, STARTED, ..., ERROR}; ... // client.nc #include "states.h" ... norace State state = OFF; int failures = 0; ... async command void Init.init() { state = INITING; failures = 0; post mediator(); } ... async event void Device.initDone(bool success) { if(success) { state = INITED; failures = 0; post mediator(); } else { failures++; post mediator(); } } ... task void mediator() { atomic { if(state == INITING) { ... /* access driver state and request init. */ ... } else if((state == INITED) && (failures

Suggest Documents