一、准备环境
磨刀不误砍柴工,要想更好的进行dpdk源码分析,需要搭建一套dpdk环境,观察数据包的转发流程。由于个人电脑条件有限,只能在vmware虚拟机环境下搭建dpdk环境。dpdk源码分析系列的所有文章都是基于这套环境来分析。
1、vmware虚拟机上安装ubuntun系统, ubuntu版本为12.04; linux内核版本为3.13.0-32-generic。我的系统架构是32位,可以通过uname -a命令来查看。
root@apelife:/home/xyd/work/bin/dpdk# uname -a
Linux apelife 3.13.0-32-generic #57~precise1-Ubuntu SMP Tue Jul 15 03:50:54 UTC 2014 i686 i686 i386 GNU/Linux
x86_64表示64位系统, i686 i386表示32位系统。i686 只是i386的一个子集
2、dpdk版本为dpdk-1.8.0
3、虚拟机物理内存2G; 4个cpu,单线程; 5个网卡,采用桥接模式,其中4个网卡用于dpdk, 另一个网卡用于与宿主机通信。
物理机与虚拟机网络环境的搭建可以参考ubuntu网络配置(桥接模式, nat模式, host主机模式)这篇文章
二、dpdk环境的安装流程
下载好dpdk代码后,需要将其进行解压。 例如将其解压到/home/xyd/work/bin/dpdk目录
root@apelife:/home/xyd/work/packet# tar -xvf dpdk-1.8.0.tar.xz -C /home/xyd/work/bin/dpdk
dpdk环境搭建流程,包含下面几个过程
1、设置环境变量
2、编译dpdk
3、设置大页内存
4、加载uio模块
5、将网卡绑定到uio驱动
我们可以通过两种方式来搭建dpdk环境,一种方式是使用dpdk根目录下的tools/setup.sh脚本来安装, 另一种方式是通过命令行来安装。脚本安装方式已经帮我们封装好了安装流程,操作比较简单, 但不容易理解dpdk安装的内部细节, 而命令行方式操作麻烦些, 但可以对整个安装流程有个很好的理解。 先来介绍命令行安装方式,之后在介绍脚本安装方式。
三、使用命令行安装
1、设置环境变量
/home/xyd/work/bin/dpdk是我dpdk的安装目录, 在这个目录下添加一个dpdkenv文件,当然文件名可以随便取,我就把这个文件命名为dpdkenv。 在这个文件里面添加下面三个环境变量。
root@apelife:/home/xyd/work/bin/dpdk# cat dpdkenv
export RTE_SDK=`pwd`
export RTE_TARGET=i686-native-linuxapp-gcc
export EXTRA_CFLAGS="-O0 -g"
其中RTE_SDK为dpdk的安装目录; RTE_TARGET为编译器,由于我的操作系统为32位,因此RET_TARGET设置为i686-native-linuxapp-gcc。 如果系统是64位,则设置为x86_64-native-linuxapp-gcc; EXTRA_CFLAGS意思是打开调试选项,开启了调试选项后,才能使用gdb调试。
然后执行source dpdkenv,意思就是不用重启设备,让dpdkenv文件里面设置的所有命令立即生效。
root@apelife:/home/xyd/work/bin/dpdk# source dpdkenv
为什么要添加dpdkenv这个文件名,搞这么复杂。 那是因为每次新打开一个终端时,之前设置的环境变量会失效,需要重新设置环境变量。因此把这个设置环境需要的命令放到文件中来,每次打开终端时,只需要执行source dpdkenv就好了,操作简单。
2、编译dpdk
先在dpdk安装目录下make clean, 清除一些残留信息。 由于我的系统是32位的,因此使用make install T=i686-native-linuxapp-gcc开始编译。如果是64位操作系统,则使用make install T=x86_64-native-linuxapp-gcc
root@apelife:/home/xyd/work/bin/dpdk# make clean
root@apelife:/home/xyd/work/bin/dpdk# make install T=i686-native-linuxapp-gcc
如果dpdk编译过程中出现了 cc1: out of memory allocating 33554432 bytes after a total of 19070976 bytes这种类型的错误,那就把虚拟机重启下吧。编译成功会提示Build complete
3、设置大页内存
系统默认每个大页的大小是2M, 在这里设置256个大页,也就是大页内存一共512M。 大页内存尽量设置大一些,通常设置为512---1024M之间, 否则后续执行测试例子的时候有可能会报大页内存不足错误信息。 设置好大页内存后,挂载大页系统
root@apelife:/home/xyd/work/bin/dpdk# echo 256 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
root@apelife:/home/xyd/work/bin/dpdk# mkdir /mnt/huge
root@apelife:/home/xyd/work/bin/dpdk# mount -t hugetlbfs nodev /mnt/huge
之后可以在/proc/meminfo文件中看到大页内存信息
root@apelife:/home/xyd/work/bin/dpdk# cat /proc/meminfo | grep Huge
4、安装igb_uio驱动
编译好dpdk后,会在dpdk的安装目录下生成uio驱动: i686-native-linuxapp-gcc/kmod/igb_uio.ko,需要把这个驱动加载到内核
root@apelife:/home/xyd/work/bin/dpdk# modprobe uio
root@apelife:/home/xyd/work/bin/dpdk# insmod i686-native-linuxapp-gcc/kmod/igb_uio.ko
记得一定要先执行modprobe uio, 否则会找不到驱动。当然如果需要删除驱动,则执行rmmod igb_uio
5、将物理网卡与igb_uio驱动进行绑定
使用dpdk自带的脚本,查看网卡信息。从中可以看出我有5个千兆网卡, 每个网卡使用e1000驱动。
root@apelife:/home/xyd/work/bin/dpdk# ./tools/dpdk_nic_bind.py --status
Network devices using DPDK-compatible driver
============================================
Network devices using kernel driver
===================================
0000:02:00.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth0 drv=e1000 unused=igb_uio *Active*
0000:02:04.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth1 drv=e1000 unused=igb_uio *Active*
0000:02:05.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth2 drv=e1000 unused=igb_uio *Active*
0000:02:06.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth3 drv=e1000 unused=igb_uio *Active*
0000:02:07.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth4 drv=e1000 unused=igb_uio *Active*
Other network devices
=====================
现在想对eth0, eth1, eth2, eth3共4个网卡解除与e1000驱动绑定,从而与igb_uio驱动进行绑定。-b选项表示bind绑定的意思
root@apelife:/home/xyd/work/bin/dpdk# ./tools/dpdk_nic_bind.py -b igb_uio 0000:02:00.0
root@apelife:/home/xyd/work/bin/dpdk# ./tools/dpdk_nic_bind.py -b igb_uio 0000:02:04.0
root@apelife:/home/xyd/work/bin/dpdk# ./tools/dpdk_nic_bind.py -b igb_uio 0000:02:05.0
root@apelife:/home/xyd/work/bin/dpdk# ./tools/dpdk_nic_bind.py -b igb_uio 0000:02:06.0
再来查看下网卡信息, 可以看出eth0, eth1, eth2, eth4这四个网卡都与igb_uio驱动进行了绑定
root@apelife:/home/xyd/work/bin/dpdk# ./tools/dpdk_nic_bind.py --status
Network devices using DPDK-compatible driver
============================================
0000:02:00.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
0000:02:04.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
0000:02:05.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
0000:02:06.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
Network devices using kernel driver
===================================
0000:02:07.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth4 drv=e1000 unused=igb_uio *Active*
Other network devices
=====================
如果绑定过程出现这样的错误Routing table indicates that interface 0000:02:00.0 is active. Not modifying。则说明网卡已经启用了,需要把这个网卡down掉, 例如ifconfig eth0 down
当然如果网卡不再需要与igb_uio绑定,从而继续与e1000绑定,则可以使用-u选项解除绑定,然后再重新与e1000重新绑定。例如
./tools/dpdk_nic_bind.py -u 0000:02:00.0 解除网卡与igb_uio绑定
./tools/dpdk_nic_bind.py -b e1000 0000:02:00.0 网卡重新与e1000绑定
需要注意的是lspci查看网卡信息,如果发现不是intel的e1000网卡,而是AMD的网卡,则关闭虚拟机,在虚拟机安装目录下找到vmx后缀的文件,例如ubuntu-new.vmx,在这个文件末尾添加下面的内容。 在这里我有5个网卡,因此在这个文件末尾加了五条信息。修改完这个文件后重新开启虚拟机。
ethernet0.virtualDev = "e1000"
ethernet1.virtualDev = "e1000"
ethernet2.virtualDev = "e1000"
ethernet3.virtualDev = "e1000"
ethernet4.virtualDev = "e1000"
到此为止,dpdk环境已经搭建成功了,恭喜你。下面来执行下dpdk的测试程序,感受下。
四、dpdk测试程序的执行
1、测试下pmd驱动是否成功。由于我有4个cpu, 因此对应的掩码为0xf; -n 2表示内存通道为2
root@apelife:/home/xyd/work/bin/dpdk/i686-native-linuxapp-gcc/app# ./testpmd -c 0xf -n 2 -- -i
EAL: Cannot read numa node link for lcore 0 - using physical package id instead
EAL: Detected lcore 0 as core 0 on socket 0
EAL: Cannot read numa node link for lcore 1 - using physical package id instead
EAL: Detected lcore 1 as core 0 on socket 2
EAL: Cannot read numa node link for lcore 2 - using physical package id instead
EAL: Detected lcore 2 as core 0 on socket 4
EAL: Cannot read numa node link for lcore 3 - using physical package id instead
EAL: Detected lcore 3 as core 0 on socket 6
EAL: Support maximum 128 logical core(s) by configuration.
...................................
...................................忽略剩余过程
Port 0 Link Up - speed 1000 Mbps - full-duplex
Port 1 Link Up - speed 1000 Mbps - full-duplex
Port 2 Link Up - speed 1000 Mbps - full-duplex
Port 3 Link Up - speed 1000 Mbps - full-duplex
Done
testpmd>quit
如果执行testpmd过程中出现EAL: Error reading from file descriptor 13: Input/output error错误信息,不要担心,这个只在虚拟机环境下才会出现,物理机是不会出现这个错误的。 需要在dpdk/lib/librte_eal/linuxapp/igb_uio/igb_uio.c文件中将if (pci_intx_mask_supported(dev))修改为
if (pci_intx_mask_supported(dev) || true), 然后重新编译dpdk
509 case RTE_INTR_MODE_LEGACY:
510 if (pci_intx_mask_supported(dev) || true) {
511 dev_dbg(&dev->dev, "using INTX");
512 udev->info.irq_flags = IRQF_SHARED;
513 udev->info.irq = dev->irq;
514 udev->mode = RTE_INTR_MODE_LEGACY;
515 break;
516 }
2、测试下hello程序
在./examples/目录下有许多例子,其中有一个hellowoeld路径。进入helloworld路径直接make之后会在当前helloworld路径下生成build文件夹。 build文件夹中存在helloworld可执行程序。由于我有4个cpu, 因此对应的掩码为0xf; -n 2表示内存通道为2
root@apelife:/home/xyd/work/bin/dpdk/examples/helloworld/build# ./helloworld -c 0x0f -n 2 -- -i
EAL: Cannot read numa node link for lcore 0 - using physical package id instead
EAL: Detected lcore 0 as core 0 on socket 0
EAL: Cannot read numa node link for lcore 1 - using physical package id instead
EAL: Detected lcore 1 as core 0 on socket 2
EAL: Cannot read numa node link for lcore 2 - using physical package id instead
EAL: Detected lcore 2 as core 0 on socket 4
EAL: Cannot read numa node link for lcore 3 - using physical package id instead
EAL: Detected lcore 3 as core 0 on socket 6
EAL: Support maximum 128 logical core(s) by configurati
.............................
.............................忽略剩余过程
hello from core 1
hello from core 2
hello from core 3
hello from core 0
3、测试下l2fwd二层转换的例子
由于我有4个cpu, 因此对应的掩码为0xf; -n 2表示内存通道为2; -q表示每个cpu有两个接收队列,两个发送队列。 -p 用于指定网卡, 在这里我的eth0,eth1,eth2,eth3共四个网卡绑定了igb_uio驱动,因此掩码为0xf
root@apelife:/home/xyd/work/bin/dpdk/examples/l2fwd/build# ./l2fwd -c 0xf -n 2 -- -q 2 -p 0xf
Port statistics ====================================
Statistics for port 0 ------------------------------
Packets sent: 0
Packets received: 0
Packets dropped: 0
Statistics for port 1 ------------------------------
Packets sent: 0
Packets received: 0
Packets dropped: 0
Statistics for port 2 ------------------------------
Packets sent: 0
Packets received: 0
Packets dropped: 0
Statistics for port 3 ------------------------------
Packets sent: 0
Packets received: 0
Packets dropped: 0
Aggregate statistics ===============================
Total packets sent: 0
Total packets received: 0
Total packets dropped: 0
====================================================
其它例子的执行也是相同的,就不再举例了。在dpdk下有个example目录下有很多实例, 需要调试哪个则进入对应目录执行make就好了。至于每个例子要带什么参数,那就分析源码或者查找文档吧。
五、使用dpdk脚本方式安装
dpdk自带了一个安装脚本setup.sh, 路径为dpdk根目录下的toos目录下。使用这个脚本可以完成一系列安装过程。在学会了如何使用命令行安装后,后续为了方便,还是使用脚本方式来搭建dpdk环境。来看下这个脚本有什么功能。
root@apelife:/home/xyd/work/bin/dpdk# ./tools/setup.sh
------------------------------------------------------------------------------
RTE_SDK exported as /home/xyd/work/bin/dpdk
------------------------------------------------------------------------------
----------------------------------------------------------
Step 1: Select the DPDK environment to build
----------------------------------------------------------
[1] i686-native-linuxapp-gcc
[2] i686-native-linuxapp-icc
[3] ppc_64-power8-linuxapp-gcc
[4] x86_64-ivshmem-linuxapp-gcc
[5] x86_64-ivshmem-linuxapp-icc
[6] x86_64-native-bsdapp-clang
[7] x86_64-native-bsdapp-gcc
[8] x86_64-native-linuxapp-clang
[9] x86_64-native-linuxapp-gcc
[10] x86_64-native-linuxapp-icc
----------------------------------------------------------
Step 2: Setup linuxapp environment
----------------------------------------------------------
[11] Insert IGB UIO module
[12] Insert VFIO module
[13] Insert KNI module
[14] Setup hugepage mappings for non-NUMA systems
[15] Setup hugepage mappings for NUMA systems
[16] Display current Ethernet device settings
[17] Bind Ethernet device to IGB UIO module
[18] Bind Ethernet device to VFIO module
[19] Setup VFIO permissions
----------------------------------------------------------
Step 3: Run test application for linuxapp environment
----------------------------------------------------------
[20] Run test application ($RTE_TARGET/app/test)
[21] Run testpmd application in interactive mode ($RTE_TARGET/app/testpmd)
----------------------------------------------------------
Step 4: Other tools
----------------------------------------------------------
[22] List hugepage info from /proc/meminfo
----------------------------------------------------------
Step 5: Uninstall and system cleanup
----------------------------------------------------------
[23] Uninstall all targets
[24] Unbind NICs from IGB UIO driver
[25] Remove IGB UIO module
[26] Remove VFIO module
[27] Remove KNI module
[28] Remove hugepage mappings
[29] Exit Script
Option:
1、环境变量的设置
这个过程和上面使用命令行方式下环境变量的设置是一模一样的,可以参考上面的设置。
2、编译dpdk
由于我的系统是32位,因此选择[1] i686-native-linuxapp-gcc; 如果是64位系统,选择[9] x86_64-native-linuxapp-gcc。 编译完成后会出现Build complete
3、安装igb_uio驱动
选择[11] Insert IGB UIO module, 使得内核加载这个uio驱动
4、设置大页内存
选择[14] Setup hugepage mappings for non-NUMA systems开始设置大页内存。系统默认每个大页的大小是2M, 在这里设置256个大页,也就是大页内存一共512M。 大页内存尽量设置大一些,通常设置为512---1024M之间, 否则后续执行测试例子的时候有可能会报大页内存不足错误信息。 设置后大页内存后,挂载大页系统。
Option: 14
Removing currently reserved hugepages
.echo_tmp: 2: .echo_tmp: cannot create /sys/devices/system/node/node?/hugepages/hugepages-2048kB/nr_hugepages: Directory nonexistent
Unmounting /mnt/huge and removing directory
Input the number of 2MB pages
Example: to have 128MB of hugepages available, enter '64' to
reserve 64 * 2MB pages
Number of pages: 256
Reserving hugepages
Creating /mnt/huge and mounting as hugetlbfs
可以选择[22] List hugepage info from /proc/meminfo查看下大页内存的信息
Option: 22
AnonHugePages: 28672 kB
HugePages_Total: 256
HugePages_Free: 256
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
5、将网卡与uio驱动进行绑定
选择[17] Bind Ethernet device to IGB UIO module,将物理网卡与uio驱动进行绑定操作。现在想对eth0, eth1, eth2, eth3共4个网卡解除与e1000驱动绑定,从而与igb_uio驱动进行绑定。 在绑定操作之前需要将eth0, eth1, eth2, eth3禁用掉,例如ifconfig eth0 down, 否则会报Routing table indicates that interface 0000:02:00.0 is active. Not modifying错误
Enter PCI address of device to bind to IGB UIO driver: 0000:02:00.0
OK
将eth0, eth1, eth2, eth3绑定好后,选择[16] Display current Ethernet device settings可以看到网卡的绑定信息。从中可以看出这四个网卡与igb_uio驱动进行了绑定。
Option: 16
Network devices using DPDK-compatible driver
============================================
0000:02:00.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
0000:02:04.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
0000:02:05.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
0000:02:06.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
Network devices using kernel driver
===================================
0000:02:07.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth4 drv=e1000 unused=igb_uio *Active*
Other network devices
=====================
此时通过脚本方式dpdk环境也搭建好了,下面来测试下pmd的例子
选择[21] Run testpmd application in interactive mode ($RTE_TARGET/app/testpmd)
Option: 21
Enter hex bitmask of cores to execute testpmd app on
Example: to execute app on cores 0 to 7, enter 0xff
bitmask: 0xf
Port 0 Link Up - speed 1000 Mbps - full-duplex
Port 1 Link Up - speed 1000 Mbps - full-duplex
Port 2 Link Up - speed 1000 Mbps - full-duplex
Port 3 Link Up - speed 1000 Mbps - full-duplex
Done
输入start开始发包,输入stop停止发包
testpmd> start
io packet forwarding - CRC stripping disabled - packets/burst=32
nb forwarding cores=1 - nb forwarding ports=4
RX queues=1 - RX desc=128 - RX free threshold=32
RX threshold registers: pthresh=8 hthresh=8 wthresh=0
TX queues=1 - TX desc=512 - TX free threshold=0
TX threshold registers: pthresh=32 hthresh=0 wthresh=0
TX RS bit threshold=0 - TXQ flags=0x0
testpmd>
testpmd> stop
Telling cores to stop...
Waiting for lcores to finish...
---------------------- Forward statistics for port 0 ----------------------
RX-packets: 597120 RX-dropped: 0 RX-total: 597120
TX-packets: 597152 TX-dropped: 0 TX-total: 597152
----------------------------------------------------------------------------
---------------------- Forward statistics for port 1 ----------------------
RX-packets: 597120 RX-dropped: 0 RX-total: 597120
TX-packets: 597152 TX-dropped: 0 TX-total: 597152
----------------------------------------------------------------------------
---------------------- Forward statistics for port 2 ----------------------
RX-packets: 597120 RX-dropped: 0 RX-total: 597120
TX-packets: 597152 TX-dropped: 0 TX-total: 597152
----------------------------------------------------------------------------
---------------------- Forward statistics for port 3 ----------------------
RX-packets: 597120 RX-dropped: 0 RX-total: 597120
TX-packets: 597152 TX-dropped: 0 TX-total: 597152
----------------------------------------------------------------------------
+++++++++++++++ Accumulated forward statistics for all ports+++++++++++++++
RX-packets: 2388480 RX-dropped: 0 RX-total: 2388480
TX-packets: 2388608 TX-dropped: 0 TX-total: 2388608
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Done.
testpmd> quit
输入start开始发包测试的时候,如果出现这个错误Warning! Cannot handle an odd number of ports with the current port topology. Configuration must be changed to have an even number of ports, or relaunch application with --port-topology=chained那是因为网卡个数不能是奇数,而应该是偶数个。
此时top查看cpu信息,发现testpmd占用率高达100%,设备非常卡,这是正常的。cpu全速运转,处理报文的转发操作。
六、gdb调试dpdk源码
还记得上面的那个dpdkenv,在里面设置了环境变量export EXTRA_CFLAGS="-O0 -g"。 编译好了dpdk后,就可以使用gdb调试了。但在实际分析源码过程中,可能会多次的修改源码,例如添加日志来加深对代码的理解。 那修改了源码后如何重新编译源码以便能够打印出日志与gdb调试呢? 有两种方式可以实现, 任意选择一种就好了,下面分别看下这两种方式。
1. 方式1: 直接编译i686-native-linuxapp-gcc
如果修改了lib目录下的源码,可以在dpdk根目录里面的i686-native-linuxapp-gcc目录下直接make,然后在想要调试的例子: 例如example/helloworld目录下执行make,接下里就可以使用gdb设置断点调试。
root@apelife:/home/xyd/work/bin/dpdk/i686-native-linuxapp-gcc# make
root@apelife:/home/xyd/work/bin/dpdk/examples/helloworld# make
gdb设置断点开始调试,其中r 后面的参数为hellworld程序需要的参数。
root@apelife:/home/xyd/work/bin/dpdk/examples/helloworld/build# gdb helloworld
(gdb) b rte_eal_init
Breakpoint 1 at 0x80e85b2: file /home/xyd/work/bin/dpdk/lib/librte_eal/linuxapp/eal/eal.c, line 703.
(gdb) r -c 0xf -n 2 -- -i
Breakpoint 1, rte_eal_init (argc=7, argv=0xbffff694) at /home/xyd/work/bin/dpdk/lib/librte_eal/linuxapp/eal/eal.c:703
703 struct shared_driver *solib = NULL;
(gdb) c
通过这种方式,不需要重新加载uio; 也不需要重新绑定网卡到uio驱动;也不需重新设置大页内存
2、方式2: 重新安装dpdk
直接运行dpdk/tools/setup.sh脚本,输入选项1进行重新编译dpdk
root@apelife:/home/xyd/work/bin/dpdk# ./tools/setup.sh
------------------------------------------------------------------------------
RTE_SDK exported as /home/xyd/work/bin/dpdk
------------------------------------------------------------------------------
----------------------------------------------------------
Step 1: Select the DPDK environment to build
----------------------------------------------------------
[1] i686-native-linuxapp-gcc
Option: 1
在想要调试的例子: 例如example/helloworld目录下执行make, 接下里就可以使用gdb设置断点调试。
通过这种方式,不需要重新加载uio; 也不需要重新绑定网卡到uio驱动;也不需重新设置大页内存。 好了, dpdk编译环境已经搭建好了,接下来一系列文章将会对源码进行分析