Zephyr网络内存分析之net-buf篇
Zephyr的net pkt內存由net_pkt和net_buf組成,net_pkt通过slab管理, net_buf通过buf pool管理。net_pkt作为管理结构,将一组net_buf用链表串在一起行程net pkt,用于管理和存储网络封包。本文基于rx和tx分析了net_buf的初始化和管理机制。
概述 链接到标题
net_buf_pool存放了所有的net_buf,net_buf容纳的数据长度是固定的大小的,每个net_buf指向一片固定大小的内存data buf用于保存pkt数据,一个net_buf和一片data buf一一对应,见图一
net_buf 链接到标题
tx和rx的net_buf通过net_buf_pool来管理,通过net_buf_pool来完成net_buf内存定义,初始化,分配,释放。
net buf pool 链接到标题
zephyr用NET_PKT_DATA_POOL_DEFINE定义了两个数组rx_bufs和tx_bufs,在配置文件内通过CONFIG_NET_BUF_RX_COUNT和CONFIG_NET_BUF_TX_COUNT定义其pool内容纳net_bufr的个数。该阶段在编译时就完成
// subsys/net/ip/net_pkt.c
NET_PKT_DATA_POOL_DEFINE(rx_bufs, CONFIG_NET_BUF_RX_COUNT);
NET_PKT_DATA_POOL_DEFINE(tx_bufs, CONFIG_NET_BUF_TX_COUNT);
展开为
static struct net_buf net_buf_rx_bufs[CONFIG_NET_BUF_RX_COUNT] __noinit;
static u8_t __noinit net_buf_data_rx_bufs[CONFIG_NET_BUF_RX_COUNT][CONFIG_NET_BUF_DATA_SIZE];
static const struct net_buf_pool_fixed net_buf_fixed_rx_bufs = {
.data_size = CONFIG_NET_BUF_DATA_SIZE,
.data_pool = (u8_t *)net_buf_data_rx_bufs,
};
static const struct net_buf_data_alloc net_buf_fixed_alloc_rx_bufs = {
.cb = &net_buf_fixed_cb,
.alloc_data = (void *)&net_buf_fixed_rx_bufs,
};
struct net_buf_pool rx_bufs __net_buf_align
__attribute__((section(._net_buf_pool.static.rx))) =
{
.alloc = &net_buf_fixed_rx_bufs,
.free = {
._queue =
{
.wait_q = {{(&rx_bufs.free.wait_q)}, {(&rx_bufs.free.wait_q)}},
.data_q = {{(&rx_bufs.free.data_q)}, {(&rx_bufs.free.data_q)}},
}
},
.__bufs = net_buf_rx_bufs,
.buf_count = CONFIG_NET_BUF_RX_COUNT,
.uninit_count = CONFIG_NET_BUF_RX_COUNT,
.destroy = NULL,
}
static struct net_buf net_buf_tx_bufs[CONFIG_NET_BUF_TX_COUNT] __noinit;
static u8_t __noinit net_buf_data_tx_bufs[CONFIG_NET_BUF_TX_COUNT][CONFIG_NET_BUF_DATA_SIZE];
static const struct net_buf_pool_fixed net_buf_fixed_tx_bufs = {
.data_size = CONFIG_NET_BUF_DATA_SIZE,
.data_pool = (u8_t *)net_buf_data_tx_bufs,
};
static const struct net_buf_data_alloc net_buf_fixed_alloc_tx_bufs = {
.cb = &net_buf_fixed_cb,
.alloc_data = (void *)&net_buf_fixed_tx_bufs,
};
struct net_buf_pool tx_bufs __net_buf_align
__attribute__((section(._net_buf_pool.static.tx))) =
{
.alloc = &net_buf_fixed_tx_bufs,
.free = {
._queue =
{
.wait_q = {{(&tx_bufs.free.wait_q)}, {(&tx_bufs.free.wait_q)}},
.data_q = {{(&tx_bufs.free.data_q)}, {(&tx_bufs.free.data_q)}},
}
},
.__bufs = net_buf_tx_bufs,
.buf_count = CONFIG_NET_BUF_TX_COUNT,
.uninit_count = CONFIG_NET_BUF_TX_COUNT,
.destroy = NULL,
}
从上面的展开可以看到一个net buf pool由4个部分组成:
- 一片连续内存data buf: net_buf_data_tx_bufs[CONFIG_NET_BUF_TX_COUNT][CONFIG_NET_BUF_DATA_SIZE];
- 一个net_buf_data_alloc提供连续内存的分配和释放函数:net_buf_fixed_alloc_tx_bufs
- 一组net_buf用于管理net_buf_data_alloc alloc的buf和net_buf_data_tx_bufs[n]一一对应: net_buf_tx_bufs
- 一个net_buf_pool用于管理net_buf pool:tx_bufs 四个部分的关系见图二:
net_buf_pool 链接到标题
net_buf_pool用于管理net_buf pool
struct net_buf_pool {
struct k_lifo free; //free net_buf LIFO,释放后的net_buf放到这个LIFO内
const u16_t buf_count; //pool含有net_buf的总数
u16_t uninit_count; //未初始化的net_buf,系统初始化完成后所有的net_buf都是unint状态,每alloc一个就取一个出去,当释放时就只会放大free LIFO内
void (*const destroy)(struct net_buf *buf);
const struct net_buf_data_alloc *alloc;
struct net_buf * const __bufs;
};
net_buf结构 链接到标题
struct net_buf { union { sys_snode_t node; struct net_buf *frags; //指向下一个net_buf };
u8_t ref; //引用一次+1, free一次-1, -到0时方可做真正的free
u8_t flags;
u8_t pool_id; //指向自己所属的pool
union {
struct {
u8_t *data; //指向buffer的data起始位置 = __buf+header len
u16_t len; //data指针中拥有数据的长度
u16_t size; //data指针最大数据容量
u8_t *__buf; //net buf指向的data buf的起始位置
};
struct net_buf_simple b; //实际代码操作过程中使用simple buf的API操作net buf
};
/** System metadata for this buffer. */
u8_t user_data[CONFIG_NET_BUF_USER_DATA_SIZE] __net_buf_align;
};
分配net_buf 链接到标题
net_pkt_get_data从net buf pool分配并初始化为一个net_buf,调用关系如下net_pkt_get_data->_pkt_get_data->net_pkt_get_reserve_data->net_buf_alloc->net_buf_alloc_fixed->net_buf_alloc_len,数据的设置一共分2层
- net_buf_alloc_len完成从指定pool内分配一个net_buf(net_buf_alloc,net_buf_alloc_fixed只是包装)并建立net_buf和data buf对应联系,见图三
- net_pkt_get_reserve_data(net_pkt_get_data,_pkt_get_data只是包装和获取一些参数)改变net_buf中data的指针(跳过header,指向payload)
struct net_buf *net_buf_alloc_len(struct net_buf_pool *pool, size_t size,
s32_t timeout)
{
//如果uninit中有net_buf从uninit array中分配
if (pool->uninit_count) {
uninit_count = pool->uninit_count--;
buf = pool_get_uninit(pool, uninit_count);
goto success;
}
//如果uninit中没有net_buf从free lifo中获取
buf = k_lifo_get(&pool->free, timeout);
success:
buf->__buf = data_alloc(buf, &size, timeout); //从data buf pool中分配出data buf,并和net buf关联
buf->ref = 1; //设置ref
buf->flags = 0;
buf->frags = NULL; //net buf链表
buf->size = size; //net buf指向data buf的大小
net_buf_reset(buf); //将net_buf->data指针指向__buf
return buf;
}
struct net_buf *net_pkt_get_reserve_data(struct net_buf_pool *pool,
u16_t reserve_head,
s32_t timeout)
{
frag = net_buf_alloc(pool, timeout);
net_buf_reserve(frag, reserve_head); //将data指针指向payload
}
#define net_buf_reserve(buf, reserve) net_buf_simple_reserve(&(buf)->b, reserve)
void net_buf_simple_reserve(struct net_buf_simple *buf, size_t reserve)
{
buf->data = buf->__buf + reserve; //移动data指针
}
net_buf和net_pkt关联 链接到标题
从图一可以看到,net_buf链和net_pkt关联形成一个完整的网络封包,这个关联过程由net_pkt_frag_add完成:
- 当net_pkt内frags为NULL时(为发生过关联),frags指向net_buf即可
- 当frags不为NULL时,将add的net_buf放入frags指向的链表尾
void net_pkt_frag_add(struct net_pkt *pkt, struct net_buf *frag)
{
NET_DBG("pkt %p frag %p (%s:%d)", pkt, frag, caller, line);
//第一次add加到frags中
if (!pkt->frags) {
pkt->frags = frag;
return;
}
//再次add添加到链表尾
net_buf_frag_insert(net_buf_frag_last(pkt->frags), frag);
}
net_pkt&net_buf的使用 链接到标题
zephyr使用下面几个API将数据cp到net_pkt所持有的net_buf中
- net_pkt_append_u8
- net_pkt_append_be16
- net_pkt_append_be32
- net_pkt_append_le32
- net_pkt_append_all 前面四个API都是调整了字节序然后调用net_pkt_append_all完成的,因此只用分析net_pkt_append_all,net_pkt_append_all通过net_pkt_append->net_pkt_append_bytes完成数据拷贝
static inline u16_t net_pkt_append_bytes(struct net_pkt *pkt,
const u8_t *value,
u16_t len, s32_t timeout)
{
struct net_buf *frag = net_buf_frag_last(pkt->frags);
u16_t added_len = 0;
do {
u16_t count = min(len, net_buf_tailroom(frag));
void *data = net_buf_add(frag, count); //获取net_buf所属data buf空闲位置的指针net_buf->data+net_buf->len,然后将net_buf->len增加
memcpy(data, value, count); //拷贝数据到data buf内
len -= count;
added_len += count;
value += count;
if (len == 0) {
return added_len;
}
//如果当前的net_buf放不完append的数据,再开net_buf
frag = net_pkt_get_frag(pkt, timeout);
if (!frag) {
return added_len;
}
net_pkt_frag_add(pkt, frag);
} while (1);
/* Unreachable */
return 0;
}
net_buf free 链接到标题
使用net_pkt_frag_unref->net_buf_unref释放frag net_buf可能被多次引用当ref大于0时,只对buf->ref–,直到没人ref时(ref==0),使用pool->alloc->cb->unref对buf->__buf进行释放(未做任何事情),再使用net_buf_destroy将buf退回到free LIFO中
void net_buf_unref(struct net_buf *buf)
{
while (buf) {
struct net_buf *frags = buf->frags;
struct net_buf_pool *pool;
// ref --
if (--buf->ref > 0) {
return;
}
//无其它使用者ref时, 将data buf释放
if (buf->__buf) {
data_unref(buf, buf->__buf);
buf->__buf = NULL;
}
buf->data = NULL;
buf->frags = NULL;
net_buf_destroy(buf); //将net_buf放回pool
buf = frags;
}
}
static inline void net_buf_destroy(struct net_buf *buf)
{
struct net_buf_pool *pool = net_buf_pool_get(buf->pool_id);
k_lifo_put(&pool->free, buf);
}
参考代码 链接到标题
https://github.com/zephyrproject-rtos/zephyr/tree/master/subsys/net/ip