· μC/OS-II怎样实现多任务 前面我们大概讨论了任务切换,还没涉及调度等基础且核心的内容。 本节接着就针对μC/OS-II简单说明下这些内容。 『调度思想』 每个操作系统内核有自身的任务调度思想。多数实时内核基于优先级调度。 基于优先级调度的意思是:内核总是让处于就绪态的、优先级最高的任务先运行。 基于优先级调度的内核还分为两种:不可剥夺型、可剥夺型。μC/OS-II的内核为可剥夺型。 多数实时内核都是可剥夺型:优先级最高的任务一旦就绪,总能得到CPU使用权,而原来运行的任务就挂了。 顺便提下,某些OS支持时间片轮转(如μC/OS-III),某些则不支持(如μC/OS-II)。 『调度器』 查找最高优先级就绪任务,并确定是否让它运行的工作由调度器(Scheduler)完成。 任务级的调度由函数OSSched()完成。中断级的调度由另一个函数OSIntExt()完成,这些函数将在以后说明。 『任务的状态』 μC/OS-II的任务有五种状态: ·休眠态:任务驻留在内存中,但并不被操作系统调度。 ·就绪态:任务已经准备好,可以运行,但是由于比它优先级更高的任务在运行,所以,它暂时还不能运行。 ·运行态:任务掌握了CPU的使用权,正在运行。 ·挂起态:任务在等待某一事件(共享资源、信号)的发生。 ·被中断态:任务被中断,暂时得不到运行。 『任务的组成』 μC/OS-II的任务由以下三部分组成: ·任务控制块 前文提到: ·将当前任务的SP保存到全局变量中,μC/OS做的是将SP保存到“当前运行任务控制块”中。 ·将当前任务更新为即将运行任务,μC/OS做的是将“当前任务控制块指针”指向“即将运行任务控制块”。 实际上,任务控制块还有很多用途,更多相关说明请参阅后面章节。 ·任务堆栈 这个前面章节已有相关说明,简单来说,就是各个任务有自己的堆栈。 ·任务程序代码 这个显而易见,每个任务当然有自己的程序代码。 · 前后台系统与实时系统对比· 临界区 由于实现多任务,需要保护临界区,所以,“临界区”也就暂寄放在这(不排除以后调到其它章)。 【临界资源与临界区】 临界资源是一次仅允许一个进程使用的共享资源。全局变量是一种临界资源。 不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。 每个进程中访问临界资源的那段代码称为临界区(CriticalSection)。 每次只准许一个进程进入临界区,进入后不允许其他进程进入。 不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。 【保护临界区的实现】 关中断可使得系统能够避免同时有其它任务或中断服务进入临界区。 在μC/OS-II中,定义了两个宏,以避免不同C编译器厂商选择不同的方法来处理开关中断。 这两个宏是: ·OS_ENTER_CRITICAL():进入临界区。 ·OS_EXIT_CRITICAL():离开临界区。 它们总是成双成对,不离不弃,将临界区“夹住”。 【进出临界区的实现方式】 在μC/OS-II中,有三种方式可以实现进出临界区,选择哪种方法由OS_CRITICAL_METHOD决定。 『OS_CRITICAL_METHOD = 1』 ·OS_ENTER_CRITICAL():用CPU指令关中断。 ·OS_EXIT_CRITICAL():用CPU指令开中断。 这种方式虽然最简单,但有如下缺点:离开临界区后,中断总是被打开。而如果,进入临界区前,中断是被关着的,显然不应被打开。在Jean.J.Labrosse著的μC/OS-II(第2版)中,提到:“对一些CPU或编译器,使用这种方式却是唯一选择①。” 『OS_CRITICAL_METHOD = 2』 ·OS_ENTER_CRITICAL():在堆栈中保存中断的开关状态,然后再关中断,示例代码如下: #define OS_ENTER_CRITICAL() asm("PUSH PSW")asm("DI") ·OS_EXIT_CRITICAL():从堆栈中弹出中断的开关状态,示例代码如下: #define OS_EXIT_CRITICAL() asm("POP PSW") 这种方式能够得到原来中断的开关状态(中断的开关状态记录在PSW中),但却很可能由于编译器对插入的行汇编支持不好,导致以上方法不可行,或者说导致严重错误。因为,PUSH的出现导致了堆栈指针发生了改变,这样,在临界区内存取若存放在栈中的局部变量,就不再是局部变量原来的地址了。 『OS_CRITICAL_METHOD = 3』 ·OS_ENTER_CRITICAL():将CPU的状态寄存器值保存到局部变量cpu_sr,在笔者的工程中,代码如下: #define OS_ENTER_CRITICAL() {cpu_sr =OS_CPU_SR_Save();} OS_CPU_SR_Save MRS R0②, PRIMASK ;保存全局中断标志 CPSID I ;关中断 BX LR ·OS_EXIT_CRITICAL():将cpu_sr装回CPU的状态寄存器,在笔者的工程中,代码如下: #define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);} OS_CPU_SR_Restore MSR PRIMASK, R0 ;恢复全局中断标志 BX LR 这种方式能够得到原来中断的开关状态,条件是:CPU拥有状态寄存器,且状态寄存器的某一位可以被用来标识某处理器设置中断的办法。大部分ARM都采用本方式保护临界区。 ------------------------------------------------------------------------------------------------------------------------------------------- ① 也就是说,无法使用方式2及方式3,也就是无法读写PSW(可能根本就没有PSW)。笔者认为,如果使用方式1,要解决前文提到的缺点,则只能是: 开关中断时(进出临界区除外),将中断记录到某个全局变量中,相应的,恢复中断,则是读取相应的全局变量。 ② 由于函数返回值存在R0中,所以将PRIMASK放到R0,之后,即可通过代码“cpu_sr =OS_CPU_SR_Save();”将cpu状态寄存器存到cpu_sr中。 ------------------------------------------------------------------------------------------------------------------------------------------- 【保护临界区的注意事项】 使用OS_ENTER_CRITICAL()、OS_EXIT_CRITICAL()的方式来保护临界区要注意:如果在调用一些如OSTimeDel()之类的功能函数之前关中断,应用程序将会崩溃。这个问题是这样引起的:任务被挂起一段时间,直到挂起时间到,但由于中断关掉了,时钟节拍中断一直得不到服务。显然,所有的挂起(PEND)类调用都存在这类问题。 读者可以记得一条普适规则:调用功能函数时,中断应当总是开着的。 |