代码之家  ›  专栏  ›  技术社区  ›  user8872869

HAL锁定和解锁功能是如何使用的?为什么?

  •  6
  • user8872869  · 技术社区  · 7 年前

    我正在努力理解另一个程序员编写的代码。它使用 I²C 在STM32微控制器的EEPROM上写入数据的通信。

    一般来说,我理解他的代码是如何工作的,但我不理解他为什么使用 HAL_LOCK HAL_UNCLOCK 功能。

    以下是这些方法的代码:

    typedef enum
    {
        HAL_UNLOCKED = 0x00U,
        HAL_LOCKED   = 0x01U
    } HAL_LockTypeDef;
    
    
    #if (USE_RTOS == 1)
    
        /* Reserved for future use */
        #error "USE_RTOS should be 0 in the current HAL release"
    
    #else
    
      #define __HAL_LOCK(__HANDLE__)                 \
          do{                                        \
              if((__HANDLE__)->Lock == HAL_LOCKED)   \
              {                                      \
                 return HAL_BUSY;                    \
              }                                      \
              else                                   \
              {                                      \
                 (__HANDLE__)->Lock = HAL_LOCKED;    \
              }                                      \
          } while (0)
    
    
      #define __HAL_UNLOCK(__HANDLE__)              \
          do{                                       \
              (__HANDLE__)->Lock = HAL_UNLOCKED;    \
          } while (0)
    

    在哪些情况下可以使用这些方法?

    1 回复  |  直到 6 年前
        1
  •  12
  •   zwol    6 年前

    有人是 尝试 实现基本的、咨询的、非递归的 mutex 对于这些宏(有时称为“锁”或“临界段”,但这些术语有其他含义;“互斥”是明确的)。我们的想法是编写如下代码:

    int frob_handle(handle_t hdl)
    {
        __HAL_LOCK(hdl);
        hdl->frob_counter += 1;
        __HAL_UNLOCK(hdl);
        return 0;
    }
    

    然后一次只有一个执行线程可以执行该语句 hdl->frob_counter += 1 。(在实际程序中,可能会有更多的代码。)它是“建议性的”,因为没有什么可以阻止您在需要时忘记使用互斥,它是“非递归的”,因为您不能调用 __HAL_LOCK 如果已经锁定,请再次锁定。对于互斥体来说,这两个属性都是相对正常的。

    在对这个问题的评论中,我说这些宏是“灾难性的错误”,“我相信你最好不要使用它们。”最重要的问题是 __HAL\U锁 不是 atomic

    // This code is incorrect.
    if ((__HANDLE__)->Lock == HAL_LOCKED)
      return HAL_BUSY;
    else
      (__HANDLE__)->Lock = HAL_LOCKED;
    

    假设两个执行线程正试图同时获取锁。他们都会来的 __HANDLE__->Lock 同时从内存中,因此它们都将观察其值 HAL_UNLOCKED ,它们都将继续执行一次只能由一个线程执行的代码。如果我写出可能生成的汇编语言,可能更容易看到问题:

        ; This code is incorrect.
        ; r1 contains the __HANDLE__ pointer
        load.b  r0, Lock(r1)
        test.b  r0
        bnz     .already_locked
        inc.b   r0
        store.b Lock(r1), r0
        ...
    .already_locked:
        mov.b   r0, #HAL_BUSY
        ret
    

    没有任何东西可以阻止两个线程同时执行加载指令,从而都可以观察到要解锁的互斥锁。即使只有一个CPU,当线程1处于加载和存储之间时,也可能触发中断,从而导致上下文切换,并允许线程2在线程1执行存储之前执行加载。

    为了让互斥体完成其工作,您必须以某种方式确保它是 不可能的 两个并发线程都要加载 HAL\u解锁 从…起 __句柄\->锁 ,而这不能用普通的C来完成。事实上,它不能用普通的机器语言来完成;您需要使用特殊说明,例如 compare-and-swap

    如果编译器实现 C2011 ,然后您可以使用 atomic types ,但我不知道该怎么做,我也不会写出可能有错的东西。否则,您需要使用编译器扩展或手工编写的程序集。

    第二个问题是 __HAL\U锁 不执行通常称为“锁定”的操作。“锁”应该是 等待 如果它不能立即获取锁,但是 __HAL\U锁 确实是 失败 。该操作的名称为“try lock”,应相应地命名宏。此外,可能导致调用函数返回的宏被认为是错误的做法。