信号量用于控制共享资源访问的场景相当于一个上锁机制,代码只有获得了这个锁的钥匙 才能够执行 ,中断不能执行太长太复杂的流程,可以通过上锁机制放入任务中。
二值信号量
二值信号量简介
二值信号量通常用于互斥访问或同步,二值信号量和互斥信号量非常类似,但是还是有一 些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。因此二值信号 另更适合用于同步(任务与任务或任务与中断的同步),而互斥信号量适合用于简单的互斥访问。
创建二值信号量
函数 vSemaphoreCreateBinary ()
此函数是老版本 FreeRTOS 中的创建二值信号量函数,新版本已经不再使用了,新版本的 FreeRTOS 使用 xSemaphoreCreateBinary()来替代此函数,具体创建过程是由函数 xQueueGenericCreate()来完成的,在文件 semphr.h 中有如下定义:
/*
xSemaphore:保存创建成功的二值信号量句柄。
return:
NULL: 二值信号量创建失败。
其他值: 二值信号量创建成功。
*/
void vSemaphoreCreateBinary( SemaphoreHandle_t xSemaphore );
函数 xSemaphoreCreateBinaryStatic()
此函数也是创建二值信号量的,只不过使用此函数创建二值信号量的话信号量所需要的 RAM 需要由用户来分配,此函数是个宏,具体创建过程是通过函数 xQueueGenericCreateStatic() 来完成的,函数原型如下:
/*
pxSemaphoreBuffer:此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
return:
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量句柄
*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );
释放信号量
函数 xSemaphoreGive()
此函数用于释放二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正释放信 号量的过程是由函数 xQueueGenericSend()来完成的,函数原型如下:
/*
xSemaphore:要释放的信号量句柄。
return:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败。
*/
BaseType_t xSemaphoreGive( xSemaphore )
函数 xSemaphoreGiveFromISR()
此函数用于在中断中释放信号量,此函数只能用来释放二值信号量和计数型信号量,绝对 不能用来在中断服务函数中释放互斥信号量!此函数是一个宏,真正执行的是函数 xQueueGiveFromISR(),此函数原型如下:
/*
xSemaphore: 要释放的信号量句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这
三个函数来设置的,用户不用进行设置,用户只需要提供一
个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退
出中断服务函数之前一定要进行一次任务切换。
return:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败。
*/
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t * pxHigherPriorityTaskWoken)
获取信号量
函数 xSemaphoreTake()
此函数用于获取二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正获取信 号量的过程是由函数 xQueueGenericReceive ()来完成的,函数原型如下:
/*
xSemaphore:要获取的信号量句柄。
xBlockTime: 阻塞时间。
return:
pdTRUE: 获取信号量成功。
pdFALSE: 超时,获取信号量失败。
*/
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,
TickType_t xBlockTime)
函数 xSemaphoreTakeFromISR ()
此函数用于在中断服务函数中获取信号量,此函数用于获取二值信号量和计数型信号量,
绝 对 不 能 使 用 此 函 数 来 获 取 互 斥 信 号 量 ! 此 函 数 是 一 个 宏 , 真 正 执 行 的 是 函 数
xQueueReceiveFromISR (),此函数原型如下:
/*
xSemaphore: 要获取的信号量句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这
三个函数来设置的,用户不用进行设置,用户只需要提供一
个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退
出中断服务函数之前一定要进行一次任务切换。
return:
pdPASS: 获取信号量成功。
pdFALSE: 获取信号量失败。
*/
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,
BaseType_t * pxHigherPriorityTaskWoken)
任务函数
任务循环中不断获取信号量,如果没有获取到就阻塞
// 二值信号量句柄
SemaphoreHandle_t BinarySemaphore;//二值信号量句柄
// 创建二值信号量
BinarySemaphore=xSemaphoreCreateBinary();
if(BinarySemaphore!=NULL) {
BaseType_t x_return = pdFALSE;
// 获取信号量
x_return = xSemaphoreTake(BinarySemaphore,portMAX_DELAY);
// 获取信号量成功
if(x_return == pdTRUE)
}
串口接收完成之后释放信号量
BaseType_t xHigherPriorityTaskWoken;
// 释放二值信号量
if((USART_RX_STA&0x8000)&&(BinarySemaphore!=NULL)) {
// 释放二值信号量
xSemaphoreGiveFromISR(BinarySemaphore, &xHigherPriorityTaskWoken);
// 如果需要的话进行一次任务切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
计数型信号量
计数型信号量简介
计数型信号量就是长度大于 1 的队列。同二值信号量一样,用户不需要关心队列中存储了什么 数据,只需要关心队列是否为空即可。计数型信号量通常用于如下两个场合:
1、事件计数
在这个场合中,每次事件发生的时候就在事件处理函数中释放信号量(增加信号量的计数 值),其他任务会获取信号量(信号量计数值减一,信号量值就是队列结构体成员变量 uxMessagesWaiting)来处理事件。在这种场合中创建的计数型信号量初始计数值为 0。
2、资源管理
在这个场合中,信号量值代表当前资源的可用数量,比如停车场当前剩余的停车位数量。 一个任务要想获得资源的使用权,首先必须获取信号量,信号量获取成功以后信号量值就会减 一。当信号量值为 0 的时候说明没有资源了。当一个任务使用完资源以后一定要释放信号量, 释放信号量以后信号量值会加一。在这个场合中创建的计数型信号量初始值应该是资源的数量, 比如停车场一共有 100 个停车位,那么创建信号量的时候信号量值就应该初始化为 100。
创建计数型信号量
函数 xSemaphoreCreateCounting()
此函数用于创建一个计数型信号量,所需要的内存通过动态内存管理方法分配。此函数本质是一个宏,真正完成信号量创建的是函数 xQueueCreateCountingSemaphore(),此函数原型如 下:
/*
uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
uxInitialCount: 计数信号量初始值。
return:
NULL: 计数型信号量创建失败。
其他值: 计数型信号量创建成功,返回计数型信号量句柄。
*/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount )
函数 xSemaphoreCreateCountingStatic()
此函数也是用来创建计数型信号量的,使用此函数创建计数型信号量的时候所需要的内存 需要由用户分配。此函数也是一个宏,真正执行的是函数xQueueCreateCountingSemaphoreStatic(), 函数原型如下:
/*
uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
uxInitialCount: 计数信号量初始值。
pxSemaphoreBuffer:指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
返回值:
NULL: 计数型信号量创建失败。
其他值: 计数型号量创建成功,返回计数型信号量句柄。
*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount,
StaticSemaphore_t * pxSemaphoreBuffer )
释放和获取计数信号量
计数型信号量的释放和获取与二值信号量相同
任务函数
释放信号量的话信号量值就会增加,获取信号量的话信号量值就会减少, 当信号量值减为 0 的时候就表示信号量无效,任务 SemapTake_task()获取信号量失败,任务因此进入阻塞态
// 计数型信号量句柄
SemaphoreHandle_t CountSemaphore;//计数型信号量
// 创建计数型信号量
CountSemaphore = xSemaphoreCreateCounting(255,0);
// 释放计数型信号量
x_return = xSemaphoreGive(CountSemaphore);
// 获取计数型信号量值
semavalue = uxSemaphoreGetCount(CountSemaphore);
// 等待数值信号量
xSemaphoreTake(CountSemaphore,portMAX_DELAY);
// 获取数值信号量值
semavalue=uxSemaphoreGetCount(CountSemaphore);
优先级翻转
在使用二值信号量的时候会遇到很常见的一个问题——优先级翻转,优先级翻转在可剥夺 内核中是非常常见的,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能 会导致严重的后果
互斥信号量
互斥信号量其实就是一个拥有优先级继承
的二值信号量
,在同步的应用中(任务与任务或中 断与任务之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。在 互斥访问中互斥信号量相当于一个钥匙,当任务想要使用资源的时候就必须先获得这个钥匙, 当使用完资源以后就必须归还这个钥匙,这样其他的任务就可以拿着这个钥匙去使用资源。
互斥信号量使用和二值信号量相同的 API 操作函数,所以互斥信号量也可以设置阻塞时间, 不同于二值信号量的是互斥信号量具有优先级继承的特性。
当一个互斥信号量正在被一个低优 先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。不 过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是 优先级继承。优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并且将已经出现的 “优先级翻转”的影响降到最低。
优先级继承并不能完全的消除优先级翻转,它只是尽可能的降低优先级翻转带来的影响。 硬实时应用应该在设计之初就要避免优先级翻转的发生。
互斥信号量不能
用于中断服务函数中, 原因如下:
- 互斥信号量有优先级继承的机制,所以只能用在任务中,不能用于中断服务函数。
- 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
创建互斥信号量
函数 xSemaphoreCreateMutex()
此函数用于创建一个互斥信号量,所需要的内存通过动态内存管理方法分配。此函数本质是一个宏,真正完成信号量创建的是函数 xQueueCreateMutex(),此函数原型如下:
/*
return:
NULL: 互斥信号量创建失败。
其他值: 创建成功的互斥信号量的句柄。
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void )
函数 xSemaphoreCreateMutexStatic()
此函数也是创建互斥信号量的,只不过使用此函数创建互斥信号量的话信号量所需要的 RAM 需要由用户来分配,此函数是个宏,具体创建过程是通过函数 xQueueCreateMutexStatic () 来完成的,函数原型如下:
/*
pxMutexBuffer:此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
return:
NULL: 互斥信号量创建失败。
其他值: 创建成功的互斥信号量的句柄
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer )
释放互斥信号量
释 放 互 斥 信 号 量 的 时 候 和 二 值 信 号 量 、 计 数 型 信 号 量 一 样 , 都 是 用 的 函 数 xSemaphoreGive()
获取互斥信号量
获取互斥信号量的函数同获取二值信号量和计数型信号量的函数相同,都是 xSemaphoreTake()(实际执行信号量获取的函数是 xQueueGenericReceive()),
任务函数
// 开始任务任务函数
void start_task(void *pvParameters) {
taskENTER_CRITICAL(); //进入临界区
//创建互斥信号量
MutexSemaphore=xSemaphoreCreateMutex(); (1)
//创建高优先级任务
//创建中等优先级任务
//创建低优先级任务
taskEXIT_CRITICAL(); //退出临界区
}
//高优先级任务的任务函数
void high_task(void *pvParameters) {
u8 num;
while(1) {
vTaskDelay(500); //延时 500ms,也就是 500 个时钟节拍
num++;
printf("high task Pend Sem\r\n");
xSemaphoreTake(MutexSemaphore,portMAX_DELAY); //获取互斥信号量 (2)
printf("high task Running!\r\n");
xSemaphoreGive(MutexSemaphore); //释放信号量 (3)
vTaskDelay(500); //延时 500ms,也就是 500 个时钟节拍
}
}
//中等优先级任务的任务函数
void middle_task(void *pvParameters) {
u8 num;
while(1) {
num++;
printf("middle task Running!\r\n");
vTaskDelay(1000); //延时 1s,也就是 1000 个时钟节拍
}
}
//低优先级任务的任务函数
void low_task(void *pvParameters) {
static u32 times;
while(1) {
xSemaphoreTake(MutexSemaphore,portMAX_DELAY); //获取互斥信号量 (4)
printf("low task Running!\r\n");
// 模拟低优先级任务占用互斥信号量 (5)
for(times=0;times<20000000;times++) {
taskYIELD(); //发起任务调度
}
xSemaphoreGive(MutexSemaphore); //释放互斥信号量 (6)
vTaskDelay(1000); //延时 1s,也就是 1000 个时钟节拍
}
}
递归互斥信号量
递归互斥信号量简介
递归互斥信号量可以看作是一个特殊的互斥信号量,已经获取了互斥信号量的任务就不能 再次获取这个互斥信号量,但是递归互斥信号量不同,已经获取了递归互斥信号量的任务可以 再次获取这个递归互斥信号量,而且次数不限!
一个任务使用函数 xSemaphoreTakeRecursive() 成功的获取了多少次递归互斥信号量就得使用函数 xSemaphoreGiveRecursive()释放多少次!
创建互斥信号量
函数 xSemaphoreCreateRecursiveMutex()
此函数用于创建一个递归互斥信号量,所需要的内存通过动态内存管理方法分配。此函数 本质是一个宏,真正完成信号量创建的是函数 xQueueCreateMutex (),此函数原型如下:
/*
return:
NULL: 互斥信号量创建失败。
其他值: 创建成功的互斥信号量的句柄。
*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void )
函数 xSemaphoreCreateRecursiveMutexStatic()
/*
pxMutexBuffer:此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
return:
NULL: 互斥信号量创建失败。
其他值: 创建成功的互斥信号量的句柄。
*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutexStatic( StaticSemaphore_t *pxMutexBuffer )
释放递归互斥信号量
递归互斥信号量有专用的释放函数:xSemaphoreGiveRecursive(),此函数为宏,如下:
#define xSemaphoreGiveRecursive( xMutex ) xQueueGiveMutexRecursive( ( xMutex ) )
获取递归互斥信号量
递归互斥信号量的获取使用函数 xSemaphoreTakeRecursive(),此函数是个宏,定义如下:
#define xSemaphoreTakeRecursive( xMutex, xBlockTime )
xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )
任务函数
SemaphoreHandle_t RecursiveMutex; //递归互斥信号量句柄
//某个任务中创建一个递归互斥信号量
void vATask( void * pvParameters ) {
//没有创建创建递归互斥信号量之前不要使用!
RecursiveMutex = xSemaphoreCreateRecursiveMutex(); //创建递归互斥信号量
for( ;; ) {
/************任务代码**************/
}
}
//任务调用的使用递归互斥信号量的功能函数。
void vAFunction( void ) {
/**********其他处理代码*****************/
if( xMutex != NULL ) {
// 获取递归互斥信号量,阻塞时间为 10 个节拍
if( xSemaphoreTakeRecursive( RecursiveMutex, 10 ) == pdTRUE ) {
/***********其他处理过程*************/
// 这里为了演示,所以是顺序的获取递归互斥信号量,但是在实际的代码中肯定
// 不是这么顺序的获取的,真正的代码中是混合着其他程序调用的。
xSemaphoreTakeRecursive( RecursiveMutex, ( TickType_t ) 10 );
xSemaphoreTakeRecursive( RecursiveMutex, ( TickType_t ) 10 );
// 任务获取了三次递归互斥信号量,所以就得释放三次!
xSemaphoreGiveRecursive( RecursiveMutex);
xSemaphoreGiveRecursive( RecursiveMutex);
xSemaphoreGiveRecursive( RecursiveMutex);
// 递归互斥信号量释放完成,可以被其他任务获取了
} else {
/**********递归互斥信号量获取失败***********/
}
}
}