rte_eal_init()
接上次内容继续对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()等。
struct rte_bus {
TAILQ_ENTRY(rte_bus) next; //在rte_bus_list中的入口
const char *name; //总线名称
rte_bus_scan_t scan; //遍历总线上挂载的设备
rte_bus_probe_t probe; //探测总线上挂载的设备
rte_bus_find_device_t find_device; //查找总线上挂载的设备
rte_bus_plug_t plug; //建立某个设备和驱动的联系
rte_bus_unplug_t unplug; //接触某个设备和驱动的联系
rte_bus_parse_t parse; //解析设备名称
rte_dev_dma_map_t dma_map; //设备映射到DMP
rte_dev_dma_unmap_t dma_unmap; //解除设备到DMA的映射
struct rte_bus_conf conf; //总线配置
rte_bus_get_iommu_class_t get_iommu_class; //iommu类别
rte_dev_iterate_t dev_iterate; //设备迭代
rte_bus_hot_unplug_handler_t hot_unplug_handler; //处理热拔出设备失败
rte_bus_sigbus_handler_t sigbus_handler; //处理sigbus错误
};
rte_bus的实现是仿照了Linux内核中总线的角色,主要是为了建立设备和驱动之间的联系,后期我们会以某个总线设备为例查看这些接口方法的具体实现。
未完待续…