首页 理论教育 AM通信:一个自动分发数据包的实用方式

AM通信:一个自动分发数据包的实用方式

时间:2023-06-28 理论教育 版权反馈
【摘要】:由于这种类型往往自动将接收到的数据包分发给适当的处理者,就像是信息可以主动地寻找并到达目的地,因此这种类型的通信方式被称为Active Message。

AM通信:一个自动分发数据包的实用方式

在节点之间无线通信过程中通常有多种服务都要接入无线射频模块,TinyOS提供了Active Message(AM)层对无线射频模块的复用,每个数据包被定义成AM类型,并通过一个字节来辨别该数据包类型。由于这种类型往往自动将接收到的数据包分发给适当的处理者,就像是信息可以主动地寻找并到达目的地,因此这种类型的通信方式被称为Active Message。以下是两种AM接口,在目录tos/interface中:

AMPacket:对message_t抽象数据类型进行基本AM存取。这个接口提供了获得一个节点AM地址、AM包的目的地和AM包类型的命令,也提供了设置AM包目的地和类型,并检查目的地是否为本地节点。

AMSend:相似于Send接口,提供了基本的AM发送接口。这二者的关键差别是AMSend是在send命令中获得目的地的AM地址。

另外很多组件实现了基本通信和active message接口,以下是在tos/system目录下的组件。

AMReceiverC:提供了接口:Receive,Packet,AMPacket。

AMSenderC:提供了接口:AMSend,Packet,AMPacket,PacketAcknowledgements as Acks。

AMSnooperC:提供了接口:Receive,Packet,AMPacket。

AMSnoopingReceiverC:提供了接口:Receive,Packet,AMPacket。

ActiveMessageAddressC:提供了获得和设置AM地址的命令。这个接口一般不怎么使用,因为改变节点的AM地址可能会破坏网络堆栈

1.ActiveMessageC

由于TinyOS支持多种硬件平台,每个平台都有其自己的无线射频芯片驱动,ActiveMessageC是AM接口和下层实现的桥梁,提供了大多数AM通信所需的接口。ActiveMessageC根据其平台的不同进行不同的命名,主要根据其射频芯片。其中

eyesIFX平台的ActiveMessageC,由Tda5250ActiveMessageC实现。

intelmote2,micaz,telosa和telosb平台的ActiveMessageC,由CC2220ActiveMessageC实现。

mica2平台的ActiveMessageC由CC1000ActiveMessageC实现。

以下是CC1000ActiveMessageC的代码实现如下:

978-7-111-40722-5-Chapter07-64.jpg

978-7-111-40722-5-Chapter07-65.jpg

2.message_t

TinyOS提供了许多组件,这些组件提供了许多接口来抽象底层通信服务。这些接口和组件均使用一个共同的消息缓冲器抽象,被称为message_t。message_t代替了TinyOS1.x中的TOS_Msg提取。message_t是不透明的,因此它是不可以直接访问的。"message_t"类型的主要目的是允许报文作为内存的一个连续存储区域以零拷贝的方式在不同的链路层传输。

在TinyOS2.x中,标准的消息缓存是message_t。message_t结构在"tos/types/message.h"中定义。message_t的代码实现如下:

978-7-111-40722-5-Chapter07-66.jpg

此格式将数据字段保持在一个固定的偏移量上,这对于在两个不同的链路层之间传送数据是非常重要的。如果数据负载字段对于不同的链路层有不同的偏移量,那么将一个包从一个链路层发送到另一个链路层就需要使用"memmove"操作(需要使用拷贝)。在TinyOS 1.x中TOS_Msg是一个明确的active messaging报文,而message_t是一个更泛化的数据链路缓存。实际上,大多数TinyOS 2.x数据链路层都提供active messaging,同时也可以由AM协议栈向非AM协议栈传输message_t报文。

message_t的头部,尾部,元数据对于上层协议来说都是不透明的,不能直接存取这些域。而数据链路层提供从nesC接口,通过它们来实现访问结构内部的字段。

每个不同的链路层都会定义它们自己的头,尾,元数据字段。这些字段必须是external结构("nx_struct"),这些结构的所有成员必须是external类型("nx_")。这样做有两个原因:一、external类型能确保跨平台的兼容;二,它会强制结构在字节边界上对齐,从而避免包缓冲区及其内部的字段的对齐问题。在整个包被发往串口被记录通信日志时,元数据字段也必须是nx_structs的。

例7.1:CC1000的射频实现在其"CC1000Msg.h"中定义message_t结构

978-7-111-40722-5-Chapter07-67.jpg

978-7-111-40722-5-Chapter07-68.jpg

每个数据链路层都要定义它自己的结构体,而一个平台要负责定义其message_header_t,message_footer_t和message_metadata_t。这是因为一个平台可能有多个数据链路层,通过以上三个部分来决定有哪些结构体是需要的。这些必须定义在平台目录下的platform_message.h文件中。

例7.2:在mica2平台下有两个数据链路层:CC1000射频模块和TinyOS串口协议栈,在tos/platforms/mica2/platform_message.h文件中如下代码:

978-7-111-40722-5-Chapter07-69.jpg

一般来说串口数据包的格式中没有footer或者metadata部分,在代码中可以看到message_footer中只有cc1000_footer_t cc1k。(www.xing528.com)

例7.3:假如一个名为“megamica”的平台同时有CC1000和CC2420射频,那么它的“platform_message.h”就应该如下:

978-7-111-40722-5-Chapter07-70.jpg

978-7-111-40722-5-Chapter07-71.jpg

如果一个平台有多个链路层,那么它应该将message_t的每一个header,footer,metadata字段分别定义成链路层结构的联合字段,这样能保证在结构中为所有的链路层都分配足够的空间。

3.The messaget fields

TinyOS 2.x组件将包看做是一个抽象数据类型(ADTs),而非一个C语言结构体,这样做有比较明显的优势,链路层的客户并不依赖于结构中特定的字段名字和位置,这就使其能够选择包的结构,增强了操作的自主性,另外还在其他方面还作出了各种各样的优化

在数据链路层之上的组件必须从接口中存取包的字段。一个引入新的包字段的组件应该要为其他与其交互的组件提供一个访问接口。这些结构应当实现存取数据的get/set操作,而不是给出包结构中相关字段的偏移量。

例如在active message中的AMPacket接口,它能提供接入AM字段的命令。在TinyOS1.x中,组件能直接访问TOS_Msg.addr,在TinyOS 2.x中,组件需要调用AMPacket.get Address(msg)。这些接口中最基础的是Packet接口,它能提供存取一个包的数据负荷的命令。

链路层组件在存取包字段时可能不同于其他组件,它们关心具体的包格式,因此它们要实现负责为其他组件实现访问这些字段的接口。

message_t的头部是一个字节数组,它的长度是一个硬件平台的数据链路头部组合的长度。这些射频协议栈倾向于将数据包连续地存储起来,因此包在内存中的布局并不一定需要反映其nesC结构的布局。

一个包的头部字段可能会在message_t的某个位置开始,而并非一定在message_t的首个字节处。

例7.4:在Telos平台中:

978-7-111-40722-5-Chapter07-72.jpg

CC2420头部有11个字节长,而串口头部才5个字节。串口头部结尾处紧挨着数据字段的开始处,因此需要在串口头部前填充6个字节。下图显示了message_t的布局:一个10字节的CC2420包和一个12字节的串口包。

978-7-111-40722-5-Chapter07-73.jpg

图7-8 message_t的布局

CC2420包和串口包都没有footer字段,串口包没有任何元数据。

数据链路层包头字段并不需要在message_t的开始处,它在数据字段开始处的一个负偏移位置上。当一个链路层组件需要读写包头部字段时,它必须从数据字段开始处减去一个偏移量来计算头部字段所在位置。例如,串口协议栈头部有一个active message字段,是AM类型,返回AM类型的“AMPacket.type”命令如下:

978-7-111-40722-5-Chapter07-74.jpg

直接计算负的偏移位置是不方便的,所以串口协议栈使用内部的getHeader函数来帮助完成此事。很多其他单跳协议也采用了此方法,nesC编译器会将此函数内联化,以降低开销,大多数情况下C编译器会将其编译成一个偏移寻址。

在message_t的数据字段中存储了单跳的包负荷。它的长度是TOSH_DATA_LENGTH,默认值28字节。TinyOS应用能在编译时重新定义TOSH_DATA_LENGTH,重新定义使用ncc的一个选项:-DTOSH_DATA_LENGTH=x。因为这个值是能重新配置的,所以有可能存在2个不同版本的应用,它们的MTU是不同的。如果接收到的包数据长度大于TOSH_DATA_LENGTH,那么它必须将包丢弃。因为头部正好在数据字段之前,所以一个平台上所有的链路层数据字段都有一个相对message_t缓冲区并且都有一个固定的偏移量。

message_footer_t域确保message_t为所有的底层链路层提供足够的空间来存储尾部。就像header一样,footer并不必像C结构所表示的那样存储在内存中,相反,它们是依赖于特定实现的。一个单跳的层次可能将footer紧接在数据字段后。对于长度短的包而言,这就意味着footer字段实际上是被存储在数据域中的。

message_t的元数据字段用于收集单跳协议栈所需要的元数据信息,而非用于传输。这种机制使得基于包的协议层能为每个包存储RSSI,时间戳等信息。

例7.5:CC2420元数据类型结构:

978-7-111-40722-5-Chapter07-75.jpg

978-7-111-40722-5-Chapter07-76.jpg

message_t结构为固定长度的header和footer优化过,可变长的footers通常是容易实现的。可变长的header则稍微困难。有三种通用方法能使用。

如果链路硬件是基于字节的,那么头部可以存储在message_t结构的开始处,这样有一个可知的偏移量。头部和数据字段之间可能会填充其他值。

如果链路硬件是基于包的,那么协议栈既能包含指示头部开始的元数据,或者把头部放在固定位置,也能在接收和发送端使用"memmove(3)"。在接收端和发送端使用memmove的情况下,接收端接收包,将其持续地读入message_t的header处,一旦包被完整地接收了,头部就会被解码,以获得数据字段的长度,然后此包的数据字段就能被拷入message_t的数据域中。要注意的是,一旦传输完成,它们就要被拷贝回来。可选的方法是射频协议栈在底层保存一个包的拷贝。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈