DPDK non-EAL线程环境下的使用

13 Nov 2017 | DPDK | | ˚C

最近群里有人问,在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_mempoolrte_timerrte_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_FOREACHRTE_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中都会受到影响。


Older · View Archive (13)

博客还是不能丢的

Newer

char类型的一个小坑