目录
1 临界段应用
1.1临界段的作用
1.2临界段API
1.3临界段应用
2 临界段原理
2.1FreeRTOS中断管理实现
2.2关开中断实现
2.3临界段实现
3 任务栈大小确定
3.1确定
3.2MKD-htm文件分析
3.3堆栈检测API
4 栈溢出检测应用
4.1栈溢出检测方案一
4.2栈溢出检测方案二
4.3获取任务状态方案(推荐)
5 CPU使用率
5.1 CPU利用率统计的作用
5.2 CPU利用率统计API
5.3 CPU利用率统计实现
不想被打断访问的资源
问:什么是不可重入函数?
比如使用静态全局变量,这时候被系统打断,那么会出问题;还比如malloc,分配到一半,被系统打断。
问:使用硬件资源为什么不希望被打断?
串口、SPI、II2 通信的时候,不希望被打断
问:什么情况下对时序有精准要求的操作不希望被打断?
比如写了一个驱动,延时被打断
门限值 ,低于配置宏定义的优先级的中断才会关闭
保证实时性操作不会屏蔽,不允许嵌套
上述两个接口一般不建议这样使用,特殊情况下才使用。
注意事项
下面是专门在中断中使用的临界段接口函数
问:为何要返回上次中断屏蔽寄存器的操作值?
因为这个接口支持嵌套,内核在调用,其他任务也可以调用,那么退出中断的时候,要恢复之前临界段的状态,不影响其他任务而产生错误
1、分别修改Usart_Task、DelayTask任务
2、配置延时周期为50ms打印一次运行状态,观察现象,是否出现,资源冲突问题
3、当多任务共同使用串口的时候,如果冲突,如何通过临界段解决共享资源冲突问题
这是未使用临界段的情况
问:出现这个问题的原因?
当高优先级任务就绪态的时候,会抢占低优先级的任务,但是当恢复的时候,usarttask和delaytask相同优先级,会看谁的阻塞时间先到,之后再会返回给UsartTask
问:如何解决?
在printf的时候用临界段包裹一下
相比STM32的优先级8bit,FreeRTOS没有亚优先级的位段,只有4bit ,对应3
main(void) //---->
HAL_Init(); //---->
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); //---->3
FreeRTOS采用最后一种BASERPRI
门限值是5,>5要屏蔽掉,<5没关系,可以响应
屏蔽 15<<(8-5),因为内核优先级在高4位,所以最低是这么设置屏蔽
屏蔽 5<<(8-5),因为内核优先级在高4位,所以最低是这么设置屏蔽
开中断实现
//portENTER_CRITICAL更硬件平台有关系,如果要移植其他平台函数要自己写
//uxCriticalNesting++嵌套增加,使用启动程序的时候用到过变为0。如果第一次检查一下参数
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
}
#define portDISABLE_INTERRUPTS vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/*将BASEPRI设置为最大系统调用优先级,以实现critical部分。 */
msr basepri, ulNewBASEPRI //basepri赋值为5
dsb
isb
}
}
关中断实现
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
/* Barrier instructions are not used as this function is only used to
lower the BASEPRI value. */
msr basepri, ulBASEPRI
}
}
总结:freeRTOS都是通过门限值来开关中断的。
进入临界段
如果移植其他平台,这段代码要自己实现,FreeRTOS为我们实现的代码如下
void vTaskEnterCritical( void )
{
//屏蔽中断寄存器设置为5
portDISABLE_INTERRUPTS();
if( xSchedulerRunning != pdFALSE )
{
//嵌套功能+1,启动内核时候是0
( pxCurrentTCB->uxCriticalNesting )++;
//是否第一次使用
if( pxCurrentTCB->uxCriticalNesting == 1 )
{
//检查
portASSERT_IF_IN_ISR();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
退出临界段
void vTaskExitCritical( void )
{
if( xSchedulerRunning != pdFALSE )
{
if( pxCurrentTCB->uxCriticalNesting > 0U )
{
( pxCurrentTCB->uxCriticalNesting )--;
//等于0,主要是用于判断是否成对出现
if( pxCurrentTCB->uxCriticalNesting == 0U )
{
portENABLE_INTERRUPTS();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
中断中进入临界段
带返回值是用来满足嵌套功能
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
//设置屏蔽寄存器的值,以及返回值
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* 将目前屏蔽寄存器的值读出来返回*/
mrs ulReturn, basepri
/* 将BASEPRI设置为最大系统调用优先级*/
msr basepri, ulNewBASEPRI
dsb
isb
}
return ulReturn;
}
中断中退出临界段
如果第一次使用,那么是0,
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
/*每次退出把值写入中断屏蔽寄存器中 */
msr basepri, ulBASEPRI
}
}
总结:
其中任务栈PSP,系统栈MSP
以上的分析太繁琐,需要研究函数循环嵌套等,以下方式更简单。
注意:函数指针、中断嵌套是不在分析范围内的
栈溢出检测:针对TOP指针和stack起始地址分析,当然只能在任务切换的时候检测,但是running的时候没法检测。所以FreeRTOS初始化数据为0xa5,末尾16个字节也是0xa5,如果不是0xa5就会栈溢出。
上例分析: 代码长度22byte,栈空间0byte,栈最大深度72字节,主要调用了Calls5处内容,当然类似printf属于C库大小未能分析准确。
方案一、在任务切换时检测任务栈指针是否过界
方案二、任务创建的时候将任务栈所有数据初始化为 0xa5,任务切换时进行任务栈检测的时候会检测末尾的 16 个字节是否都是 0xa5
实验流程
开启检测方案一
模拟溢出,根据满减栈特点,逆序写入0x88
回调函数打印便可知道是否溢出
效果:已经溢出了,打印的时候会有乱码,但是最后又OverFlow标志
方案一、二都会有弊端
其余不变,编译结果
功能需求
使能两个宏定义
定义一个全局buff
在按键按下后调用vTasklist接口,并打印
效果
如果stack接近0,说明堆栈将用尽,以字为单位。
发现内存、cpu卡顿的原因
与vTaskList差不多,名称、绝对时间,时间占用率。
注意事项:
业务流程:
根据stm32参考手手册配置定时器
20us的频率,计算公式:84Mhz/85/50=20
使能TIM6中断
新建全局变量
回调函数中计数
改写并调用vTaskGetRunTimsStats
实验效果