【问题标题】:Is there a way to tell GCC not to reorder any instructions, not just load/stores?有没有办法告诉 GCC 不要重新排序任何指令,而不仅仅是加载/存储?
【发布时间】:2016-12-08 16:36:20
【问题描述】:

我正在研究 RTOS 的 irq_lock() / irq_unlock() 实现,但发现了一个问题。我们希望最大限度地减少 CPU 花费在中断锁定上的时间。现在,我们用于 x86 的 irq_lock() 内联函数使用“memory”clobber:

static ALWAYS_INLINE unsigned int _do_irq_lock(void)
{
    unsigned int key;

    __asm__ volatile (
        "pushfl;\n\t"
        "cli;\n\t"
        "popl %0;\n\t"
        : "=g" (key)
        :
        : "memory"
        );

    return key;
}

问题在于,如果它们只是接触寄存器而不是内存,编译器仍会将可能昂贵的操作重新排序到临界区。一个具体的例子在我们内核的 sleep 函数中:

void k_sleep(int32_t duration)
{
    __ASSERT(!_is_in_isr(), "");
    __ASSERT(duration != K_FOREVER, "");

    K_DEBUG("thread %p for %d ns\n", _current, duration);

    /* wait of 0 ns is treated as a 'yield' */
    if (duration == 0) {
        k_yield();
        return;
    }

    int32_t ticks = _TICK_ALIGN + _ms_to_ticks(duration);
    int key = irq_lock();

    _remove_thread_from_ready_q(_current);
    _add_thread_timeout(_current, NULL, ticks);

    _Swap(key);
}

'ticks' 计算,它进行昂贵的数学运算,在我们锁定中断的地方重新排序,所以我们调用 __divdi3 并锁定中断,这不是我们想要的:

Dump of assembler code for function k_sleep:
   0x0010197a <+0>: push   %ebp
   0x0010197b <+1>: mov    %esp,%ebp
   0x0010197d <+3>: push   %edi
   0x0010197e <+4>: push   %esi
   0x0010197f <+5>: push   %ebx
   0x00101980 <+6>: mov    0x8(%ebp),%edi
   0x00101983 <+9>: test   %edi,%edi
   0x00101985 <+11>:    jne    0x101993 <k_sleep+25>
   0x00101987 <+13>:    lea    -0xc(%ebp),%esp
   0x0010198a <+16>:    pop    %ebx
   0x0010198b <+17>:    pop    %esi
   0x0010198c <+18>:    pop    %edi
   0x0010198d <+19>:    pop    %ebp
   0x0010198e <+20>:    jmp    0x101944 <k_yield>
   0x00101993 <+25>:    pushf  
   0x00101994 <+26>:    cli    
   0x00101995 <+27>:    pop    %esi
   0x00101996 <+28>:    pushl  0x104608
   0x0010199c <+34>:    call   0x101726 <_remove_thread_from_ready_q>
   0x001019a1 <+39>:    mov    $0x64,%eax
   0x001019a6 <+44>:    imul   %edi
   0x001019a8 <+46>:    mov    0x104608,%ebx
   0x001019ae <+52>:    add    $0x3e7,%eax
   0x001019b3 <+57>:    adc    $0x0,%edx
   0x001019b6 <+60>:    mov    %ebx,0x20(%ebx)
   0x001019b9 <+63>:    movl   $0x0,(%esp)
   0x001019c0 <+70>:    push   $0x3e8
   0x001019c5 <+75>:    push   %edx
   0x001019c6 <+76>:    push   %eax
   0x001019c7 <+77>:    call   0x1000a0 <__divdi3>
   0x001019cc <+82>:    add    $0x10,%esp
   0x001019cf <+85>:    inc    %eax
   0x001019d0 <+86>:    mov    %eax,0x28(%ebx)
   0x001019d3 <+89>:    movl   $0x0,0x24(%ebx)
   0x001019da <+96>:    lea    0x18(%ebx),%edx
   0x001019dd <+99>:    mov    $0x10460c,%eax
   0x001019e2 <+104>:   add    $0x28,%ebx
   0x001019e5 <+107>:   mov    $0x10162b,%ecx
   0x001019ea <+112>:   push   %ebx
   0x001019eb <+113>:   call   0x101667 <sys_dlist_insert_at>
   0x001019f0 <+118>:   mov    %esi,0x8(%ebp)
   0x001019f3 <+121>:   pop    %eax
   0x001019f4 <+122>:   lea    -0xc(%ebp),%esp
   0x001019f7 <+125>:   pop    %ebx
   0x001019f8 <+126>:   pop    %esi
   0x001019f9 <+127>:   pop    %edi
   0x001019fa <+128>:   pop    %ebp
   0x001019fb <+129>:   jmp    0x100f77 <_Swap>
End of assembler dump.

我们发现我们可以通过声明 'ticks' volatile 来获得我们想要的排序:

Dump of assembler code for function k_sleep:
   0x0010197a <+0>: push   %ebp
   0x0010197b <+1>: mov    %esp,%ebp
   0x0010197d <+3>: push   %ebx
   0x0010197e <+4>: push   %edx
   0x0010197f <+5>: mov    0x8(%ebp),%edx
   0x00101982 <+8>: test   %edx,%edx
   0x00101984 <+10>:    jne    0x10198d <k_sleep+19>
   0x00101986 <+12>:    call   0x101944 <k_yield>
   0x0010198b <+17>:    jmp    0x1019f5 <k_sleep+123>
   0x0010198d <+19>:    mov    $0x64,%eax
   0x00101992 <+24>:    push   $0x0
   0x00101994 <+26>:    imul   %edx
   0x00101996 <+28>:    add    $0x3e7,%eax
   0x0010199b <+33>:    push   $0x3e8
   0x001019a0 <+38>:    adc    $0x0,%edx
   0x001019a3 <+41>:    push   %edx
   0x001019a4 <+42>:    push   %eax
   0x001019a5 <+43>:    call   0x1000a0 <__divdi3>
   0x001019aa <+48>:    add    $0x10,%esp
   0x001019ad <+51>:    inc    %eax
   0x001019ae <+52>:    mov    %eax,-0x8(%ebp)
   0x001019b1 <+55>:    pushf  
   0x001019b2 <+56>:    cli    
   0x001019b3 <+57>:    pop    %ebx
   0x001019b4 <+58>:    pushl  0x1045e8
   0x001019ba <+64>:    call   0x101726 <_remove_thread_from_ready_q>
   0x001019bf <+69>:    mov    0x1045e8,%eax
   0x001019c4 <+74>:    mov    -0x8(%ebp),%edx
   0x001019c7 <+77>:    movl   $0x0,0x24(%eax)
   0x001019ce <+84>:    mov    %edx,0x28(%eax)
   0x001019d1 <+87>:    mov    %eax,0x20(%eax)
   0x001019d4 <+90>:    lea    0x18(%eax),%edx
   0x001019d7 <+93>:    add    $0x28,%eax
   0x001019da <+96>:    mov    %eax,(%esp)
   0x001019dd <+99>:    mov    $0x10162b,%ecx
   0x001019e2 <+104>:   mov    $0x1045ec,%eax
   0x001019e7 <+109>:   call   0x101667 <sys_dlist_insert_at>
   0x001019ec <+114>:   mov    %ebx,(%esp)
   0x001019ef <+117>:   call   0x100f77 <_Swap>
   0x001019f4 <+122>:   pop    %eax
   0x001019f5 <+123>:   mov    -0x4(%ebp),%ebx
   0x001019f8 <+126>:   leave  
   0x001019f9 <+127>:   ret    
End of assembler dump.

但是,这只是在一个地方解决了它。我们确实需要一种方法来修改 irq_lock() 实现,使其在任何地方都能做正确的事情,而现在“内存”破坏器还不够。

【问题讨论】:

  • 参见stdatomic.h 并使用编译器屏障。 irq-enable/disable 通常是 gcc 中的宏也是有原因的。
  • 可能禁用优化:-O0.
  • @FiddlingBits:不好的建议。应该编写代码,以便优化不会改变它。别说完全禁用优化会增加一堆惩罚。

标签: c interrupt


【解决方案1】:

由于您的架构无论如何都是 x86,请尝试使用 __sync_synchronize 而不是内存破坏器。这是 x86 支持的完整硬件内存屏障。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-31
    • 2017-08-05
    • 1970-01-01
    • 1970-01-01
    • 2013-03-07
    相关资源
    最近更新 更多