• OSSchedLock()函数透析


    uC/OS-II的OSSchedLock()和OSSchedUnlock()函数允许应用程序锁定当前任务不被其它任务抢占。
    使用时应当注意的是:当你调用了OSSchedLock()之后,而在调用OSSchedUnlock()之前,
    千万不要再调用诸如OSFlagPend()、OSMboxPend()、OSMutexPend()、OSQPend()、OSSemPend()之类的事件等待函数!
    而且应当确保OSSchedLock()和OSSchedUnlock()函数成对出现,特别是在有些分支条件语句中,要考虑各种分支情况,不要有遗漏!
     
    需要一并提醒用户的是:当您调用开关中断函数OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()时也要确保成对出现,否则系统将可能崩溃!
    不过,在OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()函数之间调用OSFlagPend()、OSMboxPend()、OSMutexPend()、OSQPend()、OSSemPend()之类的事件等待函数是允许的。

    OSSchedLock()

    调用OSSchedLock()以后,用户的应用程序不得使用任何能将现行任务挂起的系统调用。

    也就是说,用户程序不得调用OSMboxPend()、OSQPend()、OSSemPend()、

    OSTaskSuspend(OS_PR1O_SELF)、OSTimeDly()或OSTimeDlyHMSM(),直到OSLockNesting回零为止。

    因为调度器上了锁,用户就锁住了系统,任何其它任务都不能运行

    下面贴出这个函数的代码 :
    #if OS_SCHED_LOCK_EN > 0   //这是个全局变量,在Os_cfg.h中定义
    void  OSSchedLock (void)
    {
    #if OS_CRITICAL_METHOD == 3                      /* Allocate storage for CPU status register           */
        OS_CPU_SR  cpu_sr;
    #endif    
        
        
        if (OSRunning == TRUE) {                     /* 确认是否是多个任务在运行              */
            OS_ENTER_CRITICAL();
            if (OSLockNesting < 255) {               /* OSLockNesting最大值为255     */
                OSLockNesting++;                     /* Increment lock nesting level                       */
            }
            OS_EXIT_CRITICAL();
        }
    }
    #endif 
    从上锁函数我们可以看出,这个函数就是一个确定任务上锁级数的函数,也就是为OSLockNesting这个变量更新取值的函数。
    如果我们在任务里面执行一次该函数,则上锁级数就是1。我们也可以知道,上锁函数的深度最大值为255,我们可以通过上锁到255,到逐级解锁来实现不同的应用。
    但这个过程,本人从来没有实现过。
    并且补充一点,在上锁函数执行后,若任务遇到中断,则中断函数的执行会为OSIntNesting变量加1,所以上锁函数执行后,CPU一直处于当前任务与中断服务函数之间的运行,

    直到解锁函数将OSLockNesting和OSIntNesting的值减到0时。方可解除系统锁定。。。。

    转:ucos中的三种临界区管理机制(OS_CRITICAL_METHOD的解释)

    熟悉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_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()的可以用汇编更高效地实现,也可以选择只恢复中断标志的状态。这种方法让我们显示地管理状态保存的情况,我觉得至少要比宏定义清楚多了

  • 相关阅读:
    Django REST framework解析器、渲染器、分页
    微前端qiankun从搭建到部署的实践
    前端开发常用免费资源,显著提升工作效率
    Vue切换页面时碰见过中断axios请求的场景吗?如何中断?
    JavaScript与ES的25个重要知识点!
    电脑端支付宝支付 -前端获取支付宝返回的form 以及submit 调用支付扫码页面
    element ui 分页记忆checked
    通过css改变svg img的颜色
    Howler.js Web音频播放终极解决方案
    5、Redis中对Set类型的操作命令
  • 原文地址:https://www.cnblogs.com/pengwangguoyh/p/4446024.html
Copyright © 2020-2023  润新知