Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1149169
  • 博文数量: 241
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 2279
  • 用 户 组: 普通用户
  • 注册时间: 2012-11-27 19:53
个人简介

JustForFun

文章分类

全部博文(241)

文章存档

2023年(8)

2022年(2)

2021年(3)

2020年(30)

2019年(11)

2018年(27)

2017年(54)

2016年(83)

2015年(23)

我的朋友

分类: 嵌入式

2015-07-21 21:50:38

摘自原文:http://blog.csdn.net/liuhui_8989/article/details/8783323
 熟悉ucos,或者读过Jean.J.Labrosse写过的ucos书籍的人,一定会知道ucos中著名的临界去管理宏:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。

同样是通过关中断来保护临界区,OS_ENTER_CRITICAL/OS_EXIT_CRITICAL一共实现了三种实现方式,如下所示:


  1. #if OS_CRITICAL_METHOD == 1  
  2. #define OS_ENTER_CRITICAL() __asm__("cli")  
  3. #define OS_EXIT_CRITICAL() __asm__("sti")  
  4. #endif  
  5.   
  6. #if OS_CRITICAL_METHOD == 2  
  7. #define OS_ENTER_CRITICAL() __asm__("pushf \n\t cli")  
  8. #define OS_EXIT_CRITICAL() __asm__("popf")  
  9. #endif  
  10.   
  11. #if OS_CRITICAL_METHOD == 3  
  12. #define OS_ENTER_CRITICAL() (cpu_sr = OSCPUSaveSR())  
  13. #define OS_EXIT_CRITICAL() (OSCPURestoreSR(cpu_sr))  
  14. #endif  


os_cpu_a.asm中 

OS_CPU_SR_Save
    MRS     R0, PRIMASK                                         ; Set prio int mask to mask all (except faults)
    CPSID   I
    BX      LR

OS_CPU_SR_Restore
    MSR     PRIMASK, R0
    BX      LR

   第一种方式,OS_ENTER_CRITICAL()简单地关中断,OS_EXIT_CRITICAL()简单地开中断。这种方式虽然简单高效,但无法满足嵌套的情况。如果有两层临界区保护,在退出内层临界区时就会开中断,使外层的临界区也失去保护。虽然ucos的内核写的足够好,没有明显嵌套临界区的情况,但谁也无法保证一定没有,无法保证今后没有,无法保证在附加的驱动或什么位置没有,所以基本上第一种方法是没有人用的。

   第二种方式,OS_ENTER_CRITICAL()会在关中断前保存之前的标志寄存器内容到堆栈中,OS_EXIT_CRITICAL()从堆栈中恢复之前保存的状态。这样就允许了临界区嵌套的情况。但现在看来,这种方法还存在很大的问题,甚至会出现致命的漏洞。

      在OS_CRITICAL_METHOD=2的情况下,假设有如下代码:


  1. function_a()  
  2. {  
  3.      int a=(1<<31);  
  4.      OS_ENTER_CRITICAL();  
  5.      function_b(a);  
  6.      OS_EXIT_CRITICAL();  
  7.        
  8. }  
   会出现什么情况?在我的实验中,OS_EXIT_CRITICAL()之后,会出现处理器异常。为什么会出现处理起异常,让我来模拟一下它的汇编代码。之所以是模拟,并非是我虚构数据,而是因为我实际碰到问题的函数复杂一些,理解起来就需要更多的代码。而这个问题是有普遍意义的,所以请允许我来浅显地揭示这个隐藏的bug。



  1. function_a:  
  2.      push ebp  
  3.      mov ebp, esp  
  4.      sub esp, 8  
  5.      mov 4(esp), 0x80000000  
  6.      pushfd  
  7.      cli  
  8.      mov edi, 4(esp)  
  9.      mov (esp), edi  
  10.      call function_b  
  11.     popfd  
  12.     mov esp, ebp  
  13.     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()宏定义并没有提供传递状态参数的功能。所以它的临界去必须这么用:


  1. function_a()  
  2. {  
  3. #if OS_CRITICAL_METHOD == 3  
  4.     int cpu_sr;  
  5. #endif  
  6.       int a = 1<<31;  
  7.       OS_ENTER_CRITICAL();  
  8.       function_b(a);  
  9.       OS_EXIT_CRITICAL();  
  10. }  
这种代码怎么看怎么别扭,可能是因为在函数体内加了宏定义吧。然后,第三种方法对同一个函数体内的嵌套临界区无法支持,这在一些很长大的函数中使用时或许会造成一定困扰。



 


    好吧,如果有了问题,就要有解决方案,毕竟我不是为了让大家对ucos失去信心的。我们可以参考下一般的实时操作系统是如何实现关中断临界区的,就是以显式的方式用局部变量保存中断状态。


  1. int int_lock()  
  2. {  
  3.    int cpu_sr;  
  4.     __asm__ __volatile__("pushfd \n\t pop %0\n\t cli":"=r"(cpu_sr));  
  5.     return cpu_sr;  
  6. }  
  7.   
  8. void int_unlock(int cpu_sr)  
  9. {  
  10.      __asm__ __volatile__("push %0\n\t popfd"::"r"(cpu_sr));  
  11. }  
  12.   
  13. function_a()  
  14. {  
  15.    int a, cpu_sr;  
  16.    a=1<<31;  
  17.    cpu_sr = int_lock();  
  18.    function_b(a);  
  19.    int_unlock(cpu_sr);  
  20. }  


   int_lock()和int_unlock()的可以用汇编更高效地实现,也可以选择只恢复中断标志的状态。这种方法让我们显示地管理状态保存的情况,我觉得至少要比宏定义清楚多了
C代码:                                                               汇编码:                                                     

                                                                       
                                                                            
                                                                       
OS_CPU_SR  cpu_sr = 0;                   0x08003236 F04F0900  MOV      r9,#0x00

OS_ENTER_CRITICAL();                    0x08003244 F7FDFD6A  BL.W     0x08000D1C (0x080001D0)
                                                      0x08003248 4681      MOV      r9,r0           

                                                                                  ;保存PRIMASK 到cpu_sr 



 OS_CPU_SR_Save
    MRS     R0, PRIMASK             ;  0x08000D1C  就是   OS_CPU_SR_Save的函数代表的地址   
                                                  0x08000D1C F3EF8010  MRS      r0,PRIMASK

                                                                   ; Set prio int mask to mask all (except faults)
    CPSID   I                                 0x08000D20 B672      CPSID    I  
    BX      LR                                0x08000D22 4770      BX       lr 
 
 
                                                   0x0800326E 4648      MOV      r0,r9           ;恢复
OS_CPU_SR_Restore                     0x08003270 F7FDFD58  BL.W     0x08000D24
    MSR     PRIMASK, R0                 0x08000D24 F3808810  MSR      PRIMASK,r0            
    BX      LR                                                         104:     BX      LR 

// OS_CPU_SR  cpu_sr = 0;所以cpu_sr是用来保存PRIMASK 然后再恢复用的

阅读(3565) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~