原创 (转载)ucos中的三种临界区管理机制

2020-7-23 11:27 187 2 2 分类: MCU/ 嵌入式 文集: STM32应用
熟悉ucos,或者读过Jean.J.Labrosse写过的ucos书籍的人,一定会知道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()简单地开中断。这种方式虽然简单高效,但无法满足嵌套的情况。如果有两层临界区保护,在退出内层临界区时就会开中断,使外层的临界区也失去保护。虽然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();
     
}
   会出现什么情况?在我的实验中,OS_EXIT_CRITICAL()之后,会出现处理器异常。为什么会出现处理起异常,让我来模拟一下它的汇编代码。之所以是模拟,并非是我虚构数据,而是因为我实际碰到问题的函数复杂一些,理解起来就需要更多的代码。而这个问题是有普遍意义的,所以请允许我来浅显地揭示这个隐藏的bug。
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
    这是参照了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()宏定义并没有提供传递状态参数的功能。所以它的临界去必须这么用:

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()的可以用汇编更高效地实现,也可以选择只恢复中断标志的状态。这种方法让我们显示地管理状态保存的情况,我觉得至少要比宏定义清楚多了。
————————————————
版权声明:本文为CSDN博主「qb_2008」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qb_2008/java/article/details/7201340

文章评论0条评论)

登录后参与讨论
相关推荐阅读
RLink 2020-09-10 14:49
rtthread scons使用备注
1. 更改默认环境变量使用scons编译时,RTT_CC环境变量用于标识编译时使用的工具链,默认配置使用gcc,如果需要修改为iar,则可以编辑文件env_released_1.2.0\env\too...
RLink 2020-07-23 16:20
(转载)中断控制及basepri 与 basepri_max
转载自https://www.cnblogs.com/qiyuexin/p/8921718.html目录FAULTMASKPRIMASKBASEPRIBASEPRI_MAX 正文1、总开关每...
RLink 2020-07-23 10:44
(转载)Cortex-M0/M0+屏蔽优先级低于BASEPRI设定的中断
Cortex-M3/M4/M7有BASEPRI特殊功能寄存器可以屏蔽优先级低于BASEPRI设定值的中断,但Cortex-M0/M0+没有BASEPRI寄存器,不过仍然可以实现相识的功能。mingdu...
RLink 2020-07-09 15:12
STM32L系列I2C应用注意
噪音滤波器I2C模块包含模拟噪音滤波器和数字噪音滤波器,其中模拟噪音滤波器可以抑制尖峰宽度高达50ns(满足快速模式协议规格),用户可以选择关闭这一特性;数字滤波器抑制尖峰噪音的长度可以通过软件配置,...
RLink 2020-07-04 06:23
STM32L系列电源管理
电源供应1. VDD -> 1.8V~3.6V,用于内部电压调整器和IO供电,通过外部管脚引入2. Vcore -> 1.2V~1.8V ,用于内部的各数字外围模块、SRAM和FLASH供电,通过内部...
广告
EE直播间
更多
我要评论
0
2
广告
关闭 热点推荐上一条 /6 下一条