鸿蒙内核源码分析(原子操作篇) | 谁在为原子操作保驾护航

基本概念

在支持多任务的操作系统中,修改一块内存区域的数据需要“读取-修改-写入”三个步骤。然而同一内存区域的数据可能同时被多个任务访问,如果在修改数据的过程中被其他任务打断,就会造成该操作的执行结果无法预知。

使用开关中断的方法固然可以保证多任务执行结果符合预期,但这种方法显然会影响系统性能。

ARMv6架构引入了LDREXSTREX指令,以支持对共享存储器更缜密的非阻塞同步。由此实现的原子操作能确保对同一数据的“读取-修改-写入”操作在它的执行期间不会被打断,即操作的原子性。

有多个任务对同一个内存数据进行加减或交换操作时,使用原子操作保证结果的可预知性。

看过自旋锁篇的应该对LDREX和STREX指令不陌生的,自旋锁的本质就是对某个变量的原子操作,而且一定要通过汇编代码实现,也就是说LDREXSTREX指令保证了原子操作的底层实现.

回顾下自旋锁申请和释放锁的汇编代码.

ArchSpinLock 申请锁代码

    FUNCTION(ArchSpinLock)  @死守,非要拿到锁mov     r1, #1      @r1=11:                      @循环的作用,因SEV是广播事件.不一定lock->rawLock的值已经改变了ldrex   r2, [r0]    @r0 = &lock->rawLock, 即 r2 = lock->rawLockcmp     r2, #0      @r2和0比较wfene               @不相等时,说明资源被占用,CPU核进入睡眠状态strexeq r2, r1, [r0]@此时CPU被重新唤醒,尝试令lock->rawLock=1,成功写入则r2=0cmpeq   r2, #0      @再来比较r2是否等于0,如果相等则获取到了锁bne     1b          @如果不相等,继续进入循环dmb                 @用DMB指令来隔离,以保证缓冲中的数据已经落实到RAM中bx      lr          @此时是一定拿到锁了,跳回调用ArchSpinLock函数

ArchSpinUnlock 释放锁代码

    FUNCTION(ArchSpinUnlock)    @释放锁mov     r1, #0          @r1=0               dmb                     @数据存储隔离,以保证缓冲中的数据已经落实到RAM中str     r1, [r0]        @令lock->rawLock = 0dsb                     @数据同步隔离sev                     @给各CPU广播事件,唤醒沉睡的CPU们bx      lr              @跳回调用ArchSpinLock函数

运作机制

鸿蒙通过对ARMv6架构中的LDREXSTREX进行封装,向用户提供了一套原子操作接口。

  • LDREX Rx, [Ry]
    读取内存中的值,并标记对该段内存为独占访问:

    • 读取寄存器Ry指向的4字节内存数据,保存到Rx寄存器中。
    • 对Ry指向的内存区域添加独占访问标记。
  • STREX Rf, Rx, [Ry]
    检查内存是否有独占访问标记,如果有则更新内存值并清空标记,否则不更新内存:

    • 有独占访问标记
      • 将寄存器Rx中的值更新到寄存器Ry指向的内存。
      • 标志寄存器Rf置为0。
    • 没有独占访问标记
      • 不更新内存。
      • 标志寄存器Rf置为1。
  • 判断标志寄存器
    标志寄存器为0时,退出循环,原子操作结束。
    标志寄存器为1时,继续循环,重新进行原子操作。

功能列表

原子数据包含两种类型Atomic(有符号32位数)与 Atomic64(有符号64位数)。原子操作模块为用户提供下面几种功能,接口详细信息可以查看源码。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此处讲述 LOS_AtomicAdd , LOS_AtomicSubLOS_AtomicReadLOS_AtomicSet
理解了函数的汇编代码是理解的原子操作的关键.

LOS_AtomicAdd

//对内存数据做加法
STATIC INLINE INT32 LOS_AtomicAdd(Atomic *v, INT32 addVal)	
{INT32 val;UINT32 status;do {__asm__ __volatile__("ldrex   %1, [%2]\n""add   %1, %1, %3\n" "strex   %0, %1, [%2]": "=&r"(status), "=&r"(val): "r"(v), "r"(addVal): "cc");} while (__builtin_expect(status != 0, 0));return val;
}

这是一段C语言内嵌汇编,逐一解读

    1. 先将 status val v addVal的值交由通用寄存器(R0~R3)接管.
    1. %2代表了入参v,[%2]代表的是参数v指向地址的值,也就是 *v ,函数要独占的就是它
    1. %0 ~ %3 对应 status val v addVal
    1. ldrex %1, [%2] 表示 val = *v ;
    1. add %1, %1, %3 表示 val = val + addVal;
    1. strex %0, %1, [%2] 表示 *v = val;
    1. status 表示是否更新成功,成功了置0,不成功则为 1
    1. __builtin_expect是结束循环的判断语句,将最有可能执行的分支告诉编译器。
      这个指令的写法为:__builtin_expect(EXP, N)。

      意思是:EXP==N 的概率很大。

      综合理解__builtin_expect(status != 0, 0)

      说的是status = 0 的可能性很大,不成功就会重新来一遍,直到strex更新成(status == 0)为止.

    1. “=&r”(val) 被修饰的操作符作为输出,即将寄存器的值回给val,val为函数的返回值
    1. "cc"向编译器声明以上信息.

LOS_AtomicSub

//对内存数据做减法
STATIC INLINE INT32 LOS_AtomicSub(Atomic *v, INT32 subVal)	
{INT32 val;UINT32 status;do {__asm__ __volatile__("ldrex   %1, [%2]\n""sub   %1, %1, %3\n""strex   %0, %1, [%2]": "=&r"(status), "=&r"(val): "r"(v), "r"(subVal): "cc");} while (__builtin_expect(status != 0, 0));return val;
}

解读

  • 同 LOS_AtomicAdd解读

volatile

这里要重点说下volatilevolatile 提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都要直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。

//读取内存数据
STATIC INLINE INT32 LOS_AtomicRead(const Atomic *v)	
{return *(volatile INT32 *)v;
}
//写入内存数据
STATIC INLINE VOID LOS_AtomicSet(Atomic *v, INT32 setVal)	
{*(volatile INT32 *)v = setVal;
}

编程实例

调用原子操作相关接口,观察结果:

1.创建两个任务

  • 任务一用LOS_AtomicAdd对全局变量加100次。
  • 任务二用LOS_AtomicSub对全局变量减100次。

2.子任务结束后在主任务中打印全局变量的值。

#include "los_hwi.h"
#include "los_atomic.h"
#include "los_task.h"UINT32 g_testTaskId01;
UINT32 g_testTaskId02;
Atomic g_sum;
Atomic g_count;UINT32 Example_Atomic01(VOID)
{int i = 0;for(i = 0; i < 100; ++i) {LOS_AtomicAdd(&g_sum,1);}LOS_AtomicAdd(&g_count,1);return LOS_OK;
}UINT32 Example_Atomic02(VOID)
{int i = 0;for(i = 0; i < 100; ++i) {LOS_AtomicSub(&g_sum,1);}LOS_AtomicAdd(&g_count,1);return LOS_OK;
}UINT32 Example_TaskEntry(VOID)
{TSK_INIT_PARAM_S stTask1={0};stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Atomic01;stTask1.pcName       = "TestAtomicTsk1";stTask1.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;stTask1.usTaskPrio   = 4;stTask1.uwResved     = LOS_TASK_STATUS_DETACHED;TSK_INIT_PARAM_S stTask2={0};stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Atomic02;stTask2.pcName       = "TestAtomicTsk2";stTask2.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;stTask2.usTaskPrio   = 4;stTask2.uwResved     = LOS_TASK_STATUS_DETACHED;LOS_TaskLock();LOS_TaskCreate(&g_testTaskId01, &stTask1);LOS_TaskCreate(&g_testTaskId02, &stTask2);LOS_TaskUnlock();while(LOS_AtomicRead(&g_count) != 2);dprintf("g_sum = %d\n", g_sum);return LOS_OK;
}

结果验证

g_sum = 0

经常有很多小伙伴抱怨说:不知道学习鸿蒙开发哪些技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?

为了能够帮助到大家能够有规划的学习,这里特别整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。

在这里插入图片描述

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

鸿蒙开发面试真题(含参考答案)

在这里插入图片描述

OpenHarmony 开发环境搭建
图片

《OpenHarmony源码解析》

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……
  • 系统架构分析
  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

图片

OpenHarmony 设备开发学习手册

图片

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙

  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往在这里插入图片描述

http://www.ppmy.cn/server/104959.html

相关文章

SpringCache操作Redis

目录 &#xff08;一&#xff09;什么是SpringCache? &#xff08;二&#xff09;解决了什么问题&#xff1f; &#xff08;三&#xff09;如何使用SpringCache&#xff1f; &#xff08;一&#xff09;什么是SpringCache? SpringCache是一个由Spring提供的缓存框架&…

Git 版本控制操作

1. 版本回退 Git 能够管理⽂件的历史版本&#xff0c;这是版本控制器重要的能⼒。如果有⼀天你发现之前前的⼯作做的出现了很⼤的问题&#xff0c;需要在某个特定的历史版本重新开始&#xff0c;这个时候&#xff0c;就需要版本回退的功能了。 执⾏ git reset 命令⽤于回退版…

ACCESS 工具注入实战 凡诺靶场

简介 Access数据库注入攻击是一种常见的网络安全&#xff0c;通过注入SQL代码来获取未授权的数据访问权限。这种攻击利用了应用程序与数据库之间的交互漏洞&#xff0c;攻击者通过输入特定的SQL代码片段来操纵数据库查询&#xff0c;从而绕过应用程序的安全机制&#xff0c;获取…

Go Convey测试框架入门(go convey gomonkey)

Go Convey测试框架入门 介绍 GoConvey是一款针对Golang的测试框架&#xff0c;可以管理和运行测试用例&#xff0c;同时提供了丰富的断言函数&#xff0c;并支持很多 Web 界面特性。 Golang虽然自带了单元测试功能&#xff0c;并且在GoConvey框架诞生之前也出现了许多第三方测…

第132天:内网安全-横向移动Exchange服务有账户CVE漏洞无账户口令爆破

域控环境0day.org 通过网盘分享的文件&#xff1a;131-0day.org内网域环境镜像文件 链接: https://pan.baidu.com/s/1rf_gHVJSNG8PEsiSr7DFSw?pwdr5jc 提取码: r5jc 给win7设置一张nat网卡&#xff0c;其他各个主机都设置为vm2 案例一&#xff1a; 域横向移动-内网服务-Exchan…

[kaggle竞赛] 毒蘑菇的二元预测

毒蘑菇的二元预测 您提供了很多关于不同二元分类任务的资源和链接&#xff0c;看起来这些都是Kaggle竞赛中的参考资料和高分解决方案。为了帮助您更好地利用这些资源&#xff0c;这里是一些关键点的总结&#xff1a; Playground Season 4 Episode 8 主要关注的竞赛: 使用银行…

Java ArrayList和LinkedList

ArrayList ArrayList是Java中最常用的数据结构之一&#xff0c;它是一个动态数组的实现&#xff0c;允许你在程序中存储和管理一个可变大小的对象列表&#xff0c;我们可以添加或删除元素。 ArrayList 继承了 AbstractList &#xff0c;并实现了 List 接口。 基本概念 Arra…

Git(面试篇)

目录 配置操作 全局配置 当前仓库配置 查看global配置 查看当前仓库配置 删除global配置 删除当前仓库配置 本地操作 查看变更情况 将当前目录及其子目录下所有变更都加入到暂存区 将仓库内所有变更都加入到暂存区 将指定文件添加到暂存区 比较工作区和暂存区的所有…

JavaScript学习文档(5):为什么需要函数、函数使用、函数传参、函数返回值、作用域、匿名函数、逻辑中断

目录 一、为什么需要函数 1、函数 2、说明 二、函数使用 1、函数的声明语法 2、函数名命名规范 3、函数调用语法 4、函数体 5、函数案例&#xff08;数字求和&#xff09; &#xff08;1&#xff09;计算1-100之间所有数字的和 三、函数传参 1、声明语法 2、调用语…

【Redis】Redis数据结构——List列表

List列表 命令lpushlpushxrpushrpushxlrangelpoprpoplindexlinsertllen 阻塞版本命令blpopbrpop 命令⼩结内部编码使用场景消息队列分频道的消息队列微博 Timeline 列表类型是⽤来存储多个有序的字符串&#xff0c;如图 2-19 所⽰&#xff0c;a、b、c、d、e 五个元素从左到右组…

Java 使用线程池和CountDownLatch分批插入或者更新数据

需求&#xff1a;在开发业务报表时&#xff0c;需要从MySQL数据库读取数据后进行操作&#xff0c;然后写入数据库&#xff0c;使用定时任务跑批。 分析&#xff1a;①兼顾性能&#xff0c;② MySQL没有Oracle那么方便、强大的存储过程。综上所述&#xff0c;使用线程池以分批提…

python dash框架 油气田可视化软件设计文档

V1.1:机器学习框架(神经网络) 时间范围优化 表格布局优化 添加前端设计元素布局 V1.0&#xff1a;基础布局和对应计算函数 要求 首先第一部分是通过神经网络预测天然气流量&#xff0c;其中输入开始时间和截止时间是为了显示这一段时间内的天然气流量预测结果 第二部分&…

前端实现首次访问,后续从本地访问

在前端实现将PDF文件下载到用户的本地磁盘&#xff0c;并在后续加载时使用本地文件&#xff0c;而不是重新从服务器下载&#xff0c;可以通过以下步骤实现&#xff1a; 1. **使用<a>标签的download属性**&#xff1a;当用户首次点击下载PDF时&#xff0c;通过<a>标…

私有仓库tomcat镜像构建

通过Tomcat安装包构建镜像 Dockerfile # 使用官方的OpenJDK镜像作为基础镜像 FROM xa-test.harbor.com:55555/idaas/openjdk:8u232 ENV CATALINA_HOME/usr/local/tomcat ENV PATH$CATALINA_HOME/bin:$PATH # 将Tomcat的压缩包复制到镜像中并解压到指定目录 COPY apache-tomcat…

Apollo9.0 PNC源码学习之Planning模块—— Lattice规划(六):横纵向运动轨迹评估

参考文章: (1)Apollo6.0代码Lattice算法详解——Part6:轨迹评估及碰撞检测对象构建 (2)自动驾驶规划理论与实践Lattice算法详解 0 前言 横纵向运动轨迹的评估,主要通过构建定点巡航和定点停车两个场景下,对纵向运动参考速度、加速度、加加速度的大小进行检验和过滤,然…

1.初识redis

文章目录 1.认识redis1.1 mysql和redis 对比1.2分布式系统1.2.1单机架构与分布式架构1.2.2数据库分离(应用服务器和存储服务器分离)与负载均衡1.2.3负载均衡器1.2.4 数据库读写分离1.2.5 数据库服务器引入缓存1.2.6数据库分库分表1.2.7 引入微服务 2.常见概念解释2.1 应用(Appl…

JDK15.0.2安装

JDK15.0.2安装 1. 下载 下载地址&#xff1a; https://www.oracle.com/java/technologies/downloads/archive/ 通过百度网盘分享的文件&#xff1a;jdk-15.0.2_windows-x64_bin.exe 链接&#xff1a;https://pan.baidu.com/s/15AOcTby3YLSp26_btCkEIw 提取码&#xff1a;vs7…

10. 指针数组和数组指针详细区别

指针数组和数组指针在存储位置和占用内存大小方面也有显著的区别&#xff0c;尤其是它们的结构不同导致内存分布上的差异。接下来详细说明它们在这两个方面的区别&#xff1a; 1. 指针数组 (Array of Pointers) 定义回顾&#xff1a; int *array[5];这里 array 是一个指针数…

K8S部署MySQL5.7的主从服务

mysql-slave-0是master mysql-slave-1是备份 当mysql写的时候&#xff0c;找headless service中的 mysql-slave-0.mysql57-slave-headless&#xff1b;当mysql读的时候&#xff0c;找clusterip service中的mysql57-slave-read读&#xff0c;实现读写分离。 statefulset维护两个…

Spring + Boot + Cloud + JDK8 + Elasticsearch 单节点 模式下实现全文检索高亮-分页显示 快速入门案例

1. 安装elasticsearchik分词器插件 sudo wget https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-8.13.4.zip sudo mkdir -p ./es_plugins/analysis-ik sudo mkdir ./es_data sudo unzip elasticsearch-analysis-ik-8.13.4.zip -d ./es_plugins/a…