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

Ion*_*rba 4 c hal stm32 i2c

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

一般来说,我理解他的代码是如何工作的,但我无法理解他为什么使用HAL_LOCKHAL_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)
Run Code Online (Sandbox Code Playgroud)

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

zwo*_*wol 7

有人试图用这些宏实现一个基本的,咨询性的,非递归的互斥体(有时称为"锁定"或"关键部分",但这些术语具有其他含义;"互斥体"是明确的).想法是你写这样的代码:

int frob_handle(handle_t hdl)
{
    __HAL_LOCK(hdl);
    hdl->frob_counter += 1;
    __HAL_UNLOCK(hdl);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

然后,一次只有一个执行线程可以执行该语句hdl->frob_counter += 1.(在一个真实的程序中,那里可能会有相当多的代码.)它是"建议性的",因为没有什么可以阻止你在需要时忘记使用互斥锁,并且它是"非递归的"因为你无法调用__HAL_LOCK第二次,如果你已经锁定它.这些都是互斥锁具有相对正常的属性.

在对这个问题的评论中,我说这些宏是"灾难性的错误","我相信你最好不要使用它们." 最重要的问题__HAL_LOCK是不是原子的.

// This code is incorrect.
if ((__HANDLE__)->Lock == HAL_LOCKED)
  return HAL_BUSY;
else
  (__HANDLE__)->Lock = HAL_LOCKED;
Run Code Online (Sandbox Code Playgroud)

想象一下,两个执行线程试图在同一时间获取锁.它们将同时__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
Run Code Online (Sandbox Code Playgroud)

没有什么可以阻止两个线程同时执行加载指令,因此都观察到互斥锁被解锁.即使只有一个CPU,当线程1位于加载和存储之间时,也会触发中断,导致上下文切换并允许线程2在线程1执行存储之前执行加载.

对于互斥体,以完成其工作,你必须以某种方式确保它是不可能有两个并发线程都加载HAL_UNLOCKED__HANDLE__->Lock,这不能用普通C.其实做,不能与普通机器语言完成的; 您需要使用特殊指令,例如比较和交换.

如果您的编译器实现了C2011,那么您可以使用原子类型的新特性来获取这些特殊指令,但我不知道如何做到这一点,我不会写出一些东西,可能是错的.否则,您需要使用编译器扩展或手写程序集.

第二个问题是__HAL_LOCK没有实现通常称为"锁定"的操作.如果无法立即获取锁定,"Lock"应该等待,但是失败的__HAL_LOCK是什么.该操作的名称是"try-lock",并且应该相应地命名宏.此外,可能导致调用函数返回的宏被认为是不好的做法.