版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
12.1概述
无线传感器网络节点是个资源受限的嵌入式系统,尤其是它的电能和内存、接口资源等。这就决定了现有的一些嵌入式操作系统不能很好地适用于传感器网络节点,所以WSN需要拥有适合于本身的操作系统。本章介绍了三个比较有代表性的无线传感器网络操作系统:TinyOS、SCS和MantisOS(MOS),这三个操作系统都是开放源码的系统。希望能对有志于无线传感器网络操作系统领域开发和研究的人员有一定参考作用。我们介绍的三个操作系统版本分别是TinyOS2.0、MOS0.9.5和SOS1.7。返回12.2TinyOS操作系统12.2.1TinyOS2.0系统介绍TinyOS是一个典型的无线传感器网络操作系统。它提出了很多全新的设计概念,能够很好地满足无线传感器网络操作的要求。在国内它也是使用最广泛的无线传感器网络操作系统。下一页返回12.2TinyOS操作系统1.基本概念使用nesC编写的应用程序是通过把一个或多个组件连接起来从而组成一个完整的可执行程序。nesC有两种类型的组件:模块(module)和配件(configuration)。组件的定义格式请见图12-1。模块提供应用程序代码,实现一个或多个接口;配件则是用来将其他组件连接起来的组件。组件定义了两个作用域,一个是形式说明(specification)作用域,在该作用域内声明了组件提供和使用的接口;另一个是组件实现(implementation)作用域,该作用域对于模块来说该部分是程序功能的代码实现部分,而对于配件来说是将接口的使用组件和提供组件连接起来组成一个程序。组件内部还可以定义任务。上一页下一页返回12.2TinyOS操作系统
组件可以提供(provide)和使用(use)接口,接口是一组相关函数的集合,它是双向的并且是组件间的唯一访问点。接口声明了一组函数,称为命令(command,接口的提供者必须实现它们;接口还声明了另外一组函数,称为事件(event),接口的使用者必须实现它们。图12-2是接口send的定义。
send接口定义了四个命令:send、cancel、maxPayloadLength、getPayload,其中前两个命令的返回值都是error_t类型的,而后两个命令的返回值分别是uint8_t和void*。它还定义了一个事件、endDone,该事件无返回值。对于一个组件而言,如果它要使用某个接口中的命令,那么它还必须实现这个接口的事件。一个组件可以使用或提供多个接口以及同一个接口的多个实例。上一页下一页返回12.2TinyOS操作系统2.TinyOS2.0组件命名规则
1)C和P的命名规则
TinyOS所有的终端程序组件都以字母C或P为结尾。以C结尾所命名的组件表示它是一个可用的抽象,而以P结尾的组件则表示它是私有的。以P结尾的组件不能被直接地连接,但可以把它做一些封装以使它变成可用(变成名字以C结束的)。一旦C组件的形式声明(specification)定义好以后,就不能随息地改变了。这是因为许多其他的组件可能会依赖它,改变它会导致其他组件的编译错误。相反,P组件的形式声明则是可以被改变的。上一页下一页返回12.2TinyOS操作系统2)硬件平台抽象命名规则
TinyOS2.0中的硬件抽象通常是三级抽象架构,称作HAA(HardwareAbstractionArchitecture)。HAA的最底层是HPI层。HPI层是原始硬件的直接上层,把硬件的I/O引脚和寄存器以接口的形式提供给用户。HPI层通常不包含除了硬件本身之外的其他信息(例如变量),HPI层的组件一般在芯片的名称前面加上HPI。上一页下一页返回12.2TinyOS操作系统HAA的中间层是HAL(HardwareAbstractionLayer)。HAL在HPL层之上并且提供了更高级的抽象,这使得它比HPL层更易于使用,但仍然只提供底层硬件的功能HAA的最高层是HIL(HardwareIndependentLayer)。HIL建立在HAL之上并且提供了硬件独立性,这息味着HIL通常无需提供HAL所能提供的所有功能。HIL组件没响名称前缀,因为它所提供的是底层硬件的高层抽象,它可以被应用程序使用,也可用于多种平台。上一页下一页返回12.2TinyOS操作系统3.TinyOS2.0的任务任务一般用于对实用性要求不高的应用中。它实际上是一种延迟计算机制。任务之间不能互相抢占,也就是说任务之间的运行是原子的。
TinyOS2.0中提供了两种类型的任务:一种是1.x中使用的基本任务模型,另一种是2.x中新出现的任务接口。
1)基本任务模型基本任务模型中任务的原型声明如下:taskvoidtaskname(){……}
用户使用post关键字抛出任务,调用方式如下:result_tret=posttastname()上一页下一页返回12.2TinyOS操作系统2)任务接口模型
TinyOS2.x除了提供了上述的基本任务,还提供了一种新类型的任务:任务接口。任务接口扩展了任务的语法和语义。通常情况下,任务接口包含一个异步(async)的post命和一个run事件,这些函数的具体声明由接口决定。
4.TinyOS2.0调度器
TinyOS的调度器实现了任务和事件的两级调度,如图12-3所示.TinyOS对任务的调度遵循FIFO模型。任务之间不能互相抢占。事件能抢占任务,而且事件之间也能互相抢占。上一页下一页返回12.2TinyOS操作系统TinyOS2.x调度器被实现为一个TinyOS组件。调度器既支持最基本的任务模型,也支持任务接口,并且由调度器负责协调不同的任务类型。调度器提供了一个参数化的任务接口,每一个连接到这个任务接口的任务都需要使用unique()函数来获得一个唯一的标识符,调度器使用这个标志符来调度任务。例如标准的TinyOS调度器的形式声明可以如下(定义在tinyos-2.x\tos\system目录):上一页下一页返回12.2TinyOS操作系统moduleSchedulerBasicP{providesinterfaceScheduler;providesinterfaceTaskBasic[uint8_ttaskID];usesinterfaceMcuSleep;}配件TinySchedulerC(见表12-1)封装了组件SchedulerBasicP,其连接关系如图12-4所示。上一页下一页返回12.2TinyOS操作系统
调度器必须提供参数化的TaskBasic接口。如果调用了TaskBasic.postTask()并返回了SUCCESS,那么调度器在合适的时一候便会运行它。调度器对TaskBasic.postTask()的调用必须返回SUCCESS,除非这不是第一次调用,因为如果这是第一次调用的话,任务接口的TaskBasic.runTask()事件已经被signal了。McuSleep接口用于微处理器的能量管理。调度器还必须提供Scheduler接口,这个接口定义了用于初始化和运行任务的命令,TinyOS使用该接口执行任务,它定义如下:上一页下一页返回12.2TinyOS操作系统interfaceScheduler{commandvoidinit();commandboolrunNextTask(hoofsleep);commandvoidtaskLoop();}上一页下一页返回12.2TinyOS操作系统init()命令用来初始化任务队列和调度器的数据结构。runNextTask()一旦运行就必须运行到结束,其返回值表T它是否运行了任务。runNextTask的参数sleep表示在没有任务可执行的情况下调度器应采取的执行策略。若sleep为FALSE,则该命令会立即返回FALSE;若sleep为TRUE,则任务被执行前该命令不能返回,并且该命令还能让处理器进入休眠状态直到新任务到来。调用runNextTask(FALSE)可能返回TRUE或者FALSE,调用runNextTask(TRUE)总是返回TRUE。taskLoop()命令会使调度器进入无限任务循环中,它能够在处理器处于空闲时把MCU设置成低能耗模式。下面是TaskBasic接口的定义:上一页下一页返回12.2TinyOS操作系统interfaceTaskBasicasynccommanderror_tpostTask();voideventrunTask():}当组件使用task关键字声明任务时,它采用隐含方式声明使用了TaskBasic的接口实例。任务的主体是runTask事件。当组件使用post关键字时,它将调用postTask命令。每一个TaskBasic必须使用unique(“TinySchedulerC.TaskBasic”)获得唯一的标志符作为它的参数以便被连接到调度器。当使用了关键字tusk和post后,nesC编译器便会自动地实现这些连接。配件TinySchedulerC连接关系见图12-4。上一页下一页返回12.2TinyOS操作系统
组件SchedulerBasicP使用了这些标志符作为任务队列的入口。当TinyOS通知调度器运行任务时,它会从队列中取出下一个标志符,并且使用该标志符调度参数化接口TaskBasic。
TinyOS2.0允许用户使用自己的应用程序(组件)取代系统调度器。程序员可以根据需要使用新的调度策略,例如基于优先级的调度。TinyOS推荐使用非抢占的调度策略,否则,调度器将会违反nesC的静态并发性分析。上一页下一页返回12.2TinyOS操作系统5.内存分配
TinyOS使用了静态的内存分配和管理机制。TinyOS中的组件在编译时分配所需要的内存。根据nesC的命名作用域规则,变量是组件完全私有的。所有执行实体的执行状态存储在组件内部。当TinyOS系统处于不活动状态时,组件变量代表整个系统的软件状态(softwarestate)。组件之间能共享状态(State)的唯一方法是通过函数调用。和C中一样,组件传递参数使用了最基本的两种方法:值传送和指针传送。使用值传送方法时,数据被拷贝到了堆栈,所以被调用者可以自由地修改它;使用指针传送时,调用者和被调用者共享一个指向数据的指针。使用指针传递参数有很多好处,但用户需要小心地使用访问数据以避免存储区崩溃和存储数据泄露。较好的做法是:在任何时候,每一个指针都有一个明确的所有者,并且只有所有者才能修改相应的存储区。上一页下一页返回12.2TinyOS操作系统6.TinyOS通信
1)消息缓冲区
TinyOS2.0中的消息缓冲区类型是message_t(与1.x不同),并且仍采用了静态包缓冲区。Message_t(定义在opt\tinyos-2.x\tos\types\message.h中)定义如下:上一页下一页返回12.2TinyOS操作系统
缓冲区大小可以适合任何节点的通信接口,并且结构中header、footer和metadata对用户是不透明的,组件不能直接访问结构的各域,所有缓冲区的访问必须通过接口AM-Packet和Packet(定义在opt\tinyos-2.x\tos\interfaces目录)实现。不同的链路层需要定义自己的header、footer和metadata结构。这些结构必须是外部结构(nx_truct},并且它们所有的域也必须是外部类型(nx_*).这样做有两个好处:第一,外部类型确保了交叉平台的兼容性;第二,它使得结构都对齐了字节边界,解决了包缓冲区对齐和内部的域偏移量的问题。上一页下一页返回12.2TinyOS操作系统2)通信组件主动消息层是网络的HIL层,平台的ActiveMessageC组件定义的网络接口是标准的通信接口,通过它实现了通信平台无关性。例如,mica2定义了cc1000主动消息层是ActiveMessageC,而Tmote把CC2420主动消息层也定义为ActiveMessageC。我们可以使用如下的四个主动消息通信组件实现无线消息的收发,这四个通用组件都实现了虚拟化。它们将AM类型作为参数,定义在tos\tinyos-2.x\tos\system目录:上一页下一页返回12.2TinyOS操作系统(1)AMSenderC:用于发送消息的组件。它提供了四个接口;AMSend实现消息的发送,Packet是数据分组层的消息数据类型存取器,用于设置和访问消息的负载域和负载长度等信息;AMPacket是通信层AM的消息存取器,用于访问或设置AM消息的目的地址、源地址、消息类型等信息;Acks使得组件可以使能或不使能ACK机制。(2)AMReceiverC:它提供了三个接口;Receive,Packet和AMPacket。当接收到具有相同的AM类型,并且目的地址是本地节点地址或广播地址的数据包时,会通知(signal)AMReceiver.Receive.receive事件。后面两个接口的功能与AMSenderC中的相同。上一页下一页返回12.2TinyOS操作系统(3)AMSnooperC:它提供了三个接口;Receive,Packet和AMPacket。当接收到具有相同的AM类型并且数据包的目的地址既不是本地节点地址也不是广播地址时,会通知(signal)AMSnooper.Receive.receive事件。后面两个接口的功能与AMSenderC中的相同。
(4)AMSnoopingReceiverC:它也提供了三个接口;Receive,Packet和AMPacket。当接收到具有相同的AM类型并且不管目的地址是什么都会通知(signal)AMSnoopingReceiverC.Receive.receive事件。后面两个接口的功能与AMSenderC中的相同。上一页下一页返回12.2TinyOS操作系统7.并发模型(ConcurrencyModel)TinyOS一次仅执行一个程序,每个节点中在任何时刻只可能存在一个应用程序映像。组成程序的组件来自于两个方面:一方面是系统提供的组件,另一方面是用户自定义的组件。程序运行时,有两个执行线程:一个称为任务(task),另一个称为硬件事件处理程序(hardwareeventhandler)。任务是原子运行的,并且任务的代码也比较简单,所以不存在一个任务突然抢占正在运行的任务而且修改其数据的危险。但是,中断是可以抢占的:一个中断将会中断当前任务的执行。当共享状态被可抢占执行的中断程序访问时,就可能产生竞争危险。上一页下一页返回12.2TinyOS操作系统在nesC和TinyOS中,来自任务环境外界的可以抢占式运行的函数必须用关键字async来标示:它异步运行于任务。nesC的规则是异步函数调用的命令和事件也必须是异步的,这就是说异步的命令或事件不能调用非异步的命令或事件。一个函数(命令或事件)不是异步就是同步(缩写为、ync),缺省情况下是同步的。接口的定义指明了命令和事件是异步还是同步。所有的中断处理都是异步的,所以也不能在它们的调用中包含任何同步的函数。中断(异步函数)可以执行同步函数的唯一方法就是post一个任务。因为任务的post操作是异步的,但任务的执行却是同步的。因为任务和硬件事件处理程序可能会被其他异步代码所抢占,所以在特定竞争条件下会导致数据不一致性。避免出现数据不一致的方法就是使用原子语句块来实现对临界数据的访问。nesC编译器也会在编译时检查潜在的数据争用。上一页下一页返回12.2TinyOS操作系统8.系统启动和初始化(Booting/initialization)TinyOS1.x使用了同步接口StdControl来实现系统初始化和启动必要的软件系统。但这个接口存在着许多限制。为了解决1.x的启动和初始化中存在的问题,TinyOS2.x的启动序列使用了3个接口;(1)Init:初始化组件和硬件状态。
(2)Scheduler:初始化和运行任务。
(3)Boot:通知系统已经成功地启动。上一页下一页返回12.2TinyOS操作系统Init是一个同步的接口,它使得初始化能够有序进行,定义如下:interfaceInit{commanderror_tinit();}Init接口执行的是顺序的、同步的操作,在初始化完成前不会启动任何组件。Schedule:接口用于初始化和控制任务执行,它在上面已经介绍过了。上一页下一页返回12.2TinyOS操作系统Boot接口中定义了一个事件booted(),用它通知系统已经被成功地启动。定义如下:interfaceBoot{eventvoidbooted();}9.能量管理TinyOS2.x中的能量管理分为处理器能量管理和设备能量管理。上一页下一页返回12.2TinyOS操作系统1)微处理器能量管理
TinyOS2.x使用了3个基本机制来管理和控制微处理器的能量状态:一个Dirty位,一个具体芯片的能量状态计算函数和一个能量状态重载函数。Dirty位通知TinyOS什么时候需要计算新的能量状态,能量状态计算函数用来执行计算,重载函数用来满足高层组件的能量管理需求。
2)外设能量管理
TinyOS2.0定义了两种不同的能量管理模型:显式能量管理模型和隐式能量管理模型。显式能量管理模型被高层组件用来显式地控制设备的状态。任何时候高层组件使用这种方式通知设备打开或关闭设备时,设备会立刻被打开或关闭。隐式能量管理模型提供了一种用于允许设备自身来控制设备的状态的方法,遵循这种模型的设备不能够被外部组件显式地打开或关闭。TinyOS提供了如下的接口用于实现设备能量管理:上一页下一页返回12.2TinyOS操作系统(1)StdControl;若一个设备的开启或关闭所花费的时间可以忽略,那么它应该提供这个接口。
(2)SplitControl;若一个设备的开启或关闭所花费的时间不可以被忽略,那么它应该提供这个接口。(3)AsyncStdControl:由于上述两个接口都是同步接口,所以若想在异步代码中控制一个设备的能量状态,那么就必须使用AsyncStdControl接口。TinyOS的调度器在任务队列中没有可执行的任务时,会使处理器进入低功耗模式以节省能量。上一页下一页返回12.2TinyOS操作系统12.2.2模拟服务
TOSSIM是TinyOS的模拟器。它通过把组件替换成模拟的实现来向用户提供模拟服务。TOSSIM是一个离散的时间模拟器,当它运行的时候,它从时间队列中依次取出事件(以时间排序)并且执行它们。随着模拟的层次的不同,模拟事件可以是硬件中断也可以是高层的系统事件(例如包接收事件)。同时,任务也可以是模拟事件,所以post一个事件后,该事件在随后某个时间会运行,它的运行时间往往很短(例如几个微秒)。TOSSIM是一个程序库。用户必须写一个程序用来配置和运行模拟。TOSSIM支持两种编程接口;Python和C++这两种编程接口各有优缺点,而且这两种代码之间的转换也比较简单。目前,TOSSIM唯一支持的平台是mica2,而且还不支持能量检测。上一页下一页返回12.2TinyOS操作系统12.2.3基于TinyOS2.0系统的编程
1.组件和接口(ComponentsandInterfaces)1)组件
nesC组件使用的是一个纯局部的命名空间,这就是说一个组件除了要声明它将执行的函数外,还要声明它所调用的函数。组件调用函数时使用的名字都是局部的:这些局部函数名字可以与真正去执行的那些函数的名字不同,当一个组件A声明它调用一个函数B,这时A.B就是全局变量,另一个组件C,它调用函数B,那么C.B就是全局变量,这里A和B都引用函数B,但它们可能得到的是完全不同的实现上一页下一页返回12.2TinyOS操作系统2)接口每一个组件都有一个形式说明(specification),这个形式说明是一段代码,它声明了组件所提供(执行)接口(函数)和所使用(调用)的接口(函数)。在实际的使用中,组件很少只单独地声明函数,大多数组件所声明的都是接口。接口(interface)是相关函数的一个集合,用于可以根据功能的需要定义自己的接口,但在定义接口中的函数时,必须使用command或event关键字声明该函数是命令或是事件,否则编译时会报错。上一页下一页返回12.2TinyOS操作系统2.模块(InterfacesandModules)nesC有两种组件:配件(configurations)和模块(modules)。配件用于将组件连接在一起从而形成一个新的组件,模块提供了接口代码的实现并且分配组件内部状态,是组件内部行为的具体实现。下面介绍与模块有关的一些概念。
1)分段操作(SplitPhase)TinyOS在硬件和软件中都使用了分段操作。表12-7展示了连续操作和分段操作的区别。上一页下一页返回12.2TinyOS操作系统
从表中我们可以看到,在分段操作中,sendCount的加1操作是在发送完成事件中实现的。分段接口的一个重要特征就是两个阶段的调用是相反的:向下调用是开始操作,向上的signal操作是完成操作。在nesC中,向下调用的是命令,而向上调用的是事件。接口指定了这种关系的两个方面。上一页下一页返回12.2TinyOS操作系统2)类型化接口(typedinterfaces)
接口可以带有类型参数.例如上面的Read接口。接口的类型参数放在一对尖括号里,Read接口只有一个参数,该参数定义了它所要处理的数据的类型。如果提供者和使用者的接口都带有类型参数,在连接时,它们的类型必须匹配。例如,不能把接口Read<uint8t>连接到Read<uint16>.编译器在连接时会对参数进行张制类型检查,寻找在命令和事件中不相称的参数。上一页下一页返回12.2TinyOS操作系统3)模块实现(ModuleImplementation)
我们以表12-8中的采集程序Sense来看看模块实现部分的代码的编写。在implementation之后是该模块的实现部分。模块必须实现它所提供的接口中的每一个命令(Sense模块中没有提供接口命令)和它所使用的接口中的每一个事件。该程序周期地读取采集到的数据并根据数据决定点亮哪一个Led灯。注息它是怎样通过分段接口来实现上述功能的。调用Timer.startPeriodic(SAMPLINGFREQUEVCY)将启动定时器,在定时器触发事件Timer.fired中调用命令Read.read并且返回,随后的某个时刻(这段时间依赖于读操作的延时时间),数据源的程序将通知一个Read.readDone事件,并将读取得到的数据作为参数传递。在Read.readDone事件中根据读到的数据决定点亮哪一个Led灯。上一页下一页返回12.2TinyOS操作系统4)任务(Tasks)
任务是一个延迟执行的过程调用。一个模块可以抛出(post)一个任务给TinyOS调度器。在随后的某一时一刻,调度器将会执行这个任务。因为任务不是立即被调用的,所以它没有返回值。我们知道nesC组件使用的是一个纯局部的命名空间,所以任务必然在某个组件的命名作用域内。任务无须带任何参数,只需把任务所需的参数存储在组件中就可以了。任务其实就是一个无参数也无返回值的函数,但是这个函数前面必须用task关键字声明,原型如下:taskvoidTaskname();
组件使用post关键字来抛出一个任务给调度器,如下:postreadDoneTask();上一页下一页返回12.2TinyOS操作系统(5)原子语句(atomicstatements)
前面介绍了TinyOS中的并发模型。因为任务和硬件事件处理程序可能会被其他异步代码所抢占,所以在特定竞争条件下会导致数据不一致性。在nesC中通过使用原子语句(atomicstatements)的方式实现了对临界数据保护,例如;commandboolincrement(){atomic{a++;b-a+l;}}上一页下一页返回12.2TinyOS操作系统6)enum
由于nesC和TinyOS中可能会为常整数也分配了RAM空间,这依赖于编译器(好的编译器会把它们存储在程序存储器中)。但我们可以使用enum来节省空间,例如:enum{AM_MESSAGE=5};
这样组件就可以使用一个名字来维护常数值,这个值既不会被存储在RAM中也不会被存在程序空间,而且这种做法同时也改进了系统的性能。因为不需要从存储器装载该数据。它甚至比#define更好,因为它被存放在了调试符号表和应用程序元数据中。但还需注息:在变量的定义中使用enum可能浪费存储空间,因为enum缺省的是整型宽度。上一页下一页返回12.2TinyOS操作系统3.配件和连接(ConfigurationsandWiring)组件之间是完全独立的,只有通过连接才能绑定到一起。配件就是用于实现不同组件的接口之间的连接的组件,它把多个组件连接在一起从而形成一个新的组件,而且它也可以导出接口(另一种形式的连接)。配件的定义与模块类似,参考图12-1。它们都有形式说明作用域和实现作用域。上一页下一页返回12.2TinyOS操作系统
从使用组件的角度看,组件是模块还是配件是没有太大关系的。和模块一样,配件可以提供和使用接口。但是由于配件没有代码实现,所以这些接口的实现必须依赖其他的组件。
1)导通连接(PassThroughWiring)
导通连接是一个配件将两个组件连接到一起,并且必须使用“=”操作符把使用者一连接到提供者一操作符。“一>”操作符用于在同一个配件所命名的两个组件的接口之间实现映射(连接),而操作符“=”用于在配件自己的接口和配件所命名的组件的接口间实现映射(连接),它将配件内组件的接ii导出到配件的命名空间。上一页下一页返回12.2TinyOS操作系统2)扇入和扇出上面我们看到的接口之间的连接都是one-to-one的关系。其实,接口之间还可以n-to-k的关系,这里n是使用者数,k是提供者数。例如,在TinyOS通信部分介绍的AMSnoop-ingReceiverC通用组件(定义在opt\tinyos-2.x\tos\system),见表12-13.扇入(fan-ins)用来描述多个人调用同一个函数。例如有两个组件A和B都需要使用随机数,见表12-14。上一页下一页返回12.2TinyOS操作系统3)组合函数(CombineFunctions)
在上面的扇出问题中,调用一次AMSnoopingReceiverC.Receive.getPayload,有两个命令会被调用,每一个调用都会有一个返回值,我们可以通过ifelse语句来判断它们的返回值以确定这两个命令的调用是否都正确地返回了。这种情况下是比较简单的,但如果有10个命令被调用,每一个都有返回值时。我们再用ifelse的方法判断就比较麻烦而且容易出错,这时一可以使用nesC提供的combine函数,它将多个返回值组合后只返回一个值。一个数据类型可以有一个相关的combine函数。因为一个fan-out总是涉及调用N个相同的函数,调用者最终得到的返回值是对所有的被调用者的返回值使用combine函数之后得到的返回值。当nesC编译应用程序时,它自动产生一个fan-out函数,该函数会使用combine函数。上一页下一页返回12.2TinyOS操作系统4)参数化连接(ParameterizedWiring)
根据功能需要,组件有时一需要提供同一组件的多个不同实例。例如主动消息通信组件ActiveMessageC,这个组件有时需要提供多个AMSend接口和Receive接口实例,以满足不同的通信协议的要求(如一个接口专用于发送节点采集到的数据,另一个接口专用于发送路由消息),为了满足这种功能需求,可以使用如下的方式:上一页下一页返回12.2TinyOS操作系统configurationActiveMessageC{provides{interfaceSplitControl;interfaceAMSendasAMSendl;interfaceAMSendasAMSend2;interfaceAMSendasAMSend3;interfaceReceiveasReceivel;interfaceReceiveasReceive2;interfaceReceiveasReceive2;……}}上一页下一页返回12.2TinyOS操作系统5)缺省连接(Defaults)nesC中许多编译错误往往是由于忘记连接而产生的,所以nesC提供了缺省连接处理。如果一个组件连接到了某个接口,那么就按照该连接调用接口中的函数。若没有,则命令(或事件)会执行缺省的处理函数。例如,在初始化和启动部分介绍的RealMainP模块,它有三个缺省处理函数:defaultcommanderrortPlatformInit.init(){returnSUCCESS;}defaultcommanderror_tSoftwareInit.init(){returnSUCCESS;}defaulteventvoidBoot.booted(){}上一页下一页返回12.2TinyOS操作系统6)unique()和uniqueCount()函数在使用参数化接口的时候,必须保证每一个相同的接口有不同的参数ID,这就要求程序员必须知道已经使用的参数和未使用的参数,这对程序员来说是一个很大的负担。为了减轻这种负担,nesC提供了一个编译时的函数unique(),使用这个函数就不必为寻找一个没有使用的ID而烦恼。当nesC编译一个程序,它将把所有的对unique()调用变换成整数标志符。unique函数需要一个字符串关键字作为参数。上一页下一页返回12.2TinyOS操作系统4.通用组件(GenericComponents)
通用组件和类型化接口是nesC1.2的新特性。一般来说,组件是单一实例的,这就是说,组件的名字是全局命名空间一个单独的实体。一个组件只可以被实例化一次。当两个不同的组件引用MainC,它们都将会引用同样的代码段和状态。但通用组件不是单一实例的,它在配件内能被实例化,通用组件的定义见图12-6。通用组件与非通用组件原型定义的最大差别有两点:(1)在关键字component(表示module或configuration)之前有一个generic关键字,它表示该组件是通用组件。
(2)通用组件在组件名字后必须带有参数列表,从这方面来看类似于函数的定义,若该通用组件不需要参数,那么该参数列表为空。上一页下一页返回12.2TinyOS操作系统1)通用模块(GenericModules)
通用组件带有三种参数,如果参数是一个类型,那么必须用typedef关键字声明,例如用模块VirtualizeTimerC(定义在opt\tinyos-2.x\tos\lib\timer),定义如下:上一页下一页返回12.2TinyOS操作系统
通用模块VirtualizeTimerC带有两个参数。第一个参数是定时器精度参数,因为该参数是一个类型,所以使用typedef关键字声明,有关定时器精度参数的意义在参数化接口部分已经有所介绍,该参数作为参数化接口的参数,而且这个参数也提供了额外的用于检查接口类型的功能;第二个参数表示用户使用(实例化)的最大定时一器个数,它通常使用前面我们介绍的uniqueCount()来计算得到。上一页下一页返回12.2TinyOS操作系统2)通用配件(GenericConfigurations)
模块是一个包含可执行代码的组件,而配件定义组件之间的关系用来构成更高层次的抽象。通用模块是可执行代码的可重用部分,而通用配件是可重用的用于构成高层次抽象的关系集。所以通用配件构成了更高层次的虚拟化和抽象。使用通用配件与使用通用模块的方法是一样的,下面我们再看一个简单的通用配件AMSenderC,这个配件在TinyOS通信部分已经有所介绍了,定义如下:上一页下一页返回12.2TinyOS操作系统上一页下一页返回12.2TinyOS操作系统上一页下一页返回12.3MANTIS操作系统12.3.1MANTISOS系统架构
MOS系统是经典的分层式多线程结构,如图12-7所示。应用程序线程和底层操作系统API相互独立,所以MOS通过提供不同平台的API就可以实现对多个平台的支持。MOS系统由内核(kernal/scheduler)、网络栈、通信层(COMM)以及其他组件构成(如dev驱动层)。下一页返回12.3MANTIS操作系统1.MANT1SOS内核和调度器
MOS使用了一个类似于Unix风格的调度器,它提供了基于优先级的多线程调度和在同一优先级中进行轮转调度的服务。它的运行时间少片是可配置的,缺省配置为10ms。MOS也支持互斥信号量和计数信号量。MOS在逻辑上把KAM可以分成两部分:一部分在编译时间分配给全局变量,其他部分以堆的形式管理。当系统创建了一个新的线程,内核便从堆中分配堆栈空间,当线程退出后,堆栈空间又被归还给了堆。与内核有关的一些重要函数在表12-15中给出。上一页下一页返回12.3MANTIS操作系统
内核的主要全局数据结构是线程表,每一个线程占一个条目。其线程数据结构定义如下:上一页下一页返回12.3MANTIS操作系统线程的状态有五种:空闲状态、运行状态、就绪状态、阻塞状态、休眠状态。线程的阻塞状态有两种:阻塞空闲状态和阻塞休眠状态。MOS为线程设置了5个优先级:内核级优先级、休眠级优先级、高优先级(能抢占所有其他的优先级的线程)、正常级的优先级、空闲级优先级。系统中缺省定义的最大线程数为6个。内核为每一个优先级维护一个含有头指针和尾指针的就绪列表。其数据结构定义如下:typedefstruct{threadt*headthreadt*tail}tlistt;上一页下一页返回12.3MANTIS操作系统MOS也可以使用信号量机制,应用程序可以根据自己的需要在程序中声明,其数据结构定义如下:typedefstruct{int8_tval;//计数字节
tlist_tq;//头指针和尾指针
}mos_sem_t
该结构包含头指针、尾指针和一个计数字节。在任何时刻,任每一个线程或是属于就绪列表,或是属于信号量列表(等待某个事件发生或等待处理器运行)。内核中还维护着一个用于指不当前正在运行线程的全局数据结构,定义如下:thread_t*_currentthread上一页下一页返回12.3MANTIS操作系统整个MOS系统是从main()主函数开始运行的,这个函数定义在mantis-0.9.5\src\mos\sys中,如下:上一页下一页返回12.3MANTIS操作系统2.网络协议栈在MOS中,通信是层式的网络协议栈,没有和线程堆栈相混合。网络协议栈作为一个或多个用户级线程执行,如图12-7所示。网络栈支持网络的第三层及三层以上,例如,路由层、传输层和应用层。MAC协议由COMM层提供。网络协议不同的层可以执行在不同的线程中,或者一网络协议栈中所有的层可以执行在同一个线程中。网络协议栈的各层都被设计成了最小化内存缓冲区,一个数据包的数据负载通常在一个线程内会经历网络协议栈的所有层。通过采用这种机制,网络协议栈避免了数据拷贝,这和TinyOS是一样的。上一页下一页返回12.3MANTIS操作系统3.通信层(COMM层)MOS的COMM层为通信设备的驱动程序定义了统一接口,如串口、无线通信设备等,它实现了异步的I/O操作。COMM层也管理数据包缓冲和同步设备驱动程序(如读取传感器数据),如图12-8所示。COMM层也实现了零拷贝操作和零轮询。高层的网络协议或用户线程可以使用COMM层提供的统一接口函数与通信设备交互。表12-16列出了用于实现交互的几个重要函数。上一页下一页返回12.3MANTIS操作系统4.设备驱动层(DEV层)MOS设备驱动层(DEV层)涵盖了同步I/O设备的驱动程序(如传感器、外部存储器等)和异步通信设备的驱动程序(如无线电、串口等),见图12-8(右)。每一个设备都为上层用户提供了表12-17所示的POSIX风格的系统调用函数。5.能量管理MOS提供了标准的接口用来控制外部设备的能量状态。MOS中可以使用devmode()函数来修改底层设备的能量状态。它支持三种不同的能量设备状态:开启、关闭和空闲。上一页下一页返回12.3MANTIS操作系统MOS中的微处理器能量管理与线程调度是紧密结合的,并且它支持2个级别的节能。当调度器就绪队列空闲了,调度器会隐式地使微处理器进入空闲状态。在这种状态下微处理器比在活跃状态下消耗更少的能量,但是仍然支持所有的外设操作。为了实现进一步的节能,调度器需要更多的来自线程的信息以便决定使处理器进入休眠状态是否是安全的。线程使用了mos_thread_sleep()函数来通知内核它要进入休眠状态,并可以通过该函数的参数指定需要休眠的时间。当系统中所有的线程都调用了函数mos_thread_sleep()时,调度器就会使处理器进入更节能的休眠状态,在这种状态下,只有一个定时器处于运行状态以便在休眠周期到的时候能唤醒相应的进程。上一页下一页返回12.3MANTIS操作系统12.3.2MANTISOS编程
MOS使用了C语言作为编程语言。读者发现基于MOS的C编程是非常简单的。MOS中每一个应用程序的开头都有一个start()函数。MOS系统在初始化完毕后会自动调用start()函数从而启动应用程序运行,所以该函数是所有应用程序的开始执行点,用户应用必须首先实现该函数。但请注息,因为该函数是所有应用程序的开始执行点,所以它不应该有过多的繁重计算。如果用户程序需要做大量的工作,那么可以使用MOSthreadnew()生成新的子线程,用户可以把一些繁重的工作安排在子线程中完成。上一页下一页返回12.3MANTIS操作系统1.使用无交互后台程序命令Commanddaemon(无交互后台程序命令)提供了一种与连接在串口上的传感器节点交互的方式。用户也可以定义自己的命令。系统中有一个命令列表,用于记录命令,其定义如下:上一页下一页返回12.3MANTIS操作系统MOS系统中有一个用于生成新线程的函数,其原形如下:uint8_tmos_thread_new(void(*function_start)(void),uint16_tstack_size,uint8_tpriority),其参数含义如下:(1)function_start:指向在start()函数中将要调用的函数(进程)。(2)stack_size:为新生成的进程分配的堆栈大小(字节),一般情况下128比较合适。(3)priority:为新线程赋子的优先级。上一页下一页返回12.3MANTIS操作系统要想使用无交互后台程序命令,必须在start函数里生成一个新的commanddaemon线程,如下:mos_thread_new(mos_command_daemon,MOS_COMMANDER_STACK_SIZE,PRIORITY_NORMAL)上述语句生成了一个新线程mos_commanddaemon,原型如下:voidmos_command_daemon(void),上一页下一页返回12.3MANTIS操作系统
该线程的作用是监听串口的输入。使用MOS_register_function函数可以注册用户定制的函数。该函数的原型如下:boolmos_register_function(char*name,void(*func_pointer)(void))
该函数用于注册函数指针func_pointer所指向的函数。当输入字符串name是,这个注册的函数将被调用。上一页下一页返回12.3MANTIS操作系统2.设备层(DeviceLayer)
上面也介绍了一些与使用设备相关的函数。设备层提供了与硬件设备通信的通用接口。设备的驱动被定义在MANTIS/src/MOS/dev中,设备列表定义在MANTIS/src/MOS/dev/include/dev.h中。设备通过DEV_NAME指定名字。程序中要想使用设备,首先应用#include"dev.h',包含相关的头文件,然后调用如下函数:上一页下一页返回12.3MANTIS操作系统上一页下一页返回12.3MANTIS操作系统
若系统中新加入了设备,为了能使用这个设备,需要做以下的工作:(1)把设备的驱动程序(.c文件)放到MANTIS/src/MOS/dev中,并且将它的头文件放在相应的子目录中,这样做的目的是确保它们都被编译进内核。同时还需考虑该设备是否支持写或读或是读写都支持,以及是否需要ioctl。
(2)创建新设备所需要的何一个函数,函数应具备如下的格式:uint8_tmy_dev_write(constvoid*buf,uintl6_tsize);上一页下一页返回12.3MANTIS操作系统uint8_tmy_dev_read(constvoid}buf,uintl6_tsize);uint8_tmy_dev_ioctl(int8_trequest,va_listarg);(3)为dev_open(),dev_close()这两个函数增加一个信号量。该信号量需要在dev文件中声明,而且需要在dev.h文件中声明成extern。(4)最后,把init调用放在main.C中,init应该能够初始化该设备的mutex信号量,并且执行与该设备相关的初始化。上一页下一页返回12.3MANTIS操作系统3.使用通信层
COMM层用于通信,是底层硬件操作的抽象。COMM掩盖了创建缓冲区和底层不同硬件通信设备接口的细节,以方便用户使用。用户线程或高层通信协议线程调用commsend发送数据包时,这时需要把指向包缓冲区的指针作为参数。COMM层将阻塞这个发送进程,并把这个指针传递给具体的设备驱动程序,由低层的驱动程序完成数据的发送,发送完成后,被阻塞的线程会继续运行。上一页下一页返回12.3MANTIS操作系统接收缓冲区是由COMM层自己管理的。设备的驱动程序可以中请缓冲区,并把收到的数据包放入该缓冲区。当数据包接收完成后,设备驱动程序调用com_swap_bufso。该函数实现了满缓冲区和空闲缓冲区的交换。同时COMM层会把这些填满的数据包按顺序放在缓冲区中。当应用程序线程调用com_recv时,它将会被阻塞,直到设备填满了缓冲区,同时会返回指向填满数据缓冲区的指针。因为接收线程现在处理一个由COMM层分配的缓冲区,所以它在使用完该缓冲区后必须调用com_freebuf(声明为静态存储的除外)来释放缓冲区,从而通知COMM层该缓冲区可以被重新使用。接收线程释放缓冲区的这个额外调用会增加复杂度,但这样做使COMM层提供了真正的零拷贝服务。同时,因为COMM层是中断驱动的,所以COMM层的零轮询能较好地实现节省能量服务。上一页下一页返回12.3MANTIS操作系统COMM的缓冲区结构定义如下:上一页下一页返回12.3MANTIS操作系统上一页下一页返回12.3MANTIS操作系统4.休眠和定时
1)休眠(Sleeping)MOS中使用mos_thread_sleep函数使线程进入休眠状态,其原型如下:voidmos_thread_sleep(uint32_tsleeptime)
该函数带有一个32位的参数、leeptime,该参数表示线程睡眠的毫秒数。目前最小的休眠时间是128ms.
调用mos_thread_sleep将使线程进入休眠状态。如果就绪队列中没有线程,内核将使处理器进入低功耗模式以节省电能。一旦睡眠时间结束,线程将会被重新放入就绪队列。上一页下一页返回12.3MANTIS操作系统2)使用计时器时钟可以使用realtimer来实现计时操作,它是底层硬件定时器的抽象。系统中有3个与计时器有关的函数,见表12-19。要使用real_timer,首先需要包含它的头文件:#include"realtime.h"接着必须将硬件定时器初始化为CTC模式:real_timer_init();最后需要做的就是提取时间。上一页下一页返回12.3MANTIS操作系统3)使用定时器AlarmsAlarm用来实现节点上的定时器功能。与alarm有关的函数见表12-20。使用alarm过程如下:
首先必须包含提供alarm的头文件,如下:#include"clock.h"
下一步需要实例化;alarm。alarm是一个结构类型,结构中包含一个用于回调的函数和一个“void”类型指针(可以指向用户定义的数据),alarm结构定义请参见表12-21。上一页下一页返回12.3MANTIS操作系统
下一步需要设定回调函数,当alarm触发(溢出)时该函数将被调用。该函数必须具有以下的原型:voidmyfunc(void*data);
另外还需要注息:该函数将在中断处理中执行,所以在该函数中不要有输出语句或有过多的处理。
5.使用输出语句
MOS中可以使用printf函数实现输出功能,但它和标准系统中的函数有一些差别。对printf调用将把数据包放到独立的缓冲区中,并且将它通过串口发送出去。MOS中printf的参数和标准系统中也有所不同:上一页下一页返回12.3MANTIS操作系统%s——字符串%c——字符%C——8位于进制数%d——16位于进制数%l——32位于进制数%x——16位的于六进制数%o——16位八进制数%b——16位二进制数%%——%字符上一页下一页返回12.3MANTIS操作系统除了printf函数,MOS中也可以使用put、函数和putchar函数,原型如下:intputs(constchar*msg);intputchar(intcharacter);这些函数的用法和标准C中的用法相同。上一页下一页返回12.3MANTIS操作系统6.一个简单的应用程序下面是Blink应用程序完整的代码,它使用了三个线程周期地点亮led指示灯。上一页下一页返回12.3MANTIS操作系统上一页下一页返回12.3MANTIS操作系统上一页下一页返回12.3MANTIS操作系统12.3.3MANTISOS模拟器
MANTIS的原型环境为不同应用程序的原型设计提供了一个框架(Framework),使得这些应用程序在因特网和配置好的传感器网络间建立连接。
MANTIS的原型设计环境扩展了模拟功能,为传感器网络提供了网络管理开发接口,并实现了模拟过程的可视化。首先,MANTIS允许程序员在虚拟传感器节点和真实的传感器节点上测试同一个程序。第二,MANTIS把虚拟环境和真实的网络结合在一起,例如允许虚拟节点和物理节点共存,并且在原型环境中还可以互相通信,如图12-9所示。第三,MANTIS的虚拟节点可以使用MANTISAPI以外的API。例如一个带有MANTISAPI的虚拟节点可以作为一个UNIXX窗口应用程序运行,这个应用程序可以和数据库API通信。上一页下一页返回12.3MANTIS操作系统MANTIS通过使用跨平台的通用API实现了多模型原型环境。MOS可以在x86Linux或Windows)平台上作为一个应用程序运行。其中运行于x86平台的用户应用程序称为XMOS。如图12-10所示,XMOS使用POSIX的shim层实现了MANTIS的API和Unix操作系统之间的转换,这样就使得MOS应用程序既可以作为运行在x86平台上的虚拟传感器节点,也可以作为运行于ATMEI传感器节点(AMOS)上的应用程序。上一页返回12.4SOS操作系统12.4.1系统架构(SystemArchitecture)SOS由可以动态加载的模块和静态内核组成,参见图12-11。静态内核可以先被烧写到节点上,节点运行过程中用户还可以根据任务的需要动态地增删模块。静态内核实现了最基本的服务,包括底层硬件抽象、灵活的优先级消息调度器、动态内存分配等功能。模块实现了系统大多数的功能,包括驱动程序、协议、应用程序等。这些模块都是独立的,对模块的修改不会中断系统的操作。下一页返回12.4SOS操作系统12.4.2模块(Modules)1.模块结构每一个模块本身都是一个独立的代码实体,它有下面的原型:上一页下一页返回12.4SOS操作系统上一页下一页返回12.4SOS操作系统上一页下一页返回12.4SOS操作系统
执行流程可以通过下面的两种入口机制之一进入模块,如图12-12所示。
(1)调度器发出的消息。
(2)外部模块调用它们所注册的函数。何一个模块都有一个消息处理函数(如上面的module函数),其功能就是把不同的消息派发给模块内对应的处理程序以实现消息处理,这个功能是通过模块处理函数中的switch语句实现的。模块处理函数有下面的原型int8_tmodule(void*state,Message*msg),这个函数把消息和模块的状态作为参数。上一页下一页返回12.4SOS操作系统所有的模块消息处理程序都需要实现对init和final消息的处理,因为两个消息是内核在插入和删除期间产生的。在模块插入完成时,内核会调度init消息以设置模块的初始状态,包括初始化定时器、函数注册和函数订阅等;模块被删除时,内核调度final消息用于释放节点的所有资源,包括定时器、存储空间和被注册的函数等。模块消息处理程序也能够处理其他的消息。例如,可能处理定时器触发消息、传感器数据到来消息和从其他节点或模块发送来的数据信息等。上一页下一页返回12.4SOS操作系统SOS中的消息都是异步的。模块发出一个消息后,这个消息被放到了消息队列中。SOS主调度循环从优先级队列中取得消息并将消息传递给目标模块的消息处理程序。模块之间的直接函数调用用于实现模块间需要同步运行的操作。SOS使用了函数注册和订阅机制实现了直接函数调用。模块状态存储在模块外部的RAM中。模块在内存中是可被重定位的,因为程序状态由SOS内核管理。跨模块函数的位置通过一个注册进程提供给外界,并且任何模块的消息处理函数在二进制代码中都有一致的偏移量。上一页下一页返回12.4SOS操作系统2.模块交互(ModuleInteraction)
在下面的几种情形下,模块间会发生交互:①模块间消息传递;②对一个函数的直接调用,这个函数被另一个模块所注册;③模块使用内核调用函数ker一*实现对系统内核的调用。
1)函数注册
SOS使用函数注册和汀阅机制提供了跨模块的直接通信和向上调用(内核调用模块)。一个模块提供给其他模块调用的函数个数在模块头中的.num_prow_funs变量给出了,该模块调用的其他模块的函数个数也在.num_sub_funs给出了。上一页下一页返回12.4SOS操作系统(2)动态函数订阅。使用动态函数订阅时,只需在模块头中指明原型信息,并且模块ID和函数ID均使用运行时的ID。(3)调用订阅到的函数。成功订阅函数后,用户使用SOS_CALL(fnptrptr,type,args…)来调用被汀阅到的函数,它定义在\sos1.7\kernel\include\fntable_type.H中。3)模块与内核交互模块使用了跳转表来实现对内核函数的访问。跳转表是一种重定向调用的技术,定义在\sos1.7\kernel\include\kertable.h上一页下一页返回12.4SOS操作系统
如图12-13所示,模块调用内核函数的过程如下:模块首先查找跳转表的某一位置(如定时器ker_time:所在的位置),然后由跳转表负责调用内核中的底层硬件的API函数。当硬件完成操作后(如定时时间到)会产生相应的中断消息。内核会把该消息放入高优先级的消息缓冲队列等待调度,最后由调度器将该消息传递给调用模块进行处理,由此实现对内核函数的访问(通过跳转表间接实现)。上一页下一页返回12.4SOS操作系统4)模块插入(ModuleInsertion)SOS模块很多好的特点使得模块插入变得比较容易。例如,模块化的结构、函数注册和订阅机制等。除此之外,SOS还使用了其他的技术使得模块插入变得比较容易。例如在编译的时候,SOS使用了一个链接脚本程序,通过它把模块的消息处理函数放在模块二进制映像中的已知偏移量处,这使得在模块插入期间的链接更容易实现。上一页下一页返回12.4SOS操作系统
模块插入过程如下:.运行于节点上的分布协议监听网络中的新模块。
.当监听到一个模块的广播后,它检查该模块的版本是否比运行在自己节点上的模块更新,或者节点对这个模块感兴趣并且有空闲的程序空间可以容纳这个模块。
.当上述的两种情况之一成立,则分布协议开始下载模块并立即检查数据包头中的元数据。元数据中包含了模块ID,所需的内存空间大小(用于存储模块的局部状态),模块版本信息(用于区分模块的新旧)。
.若SOS内核不能为模块分配用于存储模块局部状态的内存,模块插入立即失败退出。上一页下一页返回12.4SOS操作系统.在模块插入期间会创建一个内核数据结构,该结构以模块ID(包含在元数据中)作为索引。这个数据结构用来存储模块消息处理的绝对地址,指向用于存储模块状态的动态内存的指针和模块的标志。
5)模块删除(ModuleRemoval)
内核通过调度一个final消息给模块来启动模块删除。使用这个消息,模块可以进行资源回收并通知依赖于该模块的其他模块。收到final消息后,内核将通过释放动态分配的存储空间、定时器、传感器驱动程序和被模块所拥有的其他的资源。上一页下一页返回12.4SOS操作系统12.4.3传感器管理
如图12-14所示,SOS中传感器管理使多个模块间共享传感器数据,它为多种不同的传感器提供了一致的数据访问API,隐藏了不同设备的实现细节。底层设备的驱动程序把自己注册到传感器管理中。传感器驱动还可以动态地装载。这样系统可以先把其他的配置完成后再配置传感器。上一页下一页返回12.4SOS操作系统12.4.4消息调度(MessageScheduling)SOS使用了非抢占的优先级队列调度。优先级队列分为两种:高优先级和低优先级。高优先级队列用于实时性比较张的事件,包括硬件中断和敏感定时器等。低优先级队列用于调度大多数普通的事件。优先级机制使对时间有要求的消息能得到优先的调度,例如来自ADC中断的消息,从而很好地改善系统的中断响应服务性能。SOS的消息也能够传递参数。当动态分配的内存需要在模块间传递时,SOS还提供了一种用于请求数据所有权改变的机制。SOS使用post_short和post_long这两个函数将消息放入相应的队列,这两个函数的参数说明请参见表12-22。SOS中消息结构定义如下:上一页下一页返回12.4SOS操作系统上一页下一页返回12.4SOS操作系统优先级消息队列定义如下:上一页下一页返回12.4SOS操作系统
从消息结构的定义中可以看到:消息头中包含了完整的源和目的信息,这样SOS就可以把接收到的消息直接插入到相应的队列。消息头中还有一个指向数据负载的指针,它用来在模块间传递简单的参数和复杂的数据。如果想要在模块间传递几个字节的数据,可以直接使用消息头中提供的缓冲区,只要将消息头中(上述的)数据负载指针指向该缓冲区即可。若要传输的数据量较大,需要单独地分配一块缓冲区。下面是系统的主调度函数(该程序来自SOS的源程序sos-1.7\kernel\sched.c)上一页下一页返回12.4SOS操作系统上一页下一页返回12.4SOS操作系统上一页下一页返回12.4SOS操作系统12.4.5动态内存(DynamicMemory)SOS使用了动态内存。它采用了简单的最佳适应固定块内存分配策略,块的大小有三种。大多数的内存分配(包括消息头)只分配最小的块。空闲的内存按照块的大小组成空闲链表,这种组织方式提供了常数时间的内存分配和回收开销。与内存有关的API函数在表12-23中列出。下面是内存分配函数的一个例子:pkt=(uint8_t*)ker_malloc(hdr_size+sizeof(Surg
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026督察岗面试题目及答案
- 2026年成都新高考地理全程备考指南
- 2026供销社出纳面试题及答案
- 2026管理专员面试题目及答案
- 2026合同工面试题目及答案
- 2026年浙江卫视竞赛试题及答案
- 公务员服装试题及答案
- 管道标识试题及答案
- 面包品牌施工方案
- 保温封闭施工方案
- 护理会诊制度及查房制度课件
- GB/T 42598-2023机械安全使用说明书起草通则
- 大学英语六级词汇表(全)含音标
- 主要施工管理计划(通用版)
- 农业银行境外汇款申请书样板
- JJG 921-2021环境振动分析仪
- GB/T 5900.4-2022机床主轴端部与卡盘连接尺寸第4部分:圆柱连接
- SB/T 10468.2-2012轮胎理赔技术规范
- SA8000-2014社会责任绩效委员会SPT组织架构、职责和定期检讨及评审会议记录
- 学校问题整改情况台账
- 二年级上册数学课件-6.12 找规律填数整理丨苏教版 (共20张PPT)
评论
0/150
提交评论