2024年3月19日发(作者:)

第5期 

2011年1O月 

微处理机 

No.5 

MICROPR0CESSORS 

Oct.,2011 

微机软件・ 

基于X86体系p ̄COS—II在VC6.0下的移植 

段玉波,吴光敏 

(昆明理工大学理学院,昆明650093) 

摘 要: COS—II的范例程序是在Borland C/C++上编译的,对于大多数学习者来说并不熟 

悉这个编译环境,为学习 cOs—II还要去适应BC的编译环境。基于Vc++6.0良好的集成编译 

环境,探讨了基于x86计算机将 COS—II移植到VC++6.0编译环境,以供教学与学习参考使用。 

关键词:txCOS—II操作系统;移植;VC++6.0编译环境 

DOI编码:10.3969/j.issn.1002—2279.2011.05.012 

中图分类号:TP368.2 文献标识码:B 文章编号:1002—2279(2011)05—0041—05 

Porting of uCOS—Il in VC6.0 based on x86 

DUAN Yu—b0,WU Guang—rain 

(Faculty ofScience,Kunming Univeniq fScoience and Technology,Kunming 650093,China) 

Abstract:The original txCOS—II examples are compiled on Borland C/C++,which is not familiar 

for most learners,SO they have to adapt to the compile environment before learning COS—II.Based on 

VC++6.0 good integrated compile environment.this paper discussed the process of porting the IxCOS— 

II to the VC++6.0 compile environment based on x86 computer.aiming to provide teaching and learning 

re ̄rence use. 

Key words:txCOS—II;Porting;VC++6.0 

1移植需要解决的主要问题 

移植主要解决三个问题,时钟获得、中断处理和 

任务切换。这里仅分析这几个问题的解决思路,后 

面第三部分将详细讲述其实现过程。 

在PC中,时钟节拍由硬件定时器产生,硬件定 

时器会周期性中断CPU。PC机时钟节拍的中断向 

量为0x08,IxCOS—II将此向量截取,使之指向 

c0s—II的时钟节拍中断服务子程序OSTickISR 

(),而原先的中断向量保存在中断向量0x81中。 

然而,在基于控制台的windows保护模式下不能像 

数调用定时回调函数是和主线程同时被windows操 

作系统调度的,并没有起到中断的作用。所以在调 

用定时回调函数的时候必须停止主线程的运行,退 

出回调函数则恢复主线程的运行。这些事情可以都 

放在定时回调函数,也就是 ̄C/OS—II的时钟中断 

处理函数中完成。Windows下要挂起一个线程的运 

行,首先要得到这个线程的句柄,然后调用Suspend— 

Thread(hangdler)和ResumeThread(handler)就可以 

挂起和继续执行线程。 

任务的切换。任务切换就是进行任务的上下文 

切换,研究选择了不带浮点运算的上下文切换进行 

DOS下面那么容易,直接通过一个函数调用就能够 

修改中断。windows下要修改中断涉及到驱动程 

序,这样就加大了移植的困难度与复杂度。因此,考 

虑到本移植只是为了教学和学习,并没有应用到对 

实时性要求高的产品,所以最终决定采用windows 

分析,任务的上下文和 C0s—II在80x86上移植的 

上下文很相近,不同点是段寄存器不用保存,因为 

VC下任务的切换是在同一个线程完成的,而保护模 

式下段寄存器的值在同一个线程下是不改变的。 

下的软件定时器来模拟时钟中断。 

时钟中断处理子程序。通过软件定时器来模拟 

产生I ̄C/OS—II的时钟中断,但timeSetEvent()函 

2 Borland C/C++与VC++6.0下编译的主 

要区别 

p ̄COS—II的作者使用了Borland C/C++编译 

作耋简 :收稿日期

段玉波(1987一),男,云南省曲靖市人,在读硕士研究生,主研方向:嵌入式系统。 

2011—01—13 

42・ 微处理机 

器,如今为便于学习要将其移植到VC++6.0编译 

环境下,首先要说明二者的一些区别,因为这直接关 

系到与CPU相关的那部分代码的编写。 

采用Borland C/C++编译器编译连接源文件 

模式下,而VC++6.0建立win32工程文件编译连 

接后, cOs—II是在Windows系统平台上作为控制 

台应用程序运行于保护模式下,二者主要区别如表 

1所示。 

后, c0s—II作为DOS程序运行于大存储模式实 

表1 DOS程序与控制台程序比较表 

Borland C/C++的库文件与VC使用的库文件 

有很大差异,Borland C/C++的一些宏也是VC++ 

32位宽 

opt=opt; 

6.0所不支持的,因此需要做相应的替换或屏蔽部 

分未使用代码。 

Borland C/C++编译器对C文件不支持汇编语 

言嵌套,而VC++6.0支持在C程序中嵌套汇编代 

码。基于此,工程中没有单独的汇编文件,而是将相 

关代码放到OS—CPU—C.C中。 

stk=(INT32U )ptos;/ 装载空堆栈人口 

指针 / 

stk=(INT32U)pdata;/ 带参数的函 

数调用 / 

一一

一一

stk=(INT32U)0x00000000;//子程序 

是从当前esp+4处取得传人的参数,所以此处要空 

出4个字节 

一一

3具体移植步骤 

3.1建立和配置工程 

stk=(INT32U)task;/ Put pointer to 

stk=(INT32U)0x00000202;/ EFL 

task on top of stack / 

一一

创建工程:建立一个空的win32控制台应用程 

序,在此工程内创建相关目录并将 cOs—II文件 

添加到其中。 

配置工程:将 COs—II相关路径添加到工程配 

置选项中;添加与软件定时器有关的库winmm.1ib。 

3.2 Includes.h 

=0X00000202 / 

一一

stk=(INT32U)0xAAAAAAAA;/:}= 

/ EAX=0xAAAAAAAA 

一一

stk=(INT32U)0xCCCCCCCC;/ 

stk:(INT32U)0xDDDDDDDD;/ 

/ 

ECX=OxCCCCCCCC / 

一一

在原有基础上增加两个头文件: 

#include<windows.h> 

EDX=0xDDDDDDDD 

一一

#include<mmsystem.h>//包含时钟函数的头 

文件,需要windows.h的支持。 

3.3 OS

CPU

stk=(INT32U)0xBBBBBBBB;/:l: 

EBX=0xBBBBBBBB / 

一一

C.C文件的修改 

stk=(INT32U)0x00000000;/ ESP 

stk=(INT32U)0xl11111I1;/ EBP 

/ 

stk 

0x00000000 esp可以任意 / 

一一

3.3.1 OSTaskStkInit() 

初始化任务堆栈。保护模式下程序是在同一个 

段址内处理的,因此段址不用压栈,为研究方便,也 

没有把浮点寄存器压栈。Windows保护模式下堆栈 

以32位字为单位进行处理,因此数据类型由 

Oxl111 

.一

(INT32U)0x22222222;/ ESI 

0x2222 / 

¥一一stk 

(INT32U)0x33333333;/:lc EDI 

INT16U改为INT32U,代码如下: 

程序清单L1初始化任务堆栈 

OS

0x3333 / 

retum((OS—STK )stk);/ 返回新的堆栈 

STK OSTaskStkInit(void(:lc task)(void 

栈顶指针 / 

} 

3.3.2 OSStartHighRdy() 

从处于就绪态优先级最高的任务的TCB中取 

pd),void pdata,OS—STK ptos,INT16U opt) 

{ 

INT32U stk;//x86体系console下寄存器为 

5期 段玉波等:基于X86体系tzCOS—II在VC6.0下的移植 ・43・ 

得堆栈地址并恢复到SP,恢复所有寄存器使优先级 

asm{ 

最高的任务开始运行,该函数由OSStart()函数调 lea eax,nextstart;任务切换回来后从nextstart 

用。代码如下: 

程序清单L2启动最高优先级任务 

void OSStartHighRdy(void) 

{ 

0STaskSwHook(); 

OSRunning=TRUE; 

asm{ 

nov ebx,[OSTCBCur];0STCBCur结构的第一 

个参数就是esp,不用管ess;注:此处OSTCBCur和 

OSTCBHighRdy指向同一个TCB 

nov esp,[ebx];恢复堆栈 

popad;恢复所有通用寄存器,共8个 

popfd;恢复标志寄存器 

ret;ret指令相当于pop eip但保护模式下不允 

许使用eip 

;永远都不返回 

} 

} 

3.3.3 OSCtxSw() 

任务级的任务切换函数,其实是完成任务的上 

下文切换。由于VC++6.0下任务是在一个线程中 

切换的,而且保护模式下段址寄存器在同一个线程 

下的值不变,因此不用保存段寄存器。任务切换时 

的压栈情况如图1所示。 

模拟pushad 

模拟pushfd——一 

子程序是从当前的 

保存任务切换地址———_+ 

esp+4处取得的传 

入参数,所以要空 

出4个字节 

图1 任务切换压栈后状态 

程序清单L3手动任务切换 

void OSCtxSw(void) 

{ 

开始 

push eax 

pushfd;标志寄存器的值 

pushad;ift存EAX一一EDI 

mov ebx,『OSTCBCur] 

mov[ebx],esp;把堆栈人口的地址保存到当 

前TCB结构中 

} 

OSTaskSwHook(); 

OSTCBCur=OSTCBHighRdy; 

OSPrioCur=OSPrioHighRdy; 

asm{ 

nov ebx,[OSTCBCur] 

mov esp,[ebx];得到OSTCBHighRdy的esp 

popad;恢复所有通用寄存器,共8个 

popfd;恢复标志寄存器 

ret;跳转到指定任务运行 

} 

nextstart://任务切换回来的运行地址 

return; 

} 

3.3.4 0SIntCtxSw() 

中断级任务切换函数,由于控制台程序不能处 

理中断,所以此处用库函数模拟中断来实现时钟节 

拍终端服务子程序,但其中并没有保存相应寄存器, 

因此需要在这里保存CPU寄存器,这与IxCOS—II 

原来的程序是不同的。在Test.C中定义了一个主 

线程句柄HANDLE和一个CONTEXT保存主线程上 

下文。在进行任务切换时,首先保存相应寄存器,然 

后把要运行的的任务的上下文填人CONTEXT结构 

并保存,完成切换。代码如下: 

程序清单L4中断级任务切换函数 

extem C0NTExT Context: 

extern HANDLE mainhandle; 

void OSIntCtxSw(void) 

{ 

OS

STK sp; 

OSTaskSwHook(); 

sp=(OS—STK )Context.Esp;//得到主线 

程当前堆栈指针 

//在堆栈中保存相应寄存器。 

一一

sp=Context.Eip;//先保存eip 

44・ 微处理机 

一一

sp=Context.EFlags;//保存en 

一一

印 Context.Eax; 

:Ic一一sp Context.Ecx: 

一一

sp Context.Edx; 

一一

sp Context.Ebx: 

一一

sp:Context.Esp;//此时保存的esp是 

错误的,但OSTCBCur保存了正确的 

一一

sp Context.Ebp: 

一一

sp Context.Esi: 

:l=一一sp=Context.Edi; 

OSTCBCur一>OSTCBStkPtr:(OS_STK )sp; 

//保存当前esp 

OSTCBCur=OSTCBHighRdy;//得到当前就 

绪最高优先级任务的tcb 

OSPrioCur:OSPrioHighRdy;//得到当前就绪 

任务最高优先级数 

sp=OSTCBHighRdy一>OSTCBStkPtr;//得到 

重新执行的任务的堆栈指针 

//恢复所有处理器的寄存器 

Context.Edi=:}:sp++; 

Context.Esi= sp++: 

Context.Ebp sp++: 

Context.Esp: sp++;//此时上下文中得到 

的esp是不正确的 

Context.Ebx’= sp++; 

Context.Edx= sp++: 

Context.Eex= sp++; 

Context.Eax=:}=sp++: 

Context.EFlags= sp++; 

Context.Eip sp++: ’ 

Context.Esp=(unsigned long)sp;//得到正确 

的esp 

SetThreadContext(mainhandle,&Context);//保 

存主线程上下文 

} 

3.3.5 OSTickISR() 

在调用timeSetEvent后,被定时器线程被周期 

调用,其代码如程序清单I5所示。 

程序清单L5时钟节拍ISR 

Void CALLBACK OSTickISR(unsigned int a,un- 

signed int b,unsigned long e,unsigned long d,un— 

signed long e) 

{ 

if(!FlagEn)//通过一个全局变量表示是否屏 

蔽中断 

return;//如果当前中断被屏蔽则返回 

SuspendThread(mainhandle);//中止主线程的 

运行,模拟中断产生.但没有保存寄存器 

GetThreadContext(mainhandle。&Context);//得 

到主线程上下文,为切换任务做准备 

OSIntNesting++: 

if(OSIntNesting==1){ 

OSTCBCur一>OSTCBStkPtr=(OS—STK ) 

Context.Esp;//保存当前esp 

} 

OSTimeTick();//ucos内部定时 

OSIntExit();//由于不能使用中断返回指令, 

所以此函数是要返回的 

ResumeThread(mainhandle);//模拟中断返回, 

主线程得以继续执行 

3.4 OS

CPU

C.H文件的修改 

开关中断通过设置一个全局变量来解决,代码 

如下: 

#define OS

ENTER

CRITICAL()FlagEn=0// 

禁止定时器调度 

#define OS

EXIT

_

CRITICAL()FlagEn=1//允 

许定时器调度 

因此在中断处理程序中首先判断当前是否允许 

中断,如果允许才挂起主线程,模拟中断产生,完成 

进一步的工作。另外,任务切换函数通过宏定义来 

调用。 

#define OSJ SK—SW()OSCtxSw()//保护模 

式下不能中断 

由于控制台程序中CPU状态寄存器是32位 

的,以及堆栈是以32位字进行处理,因此要做相应 

修改: 

typedef unsigned int OS

CPU

SR; 

typedef unsigned int OS

STK; 

3.5 Test.e文件的修改 

在main函数的OSInit()前,加入了一个VCInit 

()函数,主要初始化VC环境,包括获得主线程的句 

柄,设置上下文环境标志位,特别要注意的是,句柄 

的获得是要通过伪句柄转换的,代码如下所示: 

程序清单L6初始化VC环境 

HANDLE mainhandle;//增加的全局变量:主线 

程句柄 

CONTEXT Context;//增加的全局变量:主线程 

5期 段玉波等:基于X86体系 C0S—II在VC6.0下的移植 ・45・ 

切换上下文 

void VCInit(void) 

{ 

HANDLE cp,ct; 

//分辨率越高,缺省值为1ms 

LPTIMECALLBACK fptc,//指向一个回调函数 

DWORD dwUser,//存放用户提供的回调数据 

UINT fuEvent);//指定定时器事件类型: 

_

Context.ContextFlags=CONTEXTCONTROL; 

TIME

ONESHOT:uDelay毫秒后只产生一次事件; 

PERIODIC:每隔uDelay毫秒周期性地产生 

cp=GetCurrentProcess();//得到当前进程句柄 

ct=GetCurrentThread();//得到当前线程伪句 

柄 

TIME

事件,注意:该函数包含在mmsystem.h头文件中,同 

时需要windows.h的支持,并在工程设置“连接列‘象 

Dupli eHandle(cp,ct,cp,&mainhandle,0, 

/库模块”中添加winmm 1ib。 

TRUE,2);//伪句柄转换,得到线程真句柄 

3.8任务堆栈分配 

} 

尽量为任务分配足够大的堆栈空间,如果分配 

3.6随机数的产生 

不够,则可能导致调试或运行时发生内存溢出错误。 

random函数不是ANSI C标准,不能在gcc,VC 

等编译器下编译通过,可改用C++下的rand函数 

4结束语 

来实现,它由C++标准函数库提供。 

通过以上分析进行试验,移植工作成功完成,运 

具体实现方法是:首先用srand()来初始化随 

行正常,且经过单步跟踪调试也没有出现错误。 

机种子数,rand产生的随机数是从0到rand,max 

虽然 c0s—II在各种处理器上的移植项目不 

的,而rand—max是一个很大的数,因此产生从x到 

胜枚举,但单纯在VC++6.0下进行该例程的移植 

Y的数:从x到Y有Y—x+1个数,所以要产生从 

还未见到。移植的过程可以使人进一步了解与 

x到Y的数,只需要这样写:k=rand()%(Y—x+ 

CPU相关部分程序的编写以及弄懂t ̄COS—II时钟 

1)+X。 

调用的方法,为深入研究 C0s—II及运用到实际 

3.7启动时钟线程 

项目中打下基础。 

多任务启动后,优先级最高的任务TaskStart() 

最后,希望移植后的例程能够为IxCOS—II学 

首先运行,该任务调用timeSetEvent()产生定时器 

习者提供一个新的学习环境,也为 cOs—II课堂 

线程,定时器线程就会按预定的时间周期调用p.C/ 教学提供参考。 

Os—II的时钟节拍中断服务子程序OSTickISR()。 

参考文献: 

代码如下: 

[1] Jean J Labr0sse.嵌入式实时操作系统 ̄c/os—II[M]. 

timeSetEvent(1000/OS_TICKS—.PER—.SEC,0, 

北京:北京航空航天大学出版社,2003. 

OSTickISR,0,TIME

PERIODIC);//开启一个定时 

[2] 沈美明,温东掸.IBM—PC汇编语言程序设计[M].北 

器线程 

京:清华大学出版社,1999. 

timeSetEvent函数原型说明: 

[3] 罗清平.基于X86体系结构的 ̄COSII移植研究[D]. 

MMRESULT timeSetEvent(UINT uDelay,//以毫 

成都:四川大学,2008. 

[4]Jean Louis Gareau.Porting ̄C/OS—II to the x86 Protec. 

秒指定事件的周期 

ted Mode[EB/OL].2001.http://theblog.tistory.corn/ 

UINT uResolution,//以毫秒指定延时精度,数 

71 

值越小定时器事件 

特约声明 

本刊已人编《中国学术期刊网》(光盘版)、中国科学技术信息研究所《万方数据网》、科学技术部西南信 

息中心《中文科技期刊数据库》和(台湾)华艺数位艺术股份有限公司《中文电子期刊服务》

其作者文章著 

作权使用费与本刊稿费一次性付清。凡不同意人编的稿件,请作者在投稿时声明

。