第4章第7节 二进制信号量(二)

目前更新到5.3节,请在http://dl.dbank.com/c02ackpwp6下载5.3节的全部文档

本节源代码请在http://dl.dbank.com/c0fp2g5z9s下载

 

释放信号量的函数MDS_SemGive的代码如下:

00143  U32 MDS_SemGive(M_SEM* pstrSem)

00144  {

00145      M_TCB* pstrTcb;

00146      M_CHAIN* pstrChain;

00147      M_CHAIN* pstrNode;

00148      M_PRIOFLAG* pstrPrioFlag;

00149      U8 ucTaskPrio;

00150  

00151      

00152      if(NULL == pstrSem)

00153      {

00154          return RTN_FAIL;

00155      }

00156  

00157      (void)MDS_IntLock();

00158  

00159      

00160      if(SEMEMPTY == pstrSem->uiCounter)

00161      {

00162          

00163          pstrTcb MDS_SemGetActiveTask(pstrSem);

00164  

00165          

00166          if(NULL != pstrTcb)

00167          {

00168              

00169              (void)MDS_TaskDelFromSemTab(pstrTcb);

00170  

00171              

00172              if(DELAYQUEFLAG == (pstrTcb->uiTaskFlag DELAYQUEFLAG))

00173              {

00174                  pstrNode &pstrTcb->strDelayQue.strQueHead;

00175                  (void)MDS_ChainCurNodeDelete(&gstrDelayTab, pstrNode);

00176  

00177                  

00178                  pstrTcb->uiTaskFlag &= (~((U32)DELAYQUEFLAG));

00179              }

00180  

00181              

00182              pstrTcb->strTaskOpt.ucTaskSta &= ~((U8)TASKPEND);

00183  

00184              

00185              pstrTcb->strTaskOpt.uiDelayTick RTN_SUCD;

00186  

00187              

00188              pstrNode &pstrTcb->strTcbQue.strQueHead;

00189              ucTaskPrio pstrTcb->ucTaskPrio;

00190              pstrChain &gstrReadyTab.astrChain[ucTaskPrio];

00191              pstrPrioFlag &gstrReadyTab.strFlag;

00192  

00193              MDS_TaskAddToSchedTab(pstrChain, pstrNode, pstrPrioFlag, ucTaskPrio);

00194  

00195              

00196              pstrTcb->strTaskOpt.ucTaskSta |= TASKREADY;

00197  

00198              (void)MDS_IntUnlock();

00199  

00200              

00201              MDS_TaskSwiSched();

00202  

00203              return RTN_SUCD;

00204          }

00205          else 

00206          {

00207              pstrSem->uiCounter SEMFULL;

00208          }

00209      }

00210  

00211      (void)MDS_IntUnlock();

00212  

00213      return RTN_SUCD;

00214  }

00143行,函数返回值RTN_SUCD代表释放信号量成功,RTN_FAIL代表释放信号量失败。入口参数pstrSem为释放的信号量的指针。

00152~00155行,对入口参数pstrSem进行检查,若为空则返回失败。

00157行,锁中断,防止多个任务同时操作信号量。

00160行,若信号量处于空状态走此分支。

00163行,从信号量pstrSem的调度表里面被阻塞的任务中查找需要被释放的任务,返回该任务的TCB

00166行,若该任务的TCB不为空,说明有被阻塞的任务,走此分支。

00169行,将这个任务从信号量调度表中删除。

00172行,如果这个任务处于delay表中,走此分支。

00174行,获取这个任务TCB中挂接到delay表的节点。

00175行,将该任务从delay表中删除。

00178行,将该任务的标志修改为不在delay表中。

00182行,清除该任务的pend状态。

00185行,将该任务的返回值保存在TCBstrTaskOpt.uiDelayTick中,当该任务重新运行时就会得到RTN_SUCD这个返回值。注意,该任务是当前运行任务使用信号量释放出来的任务,而不是当前正在运行的任务。

00188~00196行,将该任务添加到ready表中,并将任务的状态修改为ready状态。

00198行,对信号量操作完毕,解锁中断。

00201行,已经有任务新加入到ready表中,使用软中断函数调度任务。

00205行,信号量中没有被阻塞的任务,走此分支。

00207行,走到此分支,说明信号量是在空的状态下被释放,并且没有被阻塞的任务需要获取信号量,因此直接将信号量置为满状态。

00211行,对信号量操作完毕,解锁中断。

00213行,对信号量操作完毕,返回释放信号量成功。

 

如果有任务被信号量阻塞,在中断中使用MDS_SemGive函数释放信号量时并不会立刻发生任务调度,因为MDS_SemGive函数所使用的软中断调度函数MDS_TaskSwiSched无法在中断中执行,任务调度需要等到下个tick中断到来时才能执行,因此在中断中释放信号量激活的最高优先级任务可能会有一个小的延迟后才能运行。在后面章节我们会将Mindows移植到cortex内核的芯片上,cortex内核拥有pendsv软中断,使用pendsv软中断可以解决这个问题,不会有延迟产生。

 

MDS_SemGetActiveTask函数的功能是从信号量的调度表中获取需要被最先释放的任务,如果信号量采用的是优先级调度方式,则使用MDS_TaskHighestPrioGet函数从信号量调度表中找出最高优先级任务的TCB,如果信号量采用的是先进先出的调度方式,则从信号量调度表中取出最先入链表的头节点。函数比较简单,不再详细介绍,代码如下:

00424  M_TCB* MDS_SemGetActiveTask(M_SEM* pstrSem)

00425  {

00426      M_CHAIN* pstrNode;

00427      M_TCBQUE* pstrTaskQue;

00428      U8 ucTaskPrio;

00429  

00430      

00431      if(SEMPRIO == (SEMSCHEDOPTMASK pstrSem->uiSemOpt))

00432      {

00433          

00434          ucTaskPrio MDS_TaskHighestPrioGet(&pstrSem->strSemTab.strFlag);

00435      }

00436      else 

00437      {

00438          

00439          ucTaskPrio LOWESTPRIO;

00440      }

00441  

00442      pstrNode MDS_ChainEmpInq(&pstrSem->strSemTab.astrChain[ucTaskPrio]);

00443  

00444      

00445      if(NULL == pstrNode)

00446      {

00447          return (M_TCB*)NULL;

00448      }

00449      else

00450      {

00451          pstrTaskQue (M_TCBQUE*)pstrNode;

00452  

00453          return pstrTaskQue->pstrTcb;

00454      }

00455  }

 

MDS_TaskDelFromSemTab函数的功能是将任务从信号量调度表中删除,如果信号量采用的是优先级调度方式,则从任务TCB中找出任务的相关属性,使用MDS_TaskDelFromSchedTab函数将任务从信号量调度表中删除,这个过程与从ready表中删除任务的过程是一样的。如果信号量采用的是先进先出的调度方式,则从信号量调度表中删除最先入链表的头节点。函数比较简单,不再详细介绍,代码如下:

00388  M_CHAIN* MDS_TaskDelFromSemTab(M_TCB* pstrTcb)

00389  {

00390      M_SEM* pstrSem;

00391      M_CHAIN* pstrChain;

00392      M_PRIOFLAG* pstrPrioFlag;

00393      U8 ucTaskPrio;

00394  

00395      

00396      pstrSem pstrTcb->pstrSem;

00397  

00398      

00399      if(SEMPRIO == (SEMSCHEDOPTMASK pstrSem->uiSemOpt))

00400      {

00401          

00402          ucTaskPrio pstrTcb->ucTaskPrio;

00403          pstrChain &pstrSem->strSemTab.astrChain[ucTaskPrio];

00404          pstrPrioFlag &pstrSem->strSemTab.strFlag;

00405  

00406          

00407          return MDS_TaskDelFromSchedTab(pstrChain, pstrPrioFlag, ucTaskPrio);

00408      }

00409      else 

00410      {

00411          

00412          pstrChain &pstrSem->strSemTab.astrChain[LOWESTPRIO];

00413  

00414          

00415          return MDS_ChainNodeDelete(pstrChain);

00416      }

00417  }

 

MDS_SemGive函数一次只能释放一个被阻塞的任务,MDS_SemFlush可以一次性释放被信号量阻塞的所有任务。MDS_SemFlush函数的原理与MDS_SemGive函数是一样的,MDS_SemFlush函数会循环查找信号量调度表中的所有任务,将他们全部释放掉,不再做详细介绍,代码如下,请读者自行分析:

00301  U32 MDS_SemFlush(M_SEM* pstrSem)

00302  {

00303      

00304      return MDS_SemFlushValue(pstrSem, RTN_SUCD);

00305  }

00224  U32 MDS_SemFlushValue(M_SEM* pstrSem, U32 uiRtnValue)

00225  {

00226      M_TCB* pstrTcb;

00227      M_CHAIN* pstrChain;

00228      M_CHAIN* pstrNode;

00229      M_PRIOFLAG* pstrPrioFlag;

00230      U8 ucTaskPrio;

00231  

00232      

00233      if(NULL == pstrSem)

00234      {

00235          return RTN_FAIL;

00236      }

00237  

00238      (void)MDS_IntLock();

00239  

00240      

00241      while(1)

00242      {

00243          pstrTcb MDS_SemGetActiveTask(pstrSem);

00244  

00245          

00246          if(NULL != pstrTcb)

00247          {

00248              

00249              (void)MDS_TaskDelFromSemTab(pstrTcb);

00250  

00251              

00252              if(DELAYQUEFLAG == (pstrTcb->uiTaskFlag DELAYQUEFLAG))

00253              {

00254                  pstrNode &pstrTcb->strDelayQue.strQueHead;

00255                  (void)MDS_ChainCurNodeDelete(&gstrDelayTab, pstrNode);

00256  

00257                  

00258                  pstrTcb->uiTaskFlag &= (~((U32)DELAYQUEFLAG));

00259              }

00260  

00261              

00262              pstrTcb->strTaskOpt.ucTaskSta &= ~((U8)TASKPEND);

00263  

00264              

00265              pstrTcb->strTaskOpt.uiDelayTick uiRtnValue;

00266  

00267              

00268              pstrNode &pstrTcb->strTcbQue.strQueHead;

00269              ucTaskPrio pstrTcb->ucTaskPrio;

00270              pstrChain &gstrReadyTab.astrChain[ucTaskPrio];

00271              pstrPrioFlag &gstrReadyTab.strFlag;

00272  

00273              MDS_TaskAddToSchedTab(pstrChain, pstrNode, pstrPrioFlag, ucTaskPrio);

00274  

00275              

00276              pstrTcb->strTaskOpt.ucTaskSta |= TASKREADY;

00277          }

00278          else 

00279          {

00280              break;

00281          }

00282      }

00283  

00284      

00285      pstrSem->uiCounter SEMEMPTY;

00286  

00287      (void)MDS_IntUnlock();

00288  

00289      

00290      MDS_TaskSwiSched();

00291  

00292      return RTN_SUCD;

00293  }

 

当信号量不再需要使用时,可以使用MDS_SemDelete函数删除信号量,释放信号量所占用的资源。删除信号量时,被阻塞在该信号量上的所有任务全部会被激活,重新挂入ready表中参与任务调度,这个过程使用MDS_SemFlushValue函数就可以实现。MDS_SemDelete函数比较简单,代码如下,不再详细介绍。

00313  U32 MDS_SemDelete(M_SEM* pstrSem)

00314  {

00315      

00316      if(NULL == pstrSem)

00317      {

00318          return RTN_FAIL;

00319      }

00320  

00321      

00322      if(RTN_SUCD != MDS_SemFlushValue(pstrSem, RTN_SMTKDL))

00323      {

00324          return RTN_FAIL;

00325      }

00326  

00327      return RTN_SUCD;

00328  }

 

本节新增加了一个pend任务状态,那么在任务调度的时候也需要对这个状态进行处理,需要在MDS_TaskDelayTabSched函数里增加对pend状态的任务的处理,该函数改动较小,只是增加了一个对pend状态处理的分支。

00544  void MDS_TaskDelayTabSched(void)

00545  {

……                  ……

00588  

00589                  

00590                  else if(TASKPEND == (TASKPEND& pstrTcb->strTaskOpt.ucTaskSta))

00591                  {

00592                      

00593                      (void)MDS_TaskDelFromSemTab(gpstrCurTcb);

00594  

00595                      

00596                      pstrTcb->strTaskOpt.ucTaskSta &= ~((U8)TASKPEND);

00597  

00598                      

00599                      pstrTcb->strTaskOpt.uiDelayTick RTN_SMTKTO;

00600                  }

00601  

……                  ……

00631  }

00590行,走到此处说明delay表中有时间耗尽的任务,如果这个任务是pend状态,那么走此分支。

00593行,走到此处说明该任务是pend状态,并且时间耗尽,需要从信号量调度表删除。

00596行,清除任务的pend状态。

00599行,将返回值RTN_SMTKTO存入超时任务的TCB中的strTaskOpt.uiDelayTick变量中,当这个超时任务返回时,返回值RTN_SMTKTO可以说明这个任务是由于时间耗尽超时返回的。

 

任务增加了pend状态,还影响到了MDS_TaskDelete函数,在删除处于pend状态的任务时需要将这个任务从相关的信号量调度表中拆除,MDS_TaskDelete函数新增的代码如下:

00121  U32 MDS_TaskDelete(M_TCB* pstrTcb)

00122  {

……       ……

00167  

00168      

00169      if(TASKPEND == (TASKPEND ucTaskSta))

00170      {

00171          

00172          (void)MDS_TaskDelFromSemTab(pstrTcb);

00173      }

00174  

……       ……

00191  }

 

当我们需要串行访问一个需要多步骤操作的资源时,就可以使用二进制信号量对其进行保护,例如对串口、FLASH等外设操作工程的保护。在使用信号量保护资源时要避免出现信号量死锁的情况,所谓信号量死锁就是每个任务占有一个信号量,它们都因无法获取到对方已占有的信号量而无法运行,因此本身占有的信号量也无法被释放,这样,多个任务之间形成互相等待信号量的情况,所有任务都处于一种互相等待的永久等待状态,形成死锁。例如任务A已经获取到了信号量a,任务B已经获取到了信号量b,但任务A需要获取到信号量b才能继续运行,而任务B则需要获取到信号量a才能继续运行,这样2个任务就会形成死锁,这两个任务也就永远无法运行了。

void TaskA(void)

{

    已获取信号量a;

 

    (void)MDS_SemTake(&b, SEMWAITFEV);

    

    ……

}

void TaskB(void)

{

    已获取信号量b;

 

    (void)MDS_SemTake(&a, SEMWAITFEV);

    

    ……

}

 

前面我们主要介绍了二进制信号量互斥的作用,二进制信号量互斥的这个特点也可以用来触发任务,当同步事件使用。例如,当我们需要由任务B来触发任务A的运行时,先将信号量a初始化为空状态,由任务A获取信号量a,由任务B释放a。当任务B没有释放信号量a时,任务A就会因为获取不到信号量a而被阻塞,一旦任务B释放了信号量a,任务B就会被激活,开始运行。

void TaskA(void)

{

    ……

 

(void)MDS_SemTake(&a, SEMWAITFEV);

 

    ……

}

void TaskB(void)

{

    ……

 

    (void)MDS_SemGive(&a);

 

    ……

}

 

到目前为止操作系统已经有了ready表、delay表和多个信号量表,系统运行时需要不断的更新任务TCB中的结构与各种调度表之间的关系。strTcbQue结构中的strQueHead节点可以挂接到ready表或者信号量调度表,strDelayQue结构中的strQueHead节点可以挂接到delay表,pstrSem指针需要指向阻塞任务的信号量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值