深度解剖~ FreeRtos阅读笔记3 freertos调度器启动、中断优先级管理、中断优先级分组

3. freertos调度器启动、中断优先级管理、中断优先级分组

 

永远不要小看不起眼的东西,哪怕是短短的一行代码!编程

(某些图片分辨率过大显示不清楚,保存到本地或拖动可放大)。api

 

不少例程将vTaskStartScheduler函数做为main执行的最后一行代码,由于执行了vTaskStartScheduler后主权便交给了freertos调度器,程序永远不会在这个函数中获得返回。数组

vTaskStartScheduler主要动做有:建立IDLE任务,初始化systick等重要寄存器,手动触发中断从新配置MSP并返回到handle模式执行第一个任务。如图展现源码主要流程:函数

(调度器启动流程)this

流程分析:spa

3.1 建立IDLE任务

vTaskStartScheduler函数中建立了名为IDLE的任务,这个任务的功能先无论它,先饶它不死等对源码逐个击破后它就会不攻自破。设计

 

3.2 关中断

某些不可重入代码段或定义的一些全局变量,在使用它们以前每每先进入临界区保护下,以避免突发事件产生时破坏当前正要处理的数据。好比一些全局变量,若是在中断里或多个任务中都被使用到,当任务正在处理某个全局数组的数据时,此时中断发生并更新了数组中的数据,这样中断返回后就会产生一些不可预料的结果。调试

记得在实习那会写过很多这样的错误代码,没有考虑代码能不能重入,结果只能害人害己啊~。code

在task.c中有一些全局变量,freertos为了保护这些变量数据在处理时不被异常破坏而使用了中断屏蔽,不过这种操做并无将全部可屏蔽中断一股脑所有杀死:freertos提供了“可管理的中断范围”。以某个中断优先级为阈值,优先级低于(或等于)此分界线的中断将所有屏蔽,较高的中断则不受影响。固然不会白白让“高等”优先级执行的,代价即是freertos不会让高优先级的中断参与任何api的调用。因此“高等”优先级是否触发都不会影响freertos的正常运行。事件

事实上可以有如此方便的屏蔽操做还要归功于CM3/CM4中NVIC的强大。写到这里,终于引出今天的主角了~。NVIC :嵌套向量中断控制器是CM3/CM4内核水平上搭载了一个异常响应系统,支持为数众多的系统异常和外部中断。不只如此,NVIC拥有者更多精巧的设计,BASEPRI寄存器即是其中之一,屏蔽中断所使用的阈值就存储在其中,屏蔽时将须要屏蔽中断的最高优先级值填入寄存器便能达到效果。若是向BASEPRI写0则意味着中止掩蔽任何中断。

在配置头文件中能够看到阈值的身影:configMAX_SYSCALL_INTERRUPT_PRIORITY

freertos执行中断屏蔽的源码:

mov r1, #configMAX_SYSCALL_INTERRUPT_PRIORITY                    

msr basepri, r1   

 

3.3 优先级定义

CM3中使用了8位寄存器来表达中断优先级,因此原则上,CM3支持256级可编程优先级和3个固定优先级(复位,NMI ,硬 fault)。

可是,绝大多数 CM3 芯片都会精简设计,对优先级有效位数进行裁剪以达到减小优先级数的目的,不过不管怎样裁剪,有效位都是在高位开始算起。

注:CM3 容许的最少使用位数为 3 个位,亦即至少要支持 8 级优先级。增长位数将增长更多成本和功耗。

优先级的数值越小,则优先级越高

举个例子,使用了3位有效位表达优先级的寄存器:

(使用了3位有效位表达优先级)

对有效位写1能读回1,无效位读回0,上图寄存器中,若对它写入0xFF则读回0xE0,可使用读回的方法判断芯片支持多少级中断。

对比下3位和4位优先级情况:

(3-4优先级对比)

上图仍是来自宝典,有图截取真爽。很明显4位要比3位表示的优先级范围更细致些,固然这是废话,不过为何必定要将有效位放在高位,而不放在低位更直观。这也彻底是为了开发者考虑,高位能够简化程序的跨器件移植:

假设一个程序要从4位向3位上移植(3位向4位上移植确定是没问题的,由于4位范围更广),在没有刻意更改优先级的状况下如图:

(MSB减小有效位情况)

移植后优先级有效位减小,原来相邻的两个优先级被置为一个,消失的中断优先级被拉高,不过这些改变将不会带来致命错误。

若是使用低位来表明有效位的话将不会这样幸运了:

(LSB减小有效位情况)

红色区域出现移植后超过7的优先级反而升高,这对于以前的程序将会带来不可预测的错误。

 

3.4优先级分组

最后一个函数xPortStartScheduler中有一段断言宏打开时执行的代码,调试阶段确定要将断言配置打开,这样便于程序在调试阶段及时发现异常。程序段以下:

乍一看彻底不知道执行这段程想要达到的目的,想要弄清楚以前首先要搞懂优先级分组的原则。

关于优先级分组的信息,引用宝典的原话:“为了使抢占机能变得更可控,CM3 还把 256 级优先级按位分红高低两段,分别是抢占优先级和亚优先级。 NVIC 中有一个寄存器是“应用程序中断及复位控制寄存器”,它里面有一个位段名为“优先级组”。该位段的值对每个优先级可配置的异常都有影响——把其优先级分为个位段:MSB 所在的位段(左边的)对应抢占优先级,而 LSB 所在的位段(右边的)对应亚优先级”。

(分组位与分组优先级对应表)

抢占优先级决定了抢占行为:当系统正在响应某异常 L 时,若是来了抢占优先级更高的异常 H,则 H 能够抢占 L。亚优先级则处理“内务”:当抢占优先级相同的异常有不止一个悬起时,就优先响应亚优先级最高的异常。这种优先级分组规定:亚优先级至少是 1 个位。所以抢占优先级最可能是 7 个位,形成了最多只有 128 级抢占的现象。可是 CM3 容许从比特 7 处分组,此时全部的位都表达亚优先级,没有任何位表达抢占优先级,于是全部优先级可编程的异常之间就不会发生抢占——至关于在它们之中除能了CM3 的中断嵌套机制。

注:分组位置能够在无效位,在任意无效位的效果都同样,它们都舍去了亚优先级。

那么了解优先级分组以后,再去分析上面断言宏的主要动做,先向某个外部中断优先级配置寄存器写0xFF再读回,主要判断芯片有多少优先级有效位,而后根据有效位左移依次将最大分组数7减1,这样便获得了可以划分抢占优先级为最大数的分组最大位置,例如开发板优先级有效位为3(优先级寄存器7,6,5位),优先级最多能被分为8组,可是可以将其分为8组的分组位置为4,3,2,1,0。这段程序只取最大值因此ulMaxPRIGROUPValue值获得为4。

ulMaxPRIGROUPValue值将会在freertos提供的中断api中被使用到,这些api中都会首先执行vPortValidateInterruptPriority()函数。这个函数中有两个断言宏,第一个检查了当前的中断优先级是否低于可屏蔽的中断(上文提到高于可屏蔽的优先级不容许调用freertos的中断api);第二个检查NVIC中设置的分组值是否大于ulMaxPRIGROUPValue,若是大于则意味着存在着主优先级和亚优先级,freertos不容许存在亚优先级,不然断言宏伺候,贴下注释原话:Priority grouping:  The interrupt controller (NVIC) allows the bits that define each interrupt's priority to be split between bits that define the interrupt's pre-emption priority bits and bits that define the interrupt's sub-priority.  For simplicity all bits must be defined to be pre-emption priority bits.  The following assertion will fail if this is not the case (if some bits represent a sub-priority).

按注释的描述是为了简化而不去设置亚优先级。

 

3.5 调度器最后的配置

在调度器启动以前还进行了:

1.      配置了两个中断PendSV 和SysTick的优先级,配置头文件中的宏

configKERNEL_INTERRUPT_PRIORITY表示它们的中断级别,configKERNEL_INTERRUPT_PRIORITY数值必定要大于或等于宏configMAX_SYSCALL_INTERRUPT_PRIORITY的值,或者说PendSV 和SysTick中断优先级必定要在可屏蔽中断范围内,若相反的话可就乱成一锅粥了。

2.      开启freertos的心脏systick,填入计数值来决定systick中断频率,也是任务调度频率或说是时间片长度。

3.      跳入prvPortStartFirstTask函数:

ldr r0, NVIC_VTABLE_R  // NVIC_VTABLE_R: 0xE000ED08 ,向量表偏移量寄存器的地址,须要现将向量表重定向

ldr r0, [r0]        

ldr r0, [r0]      //取出MSP新地址  

msr msp, r0    //从新配置MSP地址                               

cpsie i       //开中断                     

dsb

isb

svc #0   //触发SVC中断

至此,在进入SVC中断以前,freertos开启调度器代码流程已执行完毕,等待SVC中断处理完成后freertos便进入正轨。奔跑吧!fucking source code

相关文章
相关标签/搜索