Contiki协议栈Rime:缓冲区管理packetbuf management

来源:互联网 发布:php浏览次数 编辑:程序博客网 时间:2024/05/09 01:16

更多的Contiki协议栈知识,请参考索引目录:
《Contiki协议栈:索引目录》

1 概述

  关于Rime的缓冲区管理这一块,能在网上搜到很多博客,但是我想说的是,99%+都是过时的,坑爹啊!Contiki的开发非常活跃,所以对代码的改进很多,而Rime的缓冲区管理这也在今年二月份进行了优化,由之前难以理解的、晦涩的“双头栈”改为了现在通俗易懂的结构。双头栈有多晦涩,你将contiki的代码reset到今年二月份之前去看看就知道了。
  不过不要马虎,虽然现在的缓冲管理变简单了,但是它很重要!很重要!!很重要!!!
  为啥说它很重要呢?

  • 其一,Rime整个协议栈里面的子协议,都得使用它。(至于uIP协议会不会使用它,我还没接触过,不知道呢)
  • 其二,底层协议,比如MAC,RDC,FRAMER,LLSEC都得使用它。
  • 其三,甚至某些应用程序也会用到它。还记得博客《Contiki协议栈Rime:引子》中的匿名广播的例程吗?里面有packetbuf_copyfrom("Hello", 6);这句话,它就使用到了packetbuf。

缓冲区的作用很简单,将发出和收到的数据包(包括数据和包属性)都存储在一个单一的缓冲区packetbuf,由头部和数据两部分组成。
相关代码位于contiki/core/net/packetbuf.[ch]

2 相关变量

packetbuf

packbuf就是Rime的缓冲区,它是一个指向数组的指针,其定义为:

/* 以下声明确保包缓冲区可以对齐32bit边界,在一些平台(最常见的如msp430或OpenRISC),在访问字节时,有可能会出现非对齐包缓冲而导致问题的发生。 */static uint32_t packetbuf_aligned[(PACKETBUF_SIZE + 3) / 4];static uint8_t *packetbuf = (uint8_t *)packetbuf_aligned;

其中,PACKETBUF_SIZE被定义为:

#ifdef PACKETBUF_CONF_SIZE#define PACKETBUF_SIZE PACKETBUF_CONF_SIZE#else#define PACKETBUF_SIZE 128 // 缓冲区默认长度为128个字节#endif

所以默认情况下,Rime的buffer是大小为128字节的连续的内存空间。

需要说明的是,这里的PACKETBUF_SIZE是指整个缓冲的长度,包括头部和数据。在以前的机制中,这个PACKETBUF_SIZE只包括数据部分,头部还另外占据PACKETBUF_HDR_SIZE个字节的缓冲。

buflen hdrlen bufptr

这三个变量的定义如下:

static uint16_t buflen, bufptr;static uint8_t hdrlen;

其中,
buflen:已使用的数据部分的长度
hdrlen:已使用的头部部分的长度
bufptr:这不是指针,而是一个整型变量。它相当于一个索引,指向缓冲的某个地址。这个变量在今后解析buffer时非常有用。

3 相关函数

packetbuf_clear

void packetbuf_clear(void){  buflen = bufptr = 0;  hdrlen = 0;  packetbuf_attr_clear();}

该函数负责清空数据,包括packetbuf指向的buffer,以及两个属性数组里的内容。在将包压入包缓冲之前,会调用该函数。

packetbuf_copyfrom

int packetbuf_copyfrom(const void *from, uint16_t len){  uint16_t l;  packetbuf_clear(); // 先清空属性数组和packetbuf  l = MIN(PACKETBUF_SIZE, len);  // 如果len大于PACKETBUF_SIZE,则截断  memcpy(packetbuf, from, l);  buflen = l;  return l;}

很容易理解:先清空属性数组和packetbuf,然后从from中拷贝len个长度的数据到packetbuf中。如果需要拷贝的长度但对于定义的buffer长度,则进行截断处理,只拷贝PACKETBUF_SIZE个字节。该函数返回所拷贝的数据的长度。

packetbuf_compact

void packetbuf_compact(void){  int16_t i;  if(bufptr) {    /* 将数据部分向左移至头部后面 */    for(i = 0; i < buflen; i++) {      packetbuf[hdrlen + i] = packetbuf[packetbuf_hdrlen() + i];    }    bufptr = 0;  }}

该函数通过拷贝packetbuf的数据部分,使其紧紧跟随头部。头部和数据之间可能有若干个字节是隔开的,但是为啥会隔开呢?看后面的函数packetbuf_hdrreduce()和函数packetbuf_dataptr()就会明白。Rime中的协议在将包发送给设备驱动之前,会调用该函数,以确保包在内存中是连续的。
为了更容易理解,直接上图:

这里写图片描述

packetbuf_copyto

int packetbuf_copyto(void *to){  if(hdrlen + buflen > PACKETBUF_SIZE) { // 怎么会发生这样的情况?    return 0;  }  // 由于数据部分和头部中间可能有间隔,所以分开拷贝这两个部分,  // 否则当中间真的存在间隔时,就会拷贝错误  memcpy(to, packetbuf_hdrptr(), hdrlen);  memcpy((uint8_t *)to + hdrlen, packetbuf_dataptr(), buflen);  return hdrlen + buflen;}

拷贝一个完整的packbuf到一个外部buffer。

packetbuf_hdralloc

intpacketbuf_hdralloc(int size){  int16_t i;  if(size + packetbuf_totlen() > PACKETBUF_SIZE) {    return 0;  }  /* shift data to the right */  for(i = packetbuf_totlen() - 1; i >= 0; i--) {    packetbuf[i + size] = packetbuf[i];  }  hdrlen += size;  return 1;}

分配size个字节的头部空间。我们后面会看到,当需要向packetbuf中写入新的包属性时,会先调用此函数预分配空间。上图:

这里写图片描述

packetbuf_hdrreduce

int packetbuf_hdrreduce(int size){  if(buflen < size) {    return 0;  }  bufptr += size;  buflen -= size;  return 1;}

该函数主要用于解析packetbuf。调用函数packetbuf_dataptr()会返回一个指向数据部分的第一个字节的指针。如果先调用packetbuf_hdrreduce(size1),此时bufptr会增加size个字节,如果再调用函数packetbuf_dataptr(),此时返回的函数指针就比之前的函数指针后移了size个字节。解析packetbuf的流程就是不断地调用packetbuf_hdrreduce()和packetbuf_dataptr()。

packetbuf_set_datalen

voidpacketbuf_set_datalen(uint16_t len){  PRINTF("packetbuf_set_len: len %d\n", len);  buflen = len;}

该函数用于设置数据部分的长度

packetbuf_dataptr

void *packetbuf_dataptr(void){  return packetbuf + packetbuf_hdrlen();}

该函数返回指向数据部分第一个字节的指针。

packetbuf_hdrptr

void *packetbuf_hdrptr(void){  return packetbuf;}

返回头部指针,即这个buf的指针

packetbuf_datalen

uint8_tpacketbuf_hdrlen(void){  return bufptr + hdrlen;}

返回数据部分长度

packetbuf_totlen

uint16_tpacketbuf_totlen(void){  return packetbuf_hdrlen() + packetbuf_datalen();}

返回头部和数据部分总长度

packetbuf_holds_broadcast

intpacketbuf_holds_broadcast(void){  return linkaddr_cmp(&packetbuf_addrs[PACKETBUF_ADDR_RECEIVER - PACKETBUF_ADDR_FIRST].addr, &linkaddr_null);}

通过比较属性数组中的PACKETBUF_ADDR_RECEIVER属性的值与linkaddr_null的值是否相等,判断packetbuf中是否包含广播地址。

关于这个函数的使用,在底层协议里有,暂时先不管,今后再补充

4 小结

在这篇博客中,简单介绍了缓冲区管理的各个函数,通过两张图片,应该更容易理解。但是函数太多了,我不可能相关的函数都画图,太浪费时间了。第一次阅读的话可能会觉得有些函数模棱两可,但是结合后面几篇博客,就能真正知道其具体应用了。

0 0
原创粉丝点击