Boot Sequence
Contents |
Introduction
一个在考虑 TinyOS而经常被问的问题是“ main() 在哪?”。在之前的课程里,我们推迟了 TinyOS启动顺序的细节问题的讨论:应用程序处理 Boot.booted事件和从那里开始。此教程描述这个事件之前和之后的步骤,展示如何正确地初始化组件。
Boot Sequence
TinyOS启动顺序有 4步:
1.Scheduler initialization调度初始化
2.Component initialization 组件初始化
3.Signal that the boot process has completed 通知告诉 boot过程已经完成
4.Run the scheduler 运行调度
应用程序级的启动顺序是 MainC(在 tos/system)。 MainC提供一个接口 Boot和使用一个接口 Init作为 SoftwareInit。启动顺序调用 SoftwareInit()作为第二步的一部分和通知在第三步中的 Boot.booted。
默认的实际的启动顺序是在组件中的 RealMainP。注意,它的标示是以 P结束,代表此组件不能直接 wire它。如下是 RealMainP的形式:
module RealMainP { provides interface Booted; uses { interface Scheduler; interface Init as PlatformInit; interface Init as SoftwareInit; } }
MainC仅仅提供 Boot和使用 SoftwareInit;RealMainP使用 2个附加的接口, PlatformInit和 Schuduler。 MainC在应用程序中自动 wire它们到系统调度和平台初始化顺序中而隐藏了这些接口。 PlatformInit和 SoftwareInit之间的主要的不同是一个是硬件和软件, PlatformInit负责将核心平台服务转入有意义的状态;例如, mica平台的 PlatforInit标准化它们的时钟。
这里是 RealMainP的代码:
/**
* RealMain implements the TinyOS boot sequence, as documented in TEP 107.
*
* @author Philip Levis
* @date January 17 2005
*/
module RealMainP @safe() {
provides interface Boot;
uses interface Scheduler;
uses interface Init as PlatformInit;
uses interface Init as SoftwareInit;
}
implementation {
int main() @C() @spontaneous() {
atomic
{
/* First, initialize the Scheduler so components can post
tasks. Initialize all of the very hardware specific stuff, such
as CPU settings, counters, etc. After the hardware is ready,
initialize the requisite software components and start
execution.*/
platform_bootstrap();
call Scheduler.init();
/* Initialize the platform. Then spin on the Scheduler, passing
* FALSE so it will not put the system to sleep if there are no
* more tasks; if no tasks remain, continue on to software
* initialization */
call PlatformInit.init();
while (call Scheduler.runNextTask());
/* Initialize software components.Then spin on the Scheduler,
* passing FALSE so it will not put the system to sleep if there
* are no more tasks; if no tasks remain, the system has booted
* successfully.*/
call SoftwareInit.init();
while (call Scheduler.runNextTask());
}
/* Enable interrupts now that system is ready. */
__nesc_enable_interrupt();
signal Boot.booted();
/* Spin in the Scheduler */
call Scheduler.taskLoop();
/* We should never reach this point, but some versions of
* gcc don't realize that and issue a warning if we return
* void from a non-void function. So include this. */
return -1;
}
default command error_t PlatformInit.init() { return SUCCESS; }
default command error_t SoftwareInit.init() { return SUCCESS; }
default event void Boot.booted() { }
}
代码显示了如上描述的 4步。
Scheduler Initialization
第一个启动步骤是初始化调度。如果调度没有在组件之前初始化,组件初始化应用将不能派发任务。并不是所有的组件都需要任务来派发,这就很自然的给出了那些组件需要那么做了。启动顺序执行任务在初始化阶段之后,为了允许长时间的操作,因为它们只发生一次。 TEP106详细的描述了 TinyOS调度,包括如何去替换调度的信息。
Component initialization.
在 realMainP初始化调度之后,它初始化平台。 Init接口仅执行一个单一的命令 init()。
tos/interfaces/Init.nc: interface Init { command error_t init(); }
平台初始化阶段负责平台的执行。因此 PlatformInit被明确的平台初始化组件 导通(wire), PlatformC。没有别的组件需要 (导通)wire PlatformInit。任何组件需要初始化能执行 Init接口和 (导通)wire它自己到 MainC的 SoftwareInit接口:
tos/system/MainC.nc: configuration MainC { provides interface Boot; uses interface Init as SoftwareInit; } implementation { components PlatformC, RealMainP, TinySchedulerC; RealMainP.Scheduler -> TinySchedulerC; RealMainP.PlatformInit -> PlatformC; // Export the SoftwareInit and Booted for applications SoftwareInit = RealMainP.SoftwareInit; Boot = RealMainP; }
一般的初始化问题都依赖于系统的不同部分。在TinyOS做如下的3个方面的处理:
-
Hardware-specific initialization issues are handled directly by each platform's
PlatformC
component.明确的硬件初始化问题被每一个平台的PlatformC组件直接处理掉了。 -
System services (e.g., the timer, the radio) are typically written to be independently initializable. For instance, a radio that uses a timer does not setup the timer at radio initialisation time, rather it defers that action until the radio is started. In other words, initialisation is used to setup software state, and hardware state wholly owned by the service.系统服务(例如,timer,radio)通常写成独立初始化的。例如,一个radio使用timer,在radio初始化时间里并没有安转timer,不同于action直到radio开始了。换句话说,初始化通常被用来设置软件状态,和硬件状态完全被服务拥有。
-
When a service is split into several components, the
Init
interface for one of these components may well callInit
(and other) interfaces of the other components forming the service, if a specific order is needed.当一个服务被分成几个组件时,如果一个明确的顺序需要的话,对于这些组件中的一个初始化接口可能会调用Init接口(和别的)其他的来自服务的组件接口。
Signal that the boot process has completed.
一旦所有的初始化都已完成了, MainC's Boot.booted() 事件被触发。组件现在可以自由调用 start()和其他的命令在任何它们正在使用的组件上。从新看看 Blink应用程序, timer是从 booted()开始的。这个 booted事件是 Tinyos中类似于 Unix应用程序中的 main函数。
Once all initialization has completed, MainC
's Boot.booted()
event is signaled. Components are now free to call start()
and other commands on any components they are using. Recall that in the Blink
application, the timers were started from the booted()
event. This booted
event is TinyOS's analogue of main
in a Unix application.
Run the scheduler loop.
一旦应用程序已经通知了作为 booted和启动需要的服务的系统, Tinyos进入它的调度循环中。队列中只要有任务调度就会运行。直到它检测到一个空的队列,调度将微控制器设置成能允许活动的硬件资源的低能耗状态,例如,只有一个 timer频繁运行允许低能耗状态相对于不重要的 buss像 UART.TEP112详细地描述了一个处理是如何运行的。
处理器进入睡眠状态直到它处理到一个中断。当一个中断倒带, MCU退出他的睡眠状态运行中断句柄。这就引起了调度循环从新启动。如果中断句柄派发了一个或更多的任务,调度执行任务指导任务队列空然后从新返回到睡眠状态。
Once the application has been informed that the system as booted and started needed services, TinyOS enters its core scheduling loop. The scheduler runs as long as there are tasks on the queue. As soon as it detects an empty queue, the scheduler puts the microcontroller into the lowest power state allowed by the active hardware resources. For example, only having timers running usually allows a lower power state than peripheral buses like the UART. TEP 112 describes in detail how this process works.
The processor goes to sleep until it handles an interrupt. When an interrupt arrives, the MCU exits its sleep state and runs the interrupt handler. This causes the scheduler loop to restart. If the interrupt handler posted one or more tasks, the scheduler runs tasks until the task queue and then returns to sleep.
Boot and SoftwareInit
从一个应用程序或者高级服务的角度来看,在 boot顺序里 2个重要的接口是那些 MainC输出的: Boot和 SoftwareInit.Boot通常仅被高级应用程序处理:它启动像 timer或者 radio的服务。 SoftwareInit与此相反,接触系统的很多不同的部分。如果一个组件需要编码来运行一次来初始化它的状态或者配置,那么它就可以通告( wire) SoftwareInit。
通常,服务组件需要初始化 wire它们自己来 SoftwareInit,这优于依赖应用程序作者来做这些。当一个应用程序开发者正在写一个巨大复杂的系统,一直跟踪着所有的初始化会是十分困难的,调试一个没有正被运行的调用会是十分困难的。为了避免 bug和简化应用程序的开发,服务通常使用 auto-wiring。
术语 auto-wiring涉及到当一个组件自动通告它的依赖相对于输出它们对于一个应用程序作者来解决。在这个用例里,到不如提供 Init接口,一个服务组件为 RealMain wire它的 Init接口。例如, PoolC是一个一般的抽象内存池,它允许你能为动态分配声明一种内存对象的保护。 在下面,它的执行 (PoolP)需要初始化它的数据结构。表示这个必须能让组件正确的操作,一个应用程序作者不应该去当心它。所以 PoolC组件实例化 PoolP和 wire它到 MainC.SoftwareInit:
From the perspective of an application or high-level services, the two important interfaces in the boot sequence are those which MainC exports: Boot and SoftwareInit. Boot is typically only handled by the top-level application: it starts services like timers or the radio. SoftwareInit, in contrast, touches many difference parts of the system. If a component needs code that runs once to initialize its state or configuration, then it can wire to SoftwareInit.
Typically, service components that require intialization wire themselves to SoftwareInit rather than depend on the application writer to do so. When an application developer is writing a large, complex system, keeping track of all of the initialization routines can be difficult, and debugging when one is not being called can be very difficult. To prevent bugs and simplify application development, services typically use auto-wiring .
The term auto-wiring refers to when a component automatically wires its dependencies rather than export them for the application writer to resolve. In this case, rather than provide the Init interface, a service component wires its Init interface to RealMainC. For example, PoolC is a generic memory pool abstraction that allows you to declare a collection of memory objects for dynamic allocation. Underneath, its implementation (PoolP ) needs to initialize its data structures. Given that this must happen for the component to operate properly, an application writer shouldn't have to worry about it. So the PoolC component instantiates a PoolP and wires it to MainC.SoftwareInit:
generic configuration PoolC(typedef pool_t, uint8_t POOL_SIZE) { provides interface Pool; } implementation { components MainC, new PoolP(pool_t, POOL_SIZE); MainC.SoftwareInit -> PoolP; Pool = PoolP; }
实际上,这意味着当 MainP调用 SoftwareInit.init,它在很多的组件上调用 Init.init。在一些通常的大应用程序里,初始化顺序可能需要和 30个组件一样多。但是应用程序开发者不需要当心这个:正确的已经写好的组件会自动考虑它的。
In practice, this means that when MainP calls SoftwareInit.init, it calls Init.init on a large number of components. In a typical large application, the initialization sequence might involve as many as thirty components. But the application developer doesn't have to worry about this: properly written components take care of it automatically.
Related Documentation
Programming Hint 8: In the top-level configuration of a software abstraction, auto-wire Init to MainC. This removes the burden of wiring Init from the programmer, which removes unnecessary work from the boot sequence and removes the possibility of bugs from forgetting to wire. From TinyOS Programming