DPDK线程初始化逻辑详解

DPDK线程简介

DPDK的线程分为主线程和工作线程(worker)。

每个线程绑定一个核心,poll mod模式最大限度使用CPU资源,达到数据包处理能力最大化。

所绑定的核心由eal参数 -l 决定, 如 -l 2,3,4,5,

第一个核心2,作为主线程绑定核心; 3,4,5是工作线程绑定核心。

参数后面跟多少核心,就开启多少个工作线程。

线程初始化

所有的线程初始化都在rte_eal_init()初始化函数中完成的。

主线程初始化

执行rte_eal_init()函数的线程成为主线程,一般由main函数执行该初始化函数。

rte_eal_init()函数中将当前线程id与主核心绑定,即主线程绑定核心。

代码如下:

	if (pthread_setaffinity_np(pthread_self(), sizeof(rte_cpuset_t),
			&lcore_config[config->main_lcore].cpuset) != 0) {
		rte_eal_init_alert("Cannot set affinity");
		rte_errno = EINVAL;
		return -1;
	}

工作线程初始化

第一步,每个worker线程分配一对pipe,用于和主线程通信

第二步,每个worker线程启动,并执行eal_thread_loop()线程函数

第三步,每个worker线程绑定相应核心。

	RTE_LCORE_FOREACH_WORKER(i) {

		/*
		 * create communication pipes between main thread
		 * and children
		 */
        // 创建通信管道
		if (pipe(lcore_config[i].pipe_main2worker) < 0)
			rte_panic("Cannot create pipe\n");
		if (pipe(lcore_config[i].pipe_worker2main) < 0)
			rte_panic("Cannot create pipe\n");

		lcore_config[i].state = WAIT;

		/* create a thread for each lcore */
        // 创建工作线程
		ret = pthread_create(&lcore_config[i].thread_id, NULL,
				     eal_thread_loop, NULL);
		if (ret != 0)
			rte_panic("Cannot create thread\n");

		/* Set thread_name for aid in debugging. */
		snprintf(thread_name, sizeof(thread_name),
			"lcore-worker-%d", i);
		ret = rte_thread_setname(lcore_config[i].thread_id,
						thread_name);
		if (ret != 0)
			RTE_LOG(DEBUG, EAL,
				"Cannot set name for lcore thread\n");
		// 绑定相应核心
		ret = pthread_setaffinity_np(lcore_config[i].thread_id,
			sizeof(rte_cpuset_t), &lcore_config[i].cpuset);
		if (ret != 0)
			rte_panic("Cannot set affinity\n");
	}

Worker线程逻辑

Worker线程初始化以后,执行eal_trhead_loop

这个loop三件事

第一件,等待管道主线程的通知

第二件,得到通知,执行业务函数lcore_config[lcore_id].f,该函数指针由主线程赋值

第三件,执行完业务函数,重新等待管道的通知

/* main loop of threads */
__rte_noreturn void *
eal_thread_loop(__rte_unused void *arg)
{
	char c;
	int n, ret;
	unsigned lcore_id;
	pthread_t thread_id;
	int m2w, w2m;
	char cpuset[RTE_CPU_AFFINITY_STR_LEN];

	thread_id = pthread_self();

	/* retrieve our lcore_id from the configuration structure */
	RTE_LCORE_FOREACH_WORKER(lcore_id) {
		if (thread_id == lcore_config[lcore_id].thread_id)
			break;
	}
	if (lcore_id == RTE_MAX_LCORE)
		rte_panic("cannot retrieve lcore id\n");

	m2w = lcore_config[lcore_id].pipe_main2worker[0];
	w2m = lcore_config[lcore_id].pipe_worker2main[1];

	__rte_thread_init(lcore_id, &lcore_config[lcore_id].cpuset);

	ret = eal_thread_dump_current_affinity(cpuset, sizeof(cpuset));
	RTE_LOG(DEBUG, EAL, "lcore %u is ready (tid=%zx;cpuset=[%s%s])\n",
		lcore_id, (uintptr_t)thread_id, cpuset, ret == 0 ? "" : "...");

	rte_eal_trace_thread_lcore_ready(lcore_id, cpuset);

	/* read on our pipe to get commands */
	while (1) {
		void *fct_arg;

		/* wait command */
        // 等待管道通知
		do {
			n = read(m2w, &c, 1);
		} while (n < 0 && errno == EINTR);

		if (n <= 0)
			rte_panic("cannot read on configuration pipe\n");

		lcore_config[lcore_id].state = RUNNING;

		/* send ack */
		n = 0;
		while (n == 0 || (n < 0 && errno == EINTR))
			n = write(w2m, &c, 1);
		if (n < 0)
			rte_panic("cannot write on configuration pipe\n");

		if (lcore_config[lcore_id].f == NULL)
			rte_panic("NULL function pointer\n");

		/* call the function and store the return value */
		fct_arg = lcore_config[lcore_id].arg;
        // 执行业务函数
		ret = lcore_config[lcore_id].f(fct_arg);
		lcore_config[lcore_id].ret = ret;
		lcore_config[lcore_id].f = NULL;
		lcore_config[lcore_id].arg = NULL;
		rte_wmb();

		/* when a service core returns, it should go directly to WAIT
		 * state, because the application will not lcore_wait() for it.
		 */
		if (lcore_config[lcore_id].core_role == ROLE_SERVICE)
			lcore_config[lcore_id].state = WAIT;
		else
			lcore_config[lcore_id].state = FINISHED;
	}

通常,Worker线程是一直死在业务函数中的,因为业务函数是有条件的一个死循环。

Main线程逻辑

主线程一般使用rte_eal_mp_remote_launch(f, arg, call_main)来启动各个工作线程的业务。

rte_eal_mp_remote_launch就做两件事

第一件,调用函数rte_eal_remote_launch,依次启动各个worker,将执行函数交给worker执行。

第二件,如果call_main,主线程就调用业务函数f(arg), 主线程就死在这里了;如果skip_mian,退出该函数,主线程不做任何事。

int
rte_eal_mp_remote_launch(int (*f)(void *), void *arg,
			 enum rte_rmt_call_main_t call_main)
{
	int lcore_id;
	int main_lcore = rte_get_main_lcore();

	/* check state of lcores */
	RTE_LCORE_FOREACH_WORKER(lcore_id) {
		if (lcore_config[lcore_id].state != WAIT)
			return -EBUSY;
	}

	/* send messages to cores */
	RTE_LCORE_FOREACH_WORKER(lcore_id) {
		rte_eal_remote_launch(f, arg, lcore_id);
	}

	if (call_main == CALL_MAIN) {
		lcore_config[main_lcore].ret = f(arg);
		lcore_config[main_lcore].state = FINISHED;
	}

	return 0;
}

rte_eal_remote_launch(f, arg, work_id) 如何来启动各个工作线程的业务呢

rte_eal_remote_launch就做两件事

第一件,给相应的worker线程赋予业务函数指针

第二件,pipe管道通知worker:起来干活啦!

int
rte_eal_remote_launch(int (*f)(void *), void *arg, unsigned int worker_id)
{
	int n;
	char c = 0;
	int m2w = lcore_config[worker_id].pipe_main2worker[1];
	int w2m = lcore_config[worker_id].pipe_worker2main[0];
	int rc = -EBUSY;

	if (lcore_config[worker_id].state != WAIT)
		goto finish;
	// 业务函数指针赋值
	lcore_config[worker_id].f = f;
	lcore_config[worker_id].arg = arg;

	/* send message */
    // 管道消息通知
	n = 0;
	while (n == 0 || (n < 0 && errno == EINTR))
		n = write(m2w, &c, 1);
	if (n < 0)
		rte_panic("cannot write on configuration pipe\n");

	/* wait ack */
	do {
		n = read(w2m, &c, 1);
	} while (n < 0 && errno == EINTR);

	if (n <= 0)
		rte_panic("cannot read on configuration pipe\n");

	rc = 0;
finish:
	rte_eal_trace_thread_remote_launch(f, arg, worker_id, rc);
	return rc;
}

举例

dpdk example的l2fwd例子中,执行的是

rte_eal_mp_remote_launch(l2fwd_launch_one_lcore, NULL, CALL_MAIN);

那么 所有的工作线程和主线程,都会执行l2fwd_launch_one_lcore业务函数,都死在里面干活。

思考

如果有不同的业务怎么办?

主线程可以直接执行rte_eal_remote_launch(),对不同的 worker 使用不用的业务函数即可。

主线程在完成启动worker以后,也可以继续直接执行自己的业务函数。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值