第二章:时钟节拍

时钟节拍

书中用了比较通俗的一句话解释了时钟节拍:CPU以一定的频率进行中断,可以看成操作系统的心跳。利用时间节拍可以做到一些任务的时间管理,延时、定时、超时检测、时间片轮转调度。
设问:常规的检查每个任务的延时是否完成,在每个时钟节拍到达的时侯要检查每个任务?这样比较消耗系统资源,UCOSIII给出比较好的解决办法。。。

2.1系统节拍中断服务程序

这里的时钟频率大致设置为10hz-1000hz,如果这个频率太低就会导致有时候任务不能够及时的就绪(任务的就绪靠的是每个时钟节拍的检测,比如始终节拍是1s则当一个任务明明过了0.5s就能处于就绪态、等待运行,但是由时钟节拍的原因,在1s钟的时候才被设置为就绪态),同时这个频率也不能太高,太高系统中断过于频繁,内核负担加重。在os_cfg_app.c文件中能够设置这个值

   #define OS_CFG_TICK_RATE_HZ   1000

每次产生时钟节时候就会进入一个ISR(Interrupt Serive Routine),在stm32中选用的是系统滴答定时器systick,所以每次时钟节拍到来的时候就会执行以下函数

    void SysTick_Handler(void)
   {
      OSIntEnter();//中断嵌套层数向量加一
      OSTimeTick();
      OSIntExit();//中断嵌套层数向量减一
   }

这里主要就是OSTimeTick();这个函数,这个函数里面首先执行了一个
1:钩子函数OSTimeTickHook();
钩子函数简单来说就是自己要编写的函数,写在这里是希望某些函数以一个时钟节拍为周期而执行。
若需要使用这个钩子函数首先要把OS_CFG_APP_HOOKS_EN 这个宏置为1。其次初始化一个指向目标函数的指针。

     void OSTimeTickHook(void)
    {
       #if OS_CFG_APP_HOOKS_EN >0u
       if(OS_AppTimeTickHookPtr!=0)
       (*OS_AppTimeTickHookPtr)();
       #endif
     }

从代码很容易看出如果定义的宏OS_CFG_APP_HOOKS_EN不为1那么就会执行指向OS_AppTimeTickHookPtr()的函数。
所以当你想使用这个以一个时钟节拍为周期的钩子函数(假设函数名为My_Hook_Handler())初始化语句如下:
OS_AppTimeTickHoolPtr=(OS_APP_HOOK_TCB)My_Hook_Handler;
紧接着这个函数给时钟阶节拍任务发送了一个信号量(注意这里不是执行时钟节拍任务,相当于只是置了一个标志位,后面会做解释
2:向时钟节拍任务OSTickTaskTCB发送信号量;
此时是系统滴答定时器执行的第二个步骤,按道理本来要执行时钟节拍相关的任务(),但是可能这个任务里面要做的事情比较复杂。于是UCOSIII做了一个优化处理,把时钟节拍的处理当成一个任务,而不再ISR里面执行.原则上ISR里面要做时间比较短的事情。所以说如果现在有一个时间要求比较长但是又相对比较重要的时候,可以在ISR里面发信号给当前优先级较高的任务,要处理的事情放在这个任务中,并把任务挂起,等待ISR的信号量。这样就实现了中断级到任务级的转换!
3:还有一些时间片轮转调度和定时器的相关任务,暂时不赘述。。。;

2.2节拍任务处理时间相关事务

衔接上文的ISR发出的信号量,该信号量是发送给下面这个函数:OSTickTask(void *p_arg);
这个函数调用了OSTaskSemPend();

 void OS_TickTask(void *p_parg)
 {
   OS_ERR err;
  CPU_TS  ts;
  p_arg=p_arg;//undo the warning
  whie(DEF_ON)
  {
  (void)OSTaskSePend(
  (OS_TICK)0,
  (OA_OPT)OS_OPT_PEND_BLOCKING,
  (CPU_TS*)&ts,
  (OS_ERR*)&err;
  )
  if(err==OS_ERR_NONE)//系统是否在运行
      {
      if(OSRunning==OS_STATR_OD_RUNNING)
       {
     OS_TickListUpdate();
       }
      }
  } 
 }  

一般在UCOSIII中第一个参数都是操作的对象,最后一个参数都是错误信息。上述代码的OSTaskSemPend();函数就是这样的规则。

2.2.1节拍列表更新

详细代码见OS_TickListUpdate(void);这个函数。

2.2.2节拍列表

设问:UcosIII怎么能够快速的查找到已经到期的任务?
首先UcosIII中会定义一个全局变量数组OSCfg_TickWheel[OS_CFG_TICK_WHEEL_SIZE],用于存放延时、超时检测等全部信息,用链表的形式把他们都串起来,就得到了一个节拍列表Tick_List.
在这里插入图片描述
上图为OS_CfgTickWheel[OS_CFG_TICK_SIZE]这个全局变量数组的数据结构,每个元素后面有一系列TCB任务的结构体变量,这些任务是由双向链表进行连接的。接下来分别对上述任务控制块中和TickList里面有关的元素进行介绍。
1)TickNextPtr、TickPrevPtr共同组成双向链表,此双向链表对应挂载在数组的某一个元素上。双向链表中的越后面的任务越晚到期。
2)TickCtrMatch 该变量和OSTickCtr相匹配的时候,任务退出TickList.
3) TickRemain 任务脱离TickList锁剩下的时钟节拍,等于OSTickCtr -TickCtrMatch
4) TaskState 任务状态 表征任务处于什么状态,
5) TS 纪录上一个任务的时间戳
6) PendStatus 等待的相关状态
7) TickSpokePtr 标记了任务在全局数组里面的哪个元素里面
8) PendOn 描述了任务等待的对象,比如事件标志、二值信号量、等待消息队列

2.2.3哈希算法检测到期任务

当一个任务到来的时候要进行超时检测和延时检测,内核会把这些任务插入到这个全局变量数组的各个元素中去,为了能够快速的检测到期的任务,这里采用了一个方法在任务插入进数组的时候就进行了分类。OSCtr_TickWheelSize是一个const修饰的一个变量。根据任务的TickCtrMatch对OSCtr_TickWheelSize取余数得到该任务应该插入到数组的哪个元素里面。每次检查的任务最多就是全部任务的1/OSCtr_TickWheelSize这么多个。OSCtr_TickWheelSize个数越大,检查的次数就越小,但那个全局变量数组就会越大。这个看各个单片机自己的存储空间而定。数据分类方法就是对数据取余运算。
具体实现代码如下:

              spoke=(OS_TICK_SPOKE_IX)(p_tcb->TickCtrMatch%OSTickWheelSize);
              p_spoke=&OSCfg_TickWheel[spoke];

2.3总结

CPU以一定的时间进行中断就是时钟节拍。当时钟节拍到来的时候,先给时钟节拍任务发送一个信号量(由于此时是高频率的中断中,所以是给的信号量而不是直接在这个中断服务函数里面执行时钟节拍任务)。时钟节拍任务收到这个信号量后更新TickList。当任务插入代TickList里面的时候按照哈希取余运算进行分类。余数相同的放在特定数组的同一个元素里面,并用双向链表把这些具有相同余数的任务串起来。同时系统会计算OSTickCtr对常数的余数,并根据此余数找到数组中对应的元素,并在此元素中的双向链表找到对应的任务,进行判断和相关操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章