rte_eal_init()
接上次内容继续对rte_eal_init()所做的工作进行分析。
25. 内存堆初始化
调用rte_eal_malloc_heap_init()进行初始化。
如果进程类型是primary的,需要额外先做如下工作:初始化mem_config.malloc_heaps数组,初始化元素的数量为当前socket的数量,即每个socket对应一个堆结构。
接下来调用register_mp_requests(),该func是注册多进程间内存分配的action处理函数,分进程是否是primary和非primary进行。
-
进程是primary
primary进程仅需要注册一个action,即MP_ACTION_REQUEST,相应的处理函数为handle_request。
handle_request接收rte_mp_msg类型的消息,并从中提取出malloc_mp_req类型的请求。首先检查请求是否是重复请求,不是重复请求,则判断请求类型是alloc还是free。如果是alloc,调用handle_alloc_request()处理;如果是free,则调用malloc_heap_free_pages()处理。alloc和free处理完成之后,如果处理是失败的,调用rte_mp_sendmsg()发送fail结果对请求方;如果是成功的,则构造sync请求类型并多次调用rte_mp_request_async()直到同步完成。
-
进程是非primary
非primary进程需要注册多个action,包括MP_ACTION_SYNC,MP_ACTION_ROLLBACK,MP_ACTION_RESPONSE,前两个action处理函数都为handle_sync,最后一个action处理函数为handle_response。
handle_sync接收类型为sync的请求,并调用eal_memalloc_sync_with_primary()来处理之。
handle_response接收到response之后,调用find_request_by_id(),查找之前发送过的请求,如果存在,则调用pthread_cond_signal对请求做完结处理。
执行完register_mp_requests()注册了多进程内存分配处理函数之后,对于primary进程,需要额外调用malloc_add_seg()将所有iova连续内存区域加入到堆当中。malloc_add_seg是通过rte_memseg_contig_walk()调用的,contig_walk()实际上是前面提到的rte_memseg_list_walk()的变种,与list_walk()不同的是,contig_walk()在遍历memseg list时会将连续分配的memseg统一交给作为参数传入其中的func处理,这里的func参数即是malloc_add_seg。
26. 初始化尾队列
调用rte_eal_tailqs_init()来初始化,该func中遍历以全局变量rte_tailq_elem_head为头部的列表中的元素,元素类型为rte_tailq_elem,而每个元素又是一个列表头。对每个元素调用rte_eal_tailq_update()来进行初始化,同样primary和secondary进行。
-
进程是primary
调用rte_eal_tailq_create(),首先去调用rte_eal_tailq_lookup()查找mem_config.tailq_head中是否已经创建好,没有创建则在mem_config.tailq_head中查找一个空闲项,初始化之后并返回。
-
进程是secondary
调用rte_eal_tailq_lookup(),在mem_config.tailq_head中根据传入的名称进行匹配,查找匹配的项并返回。
27. 初始化计时器
调用rte_eal_timer_init()初始化计时器,该func调用set_tsc_freq()设定计时器的刷新频率,如果是secondary进程,直接用mem_config.tsc_hz作为全局变量eal_tsc_resolution_hz的值;如果是primary,则通过系统调用get_tsc_freq_arch()/get_tsc_freq()/estimate_tsc_freq()等获取到具体的值。
28. 检查main_lcore所在socket上的内存配置
调用eal_check_mem_on_local_socket(),获取main_lcore上所在的socket_id,并在list_walk()的帮助下调用check_socket()检查各memseg list的socket是否与main_lcore的socket相符。
29. 设置主线程的cpu亲和性
rte_eal_init()是由main线程启动的,在启动完成之后,他应该跑在main_lcore上,故调用pthread_setaffinity_np()将当前现场绑定在lcore_config[config->main_lcore].cpuset指定的处理核上。pthread_setaffinity_np()是Linux的系统调用。
接下来调用__rte_thread_init(),将main_lcore的值、当前线程的id、当前的socket id设定在per-lcore的内存空间当中
30. 为每个worker lcore启动线程
使用宏定义RTE_LCORE_FOREACH_WORKER遍历每个worker处理核,为每个处理核建立两条管道,分别是从main线程到该worker线程以及从该worker线程到main线程。接下来调用Linux系统调用pthread_create()启动一个线程,线程入口函数为eal_thread_loop();然后为线程设定名称,名称的格式为“lcore-worker-${worker lcore id}”;最后调用pthread_setaffinity_np()来设定worker线程的亲和性。
eal_thread_loop()这个线程完成以下工作:根据thread id在lcore_config中找到当前线程属于哪个lcore;调用__rte_thread_init(),将main_lcore的值、当前线程的id、当前的socket id设定在per-lcore的内存空间当中;最后进入一个无限循环执行如下工作:从main_to_worker管道接收消息,将线程设为running状态,并向worker_to_main管道写入确认消息;执行lcore_config[lcore_id].f函数指针指向的入口函数,将lcore_config[lcore_id].arg作为函数的参数;待入口函数执行完毕之后,将结果记录在lcore_config[lcore_id].ret当中;最后将线程状态设置为finish。
31.等待所有worker lcore的线程执行完毕
调用rte_eal_mp_wait_lcore()等待所有worker lcore上的线程变为finish状态。
32. 初始化service
调用rte_service_init(),初始化service类型的lcore。
33. 总线探测
调用rte_bus_probe(),遍历所有总线,调用总线的probe方法,其中vdev类型的总线只调用一次。以pci总线的probe为例,pci_probe()中,遍历总线上的每一个设备,去查找是否存在匹配的驱动,如果所有设备都没有找到匹配的驱动,则返回错误。
34. 按默认配置启动service
调用rte_service_start_with_defaults(),运行service处理函数。
35. 清理运行时目录
调用eal_clean_runtime_dir()清理掉runtime目录下的无用文件。
36. 初始化遥测接口
调用rte_telemtry_init(),开启一个线程,监听一个socket,接受外界发来的查询dpdk进程运行状态的请求。
37. 标记memconfig初始化完成
调用eal_mcf_complete(),标记internal_config->init_compelete为true,标识内存配置初始化完成。
到此为止,整个rte_eal_init()过程完全初始化完成,之后即可进行dpdk接下来的配置。