DPDK 源码分析之 rte_eal_init(一)

EAL 是什么
环境抽象层(EAL)负责获得对底层资源(如硬件和内存空间)的访问。对于应用程序和其他库来说,使用这个通用接口可以不用关系具体操作系统的环境细节。rte_eal_init 初始化例程负责决定如何分配操作系统的这些资源(即内存空间、设备、定时器、控制台等等)。
EAL 提供的典型服务是:
DPDK 加载和启动:DPDK 及其提供的应用程序是被链接为单个应用程序,因此必须由某种方式加载。
核心绑定/分配过程:EAL 提供用于将执行单元分配给特定核心的机制以及创建其执行实例。
系统内存预留:EAL 便于预留不同的存储区,例如,用于设备交互的物理内存区域。
跟踪和调试功能:日志,打印栈信息,类似于 printf 函数功能等。
实用功能:标准 c 中未提供的旋转锁和原子锁功能。
CPU 功能标识:确定运行时的一些 CPU 特性,以确定该 CPU 是否支持 DPDK 功能
中断处理:提供注册/取消注册的一系列接口,用于出现中断源时调用
报警功能:提供注册/取消注册的一系列接口,在特定时间或事件时调用
EAL 涉及的专业名词
__atomic_compare_exchange_n
Built-in Function:bool__atomic_compare_exchange_n(type*ptr,type*expected,typedesired, bool weak, int success_memorder, int failure_memorder)
此内置功能是 C++11 用于多线程中对共享变量的原子比较和交换操作。
This compares the contents of*ptr
with the contents of*expected
. If equal, the operation is aread-modify-writeoperation that writesdesiredinto*ptr
. If they are not equal, the operation is areadand the current contents of*ptr
are written into*expected
.
If desiredis written into*ptr
then true is returned,Otherwise, false is returned.
mmap 映射
mmap 是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用 read,write 等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。如下图所示:

总结来说,常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的 buffer 在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。
而使用 mmap 操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。
总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而 mmap 操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。
runtime
应用程序运行过程中的环境参数
uio device
UIO 出现的原因是,对于标准的硬件设备如 PCI 设备,USB 设备等。它们被不同的内核子系统支持。这些标准的设备的驱动编写较为容易而且容易维护。很容易加入主内核源码树,但是有一些设备如 I/O 卡,现场总线接口或者定制的 FPGA。通常这些非标准设备的驱动被实现为字符驱动。这些驱动使用了很多内核内部函数和宏。而这些内部函数和宏是变化的。这样驱动的编写者必须编写一个完全的内核驱动,而且一直维护这些代码。像这种设备如果把驱动放入 Linux 内核,不但增大了内核的负担而且还很少使用,所以 UIO 框架应运而生。
一个设备驱动的主要任务有两个:1. 存取设备的内存 2. 处理设备产生的中断
对于第一个任务,UIO 核心实现了 mmap()可以处理物理内存(physical memory),虚拟内存(virtual memory)。第二个任务,如果用户空间要等待一个设备中断,它只需要简单的阻塞在对 /dev/uioX 的 read()操作上。当设备产生中断时,read()操作立即返回。UIO 也实现了 poll()系统调用,你可以使用 select()来等待中断的发生。其次,对设备的控制还可以通过/sys/class/uio 下的各个文件的读写来完成。你注册的 uio 设备将会出现在该目录下。假如你的 uio 设备是 uio0 那么映射的设备内存文件出现/sys/class/uio/uio0/maps/mapX,对该文件的读写就是对设备内存的读写。

内核操作:1. - 使能设备 2. - 申请资源 3. - 读取并记录配置信息 4. - 注册 uio 设备// uio_register_device()
用户态操作:5. 判断是否产生了硬件中断 6. 响应硬件中断
Sysfs
Sysfs 是 Linux 2.6 所提供的一种虚拟文件系统。这个文件系统不仅可以把设备(devices)和驱动程序(drivers)的信息从内核输出到用户空间,也可以用来对设备和驱动程序做设置。
epoll
在高并发场景,随epoll
使用了内核文件级别的回调机制 O(1)。它有两种触发机制,水平触发(level-triggeredsocke),即 t 接收缓冲区不为空 有数据可读,读事件一直触发,和边沿触发(edge-triggered),即 socket 的接收缓冲区状态变化时触发读事件,即空的接收缓冲区刚接收到数据时触发读事件,边沿触发仅触发一次,水平触发会一直触发。
具体原理如下:
调用 epoll_create 时,内核除了帮我们在 epoll 文件系统里建了个 file 结点,在内核 cache 里建了个 红黑树 用于存储以后 epoll_ctl 传来的 socket 外,还会再建立一个list链表,用于存储准备就绪的事件.
当 epoll_wait 调用时,仅仅观察这个 list 链表里有没有数据即可。有数据就返回,没有数据就 sleep,等到 timeout 时间到后即使链表没数据也返回。所以,epoll_wait 非常高效。而且,通常情况下即使我们要监控百万计的句柄,大多一次也只返回很少量的准备就绪句柄而已,所以,epoll_wait 仅需要从内核态 copy 少量的句柄到用户态而已.
当我们执行 epoll_ctl 时,除了把 socket 放到 epoll 文件系统里 file 对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪 list 链表里。所以,当一个 socket 上有数据到了,内核在把网卡上的数据 copy 到内核中后就来把 socket 插入到准备就绪链表里了。

创建 pipe
pipe 用于实现进程间的通信,工作原理如下:1)父进程创建管道,得到两个⽂件描述符指向管道的两端 2)父进程 fork 出子进程,⼦进程也有两个⽂件描述符指向同⼀管道。3)父进程关闭 fd[0],子进程关闭 fd[1],即⽗进程关闭管道读端,⼦进程关闭管道写端(因为管道只支持单向通信,父进程关闭 fd[1]子进程关闭 fd[0]也可以)。
⽗进程可以往管道⾥写,⼦进程可以从管道⾥读,管道是⽤环形队列实现的,数据从写端流⼊从读端流出,这样就实现了进程间通信。
管道的特点如下:1.管道只允许具有血缘关系的进程间通信,如父子进程间的通信。 2.管道只允许单向通信。 3.管道内部保证同步机制,从而保证访问数据的一致性。 4.面向字节流 5.管道随进程,进程在管道在,进程消失管道对应的端口也关闭,两个进程都消失管道也消失。
bus 设备
总线(bus)是 linux 发展过程中抽象出来的一种设备模型,为了统一管理所有的设备,内核中每个设备都会被挂载在总线上,这个 bus 可以是对应硬件的 bus(i2c bus、spi bus)、可以是虚拟 bus(platform bus)。
驱动程序对应 driver、需要的具体型号的硬件资源对应 device,将其挂在 bus 上。将 driver 注册到 bus 上,当用户需要使用 AT24C01 时,以 AT24C01 的参数构建一个对应 device,注册到 bus 中,bus 的 match 函数匹配上之后,调用 probe 函数,即可完成 AT24C01 的初始化,完成在用户空间的文件接口注册。
以此类推,添加所有的 AT24CXX 设备都可以以这样的形式实现,只需要构建一份 device,而不用为每个设备重写一份驱动,提高了复用性,节省了内存空间。
linux 将设备挂在总线上,对应设备的注册和匹配流程由总线进行管理,在 linux 内核中,每一个 bus,都由 struct bus_type 来描述:
name : 该 bus 的名字,这个名字是这个 bus 在 sysfs 文件系统中的体现,对应/sys/bus/$name.
dev_name : 这个 dev_name 并不对应 bus 的名称,而是对应 bus 所包含的 struct device 的名字,即对应 dev_root。
dev_root:bus 对应的 device 结构,每个设备都需要对应一个相应的 struct device.
match:bus 的 device 链表和 driver 链表进行匹配的实际执行回调函数,每当有 device 或者 driver 添加到 bus 中时,调用 match 函数,为 device(driver)寻找匹配的 driver(device)。
uevent:bus 时间回调函数,当属于这个 bus 的设备发生添加、删除、修改等行为时,都将出发 uvent 事件。
probe:当 device 和 driver 经由 match 匹配成功时,将会调用总线的 probe 函数实现具体 driver 的初始化。事实上每个 driver 也会提供相应的 probe 函数,先调用总线的 probe 函数,在总线 probe 函数中调用 driver 的 probe 函数。
remove:移除挂载在设备上的 driver,bus 上的 driver 部分也会提供 remove 函数,在执行移除时,先调用 driver 的 remove,然后再调用 bus 的 remove 以清除资源。
--iova-mode=<pa|va>
DPDK 是一个用户态应用框架,使用 DPDK 的软件可以像其他软件一样使用常规虚拟地址。但除此之外,DPDK 还提供了用户态 PMD 和一组 API,以实现完全以用户态执行 IO 操作
作为 PA 的 IOVA 模式下,分配到整个 DPDK 存储区的 IOVA 地址都是实际的物理地址,而虚拟内存的分配与物理内存的分配相匹配。该模式的一大优点就是它很简单:它适用于所有硬件(也就是说,不需要 IOMMU),并且它适用于内核空间(将真实物理地址转换为内核空间地址的开销是微不足道的)。
作为 VA 的 IOVA 模式不需遵循底层物理内存的分布。而是重新分配物理内存,与虚拟内存的分配匹配,即使底层物理内存可能不存在,内存看上去还是 IOVA 连续的。由于重新映射,IOVA 空间片段化的问题就变得无关紧要。不管物理内存被分段得多么严重,它总能被重新映射为 IOVA-连续的大块内存。

soket_id
socket_id 存放着物理 cpu 号
TSC frequency
Linux 中有 3 种 timer:
1、Real Time Clock(RTC)
2、Programmalbe Interval Timer(PIT)
3、Time Stamp Counter.(TSC)
其中 RTC 是位于 CMOS 中的,其频率范围是 2HZ--8192HZ.
PIT 主要由 8254 时钟芯片实现的
TSC 的主体是位于 CPU 里面的一个 64 位的 TSC 寄存器。每个 CPU 时钟周期其值加一。
numa socket
socket 是一个物理上的概念,指的是主板上的 cpu 插槽。node 是一个逻辑上的概念,对应于 socket。
numa 架构下,访问本地内存的速度要快于访问远端内存的速度,访问速度与 node 的距离有关系。

EAL 源码框图

Reference
3. Environment Abstraction Layer — Data Plane Development Kit 21.11.0 documentation (dpdk.org)
DPDK初始化 - 坚持,每天进步一点点 - 博客园 (cnblogs.com)
(21条消息) 深度分析mmap:是什么 为什么 怎么用 性能总结_精诚所至-CSDN博客_mmap
Using the GNU Compiler Collection (GCC): __atomic Builtins
(21条消息) Linux 设备驱动之 UIO 机制(测试 UIO 机制)_ZP1015-CSDN博客_linux uio
(21条消息) 进程间的通信方式——pipe(管道)_sky_Mata的博客-CSDN博客_管道通信
linux设备驱动程序--bus - 牧野星辰 - 博客园 (cnblogs.com)
(21条消息) DPDK内存篇(二): 深入学习 IOVA_老马农的博客-CSDN博客
(23条消息) numa下socket node cpu thread的关系_冰糖银耳盅的博客-CSDN博客
(23条消息) DPDK18.11.11内存初始化流程总结_aixueai的博客-CSDN博客
(23条消息) intel dpdk rte_eal_cpu_init() 函数介绍_编码人生——朝阳_tony-CSDN博客
(23条消息) DPDK跟踪库:trace library_RToax-CSDN博客
DPDK中断机制简析 - MerlinJ - 博客园 (cnblogs.com)
https://blog.csdn.net/xiajun07061225/article/details/9250579
DPDK的进程间通信机制 – 孙希栋的博客 (sunxidong.com)
(24条消息) DPDK学习记录9 - 内存初始化2之rte_eal_memzone_init_jeawayfox的博客-CSDN博客
版权声明: 本文为 InfoQ 作者【于顾而言】的原创文章。
原文链接:【http://xie.infoq.cn/article/c6addc3a11862f5fcbe99c9f9】。文章转载请联系作者。
评论