当前位置: 首页 > 科技观察

Loongarch架构介绍——内存模型及相关说明(二)

时间:2023-03-17 21:36:44 科技观察

了解更多开源请访问:开源基础软件社区https://ost.51cto.com前言上一篇文章介绍了基础部分loonarch架构的基础知识,包括基本的整数运算指令、浮点运算指令、内存访问指令等,以及loongarch架构中的一些寄存器约定和汇编方法。本文主要介绍loongarch架构中内存一致性模型的相关信息,以及原子指令和屏障指令的含义和用法。本文将首先介绍内存一致性模型的背景知识,然后介绍目前loonarch资料中与内存一致性模型相关的资料,然后介绍loonarch中的原子指令和屏障指令,最后对原子指令和屏障指令进行讲解结合实现自旋锁的使用方法。1.内存一致性模型内存一致性模型(memoryconsistencymodel),简称内存模型(memorymodel),是描述与程序共享内存相关的内存访问指令执行顺序行为的模型。对于一个程序,内存模型可以指出哪些内存访问指令的执行顺序是CPU允许的,哪些执行顺序是不可能的。对于一般的应用开发者来说,内存一致性模型是透明的,代码看起来是顺序执行的。在多线程中,可以通过调用系统封装的mutex等实现同步。但是对于系统开发者来说,内存一致性模型是需要了解的,这关系到应用场景,比如底层程序集的自旋锁的实现.1.SC、TSO和RMO这里列出一些常见的内存一致性模型:顺序一致性(SC):不打乱内存访问指令顺序的顺序一致性模型TotalStoreOrder(TSO):强顺序模型。x86的内存一致性模型类似于TSORelaxedMemoryOrder(RMO):weakordermodel。比如arm支持RMO,其中Load表示加载内存操作,Store表示写内存操作。除SC模型外,其他类型打乱了内存访问指令的执行顺序,这主要是出于硬件性能因素的考虑。下面分别介绍这些模型中定义的内存访问指令序列。(1)SC中内存访问指令的顺序对于Load和Store操作,其实一共有四种顺序:Load->LoadLoad->StoreStore->StoreStore->LoadSC模型保证了以上所有顺序,即实际内存访问指令的执行顺序与它们在程序中的优先级相同。下表是两个CPU核并行运行的例子:CoreC1CoreC2说明S1:x=NEWL1:r1=yS2:y=NEWL2:r2=xx,y初始为0其中,S1和S2是Store操作,L1和L2对于Load操作,x和y是cpucoreC1和C2中的共享变量,r1和r2是cpucoreC1和C2中的局部变量。SC中所有可能的执行顺序如下:S1->L1->S2->L2,result(r1,r2)=(0,NEW)S2->L2->S1->L1,result(r1,r2)=(NEW,0)S1,S2->L1,L2,结果(r1,r2)=(NEW,NEW)。S1和S2之间的顺序,以及L1和L2之间的顺序是不确定的。结果与一般程序员眼中的模型是一样的。(2)TSO中的内存访问指令顺序TSO中保证的内存访问指令顺序如下:Load->LoadLoad->StoreStore->StoreStore->Load的顺序可能会被打乱。下面用上面SC中相同的例子来说明:CoreC1CoreC2解释S1:x=NEWL1:r1=yS2:y=NEWL2:r2=xx,y初始为0TSO中所有可能的执行顺序如下:S1->L1->S2->L2,结果(r1,r2)=(0,NEW)S2->L2->S1->L1,结果(r1,r2)=(NEW,0)S1,S2->L1,L2,结果(r1,r2)=(NEW,NEW)L1,L2->S1,S2,结果(r1,r2)=(0,0)可见在TSO中,Load可以在Store之前执行,导致r1和r2均为0。(3)RMO中访问内存指令的顺序允许在RMO中进一步乱序执行,例如只保证如下顺序:Load->Load,且地址相同Load->Store,且地址相同Store->Store,地址相同2.同步方式由于TSO、RMO等内存模型中内存访问指令的执行顺序是不确定的,硬件提供同步使用原子指令和屏障指令(称为栅栏或障碍物等)。在程序中需要同步的地方手动标上原子指令和屏障指令,目标代码就可以同步了。下面举例说明:CoreC1CoreC2S1:data1=NEWS2:data2=NEWF1:FENCES3:flag=SETL1:r1=flagB1:if(r1!=SET)gotoL1F2:FENCEL2:r2=data1L3:r3=data2whereFENCE该指令保证前后的内存访问指令顺序一致。最后一条内存访问指令顺序为:S1,S2->F1->S3->L1->F2->L2,L3,结果(r1,r2,r3)=(SET,NEW,NEW)。如果去掉中间的FENCE指令,那么r2和r3可能为0。2.Loongarch内存模型相关信息1.Loongarch内存一致性模型根据loongarch架构手册的相关资料,它采用了一种弱一致性(WeaklyConsistency)模型。在这种模型下,程序员必须通过同步指令来保护对写共享单元的访问,以保证多个处理器内核在访问写共享单元时是互斥的。虽然从目前的资料无法得到loongarch内存一致性模型的具体信息,但可以推测应该是打乱了一些内存访问指令的顺序,需要使用barrier指令(即fence或barrierinstructions)进行同步.2.Loongarch内存访问类型Loongarch架构下的内存访问类型分为三种:CoherentCached、Strongly-orderedUnCached和Weakly-orderedUnCached。例如,在页表项的MAT(MemoryAccessType)字段中可以设置对应内存区域的访问类型。MAT值与内存访问类型的关系如下:0对应强序非缓存1对应一致可缓存2对应弱序非缓存3reserved通过设置内存访问类型,相当于指定内存区域的内存一致性模型。根据loonarch手册,只有强序非缓存没有副作用,即内存访问指令的顺序不被打乱。但是loongarch资料中的内存一致性模型和内存访问类型没有详细解释,很多地方都有疑问。只能推测其作用与arm上的Normal、Device、Strongly-orderedmemory模型类似。三、loongarch相关指令1、barrier指令loongarch中的barrier指令如下:dbar指令:dbarhint。dbar指令用于完成加载/存储内存访问操作之间的屏障功能,其中hint用于指示dbar指令的同步对象和同步程度。必须在loonarch中实现0的提示。如果没有特殊的函数实现,其他所有的hint值都会按hint=0执行。提示0表示功能齐全的同步屏障。只有在前面所有的load/store操作都执行完之后,dbar0才会被执行;只有在dbar0被执行后,所有后续的加载/存储操作才会被执行。ibar命令:ibar提示。ibar指令用于完成单核处理器内部store操作和取指令操作的同步,hint用于指示dbar指令的同步对象和同步程度。同样,在loonarch中必须实现0的提示。ibar0确保后续的fetch必须能够观察到ibar0指令之前的所有store操作的执行效果。2、原子指令loongarch中的原子指令有:amswap、amadd、ammax等指令:普通的原子指令,可以原子地完成对某个内存单元的“读-修改-写”过程。例如amadd.wrd,rk,rj表示以rj寄存器的值作为地址,从这个地址读取值写入rd,然后将rd和rk相加,最后将结果写回这个地址,并将此地址保存在rd的旧值中。整个过程是原子的。amswap_db、amadd_db、ammax_db等指令:具有数据屏障功能的原子指令。除了上述普通原子指令的作用外,还有dbar指令的作用。ll/sc指令:一对ll/sc指令可以完成“读-修改-写”原子操作。它的作用机制是:ll指令可以完成读操作,同时记录访问地址并在相关寄存器中设置一个标志(LL位设置为1)。sc指令可以完成写操作,会先检查寄存器中的LLbit,只有当LLbit为1时才写入。而ll和sc之间的指令完成自定义的修改操作。在ll/sc指令执行过程中,如果其他处理器核对ll/sc指令操作的同一个地址进行写操作,LLbit会被清0,sc指令也会返回失败。这样的硬件校验机制保证了原子性。另外,ll/sc指令还有一个屏障功能。有关如何使用它的详细信息,请参阅下面的自旋锁实现。loongarch原子指令的特点类似于RISC架构的一些原子指令,同样使用ll/sc等指令来替代CAS(compare-and-swap)类型的原子指令。与CAS指令相比,ll/sc指令在用CAS指令实现锁时可能会出现ABA问题,而使用ll/sc指令可以避免这个问题。4、应用场景:自旋锁下面以自旋锁的实现来说明原子指令和屏障指令的应用。作为对比,先介绍一下linux3.2下arm下自旋锁的实现。锁操作函数如下:staticinlinevoidarch_spin_lock(arch_spinlock_t*lock){unsignedlongtmp;__asm____volatile__(//加载lock->lock到tmp,ldrex和strex是一对原子指令//lock是函数参数,lock->lock是整型数据"1:ldrex%0,[%1]\n"//判断tmp是否为0"teq%0,#0\n"//失败则执行wfe,等待事件唤醒WFE("ne")|//省略部分实现细节|->#defineWFE(cond)ALT_SMP("wfe"cond,"nop")//如果成功,写1到lock->lock,并将strexeq的结果保存在tmp中"strexeq%0,%2,[%1]\n"//判断tmp的值,如果strexeq成功,tmp为0,否则为1"teqeq%0,#0\n"//如果strexeq失败,则跳转到上一个标号1,在ldrexinstruction"bne1b":"=&r"(tmp):"r"(&lock->lock),"r"(1):"cc");//barrier指令smp_mb();}unlock操作函数如下:staticinlinevoidarch_spin_unlock(arch_spinlock_t*lock){//屏障指令smp_mb();//只需将lock->lock设置为1__asm____volatile__("str%1,[%0]\n"::"r"(&lock->lock),"r"(0):"cc");//dsb和sev指令//dsb是一个barrier,保证sev在str执行后执行//sev发送事件唤醒等待事件的cpu核dsb_sev();}可以看到,在arm中也有类似的原子指令和屏障指令。ldrex/strex指令类似于loongarch中的ll/sc指令。并且在lock和unlock函数之后,还有同步的barrier指令。同理,loongarch中的自旋锁也可以按照上面的代码实现:spin_lock//加载锁值到寄存器t0,假设参数寄存器a0为传入参数lock1:ll.dt0,a0,0//如果lock不为0则跳转到bnezt0,1b//设置t0为1并写入lock表示加锁,t0保存sc执行结果li.dt0,1sc.dt0,a0,0//如果sc失败则跳转bnezt0,1b//barrierdbar0spin_unlock:dbar0//将参数lock写入0st.dzero,a0,0总结本文介绍内存一致性模型,关于内存一致性模型的资料在loonarch架构中,原子指令和屏障指令的含义和用法结合自旋锁的情况进行了解释。同时发现,由于目前市场上掌握的信息有限,loonarch架构的内存一致性模型还存在一些不明确的地方,需要相关厂商后期进行完善。了解更多开源请访问:开源基础软件社区https://ost.51cto.com