08.DPU网络开发SDK—DPDK(七)

接上次内容继续对rte_eal_init()所做的工作进行分析。
 
24. 内存初始化
24.2. 内存分配
调用eal_memalloc_init()来处理内存分配,前半部分根据进程是primary还是secondary走不同的流程,后半部分两者相同。

前后两个初始化过程中,用到了一个特殊的func,rte_memseg_list_walk(),该func的传入参数是一个rte_memseg_list_walk_t类型的函数指针及void *通用指针。在list_walk()中,会对rte_config.mem_config.memsegs列表中每一个memseg list依次调用函数指针指向的函数,且保证了整个过程中是在mem_config.memory_hotplug_lock锁的保护下进行的。

  • 以secondary方式
通过list_walk()来调用secondary_msl_create_walk()来为每个memseg list分配内存,该func中,根据primary的memseg list的大小,初始化一个local_memsegs结构,初始化还是调用rte_fbarray_init()来实现。primary的memseg list的大小从mem_config.memsegs获取到。

  • 以primary方式
该过程中,通过test_memfd_create执行memfd_create系统调用,测试当前系统是否支持创建匿名的内存共享,检查结果根据当前系统的一些配置情况决定是否以错误退出。

分primary和secondary之后,不管哪种方式初始化,都会执行fd_list_create_walk()。create_walk()对每个memseg list调用alloc_list(),alloc_list()会初始化文件描述符列表数组fd_list,每个memseg list对应一个数组项,数组项中会指向一块内存,用于存储文件描述符,这里的文件描述符即为匿名内存共享文件的描述符。

24.3. 初始化大页
初始化大页同样分primary和secondary

  • 以primary方式
调用rte_eal_hugepage_init(),如果internal_config设置了legacy_mem,那么调用eal_legacy_hugepage_init(),否则调用eal_dynmem_hugepage_init()。

 A. eal_dynmem_hugepage_init()
对于每一种大小类型的大页,确定好每个socket上页面的数量;统计出每个numa node上的内存大小;根据这些信息调用eal_dymem_calc_num_pages_per_socket()最终确定每种大小类型的页面的数量。

确定好这些信息之后,多次调用eal_memalloc_alloc_seg_bulk()来映射页面,该func通过list_walk()调用alloc_seg_walk()来完成这些工作。

B. eal_legacy_hugepage_init()
分为两种情况执行,一种禁用hugetlbfs情况下,另一种是在启用情况下。
 
a. 禁用hugetlbfs
此时允许的内存大小为64GB,页面大小为4KB,以此得出所需要的一个页面数量之后,调用eal_memseg_list_init_named()来重新初始化mem_config.memsegs[0]这个memseg list。然后通过eal_memseg_list_alloc()间接调用eal_get_virtual_area()为memseg分配虚拟地址,下一步通过mmap()系统调用创建匿名映射并获得匿名映射地址addr,接下来通过eal_memseg_list_populate()填充mem_config.memsegs[0]这个memseg list,以addr为基准计算出每个memseg的地址,填到相应的结构体中。
 
b. 启用hugetlbfs
首先统计出用了哪几种大小类型的大页内存,并且计算出总的大页内存页面数量,分别存放在used_hp数组和nr_hugepages当中。接下来开辟一块内存tmp_hp,用于存放nr_hugepages个struct hugepage_file结构,hugepage_file结构用于存放一些信息,比如该大页被映射到进程地址空间的哪个虚拟地址,大页物理地址是多少,所属socket_id是多少。

接下来,对于每一种页面大小类型的大页,做如下操作:

1.调用map_all_hugepages()映射该大小类型的所有大页,具体做法是调用open()打开大页在/sys文件系统中的文件,然后mmap()之后获取一个虚拟地址,并记录在结构体当中。

2.如果启用了物理地址,且IOVA的模式不是VA,那么调用find_physaddrs()获取到每个大页的物理地址并保存下来,否则调用set_physaddrs()设定一个伪物理地址。

3.调用find_numasocket()确定每个大页所属的socket_id。

4.根据物理地址将tmp_hp进行排序。

接下来根据tmp_hp中的信息,统计出每个socket每种大小类型的大页的数量,更新这些信息更新到internal_config.hugepage_info结构体当中,并最终确定每个socket下大页的数量。

接下来调用create_shared_memory()创建一个共享文件,大小为nr_hugepages个struct hugepage_file结构体,路径为/var/run/dpdk/rte/hugepage_data;接下来调用copy_hugepages_to_shared_mem()将该进程中分配到的大页信息写入到该文件当中。需要指出的是,到此为止所需要分配的内存就分配完了,后续不会再根据需要再额外分配大页内存,即使在DPDK进程运行过程中遇到内存耗尽的情况,所以在最后的收尾工作中,会将一些不需要的memseg list做释放处理。

  • 以secondary方式
调用rte_eal_hugepage_attach()来实现,同样分为是否设置了lagacy_mem,分别调用eal_legacy_hugepage_attach()和eal_hugepage_attach()

A. eal_legacy_hugepage_attach()
打开primary进程创建的文件/var/run/dpdk/rte/hugepage_data,并读取相应的信息,该文件的是多个hugepage_file结构体。对于每一个结构体中包含的信息,通过mmap()将大页内存还原到当前secondary进程当中,并以此初始化memseg list。

B. eal_hugepage_attach()
调用eal_memalloc_sync_with_primary(),该func会调用sync_walk()。sync_walk()会根据primary进程中的memseg list信息初始化自己的memseg list。sync_walk()会调用sync_existing()去打开/sys文件系统中的大页文件去确认已经被primary分配的内存和未被primary分配的内存跟memseg list中的信息是一致的,在确保一致的情况下才可以进行后续工作。
未完待续… 
往期回顾:
07.DPU网络开发SDK—DPDK(六)
06.DPU网络开发SDK—DPDK(五)
05.DPU网络开发SDK—DPDK(四)

07.DPU网络开发SDK—DPDK(六)

上次内容继续对rte_eal_init()所做的工作进行分析。
 
  1. 大页内存配置
internal_conf中的no_hugetlbfs指明是否禁用大页内存,通过命令行参数”–no-huge”设置禁用,默认情况下大页内存是开启的。DPDK根据进程是primary还是secondary的调用不同的初始化过程来初始化大页内存。
  • 按照primary方式初始化
调用eal_hugepage_info_init()来初始化primary进程。
该func首先调用hugepage_info_init()来初始化大页信息,首先遍历/sys/kernel/mm/hugepages中每个名称以”hugepages-“开头的目录,这种目录每个都代表了一种大小的页面,从目录名称中即可获得大页的大小,如hugepages-1048576kB代表1GB大小的页面。遍历过程中,根据目录名称获取到大页内存页面大小之后,接下来检查大页是否被正确挂载。打开/proc/mounts文件,查找到fstype为hugetlbfs类型的挂载项,获取到挂载点;如果没有挂载点,那么会寻找下一个大小类型的大页。找到挂载点之后,会调用calc_num_pages()计算页面数量。
需要额外注意的是,对于arm架构,可以支持4种页面大小类型,其他架构则只支持3种页面类型,多出的页面大小类型不做处理。多种符合要求的大页类型经过分析之后,相关信息会存放在internal_config的hugepage_info数组中。
hugepage_info_init()之后,如果是不共享文件模式(internal_config中的no_shconf置位),那么初始化过程就到此为止了,如果是共享模式,需要额外完成如下工作:通过调用create_shared_memory()在/var/run/dpdk/rte目录下创建hugepage_info文件,并通过mmap()将文件映射到DPDK进程的虚拟地址空间,之后将internal_config的hugepage_info数组拷贝到该文件中。
  • 按照secondary方式初始化
调用eal_hugepage_info_read()来初始化secondary进程,该func打开primary进程创建的/var/run/dpdk/rte目录下的hugepage_info文件,将文件中的信息拷贝到internal_config结构中的hugepage_info数组当中。
  1. 日志初始化
调用rte_eal_log_init()来初始化文件,该func中调用系统调用fopencookie和openlog来初始化日志文件。
  1. VFIO初始化(可选)
VFIO是否进行初始化由DPDK编译时是否开启了VFIO_PRESENT编译选项决定,开启了该编译选项时,调用rte_eal_vfio_setup()来初始化VFIO。初始化过程主要是判断当前系统中是否存在vifo模块。
  1. 内存域初始化
rte_eal_memzone_init()初始化的是rte_config中的mem_config下的memzone成员。同样根据DPDK进程是primary还是secondary分为不同的初始化过程。
  • 按照primary方式初始化
调用rte_fbarray_init()来初始化primary进程的memzone。DPDK中rte_memzone结构体是表示memzone的结构,需要为其分配内存空间;分配内存空间之前需要确定空间大小;DPDK允许RTE_MAX_MEMZONE(默认值2560)个内存域,那么就需要sizeof(rte_memzone) * RTE_MAX_MEMZONE大小的内存空间;同时还需要一个位图结构来表示这些内存域的使用情况,cal_data_size()完成了上述的计算工作。
确定完需要的内存大小之后,通过eal_get_virtual_area()确定一个虚拟地址data作为rte_memzone结构体的存储地址。确定好地址之后,接下来会根据是否设置了no_shconf对data进行不同的处理:如果是非共享模式,那么需要在虚拟地址data上创建一个匿名的内存映射;如果是共享的,会在/var/run/dpdk/rte目录下创建一个名称为fbarray_memzone的文件,并将文件通过mmap映射到虚拟地址data上。上面提到的一些信息,会存放在一个mem_area类型的结构体当中,并插入到模块全局列表mem_area_tailq中;另外还会存放在mem_config下的memzone成员当中。
  • 按照secondary方式初始化
调用rte_fbarray_attach()来初始化secondary进程的memzone,部分过程和rte_fbarry_init()相同,但是会直接打开fbarray_memzone的文件将该文件mmap()到虚拟地址空间data上。
  1. 内存初始化
调用rte_eal_memory_init()来初始化内存配置,分为多个步骤进行。
24.1. 内存段初始化
首先调用rte_eal_memseg_init()来初始化memseg。初始化memseg之前,需要将当前进程的资源限制进行下修改,更改可打开文件的最大数量。初始化memseg同样按照进程是primary还是secondary分别执行不同的初始化过程。
  • 按照primary方式初始化
编译DPDK时,会通过宏定义指定编译的系统架构为32位还是64位,这也决定了memseg的初始化方式的不同。
以64位系统初始化memseg_primary_init()为例,首先会初始化一个本地的结构体memtype的数组,结构体内包含hugepagesize和socket_id两个值,也就是说数组大小n_memtypes为前面统计出的大页内存种类数量*主机socket数量。memtype数组信息填充之后,计算出一些内存大小限制条件:通过宏定义RTE_MAX_MEM_MB(值为512GB)、RTE_MAX_MEM_MB_PER_TYPE(值为64GB)和n_memtypes确定单种类内存的大小限制max_mem_per_type和总的内存大小限制max_mem;通过RTE_MAX_MEMSEG_LISTS和n_memtypes确定每种memtype的segment数量。
接下来需要为每种memtype创建segment list,针对每种类型,需要将下列内容列入考虑范围后,得出一个精确的segment大小。
  • 该memtype可用内存总量
  • 每个segment允许的内存总量
  • 适配内存总量需要的segment数量
  • memtype允许的segment数量
  • 每个segment list允许的segment数量
  • 允许创建的segment list数量
确定好这些限制之后,调用eal_memseg_list_init()初始化每一个memseg list,该func最终是调用rte_fbarray_init()来实现初始化;然后是调用eal_memseg_list_alloc()来分配空间,该func最终是调用eal_get_virtual_area()来实现虚拟地址的分配。
  • 按照secondary方式初始化
调用memseg_secondary_init()来初始化memseg,该func中首先调用rte_fbarray_attach()来实现从文件获取primary进程memseg分配情况,然后通过调用eal_memseg_list_alloc()并最终调用eal_get_virtual_area()来分配虚拟内存空间。出现的几个func在前面memzone的初始化过程中已经出现过,这里不再具体说明。
 
内存初始化过程很长,未完待续……
 
往期回顾:
06.DPU网络开发SDK—DPDK(五)
05.DPU网络开发SDK—DPDK(四)
04.DPU网络开发SDK—DPDK(三)

06.DPU网络开发SDK—DPDK(五)

接上次内容继续对rte_eal_init()所做的工作进行分析。

 

18.检查是否允许直接物理地址访问

rte_eal_using_phys_addrs()会去检查当前系统是否允许在进程虚拟地址空间直接访问物理地址。需要有两个支持条件:存在大页内存和能够进行虚拟地址到物理地址的转换。

  • rte_eal_has_hugepages()

该func就是检查internal_config中的no_hugetlbfs是否置位,置位表示不使用大页内存,默认情况下是不置位的,如果想置位,需要通过DPDK启动参数–no-huge来指定。

  • rte_mem_virt2phy()

该func的作用是随机传入一个进程中的虚拟内存地址,返回该虚拟内存地址映射到的物理地址。在这里,通过传入地址为0的虚拟地址,如果返回的结果不是无效的物理地址的话,则表示当前系统是允许的。

要实现从虚拟地址获得物理地址,需要借助/proc文件系统的支持。假设被寻找物理地址的虚拟地址值为virtaddr,首先要确保能够打开/proc/self/pagemap文件,获取到系统默认的页面大小pagesize。接下来获取virtaddr在虚拟地址空间的页面索引值vrit_pfn,即virt_pfn = virtaddr/pagesize。因为页面是虚拟内存映射到物理内存的最小单位,故需要获取到页面索引。

获取到virt_pfn之后,即可根据此值访问pagemap文件了。在Linux 64位操作系统中,需要54比特位空间存放物理页面的索引(参考Linux文档pagemap.txt得知),向上对齐为8字节空间。那么在pagemap中,offset为virt_pfn * 8的位置,即为索引为virt_pfn的虚拟内存页面映射到的物理内存页面的索引phy_page。

获取到phy_page之后,即可根据此值,还原出virtaddr映射到的物理地址phyaddr。特殊情况下,通过pagemap文件无法获取到phy_page的值,那么表示该系统不支持虚拟地址到物理地址的转换。

 

19.iova模式的处理

 

Internal_config中iova_mode指明设备访问物理内存的方式,一共有三种:RTE_IOVA_PA、RTE_IOVA_VA和RTE_IOVA_DC,分别表示直接访问内存物理地址,通过进程虚拟地址访问,不关心访问方式。默认情况下不关心具体访问方式,其他两种情况,需要通过DPDK启动参数–iova-mode来指定。


 

接下来详细介绍一下DPDK的iova模式。


DPDK是用户态的SDK,使用该SDK访问的是进程的虚拟地址,常规情况下,用户态进行IO操作需要通过系统调用进入内核态,在内核态完成设备与内存之间的交互,此种操作效率低下。为解决此问题,DPDK提供了一些API,能够实现在用户态模式下实现IO操作。然而硬件设备是不能直接操作进程的虚拟地址空间的,能够操作的是物理地址,为此DPDK提供了IO虚拟地址IOVA来支持硬件设备可以直接操作进程的虚拟地址。


DPDK统一通过IOVA实现与硬件设备的数据交换,但需要区分IOVA是寻址的是内存物理地址还是进程的虚拟地址的情况,这就分区出了iova_pa和iova_va两种情况。

  • PA模式

该模式下,分配到DPDK存储区的iova都是实际的物理地址,当进程初始化iova时,虚拟地址空间会完成到物理内存页面的映射,这样当分配内存时,不会引发缺页异常的情况。但这种模式也存在一定的缺点,首先是需要在进程启动时需要root用户的特权,否则无法访问系统的页面映射,也就无法获取到内存区域的真实物理地址。其次是虚拟内存的分配受到物理内存的限制,当物理内存不是连续的内存片段时,iova的虚拟内存也只能是多个片段,不能在逻辑上将物理内存合并。如下图所示:

极端情况下,如果物理内存过于破碎,片段太多,会耗尽表示iova虚拟内存的数据结构,导致初始化失败。解决该问题的方法一般是使用1GB或者2MB的大页内存,并在机器启动时显式指定保留一定数量的大页内存。

  • VA模式

相比PA模式,VA模式下虚拟内存映射物理内存不会受到物理内存片段过多的影响,DPDK的eal层会依靠内核的基础设施将多个物理内存片段映射为一个连续的虚拟内存片段,该过程不需要root权限也可以执行。如下图所示:

 

internal_config中的iova_mode默认为RTE_IOVA_DC,即不关心是哪种。在这种情况下,DPDK初始化过程中需要自行判断该采取哪种模式。首先调用rte_bus_get_iommu_class()来获取iova的模式,该func中遍历rte_bus_list中每个bus,如果bus定义了get_iommu_class这个方法,那么就调用该方法获取该bus需要PA还是VA。如果所有的bus都是PA或者都是VA,那么get_iommu_class就返回具体的模式,如果有的bus需要PA模式而有的需要VA模式,那么仍然是返回DC模式。

调用了get_iommu_class之后如果仍然是DC模式,那么就需要最终确定选择PA还是VA了。如果不支持直接物理地址访问,选择VA;如果内核模块rte_kni有加载,选择PA;如果iommu是使能的,选择VA;除此之外的情况,选择PA。


确定iommu是不是使能的是通过调用is_iommu_enabled()实现,该func通过打开/sys/kernel/iommu_groups目录,统计其中的文件数量再确定。

 

未完待续……

往期回顾:

05.DPU网络开发SDK—DPDK(四)

04.DPU网络开发SDK—DPDK(三)

03.DPU网络开发SDK—DPDK(二)

05.DPU网络开发SDK—DPDK(四)

接上次内容继续对rte_eal_init()所做的工作进行分析。
12.初始化配置
rte_config_init()中,会根据process_type进行不同的初始化任务。process_type是由eal的启动参数指定的,目前支持两种模式:primary和secondary。process_type在解析参数时存放在internal_config中,然后此处赋值到rte_config中。
 
  • 按照primary方式启动
DPDK在解析参数的时候,会初始化一个runtime_dir全局变量。如果没有特殊配置,这个目录为/var/run/dpdk/rte,DPDK会基于此目录中的文件实现一些进程间通信。
 
primary方式启动时,会调用rte_eal_config_create(),该过程在runtime_dir中创建一个名称为config的文件,将文件大小截为rte_mem_config的大小,并添加一个文件锁。文件锁类型为struct flock,这种锁可以指定文件的哪些数据片段需要被保护。在此处保护的是整个文件,也就是整个rte_mem_config。
 
接下来需要将该文件映射到DPDK进程的虚拟空间当中,调用mmap()进行此操作,映射之后可读可写,且其他进程同样可以映射该文件到其虚拟空间当中。在映射之前,会确定映射到进程的具体哪个虚拟地址上,该过程由eal_get_virtual_area()实现。
 
eal_get_virtual_area()会基于internal_config中的base_virtaddr确定目标映射地址(该值默认为0,可通过启动参数修改之)。在base_virtaddr的基础上,以系统的page_size为递增单位,依次检查某个映射地址是否满足映射要求,直到找第一个满足要求的映射地址为止。
 
完成对config文件的映射之后,将rte_config中的rte_mem_config拷贝到映射好的虚拟地址空间,并将rte_config中rte_mem_config的指针指向此空间,后续对rte_mem_config的修改就可以直接反馈到config文件当中了。
 
  • 按照secondary方式启动
如果是secondary启动DPDK进程,则直接打开primary进程创建的config文件并mmap()到secondary进程中,后将rte_config中rte_mem_config的指针指向此空间。如此secondary进程看到的rte_mem_config看到的内容和primary进程看到的是一样的,两者在mem配置方面实现了进程间的通信,且primary进程在创建config文件的过程中为其设置了锁,使得对其的写操作是互斥的,避免了两个进程之间的竞争问题。
13.初始化信号处理
此处启动一个线程eal_intr_thread_main(),该线程中无限循环执行如下的过程:
  • 创建一个epoll,并将中断信号传递管道中读信号的一侧的文件描述符intr_pipe.readfp添加到epoll中进行监听。
  • 将intr模块的全局变量intr_sources列表中的所有文件描述符添加到epoll中进行监听。
  • 无限循环等待epoll中的文件描述符的事件的发生,并依次处理发生的事件。
  • 关闭epoll。
14.创建一个定时器
此处调用Linux的系统函数timerfd_create()创建一个定时器的文件描述符,并将其存在alarm模块的全局变量intr_handle中。在后续的操作中,intr_handle会注册到前面提到的intr_sources列表中。
15.初始化进程间通信文件
rte_mp_channel_init()在/var/run/dpdk/rte目录下生成一个文件,名称为mp_socket_*。调用open_socket_id创建UDP类型的socket,保存于全局变量mp_fd中,并调用bind将mp_fd与通信文件进行绑定。
如果进程是primary,那么通信文件为/var/run/dpdk/rte/mp_socket,如果是secondary,通信文件为/var/run/dpdk/rte/mp_socket_${pid}_${timestamp}。然后重启一个线程,线程处理函数为mp_handle(),该处理函数无限循环接收mp_fd代表的socket发来的信息并处理之。该进程间通信文件的是primary和secondary进程之间的通信,secondary进程之间是没有通信需求的。
 
16.设备热插拔初始化
eal_mp_dev_hotplug_init()中,如果当前进程是primary,则会注册一个回调函数handle_secondary_request()用于处理处理来自secondary进程的设备热插拔请求。反之secondary进程中会注册handle_primary_request()函数。
 
17.各总线遍历设备
bus模块全局变量rte_bus_list存储了所有的总线的列表,此处依次遍历每个总线并调用总线的scan()方法。
 
在DPDK中存在一个描述总线的结构struct rte_bus,该结构中定义了一些总线的接口函数,scan()就是其中之一。对于总线来说,scan()方法的主要作用是用来遍历所有的设备,并将设备与注册在该总线上的驱动进行匹配,当匹配成功时,建立驱动和设备之间的对应关系。DPDK只提供了接口要求各总线实现该接口,具体如何实现则由各总线设备自行定义。
 
总线通过注册的方式注册到rte_bus_list当中,目前DPDK支持的总线有:
  • vmbus
  • vdev
  • pci
  • fpga
  • fslmc
  • dpaa
struct rte_bus的结构如下,除了scan()方法,还有probe(),find_device()等。

 

WechatIMG318

rte_bus的实现是仿照了Linux内核中总线的角色,主要是为了建立设备和驱动之间的联系,后期我们会以某个总线设备为例查看这些接口方法的具体实现。
未完待续… 
 
往期回顾:
04.DPU网络开发SDK—DPDK(三)
03.DPU网络开发SDK——DPDK(二)
02.DPU网络开发SDK——DPDK(一)
01.DPU简介

04.DPU网络开发SDK—DPDK(三)

作者:大空新一

 

上次内容继续对rte_eal_init所做的工作进行分析。

 

  1. 将DPDK进程的启动参数保留下来

在启动DPDK进程时会传入启动参数,这些参数会分成两部分,一部分是DPDK的eal配置参数,如占用的CPU处理核的列表,分配的大页内存大小,网卡的黑白名单等;另外一部分是进程自定义参数,由开发者自行定义参数列表和含义。参数列表中eal配置参数放在前面,进程自定义参数在后面,中间通过“–”进行分割。

 

eal_save_args()中,分配两段内存空间并交给两个全局的变量eal_args和eal_app_args,后将两部分参数分别存放在这两段内存空间当中。

 

  1. CPU处理核信息初始化

存在一个全局数组变量lcore_config,类型为struct lcore_config,大小由宏定义RTE_MAX_LCORE指定。在rte_eal_cpu_init()中,首先依次遍历数组中的每个元素并对struct lcore_config中的一些元素赋默认值。该结构体的数据结构包含如下内容:

微信图片_20220416190135

要理解该结构体中一些元素的含义,首先需要了解一下CPU的结构。在Linux中执行lscpu命令,可得到如下输出内容:

微信图片_20220416190140

该例子的主机中,有一个socket。socket可以对应主板上的一个CPU插槽,就是一块CPU芯片。对于一般的PC机来说,只有一个socket,大型服务器一般有多个socket。一个socket中一般会有多个core,即一个独立的处理单元,在单线程模式下,一个独立的处理单元包含一套标准的寄存器和L1级缓存,同时刻只能运行一个线程;而多线程模式下,一套处理单元有两套寄存器,同时刻可以运行两个线程,这两个线程共享L1级缓存。上例中所显示的主机的CPU有8个处理单元,因为开启了多线程,每个处理单元线程数是2,这样逻辑上讲一共有16个线程(或CPU)。

 

对应到lcore_config中的相关结构,socket_id即为socket的编号,core_id对应的是处理单元的编号,而DPDK的处理核lcore则对应的是线程(或CPU)的编号。

 

在遍历lcore_config的每个元素时的索引就是lcore的编号,遍历时,根据lcore的编号去系统中查找对应的线程(或CPU)。如果该线程没有被启用,则将lcore_config结构体中的core_index置为负数,并将rte_config中的lcore_role中对应的值置为ROLE_OFF;线程被启用时,core_index设置为相对索引,lcore_role设为ROLE_RTE。

 

遍历完lcore之后,也就能够统计出涉及到的socket有哪些了,会根据这些信息设置rte_config中的numa_node信息。

 

需要另外指出的是,rte_eal_cpu_init()中涉及到的某些func,是通过直接访问sys文件系统实现的。

 

如eal_cpu_socket_id()是遍历/sys/devices/system/node/nodeX目录中如果存在cpuY目录,则lcoreY的socket id是X;eal_cpu_detected()是遍历/sys/devices/system/cpu/cpuX/topology/core_id是否存在。

 

  1. DPDK参数解析

eal_parse_args()解析DPDK参数,比较重要的几个参数有:

  • -a:allow list,指定哪些网卡设备是允许被DPDK接管的,传入的参数值是设备的pci总线编号。

  • -b:block list,指定哪些网卡设备是进制被DPDK禁止的,传入值同上。

  • -c:指定DPDK进程所占用的处理核是哪些,用bitmask的方式进行表示。

  • -m:指定分配多大的内存空间给DPDK的内存池,以MB为单位。

解析之后的信息存放在internal_config这个全局变量或者其他的变量当中。DPDK涉及到的参数比较多,后续会根据需要补充说明各参数的含义。

 

  1. 插件初始化

如果是Windows系统,是不支持插件动态载入的,eal_plugins_init()的内容为空。如果是Linux系统时,如果能通过执行系统调用dlopen打开动态链接库librte_eal.so.XX,则调用eal_plugin_add将默认动态库的目录default_solib_dir添加到全局变量solib_list当中(此外还可以通过DPDK的启动参数-d,将自定义目录添加到solib_list中)。

 

由于在初始化阶段加入solib_list中的可能是动态库本身也可能是动态库所在的目录,eal_plugins_init()会在遍历solib_list中将目录中的*.so文件全部遍历之后加入solib_list中;如果已经是*.so本身,则设置solib的lib_handle。

 

  1. 初始化跟踪功能

调用eal_trace_init()初始化跟踪功能,该功能是对一些事件和变量进行跟踪,用于多线程同步和时间度量等。该功能比较复杂,后续分析过程中会详细介绍,此处先略过。

 

  1. 解析设备

前面提到DPDK的参数时,-a和-b参数分别指定网卡设备的白黑名单,这些信息是存放在devopt_list这个全局变量当中的,eal_option_device_parse()会遍历其中的每个设备,并转换成新的数据结构存放在devargs_list当中。

 

未完待续

 

往期回顾:

03.DPU网络开发SDK——DPDK(二)

02.DPU网络开发SDK——DPDK(一)

01.DPU简介