dpdk多线程、多进程模型详解(druid 多线程)
citgpt 2024-09-09 02:16 9 浏览 0 评论
dpdk支持多线程的运行方式, 也支持多进程的运行方式。本篇文章来分析下dpdk的多线程, 多进程模型。
一、多线程模型
一个cpu上可以运行多个线程, 由linux内核来调度各个线程的执行。内核在调度线程时,会进行上下文切换,保存线程的堆栈等信息, 以便这个线程下次再被调度执行时,继续从指定的位置开始执行。然而上下文切换是需要耗费cpu资源的的,多核体系的CPU,物理核上的线程来回切换,会导致L1/L2 cache命中率的下降。同时NUMA架构下,如果操作系统调度线程的时候,跨越了NUMA节点,将会导致大量的L3 cache的丢失。
Linux对线程的亲和性是有支持的, 如果将线程和cpu进行绑定的话,线程会一直在指定的cpu上运行,不会被操作系统调度到别的cpu上,线程之间互相独立工作而不会互相扰完,节省了操作系统来回调度的时间。目前DPDK通过把线程绑定到cpu的方法来避免跨核任务中的切换开销。
现在开看下dpdk多线程的实现方式。
我的系统上有4个cpu, 刚开始运行dpdk进程的时候, 假设linux将调度这个dpdk主线程在cpu0上运行。dpdk主线程在运行过程中发现系统有4个cpu, 则会创建三个子线程。需要注意的是,此时这三个子线程还是运行在cpu0上的,直到每个子线程自己使用linux的亲和性功能,将子线程自己绑定到特定的cpu上。例如子线程1绑定到cpu1上运行;子线程2绑定到cpu2上运行;子线程3绑定到cpu3上运行。同时dpdk主线程自己也会进行绑定,例如绑定到cpu0上。
从图中可以看出,dpdk主线程, 从线程分别绑定到了不同的cpu上。dpdk主线程, 也叫做master控制线程,通常绑定在master core上,用于接收用户配置信息,并传递配置参数给dpdk从线程等。dpdk从线程,也称为slave数据线程,用于高速处理报文转发任务,绑定到各个slave core上。每个dpdk线程都独立处理各自的业务逻辑,而不用担心被切换到其他cpu上。
一个cpu上只会运行一个dpdk线程。系统有多少个cpu,就会创建多少个子线程,然后绑定到cpu上。当然了这是可以配置的,而不是固定死的,例如我有4个cpu, 则可以配置为只创建一个主线程,2个从线程等。
下面从代码层面来分析dpdk的多线程模型,先从主线程这一层面入手分析。
主线程处理逻辑:
1、子线程的创建
dpdk主线程运行的时候,会查看系统当前有多少cpu,然后为每个cpu都创建一个子线程。此时创建的子线程还是运行在和dpdk主线程同一个cpu上,也就是master core上。每个子线程内部会将自己绑定到不同的cpu上。
int rte_eal_init(int argc, char **argv)
{
//遍历所有的逻辑core, 除了master core之外
RTE_LCORE_FOREACH_SLAVE(i)
{
//创建主到从线程的管道
pipe(lcore_config[i].pipe_master2slave);
//创建从到主线程的管道
pipe(lcore_config[i].pipe_slave2master);
//开始设置每个从线程为wait状态
lcore_config[i].state = WAIT;
//创建子线程,此时每个子线程还是在master core上运行。内部会将每个子线程绑定到不同的从逻辑core上
ret = pthread_create(&lcore_config[i].thread_id, NULL, eal_thread_loop, NULL);
}
}
我们可以看到dpdk创建了两个方向的管道,一个是主线程到从线程方向的管道;另一个是从线程到主线程方向的管道。每个主从线程都有两个方向的管道。这两个方向的管道有什么作用呢?这两个方向的管道都是命令通道,用于主线程发送命令消息给从线程, 或者从线程发命令响应消息给主线程。每次主线程通过管道发消息给从线程后,从线程都会立即通过管道给主线程回响应消息, 之后从线程将会执行主线程通知从线程执行的特定的回调接口。
2、主线程通过管道发送命令消息给从线程
管道创建好后,主从线性就可以通过管道来进行消息交互了。那什么时候主线程可以发消息给从线程呢?主线程只有在从线程进入wait等待状态时,才会通过管道发送消息给从线程。从线程默认阻塞在read系统调用上,等待主线程的命令消息,此时从线程的状态为wait等待状态。因此只有从线程阻塞在read系统调用,也就是wait状态,表示从线程准备好接收消息了,主线程才能发消息到从线程, 如果从线程被设置为running运行状态, 或者finish状态,主线程是不会发消息给从线程的,此时主线程会等待,直到从线程进入wait状态才会发消息。从线程收到消息后,就会被唤醒,进而read系统调用返回,读取主线程通过管道发来的消息。
主线程发送消息给从线程的时候,还会传递一个回调接口,这个接口用来做什么的。其实就是用于通知从线程收到消息后,执行这个回调。例如l2fwd二层转发这个例子中,主线程传递给从线程的回调为l2fwd_launch_one_lcore, 每个从线程收到消息后,就会执行这个回调,进而处理报文的高速转发。因此也可以看出管道只是一个命令通道,而主线程传入的回调函数才是从线程真正执行的操作。
//master core发消息给所有的从线程, 让从线程执行相应的回调,并等待从线程的响应。
//只要有一个没进入wait状态,就会返回!此时由rte_eal_mp_wait_lcore接口来等待从线程进入wait状态
int rte_eal_mp_remote_launch(int (*f)(void *), void *arg,
enum rte_rmt_call_master_t call_master)
{
//遍历所有的逻辑core, 除了master core之外。确保每个逻辑core都是处于wait状态
RTE_LCORE_FOREACH_SLAVE(lcore_id)
{
if (lcore_config[lcore_id].state != WAIT)
{
return -EBUSY;
}
}
//master core发送消息到每个从线程, 让从线程执行相应的回调。并接收从线程的响应
RTE_LCORE_FOREACH_SLAVE(lcore_id)
{
rte_eal_remote_launch(f, arg, lcore_id);
}
//标记master core是否也要执行相应的回调
if (call_master == CALL_MASTER)
{
lcore_config[master].ret = f(arg);
lcore_config[master].state = FINISHED;
}
}
需要注意的是,刚开始的时候,主线程传递给从线程的回调为sync_func,这是一个空函数,从线程收到消息后将会执行这个空函数。dpdk之所以传递一个空函数,仅仅是为了测试否所有的从逻辑core已经进入wait状态,仅此而已,没有别的意图。
rte_eal_remote_launch是管道的实现方式,用于主线程通过管道发送命令给从线程,并通过管道接收从线程的响应。来看下这个接口的实现。
//master core发送消息到从线程, 并接收从线程的响应
int rte_eal_remote_launch(int (*f)(void *), void *arg, unsigned slave_id)
{
//设置从线程需要执行的回调
lcore_config[slave_id].f = f;
//master core发消息给从线程
while (n == 0 || (n < 0 && errno == EINTR))
{
n = write(m2s, &c, 1);
}
//master core等待从线程响应
do
{
n = read(s2m, &c, 1);
} while (n < 0 && errno == EINTR);
}
相关视频推荐
学习地址:Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂
需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
3、主线程等待从线程进入wait状态
来看下主线程的最后一个接口, 那就是等待从线程进入wait状态。上面已经提到了,只有在所有的从线程都进入wait状态,表示准备好了接收主线程的消息,主线程才能发消息给从线程,此时从线程才能够在read系统调用返回后接收到消息。那如果从线程处于running,或者finish状态呢,则主线程必须等待, 等待所有的从线程进入wait状态,然后发消息给所有从线程,否则因为从线程没有阻塞在read系统调用,此时主线程发消息给从线程,从线程是收不到的,相当于消息被丢弃了。例如l2fwd二层转发例子,如果rte_eal_init函数返回时,从线程都还没有进入wait状态,则主线程传递报文转发回调l2fwd_launch_one_lcore给从线程是没意义的,因为从线程收不到消息。
void rte_eal_mp_wait_lcore(void)
{
//遍历所有的逻辑core, 除了master core之外
RTE_LCORE_FOREACH_SLAVE(lcore_id)
{
//使用循环的方式,等待从线程进入wait状态。
//如果从线程一直不进入wait状态,则master core的cpu占用率将会飙升
rte_eal_wait_lcore(lcore_id);
}
}
int rte_eal_wait_lcore(unsigned slave_id)
{
//如果从线程处于run状态,则循环等待从线程进入finish状态
while (lcore_config[slave_id].state != WAIT &&
lcore_config[slave_id].state != FINISHED)
{
;
}
//将finish状态设置为wait状态
lcore_config[slave_id].state = WAIT;
}
需要注意的是,dpdk使用while循环方式等待所有从线程进入wait状态, 如果从线程迟迟不进入wait状态,则主线程将会卡在while死循环,这时会导致主线程所在的cpu利用率飙升。当然这只在dpdk进程运行瞬间才会,因为通常在rte_eal_init返回后,dpdk主线程只会发一次消息通知从线程,传递一个回调给从线程,这个回调一般也是一个死循环,用于从线程处理转发高速报文。
到此为止,主线程的处理逻辑已经分析完成了,现在来看下从线程的处理逻辑。
从线程处理逻辑
1、从线程cpu绑定
从线程的入口为eal_thread_loop,从线程首先就进行cpu的绑定,将自己绑定到某个cpu上。这样每个cpu都只运行一个dpdk线程,避免上下文切换消耗资源。
__attribute__((noreturn)) void * eal_thread_loop(__attribute__((unused)) void *arg)
{
//将某个线程绑定到从逻辑core上
if (eal_thread_set_affinity() < 0)
}
dpdk是通过调用pthread_setaffinity_np接口,将线程与cpu进行绑定的。关于这个接口使用方法,读者自行查找资料吧!这里就不再展开分析了。
2、接收主线程的消息
每个从线程都可以通过管道,接收来自主线程的消息,然后也是通过管道给主线程发送命令响应。子线程阻塞在read系统调用,等待来自主线程的命令消息。当收到主线程的消息后,从线程被唤醒,read返回后读取管道消息,并立即通过管道给主线程发送响应消息。从线程给主线程回了响应消息后,立即执行回调函数,真正执行回调函数里面的业务逻辑,例如上面提到的l2fwd二层转发l2fwd_launch_one_lcore高速报文转发接口。
__attribute__((noreturn)) void * eal_thread_loop(__attribute__((unused)) void *arg)
{
//从逻辑core线程的while死循环
while (1)
{
//从线程阻塞读取master core发来的命令
do
{
n = read(m2s, &c, 1);
} while (n < 0 && errno == EINTR);
//设置为run状态
lcore_config[lcore_id].state = RUNNING;
//从线程给主线程发送命令响应
while (n == 0 || (n < 0 && errno == EINTR))
{
n = write(s2m, &c, 1);
}
//从线程执行回调
fct_arg = lcore_config[lcore_id].arg;
ret = lcore_config[lcore_id].f(fct_arg);
lcore_config[lcore_id].ret = ret;
//设置为finish状态
lcore_config[lcore_id].state = FINISHED;
}
}
到此为止,dpdk主从线程模型已经分析完成了。
二、多进程模型
dpdk除了主从线程运行方式外, 还支持主从进程的运行方式。一个dpdk主进程对应多个dpdk从进程。dpdk主从进程共享大页内存(包括段内存,内存区,malloc堆空间,内存池), 以及共享队列,例如中断队列等。 dpdk主进程用于创建大页内存,创建内存池,创建队列;而dpdk从进程是不会重复创建的,而是通过mmap进行映射。
需要注意的是,不管是dpdk主线程,还是dpdk从进程, 内部都是支持多线程的。上面提到的主从线程,都是适用于dpdk主从进程。也就是说,dpdk主进程,内部会创建多个子线;dpdk从进程,内部也会创建多个子线程。
如何运行dpdk主从进程呢?可以在运行dpdk程序时,指定主从进程模式,设置proc-type为primary或者为secondary。例如下面的例子,将会运行4个dpdk进程,其中一个为dpdk主进程,另三个为dpdk从进程
./l2fwd -c 0xf -n 2 -- -q 2 -p 0xf -proc-type primary
./l2fwd -c 0xf -n 2 -- -q 2 -p 0xf -proc-type secondary
./l2fwd -c 0xf -n 2 -- -q 2 -p 0xf -proc-type secondary
./l2fwd -c 0xf -n 2 -- -q 2 -p 0xf -proc-type secondary
相关推荐
- js中arguments详解
-
一、简介了解arguments这个对象之前先来认识一下javascript的一些功能:其实Javascript并没有重载函数的功能,但是Arguments对象能够模拟重载。Javascrip中每个函数...
- firewall-cmd 常用命令
-
目录firewalldzone说明firewallzone内容说明firewall-cmd常用参数firewall-cmd常用命令常用命令 回到顶部firewalldzone...
- epel-release 是什么
-
EPEL-release(ExtraPackagesforEnterpriseLinux)是一个软件仓库,它为企业级Linux发行版(如CentOS、RHEL等)提供额外的软件包。以下是关于E...
- FullGC详解 什么是 JVM 的 GC
-
前言:背景:一、什么是JVM的GC?JVM(JavaVirtualMachine)。JVM是Java程序的虚拟机,是一种实现Java语言的解...
-
2024-10-26 08:50 citgpt
- 跨域(CrossOrigin)
-
1.介绍 1)跨域问题:跨域问题是在网络中,当一个网络的运行脚本(通常时JavaScript)试图访问另一个网络的资源时,如果这两个网络的端口、协议和域名不一致时就会出现跨域问题。 通俗讲...
- 微服务架构和分布式架构的区别
-
1、含义不同微服务架构:微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并...
- 深入理解与应用CSS clip-path 属性
-
clip-pathclip-path是什么clip-path 是一个CSS属性,允许开发者创建一个剪切区域,从而决定元素的哪些部分可见,哪些部分会被隐...
-
2024-10-25 11:51 citgpt
- Request.ServerVariables 大全
-
Request.ServerVariables("Url")返回服务器地址Request.ServerVariables("Path_Info")客户端提供的路...
- python操作Kafka
-
目录一、python操作kafka1.python使用kafka生产者2.python使用kafka消费者3.使用docker中的kafka二、python操作kafka细...
- Runtime.getRuntime().exec详解
-
Runtime.getRuntime().exec详解概述Runtime.getRuntime().exec用于调用外部可执行程序或系统命令,并重定向外部程序的标准输入、标准输出和标准错误到缓冲池。...
- promise.all详解 promise.all是干什么的
-
promise.all详解promise.all中所有的请求成功了,走.then(),在.then()中能得到一个数组,数组中是每个请求resolve抛出的结果...
-
2024-10-24 16:21 citgpt
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracleclient (56)
- springbatch (59)
- oracle恢复数据 (56)
- 简单工厂模式 (68)
- 函数指针 (72)
- fill_parent (135)
- java配置环境变量 (140)
- linux文件系统 (56)
- 计算机操作系统教程 (60)
- 静态ip (63)
- notifyicon (55)
- 线程同步 (58)
- xcode 4 5 (60)
- 调试器 (60)
- c0000005 (63)
- html代码大全 (61)
- header utf 8 (61)
- 多线程多进程 (65)
- require_once (60)
- 百度网盘下载速度慢破解方法 (72)
- 谷歌浏览器免费入口 (72)
- npm list (64)
- 网站打开速度检测 (59)
- 网站建设流程图 (58)
- this关键字 (67)