OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 3826|回复: 3

初次尝试~优化UCOSII在CM4带FPU上的压栈

[复制链接]

9

主题

44

帖子

0

精华

初级会员

Rank: 2

积分
100
金钱
100
注册时间
2016-6-5
在线时间
27 小时
发表于 2016-6-5 12:36:41 | 显示全部楼层 |阅读模式
本帖最后由 Tekmarine 于 2016-6-5 15:11 编辑

翻了半天的《ARM Cortex-M3Cortex-M4权威指南》,想出这么个能凑合着用的办法,如有雷同纯属巧合。以下都是在Explorer开发板附带的UCOS例程上修改,IDEKeil MDK 5.11


原始的例程在切换任务时,对浮点计算单元的处理是把全部寄存器都压栈,实际上发现即使任务没有浮点计算,也会有把S0-S31FPSCR都压栈。另外,在初始化任务栈时,得给FPU寄存器留空间,否则弹栈时就会出错。感觉这样有点浪费内存和时间。以下是我想的一个办法,貌似运行还算正常

先看下和FPU有关的异常标志:
EXC_RET.png

没找到中文版的PDF,凑合着看吧

没找到中文版的PDF,凑合着看吧
FPCA.jpg
这个是原始的PendSV_Handler
SV.jpg
处理和FPU相关时,是先检查R14的bit4,也就是看EXC_RETURN指示是否执行了浮点指令,如果有就压S15-S31,然后载入下一个任务的TCB,再检查LR的bit4,若为0就弹出S15-S31。
不过这样会出两个问题,其一是这样实际是不知道下一个任务是否是要用FPU,因为设置Control的FPCA位和EXC_RETURN的是上一个任务。
假如上一个任务用了FPU,那么LR的bit4就是0,但如果下一个任务不用FPU,由于此时的LR的bit4还是0,那么就会按长帧弹栈,假若下一个任务的栈里没给FPU留位置,那么就会弹栈顺序出错,然后就是Hardfault了。另一个问题就是,在Reset_Handler里,进入主函数main()前,还有个初始化的函数__main(),这个函数会初始化FPU,其中会执行VMRS指令来设置FPU的工作模式,这样会把Control的FPCA置1。所以进入main()后就会看到说执行浮点指令了,这个标志会一直保持到有中断产生,并且会让进入中断的压栈是压长帧。
QQ图片20160605133226.png
而且,由于启动UCOSII会调用OSStartHighRdy()函数,触发PendSV中断,而PendSV_Handler会根据LR的bit4确定是否压FPU——显然,由于一开始Control的FPCA就是1,所以LR的bit4是0,这样就会执行VSTMDBEQ和VLDMIAEQ指令,原本被中断清零的Control的FPCA又被置1了。以上的结果就是,在OS运行期间,Control的FPCA位一直为1,不管有没有执行浮点指令,对此的解决办法通常就是把所有的任务都当成用FPU的,比如初始化任务栈时,所有的任务都是按带FPU初始化
QQ图片20160605135409.jpg
这样是可以正常运行,但是感觉浪费了不少的内存空间,因为不是所有的任务都有浮点计算,而且这样也拖慢了任务切换速度。








以下是本人想出的解决办法,可以运行,只是不知道是否会在未来某个时候出些BUG……因为得改写部分内核代码
首先,由于在切换任务时,是不知道下一个任务是否有浮点计算的,但编程序的人肯定知道,所以可以人为的加个flag,比如就加在任务的TCB里面,设个是否使用FPU的标志
QQ图片20160605140344.png
对应的要修改OSTaskCreate()函数和OSTaskStkInit(),OS_TCBInit(),为这三个增加一个输入参数FPU_flag,用于指示任务是否有浮点计算
QQ图片20160605140704.png
QQ图片20160605140710.png
QQ图片20160605141534.png
OSTaskCreate()里的修改如下,只是给OSTaskStkInit(),OS_TCBInit()传参数,OSTaskCreateExt()什么的暂时不管
QQ图片20160605141230.png
OS_TCBInit()的里面要增加给TCB里新加的那个FPU标志赋值
QQ图片20160605141757.png
OSTaskStkInit()则是在原本给FPU留位置的地方加上判断是否用FPU,没有就不初始化,这个地方有两处
QQ图片20160605141840.png
以上修改就是为任务的TCB加个是否使用FPU的标志位,如果用了FPU,那么任务栈就得给S0-S15,S16-S31留空间并初始化;如果不用FPU,那么就不必初始化,而且任务栈可以设小一些,比如给默认的Idle_Task留24个字就行了

最后就是改PendSV_Handler了,这个得用汇编写
我的修改如下
QQ图片20160605143110.jpg
红线以上是和以前的代码一样的,到载入新任务的TCB指针前都不用改,我增加的是红框里的,因为我把加在TCB里的标志放在栈指针后,所以地址偏移是4,红框里的第一句是读取是否使用FPU的标志。

在此再次说明,退出中断的是用BX LR指令,此时LR里面的值是EXC_RETURN,如果LR的bit4是0,那么就按长帧弹
所以在LDRB载入了是否用FPU的标志后,就该判断新任务是否用FPU,如果是,那么就把LR的bit4清0,否则就置1,这样退出中断时,如果任务不用FPU,就会按短帧弹栈,保证出栈顺序正确
另外这里有个取巧的做法,如上图那样不是判断标志,而是直接把标志写入(严格来说是插入)LR,因为在有UCOSII下,退出PendSV后肯定就是普通任务状态了,用的是PSP,Thread模式,处理器的指令模式为Thumb,加上确定是否用FPU,所以此时的EXC_RETURN的值也就是确定的
so,用FPU的话,那么EXC_RETURN必然是0xFFFFFFED,否则就是0xFFFFFFFD,把这个的低8位当成标志位,用的时候直接用BFI指令插进LR就行了,连比较跳转都不要~

还有一处要改的……由于MDK默认的函数里会初始化FPU,把Control.FPCA置位,造成初始状态就不正确,所以得在启动UCOSII前把这个抹掉!
QQ图片20160605145302.png
我的就是加载OS_Init()里面,用内联汇编或是内在函数都行,反正得保证首次进入PendSV_Handler时Control寄存器的bit2为0
OK,改动就这么多,目前看来没发现错误,如有BUG欢迎指出

附两张调试时的图,IdleTask的栈很小,但是却不会溢出,因为不用给FPU的寄存器留位置,所以节省了33*4=132个字节~
QQ图片20160605150726.png
QQ图片20160605150702.png
QQ图片20160605140704.png
QQ图片20160605140710.png
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

9

主题

44

帖子

0

精华

初级会员

Rank: 2

积分
100
金钱
100
注册时间
2016-6-5
在线时间
27 小时
 楼主| 发表于 2016-6-5 12:50:41 | 显示全部楼层
……卧槽,怎么没法嵌图片,等下
回复 支持 反对

使用道具 举报

27

主题

711

帖子

0

精华

版主

Rank: 7Rank: 7Rank: 7

积分
11924
金钱
11924
注册时间
2015-11-5
在线时间
2086 小时
发表于 2016-6-5 13:42:02 | 显示全部楼层
看了个大概感觉楼主敢于当第一个吃螃蟹的人,为你点赞!楼主是利用任务切换时对R14的bit4做判断来决定是否对S15~S31压栈最终达到对时间上的优化,但楼主有否对比过你引入的这个判断本身也会导致CPU多执行了几条指令,如果在绝大多数需要压栈的前提下,这多出的几条判断跳转指令是否会显得冗余了?
回复 支持 反对

使用道具 举报

9

主题

44

帖子

0

精华

初级会员

Rank: 2

积分
100
金钱
100
注册时间
2016-6-5
在线时间
27 小时
 楼主| 发表于 2016-6-5 13:51:10 | 显示全部楼层
本帖最后由 Tekmarine 于 2016-6-5 15:03 编辑
FreeRTOS 发表于 2016-6-5 13:42
看了个大概感觉楼主敢于当第一个吃螃蟹的人,为你点赞!楼主是利用任务切换时对R14的bit4做判断来决定是否 ...

最上面的那个PendSV_handler是开发板带的,我改写的在后面,还没发上来,另外我觉得单片机主要的任务就是控制,浮点计算任务虽然重要,但是缺不多,多数任务还是不用FPU的,而且压、弹31个FPU寄存器大概要花100个时钟,相比之下,加几条判断语句貌似划算些
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-23 19:50

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表