(上海海事大学,上海 201306)
μC/OS-II是一个完整的可移植的抢占式实时多任务内核。由于它的可移植性特别强,大量的芯片均有向其移植的案例。由于Microsoft Visual C++ 6.0 (下简称“VC6.0”)具有相对于其它硬件芯片更直观简单的调试方法,本文主要研究在VC6.0环境下μC/OS-II移植的可行性与实现问题。
μC/OS-II作为一个自发表至今已经经历了许多年,无论是国外还是国内地位一直经久不衰的嵌入式操作系统内核,具有以下几个特点:有开源代码、具有可移植性、可固化、可裁剪、可剥夺性、多任务、可确定性、任务栈、系统服务、中断管理、稳定性与可靠性。本文重点讨论其可移植性。
移植就是让一个实时内核能在其它微处理器或者微控制器上运行。μC/OS-II代码大多使用C语言编写的原因就是为了能够方便移植,只是在读/写处理器、寄存器时,只能通过汇编来实现,由于μC/OS-II在设计时已经充分考虑了可移植性,所以μC/OS-II的移植相对来说是比较容易的。
要使μC/OS-II正常运行,处理器应当满足以下条件:
① 处理器C编译器可以产生重入型代码。
② 处理器支持中断并能产生定时中断。
③ 用C语言便可开关中断。
④ 能支持一定数量的数据存储和硬件堆栈。
⑤ 处理器有将堆栈指针以及其它CPU寄存器的内容读出,并存到堆栈或者内存中去的指令。
而VC6.0本身就是一款在Windows系统环境下的C语言开发软件,它可以产生可重入代码,并且具有函数timesetEvent产生时钟进行中断处理,suspendThread开中断,ResumeThread恢复中断。这些都是通过C语言来实现的。
同时只要确保电脑有足够的内存和存储空间,VC6.0就能支持一定数量的数据存储和硬件堆栈。最后VC6.0支持产生输入输出文件流的代码,满足最后一个要求。
由此分析得出μC/OS-II可以向VC6.0进行移植。
OS_CPU.H中的参数及类型如表1所列。
表1 OS_CPU.H中的参数及类型
OS_CPU_A.ASM中的参数及类型如表2所列。
表2 OS_CPU_A.ASM中的参数及类型
OS_CPU_C.C中的参数及类型如表3所列。
表3 OS_CPU_C.C中的参数及类型
OS_CPU.H的改写分为数据类型和宏与宏定义两类讨论。
根据表1中所示的数据类型性质进行改写如下:
用无符号字符(unsigned char)来移植布尔(BOOLEAN)和无符号8位整数(INT8U)。
用有符号字符(signed char)来移植有符号8位整数(INT8S)。
用无符号整形(unsigned int)来移植无符号16位整数(INT16U)和16位堆栈数据类型(OS_STK)与16位状态寄存器(OS_CPU_SR)。
用有符号整形(signed int)来移植有符号16位整数(INT16S)。
用无符号长整形(unsigned long)来移植无符号32位整数(INT32U)。
用有符号长整形(signed long)来移植有符号32位整数(INT32S)。
用单精度浮点型(float)移植单精度浮点数(FP32)。
用双精度浮点型(double)移植双精度浮点数(FP64)。
最后再用BYTE UBYTE WORD UWORD LONG ULONG分别对INT8S INT8U INT16S INT16U INT32S INT32U进行封装。
移植格式和封装方法分别用INT8S进行举例,其余均与其类似,在此就不多做阐述。
typedef signed char INT8S;
#define BYTE INT8S
(1)OS_STK_GROWTH
由于VC6.0是基于Windows的编译器,堆栈的使用方法和Intel 80x86一样,是从高到低的增长方式,所以OS_STK_GROWTH应当赋值为1,用define进行宏封装:
#define OS_STK_GROWTH 1
(2)OS_CRITICAL_METHOD
OS_CRITICAL_METHOD的值决定了操作系统的中断模式,μC/OS-II支持三种中断模式,这三种中断模式的区别就不在这一一论述,本文我们选用模式1:
#define OS_CRITICAL_METHOD 1
(3)OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()
关于函数OS_ENTER_CRITICAL()还有函数OS_EXIT_CRITICAL()的定义与前面所提到的OS_CRITICAL_METHOD的值有关,我们采用模式1,故有:
#defineOS_ENTER_CRITICAL() FlagEn=0
#define OS_EXIT_CRITICAL() FlagEn=1
只有OSTaskStkInit()函数在μC/OS-II中是必要的。另外9个函数必须声明,但可不包含任何代码。向VC6.0的移植就是这样的,注意的是,在OS_CFG.H中的OS_CPU_HOOKS_EN应当置为1。
OSTaskStkInit()函数作用是任务堆栈的初始化,函数中需要定义一个用于指向当前堆栈的堆栈指针stk,通过stk的指向来进行初始化的赋值,由于寄存器是32位的,所以stk的数据类型为INT32U。
前面也说过由于VC6.0是基于Windows的编译器,因此这边的移植也类似μC/OS-II向80x86上的移植,只是位数从16位变成了32位。
OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt){
INT32U *stk;
stk = (INT32U *)ptos;
*--stk = (INT32U)pdata;
*--stk = (INT32U)0X00000000;
*--stk = (INT32U)task;
*--stk = (INT32U)0x00000202;
*--stk = (INT32U)0xAAAAAAAA;
*--stk = (INT32U)0xCCCCCCCC;
*--stk = (INT32U)0xDDDDDDDD;
*--stk = (INT32U)0xBBBBBBBB;
*--stk = (INT32U)0x00000000;
*--stk = (INT32U)0x11111111;
*--stk = (INT32U)0x22222222;
*--stk = (INT32U)0x33333333;
return ((OS_STK *)stk);
}
由于在VC6.0中不支持直接对“*.asm”文件的编译,需要采用_asm{}进行汇编方面的操作,不过这也同时说明了许多操作可以用C语言进行代替,例如函数的调度以及赋值语句,接下来一个个进行讨论。
需要注意的是可能有部分编译器对_asm{}的编译过程中,没有“;”会报错,因此为了方便,本文均添加了“;”。
OSStartHighRdy()函数的作用是将进入就绪态的最高优先级任务运行。
它一开始需要先调用OSTaskSwHook()恢复CPU,还需要将OSRunning赋为TRUE。这两个操作可以不用汇编直接用C语言完成。
void OSStartHighRdy(void){
OSTaskSwHook();
OSRunning = TRUE;
_asm{
mov ebx, [OSTCBCur];//注释1
mov esp, [ebx];
popad;//注释2
popfd;
ret;
}
}
注释1:从任务控制块OS_TCB中获得并恢复堆栈指针,由于TCB的开始处就是堆栈指针的值,故可以这么直接赋值。
注释2:把CPU的所有整数寄存器的值保存在堆栈中。
OSCtxSw()函数是一个任务级的任务切换函数,它需要调用OSTaskSwHook()以及对OSTCBCur与OSPrioCur的值修改,这些可以直接用C语言完成。
void OSCtxSw(void){
_asm{
lea eax, restart; //注释1
push eax; //注释2
pushfd;
pushad;
mov ebx, [OSTCBCur]; //注释3
mov [ebx], esp;
}
OSTaskSwHook();
OSTCBCur = OSTCBHighRdy; //注释4
OSPrioCur = OSPrioHighRdy;
_asm{
mov ebx, [OSTCBCur] //注释5
mov esp, [ebx];
popad; //注释6
popfd;
ret;
}
restart:return;
}
注释1:告诉任务切换回来后,应当从写有“ restart:”处后开始。
注释2:将挂起任务的其它CPU寄存器保存到当前任务堆栈中。
注释3:将指向新的堆栈结构的指针保存到任务控制块OS_TCB中。
注释4 :将OSTCBHighRdy与OSPrioHighRdy分别赋值给OSTCBCur和OSPrioCur,因为新任务就是当前任务。
注释5:将新任务保存的栈指针传递给处理器相应寄存器中。
注释6:从新任务的堆栈中弹出任务。
OSIntCtxSw()任务切换函数作用是将当前任务的寄存器保存到堆栈中后将新任务堆栈弹出。由于该函数只调用了OSTaskSwHook()函数,其余均为赋值语句,因此,无需使用汇编语言。值得注意的是,esp的值由于在保存和弹出过程中发生了改变,需要再次赋值才行。
通过定义一个堆栈指针esp来作为动态指针,在全局变量Context辅助下完成以下操作:
void OSIntCtxSw(void){
OS_STK *esp;
OSTaskSwHook();
esp = (OS_STK *)Context.Esp; //注释1
*--esp = Context.Eip; //注释2
*--esp = Context.EFlags;
*--esp = Context.Eax;
*--esp = Context.Ecx;
*--esp = Context.Edx;
*--esp = Context.Ebx;
*--esp = Context.Esp;
*--esp = Context.Ebp;
*--esp = Context.Esi;
*--esp = Context.Edi;
OSTCBCur->OSTCBStkPtr = (OS_STK *)esp;
//注释3
OSPrioCur = OSPrioHighRdy; //注释4
OSTCBCur = OSTCBHighRdy;
esp = OSTCBHighRdy->OSTCBStkPtr;
Context.Edi = *esp++; //注释5
Context.Esi = *esp++;
Context.Ebp = *esp++;
Context.Esp = *esp++;
Context.Ebx = *esp++;
Context.Edx = *esp++;
Context.Ecx = *esp++;
Context.Eax = *esp++;
Context.EFlags = *esp++;
Context.Eip = *esp++;
Context.Esp = (INT32U)esp; //注释6
}
注释1:获取旧状态的堆栈指针给esp。
注释2:依次保存旧寄存器的值。
注释3:由于堆栈指针发生改变,再次保存现在的指针给esp。
注释4:将获得的需要被交换的新任务的OSTCBHighRdy与 OSPrioHighRdy赋值给OSTCBCur和OSPrioCur,并获取新任务的堆栈指针赋值给esp。
注释5:依次弹出新寄存器的值。
注释6:由于堆栈指针发生改变,再次保存现在的指针给Context.Esp。
OSTickISR()函数为节拍中断服务程序,用于在发生时钟中断的时候调用。
需要判断标识符FlagEn是否中断被静止,如果被静止,则立即返回任务。需要通过全局变量mainhandle和Context来进行辅助。
void OSTickISR(INT16U a, INT16U b, INT32U c, INT32U d, INT32U e){
if(!FlagEn) return;
SuspendThread(mainhandle); //注释1
if(!FlagEn){ //注释2
ResumeThread(mainhandle);
return;
}
GetThreadContext(mainhandle, &Context); //注释3
OSIntNesting++;
if (OSIntNesting == 1) {
OSTCBCur->OSTCBStkPtr = (OS_STK *)Context.Esp;
}
OSTimeTick();
OSIntExit();
SetThreadContext(mainhandle, &Context); //注释4
ResumeThread(mainhandle); //注释5
}
注释1:挂起主线程。
注释2:由于在挂起主线程的时候,可能系统正好禁止了中断,所以需要再次判断。
注释3:获取主线程的上下文,赋值给Context。
注释4:将经过变化后的Context保存在主线程。
注释5:恢复主线程。
综合上面所述,其实已经改写完成移植μC/OS-II到VC6.0上大部分的内容了,但是在主函数文件main.c中,仍需要添加一些东西。
在移植过程中,用到两个全局变量Context和mainhandle,这两个全局变量应在main.c中被定义:
HANDLE mainhandle;
CONTEXT Context;
其中mainhandle表示主线程,Context表示对应的上下文。此外,还需要一个初始化函数VCInit()。
void VCInit(void){
Context.ContextFlags = CONTEXT_CONTROL;
图6 二乘二取二平台实物图
[1] 游小明.轨道交通安全计算机的研究与实现[J].计算机工程,2011,37(6):231-233.
[2] 刘真.一种三取二安全计算机系统的设计与实现[J].铁路计算机应用,2016,25(11):49-52.
[3] 黄波,曹帮林,张福鑫,等.一种三模混合冗余总线控制系统设计研究[J].航天控制,2015,33(6):76-80.
[4] DaiShenghua,Li Yishi.Research on 2-out-of-2 multiplying 2 redundancy system used in high-speed train[C]//IEEE International Conference on Computer Science and Automation Engineering.IEEE,2011:483-486.
[5] 李勇,樊丁,石斌.航空发动机FADEC系统双机时钟级同步技术研究[J].计算机测量与控制,2006,14(4):508-509.
[6] 蔡煊,王长林.车载列车自动防护的二乘二取二安全计算机平台同步机制[J].计算机工程,2015,41(8):301-305.
[7] 张建军,王振志,张本宏.CAN网络精确时钟同步方法研究[J].电子测量与仪器学报,2012,26(9):752-757.
[8] 弓剑.各类电子式计轴设备工程应用浅析[J].铁道通信信号,2011,47(6):34-37.
[9] 朱党杰,杨仁忠.一种新型软件帧同步算法的研究与实现[J].科学技术与工程,2007(16):4044-4048.
陈梅(副教授),研究方向为交通信息工程及控制、嵌入式系统应用。