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.hqwc.cn/news/557353.html

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

相关文章

软考高级:特定领域软件架构(DSSA)概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

c语言--字符转换函数(tolower、toupper.)

目录 一、前言二、使用举例 一、前言 C语⾔提供了2个字符转换函数&#xff1a; int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写 int toupper ( int c ); //将参数传进去的⼩写字⺟转⼤写二、使用举例 #include <ctype.h> #include<stdio.h> int main(…

P4344 [SHOI2015] 脑洞治疗仪 线段树+二分

主要是维护一个连续区间&#xff0c;比较经典的题目&#xff0c;还要考虑一下二分的情况&#xff0c;否则很难处理&#xff0c;比较有难度。这里和序列操作一题的区别是不需要考虑1的个数&#xff0c;因为不需要取反。传送门https://www.luogu.com.cn/problem/P4344 #include&…

2024.3.21 QT

思维导图 自由发挥登录窗口的应用场景&#xff0c;实现一个登录窗口界面。&#xff08;不要使用课堂上的图片和代码&#xff0c;自己发挥&#xff0c;有利于后面项目的完成&#xff09; 要求&#xff1a; 1. 需要使用Ui界面文件进行界面设计 2. ui界面上的组件相关设置&…

2024全国水科技大会【发言单位】天健水务集团(杭州)有限公司

天健水务&#xff0c;始创于2003年&#xff0c;下属浙江天行健水务有限公司、杭州天勤水处理技术有限公司、杭州天行健新能源有限公司&#xff0c;是一家致力于现代化水处理设备与系统研发、生产及工程应用的国家高新技术企业。以天健智造、天健工程、天健运维的“一站式全流程…

[QT] QTextBrowser取消默认右键菜单项 复制链接地址

setTextInteractionFlags(Qt::TextSelectableByMouse);原理 QTextBrowser默认下有三个标志位&#xff0c;QTextBrowser右键菜单相关源码如下 源码链接 if ((d->interactionFlags & Qt::LinksAccessibleByKeyboard)|| (d->interactionFlags & Qt::LinksAccessible…

【Unity】捕捉PC桌面的插件

【背景】 之前介绍了如何用一款名为uWindowCapture的Unity免费插件在Unity的Canvas上展示PC桌面。经过一段时间的使用,本篇继续分享此插件的一些功能和限制。 在此感谢作者Hecomi。 【特征和限制】 一般局域网络环境只能最多达到15帧的帧率,所以别幻想用来窜流游戏或者看电…

外包干了20天,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;21年通过校招进入杭州某软件公司&#xff0c;干了接近2年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了2年的功能测试…

做独立站烧不烧钱?真做起来的话要投入多少成本?

建立一个独立网站需要花钱吗&#xff1f; 实际做起来要花多少钱&#xff1f; 这是一种灵魂的拷问&#xff0c;也是大多数想进入这个行业或者刚刚起步的人都在思考或者思考的问题。 对于这样的问题&#xff0c;没有人能够给出确切的数字&#xff0c;甚至是确定的答案。 至于为什…

openssl3.2 - note - Getting Started with OpenSSL

文章目录 openssl3.2 - note - Getting Started with OpenSSL概述笔记openssl 历史版本Configure 选项开关支持的OSopenssl 文档简介安装新闻每个平台的安装文档支持的命令列表配置文件格式环境变量 END openssl3.2 - note - Getting Started with OpenSSL 概述 看到官方文档…

STM32最小核心板使用HAL库ADC读取MCU温度(使用DMA通道)

STM32自带CPU的温度数据&#xff0c;需要使用ADC去读取。因此在MX创建项目时如图配置&#xff1a; 模块初始化代码如下&#xff1a; void MX_ADC1_Init(void) {/* USER CODE BEGIN ADC1_Init 0 *//* USER CODE END ADC1_Init 0 */ADC_ChannelConfTypeDef sConfig {0};/* USER…

日报小工具 - 钉钉定时发送Wiki日报

前言&#xff1a;   测试日报是用来记录每天测试工作的完成情况、发现的问题&#xff0c;以及问题修复进度&#xff0c;以便团队了解项目进展、及时调整计划&#xff0c;并保持信息同步交流。每日发送测试日报属于重复性工作&#xff0c;开发日报小工具结合Jenkins进行定时自…