第4章第4节 任务切换钩子函数


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

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

 

第4节 任务切换钩子函数

上节,我们引入了任务的delay态,通过最后例子的打印可以看到任务在交替运行,但这个打印只发生在每个任务每次循环的开始,看不到中间运行过程中任务的切换过程。

本小节将引入任务切换钩子函数,输出任务切换过程的打印信息。

 

钩子函数正如其名,它像钩子一样可以将函数挂在它上面,一旦它开始运行,它就会引发挂在它上面的函数也开始运行。按照这个思路,我们可以将一个钩子函数放到任务切换过程中,当我们需要输出任务切换过程的打印时,只需要将相关的打印函数挂到这个钩子函数上就可以了。

 

钩子函数的功能是使用一个指向函数的指针型全局变量实现的,在使用钩子函数前,需要使用钩子添加函数将需要执行的函数挂接到这个全局变量上,这个全局变量此时等效于被挂接的函数,因此运行这个全局变量就相当于是运行被挂接的函数了,来看代码:

定义一个任务切换的钩子全局变量:

VFHSWT gvfTaskSwitchHook;

其中VFHSWT是被挂接函数的类型:

typedef void    (*VFHSWT)(M_TCB*, M_TCB*);

被挂接的函数原型为:

void TEST_TaskSwitchPrint(M_TCB* pstrOldTcb, M_TCB* pstrNewTcb);

可以看到定义的全局变量gvfTaskSwitchHook与函数TEST_TaskSwitchPrint类型是完全一致的,它们都是同一种类型的函数指针。

在使用钩子函数前需要使用钩子初始化函数MDS_TaskHookInit将全局变量gvfTaskSwitchHook初始化为NULL,以表明钩子变量没有挂接函数,不能运行。

00340  void MDS_TaskHookInit(void)

00341  {

00342      

00343      gvfTaskSwitchHook (VFHSWT)NULL;

00344  }

 

添加钩子函数时,将被添加函数TEST_TaskSwitchPrint作为参数传递给钩子添加函数MDS_TaskSwitchHookAddMDS_TaskSwitchHookAdd(TEST_TaskSwitchPrint),执行的过程就是将被添加函数的指针赋给全局变量,使全局变量等效于被添加的函数。

00351  void MDS_TaskSwitchHookAdd(VFHSWT vfFuncPointer)

00352  {

00353      gvfTaskSwitchHook vfFuncPointer;

00354  }

 

删除钩子函数时,就是将全局变量置为NULL,撇清与被添加函数的关系。

00361  void MDS_TaskSwitchHookDel(void)

00362  {

00363      gvfTaskSwitchHook (VFHSWT)NULL;

00364  }

使用钩子函数时,若全局变量gvfTaskSwitchHook不为NULL,说明已经挂接了函数,执行gvfTaskSwitchHook全局变量就相当于执行了被挂接的函数。

 

下面在任务调度函数MDS_TaskSched中增加了任务切换钩子功能,将打印任务切换的函数挂接到此钩子上即可打印出任务切换过程。

00352  void MDS_TaskSched(void)

00353  {

00354      M_TCB* pstrTcb;

00355  

00356      

00357      MDS_TaskDelayTabSched();

00358  

00359      

00360      pstrTcb MDS_TaskReadyTabSched();

00361  

00362  #ifdef MDS_INCLUDETASKHOOK

00363  

00364      

00365      if((VFHSWT)NULL != gvfTaskSwitchHook)

00366      {

00367          

00368          if(pstrTcb != gpstrCurTcb)

00369          {

00370              gvfTaskSwitchHook(gpstrCurTcb, pstrTcb);

00371          }

00372      }

00373  

00374  #endif

00375  

00376      

00377      MDS_TaskSwitch(pstrTcb);

00378  }

000362行,若定义了MDS_INCLUDETASKHOOK宏,才执行任务切换钩子函数。其它与任务切换钩子相关的代码也被该宏包含了,若不想使用钩子功能,不定义该宏即可。

00365行,若已添加了钩子函数,则走下面分支。

00368行,若切换前的任务与切换后的任务是同一个任务,则不执行任务切换钩子函数,只有在发生不同任务切换时才调用任务切换钩子函数。

00370行,执行钩子函数。

 

除了MDS_TaskSched函数,在MDS_TaskReadySched函数里也执行了任务调度功能,也需要增加362~374行的内容。

任务切换钩子函数gvfTaskSwitchHook在任务调度函数调度完成后运行,但它还是处于调度的中断中,因此不能将耗时长的函数挂接到任务切换钩子函数上。

钩子函数可以在代码运行时动态挂接,不需要更改代码,而且,如果需要更改钩子函数执行的功能时,只需要更改挂接到钩子上的函数就可以实现,非常方便。

钩子变量可以有不同的指针类型,但需要与被挂接的函数指针类型保持一致。

 

被挂接函数的打印信息包括了切换时刻,包括了从哪个任务切换到了哪个任务的信息:

00072  void TEST_TaskSwitchPrint(M_TCB* pstrOldTcb, M_TCB* pstrNewTcb)

00073  {

00074      DEV_PutStrToMem((U8*)"\r\nTask %s ---> Task %s! Tick is: %d",

00075                      pstrOldTcb->pucTaskName, pstrNewTcb->pucTaskName,

00076                      MDS_SystemTickGet());

00077  }

上面所使用的pucTaskName结构是任务名指针,它里面存放的是任务名字符串的指针。为了区分每个任务,本小节在TCB里增加了任务名指针pucTaskName这个结构。

typedef struct m_tcb

{

    STACKREG strStackReg;           

    M_TCBQUE strTcbQue;             

    M_TCBQUE strDelayQue;           

    U8* pucTaskName;                

    U32 uiTaskFlag;                 

    U8 ucTaskPrio;                  

    M_TASKOPT strTaskOpt;           

    U32 uiStillTick;                

}M_TCB;

在创建任务时,保存有任务名的字符串的指针作为一个入口参数被传递给MDS_TaskCreate函数,该指针会被赋给TCB中的pucTaskName变量。如果创建的任务没有名称,则任务名参数需要将NULL传递给MDS_TaskCreate函数。

MDS_TaskCreate函数的原型如下,代码改动较小,不再详细介绍。

M_TCB* MDS_TaskCreate(U8* pucTaskName, VFUNC vfFuncPointer, U8* pucTaskStack,

                      U32 uiStackSize, U8 ucTaskPrio, M_TASKOPT* pstrTaskOpt);

 

下面我们来看看验证本节功能的测试函数,测试函数TEST_TestTask1~TEST_TestTask3,循环执行打印、运行、延迟这3个过程,它们会在运行过程中不断的发生切换,本节新增加的钩子函数会将这些切换过程打印出来。

00017  void TEST_TestTask1(void)

00018  {

00019      while(1)

00020      {

00021          DEV_PutStrToMem((U8*)"\r\nTask1 is running! Tick is: %d",

00022                          MDS_SystemTickGet());

00023  

00024          DEV_DelayMs(2000);

00025  

00026          (void)MDS_TaskDelay(150);

00027      }

00028  }

00035  void TEST_TestTask2(void)

00036  {

00037      while(1)

00038      {

00039          DEV_PutStrToMem((U8*)"\r\nTask2 is running! Tick is: %d",

00040                          MDS_SystemTickGet());

00041  

00042          DEV_DelayMs(1000);

00043  

00044          (void)MDS_TaskDelay(100);

00045      }

00046  }

00053  void TEST_TestTask3(void)

00054  {

00055      while(1)

00056      {

00057          DEV_PutStrToMem((U8*)"\r\nTask3 is running! Tick is: %d",

00058                          MDS_SystemTickGet());

00059  

00060          DEV_DelayMs(5000);

00061  

00062          (void)MDS_TaskDelay(500);

00063      }

00064  }

TEST_TestTask1函数不使用任务选项参数,默认为ready态。TEST_TestTask2函数使用ready态的任务选项参数。TEST_TestTask3函数使用任务选项参数,需要先delay 2000ticks

 

00014  void MDS_RootTask(void)

00015  {

00016      M_TASKOPT strOption;

00017  

00018      

00019      DEV_SoftwareInit();

00020  

00021      

00022      DEV_HardwareInit();

00023  

00024      

00025      (void)MDS_TaskCreate((U8*)"Test1", (VFUNC)TEST_TestTask1, gaucTask1Stack,

00026                           TASKSTACK, 2, (M_TASKOPT*)NULL);

00027  

00028      

00029      strOption.ucTaskSta TASKREADY;

00030      (void)MDS_TaskCreate((U8*)"Test2", (VFUNC)TEST_TestTask2, gaucTask2Stack,

00031                           TASKSTACK, 3, &strOption);

00032  

00033      

00034      strOption.ucTaskSta TASKDELAY;

00035      strOption.uiDelayTick 2000;

00036      (void)MDS_TaskCreate((U8*)"Test3", (VFUNC)TEST_TestTask3, gaucTask3Stack,

00037                           TASKSTACK, 1, &strOption);

00038  

00039      (void)MDS_TaskDelay(10000);

00040  

00041  #ifdef MDS_INCLUDETASKHOOK

00042  

00043      

00044      MDS_TaskSwitchHookDel();

00045  

00046  #endif

00047  

00048      (void)MDS_TaskDelay(DELAYWAITFEV);

00049  }

00019行,DEV_SoftwareInit函数里会使用钩子添加函数MDS_TaskSwitchHookAdd将打印切换过程的TEST_TaskSwitchPrint函数添加到钩子变量gvfTaskSwitchHook上。

00039行,root任务delay 10000ticks,在这1000ticks时间内,3个测试任务将不断的发生任务切换,钩子函数会打印出这些切换过程。

00044行,删除任务切换钩子函数,此后将不再打印任务切换过程。

 

root任务具有最高的优先级,当root任务进入delay状态时,操作系统发生第一次任务切换,按照任务状态和任务优先级,我们应该会看到任务切换钩子函数打印出root任务切换到TEST_TestTask1任务的信息,然后TEST_TestTask1任务开始运行,输出打印信息。TEST_TestTask1任务运行200ticks后,调用MDS_TaskDelay函数进入delay状态,这时候应该是切换到TEST_TestTask2任务,我们应该可以看到TEST_TestTask1任务切换到TEST_TestTask2任务的信息。随后TEST_TestTask2任务开始运行,输出TEST_TestTask2任务的打印。此后这2个任务不断的交替运行,当这2个任务都处于delay状态时,idle任务开始运行,将前面切换过程的信息从内存打印到串口上。

当系统运行到2000 ticks时,TEST_TestTask3任务delay时间耗尽,开始运行,它比其它2个任务具有更高的优先级,会抢占正在运行的任务,我们应该可以看到任务切换到了TEST_TestTask3,并输出了TEST_TestTask3的打印信息。此后这3个任务不断的交替运行。

当系统运行到10000 ticks时,任务切换钩子函数被删除,此后我们不会再看到任务切换过程的打印,只能看到任务每次循环的打印。

第4章第4节 <wbr>任务切换钩子函数

图 46  带有任务切换过程的打印

46是一部分串口打印数据,读者可从网站下载视频,观看全部数据的打印过程,可以看到实际打印输出的结果与我们推断的结果是一致的。

 

    我在串口工具中将本节的打印数据捕获了下来,保存到4.4.txt文件里,并使用VC编写了一个小工具,该工具可以解析4.4.tet文件里任务切换过程的数据,描绘出任务切换的过程,使不同任务之间的切换过程看的更直观些。

第4章第4节 <wbr>任务切换钩子函数

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值