目前更新到5.3节,请在http://dl.dbank.com/c02ackpwp6下载5.3节的全部文档
本节源代码请在http://dl.dbank.com/c0ddwgf0k5下载
第4节 任务切换钩子函数
上节,我们引入了任务的delay态,通过最后例子的打印可以看到任务在交替运行,但这个打印只发生在每个任务每次循环的开始,看不到中间运行过程中任务的切换过程。
本小节将引入任务切换钩子函数,输出任务切换过程的打印信息。
钩子函数正如其名,它像钩子一样可以将函数挂在它上面,一旦它开始运行,它就会引发挂在它上面的函数也开始运行。按照这个思路,我们可以将一个钩子函数放到任务切换过程中,当我们需要输出任务切换过程的打印时,只需要将相关的打印函数挂到这个钩子函数上就可以了。
钩子函数的功能是使用一个指向函数的指针型全局变量实现的,在使用钩子函数前,需要使用钩子添加函数将需要执行的函数挂接到这个全局变量上,这个全局变量此时等效于被挂接的函数,因此运行这个全局变量就相当于是运行被挂接的函数了,来看代码:
定义一个任务切换的钩子全局变量:
VFHSWT
其中VFHSWT是被挂接函数的类型:
typedef
被挂接的函数原型为:
void
可以看到定义的全局变量gvfTaskSwitchHook与函数TEST_TaskSwitchPrint类型是完全一致的,它们都是同一种类型的函数指针。
在使用钩子函数前需要使用钩子初始化函数MDS_TaskHookInit将全局变量gvfTaskSwitchHook初始化为NULL,以表明钩子变量没有挂接函数,不能运行。
00340
00341
00342
00343
00344
添加钩子函数时,将被添加函数TEST_TaskSwitchPrint作为参数传递给钩子添加函数MDS_TaskSwitchHookAdd,MDS_TaskSwitchHookAdd(TEST_TaskSwitchPrint),执行的过程就是将被添加函数的指针赋给全局变量,使全局变量等效于被添加的函数。
00351
00352
00353
00354
删除钩子函数时,就是将全局变量置为NULL,撇清与被添加函数的关系。
00361
00362
00363
00364
使用钩子函数时,若全局变量gvfTaskSwitchHook不为NULL,说明已经挂接了函数,执行gvfTaskSwitchHook全局变量就相当于执行了被挂接的函数。
下面在任务调度函数MDS_TaskSched中增加了任务切换钩子功能,将打印任务切换的函数挂接到此钩子上即可打印出任务切换过程。
00352
00353
00354
00355
00356
00357
00358
00359
00360
00361
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371
00372
00373
00374
00375
00376
00377
00378
000362行,若定义了MDS_INCLUDETASKHOOK宏,才执行任务切换钩子函数。其它与任务切换钩子相关的代码也被该宏包含了,若不想使用钩子功能,不定义该宏即可。
00365行,若已添加了钩子函数,则走下面分支。
00368行,若切换前的任务与切换后的任务是同一个任务,则不执行任务切换钩子函数,只有在发生不同任务切换时才调用任务切换钩子函数。
00370行,执行钩子函数。
除了MDS_TaskSched函数,在MDS_TaskReadySched函数里也执行了任务调度功能,也需要增加362~374行的内容。
任务切换钩子函数gvfTaskSwitchHook在任务调度函数调度完成后运行,但它还是处于调度的中断中,因此不能将耗时长的函数挂接到任务切换钩子函数上。
钩子函数可以在代码运行时动态挂接,不需要更改代码,而且,如果需要更改钩子函数执行的功能时,只需要更改挂接到钩子上的函数就可以实现,非常方便。
钩子变量可以有不同的指针类型,但需要与被挂接的函数指针类型保持一致。
被挂接函数的打印信息包括了切换时刻,包括了从哪个任务切换到了哪个任务的信息:
00072
00073
00074
00075
00076
00077
上面所使用的pucTaskName结构是任务名指针,它里面存放的是任务名字符串的指针。为了区分每个任务,本小节在TCB里增加了任务名指针pucTaskName这个结构。
typedef
{
}M_TCB;
在创建任务时,保存有任务名的字符串的指针作为一个入口参数被传递给MDS_TaskCreate函数,该指针会被赋给TCB中的pucTaskName变量。如果创建的任务没有名称,则任务名参数需要将NULL传递给MDS_TaskCreate函数。
MDS_TaskCreate函数的原型如下,代码改动较小,不再详细介绍。
M_TCB*
下面我们来看看验证本节功能的测试函数,测试函数TEST_TestTask1~TEST_TestTask3,循环执行打印、运行、延迟这3个过程,它们会在运行过程中不断的发生切换,本节新增加的钩子函数会将这些切换过程打印出来。
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
TEST_TestTask1函数不使用任务选项参数,默认为ready态。TEST_TestTask2函数使用ready态的任务选项参数。TEST_TestTask3函数使用任务选项参数,需要先delay
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00019行,DEV_SoftwareInit函数里会使用钩子添加函数MDS_TaskSwitchHookAdd将打印切换过程的TEST_TaskSwitchPrint函数添加到钩子变量gvfTaskSwitchHook上。
00039行,root任务delay
00044行,删除任务切换钩子函数,此后将不再打印任务切换过程。
root任务具有最高的优先级,当root任务进入delay状态时,操作系统发生第一次任务切换,按照任务状态和任务优先级,我们应该会看到任务切换钩子函数打印出root任务切换到TEST_TestTask1任务的信息,然后TEST_TestTask1任务开始运行,输出打印信息。TEST_TestTask1任务运行200个ticks后,调用MDS_TaskDelay函数进入delay状态,这时候应该是切换到TEST_TestTask2任务,我们应该可以看到TEST_TestTask1任务切换到TEST_TestTask2任务的信息。随后TEST_TestTask2任务开始运行,输出TEST_TestTask2任务的打印。此后这2个任务不断的交替运行,当这2个任务都处于delay状态时,idle任务开始运行,将前面切换过程的信息从内存打印到串口上。
当系统运行到2000
当系统运行到10000
图46是一部分串口打印数据,读者可从网站下载视频,观看全部数据的打印过程,可以看到实际打印输出的结果与我们推断的结果是一致的。