freertos 创建互斥量_FreeRTOS 从入门到精通10--资源管理(互斥锁与信号量)

在上一讲中奔腾的心:FreeRTOS 从入门到精通9--中断管理​zhuanlan.zhihu.com

我们探讨了中断管理在FreeRTOS中的概念和应用,在本讲中我们将讨论FreeRTOS一个十分重要的话题--资源管理(Resource Management),并借此介绍一个值得嵌入式开发者重视的现象--优先级倒置(Priority inversion )。本个系列教程也将完结于此,很高兴读者能一路支持过来并希望能有所收获。其实通过这十讲离真正的精通还是有不少差距的,应该能起到入门及抛砖引玉的作用,而我又不小心当了回标题党。感叹完毕,让我们言归正传。

什么是资源管理

资源管理(Resource Management)顾名思义,就是对资源的管理。资源在FreeRTOS中可以表现为一个负责保存数据的全局变量,一个队列上的数据等需要在任务之间共享的数据或者对UART串口的操作资源等。

资源管理主要包括两个方面内容--数据的同步机制和资源的保护机制。

数据的同步与信号量

数据的同步是指,如何能通知任务新数据的到来并同时提高CPU的利用率。假设一个简单的生产者消费者模型--有两个任务,一个任务是数据的生产者(Producer)任务,一个任务是数据的消费者(Consumer)任务,消费者任务等待生产者任务产生的数据并进行处理。按照正常的思路,消费者任务和生产者任务会轮流执行,但是如果在消费者任务执行的时候数据还没有产生的话,消费者任务的运行就是无用的,会降低CPU的使用效率。为此,FreeRTOS引入了信号量(Semaphore)概念,通过信号量的同步机制可以使消费者任务在数据还没到达的时候进入阻塞状态,并让出CPU资源给其他任务。信号量是一种同步机制,可以起到消息通知和提高CPU利用率的作用。对于信号量的操作有两种,获取信号量(taking a semaphore)和给予信号量(giving a semaphore)。在生产者消费者模型中,生产者生产数据后给予信号量,消费者获取信号量后可以处理数据。信号量又分为二进制信号量(binary semaphore)和计数信号量(counting semaphore)。二进制信号量中信号量的数目最多为1,即最多只能通知解锁一个任务;多元信号量信号量的数目可以自定义设定,可以通知解锁多个任务。

通常可以把二进制信号量(binary semaphore)类比成含有一个“钥匙”的队列,计数信号量(counting semaphore)可以类比成含有多个“钥匙”的队列。

下面介绍下信号量相关的系统API函数

SemaphoreHandle_t xSemaphoreCreateBinary( void )

通过xSemaphoreCreateBinary()函数可以创建一个二进制信号量,如果返回值是NULL的话表示创建信号量失败(通常是因为没有足够可用的堆内存)。如果返回值非空的话表示创建成功,返回值即为所创建信号量的具柄。

BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait )

xSemaphoreTake()函数表示获取信号量,函数的参数如下xSemaphore 信号量的句柄

xTicksToWait 如果信号量不可用的话任务处于阻塞状态的最长时间,设置为 portMAX_DELAY的话任务会一直处于阻塞状态直到信号量可用,设置为0的话如果信号量不可用的话会直接返回

函数的返回值为pdPASS表示成功获取了信号量,返回值为pdFALSE表示获取信号量失败

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );

xSemaphoreGive()函数表示给予信号量,函数的参数如下xSemaphore 信号量的句柄

函数的返回值为pdPASS表示成功给予信号量,返回值为pdFALSE表示给予信号量失败

在中断函数中应该使用如下版本

BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,

BaseType_t *pxHigherPriorityTaskWoken )

二进制信号量的应用伪代码

首先申明个信号量的全局变量

SemaphoreHandle_t xBinarySemaphore;

然后在main函数中创建信号量

int main(void)

{

...

xBinarySemaphore = xSemaphoreCreateBinary();

...

}

void Task_Producer(void const * argument)

{

//生产者任务负责生产数据并给予信号量 for( ;; )

{

...

if(true==produce_some_data())

{

xSemaphoreGive(xBinarySemaphore);

}

...

}

}

void Task_Consumer(void const * argument)

{

//消费者任务等待获取信号量并进行数据处理 for( ;; )

{

xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );

//下面是对数据的处理函数 ...

}

}

二进制信号量适用于在数据产生的频率比较低的场合。如果数据产生的频率较高,因为信号量最多只能保存一个信号,更多产生的数据将会直接被忽略抛弃。对此,我们需要使用计数信号量(counting semaphore)。

计数信号量相关的函数如下

SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,

UBaseType_t uxInitialCount );

xSemaphoreCreateCounting()用于创建计数信号量,函数的参数如下uxMaxCount计数信号量包含信号量的最大值

uxInitialCount 计数信号量中信号量的初始值

计数信号量创建好对信号量的操作函数和二进制信号量一样,所以应用代码参照二进制信号量

资源的保护与互斥量

资源的保护是指,如何保护资源不被多个任务同时操作。如果一个保存数据的全局变量在被一个任务操作的过程中,又被多个更高优先级的任务抢占并处理,那么最后这个支离破碎的数据将会毫无意义,破坏了数据的独立完整性;如果一个任务在使用uart串口发送数据的时候,又被多个更高优先级的任务抢占串口资源并发送数据,那么最后串口发送的数据将只是一堆乱码毫无意义,造成了系统的不稳定性。

在一定程度上,使用上文介绍的信号量不仅可以同步数据,也可以起到资源保护的作用。信号量用于资源保护的作用

在上图中,有个低优先级的任务(LP)和高优先级的任务(HP),两个任务都可以对一个资源进行操作。为了对资源进行保护采用了信号量的机制。LP首先获得信号量可以对资源进行操作,在时刻1,HP因为优先级高在内核调度中抢占了LP,在时刻2,HP想获得信号量但失败因此进入了阻塞状态。之后LP可以继续对资源操作,在时刻3执行完毕后归还了信号量。在时刻4,HP因为优先级高在内核调度中抢占了LP并获取了信号量可以对资源可以操作。

在上面这个示例中,不同优先级的任务采用信号量机制可以独立地使用资源并不会被打断,因此保护了资源。但采用信号量保护资源的话会有个弊端,有时会产生一种现象叫做优先级倒置(Priority inversion )。下图将介绍这个现象。优先级倒置

这个示例和上图示例基本相同,就是多了一个优先级介于LP和HP之间的任务(MP),问题发生在时刻3。此时低优先级的任务获得了信号量和对资源的操作权,但被优先级更高的MP抢占了并执行。最高优先级的任务HP等待低优先级任务LP返还信号量,而低优先级因为被中等优先级的任务MD抢占无法返回信号量。从外部的视角看,此时中等优先级的任务MD相当于抢占了高优先级的任务HP,而高优先级的任务HP表现起来却又像是有最低的优先级。这种不正常的现象就被称为优先级倒置。优先级倒置是采用信号量保护资源可能会产生的负面效果。

FreeRTOS为了解决资源保护的问题引入了互斥量(Mutex)。互斥量又是何方神圣,如何解决优先级倒置的问题呢?

互斥量是二进制信号量的一个变种,开启互斥量需要在头文件FreeRTOSConfig.h设置configUSE_MUTEXES 为1。互斥量和信号量的主要区别如下互斥量用于保护资源时必须要被返还

信号量用于数据同步时不需要返还

互斥量操作的相关函数

SemaphoreHandle_t xSemaphoreCreateMutex( void )

xSemaphoreCreateMutex()函数用于创建互斥量

互斥量的应用伪代码

首先申明个互斥量的全局变量

SemaphoreHandle_t xMutex;

然后在main函数中创建互斥量

int main(void)

{

...

xMutex = xSemaphoreCreateMutex();

...

}

void Function_Resource(void const * argument)

{

//要保护的资源函数 ...

xSemaphoreTake( xMutex, portMAX_DELAY );

{

//对资源的处理 ...

}

xSemaphoreGive( xMutex );

...

}

在上段代码中,如果有多个任务要调用资源函数的话,通过资源函数内部的互斥量机制可以保护资源不被其他任务打断。关于互斥量如何解决优先级倒置的问题,FreeRTOS为互斥量赋予了优先级继承(priority inheritance)的特性。优先级继承会暂时提高获得互斥量的任务的优先级,使得含有互斥量的任务的优先级和想要获取互斥量的任务中的最高优先级一样。互斥量无法彻底避免优先级倒置的问题,但能显著降低事情发生的概率。

关于资源保护的另一种方法--关键区(Critical Section)

在FreeRTOS有两个宏定义,分别是taskENTER_CRITICAL()和taskEXIT_CRITICAL()。

代码如下

taskENTER_CRITICAL();

...

taskEXIT_CRITICAL();

taskENTER_CRITICAL()宏定义会关闭所有中断包括内核切换所在的中断,taskEXIT_CRITICAL()宏定义会再次打开中断。所以处于宏定义之间的代码可以被独占地执行,这是保护资源的一种比较暴力的方式。

这个FreeRTOS的教程系列基本上到此完结了,如果你对这个系列有什么想法感受或者建议的话欢迎发表评论。之后我会考虑是否写一些FreeRTOS的实战应用教程。

你可能感兴趣的:(freertos,创建互斥量)