TriCore架构多核多线程锁探究(TC264双核互斥锁)

序言

现在TriCore架构已经是一个十分成熟的架构,并广泛用于新能源行业各类控制器。从TC1.3.1开始有了很多重大的调整。新增了几个指令,有了全流水线结构的浮点单元等。大家想详细了解也可以去翻阅数据手册中Summary of functional changes from TC1.3.1一节。其中有一个新的克隆指令——CMPSWAP.W。这一更改也引出了新的方法来实现TriCore架构的多核多线程锁。

一、问题起因

在开发TC264的双核程序的时候突然思考起了一个问题。在学习线程锁的过程中我们讨论的总是单核,可是单核毕竟在一个时间片下只能处理一个线程的指令,而在多核的情况下每一个核心都可以独立执行一个线程,所以多核是真正实现多线程并行的。按照那时这样简单的考虑来说那作为全局变量的线程锁对于两个核心上的线程来说不也成了公共资源?而线程锁都成了公共资源,那抢锁的行为不也是会产生冲突的?

二、后续的理论补全

1、数据总线

数据总线的一些基本概念这里就不提出来再讲了。有一个细节是在总线上同一时刻只能有一个主设备控制总线传输操作。而对于多核来说也是如此,他们需要互相争抢总线的使用权,而这一现象又能帮助我们实现一些原子操作。

2、总线操作

使用总线对数据进行操作的时候并不是全部都能一次完成的,有时候可能需要多个操作才能实现我们编程中看似简单的操作,而在找个时候就不一定能满足我们的原子性了。

三、官方库的解决办法

在官方库的/Cpu/Std文件夹我们能找的IFxCpu.c的文件,其中给出了抢锁的代码(使用方法在.h文件的注释中有,这里就不再做解释了)

boolean IfxCpu_acquireMutex(IfxCpu_mutexLock *lock)
{
    boolean         retVal;
    volatile uint32 spinLockVal;

    retVal      = FALSE;

    spinLockVal = 1UL;
    spinLockVal =
        (uint32)__cmpAndSwap(((unsigned int *)lock), spinLockVal, 0);

    /* Check if the SpinLock WAS set before the attempt to acquire spinlock */
    if (spinLockVal == 0)
    {
        retVal = TRUE;
    }

    return retVal;
}

这段代码的逻辑及其简单,就是去查找我们lock变量的值是否为0,如果为0便把它赋值为1,并且返回成功抢到锁的信息。而有一个操作是值得关注的。__cmpAndSwap() 这一操作为什么能保证原子性并且能做到对变量进行加锁的呢?

进入定义我们能看见

/** brief This function is a implementation of a binary semaphore using compare and swap instruction
 * param address address of resource.
 * param value This variable is updated with status of address
 * param condition if the value of address matches with the value of condition, then swap of value & address occurs.
 *
 */
IFX_INLINE unsigned int Ifx__cmpAndSwap (unsigned int volatile *address,
           unsigned int value, unsigned int condition)
{
  unsigned long long reg64
    = value | (unsigned long long) condition << 32;

  __asm__ __volatile__ ("cmpswap.w [%[addr]]0, %A[reg]"
                        : [reg] "+d" (reg64)
                        : [addr] "a" (address)
                        : "memory");
    return reg64;
}

这里使用了汇编语言对芯片进行操作,而cmpswap.w操作正是我们在数据手册中找到的新指令。而正如注释说这一个指令能比较两个地址中的值是否相同,并完成交换。

可是这并没有解决我们对原子性的疑问。并且我们又有了新的疑问。为什么不用赋值,而使用交换呢?

而代码到这里已经结束了,我们需要进入数据手册深入查找原因。

四、深入数据手册解决疑问

在数据手册的Atomicity of Data Accesses一节中我们找到了答案。非外围空间的对齐规则一表中有一栏是这样的

Access type Access size Alignment of address in memory Min/Max number of SRI bus transactions
Load, Store Data Register Word 2 bytes (2H) 1/2*
SWAP.W, LDMST, CMPSWAP.W, SWAPMSK.W Word 4 bytes (4H) 1/1

而加了*号的在后面也有解释

In the case where a single access leads to multiple bus transactions (marked as “*” in the above tables) then atomicity needs to be considered. In these accesses it is possible for another bus master to read or write the target memory location between the bus transactions required to complete the access.

这里就已经提示我们在出现这种情况的时候数据的原子性是无法保证的,另外一个核心可能会在这两个总线操作之间获得总线的控制权,需要我们去考虑。
这就解释了为什么不是直接对内存经行赋值操作,而是进行交换。cmpswap.w在保证原子性的同时既实现比较,又实现了交换,保证抢锁的过程中不会导致多核多线程同时抢到锁。

五、总结

在解决问题的过程中我也询问了操作系统等方向的老师。在我们现在已经习以为常的多核多线程的调度并不是只是简单的软件层面在实现。这是一个硬件与操作系统共同完成的精妙工程。感谢前人为我们铺下的基础,让我们简单到只用使用一个函数,定义一个变量,就能完成茫茫历史上的一个壮举。

若理解有误,欢迎有大佬对其指正