http://www.dpdk.org/
Intel开源的x86平台上的报文处理套件。
要求
-
Kernel >= 2.6.33
-
glibc >= 2.7
-
Kernel需要支持UIO、HUGETLBFS、PROC_PAGE_MONITOR
-
如果使用HPET支持, Kernel需要支持HPET和HPET_MMAP
-
如果使用HPET timer和电源管理(power management)功能, 需要设置BIOS
HUGETLBFS是指通过使用大的内存页, 减少TLB条目的数量, 提高TLB缓存的命中率, 提高了内存地址转换的效率,从而提高了内存的操作效率。
dpdk中用来存放报文的内存使用的就是HUGEPAGE
安装
下载源码解压。按照doc/build-sdk-quick.txt指示编译,安装。
make config T=x86_64-default-linuxapp-gcc DESTDIR=/opt/dpdk/
make //编译过程中发现有个别结构体重复定义,注释掉就可以了
make install T=x86_64-default-linuxapp-gcc DESTDIR=/opt/dpdk/
在另一个目录中编译:
make config T=x86_64-default-linuxapp-gcc O=my_sdk_build_dir
然后在my_sdk_build_dir中直接make, 就在新的目录中完成了dpdk的编译
安装后在安装目录下生成两个目录:
mk //dpdk开发框架的的全套makefile
x86_64-default-linuxapp-gcc //dpdk的头文件和对应架构下的链接库
加载驱动:
到安装目录下找到kmod/igb_uio.ko kmod/rte_kni.ko
modprobe uio
insmod igb_uio.ko
运行前准备
在运行dpdk应用时需要准备的事项。
设置hugepages
系统启动前,通过内核参数分配内存:
// 分配1024个2M的页
hugepages=1024
// 分配4个1G的页, 64位应用推荐使用1G的页
default_hugepages=1G hugepagesz=1G hugepages=4
在系统运行的情况,通过修改/proc中参数分配:
// 分配24个2MB的页
echo 24 >/sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
在系统运行时,直接mount挂载hugepages:
mkdir /mnt/huge
mount -t hugetlbfs nodev /mnt/huge
或者添加到添加到/etc/fstab中,启动时自动挂载:
nodev /mnt/huge hugetlbfs defaults 0 0
1GB的页需要指定页的大小
nodev /mnt/huge_1GB hugetlbfs pagesize=1GB 0 0
加载内核模块
dpdk依赖uio, 提供了igb_uio.ko, 需要将这两个内核模块加载:
sudo modprobe uio
sudo insmod kmod/igb_uio.ko
在dpdk编译后的目录中可以看到dpdk提供的驱动:
[root@localhost kmod]# ls
igb_uio.ko rte_kni.ko
替换网卡的驱动
在dpdk对网卡的称呼是port, 就是网口。
dpdk的应用程序要使用某块网卡是,需要使用tools/pci_unbind.py将网卡绑定到驱动igb_uio。
依赖lspci命令
使用pci_unbind.py查看网卡状态:
[root@localhost tools]# ./pci_unbind.py --status //查看网卡状态
Network devices using IGB_UIO driver
====================================
<none>
Network devices using kernel driver
===================================
0000:02:01.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
Other network devices
=====================
<none>
设置eth2使用igb_uio:
[root@localhost tools]# ./pci_unbind.py --bind=igb_uio eth2 //eth2绑定到驱动igb_uio
查看绑定后的网卡状态:
[root@localhost tools]# ./pci_unbind.py --status //绑定后的网卡状态
Network devices using IGB_UIO driver
====================================
0000:02:05.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
Network devices using kernel driver
===================================
0000:02:01.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth1 drv=e1000 unused=igb_uio *Active*
Other network devices
=====================
<none>
自动化工具
dpdk中提供了一个脚本(tools/setup.sh), 用于方便的编译以及设置运行环境.
[root@localhost dpdk-1.5.0r1]# source tools/setup.sh
------------------------------------------------------------------------------
RTE_SDK exported as /export/App/ads/dpdk/dpdk-1.5.0r1
------------------------------------------------------------------------------
----------------------------------------------------------
Step 1: Select the DPDK environment to build
----------------------------------------------------------
[1] i686-default-linuxapp-gcc
[2] i686-default-linuxapp-icc
[3] x86_64-default-linuxapp-gcc
[4] x86_64-default-linuxapp-icc
----------------------------------------------------------
Step 2: Setup linuxapp environment
----------------------------------------------------------
[5] Insert IGB UIO module
[6] Insert KNI module
[7] Setup hugepage mappings for non-NUMA systems
[8] Setup hugepage mappings for NUMA systems
[9] Display current Ethernet device settings
[10] Bind Ethernet device to IGB UIO module
...
使用其它特性
HPET – High Precision Event Timer
HPET需要BIOS支持:
Advanced->PCH-IO Configuration -> High Precision Timer
可以从/proc/timer_list中查看HPET是否已经开启:
grep hpet /proc/timer_list
内核选项需要勾选HPET_MMAP
在Fedora、Ubuntu等发行版中, HPET_MMAP默认是disable的, 需要重新编译内核。
dpdk默认是不使用HPET的,因此还需要在dpdk的配置文件(源码config目录下)中设置支持HPET:
CONFIG_RTE_LIBEAL_USE_HPET=n
使用非Root身份运行
dpdk可以使用非root身份运行,需要设置好一下的目录或文件的所属:
hugepage挂在点:
/mnt/huge
userspace-io设备文件:
/dev/uio0 ...
如果使用HPET:
/dev/hpet
使用电源管理功能
需要在BIOS中启用Enhanced Intel SpeedStep Technology
Advanced->Processor Configuration->Enhanced Intel SpeedStep® Tech
还需要在BIOS中启用C3、C6
Advanced->Processor Configuration->Processor C3
Advanced->Processor Configuration-> Processor C6
core isolation – 独占CPU核心
将dpdk应用绑定到特定的CPU上后, 这些CPU还是参与系统调度,承担系统的其它的任务。
可以通过配置Linux的内核参数使dpdk占用的CPU不参与调度,从而使dpdk应用独占CPU。
isolcpus=2,4,6 //核2, 4, 6不参与任务调度
Kernel NIC Interface
dpdk的示例程序中给出了一个KNI(Kernel NIC Interface)示例, 要使用该示例, 需要加载kni模块:
#insmod kmod/rte_kni.ko
这个模块和igb_uio.ko在一个目录,前面已经见过。
使用利用Intel VT-d的IOMMU Pass-Through
TODO: 对IOMMU不了解,这里是猜测
经查Passthrough I/O模型是指在客户机内部能够直接对硬件进行操作, Intel的支持技术叫做VT-d(Virtualization Technology for Directed I/O),AMD的支持技术叫做IOMMU(I/O Memory Management Unit)。这里涉及的应当是dpdk应用运行在虚拟机中的情形, 在虚拟机中的dpdk应用需要能够透过虚拟机直接访问设备硬件。
首先要启用Intel VT技术, 需要设置内核的编译选项:
IOMMU_SUPPORT
IOMMU_API
INTEL_IOMMU
运行时需要指定内核参数:
iommu = pt
如果编译的内核中没有设置INTEL_IOMMU_DEFAULT_ON, 还需要指定内核参数:
intel_iommu=on
TODO:这一节的内容还需要继续深挖, 特别是虚拟化相关的内容。
应用运行
dpdk提供了一个统一的环境抽象层(EAL, Environmental Abstraction Layer), 这一层为dpdk应用提供了通用的统一的选项。
./rte-app -c COREMASK -n NUM [-b <domain:bus:devid.func>] [--socket-mem=MB,...]
[-m MB] [-r NUM] [-v] [--file-prefix] [--proc-type <primary|secondary|auto>] [--xen-dom0]
-c: coremask, 指定使用的CPU
-n: Num of memory channels per processor socket
-b: blacklisting of port
--use-device: 只是用指定的网卡, 与-b互斥
--socket-mem: 从每个socket上申请的hugepage内存的大小
-m: 从hugepage申请的内存的大小, 注意如果使用这个选项, 每个socket贡献的内存大小是不确定的
-r: Num of memory ranks
-v: 版本
--huge-dir: hugetlbfs挂载位置
--file-prefix: the prefix text used for hugepage filenames
--proc-type: 进程类型
--xen-dom0: 在没有hugetlbfs的Xen Domain0上运行
--vmware-tsc-map: 使用vmware的TSC map代替native RDSTC
--base-virtaddr: 指定虚拟基址
这里涉及到两个内存相关的指标: channels和ranks, 含义不明!!TODO:
内部实现
rte_eal是核心的基础库, rte_eal+libc是dpdk中其它的lib库的根基。
eal通过dpdk提供的igb_uio.ko或的设备信息,igb_uio是使用了内核的UIO特性, 可以在用户空间访问操作设备.
关于UIO: 参考linux内核技术
eal通过mmap将hugetlbfs映射到进程地址中, ring、buf等使用的内存都来自hugetlbfs
dpdk使用run-to-completion模式进行报文的处理。在进入Data Plane之前,事先将相关资源准备好, 然后作为一个执行单元直接在core上执行。
dpdk还提供了一种pipeline模式作为对run-to-completion模式的补充。pipleline模式中,cores之间通过ring传递报文或者消息,从而可以控制任务的执行阶段, 提高cores的利用率。
开发模型
dpdk提供的编程的模型是并发的, 并发的数量与CPU的核数相关, 每个核上的执行单元是最小的并发单位。
使用dpdk开发的应用可以看做是多个单独的执行单元分别依附在各自的核心默默的跑着。
dpdk提供了ring等内存结构, 执行单元借助这些数据结构互相传递信息。
开发环境
dpdk开发环境使用相当简单, 只需要指定两个环境变量RTE_SDK和RTE_TARGET, 和撰写一个dpdk的makefile即可。
通过环境变量指定SDK的路径和系统类型:
export RTE_SDK=/home/user/DPDK //DPDK安装路径
export RTE_TARGET=x86_64-default-linuxapp-gcc //系统类型
dpdk提供了一套的Makefile, 正是得益于这套Makefile, 开发环境才得以如此简单。在dpdk项目中需要撰写一个符合dpdk要求的Makefile。
dpdk的Makefile的详细可以使用参考《intel-dpdk-programmers-guide.pdf》 Part2 Development Environment
Hello World
这是dpdk自带的一个示例。
环境变量:
export RTE_SDK=/opt/dpdk-1.5
export RTE_TARGET=x86_64-default-linuxapp-gcc
也可以在Makefile中重置RTE_SDK, 例如在文件开始出添加一行:
export RTE_SDK=/export/App/ads/dpdk/dpdk-1.6.0r1
项目路径:
/root/dpdk-1.6.0r1/examples/helloworld
项目文件:
[root@localhost helloworld]# ls
main.c main.h Makefile
项目的Makefile:
include $(RTE_SDK)/mk/DPDK.vars.mk
# binary name
APP = helloworld #指定应用名称,编译得到的程序将会使用这个名字
# all source are stored in SRCS-y
SRCS-y := main.c #指定源文件
CFLAGS += -O3 #编译选项
CFLAGS += $(WERROR_FLAGS)
include $(RTE_SDK)/mk/DPDK.extapp.mk
main.c:
#include "main.h"
/***************** lcore_hello是每个执行单元的工作内容 ******************/
static int
lcore_hello(__attribute__((unused)) void *arg)
{
| unsigned lcore_id;
| lcore_id = rte_lcore_id();
| printf("hello from core %u\n", lcore_id);
| return 0;
}
int
MAIN(int argc, char **argv)
{
| int ret;
| unsigned lcore_id;
| ret = rte_eal_init(argc, argv);
| if (ret < 0)
| | rte_panic("Cannot init EAL\n");
| /*** 将工作内容下发到其它的执行单元 ***/
| /* call lcore_hello() on every slave lcore */
| RTE_LCORE_FOREACH_SLAVE(lcore_id) {
| | rte_eal_remote_launch(lcore_hello, NULL, lcore_id);
| }
| /*** 当前执行单元的工作内容 ***/
| /* call it on master lcore too */
| lcore_hello(NULL);
| /*** 等待所有的执行单元结束 ***/
| rte_eal_mp_wait_lcore();
| return 0;
}
编译:
make O=`pwd`/../out
O=指定输出路径, 如果不指定默认是当前路径下的build目录
编译结果:
[root@localhost build]# ls -R
.:
app helloworld helloworld.map _install main.o _postbuild _postinstall _preinstall
./app:
helloworld helloworld.map
运行:
echo 24 >/sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
mkdir /mnt/huge
mount -t hugetlbfs nodev /mnt/huge
sudo modprobe uio
sudo insmod kmod/igb_uio.ko
./helloworld -c 3 -n 1
运行结果:
[root@localhost build]# ./helloworld -c 7 -n 1
EAL: Detected lcore 0 as core 0 on socket 0 //CPU核心检测
EAL: Detected lcore 1 as core 1 on socket 0
EAL: Detected lcore 2 as core 2 on socket 0
EAL: Skip lcore 3 (not detected)
...
EAL: Skip lcore 63 (not detected)
EAL: Setting up memory...
EAL: Ask a virtual area of 0x2097152 bytes //TODO!!!
EAL: Virtual area found at 0x7f056fe00000 (size = 0x200000)
EAL: Ask a virtual area of 0x2097152 bytes
EAL: Virtual area found at 0x7f056fa00000 (size = 0x200000)
EAL: Ask a virtual area of 0x12582912 bytes
EAL: Virtual area found at 0x7f056ec00000 (size = 0xc00000)
EAL: Ask a virtual area of 0x2097152 bytes
EAL: Virtual area found at 0x7f056e800000 (size = 0x200000)
EAL: Ask a virtual area of 0x2097152 bytes
EAL: Virtual area found at 0x7f056e400000 (size = 0x200000)
EAL: Requesting 10 pages of size 2MB from socket 0
EAL: TSC frequency is ~2459655 KHz //TODO!!!
EAL: WARNING: cpu flags constant_tsc=yes nonstop_tsc=no -> using unreliable clock cycles ! //TODO!!!
EAL: Master core 0 is ready (tid=7103a840)
EAL: Core 2 is ready (tid=6d3fd700)
EAL: Core 1 is ready (tid=6dbfe700)
hello from core 1 //执行单元的工作结果
hello from core 0
hello from core 2
从运行结果的输出可以看到,启动时首先会检测设备的CPU核心数(检测0-63,如果存在就显示Core的位置), 然后申请内存等, Core就位后开始执行单元的执行。
API
首先到dpdk的安装目录下查看一下dpdk提供的内容:
[root@localhost dpdk-1.5]# ls
mk x86_64-default-linuxapp-gcc
其中mk目录下dpdk提供的一套makefile, 在这些makefile的辅助下, 项目的顶层Makefile变得非常整洁。
x86_64-default-linuxapp-gcc对应平台上的开发接口。
[root@localhost x86_64-default-linuxapp-gcc]# ls
app include kmod lib
app: 编译得到dpdk的应用程序
include: dpdk对外提供的接口文件(.h文件)
kmod: dpdk的相关内核模块
lib: dpdk的静态库文件(.a)
这里关心的是include目录中的.h文件中都提供了哪些接口, 分别作什么用途。intel-dpdk-programmers-guide.pdf做了分类说明。
详细内容可以查看DPDK在线API手册
常用接口
dpdk提供了对网卡进行操作的接口, 关于网卡的知识可以从这里获取Intel官网上获取。
Parameter Talk: TX and RX descriptors
rte_eal_init(int argc, char **argv)
//EAL的初始化,在master core上运行
rte_pmd_init_all
rte_eal_pci_probe
rte_lcore_count
rte_eth_dev_count
rte_eth_macaddr_get
rte_eth_dev_configure
//设置网卡的接收队列和发送队列数目, 以及硬件特性
rte_lcore_is_enabled
rte_lcore_to_socket_id
rte_mempool_create
rte_eth_tx_queue_setup
rte_eth_rx_queue_setup
rte_eth_dev_start
rte_eth_promiscuous_enable
rte_eth_link_get_nowait
rte_ring_create
rte_rand
rte_eal_mp_remote_launch
rte_lcore_id
rte_ring_dequeue_burst
rte_pktmbuf_mtod
rte_rdtsc
rte_eth_rx_burst
rte_prefetch0
rte_be_to_cpu_16
rte_jhash_2words
rte_ring_enqueue
RTE_PER_LCORE
优化
《intel-dpdk-programmers-guide.pdf》给出了一些性能优化建议
内存
-
在data plane操作内存的时候, 使用dpdk的api, 不要使用libc
例如: rte_memcpy – memcpy rte_malloc – malloc
-
降低cache miss
通过使用RTE_PER_LCORE
-
NUMA
-
均衡Memory Channels
核间通信
- 使用dpdk ring进行核间通信
Poll Mode Driver (PMD)
-
低延迟与高吞吐之间的延迟
控制每次轮询网卡时处理的报文的数量
锁和原子操作
-
在data plane避免使用锁
使用per-lcore variables 使用RCU算法替代rwlocks
编码技巧
-
使用inline函数
-
使用分支预测
if (likely(x>1)) XXXX;
设置CPU类型
-
dpdk支持对特定的CPU架构进行优化
通过CONFIG_RTE_MACHINE指定CPU特性
对虚拟化的支持
DPDK对虚拟化提供了比较全面的支持。可以用于虚拟机中。
对比pf_ring
直观感觉,dpdk的定位是sdk, 提供了更多的支持库, 自成体系, 绑定在intel平台上。pf_ring定位是高效的抓包工具, 支持的硬件更多一些。
文献
- intel-dpdk-programmers-guide.pdf
- intel-dpdk-getting-started-guide.pdf