标签:
FreeRTOS任务相关的代码大约占总代码的一半左右,这些代码都在为一件事情而努力,即找到优先级最高的就绪任务,并使之获得CPU运行权。任务切换是这一过程的直接实施者,为了更快的找到优先级最高的就绪任务,任务切换的代码通常都是精心设计的,甚至会用到汇编指令或者与硬件相关的特性,比如Cortex-M3的CLZ指令。因此任务切换的大部分代码是由硬件移植层提供的,不同的平台,实现发方法也可能不同,这篇文章以Cortex-M3为例,讲述FreeRTOS任务切换的过程。#define portYIELD() { /*产生PendSV中断*/ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; }对于第二种任务切换方法,在系统节拍时钟中断服务函数中,首先会更新tick计数器的值、查看是否有任务解除阻塞,如果有任务解除阻塞的话,则使能PandSV中断,代码如下所示:
void xPortSysTickHandler( void ) { /* 设置中断掩码 */ vPortRaiseBASEPRI(); { /* 增加tick计数器值,并检查是否有任务解除阻塞 */ if( xTaskIncrementTick() != pdFALSE ) { /* 需要任务切换。产生PendSV中断 */ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; } } vPortClearBASEPRIFromISR(); }从上面的代码中可以看出,PendSV中断的产生是通过代码:portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT实现的,它向中断状态寄存器bit28位写入1,将PendSV中断设置为挂起状态,等到优先级高于PendSV的中断执行完成后,PendSV中断服务程序将被执行,进行任务切换工作。
__asm void xPortPendSVHandler( void ) { extern uxCriticalNesting; extern pxCurrentTCB; /* 指向当前激活的任务 */ extern vTaskSwitchContext; PRESERVE8 mrs r0, psp /* PSP内容存入R0 */ isb /* 指令同步隔离,清流水线 */ ldr r3, =pxCurrentTCB /* 当前激活的任务TCB指针存入R2 */ ldr r2, [r3] stmdb r0!, {r4-r11} /* 保存剩余的寄存器,异常处理程序执行前,硬件自动将xPSR、PC、LR、R12、R0-R3入栈 */ str r0, [r2] /* 将新的栈顶保存到任务TCB的第一个成员中 */ stmdb sp!, {r3, r14} /* 将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext,调用函数时,返回地址自动保存到R14中,所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护; R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护*/ mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY /* 进入临界区 */ msr basepri, r0 dsb /* 数据和指令同步隔离 */ isb bl vTaskSwitchContext /* 调用函数,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换 */ mov r0, #0 /* 退出临界区*/ msr basepri, r0 ldmia sp!, {r3, r14} /* 恢复R3和R14*/ ldr r1, [r3] ldr r0, [r1] /* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/ ldmia r0!, {r4-r11} /* 出栈*/ msr psp, r0 isb bx r14 /* 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回,然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。*/ nop }为了便于理解上面的代码,我们先用流程图的方式将整个过程画出来,然后再逐句分析代码。因为图形可以简化程序,并且信息更容易接受。
mrs r0, psp是将任务堆栈指针PSP的值保存到寄存器R0中,因为接下来我们会将寄存器R4~R11也保存到任务堆栈中,但是我们没有哪个汇编指令能直接操作PSP完成入栈,所以只能借助R0。
ldr r3, =pxCurrentTCB /* 当前激活的任务TCB指针存入R2 */ ldr r2, [r3]这两句代码是获取当前激活的任务TCP指针,指针pxCurrentTCB前面文章已经提到过很多次了,它是位于tasks.c文件中定义的唯一一个全局指针型变量,指向当前激活的任务TCB。
stmdb r0!, {r4-r11}这句代码用于将寄存器R4~R11保存到当前激活的程序任务堆栈中,并且同步更新寄存器R0的值。
str r0, [r2]寄存器R2中保存当前激活的任务TCB指针,在《FreeRTOS高级篇2---FreeRTOS任务创建分析》中讲任务TCB数据结构时我们知道,任务TCB数据结构第一个成员一定是指向任务当前堆栈栈顶的指针变量pxTopOfStack。这句代码将R0的内容保存到任务TCB数据结构的第一个成员pxTopOfStack中,也就是将最新的任务堆栈指针保存到任务TCB的pxTopOfStack字段中。当任务被激活时,就是从这个字段中获取任务堆栈指针,然后完成数据出栈操作的。
stmdb sp!, {r3, r14}将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext。调用函数时,返回地址自动保存到R14中,所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护。R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护。
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0这两句代码用来进入临界区,中断优先级大于等于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断都会被屏蔽。
bl vTaskSwitchContext调用函数,选择下一个要执行的任务,也就是寻找处于就绪态的最高优先级任务。变量pxCurrentTCB指向找到的任务TCB。这个函数是核心中的核心,所有的其它代码都是为了保证这个函数能正确运行。
#define taskSELECT_HIGHEST_PRIORITY_TASK() { /* 从就绪列表数组中找出最高优先级列表*/ while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ) ) { configASSERT( uxTopReadyPriority ); --uxTopReadyPriority; } /* 相同优先级的任务使用时间片共享处理器就是通过这个宏实现*/ listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &( pxReadyTasksLists[ uxTopReadyPriority ] ) ); } /* taskSELECT_HIGHEST_PRIORITY_TASK */对于Cortex-M3硬件,还支持特殊方法选择下一个要执行的任务,那就是利用硬件提供的计算前导零指令CLZ。特殊方法时,宏taskSELECT_HIGHEST_PRIORITY_TASK()的代码如下所示。
#define taskSELECT_HIGHEST_PRIORITY_TASK() { UBaseType_t uxTopPriority; /* 从就绪列表数组中找出最高优先级列表*/ portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); } /* taskSELECT_HIGHEST_PRIORITY_TASK() */与通用方法相比,可以发现从就绪列表数组中找出最高优先级列表代码不同了,特殊方法使用宏portGET_HIGHEST_PRIORITY来实现,将宏定义替换后,代码为:
uxTopPriority = ( 31UL - ( uint32_t ) __clz( (uxTopReadyPriority) ) )在此之前,静态变量uxTopReadyPriority同样已经包含处于就绪态任务的最高优先级的信息。与通用方法中使用任务优先级数值不同,在特殊方法中,uxTopReadyPriority使用每一位来表示任务,比如变量uxTopReadyPriority的bit0为1,则表示存在优先级为0的就绪任务,bit10为1则表示存在优先级为10的就绪任务。由于32位整形数最多只有32位,因此使用这种特殊方法限定最大可用优先级数目为32,即优先级0~31。
mov r0, #0 /* 退出临界区*/ msr basepri, r0这两句代码用来退出临界区,通过向寄存器BASEPRI写入数值0来实现。
ldmia sp!, {r3, r14}这句代码将寄存器R3和R14从堆栈中恢复,现在R3保存变量pxCurrentTCB的地址,需要注意的是,变量pxCurrentTCB在函数vTaskSwitchContext中可能已被修改,指向新的最高优先级就绪任务;R14保存退出异常需要的信息。
ldr r1, [r3] ldr r0, [r1]这两句代码获取变量pxCurrentTCB指向的任务TCB指针,并将TCB的第一个成员——当前堆栈栈顶的指针变量pxTopOfStack的值保存到寄存器R0中,也就是将即将运行的任务堆栈栈顶值存入R0。
ldmia r0!, {r4-r11}将寄存器R4~R11出栈,并同时更新R0的值。
msr psp, r0
将最新的任务堆栈栈顶赋值给线程堆栈指针PSP。
bx r14从异常中断服务程序退出。异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、使用PSP堆栈指针还是MSP堆栈指针。当调用 bx r14指令后,硬件会知道要从异常返回,然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。
标签:
原文地址:http://blog.csdn.net/zhzht19861011/article/details/51418383