第4章第3节 实时事件触发的实时抢…

来源:互联网 发布:中国程序员联合开发网 编辑:程序博客网 时间:2024/06/16 09:55

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

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

本节用户代码在打印输出方面做了较大的修改,从本节开始,串口打印功能不再由任务实时向串口打印,而是由任务先将字符串打印到内存,然后使用一个低优先级的任务从内存中取出字符串打印到串口。串口是一个低速率的外设,9600的波特率差不多1ms打印1个字符,打印几十个字符的字符串时就需要几十ms,如果采用原有的打印方式,在打印字符串的过程中可能会发生任务切换,如果切换后的任务也在向串口打印字符串,那么这2个任务的字符就会混在一起,串口输出的字符发生混乱,得不到我们预期的输出结果。如果采用新的打印方式,每个任务需要从内存申请一个消息缓冲用来存放打印的字符,申请消息缓冲的过程非常快,可以将这一过程用中断锁住,防止其它任务干扰。消息缓冲对于任务来说,它是私有的,任务将字符串打印到消息缓冲的过程不会受任务切换的影响。当任务向消息缓冲打印完成后,先锁中断,然后将消息缓冲挂入消息队列,再解锁中断。消息打印任务会不断的查询消息队列,从队列中获取消息缓冲,将消息缓冲里面的数据打印到串口。向串口打印消息采用的是中断方式,消息打印任务将每条消息的第一个字符输出到串口,消息中剩下的其余字符会由上个字符触发的中断打印到串口,直至打印完最后一个字符。由于向串口打印数据是由同一个任务完成的,因此不会存在打印字符混乱的情况。

 第4章第3节 <wbr>实时事件触发的实时抢占调度(三)

 

图 43  打印消息处理过程

我们可以定义一个消息缓冲池,所有的消息缓冲都在这里:

BUFPOOL gstrBufPool;

展开里面的结构体:

typedef struct bufpool

{

    M_CHAIN strFreeList;                

    MSGBUF astrBufPool[BUFPOOLNUM];     

}BUFPOOL;

typedef struct msgbuf

{

    M_CHAIN strChain;                   

    U8 ucLength;                        

    U8 ucCounter;                       

    U8 aucBuf[MSGBUFLEN];               

}MSGBUF;

其中strFreeList是空闲消息链表,初始化消息缓冲池的时候将所有消息缓冲挂接到这个链表上,申请消息缓冲时从该链表就可以获取到空闲的消息缓冲。当消息缓冲使用完毕后需要再挂接到这个链表上,可以继续重复使用。strChain是每个消息缓冲挂接到空闲消息链表的节点,ucLength中存储的是消息的长度,ucCounter中存储的是消息收发时的计数,aucBuf是存放消息的数组。

任务向消息缓冲填充完字符串之后,需要将消息挂入队列,目前的队列结构就是一个链表,后续章节我们会再补充一些东西进来。

typedef struct m_que    

{

    M_CHAIN strChain;   

}M_QUE;

 

关于消息打印部分的细节就不做过多介绍了,请读者自行参考代码。

 

在学习C语言时,我们都使用过printf函数,这个函数可以根据个人需要,很灵活的向显示器终端打印数据,比如,我们可以按照下面的方式输出:

printf("Wanlix Mindows");

printf("%d, %c", i, j);

不知道你注意过没有,printf函数的参数个数是可变的,上面的第一个例子只有1个参数,第二个例子有3个参数,这点与我们一般所使用的函数是不同的。在Mindows中,我们仿造printf函数,将向内存打印的函数DEV_PutStrToMem也编写成一个参数可变的函数,下面我们一起来看看这是怎么实现的。

参数可变的函数原型定义为:

void DEV_PutStrToMem(U8* pvStringPt, ...);

其中比较特别的是“...”,这三个点表示省略的参数。可变参数函数的原理非常简单,可变参数函数的参数不像我们前面讲过的那样,使用R0~R3寄存器传递参数,而是直接使用堆栈传递参数,而且这些参数都是连在一起存放的,而函数原型中第一个参数是固定的,我们可以获取到第一个参数的地址&pvStringPt,然后将这个地址加4就可以得到第二个参数的地址,再加4就是第三个参数的地址,依次类推,这样就可以获取到任意多的参数地址了,有了参数地址那么获取参数的内容也就不成问题了。还有一个问题要解决,DEV_PutStrToMem函数是怎么知道它究竟有多少个参数的呢?注意到参数里的“%”号了么,每个%号对应一个参数,我们只需要查找%号的个数就可以确定可变参数的个数,再将每个可变参数转换为对应的%号后面的格式就可以实现DEV_PutStrToMem函数的功能了。

 第4章第3节 <wbr>实时事件触发的实时抢占调度(三)

图 44  可变参数函数

可变参数函数的原型可以有多种形式,但必须要保证:

1.必须有第一个参数,通过这个参数才能获取到参数存放在栈中的首地址,后面的参数才是可变的。

2.在设计可变参数时需要能体现出可变参数的个数,如上面查找第一个参数中%号的方法,或者将第一个参数定义为可变参数的个数,或者其它方式。

C语言标准头文件stdarg.h里面已经为可变函数定义了几个宏,使用这些宏也可以实现可变参数函数,原理都一样, 细节不再介绍了。

 

本节内容已经介绍完毕,设计一下测试函数来验证本节新增加的功能。本节共有4个测试函数TEST_TestTask1~TEST_TestTask4,每个函数的结构非常相似,循环执行打印、运行、延迟这3个过程。

00020  void TEST_TestTask1(void)

00021  {

00022      while(1)

00023      {

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

00025                          MDS_SystemTickGet());

00026  

00027          DEV_DelayMs(2000);

00028  

00029          (void)MDS_TaskDelay(150);

00030      }

00031  }

00038  void TEST_TestTask2(void)

00039  {

00040      while(1)

00041      {

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

00043                          MDS_SystemTickGet());

00044  

00045          DEV_DelayMs(1000);

00046  

00047          (void)MDS_TaskDelay(100);

00048      }

00049  }

00056  void TEST_TestTask3(void)

00057  {

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

00059  

00060      

00061      (void)MDS_TaskWake(gpstrTask4Tcb);

00062  

00063      while(1)

00064      {

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

00066                          MDS_SystemTickGet());

00067  

00068          DEV_DelayMs(5000);

00069  

00070          (void)MDS_TaskDelay(500);

00071      }

00072  }

00079  void TEST_TestTask4(void)

00080  {

00081      while(1)

00082      {

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

00084                          MDS_SystemTickGet());

00085  

00086          DEV_DelayMs(1000);

00087  

00088          (void)MDS_TaskDelay(1000);

00089      }

00090  }

不同的是TEST_TestTask1任务优先级为4,创建时不使用任务选项参数,默认为ready态。TEST_TestTask2任务优先级为6,创建时使用任务选项参数,为ready态。TEST_TestTask3任务优先级为2,创建时使用任务选项参数,为delay态,延迟2000ticksdelay态结束后会使用MDS_TaskWake函数唤醒TEST_TestTask4任务。TEST_TestTask4任务优先级为1,创建时使用任务选项参数,为delay态,永久延迟。

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((VFUNC)TEST_TestTask1, gaucTask1Stack, TASKSTACK, 4,

00026                           (M_TASKOPT*)NULL);

00027  

00028      

00029      strOption.ucTaskSta TASKREADY;

00030      (void)MDS_TaskCreate((VFUNC)TEST_TestTask2, gaucTask2Stack, TASKSTACK, 6,

00031                           &strOption);

00032  

00033      

00034      strOption.ucTaskSta TASKDELAY;

00035      strOption.uiDelayTick 2000;

00036      (void)MDS_TaskCreate((VFUNC)TEST_TestTask3, gaucTask3Stack, TASKSTACK, 2,

00037                           &strOption);

00038  

00039      

00040      strOption.ucTaskSta TASKDELAY;

00041      strOption.uiDelayTick DELAYWAITFEV;

00042      gpstrTask4Tcb MDS_TaskCreate((VFUNC)TEST_TestTask4, gaucTask4Stack, TASKSTACK,

00043                                     1, &strOption);

00044  

00045      (void)MDS_TaskDelay(DELAYWAITFEV);

00046  }

如果单从任务优先级来看,应该是TEST_TestTask4任务最先运行,但TEST_TestTask4任务处于永久delay状态,不能运行。剩下的优先级最高的任务是TEST_TestTask3,但TEST_TestTask3任务也处于delay状态,需要延迟2000ticks后才能重新参与调度。TEST_TestTask1TEST_TestTask2任务尽管使用了不同的任务参数,但都处于ready态,TEST_TestTask1的优先级高,先运行TEST_TestTask1,在TEST_TestTask1任务处于delay态时就会运行TEST_TestTask2任务。在前2000ticksTEST_TestTask1TEST_TestTask2任务会交替运行,在2000ticks时,TEST_TestTask3任务delay时间耗尽,参与调度,应该可以看到TEST_TestTask3任务会打断其它任务开始运行。在TEST_TestTask3运行之后它立刻唤醒了TEST_TestTask4任务,应该还可以看到在TEST_TestTask3任务运行后立刻被TEST_TestTask4任务打断了,此后这4个任务按照优先级的调度方式交替运行。

 第4章第3节 <wbr>实时事件触发的实时抢占调度(三)

图 45  增加delay状态的4个任务运行结果

 从图45中我们可以看到输出结果与我们设想的是一样的,TEST_TestTask1任务最先开始运行,经过200ticks进入delay状态,TEST_TestTask2任务开始运行,此后TEST_TestTask1TEST_TestTask2任务交替运行。在2000ticks的时候TEST_TestTask3delay时间耗尽,抢占其它任务开始运行,然后又被TEST_TestTask4任务抢占,之后4个任务参与任务调度。

读者可以从网站下载视频观看串口输出的过程。在视频中我们看到要么是在一段时间内没有字符打印出来,要么是几个任务的字符一起打印出来。这是因为打印字符的任务处于最低优先级,只有当其它任务都处于delay状态时它才可以运行,将内存中的字符打印到串口上来。

我们在这4个函数里使用了DEV_DelayMs函数模拟任务的业务功能,该函数会连续运行几秒时间,这段时间内的CPU占有率是100%,因此字符打印到串口的时刻相比任务将字符打印到内存的时刻有较大的延迟,如果CPU占有率较低的话,我们可以看到几乎是实时的打印数据。

阅读全文
0 0
原创粉丝点击