高优先级的任务会抢占低优先级的任务来运行。在FreeRTOS中数字优先级越小逻辑优先级也越小(参考空闲任务的优先级是0之前内容的中断优先级则相反)。在之前的内容直接调用插入的操作将任务1插入到就绪列表的第一个列表里面将任务2插入到第二个就绪列表第一个列表里面我们了解到就绪列表就是一个数组数组里面存的是就绪任务的TCB(其实就是TCB中的节点)该数组的下标代表的是任务的优先级刚刚我们知道下标为0时优先级最低此时我们定义了一个任务是空闲任务这是为了保证系统总是有一个任务在执行中。任务在创建的时候会根据任务的优先级将任务插入到就绪列表不同的位置相同优先级的任务被插入到就绪列表里同一个链表中也就是我们后面要学习的时间片。任务优先级越大插入到的对应就绪列表的数组下标越大反之同理。那么怎么加入优先级呢也就是让任务支持优先级。再通俗一点说就是找到最高优先级的就绪任务的TCB。FreeRTOS中提供两套方法我们一一学习一是通用方法二是根据特定的处理器优化过的默认。由宏configUSE_PORT_OPTIMISED_TASK_SELECTION控制定义为0为通用方法定义为1为优化方法。我们提前要了解pxCurrentTCB是一个全局的TCB指针用于指向优先级最高的就绪任务的TCB也就是当前正在运行的TCB。那么我们想要让任务支持优先级就要先解决在任务切换的时候让pxCurrentTCB指向最高优先级的就绪任务的TCB就OK。将以下内容COPY到task.c中通用方法和优化方法都是一些宏定义/* ************************************************************************* * 宏定义 ************************************************************************* */ /* 将任务添加到就绪列表 */ #define prvAddTaskToReadyList( pxTCB ) \ taskRECORD_READY_PRIORITY( ( pxTCB )-uxPriority ); \ vListInsertEnd( ( pxReadyTasksLists[ ( pxTCB )-uxPriority ] ), ( ( pxTCB )-xStateListItem ) ); \ /* 查找最高优先级的就绪任务通用方法 */ #if ( configUSE_PORT_OPTIMISED_TASK_SELECTION 0 ) /* uxTopReadyPriority 存的是就绪任务的最高优先级 */ #define taskRECORD_READY_PRIORITY( uxPriority ) \ { \ if( ( uxPriority ) uxTopReadyPriority ) \ { \ uxTopReadyPriority ( uxPriority ); \ } \ } /* taskRECORD_READY_PRIORITY */ /*-----------------------------------------------------------*/ #define taskSELECT_HIGHEST_PRIORITY_TASK() \ { \ UBaseType_t uxTopPriority uxTopReadyPriority; \ \ /* 寻找包含就绪任务的最高优先级的队列 */ \ while( listLIST_IS_EMPTY( ( pxReadyTasksLists[ uxTopPriority ] ) ) ) \ { \ --uxTopPriority; \ } \ \ /* 获取优先级最高的就绪任务的TCB然后更新到pxCurrentTCB */ \ listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, ( pxReadyTasksLists[ uxTopPriority ] ) ); \ /* 更新uxTopReadyPriority */ \ uxTopReadyPriority uxTopPriority; \ } /* taskSELECT_HIGHEST_PRIORITY_TASK */ /*-----------------------------------------------------------*/ /* 这两个宏定义只有在选择优化方法时才用这里定义为空 */ #define taskRESET_READY_PRIORITY( uxPriority ) #define portRESET_READY_PRIORITY( uxPriority, uxTopReadyPriority ) /* 查找最高优先级的就绪任务根据处理器架构优化后的方法 */ #else /* configUSE_PORT_OPTIMISED_TASK_SELECTION */ #define taskRECORD_READY_PRIORITY( uxPriority ) portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority ) /*-----------------------------------------------------------*/ #define taskSELECT_HIGHEST_PRIORITY_TASK() \ { \ UBaseType_t uxTopPriority; \ \ /* 寻找最高优先级 */ \ portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \ /* 获取优先级最高的就绪任务的TCB然后更新到pxCurrentTCB */ \ listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, ( pxReadyTasksLists[ uxTopPriority ] ) ); \ } /* taskSELECT_HIGHEST_PRIORITY_TASK() */ /*-----------------------------------------------------------*/ #if 0 #define taskRESET_READY_PRIORITY( uxPriority ) \ { \ if( listCURRENT_LIST_LENGTH( ( pxReadyTasksLists[ ( uxPriority ) ] ) ) ( UBaseType_t ) 0 ) \ { \ portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \ } \ } #else #define taskRESET_READY_PRIORITY( uxPriority ) \ { \ portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \ } #endif #endif /* configUSE_PORT_OPTIMISED_TASK_SELECTION */将宏configUSE_PORT_OPTIMISED_TASK_SELECTION COPY到portmacro.h中#ifndef configUSE_PORT_OPTIMISED_TASK_SELECTION #define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 #endif #if configUSE_PORT_OPTIMISED_TASK_SELECTION 1 /* 检测优先级配置 */ #if( configMAX_PRIORITIES 32 ) #error configUSE_PORT_OPTIMISED_TASK_SELECTION can only be set to 1 when configMAX_PRIORITIES is less than or equal to 32. It is very rare that a system requires more than 10 to 15 difference priorities as tasks that share a priority will time slice. #endif /* 根据优先级设置/清除优先级位图中相应的位 */ #define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) | ( 1UL ( uxPriority ) ) #define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) ~( 1UL ( uxPriority ) ) /*-----------------------------------------------------------*/ #define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) ) #endif /* taskRECORD_READY_PRIORITY */将变量声明一下在task.c中static volatile UBaseType_t uxTopReadyPriority tskIDLE_PRIORITY;在task.h中定义#define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U )我们先理解下通用方法下面我们学习下优化方法。Cortex-M内核有一个计算前导零的指令CLZ那什么是前导零呢其实就是计算一个变量从高位开始第一次出现1的位的前面的0的个数。Cortex-M内核单片机的变量为32位假如我们这个变量uxTopReadyPriority记录最高优先级任务它的第25位和第24位设置为1我们按普通方法来查找就是从31位开始依次查找找到25位那么我们使用前导零指令可以一步得出25位之前有多少个零这里得到了6个0那怎么得到25呢31-625那我们从25这里就得到了一个任务控制块之后该任务控制块我们就把他更新到pxCurrentTCB指向当前控制块的指针这里就可以了。因此优化方法使用指令操作的它是Cortex-M内核里面的指令所以执行速度更快然后我们来修改一下代码来实现一下支持多优先级1.任务控制块在任务控制块中增加与优先级相关的成员uxPriority/* 在FreeRTOS.h中修改 */ typedef struct tskTaskControlBlock { 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.修改xTaskCreateStatic():增加优先级形参数值越大优先级越高/* 在task.c和task.h中修改任务创建函数的定义和声明 */ /* 静态任务创建函数 函数定义和声明的地方都需要修改 */ TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, /* 任务入口 */ //TaskFunction_t在projdefs.h声明并且已经在#include FreeRTOS.h中声明引用 const char * const pcName, /* 任务名称字符串形式 */ const uint32_t ulStackDepth, /* 任务栈大小单位为字 */ void * const pvParameters, /* 任务形参 */ UBaseType_t uxPriority, /* 任务优先级数值越大优先级越高 */ StackType_t * const puxStackBuffer, /* 任务栈起始地址 */ TCB_t * const pxTaskBuffer ); /* 任务控制块指针 */ /* 此处的函数参数需要按照对上面说明的理解来添加到相应位置*/3.修改prvInitialiseNewTask():增加优先级形参和优先级初始化相关代码/* 此处代码完全在task.c中修改主要修改函数的参数以及在函数体中 * 添加“初始化优先级”的代码 */ /* 修改的参数主要是添加的任务优先级uxPriority的参数 * 主要修改了三个位置 * 1.xTaskCreateStatic()中调用prvInitialiseNewTask()的内容 * 2.prvInitialiseNewTask()的在task.c中的声明 * 3.prvInitialiseNewTask()函数体的参数 */ /* 初始化优先级的代码放在prvInitialiseNewTask()中 具体见附件代码*/ /* 此处先摘出来方便理解*/ /* 初始化优先级 */ if( uxPriority ( UBaseType_t ) configMAX_PRIORITIES ) { uxPriority ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U; } pxNewTCB-uxPriority uxPriority;4.实现prvAddNewTaskToReadyList():将新创建的任务添加到就绪列表(之前我们是在创建任务之后将任务添加到我们的就绪列表中见之前的main.c中)现在我们不需要指定而是让它自动根据优先级添加到就绪列表中。/* 在task.c中操作 */ /* 1.主要添加一个函数到xTaskCreateStatic()函数体中 */ /* 将任务添加到就绪列表 */ prvAddNewTaskToReadyList( pxNewTCB ); /* 2.实现函数prvAddNewTaskToReadyList() */ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB ) { /* 进入临界段 */ taskENTER_CRITICAL(); { /* 全局任务计时器加一操作 */ uxCurrentNumberOfTasks; /* 如果pxCurrentTCB为空则将pxCurrentTCB指向新创建的任务 */ if( pxCurrentTCB NULL ) { pxCurrentTCB pxNewTCB; /* 如果是第一次创建任务则需要初始化任务相关的列表 */ if( uxCurrentNumberOfTasks ( UBaseType_t ) 1 ) { /* 初始化任务相关的列表 */ prvInitialiseTaskLists(); } } else /* 如果pxCurrentTCB不为空则根据任务的优先级将pxCurrentTCB指向最高优先级任务的TCB */ { if( pxCurrentTCB-uxPriority pxNewTCB-uxPriority ) { pxCurrentTCB pxNewTCB; } } uxTaskNumber; /* 将任务添加到就绪列表 */ prvAddTaskToReadyList( pxNewTCB ); } /* 退出临界段 */ taskEXIT_CRITICAL(); } /* 3.实现函数prvAddNewTaskToReadyList()中的宏定义一一在相应文件中定义 */ /* 在task.h中定义 */ #define taskENTER_CRITICAL() portENTER_CRITICAL() #define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR() #define taskEXIT_CRITICAL() portEXIT_CRITICAL() #define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x ) /* 在task.c中定义 */ static volatile UBaseType_t uxCurrentNumberOfTasks ( UBaseType_t ) 0U; static UBaseType_t uxTaskNumber ( UBaseType_t ) 0U;5.实现prvInitialiseTaskLists():初始化任务相关的列表此处只有就绪列表6.将任务添加到就绪列表中。7.修改开启任务调度函数vTaskStartScheduler()我们在开启调度的时候创建了一个空闲任务我们需要修改将空闲任务的优先级添加到任务创建函数中/* 在task.c中修改相关部分*/ void vTaskStartScheduler( void ) { /*创建空闲任务start*/ TCB_t *pxIdleTaskTCBBuffer NULL; /* 用于指向空闲任务控制块 */ StackType_t *pxIdleTaskStackBuffer NULL; /* 用于空闲任务栈起始地址 */ uint32_t ulIdleTaskStackSize; /* 获取空闲任务的内存任务栈和任务TCB */ vApplicationGetIdleTaskMemory( pxIdleTaskTCBBuffer, pxIdleTaskStackBuffer, ulIdleTaskStackSize ); xIdleTaskHandle xTaskCreateStatic( (TaskFunction_t)prvIdleTask, /* 任务入口 */ (char *)IDLE, /* 任务名称字符串形式 */ (uint32_t)ulIdleTaskStackSize , /* 任务栈大小单位为字 */ (void *) NULL, /* 任务形参 */ (UBaseType_t) tskIDLE_PRIORITY, /* 任务优先级数值越大优先级越高 1*/ (StackType_t *)pxIdleTaskStackBuffer, /* 任务栈起始地址 */ (TCB_t *)pxIdleTaskTCBBuffer ); /* 任务控制块 */ /* 将任务添加到就绪列表 2*/ //vListInsertEnd( ( pxReadyTasksLists[0] ), ( ((TCB_t *)pxIdleTaskTCBBuffer)-xStateListItem ) ); /*创建空闲任务end*/ /* 手动指定第一个运行的任务 */ pxCurrentTCB Task1TCB; /* 启动调度器 */ if( xPortStartScheduler() ! pdFALSE ) { /* 调度器启动成功则不会返回即不会来到这里 */ } } /* 在task.h中声明*/ #define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U )1添加2我们在创建任务的函数里面已经把空闲任务添加到就绪列表里了所以这里不需要再重复添加指定的列表了所以这里注释掉8.修改vTaskDelay():根据优先级将优先级位图表uxTopReadyPriority中对应的位清零那为什么要清0呢只有将27位清零了之后才能找到25所以清零是很有必要的。/* 在task.c中修改相关部分*/ void vTaskDelay( const TickType_t xTicksToDelay ) { TCB_t *pxTCB NULL; /* 获取当前任务的TCB */ pxTCB pxCurrentTCB; /* 设置延时时间 */ pxTCB-xTicksToDelay xTicksToDelay; /* 将任务从就绪列表移除 1*/ //uxListRemove( ( pxTCB-xStateListItem ) ); taskRESET_READY_PRIORITY( pxTCB-uxPriority ); /* 任务切换 */ taskYIELD(); }1首先他本来是有两个操作的但是暂时还没有用到为什么他首先要移除移除这个变量从对应的就绪列表中移除目前暂时没有用到。因为我们在更新系统时基的时候是要靠扫描就绪列表里面的东西才发现他有没有到期而如果我们把它移除了呢它根本就扫不到那我们就不知道他有没有到期。所以暂时我们不能把它移除。我们只是将他的标志位清零就好了。清0了之后我们找最高优先级的任务的话它就不会找到这个27这里而是找到25。9.修改vTaskSwitchContext():直接调用函数taskSELECT_HIGHEST_PRIORITY_TASK()寻找优先级最高的就绪任务的TCB然后更新到pxCurrentTCB。/* 在task.c中修改相关部分*/ void vTaskSwitchContext( void ) { /* 获取优先级最高的就绪任务的TCB然后更新到pxCurrentTCB */ taskSELECT_HIGHEST_PRIORITY_TASK(); // /* 如果当前线程是空闲线程那么就去尝试执行线程1或者线程2 // 看看他们的延时时间是否结束如果线程的延时时间均没有到期 // 那就返回继续执行空闲线程 */ // if( pxCurrentTCB IdleTaskTCB ) // { // if(Task1TCB.xTicksToDelay 0) // { // pxCurrentTCB Task1TCB; // } // else if(Task2TCB.xTicksToDelay 0) // { // pxCurrentTCB Task2TCB; // } // else // { // return; /* 线程延时均没有到期则返回继续执行空闲线程 */ // } // } // else // { // /*如果当前线程是线程1或者线程2的话检查下另外一个线程,如果另外的线程不在延时中就切换到该线程 // 否则判断下当前线程是否应该进入延时状态如果是的话就切换到空闲线程。否则就不进行任何切换 */ // if(pxCurrentTCB Task1TCB) // { // if(Task2TCB.xTicksToDelay 0) // { // pxCurrentTCB Task2TCB; // } // else if(pxCurrentTCB-xTicksToDelay ! 0) // { // pxCurrentTCB IdleTaskTCB; // } // else // { // return; /* 返回不进行切换因为两个线程都处于延时中 */ // } // } // else if(pxCurrentTCB Task2TCB) // { // if(Task1TCB.xTicksToDelay 0) // { // pxCurrentTCB Task1TCB; // } // else if(pxCurrentTCB-xTicksToDelay ! 0) // { // pxCurrentTCB IdleTaskTCB; // } // else // { // return; /* 返回不进行切换因为两个线程都处于延时中 */ // } // } // } }10.修改xTaskIncrementTick()在原来的基础上增加当任务延时时间到将任务就绪的代码根据优先级将优先级位图表uxTopReadyPriority中对应的位置位。/* 在task.c中修改相关部分*/ void xTaskIncrementTick( void ) { TCB_t *pxTCB NULL; BaseType_t i 0; /* 更新系统时基计数器xTickCountxTickCount是一个在port.c中定义的全局变量 */ const TickType_t xConstTickCount xTickCount 1; xTickCount xConstTickCount; /* 扫描就绪列表中所有线程的xTicksToDelay如果不为0则减1 */ for(i0; iconfigMAX_PRIORITIES; i) { pxTCB ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( pxReadyTasksLists[i] ) ); if(pxTCB-xTicksToDelay 0) { pxTCB-xTicksToDelay --; /* 延时时间到将任务就绪 1*/ if( pxTCB-xTicksToDelay 0 ) { taskRECORD_READY_PRIORITY( pxTCB-uxPriority ); } } } /* 任务切换 */ portYIELD(); }1比如说当我们27的任务到期了之后之前我们把它清零了那么我们再把它置一这样我们又可以找到27这个任务了运行这个任务了。也就是说如果延时时间到了我们就把任务就绪一下。这样整个的过程差不多完成了。最后在main函数中实现创建任务并启动调度器。我们在main函数中传输任务的优先级。int main(void) { prvInitialiseTaskLists(); /* 创建任务 */ Task1_Handle xTaskCreateStatic( (TaskFunction_t)Task1_Entry, /* 任务入口 */ (char *)Task1, /* 任务名称字符串形式 */ (uint32_t)TASK1_STACK_SIZE , /* 任务栈大小单位为字 */ (void *) NULL, /* 任务形参 */ 1, //(1) (StackType_t *)Task1Stack, /* 任务栈起始地址 */ (TCB_t *)Task1TCB ); /* 任务控制块 */ //核心函数是vListInser(),将任务控制块的列表项插入到就绪列表中(2) //vListInser(( pxReadyTasksLists[1] ), ( ((TCB_t *)(Task1TCB))-xStateListItem )); /* 创建任务 */ Task1_Handle xTaskCreateStatic( (TaskFunction_t)Task2_Entry, /* 任务入口 */ (char *)Task2, /* 任务名称字符串形式 */ (uint32_t)TASK2_STACK_SIZE , /* 任务栈大小单位为字 */ (void *) NULL, /* 任务形参 */ 2, //(1) (StackType_t *)Task2Stack, /* 任务栈起始地址 */ (TCB_t *)Task2TCB ); /* 任务控制块 */ //vListInser( ( pxReadyTasksLists[2] ), ( ((TCB_t *)(Task2TCB))-xStateListItem ) ); //(2) vTaskStartScheduler(); for(;;) { /* 啥也不干 */ } }1添加优先级2将插入到就绪列表的部分注释掉/删掉最后仿真下现象如下总结一下为什么要加入就绪列表把它的位清零就是因为为了要找到最高优先级的任务假如不把位清零永远找到的都是27后面的任务都是无法运行的到的。所以每次更新完了之后把相应位清零时间到了再置一就好了。