这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 行业应用 » 汽车电子 » 【S32K3】独占访问使用

共1条 1/1 1 跳转至

【S32K3】独占访问使用

高工
2025-10-30 19:26:09     打赏

以下的代码段在两个任务中同时对一个全局变量++、--  5000000 次数因为对变量的计算结果是多少呢?

volatile int32_t counter = 0;

void start_task(void *pvParameters)
{
    (void)(pvParameters);
    int add = 5000000;
    while(1)
    {
    	if(--add)
    	{
    		counter++;
    	}
    	else
    	{
    		break;
    	}
    }
    while(1);
}


void start_task1(void *pvParameters)
{
    (void)(pvParameters);
    int sub = 5000000;
    while(1)
    {
    	if(--sub)
    	{
    		counter--;
    	}
    	else
    	{
    		break;
    	}
    }
    while(1);
}

在 ARM 架构下,如果++和--操作不是原子操作(这是多数情况下的实际情况),两个任务同时对全局变量counter进行 5000000 次自增和自减,最终结果不会是 0,而是一个不确定的非零值(可能为正、负或零,但大概率非零)。

关键原因分析:

counter++和counter--在 ARM 架构下通常会被编译为多步指令(非原子操作),大致流程为:

从内存读取counter的值到寄存器(load)。

寄存器中的值加 1 或减 1(modify)。

将寄存器中的结果写回内存(store)。

当两个任务并发执行时,可能出现指令交错,导致数据覆盖。例如:

任务 1 读取counter=0,准备执行+1。

任务 2 同时读取counter=0,准备执行-1。

任务 1 写入结果1,任务 2 随后写入结果-1。

最终counter=-1,但实际执行了 1 次+1和 1 次-1,理论上应为 0,却因交错导致结果错误。

随着操作次数增加(5000000 次),这种交错会累积,导致最终结果偏离 0,且每次运行结果可能不同(不确定性)。

本地代码的运行结果如下:

image.png

为了解决上述问题我们可以使用FTOS 的 互斥锁来保护上述临界资源,我么也可以使用ARM LDREX/STREX 指令来实现原子操作来避免上述问题。ARM 中 ldrex 和 strex 通过独占访问监控器实现原子操作。ldrex 读取内存时标记该位置为当前处理器独占;strex 仅在该位置仍保持独占状态时写入成功,否则失败。

LDREX 指令描述如下:

image.png

STREX 的指令描述如下

image.png

有了上述认识我们通过LDREX 和 STREX 就可以实现原子操作更新内存,添加如下代码来原子访问内存

__attribute__((always_inline))     static inline rt_atomic_t __LDREXW(volatile rt_atomic_t *addr)
{
    rt_atomic_t result;

    __asm volatile ("ldrex %0, %1" : "=r" (result) : "Q" (*addr) );
    return result;
}

__attribute__((always_inline))     static inline rt_atomic_t __STREXW(volatile rt_atomic_t value, volatile rt_atomic_t *addr)
{
    rt_atomic_t result;

    __asm volatile ("strex %0, %2, %1" : "=&r" (result), "=Q" (*addr) : "r" (value) );
    return result;
}

rt_atomic_t rt_hw_atomic_add(volatile rt_atomic_t *ptr, rt_atomic_t val)
{
    rt_atomic_t oldval;
    do
    {
        oldval = __LDREXW(ptr);
    } while ((__STREXW(oldval + val, ptr)) != 0U);
    return oldval;
}

rt_atomic_t rt_hw_atomic_sub(volatile rt_atomic_t *ptr, rt_atomic_t val)
{
    rt_atomic_t oldval;
    do
    {
        oldval = __LDREXW(ptr);
    } while ((__STREXW(oldval - val, ptr)) != 0U);
    return oldval;
}


修改上述测试代码继续验证

void start_task(void *pvParameters)
{
    (void)(pvParameters);
    int add = 5000000;
    while(1)
    {
        if(--add)
        {
            rt_hw_atomic_add(&counter,1);
        }
        else
        {
            break;
        }
    }
    while(1);
}


void start_task1(void *pvParameters)
{
    (void)(pvParameters);
    int sub = 5000000;
    while(1)
    {
        if(--sub)
        {
        	rt_hw_atomic_sub(&counter,1);
        }
        else
        {
            break;
        }
    }
    while(1);
}


通过LDREX 和 STREX 保护变量的访问后上述代码的执行结果为0,和预期的保持一致。

image.png


共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]