官方文档请访问http://dpdk.org/,这里面很多干货
DPDK(Intel Data Plane Development Kit)是Intel提供的数据平面开发工具集,专注于网络应用中数据包的高性能处理。DPDK应用程序运行在用户空间,利用自身提供的数据平面库来收发数据包,绕过了Linux内核协议栈对数据包处理过程。DPDK其实也是网络开发框架和开发库、高效数据结构、众多Linux系统优化方法的合集。
网络设备(路由器、交换机、媒体网关、SBC、PS网关等)需要在瞬间进行大量的报文收发,因此在传统的网络设备上,往往能够看到专门的NP(Network Process)处理器,有的用FPGA,有的用ASIC。这些专用器件通过内置的硬件电路(或通过编程形成的硬件电路)高效转发报文,只有需要对报文进行深度处理的时候才需要CPU干涉。
但在公有云、NFV等应用场景下,基础设施以CPU为运算核心,往往不具备专用的NP处理器,操作系统也以通用Linux为主,网络数据包的收发处理路径如下图所示:
在虚拟化环境中,路径则会更长
由于包处理任务存在内核态与用户态的切换,以及多次的内存拷贝,系统消耗变大,以CPU为核心的系统存在很大的处理瓶颈。为了提升在通用服务器(COTS)的数据包处理效能,Intel推出了服务于IA(Intel Architecture)系统的DPDK技术。
DPDK是Data Plane Development Kit的缩写。简单说,DPDK应用程序运行在操作系统的User Space,利用自身提供的数据面库进行收发包处理,绕过了Linux内核态协议栈,以提升报文处理效率。
DPDK是一组lib库和工具包的集合。最简单的架构描述如下图所示:
上图蓝色部分是DPDK的主要组件(更全面更权威的DPDK架构可以参考Intel官网),简单解释一下:
- PMD:Pool Mode Driver,轮询模式驱动,通过非中断,以及数据帧进出应用缓冲区内存的零拷贝机制,提高发送/接受数据帧的效率
- 流分类:Flow Classification,为N元组匹配和LPM(最长前缀匹配)提供优化的查找算法
- 环队列:Ring Queue,针对单个或多个数据包生产者、单个数据包消费者的出入队列提供无锁机制,有效减少系统开销
- MBUF缓冲区管理:分配内存创建缓冲区,并通过建立MBUF对象,封装实际数据帧,供应用程序使用
- EAL:Environment Abstract Layer,环境抽象(适配)层,PMD初始化、CPU内核和DPDK线程配置/绑定、设置HugePage大页内存等系统初始化
这么说可能还有一点点抽象,再总结一下DPDK的核心思想:
- 用户态模式的PMD驱动,去除中断,避免内核态和用户态内存拷贝,减少系统开销,从而提升I/O吞吐能力
- 用户态有一个好处,一旦程序崩溃,不至于导致内核完蛋,带来更高的健壮性
- HugePage,通过更大的内存页(如1G内存页),减少TLB(Translation Lookaside Buffer,即快表) Miss,Miss对报文转发性能影响很大
- 多核设备上创建多线程,每个线程绑定到独立的物理核,减少线程调度的开销。同时每个线程对应着独立免锁队列,同样为了降低系统开销
- 向量指令集,提升CPU流水线效率,降低内存等待开销
下图简单描述了DPDK的多队列和多线程机制:
DPDK将网卡接收队列分配给某个CPU核,该队列收到的报文都交给该核上的DPDK线程处理。存在两种方式将数据包发送到接收队列之上:
RSS(Receive Side Scaling,接收方扩展)机制:根据关键字,比如根据UDP的四元组<srcIP><dstIP><srcPort><dstPort>进行哈希
Flow Director机制:可设定根据数据包某些信息进行精确匹配,分配到指定的队列与CPU核
当网络数据包(帧)被网卡接收后,DPDK网卡驱动将其存储在一个高效缓冲区中,并在MBUF缓存中创建MBUF对象与实际网络包相连,对网络包的分析和处理都会基于该MBUF,必要的时候才会访问缓冲区中的实际网络包
0、依赖安装
在dpdk编译过程中,由于一些依赖项的限制,dpdk在纯净的系统上安装需要花一些功夫。
yum makecache
yum install -y gcc gcc-c++ kernel-devel kernel-headers kernel.x86_64 net-tools
yum install -y numactl-devel.x86_64 numactl-libs.x86_64
yum install -y libpcap.x86_64 libpcap-devel.x86_64
yum install -y pciutils
ubuntu
-
所需依赖合集
更新软件
- 更新软件源中的所有软件列表:# sudo apt-get update
-
更新软件:# sudo apt-get upgrade
GNU make
- 一种构建工具,控制应用程序源代码的可执行代码和其他部分代码生成。
- 安装:# sudo apt-get install make
Coreutils
- GNU 下的一个软件包,包含ls等常用命令
- 安装:# sudo apt-get install coreutils
gcc
- GNU下的编译器套件,版本需要>=4.9
- 版本检测:# gcc -v
libc headers
- Linux ANSIC 的函数库
- 打包安装:# sudo apt-get install gcc-multilib
Linux Kernel header or sources
- Kernel-devel.×86_64
- Kernel-devel.ppc64
- apt-get install linux-kernel-headers kernel-package
NUMA
- 安装:
- # sudo apt-get install numactl
- # sudo apt-get install libnuma-dev
python
- 版本2.7+ or 3.2+
- 安装:# sudo apt-get install python
- 检测是否安装成功:# python
- 退出python:# exit()
Kernel version >=3.2
- 版本检测:# uname -r
glibc
- 版本大于等于2.7
- 版本检测:# ldd --version
libpcap
- 网络数据包捕获函数库
- 下载:http://www.tcpdump.org 找到libpcap并下载
- 解压:# tar zxvf libpcap
- # sudo apt-get install build-essential
- # sudo apt-get install m4
- # sudo apt-get install flex
- # sudo apt-get install bison
- # ./configure
- # make
- # sudo make install
1、DPDK下载与测试
源码设置,在虚拟机终端从dpdk官方直接git clone最新的代码:
git clone git://dpdk.org/dpdk
进入dpdk目录;编辑一个环境变量文件,然后source;
export RTE_SDK=`pwd`
export RTE_TARGET=x86_64-native-linuxapp-gcc
然后执行:
make config T=x86_64-native-linuxapp-gcc
make install T=x86_64-native-linuxapp-gcc
//make install T=x86_64-native-linuxapp-gcc DESTDIR=/usr/local
等编译OK后,执行一些准备工作:
#!/bin/sh
cat /proc/meminfo | grep Huge
HUGEPGSZ=`cat /proc/meminfo | grep Hugepagesize | cut -d : -f 2 | tr -d ' '`
bind_nic(){
ifconfig $1 down
${RTE_SDK}/usertools/dpdk-devbind.py --bind=igb_uio $1
}
#
echo "Loading DPDK UIO module"
/sbin/lsmod | grep -s uio > /dev/null
if [ $? -ne 0 ] ; then
modinfo uio > /dev/null
if [ $? -eq 0 ]; then
modprobe uio
fi
fi
insmod ${RTE_SDK}/${RTE_TARGET}/kmod/igb_uio.ko
#
echo "Creating /mnt/huge and mounting as hugetlbfs"
sudo mkdir -p /mnt/huge
grep -s '/mnt/huge' /proc/mounts > /dev/null
if [ $? -ne 0 ] ; then
sudo mount -t hugetlbfs nodev /mnt/huge
fi
#
echo "@@@@@Reserving hugepages"
#echo 1024 > /sys/kernel/mm/hugepages/hugepages-${HUGEPGSZ}/nr_hugepages
echo > .echo_tmp
for d in /sys/devices/system/node/node? ; do
node=$(basename $d)
echo -n "Number of pages for $node: "
read Pages
echo "echo $Pages > $d/hugepages/hugepages-${HUGEPGSZ}/nr_hugepages" >> .echo_tmp
done
sh .echo_tmp
rm -f .echo_tmp
#
echo "Bind interface"
ifconfig -a
${RTE_SDK}/usertools/dpdk-devbind.py --status
bind_nic ens5
bind_nic ens6
cat /proc/meminfo | grep Huge
2、编译helloword
进入helloword的目录,执行make
[root@CC build]# ./helloworld
EAL: Detected 5 lcore(s)
EAL: Detected 1 NUMA nodes
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
EAL: No available hugepages reported in hugepages-1048576kB
EAL: Probing VFIO support...
EAL: WARNING: cpu flags constant_tsc=yes nonstop_tsc=no -> using unreliable clock cycles !
EAL: PCI device 0000:00:03.0 on NUMA socket -1
EAL: Invalid NUMA socket, default to 0
EAL: probe driver: 8086:100e net_e1000_em
EAL: PCI device 0000:00:04.0 on NUMA socket -1
EAL: Invalid NUMA socket, default to 0
EAL: probe driver: 8086:100e net_e1000_em
EAL: PCI device 0000:00:05.0 on NUMA socket -1
EAL: Invalid NUMA socket, default to 0
EAL: probe driver: 8086:100e net_e1000_em
EAL: PCI device 0000:00:06.0 on NUMA socket -1
EAL: Invalid NUMA socket, default to 0
EAL: probe driver: 8086:100e net_e1000_em
EAL: PCI device 0000:00:07.0 on NUMA socket -1
EAL: Invalid NUMA socket, default to 0
EAL: probe driver: 8086:100e net_e1000_em
hello from core 1
hello from core 2
hello from core 3
hello from core 0
hello from core 4
3、helloworld例子
这个是最简单的使用dpdk开发套件的例程。
源码分析:
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/queue.h>
//以上头开发环境glibc的相关头文件
#include <rte_memory.h>
#include <rte_memzone.h>
#include <rte_launch.h>
#include <rte_eal.h>
#include <rte_per_lcore.h>
#include <rte_lcore.h>
#include <rte_debug.h>
/*以上为dpdk自己编写的一些公共库头文件,如内存池、线程、抽象环境等工具,DPDK有自己的开发风格,适应之。*/
static int
lcore_hello(__attribute__((unused)) void *arg)/* 此处有一个字节对齐操作,此处不做详细分析。*/
{
unsigned lcore_id;
lcore_id = rte_lcore_id(); //获取逻辑核编号,并输出逻辑核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");
/* 每个从逻辑核调用回调函数lcore_hello输出相关信息。 */
RTE_LCORE_FOREACH_SLAVE(lcore_id) {
rte_eal_remote_launch(lcore_hello, NULL, lcore_id);
}
/* 再次调用主逻辑核输出相关信息。 */
lcore_hello(NULL);
/* 等待所有从逻辑核调用返回,相当于主线程阻塞等待。*/
rte_eal_mp_wait_lcore();
return 0;
}
Makefile:
#判断相关环境变量是否设置
ifeq ($(RTE_SDK),)
$(error "Please define RTE_SDK environment variable")
endif
# 默认的平台目标
RTE_TARGET ?= x86_64-native-linuxapp-gcc
include $(RTE_SDK)/mk/rte.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/rte.extapp.mk
1.带参数运行 -l
2.带参数运行 -l --master-lcore
选项解释
[root@dev build]# ./helloworld --help
EAL: Detected lcore 0 as core 0 on socket 0
EAL: Detected lcore 1 as core 1 on socket 0
EAL: Support maximum 128 logical core(s) by configuration.
EAL: Detected 2 lcore(s)
Usage: ./helloworld [options]
EAL common options:
-c COREMASK 逻辑核16进制掩码
-l CORELIST 列出运行时逻辑核列表
参数格式 <c1>[-c2][,c3[-c4],...]
where c1, c2, etc are core indexes between 0 and 128
--lcores COREMAP 映射逻辑核到物理逻辑核集合中
The argument format is
'<lcores[@cpus]>[<,lcores[@cpus]>...]'
lcores and cpus list are grouped by '(' and ')'
Within the group, '-' is used for range separator,
',' is used for single number separator.
'( )' can be omitted for single element group,
'@' can be omitted if cpus and lcores have the same value
--master-lcore ID 指定主线程逻辑核id
-n CHANNELS 指定内存通道数
-m MB 指定内存分配 (类似 --socket-mem)
-r RANKS Force number of memory ranks (don't detect) 强制内存参数
-b, --pci-blacklist 将PCI网络设备列入黑名单,防止EAL环境使用这些PCI设备,参数格式为<domain:bus:devid.func>
-w, --pci-whitelist 将PCI网络设备列入白名单,仅仅用指定的PCI设备,参数格式为<[domain:]bus:devid.func>
--vdev 添加一块虚拟设备,这个参数格式为 <driver><id>[,key=val,...]
(例如: --vdev=eth_pcap0,iface=eth2).
-d LIB.so|DIR 添加驱动活驱动目录(can be used multiple times)
--vmware-tsc-map Use VMware TSC map instead of native RDTSC
--proc-type 进程的类型 (primary|secondary|auto)
--syslog 设定syslog日志
--log-level 设定默认日志级别
-v 启动时显示版本信息
-h, --help This help
EAL options for DEBUG use only: 调试
--huge-unlink 在初始化后去掉大页面文件连接
--no-huge 用 malloc 代替 hugetlbfs
--no-pci 关闭 PCI
--no-hpet 关闭 HPET
--no-shconf 不共享配置(mmap'd files)
EAL Linux options: 选项
--socket-mem 内存分配
--huge-dir 大页面挂载目录
--file-prefix 页表文件前缀
--base-virtaddr 虚拟地址基址
--create-uio-dev Create /dev/uioX (usually done by hotplug)
--vfio-intr Interrupt mode for VFIO (legacy|msi|msix)
--xen-dom0 Support running on Xen dom0 without hugetlbfs</span>
项目makefile的修改
使用dpdk库的项目,无须将makefile修改为dpdk自带的应用程序的makefile风格,可以继续使用项目自己的makefile。
但是,需要对项目的makefile做出如下修改。
编译选项,增加如下内容。
-march=native -DRTE_CACHE_LINE_SIZE=64\
-I/usr/local/include/dpdk/ -include rte_config.h
链接选项,增加如下内容。
-L/usr/local/lib -Wl,--whole-archive \
-lrte_distributor -lrte_reorder -lrte_pipeline -lrte_table -lrte_port \
-lrte_timer -lrte_hash -lrte_jobstats -lrte_lpm -lrte_power \
-lrte_acl -lrte_meter -lrte_sched -lm -lrt -lrte_vhost \
-Wl,--start-group -lrte_kvargs -lrte_mbuf -lrte_mbuf_offload -lrte_ip_frag \
-lethdev -lrte_cryptodev -lrte_mempool -lrte_ring -lrte_eal \
-lrte_cmdline -lrte_cfgfile -lrte_pmd_bond -lrte_pmd_vmxnet3_uio \
-lrte_pmd_virtio -lrte_pmd_cxgbe -lrte_pmd_enic -lrte_pmd_i40e \
-lrte_pmd_fm10k -lrte_pmd_ixgbe -lrte_pmd_e1000 \
-lrte_pmd_ring -lrte_pmd_af_packet -lrte_pmd_null -lrt -lm -ldl \
-Wl,--end-group -Wl,--no-whole-archive
总结
有些细节没去认真的分析,细节部分前面也进行了一些理论学习,后面的学习目标是:快速上手,熟悉使用,学习设计思想。