【问题标题】:understanding GCC inline asm function理解 GCC 内联 asm 函数
【发布时间】:2020-02-17 08:00:33
【问题描述】:

我将在下面的问题中写下我的假设(基于我的研究),我假设我的假设在问题本身之外存在错误:

我正在研究为 ARM 编写的一些代码:

这个函数(取自 FreeRTOS 端口代码):

portFORCE_INLINE static uint32_t ulPortRaiseBASEPRI(void)
{
    uint32_t ulOriginalBASEPRI, ulNewBASEPRI;

    __asm volatile("    mrs %0, basepri                                         \n"
                   "    mov %1, %2                                              \n"
                   "    msr basepri, %1                                         \n"
                   "    isb                                                     \n"
                   "    dsb                                                     \n"
                   : "=r"(ulOriginalBASEPRI), "=r"(ulNewBASEPRI)
                   : "i"(configMAX_SYSCALL_INTERRUPT_PRIORITY));

    /* This return will not be reached but is necessary to prevent compiler
    warnings. */
    return ulOriginalBASEPRI;
}

我在 gcc 中理解“=r”是输出操作数。所以我们将值从 asm 保存到 C 变量中

现在我理解的代码相当于:

ulOriginalBASEPRI = basepri
ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY
basepri = ulNewBASEPRI

我知道我们正在返回 BASEPRI 的原始值,所以这是第一行。但是,我不明白为什么我们分配变量 ulNewBASEPRI 然后我们在 MSR 指令中使用它..

所以我查看了ARMV7 instruction set,我看到了这个:

我假设拇指指令中没有(MSR 立即数),并且“编码 A1”意味着它仅在 Arm 指令模式下。 所以我们必须使用 =r 输出操作数让汇编器为我们的变量自动选择一个寄存器我正确吗?


编辑:忽略此部分,因为我记错了冒号

: "i"(configMAX_SYSCALL_INTERRUPT_PRIORITY));

根据我对assembly template的理解:

   asm ( assembler template 
       : output operands                  /* optional */
       : input operands                   /* optional */
       : list of clobbered registers      /* optional */
       );

“i”在程序集中不只是表示(立即)或常量吗?
这是否意味着第三个冒号不仅用于clobber列表?
如果是这样,在输入操作数中找到约束“i”不是更合适吗?
编辑:忽略本节,因为我记错了冒号


我了解 isb,dsb 是内存屏障的东西,但我真的不明白它们的描述。他们到底在做什么? 例如,如果我们删除 dsb 或 isb 指令会发生什么?

【问题讨论】:

  • 我想你可能记错了这里的冒号数。 “i”一个输入。 ulOriginalBASEPRIulNewBASEPRI 都出现在第一个冒号之后,因此是输出。 configMAX_SYSCALL_INTERRUPT_PRIORITY 出现在第二个冒号之后,输入也是如此。这个例子没有clobbers。
  • @DavidWohlferd,啊是的,我数错了!

标签: c gcc assembly arm


【解决方案1】:

所以我们必须使用 =r 输出操作数让汇编器为我们的变量自动选择一个寄存器,我正确吗?

是的,但它是 编译器 进行寄存器分配。它只是在 asm 模板字符串中填充 %[operand] 作为文本替换并将 that 提供给汇编器。

或者,您可以硬编码 asm 模板字符串中的特定寄存器,并使用 register-asm 局部变量来确保 "=r" 约束选择它。或者使用"=m" 内存输出操作数和str 一个结果,并在您使用的任何寄存器上声明一个clobber。但与仅仅告诉编译器你的 asm 块如何产生输出相比,这些替代方案显然很糟糕。


我不明白为什么评论说 return 语句没有运行:

   /* This return will not be reached but is necessary to prevent compiler
   warnings. */
   return ulOriginalBASEPRI;

basepri (ARM docs) 提高到更高的数字可能会允许中断处理程序在后续指令之前立即运行,但如果该异常再次返回,执行最终将到达 asm 语句之外的 C。这就是将旧的basepri 保存到寄存器并为其提供输出操作数的全部意义。

(我一直假设“提高”意味着更高的数量 = 允许更多的中断。但罗斯认为它永远不会允许更多的中断;他们正在“提高标准”= 更低的数量 = 允许的中断更少。)

如果执行真的永远不会出现在你的 asm 的末尾,你应该告诉编译器它。有asm goto,但这需要一个可能的分支目标列表。 GCC 手册说:

GCC 假定 asm 执行到下一条语句(如果不是这种情况,请考虑在 asm 语句之后使用 __builtin_unreachable() 内在函数)。

如果不这样做可能会导致编译器计划在 asm 之后执行某些操作,然后即使在源代码中它位于 asm 之前,它也永远不会发生。


最好使用"memory" clobber 来确保编译器的内存内容与 C 抽象机同步。 (至少对于中断处理程序可能访问的本地变量以外的变量)。对于像dsb 这样的asm 屏障指令,这通常是可取的,但似乎在这里我们可能并不关心成为SMP 内存屏障,只是在更改basepri 后保持一致执行?我不明白为什么这是必要的,但如果你这样做了,那么值得考虑一种或另一种方式,是否围绕asm 语句对内存访问进行编译时重新排序是否存在问题。

您将在 asm 语句中使用第三个以冒号分隔的部分(在输入之后): "memory"

否则,编译器可能会决定在此 asm 之后而不是之前进行赋值 ,只在寄存器中留下一个值。

// actual C source
  global_var = 1;
  uint32_t oldpri = ulPortRaiseBASEPRI();
  global_var = 2;

可以优化(通过消除死存储)成这样工作的 asm

// possible asm
  global_var = 2;
  uint32_t oldpri = ulPortRaiseBASEPRI();
  // or global_var = 2; here *instead* of before the asm

【讨论】:

  • 可能是因为 (always_inline) 没有进行函数调用而只是复制代码?并且该寄存器已经存在于代码中,因此编译器将知道如何使用它(因此返回是重复的)/编译器将优化并删除它?
  • @Hasanalattar:我想知道他们是不是这么想的。这不是我要描述的方式,这听起来像是一种非常混乱的思维方式。在 C 中,return 肯定达到了。内联 return 后不需要任何 asm 指令;编译器可以只进行寄存器分配以将ulOriginalBASEPRI 放入它想要的任何寄存器中。说它没有到达意味着完全不同于说它是免费return 语句将一个值转换为函数的返回值,调用者可以将其分配给某些东西。
  • 更改异常优先级可能会取消屏蔽永远不会返回的异常,从而导致函数永远不会返回,但该函数似乎会将异常优先级提高到 FreeRTOS 使用的最高级别。这意味着它永远不会取消屏蔽异常,更不用说总是取消屏蔽代码可能假定永远不会返回的异常。
  • @RossRidge:谢谢,我一直假设他们的意思是提高数字(允许更多中断),但这实际上对函数名称更有意义。已更新。
  • 在带有 BASEPRI 寄存器的 ARM CPU 上令人困惑的是,数值上较低的优先级值实际上是较高的优先级。
【解决方案2】:

关于msr 上的 ARM/Thumb 指令集差异:您应该能够从文档中自己回答这个问题。 ;-) 仅仅 2 页之后。 编辑:链接手册的第 A8.1.3 章清楚地说明了如何在说明中记录编码。

dsb(数据同步屏障)确保在执行下一条指令之前完成所有内存访问。 这真的写得很短,完整的细节你需要阅读文档。如果您对此操作还有其他具体问题,请发布另一个问题。

isb(指令同步屏障)清除指令流水线。此流水线缓冲已从内存中取出但尚未执行的指令。因此,下一条指令将通过可能更改的内存访问来获取,这正是程序员所期望的。 上面的注释也适用于此。

【讨论】:

  • 您是否从 Atari ST ROM 中获得了“蜜蜂”图标?对我来说看起来很熟悉,在我获得 x86 Linux PC 之前,我一直使用 Atari。 :)
  • @PeterCordes 正确,在我不得不切换到 Linux 之前,ST 和我一起生活了大约 15 年。 ;-) 目前我正在考虑将 W10 蓝色圆圈制作为 GIF 作为头像。会很有趣。
  • 嗨,我已经看到了 MSR(寄存器)之前的询问,但我的问题是关于 MSR 立即数,它是否像仅在 ARM 模式下可用。我可以看到(编码 T1 / A1)具有 16 位和 32 位的东西,所以这是我的假设。
  • 我不知道为什么,但蜜蜂让我想起了(蝉 3301)
  • 无论我给出什么评论,都是值得怀疑的。 ;-) “是”:蝉 3301 不会透露她的身份; “不”:你会相信吗?嗯,真的只是GEM的忙碌光标,因为我通常需要很长时间才能回答,而且我喜欢我的ST。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-08
  • 1970-01-01
  • 1970-01-01
  • 2015-02-19
相关资源
最近更新 更多