原型来自PC.C
void PC_VectSet (INT8U vect, void (*isr)(void))
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_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();
}
uCOS-II必须在多任务系统启动之后,也就是在调用OSStart()之后,再开启时钟节拍器.换句话说,调用OSStart()之后应做的第一件事是初始化定时器中断.
uCOS-II中,时钟节拍中断是一个非常重要的中断,因为整个操作系统的活动都受到它的激励.系统利用时钟中断来维持任务的延时、等待以及切换等操作,以保证多有任务都能平等的得到cpu的拥有权.可以说,它是整个OS的脉搏.
时钟节拍的任务切换是中断级的任务切换,中断程序中调用的切换函数OSIntCtxSw()
PC_VectSet(0x08,OSTickISR)
时钟节拍函数OSTickISR,时钟中断服务程序,原型OS_CPU_A.ASM
_OSTickISR PROC FAR
;
;将通用寄存器的内容压入堆栈.
;这些寄存器按以下顺序存储到堆栈:EAX、ECX、EDX、EBX、ESP、EBP、ESI 及 EDI
;注意:按照依次压栈顺序,当执行PUSH SP时,把此时堆栈指针SP的位置记录下来,堆栈中记录的是SP_BX(此时SP指向BX存储单元)
;保护上下文环境
PUSHA ; Save interrupted task's context
PUSH ES
PUSH DS
;
;uCOS-II.H中定义的OS_EXT INT8U OSIntNesting; /* Interrupt nesting level中断嵌套级别 */
;SEG-汇编程序将回送变量或标号的段地址值.获取记录中断嵌套层数
MOV AX, SEG(_OSIntNesting) ; Reload DS
MOV DS, AX
;uC/OS-II要求在中断服务程序开头调用OSIntEnter(),其作用是将记录中断嵌套层数的全局变量OSIntNesting加1.如果不调用OSIntEnter(),直接将OSIntNesting加1也是允许的
INC BYTE PTR DS:_OSIntNesting ; Notify uC/OS-II of ISR
;
CMP BYTE PTR DS:_OSIntNesting, 1 ; if (OSIntNesting == 1)
;DS:_OSIntNesting不为1,不可以调度,短转移至_OSTickISR1
JNE SHORT _OSTickISR1
;DS:_OSIntNesting为1,说明可以调度
;载入当前任务控制块
MOV AX, SEG(_OSTCBCur) ; Reload DS
MOV DS, AX
;将任务的私栈的堆栈指针从任务的任务控制块OS_TCB的在成员OSTCBStkPtr中取出来
;LES指令的功能是:把内存中指定位置的双字操作数的低位字装入指令中指定的寄存器、高位字装入ES寄存器
LES BX, DWORD PTR DS:_OSTCBCur ; OSTCBCur->OSTCBStkPtr = SS:SP
MOV ES:[BX+2], SS ;
MOV ES:[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的中断程序中已经完成了
MOV AX, SEG(_OSTickDOSCtr) ; Reload DS
MOV DS, AX
DEC BYTE PTR DS:_OSTickDOSCtr
CMP BYTE PTR DS:_OSTickDOSCtr, 0
;
;OSTickDOSCtr不为0,说明还没有发生11次中断,转移;否则调用DOS的时钟中断处理函数
JNE SHORT _OSTickISR2 ; Every 11 ticks (~199.99 Hz), chain into DOS
;
;
MOV BYTE PTR DS:_OSTickDOSCtr, 11 ;赋值OSTickDOSCtr=11,重新开始自减过程
;调用软中断81H.即DOS中断处理函数
INT 081H ; Chain into DOS's tick ISR
JMP SHORT _OSTickISR3
_OSTickISR2:
;数据输入输出时的地址要放在DX中,数据以AX作传输媒介
;eg IN AL,21H 表示从21H端口读一个字节数据到AL.
;将AL持有的数据写入20H端口
;当每一次中断处理结束,需要发送一个EOI给8259A,以便继续接收中断
;发送EOI是通过往端口20H或A0H写OCW2来实现的
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:
;调用内核OS_CORE.C函数void OSTimeTick (void)
CALL FAR PTR _OSTimeTick ; Process system tick
;
;
;
;调用内核OS_CORE.C函数void OSIntExit (void)
;在uC/OS-II中,由于中断的产生可能会引起任务切换,在中断服务程序的最后会调用OSIntExit()函数检查任务就绪状态,如果需要进行任务切换,将调用OSIntCtxSw().
;所以OSIntCtxSw()又称为中断级的任务切换函数.由于在调用OSIntCtxSw()之前已经发生了中断,OSIntCtxSw()将默认CPU寄存器已经保存在被中断任务的堆栈中了.
CALL FAR PTR _OSIntExit ; Notify uC/OS-II of end of ISR
;
;依次执行POP DS/POP ES/POPA,从任务A的私栈中恢复相关寄存器的内容到CPU的相关寄存器中
POP DS ; Restore interrupted task's context
POP ES
;依次弹出 edi, esi, ebp, esp, ebx, edx, ecx, eax
POPA
;
;执行IRET中断返回指令
IRET ; Return to interrupted task
;
_OSTickISR ENDP
;
END
函数原型来自OS_CORE.C
//uC/OS-II有两种任务调度器:任务级的调度器和中断级的调度器.
//任务级的调度器由函数OSSched()来实现;中断级的调度器由函数OSIntExit()来实现
void OSIntExit (void)//关中断
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_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,如果是,那么表明这个任务刚才是挂起的状态,此时应改变为就绪态
void OSTimeTick (void)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_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 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