收藏官网首页
查看: 798|回复: 0

[经验分享] 【一起来玩RTOS系列】之RT-Thread 互斥锁解决优先级反转

发表于 2017-12-9 21:42:27 | 显示全部楼层 |阅读模式
校园创客福利
本帖最后由 bigfanofloT 于 2017-12-9 21:45 编辑

互斥量

互斥量又叫相互排斥的信号量,是一种特殊的二值性信号量。它和信号量不同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性。互斥量工作如图所示
rt_mutex_semaphore.png

互斥量的状态只有两种,开锁或闭锁(两种状态值)。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去它的所有权。当一个线程持有互斥量时,其他线程将不能够对它进行开锁或持有它,持有该互斥量的线程也能够再次获得这个锁而不被挂起。

在RT-Thread操作系统中实现的是优先级继承算法。优先级继承是通过在线程A被阻塞的期间内,将线程C的优先级提升到线程A的优先级别,从而解决优先级翻转引起的问题。这样能够防止C(间接地防止A)被B抢占。优先级继承协议是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。

警告: 在获得互斥量后,请尽快释放互斥量,并且在持有互斥量的过程中,不得再行更改持有互斥量线程的优先级。
互斥量控制块

互斥量控制块的数据结构



  1.     struct rt_mutex
  2.     {
  3.         struct rt_ipc_object parent;                /* 继承自ipc_object类 */

  4.         rt_uint16_t          value;                 /* 互斥量的值 */
  5.         rt_uint8_t           original_priority;     /* 持有线程的原始优先级 */
  6.         rt_uint8_t           hold;                  /* 持有线程的持有次数   */
  7.         struct rt_thread    *owner;                 /* 当前拥有互斥量的线程 */
  8.     };
  9.     /* rt_mutext_t为指向互斥量结构体的指针 */
  10.     typedef struct rt_mutex* rt_mutex_t;
复制代码

rt_mutex对象从rt_ipc_object中派生,由IPC容器管理。

互斥量相关接口

创建互斥量

创建一个互斥量时,内核首先创建一个互斥量控制块,然后完成对该控制块的初始化工作。创建互斥量使用下面的函数接口:

  1. rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag);
复制代码

可以调用rt_mutex_create函数创建一个互斥量,它的名字有name所指定。创建的互斥量由于指定的flag不同,而有不同的意义: 使用PRIO优先级flag创建的IPC对象,在多个线程等待资源时,将由优先级高的线程优先获得资源。而使用FIFO先进先出flag创建的IPC对象,在多个线程等待资源时,将按照先来先得的顺序获得资源。

函数参数


      参数  描述

      name  互斥量的名称;

      flag  互斥量标志,可以取如下类型的数值:

  1. #define RT_IPC_FLAG_FIFO 0x00 /* IPC参数采用FIFO先进先出方式*/
  2. #define RT_IPC_FLAG_PRIO 0x01 /* IPC参数采用优先级方式*/
复制代码

函数返回

创建成功返回指向互斥量的互斥量句柄;否则返回RT_NULL。

删除互斥量

系统不再使用互斥量时,通过删除互斥量以释放系统资源。删除互斥量使用下面的函数接口:

  1. rt_err_t rt_mutex_delete (rt_mutex_t mutex);
复制代码

当删除一个互斥量时,所有等待此互斥量的线程都将被唤醒,等待线程获得的返回值是-RT_ERROR。然后系统将该互斥量从内核对象管理器链表中删除并释放互斥量占用的内存空间。

函数参数


      参数  描述

     mutex  互斥量对象的句柄;
    函数返回

    RT_EOK

初始化互斥量

静态互斥量对象的内存是在系统编译时由编译器分配的,一般放于数据段或ZI段中。在使用这类静态互斥量对象前,需要先进行初始化。初始化互斥量使用下面的函数接口:

  1. rt_err_t rt_mutex_init (rt_mutex_t mutex, const char* name, rt_uint8_t flag);
复制代码

使用该函数接口时,需指定互斥量对象的句柄(即指向互斥量控制块的指针),互斥量名称以及互斥量标志。互斥量标志可用上面创建互斥量函数里提到的标志。

函数参数


      参数  描述

     mutex  互斥量对象的句柄,它由用户提供,并指向互斥量对象的内存块;

      name  互斥量名称;

      flag  互斥量标志,可以取如下类型的数值:

  1. #define RT_IPC_FLAG_FIFO 0x00 /* IPC参数采用FIFO先进先出方式*/
  2. #define RT_IPC_FLAG_PRIO 0x01 /* IPC参数采用优先级方式*/
复制代码

函数返回

RT_EOK

脱离互斥量

脱离互斥量将把互斥量对象从内核对象管理器中删除。脱离互斥量使用下面的函数接口:

  1. rt_err_t rt_mutex_detach (rt_mutex_t mutex);
复制代码

使用该函数接口后,内核先唤醒所有挂在该互斥量上的线程(线程的返回值是-RT_ERROR),然后系统将该互斥量从内核对象管理器链表中删除。

函数参数


      参数  描述

     mutex  互斥量对象的句柄;

函数返回

RT_EOK

获取互斥量

线程通过互斥量申请服务获取互斥量的所有权。线程对互斥量的所有权是独占的,某一个时刻一个互斥量只能被一个线程持有。获取互斥量使用下面的函数接口:

  1. rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time);
复制代码

如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得该互斥量。如果互斥量已经被当前线程线程控制,则该互斥量的持有计数加1,当前线程也不会挂起等待。如果互斥量已经被其他线程占有,则当前线程在该互斥量上挂起等待,直到其他线程释放它或者等待时间超过指定的超时时间。

函数参数


      参数  描述

     mutex  互斥量对象的句柄;

      time  指定等待的时间。
函数返回

成功获得互斥量返回RT_EOK;超时返回-RT_ETIMEOUT;其他返回-RT_ERROR。

释放互斥量

当线程完成互斥资源的访问后,应尽快释放它占据的互斥量,使得其他线程能及时获取该互斥量。释放互斥量使用下面的函数接口:

  1. rt_err_t rt_mutex_release(rt_mutex_t mutex);
复制代码

使用该函数接口时,只有已经拥有互斥量控制权的线程才能释放它,每释放一次该互斥量,它的持有计数就减1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),它变为可用,等待在该信号量上的线程将被唤醒。如果线程的运行优先级被互斥量提升,那么当互斥量被释放后,线程恢复为持有互斥量前的优先级。

函数参数


      参数  描述

     mutex  互斥量对象的句柄;
函数返回

RT_EOK

使用场合

互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量永远都处于开锁的状态,而被线程持有的时候则立刻转为闭锁的状态。互斥量更适合于:
线程多次持有互斥量的情况下。这样可以避免同一线程多次递归持有而造成死锁的问题;
可能会由于多线程同步而造成优先级翻转的情况;
另外需要切记的是互斥量不能在中断服务例程中使用。

下面在机智云gokit智能硬件开发板上演示如何使用互斥锁解决上节提到的优先级反转问题。

注意:使用互斥锁需要在配置文件打开宏
搜狗截图20171209213910.png

  1. /**
  2.   ***********************************
  3.   * File Name          : main.c
  4.   * Description        : Main program body
  5.   ***********************************
  6.   ** This notice applies to any and all portions of this file
  7.   * that are not between comment pairs USER CODE BEGIN and
  8.   * USER CODE END. Other portions of this file, whether
  9.   * inserted by the user or by software development tools
  10.   * are owned by their respective copyright owners.
  11.   *
  12.   * COPYRIGHT(c) 2017 STMicroelectronics
  13.   *
  14.   * Redistribution and use in source and binary forms, with or without modification,
  15.   * are permitted provided that the following conditions are met:
  16.   *   1. Redistributions of source code must retain the above copyright notice,
  17.   *      this list of conditions and the following disclaimer.
  18.   *   2. Redistributions in binary form must reproduce the above copyright notice,
  19.   *      this list of conditions and the following disclaimer in the documentation
  20.   *      and/or other materials provided with the distribution.
  21.   *   3. Neither the name of STMicroelectronics nor the names of its contributors
  22.   *      may be used to endorse or promote products derived from this software
  23.   *      without specific prior written permission.
  24.   *
  25.   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  26.   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  27.   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  28.   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  29.   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  30.   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  31.   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  32.   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  33.   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  34.   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  35.   *
  36.   ***********************************
  37.   */
  38. /* Includes ------------------------------------------------------------------*/
  39. #include "main.h"
  40. #include "STM32f1xx_hal.h"
  41. #include "usart.h"
  42. #include "gpio.h"

  43. /* USER CODE BEGIN Includes */
  44. #include "rtthread.h"
  45. #include "string.h"
  46. /* USER CODE END Includes */

  47. /* Private variables ---------------------------------------------------------*/

  48. /* USER CODE BEGIN PV */
  49. /* Private variables ---------------------------------------------------------*/

  50. /* USER CODE END PV */

  51. /* Private function prototypes -----------------------------------------------*/
  52. void SystemClock_Config(void);

  53. /* USER CODE BEGIN PFP */
  54. /* Private function prototypes -----------------------------------------------*/

  55. /* USER CODE END PFP */

  56. /* USER CODE BEGIN 0 */
  57. //重映射串口1到rt_kprintf
  58. void rt_hw_console_output(const char *str)
  59. {
  60.     /* empty console output */
  61.         char aa='\r';
  62.           rt_enter_critical();

  63.                 while(*str!='\0')
  64.                 {
  65.                         if(*str=='\n')
  66.                         {
  67.                                 HAL_UART_Transmit(&huart1, (uint8_t *)&aa, 1, 10);
  68.                         }
  69.                                 HAL_UART_Transmit(&huart1, (uint8_t *)(str++), 1, 10);
  70.                 }
  71.                
  72.                 rt_exit_critical();
  73. }

  74. void rt_hw_us_delay(int us)
  75. {
  76.     rt_uint32_t delta;

  77.     /* 获得延时经过的tick数 */
  78.     us = us * (SysTick->LOAD/(1000000/RT_TICK_PER_SECOND));

  79.     /* 获得当前时间 */
  80.     delta = SysTick->VAL;

  81.     /* 循环获得当前时间,直到达到指定的时间后退出循环 */
  82.     while (delta - SysTick->VAL< us);
  83. }

  84. void rt_hw_ms_delay(int ms)
  85. {
  86.         int i=0,j=0;
  87.         for(j=0;j<ms;j++)
  88.         {
  89.                 for (i=0;i<2;i++)
  90.                 rt_hw_us_delay(500);
  91.         }
  92. }
  93. uint32_t rt_hw_delay_Init(void)
  94. {
  95. #if !defined(STM32F0xx)
  96.         uint32_t c;
  97.         
  98.     /* Enable TRC */
  99.     CoreDebug->DEMCR &= ~0x01000000;
  100.     CoreDebug->DEMCR |=  0x01000000;
  101.         
  102.     /* Enable counter */
  103.     DWT->CTRL &= ~0x00000001;
  104.     DWT->CTRL |=  0x00000001;
  105.         
  106.     /* Reset counter */
  107.     DWT->CYCCNT = 0;
  108.         
  109.         /* Check if DWT has started */
  110.         c = DWT->CYCCNT;
  111.         
  112.         /* 2 dummys */
  113.         __ASM volatile ("NOP");
  114.         __ASM volatile ("NOP");
  115.         
  116.         /* Return difference, if result is zero, DWT has not started */
  117.         return (DWT->CYCCNT - c);
  118. #else
  119.         /* Return OK */
  120.         return 1;
  121. #endif
  122. }
  123. void rt_hw_delay_us(__IO uint32_t micros)
  124. {
  125. #if !defined(STM32F0xx)
  126.         uint32_t start = DWT->CYCCNT;
  127.         
  128.         /* Go to number of cycles for system */
  129.         micros *= (HAL_RCC_GetHCLKFreq() / 1000000);
  130.         
  131.         /* Delay till end */
  132.         while ((DWT->CYCCNT - start) < micros);
  133. #else
  134.         /* Go to clock cycles */
  135.         micros *= (SystemCoreClock / 1000000) / 5;
  136.         
  137.         /* Wait till done */
  138.         while (micros--);
  139. #endif
  140. }
  141. void rt_hw_delay_ms(__IO uint32_t mills)
  142. {
  143.         rt_hw_delay_us(1000*mills);
  144. }
  145. /*
  146. * 程序清单:若使用二值信号量:有优先级为A、B和C的三个线程,优先级A> B > C。
  147. 线程A,B处于挂起状态,等待某一事件触发,线程C正在运行,此时线程C开始使用某一共享资源M。
  148. 在使用过程中,线程A等待的事件到来,线程A转为就绪态,因为它比线程C优先级高,所以立即执行。
  149. 但是当线程A要使用共享资源M时,由于其正在被线程C使用,因此线程A被挂起切换到线程C运行。
  150. 如果此时线程B等待的事件到来,则线程B转为就绪态。
  151. 由于线程B的优先级比线程C高,因此线程B开始运行,直到其运行完毕,线程C才开始运行。
  152. 只有当线程C释放共享资源M后,线程A才得以执行。
  153. 在这种情况下,优先级发生了翻转,线程B先于线程A运行。

  154. 我们将信号量改为互斥锁完美的解决了优先级反转问题。
  155. *
  156. */

  157. /* 指向线程控制块的指针 */
  158. static rt_thread_t tid1 = RT_NULL;
  159. static rt_thread_t tid2 = RT_NULL;
  160. static rt_thread_t tid3 = RT_NULL;


  161. struct rt_mutex mutex;

  162. /* 线程1入口 */
  163. void thread1_entry(void* parameter)
  164. {

  165.     while(1)
  166.     {
  167.                         rt_thread_delay(100);
  168.                         rt_mutex_take(&mutex, RT_WAITING_FOREVER);
  169.                         rt_kprintf("thread1 is running.\n");        
  170.                         rt_mutex_release(&mutex);                                       
  171.     }

  172. }
  173. /* 线程2入口 */
  174. void thread2_entry(void* parameter)
  175. {

  176.     while(1)
  177.     {
  178.                         rt_thread_delay(300);
  179.                         rt_kprintf("thread2 is running.\n");                                
  180.     }

  181. }
  182. /* 线程3入口 */
  183. void thread3_entry(void* parameter)
  184. {

  185.     while(1)
  186.     {
  187.                         rt_mutex_take(&mutex, RT_WAITING_FOREVER);
  188.                         rt_kprintf("thread3 is running.\n");
  189.                         rt_hw_delay_ms(500);                                
  190.                         rt_mutex_release(&mutex);
  191.     }

  192. }
  193. /* USER CODE END 0 */

  194. int main(void)
  195. {

  196.   /* USER CODE BEGIN 1 */

  197.   /* USER CODE END 1 */

  198.   /* MCU Configuration----------------------------------------------------------*/

  199. //  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  200. //  HAL_Init();

  201. //  /* USER CODE BEGIN Init */

  202. //  /* USER CODE END Init */

  203. //  /* Configure the system clock */
  204. //  SystemClock_Config();

  205. //  /* USER CODE BEGIN SysInit */

  206. //  /* USER CODE END SysInit */

  207. //  /* Initialize all configured peripherals */
  208. //  MX_GPIO_Init();
  209. //  MX_USART1_UART_Init();

  210.   /* USER CODE BEGIN 2 */
  211.         
  212.         rt_mutex_init(&mutex, "mutex",  RT_IPC_FLAG_FIFO);//初始化信号量,初值为0

  213. /* 创建线程1 */
  214.     tid1 = rt_thread_create("thread1",
  215.         thread1_entry, /* 线程入口是thread_entry */
  216.         RT_NULL, /* 入口参数是RT_NULL */
  217.         512, //堆栈大小
  218.                                 2, //优先级
  219.                                 20);//时间片
  220.     if (tid1 != RT_NULL)
  221.         rt_thread_startup(tid1);
  222.                
  223.                 /* 创建线程2 */
  224.     tid2 = rt_thread_create("thread2",
  225.         thread2_entry, /* 线程入口是thread_entry */
  226.         RT_NULL, /* 入口参数是RT_NULL */
  227.         512, //堆栈大小
  228.                                 4, //优先级
  229.                                 20);//时间片
  230.     if (tid2 != RT_NULL)
  231.         rt_thread_startup(tid2);
  232.                
  233.                 /* 创建线程3 */
  234.     tid3 = rt_thread_create("thread3",
  235.         thread3_entry, /* 线程入口是thread_entry */
  236.         RT_NULL, /* 入口参数是RT_NULL */
  237.         512, //堆栈大小
  238.                                 6, //优先级
  239.                                 20);//时间片
  240.     if (tid3 != RT_NULL)
  241.         rt_thread_startup(tid3);
  242.                
  243.         rt_hw_delay_Init();
  244.                
  245.         printf("\r\n机智云  只为智能硬件而生\r\n");
  246.         printf("Gizwits Smart Cloud for Smart Products\r\n");
  247.         printf("链接|增值|开放|中立|安全|自有|自由|生态\r\n");
  248.         printf("www.gizwits.com\r\n");
  249.         printf("\r\nGokit RT-Thread Demo\r\n\r\n");
  250.         
  251.         return 0;
  252.         
  253.         


  254.   /* USER CODE END 2 */

  255.   /* Infinite loop */
  256.   /* USER CODE BEGIN WHILE */
  257. //  while (1)
  258. //  {
  259.   /* USER CODE END WHILE */

  260.   /* USER CODE BEGIN 3 */
  261. //               
  262. //  }
  263.   /* USER CODE END 3 */

  264. }

  265. /** System Clock Configuration
  266. */
  267. void SystemClock_Config(void)
  268. {

  269.   RCC_OscInitTypeDef RCC_OscInitStruct;
  270.   RCC_ClkInitTypeDef RCC_ClkInitStruct;

  271.     /**Initializes the CPU, AHB and APB busses clocks
  272.     */
  273.   RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  274.   RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  275.   RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  276.   RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  277.   RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  278.   RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  279.   RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  280.   if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  281.   {
  282.     _Error_Handler(__FILE__, __LINE__);
  283.   }

  284.     /**Initializes the CPU, AHB and APB busses clocks
  285.     */
  286.   RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
  287.                               |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  288.   RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  289.   RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  290.   RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  291.   RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  292.   if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  293.   {
  294.     _Error_Handler(__FILE__, __LINE__);
  295.   }

  296.     /**Configure the Systick interrupt time
  297.     */
  298.   HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

  299.     /**Configure the Systick
  300.     */
  301.   HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  302.   /* SysTick_IRQn interrupt configuration */
  303.   HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
  304. }

  305. /* USER CODE BEGIN 4 */

  306. /* USER CODE END 4 */

  307. /**
  308.   * @brief  This function is executed in case of error occurrence.
  309.   * @param  None
  310.   * @retval None
  311.   */
  312. void _Error_Handler(char * file, int line)
  313. {
  314.   /* USER CODE BEGIN Error_Handler_Debug */
  315.   /* User can add his own implementation to report the HAL error return state */
  316.   while(1)
  317.   {
  318.   }
  319.   /* USER CODE END Error_Handler_Debug */
  320. }

  321. #ifdef USE_FULL_ASSERT

  322. /**
  323.    * @brief Reports the name of the source file and the source line number
  324.    * where the assert_param error has occurred.
  325.    * @param file: pointer to the source file name
  326.    * @param line: assert_param error line source number
  327.    * @retval None
  328.    */
  329. void assert_failed(uint8_t* file, uint32_t line)
  330. {
  331.   /* USER CODE BEGIN 6 */
  332.   /* User can add his own implementation to report the file name and line number,
  333.     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  334.   /* USER CODE END 6 */

  335. }

  336. #endif

  337. /**
  338.   * @}
  339.   */

  340. /**
  341.   * @}
  342. */

  343. /*********** (C) COPYRIGHT STMicroelectronics ***END OF FILE**/
复制代码


串口信息:和上节对比,线程执行优先级和预期一致,没有优先级反转现象了。。。
互斥锁解决优先级反转.png

源码下载:
Gokit_RT-Thread.zip (8.65 MB, 下载次数: 1, 售价: 1 金钱)
1、机智云QQ群:G1群:104975951 G2群:491509598 G3群:287087942
机智云爱好者-APP开发群: 599735135
QQ群目前非常活跃,欢迎大家参与进来,交流,讨论,答疑,解惑~~
2、机智云微信公众号: 机智云 gizwits、 机智云智能宠物屋go-iot
关注机智云Gizwits官方公众号随时掌握最新资讯和活动信息
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

加入Q群 返回顶部

© 2011-2017 Gizwits ( 粤ICP备11090211号 )

快速回复 返回顶部 返回列表