启动ucosii之六PC_VectSet(0x08,OSTickISR)

原型来自PC.C
void PC_VectSet (INT8U vect, void (*isr)(void))
{
#if OS_CRITICAL_METHOD == 3/* Allocate storage for CPU status register*/
OS_CPU_SRcpu_sr;
#endif
INT16U*pvect;

pvect= (INT16U *)MK_FP(0x0000, vect * 4); /* Point into IVT at desired vector location*/
OS_ENTER_CRITICAL();
/*
函数名: FP_OFF
功能: 获取远地址偏移量
用法: unsigned FP_OFF(void far *farptr);
函数位置: dos.h
*/
*pvect++ = (INT16U)FP_OFF(isr); /* Store ISR offset*/
/*
函数名: FP_SEG
功能: 获取远地址段值
用法: unsigned FP_SEG(void far *farptr);
函数位置: dos.h
*/
*pvect= (INT16U)FP_SEG(isr); /* Store ISR segment*/
OS_EXIT_CRITICAL();
}
【启动ucosii之六PC_VectSet(0x08,OSTickISR)】
uCOS-II必须在多任务系统启动之后,也就是在调用OSStart()之后,再开启时钟节拍器.换句话说,调用OSStart()之后应做的第一件事是初始化定时器中断.
uCOS-II中,时钟节拍中断是一个非常重要的中断,因为整个操作系统的活动都受到它的激励.系统利用时钟中断来维持任务的延时、等待以及切换等操作,以保证多有任务都能平等的得到cpu的拥有权.可以说,它是整个OS的脉搏.
时钟节拍的任务切换是中断级的任务切换,中断程序中调用的切换函数OSIntCtxSw()
PC_VectSet(0x08,OSTickISR)
时钟节拍函数OSTickISR,时钟中断服务程序,原型OS_CPU_A.ASM
_OSTickISRPROCFAR
;
; 将通用寄存器的内容压入堆栈.
; 这些寄存器按以下顺序存储到堆栈:EAX、ECX、EDX、EBX、ESP、EBP、ESI 及 EDI
; 注意:按照依次压栈顺序,当执行PUSH SP时,把此时堆栈指针SP的位置记录下来,堆栈中记录的是SP_BX(此时SP指向BX存储单元)
; 保护上下文环境
PUSHA; Save interrupted task's context
PUSHES
PUSHDS
;
; uCOS-II.H中定义的OS_EXTINT8UOSIntNesting; /* Interrupt nesting level中断嵌套级别 */
; SEG-汇编程序将回送变量或标号的段地址值.获取记录中断嵌套层数
MOVAX, SEG(_OSIntNesting); Reload DS
MOVDS, AX
; uC/OS-II要求在中断服务程序开头调用OSIntEnter(),其作用是将记录中断嵌套层数的全局变量OSIntNesting加1.如果不调用OSIntEnter(),直接将OSIntNesting加1也是允许的
INCBYTE PTR DS:_OSIntNesting; Notify uC/OS-II of ISR
;
CMPBYTE PTR DS:_OSIntNesting, 1; if (OSIntNesting == 1)
; DS:_OSIntNesting不为1,不可以调度,短转移至_OSTickISR1
JNESHORT _OSTickISR1
; DS:_OSIntNesting为1,说明可以调度
; 载入当前任务控制块
MOVAX, SEG(_OSTCBCur); Reload DS
MOVDS, AX
; 将任务的私栈的堆栈指针从任务的任务控制块OS_TCB的在成员OSTCBStkPtr中取出来
; LES指令的功能是:把内存中指定位置的双字操作数的低位字装入指令中指定的寄存器、高位字装入ES寄存器
LESBX, DWORD PTR DS:_OSTCBCur; OSTCBCur->OSTCBStkPtr = SS:SP
MOVES:[BX+2], SS;
MOVES:[BX+0], SP;
;
_OSTickISR1:
; OSTickDOSCtr:OS_CPU.H中定义的全局变量,为调用DOS时钟中断而定义的计数器
; 8位变量OSTickDOSCtr,将保存时钟节拍发生的次数,每发生11次,调用DOS的时钟节拍函数一次,从而实现与DOS时钟的同步.
; OSTickDOSCtr是专门为PC环境而声明的,如果在其他非PC的系统中运行uC/OS-II,就不用这种同步方法,直接设定时钟节拍发生频率就行了
; 计数器OSTickDOSCtr减1,每发生11次中断,OSTickDOSCtr减到0,则调用DOS的时钟中断处理函数,调用间隔大约是54.93ms.
; 如果不调用DOS时钟中断函数,则向中断优先级控制器(PIC)发送命令清除中断标志.如果调用了DOS中断,则此项操作可免,因为在DOS的中断程序中已经完成了
MOVAX, SEG(_OSTickDOSCtr); Reload DS
MOVDS, AX
DECBYTE PTR DS:_OSTickDOSCtr
CMPBYTE PTR DS:_OSTickDOSCtr, 0
;
; OSTickDOSCtr不为0,说明还没有发生11次中断,转移; 否则调用DOS的时钟中断处理函数
JNESHORT _OSTickISR2; Every 11 ticks (~199.99 Hz), chain into DOS
;
;
MOVBYTE PTR DS:_OSTickDOSCtr, 11; 赋值OSTickDOSCtr=11,重新开始自减过程
; 调用软中断81H.即DOS中断处理函数
INT081H; Chain into DOS's tick ISR
JMPSHORT _OSTickISR3
_OSTickISR2:
; 数据输入输出时的地址要放在DX中,数据以AX作传输媒介
; egIN AL,21H 表示从21H端口读一个字节数据到AL.
; 将AL持有的数据写入20H端口
; 当每一次中断处理结束,需要发送一个EOI给8259A,以便继续接收中断
; 发送EOI是通过往端口20H或A0H写OCW2来实现的
MOVAL, 20H; Move EOI code into AL.
MOVDX, 20H; Address of 8259 PIC in DX.
OUTDX, AL; Send EOI to PIC if not processing DOS timer.
;
_OSTickISR3:
; 调用内核OS_CORE.C函数voidOSTimeTick (void)
CALLFAR PTR _OSTimeTick; Process system tick
;
;
;
; 调用内核OS_CORE.C函数voidOSIntExit (void)
; 在uC/OS-II中,由于中断的产生可能会引起任务切换,在中断服务程序的最后会调用OSIntExit()函数检查任务就绪状态,如果需要进行任务切换,将调用OSIntCtxSw().
; 所以OSIntCtxSw()又称为中断级的任务切换函数.由于在调用OSIntCtxSw()之前已经发生了中断,OSIntCtxSw()将默认CPU寄存器已经保存在被中断任务的堆栈中了.
CALLFAR PTR _OSIntExit; Notify uC/OS-II of end of ISR
;
; 依次执行POP DS/POP ES/POPA,从任务A的私栈中恢复相关寄存器的内容到CPU的相关寄存器中
POPDS; Restore interrupted task's context
POPES
; 依次弹出 edi, esi, ebp, esp, ebx, edx, ecx, eax
POPA
;
; 执行IRET中断返回指令
IRET; Return to interrupted task
;
_OSTickISRENDP
;
END

函数原型来自OS_CORE.C
//uC/OS-II有两种任务调度器:任务级的调度器和中断级的调度器.
//任务级的调度器由函数OSSched()来实现; 中断级的调度器由函数OSIntExit()来实现
voidOSIntExit (void)//关中断
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SRcpu_sr; //有些编译器可以得到处理器的状态字,OS_CPU_SR 这个宏就会在os_cpu.h中定义
#endif

//处理器只有在开放中断期间才能响应中断请求,而在其他时间是不能响应中断请求的.因为在应用程序中经常有一些代码段必须不受任何干扰地连续运行,这样的代码段称为临界段.
//因此为了使临界段在运行时不受中断所打断,在临界段代码前必须使处理器屏蔽中断请求,而在临界段代码后重新接触屏蔽,使处理器可以响应中断请求.
//uC/OS-II中用OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()这两个宏来实现中断的开放和关闭.而把与系统硬件相关的关中断和开中断的指令分别封装在这两个宏中.
if (OSRunning == TRUE) {//判断操作系统是否已经启动
OS_ENTER_CRITICAL(); //代码临界段,不允许中断
if (OSIntNesting > 0) { /* Prevent OSIntNesting from wrapping */
OSIntNesting--;
}
//在中断服务程序中不允许进行任务调度,所以每当进入中断服务程序就要把变量OSIntNesting加1,而当中断返回前则要把OSIntNesting减1.
//这样调度器就不会在中断服务程序中进行调度工作了
//需要注意的是OSIntNesting是一个记录中断嵌套层数的计数器变量,所以只有当其值为0的时候才允许调度.
//因此,尽管在嵌套中断中每个中断服务程序都调用了中断退出函数OSIntExit(),但并非每个嵌套中断结束前都会发生调度,而只有当变量OSIntNesting为0时才会发生调度
//没有其它中断且任务调度没有锁定→中断退出进行任务调度的条件
if ((OSIntNesting == 0) && (OSLockNesting == 0)) { /* Reschedule only if all ISRs complete ... */
/* ... and not locked.*/
//获取最高优先级别的任务在任务就绪
//表中的纵向位置 Y
OSIntExitY= OSUnMapTbl[OSRdyGrp];
//获取最高优先级别的任务的优先级别
OSPrioHighRdy = (INT8U)((OSIntExitY << 3) + OSUnMapTbl[OSRdyTbl[OSIntExitY]]);
//如果当前就绪任务中最高优先级别不等于当前中断的任务优先级则任务切换
if (OSPrioHighRdy != OSPrioCur) {/* No Ctx Sw if current task is highest rdy */
//获取最高优先级别的任务控制块指针
OSTCBHighRdy= OSTCBPrioTbl[OSPrioHighRdy];
//任务调度切换次数计数
OSCtxSwCtr++; /* Keep track of the number of ctx switches */
//进行中断级任务调度切换
//任务级调度器获得了最高级就绪任务的任务控制块指针之后,任务切换的工作是由宏OSCtxSw()来执行.(OSCtxSw()在文件OS_CPU_A.ASM中,由汇编编写).
//中断级的调度器任务切换工作是由OSIntCtxSw()来完成的,通常也是用汇编语言来完成的(在文件OS_CPU_A.ASM中)
OSIntCtxSw(); /* Perform interrupt level ctx switch*/
}
}
OS_EXIT_CRITICAL(); //打开中断
}
}

//时钟节拍
//目的是在时钟节拍到来时,检查每个任务的任务控制块中的.OSTCBDly-1后是否为0,如果是,那么表明这个任务刚才是挂起的状态,此时应改变为就绪态
voidOSTimeTick (void)
{
#if OS_CRITICAL_METHOD == 3/* Allocate storage for CPU status register */
OS_CPU_SRcpu_sr;
#endif
OS_TCB*ptcb;
OSTimeTickHook(); /* Call user definable hook*/
#if OS_TIME_GET_SET_EN > 0
OS_ENTER_CRITICAL(); /* Update the 32-bit tick counter*/
OSTime++;
OS_EXIT_CRITICAL();
#endif
if (OSRunning == TRUE) {
ptcb = OSTCBList; //时钟节拍到来时,传递控制块双向链表的指针
//空闲任务处于控制块双向链表的最后一个,如果取出的控制块为空闲任务的控制块,那么已经取到最后一个了,就结束/* Point at first TCB in TCB list*/
while (ptcb->OSTCBPrio != OS_IDLE_PRIO) {/* Go through all TCBs in TCB list*/
OS_ENTER_CRITICAL();
if (ptcb->OSTCBDly != 0) {/* Delayed or waiting for event with TO*/
if (--ptcb->OSTCBDly == 0) {/* Decrement nbr of ticks to end of delay*/
//检查任务是否处于强制挂起状态,如果是,那再挂起一个时钟节拍,否则就将它就绪
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended?*/
OSRdyGrp|= ptcb->OSTCBBitY; /* No,Make task R-to-R (timed out)*/
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
} else {/* Yes, Leave 1 tick to prevent ...*/
ptcb->OSTCBDly = 1; /* ... loosing the task when the ...*/
}/* ... suspension is removed.*/
}
}
ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list*/
OS_EXIT_CRITICAL();
}
}
}

; 中断级的调度器任务切换
; 在uC/OS-II中,由于中断的产生可能会引起任务切换,在中断服务程序的最后会调用OSIntExit()函数检查任务就绪状态,如果需要进行任务切换,将调用OSIntCtxSw().所以OSIntCtxSw()又称为中断级的任务切换函数.由于在调用OSIntCtxSw()之前已经发生了中断,OSIntCtxSw()将默认CPU寄存器已经保存在被中断任务的堆栈中了.
代码大部分与OSCtxSw()的代码相同,不同之处是,第一由于中断已经发生,此处不需要再保存CPU寄存器(没有PUSHA, PUSH ES, 或PUSH DS); 第二OSIntCtxSw()需要调整堆栈指针,去掉堆栈中一些不需要的内容,以使堆栈中只包含任务的运行环境
; 如果确实要进行任务切换,指针OSTCBHighRdy将指向新的就绪任务的OS_TCB
_OSIntCtxSw PROCFAR
;
CALLFAR PTR _OSTaskSwHook; Call user defined task switch hook
;
MOVAX, SEG _OSTCBCur; Reload DS in case it was altered
MOVDS, AX;
;
MOVAX, WORD PTR DS:_OSTCBHighRdy+2 ; OSTCBCur = OSTCBHighRdy
MOVDX, WORD PTR DS:_OSTCBHighRdy;
MOVWORD PTR DS:_OSTCBCur+2, AX;
MOVWORD PTR DS:_OSTCBCur, DX;
;
MOVAL, BYTE PTR DS:_OSPrioHighRdy; OSPrioCur = OSPrioHighRdy
MOVBYTE PTR DS:_OSPrioCur, AL
;
LESBX, DWORD PTR DS:_OSTCBHighRdy; SS:SP = OSTCBHighRdy->OSTCBStkPtr
MOVSS, ES:[BX+2];
MOVSP, ES:[BX];
;
POPDS; Load new task's context
POPES;
POPA;
;
IRET; Return to new task
;
_OSIntCtxSw ENDP

    推荐阅读