μC/OS-II 移植笔记 1(FreeScale 68HCS12 核单片机)
- defineOS_ENTER_CRITICAL()(cpu_sr=OSCPUSaveSR())/*Disableinterrupts*/
- #defineOS_EXIT_CRITICAL()(OSCPURestoreSR(cpu_sr))/*Enableinterrupts*/
上述代码中虽然列出了三种对临界区的不同处理方法,但实际只有第三种是正确的。
第一种方法直接开关中断,这种方法的问题在于退出临界区后中断就被强制性的打开了,及时在进入临界区前中断是关着的。在任务级代码中这样做没有太大的问题,因为在执行任务代码是中断基本都是打开的,中断几乎只有在临界区中才是关闭的。但是在中断处理函数中情况就不是这样的了,68HCS12 内核在进入中断处理函数后中断是关闭的,中断处理函数中要调用 OSIntExit(),OSIntExit()中是有临界区的,一退出临界区就会导致中断打开,CPU 处理新的中断,也就是形成所谓的中断嵌套。而中断嵌套是我们不希望发生的,因为允许中断嵌套并不能显著提升系统的性能,还会导致各个任务的堆栈使用量加大,,对于内存紧张的单片机来说,绝对弊大于利。因此,在我的移植代码中不允许中断嵌套,也就否掉了第一种临界区的处理方式。
第二种方法看似很好,进入临界区时先将 CCR 的值存入堆栈然后关闭中断,退出临界区时直接将堆栈的内容恢复到 CCR。看似很完美的解决方法,实际上却行不通。C 编译器要利用堆栈指针的地址来寻址局部变量,而汇编语句 pshc 却改变了堆栈指针的指向,导致对局部变量的访问产生了错位。
要想说明这个问题还要从 C 编译器对局部变量的处理方式说起,我使用的 C 编译器将局部变量放到堆栈上,利用堆栈指针(SP)间接寻址局部变量。如果堆栈指针指向的地址变了,访问局部变量时就要出问题。下面用个例子来说明:
volatile char a = 1;
volatile char b = 2;
__asm pshc; __asm sei;
a = 3;
b = 4;
__asm pulc;
将其转化为汇编代码后如下:
PSHD
volatile char a = 1;
LDAB #1
STAB 1,SP
volatile char b = 2;
LSLB
STAB 0,SP
__asm pshc; __asm sei;
PSHC
SEI
a = 3;
INCB
STAB 1,SP
b = 4;
INCB
STAB 0,SP
__asm pulc;
PULC
PSHD 指令调整堆栈指针,在堆栈上空出 2 个字节存放 a 和 b 的值。a 的地址为[SP+1],b 的地址为[SP],然后给 a 和 b 赋初值。PSHC 首先调整堆栈指针(SP=SP-1),然后将 CCR 寄存器的值存入堆栈,这里需要注意的是68HCS12核的堆栈是倒生堆栈,实栈顶(也就是说SP指向的是有数据的地方,而不是个空位),而老的68HC11内核是虚栈顶的(SP指向的是个空位)。这时 [SP] 中存的是 CCR 的值, [SP+1]指向 b,[SP+2]指向a。因此, STAB, 0,SP 将原本存的 CCR 的值改成了 4, b 的值却没有任何改变,说明这款编译器无法感知堆栈的变动生成正确的代码。因此第二种方法会导致错误的结果,不能采用。
这里多说两句,熟悉 x86 体系的读者可以将 68HCS12 核与 x86 内核做个对比。这两个核都是冯诺依曼结构体系,复杂指令集,从某种意义上可以说这两种内核具有某种神似。在 x86 内核上大多数编译器也是将局部变量存放到堆栈上,但是不同的是访问局部变量时用的是 BSP 寄存器而不直接使用 ESP 寄存器,因此在 x86 内核上用第二种方法处理临界区是没有问题的,并且可以认为是一种相当好的方式。从这也可以看出来寄存器多一些确实是有好处的。
第三种方式是实现两个函数 OSCPUSaveSR 和 OSCPURestoreSR。虽然这样会多些函数调用产生的额外开销,却没有了前两种方法的问题。这两个函数具体的实现可以放到OS_CPU_A.S 中,也可以在 OS_CPU_C.C。
如果用 C 语言实现,可以如下:
- OS_CPU_SROSCPUSaveSR(void)
- {
- __asm
- {
- tfrccr,b//copythevalueofCCRtotheregisterB
- sei//Disableinterrupts
- }
- }
- voidOSCPURestoreSR(OS_CPU_SRos_cpu_sr)
- {
- __asm
- {
- tfrb,ccr//BcontainstheCCRvaluetorestore,movetoCCR
- }
- }
想要理解上面代码,除了要知道汇编指令 tfr 和 sei 的含义。还要知道所谓的 C 语言调用约定,对于当前代码来说需要知道 C 语言编译器使用何种方式传递函数的参数和返回值。 这些知识在 编译器附带的文档《S12(X)Build Tools Reference Manual》可以查到。我这里只介绍直接相关的几条,其余的请自己查阅手册。
我所采用的编译器对参数固定的 C 函数采用 PASCAL 调用约定,参数采用堆栈传递,传递顺序为从左到右依次入栈,如果最后一个参数小于 4 个字节则最后一个参数采用寄存器传递。1 字节的参数存放到寄存器 B 中,OSCPURestoreSR 就是这种情况。函数的返回值如果也是 1 个字节,那么也通过 寄存器 B 传出来,OSCPUSaveSR 就是这种情况。关于这两个函数我就说这么多了。
如果想直接
μCOS-II移植笔记68HCS12核单片 相关文章:
- μC/OS-II 移植笔记 2(FreeScale 68HCS12 核单片机)(11-20)
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)