本文介紹 DPDK 中 vdev 的創建方式和簡單的使用方式。
用戶編寫的 DPDK 應用使用 DPDK 的 API 來發送和接收網絡數據包。這些 API 隱藏了底下網卡硬件的差異和編程環境的差異,提供統一的編程接口。這些 API 統稱為環境抽象層 EAL(Environment Abstraction Layer)。
PMD (Poll Mode Driver) 是 DPDK 提供的用戶態網卡驅動,不同網卡有自己對應的 PMD,以靜態或動態鏈接方式使用。一般 DPDK 應用編譯時鏈接了很多類型的 PMD,我們也可以適當裁剪來去除平時用不到的 PMD。
攜帶多種 PMD 時,應用程序用一套業務邏輯代碼和收發網絡數據包的 API 就能操作不同的網卡來完成工作。
為了這套網絡數據包從應用層到網卡物理層能對接起來,需要打通從 EAL 層的收發包 API 到物理網卡的收發包。
這里一共分為以下幾個層次:
- 用戶配置應用程序使用到哪些物理網卡
- 應用層使用物理網卡對應的網口號調用統一的 EAL 收發包 API
- EAL 收發包 API 使用物理網卡對應的 PMD 的收發包 API
- PMD 從物理網卡收發網絡數據包
其中物理網口的網口號分配,EAL API 與 PMD API 的對應,PMD 與物理網卡的對應都是 DPDK 自動完成的。
上面的 4 個步驟的執行過程如下(跟上面不是一一對應)。
- 用戶為使用到的網卡綁定 DPDK 需要的設備驅動,例如 igb_uio/uio_generic_pci/vfio_pci,有些網卡不需要綁定,例如 MLNX 的網卡。
- 應用程序鏈接的 PMD 在應用程序全局初始化時注冊到對應的 bus 的驅動列表上。一般的物理網卡對應 pci bus, 虛擬類型的網絡設備對應 vdev bus。
- DPDK 框架初始化時,會查找系統的網絡設備,這步由不同的 bus 自己查找自己所管理的設備。
- DPDK 初始化過程中,調用 bus 的 probe 過程來關聯 PMD 與對應的設備,例如物理網卡或虛擬網絡設備。DPDK 會為查找到的網絡設備分配網口號,并創建一個以太網設備對象 ethdev, ethdev 的收發包 API 關聯到 PMD 的 API。
- PMD 收發網絡包時,直接操作物理網卡對應的內存和寄存器來完成收發功能。
DPDK 中虛擬網絡設備 vdev 與物理網卡不一樣,沒有直接對應的物理設備,不像在 linux 中可以通過查找 pci bus 來收集設備列表,vdev 只能通過 api 來創建虛擬設備,并且有自己對應的 bus 類型 vdev。
vdev 的設備來源包括命令行參數和直接的 api 調用。其中命令行參數最終也是調 api 來創建虛擬設備。
常用的虛擬設備包括 bonding, virtio_user, net_pcap 等。
vdev 介紹
下面介紹一下 vdev 的創建和設備參數的使用
通過 EAL 參數創建
./dpdk-testpmd -l 1-3 -n 4 -a 01:00.0 -a 01:00.1 \
--vdev "net_bonding0,mode=2,xmit_policy=l23,member=0000:01:00.0,member=0000:01:00.1" \
-- -i --portmask=0x3 --nb-cores=2
上面命令行參數指定了 2 個物理設備 01:00.0 和 01:00.1,還有一個 vdev, 類型是 net_bonding
通過命令行方式創建 vdev 時,設備類型后面帶了詳細的設備參數。
DPDK EAL 的命令行參數可以指定三種設備類型參數
enum rte_devtype {
RTE_DEVTYPE_ALLOWED, // 對應 -a
RTE_DEVTYPE_BLOCKED, // 對應 -b
RTE_DEVTYPE_VIRTUAL, // 對應 --vdev
};
allow list 是允許使用的 pci 設備列表,block list 是禁止使用的 pci 設備列表,virtual 的 vdev 設備的參數。
vdev 設備初始化分為四步:
- 注冊 vdev bus, 注冊 vdev 對應的 PMD 到 vdev bus 的驅動列表
- 處理 EAL 參數 - eal_parse_args(argc, argv) ,包括 allow/block list, vdev
- scan bus 上注冊的設備 - rte_bus_scan(),
- pci 設備從系統目錄尋找
- vdev 設備從 EAL 參數解析得到的設備參數列表中找
- 已找到的設備,查找設備匹配的驅動并進行驅動 probe,處理設備參數 - rte_bus_probe()
對應 eal 初始化函數
// lib/eal/linux/eal.c
eal_parse_args(int argc, char **argv) {
...
eal_parse_args(argc, argv);
...
rte_bus_scan(); // pci 存入 pci bus 的 device list 中,vdev 存入 vdev device list 中
...
rte_bus_probe();
...
}
在 pci 設備初始化完后,會為 vdev 分配網口號。
通過封裝過的 API 創建
int bond_port_id = rte_eth_bond_create("net_bonding0", BONDING_MODE_BALANCE, 0);
rte_eth_dev_configure(BOND_PORT, 1, 1, ...;
rte_eth_bond_member_add(bond_port_id, 0);
rte_eth_bond_member_add(bond_port_id, 1);
rte_eth_rx_queue_setup(bond_port_id, 0, ...);
rte_eth_tx_queue_setup(bond_port_id, 0, ...);
rte_eth_dev_start(bond_port_id);
net_bonding 有很多參數,通過 API 方式創建 vdev 時,rte_eth_bond_create 里只指定了 mode 和 socket_id,其他參數需要通過 API 來設置,例如 bonding 的 member 和 master。
rte_eth_bond_create 內部創建流程
rte_eth_bond_create
rte_vdev_init
insert_vdev // 插入全局列表 vdev_device_list
rte_devargs_insert // 這一步是將參數對象插入全局列表 devargs_list,后續可以處理
vdev_probe_all_drivers // probe 階段會解析設備參數和執行初始化
通過 hotplug api 創建
rte_eal_hotplug_add("vdev", "virtio_user0", "path=/dev/vhost-net");
內部處理流程
rte_eal_hotplug_add
build_devargs
rte_dev_probe
local_dev_probe
rte_devargs_insert
da->bus->scan() // vdev_scan
dev=da->bus->find_device // rte_vdev_find_device
dev->bus->plug(dev) // vdev_plug 內部調 vdev_probe_all_drivers
上面的 vdev_scan 步驟跟 rte_vdev_init 中的 insert_vdev 效果差不多,都是將 device 放入 vdev_device_list, 有什么區別,為什么不統一成一個?
secondary 進程調用 vdev_action VDEV_SCAN_ONE 時會調 insert_vdev
device 介紹常用(通用)參數。
EAL 創建和 API 創建對比
EAL 的好處是可以在不修改程序邏輯的情況下,只修改程序運行時的命令行參數就能為程序提供額外功能支持。例如應用程序原來只處理一個網口的報文,現在通過命令行創建 bonding, 應用程序只處理這個 bonding 接口,實際底下可以通過命令行參數指定多個物理口成員。
API 相比 EAL 參數的好處理是可以動態修改,例如 bonding 設備可以動態添加和刪除 member。
查看 PMD 支持哪些參數
usertools/dpdk-pmdinfo.py dpdk-testpmd | jq '.[] | select(.name=="net_bonding")'