FreeRTOS学习笔记6——Resource Management
资源管理Resources Management
临界区域 和 调度挂起
临界区域 Basic Critical Sections
调度挂起Suspending (or Locking) the Scheduler
互斥锁
xSemaphoreCreateMutex() API
优先级反转
优先级继承
死锁
循环互斥锁
API
examples
互斥锁与任务调度
Gatekeeper Tasks
使用tick hook function
examples
临界区域 和 调度挂起
临界区域 Basic Critical Sections
调度挂起Suspending (or Locking) the Scheduler
互斥锁
xSemaphoreCreateMutex() API
优先级反转
优先级继承
死锁
循环互斥锁
API
examples
互斥锁与任务调度
Gatekeeper Tasks
使用tick hook function
examples
资源管理Resources Management
临界区域 和 调度挂起
临界区域 Basic Critical Sections
taskENTER_CRITICAL()
与taskEXIT_CRITICAL()
成对出现- taskENTER_CRITICAL() 会禁止task调度,只允许中断逻辑优先级大于
configMAX_SYSCALL_INTERRUPT_PRIORITY
的中断,打断 - 在ISR中对应的interrupt safe version API 为
taskENTER_CRITICAL_FROM_ISR()
和taskEXIT_CRITICAL_FROM_ISR()
。taskENTER_CRITICAL_FROM_ISR()返回的参数,必需传给taskEXIT_CRITICAL_FROM_ISR()
/* Declare a variable in which the return value from taskENTER_CRITICAL_FROM_ISR()
will be saved. */
UBaseType_t uxSavedInterruptStatus;
/* This part of the ISR can be interrupted by any higher priority interrupt. */
/* Use taskENTER_CRITICAL_FROM_ISR() to protect a region of this ISR. Save the
value returned from taskENTER_CRITICAL_FROM_ISR() so it can be passed into the
matching call to taskEXIT_CRITICAL_FROM_ISR(). */
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
/* This part of the ISR is between the call to taskENTER_CRITICAL_FROM_ISR() and
taskEXIT_CRITICAL_FROM_ISR(), so can only be interrupted by interrupts that have
a priority above that set by the configMAX_SYSCALL_INTERRUPT_PRIORITY constant. */
/* Exit the critical section again by calling taskEXIT_CRITICAL_FROM_ISR(),
passing in the value returned by the matching call to
taskENTER_CRITICAL_FROM_ISR(). */
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
/* This part of the ISR can be interrupted by any higher priority interrupt. */
调度挂起Suspending (or Locking) the Scheduler
- 调度挂起禁止了任务切换,但是允许中断发生
void vTaskSuspendAll( void );
BaseType_t xTaskResumeAll( void );
// 返回值 pdTRUE or pdFALSE
互斥锁
Mutexes (and Binary Semaphores)
可以用二进制信号量实现互斥锁
- configUSE_MUTEXES 设置为 1
xSemaphoreCreateMutex() API
FreeRTOS v9.0, 引入了xSemaphoreCreateMutexStatic(),用于静态生成互斥锁。
SemaphoreHandle_t xSemaphoreCreateMutex( void );
/**
* example
*/
xMutex = xSemaphoreCreateMutex();
xSemaphoreTake( xMutex, portMAX_DELAY );
/****User Code*****/
xSemaphoreGive( xMutex );
优先级反转
一个高优先级的task等待低优先级的task释放互斥锁,称为优先级反转
Priority Inversion
- 最坏情况 当一个高优先级等待低优先级task释放互斥锁时,低优先级在释放互斥锁前,被中优先级抢占,如下图所示:
- 优先级反转问题不小,但在小的嵌入式系统中,可以在设计阶段想好资源如何获取,以避免这样的问题。
优先级继承
Priority Inheritance
- 在FreeRTOS中,
互斥锁
与二进制信号量
用法很相似,但是互斥锁有优先级继承
的机制 而 二进制信号量没有。优先级继承
是一种机制, 减少优先级反转的消极影响优先级继承
并不能解决优先级反转,而仅仅是减少它的影响,通过保证优先级反转总是有时间边界的。优先级继承
会使得时间系统时序变得复杂,所以不能依赖于此来保证系统的正确运行。
优先级继承
是 短暂提升互互斥锁的持有者(task)的优先级至试图获取互斥锁的task中优级级最的优先级- 持有互斥锁的低优级task 继承了等待此互斥锁的task的优先级
死锁
死锁是用互斥锁的另一个陷阱(pitfall)
- 死锁一般发生在,两个task都在等待对方释放自己等待的资源时。
- 和优先级反转一样,避免死锁的最好的方法是在设计阶段考虑潜在可能发生的情况
- 实际项目中要避免无限等待一个互斥锁, 可以用一个比预计等待互斥锁稍长一点的超时时间
事实上死锁在小系统中并不是大问题,因为很容易能找到问题并解决掉死锁区域。
循环互斥锁
循环互斥锁
可以解决同一个task重复去试图获取同一互斥锁时的死锁问题
一个recursive mutex
可以taken
多次同一个mutex, 只有当每个take
都执行一次give
后,才能完成释放。
API
- xSemaphoreCreateRecursiveMutex()
- xSemaphoreTakeRecursive()
- xSemaphoreGiveRecursive()
examples
const TickType_t xMaxBlock20ms = pdMS_TO_TICKS( 20 );
/* Recursive mutexes are variables of type SemaphoreHandle_t. */
SemaphoreHandle_t xRecursiveMutex;
/* Before a recursive mutex is used it must be explicitly created. */
xRecursiveMutex = xSemaphoreCreateRecursiveMutex();
//pdPASS,pdFALSE
xSemaphoreTakeRecursive( xRecursiveMutex, xMaxBlock20ms )
xSemaphoreGiveRecursive( xRecursiveMutex );
互斥锁与任务调度
Mutexes and Task Scheduling
- 如果优先级不同的task使用同一个mutex时,如果低优先级占用了mutex,当低优先级释放mutex时, 高优先级会立即抢占低优先级task,并占用mutex.
- 如果优先级相同的task使用同一个mutex时,task1占用mutex,task2进入阻塞状态,当task1释放mutex时,task2并不会抢占task1运行,而是由阻塞状态进入准备状态。等待下一个时间片,进入运行状态。由下图所示
- a task that uses a mutex in a tight loop
/* The implementation of a task that uses a mutex in a tight loop. The task creates
a text string in a local buffer, then writes the string to a display. Access to the
display is protected by a mutex. */
void vATask( void *pvParameter )
{
extern SemaphoreHandle_t xMutex;
char cTextBuffer[ 128 ];
for( ;; )
{
/* Generate the text string – this is a fast operation. */
vGenerateTextInALocalBuffer( cTextBuffer );
/* Obtain the mutex that is protecting access to the display. */
xSemaphoreTake( xMutex, portMAX_DELAY );
/* Write the generated text to the display – this is a slow operation. */
vCopyTextToFrameBuffer( cTextBuffer );
/* The text has been written to the display, so return the mutex. */
xSemaphoreGive( xMutex );
}
}
这样的tingt loop,两个task得不到平等的支行时间
上图中task1只有在新的时间片下,且task2没有占用的情况下,才能占用mutex
为了避免以上图的情况,可以在调用xSemaphoreGive()
后执行taskYIELD()
。
void vFunction( void *pvParameter )
{
extern SemaphoreHandle_t xMutex;
char cTextBuffer[ 128 ];
TickType_t xTimeAtWhichMutexWasTaken;
for( ;; )
{
/* Generate the text string – this is a fast operation. */
vGenerateTextInALocalBuffer( cTextBuffer );
/* Obtain the mutex that is protecting access to the display. */
xSemaphoreTake( xMutex, portMAX_DELAY );
/* Record the time at which the mutex was taken. */
xTimeAtWhichMutexWasTaken = xTaskGetTickCount();
/* Write the generated text to the display – this is a slow operation. */
vCopyTextToFrameBuffer( cTextBuffer );
/* The text has been written to the display, so return the mutex. */
xSemaphoreGive( xMutex );
/* If taskYIELD() was called on each iteration then this task would only ever
remain in the Running state for a short period of time, and processing time
would be wasted by rapidly switching between tasks. Therefore, only call
taskYIELD() if the tick count changed while the mutex was held. */
if( xTaskGetTickCount() != xTimeAtWhichMutexWasTaken )
{
taskYIELD();
}
}
}
Gatekeeper Tasks
Gatekeeper Tasks
提供了一种简单的方法实施互斥操作,而没有优先级反转或者死锁的问题。
Gatekeeper Tasks
是资源的唯一拥有者,其它task想要获取资源都要通过Gatekeeper Tasks
提供的服务来间接获取资源。
使用tick hook function
- Set
configUSE_TICK_HOOK
to 1 inFreeRTOSConfig.h
. - void vApplicationTickHook( void );
examples
static void prvStdioGatekeeperTask( void *pvParameters )
{
char *pcMessageToPrint;
/* This is the only task that is allowed to write to standard out. Any other
task wanting to write a string to the output does not access standard out
directly, but instead sends the string to this task. As only this task accesses
standard out there are no mutual exclusion or serialization issues to consider
within the implementation of the task itself. */
for( ;; )
{
/* Wait for a message to arrive. An indefinite block time is specified so
there is no need to check the return value – the function will only return
when a message has been successfully received. */
xQueueReceive( xPrintQueue, &pcMessageToPrint, portMAX_DELAY );
/* Output the received string. */
printf( "%s", pcMessageToPrint );
fflush( stdout );
/* Loop back to wait for the next message. */
}
}
static void prvPrintTask( void *pvParameters )
{
int iIndexToString;
const TickType_t xMaxBlockTimeTicks = 0x20;
/* Two instances of this task are created. The task parameter is used to pass
an index into an array of strings into the task. Cast this to the required
type. */
iIndexToString = ( int ) pvParameters;
for( ;; )
{
/* Print out the string, not directly, but instead by passing a pointer to
the string to the gatekeeper task via a queue. The queue is created before
the scheduler is started so will already exist by the time this task executes
for the first time. A block time is not specified because there should
always be space in the queue. */
xQueueSendToBack( xPrintQueue, &( pcStringsToPrint[ iIndexToString ] ), 0 );
/* Wait a pseudo random time. Note that rand() is not necessarily reentrant,
but in this case it does not really matter as the code does not care what
value is returned. In a more secure application a version of rand() that is
known to be reentrant should be used - or calls to rand() should be protected
using a critical section. */
vTaskDelay( ( rand() % xMaxBlockTimeTicks ) );
}
}
void vApplicationTickHook( void )
{
static int iCount = 0;
/* Print out a message every 200 ticks. The message is not written out directly,
but sent to the gatekeeper task. */
iCount++;
if( iCount >= 200 )
{
/* As xQueueSendToFrontFromISR() is being called from the tick hook, it is
not necessary to use the xHigherPriorityTaskWoken parameter (the third
parameter), and the parameter is set to NULL. */
xQueueSendToFrontFromISR( xPrintQueue,
&( pcStringsToPrint[ 2 ] ),
NULL );
/* Reset the count ready to print out the string again in 200 ticks time. */
iCount = 0;
}
}
main.c
/* Define the strings that the tasks and interrupt will print out via the gatekeeper. */
static char *pcStringsToPrint[] =
{
"Task 1 ****************************************************\r\n",
"Task 2 ----------------------------------------------------\r\n",
"Message printed from the tick hook interrupt ##############\r\n"
};
/*-----------------------------------------------------------*/
/* Declare a variable of type QueueHandle_t. The queue is used to send messages
from the print tasks and the tick interrupt to the gatekeeper task. */
QueueHandle_t xPrintQueue;
/*-----------------------------------------------------------*/
int main( void )
{
/* Before a queue is used it must be explicitly created. The queue is created
to hold a maximum of 5 character pointers. */
xPrintQueue = xQueueCreate( 5, sizeof( char * ) );
/* Check the queue was created successfully. */
if( xPrintQueue != NULL )
{
/* Create two instances of the tasks that send messages to the gatekeeper.
The index to the string the task uses is passed to the task via the task
parameter (the 4th parameter to xTaskCreate()). The tasks are created at
different priorities so the higher priority task will occasionally preempt
the lower priority task. */
xTaskCreate( prvPrintTask, "Print1", 1000, ( void * ) 0, 1, NULL );
xTaskCreate( prvPrintTask, "Print2", 1000, ( void * ) 1, 2, NULL );
/* Create the gatekeeper task. This is the only task that is permitted
to directly access standard out. */
xTaskCreate( prvStdioGatekeeperTask, "Gatekeeper", 1000, NULL, 0, NULL );
/* Start the scheduler so the created tasks start executing. */
vTaskStartScheduler();
}
/* If all is well then main() will never reach here as the scheduler will now be
running the tasks. If main() does reach here then it is likely that there was
insufficient heap memory available for the idle task to be created. Chapter 2
provides more information on heap memory management. */
for( ;; );
}