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