写点什么

DPDK 源码分析之 rte_eal_init(二)

作者:于顾而言
  • 2022 年 9 月 17 日
    江苏
  • 本文字数:4507 字

    阅读完需:约 15 分钟

DPDK源码分析之rte_eal_init(二)

gdb 前的准备

如果想要调试到 dpdk 源码的内部,则需要在编译的时候指定一些命令行参数:

meson -Dexamples=all -Dbuildtype=debug build
复制代码

这样我们就可以进入到 ./build/app 进行 gdb 调试了:



无情 gdb

rte_cpu_is_supported





dpdk 运行时会检查 cpu 是否支持,它通过 cpuid 将 cpu 的具体信息存放在 4 个寄存器中,这里 feat leaf 为 1 即 EBX=1 存放处理器签名(Processor Signiture)和功能(Feature)位。dpdk 实现建立一个 fearture_map,即检查哪个寄存器的某一位来查看 cpu 是否支持 dpdk 功能。

__atomic_compare_exchange_n


原子操作,run_once 置 1

pthread_self



获取主线程的线程 id,他肯定是主线程,因为还没创建别的线程呢。

rte_eal_cpu_init

这一步是读取 cpu 信息,并填充到每一个 lcore_config[lcoreid]中,我的虚拟机是 1 个真实 CPU 虚拟出的 4 个逻辑 cpu,numa 有 1 个节点 node0,包含 cpu0,cpu1,cpu2,cpu3。





可见 DPDK 的逻辑核赋值为 4 个,其中 coreid 即实体 cpu id 只有一个,且 numa node 也只有一个。dpdk 实现默认情况下,dpdk 逻辑核映射到 CPU 中为一一对应,因此 cpuset 分别 1,2,4,8 -》cpu0,cpu1,cpu2,cpu3->lcore0, lcore1,lcore2,lcore3。后面未用到的 lcore 的 index 会置为-1。

eal_parse_args

这一步是进行命令行解析,我一般运行时加的时会用 -c f -n 4 指定可以被 dpdk 接管的 CPU 以及内存读写采用 4 通道。命令行解析的关键函数为

opt = getopt_long(argc, argvopt, eal_short_options, eal_long_options, &option_index))
复制代码

它通过事先指定的短命令和长命令进行解析,其中 optionindex 为解析到长 option 在 eallong_options 数组的索引,opt 为解析到短 option 字符即 c 和 n:




对于 c 指定 cpu 掩码来说,首先看是否与之前的已经设为服务核,接着调用 eal_parse_coremask,解析得到新的 ldcore[],再用新值去更新 update_lcore_config,如果 dpdk 逻辑核为-1 对应内部配置逻辑核应该 ROLE_OFF。如果是缺省的,那么会调用 eal_auto_detect_cores 获取可用的逻辑核,关键函数为 pthread_getaffinity_np 可以获取当前线程可以被哪些 CPU 调度,并默认 lcoreid0 为 master core.



  • 对于 n 指定内存通道数时,实现很简单 conf->force_nchannel = atoi(optarg);

  • 接着会创建运行时目录,run_dir

XDG_RUNTIME_DIR:指向用户专用用户可写目录的路径,该目录与计算机上的用户登录时间绑定。它是在用户首次登录时自动创建的,并在用户的最终注销时被删除。如果用户登录一次,然后再次注销,然后再次登录,则目录之间的内容将丢失



DPDK 的线程分为控制线程和数据线程,控制线程默认绑定到 MASTER 核上,除非仍有 CPU 可用,主要逻辑为首先将之前绑定的 cpuset 和控制线程的 cpuset 做或运算,然后取反,接着与当前线程的调度 cpuset 作与运算。



eal_plugins_init

插件机制,就是动态库,首先会通过 NOLOAD 加载.so 测试当前运行程序是否是动态链接库加载 dpdk,如果是静态库加载直接返回,否则将会加载插件,插件的路径可以由-d 参数指定,也可以由 RTE_EAL_PMD_PATH(/usr/local/lib64/dpdk/pmds-21.0)指定,函数会加载-d 指定的 so 或者将指定路径下满足条件的插件加入管理(eal_plugin_add),并通过 dlopen 加载



eal_trace_init

这个函数是对一些事件或变量进行跟踪,一般用于多线程同步,等待时间测量等。DPDK 使用的是CTF格式 ,为了记录非常频繁的底层操作,一般开销为 20 个时钟周期。使用 option OPT_TRACE_NUM 和 OPT_TRACE_DIR_NUM 可以开启这个功能,这部分实现太复杂了,后续 debug dpdk-test 时专门写一个跟踪器的分析。我这边直接 return 掉。



rte_config_init

创建/var/run/dpdk/rte/config 文件,并将主进程的虚拟空间地址(void *) 0x100000000(Linux kernel uses a really high address as starting address for serving mmaps calls) mmap 该文件中,映射的配置大小为 sysconf(_SC_PAGE_SIZE)的倍数,这也是内存对齐。最后会把这个地址记录下来,后续从进程也会参照这个使用一样的逻辑地址。

关键库函数:


sysconf 函数用来获取系统执行的配置信息。例如页大小、最大页数、cpu 个数、打开句柄的最大个数等等。


ftruncate()会将参数 fd 指定的文件大小改为参数 length 指定的大小。参数 fd 为已打开的文件描述词,而且必须是以写入模式打开的文件。


fcntl 函数功能依据 cmd 的值的不同而不同,fcntl(mem_cfg_fd, F_SETLK, &wr_lock)设置文件读写锁。


mmap 将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系,函数返回被映射区的指针



rte_eal_intr_init

这个函数是创建处理中断的线程,这个线程也是绑定在 master core 上,它首先创建一个 pipe 用来后续与 slave 线程通信,然后 epoll add 监听这个 fd,直到有数据时才会处理。另外,uio device file descriptor 也会加入到监听,这个就是在用户空间模拟设备中断处理,我这边没有配置设备直接跳过。中断线程初始化和亲和性绑定之后设置了线程屏障,refcnt=2,即等待中断线程创建完毕后主线程才继续 rte_eal_int()剩下函数。

pipe 函数来创建管道用于进程间通信,fd 参数返回两个文件描述符,fd[0]指向管道的读端,fd[1]指向管道的写端。fd[1]的输出是 fd[0]的输入。


epoll_create(); // 创建监听红黑树


epoll_ctl(); // 向书上添加监听 fd


epoll_wait(); // 有监听 fd 事件发送--->返回监听满足数组--->判断返回数组元素


barrier:多个线程约定一个栅栏,只有当所有的线程都达到这个栅栏时,栅栏才会放行,否则到达此处的线程将被阻塞



rte_eal_alarm_init

简单的函数,timerfd_create 创建文件描述符来通知定时器是否到期

rte_eal_timer_init

简单的函数,获取 cpu 时钟运行频率(tsc_hz)



memory 相关

  • rte_mp_channel_init

unlink:执行 unlink()函数并不一定会真正的删除文件,它先会检查文件系统中此文件的连接数是否为 1,如果不是 1 说明此文件还有其他链接对象,因此只对此文件的连接数进行减 1 操作。若连接数为 1,并且在此时没有任何进程打开该文件,此内容才会真正地被删除掉


flock:文件锁是一种文件读写机制,在任何特定的时间只允许一个进程访问一个文件


socket:多进程通信,sockert()->bind()->connect()->accept()->read()/write()

新建 unix socket 用于主从,从从进程通信,并新建 mp 处理线程监听消息并根据 msg name 进行相应的 action(msg, s->sun_path)处理。



  • eal_hugepage_info_init

读取巨页内存信息




  • rte_eal_memzone_init

DPDK学习记录9 - 内存初始化2之rte_eal_memzone_init_jeawayfox的博客-CSDN博客_dpdk eal


rte_eal_memzone_init 主要就是调用了 rte_fbarray_init。fbarry_init 用来创建一块内存,存放一组 element,每个 element 的大小相同。


这组 element 的长度为 RTE_MAX_MEMZONE=2560 个,每个 element 的大小是 sizeof(struct rte_memzone)=72bytes,申请并初始化成功之后挂到 rte_config->mem_config->memzones 下,且命名为“memzone”。


更新全局变量 next_baseaddr(之前 mmap 地址是 0x1000000000),作为下次进入申请时的 baseaddr。



  • rte_eal_memory_init

dpdk内存管理——内存初始化-lvyilong316-ChinaUnix博客


mmap 系统调用可以设置为共享的映射,dpdk 的内存共享就依赖于此,在这多个进程中,分为两种角色,第一种是主进程(RTE_PROC_PRIMARY),第二种是从进程(RTE_PROC_SECONDARY)。主进程只有一个,必须在从进程之前启动,负责执行 DPDK 库环境的初始化,从进程 attach 到主进程初始化的 DPDK 上,主进程先 mmap hugetlbfs 文件,构建内存管理相关结构将这些结构存入 hugetlbfs 上的配置文件 rte_config,然后其他进程 mmap rte_config 文件,获取内存管理结构,dpdk 采用了一定的技巧,使得最终同样的共享物理内存在不同进程内部对应的虚拟地址是完全一样的,意味着一个进程内部的基于 dpdk 的共享数据和指向这些共享数据的指针,可以在不同进程间通用。

这块有点复杂啊,后面再看吧,怪我过分菜-_-|||。

  • rte_eal_malloc_heap_init

DPDK学习记录12 - 内存初始化5之rte_eal_malloc_heap_init_jeawayfox的博客-CSDN博客

DPDK : 解析内存初始化的过程_pcokk的博客-CSDN博客_eal_legacy_hugepage_init

用于多进程环境下的内存分配,其中每一个 socket 会对应一个 heap



rte_eal_log_init

设置日志写入函数和日志流,用于写入标准错误输出(终端)或 syslog.conf 配置的日志路径中

openlog:此函数用来打开一个到系统日志记录程序的连接,打开之后就可以用 syslog 或 vsyslog 函数向系统日志里添加信息了


fopencookie:此函数定义文件流的写入,关闭等函数




or_each_worker_thread_create

这个会在后面解析 example:dpdk-helloworld 分析

DPDK源码分析之hello_world

设备扫描

  • rte_bus_scan

  • rte_bus_probe

首先进行总线注册(dpaa,fslmc,ifpga,pci,vdev,vmbus),然后遍历各个总线上的设备和驱动,接着以具体型号的设备以参数形式进行驱动绑定,完成在用户空间的文件接口注册。



以 PCI 设备举例,

pci_scan_one (扫描/sys/bus/pci/devices 目录下的设备)

-eal_parse_sysfs_value (/* get device id */) -》

-pci_get_kernel_driver_by_path(扫描/sys/bus/pci/devices/driver 目录下的设备驱动) -》

rte_pci_match(检查驱动知否支持设备)-》

rte_pci_map_device (设备分配资源) -》

dr->probe(设备驱动绑定)




Reference

dpdk_lcore_note_DPDK_lcore_学习笔记_Andrew Yang's Note-CSDN博客

DPDK — IGB_UIO,与 UIO Framework 进行交互的内核模块 - 走看看 (zoukankan.com)

Intel CPU的CPUID指令(转载)_weixin_34337265的博客-CSDN博客

PCIe总线_yundanfengqing_nuc的专栏-CSDN博客

集成存储器控制器(IMC)功能_zdx19880830的专栏-CSDN博客_imc控制器

/proc/cpuinfo 文件分析(查看CPU信息)_GoodIdea-CSDN博客_cpuinfo怎么看

多核多线程——pthread_setaffinity_np,cpulimit分析CPU资源对应用程序的影响_weixin_34268579的博客-CSDN博客

DPDK跟踪库:trace library_RToax-CSDN博客

内存序列-memor order_字节跳动 内推找我-CSDN博客_memory_order_relaxed

Linux fcntl函数详解 - 夕相待 - 博客园 (cnblogs.com)

DPDK之内存管理 - AISEED - 博客园 (cnblogs.com)

DPDK 中断机制 eal_intr_handle_interrupts_ZP1015-CSDN博客

如果这篇文章说不清epoll的本质,那就过来掐死我吧! (1) - 知乎 (zhihu.com)

进程间的通信方式——pipe(管道)_sky_Mata的博客-CSDN博客_管道通信

Linux编程- pthread_barrier_xxx介绍_Ailson Jack的专栏-CSDN博客

epoll原理详解及epoll反应堆模型_青萍之末的博客-CSDN博客_epoll

IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)_智障大师 的专栏-CSDN博客_同步 异步 阻塞 非阻塞

阻塞、非阻塞、异步、同步以及select/poll和epoll_李春喜的专栏-CSDN博客_epoll 同步非阻塞

linux timerfd_create说明简单翻译_Amrf的博客-CSDN博客

linux日志系统介绍 —— syslog(),openlog(),closelog()_wangyuling1234567890的专栏-CSDN博客_openlog

fopencookie函数的使用说明_insswer的专栏-CSDN博客

总线、设备、驱动模型_笔记专栏-CSDN博客_总线设备驱动模型

DPDK内存篇(一): 基本概念_weixin_37097605的博客-CSDN博客

DPDK内存篇(二): 深入学习 IOVA_weixin_37097605的博客-CSDN博客

DPDK_u013982161的专栏-CSDN博客

flock()函数--文件锁_hunter-CSDN博客

Linux的SOCKET编程详解_黄规速博客:学如逆水行舟,不进则退-CSDN博客_linux socket

发布于: 刚刚阅读数: 5
用户头像

于顾而言

关注

| 且将新火试新茶 × 诗酒趁年华诗酒 2022.09.10 加入

| SASE | SangFor | Senior Developer | 知乎专栏:https://www.zhihu.com/people/whisper-of-the-Koo

评论

发布
暂无评论
DPDK源码分析之rte_eal_init(二)_DPDK_于顾而言_InfoQ写作社区