1、信号量的特性
队列可以用于传输数据:在任务之间、任务和中断之间。而有时候我们只需要传递状态,并不需要传递具体的信息,在这种情况下,我们可以使用信号量(semaphore),它更节省内存。
1.1 什么是信号量
信号:起通知作用
量:还可以用来表示资源的数量,当"量"没有限制时,它就是"计数型信号量"(Counting Semaphores),当"量"只有0、1两个取值时,它就是"二进制信号量"(Binary Semaphores)。
支持的动作:"give"给出资源,计数值加1;"take"获得资源,计数值减1
计数型信号量的典型场景:
计数:事件产生时"give"信号量,让计数值加1;处理事件时要先"take"信号量,就是获得信号量, 让计数值减1。
资源管理:要想访问资源需要先"take"信号量,让计数值减1;用完资源后"give"信号量,让计数值 加1。
信号量的"give"、"take"双方并不需要相同,可以用于生产者-消费者场合:
生产者为任务A、B,消费者为任务C、D
一开始信号量的计数值为0,如果任务C、D想获得信号量,会有两种结果: 阻塞:买不到东西咱就等等吧,可以定个闹钟(超时时间);即刻返回失败:不等
任务A、B可以生产资源,就是让信号量的计数值增加1,并且把等待这个资源的顾客唤醒 。唤醒谁?谁优先级高就唤醒谁,如果大家优先级一样就唤醒等待时间最长的人。
1.2 信号量和队列的对比
队列
|
信号量
|
可以容纳多个数据,
创建队列时有
2
部分内存
:
队列结构体、存储数
据的空间
|
只有计数值,无法容纳其他数据。
创建信号量时,只需要分配信号量结构体
|
生产者:没有空间存入数据时可以阻塞
|
生产者:用于不阻塞,计数值已经达到最大时
返回失败
|
消费者:没有数据时可以阻塞
|
消费者:没有资源时可以阻塞
|
1.3 两种信号量的对比
信号量的计数值都有限制:限定了最大值。如果最大值被限定为1,那么它就是二进制信号量;如果最 大值不是1,它就是计数型信号量。
二进制信号量
|
技术型信号量
|
被创建时初始值为
0
|
被创建时初始值可以设定
|
其他操作是一样的
|
其他操作是一样的
|
2、信号量函数
2.1 创建
使用信号量之前,要先创建,得到一个句柄;使用信号量时,要使用句柄来表明使用哪个信号量。 对于二进制信号量、计数型信号量,它们的创建函数不一样:
二进制信号量
|
计数型信号量
| |
动态创建
|
xSemaphoreCreateBinary
计数值初始值为
0
|
xSemaphoreCreateCounting
|
vSemaphoreCreateBinary(
过时了
)
计数值初始值为
1
| ||
静态创建
|
xSemaphoreCreateBinaryStatic
|
xSemaphoreCreateCountingStatic
|
/* 创建一个二进制信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinary( void );
/* 创建一个二进制信号量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t
*pxSemaphoreBuffer );
/* 创建一个计数型信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* uxMaxCount: 最大计数值
* uxInitialCount: 初始计数值
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t
uxInitialCount);
/* 创建一个计数型信号量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
* uxMaxCount: 最大计数值
* uxInitialCount: 初始计数值
* pxSemaphoreBuffer: StaticSemaphore_t结构体指针
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount,
StaticSemaphore_t,
*pxSemaphoreBuffer );
2.2 删除
对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。vSemaphoreDelete可以用来删除二进制信号量、计数型信号量。
/*
* xSemaphore: 信号量句柄,你要删除哪个信号量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
2.3 give/take
二进制信号量、计数型信号量的give、take操作函数是一样的。这些函数也分为2个版本:给任务使 用,给ISR使用。
在任务中使用
|
在
ISR
中使用
| |
give
|
xSemaphoreGive
|
xSemaphoreGiveFromISR
|
take
|
xSemaphoreTake
|
xSemaphoreTakeFromISR
|
//函数原型
//xSemaphore 信号量句柄,释放哪个信号量
//返回值
//pdTRUE表示成功,
//如果二进制信号量的计数值已经是1,再次调用此函数则返回失败;
//如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
//函数原型
//xSemaphore 信号量句柄,释放哪个信号量
//pxHigherPriorityTaskWoken 如果释放信号量导致更高优先级的任务变为了就绪态,
//则*pxHigherPriorityTaskWoken = pdTRUE
//返回值
//pdTRUE表示成功,如果二进制信号量的计数值已经是1,再次调用此函数则返回失败;
//如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
//函数原型
//xSemaphore 信号量句柄,获取哪个信号量
//xTicksToWait 如果无法马上获得信号量,阻塞一会:0:不阻塞,马上返回;
//portMAX_DELAY: 一直阻塞直到成功;
//其他值: 阻塞的Tick个数,可以使用 pdMS_TO_TICKS() 来指定阻塞时间为若干ms
//返回值
//pdTRUE表示成功
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);
//函数原型
//xSemaphore 信号量句柄,获取哪个信号量
//pxHigherPriorityTaskWoken 如果获取信号量导致更高优先级的任务变为了就绪态,
//则*pxHigherPriorityTaskWoken = pdTRUE
//返回值
//pdTRUE表示成功
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
3、示例代码
3.1 同步
根据错误提示要#define configUSE_COUNTING_SEMAPHORES 1
static int sum = 0;
static volatile int flagCalcEnd = 0;
static SemaphoreHandle_t xSemCalc;
void Task1Function( void * param)
{
volatile int i = 0; //使用volatile修饰,让系统不要去优化这个变量
while(1){
for(i = 0; i < 10000000; i++){
sum++;
}
xSemaphoreGive(xSemCalc);
vTaskDelete(NULL);
}
}
void Task2Function( void * param)
{
while(1){
flagCalcEnd = 0;
xSemaphoreTake(xSemCalc, portMAX_DELAY);
flagCalcEnd = 1;
printf("sum = %d\r\n", sum);
}
}
//main函数中
xSemCalc = xSemaphoreCreateCounting(10, 0);
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
使用技术性信号量达到同步的效果,可见在这里完成10000000次计数也只是耗时2s。
3.2 互斥
static SemaphoreHandle_t xSemUART;
void TaskGenericFunction( void * param)
{
while(1){
xSemaphoreTake(xSemUART, portMAX_DELAY); //在使用串口前先take这个信号量
printf("%s\r\n", (char *)param);
xSemaphoreGive(xSemUART); //用完释放掉
vTaskDelay(1);
}
}
//main函数中
xSemUART = xSemaphoreCreateBinary();
xSemaphoreGive(xSemUART); //二进制信号量初始值为0,这里先给个1,表明这个串口是可以使用的
xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 4 is running", 1, NULL);
使用二进制信号量达到互斥的效果,任务三和任务四互斥的使用串口。