搞了一份KEIL手册来看汇编,呵呵,现在开始学习OSCtxSW这个函数
RSEG ?PR?OSCtxSw?OS_CPU_A
OSCtxSw:
USING 0 ;使用BANK 0
;设置标志:任务再次恢复运行时不必恢复所有寄存器
MOV DPTR,#OSMapTbl ;将OSMapTbl的地址作为基址
//将OSTaskID作为便宜地址,就可以访问到SOMapTbl数组中相对应OSTaskID(也就是当前任务ID)的常量
MOV A,OSTaskID
#if OS_MAX_TASKS < 9 // 判断任务数是否在9以下,也就是看是否使用8BIT的OSFastSwap可以表示
// 小于,那么直接将对应的常量跟OSFastSwap相或,既可将OSFastSwap中相应的位置位
// OSFastSwap相对应任务ID的位为高,则表示有任务调用,否则表示为中断调用
MOVC A,@A+DPTR
ORL A,OSFastSwap
MOV OSFastSwap,A
#else
// 大于,将ACC的借位标志位CY清零,并将任务ID与8相减,若是任务号小于8,则CY会置为,通过
// 判断CY的高低就可以知道当前任务是否小于8(也就说任务小于8呢,将使用OSFastSwap的低八位)
// (若是任务大于8,那么肯定在OSFastSwap的高八位中表示)
CLR C
SUBB A,#8
JC OSCtxSw_1 ;小于8,跳转到OSCtxSW_1处执行
MOVC A,@A+DPTR ; 大于8,那么将该任务ID减去8之后的值作为偏移量
ORL A,OSFastSwap
MOV OSFastSwap,A ;将A中的值与OSFastSwap高八位相或
LJMP C_OSCtxSw
OSCtxSw_1:
MOV A,OSTaskID
MOVC A,@A+DPTR
ORL A,OSFastSwap+1 ;与OSFastSwap低八位操作
MOV OSFastSwap+1,A
#endif
LJMP C_OSCtxSw
当看这段代码的时候,我十分不解的地方有两个,不过都是因为逻辑能力太弱和
汇编匮乏引起的,一个就是,如果最大任务大于9且当前任务大于8的时候,常量表中
只有9个量呀。假如当前任务是12,那么将超出常量表的最大表示范围了,后来才看
原来用了SUBB后,结果是存放在A中的,也就是说相当于偏移量其实是4,那么,用OSFastSwap的
高八位与常量表的第四位相或,其实就实现了呀,作者实现得真是非常巧妙,呵呵
还有一点就是KEIL对双字节变量在内存中的存储方式不了解,后来查看了资料,翻看了调试中的
汇编代码,知道了KEIL在存放双字节变量的时候,是先放高字节,再放低字节的,所以OSFastSWap+1
也就可以理解了。
继续看代码...........跳转到C_OSCtxSw
貌似这里才是最关键的地方哦,真正的任务堆栈处理就在这里
这段代码作者有用汇编来写了,为了提高效率
RSEG ?PR?C_OSCtxSw?OS_CPU_C
C_OSCtxSw:
PUSH Os_Enter_Sum ;保存关中断计数器
mov r2,sp ;保存堆栈指针(若是OSTaskID = OSNextTaskID)就直接函数返回
; cp1 = (unsigned char idata *)SP +1; 将CP1指向栈顶
MOV R0,SP
IF EN_SP2 <> 0
mov sp,#(Sp2-1) ;堆栈指向临时空间,允许“软非屏蔽中断”
ENDIF
INC R0
;temp用于保存OSnextTaskID的栈底地址
; temp = (unsigned char )OSTsakStackBotton[OSNextTaskID+1];
MOV A,#LOW (OSTsakStackBotton+01H)
ADD A,OSNextTaskID
MOV R1,A
MOV A,@R1
MOV R7,A
;CP2保存当前任务的栈底地址
; cp2 = OSTsakStackBotton[OSTaskID+1];
MOV A,#LOW (OSTsakStackBotton+01H)
ADD A,OSTaskID
MOV R1,A
MOV A,@R1
MOV R1,A
;如果当前任务小于下个任务
; if( OSNextTaskID > OSTaskID)
MOV A,OSNextTaskID
SETB C
SUBB A,OSTaskID
JC ?C0001
; {
;将OSNextTaskID堆栈复制到当前栈顶处
; while(cp2 != (unsigned char idata *)temp)
; {
; *cp1++ = *cp2++;
; }
MOV A,R7
CLR C
SUBB A,R1
MOV R6,A
?C0002:
MOV A,@R1
MOV @R0,A
INC R0
INC R1
DJNZ R6,?C0002
?C0003:
; temp = OSTsakStackBotton[OSTaskID+1] - (unsigned char idata *)SP-1;
MOV A,#LOW (OSTsakStackBotton+1)
ADD A,OSTaskID
MOV R1,A
MOV A,@R1
SETB C
;SUBB A,sp
SUBB A,r2
MOV R7,A
;SP指向刚移动好的下个任务堆栈的地方
; SP = (unsigned char )cp1 - 1;
DEC R0;
MOV SP,R0
;重新给任务分配空间
; for(i = OSTaskID+1;i < OSNextTaskID+1; i++)
; {
; OSTsakStackBotton -= temp;
; }
MOV A,OSNextTaskID
CLR C
SUBB A,OSTaskID
MOV R6,A
JZ ?C0005
MOV A,#LOW (OSTsakStackBotton)
ADD A,OSTaskID
MOV R1,A
MOV A,R7
CPL A
INC A
MOV R7,A
?C0004:
INC R1
MOV A,R7
ADD A,@R1
MOV @R1,A
DJNZ R6,?C0004
?C0005:
; OSTaskID = OSNextTaskID;
MOV OSTaskID,OSNextTaskID
; LoadCtx();
LJMP LoadCtx
; }
?C0001:
; 如果当前任务ID小于下个任务ID
; if( OSNextTaskID != OSTaskID)
MOV A,OSNextTaskID
XRL A,OSTaskID
JZ ?C000r
; {
;反向移动堆栈空间
; cp2--;
; cp1--;
; while(cp2 != (unsigned char idata *)temp)
; {
; *cp2-- = *cp1--;
; }
;MOV A,R7
;CLR C
;SUBB A,R1
;MOV R6,A
mov a,r0
clr c
subb a,r7
mov r6,a
?C0008:
DEC R0
DEC R1
MOV A,@R0
MOV @R1,A
DJNZ R6,?C0008
?C0009:
; temp = OSTsakStackBotton[OSTaskID+1] - (unsigned char idata *)SP-1;
MOV A,#LOW (OSTsakStackBotton+01H)
ADD A,OSTaskID
MOV R1,A
MOV A,@R1
SETB C
;SUBB A,SP
SUBB A,r2
MOV R7,A
; SP = (unsigned char )OSTsakStackBotton[OSNextTaskID+1];
MOV A,#LOW (OSTsakStackBotton+01H)
ADD A,OSNextTaskID
MOV R1,A
MOV A,@R1
MOV SP,A
; for(i = OSNextTaskID+1;i < OSTaskID+1; i++)
; {
; OSTsakStackBotton += temp;
; }
MOV A,OSTaskID
CLR C
SUBB A,OSNextTaskID
JZ ?C0011
MOV R6,A
MOV A,#LOW (OSTsakStackBotton)
ADD A,OSNextTaskID
MOV R1,A
?C0010:
INC R1
MOV A,R7
ADD A,@R1
MOV @R1,A
DJNZ R6,?C0010
?C0011:
; OSTaskID = OSNextTaskID;
MOV OSTaskID,OSNextTaskID
; SP--;
DEC SP
; }
?C0007:
; LoadCtx();
LJMP LoadCtx
?C000r:
IF EN_SP2 <> 0
mov SP,r2
ENDIF
LJMP LoadCtx
这个过程理解了很长时间,现在终于有点眉目,过程大致是这样的
首先呢,比如现在是ID为0的任务正在运行,ID为0的任务堆栈空间是从STACK定义的地址开始的
我这边运行结果来看是0x18地址开始(在OSStart()函数分配的时候决定的)
而其他任务的堆栈空间分配时由ID从大到小,从0XFF往下分配的
我们分配任务堆栈空间完毕之后不是将SP指向了ID0的地址处么。那么这个时候,SP其实就是跟着
任务堆栈空间在走的。所以当下个任务大于当前任务的时候(我们第一次运行,肯定满足这个条件)
那么就会利用*cp1++ = *cp2++将下个任务的堆栈往下移动到当前任务堆栈结束的地方,也就是当前
SP指向的地方。SP我是这么理解的,沿途记录来的地方,换个车站就记录上一个车站的地址
也就是调用一个函数,那么就记录上一个函数的地点,执行完这个函数就返回,当然还记录一些入栈
的量。当函数需要返回的时候呢,就依次弹出相应的值,以便回到来的地方。
说了这么多,回到正题,将SP指向下个任务堆栈的顶部,其实在这里也就是存放了下个任务存放在CODE
中的位置,那么当函数返回的时候,SP会将这个值弹出给PC,就直接飞到了下个任务的地方开始执行了
依次类推。
若是当前任务ID比下个任务ID大其实也是同样的道理,只不是要反方向移动堆栈空间
下面这个函数是恢复下个要执行任务的堆栈的函数了
RSEG ?PR?LoadCtx?OS_CPU_A
LoadCtx:
USING 0
POP Os_Enter_Sum ;恢复关中断计数器
;判断是否需要恢复所有寄存器
MOV A,OSTaskID
CJNE A,#OS_MAX_TASKS,LoadCtx_0
SJMP LoadCtx_2
LoadCtx_0:
MOV DPTR,#OSMapTbl
#if OS_MAX_TASKS < 9
MOVC A,@A+DPTR
ANL A,OSFastSwap
#else
MOV R6,OSFastSwap
CLR C
SUBB A,#8
JNC LoadCtx_1
MOV R6,OSFastSwap + 1
MOV A,OSTaskID
LoadCtx_1:
MOVC A,@A+DPTR
ANL A,R6
#endif
JNZ LoadCtx_2
;恢复寄存器
POP 7
POP 6
POP 5
POP 4
POP 3
POP 2
POP 1
POP 0
POP PSW
POP DPL
POP DPH
POP B
POP ACC
LoadCtx_2:
;判断是否需要开中断
INC Os_Enter_Sum
djnz Os_Enter_Sum,LoadCtx_3
SET_EA ;开中断
LoadCtx_3:
RET
以上就是其实就是首先将C_OSCtxSw()入栈的Os_Enter_Sum弹出
然后就是判断当前执行时中断所为还是任务所为,我们先来分析任务所为
因为现在确实任务所为,任务所为我们就不需要弹出其他东西了,因为调用
函数的时候,寄存器会帮忙保存一下量,所以我们返回值,相应的值会自动填
到相应的寄存器里面去。中断就暂不做讨论了,等下再来看
我们定时器一直在跳动着,假如时间到了我们任务0延时结束的时间了
前面说到了 OSTickISR(void) interrupt OS_TIME_ISR调用OSTimeTick()会将
OSWaitTick的值减到0,
void OSTimeTick(void)
{
uint8 i;
for (i = 0; i < OS_MAX_TASKS; i++)
{
if (OSWaitTick != 0 )
{
OSWaitTick--;
if (OSWaitTick == 0)
{
OSIntSendSignal(i);
}
}
}
}
到0过后,就要调用OSIntSendSignal()函数,再看看具体实现过程
void OSIntSendSignal(uint8 TaskId)
{
if (TaskId < OS_MAX_TASKS) ;判断任务ID是否有效
{
OS_ENTER_CRITICAL();
#if OS_MAX_TASKS < 9
OSTaskRuning |= OSMapTbl[TaskId]; 任务有原来的挂起进入就绪状态
#else
if (TaskId < 8)
{
((uint8 *)(&OSTaskRuning))[LOW_BYTE] |= OSMapTbl[TaskId];
}
else
{
((uint8 *)(&OSTaskRuning))[HIGH_BYTE] |= OSMapTbl[TaskId & 0x07];
}
#endif
OS_EXIT_CRITICAL();
}
}
这个函数实现的功能就是将原来挂起的任务就绪,然后调用OS_EXIT_CRITICAL函数
void OSIntExit(void)
{
uint8 temp;
OS_ENTER_CRITICAL();
/* 中断嵌套处理 */
#if EN_OS_INT_ENTER > 0
if (OSIntNesting > 0)
{
OSIntNesting--;
}
if (OSIntNesting == 0)
{
#endif
Os_Enter_Sum = 0;/* 因为在中断中,所以关中断计数器为0 */
#if OS_MAX_TASKS < 9
/* 查找处于就绪状态的任务中优先级最高的任务 */
temp = OSTaskRuning;
for (OSNextTaskID = 0; OSNextTaskID < OS_MAX_TASKS; OSNextTaskID++)
{
if ((temp & 0x01) != 0)
{
break;
}
temp = temp >> 1;
}
OSIntCtxSw(); /* 进行任务调度 */
#else
/* 查找处于就绪状态的任务中优先级最高的任务 */
temp = OSTaskRuning % 256;
for (OSNextTaskID = 0; OSNextTaskID < 8; OSNextTaskID++)
{
if ((temp & 0x01) != 0)
{
goto TaskSw;
}
temp = temp >> 1;
}
temp = OSTaskRuning / 256;
for (; OSNextTaskID < OS_MAX_TASKS; OSNextTaskID++)
{
if ((temp & 0x01) != 0)
{
break;
}
temp = temp >> 1;
}
TaskSw:
OSIntCtxSw(); /* 进行任务调度 */
#endif
#if EN_OS_INT_ENTER >0
}
#endif
OS_EXIT_CRITICAL();
}
该函数就是要查找出处于就绪状态的优先级最好的任务,也就是OSNextTaskID作为参数
传递给OSIntCtxSW()函数,该函数定义在OS_CPU_A.ASM中
RSEG ?PR?OSIntCtxSw?OS_CPU_A
OSIntCtxSw:
USING 0
;是否是优先级最低任务
MOV A,#OS_MAX_TASKS
XRL A,OSTaskID
JNZ OSIntCtxSw_0
;是则不需要保存所有寄存器
;SP=SP-13-4 ;4:两层函数调用堆栈,13:寄存器数目
MOV A,#(-17)
ADD A,SP
MOV SP,A
;跳转到OSCtxSw,同时通知CPU中断处理完成
MOV A, #LOW OSCtxSw
PUSH ACC
MOV A, #HIGH OSCtxSw
PUSH ACC
RETI
;需要保存所有寄存器
OSIntCtxSw_0:
;SP=SP-4 ;4:两层函数调用堆栈
MOV A,#0FCH
ADD A,SP
MOV SP,A
;设置标志:任务再次恢复运行时需要恢复所有寄存器
MOV DPTR,#OSMapTbl
MOV A,OSTaskID
#if OS_MAX_TASKS < 9
MOVC A,@A+DPTR
CPL A
ANL A,OSFastSwap
MOV OSFastSwap,A
#else
CLR C
SUBB A,#8
JC OSIntCtxSw_1
MOVC A,@A+DPTR
CPL A
ANL A,OSFastSwap
MOV OSFastSwap,A
SJMP OSIntCtxSw_2
OSIntCtxSw_1:
MOV A,OSTaskID
MOVC A,@A+DPTR
CPL A
ANL A,OSFastSwap+1
MOV OSFastSwap+1,A
OSIntCtxSw_2:
#endif
;跳转到堆栈处理,同时通知CPU中断处理完成
MOV A, #LOW C_OSCtxSw
PUSH ACC
MOV A, #HIGH C_OSCtxSw
PUSH ACC
RETI
中断调用的时候,寄存器会自动入栈,所以该函数就是要判断出该任务需要恢复寄存器
最后调用C_OSCtxSw()恢复任务寄存器就OK了,大致分下来明白了该OS运行的原理,在继续
分析一段时间,写一篇总结性的文字。
文章评论(0条评论)
登录后参与讨论