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以后,也可以继续直接执行自己的业务函数。