# loongarch架构介绍#[二]内存模型和相关指令 原创 精华

深开鸿
发布于 2023-1-3 15:26
浏览
5收藏

作者:蒋卫峰 李涛

前言

前面一篇文章中介绍了loongarch架构中的基础部分,包括基础的整数运算指令、浮点运算指令、访存指令等,以及loongarch架构中的一些寄存器约定和汇编写法。

这篇文章则主要介绍loongarch架构中内存一致性模型相关信息,以及原子指令和栅障指令的含义和使用方法。本文会先介绍内存一致性模型相关背景知识,然后介绍目前loongarch资料中内存一致性模型相关的信息,再介绍loongarch中的原子指令和栅障指令,最后结合spinlock的实现说明原子指令和栅障指令的使用方法。

1. 内存一致性模型

内存一致性模型(memory consistency model),简称内存模型(memory model),是描述程序的与shared memory相关的访存指令执行顺序行为的模型。对于一段程序,内存模型能够指出哪些访存指令执行顺序是cpu允许存在的,哪些执行顺序是不可能发生的。

对于一般应用程序的开发人员来说,内存一致性模型是透明的,代码看上去是顺序执行的,在多线程中通过调用系统封装的互斥锁等就可实现同步。

但对于系统开发人员,内存一致性模型需要了解,这关系到如底层汇编中自旋锁实现等应用场景。

1.1 SC、TSO和RMO

这里列举出一些常见的内存一致性模型:

  • Sequential Consistency (SC):顺序一致性模型,不打乱访存指令顺序
  • Total Store Order (TSO):一种强序模型。x86的内存一致性模型类似于TSO
  • Relaxed Memory Order (RMO):弱序模型。如arm中支持一种RMO

其中Load表示加载内存操作,Store表示写入内存操作。

除了SC模型外,其他几种都打乱了访存指令的执行顺序,这主要是硬件上性能因素的考虑。

下面对这些模型中定义的访存指令顺序进行介绍。

1.1.1 SC中访存指令顺序

对于Load和Store操作,实际上总共有4种顺序:

  • Load->Load

  • Load->Store

  • Store->Store

  • Store->Load

SC模型保证以上所有的顺序,即访存指令实际的执行顺序与其在程序中的先后顺序相同。

下表为两个cpu核并行运行的例子:

Core C1 Core C2 说明
S1: x = NEW<br>L1: r1 = y S2: y = NEW<br>L2: r2 = x x, y初始为0

其中,S1和S2为Store操作,L1和L2为Load操作,x和y是cpu核C1和C2中共享的变量,r1和r2是cpu核C1和C2中的局部变量。

SC中所有可能的执行顺序如下:

  1. S1->L1->S2->L2,结果(r1, r2) = (0, NEW)

  2. S2->L2->S1->L1,结果(r1, r2) = (NEW, 0)

  3. S1,S2->L1,L2,结果(r1, r2) = (NEW, NEW)。其中S1和S2之间的顺序,以及L1和L2之间的顺序不确定

结果与一般程序员视角中的模型相同。

1.1.2 TSO中访存指令顺序

TSO中保证的访存指令顺序如下:

  • Load->Load

  • Load->Store

  • Store->Store

其中对于Store->Load可能会被打乱顺序。

下面用与上文SC中相同的例子进行说明:

Core C1 Core C2 说明
S1: x = NEW<br>L1: r1 = y S2: y = NEW<br>L2: r2 = x x, y初始为0

TSO中所有可能的执行顺序如下:

  1. S1->L1->S2->L2,结果(r1, r2) = (0, NEW)

  2. S2->L2->S1->L1,结果(r1, r2) = (NEW, 0)

  3. S1,S2->L1,L2,结果(r1, r2) = (NEW, NEW)

  4. L1,L2->S1,S2,结果(r1, r2) = (0, 0)

可以看到,TSO中可以出现Load比Store先执行的情况,从而导致结果r1和r2均为0。

1.1.3 RMO中访存指令顺序

在RMO中允许更进一步的乱序执行,如只保证以下顺序:

  • Load->Load,且地址相同

  • Load->Store,且地址相同

  • Store->Store,且地址相同

1.2 同步方法

由于如TSO、RMO等内存模型中访存指令的执行顺序不确定,因此硬件上提供了利用原子指令和栅障指令(称为fence或barrier等)来进行同步。程序中将需要同步的地方手动将其用原子指令和栅障指令进行标记,就能将目标代码同步。

下面用一个例子进行说明:

Core C1 Core C2
S1: data1 = NEW<br>S2: data2 = NEW<br>F1: FENCE<br>S3: flag = SET L1: r1 = flag<br>B1: if (r1 != SET) goto L1<br>F2: FENCE<br>L2: r2 = data1<br>L3: r3 = data2

其中FENCE指令保证前后的访存指令顺序一致。最后访存指令的顺序为:S1,S2->F1->S3->L1->F2->L2,L3,结果(r1, r2, r3) = (SET, NEW, NEW)。

如果中间的FENCE指令去掉,那么可能会出现r2和r3为0的情况。

2. loongarch内存模型相关信息

2.1 loongarch内存一致性模型

根据loongarch架构手册中相关信息,其采用弱一致性(Weakly Consistency)的模型。在该模型下,程序员必须通过同步指令将对写共享单元的访问保护起来,以保证多个处理器核对于写共享单元的访问是互斥的。

虽然目前的资料中无法得知loongarch内存一致性模型的具体信息,但可以推测其应该打乱了一些访存指令的顺序,需要借助栅障指令(即fence或barrier指令)进行同步。

2.2 loongarch内存访问类型

loongarch架构下有三种内存访问类型:

  • 一致可缓存(Coherent Cached)

  • 强序非缓存(Strongly-ordered UnCached)

  • 弱序非缓存(Weakly-ordered UnCached)

例如,在页表项中的MAT(Memory Access Type)域中可以设置对应内存区域的访问类型。MAT值与内存访问类型的关系如下:

  • 0对应强序非缓存

  • 1对应一致可缓存

  • 2对应弱序非缓存

  • 3为保留

通过设置内存访问类型,相当于规定了一片内存区域的内存一致性模型。loongarch手册资料中指出,只有强序非缓存没有副作用,即不打乱访存指令的顺序。

不过loongarch资料中内存一致性模型和内存访问类型并没有详细解释,很多地方存在疑点,只能推测其作用类似于arm上的Normal、Device、Strongly-ordered几种内存模型。

3. loongarch相关指令

3.1 栅障指令

loongarch中栅障指令如下:

  • dbar指令:dbar hint。dbar指令用于完成load/store访存操作之间的栅障功能,其中hint用于指示dbar指令的同步对象和同步程度。

    hint为0在loongarch中是必须实现的,如果没有专门的功能实现,其他所有的hint值都将视为hint=0执行。hint为0指示一个完全功能的同步栅障,只有等到之前所有load/store操作执行完后,dbar 0才会执行;且只有dbar 0执行后,其后所有的load/store操作才能执行。

  • ibar指令:ibar hint。ibar指令用于完成单个处理器核内部store操作和取指操作之间的同步,其中hint用于指示dbar指令的同步对象和同步程度。

    同样的,hint为0在loongarch中是必须实现的。ibar 0确保其后的取指一定能够观察到ibar 0指令之前所有store操作的执行效果。

3.2 原子指令

loongarch中原子指令如下:

  • amswap、amadd、ammax等指令:普通原子指令,能够原子地完成对某个内存单元的“读-修改-写”过程。

    amadd.w rd, rk, rj表示将rj寄存器值作为地址,从该地址读取值写入rd,然后将rd与rk进行加法操作,最后将结果写回该地址,rd中保存该地址中的旧值。整个过程是原子的。

  • amswap_db、amadd_db、ammax_db等指令:带数据栅障功能的原子指令。除了和上面普通原子指令的功能外,还有dbar指令的效果。

  • ll/sc指令:ll/sc一对指令能够完成“读-修改-写”原子操作。

    其作用机制为:ll指令能够完成读操作,并同时记录访问地址和在相关寄存器中置上一个标记(LLbit置为1)。sc指令能够完成写操作,并会先检查寄存器中的LLbit,只有当LLbit为1时才进行写入。而ll和sc之间的指令完成自定义的修改操作。

    在ll/sc指令期间,如果其他处理器核中对该ll/sc指令操作的相同地址进行了写入,那么LLbit就会被清0,这样sc指令也会返回失败。这样的硬件检查机制保证了原子性。

    另外,ll/sc指令也有栅障功能。

    具体使用方法参见下文中spinlock实现。

loongarch原子指令的特点与一些RISC架构的原子指令相似,也类似的用ll/sc这样的指令来代替了CAS(compare-and-swap)类型原子指令。ll/sc指令相比于CAS类指令,CAS指令实现锁时可能会有ABA问题,而使用ll/sc指令可以避免这个问题。

4. 应用场景:spinlock

下面通过spinlock的实现,说明原子指令和栅障指令的应用。

作为对比,首先介绍linux3.2中arm下spinlock的实现。

lock操作函数如下:

static inline void arch_spin_lock(arch_spinlock_t *lock)
{
    unsigned long tmp;

    __asm__ __volatile__(
// 将lock->lock加载到tmp,ldrex和strex为一对原子指令
// lock是函数参数,lock->lock为整型数据
"1:    ldrex    %0, [%1]\n"
// 判断tmp是否为0
"    teq    %0, #0\n"

// 失败则执行wfe,等待事件将其唤醒
    WFE("ne")
    |   // 省略了一些实现细节
    |-> #define WFE(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,ldrex指令处
"    bne    1b"
    : "=&r" (tmp)
    : "r" (&lock->lock), "r" (1)
    : "cc");

// barrier指令
    smp_mb();
}

unlock操作函数如下:

static inline void arch_spin_unlock(arch_spinlock_t *lock)
{
    // barrier指令
    smp_mb();

    // 简单的将lock->lock置1
    __asm__ __volatile__(
"    str    %1, [%0]\n"
    :
    : "r" (&lock->lock), "r" (0)
    : "cc");

    // dsb和sev指令
    // dsb为barrier,保证str执行完毕后才执行sev
    // sev发送事件唤醒正在等待事件的cpu核
    dsb_sev();
}

可以看到,arm中也有类似的原子指令和栅障指令。其中ldrex/strex指令类似于loongarch中的ll/sc指令。并且lock和unlock函数之后均有栅障指令进行同步。

类似地,loongarch中spinlock也可以仿照上述代码实现:

spin_lock:
    // 将lock值加载到寄存器t0,假设参数寄存器a0中为传入的参数lock
1:  ll.d    t0, a0, 0
    // 如果lock不为0则跳转
    bnez    t0, 1b

    // 将t0置1并写入lock表示上锁,t0保存sc执行结果
    li.d    t0, 1
    sc.d    t0, a0, 0

    // 如果sc失败则跳转
    bnez    t0, 1b

    // barrier
    dbar    0

spin_unlock:
    dbar    0
    // 将参数lock写入0
    st.d    zero, a0, 0

总结

本文介绍了内存一致性模型、loongarch架构中内存一致性模型相关信息、原子指令和栅障指令的含义和使用方法,并结合自旋锁的案例进行了说明。同时发现,因为目前市场上得到的资料有限,loongarch架构内存一致性模型中还有一些不清楚的地方,需要后面相关厂家完善。

更多原创内容请关注:深开鸿技术团队

入门到精通、技巧到案例,系统化分享OpenHarmony开发技术,欢迎投稿和订阅,让我们一起携手前行共建生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
8
收藏 5
回复
举报
8条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

看来开发任何系统都需要大量的知识储备。

1
回复
2023-1-3 16:43:41
0aaron
0aaron

学习大佬对内存的解读

1
回复
2023-1-4 17:21:08
诺舒华吃西瓜
诺舒华吃西瓜

这些是根据厂家提供的文档总结的吗

1
回复
2023-1-5 15:55:04
wzhishun
wzhishun

跟着大佬学习下龙芯架构

1
回复
2023-1-5 18:38:01
皮皮虾233
皮皮虾233

平时做应用确实接触不到这些,学习了

1
回复
2023-1-6 10:54:42
冰淇淋爱我
冰淇淋爱我

希望后续资料能放出更多具体的信息供开发者使用

1
回复
2023-1-6 14:40:03
带带小老弟
带带小老弟

对于系统开发感觉对逻辑性会有较高的要求

1
回复
2023-1-6 18:49:10
HUAWEI_Engineer
HUAWEI_Engineer

必须学习


回复
2023-1-10 21:08:13
回复
    相关推荐