io_uring 零複製 Rx¶
簡介¶
io_uring 零複製 Rx (ZC Rx) 是一種功能,它消除了網路接收路徑上的核心到使用者的複製,允許將資料包資料直接接收到使用者空間記憶體中。此功能與 TCP_ZEROCOPY_RECEIVE 的不同之處在於,它沒有嚴格的對齊要求,也不需要 mmap()/munmap()。 與核心旁路解決方案(例如 DPDK)相比,資料包標頭由核心 TCP 協議棧像往常一樣處理。
網絡卡硬體要求¶
io_uring ZC Rx 要工作,需要幾個網絡卡硬體功能。目前,核心 API 不配置網絡卡,必須由使用者完成。
報頭/資料分離¶
需要將資料包在 L4 邊界處拆分為報頭和有效負載。報頭像往常一樣接收到核心記憶體中,並由 TCP 協議棧像往常一樣處理。有效負載直接接收到使用者空間記憶體中。
流控制¶
為該功能配置了特定的硬體 Rx 佇列,但現代網絡卡通常將流分佈在所有硬體 Rx 佇列中。流控制是必需的,以確保只有所需的流被定向到為 io_uring ZC Rx 配置的硬體佇列。
RSS¶
除了上面的流控制之外,還需要 RSS 將所有其他非零複製流從配置為 io_uring ZC Rx 的佇列中移開。
用法¶
設定網絡卡¶
目前必須帶外完成。
確保至少有兩個佇列
ethtool -L eth0 combined 2
啟用報頭/資料分離
ethtool -G eth0 tcp-data-split on
使用 RSS 劃分一半的硬體 Rx 佇列用於零複製
ethtool -X eth0 equal 1
設定流控制,請記住佇列從 0 開始索引
ethtool -N eth0 flow-type tcp6 ... action 1
設定 io_uring¶
本節介紹底層 io_uring 核心 API。 有關如何使用更高級別 API 的資訊,請參閱 liburing 文件。
使用以下必需的設定標誌建立一個 io_uring 例項
IORING_SETUP_SINGLE_ISSUER
IORING_SETUP_DEFER_TASKRUN
IORING_SETUP_CQE32
建立記憶體區域¶
分配使用者空間記憶體區域以接收零複製資料
void *area_ptr = mmap(NULL, area_size,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE,
0, 0);
建立填充環¶
為用於返回已用緩衝區的共享環形緩衝區分配記憶體
void *ring_ptr = mmap(NULL, ring_size,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE,
0, 0);
此填充環包含用於報頭的一些空間,後跟一個 struct io_uring_zcrx_rqe 陣列
size_t rq_entries = 4096;
size_t ring_size = rq_entries * sizeof(struct io_uring_zcrx_rqe) + PAGE_SIZE;
/* align to page size */
ring_size = (ring_size + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
註冊 ZC Rx¶
填寫註冊結構體
struct io_uring_zcrx_area_reg area_reg = {
.addr = (__u64)(unsigned long)area_ptr,
.len = area_size,
.flags = 0,
};
struct io_uring_region_desc region_reg = {
.user_addr = (__u64)(unsigned long)ring_ptr,
.size = ring_size,
.flags = IORING_MEM_REGION_TYPE_USER,
};
struct io_uring_zcrx_ifq_reg reg = {
.if_idx = if_nametoindex("eth0"),
/* this is the HW queue with desired flow steered into it */
.if_rxq = 1,
.rq_entries = rq_entries,
.area_ptr = (__u64)(unsigned long)&area_reg,
.region_ptr = (__u64)(unsigned long)®ion_reg,
};
向核心註冊
io_uring_register_ifq(ring, ®);
對映填充環¶
核心在註冊 struct io_uring_zcrx_ifq_reg 中填寫填充環的欄位。將其對映到使用者空間
struct io_uring_zcrx_rq refill_ring;
refill_ring.khead = (unsigned *)((char *)ring_ptr + reg.offsets.head);
refill_ring.khead = (unsigned *)((char *)ring_ptr + reg.offsets.tail);
refill_ring.rqes =
(struct io_uring_zcrx_rqe *)((char *)ring_ptr + reg.offsets.rqes);
refill_ring.rq_tail = 0;
refill_ring.ring_ptr = ring_ptr;
接收資料¶
準備一個零複製接收請求
struct io_uring_sqe *sqe;
sqe = io_uring_get_sqe(ring);
io_uring_prep_rw(IORING_OP_RECV_ZC, sqe, fd, NULL, 0, 0);
sqe->ioprio |= IORING_RECV_MULTISHOT;
現在,提交併等待
io_uring_submit_and_wait(ring, 1);
最後,處理完成
struct io_uring_cqe *cqe;
unsigned int count = 0;
unsigned int head;
io_uring_for_each_cqe(ring, head, cqe) {
struct io_uring_zcrx_cqe *rcqe = (struct io_uring_zcrx_cqe *)(cqe + 1);
unsigned long mask = (1ULL << IORING_ZCRX_AREA_SHIFT) - 1;
unsigned char *data = area_ptr + (rcqe->off & mask);
/* do something with the data */
count++;
}
io_uring_cq_advance(ring, count);
回收緩衝區¶
將緩衝區返回給核心以再次使用
struct io_uring_zcrx_rqe *rqe;
unsigned mask = refill_ring.ring_entries - 1;
rqe = &refill_ring.rqes[refill_ring.rq_tail & mask];
unsigned long area_offset = rcqe->off & ~IORING_ZCRX_AREA_MASK;
rqe->off = area_offset | area_reg.rq_area_token;
rqe->len = cqe->res;
IO_URING_WRITE_ONCE(*refill_ring.ktail, ++refill_ring.rq_tail);
測試¶
參見 tools/testing/selftests/drivers/net/hw/iou-zcrx.c