最近群里有人问,在non-EAL环境下使用DPDK有什么不同。自己对这一部分的实现原理了解的也不多,所以查找、总结了一下DPDK中的lcore实现原理。
首先,关于non-EAL,并不是说整个程序完全不使用EAL(这样的话几乎所有DPDK功能都不可用)。程序本身还是要执行rte_eal_init()
来对EAL初始化,通过参数-l
、-c
或者--lcores
来指定使用哪些cpu lcore,在每一个管辖的lcore上绑定一个线程待命,这些线程由EAL管理,即为EAL线程。除此之外,也可以手工利用pthread运行新的线程,这些线程不受EAL控制,就是所谓的non-EAL线程。由于EAL线程与lcore的绑定关系,很多地方用lcore指代其上的EAL Thread。
在Environment Abstraction Layer-Known Issues中,列举了在non-EAL Thread中受限制的功能,包括rte_mempool
、rte_timer
、rte_log
和一些调试功能。提及到的理由是,在non-EAL Thread中,变量_lcore_id的值不是一个有效值,因此所有需要提供_lcore_id的DPDK功能均会受限制。
我们来看看DPDK EAL对于lcore是如何管理的。
在DPDK中,与lcore相关的数据结构有两个:rte_config
(位于librte_eal/common/include/rte_eal.h
),和lcore_config
(位于librte_eal/common/include/rte_lcore.h
)。
rte_config
包含的信息并不多。其相关的定义如下(DPDK version 17.08):
rte_eal.h, line 58~95:
/**
* The lcore role (used in RTE or not).
*/
enum rte_lcore_role_t {
ROLE_RTE,
ROLE_OFF,
ROLE_SERVICE,
};
...省略...
/**
* The global RTE configuration structure.
*/
struct rte_config {
uint32_t master_lcore; /**< Id of the master lcore */
uint32_t lcore_count; /**< Number of available logical cores. */
uint32_t service_lcore_count;/**< Number of available service cores. */
enum rte_lcore_role_t lcore_role[RTE_MAX_LCORE]; /**< State of cores. */
...省略...
} __attribute__((__packed__));
rte_config
用来保存EAL命令行参数,与lcore相关的是前4个字段。其中前两个很好理解;第三个是关于17.08版本新加入的service core,先不谈;第四个是lcore的角色,以lcore_id作为下标,根据命令行参数(-l
,-c
,--lcores
),EAL thread会被赋为ROLE_RTE
,非EAL thread被赋为ROLE_OFF
。
其实在DPDK中,这个lcore_role
唯一的作用只是用在函数rte_lcore_is_enabled()
(位于librte_eal/common/include/rte_lcore.h
),该函数判断给定的lcore_id是否是ROLE_RTE
,进一步被用于rte_get_next_lcore()
和宏RTE_LCORE_FOREACH
和RTE_LCORE_FOREACH_SLAVE
中。
lcore_config
则包含了关于lcore的绝大部分重要信息。其定义如下:
rte_lcore.h, line 60~77:
/**
* Structure storing internal configuration (per-lcore)
*/
struct lcore_config {
unsigned detected; /**< true if lcore was detected */
pthread_t thread_id; /**< pthread identifier */
int pipe_master2slave[2]; /**< communication pipe with master */
int pipe_slave2master[2]; /**< communication pipe with master */
lcore_function_t * volatile f; /**< function to call */
void * volatile arg; /**< argument of function */
volatile int ret; /**< return value of function */
volatile enum rte_lcore_state_t state; /**< lcore state */
unsigned socket_id; /**< physical socket id for this lcore */
unsigned core_id; /**< core number on socket for this lcore */
int core_index; /**< relative index, starting from 0 */
rte_cpuset_t cpuset; /**< cpu set which the lcore affinity to */
uint8_t core_role; /**< role of core eg: OFF, RTE, SERVICE */
};
eal.c, line 116:
struct lcore_config lcore_config[RTE_MAX_LCORE];
该结构体由rte_eal_init()
在EAL初始化时填写,每一个EAL Thread都对应一个lcore_config。可以说EAL Thread和non-EAL Thread根本性区别就在于是否有一个lcore_config结构。在一个线程中,如果要用到EAL特性,首先需要得到该线程的lcore_config结构。为此,每一个线程都有一个_lcore_id,范围是[0. RTE_MAX_LCORE
),对应于lcore_config数组的下标。_lcore_id定义于librte_eal/linuxapp/eal/eal_thread.c
:
eal_thread.c, line 57:
RTE_DEFINE_PER_LCORE(unsigned, _lcore_id) = LCORE_ID_ANY;
其中,RTE_DEFINE_PER_LCORE
的宏展开为:
rte_per_lcore.h, line 60:
#define RTE_DEFINE_PER_LCORE(type, name) \
__thread __typeof__(type) per_lcore_##name
在这里使用了gcc的扩展__thread
,参见Thread-Local Storage,和c++11的_Thread_local
关键字作用类似。具体来说就是让其修饰的变量在每个线程中自动产生一个副本。
在eal_init()
中,调用pthread_create()
创建EAL Thread,将线程ID(pthread_t
)存入lcore_config的thread_id中,线程执行函数为eal_thread_loop()
(位于librte_eal/linuxapp/eal/eal_thread.c
):
eal_init(), eal.c line 903~930:
RTE_LCORE_FOREACH_SLAVE(i) {
...省略...
/* create a thread for each lcore */
ret = pthread_create(&lcore_config[i].thread_id, NULL,
eal_thread_loop, NULL);
...省略...
}
eal_thread_loop()
根据thread_id确定自己的_lcore_id:
eal_thread_loop(), eal_thread.c line 132~146:
thread_id = pthread_self();
/* retrieve our lcore_id from the configuration structure */
RTE_LCORE_FOREACH_SLAVE(lcore_id) {
if (thread_id == lcore_config[lcore_id].thread_id)
break;
}
...省略...
/* set the lcore ID in per-lcore memory area */
RTE_PER_LCORE(_lcore_id) = lcore_id;
至此,EAL Thread建立完成。对于一个EAL Thread,可以通过自身_lcore_id找到对应的lcore_config结构;而non-EAL Thread中,_lcore_id为LCORE_ID_ANY。
在DPDK中,凡是用到线程相关的功能,均需要提供_lcore_id(通常由rte_lcore_id()
获得)。因此,所有要求提供_lcore_id的函数在non-EAL Thread中都会受到影响。