DPDK 编译安装与使用

DPDK 在 CentOS 7 上的编译使用记录

0x00 准备

1. BIOS 设置

如果 UEFI secure boot 被启用了, Linux 内核可能会禁止系统使用 UIO, 请关闭相应设置或者使用 vfio-pci 内核模块, 详见.

2. 系统环境

  • Linux kernel >= 3.10
  • Linux kernel headers and modules(etc. kernel-devel)
  • gcc 4.9+ / clang 3.4+
  • Python 2.7+ / 3.5+
  • Meson 0.47.1+
  • ninjia
  • Library for handling NUMA(Non Uniform Memory Access)
    • numactl-devel
    • libnuma-dev
1
2
# centos 7
yum install -y kernel-devel gcc python make numactl-devel libpcap-devel pciutils

2.1 查看和升级 Linux 内核(CentOS 7)

DPDK 对 Linux 内核有要求, CentOS 默认的内核版本为 3.10, 低版本 DPDK 无法使用.

查看当前内核版本:

1
2
3
cat /boot/grub2/grub.cfg | grep menuentry 
# 或者
uname -r

推荐使用 elrepo 编译好的 CentOS 内核.

安装 elrepo:

1
2
3
4
rpm -Uvh https://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
# 可以同时设置好镜像加速 推荐使用清华源
# baseurl=https://mirrors.tuna.tsinghua.edu.cn/elrepo/elrepo/el7/$basearch/
# baseurl=https://mirrors.tuna.tsinghua.edu.cn/elrepo/kernel/el7/$basearch/

避免冲突,卸载旧版的一些包

1
yum remove -y kernel-headers kernel-tools kernel-tools-libs

安装lt版内核:

1
yum --enablerepo=elrepo-kernel install kernel-lt kernel-lt-devel kernel-lt-headers

查看当前可以使用的内核启动项:

1
2
3
4
# bios
awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg
# uefi
awk -F\' '$1=="menuentry " {print i++ " : " $2}' /boot//efi/EFI/centos/grub.cfg

设置默认启动内核:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
grub2-set-default 0
# 或者
grub2-set-default "CentOS Linux (xxxx) 7 (Core)"

# 备份
# 如果是 BIOS
cp /boot/grub2/grub.cfg /boot/grub2/grub.cfg.bak
# 如果是 UEFI
cp /boot/efi/EFI/centos/grub.cfg /boot/efi/EFI/centosgrub.cfg.bak

# 重新生成配置 BIOS
grub2-mkconfig -o /boot/grub2/grub.cfg
# 重新生成配置 UEFI
grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg

验证:

1
2
3
grub2-editenv list
# 或者
uname -r

2.2 支持的网卡设备

参考官网设备列表

0x01 编译

1. 下载 DPDK 源文件

官网下载源文件,解压。本文基于 19.11。

  • lib: DPDK 库文件
  • drivers: DPDK 轮询驱动源文件
  • app: DPDK 应用程序 (自动测试)源文件
  • examples: DPDK 应用例程
  • config, buildtools, mk: 框架相关的 makefile、脚本及配置文件

2. 修改源文件(非必须)

可能可以提高性能.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// drivers/net/ixgbe/base/ixgbe_common.c
/**
* ixgbe_enable_relaxed_ordering_gen2 - Enable relaxed ordering
* @hw: pointer to hardware structure
*
**/
void ixgbe_enable_relaxed_ordering_gen2(struct ixgbe_hw *hw)
{
u32 regval;
u32 i;

DEBUGFUNC("ixgbe_enable_relaxed_ordering_gen2");

/* Enable relaxed ordering */
for (i = 0; i < hw->mac.max_tx_queues; i++) {
regval = IXGBE_READ_REG(hw, IXGBE_DCA_TXCTRL_82599(i));
#if 0
regval |= IXGBE_DCA_TXCTRL_DESC_WRO_EN;
#else
regval |= IXGBE_DCA_TXCTRL_DESC_WRO_EN |
IXGBE_DCA_TXCTRL_DESC_RRO_EN |
IXGBE_DCA_TXCTRL_DATA_RRO_EN;
IXGBE_WRITE_REG(hw, IXGBE_DCA_TXCTRL_82599(i), regval);
#endif
}

for (i = 0; i < hw->mac.max_rx_queues; i++) {
regval = IXGBE_READ_REG(hw, IXGBE_DCA_RXCTRL(i));
#if 0
regval |= IXGBE_DCA_RXCTRL_DATA_WRO_EN |
IXGBE_DCA_RXCTRL_HEAD_WRO_EN;
#else
regval |= IXGBE_DCA_RXCTRL_DATA_WRO_EN |
IXGBE_DCA_RXCTRL_HEAD_WRO_EN |
IXGBE_DCA_TXCTRL_DESC_RRO_EN;
IXGBE_WRITE_REG(hw, IXGBE_DCA_RXCTRL(i), regval);
#endif
}

}

/**
* ixgbe_start_hw_gen2 - Init sequence for common device family
* @hw: pointer to hw structure
*
* Performs the init sequence common to the second generation
* of 10 GbE devices.
* Devices in the second generation:
* 82599
* X540
**/
s32 ixgbe_start_hw_gen2(struct ixgbe_hw *hw)
{
u32 i;
u32 regval;

/* Clear the rate limiters */
for (i = 0; i < hw->mac.max_tx_queues; i++) {
IXGBE_WRITE_REG(hw, IXGBE_RTTDQSEL, i);
IXGBE_WRITE_REG(hw, IXGBE_RTTBCNRC, 0);
}
IXGBE_WRITE_FLUSH(hw);

#if 0
/* Disable relaxed ordering */
for (i = 0; i < hw->mac.max_tx_queues; i++) {
regval = IXGBE_READ_REG(hw, IXGBE_DCA_TXCTRL_82599(i));
regval &= ~IXGBE_DCA_TXCTRL_DESC_WRO_EN;
IXGBE_WRITE_REG(hw, IXGBE_DCA_TXCTRL_82599(i), regval);
}

for (i = 0; i < hw->mac.max_rx_queues; i++) {
regval = IXGBE_READ_REG(hw, IXGBE_DCA_RXCTRL(i));
regval &= ~(IXGBE_DCA_RXCTRL_DATA_WRO_EN |
IXGBE_DCA_RXCTRL_HEAD_WRO_EN);
IXGBE_WRITE_REG(hw, IXGBE_DCA_RXCTRL(i), regval);
}
#else
ixgbe_enable_relaxed_ordering(hw);
#endif

return IXGBE_SUCCESS;
}

// kernel/linux/kni/compat.h 兼容 aarch64 的 centos 7

/*
* RHEL has two different version with different kernel version:
* 3.10 is for AMD, Intel, IBM POWER7 and POWER8;
* 4.14 is for ARM and IBM POWER9
*/ /*
#if (defined(RHEL_RELEASE_CODE) && \
(RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(7, 5)) && \
(RHEL_RELEASE_CODE < RHEL_RELEASE_VERSION(8, 0)) && \
(LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0)))
#define ndo_change_mtu ndo_change_mtu_rh74
#endif
*/

2. 配置与编译

解压并进入 DPDK 源码根目录

1
cd dpdk-xxx/

设置环境变量

1
2
# 设置 DPDK SDK 所在目录, 设置编译输出目录
export RTE_SDK=$(pwd) && export RTE_TARGET=build

2.1 手动配置

DPDK目标文件的格式为:

ARCH-MACHINE-EXECENV-TOOLCHAIN

其中:

  • ARCH 可以是: i686, x86_64, ppc_64, arm64
  • MACHINE 可以是: native, power8, armv8a
  • EXECENV 可以是: linux, freebsd
  • TOOLCHAIN 可以是: gcc, icc, clang

设置好编译参数并编译 DPDK 库

1
2
3
4
5
6
7
# 直接编译
make install T=x86_64-native-linux-gcc [DESTDIR=/usr/local/dpdk] -j 16

# 设置编译器和环境再进行编译
make config T=x86_64-native-linux-gcc; make -j 16
# make config T=x86_64-native-linuxapp-gcc; make -j 16
# make config T=x86_64-native-linux-clang; make -j 16

2.2 使用 meson 和 ninjia

通过 meson 和 ninjia 配置 DPDK 编译环境:

1
2
3
4
5
meson <options> build
cd build
ninja
ninja install
ldconfig

设置好编译参数并编译 DPDK 库

1
2
# 直接编译
make install T=x86_64-native-linux-gcc [DESTDIR=/usr/local/dpdk] -j 16

2.3 其他选项

编译成动态库:

1
make CONFIG_RTE_BUILD_SHARED_LIB=y ...

20.02开始默认不编译 igb_uio, 如有需要:

1
make CONFIG_RTE_EAL_IGB_UIO=y ...

添加 debug 选项:

1
make EXTRA_CFLAGS="-O0 -g"

2.4 使用脚本编译和配置

为了能清晰的进行编译和配置, 第一次使用时推荐使用 usertools 下的 dpdk-setup.sh 进行编译和配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
cd usertools; ./dpdk-setup.sh
============================
------------------------------------------------------------------------------
RTE_SDK exported as /root/dpdk-stable-18.11.2
------------------------------------------------------------------------------
----------------------------------------------------------
Step 1: Select the DPDK environment to build
----------------------------------------------------------
[1] arm64-armv8a-linuxapp-clang
[2] arm64-armv8a-linuxapp-gcc
[3] arm64-dpaa2-linuxapp-gcc
[4] arm64-dpaa-linuxapp-gcc
[5] arm64-stingray-linuxapp-gcc
[6] arm64-thunderx-linuxapp-gcc
[7] arm64-xgene1-linuxapp-gcc
[8] arm-armv7a-linuxapp-gcc
[9] i686-native-linuxapp-gcc
[10] i686-native-linuxapp-icc
[11] ppc_64-power8-linuxapp-gcc
[12] x86_64-native-bsdapp-clang
[13] x86_64-native-bsdapp-gcc
[14] x86_64-native-linuxapp-clang
[15] x86_64-native-linuxapp-gcc
[16] x86_64-native-linuxapp-icc
[17] x86_x32-native-linuxapp-gcc

----------------------------------------------------------
Step 2: Setup linuxapp environment
----------------------------------------------------------
[18] Insert IGB UIO module
[19] Insert VFIO module
[20] Insert KNI module
[21] Setup hugepage mappings for non-NUMA systems
[22] Setup hugepage mappings for NUMA systems
[23] Display current Ethernet/Crypto device settings
[24] Bind Ethernet/Crypto device to IGB UIO module
[25] Bind Ethernet/Crypto device to VFIO module
[26] Setup VFIO permissions

----------------------------------------------------------
Step 3: Run test application for linuxapp environment
----------------------------------------------------------
[27] Run test application ($RTE_TARGET/app/test)
[28] Run testpmd application in interactive mode ($RTE_TARGET/app/testpmd)

----------------------------------------------------------
Step 4: Other tools
----------------------------------------------------------
[29] List hugepage info from /proc/meminfo

----------------------------------------------------------
Step 5: Uninstall and system cleanup
----------------------------------------------------------
[30] Unbind devices from IGB UIO or VFIO driver
[31] Remove IGB UIO module
[32] Remove VFIO module
[33] Remove KNI module
[34] Remove hugepage mappings

[35] Exit Script

Option:
  1. 按需进行编译, 如在 x64 的 Linux 下使用 gcc 进行编译则选择15, 要和环境变量的一样.

  2. 挂载 uio 内核模块, 可选择 igb_uio 或者 vfio. 推荐使用 vfio, 需要开启 iommu.

  3. 配置大页内存, 至少 512, 按照推荐的 64 会报错.

  4. 将网卡挂载到 igb_uio / vfio 上.

0x02 使用 DPDK

1. 加载驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 加载 igb_uio
# 查看系统是否加载 uio 模块
lsmod | grep uio
# 如果没有加载需要手动挂载该模块
modprobe uio
# 把编译好的 igb_uio 加载进内核
insmod /path/to/dpdk/build/kmod/igb_uio.ko
# 加载 kni
insmod /path/to/dpdk/build/kmod/rte_kni.ko

# 加载 vfio
modprobe vfio-pci

# 卸载驱动
rmmod igb_uio
rmmod vfio-pci
rmmod vfio

20.02 开始默认使用的是 uio_pci_generic, 它不支持 virtual function, 因此会和 sriov 产生一定 的冲突, 需要换回 igb_uio / vfio, 而且 uio_pci_generic 使用的时候需要把 intel_iommu / amd_iommu 关闭.

2. 绑定网卡驱动

1
2
3
4
5
6
7
8
9
10
11
12
# 首先保证网卡非活动
ip link set ethx down

# 查看当前网卡信息
./usertools/dpdk-devbind.py --status

# 使用dpdk脚本进行绑定
./usertools/dpdk-devbind.py --bind=igb_uio 00:09.0 ...
./usertools/dpdk-devbind.py --bind=igb_uio eth1 ...

# 还原
./usertools/dpdk-devbind.py --bind=ixgbe 00:09.0 ...

3. 设置大页内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 查看numa支持 支持的机器会显示 node, 需要安装 numactl
numastat

# 让系统自动去分配大页内存
echo 2048 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

# 对于有 numa 的机器, 假设有两个 node
# 清空大页内存只需要把数字改 0 即可
echo 1024 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
echo 1024 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages

# 挂载大页内存
mkdir /mnt/huge
mount -t hugetlbfs nodev /mnt/huge
# mount -t hugetlbfs none /mnt/huge -o pagesize=2MB
# mount -t hugetlbfs none /mnt/huge -o pagesize=1GB

# 可以把大页内存加入 /etc/fstab 中
echo "nodev /mnt/huge hugetlbfs defaults 0 0" >> /etc/fstab
# 如果要使用 1GB 的页
echo "nodev /mnt/huge_1GB hugetlbfs pagesize=1GB 0 0" >> /etc/fstab

4. 设置库路径

如果使用了动态库进行编译, 需要设置动态库环境查找环境:

1
2
echo "/path/to/dpdk/lib" >> /etc/ld.conf.d/dpdk.conf
ldconfig

5. 使用 example

进入 example 目录编译

1
2
# 之前已经设置环境变量 RTE_SDK, make 的时候依赖这个环境变量
cd example && make

使用 helloworld 进行测试

1
2
./helloworld/x86_64-native-linuxapp-gcc/helloworld -c f
# -c 指 coremask 十六进制的数代表使用几号 cpu 核心, f 代表 1111 指使用 0-3 号共 4 颗 cpu.

如果出现 EAL: Invalid NUMA socket, default to 0, 可以修改对应网卡的 numa 设置:

1
vim /sys/bus/pci/devices/[device_pci_id]/numa_node

0xff 其他

1. EAL 部分参数说明

  • -c 以十六进制形式指定需要运行的核,转成二进制后,对应的位数为1表示运行在相应的核上。

    -c 0x5 即二进制 0101 表示运行在0和2号cpu上

  • -l 指定运行在哪几个核上

    -l 0-2,4 即运行在id为0,1,2,4的cpu上

  • --lcores 设置lcore集运行在指定的cpu集上,格式如下

    1
    --lcores='<lcore_set>[@cpu_set][,<lcore_set>[@cpu_set],...]'

    如果没有指定cpu集,那么cpu号和lcore号一样

    --lcores=’1,2@(5-7),(3-5)@(0,2),(0,6),7-8’

    这会开启9个EAL的线程

    lcore 0 在(0,6)中被设置,因此会运行在id为0和id为6的cpu上(0x41)

    lcore 1 在1中被设置,因此会运行在id为1的cpu上(0x2)

    lcore 2 在2@(5-7)中被设置,因此会运行在id为5到id为7的cpu上(0xe0)

    lcore 3-5 在(3-5)@(0,2)中被设置,因此会运行在id为0和id为2的cpu上(0x5)

    lcore 6 在(0,6)中被设置,因此会运行在id为0和id为6的cpu上(0x41)

    lcore 7 在7-8中被设置,因此会运行在id为7的cpu上(0x80)

    lcore 8 在7-8中被设置,因此会运行在id为8的cpu上(0x100)

  • -n 内存通道数

2. virtio_user 接口使用

启用 virtio_user 接口代替 kni 与内核通信, 添加参数:

1
--vdev=virtio_user<num>,path=/dev/vhost-net<,mac=xx:xx:xx:xx:xx:xx>

virtio 的 mac 需要和业务口的 mac 一样, 否则无法与外界通信.

3. 单机启动多 DPDK 应用

需要进行数据隔离, 启动时添加参数:

1
--file-prefix=<container_prefix_name>

4. 网卡的手动内核绑定

网卡的手动内核绑定

需要使用支持多队列的网卡, 可以用以下命令查看当前网卡是否支持:

1
2
3
4
➜ ~ cat /proc/interrupts | grep enp4s0
40: 10 0 0 0 0 0 IR-PCI-MSI-edge enp4s0-TxRx-0
41: 8 0 0 0 0 0 IR-PCI-MSI-edge enp4s0-TxRx-1
42: 24 0 0 0 0 0 IR-PCI-MSI-edge enp4s0

最前面的是中断号, 使用以下命令查看绑定状态:

1
2
3
4
5
6
7
8
# 查看当前绑定状态 绑定在
➜ ~ cat /proc/irq/40/smp_affinity
10

# 绑定到 3 号 cpu 上(按照 0, 1, 2, 4, 8, 10, 11, 12, 14, 18 ...)
➜ ~ echo 4 > /proc/irq/40/smp_affinity
➜ ~ cat /proc/irq/40/smp_affinity
04