第15章 互斥信号量 本章节开始讲解RTX的另一个重要的资源共享机制---互斥信号量(Mutex,即Mutual Exclusion的缩写)。注意,建议初学者学习完上个章节的信号量后再学习本章节的互斥信号量。 本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407。 15.1 互斥信号量 15.2 互斥信号量API函数 15.3 实验例程说明 15.4 总结 15.1 互斥信号量
15.1.1 互斥信号量的概念及其作用 互斥信号量就是信号量的一种特殊形式,也就是信号量初始值为1的情况。有些RTOS中也将信号量初始值设置为1的情况称之为二值信号量。为什么叫二值信号量呢?因为信号量资源被获取了,信号量值就是0,信号量资源被释放,信号量值就是1,把这种只有0和1两种情况的信号量称之为二值信号量。互斥信号量的主要作用就是对资源实现互斥访问。下面举一个通过二值信号量实现资源独享,即互斥访问的例子,让大家有一个形象的认识 运行条件: 1. 让两个任务Task1和Task2都有运行串口打印printf,这里我们就对函数printf通过二值信号量实现互斥访问。如果不对函数printf进行互斥访问,串口打印容易出现乱码。 2. 用信号量实现二值信号量只需将信号量的初始值设置为1即可。 代码实现: 创建二值信号量
- OS_SEM semaphore;
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppObjCreate
- * 功能说明: 创建任务通信机制
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppObjCreate (void)
- {
- /* 创建二值信号量,实现对互斥资源的独享 */
- os_sem_init (semaphore, 0);
- }
复制代码
通过二值信号量实现对printf函数互斥访问的两个任务
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskLED
- * 功能说明: 串口打印
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskLED(void)
- {
- const uint16_t usFrequency = 1000; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
-
- /* 永久等待互斥资源可用 */
- os_sem_wait (&semaphore, 0xffff);
- printf("任务AppTaskLED正在运行rn");
- /* 释放互斥资源 */
- os_sem_send (&semaphore);
-
- /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskMsgPro
- * 功能说明: 串口打印
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 3
- *********************************************************************************************************
- */
- __task void AppTaskMsgPro(void)
- {
- const uint16_t usFrequency = 1000; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
- /* 永久等待互斥资源可以 */
- os_sem_wait (&semaphore, 0xffff);
- printf("任务AppTaskMsgPro正在运行rn");
- /* 释放互斥资源 */
- os_sem_send (&semaphore);
-
- /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
复制代码
有了上面二值信号量的认识之后,互斥信号量跟二值信号量又有什么区别呢?互斥信号量可以防止优先级翻转,而二值信号量不支持,下面我们就讲解一下优先级翻转问题。
15.1.2 优先级翻转问题 下面我们通过如下的框图来说明一下优先级翻转的问题,让大家有一个形象的认识。
运行条件: 1. 创建3个任务Task1,Task2和Task3,优先级分别为3,2,1。也就是Task1的优先级最高 2. 任务Task1和Task3互斥访问串口打印printf,采用二值信号实现互斥访问。 3. 起初Task3通过二值信号量正在调用printf,被任务Task1抢占,开始执行任务Task1,也就是上图的起始位置。 运行过程描述如下: 1. 任务Task1运行的过程需要调用函数printf,发现任务Task3正在调用,任务Task1会被挂起,等待Task3释放函数printf。 2. 在调度器的作用下,任务Task3得到运行,Task3运行的过程中,由于任务Task2就绪,抢占了Task3的运行。优先级翻转问题就出在这里了,从任务执行的现象上看,任务Task1需要等待Task2执行完毕才有机会得到执行,这个与抢占式调度正好反了,正常情况下应该是高优先级任务抢占低优先级任务的执行,这里成了高优先级任务Task1等待低优先级任务Task2完成。所以这种情况被称之为优先级翻转问题。 3. 任务Task2执行完毕后,任务Task3恢复执行,Task3释放互斥资源后,任务Task1得到互斥资源,从而可以继续执行。
上面就是一个产生优先级翻转问题的现象。
15.1.3 RTX互斥信号量的实现 RTX互斥信号量是怎么实现的呢?其实相比二值信号量就是解决了一下优先级翻转的问题。下面我们通过如下的框图来说明一下RTX互斥信号量的实现,让大家有一个形象的认识。
运行条件: 1. 创建2个任务Task1和Task2,优先级分别为1和3,也就是任务Task2的优先级最高 2. 任务Task1和Task2互斥访问串口打印printf。 3. 使用RTX的互斥信号量实现串口打印printf的互斥访问。 运行过程描述如下: 1. 低优先级任务Task1执行过程中先获得互斥资源printf的执行。此时任务Task2抢占了任务Task1的执行,任务Task1被挂起。任务Task2得到执行。 2. 任务Task2执行过程中也需要调用互斥资源,但是发现任务Task1正在访问,此时任务Task1的优先级会被提升到跟Task2同一个优先级,也就是优先级3,这个就是所谓的优先级继承(Priorityinheritance),这样就有效的防止了优先级翻转问题。任务Task2被挂起,任务Task1有新的优先级继续执行。 3. 任务Task1执行完毕并释放互斥资源后,优先级恢复到原来的水平。由于互斥资源可以使用,任务Task2获得互斥资源后开始执行。
上面就是一个简单RTX互斥信号量的实现过程。
15.1.4 RTX中断方式互斥信号量的实现 互斥信号量仅支持用在RTX的任务中,中断函数中不可使用。
|