c - HAL 锁定和解锁函数如何使用以及为什么?

标签 c stm32 i2c hal

我正在尝试理解另一位程序员编写的代码。它使用I²C通信以将数据写入 STM32 微 Controller 的 EEPROM。

一般来说我理解他的代码是如何工作的,但我不明白他为什么使用HAL_LOCKHAL_UNLOCK函数。

这些是这些方法的代码:

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)

什么情况下可以使用这些方法?

最佳答案

有人试图实现一个基本的、咨询性的、非递归的 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_LOCK不是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_UNLOCKED来自__HANDLE__->Lock ,而这用普通的C是做不到的。事实上,用普通的机器语言是做不到的;您需要使用特殊指令,例如 compare-and-swap .

如果您的编译器实现 C2011 ,然后您可以使用 atomic types 的新功能获取这些特殊说明,但我不知道如何从我的头脑中做到这一点,而且我不会写出可能是错误的东西。否则,您需要使用编译器扩展或手写汇编。

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

关于c - HAL 锁定和解锁函数如何使用以及为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49132332/

相关文章:

c - 如何转换温度传感器得到的值?

c - 嵌入式软件程序 block ,I2C?

linux - wire.requestFrom(...) 的 wiringPi 等价物是什么

c - 我仍然对 free() 和 malloc() 感到困惑

c - 分配给变量时指针的地址发生变化

stm32 - FreeRTOS STM32 集成

stm32 - 如何在 STWIN 上的软件中进入引导加载程序(DFU 模式)

c - 为什么在信号处理程序中使用互斥锁会出现问题?

c - 错误 : Expected expression before structure

STM32 和 SD 卡(FATFS 和 SPI)