了解GCC内联asm功能

时间:2019-10-21 08:47:16

标签: c gcc assembly arm

我将在下面的问题中写下我的假设(基于我的研究),我认为我自己的疑问中存在自我问题之外的错误:

我正在研究一些为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,发现了: enter image description here

我假设拇指指令中没有(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”在汇编中不只是意味着(立即)还是常量?
这是不是意味着第三个冒号不仅用于破坏列表?
如果是这样,在输入操作数中找到约束“ i”是否更合适?
编辑:忽略此部分,因为我误记了冒号


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

2 个答案:

答案 0 :(得分:2)

关于msr上的ARM / Thumb指令集差异:您应该可以自己从文档中回答。 ;-)仅两页后。 编辑:链接手册的第A8.1.3章明确说明了如何在说明中记录编码。

dsb(数据同步屏障)确保在执行下一条指令之前完成所有内存访问。 这确实是简短的文字,有关阅读文档所需的全部详细信息。如果您对此操作还有其他特定问题,请发布另一个问题。

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

答案 1 :(得分:2)

  

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

是的,但是是编译器进行注册分配。它只是将asm模板字符串中的%[operand]填充为文本替换,然后将那个馈入汇编程序。

或者,您可以可以对asm模板字符串中的特定寄存器进行硬编码,然后使用register-asm局部变量来确保"=r"约束选择了它。或者使用"=m"内存输出操作数并将结果str放入其中,并在您使用的所有寄存器上声明一个破坏对象。但是,与仅告诉编译器您的asm块如何产生输出相比,这些选择显然是可怕的。


我不明白为什么注释中的return语句不运行:

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

basepriARM docs)增大到一个更大的数字可能会允许中断处理程序在以后的指令之前立即运行,但是如果该异常返回,则执行最终将到达C asm语句。我认为这就是将旧basepri保存到寄存器并为其提供输出操作数的全部要点。

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

如果的执行确实从未结束过,则应将其告知编译器。有asm goto,但是需要可能的分支目标的列表。 GCC手册说:

  

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

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


最好使用"memory"破坏符来确保编译器具有与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