SPDK预备知识-DPDK

DPDK本属于网络技术范畴,但是SPDK在DPDK基础上开发,到处都能看到它的影子。要想对SPDK有深入了解,就必须对DPDK有一定的认识。鉴于我们要讲的是SPDK,本文对DPDK中的网络部分不做解读。

DPDK从2010年开源至今,生态系统已经初具规模。无论是上游的设备厂商(Mellanox等推出了DPDK驱动),还是中间系统提供商(VMware,RedHat对DPDK的支持),再到下游的应用集成(Vrouter,Vswitch等)都做出了积极配合。

SPDK预备知识-DPDK_第1张图片

DPDK有完整的框架,简洁的编程接口,丰富详细的文档,这些无疑降低了外界学习的难度。DPDK的内容较多,大部分都是针对网络设备(如驱动模型,Ring队列,数据包转发等),与NVMe存储设备相关度不大,下面就介绍SPDK可能用到的一些知识。

DPDK支持多线程编程,以lcore表示一个CPU。DPDK启动时,会自动检测系统中的CPU信息,并根据用户指定的-c参数(以hex值表示cpu mask信息)运行线程。为了方便线程间的通信和管理,DPDK将这些线程分为一个Master线程和其他Slave线程。Master线程是用户运行程序的主进程,Slave线程用来运行用户自定义的程序。通过这个方式,省去了用户自己创建和管理线程的步骤。下图是SPDK用这个模型跑的一个例子(见examples/perf)。

SPDK预备知识-DPDK_第2张图片

如上图右侧,在用户程序Main()函数中,调用rte_eal_init()初始化。DPDK通过RTE_LCORE_FOREACH_SLAVE遍历所有的slave CPU(通过RTE_ROLE标志),创建slave线程。这些线程一开始执行eal_thread_loop函数,等待用户指示。接着在Main()中调用rte_eal_remote_launch()设置自定义程序。rte_eal_remote_launch把传入的参数(执行函数,cpu_id等)保存到一个结构体,然后通过Pipe跟与cpu_id相对应的slave建立通信。slave中的eal_thread_loop()收到通知后就调用那个结构体中的函数执行。具体的过程如下。

SPDK预备知识-DPDK_第3张图片

对DPDK了解的一定听说过PMD,即Poll Mode Driver。DPDK中所有的PMD都按照固定的方法编写。具体说,就是定义一个rte_driver变量,然后使用PMD_REGISTER_DRIVER()将其注册到系统中。当运行程序时,rte_driver中的init函数就会先执行。通常将初始化工作放在这个init函数中。下面我们来看看,Mellanox 的mlx4 PMD是怎么做的。第1和2步在mlx4 PMD代码中,3和4是驱动的注册和调用过程。可以看到,mlx4 PMD被放到一个dev_driver_list列表中,当rte_eal_dev_init()调用时执行这些列表中driver的init函数。

SPDK预备知识-DPDK_第4张图片

由于mlx4是PCIe设备,mlx4 PMD将init指定给rte_mlx4_pmd_init(),这个函数向另一个列表注册了自己的mlx4_driver.pci_drv。这个列表用来记录当前DPDK中注册的Userspace PCI Driver。就像PCI Bus上的NVMe驱动一样,当DPDK枚举系统中的PCIe设备时,会根据其Class Code(NVMe设备为0x080201)选择相应的驱动。在DPDK中,则执行这个驱动中的devinit函数。

SPDK预备知识-DPDK_第5张图片

可以看到,虽然有两个driver列表,但是它们的功能和调用时间是不一样的,rte_drive.init在DPDK初始化时调用,是DPDK下所有Driver必须实现的;eth_driver.pci_drv.devinit在检测到设备时调用,用来初始化PCIe设备。正是因为网络设备类型繁多,接口不定,各个厂家都有自己的驱动,导致了复杂的驱动加载。NVMe就不同了,大家准守相同的规则,所以SPDK的NVMe驱动简化了上述过程。

要编写PCIe驱动,必须能够访问BAR空间。对于用户态的程序来说,是不能直接访问BAR空间的,DPDK使用UIO来达到这个目的。UIO还有一个特点就是让用户态的驱动也可以使用中断(Linux系统中,中断只能通过内核线程处理,UIO也只是通过内核函数设置一个计数器,当用户态的程序读这个计数器的时候就知道有没有新的中断了,DPDK据此特别启用了一个线程来管理中断)。用户可以通过uio_pci_generic申明一个PCIe设备作为UIO注册到PCI Bus上。uio_pci_generic默认编译到内核中,跟前一期讲的内核NVMe驱动是同等类型(pci_driver),可以通过modprobe加载。但是uio_pci_generic没有匹配特别的device,需要用户手动配置。SPDK有一个setup脚本,这个脚本会把设备与内核的NVMe驱动unbind,再绑定到uio_pci_generic上。

当把nvme设备bind到uio_pci_generic上后,PCI bus就会去调用它的probe函数。uio_pci_generic的probe函数如下,注册了UIO设备。

SPDK预备知识-DPDK_第6张图片

bind到uio_pci_generic的设备会出现在/dev目录下,此后,就可以通过/dev/uio#操作设备了 。

SPDK预备知识-DPDK_第7张图片

下图是我整理的DPDK通过UIO映射BAR空间的函数调用关系。DPDK通过打开/dev目录下的uio设备,使用mmap函数将BAR空间提供给应用程序。这个过程看起来还是比较复杂的。SPDK中由于没有使用中断,默认选择了另一种访问PCI的库libpciaccess.so。但是为了支持DPDK,还是需要将UIO绑定到NVMe设备上(同时也可以避免和内核NVMe驱动产生冲突)。

SPDK预备知识-DPDK_第8张图片


最后,再介绍下DPDK的初始化。DPDK提供了一个初始化函数rte_eal_init()。前面提到的master和slave线程,设备扫描和驱动加载都是在这个函数中进行的。另外,DPDK还使用Hugepages给读写数据预留内存空间。所有要使用DPDK的程序(包括SPDK中的应用程序),都必须先调用这个初始化函数。

总的来说,SPDK还是有不少变化,并没有完全跟随DPDK的设计。下一篇将进一步介绍SPDK对NVMe的实现。

原文链接:https://mp.weixin.qq.com/s/N9068mpCnv5lEbTWcuyQpw


 
学习更多dpdk视频
DPDK 学习资料、教学视频和学习路线图 :https://space.bilibili.com/1600631218
Dpdk/网络协议栈/ vpp /OvS/DDos/NFV/虚拟化/高性能专家 上课地址: https://ke.qq.com/course/5066203?flowToken=1043799
DPDK开发学习资料、教学视频和学习路线图分享有需要的可以自行添加学习交流q 群909332607备注(XMG) 获取

你可能感兴趣的:(网络,服务器,java,网络协议,架构)