本文主要是介绍Contiki协议栈Rime:缓冲区管理packetbuf management,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
更多的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(); // 先清空属性数组和packetbufl = 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
int
packetbuf_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
void
packetbuf_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_t
packetbuf_hdrlen(void)
{return bufptr + hdrlen;
}
返回数据部分长度
packetbuf_totlen
uint16_t
packetbuf_totlen(void)
{return packetbuf_hdrlen() + packetbuf_datalen();
}
返回头部和数据部分总长度
packetbuf_holds_broadcast
int
packetbuf_holds_broadcast(void)
{return linkaddr_cmp(&packetbuf_addrs[PACKETBUF_ADDR_RECEIVER - PACKETBUF_ADDR_FIRST].addr, &linkaddr_null);
}
通过比较属性数组中的PACKETBUF_ADDR_RECEIVER属性的值与linkaddr_null的值是否相等,判断packetbuf中是否包含广播地址。
关于这个函数的使用,在底层协议里有,暂时先不管,今后再补充
4 小结
在这篇博客中,简单介绍了缓冲区管理的各个函数,通过两张图片,应该更容易理解。但是函数太多了,我不可能相关的函数都画图,太浪费时间了。第一次阅读的话可能会觉得有些函数模棱两可,但是结合后面几篇博客,就能真正知道其具体应用了。
这篇关于Contiki协议栈Rime:缓冲区管理packetbuf management的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!