源码请在https://github.com/ifreecoding/MbedRtos.git下载
下面来看一下MDS_TaskDelay函数的代码:
00196 U32 MDS_TaskDelay(U32 uiDelayTick)
00197 {
00198 M_CHAIN* pstrChain;
00199 M_CHAIN* pstrNode;
00200 M_CHAIN* pstrDelayNode;
00201 M_TCBQUE* pstrTaskQue;
00202 M_PRIOFLAG* pstrPrioFlag;
00203 U8 ucTaskPrio;
00204
00205
00206 if(gpstrCurTcb == gpstrIdleTaskTcb)
00207 {
00208 return RTN_FAIL;
00209 }
00210
00211
00212 if(DELAYNOWAIT != uiDelayTick)
00213 {
00214
00215 ucTaskPrio = gpstrCurTcb->ucTaskPrio;
00216 pstrChain = &gstrReadyTab.astrChain[ucTaskPrio];
00217 pstrPrioFlag = &gstrReadyTab.strFlag;
00218
00219 (void)MDS_IntLock();
00220
00221
00222 pstrNode = MDS_TaskDelFromSchedTab(pstrChain, pstrPrioFlag, ucTaskPrio);
00223
00224
00225 gpstrCurTcb->strTaskOpt.ucTaskSta &= ~((U8)TASKREADY);
00226
00227
00228 gpstrCurTcb->strTaskOpt.uiDelayTick = uiDelayTick;
00229
00230
00231 if(DELAYWAITFEV != uiDelayTick)
00232 {
00233 gpstrCurTcb->uiStillTick = guiTick + uiDelayTick;
00234
00235
00236 pstrTaskQue = (M_TCBQUE*)pstrNode;
00237 pstrDelayNode = &pstrTaskQue->pstrTcb->strDelayQue.strQueHead;
00238
00239
00240 MDS_TaskAddToDelayTab(pstrDelayNode);
00241
00242
00243 gpstrCurTcb->uiTaskFlag |= DELAYQUEFLAG;
00244 }
00245
00246
00247 gpstrCurTcb->strTaskOpt.ucTaskSta |= TASKDELAY;
00248
00249 (void)MDS_IntUnlock();
00250 }
00251 else
00252 {
00253
00254 gpstrCurTcb->strTaskOpt.uiDelayTick = RTN_SUCD;
00255 }
00256
00257
00258 MDS_TaskSwiSched();
00259
00260
00261 return gpstrCurTcb->strTaskOpt.uiDelayTick;
00262 }
00196行,函数返回值分4种,RTN_SUCD: 任务没有delay,仅发生任务切换,仅在入口参数为0时才会返回该值。RTN_FAIL: 任务delay失败,没有进入delay状态。RTN_TKDLTO: 任务已经进入过delay状态,并且又从delay状态返回到running状态,delay的时间已耗尽, 超时返回。RTN_TKDLBK: 任务delay状态被打断,被其它任务使用MDS_TaskWake函数唤醒。
入口参数uiDelayTick是需要delay的tick数。
00206~00209行,idle任务不能延迟。
00212行,对任务delay的时间进行判断,不是delay 0 tick的情况走下面分支。
00222行,当前任务需要切换到delay状态,从ready表拆除。
00225行,清除任务的ready状态。
00227行,更新当前任务的延迟时间。
00231行,对任务delay的时间进行判断,不是永久delay的情况走下面分支。
00233行,计算该任务需要delay到的tick数值,存入TCB中。
00236行,将当前任务的M_TCBQUE型ready节点指针强制转换为M_TCBQUE型指针。
00237行,从任务的ready节点找到delay节点指针。
00240行,将该任务加入delay表。
00243行,在该任务的TCB中的任务标志uiTaskFlag中设置标志,表明该任务已经处于delay表中。
00247行,增加任务的delay状态。
00251~00255行,delay 0 tick时走此分支,任务只切换不延迟,将返回值保存在strTaskOpt中的uiDelayTick变量中。
00258行,任务相关变量、ready表、delay表的操作已经完成,调用MDS_TaskSwiSched函数触发软中断,开始任务调度。
00263行,将strTaskOpt中的uiDelayTick变量作为返回值返回给上级父函数。uiDelayTick变量中的返回值会在248行仅发生任务切换时存入,或者在任务调度函数MDS_TaskDelayTabSched中delay时间耗尽时存入,或者在MDS_TaskWake函数中由其它任务唤醒时存入。
261行与258行在代码编写上是连在一起的,但在执行时可能会有时间间隔,258行会发生任务调度,中间可能会插入其它任务的执行过程。
MDS_TaskWake函数正好与MDS_TaskDelay函数相反,它将处于delay状态的任务从delay表拆除,添加到ready表,并修改任务的一些相关状态变量,代码如下,不再详细介绍。
00270 U32 MDS_TaskWake(M_TCB* pstrTcb)
00271 {
00272 M_CHAIN* pstrChain;
00273 M_CHAIN* pstrNode;
00274 M_PRIOFLAG* pstrPrioFlag;
00275 U8 ucTaskPrio;
00276
00277
00278 if(NULL == pstrTcb)
00279 {
00280 return RTN_FAIL;
00281 }
00282
00283 (void)MDS_IntLock();
00284
00285
00286 if(TASKDELAY != (TASKDELAY & pstrTcb->strTaskOpt.ucTaskSta))
00287 {
00288 (void)MDS_IntUnlock();
00289
00290 return RTN_FAIL;
00291 }
00292
00293 pstrNode = &pstrTcb->strDelayQue.strQueHead;
00294
00295
00296 if(DELAYWAITFEV != pstrTcb->strTaskOpt.uiDelayTick)
00297 {
00298
00299 (void)MDS_ChainCurNodeDelete(&gstrDelayTab, pstrNode);
00300
00301
00302 pstrTcb->uiTaskFlag &= (~((U32)DELAYQUEFLAG));
00303 }
00304
00305
00306 pstrTcb->strTaskOpt.ucTaskSta &= ~((U8)TASKDELAY);
00307
00308
00309 pstrTcb->strTaskOpt.uiDelayTick = RTN_TKDLBK;
00310
00311
00312 ucTaskPrio = pstrTcb->ucTaskPrio;
00313 pstrChain = &gstrReadyTab.astrChain[ucTaskPrio];
00314 pstrPrioFlag = &gstrReadyTab.strFlag;
00315
00316
00317 MDS_TaskAddToSchedTab(pstrChain, pstrNode, pstrPrioFlag, ucTaskPrio);
00318
00319
00320 pstrTcb->strTaskOpt.ucTaskSta |= TASKREADY;
00321
00322 (void)MDS_IntUnlock();
00323
00324
00325 MDS_TaskSwiSched();
00326
00327 return RTN_SUCD;
00328 }
现在我们已经拥有了ready和delay两种任务状态,那么在tick中断里就需要对这两个调度表进行操作。这个过程分为2步,先对delay表操作,将delay时间耗尽的任务从delay表拆除,挂入ready表,使之能参与到ready表的调度之中,然后再对ready表进行调度。
来看调度函数的代码:
00345 void MDS_TaskSched(void)
00346 {
00347 M_TCB* pstrTcb;
00348
00349
00350 MDS_TaskDelayTabSched();
00351
00352
00353 pstrTcb = MDS_TaskReadyTabSched();
00354
00355
00356 MDS_TaskSwitch(pstrTcb);
00357 }
有关ready表调度的代码被封装到了353行的MDS_TaskReadyTabSched函数里面,本节新增的delay表调度代码被封装到350行的MDS_TaskDelayTabSched函数里面,下面来看看MDS_TaskDelayTabSched函数的代码:
00399 void MDS_TaskDelayTabSched(void)
00400 {
00401 M_TCB* pstrTcb;
00402 M_CHAIN* pstrChain;
00403 M_CHAIN* pstrNode;
00404 M_CHAIN* pstrDelayNode;
00405 M_CHAIN* pstrNextNode;
00406 M_PRIOFLAG* pstrPrioFlag;
00407 M_TCBQUE* pstrDelayQue;
00408 U32 uiTick;
00409 U8 ucTaskPrio;
00410
00411
00412 pstrDelayNode = MDS_ChainEmpInq(&gstrDelayTab);
00413
00414
00415 if(NULL != pstrDelayNode)
00416 {
00417
00418 while(1)
00419 {
00420
00421 pstrDelayQue = (M_TCBQUE*)pstrDelayNode;
00422 pstrTcb = pstrDelayQue->pstrTcb;
00423 uiTick = pstrTcb->uiStillTick;
00424
00425
00426 if(uiTick == guiTick)
00427 {
00428
00429 pstrNextNode = MDS_ChainCurNodeDelete(&gstrDelayTab, pstrDelayNode);
00430
00431
00432 pstrTcb->uiTaskFlag &= (~((U32)DELAYQUEFLAG));
00433
00434
00435 pstrTcb->strTaskOpt.ucTaskSta &= ~((U8)TASKDELAY);
00436
00437
00438 pstrTcb->strTaskOpt.uiDelayTick = RTN_TKDLTO;
00439
00440
00441 pstrNode = &pstrTcb->strTcbQue.strQueHead;
00442 ucTaskPrio = pstrTcb->ucTaskPrio;
00443 pstrChain = &gstrReadyTab.astrChain[ucTaskPrio];
00444 pstrPrioFlag = &gstrReadyTab.strFlag;
00445
00446
00447 MDS_TaskAddToSchedTab(pstrChain, pstrNode, pstrPrioFlag,
00448 ucTaskPrio);
00449
00450
00451 pstrTcb->strTaskOpt.ucTaskSta |= TASKREADY;
00452
00453
00454 if(NULL == pstrNextNode)
00455 {
00456 break;
00457 }
00458 else
00459 {
00460 pstrDelayNode = pstrNextNode;
00461 }
00462 }
00463 else
00464 {
00465 break;
00466 }
00467 }
00468 }
00469 }
00412行,从delay表中获取第一个子节点pstrDelayNode。
00415行,判断第一个子节点是否为空。
00418行,循环查询delay表中的任务。执行到此行说明delay链表不为空,说明有处于delay状态的任务。
000421行,将M_CHAIN型的指针pstrDelayNode强制转换为M_TCBQUE型的指针pstrDelayQue。
00422行,通过pstrDelayQue获取TCB指针。
00423行,通过TCB指针获取到任务delay状态耗尽时的tick数值。
00426行,被查询的任务delay状态在当前tick已经耗尽,需要从delay状态转换为ready状态,走下面分支。
00429行,将该任务从delay表中拆除。
00432行,从TCB中的任务标志uiTaskFlag中去除任务处于delay表的标志。
00435行,去除任务的delay状态。
00438行,将delay耗尽的返回值存入strTaskOpt中的uiDelayTick变量,供MDS_TaskDelay函数返回时使用。
00441~00451行,将该任务添加到ready表中。
00454~00461行,若下个节点为空,说明delay表已经遍历完毕,退出调度函数。若下个节点不为空则继续查询。
00463~00466行,走到此分支说明当前节点delay时间还没有耗尽,因为delay表是按照delay耗尽时间从少到多的顺序排列的,因此后面节点的时间也不会耗尽,不需要再遍历,直接返回。
下面介绍一下与软中断相关的函数。
Mindows中使用MDS_TaskSwiSched函数触发软中断,MDS_TaskSwiSched函数里面使用MDS_RunInInt函数对芯片当前的状态做了一个判断,避免在中断中触发软中断造成异常,实际触发软中断的函数是MDS_TaskOccurSwi函数。
00137 void MDS_TaskSwiSched(void)
00138 {
00139
00140 if(RTN_SUCD == MDS_RunInInt())
00141 {
00142 return;
00143 }
00144
00145
00146 MDS_TaskOccurSwi(SWI_TASKSCHED);
00147 }
MDS_RunInInt调用了汇编函数MDS_GetChipMode获取芯片的工作模式,若不是USR模式则表明当前是在中断中运行。
00155 U32 MDS_RunInInt(void)
00156 {
00157
00158 if(MODE_USR != MDS_GetChipMode())
00159 {
00160 return RTN_SUCD;
00161 }
00162 else
00163 {
00164 return RTN_FAIL;
00165 }
00166 }
MDS_GetChipMode函数是一个汇编函数,CPSR寄存器中的bit4~bit0中保存的就是当前模式,它将CPSR寄存器的bit4~bit0作为函数返回值保存到R0中。
00114 .func MDS_GetChipMode
00115 MDS_GetChipMode:
00116
00117 MRS R0, CPSR
00118 AND R0, R0, #0x1F
00119 BX R14
00120
00121 .endfunc
MDS_TaskOccurSwi函数也是一个汇编函数,它有一个入口参数,用来表明该软中断的功能,C语言的函数原型为
MDS_TaskOccurSwi(U32 uiSwiNo);
汇编语言的源代码为:
00127 .func MDS_TaskOccurSwi
00128 MDS_TaskOccurSwi:
00129
00130 SWI 0
00131 BX R14
00132
00133 .endfunc
00130行,执行“SWI”汇编指令,该指令会触发软中断,将PC指针跳转到软中断向量表。在startup.s文件的软中断向量表里面使用MDS_SwiContextSwitch函数作为软中断的服务函数,它的功能与tick中断的MDS_TickContextSwitch函数功能非常相似,都是用来调度任务的,但还是有一些不同的地方。
00054 .func MDS_SwiContextSwitch
00055 MDS_SwiContextSwitch:
00056
00057 @保存接口寄存器
00058 ADD R14, R14, #4
00059 STMDB R13!, {R0 - R3, R12, R14}
00060
00061 @调用C语言SWI中断处理函数
00062 LDR R3, =MDS_SwiIsr
00063 MOV R14, PC
00064 BX R3
00065
00066 @保存当前任务的堆栈信息
00067 LDR R0, =gpstrCurTaskSpAddr
00068 LDR R14, [R0]
00069 MRS R0, SPSR
00070 STMIA R14!, {R0}
00071 LDMIA R13!, {R0 - R3, R12}
00072 STMIA R14, {R0 - R14}^
00073 ADD R14, R14, #0x3C
00074 LDMIA R13!, {R0}
00075 STMIA R14, {R0}
00076
00077 @任务调度完毕, 恢复将要运行任务现场
00078 LDR R0, =gpstrNextTaskSpAddr
00079 LDR R14, [R0]
00080 LDMIA R14, {R0}
00081 MSR SPSR, R0
00082 LDMIB R14, {R0 - R14}^
00083 NOP
00084 ADD R14, R14, #0x40
00085 LDMIA R14, {R14}
00086 SUB R14, R14, #4
00087 MOVS PC, R14
00088
00089 .endfunc
00058行,将返回的LR寄存器地址多加4字节,这是因为在进入MDS_TickContextSwitch函数时硬件会自动多加4字节,为了使MDS_SwiContextSwitch函数与MDS_TickContextSwitch函数的栈格式相同,这里先多加4字节,在MDS_SwiContextSwitch函数返回前会再多减去4字节。
00062~00064行,调用软中断处理函数MDS_SwiIsr。注意,这里不能使用R0寄存器,因为此时R0中保存的是MDS_TaskOccurSwi函数传递进来的入口参数。
00067~00085行,寄存器组备份还原过程,与MDS_TickContextSwitch函数完全一致。
00086行,函数返回前减去前面多加的4字节。
00087行,软中断调度完成,使用MOVS指令返回,这点与tick中断所使用的ISR中断不同。
MDS_SwiIsr函数才是真正的软中断处理过程,使用C语言编写,所有的软中断处理过程都在这个函数内实现。
00094 void MDS_SwiIsr(U32 uiSwiNo)
00095 {
00096
00097 if(SWI_TASKSCHED == (SWI_TASKSCHED & uiSwiNo))
00098 {
00099
00100 MDS_TaskReadySched();
00101 }
00102 else
00103 {
00104
00105 MDS_TaskSwitch(gpstrCurTcb);
00106
00107 switch(uiSwiNo)
00108 {
00109 case SWI_INTENABLE:
00110
00111 MDS_IntEnable();
00112
00113 break;
00114
00115 case SWI_INTDISABLE:
00116
00117 MDS_IntDisable();
00118
00119 break;
00120
00121
00122
00123 default:
00124
00125 break;
00126
00127
00128 }
00129 }
00130 }
00094行,入口参数uiSwiNo从MDS_TaskOccurSwi函数开始,被保存在R0中,经历了MDS_SwiContextSwitch函数传递给MDS_SwiIsr函数。
00097行,软中断调度的中断走此分支。
00100行,在软中断中调度任务,只需要调度ready表。delay表的调度是以tick为周期的,在tick中断中才能调度。
00102行,非软中断调度产生的软中断走此分支。
00105行,更新调度中所使用的全局变量。尽管是非软中断调度,但由于该函数的上级函数MDS_SwiContextSwitch会对寄存器组进行备份、恢复,因此此处也需要更新一下调度中所使用的全局变量。
00107行,选择不同的软中断命令分支。
00109~00113行,解锁中断命令,调用MDS_IntEnable函数解锁中断。MDS_IntEnable函数是汇编函数,直接修改CPSR中的全局中断控制位允许中断产生。
00115~00119行,锁中断命令,调用MDS_IntDisable函数锁中断。MDS_IntDisable函数是汇编函数,直接修改CPSR中的全局中断控制位禁止中断产生。
00121~00127行,用户可以将自定义的软中断命令放在此处执行。
MDS_IntEnable和MDS_IntDisable函数直接操作的对象是SVC模式下SPSRSVC寄存器,当程序从SVC模式返回到USR模式时,SPSRSVC寄存器就会被恢复到CPSR中,达到修改CPSR寄存器控制中断产生的目的。
00139 .func MDS_IntEnable
00140 MDS_IntEnable:
00141
00142 MRS R0, SPSR
00143 LDR R1, =(CPSR_IRQENABLE & CPSR_FIQENABLE)
00144 AND R0, R0, R1
00145 MSR SPSR, R0
00146 BX R14
00147
00148 .endfunc
00154 .func MDS_IntDisable
00155 MDS_IntDisable:
00156
00157 MRS R0, SPSR
00158 LDR R1, =(CPSR_IRQDISABLE | CPSR_FIQDISABLE)
00159 ORR R0, R0, R1
00160 MSR SPSR, R0
00161 BX R14
00162
00163 .endfunc
提供给用户使用的锁中断函数是MDS_IntLock,解锁中断函数是MDS_IntUnlock,它们同软中断调度函数一样,都是通过调用MDS_TaskOccurSwi函数触发软中断,通过入口参数区分功能,在软中断服务函数MDS_SwiIsr里实现自己的功能。
00650 U32 MDS_IntLock(void)
00651 {
00652
00653 if(NULL == gpstrCurTcb)
00654 {
00655 return RTN_SUCD;
00656 }
00657
00658
00659 if(RTN_SUCD == MDS_RunInInt())
00660 {
00661 return RTN_SUCD;
00662 }
00663
00664
00665 if(0 == guiIntLockCounter)
00666 {
00667 MDS_TaskOccurSwi(SWI_INTDISABLE);
00668
00669 guiIntLockCounter++;
00670
00671 return RTN_SUCD;
00672 }
00673
00674 else if(guiIntLockCounter < 0xFFFFFFFF)
00675 {
00676 guiIntLockCounter++;
00677
00678 return RTN_SUCD;
00679 }
00680 else
00681 {
00682 return RTN_FAIL;
00683 }
00684 }
00653~00656行,在还没有进入操作系统状态前不能执行此函数。
00659~00662行,在中断中不能执行此函数。
00665~00672行,guiIntLockCounter变量里存放的是锁中断、解锁中断的次数,初始化为0,当guiIntLockCounter为0时,说明是第一次执行该函数,只有第一次执行该函数时才操作硬件寄存器,执行锁中断动作。
需要注意一点,669行guiIntLockCounter变量自加需要在667行MDS_TaskOccurSwi函数之后执行,因为MDS_IntLock函数可能会重入,这样做在函数重入时,不会使guiIntLockCounter变量计数产生错误。
00674~00679行,guiIntLockCounter变量没有溢出则做自加操作,返回锁中断成功,是一个虚假的锁中断过程。
00680~00683行,guiIntLockCounter变量溢出,返回成功。
00693 U32 MDS_IntUnlock(void)
00694 {
00695
00696 if(NULL == gpstrCurTcb)
00697 {
00698 return RTN_SUCD;
00699 }
00700
00701
00702 if(RTN_SUCD == MDS_RunInInt())
00703 {
00704 return RTN_SUCD;
00705 }
00706
00707
00708 if(guiIntLockCounter > 1)
00709 {
00710 guiIntLockCounter--;
00711
00712 return RTN_SUCD;
00713 }
00714
00715 else if(1 == guiIntLockCounter)
00716 {
00717 guiIntLockCounter--;
00718
00719 MDS_TaskOccurSwi(SWI_INTENABLE);
00720
00721 return RTN_SUCD;
00722 }
00723 else
00724 {
00725 return RTN_FAIL;
00726 }
00727 }
MDS_IntUnlock函数与MDS_IntLock函数,非常相似,只有当guiIntLockCounter变量为1时才操作硬件寄存器,执行解锁中断动作。
本节操作系统部分的内容就介绍到这里。