mTCP中使用DPDK将网卡上的数据提取到用户态,然后经过用户态的TCP/IP协议进行处理。DPDK全称Data Plane Development Kit,是Intel发布的用于快速处理数据包的开发平台和接口1。
网卡驱动在接收到数据包会产生中断来通知CPU进行处理,然后CPU拷贝数据给内核协议栈进行处理。当数据量非常大时,中断和数据拷贝的开销很大。DPDK将网卡的数据旁路到用户态直接进行处理,不经过内核,不产生中断也不进行昂贵的数据拷贝。Intel自己给出的性能数据是,一个数据包可以在80个时钟周期内处理完成,而正常情况下处理器访问DDR3内存都需要200个时钟周期,还不算协议处理的时间。
下载安装
系统要求
DPDK的文档中对Linux系统的部署有很详细的描述Guide for Linux。
首先由于DPDK的是Intel推出的技术,因此只支持x86架构的机器。
查看是否支持hpet,如果不支持则无输出内容,需要在BIOS中开启:
grep hpet /proc/timer_list
Clock Event Device: hpet
set_next_event: hpet_legacy_next_event
shutdown: hpet_legacy_shutdown
periodic: hpet_legacy_set_periodic
oneshot: hpet_legacy_set_oneshot
resume: hpet_legacy_resume
启动hugepage支持(64位系统推荐使用1G的hugepages):
# 每块内存大小2MB,共预留1024个2MB内存块
echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
然后将hugepages中的内存给DPDK使用:
mkdir /mnt/huge
mount -t hugetlbfs nodev /mnt/huge
#add item to the /etc/fstab
nodev /mnt/huge hugetlbfs defaults 0 0
DPDK的源码下载和文档都在官网。下载最新版本的代码:
wget http://fast.dpdk.org/rel/dpdk-16.07.tar.xz
解压后编译:
make install T=x86_64-native-linuxapp-gcc
其中T表示Target, Target的描述格式是:
ARCH-MACHINE-EXECENV-TOOLCHAIN
- ARCH = i686, x86_64, ppc_64
- MACHINE = native, ivshmem, power8
- EXECENV = linuxapp, bsdapp
- TOOLCHAIN = gcc, icc
编译完成之后生产了环境目录x86_64-native-linuxapp-gcc
:
cd x86_64-native-linuxapp-gcc
ls
app build include kmod lib Makefile
加载uio内核模块:
modprobe uio_pci_generic
要使用DPDK,必须将网卡绑定到uio_pci_generi模块,DPDK在tools目录下提供了dpdk-devbind.py脚本完成这个工作。首先在绑定前查看一下状态:
[root@localhost tools]$ ./dpdk-devbind.py --status
Network devices using DPDK-compatible driver
============================================
<none>
Network devices using kernel driver
===================================
0000:00:03.0 '82540EM Gigabit Ethernet Controller' if=enp0s3 drv=e1000 unused= *Active*
0000:00:08.0 '82540EM Gigabit Ethernet Controller' if=enp0s8 drv=e1000 unused= *Active*
Other network devices
=====================
<none>
可以看到现在没有绑定到DPDK的网络接口,现在将enp0s8绑定到DPDK:
#需要先down掉,不然没办法绑定成功
[root@localhost tools]# ifconfig enp0s8 down
[root@localhost tools]# ./dpdk-devbind.py --bind=uio_pci_generic enp0s8
[root@localhost tools]# ./dpdk-devbind.py --status
Network devices using DPDK-compatible driver
============================================
0000:00:08.0 '82540EM Gigabit Ethernet Controller' drv=uio_pci_generic unused=e1000
Network devices using kernel driver
===================================
0000:00:03.0 '82540EM Gigabit Ethernet Controller' if=enp0s3 drv=e1000 unused=uio_pci_generic *Active*
Other network devices
=====================
<none>
可以看出,现在已经有一个设备使用DPDK了。
编译一个使用DPDK的简单程序
在编译之前必须将RET_SDK
和RTE_TARGET
导入到环境变量中,其中RET_SDK
是DPDK的安装目录,RTE_TARGET
是DPDK目标环境目录。
export RTE_SDK=/home/lyt/dpdk-16.07
export RTE_TARGET=x86_64-native-linuxapp-gcc
在examples目录里面编译一个简单的应用:
cd examples/helloworld/
make
可以看到当前目录下生成了build目录,可执行程序就在build目录中,执行helloworld:
[root@localhost build]# ./helloworld
EAL: Detected 2 lcore(s)
EAL: Probing VFIO support...
PMD: bnxt_rte_pmd_init() called for (null)
EAL: PCI device 0000:00:03.0 on NUMA socket -1
EAL: probe driver: 8086:100e rte_em_pmd
EAL: PCI device 0000:00:08.0 on NUMA socket -1
EAL: probe driver: 8086:100e rte_em_pmd
hello from core 1
hello from core 0
hello world的代码结构
helloword程序成功编译并运行,来简单看一看代码:
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/queue.h>
#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>
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;
}
这个hello world代码非常简单,简单进行一下阐述。
- 第一步是调用
rte_eal_init
初始化EAL(Environment Abstraction Layer, 环境抽象层)。EAL在每一个slave核上都创建一个线程,并绑定CPU。 - 使用RTE_LCORE_FOREACH_SLAVE遍历分配给DPDK的slave CPU核心,然后调用
rte_eal_mp_remote_launch
注册回调函数lcore_hello
函数打印。 - 在master lcore上调用lcore_hello。
- rte_eal_mp_wait_lcore等待所有的slave核心退出,然后自己再退出。