Page Pool API

page_pool 分配器针对回收 skb 数据包和 xdp 帧使用的页面或页面片段进行了优化。

基本用法包括将任何 alloc_pages() 调用替换为 page_pool_alloc(),这将根据请求的内存大小分配具有或不具有页面拆分的内存。

如果驱动程序知道它始终需要完整页面,或者其分配始终小于半页,则可以使用更具体的 API 调用之一

1. page_pool_alloc_pages():当驱动程序知道它需要的内存始终大于从页面池分配的页面的一半时,分配不带页面拆分的内存。当页面被回收回页面池时,没有针对“struct page”的缓存行污染。

2. page_pool_alloc_frag():当驱动程序知道它需要的内存始终小于或等于从页面池分配的页面的一半时,分配带有页面拆分的内存。页面拆分能够节省内存,从而避免数据访问的 TLB/缓存未命中,但实现页面拆分也有一些成本,主要是“struct page”的一些缓存行污染/反弹以及 page->pp_ref_count 的原子操作。

该 API 跟踪正在使用的页面,为了让 API 用户知道何时可以安全地释放 page_pool 对象,API 用户必须调用 page_pool_put_page()page_pool_free_va() 来释放 page_pool 对象,或者将 page_pool 对象附加到页面池感知对象,例如标记为 skb_mark_for_recycle() 的 skb。

如果页面被拆分为多个片段,则可以在同一页面上多次调用 page_pool_put_page()。对于最后一个片段,它将回收该页面,或者在 page->_refcount > 1 的情况下,它将释放 DMA 映射和正在使用的状态统计。

仅当使用 PP_FLAG_DMA_SYNC_DEV 标志创建 page_pool 时,才为最后一个片段调用 dma_sync_single_range_for_device(),因此它依赖于最后一个释放的片段来为同一页面中的所有片段执行 sync_for_device 操作(当页面被拆分时)。API 用户必须正确设置 pool->p.max_len 和 pool->p.offset,并确保为片段 API 调用 page_pool_put_page() 且 dma_sync_size 为 -1。

架构概述

+------------------+
|       Driver     |
+------------------+
        ^
        |
        |
        |
        v
+--------------------------------------------+
|                request memory              |
+--------------------------------------------+
    ^                                  ^
    |                                  |
    | Pool empty                       | Pool has entries
    |                                  |
    v                                  v
+-----------------------+     +------------------------+
| alloc (and map) pages |     |  get page from cache   |
+-----------------------+     +------------------------+
                                ^                    ^
                                |                    |
                                | cache available    | No entries, refill
                                |                    | from ptr-ring
                                |                    |
                                v                    v
                      +-----------------+     +------------------+
                      |   Fast cache    |     |  ptr-ring cache  |
                      +-----------------+     +------------------+

监控

可以通过 netdev genetlink 系列访问有关系统上页面池的信息(请参阅 Documentation/netlink/specs/netdev.yaml)。

API 接口

创建的池的数量**必须**与硬件队列的数量匹配,除非硬件限制使其不可能。 否则会违背页面池的目的,即在没有锁定的情况下从缓存快速分配页面。 这种无锁保证自然来自在 NAPI softirq 下运行。 该保护不必严格地是 NAPI,任何保证分配页面不会导致竞争条件的保证就足够了。

struct page_pool *page_pool_create(const struct page_pool_params *params)

创建页面池

参数

const struct page_pool_params *params

参数,请参阅 struct page_pool_params

struct page_pool_params

页面池参数

定义:

struct page_pool_params {
    struct page_pool_params_fast  fast;
    unsigned int    order;
    unsigned int    pool_size;
    int nid;
    struct device   *dev;
    struct napi_struct *napi;
    enum dma_data_direction dma_dir;
    unsigned int    max_len;
    unsigned int    offset;
    struct page_pool_params_slow  slow;
    STRUCT_GROUP( struct net_device *netdev;
    unsigned int queue_idx;
    unsigned int    flags;
};

成员

fast

在热路径上经常访问的参数

order

分配时 2^order 个页面

pool_size

ptr_ring 的大小

nid

用于从中分配页面的 NUMA 节点 ID

dev

设备,用于 DMA 预映射

napi

作为页面的唯一使用者的 NAPI,否则为 NULL

dma_dir

DMA 映射方向

max_len

PP_FLAG_DMA_SYNC_DEV 的最大 DMA 同步内存大小

offset

PP_FLAG_DMA_SYNC_DEV 的 DMA 同步地址偏移

slow

仅具有慢路径访问权限的参数(初始化和 Netlink)

netdev

此池将服务的 netdev(如果没有或有多个,则保留为 NULL)

queue_idx

正在为其创建此 page_pool 的队列索引。

flags

PP_FLAG_DMA_MAP、PP_FLAG_DMA_SYNC_DEV、PP_FLAG_SYSTEM_POOL、PP_FLAG_ALLOW_UNREADABLE_NETMEM。

struct page *page_pool_dev_alloc_pages(struct page_pool *pool)

分配一个页面。

参数

struct page_pool *pool

从中分配的池

描述

从页面分配器或 page_pool 缓存获取页面。

struct page *page_pool_dev_alloc_frag(struct page_pool *pool, unsigned int *offset, unsigned int size)

分配页面片段。

参数

struct page_pool *pool

从中分配的池

unsigned int *offset

分配页面的偏移量

unsigned int size

请求的大小

描述

从页面分配器或 page_pool 缓存获取页面片段。

返回值

分配的页面片段,否则返回 NULL。

struct page *page_pool_dev_alloc(struct page_pool *pool, unsigned int *offset, unsigned int *size)

分配页面或页面片段。

参数

struct page_pool *pool

从中分配的池

unsigned int *offset

分配页面的偏移量

unsigned int *size

输入为请求的大小,输出为分配的大小

描述

从页面分配器或 page_pool 缓存获取页面或页面片段,具体取决于请求的大小,以便以最小的内存利用率和性能损失来分配内存。

返回值

分配的页面或页面片段,否则返回 NULL。

void *page_pool_dev_alloc_va(struct page_pool *pool, unsigned int *size)

分配页面或页面片段并返回其 va。

参数

struct page_pool *pool

从中分配的池

unsigned int *size

输入为请求的大小,输出为分配的大小

描述

这只是 page_pool_alloc() API 的一个薄包装,它返回分配的页面或页面片段的 va。

返回值

分配的页面或页面片段的 va,否则返回 NULL。

enum dma_data_direction page_pool_get_dma_dir(const struct page_pool *pool)

检索存储的 DMA 方向。

参数

const struct page_pool *pool

从中分配页面的池

描述

获取存储的 dma 方向。 驱动程序可能会决定在本地存储此信息,并避免额外的缓存行从 page_pool 确定方向。

void page_pool_put_page(struct page_pool *pool, struct page *page, unsigned int dma_sync_size, bool allow_direct)

释放对页面池页面的引用

参数

struct page_pool *pool

从中分配页面的池

struct page *page

要释放引用的页面

unsigned int dma_sync_size

设备可能已触摸页面的多少

bool allow_direct

由消费者释放,允许无锁缓存

描述

结果取决于页面 refcnt。 如果驱动程序将 refcnt 提升到 > 1,这将取消映射页面。 如果页面 refcnt 为 1,则分配器拥有该页面,并将尝试在其中一个池缓存中回收它。 如果设置了 PP_FLAG_DMA_SYNC_DEV,则将使用 dma_sync_single_range_for_device() 为该设备同步该页面。

void page_pool_put_full_page(struct page_pool *pool, struct page *page, bool allow_direct)

释放页面池页面上的引用

参数

struct page_pool *pool

从中分配页面的池

struct page *page

要释放引用的页面

bool allow_direct

由消费者释放,允许无锁缓存

描述

page_pool_put_page() 类似,但将 DMA 同步在 page_pool_params.max_len 中配置的整个内存区域。

void page_pool_recycle_direct(struct page_pool *pool, struct page *page)

释放页面池页面上的引用

参数

struct page_pool *pool

从中分配页面的池

struct page *page

要释放引用的页面

描述

page_pool_put_full_page() 类似,但调用者必须保证安全上下文(例如 NAPI),因为它会将页面直接回收回池快速缓存。

void page_pool_free_va(struct page_pool *pool, void *va, bool allow_direct)

将 va 释放到 page_pool 中

参数

struct page_pool *pool

从中分配 va 的池

void *va

要释放的 va

bool allow_direct

由消费者释放,允许无锁缓存

描述

释放从 page_pool_allo_va() 分配的 va。

dma_addr_t page_pool_get_dma_addr(const struct page *page)

检索存储的 DMA 地址。

参数

const struct page *page

从页面池分配的页面

描述

获取页面的 DMA 地址。 必须使用 PP_FLAG_DMA_MAP 创建页面所属的页面池。

bool page_pool_get_stats(const struct page_pool *pool, struct page_pool_stats *stats)

获取页面池统计信息

参数

const struct page_pool *pool

从中分配页面的池

struct page_pool_stats *stats

要填写的 struct page_pool_stats

描述

检索有关 page_pool 的统计信息。 仅当内核已配置为 CONFIG_PAGE_POOL_STATS=y 时,此 API 才可用。 指向调用者分配的 struct page_pool_stats 结构的指针将传递给此 API,该 API 将被填充。 然后,调用者可以向用户报告这些统计信息(可能通过 ethtool、debugfs 等)。

DMA 同步

驱动程序始终负责为 CPU 同步页面。 驱动程序可以选择也负责为设备同步,或者设置 PP_FLAG_DMA_SYNC_DEV 标志,以请求从页面池分配的页面已为设备同步。

如果设置了 PP_FLAG_DMA_SYNC_DEV,则驱动程序必须告知核心必须同步缓冲区的哪个部分。 这允许核心在驱动程序知道设备仅访问了页面的一部分时避免同步整个页面。

大多数驱动程序将在帧前面保留空间。 设备不会触摸缓冲区的这一部分,因此为了避免同步,驱动程序可以适当地设置 struct page_pool_params 中的 offset 字段。

对于在 XDP xmit 和 skb 路径上回收的页,页池将使用 struct page_pool_paramsmax_len 成员来决定需要同步页面的多少(从 offset 开始)。 当直接在驱动程序中释放页面 (page_pool_put_page()) 时,dma_sync_size 参数指定需要同步缓冲区多少。

如果不确定,请将 offset 设置为 0,将 max_len 设置为 PAGE_SIZE,并将 -1 作为 dma_sync_size 传递。 这种参数组合始终是正确的。

请注意,同步参数是针对整个页面的。 当使用片段 (PP_FLAG_PAGE_FRAG) 时,这一点很重要,因为分配的缓冲区可能小于一个完整的页面。 除非驱动程序作者真正了解页池的内部结构,否则建议始终使用 offset = 0max_len = PAGE_SIZE 与分片的页池。

统计信息 API 和结构体

如果内核配置了 CONFIG_PAGE_POOL_STATS=y,则 API page_pool_get_stats() 和下面描述的结构体可用。 它接受一个指向 struct page_pool 的指针和一个指向调用者分配的 struct page_pool_stats 的指针。

较旧的驱动程序通过 ethtool 或 debugfs 公开页池统计信息。 相同的统计信息可以通过 netlink netdev 系列以独立于驱动程序的方式访问。

struct page_pool_alloc_stats

分配统计信息

定义:

struct page_pool_alloc_stats {
    u64 fast;
    u64 slow;
    u64 slow_high_order;
    u64 empty;
    u64 refill;
    u64 waive;
};

成员

fast

成功的快速路径分配

slow

慢速路径 order-0 分配

slow_high_order

慢速路径高阶分配

empty

ptr 环为空,因此强制进行慢速路径分配

refill

触发缓存重新填充的分配

waive

从 ptr 环获得的由于 NUMA 不匹配而无法添加到缓存的页面

struct page_pool_recycle_stats

回收(释放)统计信息

定义:

struct page_pool_recycle_stats {
    u64 cached;
    u64 cache_full;
    u64 ring;
    u64 ring_full;
    u64 released_refcnt;
};

成员

cached

回收将页面放置在页池缓存中

cache_full

页池缓存已满

ring

页面放置到 ptr 环中

ring_full

由于 ptr 环已满,因此从页池释放页面

released_refcnt

页面已释放(未回收),因为 refcnt > 1

struct page_pool_stats

组合的页池使用统计信息

定义:

struct page_pool_stats {
    struct page_pool_alloc_stats alloc_stats;
    struct page_pool_recycle_stats recycle_stats;
};

成员

alloc_stats

参见 struct page_pool_alloc_stats

recycle_stats

参见 struct page_pool_recycle_stats

描述

用于将页池统计信息与不同存储需求相结合的包装结构体。

编码示例

注册

/* Page pool registration */
struct page_pool_params pp_params = { 0 };
struct xdp_rxq_info xdp_rxq;
int err;

pp_params.order = 0;
/* internal DMA mapping in page_pool */
pp_params.flags = PP_FLAG_DMA_MAP;
pp_params.pool_size = DESC_NUM;
pp_params.nid = NUMA_NO_NODE;
pp_params.dev = priv->dev;
pp_params.napi = napi; /* only if locking is tied to NAPI */
pp_params.dma_dir = xdp_prog ? DMA_BIDIRECTIONAL : DMA_FROM_DEVICE;
page_pool = page_pool_create(&pp_params);

err = xdp_rxq_info_reg(&xdp_rxq, ndev, 0);
if (err)
    goto err_out;

err = xdp_rxq_info_reg_mem_model(&xdp_rxq, MEM_TYPE_PAGE_POOL, page_pool);
if (err)
    goto err_out;

NAPI 轮询器

/* NAPI Rx poller */
enum dma_data_direction dma_dir;

dma_dir = page_pool_get_dma_dir(dring->page_pool);
while (done < budget) {
    if (some error)
        page_pool_recycle_direct(page_pool, page);
    if (packet_is_xdp) {
        if XDP_DROP:
            page_pool_recycle_direct(page_pool, page);
    } else (packet_is_skb) {
        skb_mark_for_recycle(skb);
        new_page = page_pool_dev_alloc_pages(page_pool);
    }
}

统计信息

#ifdef CONFIG_PAGE_POOL_STATS
/* retrieve stats */
struct page_pool_stats stats = { 0 };
if (page_pool_get_stats(page_pool, &stats)) {
        /* perhaps the driver reports statistics with ethool */
        ethtool_print_allocation_stats(&stats.alloc_stats);
        ethtool_print_recycle_stats(&stats.recycle_stats);
}
#endif

驱动程序卸载

/* Driver unload */
page_pool_put_full_page(page_pool, page, false);
xdp_rxq_info_unreg(&xdp_rxq);