学习之路主要为FreeRTOS操作系统在STM32F103(STM32F103C8T6)上的运用,采用的是标准库编程的方式,使用的IDE为KEIL5。
注意!!!本学习之路可以通过购买STM32最小系统板以及部分配件的方式进行学习,也可以通过Proteus仿真的方式进行学习。
后续文章会同时发表在个人博客(jason1016.club)、CSDN;
视频会发布在bilibili(UID:399951374)
信号量概念
信号量常常用于控制对共享资源的访问和任务同步,我的理解是:信号量就是全局定义的存储数据,任何任务以及中断都可以对其进行访问以及写入操作。实现一个资源的共享。
一、二值信号量
1、概念
二值信号量通常用于互斥访问或同步。二值信号量,顾名思义他的状态只有1和0,即他的状态只有接收到信号和未接收到信号。我的理解是:把他理解成一个标志位。他的作用在于在中断标志二值信号,然后再任务中释放信号量,实现中断标志事件(告诉系统,这个要执行了),在由任务执行事件(由任务进行执行操作)。大大避免了因此中断过久导致的系统冗余。
二值信号量的使命就是同步,完成任务与任务或中断与任务之间的同步
2、二值信号量的创建
//动态创建方案(新版)
SemaphoreHandle_t xSemaphoreCreateBinary( void ) //xSemaphore:保存创建成功的二值信号量句柄。
/*返回值
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量的句柄。*/
3、释放(发送)和获取信号量
释放信号量
//任务级
BaseType_t xSemaphoreGive( xSemaphore ) //xSemaphore:要释放的信号量句柄。
/*返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败。*/
//中断级
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, // xSemaphore: 要释放的信号量句柄。
BaseType_t * pxHigherPriorityTaskWoken) //pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
/*返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败。*/
获取信号量
//任务级
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, //xSemaphore:要获取的信号量句柄。
TickType_t xBlockTime) //xBlockTime: 阻塞时间。
/*返回值:
pdTRUE: 获取信号量成功。
pdFALSE: 超时,获取信号量失败。*/
//中断级
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore, //xSemaphore: 要获取的信号量句柄。
BaseType_t * pxHigherPriorityTaskWoken) //pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
/*返回值
pdPASS: 获取信号量成功。
pdFALSE: 获取信号量失败。*/
二、计数型信号量
1、概念
同二值信号量一样,用户不需要关心队列中存储了什么数据,只需要关心队列是否为空即可
在这个场合中,每次事件发生的时候就在事件处理函数中释放信号量(增加信号量的计数值),其他任务会获取信号量(信号量计数值减一,信号量值就是队列结构体成员变量uxMessagesWaiting)来处理事件。在这种场合中创建的计数型信号量初始计数值为 0。
在这个场合中,信号量值代表当前资源的可用数量,比如停车场当前剩余的停车位数量。一个任务要想获得资源的使用权,首先必须获取信号量,信号量获取成功以后信号量值就会减一。当信号量值为 0 的时候说明没有资源了。当一个任务使用完资源以后一定要释放信号量,释放信号量以后信号量值会加一。在这个场合中创建的计数型信号量初始值应该是资源的数量,比如停车场一共有 100 个停车位,那么创建信号量的时候信号量值就应该初始化为 100。
我的理解是:计数型信号量同样是作为一个标志位的类型存在于系统当中的,而计数型与二值信号的不同之处为:二值信号只能释放(挂起)一次,之后就要等待获取(消除)了;而计数型可以释放多次并且叠加计数,然后再慢慢获取信号量实现(消除)操作。
2、计数型信号量的创建
//动态的方法
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, //uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
UBaseType_t uxInitialCount ) //uxInitialCount: 计数信号量初始值。
/*返回值
NULL: 计数型信号量创建失败。
其他值: 计数型信号量创建成功,返回计数型信号量句柄。*/
3、信号量的释放和获取
uxSemaphoreGetCount() |
//获取数值信号量的信号值
uxSemaphoreGetCount(SemaphoreHandle_t xSemaphore)//xSemaphore:要获取的信号量句柄
/*返回值:
数值信号量值*/
三、优先级翻转
在使用二值信号量的时候会遇到很常见的一个问题——优先级翻转,优先级翻转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能会导致严重的后果。
当一个低优先级任务和一个高优先级任务同时使用同一个信号量,而系统中还有其他中等优先级任务时。如果低优先级任务获得了信号量,那么高优先级的任务就会处于等待状态,但是,中等优先级的任务可以打断低优先级任务而先于高优先级任务运行(此时高优先级的任务在等待信号量 ,所以不能运行),这是就出现了优先级翻转的现象。
既然优先级翻转是个很严重的问题,那么有没有解决方法呢?有!这就要引出另外一种信号量——互斥信号量!
四、互斥信号量(解决优先级翻转问题)
1、概念
互斥信号量其实就是一个拥有优先级继承的二值信号量量,在同步的应用中(任务与任务或中断与任务之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。在互斥访问中互斥信号量相当于一个钥匙,当任务想要使用资源的时候就必须先获得这个钥匙,当使用完资源以后就必须归还这个钥匙,这样其他的任务就可以拿着这个钥匙去使用资源。
互斥信号量也可以设置阻塞时间,不同于二值信号量的是互斥信号量具有优先级继承的特性。当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并且将已经出现的“优先级翻转”的影响降到最低。优先级继承并不能完全的消除优先级翻转,它只是尽可能的降低优先级翻转带来的影响。硬实时应用应该在设计之初就要避免优先级翻转的发生。互斥信号量不能用于中断服务函数中,原因如下:
● 互斥信号量有优先级继承的机制,所以只能用在任务中,不能用于中断服务函数。
● 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
2、互斥信号量的创建
//动态创建
SemaphoreHandle_t xSemaphoreCreateMutex( void )
/*返回值
NULL: 互斥信号量创建失败。
其他值: 创建成功的互斥信号量的句柄。*/
3、互斥信号的释放和获取
同上
五、递归互斥信号量
1、概念
递归互斥信号量可以看作是一个特殊的互斥信号量,已经获取了互斥信号量的任务就不能再次获取这个互斥信号量,但是递归互斥信号量不同,已经获取了递归互斥信号量的任务可以再次获取这个递归互斥信号量,而且次数不限!一个任务使用函数 xSemaphoreTakeRecursive()成功的获取了多少次递归互斥信号量就得使用函数 xSemaphoreGiveRecursive()释放多少次!比如某个任务成功的获取了 5 次递归信号量,那么这个任务也得同样的释放 5 次递归信号量。递归互斥信号量也有优先级继承的机制,所以当任务使用完递归互斥信号量以后一定要记得释放。
● 由于优先级继承的存在,就限定了递归互斥信号量只能用在任务中,不能用在中断服务函数中!
● 中断服务函数不能设置阻塞时间。要使用递归互斥信号量的话宏 configUSE_RECURSIVE_MUTEXES 必须为 1!
2、递归互斥信号量的创建
//动态方法
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void )
/*返回值:
NULL: 互斥信号量创建失败。
其他值: 创建成功的互斥信号量的句柄。*/
3、递归互斥信号量的释放与获取
递归互斥信号量_释放函数 | |
递归互斥信号量_获取函数 |
//释放函数
xSemaphoreGiveRecursive(SemaphoreHandle_t xSemaphore)//xSemaphore:要释放的信号量句柄
/*返回值:
pdPASS:递归互斥信号量释放成功
pdFAIL:递归互斥信号量释放未成功*/
//获取函数
xSemaphoreTakeRecursive(SemaphoreHandle_t xSemaphore,//xSemaphore:要释放的信号量句柄
TickType_t xBlockTime) //xBlockTime: 阻塞时间。
/*返回值:
pdPASS:递归互斥信号量获取成功
pdTRUE: 从队列中读取数据成功。//第一次获取递归互斥信号量
pdFALSE: 从队列中读取数据失败。//第一次获取递归互斥信号量 */