04. FreeRTOS任务创建与任务删除
1. FreeRTOS创建和删除任务相关API函数
函数 | 描述 |
---|---|
xTaskCreate() | 动态方式创建任务 |
xTaskCreateStatic() | 静态方式创建任务 |
xTaskCreateRestricted() | 动态方式创建使用 MPU 限制的任务 |
xTaskCreateRestrictedStatic() | 静态方式创建使用 MPU 限制的任务 |
vTaskDelete() | 删除任务 |
-
函数xTaskCreate()
动态方式创建函数,任务的任务控制块及栈空间所需的内存,均由FreeRTOS从FreeRTOS管理的堆中分配,若使用此函数,需要在
FreeRTOSConfig.h
文件中将宏configSUPPORT_DYNAMIC_ALLOCATION
配置为1。此函数创建的任务会立刻进入就绪态,由任务调度器运行。函数原型如下所示:
-
函数xTaskCreateStatic()
静态方式创建任务,任务的任务控制块及任务的栈空间所需的内存,需要由用户分配提供,若使用此函数,需要在
FreeRTOSConfig.h
文件中将宏configSUPPORT_STATIC_ALLOCATION
配置为1。此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。函数原型如下所示:
-
函数vTaskDelete()
此函数用于删除已被创建的任务,被删除的任务将被从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除,要注意的是,空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存,则需要由用户在任务被删除前提前释放,否则将导致内存泄露。若使用此函数,需要在
FreeRTOSConfig.h
文件中将宏INCLUDE_vTaskDelete
配置为1。函数原型如下所示:
2. 动态创建任务及具体实现
-
步骤:
- 将宏
configSUPPORT_DYNAMIC_ALLOCATION
配置为1
- 定义函数入口参数
- 编写任务函数
- 将宏
-
动态创建任务函数内部实现:
- 申请堆栈内存和任务控制块内存
- TCB结构体成员赋值
- 添加新任务到就绪列表中
-
任务控制块:
-
具体代码实现:
本实验共创建6个任务,1个开始任务和5个不同功能的任务函数
任务优先级、任务堆栈大小、任务句柄:
#define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler;#define TASK1_TASK_PRIO 2 #define TASK1_TASK_STACK_SIZE 128 TaskHandle_t task1_task_handler;#define TASK2_TASK_PRIO 3 #define TASK2_TASK_STACK_SIZE 128 TaskHandle_t task2_task_handler;#define TASK3_TASK_PRIO 4 #define TASK3_TASK_STACK_SIZE 128 TaskHandle_t task3_task_handler;#define TASK4_TASK_PRIO 5 #define TASK4_TASK_STACK_SIZE 128 TaskHandle_t task4_task_handler;#define TASK5_TASK_PRIO 6 #define TASK5_TASK_STACK_SIZE 128 TaskHandle_t task5_task_handler;//LCD刷屏时使用的颜色 uint16_t lcd_discolor[11] = {WHITE, BLACK, BLUE, RED, MAGENTA, GREEN, CYAN, YELLOW,BROWN, BRRED, GRAY};
主函数入口:
void freertos_Dynamic_Create(void) {lcd_show_string(10, 10, 220, 32, 32, "STM32", RED);lcd_show_string(10, 47, 220, 24, 24, "Task Create&Delete", LIGHTGREEN);lcd_draw_rectangle(5, 110, 115, 314, BLACK);lcd_draw_rectangle(125, 110, 234, 314, BLACK);lcd_draw_line(5, 130, 115, 130, BLACK);lcd_draw_line(125, 130, 234, 130, BLACK);lcd_show_string(15, 80, 110, 16, 16, "Task1: 000", GREEN);lcd_show_string(135, 80, 110, 16, 16, "Task2: 000", GREEN);lcd_show_string(15, 111, 110, 16, 16, "Task4: 000", BLUE);lcd_show_string(135, 111, 110, 16, 16, "Task5: 000", BLUE);xTaskCreate((TaskFunction_t ) start_task, //指向任务函数的指针(char * ) "start_task", //任务名称(configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,//任务堆栈大小,字节为单位(void * ) NULL, //传递给任务函数的参数(UBaseType_t ) START_TASK_PRIO, //任务优先级(TaskHandle_t * ) &start_task_handler //任务句柄:任务控制块);vTaskStartScheduler(); //开启任务调度 }
开始任务:
void start_task(void* pvParamter) {taskENTER_CRITICAL(); // 进入临界区 xTaskCreate((TaskFunction_t ) task1, //指向任务函数的指针(char * ) "task1", //任务名称(configSTACK_DEPTH_TYPE) TASK1_TASK_STACK_SIZE, //任务堆栈大小,字节为单位(void * ) NULL, //传递给任务函数的参数(UBaseType_t ) TASK1_TASK_PRIO, //任务优先级(TaskHandle_t * ) &task1_task_handler //任务句柄:任务控制块);xTaskCreate((TaskFunction_t ) task2, //指向任务函数的指针(char * ) "task2", //任务名称(configSTACK_DEPTH_TYPE) TASK2_TASK_STACK_SIZE, //任务堆栈大小,字节为单位(void * ) NULL, //传递给任务函数的参数(UBaseType_t ) TASK2_TASK_PRIO, //任务优先级(TaskHandle_t * ) &task2_task_handler //任务句柄:任务控制块); xTaskCreate((TaskFunction_t ) task3, //指向任务函数的指针(char * ) "task3", //任务名称(configSTACK_DEPTH_TYPE) TASK3_TASK_STACK_SIZE, //任务堆栈大小,字节为单位(void * ) NULL, //传递给任务函数的参数(UBaseType_t ) TASK3_TASK_PRIO, //任务优先级(TaskHandle_t * ) &task3_task_handler //任务句柄:任务控制块);xTaskCreate((TaskFunction_t ) task4, //指向任务函数的指针(char * ) "task4", //任务名称(configSTACK_DEPTH_TYPE) TASK4_TASK_STACK_SIZE, //任务堆栈大小,字节为单位(void * ) NULL, //传递给任务函数的参数(UBaseType_t ) TASK4_TASK_PRIO, //任务优先级(TaskHandle_t * ) &task4_task_handler //任务句柄:任务控制块);xTaskCreate((TaskFunction_t ) task5, //指向任务函数的指针(char * ) "task5", //任务名称(configSTACK_DEPTH_TYPE) TASK5_TASK_STACK_SIZE, //任务堆栈大小,字节为单位(void * ) NULL, //传递给任务函数的参数(UBaseType_t ) TASK5_TASK_PRIO, //任务优先级(TaskHandle_t * ) &task5_task_handler //任务句柄:任务控制块 ); vTaskDelete(NULL);taskEXIT_CRITICAL(); // 退出临界区 }
任务一:实现LED0每500ms翻转一次
void task1(void* pvParamter) {uint32_t task1_num = 0;while(1){printf("task1正在运行!!!\r\n");lcd_show_xnum(71, 80, ++task1_num, 3, 16, 0x80, GREEN);LED0_TOGGLE();vTaskDelay(500);} }
任务二:实现LED1每500ms翻转一次
void task2(void* pvParamter) {uint32_t task2_num = 0;while(1){printf("task2正在运行!!!\r\n");lcd_show_xnum(191, 80, ++task2_num, 3, 16, 0x80, GREEN);LED1_TOGGLE();vTaskDelay(500);} }
任务三:删除任务一
void task3(void* pvParamter) {uint8_t key = 0;while(1){printf("task3正在运行!!!\r\n");key = key_scan(0);if(key == KEY0){if(task1_task_handler != NULL){printf("删除task1任务!!!\r\n");vTaskDelete(task1_task_handler);task1_task_handler = NULL;} }vTaskDelay(10);} }
任务四:不同颜色填充块一
void task4(void *pvParameter) {uint32_t task4_num = 0;while(1){lcd_fill(6, 131, 114, 313, lcd_discolor[++task4_num % 11]);lcd_show_xnum(71, 111, task4_num, 3, 16, 0x80, BLUE);vTaskDelay(500);} }
任务五:不同颜色填充块二
void task5(void *pvParameter) {uint32_t task5_num = 0;while(1){lcd_fill(126, 131, 233, 313, lcd_discolor[11 - (++task5_num % 11)]);lcd_show_xnum(191, 111, task5_num, 3, 16, 0x80, BLUE);vTaskDelay(500);} }
-
函数xTaskCreate()的内部实现(源码):
步骤:
-
申请堆栈内存(返回首地址)
pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
-
申请任务控制块内存(返回首地址)
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
-
把前面申请的堆栈地址 赋值给控制块的堆栈成员
pxNewTCB->pxStack = pxStack;
-
调用
prvInitialiseNewTask
初始化任务控制块中的成员prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
-
调用
prvAddNewTaskToReadyList
添加新创建的任务到就绪列表prvAddNewTaskToReadyList( pxNewTCB );
具体源码:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */const configSTACK_DEPTH_TYPE usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask ){TCB_t * pxNewTCB;BaseType_t xReturn;/* If the stack grows down then allocate the stack then the TCB so the stack* does not grow into the TCB. Likewise if the stack grows up then allocate* the TCB then the stack.如果堆栈向下扩展,则先分配堆栈然后分配TCB,这样堆栈*不会扩展到TCB。同样,如果堆栈向上扩展,则再分配TCB然后堆栈*/#if ( portSTACK_GROWTH > 0 ){/* Allocate space for the TCB. Where the memory comes from depends on* the implementation of the port malloc function and whether or not static* allocation is being used. */pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );if( pxNewTCB != NULL ){/* Allocate space for the stack used by the task being created.* The base of the stack memory stored in the TCB so the task can* be deleted later if required. */pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */if( pxNewTCB->pxStack == NULL ){/* Could not allocate the stack. Delete the allocated TCB. */vPortFree( pxNewTCB );pxNewTCB = NULL;}}}#else /* portSTACK_GROWTH */{StackType_t * pxStack;/* Allocate space for the stack used by the task being created.为正在创建的任务所使用的堆栈分配空间*///1.申请堆栈内存(返回首地址)pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */if( pxStack != NULL ){/* Allocate space for the TCB. *///2.申请任务控制块内存(返回首地址)pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */if( pxNewTCB != NULL ){/* Store the stack location in the TCB. *///3.把前面申请的堆栈地址 赋值给控制块的堆栈成员pxNewTCB->pxStack = pxStack;}else{/* The stack cannot be used as the TCB was not created. Free* it again. */vPortFreeStack( pxStack );}}else{pxNewTCB = NULL;}}#endif /* portSTACK_GROWTH */if( pxNewTCB != NULL ){#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */{/* Tasks can be created statically or dynamically, so note this* task was created dynamically in case it is later deleted. */pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;}#endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE *///4.调用prvInitialiseNewTask初始化任务控制块中的成员prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );//5.调用prvAddNewTaskToReadyList添加新创建的任务到就绪列表prvAddNewTaskToReadyList( pxNewTCB );xReturn = pdPASS;}else{xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;}return xReturn;}
上述第4步调用
prvInitialiseNewTask
初始化任务控制块中的成员的步骤:-
用已知值填充堆栈以协助调试,初始化堆栈0xa5
( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
-
记录栈顶,保存在pxTopOfStack,并8字节向下对齐
pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] ); pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
-
保存任务名字到pxNewTCB->pcTaskName[x]
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ) {pxNewTCB->pcTaskName[ x ] = pcName[ x ];if( pcName[ x ] == ( char ) 0x00 ){break;}else{mtCOVERAGE_TEST_MARKER();} }
-
保存任务优先级到pxNewTCB->uxPriority中
pxNewTCB->uxPriority = uxPriority;
-
设置状态列表项的所属控制块,设置事件列表项的值
vListInitialiseItem( &( pxNewTCB->xStateListItem ) ); vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
-
列表项的插入是从小到大插入,所以这里将越高优先级的任务他的事件列表项值设置越小,这样就可以拍到前面
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
-
调用pxPortInitialiseStack:初始化任务堆栈,用于保存当前任务上下文寄存器信息已备后续任务切换使用
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
-
将任务句柄等于任务控制块
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
具体源码:
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */const uint32_t ulStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask,TCB_t * pxNewTCB,const MemoryRegion_t * const xRegions ) {StackType_t * pxTopOfStack;UBaseType_t x;#if ( portUSING_MPU_WRAPPERS == 1 )/* Should the task be created in privileged mode? */BaseType_t xRunPrivileged;if( ( uxPriority & portPRIVILEGE_BIT ) != 0U ){xRunPrivileged = pdTRUE;}else{xRunPrivileged = pdFALSE;}uxPriority &= ~portPRIVILEGE_BIT;#endif /* portUSING_MPU_WRAPPERS == 1 *//* Avoid dependency on memset() if it is not required. */#if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 ){/* Fill the stack with a known value to assist debugging. *///1.用已知值填充堆栈以协助调试,初始化堆栈0xa5( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );}#endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE *//* Calculate the top of stack address. This depends on whether the stack* grows from high memory to low (as per the 80x86) or vice versa.* portSTACK_GROWTH is used to make the result positive or negative as required* by the port. */#if ( portSTACK_GROWTH < 0 ){//2.记录栈顶,保存在pxTopOfStackpxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );//8字节向下对齐pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 !e9033 !e9078 MISRA exception. Avoiding casts between pointers and integers is not practical. Size differences accounted for using portPOINTER_SIZE_TYPE type. Checked by assert(). *//* Check the alignment of the calculated top of stack is correct. */configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );#if ( configRECORD_STACK_HIGH_ADDRESS == 1 ){/* Also record the stack's high address, which may assist* debugging. */pxNewTCB->pxEndOfStack = pxTopOfStack;}#endif /* configRECORD_STACK_HIGH_ADDRESS */}#else /* portSTACK_GROWTH */{pxTopOfStack = pxNewTCB->pxStack;/* Check the alignment of the stack buffer is correct. */configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );/* The other extreme of the stack space is required if stack checking is* performed. */pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );}#endif /* portSTACK_GROWTH *//* Store the task name in the TCB. */if( pcName != NULL ){//3.保存任务名字到pxNewTCB->pcTaskName[x]for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ){pxNewTCB->pcTaskName[ x ] = pcName[ x ];/* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than* configMAX_TASK_NAME_LEN characters just in case the memory after the* string is not accessible (extremely unlikely). */if( pcName[ x ] == ( char ) 0x00 ){break;}else{mtCOVERAGE_TEST_MARKER();}}/* Ensure the name string is terminated in the case that the string length* was greater or equal to configMAX_TASK_NAME_LEN. *///末尾加结束符pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';}else{/* The task has not been given a name, so just ensure there is a NULL* terminator when it is read out. */pxNewTCB->pcTaskName[ 0 ] = 0x00;}/* This is used as an array index so must ensure it's not too large. */configASSERT( uxPriority < configMAX_PRIORITIES );if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES ){uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;}else{mtCOVERAGE_TEST_MARKER();}//4.保存任务优先级到pxNewTCB->uxPriority中pxNewTCB->uxPriority = uxPriority;#if ( configUSE_MUTEXES == 1 ){pxNewTCB->uxBasePriority = uxPriority;pxNewTCB->uxMutexesHeld = 0;}#endif /* configUSE_MUTEXES *///5.设置状态列表项的所属控制块,设置事件列表项的值vListInitialiseItem( &( pxNewTCB->xStateListItem ) );vListInitialiseItem( &( pxNewTCB->xEventListItem ) );/* Set the pxNewTCB as a link back from the ListItem_t. This is so we can get* back to the containing TCB from a generic item in a list. *///6.列表项的插入是从小到大插入,所以这里将越高优先级的任务他的事件列表项值设置越小,这样就可以拍到前面listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );/* Event lists are always in priority order. */listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );#if ( portCRITICAL_NESTING_IN_TCB == 1 ){pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;}#endif /* portCRITICAL_NESTING_IN_TCB */#if ( configUSE_APPLICATION_TASK_TAG == 1 ){pxNewTCB->pxTaskTag = NULL;}#endif /* configUSE_APPLICATION_TASK_TAG */#if ( configGENERATE_RUN_TIME_STATS == 1 ){pxNewTCB->ulRunTimeCounter = ( configRUN_TIME_COUNTER_TYPE ) 0;}#endif /* configGENERATE_RUN_TIME_STATS */#if ( portUSING_MPU_WRAPPERS == 1 ){vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );}#else{/* Avoid compiler warning about unreferenced parameter. */( void ) xRegions;}#endif#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 ){memset( ( void * ) &( pxNewTCB->pvThreadLocalStoragePointers[ 0 ] ), 0x00, sizeof( pxNewTCB->pvThreadLocalStoragePointers ) );}#endif#if ( configUSE_TASK_NOTIFICATIONS == 1 ){memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) );memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) );}#endif#if ( configUSE_NEWLIB_REENTRANT == 1 ){/* Initialise this task's Newlib reent structure.* See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html* for additional information. */_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );}#endif#if ( INCLUDE_xTaskAbortDelay == 1 ){pxNewTCB->ucDelayAborted = pdFALSE;}#endif/* Initialize the TCB stack to look as if the task was already running,* but had been interrupted by the scheduler. The return address is set* to the start of the task function. Once the stack has been initialised* the top of stack variable is updated. */#if ( portUSING_MPU_WRAPPERS == 1 ){/* If the port has capability to detect stack overflow,* pass the stack end address to the stack initialization* function as well. */#if ( portHAS_STACK_OVERFLOW_CHECKING == 1 ){#if ( portSTACK_GROWTH < 0 ){pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged );}#else /* portSTACK_GROWTH */{pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged );}#endif /* portSTACK_GROWTH */}#else /* portHAS_STACK_OVERFLOW_CHECKING */{pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );}#endif /* portHAS_STACK_OVERFLOW_CHECKING */}#else /* portUSING_MPU_WRAPPERS */{/* If the port has capability to detect stack overflow,* pass the stack end address to the stack initialization* function as well. */#if ( portHAS_STACK_OVERFLOW_CHECKING == 1 ){#if ( portSTACK_GROWTH < 0 ){pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );}#else /* portSTACK_GROWTH */{pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );}#endif /* portSTACK_GROWTH */}#else /* portHAS_STACK_OVERFLOW_CHECKING */{//7.调用pxPortInitialiseStack:初始化任务堆栈,用于保存当前任务上下文寄存器信息已备后续任务切换使用pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );}#endif /* portHAS_STACK_OVERFLOW_CHECKING */}#endif /* portUSING_MPU_WRAPPERS */if( pxCreatedTask != NULL ){/* Pass the handle out in an anonymous way. The handle can be used to* change the created task's priority, delete the created task, etc.*///8.将任务句柄等于任务控制块*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;}else{mtCOVERAGE_TEST_MARKER();} }
上述第5步调用
prvAddNewTaskToReadyList
添加新创建的任务到就绪列表的步骤:-
记录任务数量uxCurrentNumberOfTasks++
uxCurrentNumberOfTasks++;
-
判断新创建的任务是否为第一个任务
//如果创建的是第一个任务,初始化任务列表prvInitialiseTaskLists if( pxCurrentTCB == NULL ) {pxCurrentTCB = pxNewTCB;if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){ prvInitialiseTaskLists();}else{mtCOVERAGE_TEST_MARKER();} } //如果创建的不是第一个任务,并且调度器还未开始启动,比较新任务与正在执行的任务 //优先级大小,新任务优先级大的话,将当前控制块重新指向新的控制块 else {if( xSchedulerRunning == pdFALSE ){if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){pxCurrentTCB = pxNewTCB;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();} }
-
将新的任务控制块添加到就绪列表中,使用函数prvAddTaskToReadyList。将uxTopReadyPriority相应bit置一,表示相应优先级有就绪任务,比如任务优先级为5,就将该变量的位5置一,方便后续任务切换判断,对应的就绪列表是否有任务存在。
prvAddTaskToReadyList( pxNewTCB );
-
如果调度器已经开始运行,并且新任务的优先级更大的话,进行一次任务切换
if( xSchedulerRunning != pdFALSE ) {if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ){taskYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();} } else {mtCOVERAGE_TEST_MARKER(); }
具体源码:
static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB ) {/* Ensure interrupts don't access the task lists while the lists are being* updated. */taskENTER_CRITICAL();{//1.记录任务数量uxCurrentNumberOfTasks++uxCurrentNumberOfTasks++;//2.判断新创建的任务是否为第一个任务//如果创建的是第一个任务,初始化任务列表prvInitialiseTaskListsif( pxCurrentTCB == NULL ){/* There are no other tasks, or all the other tasks are in* the suspended state - make this the current task. */pxCurrentTCB = pxNewTCB;if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){/* This is the first task to be created so do the preliminary* initialisation required. We will not recover if this call* fails, but we will report the failure. */prvInitialiseTaskLists();}else{mtCOVERAGE_TEST_MARKER();}}//如果创建的不是第一个任务,并且调度器还未开始启动,比较新任务与正在执行的任务//优先级大小,新任务优先级大的话,将当前控制块重新指向新的控制块else{/* If the scheduler is not already running, make this task the* current task if it is the highest priority task to be created* so far. */if( xSchedulerRunning == pdFALSE ){if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){pxCurrentTCB = pxNewTCB;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}uxTaskNumber++;#if ( configUSE_TRACE_FACILITY == 1 ){/* Add a counter into the TCB for tracing only. */pxNewTCB->uxTCBNumber = uxTaskNumber;}#endif /* configUSE_TRACE_FACILITY */traceTASK_CREATE( pxNewTCB );//3.将新的任务控制块添加到就绪列表中,使用函数prvAddTaskToReadyList//将uxTopReadyPriority相应bit置一,表示相应优先级有就绪任务,比如任务优先级为5,就将该//变量的位5置一,方便后续任务切换判断,对应的就绪列表是否有任务存在prvAddTaskToReadyList( pxNewTCB );portSETUP_TCB( pxNewTCB );}taskEXIT_CRITICAL();//4.如果调度器已经开始运行,并且新任务的优先级更大的话,进行一次任务切换if( xSchedulerRunning != pdFALSE ){/* If the created task is of a higher priority than the current task* then it should run now. */if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ){taskYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();} }
-
-
-
实验结果:
一共有五个任务,任务点亮LED0;任务二点亮LED;任务三删除任务1;任务四填充块一;任务五填充块二。从实验结果可以看出,LED0和LED1同时闪烁,间隔500ms;两个填充块已填充不同的颜色;当按键KEY0按下时,任务1被删除,LED0停止闪烁。
3. 静态创建任务及具体实现
-
步骤:
-
需将宏
configSUPPORT_STATIC_ALLOCATION
配置为 1// 1: 支持静态申请内存, 默认: 0 #define configSUPPORT_STATIC_ALLOCATION 1
-
定义空闲任务和定时器任务的任务堆栈及TCB
//空闲任务配置 StaticTask_t idle_task_tcb; StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];//软件定时器任务配置 StaticTask_t timer_task_tcb; StackType_t timer_task_stack[configTIMER_TASK_STACK_DPTH];
-
实现两个接口函数
/*空闲任务内存分配*/ void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,StackType_t ** ppxIdleTaskStackBuffer,uint32_t * pulIdleTaskStackSize ) {* ppxIdleTaskTCBBuffer = &idle_task_tcb;* ppxIdleTaskStackBuffer = idle_task_stack;* pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; }/*软件定时器内存分配*/ void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,StackType_t ** ppxTimerTaskStackBuffer,uint32_t * pulTimerTaskStackSize ) {* ppxTimerTaskTCBBuffer = &timer_task_tcb;* ppxTimerTaskStackBuffer = timer_task_stack;* pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH; }
-
定义函数入口参数
freertos_Static_Create();
-
编写任务函数
#define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; StackType_t start_task_stack[START_TASK_STACK_SIZE]; StaticTask_t start_task_tcb;#define TASK1_TASK_PRIO 2 #define TASK1_TASK_STACK_SIZE 128 TaskHandle_t task1_task_handler; StackType_t task1_task_stack[TASK1_TASK_STACK_SIZE]; StaticTask_t task1_task_tcb;#define TASK2_TASK_PRIO 3 #define TASK2_TASK_STACK_SIZE 128 TaskHandle_t task2_task_handler; StackType_t task2_task_stack[TASK2_TASK_STACK_SIZE]; StaticTask_t task2_task_tcb;#define TASK3_TASK_PRIO 4 #define TASK3_TASK_STACK_SIZE 128 TaskHandle_t task3_task_handler; StackType_t task3_task_stack[TASK3_TASK_STACK_SIZE]; StaticTask_t task3_task_tcb;void start_task(void * pvParamter) {taskENTER_CRITICAL();task1_task_handler = xTaskCreateStatic((TaskFunction_t) task1, (char * ) "task1", (uint32_t ) TASK1_TASK_STACK_SIZE, ( void * ) NULL, (UBaseType_t ) TASK1_TASK_PRIO,(StackType_t * ) task1_task_stack, (StaticTask_t *) &task1_task_tcb); task2_task_handler = xTaskCreateStatic((TaskFunction_t) task2, (char * ) "task2", (uint32_t ) TASK2_TASK_STACK_SIZE, ( void * ) NULL, (UBaseType_t ) TASK2_TASK_PRIO,(StackType_t * ) task2_task_stack, (StaticTask_t *) &task2_task_tcb); task3_task_handler = xTaskCreateStatic((TaskFunction_t) task3, (char * ) "task3", (uint32_t ) TASK3_TASK_STACK_SIZE, ( void * ) NULL, (UBaseType_t ) TASK3_TASK_PRIO,(StackType_t * ) task3_task_stack, (StaticTask_t *) &task3_task_tcb); vTaskDelete(start_task_handler);taskEXIT_CRITICAL(); }/*任务一:实现LED0每500ms翻转一次*/ void task1(void* pvParamter) {uint32_t task1_num = 0;while(1){printf("task1正在运行!!!\r\n");lcd_show_xnum(71, 80, ++task1_num, 3, 16, 0x80, GREEN);LED0_TOGGLE();vTaskDelay(500);} }/*任务二:实现LED1每500ms翻转一次*/ void task2(void* pvParamter) {uint32_t task2_num = 0;while(1){printf("task2正在运行!!!\r\n");lcd_show_xnum(191, 80, ++task2_num, 3, 16, 0x80, GREEN);LED1_TOGGLE();vTaskDelay(500);} }/*任务三:删除任务一*/ void task3(void* pvParamter) {uint8_t key = 0;while(1){printf("task3正在运行!!!\r\n");key = key_scan(0);if(key == KEY0){if(task1_task_handler != NULL){printf("删除task1任务!!!\r\n");vTaskDelete(task1_task_handler);task1_task_handler = NULL;} }vTaskDelay(10);} }
-
4. 删除任务及具体实现
内部实现:
-
获取所要删除任务的控制块:通过传入的任务句柄,判断所需要删除哪个任务,NULL代表删除自身
pxTCB = prvGetTCBFromHandle( xTaskToDelete );
-
将被删除任务,移除所在列表:将该任务从列表中移除,包括:就绪、阻塞、挂起、事件等列表
//2.将被删除任务,移除所在列表:将该任务从列表中移除,包括:就绪、阻塞、挂起、事件等列表 if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) {taskRESET_READY_PRIORITY( pxTCB->uxPriority ); } else {mtCOVERAGE_TEST_MARKER(); }
-
判断所需要删除的任务
删除任务自身,需先添加到等待删除列表,内存释放将在空闲任务执行 if( pxTCB == pxCurrentTCB ) {vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );++uxDeletedTasksWaitingCleanUp;traceTASK_DELETE( pxTCB );portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending ); } //删除其他任务,当前任务数量-- else {--uxCurrentNumberOfTasks;traceTASK_DELETE( pxTCB );prvResetNextTaskUnblockTime(); }
-
更新下一个任务的阻塞超时时间,以防被删除的任务就是下一个阻塞超时的任务
prvResetNextTaskUnblockTime();
具体实现:
void vTaskDelete( TaskHandle_t xTaskToDelete ){TCB_t * pxTCB;taskENTER_CRITICAL();{/* If null is passed in here then it is the calling task that is* being deleted. *///1.获取所要删除任务的控制块:通过传入的任务句柄,判断所需要删除哪个任务,NULL代表删除自身pxTCB = prvGetTCBFromHandle( xTaskToDelete );/* Remove task from the ready/delayed list. *///2.将被删除任务,移除所在列表:将该任务从列表中移除,包括:就绪、阻塞、挂起、事件等列表if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){taskRESET_READY_PRIORITY( pxTCB->uxPriority );}else{mtCOVERAGE_TEST_MARKER();}/* Is the task waiting on an event also? */if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ){( void ) uxListRemove( &( pxTCB->xEventListItem ) );}else{mtCOVERAGE_TEST_MARKER();}/* Increment the uxTaskNumber also so kernel aware debuggers can* detect that the task lists need re-generating. This is done before* portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will* not return. */uxTaskNumber++;//判断所需要删除的任务//删除任务自身,需先添加到等待删除列表,内存释放将在空闲任务执行if( pxTCB == pxCurrentTCB ){/* A task is deleting itself. This cannot complete within the* task itself, as a context switch to another task is required.* Place the task in the termination list. The idle task will* check the termination list and free up any memory allocated by* the scheduler for the TCB and stack of the deleted task. */vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );/* Increment the ucTasksDeleted variable so the idle task knows* there is a task that has been deleted and that it should therefore* check the xTasksWaitingTermination list. */++uxDeletedTasksWaitingCleanUp;/* Call the delete hook before portPRE_TASK_DELETE_HOOK() as* portPRE_TASK_DELETE_HOOK() does not return in the Win32 port. */traceTASK_DELETE( pxTCB );/* The pre-delete hook is primarily for the Windows simulator,* in which Windows specific clean up operations are performed,* after which it is not possible to yield away from this task -* hence xYieldPending is used to latch that a context switch is* required. */portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );}//删除其他任务,当前任务数量--//更新下一个任务的阻塞超时时间,以防被删除的任务就是下一个阻塞超时的任务else{--uxCurrentNumberOfTasks;traceTASK_DELETE( pxTCB );/* Reset the next expected unblock time in case it referred to* the task that has just been deleted. */prvResetNextTaskUnblockTime();}}taskEXIT_CRITICAL();/* If the task is not deleting itself, call prvDeleteTCB from outside of* critical section. If a task deletes itself, prvDeleteTCB is called* from prvCheckTasksWaitingTermination which is called from Idle task. */if( pxTCB != pxCurrentTCB ){prvDeleteTCB( pxTCB );}/* Force a reschedule if it is the currently running task that has just* been deleted. */if( xSchedulerRunning != pdFALSE ){if( pxTCB == pxCurrentTCB ){configASSERT( uxSchedulerSuspended == 0 );portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}}}