分享

TEP111 message_t类型简介

 幸福的乐土 2012-03-31
message_t类型简介 


目录

[隐藏]

摘要

此文档描述了TinyOS 2.x 消息缓存的抽象类型"message_t",介绍了消息缓存的设计考虑还有"message_t"在哪和怎样定义,以及数据链路层是应该如何使用它的。"message_t"类型的主要目的是允许报文作为内存的一个连续存储区域以零拷贝的方式在不同的链路层传输。

This memo covers the TinyOS 2.x message buffer abstraction, ``message_t``. It describes the message buffer design considerations, how and where ``message_t`` is specified, and how data link layers should access it. The major goal of ``message_t`` is to allow datagrams to be passed between different link layers as a contiguous region of memory with zero copies.

简介

在TinyOS 1.x中,消息缓存是"TOS_Msg". 这个消息缓存类型包含了AM包和形如时间戳、应答位、信号长度等包的元数据。"TOS_Msg"是一个固定长度的结构,最大长度值默认为29字节。定长的缓存允许TinyOS 1.x拥有零拷贝的语义:当一个组件接收到一个buffer后,它能为低层返回一个指向新buffer的指针,以便接受下一个数据包,而非将此buffer的内容拷贝出去来腾出空间。

In TinyOS 1.x, a message buffer is a ``TOS_Msg``. A buffer contains an active message (AM) packet as well as packet metadata, such as timestamps, acknowledgement bits, and signal strength if the packet was received. ``TOS_Msg`` is a fixed size structure whose size is defined by the maximum AM payload length (the default is 29 bytes). Fixed sized buffers allows TinyOS 1.x to have zero-copy semantics: when a component receives a buffer, rather than copy out the contents it can return a pointer to a new buffer for the underlying layer to use for the next received packet.

一个问题出现了:什么定义了“TOS_Msg”结构,不同的链路层可能需要不同的布局。例如:802.15.4射频器可能需要802.15.4.头(好比CC2420,使用在Telos和micaZ平台),字节射频(例如CC1000,使用在mica2平台)需要定义它自己的包格式。这就意味着不同的平台可能有不同的"TOS_Msg"结构。

One issue that arises is what defines the ``TOS_Msg`` structure, as different link layers may require different layouts. For example, 802.15.4 radio hardware (such as the CC2420, used in the Telos and micaZ platforms) may require 802.15.4 headers, while a software stack built on top of byte radios (such as the CC1000, used in the mica2 platform) can specify its own packet format. This means that ``TOS_Msg`` may be different on different platforms.

TinyOS 1.x中的解决办法是只有一个标准的"TOS_Msg"定义,特定平台可以将其重新定义成符合它自己需要的结构,例如一个mica2节点使用如下标准定义: The solution to this problem in TinyOS 1.x is for there to be a standard definition of ``TOS_Msg``, which a platform (e.g., the micaZ) can redefine to match its radio. For example, a mica2 mote uses the standard definition, which is::

 typedef struct TOS_Msg {
   // The following fields are transmitted/received on the radio.
   uint16_t addr;
   uint8_t type;
   uint8_t group;
   uint8_t length;
   int8_t data[TOSH_DATA_LENGTH];
   uint16_t crc;
   // The following fields are not actually transmitted or received
   // on the radio! They are used for internal accounting only.
   // The reason they are in this structure is that the AM interface
   // requires them to be part of the TOS_Msg that is passed to
   // send/receive operations.
   uint16_t strength;
   uint8_t ack;
   uint16_t time;
   uint8_t sendSecurityMode;
   uint8_t receiveSecurityMode;
 } TOS_Msg;

在使用CC2420射频的平台上,“TOS_Msg”定义为: while on a mote with a CC2420 radio (e.g., micaZ), ``TOS_Msg`` is defined as::

 typedef struct TOS_Msg {
   // The following fields are transmitted/received on the radio.
   uint8_t length;
   uint8_t fcfhi;
   uint8_t fcflo;
   uint8_t dsn;
   uint16_t destpan;
   uint16_t addr;
   uint8_t type;
   uint8_t group;
   int8_t data[TOSH_DATA_LENGTH];
   // The following fields are not actually transmitted or received
   // on the radio! They are used for internal accounting only.
   // The reason they are in this structure is that the AM interface
   // requires them to be part of the TOS_Msg that is passed to
   // send/receive operations.
   uint8_t strength;
   uint8_t lqi;
   bool crc;
   uint8_t ack;
   uint16_t time;
  } TOS_Msg;


这个方法存在2个基本问题。第一,暴露所有的链路层结构字段导致组件会直接存取包结构,这会给高层组件和底层机构之间引入依赖(注:高耦合)。例如,很多建立在数据链路层之上的网络层服务会是否收到已发包的应答,因此它们会检查TOS_Msg中的ack字段。如果某个链路层不提供应答机制,它也必须在结构中包含"ack"字段,并总是将其设置为0,这样就浪费了RAM每个buffer中的一个字节。

There are two basic problems with this approach. First, exposing all of the link layer fields leads components to directly access the packet structure. This introduces dependencies between higher level components and the structure layout. For example, many network services built on top of data link layers care whether sent packets are acknowledged. They therefore check the ``ack`` field of ``TOS_Msg``. If a link layer does not provide acknowledgements, it must still include the ``ack`` field and always set it to 0, wasting a byte of RAM per buffer.

第二,这个模型要支持多种数据链路层是困难的。射频芯片的实现会假设它们需要的字段以及在结构中得到了预定义,并会直接存取它们。如果一个平台有2个不同的数据链路层(例如同时拥有CC2420和CC1000射频),那么一个“TOS_Msg”需要为2个结构的头部字段分配正确合适的空间,以便它们能够直接存取这些头部字段。这在C语言中是相当困难的。 Second, this model does not easily support multiple data link layers. Radio chip implementations assume that the fields they require are defined in the structure and directly access them. If a platform has two different link layers (e.g., a CC1000 *and* a CC2420 radio), then a ``TOS_Msg`` needs to allocate the right amount of space for both of their headers while allowing implementations to directly access header fields. This is very difficult to do in C.

数据负荷是更是问题多多。许多组件会访问此字段,因此它离结构的开头必须是定长的。对于不同的链路层,在数据字段之前的头部字段可能有不同的长度,而且对于报文式的射频(注:比如CC2420,相对于CC1000字节流)通常要求报文在内存中是一块连续的区域。总而言之,这些复杂行使得定义“TOS_Msg”的格式相当困难。

The ``data`` payload is especially problematic. Many components refer to this field, so it must be at a fixed offset from the beginning of the structure. Depending on the underlying link layer, the header fields preceding it might have different lengths, and packet-level radios often require packets to be contiguous memory regions. Overall, these complexities make specifying the format of ``TOS_Msg`` very difficult.

TinyOS使用传统的静态大小的包缓存,而其他非动态方法,比如UNIX socket中的发散-映射式I/O(详情请参看man手册中的"recv(2)").TinyOS 2.x 继续采用此方法。

TinyOS has traditionally used statically sized packet buffers, rather than more dynamic approaches, such as scatter-gather I/O in UNIX sockets (see the man page for ``recv(2)`` for details). TinyOS 2.x continues this approach.


message_t

在TinyOS 2.x中,标准的消息缓存是“message_t”。message_t结构在"tos/types/message.h"中定义:

In TinyOS 2.x, the standard message buffer is ``message_t``. The message_t structure is defined in ``tos/types/message.h``::

 typedef nx_struct message_t {
   nx_uint8_t header[sizeof(message_header_t)];
   nx_uint8_t data[TOSH_DATA_LENGTH];
   nx_uint8_t footer[sizeof(message_footer_t)];
   nx_uint8_t metadata[sizeof(message_metadata_t)];
 } message_t;


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

This format keeps data at a fixed offset on a platform, which is important when passing a message buffer between two different link layers. If the data payload were at different offsets for different link layers, then passing a packet between two link layers would require a ``memmove(3)`` operation (essentially, a copy). Unlike in TinyOS 1.x, where TOS_Msg as explicitly an active messaging packet, message_t is a more general data-link buffer. In practice, most data-link layers in TinyOS 2.x provide active messaging, but it is possible for a non-AM stack to share message_t with AM stacks.

message_t的头部,尾部,元数据对于上层协议来说都是不可理解的,不能直接存取这些域。而数据链路层提供从nesC接口,通过它们来实现访问结构内部的字段。第三节将会详细讨论这些。

The header, footer, and metadata formats are all opaque. Source code cannot access fields directly. Instead, data-link layers provide access to fields through nesC interfaces. Section 3 discusses this in greater depth.

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

Every link layer defines its header, footer, and metadata structures. These structures MUST be external structs (``nx_struct``), and all of their fields MUST be external types (``nx_*``), for two reasons. First, external types ensure cross-platform compatibility [1]_. Second, it forces structures to be aligned on byte boundaries, circumventing issues with the alignment of packet buffers and field offsets within them. Metadata fields must be nx_structs for when complete packets are forwarded to the serial port in order to log traffic.

例如,CC1000射频实现在其"CC1000Msg.h"中定义如下message_t结构:

For example, the CC1000 radio implementation defines its structures in ``CC1000Msg.h``::

 typedef nx_struct cc1000_header {
   nx_am_addr_t addr;
   nx_uint8_t length;
   nx_am_group_t group;
   nx_am_id_t type;
 } cc1000_header_t;
 typedef nx_struct cc1000_footer {
   nxle_uint16_t crc;
 } cc1000_footer_t;
 typedef nx_struct cc1000_metadata {
   nx_uint16_t strength;
   nx_uint8_t ack;
   nx_uint16_t time;
   nx_uint8_t sendSecurityMode;
   nx_uint8_t receiveSecurityMode;
 } cc1000_metadata_t;

每个链路层都定义他们自己的结构,而一个平台负责定义"message_header_t","message_footer_t","message_metadata_t"。这是因为一个平台可能有多个不同的链路层,它能决定需要哪些结构。这些结构必须定义在平台相关的文件夹,且名为"platform_message.h"的头文件。例如mica2平台,有2个数据链路层:CC1000射频和TinyOS串行协议栈。串口包没有尾部和元数据字段,mica2的"platform_message.h"如下:

Each link layer defines its structures, but a **platform** is responsible for defining ``message_header_t``, ``message_footer_t``, and ``message_metadata_t``. This is because a platform may have multiple link layers, and so only it can resolve which structures are needed. These definitions MUST be in a file in a platform directory named ``platform_message.h``. The mica2 platform, for example, has two data link layers: the CC1000 radio and the TinyOS serial stack [2]_. The serial packet format does not have a footer or metadata section. The ``platform_message.h`` of the mica2 looks like this::

 typedef union message_header {
   cc1000_header_t cc1k;
   serial_header_t serial;
 } message_header_t;
 typedef union message_footer {
   cc1000_footer_t cc1k;
 } message_footer_t;
 typedef union message_metadata {
   cc1000_metadata cc1k;
 } message_metadata_t;

下面再来看一个更复杂的例子:假如一个名为“megamica”的平台同时有CC1000和CC2420射频,那么它的“platform_message.h”就应该如下:

For a more complex example, consider a fictional platform named 'megamica' that has both a CC1000 and a CC2420 radio. Its ``platform_message.h`` would look like this::

 typedef union mega_mica_header {
   cc1000_header_t cc1k;
   cc2420_header_t cc2420;
   serial_header_t serial;
 } message_header_t;
 typedef union mega_mica_footer {
   cc1000_footer_t cc1k;
   cc2420_footer_t cc2420;
 } message_footer_t;
 typedef union mega_mica_metadata {
   cc1000_metadata_t cc1k;
   cc2420_metadata_t cc2420;
 } message__metadata_t;

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

If a platform has more than one link layer, it SHOULD define each of the message_t fields to be a union of the underlying link layer structures. This ensures that enough space is allocated for all underlying link layers.

The message_t fields

TinyOS 2.x组件将包看做是一个抽象数据类型(ADTs),而非一个C语言结构体,这样做得到了传统的好处。第一,链路层的客户并不依赖于结构中特定的字段名字和位置,这就让其实现能选择包的结构,作出一些优化。

TinyOS 2.x components treat packets as abstract data types (ADTs), rather than C structures, obtaining all of the traditional benefits of this approach. First and foremost, clients of a packet layer do not depend on particular field names or locations, allowing the implementations to choose packet formats and make a variety of optimizations.

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

Components above the basic data-link layer MUST always access packet fields through interfaces. A component that introduces new packet fields SHOULD provide an interface to those that are of interest to other components. These interfaces SHOULD take the form of get/set operations that take and return values, rather than offsts into the structure.

例如,active message有一个名为"AMPacket"的接口,它能提供存取AM字段的命令。在TinyOS 1.x中,组件能直接访问TOS_Msg.addr,在TinyOS 2.x中,组件调用AMPacket.getAddress(msg)。这些接口中最基础的是Packet接口,它能提供存取一个包的数据负荷的命令。TEP 116描述了通用TinyOS包的ADT接口[3]_。

For example, active messages have an interface named ``AMPacket`` which provides access commands to AM fields. In TinyOS 1.x, a component would directly access ``TOS_Msg.addr``; in TinyOS 2.x, a component calls ``AMPacket.getAddress(msg)``. The most basic of these interfaces is Packet, which provides access to a packet payload. TEP 116 describes common TinyOS packet ADT interfaces [3]_.

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

Link layer components MAY access packet fields differently than other components, as they are aware of the actual packet format. They can therefore implement the interfaces that provide access to the fields for other components.


Headers

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

The message_t header field is an array of bytes whose size is the size of a platform's union of data-link headers. Because radio stacks often prefer packets to be stored contiguously, the layout of a packet in memory does not necessarily reflect the layout of its nesC structure.

一个包的头部字段可能会在message_t的某个位置开始,而非一定在message_t的首个字节处。例如Telos平台:

A packet header MAY start somewhere besides the beginning of the message_t. For example, consider the Telos platform::

 typedef union message_header {
   cc2420_header_t cc2420;
   serial_header_t serial;
 } message_header_t;

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

The CC2420 header is 11 bytes long, while the serial header is 5 bytes long. The serial header ends at the beginning of the data payload, and so six padding bytes precede it. This figure shows the layout of message_t, a 12-byte CC2420 packet, and a 12-byte serial packet on the Telos platform::

             11 bytes         TOSH_DATA_LENGTH        7 bytes
           +-----------+-----------------------------+-------+
 message_t |  header   |          data               | meta  |
           +-----------+-----------------------------+-------+
           +-----------+------------+                +-------+
 CC2420    |  header   |   data     |                | meta  |
           +-----------+------------+                +-------+
                 +-----+------------+
 Serial          | hdr |   data     |
                 +-----+------------+

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

Neither the CC2420 nor the serial stack has packet footers, and the serial stack does not have any metadata.

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

The packet for a link layer does not necessarily start at the beginning of the message_t. Instead, it starts at a negative offset from the data field. When a link layer component needs to read or write protocol header fields, it MUST compute the location of the header as a negative offset from the data field. For example, the serial stack header has active message fields, such as the AM type. The command that returns the AM type, ``AMPacket.type()``, looks like this::

 serial_header_t* getHeader(message_t* msg) {
   return (serial_header_t*)(msg->data - sizeof(serial_header_t));
 }
 command am_id_t AMPacket.type(message_t* msg) {
   serial_header_t* hdr = getheader(msg);
   return hdr->type;
 }

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

Because calculating the negative offset is a little bit unwieldy, the serial stack uses the internal helper function getHeader(). Many single-hop stacks follow this approach, as it is very likely that nesC will inline the function, eliminating the possible overhead. In most cases, the C compiler also compiles the call into a simple memory offset load.

下面的代码是不正确的,因为它直接强制转换头部字段。下面距离说明组件无论如何都不能这样做:

The following code is incorrect, as it directly casts the header field. It is an example of what components MUST NOT do::

 serial_header_t* getHeader(message_t* msg) {
   return (serial_header_t*)(msg->header);
 }

在Telos中,这将会导致包中的字段布局看起来像:

In the case of Telos, for example, this would result in a packet

layout that looks like this::

             11 bytes         TOSH_DATA_LENGTH           7 bytes
           +-----------+-----------------------------+-------+
 message_t |  header   |          data               | meta  |
           +-----------+-----------------------------+-------+
           +-----+     +------------+
 Serial    | hdr |     |   data     |
           +-----+     +------------+

Data

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

The data field of message_t stores the single-hop packet payload. It is TOSH_DATA_LENGTH bytes long. The default size is 28 bytes. A TinyOS application can redefine TOSH_DATA_LENGTH at compile time with a command-line option to ncc: ``-DTOSH_DATA_LENGTH=x``. Because this value can be reconfigured, it is possible that two different versions of an application can have different MTU sizes. If a packet layer receives a packet whose payload size is longer than TOSH_DATA_LENGTH, it MUST discard the packet. As headers are right justified to the beginning of the data payload, the data payloads of all link layers on a platform start at the same fixed offset from the beginning of the message buffer.

3.3 Footer

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

The message_footer_t field ensures that message_t has enough space to store the footers for all underlying link layers when there are MTU-sized packets. Like headers, footers are not necessarily stored where the C structs indicate they are: instead, their placement is implementation dependent. A single-hop layer MAY store the footer contiguously with the data region. For short packets, this can mean that the footer will actually be stored in the data field.

Metadata

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

The metadata field of message_t stores data that a single-hop stack uses or collects does not transmit. This mechanism allows packet layers to store per-packet information such as RSSI or timestamps. For example, this is the CC2420 metadata structure::

 typedef nx_struct cc2420_metadata_t {
   nx_uint8_t tx_power;
   nx_uint8_t rssi;
   nx_uint8_t lqi;
   nx_bool crc;
   nx_bool ack;
   nx_uint16_t time;
 } cc2420_metadata_t;

Variable Sized Structures

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

The message_t structure is optimized for packets with fixed-size headers and footers. Variable-sized footers are generally easy to implement. Variable-sized headers are a bit more difficult. There are three general approaches that can be used.

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

If the underlying link hardware is byte-based, the header can just be stored at the beginning of the message_t, giving it a known offset. There may be padding between the header and the data region, but assuming a byte-based send path this merely requires adjusting the index.

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

If the underlying link hardware is packet-based, then the protocol stack can either include metadata (e.g., in the metadata structure) stating where the header begins or it can place the header at a fixed position and use ``memmove(3)`` on reception and transmit. In this latter case, on reception the packet is continguously read into the message_t beginning at the offset of the header structure. Once the packet is completely received, the header can be decoded, its length calculated, and the data region of the packet can be moved to the ``data`` field. On transmission, the opposite occurs: the data region (and footer if need be) are moved to be contiguous with the header. Note that on completion of transmission, they need to be moved back. Alternatively, the radio stack can institute a single copy at the botttom layer.

Implementation 实现

The definition of message_t can be found in ``tinyos-2.x/tos/types/message.h``.

The definition of the CC2420 message format can be found in ``tinyos-2.x/tos/chips/cc2420/CC2420.h``.

The defintion of the CC1000 message format can be found in ``tinyos-2.x/tos/chips/cc1000/CC1000Msg.h``.

The definition of the standard serial stack packet format can be found in ``tinyos-2.x/tos/lib/serial/Serial.h``

The definition of the telos family packet format can be found in ``tinyos-2.x/tos/platform/telosa/platform_message.h`` and the micaz format can be found in ``tinyos-2.x/tos/platforms/micaz/platform_message.h``.

Author's Address

| Philip Levis | 358 Gates Hall | Computer Science Laboratory | Stanford University | Stanford, CA 94305 | phone - +1 650 725 9046 | email - pal@cs.stanford.edu

Citations

.. [1] `nesC: A Programming Language for Deeply Embedded Networks. <http://nescc.>`_

.. [2] TEP 113: Serial Communication.

.. [3] TEP 116: Packet Protocols.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多