看的一篇帖子很好,把他贴过来:熟悉ucos,或者读过Jean.J.Labrosse写过的ucos书籍的人,一定会知道ucos中著名的临界去管理宏:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。
同样是通过关中断来保护临界区,OS_ENTER_CRITICAL/OS_EXIT_CRITICAL一共实现了三种实现方式,如下所示:
第一种方式,OS_ENTER_CRITICAL()
简单地
关中断,OS_EXIT_CRITICAL()
简单地
开中断。这种方式虽然简单高效,但
无法满足
嵌套
的情况。如果有两层临界区保护,在退出内层临界区时就会开中断,使外层的临界区也失去保护。
虽然ucos的内核写的足够好,没有明显嵌套临界区的情况,但谁也无法保证一定没有,无法保证今后没有,无法保证在附加的驱动或什么位置没有
,所以基本上第一种方法是没有人用的。
第二种方式,OS_ENTER_CRITICAL()会在关中断前 保存 之前的 标志寄存器内容 到 堆栈 中,OS_EXIT_CRITICAL()从 堆栈 中 恢复 之前保存的状态。这样就允许了临界区嵌套的情况。但现在看来,这种方法 还存在很大的问题,甚至会出现致命的漏洞 。
在OS_CRITICAL_METHOD=2的情况下,假设有如下代码:
会出现什么情况?在我的实验中,OS_EXIT_CRITICAL()之后,会出现
处理器异常
。为什么会出现处理起异常,让我来模拟一下它的汇编代码。之所以是模拟,并非是我虚构数据,而是因为我实际碰到问题的函数复杂一些,理解起来就需要更多的代码。而
这个问题是有普遍意义
的,所以请允许我来
浅显地揭示这个隐藏的bug
。
这是参照了gcc编译结果的汇编模拟,无论是否加优化选项这一问题都存在。这个问题的起因很简单,
gcc想聪明一点,一次把堆栈降个够,然后它就可以在栈上随意放参数
去调用其他函数。尤其是在调用函数较多的时候,这种做法就更有意义。而且,gcc这种聪明与优化选项O好像没有太大关系,好像没有什么能禁止它这么做。但问题是,
gcc不知道我们的OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()是操作了堆栈的
,我尝试过使用__asm__ __volatile__("pushfd \n\tcli":::"memory")来通知gcc
内存数据改变
了,但显然
gcc不认为堆栈也改变
了。于是,
OS_ENTER_CRITICAL()保存在栈上的状态就被冲掉
了,比如被这里调用参数a的值。在恢复时,是否会引发异常,会引发什么异常,这个就要靠运气了。但我相信一个人的运气不会总是那么好的,所以最后别使用OS_CRITICAL_METHOD=2。
第三种,在关中断前,使用 局部变量 保存 中断状态 。这也是几乎所有实时操作系统共有的选择。但ucos是一朵奇葩,为了兼容前两种方式,OS_ENTER_CRITICAL()/ OS_EXIT_CRITICAL()宏定义并没有提供传递状态参数的功能。所以它的临界去必须这么用:
这种代码怎么看怎么别扭,可能是因为在函数体内加了宏定义吧。然后,第三种方法对同一个函数体内的嵌套临界区无法支持,这在一些很长大的函数中使用时或许会造成一定困扰。希望对你有帮助。
同样是通过关中断来保护临界区,OS_ENTER_CRITICAL/OS_EXIT_CRITICAL一共实现了三种实现方式,如下所示:
- #if OS_CRITICAL_METHOD == 1
- #define OS_ENTER_CRITICAL() __asm__("cli")
- #define OS_EXIT_CRITICAL() __asm__("sti")
- #endif
- #if OS_CRITICAL_METHOD == 2
- #define OS_ENTER_CRITICAL() __asm__("pushf \n\t cli")
- #define OS_EXIT_CRITICAL() __asm__("popf")
- #endif
- #if OS_CRITICAL_METHOD == 3
- #define OS_ENTER_CRITICAL() (cpu_sr = OSCPUSaveSR())
- #define OS_EXIT_CRITICAL() (OSCPURestoreSR(cpu_sr))
- #endif
第二种方式,OS_ENTER_CRITICAL()会在关中断前 保存 之前的 标志寄存器内容 到 堆栈 中,OS_EXIT_CRITICAL()从 堆栈 中 恢复 之前保存的状态。这样就允许了临界区嵌套的情况。但现在看来,这种方法 还存在很大的问题,甚至会出现致命的漏洞 。
在OS_CRITICAL_METHOD=2的情况下,假设有如下代码:
- function_a()
- {
- int a=(1<<31);
- OS_ENTER_CRITICAL();
- function_b(a);
- OS_EXIT_CRITICAL();
- }
- function_a:
- push ebp
- mov ebp, esp
- sub esp, 8
- mov 4(esp), 0x80000000
- pushfd
- cli
- mov edi, 4(esp)
- mov (esp), edi
- call function_b
- popfd
- mov esp, ebp
- ret
第三种,在关中断前,使用 局部变量 保存 中断状态 。这也是几乎所有实时操作系统共有的选择。但ucos是一朵奇葩,为了兼容前两种方式,OS_ENTER_CRITICAL()/ OS_EXIT_CRITICAL()宏定义并没有提供传递状态参数的功能。所以它的临界去必须这么用:
- function_a()
- {
- #if OS_CRITICAL_METHOD == 3
- int cpu_sr;
- #endif
- int a = 1<<31;
- OS_ENTER_CRITICAL();
- function_b(a);
- OS_EXIT_CRITICAL();
- }