百篇博客系列篇.本篇为:

本篇说清楚原子操作

读本篇之前建议先读鸿蒙内核源码分析(总目录)系列篇.

基本概念

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

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

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

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

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

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

ArchSpinLock 申请锁代码

    FUNCTION(ArchSpinLock)  @死守,非要拿到锁
mov r1, #1 @r1=1
1: @循环的作用,因SEV是广播事件.不一定lock->rawLock的值已经改变了
ldrex r2, [r0] @r0 = &lock->rawLock, 即 r2 = lock->rawLock
cmp r2, #0 @r2和0比较
wfene @不相等时,说明资源被占用,CPU核进入睡眠状态
strexeq r2, r1, [r0]@此时CPU被重新唤醒,尝试令lock->rawLock=1,成功写入则r2=0
cmpeq 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 = 0
dsb @数据同步隔离
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_AtomicAddLOS_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

鸿蒙内核源码分析.总目录

v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51.c.h .o

百万汉字注解.百篇博客分析

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee| github| csdn| coding >

百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto| csdn| harmony| osc >

关注不迷路.代码即人生

QQ群:790015635 | 入群密码: 666

原创不易,欢迎转载,但请注明出处.

鸿蒙内核源码分析(原子操作篇) | 是谁在为原子操作保驾护航 | 百篇博客分析OpenHarmony源码 | v34.02的更多相关文章

  1. v72.01 鸿蒙内核源码分析(Shell解析) | 应用窥伺内核的窗口 | 百篇博客分析OpenHarmony源码

    子曰:"苟正其身矣,于从政乎何有?不能正其身,如正人何?" <论语>:子路篇 百篇博客系列篇.本篇为: v72.xx 鸿蒙内核源码分析(Shell解析篇) | 应用窥视 ...

  2. v75.01 鸿蒙内核源码分析(远程登录篇) | 内核如何接待远方的客人 | 百篇博客分析OpenHarmony源码

    子曰:"不学礼,无以立 ; 不学诗,无以言 " <论语>:季氏篇 百篇博客分析.本篇为: (远程登录篇) | 内核如何接待远方的客人 设备驱动相关篇为: v67.03 ...

  3. v76.01 鸿蒙内核源码分析(共享内存) | 进程间最快通讯方式 | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(共享内存篇) | 进程间最快通讯方式 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) | 同样 ...

  4. v79.01 鸿蒙内核源码分析(用户态锁篇) | 如何使用快锁Futex(上) | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(用户态锁篇) | 如何使用快锁Futex(上) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...

  5. v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析( ...

  6. v80.01 鸿蒙内核源码分析(内核态锁篇) | 如何实现快锁Futex(下) | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(内核态锁篇) | 如何实现快锁Futex(下) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...

  7. 鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段 | 百篇博客分析OpenHarmony源码 | v71.01

    子曰:"我非生而知之者,好古,敏以求之者也." <论语>:述而篇 百篇博客系列篇.本篇为: v71.xx 鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段 ...

  8. 鸿蒙内核源码分析(管道文件篇) | 如何降低数据流动成本 | 百篇博客分析OpenHarmony源码 | v70.01

    百篇博客系列篇.本篇为: v70.xx 鸿蒙内核源码分析(管道文件篇) | 如何降低数据流动成本 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一 ...

  9. 鸿蒙内核源码分析(文件句柄篇) | 深挖应用操作文件的细节 | 百篇博客分析OpenHarmony源码 | v69.01

    百篇博客系列篇.本篇为: v69.xx 鸿蒙内核源码分析(文件句柄篇) | 深挖应用操作文件的细节 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说 ...

随机推荐

  1. zookeeper同一台服务器创建伪集群

    下载zk wget https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.7.0/apache-zookeeper-3.7 ...

  2. Centos7上yum安装mongodb(安装epel中的版本可能会比较老)

    yum install epel-release 搜索mongodb-server 安装mongodb yum install mongodb-server.x86_64 配置mongodb vim ...

  3. WPF 饼状图,柱形图,折线图 (1 柱形图)

    WPF三贱客绘制,柱形图应该是比较简单的一个了.效果如下: ItemSource数据结构可自己定义,我的如下列子,自定义的数据结构属性,要对应配置下DisplayMemberMsg 和DisplayM ...

  4. 使用DOM方法来遍历一个文档

    问题 你有一个HTML文档要从中提取数据,并了解这个HTML文档的结构. 方法 将HTML解析成一个Document之后,就可以使用类似于DOM的方法进行操作.示例代码: File input = n ...

  5. 深入浅出Mybatis系列(九)---缓存

    MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存. 1.默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启. 2.二级缓存需要手动开启和配置,他是基于namesp ...

  6. node上传包到npm公共库

    自己想做些插件分享出去,npm上传你的插件包是最好的选择,废话不多说,直接开干: 1.我们先建一个文件夹,随便先写个js文件,名字随便写,命名hello.js: 2.再用npm init命令生成pac ...

  7. Mybatis中多表联查,查询出来的字段出现重名,造成数据异常的解决方法!

    在做一对多出现的问题,引发的思考:当数据库表中,主表的主键id和明细表的中的字段名相同时怎么办?Mybatis进行自动映射赋值的时候会不会出现异常?                      注意:M ...

  8. jdbc操作mysql(四):利用反射封装

    前言 有了前面利用注解拼接sql语句,下面来看一下利用反射获取类的属性和方法 不过好像有一个问题,数据库中的表名和字段中带有下划线该如何解决呢 实践操作 工具类:获取connection对象 publ ...

  9. 3k+2形式的素数相关的两个命题的证明

  10. .Net中异步任务的取消和监控

    相关类型: CancellationTokenSource 主要用来创建或取消令牌 CancellationToken 监听令牌状态,注册令牌取消事件 OperationCanceledExcepti ...