linux 锁-- atomic per_cpu

news/2024/5/20 21:07:17/文章来源:https://blog.csdn.net/kakaBack/article/details/126904191

atomic引入背景

对于 SMP 系统中,在开启 preempt 情况下,对于公共资源,如果存在两个 task 来进行更改,这就面临临界区资源竞争问题,此时会产生意想不到的结果,这是不符合预期的,因此需要来进行解决。

典型问题描述

对于变量的操作: a =0; a++; 汇编是如下实现:

ldr   r3, [r3, #0] 
adds  r2, r3, #1
str   r2, [r3, #0] 

也就是说,一个 a++ 实际上需要三条指令来完成,分别对应上图的 R,M,W。

这样如果 task1 在W之后,紧接着task2 也来W, 此时会产生不符合 task1 预想的结果,会产生问题。因此 arm 提出 atomic 来解决这种问题。

atomic 实现

arm32 实现

/* arch/arm/include/asm/atomic.h */
#undef ATOMIC_OPS
#define ATOMIC_OPS(op, c_op, asm_op)                    \ATOMIC_OP(op, c_op, asm_op)                 \ATOMIC_FETCH_OP(op, c_op, asm_op)#define ATOMIC_OP(op, c_op, asm_op)                 \                                                                                
static inline void atomic_##op(int i, atomic_t *v)          \
{                                   \unsigned long tmp;                      \int result;                         \\prefetchw(&v->counter);                     \__asm__ __volatile__("@ atomic_" #op "\n"           \
"1: ldrex   %0, [%3]\n"                     \     ①
"   " #asm_op " %0, %0, %4\n"                   \ ②
"   strex   %1, %0, [%3]\n"                     \ ③
"   teq %1, #0\n"                       \         ④
"   bne 1b"                         \             ⑤: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)       \: "r" (&v->counter), "Ir" (i)                   \: "cc");                            \
}                                   \ATOMIC_OPS(add, +=, add)                     

这里选取了 atomic_add 来分析,上面的 #asm_op 就是 add 了,此时代码可以解析为:

  • Prefetch data
  • 将v->counter所在地址的数据加载到 result
  • result += i, 结果存放在 result 中
  • 将result 保存到 v->counter 所在地址,同时结果保存在 tmp
  • 检查 tmp 和 0 比较,如果不等于0需要重新处理一遍

那么为什么仅仅使用了 ldrex 和 strex 就实现了 atomic 功能呢?

实际上 ldrex 和 strex 在使用过程中使用了 monitor 的功能,这里选取蜗窝科技的介绍方式介绍:

 arm64实现

/* arch/arm64/include/asm/atomic_lse.h */
#define ATOMIC64_OP(op, asm_op)                     \                                                                                
static inline void __lse_atomic64_##op(s64 i, atomic64_t *v)        \
{                                   \   asm volatile(                           \   __LSE_PREAMBLE                          \   
"   " #asm_op " %[i], %[v]\n"                   \   : [i] "+r" (i), [v] "+Q" (v->counter)               \   : "r" (v));                         \   
}ATOMIC64_OP(andnot, stclr)
ATOMIC64_OP(or, stset)
ATOMIC64_OP(xor, steor)
ATOMIC64_OP(add, stadd)  // 定义了 __lse_atomic64_add 函数, asm_op 是 stadd/* arch/arm64/include/asm/lse.h */
#define __lse_ll_sc_body(op, ...)                   \                                                                                
({                                  \   system_uses_lse_atomics() ?                 \   __lse_##op(__VA_ARGS__) :               \   __ll_sc_##op(__VA_ARGS__);              \   
})/* arch/arm64/include/asm/atomic.h */#define ATOMIC64_OP(op)                         \
static __always_inline void arch_##op(long i, atomic64_t *v)        \
{                                   \__lse_ll_sc_body(op, i, v);                 \
}                                                                                                                                    ATOMIC64_OP(atomic64_andnot)
ATOMIC64_OP(atomic64_or)
ATOMIC64_OP(atomic64_xor)
ATOMIC64_OP(atomic64_add) //这里传入的 op 是 atomic64_add, 定义了 arch_atomic64_add
ATOMIC64_OP(atomic64_and)
ATOMIC64_OP(atomic64_sub)

 对于 arch_atomic64_add ,其又调用了 __lse_ll_sc_body(atomic64_add, i, v);

这样 atomic64_add 就有了定义:

/* lib/atomic64.c */
#define ATOMIC64_OPS(op, c_op)                      \ATOMIC64_OP(op, c_op)                       \ATOMIC64_OP_RETURN(op, c_op)                    \ATOMIC64_FETCH_OP(op, c_op)ATOMIC64_OPS(add, +=)                                                                                                                
ATOMIC64_OPS(sub, -=)

这里才真正使用 宏来声明了 atomic64_add 函数,它通过 stladd 将 i 加到 atomic64_add 的变量中的 counter 上面去。stladd 是 armv8.1 提供了原子操作变量,相对于 ldrex, strex 在性能又进一步提升。

atomic典型使用

atomic_t val;atomic_set(&val, 10);int read_val = atomic_read(&val);

per_cpu 引入背景

 对于 outer-shareable 的内存而言,由于cache MESI 机制(假设是outer shareable 的),会发生如下变化:

  1. 假设原始的 CPU cache情况如下:

 图中黄色的小球是 Cache 是否命中,且和 RAM 中内容一致

2.更改 CacheB 中内容

此时CacheB 内容被修改,内容发生变化。

3.Invalidate 其它cpu cache

 因为MESI 机制,因为B更改了,此时会自动 invalidate outer shareable 的 cache 内容。

这样会带来性能上的损耗,因为被invalidate 的内容,之后如果用到,还要重新加载。

假入内容有这样的一块内存,属于CPU自己独有,它的加载以及Cache 操作不会影响到别的CPU,这样就解决了上述面临的问题。因此linux中提出 per_cpu 变量来操作。

per_cpu 变量定义

#define __PCPU_ATTRS(sec)                       \__percpu __attribute__((section(PER_CPU_BASE_SECTION sec))) \PER_CPU_ATTRIBUTES#define DEFINE_PER_CPU_SECTION(type, name, sec)             \__PCPU_ATTRS(sec) __typeof__(type) name
#endif#define DEFINE_PER_CPU(type, name)                  \DEFINE_PER_CPU_SECTION(type, name, "")

定义一个位于 PER_CPU_BASE_SECTION 的一个变量,这是一个静态声明,指定了其位于的地址空间。其section 定义如下:

#ifndef PER_CPU_BASE_SECTION
#ifdef CONFIG_SMP
#define PER_CPU_BASE_SECTION ".data..percpu"
#else
#define PER_CPU_BASE_SECTION ".data"                                                                                
#endif
#ifdef MODULE
#define PER_CPU_SHARED_ALIGNED_SECTION ""                                                                           
#define PER_CPU_ALIGNED_SECTION ""
#else
#define PER_CPU_SHARED_ALIGNED_SECTION "..shared_aligned"
#define PER_CPU_ALIGNED_SECTION "..shared_aligned"
#endif#define DEFINE_PER_CPU_SHARED_ALIGNED(type, name)           \DEFINE_PER_CPU_SECTION(type, name, PER_CPU_SHARED_ALIGNED_SECTION) \____cacheline_aligned_in_smp#define DEFINE_PER_CPU_PAGE_ALIGNED(type, name)             \ DEFINE_PER_CPU_SECTION(type, name, "..page_aligned")    \__aligned(PAGE_SIZE)

分别也是定义了位于 section name 为 "..page_aligned" 和 "..shared_aligned" 的变量。

那么为什么需要特殊的 Section呢?

对于kernel中的普通变量,经过了编译和链接后,会被放置到.data或者.bss段,系统在初始化的时候会准备好一切(例如clear bss),由于per cpu变量的特殊性,内核将这些变量放置到了其他的section,位于kernel address space中__per_cpu_start和__per_cpu_end之间,我们称之Per-CPU变量的原始变量。(参考蜗窝科技).

典型应用

DEFINE_PER_CPU(int, state);
int cpu = 0;
per_cpu(state, cpu) = 1;
int got_state = per_cpu(state, cpu);

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

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

相关文章

nginx-nginx的文件服务器的配置

nginx的文件服务器的配置location /data {charset gbk,utf-8;autoindex on;autoindex_exact_size off;autoindex_localtime on;limit_rate_after 10m;alias D:;allow all; }访问文件路径xxx/data访问成功的返回界面

CAS:385437-57-0,DSPE-PEG-Biotin,Biotin-PEG-DSPE,磷脂-聚乙二醇-生物素试剂供应

DSPE-PEG-Biotin(磷脂-聚乙二醇-生物素)的分子量有:1k,2k,3.4k等,其它的分子量可以定制。质量控制在95%,Biotin-PEG-DSPE主要用于科研实验使用,非药用,非食用。它溶于水和…

ETH 2.0 背景下的新机会与新叙事

以太坊 POW 到 POS 的转型为整个 Web3 行业的发展引入了一层新的叙事,即对于去中心化更深切的要求。TIPS 在 Merge - Shanghai Upgrade 的约 9 个月的小周期内,流通状态的 ETH 在持续单调递减; 从长远来看,未来 ETC 等 Ethhash P…

多人协作多版本开发冲突的正确解决姿势

多人版本开发工作流程:https://blog.csdn.net/qq_32442973/article/details/125717959 这里实际上用上一个关键命令:git merge --no-ff 分支名 注意:无论何种情况,都绝不允许把开发、测试、预生产的代码拉到自己分支上解决冲突&am…

Druid1.2.12版本发布,新增连接池默认配置connectTimeout和socketTimeout详解

新版本特性如下 这个版本连接池默认增加配置connectTimeout和socketTimeout,增强了SQL Parser 连接池DruidDataSource支持新的配置connectTimeout和socketTimeout,分别都是10秒。这个默认值会减少因为网络丢包时导致的连接池无法创建链接。修复连接池D…

股票量化分析工具QTYX使用攻略代码说明——高速版本地行情源v2.5.1

搭建自己的量化系统如果要长期在市场中立于不败之地!必须要形成一套自己的交易系统。否则,赚钱或者亏钱我们很难归纳总结,往往是凭借运气赚钱,而不是合理的系统模型,一时凭借运气赚的钱长期来看会因为实力还回去。QTYX…

mac 中配置idea自带maven环境变量

1.查找 maven 地址 访达--应用程序-- idea如图: 2.双击 选择 显示包内容: 3.找到maven地址:/Applications/IntelliJ IDEA.app/Contents/plugins/maven/lib/maven3 4.配置环境变量 (1)配置环境变量 终端输入 vim …

c++ Primer 第四章 表达式

4.1 基础 略 4.2 算术运算符4.3 逻辑和关系运算符4.4 赋值运算符 略 4.5 递增和递减 ++i 先自增后运算 i++ 先运算后自增 4.6 成员访问运算符 int main() {string s1 = "a string";string *pS1 = &s1;cout << pS1->size() << endl; // 等价于(*p)…

【JavaScript设计模式】增强版发布订阅模式——Webpack的核心Tapable(一)

Tapable简介 Webpack整体架构的实现就是靠它的插件系统&#xff0c;其中Compiler和Compilation负责管理整个构建流程&#xff0c;同时暴露出一些Hook&#xff0c;然后由不同职责的插件来监听这些Hook&#xff0c;并在合适的时机完成具体的工作。Tapable是整个Webpack插件系统的…

CentOS二进制安装Containerd

Containerd有两种安装包∶ 1>. 第一种是containerd-xxx&#xff0c;这种包用于单机测试没问题&#xff0c;不包runC&#xff0c;需要提前安装。 2>. 第二种是cri-containerd-cni-xxx&#xff0c;包含runC和k8s里的所需要的相关文件。k8s集群里需要用到此包&#xff0c;…

Qt5.12.2添加mqtt模块

Qt5.12.2添加mqtt模块下载 mqtt Qt 子模块使用 Qt 编译模块并install 生成动态库qtcreator 打开解压后的 qtmqtt源码下的 pro 工程文件下载 mqtt Qt 子模块 https://download.qt.io/official_releases/qt/5.15/5.15.4/submodules/ 下载后解压&#xff1a; 使用 Qt 编译模块并…

Go语言实现网盘系统(上)

该项目将基于go-zero和xorm go-zero中文文档: https://legacy.go-zero.dev/cn/ Xorm中文文档: http://xorm.topgoer.com/ 功能划分 整个项目可以分为3个模块: 用户模块、存储池模块和文件共享模块数据库设计 用户是一个实体,建立对应的表user_basic,存储了用户信息,DDL如下:…

ViLBERT—(NeurIPS-2019)

ViLBERT(Vision-and-Language BERT)是发表于2019年的论文&#xff0c;在功能上实现了文本图像的多模态特征提取与分类。改论文的特点是使用了双流模型&#xff0c;即先各个模态特征单独自注意力&#xff0c;再经过transformer交叉注意力。单流是将不同模态特征序列先拼接起来&a…

FluentCRM 2.5 – 大量新功能,自动化你的业务!

大家好&#xff01; 等待结束了&#xff01;我知道你们都在热切地等待另一个 FluentCRM 更新并且它已经上线了。 这一次&#xff0c;我很高兴地宣布我们开发了一些史诗级、最令人期待的功能。让我们开始了解 FluentCRM 2.5&#xff01; 客户关系管理是关于从您的潜在客户、顾…

ASO优化之手游该如何获得巨量新增(上)

现今各家应用商店里的应用和手游数量都非常多&#xff0c;那么提高曝光和获得自然新增是一项非常困难的工作。所以&#xff0c;应用产品ASO优化已经是所有同行都必做的功课之一。且每天都有很多款新游戏发布&#xff0c;所以想让你的手游脱颖而出就更加困难。手机游戏现已经成为…

多线程---同步方法及同步块(解决线程不安全)

同步方法 注意:锁的量是变化的量,需要增删改的对象 三大不安全案例解决方案: 案例1(火车站买票) package com.mokuiran.thread.synchronizedtest;​//不安全买票//线程不安全,将会输出负数public class UnsafeBuyTicket{​ public static void main(String[] args) { …

LayaAir 2.12.2新版本已发布,即将进入3.0时代

2.13.2这个LayaAir引擎小版本&#xff0c;修复了若干IDE与引擎的BUG&#xff0c;新增了一些2D的功能&#xff0c;3D也有所优化。这个小版本将成为LayaAir 2.0系列引擎的一个重要里程碑版本。自此开始&#xff0c;2.x引擎如果没有平台适配的新功能&#xff0c;将不会再出现beta版…

【博客498】k8s kubelet device-plugins

k8s kubelet device-plugins 场景&#xff1a; 对于云的用户来说&#xff0c;在 GPU 的支持上&#xff0c;他们最基本的诉求其实非常简单&#xff1a;我只要在 Pod 的 YAML 里面&#xff0c;声明某容器需要的 GPU 个数&#xff0c;那么 Kubernetes 为我创建的容器里就应该出现…

2022Google开发者大会—我的首次参会体验

一、大会简介 Google 开发者大会 (Google Developer Summit) 是 Google 面向开发者和科技爱好者展示最新产品和平台的年度盛会。2022 年&#xff0c;Google 开发者大会以 “共码未来” 为主题&#xff0c;携手开发者与合作伙伴&#xff0c;以科技之力&#xff0c;突破想象&…

Petalinux配置

目录 一、设计流程 1.Petalinux环境变量 2.创建petalinux工程 3.配置 petalinux 工程 4.配置 Linux 内核 5.配置 Linux 根文件系统 6.配置设备树 7.编译工程 8.制作BOOT.BIN启动文件 9.启动下载 三、配置详解 ①使能环境变量 ②创建petalinux工程 ③配置petalinu…