Zephyr 网络内存分析之net_pkt篇

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 链接到标题

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

// 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的定义将宏展开为

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阶段被调用

//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

//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

//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没有空闲的节点时,等待其它用户释放节点

//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,如图二

//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);
}

net_pkt结构 链接到标题

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

// 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长度)

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

// 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;
}

上面的描述过程见图三


net_pkt ref 链接到标题

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

// 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进行释放

// 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