更多内容请访问:Harmonyos技术社区https://harmonyos.51cto.com与华为官方共同打造这篇文章把原子操作解释的很清楚。阅读本文前,推荐阅读鸿蒙内核源码解析(总目录)系列。基本概念在支持多任务的操作系统中,修改一块内存区域的数据需要“读-修改-写”三个步骤。但是,同一个内存区域中的数据可能会被多个任务同时访问。如果其他任务中断数据修改过程,操作的执行结果将不可预测。使用开关中断的方法可以保证多任务执行的结果符合预期,但是这种方法会明显影响系统性能。ARMv6架构引入了LDREX和STREX指令,以支持更精细的共享内存非阻塞同步。这样实现的原子操作可以保证对同一数据的“读-修改-写”操作在执行过程中不会被打断,即操作的原子性。当多个任务对同一份内存数据进行加减或交换操作时,使用原子操作来保证结果的可预测性。看过鸿蒙内核源码分析(总目录)自旋锁章节的应该对LDREX和STREX指令不陌生。自旋锁的本质是对变量的原子操作,必须通过汇编代码来实现,也就是说LDREX和STREX指令保证了原子操作的底层实现。查看用于应用和释放自旋锁的汇编代码。ArchSpinLock申请锁代码FUNCTION(ArchSpinLock)@死守,必须拿到锁movr1,#1@r1=11:@cycle的作用,因为SEV是广播事件。不一定是lock->rawLock的值改变了ldrexr2,[r0]@r0=&lock->rawLock,即r2=lock->rawLockcmpr2,#0@r2与0比较时wfene@不是等于,表示资源被占用,CPU核进入休眠状态@我们比较一下r2是否等于0,等于则获取锁bne1b@不等于则继续进入循环dmb@使用DMB指令进行隔离,保证buffer中的数据已经在RAM中执行了bxlr@,此时必须获取Locked,跳转回调用ArchSpinLock函数ArchSpinUnlock释放锁代码FUNCTION(ArchSpinUnlock)@releaselockmovr1,#0@r1=0dmb@data存储隔离保证buffer中的数据已经在RAM中执行strr1,[r0]@orderlock->rawLock=0dsb@data同步隔离sev@broadcast事件到各个CPU,wakeupthesleepCPUsbxlr@jumpback调用ArchSpinLock函数运行机制Harmony为用户提供了一套at组态操作界面。●LDREXRx,[Ry]读取内存中的值,并将此段内存标记为独占访问:?读取寄存器Ry指向的4字节内存数据,保存在Rx寄存器中。?对Ry指向的内存区域添加独占访问标志。●STREXRf,Rx,[Ry]检查内存是否有独占访问标志,如果有,更新内存值并清除标志,否则不更新内存:?有独占访问标志?更新值在寄存器Rx中指向寄存器RyMemory指向的值。?标志寄存器Rf设置为0.?没有独占访问标志?没有内存更新。?标志寄存器Rf置1。●判断标志寄存器当标志寄存器为0时,退出循环,原子操作结束。当标志寄存器为1时,继续循环,再次进行原子操作。函数列表原子数据包括两种类型Atomic(带符号的32位数字)和Atomic64(带符号的64位数字)。原子操作模块为用户提供了以下功能,接口详情可查看源码。这里我们说说LOS_AtomicAdd、LOS_AtomicSub、LOS_AtomicRead、LOS_AtomicSet。理解函数的汇编代码是理解原子操作的关键。LOS_AtomicAdd//添加内存数据STATICINLINEINT32LOS_AtomicAdd(Atomic*v,INT32addVal){INT32val;UINT32status;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));returnval;}这是C语言内联汇编,一一解读●1.首先将valstatusvaddVal的值交给通用寄存器(R0~R3).●2、%2表示输入参数v,[%2]表示参数v指向的地址的值,即*v,为函数独占●3.%0~%3对应valstatusvaddVal●4.ldrex%1,[%2]表示val=*v;●5.add%1,%1,%3表示val=val+addVal;●6.strex%0,%1,[%2]表示*v=val;●7.status表示更新是否成功,成功设置为0,不成功设置为1●8.__builtin_expect是结束循环的判断语句,告诉编译器最有可能执行的分支。这条指令写成:__builtin_expect(EXP,N)。意思是:EXP==N的概率很高。全面理解__builtin_expect(status!=0,0)意味着status=1很有可能会失败。如果失败,请重试,直到strex更新为(status==0)。9."=&r"(val)修饰运算符作为输出,即寄存器的值返回给val,val为函数的返回值●10."cc"声明以上信息到GCC编译器。LOS_AtomicSub//内存数据减法STATICINLINEINT32LOS_AtomicSub(Atomic*v,INT32subVal){INT32val;UINT32status;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));returnval;}解释●volatile与LOS_AtomicAdd的解释。这里我们应该关注volatile。volatile提醒编译器,其背后定义的变量随时可能发生变化,因此编译后的程序每次都需要存储或读取这个变量。任何时候都必须直接从变量地址中读取数据。如果没有volatile关键字,编译器可能会优化读取和存储,可能会暂时使用寄存器中的值。如果这个变量被其他程序更新,就会出现不一致。//读内存数据STATICINLINEINT32LOS_AtomicRead(constAtomic*v){return*(volatileINT32*)v;}//写内存数据STATICINLINEVOIDLOS_AtomicSet(Atomic*v,INT32setVal){*(volatileINT32*)v=setVal;}编程实例调用接口相关对原子操作,观察结果:1.创建两个任务●任务1使用LOS_AtomicAdd向全局变量添加100次。●Task2使用LOS_AtomicSub从全局变量中减去100次。2.子任务结束后打印主任务中全局变量的值。#include"los_hwi.h"#include"los_atomic.h"#include"los_task.h"UINT32g_testTaskId01;UINT32g_testTaskId02;Atomicg_sum;Atomicg_count;UINT32Example_Atomic01(VOID){inti=0;for(i=0;i<100;++i){LOS_AtomicAdd(&g_sum,1);}LOS_AtomicAdd(&g_count,1);returnLOS_OK;}UINT32Example_Atomic02(VOID){inti=0;for(i=0;i<100;++i){LOS_AtomicSub(&g_sum,1);}LOS_AtomicAdd(&g_count,1);returnLOS_OK;}UINT32Example_TaskEntry(VOID){TSK_INIT_PARAM_SstTask1={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_SstTask2={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);returnLOS_OK;}结果验证g_sum=0更多信息请访问:https://harmonyos.51cto.com,与华为联合成立的鸿蒙技术社区
