你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】多任务系统中的不可重函数使用注意事项

[复制链接]
STMCU小助手 发布时间:2022-3-2 22:24
前言
本文章主要介绍多任务系统中的的不可重入函数的用法和如何避免出错。

1. 可重入函数与不可重入函数的区别
   在多任务环境中,可重入函数可以由多于一个的任务并发使用,而不用担心数据错误。这个函数可以在执行的任何时刻中断它。
   不可重入函数由于使用了一些系统资源,比如全局变量等,不能被很多任务所共享,除非能确保函数的互斥性。
1)可重入函数:
不返回指向静态数据的指针。
使用本地数据。
如果必须访问全局变量,记住利用互斥信号量来保护全局变量。
绝不调用任何不可重入函数。
2)不可重入函数:
函数中使用了静态变量。
函数返回静态变量。
函数中调用了不可重入函数。
函数体内使用了静态的数据结构;
函数体内调用了 malloc()或者 free()函数;
函数体内调用了其他标准 I/O 函数。

总的来说,如果一个函数在重入条件下使用了未受保护的共享的资源,那么它是不可重入的。

2. 不可重入函数的一次错误应用示例,造成 I2C Master 端收到的数据无法正确解析。
这个产品使用的 MCU 为 STM32F411,是作为 Sensor hub 使用的,做为 I2C 的 Master 端,总线上挂了好几个传感器,如加速度、陀螺仪、光感应等。并且同时采用定时方式和中断方式通过 I2C 总线读取 sensor 上数据的。

~O5[B@9MZKG3AKNCB_4FX%R.png

客户产品功能是在实时操作系统上实现的,属于多任务系统。在中断处理函数中会调用 I2C_Read(…)函数,而I2C_Read(…)函数中又调用了 HAL_I2C_Master_Receive_IT(…)函数。

函数代码在示例程序中的位置如下:
…\stm32cubef4\STM32Cube_FW_F4_V1.6.0\Drivers\STM32F4xx_HAL_Driver\Src 的 stm32f4xx_hal_i2c.c
stm32f4xx_hal_i2c.c 文件中有一段 I2C slave 接收程序,具体代码如下:
  1. /**
  2. /**

  3. * @brief Receive in master mode an amount of data in no-blocking mode with Interrupt
  4. * @param hi2c: pointer to a I2C_HandleTypeDef structure that contains
  5. * the configuration information for I2C module
  6. * @param DevAddress: Target device address
  7. * @param pData: Pointer to data buffer
  8. * @param Size: Amount of data to be sent
  9. * @retval HAL status
  10.   */ */
  11.   */
  12.   HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress,
  13.   uint8_t *pData, uint16_t Size)
  14.   {
  15.   if(hi2c->State == HAL_I2C_STATE_READY)
  16.   {
  17.   if((pData == NULL) || (Size == 0))
  18.   {
  19.   return HAL_ERROR;
  20.   }
  21.   /* Wait until BUSY flag is reset */
  22.   if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG) !=
  23.   HAL_OK)
  24.   {
  25.   return HAL_BUSY;
  26.   }
  27.   /* Process Locked */
  28.   主控 STM32F411
  29.   sensorhub
  30.   Sensor1
  31.   Sensor2
  32.   Sensor3
  33.   Sensor4
  34.   __HAL_LOCK(hi2c);

  35. /* Disable Pos */
  36. hi2c->Instance->CR1 &= ~I2C_CR1_POS;
  37. hi2c->State = HAL_I2C_STATE_BUSY_RX;
  38. hi2c->ErrorCode = HAL_I2C_ERROR_NONE;
  39. hi2c->pBuffPtr = pData;
  40. hi2c->XferSize = Size;
  41. hi2c->XferCount = Size;
  42. /* Send Slave Address */
  43. if(I2C_MasterRequestRead(hi2c, DevAddress, I2C_TIMEOUT_FLAG) != HAL_OK)
  44. {
  45. if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)
  46. {
  47. /* Process Unlocked */
  48. __HAL_UNLOCK(hi2c);
  49. return HAL_ERROR;
  50. }
  51. else
  52. {
  53. /* Process Unlocked */
  54. __HAL_UNLOCK(hi2c);
  55. return HAL_TIMEOUT;
  56. }
  57. }
  58. if(hi2c->XferCount == 1)
  59. {
  60. /* Disable Acknowledge */
  61. hi2c->Instance->CR1 &= ~I2C_CR1_ACK;
  62. /* Clear ADDR flag */
  63. __HAL_I2C_CLEAR_ADDRFLAG(hi2c);
  64. /* Generate Stop */
  65. hi2c->Instance->CR1 |= I2C_CR1_STOP;
  66. }
  67. else if(hi2c->XferCount == 2)
  68. {
  69. /* Disable Acknowledge */
  70. hi2c->Instance->CR1 &= ~I2C_CR1_ACK;
  71. /* Enable Pos */
  72. hi2c->Instance->CR1 |= I2C_CR1_POS;
  73. /* Clear ADDR flag */
  74. __HAL_I2C_CLEAR_ADDRFLAG(hi2c);
  75. }
  76. else
  77. {
  78. /* Enable Acknowledge */
  79. hi2c->Instance->CR1 |= I2C_CR1_ACK;
  80. /* Clear ADDR flag */
  81. __HAL_I2C_CLEAR_ADDRFLAG(hi2c);
  82. }
  83. /* Process Unlocked */
  84. __HAL_UNLOCK(hi2c);
  85. /* Note : The I2C interrupts must be enabled after unlocking current process
  86. to avoid the risk of I2C interrupt handle execution before current
  87. process unlock */
  88. /* Enable EVT, BUF and ERR interrupt */
  89. __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR);
  90. return HAL_OK;
  91. } */
  92. else
  93. {
  94. return HAL_BUSY;
  95. }
  96. }
复制代码

上面程序中通过下面指令对硬件端口进行加锁:
__HAL_LOCK(hi2c);
__HAL_UNLOCK(hi2c);
初步看来多任务系统中好像是安全的,但实际应用中并非如此(客户的不可重入函数调用了这个 I2C 函数),下面用图例来说明:

{P5MT]A4BUNRE3C_E3U2@2T.png

上图中”t”为时间轴,”T1”时间发生 sensor 1 中断并调用 I2C_Read(…),在 I2C_Read(…)函数中会调用HAL_I2C_Master_Receive_IT(…)函数,在调用 HAL_I2C_Master_Receive_IT(…)函数之前已经进行了传参,即:Addr=0x1A0,Size=3,Buf 指向 buffer1 的地址。

当程序运行到”T2”时又来了一个 sensor 2 中断,调用 I2C_Read(…)函数,在调用 HAL_I2C_Master_Receive_IT(…)函数之前已经传参完成。此时的 I2C 地址,Size 等的值如下:
Addr=0x1B0,Size=5,Buf 指向 buffer1 的地址。
由于 HAL_I2C_Master_Receive_IT(…)程序有加锁,和判断 busy 状况,当程序判断到总线 busy 时或是有加锁时跳出HAL_I2C_Master_Receive_IT(…)函数;但此时的 I2C 地址,buffer 地址, Size 都已经是 Sensor2 对应的值了。然后程序进入 ”T3“。
Sensor2 中断返回时会把 sensor2 的 Size 值 5 赋给 hi2c->XferSize,因此后面的 I2C 通讯中的值如下:
Addr=0x1A0(sensor1 value)
Buf 指向 buffer1(sensor1 value)
Size=5(sensor2 value)
因此程序处理中会出错。

3.上面的问题解决方法:
上面多任务系统的 I2C 处理程序中出现了不可重入函数的多次调用问题,主要是 I2C_Read(…)为不可重入函数,但对它进行了多次调用,使变量更改。
下面的办法可以解决多次调用问题,既对调用 I2C_Read(…)的动作加锁,使第二个 I2C 中断发生后不进行传参动作,如下图所示:


S3KT$T[QA00MI8%%`NAHS{P.png

也可以使用信号量方式处理。如下图:
`({5S`APIE$O84EV021_DEX.png



4. 程序占用资源后要防止死锁发生

上面 I2C 程序处理中要注意不要发生死锁,假设在 I2C_Read(…) 函数里有一条指令是对 I2C 端口的状态进行判断,如 busy状态,如果程序一直在等待中,就很容易发生死锁,下面图示是程序发生死锁的情况:

CB5X]W]EBYFT4L6B7FQZTCG.png

在上面程序中需要增加超时跳出机制来防止死锁。

5.总结
在多任务系统中,在使用函数时要了解函数的可重入性,对待有信号量或是加互斥锁的函数要防止死锁的发生。通讯程序比如 I2C, UART 等最好增加超时跳出机制。

收藏 评论0 发布时间:2022-3-2 22:24

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版