优先级
rtos中,对任务的调度是按最高优先级的顺序进行的,所以需要对每个任务进行优先级的定义,而有了优先级之后,其他的代码:如结构体,创建任务,切换任务,阻塞延时等都需要相应修改
1. 任务TCB结构体:TCB结构体添加优先级属性成员
typedef struct tasTaskControlerBlock { volatile StackType_t *pxTopOfStack; //栈顶 ListItem_t xStateListItem; //任务节点(链表项) StackType_t *pxStack; //任务栈起始地址 char pcTaskName[configMAX_TASK_NAME_LEN]; //任务名称 TickType_t xTicksToDelay; //任务延时计数值 UBaseType_t uxPriority; //优先级 }tskTCB; typedef tskTCB TCB_t;
2. 创建任务→初始化任务:初始化任务优先级
static void prvInitialiseNewTask(TaskFunction_t pxTaskCode, const char * const pcName, const uint32_t ulStackDepth, void * pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask, TCB_t *pxNewTCB) { /* brief:初始化新任务 param: pxTaskCode:任务函数 pcName:任务名称 ulStackDepth:任务栈深 pvParameters:任务函数传参 pxCreatedTask:任务句柄,指向TCB pxNewTCB:TCB return:NULL */ StackType_t *pxTopOfStack; UBaseType_t x; //计算栈顶 pxTopOfStack = pxNewTCB->pxStack + (ulStackDepth - (uint32_t)1); pxTopOfStack = (StackType_t *)((uint32_t)pxTopOfStack & ~((uint32_t)0x0007)); //向下对齐八个字节 //初始化任务栈(伪造现场) pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, pxTaskCode, pvParameters); //初始化任务名称 for(x = (UBaseType_t)0;x < (UBaseType_t)configMAX_TASK_NAME_LEN;x++) { pxNewTCB->pcTaskName[x] = pcName[x]; if(pcName[x] == 0x00) { break; } } pxNewTCB->pcTaskName[configMAX_TASK_NAME_LEN -1] = '\0'; //最后一个字符为结束符? //初始化任务节点 vListInitialiseItem(&(pxNewTCB->xStateListItem)); //设置节点owner listSET_LIST_ITEM_OWNER((pxNewTCB->xStateListItem),pxNewTCB); //关联owner,令该任务节点挂载TCB //初始化优先级 if(uxPriority >= (UBaseType_t) configMAX_PRIORITIES) { uxPriority = (UBaseType_t) configMAX_PRIORITIES - (UBaseType_t) 1U; } pxNewTCB->uxPriority = uxPriority; //任务句柄指向TCB if((void *) pxCreatedTask != NULL) { *pxCreatedTask = (TaskHandle_t) pxNewTCB; } }
3. 添加任务到就绪链表:需要做三件事:1. 当前任务指向最高任务;2. 更新优先级(保证最高优先级能被记录);3. 插入相应优先级链表尾部
首先定义添加函数prvAddNewTaskToReadyList。其完成了任务计数加1,当前任务指向
static void prvAddNewTaskToReadyList(TCB_t *pxNewTCB) { taskENTER_CRITICAL(); //进入临界区 uxCurrentNumberOfTasks++; //添加了新任务,计数加一 if(pxCurrentTCB == NULL) //如果当前任务为空,则指向新创建的任务 { pxCurrentTCB = pxNewTCB; if(uxCurrentNumberOfTasks == (UBaseType_t) 1) //第一次创建任务则初始化任务列表 { prvInitialiseTaskLists(); } } else { if(pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority) //保持当前任务始终为最高优先级任务 { pxCurrentTCB = pxNewTCB; } } prvAddTaskToReadyList(pxNewTCB); //添加到就绪链表 taskEXIT_CRITICAL(); //退出临界区 }
函数中使用了添加就绪链表的宏定义prvAddTaskToReadyList。其完成了更新优先级,插入相应链表尾部的操作
#define prvAddTaskToReadyList(pxTCB) \ { /* 更新最高优先级 */ \ taskRECORD_READY_PRIORITY((pxTCB)->uxPriority); \ /* 插入相应优先级尾部 */ \ vListInsertEnd(&(pxReadyTasksLists[(pxTCB)->uxPriority]),&((pxTCB)->xStateListItem)); \ }
最后在创建任务xTaskCreateStatic函数中,完成初始化任务后调用prvAddNewTaskToReadyList函数,由于代码过长不演示
4. 任务调度函数:调度函数内核自动创建空闲任务后,再无需将空闲任务手动插入就绪链表和指定当前任务,由3可知,创建任务函数内部已自动将任务加入就绪链表,同时指定当前任务位最高优先级任务,所以把两个相关语句注释
void vTaskStartScheduler(void) { /* 启动任务调度(含有空闲任务创建) */ TaskHandle_t xIdleTaskHandle; //空闲任务句柄 TCB_t *pxIdleTaskTCBBuffer = NULL; //空闲任务TCB的指针 StackType_t *pxIdleTaskStackBuffer = NULL; //空闲任务栈指针 uint32_t ulIdleTaskStackSize; //空闲任务栈大小 vApplicationGetIdleTaskMemory(&pxIdleTaskTCBBuffer,&pxIdleTaskStackBuffer,&ulIdleTaskStackSize); //获取空闲任务信息,注意用二重指针 //创建空闲任务 xIdleTaskHandle = xTaskCreateStatic((TaskFunction_t)prvIdleTask,(char *)"IDLE", (uint32_t)ulIdleTaskStackSize,(void *)NULL, (UBaseType_t)tskIDLE_PRIORITY, (StackType_t *)pxIdleTaskStackBuffer,(TCB_t *)pxIdleTaskTCBBuffer); //vListInsertEnd(&(pxReadyTasksLists[0]),&(((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem)); //换成(*xIdleTaskHandle)->xStateListItem呢? /* 原本调度器的调度部分 */ //pxCurrentTCB = &Task1TCB; //指定第一个任务 if(xPortStartScheduler() != pdFALSE) { //调度器启动失败则进入这里 } }
5. 阻塞延时:由前面文章可知,在没有优先级的情况下,阻塞延时函数的作用是给任务赋予延时计数值,当有了优先级,就需要将任务从就绪链表移出,放入延时链表(但本章未实现延时链表,所以仅仅是将这一优先级从优先级位图表置零)
void vTaskDelay(const TickType_t xTicksToDelay) { TCB_t *pxTCB = NULL; //设置当前任务的延时计数 pxTCB = pxCurrentTCB; pxTCB->xTicksToDelay = xTicksToDelay; // 任务从就绪列表中删除进入延时列表 //uxListRemove(&(pxTCB->xStateListItem)); //就绪优先级位置0 taskRESET_READY_PRIORITY(pxTCB->uxPriority); //任务切换 taskYIELD(); }
6. 切换任务(切换上下文):由前面的文章可知,在没有优先级情况下,仅仅依靠判断任务的延时计数值进行切换,但是,当任务多的时候,其切换顺序或者说判断顺序乱套了(因为是人工决定的),所以,当有优先级概念时,按优先级顺序进行切换。
由下面代码可知,定义一个选择最高优先级任务的宏定义,其完成两件事:获取当前最高优先级,然后按着最高优先级去找到链表的入口链表项(任务),然后将其作为当前任务,这样就实现了任务切换
/* 选择最高优先级的任务(TCB) */ #define taskSELECT_HIGHEST_PRIORITY_TASK() \ { \ UBaseType_t uxTopPriority; \ /* 获得最高优先级 */ \ portGET_HIGHEST_PRIORITY(uxTopPriority, uxTopReadyPriority); \ /* 如果是表头则下一项就是第一个链表项,如果是某个已经执行的链表项则是下一个链表项 */ \ listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &(pxReadyTasksLists[uxTopPriority])); \ } void vTaskSwitchContext(void) { /* 上下文切换(任务切换) */ taskSELECT_HIGHEST_PRIORITY_TASK(); //选择优先性最高的任务切换 }
7. 计数器递减:由5知道,任务进入阻塞时,将任务从就绪链表移出,放入延时链表;那当任务就绪时要回到就绪链表怎么做?答案就是在计数器递减函数中实现。
计数器递减函数是对每个优先级任务延时计数值进行减1,同时,如果有任务的延时计数值已经为0,表示延时结束,则将其移出延时链表,加入就绪链表(但这里没有实现延时链表,所以只有实现优先级位图相应优先级置1)
void xTaskIncrementTick(void) { /* 进入计数器递减,并在每一次递减后判断是否有超时任务,进行切换 */ TCB_t *pxTCB = NULL; BaseType_t i = 0; extern TickType_t xTickCount; const TickType_t xConstTickCount = xTickCount + 1; xTickCount = xConstTickCount; /* 扫描就绪列表中所有优先级链表的第一个任务的xTicksToDelay,如不为0,则减1 */ for(i=0;i<configMAX_PRIORITIES;i++) { pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY((&pxReadyTasksLists[i])); if(pxTCB->xTicksToDelay > 0) { pxTCB->xTicksToDelay--; /* 如果该任务计数值为0,则放入就绪链表 */ if(pxTCB->xTicksToDelay == 0) { taskRECORD_READY_PRIORITY(pxTCB->uxPriority); //暂时先实现优先级位图置1 } } } portYIELD(); }