声明及感谢: 跟随正点原子资料学习, 在此作为学习的记录和总结
环境 keil , stm32f103
二值信号量:
二值信号量 通常用于互斥访问,或同步, 大多数用于同步(任务与任务 或 任务 与中断的同步).
和队列一样,信号量API函数允许设置一个阻塞时间, 阻塞时间是当任务获取信号量时, 由于信号量无效从而导致
任务进入阻塞态的最大时钟节拍数. 如果多个任务同时阻塞在同一个信号量上的话,那么优先级最高的那个任务会有限获得信号量. 这样当信号量有效的时候高优先级的任务会解除阻塞状态.
1. 创建二值信号量
SemaphoreHandle_t BinarySemaphore; // 二值信号量句柄
BinarySemaphore = xSemaphoreCreateBinary(); // 创建二值信号量
if(BinarySemaphore!=NULL)
{
xSemaphoreGive(BinarySemaphore); // 二值信号量创建成功以后要先释放一下
}
注意 创建成功之后需要先释放一下.
信号量的创建 由xSemaphoreCreateBinary() 函数进行创建
此函数无参数
返回值 NULL 为创建失败
其他值 : 创建成功的 二值信号量的句柄.
2. 获取信号量
分两种
第一种 : 在任务中获取信号量
函数原型
xSemaphoreTake( xSemaphore, xBlockTime )
参数 xSemaphore 信号量句柄
xBlockTime 阻塞时间
返回值 pdTRUE 获取信号量成功
pdFALSE 获取信号量失败
第二种 在中断中获取信号量
函数原型
xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )
参数 xSemaphore 信号量句柄
pxHigherPriorityTaskWoken 标记退出此函数以后是否进行任务切换, 当此值为pdTRUE的时候在退出中断服务函数之前一定要进行一次任务切换.
返回值 pdTRUE 获取信号量成功
pdFALSE 获取信号量失败
注意: 进行任务切换 即 调用 portYIELD_FROM_ISR(pxHigherPriorityTaskWoken ); 参数传 发送消息的第三个参数进去, 即可.
3. 释放信号量
释放信号量分两种
第一种 任务中释放信号量
函数原型
xSemaphoreGive( xSemaphore )
参数 xSemaphore 要释放信号量的句柄
返回值 pdPASS 释放信号量成功
errQUEUE_FULL 释放信号量失败
第二种 中断中释放信号量
函数原型
xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )
参数 xSemaphore 信号量句柄
pxHigherPriorityTaskWoken 标记退出此函数以后是否进行任务切换, 当此值为pdTRUE的时候在退出中断服务函数之前一定要进行一次任务切换.
返回值 pdPASS 释放信号量成功
errQUEUE_FULL 释放信号量失败
注意: 进行任务切换 即 调用 portYIELD_FROM_ISR(pxHigherPriorityTaskWoken ); 参数传 发送消息的第三个参数进去, 即可.
示例:
SemaphoreHandle_t BinarySemaphore; // 二值信号量句柄
BinarySemaphore = xSemaphoreCreateBinary(); // 创建二值信号量
void DataProcess_task(void *pvParameters) // 在任务函数中
{
BaseType_t err = pdFALSE;
/*** 此处省略部分代码***/
while(1)
{
if(BinarySemaphore!=NULL)
{
err = xSemaphoreTake(BinarySemaphore,portMAX_DELAY); // 获取信号量
if(err==pdTRUE) // 获取信号量成功,
{
// 处理事情.代码省略
}
}
else if(err==pdFALSE)
{
vTaskDelay(10); // 相对延时 10个时钟节拍
}
}
}
在中断服务函数中
void USART1_IRQHandler(void)
{
省略部分代码
BaseType_t xHigherPriorityTaskWoken;
if(// 满足释放信号量条件)
{
xSemaphoreGiveFromISR(BinarySemaphore,&xHigherPriorityTaskWoken); // 释放二值信号量
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);// 注意 要检测是否需要进行任务切换
}
}
上面示例就演示了, 使用二值信号量任务和中断的同步
计数型信号量
对于信号量来说, 长度为1的叫二值信号量,那么信号量长度大于1的队列就叫计数型信号量. 同二值信号量一样, 用户不需要关心队列中存储了什么数据, 只需关心 队列是否为空,
计数型信号量通常用于如下两个场合
1, 事件计数
2, 资源管理
1. 创建计数型信号量
SemaphoreHandle_t CountSemaphore; // 计数型信号量句柄
CountSemaphore = xSemaphoreCreateCounting(255,0); // 创建计数型信号量
xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )
参数 uxMaxCount: 计数信号量最大计数值, 当信号量等于此值的时候释放信号量就会失败.
uxInitialCount 计数信号量初始值
返回值 : NULL: 计数型信号量创建失败
其他值: 计数型信号量创建成功, 返回计数型信号量句柄.
2.释放计数值信号量
和释放二值信号量一样.
3. 获取计数值信号量
和获取二值信号量一样.
互斥信号量
为什么需要使用互斥信号量.
答: 有一个概念叫优先级翻转. 当一个低优先级任务和一个高优先级任务同时使用同一个信号量,而系统中还有其他中等优先级任务时, 如果低优先级任务获得了信号量, 那么高优先级的任务就会处于等待状态, 但是中等优先级的任务可以打断低优先级任务而先于高优先级任务运行(此时高优先级的任务在等待信号量, 所以不能运行,)这就是出现了优先级翻转的现象. 一旦出现优先级翻转, 那么后果很严重
想要解决优先级反转问题, 需要引入互斥信号量:
互斥信号量其实就是一个拥有优先级继承的二值信号量。
互斥信号量使用的函数和二值信号量的API函数相同, 当一个互斥信号量正在被一个低优先级的任务使用,而此时有高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。 不过这高优先级的任务会将低优先级的任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。
所以优先级集成并不能完全的消除优先级翻转,它只是尽可能的降低优先级翻转带来的影响。所以在设计之初就要避免优先级翻转的发生, 互斥信号量不能用户中断服务函数中,
原因如下:
(1) 互斥信号量有优先级继承的机制, 所以只能用在任务中, 不能被用于中断服务函数。
(2) 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态
1. 创建互斥信号量
SemaphoreHandle_t MutexSemaphore; // 互斥信号量
MutexSemaphore=xSemaphoreCreateMutex(); // 创建互斥信号量
xSemaphoreCreateMutex()
参数 : 无
返回值: NULL 互斥信号量创建失败。
其它值, 创建互斥信号量的句柄。
2.获取和释放互斥信号量
xSemaphoreTake() // 获取信号量
xSemaphoreGive() // 释放信号量。
这两个函数和二值信号量时调用是一样的,所以就简单叙述。
递归互斥信号量
递归互斥信号量可以看做是一个特殊的互斥信号量, 普通的互斥信号量是已经获取了互斥信号量的任务就不能在此获取这个互斥信号量, 但是递归互斥信号量不同, 已经获取了递归互斥信号量的任务可以在此获取这个递归互斥信号量,而且次数不限!, 一个任务使用函数 xSemaphoreTakeRecursive()成功获取了多少次递归互斥信号量就得使用函数xSemaphoreGiveRecursive() 释放多少次!, 比如某个任务成功的获取了5次递归信号量, 那么这个任务也得同样释放5次递归信号量。
递归互斥信号量也有优先级集成的机制, 所以当任务使用完递归互斥信号量后一定要释放, 同互斥信号量一样,递归互斥信号量不能用在中断服务函数中,
由于优先级继承的存在,就限定了递归互斥信号量只能用在任务中, 不能再中断服务函数中,
中断服务函数不能设置阻塞时间。
要使用递归互斥信号量的话 需要设置 configUSE_RECURSIVE_MUTEXES 必须为1
xSemaphoreCreateRecursiveMutex() // 创建递归互斥信号量
xSemaphoreTakeRecursive() // 获取递归互斥信号量
xSemaphoreGiveRecursive() // 释放递归互斥信号量