mbuf是报文中的描素的结构体,是整个转发过程中最核心的数据结构之一。主要针对于mbuf的常用API与基本原理做一个简单的介绍。
内存结构
首先我们看一下rte_mbuf的数据结构的定义:先主要说明几个跟数据有关的变量
struct rte_mbuf {
void *buf_addr; /**< Virtual address of segment buffer. */
uint16_t data_off;
uint32_t pkt_len; /**< Total pkt len: sum of all segments. */
uint16_t data_len; /**< Amount of data in segment buffer. */
uint16_t buf_len
......
}
既然叫mbuf,其实就是一种buf管理的结构体:
mbuf整个用来存数据的buf就是上图所示的内容。一般数据都会有分3个区域;
1, headroom
2, data
3, tailroom
其实这个中间还包含一个含义,整个buf的大小。 也就是数据结构中的buf_len的大小一般是4096
其中headroom一般含义是:
保留区域headroom:一般用来存放用户自己针对于mbuf的一些描素信息,一般保留给用户使用,可以通过修改mbuf头文件,来实现headroom的大小;data_off的默认值就是mbuf的headroom的大小;默认就是128。如果要定义超过这个范围的私有字段,请自行修改 RTE_PKTMBUF_HEADROOM
数据字段:data。
data区域一般指的是地址区间在data_off+buf_addr 到data_off+data_len+buf_add即,data_len就是这段数据的长短,这个data_len一般都是通过mbuf的几个基本操作,或者通过赋值来实现的。
tailroom:一般指的是,data_len还未包含的东西。默认其实data_len是0。所以说默认来说tailroom应该是占了很大的空间的;
其实mbuf的控制,就是不断的控制这个几个区域的大小,永远记住,我们的报文数据永远是存放在data中的;主要控制的就是data_off 与data_len
至于pkt_len在普通情况下,就是和data_len是一个大小,在大报文的时候,就是两个mbuf通过链表组合起来的。本文假定报文的长度不超过rte_mbuf->buf_len的长度
mbuf基本操作以及注意点:
rte_pktmbuf_prepend
移动data_off指针,注意:需要查看返回值,如果已经偏移到headroom的时候,会返回NULL;(报文向前扩容),例如报文从应用层往下,一层一层的封装就用这个。
rte_pktmbuf_append
改变data_len的长度 ,返回改变前的尾地址。(向后扩容)
例如先有首部再填数据字段,就可以用这个
rte_pktmbuf_adj
(首部向后缩小空间) 改变data_off的值 从二层到三层转发,去二层头就可以用这个
rte_pktmbuf_trim
(尾部向前缩小空间) 移动data_len减少buf_len;(预分配的内容太大,数据没那么大可以用这个)
总结:
这4个API就是我们常见的调整数据部分大小,其实用法和API的名字和内核的skbuf类似。
rte_pktmbuf_mtod
rte_pktmbuf_mtod_offset
这两个API就是就是返回buf_addr+data_off +useroff 然后再强制类型转换一下而已~~
只是mtod这个API默认是useroff ==0而已。就是把数据data部分的首指针返回。
注意:
这个API其实是不安全的,往这个地址里面copy内容的时候,注意数据的长度~。。
有用的连接:
http://www.cnblogs.com/ziding/p/4214499.html
http://blog.csdn.net/todd911/article/details/24182551
其实mbuf不仅仅可以用来装报文只要是数据缓存都可以~~
分配操作
Mbuf由缓冲池rte_mempool管理,rte_mempool在初始化时一次申请多个mbuf,申请的mbuf个数和长度都由用户指定。宏MBUF_SIZE是例子程序中使用的mbuf长度:
#define MBUF_SIZE (2048 + sizeof(struct rte_mbuf) + RTE_PKTMBUF_HEADROOM)
用下面函数向rte_mempool申请一个mbuf:
struct rte_mbuf *rte_pktmbuf_alloc(struct rte_mempool *mp);
拷贝操作
宏RTE_MBUF_SCATTER_GATHER定义rte_mbuf是否支持拷贝功能。
dpdk接收报文并把报文上送上层应用的过程中,报文传输是“零拷贝”,即不需要拷贝报文内容,只需要传送mbuf地址。然而在一个报文上送给多个应用时,仍然需要对报文做拷贝并送给不同的应用。Librte_mbuf采用“复制rte_mbuf,共享data数据域”的方式实现报文的拷贝函数rte_pktmbuf_clone(),函数原型如下:
struct rte_mbuf *rte_pktmbuf_clone(struct rte_mbuf *md, struct rte_mempool *mp)
rte_pktmbuf_clone()函数首先申请一个新的rte_mbuf,我们称这个mbuf为indirect buffer,用mi表示,参数md称为direct buffer。函数将md的各结构体成员(引用计数refcnt除外)一一复制给mi,同时将md的引用计数refcnt增1。此时,mi->pkt.data指向md的data数据域。
Rte_pktmbuf_clone()要求参数md必须是direct buffer,我们可以通过判断md->buf_addr – sizeof(struct rte_mbuf) == md 是否为真,确定md是否为direct buffer,该功能由宏RTE_MBUF_DIRECT(mb)实现。
注意:rte_pktmbuf_clone()提供的拷贝机制在某些场景不一定适用,如多个应用竞争data数据域。为避免竞争的发生,使用者可以通过拷贝data数据域实现自己的clone()。具体实现参考openvswitch-dpdk-1.1。
释放操作
用下面函数释放一个mbuf,释放过程即把mbuf归还到rte_mempool中:
void rte_pktmbuf_free(struct rte_mbuf *m);
根据m的引用计数和m的indirect/direct类型,rte_pktmbuf_free()分以下方式释放m:
如果m的引用计数大于1,则只将m的引用计数减1,函数返回;
如果m的引用计数是1且m是direct类型,则将m的引用计数置0,然后把m归还mempool,函数返回;
如果m的引用计数是1且m是indirect类型,则rte_pktmbuf_free()将m引用计数置0,同时将m对应的direct buffer的引用计数减1(减1后引用计数为0则把direct buffer归还mempool),把m归还mempool,函数返回;
rte_pktmbuf_free()通过宏RTE_MBUF_FROM_BADDR(m->buf_addr)找到m对应的direct buffer,宏实现如下:
#define RTE_MBUF_FROM_BADDR(ba) (((struct rte_mbuf *)(ba)) - 1)
rte_pktmbuf_free()通过判断m != RTE_MBUF_FROM_BADDR(m->buf_addr)是否为真判断m的indirect/direct类型。
解封装操作
rte_mbuf的结构与linux内核协议栈的skb_buf相似,在保存报文的内存块前后分别保留headroom和tailroom,以方便应用解封报文。Headroom默认128字节,可以通过宏RTE_PKTMBUF_HEADROOM调整。
我们可以通过m->pkt.data – m->buf_addr计算出headroom长度,通过m->buf_len – m->pkt.data_len – headroom_size计算出tailroom长度。这些计算过程都由以下函数实现:
uint16_t rte_pktmbuf_headroom(const struct rte_mbuf *m)
uint16_t rte_pktmbuf_tailroom(const struct rte_mbuf *m)
假设m->pkt.data指向报文的二层首地址,我们可以通过以下一系列操作剥去报文的二层头部:
m->pkt.data += 14;
m->pkt.data_len -= 14;
m->pkt.pkt_len -= 14;
这些操作已经由rte_pktmbuf_adj()实现,函数原型如下:
char *rte_pktmbuf_adj(struct rte_mbuf *m, uint16_t len)
我们可以通过以下一系列操作为IP报文封装二层头部:
m->pkt.data -= 14;
m->pkt.data_len += 14;
m->pkt.pkt_len += 14;
这些操作由rte_pktmbuf_prepend()实现,函数原型如下:
char *rte_pktmbuf_prepend(struct rte_mbuf *m, uint16_t len)
如果需要在tailroom 中加入N个字节数据,我们可以通过以下操作完成:
tail = m->pkt.data + m->pkt.data_len; // tail记录tailroom首地址
m->pkt.data_len += N;
m->pkt.pkt_len += N;
这些操作由rte_pktmbuf_append()实现,函数原型如下:
char *rte_pktmbuf_append(struct rte_mbuf *m, uint16_t len)
librte_mbuf还提供了rte_pktmbuf_trim()函数,用来移除mbuf中data数据域的最后N个字节,函数实现如下:
m->pkt.data_len -= N;
m->pkt.pkt_len -= N;
函数原型如下:
int rte_pktmbuf_trim(struct rte_mbuf *m, uint16_t len)