[Contiki系列论文之1]Contiki——为微传感器网络而生的轻量级的、灵活的操做系统

说明:本系列文章翻译自Contiki之父Adam Dunkels经典论文,版权归原做者所有。java

Contiki是由Adam Dunkels及其团队开发的系统,研读其论文是对深刻理解Contiki系统的最佳资料。算法

Contiki经典论文翻译——索引文件夹
编程

-----------------------------------------------------------------------------------------------------------------------------------------------------------
摘要:

     无线传感器网络由大量微小的联网可通讯设备组成。对于大规模网络而言,动态地下载代码到网络中是极其重要的。在本论文中。咱们描写叙述了一个支持动态地载入、替换我的储蓄和服务的轻量级操做系统——Contiki。缓存

Contiki基于事件驱动(event-driven)内核,并提供可选的抢占式多线程。在一个资源受限的环境中,Contiki能作到保持基础系统轻量、紧凑的同一时候,还可以灵活地动态载入和卸载程序和服务。安全


1.介绍


      无线传感器网络由大量具备无线通讯能力的微传感器设备组成。这些传感器设备可以自治地构成网络并数据传输。一般状况下。这些传感器设备是资源受限的。板载电池或者太阳能面板仅仅能提供有限的电源供应。此外。微小的尺寸和低廉的价格也限制着系统的复杂性。网络

典型的传感器的配置包含8位微控制器、大约100kb的代码存储空间和小雨20kb的RAM。依据摩尔定律,这些设备在未来会愈来愈小、愈来愈便宜。虽然这意味着传感器网络可以被扩展到更大的范围,但却不必定说明相关资源会受到更小的限制。多线程

      做为一个为传感器节点设计操做系统的设计者连说。其面临的挑战在于找到一个轻量级的机制和抽象,以在资源受限的设备上提供足够丰富的运行环境。咱们已经开发了Contiki——针对资源受限环境而开发的操做系统。Contiki提供了我的程序和服务动态载入、卸载的功能。其内核基于时间驱动(event-driven),并支持抢占式多线程机制。哪些明白需要多线程的程序将被连接为一个库。从而实现可抢占式多线程。
      Contiki由C语言实现。已被移植到不少微处理器架构,包含TI MSP430、Ateml AVR。

咱们眼下将Contiki执行在ESB平台[5]。ESB使用的微处理器是MSP430,带有2kbRAM,60kbROM。执行在1MHz。这个微处理器可以可选地重烧部分片上flash存储。
架构

      本篇论文的贡献主要在两个方面。第一是灵活性。一个资源受限传感器设备之上可载入程序和服务。第二个通用性。抢占式多线程没必要再内核最底层实现,而是构建于事件驱动内核顶层做为应用程序库。这赞成基于线程的程序执行在基于事件的内核之上,下降系统各部分重入带来的开支、栈空间。并发


1.1执行时下载代码

      咱们指望无线传感器网络是大规模的,在每个网络里由成百上千,甚至成千上万的节点。当为这种大规模传感器网络开发软件时,动态地下载程序代码到网络里就显得尤其重要。此外,在网络执行期间,可能会对其打补丁[9]。

普通状况下,单独用人去收集和又一次烧写所有的传感器设备是不可行的。其实。眼下已有很是多种方法可以将代码分发到无线传感器网络中。对于这些方法。由于通讯将占用大部分有效节点能源。因此下降发送到网络中的字节数是关键。框架


      大多数用于嵌入式的操做系统都需要编译、下载一个包括完整系统的二进制镜像到设备中。

这个二进制镜像包括操做系统、系统库和执行在系统之上的实际应用程序。

而与此不一样的是,Contiki赞成在执行期间载入、卸载我的应用程序和服务。

大多数状况下,应用程序远远小于整个系统,所以在经过网络传输时Contiki需要更少的能量。

1.2可移植性

      随着不一样的传感器设备平台的添加。咱们需要一个可以在不用硬件平台移植的通用软件基础架构。

当前有效的传感器平台上都载有全然不一样的配置。

由于应用程序相关的特性,咱们指望着部分在未来不会改变。

在今天,平台间的统一不变的特性仅仅有一个——不使用段机制或者内存保护机制的CPU架构。

在这样的架构下。程序代码被存放于可又一次编程的ROM,程序数据被存放于RAM。咱们设计了Contiki操做系统,其提供CPU多线程抽象(且是惟一的抽象)。并支持程序和服务的可载入性。考虑到传感器网络中应用的相关特性,咱们以为其它抽象被设计为库或者服务、提供动态机制更合适


1.3事件驱动系统

      在内存严重受限的环境中,多线程模型的相关操做占领了大部分的系统内存资源。

因为每一个线程都有本身的栈,并且因为一般很是难提早知道一个线程需要多大的栈空间。因此系统给线程预分配栈是老是足够多的。当线程被建立时。需要为每一个栈分配内存,栈中的内存仅仅能被相应线程使用。不能被当前执行的多个线程共享。此外,线程并发模型需要锁机制来保护可以修修改态资源的线程。

    
      为了在不为每个线程分配栈、提供锁机制的同一时候依旧可以提供并发服务。咱们使用了事件驱动机制。

在事件驱动系统中,进程以事件操做的方式实现。由于一个事件操做不能被堵塞。所有的进程可以使用一样的栈,从而可以在进程间有效地实现稀缺的内存资源的共享。与此同一时候。由于两个事件操做从不并发地执行,普通状况下锁机制也不需要了。
      虽然事件驱动系统在很是多种传感器网络应用中都能很是好的工做,但是依旧存在问题——程序猿很是难去管理状态驱动编程模型,换句话说。不是所有的程序均可以很是方便地使用状态机来描写叙述,比方password学中的评估算法时间长度。一般,在标准的CPU平台下,这需要几秒钟的时间。

在一个纯粹的事件驱动操做系统中,时间长度计算全然独占CPU。使系统不能够响应外部时间。假设操做系统基于可抢占式的多线程模型,这将不是问题,因为时间长度计算能被抢占。


      为告终合时间驱动和可抢占式线程,Contiki使用了一个混合模型:系统基于时间驱动内核。而将可抢占式多线程以可选应用库的形式实现,在程序需要它时才被连接。


      本论文剩下的部分以例如如下的形式组织:第2章回想相关的工做。第3章描写叙述Contiki系统的框架。第4章描写叙述Contiki内核的设计,第5章描写叙述Contiki服务的概念,第六、7章描写叙述Contiki怎样处理库、怎样处理通讯支持,第8章描写叙述可抢占式多线程的实现,第9章描写叙述咱们使用该系统的经验。最后,第10章做总结。


2.相关工做

      TinyOS[15]应该是针对指定应用和受限传感器设备而开发的最先的操做系统。TinyOS也是基于轻量级的事件机制。所有的程序执行都是在一个从执行到结束任务里执行。TinyOS使用了一个指定的类型语言来完毕小组件[12]系统。而这些组件被静态地连接到内核中。连接后。就很是难改动系统了[17]。相反地,Contiki提供一个动态结构,赞成程序和驱动在执行时无需又一次连接就被替换。

为了给TinyOS提供执行时重编程功能,Levis和Culler为TinyOS设备开发了一个虚拟机——Mate[17]。

这个虚拟机为了典型传感器网络应用而单独设计的。很是类似地,MagneOS操做系统[7]使用了java虚拟机来将应用分发到传感器网络。使用虚拟机而不适用本地机器代码的优势是虚拟机代码可以写得很是小,在传输代码到网络中时可以减少能量消耗。

但是其缺点是添加了解释代码的能量消耗,因为对于长期执行的程序而言,在传输二进制代码时节省的能量将在执行代码时被消耗。

Contiki程序使用本地代码,所以可以被适用于各类类型的程序,包含不丢失执行效率的底层设备驱动。


      SensorWare[8]提供了一个针对可编程传感器的抽象脚本语言,但是他们的目标平台不是像咱们这样的资源受限的平台。类似的。EmStar[3]环境被设计用于资源更少的受限系统。Reijers和Langendoen[21]使用一个补丁语言去小改处于执行的系统的二进制镜像。这在所有节点执行一样二进制代码的网络中是有效的,但是一旦有一点点不一样的程序或者程序的版本号不一样一时候就变得特别复杂。
      Mantio操做系统[3]使用传统的可抢占式多线程模型操做。

经过下载程序镜像到EEPROM,再从EEPROM烧写到flash ROM, Mantio既支持又一次编程完整的操做系统,也支持又一次烧写部分存储空间。由于多线程的理论,每个Mantis程序必须从系统堆分配栈空间,必须使用锁机制以实现共享变量相互排斥。

相反地。Contiki由于使用基于事件的、没有抢占的调度器,依次避免了多栈分配和锁机制。可抢占式多线程以库的方式提供,当需要的时候才被连接到程序。
      Contiki中的可抢占式多线程与fibers[4]相似。

轻量级的fibers有Welsh和Mainland[23]实现。余轻量级的fibers不一样的是,Contiki不会限制当前线程数量的最大值——2。此外。不像fibers那样。Contiki中的线程支持可抢占式。


      与Exokernel[11]和Nemesis[16]相似,Contiki尽可能下降抽象的数量,以保持内核尺寸的最小化。抽象以库的形式实现,并且差点儿实用下层硬件的全然訪问权限。

Exokernel追求性能、Nemesis追求服务质量,Contiki追求减少尺寸、维持灵活。

与Exokernel不一样的是,Contiki不支持保护机制,因为Contiki的硬件在设计时就不支持内存保护。


3.系统概述

      一个执行的Contiki系统由内核、库、程序载入器和一系列进程组成。进程多是应用程序或者服务,服务为多个进程提供功能。

所有的进程。包含应用程序和服务。可以在执行时动态载入。进程间通讯老是经过内核进行的,但是内核不提供邮件抽象层,而让设备驱动、应用直接与硬件通讯。
      一个进程(process)有一个事件处理函数(event hander)或者可选的轮询处理函数(poll hander function)定义。进程的状态保存在进程的私有内存中,内核中仅仅有一个指向该状态的指针。在ESB平台[5],进程状态由23字节组成。所有的进程共享一样的地址空间。而不是执行在不一样的保护域。进程间经过邮差事件(posting events)通讯。
      

    
图1. 分区——内核、可载入程序
      Contiki系统被分为两部分:即图1中的核心和被载入程序。分区在编译时指定,且在Contiki的部署中是特定的。典型地。核心由Contiki内核、程序载入器、执行时的通用部分、支持的库、设备驱动中用来与硬件通讯的通讯栈组成。

核心被编译成单一的二进制镜像文件,并被优先部署到设备上。在部署以后。即便使用程序载入器去覆盖核心或者给核心打补丁,核心通常不会被改动。


      程序经过程序载入器载入到系统中。

程序载入器经过使用通讯栈或者直接使用像EEPROM之类的附加存储设备来得到程序二级制文件。典型地,在程序被烧写内存以前是存放在EEPROM里,而后被载入系统。


4.内核架构

      Contiki内核由一个轻量级调度器组成。调度器派遣事件去执行进程,并周期性地调用进程的轮询处理器。

所有的程序都经过内核低筒的事件派遣或者经过轮询机制执行。内核一旦被调度后,将不会抢占时间处理区。所以时间处理器必须执行到结束。只是,将在第8章中看到。时间处理器必须使用内部机制来实现可抢占式。
      内核支持两种事件:异步和同步时间。异步事件是一种延迟处理调用的一种形式:异步事件是内核里的一个队列。在一段时间后会被派遣到目标进程。同步事件与异步事件相似。但是会被立刻派遣到需要被调度的目标进程中去。

当目标进程处理完事件后,会返回结果给邮差进程。这与Spring操做系统中的门抽象相似,可以被看作进程间的程序调用。


      除事件以外。内核提供轮询机制。轮询可以看作在异步事件中调度的高优先级的事件。进程的轮询通常用于与硬件相关的操做,比方检车硬件设备的状态。

当一个轮询被调度时,所有的实现了轮询处理器的进程将会依照他们优先级被调度。

      Contiki内核为所有进程运行提供惟一的共享的栈。由于事件处理器的调用时栈是倒回的(rewound)。异步事件的使用下降了栈空间。


4.1两级调度层级

      Contiki中的所有事件调度都在一个级别上完毕,所以事件不能抢占其它事件运行。事件仅仅能被中断抢占。正常状况下。中断事件由硬件中断实施,但是也可能由根本的实时运行者(an underlying real-time executive)实施。Linux内核里已经使用后来的技术来提供实时保证。

      为了能够支持一个根本的实时运行体,Contiki从不关闭中断。因为这个缘由。Contiki不一样意中断处理器派遣事件,因为那会致使在事件处理器中产生竞争条件。相反地。内核提供了一个轮询标记。用以请求轮询事件。这个标记给中断处理器提供了一个请求立刻轮询的方法。


4.2可载入程序

      经过执行时重定位功能,以及二进制格式中包含的重定位信息,可以实现程序的可载入特性。当载入器载入程序到系统中时,它会先基于二级制提供的信息来尽量分配足够的内存空间。假设内存分配失败,就会终止程序载入。


      将程序被载入到内存后,载入器会调用程序的初始化函数。程序的初始化函数可能会開始或者替换一个或多个进程。


4.3节电模式

      在传感器网络中,老是但愿当网络不活动时能够关闭节点电源以达到减少能量消耗的目的。电源保护机制既依赖于应用[18],也依赖于网络协议[20]。

Contiki内核包括了非显式节电抽象。但是需要应用去指定去实现这个机制。为了能够帮助应用决定何时关闭系统电源,时间调度器公开了时间队列的大小。当没有事件被调度的时候,能够依据这个信息来关闭处理器。当处理器响应中断被唤醒的时候,经过轮询处理器处理外部事件。


5.服务


图2.应用函数调用服务的过程

      在Contiki中。服务是为其它进程提供特定功能的进程,可以当作是共享库的一种形式。服务在执行时可以被动态地替换,所以也必须被动态地连接。

典型的服务包含通讯协议栈、传感器设备驱动程序、高级别功能(比方传感器数据处理算法)。
      服务层牢牢靠近内核,负责管理服务。详细地说,服务层负责跟踪正在执行的服务。并提供一种方法以找到被安装的服务。一个服务由一个文本字符串所标识,用于描写叙述该服务。服务层使用通用的字符串匹配方法来查询已经安装的服务。
      服务由服务接口和实现该接口的进程组成。服务接口由一个版本和一个函数表组成。

函数表由指向该函数接口的函数指针组成。
      应用程序经过根库(stub library)与服务通讯。根库被连接到应用程序,而后使用服务层找到服务多相应的进程。一旦一个服务被定位,根将服务进程的进程ID缓存起来,用于未来所有的服务请求。


      程序经过服务接口根调用服务。在调用服务的过程当中。程序没必要知道他所调用的接口是用于服务的特殊接口。服务第一次被调用时,服务接口根会在服务层查找服务。

假设指定的服务存在。查找时将返回一个指向该服务接口的指针。然后存在服务接口中的版本将与根中的版本进行检查。除了版本以外,服务接口中还包括了指向服务函数的指针。假设服务根中的版本与服务接口中的版本成功匹配。服务接口根将调用被请求的函数。


5.1服务替换

      与所有的进程同样,服务也可以在正在执行的Contiki系统中被动态地载入和替换。

由于服务进程的进程ID用于标识进程,假设服务进程被替换,保留其进程ID是相当重要的。基于这个缘由。内核提供了特殊的机制用于替换进程和保留进程ID。
      当一个服务被替换的时候。内核经过给服务进程邮寄一个特殊的事件。做为对该事件的响应,服务必须从系统中移除本身。
      不少服务内部有一个状态,用于传递到新进程。内核提供了一种方法。将一个指针传递到新的服务进程,新服务进程可以依据传入的状态产生一个状态描写叙述符,用于描写叙述进程的状态。

保存有状态的内存必须从共享源里分配,因为当老进程被移除的时候会又一次分配进程内存。


      服务的状态描写叙述符中包括服务的版本。所以内核不会尝试去载入具备不兼容版本的一样服务。


6.库

      Contiki内核仅仅提供最主要的CPU复用和事件处理特性,系统其余部分以系统库的形式实现。并被可选择地连接到程序中。

程序与库的连接有三种方式。第一种。做为核心的一部分的库。被静态地连接到程序中。另一种,做为可载入程序的一部分的库,被静态地连接到程序中。

第三种,程序可以调用服务来实现一个特定的库。以服务形式存在的库,可以在执行时被动态地替换。
      典型地,执行时的库,比方经常被使用的库。最好放到Contiki核心。很是少被使用,或者应用相关的库,更适合与可载入程序连接。做为核心一部分的库。老是存在系统中,所以没必要被包括到可载入程序库中。
      举个样例。若是程序使用函数memcpy()拷贝内存。使用atoi()将字符串转变为整数。函数memcpy()是经常被使用的C库函数,而atoi()被用得不是那么多。

所以,在这个特殊的样例中,memcpy()将被包括到系统核心,而atoi()不会被包括。当程序被连接成二进制时,函数memcpy()的静态地址将会被再次连接到核心。然而,C库中实现auoi()的那部分目标代码必须被包括到程序二进制中。


7.通讯支持


图3.通讯栈

      通讯是传感器网络中的基础概念。在Contiki中。为了使执行时替换成为可能,通讯是以服务的形式实现的。

多个通讯栈被同一时候载入。

经过实验研究发现。这可以被用于评估和比較不一样的通讯协议。而且。正如图3所看到的。一个通讯栈可能会被分红不一样的服务,这就是的执行时替换部分通讯栈成为可能。

      通讯服务使用服务机制去调用其它服务,并使用同步事件去与应用程序通讯。

由于同步事件处理器被要求一旦执行起来就必须执行到结束,这就使得所欲的通讯处理使用一个buffer成为可能。

设备驱动程序读取接收到的包到通讯buffer中。而后就调用上层的通讯服务。通讯栈负责处理包头。并邮寄一个同步事件给应用程序,告知已经接收到此包。应用程序对包的内容做出响应。而后可选地在buffer中保持一个响应。最后将控制权返回通讯栈。

通讯栈预先准备需要发送到外部的新的包头。而后将控制权限交给设备驱动程序,这样就完毕了一个包的传输。


8.可抢占式多线程

      在Contiki中,可抢占式多线程是在基于事件的内核之上以库的形式实现的。库是以可选的形式被连接到应用程序中的,即应用程序明白指出需要进行多线程模型操做的时候。才将库连接到应用程序中。库被分为两类:与平台独立的部分——用于与事件内核相互做用。以及与平台相关的部分——用于实现栈的切换和抢占机制。一般,抢占经过一个定时器中断实现,保持处理器的寄存器到栈中,而后切换回内核栈。在实际中,当移植平台相关部分库时,仅仅需要重写很是少的代码。举个样例,对于MSP430平台,仅仅用了25行C代码。


      与常规Contiki进程不一样。每个线程都需要一个独立的栈。

库中提供了必要的栈管理函数。在线程明白地推出、或者被抢占以前。每个线程在各自的栈上运行。


图4.多线程库的API

      图4展现了多线程库中的API。它由六个函数(mt_yield(), mt_post(), mt_wait(), mt_exit(),mt_start() and mt_exec())组成。mt_exec()函数被事件处理器调用,用于运行实际的线程调度。

9.讨论


      咱们已经用Contiki操做系统实现了很是多传感器网络应用程序。比方多跳路由、运动检測。


9.1空中编程(over-the-air programming)

      咱们已经实现了一个简单的空中编程协议。该协议使用点对点通讯。传输一个单一的二进制文件到选定的集线器。该二进制文件存储在EEPROM,并在本节点接收到完整的程序时。被广播到邻居节点。

假设发生包丢失,可以由邻居节点发出低电平信号做为示意。而后由集线器节点修复。

咱们打算在未来实现更好的协议。比方Trickle算法。


      咱们作了一个实验,动态地分布40个节点到报警系统中。咱们使用了空中重编程方法和人工有线重编程方法。最初。程序载入机制不是很无缺,在咱们的实验中不能使用程序载入。咱们的目标程序大约为6KB。加上Contiki核心和C库,完整的系统镜像接近30KB。使用人工有线重编程的方法,为一个独立的传感器节点编程花费了超过30秒。而一共40个点检,对整个网络冲编程至少需要30分钟。相反地。经过空中重编程的方法。仅仅使用了2分钟。这减少了一个数量级。


9.2代码尺寸


表1.编译后的代码尺寸(字节)

      对于一个为受限设备开发的操做系统。代码的尺寸和RAM的利用率必须足够小,这样才干给执行在系统之上的应用留下足够的空间。

表1展现了Contiki在两个架构上的代码尺寸和RAM使用率:TI MSP430和Atmel AVR。表中的数字翻译了核心组件和应用案例——传感器数据复制服务 的尺寸。

这个数据复制服务由服务接口根和和服务自身实现两部分组成。眼下。程序载入器仅仅在MSP430平台上实现了。
      Contiki的代码尺寸比TinyOS大,比Mantis系统小。由于提供了不一样的服务。contiki的事件内核比TinyOS大是理所固然的。

TinyOS的事件内核仅提供了一个FIFO事件队列调度器,而Contiki内核既支持FIFO事件,也支持优先级的轮训处理器。而且,对于TinyOS这类系统而言,Contiki的灵活性也需要不少其它的执行时代码。
      RAM的需求依赖于系统配置的最大进程个数(p)。异步事件队列的最大尺寸(e)以及多线程操做中的线程栈大小(s)。


9.3可抢占式


图5.在可抢占计算期间响应时间有很是少的增量

      抢占式的目的是使长时间执行更有意义。能够响应到来的事件,比方传感器输入或者到来的通讯数据包。

图5描写叙述了当Contiki正在执行一个需要8秒才干完毕的可抢占式线程是怎样响应到来的通讯数据包的。曲线由200个往返ping包(每个包40直接)測试而来的。ping包的过程大约从第5秒開始到第13秒结束。

在这期间。往返的时间略微添加了但是依旧能够响应ping包。
      咱们的測试方法是每隔200ms,从1.4GHz的PC经过57600kb/s的速度向执行Contiki的ESB节点发送包。

数据包之因此经过串行线而不是无线链接,是为了不无线电影响,比方位错位或者MAC冲突。


9.4可移植性

      咱们已经将Contiki移植到了很是多架构上。包含TI MSP430和Atmel AVR。其它人已经将系统一直到Hitachi SH3 和Zilog Z80。

移植过程包含写启动代码、设备驱动、架构相关的程序载入器以及多线程库中的栈切换代码。内核和服务层不需要作不论什么修改。
      由于内核和服务层不需要不论什么修改,第一个I/O驱动程序写完后就可以进行实际的port測试了。Atem AVR的port移植由咱们本身完毕。包含有效的设备驱动程序,一共仅仅用了2小时。Zilog Z80的port移植由第三方完毕,也仅仅用了一天。


10.总结

      咱们已经描写叙述了为内存受限系统而设计的Contiki操做系统。为了减少系统的大小。Contiki基于事件驱动内核。由于对于长时间执行的程序来讲,很是难使用事件驱动系统中的状态机编程。Contiki提供了执行时在事件驱动内核之上的抢占式多线程应用库,这个库是可选地被连接到应用中,即当应用明白说明需要多线程模型时才干连接。


      一个正在执行的Contiki分为两部分:核心和可载入程序。核心由内核、一些类基本服务、语言执行时部分和支持的库组成。载入程序可以在执行时被独立地载入、卸载。

共享功能以共享库的形式实现。服务可以被独立更新或者替换,也添加了系统灵活性。       咱们可以看出,在保持基本系统轻量级和紧凑的同一时候。作到了在资源受限系统上灵活地载入、卸载程序和服务。虽然咱们的内核是基于事件驱动的。也为应用程序提供了可抢占式的多线程支持。       由于Contiki的动态特性,它可以被用于传感器网络的多硬件、多应用甚至多用户。咱们将在支持安全机制方面继续咱们的工做。

相关文章
相关标签/搜索