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,一经查实,立即删除!

相关文章

深度学习模型部署-番外-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 类中多态的一些作用…

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

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

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

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

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

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

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

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

java 继承(下)

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

若依添加页面

背景&#xff1a;我想增加的是一个收支管理的页面 views中直接添加income文件夹&#xff0c;里面放着index.vue 网页的菜单中添加这个页面的菜单

基于springboot的留守儿童爱心网站

技术&#xff1a;springbootmysqlvue 一、系统背景 现代社会&#xff0c;由于经济不断发展&#xff0c;旧物捐赠的数量也在不断的增加&#xff0c;人们对留守儿童爱心信息的需求也越来越高。 以往的留守儿童爱心的管理&#xff0c;一般都是纸质文件来管理留守儿童爱心信息&am…

分布式异步任务框架celery

Celery介绍 github地址&#xff1a;GitHub - celery/celery: Distributed Task Queue (development branch) 文档地址&#xff1a;Celery - Distributed Task Queue — Celery 5.3.6 documentation 1.1 Celery是什么 celery时一个灵活且可靠的处理大量消息的分布式系统&…

Java:设计模式

文章目录 参考简介工厂模式简单工厂模式工厂方法模式抽象工厂模式总结 单例模式预加载懒加载线程安全问题 策略模式 参考 知乎 简介 总体来说设计模式分为三类共23种。 创建型模式&#xff0c;共五种&#xff1a;工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模…

基于浏览器localStorage作为数据库完成todolsit项目

一、文章内容 TodoList结构搭建HTML代码 TodoList样式编写Css代码 TodoList行为表现JavaScript代码 二、项目展示 项目介绍 Todolist是一个基于B/S模式开发的待办事项软件&#xff0c;主要功能是离线记录用户的待办事项和已经完成的事情&#xff0c;基于htmlcssjs实现&am…

【C++】---string的模拟

【C】---string的模拟 一、string类实现1.string类的构造函数2.swap&#xff08;&#xff09;函数3.拷贝构造函数4.赋值运算符重载5.析构6.迭代器7.operator[ ]8.size9.c_str&#xff08;&#xff09;10.reserve&#xff08;&#xff09;11.resize&#xff08;&#xff09;12.p…

flutter 局部view更新,dialog更新进度,dialog更新

局部更新有好几种方法&#xff0c;本次使用的是 StatefulBuilder 定义 customState去更新对话框内容 import package:flutter/cupertino.dart; import package:flutter/material.dart;class ProgressDialog {final BuildContext context;BuildContext? dialogContext;double _…

【DL经典回顾】激活函数大汇总(四十一)(SinReLU附代码和详细公式)

激活函数大汇总(四十一)(SinReLU附代码和详细公式) 更多激活函数见激活函数大汇总列表 一、引言 欢迎来到我们深入探索神经网络核心组成部分——激活函数的系列博客。在人工智能的世界里,激活函数扮演着不可或缺的角色,它们决定着神经元的输出,并且影响着网络的学习能…

8节点空间壳单元Matlab有限元编程 | 曲壳单元 | 模态分析 | 3D壳单元 | 板壳理论| 【源代码+理论文本】

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

Mysql的行级锁

MySQL 中锁定粒度最小的一种锁&#xff0c;是 针对索引字段加的锁 &#xff0c;只针对当前操作的行记录进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小&#xff0c;并发度高&#xff0c;但加锁的开销也最大&#xff0c;加锁慢&#xff0c;会出现死锁。行级锁和存…

数据结构面试常见问题之Insert or Merge

&#x1f600;前言 本文将讨论如何区分插入排序和归并排序两种排序算法。我们将通过判断序列的有序性来确定使用哪种算法进行排序。具体而言&#xff0c;我们将介绍判断插入排序和归并排序的方法&#xff0c;并讨论最小和最大的能区分两种算法的序列长度。 &#x1f3e0;个人主…