freeRTOS 任务切换
使用PendSV实现任务切换
上下文切换被触发的场合可以是:
#1 执行一个系统调用
#2 系统滴答定时器(SysTick)中断。
PendSV中断服务函数
TaskSelectHighestPrior的两种方法
#3 最后补充了时间片调度相关的东西。
在调度器不挂起的情况下,在任务函数中,一旦置位PendSV请求,立即产生任务切换。
一旦置位,立即切换!
一旦置位,立即切换!
一旦置位,立即切换!
一、系统调用 taskYIELD:
#define taskYIELD() portYIELD()
#define portYIELD() \ { \ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \ 往寄存器地址0xe000ed04中,写入1<<28ul.置位PendSV异常。 __dsb( portSY_FULL_READ_WRITE ); \ Barriers寄存器什么的,不懂 __isb( portSY_FULL_READ_WRITE ); \ }
/*-----------------------------------------------------------*/ #define portNVIC_INT_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) ) #define portNVIC_PENDSVSET_BIT ( 1UL << 28UL )
#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired != pdFALSE ) portYIELD() #define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x ) 中断中切换任务 /*-----------------------------------------------------------*/
二、系统滴答定时器中断:
void SysTick_Handler(void) { if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行 { xPortSysTickHandler(); } HAL_IncTick(); } void xPortSysTickHandler( void ) { vPortRaiseBASEPRI(); 关中断 { if( xTaskIncrementTick() != pdFALSE ) //增加时钟计数器 xTickCount 的值,返回值ture时需要切换任务。函数TaskIncrementTick见章节:时间管理 { portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; 置位PendSV } } vPortClearBASEPRIFromISR(); 开中断 }
关中断、开中断
static portFORCE_INLINE void vPortRaiseBASEPRI( void ) { uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { /* Set BASEPRI to the max syscall priority to effect a critical BASEPRI设为Max_syscall,实际上产生了临界段 section. */ msr basepri, ulNewBASEPRI dsb isb } } /*-----------------------------------------------------------*/ static portFORCE_INLINE void vPortClearBASEPRIFromISR( void ) { __asm { /* Set BASEPRI to 0 so no interrupts are masked. This function is only used to lower the mask in an interrupt, so memory barriers are not used. */ msr basepri, #0 } } /*-----------------------------------------------------------*/
系统调用TaskYield和滴答定时器中断,都在做同一个事情。就是置位PendSV异常。
然后看一下PendSV中断服务函数:
1 #define xPortPendSVHandler PendSV_Handler 2 3 __asm void xPortPendSVHandler( void ) 4 { 5 extern uxCriticalNesting; 6 extern pxCurrentTCB; 7 extern vTaskSwitchContext; 8 9 PRESERVE8 10 11 mrs r0, psp 12 isb 13 /* Get the location of the current TCB. */ 14 ldr r3, =pxCurrentTCB 15 ldr r2, [r3] 16 17 /* Is the task using the FPU context? If so, push high vfp registers. */ 18 tst r14, #0x10 19 it eq 20 vstmdbeq r0!, {s16-s31} 21 22 /* Save the core registers. */ 23 stmdb r0!, {r4-r11, r14} 24 25 /* Save the new top of stack into the first member of the TCB. */ 26 str r0, [r2] 27 28 stmdb sp!, {r3} 29 mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY 30 msr basepri, r0 31 dsb 32 isb 33 bl vTaskSwitchContext 任务调度函数34 mov r0, #0 35 msr basepri, r0 36 ldmia sp!, {r3} 37 38 /* The first item in pxCurrentTCB is the task top of stack. */ 39 ldr r1, [r3] 40 ldr r0, [r1] 41 42 /* Pop the core registers. */ 43 ldmia r0!, {r4-r11, r14} 44 45 /* Is the task using the FPU context? If so, pop the high vfp registers 46 too. */ 47 tst r14, #0x10 48 it eq 49 vldmiaeq r0!, {s16-s31} 50 51 msr psp, r0 52 isb 53 #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */ 54 #if WORKAROUND_PMU_CM001 == 1 55 push { r14 } 56 pop { pc } 57 nop 58 #endif 59 60 #endif 61 bx r14 62 }
1 void vTaskSwitchContext( void ) 2 { 3 if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE ) 4 { 5 /* The scheduler is currently suspended - do not allow a context 6 switch. */ 7 xYieldPending = pdTRUE; 8 } 9 else 10 { 11 xYieldPending = pdFALSE; 12 traceTASK_SWITCHED_OUT(); 13 14 #if ( configGENERATE_RUN_TIME_STATS == 1 ) 【略】 15 { 16 #ifdef portALT_GET_RUN_TIME_COUNTER_VALUE 17 portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime ); 18 #else 19 ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE(); 20 #endif 21 22 /* Add the amount of time the task has been running to the 23 accumulated time so far. The time the task started running was 24 stored in ulTaskSwitchedInTime. Note that there is no overflow 25 protection here so count values are only valid until the timer 26 overflows. The guard against negative values is to protect 27 against suspect run time stat counter implementations - which 28 are provided by the application, not the kernel. */ 29 if( ulTotalRunTime > ulTaskSwitchedInTime ) 30 { 31 pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime ); 32 } 33 else 34 { 35 mtCOVERAGE_TEST_MARKER(); 36 } 37 ulTaskSwitchedInTime = ulTotalRunTime; 38 } 39 #endif /* configGENERATE_RUN_TIME_STATS */ 40 41 /* Check for stack overflow, if configured. */ 42 taskCHECK_FOR_STACK_OVERFLOW(); 43 44 /* Select a new task to run using either the generic C or port 45 optimised asm code. */ 46 taskSELECT_HIGHEST_PRIORITY_TASK(); 47 traceTASK_SWITCHED_IN(); 48 49 #if ( configUSE_NEWLIB_REENTRANT == 1 )【略】 50 { 51 /* Switch Newlib's _impure_ptr variable to point to the _reent 52 structure specific to this task. */ 53 _impure_ptr = &( pxCurrentTCB->xNewLib_reent ); 54 } 55 #endif /* configUSE_NEWLIB_REENTRANT */ 56 } 57 }
TaskSelectHighestPrior的方法有两种:
方法1:通用的c语言方法 #define taskSELECT_HIGHEST_PRIORITY_TASK() \ { \ UBaseType_t uxTopPriority = uxTopReadyPriority; \ \ /* Find the highest priority queue that contains ready tasks. */ \ while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \ { \ configASSERT( uxTopPriority ); \ --uxTopPriority; \ } \ \ /* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of \ the same priority get an equal share of the processor time. */ \ listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \ uxTopReadyPriority = uxTopPriority; \ } /* taskSELECT_HIGHEST_PRIORITY_TASK */
(1)uxTopReadyPriority 代表处于就绪态的最高优先级值,
每次创建任务时都会判断新任务的优先级是否大于 uxTopReadyPriority,如果大于的话就将这个新任务的优先级赋值给变量 uxTopReadyPriority。
函数 prvAddTaskToReadyList() 也会修改这个值,将某个任务添加到就绪列表中的时候,用 uxTopReadyPriority 来记录就绪列表中的最高优先级。
这里就从这个最高优先级开始判断,看看哪个列表不为空就说明哪个优先级有就绪的任务。
函数 listLIST_IS_EMPTY()用于判断某个列表是否为空,
uxTopPriority 用来记录这个有就绪任务的优先级。(临时值)
(2)已经找到了有就绪任务的优先级了, 接下来就是从对应的列表中找出下一个要运行的任务,
查找方法就是使用函数 listGET_OWNER_OF_NEXT_ENTRY()来获取列表中的下一个列表项,
然后将获取到的列表项所对应的任务控制块赋值给 pxCurrentTCB,这样我们就确定了下一个要运行的任务了。
可以看出通用方法是完全通过 C 语言来实现的,肯定适用于不同的芯片和平台, 而且对于任务数量没有限制, 但是效率肯定相对于使用硬件方法的要低很多。
1 方法2:硬件实现 2 #define taskSELECT_HIGHEST_PRIORITY_TASK() \ 3 { \ 4 UBaseType_t uxTopPriority; \ 5 \ 6 /* Find the highest priority list that contains ready tasks. */ \ 7 portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \ 8 configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \ 9 listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \ 10 }
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
使用硬件方法的时候 uxTopReadyPriority 就不代表处于就绪态的最高优先级了,而是使用每个 bit 代表一个优先级, bit0 代表优先级 0, bit31 就代表优先级 31,
当某个优先级有就绪任务的话就将其对应的 bit 置 1。
从这里就可以看出,如果使用硬件方法的话最多只能有 32 个优先级。
__clz(uxReadyPriorities)就是计算 uxReadyPriorities 的前导零个数(汇编指令CLZ)
得到 uxTopReadyPriority 的前导零个数以后在用 31 减去这个前导零个数,得到的就是处于就绪态的最高优先级了,
比如优先级 30 为此时的处于就绪态的最高优先级, 30 的前导零个数为1,那么 31-1=30,得到处于就绪态的最高优先级为 30。
可以看出硬件方法借助一个指令就可以快速的获取处于就绪态的最高优先级,但是会限制任务的优先级数。
三、最后说一点时间片调度相关的东西:
FreeRTOS 支持多个任务同时拥有一个优先级, 一个任务运行一个时间片(一个时钟节拍的长度)后让出 CPU 的使用权,让拥有同优先级的下一个任务运行。
时间片的长度由宏 configTICK_RATE_HZ 来确定,一个时间片的长度就是滴答定时器的中断周期,比如configTICK_RATE_HZ 为 1000,那么一个时间片的长度就是 1ms。
SysTick_Handler -> xPortSysTickHandler -> xTaskIncrementTick如下:
1 BaseType_t xTaskIncrementTick( void ) 2 { 3 TCB_t * pxTCB; 4 TickType_t xItemValue; 5 BaseType_t xSwitchRequired = pdFALSE; 6 7 /* Called by the portable layer each time a tick interrupt occurs. 8 Increments the tick then checks to see if the new tick value will cause any 9 tasks to be unblocked. */ 10 traceTASK_INCREMENT_TICK( xTickCount ); 11 if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) 调度器没挂起 12 { 112 /* Tasks of equal priority to the currently running task will share 113 processing time (time slice) if preemption is on, and the application 114 writer has not explicitly turned time slicing off. */ 115 #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) 时间片相关的处理 116 { 117 if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 ) 118 { 119 xSwitchRequired = pdTRUE; 120 } 121 else 122 { 123 mtCOVERAGE_TEST_MARKER(); 124 } 125 } 126 #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */ } 169 return xSwitchRequired; 170 }
如果当前任务所对应的优先级下有其他的任务存在,那么函数xTaskIncrementTick() 就会返回 pdTURE ,
由于函数返回值为 pdTURE ,因此函数 xPortSysTickHandler() 就会进行一次任务切换。
@@TaskIncrementTick其他的处理,见时间管理章节。
留白
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步