Zephyr网络内存分析之net-buf篇

Creative Commons
本作品采用知识共享署名

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 pkt
图一

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的个数。该阶段在编译时就完成

1
2
3
// 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);

展开为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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
    四个部分的关系见图二

    buf pool
    图二

    从展开的代码也可以看到net_buf pool 4个部分在定义的时候已经确定了连接关系,因此无需额外的初始化.

net_buf_pool

net_buf_pool用于管理net_buf pool

1
2
3
4
5
6
7
8
9
10
11
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)

    netbuf
    图二
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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指向的链表尾
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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完成数据拷贝
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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