一、前 言 通常把单片机RTOS中的核心数据称为最佳“临界资源”,比如任务表中每个任务的状态,比如说系统的节拍,这些临界资源的特点是任何一个时刻必须是一个确定的值,其次是这些临界资源发生变化时,需要用程序执行一系列操作,让整个RTOS建立起新的运行状态。 比如当某个任务的休眠节拍达到预定值时,就需要将该任务的状态从休眠变成就绪。由于就绪的任务多了,然后就需要查询就绪任务中优先级最高的任务,如果不是当前任务,还需要进行任务切换。进行这一系列的操作必须是连续的,不能被任何其他试图对这个过程涉及的临界资源进行读写操作的程序打断,因此这些对临界资源的操作也称为“临界过程”。 在操作系统理论中处理这类问题的方法就是建立一个“临界区”,临界区包括对应的临界资源和临界过程,然后选择一个方法来对这个临界区进行保护,使得临界过程不被打断。在单片机程序中,连续执行的程序只可能被中断打断,因此对于单片机RTOS进行临界区保护的最简单方法就是进入临界区时候关闭总中断,不让任何程序来打断临界过程,退出临界区的时候再打开总中断,恢复系统的正常工作状态。 微山x51-uCOSII是笔者在STC8H单片机上移植的uC/OS-II系统,但是原版上的临界区保护方法2和方法3笔者认为存在瑕疵,所以另外选择了加锁解锁方法作为可嵌套的临界区保护方法。 二、临界区保护方法可嵌套的必要性 (1)在uC/OS-II中最简单的临界区保护是方法1: #defineOS_ENTER_CRITICAL() EA=0 // 直接禁止中断 #defineOS_EXIT_CRITICAL() EA=1 // 直接允许中断 但是这个方法1是不可嵌套的。 (2)对于uC/OS-II程序,假如原来有两段程序被临界区保护: … L1: OS_ENTER_CRITICAL(); … L2: OS_EXIT_CRITICAL(); … 其中L1到L2之间的程序就是保护着的临界区。然后如果用户扩展这个这个保护区域如下: … L0: OS_ENTER_CRITICAL(); … L1: OS_ENTER_CRITICAL(); … L2: OS_EXIT_CRITICAL() … L3: OS_EXIT_CRITICAL() … 新的临界区范围设计为从“L0”到“L3”,也就是从“L0”开始关闭中断,到“L3”再打开中断。通过宏展开,上面的程序变为: … L0: EA=0; … L1: EA=0; … L2: EA=1; … L3: EA=1; … 显然中断被在“L2”意外提前打开,“L2”到“L3”之间的临界区没有得到保护,如果这段程序被某个中断服务程序打断,则可能产生意外的后果。 因此可靠的RTOS临界区保护方法应该是可以嵌套的。 三、微山x51-uCOSII的可嵌套临界区保护方法 (3)加锁解锁是操作系统最常用的临界区保护方法,用到关闭打开总中断的具体保护方法上,其基本的思路是设置一个计数器变量(也称“锁”),每次进入临界区,计数器加1(也称“加锁”),每次退出临界区,计数器减1(也称“解锁”),第一次进入临界区时关闭总中断,退出临界区时如果计数器减到0,则打开总中断。 (4)定义了两种临界区保护方法,缺省的是加锁解锁方法: 其中进入和退出临界区的函数是用汇编语言写的: (5)进入临界区保护函数“UCX51_BIOS_ENTER_CRITICAL”的第一条汇编指令“JBC EA,UCX51_BIOS_ENTER_CRITICAL_02”是一条8051CPU专门为加锁解锁定制的“原子指令”,其定义为: JBC BIT, Lab 这条指令相当于下面两条指令: JB BIT, Lab … Lab: CLR BIT 其含义是如果BIT=1,则转移到Lab处执行,并且同时清掉BIT,否则继续执行JBC下面一条指令。之所以被称为原子指令,是因为使用两条指令存在漏洞,以BIT为EA为例: JB EA, Lab … Lab: CLR EA 对于这两条指令,在执行完第一条指令,在执行Lab处指令之前,中断并没有被禁止,因此有可能产生硬件中断,加锁程序被中断,俗称“锁没有加牢”。 采用原子指令“JBC”就不一样,这条指令执行时EA同时被关闭了,因此这条指令后不会再发生硬件中断,“锁”就加牢了。换言之原子指令是8051CPU提供的一把“硬件锁”。 (6)逻辑上说,进入临界区时如果中断是允许的EA=1,那么肯定是第一次进入临界区,所以第580行和第581行程序进行初始设置。 反之,进入临界区时如果中断是不允许的EA=0,那么就要区分是否是第一次进入,第572行就设置进入临界区时中断标准为0。
(7)笔者认为对于RTOS,系统的可靠性是第一位的,如果有可能对于程序员的疏漏也应该进行保护,因此比起一般的退出临界区保护程序,微山x51增加第586和第587行保护程序。由于加锁计数器“UCX51_BIOS_CRITICAL_ENTERED”是一个8位字节变量,完整退出临界区保护后其值为0。如果没有这个保护,如果程序中由于某种原因先出现了退出临界区保护函数调用,则计数器就会被减为“255”的非0嵌套值,这将会影响下面程序执行,而加了这个保护就避免了这种情况。
|