Hello world
上一次的文章中主要介绍了DPDK是什么,主要用在什么地方。作为一个SDK,DPDK提供了大量的function接口用于网络转发面程序的编写。接下来的几篇文章,我们会基于DPDK程序的实例,以剖析关键function接口的方式逐步分析DPDK的实现机制,以此来熟悉DPDK的整个样貌。由于DPDK的版本发生过巨大的变化,涉及到编译方式等的改变,之后的分析统一基于20.11.0这个版本进行。
static int lcore_hello(__rte_unused void *arg) {
unsigned lcore_id;
lcore_id = rte_lcore_id();
printf("hello from core %u\n", lcore_id);
return 0;
}
int main(int argc, char **argv) {
int ret;
unsigned lcore_id;
ret = rte_eal_init(argc, argv);
if (ret < 0)
rte_panic("Cannot init EAL\n");
RTE_LCORE_FOREACH_WORKER(lcore_id) {
rte_eal_remote_launch(lcore_hello, NULL, lcore_id);
}
lcore_hello(NULL);
rte_eal_mp_wait_lcore();
return 0;
}
我们以DPDK版本的“hello world”为例开始我们的分析过程,其代码内容如上(省略头文件)。
进入主函数之后,首先需要调用的是rte_eal_init,该函数的作用是用来初始化DPDK的运行时环境(runtime environment, rte),在DPDK中称为环境抽象层(environment abstract layer, eal)。该初始化过程中会提取命令行参数列表中与DPDK相关的参数进行解析,初始化各类全局数据结构,初始化内存等。该过程涉及到的初始化内容很多,是接下来的几篇DPDK分析文章中会重点分析的内容。
DPDK的eal环境初始化完毕之后,会在各处理核(lcore)上启动各线程,DPDK用RTE_LCORE_FOREACH_WORKER这个宏定义来遍历所有的worker处理核,并在该处理核上启动线程的入口函数。本例中的入口函数是lcore_hello,DPDK通过rte_eal_remote_launch将线程入口函数与处理核进行绑定,绑定之后,在DPDK进程结束前,该线程会独占该处理核且不会有其他线程调度到该处理核上运行。
与worker处理核相对应的是main处理核,在执行DPDK进程时,进程被调度到哪个处理核上执行,该处理核就是main处理核,即main处理核承担了初始化DPDK的任务。在本例中,main函数在各worker处理核上启动线程之后,同样执行了lcore_hello,并且调用rte_eal_mp_wait_lcore等待所有线程执行完毕之后再退出。
lcore_hello的工作仅为获取当前处理核的ID并打印到控制台,在DPDK中处理核的ID从0开始连续编号,最大支持的处理核数量在编译DPDK的lib库时由配置文件指定为宏定义。运行过程中DPDK进程实际占用的处理核与CPU的Processor的对应列表则是由启动DPDK时的参数指定的,rte_eal_init会对此进行解析并初始化相关处理结构。
rte_eal_init()
rte_eal_init的整个初始化过程非常长,此处的分析过程中会尽可能将重点过程保留下来,对某些细节内容则先忽略。因rte_eal_init的代码非常长,此处不做粘贴处理,请自行阅读源代码。
1. 获取两个全局配置
DPDK中有两个全局配置的变量,类型分别为struct rte_config和struct internal_config。数据结构的定义分别在lib/librte_eal/common目录下的eal_private.h和eal_internal_cfg.h两个文件中,变量定义在eal_common_config.c文件中,并提供了ret_eal_get_configuration()和eal_get_internal_configuration()两个接口用于获取这两个配置的变量。
在rte_eal_init中,首先获取这两个全局变量,用于记录经参数解析后的配置及初始化的其他数据结构等。
2. 检查主机是否满足运行要求
该项检查主要是检查CPU的特性,执行入口为rte_cpu_is_supported()。该function中,会根据编译时定义的宏RTE_COMPILE_TIME_CPUFLAGS当中指定cpuflag列表,去依次检查当前主机的CPU是否支持这些flag特性,出现不支持的特性时则会以错误返回。该function的实现是分CPU架构的。
3. 确保初始化过程只运行了一次
通过static类型的变量run_once和来标识是否init是否已经执行过了,在一个DPDK进程中rte_eal_init只能执行一次。
4. 初始化internal_config
将全局配置变量internal_config中相关成员的值设置为默认值。
5. 设置日志级别
调用eal_log_level_parse()负责日志级别的解析,通过解析命令行参数"--log-leve"确定以什么级别打印日志,并将日志等级存入internal_config中。
由于日志越早打印越有助于在出现问题时及时定位,所以DPDK在初始化阶段的早期就解析了日志级别,而其他的命令行参数则在随后才做解析。
在下期我们将继续进行rte_eal_init的过程分析。