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

本文介绍了如何使用任务切换钩子函数来记录实时操作系统中任务间的切换过程。通过定义全局函数指针并将其初始化为空,可在需要时挂载或取消挂载特定函数。文章详细展示了如何在任务调度过程中插入钩子函数,以便于跟踪和调试多任务系统。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


目前更新到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>任务切换钩子函数

<think>好的,我现在需要处理用户关于HTML实体&nbsp;的提问。首先,用户想了解它的含义和使用场景。根据用户提供的引用内容,我需要整理相关信息,确保回答准确且符合格式要求。 首先,&nbsp;是HTML中的非换行空格实体,对应unicode的U+00A0。它的主要作用是显示不会被浏览器合并或忽略的空格。这点在引用1、3、4中都有提到,特别是在引用4的例子里,使用多个&nbsp;可以在网页中显示多个空格,而普通空格会被合并成一个。 接下来要考虑使用场景。根据引用2,&nbsp;可以防止空元素折叠,比如保持带有边框的div的高度。引用4的例子也说明了在需要多个连续空格时使用。另外,引用3提到在需要输入特殊字符如&时使用实体,类似地,&nbsp;用于空格。所以使用场景包括保持布局、占位符和特殊格式。 需要注意的是,用户可能混淆其他空格实体,比如普通的空格和全角空格,但根据引用内容,&nbsp;的特殊性在于其非换行和防合并特性。同时,要提醒用户不要滥用,以免影响排版。 现在要组织回答,确保使用正确的LaTeX格式,内部数学表达式用$...$,独立公式用$$...$$,但这个问题可能不涉及数学公式,所以重点放在清晰的结构和引用上。同时生成相关问题,考虑用户可能进一步想知道与其他空格的区别、替代方法或最佳实践。 最后检查引用是否正确标注,每个引用末尾加上对应的标识,比如[^1]、[^2]等,并确保没有使用markdown格式,段落用空行分隔。回答要使用中文,语言自然,结构清晰。</think>### HTML实体`&nbsp;`的含义与使用场景 `&nbsp;`是HTML中的**非换行空格实体**(Non-Breaking Space),对应Unicode字符`U+00A0`。其核心作用是强制浏览器显示一个空格,且该空格**不会被合并或忽略**,同时阻止在此处自动换行[^3][^4]。 --- #### 主要特性 1. **防空格合并** 普通连续空格(ASCII空格,`U+0020`)在HTML中默认会被合并为一个。例如: ```html <p>知 识</p> <!-- 网页显示为"知 识" --> <p>知&nbsp;&nbsp;&nbsp;&nbsp;识</p> <!-- 显示为"知 识" --> ``` 使用`&nbsp;`可保留多个空格。 2. **防元素折叠** 空元素(如无内容的`<div>`)可能因内容缺失导致布局塌陷。例如: ```html <div style="height: 20px; border: 1px solid black">&nbsp;</div> ``` 添加`&nbsp;`可维持元素高度。 3. **禁止换行** 文本中的`&nbsp;`会强制其前后内容保持在同一行,避免因换行破坏排版(如日期“2023年10月1日”)[^3]。 --- #### 典型应用场景 | 场景 | 示例 | |---------------------|----------------------------------------------------------------------| | 固定多空格 | 表格中对齐、诗歌格式排版 | | 占位符 | 空按钮、占位div防止折叠 | | 特殊格式保留 | 代码示例中模拟缩进 | | 非换行内容 | 单位与数值的组合(如`10&nbsp;kg`) | --- #### 注意事项 - **避免滥用**:过多使用可能影响响应式布局,建议优先通过CSS(如`white-space: pre`)控制空格行为。 - **与其他空格区别**:`&nbsp;`为“硬空格”,而CSS的`margin/padding`更适合布局调整。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值