菜鸟学uC/OS_II(6)<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
By <?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />Norman
2008-7-12
移植uCOS_II<续>
昨天学习了uCOS_II移植要注意的一般事项,很空洞;今天需要来思考一下如何具体学习移植,是继续看书,然后再去实践?还是直接找块板子来干上一票?这是个问题!
还是先来看看利用BC45IDE集成环境是如何来编译作者所给出的例程的。
1、新建一个工程,设置好编译模式:DOS(Standard)/Large
2、添加所需要的文件。这里需要5个:ucos_ii.c、os_cpu_c.c、os_cpu_a.asm三个移植有关的文件以及源文件test.c、pc.c(这是PC功能函数,可以将其归为应用程序源文件)
3、编译、build(这个怎么翻译的?忘了,直接上英文吧)、运行
这里,似乎与前面看到的移植文件有些不同,其实这只是编译文件有所不同的,其实,在文件中,必定包含了其他相关的文件如OS_CFG.H等文件(邵老师的书中提到这么一句话:“INCLUDES.H不涉及真正移植部分的内容,但是在编译移植到时候要用到它”,我想这就是最好的解释了)。例如:test.c文件中就有一句”#include ‘includes.h’”,而我们知道,在includes.h中又包含了很多其他的文件,那么我就在想,是否连其他文件都不需要添加,就直接编译test.c好不好?试一试。
不行吧?结果发现了23个链接错误;这应该是预料之中的,因为,在includes.h中,包含的是头文件,并没有我所需要的处理器相关的源文件,因此,我们没有编译产生处理器相关的目标文件,所以,在编译器链接这些文件的时候当然就找不到这些所需要的文件了。
所以,从分解的角度来讲,我们需要将所需的移植文件都编译成为目标文件,然后将其链接成为应用程序文件或者映像文件。
从这些分析思考中,我觉得移植过程的编译如果依赖IDE的编译器,最重要的就是设置编译器的环境选项。这些可能都没有什么困难。而我所面临的问题在于:1、理解进而修改、编写移植所需要的文件;2、使用非IDE环境的编译
如何移植uCOS_II到80X86
接下来跟着作者的思路来具体理解一下移植文件。
INCLUDES.H文件:
包含编译所需要的文件头,这没有什么好说的。
OS_CPU.H文件:
定义数据类型、堆栈中断处理方法;这里需要注意的是,在方法3中,定义的两个函数要在OS_CPU_A.ASM中添加;作者在PC环境下使用的中断向量为128(0x80);PC上留给用户使用的中断向量还是有很多的,如:0x80~0xF0、0x4B~0x5B、0x5D~0x66、0x68~0x6F等等,如果使用其他处理器,相应也有众多中断向量提供使用。
这里作者作了一个宏定义:#define uCOS 0x80
而我们前面看到的OS_TASK_SW()宏也是在这里定义的:#define OS_TASK_SW() asm INT uCOS
它模仿了一次中断,而中断服务子程序必须指向汇编函数OSCtxSw()(OS_CPU_A.ASM)。
疑惑:他们是如何关联的?这个入口地址真的是OSCtxSw()吗?
书上第十三章也提到,需要中断指令、陷阱指令指向函数OSCtxSw(),移植者必须知道编译器、处理器是如何让中断向量指向此函数的。这句话真伤我的心,为什么作者就不直接说说例程是如何关联的呢?文中似乎提到了:使用中断指令的时候,会作一些处理,自然而然责任落到了OSCtxSw()函数的头上,因为OSCtxSw()就是这个作用。天哪,我困惑了。看来是计算机硬件没有学好,要不要再看看?
<解惑:哈哈哈,郁闷,早该想到修改中断向量应该是应用程序的事情嘛:在例程中,PC功能模块中定义了一个修改中断向量的函数PC_VectSet(),在TEST.C的main函数中调用了一次安装任务切换中断,在TaskStart()函数中调用一次安装时钟中断。这样就完全明白了。唉,菜鸟就是菜! ~_~ >
接下来是时钟节拍处理和浮点仿真,这些都是针对PC的,先不深究。
_OSCtxSw PROC FAR
;
PUSHA ; Save current task's context
PUSH ES ;
PUSH DS ;
;
MOV AX, SEG _OSTCBCur ; Reload DS in case it was altered
MOV DS, AX ;
;
LES BX, DWORD PTR DS:_OSTCBCur ; OSTCBCur->OSTCBStkPtr = SS:SP
MOV ES:[BX+2], SS ;
MOV ES:[BX+0], SP ;
;
CALL FAR PTR _OSTaskSwHook ; Call user defined task switch hook
;
MOV AX, WORD PTR DS:_OSTCBHighRdy+2 ; OSTCBCur = OSTCBHighRdy
MOV DX, WORD PTR DS:_OSTCBHighRdy ;
MOV WORD PTR DS:_OSTCBCur+2, AX ;
MOV WORD PTR DS:_OSTCBCur, DX ;
;
MOV AL, BYTE PTR DS:_OSPrioHighRdy ; OSPrioCur = OSPrioHighRdy
MOV BYTE PTR DS:_OSPrioCur, AL ;
;
LES BX, DWORD PTR DS:_OSTCBHighRdy ; SS:SP = OSTCBHighRdy->OSTCBStkPtr
MOV SS, ES:[BX+2] ;
MOV SP, ES:[BX] ;
;
POP DS ; Load new task's context
POP ES ;
POPA ;
;
IRET ; Return to new task
;
_OSCtxSw ENDP
OS_CPU_C.C文件:
此文件需要改写10个函数,其中9个Hook相关函数不一定需要,必须的就是一个OSTaskStkInit()函数,此函数初始化任务堆栈,由OSTaskCreate()或OSTaskCreateExt()调用。
其初始化同中断后的堆栈处理可能看起来比较相似,在内容上有所不同。
堆栈初始化传递了三个比较重要的参数(参数opt目前没有用到作:opt = opt处理):task、pdata、ptos,按照编译器直接传参处理,以此初始化参数pdata的段地址、偏移地址,task的段地址、偏移地址,SW状态字(这里又有一个疑惑:就是状态字的设置是根据处理器还是编译器来设置的?不幸的是,现在还没有看到合理的解释),以及按顺序的寄存器压栈。最后返回初始化后的堆栈栈顶指针。
OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
{
INT16U *stk;
opt = opt; /* 'opt' is not used, prevent warning */
stk = (INT16U *)ptos; /* Load stack pointer */
*stk-- = (INT16U)FP_SEG(pdata); /* Simulate call to function with argument */
*stk-- = (INT16U)FP_OFF(pdata);
*stk-- = (INT16U)FP_SEG(task);
*stk-- = (INT16U)FP_OFF(task);
*stk-- = (INT16U)0x0202; /* SW = Interrupts enabled */
*stk-- = (INT16U)FP_SEG(task); /* Put pointer to task on top of stack */
*stk-- = (INT16U)FP_OFF(task);
*stk-- = (INT16U)0xAAAA; /* AX = 0xAAAA */
*stk-- = (INT16U)0xCCCC; /* CX = 0xCCCC */
*stk-- = (INT16U)0xDDDD; /* DX = 0xDDDD */
*stk-- = (INT16U)0xBBBB; /* BX = 0xBBBB */
*stk-- = (INT16U)0x0000; /* SP = 0x0000 */
*stk-- = (INT16U)0x1111; /* BP = 0x1111 */
*stk-- = (INT16U)0x2222; /* SI = 0x2222 */
*stk-- = (INT16U)0x3333; /* DI = 0x3333 */
*stk-- = (INT16U)0x4444; /* ES = 0x4444 */
*stk = _DS; /* DS = Current value of DS */
return ((OS_STK *)stk);
}
OSTaskStkInit_FPE_x86()函数是作者针对Borland编译器写的一个浮点仿真库的堆栈初始化函数,目的是在任务堆栈中预留一段浮点仿真库所使用的堆栈;它还有一个功能就是规格化任务堆栈(要使用浮点仿真库的话),使得堆栈栈底上移,留下任务堆栈空间,这样做不会在使用中冲毁任务堆栈。这个函数是处理器、编译器相关的,是作者做模拟浮点运算的,先不深究。
其他Hook函数<略>
OS_CPU_A.ASM文件:
这个文件中的四个函数我决定要花足够的时间来深入理解,一来搞清楚同CPU相关的操作,二来看是否能够解除我前面的疑惑。
函数OSStartHighRdy()——
先从书中领悟一些东西:
【
该函数由SStart()函数调用,功能是运行优先级最高的就绪任务,在调用OSStart()之前,用户必须先调用OSInit(),并且已经至少创建了一个任务。OSStartHighRdy()默认指针OSTCBHighRdy指向优先级最高就绪任务的任务控制块(OS_TCB)(在这之前OSTCBHighRdy已由OSStart()设置好了)。图F9.3给出了由函数OSTaskCreate()或 OSTaskCreateExt()创建的任务的堆栈结构。很明显,OSTCBHighRdy->OSTCBStkPtr指向的是任务堆栈的顶端。
为了启动任务,OSStartHighRdy()从任务控制块(OS_TCB)中找到指向堆栈的指针,然后运行POP DS,POP ES , POPA, 和 IRET指令。此处笔者将任务堆栈指针保存在任务控制块的开头,这样使得堆栈指针的存取在汇编语言中更容易操作见(OS_TCB结构)。
当执行了IRET指令后,CPU会从(SS:SP)指向的堆栈中恢复各个寄存器的值并执行中断前的指令。SS:SP+4指向传递给任务的参数pdata。
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
】
这些内容基本解释了这个函数的工作过程:在调用OSStartHighRdy()之前,将被调用的高优先级任务环境是在某时刻被保存到任务堆栈中的;调用OSStartHighRdy()就是要来重新运行这个任务;那么,OSStartHighRdy()的工作就是要先找到这个指针——OSTCBHighRdy——>OSTCBStkPtr,就是找到任务堆栈的入口地址,执行恢复任务环境的命令:POP DS,POP ES ,POPA;并开始这个任务:IRET;IRET执行时,堆栈指针指向任务返回地址,这样就即将开始运行任务:因为这个返回地址就是我们前面看到的任务入口地址,IRET会将这个任务入口地址弹出,并把任务入口地址放在CS:IP寄存器中,地址后面跟着的状态字或者状态位放在SW寄存器中。这里注意,在执行IRET时,SS:SP+4指向的pdata参数会传递给任务。其实从整个过程来看,我们并不知道到底是谁调用或者说激活了这个任务,总之这个任务就这样运行了。
_OSStartHighRdy PROC FAR
MOV AX, SEG _OSTCBHighRdy ; 载入DS,应该是开始运行函数吧
MOV DS, AX ;
;
CALL FAR PTR _OSTaskSwHook ; 调用用户函数处理CPU寄存器等
;此时OSRunning=FALSE,只做恢复寄存器的工作,并不做保存工作。函数之前是没有运行的,那OSRunning
;肯定是FALSE了,而OSStartHighRdy()函数就是要运行这个函数,那么将OSRuning也是必须做到:
MOV AL, 1 ; OSRunning 被设置为TRUE
MOV BYTE PTR DS:_OSRunning, AL ;标志多任务已经开始运行
;将OSRunning设置成为TRUE之后,OSTaskSwHook()函数就可以做先保存后恢复到操作了。
LES BX, DWORD PTR DS:_OSTCBHighRdy ;找到堆栈入口地址
MOV SS, ES:[BX+2] ;恢复堆栈段寄存器
MOV SP, ES:[BX+0] ;恢复堆栈指针寄存器
;
POP DS ;恢复任务环境
POP ES ;
POPA ;
;
IRET ; 弹出任务入口地址,运行任务
_OSStartHighRdy ENDP
这里有一个Hook函数的处理,若要用到,必须查询OSRuning的状态;来确定Hook函数能够做什么样的工作,或者是只需做什么样的工作等等……
函数OSCtxSw()——
同样先来理解书中所述内容:
【
OSCtxSw()是一个任务级的任务切换函数(在任务中调用,区别于在中断程序中调用的OSIntCtxSw())。在80x86系统上,它通过执行一条软中断的指令来实现任务切换。软中断向量指向OSCtxSw()。在μC/OS-II中,如果任务调用了某个函数,而该函数的执行结果可能造成系统任务重新调度(例如试图唤醒了一个优先级更高的任务),则在函数的末尾会调用OSSched(),如果OSSched()判断需要进行任务调度,会找到该任务控制块OS_TCB的地址,并将该地址拷贝到OSTCBHighRdy,然后通过宏OS_TASK_SW()执行软中断进行任务切换。注意到在此过程中,变量OSTCBCur始终包含一个指向当前运行任务OS_TCB的指针。
图F9.4是任务被挂起或被唤醒时的堆栈结构。在80x86处理器上,任务调用OS_TASK_SW()执行软中断指令后,先向堆栈中压入返回地址(段地址和偏移量),然后是状态字寄存器SW。紧接着用PUSHA ,PUSH ES,和 PUSH DS保存任务运行环境。最后用OSCtxSw()在任务OS_TCB中保存SS和SP寄存器。
任务环境保存完后,将调用用户定义的对外接口函数OSTaskSwHook()。请注意,此时OSTCBCur指向当前任务OS_TCB,OSTCBHighRdy指向新任务的OS_TCB。在OSTaskSwHook()中,用户可以访问这两个任务的OS_TCB。
从对外接口函数OSTaskSwHook()返回后,由于任务的更替,变量OSTCBHighRdy被拷贝到OSTCBCur中,同样,OSPrioHighRdy被拷贝到OSPrioCur中。OSCtxSw()将载入新任务的CPU环境,首先从新任务OS_TCB中取出SS和SP寄存器的值,然后运行POP DS,POP ES,POPA取出其他寄存器的值,最后用中断返回指令IRET完成任务切换。
需要注意的是在运行OSCtxSw()和OSTaskSwHook()函数期间,中断是禁止的。
】
结合对OSStartHighRdy()的理解,根据代码,对这一函数有了更进一步的理解。切换函数总的来说,是保存当前任务的环境,恢复新任务的环境,中间插入了一个Hook函数的调用,先不深究。
具体来说,这里增加了对保存和恢复堆栈环境的理解,因为,内核是通过堆栈的指针SS:SP来对任务堆栈来操作的,因此,一个任务切换下,需要保存被切换任务的堆栈;新的任务要运行,恢复环境还得靠堆栈SS:SP。具体过程应该是:被切换的任务——找到当前任务的OS_TCB将被切换任务堆栈段寄存器SS和堆栈指针SP保存;恢复的任务——找到被恢复任务的OS_TCB,将保存在OS_TCB中的SS:SP恢复到CPU中,CPU利用它们来恢复任务其他环境。
_OSCtxSw PROC FAR
;
PUSHA ;保存当前任务环境
PUSH ES ;
PUSH DS ;
;
MOV AX, SEG _OSTCBCur ; Reload DS in case it was altered
MOV DS, AX ;
;
LES BX, DWORD PTR DS:_OSTCBCur ;指向新的堆栈的指针保存在OS_TCB中
MOV ES:[BX+2], SS ;OSTCBCur->OSTCBStkPtr = SS:SP
MOV ES:[BX+0], SP ;任务环境包括这个堆栈指针SS:SP
;
CALL FAR PTR _OSTaskSwHook ; Call user defined task switch hook
;
MOV AX, WORD PTR DS:_OSTCBHighRdy+2 ;处理新任务,新任务成为当前任务
MOV DX, WORD PTR DS:_OSTCBHighRdy ;首先要找到OS_TCB
MOV WORD PTR DS:_OSTCBCur+2, AX ;
MOV WORD PTR DS:_OSTCBCur, DX ;
;
MOV AL, BYTE PTR DS:_OSPrioHighRdy ;新任务优先级成为当前优先级
MOV BYTE PTR DS:_OSPrioCur, AL ;
; 然后去找OS_TCB中的堆栈相关指针,利用堆栈相关指针来恢复新任务的环境的:
LES BX, DWORD PTR DS:_OSTCBHighRdy ; SS:SP = OSTCBHighRdy->OSTCBStkPtr
MOV SS, ES:[BX+2] ;新任务堆栈寄存器传给CPU寄存器
MOV SP, ES:[BX] ;这个堆栈寄存器在新任务的OS_TCB中保
; 存,就像前面保存任务的环境一样。SS:堆栈段寄存器;SP:堆栈指针寄存器;CPU要通过它们来找到新任务
; 的环境,并进行恢复。
POP DS ; 恢复新任务环境
POP ES ;
POPA ;
;
IRET ; 执行中断返回,装入新任务的程序计数器
; 值和状态字,让新任务恢复运行。
_OSCtxSw ENDP
函数OSIntCtxSw()——
作为专用于中断切换,它的功能同OSCtxSw()相似,只不过由于中断机制中,自动保存了SW等CPU现场,所以此函数就不负责这类工作了,它的工作就是恢复新任务环境并使之运行。
所以,具体代码比较容易分析(可参看OSCtxSw()的分析),需要注意的却是中断处理需要注意到地方(以前的内容)。
_OSIntCtxSw PROC FAR
;
CALL FAR PTR _OSTaskSwHook ; Call user defined task switch hook
;
MOV AX, SEG _OSTCBCur ; Reload DS in case it was altered
MOV DS, AX ;
;
MOV AX, WORD PTR DS:_OSTCBHighRdy+2 ; OSTCBCur = OSTCBHighRdy
MOV DX, WORD PTR DS:_OSTCBHighRdy ;
MOV WORD PTR DS:_OSTCBCur+2, AX ;
MOV WORD PTR DS:_OSTCBCur, DX ;
;
MOV AL, BYTE PTR DS:_OSPrioHighRdy ; OSPrioCur = OSPrioHighRdy
MOV BYTE PTR DS:_OSPrioCur, AL
;
LES BX, DWORD PTR DS:_OSTCBHighRdy ;SS:SP = OSTCBHighRdy->OSTCBStkPtr
MOV SS, ES:[BX+2] ;
MOV SP, ES:[BX] ;
;
POP DS ; Load new task's context
POP ES ;
POPA ;
;
IRET ; Return to new task
;
_OSIntCtxSw ENDP
函数OSTickISR()——
其实,笔者在这个函数中加入了很多PC相关的处理,所以看起来功能比较杂乱,实际上,可能移植到其他处理器上就没有这么复杂了。
如果中断嵌套是第一层,那么需要保存堆栈指针;核心的内容是要调用OSTimeTick(),这样uCOS_II就进行相应的处理:给所有延时任务的等待时间以及有时限等待任务的等待时限节拍参数减1——这里可以编写用户的中断服务子程序,也就是说,在这里的DOS时钟节拍只是对应于PC的处理;中断服务子程序完成之后,调用OSIntExit(),如果中断服务子程序ISR使优先级更高的任务进入了就绪态,则OSIntExit()不返回中断的任务,而是做任务切换,让更高优先级任务运行。
来看看代码:
首先是在PC上的代码:这段代码处理了DOS中断,因此显得比较长——
_OSTickISR PROC FAR
;
PUSHA ; Save interrupted task's context
PUSH ES
PUSH DS
;
MOV AX, SEG(_OSIntNesting) ; Reload DS
MOV DS, AX
INC BYTE PTR DS:_OSIntNesting ; Notify uC/OS-II of ISR
;
CMP BYTE PTR DS:_OSIntNesting, 1 ; if (OSIntNesting == 1)
JNE SHORT _OSTickISR1 ; OSIntNesting = 1,则需要保存SS:SP,否则……
MOV AX, SEG(_OSTCBCur) ; Reload DS
MOV DS, AX
LES BX, DWORD PTR DS:_OSTCBCur ; OSTCBCur->OSTCBStkPtr = SS:SP
MOV ES:[BX+2], SS ; 保存当前堆栈指针
MOV ES:[BX+0], SP ; 之后再去处理PC环境
;
_OSTickISR1:
MOV AX, SEG(_OSTickDOSCtr) ; Reload DS
MOV DS, AX
DEC BYTE PTR DS:_OSTickDOSCtr ; 处理PC环境
CMP BYTE PTR DS:_OSTickDOSCtr, 0
JNE SHORT _OSTickISR2 ; Every 11 ticks (~199.99 Hz), chain into DOS
;
MOV BYTE PTR DS:_OSTickDOSCtr, 11
INT 081H ; Chain into DOS's tick ISR
JMP SHORT _OSTickISR3
_OSTickISR2:
MOV AL, 20H ; Move EOI code into AL.
MOV DX, 20H ; Address of 8259 PIC in DX.
OUT DX, AL ; Send EOI to PIC if not processing DOS timer.
;
_OSTickISR3:
CALL FAR PTR _OSTimeTick ; Process system tick
;
CALL FAR PTR _OSIntExit ; Notify uC/OS-II of end of ISR
;
POP DS ; Restore interrupted task's context
POP ES
POPA
;
IRET ; Return to interrupted task
;
_OSTickISR ENDP
而真正的比较通用的代码如下(所以,一般按照这个模式来编写):
_OSTickISR PROC FAR
;
PUSHA ; Save interrupted task's context
PUSH ES
PUSH DS
;
MOV AX, SEG(_OSIntNesting) ; Reload DS
MOV DS, AX
INC BYTE PTR DS:_OSIntNesting ; Notify uC/OS-II of ISR
;
CMP BYTE PTR DS:_OSIntNesting, 1 ; if (OSIntNesting == 1)
JNE SHORT _OSTickISR1 ; OSIntNesting = 1,则需要保存SS:SP,否则……
MOV AX, SEG(_OSTCBCur) ; Reload DS
MOV DS, AX
LES BX, DWORD PTR DS:_OSTCBCur ; OSTCBCur->OSTCBStkPtr = SS:SP
MOV ES:[BX+2], SS ; 保存当前堆栈指针
MOV ES:[BX+0], SP ; 之后再去处理PC环境
;
_OSTickISR1:
INT 081H ; Chain into DOS's tick ISR
;
CALL FAR PTR _OSTimeTick ; Process system tick
;
CALL FAR PTR _OSIntExit ; Notify uC/OS-II of end of ISR
;
POP DS ; Restore interrupted task's context
POP ES
POPA
;
IRET ; Return to interrupted task
;
_OSTickISR ENDP
(待续)
文章评论(0条评论)
登录后参与讨论