程序文件packetbuf.c和头文件packetbuf.h中定义了数据分组缓存的实现和操作方法,这两个文件保存在core/net/文件夹下。
1.数据分组属性
程序文件packetbuf.c中定义了两个结构体数组如下:
struct packetbuf_attr packetbuf_attrs[PACKETBUF_NUM_ATTRS];
struct packetbuf_addr packetbuf_addrs[PACKETBUF_NUM_ADDRS];
结构体数组packetbuf_attrs[]用于存储保存在分组缓冲区的数据分组的属性,而packetbuf_addrs[]用于存储与保存在分组缓存区中数据分组相关的地址信息,例如发送数据分组的数据源的源地址和希望接收数据分组的目的主机的目的地址,这里的源和目的地址是RIME 地址,RIME 地址是用于Contiki 系统RIME 栈中的地址格式。文件packetbuf.h中定义了结构体类型packetbuf_attr和packetbuf_addr,定义如下:


从前面的数组定义中可以看出, 数据分组packetbuf_attrs[] 和packetbuf_addrs[] 的 大 小 分 别 是 PACKETBUF_NUM_ATTRS 和PACKETBUF_NUM_ADDRS,程序文件packetbuf.h中定义了这两个宏常量,定义如下,其中PACKETBUF_ATTR_MAX 的值为28:

采用以上定义的原因是,把与数据分组相关的RIME地址也当成数据分组的属性。
现在,必须调用函数packetbuf_set_attr()设置数据分组缓存区的属性,也就是需要提供属性类型“type”和值“val”。

上面函数中可使用的“类型”属性定义如下:


因此,必须从前面的24 项中选择属性,例如函数packetbuf_set_attr(PACKETBUF_ATTR_RSSI,8)设置数据分组的RSSI 属性为8,类似地,为了得到确定的数据分组的属性值,必须使用下面的函数:


例如packetbuf_attr(PACKETBUF_ATTR_RSSI)函数返回与数据分组相关的RSSI值。
为了设置数据分组的地址,必须使用packetbuf_set_addr(type,*addr)函数,程序文件packetbuf.c中该函数的定义如下:

这里的类型参数type 可能是PACKETBUF_ADDR_SENDER、PACKET⁃BUF_ADDR_RECEIVER、PACKETBUF_ADDR_ESENDER 或PACKETBUF_AD⁃DR_ERECEIVER,它们的取值是任意介于24~27 之间的值,PACKET⁃BUF_ADDR_FIRST 的值是24,其中PACKETBUF_ADDR_ESENDER 或PACK⁃ETBUF_ADDR_ERECEIVER 用于多跳路由,由这四种类型的数组存储的数据如下:


图8-10 属性设置
例如,如图8-10 所示,节点4 经过中间节点3 和节点2 向节点1 发送数据分组,当节点3 将从节点4 接收到的数据分组转发给中间节点2 时,上面提到的四个地址的值如下:

类似地,为了获取地址属性值,必须调用如下函数:

packetbuf.c 程序文件中还定义了两个用于拷贝整个属性数组的函数,它们的定义如下:

2.重要变量
下面讨论实际实现数据分组缓存区的结构,也将讨论用于修改和访问数据分组缓存区中实际数据的一些重要变量和指针。Contiki 操作系统中使用如下声明的数组结构实现数据分组缓冲区:

![]()
上面的声明确保数据分组缓存区在偶数16 位边界上对齐,但在某些平台上,如著名的msp430平台,未对齐的数据分组缓冲区可能会导致访问16位值时出现问题。第一条语句中的PACKETBUF_SIZE 是包括分组首部的数据分组的最大长度,而PACKETBUF_HDR_SIZE 是数据分组首部的长度。第二条语句中的指针packetbuf 指向存储数据分组数据部分的数组。需要注意的是,第二条语句需要实现从uint16_t类型数组结构到uint8_t类型数组结构的转换,使用uint8_t 类型格式数组的原因是它可以存储8 位的字符,这样,数组的每个元素可以用于存储一个字符。第一条语句定义的缓存区大小为(PACKET⁃BUF_SIZE+PACKETBUF_HDR_SIZE )/2+1,缓存区的大小为该值的原因是收到的整个数据分组将保存在缓存区的数据部分,因此,缓存区的数据部分应该有足够的空间来容纳整个数据分组,整个分组的大小可能等于PACKET⁃BUF_SIZE 个字节或字符。默认的PACKETBUF_SIZE 的值是128,PACKET⁃BUF_HDR_SIZE的值是48,它们的定义在Contiki系统程序文件packetbuf.h中。

程序文件packetbuf.c 中声明的指针packetbufptr 用于指向存储分组的数据/有效载荷部分。数据部分可能在数据分组缓存区中,也可能存储在某个外部位置。在这两种情况下,packetbufptr 指针指向数据分组的有效载荷/数据部分(可能是数据分组缓存区packetbuf的一部分)或某个外部存储器上的地址。变量hdrptr 存储数据分组缓存packetbuf 中数组的索引值,即存储分组首部的第一个字符的位置。对于即将输出的数据分组,数据分组首部存储在从变量hdrptr 记录的位置到PACKETBUF_HDR_SIZE-1 的几个位置上,如图8-11 所示。要记住的是,数组的第一个位置的编号是0,因此,数据分组首部存储在从packetbuf[hdrptr]开始到packetbuf[PACKETBUF_HDR_SIZE-1]的位置上,例如在图8-11 中,假设PACKETBUF_HDR_SIZE=9,hdrptr=5,那么packetbuf[5:8]存储了数据分组的首部。

图8-11 变量hdrpt举例
上面定义的变量buflen 存储数据分组有效载荷/数据部分的长度,该数据分组保存在数据分组缓存区packetbuf 中的数据部分。对于即将输出的数据分组,它是数据分组有效负载或数据部分的长度,而对于接收到的数据分组,因为整个数据分组将被存储在缓存区的数据部分,所以变量buflen 的值是整个数据分组的长度。变量bufptr 用于定位数据分组缓存数组packetbuf 中数据分组数据部分/有效载荷第一个字符的位置,变量bufptr 中存储的值是一个偏移值,当变量bufptr 值与PACKETBUF_HDR_SIZE 相加时,得到的结果是存储数据分组有效载荷/数据部分的第一个字符的数组索引。如图8-12所示,假设bufptr=4,PACKETBUF_HDR_SIZE 的值是9,那么数据分组数据部分/有效载荷部分的第一个字符存储在packetbuf数组的第13个元素位置上。

图8-12 所示变量bufptr举例
3.重要函数
程序文件packetbuf.c定义了对数据分组缓存操作的函数。
(1)void packetbuf_clear(void)
函数packetbuf_clear()清除数据分组缓存区,也就是重置所有内部状态指针(首部大小、首部指针和外部数据指针)。准备在数据分组缓存数组packetbuf 中存储一个数据分组或复制一个数据分组到数组packetbuf 之前使用该函数。然而该函数并不会删除以前的数据,它只是重置前一节讨论的指针和变量,该函数的源代码如下:

上面的第一行代码重置指向数据分组有效载荷部分的变量,设置变量buflen 的值为0,意味着缓存区不包含数据分组有效负载/数据部分,设置bufptr 的值为0,意味着新的数据分组的数据部分必须从数据分组缓存区的数据部分开始存储。上面代码的第二行和第三行重置首部指针到数据分组缓存区的首部部分的末尾,这意味着没有首部部分,它也重置packetbufptr 指针到数据缓存分组的首部部分后第一个元素位置,也就是有效载荷部分的开始位置。要注意的是,该函数不会删除内存中的内容,它仅仅重置指针,代码中使用该函数时应该考虑这个问题。
packetbuf_clear()函数第四行调用的函数packetbuf_attr_clear()会清除所有属性,包括数据分组缓存区中的地址字段等,该函数的代码如下:

(2)void packetbuf_clear_hdr(void)
函数packetbuf_clear_hdr()仅仅重置数据分组缓存区的首部字段,该函数重置所有与数据分组首部有关的内部状态指针(首部大小、首部指针,但除外部数据指针之外),在数据分组缓存中发送一个数据分组之后,使用该函数的目的是为了在以后的数据重传能够重用分组缓存区。
hdrptr=PACKETBUF_HDR_SIZE;
类似于数据分组缓存清除操作,上面行仅仅重置分组缓存首部指针指向分组缓存区的末尾,这意味着没有首部,它没有删除内存内容,编程时需考虑到。图8-13是PACKETBUF_HDR_SIZE=10的例子。

图8-13 packetbuf_clear_hdr()函数例子
(3)void packetbuf_hdr_remove(int size)
当处理即将输出的数据分组时,函数packetbuf_hdr_remove()用于删除数据分组缓存区中首部的第一部分,它删除整个数据分组缓存区整个首部的“size”大小,该函数除了重置hdrptr指针指向新的位置的指针操作外没有其他操作。下面代码的功能与该函数的功能完全相同:
hdrptr+=size;
它将首部指针向前移动“size”个间隔。要注意的是,该操作仅适用于即将输出的数据分组。下面是当size=4,hdrptr=1 和PACKETBUF_HDR_SIZE=13时该函数操作的例子:


图8-14 packetbuf_hdr_remove()函数操作
(4)void packetbuf_hdrreduce(int size)
当处理收到的数据分组时,函数packetbuf_hdrreduce()用于删除数据分组缓存区中首部的第一个部分,如果该函数无法删除请求的首部空间的大小size,它返回0而不做任何其他操作,该函数的代码如下:

由于接收到的分组将被存储在数据分组缓存区的数据部分,bufptr实际上是得到分组的第一个字节位置的偏移值,实际也是数据分组首部的第一个字节。因此,为了删除数据分组首部的前面size 个字节,仅仅需要增加bufptr值,例如偏移值,所以需执行的语句如下:
bufptr+=size;
由于数据分组的大小比实际的小,而且接收到的分组的buflen 是分组的总长度,所以函数中buflen减去了size的值,即执行了如下语句:
buflen-=size;
要注意的是,该函数仅用于对接收到的分组的操作,下面是假设PACKETBUF_HDR_SIZE=6,size=2的函数操作实例:

图8-15 packetbuf_hdrreduce()函数举例
(5)void packetbuf_set_datalen(uint16_t len)(https://www.xing528.com)
对于将要发送的出站数据分组,数据分组缓存packetbuf 由两部分构成:首部和数据。packetbuf_set_datalen()函数用于设置backetbuf中数据的长度,需要做的唯一的事情是设置“buflen”的值为“len”,因为“buflen”的值用于指向数据分组的数据部分,数据部分位于数组索引从PACKETBUF_HDR_SIZE+bufptr到PACKETBUF_HDR_SIZE+bufptr+buflen-1的项中。
buflen=len;
下面是假设len=8和bufptr=0的出站数据分组的例子:

图8-16 packetbuf_set_datalen()函数举例
(6)uint16_t packetbuf_datalen(void)
函数packetbuf_datalen()返回存储在数据分组缓存区数据部分中的数据的长度/数量。对于出站数据分组,数据分组缓存区packetbuf由两部分组成:首部和数据。函数packetbuf_datalen()用于获得packetbuf 中数据的长度。对于收到的入站数据分组,数据分组首部和数据存储在packetbuf 的数据部分,函数packetbuf_datalen()获得分组的首部和数据部分总长度。因为长度值保存在变量buflen 中,函数packetbuf_datalen()的唯一操作是返回buflen 的值,这强调了变量buflen 的重要性,如果该变量设置不合理,所有对当前数据分组的操作将会出现错误。函数的Contiki系统代码如下:
return buflen;
(7)uint8_t packetbuf_hdrlen(void)
函数packetbuf_hdrlen()返回数据分组缓存packetbuf 中首部的长度,数据分组首部保存在数据分组缓存packetbuf中,并且可通过packetbuf_hdrptr()函数访问。该函数仅适用于即将发送的出站数据分组,因为收到的入站数据分组保存在数据分组缓存的数据部分。因此,对收到的数据分组,该函数返回值是0。
因为数据分组首部位于数据分组数组从hfrptr (包括该位置) 到PACKERBUF_HDR_SIZE-1(包括该位置)的位置,数据分组首部的大小不过是这两个位置之间位置的个数,所以该值与PACKETBUF_HDR_SIZE-hdrptr相同。假设PACKERBUF_HDR_SIZE=8,hdrptr=3,那么数据分组首部存储在3、 4、 5、 6、 7 的位置上, 例如数据分组首部的长度size=5=8-3=PACKETBUF_HDR_ZISE-hdrptr,所以该函数的代码如下:
return PACKETBUF_HDR_ZISE-hdrptr
(8)uint16_t packetbuf_totlen(void)
函数packetbuf_totlen()用于获得数据分组缓存区packetbuf 中首部和数据部分的长度,如前所述,对于即将发送的出站数据分组,函数packetbuf_hdrlen()返回数据分组首部长度,函数packetbuf_datalen()返回数据分组数据部分的长度。对于收到的入站数据分组,函数packetbuf_hdrlen()返回0,函数packetbuf_dataken()返回数据分组的总长度。该函数的代码如下:
ruturn packetbuf_hdrlen()+packetbuf_datalen();
(9)void*packetbuf_dataptr(void)
函数packetbuf_dataptr()用于获得指向数据分组缓存packetbuf 中数据的指针,该数据要么保存在数据分组缓存packetbuf 中,要么引用一个外部位置。对于即将发送的出站数据分组,数据分组缓存packetbuf 由两部分组成:首部和数据。首部可使用packetbuf_hdrptr()函数访问,因此,出站数据分组函数packetbuf_dataptr(),用于获得指向数据分组数据部分的指针。收到的入站数据分组,数据分组首部和数据分组数据部分存储在数据分组缓存packetbuf 的数据部分。因此,对于收到的入站数据分组,该函数用于获得指向入站数据分组首部部分的指针。该函数的代码如下:
return(void*)(&packetbuf[bufptr+PACKETBUF_HDR_SIZE])
在这种情况下,bufptr的值是从缓存的数据部分开始到包含数据分组的第一个字符的位置偏移值。对于即将发送的出站数据分组,如果bufptr=0,数据分组的数据部分/有效载荷存储在从缓存区数据部分开始的位置,例如缓存的检索号index=PACKETBUF_HDR_SIZE。然而,对于收到的数据分组,如果bufptr=0, 数据分组缓存数据部分第一个字符的检索号index=(PACKETBUF_HDR_SIZE+数据分组首部的大小),因为数据分组数据首部和数据部分都保存在数据分组缓存packetbuf 的数据部分。图8-17 是该函数的实例。

图8-17 函数packetbuf_dataptr()举例
(10)void*packetbuf_hdrptr(void)
对于即将发送的出站数据分组,数据分组缓存packetbuf 由两部分组成:首部部分和数据部分。函数packetbuf_hdrptr( )用于获得指向数据分组缓存packetbuf中首部部分的指针,数据分组首部存储在packetbuf中,hdrptr提供了在数据分组缓存packetbuf 数组中第一个字符位置的索引,因此该函数仅仅返回由hdrptr 指向的packetbuf 数组的地址。对于入站数据分组,hdrptr=PACK⁃ETBUF_HDR_SIZE。该函数仅适用于即将发送的出站数据分组,该函数的代码如下:
return(void*)(&packetbuf[hdrptr])
(11)void packetbuf_reference(void*ptr,uint16_t len)
函数packetbuf_reference( )用于使数据分组缓存packetbuf 指向外部数据,该函数也指定了数据分组缓存packetbuf引用的外部数据的长度,参数ptr指向外部数据,参数len 是外部数据的长度,因为数据分组缓存不再存储数据部分,该数据分组缓存区应该被清除,因此,该函数的代码如下所示:

(12)int packetbuf_is_reference(void)
对于即将发送的出站数据分组,packetbuf 由两部分组成:首部部分和数据部分。函数packetbuf_is_reference()用于检查packetbuf 是否指向先前已使用packetbuf_reference()引用的外部数据。该函数代码如下:
return packetbufptr!=&packetbuf[PACKETBUF_HDR_SIZE];
该函数检查packetbufptr 是否指向数据分组缓存packetbuf 的数据部分,如果函数返回0,说明外部数据之前没有被引用;否则返回1 或TRUE。因此,函数packetbuf_is_reference()是一个布尔函数。
(13)void*packetbuf_reference_ptr(void)
对于即将发送的出站数据分组,数据分组缓存packetbuf 由两部分组成:首部部分和数据部分。该数据可能指向之前用packetbuf_reference()引用的外部数据。函数packetbuf_reference_ptr()用于获得指向外部数据的指针。如果没有外部数据,它将返回指向数据分组缓存packetbuf中数据部分开始位置的指针,该函数的代码如下:
return packetbufptr
(14)int packetbuf_copyfrom(const void*from,uint16_t len)
函数packetbuf_copyfrom()复制from所指向内存位置的数据到数据分组缓存packetbuf 中,如果要复制的数据比packetbuf 大,则仅复制与packetbuf 大小相同的数据到packetbuf 中,函数返回可以复制到packetbuf 中的字节数,该函数的代码如下:

(15)void packetbuf_compact(void)
函数packetbuf_compact()通过复制数据分组缓存packetbuf 中的数据部分压缩packetbuf,以使得数据部分和首部部分连续,它也复制之前使用packetbuf_reference()函数引用过的外部数据到数据分组缓存packetbuf中。设备驱动程序发送数据分组之前,由Rime 协议的代码调用该函数,这种方法确保存储在内存中的整个数据分组连续。另外,要注意的是,该代码假设长度和其他变量的设置也正确,该函数的代码如下:


上面代码中,第一步检查数据是否是外部数据,如果是,仅仅可以复制数据到数据分组缓存区中,下面的代码精确地完成了该功能。正如之前所讨论的,packetbuf_is_reference()函数是一个boolean 函数,用于检查是否数据分组的数据部分指向外部数据,函数packetbuf_reference_ptr()返回指向外部数据的指针,函数packetbuf_datalen()返回复制的字节数,例如数据部分的长度。

图8-18是该函数的使用举例:

图8-18 函数packetbuf_compact()举例
如果数据不是外部数据,而且数据是非压缩存储的,该函数的功能是使其变得紧凑。如果bufptr>0,那么数据部分的实际开始位置和数据部分的假定的开始位置(例如id=PACKETBUF_HDR_SIZE 的位置)之间存在偏移值,该函数把数据移动到缓存区中假定的位置,完成该操作的代码如下:
len = packetbuf_datalen() + PACKETBUF_HDR_SIZE;//给出分组的最后字节假定的位置

bufptr=0;//数据被移动到缓存的数据部分的开始位置,因此偏移值为零图8-19是上面操作的举例:

图8-19 数据移动举例
(16)int packetbuf_copyto_hdr(uint8_t*to)
函数packetbuf_copyto_hdr()复制数据分组缓存packetbuf中数据分组首部到外部缓存区中,从packetbuf 中复制到外部缓存区的数据必须至少包含PACKETBUF_HDR_SIZE 个字节,该函数会返回复制到外部缓存区的字节数,函数的代码如下:


在上面代码中,主要的代码行是下面的函数调用:
memcpy(to,packetbuf+hdrptr,PACKETBUF_HDR_SIZE-hdrptr);
这里的to是目的地址,packetbuf+hdrptr 是缓存区的入口地址,其存储了数据分组首部的第一个字节。PACKETBUF_HDR_SIZE-hdrptr 是数据分组首部的实际大小。要注意的是,该函数应该仅被用于即将发送的出站数据分组,因为收到的数据分组首部被存储在数据分组缓存区的数据部分,这种方法对收到的数据分组没有用处(PACKETBUF_HDR_SIZE-hdrptr=0)。
(17)int packetbuf_copyto(void*to)
函数packetbuf_copyto()复制数据分组缓存packetbuf 中的数据到外部缓存区中,packetbuf 中数据分组的数据部分和首部部分均被复制。如果packetbuf引用了(用函数packetbuf_reference())外部数据,外部数据就会被复制。
从packetbuf 中复制到外部缓存区的数据必须至少包含PACKETBUF_HDR_SIZE个字节,该函数会返回复制到外部缓冲区的字节数,函数代码如下所示:


该函数首先复制外部缓冲区的首部部分,然后再复制数据部分。下面的代码行从数据分组缓存区packetbuf 的首部复制数据,目的地址是to,源地址是packetbuf[hdrptr]中的地址,即packetbuf+hdrptr。PACKETBUF_HDR_SIZEhdrptr是首部的实际大小,例如仅仅为首部复制的字节数。
memcpy(to,packetbuf+hdrptr,PACKETBUF_HDR_SIZE-hdrptr)
下面代码行从数据分组缓存区packetbuf 的数据部分/有效载荷部分复制数据,目的地址是(uint8_t*)to+PACKETBUF_HDR_SIZE-hdrptr,原因是首部数据的PACKETBUF_HDR_SIZE-hdrptr 个字节已经被复制,该值给出了下一个可用的内存位置。源地址是packetbufptr+bufptr,packetbufptr 指向数据分组数据部分的开始位置,如果数据是外部数据,packetbufptr 指向外部源,否则packetbufptr 指向packetbuf[PACKETBUF_HDR_SIZE],buflen 是数据的实际大小,例如从数据分组的数据部分要被复制的字节数。

(18)int packetbuf_hdralloc(int size)
当准备传输数据分组时,函被packetbuf_hdralloc()用于在数据分组缓存packetbuf 的首部部分分配充足的空间。如果该函数不能分配足够的数据分组首部空间,函数将返回0,并且不分配任何空间,函数代码如下:

如果hdrptr>=size,那么存在这种可能性。但如果增加额外的空间后数据分组的总长度大于PACKETBUF_SIZE,那么可以分配更多的空间。如果hdrptr<=size 或分组的总长度+size>PACKETBUF_SIZE,那么分配size 个额外字节或者空间是不可能的。
hdrptr-=size;
上面的代码增加了数据分组首部的大小,因为首部的末尾位置是固定PACKETBUF_HDR_SIZE-1 的,分配更多空间的唯一方式是移动开始位置的指针。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。
