原创 关于keilc51入出临界区的内嵌汇编

2008-8-6 23:26 3595 4 4 分类: MCU/ 嵌入式

关于keilc51入出临界区的内嵌汇编


作者:杨屹
关键字/摘要:

关于keilc51入出临界区的内嵌汇编   
                                                                          


背景
====
2004/02/18收到陈学章网友来信,报告ucos51的BUG,内容如下:
Wednesday, February 18, 2004 1:43 PM

> 在OS_CPU_A.ASM中的OSStartHighRdy函数中你注释
> “;上电后51自动关中断,此处不必用CLR EA指令,因为到此处还未开中断,本程序退出后,开中断.”
> 小弟感觉不妥,因为在系统调用OSInit()时会自动创建一个优先级最低的系统任务,创建过程中会调用OS_EXIT_CRITICAL()打开EA。

经查情况属实。当时移植的时候我也隐隐觉得有些不妥,但考虑到要用内嵌汇编等我不熟悉的技术时,感到困难太大(当时还有很多更重要的技术难点需要解决),就没有深入思考。后来,在调试ucos51shell程序时,发现按键不能及时显示在超级终端上,初步判断是由于在显示程序中使用了不合理的临界区保护代码,但当时没有解决。从现在分析来看,这两个问题都与临界区保护有关,实践的结果也确实如此。

===============================
1.ucos51临界区BUG的保守解决方案
===============================
ucos的作者给出两种临界区保护方案:
方法一:
执行这两个宏的第一个也是最简单的方法是在OS_ENTER_CRITICAL()中调用处理器指令来禁止中断,以及在OS_EXIT_CRITICAL()中调用允许中断指令。
缺点:在这个过程中还存在着小小的问题。如果用户在禁止中断的情况下调用μC/OS-Ⅱ函数,在从μC/OS-Ⅱ返回的时候,中断可能会变成是允许的了!如果用户禁止中断就表明用户想在从μC/OS-Ⅱ函数返回的时候中断还是禁止的。在这种情况下,光靠这种执行方法可能是不够的。嵌套调用OS_ENTER_CRITICAL()/OS_EXIT_CRITICAL()对时,会引发错误。
优点:(1)速度快;(2)避免关闭中断后调用PEND阻塞类API函数引起死机。

方法二:
执行OS_ENTER_CRITICAL()时先将中断禁止状态保存到堆栈中,然后禁止中断。而执行OS_EXIT_CRITICAL()时只是从堆栈中恢复中断状态。
缺点:(1)总指令周期长,速度慢;
(2)如果用户在中断禁止的时候调用μC/OS-Ⅱ服务,其实用户是在延长应用程序的中断响应时间。
(3)用户的应用程序还可以用OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()来保护代码的临界段。但是,用户在使用这种方法的时候还得十分小心,因为如果用户在调用象OSTimeDly()之类的服务之前就禁止中断,很有可能用户的应用程序会崩溃。发生这种情况的原因是任务被挂起直到时间期满,而中断是禁止的,因而用户不可能获得节拍中断!很明显,所有的PEND调用都会涉及到这个问题,用户得十分小心。一个通用的办法是用户应该在中断允许的情况下调用μC/OS-Ⅱ的系统服务!
优点:如果用这个方法的话,不管用户是在中断禁止还是允许的情况下调用μC/OS-Ⅱ服务,在整个调用过程中都不会改变中断状态。

结论:
哪种方法更好一点?这就得看用户想牺牲些什么。如果用户并不关心在调用μC/OS-Ⅱ服务后用户的应用程序中中断是否是允许的,那么用户应该选择第一种方法执行。如果用户想在调用μC/OS-Ⅱ服务过程中保持中断禁止状态,那么很明显用户应该选择第二种方法。

ucos书上给出的例子是基于PC机的,它运行的环境是Windows下的DOS仿真。由于此时CPU工作在保护模式,程序运行在V86模式,中断被Windows操作系统接管了,ucos无法实际关闭中断。而且在ucos启动前(OSStart),就有时钟中断存在。作者给出的ucos在DOS环境下的演示方案是:所有例子只提供一个时钟中断,没有其他任何IO中断存在,开关中断只影响tickISR,用户根本不用关心中断是否是允许的,用方法一或方法二入出临界区都可以。“用户必须在开始多任务调度后(即调用OSStart()后)允许时钟节拍中断。换句话说,就是用户应该在OSStart()运行后,μC/OS-Ⅱ启动运行的第一个任务中初始化节拍中断。通常所犯的错误是在调用OSInit()和OSStart()之间允许时钟节拍中断。”对此,作者给出的方案是,创建第一个任务TaskStart ,在这个任务里把ucos的tickISR挂接到硬时钟中断上,然后创建需要的各种任务,接下来死循环周期采样判断是否有按键退出。这样满足了允许时钟节拍中断的时机要求。
对于51上的ucos,由于有多个IO中断,OS_ENTER_CRITICAL()/OS_EXIT_CRITICAL()对有时嵌套调用,所以,我保守地建议使用第二种方法入出临界区。虽然执行速度慢,但稳定可靠。只要用户遵循“在中断允许的情况下调用μC/OS-Ⅱ的系统服务!”原则,就不会死机。
1。由于#pragma关键字不能出现在H头文件里,所以必须手工修改所有的OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()为“4.入出临界区标准代码”所示的样子。用户程序使用入出临界区保护代码时也需手工粘贴此代码,然后按“3.KEILC51内嵌汇编的方法”进行编译。虽然不如宏定义方便,但也不是很麻烦。

2。使用第二种方法,需要在OSIntCtxSw中调整SP指针去掉在调用OSIntExit(),OS_ENTER_CRITICAL(),OSIntCtxSw()过程中压入堆栈的多余内容(SP=SP-5,2+1+2),如下所示:
OS_CORE.C文件:51堆栈由下至上
OSIntExit //2字节返回地址
OS_ENTER_CRITICAL() //PUSH IE; 1字节
OSIntCtxSw() //2字节返回地址

3。上电后51自动关中断,在什么地方打开中断最合适呢?在OSStartHighRdy退出时SETB EA开中断最合适!
为了减少代码量,OSCtxSw、OSIntCtxSw和OSStartHighRdy合用退出代码段,会不会有问题呢?不会!
任务切换只会发生在1、复位;2、tick中断;3、中断;4、系统API调用四个阶段。复位后打开中断,允许系统正常运行是必然的,在OSStartHighRdy退出时必须打开中断。如果是2、3情况引发的调度,中断本来就是打开的,OSIntCtxSw退出时打开中断毫无影响。按照“在中断允许的情况下调用μC/OS-Ⅱ的系统服务!”原则,用户必须在打开中断的情况下调用系统API,所以OSCtxSw退出时打开中断也毫无影响。虽然此时打开中断多此一举,浪费了代码空间和执行时间,但强制退出时开中断,可以稍微起到保护系统不死机的作用(例如:调用系统API前关中断的错误情况)。

通过上面三步,入出临界区的BUG得以完整解决,我仔细审查了一下,觉得这个思路比较严密,应该是比较健壮了,希望网友们继续提出意见和建议,让我们一起把它做得更完善。

=======================================
2.ucos51shell固化后显示不正常的解决方法
=======================================
显示函数(serial.c)在多任务环境下需要临界保护,因为共享同一个输出设备,资源是独占的,否则会出现混乱。比较好的方法是使用信号量互斥,但那样实在是太慢了,为了提高效率,一个简单的办法就是关中断(前提是关中断时间不能太长,以免影响中断响应时间)。
开关中断的方法如“4.入出临界区标准代码”所示,在需要保护的关键段两头粘贴此段代码,然后按照“3.KEILC51内嵌汇编的方法”所示编译即可。
详见ucos51shellv2代码。

=======================
3.KEILC51内嵌汇编的方法
=======================
有时在C51程序中需要嵌入一些汇编代码,这时当然可以用通常的作法:按照 C51 与汇编的接口写一个汇编函数,然后在 C51 程序中调用该函数。(此种方法可在论坛里搜索(www.c51bbs.com),以前有很多帖子讲到,不再重复)
下面介绍直接嵌入汇编代码的方法:
1、在 C 文件中要嵌入汇编代码片以如下方式加入汇编代码:
#pragma ASM
; Assembler Code Here
#pragma ENDASM
例如:
#pragma ASM
PUSH IE;
CLR EA;
#pragma ENDASM
2、在 Project 窗口中包含汇编代码的 C 文件上单击右键,选择“Options for ...”,点击右边的“Generate Assembler SRC File”和“Assemble SRC File”,使检查框由灰色变成黑色(有效)状态;
3、根据选择的编译模式,把相应的库文件(如 Small 模式时,是 Keil\C51\Lib\C51S.Lib;Large 模式时,是 Keil\C51\Lib\C51L.Lib)加入工程中,该文件必须作为工程的最后文件(切记是放在最后位置);
4、编译,即可生成目标代码。
问题解答:
问:为什么要把库文件加入工程?是BUG吗?
答:因为实际已经处于汇编连接,所以要加LIB。C连接是自动加的。
建议:
把SRC文件也加入项目中,不然连接的OBJ只是前次生成的。

====================
4.入出临界区标准代码
====================
入临界区时首先保存EA值,然后令EA=0关中断,出临界区时恢复保存的EA值。采用这种方法,在嵌套调用OS_ENTER_CRITICAL()/OS_EXIT_CRITICAL()对时,不会发生错误(EA进入临界区前是什么值,退出后还是什么值)。

//OS_ENTER_CRITICAL()
//入临界区
#pragma ASM
PUSH IE;
CLR EA;
#pragma ENDASM

//OS_EXIT_CRITICAL()
//出临界区
#pragma ASM
POP IE;
#pragma ENDASM  

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
4
关闭 站长推荐上一条 /3 下一条