根据个人的学习方向,学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点。作为个人笔记,仅供参考或查阅。
配套资料:FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视频。搭配来看更佳哟!!!
不管是静态创建还是动态创建任务,都是来自SRAM区。静态创建任务来自SRAM区的堆栈,动态创建任务来自SRAM区的总堆。
总堆的定义来自于FreeRTOSConfig.h,这在上节有讲过。
//系统所有总的堆大小
#define configTOTAL_HEAP_SIZE ((size_t)(36*1024))
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, //函数入口
const char * const pcName, //函数名称
const configSTACK_DEPTH_TYPE usStackDepth, //函数大小,单位为字节
void * const pvParameters, //函数参数
UBaseType_t uxPriority, //函数优先级
TaskHandle_t * const pxCreatedTask ) //任务句柄
{
TCB_t *pxNewTCB;
BaseType_t xReturn;
#if( portSTACK_GROWTH > 0 )
{
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
if( pxNewTCB->pxStack == NULL )
{
vPortFree( pxNewTCB );
pxNewTCB = NULL;
}
}
}
#else /* portSTACK_GROWTH */
{
StackType_t *pxStack;
//为正在创建的任务使用的堆栈分配空间,对正在创建的任务开阔一个堆栈
pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
if( pxStack != NULL )
{
//分配任务TCB
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
pxNewTCB->pxStack = pxStack;
}
else
{
//释放堆栈
vPortFree( pxStack );
}
}
else
{
pxNewTCB = NULL;
}
}
#endif /* portSTACK_GROWTH */
if( pxNewTCB != NULL )
{
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
{
pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL ); //创建新任务
prvAddNewTaskToReadyList( pxNewTCB ); //添加到就绪列表
xReturn = pdPASS;
}
else
{
xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
return xReturn;
}
void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;
vTaskSuspendAll();
{
//如果这是对 malloc 的第一次调用,那么堆将需要初始化来设置空闲块列表
if( pxEnd == NULL )
{
prvHeapInit();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 省略代码 */
}
( void ) xTaskResumeAll();
/* 省略代码 */
return pvReturn;
}
//#define portBYTE_ALIGNMENT_MASK ( 0x0007 )
//#define portBYTE_ALIGNMENT 8
//static const size_t xHeapStructSize = ( sizeof( BlockLink_t ) + ( ( size_t ) ( portBYTE_ALIGNMENT - 1 ) ) ) & ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;
//static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
uxAddress = ( size_t ) ucHeap;
/* 确保堆在正确对齐的边界上启动 */
if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
{
uxAddress += ( portBYTE_ALIGNMENT - 1 );
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
}
pucAlignedHeap = ( uint8_t * ) uxAddress;
//xStart用于保存指向空闲块列表中第一个任务的指针,void用于防止编译器警告
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
xStart.xBlockSize = ( size_t ) 0;
//pxEnd用于标记空闲块列表的末尾,并插入堆空间的末尾
uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
uxAddress -= xHeapStructSize;
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
pxEnd = ( void * ) uxAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL;
//首先,有一个空闲块,其大小可以占用整个堆空间,减去 pxEnd 占用的空间
pxFirstFreeBlock = ( void * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
pxFirstFreeBlock->pxNextFreeBlock = pxEnd;
//只存在一个块 - 它覆盖整个可用堆空间。 因为是刚初始化的堆内存
xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}
假如在临界区创建任务,任务只能在退出临界区的时候才执行最高优先级任务。
假如没使用临界区的话,就会分三种情况:
1、应用任务的优先级比初始任务的优先级高,那创建完后立马去执行刚刚创建的应用任务,当应用任务被阻塞时,继续回到初始任务被打断的地方继续往下执行,直到所有应用任务创建完成,最后初始任务把自己删除,完成自己的使命;
2、应用任务的优先级与初始任务的优先级一样,那创建完后根据任务的时间片来执行,直到所有应用任务创建完成,最后初始任务把自己删除,完成自己的使命;
3、应用任务的优先级比初始任务的优先级低,那创建完后任务不会被执行。
动态任务创建实例,上述函数官方都封装好的了,我们直接使用。
/* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle = NULL;
/* LED 任务句柄 */
static TaskHandle_t LED_Task_Handle = NULL;
int main()
{
/*省略初始化*/
/* 创建 AppTaskCreate 任务 */
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
/* 启动任务调度 */
if (pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
while (1); /* 正常不会执行到这里 */
}
vTaskDelete()用于删除一个任务。当一个任务删除另外一个任务时, 形参为要删除任务创建时返回的任务句柄,如果是删除自身, 则形参为 NULL。
要想使用该函数必须在FreeRTOSConfig.h 中把 INCLUDE_vTaskDelete 定义为 1,删除的任务将从所有就绪,阻塞,挂起和事件列表中删除。
#if ( INCLUDE_vTaskDelete == 1 )
void vTaskDelete( TaskHandle_t xTaskToDelete )
{
TCB_t *pxTCB;
taskENTER_CRITICAL();
{
//#define prvGetTCBFromHandle( pxHandle ) ( ( ( pxHandle ) == NULL ) ? ( TCB_t * ) pxCurrentTCB : ( TCB_t * ) ( pxHandle ) )
//如果pxTCB = prvGetTCBFromHandle( NULL );则pxTCB = ( TCB_t * ) pxCurrentTCB;则要删除的任务为当前任务
//如果pxTCB = prvGetTCBFromHandle( xTaskToDelete );则pxTCB = ( TCB_t * ) xTaskToDelete;则要删除的任务为指定任务
pxTCB = prvGetTCBFromHandle( xTaskToDelete );
//从就绪列表中删除任务,并在就绪列表中把任务的优先级置0
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//listLIST_ITEM_CONTAINER返回链表所包含的节点
//如果任务在等待事件,也从等待事件列表中移除
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
uxTaskNumber++;
/*任务正在删除自己。 这不能在任务本身内完成,因为需要上下文切换到另一个任务。
将任务放在结束列表中。空闲任务会检查结束列表并释放掉删除的任务控制块和已删除任务的堆栈的任何内存。 */
if( pxTCB == pxCurrentTCB )
{
//将任务插入到结束列表
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
//记录有多少个任务需要释放内存,以便空闲任务知道有一个已删除的任务,然后进行内存释放空闲任务会检查结束列表 xTasksWaitingTermination
++uxDeletedTasksWaitingCleanUp;
//任务删除钩子函数
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
}
else
{
--uxCurrentNumberOfTasks;
prvDeleteTCB( pxTCB );
/* 重置下一个任务的解除阻塞时间。重新计算一下还要多长时间执行下一个任务,如果下个任务的解锁,刚好是被删除的任务,那么这就是不正确的,
因为删除的任务对调度器而言是不可见的,所以调度器是无法对删除的任务进行调度,所以要重新从延时列表中获取下一个要解除阻塞的任务。
它是从延时列表的头部来获取的任务 TCB,延时列表是按延时时间排序的*/
prvResetNextTaskUnblockTime();
}
traceTASK_DELETE( pxTCB );
}
taskEXIT_CRITICAL();
//调度器在运行
if( xSchedulerRunning != pdFALSE )
{
if( pxTCB == pxCurrentTCB )
{
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
#endif /* INCLUDE_vTaskDelete */
动态任务删除实例,上述函数官方都封装好的了,我们直接使用。
/* 创建一个任务,将创建的任务句柄存储在 DeleteHandle 中*/
TaskHandle_t DeleteHandle;
if (xTaskCreate(DeleteTask,
"DeleteTask",
STACK_SIZE,
NULL,
PRIORITY,
&DeleteHandle)
!= pdPASS )
{
/* 创建任务失败,因为没有足够的堆内存可分配。 */
}
void DeleteTask( void )
{
/* 用户代码 xxxxx */
/* ............ */
/* 删除任务本身 */
vTaskDelete( NULL );
}
/* 在其他任务删除 DeleteTask 任务 */
vTaskDelete( DeleteHandle );
创建完任务后,需要开启调度器-vTaskStartScheduler()函数。vTaskStartScheduler()函数实现了空闲任务和定时器任务。
FreeRTOS 一旦启动,就必须要保证系统中每时每刻都有一个任务处于运行态(Runing),并且空闲任务不可以被挂起与删除, 空闲任务的优先级是最低的,以便系统中其他任务能随时抢占空闲任务的 CPU 使用权。
vTaskStartScheduler()如下
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
//添加空闲任务
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
StaticTask_t *pxIdleTaskTCBBuffer = NULL;
StackType_t *pxIdleTaskStackBuffer = NULL;
uint32_t ulIdleTaskStackSize;
//获取空闲任务堆栈和指针,使用静态分配的 RAM 创建空闲任务
vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
configIDLE_TASK_NAME,
ulIdleTaskStackSize,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
pxIdleTaskStackBuffer,
pxIdleTaskTCBBuffer );
if( xIdleTaskHandle != NULL )
{
xReturn = pdPASS;
}
else
{
xReturn = pdFAIL;
}
}
#else
{
//使用动态分配的 RAM 创建空闲任务
xReturn = xTaskCreate( prvIdleTask,
configIDLE_TASK_NAME,
configMINIMAL_STACK_SIZE,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
&xIdleTaskHandle );
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
#if ( configUSE_TIMERS == 1 )
{
//如果使能了 configUSE_TIMERS 宏定义表明使用定时器,需要创建定时器任务*/
if( xReturn == pdPASS )
{
xReturn = xTimerCreateTimerTask();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TIMERS */
if( xReturn == pdPASS )
{
/* 省略代码 */
//关中断,以确保不会发生中断
//在调用 xPortStartScheduler()之前或期间。 堆栈的创建的任务包含打开中断的状态
//因此,当第一个任务时,中断将自动重新启用开始运行
portDISABLE_INTERRUPTS();
/* 省略代码 */
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;
xTickCount = ( TickType_t ) 0U;
/* 省略代码 */
//调用 xPortStartScheduler 函数配置相关硬件,如滴答定时器、 FPU、 pendsv 等
if( xPortStartScheduler() != pdFALSE )
{
//运行成功,则不会运行到这里
}
else
{
//不会运行到这里,除非调用 xTaskEndScheduler() 函数
}
}
else
{
//只有在内核无法启动时才会到达此行,因为没有足够的堆内存来创建空闲任务或计时器任务
//此处使用了断言,会输出错误信息,方便错误定位
configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
}
( void ) xIdleTaskHandle;
}
在启动任务调度器的时候,假如启动成功的话,任务就不会有返回了,假如启动没成功,则通过 LR 寄存器指定的地址退出,在创建 AppTaskCreate 任务的时候,任务栈对应 LR 寄存器指向是任务退出函数 prvTaskExitError(),该函数里面是一个死循环, 这代表着假如创建任务没成功的话,就会进入死循环,该任务也不会运行。
创建定时器任务函数xTimerCreateTimerTask()如下
BaseType_t xTimerCreateTimerTask( void )
{
BaseType_t xReturn = pdFAIL;
//检查使用了哪些活动计时器的列表,以及用于与计时器服务通信的队列
prvCheckForValidListAndQueue();
if( xTimerQueue != NULL )
{
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
StaticTask_t *pxTimerTaskTCBBuffer = NULL;
StackType_t *pxTimerTaskStackBuffer = NULL;
uint32_t ulTimerTaskStackSize;
vApplicationGetTimerTaskMemory( &pxTimerTaskTCBBuffer, &pxTimerTaskStackBuffer, &ulTimerTaskStackSize );
xTimerTaskHandle = xTaskCreateStatic( prvTimerTask,
configTIMER_SERVICE_TASK_NAME,
ulTimerTaskStackSize,
NULL,
( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
pxTimerTaskStackBuffer,
pxTimerTaskTCBBuffer );
if( xTimerTaskHandle != NULL )
{
xReturn = pdPASS;
}
}
#else
{
xReturn = xTaskCreate( prvTimerTask,
configTIMER_SERVICE_TASK_NAME,
configTIMER_TASK_STACK_DEPTH,
NULL,
( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
&xTimerTaskHandle );
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
configASSERT( xReturn );
return xReturn;
}
本节所有函数都封装好的了,可以直接用。
动态创建任务直接用xTaskCreate()
动态删除任务直接用vTaskDelete()
启动调度器vTaskStartScheduler( )