文章目录
- 前言
- 一、Freertos的信号量是什么?
- 二、二进制信号量和计数型信号量是什么?
- 三、信号量初步了解
- 1.二进制信号量的使用
- 2.give和take是什么?
- 3.为什么需要动态和静态创建的方式?
- 四、二进制信号量示例代码
- 总结
前言
本系列基于stm32
系列单片机来使用freerots
FreeRTOS是一个流行的实时操作系统,提供了许多功能强大的特性,其中包括信号量。信号量是一种在并发编程中常用的同步机制,用于实现资源的共享和互斥访问。本文将介绍FreeRTOS中的信号量,包括其基本概念、用法和示例。
一、Freertos的信号量是什么?
当我们在编写多任务并发程序时,经常会遇到需要多个任务共享资源的情况,比如共享一个打印机、共享一个数据缓冲区等。在这种情况下,为了保证数据的正确性和避免冲突,我们需要一种机制来控制任务对资源的访问。FreeRTOS提供了信号量来解决这个问题。
可以将信号量看作是一种特殊的计数器。它用来记录资源的可用数量。当某个任务想要使用资源时,它需要先获取(acquire)信号量。如果信号量的计数器大于0,那么该任务就可以继续执行,并且计数器会递减。这样其他任务在同一时间就无法获取相同的资源。当任务使用完资源后,它需要释放(release)信号量,这时计数器会递增,其他任务就有机会获取资源了。
实际上,信号量可以用于两种情况:互斥访问和任务同步。在互斥访问中,信号量起到了一种锁的作用,确保同一时间只有一个任务能够访问共享资源,这样可以保证数据的一致性。而在任务同步中,信号量可以用来控制多个任务的执行顺序,一个任务在等待某个事件发生时,可以等待一个信号量,当其他任务触发了这个事件并释放了信号量,等待的任务就能继续执行了。
在FreeRTOS中,我们可以使用一些函数来创建和操作信号量。通过合理地使用信号量,我们可以确保多个任务能够安全地共享资源,并且按照设计的顺序进行执行,从而提高系统的并发性能。
二、二进制信号量和计数型信号量是什么?
当我们使用FreeRTOS的信号量时,有两种常见的类型:二进制信号量和计数型信号量。
二进制信号量(Binary Semaphore):
二进制信号量就像一把开关,只有两个状态:开和关。它的计数器只能是0或1。当一个任务获取二进制信号量时,如果它是开的(计数器为1),那么它可以继续执行;如果它是关的(计数器为0),那么获取操作会被阻塞,直到有其他任务释放了信号量让它变为开。这种信号量常用于实现互斥访问,确保同一时间只有一个任务可以访问共享资源。
计数型信号量(Counting Semaphore):
计数型信号量的计数器不仅仅只是0和1,它可以是任意非负整数。当一个任务获取计数型信号量时,如果计数器大于0,那么计数器会减1,并且获取操作会立即返回;如果计数器为0,那么获取操作会被阻塞,直到有其他任务释放了信号量让计数器变为非零。这种信号量常用于表示资源的可用数量,比如空闲的内存块或可用的任务槽位。
区别:
二进制信号量只有两个状态(开和关),而计数型信号量的计数器可以是任意非负整数。
二进制信号量常用于互斥访问,确保同一时间只有一个任务可以访问共享资源。计数型信号量通常用于表示可用资源的数量。
二进制信号量的计数器只能通过获取和释放操作切换状态。计数型信号量的计数器可以通过获取和释放操作增加或减少。
二进制信号量只能实现互斥访问,而计数型信号量不仅可以实现互斥访问,还可以用于任务同步、事件触发等应用场景。
要根据具体的需求选择信号量的类型,以及合适的获取和释放操作,以确保任务间的资源共享和同步能够正常进行。
三、信号量初步了解
1.二进制信号量的使用
- 动态创建函数xSemaphoreCreateBinary()
函数原型如下:
/* 创建一个二进制信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinary( void );
- 静态创建函数xSemaphoreCreateBinaryStatic()
函数原型如下:
/* 创建一个二进制信号量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t
*pxSemaphoreBuffer );
- 二进制信号量的删除函数
对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。
vSemaphoreDelete可以用来删除二进制信号量、计数型信号量,函数原型如下:
/*
* xSemaphore: 信号量句柄,你要删除哪个信号量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
- give/take函数
在任务中使用:
1.xSemaphoreGive
函数原型如下:
/*参数为要give的信号量,
返回值:pdTRUE表示成功,
如果二进制信号量的计数值已经是1,再次调用此函数则返回失败;
如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败*/
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
2.xSemaphoreTake
函数原型如下:
/*参数为要take的信号量,等待的时间
返回值:pdTRUE表示成功
*/
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);
在中断中使用:
1.xSemaphoreGiveFromISR
函数原型如下:
/*参数为要give的信号量,如果释放信号量导致更高优先级的任务变为了就绪态,
则*pxHigherPriorityTaskWoken = pdTRUE*/
/*返回值:pdTRUE表示成功,
如果二进制信号量的计数值已经是1,再次调用此函数则返回失
败;
如果计数型信号量的计数值已经是最大值,再次调用此函数则返
回失败*/
BaseType_t xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
2.xSemaphoreTakeFromISR
函数原型如下:
/*xSemaphore 信号量句柄,获取哪个信号量
pxHigherPriorityTaskWoken
如果获取信号量导致更高优先级的任务变为了就绪态,
则*pxHigherPriorityTaskWoken = pdTRUE
返回值 pdTRUE表示成功
*/
BaseType_t xSemaphoreTakeFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
2.give和take是什么?
通过上图可以看到任务1先take,然后二进制信号量变成1,当任务2需要take时,发现二进制信号量是1,则任务等待任务1give后,二进制信号量变成0后任务2才take到信号量,然后信号量又变成1,从此往复。
3.为什么需要动态和静态创建的方式?
在前面的博客我们看到了很多函数,在queue中,他有动态创建和静态创建,那freertos的编写代码的人难道没事找事吗?肯定不然,
下面就是freertos代码编写者为什么需要这样做的原因:
FreeRTOS提供了两种方式来创建任务、队列、信号量等内核对象:动态创建和静态创建。
动态创建:
动态创建是在运行时动态分配内存来创建内核对象。通过调用API函数,FreeRTOS会在堆上为内核对象动态分配内存空间。
动态创建灵活,可以根据具体的需求动态调整内核对象的数量和大小。
动态创建可以在运行时创建和删除内核对象,可以根据系统的动态需求进行动态管理。
动态创建适用于需要动态管理内存的场景,比如对象的数量和大小在运行时发生变化,或者内存资源受限的情况。
静态创建:
静态创建是在编译时静态分配内存来创建内核对象。用户需要提前为内核对象预留内存空间,并在创建时使用静态分配的内存空间。
静态创建可以避免在运行时进行内存分配和释放的开销,节省了系统的运行时间和资源消耗。
静态创建需要提前估计和分配内存空间,并在编译时固定了内核对象的数量和大小。
静态创建适用于内存资源相对稳定,内核对象数量和大小较为确定的场景,可以提高系统的可靠性和性能。
动态创建和静态创建各有其适用场景。动态创建适合需要在运行时动态管理内核对象的情况,而静态创建适合内存资源相对稳定,对象数量和大小可以在编译时确定的情况。选择合适的创建方式可以根据系统的需求、资源约束和代码设计来做出权衡。
四、二进制信号量示例代码
uint32_t count = 0;
SemaphoreHandle_t s;
void Task(void *p)
{
while(1)
{
xSemaphoreTake(s,pdMS_TO_TICKS(1000));//信号量1
printf("Count:%d\r\n",count);
xSemaphoreGive(s);//信号量变成0
vTaskDelay(pdMS_TO_TICKS(500));
}
vTaskDelete(NULL);
vvSemaphoreDelete(s);//在任务的外面删除,如果删除了,可能是出错了!!!
}
void Task2(void *p)
{
while(1)
{
xSemaphoreTake(s,pdMS_TO_TICKS(1000));//信号量1
count++;
xSemaphoreGive(s);//信号量变成0
vTaskDelay(1);
}
vTaskDelete(NULL);
}
void TaskTest(void)
{
s = xSemaphoreCreateBinary( );
xTaskCreate(Task,"MyTask",50,&count,1,NULL);
xTaskCreate(Task2,"MyTask2",50,&count,3,NULL);
vTaskStartScheduler();
}
注意:如果没有信号量,可以就会冲突
总结
信号量是FreeRTOS中用于实现资源的共享和互斥访问的重要机制。本文介绍了信号量的基本概念、创建和初始化、获取和释放操作以及用途。通过合理地使用信号量,可以提高系统并发性能,并确保共享资源的正确访问。在实际应用中,根据具体需求选择适当的信号量类型和操作方式,可以更好地利用FreeRTOS的功能来设计健壮的并发应用。