3. 环境抽象层
环境抽象层(Environment Abstraction Layer,下文简称EAL)是对操作系统底层资源(如内存空间)的抽象, 用于DPDK应用程序访问底层资源。 EAL隐藏了不同操作系统访问底层资源的接口, 给DPDK应用程序提供了统一的访问接口。 EAL的初始化例程负责底层资源的申请,如内存,PCI设备等。
EAL中提供的典型服务有:
DPDK库的加载和启动: DPDK库和DPDK应用在编译阶段被链接成一个应用程序。而且库的加载需要一些额外的操作,这些操作由EAL完成,应用开发者无需特别关心。
CPU亲和性/分配: EAL能把一个执行单元分配到指定CPU上运行。
系统内存预留: EAL预留了各种内存,比如,用于设备交互的物理内存区域。
PCI地址抽象: EAL提供了访问PCI设备地址空间的接口。
跟踪和调试功能: 日志、dump堆栈、panic等等。
实用功能: 自旋锁和原子计数器(这类函数libc中没有提供)。
CPU特性标识: 在程序运行期间决定CPU是否支持指定的特性,如Intel® AVX。 判断当前CPU是否和DPDK库所支持的CPU匹配。
3.1. Linux下的EAL
在Linux中,DPDK程序以一个用户态程序运行,使用的线程库是pthread。 设备的PCI信息和地址使用linux的sysfs内核接口和内核模块(uio_pci_generic或者igb_uio)获取。 内存则是通过mmap映射到程序内存空间的。
EAL在hugetlbfs(巨页内存,提高性能) 中通过mmap()申请物理内存,这些内存是提供给DPDK服务层使用的, 如内存池库.
在DPDK服务层初始化的时候,会调用线程亲和性设定函数(pthread提供), 让每一个执行单元绑定到一个逻辑CPU上,并让每个执行单元以一个用户线程运行。
时钟则是由CPU的TSC或者内核的HPET提供(通过mmap调用)。
3.1.1. EAL初始化和核心启动
glibc中的启动函数(入口函数)完成了程序初始化的一部分,也会检查当前CPU和DPDK程序的架构是否匹配。 然后主函数main()被调用。核心的初始化和启动是由rte_eal_init() 完成的。 它由一组pthread调用组成(具体有pthread_self(), pthread_create(), and pthread_setaffinity_np())。
图 3.1Linux环境下EAL的初始化
注解
对象(如内存区域、ring、内存池、lpm表、哈希表)的初始化应该在程序初始化阶段在主核上完成, 因为这些对象的创建和初始化函数不是线程安全的。 但是,这些对象一旦初始化完成,对它们的使用是线程安全的。
3.1.2. 多进程支持
Linux EAL允许以多进程模式开发应用,详细参考多进程支持
3.1.3. 内存映射和内存预留
初始化阶段EAL会从hugetlbfs申请大量且地址连续的内存, 这些内存可以通过EAL提供的"内存区预留API"提供给上层应用使用。 API会把这个内存区对应的物理地址返回给用户。
注解
内存预留使用rte_malloc提供的API。rte_malloc的内存也是从hugetlbfs文件系统中获取的。
3.1.4. 对无hugetbls的Xen Dom0支持
目前的内存管理实现是基于Linux内核的巨页机制。但是,Xen Dom0不支持巨页,因此需要额外的rte_dom0_mm内核模块完成这项工作。
EAL使用IOCTL接口通知rte_dom0_mm内核模块申请指定大小内存,并获取所有内存段信息,然后EAL使用MMAP接口映射申请的内存。 对于每个内存段它的物理地址都是连续的,但是硬件地址是2MB连续的。
3.1.5. PCI访问
EAL通过扫描/sys/bus/pci获取PCI总线上设备信息。 为了访问PCI设备内存,uio_pci_generic内核模块会提供/dev/uioX设备文件和sysfs资源文件, 通过这些就可以使用mmap把PCI设备内存映射到应用程序内存空间。 DPDK定制的模块igb_uio也可以用于此。两种模块都使用了uio这个内核特性(用户空间I/O)
3.1.6. Per-lcore和共享变量
注解
lcore(逻辑核)指的是处理器的逻辑执行单元,有时也称为硬线程
共享变量是在所有线程之间共享的,所有线程都可以访问。 Per-lcore变量则是线程本地存储,线程只能访问自己的Per-lcore变量,Per-lcore变量使用Thread Local Storage(TLS) 实现。
3.1.7. 日志
EAL提供了日志API。 在Linux中,默认情况下,日志会被发送到syslog和控制台。 用户也可以覆盖这些日志函数,使用自定义的日志机制。
3.1.7.1. 跟踪和调试功能
glibc中的调试函数可以把程序堆栈dump出来。 EAL提供的rte_panic()函数能够自动发出SIG_ABORT信号,这个信号能触发core文件的生成, 然后开发者可以通过gdb读取core文件排除错误。
3.1.8. CPU特性标识
EAL能够在运行时查询CPU信息(rte_cpu_get_feature()函数)并判断哪些CPU特性可用。
3.1.9. 用户空间中断事件
主线程(Host Thread)的中断和告警的处理
EAL初始化时创建了一个专用的主线程用于检测中断(通过轮询UIO设备描述符/dev/uioX)。 开发者通过EAL提供的函数为指定中断事件注册/注销回调函数,事件发生时回调函数会被这个主线程异步调用。 对于NIC中断,EAL也提供了同样的定时回调。
注解
在DPDK PMD(轮询模式驱动)中,主线程处理的中断事件只有链路状态变更(链路连接和断开通知)和设备意外移除事件。
Rx(接收)中断事件
PMD提供的数据包接收和发送例程允许在线程中轮询执行。 在网络吞吐量小的时候,为了降低空闲轮询可以先暂停轮询,然后等待一个“唤醒”事件的发生。 Rx中断事件可以作为首选“唤醒”事件,但也可能有其他事件作为“唤醒”事件。
EAL为事件驱动线程模式提供了事件API。 以Linux环境中的应用为例,它的事件驱动依赖于epoll。每个事件驱动的线程会监视一个epoll实例, 所关心的“唤醒”事件描述符会被加到这个epoll实例中。 事件描述符通过UIO/VFIO创建和映射到中断向量表中。 对于BSD应用,kqueue也是一种方式,只是目前还没有实现。
EAL会负责事件描述符和中断向量之间的映射,然而设备的队列和中断向量之间的映射是由设备自己完成的, EAL无法感知到这些中断向量上面的中断事件,因此以太网设备驱动会负责把这些中断向量和事件描述符映射起来。(原文:EAL initializes the mapping between event file descriptors and interrupt vectors, while each device initializes the mapping between interrupt vectors and queues. In this way, EAL actually is unaware of the interrupt cause on the specific vector. The eth_dev driver takes responsibility to program the latter mapping.)
注解
每个队列的Rx中断事件仅在VFIO中可用(VFIO支持multiple MSI-X vector)。在UIO中,Rx中断和其他中断共享同一个中断向量, 这种情况下,如果Rx中断和LSC(link status change)中断同时启用的话(intr_conf.lsc == 1 && intr_conf.rxq == 1),只有前者有效。
Rx中断能够使用ethdev API进行控制/启用/关闭 - 'rte_eth_dev_rx_intr_*'。PMD不支持的操作返回失败。 intr_conf.rxq标志是用来开启设备Rx中断的。
设备移除事件
当设备从总线上移除时会触发该事件。事件发生时其底层资源可能已经不可用了(也就是PCI映射解除)。 PMD要确保这种情况下,应用仍能够安全地使用它的回调。
设备移除事件的订阅和链接状态变更订阅一样。因此执行的环境也一样,也就是专门用于处理中断的主线程。
考虑这样一种情况,应用程序去关闭一个已经发出设备移除事件的设备。这种情况下,rte_eth_dev_close()调用会注销设备移除事件的回调。 要小心的是不要在中断处理上下文中关闭设备,应该通过其他方式去关闭设备。



