作者简介:文殊博,北京邮电大学未来网络理论与应用实验室(FNL)研究生
一、简介及参考文献
本文主要是搭建一个基于DPDK的VNF开发实验环境,利用docker和vhost-user摆脱DPDK的硬件约束,方便在一台机器上进行VNF的开发和测试。但是,DPDK的PMD对不同网卡的特性支持差异很大,所以不能完全以这种环境下的运行结果为准。接下来会先介绍环境的配置过程然后搭建一个简单的拓扑,最后会对DPDK的testpmd应用中最简单的iofwd转发引擎中关键部分进行简单说明。
- NFV(Network Funciton Virtualization, 网络功能虚拟化)旨在通过标准化设备替代现存的专用IT设备,以降低CapEX和OpEX并提供更高的灵活性。更详细内容的可以查看ETSI发布的NFV白皮书或OPNFV提供的相关内容。
- DPDK是intel开发的开源高性能数据平面开发工具集。它提供了在通用处理器上进行快速包处理的库和驱动,这种特性非常适合NFV。更详细内容的内容可以查看DPDK官方文档。
- pktgen即packet generator,以下都特指pktgen-dpdk。这是一款基于DPDK的强大的流量产生工具。更详细的内容可以查看pktgen官方文档
- Docker是一款简单易用的容器管理工具。由于容器本身启停快、体积小等、
- 特点,容器化也成为了NFV的趋势之一。更详细内容可以查看Docker官方文档
- vhost-user是实现在用户态的vhost,相比virtio和vhost需要内核支撑,vhost-user可以完全工作在用户态。vhost-user通过socket文件和共享内存来完成数据的传递。更详细内容可以查看QEMU关于vhost-user的介绍
二、环境配置
2.1 DPDK的安装
- 下载DPDK源码,并配置安装所需环境变量。
123456789wget http://fast.dpdk.org/rel/dpdk-17.05.2.tar.xztar -xvf dpdk-17.05.2.tar.xzcd dpdk-stable-17.05.2#设置DPDK库目录位置echo export RTE_SDK=$(pwd) >>~/.bashrc#设置DPDK目标环境#注意!这里的x86_64-native-linuxapp-gcc应替换为实际运行环境echo export RTE_TARGET=x86_64-native-linuxapp-gcc >> ~/.bashrcsource ~/.bashrc - 配置DPDK
1vim config/common_base
这里由于要是用Vhost-user驱动,修改使得文件中CONFIG_RTE_LIBRTE_VHOST=y。如果需要连接其他驱动的网卡,也要确认其他驱动相关设置正确。 - 安装DPDK
123make config T=$RTE_TARGETmake T=$RTE_TARGET -j8make install T=$RTE_TARGET -j8
2.2 hugepages的配置
- 本实验中由于我们需要使用virtio-user这种连接方式,由于实现时的一些限制导致使用vhost-user时同时最多只能使用8个hugepages无论2M还是1G大小的hugepagesize。所以这里必须使用1G大小的hugepagesize同时最多分配8个。
1 2 3 4 5 6 7 8 9 10 11 |
sudo vim /etc/default/grub #找到其中一项为 GRUB_CMDLINE_LINUX_DEFAULT= ,不论后面的引号内包含任何内容,在原本内容之后添加 default_hugepagesz=1GB hugepagesz=1G hugepages=8(这里分配了8个1G的hugepages) sudo update-grub #之后需要重启一下 reboot #查看分配情况 grep Huge /proc/meminfo #分配成功后进行挂载 mkdir -p /dev/hugepages mount -t hugetlbfs none /dev/hugepages #之后每次重启后都要再执行后两句 |
2.3 Pktgen的安装
- pktgen的安装依赖于DPDK,安装前确保RTE_SDK和RTE_TARGET环境变量设置正确。
1 2 3 4 5 6 7 |
#安装依赖 sudo apt-get install -y libpcap-dev lua5.3 wget http://www.dpdk.org/browse/apps/pktgen-dpdk/snapshot/pktgen-3.4.2.tar.gz tar -xvf pktgen-3.4.2.tar.gz cd pktgen-3.4.2 make -j8 ln -s $(pwd)/app/$RTE_TARGET/pktgen /usr/bin/pktgen |
2.4 Docker的安装
国内连接Docker官方仓库非常不稳定,经常404无法获取镜像。推荐使用阿里云提供的Docker镜像仓库。在其中的管理中心——Docker Hub镜像站点中有完整的Docker安装和加速器配置过程。以下从其中复制出的简单安装过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# step 1: 安装必要的一些系统工具 sudo apt-get update sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common # step 2: 安装GPG证书 curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add - # Step 3: 写入软件源信息 sudo add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" # Step 4: 更新并安装 Docker-CE sudo apt-get -y update sudo apt-get -y install docker-ce # 阿里云加速器配置 # 这里的镜像地址可以在注册阿里云后获得自己的 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF&' { "registry-mirrors": ["https://ksct1u2m.mirror.aliyuncs.com"] } EOF service docker restart |
安装完成后可以简单测试。
1 2 |
sudo docker pull busybox sudo docker run -ti --rm busybox |
正确安装的话,可以进入一个最简单的容器环境中,Ctrl+D可以退出。
三、简单的拓扑搭建
3.1 拓扑
预建立的简单拓扑如下
1 2 3 4 5 6 7 |
+--------+---------------+ +-------------------+---------------+ | | socket file 1 || vhost-user port 1 | | | +---------------+ +-------------------+ | | host | pktgen | | testpmd | container | | +---------------+ +-------------------+ | | | socket file 0 || vhost-user port 0 | | +--------+---------------+ +-------------------+---------------+ |
testpmd是DPDK提供的一个通过数据包转发进行DPDK测试的工具,也是一个非常好的DPDK应用开发示例。如果想进行简单的DPDK开发实验推荐修改$RTE_SDK/app/test-pmd/iofwd.c
后编译testpmd应用,之后在同一目录$RTE_SDK/app/test-pmd/
下可以找到下编译后的testpmd应用。
pktgen运行在主机中,testpmd运行在容器中,两个应用之间通过建立的socket文件以及vhost-user进行通信,相当于虚拟网卡。
3.2 启动容器
通过Dockerfile创建一个DPDK的Docker镜像。
首先创建一个Dockerfile。
1 2 3 4 5 6 |
cat <> $RTE_SDK/../Dockerfile FROM ubuntu:latest WORKDIR /root/dpdk COPY dpdk-stable-17.05.2 /root/dpdk/. ENV PATH "$PATH:/root/dpdk/$RTE_TARGET/app/" EOT |
使用创建的Dockerfile建立Docker镜像.
1 2 3 |
cd $RTE_SDK/.. #-t为镜像名,可以以name:tag的格式来写,不加tag默认tag为latest sudo docker build -t dpdk . |
运行容器
这里运行一个使用刚刚建立的dpdk镜像的容器。
1 2 3 4 5 6 7 8 9 |
mkdir -p /tmp/virtio # -ti 提供一个PTY交互界面并保持STDIN持续可用 # -v /dev/... 将主机的hugepages通过数据卷挂载到容器内,从而可以与主机共享hugepages # -v /tmp/... 挂载一个数据卷到容器中用于共享virtio_user的sockets # --privilege 不启用特权可能导致无法共享hugepages和sockets sudo docker run -ti --rm --name=test \ -v /dev/hugepages:/dev/hugepages \ -v /tmp/virtio/:/tmp/virtio/ \ --privileged dpdk |
3.3 启动应用
在容器中运行testpmd
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 以下命令在容器中运行 # DPDK的参数 # -l 列出所使用的CPU核 # -n 给出memory channels的数量 # --socket-mem 限制用于每个socket的内存量 # --vdev 创建vhost的socket文件用于进行网络连接,可以理解为虚拟网卡 # --file-prefix hugepages字首,用于区分所使用的hugepage # testpmd的参数 # --forward-mode=io 对两个端口情况从一个端口进来的包直接从另一个端口发出 testpmd -l 0-1 -n 1 --socket-mem 1024,1024 \ --vdev 'eth_vhost0,iface=/tmp/virtio/sock0' --vdev 'eth_vhost1,iface=/tmp/virtio/sock1' \ --file-prefix=test --no-pci \ -- -i --forward-mode=io --auto-start |
在主机上运行pktgen
注意,pktgen一定要在与pktgen.lua文件同一目录下运行,即pktgen的文件夹中运行。
1 2 3 4 5 6 7 8 |
# 以下命令在主机中运行 # DPDK的参数同上,--vdev在这里用来连接到socket文件 # pktgen的参数 # -P 在所有端口启用混杂模式 # -m 定义CPU核到端口的绑定,在这里core5绑定到prot0,core6绑定到port1 pktgen -l 2-6 -n 3 --socket-mem 1024,1024 \ --vdev='virtio_user0,path=/tmp/virtio/sock0' --vdev='virtio_user1,path=/tmp/virtio/sock1' \ -- -P -m "5.0,6.1" |
3.4 简单实验
在pktgen中设置速率为10%,更具体的速率设置可以通过tx_cycles设置。端口0共发送100个包,端口1发送200个。
1 2 3 4 5 6 |
# 在pktgen中设置速率为10%,更具体的速率设置可以通过tx_cycles设置 # 端口0共发送100个包,端口1发送200个 Pktgen:/>set all rate 10 Pktgen:/>set 0 count 100 Pktgen:/>set 1 count 200 Pktgen:/>str |
在testpmd中查看端口状态,可以看到端口0接收100个包发出200个,与预期一致:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
testpmd> show port stats all ######################## NIC statistics for port 0 ######################## RX-packets: 100 RX-missed: 0 RX-bytes: 6000 RX-errors: 0 RX-nombuf: 0 TX-packets: 200 TX-errors: 0 TX-bytes: 12000 Throughput (since last show) Rx-pps: 10 Tx-pps: 21 ############################################################################ ######################## NIC statistics for port 1 ######################## RX-packets: 200 RX-missed: 0 RX-bytes: 12000 RX-errors: 0 RX-nombuf: 0 TX-packets: 100 TX-errors: 0 TX-bytes: 6000 Throughput (since last show) Rx-pps: 21 Tx-pps: 10 ############################################################################ |
四、简单分析iofwd.c代码
因为这段代码非常简单,这里只介绍几个关键点帮助快速理解。由于DPDK库非常庞大复杂,阅读源码时推荐通过ctags、YCM等类似工具帮助阅读。
- 与
RTE_TEST_PMD_RECORD_CORE_CYCLES
- 相关的内容用于统计每条转发流处理消耗的时钟周期,
rte_rdtsc()
- 函数用于通过RDTSC或VMWARE TSC map读取时钟周期信息。与
RTE_TEST_PMD_RECORD_BURST_STATS
- 相关的内容用于统计每条转发流接收和发送时burst(处理时的一组数据包)的情况。这里的接收端口和发送端口等参数已经在
config.c
- 文件中设置过,更详细的过程可以看到其中的
simple_fwd_config_setup()
- 函数以及其他模式下的相关设置函数。
rte_eth_rx_burst()
- 函数用于从指定端口和队列中读取数据包。会一次读入很多个数据包(取决于端口排队情况),称为burst。
rte_eth_rx_burst()
- 这个API在同步和异步包处理环境下都针对burst做了非常多的优化,性能非常高。但是,这个函数为了降低额外开销不提供任何错误提示。所以当上层应用多次尝试都接收到0返回值时应该检查设备运行状态。
rte_eth_tx_burst()
- 函数用于从指定端口和队列处发送数据包。同接收一样,会一次把一个burst的数据放入到发送队列。同时,
rte_eth_tx_burst()
- 这个函数会透明地释放掉先前发送的数据包占用的内存。
likely()
- 和
unlikely()
- 函数是常用的优化方式,是GCC內建函数
__builtin_expect()
- 的宏。通过提高条件分支预测准确性提高cache命中率从而提升程序性能。由于现代CPU都通过预读机制提升性能,而由于分支造成的转跳会导致pipeline flushing,这会降低程序性能。通过为编译器提供分支预测信息可以让编译结果中可能性更大的代码紧跟分支判断部分减少转跳。从理解代码逻辑的角度上这两个函数可以完全忽略。更详细的内容可以参考这篇
- 代码的最后定义的
io_fwd_engine
- 相当于注册了一种新的转发引擎,可以在
testpmd.h
- 文件中找到结构体定义,在
testpmd.c
- 中看到对转发引擎的选择和使用。在理解了iofwd中的关键部分之后,就可以在其基础上参考DPDK的
- 进行基础性的实验。当然这只是一个最简单的进行DPDK实验的方式。一个更加全面、有代表性的DPDK示例应用可以参考examples目录下的l3fwd。