《2022年uCOS重入问题的解决-巨龙公司 .pdf》由会员分享,可在线阅读,更多相关《2022年uCOS重入问题的解决-巨龙公司 .pdf(7页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、uCOS51 重入问题的解决巨龙公司系统集成开发部杨屹 2002/10/09引言自从发表 uCOS51 移植心得以来,我收到了很多朋友们的来信,大家对公开源码表示鼓励,谢谢大家的支持!很多人对于编写自己的操作系统很感兴趣,uCOS51 是个不错的选择。它的优点是简单易懂,学习成本低,有利于向32 位 CPU 过渡。目前,嵌入式BBS 上的热点是:嵌入式实时多任务操作系统、单片机上网、32bitCPU(如 ARM 等) 。其实通过uCOS51 学习完全可以掌握这些热门技术的精髓,而且学习成本低廉。为此我会陆续将我在研发过程中的经验体会写出来与大家交流,共同进步。我准备讨论以下内容: uCOS51
2、 高效内核、OS 人机界面SHELL 的编写、51 机开发板的硬件设计、 RTL8019AS网卡驱动程序、51TCP/IP 协议栈设计、应用协议FTP、PPP、HTTP、SMTP、SNMP 在 51 上的实现技术、51OS 任务划分和应用程序实例、由51 软件系统向ARM 的移植以及其他想到的题目。欢迎大家积极参与。讨论 1-uCOS51 高效内核在提供了大量uCOS51 测试版后, zxgllp 网友提出了OS 程序不支持函数重入问题,具体表现在任务函数中带有形参和局部变量时若使用reentrant 关键字,任务函数重入时会导致致命错误,经查属实。具体原因是OS 切换程序没有保存/恢复仿真堆
3、栈内容。由于我刚接触KEIL 对其细节不熟悉,参考的范例中有些不是KEIL 编译的,没有处理仿真堆栈内容,我也理所当然地认为KEIL 象 TC 一样自动处理了重入问题,所以导致致命错误。(最新版本中已改正!)我仔细研究了C51.PDF 129-131 页的内容,了解到:为了函数重入,形参和局部变量必须保存在堆栈里,由于 51 硬件堆栈太小,KEIL 将根据内存模式在相应内存空间仿真堆栈(生长方向由上向下,与硬件栈相反)。对于大模式编译,函数返回地址保存在硬件堆栈里,形参和局部变量放在仿真堆栈中,栈指针为?C_XBP,XBPSTACK=1 时,起始值在startup.a51中初始化为FFFFH+
4、1 。仿真堆栈效率低下,KEIL 建议尽量不用,但为了重入操作必须使用。KEIL 可以混合使用3 种仿真堆栈 (大、中、小模式),为了提高效率,针对51 我推荐统一使用大模式编译。为了支持重入, 我重新设计了堆栈结构(如下图 )。 增加了保存仿真堆栈指针?C_XBP 和堆栈内容的数据结构。相应改变的文件有:OS_CPU_A.ASM 、OS_CPU_C.C 、OS_CPU.H、 YY.C。由图可知,用户栈中保存的仿真栈与硬件栈相向生长,中间为空闲间隔,显然 uCOSII 的堆栈检测函数失效。硬件栈的保存恢复详见移植心得 ,仿真堆栈的保存与8086 移植中的一样, OS 只提供堆栈空间和只操作堆栈
5、指针,不进行内存拷贝,效率相对很高。我建议使用统一的固定大小的堆栈空间,尽管 uCOSII 原作者把不同任务使用不同空间看成是优点,但为了在 51 上有效实现任务重入,针对51 我还是坚持不使用这个优点。用户堆栈空间的大小是可以精确计算出来的。用户堆栈空间=硬件堆栈空间 +仿真堆栈空间。硬件栈占用内部 RAM ,内部 RAM 执行效率高,如果堆栈空间过大,会影响KEIL 编译的程序性能。如果堆栈空间小,在中断嵌套和程序调用时会造成系统崩溃。综合考虑,我把硬件堆栈空间大小定成了64 字节, 用户根据实际情况可以自行设定。仿真堆栈大小取决于形参和局部变量的类型及数量,可以精确算出。因为所有用户栈使
6、用相同空间大小,所以取占用空间最大的任务函数的空间大小为仿真堆栈空间大小。这样用户堆栈空间大小就唯一确定了。我将用户堆栈空间大小用宏定义在OS_CFG.H 文件中,宏名为MaxStkSize。51 的 SP 只有 8 位,无法在 64K 空间中自由移动, 只好采用拷贝全部硬件堆栈内容的笨办法。51 本来就弱,这么一来缺点更明显了。其实,引入OS 必然要付出代价,一般OS 要占用 CPU10%-20% 的负荷能力,请权名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 1 页,共 7 页
7、- - - - - - - - - 衡利弊决定。切换频率决定了CPU 的耗费,频率越高耗费越大,大到一定程度就该换更强的CPU 了。我选了 50Hz 的切换频率,不高也不低,用户可以根据需要自行定夺。在耗费无法避免的情况下,我采取了几个措施来提高效率:1。ret 和 reti 混用减少代码;2。IE、SP不入出栈,通过另外方式解决;3。用 IDATA 关键字声明在汇编中用到的全局变量,变DPTR 操作为 Ri 操作; 4。设计堆栈结构,简化算法;5。让串口输入输出工作在系统态,不占用任务TCB 和优先级,增加弹性缓冲区,减少等待。任务堆栈结构 :OS_CPU_C.C:OS_STK *OSTas
8、kStkInit (void (*task)(void *pd) reentrant, void *ppdata, OS_STK *ptos, INT16U opt) reentrantOS_STK *stk;ppdata= ppdata;opt= opt;/opt 没被用到,保留此语句防止告警产生stk= ptos;/用户堆栈最低有效地址*stk+ = 15;/用户堆栈长度*stk+ = (INT16U)task & 0 xFF;/任务地址低8 位*stk+ = (INT16U)task 8;/任务地址高8 位*stk+ = 0 x0A;/ACC*stk+ = 0 x0B;/B*stk+ =
9、 0 x00;/DPH*stk+ = 0 x00;/DPL*stk+ = 0 x00;/PSW*stk+ = 0 x00;/R0名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 2 页,共 7 页 - - - - - - - - - /R3、R2、R1 用于传递任务参数ppdata,其中 R3 代表存储器类型,R2 为高字节偏移,R1 为低字节位移。/通过分析KEIL 汇编,了解到任务的void *ppdata 参数恰好是用R3、R2、R1 传递,不是通过虚拟堆栈。*stk+ = (I
10、NT16U)ppdata & 0 xFF;/R1*stk+ = (INT16U)ppdata 8;/R2*stk+ = 0 x01;/R3因为我用的全是XDATA ,所以存储器类型固定为1,见 C51.PDF 第 178 页说明。*stk+ = 0 x04;/R4*stk+ = 0 x05;/R5*stk+ = 0 x06;/R6*stk+ = 0 x07;/R7/不用保存 SP,任务切换时根据用户堆栈长度计算得出。*stk+ = (INT16U) (ptos+MaxStkSize) 8;/?C_XBP仿真堆栈指针高8 位*stk+ = (INT16U) (ptos+MaxStkSize) &
11、 0 xFF; /?C_XBP 仿真堆栈指针低8 位return (void *)ptos);OS_CPU_A.ASM:RSEG ?PR?OSStartHighRdy?OS_CPU_AOSStartHighRdy:USING 0;上电后 51 自动关中断, 此处不必用CLR EA 指令, 因为到此处还未开中断,本程序退出后,开中断。LCALL_?OSTaskSwHookOSCtxSw_in:;# YYY 注释 #;把要切换到的任务堆栈恢复到系统的硬件堆栈;从当前要切换到的任务的控制块获取该任务堆栈的栈顶地址(低字节);把任务堆栈数据复制到硬件堆栈区;出栈,把数据恢复到CPU 寄存器中,执行中断
12、返回;出栈只出R7-R0 , PSW , DPL , DPH , B , ACC ,剩下的就是PC 值;#;OSTCBCur = DPTR获得当前 TCB 指针,详见C51.PDF 第 178 页MOVR0,#LOW (OSTCBCur) ;获得 OSTCBCur 指针低地址, 指针占 3 字节。+0 类型+1 高 8 位数据+2 低 8 位数据INCR0MOVDPH,R0;全局变量 OSTCBCur 在 IDATA 中INCR0MOVDPL,R0;OSTCBCur-OSTCBStkPtr= DPTR获得用户堆栈指针INCDPTR;指针占 3 字节。 +0 类型 +1 高 8 位数据 +2 低
13、 8 位数据名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 3 页,共 7 页 - - - - - - - - - MOVX A,DPTR;.OSTCBStkPtr 是 void 指针MOVR0,AINCDPTRMOVX A,DPTRMOVR1,AMOVDPH,R0MOVDPL,R1;*UserStkPtr = R5用户堆栈起始地址内容(即用户堆栈长度放在此处)详见文档说明指针用法详见 C51.PDF 第 178 页MOVX A,DPTR;用户堆栈中是unsigned char 类型
14、数据MOVR5,A;R5=用户堆栈长度;恢复现场堆栈内容MOVR0,#OSStkStartrestore_stack:INCDPTRINCR0MOVX A,DPTRMOVR0,ADJNZ R5,restore_stack;恢复堆栈指针SPMOVSP,R0;恢复仿真堆栈指针?C_XBPINCDPTRMOVX A,DPTRMOV?C_XBP,A;?C_XBP 仿真堆栈指针高8 位INCDPTRMOVX A,DPTRMOV?C_XBP+1,A;?C_XBP 仿真堆栈指针低8 位;OSRunning=TRUEMOVR0,#LOW (OSRunning)MOVR0,#01POPALLSETB EA;开中
15、断RETI;-;-RSEG ?PR?OSCtxSw?OS_CPU_AOSCtxSw:名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 4 页,共 7 页 - - - - - - - - - PUSHALL;# YYY 注释 #;保存当前任务的硬件堆栈至任务软件堆栈;先进栈ACC , B ,DPH , DPL , PSW , R0-R7 ,己经有 PC 值在栈底 (PC 至少己两次入栈),需重新计算有效堆栈长度;计算出的有效堆栈长度放到当前任务堆栈的栈底;其后是 PC , ACC , B
16、 , DPH , DPL , PSW , R0-R7;再保存仿真堆栈指针;#OSIntCtxSw_in:;获得堆栈长度和起址MOVA,SPCLRCSUBB A,#OSStkStartMOVR5,A;获得堆栈长度;OSTCBCur = DPTR获得当前 TCB 指针,详见C51.PDF 第 178 页MOVR0,#LOW (OSTCBCur) ;获得 OSTCBCur 指针低地址, 指针占 3 字节。+0 类型 +1 高 8 位数据+2 低 8 位数据INCR0MOVDPH,R0;全局变量 OSTCBCur 在 IDATA 中INCR0MOVDPL,R0;OSTCBCur-OSTCBStkPtr
17、= DPTR获得用户堆栈指针INCDPTR;指针占 3 字节。 +0 类型 +1 高 8 位数据 +2 低 8 位数据MOVX A,DPTR;.OSTCBStkPtr 是 void 指针MOVR0,AINCDPTRMOVX A,DPTRMOVR1,AMOVDPH,R0MOVDPL,R1;保存堆栈长度MOVA,R5MOVX DPTR,AMOVR0,#OSStkStart;获得堆栈起址save_stack:INCDPTRINCR0MOVA,R0MOVX DPTR,A名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - -
18、 - - - - - 第 5 页,共 7 页 - - - - - - - - - DJNZ R5,save_stack;保存仿真堆栈指针?C_XBPINCDPTRMOVA,?C_XBP;?C_XBP 仿真堆栈指针高8 位MOVX DPTR,AINCDPTRMOVA,?C_XBP+1;?C_XBP 仿真堆栈指针低8 位MOVX DPTR,A;调用用户程序LCALL_?OSTaskSwHook;# YYY 注释 #;使 OSTCBCur指向当前就绪的最高优先级任务控制块;一般指针占三个字节;#;OSTCBCur = OSTCBHighRdyMOVR0,#OSTCBCurMOVR1,#OSTCBHi
19、ghRdyMOVA,R1MOVR0,AINCR0INCR1MOVA,R1MOVR0,AINCR0INCR1MOVA,R1MOVR0,A;# YYY 注释 #;使 OSPrioCur 指向当前就绪的最高优先级;#;OSPrioCur = OSPrioHighRdyMOVR0,#OSPrioCurMOVR1,#OSPrioHighRdyMOVA,R1MOVR0,ALJMP OSCtxSw_in;-;-RSEG ?PR?OSIntCtxSw?OS_CPU_AOSIntCtxSw:;# YYY 注释 #;在执行该中断任务切换函数之前,己经调用了OSIntExit() PC 入了一次栈,再调用该函数时P
20、C 又入了一次栈名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 6 页,共 7 页 - - - - - - - - - ;对比任务切换时PC 入栈。如在任务正常运行时调用OSTimeDly - OS_Sched - OS_TASK_SW(OSCtxSw) -PUSHALL,PC 一共 3 次入栈;而 OSTickISR - PUSHALL - OSIntExit - OSIntCtxSw,PC 也是 3 次入栈;为什么要在中断任务切换要调整SP?;任务切换时入栈后栈结构PC ,PC
21、, PC ,ACC , B , DPH , DPL , PSW , R0-R7每个 PC2 字节 ;中断任务切换入栈后栈结构PC , ACC , B , DPH ,DPL , PSW , R0-R7 , PC , PC;所以要调整SP四字节;#;调整 SP指针去掉在调用OSIntExit(),OSIntCtxSw() 过程中压入堆栈的多余内容;SP=SP-4MOVA,SPCLRCSUBB A,#4MOVSP,A;此处己经在中断服务子程序开始部分入栈了;下面就直接调用任务切换函数的“保存当前任务的硬件堆栈至任务软件堆栈”LJMP OSIntCtxSw_in名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 7 页,共 7 页 - - - - - - - - -