浅谈ldrex

2 篇文章 0 订阅

ldrex简介

Armv6开始支持多核,通过ldrexstrex指令来保证数据操作的原子性,比如自旋锁的上锁操作、原子变量操作等。在Armv6之前,都是单核,为保证数据的原子性,需要进行关中断操作。对于多核平台,关中断操作只能关闭本核中断,要想对数据进行原子操作,必须借助ldrex指令与strex。对于ldrex指令与strex指令原理这里不在介绍,网上资料很多,下面简单介绍SylixOS的队列式自旋锁实现。

static VOID  armSpinLock (SPINLOCKTYPE *psld, VOIDFUNCPTR  pfuncPoll, PVOID  pvArg)
{
#if __SYLIXOS_ARM_ARCH__ >= 6
    UINT32          uiTemp;
    UINT32          uiNewVal;
    SPINLOCKTYPE    sldVal;

    ARM_PREFETCH(&psld->SLD_uiLock);
    __asm__ __volatile__(
        "1: ldrex   %[oldvalue], [%[slock]]                 \n"
        "   add     %[newvalue], %[oldvalue], %[tshift]     \n"
        "   strex   %[temp],     %[newvalue], [%[slock]]    \n"
        "   teq     %[temp],     #0                         \n"
        "   bne     1b"
        : [oldvalue] "=&r" (sldVal), [newvalue] "=&r" (uiNewVal), [temp] "=&r" (uiTemp)
        : [slock] "r" (&psld->SLD_uiLock), [tshift] "I" (1 << LW_SPINLOCK_TICKET_SHIFT)
        : "cc");

    while (sldVal.SLD_usTicket != sldVal.SLD_usSvcNow) {
        if (pfuncPoll) {
            pfuncPoll(pvArg);
        } else {
            __asm__ __volatile__(ARM_WFE(""));
        }
        sldVal.SLD_usSvcNow = LW_ACCESS_ONCE(UINT16, psld->SLD_usSvcNow);
    }
#endif                                                                  /*  __SYLIXOS_ARM_ARCH__ >= 6   */
}

为避免线程在获取自旋锁时出现饥饿状态,在实现自旋锁时按照先到先得的机制。主要实现思路就是每个线程在获取自旋锁时,会对SLD_usTicket加1,释放时对SLD_usSvcNow加1,当两个值相等时,代表获取到锁。假设有三个线程A、B、C,访问锁的顺序依次为A -> B -> C,起初SLD_usTicketSLD_usSvcNow都为0,线程A首先获取到锁的值,存储在sldVal中,此时sldValSLD_usTicketSLD_usSvcNow都为0。注意,这里是先将锁的值保存到sldVal中,然后对锁的SLD_usTicket加1。这样,当线程B获取锁时,sldVal中的SLD_usTicket为1,然后也对锁的SLD_usTicket加1,当线程C获取锁时,sldVal中的SLD_usTicket为2。当线程A释放锁时,SLD_usSvcNow的值才会加1,此时线程B才可以获取锁,这样就保证了按队列方式获取锁。因为在自旋的过程中,不会发生任务切换,所以至多有CPU核数数量的线程同时获取锁,不会导致SLD_usTicket加1溢出时会和先到者冲突。

对于自旋锁的释放则比较简单:

static VOID  armSpinUnlock (SPINLOCKTYPE *psld)
{
#if __SYLIXOS_ARM_ARCH__ >= 6
    psld->SLD_usSvcNow++;
    armDsb();
    __asm__ __volatile__(ARM_SEV);
#endif                                                                  /*  __SYLIXOS_ARM_ARCH__ >= 6   */
}

ldrex使用问题

ldrexstrex指令的结合确实强大,对于一些古老的多核处理器,需要锁总线来保证数据原子操作(如x86),这样会导致访问效率降低。而ldrexstrex没有进行锁总线操作,并且在两条指令之间可以对变量进行复杂的操作,不仅仅是加1减1操作。但是也需要注意ldrexstrex指令要想正常工作,也是有前提的,笔者在开发ti 66AH2H(4核cortex-A15)平台BSP时,就遇到了这个问题,先看下ARM手册对于ldrex指令的相关说明:
ldrex介绍
ldrex介绍
上述内容来源于ARM官方文档(DDI0438I_cortex_a15_r4p0_trm.pdf),主要表达意思就是对于ldrexstrex指令的支持需要global monitor,而global monitor的实现由两种,一种是内部实现,需要开启cache。另一种是外部实现(和芯片有关系,有的芯片没有实现),通过总线监听的方式。当内部和外部都实现时,优先使用内部global monitor。笔者在开发ti 66AH2H(4核cortex-A15)平台BSP时,就在没有开启cache时使用了ldrex指令,系统工作异常,这也说明了66AH2H没有实现外部global monitor。同样是ti cortex-A15(AM5728),就实现了外部global monitorzynq 7000双核cortex-A9(Armv7-A)也可以在不开启cache时使用ldrex指令,外部global monitor是否实现与芯片有关系,为安全起见,尽量不在开启cache前使用ldrexstrex指令。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值