再看3处代码:
在uCOS_II.H中有如下定义:
OS_EXT OS_TCB *OSTCBPrioTbl[OS_LOWEST_PRIO +
1];//定义指向任务控制块的指针数//组,且每个优先级在同一时刻只对应一个任务
OS_EXT INT8U OSPrioCur;//用于保存目前任务的优先级
OS_EXT INT32U OSCtxSwCtr;//32位无符号全局整型变量,作为任务切换计数器
OS_EXT OS_TCB *OSTCBHighRdy;//指向最高优先级任务任务控制块的指针
if (OSPrioHighRdy != OSPrioCur)
//就绪态任务中的最高优先级已不是目前任务的优先级,则进行中断级的任务//切换
{
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
//将最高优先级任务控制块指针指向当前优先级最高的任务的任务控制块
OSCtxSwCtr++;//任务切换计数器加1
OSIntCtxSw();//调用中断级任务切换函数
}
此段代码体现出了可剥夺型实时操作系统内核的特点.
OSIntCtxSw()在80x86上的移植代码,此代码在OS_CPU_A.ASM中,代码如下:
_OSIntCtxSw PROC FAR
;
CALL FAR PTR _OSTaskSwHook ; 调用OSTaskSwHook()函数,此函数在
;OS_CPU_C.C中只是个空函数,留给用户
;在代码移植时自定义
;
MOV AX, SEG _OSTCBCur ;由于发生了段转移,恢复刚才(当前任务)数
MOV DS, AX; 据段
;
MOV AX, WORD PTR DS:_OSTCBHighRdy+2
;AH=_OSTCBHighRdy+3
;AL=_OSTCBHighRdy+2
MOV DX, WORD PTR DS:_OSTCBHighRdy
;DH=_OSTCBHighRdy+1
;DL=_OSTCBHighRdy
MOV WORD PTR DS:_OSTCBCur+2, AX ;_OSTCBCur+3=AH
;_OSTCBCur+2=AL
MOV WORD PTR DS:_OSTCBCur, DX ;_OSTCBCur+1=DH
;_OSTCBCur=DL
;OSTCBCur=OSTCBHighRdy
MOV AL, BYTE PTR DS:_OSPrioHighRdy ;
MOV BYTE PTR DS:_OSPrioCur, AL;OSPrioCur=
OSPrioHighRdy
;
LES BX, DWORD PTR DS:_OSTCBHighRdy ;取址指令
MOV SS, ES:[BX+2] ;
MOV SP, ES:[BX] ;
;SS:SP=OSTCBHighRdy->OSTCBStkPtr
POP DS ;DS出栈
POP ES ;ES出栈
POPA ;CPU其余寄存器出栈
;
IRET ; 中断返回
;
_OSIntCtxSw ENDP
以上汇编代码在移植时根据处理器不同要作修改
四.在ISR中通知任务做事的理解(以OSSemPost()为例)
在理解OSSemPost(),先要理解事件,如下是事件的数据结构:
typedef struct {
INT8U OSEventType;//事件类型,这里是OS_EVENT_TYPE_SEM即信号量
INT8U OSEventGrp; //等待任务所在的组
INT16U OSEventCnt; //当事件是信号量时,使用此计数器
void *OSEventPtr; //信号量时不使用
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//等待任务列表
} OS_EVENT;
其中OSEventGrp与OSEventTbl[]构成等待事件的任务列表,前面所讲的OSRdyGrp与OSRdyTbl[]具有同样的功能,划分也一模一样.
在ISR中调用函数OSSemPost(),给任务发信息,此函数在OS_SEM.C中:
INT8U OSSemPost (OS_EVENT *pevent)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif //定义开关中断类型
#if OS_ARG_CHK_EN > 0//如果启用了函数参数检查功能则进行参数检查
if (pevent == (OS_EVENT *)0) {
return (OS_ERR_PEVENT_NULL);//检查是否有事件发生,如果没有则报错
}
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {
return (OS_ERR_EVENT_TYPE);//检查当前事件是不是信号量,不是则出错
}
#endif
OS_ENTER_CRITICAL();//关中断
if (pevent->OSEventGrp != 0x00) { //如果等待事件发生的任务列表不为空,
//即有任务处于等待状态,则进入if
OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM);
//使对应事件的任务从等待变为就绪
OS_EXIT_CRITICAL();//开中断
OS_Sched(); //进行任务调度
return (OS_NO_ERR);
}
if (pevent->OSEventCnt < 65535) { //如果等待事件发生的任务列表为空,且信号量计数
//器的值小于65535,则信号量计数器加1,否则不执
//行if,而报信号量益出
pevent->OSEventCnt++;
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
OS_EXIT_CRITICAL();
return (OS_SEM_OVF);}
附:
uCOS_II 大致的启动过程:
main()
{
......
OSInit();
......
OSTaskCreate();//此函数在OS_TASK.C中,用于创建任务,调用了三个重要的系统函数
//它们是OSTaskInit();OS_TCBInit();OS_Sched();
......
OSStart();
}
OSTaskCreate()//此函数只能在main()及任务中调用,中断服务子程序不能调用
{
......
OSTaskStkInit();//此函数在OS_CPU_C.C中,用于创建任务堆栈,在移植过程中可根据
//具体情况做修改
OS_TCBInit();//此函数在OS_CORE.C中,用于初始化任务控制块,及就绪表
......
OS_Sched()();//此函数在OS_CORE.C中,是任务级调度函数,作用是获得最高优先级任务
...... //并进行调度,此函数包含一个重要函数OS_TASK_SW()
}
OSStart()
{
......
If(没有任务启动)
{ 获取最高优先级任务
OSStartHighRdy();//此函数在OS_CPU_A.ASM中,用于启动任务,在移植过程中随处理器
//不同要作修改
}
}
OS_TASK_SW()//在OS_CPU.H中它是一个宏定义,用于产生任务切换的中断,移植中要作修改
#define uCOS 0x80
#define OS_TASK_SW() asm INT uCOS
为什么在OSTaskCreate()中调用OS_Sched()后还要调用OSStart()来启用任务呢?
事实上在从main()中创建的任务是不执行OS_Sched()函数的,因为此时的任务并未启动,OSRunning的值为0。任务启动要通过OSStart()才行。相反,当在一个已启动的任务中调用OSTaskCreate()就通过OS_Sched()函数(OSRunning==1),而不用OSStart(),OSStart()只在操作系统启动时调用,任务中不调用
一.任务调度中的几个函数的区别:
----------------------------------------uCOSII启动时的任务调度--------------------------------------
OSStartHighRdy():该函数在OS_CPU_A.ASM中原形如下:
_OSStartHighRdy PROC FAR
MOV AX, SEG _OSTCBHighRdy ;
MOV DS, AX ;获得要运行任务的任务控制块所在段的段址
CALL FAR PTR _OSTaskSwHook ;调用用户自定义的任务切换接口函数
MOV AL, 1 ;0
MOV BYTE PTR DS:_OSRunning, AL ;置任务运行标志
;
LES BX, DWORD PTR DS:_OSTCBHighRdy ;
MOV SS, ES:[BX+2] ;
MOV SP, ES:[BX+0] ;从任务控制块首指的四个8位内存单元获得该任务的任务堆栈的地址
;
POP DS ;DS出栈至任务堆栈
POP ES ;ES出栈至任务堆栈
POPA ;将其余CPU寄存器出栈
;
IRET ;恢复代码段及指令寄存器内容,运行任务
_OSStartHighRdy ENDP
该函数由OSStart()调用
void OSStart (void)//在OS_CORE.C中
{
INT8U y;
INT8U x;
if (OSRunning == FALSE) {
y = OSUnMapTbl[OSRdyGrp];
x = OSUnMapTbl[OSRdyTbl[y]];
OSPrioHighRdy = (INT8U)((y << 3) + x);
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
OSTCBCur = OSTCBHighRdy;
OSStartHighRdy();
}//如果多任务环境还没有启动,则先获得就绪表中任务的最高优先级,再获得该优先级下任务的任务控制块的地址(通过OSTCBHighRdy =
OSTCBPrioTbl[OSPrioHighRdy],
OSTCBPrioTbl[]是一个指针数组,用于存放各优先级任务的任务控制块的地址,它在OS_TCBInit()中被赋值),然后调用OSStartHighRdy()启动任务。
}
-------------------------------任务级的任务切换--------------------------------
OSCtxSw():函数在OS_CPU_A.ASM中的原形如下:
_OSCtxSw PROC FAR
;
PUSHA ;将所有CPU寄存器压栈
PUSH ES ;经附加段寄存器压栈
PUSH DS ;将数据段寄存器压栈
MOV AX, SEG _OSTCBCur ;获取当前任务所在段的段址,放入DS
MOV DS, AX ;
LES BX, DWORD PTR DS:_OSTCBCur
;获取当前任务任务控制块的段地址及偏移地址
MOV ES:[BX+2], SS ;将当前任务堆栈的断址保存在当前任务的任务控制块中
MOV ES:[BX+0], SP ;将当前任务堆栈的偏移地址保存在当前的任务控制块中
CALL FAR PTR _OSTaskSwHook ;
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 ;将最高优先级任务的堆栈
MOV SS, ES:[BX+2] ;指针放回CPU的堆栈段寄存
MOV SP, ES:[BX] ;器及堆栈指针寄存器中
;SS:SP=
OSTCBHighRdy->OSTCBStkPtr此时的任务堆栈已改变,变为最高优先级任务(已是当前任务)的任务堆栈
POP DS ;
POP ES ;
POPA ;
IRET ;
_OSCtxSw ENDP
该函数并非由OS_Sched()直接调用而是通过软中断指令INT 0x80(CPU为80x86),
产生中断,到中断向量表中找的OSCtxSw()的入口地址,然后跳转到该函数并执行的。
OS_Sched() 函数原形如下:
void OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
INT8U y;
OS_ENTER_CRITICAL();
if ((OSIntNesting == 0) && (OSLockNesting == 0))
//已是中断前套最外层且无任务锁定则执行以下代码
{
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3) +
OSUnMapTbl[OSRdyTbl[y]]);
//从就绪表中获得任务的最高优先级
if (OSPrioHighRdy != OSPrioCur)
//如果当前任务的优先级已不是最高,则将行任务切换
{
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
//将OSTCBHighRdy指向具有最高优先级任务的任务控制块
OSCtxSwCtr++; //任务切换计数器加一
OS_TASK_SW(); //此函数是一个宏,见OS_CPU.H中的定义:
//#define uCOS 0x80
//#define OS_TASK_SW() asm INT uCOS
//执行中断,调用OSCtxSw()
}
}
OS_EXIT_CRITICAL();
}
-------------------------------中断级的任务切换--------------------------------
OSIntCtwSw()已经在中断处理过程中解释过,它是通过OSIntExit()来调用的.
二.关于操作系统源文件系统结构的认识
uCOSII源文件已将各文件作了很好的归类,以方便用户在其他处理器上以移植它的代码,其中OS_CPU.H,OS_CPU_A.ASM,OS_CPU_C.C是与用户具体使用的处理器相关的,在移植时
要根据处理器对其中的代码作相应修改,这就是所谓的HAL(硬件抽象层)。
另外OS_CFG.H,INCLUDES.H与用户具体的应用程序相关,包括决定任务的最低优先级,用户应用程序所能拥有的最大任务数等。
对于其余文件,用户在移植时一般不用考虑修改。
三.关于中断
1.中断嵌套:中断嵌套只能发生在中断服务子程序中,在中断服务子程序运行过程中,
当有更高优先级的中断发生且此时中断是打开的,则将发生中断嵌套
2.中断服务程序通知任务做事是通过事件使任务处于就绪状态,而并非立即进行任务切换,因为它们都是调用OS_Sched(),而函数只能在所有中断服务程序结束运行后才进行切换任务(因为if
((OSIntNesting == 0) && (OSLockNesting ==
0)){…OS_TASK_SW();…}),所以任何任务切换都不能发生在ISR中,而必须等到所有ISR
结束运行后(因为if ((OSIntNesting == 0) && (OSLockNesting == 0)){
…}),在OSIntExit()中进行.所以任何时刻uCOSII只能有一个任务处于运行态.
四.关于任务调度
在任务中调用OSTaskCreate ()与在任务中调用OSSemPost(),OSMboxPost(),OSFlagPost()
,OSQPost(),OSQPostFront()进行任务调度的区别是,前者任务处于睡眠态(尚未创建任务),而后者的任务处于等待状态(任务已将创建)。
五.OSSemPost(),OSMboxPost(),OSFlagPost(),OSQPost(),OSQPostFront()的相同点
在中断服务子程序中,都只是使任务从等待状态进入就绪状态,而不调用OS_Sched()
而在任务中调用这些函数进行任务切换,都是通过OS_Sched
文章评论(0条评论)
登录后参与讨论