DPDK系列之二十四内存分配mbuf

一、网络传输中的分包

有过网络编程经验的程序员都知道,一般在上层应用设计时,尽量保持数据包体的大小和MTU(最小传输单元)保持一致,这样就可以保证一包数据的传输中的完整性。减少IP层出现问题导致传输层的重传机制启动(主要是指UDP,TCP自己已经处理)。一般来说MTU在传输过程中饰面还会加上开头结尾校验等单元,这样,使用抓包工具时的范围在601514(MTU实际是461500)。那小于46个字节呢?老规矩,自动补齐呗。
DPDK做一种直接和IO打交道的框架,自然有收发两种情况,也就是说既有从上层传入的完整的数据流需要DPDK框架来拆解成分片大小(MTU可允许)又要可以将分片数据重组为完整的数据流。正反两个方向都是流畅清晰的,这是做为网络编程框架的一个基本要求。在这个分片和组装的过程中,就会用前面学习过的零拷贝技术。

二、mbuf

再重复看一下mbuf的数据结构(全部数据定义请查看源码):

/**
 * The generic rte_mbuf, containing a packet mbuf.
 */
struct rte_mbuf {
	MARKER cacheline0;

	void * buf_addr;           /**< Virtual address of segment buffer. */
	/**
	 * Physical address of segment buffer.
	 * Force alignment to 8-bytes, so as to ensure we have the exact
	 * same mbuf cacheline0 layout for 32-bit and 64-bit. This makes
	 * working on vector drivers easier.
	 */
	RTE_STD_C11
	union {
		rte_iova_t buf_iova;
		rte_iova_t buf_physaddr; /**< deprecated */
	} __rte_aligned(sizeof(rte_iova_t));

	/* next 8 bytes are initialised on RX descriptor rearm * /
	MARKER64 rearm_data;
	uint16_t data_off;

......
/* second cache line - fields only used in slow path or on TX */
MARKER cacheline1 __rte_cache_min_aligned;

	/** Size of the application private data. In case of an indirect
	 * mbuf, it stores the direct mbuf private data size.
	 */
	uint16_t priv_size;

	/** Timesync flags for use with IEEE1588. */
	uint16_t timesync;

	/** Sequence number. See also rte_reorder_insert(). */
	uint32_t seqn;

	/** Shared data for external buffer attached to mbuf. See
	 * rte_pktmbuf_attach_extbuf().
	 */
	struct rte_mbuf_ext_shared_info * shinfo;

	uint64_t dynfield1[2]; /**< Reserved for dynamic fields. * /
}

在这个数据结构体里有两个奇怪的标记MARKER cacheline1,和MARKER cacheline0,看这个MARKER定义,这类似于汇编语言中的标签。MARKER被定义为(void**),需要注意的是MARKER[0],是一个可扩展数组,是GCC支持的,但在VS编译器中会报错,可以使用1长度数组来代替。可以理解成一个数据结构里面有两个指针分别指向不同功能的域。
mbuf虽然是传输网络包的,但是理论上缓冲存储何种数据都是可以的。其实主要看利用效率,所以把它分为两个域也是可以理解的。经常使用的放在第一个域内,扩展的数据放在第二个域内。mbuf可以使用next指针指向下一个相同的数据结构形成链表。而mbuf本身也有两种方式来存储数据,一种是直接的,即元数据和数据在一个mbuf内,而另外一种是元数据和数据分别使用各自的缓冲区。两者的各自优势也很明显,前者类似于固定的内存池,操作容易但可能因为数据包大小不同导致有浪费;而后者则可能导致操作复杂,效率低。DPDK为了效率,肯定是选用第一种了。不过,在巨型帧中,元信息只在第一帧中体现,其后帧该部分为空。
需要说明的是在mbuf内部的头部和实际的数据包之间是有一段控制信息的,也就是headroom,用来存储一些交互信息,其起始的地址为buf_addr指针,可以用RTE_PKTMBUF_HEADROOM来调整大小。而数据帧的地址可以调用rte_pktmbuf_mtod宏来得到,看一下这个宏的定义就明白了,其实就是地址的偏移处理。

#define rte_pktmbuf_mtod_offset(m, t, o)	\
	((t)(void * )((char * )(m)->buf_addr + (m)->data_off + (o)))

另外其还还有一个尾部的tailroom,它们的定义有内核中的协议栈skb_buf有些类似。

相关的接口都是rte_mbuf.h中,包括创建、匹配、释放等:

static inline void
rte_mbuf_prefetch_part1(struct rte_mbuf *m)
{
	rte_prefetch0(&m->cacheline0);
}

static inline void
rte_mbuf_prefetch_part2(struct rte_mbuf *m)
{
#if RTE_CACHE_LINE_SIZE == 64
	rte_prefetch0(&m->cacheline1);
#else
	RTE_SET_USED(m);
#endif
}
__rte_experimental
void rte_pktmbuf_free_bulk(struct rte_mbuf **mbufs, unsigned int count);


struct rte_mbuf *
rte_pktmbuf_clone(struct rte_mbuf *md, struct rte_mempool *mp);
static inline uint16_t rte_pktmbuf_headroom(const struct rte_mbuf *m)
{
	__rte_mbuf_sanity_check(m, 0);
	return m->data_off;
}

static inline uint16_t rte_pktmbuf_tailroom(const struct rte_mbuf *m)
{
	__rte_mbuf_sanity_check(m, 0);
	return (uint16_t)(m->buf_len - rte_pktmbuf_headroom(m) -
			  m->data_len);
}
static inline char *rte_pktmbuf_append(struct rte_mbuf *m, uint16_t len)
{
	void * tail;
	struct rte_mbuf * m_last;

	__rte_mbuf_sanity_check(m, 1);

	m_last = rte_pktmbuf_lastseg(m);
	if (unlikely(len > rte_pktmbuf_tailroom(m_last)))
		return NULL;

	tail = (char * )m_last->buf_addr + m_last->data_off + m_last->data_len;
	m_last->data_len = (uint16_t)(m_last->data_len + len);
	m->pkt_len  = (m->pkt_len + len);
	return (char* ) tail;
}
static inline int rte_pktmbuf_trim(struct rte_mbuf *m, uint16_t len)
{
	struct rte_mbuf * m_last;

	__rte_mbuf_sanity_check(m, 1);

	m_last = rte_pktmbuf_lastseg(m);
	if (unlikely(len > m_last->data_len))
		return -1;

	m_last->data_len = (uint16_t)(m_last->data_len - len);
	m->pkt_len  = (m->pkt_len - len);
	return 0;
}

上面只是举了几个典型的接口,更多的可以去查看相关源码。

三、内存池

在DPDK中,网络数据会被存储在一个环形(ring)缓冲区内,同时,在mbuf的环形缓冲区内创建一个mbuf对象。因为是内存池,所以这都不需要申请新的内存,意思就是速度非常快。二者之间通过通道或者RANK进行对齐(其实就是补0,有C++开发经验的都知道对齐)。
上面的内存池是在DPDK中已经创建好了,不过这里面还有一个问题,当多核CPU同时访问一个缓冲区时,仍然有竞争的问题,虽然使用CAS减少了锁的压力,但仍然导致效率会降低。这时,就需要通过DPDK对每个核心进行缓存,这样通过减少访问内存池的次数来降低竞争。
mbuf使用mempool来进行管理,在上面的数据结构定义中,可以看到一个指针向了mempool。当然,在这里面为了提高效率也大量使用了汇编汇合编程如:

static inline void rte_prefetch1(const volatile void *p)
{
	asm volatile ("dcbt 0,%[p],0" : : [p] "r" (p));
}

更详细的内存池会在相关部分详细分析,此处只是带过。

四、网络中的控制

在网络通信过程中,会产生很多控制数据,其实就是元数据,包括一些硬件和协议层的元数据,最典型的就是一些校验和,长度等等。另外,一些扩展数据可以在mbuf中自己定义,在其数据结构最后有一个dynfield1字段,它的说明就是“保留的动态域”。这些都可以是mbuf的一种动态适应机制。

五、总结

从基础入手,继续分层剥离,越来越清晰的逼近DPDK的内部。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值