学习札记--uC/OS-II处理临界区代码的三种方法小结

实现临界区的正确访问可以采用硬件方法或是软件方法。硬件方法是解决临界段问题的低级方法,也叫做元方法。软件方法则主要指的是信号量机制。以前一遇到进程的同步或是互斥,首先想到的就是信号量,n个进程共享一个公共的信号量mutex,初值为1,各进程在进入临界区之前对该信号量进行P操作,只有在获得该信号量的情况下才能进入临界区,否则将自己阻塞,等待信号量的释放,原理简单易懂。

       硬件方法有两类:一类是屏蔽中断方法,中断会引发多进程的并发执行,尤其对于可剥夺型内核,中断处理过程中若是有优先级更高的进程进入就绪状态,则中断结束后会立即进行进程的调度,剥夺被中断进程的处理机使用权,交给新到达的高优先级进程。中断处理程序和中断结束后获得处理机的高优先级进程都有可能访问临界资源,从而造成数据的不一致,屏蔽了中断也就杜绝了这些现象的发生。中断屏蔽的方法有其局限性,首先它只适用于单处理机的情况,其次,对于uC/OS-II这种对实时性要求较高的内核,关中断时间的长短对于系统服务质量的影响非常大,原则上应该使关中断时间降至最低。第二类方法就是在设计硬件的指令系统时,专门设置类似“Test_and_Set”的指令来对临界资源实行加锁和解锁操作,由于指令是完全由硬件逻辑实现的(指令系统的实现包括硬布线和微程序两种方法,但无论如何都要通过逻辑电路去实现指令的取指、译码、执行等步骤),是不可能被打断的原子操作,因而也就实现了对临界资源的保护。与中断屏蔽方法相比较,这种方法在多处理机情况下也是可行的,但很显然增加了指令系统的设计复杂度,而且在当今广泛使用的RISC(如ARM)中,用户是没有机会去或是没有能力去设计指令系统的,所以也就使得这种方法的使用大大减少。

       值得注意和需要加强理解的是,在信号量机制中使用的P、V(或是wait、signal)操作是两条原语,而很显然这两条原语并不是硬件指令,那么由谁来保证其原子性呢,在单处理机系统中还是需要通过屏蔽中断的方法来实现,因此归根结底,实现临界区正确访问这一问题还是要在硬件层来解决。 至于多处理机的情况,就需要以后去慢慢理解了,而且多处理机的指令系统要怎么实现、和单处理机有多大区别,还有许多问题需要去弄明白,毕竟现在是初学么,呵呵。

       现在就开始对 uC/OS-II处理临界区代码的三种方法进行一下小结。之所以前面写了这么几大段,主要是我一开始不明白为什么uC/OS-II在实现临界段问题时只提到用屏蔽中断而根本不提信号量方法等等,而且在学习的过程中可以发现,它把屏蔽中断作为一个非常重要的概念并放在首要的位置去理解,一开始非常疑惑,但就像上一段中得到的结论,内核就是直接与底层硬件打交道的,而解决临界段问题根本的手段还是屏蔽中断,那么关中也就是作为内核解决此问题的最高效的方法。

       因为涉及到底层硬件,不同CPU的关中断指令不尽相同,所以这一部分代码应写在移植相关的文件OS_CPU.H中,并采用宏以方便定义和调用。以80x86系列处理器为例。

#define  OS_CRITICAL_METHOD    2                                          /*默认采用第二种方法*/

typedef unsigned short OS_CPU_SR;                                          /*定义 程序状态字CPU status register (PSW = 16 bits) */

第一种方法:

#if      OS_CRITICAL_METHOD == 1
#define  OS_ENTER_CRITICAL()  asm  CLI                                 /* Disable interrupts                        */
#define  OS_EXIT_CRITICAL()   asm  STI                                    /* Enable  interrupts                        */
#endif

简单的开中断和关中断,进入临界区之前调用宏OS_ENTER_CRITICAL(),退出临界段时调用OS_EXIT_CRITICAL(),这种方法的问题在于,如果在进入临界区之前中断就已经是关闭状态,且临界区中调用了uC\OS-II功能函数,那么不用等到OS_EXIT_CRITICAL(),从uC\OS-II的函数返回时,中断就被该系统功能函数恢复了,而不会保持进入临界区之前的中断关闭状态,所以效果不如预期的理想。之所以定义这种中断,书中给出的解释是“针对某些特定的处理器或是编译器,这种方法是唯一的”。

第二种方法:

#if      OS_CRITICAL_METHOD == 2
#define  OS_ENTER_CRITICAL()  asm {PUSHF; CLI}                   /* Disable interrupts                        */
#define  OS_EXIT_CRITICAL()   asm  POPF                                 /* Enable  interrupts                        */
#endif

进入临界区之前将标志寄存器进栈,然后关中断、进入临界区,退出临界区时出栈恢复标志寄存器,那么就能保持进入临界区之前中断关闭或是开启的状态。这种方法问题在于,一些编译器可能对插入的行汇编代码优化得并不好,这一方法未必可行,尤其重要的是,PUSH和POP指令改变了堆栈指针的值,若处理器支持堆栈指针相对寻址模式,则会造成所有使用堆栈指针的程序出错,问题严重。

第三种方法:

#if      OS_CRITICAL_METHOD == 3
#define  OS_ENTER_CRITICAL()  (cpu_sr = OSCPUSaveSR())    /* Disable interrupts                        */
#define  OS_EXIT_CRITICAL()   (OSCPURestoreSR(cpu_sr))       /* Enable  interrupts                        */
#endif

解决上述矛盾的最好方法,专门定义一个变量OS_CPU_SR来暂存PSW,而不用堆栈,避免对堆栈指针的改变从而引发的系统不稳定,但明显的后果是,需要访问内存,没有前两者实现效率高,而且在对实时性要求严格的系统中,曾加时间复杂度很可能是不能被接受的。

      综上所述,在实际开发时针对不同的情况选择不同的实现方式以达到令人接受的效果。

      还有个需要想明白的问题,对于不可屏蔽中断应该做怎样的处理...

你可能感兴趣的:(操作系统)