Zephyr 网络内存分析之net_pkt篇

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_pkt的初始化和管理机制。

概述

一个net pkt由一个net_pkt将多个net_buf串在一起组成,每个net_buf指向一片固定大小的内存用于保存pkt数据,如下图



net pkt
图一

net_pkt

tx和rx的net_pkt都通过slab来管理,通过slab分配一个内存块用于保存net_pkt,下面针对slab管理的pkt都以tx_pkts来分析

slab管理

定义slab

zephyr用NET_PKT_SLAB_DEFINE定义了两个数组rx_pkts和tx_pkts,在配置文件内通过CONFIG_NET_BUF_RX_COUNT和CONFIG_NET_BUF_TX_COUNT定义其packet的个数。该阶段在编译时就完成

1
2
3
// subsys/net/ip/net_pkt.c
NET_PKT_SLAB_DEFINE(rx_pkts, CONFIG_NET_PKT_RX_COUNT);
NET_PKT_SLAB_DEFINE(tx_pkts, CONFIG_NET_PKT_TX_COUNT);

NET_PKT_SLAB_DEFINE的定义将宏展开为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
char __noinit __aligned(4) _k_mem_slab_buf_rx_pkts[sizeof(struct net_pkt)*CONFIG_NET_BUF_RX_COUNT];
struct k_mem_slab rx_pkts __attribute__((section(._k_mem_slab.static.rx))) = {
.wait_q = {{(&rx_pkts.wait_q)},{(&rx_pkts.wait_q)}},
.buffer = _k_mem_slab_buf_rx_pkts,
.block_size = sizeof(struct net_pkt),
.num_blocks = CONFIG_NET_BUF_RX_COUNT,
.free_list = NULL,
.num_used = 0,
}

char __noinit __aligned(4) _k_mem_slab_buf_tx_pkts[sizeof(struct net_pkt)*CONFIG_NET_BUF_TX_COUNT];
struct k_mem_slab tx_pkts __attribute__((section(._k_mem_slab.static.tx))) = {
.wait_q = {{(&tx_pkts.wait_q)},{(&tx_pkts.wait_q)}},
.buffer = _k_mem_slab_buf_tx_pkts,
.block_size = sizeof(struct net_pkt),
.num_blocks = CONFIG_NET_BUF_TX_COUNT,
.free_list = NULL,
.num_used = 0,
}

从展开可见rx,tx分别定义了两个slab管理器rx_pkts和tx_pkts放到seciton ._k_mem_slab.static.中,同时定义了slab管理的内存_k_mem_slab_buf_rx_pkts和_k_mem_slab_buf_tx_pkts被放在heap内,由k_mem_slab->buffer指向slab要管理的buffer。

初始化slab

slab初始化函数init_mem_slab_module通过SYS_INIT注册入drv init section,在驱动初始化PRE_KERNEL_1阶段被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//kernel/mem_slab.c
SYS_INIT(init_mem_slab_module, PRE_KERNEL_1,
CONFIG_KERNEL_INIT_PRIORITY_OBJECTS);
static int init_mem_slab_module(struct device *dev)
{
ARG_UNUSED(dev);

struct k_mem_slab *slab;

for (slab = _k_mem_slab_list_start;
slab < _k_mem_slab_list_end;
slab++) {
create_free_list(slab);
SYS_TRACING_OBJ_INIT(k_mem_slab, slab);
}
return 0;
}

init_mem_slab_module会将._k_mem_slab.static.段中所有的slab管理器都逐一初始化,包括tx_pkts和rx_pkts

1
2
3
4
5
6
7
8
9
//include/linker/common-ram.ld
SECTION_DATA_PROLOGUE(_k_mem_slab_area, (OPTIONAL), SUBALIGN(4))
{
_k_mem_slab_list_start = .;
KEEP(*(SORT_BY_NAME("._k_mem_slab.static.*")))
_k_mem_slab_list_end = .;
} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)

//

使用create_free_list初始化一个slab,主要是把slab所管理的内存进行分块然后建立链表,每个内存块的最开始4byte用存储链表节点的指针,当初始化完成后_k_mem_slab_buf_tx_pkts以sizeof(struct net_pkt)被割裂成内存块,被链表串接起来放到slab管理器的free_list中见图2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//kernel/mem_slab.c
static void create_free_list(struct k_mem_slab *slab)
{
u32_t j;
char *p;

slab->free_list = NULL;
p = slab->buffer;

for (j = 0; j < slab->num_blocks; j++) {
*(char **)p = slab->free_list; //被分割内存的最开始4字节被用作存储指向下一个节点地址
slab->free_list = p;
p += slab->block_size;
}
}

分配Slab

分配Slab就是从free_list里面取出一个节点,如图2 ②,当free_list没有空闲的节点时,等待其它用户释放节点

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
//kernel/mem_slab.c
int k_mem_slab_alloc(struct k_mem_slab *slab, void **mem, s32_t timeout)
{
unsigned int key = irq_lock();
int result;

if (slab->free_list != NULL) {
/* 从free list里面取空闲节点内存 */
*mem = slab->free_list;
slab->free_list = *(char **)(slab->free_list);
slab->num_used++;
result = 0;
} else if (timeout == K_NO_WAIT) {
/* don't wait for a free block to become available */
*mem = NULL;
result = -ENOMEM;
} else {
/* 没有空闲节点等待其它用户释放 */
_pend_current_thread(&slab->wait_q, timeout);
result = _Swap(key);
if (result == 0) {
*mem = _current->base.swap_data;
}
return result;
}

irq_unlock(key);

return result;
}

释放slab

当free slab时,如果发现有其它用户在等待空闲的节点内存,就将释放的提供给它,否则直接加入到free_list,如图二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//kernel/mem_slab.c
void k_mem_slab_free(struct k_mem_slab *slab, void **mem)
{
int key = irq_lock();
struct k_thread *pending_thread = _unpend_first_thread(&slab->wait_q);

if (pending_thread) {
//提供给等待free slab的用户
_set_thread_return_value_with_data(pending_thread, 0, *mem);
_abort_thread_timeout(pending_thread);
_ready_thread(pending_thread);
if (_must_switch_threads()) {
_Swap(key);
return;
}
} else {
//加入到free list
**(char ***)mem = slab->free_list;
slab->free_list = *(char **)mem;
slab->num_used--;
}

irq_unlock(key);
}



pkgslab
图二

net_pkt结构

从slab分配出来的pkt管理buf是无任何数据的空buf,zephyr以struct net_pkt来格式化pkt slab buf,net_pkt内不含封包的数据内存

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
// include/net/net_pkt.h
struct net_pkt {
int _reserved; //net_pkt被slab分配管理, 这里保留为slab作为链表用,前面提到的4字节指针

struct k_mem_slab *slab; //指向自己的slab管理器

struct net_buf *frags; //指向net_buf

struct net_context *context; //指向pkt所属的net context

void *token;

struct net_if *iface; //指向pkt所属的iface(网络接口)

u8_t *appdata; /* application data starts here */
u8_t *next_hdr; /* where is the next header */

struct net_linkaddr lladdr_src; //pkt的src address(mac)
struct net_linkaddr lladdr_dst; //pkt的dst address(mac)

u16_t data_len; //payload的长度

u16_t appdatalen;
u8_t ll_reserve; //link layer的长度(MAC address+type)
u8_t ip_hdr_len;
u8_t sent_or_eof: 1;
u8_t pkt_queued: 1;
u8_t forwarding : 1;
u8_t family : 4; //ipv4 or v6
u8_t _unused : 3;

union {
u8_t ipv6_hop_limit;
u8_t ipv4_ttl;
};
u8_t ref; //一个pkt实例可以被多个用户引用,引用一次+1, free一次-1, -到0时方可做真正的free
}

net pkt分配

net_pkt_get_tx从tx_pkts slab中分配一个slab并初始化为一个net_pkt,调用关系net_pkt_get_tx->net_pkt_get->net_pkt_get_reserve->k_mem_slab_alloc,数据的设置一共分为三层:

  • 通过k_mem_slab_alloc得到net_pkt的slab buf
  • 通过net_pkt_get_reserve初始化net_pkt中的slab(所属slab管理器),ref(每引用一次+1)
  • 通过net_pkt_get初始化net_pkt中的context,iface,family,data_len(data_len是可以存储的数据多少,是mtu抛出TCP/IP header长度)

示意代码如下,可以在源文件中查看详细代码。

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
// subsys/net/ip/net_pkt.c
struct net_pkt *net_pkt_get_tx(struct net_context *context, s32_t timeout)
{
return net_pkt_get(&tx_pkts, context, timeout);
}

static struct net_pkt *net_pkt_get(struct k_mem_slab *slab,
struct net_context *context,
s32_t timeout)
{
...
pkt = net_pkt_get_reserve(slab, net_if_get_ll_reserve(iface, addr6),
timeout);

net_pkt_set_context(pkt, context);
net_pkt_set_iface(pkt, iface);
family = net_context_get_family(context);
net_pkt_set_family(pkt, family);

if (slab != &rx_pkts) {
...
pkt->data_len = data_len;
}

return pkt;
}

struct net_pkt *net_pkt_get_reserve(struct k_mem_slab *slab,
u16_t reserve_head,
s32_t timeout)
#endif /* CONFIG_NET_DEBUG_NET_PKT */
{
...
ret = k_mem_slab_alloc(slab, (void **)&pkt, timeout);
memset(pkt, 0, sizeof(struct net_pkt));

net_pkt_set_ll_reserve(pkt, reserve_head);

pkt->ref = 1;
pkt->slab = slab;

return pkt;
}

上面的描述过程见图三



pkt.png
图三

net_pkt ref

一个网络封包可以被多个用户使用,使用一次就用net_pkt_ref增加一次ref

1
2
3
4
5
6
7
// subsys/net/ip/net_pkt.c
struct net_pkt *net_pkt_ref(struct net_pkt *pkt)
{
pkt->ref++;

return pkt;
}

pkg free

由于一个网络封包可以被多个用户ref,因此net_pkt_unref来进行free,当ref大于0时,只对pkt->ref–,直到没人ref时(ref==0)才调用k_mem_slab_free对pkt进行释放

1
2
3
4
5
6
7
8
9
10
11
12
13
// subsys/net/ip/net_pkt.c
void net_pkt_unref(struct net_pkt *pkt)
{
if (--pkt->ref > 0) {
return;
}

if (pkt->frags) {
net_pkt_frag_unref(pkt->frags);
}

k_mem_slab_free(pkt->slab, (void **)&pkt);
}

参考代码

https://github.com/zephyrproject-rtos/zephyr/tree/master/subsys/net/ip