To ensure that an application code has an extremely small footprint, TinyOS chooses to have no file system, supports only static memory allocation, implements a simple task model, and provides minimal device and networking abstractions. Furthermore, it takes a language-based
application development approach, to be discussed later, so that only the necessary parts of the operating system are compiled with the application. To a certain extent, each TinyOS application is built into the operating system.
Like many operating systems, TinyOS organizes components into layers. Intuitively, the lower a layer is, the “ closer ” it is to the hardware; the higher a layer is, the “ closer ” it is to the application.
In addition to the layers, TinyOS has a unique component architecture and provides as a library a set of system software components. A component specification is independent of the component implementation. Although most components encapsulate software functionalities, some are just thin wrappers around hardware. An application, typically developed in the nesC language covered in the next section, wires these components together with other application specific components.
Let us consider a TinyOS application example — FieldMonitor , where all nodes in a sensor field periodically send their temperature and photosensor readings to a base station via an ad hoc routing mechanism. A diagram of the FieldMonitor application, where blocks represent TinyOS components and arrows represent function calls among them. The directions of the arrows are from callers to callees.
To explain in detail the semantics of TinyOS components, let us fi rst look at the Timer component of the FieldMonitor application. This component is designed to work with a clock, which is a software wrapper around a hardware clock that generates periodic interrupts. The method calls of the Timer component. An arrowhead pointing into the component is a method of the component that other components can call. An arrowhead pointing outward is a method that
component requires another layer component to provide. The absolute directions of the arrows, up or down, illustrate this component’s relationship with other layers. For example, the Timer depends on a lower layer HWClock component. The Timer can set the rate of the clock, and in response to each clock interrupt it toggles an internal Boolean flag, evenFlag , between true (or 1) and false (or 0). If the flag is 0, the Timer produces a timer0Fire event to trigger other components; otherwise, it produces a timer1Fire event. The Timer has an init() method that initializes its internal flag, and it can be enabled and disabled via the start and stop calls.
A program executed in TinyOS has two contexts, tasks and events , which provide two sources of concurrency. Tasks are created (also called posted ) by components to a task scheduler. The default implementation of the TinyOS scheduler maintains a task queue and invokes tasks according to the order in which they were posted. Thus tasks are deferred computation mechanisms. Tasks always run to completion without preempting or being preempted by other tasks. Thus tasks are non preemptive. The scheduler invokes a new task from the task queue only when the current task has completed. When no tasks are available in the task queue, the scheduler puts the CPU into the sleep mode to save energy.
The ultimate sources of triggered execution are events from hardware: clock, digital inputs, or other kinds of interrupts. The execution of an interrupt handler is called an event context . The processing of events also runs to completion, but it preempts tasks and can be preempted by other events. Because there is no preemption mechanism among tasks and because events always preempt tasks, programmers are required to chop their code, especially the code in the
event contexts, into small execution pieces, so that it will not block other tasks for too long.
Another trade-off between nonpreemptive task execution and program reactiveness is the design of split-phase operations in TinyOS. Similar to the notion of asynchronous method calls in distributed computing, a split-phase operation separates the initiation of a method call from the return of the call. A call to a split-phase operation returns immediately, without actually performing the body of the operation. The true execution of the operation is scheduled later; when the execution of the body finishes, the operation notifies the original caller through a separate method call. An example of a split-phase operation is the packet send method in the Active Messages (AM) component. Sending a packet is a long operation, involving converting the packets to bytes, then to bits, and ultimately driving the RF circuits to send the bits one by one. Without a split-phase execution, sending a packet will block the entire system from reacting to new events for a significant period of time. In the TinyOS implementation, the send() command in the AM component returns immediately. However, it is the caller’s responsibility to remember that the packet has not yet been sent. When the packet is indeed sent, the AM component will notify its caller by a sendDone() method call. Only at this time is the AM component ready to accept another packet.
In TinyOS, resource contention is typically handled through explicit rejection of concurrent requests. All split-phase operations return Boolean values indicating whether a request to perform the operation is accepted. In the above example, a call of send() , when the AM component is still sending the fi rst packet, will result in an error signaled by the AM component. To avoid such an error, the caller of the AM component typically implements a pending lock to remember not to request further sendings until the sendDone() method is called. To avoid loss of packets, a queue should be incorporated by the caller if necessary.
In summary, many design decisions in TinyOS are made to ensure that it is extremely lightweight. Using a component architecture that contains all variables inside the components and disallowing dynamic memory allocation reduces the memory management overhead and makes the data memory usage statically analyzable. The simple concurrency model allows high concurrency with low thread maintenance overhead. As a consequence, the entire FieldMonitor system takes only 3 KB of space for code and 226 bytes for data. However, the advantage of being lightweight is not without cost. Many hardware idiosyncrasies and complexities of concurrency management are left for the application programmers to handle. Several tools have been developed to give programmers language level support for improving programming productivity and code robustness. We introduce in the next two sections two special-purpose languages for programming sensor network nodes. Although both languages are designed on top of TinyOS, the principles they represent may apply to other platforms.
Source of Information : Elsevier Wireless Networking Complete 2010
1 comments: on "Operating System: TinyOS"
Post a Comment