DPDK-RCU的简明使用

文章目录

    • 摘要
    • RCU的基本概念
    • DPDK RCU Library的使用
    • 其他

摘要

本文主要介绍DPDK中RCU Library的使用。

在使用这个库之前,我们先了解RCU的基本概念。

掌握RCU的基本概念后,便可轻松的使用这个库。


RCU的基本概念

参考:Linux内核同步机制之(七):RCU基础、 What is RCU? – “Read, Copy, Update” — The Linux Kernel documentation

上面两篇文档交叉着看下,基本能明白RCU的概念。(我英语比较烂,读英文隔着点意思,我更喜欢中文文档

下面内容,来自上面链接

每个工具,都有最佳的适应场景。RCU主要适应如下场景:(1)RCU只能保护动态分配的数据结构,并且必须是通过指针访问该数据结构。(2)受RCU保护的临界区内不能阻塞。(3)读写不对称,对writer的性能没有特别要求,但是reader性能要求极高。(4)reader端对新旧数据不敏感。

我们再来看下RCU的基本思路。

在这里插入图片描述

上图可以看到RCU中核心的两部:removal 和 reclamation。

removal:(1)读取线程一直读,没有任何锁。(2)当写线程需要修改这块内容时,它先拷贝下这块内容到新的内存,然后在新的内存中进行修改。此时读取线程,读取的任然是旧的未修改的内容。(3) 原子操作,使用新内存地址,替换旧的内存地址。此时,原来在读取旧内存的线程,继续读旧内存中的数据;后来的读取线程,将读新内存的数据。

reclamation:待读取旧内存的线程,都离开这段区域后,写线程回收释放掉旧内存。

理解RCU的基本概念后,我们再看下RCU的API,可以抽象成下面两部分:

读取线程使用的RCU API:

  • rcu_read_lock,用来标识临界区的开始
  • rcu_dereference, 用来获取RCU保护数据的指针
  • rcu_read_unlock,用来标识离开临界区

写线程使用的RCU API:

  • rcu_assign_pointer, 使用新版本的内存的指针,替换旧版本的指针。
  • synchronize_rcu, 等待读取旧版本的线程,都离开,这会造成写线程阻塞。
  • call_rcu, 异步操作,当读取旧版本的线程都离开临界区后,自动调用回调函数,回调函数中回收旧版本的资源

明白上面内容后,我们即掌握了RCU的基本概念。这里再补充几个RCU相关的术语,以便沟通。

Quiescent State: 当前CPU对应的线程,离开了这块不同线程共享的内存。(这描述的是当前这点的状态,而不是时间段)

grace period: 在将旧版本的内存指针,替换成新版本的数据之后,到允许将旧版本的内容回收,这段时间。(这要求读取旧版本数据的线程,都到达一次Quiescent State)


DPDK RCU Library的使用

参考:RCU Library、 DPDK: lib/rcu/rte_rcu_qsbr.h File Reference、 test_rcu_qsbr.c

这节提供了一个demo,这个demo模拟这样的场景:DPDK在高速的转发数据中; 然后允许通过信号动态的修改配置。(这里很使用使用RCU,对reader的性能要求极高,对writer的性能没啥要求)

首先是初始化:rte_rcu_qsbr_get_memsize()rte_rcu_qsbr_init()

读取线程:

  • reader线程, 注册,用来将来报告quiescent state: rte_rcu_qsbr_thread_register()
  • reader线程,进入临界区时调用:rte_rcu_qsbr_thread_online()
  • reader线程,离开临界区时调用:rte_rcu_qsbr_thread_offline()
  • reader线程,离开临界区之前,更新下自己的quiescent state: rte_rcu_qsbr_quiescent()

写线程:

  • writer线程,阻塞grace period时间,然后便可回收资源:rte_rcu_qsbr_synchronize
  • writer线程,如果不想阻塞,可以将待回收的资源放入队列中自动回收:rte_rcu_qsbr_dq_enqueue

上面这些API都是用户层面的。它的源码值得一读,以观RCU的一种实现。(我没看它的源码目前)

#include <signal.h>
#include <stdatomic.h>#include <rte_common.h>
#include <rte_eal.h>
#include <rte_errno.h>
#include <rte_log.h>
#include <rte_malloc.h>
#include <rte_memcpy.h>
#include <rte_rcu_qsbr.h>
#include <rte_thread.h>#define RTE_LOGTYPE_RCU_TEST RTE_LOGTYPE_USER1
#define CONFIG_SIZE 100struct rte_rcu_qsbr *rcu = NULL;
char *global_config = NULL;void signal_handle(int signum) {if (signum == SIGTERM) {exit(0);} else if (signum == SIGHUP) {char *new_config = rte_zmalloc(NULL, CONFIG_SIZE, 0);if (strcmp(global_config, "enable") == 0) {snprintf(new_config, CONFIG_SIZE, "%s", "disable");} else {snprintf(new_config, CONFIG_SIZE, "%s", "enable");}// 使用新的内存地址,替换旧的内存地址char *old_config = global_config;global_config = new_config;// __atomic_store(&global_config, &new_config, __ATOMIC_RELAXED);// 阻塞,知道所有reader在旧地址上的读取结束rte_rcu_qsbr_synchronize(rcu, RTE_QSBR_THRID_INVALID);// 释放旧内存rte_free(old_config);} else {RTE_LOG(DEBUG, RCU_TEST, "unsolve signal: %d\n", signum);}
}uint32_t loop(void *arg) {// can not lcore id// unsigned int lcore_id = rte_lcore_id();unsigned int lcore_id = *(int *)arg;RTE_LOG(DEBUG, RCU_TEST, "start thread %u\n", lcore_id);rte_rcu_qsbr_thread_register(rcu, lcore_id);while (1) {rte_rcu_qsbr_thread_online(rcu, lcore_id);RTE_LOG(DEBUG, RCU_TEST, "curret config: %s\n", global_config);rte_rcu_qsbr_quiescent(rcu, lcore_id);rte_rcu_qsbr_thread_offline(rcu, lcore_id);}return 0;
}int main(int argc, char *argv[]) {rte_log_set_level(RTE_LOGTYPE_RCU_TEST, RTE_LOG_DEBUG);RTE_LOG(DEBUG, RCU_TEST, "%s\n", "start dpdk_rcu_test...");if (rte_eal_init(argc, argv) < 0) {RTE_LOG(ERR, RCU_TEST, "%s\n", rte_strerror(rte_errno));exit(-1);}size_t size = rte_rcu_qsbr_get_memsize(RTE_MAX_LCORE);rcu = rte_zmalloc(NULL, size, 0);rte_rcu_qsbr_init(rcu, RTE_MAX_LCORE);global_config = rte_zmalloc(NULL, CONFIG_SIZE, 0);if (global_config == NULL) {RTE_LOG(ERR, RCU_TEST, "%s\n", "no enough space");exit(-1);}char status[] = "enable";rte_memcpy(global_config, status, sizeof(status));// 注册信号处理函数struct sigaction action;action.sa_handler = signal_handle;action.sa_flags = SA_RESTART;sigfillset(&action.sa_mask);sigaction(SIGTERM, &action, NULL);sigaction(SIGHUP, &action, NULL);// 创建多个reader线程unsigned int lcore_cnt = rte_lcore_count();rte_thread_t thread_ids[lcore_cnt];for (unsigned int i = 0; i < lcore_cnt; i++) {rte_thread_create(&thread_ids[i], NULL, loop, &i);}// 避免进程退出for (unsigned int i = 0; i < lcore_cnt; i++) {rte_thread_join(thread_ids[i], NULL);}rte_free(global_config);
}

其他

第一个问题是,上面,使用新内存指针,替换旧内存指针时,需不需要使用atomic来确保原子操作。

global_config = new_config;
// __atomic_store(&global_config, &new_config, __ATOMIC_RELAXED);

直觉是不需要的。如果需要的话,RCU API文档中一定会说明的。另外我们可以验证下上面的赋值操作是否为原子操作。我们通过gdb查看它对应的汇编代码。

32          global_config = new_config;0x000000000005b009 <+185>:   mov    -0x10(%rbp),%rax0x000000000005b00d <+189>:   mov    %rax,0x5413ffc(%rip)        # 0x546f010 <global_config>

可以看到赋值操作,不是一个原子操作,分为两步:将new_config的值加载到rax寄存器中,然后修改global_config的值。但是修改global_config的值是原子操作。(一般情况下,一条汇编对应一个机器指令,但也不一定:cpu architecture - Do assembly instructions map 1-1 to machine language? - Stack Overflow)

所以,直接赋值修改就行了。不是加加减减的操作,这里赋值用不着进行原子操作。

另外,上面示例中使用了信号。信号还是一个挺麻烦的东西,它的使用可见:Linux上信号的使用_linux 设置信号-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/761139.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

每天学习几道面试题|Kafka(二)架构设计类

文章目录 1. Kafka 是如何保证高可用性和容错性的&#xff1f;2. Kafka 的存储机制是怎样的&#xff1f;它是如何处理大量数据的&#xff1f;3. Kafka 如何处理消费者的消费速率低于生产者的生产速率&#xff1f;4. Kafka 集群中的 Controller 是什么&#xff1f;它的作用是什么…

深度学习模型部署-番外-TVM机器学习编译

什么是机器学习编译器/AI编译&#xff1f; 图片来自知乎大佬的文章 机器学习编译是指&#xff1a;将模型从训练形式转变为部署模式 训练模式&#xff1a;使用训练框架定义的模型部署模式&#xff1a;部署所需要的模式&#xff0c;包括模型每个步骤的实现代码&#xff0c;管理资…

什么是代理IP?TikTok运营需要知道的IP知识

对于运营TikTok的从业者来说&#xff0c;IP的重要性自然不言而喻。 在其他条件都正常的情况下&#xff0c;拥有一个稳定&#xff0c;纯净的IP&#xff0c;你的视频起始播放量很可能比别人高出不少&#xff0c;而劣质的IP轻则会限流&#xff0c;重则会封号。那么&#xff0c;如何…

插入排序+希尔排序

目录 插入排序&#xff1a; 希尔排序&#xff1a; 插入排序&#xff1a; 注意这里不要将插入排序和冒泡排序弄混&#xff1a; 插入排序是将数据不断放入前一个有序数列&#xff1a; // 插入排序 void InsertSort(int* a, int n) {for (int j 1; j < n; j){for (int i j;…

Java类的多态作用及解析

多态是面向对象编程中一个重要的特性。简单来说&#xff0c;多态就是指同一个方法在不同的对象上有不同的实现。通过多态&#xff0c;我们可以在运行时根据对象的实际类型来动态地调用相应的方法&#xff0c;从而提高代码的灵活性和可扩展性。 以下是 Java 类中多态的一些作用…

sobel算子详解

Sobel 算子是一种常用的边缘检测算子&#xff0c;它 可以在图像中检测出边缘的位置和方向。Sobel 算子结合了平滑和微分操作&#xff0c;能够有效地检测出图像中的边缘。 Sobel 算子原理&#xff1a; Sobel 算子使用两个 3x3 的卷积核&#xff08;一个用于检测水平边缘&…

如何用HBuider x网页制作蜡笔小新

目录 下载软件 ​编辑 一.制作蜡笔小新个人介绍界面 二.制作蜡笔小新我的偶像界面 三.制作蜡笔小新我的家乡界面 四.制作蜡笔小新会员注册界面 下载软件 一、HBuilder IDE的下载 HBuilder下载官网地址&#xff1a;http://www.pc6.com/mac/140609.htmlHBuilderX官方电脑版…

UpGrow评论:AI能将我的Instagram粉丝数增加10倍吗?

UpGrow Review: Can AI Grow My Instagram Followers 10X? 概述 UpGrow是一款专注于Instagram增长的AI驱动型社交媒体工具。它通过其庞大的300多人的网络&#xff0c;先进的定位功能&#xff0c;实时分析以及卓越的客户服务&#xff0c;帮助用户有机地增长Instagram关注者。…

Oracle锁表解决方案

一&#xff1a;查看完成等待事件的SQL select distinct a.sid, a.event, a.seconds_in_wait, a.wait_class, c.sql_text, c.SQL_ID, d.spid, b.OSUSER, b.USERNAME, d.program from gv$session_wait a, gv$session b, gv$sqlarea c, gv$process d where a.sid b.sid and a.st…

leetcode排列硬币

求根公式解法 public static int arrangeCoins(int n) {return (int) ((Math.sqrt((long) n * 8 1) - 1) / 2);} 二分法 暂未实现 牛顿迭代 暂未实现

【机器学习-07】逻辑回归(Logistic Regression)的介绍和python实现

Logistic Regression 虽然被称为回归&#xff0c;但其实际上是分类模型&#xff0c;并常用于二分类。主要用来表示某件事情发生的可能性&#xff0c;因此因变量的范围在 0 和 1 之间。Logistic Regression 因其简单、可并行化、可解释强深受工业界喜爱。例如&#xff0c;探讨引…

MyBatis配置文件详解

下面是一个典型的 MyBatis 配置文件 <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configurat…

Acwing.1355 母亲的牛奶(BFS)

题目 农夫约翰有三个容量分别为 A,B,C升的挤奶桶。 最开始桶 A和桶 B都是空的&#xff0c;而桶 C里装满了牛奶。 有时&#xff0c;约翰会将牛奶从一个桶倒到另一个桶中&#xff0c;直到被倒入牛奶的桶满了或者倒出牛奶的桶空了为止。 这一过程中间不能有任何停顿&#xff0…

前端静态开发案例-基于H5C3开发的仿照视频网站的前端静态页面-2 样式表部分和效果展示

原创作者&#xff1a;田超凡&#xff08;程序员田宝宝&#xff09; 版权所有&#xff0c;引用请注明原作者&#xff0c;严禁复制转载 charset "utf-8"; /* 程序员田宝宝原创版权所有&#xff0c;仿冒必究&#xff0c;该界面是仿照某视频网站官网开发的静态页面 */ …

Python 操作sqllite

共有5个字段&#xff0c;实现增、查、改功能 import sqlite3 import threading import functoolsPATH_DATABASE threelang.dbdef synchronized(func):functools.wraps(func)def wrapper(self, *args, **kwargs):with self.lock:return func(self, *args, **kwargs)return wra…

基于Jenkins + Argo 实现多集群的持续交付

作者&#xff1a;周靖峰&#xff0c;青云科技容器顾问&#xff0c;云原生爱好者&#xff0c;目前专注于 DevOps&#xff0c;云原生领域技术涉及 Kubernetes、KubeSphere、Argo。 前文概述 前面我们已经掌握了如何通过 Jenkins Argo CD 的方式实现单集群的持续交付&#xff0c…

埃克拉姆·阿拉姆,MindPortal的CEO和联合创始人 - 采访系列

Ekram Alam 专访&#xff1a;MindPortal 创始人兼首席执行官 引言 Ekram Alam 是 MindPortal 的创始人兼首席执行官&#xff0c;该公司致力于开发非侵入式神经接口&#xff0c;改变人类与人工智能的交互方式。他们的使命是通过让用户仅通过思想与人工智能交互&#xff0c;从而…

c++11笔记 跨平台线程池

1. 左值&#xff1a; 简单的说&#xff0c;可以放在等号左边的变量可以称之为左值&#xff0c;可以对该变量进行取地址运算的是左值,左值在内存中有确切的地址&#xff0c;可以长期存在&#xff0c;拥有具体的名字的。 比如 int a 10; int *p &a;//这里的a就是左值 2.…

java 继承(下)

前面我们已经说明了什么是继承&#xff1f;继承的好处弊端等&#xff0c;不清楚的可参照链接 java 继承&#xff08;上&#xff09;-CSDN博客 本篇文章主要理解 继承中变量&#xff0c;构造方法&#xff0c;成员方法的访问特点。 1、继承中变量的访问特点 1.1 代码实现 不看…

亚马逊认证考试系列 - 知识点 - 安全组简介

AWS安全组是一种虚拟防火墙&#xff0c;用于控制实例进出网络流量。安全组是一个实例级别的防火墙&#xff0c;可以定义哪些流量可以进入或离开特定的EC2实例。 功能&#xff1a;安全组可以用于限制特定类型的流量&#xff0c;如HTTP或SSH&#xff0c;允许特定IP地址范围的流量…