同样是通过关中断来保护临界区,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()简单地开中断。这种方式虽然简单高效,但无法满足嵌套的情况。如果有两层临界区保护,在退出内层临界区时就会开中断,使外层的临界区也失去保护。虽然ucos的内核写的足够好,没有明显嵌套临界区的情况,但谁也无法保证一定没有,无法保证今后没有,无法保证在附加的驱动或什么位置没有,所以基本上第一种方法是没有人用的。
第二种方式,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();
- }
好吧,如果有了问题,就要有解决方案,毕竟我不是为了让大家对ucos失去信心的。我们可以参考下一般的实时操作系统是如何实现关中断临界区的,就是以显式的方式用局部变量保存中断状态。
- int int_lock()
- {
- int cpu_sr;
- __asm__ __volatile__("pushfd \n\t pop %0\n\t cli":"=r"(cpu_sr));
- return cpu_sr;
- }
- void int_unlock(int cpu_sr)
- {
- __asm__ __volatile__("push %0\n\t popfd"::"r"(cpu_sr));
- }
- function_a()
- {
- int a, cpu_sr;
- a=1<<31;
- cpu_sr = int_lock();
- function_b(a);
- int_unlock(cpu_sr);
- }
int_lock()和int_unlock()的可以用汇编更高效地实现,也可以选择只恢复中断标志的状态。这种方法让我们显示地管理状态保存的情况,我觉得至少要比宏定义清楚多了。